Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/mono/mono/eventpipe/ep-rt-mono.h
Original file line number Diff line number Diff line change
Expand Up @@ -1024,7 +1024,7 @@ ep_rt_queue_job (
// it's called from browser event loop
ds_job_cb cb = (ds_job_cb)job_func;

// invoke the callback inline for the fist time
// invoke the callback inline for the first time
gsize done = cb (params);

// see if it's done or needs to be scheduled again
Expand Down
2 changes: 2 additions & 0 deletions src/native/eventpipe/ep-shared-config.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@

#cmakedefine FEATURE_PERFTRACING_DISABLE_THREADS
#ifdef FEATURE_PERFTRACING_DISABLE_THREADS
#ifndef PERFTRACING_DISABLE_THREADS
#define PERFTRACING_DISABLE_THREADS
#endif
#endif

#cmakedefine FEATURE_PERFTRACING_DISABLE_PERFTRACING_LISTEN_PORTS
#ifdef FEATURE_PERFTRACING_DISABLE_PERFTRACING_LISTEN_PORTS
Expand Down
1 change: 1 addition & 0 deletions src/native/libs/Common/JavaScript/cross-module/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ export function dotnetUpdateInternalsSubscriber() {
ds_rt_websocket_poll: table[4],
ds_rt_websocket_recv: table[5],
ds_rt_websocket_close: table[6],
ds_rt_browser_performance_measure: table[7],
};
Object.assign(interop, interopLocal);
}
Expand Down
4 changes: 3 additions & 1 deletion src/native/libs/Common/JavaScript/types/exchange.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

import type { EmsAmbientSymbolsType } from "../types";
import type { CharPtr, EmsAmbientSymbolsType } from "../types";

import type { check, error, info, warn, debug, fastCheck, normalizeException } from "../loader/logging";
import type { resolveRunMainPromise, rejectRunMainPromise, getRunMainPromise, abortStartup } from "../loader/run";
Expand Down Expand Up @@ -197,6 +197,7 @@ export type DiagnosticsExportsTable = [
typeof ds_rt_websocket_poll,
typeof ds_rt_websocket_recv,
typeof ds_rt_websocket_close,
(namePtr: CharPtr, start: number) => void
]

export type DiagnosticsExports = {
Expand All @@ -207,4 +208,5 @@ export type DiagnosticsExports = {
ds_rt_websocket_poll: typeof ds_rt_websocket_poll,
ds_rt_websocket_recv: typeof ds_rt_websocket_recv,
ds_rt_websocket_close: typeof ds_rt_websocket_close,
ds_rt_browser_performance_measure: (namePtr: CharPtr, start: number) => void
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export function commandCounters(options: DiagnosticCommandOptions) {
keywords: [0, Keywords.GCHandle],
logLevel: 4,
providerName: "System.Diagnostics.Metrics",
arguments: `SessionId=SHARED;Metrics=System.Runtime;RefreshInterval=${options.intervalSeconds || 1};MaxTimeSeries=1000;MaxHistograms=10;ClientId=${uuidv4()};`,
arguments: `SessionId=SHARED;Metrics=System.Runtime;RefreshInterval=${options.intervalSeconds ?? 1};MaxTimeSeries=1000;MaxHistograms=10;ClientId=${uuidv4()};`,
},
...options.extraProviders || [],
]
Expand Down
19 changes: 14 additions & 5 deletions src/native/libs/System.Native.Browser/diagnostics/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@
import type { VoidPtr } from "../../Common/JavaScript/types/emscripten";
import { dotnetApi, dotnetLogger } from "./cross-module";

// Minimum number of consumed entries before compacting the receive queue
Comment thread
pavelsavara marked this conversation as resolved.
const RECV_QUEUE_COMPACT_THRESHOLD = 128;

export class DiagnosticConnectionBase {
protected messagesToSend: Uint8Array[] = [];
protected messagesReceived: Uint8Array[] = [];
private messagesReceivedHead = 0;
constructor(public clientSocket: number) {
}

Expand All @@ -16,21 +20,26 @@ export class DiagnosticConnectionBase {
}

poll(): number {
return this.messagesReceived.length;
return this.messagesReceived.length - this.messagesReceivedHead;
}

recv(buffer: VoidPtr, bytesToRead: number): number {
if (this.messagesReceived.length === 0) {
if (this.messagesReceivedHead >= this.messagesReceived.length) {
return 0;
}
const message = this.messagesReceived[0]!;
const message = this.messagesReceived[this.messagesReceivedHead]!;
const bytesRead = Math.min(message.length, bytesToRead);
const view = dotnetApi.localHeapViewU8();
view.set(message.subarray(0, bytesRead), buffer as any >>> 0);
if (bytesRead === message.length) {
this.messagesReceived.shift();
this.messagesReceivedHead++;
// Compact when enough dead slots accumulate (>128) and they represent ≥50% of the array
if (this.messagesReceivedHead > RECV_QUEUE_COMPACT_THRESHOLD && this.messagesReceivedHead >= (this.messagesReceived.length >>> 1)) {
this.messagesReceived = this.messagesReceived.slice(this.messagesReceivedHead);
this.messagesReceivedHead = 0;
}
Comment thread
pavelsavara marked this conversation as resolved.
} else {
this.messagesReceived[0] = message.subarray(bytesRead);
this.messagesReceived[this.messagesReceivedHead] = message.subarray(bytesRead);
}
return bytesRead;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { collectCpuSamples } from "./dotnet-cpu-profiler";
// .withEnvironmentVariable("DOTNET_DiagnosticPorts", "download:gcdump")
// or implement function globalThis.dotnetDiagnosticClient with IDiagClient interface

let startupJsClient: IDiagnosticClient | undefined = undefined;
let nextJsClient: PromiseCompletionSource<IDiagnosticClient>;
let fromScenarioNameOnce = false;

Expand Down Expand Up @@ -54,7 +55,12 @@ class DiagnosticSession extends DiagnosticConnectionBase implements IDiagnosticC
}

async connectNewClient() {
this.diagClient = await nextJsClient.promise;
if (startupJsClient) {
this.diagClient = startupJsClient;
startupJsClient = undefined;
} else {
this.diagClient = await nextJsClient.promise;
}
initializeJsClient();
const firstCommand = this.diagClient.commandOnAdvertise();
this.respond(firstCommand);
Expand Down Expand Up @@ -136,29 +142,37 @@ class DiagnosticSession extends DiagnosticConnectionBase implements IDiagnosticC

export function initializeJsClient() {
nextJsClient = dotnetLoaderExports.createPromiseCompletionSource<IDiagnosticClient>();
startupJsClient = undefined;
}

export function setupJsClient(client: IDiagnosticClient) {
if (!dotnetLoaderExports.isRuntimeRunning()) {
export function setupJsClient(client: IDiagnosticClient, startup?: boolean) {
if (!startup && !dotnetLoaderExports.isRuntimeRunning()) {
throw new Error("Runtime is not running");
}
if (nextJsClient.isDone) {
throw new Error("multiple clients in parallel are not allowed");
if (startup) {
if (startupJsClient) {
throw new Error("startup diagnostic client already registered");
}
startupJsClient = client;
} else {
if (nextJsClient.isDone) {
throw new Error("multiple clients in parallel are not allowed");
}
nextJsClient.resolve(client);
}
Comment thread
pavelsavara marked this conversation as resolved.
nextJsClient.resolve(client);
}

export function createDiagConnectionJs(socketHandle: number, scenarioName: string): DiagnosticSession {
if (!fromScenarioNameOnce) {
fromScenarioNameOnce = true;
if (scenarioName.startsWith("js://gcdump")) {
collectGcDump({});
collectGcDump({}, true);
}
if (scenarioName.startsWith("js://counters")) {
collectMetrics({});
collectMetrics({}, true);
}
if (scenarioName.startsWith("js://cpu-samples")) {
collectCpuSamples({});
collectCpuSamples({}, true);
}
const dotnetDiagnosticClient: FnClientProvider = (globalThis as any).dotnetDiagnosticClient;
if (typeof dotnetDiagnosticClient === "function") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,12 @@ class DiagnosticConnectionWS extends DiagnosticConnectionBase implements IDiagno
return super.store(message);
}

this.ws!.send(message as any);
try {
this.ws!.send(message as any);
} catch {
dotnetLogger.warn("Diagnostic server WebSocket connection failed unexpectedly.");
Comment thread
pavelsavara marked this conversation as resolved.
return -1;
}

return message.length;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,11 @@ export function connectDSRouter(url: string): void {
}

export function initializeDS() {
/* WASM-TODO, do this only when <EnableDiagnostics>true</EnableDiagnostics>
const loaderConfig = dotnetApi.getConfig();
const diagnosticPorts = "DOTNET_DiagnosticPorts";
if (!loaderConfig.environmentVariables![diagnosticPorts]) {
loaderConfig.environmentVariables ??= {};
if (loaderConfig.environmentVariables![diagnosticPorts] === undefined) {
loaderConfig.environmentVariables![diagnosticPorts] = "js://ready";
}
*/
initializeJsClient();
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,20 @@

import type { DiagnosticCommandOptions } from "../types";

import { commandStopTracing, commandCounters } from "./client-commands";
import { commandResumeRuntime, commandStopTracing, commandCounters } from "./client-commands";
import { dotnetLoaderExports, Module } from "./cross-module";
import { serverSession, setupJsClient } from "./diagnostic-server-js";
import { IDiagnosticSession } from "./types";

export function collectMetrics(options?: DiagnosticCommandOptions): Promise<Uint8Array[]> {
export function collectMetrics(options?: DiagnosticCommandOptions, startup?: boolean): Promise<Uint8Array[]> {
if (!options) options = {};
if (!serverSession) {
if (!startup && !serverSession) {
throw new Error("No active JS diagnostic session");
}

const onClosePromise = dotnetLoaderExports.createPromiseCompletionSource<Uint8Array[]>();
function onSessionStart(session: IDiagnosticSession): void {
session.sendCommand(commandResumeRuntime());
// stop tracing after period of monitoring
Module.safeSetTimeout(() => {
session.sendCommand(commandStopTracing(session.sessionId));
Expand All @@ -26,6 +27,6 @@ export function collectMetrics(options?: DiagnosticCommandOptions): Promise<Uint
skipDownload: options.skipDownload,
commandOnAdvertise: () => commandCounters(options),
onSessionStart,
});
}, startup);
return onClosePromise.promise;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@

import type { DiagnosticCommandOptions } from "../types";

import { commandStopTracing, commandSampleProfiler } from "./client-commands";
import { commandResumeRuntime, commandStopTracing, commandSampleProfiler } from "./client-commands";
import { dotnetApi, dotnetLoaderExports, Module } from "./cross-module";
import { serverSession, setupJsClient } from "./diagnostic-server-js";
import { IDiagnosticSession } from "./types";

export function collectCpuSamples(options?: DiagnosticCommandOptions): Promise<Uint8Array[]> {
export function collectCpuSamples(options?: DiagnosticCommandOptions, startup?: boolean): Promise<Uint8Array[]> {
if (!options) options = {};
if (!serverSession) {
if (!startup && !serverSession) {
throw new Error("No active JS diagnostic session");
}
if (!dotnetApi.getConfig().environmentVariables!["DOTNET_WasmPerformanceInstrumentation"]) {
Expand All @@ -19,6 +19,7 @@ export function collectCpuSamples(options?: DiagnosticCommandOptions): Promise<U

const onClosePromise = dotnetLoaderExports.createPromiseCompletionSource<Uint8Array[]>();
function onSessionStart(session: IDiagnosticSession): void {
session.sendCommand(commandResumeRuntime());
// stop tracing after period of monitoring
Module.safeSetTimeout(() => {
session.sendCommand(commandStopTracing(session.sessionId));
Expand All @@ -30,6 +31,6 @@ export function collectCpuSamples(options?: DiagnosticCommandOptions): Promise<U
skipDownload: options.skipDownload,
commandOnAdvertise: () => commandSampleProfiler(options),
onSessionStart,
});
}, startup);
return onClosePromise.promise;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,27 @@

import type { DiagnosticCommandOptions } from "../types";

import { commandStopTracing, commandGcHeapDump, } from "./client-commands";
import { commandResumeRuntime, commandStopTracing, commandGcHeapDump, } from "./client-commands";
import { dotnetLoaderExports, Module } from "./cross-module";
import { serverSession, setupJsClient } from "./diagnostic-server-js";
import { IDiagnosticSession } from "./types";

export function collectGcDump(options?: DiagnosticCommandOptions): Promise<Uint8Array[]> {
export function collectGcDump(options?: DiagnosticCommandOptions, startup?: boolean): Promise<Uint8Array[]> {
if (!options) options = {};
if (!serverSession) {
if (!startup && !serverSession) {
throw new Error("No active JS diagnostic session");
}

const onClosePromise = dotnetLoaderExports.createPromiseCompletionSource<Uint8Array[]>();
let stopDelayedAfterLastMessage = 0;
let stopSent = false;
function onSessionStart(session: IDiagnosticSession): void {
session.sendCommand(commandResumeRuntime());
}
function onData(session: IDiagnosticSession, message: Uint8Array): void {
session.store(message);
if (!stopSent) {
// stop 1000ms after last GC message on this session, there will be more messages after that
// stop durationSeconds (default 1s) after last GC message on this session, there will be more messages after that
if (stopDelayedAfterLastMessage) {
clearTimeout(stopDelayedAfterLastMessage);
}
Expand All @@ -35,7 +38,8 @@ export function collectGcDump(options?: DiagnosticCommandOptions): Promise<Uint8
onClosePromise: onClosePromise,
skipDownload: options.skipDownload,
commandOnAdvertise: () => commandGcHeapDump(options),
onSessionStart,
onData,
});
}, startup);
return onClosePromise.promise;
}
20 changes: 18 additions & 2 deletions src/native/libs/System.Native.Browser/diagnostics/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

import type { DiagnosticsExportsTable, InternalExchange, DiagnosticsExports } from "./types";
import type { DiagnosticsExportsTable, InternalExchange, DiagnosticsExports, CharPtr } from "./types";
import { InternalExchangeIndex } from "../types";

import GitHash from "consts:gitHash";

import { dotnetApi, dotnetUpdateInternals, dotnetUpdateInternalsSubscriber } from "./cross-module";
import { ENVIRONMENT_IS_WEB } from "./per-module";
import { dotnetApi, dotnetUpdateInternals, dotnetUpdateInternalsSubscriber, Module } from "./cross-module";
import { registerExit } from "./exit";
import { installNativeSymbols, symbolicateStackTrace } from "./symbolicate";
import { installLoggingProxy } from "./console-proxy";
Expand All @@ -24,6 +25,19 @@ export function dotnetInitializeModule(internals: InternalExchange): void {
if (runtimeApi.runtimeBuildInfo.gitHash && runtimeApi.runtimeBuildInfo.gitHash !== GitHash) {
throw new Error(`Mismatched git hashes between loader and runtime. Loader: ${runtimeApi.runtimeBuildInfo.gitHash}, Diagnostics: ${GitHash}`);
}
const ds_rt_browser_performance_measure =
globalThis.performance && typeof globalThis.performance.measure === "function"
? (namePtr: CharPtr, start: number) => {
try {
const fnName = Module.UTF8ToString(namePtr);
// NodeJs accepts startTime, browsers accepts start
const options = ENVIRONMENT_IS_WEB ? { start: start } : { startTime: start };
globalThis.performance.measure(fnName, options);
} catch {
// Ignore
}
}
: () => { };

internals[InternalExchangeIndex.DiagnosticsExportsTable] = diagnosticsExportsToTable({
symbolicateStackTrace,
Expand All @@ -33,6 +47,7 @@ export function dotnetInitializeModule(internals: InternalExchange): void {
ds_rt_websocket_poll,
ds_rt_websocket_recv,
ds_rt_websocket_close,
ds_rt_browser_performance_measure,
});
dotnetUpdateInternals(internals, dotnetUpdateInternalsSubscriber);

Expand All @@ -56,6 +71,7 @@ export function dotnetInitializeModule(internals: InternalExchange): void {
map.ds_rt_websocket_poll,
map.ds_rt_websocket_recv,
map.ds_rt_websocket_close,
map.ds_rt_browser_performance_measure,
];
}
}
4 changes: 4 additions & 0 deletions src/native/libs/System.Native.Browser/native/diagnostics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,7 @@ export function ds_rt_websocket_recv(clientSocket: number, buffer: VoidPtr, byte
export function ds_rt_websocket_close(clientSocket: number): number {
return dotnetDiagnosticsExports.ds_rt_websocket_close(clientSocket);
}

export function ds_rt_browser_performance_measure(namePtr: CharPtr, start: number): void {
return dotnetDiagnosticsExports.ds_rt_browser_performance_measure(namePtr, start);
}
2 changes: 1 addition & 1 deletion src/native/libs/System.Native.Browser/native/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export { SystemJS_RandomBytes } from "./crypto";
export { SystemJS_GetLocaleInfo } from "./globalization-locale";
export { SystemJS_RejectMainPromise, SystemJS_ResolveMainPromise, SystemJS_MarkAsyncMain, SystemJS_ConsoleClear } from "./main";
export { SystemJS_ScheduleTimer, SystemJS_ScheduleBackgroundJob, SystemJS_ScheduleFinalization, SystemJS_ScheduleDiagnosticServer } from "./scheduling";
export { ds_rt_websocket_close, ds_rt_websocket_create, ds_rt_websocket_poll, ds_rt_websocket_recv, ds_rt_websocket_send } from "./diagnostics";
export { ds_rt_websocket_close, ds_rt_websocket_create, ds_rt_websocket_poll, ds_rt_websocket_recv, ds_rt_websocket_send, ds_rt_browser_performance_measure } from "./diagnostics";


export const gitHash = GitHash;
Expand Down
6 changes: 5 additions & 1 deletion src/native/rollup.config.plugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,11 @@ export function onwarn(warning) {
if (warning.code === "CIRCULAR_DEPENDENCY" && warning.ids.findIndex(id => {
return id.includes("marshal-to-cs")
|| id.includes("marshal-to-js")
|| id.includes("diagnostics-js");
|| id.includes("diagnostics-js")
|| id.includes("dotnet-gcdump")
|| id.includes("dotnet-cpu-profiler")
|| id.includes("dotnet-counters")
|| id.includes("diagnostic-server-js");
}) !== -1) {
// ignore circular dependency warnings from marshal-to-cs <-> marshal-to-js and diagnostics
return;
Expand Down
Loading