From 3c5bf6d3e6aeb02e697a9f10e74df29a9a9fd4f8 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Thu, 13 Oct 2022 11:43:50 +0200 Subject: [PATCH 1/2] - the EP sample will download .nettrace when compiled with MonoDiagnosticsMock (debug) To allow the app and the mock communicate - added postMessageToBrowser and addEventListenerFromBrowser to mock environment - exposed INTERNAL.diagnosticServerThread when built with MonoDiagnosticsMock - added processSend to MockScriptEngineImpl so that mock could process more than just first message - fixed EP serializer of empty string vs null - make mime types and http headers more configurable for all samples - move CSP policy header just into advanced sample (it prevents the EP socket to be open on another port) - added README - minor cleanup of async stuff --- src/mono/sample/wasm/Directory.Build.targets | 21 +++-- .../Wasm.Advanced.Sample.csproj | 2 + .../sample/wasm/browser-eventpipe/README.md | 43 ++++++++++ .../Wasm.Browser.EventPipe.Sample.csproj | 5 +- .../sample/wasm/browser-eventpipe/main.js | 43 ++++++++-- .../sample/wasm/browser-eventpipe/mock.js | 79 +++++++++++++++---- src/mono/wasm/runtime/diagnostics-mock.d.ts | 5 +- .../wasm/runtime/diagnostics-mock.d.ts.sha256 | 2 +- src/mono/wasm/runtime/diagnostics/README.md | 7 +- .../runtime/diagnostics/browser/controller.ts | 5 ++ .../runtime/diagnostics/mock/environment.ts | 19 ++++- .../wasm/runtime/diagnostics/mock/index.ts | 42 +++++++--- .../wasm/runtime/diagnostics/mock/types.ts | 3 + .../diagnostics/server_pthread/index.ts | 4 +- .../ipc-protocol/base-serializer.ts | 2 +- .../diagnostics/server_pthread/mock-remote.ts | 3 +- .../protocol-client-commands.ts | 2 +- 17 files changed, 231 insertions(+), 56 deletions(-) create mode 100644 src/mono/sample/wasm/browser-eventpipe/README.md diff --git a/src/mono/sample/wasm/Directory.Build.targets b/src/mono/sample/wasm/Directory.Build.targets index 8970dc1dc3716..a0ec478d9e757 100644 --- a/src/mono/sample/wasm/Directory.Build.targets +++ b/src/mono/sample/wasm/Directory.Build.targets @@ -7,7 +7,18 @@ WasmRunnerTemplate.cmd $([MSBuild]::NormalizePath('$(WasmAppDir)', '$(RunScriptOutputName)')) - $(WasmXHarnessArgs) --web-server-use-cop + + + <_MonoWasmThreads Condition="'$(WasmEnableThreads)' == 'true' or '$(WasmEnablePerfTracing)' == 'true' or '$(MonoWasmBuildVariant)' == 'multithread' or '$(MonoWasmBuildVariant)' == 'perftrace'">true + $(WasmXHarnessArgs) --web-server-use-cop + <_ServeHeaders Condition="'$(_MonoWasmThreads)' == 'true'">$(_ServeHeaders) -h Cross-Origin-Embedder-Policy:require-corp -h Cross-Origin-Opener-Policy:same-origin + + + <_ServeMimeTypes>$(_ServeMimeTypes) --mime .wasm=application/wasm + <_ServeMimeTypes>$(_ServeMimeTypes) --mime .json=application/json + <_ServeMimeTypes>$(_ServeMimeTypes) --mime .mjs=text/javascript + <_ServeMimeTypes>$(_ServeMimeTypes) --mime .cjs=text/javascript + <_ServeMimeTypes>$(_ServeMimeTypes) --mime .js=text/javascript $(RepoRoot)dotnet$(_ScriptExt) <_AOTFlag Condition="'$(RunAOTCompilation)' != ''">/p:RunAOTCompilation=$(RunAOTCompilation) <_WasmMainJSFileName>$([System.IO.Path]::GetFileName('$(WasmMainJSPath)')) + $(BuildAdditionalArgs) /p:MonoDiagnosticsMock=$(MonoDiagnosticsMock) @@ -42,12 +54,7 @@ - - + diff --git a/src/mono/sample/wasm/browser-advanced/Wasm.Advanced.Sample.csproj b/src/mono/sample/wasm/browser-advanced/Wasm.Advanced.Sample.csproj index cb5bcc82f3ed8..70b3e5c9fbd23 100644 --- a/src/mono/sample/wasm/browser-advanced/Wasm.Advanced.Sample.csproj +++ b/src/mono/sample/wasm/browser-advanced/Wasm.Advanced.Sample.csproj @@ -6,6 +6,8 @@ true -s USE_CLOSURE_COMPILER=1 -s LEGACY_GL_EMULATION=1 -lGL -lSDL + + <_ServeHeaders>$(_ServeHeaders) -h "Content-Security-Policy: default-src 'self' 'wasm-unsafe-eval'" diff --git a/src/mono/sample/wasm/browser-eventpipe/README.md b/src/mono/sample/wasm/browser-eventpipe/README.md new file mode 100644 index 0000000000000..dd5f8053458d6 --- /dev/null +++ b/src/mono/sample/wasm/browser-eventpipe/README.md @@ -0,0 +1,43 @@ +[see also](../../../wasm/runtime/diagnostics/README.md) + +To be able to run this sample you need to build the runtime with `/p:WasmEnablePerfTracing=true` and use Chrome browser + +# Testing with mock + +Build the runtime with `/p:WasmEnablePerfTracing=true /p:MonoDiagnosticsMock=true` +Run this test with `/p:MonoDiagnosticsMock=true` + +It will inject file [mock.js](./mock.js) into the worker thread, which is mocking the `dotnet trace` tool. + +The sample will communicate with the mock and start the Fibonacci experiment and start and stop the trace recording automatically. +It will also download the .nettrace from the browser. You can covert it to other formats, for example +``` +dotnet trace convert --format Speedscope c:\Downloads\trace.1665653486202.nettrace -o c:\Downloads\trace.1665653486202.speedscope +``` + +# Testing with dotnet trace tool + +Build the runtime with `/p:WasmEnablePerfTracing=true` +Build version of dsrouter with WebSockets https://github.com/lambdageek/diagnostics/tree/wasm-server + +In console #1 start dsrouter +``` +c:\Dev\diagnostics\artifacts\bin\dotnet-dsrouter\Debug\net6.0\dotnet-dsrouter.exe server-websocket -ws http://127.0.0.1:8088/diagnostics -ipcs C:\Dev\diagnostics\socket +``` + +In console #2 start the sample +``` +dotnet build /p:TargetOS=Browser /p:TargetArchitecture=wasm /p:Configuration=Debug /t:RunSample src/mono/sample/wasm/browser-eventpipe /p:MonoDiagnosticsMock=false +``` + +In console #3 start the dotnet trace +``` +dotnet trace collect --diagnostic-port C:\Dev\diagnostics\socket,connect --format Chromium +``` +This will use `cpu-sampling`, + +In the browser click `Start Work` button and after it finished + +In the #3 console press `Enter` + +In the browser open dev tools and on performance tab import file `C:\Dev\socket_XXXXXXXX_YYYYYY.chromium.json` which dotnet trace produced \ No newline at end of file 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 c7cfb7dc62236..4dd51a99c1b11 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 @@ -3,14 +3,11 @@ true true + true true $(NoWarn);CA2007 - - - - true diff --git a/src/mono/sample/wasm/browser-eventpipe/main.js b/src/mono/sample/wasm/browser-eventpipe/main.js index 8c931537ab7ee..2bf5dc8d9ff9a 100644 --- a/src/mono/sample/wasm/browser-eventpipe/main.js +++ b/src/mono/sample/wasm/browser-eventpipe/main.js @@ -26,6 +26,11 @@ async function doWork(startWork, stopWork, getIterationsDone) { const ret = await workPromise; // get the answer const iterations = getIterationsDone(); // get how many times the loop ran + INTERNAL.diagnosticServerThread.postMessageToWorker({ + type: "diagnostic_server_mock", + cmd: "fibonacci-done" + }); + btn = document.getElementById("startWork"); btn.disabled = false; btn.innerText = "Start Work"; @@ -37,12 +42,6 @@ async function doWork(startWork, stopWork, getIterationsDone) { return ret; } -function getOnClickHandler(startWork, stopWork, getIterationsDone) { - return async function () { - await doWork(startWork, stopWork, getIterationsDone); - } -} - const isTest = (config) => config.environmentVariables["CI_TEST"] === "true"; async function runTest({ StartAsyncWork, StopWork, GetIterationsDone }) { const result = await doWork(StartAsyncWork, StopWork, GetIterationsDone); @@ -51,19 +50,47 @@ async function runTest({ StartAsyncWork, StopWork, GetIterationsDone }) { } async function main() { - const { MONO, Module, getAssemblyExports, getConfig } = await dotnet + const { INTERNAL, MONO, Module, getAssemblyExports, getConfig } = await dotnet .withElementOnExit() .withExitCodeLogging() + .withDiagnosticTracing(false) .create(); globalThis.__Module = Module; globalThis.MONO = MONO; + globalThis.INTERNAL = INTERNAL; const exports = await getAssemblyExports("Wasm.Browser.EventPipe.Sample.dll"); const btn = document.getElementById("startWork"); btn.style.backgroundColor = "rgb(192,255,192)"; - btn.onclick = getOnClickHandler(exports.Sample.Test.StartAsyncWork, exports.Sample.Test.StopWork, exports.Sample.Test.GetIterationsDone); + btn.onclick = () => doWork(exports.Sample.Test.StartAsyncWork, exports.Sample.Test.StopWork, exports.Sample.Test.GetIterationsDone); + + INTERNAL.diagnosticServerThread.port.addEventListener("message", (event) => { + console.warn("diagnosticServerThread" + event.type) + + if (event.data.cmd === "collecting") { + btn.onclick(); + } + + if (event.data.cmd === "collected") { + const buffer = event.data.buffer; + const length = event.data.length; + console.warn("Downloading trace " + length) + const view = new Uint8Array(buffer, 0, length) + const blobUrl = URL.createObjectURL(new Blob([view.slice()])); + const link = document.createElement("a"); + link.href = blobUrl; + link.download = "trace." + (new Date()).valueOf() + ".nettrace"; + document.body.appendChild(link); + + link.dispatchEvent(new MouseEvent('click', { + bubbles: true, cancelable: true, view: window + })); + + document.body.removeChild(link); + } + }); const config = getConfig(); if (isTest(config)) { diff --git a/src/mono/sample/wasm/browser-eventpipe/mock.js b/src/mono/sample/wasm/browser-eventpipe/mock.js index 0fd7d6fe3d0bd..51ceafa2db8ed 100644 --- a/src/mono/sample/wasm/browser-eventpipe/mock.js +++ b/src/mono/sample/wasm/browser-eventpipe/mock.js @@ -22,24 +22,71 @@ function script(env) { const runtimeResumed = env.createPromiseController(); /** @type { PromiseAndControllerVoid } */ const waitForever = env.createPromiseController(); + + /** @type { PromiseAndControllerVoid } */ + const fibonacciDone = env.createPromiseController(); + env.addEventListenerFromBrowser("fibonacci-done", (event) => { + fibonacciDone.promise_control.resolve(); + }); + return [ async (conn) => { - await conn.waitForSend(env.expectAdvertise); - conn.reply(env.command.makeEventPipeCollectTracing2({ - circularBufferMB: 1, - format: 1, - requestRundown: true, - providers: [ - { - keywords: [0, 0], - logLevel: 5, - provider_name: "WasmHello", - filter_data: "EventCounterIntervalSec=1" + try { + await conn.waitForSend(env.expectAdvertise); + conn.reply(env.command.makeEventPipeCollectTracing2({ + circularBufferMB: 256, + format: 1, + requestRundown: true, + providers: [ + { + keywords: [0, 0], + logLevel: 5, + provider_name: "WasmHello", + filter_data: "EventCounterIntervalSec=1" + }, + { + keywords: [0, 61440], + logLevel: 4, + provider_name: "Microsoft-DotNETCore-SampleProfiler", + filter_data: null + }, + { + keywords: [ + -1051734851, + 20 + ], + logLevel: 4, + provider_name: "Microsoft-Windows-DotNETRuntime", + filter_data: null + } + ] + })); + let sessionID = undefined; + const buffer = new SharedArrayBuffer(2_000_000); + const view = new Uint8Array(buffer); + let length = 0; + await conn.processSend((bytes) => { + if (sessionID === undefined) { + // first block is just a session handshake + if (!env.reply.expectOk(4)) { + throw new Error("bad data"); + } + sessionID = env.reply.extractOkSessionID(bytes) + sessionStarted.promise_control.resolve(sessionID); + env.postMessageToBrowser({ cmd: "collecting" }); + } + else { + const bytesView = new Uint8Array(bytes) + view.set(bytesView, length); + length += bytesView.byteLength; } - ] - })); - const sessionID = await conn.waitForSend(env.reply.expectOk(4), env.reply.extractOkSessionID); - sessionStarted.promise_control.resolve(sessionID); + }); + console.warn("final totalBytesStreamed " + length); + env.postMessageToBrowser({ cmd: "collected", buffer, length }); + } + catch (err) { + console.error(err) + } }, async (conn) => { await Promise.all([conn.waitForSend(env.expectAdvertise), sessionStarted.promise]); @@ -47,7 +94,7 @@ function script(env) { runtimeResumed.promise_control.resolve(); }, async (conn) => { - await Promise.all([conn.waitForSend(env.expectAdvertise), runtimeResumed.promise, sessionStarted.promise]); + await Promise.all([conn.waitForSend(env.expectAdvertise), runtimeResumed.promise, sessionStarted.promise, fibonacciDone.promise]); const sessionID = await sessionStarted.promise; await env.delay(5000); conn.reply(env.command.makeEventPipeStopTracing({ sessionID })); diff --git a/src/mono/wasm/runtime/diagnostics-mock.d.ts b/src/mono/wasm/runtime/diagnostics-mock.d.ts index ee4926fbdb037..706e4f8275fff 100644 --- a/src/mono/wasm/runtime/diagnostics-mock.d.ts +++ b/src/mono/wasm/runtime/diagnostics-mock.d.ts @@ -42,7 +42,7 @@ interface EventPipeCollectTracingCommandProvider { keywords: [number, number]; logLevel: number; provider_name: string; - filter_data: string; + filter_data: string | null; } declare type RemoveCommandSetAndId = Omit; @@ -50,6 +50,7 @@ declare type FilterPredicate = (data: ArrayBuffer) => boolean; interface MockScriptConnection { waitForSend(filter: FilterPredicate): Promise; waitForSend(filter: FilterPredicate, extract: (data: ArrayBuffer) => T): Promise; + processSend(onMessage: (data: ArrayBuffer) => any): Promise; reply(data: ArrayBuffer): void; } interface MockEnvironmentCommand { @@ -62,6 +63,8 @@ interface MockEnvironmentReply { extractOkSessionID(data: ArrayBuffer): number; } interface MockEnvironment { + postMessageToBrowser(message: any, transferable?: Transferable[]): void; + addEventListenerFromBrowser(cmd: string, listener: (data: any) => void): void; createPromiseController(): PromiseAndController; delay: (ms: number) => Promise; command: MockEnvironmentCommand; diff --git a/src/mono/wasm/runtime/diagnostics-mock.d.ts.sha256 b/src/mono/wasm/runtime/diagnostics-mock.d.ts.sha256 index 73b0d251126a6..1e0dfcbd73dde 100644 --- a/src/mono/wasm/runtime/diagnostics-mock.d.ts.sha256 +++ b/src/mono/wasm/runtime/diagnostics-mock.d.ts.sha256 @@ -1 +1 @@ -4a0d47e4e5fabdc42443a54d452be697161a5053d1e24c867200e42742427e3f \ No newline at end of file +df0de24fd6e67a483f9b3713248d053f912d0cead178e6ec1f537460067495cd \ No newline at end of file diff --git a/src/mono/wasm/runtime/diagnostics/README.md b/src/mono/wasm/runtime/diagnostics/README.md index 52995f3895578..64a37345b8340 100644 --- a/src/mono/wasm/runtime/diagnostics/README.md +++ b/src/mono/wasm/runtime/diagnostics/README.md @@ -5,10 +5,11 @@ What's in here: - `index.ts` toplevel APIs - `browser/` APIs for the main thread. The main thread has 2 responsibilities: - control the overall diagnostic server `browser/controller.ts` -- `server_pthread/` A long-running worker that owns the WebSocket connections out of the browser to th ehost and that receives the session payloads from the streaming threads. The server receives streaming EventPipe data from -EventPipe streaming threads (that are just ordinary C pthreads) through a shared memory queue and forwards the data to the WebSocket. The server uses the [DS binary IPC protocol](https://github.com/dotnet/diagnostics/blob/main/documentation/design-docs/ipc-protocol.md) which repeatedly opens WebSockets to the host. +- `server_pthread/` A long-running worker that owns the WebSocket connections out of the browser to the host and that receives the session payloads from the streaming threads. + The server receives streaming EventPipe data from EventPipe streaming threads (that are just ordinary C pthreads) through a shared memory queue and forwards the data to the WebSocket. + The server uses the [DS binary IPC protocol](https://github.com/dotnet/diagnostics/blob/main/documentation/design-docs/ipc-protocol.md) which repeatedly opens WebSockets to the host. - `shared/` type definitions to be shared between the worker and browser main thread -- `mock/` a utility to fake WebSocket connectings by playing back a script. Used for prototyping the diagnostic server without hooking up to a real WebSocket. +- `mock/` a utility to fake WebSocket connections by playing back a script. Used for prototyping the diagnostic server without hooking up to a real WebSocket. ## Mocking diagnostics clients diff --git a/src/mono/wasm/runtime/diagnostics/browser/controller.ts b/src/mono/wasm/runtime/diagnostics/browser/controller.ts index b7cdd6f0f7e5f..35ccb1d64207d 100644 --- a/src/mono/wasm/runtime/diagnostics/browser/controller.ts +++ b/src/mono/wasm/runtime/diagnostics/browser/controller.ts @@ -2,9 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. import cwraps from "../../cwraps"; +import { INTERNAL } from "../../imports"; import { withStackAlloc, getI32 } from "../../memory"; import { Thread, waitForThread } from "../../pthreads/browser"; import { isDiagnosticMessage, makeDiagnosticServerControlCommand } from "../shared/controller-commands"; +import monoDiagnosticsMock from "consts:monoDiagnosticsMock"; /// An object that can be used to control the diagnostic server. export interface ServerController { @@ -63,6 +65,9 @@ export async function startDiagnosticServer(websocket_url: string): Promise void) { + pthread_self.addEventListenerFromBrowser((event) => { + if (event.data.cmd === cmd) listener(event.data); + }); +} + export function createMockEnvironment(): MockEnvironment { const command = { makeEventPipeCollectTracing2, @@ -117,6 +130,8 @@ export function createMockEnvironment(): MockEnvironment { extractOkSessionID, }; return { + postMessageToBrowser, + addEventListenerFromBrowser, createPromiseController, delay, command, diff --git a/src/mono/wasm/runtime/diagnostics/mock/index.ts b/src/mono/wasm/runtime/diagnostics/mock/index.ts index b8f696b6f9140..38f418613967e 100644 --- a/src/mono/wasm/runtime/diagnostics/mock/index.ts +++ b/src/mono/wasm/runtime/diagnostics/mock/index.ts @@ -104,24 +104,48 @@ export function mock(script: MockScript, options?: MockOptions): Mock { this.eventTarget.dispatchEvent(new MessageEvent("message", { data: sendData })); } + processSend(onMessage: (data: ArrayBuffer) => any): Promise { + const trace = this.trace; + if (trace) { + console.debug(`mock ${this.ident} processSend`); + } + return new Promise((resolve, reject) => { + this.mockReplyEventTarget.addEventListener("close", () => { + resolve(); + }); + this.mockReplyEventTarget.addEventListener("message", (event: any) => { + const data = event.data; + if (typeof data === "string") { + console.warn(`mock ${this.ident} waitForSend got string:`, data); + reject(new Error("mock script connection received string data")); + } + + if (trace) { + console.debug(`mock ${this.ident} processSend got:`, data.byteLength); + } + onMessage(data); + }); + }); + } + async waitForSend(filter: (data: ArrayBuffer) => boolean, extract?: (data: ArrayBuffer) => T): Promise { const trace = this.trace; if (trace) { console.debug(`mock ${this.ident} waitForSend`); } - const event = await new Promise>((resolve) => { - this.mockReplyEventTarget.addEventListener("message", (event) => { + const data = await new Promise((resolve) => { + this.mockReplyEventTarget.addEventListener("message", (event: any) => { + const data = event.data; + if (typeof data === "string") { + console.warn(`mock ${this.ident} waitForSend got string:`, data); + throw new Error("mock script connection received string data"); + } if (trace) { - console.debug(`mock ${this.ident} waitForSend got:`, event); + console.debug(`mock ${this.ident} waitForSend got:`, data.byteLength); } - resolve(event as MessageEvent); + resolve(data); }, { once: true }); }); - const data = event.data; - if (typeof data === "string") { - console.warn(`mock ${this.ident} waitForSend got string:`, data); - throw new Error("mock script connection received string data"); - } if (!filter(data)) { throw new Error("Unexpected data"); } diff --git a/src/mono/wasm/runtime/diagnostics/mock/types.ts b/src/mono/wasm/runtime/diagnostics/mock/types.ts index b3682db8c3f05..92f8fe4b017de 100644 --- a/src/mono/wasm/runtime/diagnostics/mock/types.ts +++ b/src/mono/wasm/runtime/diagnostics/mock/types.ts @@ -13,6 +13,7 @@ export type FilterPredicate = (data: ArrayBuffer) => boolean; export interface MockScriptConnection { waitForSend(filter: FilterPredicate): Promise; waitForSend(filter: FilterPredicate, extract: (data: ArrayBuffer) => T): Promise; + processSend(onMessage: (data: ArrayBuffer) => any): Promise; reply(data: ArrayBuffer): void; } @@ -31,6 +32,8 @@ interface MockEnvironmentReply { } export interface MockEnvironment { + postMessageToBrowser(message: any, transferable?: Transferable[]): void; + addEventListenerFromBrowser(cmd: string, listener: (data: any) => void): void; createPromiseController(): PromiseAndController; delay: (ms: number) => Promise; command: MockEnvironmentCommand; diff --git a/src/mono/wasm/runtime/diagnostics/server_pthread/index.ts b/src/mono/wasm/runtime/diagnostics/server_pthread/index.ts index 989f826d57066..01587e0bc8511 100644 --- a/src/mono/wasm/runtime/diagnostics/server_pthread/index.ts +++ b/src/mono/wasm/runtime/diagnostics/server_pthread/index.ts @@ -179,7 +179,7 @@ class DiagnosticServerImpl implements DiagnosticServer { parseCommand(message: ProtocolCommandEvent, connNum: number): ProtocolClientCommandBase | null { console.debug("MONO_WASM: parsing byte command: ", message.data, connNum); const result = parseProtocolCommand(message.data); - console.debug("MONO_WASM: parsied byte command: ", result, connNum); + console.debug("MONO_WASM: parsed byte command: ", result, connNum); if (result.success) { return result.result; } else { @@ -230,7 +230,7 @@ class DiagnosticServerImpl implements DiagnosticServer { } async stopEventPipe(ws: WebSocket | MockRemoteSocket, sessionID: EventPipeSessionIDImpl): Promise { - console.debug("MONO_WASM: stopEventPipe", sessionID); + console.info("MONO_WASM: stopEventPipe", sessionID); cwraps.mono_wasm_event_pipe_session_disable(sessionID); // we might send OK before the session is actually stopped since the websocket is async // but the client end should be robust to that. diff --git a/src/mono/wasm/runtime/diagnostics/server_pthread/ipc-protocol/base-serializer.ts b/src/mono/wasm/runtime/diagnostics/server_pthread/ipc-protocol/base-serializer.ts index 616b7338ecfd5..09115d7245bc3 100644 --- a/src/mono/wasm/runtime/diagnostics/server_pthread/ipc-protocol/base-serializer.ts +++ b/src/mono/wasm/runtime/diagnostics/server_pthread/ipc-protocol/base-serializer.ts @@ -57,7 +57,7 @@ const Serializer = { advancePos(pos, payload.byteLength); }, serializeString(buf: Uint8Array, pos: { pos: number }, s: string | null): void { - if (s === null) { + if (s === null || s === undefined || s === "") { Serializer.serializeUint32(buf, pos, 0); } else { const len = s.length; diff --git a/src/mono/wasm/runtime/diagnostics/server_pthread/mock-remote.ts b/src/mono/wasm/runtime/diagnostics/server_pthread/mock-remote.ts index 37c65800db01d..47e98e29b36b5 100644 --- a/src/mono/wasm/runtime/diagnostics/server_pthread/mock-remote.ts +++ b/src/mono/wasm/runtime/diagnostics/server_pthread/mock-remote.ts @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. import monoDiagnosticsMock from "consts:monoDiagnosticsMock"; +import { runtimeHelpers } from "../../imports"; import type { Mock } from "../mock"; import { mock } from "../mock"; @@ -11,7 +12,7 @@ export function importAndInstantiateMock(mockURL: string): Promise { const scriptURL = mockURL.substring(mockPrefix.length); return import(scriptURL).then((mockModule) => { const script = mockModule.default; - return mock(script, { trace: true }); + return mock(script, { trace: runtimeHelpers.diagnosticTracing }); }); } else { return Promise.resolve(undefined as unknown as Mock); diff --git a/src/mono/wasm/runtime/diagnostics/server_pthread/protocol-client-commands.ts b/src/mono/wasm/runtime/diagnostics/server_pthread/protocol-client-commands.ts index 0232ad32db746..13276942e2704 100644 --- a/src/mono/wasm/runtime/diagnostics/server_pthread/protocol-client-commands.ts +++ b/src/mono/wasm/runtime/diagnostics/server_pthread/protocol-client-commands.ts @@ -44,7 +44,7 @@ export interface EventPipeCollectTracingCommandProvider { keywords: [number, number]; // lo,hi. FIXME: this is ugly logLevel: number; provider_name: string; - filter_data: string; + filter_data: string | null; } export type RemoveCommandSetAndId = Omit; From 43a5a02603d15633cc4cbdbeef6fc93382feb9af Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Thu, 13 Oct 2022 11:54:53 +0200 Subject: [PATCH 2/2] readme --- .../sample/wasm/browser-eventpipe/README.md | 2 +- src/mono/wasm/runtime/diagnostics/README.md | 32 ++----------------- 2 files changed, 4 insertions(+), 30 deletions(-) diff --git a/src/mono/sample/wasm/browser-eventpipe/README.md b/src/mono/sample/wasm/browser-eventpipe/README.md index dd5f8053458d6..ad7ffe1158093 100644 --- a/src/mono/sample/wasm/browser-eventpipe/README.md +++ b/src/mono/sample/wasm/browser-eventpipe/README.md @@ -25,7 +25,7 @@ In console #1 start dsrouter c:\Dev\diagnostics\artifacts\bin\dotnet-dsrouter\Debug\net6.0\dotnet-dsrouter.exe server-websocket -ws http://127.0.0.1:8088/diagnostics -ipcs C:\Dev\diagnostics\socket ``` -In console #2 start the sample +In console #2 start the sample ``` dotnet build /p:TargetOS=Browser /p:TargetArchitecture=wasm /p:Configuration=Debug /t:RunSample src/mono/sample/wasm/browser-eventpipe /p:MonoDiagnosticsMock=false ``` diff --git a/src/mono/wasm/runtime/diagnostics/README.md b/src/mono/wasm/runtime/diagnostics/README.md index 64a37345b8340..50273012431ee 100644 --- a/src/mono/wasm/runtime/diagnostics/README.md +++ b/src/mono/wasm/runtime/diagnostics/README.md @@ -5,8 +5,8 @@ What's in here: - `index.ts` toplevel APIs - `browser/` APIs for the main thread. The main thread has 2 responsibilities: - control the overall diagnostic server `browser/controller.ts` -- `server_pthread/` A long-running worker that owns the WebSocket connections out of the browser to the host and that receives the session payloads from the streaming threads. - The server receives streaming EventPipe data from EventPipe streaming threads (that are just ordinary C pthreads) through a shared memory queue and forwards the data to the WebSocket. +- `server_pthread/` A long-running worker that owns the WebSocket connections out of the browser to the host and that receives the session payloads from the streaming threads. + The server receives streaming EventPipe data from EventPipe streaming threads (that are just ordinary C pthreads) through a shared memory queue and forwards the data to the WebSocket. The server uses the [DS binary IPC protocol](https://github.com/dotnet/diagnostics/blob/main/documentation/design-docs/ipc-protocol.md) which repeatedly opens WebSockets to the host. - `shared/` type definitions to be shared between the worker and browser main thread - `mock/` a utility to fake WebSocket connections by playing back a script. Used for prototyping the diagnostic server without hooking up to a real WebSocket. @@ -68,30 +68,4 @@ The connection object (of type `MockScriptConnection` defined in [./mock/index.t ### Mock example -```js -function script (env) { - const sessionStarted = env.createPromiseController(); /* coordinate between the connections */ - return [ - async (conn) => { - /* first connection. Expect an ADVR packet */ - await conn.waitForSend(isAdvertisePacket); - conn.reply(makeEventPipeStartCollecting2 ({ "collectRundownEvents": "true", "providers": "WasmHello::5:EventCounterIntervalSec=1" })); - /* wait for an "OK" reply with 4 extra bytes of payload, which is the sessionID */ - const sessionID = await conn.waitForSend(isReplyOK(4), extractSessionID); - sessionStarted.promise_control.resolve(sessionID); - /* connection kept open. the runtime will send EventPipe data here */ - }, - async (conn) => { - /* second connection. Expect an ADVR packet and the sessionStarted sessionID */ - await Promise.all([conn.waitForSend (isAdvertisePacket); sessionStarted.promise]); - /* collect a trace for 5 seconds */ - await new Promise((resolve) => await new Promise((resolve) => { setTimeout(resolve, 1000); }); - const sessionID = await sessionStarted.promise; - conn.reply(makeEventPipeStopCollecting({sessionID})); - /* wait for an "OK" with no payload */ - await conn.waitForSend(isReplyOK()); - } - /* any further calls to "open" will be an error */ - ] -} -``` +See [browser-eventpipe](../../../sample/wasm/browser-eventpipe/)