diff --git a/eng/liveBuilds.targets b/eng/liveBuilds.targets index 79ad14fb9fca24..cb8b9dbb6f9105 100644 --- a/eng/liveBuilds.targets +++ b/eng/liveBuilds.targets @@ -209,6 +209,8 @@ $(LibrariesNativeArtifactsPath)dotnet.native.js; $(LibrariesNativeArtifactsPath)dotnet.runtime.js; $(LibrariesNativeArtifactsPath)dotnet.runtime.js.map; + $(LibrariesNativeArtifactsPath)dotnet.diagnostics.js; + $(LibrariesNativeArtifactsPath)dotnet.diagnostics.js.map; $(LibrariesNativeArtifactsPath)dotnet.d.ts; $(LibrariesNativeArtifactsPath)package.json; $(LibrariesNativeArtifactsPath)dotnet.native.wasm; diff --git a/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props b/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props index 910aa76c2c28f4..80932ca681b896 100644 --- a/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props +++ b/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props @@ -224,6 +224,8 @@ + + diff --git a/src/mono/browser/browser.proj b/src/mono/browser/browser.proj index e487f605f29a40..b82bfdf21a5ad8 100644 --- a/src/mono/browser/browser.proj +++ b/src/mono/browser/browser.proj @@ -340,6 +340,8 @@ { "identity": "WasmSingleFileBundle", "defaultValueInRuntimePack": "$(WasmSingleFileBundle)" }, { "identity": "WasmEnableSIMD", "defaultValueInRuntimePack": "$(WasmEnableSIMD)" }, { "identity": "WasmEnableExceptionHandling", "defaultValueInRuntimePack": "$(WasmEnableExceptionHandling)" }, + { "identity": "FeaturePerfTracing", "defaultValueInRuntimePack": "$(FeaturePerfTracing)" }, + { "identity": "WasmProfilers", "defaultValueInRuntimePack": "$(WasmProfilers)" }, { "identity": "EmccMaximumHeapSize", "defaultValueInRuntimePack": "$(EmccMaximumHeapSize)" } ] } @@ -497,6 +499,8 @@ $(NativeBinDir)dotnet.js.map; $(NativeBinDir)dotnet.runtime.js; $(NativeBinDir)dotnet.runtime.js.map; + $(NativeBinDir)dotnet.diagnostics.js; + $(NativeBinDir)dotnet.diagnostics.js.map; $(NativeBinDir)dotnet.native.js; $(NativeBinDir)dotnet.d.ts; $(NativeBinDir)package.json; diff --git a/src/mono/browser/build/BrowserWasmApp.targets b/src/mono/browser/build/BrowserWasmApp.targets index b5b474c5562e27..bb7c499c91967b 100644 --- a/src/mono/browser/build/BrowserWasmApp.targets +++ b/src/mono/browser/build/BrowserWasmApp.targets @@ -3,7 +3,7 @@ true $(WasmEnableExceptionHandling) - true + false @@ -90,6 +90,7 @@ + @@ -101,6 +102,8 @@ Condition="'$(WasmEmitSourceMap)' != 'false'" /> + @@ -113,6 +116,7 @@ + boolean), name: string, returnType: string | null, argTypes?: string[], opts?: any]; const threading_cwraps: SigLine[] = WasmEnableThreads ? [ - // MONO.diagnostics [false, "mono_wasm_init_finalizer_thread", null, []], [false, "mono_wasm_invoke_jsexport_async_post", "void", ["number", "number", "number"]], [false, "mono_wasm_invoke_jsexport_sync_send", "void", ["number", "number", "number"]], @@ -139,7 +138,6 @@ const fn_signatures: SigLine[] = [ ]; export interface t_ThreadingCwraps { - // MONO.diagnostics mono_wasm_init_finalizer_thread(): void; mono_wasm_invoke_jsexport_async_post(targetTID: PThreadPtr, method: MonoMethod, args: VoidPtr): void; mono_wasm_invoke_jsexport_sync_send(targetTID: PThreadPtr, method: MonoMethod, args: VoidPtr): void; diff --git a/src/mono/browser/runtime/diagnostics.ts b/src/mono/browser/runtime/diagnostics.ts new file mode 100644 index 00000000000000..4d8675c90e5670 --- /dev/null +++ b/src/mono/browser/runtime/diagnostics.ts @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +import type { CharPtr, VoidPtr } from "./types/emscripten"; + +import { diagnosticHelpers } from "./globals"; + +export function ds_rt_websocket_create (urlPtr :CharPtr):number { + return diagnosticHelpers.ds_rt_websocket_create(urlPtr); +} + +export function ds_rt_websocket_send (client_socket :number, buffer:VoidPtr, bytes_to_write:number):number { + return diagnosticHelpers.ds_rt_websocket_send(client_socket, buffer, bytes_to_write); +} + +export function ds_rt_websocket_poll (client_socket :number):number { + return diagnosticHelpers.ds_rt_websocket_poll(client_socket); +} + +export function ds_rt_websocket_recv (client_socket :number, buffer:VoidPtr, bytes_to_read:number):number { + return diagnosticHelpers.ds_rt_websocket_recv(client_socket, buffer, bytes_to_read); +} + +export function ds_rt_websocket_close (client_socket :number):number { + return diagnosticHelpers.ds_rt_websocket_close(client_socket); +} diff --git a/src/mono/browser/runtime/diagnostics/globals.ts b/src/mono/browser/runtime/diagnostics/globals.ts new file mode 100644 index 00000000000000..61a6b940dce3da --- /dev/null +++ b/src/mono/browser/runtime/diagnostics/globals.ts @@ -0,0 +1,23 @@ + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +import type { DiagnosticHelpers, GlobalObjects, LoaderHelpers, RuntimeHelpers, DotnetModuleInternal } from "../types/internal"; + +export let _diagnosticModuleLoaded = false; // please keep it in place also as rollup guard + +export let diagnosticHelpers: DiagnosticHelpers = null as any; +export let runtimeHelpers: RuntimeHelpers = null as any; +export let loaderHelpers: LoaderHelpers = null as any; +export let Module: DotnetModuleInternal = null as any; + +export function setRuntimeGlobalsImpl (globalObjects: GlobalObjects): void { + if (_diagnosticModuleLoaded) { + throw new Error("Diag module already loaded"); + } + _diagnosticModuleLoaded = true; + diagnosticHelpers = globalObjects.diagnosticHelpers; + runtimeHelpers = globalObjects.runtimeHelpers; + loaderHelpers = globalObjects.loaderHelpers; + Module = globalObjects.module; +} diff --git a/src/mono/browser/runtime/diagnostics/index.ts b/src/mono/browser/runtime/diagnostics/index.ts new file mode 100644 index 00000000000000..8ee5274257e5f6 --- /dev/null +++ b/src/mono/browser/runtime/diagnostics/index.ts @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +import type { GlobalObjects } from "../types/internal"; +import type { CharPtr, VoidPtr } from "../types/emscripten"; + +import { diagnosticHelpers, setRuntimeGlobalsImpl } from "./globals"; + +/* eslint-disable @typescript-eslint/no-unused-vars */ +export function setRuntimeGlobals (globalObjects: GlobalObjects): void { + setRuntimeGlobalsImpl(globalObjects); + + diagnosticHelpers.ds_rt_websocket_create = (urlPtr :CharPtr):number => { + throw new Error("Not implemented"); + }; + + diagnosticHelpers.ds_rt_websocket_send = (client_socket :number, buffer:VoidPtr, bytes_to_write:number):number => { + throw new Error("Not implemented"); + }; + + diagnosticHelpers.ds_rt_websocket_poll = (client_socket :number):number => { + throw new Error("Not implemented"); + }; + + diagnosticHelpers.ds_rt_websocket_recv = (client_socket :number, buffer:VoidPtr, bytes_to_read:number):number => { + throw new Error("Not implemented"); + }; + + diagnosticHelpers. ds_rt_websocket_close = (client_socket :number):number => { + throw new Error("Not implemented"); + }; +} diff --git a/src/mono/browser/runtime/diagnostics/logging.ts b/src/mono/browser/runtime/diagnostics/logging.ts new file mode 100644 index 00000000000000..727f5a2ff4d71b --- /dev/null +++ b/src/mono/browser/runtime/diagnostics/logging.ts @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +import { loaderHelpers } from "./globals"; + +/* eslint-disable no-console */ + +const prefix = "MONO_WASM: "; + +export function mono_log_debug (messageFactory: string | (() => string)) { + if (loaderHelpers.diagnosticTracing) { + const message = (typeof messageFactory === "function" + ? messageFactory() + : messageFactory); + console.debug(prefix + message); + } +} + +export function mono_log_info (msg: string, ...data: any) { + console.info(prefix + msg, ...data); +} + +export function mono_log_warn (msg: string, ...data: any) { + console.warn(prefix + msg, ...data); +} + +export function mono_log_error (msg: string, ...data: any) { + if (data && data.length > 0 && data[0] && typeof data[0] === "object") { + // don't log silent errors + if (data[0].silent) { + return; + } + if (data[0].toString) { + console.error(prefix + msg, data[0].toString()); + return; + } + } + console.error(prefix + msg, ...data); +} diff --git a/src/mono/browser/runtime/dotnet.d.ts b/src/mono/browser/runtime/dotnet.d.ts index 1a9ba31bb58186..1485010132be76 100644 --- a/src/mono/browser/runtime/dotnet.d.ts +++ b/src/mono/browser/runtime/dotnet.d.ts @@ -256,7 +256,7 @@ interface ResourceGroups { corePdb?: ResourceList; pdb?: ResourceList; jsModuleWorker?: ResourceList; - jsModuleGlobalization?: ResourceList; + jsModuleDiagnostics?: ResourceList; jsModuleNative: ResourceList; jsModuleRuntime: ResourceList; wasmSymbols?: ResourceList; @@ -360,6 +360,10 @@ type SingleAssetBehaviors = * The javascript module for threads. */ | "js-module-threads" +/** + * The javascript module for diagnostic server and client. + */ + | "js-module-diagnostics" /** * The javascript module for runtime. */ diff --git a/src/mono/browser/runtime/es6/dotnet.es6.lib.js b/src/mono/browser/runtime/es6/dotnet.es6.lib.js index 2ff1c066d6cd48..306d858b59855f 100644 --- a/src/mono/browser/runtime/es6/dotnet.es6.lib.js +++ b/src/mono/browser/runtime/es6/dotnet.es6.lib.js @@ -78,7 +78,7 @@ function createWasmImportStubsFrom(collection) { // we will replace them with the real implementation in replace_linker_placeholders function injectDependencies() { createWasmImportStubsFrom(methodIndexByName.mono_wasm_imports); - createWasmImportStubsFrom(methodIndexByName.mono_wasm_js_globalization_imports); + if (FEATURE_PERFTRACING) createWasmImportStubsFrom(methodIndexByName.mono_wasm_diagnostic_imports); #if USE_PTHREADS createWasmImportStubsFrom(methodIndexByName.mono_wasm_threads_imports); diff --git a/src/mono/browser/runtime/exports-binding.ts b/src/mono/browser/runtime/exports-binding.ts index 29d5eead5f5958..d7175ac9ee634a 100644 --- a/src/mono/browser/runtime/exports-binding.ts +++ b/src/mono/browser/runtime/exports-binding.ts @@ -13,7 +13,6 @@ import { mono_wasm_resolve_or_reject_promise } from "./marshal-to-js"; import { mono_wasm_schedule_timer, schedule_background_exec } from "./scheduling"; import { mono_wasm_asm_loaded } from "./startup"; import { mono_log_warn, mono_wasm_console_clear, mono_wasm_trace_logger } from "./logging"; -import { mono_wasm_profiler_record, mono_wasm_profiler_now, ds_rt_websocket_close, ds_rt_websocket_create, ds_rt_websocket_poll, ds_rt_websocket_recv, ds_rt_websocket_send } from "./profiler"; import { mono_wasm_browser_entropy } from "./crypto"; import { mono_wasm_cancel_promise } from "./cancelable-promise"; @@ -24,7 +23,10 @@ import { } from "./pthreads"; import { mono_wasm_dump_threads } from "./pthreads/ui-thread"; import { mono_wasm_schedule_synchronization_context } from "./pthreads/shared"; -import { mono_wasm_js_globalization_imports } from "./globalization"; +import { mono_wasm_get_locale_info } from "./globalization-locale"; + +import { mono_wasm_profiler_record, mono_wasm_profiler_now } from "./profiler"; +import { ds_rt_websocket_create, ds_rt_websocket_send, ds_rt_websocket_poll, ds_rt_websocket_recv, ds_rt_websocket_close } from "./diagnostics"; // the JS methods would be visible to EMCC linker and become imports of the WASM module @@ -48,6 +50,15 @@ export const mono_wasm_threads_imports = !WasmEnableThreads ? [] : [ mono_wasm_warn_about_blocking_wait, ]; +export const mono_wasm_diagnostic_imports = [ + //event pipe + ds_rt_websocket_create, + ds_rt_websocket_send, + ds_rt_websocket_poll, + ds_rt_websocket_recv, + ds_rt_websocket_close, +]; + export const mono_wasm_imports = [ // mini-wasm.c mono_wasm_schedule_timer, @@ -70,6 +81,7 @@ export const mono_wasm_imports = [ mono_interp_flush_jitcall_queue, mono_wasm_free_method_data, + // browser.c mono_wasm_profiler_now, mono_wasm_profiler_record, @@ -88,22 +100,16 @@ export const mono_wasm_imports = [ mono_wasm_invoke_jsimport_ST, mono_wasm_resolve_or_reject_promise, mono_wasm_cancel_promise, - - //event pipe - ds_rt_websocket_create, - ds_rt_websocket_send, - ds_rt_websocket_poll, - ds_rt_websocket_recv, - ds_rt_websocket_close, + mono_wasm_get_locale_info, ]; - +// !!! Keep in sync with exports-linker.ts const wasmImports: Function[] = [ ...mono_wasm_imports, + // diagnostic server exports, when enabled + ...mono_wasm_diagnostic_imports, // threading exports, if threading is enabled ...mono_wasm_threads_imports, - // globalization exports - ...mono_wasm_js_globalization_imports, ]; export function replace_linker_placeholders (imports: WebAssembly.Imports) { diff --git a/src/mono/browser/runtime/exports-linker.ts b/src/mono/browser/runtime/exports-linker.ts index 6eda7b34ae1b82..5c24cc97b0b9f8 100644 --- a/src/mono/browser/runtime/exports-linker.ts +++ b/src/mono/browser/runtime/exports-linker.ts @@ -1,15 +1,14 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { mono_wasm_imports, mono_wasm_threads_imports } from "./exports-binding"; +import { mono_wasm_diagnostic_imports, mono_wasm_imports, mono_wasm_threads_imports } from "./exports-binding"; import gitHash from "consts:gitHash"; -import { mono_wasm_js_globalization_imports } from "./globalization"; export function export_linker_indexes_as_code (): string { const indexByName: any = { mono_wasm_imports: {}, mono_wasm_threads_imports: {}, - mono_wasm_js_globalization_imports: {}, + mono_wasm_diagnostic_imports: {}, }; let idx = 0; for (const wi of mono_wasm_imports) { @@ -20,8 +19,8 @@ export function export_linker_indexes_as_code (): string { indexByName.mono_wasm_threads_imports[wi.name] = idx; idx++; } - for (const wi of mono_wasm_js_globalization_imports) { - indexByName.mono_wasm_js_globalization_imports[wi.name] = idx; + for (const wi of mono_wasm_diagnostic_imports) { + indexByName.mono_wasm_diagnostic_imports[wi.name] = idx; idx++; } return ` diff --git a/src/mono/browser/runtime/exports.ts b/src/mono/browser/runtime/exports.ts index 23d6d30e28800f..3dc0117767161b 100644 --- a/src/mono/browser/runtime/exports.ts +++ b/src/mono/browser/runtime/exports.ts @@ -25,6 +25,7 @@ import { forceDisposeProxies } from "./gc-handles"; import { mono_wasm_dump_threads } from "./pthreads"; import { threads_c_functions as tcwraps } from "./cwraps"; +import { utf8ToString } from "./strings"; export let runtimeList: RuntimeList; @@ -40,7 +41,9 @@ function initializeExports (globalObjects: GlobalObjects): RuntimeAPI { instantiate_asset, jiterpreter_dump_stats, forceDisposeProxies, - + utf8ToString, + mono_background_exec: () => tcwraps.mono_background_exec(), + mono_wasm_ds_exec: () => tcwraps.mono_wasm_ds_exec(), }; if (WasmEnableThreads) { rh.dumpThreads = mono_wasm_dump_threads; diff --git a/src/mono/browser/runtime/globalization.ts b/src/mono/browser/runtime/globalization.ts deleted file mode 100644 index c7fb8eeafce9d5..00000000000000 --- a/src/mono/browser/runtime/globalization.ts +++ /dev/null @@ -1,10 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -import { mono_wasm_get_locale_info } from "./globalization-locale"; - -// JS-based globalization support for WebAssembly - -export const mono_wasm_js_globalization_imports = [ - mono_wasm_get_locale_info, -]; diff --git a/src/mono/browser/runtime/globals.ts b/src/mono/browser/runtime/globals.ts index e1eeed5ddce1d5..8ba8d54110b406 100644 --- a/src/mono/browser/runtime/globals.ts +++ b/src/mono/browser/runtime/globals.ts @@ -8,8 +8,8 @@ import gitHash from "consts:gitHash"; -import { RuntimeAPI } from "./types/index"; -import type { GlobalObjects, EmscriptenInternals, RuntimeHelpers, LoaderHelpers, DotnetModuleInternal, PromiseAndController, EmscriptenBuildOptions, GCHandle } from "./types/internal"; +import type { RuntimeAPI } from "./types/index"; +import type { GlobalObjects, EmscriptenInternals, RuntimeHelpers, LoaderHelpers, DotnetModuleInternal, PromiseAndController, EmscriptenBuildOptions, GCHandle, DiagnosticHelpers } from "./types/internal"; import { mono_log_error } from "./logging"; // these are our public API (except internal) @@ -29,6 +29,8 @@ export let ENVIRONMENT_IS_PTHREAD: boolean; export let exportedRuntimeAPI: RuntimeAPI = null as any; export let runtimeHelpers: RuntimeHelpers = null as any; export let loaderHelpers: LoaderHelpers = null as any; +export let diagnosticHelpers: DiagnosticHelpers = null as any; +export let globalObjectsRoot: GlobalObjects = null as any; export let _runtimeModuleLoaded = false; // please keep it in place also as rollup guard @@ -49,10 +51,12 @@ export function setRuntimeGlobals (globalObjects: GlobalObjects) { throw new Error("Runtime module already loaded"); } _runtimeModuleLoaded = true; + globalObjectsRoot = globalObjects; Module = globalObjects.module; INTERNAL = globalObjects.internal; runtimeHelpers = globalObjects.runtimeHelpers; loaderHelpers = globalObjects.loaderHelpers; + diagnosticHelpers = globalObjects.diagnosticHelpers; exportedRuntimeAPI = globalObjects.api; const rh: Partial = { diff --git a/src/mono/browser/runtime/loader/assets.ts b/src/mono/browser/runtime/loader/assets.ts index e749fb42afb783..7ef9caec78204a 100644 --- a/src/mono/browser/runtime/loader/assets.ts +++ b/src/mono/browser/runtime/loader/assets.ts @@ -32,6 +32,7 @@ const jsRuntimeModulesAssetTypes: { "js-module-runtime": true, "js-module-dotnet": true, "js-module-native": true, + "js-module-diagnostics": true, }; const jsModulesAssetTypes: { @@ -119,16 +120,10 @@ function set_single_asset (asset: AssetEntryInternal) { } } -function get_single_asset (behavior: SingleAssetBehaviors): AssetEntryInternal { +export function try_resolve_single_asset_path (behavior: SingleAssetBehaviors): AssetEntryInternal|undefined { mono_assert(singleAssetTypes[behavior], `Unknown single asset behavior ${behavior}`); const asset = singleAssets.get(behavior); - mono_assert(asset, `Single asset for ${behavior} not found`); - return asset; -} - -export function resolve_single_asset_path (behavior: SingleAssetBehaviors): AssetEntryInternal { - const asset = get_single_asset(behavior); - if (!asset.resolvedUrl) { + if (asset && !asset.resolvedUrl) { asset.resolvedUrl = loaderHelpers.locateFile(asset.name); if (jsRuntimeModulesAssetTypes[asset.behavior]) { @@ -147,6 +142,12 @@ export function resolve_single_asset_path (behavior: SingleAssetBehaviors): Asse return asset; } +export function resolve_single_asset_path (behavior: SingleAssetBehaviors): AssetEntryInternal { + const asset = try_resolve_single_asset_path(behavior); + mono_assert(asset, `Single asset for ${behavior} not found`); + return asset; +} + let downloadAssetsStarted = false; export async function mono_download_assets (): Promise { if (downloadAssetsStarted) { @@ -303,6 +304,9 @@ export function prepareAssets () { convert_single_asset(assetsToLoad, resources.wasmNative, "dotnetwasm"); convert_single_asset(modulesAssets, resources.jsModuleNative, "js-module-native"); convert_single_asset(modulesAssets, resources.jsModuleRuntime, "js-module-runtime"); + if (resources.jsModuleDiagnostics) { + convert_single_asset(modulesAssets, resources.jsModuleDiagnostics, "js-module-diagnostics"); + } if (WasmEnableThreads) { convert_single_asset(modulesAssets, resources.jsModuleWorker, "js-module-threads"); } @@ -828,6 +832,7 @@ export async function streamingCompileWasm () { loaderHelpers.wasmCompilePromise.promise_control.reject(err); } } + export function preloadWorkers () { if (!WasmEnableThreads) return; const jsModuleWorker = resolve_single_asset_path("js-module-threads"); diff --git a/src/mono/browser/runtime/loader/config.ts b/src/mono/browser/runtime/loader/config.ts index 3165672e6257e6..528ce84bdd8dc2 100644 --- a/src/mono/browser/runtime/loader/config.ts +++ b/src/mono/browser/runtime/loader/config.ts @@ -71,6 +71,9 @@ function deep_merge_resources (target: ResourceGroups, source: ResourceGroups): if (providedResources.jsModuleNative !== undefined) { providedResources.jsModuleNative = { ...(target.jsModuleNative || {}), ...(providedResources.jsModuleNative || {}) }; } + if (providedResources.jsModuleDiagnostics !== undefined) { + providedResources.jsModuleDiagnostics = { ...(target.jsModuleDiagnostics || {}), ...(providedResources.jsModuleDiagnostics || {}) }; + } if (providedResources.jsModuleRuntime !== undefined) { providedResources.jsModuleRuntime = { ...(target.jsModuleRuntime || {}), ...(providedResources.jsModuleRuntime || {}) }; } @@ -167,6 +170,9 @@ export function normalizeConfig () { case "js-module-native": toMerge.jsModuleNative = resource; break; + case "js-module-diagnostics": + toMerge.jsModuleDiagnostics = resource; + break; case "js-module-dotnet": // don't merge loader break; diff --git a/src/mono/browser/runtime/loader/globals.ts b/src/mono/browser/runtime/loader/globals.ts index 5ef69d0e2e8ced..962d8631cd7c7f 100644 --- a/src/mono/browser/runtime/loader/globals.ts +++ b/src/mono/browser/runtime/loader/globals.ts @@ -8,7 +8,7 @@ import { exceptions, simd } from "wasm-feature-detect"; import gitHash from "consts:gitHash"; -import type { DotnetModuleInternal, GlobalObjects, LoaderHelpers, MonoConfigInternal, PThreadWorker, RuntimeHelpers } from "../types/internal"; +import type { DiagnosticHelpers, DotnetModuleInternal, GlobalObjects, LoaderHelpers, MonoConfigInternal, PThreadWorker, RuntimeHelpers } from "../types/internal"; import type { MonoConfig, RuntimeAPI } from "../types"; import { assert_runtime_running, installUnhandledErrorHandler, is_exited, is_runtime_running, mono_exit } from "./exit"; import { assertIsControllablePromise, createPromiseController, getPromiseController } from "./promise-controller"; @@ -33,6 +33,7 @@ export const ENVIRONMENT_IS_SHELL = !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_NODE; export let runtimeHelpers: RuntimeHelpers = {} as any; export let loaderHelpers: LoaderHelpers = {} as any; +export let diagnosticHelpers: DiagnosticHelpers = {} as any; export let exportedRuntimeAPI: RuntimeAPI = {} as any; export let INTERNAL: any = {}; export let _loaderModuleLoaded = false; // please keep it in place also as rollup guard @@ -48,6 +49,7 @@ export const globalObjectsRoot: GlobalObjects = { module: emscriptenModule, loaderHelpers, runtimeHelpers, + diagnosticHelpers: diagnosticHelpers, api: exportedRuntimeAPI, } as any; @@ -62,6 +64,7 @@ export function setLoaderGlobals ( _loaderModuleLoaded = true; runtimeHelpers = globalObjects.runtimeHelpers; loaderHelpers = globalObjects.loaderHelpers; + diagnosticHelpers = globalObjects.diagnosticHelpers; exportedRuntimeAPI = globalObjects.api; INTERNAL = globalObjects.internal; Object.assign(exportedRuntimeAPI, { diff --git a/src/mono/browser/runtime/loader/run.ts b/src/mono/browser/runtime/loader/run.ts index dc507d794d3767..b915ddc11ea834 100644 --- a/src/mono/browser/runtime/loader/run.ts +++ b/src/mono/browser/runtime/loader/run.ts @@ -4,13 +4,13 @@ import BuildConfiguration from "consts:configuration"; import { type MonoConfig, type DotnetHostBuilder, type DotnetModuleConfig, type RuntimeAPI, type LoadBootResourceCallback } from "../types"; -import type { EmscriptenModuleInternal, RuntimeModuleExportsInternal, NativeModuleExportsInternal } from "../types/internal"; +import type { EmscriptenModuleInternal, RuntimeModuleExportsInternal, NativeModuleExportsInternal, DiagnosticModuleExportsInternal } from "../types/internal"; import { ENVIRONMENT_IS_WEB, ENVIRONMENT_IS_WORKER, emscriptenModule, exportedRuntimeAPI, globalObjectsRoot, monoConfig, mono_assert } from "./globals"; import { deep_merge_config, deep_merge_module, mono_wasm_load_config } from "./config"; import { installUnhandledErrorHandler, mono_exit, registerEmscriptenExitHandlers } from "./exit"; import { setup_proxy_console, mono_log_info, mono_log_debug } from "./logging"; -import { mono_download_assets, preloadWorkers, prepareAssets, prepareAssetsWorker, resolve_single_asset_path, streamingCompileWasm } from "./assets"; +import { mono_download_assets, preloadWorkers, prepareAssets, prepareAssetsWorker, resolve_single_asset_path, streamingCompileWasm, try_resolve_single_asset_path } from "./assets"; import { detect_features_and_polyfill } from "./polyfills"; import { runtimeHelpers, loaderHelpers } from "./globals"; import { init_globalization } from "./icu"; @@ -434,13 +434,14 @@ export async function createEmscripten (moduleFactory: DotnetModuleConfig | ((ap let jsModuleRuntimePromise: Promise; let jsModuleNativePromise: Promise; +let jsModuleDiagnosticPromise: Promise; // in the future we can use feature detection to load different flavors function importModules () { const jsModuleRuntimeAsset = resolve_single_asset_path("js-module-runtime"); const jsModuleNativeAsset = resolve_single_asset_path("js-module-native"); if (jsModuleRuntimePromise && jsModuleNativePromise) { - return [jsModuleRuntimePromise, jsModuleNativePromise]; + return [jsModuleRuntimePromise, jsModuleNativePromise, jsModuleDiagnosticPromise]; } if (typeof jsModuleRuntimeAsset.moduleExports === "object") { @@ -456,14 +457,30 @@ function importModules () { mono_log_debug(() => `Attempting to import '${jsModuleNativeAsset.resolvedUrl}' for ${jsModuleNativeAsset.name}`); jsModuleNativePromise = import(/*! webpackIgnore: true */jsModuleNativeAsset.resolvedUrl!); } - return [jsModuleRuntimePromise, jsModuleNativePromise]; + + const jsModuleDiagnosticAsset = try_resolve_single_asset_path("js-module-diagnostics"); + if (jsModuleDiagnosticAsset) { + if (typeof jsModuleDiagnosticAsset.moduleExports === "object") { + jsModuleDiagnosticPromise = jsModuleDiagnosticAsset.moduleExports; + } else { + mono_log_debug(() => `Attempting to import '${jsModuleDiagnosticAsset.resolvedUrl}' for ${jsModuleDiagnosticAsset.name}`); + jsModuleDiagnosticPromise = import(/*! webpackIgnore: true */jsModuleDiagnosticAsset.resolvedUrl!); + } + } + + return [jsModuleRuntimePromise, jsModuleNativePromise, jsModuleDiagnosticPromise]; } -async function initializeModules (es6Modules: [RuntimeModuleExportsInternal, NativeModuleExportsInternal]) { +async function initializeModules (es6Modules: [RuntimeModuleExportsInternal, NativeModuleExportsInternal, DiagnosticModuleExportsInternal?]) { const { initializeExports, initializeReplacements, configureRuntimeStartup, configureEmscriptenStartup, configureWorkerStartup, setRuntimeGlobals, passEmscriptenInternals } = es6Modules[0]; const { default: emscriptenFactory } = es6Modules[1]; + const diagnosticModule = es6Modules[2]; setRuntimeGlobals(globalObjectsRoot); initializeExports(globalObjectsRoot); + if (diagnosticModule) { + diagnosticModule.setRuntimeGlobals(globalObjectsRoot); + } + await configureRuntimeStartup(emscriptenModule); loaderHelpers.runtimeModuleLoaded.promise_control.resolve(); diff --git a/src/mono/browser/runtime/rollup.config.js b/src/mono/browser/runtime/rollup.config.js index c1e3874c052766..8147a3dc58779d 100644 --- a/src/mono/browser/runtime/rollup.config.js +++ b/src/mono/browser/runtime/rollup.config.js @@ -95,6 +95,11 @@ const checkNoRuntime = pattern: /_runtimeModuleLoaded/gm, failure: "module should not contain runtimeModuleLoaded member. This is probably duplicated code in the output caused by a dependency on the runtime module." }; +const checkNoDiagnostics = +{ + pattern: /_diagnosticModuleLoaded/gm, + failure: "module should not contain _diagnosticModuleLoaded member. This is probably duplicated code in the output caused by a dependency on the runtime module." +}; let gitHash; @@ -174,7 +179,7 @@ const loaderConfig = { } ], external: externalDependencies, - plugins: [nodeResolve(), regexReplace(inlineAssert), regexCheck([checkAssert, checkNoRuntime]), ...outputCodePlugins], + plugins: [nodeResolve(), regexReplace(inlineAssert), regexCheck([checkAssert, checkNoRuntime, checkNoDiagnostics]), ...outputCodePlugins], onwarn: onwarn }; const runtimeConfig = { @@ -191,7 +196,24 @@ const runtimeConfig = { } ], external: externalDependencies, - plugins: [regexReplace(inlineAssert), regexCheck([checkAssert, checkNoLoader]), ...outputCodePlugins], + plugins: [regexReplace(inlineAssert), regexCheck([checkAssert, checkNoLoader, checkNoDiagnostics]), ...outputCodePlugins], + onwarn: onwarn +}; +const diagConfig = { + treeshake: !isDebug, + input: "diagnostics/index.ts", + output: [ + { + format: "es", + file: nativeBinDir + "/dotnet.diagnostics.js", + banner, + plugins, + sourcemap: true, + sourcemapPathTransform, + } + ], + external: externalDependencies, + plugins: [regexReplace(inlineAssert), regexCheck([checkAssert, checkNoLoader, checkNoRuntime]), ...outputCodePlugins], onwarn: onwarn }; const wasmImportsConfig = { @@ -241,6 +263,7 @@ if (isDebug) { const allConfigs = [ loaderConfig, runtimeConfig, + diagConfig, wasmImportsConfig, typesConfig, ] diff --git a/src/mono/browser/runtime/types/index.ts b/src/mono/browser/runtime/types/index.ts index eb6c12a1b3e2e5..46a39a816d946a 100644 --- a/src/mono/browser/runtime/types/index.ts +++ b/src/mono/browser/runtime/types/index.ts @@ -211,7 +211,7 @@ export interface ResourceGroups { pdb?: ResourceList; jsModuleWorker?: ResourceList; - jsModuleGlobalization?: ResourceList; + jsModuleDiagnostics?: ResourceList; jsModuleNative: ResourceList; jsModuleRuntime: ResourceList; wasmSymbols?: ResourceList; @@ -318,6 +318,10 @@ export type SingleAssetBehaviors = * The javascript module for threads. */ | "js-module-threads" + /** + * The javascript module for diagnostic server and client. + */ + | "js-module-diagnostics" /** * The javascript module for runtime. */ diff --git a/src/mono/browser/runtime/types/internal.ts b/src/mono/browser/runtime/types/internal.ts index 8dd0e98d08e0a8..61e82dc1eceb66 100644 --- a/src/mono/browser/runtime/types/internal.ts +++ b/src/mono/browser/runtime/types/internal.ts @@ -243,14 +243,17 @@ export type RuntimeHelpers = { forceDisposeProxies: (disposeMethods: boolean, verbose: boolean) => void, dumpThreads: () => void, mono_wasm_print_thread_dump: () => void, + utf8ToString: (ptr: CharPtr) => string, + mono_background_exec: () =>void; + mono_wasm_ds_exec: () =>void; +} - stringToUTF16: (dstPtr: number, endPtr: number, text: string) => void, - stringToUTF16Ptr: (str: string) => VoidPtr, - utf16ToString: (startPtr: number, endPtr: number) => string, - utf16ToStringLoop: (startPtr: number, endPtr: number) => string, - localHeapViewU16: () => Uint16Array, - setU16_local: (heap: Uint16Array, ptr: number, value: number) => void, - setI32: (offset: MemOffset, value: number) => void, +export type DiagnosticHelpers = { + ds_rt_websocket_create:(urlPtr :CharPtr)=>number, + ds_rt_websocket_send:(client_socket :number, buffer:VoidPtr, bytes_to_write:number)=>number, + ds_rt_websocket_poll:(client_socket :number)=>number, + ds_rt_websocket_recv:(client_socket :number, buffer:VoidPtr, bytes_to_read:number)=>number, + ds_rt_websocket_close:(client_socket :number)=>number, } export type AOTProfilerOptions = { @@ -310,6 +313,7 @@ export type GlobalObjects = { module: DotnetModuleInternal, loaderHelpers: LoaderHelpers, runtimeHelpers: RuntimeHelpers, + diagnosticHelpers: DiagnosticHelpers, api: RuntimeAPI, }; export type EmscriptenReplacements = { @@ -505,6 +509,10 @@ export type RuntimeModuleExportsInternal = { passEmscriptenInternals: passEmscriptenInternalsType, } +export type DiagnosticModuleExportsInternal = { + setRuntimeGlobals: setGlobalObjectsType, +} + export type NativeModuleExportsInternal = { default: (unificator: Function) => Promise } diff --git a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets index 5e4f39455f76ad..17e6e261f7e93b 100644 --- a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets +++ b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets @@ -242,6 +242,7 @@ Copyright (c) .NET Foundation. All rights reserved. CopySymbols="$(_WasmCopyOutputSymbolsToOutputDirectory)" OutputPath="$(OutputPath)" EnableThreads="$(_WasmEnableThreads)" + FeaturePerfTracing="$(FeaturePerfTracing)" EmitSourceMap="$(_WasmEmitSourceMapBuild)" FingerprintAssets="$(_WasmFingerprintAssets)" FingerprintDotnetJs="$(_WasmFingerprintDotnetJs)" @@ -479,6 +480,7 @@ Copyright (c) .NET Foundation. All rights reserved. CopySymbols="$(CopyOutputSymbolsToPublishDirectory)" ExistingAssets="@(_WasmPublishPrefilteredAssets)" EnableThreads="$(_WasmEnableThreads)" + FeaturePerfTracing="$(FeaturePerfTracing)" EmitSourceMap="$(_WasmEmitSourceMapPublish)" IsWebCilEnabled="$(_WasmEnableWebcil)" FingerprintAssets="$(_WasmFingerprintAssets)" diff --git a/src/mono/sample/wasm/Directory.Build.targets b/src/mono/sample/wasm/Directory.Build.targets index 207f85efedf1e2..30db0e4f88953c 100644 --- a/src/mono/sample/wasm/Directory.Build.targets +++ b/src/mono/sample/wasm/Directory.Build.targets @@ -18,6 +18,7 @@ <_ServeMimeTypes>$(_ServeMimeTypes) --mime .cjs=text/javascript <_ServeMimeTypes>$(_ServeMimeTypes) --mime .js=text/javascript <_ServeMimeTypes>$(_ServeMimeTypes) --mime .webcil=application/octet-stream + true + + + diff --git a/src/mono/wasi/wasi.proj b/src/mono/wasi/wasi.proj index 1cd944f822832d..47a270575245e5 100644 --- a/src/mono/wasi/wasi.proj +++ b/src/mono/wasi/wasi.proj @@ -164,6 +164,8 @@ { "identity": "InvariantGlobalization", "defaultValueInRuntimePack": "$(InvariantGlobalization)" }, { "identity": "WasmNativeStrip", "defaultValueInRuntimePack": "$(WasmNativeStrip)" }, { "identity": "WasmSingleFileBundle", "defaultValueInRuntimePack": "$(WasmSingleFileBundle)" }, + { "identity": "FeaturePerfTracing", "defaultValueInRuntimePack": "$(FeaturePerfTracing)" }, + { "identity": "WasmProfilers", "defaultValueInRuntimePack": "$(WasmProfilers)" }, { "identity": "WasmEnableSIMD", "defaultValueInRuntimePack": "$(WasmEnableSIMD)" } ] } diff --git a/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/BuildOptions.cs b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/BuildOptions.cs index d573405986a9cd..76a9053d9de493 100644 --- a/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/BuildOptions.cs +++ b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/BuildOptions.cs @@ -25,7 +25,8 @@ public BuildOptions( IDictionary? ExtraBuildEnvironmentVariables = null, string BootConfigFileName = "blazor.boot.json", string NonDefaultFrameworkDir = "", - string ExtraMSBuildArgs = "" + string ExtraMSBuildArgs = "", + bool FeaturePerfTracing = false ) : base( IsPublish, AOT, @@ -42,7 +43,8 @@ public BuildOptions( ExtraBuildEnvironmentVariables, BootConfigFileName, NonDefaultFrameworkDir, - ExtraMSBuildArgs + ExtraMSBuildArgs, + FeaturePerfTracing ) { } diff --git a/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/MSBuildOptions.cs b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/MSBuildOptions.cs index 24ae96b0745816..13506f5eee71e9 100644 --- a/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/MSBuildOptions.cs +++ b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/MSBuildOptions.cs @@ -24,5 +24,6 @@ public abstract record MSBuildOptions IDictionary? ExtraBuildEnvironmentVariables = null, string BootConfigFileName = "blazor.boot.json", string NonDefaultFrameworkDir = "", - string ExtraMSBuildArgs = "" + string ExtraMSBuildArgs = "", + bool FeaturePerfTracing = false ); diff --git a/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/PublishOptions.cs b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/PublishOptions.cs index 4eda155ad5d6f4..8d8d543d1bcc52 100644 --- a/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/PublishOptions.cs +++ b/src/mono/wasm/Wasm.Build.Tests/BrowserStructures/PublishOptions.cs @@ -30,7 +30,8 @@ public PublishOptions( string NonDefaultFrameworkDir = "", string ExtraMSBuildArgs = "", bool BuildOnlyAfterPublish = true, - bool ExpectRelinkDirWhenPublishing = false + bool ExpectRelinkDirWhenPublishing = false, + bool FeaturePerfTracing = false ) : base( IsPublish, AOT, @@ -47,7 +48,8 @@ public PublishOptions( ExtraBuildEnvironmentVariables, BootConfigFileName, NonDefaultFrameworkDir, - ExtraMSBuildArgs + ExtraMSBuildArgs, + FeaturePerfTracing ) { this.IsPublish = IsPublish; diff --git a/src/mono/wasm/Wasm.Build.Tests/DiagnosticsTests.cs b/src/mono/wasm/Wasm.Build.Tests/DiagnosticsTests.cs new file mode 100644 index 00000000000000..a56b88c9e86732 --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/DiagnosticsTests.cs @@ -0,0 +1,44 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Text.RegularExpressions; +using Xunit.Abstractions; +using Xunit; + +#nullable enable + +namespace Wasm.Build.Tests; + +public class DiagnosticsTests : WasmTemplateTestsBase +{ + public DiagnosticsTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) + : base(output, buildContext) + { + } + + [Fact] + public async Task RunSimpleAppWithProfiler() + { + Configuration config = Configuration.Release; + ProjectInfo info = CopyTestAsset(config, false, TestAsset.WasmBasicTestApp, "ProfilerTest"); + // are are linking all 3 profilers, but below we only initialize log profiler and test it + string extraArgs = $"-p:WasmProfilers=\"aot+browser+log\""; + BuildProject(info, config, new BuildOptions(ExtraMSBuildArgs: extraArgs, AssertAppBundle: false, FeaturePerfTracing: true), isNativeBuild: true); + + var result = await RunForBuildWithDotnetRun(new BrowserRunOptions(Configuration: config, TestScenario: "ProfilerTest")); + Regex regex = new Regex(@"Profile data of size (\d+) bytes"); + var match = result.TestOutput + .Select(line => regex.Match(line)) + .FirstOrDefault(m => m.Success); + Assert.True(match != null, $"TestOutput did not contain log matching {regex}"); + if (!int.TryParse(match.Groups[1].Value, out int fileSize)) + { + Assert.Fail($"Failed to parse profile size from {match.Groups[1].Value} to int"); + } + Assert.True(fileSize >= 10 * 1024, $"Profile file size is less than 10KB. Actual size: {fileSize} bytes."); + } +} diff --git a/src/mono/wasm/Wasm.Build.Tests/MemoryTests.cs b/src/mono/wasm/Wasm.Build.Tests/MemoryTests.cs index 39d7f5be61c5d8..1b0697d7704483 100644 --- a/src/mono/wasm/Wasm.Build.Tests/MemoryTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/MemoryTests.cs @@ -45,26 +45,4 @@ await RunForBuildWithDotnetRun(new BrowserRunOptions( )); } } - - [Fact] - public async Task RunSimpleAppWithProfiler() - { - Configuration config = Configuration.Release; - ProjectInfo info = CopyTestAsset(config, false, TestAsset.WasmBasicTestApp, "ProfilerTest"); - // are are linking all 3 profilers, but below we only initialize log profiler and test it - string extraArgs = $"-p:WasmProfilers=\"aot+browser+log\" -p:WasmBuildNative=true"; - BuildProject(info, config, new BuildOptions(ExtraMSBuildArgs: extraArgs, AssertAppBundle: false), isNativeBuild: true); - - var result = await RunForBuildWithDotnetRun(new BrowserRunOptions(Configuration: config, TestScenario: "ProfilerTest")); - Regex regex = new Regex(@"Profile data of size (\d+) bytes"); - var match = result.TestOutput - .Select(line => regex.Match(line)) - .FirstOrDefault(m => m.Success); - Assert.True(match != null, $"TestOuptup did not contain log matching {regex}"); - if (!int.TryParse(match.Groups[1].Value, out int fileSize)) - { - Assert.Fail($"Failed to parse profile size from {match.Groups[1].Value} to int"); - } - Assert.True(fileSize >= 10 * 1024, $"Profile file size is less than 10KB. Actual size: {fileSize} bytes."); - } } diff --git a/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs b/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs index 56cd08e08eef7f..979823780f2349 100644 --- a/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/ProjectProviderBase.cs @@ -83,8 +83,10 @@ public IReadOnlyDictionary FindAndAssertDotnetFiles(Asse EnsureProjectDirIsSet(); return FindAndAssertDotnetFiles(binFrameworkDir: assertOptions.BinFrameworkDir, expectFingerprintOnDotnetJs: IsFingerprintingOnDotnetJsEnabled, + assertOptions, superSet: GetAllKnownDotnetFilesToFingerprintMap(assertOptions), - expected: GetDotNetFilesExpectedSet(assertOptions)); + expected: GetDotNetFilesExpectedSet(assertOptions) + ); } protected abstract IReadOnlyDictionary GetAllKnownDotnetFilesToFingerprintMap(AssertBundleOptions assertOptions); @@ -93,15 +95,15 @@ public IReadOnlyDictionary FindAndAssertDotnetFiles(Asse public IReadOnlyDictionary FindAndAssertDotnetFiles( string binFrameworkDir, bool expectFingerprintOnDotnetJs, + AssertBundleOptions assertOptions, IReadOnlyDictionary superSet, - IReadOnlySet? expected) + IReadOnlySet expected) { EnsureProjectDirIsSet(); var actual = new SortedDictionary(); if (!Directory.Exists(binFrameworkDir)) throw new XunitException($"Could not find bundle directory {binFrameworkDir}"); - IList dotnetFiles = Directory.EnumerateFiles(binFrameworkDir, "dotnet.*", SearchOption.TopDirectoryOnly) @@ -155,15 +157,19 @@ public IReadOnlyDictionary FindAndAssertDotnetFiles( { throw new XunitException($"Found unknown files in {binFrameworkDir}:{Environment.NewLine} " + $"{string.Join($"{Environment.NewLine} ", dotnetFiles.Select(f => Path.GetRelativePath(binFrameworkDir, f)))}{Environment.NewLine}" + - $"Add these to {nameof(GetAllKnownDotnetFilesToFingerprintMap)} method"); + $"Add these to {nameof(GetAllKnownDotnetFilesToFingerprintMap)} method{Environment.NewLine}" + + $"Expected {string.Join($"{Environment.NewLine} ", expected)}{Environment.NewLine}" + + $"Options {assertOptions} {Environment.NewLine}" + ); } if (expected is not null) - AssertDotNetFilesSet(expected, superSet, actual, expectFingerprintOnDotnetJs, binFrameworkDir); + AssertDotNetFilesSet(assertOptions, expected, superSet, actual, expectFingerprintOnDotnetJs, binFrameworkDir); return actual; } private void AssertDotNetFilesSet( + AssertBundleOptions assertOptions, IReadOnlySet expected, IReadOnlyDictionary superSet, IReadOnlyDictionary actualReadOnly, @@ -177,7 +183,7 @@ private void AssertDotNetFilesSet( { bool expectFingerprint = superSet[expectedFilename]; - Assert.True(actual.ContainsKey(expectedFilename), $"Could not find {expectedFilename} in bundle directory: {bundleDir}. Actual files on disk: {string.Join(", ", actual.Keys)}"); + Assert.True(actual.ContainsKey(expectedFilename), $"Could not find {expectedFilename} in bundle directory: {bundleDir}. Actual files on disk: {string.Join(", ", actual.Keys)} Options {assertOptions}"); // Check that the version and hash are present or not present as expected if (ShouldCheckFingerprint(expectedFilename: expectedFilename, @@ -185,12 +191,12 @@ private void AssertDotNetFilesSet( expectFingerprintForThisFile: expectFingerprint)) { if (string.IsNullOrEmpty(actual[expectedFilename].Hash)) - throw new XunitException($"Expected hash in filename: {actual[expectedFilename].ActualPath}"); + throw new XunitException($"Expected hash in filename: {actual[expectedFilename].ActualPath} Options {assertOptions}"); } else { if (!string.IsNullOrEmpty(actual[expectedFilename].Hash)) - throw new XunitException($"Expected no hash in filename: {actual[expectedFilename].ActualPath}"); + throw new XunitException($"Expected no hash in filename: {actual[expectedFilename].ActualPath} Options {assertOptions}"); } actual.Remove(expectedFilename); } @@ -198,7 +204,7 @@ private void AssertDotNetFilesSet( if (actual.Any()) { var actualFileNames = actual.Values.Select(x => x.ActualPath).Order(); - throw new XunitException($"Found unexpected files: {string.Join(", ", actualFileNames)}"); + throw new XunitException($"Found unexpected files: {string.Join(", ", actualFileNames)} Options {assertOptions}"); } } @@ -508,7 +514,7 @@ public BootJsonData AssertBootJson(AssertBundleOptions options) .Union(bootJson.resources.wasmNative.Keys) .Union(bootJson.resources.jsModuleRuntime.Keys) .Union(bootJson.resources.jsModuleWorker?.Keys ?? Enumerable.Empty()) - .Union(bootJson.resources.jsModuleGlobalization?.Keys ?? Enumerable.Empty()) + .Union(bootJson.resources.jsModuleDiagnostics?.Keys ?? Enumerable.Empty()) .Union(bootJson.resources.wasmSymbols?.Keys ?? Enumerable.Empty()) .ToArray(); diff --git a/src/mono/wasm/Wasm.Build.Tests/WasmSdkBasedProjectProvider.cs b/src/mono/wasm/Wasm.Build.Tests/WasmSdkBasedProjectProvider.cs index 89b99ca922fe10..184d094ea82dec 100644 --- a/src/mono/wasm/Wasm.Build.Tests/WasmSdkBasedProjectProvider.cs +++ b/src/mono/wasm/Wasm.Build.Tests/WasmSdkBasedProjectProvider.cs @@ -35,6 +35,8 @@ protected override IReadOnlyDictionary GetAllKnownDotnetFilesToFin { "dotnet.native.worker.mjs", true }, { "dotnet.runtime.js", true }, { "dotnet.runtime.js.map", false }, + { "dotnet.diagnostics.js", true }, + { "dotnet.diagnostics.js.map", false }, }; protected override IReadOnlySet GetDotNetFilesExpectedSet(AssertBundleOptions assertOptions) @@ -60,6 +62,13 @@ protected override IReadOnlySet GetDotNetFilesExpectedSet(AssertBundleOp if (assertOptions.AssertSymbolsFile && assertOptions.ExpectSymbolsFile) res.Add("dotnet.native.js.symbols"); + if (assertOptions.BuildOptions.FeaturePerfTracing) + { + res.Add("dotnet.diagnostics.js"); + if (!assertOptions.BuildOptions.IsPublish) + res.Add("dotnet.diagnostics.js.map"); + } + return res; } diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/AssetsComputingHelper.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/AssetsComputingHelper.cs index ecbc3e9c6b631b..b4188444d901dd 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/AssetsComputingHelper.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/AssetsComputingHelper.cs @@ -25,6 +25,11 @@ public class AssetsComputingHelper "dotnet.runtime" }; + private static readonly string[] dotnetJsDiagNames = new[] + { + "dotnet.diagnostics", + }; + private static readonly string[] icuShardsFromRuntimePack = new[] { "icudt_EFIGS", @@ -40,6 +45,7 @@ public static bool ShouldFilterCandidate( bool copySymbols, string customIcuCandidateFilename, bool enableThreads, + bool featurePerfTracing, bool emitSourceMap, out string reason) { @@ -66,9 +72,15 @@ public static bool ShouldFilterCandidate( ".dat" when IsDefaultIcuMode() && !(icuShardsFromRuntimePack.Any(f => f == fileName)) => "automatic icu shard selection, based on application culture, is enabled", ".json" when fromMonoPackage && (fileName == "wasm-props" || fileName == "package") => $"{fileName}{extension} is not used by Blazor", ".ts" when fromMonoPackage && fileName == "dotnet.d" => "dotnet type definition is not used by Blazor", - ".map" when !emitSourceMap && fromMonoPackage && (fileName == "dotnet.js" || fileName == "dotnet.runtime.js") => "source map file is not published", + ".map" when emitSourceMap && fromMonoPackage && (fileName == "dotnet.js" || fileName == "dotnet.runtime.js") => null, + ".map" when emitSourceMap && fromMonoPackage && featurePerfTracing && fileName == "dotnet.diagnostics.js" => null, + ".map" when emitSourceMap && fromMonoPackage && !featurePerfTracing && fileName == "dotnet.diagnostics.js" => "perf tracing is not enabled", + ".map" when !emitSourceMap && fromMonoPackage => "source map file is not published", ".ts" when fromMonoPackage && fileName == "dotnet-legacy.d" => "dotnet type definition is not used by Blazor", - ".js" when assetType == "native" && !dotnetJsSingleThreadNames.Contains(fileName) => $"{fileName}{extension} is not used by Blazor", + ".js" when assetType == "native" && dotnetJsSingleThreadNames.Contains(fileName) => null, + ".js" when assetType == "native" && featurePerfTracing && dotnetJsDiagNames.Contains(fileName) => null, + ".js" when assetType == "native" && !featurePerfTracing && dotnetJsDiagNames.Contains(fileName) => "perf tracing is not enabled", + ".js" when assetType == "native" => $"{fileName}{extension} is not used by Blazor", ".mjs" when assetType == "native" && !(enableThreads && fileName == "dotnet.native.worker") => $"{fileName}{extension} is not used by Blazor", ".pdb" when !copySymbols => "copying symbols is disabled", ".symbols" when fromMonoPackage => "extension .symbols is not required.", @@ -115,6 +127,7 @@ public static string GetCandidateRelativePath(ITaskItem candidate, bool fingerpr { ("dotnet", ".js") => string.Concat(fileName, fingerprintDotNetJs ? requiredFingerprint : optionalFingerprint, extension), ("dotnet.runtime", ".js") => string.Concat(fileName, requiredFingerprint, extension), + ("dotnet.diagnostics", ".js") => string.Concat(fileName, requiredFingerprint, extension), ("dotnet.native", ".js") => string.Concat(fileName, requiredFingerprint, extension), ("dotnet.native.worker", ".mjs") => string.Concat(fileName, requiredFingerprint, extension), _ => string.Concat(fileName, extension) diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.cs index 7c6c0836f4a2ce..2077fb40d6e0fe 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonBuilderHelper.cs @@ -54,7 +54,7 @@ static void AddDictionary(StringBuilder sb, Dictionary? res) AddDictionary(sb, bootConfig.resources.coreAssembly); AddDictionary(sb, bootConfig.resources.jsModuleWorker); - AddDictionary(sb, bootConfig.resources.jsModuleGlobalization); + AddDictionary(sb, bootConfig.resources.jsModuleDiagnostics); AddDictionary(sb, bootConfig.resources.jsModuleNative); AddDictionary(sb, bootConfig.resources.jsModuleRuntime); AddDictionary(sb, bootConfig.resources.wasmNative); @@ -89,8 +89,8 @@ static void AddDictionary(StringBuilder sb, Dictionary? res) string resourceExtension = Path.GetExtension(resourceName); if (resourceName.StartsWith("dotnet.native.worker", StringComparison.OrdinalIgnoreCase) && string.Equals(resourceExtension, ".mjs", StringComparison.OrdinalIgnoreCase)) return bootConfig.resources.jsModuleWorker ??= new(); - if (resourceName.StartsWith("dotnet.globalization", StringComparison.OrdinalIgnoreCase) && string.Equals(resourceExtension, ".js", StringComparison.OrdinalIgnoreCase)) - return bootConfig.resources.jsModuleGlobalization ??= new(); + else if (resourceName.StartsWith("dotnet.diagnostics", StringComparison.OrdinalIgnoreCase) && string.Equals(resourceExtension, ".js", StringComparison.OrdinalIgnoreCase)) + return bootConfig.resources.jsModuleDiagnostics ??= new(); else if (resourceName.StartsWith("dotnet.native", StringComparison.OrdinalIgnoreCase) && string.Equals(resourceExtension, ".js", StringComparison.OrdinalIgnoreCase)) return bootConfig.resources.jsModuleNative ??= new(); else if (resourceName.StartsWith("dotnet.runtime", StringComparison.OrdinalIgnoreCase) && string.Equals(resourceExtension, ".js", StringComparison.OrdinalIgnoreCase)) diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs index 32e09c9826c109..4d3c57ff4580ff 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs @@ -141,7 +141,7 @@ public class ResourcesData public ResourceHashesByNameDictionary jsModuleWorker { get; set; } [DataMember(EmitDefaultValue = false)] - public ResourceHashesByNameDictionary jsModuleGlobalization { get; set; } + public ResourceHashesByNameDictionary jsModuleDiagnostics { get; set; } [DataMember(EmitDefaultValue = false)] public ResourceHashesByNameDictionary jsModuleNative { get; set; } diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ComputeWasmBuildAssets.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ComputeWasmBuildAssets.cs index a9e9b91cb4e9e1..7d64709ad58f2b 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ComputeWasmBuildAssets.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ComputeWasmBuildAssets.cs @@ -48,6 +48,8 @@ public class ComputeWasmBuildAssets : Task public bool EnableThreads { get; set; } + public bool FeaturePerfTracing { get; set; } + public bool EmitSourceMap { get; set; } public bool FingerprintAssets { get; set; } @@ -88,7 +90,7 @@ public override bool Execute() for (int i = 0; i < Candidates.Length; i++) { var candidate = Candidates[i]; - if (AssetsComputingHelper.ShouldFilterCandidate(candidate, TimeZoneSupport, InvariantGlobalization, LoadFullICUData, CopySymbols, customIcuCandidateFilename, EnableThreads, EmitSourceMap, out var reason)) + if (AssetsComputingHelper.ShouldFilterCandidate(candidate, TimeZoneSupport, InvariantGlobalization, LoadFullICUData, CopySymbols, customIcuCandidateFilename, EnableThreads, FeaturePerfTracing, EmitSourceMap, out var reason)) { Log.LogMessage(MessageImportance.Low, "Skipping asset '{0}' because '{1}'", candidate.ItemSpec, reason); filesToRemove.Add(candidate); diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ComputeWasmPublishAssets.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ComputeWasmPublishAssets.cs index 613301b7a722de..384e710b154a15 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ComputeWasmPublishAssets.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/ComputeWasmPublishAssets.cs @@ -54,6 +54,8 @@ public class ComputeWasmPublishAssets : Task public bool EnableThreads { get; set; } + public bool FeaturePerfTracing { get; set; } + public bool EmitSourceMap { get; set; } public bool IsWebCilEnabled { get; set; } @@ -220,6 +222,8 @@ private void ProcessNativeAssets( baseName = "dotnet.native"; else if (baseName.StartsWith("dotnet.runtime")) baseName = "dotnet.runtime"; + else if (baseName.StartsWith("dotnet.diagnostics")) + baseName = "dotnet.diagnostics"; else if (baseName.StartsWith("dotnet")) baseName = "dotnet"; @@ -659,7 +663,7 @@ private void GroupResolvedFilesToPublish( foreach (var candidate in resolvedFilesToPublish) { #pragma warning disable CA1864 // Prefer the 'IDictionary.TryAdd(TKey, TValue)' method. Dictionary.TryAdd() not available in .Net framework. - if (AssetsComputingHelper.ShouldFilterCandidate(candidate, TimeZoneSupport, InvariantGlobalization, LoadFullICUData, CopySymbols, customIcuCandidateFilename, EnableThreads, EmitSourceMap, out var reason)) + if (AssetsComputingHelper.ShouldFilterCandidate(candidate, TimeZoneSupport, InvariantGlobalization, LoadFullICUData, CopySymbols, customIcuCandidateFilename, EnableThreads, FeaturePerfTracing, EmitSourceMap, out var reason)) { Log.LogMessage(MessageImportance.Low, "Skipping asset '{0}' because '{1}'", candidate.ItemSpec, reason); if (!resolvedFilesToPublishToRemove.ContainsKey(candidate.ItemSpec)) diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs index 2befd468f50d6d..b6eaa0e170141c 100644 --- a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs +++ b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs @@ -169,7 +169,7 @@ protected override bool ExecuteInternal() if (!IncludeThreadsWorker && name == "dotnet.native.worker.mjs") continue; - if (name == "dotnet.runtime.js.map" || name == "dotnet.js.map") + if (name == "dotnet.runtime.js.map" || name == "dotnet.js.map" || name == "dotnet.diagnostics.js.map") { Log.LogMessage(MessageImportance.Low, $"Skipping {item.ItemSpec} from boot config"); continue;