ep_rt_mono_fire_method_rundown_events_func method_events_func;
} EventPipeFireMethodEventsData;
+typedef struct _EventPipeSampleProfileData {
+ EventPipeStackContents stack_contents;
+ uint64_t thread_id;
+ uintptr_t thread_ip;
+ uint32_t payload_data;
+} EventPipeSampleProfileData;
+
gboolean ep_rt_mono_initialized;
MonoNativeTlsKey ep_rt_mono_thread_holder_tls_id;
gpointer ep_rt_mono_rand_provider;
static ep_rt_thread_holder_alloc_func thread_holder_alloc_callback_func;
static ep_rt_thread_holder_free_func thread_holder_free_callback_func;
+static GArray * _ep_rt_mono_sampled_thread_callstacks = NULL;
+static uint32_t _ep_rt_mono_max_sampled_thread_count = 32;
+
/*
* Forward declares of all static functions.
*/
void
eventpipe_fire_method_events (
MonoJitInfo *ji,
+ MonoMethod *method,
EventPipeFireMethodEventsData *events_data);
static
static
gboolean
+eventpipe_sample_profiler_walk_managed_stack_for_thread_func (
+ MonoStackFrameInfo *frame,
+ MonoContext *ctx,
+ gpointer data);
+
+static
+gboolean
+eventpipe_sample_profiler_write_sampling_event_for_threads (
+ ep_rt_thread_handle_t sampling_thread,
+ EventPipeEvent *sampling_event);
+
+static
+gboolean
eventpipe_method_get_simple_assembly_name (
ep_rt_method_desc_t *method,
ep_char8_t *name,
void
eventpipe_fire_method_events (
MonoJitInfo *ji,
+ MonoMethod *method,
EventPipeFireMethodEventsData *events_data)
{
g_assert_checked (ji != NULL);
//TODO: Optimize string formatting into functions accepting GString to reduce heap alloc.
- MonoMethod *method = jinfo_get_method (ji);
if (method) {
method_id = (uint64_t)method;
method_token = method->token;
EventPipeFireMethodEventsData *events_data = (EventPipeFireMethodEventsData *)user_data;
g_assert_checked (events_data != NULL);
- if (ji && !ji->is_trampoline && !ji->async)
- eventpipe_fire_method_events (ji, events_data);
+ if (ji && !ji->is_trampoline && !ji->async) {
+ MonoMethod *method = jinfo_get_method (ji);
+ if (method && !m_method_is_wrapper (method))
+ eventpipe_fire_method_events (ji, method, events_data);
+ }
}
static
if (!frame->ji)
return FALSE;
MonoMethod *method = frame->ji->async ? NULL : frame->actual_method;
- ep_stack_contents_append ((EventPipeStackContents *)data, (uintptr_t)((uint8_t*)frame->ji->code_start + frame->native_offset), method);
+ if (method && !m_method_is_wrapper (method))
+ ep_stack_contents_append ((EventPipeStackContents *)data, (uintptr_t)((uint8_t*)frame->ji->code_start + frame->native_offset), method);
return ep_stack_contents_get_length ((EventPipeStackContents *)data) >= EP_MAX_STACK_DEPTH;
default:
g_assert_not_reached ();
static
gboolean
+eventpipe_sample_profiler_walk_managed_stack_for_thread_func (
+ MonoStackFrameInfo *frame,
+ MonoContext *ctx,
+ gpointer data)
+{
+ g_assert_checked (frame != NULL);
+ g_assert_checked (data != NULL);
+
+ EventPipeSampleProfileData *sample_data = (EventPipeSampleProfileData *)data;
+
+ if (sample_data->payload_data == EP_SAMPLE_PROFILER_SAMPLE_TYPE_ERROR) {
+ if (frame->type == FRAME_TYPE_MANAGED_TO_NATIVE)
+ sample_data->payload_data = EP_SAMPLE_PROFILER_SAMPLE_TYPE_EXTERNAL;
+ else
+ sample_data->payload_data = EP_SAMPLE_PROFILER_SAMPLE_TYPE_MANAGED;
+ }
+
+ return eventpipe_walk_managed_stack_for_thread_func (frame, ctx, &sample_data->stack_contents);
+}
+
+static
+gboolean
+eventpipe_sample_profiler_write_sampling_event_for_threads (
+ ep_rt_thread_handle_t sampling_thread,
+ EventPipeEvent *sampling_event)
+{
+ // Follows CoreClr implementation of sample profiler. Generic invasive/expensive way to do CPU sample profiling relying on STW and stackwalks.
+ // TODO: Investigate alternatives on platforms supporting Signals/SuspendThread (see Mono profiler) or CPU PMU's (see ETW/perf_event_open).
+
+ // Sample profiler only runs on one thread, no need to synchorinize.
+ if (!_ep_rt_mono_sampled_thread_callstacks)
+ _ep_rt_mono_sampled_thread_callstacks = g_array_sized_new (FALSE, FALSE, sizeof (EventPipeSampleProfileData), _ep_rt_mono_max_sampled_thread_count);
+
+ // Make sure there is room based on previous max number of sampled threads.
+ // NOTE, there is a chance there are more threads than max, if that's the case we will
+ // miss those threads in this sample, but will be included in next when max has been adjusted.
+ g_array_set_size (_ep_rt_mono_sampled_thread_callstacks, _ep_rt_mono_max_sampled_thread_count);
+
+ uint32_t filtered_thread_count = 0;
+ uint32_t sampled_thread_count = 0;
+
+ mono_stop_world (MONO_THREAD_INFO_FLAGS_NO_GC | MONO_THREAD_INFO_FLAGS_NO_SAMPLE);
+
+ // Record all info needed in sample events while runtime is suspended, must be async safe.
+ FOREACH_THREAD_SAFE_EXCLUDE (thread_info, MONO_THREAD_INFO_FLAGS_NO_GC | MONO_THREAD_INFO_FLAGS_NO_SAMPLE) {
+ if (!mono_thread_info_is_running (thread_info)) {
+ MonoThreadUnwindState *thread_state = mono_thread_info_get_suspend_state (thread_info);
+ if (thread_state->valid) {
+ if (sampled_thread_count < _ep_rt_mono_max_sampled_thread_count) {
+ EventPipeSampleProfileData *data = &g_array_index (_ep_rt_mono_sampled_thread_callstacks, EventPipeSampleProfileData, sampled_thread_count);
+ data->thread_id = ep_rt_thread_id_t_to_uint64_t (mono_thread_info_get_tid (thread_info));
+ data->thread_ip = (uintptr_t)MONO_CONTEXT_GET_IP (&thread_state->ctx);
+ data->payload_data = EP_SAMPLE_PROFILER_SAMPLE_TYPE_ERROR;
+ ep_stack_contents_reset (&data->stack_contents);
+ mono_get_eh_callbacks ()->mono_walk_stack_with_state (eventpipe_sample_profiler_walk_managed_stack_for_thread_func, thread_state, MONO_UNWIND_SIGNAL_SAFE, data);
+ sampled_thread_count++;
+ }
+ }
+ }
+ filtered_thread_count++;
+ } FOREACH_THREAD_SAFE_END
+
+ mono_restart_world (MONO_THREAD_INFO_FLAGS_NO_GC | MONO_THREAD_INFO_FLAGS_NO_SAMPLE);
+
+ // Fire sample event for threads. Must be done after runtime is resumed since it's not async safe.
+ // Since we can't keep thread info around after runtime as been suspended, use an empty
+ // adapter instance and only set recorded tid as parameter inside adapter.
+ THREAD_INFO_TYPE adapter = { 0 };
+ for (uint32_t i = 0; i < sampled_thread_count; ++i) {
+ EventPipeSampleProfileData *data = &g_array_index (_ep_rt_mono_sampled_thread_callstacks, EventPipeSampleProfileData, i);
+ if (data->payload_data != EP_SAMPLE_PROFILER_SAMPLE_TYPE_ERROR && ep_stack_contents_get_length(&data->stack_contents) > 0) {
+ mono_thread_info_set_tid (&adapter, ep_rt_uint64_t_to_thread_id_t (data->thread_id));
+ ep_write_sample_profile_event (sampling_thread, sampling_event, &adapter, &data->stack_contents, (uint8_t *)&data->payload_data, sizeof (data->payload_data));
+ }
+ }
+
+ // Current thread count will be our next maximum sampled threads.
+ _ep_rt_mono_max_sampled_thread_count = filtered_thread_count;
+
+ return TRUE;
+}
+
+static
+gboolean
eventpipe_method_get_simple_assembly_name (
ep_rt_method_desc_t *method,
ep_char8_t *name,
table->ep_rt_mono_get_managed_cmd_line = mono_runtime_get_managed_cmd_line;
table->ep_rt_mono_execute_rundown = eventpipe_execute_rundown;
table->ep_rt_mono_walk_managed_stack_for_thread = eventpipe_walk_managed_stack_for_thread;
+ table->ep_rt_mono_sample_profiler_write_sampling_event_for_threads = eventpipe_sample_profiler_write_sampling_event_for_threads;
table->ep_rt_mono_method_get_simple_assembly_name = eventpipe_method_get_simple_assembly_name;
table->ep_rt_mono_method_get_full_name = evetpipe_method_get_full_name;
}
void
mono_eventpipe_fini (void)
{
+ if (_ep_rt_mono_sampled_thread_callstacks)
+ g_array_free (_ep_rt_mono_sampled_thread_callstacks, TRUE);
+
if (ep_rt_mono_initialized)
mono_rand_close (ep_rt_mono_rand_provider);
+ _ep_rt_mono_sampled_thread_callstacks = NULL;
ep_rt_mono_rand_provider = NULL;
thread_holder_alloc_callback_func = NULL;
thread_holder_free_callback_func = NULL;
#define TV_GETTIME SGEN_TV_GETTIME
#define TV_ELAPSED SGEN_TV_ELAPSED
-static void sgen_unified_suspend_restart_world (void);
-static void sgen_unified_suspend_stop_world (void);
+typedef void (*unified_suspend_thread_stopped_func)(THREAD_INFO_TYPE *thread_info);
+typedef void (*unified_suspend_thread_restarted_func)(THREAD_INFO_TYPE *thread_info);
+
+static void unified_suspend_restart_world (MonoThreadInfoFlags flags, unified_suspend_thread_restarted_func thread_restarted_callback);
+static void unified_suspend_stop_world (MonoThreadInfoFlags flags, unified_suspend_thread_stopped_func thread_stopped_callback);
static TV_DECLARE (end_of_last_stw);
static guint64 time_stop_world;
static guint64 time_restart_world;
+static
+void
+sgen_client_stop_world_thread_stopped_callback (THREAD_INFO_TYPE *info)
+{
+ info->client_info.ctx = mono_thread_info_get_suspend_state (info)->ctx;
+
+ /* Once we remove the old suspend code, we should directly access the state in MonoThread */
+ info->client_info.stack_start = (gpointer) ((char*)MONO_CONTEXT_GET_SP (&info->client_info.ctx) - REDZONE_SIZE);
+
+ if (info->client_info.stack_start < info->client_info.info.stack_start_limit
+ || info->client_info.stack_start >= info->client_info.info.stack_end) {
+ /*
+ * Thread context is in unhandled state, most likely because it is
+ * dying. We don't scan it.
+ * FIXME We should probably rework and check the valid flag instead.
+ */
+ info->client_info.stack_start = NULL;
+ }
+
+ sgen_binary_protocol_thread_suspend ((gpointer)(gsize)mono_thread_info_get_tid (info), (gpointer) (MONO_CONTEXT_GET_IP (&info->client_info.ctx)));
+}
+
/* LOCKING: assumes the GC lock is held */
void
sgen_client_stop_world (int generation, gboolean serial_collection)
SGEN_LOG (3, "stopping world n %d from %p %p", sgen_global_stop_count, mono_thread_info_current (), (gpointer) (gsize) mono_native_thread_id_get ());
TV_GETTIME (stop_world_time);
- sgen_unified_suspend_stop_world ();
+ unified_suspend_stop_world (MONO_THREAD_INFO_FLAGS_NO_GC, sgen_client_stop_world_thread_stopped_callback);
SGEN_LOG (3, "world stopped");
sgen_bridge_reset_data ();
}
+static
+void
+sgen_client_stop_world_thread_restarted_callback (THREAD_INFO_TYPE *info)
+{
+ sgen_binary_protocol_thread_restart ((gpointer) mono_thread_info_get_tid (info));
+}
+
/* LOCKING: assumes the GC lock is held */
void
sgen_client_restart_world (int generation, gboolean serial_collection, gint64 *stw_time)
TV_GETTIME (start_handshake);
- sgen_unified_suspend_restart_world ();
+ unified_suspend_restart_world (MONO_THREAD_INFO_FLAGS_NO_GC, sgen_client_stop_world_thread_restarted_callback);
TV_GETTIME (end_sw);
/* Unified suspend code */
static gboolean
-sgen_is_thread_in_current_stw (SgenThreadInfo *info, int *reason)
+is_thread_in_current_stw (SgenThreadInfo *info, int *reason)
{
/*
* No need to check MONO_THREAD_INFO_FLAGS_NO_GC here as we rely on the
return TRUE;
}
-static void
-sgen_unified_suspend_stop_world (void)
+static
+void
+unified_suspend_stop_world (MonoThreadInfoFlags flags, unified_suspend_thread_stopped_func thread_stopped_callback)
{
int sleep_duration = -1;
g_assert (!mono_thread_info_will_not_safepoint (mono_thread_info_current ()));
mono_threads_begin_global_suspend ();
- THREADS_STW_DEBUG ("[GC-STW-BEGIN][%p] *** BEGIN SUSPEND *** \n", mono_thread_info_get_tid (mono_thread_info_current ()));
+ THREADS_STW_DEBUG ("[STW-BEGIN][%p] *** BEGIN SUSPEND *** \n", mono_thread_info_get_tid (mono_thread_info_current ()));
for (MonoThreadSuspendPhase phase = MONO_THREAD_SUSPEND_PHASE_INITIAL; phase < MONO_THREAD_SUSPEND_PHASE_COUNT; phase++) {
gboolean need_next_phase = FALSE;
- FOREACH_THREAD_EXCLUDE (info, MONO_THREAD_INFO_FLAGS_NO_GC) {
+ FOREACH_THREAD_EXCLUDE (info, flags) {
/* look at every thread in the first phase. */
if (phase == MONO_THREAD_SUSPEND_PHASE_INITIAL) {
info->client_info.skip = FALSE;
info->client_info.suspend_done = FALSE;
} else {
/* skip threads suspended by previous phase. */
- /* threads with info->client_info->skip set to TRUE will be skipped by sgen_is_thread_in_current_stw. */
+ /* threads with info->client_info->skip set to TRUE will be skipped by unified_is_thread_in_current_stw. */
if (info->client_info.suspend_done)
continue;
}
int reason;
- if (!sgen_is_thread_in_current_stw (info, &reason)) {
- THREADS_STW_DEBUG ("[GC-STW-BEGIN-SUSPEND-%d] IGNORE thread %p skip %s reason %d\n", (int)phase, mono_thread_info_get_tid (info), info->client_info.skip ? "true" : "false", reason);
+ if (!is_thread_in_current_stw (info, &reason)) {
+ THREADS_STW_DEBUG ("[STW-BEGIN-SUSPEND-%d] IGNORE thread %p skip %s reason %d\n", (int)phase, mono_thread_info_get_tid (info), info->client_info.skip ? "true" : "false", reason);
continue;
}
g_assert_not_reached ();
}
- THREADS_STW_DEBUG ("[GC-STW-BEGIN-SUSPEND-%d] SUSPEND thread %p skip %s\n", (int)phase, mono_thread_info_get_tid (info), info->client_info.skip ? "true" : "false");
+ THREADS_STW_DEBUG ("[STW-BEGIN-SUSPEND-%d] SUSPEND thread %p skip %s\n", (int)phase, mono_thread_info_get_tid (info), info->client_info.skip ? "true" : "false");
} FOREACH_THREAD_END;
mono_thread_info_current ()->client_info.suspend_done = TRUE;
for (;;) {
gint restart_counter = 0;
- FOREACH_THREAD_EXCLUDE (info, MONO_THREAD_INFO_FLAGS_NO_GC) {
+ FOREACH_THREAD_EXCLUDE (info, flags) {
gint suspend_count;
int reason = 0;
- if (info->client_info.suspend_done || !sgen_is_thread_in_current_stw (info, &reason)) {
- THREADS_STW_DEBUG ("[GC-STW-RESTART] IGNORE RESUME thread %p not been processed done %d current %d reason %d\n", mono_thread_info_get_tid (info), info->client_info.suspend_done, !sgen_is_thread_in_current_stw (info, NULL), reason);
+ if (info->client_info.suspend_done || !is_thread_in_current_stw (info, &reason)) {
+ THREADS_STW_DEBUG ("[STW-RESTART] IGNORE RESUME thread %p not been processed done %d current %d reason %d\n", mono_thread_info_get_tid (info), info->client_info.suspend_done, !is_thread_in_current_stw (info, NULL), reason);
continue;
}
if (!mono_thread_info_in_critical_location (info)) {
info->client_info.suspend_done = TRUE;
- THREADS_STW_DEBUG ("[GC-STW-RESTART] DONE thread %p deemed fully suspended\n", mono_thread_info_get_tid (info));
+ THREADS_STW_DEBUG ("[STW-RESTART] DONE thread %p deemed fully suspended\n", mono_thread_info_get_tid (info));
continue;
}
if (!info->client_info.skip)
restart_counter += 1;
- THREADS_STW_DEBUG ("[GC-STW-RESTART] RESTART thread %p skip %s\n", mono_thread_info_get_tid (info), info->client_info.skip ? "true" : "false");
+ THREADS_STW_DEBUG ("[STW-RESTART] RESTART thread %p skip %s\n", mono_thread_info_get_tid (info), info->client_info.skip ? "true" : "false");
} FOREACH_THREAD_END
mono_threads_wait_pending_operations ();
sleep_duration += 10;
}
- FOREACH_THREAD_EXCLUDE (info, MONO_THREAD_INFO_FLAGS_NO_GC) {
+ FOREACH_THREAD_EXCLUDE (info, flags) {
int reason = 0;
- if (info->client_info.suspend_done || !sgen_is_thread_in_current_stw (info, &reason)) {
- THREADS_STW_DEBUG ("[GC-STW-RESTART] IGNORE SUSPEND thread %p not been processed done %d current %d reason %d\n", mono_thread_info_get_tid (info), info->client_info.suspend_done, !sgen_is_thread_in_current_stw (info, NULL), reason);
+ if (info->client_info.suspend_done || !is_thread_in_current_stw (info, &reason)) {
+ THREADS_STW_DEBUG ("[STW-RESTART] IGNORE SUSPEND thread %p not been processed done %d current %d reason %d\n", mono_thread_info_get_tid (info), info->client_info.suspend_done, !is_thread_in_current_stw (info, NULL), reason);
continue;
}
if (!mono_thread_info_is_running (info)) {
- THREADS_STW_DEBUG ("[GC-STW-RESTART] IGNORE SUSPEND thread %p not running\n", mono_thread_info_get_tid (info));
+ THREADS_STW_DEBUG ("[STW-RESTART] IGNORE SUSPEND thread %p not running\n", mono_thread_info_get_tid (info));
continue;
}
g_assert_not_reached ();
}
- THREADS_STW_DEBUG ("[GC-STW-RESTART] SUSPEND thread %p skip %s\n", mono_thread_info_get_tid (info), info->client_info.skip ? "true" : "false");
+ THREADS_STW_DEBUG ("[STW-RESTART] SUSPEND thread %p skip %s\n", mono_thread_info_get_tid (info), info->client_info.skip ? "true" : "false");
} FOREACH_THREAD_END
mono_threads_wait_pending_operations ();
}
- FOREACH_THREAD_EXCLUDE (info, MONO_THREAD_INFO_FLAGS_NO_GC) {
- gpointer stopped_ip;
-
+ FOREACH_THREAD_EXCLUDE (info, flags) {
int reason = 0;
- if (!sgen_is_thread_in_current_stw (info, &reason)) {
+ if (!is_thread_in_current_stw (info, &reason)) {
g_assert (!info->client_info.suspend_done || info == mono_thread_info_current ());
- THREADS_STW_DEBUG ("[GC-STW-SUSPEND-END] thread %p is NOT suspended, reason %d\n", mono_thread_info_get_tid (info), reason);
+ THREADS_STW_DEBUG ("[STW-SUSPEND-END] thread %p is NOT suspended, reason %d\n", mono_thread_info_get_tid (info), reason);
continue;
}
g_assert (info->client_info.suspend_done);
- info->client_info.ctx = mono_thread_info_get_suspend_state (info)->ctx;
-
- /* Once we remove the old suspend code, we should move sgen to directly access the state in MonoThread */
- info->client_info.stack_start = (gpointer) ((char*)MONO_CONTEXT_GET_SP (&info->client_info.ctx) - REDZONE_SIZE);
-
- if (info->client_info.stack_start < info->client_info.info.stack_start_limit
- || info->client_info.stack_start >= info->client_info.info.stack_end) {
- /*
- * Thread context is in unhandled state, most likely because it is
- * dying. We don't scan it.
- * FIXME We should probably rework and check the valid flag instead.
- */
- info->client_info.stack_start = NULL;
- }
+ if (thread_stopped_callback)
+ thread_stopped_callback (info);
- stopped_ip = (gpointer) (MONO_CONTEXT_GET_IP (&info->client_info.ctx));
+ THREADS_STW_DEBUG ("[STW-SUSPEND-END] thread %p is suspended, stopped_ip = %p, stack = %p -> %p\n",
+ mono_thread_info_get_tid (info),
+ (gpointer)(MONO_CONTEXT_GET_IP (&(mono_thread_info_get_suspend_state (info)->ctx))),
+ info->client_info.stack_start ? info->client_info.stack_start : NULL, info->client_info.stack_start ? info->client_info.info.stack_end : NULL);
- sgen_binary_protocol_thread_suspend ((gpointer)(gsize)mono_thread_info_get_tid (info), stopped_ip);
-
- THREADS_STW_DEBUG ("[GC-STW-SUSPEND-END] thread %p is suspended, stopped_ip = %p, stack = %p -> %p\n",
- mono_thread_info_get_tid (info), stopped_ip, info->client_info.stack_start, info->client_info.stack_start ? info->client_info.info.stack_end : NULL);
} FOREACH_THREAD_END
}
static void
-sgen_unified_suspend_restart_world (void)
+unified_suspend_restart_world (MonoThreadInfoFlags flags, unified_suspend_thread_restarted_func thread_restarted_callback)
{
- THREADS_STW_DEBUG ("[GC-STW-END] *** BEGIN RESUME ***\n");
- FOREACH_THREAD_EXCLUDE (info, MONO_THREAD_INFO_FLAGS_NO_GC) {
+ THREADS_STW_DEBUG ("[STW-END] *** BEGIN RESUME ***\n");
+ FOREACH_THREAD_EXCLUDE (info, flags) {
int reason = 0;
- if (sgen_is_thread_in_current_stw (info, &reason)) {
+ if (is_thread_in_current_stw (info, &reason)) {
g_assert (mono_thread_info_begin_resume (info));
- THREADS_STW_DEBUG ("[GC-STW-RESUME-WORLD] RESUME thread %p\n", mono_thread_info_get_tid (info));
-
- sgen_binary_protocol_thread_restart ((gpointer) mono_thread_info_get_tid (info));
+ THREADS_STW_DEBUG ("[STW-RESUME-WORLD] RESUME thread %p\n", mono_thread_info_get_tid (info));
+ if (thread_restarted_callback)
+ thread_restarted_callback (info);
} else {
- THREADS_STW_DEBUG ("[GC-STW-RESUME-WORLD] IGNORE thread %p, reason %d\n", mono_thread_info_get_tid (info), reason);
+ THREADS_STW_DEBUG ("[STW-RESUME-WORLD] IGNORE thread %p, reason %d\n", mono_thread_info_get_tid (info), reason);
}
} FOREACH_THREAD_END
mono_threads_wait_pending_operations ();
mono_threads_end_global_suspend ();
}
+
+void
+mono_stop_world (MonoThreadInfoFlags flags)
+{
+ LOCK_GC;
+ acquire_gc_locks ();
+ unified_suspend_stop_world (flags, NULL);
+}
+
+void
+mono_restart_world (MonoThreadInfoFlags flags)
+{
+ unified_suspend_restart_world (flags, NULL);
+ release_gc_locks ();
+ UNLOCK_GC;
+}
#endif