From 8166db7e95ee3579c4bce4ff25a8287ee4a014de Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Tue, 26 Apr 2022 14:48:34 -0400 Subject: [PATCH 01/36] add DISABLE_WASM_USER_THREADS mono cmake option --- src/mono/cmake/config.h.in | 3 +++ src/mono/cmake/options.cmake | 1 + 2 files changed, 4 insertions(+) diff --git a/src/mono/cmake/config.h.in b/src/mono/cmake/config.h.in index 7b8a193da0c24..7dd80ddbaf861 100644 --- a/src/mono/cmake/config.h.in +++ b/src/mono/cmake/config.h.in @@ -330,6 +330,9 @@ /* Disable Threads */ #cmakedefine DISABLE_THREADS 1 +/* Disable user thread creation on WebAssembly */ +#cmakedefine DISABLE_WASM_USER_THREADS 1 + /* Disable MONO_LOG_DEST */ #cmakedefine DISABLE_LOG_DEST diff --git a/src/mono/cmake/options.cmake b/src/mono/cmake/options.cmake index c0fd0ecf43222..7a0fa12521dc3 100644 --- a/src/mono/cmake/options.cmake +++ b/src/mono/cmake/options.cmake @@ -56,6 +56,7 @@ option (ENABLE_OVERRIDABLE_ALLOCATORS "Enable overridable allocator support") option (ENABLE_SIGALTSTACK "Enable support for using sigaltstack for SIGSEGV and stack overflow handling, this doesn't work on some platforms") option (USE_MALLOC_FOR_MEMPOOLS "Use malloc for each single mempool allocation, so tools like Valgrind can run better") option (STATIC_COMPONENTS "Compile mono runtime components as static (not dynamic) libraries") +option (DISABLE_WASM_USER_THREADS "Disable creation of managed threads, only allow runtime internal threads") set (MONO_GC "sgen" CACHE STRING "Garbage collector implementation (sgen or boehm). Default: sgen") set (GC_SUSPEND "default" CACHE STRING "GC suspend method (default, preemptive, coop, hybrid)") From a4dd0a86244102dd602021c07599638f68a7a497 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Fri, 29 Apr 2022 12:29:37 -0400 Subject: [PATCH 02/36] Disable Thread.StartInternal icall if DISABLE_WASM_USER_THREADS if threading is enabled for the runtime internally, but disabled for user code, throw PNSE --- src/mono/mono/metadata/threads.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/mono/metadata/threads.c b/src/mono/mono/metadata/threads.c index 418ee6100a95e..50576527215d1 100644 --- a/src/mono/mono/metadata/threads.c +++ b/src/mono/mono/metadata/threads.c @@ -4815,7 +4815,7 @@ ves_icall_System_Threading_Thread_StartInternal (MonoThreadObjectHandle thread_h MonoThread *internal = MONO_HANDLE_RAW (thread_handle); gboolean res; -#ifdef DISABLE_THREADS +#if defined (DISABLE_THREADS) || defined (DISABLE_WASM_USER_THREADS) mono_error_set_platform_not_supported (error, "Cannot start threads on this runtime."); return; #endif From 3f6af6fc6e3a3b8edf7872f961559f3e86b06649 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Mon, 2 May 2022 14:49:13 -0400 Subject: [PATCH 03/36] add an eventpipe sample --- .../sample/wasm/browser-eventpipe/Makefile | 11 ++ .../sample/wasm/browser-eventpipe/Program.cs | 34 +++++ .../Wasm.Browser.EventPipe.Sample.csproj | 43 ++++++ .../sample/wasm/browser-eventpipe/index.html | 20 +++ .../sample/wasm/browser-eventpipe/main.js | 128 ++++++++++++++++++ 5 files changed, 236 insertions(+) create mode 100644 src/mono/sample/wasm/browser-eventpipe/Makefile create mode 100644 src/mono/sample/wasm/browser-eventpipe/Program.cs create mode 100644 src/mono/sample/wasm/browser-eventpipe/Wasm.Browser.EventPipe.Sample.csproj create mode 100644 src/mono/sample/wasm/browser-eventpipe/index.html create mode 100644 src/mono/sample/wasm/browser-eventpipe/main.js diff --git a/src/mono/sample/wasm/browser-eventpipe/Makefile b/src/mono/sample/wasm/browser-eventpipe/Makefile new file mode 100644 index 0000000000000..6f4130b5b64b5 --- /dev/null +++ b/src/mono/sample/wasm/browser-eventpipe/Makefile @@ -0,0 +1,11 @@ +TOP=../../../../.. + +include ../wasm.mk + +ifneq ($(AOT),) +override MSBUILD_ARGS+=/p:RunAOTCompilation=true +endif + +PROJECT_NAME=Wasm.Browser.EventPipe.Sample.csproj + +run: run-browser diff --git a/src/mono/sample/wasm/browser-eventpipe/Program.cs b/src/mono/sample/wasm/browser-eventpipe/Program.cs new file mode 100644 index 0000000000000..ca644051c1770 --- /dev/null +++ b/src/mono/sample/wasm/browser-eventpipe/Program.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; + +namespace Sample +{ + public class Test + { + public static void Main(string[] args) + { + Console.WriteLine ("Hello, World!"); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static async Task StartAsyncWork() + { + int a = 0; + int b = 1; + const int N = 30; + const int expected = 832040; + for (int i = 1; i < N; i++) + { + int tmp = a + b; + a = b; + b = tmp; + await Task.Yield(); + } + return b == expected ? 42 : 0; + } + } +} 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 new file mode 100644 index 0000000000000..124d312b33d21 --- /dev/null +++ b/src/mono/sample/wasm/browser-eventpipe/Wasm.Browser.EventPipe.Sample.csproj @@ -0,0 +1,43 @@ + + + true + main.js + true + embedded + 1 + false + true + true + $(ExecXHarnessCmd) wasm test-browser --app=. --browser=Chrome $(XHarnessBrowserPathArg) --html-file=index.html --output-directory=$(XHarnessOutput) -- $(MSBuildProjectName).dll + true + + + + + + + + + <_SampleProject>Wasm.Browser.CJS.Sample.csproj + + + + + true + + + + + + + + + + + + + diff --git a/src/mono/sample/wasm/browser-eventpipe/index.html b/src/mono/sample/wasm/browser-eventpipe/index.html new file mode 100644 index 0000000000000..7dfcd3da04e5a --- /dev/null +++ b/src/mono/sample/wasm/browser-eventpipe/index.html @@ -0,0 +1,20 @@ + + + + + + + Sample EventPipe profile session + + + + + + + Answer to the Ultimate Question of Life, the Universe, and Everything is : + + + + + + diff --git a/src/mono/sample/wasm/browser-eventpipe/main.js b/src/mono/sample/wasm/browser-eventpipe/main.js new file mode 100644 index 0000000000000..25fd5c2b0fa68 --- /dev/null +++ b/src/mono/sample/wasm/browser-eventpipe/main.js @@ -0,0 +1,128 @@ +function wasm_exit(exit_code) { + /* Set result in a tests_done element, to be read by xharness in runonly CI test */ + const tests_done_elem = document.createElement("label"); + tests_done_elem.id = "tests_done"; + tests_done_elem.innerHTML = exit_code.toString(); + document.body.appendChild(tests_done_elem); + + console.log(`WASM EXIT ${exit_code}`); +} + +function downloadData(dataURL,filename) +{ + // make an `` link and click on it to trigger a download with the given name + const elt = document.createElement('a'); + elt.download = filename; + elt.href = dataURL; + + document.body.appendChild(elt); + + elt.click(); + + document.body.removeChild(elt); +} + +function makeTimestamp() +{ + // ISO date string, but with : and . replaced by - + const t = new Date(); + const s = t.toISOString(); + return s.replace(/[:.]/g, '-'); +} + +function Uint8ToString(u8a){ + var CHUNK_SZ = 0x8000; + var c = []; + for (var i=0; i < u8a.length; i+=CHUNK_SZ) { + c.push(String.fromCharCode.apply(null, u8a.subarray(i, i+CHUNK_SZ))); + } + return c.join(""); +} + +async function loadRuntime() { + globalThis.exports = {}; + await import("./dotnet.js"); + return globalThis.exports.createDotnetRuntime; +} + + +function createEventPipeSession() { + class EventPipeSession { + state; + + constructor () { + this.state = 'INIT'; + } + + start = () => { + this.state = 'STARTED'; + console.log ('"Event"pipe session started'); + } + + stop = () => { + this.state = 'DONE'; + console.log ('"Event"pipe session stopped'); + } + + saveTrace = () => { + if (this.state !== 'DONE') { + console.err (`session is in state ${this.state}, not 'DONE'`); + return; + } + console.log ('"saving" session trace'); + return ''; + } + } + + return new EventPipeSession(); +} + +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') + + try { + const testMeaning = BINDING.bind_static_method("[Wasm.Browser.EventPipe.Sample] Sample.Test:StartAsyncWork"); + const eventSession = createEventPipeSession(); + eventSession.start(); + const ret = await testMeaning(); + document.getElementById("out").innerHTML = `${ret} as computed on dotnet ver ${RuntimeBuildInfo.ProductVersion}`; + + + console.debug(`ret: ${ret}`); + + eventSession.stop(); + + const sessionRes = eventSession.saveTrace(); + + let exit_code = ret == 42 ? 0 : 1; + Module._mono_wasm_exit(exit_code); + + wasm_exit(exit_code); + } catch (err) { + console.log('WASM ERROR %o', err); + + var b = Module.FS.readFile('/trace.nettrace'); + var bits = btoa((Uint8ToString(b))); + + const filename = "dotnet-wasm-" + makeTimestamp() + ".nettrace"; + downloadData("data:application/octet-stream;base64," + bits, filename); + + wasm_exit(2); + } +} + +setTimeout(main, 10000); From cc7162fcab407788639ae997626a6ed6b069d61f Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Mon, 2 May 2022 16:27:03 -0400 Subject: [PATCH 04/36] [wasm-ep] (browser-eventpipe sample) run loop for longer --- .../sample/wasm/browser-eventpipe/Program.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/mono/sample/wasm/browser-eventpipe/Program.cs b/src/mono/sample/wasm/browser-eventpipe/Program.cs index ca644051c1770..cfc5f66d04136 100644 --- a/src/mono/sample/wasm/browser-eventpipe/Program.cs +++ b/src/mono/sample/wasm/browser-eventpipe/Program.cs @@ -17,16 +17,20 @@ public static void Main(string[] args) [MethodImpl(MethodImplOptions.NoInlining)] public static async Task StartAsyncWork() { - int a = 0; + int a; int b = 1; const int N = 30; const int expected = 832040; - for (int i = 1; i < N; i++) + for (int j = 0; j < 1000; j++) { - int tmp = a + b; - a = b; - b = tmp; - await Task.Yield(); + a = 0; b = 1; + for (int i = 1; i < N; i++) + { + int tmp = a + b; + a = b; + b = tmp; + await Task.Yield(); + } } return b == expected ? 42 : 0; } From f269cee4f04a06112140e8736bb6f1186bccf92f Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Mon, 9 May 2022 12:47:27 -0400 Subject: [PATCH 05/36] [samples/wasm-eventpipe] make an async task sample change the sample to do some work asynchronously using setTimeout instead of blocking --- .../sample/wasm/browser-eventpipe/Program.cs | 33 +++++++++++++++++-- .../sample/wasm/browser-eventpipe/main.js | 22 ++++++++++--- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/src/mono/sample/wasm/browser-eventpipe/Program.cs b/src/mono/sample/wasm/browser-eventpipe/Program.cs index cfc5f66d04136..c03293604e884 100644 --- a/src/mono/sample/wasm/browser-eventpipe/Program.cs +++ b/src/mono/sample/wasm/browser-eventpipe/Program.cs @@ -3,8 +3,10 @@ using System; using System.Runtime.CompilerServices; +using System.Threading; using System.Threading.Tasks; + namespace Sample { public class Test @@ -14,14 +16,26 @@ public static void Main(string[] args) Console.WriteLine ("Hello, World!"); } + private static int iterations; + private static CancellationTokenSource cts; + + public static CancellationToken GetCancellationToken() + { + if (cts == null) { + cts = new CancellationTokenSource (); + } + return cts.Token; + } + [MethodImpl(MethodImplOptions.NoInlining)] public static async Task StartAsyncWork() { + CancellationToken ct = GetCancellationToken(); int a; - int b = 1; + int b; const int N = 30; const int expected = 832040; - for (int j = 0; j < 1000; j++) + while (true) { a = 0; b = 1; for (int i = 1; i < N; i++) @@ -29,10 +43,23 @@ public static async Task StartAsyncWork() int tmp = a + b; a = b; b = tmp; - await Task.Yield(); + await Task.Delay(1).ConfigureAwait(false); } + if (ct.IsCancellationRequested) + break; + iterations++; } return b == expected ? 42 : 0; } + + public static void StopWork() + { + cts.Cancel(); + } + + public static string GetIterationsDone() + { + return iterations.ToString(); + } } } diff --git a/src/mono/sample/wasm/browser-eventpipe/main.js b/src/mono/sample/wasm/browser-eventpipe/main.js index 25fd5c2b0fa68..fa8ebdf4de0b7 100644 --- a/src/mono/sample/wasm/browser-eventpipe/main.js +++ b/src/mono/sample/wasm/browser-eventpipe/main.js @@ -77,6 +77,8 @@ function createEventPipeSession() { return new EventPipeSession(); } +const delay = (ms) => new Promise((resolve) => setTimeout (resolve, ms)) + async function main() { const createDotnetRuntime = await loadRuntime(); const { MONO, BINDING, Module, RuntimeBuildInfo } = await createDotnetRuntime(() => { @@ -95,17 +97,29 @@ async function main() { console.log('after createDotnetRuntime') try { - const testMeaning = BINDING.bind_static_method("[Wasm.Browser.EventPipe.Sample] Sample.Test:StartAsyncWork"); + 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 = createEventPipeSession(); eventSession.start(); - const ret = await testMeaning(); - document.getElementById("out").innerHTML = `${ret} as computed on dotnet ver ${RuntimeBuildInfo.ProductVersion}`; + const workPromise = startWork(); + document.getElementById("out").innerHTML = '<<running>>'; + await delay(5000); // let it run for 5 seconds - console.debug(`ret: ${ret}`); + stopWork(); + + document.getElementById("out").innerHTML = '<<stopping>>'; + + const ret = await workPromise; // get the answer + const iterations = getIterationsDone(); // get how many times the loop ran eventSession.stop(); + document.getElementById("out").innerHTML = `${ret} as computed in ${iterations} iterations on dotnet ver ${RuntimeBuildInfo.ProductVersion}`; + + console.debug(`ret: ${ret}`); + const sessionRes = eventSession.saveTrace(); let exit_code = ret == 42 ? 0 : 1; From 18f73b92d79701a0316248f883a450f6a882163e Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Mon, 9 May 2022 16:22:11 -0400 Subject: [PATCH 06/36] [wasm] Add MONO.diagnostics interface Binds enable, start, disable methods defaulting to non-streaming FILE mode --- src/mono/mono/component/event_pipe-stub.c | 63 ++++++++++++ src/mono/mono/component/event_pipe.c | 84 ++++++++++++++++ .../Wasm.Browser.EventPipe.Sample.csproj | 6 +- .../sample/wasm/browser-eventpipe/main.js | 39 +------- src/mono/wasm/runtime/cwraps.ts | 12 ++- src/mono/wasm/runtime/diagnostics.ts | 99 +++++++++++++++++++ src/mono/wasm/runtime/dotnet.d.ts | 13 +++ src/mono/wasm/runtime/exports.ts | 6 +- src/mono/wasm/runtime/types.ts | 8 +- 9 files changed, 290 insertions(+), 40 deletions(-) create mode 100644 src/mono/wasm/runtime/diagnostics.ts diff --git a/src/mono/mono/component/event_pipe-stub.c b/src/mono/mono/component/event_pipe-stub.c index cb82a216b2d3d..4cfa176ffb989 100644 --- a/src/mono/mono/component/event_pipe-stub.c +++ b/src/mono/mono/component/event_pipe-stub.c @@ -6,6 +6,35 @@ #include "mono/component/event_pipe.h" #include "mono/metadata/components.h" + +#ifdef HOST_WASM +#include + +G_BEGIN_DECLS + +EMSCRIPTEN_KEEPALIVE gboolean +mono_wasm_event_pipe_enable (const char *output_path, + uint32_t circular_buffer_size_in_mb, + const char *providers, + /* EventPipeSessionType session_type = EP_SESSION_TYPE_FILE, */ + /* EventPipieSerializationFormat format = EP_SERIALIZATION_FORMAT_NETTRACE_V4, */ + /* bool */ gboolean rundown_requested, + /* IpcStream stream = NULL, */ + /* EventPipeSessionSycnhronousCallback sync_callback = NULL, */ + /* void *callback_additional_data, */ + int32_t *out_session_id); + + +EMSCRIPTEN_KEEPALIVE gboolean +mono_wasm_event_pipe_session_start_streaming (int32_t session_id); + +EMSCRIPTEN_KEEPALIVE gboolean +mono_wasm_event_pipe_session_disable (int32_t session_id); + +G_END_DECLS + +#endif /* HOST_WASM */ + static EventPipeSessionID _dummy_session_id; static uint8_t _max_event_pipe_type_size [256]; @@ -495,3 +524,37 @@ mono_component_event_pipe_init (void) { return component_event_pipe_stub_init (); } + +#ifdef HOST_WASM + +EMSCRIPTEN_KEEPALIVE gboolean +mono_wasm_event_pipe_enable (const char *output_path, + uint32_t circular_buffer_size_in_mb, + const char *providers, + /* EventPipeSessionType session_type = EP_SESSION_TYPE_FILE, */ + /* EventPipieSerializationFormat format = EP_SERIALIZATION_FORMAT_NETTRACE_V4, */ + /* bool */ gboolean rundown_requested, + /* IpcStream stream = NULL, */ + /* EventPipeSessionSycnhronousCallback sync_callback = NULL, */ + /* void *callback_additional_data, */ + int32_t *out_session_id) +{ + if (out_session_id) + *out_session_id = 0; + return 0; +} + + +EMSCRIPTEN_KEEPALIVE gboolean +mono_wasm_event_pipe_session_start_streaming (int32_t session_id) +{ + g_assert_not_reached (); +} + +EMSCRIPTEN_KEEPALIVE gboolean +mono_wasm_event_pipe_session_disable (int32_t session_id) +{ + g_assert_not_reached (); +} + +#endif /* HOST_WASM */ diff --git a/src/mono/mono/component/event_pipe.c b/src/mono/mono/component/event_pipe.c index 3c950bf11ccb4..f15783008d7af 100644 --- a/src/mono/mono/component/event_pipe.c +++ b/src/mono/mono/component/event_pipe.c @@ -6,11 +6,39 @@ #include #include #include +#include #include #include #include #include +#ifdef HOST_WASM +#include + +G_BEGIN_DECLS + +EMSCRIPTEN_KEEPALIVE gboolean +mono_wasm_event_pipe_enable (const char *output_path, + uint32_t circular_buffer_size_in_mb, + const char *providers, + /* EventPipeSessionType session_type = EP_SESSION_TYPE_FILE, */ + /* EventPipieSerializationFormat format = EP_SERIALIZATION_FORMAT_NETTRACE_V4, */ + /* bool */ gboolean rundown_requested, + /* IpcStream stream = NULL, */ + /* EventPipeSessionSycnhronousCallback sync_callback = NULL, */ + /* void *callback_additional_data, */ + int32_t *out_session_id); + +EMSCRIPTEN_KEEPALIVE gboolean +mono_wasm_event_pipe_session_start_streaming (int32_t session_id); + +EMSCRIPTEN_KEEPALIVE gboolean +mono_wasm_event_pipe_session_disable (int32_t session_id); + +G_END_DECLS + +#endif /* HOST_WASM */ + extern void ep_rt_mono_component_init (void); static bool _event_pipe_component_inited = false; @@ -327,3 +355,59 @@ mono_component_event_pipe_init (void) return &fn_table; } + + +#ifdef HOST_WASM + +EMSCRIPTEN_KEEPALIVE gboolean +mono_wasm_event_pipe_enable (const char *output_path, + uint32_t circular_buffer_size_in_mb, + const char *providers, + /* EventPipeSessionType session_type = EP_SESSION_TYPE_FILE, */ + /* EventPipieSerializationFormat format = EP_SERIALIZATION_FORMAT_NETTRACE_V4, */ + /* bool */ gboolean rundown_requested, + /* IpcStream stream = NULL, */ + /* EventPipeSessionSycnhronousCallback sync_callback = NULL, */ + /* void *callback_additional_data, */ + int32_t *out_session_id) +{ + MONO_ENTER_GC_UNSAFE; + EventPipeSerializationFormat format = EP_SERIALIZATION_FORMAT_NETTRACE_V4; + EventPipeSessionType session_type = EP_SESSION_TYPE_FILE; + + EventPipeSessionID session; + session = ep_enable_2 (output_path, + circular_buffer_size_in_mb, + providers, + session_type, + format, + !!rundown_requested, + /* stream */NULL, + /* callback*/ NULL, + /* callback_data*/ NULL); + + if (out_session_id) + *out_session_id = (int32_t)session; + MONO_EXIT_GC_UNSAFE; + return TRUE; +} + +EMSCRIPTEN_KEEPALIVE gboolean +mono_wasm_event_pipe_session_start_streaming (int32_t session_id) +{ + MONO_ENTER_GC_UNSAFE; + ep_start_streaming ((EventPipeSessionID)session_id); + MONO_EXIT_GC_UNSAFE; + return TRUE; +} + +EMSCRIPTEN_KEEPALIVE gboolean +mono_wasm_event_pipe_session_disable (int32_t session_id) +{ + MONO_ENTER_GC_UNSAFE; + ep_disable ((EventPipeSessionID)session_id); + MONO_EXIT_GC_UNSAFE; + return TRUE; +} + +#endif /* HOST_WASM */ 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 124d312b33d21..ac01c7e8505c1 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 @@ -14,10 +14,10 @@ - diff --git a/src/mono/sample/wasm/browser-eventpipe/main.js b/src/mono/sample/wasm/browser-eventpipe/main.js index fa8ebdf4de0b7..31cc30f005cae 100644 --- a/src/mono/sample/wasm/browser-eventpipe/main.js +++ b/src/mono/sample/wasm/browser-eventpipe/main.js @@ -46,37 +46,6 @@ async function loadRuntime() { } -function createEventPipeSession() { - class EventPipeSession { - state; - - constructor () { - this.state = 'INIT'; - } - - start = () => { - this.state = 'STARTED'; - console.log ('"Event"pipe session started'); - } - - stop = () => { - this.state = 'DONE'; - console.log ('"Event"pipe session stopped'); - } - - saveTrace = () => { - if (this.state !== 'DONE') { - console.err (`session is in state ${this.state}, not 'DONE'`); - return; - } - console.log ('"saving" session trace'); - return ''; - } - } - - return new EventPipeSession(); -} - const delay = (ms) => new Promise((resolve) => setTimeout (resolve, ms)) async function main() { @@ -96,11 +65,13 @@ async function main() { globalThis.MONO = MONO; console.log('after createDotnetRuntime') + let sessionFile = ''; + try { 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 = createEventPipeSession(); + const eventSession = MONO.diagnostics.createEventPipeSession(); eventSession.start(); const workPromise = startWork(); @@ -120,7 +91,7 @@ async function main() { console.debug(`ret: ${ret}`); - const sessionRes = eventSession.saveTrace(); + sessionFile = eventSession.saveTrace(); let exit_code = ret == 42 ? 0 : 1; Module._mono_wasm_exit(exit_code); @@ -129,7 +100,7 @@ async function main() { } catch (err) { console.log('WASM ERROR %o', err); - var b = Module.FS.readFile('/trace.nettrace'); + var b = Module.FS.readFile(sessionFile); var bits = btoa((Uint8ToString(b))); const filename = "dotnet-wasm-" + makeTimestamp() + ".nettrace"; diff --git a/src/mono/wasm/runtime/cwraps.ts b/src/mono/wasm/runtime/cwraps.ts index 861824f56d77b..da5c14c621a5c 100644 --- a/src/mono/wasm/runtime/cwraps.ts +++ b/src/mono/wasm/runtime/cwraps.ts @@ -65,6 +65,11 @@ const fn_signatures: [ident: string, returnType: string | null, argTypes?: strin ["mono_wasm_get_type_name", "string", ["number"]], ["mono_wasm_get_type_aqn", "string", ["number"]], + // MONO.diagnostics + ["mono_wasm_event_pipe_enable", "bool", ["string", "number", "string", "bool", "number"]], + ["mono_wasm_event_pipe_session_start_streaming", "bool", ["number"]], + ["mono_wasm_event_pipe_session_disable", "bool", ["number"]], + //DOTNET ["mono_wasm_string_from_js", "number", ["string"]], @@ -156,6 +161,11 @@ export interface t_Cwraps { */ mono_wasm_obj_array_set(array: MonoArray, idx: number, obj: MonoObject): void; + // MONO.diagnostics + mono_wasm_event_pipe_enable(outputPath: string, bufferSizeInMB: number, providers: string, rundownRequested: boolean, outSessionId: Int32Ptr): boolean; + mono_wasm_event_pipe_session_start_streaming(sessionId: number): boolean; + mono_wasm_event_pipe_session_disable(sessionId: number): boolean; + //DOTNET /** * @deprecated Not GC or thread safe @@ -191,4 +201,4 @@ export function wrap_c_function(name: string): Function { const fce = Module.cwrap(sig[0], sig[1], sig[2], sig[3]); wf[sig[0]] = fce; return fce; -} \ No newline at end of file +} diff --git a/src/mono/wasm/runtime/diagnostics.ts b/src/mono/wasm/runtime/diagnostics.ts new file mode 100644 index 0000000000000..eaefb314f5aca --- /dev/null +++ b/src/mono/wasm/runtime/diagnostics.ts @@ -0,0 +1,99 @@ +import { Module } from './imports'; +import cwraps from './cwraps'; +import type { EventPipeSessionOptions } from './types'; +import type { Int32Ptr, VoidPtr } from './types/emscripten'; +import * as memory from './memory'; + +export interface EventPipeSession { + get sessionID(): number; + stop(): void; + saveTrace(): string; + +} + +// internal JS state +enum State { + Initialized, + Started, + Done, +} + +/// An EventPipe session in the runtime. There may be multiple sessions. +class EventPipeFileSession implements EventPipeSession { + private state: State; + private _sessionID: number; // integer session ID + private _tracePath: string; // VFS file path to the trace file + + get sessionID(): number { return this._sessionID; } + + constructor (sessionID: number, tracePath: string) { + this.state = State.Initialized; + this._sessionID = sessionID; + this._tracePath = tracePath; + console.debug (`EventPipe session ${sessionID} started`); + } + + start = () => { + if (this.state !== State.Initialized) { + throw new Error(`EventPipe session ${this._sessionID} already started`); + } + this.state = State.Started; + cwraps.mono_wasm_event_pipe_session_start_streaming (this._sessionID); + console.debug (`EventPipe session ${this._sessionID} started`); + } + + stop = () => { + if (this.state !== State.Started) { + throw new Error(`cannot stop an EventPipe session in state ${this.state}, not 'Started'`); + } + this.state = State.Done; + cwraps.mono_wasm_event_pipe_session_disable (this._sessionID); + console.debug (`EventPipe session ${this._sessionID} stopped`); + } + + saveTrace = () => { + if (this.state !== State.Done) { + throw new Error (`session is in state ${this.state}, not 'Done'`); + } + console.debug (`session ${this._sessionID} trace in ${this._tracePath}`); + return this._tracePath; + } + + +} + + +export interface Diagnostics { + createEventPipeSession (options?: EventPipeSessionOptions): EventPipeSession | null; +} + +export const defaultOutputPath: string = '/trace.nettrace'; + +export const diagnostics: Diagnostics = { + createEventPipeSession(options?: EventPipeSessionOptions): EventPipeSession | null { + const rundownRequested = false; + const defaultProviders = ''; + const defaultBufferSizeInMB = 1; + + let sessionIdPtr: Int32Ptr = Module._malloc(4) as unknown as Int32Ptr; + + try { + const tracePath = options?.traceFilePath ?? defaultOutputPath + if (!cwraps.mono_wasm_event_pipe_enable(tracePath, + defaultBufferSizeInMB, + defaultProviders, + rundownRequested, + sessionIdPtr)) + return null; + + const sessionID = memory.getI32(sessionIdPtr); + + const session = new EventPipeFileSession (sessionID, tracePath); + return session; + } finally { + Module._free(sessionIdPtr as unknown as VoidPtr); + } + }, +}; + +export default diagnostics; diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index 0a4a8d48e8e7f..2f6aefa2bdaf0 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -204,6 +204,9 @@ declare type CoverageProfilerOptions = { write_at?: string; send_to?: string; }; +interface EventPipeSessionOptions { + traceFilePath?: string; +} declare type DotnetModuleConfig = { disableDotnet6Compatibility?: boolean; config?: MonoConfig | MonoConfigError; @@ -235,6 +238,15 @@ declare type DotnetModuleConfigImports = { url?: any; }; +interface EventPipeSession { + get sessionID(): number; + stop(): void; + saveTrace(): string; +} +interface Diagnostics { + createEventPipeSession(options?: EventPipeSessionOptions): EventPipeSession | null; +} + declare function mono_wasm_runtime_ready(): void; declare function mono_wasm_setenv(name: string, value: string): void; @@ -343,6 +355,7 @@ declare const MONO: { getU32: typeof getU32; getF32: typeof getF32; getF64: typeof getF64; + diagnostics: Diagnostics; }; declare type MONOType = typeof MONO; declare const BINDING: { diff --git a/src/mono/wasm/runtime/exports.ts b/src/mono/wasm/runtime/exports.ts index 064ddcfa21478..2608b0d59454e 100644 --- a/src/mono/wasm/runtime/exports.ts +++ b/src/mono/wasm/runtime/exports.ts @@ -67,6 +67,7 @@ import { create_weak_ref } from "./weak-ref"; import { fetch_like, readAsync_like } from "./polyfills"; import { EmscriptenModule } from "./types/emscripten"; import { mono_run_main, mono_run_main_and_exit } from "./run"; +import { diagnostics } from "./diagnostics"; const MONO = { // current "public" MONO API @@ -110,6 +111,9 @@ const MONO = { getU32, getF32, getF64, + + // Diagnostics + diagnostics }; export type MONOType = typeof MONO; @@ -418,4 +422,4 @@ class RuntimeList { const wr = this.list[runtimeId]; return wr ? wr.deref() : undefined; } -} \ No newline at end of file +} diff --git a/src/mono/wasm/runtime/types.ts b/src/mono/wasm/runtime/types.ts index 2b85e89953816..8c033bba16be8 100644 --- a/src/mono/wasm/runtime/types.ts +++ b/src/mono/wasm/runtime/types.ts @@ -173,6 +173,12 @@ export type CoverageProfilerOptions = { send_to?: string // should be in the format ::, default: 'WebAssembly.Runtime::DumpCoverageProfileData' (DumpCoverageProfileData stores the data into INTERNAL.coverage_profile_data.) } +/// Options to configure the event pipe session +export interface EventPipeSessionOptions { + /// location on the VFS where the session trace will be saved + traceFilePath?: string; +} + // how we extended emscripten Module export type DotnetModule = EmscriptenModule & DotnetModuleConfig; @@ -260,4 +266,4 @@ export const enum MarshalError { NULL_TYPE_POINTER = 514, UNSUPPORTED_TYPE = 515, FIRST = BUFFER_TOO_SMALL -} \ No newline at end of file +} From 75d33cb4f5fdbc8e9ec0e8725d7c51dea693635d Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Tue, 10 May 2022 16:19:26 -0400 Subject: [PATCH 07/36] if wasm threads are disabled, but perftracing is enabled, don't log overlapped io events --- .../System.Private.CoreLib/src/System/Threading/Overlapped.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/mono/System.Private.CoreLib/src/System/Threading/Overlapped.cs b/src/mono/System.Private.CoreLib/src/System/Threading/Overlapped.cs index 602c2c27bcaea..708c6d04f200b 100644 --- a/src/mono/System.Private.CoreLib/src/System/Threading/Overlapped.cs +++ b/src/mono/System.Private.CoreLib/src/System/Threading/Overlapped.cs @@ -99,8 +99,10 @@ internal sealed unsafe class OverlappedData success = true; #if FEATURE_PERFTRACING +#if !(TARGET_BROWSER && !FEATURE_WASM_THREADS) if (NativeRuntimeEventSource.Log.IsEnabled()) NativeRuntimeEventSource.Log.ThreadPoolIOPack(pNativeOverlapped); +#endif #endif return _pNativeOverlapped; } From 327502d888e87861373ed5ada20ebdb08ae87427 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Tue, 10 May 2022 16:34:14 -0400 Subject: [PATCH 08/36] fix whitespace and nits --- src/mono/mono/component/event_pipe.c | 52 ++++++++++++++-------------- src/mono/wasm/runtime/diagnostics.ts | 2 +- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/mono/mono/component/event_pipe.c b/src/mono/mono/component/event_pipe.c index f15783008d7af..0cf6422d41391 100644 --- a/src/mono/mono/component/event_pipe.c +++ b/src/mono/mono/component/event_pipe.c @@ -371,43 +371,43 @@ mono_wasm_event_pipe_enable (const char *output_path, /* void *callback_additional_data, */ int32_t *out_session_id) { - MONO_ENTER_GC_UNSAFE; - EventPipeSerializationFormat format = EP_SERIALIZATION_FORMAT_NETTRACE_V4; - EventPipeSessionType session_type = EP_SESSION_TYPE_FILE; - - EventPipeSessionID session; - session = ep_enable_2 (output_path, - circular_buffer_size_in_mb, - providers, - session_type, - format, - !!rundown_requested, - /* stream */NULL, - /* callback*/ NULL, - /* callback_data*/ NULL); + MONO_ENTER_GC_UNSAFE; + EventPipeSerializationFormat format = EP_SERIALIZATION_FORMAT_NETTRACE_V4; + EventPipeSessionType session_type = EP_SESSION_TYPE_FILE; + + EventPipeSessionID session; + session = ep_enable_2 (output_path, + circular_buffer_size_in_mb, + providers, + session_type, + format, + !!rundown_requested, + /* stream */NULL, + /* callback*/ NULL, + /* callback_data*/ NULL); - if (out_session_id) - *out_session_id = (int32_t)session; - MONO_EXIT_GC_UNSAFE; - return TRUE; + if (out_session_id) + *out_session_id = (int32_t)session; + MONO_EXIT_GC_UNSAFE; + return TRUE; } EMSCRIPTEN_KEEPALIVE gboolean mono_wasm_event_pipe_session_start_streaming (int32_t session_id) { - MONO_ENTER_GC_UNSAFE; - ep_start_streaming ((EventPipeSessionID)session_id); - MONO_EXIT_GC_UNSAFE; - return TRUE; + MONO_ENTER_GC_UNSAFE; + ep_start_streaming ((EventPipeSessionID)session_id); + MONO_EXIT_GC_UNSAFE; + return TRUE; } EMSCRIPTEN_KEEPALIVE gboolean mono_wasm_event_pipe_session_disable (int32_t session_id) { - MONO_ENTER_GC_UNSAFE; - ep_disable ((EventPipeSessionID)session_id); - MONO_EXIT_GC_UNSAFE; - return TRUE; + MONO_ENTER_GC_UNSAFE; + ep_disable ((EventPipeSessionID)session_id); + MONO_EXIT_GC_UNSAFE; + return TRUE; } #endif /* HOST_WASM */ diff --git a/src/mono/wasm/runtime/diagnostics.ts b/src/mono/wasm/runtime/diagnostics.ts index eaefb314f5aca..6e675ff0de5af 100644 --- a/src/mono/wasm/runtime/diagnostics.ts +++ b/src/mono/wasm/runtime/diagnostics.ts @@ -75,7 +75,7 @@ export const diagnostics: Diagnostics = { const defaultProviders = ''; const defaultBufferSizeInMB = 1; - let sessionIdPtr: Int32Ptr = Module._malloc(4) as unknown as Int32Ptr; + const sessionIdPtr = Module._malloc(4) as unknown as Int32Ptr; try { const tracePath = options?.traceFilePath ?? defaultOutputPath From fafecedf43384823889eac35e42a09241ce48c76 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Tue, 10 May 2022 16:34:25 -0400 Subject: [PATCH 09/36] don't need try/finally in the sample anymore --- .../sample/wasm/browser-eventpipe/main.js | 57 ++++++++----------- 1 file changed, 25 insertions(+), 32 deletions(-) diff --git a/src/mono/sample/wasm/browser-eventpipe/main.js b/src/mono/sample/wasm/browser-eventpipe/main.js index 31cc30f005cae..b864506f7442a 100644 --- a/src/mono/sample/wasm/browser-eventpipe/main.js +++ b/src/mono/sample/wasm/browser-eventpipe/main.js @@ -65,49 +65,42 @@ async function main() { globalThis.MONO = MONO; console.log('after createDotnetRuntime') - let sessionFile = ''; - - try { - 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(); - - document.getElementById("out").innerHTML = '<<running>>'; - await delay(5000); // let it run for 5 seconds + 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(); - stopWork(); + document.getElementById("out").innerHTML = '<<running>>'; + await delay(5000); // let it run for 5 seconds - document.getElementById("out").innerHTML = '<<stopping>>'; + stopWork(); - const ret = await workPromise; // get the answer - const iterations = getIterationsDone(); // get how many times the loop ran + document.getElementById("out").innerHTML = '<<stopping>>'; - eventSession.stop(); + const ret = await workPromise; // get the answer + const iterations = getIterationsDone(); // get how many times the loop ran - document.getElementById("out").innerHTML = `${ret} as computed in ${iterations} iterations on dotnet ver ${RuntimeBuildInfo.ProductVersion}`; + eventSession.stop(); - console.debug(`ret: ${ret}`); + document.getElementById("out").innerHTML = `${ret} as computed in ${iterations} iterations on dotnet ver ${RuntimeBuildInfo.ProductVersion}`; - sessionFile = eventSession.saveTrace(); + console.debug(`ret: ${ret}`); - let exit_code = ret == 42 ? 0 : 1; - Module._mono_wasm_exit(exit_code); + const sessionFile = eventSession.saveTrace(); - wasm_exit(exit_code); - } catch (err) { - console.log('WASM ERROR %o', err); + const exit_code = ret == 42 ? 0 : 1; - var b = Module.FS.readFile(sessionFile); - var bits = btoa((Uint8ToString(b))); + wasm_exit(exit_code); + + const b = Module.FS.readFile(sessionFile); + + const bits = btoa((Uint8ToString(b))); + + const filename = "dotnet-wasm-" + makeTimestamp() + ".nettrace"; + downloadData("data:application/octet-stream;base64," + bits, filename); - const filename = "dotnet-wasm-" + makeTimestamp() + ".nettrace"; - downloadData("data:application/octet-stream;base64," + bits, filename); - - wasm_exit(2); - } } setTimeout(main, 10000); From 252d06af5199984b0d4a4cc6de677363011f1244 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Tue, 10 May 2022 16:42:12 -0400 Subject: [PATCH 10/36] more whitespace --- src/mono/mono/component/event_pipe-stub.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mono/mono/component/event_pipe-stub.c b/src/mono/mono/component/event_pipe-stub.c index 4cfa176ffb989..055cc62e30279 100644 --- a/src/mono/mono/component/event_pipe-stub.c +++ b/src/mono/mono/component/event_pipe-stub.c @@ -548,13 +548,13 @@ mono_wasm_event_pipe_enable (const char *output_path, EMSCRIPTEN_KEEPALIVE gboolean mono_wasm_event_pipe_session_start_streaming (int32_t session_id) { - g_assert_not_reached (); + g_assert_not_reached (); } EMSCRIPTEN_KEEPALIVE gboolean mono_wasm_event_pipe_session_disable (int32_t session_id) { - g_assert_not_reached (); + g_assert_not_reached (); } #endif /* HOST_WASM */ From 8b54aab8b34c70ad8342a54e3993daefe5835c8b Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Tue, 10 May 2022 16:45:49 -0400 Subject: [PATCH 11/36] add start method to EventPipeSession interface --- src/mono/wasm/runtime/diagnostics.ts | 1 + src/mono/wasm/runtime/dotnet.d.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/mono/wasm/runtime/diagnostics.ts b/src/mono/wasm/runtime/diagnostics.ts index 6e675ff0de5af..c7d23d2cf1e7b 100644 --- a/src/mono/wasm/runtime/diagnostics.ts +++ b/src/mono/wasm/runtime/diagnostics.ts @@ -6,6 +6,7 @@ import * as memory from './memory'; export interface EventPipeSession { get sessionID(): number; + start(): void; stop(): void; saveTrace(): string; diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index 2f6aefa2bdaf0..7aa69ea4d514d 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -240,6 +240,7 @@ declare type DotnetModuleConfigImports = { interface EventPipeSession { get sessionID(): number; + start(): void; stop(): void; saveTrace(): string; } From e2e1b66d64642022d873f378cf2453360e8af80e Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Tue, 10 May 2022 20:07:40 -0400 Subject: [PATCH 12/36] don't run wasm-eventpipe sample on CI lanes without perftracing --- src/libraries/tests.proj | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libraries/tests.proj b/src/libraries/tests.proj index 73d607611079d..973254335d901 100644 --- a/src/libraries/tests.proj +++ b/src/libraries/tests.proj @@ -57,6 +57,11 @@ + + + + + From ded3aeb374cdc3354ca052df890dee29eb9071cc Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Tue, 10 May 2022 20:08:48 -0400 Subject: [PATCH 13/36] more whitespace --- src/mono/mono/component/event_pipe-stub.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mono/mono/component/event_pipe-stub.c b/src/mono/mono/component/event_pipe-stub.c index 055cc62e30279..2c409f550a4e8 100644 --- a/src/mono/mono/component/event_pipe-stub.c +++ b/src/mono/mono/component/event_pipe-stub.c @@ -539,9 +539,9 @@ mono_wasm_event_pipe_enable (const char *output_path, /* void *callback_additional_data, */ int32_t *out_session_id) { - if (out_session_id) - *out_session_id = 0; - return 0; + if (out_session_id) + *out_session_id = 0; + return 0; } From 46c1a7c9db4be0d243e347fb45678444e6e011ea Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Tue, 10 May 2022 22:06:15 -0400 Subject: [PATCH 14/36] fix eslint warnings, default rundown to true, allow callback for traceFilePath option --- src/mono/wasm/runtime/diagnostics.ts | 64 ++++++++++++++++------------ src/mono/wasm/runtime/types.ts | 7 ++- 2 files changed, 42 insertions(+), 29 deletions(-) diff --git a/src/mono/wasm/runtime/diagnostics.ts b/src/mono/wasm/runtime/diagnostics.ts index c7d23d2cf1e7b..224c36344fe5f 100644 --- a/src/mono/wasm/runtime/diagnostics.ts +++ b/src/mono/wasm/runtime/diagnostics.ts @@ -1,9 +1,13 @@ -import { Module } from './imports'; -import cwraps from './cwraps'; -import type { EventPipeSessionOptions } from './types'; -import type { Int32Ptr, VoidPtr } from './types/emscripten'; -import * as memory from './memory'; - +import { Module } from "./imports"; +import cwraps from "./cwraps"; +import type { EventPipeSessionOptions } from "./types"; +import type { Int32Ptr, VoidPtr } from "./types/emscripten"; +import * as memory from "./memory"; + +/// An EventPipe session object represents a single diagnostic tracing session that is collecting +/// events from the runtime and managed libraries. There may be multiple active sessions at the same time. +/// Each session subscribes to a number of providers and will collect events from the time that start() is called, until stop() is called. +/// Upon completion the session saves the events to a file on the VFS. export interface EventPipeSession { get sessionID(): number; start(): void; @@ -12,7 +16,7 @@ export interface EventPipeSession { } -// internal JS state +// internal session state of the JS instance enum State { Initialized, Started, @@ -21,40 +25,40 @@ enum State { /// An EventPipe session in the runtime. There may be multiple sessions. class EventPipeFileSession implements EventPipeSession { - private state: State; + private _state: State; private _sessionID: number; // integer session ID private _tracePath: string; // VFS file path to the trace file get sessionID(): number { return this._sessionID; } constructor (sessionID: number, tracePath: string) { - this.state = State.Initialized; + this._state = State.Initialized; this._sessionID = sessionID; this._tracePath = tracePath; console.debug (`EventPipe session ${sessionID} started`); } start = () => { - if (this.state !== State.Initialized) { + if (this._state !== State.Initialized) { throw new Error(`EventPipe session ${this._sessionID} already started`); } - this.state = State.Started; + this._state = State.Started; cwraps.mono_wasm_event_pipe_session_start_streaming (this._sessionID); console.debug (`EventPipe session ${this._sessionID} started`); } stop = () => { - if (this.state !== State.Started) { - throw new Error(`cannot stop an EventPipe session in state ${this.state}, not 'Started'`); + if (this._state !== State.Started) { + throw new Error(`cannot stop an EventPipe session in state ${this._state}, not 'Started'`); } - this.state = State.Done; + this._state = State.Done; cwraps.mono_wasm_event_pipe_session_disable (this._sessionID); console.debug (`EventPipe session ${this._sessionID} stopped`); } saveTrace = () => { - if (this.state !== State.Done) { - throw new Error (`session is in state ${this.state}, not 'Done'`); + if (this._state !== State.Done) { + throw new Error (`session is in state ${this._state}, not 'Done'`); } console.debug (`session ${this._sessionID} trace in ${this._tracePath}`); return this._tracePath; @@ -68,28 +72,34 @@ export interface Diagnostics { createEventPipeSession (options?: EventPipeSessionOptions): EventPipeSession | null; } -export const defaultOutputPath: string = '/trace.nettrace'; +export const defaultOutputPath = "/trace.nettrace"; + +function computeTracePath (tracePath? : string | (() => string | null | undefined)): string { + if (tracePath === undefined) { + return defaultOutputPath; + } + if (typeof tracePath === "function") + return tracePath() ?? defaultOutputPath; + return tracePath; +} export const diagnostics: Diagnostics = { createEventPipeSession(options?: EventPipeSessionOptions): EventPipeSession | null { - const rundownRequested = false; - const defaultProviders = ''; + const defaultRundownRequested = true; + const defaultProviders = ""; const defaultBufferSizeInMB = 1; const sessionIdPtr = Module._malloc(4) as unknown as Int32Ptr; try { - const tracePath = options?.traceFilePath ?? defaultOutputPath - if (!cwraps.mono_wasm_event_pipe_enable(tracePath, - defaultBufferSizeInMB, - defaultProviders, - rundownRequested, - sessionIdPtr)) - return null; + const tracePath = computeTracePath(options?.traceFilePath); + const rundown = options?.collectRundownEvents ?? defaultRundownRequested; + if (!cwraps.mono_wasm_event_pipe_enable(tracePath, defaultBufferSizeInMB, defaultProviders, rundown, sessionIdPtr)) + return null; const sessionID = memory.getI32(sessionIdPtr); - const session = new EventPipeFileSession (sessionID, tracePath); + const session = new EventPipeFileSession(sessionID, tracePath); return session; } finally { Module._free(sessionIdPtr as unknown as VoidPtr); diff --git a/src/mono/wasm/runtime/types.ts b/src/mono/wasm/runtime/types.ts index 8c033bba16be8..4c342bcad271b 100644 --- a/src/mono/wasm/runtime/types.ts +++ b/src/mono/wasm/runtime/types.ts @@ -175,8 +175,11 @@ export type CoverageProfilerOptions = { /// Options to configure the event pipe session export interface EventPipeSessionOptions { - /// location on the VFS where the session trace will be saved - traceFilePath?: string; + /// Location on the VFS where the session trace will be saved (default: "/trace.nettrace") + traceFilePath?: string | (() => string | null | undefined); + /// 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; } // how we extended emscripten Module From c29915db2eb0f3c3dacb4fe11c1b70a2f13655ff Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Tue, 10 May 2022 23:43:01 -0400 Subject: [PATCH 15/36] add EventPipeSession.getTraceBlob and EventPipeSession.getTraceDataURI two ways of retreiving the collected traces instead of exposing the emscripten VFS directly. Probably the Blob one is enough. Is there any reason to also provide a data URI? update the sample to use URL.createObjectURL (session.getTraceBlob()) to create the download link --- .../sample/wasm/browser-eventpipe/main.js | 31 +++++++---------- src/mono/wasm/runtime/diagnostics.ts | 34 ++++++++++++++----- src/mono/wasm/runtime/dotnet.d.ts | 6 ++-- 3 files changed, 43 insertions(+), 28 deletions(-) diff --git a/src/mono/sample/wasm/browser-eventpipe/main.js b/src/mono/sample/wasm/browser-eventpipe/main.js index b864506f7442a..09a24924a0e87 100644 --- a/src/mono/sample/wasm/browser-eventpipe/main.js +++ b/src/mono/sample/wasm/browser-eventpipe/main.js @@ -30,15 +30,6 @@ function makeTimestamp() return s.replace(/[:.]/g, '-'); } -function Uint8ToString(u8a){ - var CHUNK_SZ = 0x8000; - var c = []; - for (var i=0; i < u8a.length; i+=CHUNK_SZ) { - c.push(String.fromCharCode.apply(null, u8a.subarray(i, i+CHUNK_SZ))); - } - return c.join(""); -} - async function loadRuntime() { globalThis.exports = {}; await import("./dotnet.js"); @@ -48,6 +39,8 @@ async function loadRuntime() { const delay = (ms) => new Promise((resolve) => setTimeout (resolve, ms)) +const saveUsingBlob = true; + async function main() { const createDotnetRuntime = await loadRuntime(); const { MONO, BINDING, Module, RuntimeBuildInfo } = await createDotnetRuntime(() => { @@ -88,19 +81,21 @@ async function main() { console.debug(`ret: ${ret}`); - const sessionFile = eventSession.saveTrace(); + 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; wasm_exit(exit_code); - - const b = Module.FS.readFile(sessionFile); - - const bits = btoa((Uint8ToString(b))); - - const filename = "dotnet-wasm-" + makeTimestamp() + ".nettrace"; - downloadData("data:application/octet-stream;base64," + bits, filename); - } setTimeout(main, 10000); diff --git a/src/mono/wasm/runtime/diagnostics.ts b/src/mono/wasm/runtime/diagnostics.ts index 224c36344fe5f..1e225da128730 100644 --- a/src/mono/wasm/runtime/diagnostics.ts +++ b/src/mono/wasm/runtime/diagnostics.ts @@ -8,12 +8,13 @@ import * as memory from "./memory"; /// events from the runtime and managed libraries. There may be multiple active sessions at the same time. /// Each session subscribes to a number of providers and will collect events from the time that start() is called, until stop() is called. /// Upon completion the session saves the events to a file on the VFS. +/// The data can then be retrieved as Blob or as a data URI (prefer Blob). export interface EventPipeSession { get sessionID(): number; start(): void; stop(): void; - saveTrace(): string; - + getTraceBlob(): Blob; + getTraceDataURI(): string; } // internal session state of the JS instance @@ -23,7 +24,7 @@ enum State { Done, } -/// An EventPipe session in the runtime. There may be multiple sessions. +/// An EventPipe session that saves the event data to a file in the VFS. class EventPipeFileSession implements EventPipeSession { private _state: State; private _sessionID: number; // integer session ID @@ -56,17 +57,30 @@ class EventPipeFileSession implements EventPipeSession { console.debug (`EventPipe session ${this._sessionID} stopped`); } - saveTrace = () => { + getTraceBlob = () => { if (this._state !== State.Done) { throw new Error (`session is in state ${this._state}, not 'Done'`); } - console.debug (`session ${this._sessionID} trace in ${this._tracePath}`); - return this._tracePath; + const data = Module.FS_readFile(this._tracePath, { encoding: "binary" }) as Uint8Array; + return new Blob([data], { type: "application/octet-stream" }); } - + getTraceDataURI = () => { + if (this._state !== State.Done) { + throw new Error (`session is in state ${this._state}, not 'Done'`); + } + const data = Module.FS_readFile(this._tracePath, { encoding: "binary" }) as Uint8Array; + return `data:application/octet-stream;base64,${dataUriEncode(data)}`; + } +} +function dataUriEncode (data: Uint8Array): string { + const CHUNK_SZ = 0x8000; + const c: string[] = []; + for (let i=0; i < data.length; i+=CHUNK_SZ) { + c.push(String.fromCharCode(... data.subarray(i, i+CHUNK_SZ))); + } + return btoa(c.join("")); } - export interface Diagnostics { createEventPipeSession (options?: EventPipeSessionOptions): EventPipeSession | null; @@ -83,7 +97,11 @@ function computeTracePath (tracePath? : string | (() => string | null | undefine return tracePath; } +/// APIs for working with .NET diagnostics from JavaScript. export const diagnostics: Diagnostics = { + /// Creates a new EventPipe session that will collect trace events from the runtime and managed libraries. + /// Use the options to control the output file and the level of detail. + /// Note that if you use multiple sessions at the same time, you should specify a unique 'traceFilePath' for each session. createEventPipeSession(options?: EventPipeSessionOptions): EventPipeSession | null { const defaultRundownRequested = true; const defaultProviders = ""; diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index 7aa69ea4d514d..b5a3276f36bfb 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -205,7 +205,8 @@ declare type CoverageProfilerOptions = { send_to?: string; }; interface EventPipeSessionOptions { - traceFilePath?: string; + traceFilePath?: string | (() => string | null | undefined); + collectRundownEvents?: boolean; } declare type DotnetModuleConfig = { disableDotnet6Compatibility?: boolean; @@ -242,7 +243,8 @@ interface EventPipeSession { get sessionID(): number; start(): void; stop(): void; - saveTrace(): string; + getTraceBlob(): Blob; + getTraceDataURI(): string; } interface Diagnostics { createEventPipeSession(options?: EventPipeSessionOptions): EventPipeSession | null; From 828739612df3bf0ab6c68ede0abe95b3cdeabf8b Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Wed, 11 May 2022 10:21:10 -0400 Subject: [PATCH 16/36] [browser-eventpipe sample] remove unnecessary ref assemblies --- .../browser-eventpipe/Wasm.Browser.EventPipe.Sample.csproj | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 ac01c7e8505c1..477e50104edce 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 @@ -30,12 +30,10 @@ true - + - - From a7bd41b9e92ce3661a458fec66d6b8714f1c7c2c Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Wed, 11 May 2022 10:44:26 -0400 Subject: [PATCH 17/36] use toBase64StringImpl instead of btoa --- src/mono/wasm/runtime/diagnostics.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/mono/wasm/runtime/diagnostics.ts b/src/mono/wasm/runtime/diagnostics.ts index 1e225da128730..17f27fac82e4f 100644 --- a/src/mono/wasm/runtime/diagnostics.ts +++ b/src/mono/wasm/runtime/diagnostics.ts @@ -3,6 +3,7 @@ import cwraps from "./cwraps"; import type { EventPipeSessionOptions } from "./types"; import type { Int32Ptr, VoidPtr } from "./types/emscripten"; import * as memory from "./memory"; +import { toBase64StringImpl } from './base64'; /// An EventPipe session object represents a single diagnostic tracing session that is collecting /// events from the runtime and managed libraries. There may be multiple active sessions at the same time. @@ -70,17 +71,9 @@ class EventPipeFileSession implements EventPipeSession { throw new Error (`session is in state ${this._state}, not 'Done'`); } const data = Module.FS_readFile(this._tracePath, { encoding: "binary" }) as Uint8Array; - return `data:application/octet-stream;base64,${dataUriEncode(data)}`; + return `data:application/octet-stream;base64,${toBase64StringImpl(data)}`; } } -function dataUriEncode (data: Uint8Array): string { - const CHUNK_SZ = 0x8000; - const c: string[] = []; - for (let i=0; i < data.length; i+=CHUNK_SZ) { - c.push(String.fromCharCode(... data.subarray(i, i+CHUNK_SZ))); - } - return btoa(c.join("")); -} export interface Diagnostics { createEventPipeSession (options?: EventPipeSessionOptions): EventPipeSession | null; From be8d199aa38c5d0b23657411e735b684ad8111e1 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Wed, 11 May 2022 11:49:45 -0400 Subject: [PATCH 18/36] use int64_t for event pipe session IDs --- src/mono/mono/component/event_pipe-stub.c | 12 ++-- src/mono/mono/component/event_pipe.c | 18 +++--- src/mono/wasm/runtime/cuint64.ts | 48 +++++++++++++++ src/mono/wasm/runtime/cwraps.ts | 6 +- src/mono/wasm/runtime/diagnostics.ts | 75 ++++++++++++++++------- src/mono/wasm/runtime/dotnet.d.ts | 2 +- src/mono/wasm/runtime/memory.ts | 13 ++++ 7 files changed, 132 insertions(+), 42 deletions(-) create mode 100644 src/mono/wasm/runtime/cuint64.ts diff --git a/src/mono/mono/component/event_pipe-stub.c b/src/mono/mono/component/event_pipe-stub.c index 2c409f550a4e8..a3fb58a483719 100644 --- a/src/mono/mono/component/event_pipe-stub.c +++ b/src/mono/mono/component/event_pipe-stub.c @@ -22,14 +22,14 @@ mono_wasm_event_pipe_enable (const char *output_path, /* IpcStream stream = NULL, */ /* EventPipeSessionSycnhronousCallback sync_callback = NULL, */ /* void *callback_additional_data, */ - int32_t *out_session_id); + int64_t *out_session_id); EMSCRIPTEN_KEEPALIVE gboolean -mono_wasm_event_pipe_session_start_streaming (int32_t session_id); +mono_wasm_event_pipe_session_start_streaming (const int64_t *session_id); EMSCRIPTEN_KEEPALIVE gboolean -mono_wasm_event_pipe_session_disable (int32_t session_id); +mono_wasm_event_pipe_session_disable (const int64_t *session_id); G_END_DECLS @@ -537,7 +537,7 @@ mono_wasm_event_pipe_enable (const char *output_path, /* IpcStream stream = NULL, */ /* EventPipeSessionSycnhronousCallback sync_callback = NULL, */ /* void *callback_additional_data, */ - int32_t *out_session_id) + int64_t *out_session_id) { if (out_session_id) *out_session_id = 0; @@ -546,13 +546,13 @@ mono_wasm_event_pipe_enable (const char *output_path, EMSCRIPTEN_KEEPALIVE gboolean -mono_wasm_event_pipe_session_start_streaming (int32_t session_id) +mono_wasm_event_pipe_session_start_streaming (const int64_t *session_id) { g_assert_not_reached (); } EMSCRIPTEN_KEEPALIVE gboolean -mono_wasm_event_pipe_session_disable (int32_t session_id) +mono_wasm_event_pipe_session_disable (const int64_t *session_id) { g_assert_not_reached (); } diff --git a/src/mono/mono/component/event_pipe.c b/src/mono/mono/component/event_pipe.c index 0cf6422d41391..961d20a2047e9 100644 --- a/src/mono/mono/component/event_pipe.c +++ b/src/mono/mono/component/event_pipe.c @@ -27,13 +27,13 @@ mono_wasm_event_pipe_enable (const char *output_path, /* IpcStream stream = NULL, */ /* EventPipeSessionSycnhronousCallback sync_callback = NULL, */ /* void *callback_additional_data, */ - int32_t *out_session_id); + int64_t *out_session_id); EMSCRIPTEN_KEEPALIVE gboolean -mono_wasm_event_pipe_session_start_streaming (int32_t session_id); +mono_wasm_event_pipe_session_start_streaming (const int64_t *session_id); EMSCRIPTEN_KEEPALIVE gboolean -mono_wasm_event_pipe_session_disable (int32_t session_id); +mono_wasm_event_pipe_session_disable (const int64_t *session_id); G_END_DECLS @@ -369,7 +369,7 @@ mono_wasm_event_pipe_enable (const char *output_path, /* IpcStream stream = NULL, */ /* EventPipeSessionSycnhronousCallback sync_callback = NULL, */ /* void *callback_additional_data, */ - int32_t *out_session_id) + int64_t *out_session_id) { MONO_ENTER_GC_UNSAFE; EventPipeSerializationFormat format = EP_SERIALIZATION_FORMAT_NETTRACE_V4; @@ -387,25 +387,25 @@ mono_wasm_event_pipe_enable (const char *output_path, /* callback_data*/ NULL); if (out_session_id) - *out_session_id = (int32_t)session; + *out_session_id = session; MONO_EXIT_GC_UNSAFE; return TRUE; } EMSCRIPTEN_KEEPALIVE gboolean -mono_wasm_event_pipe_session_start_streaming (int32_t session_id) +mono_wasm_event_pipe_session_start_streaming (const int64_t *session_id) { MONO_ENTER_GC_UNSAFE; - ep_start_streaming ((EventPipeSessionID)session_id); + ep_start_streaming ((EventPipeSessionID)*session_id); MONO_EXIT_GC_UNSAFE; return TRUE; } EMSCRIPTEN_KEEPALIVE gboolean -mono_wasm_event_pipe_session_disable (int32_t session_id) +mono_wasm_event_pipe_session_disable (const int64_t *session_id) { MONO_ENTER_GC_UNSAFE; - ep_disable ((EventPipeSessionID)session_id); + ep_disable ((EventPipeSessionID)*session_id); MONO_EXIT_GC_UNSAFE; return TRUE; } diff --git a/src/mono/wasm/runtime/cuint64.ts b/src/mono/wasm/runtime/cuint64.ts new file mode 100644 index 0000000000000..eed6f55527043 --- /dev/null +++ b/src/mono/wasm/runtime/cuint64.ts @@ -0,0 +1,48 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +/// Define a type that can hold a 64 bit integer value from Emscripten. +/// Import this module with 'import * as cuint64 from "./cuint64";' +/// and 'import type { CUInt64 } from './cuint64'; +export type CUInt64 = readonly [number, number]; + +export function toBigInt (x: CUInt64): bigint { + return BigInt(x[0]) << BigInt(32) | BigInt(x[1]); +} + +export function fromBigInt (x: bigint): CUInt64 { + if (x < BigInt(0)) + throw new Error(`${x} is not a valid 64 bit integer`); + if (x > BigInt(0xFFFFFFFFFFFFFFFF)) + throw new Error(`${x} is not a valid 64 bit integer`); + const low = Number(x & BigInt(0xFFFFFFFF)); + const high = Number(x >> BigInt(32)); + return [low, high]; +} + +export function dangerousToNumber (x: CUInt64): number { + return x[0] | x[1] << 32; +} + +export function fromNumber (x: number): CUInt64 { + if (x < 0) + throw new Error(`${x} is not a valid 64 bit integer`); + if ((x >> 32) > 0xFFFFFFFF) + throw new Error(`${x} is not a valid 64 bit integer`); + if (Math.trunc(x) != x) + throw new Error(`${x} is not a valid 64 bit integer`); + return [x & 0xFFFFFFFF, x >> 32]; +} + +export function pack32 (lo: number, hi: number): CUInt64 { + return [lo, hi]; +} + +export function unpack32 (x: CUInt64): [number, number] { + return [x[0], x[1]]; +} + +export const zero: CUInt64 = [0, 0]; + + + diff --git a/src/mono/wasm/runtime/cwraps.ts b/src/mono/wasm/runtime/cwraps.ts index da5c14c621a5c..f9025769c6b31 100644 --- a/src/mono/wasm/runtime/cwraps.ts +++ b/src/mono/wasm/runtime/cwraps.ts @@ -162,9 +162,9 @@ export interface t_Cwraps { mono_wasm_obj_array_set(array: MonoArray, idx: number, obj: MonoObject): void; // MONO.diagnostics - mono_wasm_event_pipe_enable(outputPath: string, bufferSizeInMB: number, providers: string, rundownRequested: boolean, outSessionId: Int32Ptr): boolean; - mono_wasm_event_pipe_session_start_streaming(sessionId: number): boolean; - mono_wasm_event_pipe_session_disable(sessionId: number): boolean; + mono_wasm_event_pipe_enable(outputPath: string, bufferSizeInMB: number, providers: string, rundownRequested: boolean, outSessionId: VoidPtr): boolean; + mono_wasm_event_pipe_session_start_streaming(sessionIdPtr: VoidPtr): boolean; + mono_wasm_event_pipe_session_disable(sessionIdPtr: VoidPtr): boolean; //DOTNET /** diff --git a/src/mono/wasm/runtime/diagnostics.ts b/src/mono/wasm/runtime/diagnostics.ts index 17f27fac82e4f..7e230d72e1876 100644 --- a/src/mono/wasm/runtime/diagnostics.ts +++ b/src/mono/wasm/runtime/diagnostics.ts @@ -1,9 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + import { Module } from "./imports"; import cwraps from "./cwraps"; import type { EventPipeSessionOptions } from "./types"; -import type { Int32Ptr, VoidPtr } from "./types/emscripten"; +import type { VoidPtr } from "./types/emscripten"; import * as memory from "./memory"; -import { toBase64StringImpl } from './base64'; +import { toBase64StringImpl } from "./base64"; +import type { CUInt64 } from "./cuint64"; +import * as cuint64 from "./cuint64"; + +const sizeOfCUInt64 = 8; /// An EventPipe session object represents a single diagnostic tracing session that is collecting /// events from the runtime and managed libraries. There may be multiple active sessions at the same time. @@ -11,7 +18,7 @@ import { toBase64StringImpl } from './base64'; /// Upon completion the session saves the events to a file on the VFS. /// The data can then be retrieved as Blob or as a data URI (prefer Blob). export interface EventPipeSession { - get sessionID(): number; + get sessionID(): bigint; start(): void; stop(): void; getTraceBlob(): Blob; @@ -25,15 +32,37 @@ enum State { Done, } +function withCUIn64Ptr (x: CUInt64, f: (ptr: VoidPtr) => TRes): TRes { + const tmp = Module._malloc (sizeOfCUInt64); + try { + memory.setCU64 (tmp, x); + return f (tmp); + } finally { + Module._free (tmp); + } +} + +function start_streaming (sessionID: CUInt64): void { + withCUIn64Ptr (sessionID, (ptr) => { + cwraps.mono_wasm_event_pipe_session_start_streaming (ptr); + }); +} + +function stop_streaming (sessionID: CUInt64): void { + withCUIn64Ptr (sessionID, (ptr) => { + cwraps.mono_wasm_event_pipe_session_disable (ptr); + }); +} + /// An EventPipe session that saves the event data to a file in the VFS. class EventPipeFileSession implements EventPipeSession { private _state: State; - private _sessionID: number; // integer session ID + private _sessionID: CUInt64; // integer session ID private _tracePath: string; // VFS file path to the trace file - get sessionID(): number { return this._sessionID; } + get sessionID(): bigint { return cuint64.toBigInt (this._sessionID); } - constructor (sessionID: number, tracePath: string) { + constructor (sessionID: CUInt64, tracePath: string) { this._state = State.Initialized; this._sessionID = sessionID; this._tracePath = tracePath; @@ -42,11 +71,11 @@ class EventPipeFileSession implements EventPipeSession { start = () => { if (this._state !== State.Initialized) { - throw new Error(`EventPipe session ${this._sessionID} already started`); + throw new Error(`EventPipe session ${this.sessionID} already started`); } this._state = State.Started; - cwraps.mono_wasm_event_pipe_session_start_streaming (this._sessionID); - console.debug (`EventPipe session ${this._sessionID} started`); + start_streaming (this._sessionID); + console.debug (`EventPipe session ${this.sessionID} started`); } stop = () => { @@ -54,8 +83,8 @@ class EventPipeFileSession implements EventPipeSession { throw new Error(`cannot stop an EventPipe session in state ${this._state}, not 'Started'`); } this._state = State.Done; - cwraps.mono_wasm_event_pipe_session_disable (this._sessionID); - console.debug (`EventPipe session ${this._sessionID} stopped`); + stop_streaming (this._sessionID); + console.debug (`EventPipe session ${this.sessionID} stopped`); } getTraceBlob = () => { @@ -100,21 +129,21 @@ export const diagnostics: Diagnostics = { const defaultProviders = ""; const defaultBufferSizeInMB = 1; - const sessionIdPtr = Module._malloc(4) as unknown as Int32Ptr; + const tracePath = computeTracePath(options?.traceFilePath); + const rundown = options?.collectRundownEvents ?? defaultRundownRequested; - try { - const tracePath = computeTracePath(options?.traceFilePath); - const rundown = options?.collectRundownEvents ?? defaultRundownRequested; - if (!cwraps.mono_wasm_event_pipe_enable(tracePath, defaultBufferSizeInMB, defaultProviders, rundown, sessionIdPtr)) - return null; + const [success, sessionID] = withCUIn64Ptr (cuint64.zero, (ptr) => { + if (!cwraps.mono_wasm_event_pipe_enable(tracePath, defaultBufferSizeInMB, defaultProviders, rundown, ptr)) { + return [false, cuint64.zero]; + } else { + return [true, memory.getCU64 (ptr)]; + }}); - const sessionID = memory.getI32(sessionIdPtr); + if (!success) + return null; - const session = new EventPipeFileSession(sessionID, tracePath); - return session; - } finally { - Module._free(sessionIdPtr as unknown as VoidPtr); - } + const session = new EventPipeFileSession(sessionID, tracePath); + return session; }, }; diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index b5a3276f36bfb..446eddbf5e87c 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -240,7 +240,7 @@ declare type DotnetModuleConfigImports = { }; interface EventPipeSession { - get sessionID(): number; + get sessionID(): bigint; start(): void; stop(): void; getTraceBlob(): Blob; diff --git a/src/mono/wasm/runtime/memory.ts b/src/mono/wasm/runtime/memory.ts index b61b379e87e8c..5dcd39b88de6d 100644 --- a/src/mono/wasm/runtime/memory.ts +++ b/src/mono/wasm/runtime/memory.ts @@ -1,5 +1,6 @@ import { Module } from "./imports"; import { VoidPtr, NativePointer, ManagedPointer } from "./types/emscripten"; +import * as cuint64 from "./cuint64"; const alloca_stack: Array = []; const alloca_buffer_size = 32 * 1024; @@ -114,3 +115,15 @@ export function getF32(offset: _MemOffset): number { export function getF64(offset: _MemOffset): number { return Module.HEAPF64[offset >>> 3]; } + +export function getCU64(offset: _MemOffset): cuint64.CUInt64 { + const lo = getI32 (offset); + const hi = getI32 (offset + 4); + return cuint64.pack32(lo, hi); +} + +export function setCU64(offset: _MemOffset, value: cuint64.CUInt64): void { + const [lo, hi] = cuint64.unpack32(value); + setI32 (offset, lo); + setI32 (offset + 4, hi); +} From 63190bde11f5f2b2b3e3ac3a87a53ae513570254 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Wed, 11 May 2022 12:01:34 -0400 Subject: [PATCH 19/36] use ep_char8_t for C decls of event pipe wasm exports --- src/mono/mono/component/event_pipe-stub.c | 8 ++++---- src/mono/mono/component/event_pipe.c | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/mono/mono/component/event_pipe-stub.c b/src/mono/mono/component/event_pipe-stub.c index a3fb58a483719..bcca3e7dd7a25 100644 --- a/src/mono/mono/component/event_pipe-stub.c +++ b/src/mono/mono/component/event_pipe-stub.c @@ -13,9 +13,9 @@ G_BEGIN_DECLS EMSCRIPTEN_KEEPALIVE gboolean -mono_wasm_event_pipe_enable (const char *output_path, +mono_wasm_event_pipe_enable (const ep_char8_t *output_path, uint32_t circular_buffer_size_in_mb, - const char *providers, + const ep_char8_t *providers, /* EventPipeSessionType session_type = EP_SESSION_TYPE_FILE, */ /* EventPipieSerializationFormat format = EP_SERIALIZATION_FORMAT_NETTRACE_V4, */ /* bool */ gboolean rundown_requested, @@ -528,9 +528,9 @@ mono_component_event_pipe_init (void) #ifdef HOST_WASM EMSCRIPTEN_KEEPALIVE gboolean -mono_wasm_event_pipe_enable (const char *output_path, +mono_wasm_event_pipe_enable (const ep_char8_t *output_path, uint32_t circular_buffer_size_in_mb, - const char *providers, + const ep_char8_t *providers, /* EventPipeSessionType session_type = EP_SESSION_TYPE_FILE, */ /* EventPipieSerializationFormat format = EP_SERIALIZATION_FORMAT_NETTRACE_V4, */ /* bool */ gboolean rundown_requested, diff --git a/src/mono/mono/component/event_pipe.c b/src/mono/mono/component/event_pipe.c index 961d20a2047e9..301a05b390283 100644 --- a/src/mono/mono/component/event_pipe.c +++ b/src/mono/mono/component/event_pipe.c @@ -18,9 +18,9 @@ G_BEGIN_DECLS EMSCRIPTEN_KEEPALIVE gboolean -mono_wasm_event_pipe_enable (const char *output_path, +mono_wasm_event_pipe_enable (const ep_char8_t *output_path, uint32_t circular_buffer_size_in_mb, - const char *providers, + const ep_char8_t *providers, /* EventPipeSessionType session_type = EP_SESSION_TYPE_FILE, */ /* EventPipieSerializationFormat format = EP_SERIALIZATION_FORMAT_NETTRACE_V4, */ /* bool */ gboolean rundown_requested, @@ -360,9 +360,9 @@ mono_component_event_pipe_init (void) #ifdef HOST_WASM EMSCRIPTEN_KEEPALIVE gboolean -mono_wasm_event_pipe_enable (const char *output_path, +mono_wasm_event_pipe_enable (const ep_char8_t *output_path, uint32_t circular_buffer_size_in_mb, - const char *providers, + const ep_char8_t *providers, /* EventPipeSessionType session_type = EP_SESSION_TYPE_FILE, */ /* EventPipieSerializationFormat format = EP_SERIALIZATION_FORMAT_NETTRACE_V4, */ /* bool */ gboolean rundown_requested, From 4c50aa6b36ad1d828d8f44faa251aad178cbafe2 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Wed, 11 May 2022 12:45:32 -0400 Subject: [PATCH 20/36] fix debug output --- src/mono/wasm/runtime/diagnostics.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/wasm/runtime/diagnostics.ts b/src/mono/wasm/runtime/diagnostics.ts index 7e230d72e1876..2480b5ea7f681 100644 --- a/src/mono/wasm/runtime/diagnostics.ts +++ b/src/mono/wasm/runtime/diagnostics.ts @@ -66,7 +66,7 @@ class EventPipeFileSession implements EventPipeSession { this._state = State.Initialized; this._sessionID = sessionID; this._tracePath = tracePath; - console.debug (`EventPipe session ${sessionID} started`); + console.debug (`EventPipe session ${this.sessionID} created`); } start = () => { From a67ecd4c7f3df82994200e965ad811b088fb35dd Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Wed, 11 May 2022 12:49:14 -0400 Subject: [PATCH 21/36] fix endianness issue --- src/mono/wasm/runtime/cuint64.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/wasm/runtime/cuint64.ts b/src/mono/wasm/runtime/cuint64.ts index eed6f55527043..20ab2c88ca422 100644 --- a/src/mono/wasm/runtime/cuint64.ts +++ b/src/mono/wasm/runtime/cuint64.ts @@ -7,7 +7,7 @@ export type CUInt64 = readonly [number, number]; export function toBigInt (x: CUInt64): bigint { - return BigInt(x[0]) << BigInt(32) | BigInt(x[1]); + return BigInt(x[0]) | BigInt(x[1]) << BigInt(32); } export function fromBigInt (x: bigint): CUInt64 { From f0577f4a2def5c91ea7a8fada5a004483a895eb0 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Wed, 11 May 2022 15:28:22 -0400 Subject: [PATCH 22/36] Use stack allocation for temporaries Expose the emscripten stack allocation API --- src/mono/wasm/runtime/diagnostics.ts | 5 +++-- src/mono/wasm/runtime/dotnet.d.ts | 3 +++ src/mono/wasm/runtime/types/emscripten.ts | 6 +++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/mono/wasm/runtime/diagnostics.ts b/src/mono/wasm/runtime/diagnostics.ts index 2480b5ea7f681..3a6b52ac56eda 100644 --- a/src/mono/wasm/runtime/diagnostics.ts +++ b/src/mono/wasm/runtime/diagnostics.ts @@ -33,12 +33,13 @@ enum State { } function withCUIn64Ptr (x: CUInt64, f: (ptr: VoidPtr) => TRes): TRes { - const tmp = Module._malloc (sizeOfCUInt64); + const sp = Module.stackSave(); + const tmp = Module.stackAlloc (sizeOfCUInt64); try { memory.setCU64 (tmp, x); return f (tmp); } finally { - Module._free (tmp); + Module.stackRestore(sp); } } diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index 446eddbf5e87c..5a302f33be973 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -46,6 +46,9 @@ declare interface EmscriptenModule { FS_readFile(filename: string, opts: any): any; removeRunDependency(id: string): void; addRunDependency(id: string): void; + stackSave(): VoidPtr; + stackRestore(stack: VoidPtr): void; + stackAlloc(size: number): VoidPtr; ready: Promise; preInit?: (() => any)[]; preRun?: (() => any)[]; diff --git a/src/mono/wasm/runtime/types/emscripten.ts b/src/mono/wasm/runtime/types/emscripten.ts index dde4798263b2f..6a9ea3605c21e 100644 --- a/src/mono/wasm/runtime/types/emscripten.ts +++ b/src/mono/wasm/runtime/types/emscripten.ts @@ -52,6 +52,10 @@ export declare interface EmscriptenModule { FS_readFile(filename: string, opts: any): any; removeRunDependency(id: string): void; addRunDependency(id: string): void; + stackSave(): VoidPtr; + stackRestore(stack: VoidPtr): void; + stackAlloc(size: number): VoidPtr; + ready: Promise; preInit?: (() => any)[]; @@ -62,4 +66,4 @@ export declare interface EmscriptenModule { instantiateWasm: (imports: any, successCallback: Function) => any; } -export declare type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array; \ No newline at end of file +export declare type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array; From 99f2ddfe9929a0eeb54a0399cf4e81d1177a4efc Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Thu, 12 May 2022 15:53:18 -0400 Subject: [PATCH 23/36] Use 32-bit EventPipe session ID on WASM 64 bit integers are awkward to work with in JavaScript. The EventPipe session ID is derived from a pointer address, so even though it is nominally a 64-bit value, in practice the top bits are zero. Use a 32-bit int to represent the session ID on the javascript side and convert to 64-bit in C when calling down to the EventPipe APIs --- src/mono/mono/component/CMakeLists.txt | 1 + src/mono/mono/component/event_pipe-stub.c | 36 ++-------- src/mono/mono/component/event_pipe-wasm.h | 52 ++++++++++++++ src/mono/mono/component/event_pipe.c | 53 ++++++-------- src/mono/wasm/runtime/cwraps.ts | 4 +- src/mono/wasm/runtime/diagnostics.ts | 85 ++++++++++------------- src/mono/wasm/runtime/dotnet.d.ts | 5 +- src/mono/wasm/runtime/types.ts | 2 - 8 files changed, 120 insertions(+), 118 deletions(-) create mode 100644 src/mono/mono/component/event_pipe-wasm.h diff --git a/src/mono/mono/component/CMakeLists.txt b/src/mono/mono/component/CMakeLists.txt index c00f11b841586..6e34cbf41b502 100644 --- a/src/mono/mono/component/CMakeLists.txt +++ b/src/mono/mono/component/CMakeLists.txt @@ -65,6 +65,7 @@ set(${MONO_DIAGNOSTICS_TRACING_COMPONENT_NAME}-sources ${diagnostic_server_sources} ${MONO_COMPONENT_PATH}/event_pipe.c ${MONO_COMPONENT_PATH}/event_pipe.h + ${MONO_COMPONENT_PATH}/event_pipe-wasm.h ${MONO_COMPONENT_PATH}/diagnostics_server.c ${MONO_COMPONENT_PATH}/diagnostics_server.h ) diff --git a/src/mono/mono/component/event_pipe-stub.c b/src/mono/mono/component/event_pipe-stub.c index bcca3e7dd7a25..5069532985e76 100644 --- a/src/mono/mono/component/event_pipe-stub.c +++ b/src/mono/mono/component/event_pipe-stub.c @@ -4,37 +4,9 @@ #include #include "mono/component/event_pipe.h" +#include "mono/component/event_pipe-wasm.h" #include "mono/metadata/components.h" - -#ifdef HOST_WASM -#include - -G_BEGIN_DECLS - -EMSCRIPTEN_KEEPALIVE gboolean -mono_wasm_event_pipe_enable (const ep_char8_t *output_path, - uint32_t circular_buffer_size_in_mb, - const ep_char8_t *providers, - /* EventPipeSessionType session_type = EP_SESSION_TYPE_FILE, */ - /* EventPipieSerializationFormat format = EP_SERIALIZATION_FORMAT_NETTRACE_V4, */ - /* bool */ gboolean rundown_requested, - /* IpcStream stream = NULL, */ - /* EventPipeSessionSycnhronousCallback sync_callback = NULL, */ - /* void *callback_additional_data, */ - int64_t *out_session_id); - - -EMSCRIPTEN_KEEPALIVE gboolean -mono_wasm_event_pipe_session_start_streaming (const int64_t *session_id); - -EMSCRIPTEN_KEEPALIVE gboolean -mono_wasm_event_pipe_session_disable (const int64_t *session_id); - -G_END_DECLS - -#endif /* HOST_WASM */ - static EventPipeSessionID _dummy_session_id; static uint8_t _max_event_pipe_type_size [256]; @@ -537,7 +509,7 @@ mono_wasm_event_pipe_enable (const ep_char8_t *output_path, /* IpcStream stream = NULL, */ /* EventPipeSessionSycnhronousCallback sync_callback = NULL, */ /* void *callback_additional_data, */ - int64_t *out_session_id) + MonoWasmEventPipeSessionID *out_session_id) { if (out_session_id) *out_session_id = 0; @@ -546,13 +518,13 @@ mono_wasm_event_pipe_enable (const ep_char8_t *output_path, EMSCRIPTEN_KEEPALIVE gboolean -mono_wasm_event_pipe_session_start_streaming (const int64_t *session_id) +mono_wasm_event_pipe_session_start_streaming (MonoWasmEventPipeSessionID session_id) { g_assert_not_reached (); } EMSCRIPTEN_KEEPALIVE gboolean -mono_wasm_event_pipe_session_disable (const int64_t *session_id) +mono_wasm_event_pipe_session_disable (MonoWasmEventPipeSessionID session_id) { g_assert_not_reached (); } diff --git a/src/mono/mono/component/event_pipe-wasm.h b/src/mono/mono/component/event_pipe-wasm.h new file mode 100644 index 0000000000000..90e4a4d8b9948 --- /dev/null +++ b/src/mono/mono/component/event_pipe-wasm.h @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// + +#ifndef _MONO_COMPONENT_EVENT_PIPE_WASM_H +#define _MONO_COMPONENT_EVENT_PIPE_WASM_H + +#include +#include +#include +#include + +#ifdef HOST_WASM + +#include + +G_BEGIN_DECLS + +#if SIZEOF_VOID_P == 4 +/* EventPipeSessionID is 64 bits, which is awkward to work with in JS. + Fortunately the actual session IDs are derived from pointers which + are 32-bit on wasm32, so the top bits are zero. */ +typedef uint32_t MonoWasmEventPipeSessionID; +#else +#error "EventPipeSessionID is 64-bits, update the JS side to work with it" +#endif + +EMSCRIPTEN_KEEPALIVE gboolean +mono_wasm_event_pipe_enable (const ep_char8_t *output_path, + uint32_t circular_buffer_size_in_mb, + const ep_char8_t *providers, + /* EventPipeSessionType session_type = EP_SESSION_TYPE_FILE, */ + /* EventPipieSerializationFormat format = EP_SERIALIZATION_FORMAT_NETTRACE_V4, */ + /* bool */ gboolean rundown_requested, + /* IpcStream stream = NULL, */ + /* EventPipeSessionSycnhronousCallback sync_callback = NULL, */ + /* void *callback_additional_data, */ + MonoWasmEventPipeSessionID *out_session_id); + +EMSCRIPTEN_KEEPALIVE gboolean +mono_wasm_event_pipe_session_start_streaming (MonoWasmEventPipeSessionID session_id); + +EMSCRIPTEN_KEEPALIVE gboolean +mono_wasm_event_pipe_session_disable (MonoWasmEventPipeSessionID session_id); + +G_END_DECLS + +#endif /* HOST_WASM */ + + +#endif /* _MONO_COMPONENT_EVENT_PIPE_WASM_H */ + diff --git a/src/mono/mono/component/event_pipe.c b/src/mono/mono/component/event_pipe.c index 301a05b390283..f933b416d6840 100644 --- a/src/mono/mono/component/event_pipe.c +++ b/src/mono/mono/component/event_pipe.c @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -12,32 +13,6 @@ #include #include -#ifdef HOST_WASM -#include - -G_BEGIN_DECLS - -EMSCRIPTEN_KEEPALIVE gboolean -mono_wasm_event_pipe_enable (const ep_char8_t *output_path, - uint32_t circular_buffer_size_in_mb, - const ep_char8_t *providers, - /* EventPipeSessionType session_type = EP_SESSION_TYPE_FILE, */ - /* EventPipieSerializationFormat format = EP_SERIALIZATION_FORMAT_NETTRACE_V4, */ - /* bool */ gboolean rundown_requested, - /* IpcStream stream = NULL, */ - /* EventPipeSessionSycnhronousCallback sync_callback = NULL, */ - /* void *callback_additional_data, */ - int64_t *out_session_id); - -EMSCRIPTEN_KEEPALIVE gboolean -mono_wasm_event_pipe_session_start_streaming (const int64_t *session_id); - -EMSCRIPTEN_KEEPALIVE gboolean -mono_wasm_event_pipe_session_disable (const int64_t *session_id); - -G_END_DECLS - -#endif /* HOST_WASM */ extern void ep_rt_mono_component_init (void); static bool _event_pipe_component_inited = false; @@ -359,6 +334,20 @@ mono_component_event_pipe_init (void) #ifdef HOST_WASM + +static MonoWasmEventPipeSessionID +ep_to_wasm_session_id (EventPipeSessionID session_id) +{ + g_assert (0 == (uint64_t)session_id >> 32); + return (uint32_t)session_id; +} + +static EventPipeSessionID +wasm_to_ep_session_id (MonoWasmEventPipeSessionID session_id) +{ + return session_id; +} + EMSCRIPTEN_KEEPALIVE gboolean mono_wasm_event_pipe_enable (const ep_char8_t *output_path, uint32_t circular_buffer_size_in_mb, @@ -369,7 +358,7 @@ mono_wasm_event_pipe_enable (const ep_char8_t *output_path, /* IpcStream stream = NULL, */ /* EventPipeSessionSycnhronousCallback sync_callback = NULL, */ /* void *callback_additional_data, */ - int64_t *out_session_id) + MonoWasmEventPipeSessionID *out_session_id) { MONO_ENTER_GC_UNSAFE; EventPipeSerializationFormat format = EP_SERIALIZATION_FORMAT_NETTRACE_V4; @@ -387,25 +376,25 @@ mono_wasm_event_pipe_enable (const ep_char8_t *output_path, /* callback_data*/ NULL); if (out_session_id) - *out_session_id = session; + *out_session_id = ep_to_wasm_session_id (session); MONO_EXIT_GC_UNSAFE; return TRUE; } EMSCRIPTEN_KEEPALIVE gboolean -mono_wasm_event_pipe_session_start_streaming (const int64_t *session_id) +mono_wasm_event_pipe_session_start_streaming (MonoWasmEventPipeSessionID session_id) { MONO_ENTER_GC_UNSAFE; - ep_start_streaming ((EventPipeSessionID)*session_id); + ep_start_streaming (wasm_to_ep_session_id (session_id)); MONO_EXIT_GC_UNSAFE; return TRUE; } EMSCRIPTEN_KEEPALIVE gboolean -mono_wasm_event_pipe_session_disable (const int64_t *session_id) +mono_wasm_event_pipe_session_disable (MonoWasmEventPipeSessionID session_id) { MONO_ENTER_GC_UNSAFE; - ep_disable ((EventPipeSessionID)*session_id); + ep_disable (wasm_to_ep_session_id (session_id)); MONO_EXIT_GC_UNSAFE; return TRUE; } diff --git a/src/mono/wasm/runtime/cwraps.ts b/src/mono/wasm/runtime/cwraps.ts index f9025769c6b31..52663f7f39aa6 100644 --- a/src/mono/wasm/runtime/cwraps.ts +++ b/src/mono/wasm/runtime/cwraps.ts @@ -163,8 +163,8 @@ export interface t_Cwraps { // MONO.diagnostics mono_wasm_event_pipe_enable(outputPath: string, bufferSizeInMB: number, providers: string, rundownRequested: boolean, outSessionId: VoidPtr): boolean; - mono_wasm_event_pipe_session_start_streaming(sessionIdPtr: VoidPtr): boolean; - mono_wasm_event_pipe_session_disable(sessionIdPtr: VoidPtr): boolean; + mono_wasm_event_pipe_session_start_streaming(sessionId: number): boolean; + mono_wasm_event_pipe_session_disable(sessionId: number): boolean; //DOTNET /** diff --git a/src/mono/wasm/runtime/diagnostics.ts b/src/mono/wasm/runtime/diagnostics.ts index 3a6b52ac56eda..d8412be7076cd 100644 --- a/src/mono/wasm/runtime/diagnostics.ts +++ b/src/mono/wasm/runtime/diagnostics.ts @@ -6,11 +6,11 @@ import cwraps from "./cwraps"; import type { EventPipeSessionOptions } from "./types"; import type { VoidPtr } from "./types/emscripten"; import * as memory from "./memory"; -import { toBase64StringImpl } from "./base64"; -import type { CUInt64 } from "./cuint64"; -import * as cuint64 from "./cuint64"; -const sizeOfCUInt64 = 8; +const sizeOfInt32 = 4; + +export type EventPipeSessionID = bigint; +type EventPipeSessionIDImpl = number; /// An EventPipe session object represents a single diagnostic tracing session that is collecting /// events from the runtime and managed libraries. There may be multiple active sessions at the same time. @@ -18,11 +18,11 @@ const sizeOfCUInt64 = 8; /// Upon completion the session saves the events to a file on the VFS. /// The data can then be retrieved as Blob or as a data URI (prefer Blob). export interface EventPipeSession { - get sessionID(): bigint; + // session ID for debugging logging only + get sessionID(): EventPipeSessionID; start(): void; stop(): void; getTraceBlob(): Blob; - getTraceDataURI(): string; } // internal session state of the JS instance @@ -32,38 +32,47 @@ enum State { Done, } -function withCUIn64Ptr (x: CUInt64, f: (ptr: VoidPtr) => TRes): TRes { +function withStackAlloc (bytesWanted: number, f : (ptr: VoidPtr) => TRes): TRes { const sp = Module.stackSave(); - const tmp = Module.stackAlloc (sizeOfCUInt64); + const ptr = Module.stackAlloc (bytesWanted); try { - memory.setCU64 (tmp, x); - return f (tmp); + return f (ptr); } finally { Module.stackRestore(sp); } } -function start_streaming (sessionID: CUInt64): void { - withCUIn64Ptr (sessionID, (ptr) => { - cwraps.mono_wasm_event_pipe_session_start_streaming (ptr); - }); +function start_streaming (sessionID: EventPipeSessionIDImpl): void { + cwraps.mono_wasm_event_pipe_session_start_streaming (sessionID); +} + +function stop_streaming (sessionID: EventPipeSessionIDImpl): void { + cwraps.mono_wasm_event_pipe_session_disable (sessionID); +} + +function makeTimestamp(): string +{ + // ISO date string, but with : and . replaced by - + const t = new Date(); + const s = t.toISOString(); + return s.replace(/[:.]/g, "-"); } -function stop_streaming (sessionID: CUInt64): void { - withCUIn64Ptr (sessionID, (ptr) => { - cwraps.mono_wasm_event_pipe_session_disable (ptr); - }); +// The name of the session trace file on the VFS. This is an implementation detail that is not exposed to the user. +function computeTracePath (): string { + const timestamp = makeTimestamp(); + return `/trace-${timestamp}.nettrace`; } /// An EventPipe session that saves the event data to a file in the VFS. class EventPipeFileSession implements EventPipeSession { private _state: State; - private _sessionID: CUInt64; // integer session ID + private _sessionID: EventPipeSessionIDImpl; private _tracePath: string; // VFS file path to the trace file - get sessionID(): bigint { return cuint64.toBigInt (this._sessionID); } + get sessionID(): bigint { return BigInt (this._sessionID); } - constructor (sessionID: CUInt64, tracePath: string) { + constructor (sessionID: EventPipeSessionIDImpl, tracePath: string) { this._state = State.Initialized; this._sessionID = sessionID; this._tracePath = tracePath; @@ -95,49 +104,31 @@ class EventPipeFileSession implements EventPipeSession { const data = Module.FS_readFile(this._tracePath, { encoding: "binary" }) as Uint8Array; return new Blob([data], { type: "application/octet-stream" }); } - - getTraceDataURI = () => { - if (this._state !== State.Done) { - throw new Error (`session is in state ${this._state}, not 'Done'`); - } - const data = Module.FS_readFile(this._tracePath, { encoding: "binary" }) as Uint8Array; - return `data:application/octet-stream;base64,${toBase64StringImpl(data)}`; - } } export interface Diagnostics { createEventPipeSession (options?: EventPipeSessionOptions): EventPipeSession | null; } -export const defaultOutputPath = "/trace.nettrace"; - -function computeTracePath (tracePath? : string | (() => string | null | undefined)): string { - if (tracePath === undefined) { - return defaultOutputPath; - } - if (typeof tracePath === "function") - return tracePath() ?? defaultOutputPath; - return tracePath; -} - /// APIs for working with .NET diagnostics from JavaScript. export const diagnostics: Diagnostics = { /// Creates a new EventPipe session that will collect trace events from the runtime and managed libraries. - /// Use the options to control the output file and the level of detail. - /// Note that if you use multiple sessions at the same time, you should specify a unique 'traceFilePath' for each session. + /// Use the options to control the kinds of events to be collected. + /// Multiple sessions may be created and started at the same time. createEventPipeSession(options?: EventPipeSessionOptions): EventPipeSession | null { const defaultRundownRequested = true; const defaultProviders = ""; const defaultBufferSizeInMB = 1; - const tracePath = computeTracePath(options?.traceFilePath); + const tracePath = computeTracePath(); const rundown = options?.collectRundownEvents ?? defaultRundownRequested; - const [success, sessionID] = withCUIn64Ptr (cuint64.zero, (ptr) => { - if (!cwraps.mono_wasm_event_pipe_enable(tracePath, defaultBufferSizeInMB, defaultProviders, rundown, ptr)) { - return [false, cuint64.zero]; + const [success, sessionID] = withStackAlloc (sizeOfInt32, (sessionIdOutPtr) => { + memory.setI32(sessionIdOutPtr, 0); + if (!cwraps.mono_wasm_event_pipe_enable(tracePath, defaultBufferSizeInMB, defaultProviders, rundown, sessionIdOutPtr)) { + return [false, 0]; } else { - return [true, memory.getCU64 (ptr)]; + return [true, memory.getI32 (sessionIdOutPtr)]; }}); if (!success) diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index 5a302f33be973..edb5daca51eb3 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -208,7 +208,6 @@ declare type CoverageProfilerOptions = { send_to?: string; }; interface EventPipeSessionOptions { - traceFilePath?: string | (() => string | null | undefined); collectRundownEvents?: boolean; } declare type DotnetModuleConfig = { @@ -242,12 +241,12 @@ declare type DotnetModuleConfigImports = { url?: any; }; +declare type EventPipeSessionID = bigint; interface EventPipeSession { - get sessionID(): bigint; + get sessionID(): EventPipeSessionID; start(): void; stop(): void; getTraceBlob(): Blob; - getTraceDataURI(): string; } interface Diagnostics { createEventPipeSession(options?: EventPipeSessionOptions): EventPipeSession | null; diff --git a/src/mono/wasm/runtime/types.ts b/src/mono/wasm/runtime/types.ts index 4c342bcad271b..0fe8ff8215ae5 100644 --- a/src/mono/wasm/runtime/types.ts +++ b/src/mono/wasm/runtime/types.ts @@ -175,8 +175,6 @@ export type CoverageProfilerOptions = { /// Options to configure the event pipe session export interface EventPipeSessionOptions { - /// Location on the VFS where the session trace will be saved (default: "/trace.nettrace") - traceFilePath?: string | (() => string | null | undefined); /// 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; From cdf8909400551d2bbd7558ded3b0a835a8ebab81 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Thu, 12 May 2022 15:55:02 -0400 Subject: [PATCH 24/36] Make the sample do more work in managed give the sample profiler some non-empty samples to collect --- .../sample/wasm/browser-eventpipe/Program.cs | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/mono/sample/wasm/browser-eventpipe/Program.cs b/src/mono/sample/wasm/browser-eventpipe/Program.cs index c03293604e884..e5b41a8194f96 100644 --- a/src/mono/sample/wasm/browser-eventpipe/Program.cs +++ b/src/mono/sample/wasm/browser-eventpipe/Program.cs @@ -28,23 +28,25 @@ public static CancellationToken GetCancellationToken() } [MethodImpl(MethodImplOptions.NoInlining)] + private static long recursiveFib (int n) + { + if (n < 1) + return 0; + if (n == 1) + return 1; + return recursiveFib (n - 1) + recursiveFib (n - 2); + } + public static async Task StartAsyncWork() { CancellationToken ct = GetCancellationToken(); - int a; - int b; - const int N = 30; - const int expected = 832040; + long b; + const int N = 35; + const long expected = 9227465; while (true) { - a = 0; b = 1; - for (int i = 1; i < N; i++) - { - int tmp = a + b; - a = b; - b = tmp; - await Task.Delay(1).ConfigureAwait(false); - } + await Task.Delay(1).ConfigureAwait(false); + b = recursiveFib (N); if (ct.IsCancellationRequested) break; iterations++; From 9c70c5c998ce02f0ef551aee7b4446c0d1151a0f Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Mon, 16 May 2022 10:08:04 -0400 Subject: [PATCH 25/36] Move withStackAlloc to memory.ts --- src/mono/wasm/runtime/diagnostics.ts | 13 +------------ src/mono/wasm/runtime/memory.ts | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/mono/wasm/runtime/diagnostics.ts b/src/mono/wasm/runtime/diagnostics.ts index d8412be7076cd..5d30d0f2405b5 100644 --- a/src/mono/wasm/runtime/diagnostics.ts +++ b/src/mono/wasm/runtime/diagnostics.ts @@ -4,7 +4,6 @@ import { Module } from "./imports"; import cwraps from "./cwraps"; import type { EventPipeSessionOptions } from "./types"; -import type { VoidPtr } from "./types/emscripten"; import * as memory from "./memory"; const sizeOfInt32 = 4; @@ -32,16 +31,6 @@ enum State { Done, } -function withStackAlloc (bytesWanted: number, f : (ptr: VoidPtr) => TRes): TRes { - const sp = Module.stackSave(); - const ptr = Module.stackAlloc (bytesWanted); - try { - return f (ptr); - } finally { - Module.stackRestore(sp); - } -} - function start_streaming (sessionID: EventPipeSessionIDImpl): void { cwraps.mono_wasm_event_pipe_session_start_streaming (sessionID); } @@ -123,7 +112,7 @@ export const diagnostics: Diagnostics = { const tracePath = computeTracePath(); const rundown = options?.collectRundownEvents ?? defaultRundownRequested; - const [success, sessionID] = withStackAlloc (sizeOfInt32, (sessionIdOutPtr) => { + const [success, sessionID] = memory.withStackAlloc (sizeOfInt32, (sessionIdOutPtr) => { memory.setI32(sessionIdOutPtr, 0); if (!cwraps.mono_wasm_event_pipe_enable(tracePath, defaultBufferSizeInMB, defaultProviders, rundown, sessionIdOutPtr)) { return [false, 0]; diff --git a/src/mono/wasm/runtime/memory.ts b/src/mono/wasm/runtime/memory.ts index 5dcd39b88de6d..fecbc04d94527 100644 --- a/src/mono/wasm/runtime/memory.ts +++ b/src/mono/wasm/runtime/memory.ts @@ -127,3 +127,17 @@ export function setCU64(offset: _MemOffset, value: cuint64.CUInt64): void { setI32 (offset, lo); setI32 (offset + 4, hi); } + +/// Allocates a new buffer of the given size on the Emscripten stack and passes a pointer to it to the callback. +/// Returns the result of the callback. As usual with stack allocations, the buffer is freed when the callback returns. +/// Do not attempt to use the stack pointer after the callback is finished. +export function withStackAlloc (bytesWanted: number, f : (ptr: VoidPtr) => TResult): TResult { + const sp = Module.stackSave(); + const ptr = Module.stackAlloc (bytesWanted); + try { + return f (ptr); + } finally { + Module.stackRestore(sp); + } +} + From 5af5b80e50766b7fa90f48759de829533bc27800 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Mon, 16 May 2022 10:17:31 -0400 Subject: [PATCH 26/36] fix option description --- src/mono/cmake/options.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/cmake/options.cmake b/src/mono/cmake/options.cmake index 7a0fa12521dc3..f00430fc9500d 100644 --- a/src/mono/cmake/options.cmake +++ b/src/mono/cmake/options.cmake @@ -56,7 +56,7 @@ option (ENABLE_OVERRIDABLE_ALLOCATORS "Enable overridable allocator support") option (ENABLE_SIGALTSTACK "Enable support for using sigaltstack for SIGSEGV and stack overflow handling, this doesn't work on some platforms") option (USE_MALLOC_FOR_MEMPOOLS "Use malloc for each single mempool allocation, so tools like Valgrind can run better") option (STATIC_COMPONENTS "Compile mono runtime components as static (not dynamic) libraries") -option (DISABLE_WASM_USER_THREADS "Disable creation of managed threads, only allow runtime internal threads") +option (DISABLE_WASM_USER_THREADS "Disable creation of user managed threads on WebAssembly, only allow runtime internal managed and native threads") set (MONO_GC "sgen" CACHE STRING "Garbage collector implementation (sgen or boehm). Default: sgen") set (GC_SUSPEND "default" CACHE STRING "GC suspend method (default, preemptive, coop, hybrid)") From 8fcb3191ee8962c36b4760247222c1eec944d9f3 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Mon, 16 May 2022 11:02:40 -0400 Subject: [PATCH 27/36] simplify VFS .nettrace file naming Just use consecutive integers to uniquify the session traces. Dont' need a fancy timestamp in the VFS (which would also not be unique if you create sessions below the timestamp resolution) --- src/mono/wasm/runtime/diagnostics.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/mono/wasm/runtime/diagnostics.ts b/src/mono/wasm/runtime/diagnostics.ts index 5d30d0f2405b5..a6a536554886a 100644 --- a/src/mono/wasm/runtime/diagnostics.ts +++ b/src/mono/wasm/runtime/diagnostics.ts @@ -47,12 +47,6 @@ function makeTimestamp(): string return s.replace(/[:.]/g, "-"); } -// The name of the session trace file on the VFS. This is an implementation detail that is not exposed to the user. -function computeTracePath (): string { - const timestamp = makeTimestamp(); - return `/trace-${timestamp}.nettrace`; -} - /// An EventPipe session that saves the event data to a file in the VFS. class EventPipeFileSession implements EventPipeSession { private _state: State; @@ -99,6 +93,9 @@ export interface Diagnostics { createEventPipeSession (options?: EventPipeSessionOptions): EventPipeSession | null; } +// a conter for the number of sessions created +let totalSessions = 0; + /// APIs for working with .NET diagnostics from JavaScript. export const diagnostics: Diagnostics = { /// Creates a new EventPipe session that will collect trace events from the runtime and managed libraries. @@ -109,7 +106,9 @@ export const diagnostics: Diagnostics = { const defaultProviders = ""; const defaultBufferSizeInMB = 1; - const tracePath = computeTracePath(); + // The session trace is saved to a file in the VFS. The file name doesn't matter, but we'd like it to be + // distinct from other traces. We include the current time in the file name. + const tracePath =`/trace-${makeTimestamp()}-${totalSessions++}.nettrace`; const rundown = options?.collectRundownEvents ?? defaultRundownRequested; const [success, sessionID] = memory.withStackAlloc (sizeOfInt32, (sessionIdOutPtr) => { From a3d681c75b7fce4e987f4ccb10becdeca9a1257d Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Mon, 16 May 2022 11:31:34 -0400 Subject: [PATCH 28/36] Add overloads to memory.withStackAlloc to avoid creating closures Pass up to 7 additional arguments to the callback function --- src/mono/wasm/runtime/diagnostics.ts | 29 +++++++++++----------------- src/mono/wasm/runtime/memory.ts | 12 ++++++++++-- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/mono/wasm/runtime/diagnostics.ts b/src/mono/wasm/runtime/diagnostics.ts index a6a536554886a..5acbd1d21c096 100644 --- a/src/mono/wasm/runtime/diagnostics.ts +++ b/src/mono/wasm/runtime/diagnostics.ts @@ -15,7 +15,7 @@ type EventPipeSessionIDImpl = number; /// events from the runtime and managed libraries. There may be multiple active sessions at the same time. /// Each session subscribes to a number of providers and will collect events from the time that start() is called, until stop() is called. /// Upon completion the session saves the events to a file on the VFS. -/// The data can then be retrieved as Blob or as a data URI (prefer Blob). +/// The data can then be retrieved as Blob. export interface EventPipeSession { // session ID for debugging logging only get sessionID(): EventPipeSessionID; @@ -39,14 +39,6 @@ function stop_streaming (sessionID: EventPipeSessionIDImpl): void { cwraps.mono_wasm_event_pipe_session_disable (sessionID); } -function makeTimestamp(): string -{ - // ISO date string, but with : and . replaced by - - const t = new Date(); - const s = t.toISOString(); - return s.replace(/[:.]/g, "-"); -} - /// An EventPipe session that saves the event data to a file in the VFS. class EventPipeFileSession implements EventPipeSession { private _state: State; @@ -102,22 +94,23 @@ export const diagnostics: Diagnostics = { /// Use the options to control the kinds of events to be collected. /// Multiple sessions may be created and started at the same time. createEventPipeSession(options?: EventPipeSessionOptions): EventPipeSession | null { - const defaultRundownRequested = true; - const defaultProviders = ""; - const defaultBufferSizeInMB = 1; + // The session trace is saved to a file in the VFS. The file name doesn't matter, + // but we'd like it to be distinct from other traces. + const tracePath =`/trace-${totalSessions++}.nettrace`; + + const [success, sessionID] = memory.withStackAlloc (sizeOfInt32, (sessionIdOutPtr, memory, cwraps, options, tracePath) => { + const defaultRundownRequested = true; + const defaultProviders = ""; + const defaultBufferSizeInMB = 1; - // The session trace is saved to a file in the VFS. The file name doesn't matter, but we'd like it to be - // distinct from other traces. We include the current time in the file name. - const tracePath =`/trace-${makeTimestamp()}-${totalSessions++}.nettrace`; - const rundown = options?.collectRundownEvents ?? defaultRundownRequested; + const rundown = options?.collectRundownEvents ?? defaultRundownRequested; - const [success, sessionID] = memory.withStackAlloc (sizeOfInt32, (sessionIdOutPtr) => { memory.setI32(sessionIdOutPtr, 0); if (!cwraps.mono_wasm_event_pipe_enable(tracePath, defaultBufferSizeInMB, defaultProviders, rundown, sessionIdOutPtr)) { return [false, 0]; } else { return [true, memory.getI32 (sessionIdOutPtr)]; - }}); + }}, memory, cwraps, options, tracePath); if (!success) return null; diff --git a/src/mono/wasm/runtime/memory.ts b/src/mono/wasm/runtime/memory.ts index fecbc04d94527..58d8a8b55faf4 100644 --- a/src/mono/wasm/runtime/memory.ts +++ b/src/mono/wasm/runtime/memory.ts @@ -131,11 +131,19 @@ export function setCU64(offset: _MemOffset, value: cuint64.CUInt64): void { /// Allocates a new buffer of the given size on the Emscripten stack and passes a pointer to it to the callback. /// Returns the result of the callback. As usual with stack allocations, the buffer is freed when the callback returns. /// Do not attempt to use the stack pointer after the callback is finished. -export function withStackAlloc (bytesWanted: number, f : (ptr: VoidPtr) => TResult): TResult { +export function withStackAlloc (bytesWanted: number, f: (ptr: VoidPtr) => TResult): TResult; +export function withStackAlloc (bytesWanted: number, f: (ptr: VoidPtr, ud1: T1) => TResult, ud1: T1): TResult; +export function withStackAlloc (bytesWanted: number, f: (ptr: VoidPtr, ud1: T1, ud2: T2) => TResult, ud1: T1, ud2: T2): TResult; +export function withStackAlloc (bytesWanted: number, f: (ptr: VoidPtr, ud1: T1, ud2: T2, ud3: T3) => TResult, ud1: T1, ud2: T2, ud3: T3): TResult; +export function withStackAlloc (bytesWanted: number, f: (ptr: VoidPtr, ud1: T1, ud2: T2, ud3: T3, ud4: T4) => TResult, ud1: T1, ud2: T2, ud3: T3, ud4: T4): TResult; +export function withStackAlloc (bytesWanted: number, f: (ptr: VoidPtr, ud1: T1, ud2: T2, ud3: T3, ud4: T4, ud5: T5) => TResult, ud1: T1, ud2: T2, ud3: T3, ud4: T4, ud5: T5): TResult; +export function withStackAlloc (bytesWanted: number, f: (ptr: VoidPtr, ud1: T1, ud2: T2, ud3: T3, ud4: T4, ud5: T5, ud6: T6) => TResult, ud1: T1, ud2: T2, ud3: T3, ud4: T4, ud5: T5, ud6: T6): TResult; +export function withStackAlloc (bytesWanted: number, f: (ptr: VoidPtr, ud1: T1, ud2: T2, ud3: T3, ud4: T4, ud5: T5, ud6: T6, ud7:T7) => TResult, ud1: T1, ud2: T2, ud3: T3, ud4: T4, ud5: T5, ud6: T6, ud7: T7): TResult; +export function withStackAlloc (bytesWanted: number, f : (ptr: VoidPtr, ud1?: T1, ud2?: T2, ud3?: T3, ud4?: T4, ud5?: T5, ud6?: T6, ud7?: T7) => TResult, ud1?: T1, ud2?:T2, ud3?: T3, ud4?: T4, ud5?: T5, ud6?: T6, ud7?: T7): TResult { const sp = Module.stackSave(); const ptr = Module.stackAlloc (bytesWanted); try { - return f (ptr); + return f (ptr, ud1, ud2, ud3, ud4, ud5, ud6, ud7); } finally { Module.stackRestore(sp); } From 173081c997fe94e51dc381137fd60843691aba05 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Mon, 16 May 2022 11:36:18 -0400 Subject: [PATCH 29/36] sample: explain why there's a 10s pause --- src/mono/sample/wasm/browser-eventpipe/main.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mono/sample/wasm/browser-eventpipe/main.js b/src/mono/sample/wasm/browser-eventpipe/main.js index 09a24924a0e87..ef8029db5094f 100644 --- a/src/mono/sample/wasm/browser-eventpipe/main.js +++ b/src/mono/sample/wasm/browser-eventpipe/main.js @@ -98,4 +98,5 @@ async function main() { wasm_exit(exit_code); } +console.log("Waiting 10s for curious human before starting the program"); setTimeout(main, 10000); From f07daea3ea5d0e92fd29a87fd3d7393d18b2f180 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Mon, 16 May 2022 12:26:20 -0400 Subject: [PATCH 30/36] move createEventPipeSession callback to a function ensures the closure is created once --- src/mono/wasm/runtime/diagnostics.ts | 39 ++++++++++++++++------------ 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/src/mono/wasm/runtime/diagnostics.ts b/src/mono/wasm/runtime/diagnostics.ts index 5acbd1d21c096..c603949627678 100644 --- a/src/mono/wasm/runtime/diagnostics.ts +++ b/src/mono/wasm/runtime/diagnostics.ts @@ -4,6 +4,7 @@ import { Module } from "./imports"; import cwraps from "./cwraps"; import type { EventPipeSessionOptions } from "./types"; +import type { VoidPtr } from "./types/emscripten"; import * as memory from "./memory"; const sizeOfInt32 = 4; @@ -81,13 +82,28 @@ class EventPipeFileSession implements EventPipeSession { } } +// 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 defaultBufferSizeInMB = 1; + + const rundown = options?.collectRundownEvents ?? defaultRundownRequested; + + memory.setI32(sessionIdOutPtr, 0); + if (!cwraps.mono_wasm_event_pipe_enable(tracePath, defaultBufferSizeInMB, defaultProviders, rundown, sessionIdOutPtr)) { + return false; + } else { + return memory.getI32 (sessionIdOutPtr); + } +} + export interface Diagnostics { createEventPipeSession (options?: EventPipeSessionOptions): EventPipeSession | null; } -// a conter for the number of sessions created -let totalSessions = 0; - /// APIs for working with .NET diagnostics from JavaScript. export const diagnostics: Diagnostics = { /// Creates a new EventPipe session that will collect trace events from the runtime and managed libraries. @@ -98,22 +114,11 @@ export const diagnostics: Diagnostics = { // but we'd like it to be distinct from other traces. const tracePath =`/trace-${totalSessions++}.nettrace`; - const [success, sessionID] = memory.withStackAlloc (sizeOfInt32, (sessionIdOutPtr, memory, cwraps, options, tracePath) => { - const defaultRundownRequested = true; - const defaultProviders = ""; - const defaultBufferSizeInMB = 1; - - const rundown = options?.collectRundownEvents ?? defaultRundownRequested; - - memory.setI32(sessionIdOutPtr, 0); - if (!cwraps.mono_wasm_event_pipe_enable(tracePath, defaultBufferSizeInMB, defaultProviders, rundown, sessionIdOutPtr)) { - return [false, 0]; - } else { - return [true, memory.getI32 (sessionIdOutPtr)]; - }}, memory, cwraps, options, tracePath); + const success = memory.withStackAlloc (sizeOfInt32, createSessionWithPtrCB, options, tracePath); - if (!success) + if (success === false) return null; + const sessionID = success; const session = new EventPipeFileSession(sessionID, tracePath); return session; From 19f9659c07c18d9400740e355cb0a86c07b050f2 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Wed, 18 May 2022 08:49:37 -0400 Subject: [PATCH 31/36] fix whitespace --- src/mono/wasm/runtime/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/wasm/runtime/types.ts b/src/mono/wasm/runtime/types.ts index 592c68df45d43..d26277e38fb60 100644 --- a/src/mono/wasm/runtime/types.ts +++ b/src/mono/wasm/runtime/types.ts @@ -278,4 +278,4 @@ export const enum MarshalError { // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator) export function is_nullish (value: T | null | undefined): value is null | undefined { return (value === undefined) || (value === null); -} \ No newline at end of file +} From c16d22c193462a8814e225e489eaa1a5d54ce245 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Wed, 18 May 2022 09:05:35 -0400 Subject: [PATCH 32/36] Use a tuple type for withStackAlloc --- src/mono/wasm/runtime/memory.ts | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/mono/wasm/runtime/memory.ts b/src/mono/wasm/runtime/memory.ts index 58d8a8b55faf4..08bdf0fee4183 100644 --- a/src/mono/wasm/runtime/memory.ts +++ b/src/mono/wasm/runtime/memory.ts @@ -131,19 +131,11 @@ export function setCU64(offset: _MemOffset, value: cuint64.CUInt64): void { /// Allocates a new buffer of the given size on the Emscripten stack and passes a pointer to it to the callback. /// Returns the result of the callback. As usual with stack allocations, the buffer is freed when the callback returns. /// Do not attempt to use the stack pointer after the callback is finished. -export function withStackAlloc (bytesWanted: number, f: (ptr: VoidPtr) => TResult): TResult; -export function withStackAlloc (bytesWanted: number, f: (ptr: VoidPtr, ud1: T1) => TResult, ud1: T1): TResult; -export function withStackAlloc (bytesWanted: number, f: (ptr: VoidPtr, ud1: T1, ud2: T2) => TResult, ud1: T1, ud2: T2): TResult; -export function withStackAlloc (bytesWanted: number, f: (ptr: VoidPtr, ud1: T1, ud2: T2, ud3: T3) => TResult, ud1: T1, ud2: T2, ud3: T3): TResult; -export function withStackAlloc (bytesWanted: number, f: (ptr: VoidPtr, ud1: T1, ud2: T2, ud3: T3, ud4: T4) => TResult, ud1: T1, ud2: T2, ud3: T3, ud4: T4): TResult; -export function withStackAlloc (bytesWanted: number, f: (ptr: VoidPtr, ud1: T1, ud2: T2, ud3: T3, ud4: T4, ud5: T5) => TResult, ud1: T1, ud2: T2, ud3: T3, ud4: T4, ud5: T5): TResult; -export function withStackAlloc (bytesWanted: number, f: (ptr: VoidPtr, ud1: T1, ud2: T2, ud3: T3, ud4: T4, ud5: T5, ud6: T6) => TResult, ud1: T1, ud2: T2, ud3: T3, ud4: T4, ud5: T5, ud6: T6): TResult; -export function withStackAlloc (bytesWanted: number, f: (ptr: VoidPtr, ud1: T1, ud2: T2, ud3: T3, ud4: T4, ud5: T5, ud6: T6, ud7:T7) => TResult, ud1: T1, ud2: T2, ud3: T3, ud4: T4, ud5: T5, ud6: T6, ud7: T7): TResult; -export function withStackAlloc (bytesWanted: number, f : (ptr: VoidPtr, ud1?: T1, ud2?: T2, ud3?: T3, ud4?: T4, ud5?: T5, ud6?: T6, ud7?: T7) => TResult, ud1?: T1, ud2?:T2, ud3?: T3, ud4?: T4, ud5?: T5, ud6?: T6, ud7?: T7): TResult { +export function withStackAlloc (bytesWanted: number, f : (ptr: VoidPtr, ...userargs : [...TArgs]) => TResult, ... userargs : [... TArgs]): TResult { const sp = Module.stackSave(); const ptr = Module.stackAlloc (bytesWanted); try { - return f (ptr, ud1, ud2, ud3, ud4, ud5, ud6, ud7); + return f (ptr, ... userargs); } finally { Module.stackRestore(sp); } From dc1dbadeef45a7256ba125c0670151a2060639bb Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Wed, 18 May 2022 09:26:26 -0400 Subject: [PATCH 33/36] fix whitespace --- src/mono/wasm/runtime/buffers.ts | 2 +- src/mono/wasm/runtime/diagnostics.ts | 34 ++++++++++++++-------------- src/mono/wasm/runtime/memory.ts | 14 ++++++------ 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/mono/wasm/runtime/buffers.ts b/src/mono/wasm/runtime/buffers.ts index 118c8d43162e2..3a607275fa643 100644 --- a/src/mono/wasm/runtime/buffers.ts +++ b/src/mono/wasm/runtime/buffers.ts @@ -202,4 +202,4 @@ export function mono_wasm_load_bytes_into_heap(bytes: Uint8Array): VoidPtr { const heapBytes = new Uint8Array(Module.HEAPU8.buffer, memoryOffset, bytes.length); heapBytes.set(bytes); return memoryOffset; -} \ No newline at end of file +} diff --git a/src/mono/wasm/runtime/diagnostics.ts b/src/mono/wasm/runtime/diagnostics.ts index c603949627678..eee3f061b979f 100644 --- a/src/mono/wasm/runtime/diagnostics.ts +++ b/src/mono/wasm/runtime/diagnostics.ts @@ -32,12 +32,12 @@ enum State { Done, } -function start_streaming (sessionID: EventPipeSessionIDImpl): void { - cwraps.mono_wasm_event_pipe_session_start_streaming (sessionID); +function start_streaming(sessionID: EventPipeSessionIDImpl): void { + cwraps.mono_wasm_event_pipe_session_start_streaming(sessionID); } -function stop_streaming (sessionID: EventPipeSessionIDImpl): void { - cwraps.mono_wasm_event_pipe_session_disable (sessionID); +function stop_streaming(sessionID: EventPipeSessionIDImpl): void { + cwraps.mono_wasm_event_pipe_session_disable(sessionID); } /// An EventPipe session that saves the event data to a file in the VFS. @@ -46,13 +46,13 @@ class EventPipeFileSession implements EventPipeSession { private _sessionID: EventPipeSessionIDImpl; private _tracePath: string; // VFS file path to the trace file - get sessionID(): bigint { return BigInt (this._sessionID); } + get sessionID(): bigint { return BigInt(this._sessionID); } - constructor (sessionID: EventPipeSessionIDImpl, tracePath: string) { + constructor(sessionID: EventPipeSessionIDImpl, tracePath: string) { this._state = State.Initialized; this._sessionID = sessionID; this._tracePath = tracePath; - console.debug (`EventPipe session ${this.sessionID} created`); + console.debug(`EventPipe session ${this.sessionID} created`); } start = () => { @@ -60,8 +60,8 @@ class EventPipeFileSession implements EventPipeSession { throw new Error(`EventPipe session ${this.sessionID} already started`); } this._state = State.Started; - start_streaming (this._sessionID); - console.debug (`EventPipe session ${this.sessionID} started`); + start_streaming(this._sessionID); + console.debug(`EventPipe session ${this.sessionID} started`); } stop = () => { @@ -69,13 +69,13 @@ class EventPipeFileSession implements EventPipeSession { throw new Error(`cannot stop an EventPipe session in state ${this._state}, not 'Started'`); } this._state = State.Done; - stop_streaming (this._sessionID); - console.debug (`EventPipe session ${this.sessionID} stopped`); + stop_streaming(this._sessionID); + console.debug(`EventPipe session ${this.sessionID} stopped`); } getTraceBlob = () => { if (this._state !== State.Done) { - throw new Error (`session is in state ${this._state}, not 'Done'`); + throw new Error(`session is in state ${this._state}, not 'Done'`); } const data = Module.FS_readFile(this._tracePath, { encoding: "binary" }) as Uint8Array; return new Blob([data], { type: "application/octet-stream" }); @@ -85,7 +85,7 @@ class EventPipeFileSession implements EventPipeSession { // a conter for the number of sessions created let totalSessions = 0; -function createSessionWithPtrCB (sessionIdOutPtr: VoidPtr, options: EventPipeSessionOptions | undefined, tracePath: string): false | number { +function createSessionWithPtrCB(sessionIdOutPtr: VoidPtr, options: EventPipeSessionOptions | undefined, tracePath: string): false | number { const defaultRundownRequested = true; const defaultProviders = ""; const defaultBufferSizeInMB = 1; @@ -96,12 +96,12 @@ function createSessionWithPtrCB (sessionIdOutPtr: VoidPtr, options: EventPipeSes if (!cwraps.mono_wasm_event_pipe_enable(tracePath, defaultBufferSizeInMB, defaultProviders, rundown, sessionIdOutPtr)) { return false; } else { - return memory.getI32 (sessionIdOutPtr); + return memory.getI32(sessionIdOutPtr); } } export interface Diagnostics { - createEventPipeSession (options?: EventPipeSessionOptions): EventPipeSession | null; + createEventPipeSession(options?: EventPipeSessionOptions): EventPipeSession | null; } /// APIs for working with .NET diagnostics from JavaScript. @@ -112,9 +112,9 @@ export const diagnostics: Diagnostics = { createEventPipeSession(options?: EventPipeSessionOptions): EventPipeSession | null { // The session trace is saved to a file in the VFS. The file name doesn't matter, // but we'd like it to be distinct from other traces. - const tracePath =`/trace-${totalSessions++}.nettrace`; + const tracePath = `/trace-${totalSessions++}.nettrace`; - const success = memory.withStackAlloc (sizeOfInt32, createSessionWithPtrCB, options, tracePath); + const success = memory.withStackAlloc(sizeOfInt32, createSessionWithPtrCB, options, tracePath); if (success === false) return null; diff --git a/src/mono/wasm/runtime/memory.ts b/src/mono/wasm/runtime/memory.ts index 08bdf0fee4183..6a50d8925bd15 100644 --- a/src/mono/wasm/runtime/memory.ts +++ b/src/mono/wasm/runtime/memory.ts @@ -117,25 +117,25 @@ export function getF64(offset: _MemOffset): number { } export function getCU64(offset: _MemOffset): cuint64.CUInt64 { - const lo = getI32 (offset); - const hi = getI32 (offset + 4); + const lo = getI32(offset); + const hi = getI32(offset + 4); return cuint64.pack32(lo, hi); } export function setCU64(offset: _MemOffset, value: cuint64.CUInt64): void { const [lo, hi] = cuint64.unpack32(value); - setI32 (offset, lo); - setI32 (offset + 4, hi); + setI32(offset, lo); + setI32(offset + 4, hi); } /// Allocates a new buffer of the given size on the Emscripten stack and passes a pointer to it to the callback. /// Returns the result of the callback. As usual with stack allocations, the buffer is freed when the callback returns. /// Do not attempt to use the stack pointer after the callback is finished. -export function withStackAlloc (bytesWanted: number, f : (ptr: VoidPtr, ...userargs : [...TArgs]) => TResult, ... userargs : [... TArgs]): TResult { +export function withStackAlloc(bytesWanted: number, f: (ptr: VoidPtr, ...userargs: [...TArgs]) => TResult, ...userargs: [...TArgs]): TResult { const sp = Module.stackSave(); - const ptr = Module.stackAlloc (bytesWanted); + const ptr = Module.stackAlloc(bytesWanted); try { - return f (ptr, ... userargs); + return f(ptr, ...userargs); } finally { Module.stackRestore(sp); } From 82ba6c4704f3bc9032e48ad99b5bfbc4f0956072 Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Wed, 18 May 2022 09:54:12 -0400 Subject: [PATCH 34/36] go back to explicit args for withStackAlloc insead of rest/spread --- src/mono/wasm/runtime/memory.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/mono/wasm/runtime/memory.ts b/src/mono/wasm/runtime/memory.ts index 6a50d8925bd15..205e1e90a8c58 100644 --- a/src/mono/wasm/runtime/memory.ts +++ b/src/mono/wasm/runtime/memory.ts @@ -131,11 +131,15 @@ export function setCU64(offset: _MemOffset, value: cuint64.CUInt64): void { /// Allocates a new buffer of the given size on the Emscripten stack and passes a pointer to it to the callback. /// Returns the result of the callback. As usual with stack allocations, the buffer is freed when the callback returns. /// Do not attempt to use the stack pointer after the callback is finished. -export function withStackAlloc(bytesWanted: number, f: (ptr: VoidPtr, ...userargs: [...TArgs]) => TResult, ...userargs: [...TArgs]): TResult { +export function withStackAlloc(bytesWanted: number, f: (ptr: VoidPtr) => TResult): TResult; +export function withStackAlloc(bytesWanted: number, f: (ptr: VoidPtr, ud1: T1) => TResult, ud1: T1): TResult; +export function withStackAlloc(bytesWanted: number, f: (ptr: VoidPtr, ud1: T1, ud2: T2) => TResult, ud1: T1, ud2: T2): TResult; +export function withStackAlloc(bytesWanted: number, f: (ptr: VoidPtr, ud1: T1, ud2: T2, ud3: T3) => TResult, ud1: T1, ud2: T2, ud3: T3): TResult; +export function withStackAlloc(bytesWanted: number, f: (ptr: VoidPtr, ud1?: T1, ud2?: T2, ud3?: T3) => TResult, ud1?: T1, ud2?: T2, ud3?: T3): TResult { const sp = Module.stackSave(); const ptr = Module.stackAlloc(bytesWanted); try { - return f(ptr, ...userargs); + return f(ptr, ud1, ud2, ud3); } finally { Module.stackRestore(sp); } From aed366006db20674d00e8858cee03a2452442c6c Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Wed, 18 May 2022 10:38:44 -0400 Subject: [PATCH 35/36] use unsigned 32-bit get/set in cuint64 get/set --- src/mono/wasm/runtime/memory.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/mono/wasm/runtime/memory.ts b/src/mono/wasm/runtime/memory.ts index 205e1e90a8c58..e524bb48046e3 100644 --- a/src/mono/wasm/runtime/memory.ts +++ b/src/mono/wasm/runtime/memory.ts @@ -49,7 +49,7 @@ export function setU16(offset: _MemOffset, value: number): void { Module.HEAPU16[offset >>> 1] = value; } -export function setU32 (offset: _MemOffset, value: _NumberOrPointer) : void { +export function setU32(offset: _MemOffset, value: _NumberOrPointer): void { Module.HEAPU32[offset >>> 2] = value; } @@ -61,7 +61,7 @@ export function setI16(offset: _MemOffset, value: number): void { Module.HEAP16[offset >>> 1] = value; } -export function setI32 (offset: _MemOffset, value: _NumberOrPointer) : void { +export function setI32(offset: _MemOffset, value: _NumberOrPointer): void { Module.HEAP32[offset >>> 2] = value; } @@ -117,15 +117,15 @@ export function getF64(offset: _MemOffset): number { } export function getCU64(offset: _MemOffset): cuint64.CUInt64 { - const lo = getI32(offset); - const hi = getI32(offset + 4); + const lo = getU32(offset); + const hi = getU32(offset + 4); return cuint64.pack32(lo, hi); } export function setCU64(offset: _MemOffset, value: cuint64.CUInt64): void { const [lo, hi] = cuint64.unpack32(value); - setI32(offset, lo); - setI32(offset + 4, hi); + setU32(offset, lo); + setU32(offset + 4, hi); } /// Allocates a new buffer of the given size on the Emscripten stack and passes a pointer to it to the callback. From 0a7381d1b0aa18d49fbc855c5024a19599b2027a Mon Sep 17 00:00:00 2001 From: Aleksey Kliger Date: Wed, 18 May 2022 10:40:40 -0400 Subject: [PATCH 36/36] cosmetic changes to sample --- src/mono/sample/wasm/browser-eventpipe/Program.cs | 2 +- src/mono/sample/wasm/browser-eventpipe/index.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mono/sample/wasm/browser-eventpipe/Program.cs b/src/mono/sample/wasm/browser-eventpipe/Program.cs index e5b41a8194f96..2755fcd7e254a 100644 --- a/src/mono/sample/wasm/browser-eventpipe/Program.cs +++ b/src/mono/sample/wasm/browser-eventpipe/Program.cs @@ -13,7 +13,7 @@ public class Test { public static void Main(string[] args) { - Console.WriteLine ("Hello, World!"); + // not called. See main.js for all the interesting bits } private static int iterations; diff --git a/src/mono/sample/wasm/browser-eventpipe/index.html b/src/mono/sample/wasm/browser-eventpipe/index.html index 7dfcd3da04e5a..87ef4b46638f9 100644 --- a/src/mono/sample/wasm/browser-eventpipe/index.html +++ b/src/mono/sample/wasm/browser-eventpipe/index.html @@ -11,7 +11,7 @@ - Answer to the Ultimate Question of Life, the Universe, and Everything is : + Computing Fib repeatedly: