* Adds a `/p:WasmEnablePerfTracing=true` configuration.
In this configuration the runtime is built with threading (`MonoWasmThreads` property is true), but user C# code is not allowed to start threads and doesn't use the portable threadpool (`MonoWasmThreadsNoUser` property is also true).
The upshot is that EventPipe can start threads but user code is still single-threaded.
* Adds a `MONO.diagnostics` interface in JavaScript. There's a single method for now `createEventPipeSession` which creates a session that can save a trace to a file on the virtual file system. JS code (or a user in a dev console) needs to call `start()` and `stop()` on the session object to begin collecting samples. The data is saved temporarily to the Emscripten VFS and can be retrived into a JavaScript Blob (and from there downloaded to a file outside the browser).
* Adds a sample that runs an async task for five seconds and collects samples and then triggers a `click()` to download the trace file out of the browser.
* Adds a TS module to help with working with uint64_t values in the emscripten heap.
* Exposes empscripten Module.stackSave, stackRestore and stackAlloc operations to the runtime TS modules. Use for working with event pipe session ID outparam.
---
* add DISABLE_WASM_USER_THREADS mono cmake option
* Disable Thread.StartInternal icall if DISABLE_WASM_USER_THREADS
if threading is enabled for the runtime internally, but disabled for user code, throw PNSE
* add an eventpipe sample
* [wasm-ep] (browser-eventpipe sample) run loop for longer
* [samples/wasm-eventpipe] make an async task sample
change the sample to do some work asynchronously using setTimeout instead of blocking
* [wasm] Add MONO.diagnostics interface
Binds enable, start, disable methods defaulting to non-streaming FILE mode
* if wasm threads are disabled, but perftracing is enabled, don't log overlapped io events
* fix whitespace and nits
* don't need try/finally in the sample anymore
* more whitespace
* add start method to EventPipeSession interface
* don't run wasm-eventpipe sample on CI lanes without perftracing
* more whitespace
* fix eslint warnings, default rundown to true, allow callback for traceFilePath option
* add EventPipeSession.getTraceBlob
for retrieving the collected traces instead of exposing the emscripten VFS directly.
update the sample to use URL.createObjectURL (session.getTraceBlob()) to create the download link
* [browser-eventpipe sample] remove unnecessary ref assemblies
* use ep_char8_t for C decls of event pipe wasm exports
* Use stack allocation for temporaries
Expose the emscripten stack allocation API
* 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
* Make the sample do more work in managed
give the sample profiler some non-empty samples to collect
* Move withStackAlloc to memory.ts
* 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)
* Add overloads to memory.withStackAlloc to avoid creating closures
Pass additional arguments to the callback function
* sample: explain why there's a 10s pause
* move createEventPipeSession callback to a function
ensures the closure is created once
* Use a tuple type for withStackAlloc
* use unsigned 32-bit get/set in cuint64 get/set
* fix whitespace
<ProjectExclusions Include="$(MonoProjectRoot)sample\wasm\browser-mt-eventpipe\Wasm.Browser.ThreadsEP.Sample.csproj" />
</ItemGroup>
+ <!-- Samples that require a perf-tracing wasm runtime -->
+ <ItemGroup Condition="'$(TargetOS)' == 'Browser' and '$(WasmEnablePerfTracing)' != 'true'" >
+ <ProjectExclusions Include="$(MonoProjectRoot)sample\wasm\browser-eventpipe\Wasm.Browser.EventPipe.Sample.csproj" />
+ </ItemGroup>
+
<!-- Wasm aot on all platforms -->
<ItemGroup Condition="'$(TargetOS)' == 'Browser' and '$(BuildAOTTestsOnHelix)' == 'true' and '$(RunDisabledWasmTests)' != 'true' and '$(RunAOTCompilation)' == 'true'">
<!-- https://github.com/dotnet/runtime/issues/66118 -->
success = true;
#if FEATURE_PERFTRACING
+#if !(TARGET_BROWSER && !FEATURE_WASM_THREADS)
if (NativeRuntimeEventSource.Log.IsEnabled())
NativeRuntimeEventSource.Log.ThreadPoolIOPack(pNativeOverlapped);
#endif
+#endif
return _pNativeOverlapped;
}
finally
/* 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
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 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)")
${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
)
#include <config.h>
#include "mono/component/event_pipe.h"
+#include "mono/component/event_pipe-wasm.h"
#include "mono/metadata/components.h"
static EventPipeSessionID _dummy_session_id;
{
return component_event_pipe_stub_init ();
}
+
+#ifdef HOST_WASM
+
+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)
+{
+ if (out_session_id)
+ *out_session_id = 0;
+ return 0;
+}
+
+
+EMSCRIPTEN_KEEPALIVE gboolean
+mono_wasm_event_pipe_session_start_streaming (MonoWasmEventPipeSessionID session_id)
+{
+ g_assert_not_reached ();
+}
+
+EMSCRIPTEN_KEEPALIVE gboolean
+mono_wasm_event_pipe_session_disable (MonoWasmEventPipeSessionID session_id)
+{
+ g_assert_not_reached ();
+}
+
+#endif /* HOST_WASM */
--- /dev/null
+// 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 <stdint.h>
+#include <eventpipe/ep-ipc-pal-types-forward.h>
+#include <eventpipe/ep-types-forward.h>
+#include <glib.h>
+
+#ifdef HOST_WASM
+
+#include <emscripten.h>
+
+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 */
+
#include <config.h>
#include <mono/component/event_pipe.h>
+#include <mono/component/event_pipe-wasm.h>
#include <mono/utils/mono-publib.h>
#include <mono/utils/mono-compiler.h>
+#include <mono/utils/mono-threads-api.h>
#include <eventpipe/ep.h>
#include <eventpipe/ep-event.h>
#include <eventpipe/ep-event-instance.h>
#include <eventpipe/ep-session.h>
+
extern void ep_rt_mono_component_init (void);
static bool _event_pipe_component_inited = false;
return &fn_table;
}
+
+
+#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,
+ 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)
+{
+ 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 = ep_to_wasm_session_id (session);
+ MONO_EXIT_GC_UNSAFE;
+ return TRUE;
+}
+
+EMSCRIPTEN_KEEPALIVE gboolean
+mono_wasm_event_pipe_session_start_streaming (MonoWasmEventPipeSessionID session_id)
+{
+ MONO_ENTER_GC_UNSAFE;
+ 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 (MonoWasmEventPipeSessionID session_id)
+{
+ MONO_ENTER_GC_UNSAFE;
+ ep_disable (wasm_to_ep_session_id (session_id));
+ MONO_EXIT_GC_UNSAFE;
+ return TRUE;
+}
+
+#endif /* HOST_WASM */
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
--- /dev/null
+TOP=../../../../..
+
+include ../wasm.mk
+
+ifneq ($(AOT),)
+override MSBUILD_ARGS+=/p:RunAOTCompilation=true
+endif
+
+PROJECT_NAME=Wasm.Browser.EventPipe.Sample.csproj
+
+run: run-browser
--- /dev/null
+// 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;
+using System.Threading.Tasks;
+
+
+namespace Sample
+{
+ public class Test
+ {
+ public static void Main(string[] args)
+ {
+ // not called. See main.js for all the interesting bits
+ }
+
+ private static int iterations;
+ private static CancellationTokenSource cts;
+
+ public static CancellationToken GetCancellationToken()
+ {
+ if (cts == null) {
+ cts = new CancellationTokenSource ();
+ }
+ return cts.Token;
+ }
+
+ [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<int> StartAsyncWork()
+ {
+ CancellationToken ct = GetCancellationToken();
+ long b;
+ const int N = 35;
+ const long expected = 9227465;
+ while (true)
+ {
+ await Task.Delay(1).ConfigureAwait(false);
+ b = recursiveFib (N);
+ if (ct.IsCancellationRequested)
+ break;
+ iterations++;
+ }
+ return b == expected ? 42 : 0;
+ }
+
+ public static void StopWork()
+ {
+ cts.Cancel();
+ }
+
+ public static string GetIterationsDone()
+ {
+ return iterations.ToString();
+ }
+ }
+}
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <WasmCopyAppZipToHelixTestDir Condition="'$(ArchiveTests)' == 'true'">true</WasmCopyAppZipToHelixTestDir>
+ <WasmMainJSPath>main.js</WasmMainJSPath>
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>embedded</DebugType>
+ <WasmDebugLevel>1</WasmDebugLevel>
+ <WasmEnableES6>false</WasmEnableES6>
+ <WasmBuildNative>true</WasmBuildNative>
+ <GenerateRunScriptForSample Condition="'$(ArchiveTests)' == 'true'">true</GenerateRunScriptForSample>
+ <RunScriptCommand>$(ExecXHarnessCmd) wasm test-browser --app=. --browser=Chrome $(XHarnessBrowserPathArg) --html-file=index.html --output-directory=$(XHarnessOutput) -- $(MSBuildProjectName).dll</RunScriptCommand>
+ <FeatureWasmPerfTracing>true</FeatureWasmPerfTracing>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <WasmExtraFilesToDeploy Include="index.html" />
+ <WasmExtraConfig Condition="false" Include="environment_variables" Value='
+{
+ "MONO_LOG_LEVEL": "debug",
+ "MONO_LOG_MASK": "diagnostics"
+}' />
+ </ItemGroup>
+
+ <PropertyGroup>
+ <_SampleProject>Wasm.Browser.CJS.Sample.csproj</_SampleProject>
+ </PropertyGroup>
+
+
+ <PropertyGroup>
+ <RunAnalyzers>true</RunAnalyzers>
+ </PropertyGroup>
+
+ <!-- set the condition to false and you will get a CA1416 errors about calls to create DiagnosticCounter instances -->
+ <ItemGroup Condition="true">
+ <!-- TODO: some .props file that automates this. Unfortunately just adding a ProjectReference to Microsoft.NET.WebAssembly.Threading.proj doesn't work - it ends up bundling the ref assemblies into the publish directory and breaking the app. -->
+ <!-- it's a reference assembly, but the project system doesn't know that - include it during compilation, but don't publish it -->
+ <ProjectReference Include="$(LibrariesProjectRoot)\System.Diagnostics.Tracing.WebAssembly.PerfTracing\ref\System.Diagnostics.Tracing.WebAssembly.PerfTracing.csproj" IncludeAssets="compile" PrivateAssets="none" ExcludeAssets="runtime" Private="false" />
+ </ItemGroup>
+
+ <Target Name="RunSample" DependsOnTargets="RunSampleWithBrowser" />
+</Project>
--- /dev/null
+<!DOCTYPE html>
+<!-- Licensed to the .NET Foundation under one or more agreements. -->
+<!-- The .NET Foundation licenses this file to you under the MIT license. -->
+<html>
+
+<head>
+ <title>Sample EventPipe profile session</title>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+</head>
+
+<body>
+ <h3 id="header">Wasm Browser EventPipe profiling Sample</h3>
+ Computing Fib repeatedly: <span id="out"></span>
+ <script type="text/javascript" src="./dotnet.js"></script>
+ <script type="text/javascript" src="./dotnet.worker.js"></script>
+ <script type="text/javascript" src="./main.js"></script>
+</body>
+
+</html>
--- /dev/null
+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 `<a download="filename" href="data:..."/>` link and click on it to trigger a download with the given name
+ const elt = document.createElement('a');
+ elt.download = filename;
+ 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, '-');
+}
+
+async function loadRuntime() {
+ globalThis.exports = {};
+ await import("./dotnet.js");
+ return globalThis.exports.createDotnetRuntime;
+}
+
+
+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(() => {
+ console.log('user code in createDotnetRuntime')
+ return {
+ disableDotnet6Compatibility: true,
+ configSrc: "./mono-config.json",
+ preInit: () => { console.log('user code Module.preInit') },
+ preRun: () => { console.log('user code Module.preRun') },
+ onRuntimeInitialized: () => { console.log('user code Module.onRuntimeInitialized') },
+ postRun: () => { console.log('user code Module.postRun') },
+ }
+ });
+ globalThis.__Module = Module;
+ globalThis.MONO = MONO;
+ console.log('after createDotnetRuntime')
+
+ const startWork = BINDING.bind_static_method("[Wasm.Browser.EventPipe.Sample] Sample.Test:StartAsyncWork");
+ const stopWork = BINDING.bind_static_method("[Wasm.Browser.EventPipe.Sample] Sample.Test:StopWork");
+ const getIterationsDone = BINDING.bind_static_method("[Wasm.Browser.EventPipe.Sample] Sample.Test:GetIterationsDone");
+ const eventSession = MONO.diagnostics.createEventPipeSession();
+ eventSession.start();
+ const workPromise = startWork();
+
+ document.getElementById("out").innerHTML = '<<running>>';
+ await delay(5000); // let it run for 5 seconds
+
+ 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 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);
+}
+
+console.log("Waiting 10s for curious human before starting the program");
+setTimeout(main, 10000);
const heapBytes = new Uint8Array(Module.HEAPU8.buffer, <any>memoryOffset, bytes.length);
heapBytes.set(bytes);
return memoryOffset;
-}
\ No newline at end of file
+}
--- /dev/null
+// 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(x[1]) << BigInt(32);
+}
+
+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];
+
+
+
["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"]],
*/
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: VoidPtr): 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
const fce = Module.cwrap(sig[0], sig[1], sig[2], sig[3]);
wf[sig[0]] = fce;
return fce;
-}
\ No newline at end of file
+}
--- /dev/null
+// 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 { VoidPtr } from "./types/emscripten";
+import * as memory from "./memory";
+
+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.
+/// 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.
+export interface EventPipeSession {
+ // session ID for debugging logging only
+ get sessionID(): EventPipeSessionID;
+ start(): void;
+ stop(): void;
+ getTraceBlob(): Blob;
+}
+
+// internal session state of the JS instance
+enum State {
+ Initialized,
+ Started,
+ Done,
+}
+
+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);
+}
+
+/// An EventPipe session that saves the event data to a file in the VFS.
+class EventPipeFileSession implements EventPipeSession {
+ private _state: State;
+ private _sessionID: EventPipeSessionIDImpl;
+ private _tracePath: string; // VFS file path to the trace file
+
+ get sessionID(): bigint { return BigInt(this._sessionID); }
+
+ constructor(sessionID: EventPipeSessionIDImpl, tracePath: string) {
+ this._state = State.Initialized;
+ this._sessionID = sessionID;
+ this._tracePath = tracePath;
+ console.debug(`EventPipe session ${this.sessionID} created`);
+ }
+
+ start = () => {
+ if (this._state !== State.Initialized) {
+ throw new Error(`EventPipe session ${this.sessionID} already started`);
+ }
+ this._state = State.Started;
+ 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;
+ 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'`);
+ }
+ const data = Module.FS_readFile(this._tracePath, { encoding: "binary" }) as Uint8Array;
+ return new Blob([data], { type: "application/octet-stream" });
+ }
+}
+
+// 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;
+}
+
+/// 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 kinds of events to be collected.
+ /// Multiple sessions may be created and started at the same time.
+ 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 success = memory.withStackAlloc(sizeOfInt32, createSessionWithPtrCB, options, tracePath);
+
+ if (success === false)
+ return null;
+ const sessionID = success;
+
+ const session = new EventPipeFileSession(sessionID, tracePath);
+ return session;
+ },
+};
+
+export default diagnostics;
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<unknown>;
preInit?: (() => any)[];
preRun?: (() => any)[];
write_at?: string;
send_to?: string;
};
+interface EventPipeSessionOptions {
+ collectRundownEvents?: boolean;
+}
declare type DotnetModuleConfig = {
disableDotnet6Compatibility?: boolean;
config?: MonoConfig | MonoConfigError;
url?: any;
};
+declare type EventPipeSessionID = bigint;
+interface EventPipeSession {
+ get sessionID(): EventPipeSessionID;
+ start(): void;
+ stop(): void;
+ getTraceBlob(): Blob;
+}
+interface Diagnostics {
+ createEventPipeSession(options?: EventPipeSessionOptions): EventPipeSession | null;
+}
+
declare function mono_wasm_runtime_ready(): void;
declare function mono_wasm_setenv(name: string, value: string): void;
getU32: typeof getU32;
getF32: typeof getF32;
getF64: typeof getF64;
+ diagnostics: Diagnostics;
};
declare type MONOType = typeof MONO;
declare const BINDING: {
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
getU32,
getF32,
getF64,
+
+ // Diagnostics
+ diagnostics
};
export type MONOType = typeof MONO;
const wr = this.list[runtimeId];
return wr ? wr.deref() : undefined;
}
-}
\ No newline at end of file
+}
import { Module } from "./imports";
import { VoidPtr, NativePointer, ManagedPointer } from "./types/emscripten";
+import * as cuint64 from "./cuint64";
const alloca_stack: Array<VoidPtr> = [];
const alloca_buffer_size = 32 * 1024;
Module.HEAPU16[<any>offset >>> 1] = value;
}
-export function setU32 (offset: _MemOffset, value: _NumberOrPointer) : void {
+export function setU32(offset: _MemOffset, value: _NumberOrPointer): void {
Module.HEAPU32[<any>offset >>> 2] = <number><any>value;
}
Module.HEAP16[<any>offset >>> 1] = value;
}
-export function setI32 (offset: _MemOffset, value: _NumberOrPointer) : void {
+export function setI32(offset: _MemOffset, value: _NumberOrPointer): void {
Module.HEAP32[<any>offset >>> 2] = <number><any>value;
}
export function getF64(offset: _MemOffset): number {
return Module.HEAPF64[<any>offset >>> 3];
}
+
+export function getCU64(offset: _MemOffset): cuint64.CUInt64 {
+ const lo = getU32(offset);
+ const hi = getU32(<any>offset + 4);
+ return cuint64.pack32(lo, hi);
+}
+
+export function setCU64(offset: _MemOffset, value: cuint64.CUInt64): void {
+ const [lo, hi] = cuint64.unpack32(value);
+ setU32(offset, lo);
+ setU32(<any>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<TResult>(bytesWanted: number, f: (ptr: VoidPtr) => TResult): TResult;
+export function withStackAlloc<T1, TResult>(bytesWanted: number, f: (ptr: VoidPtr, ud1: T1) => TResult, ud1: T1): TResult;
+export function withStackAlloc<T1, T2, TResult>(bytesWanted: number, f: (ptr: VoidPtr, ud1: T1, ud2: T2) => TResult, ud1: T1, ud2: T2): TResult;
+export function withStackAlloc<T1, T2, T3, TResult>(bytesWanted: number, f: (ptr: VoidPtr, ud1: T1, ud2: T2, ud3: T3) => TResult, ud1: T1, ud2: T2, ud3: T3): TResult;
+export function withStackAlloc<T1, T2, T3, TResult>(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, ud1, ud2, ud3);
+ } finally {
+ Module.stackRestore(sp);
+ }
+}
+
send_to?: string // should be in the format <CLASS>::<METHODNAME>, default: 'WebAssembly.Runtime::DumpCoverageProfileData' (DumpCoverageProfileData stores the data into INTERNAL.coverage_profile_data.)
}
+/// Options to configure the event pipe session
+export interface EventPipeSessionOptions {
+ /// Whether to collect additional details (such as method and type names) at EventPipeSession.stop() time (default: true)
+ /// This is required for some use cases, and may allow some tools to better understand the events.
+ collectRundownEvents?: boolean;
+}
+
// how we extended emscripten Module
export type DotnetModule = EmscriptenModule & DotnetModuleConfig;
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<unknown>;
preInit?: (() => any)[];
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;