Skip to content

Commit a95f3cd

Browse files
committed
sampling profiler
1 parent 9992229 commit a95f3cd

File tree

12 files changed

+194
-34
lines changed

12 files changed

+194
-34
lines changed

src/coreclr/nativeaot/Runtime/eventpipe/ep-rt-aot.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,26 @@ ep_rt_sample_profiler_write_sampling_event_for_threads (
512512
ep_rt_aot_sample_profiler_write_sampling_event_for_threads (sampling_thread, sampling_event);
513513
}
514514

515+
static
516+
inline
517+
void
518+
ep_rt_sample_profiler_enabled (void)
519+
{
520+
STATIC_CONTRACT_NOTHROW;
521+
522+
// no-op
523+
}
524+
525+
static
526+
inline
527+
void
528+
ep_rt_sample_profiler_disabled (void)
529+
{
530+
STATIC_CONTRACT_NOTHROW;
531+
532+
// no-op
533+
}
534+
515535
static
516536
inline
517537
void

src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,26 @@ ep_rt_sample_profiler_write_sampling_event_for_threads (
567567
ep_rt_coreclr_sample_profiler_write_sampling_event_for_threads (sampling_thread, sampling_event);
568568
}
569569

570+
static
571+
inline
572+
void
573+
ep_rt_sample_profiler_enabled (void)
574+
{
575+
STATIC_CONTRACT_NOTHROW;
576+
577+
// no-op
578+
}
579+
580+
static
581+
inline
582+
void
583+
ep_rt_sample_profiler_disabled (void)
584+
{
585+
STATIC_CONTRACT_NOTHROW;
586+
587+
// no-op
588+
}
589+
570590
static
571591
inline
572592
void

src/mono/browser/runtime/diag/dotnet-counters.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,17 @@
44
import { commandStopTracing, commandCounters } from "./client-commands";
55
import { IDiagClient, IDiagServer, IDiagSession } from "./common";
66
import { mono_log_warn } from "../logging";
7-
import { loaderHelpers, Module } from "../globals";
7+
import { Module } from "../globals";
88

99
export class CountersClient implements IDiagClient {
1010
private firstAdvert = false;
1111
private firstSession = false;
1212
onAdvertise (server: IDiagServer): void {
1313
if (!this.firstAdvert) {
1414
this.firstAdvert = true;
15-
// give runtime a chance to start before we send more commands
16-
if (!loaderHelpers.is_runtime_running()) {
17-
Module.safeSetTimeout(() => {
18-
server.createSession(commandCounters());
19-
}, 100);
20-
} else {
21-
server.createSession(commandCounters());
22-
}
15+
server.createSession(commandCounters());
2316
}
2417
}
25-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
2618
onSessionStart (server: IDiagServer, session: IDiagSession): void {
2719
if (!this.firstSession) {
2820
this.firstSession = true;

src/mono/browser/runtime/diag/dotnet-gcdump.ts

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import { commandStopTracing, commandGcHeapDump, } from "./client-commands";
55
import { IDiagClient, IDiagServer, IDiagSession } from "./common";
66
import { mono_log_warn } from "../logging";
7-
import { loaderHelpers, Module } from "../globals";
7+
import { Module } from "../globals";
88

99
export class GcDumpDiagClient implements IDiagClient {
1010
private firstAdvert = false;
@@ -14,25 +14,13 @@ export class GcDumpDiagClient implements IDiagClient {
1414
onAdvertise (server: IDiagServer): void {
1515
if (!this.firstAdvert) {
1616
this.firstAdvert = true;
17-
// give runtime a chance to start before we send more commands
18-
if (!loaderHelpers.is_runtime_running()) {
19-
Module.safeSetTimeout(() => {
20-
server.createSession(commandGcHeapDump());
21-
}, 100);
22-
} else {
23-
server.createSession(commandGcHeapDump());
24-
}
17+
server.createSession(commandGcHeapDump());
2518
}
2619
}
2720
// eslint-disable-next-line @typescript-eslint/no-unused-vars
2821
onSessionStart (server: IDiagServer, session: IDiagSession): void {
2922
if (!this.firstSession) {
3023
this.firstSession = true;
31-
/* commandResumeRuntime is sent by the dotnet-trace tool, but we don't need it here
32-
Module.safeSetTimeout(() => {
33-
server.sendCommand(commandResumeRuntime());
34-
}, 100);
35-
*/
3624
}
3725
}
3826
onData (server: IDiagServer, session: IDiagSession, message: Uint8Array): void {

src/mono/browser/runtime/diag/dotnet-profiler.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,17 @@
44
import { commandStopTracing, commandSampleProfiler } from "./client-commands";
55
import { IDiagClient, IDiagServer, IDiagSession } from "./common";
66
import { mono_log_warn } from "../logging";
7-
import { loaderHelpers, Module } from "../globals";
7+
import { Module } from "../globals";
88

99
export class SampleProfilerClient implements IDiagClient {
1010
private firstAdvert = false;
1111
private firstSession = false;
1212
onAdvertise (server: IDiagServer): void {
1313
if (!this.firstAdvert) {
1414
this.firstAdvert = true;
15-
// give runtime a chance to start before we send more commands
16-
if (!loaderHelpers.is_runtime_running()) {
17-
Module.safeSetTimeout(() => {
18-
server.createSession(commandSampleProfiler());
19-
}, 100);
20-
} else {
21-
server.createSession(commandSampleProfiler());
22-
}
15+
server.createSession(commandSampleProfiler());
2316
}
2417
}
25-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
2618
onSessionStart (server: IDiagServer, session: IDiagSession): void {
2719
if (!this.firstSession) {
2820
this.firstSession = true;

src/mono/mono/eventpipe/ep-rt-mono-runtime-provider.c

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,14 @@ extern EVENTPIPE_TRACE_CONTEXT MICROSOFT_WINDOWS_DOTNETRUNTIME_RUNDOWN_PROVIDER_
2525
#define NUM_NANOSECONDS_IN_1_MS 1000000
2626

2727
// Sample profiler.
28+
#if defined(HOST_BROWSER) && defined(DISABLE_THREADS)
29+
MonoProfilerHandle _ep_rt_mono_sampling_profiler_provider;
30+
static EventPipeEvent *_sampling_event = NULL;
31+
static ep_rt_thread_handle_t _sampling_thread = NULL;
32+
#else
2833
static GArray * _sampled_thread_callstacks = NULL;
2934
static uint32_t _max_sampled_thread_count = 32;
35+
#endif
3036

3137
// Mono profilers.
3238
extern MonoProfilerHandle _ep_rt_mono_default_profiler_provider;
@@ -1212,6 +1218,7 @@ ep_rt_mono_sample_profiler_write_sampling_event_for_threads (
12121218
ep_rt_thread_handle_t sampling_thread,
12131219
EventPipeEvent *sampling_event)
12141220
{
1221+
#if !defined(HOST_BROWSER) || !defined(DISABLE_THREADS)
12151222
// Follows CoreClr implementation of sample profiler. Generic invasive/expensive way to do CPU sample profiling relying on STW and stackwalks.
12161223
// TODO: Investigate alternatives on platforms supporting Signals/SuspendThread (see Mono profiler) or CPU PMU's (see ETW/perf_event_open).
12171224

@@ -1302,10 +1309,99 @@ ep_rt_mono_sample_profiler_write_sampling_event_for_threads (
13021309

13031310
// Current thread count will be our next maximum sampled threads.
13041311
_max_sampled_thread_count = filtered_thread_count;
1312+
#else
1313+
_sampling_event = sampling_event;
1314+
_sampling_thread = sampling_thread;
1315+
#endif
13051316

13061317
return true;
13071318
}
13081319

1320+
#if defined(HOST_BROWSER) && defined(DISABLE_THREADS)
1321+
1322+
static void
1323+
method_enter (MonoProfiler *prof, MonoMethod *method, MonoProfilerCallContext *ctx)
1324+
{
1325+
MonoThreadInfo *thread_info = mono_thread_info_current ();
1326+
SampleProfileStackWalkData stack_walk_data;
1327+
SampleProfileStackWalkData *data= &stack_walk_data;
1328+
THREAD_INFO_TYPE adapter = { { 0 } };
1329+
1330+
data->thread_id = ep_rt_thread_id_t_to_uint64_t (mono_thread_info_get_tid (thread_info));
1331+
data->thread_ip = (uintptr_t)MONO_CONTEXT_GET_IP (&ctx->context);
1332+
data->payload_data = EP_SAMPLE_PROFILER_SAMPLE_TYPE_ERROR;
1333+
data->stack_walk_data.stack_contents = &data->stack_contents;
1334+
data->stack_walk_data.top_frame = true;
1335+
data->stack_walk_data.async_frame = false;
1336+
data->stack_walk_data.safe_point_frame = false;
1337+
data->stack_walk_data.runtime_invoke_frame = false;
1338+
ep_stack_contents_reset (&data->stack_contents);
1339+
1340+
// mono_walk_stack_with_ctx (MonoJitStackWalk func, MonoContext *start_ctx, MonoUnwindOptions unwind_options, void *user_data)
1341+
mono_get_eh_callbacks ()->mono_walk_stack_with_ctx (sample_profiler_walk_managed_stack_for_thread_callback, &ctx->context, MONO_UNWIND_SIGNAL_SAFE, &stack_walk_data);
1342+
if (data->payload_data == EP_SAMPLE_PROFILER_SAMPLE_TYPE_EXTERNAL && (data->stack_walk_data.safe_point_frame || data->stack_walk_data.runtime_invoke_frame)) {
1343+
// If classified as external code (managed->native frame on top of stack), but have a safe point or runtime invoke frame
1344+
// as second, re-classify current callstack to be executing managed code.
1345+
data->payload_data = EP_SAMPLE_PROFILER_SAMPLE_TYPE_MANAGED;
1346+
}
1347+
if (data->stack_walk_data.top_frame && ep_stack_contents_get_length (&data->stack_contents) == 0) {
1348+
// If no managed frames (including helper frames) are located on stack, mark sample as beginning in external code.
1349+
// This can happen on attached embedding threads returning to native code between runtime invokes.
1350+
// Make sure sample is still written into EventPipe for all attached threads even if they are currently not having
1351+
// any managed frames on stack. Prevents some tools applying thread time heuristics to prolong duration of last sample
1352+
// when embedding thread returns to native code. It also opens ability to visualize number of samples in unmanaged code
1353+
// on attached threads when executing outside of runtime. If tooling is not interested in these sample events, they are easy
1354+
// to identify and filter out.
1355+
data->payload_data = EP_SAMPLE_PROFILER_SAMPLE_TYPE_EXTERNAL;
1356+
}
1357+
1358+
if ((data->stack_walk_data.top_frame && data->payload_data == EP_SAMPLE_PROFILER_SAMPLE_TYPE_EXTERNAL) || (data->payload_data != EP_SAMPLE_PROFILER_SAMPLE_TYPE_ERROR && ep_stack_contents_get_length (&data->stack_contents) > 0)) {
1359+
// Check if we have an async frame, if so we will need to make sure all frames are registered in regular jit info table.
1360+
// TODO: An async frame can contain wrapper methods (no way to check during stackwalk), we could skip writing profile event
1361+
// for this specific stackwalk or we could cleanup stack_frames before writing profile event.
1362+
if (data->stack_walk_data.async_frame) {
1363+
for (uint32_t frame_count = 0; frame_count < data->stack_contents.next_available_frame; ++frame_count)
1364+
mono_jit_info_table_find_internal ((gpointer)data->stack_contents.stack_frames [frame_count], TRUE, FALSE);
1365+
}
1366+
mono_thread_info_set_tid (&adapter, ep_rt_uint64_t_to_thread_id_t (data->thread_id));
1367+
uint32_t payload_data = ep_rt_val_uint32_t (data->payload_data);
1368+
ep_write_sample_profile_event (_sampling_thread, _sampling_event, &adapter, &data->stack_contents, (uint8_t *)&payload_data, sizeof (payload_data));
1369+
}
1370+
}
1371+
1372+
static MonoProfilerCallInstrumentationFlags
1373+
method_filter (MonoProfiler *prof, MonoMethod *method)
1374+
{
1375+
return MONO_PROFILER_CALL_INSTRUMENTATION_ENTER;
1376+
}
1377+
1378+
#endif
1379+
1380+
void
1381+
ep_rt_mono_sampling_provider_component_init (void)
1382+
{
1383+
#if defined(HOST_BROWSER) && defined(DISABLE_THREADS)
1384+
_ep_rt_mono_sampling_profiler_provider = mono_profiler_create (NULL);
1385+
mono_profiler_set_call_instrumentation_filter_callback (_ep_rt_mono_sampling_profiler_provider, method_filter);
1386+
#endif
1387+
}
1388+
1389+
void
1390+
ep_rt_mono_sample_profiler_enabled (void)
1391+
{
1392+
#if defined(HOST_BROWSER) && defined(DISABLE_THREADS)
1393+
mono_profiler_set_method_enter_callback (_ep_rt_mono_sampling_profiler_provider, method_enter);
1394+
#endif
1395+
}
1396+
1397+
void
1398+
ep_rt_mono_sample_profiler_disabled (void)
1399+
{
1400+
#if defined(HOST_BROWSER) && defined(DISABLE_THREADS)
1401+
mono_profiler_set_method_enter_callback (_ep_rt_mono_sampling_profiler_provider, NULL);
1402+
#endif
1403+
}
1404+
13091405
void
13101406
ep_rt_mono_execute_rundown (dn_vector_ptr_t *execution_checkpoints)
13111407
{

src/mono/mono/eventpipe/ep-rt-mono.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -789,6 +789,7 @@ ep_rt_mono_component_init (void)
789789
g_free (diag_env);
790790

791791
ep_rt_mono_runtime_provider_component_init ();
792+
ep_rt_mono_sampling_provider_component_init ();
792793
ep_rt_mono_profiler_provider_component_init ();
793794
}
794795

src/mono/mono/eventpipe/ep-rt-mono.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ extern void ep_rt_mono_provider_config_init (EventPipeProviderConfiguration *pro
6969
extern void ep_rt_mono_init_providers_and_events (void);
7070
extern bool ep_rt_mono_providers_validate_all_disabled (void);
7171
extern bool ep_rt_mono_sample_profiler_write_sampling_event_for_threads (ep_rt_thread_handle_t sampling_thread, EventPipeEvent *sampling_event);
72+
extern void ep_rt_mono_sample_profiler_enabled (void);
73+
extern void ep_rt_mono_sample_profiler_disabled (void);
7274
extern void ep_rt_mono_execute_rundown (dn_vector_ptr_t *execution_checkpoints);
7375
extern int64_t ep_rt_mono_perf_counter_query (void);
7476
extern int64_t ep_rt_mono_perf_frequency_query (void);
@@ -637,6 +639,22 @@ ep_rt_sample_profiler_write_sampling_event_for_threads (ep_rt_thread_handle_t sa
637639
ep_rt_mono_sample_profiler_write_sampling_event_for_threads (sampling_thread, sampling_event);
638640
}
639641

642+
static
643+
inline
644+
void
645+
ep_rt_sample_profiler_enabled (void)
646+
{
647+
ep_rt_mono_sample_profiler_enabled ();
648+
}
649+
650+
static
651+
inline
652+
void
653+
ep_rt_sample_profiler_disabled (void)
654+
{
655+
ep_rt_mono_sample_profiler_disabled ();
656+
}
657+
640658
static
641659
void
642660
ep_rt_notify_profiler_provider_created (EventPipeProvider *provider)
@@ -1993,6 +2011,8 @@ extern void ep_rt_mono_runtime_provider_fini (void);
19932011
extern void ep_rt_mono_runtime_provider_thread_started_callback (MonoProfiler *prof, uintptr_t tid);
19942012
extern void ep_rt_mono_runtime_provider_thread_stopped_callback (MonoProfiler *prof, uintptr_t tid);
19952013

2014+
extern void ep_rt_mono_sampling_provider_component_init (void);
2015+
19962016
extern void ep_rt_mono_profiler_provider_component_init (void);
19972017
extern void ep_rt_mono_profiler_provider_init (void);
19982018
extern void ep_rt_mono_profiler_provider_fini (void);

src/mono/sample/wasm/browser-eventpipe/main.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ try {
4343
}
4444
});
4545

46+
setTimeout(() => {
47+
sayHi();
48+
}, 1000);
4649

4750
await runMain();
4851
}

src/native/eventpipe/ep-rt.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,10 +204,19 @@ static
204204
void
205205
ep_rt_sample_profiler_write_sampling_event_for_threads (ep_rt_thread_handle_t sampling_thread, EventPipeEvent *sampling_event);
206206

207+
static
208+
void
209+
ep_rt_sample_profiler_enabled (void);
210+
211+
static
212+
void
213+
ep_rt_sample_profiler_disabled (void);
214+
207215
static
208216
void
209217
ep_rt_notify_profiler_provider_created (EventPipeProvider *provider);
210218

219+
211220
/*
212221
* Arrays.
213222
*/

src/native/eventpipe/ep-sample-profiler.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ sample_profiler_store_can_start_sampling (bool start_sampling)
8484
ep_rt_volatile_store_uint32_t (&_can_start_sampling, start_sampling ? 1 : 0);
8585
}
8686

87+
#if !defined(HOST_BROWSER) || !defined(DISABLE_THREADS)
8788
EP_RT_DEFINE_THREAD_FUNC (sampling_thread)
8889
{
8990
EP_ASSERT (data != NULL);
@@ -108,6 +109,7 @@ EP_RT_DEFINE_THREAD_FUNC (sampling_thread)
108109

109110
return (ep_rt_thread_start_func_return_t)0;
110111
}
112+
#endif
111113

112114
static
113115
void
@@ -192,6 +194,9 @@ sample_profiler_enable (void)
192194
if (!sample_profiler_load_profiling_enabled ()) {
193195
sample_profiler_store_profiling_enabled (true);
194196

197+
ep_rt_sample_profiler_enabled ();
198+
199+
#if !defined(HOST_BROWSER) || !defined(DISABLE_THREADS)
195200
EP_ASSERT (!ep_rt_wait_event_is_valid (&_thread_shutdown_event));
196201
ep_rt_wait_event_alloc (&_thread_shutdown_event, true, false);
197202
if (!ep_rt_wait_event_is_valid (&_thread_shutdown_event))
@@ -200,6 +205,10 @@ sample_profiler_enable (void)
200205
ep_rt_thread_id_t thread_id = ep_rt_uint64_t_to_thread_id_t (0);
201206
if (!ep_rt_thread_create ((void *)sampling_thread, NULL, EP_THREAD_TYPE_SAMPLING, &thread_id))
202207
EP_UNREACHABLE ("Unable to create sample profiler thread.");
208+
#else
209+
// once
210+
ep_rt_sample_profiler_write_sampling_event_for_threads (ep_rt_thread_get_handle (), _thread_time_event);
211+
#endif
203212

204213
sample_profiler_set_time_granularity ();
205214
}
@@ -288,9 +297,13 @@ ep_sample_profiler_disable (void)
288297
// when profiling is disabled.
289298
sample_profiler_store_profiling_enabled (false);
290299

300+
ep_rt_sample_profiler_disabled ();
301+
291302
// Wait for the sampling thread to clean itself up.
303+
#if !defined(HOST_BROWSER) || !defined(DISABLE_THREADS)
292304
ep_rt_wait_event_wait (&_thread_shutdown_event, EP_INFINITE_WAIT, false);
293305
ep_rt_wait_event_free (&_thread_shutdown_event);
306+
#endif
294307

295308
if (_time_period_is_set)
296309
sample_profiler_reset_time_granularity ();

0 commit comments

Comments
 (0)