Skip to content

[browser][event-pipe] EventPipe diagnostic server TypeScript for CoreCLR#126830

Draft
pavelsavara wants to merge 3 commits intodotnet:mainfrom
pavelsavara:browser_EP_coreclr_js
Draft

[browser][event-pipe] EventPipe diagnostic server TypeScript for CoreCLR#126830
pavelsavara wants to merge 3 commits intodotnet:mainfrom
pavelsavara:browser_EP_coreclr_js

Conversation

@pavelsavara
Copy link
Copy Markdown
Member

@pavelsavara pavelsavara commented Apr 13, 2026

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.tsDiagnosticConnectionBase class 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/close functions that the C runtime calls via the cross-module exchange. Routes connections to either JS-based or WebSocket-based transports. connectDSRouter() for switching to dotnet-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 custom globalThis.dotnetDiagnosticClient providers.
  • diagnostic-server-ws.ts — WebSocket transport for connecting to external tools via dotnet-dsrouter.
  • dotnet-gcdump.tscollectGcDump() — triggers a GC heap dump trace collection.
  • dotnet-counters.tscollectMetrics() — collects System.Runtime counters for a configurable duration.
  • dotnet-cpu-profiler.tscollectCpuSamples() — 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 with SystemJS_DiagnosticServerQueueJob and SystemJS_ExecuteDiagnosticServerCallback declarations.
  • native/diagnostics.ts — Thin forwarding layer that routes ds_rt_websocket_* calls from the emscripten native module to the diagnostics module via cross-module exports.
  • native/scheduling.tsSystemJS_ScheduleDiagnosticServer() — schedules the diagnostic server callback via safeSetTimeout, following the same pattern as timer/threadpool/finalization scheduling.

Modified files — Cross-module exchange wiring

  • Common/JavaScript/cross-module/index.ts — Added dotnetDiagnosticsExports and its diagnosticsExportsFromTable() deserializer for the new DiagnosticsExportsTable.
  • Common/JavaScript/types/exchange.ts — Added DiagnosticsExports, DiagnosticsExportsTable, and NativeBrowserExports.SystemJS_ScheduleDiagnosticServer types. Added imports for ds_rt_websocket_* and SystemJS_ScheduleDiagnosticServer.
  • Common/JavaScript/types/ems-ambient.ts — Added _SystemJS_ExecuteDiagnosticServerCallback and _SystemJS_ScheduleDiagnosticServer to the emscripten ambient type. Added lastScheduledDiagnosticServerId to DOTNET state.
  • Common/JavaScript/types/public-api.ts — Added DiagnosticsAPIType with collectCpuSamples, collectMetrics, collectGcDump, connectDSRouter to APIType.
  • Common/JavaScript/loader/dotnet.d.ts — Updated public type declarations to include DiagnosticsAPIType.
  • 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 — Registers ds_rt_websocket_* in the DiagnosticsExportsTable and assigns collectCpuSamples, collectMetrics, collectGcDump, connectDSRouter to the public dotnetApi. Calls initializeDS() on module init.
  • System.Native.Browser/native/index.ts — Exports SystemJS_ScheduleDiagnosticServer and ds_rt_websocket_*. Adds SystemJS_ScheduleDiagnosticServer to NativeBrowserExportsTable.
  • System.Native.Browser/utils/scheduling.ts — Added _SystemJS_ExecuteDiagnosticServerCallback() to runBackgroundTimers() and cleanup to abortBackgroundTimers().
  • System.Native.Browser/libSystem.Native.Browser.footer.js — Added SystemJS_ExecuteDiagnosticServerCallback to the emscripten exported functions list.
  • System.Native.Browser/CMakeLists.txt — Added diagnostic_server_jobs.c to the build.

Modified files — Mono renames

Mechanical rename of C/TypeScript symbols to follow the SystemJS_ convention:

  • mono_wasm_ds_execSystemJS_ExecuteDiagnosticServerCallback (C function and all TS wrappers)
  • mono_schedule_ds_jobSystemJS_DiagnosticServerQueueJob (C function and all callers)

Affected files:

  • src/mono/browser/runtime/cwraps.ts
  • src/mono/browser/runtime/diagnostics/common.ts
  • src/mono/browser/runtime/diagnostics/diagnostics-ws.ts (also added as any casts for ws.send)
  • src/mono/browser/runtime/exports.ts
  • src/mono/browser/runtime/types/internal.ts
  • src/mono/mono/eventpipe/ep-rt-mono.h
  • src/mono/mono/mini/mini-wasm.c
  • src/mono/mono/utils/mono-threads-wasm.c
  • src/mono/mono/utils/mono-threads-wasm.h
  • src/mono/mono/utils/mono-threads.h

Other

  • rollup.config.plugins.js — Extended circular dependency warning suppression to include diagnostics-js module (in addition to existing marshal-to-cs/marshal-to-js).

- renamed SystemJS_ExecuteDiagnosticServerCallback, SystemJS_DiagnosticServerQueueJob
@pavelsavara pavelsavara added this to the 11.0.0 milestone Apr 13, 2026
@pavelsavara pavelsavara self-assigned this Apr 13, 2026
Copilot AI review requested due to automatic review settings April 13, 2026 10:08
@pavelsavara pavelsavara added arch-wasm WebAssembly architecture area-Diagnostics-coreclr os-browser Browser variant of arch-wasm labels Apr 13, 2026
@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag
See info in area-owners.md if you want to be subscribed.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;
}
}
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
}
}
if (jobs != NULL) {
SystemJS_ScheduleDiagnosticServer ();
}

Copilot uses AI. Check for mistakes.
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);
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
url = dotnetBrowserUtilsExports.utf8ToStringRelaxed(buff);
const terminatorIndex = buff.indexOf(0);
const urlBytes = terminatorIndex >= 0 ? buff.subarray(0, terminatorIndex) : buff;
url = dotnetBrowserUtilsExports.utf8ToStringRelaxed(urlBytes);

Copilot uses AI. Check for mistakes.
Comment on lines +151 to +162
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({});
}
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
Comment on lines +35 to +40
export type ProviderV2 = {
keywords: [number, Keywords],
logLevel: number,
providerName: string,
arguments: string | null
}
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +180 to +182
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
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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

Copilot uses AI. Check for mistakes.
Comment on lines 28 to 32
internals[InternalExchangeIndex.NativeBrowserExportsTable] = nativeBrowserExportsToTable({
getWasmMemory,
getWasmTable,
SystemJS_ScheduleDiagnosticServer: _ems_._SystemJS_ScheduleDiagnosticServer,
});
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arch-wasm WebAssembly architecture area-Diagnostics-coreclr os-browser Browser variant of arch-wasm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants