From cf3554ab2f627018206987a1cdc2c2ed715867a7 Mon Sep 17 00:00:00 2001 From: Filip Navara Date: Fri, 20 Sep 2024 23:01:34 +0200 Subject: [PATCH] Add AOT sampling profiler --- .../nativeaot/Runtime/StackFrameIterator.h | 1 + .../nativeaot/Runtime/eventpipe/ep-rt-aot.cpp | 72 ++++++++++++++++++- src/coreclr/nativeaot/Runtime/thread.h | 1 + 3 files changed, 72 insertions(+), 2 deletions(-) diff --git a/src/coreclr/nativeaot/Runtime/StackFrameIterator.h b/src/coreclr/nativeaot/Runtime/StackFrameIterator.h index f174edd4c473b2..c6ecff14506c35 100644 --- a/src/coreclr/nativeaot/Runtime/StackFrameIterator.h +++ b/src/coreclr/nativeaot/Runtime/StackFrameIterator.h @@ -53,6 +53,7 @@ class StackFrameIterator bool IsActiveStackFrame(); bool GetHijackedReturnValueLocation(PTR_OBJECTREF * pLocation, GCRefKind * pKind); void SetControlPC(PTR_VOID controlPC); + PTR_VOID GetControlPC() { return m_ControlPC; } static bool IsValidReturnAddress(PTR_VOID pvAddress); diff --git a/src/coreclr/nativeaot/Runtime/eventpipe/ep-rt-aot.cpp b/src/coreclr/nativeaot/Runtime/eventpipe/ep-rt-aot.cpp index e54156891593f3..a539cf3b9c98c5 100644 --- a/src/coreclr/nativeaot/Runtime/eventpipe/ep-rt-aot.cpp +++ b/src/coreclr/nativeaot/Runtime/eventpipe/ep-rt-aot.cpp @@ -45,8 +45,29 @@ ep_rt_aot_walk_managed_stack_for_thread ( ep_rt_thread_handle_t thread, EventPipeStackContents *stack_contents) { - // NativeAOT does not support getting the call stack - return false; + STATIC_CONTRACT_NOTHROW; + EP_ASSERT (thread != NULL); + EP_ASSERT (stack_contents != NULL); + + StackFrameIterator frameIterator(thread, thread->GetTransitionFrameForSampling()); + + while (frameIterator.IsValid()) + { + frameIterator.CalculateCurrentMethodState(); + + // Get the IP. + uintptr_t control_pc = (uintptr_t)frameIterator.GetControlPC(); + + if (control_pc != 0) + { + // Add the IP to the captured stack. + ep_stack_contents_append (stack_contents, control_pc, NULL); + } + + frameIterator.Next(); + } + + return true; } bool @@ -61,6 +82,53 @@ ep_rt_aot_sample_profiler_write_sampling_event_for_threads ( ep_rt_thread_handle_t sampling_thread, EventPipeEvent *sampling_event) { + STATIC_CONTRACT_NOTHROW; + EP_ASSERT (sampling_thread != NULL); + + ThreadStore *thread_store = GetThreadStore (); + + // Check to see if we can suspend managed execution. + if (thread_store->GetSuspendingThread () != NULL) + return; + + // Actually suspend managed execution. + thread_store->LockThreadStore (); + thread_store->SuspendAllThreads (false); + + EventPipeStackContents stack_contents; + EventPipeStackContents *current_stack_contents; + current_stack_contents = ep_stack_contents_init (&stack_contents); + + EP_ASSERT (current_stack_contents != NULL); + + // Walk all managed threads and capture stacks. + FOREACH_THREAD (target_thread) + { + ep_stack_contents_reset (current_stack_contents); + + // Walk the stack and write it out as an event. + if (ep_rt_aot_walk_managed_stack_for_thread (target_thread, current_stack_contents) && !ep_stack_contents_is_empty (current_stack_contents)) { + // Set the payload. + // TODO: We can actually detect whether we are in managed or external code but does it matter?! + uint32_t payload_data = EP_SAMPLE_PROFILER_SAMPLE_TYPE_EXTERNAL; + + // Write the sample. + ep_write_sample_profile_event ( + sampling_thread, + sampling_event, + target_thread, + current_stack_contents, + (uint8_t *)&payload_data, + sizeof (payload_data)); + } + } + END_FOREACH_THREAD + + ep_stack_contents_fini (current_stack_contents); + + // Resume managed execution. + thread_store->ResumeAllThreads(false); + thread_store->UnlockThreadStore(); } const ep_char8_t * diff --git a/src/coreclr/nativeaot/Runtime/thread.h b/src/coreclr/nativeaot/Runtime/thread.h index 4c0a21e9f9ab7f..d8c4f15a65c7c0 100644 --- a/src/coreclr/nativeaot/Runtime/thread.h +++ b/src/coreclr/nativeaot/Runtime/thread.h @@ -266,6 +266,7 @@ class Thread : private RuntimeThreadLocals bool IsCurrentThreadInCooperativeMode(); PInvokeTransitionFrame* GetTransitionFrameForStackTrace(); + PInvokeTransitionFrame* GetTransitionFrameForSampling() { return GetTransitionFrame(); } void * GetCurrentThreadPInvokeReturnAddress(); //