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 f2379fc9ff34d..6fd91975b78a8 100644
--- a/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props
+++ b/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props
@@ -220,6 +220,7 @@
+
diff --git a/src/mono/browser/browser.proj b/src/mono/browser/browser.proj
index 759fa68ae5474..669270cf332d0 100644
--- a/src/mono/browser/browser.proj
+++ b/src/mono/browser/browser.proj
@@ -385,8 +385,8 @@
$(ArtifactsObjDir)wasm/pinvoke-table.h
$(ArtifactsObjDir)wasm/wasm_m2n_invoke.g.h
- -g -Os -DDEBUG=1 -DENABLE_AOT_PROFILER=1 -DENABLE_BROWSER_PROFILER=1
- -Oz -DENABLE_BROWSER_PROFILER=1
+ -g -Os -DDEBUG=1 -DENABLE_AOT_PROFILER=1 -DENABLE_BROWSER_PROFILER=1 -DENABLE_LOG_PROFILER=1
+ -Oz
$(CMakeConfigurationEmccFlags) -s ASSERTIONS=1
-O2
diff --git a/src/mono/browser/build/BrowserWasmApp.targets b/src/mono/browser/build/BrowserWasmApp.targets
index 3b5683433fe86..79ab3cfd83313 100644
--- a/src/mono/browser/build/BrowserWasmApp.targets
+++ b/src/mono/browser/build/BrowserWasmApp.targets
@@ -314,6 +314,7 @@
<_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="-DENABLE_LOG_PROFILER=1" Condition="$(WasmProfilers.Contains('log'))" />
<_EmccCFlags Include="-DENABLE_JS_INTEROP_BY_VALUE=1" Condition="'$(WasmEnableJsInteropByValue)' == 'true'" />
<_EmccCFlags Include="-DGEN_PINVOKE=1" />
@@ -368,6 +369,7 @@
+
diff --git a/src/mono/browser/runtime/cwraps.ts b/src/mono/browser/runtime/cwraps.ts
index 2819e0c44b7f1..c65651dd95de2 100644
--- a/src/mono/browser/runtime/cwraps.ts
+++ b/src/mono/browser/runtime/cwraps.ts
@@ -65,9 +65,10 @@ const fn_signatures: SigLine[] = [
[false, "mono_wasm_exit", "void", ["number"]],
[true, "mono_wasm_getenv", "number", ["string"]],
[true, "mono_wasm_set_main_args", "void", ["number", "number"]],
- // These two need to be lazy because they may be missing
+ // These three need to be lazy because they may be missing
[() => !runtimeHelpers.emscriptenBuildOptions.enableAotProfiler, "mono_wasm_profiler_init_aot", "void", ["string"]],
[() => !runtimeHelpers.emscriptenBuildOptions.enableBrowserProfiler, "mono_wasm_profiler_init_browser", "void", ["string"]],
+ [() => !runtimeHelpers.emscriptenBuildOptions.enableLogProfiler, "mono_wasm_profiler_init_log", "void", ["string"]],
[true, "mono_wasm_profiler_init_browser", "void", ["number"]],
[false, "mono_wasm_exec_regression", "number", ["number", "string"]],
[false, "mono_wasm_invoke_jsexport", "void", ["number", "number"]],
@@ -165,6 +166,7 @@ export interface t_ThreadingCwraps {
export interface t_ProfilerCwraps {
mono_wasm_profiler_init_aot(desc: string): void;
mono_wasm_profiler_init_browser(desc: string): void;
+ mono_wasm_profiler_init_log(desc: string): void;
}
export interface t_Cwraps {
diff --git a/src/mono/browser/runtime/driver.c b/src/mono/browser/runtime/driver.c
index 1b3663e4f3f3c..2a1d5b1dbbf16 100644
--- a/src/mono/browser/runtime/driver.c
+++ b/src/mono/browser/runtime/driver.c
@@ -430,6 +430,18 @@ mono_wasm_profiler_init_browser (const char *desc)
#endif
+#ifdef ENABLE_LOG_PROFILER
+
+void mono_profiler_init_log (const char *desc);
+
+EMSCRIPTEN_KEEPALIVE void
+mono_wasm_profiler_init_log (const char *desc)
+{
+ mono_profiler_init_log (desc);
+}
+
+#endif
+
EMSCRIPTEN_KEEPALIVE void
mono_wasm_init_finalizer_thread (void)
{
diff --git a/src/mono/browser/runtime/es6/dotnet.es6.lib.js b/src/mono/browser/runtime/es6/dotnet.es6.lib.js
index 69b6c31376f9e..687025478570e 100644
--- a/src/mono/browser/runtime/es6/dotnet.es6.lib.js
+++ b/src/mono/browser/runtime/es6/dotnet.es6.lib.js
@@ -11,6 +11,7 @@ const WASM_ENABLE_SIMD = process.env.WASM_ENABLE_SIMD === "1";
const WASM_ENABLE_EH = process.env.WASM_ENABLE_EH === "1";
const ENABLE_BROWSER_PROFILER = process.env.ENABLE_BROWSER_PROFILER === "1";
const ENABLE_AOT_PROFILER = process.env.ENABLE_AOT_PROFILER === "1";
+const ENABLE_LOG_PROFILER = process.env.ENABLE_LOG_PROFILER === "1";
const RUN_AOT_COMPILATION = process.env.RUN_AOT_COMPILATION === "1";
var methodIndexByName = undefined;
var gitHash = undefined;
@@ -88,6 +89,7 @@ function injectDependencies() {
`wasmEnableEH: ${WASM_ENABLE_EH ? "true" : "false"},` +
`enableAotProfiler: ${ENABLE_AOT_PROFILER ? "true" : "false"}, ` +
`enableBrowserProfiler: ${ENABLE_BROWSER_PROFILER ? "true" : "false"}, ` +
+ `enableLogProfiler: ${ENABLE_LOG_PROFILER ? "true" : "false"}, ` +
`runAOTCompilation: ${RUN_AOT_COMPILATION ? "true" : "false"}, ` +
`wasmEnableThreads: ${USE_PTHREADS ? "true" : "false"}, ` +
`gitHash: "${gitHash}", ` +
diff --git a/src/mono/browser/runtime/profiler.ts b/src/mono/browser/runtime/profiler.ts
index a223a7fdbf4a7..0829b3ec71a66 100644
--- a/src/mono/browser/runtime/profiler.ts
+++ b/src/mono/browser/runtime/profiler.ts
@@ -2,7 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
import { ENVIRONMENT_IS_WEB, mono_assert, runtimeHelpers } from "./globals";
-import { MonoMethod, AOTProfilerOptions, BrowserProfilerOptions } from "./types/internal";
+import { MonoMethod, AOTProfilerOptions, BrowserProfilerOptions, LogProfilerOptions } from "./types/internal";
import { profiler_c_functions as cwraps } from "./cwraps";
import { utf8ToString } from "./strings";
@@ -34,6 +34,12 @@ export function mono_wasm_init_browser_profiler (options: BrowserProfilerOptions
cwraps.mono_wasm_profiler_init_browser(arg);
}
+export function mono_wasm_init_log_profiler (options: LogProfilerOptions): void {
+ mono_assert(runtimeHelpers.emscriptenBuildOptions.enableLogProfiler, "Log profiler is not enabled, please use log; in your project file.");
+ mono_assert(options.takeHeapshot, "Log profiler is not enabled, the takeHeapshot method must be defined in LogProfilerOptions.takeHeapshot");
+ cwraps.mono_wasm_profiler_init_log( (options.configuration || "log:alloc,output=output.mlpd") + `,take-heapshot-method=${options.takeHeapshot}`);
+}
+
export const enum MeasuredBlock {
emscriptenStartup = "mono.emscriptenStartup",
instantiateWasm = "mono.instantiateWasm",
diff --git a/src/mono/browser/runtime/startup.ts b/src/mono/browser/runtime/startup.ts
index 834ceb81f2f7b..6bc0b69c11882 100644
--- a/src/mono/browser/runtime/startup.ts
+++ b/src/mono/browser/runtime/startup.ts
@@ -9,7 +9,7 @@ import { exportedRuntimeAPI, INTERNAL, loaderHelpers, Module, runtimeHelpers, cr
import cwraps, { init_c_exports, threads_c_functions as tcwraps } from "./cwraps";
import { mono_wasm_raise_debug_event, mono_wasm_runtime_ready } from "./debug";
import { toBase64StringImpl } from "./base64";
-import { mono_wasm_init_aot_profiler, mono_wasm_init_browser_profiler } from "./profiler";
+import { mono_wasm_init_aot_profiler, mono_wasm_init_browser_profiler, mono_wasm_init_log_profiler } from "./profiler";
import { initialize_marshalers_to_cs } from "./marshal-to-cs";
import { initialize_marshalers_to_js } from "./marshal-to-js";
import { init_polyfills_async } from "./polyfills";
@@ -544,6 +544,9 @@ export async function start_runtime () {
if (runtimeHelpers.config.browserProfilerOptions)
mono_wasm_init_browser_profiler(runtimeHelpers.config.browserProfilerOptions);
+ if (runtimeHelpers.config.logProfilerOptions)
+ mono_wasm_init_log_profiler(runtimeHelpers.config.logProfilerOptions);
+
if (WasmEnableThreads) {
// this is not mono-attached thread, so we can start it earlier
await mono_wasm_init_diagnostics();
diff --git a/src/mono/browser/runtime/types/internal.ts b/src/mono/browser/runtime/types/internal.ts
index 0627543d1fb12..2b2792056a8d1 100644
--- a/src/mono/browser/runtime/types/internal.ts
+++ b/src/mono/browser/runtime/types/internal.ts
@@ -77,6 +77,7 @@ export type MonoConfigInternal = MonoConfig & {
assets?: AssetEntryInternal[],
runtimeOptions?: string[], // array of runtime options as strings
aotProfilerOptions?: AOTProfilerOptions, // dictionary-style Object. If omitted, aot profiler will not be initialized.
+ logProfilerOptions?: LogProfilerOptions, // dictionary-style Object. If omitted, log profiler will not be initialized.
browserProfilerOptions?: BrowserProfilerOptions, // dictionary-style Object. If omitted, browser profiler will not be initialized.
waitForDebugger?: number,
appendElementOnExit?: boolean
@@ -274,6 +275,11 @@ export type AOTProfilerOptions = {
export type BrowserProfilerOptions = {
}
+export type LogProfilerOptions = {
+ takeHeapshot?: string,
+ configuration?: string // log profiler options string"
+}
+
// how we extended emscripten Module
export type DotnetModule = EmscriptenModule & DotnetModuleConfig;
export type DotnetModuleInternal = EmscriptenModule & DotnetModuleConfig & EmscriptenModuleInternal;
@@ -290,6 +296,7 @@ export type EmscriptenBuildOptions = {
wasmEnableEH: boolean,
enableAotProfiler: boolean,
enableBrowserProfiler: boolean,
+ enableLogProfiler: boolean,
runAOTCompilation: boolean,
wasmEnableThreads: boolean,
gitHash: string,
diff --git a/src/mono/mono.proj b/src/mono/mono.proj
index 5be3d221fd5b7..8fe15830cabb5 100644
--- a/src/mono/mono.proj
+++ b/src/mono/mono.proj
@@ -1185,6 +1185,9 @@ JS_ENGINES = [NODE_JS]
<_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-log.a">
+ $(RuntimeBinDir)libmono-profiler-log.a
+
<_MonoRuntimeArtifacts Condition="'$(TargetsBrowser)' == 'true' and '$(BuildMonoAOTCrossCompilerOnly)' != 'true'" Include="$(MonoObjDir)out\lib\libmono-profiler-browser.a">
$(RuntimeBinDir)libmono-profiler-browser.a
diff --git a/src/mono/mono/mini/mini-wasm.c b/src/mono/mono/mini/mini-wasm.c
index 4f094b9246810..2a210e4af6e01 100644
--- a/src/mono/mono/mini/mini-wasm.c
+++ b/src/mono/mono/mini/mini-wasm.c
@@ -577,7 +577,6 @@ mono_init_native_crash_info (void)
void
mono_runtime_setup_stat_profiler (void)
{
- g_error ("mono_runtime_setup_stat_profiler");
}
gboolean
diff --git a/src/mono/mono/profiler/CMakeLists.txt b/src/mono/mono/profiler/CMakeLists.txt
index e172774a6ebc5..f4271dc8bd4f8 100644
--- a/src/mono/mono/profiler/CMakeLists.txt
+++ b/src/mono/mono/profiler/CMakeLists.txt
@@ -6,6 +6,7 @@ include_directories(
${PROJECT_BINARY_DIR}/../../mono/eglib
${CMAKE_CURRENT_SOURCE_DIR}/../..
${PROJECT_SOURCE_DIR}/../
+ ${PROJECT_SOURCE_DIR}/../../../native/public
${PROJECT_SOURCE_DIR}/../eglib
${PROJECT_SOURCE_DIR}/../sgen)
@@ -47,4 +48,18 @@ if(NOT DISABLE_LIBS)
set_target_properties(mono-profiler-browser-static PROPERTIES OUTPUT_NAME mono-profiler-browser)
install(TARGETS mono-profiler-browser-static LIBRARY)
endif()
+
+ if(HOST_BROWSER)
+ add_library(mono-profiler-log-static STATIC helper.c log.c log-args.c)
+ set_target_properties(mono-profiler-log-static PROPERTIES OUTPUT_NAME mono-profiler-log)
+ install(TARGETS mono-profiler-log-static LIBRARY)
+
+ if(NOT DISABLE_LOG_PROFILER_GZ)
+ if (CLR_CMAKE_USE_SYSTEM_ZLIB)
+ target_link_libraries(mono-profiler-log-static PRIVATE ${Z_LIBS})
+ else()
+ target_link_libraries(mono-profiler-log-static PRIVATE zlib)
+ endif()
+ endif()
+ endif()
endif()
diff --git a/src/mono/mono/profiler/helper.c b/src/mono/mono/profiler/helper.c
index bbff8e7bf957b..05fc31b670840 100644
--- a/src/mono/mono/profiler/helper.c
+++ b/src/mono/mono/profiler/helper.c
@@ -8,7 +8,9 @@
#include
+#if !defined (HOST_WASM)
#include
+#endif
#ifdef HAVE_UNISTD_H
#include
@@ -42,6 +44,7 @@ mono_profhelper_close_socket_fd (SOCKET fd)
void
mono_profhelper_setup_command_server (SOCKET *server_socket, int *command_port, const char* profiler_name)
{
+#if !defined (HOST_WASM)
*server_socket = socket (PF_INET, SOCK_STREAM, 0);
if (*server_socket == INVALID_SOCKET) {
@@ -77,11 +80,13 @@ mono_profhelper_setup_command_server (SOCKET *server_socket, int *command_port,
}
*command_port = ntohs (server_address.sin_port);
+#endif
}
void
mono_profhelper_add_to_fd_set (fd_set *set, SOCKET fd, int *max_fd)
{
+#if !defined (HOST_WASM)
/*
* This should only trigger for the basic FDs (server socket, pipes) at
* startup if for some mysterious reason they're too large. In this case,
@@ -99,4 +104,5 @@ mono_profhelper_add_to_fd_set (fd_set *set, SOCKET fd, int *max_fd)
if (*max_fd < GUINT64_TO_INT(fd))
*max_fd = (int)fd;
+#endif
}
diff --git a/src/mono/mono/profiler/log-args.c b/src/mono/mono/profiler/log-args.c
index c8609177d18b0..124a629a14ca3 100644
--- a/src/mono/mono/profiler/log-args.c
+++ b/src/mono/mono/profiler/log-args.c
@@ -1,4 +1,5 @@
#include
+#include
#include
#include
#include
@@ -98,6 +99,9 @@ parse_arg (const char *arg, ProfilerConfig *config)
} else if (match_option (arg, "heapshot-on-shutdown", NULL)) {
config->hs_on_shutdown = TRUE;
config->enable_mask |= PROFLOG_HEAPSHOT_ALIAS;
+ } else if (match_option (arg, "take-heapshot-method", &val)) {
+ printf ("take-heapshot-method: %s\n", val);
+ set_log_profiler_take_heapshot_method(val);
} else if (match_option (arg, "sample", &val)) {
set_sample_freq (config, val);
config->sampling_mode = MONO_PROFILER_SAMPLE_MODE_PROCESS;
diff --git a/src/mono/mono/profiler/log.c b/src/mono/mono/profiler/log.c
index abe5f3f479f57..24d3834296604 100644
--- a/src/mono/mono/profiler/log.c
+++ b/src/mono/mono/profiler/log.c
@@ -189,6 +189,8 @@ typedef struct {
int small_id;
} MonoProfilerThread;
+static MonoMethodDesc *log_profiler_take_heapshot_method;
+
// Default value in `profiler_tls` for new threads.
#define MONO_PROFILER_THREAD_ZERO ((MonoProfilerThread *) NULL)
@@ -617,6 +619,7 @@ buffer_lock_helper (void);
static void
buffer_lock (void)
{
+#if !defined (HOST_WASM)
/*
* If the thread holding the exclusive lock tries to modify the
* reader count, just make it a no-op. This way, we also avoid
@@ -657,6 +660,8 @@ buffer_lock (void)
}
mono_memory_barrier ();
+
+#endif // HOST_WASM
}
static void
@@ -685,6 +690,7 @@ buffer_lock_helper (void)
static void
buffer_unlock (void)
{
+#if !defined (HOST_WASM)
mono_memory_barrier ();
gint32 state = mono_atomic_load_i32 (&log_profiler.buffer_lock_state);
@@ -697,6 +703,7 @@ buffer_unlock (void)
g_assert (!(state >> 16) && "Why is the exclusive lock held?");
mono_atomic_dec_i32 (&log_profiler.buffer_lock_state);
+#endif // HOST_WASM
}
static void
@@ -3499,6 +3506,7 @@ runtime_initialized (MonoProfiler *profiler)
mono_os_sem_init (&log_profiler.attach_threads_sem, 0);
+#if !defined (HOST_WASM)
/*
* We must start the helper thread before the writer thread. This is
* because start_helper_thread () sets up the command port which is written
@@ -3507,6 +3515,9 @@ runtime_initialized (MonoProfiler *profiler)
start_helper_thread ();
start_writer_thread ();
start_dumper_thread ();
+#else
+ dump_header ();
+#endif
/*
* Wait for all the internal threads to be started. If we don't do this, we
@@ -3588,7 +3599,7 @@ create_profiler (const char *args, const char *filename, GPtrArray *filters)
}
}
if (*nf == '|') {
-#if HAVE_API_SUPPORT_WIN32_PIPE_OPEN_CLOSE && !defined (HOST_WIN32)
+#if HAVE_API_SUPPORT_WIN32_PIPE_OPEN_CLOSE && !defined (HOST_WIN32) && !defined (HOST_WASM)
log_profiler.file = popen (nf + 1, "w");
log_profiler.pipe_output = 1;
#else
@@ -3636,6 +3647,44 @@ create_profiler (const char *args, const char *filename, GPtrArray *filters)
log_profiler.startup_time = current_time ();
}
+void
+set_log_profiler_take_heapshot_method (const char *val)
+{
+ log_profiler_take_heapshot_method = mono_method_desc_new (val, TRUE);
+
+ if (!log_profiler_take_heapshot_method) {
+ mono_profiler_printf_err ("Could not parse method description: %s", val);
+ exit (1);
+ }
+}
+
+static void
+proflog_trigger_heapshot (void);
+
+static void
+prof_jit_done (MonoProfiler *prof, MonoMethod *method, MonoJitInfo *jinfo)
+{
+ MonoImage *image = mono_class_get_image (mono_method_get_class (method));
+
+ if (!image->assembly || method->wrapper_type || !log_profiler_take_heapshot_method)
+ return;
+
+ if (log_profiler_take_heapshot_method && mono_method_desc_match (log_profiler_take_heapshot_method, method)) {
+ printf ("log-profiler | taking heapshot\n");
+ proflog_trigger_heapshot ();
+ return;
+ }
+ else {
+ printf ("log-profiler not called (%p)\n", log_profiler_take_heapshot_method);
+ }
+}
+
+static void
+prof_inline_method (MonoProfiler *prof, MonoMethod *method, MonoMethod *inlined_method)
+{
+ prof_jit_done (prof, inlined_method, NULL);
+}
+
MONO_API void
mono_profiler_init_log (const char *desc);
@@ -3758,6 +3807,9 @@ mono_profiler_init_log (const char *desc)
mono_profiler_enable_allocations ();
mono_profiler_enable_clauses ();
mono_profiler_enable_sampling (handle);
+ mono_profiler_set_jit_done_callback (handle, prof_jit_done);
+ mono_profiler_set_inline_method_callback (handle, prof_inline_method);
+
/*
* If no sample option was given by the user, this just leaves the sampling
@@ -3770,3 +3822,12 @@ mono_profiler_init_log (const char *desc)
done:
;
}
+
+static void
+proflog_trigger_heapshot (void)
+{
+ trigger_heapshot ();
+
+ while (handle_writer_queue_entry ());
+ while (handle_dumper_queue_entry ());
+}
\ No newline at end of file
diff --git a/src/mono/mono/profiler/log.h b/src/mono/mono/profiler/log.h
index 9e3b320a7cfb2..a246afd1c1788 100644
--- a/src/mono/mono/profiler/log.h
+++ b/src/mono/mono/profiler/log.h
@@ -526,5 +526,6 @@ typedef struct {
} ProfilerConfig;
void proflog_parse_args (ProfilerConfig *config, const char *desc);
+void set_log_profiler_take_heapshot_method (const char *val);
#endif /* __MONO_PROFLOG_H__ */
diff --git a/src/mono/sample/wasm/browser-logprofile/Makefile b/src/mono/sample/wasm/browser-logprofile/Makefile
new file mode 100644
index 0000000000000..52389be6050cb
--- /dev/null
+++ b/src/mono/sample/wasm/browser-logprofile/Makefile
@@ -0,0 +1,7 @@
+TOP=../../../../..
+
+include ../wasm.mk
+
+PROJECT_NAME=Wasm.BrowserLogProfile.Sample.csproj
+
+run: run-browser
\ No newline at end of file
diff --git a/src/mono/sample/wasm/browser-logprofile/Program.cs b/src/mono/sample/wasm/browser-logprofile/Program.cs
new file mode 100644
index 0000000000000..6bebd913c0592
--- /dev/null
+++ b/src/mono/sample/wasm/browser-logprofile/Program.cs
@@ -0,0 +1,31 @@
+// 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.Runtime.CompilerServices;
+using System.Runtime.InteropServices.JavaScript;
+
+namespace Sample
+{
+ public partial class Test
+ {
+ public static void Main(string[] args)
+ {
+ Console.WriteLine ("Hello, World!");
+ }
+
+ [JSExport]
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static void TakeHeapshot() { }
+
+ [JSExport]
+ public static int TestMeaning()
+ {
+ for(int i=0; i<100; i++){
+ var r = new int[1000];
+ }
+
+ return 42;
+ }
+ }
+}
diff --git a/src/mono/sample/wasm/browser-logprofile/README.md b/src/mono/sample/wasm/browser-logprofile/README.md
new file mode 100644
index 0000000000000..e652d1c04ddf1
--- /dev/null
+++ b/src/mono/sample/wasm/browser-logprofile/README.md
@@ -0,0 +1,7 @@
+## How to run a sample app with log profiling enabled
+
+### Build the sample
+1. Build the runtime
+1. Build the sample with `make`
+1. Run the sample with `make run`
+1. Navigate to https://localhost:8000
diff --git a/src/mono/sample/wasm/browser-logprofile/Wasm.BrowserLogProfile.Sample.csproj b/src/mono/sample/wasm/browser-logprofile/Wasm.BrowserLogProfile.Sample.csproj
new file mode 100644
index 0000000000000..fcc6164c1331a
--- /dev/null
+++ b/src/mono/sample/wasm/browser-logprofile/Wasm.BrowserLogProfile.Sample.csproj
@@ -0,0 +1,15 @@
+
+
+ true
+ log;
+ true
+ false
+
+
+
+
+
+
+
+
+
diff --git a/src/mono/sample/wasm/browser-logprofile/index.html b/src/mono/sample/wasm/browser-logprofile/index.html
new file mode 100644
index 0000000000000..9e6cc3984a755
--- /dev/null
+++ b/src/mono/sample/wasm/browser-logprofile/index.html
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+ Log Profiler Sample
+
+
+
+
+
+
+
+ Result from Sample.Test.TestMeaning:
+
+
+
\ No newline at end of file
diff --git a/src/mono/sample/wasm/browser-logprofile/main.js b/src/mono/sample/wasm/browser-logprofile/main.js
new file mode 100644
index 0000000000000..0ad2db48d38c8
--- /dev/null
+++ b/src/mono/sample/wasm/browser-logprofile/main.js
@@ -0,0 +1,66 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+import { dotnet, exit } from './_framework/dotnet.js'
+
+function saveProfile(Module) {
+ let profileData = readProfileFile(Module);
+
+ const a = document.createElement('a');
+ const blob = new Blob([profileData]);
+ a.href = URL.createObjectURL(blob);
+ a.download = "output.mlpd";
+ // Append anchor to body.
+ document.body.appendChild(a);
+ a.click();
+
+ // Remove anchor from body
+ document.body.removeChild(a);
+}
+
+function readProfileFile(Module) {
+ let profileFilePath="output.mlpd";
+
+ var stat = Module.FS.stat(profileFilePath);
+
+ if (stat && stat.size > 0) {
+ return Module.FS.readFile(profileFilePath);
+ }
+ else {
+ console.debug(`Unable to fetch the profile file ${profileFilePath} as it is empty`);
+ return null;
+ }
+}
+
+try {
+ const { INTERNAL, Module, getAssemblyExports: getAssemblyExports } = await dotnet
+ .withElementOnExit()
+ .withExitCodeLogging()
+ .withConfig({
+ logProfilerOptions: {
+ takeHeapshot: "Sample.Test::TakeHeapshot",
+ configuration: "log:alloc,output=output.mlpd"
+ }
+ })
+ .create();
+
+ console.log("not ready yet")
+ const exports = await getAssemblyExports("Wasm.BrowserLogProfile.Sample");
+ const testMeaning = exports.Sample.Test.TestMeaning;
+ const takeHeapshot = exports.Sample.Test.TakeHeapshot;
+ console.log("ready");
+
+ dotnet.run();
+
+ const ret = testMeaning();
+ document.getElementById("out").innerHTML = ret;
+ console.debug(`ret: ${ret}`);
+
+ takeHeapshot();
+ saveProfile(Module);
+
+ let exit_code = ret == 42 ? 0 : 1;
+ exit(exit_code);
+} catch (err) {
+ exit(-1, err);
+}
diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/MemoryTests.cs b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/MemoryTests.cs
index e95aa351ce837..239950d1fd727 100644
--- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/MemoryTests.cs
+++ b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/MemoryTests.cs
@@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
+using System.Text.RegularExpressions;
using Xunit.Abstractions;
using Xunit;
@@ -37,4 +38,26 @@ public async Task AllocateLargeHeapThenRepeatedlyInterop()
await RunSdkStyleAppForBuild(new (Configuration: config, TestScenario: "AllocateLargeHeapThenInterop"));
}
}
+
+ [Fact]
+ public async Task RunSimpleAppWithProfiler()
+ {
+ string config = "Release";
+ CopyTestAsset("WasmBasicTestApp", "ProfilerTest", "App");
+ // 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(config, assertAppBundle: false, extraArgs: extraArgs);
+
+ var result = await RunSdkStyleAppForBuild(new (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/features.md b/src/mono/wasm/features.md
index 0f84f0bd00e17..2511f9c5c7b1b 100644
--- a/src/mono/wasm/features.md
+++ b/src/mono/wasm/features.md
@@ -426,6 +426,45 @@ import { dotnet } from './dotnet.js'
await dotnet.withConfig({browserProfilerOptions: {}}).run();
```
+### Log Profiling for Memory Troubleshooting
+
+You can enable integration with log profiler via following elements in your .csproj:
+
+```xml
+
+ log;
+ true
+
+```
+
+In simple browser template, you can add following to your `main.js`
+
+```javascript
+import { dotnet } from './dotnet.js'
+await dotnet.withConfig({
+ logProfilerOptions: {
+ takeHeapshot: "MyApp.Profiling::TakeHeapshot",
+ configuration: "log:alloc,output=output.mlpd"
+ }}).run();
+```
+
+In order to trigger a heap shot, add the following:
+
+```csharp
+namespace MyApp;
+
+class Profiling
+{
+ [JSExport]
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static void TakeHeapshot() { }
+}
+```
+
+Invoke `MyApp.Profiling.TakeHeapshot()` from your code in order to create a memory heap shot and flush the contents of the profile to the VFS. Make sure to align the namespace and class of the `logProfilerOptions.takeHeapshot` with your class.
+
+You can download the mpld file to analyze it.
+
### Diagnostic tools
We have initial implementation of diagnostic server and [event pipe](https://learn.microsoft.com/dotnet/core/diagnostics/eventpipe)
diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/App/ProfilerTest.cs b/src/mono/wasm/testassets/WasmBasicTestApp/App/ProfilerTest.cs
new file mode 100644
index 0000000000000..ef3fa75dc92b1
--- /dev/null
+++ b/src/mono/wasm/testassets/WasmBasicTestApp/App/ProfilerTest.cs
@@ -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.
+
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices.JavaScript;
+
+public partial class ProfilerTest
+{
+ [JSExport]
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static void TakeHeapshot() { }
+
+ [JSExport]
+ public static int TestMeaning()
+ {
+ for(int i=0; i<100; i++){
+ var r = new int[1000];
+ }
+
+ return 42;
+ }
+}
diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/App/WasmBasicTestApp.csproj b/src/mono/wasm/testassets/WasmBasicTestApp/App/WasmBasicTestApp.csproj
index e16a8c4948ca5..548b310286bed 100644
--- a/src/mono/wasm/testassets/WasmBasicTestApp/App/WasmBasicTestApp.csproj
+++ b/src/mono/wasm/testassets/WasmBasicTestApp/App/WasmBasicTestApp.csproj
@@ -21,5 +21,6 @@
+
diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/index.html b/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/index.html
index 997c0d3047c08..5179693a495f6 100644
--- a/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/index.html
+++ b/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/index.html
@@ -8,6 +8,7 @@
+
diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js b/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js
index f0515c8dfe5cb..75071104f676b 100644
--- a/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js
+++ b/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
import { dotnet, exit } from './_framework/dotnet.js'
+import { saveProfile } from './profiler.js'
// Read test case from query string
const params = new URLSearchParams(location.search);
@@ -121,9 +122,17 @@ switch (testCase) {
};
dotnet.withConfig({ maxParallelDownloads: maxParallelDownloads });
break;
+ case "ProfilerTest":
+ dotnet.withConfig({
+ logProfilerOptions: {
+ takeHeapshot: "ProfilerTest::TakeHeapshot",
+ configuration: "log:alloc,output=output.mlpd"
+ }
+ })
+ break;
}
-const { setModuleImports, getAssemblyExports, getConfig, INTERNAL } = await dotnet.create();
+const { setModuleImports, Module, getAssemblyExports, getConfig, INTERNAL } = await dotnet.create();
const config = getConfig();
const exports = await getAssemblyExports(config.mainAssemblyName);
const assemblyExtension = Object.keys(config.resources.coreAssembly)[0].endsWith('.wasm') ? ".wasm" : ".dll";
@@ -201,6 +210,25 @@ try {
exports.MemoryTest.Run();
exit(0);
break;
+ case "ProfilerTest":
+ console.log("not ready yet")
+ const myExports = await getAssemblyExports(config.mainAssemblyName);
+ const testMeaning = myExports.ProfilerTest.TestMeaning;
+ const takeHeapshot = myExports.ProfilerTest.TakeHeapshot;
+ console.log("ready");
+
+ dotnet.run();
+
+ const ret = testMeaning();
+ document.getElementById("out").innerHTML = ret;
+ console.debug(`ret: ${ret}`);
+
+ takeHeapshot();
+ saveProfile(Module);
+
+ let exit_code = ret == 42 ? 0 : 1;
+ exit(exit_code);
+ break;
default:
console.error(`Unknown test case: ${testCase}`);
exit(3);
diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/profiler.js b/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/profiler.js
new file mode 100644
index 0000000000000..53d3841e316cf
--- /dev/null
+++ b/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/profiler.js
@@ -0,0 +1,35 @@
+export function saveProfile(Module) {
+ const fileName = "output.mlpd";
+ let profileData = readProfileFile(Module, fileName);
+
+ if (!profileData) {
+ console.error("Profile data is empty or could not be read.");
+ return;
+ }
+
+ const a = document.createElement('a');
+ const blob = new Blob([profileData]);
+ a.href = URL.createObjectURL(blob);
+ a.download = fileName;
+ // Append anchor to body.
+ document.body.appendChild(a);
+ a.click();
+
+ console.log(`TestOutput -> Profile data of size ${profileData.length} bytes started downloading.`);
+
+ // Remove anchor from body
+ document.body.removeChild(a);
+}
+
+function readProfileFile(Module, fileName) {
+
+ var stat = Module.FS.stat(fileName);
+
+ if (stat && stat.size > 0) {
+ return Module.FS.readFile(fileName);
+ }
+ else {
+ console.debug(`Unable to fetch the profile file ${fileName} as it is empty`);
+ return null;
+ }
+}
\ No newline at end of file