diff options
-rw-r--r-- | src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/CounterGroup.cs | 2 | ||||
-rw-r--r-- | src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs | 30 | ||||
-rw-r--r-- | src/mono/mono/metadata/threads.c | 6 | ||||
-rw-r--r-- | src/mono/mono/utils/mono-threads-wasm.c | 17 | ||||
-rw-r--r-- | src/mono/mono/utils/mono-threads-wasm.h | 4 | ||||
-rw-r--r-- | src/mono/mono/utils/mono-threads.c | 9 | ||||
-rw-r--r-- | src/mono/mono/utils/mono-threads.h | 1 | ||||
-rw-r--r-- | src/mono/sample/wasm/browser-eventpipe/Program.cs | 91 | ||||
-rw-r--r-- | src/mono/sample/wasm/browser-eventpipe/Wasm.Browser.EventPipe.Sample.csproj | 1 | ||||
-rw-r--r-- | src/mono/sample/wasm/browser-eventpipe/index.html | 6 | ||||
-rw-r--r-- | src/mono/sample/wasm/browser-eventpipe/main.js | 105 | ||||
-rw-r--r-- | src/mono/wasm/runtime/diagnostics.ts | 136 | ||||
-rw-r--r-- | src/mono/wasm/runtime/dotnet.d.ts | 33 | ||||
-rw-r--r-- | src/mono/wasm/runtime/driver.c | 2 | ||||
-rw-r--r-- | src/mono/wasm/runtime/types.ts | 4 |
15 files changed, 379 insertions, 68 deletions
diff --git a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/CounterGroup.cs b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/CounterGroup.cs index cf7b4471ff7..2bd67dc51c7 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/CounterGroup.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/CounterGroup.cs @@ -158,7 +158,7 @@ namespace System.Diagnostics.Tracing #if ES_BUILD_STANDALONE s_pollingThread.Start(); #else - s_pollingThread.UnsafeStart(); + s_pollingThread.InternalUnsafeStart(); #endif } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs index 8eb66011e42..0486c06bacb 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs @@ -153,20 +153,24 @@ namespace System.Threading Initialize(); } -#if !TARGET_BROWSER - internal static bool IsThreadStartSupported => true; -#else -#if FEATURE_WASM_THREADS +#if !TARGET_BROWSER || FEATURE_WASM_THREADS internal static bool IsThreadStartSupported => true; + internal static bool IsInternalThreadStartSupported => true; +#elif FEATURE_WASM_PERFTRACING + internal static bool IsThreadStartSupported => false; + internal static bool IsInternalThreadStartSupported => true; #else internal static bool IsThreadStartSupported => false; -#endif + internal static bool IsInternalThreadStartSupported => false; #endif - internal static void ThrowIfNoThreadStart() + internal static void ThrowIfNoThreadStart(bool internalThread = false) { - if (!IsThreadStartSupported) - throw new PlatformNotSupportedException(); + if (IsThreadStartSupported) + return; + if (IsInternalThreadStartSupported && internalThread) + return; + throw new PlatformNotSupportedException(); } /// <summary>Causes the operating system to change the state of the current instance to <see cref="ThreadState.Running"/>, and optionally supplies an object containing data to be used by the method the thread executes.</summary> @@ -187,9 +191,9 @@ namespace System.Threading /// </remarks> public void UnsafeStart(object? parameter) => Start(parameter, captureContext: false); - private void Start(object? parameter, bool captureContext) + private void Start(object? parameter, bool captureContext, bool internalThread = false) { - ThrowIfNoThreadStart(); + ThrowIfNoThreadStart(internalThread); StartHelper? startHelper = _startHelper; @@ -224,9 +228,11 @@ namespace System.Threading /// </remarks> public void UnsafeStart() => Start(captureContext: false); - private void Start(bool captureContext) + internal void InternalUnsafeStart() => Start(captureContext: false, internalThread: true); + + private void Start(bool captureContext, bool internalThread = false) { - ThrowIfNoThreadStart(); + ThrowIfNoThreadStart(internalThread); StartHelper? startHelper = _startHelper; // In the case of a null startHelper (second call to start on same thread) diff --git a/src/mono/mono/metadata/threads.c b/src/mono/mono/metadata/threads.c index f4a1f891cd4..378cec13b1e 100644 --- a/src/mono/mono/metadata/threads.c +++ b/src/mono/mono/metadata/threads.c @@ -4819,7 +4819,11 @@ ves_icall_System_Threading_Thread_StartInternal (MonoThreadObjectHandle thread_h MonoThread *internal = MONO_HANDLE_RAW (thread_handle); gboolean res; -#if defined (DISABLE_THREADS) || defined (DISABLE_WASM_USER_THREADS) +#if defined (DISABLE_THREADS) + /* + * N.B. not checking DISABLE_WASM_USER_THREADS - managed utility threads are allowed; we assume all + * callers of System.Thread.StartCore called System.Thread.ThrowIfNotSupported(bool internalThread) + */ mono_error_set_platform_not_supported (error, "Cannot start threads on this runtime."); return; #endif diff --git a/src/mono/mono/utils/mono-threads-wasm.c b/src/mono/mono/utils/mono-threads-wasm.c index 496daaf42d3..a6b6552032b 100644 --- a/src/mono/mono/utils/mono-threads-wasm.c +++ b/src/mono/mono/utils/mono-threads-wasm.c @@ -393,6 +393,23 @@ mono_threads_wasm_is_browser_thread (void) #endif } +MonoNativeThreadId +mono_threads_wasm_browser_thread_tid (void) +{ +#ifdef DISABLE_THREADS + return (MonoNativeThreadId)1; +#else + return (MonoNativeThreadId)emscripten_main_browser_thread_id (); +#endif +} + +gboolean +mono_threads_platform_stw_defer_initial_suspend (MonoThreadInfo *info) +{ + /* Suspend the browser thread after all the other threads are suspended already. */ + return mono_native_thread_id_equals (mono_thread_info_get_tid (info), mono_threads_wasm_browser_thread_tid ()); +} + #ifndef DISABLE_THREADS void mono_threads_wasm_async_run_in_main_thread (void (*func) (void)) diff --git a/src/mono/mono/utils/mono-threads-wasm.h b/src/mono/mono/utils/mono-threads-wasm.h index ecad01a7ec6..1f5cf6bb0c4 100644 --- a/src/mono/mono/utils/mono-threads-wasm.h +++ b/src/mono/mono/utils/mono-threads-wasm.h @@ -9,6 +9,7 @@ #define __MONO_THREADS_WASM_H__ #include <glib.h> +#include <mono/utils/mono-threads.h> #ifdef HOST_WASM @@ -22,6 +23,9 @@ gboolean mono_threads_wasm_is_browser_thread (void); +MonoNativeThreadId +mono_threads_wasm_browser_thread_tid (void); + #ifndef DISABLE_THREADS /** * Runs the given function asynchronously on the main thread. diff --git a/src/mono/mono/utils/mono-threads.c b/src/mono/mono/utils/mono-threads.c index a947b574c1b..435b7dca5a9 100644 --- a/src/mono/mono/utils/mono-threads.c +++ b/src/mono/mono/utils/mono-threads.c @@ -1099,6 +1099,8 @@ begin_suspend_peek_and_preempt (MonoThreadInfo *info); MonoThreadBeginSuspendResult mono_thread_info_begin_suspend (MonoThreadInfo *info, MonoThreadSuspendPhase phase) { + if (phase == MONO_THREAD_SUSPEND_PHASE_INITIAL && mono_threads_platform_stw_defer_initial_suspend (info)) + return MONO_THREAD_BEGIN_SUSPEND_NEXT_PHASE; if (phase == MONO_THREAD_SUSPEND_PHASE_MOPUP && mono_threads_is_hybrid_suspension_enabled ()) return begin_suspend_peek_and_preempt (info); else @@ -2171,3 +2173,10 @@ mono_thread_info_get_tools_data (void) return info ? info->tools_data : NULL; } +#ifndef HOST_WASM +gboolean +mono_threads_platform_stw_defer_initial_suspend (MonoThreadInfo *info) +{ + return FALSE; +} +#endif diff --git a/src/mono/mono/utils/mono-threads.h b/src/mono/mono/utils/mono-threads.h index 36cc46affe0..acd3d6e66b7 100644 --- a/src/mono/mono/utils/mono-threads.h +++ b/src/mono/mono/utils/mono-threads.h @@ -634,6 +634,7 @@ void mono_threads_platform_init (void); gboolean mono_threads_platform_in_critical_region (THREAD_INFO_TYPE *info); gboolean mono_threads_platform_yield (void); void mono_threads_platform_exit (gsize exit_code); +gboolean mono_threads_platform_stw_defer_initial_suspend (THREAD_INFO_TYPE *info); void mono_threads_coop_begin_global_suspend (void); void mono_threads_coop_end_global_suspend (void); diff --git a/src/mono/sample/wasm/browser-eventpipe/Program.cs b/src/mono/sample/wasm/browser-eventpipe/Program.cs index 2755fcd7e25..0feecf5c66e 100644 --- a/src/mono/sample/wasm/browser-eventpipe/Program.cs +++ b/src/mono/sample/wasm/browser-eventpipe/Program.cs @@ -5,10 +5,65 @@ using System; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; +using System.Diagnostics.Tracing; namespace Sample { + + [EventSource(Name = "WasmHello")] + public class WasmHelloEventSource : EventSource + { + public static readonly WasmHelloEventSource Instance = new (); + + private IncrementingEventCounter _calls; + + private WasmHelloEventSource () + { + } + + [NonEvent] + public void NewCallsCounter() + { + _calls?.Dispose(); + _calls = new ("fib-calls", this) + { + DisplayName = "Recursive Fib calls", + }; + } + + [NonEvent] + public void CountCall() { + _calls?.Increment(1.0); + } + + protected override void Dispose (bool disposing) + { + _calls?.Dispose(); + _calls = null; + + base.Dispose(disposing); + } + + [Event(1, Message="Started Fib({0})", Level = EventLevel.Informational)] + public void StartFib(int n) + { + if (!IsEnabled()) + return; + + WriteEvent(1, n); + } + + [Event(2, Message="Stopped Fib({0}) = {1}", Level = EventLevel.Informational)] + public void StopFib(int n, string result) + { + if (!IsEnabled()) + return; + + WriteEvent(2, n, result); + } + } + public class Test { public static void Main(string[] args) @@ -34,24 +89,34 @@ namespace Sample return 0; if (n == 1) return 1; + WasmHelloEventSource.Instance.CountCall(); return recursiveFib (n - 1) + recursiveFib (n - 2); } - public static async Task<int> StartAsyncWork() + public static async Task<double> StartAsyncWork(int N) { CancellationToken ct = GetCancellationToken(); + await Task.Delay(1); long b; - const int N = 35; - const long expected = 9227465; + WasmHelloEventSource.Instance.NewCallsCounter(); + iterations = 0; while (true) { - await Task.Delay(1).ConfigureAwait(false); + WasmHelloEventSource.Instance.StartFib(N); + await Task.Delay(1); b = recursiveFib (N); + WasmHelloEventSource.Instance.StopFib(N, b.ToString()); + iterations++; if (ct.IsCancellationRequested) break; - iterations++; } - return b == expected ? 42 : 0; + long expected = fastFib(N); + if (expected == b) + return (double)b; + else { + Console.Error.WriteLine ("expected {0}, but got {1}", expected, b); + return 0.0; + } } public static void StopWork() @@ -63,5 +128,19 @@ namespace Sample { return iterations.ToString(); } + + private static long fastFib(int N) { + if (N < 1) + return 0; + long a = 0; + long b = 1; + for (int i = 1; i < N; ++i) { + long tmp = a+b; + a = b; + b = tmp; + } + return b; + } + } } diff --git a/src/mono/sample/wasm/browser-eventpipe/Wasm.Browser.EventPipe.Sample.csproj b/src/mono/sample/wasm/browser-eventpipe/Wasm.Browser.EventPipe.Sample.csproj index 477e50104ed..d59d8baafb3 100644 --- a/src/mono/sample/wasm/browser-eventpipe/Wasm.Browser.EventPipe.Sample.csproj +++ b/src/mono/sample/wasm/browser-eventpipe/Wasm.Browser.EventPipe.Sample.csproj @@ -10,6 +10,7 @@ <GenerateRunScriptForSample Condition="'$(ArchiveTests)' == 'true'">true</GenerateRunScriptForSample> <RunScriptCommand>$(ExecXHarnessCmd) wasm test-browser --app=. --browser=Chrome $(XHarnessBrowserPathArg) --html-file=index.html --output-directory=$(XHarnessOutput) -- $(MSBuildProjectName).dll</RunScriptCommand> <FeatureWasmPerfTracing>true</FeatureWasmPerfTracing> + <NoWarn>CA2007</NoWarn> <!-- consider ConfigureAwait() --> </PropertyGroup> <ItemGroup> diff --git a/src/mono/sample/wasm/browser-eventpipe/index.html b/src/mono/sample/wasm/browser-eventpipe/index.html index 87ef4b46638..7d8904b1cca 100644 --- a/src/mono/sample/wasm/browser-eventpipe/index.html +++ b/src/mono/sample/wasm/browser-eventpipe/index.html @@ -11,7 +11,11 @@ <body> <h3 id="header">Wasm Browser EventPipe profiling Sample</h3> - Computing Fib repeatedly: <span id="out"></span> + <div> + <button id="startWork">Start Work</button> + <label for="inputN">N:</label> <input type="number" id="inputN" value="10"/> + </div> + <div>Computing Fib(N) repeatedly: <span id="out"></span> <script type="text/javascript" src="./dotnet.js"></script> <script type="text/javascript" src="./dotnet.worker.js"></script> <script type="text/javascript" src="./main.js"></script> diff --git a/src/mono/sample/wasm/browser-eventpipe/main.js b/src/mono/sample/wasm/browser-eventpipe/main.js index ef8029db509..c046f84731e 100644 --- a/src/mono/sample/wasm/browser-eventpipe/main.js +++ b/src/mono/sample/wasm/browser-eventpipe/main.js @@ -8,8 +8,7 @@ function wasm_exit(exit_code) { console.log(`WASM EXIT ${exit_code}`); } -function downloadData(dataURL,filename) -{ +function downloadData(dataURL, filename) { // make an `<a download="filename" href="data:..."/>` link and click on it to trigger a download with the given name const elt = document.createElement('a'); elt.download = filename; @@ -22,8 +21,7 @@ function downloadData(dataURL,filename) document.body.removeChild(elt); } -function makeTimestamp() -{ +function makeTimestamp() { // ISO date string, but with : and . replaced by - const t = new Date(); const s = t.toISOString(); @@ -37,66 +35,85 @@ async function loadRuntime() { } -const delay = (ms) => new Promise((resolve) => setTimeout (resolve, ms)) +const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms)) -const saveUsingBlob = true; +async function doWork(startWork, stopWork, getIterationsDone) { + const N = parseInt(document.getElementById("inputN").value); -async function main() { - const createDotnetRuntime = await loadRuntime(); - const { MONO, BINDING, Module, RuntimeBuildInfo } = await createDotnetRuntime(() => { - console.log('user code in createDotnetRuntime') - return { - disableDotnet6Compatibility: true, - configSrc: "./mono-config.json", - preInit: () => { console.log('user code Module.preInit') }, - preRun: () => { console.log('user code Module.preRun') }, - onRuntimeInitialized: () => { console.log('user code Module.onRuntimeInitialized') }, - postRun: () => { console.log('user code Module.postRun') }, - } - }); - globalThis.__Module = Module; - globalThis.MONO = MONO; - console.log('after createDotnetRuntime') - const startWork = BINDING.bind_static_method("[Wasm.Browser.EventPipe.Sample] Sample.Test:StartAsyncWork"); - const stopWork = BINDING.bind_static_method("[Wasm.Browser.EventPipe.Sample] Sample.Test:StopWork"); - const getIterationsDone = BINDING.bind_static_method("[Wasm.Browser.EventPipe.Sample] Sample.Test:GetIterationsDone"); - const eventSession = MONO.diagnostics.createEventPipeSession(); - eventSession.start(); - const workPromise = startWork(); + const workPromise = startWork(N); + + let btn = document.getElementById("startWork"); + btn.disabled = true; + btn.innerText = "Working"; + document.getElementById("out").innerHTML = '...'; - document.getElementById("out").innerHTML = '<<running>>'; await delay(5000); // let it run for 5 seconds - stopWork(); + document.getElementById("startWork").innerText = "Stopping"; + document.getElementById("out").innerHTML = '... ...'; - document.getElementById("out").innerHTML = '<<stopping>>'; + stopWork(); const ret = await workPromise; // get the answer const iterations = getIterationsDone(); // get how many times the loop ran - eventSession.stop(); + btn = document.getElementById("startWork"); + btn.disabled = false; + btn.innerText = "Start Work"; - document.getElementById("out").innerHTML = `${ret} as computed in ${iterations} iterations on dotnet ver ${RuntimeBuildInfo.ProductVersion}`; + document.getElementById("out").innerHTML = `${ret} as computed in ${iterations} iterations`; console.debug(`ret: ${ret}`); - const filename = "dotnet-wasm-" + makeTimestamp() + ".nettrace"; + return ret; +} + +function getOnClickHandler(startWork, stopWork, getIterationsDone) { + return async function () { + const options = MONO.diagnostics.SessionOptionsBuilder + .Empty + .setRundownEnabled(false) + .addProvider({ name: 'WasmHello', level: MONO.diagnostics.EventLevel.Verbose, args: 'EventCounterIntervalSec=1' }) + .build(); + console.log('starting providers', options.providers); + + const eventSession = MONO.diagnostics.createEventPipeSession(options); + + eventSession.start(); + const ret = await doWork(startWork, stopWork, getIterationsDone); + eventSession.stop(); + + const filename = "dotnet-wasm-" + makeTimestamp() + ".nettrace"; - if (saveUsingBlob) { const blob = eventSession.getTraceBlob(); const uri = URL.createObjectURL(blob); downloadData(uri, filename); - URL.revokeObjectURL(uri); - } else { - const dataUri = eventSession.getTraceDataURI(); - - downloadData(dataUri, filename); } - const exit_code = ret == 42 ? 0 : 1; +} + + +async function main() { + const createDotnetRuntime = await loadRuntime(); + const { MONO, BINDING, Module, RuntimeBuildInfo } = await createDotnetRuntime(() => { + return { + disableDotnet6Compatibility: true, + configSrc: "./mono-config.json", + } + }); + globalThis.__Module = Module; + globalThis.MONO = MONO; + + const startWork = BINDING.bind_static_method("[Wasm.Browser.EventPipe.Sample] Sample.Test:StartAsyncWork"); + const stopWork = BINDING.bind_static_method("[Wasm.Browser.EventPipe.Sample] Sample.Test:StopWork"); + const getIterationsDone = BINDING.bind_static_method("[Wasm.Browser.EventPipe.Sample] Sample.Test:GetIterationsDone"); + + + const btn = document.getElementById("startWork"); + + btn.style.backgroundColor = "rgb(192,255,192)"; + btn.onclick = getOnClickHandler(startWork, stopWork, getIterationsDone); - wasm_exit(exit_code); } -console.log("Waiting 10s for curious human before starting the program"); -setTimeout(main, 10000); +main(); diff --git a/src/mono/wasm/runtime/diagnostics.ts b/src/mono/wasm/runtime/diagnostics.ts index eee3f061b97..d346a986546 100644 --- a/src/mono/wasm/runtime/diagnostics.ts +++ b/src/mono/wasm/runtime/diagnostics.ts @@ -82,18 +82,136 @@ class EventPipeFileSession implements EventPipeSession { } } +const eventLevel = { + LogAlways: 0, + Critical: 1, + Error: 2, + Warning: 3, + Informational: 4, + Verbose: 5, +} as const; + +type EventLevel = typeof eventLevel; + +type UnnamedProviderConfiguration = Partial<{ + keyword_mask: string | 0; + level: number; + args: string; +}> + +/// The configuration for an individual provider. Each provider configuration has the name of the provider, +/// the level of events to collect, and a string containing a 32-bit hexadecimal mask (without an "0x" prefix) of +/// the "keywords" to filter a subset of the events. The keyword mask may be the number 0 or "" to skips the filtering. +/// See https://docs.microsoft.com/en-us/dotnet/core/diagnostics/well-known-event-providers for a list of known providers. +/// Additional providers may be added by applications or libraries that implement an EventSource subclass. +/// See https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.tracing.eventsource?view=net-6.0 +/// +/// Some providers also have an "args" string in an arbitrary format. For example the EventSource providers that +/// include EventCounters have a "EventCounterIntervalSec=NNN" argument that specified how often the counters of +/// the event source should be polled. +export interface ProviderConfiguration extends UnnamedProviderConfiguration { + name: string; +} + +const runtimeProviderName = "Microsoft-Windows-DotNETRuntime"; +const runtimePrivateProviderName = "Microsoft-Windows-DotNETRuntimePrivate"; +const sampleProfilerProviderName = "Microsoft-DotNETCore-SampleProfiler"; + +const runtimeProviderDefault: ProviderConfiguration = { + name: runtimeProviderName, + keyword_mask: "4c14fccbd", + level: eventLevel.Verbose, +}; + +const runtimePrivateProviderDefault: ProviderConfiguration = { + name: runtimePrivateProviderName, + keyword_mask: "4002000b", + level: eventLevel.Verbose, +}; + +const sampleProfilerProviderDefault: ProviderConfiguration = { + name: sampleProfilerProviderName, + keyword_mask: "0", + level: eventLevel.Verbose, +}; + +/// A helper class to create EventPipeSessionOptions +export class SessionOptionsBuilder { + private _rundown?: boolean; + private _providers: ProviderConfiguration[]; + /// Create an empty builder. Prefer to use SesssionOptionsBuilder.Empty + constructor() { + this._providers = []; + } + /// Gets a builder with no providers. + static get Empty(): SessionOptionsBuilder { return new SessionOptionsBuilder(); } + /// Gets a builder with default providers and rundown events enabled. + /// See https://docs.microsoft.com/en-us/dotnet/core/diagnostics/eventpipe#trace-using-environment-variables + static get DefaultProviders(): SessionOptionsBuilder { + return this.Empty.addRuntimeProvider().addRuntimePrivateProvider().addSampleProfilerProvider(); + } + /// Change whether to collect rundown events. + /// Certain providers may need rundown events to be collected in order to provide useful diagnostic information. + setRundownEnabled(enabled: boolean): SessionOptionsBuilder { + this._rundown = enabled; + return this; + } + /// Add a provider configuration to the builder. + addProvider(provider: ProviderConfiguration): SessionOptionsBuilder { + this._providers.push(provider); + return this; + } + /// Add the Microsoft-Windows-DotNETRuntime provider. Use override options to change the event level or keyword mask. + /// The default is { keyword_mask: "4c14fccbd", level: eventLevel.Verbose } + addRuntimeProvider(overrideOptions?: UnnamedProviderConfiguration): SessionOptionsBuilder { + const options = { ...runtimeProviderDefault, ...overrideOptions }; + this._providers.push(options); + return this; + } + /// Add the Microsoft-Windows-DotNETRuntimePrivate provider. Use override options to change the event level or keyword mask. + /// The default is { keyword_mask: "4002000b", level: eventLevel.Verbose} + addRuntimePrivateProvider(overrideOptions?: UnnamedProviderConfiguration): SessionOptionsBuilder { + const options = { ...runtimePrivateProviderDefault, ...overrideOptions }; + this._providers.push(options); + return this; + } + /// Add the Microsoft-DotNETCore-SampleProfiler. Use override options to change the event level or keyword mask. + // The default is { keyword_mask: 0, level: eventLevel.Verbose } + addSampleProfilerProvider(overrideOptions?: UnnamedProviderConfiguration): SessionOptionsBuilder { + const options = { ...sampleProfilerProviderDefault, ...overrideOptions }; + this._providers.push(options); + return this; + } + /// Create an EventPipeSessionOptions from the builder. + build(): EventPipeSessionOptions { + const providers = this._providers.map(p => { + const name = p.name; + const keyword_mask = "" + (p?.keyword_mask ?? ""); + const level = p?.level ?? eventLevel.Verbose; + const args = p?.args ?? ""; + const maybeArgs = args != "" ? `:${args}` : ""; + return `${name}:${keyword_mask}:${level}${maybeArgs}`; + }); + return { + collectRundownEvents: this._rundown, + providers: providers.join(",") + }; + } +} + // a conter for the number of sessions created let totalSessions = 0; function createSessionWithPtrCB(sessionIdOutPtr: VoidPtr, options: EventPipeSessionOptions | undefined, tracePath: string): false | number { const defaultRundownRequested = true; - const defaultProviders = ""; + const defaultProviders = ""; // empty string means use the default providers const defaultBufferSizeInMB = 1; const rundown = options?.collectRundownEvents ?? defaultRundownRequested; + const providers = options?.providers ?? defaultProviders; memory.setI32(sessionIdOutPtr, 0); - if (!cwraps.mono_wasm_event_pipe_enable(tracePath, defaultBufferSizeInMB, defaultProviders, rundown, sessionIdOutPtr)) { + if (!cwraps.mono_wasm_event_pipe_enable(tracePath, defaultBufferSizeInMB, providers, rundown, sessionIdOutPtr)) { return false; } else { return memory.getI32(sessionIdOutPtr); @@ -101,11 +219,25 @@ function createSessionWithPtrCB(sessionIdOutPtr: VoidPtr, options: EventPipeSess } export interface Diagnostics { + EventLevel: EventLevel; + SessionOptionsBuilder: typeof SessionOptionsBuilder; + createEventPipeSession(options?: EventPipeSessionOptions): EventPipeSession | null; } + /// APIs for working with .NET diagnostics from JavaScript. export const diagnostics: Diagnostics = { + /// An enumeration of the level (higher value means more detail): + /// LogAlways: 0, + /// Critical: 1, + /// Error: 2, + /// Warning: 3, + /// Informational: 4, + /// Verbose: 5, + EventLevel: eventLevel, + /// A builder for creating an EventPipeSessionOptions instance. + SessionOptionsBuilder: SessionOptionsBuilder, /// Creates a new EventPipe session that will collect trace events from the runtime and managed libraries. /// Use the options to control the kinds of events to be collected. /// Multiple sessions may be created and started at the same time. diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index 19d6ddbc90b..34d62ebad8e 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -210,6 +210,7 @@ declare type CoverageProfilerOptions = { }; interface EventPipeSessionOptions { collectRundownEvents?: boolean; + providers: string; } declare type DotnetModuleConfig = { disableDotnet6Compatibility?: boolean; @@ -249,7 +250,39 @@ interface EventPipeSession { stop(): void; getTraceBlob(): Blob; } +declare const eventLevel: { + readonly LogAlways: 0; + readonly Critical: 1; + readonly Error: 2; + readonly Warning: 3; + readonly Informational: 4; + readonly Verbose: 5; +}; +declare type EventLevel = typeof eventLevel; +declare type UnnamedProviderConfiguration = Partial<{ + keyword_mask: string | 0; + level: number; + args: string; +}>; +interface ProviderConfiguration extends UnnamedProviderConfiguration { + name: string; +} +declare class SessionOptionsBuilder { + private _rundown?; + private _providers; + constructor(); + static get Empty(): SessionOptionsBuilder; + static get DefaultProviders(): SessionOptionsBuilder; + setRundownEnabled(enabled: boolean): SessionOptionsBuilder; + addProvider(provider: ProviderConfiguration): SessionOptionsBuilder; + addRuntimeProvider(overrideOptions?: UnnamedProviderConfiguration): SessionOptionsBuilder; + addRuntimePrivateProvider(overrideOptions?: UnnamedProviderConfiguration): SessionOptionsBuilder; + addSampleProfilerProvider(overrideOptions?: UnnamedProviderConfiguration): SessionOptionsBuilder; + build(): EventPipeSessionOptions; +} interface Diagnostics { + EventLevel: EventLevel; + SessionOptionsBuilder: typeof SessionOptionsBuilder; createEventPipeSession(options?: EventPipeSessionOptions): EventPipeSession | null; } diff --git a/src/mono/wasm/runtime/driver.c b/src/mono/wasm/runtime/driver.c index 605169be251..32528ad0559 100644 --- a/src/mono/wasm/runtime/driver.c +++ b/src/mono/wasm/runtime/driver.c @@ -476,7 +476,7 @@ mono_wasm_load_runtime (const char *unused, int debug_level) // We should enable this as part of the wasm build later #ifndef DISABLE_THREADS monoeg_g_setenv ("MONO_THREADS_SUSPEND", "coop", 0); - monoeg_g_setenv ("MONO_SLEEP_ABORT_LIMIT", "250", 0); + monoeg_g_setenv ("MONO_SLEEP_ABORT_LIMIT", "5000", 0); #endif #ifdef DEBUG diff --git a/src/mono/wasm/runtime/types.ts b/src/mono/wasm/runtime/types.ts index 2abb34e662c..8fb000c1982 100644 --- a/src/mono/wasm/runtime/types.ts +++ b/src/mono/wasm/runtime/types.ts @@ -180,10 +180,14 @@ export type CoverageProfilerOptions = { } /// Options to configure the event pipe session +/// The recommended method is to MONO.diagnostics.SesisonOptionsBuilder to create an instance of this type export interface EventPipeSessionOptions { /// Whether to collect additional details (such as method and type names) at EventPipeSession.stop() time (default: true) /// This is required for some use cases, and may allow some tools to better understand the events. collectRundownEvents?: boolean; + /// The providers that will be used by this session. + /// See https://docs.microsoft.com/en-us/dotnet/core/diagnostics/eventpipe#trace-using-environment-variables + providers: string; } // how we extended emscripten Module |