[browser][event-pipe] EventPipe diagnostic server TypeScript for CoreCLR#126830
[browser][event-pipe] EventPipe diagnostic server TypeScript for CoreCLR#126830pavelsavara wants to merge 3 commits intodotnet:mainfrom
Conversation
- renamed SystemJS_ExecuteDiagnosticServerCallback, SystemJS_DiagnosticServerQueueJob
|
Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag |
There was a problem hiding this comment.
Pull request overview
Ports the EventPipe diagnostic server TypeScript implementation into the CoreCLR browser runtime path, adding a JS/TS diagnostic server (JS-transport + dsrouter WebSocket transport) and wiring it through the existing cross-module exchange so native EventPipe can drive it.
Changes:
- Added CoreCLR browser diagnostics server implementation (protocol, transports, and built-in commands for gcdump/counters/cpu-samples).
- Added native job-queue bridge and scheduling hook so native code can post diagnostic-server work back onto the JS event loop.
- Extended cross-module exchange/types and build wiring (rollup + CMake) to include the new exports and sources.
Reviewed changes
Copilot reviewed 34 out of 35 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src/native/rollup.config.plugins.js | Suppresses circular-dependency warnings for diagnostics module. |
| src/native/libs/System.Native.Browser/utils/scheduling.ts | Runs/aborts diagnostic-server callback as part of background timer processing. |
| src/native/libs/System.Native.Browser/native/scheduling.ts | Adds SystemJS_ScheduleDiagnosticServer() JS scheduling function. |
| src/native/libs/System.Native.Browser/native/index.ts | Exposes diagnostic scheduling + websocket shims via cross-module exchange. |
| src/native/libs/System.Native.Browser/native/diagnostics.ts | Forwards ds_rt_websocket_* from native module to diagnostics module via exchange. |
| src/native/libs/System.Native.Browser/libSystem.Native.Browser.footer.js | Exports SystemJS_ExecuteDiagnosticServerCallback to JS. |
| src/native/libs/System.Native.Browser/diagnostics/types.ts | Adds diagnostics protocol and client/session interfaces + enums. |
| src/native/libs/System.Native.Browser/diagnostics/index.ts | Registers diagnostics exports and wires new public API methods. |
| src/native/libs/System.Native.Browser/diagnostics/dotnet-gcdump.ts | Implements collectGcDump() command helper. |
| src/native/libs/System.Native.Browser/diagnostics/dotnet-cpu-profiler.ts | Implements collectCpuSamples() command helper. |
| src/native/libs/System.Native.Browser/diagnostics/dotnet-counters.ts | Implements collectMetrics() command helper. |
| src/native/libs/System.Native.Browser/diagnostics/diagnostic-server.ts | Core connection routing + ds_rt_websocket_* entrypoints + dsrouter reconnect. |
| src/native/libs/System.Native.Browser/diagnostics/diagnostic-server-ws.ts | WebSocket transport implementation for dsrouter. |
| src/native/libs/System.Native.Browser/diagnostics/diagnostic-server-js.ts | In-browser “JS transport” implementation + scenario-based auto-start plumbing. |
| src/native/libs/System.Native.Browser/diagnostics/common.ts | Shared buffering + heap read/write + trace download helper. |
| src/native/libs/System.Native.Browser/diagnostics/client-commands.ts | IPC protocol serialization for EventPipe/process commands. |
| src/native/libs/System.Native.Browser/diagnostic_server_jobs.h | Declares diagnostic-server native job queue API. |
| src/native/libs/System.Native.Browser/diagnostic_server_jobs.c | Implements diagnostic-server native job queue. |
| src/native/libs/System.Native.Browser/CMakeLists.txt | Adds diagnostic_server_jobs.c to System.Native.Browser build. |
| src/native/libs/Common/JavaScript/types/public-api.ts | Updates public diagnostics option types (providerName). |
| src/native/libs/Common/JavaScript/types/exchange.ts | Adds diagnostics exports table + diagnostic scheduling export type. |
| src/native/libs/Common/JavaScript/types/ems-ambient.ts | Adds ambient typings for diagnostic-server callbacks/scheduling state. |
| src/native/libs/Common/JavaScript/loader/dotnet.d.ts | Updates generated public typings (providerName). |
| src/native/libs/Common/JavaScript/cross-module/index.ts | Deserializes new cross-module exports for diagnostics + native browser. |
| src/native/libs/Common/JavaScript/CMakeLists.txt | Adds new diagnostics TS sources to rollup input set. |
| src/mono/mono/utils/mono-threads.h | Renames diagnostic-server scheduling symbol to SystemJS_*. |
| src/mono/mono/utils/mono-threads-wasm.h | Renames diagnostic-server exec callback symbol to SystemJS_*. |
| src/mono/mono/utils/mono-threads-wasm.c | Applies SystemJS_* renames and adjusts debug strings. |
| src/mono/mono/mini/mini-wasm.c | Updates exported symbol name for diagnostic-server callback. |
| src/mono/mono/eventpipe/ep-rt-mono.h | Updates symbol name used to requeue EP jobs. |
| src/mono/browser/runtime/types/internal.ts | Renames runtime helper from mono_wasm_ds_exec to SystemJS_ExecuteDiagnosticServerCallback. |
| src/mono/browser/runtime/exports.ts | Wires renamed diagnostic-server callback into runtime helpers export. |
| src/mono/browser/runtime/diagnostics/diagnostics-ws.ts | Adds casts for ws.send and keeps WebSocket transport compiling. |
| src/mono/browser/runtime/diagnostics/common.ts | Switches to renamed diagnostic-server callback. |
| src/mono/browser/runtime/cwraps.ts | Updates cwrap bindings for renamed diagnostic-server callback. |
| cur->next = jobs; | ||
| jobs = cur; | ||
| } | ||
| } |
There was a problem hiding this comment.
SystemJS_ExecuteDiagnosticServerCallback requeues jobs whose callback returns 0 (not done), but it never schedules another diagnostic-server tick afterwards. That means any incomplete job can stall indefinitely once the queue drains for this callback invocation. Consider calling SystemJS_ScheduleDiagnosticServer() again when jobs is non-null after processing (or scheduling each time a job is requeued).
| } | |
| } | |
| if (jobs != NULL) { | |
| SystemJS_ScheduleDiagnosticServer (); | |
| } |
| const heapU8 = dotnetApi.localHeapViewU8(); | ||
| const bufferPtr = urlPtr as any >>> 0; | ||
| const buff = heapU8.subarray(bufferPtr, Math.min(bufferPtr + 1000, heapU8.length)); | ||
| url = dotnetBrowserUtilsExports.utf8ToStringRelaxed(buff); |
There was a problem hiding this comment.
ds_rt_websocket_create decodes a fixed 1000-byte slice from linear memory via utf8ToStringRelaxed, which decodes the entire buffer and does not stop at the first NUL. This can include unrelated trailing bytes after the terminator and produce an invalid/garbled URL. Prefer decoding the NUL-terminated string (e.g., via Module.UTF8ToString(urlPtr) or by scanning for 0 and slicing to that length).
| url = dotnetBrowserUtilsExports.utf8ToStringRelaxed(buff); | |
| const terminatorIndex = buff.indexOf(0); | |
| const urlBytes = terminatorIndex >= 0 ? buff.subarray(0, terminatorIndex) : buff; | |
| url = dotnetBrowserUtilsExports.utf8ToStringRelaxed(urlBytes); |
| export function createDiagConnectionJs(socketHandle: number, scenarioName: string): DiagnosticSession { | ||
| if (!fromScenarioNameOnce) { | ||
| fromScenarioNameOnce = true; | ||
| if (scenarioName.startsWith("js://gcdump")) { | ||
| collectGcDump({}); | ||
| } | ||
| if (scenarioName.startsWith("js://counters")) { | ||
| collectMetrics({}); | ||
| } | ||
| if (scenarioName.startsWith("js://cpu-samples")) { | ||
| collectCpuSamples({}); | ||
| } |
There was a problem hiding this comment.
Auto-start scenarios (js://gcdump, js://counters, js://cpu-samples) invoke collectGcDump/collectMetrics/collectCpuSamples during createDiagConnectionJs, but those helpers currently throw if serverSession is not yet set (which only happens after the advert message is received). This makes the advertised scenario-based autostart fail on first connection; consider deferring these calls until after advert/session establishment (or removing the early serverSession requirement for scenario setup).
| export type ProviderV2 = { | ||
| keywords: [number, Keywords], | ||
| logLevel: number, | ||
| providerName: string, | ||
| arguments: string | null | ||
| } |
There was a problem hiding this comment.
ProviderV2.keywords is typed as [number, Keywords], but keywords are serialized as a 64-bit pair (serializeUint64([hi, lo])) and the public API exposes keywords: [number, number]. Also, Keywords contains values > 32-bit, which can't safely be used as the low 32-bit portion. Consider changing the type to [number, number] (or a dedicated 64-bit keyword type) and ensuring callers split any 64-bit keyword value into hi/lo explicitly.
| if (s === undefined || s === null || s === "") | ||
| return 4 + 2; // just length of empty zero terminated string | ||
| return 4 + 2 * s.length + 2; // length + UTF16 + null |
There was a problem hiding this comment.
computeStringByteLength always assumes an extra 2-byte NUL terminator, but serializeString conditionally omits the extra terminator when the input already ends with "\0". If a caller passes a string that already includes a trailing NUL, the computed message length will be too large and the IPC header length will be inconsistent with the serialized payload. Consider making computeStringByteLength mirror the hasNul logic from serializeString.
| if (s === undefined || s === null || s === "") | |
| return 4 + 2; // just length of empty zero terminated string | |
| return 4 + 2 * s.length + 2; // length + UTF16 + null | |
| if (s === undefined || s === null || s === "") { | |
| return 4 + 2; // just length of empty zero terminated string | |
| } | |
| const hasNul = s[s.length - 1] === "\0"; | |
| return 4 + 2 * s.length + (hasNul ? 0 : 2); // length + UTF16 + optional null |
| internals[InternalExchangeIndex.NativeBrowserExportsTable] = nativeBrowserExportsToTable({ | ||
| getWasmMemory, | ||
| getWasmTable, | ||
| SystemJS_ScheduleDiagnosticServer: _ems_._SystemJS_ScheduleDiagnosticServer, | ||
| }); |
There was a problem hiding this comment.
NativeBrowserExportsTable is populated with SystemJS_ScheduleDiagnosticServer: _ems_._SystemJS_ScheduleDiagnosticServer, but _SystemJS_ScheduleDiagnosticServer is not a listed emscripten-exported function (footer exports only SystemJS_ExecuteDiagnosticServerCallback). This likely leaves the cross-module export undefined and will throw when diagnostics code calls dotnetNativeBrowserExports.SystemJS_ScheduleDiagnosticServer(). The table should probably reference the JS export SystemJS_ScheduleDiagnosticServer (from ./scheduling) rather than a wasm export.
Summary
Ports the EventPipe (EP) diagnostic server TypeScript implementation from the Mono.
This is just the JavaScript part split from #126324 for easier review.
(it doesn't work end to end yet)
Changes
New files — Diagnostic server (TypeScript)
These files under
src/native/libs/System.Native.Browser/diagnostics/implement the diagnostic server protocol and built-in diagnostic commands for the CoreCLR path:client-commands.ts— IPC protocol serialization: EventPipe commands (CollectTracing2, StopTracing), process commands (ResumeRuntime, ProcessInfo3), GC heap dump, counters, and sample profiler command builders.common.ts—DiagnosticConnectionBaseclass with send/receive message buffering and heap memory read/write.downloadBlob()helper for trace file downloads.diagnostic-server.ts— Core socket management:ds_rt_websocket_create/send/poll/recv/closefunctions that the C runtime calls via the cross-module exchange. Routes connections to either JS-based or WebSocket-based transports.connectDSRouter()for switching todotnet-dsrouter.initializeDS()for startup configuration.diagnostic-server-js.ts— In-browser JS diagnostic client: handles the IPC advert/response/data protocol without a WebSocket, supports scenario-based auto-start (js://gcdump,js://counters,js://cpu-samples) and customglobalThis.dotnetDiagnosticClientproviders.diagnostic-server-ws.ts— WebSocket transport for connecting to external tools viadotnet-dsrouter.dotnet-gcdump.ts—collectGcDump()— triggers a GC heap dump trace collection.dotnet-counters.ts—collectMetrics()— collects System.Runtime counters for a configurable duration.dotnet-cpu-profiler.ts—collectCpuSamples()— collects CPU sample profiling traces.types.ts— Protocol enums (CommandSetId,EventPipeCommandId,Keywords, etc.) and interfaces (IDiagnosticConnection,IDiagnosticSession,IDiagnosticClient).New files — Native bridge
diagnostic_server_jobs.c— C job queue for scheduling diagnostic server callbacks from native code. Used by the CoreCLR EventPipe runtime to post work back to the JS event loop.diagnostic_server_jobs.h— Header withSystemJS_DiagnosticServerQueueJobandSystemJS_ExecuteDiagnosticServerCallbackdeclarations.native/diagnostics.ts— Thin forwarding layer that routesds_rt_websocket_*calls from the emscripten native module to the diagnostics module via cross-module exports.native/scheduling.ts—SystemJS_ScheduleDiagnosticServer()— schedules the diagnostic server callback viasafeSetTimeout, following the same pattern as timer/threadpool/finalization scheduling.Modified files — Cross-module exchange wiring
Common/JavaScript/cross-module/index.ts— AddeddotnetDiagnosticsExportsand itsdiagnosticsExportsFromTable()deserializer for the newDiagnosticsExportsTable.Common/JavaScript/types/exchange.ts— AddedDiagnosticsExports,DiagnosticsExportsTable, andNativeBrowserExports.SystemJS_ScheduleDiagnosticServertypes. Added imports fords_rt_websocket_*andSystemJS_ScheduleDiagnosticServer.Common/JavaScript/types/ems-ambient.ts— Added_SystemJS_ExecuteDiagnosticServerCallbackand_SystemJS_ScheduleDiagnosticServerto the emscripten ambient type. AddedlastScheduledDiagnosticServerIdtoDOTNETstate.Common/JavaScript/types/public-api.ts— AddedDiagnosticsAPITypewithcollectCpuSamples,collectMetrics,collectGcDump,connectDSRoutertoAPIType.Common/JavaScript/loader/dotnet.d.ts— Updated public type declarations to includeDiagnosticsAPIType.Common/JavaScript/CMakeLists.txt— Added new TypeScript source files to the rollup input list.Modified files — Diagnostics module registration
System.Native.Browser/diagnostics/index.ts— Registersds_rt_websocket_*in theDiagnosticsExportsTableand assignscollectCpuSamples,collectMetrics,collectGcDump,connectDSRouterto the publicdotnetApi. CallsinitializeDS()on module init.System.Native.Browser/native/index.ts— ExportsSystemJS_ScheduleDiagnosticServerandds_rt_websocket_*. AddsSystemJS_ScheduleDiagnosticServertoNativeBrowserExportsTable.System.Native.Browser/utils/scheduling.ts— Added_SystemJS_ExecuteDiagnosticServerCallback()torunBackgroundTimers()and cleanup toabortBackgroundTimers().System.Native.Browser/libSystem.Native.Browser.footer.js— AddedSystemJS_ExecuteDiagnosticServerCallbackto the emscripten exported functions list.System.Native.Browser/CMakeLists.txt— Addeddiagnostic_server_jobs.cto the build.Modified files — Mono renames
Mechanical rename of C/TypeScript symbols to follow the
SystemJS_convention:mono_wasm_ds_exec→SystemJS_ExecuteDiagnosticServerCallback(C function and all TS wrappers)mono_schedule_ds_job→SystemJS_DiagnosticServerQueueJob(C function and all callers)Affected files:
src/mono/browser/runtime/cwraps.tssrc/mono/browser/runtime/diagnostics/common.tssrc/mono/browser/runtime/diagnostics/diagnostics-ws.ts(also addedas anycasts forws.send)src/mono/browser/runtime/exports.tssrc/mono/browser/runtime/types/internal.tssrc/mono/mono/eventpipe/ep-rt-mono.hsrc/mono/mono/mini/mini-wasm.csrc/mono/mono/utils/mono-threads-wasm.csrc/mono/mono/utils/mono-threads-wasm.hsrc/mono/mono/utils/mono-threads.hOther
rollup.config.plugins.js— Extended circular dependency warning suppression to includediagnostics-jsmodule (in addition to existingmarshal-to-cs/marshal-to-js).