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
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,19 @@
<ItemGroup>
<WasmExtraFilesToDeploy Include="index.html" />
<WasmExtraFilesToDeploy Include="mock.js" Condition="'$(MonoDiagnosticsMock)' == 'true'"/>
<WasmExtraConfig Condition="true" Include="environmentVariables" Value='
{
"MONO_LOG_LEVEL": "warning",
"MONO_LOG_MASK": "all"
}' />
<!-- this option requires running dotnet-dsrouter and a real dotnet-trace client -->
<WasmExtraConfig Condition="true and '$(MonoDiagnosticsMock)' != 'true'" Include="diagnosticOptions" Value='
<WasmExtraConfig Condition="'$(MonoDiagnosticsMock)' != 'true'" Include="environmentVariables" Value='
{
"server": { "suspend": true, "connectUrl": "ws://localhost:8088/diagnostics" }
"MONO_LOG_LEVEL": "warning",
"MONO_LOG_MASK": "all",
"DOTNET_DiagnosticPorts": "ws://localhost:8088/diagnostics,suspend"
}' />
<!-- this option requires compiling the runtime with /p:MonoDiagnosticsMock=true and also building this project with the same property-->
<WasmExtraConfig Condition="true and '$(MonoDiagnosticsMock)' == 'true'" Include="diagnosticOptions" Value='
<WasmExtraConfig Condition="'$(MonoDiagnosticsMock)' == 'true'" Include="environmentVariables" Value='
{
"server": { "suspend": false, "connectUrl": "mock:./mock.js" }
}' />
<!-- this option will create an EventPipe session at startup, that will dump its data into the Emscripten VFS -->
<WasmExtraConfig Condition="false" Include="diagnosticOptions" Value='
{
"sessions": [ { "collectRundownEvents": "true", "providers": "WasmHello::5:EventCounterIntervalSec=1" } ]
"MONO_LOG_LEVEL": "warning",
"MONO_LOG_MASK": "all",
"DOTNET_DiagnosticPorts": "mock:./mock.js,suspend"
}' />
</ItemGroup>

Expand Down
4 changes: 3 additions & 1 deletion src/mono/wasm/runtime/cwraps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ const fn_signatures: SigLine[] = [

//INTERNAL
[false, "mono_wasm_exit", "void", ["number"]],
[true, "mono_wasm_getenv", "number", ["string"]],
[true, "mono_wasm_set_main_args", "void", ["number", "number"]],
[false, "mono_wasm_enable_on_demand_gc", "void", ["number"]],
[false, "mono_profiler_init_aot", "void", ["number"]],
Expand Down Expand Up @@ -189,6 +190,7 @@ export interface t_Cwraps {

//INTERNAL
mono_wasm_exit(exit_code: number): number;
mono_wasm_getenv(name: string): CharPtr;
mono_wasm_enable_on_demand_gc(enable: number): void;
mono_wasm_set_main_args(argc: number, argv: VoidPtr): void;
mono_profiler_init_aot(desc: string): void;
Expand Down Expand Up @@ -232,4 +234,4 @@ export function init_c_exports(): void {
wf[name] = fce;
}
}
}
}
87 changes: 84 additions & 3 deletions src/mono/wasm/runtime/diagnostics/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,25 @@ export const diagnostics: Diagnostics = getDiagnostics();
let suspendOnStartup = false;
let diagnosticsServerEnabled = false;

export async function mono_wasm_init_diagnostics(options: DiagnosticOptions): Promise<void> {
let diagnosticsInitialized = false;

export async function mono_wasm_init_diagnostics(opts: "env" | DiagnosticOptions): Promise<void> {
Copy link
Member

Choose a reason for hiding this comment

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

is the signature with DiagnosticOptions param type still useful ?

Copy link
Member Author

Choose a reason for hiding this comment

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

No. I want to do a follow up PR to simplify more of the diagnostics support. removing this argument is just one piece.

if (diagnosticsInitialized)
return;
if (!monoWasmThreads) {
console.warn("MONO_WASM: ignoring diagnostics options because this runtime does not support diagnostics", options);
console.warn("MONO_WASM: ignoring diagnostics options because this runtime does not support diagnostics", opts);
return;
} else {
if (!is_nullish(options.server)) {
let options: DiagnosticOptions | null;
if (opts === "env") {
options = diagnostic_options_from_environment();
if (!options)
return;
} else {
options = opts;
}
diagnosticsInitialized = true;
if (!is_nullish(options?.server)) {
if (options.server.connectUrl === undefined || typeof (options.server.connectUrl) !== "string") {
throw new Error("server.connectUrl must be a string");
}
Expand Down Expand Up @@ -130,6 +143,74 @@ function boolsyOption(x: string | boolean): boolean {
throw new Error(`invalid option: "${x}", should be true, false, or "true" or "false"`);
}

/// Parse environment variables for diagnostics configuration
///
/// The environment variables are:
/// * DOTNET_DiagnosticPorts
///
function diagnostic_options_from_environment(): DiagnosticOptions | null {
const val = memory.getEnv("DOTNET_DiagnosticPorts");
if (is_nullish(val))
return null;
// TODO: consider also parsing the DOTNET_EnableEventPipe and DOTNET_EventPipeOutputPath, DOTNET_EvnetPipeConfig variables
// to configure the startup sessions that will dump output to the VFS.
return diagnostic_options_from_ports_spec(val);
}

/// Parse a DOTNET_DiagnosticPorts string and return a DiagnosticOptions object.
/// See https://docs.microsoft.com/en-us/dotnet/core/diagnostics/diagnostic-port#configure-additional-diagnostic-ports
function diagnostic_options_from_ports_spec(val: string): DiagnosticOptions | null {
if (val === "")
return null;
const ports = val.split(";");
if (ports.length === 0)
return null;
if (ports.length !== 1) {
console.warn("MONO_WASM: multiple diagnostic ports specified, only the last one will be used");
}
const portSpec = ports[ports.length - 1];
const components = portSpec.split(",");
if (components.length < 1 || components.length > 3) {
console.warn("MONO_WASM: invalid diagnostic port specification, should be of the form <port>[,<connect>],[<nosuspend|suspend>]");
return null;
}
const uri: string = components[0];
let connect = true;
let suspend = true;
// the C Diagnostic Server goes through these parts in reverse, do the same here.
for (let i = components.length - 1; i >= 1; i--) {
const component = components[i];
switch (component.toLowerCase()) {
case "nosuspend":
suspend = false;
break;
case "suspend":
suspend = true;
break;
case "listen":
connect = false;
break;
case "connect":
connect = true;
break;
default:
console.warn(`MONO_WASM: invalid diagnostic port specification component: ${component}`);
break;
}
}
if (!connect) {
console.warn("MONO_WASM: this runtime does not support listening on a diagnostic port; no diagnostic server started");
return null;
}
return {
server: {
connectUrl: uri,
suspend: suspend,
}
};

}

export function mono_wasm_diagnostic_server_on_runtime_server_init(out_options: VoidPtr): void {
if (diagnosticsServerEnabled) {
/* called on the main thread when the runtime is sufficiently initialized */
Expand Down
6 changes: 6 additions & 0 deletions src/mono/wasm/runtime/driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,12 @@ mono_wasm_setenv (const char *name, const char *value)
monoeg_g_setenv (strdup (name), strdup (value), 1);
}

EMSCRIPTEN_KEEPALIVE char *
mono_wasm_getenv (const char *name)
{
return monoeg_g_getenv (name); // JS must free
}

static void *sysglobal_native_handle;

static void*
Expand Down
16 changes: 14 additions & 2 deletions src/mono/wasm/runtime/memory.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import monoWasmThreads from "consts:monoWasmThreads";
import { Module, runtimeHelpers } from "./imports";
import { mono_assert, MemOffset, NumberOrPointer } from "./types";
import { VoidPtr } from "./types/emscripten";
import { VoidPtr, CharPtr } from "./types/emscripten";
import * as cuint64 from "./cuint64";
import cwraps, { I52Error } from "./cwraps";

Expand Down Expand Up @@ -263,6 +263,18 @@ export function mono_wasm_load_bytes_into_heap(bytes: Uint8Array): VoidPtr {
return memoryOffset;
}

export function getEnv(name: string): string | null {
let charPtr: CharPtr = <any>0;
try {
charPtr = cwraps.mono_wasm_getenv(name);
if (<any>charPtr === 0)
return null;
else return Module.UTF8ToString(charPtr);
} finally {
if (charPtr) Module._free(<any>charPtr);
}
}

const BuiltinAtomics = globalThis.Atomics;

export const Atomics = monoWasmThreads ? {
Expand All @@ -276,4 +288,4 @@ export const Atomics = monoWasmThreads ? {
} : {
storeI32: setI32,
notifyI32: () => { /*empty*/ }
};
};
18 changes: 13 additions & 5 deletions src/mono/wasm/runtime/startup.ts
Original file line number Diff line number Diff line change
@@ -1,6 +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 MonoWasmThreads from "consts:monoWasmThreads";
import { mono_assert, CharPtrNull, DotnetModule, MonoConfig, MonoConfigError, LoadingResource, AssetEntry, ResourceRequest } from "./types";
import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_SHELL, INTERNAL, Module, runtimeHelpers } from "./imports";
import cwraps, { init_c_exports } from "./cwraps";
Expand All @@ -25,6 +26,7 @@ import { cwraps_internal } from "./exports-internal";
import { cwraps_binding_api, cwraps_mono_api } from "./net6-legacy/exports-legacy";
import { DotnetPublicAPI } from "./export-types";
import { BINDING, MONO } from "./net6-legacy/imports";
import { mono_wasm_init_diagnostics } from "./diagnostics";

let all_assets_loaded_in_memory: Promise<void> | null = null;
const loaded_files: { url: string, file: string }[] = [];
Expand Down Expand Up @@ -130,8 +132,8 @@ function preInit(isCustomStartup: boolean, userPreInit: (() => void)[]) {
abort_startup(err, true);
throw err;
}
// this will start immediately but return on first await.
// It will block our `preRun` by afterPreInit promise
// this will start immediately but return on first await.
// It will block our `preRun` by afterPreInit promise
// It will block emscripten `userOnRuntimeInitialized` by pending addRunDependency("mono_pre_init")
(async () => {
try {
Expand Down Expand Up @@ -196,7 +198,7 @@ async function onRuntimeInitializedAsync(isCustomStartup: boolean, userOnRuntime
_print_error("MONO_WASM: user callback onRuntimeInitialized() failed", err);
throw err;
}
// finish
// finish
await mono_wasm_after_user_runtime_initialized();
} catch (err) {
_print_error("MONO_WASM: onRuntimeInitializedAsync() failed", err);
Expand Down Expand Up @@ -326,6 +328,10 @@ async function mono_wasm_after_user_runtime_initialized(): Promise<void> {
}
}
}
// for Blazor, init diagnostics after their "onRuntimeInitalized" sets env variables, but before their postRun callback (which calls mono_wasm_load_runtime)
if (MonoWasmThreads) {
Copy link
Member

Choose a reason for hiding this comment

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

maybe you could now move it earlier in the startup, just after beforeOnRuntimeInitialized.promise_control.resolve. but I'm not sure it's useful.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'll move it. the only hard requirement is that the diagnostic server has to start before we call into driver.c to start the root domain initialization. Starting later is better

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, there's an annoying problem here. In the non-Blazor startup we don't set environment variables until _apply_configuration_from_args. So actually I have to try to init diagnostics in two places.

await mono_wasm_init_diagnostics("env");
}

if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: Initializing mono runtime");

Expand Down Expand Up @@ -532,8 +538,10 @@ async function _apply_configuration_from_args() {

if (config.coverageProfilerOptions)
mono_wasm_init_coverage_profiler(config.coverageProfilerOptions);

// FIXME await mono_wasm_init_diagnostics(config.diagnosticOptions);
// for non-Blazor, init diagnostics after environment variables are set
if (MonoWasmThreads) {
await mono_wasm_init_diagnostics("env");
}
}

export function mono_wasm_load_runtime(unused?: string, debugLevel?: number): void {
Expand Down