[browser] samplepoint instrumentation into Mono profiler - #112352

Merged
pavelsavara merged 30 commits into
dotnet:mainfrom
pavelsavara:mono_profile_safepoint
Feb 25, 2025
Merged

[browser] samplepoint instrumentation into Mono profiler#112352
pavelsavara merged 30 commits into
dotnet:mainfrom
pavelsavara:mono_profile_safepoint

Conversation

@pavelsavara

@pavelsavarapavelsavara commented Feb 10, 2025

Copy link
Copy Markdown
Member

Motivation

Collect sample stack traces in the single-threaded browser.
Single threaded environment can't have dedicated thread for "stop-the-world + collect stack traces" implementation.
So we need to collect samples by instrumenting the code instead.

Previously we implemented browser based profiler which collects "sample" on every method entry/leave. In realistic application, that generates too much traffic for the browser profiler.

This enhancement could also be used to implement sampling profiler with ST diagnostic server.

Goal

  • add new samplepoint instrumentation into Mono profiler, use the existing enter/leave too.
  • inject the event/call into same locations as GC safepoint.
    • we can skip emitting it at the method start, because there is already method_enter event/instrumentation.
    • This is also same location where cooperative MT would stop-the-world to take a sample.
  • take sample on each Nth visit to the samplepoint (because measuring current time is more expensive)
  • keep updating the size of N to match expected sample time interval

Contributes to #76316

Non-goal: the browser profiler is not compatible with MT.

Changes

Interop

  • new interp IR instruction MINT_PROF_SAMPLEPOINT
  • new profiler event method_samplepoint
  • new interp macro INTERP_PROFILER_RAISE

Jiterp

  • new jiterp helper append_profiler_event calling new mono_jiterp_prof_enter, mono_jiterp_prof_samplepoint, mono_jiterp_prof_leave

AOT

  • new Mono JIT method mini_profiler_emit_samplepoint
  • example of code emitted by AOT with call $mono_profiler_raise_method_samplepoint

browser profiler

  • new config option sampleIntervalMs for browser profiler. 1ms by default. All samples when set to 0.
  • changes JavaScript called only to get time mono_wasm_profiler_now and mono_wasm_profiler_record
  • all sampling logic in browser.c new should_record_frame
  • stack of start times profiler_stack_frames and skip counter sample_skip_counter
  • default sample interval 10ms
  • callspec parametr to profiler allows to filter the instrumentation, for example by namespace callspec=N:Sample.FooNs

How to use

Interpreter

  • dotnet workload install wasm-tools

In the project file

<WasmProfilers>browser;</WasmProfilers>

In your main.js

import{dotnet}from'./dotnet.js'dotnet.withConfig({browserProfilerOptions: {sampleIntervalMs: 10,// optional}}).create();

or in your Blazor app

Blazor.start({configureRuntime: function(builder){builder.withConfig({browserProfilerOptions: {sampleIntervalMs: 10,// optional}});}});

AOT

You can add following, so that the native code produced by AOT has symbols, which are visible in the browser devtools profiler.

<RunAOTCompilation>true</RunAOTCompilation>
<RunAOTCompilationAfterBuild>true</RunAOTCompilationAfterBuild>
<WasmNativeStrip>false</WasmNativeStrip>
<WasmProfilers>browser</WasmProfilers>
<WasmNativeDebugSymbols>true</WasmNativeDebugSymbols>
<WasmBuildNative>true</WasmBuildNative>

callspec

If you want to filter profiled methods, you can use callspec

Callspec is documented here https://github.com/dotnet/runtime/blob/main/docs/design/mono/diagnostics-tracing.md#trace-monovm-profiler-events-during-startup

browserProfilerOptions: {callSpec: "N:SampleNamespace"// optional}

Your for AOT needs to match callspec in the browserProfilerOptions in JS

<WasmProfilers>browser:callspec=N:Sample;</WasmProfilers>

@pavelsavarapavelsavara added arch-wasm WebAssembly architecture area-Diagnostics-mono os-browser Browser variant of arch-wasm labels Feb 10, 2025
@pavelsavarapavelsavara added this to the 10.0.0 milestone Feb 10, 2025
@pavelsavarapavelsavara self-assigned this Feb 10, 2025
@pavelsavarapavelsavara changed the title [browser] add sample point instrumentation into Mono profiler[browser] Sample point instrumentation into Mono profilerFeb 10, 2025
@pavelsavara

pavelsavara commented Feb 10, 2025

Copy link
Copy Markdown
MemberAuthor

Interpreter: 1ms, skip rate between 200-500 sample points.
image

@pavelsavara

This comment was marked as resolved.

@pavelsavara

This comment was marked as resolved.

@hakenr

Copy link
Copy Markdown
Member

Hi @pavelsavara,
I'm in! I'll try to reach you via Teams.

Comment threadsrc/mono/mono/mini/mini.c
@pavelsavara

Copy link
Copy Markdown
MemberAuthor

My testing shows that enter/leave events are unbalanced.
I think my adding samplepoint in instrumentation didn't change that.
But the design of measuring the difference between method entry and method end requires that the events are balanced.

I will test it further to see if this could be fixed in the instrumentation or if this must be detected and survived by the receiving side.

@kg

kg commented Feb 21, 2025

Copy link
Copy Markdown
Contributor

I think you can't randomly record only a % of enter/leave events, or if you do you need to ensure that they are balanced - for example if you record an entry for method x the matching return also needs to be recorded. So you can only pick a % of enters to record but you need to do the check for every LEAVE as to whether this current stack frame previously recorded an enter.

…CONTEXT
- fix flag detection for both variants
- insert enter_profiling only when not inlining, to match leave event behavior
…e_exception_internal - also when MONO_EXCEPTION_CLAUSE_NONE
- more asserts in browser profiler
Comment threadsrc/mono/mono/mini/interp/transform.c Outdated
@BrzVlad

Copy link
Copy Markdown
Member

LGTM

@pavelsavara

Copy link
Copy Markdown
MemberAuthor

/ba-g CI timeouts and known issues

@pavelsavara
pavelsavara merged commit 0473604 into dotnet:mainFeb 25, 2025
@pavelsavara
pavelsavara deleted the mono_profile_safepoint branch February 25, 2025 13:47

@lateralusXlateralusX left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like I was late to the party (was OOF last week), but add my comments/questions here anyways, either for future follow ups or PR's.

#define INTERP_PROFILER_RAISE(name_lower, name_upper) \
if ((flag & TRACING_FLAG) || ((flag & PROFILING_FLAG) && MONO_PROFILER_ENABLED (method_ ## name_lower) && \
(frame->imethod->prof_flags & (MONO_PROFILER_CALL_INSTRUMENTATION_ ## name_upper ## _CONTEXT | MONO_PROFILER_CALL_INSTRUMENTATION_ ## name_upper)))) { \
MonoProfilerCallContext *prof_ctx = g_new0 (MonoProfilerCallContext, 1);\

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to heap allocate this? Looks like stack allocation should be enough, unless we are afraid consuming stack space since the MonoProfilerCallContext that includes MonoContext.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the place that possibly deals with MonoProfilerCallContext -> context

emit_fill_call_ctx (MonoCompile*cfg, MonoInst*method, MonoInst*ret)

What would happen if we removed context field completely ?

Comment threadsrc/mono/mono/mini/mini-exceptions.c
return res;
}

EMSCRIPTEN_KEEPALIVE const char * mono_wasm_method_get_name_ex (MonoMethod *method) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you hand out ownership of this pointer and expect others to free it, it shouldn't be const.

Comment threadsrc/mono/mono/profiler/browser.c
Comment threadsrc/mono/mono/profiler/browser.c
Comment threadsrc/mono/mono/profiler/browser.c
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

arch-wasmWebAssembly architecturearea-Diagnostics-monoos-browserBrowser variant of arch-wasm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@pavelsavara@hakenr@kg@BrzVlad@kotlarmilos@lateralusX
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

[browser] samplepoint instrumentation into Mono profiler - #112352

Merged
pavelsavara merged 30 commits into
dotnet:mainfrom
pavelsavara:mono_profile_safepoint
Feb 25, 2025
Merged

[browser] samplepoint instrumentation into Mono profiler#112352
pavelsavara merged 30 commits into
dotnet:mainfrom
pavelsavara:mono_profile_safepoint

Conversation

@pavelsavara

@pavelsavarapavelsavara commented Feb 10, 2025

Copy link
Copy Markdown
Member

Motivation

Collect sample stack traces in the single-threaded browser.
Single threaded environment can't have dedicated thread for "stop-the-world + collect stack traces" implementation.
So we need to collect samples by instrumenting the code instead.

Previously we implemented browser based profiler which collects "sample" on every method entry/leave. In realistic application, that generates too much traffic for the browser profiler.

This enhancement could also be used to implement sampling profiler with ST diagnostic server.

Goal

  • add new samplepoint instrumentation into Mono profiler, use the existing enter/leave too.
  • inject the event/call into same locations as GC safepoint.
    • we can skip emitting it at the method start, because there is already method_enter event/instrumentation.
    • This is also same location where cooperative MT would stop-the-world to take a sample.
  • take sample on each Nth visit to the samplepoint (because measuring current time is more expensive)
  • keep updating the size of N to match expected sample time interval

Contributes to #76316

Non-goal: the browser profiler is not compatible with MT.

Changes

Interop

  • new interp IR instruction MINT_PROF_SAMPLEPOINT
  • new profiler event method_samplepoint
  • new interp macro INTERP_PROFILER_RAISE

Jiterp

  • new jiterp helper append_profiler_event calling new mono_jiterp_prof_enter, mono_jiterp_prof_samplepoint, mono_jiterp_prof_leave

AOT

  • new Mono JIT method mini_profiler_emit_samplepoint
  • example of code emitted by AOT with call $mono_profiler_raise_method_samplepoint

browser profiler

  • new config option sampleIntervalMs for browser profiler. 1ms by default. All samples when set to 0.
  • changes JavaScript called only to get time mono_wasm_profiler_now and mono_wasm_profiler_record
  • all sampling logic in browser.c new should_record_frame
  • stack of start times profiler_stack_frames and skip counter sample_skip_counter
  • default sample interval 10ms
  • callspec parametr to profiler allows to filter the instrumentation, for example by namespace callspec=N:Sample.FooNs

How to use

Interpreter

  • dotnet workload install wasm-tools

In the project file

<WasmProfilers>browser;</WasmProfilers>

In your main.js

import{dotnet}from'./dotnet.js'dotnet.withConfig({browserProfilerOptions: {sampleIntervalMs: 10,// optional}}).create();

or in your Blazor app

Blazor.start({configureRuntime: function(builder){builder.withConfig({browserProfilerOptions: {sampleIntervalMs: 10,// optional}});}});

AOT

You can add following, so that the native code produced by AOT has symbols, which are visible in the browser devtools profiler.

<RunAOTCompilation>true</RunAOTCompilation>
<RunAOTCompilationAfterBuild>true</RunAOTCompilationAfterBuild>
<WasmNativeStrip>false</WasmNativeStrip>
<WasmProfilers>browser</WasmProfilers>
<WasmNativeDebugSymbols>true</WasmNativeDebugSymbols>
<WasmBuildNative>true</WasmBuildNative>

callspec

If you want to filter profiled methods, you can use callspec

Callspec is documented here https://github.com/dotnet/runtime/blob/main/docs/design/mono/diagnostics-tracing.md#trace-monovm-profiler-events-during-startup

browserProfilerOptions: {callSpec: "N:SampleNamespace"// optional}

Your for AOT needs to match callspec in the browserProfilerOptions in JS

<WasmProfilers>browser:callspec=N:Sample;</WasmProfilers>

@pavelsavarapavelsavara added arch-wasm WebAssembly architecture area-Diagnostics-mono os-browser Browser variant of arch-wasm labels Feb 10, 2025
@pavelsavarapavelsavara added this to the 10.0.0 milestone Feb 10, 2025
@pavelsavarapavelsavara self-assigned this Feb 10, 2025
@pavelsavarapavelsavara changed the title [browser] add sample point instrumentation into Mono profiler[browser] Sample point instrumentation into Mono profilerFeb 10, 2025
@pavelsavara

pavelsavara commented Feb 10, 2025

Copy link
Copy Markdown
MemberAuthor

Interpreter: 1ms, skip rate between 200-500 sample points.
image

@pavelsavara

This comment was marked as resolved.

@pavelsavara

This comment was marked as resolved.

@hakenr

Copy link
Copy Markdown
Member

Hi @pavelsavara,
I'm in! I'll try to reach you via Teams.

Comment threadsrc/mono/mono/mini/mini.c
@pavelsavara

Copy link
Copy Markdown
MemberAuthor

My testing shows that enter/leave events are unbalanced.
I think my adding samplepoint in instrumentation didn't change that.
But the design of measuring the difference between method entry and method end requires that the events are balanced.

I will test it further to see if this could be fixed in the instrumentation or if this must be detected and survived by the receiving side.

@kg

kg commented Feb 21, 2025

Copy link
Copy Markdown
Contributor

I think you can't randomly record only a % of enter/leave events, or if you do you need to ensure that they are balanced - for example if you record an entry for method x the matching return also needs to be recorded. So you can only pick a % of enters to record but you need to do the check for every LEAVE as to whether this current stack frame previously recorded an enter.

…CONTEXT
- fix flag detection for both variants
- insert enter_profiling only when not inlining, to match leave event behavior
…e_exception_internal - also when MONO_EXCEPTION_CLAUSE_NONE
- more asserts in browser profiler
Comment threadsrc/mono/mono/mini/interp/transform.c Outdated
@BrzVlad

Copy link
Copy Markdown
Member

LGTM

@pavelsavara

Copy link
Copy Markdown
MemberAuthor

/ba-g CI timeouts and known issues

@pavelsavara
pavelsavara merged commit 0473604 into dotnet:mainFeb 25, 2025
@pavelsavara
pavelsavara deleted the mono_profile_safepoint branch February 25, 2025 13:47

@lateralusXlateralusX left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like I was late to the party (was OOF last week), but add my comments/questions here anyways, either for future follow ups or PR's.

#define INTERP_PROFILER_RAISE(name_lower, name_upper) \
if ((flag & TRACING_FLAG) || ((flag & PROFILING_FLAG) && MONO_PROFILER_ENABLED (method_ ## name_lower) && \
(frame->imethod->prof_flags & (MONO_PROFILER_CALL_INSTRUMENTATION_ ## name_upper ## _CONTEXT | MONO_PROFILER_CALL_INSTRUMENTATION_ ## name_upper)))) { \
MonoProfilerCallContext *prof_ctx = g_new0 (MonoProfilerCallContext, 1);\

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to heap allocate this? Looks like stack allocation should be enough, unless we are afraid consuming stack space since the MonoProfilerCallContext that includes MonoContext.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the place that possibly deals with MonoProfilerCallContext -> context

emit_fill_call_ctx (MonoCompile*cfg, MonoInst*method, MonoInst*ret)

What would happen if we removed context field completely ?

Comment threadsrc/mono/mono/mini/mini-exceptions.c
return res;
}

EMSCRIPTEN_KEEPALIVE const char * mono_wasm_method_get_name_ex (MonoMethod *method) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you hand out ownership of this pointer and expect others to free it, it shouldn't be const.

Comment threadsrc/mono/mono/profiler/browser.c
Comment threadsrc/mono/mono/profiler/browser.c
Comment threadsrc/mono/mono/profiler/browser.c
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

arch-wasmWebAssembly architecturearea-Diagnostics-monoos-browserBrowser variant of arch-wasm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@pavelsavara@hakenr@kg@BrzVlad@kotlarmilos@lateralusX
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[browser] samplepoint instrumentation into Mono profiler - #112352

Merged
pavelsavara merged 30 commits into
dotnet:mainfrom
pavelsavara:mono_profile_safepoint
Feb 25, 2025
Merged

[browser] samplepoint instrumentation into Mono profiler#112352
pavelsavara merged 30 commits into
dotnet:mainfrom
pavelsavara:mono_profile_safepoint

Conversation

@pavelsavara

@pavelsavarapavelsavara commented Feb 10, 2025

Copy link
Copy Markdown
Member

Motivation

Collect sample stack traces in the single-threaded browser.
Single threaded environment can't have dedicated thread for "stop-the-world + collect stack traces" implementation.
So we need to collect samples by instrumenting the code instead.

Previously we implemented browser based profiler which collects "sample" on every method entry/leave. In realistic application, that generates too much traffic for the browser profiler.

This enhancement could also be used to implement sampling profiler with ST diagnostic server.

Goal

  • add new samplepoint instrumentation into Mono profiler, use the existing enter/leave too.
  • inject the event/call into same locations as GC safepoint.
    • we can skip emitting it at the method start, because there is already method_enter event/instrumentation.
    • This is also same location where cooperative MT would stop-the-world to take a sample.
  • take sample on each Nth visit to the samplepoint (because measuring current time is more expensive)
  • keep updating the size of N to match expected sample time interval

Contributes to #76316

Non-goal: the browser profiler is not compatible with MT.

Changes

Interop

  • new interp IR instruction MINT_PROF_SAMPLEPOINT
  • new profiler event method_samplepoint
  • new interp macro INTERP_PROFILER_RAISE

Jiterp

  • new jiterp helper append_profiler_event calling new mono_jiterp_prof_enter, mono_jiterp_prof_samplepoint, mono_jiterp_prof_leave

AOT

  • new Mono JIT method mini_profiler_emit_samplepoint
  • example of code emitted by AOT with call $mono_profiler_raise_method_samplepoint

browser profiler

  • new config option sampleIntervalMs for browser profiler. 1ms by default. All samples when set to 0.
  • changes JavaScript called only to get time mono_wasm_profiler_now and mono_wasm_profiler_record
  • all sampling logic in browser.c new should_record_frame
  • stack of start times profiler_stack_frames and skip counter sample_skip_counter
  • default sample interval 10ms
  • callspec parametr to profiler allows to filter the instrumentation, for example by namespace callspec=N:Sample.FooNs

How to use

Interpreter

  • dotnet workload install wasm-tools

In the project file

<WasmProfilers>browser;</WasmProfilers>

In your main.js

import{dotnet}from'./dotnet.js'dotnet.withConfig({browserProfilerOptions: {sampleIntervalMs: 10,// optional}}).create();

or in your Blazor app

Blazor.start({configureRuntime: function(builder){builder.withConfig({browserProfilerOptions: {sampleIntervalMs: 10,// optional}});}});

AOT

You can add following, so that the native code produced by AOT has symbols, which are visible in the browser devtools profiler.

<RunAOTCompilation>true</RunAOTCompilation>
<RunAOTCompilationAfterBuild>true</RunAOTCompilationAfterBuild>
<WasmNativeStrip>false</WasmNativeStrip>
<WasmProfilers>browser</WasmProfilers>
<WasmNativeDebugSymbols>true</WasmNativeDebugSymbols>
<WasmBuildNative>true</WasmBuildNative>

callspec

If you want to filter profiled methods, you can use callspec

Callspec is documented here https://github.com/dotnet/runtime/blob/main/docs/design/mono/diagnostics-tracing.md#trace-monovm-profiler-events-during-startup

browserProfilerOptions: {callSpec: "N:SampleNamespace"// optional}

Your for AOT needs to match callspec in the browserProfilerOptions in JS

<WasmProfilers>browser:callspec=N:Sample;</WasmProfilers>

@pavelsavarapavelsavara added arch-wasm WebAssembly architecture area-Diagnostics-mono os-browser Browser variant of arch-wasm labels Feb 10, 2025
@pavelsavarapavelsavara added this to the 10.0.0 milestone Feb 10, 2025
@pavelsavarapavelsavara self-assigned this Feb 10, 2025
@pavelsavarapavelsavara changed the title [browser] add sample point instrumentation into Mono profiler[browser] Sample point instrumentation into Mono profilerFeb 10, 2025
@pavelsavara

pavelsavara commented Feb 10, 2025

Copy link
Copy Markdown
MemberAuthor

Interpreter: 1ms, skip rate between 200-500 sample points.
image

@pavelsavara

This comment was marked as resolved.

@pavelsavara

This comment was marked as resolved.

@hakenr

Copy link
Copy Markdown
Member

Hi @pavelsavara,
I'm in! I'll try to reach you via Teams.

Comment threadsrc/mono/mono/mini/mini.c
@pavelsavara

Copy link
Copy Markdown
MemberAuthor

My testing shows that enter/leave events are unbalanced.
I think my adding samplepoint in instrumentation didn't change that.
But the design of measuring the difference between method entry and method end requires that the events are balanced.

I will test it further to see if this could be fixed in the instrumentation or if this must be detected and survived by the receiving side.

@kg

kg commented Feb 21, 2025

Copy link
Copy Markdown
Contributor

I think you can't randomly record only a % of enter/leave events, or if you do you need to ensure that they are balanced - for example if you record an entry for method x the matching return also needs to be recorded. So you can only pick a % of enters to record but you need to do the check for every LEAVE as to whether this current stack frame previously recorded an enter.

…CONTEXT
- fix flag detection for both variants
- insert enter_profiling only when not inlining, to match leave event behavior
…e_exception_internal - also when MONO_EXCEPTION_CLAUSE_NONE
- more asserts in browser profiler
Comment threadsrc/mono/mono/mini/interp/transform.c Outdated
@BrzVlad

Copy link
Copy Markdown
Member

LGTM

@pavelsavara

Copy link
Copy Markdown
MemberAuthor

/ba-g CI timeouts and known issues

@pavelsavara
pavelsavara merged commit 0473604 into dotnet:mainFeb 25, 2025
@pavelsavara
pavelsavara deleted the mono_profile_safepoint branch February 25, 2025 13:47

@lateralusXlateralusX left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like I was late to the party (was OOF last week), but add my comments/questions here anyways, either for future follow ups or PR's.

#define INTERP_PROFILER_RAISE(name_lower, name_upper) \
if ((flag & TRACING_FLAG) || ((flag & PROFILING_FLAG) && MONO_PROFILER_ENABLED (method_ ## name_lower) && \
(frame->imethod->prof_flags & (MONO_PROFILER_CALL_INSTRUMENTATION_ ## name_upper ## _CONTEXT | MONO_PROFILER_CALL_INSTRUMENTATION_ ## name_upper)))) { \
MonoProfilerCallContext *prof_ctx = g_new0 (MonoProfilerCallContext, 1);\

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to heap allocate this? Looks like stack allocation should be enough, unless we are afraid consuming stack space since the MonoProfilerCallContext that includes MonoContext.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the place that possibly deals with MonoProfilerCallContext -> context

emit_fill_call_ctx (MonoCompile*cfg, MonoInst*method, MonoInst*ret)

What would happen if we removed context field completely ?

Comment threadsrc/mono/mono/mini/mini-exceptions.c
return res;
}

EMSCRIPTEN_KEEPALIVE const char * mono_wasm_method_get_name_ex (MonoMethod *method) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you hand out ownership of this pointer and expect others to free it, it shouldn't be const.

Comment threadsrc/mono/mono/profiler/browser.c
Comment threadsrc/mono/mono/profiler/browser.c
Comment threadsrc/mono/mono/profiler/browser.c
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

arch-wasmWebAssembly architecturearea-Diagnostics-monoos-browserBrowser variant of arch-wasm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@pavelsavara@hakenr@kg@BrzVlad@kotlarmilos@lateralusX
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[browser] samplepoint instrumentation into Mono profiler - #112352

Merged
pavelsavara merged 30 commits into
dotnet:mainfrom
pavelsavara:mono_profile_safepoint
Feb 25, 2025
Merged

[browser] samplepoint instrumentation into Mono profiler#112352
pavelsavara merged 30 commits into
dotnet:mainfrom
pavelsavara:mono_profile_safepoint

Conversation

@pavelsavara

@pavelsavarapavelsavara commented Feb 10, 2025

Copy link
Copy Markdown
Member

Motivation

Collect sample stack traces in the single-threaded browser.
Single threaded environment can't have dedicated thread for "stop-the-world + collect stack traces" implementation.
So we need to collect samples by instrumenting the code instead.

Previously we implemented browser based profiler which collects "sample" on every method entry/leave. In realistic application, that generates too much traffic for the browser profiler.

This enhancement could also be used to implement sampling profiler with ST diagnostic server.

Goal

  • add new samplepoint instrumentation into Mono profiler, use the existing enter/leave too.
  • inject the event/call into same locations as GC safepoint.
    • we can skip emitting it at the method start, because there is already method_enter event/instrumentation.
    • This is also same location where cooperative MT would stop-the-world to take a sample.
  • take sample on each Nth visit to the samplepoint (because measuring current time is more expensive)
  • keep updating the size of N to match expected sample time interval

Contributes to #76316

Non-goal: the browser profiler is not compatible with MT.

Changes

Interop

  • new interp IR instruction MINT_PROF_SAMPLEPOINT
  • new profiler event method_samplepoint
  • new interp macro INTERP_PROFILER_RAISE

Jiterp

  • new jiterp helper append_profiler_event calling new mono_jiterp_prof_enter, mono_jiterp_prof_samplepoint, mono_jiterp_prof_leave

AOT

  • new Mono JIT method mini_profiler_emit_samplepoint
  • example of code emitted by AOT with call $mono_profiler_raise_method_samplepoint

browser profiler

  • new config option sampleIntervalMs for browser profiler. 1ms by default. All samples when set to 0.
  • changes JavaScript called only to get time mono_wasm_profiler_now and mono_wasm_profiler_record
  • all sampling logic in browser.c new should_record_frame
  • stack of start times profiler_stack_frames and skip counter sample_skip_counter
  • default sample interval 10ms
  • callspec parametr to profiler allows to filter the instrumentation, for example by namespace callspec=N:Sample.FooNs

How to use

Interpreter

  • dotnet workload install wasm-tools

In the project file

<WasmProfilers>browser;</WasmProfilers>

In your main.js

import{dotnet}from'./dotnet.js'dotnet.withConfig({browserProfilerOptions: {sampleIntervalMs: 10,// optional}}).create();

or in your Blazor app

Blazor.start({configureRuntime: function(builder){builder.withConfig({browserProfilerOptions: {sampleIntervalMs: 10,// optional}});}});

AOT

You can add following, so that the native code produced by AOT has symbols, which are visible in the browser devtools profiler.

<RunAOTCompilation>true</RunAOTCompilation>
<RunAOTCompilationAfterBuild>true</RunAOTCompilationAfterBuild>
<WasmNativeStrip>false</WasmNativeStrip>
<WasmProfilers>browser</WasmProfilers>
<WasmNativeDebugSymbols>true</WasmNativeDebugSymbols>
<WasmBuildNative>true</WasmBuildNative>

callspec

If you want to filter profiled methods, you can use callspec

Callspec is documented here https://github.com/dotnet/runtime/blob/main/docs/design/mono/diagnostics-tracing.md#trace-monovm-profiler-events-during-startup

browserProfilerOptions: {callSpec: "N:SampleNamespace"// optional}

Your for AOT needs to match callspec in the browserProfilerOptions in JS

<WasmProfilers>browser:callspec=N:Sample;</WasmProfilers>

@pavelsavarapavelsavara added arch-wasm WebAssembly architecture area-Diagnostics-mono os-browser Browser variant of arch-wasm labels Feb 10, 2025
@pavelsavarapavelsavara added this to the 10.0.0 milestone Feb 10, 2025
@pavelsavarapavelsavara self-assigned this Feb 10, 2025
@pavelsavarapavelsavara changed the title [browser] add sample point instrumentation into Mono profiler[browser] Sample point instrumentation into Mono profilerFeb 10, 2025
@pavelsavara

pavelsavara commented Feb 10, 2025

Copy link
Copy Markdown
MemberAuthor

Interpreter: 1ms, skip rate between 200-500 sample points.
image

@pavelsavara

This comment was marked as resolved.

@pavelsavara

This comment was marked as resolved.

@hakenr

Copy link
Copy Markdown
Member

Hi @pavelsavara,
I'm in! I'll try to reach you via Teams.

Comment threadsrc/mono/mono/mini/mini.c
@pavelsavara

Copy link
Copy Markdown
MemberAuthor

My testing shows that enter/leave events are unbalanced.
I think my adding samplepoint in instrumentation didn't change that.
But the design of measuring the difference between method entry and method end requires that the events are balanced.

I will test it further to see if this could be fixed in the instrumentation or if this must be detected and survived by the receiving side.

@kg

kg commented Feb 21, 2025

Copy link
Copy Markdown
Contributor

I think you can't randomly record only a % of enter/leave events, or if you do you need to ensure that they are balanced - for example if you record an entry for method x the matching return also needs to be recorded. So you can only pick a % of enters to record but you need to do the check for every LEAVE as to whether this current stack frame previously recorded an enter.

…CONTEXT
- fix flag detection for both variants
- insert enter_profiling only when not inlining, to match leave event behavior
…e_exception_internal - also when MONO_EXCEPTION_CLAUSE_NONE
- more asserts in browser profiler
Comment threadsrc/mono/mono/mini/interp/transform.c Outdated
@BrzVlad

Copy link
Copy Markdown
Member

LGTM

@pavelsavara

Copy link
Copy Markdown
MemberAuthor

/ba-g CI timeouts and known issues

@pavelsavara
pavelsavara merged commit 0473604 into dotnet:mainFeb 25, 2025
@pavelsavara
pavelsavara deleted the mono_profile_safepoint branch February 25, 2025 13:47

@lateralusXlateralusX left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like I was late to the party (was OOF last week), but add my comments/questions here anyways, either for future follow ups or PR's.

#define INTERP_PROFILER_RAISE(name_lower, name_upper) \
if ((flag & TRACING_FLAG) || ((flag & PROFILING_FLAG) && MONO_PROFILER_ENABLED (method_ ## name_lower) && \
(frame->imethod->prof_flags & (MONO_PROFILER_CALL_INSTRUMENTATION_ ## name_upper ## _CONTEXT | MONO_PROFILER_CALL_INSTRUMENTATION_ ## name_upper)))) { \
MonoProfilerCallContext *prof_ctx = g_new0 (MonoProfilerCallContext, 1);\

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to heap allocate this? Looks like stack allocation should be enough, unless we are afraid consuming stack space since the MonoProfilerCallContext that includes MonoContext.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the place that possibly deals with MonoProfilerCallContext -> context

emit_fill_call_ctx (MonoCompile*cfg, MonoInst*method, MonoInst*ret)

What would happen if we removed context field completely ?

Comment threadsrc/mono/mono/mini/mini-exceptions.c
return res;
}

EMSCRIPTEN_KEEPALIVE const char * mono_wasm_method_get_name_ex (MonoMethod *method) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you hand out ownership of this pointer and expect others to free it, it shouldn't be const.

Comment threadsrc/mono/mono/profiler/browser.c
Comment threadsrc/mono/mono/profiler/browser.c
Comment threadsrc/mono/mono/profiler/browser.c
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

arch-wasmWebAssembly architecturearea-Diagnostics-monoos-browserBrowser variant of arch-wasm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@pavelsavara@hakenr@kg@BrzVlad@kotlarmilos@lateralusX
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

[browser] samplepoint instrumentation into Mono profiler - #112352

Merged
pavelsavara merged 30 commits into
dotnet:mainfrom
pavelsavara:mono_profile_safepoint
Feb 25, 2025
Merged

[browser] samplepoint instrumentation into Mono profiler#112352
pavelsavara merged 30 commits into
dotnet:mainfrom
pavelsavara:mono_profile_safepoint

Conversation

@pavelsavara

@pavelsavarapavelsavara commented Feb 10, 2025

Copy link
Copy Markdown
Member

Motivation

Collect sample stack traces in the single-threaded browser.
Single threaded environment can't have dedicated thread for "stop-the-world + collect stack traces" implementation.
So we need to collect samples by instrumenting the code instead.

Previously we implemented browser based profiler which collects "sample" on every method entry/leave. In realistic application, that generates too much traffic for the browser profiler.

This enhancement could also be used to implement sampling profiler with ST diagnostic server.

Goal

  • add new samplepoint instrumentation into Mono profiler, use the existing enter/leave too.
  • inject the event/call into same locations as GC safepoint.
    • we can skip emitting it at the method start, because there is already method_enter event/instrumentation.
    • This is also same location where cooperative MT would stop-the-world to take a sample.
  • take sample on each Nth visit to the samplepoint (because measuring current time is more expensive)
  • keep updating the size of N to match expected sample time interval

Contributes to #76316

Non-goal: the browser profiler is not compatible with MT.

Changes

Interop

  • new interp IR instruction MINT_PROF_SAMPLEPOINT
  • new profiler event method_samplepoint
  • new interp macro INTERP_PROFILER_RAISE

Jiterp

  • new jiterp helper append_profiler_event calling new mono_jiterp_prof_enter, mono_jiterp_prof_samplepoint, mono_jiterp_prof_leave

AOT

  • new Mono JIT method mini_profiler_emit_samplepoint
  • example of code emitted by AOT with call $mono_profiler_raise_method_samplepoint

browser profiler

  • new config option sampleIntervalMs for browser profiler. 1ms by default. All samples when set to 0.
  • changes JavaScript called only to get time mono_wasm_profiler_now and mono_wasm_profiler_record
  • all sampling logic in browser.c new should_record_frame
  • stack of start times profiler_stack_frames and skip counter sample_skip_counter
  • default sample interval 10ms
  • callspec parametr to profiler allows to filter the instrumentation, for example by namespace callspec=N:Sample.FooNs

How to use

Interpreter

  • dotnet workload install wasm-tools

In the project file

<WasmProfilers>browser;</WasmProfilers>

In your main.js

import{dotnet}from'./dotnet.js'dotnet.withConfig({browserProfilerOptions: {sampleIntervalMs: 10,// optional}}).create();

or in your Blazor app

Blazor.start({configureRuntime: function(builder){builder.withConfig({browserProfilerOptions: {sampleIntervalMs: 10,// optional}});}});

AOT

You can add following, so that the native code produced by AOT has symbols, which are visible in the browser devtools profiler.

<RunAOTCompilation>true</RunAOTCompilation>
<RunAOTCompilationAfterBuild>true</RunAOTCompilationAfterBuild>
<WasmNativeStrip>false</WasmNativeStrip>
<WasmProfilers>browser</WasmProfilers>
<WasmNativeDebugSymbols>true</WasmNativeDebugSymbols>
<WasmBuildNative>true</WasmBuildNative>

callspec

If you want to filter profiled methods, you can use callspec

Callspec is documented here https://github.com/dotnet/runtime/blob/main/docs/design/mono/diagnostics-tracing.md#trace-monovm-profiler-events-during-startup

browserProfilerOptions: {callSpec: "N:SampleNamespace"// optional}

Your for AOT needs to match callspec in the browserProfilerOptions in JS

<WasmProfilers>browser:callspec=N:Sample;</WasmProfilers>

@pavelsavarapavelsavara added arch-wasm WebAssembly architecture area-Diagnostics-mono os-browser Browser variant of arch-wasm labels Feb 10, 2025
@pavelsavarapavelsavara added this to the 10.0.0 milestone Feb 10, 2025
@pavelsavarapavelsavara self-assigned this Feb 10, 2025
@pavelsavarapavelsavara changed the title [browser] add sample point instrumentation into Mono profiler[browser] Sample point instrumentation into Mono profilerFeb 10, 2025
@pavelsavara

pavelsavara commented Feb 10, 2025

Copy link
Copy Markdown
MemberAuthor

Interpreter: 1ms, skip rate between 200-500 sample points.
image

@pavelsavara

This comment was marked as resolved.

@pavelsavara

This comment was marked as resolved.

@hakenr

Copy link
Copy Markdown
Member

Hi @pavelsavara,
I'm in! I'll try to reach you via Teams.

Comment threadsrc/mono/mono/mini/mini.c
@pavelsavara

Copy link
Copy Markdown
MemberAuthor

My testing shows that enter/leave events are unbalanced.
I think my adding samplepoint in instrumentation didn't change that.
But the design of measuring the difference between method entry and method end requires that the events are balanced.

I will test it further to see if this could be fixed in the instrumentation or if this must be detected and survived by the receiving side.

@kg

kg commented Feb 21, 2025

Copy link
Copy Markdown
Contributor

I think you can't randomly record only a % of enter/leave events, or if you do you need to ensure that they are balanced - for example if you record an entry for method x the matching return also needs to be recorded. So you can only pick a % of enters to record but you need to do the check for every LEAVE as to whether this current stack frame previously recorded an enter.

…CONTEXT
- fix flag detection for both variants
- insert enter_profiling only when not inlining, to match leave event behavior
…e_exception_internal - also when MONO_EXCEPTION_CLAUSE_NONE
- more asserts in browser profiler
Comment threadsrc/mono/mono/mini/interp/transform.c Outdated
@BrzVlad

Copy link
Copy Markdown
Member

LGTM

@pavelsavara

Copy link
Copy Markdown
MemberAuthor

/ba-g CI timeouts and known issues

@pavelsavara
pavelsavara merged commit 0473604 into dotnet:mainFeb 25, 2025
@pavelsavara
pavelsavara deleted the mono_profile_safepoint branch February 25, 2025 13:47

@lateralusXlateralusX left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like I was late to the party (was OOF last week), but add my comments/questions here anyways, either for future follow ups or PR's.

#define INTERP_PROFILER_RAISE(name_lower, name_upper) \
if ((flag & TRACING_FLAG) || ((flag & PROFILING_FLAG) && MONO_PROFILER_ENABLED (method_ ## name_lower) && \
(frame->imethod->prof_flags & (MONO_PROFILER_CALL_INSTRUMENTATION_ ## name_upper ## _CONTEXT | MONO_PROFILER_CALL_INSTRUMENTATION_ ## name_upper)))) { \
MonoProfilerCallContext *prof_ctx = g_new0 (MonoProfilerCallContext, 1);\

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to heap allocate this? Looks like stack allocation should be enough, unless we are afraid consuming stack space since the MonoProfilerCallContext that includes MonoContext.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the place that possibly deals with MonoProfilerCallContext -> context

emit_fill_call_ctx (MonoCompile*cfg, MonoInst*method, MonoInst*ret)

What would happen if we removed context field completely ?

Comment threadsrc/mono/mono/mini/mini-exceptions.c
return res;
}

EMSCRIPTEN_KEEPALIVE const char * mono_wasm_method_get_name_ex (MonoMethod *method) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you hand out ownership of this pointer and expect others to free it, it shouldn't be const.

Comment threadsrc/mono/mono/profiler/browser.c
Comment threadsrc/mono/mono/profiler/browser.c
Comment threadsrc/mono/mono/profiler/browser.c
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

arch-wasmWebAssembly architecturearea-Diagnostics-monoos-browserBrowser variant of arch-wasm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@pavelsavara@hakenr@kg@BrzVlad@kotlarmilos@lateralusX
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[browser] samplepoint instrumentation into Mono profiler - #112352

Merged
pavelsavara merged 30 commits into
dotnet:mainfrom
pavelsavara:mono_profile_safepoint
Feb 25, 2025
Merged

[browser] samplepoint instrumentation into Mono profiler#112352
pavelsavara merged 30 commits into
dotnet:mainfrom
pavelsavara:mono_profile_safepoint

Conversation

@pavelsavara

@pavelsavarapavelsavara commented Feb 10, 2025

Copy link
Copy Markdown
Member

Motivation

Collect sample stack traces in the single-threaded browser.
Single threaded environment can't have dedicated thread for "stop-the-world + collect stack traces" implementation.
So we need to collect samples by instrumenting the code instead.

Previously we implemented browser based profiler which collects "sample" on every method entry/leave. In realistic application, that generates too much traffic for the browser profiler.

This enhancement could also be used to implement sampling profiler with ST diagnostic server.

Goal

  • add new samplepoint instrumentation into Mono profiler, use the existing enter/leave too.
  • inject the event/call into same locations as GC safepoint.
    • we can skip emitting it at the method start, because there is already method_enter event/instrumentation.
    • This is also same location where cooperative MT would stop-the-world to take a sample.
  • take sample on each Nth visit to the samplepoint (because measuring current time is more expensive)
  • keep updating the size of N to match expected sample time interval

Contributes to #76316

Non-goal: the browser profiler is not compatible with MT.

Changes

Interop

  • new interp IR instruction MINT_PROF_SAMPLEPOINT
  • new profiler event method_samplepoint
  • new interp macro INTERP_PROFILER_RAISE

Jiterp

  • new jiterp helper append_profiler_event calling new mono_jiterp_prof_enter, mono_jiterp_prof_samplepoint, mono_jiterp_prof_leave

AOT

  • new Mono JIT method mini_profiler_emit_samplepoint
  • example of code emitted by AOT with call $mono_profiler_raise_method_samplepoint

browser profiler

  • new config option sampleIntervalMs for browser profiler. 1ms by default. All samples when set to 0.
  • changes JavaScript called only to get time mono_wasm_profiler_now and mono_wasm_profiler_record
  • all sampling logic in browser.c new should_record_frame
  • stack of start times profiler_stack_frames and skip counter sample_skip_counter
  • default sample interval 10ms
  • callspec parametr to profiler allows to filter the instrumentation, for example by namespace callspec=N:Sample.FooNs

How to use

Interpreter

  • dotnet workload install wasm-tools

In the project file

<WasmProfilers>browser;</WasmProfilers>

In your main.js

import{dotnet}from'./dotnet.js'dotnet.withConfig({browserProfilerOptions: {sampleIntervalMs: 10,// optional}}).create();

or in your Blazor app

Blazor.start({configureRuntime: function(builder){builder.withConfig({browserProfilerOptions: {sampleIntervalMs: 10,// optional}});}});

AOT

You can add following, so that the native code produced by AOT has symbols, which are visible in the browser devtools profiler.

<RunAOTCompilation>true</RunAOTCompilation>
<RunAOTCompilationAfterBuild>true</RunAOTCompilationAfterBuild>
<WasmNativeStrip>false</WasmNativeStrip>
<WasmProfilers>browser</WasmProfilers>
<WasmNativeDebugSymbols>true</WasmNativeDebugSymbols>
<WasmBuildNative>true</WasmBuildNative>

callspec

If you want to filter profiled methods, you can use callspec

Callspec is documented here https://github.com/dotnet/runtime/blob/main/docs/design/mono/diagnostics-tracing.md#trace-monovm-profiler-events-during-startup

browserProfilerOptions: {callSpec: "N:SampleNamespace"// optional}

Your for AOT needs to match callspec in the browserProfilerOptions in JS

<WasmProfilers>browser:callspec=N:Sample;</WasmProfilers>

@pavelsavarapavelsavara added arch-wasm WebAssembly architecture area-Diagnostics-mono os-browser Browser variant of arch-wasm labels Feb 10, 2025
@pavelsavarapavelsavara added this to the 10.0.0 milestone Feb 10, 2025
@pavelsavarapavelsavara self-assigned this Feb 10, 2025
@pavelsavarapavelsavara changed the title [browser] add sample point instrumentation into Mono profiler[browser] Sample point instrumentation into Mono profilerFeb 10, 2025
@pavelsavara

pavelsavara commented Feb 10, 2025

Copy link
Copy Markdown
MemberAuthor

Interpreter: 1ms, skip rate between 200-500 sample points.
image

@pavelsavara

This comment was marked as resolved.

@pavelsavara

This comment was marked as resolved.

@hakenr

Copy link
Copy Markdown
Member

Hi @pavelsavara,
I'm in! I'll try to reach you via Teams.

Comment threadsrc/mono/mono/mini/mini.c
@pavelsavara

Copy link
Copy Markdown
MemberAuthor

My testing shows that enter/leave events are unbalanced.
I think my adding samplepoint in instrumentation didn't change that.
But the design of measuring the difference between method entry and method end requires that the events are balanced.

I will test it further to see if this could be fixed in the instrumentation or if this must be detected and survived by the receiving side.

@kg

kg commented Feb 21, 2025

Copy link
Copy Markdown
Contributor

I think you can't randomly record only a % of enter/leave events, or if you do you need to ensure that they are balanced - for example if you record an entry for method x the matching return also needs to be recorded. So you can only pick a % of enters to record but you need to do the check for every LEAVE as to whether this current stack frame previously recorded an enter.

…CONTEXT
- fix flag detection for both variants
- insert enter_profiling only when not inlining, to match leave event behavior
…e_exception_internal - also when MONO_EXCEPTION_CLAUSE_NONE
- more asserts in browser profiler
Comment threadsrc/mono/mono/mini/interp/transform.c Outdated
@BrzVlad

Copy link
Copy Markdown
Member

LGTM

@pavelsavara

Copy link
Copy Markdown
MemberAuthor

/ba-g CI timeouts and known issues

@pavelsavara
pavelsavara merged commit 0473604 into dotnet:mainFeb 25, 2025
@pavelsavara
pavelsavara deleted the mono_profile_safepoint branch February 25, 2025 13:47

@lateralusXlateralusX left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like I was late to the party (was OOF last week), but add my comments/questions here anyways, either for future follow ups or PR's.

#define INTERP_PROFILER_RAISE(name_lower, name_upper) \
if ((flag & TRACING_FLAG) || ((flag & PROFILING_FLAG) && MONO_PROFILER_ENABLED (method_ ## name_lower) && \
(frame->imethod->prof_flags & (MONO_PROFILER_CALL_INSTRUMENTATION_ ## name_upper ## _CONTEXT | MONO_PROFILER_CALL_INSTRUMENTATION_ ## name_upper)))) { \
MonoProfilerCallContext *prof_ctx = g_new0 (MonoProfilerCallContext, 1);\

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to heap allocate this? Looks like stack allocation should be enough, unless we are afraid consuming stack space since the MonoProfilerCallContext that includes MonoContext.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the place that possibly deals with MonoProfilerCallContext -> context

emit_fill_call_ctx (MonoCompile*cfg, MonoInst*method, MonoInst*ret)

What would happen if we removed context field completely ?

Comment threadsrc/mono/mono/mini/mini-exceptions.c
return res;
}

EMSCRIPTEN_KEEPALIVE const char * mono_wasm_method_get_name_ex (MonoMethod *method) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you hand out ownership of this pointer and expect others to free it, it shouldn't be const.

Comment threadsrc/mono/mono/profiler/browser.c
Comment threadsrc/mono/mono/profiler/browser.c
Comment threadsrc/mono/mono/profiler/browser.c
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

arch-wasmWebAssembly architecturearea-Diagnostics-monoos-browserBrowser variant of arch-wasm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@pavelsavara@hakenr@kg@BrzVlad@kotlarmilos@lateralusX
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[browser] samplepoint instrumentation into Mono profiler - #112352

Merged
pavelsavara merged 30 commits into
dotnet:mainfrom
pavelsavara:mono_profile_safepoint
Feb 25, 2025
Merged

[browser] samplepoint instrumentation into Mono profiler#112352
pavelsavara merged 30 commits into
dotnet:mainfrom
pavelsavara:mono_profile_safepoint

Conversation

@pavelsavara

@pavelsavarapavelsavara commented Feb 10, 2025

Copy link
Copy Markdown
Member

Motivation

Collect sample stack traces in the single-threaded browser.
Single threaded environment can't have dedicated thread for "stop-the-world + collect stack traces" implementation.
So we need to collect samples by instrumenting the code instead.

Previously we implemented browser based profiler which collects "sample" on every method entry/leave. In realistic application, that generates too much traffic for the browser profiler.

This enhancement could also be used to implement sampling profiler with ST diagnostic server.

Goal

  • add new samplepoint instrumentation into Mono profiler, use the existing enter/leave too.
  • inject the event/call into same locations as GC safepoint.
    • we can skip emitting it at the method start, because there is already method_enter event/instrumentation.
    • This is also same location where cooperative MT would stop-the-world to take a sample.
  • take sample on each Nth visit to the samplepoint (because measuring current time is more expensive)
  • keep updating the size of N to match expected sample time interval

Contributes to #76316

Non-goal: the browser profiler is not compatible with MT.

Changes

Interop

  • new interp IR instruction MINT_PROF_SAMPLEPOINT
  • new profiler event method_samplepoint
  • new interp macro INTERP_PROFILER_RAISE

Jiterp

  • new jiterp helper append_profiler_event calling new mono_jiterp_prof_enter, mono_jiterp_prof_samplepoint, mono_jiterp_prof_leave

AOT

  • new Mono JIT method mini_profiler_emit_samplepoint
  • example of code emitted by AOT with call $mono_profiler_raise_method_samplepoint

browser profiler

  • new config option sampleIntervalMs for browser profiler. 1ms by default. All samples when set to 0.
  • changes JavaScript called only to get time mono_wasm_profiler_now and mono_wasm_profiler_record
  • all sampling logic in browser.c new should_record_frame
  • stack of start times profiler_stack_frames and skip counter sample_skip_counter
  • default sample interval 10ms
  • callspec parametr to profiler allows to filter the instrumentation, for example by namespace callspec=N:Sample.FooNs

How to use

Interpreter

  • dotnet workload install wasm-tools

In the project file

<WasmProfilers>browser;</WasmProfilers>

In your main.js

import{dotnet}from'./dotnet.js'dotnet.withConfig({browserProfilerOptions: {sampleIntervalMs: 10,// optional}}).create();

or in your Blazor app

Blazor.start({configureRuntime: function(builder){builder.withConfig({browserProfilerOptions: {sampleIntervalMs: 10,// optional}});}});

AOT

You can add following, so that the native code produced by AOT has symbols, which are visible in the browser devtools profiler.

<RunAOTCompilation>true</RunAOTCompilation>
<RunAOTCompilationAfterBuild>true</RunAOTCompilationAfterBuild>
<WasmNativeStrip>false</WasmNativeStrip>
<WasmProfilers>browser</WasmProfilers>
<WasmNativeDebugSymbols>true</WasmNativeDebugSymbols>
<WasmBuildNative>true</WasmBuildNative>

callspec

If you want to filter profiled methods, you can use callspec

Callspec is documented here https://github.com/dotnet/runtime/blob/main/docs/design/mono/diagnostics-tracing.md#trace-monovm-profiler-events-during-startup

browserProfilerOptions: {callSpec: "N:SampleNamespace"// optional}

Your for AOT needs to match callspec in the browserProfilerOptions in JS

<WasmProfilers>browser:callspec=N:Sample;</WasmProfilers>

@pavelsavarapavelsavara added arch-wasm WebAssembly architecture area-Diagnostics-mono os-browser Browser variant of arch-wasm labels Feb 10, 2025
@pavelsavarapavelsavara added this to the 10.0.0 milestone Feb 10, 2025
@pavelsavarapavelsavara self-assigned this Feb 10, 2025
@pavelsavarapavelsavara changed the title [browser] add sample point instrumentation into Mono profiler[browser] Sample point instrumentation into Mono profilerFeb 10, 2025
@pavelsavara

pavelsavara commented Feb 10, 2025

Copy link
Copy Markdown
MemberAuthor

Interpreter: 1ms, skip rate between 200-500 sample points.
image

@pavelsavara

This comment was marked as resolved.

@pavelsavara

This comment was marked as resolved.

@hakenr

Copy link
Copy Markdown
Member

Hi @pavelsavara,
I'm in! I'll try to reach you via Teams.

Comment threadsrc/mono/mono/mini/mini.c
@pavelsavara

Copy link
Copy Markdown
MemberAuthor

My testing shows that enter/leave events are unbalanced.
I think my adding samplepoint in instrumentation didn't change that.
But the design of measuring the difference between method entry and method end requires that the events are balanced.

I will test it further to see if this could be fixed in the instrumentation or if this must be detected and survived by the receiving side.

@kg

kg commented Feb 21, 2025

Copy link
Copy Markdown
Contributor

I think you can't randomly record only a % of enter/leave events, or if you do you need to ensure that they are balanced - for example if you record an entry for method x the matching return also needs to be recorded. So you can only pick a % of enters to record but you need to do the check for every LEAVE as to whether this current stack frame previously recorded an enter.

…CONTEXT
- fix flag detection for both variants
- insert enter_profiling only when not inlining, to match leave event behavior
…e_exception_internal - also when MONO_EXCEPTION_CLAUSE_NONE
- more asserts in browser profiler
Comment threadsrc/mono/mono/mini/interp/transform.c Outdated
@BrzVlad

Copy link
Copy Markdown
Member

LGTM

@pavelsavara

Copy link
Copy Markdown
MemberAuthor

/ba-g CI timeouts and known issues

@pavelsavara
pavelsavara merged commit 0473604 into dotnet:mainFeb 25, 2025
@pavelsavara
pavelsavara deleted the mono_profile_safepoint branch February 25, 2025 13:47

@lateralusXlateralusX left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like I was late to the party (was OOF last week), but add my comments/questions here anyways, either for future follow ups or PR's.

#define INTERP_PROFILER_RAISE(name_lower, name_upper) \
if ((flag & TRACING_FLAG) || ((flag & PROFILING_FLAG) && MONO_PROFILER_ENABLED (method_ ## name_lower) && \
(frame->imethod->prof_flags & (MONO_PROFILER_CALL_INSTRUMENTATION_ ## name_upper ## _CONTEXT | MONO_PROFILER_CALL_INSTRUMENTATION_ ## name_upper)))) { \
MonoProfilerCallContext *prof_ctx = g_new0 (MonoProfilerCallContext, 1);\

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to heap allocate this? Looks like stack allocation should be enough, unless we are afraid consuming stack space since the MonoProfilerCallContext that includes MonoContext.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the place that possibly deals with MonoProfilerCallContext -> context

emit_fill_call_ctx (MonoCompile*cfg, MonoInst*method, MonoInst*ret)

What would happen if we removed context field completely ?

Comment threadsrc/mono/mono/mini/mini-exceptions.c
return res;
}

EMSCRIPTEN_KEEPALIVE const char * mono_wasm_method_get_name_ex (MonoMethod *method) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you hand out ownership of this pointer and expect others to free it, it shouldn't be const.

Comment threadsrc/mono/mono/profiler/browser.c
Comment threadsrc/mono/mono/profiler/browser.c
Comment threadsrc/mono/mono/profiler/browser.c
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

arch-wasmWebAssembly architecturearea-Diagnostics-monoos-browserBrowser variant of arch-wasm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@pavelsavara@hakenr@kg@BrzVlad@kotlarmilos@lateralusX
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

[browser] samplepoint instrumentation into Mono profiler - #112352

Merged
pavelsavara merged 30 commits into
dotnet:mainfrom
pavelsavara:mono_profile_safepoint
Feb 25, 2025
Merged

[browser] samplepoint instrumentation into Mono profiler#112352
pavelsavara merged 30 commits into
dotnet:mainfrom
pavelsavara:mono_profile_safepoint

Conversation

@pavelsavara

@pavelsavarapavelsavara commented Feb 10, 2025

Copy link
Copy Markdown
Member

Motivation

Collect sample stack traces in the single-threaded browser.
Single threaded environment can't have dedicated thread for "stop-the-world + collect stack traces" implementation.
So we need to collect samples by instrumenting the code instead.

Previously we implemented browser based profiler which collects "sample" on every method entry/leave. In realistic application, that generates too much traffic for the browser profiler.

This enhancement could also be used to implement sampling profiler with ST diagnostic server.

Goal

  • add new samplepoint instrumentation into Mono profiler, use the existing enter/leave too.
  • inject the event/call into same locations as GC safepoint.
    • we can skip emitting it at the method start, because there is already method_enter event/instrumentation.
    • This is also same location where cooperative MT would stop-the-world to take a sample.
  • take sample on each Nth visit to the samplepoint (because measuring current time is more expensive)
  • keep updating the size of N to match expected sample time interval

Contributes to #76316

Non-goal: the browser profiler is not compatible with MT.

Changes

Interop

  • new interp IR instruction MINT_PROF_SAMPLEPOINT
  • new profiler event method_samplepoint
  • new interp macro INTERP_PROFILER_RAISE

Jiterp

  • new jiterp helper append_profiler_event calling new mono_jiterp_prof_enter, mono_jiterp_prof_samplepoint, mono_jiterp_prof_leave

AOT

  • new Mono JIT method mini_profiler_emit_samplepoint
  • example of code emitted by AOT with call $mono_profiler_raise_method_samplepoint

browser profiler

  • new config option sampleIntervalMs for browser profiler. 1ms by default. All samples when set to 0.
  • changes JavaScript called only to get time mono_wasm_profiler_now and mono_wasm_profiler_record
  • all sampling logic in browser.c new should_record_frame
  • stack of start times profiler_stack_frames and skip counter sample_skip_counter
  • default sample interval 10ms
  • callspec parametr to profiler allows to filter the instrumentation, for example by namespace callspec=N:Sample.FooNs

How to use

Interpreter

  • dotnet workload install wasm-tools

In the project file

<WasmProfilers>browser;</WasmProfilers>

In your main.js

import{dotnet}from'./dotnet.js'dotnet.withConfig({browserProfilerOptions: {sampleIntervalMs: 10,// optional}}).create();

or in your Blazor app

Blazor.start({configureRuntime: function(builder){builder.withConfig({browserProfilerOptions: {sampleIntervalMs: 10,// optional}});}});

AOT

You can add following, so that the native code produced by AOT has symbols, which are visible in the browser devtools profiler.

<RunAOTCompilation>true</RunAOTCompilation>
<RunAOTCompilationAfterBuild>true</RunAOTCompilationAfterBuild>
<WasmNativeStrip>false</WasmNativeStrip>
<WasmProfilers>browser</WasmProfilers>
<WasmNativeDebugSymbols>true</WasmNativeDebugSymbols>
<WasmBuildNative>true</WasmBuildNative>

callspec

If you want to filter profiled methods, you can use callspec

Callspec is documented here https://github.com/dotnet/runtime/blob/main/docs/design/mono/diagnostics-tracing.md#trace-monovm-profiler-events-during-startup

browserProfilerOptions: {callSpec: "N:SampleNamespace"// optional}

Your for AOT needs to match callspec in the browserProfilerOptions in JS

<WasmProfilers>browser:callspec=N:Sample;</WasmProfilers>

@pavelsavarapavelsavara added arch-wasm WebAssembly architecture area-Diagnostics-mono os-browser Browser variant of arch-wasm labels Feb 10, 2025
@pavelsavarapavelsavara added this to the 10.0.0 milestone Feb 10, 2025
@pavelsavarapavelsavara self-assigned this Feb 10, 2025
@pavelsavarapavelsavara changed the title [browser] add sample point instrumentation into Mono profiler[browser] Sample point instrumentation into Mono profilerFeb 10, 2025
@pavelsavara

pavelsavara commented Feb 10, 2025

Copy link
Copy Markdown
MemberAuthor

Interpreter: 1ms, skip rate between 200-500 sample points.
image

@pavelsavara

This comment was marked as resolved.

@pavelsavara

This comment was marked as resolved.

@hakenr

Copy link
Copy Markdown
Member

Hi @pavelsavara,
I'm in! I'll try to reach you via Teams.

Comment threadsrc/mono/mono/mini/mini.c
@pavelsavara

Copy link
Copy Markdown
MemberAuthor

My testing shows that enter/leave events are unbalanced.
I think my adding samplepoint in instrumentation didn't change that.
But the design of measuring the difference between method entry and method end requires that the events are balanced.

I will test it further to see if this could be fixed in the instrumentation or if this must be detected and survived by the receiving side.

@kg

kg commented Feb 21, 2025

Copy link
Copy Markdown
Contributor

I think you can't randomly record only a % of enter/leave events, or if you do you need to ensure that they are balanced - for example if you record an entry for method x the matching return also needs to be recorded. So you can only pick a % of enters to record but you need to do the check for every LEAVE as to whether this current stack frame previously recorded an enter.

…CONTEXT
- fix flag detection for both variants
- insert enter_profiling only when not inlining, to match leave event behavior
…e_exception_internal - also when MONO_EXCEPTION_CLAUSE_NONE
- more asserts in browser profiler
Comment threadsrc/mono/mono/mini/interp/transform.c Outdated
@BrzVlad

Copy link
Copy Markdown
Member

LGTM

@pavelsavara

Copy link
Copy Markdown
MemberAuthor

/ba-g CI timeouts and known issues

@pavelsavara
pavelsavara merged commit 0473604 into dotnet:mainFeb 25, 2025
@pavelsavara
pavelsavara deleted the mono_profile_safepoint branch February 25, 2025 13:47

@lateralusXlateralusX left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like I was late to the party (was OOF last week), but add my comments/questions here anyways, either for future follow ups or PR's.

#define INTERP_PROFILER_RAISE(name_lower, name_upper) \
if ((flag & TRACING_FLAG) || ((flag & PROFILING_FLAG) && MONO_PROFILER_ENABLED (method_ ## name_lower) && \
(frame->imethod->prof_flags & (MONO_PROFILER_CALL_INSTRUMENTATION_ ## name_upper ## _CONTEXT | MONO_PROFILER_CALL_INSTRUMENTATION_ ## name_upper)))) { \
MonoProfilerCallContext *prof_ctx = g_new0 (MonoProfilerCallContext, 1);\

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to heap allocate this? Looks like stack allocation should be enough, unless we are afraid consuming stack space since the MonoProfilerCallContext that includes MonoContext.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the place that possibly deals with MonoProfilerCallContext -> context

emit_fill_call_ctx (MonoCompile*cfg, MonoInst*method, MonoInst*ret)

What would happen if we removed context field completely ?

Comment threadsrc/mono/mono/mini/mini-exceptions.c
return res;
}

EMSCRIPTEN_KEEPALIVE const char * mono_wasm_method_get_name_ex (MonoMethod *method) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you hand out ownership of this pointer and expect others to free it, it shouldn't be const.

Comment threadsrc/mono/mono/profiler/browser.c
Comment threadsrc/mono/mono/profiler/browser.c
Comment threadsrc/mono/mono/profiler/browser.c
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

arch-wasmWebAssembly architecturearea-Diagnostics-monoos-browserBrowser variant of arch-wasm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@pavelsavara@hakenr@kg@BrzVlad@kotlarmilos@lateralusX