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 07d77161002bc..b0e6a249f89ee 100644 --- a/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props +++ b/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props @@ -210,6 +210,7 @@ + diff --git a/src/mono/mono.proj b/src/mono/mono.proj index 38731e968660a..56bc7ebaa9179 100644 --- a/src/mono/mono.proj +++ b/src/mono/mono.proj @@ -905,6 +905,9 @@ <_MonoRuntimeArtifacts Condition="'$(TargetsBrowser)' == 'true' and '$(BuildMonoAOTCrossCompilerOnly)' != 'true'" Include="$(MonoObjDir)out\lib\libmono-profiler-aot.a"> $(RuntimeBinDir)libmono-profiler-aot.a + <_MonoRuntimeArtifacts Condition="'$(TargetsBrowser)' == 'true' and '$(BuildMonoAOTCrossCompilerOnly)' != 'true'" Include="$(MonoObjDir)out\lib\libmono-profiler-browser.a"> + $(RuntimeBinDir)libmono-profiler-browser.a + <_MonoRuntimeArtifacts Condition="'$(TargetsBrowser)' == 'true' and '$(BuildMonoAOTCrossCompilerOnly)' != 'true'" Include="$(MonoObjDir)out\lib\libmono-wasm-eh-js.a"> $(RuntimeBinDir)libmono-wasm-eh-js.a diff --git a/src/mono/mono/metadata/profiler.c b/src/mono/mono/metadata/profiler.c index fe1b6b594bf37..d632fc3b6f62e 100644 --- a/src/mono/mono/metadata/profiler.c +++ b/src/mono/mono/metadata/profiler.c @@ -22,6 +22,10 @@ typedef void (*MonoProfilerInitializer) (const char *); #define OLD_INITIALIZER_NAME "mono_profiler_startup" #define NEW_INITIALIZER_NAME "mono_profiler_init" +#if defined(TARGET_WASM) && defined(MONO_CROSS_COMPILE) +MONO_API void mono_profiler_init_browser (const char *desc); +#endif + static gboolean load_profiler (MonoDl *module, const char *name, const char *desc) { @@ -148,6 +152,9 @@ load_profiler_from_installation (const char *libname, const char *name, const ch * * This function may \b only be called by embedders prior to running managed * code. + * + * This could could be triggered by \c MONO_PROFILE env variable in normal mono process or + * by \c --profile=foo argument to mono-aot-cross.exe command line. */ void mono_profiler_load (const char *desc) @@ -171,6 +178,15 @@ mono_profiler_load (const char *desc) mname = g_strdup (desc); } +#if defined(TARGET_WASM) && defined(MONO_CROSS_COMPILE) + // this code could be running as part of mono-aot-cross.exe + // in case of WASM we staticaly link in the browser.c profiler plugin + if(strcmp (mname, "browser") == 0) { + mono_profiler_init_browser (desc); + goto done; + } +#endif + if (load_profiler_from_executable (mname, desc)) goto done; diff --git a/src/mono/mono/mini/CMakeLists.txt b/src/mono/mono/mini/CMakeLists.txt index 9e74fd9e16fb4..c68af8beb34ca 100644 --- a/src/mono/mono/mini/CMakeLists.txt +++ b/src/mono/mono/mini/CMakeLists.txt @@ -325,7 +325,13 @@ else() set(llvm_runtime_sources) endif() -set(mini_sources "main-core.c;${mini_common_sources};${arch_sources};${os_sources};${mini_interp_sources};${llvm_sources};${debugger_sources};${llvm_runtime_sources}") +if(TARGET_WASM AND MONO_CROSS_COMPILE) +set(profiler_sources ../profiler/browser.c) +else() +set(profiler_sources "") +endif() + +set(mini_sources "main-core.c;${mini_common_sources};${arch_sources};${os_sources};${mini_interp_sources};${llvm_sources};${debugger_sources};${profiler_sources};${llvm_runtime_sources}") if(LLVM_INCLUDEDIR) include_directories(BEFORE SYSTEM "${LLVM_INCLUDEDIR}") diff --git a/src/mono/mono/profiler/CMakeLists.txt b/src/mono/mono/profiler/CMakeLists.txt index 4f860a1c8267e..cd5bf36dfa67d 100644 --- a/src/mono/mono/profiler/CMakeLists.txt +++ b/src/mono/mono/profiler/CMakeLists.txt @@ -35,4 +35,11 @@ if(NOT DISABLE_LIBS) set_target_properties(mono-profiler-aot-static PROPERTIES OUTPUT_NAME mono-profiler-aot) install(TARGETS mono-profiler-aot-static LIBRARY) endif() + + if(HOST_WASM) + add_library(mono-profiler-browser-static STATIC browser.c) + target_link_libraries(mono-profiler-browser-static monoapi) + set_target_properties(mono-profiler-browser-static PROPERTIES OUTPUT_NAME mono-profiler-browser) + install(TARGETS mono-profiler-browser-static LIBRARY) + endif() endif() diff --git a/src/mono/mono/profiler/browser.c b/src/mono/mono/profiler/browser.c new file mode 100644 index 0000000000000..8140b03b535d8 --- /dev/null +++ b/src/mono/mono/profiler/browser.c @@ -0,0 +1,107 @@ +/* + * browser.c: Exporting profiler events to browser dev tools. + + * Copyright 2022 Microsoft Corporation + * Licensed under the MIT license. See LICENSE file in the project root for full license information. +*/ + +#include + +#ifdef TARGET_WASM + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct _MonoProfiler { + gboolean verbose; +}; + +static MonoProfiler browser_profiler; + +#ifdef HOST_WASM + +void +mono_wasm_profiler_enter (); + +void +mono_wasm_profiler_leave (MonoMethod *method); + +static void +method_enter (MonoProfiler *prof, MonoMethod *method, MonoProfilerCallContext *ctx) +{ + mono_wasm_profiler_enter (); +} + +static void +method_leave (MonoProfiler *prof, MonoMethod *method, MonoProfilerCallContext *ctx) +{ + mono_wasm_profiler_leave (method); +} + +static void +tail_call (MonoProfiler *prof, MonoMethod *method, MonoMethod *target) +{ + method_leave (prof, method, NULL); +} + +static void +method_exc_leave (MonoProfiler *prof, MonoMethod *method, MonoObject *exc) +{ + method_leave (prof, method, NULL); +} + +#endif /* HOST_WASM */ + +static MonoProfilerCallInstrumentationFlags +method_filter (MonoProfiler *prof, MonoMethod *method) +{ + // TODO filter by namespace ? + return MONO_PROFILER_CALL_INSTRUMENTATION_ENTER | + MONO_PROFILER_CALL_INSTRUMENTATION_LEAVE | + MONO_PROFILER_CALL_INSTRUMENTATION_TAIL_CALL | + MONO_PROFILER_CALL_INSTRUMENTATION_EXCEPTION_LEAVE; +} + + +MONO_API void +mono_profiler_init_browser (const char *desc); + +/** + * mono_profiler_init_browser: + * the entry point + */ +void +mono_profiler_init_browser (const char *desc) +{ + MonoProfilerHandle handle = mono_profiler_create (&browser_profiler); + + mono_profiler_set_call_instrumentation_filter_callback (handle, method_filter); + + if (mono_jit_aot_compiling ()) { + return; + } + +#ifdef HOST_WASM + // install this only in production run, not in AOT run + mono_profiler_set_method_enter_callback (handle, method_enter); + mono_profiler_set_method_leave_callback (handle, method_leave); + mono_profiler_set_method_tail_call_callback (handle, tail_call); + mono_profiler_set_method_exception_leave_callback (handle, method_exc_leave); +#endif /* HOST_WASM */ +} + +#endif /* TARGET_WASM */ diff --git a/src/mono/sample/wasm/browser-advanced/Wasm.Advanced.Sample.csproj b/src/mono/sample/wasm/browser-advanced/Wasm.Advanced.Sample.csproj index f74fdad80fad3..ef4c5f2dda2f8 100644 --- a/src/mono/sample/wasm/browser-advanced/Wasm.Advanced.Sample.csproj +++ b/src/mono/sample/wasm/browser-advanced/Wasm.Advanced.Sample.csproj @@ -8,6 +8,8 @@ -s USE_CLOSURE_COMPILER=1 -s LEGACY_GL_EMULATION=1 -lGL -lSDL -lidbfs.js <_ServeHeaders>$(_ServeHeaders) -h "Content-Security-Policy: default-src 'self' 'wasm-unsafe-eval'" + + browser; diff --git a/src/mono/sample/wasm/browser-advanced/main.js b/src/mono/sample/wasm/browser-advanced/main.js index 37a66a8f17d68..296de716612da 100644 --- a/src/mono/sample/wasm/browser-advanced/main.js +++ b/src/mono/sample/wasm/browser-advanced/main.js @@ -19,7 +19,8 @@ try { // This is called during emscripten `dotnet.wasm` instantiation, after we fetched config. console.log('user code Module.onConfigLoaded'); // config is loaded and could be tweaked before the rest of the runtime startup sequence - config.environmentVariables["MONO_LOG_LEVEL"] = "debug" + config.environmentVariables["MONO_LOG_LEVEL"] = "debug"; + config.browserProfilerOptions = {}; }, preInit: () => { console.log('user code Module.preInit'); }, preRun: () => { console.log('user code Module.preRun'); }, diff --git a/src/mono/sample/wasm/browser-profile/Makefile b/src/mono/sample/wasm/browser-profile/Makefile index 491ffaf8ba8a5..3374e9c2c8d19 100644 --- a/src/mono/sample/wasm/browser-profile/Makefile +++ b/src/mono/sample/wasm/browser-profile/Makefile @@ -3,7 +3,7 @@ TOP=../../../../.. include ../wasm.mk PROJECT_NAME=Wasm.BrowserProfile.Sample.csproj -BUILD_ARGS=/p:WasmBuildNative=true /p:EnableProfiler=true +BUILD_ARGS=/p:WasmBuildNative=true BUILD_PROFILED_ARGS=/p:RunAOTCompilation=true /p:AOTProfilePath=$(PROFILE_PATH) run: run-browser diff --git a/src/mono/sample/wasm/browser-profile/Wasm.BrowserProfile.Sample.csproj b/src/mono/sample/wasm/browser-profile/Wasm.BrowserProfile.Sample.csproj index f6508c948e44a..7319738a6a0e1 100644 --- a/src/mono/sample/wasm/browser-profile/Wasm.BrowserProfile.Sample.csproj +++ b/src/mono/sample/wasm/browser-profile/Wasm.BrowserProfile.Sample.csproj @@ -4,10 +4,10 @@ main.js aot; true + false - diff --git a/src/mono/wasm/build/WasmApp.Native.targets b/src/mono/wasm/build/WasmApp.Native.targets index 709be4e83f99f..c59baff0684dd 100644 --- a/src/mono/wasm/build/WasmApp.Native.targets +++ b/src/mono/wasm/build/WasmApp.Native.targets @@ -215,6 +215,7 @@ <_EmccCFlags Include="-DINVARIANT_GLOBALIZATION=1" Condition="'$(InvariantGlobalization)' == 'true'" /> <_EmccCFlags Include="-DLINK_ICALLS=1" Condition="'$(WasmLinkIcalls)' == 'true'" /> <_EmccCFlags Include="-DENABLE_AOT_PROFILER=1" Condition="$(WasmProfilers.Contains('aot'))" /> + <_EmccCFlags Include="-DENABLE_BROWSER_PROFILER=1" Condition="$(WasmProfilers.Contains('browser'))" /> <_EmccCFlags Include="-DCORE_BINDINGS" /> <_EmccCFlags Include="-DGEN_PINVOKE=1" /> <_EmccCFlags Include="-emit-llvm" /> @@ -598,6 +599,7 @@ AOTProfilePath="$(AOTProfilePath)" AotModulesTablePath="$(_DriverGenCPath)" UseLLVM="true" + Profilers="$(WasmProfilers)" DisableParallelAot="$(DisableParallelAot)" DedupAssembly="$(_WasmDedupAssembly)" CacheFilePath="$(_AOTCompilerCacheFile)" diff --git a/src/mono/wasm/build/WasmApp.targets b/src/mono/wasm/build/WasmApp.targets index a2031cab5457f..b4ab2787d2173 100644 --- a/src/mono/wasm/build/WasmApp.targets +++ b/src/mono/wasm/build/WasmApp.targets @@ -75,7 +75,7 @@ - @(WasmNativeAsset) - Native files to be added to `NativeAssets` in the bundle. - @(WasmExtraConfig) - json elements to add to `mono-config.json` - Eg. + Eg. - Value attribute can have a number, bool, quoted string, or json string diff --git a/src/mono/wasm/runtime/CMakeLists.txt b/src/mono/wasm/runtime/CMakeLists.txt index c852a7df1df61..47e5d038ceccc 100644 --- a/src/mono/wasm/runtime/CMakeLists.txt +++ b/src/mono/wasm/runtime/CMakeLists.txt @@ -25,6 +25,7 @@ target_link_libraries(dotnet ${MONO_ARTIFACTS_DIR}/libmono-icall-table.a ${MONO_ARTIFACTS_DIR}/libmono-wasm-eh-js.a ${MONO_ARTIFACTS_DIR}/libmono-profiler-aot.a + ${MONO_ARTIFACTS_DIR}/libmono-profiler-browser.a ${NATIVE_BIN_DIR}/libSystem.Native.a ${NATIVE_BIN_DIR}/libSystem.IO.Compression.Native.a) diff --git a/src/mono/wasm/runtime/assets.ts b/src/mono/wasm/runtime/assets.ts index 80d6342910293..6deb4c3652786 100644 --- a/src/mono/wasm/runtime/assets.ts +++ b/src/mono/wasm/runtime/assets.ts @@ -6,6 +6,7 @@ import { mono_wasm_load_icu_data } from "./icu"; import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_SHELL, ENVIRONMENT_IS_WEB, Module, runtimeHelpers } from "./imports"; import { mono_wasm_load_bytes_into_heap } from "./memory"; import { MONO } from "./net6-legacy/imports"; +import { endMeasure, MeasuredBlock, startMeasure } from "./profiler"; import { createPromiseController, PromiseAndController } from "./promise-controller"; import { delay } from "./promise-utils"; import { abort_startup, beforeOnRuntimeInitialized } from "./startup"; @@ -346,6 +347,7 @@ function download_resource(request: ResourceRequest): LoadingResource { function _instantiate_asset(asset: AssetEntry, url: string, bytes: Uint8Array) { if (runtimeHelpers.diagnosticTracing) console.debug(`MONO_WASM: Loaded:${asset.name} as ${asset.behavior} size ${bytes.length} from ${url}`); + const mark = startMeasure(); const virtualName: string = typeof (asset.virtualPath) === "string" ? asset.virtualPath @@ -422,6 +424,7 @@ function _instantiate_asset(asset: AssetEntry, url: string, bytes: Uint8Array) { else if (asset.behavior === "resource") { cwraps.mono_wasm_add_satellite_assembly(virtualName, asset.culture!, offset!, bytes.length); } + endMeasure(mark, MeasuredBlock.instantiateAsset, asset.name); ++actual_instantiated_assets_count; } diff --git a/src/mono/wasm/runtime/cwraps.ts b/src/mono/wasm/runtime/cwraps.ts index d65947d498cdb..dde9219c65bb9 100644 --- a/src/mono/wasm/runtime/cwraps.ts +++ b/src/mono/wasm/runtime/cwraps.ts @@ -84,7 +84,8 @@ const fn_signatures: SigLine[] = [ [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"]], + [false, "mono_wasm_profiler_init_aot", "void", ["number"]], + [false, "mono_wasm_profiler_init_browser", "void", ["number"]], [false, "mono_wasm_exec_regression", "number", ["number", "string"]], [false, "mono_wasm_invoke_method_bound", "number", ["number", "number"]], [true, "mono_wasm_write_managed_pointer_unsafe", "void", ["number", "number"]], @@ -93,6 +94,7 @@ const fn_signatures: SigLine[] = [ [true, "mono_wasm_u52_to_f64", "number", ["number", "number"]], [true, "mono_wasm_f64_to_i52", "number", ["number", "number"]], [true, "mono_wasm_f64_to_u52", "number", ["number", "number"]], + [true, "mono_wasm_method_get_name", "number", ["number"]], ]; export interface t_Cwraps { @@ -193,7 +195,8 @@ export interface t_Cwraps { 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; + mono_wasm_profiler_init_aot(desc: string): void; + mono_wasm_profiler_init_browser(desc: string): void; mono_wasm_exec_regression(verbose_level: number, image: string): number; mono_wasm_invoke_method_bound(method: MonoMethod, args: JSMarshalerArguments): MonoString; mono_wasm_write_managed_pointer_unsafe(destination: VoidPtr | MonoObjectRef, pointer: ManagedPointer): void; @@ -203,6 +206,7 @@ export interface t_Cwraps { mono_wasm_f64_to_i52(destination: VoidPtr, value: number): I52Error; mono_wasm_f64_to_u52(destination: VoidPtr, value: number): I52Error; mono_wasm_runtime_run_module_cctor(assembly: MonoAssembly): void; + mono_wasm_method_get_name(method: MonoMethod): CharPtr; } const wrapped_c_functions: t_Cwraps = {}; diff --git a/src/mono/wasm/runtime/driver.c b/src/mono/wasm/runtime/driver.c index 20458ac22b886..f4d7563beab8b 100644 --- a/src/mono/wasm/runtime/driver.c +++ b/src/mono/wasm/runtime/driver.c @@ -66,6 +66,7 @@ int32_t monoeg_g_hasenv(const char *variable); void mono_free (void*); int32_t mini_parse_debug_option (const char *option); char *mono_method_get_full_name (MonoMethod *method); +char *mono_method_full_name (MonoMethod *method, int signature); static void mono_wasm_init_finalizer_thread (void); @@ -1399,13 +1400,25 @@ mono_wasm_copy_managed_pointer (PPVOLATILE(MonoObject) destination, PPVOLATILE(M void mono_profiler_init_aot (const char *desc); EMSCRIPTEN_KEEPALIVE void -mono_wasm_load_profiler_aot (const char *desc) +mono_wasm_profiler_init_aot (const char *desc) { mono_profiler_init_aot (desc); } #endif +#ifdef ENABLE_BROWSER_PROFILER + +void mono_profiler_init_browser (const char *desc); + +EMSCRIPTEN_KEEPALIVE void +mono_wasm_profiler_init_browser (const char *desc) +{ + mono_profiler_init_browser (desc); +} + +#endif + static void mono_wasm_init_finalizer_thread (void) { @@ -1467,3 +1480,7 @@ EMSCRIPTEN_KEEPALIVE int mono_wasm_f64_to_i52 (int64_t *destination, double valu *destination = (int64_t)value; return I52_ERROR_NONE; } + +EMSCRIPTEN_KEEPALIVE const char* mono_wasm_method_get_name (MonoMethod *method) { + return mono_method_full_name(method, 0); +} diff --git a/src/mono/wasm/runtime/es6/dotnet.es6.lib.js b/src/mono/wasm/runtime/es6/dotnet.es6.lib.js index 9cf4b57ebb724..612330c9df597 100644 --- a/src/mono/wasm/runtime/es6/dotnet.es6.lib.js +++ b/src/mono/wasm/runtime/es6/dotnet.es6.lib.js @@ -69,6 +69,10 @@ const linked_functions = [ // mono-threads-wasm.c "schedule_background_exec", + // interp.c + "mono_wasm_profiler_enter", + "mono_wasm_profiler_leave", + // driver.c "mono_wasm_invoke_js_blazor", "mono_wasm_trace_logger", diff --git a/src/mono/wasm/runtime/exports-internal.ts b/src/mono/wasm/runtime/exports-internal.ts index 01c267ff1ec30..0db08cb407f49 100644 --- a/src/mono/wasm/runtime/exports-internal.ts +++ b/src/mono/wasm/runtime/exports-internal.ts @@ -18,7 +18,8 @@ export function export_internal(): any { // tests mono_wasm_exit: (exit_code: number) => { Module.printErr("MONO_WASM: early exit " + exit_code); }, mono_wasm_enable_on_demand_gc: cwraps.mono_wasm_enable_on_demand_gc, - mono_profiler_init_aot: cwraps.mono_profiler_init_aot, + mono_wasm_profiler_init_aot: cwraps.mono_wasm_profiler_init_aot, + mono_wasm_profiler_init_browser: cwraps.mono_wasm_profiler_init_browser, mono_wasm_exec_regression: cwraps.mono_wasm_exec_regression, mono_method_resolve,//MarshalTests.cs mono_intern_string,// MarshalTests.cs @@ -81,7 +82,8 @@ export function cwraps_internal(internal: any): void { Object.assign(internal, { mono_wasm_exit: cwraps.mono_wasm_exit, mono_wasm_enable_on_demand_gc: cwraps.mono_wasm_enable_on_demand_gc, - mono_profiler_init_aot: cwraps.mono_profiler_init_aot, + mono_wasm_profiler_init_aot: cwraps.mono_wasm_profiler_init_aot, + mono_wasm_profiler_init_browser: cwraps.mono_wasm_profiler_init_browser, mono_wasm_exec_regression: cwraps.mono_wasm_exec_regression, }); } diff --git a/src/mono/wasm/runtime/exports-linker.ts b/src/mono/wasm/runtime/exports-linker.ts index 3c030f2f4f930..2ff03d0c39baf 100644 --- a/src/mono/wasm/runtime/exports-linker.ts +++ b/src/mono/wasm/runtime/exports-linker.ts @@ -22,6 +22,7 @@ import { mono_wasm_diagnostic_server_stream_signal_work_available } from "./diag import { mono_wasm_create_cs_owned_object_ref } from "./net6-legacy/cs-to-js"; import { mono_wasm_typed_array_to_array_ref } from "./net6-legacy/js-to-cs"; import { mono_wasm_trace_logger } from "./logging"; +import { mono_wasm_profiler_leave, mono_wasm_profiler_enter } from "./profiler"; // the methods would be visible to EMCC linker // --- keep in sync with dotnet.cjs.lib.js --- @@ -51,13 +52,17 @@ export function export_linker(): any { // mono-threads-wasm.c schedule_background_exec, - // also keep in sync with driver.c + // interp.c + mono_wasm_profiler_enter, + mono_wasm_profiler_leave, + + // driver.c mono_wasm_invoke_js_blazor, mono_wasm_trace_logger, mono_wasm_set_entrypoint_breakpoint, mono_wasm_event_pipe_early_startup_callback, - // also keep in sync with corebindings.c + // corebindings.c mono_wasm_invoke_js_with_args_ref, mono_wasm_get_object_property_ref, mono_wasm_set_object_property_ref, @@ -74,7 +79,7 @@ export function export_linker(): any { mono_wasm_bind_cs_function, mono_wasm_marshal_promise, - // also keep in sync with pal_icushim_static.c + // pal_icushim_static.c mono_wasm_load_icu_data, mono_wasm_get_icudt_name, diff --git a/src/mono/wasm/runtime/invoke-cs.ts b/src/mono/wasm/runtime/invoke-cs.ts index 581573c6b63c7..293c6731dd433 100644 --- a/src/mono/wasm/runtime/invoke-cs.ts +++ b/src/mono/wasm/runtime/invoke-cs.ts @@ -15,9 +15,11 @@ import { Int32Ptr } from "./types/emscripten"; import cwraps from "./cwraps"; import { assembly_load } from "./class-loader"; import { wrap_error_root } from "./invoke-js"; +import { startMeasure, MeasuredBlock, endMeasure } from "./profiler"; export function mono_wasm_bind_cs_function(fully_qualified_name: MonoStringRef, signature_hash: number, signature: JSFunctionSignature, is_exception: Int32Ptr, result_address: MonoObjectRef): void { const fqn_root = mono_wasm_new_external_root(fully_qualified_name), resultRoot = mono_wasm_new_external_root(result_address); + const mark = startMeasure(); try { const version = get_signature_version(signature); mono_assert(version === 1, () => `Signature version ${version} mismatch.`); @@ -60,6 +62,7 @@ export function mono_wasm_bind_cs_function(fully_qualified_name: MonoStringRef, const closure: BindingClosure = { method, + fqn: js_fqn, args_count, arg_marshalers, res_converter, @@ -84,6 +87,7 @@ export function mono_wasm_bind_cs_function(fully_qualified_name: MonoStringRef, (bound_fn)[bound_cs_function_symbol] = true; _walk_exports_to_set_function(assembly, namespace, classname, methodname, signature_hash, bound_fn); + endMeasure(mark, MeasuredBlock.bindCsFunction, js_fqn); } catch (ex: any) { Module.printErr(ex.toString()); @@ -96,8 +100,10 @@ export function mono_wasm_bind_cs_function(fully_qualified_name: MonoStringRef, function bind_fn_0V(closure: BindingClosure) { const method = closure.method; + const fqn = closure.fqn; (closure) = null; return function bound_fn_0V() { + const mark = startMeasure(); const sp = Module.stackSave(); try { const args = alloc_stack_frame(2); @@ -105,6 +111,7 @@ function bind_fn_0V(closure: BindingClosure) { invoke_method_and_handle_exception(method, args); } finally { Module.stackRestore(sp); + endMeasure(mark, MeasuredBlock.callCsFunction, fqn); } }; } @@ -112,8 +119,10 @@ function bind_fn_0V(closure: BindingClosure) { function bind_fn_1V(closure: BindingClosure) { const method = closure.method; const marshaler1 = closure.arg_marshalers[0]!; + const fqn = closure.fqn; (closure) = null; return function bound_fn_1V(arg1: any) { + const mark = startMeasure(); const sp = Module.stackSave(); try { const args = alloc_stack_frame(3); @@ -123,6 +132,7 @@ function bind_fn_1V(closure: BindingClosure) { invoke_method_and_handle_exception(method, args); } finally { Module.stackRestore(sp); + endMeasure(mark, MeasuredBlock.callCsFunction, fqn); } }; } @@ -131,8 +141,10 @@ function bind_fn_1R(closure: BindingClosure) { const method = closure.method; const marshaler1 = closure.arg_marshalers[0]!; const res_converter = closure.res_converter!; + const fqn = closure.fqn; (closure) = null; return function bound_fn_1R(arg1: any) { + const mark = startMeasure(); const sp = Module.stackSave(); try { const args = alloc_stack_frame(3); @@ -145,6 +157,7 @@ function bind_fn_1R(closure: BindingClosure) { return js_result; } finally { Module.stackRestore(sp); + endMeasure(mark, MeasuredBlock.callCsFunction, fqn); } }; } @@ -154,8 +167,10 @@ function bind_fn_2R(closure: BindingClosure) { const marshaler1 = closure.arg_marshalers[0]!; const marshaler2 = closure.arg_marshalers[1]!; const res_converter = closure.res_converter!; + const fqn = closure.fqn; (closure) = null; return function bound_fn_2R(arg1: any, arg2: any) { + const mark = startMeasure(); const sp = Module.stackSave(); try { const args = alloc_stack_frame(4); @@ -169,6 +184,7 @@ function bind_fn_2R(closure: BindingClosure) { return js_result; } finally { Module.stackRestore(sp); + endMeasure(mark, MeasuredBlock.callCsFunction, fqn); } }; } @@ -178,8 +194,10 @@ function bind_fn(closure: BindingClosure) { const arg_marshalers = closure.arg_marshalers; const res_converter = closure.res_converter; const method = closure.method; + const fqn = closure.fqn; (closure) = null; return function bound_fn(...js_args: any[]) { + const mark = startMeasure(); const sp = Module.stackSave(); try { const args = alloc_stack_frame(2 + args_count); @@ -200,11 +218,13 @@ function bind_fn(closure: BindingClosure) { } } finally { Module.stackRestore(sp); + endMeasure(mark, MeasuredBlock.callCsFunction, fqn); } }; } type BindingClosure = { + fqn: string, args_count: number, method: MonoMethod, arg_marshalers: (BoundMarshalerToCs)[], @@ -254,10 +274,12 @@ export async function mono_wasm_get_assembly_exports(assembly: string): Promise< mono_assert(runtimeHelpers.mono_wasm_bindings_is_ready, "The runtime must be initialized."); const result = exportsByAssembly.get(assembly); if (!result) { + const mark = startMeasure(); const asm = assembly_load(assembly); if (!asm) throw new Error("Could not find assembly: " + assembly); cwraps.mono_wasm_runtime_run_module_cctor(asm); + endMeasure(mark, MeasuredBlock.getAssemblyExports, assembly); } return exportsByAssembly.get(assembly) || {}; diff --git a/src/mono/wasm/runtime/invoke-js.ts b/src/mono/wasm/runtime/invoke-js.ts index b4ef450843e74..07fe9fa5caa4c 100644 --- a/src/mono/wasm/runtime/invoke-js.ts +++ b/src/mono/wasm/runtime/invoke-js.ts @@ -12,6 +12,7 @@ import { bind_arg_marshal_to_js } from "./marshal-to-js"; import { mono_wasm_new_external_root } from "./roots"; import { mono_wasm_symbolicate_string } from "./logging"; import { mono_wasm_get_jsobj_from_js_handle } from "./gc-handles"; +import { endMeasure, MeasuredBlock, startMeasure } from "./profiler"; const fn_wrapper_by_fn_handle: Function[] = [null];// 0th slot is dummy, we never free bound functions @@ -24,6 +25,7 @@ export function mono_wasm_bind_js_function(function_name: MonoStringRef, module_ mono_assert(version === 1, () => `Signature version ${version} mismatch.`); const js_function_name = conv_string_root(function_name_root)!; + const mark = startMeasure(); const js_module_name = conv_string_root(module_name_root)!; if (runtimeHelpers.diagnosticTracing) { console.debug(`MONO_WASM: Binding [JSImport] ${js_function_name} from ${js_module_name}`); @@ -55,6 +57,7 @@ export function mono_wasm_bind_js_function(function_name: MonoStringRef, module_ const closure: BindingClosure = { fn, + fqn: js_module_name + ":" + js_function_name, args_count, arg_marshalers, res_converter, @@ -82,6 +85,7 @@ export function mono_wasm_bind_js_function(function_name: MonoStringRef, module_ const fn_handle = fn_wrapper_by_fn_handle.length; fn_wrapper_by_fn_handle.push(bound_fn); setI32(function_js_handle, fn_handle); + endMeasure(mark, MeasuredBlock.bindJsFunction, js_function_name); } catch (ex: any) { Module.printErr(ex.toString()); wrap_error_root(is_exception, ex, resultRoot); @@ -93,22 +97,29 @@ export function mono_wasm_bind_js_function(function_name: MonoStringRef, module_ function bind_fn_0V(closure: BindingClosure) { const fn = closure.fn; + const fqn = closure.fqn; (closure) = null; return function bound_fn_0V(args: JSMarshalerArguments) { + const mark = startMeasure(); try { // call user function fn(); } catch (ex) { marshal_exception_to_cs(args, ex); } + finally { + endMeasure(mark, MeasuredBlock.callCsFunction, fqn); + } }; } function bind_fn_1V(closure: BindingClosure) { const fn = closure.fn; const marshaler1 = closure.arg_marshalers[0]!; + const fqn = closure.fqn; (closure) = null; return function bound_fn_1V(args: JSMarshalerArguments) { + const mark = startMeasure(); try { const arg1 = marshaler1(args); // call user function @@ -116,6 +127,9 @@ function bind_fn_1V(closure: BindingClosure) { } catch (ex) { marshal_exception_to_cs(args, ex); } + finally { + endMeasure(mark, MeasuredBlock.callCsFunction, fqn); + } }; } @@ -123,8 +137,10 @@ function bind_fn_1R(closure: BindingClosure) { const fn = closure.fn; const marshaler1 = closure.arg_marshalers[0]!; const res_converter = closure.res_converter!; + const fqn = closure.fqn; (closure) = null; return function bound_fn_1R(args: JSMarshalerArguments) { + const mark = startMeasure(); try { const arg1 = marshaler1(args); // call user function @@ -133,6 +149,9 @@ function bind_fn_1R(closure: BindingClosure) { } catch (ex) { marshal_exception_to_cs(args, ex); } + finally { + endMeasure(mark, MeasuredBlock.callCsFunction, fqn); + } }; } @@ -141,8 +160,10 @@ function bind_fn_2R(closure: BindingClosure) { const marshaler1 = closure.arg_marshalers[0]!; const marshaler2 = closure.arg_marshalers[1]!; const res_converter = closure.res_converter!; + const fqn = closure.fqn; (closure) = null; return function bound_fn_2R(args: JSMarshalerArguments) { + const mark = startMeasure(); try { const arg1 = marshaler1(args); const arg2 = marshaler2(args); @@ -152,6 +173,9 @@ function bind_fn_2R(closure: BindingClosure) { } catch (ex) { marshal_exception_to_cs(args, ex); } + finally { + endMeasure(mark, MeasuredBlock.callCsFunction, fqn); + } }; } @@ -162,8 +186,10 @@ function bind_fn(closure: BindingClosure) { const arg_cleanup = closure.arg_cleanup; const has_cleanup = closure.has_cleanup; const fn = closure.fn; + const fqn = closure.fqn; (closure) = null; return function bound_fn(args: JSMarshalerArguments) { + const mark = startMeasure(); try { const js_args = new Array(args_count); for (let index = 0; index < args_count; index++) { @@ -190,11 +216,15 @@ function bind_fn(closure: BindingClosure) { } catch (ex) { marshal_exception_to_cs(args, ex); } + finally { + endMeasure(mark, MeasuredBlock.callCsFunction, fqn); + } }; } type BindingClosure = { fn: Function, + fqn: string, args_count: number, arg_marshalers: (BoundMarshalerToJs)[], res_converter: BoundMarshalerToCs | undefined, diff --git a/src/mono/wasm/runtime/profiler.ts b/src/mono/wasm/runtime/profiler.ts index 45845417e83e5..711a4a9a02ef4 100644 --- a/src/mono/wasm/runtime/profiler.ts +++ b/src/mono/wasm/runtime/profiler.ts @@ -2,7 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. import { Module } from "./imports"; -import { AOTProfilerOptions, CoverageProfilerOptions } from "./types"; +import { AOTProfilerOptions, BrowserProfilerOptions } from "./types"; +import cwraps from "./cwraps"; +import { MonoMethod } from "./types"; // Initialize the AOT profiler with OPTIONS. // Requires the AOT profiler to be linked into the app. @@ -20,21 +22,74 @@ export function mono_wasm_init_aot_profiler(options: AOTProfilerOptions): void { if (!("sendTo" in options)) options.sendTo = "Interop/Runtime::DumpAotProfileData"; const arg = "aot:write-at-method=" + options.writeAt + ",send-to-method=" + options.sendTo; - Module.ccall("mono_wasm_load_profiler_aot", null, ["string"], [arg]); + cwraps.mono_wasm_profiler_init_aot(arg); } -// options = { writeAt: "", sendTo: "" } -// should be in the format ::. -// writeAt defaults to 'WebAssembly.Runtime::StopProfile'. -// sendTo defaults to 'WebAssembly.Runtime::DumpCoverageProfileData'. -// DumpCoverageProfileData stores the data into INTERNAL.coverage_profile_data. -export function mono_wasm_init_coverage_profiler(options: CoverageProfilerOptions): void { +export function mono_wasm_init_browser_profiler(options: BrowserProfilerOptions): void { if (options == null) options = {}; - if (!("writeAt" in options)) - options.writeAt = "WebAssembly.Runtime::StopProfile"; - if (!("sendTo" in options)) - options.sendTo = "WebAssembly.Runtime::DumpCoverageProfileData"; - const arg = "coverage:write-at-method=" + options.writeAt + ",send-to-method=" + options.sendTo; - Module.ccall("mono_wasm_load_profiler_coverage", null, ["string"], [arg]); + const arg = "browser:"; + cwraps.mono_wasm_profiler_init_browser(arg); +} + +export const enum MeasuredBlock { + emscriptenStartup = "mono.emscriptenStartup", + instantiateWasm = "mono.instantiateWasm", + preInit = "mono.preInit", + preRun = "mono.preRun", + onRuntimeInitialized = "mono.onRuntimeInitialized", + postRun = "mono.postRun", + loadRuntime = "mono.loadRuntime", + bindingsInit = "mono.bindingsInit", + bindJsFunction = "mono.bindJsFunction:", + bindCsFunction = "mono.bindCsFunction:", + callJsFunction = "mono.callJsFunction:", + callCsFunction = "mono.callCsFunction:", + getAssemblyExports = "mono.getAssemblyExports:", + instantiateAsset = "mono.instantiateAsset:", +} + +export type TimeStamp = { + __brand: "TimeStamp" +} + +export function startMeasure(): TimeStamp { + if (performance && typeof performance.measure === "function") { + return performance.now() as any; + } + return undefined as any; +} + +export function endMeasure(start: TimeStamp, block: string, id?: string) { + if (start) { + if (id) { + performance.measure(`${block}${id}`, { start: start as any }); + } else { + performance.measure(block, { start: start as any }); + } + } +} + +const stackFrames: number[] = []; +export function mono_wasm_profiler_enter(): void { + if (performance && typeof performance.measure === "function") { + stackFrames.push(performance.now()); + } +} + +const methodNames: Map = new Map(); +export function mono_wasm_profiler_leave(method: MonoMethod): void { + const start = stackFrames.pop(); + if (performance && performance.measure) { + let methodName = methodNames.get(method as any); + if (!methodName) { + const chars = cwraps.mono_wasm_method_get_name(method); + methodName = Module.UTF8ToString(chars); + methodNames.set(method as any, methodName); + Module._free(chars as any); + } + performance.measure(methodName, { + start + }); + } } diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts index c576927f17725..28767df248770 100644 --- a/src/mono/wasm/runtime/startup.ts +++ b/src/mono/wasm/runtime/startup.ts @@ -9,7 +9,7 @@ import cwraps, { init_c_exports } from "./cwraps"; import { mono_wasm_raise_debug_event, mono_wasm_runtime_ready } from "./debug"; import { mono_wasm_globalization_init } from "./icu"; import { toBase64StringImpl } from "./base64"; -import { mono_wasm_init_aot_profiler, mono_wasm_init_coverage_profiler } from "./profiler"; +import { mono_wasm_init_aot_profiler, mono_wasm_init_browser_profiler } from "./profiler"; import { mono_on_abort, mono_exit } from "./run"; import { initialize_marshalers_to_cs } from "./marshal-to-cs"; import { initialize_marshalers_to_js } from "./marshal-to-js"; @@ -27,6 +27,7 @@ import { BINDING, MONO } from "./net6-legacy/imports"; import { readSymbolMapFile } from "./logging"; import { mono_wasm_init_diagnostics } from "./diagnostics"; import { preAllocatePThreadWorkerPool, instantiateWasmPThreadWorkerPool } from "./pthreads/browser"; +import { endMeasure, MeasuredBlock, startMeasure } from "./profiler"; let config: MonoConfigInternal = undefined as any; let configLoaded = false; @@ -46,6 +47,7 @@ const MONO_PTHREAD_POOL_SIZE = 4; // we are making emscripten startup async friendly // emscripten is executing the events without awaiting it and so we need to block progress via PromiseControllers above export function configure_emscripten_startup(module: DotnetModule, exportedAPI: RuntimeAPI): void { + const mark = startMeasure(); // these all could be overridden on DotnetModuleConfig, we are chaing them to async below, as opposed to emscripten // when user set configSrc or config, we are running our default startup sequence. const userInstantiateWasm: undefined | ((imports: WebAssembly.Imports, successCallback: (instance: WebAssembly.Instance, module: WebAssembly.Module) => void) => any) = module.instantiateWasm; @@ -72,6 +74,8 @@ export function configure_emscripten_startup(module: DotnetModule, exportedAPI: module.ready = module.ready.then(async () => { // wait for previous stage await afterPostRun.promise; + // startup end + endMeasure(mark, MeasuredBlock.emscriptenStartup); // - here we resolve the promise returned by createDotnetRuntime export return exportedAPI; // - any code after createDotnetRuntime is executed now @@ -99,8 +103,10 @@ function instantiateWasm( } runtimeHelpers.diagnosticTracing = !!config.diagnosticTracing; + const mark = startMeasure(); if (userInstantiateWasm) { const exports = userInstantiateWasm(imports, (instance: WebAssembly.Instance, module: WebAssembly.Module) => { + endMeasure(mark, MeasuredBlock.instantiateWasm); afterInstantiateWasm.promise_control.resolve(); successCallback(instance, module); }); @@ -113,6 +119,7 @@ function instantiateWasm( function preInit(userPreInit: (() => void)[]) { Module.addRunDependency("mono_pre_init"); + const mark = startMeasure(); try { mono_wasm_pre_init_essential(); if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: preInit"); @@ -135,6 +142,7 @@ function preInit(userPreInit: (() => void)[]) { // - start download assets like DLLs await mono_wasm_pre_init_full(); } + endMeasure(mark, MeasuredBlock.preInit); } catch (err) { abort_startup(err, true); throw err; @@ -151,12 +159,14 @@ async function preRunAsync(userPreRun: (() => void)[]) { await afterInstantiateWasm.promise; await afterPreInit.promise; if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: preRunAsync"); - if (MonoWasmThreads) { - await instantiateWasmPThreadWorkerPool(); - } + const mark = startMeasure(); try { + if (MonoWasmThreads) { + await instantiateWasmPThreadWorkerPool(); + } // all user Module.preRun callbacks userPreRun.map(fn => fn()); + endMeasure(mark, MeasuredBlock.preRun); } catch (err) { _print_error("MONO_WASM: user callback preRun() failed", err); abort_startup(err, true); @@ -171,6 +181,7 @@ async function onRuntimeInitializedAsync(userOnRuntimeInitialized: () => void) { // wait for previous stage await afterPreRun.promise; if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: onRuntimeInitialized"); + const mark = startMeasure(); // signal this stage, this will allow pending assets to allocate memory beforeOnRuntimeInitialized.promise_control.resolve(); try { @@ -192,6 +203,7 @@ async function onRuntimeInitializedAsync(userOnRuntimeInitialized: () => void) { } // finish await mono_wasm_after_user_runtime_initialized(); + endMeasure(mark, MeasuredBlock.onRuntimeInitialized); } catch (err) { _print_error("MONO_WASM: onRuntimeInitializedAsync() failed", err); abort_startup(err, true); @@ -206,8 +218,10 @@ async function postRunAsync(userpostRun: (() => void)[]) { await afterOnRuntimeInitialized.promise; if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: postRunAsync"); try { + const mark = startMeasure(); // all user Module.postRun callbacks userpostRun.map(fn => fn()); + endMeasure(mark, MeasuredBlock.postRun); } catch (err) { _print_error("MONO_WASM: user callback posRun() failed", err); abort_startup(err, true); @@ -417,8 +431,9 @@ async function _apply_configuration_from_args() { if (config.aotProfilerOptions) mono_wasm_init_aot_profiler(config.aotProfilerOptions); - if (config.coverageProfilerOptions) - mono_wasm_init_coverage_profiler(config.coverageProfilerOptions); + if (config.browserProfilerOptions) + mono_wasm_init_browser_profiler(config.browserProfilerOptions); + // for non-Blazor, init diagnostics after environment variables are set if (MonoWasmThreads) { await mono_wasm_init_diagnostics(); @@ -432,6 +447,7 @@ export function mono_wasm_load_runtime(unused?: string, debugLevel?: number): vo } runtimeHelpers.mono_wasm_load_runtime_done = true; try { + const mark = startMeasure(); if (debugLevel == undefined) { debugLevel = 0; if (config && config.debugLevel) { @@ -439,6 +455,7 @@ export function mono_wasm_load_runtime(unused?: string, debugLevel?: number): vo } } cwraps.mono_wasm_load_runtime(unused || "unused", debugLevel); + endMeasure(mark, MeasuredBlock.loadRuntime); runtimeHelpers.waitForDebugger = config.waitForDebugger; if (!runtimeHelpers.mono_wasm_bindings_is_ready) bindings_init(); @@ -461,11 +478,13 @@ export function bindings_init(): void { } runtimeHelpers.mono_wasm_bindings_is_ready = true; try { + const mark = startMeasure(); init_managed_exports(); init_legacy_exports(); initialize_marshalers_to_js(); initialize_marshalers_to_cs(); runtimeHelpers._i52_error_scratch_buffer = Module._malloc(4); + endMeasure(mark, MeasuredBlock.bindingsInit); } catch (err) { _print_error("MONO_WASM: Error in bindings_init", err); throw err; diff --git a/src/mono/wasm/runtime/types.ts b/src/mono/wasm/runtime/types.ts index ca914585d1d6e..2355926ed8dee 100644 --- a/src/mono/wasm/runtime/types.ts +++ b/src/mono/wasm/runtime/types.ts @@ -120,7 +120,7 @@ export type MonoConfig = { export type MonoConfigInternal = MonoConfig & { runtimeOptions?: string[], // array of runtime options as strings aotProfilerOptions?: AOTProfilerOptions, // dictionary-style Object. If omitted, aot profiler will not be initialized. - coverageProfilerOptions?: CoverageProfilerOptions, // dictionary-style Object. If omitted, coverage profiler will not be initialized. + browserProfilerOptions?: BrowserProfilerOptions, // dictionary-style Object. If omitted, browser profiler will not be initialized. waitForDebugger?: number, appendElementOnExit?: boolean logExitCode?: boolean @@ -237,9 +237,7 @@ export type AOTProfilerOptions = { sendTo?: string // should be in the format ::, default: 'WebAssembly.Runtime::DumpAotProfileData' (DumpAotProfileData stores the data into INTERNAL.aotProfileData.) } -export type CoverageProfilerOptions = { - writeAt?: string, // should be in the format ::, default: 'WebAssembly.Runtime::StopProfile' - sendTo?: string // should be in the format ::, default: 'WebAssembly.Runtime::DumpCoverageProfileData' (DumpCoverageProfileData stores the data into INTERNAL.coverage_profile_data.) +export type BrowserProfilerOptions = { } // how we extended emscripten Module diff --git a/src/mono/wasm/wasm.proj b/src/mono/wasm/wasm.proj index 0fa4a43fdc8fe..a4e9ea30ca23b 100644 --- a/src/mono/wasm/wasm.proj +++ b/src/mono/wasm/wasm.proj @@ -240,8 +240,8 @@ $(ArtifactsObjDir)wasm/pinvoke-table.h $(ArtifactsObjDir)wasm/wasm_m2n_invoke.g.h - -g -Os -s -DDEBUG=1 -DENABLE_AOT_PROFILER=1 - -Oz + -g -Os -s -DDEBUG=1 -DENABLE_AOT_PROFILER=1 -DENABLE_BROWSER_PROFILER=1 + -Oz -DENABLE_BROWSER_PROFILER=1 $(CMakeConfigurationEmccFlags) -O2 diff --git a/src/tasks/AotCompilerTask/MonoAOTCompiler.cs b/src/tasks/AotCompilerTask/MonoAOTCompiler.cs index 55e93530680f4..113ed9addafc1 100644 --- a/src/tasks/AotCompilerTask/MonoAOTCompiler.cs +++ b/src/tasks/AotCompilerTask/MonoAOTCompiler.cs @@ -744,6 +744,15 @@ private PrecompileArguments GetPrecompileArgumentsFor(ITaskItem assemblyItem, st aotAssembly.SetMetadata("AotDataFile", proxyFile.TargetFile); } + if (Profilers?.Length > 0) + { + foreach (var profiler in Profilers) + { + processArgs.Add($"\"--profile={profiler}\""); + } + } + + if (AotProfilePath?.Length > 0) { aotArgs.Add("profile-only");