diff --git a/src/coreclr/clr.featuredefines.props b/src/coreclr/clr.featuredefines.props
index 7871e7d61a77da..bb0135898ad02c 100644
--- a/src/coreclr/clr.featuredefines.props
+++ b/src/coreclr/clr.featuredefines.props
@@ -22,8 +22,6 @@
false
false
-
- false
false
diff --git a/src/coreclr/clrfeatures.cmake b/src/coreclr/clrfeatures.cmake
index f5c84aaaad2947..e3098a2322e5bb 100644
--- a/src/coreclr/clrfeatures.cmake
+++ b/src/coreclr/clrfeatures.cmake
@@ -24,10 +24,8 @@ if(CLR_CMAKE_TARGET_TIZEN_LINUX)
endif()
if(NOT DEFINED FEATURE_EVENT_TRACE)
- if (NOT CLR_CMAKE_TARGET_BROWSER)
- # To actually disable FEATURE_EVENT_TRACE, also change clr.featuredefines.props
- set(FEATURE_EVENT_TRACE 1)
- endif()
+ # To actually disable FEATURE_EVENT_TRACE, also change clr.featuredefines.props
+ set(FEATURE_EVENT_TRACE 1)
endif(NOT DEFINED FEATURE_EVENT_TRACE)
if(NOT DEFINED FEATURE_EVENTSOURCE_XPLAT)
@@ -37,9 +35,11 @@ if(NOT DEFINED FEATURE_EVENTSOURCE_XPLAT)
endif()
endif(NOT DEFINED FEATURE_EVENTSOURCE_XPLAT)
-if(NOT DEFINED FEATURE_PERFTRACING AND FEATURE_EVENT_TRACE)
- set(FEATURE_PERFTRACING 1)
-endif(NOT DEFINED FEATURE_PERFTRACING AND FEATURE_EVENT_TRACE)
+if(NOT DEFINED FEATURE_PERFTRACING)
+ if(FEATURE_EVENT_TRACE)
+ set(FEATURE_PERFTRACING 1)
+ endif()
+endif(NOT DEFINED FEATURE_PERFTRACING)
if(NOT DEFINED FEATURE_DBGIPC)
if(CLR_CMAKE_TARGET_UNIX)
diff --git a/src/coreclr/inc/clrconfigvalues.h b/src/coreclr/inc/clrconfigvalues.h
index 7e280e3e059be6..23373591e9ea8e 100644
--- a/src/coreclr/inc/clrconfigvalues.h
+++ b/src/coreclr/inc/clrconfigvalues.h
@@ -610,6 +610,7 @@ RETAIL_CONFIG_DWORD_INFO(INTERNAL_EventPipeCircularMB, W("EventPipeCircularMB"),
RETAIL_CONFIG_DWORD_INFO(INTERNAL_EventPipeProcNumbers, W("EventPipeProcNumbers"), 0, "Enable/disable capturing processor numbers in EventPipe event headers")
RETAIL_CONFIG_DWORD_INFO(INTERNAL_EventPipeOutputStreaming, W("EventPipeOutputStreaming"), 1, "Enable/disable streaming for trace file set in DOTNET_EventPipeOutputPath. Non-zero values enable streaming.")
RETAIL_CONFIG_DWORD_INFO(INTERNAL_EventPipeEnableStackwalk, W("EventPipeEnableStackwalk"), 1, "Set to 0 to disable collecting stacks for EventPipe events.")
+RETAIL_CONFIG_DWORD_INFO(INTERNAL_EventPipeCpuSamplingRate, W("EventPipeCpuSamplingRate"), 0, "Desired sample interval in milliseconds for EventPipe CPU sampling profiler. 0 means use the default.")
#ifdef FEATURE_AUTO_TRACE
RETAIL_CONFIG_DWORD_INFO_EX(INTERNAL_AutoTrace_N_Tracers, W("AutoTrace_N_Tracers"), 0, "", CLRConfig::LookupOptions::ParseIntegerAsBase10)
diff --git a/src/coreclr/inc/eventtracebase.h b/src/coreclr/inc/eventtracebase.h
index b81b8386bcc0dc..ea0947e83ca1af 100644
--- a/src/coreclr/inc/eventtracebase.h
+++ b/src/coreclr/inc/eventtracebase.h
@@ -121,6 +121,12 @@ enum EtwGCSettingFlags
#define ETWFireEvent(EventName) FireEtw##EventName(GetClrInstanceId())
#define ETW_TRACING_INITIALIZED(RegHandle) (TRUE)
+#if defined(HOST_BROWSER) || defined(HOST_WASI)
+#define ETW_EVENT_ENABLED(Context, EventDescriptor) (EventPipeHelper::IsEnabled(Context, EventDescriptor.Level, EventDescriptor.Keyword))
+#define ETW_CATEGORY_ENABLED(Context, Level, Keyword) (EventPipeHelper::IsEnabled(Context, Level, Keyword))
+#define ETW_TRACING_ENABLED(Context, EventDescriptor) (EventEnabled##EventDescriptor())
+#define ETW_TRACING_CATEGORY_ENABLED(Context, Level, Keyword) (EventPipeHelper::IsEnabled(Context, Level, Keyword))
+#else // HOST_BROWSER || HOST_WASI
#define ETW_EVENT_ENABLED(Context, EventDescriptor) (EventPipeHelper::IsEnabled(Context, EventDescriptor.Level, EventDescriptor.Keyword) || \
(XplatEventLogger::IsKeywordEnabled(Context, EventDescriptor.Level, EventDescriptor.Keyword)))
#define ETW_CATEGORY_ENABLED(Context, Level, Keyword) (EventPipeHelper::IsEnabled(Context, Level, Keyword) || \
@@ -128,6 +134,7 @@ enum EtwGCSettingFlags
#define ETW_TRACING_ENABLED(Context, EventDescriptor) (EventEnabled##EventDescriptor())
#define ETW_TRACING_CATEGORY_ENABLED(Context, Level, Keyword) (EventPipeHelper::IsEnabled(Context, Level, Keyword) || \
(XplatEventLogger::IsKeywordEnabled(Context, Level, Keyword)))
+#endif // HOST_BROWSER || HOST_WASI
#define ETW_PROVIDER_ENABLED(ProviderSymbol) (TRUE)
#else //defined(FEATURE_PERFTRACING)
#define ETW_INLINE
@@ -416,7 +423,7 @@ class XplatEventLoggerConfiguration
};
#endif // defined(FEATURE_PERFTRACING) || defined(FEATURE_EVENTSOURCE_XPLAT)
-#if defined(HOST_UNIX) && (defined(FEATURE_EVENT_TRACE) || defined(FEATURE_EVENTSOURCE_XPLAT))
+#if defined(HOST_UNIX) && !defined(HOST_BROWSER) && !defined(HOST_WASI) && (defined(FEATURE_EVENT_TRACE) || defined(FEATURE_EVENTSOURCE_XPLAT))
class XplatEventLoggerController
{
@@ -557,7 +564,7 @@ class XplatEventLogger
};
-#endif // defined(HOST_UNIX) && (defined(FEATURE_EVENT_TRACE) || defined(FEATURE_EVENTSOURCE_XPLAT))
+#endif // defined(HOST_UNIX) && !defined(HOST_BROWSER) && !defined(HOST_WASI) && (defined(FEATURE_EVENT_TRACE) || defined(FEATURE_EVENTSOURCE_XPLAT))
#if defined(FEATURE_EVENT_TRACE)
diff --git a/src/coreclr/interpreter/compiler.cpp b/src/coreclr/interpreter/compiler.cpp
index 045839aa29504a..7f83d018d08729 100644
--- a/src/coreclr/interpreter/compiler.cpp
+++ b/src/coreclr/interpreter/compiler.cpp
@@ -45,6 +45,13 @@ static const char *g_stackTypeString[] = { "I4", "I8", "R4", "R8", "O ", "VT", "
const char* CorInfoHelperToName(CorInfoHelpFunc helper);
+#ifdef PERFTRACING_DISABLE_THREADS
+bool InterpCompiler::s_samplingProfilerEnabled = false;
+#ifdef HOST_BROWSER
+bool InterpCompiler::s_browserProfilerEnabled = false;
+#endif
+#endif // PERFTRACING_DISABLE_THREADS
+
#if MEASURE_MEM_ALLOC
#include
@@ -2104,6 +2111,16 @@ InterpCompiler::InterpCompiler(COMP_HANDLE compHnd,
DWORD jitFlagsSize = m_compHnd->getJitFlags(&m_corJitFlags, sizeof(m_corJitFlags));
assert(jitFlagsSize == sizeof(m_corJitFlags));
+#ifdef PERFTRACING_DISABLE_THREADS
+ m_emitSamplingProfiler = s_samplingProfilerEnabled
+ && InterpConfig.WasmPerformanceInstrumentation().contains(compHnd, m_methodHnd, m_classHnd, &m_methodInfo->args);
+
+#ifdef HOST_BROWSER
+ m_emitBrowserProfiler = s_browserProfilerEnabled
+ && InterpConfig.WasmPerformanceInstrumentation().contains(compHnd, m_methodHnd, m_classHnd, &m_methodInfo->args);
+#endif
+#endif // PERFTRACING_DISABLE_THREADS
+
#ifdef DEBUG
m_methodName = ::PrintMethodName(compHnd, m_classHnd, m_methodHnd, &m_methodInfo->args,
/* includeAssembly */ false,
@@ -2809,7 +2826,13 @@ void InterpCompiler::EmitBranch(InterpOpcode opcode, int32_t ilOffset)
// Backwards branch, emit safepoint
if (ilOffset < 0)
+ {
AddIns(INTOP_SAFEPOINT);
+#ifdef PERFTRACING_DISABLE_THREADS
+ if (m_emitSamplingProfiler)
+ AddIns(INTOP_PROF_SAMPLEPOINT);
+#endif // PERFTRACING_DISABLE_THREADS
+ }
InterpBasicBlock *pTargetBB = m_ppOffsetToBB[target];
if (pTargetBB == NULL)
@@ -5648,6 +5671,13 @@ void InterpCompiler::EmitRet(CORINFO_METHOD_INFO* methodInfo)
return;
}
+#ifdef PERFTRACING_DISABLE_THREADS
+#ifdef HOST_BROWSER
+ if (m_emitBrowserProfiler)
+ AddIns(INTOP_PROF_LEAVE);
+#endif // HOST_BROWSER
+#endif // PERFTRACING_DISABLE_THREADS
+
if (m_methodInfo->args.isAsyncCall())
{
// We're doing a standard return. Set the continuation return to NULL.
@@ -8107,6 +8137,17 @@ void InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo)
// Safepoint at each method entry. This could be done as part of a call, rather than
// adding an opcode.
AddIns(INTOP_SAFEPOINT);
+#ifdef PERFTRACING_DISABLE_THREADS
+ if (m_emitSamplingProfiler)
+ AddIns(INTOP_PROF_SAMPLEPOINT);
+#ifdef HOST_BROWSER
+ if (m_emitBrowserProfiler)
+ {
+ AddIns(INTOP_PROF_ENTER);
+ m_pLastNewIns->data[0] = GetMethodDataItemIndex(m_methodHnd);
+ }
+#endif // HOST_BROWSER
+#endif // PERFTRACING_DISABLE_THREADS
if (m_continuationArgIndex != -1)
{
diff --git a/src/coreclr/interpreter/compiler.h b/src/coreclr/interpreter/compiler.h
index 485c55503ea977..c5395db6d84c27 100644
--- a/src/coreclr/interpreter/compiler.h
+++ b/src/coreclr/interpreter/compiler.h
@@ -627,6 +627,12 @@ class InterpCompiler
COMP_HANDLE m_compHnd;
CORINFO_METHOD_INFO* m_methodInfo;
CORJIT_FLAGS m_corJitFlags;
+#ifdef PERFTRACING_DISABLE_THREADS
+ bool m_emitSamplingProfiler;
+#ifdef HOST_BROWSER
+ bool m_emitBrowserProfiler;
+#endif
+#endif // PERFTRACING_DISABLE_THREADS
void DeclarePointerIsClass(CORINFO_CLASS_HANDLE clsHnd)
{
@@ -1106,6 +1112,13 @@ class InterpCompiler
int32_t* GetCode(int32_t *pCodeSize);
+#ifdef PERFTRACING_DISABLE_THREADS
+ static bool s_samplingProfilerEnabled;
+#ifdef HOST_BROWSER
+ static bool s_browserProfilerEnabled;
+#endif
+#endif // PERFTRACING_DISABLE_THREADS
+
#if MEASURE_MEM_ALLOC
// Memory statistics for profiling.
using InterpMemStats = MemStats;
diff --git a/src/coreclr/interpreter/eeinterp.cpp b/src/coreclr/interpreter/eeinterp.cpp
index c7569a9e103174..edcfec44a47403 100644
--- a/src/coreclr/interpreter/eeinterp.cpp
+++ b/src/coreclr/interpreter/eeinterp.cpp
@@ -29,6 +29,18 @@ extern "C" INTERP_API void jitStartup(ICorJitHost* jitHost)
InterpCompiler::initMemStats();
#endif
+ // Enable profiling instrumentation if DOTNET_WasmPerformanceInstrumentation is set.
+ // This must happen before any managed code is compiled so all methods get samplepoints.
+ if (!InterpConfig.WasmPerformanceInstrumentation().isEmpty())
+ {
+#ifdef PERFTRACING_DISABLE_THREADS
+ InterpCompiler::s_samplingProfilerEnabled = true;
+#ifdef HOST_BROWSER
+ InterpCompiler::s_browserProfilerEnabled = true;
+#endif
+#endif // PERFTRACING_DISABLE_THREADS
+ }
+
g_interpInitialized = true;
}
/*****************************************************************************/
diff --git a/src/coreclr/interpreter/inc/intops.def b/src/coreclr/interpreter/inc/intops.def
index f2cdcbbc1b4eb4..0c3822cc16b904 100644
--- a/src/coreclr/interpreter/inc/intops.def
+++ b/src/coreclr/interpreter/inc/intops.def
@@ -75,6 +75,9 @@ OPDEF(INTOP_LDLOCA, "ldloca", 3, 1, 0, InterpOpInt)
OPDEF(INTOP_SWITCH, "switch", 0, 0, 1, InterpOpSwitch)
OPDEF(INTOP_SAFEPOINT, "safepoint", 1, 0, 0, InterpOpNoArgs)
+OPDEF(INTOP_PROF_SAMPLEPOINT, "prof.samplepoint", 1, 0, 0, InterpOpNoArgs)
+OPDEF(INTOP_PROF_ENTER, "prof.enter", 2, 0, 0, InterpOpMethodHandle)
+OPDEF(INTOP_PROF_LEAVE, "prof.leave", 1, 0, 0, InterpOpNoArgs)
OPDEF(INTOP_BR, "br", 2, 0, 0, InterpOpBranch)
OPDEF(INTOP_BRFALSE_I4, "brfalse.i4", 3, 0, 1, InterpOpBranch)
diff --git a/src/coreclr/interpreter/interpconfigvalues.h b/src/coreclr/interpreter/interpconfigvalues.h
index 19549942875f29..74e8bf1a40e52e 100644
--- a/src/coreclr/interpreter/interpconfigvalues.h
+++ b/src/coreclr/interpreter/interpconfigvalues.h
@@ -36,6 +36,7 @@ RELEASE_CONFIG_INTEGER(InterpMode, "InterpMode", 0); // Interpreter mode, one of
// 3: use interpreter for everything, the full interpreter-only mode, no fallbacks to R2R or JIT whatsoever. Implies DOTNET_ReadyToRun=0, DOTNET_EnableHWIntrinsic=0
RELEASE_CONFIG_INTEGER(DisplayMemStats, "JitMemStats", 0); // Display interpreter memory usage statistics (0=off, 1=summary, 2=detailed per-method)
+RELEASE_CONFIG_METHODSET(WasmPerformanceInstrumentation, "WasmPerformanceInstrumentation") // Method filter for WASM performance instrumentation profiler. Uses standard MethodSet pattern format.
#undef CONFIG_STRING
#undef RELEASE_CONFIG_STRING
diff --git a/src/coreclr/nativeaot/Runtime/eventpipe/ep-rt-aot.h b/src/coreclr/nativeaot/Runtime/eventpipe/ep-rt-aot.h
index 78d6578dce29c8..9939965bfd3beb 100644
--- a/src/coreclr/nativeaot/Runtime/eventpipe/ep-rt-aot.h
+++ b/src/coreclr/nativeaot/Runtime/eventpipe/ep-rt-aot.h
@@ -507,6 +507,23 @@ ep_rt_config_value_get_enable_stackwalk (void)
return false;
}
+static
+inline
+uint32_t
+ep_rt_config_value_get_sampling_rate (void)
+{
+ STATIC_CONTRACT_NOTHROW;
+
+ uint64_t value;
+ if (RhConfig::Environment::TryGetIntegerValue("EventPipeCpuSamplingRate", &value, true))
+ {
+ EP_ASSERT(value <= UINT32_MAX);
+ return static_cast(value);
+ }
+
+ return 0;
+}
+
/*
* EventPipeSampleProfiler.
*/
diff --git a/src/coreclr/scripts/genEventing.py b/src/coreclr/scripts/genEventing.py
index 9aaa0df0e085b1..809599d91a59bc 100644
--- a/src/coreclr/scripts/genEventing.py
+++ b/src/coreclr/scripts/genEventing.py
@@ -439,12 +439,15 @@ def generateClrallEvents(eventNodes, allTemplates, target_cpp, runtimeFlavor, is
if runtimeFlavor.coreclr or write_xplatheader or runtimeFlavor.nativeaot:
if not is_host_windows:
- # native AOT does not support non-windows eventing other than via event pipe
+ # native AOT and browser/wasi do not support non-windows eventing other than via event pipe
if not runtimeFlavor.nativeaot:
- clrallEvents.append(" || (XplatEventLogger" +
+ clrallEvents.append("\n#if !defined(HOST_BROWSER) && !defined(HOST_WASI)\n")
+ clrallEvents.append(" || (XplatEventLogger" +
("::" if target_cpp else "_") +
"IsEventLoggingEnabled() && EventXplatEnabled" +
- eventName + "());}\n\n")
+ eventName + "())\n")
+ clrallEvents.append("#endif // !HOST_BROWSER && !HOST_WASI\n")
+ clrallEvents.append(";}\n\n")
else:
clrallEvents.append(";}\n\n")
else:
@@ -534,8 +537,12 @@ def generateClrallEvents(eventNodes, allTemplates, target_cpp, runtimeFlavor, is
fnbody.append("ActivityId,RelatedActivityId);\n")
if runtimeFlavor.coreclr or write_xplatheader:
+ if not is_host_windows:
+ fnbody.append("#if !defined(HOST_BROWSER) && !defined(HOST_WASI)\n")
fnbody.append(lindent)
fnbody.append("status &= FireEtXplat" + eventName + "(" + ''.join(line) + ");\n")
+ if not is_host_windows:
+ fnbody.append("#endif // !HOST_BROWSER && !HOST_WASI\n")
if runtimeFlavor.nativeaot:
if providerName == "Microsoft-Windows-DotNETRuntime" or providerName == "Microsoft-Windows-DotNETRuntimePrivate" or providerName == "Microsoft-Windows-DotNETRuntimeRundown":
@@ -865,11 +872,18 @@ def generatePlatformIndependentFiles(sClrEtwAllMan, incDir, etmDummyFile, extern
dotnet_trace_context_typedef_unix = """
#if !defined(DOTNET_TRACE_CONTEXT_DEF)
#define DOTNET_TRACE_CONTEXT_DEF
+#if defined(HOST_BROWSER) || defined(HOST_WASI)
+typedef struct _DOTNET_TRACE_CONTEXT
+{
+ EVENTPIPE_TRACE_CONTEXT EventPipeProvider;
+} DOTNET_TRACE_CONTEXT, *PDOTNET_TRACE_CONTEXT;
+#else
typedef struct _DOTNET_TRACE_CONTEXT
{
EVENTPIPE_TRACE_CONTEXT EventPipeProvider;
PLTTNG_TRACE_CONTEXT LttngProvider;
} DOTNET_TRACE_CONTEXT, *PDOTNET_TRACE_CONTEXT;
+#endif
#endif // DOTNET_TRACE_CONTEXT_DEF
"""
@@ -894,7 +908,9 @@ def generatePlatformIndependentFiles(sClrEtwAllMan, incDir, etmDummyFile, extern
""")
if not is_host_windows and not runtimeFlavor.nativeaot:
Clrproviders.write(eventpipe_trace_context_typedef) # define EVENTPIPE_TRACE_CONTEXT
+ Clrproviders.write("#if !defined(HOST_BROWSER) && !defined(HOST_WASI)\n")
Clrproviders.write(lttng_trace_context_typedef) # define LTTNG_TRACE_CONTEXT
+ Clrproviders.write("#endif // !HOST_BROWSER && !HOST_WASI\n")
Clrproviders.write(dotnet_trace_context_typedef_unix + "\n")
allProviders = []
@@ -910,7 +926,9 @@ def generatePlatformIndependentFiles(sClrEtwAllMan, incDir, etmDummyFile, extern
eventpipeProviderCtxName = providerSymbol + "_EVENTPIPE_Context"
Clrproviders.write('__attribute__((weak)) EVENTPIPE_TRACE_CONTEXT ' + eventpipeProviderCtxName + ' = { W("' + providerName + '"), 0, false, 0 };\n')
lttngProviderCtxName = providerSymbol + "_LTTNG_Context"
+ Clrproviders.write('#if !defined(HOST_BROWSER) && !defined(HOST_WASI)\n')
Clrproviders.write('__attribute__((weak)) LTTNG_TRACE_CONTEXT ' + lttngProviderCtxName + ' = { W("' + providerName + '"), 0, false, 0 };\n')
+ Clrproviders.write('#endif // !HOST_BROWSER && !HOST_WASI\n')
Clrproviders.write("// Keywords\n");
for keywordNode in providerNode.getElementsByTagName('keyword'):
@@ -934,10 +952,12 @@ def generatePlatformIndependentFiles(sClrEtwAllMan, incDir, etmDummyFile, extern
# define and initialize runtime providers' DOTNET_TRACE_CONTEXT depending on the platform
if not is_host_windows and not runtimeFlavor.nativeaot:
+ Clrproviders.write('#if !defined(HOST_BROWSER) && !defined(HOST_WASI)\n')
Clrproviders.write('#define NB_PROVIDERS ' + str(nbProviders) + '\n')
Clrproviders.write(('constexpr ' if target_cpp else 'static const ') + 'LTTNG_TRACE_CONTEXT * ALL_LTTNG_PROVIDERS_CONTEXT[NB_PROVIDERS] = { ')
Clrproviders.write(', '.join(allProviders))
Clrproviders.write(' };\n')
+ Clrproviders.write('#endif // !HOST_BROWSER && !HOST_WASI\n')
clreventpipewriteevents = os.path.join(incDir, "clreventpipewriteevents.h")
diff --git a/src/coreclr/vm/CMakeLists.txt b/src/coreclr/vm/CMakeLists.txt
index a2dbcb4c89c429..4d9dadffc2e34b 100644
--- a/src/coreclr/vm/CMakeLists.txt
+++ b/src/coreclr/vm/CMakeLists.txt
@@ -937,6 +937,11 @@ elseif(CLR_CMAKE_TARGET_ARCH_WASM)
exceptionhandling.cpp
gcinfodecoder.cpp
)
+ if (CLR_CMAKE_TARGET_BROWSER)
+ list(APPEND VM_SOURCES_WKS_ARCH
+ ${ARCH_SOURCES_DIR}/browserprofiler.cpp
+ )
+ endif(CLR_CMAKE_TARGET_BROWSER)
if (GEN_PINVOKE)
list(APPEND VM_SOURCES_WKS_ARCH
${ARCH_SOURCES_DIR}/callhelpers-pinvoke.cpp
diff --git a/src/coreclr/vm/eventing/CMakeLists.txt b/src/coreclr/vm/eventing/CMakeLists.txt
index 39fb58c2f46daf..f5faa23294c328 100644
--- a/src/coreclr/vm/eventing/CMakeLists.txt
+++ b/src/coreclr/vm/eventing/CMakeLists.txt
@@ -40,10 +40,13 @@ add_custom_command(
set_source_files_properties(${EventingHeaders} PROPERTIES GENERATED TRUE)
add_custom_target(eventing_headers DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/eventing_headers.timestamp)
-add_dependencies(eventing_headers eventprovider)
-
-add_subdirectory(eventpipe)
if(CLR_CMAKE_HOST_WIN32)
add_subdirectory(EtwProvider)
endif()
+
+if (TARGET eventprovider)
+ add_dependencies(eventing_headers eventprovider)
+endif()
+
+add_subdirectory(eventpipe)
diff --git a/src/coreclr/vm/eventing/eventpipe/CMakeLists.txt b/src/coreclr/vm/eventing/eventpipe/CMakeLists.txt
index e6a4ba24e3bead..c593afc310544a 100644
--- a/src/coreclr/vm/eventing/eventpipe/CMakeLists.txt
+++ b/src/coreclr/vm/eventing/eventpipe/CMakeLists.txt
@@ -30,6 +30,7 @@ add_custom_command(OUTPUT ${GEN_EVENTPIPE_SOURCES}
list(APPEND CORECLR_EVENTPIPE_SHIM_SOURCES
ep-rt-coreclr.cpp
+ ep-rt-coreclr-sampling.cpp
)
list(APPEND CORECLR_EVENTPIPE_SHIM_HEADERS
diff --git a/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr-sampling.cpp b/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr-sampling.cpp
new file mode 100644
index 00000000000000..565edcbf81b2d9
--- /dev/null
+++ b/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr-sampling.cpp
@@ -0,0 +1,170 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#include
+
+#ifdef ENABLE_PERFTRACING
+
+#include
+#include
+#include
+#include
+#include
+#include "threadsuspend.h"
+
+// State for single-threaded EP sampling profiler.
+// On single-threaded WASM, sampling is cooperative: the interpreter calls
+// SamplingProfiler_OnSamplepoint() at backward branches (loop iterations)
+// and method entry. A skip counter provides a fast path, and when the
+// counter expires we check if enough wall-clock time has elapsed to
+// justify taking a real sample.
+
+static EventPipeEvent *s_currentSamplingEvent = nullptr;
+static Thread *s_currentSamplingThread = nullptr;
+
+// Adaptive sampling state.
+// s_skipsPerPeriod is the number of samplepoints to skip between actual
+// samples. It is adaptively adjusted so that samples occur approximately
+// once per s_desiredSampleIntervalMs.
+static double s_desiredSampleIntervalMs = 10.0;
+static double s_lastSampleTimeMs = 0.0;
+static int32_t s_prevSkipsPerPeriod = 1;
+static int32_t s_skipsPerPeriod = 1;
+static int32_t s_sampleSkipCounter = 0;
+
+// Returns the current time in milliseconds using the same high-resolution
+// timer as EventPipe timestamps (performance.now() on browser WASM).
+static double GetCurrentTimeMs()
+{
+ return (double)minipal_hires_ticks() * 1000.0 / (double)minipal_hires_tick_frequency();
+}
+
+// Recalculates s_skipsPerPeriod based on how long the last period actually
+// took relative to the desired interval. This is the same exponential
+// moving average approach used by Mono's ep-rt-mono-runtime-provider.c.
+static void UpdateSampleFrequency()
+{
+ double now = GetCurrentTimeMs();
+
+ if (s_lastSampleTimeMs > 0.0)
+ {
+ double elapsed = now - s_lastSampleTimeMs;
+ if (elapsed > 0.0)
+ {
+ double ratio = s_desiredSampleIntervalMs / elapsed;
+ int32_t newSkips = (int32_t)((double)s_prevSkipsPerPeriod * ratio);
+ if (newSkips < 1)
+ newSkips = 1;
+
+ s_prevSkipsPerPeriod = s_skipsPerPeriod;
+ s_skipsPerPeriod = newSkips;
+ }
+ }
+
+ s_lastSampleTimeMs = now;
+}
+
+// Called from the interpreter's INTOP_PROF_SAMPLEPOINT handler.
+// On single-threaded WASM this is the cooperative sampling entry point.
+// On multi-threaded platforms the opcode is never emitted so this is
+// never called, but must exist for linking.
+void SamplingProfiler_OnSamplepoint()
+{
+ if (++s_sampleSkipCounter < s_skipsPerPeriod)
+ return;
+
+ s_sampleSkipCounter = 0;
+
+ if (s_currentSamplingEvent == nullptr || s_currentSamplingThread == nullptr)
+ return;
+
+ UpdateSampleFrequency();
+
+ EventPipeStackContents stackContents;
+ EventPipeStackContents *pStackContents = ep_stack_contents_init(&stackContents);
+
+ if (ep_rt_coreclr_walk_managed_stack_for_thread(s_currentSamplingThread, pStackContents)
+ && !ep_stack_contents_is_empty(pStackContents))
+ {
+ uint32_t payloadData = EP_SAMPLE_PROFILER_SAMPLE_TYPE_MANAGED;
+
+ ep_write_sample_profile_event(
+ s_currentSamplingThread,
+ s_currentSamplingEvent,
+ s_currentSamplingThread,
+ pStackContents,
+ (uint8_t *)&payloadData,
+ sizeof(payloadData));
+ }
+
+ ep_stack_contents_fini(pStackContents);
+}
+
+#ifndef PERFTRACING_DISABLE_THREADS
+
+// On multi-threaded builds the sample profiler runs on a dedicated
+// thread, so these callbacks are no-ops.
+
+void ep_rt_coreclr_sample_profiler_enabled(EventPipeEvent *samplingEvent)
+{
+}
+
+void ep_rt_coreclr_sample_profiler_session_enabled(void)
+{
+}
+
+void ep_rt_coreclr_sample_profiler_disabled(void)
+{
+}
+
+#else // PERFTRACING_DISABLE_THREADS
+
+// The following functions are EP runtime callbacks invoked only on
+// single-threaded builds where the regular threaded sample profiler
+// cannot run.
+
+void ep_rt_coreclr_sample_profiler_enabled(EventPipeEvent *samplingEvent)
+{
+ s_currentSamplingEvent = samplingEvent;
+ s_currentSamplingThread = GetThread();
+
+ s_desiredSampleIntervalMs = (double)ep_sample_profiler_get_sampling_rate() / 1000000.0;
+
+ s_lastSampleTimeMs = 0.0;
+ s_prevSkipsPerPeriod = 1;
+ s_skipsPerPeriod = 1;
+ s_sampleSkipCounter = 0;
+}
+
+void ep_rt_coreclr_sample_profiler_session_enabled(void)
+{
+ if (s_currentSamplingEvent == nullptr || s_currentSamplingThread == nullptr)
+ return;
+
+ EventPipeStackContents stackContents;
+ EventPipeStackContents *pStackContents = ep_stack_contents_init(&stackContents);
+
+ uint32_t payloadData = EP_SAMPLE_PROFILER_SAMPLE_TYPE_MANAGED;
+
+ ep_write_sample_profile_event(
+ s_currentSamplingThread,
+ s_currentSamplingEvent,
+ s_currentSamplingThread,
+ pStackContents,
+ (uint8_t *)&payloadData,
+ sizeof(payloadData));
+
+ ep_stack_contents_fini(pStackContents);
+}
+
+void ep_rt_coreclr_sample_profiler_disabled(void)
+{
+ s_currentSamplingEvent = nullptr;
+ s_currentSamplingThread = nullptr;
+ s_sampleSkipCounter = 0;
+ s_skipsPerPeriod = 1;
+}
+
+#endif // PERFTRACING_DISABLE_THREADS
+
+#endif // ENABLE_PERFTRACING
diff --git a/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h b/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h
index 97991823eaff97..140767bf9d01f2 100644
--- a/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h
+++ b/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h
@@ -51,6 +51,10 @@
#undef EP_ALIGN_UP
#define EP_ALIGN_UP(val,align) ALIGN_UP(val,align)
+extern void ep_rt_coreclr_sample_profiler_enabled (EventPipeEvent *sampling_event);
+extern void ep_rt_coreclr_sample_profiler_session_enabled (void);
+extern void ep_rt_coreclr_sample_profiler_disabled (void);
+
static
inline
ep_rt_lock_handle_t *
@@ -444,9 +448,9 @@ ep_rt_provider_config_init (EventPipeProviderConfiguration *provider_config)
// This function is auto-generated from /src/scripts/genEventPipe.py
#ifdef TARGET_UNIX
extern "C" void InitProvidersAndEvents ();
-#else
+#else // TARGET_UNIX
extern void InitProvidersAndEvents ();
-#endif
+#endif // TARGET_UNIX
static
void
@@ -572,6 +576,15 @@ ep_rt_config_value_get_enable_stackwalk (void)
return CLRConfig::GetConfigValue(CLRConfig::INTERNAL_EventPipeEnableStackwalk) != 0;
}
+static
+inline
+uint32_t
+ep_rt_config_value_get_sampling_rate (void)
+{
+ STATIC_CONTRACT_NOTHROW;
+ return CLRConfig::GetConfigValue(CLRConfig::INTERNAL_EventPipeCpuSamplingRate);
+}
+
/*
* EventPipeSampleProfiler.
*/
@@ -595,7 +608,7 @@ void
ep_rt_sample_profiler_enabled (EventPipeEvent *sampling_event)
{
STATIC_CONTRACT_NOTHROW;
- // no-op
+ ep_rt_coreclr_sample_profiler_enabled (sampling_event);
}
static
@@ -604,7 +617,7 @@ void
ep_rt_sample_profiler_session_enabled (void)
{
STATIC_CONTRACT_NOTHROW;
- // no-op
+ ep_rt_coreclr_sample_profiler_session_enabled ();
}
static
@@ -613,7 +626,7 @@ void
ep_rt_sample_profiler_disabled (void)
{
STATIC_CONTRACT_NOTHROW;
- // no-op
+ ep_rt_coreclr_sample_profiler_disabled ();
}
static
@@ -622,13 +635,12 @@ void
ep_rt_notify_profiler_provider_created (EventPipeProvider *provider)
{
STATIC_CONTRACT_NOTHROW;
-
-#ifndef DACCESS_COMPILE
+#if !defined(DACCESS_COMPILE) && defined(PROFILING_SUPPORTED)
// Let the profiler know the provider has been created so it can register if it wants to
BEGIN_PROFILER_CALLBACK (CORProfilerTrackEventPipe ());
(&g_profControlBlock)->EventPipeProviderCreated (provider);
END_PROFILER_CALLBACK ();
-#endif // DACCESS_COMPILE
+#endif // !DACCESS_COMPILE && PROFILING_SUPPORTED
}
/*
@@ -659,6 +671,8 @@ ep_rt_byte_array_free (uint8_t *ptr)
* Event.
*/
+#ifndef PERFTRACING_DISABLE_THREADS
+
static
void
ep_rt_wait_event_alloc (
@@ -757,6 +771,71 @@ ep_rt_wait_event_is_valid (ep_rt_wait_event_handle_t *wait_event)
return wait_event->event->IsValid ();
}
+#else // PERFTRACING_DISABLE_THREADS
+
+static
+inline
+void
+ep_rt_wait_event_alloc (
+ ep_rt_wait_event_handle_t *wait_event,
+ bool manual,
+ bool initial)
+{
+ EP_ASSERT (wait_event != NULL);
+ wait_event->event = (CLREventStatic * )INVALID_HANDLE_VALUE;
+}
+
+static
+inline
+void
+ep_rt_wait_event_free (ep_rt_wait_event_handle_t *wait_event)
+{
+ wait_event->event = NULL;
+}
+
+static
+inline
+bool
+ep_rt_wait_event_set (ep_rt_wait_event_handle_t *wait_event)
+{
+ return true;
+}
+
+static
+inline
+int32_t
+ep_rt_wait_event_wait (
+ ep_rt_wait_event_handle_t *wait_event,
+ uint32_t timeout,
+ bool alertable)
+{
+ EP_ASSERT (wait_event != NULL && wait_event->event == (CLREventStatic *)INVALID_HANDLE_VALUE);
+ return (int32_t)0;
+}
+
+static
+inline
+EventPipeWaitHandle
+ep_rt_wait_event_get_wait_handle (ep_rt_wait_event_handle_t *wait_event)
+{
+ EP_ASSERT (wait_event != NULL);
+ return (EventPipeWaitHandle)wait_event->event;
+}
+
+static
+inline
+bool
+ep_rt_wait_event_is_valid (ep_rt_wait_event_handle_t *wait_event)
+{
+ if (wait_event == NULL || wait_event->event == NULL || wait_event->event != (CLREventStatic *)INVALID_HANDLE_VALUE)
+ return false;
+ else
+ return true;
+}
+
+#endif // PERFTRACING_DISABLE_THREADS
+
+
/*
* Misc.
*/
@@ -843,6 +922,7 @@ typedef struct _rt_coreclr_thread_params_internal_t {
#undef EP_RT_DEFINE_THREAD_FUNC
#define EP_RT_DEFINE_THREAD_FUNC(name) static ep_rt_thread_start_func_return_t WINAPI name (LPVOID data)
+#ifndef PERFTRACING_DISABLE_THREADS
EP_RT_DEFINE_THREAD_FUNC (ep_rt_thread_coreclr_start_func)
{
STATIC_CONTRACT_NOTHROW;
@@ -926,9 +1006,58 @@ ep_rt_queue_job (
void *job_func,
void *params)
{
- EP_UNREACHABLE ("Not implemented in CoreCLR");
+ EP_UNREACHABLE ("Not implemented on in multi threaded");
+ return false;
+}
+
+#else // PERFTRACING_DISABLE_THREADS
+
+static
+inline
+bool
+ep_rt_thread_create (
+ void *thread_func,
+ void *params,
+ EventPipeThreadType thread_type,
+ void *id)
+{
+ EP_UNREACHABLE ("Not implemented on in single threaded");
+ return false;
+}
+
+#ifdef HOST_BROWSER
+typedef size_t (*ep_rt_job_cb_t)(void *data);
+extern "C" void SystemJS_DiagnosticServerQueueJob (ep_rt_job_cb_t cb, void *data);
+#endif
+
+static
+bool
+ep_rt_queue_job (
+ void *job_func,
+ void *params)
+{
+#ifdef HOST_BROWSER
+ // in single-threaded, it will run the callback inline and re-schedule itself if necessary
+ // it's called from browser event loop
+ ep_rt_job_cb_t cb = (ep_rt_job_cb_t)job_func;
+
+ // invoke the callback inline for the first time
+ size_t done = cb (params);
+
+ // see if it's done or needs to be scheduled again
+ if (!done) {
+ // self schedule again
+ SystemJS_DiagnosticServerQueueJob (cb, params);
+ }
+
+ return true;
+#else
+ EP_UNREACHABLE ("Not implemented on this platform");
+#endif
}
+#endif // PERFTRACING_DISABLE_THREADS
+
static
inline
void
@@ -942,6 +1071,7 @@ inline
void
ep_rt_thread_sleep (uint64_t ns)
{
+#ifndef PERFTRACING_DISABLE_THREADS
STATIC_CONTRACT_NOTHROW;
#ifdef TARGET_UNIX
@@ -950,6 +1080,7 @@ ep_rt_thread_sleep (uint64_t ns)
const uint32_t NUM_NANOSECONDS_IN_1_MS = 1000000;
ClrSleepEx (static_cast(ns / NUM_NANOSECONDS_IN_1_MS), FALSE);
#endif //TARGET_UNIX
+#endif // PERFTRACING_DISABLE_THREADS
}
static
diff --git a/src/coreclr/vm/eventtrace.cpp b/src/coreclr/vm/eventtrace.cpp
index 2488a54040f945..1eb6788b2cb18e 100644
--- a/src/coreclr/vm/eventtrace.cpp
+++ b/src/coreclr/vm/eventtrace.cpp
@@ -50,6 +50,11 @@ DOTNET_TRACE_CONTEXT MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_DOTNET_Context = {
DOTNET_TRACE_CONTEXT MICROSOFT_WINDOWS_DOTNETRUNTIME_PRIVATE_PROVIDER_DOTNET_Context = { &MICROSOFT_WINDOWS_DOTNETRUNTIME_PRIVATE_PROVIDER_Context, MICROSOFT_WINDOWS_DOTNETRUNTIME_PRIVATE_PROVIDER_EVENTPIPE_Context };
DOTNET_TRACE_CONTEXT MICROSOFT_WINDOWS_DOTNETRUNTIME_RUNDOWN_PROVIDER_DOTNET_Context = { &MICROSOFT_WINDOWS_DOTNETRUNTIME_RUNDOWN_PROVIDER_Context, MICROSOFT_WINDOWS_DOTNETRUNTIME_RUNDOWN_PROVIDER_EVENTPIPE_Context };
DOTNET_TRACE_CONTEXT MICROSOFT_WINDOWS_DOTNETRUNTIME_STRESS_PROVIDER_DOTNET_Context = { &MICROSOFT_WINDOWS_DOTNETRUNTIME_STRESS_PROVIDER_Context, MICROSOFT_WINDOWS_DOTNETRUNTIME_STRESS_PROVIDER_EVENTPIPE_Context };
+#elif defined(HOST_BROWSER) || defined(HOST_WASI)
+DOTNET_TRACE_CONTEXT MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_DOTNET_Context = { MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_EVENTPIPE_Context };
+DOTNET_TRACE_CONTEXT MICROSOFT_WINDOWS_DOTNETRUNTIME_PRIVATE_PROVIDER_DOTNET_Context = { MICROSOFT_WINDOWS_DOTNETRUNTIME_PRIVATE_PROVIDER_EVENTPIPE_Context };
+DOTNET_TRACE_CONTEXT MICROSOFT_WINDOWS_DOTNETRUNTIME_RUNDOWN_PROVIDER_DOTNET_Context = { MICROSOFT_WINDOWS_DOTNETRUNTIME_RUNDOWN_PROVIDER_EVENTPIPE_Context };
+DOTNET_TRACE_CONTEXT MICROSOFT_WINDOWS_DOTNETRUNTIME_STRESS_PROVIDER_DOTNET_Context = { MICROSOFT_WINDOWS_DOTNETRUNTIME_STRESS_PROVIDER_EVENTPIPE_Context };
#else
DOTNET_TRACE_CONTEXT MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_DOTNET_Context = { MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_EVENTPIPE_Context, &MICROSOFT_WINDOWS_DOTNETRUNTIME_PROVIDER_LTTNG_Context };
DOTNET_TRACE_CONTEXT MICROSOFT_WINDOWS_DOTNETRUNTIME_PRIVATE_PROVIDER_DOTNET_Context = { MICROSOFT_WINDOWS_DOTNETRUNTIME_PRIVATE_PROVIDER_EVENTPIPE_Context, &MICROSOFT_WINDOWS_DOTNETRUNTIME_PRIVATE_PROVIDER_LTTNG_Context };
@@ -2277,9 +2282,9 @@ void InitializeEventTracing()
// providers can do so now
ETW::TypeSystemLog::PostRegistrationInit();
-#if defined(HOST_UNIX) && defined (FEATURE_PERFTRACING)
+#if defined(HOST_UNIX) && !defined(HOST_BROWSER) && !defined(HOST_WASI) && defined(FEATURE_PERFTRACING)
XplatEventLogger::InitializeLogger();
-#endif // HOST_UNIX && FEATURE_PERFTRACING
+#endif // HOST_UNIX && !HOST_BROWSER && !HOST_WASI && FEATURE_PERFTRACING
}
// Plumbing to funnel event pipe callbacks and ETW callbacks together into a single common
@@ -4417,7 +4422,20 @@ TADDR MethodAndStartAddressToEECodeInfoPointer(MethodDesc *pMethodDesc, PCODE pN
return 0;
}
- return GetInterpreterCodeFromInterpreterPrecodeIfPresent(start);
+ start = GetInterpreterCodeFromInterpreterPrecodeIfPresent(start);
+
+#if defined(FEATURE_INTERPRETER) && defined(FEATURE_PORTABLE_ENTRYPOINTS)
+ if (pNativeCodeStartAddress == (PCODE)0)
+ {
+ PTR_InterpByteCodeStart pInterpCode = pMethodDesc->GetInterpreterCode();
+ if (pInterpCode != NULL)
+ {
+ return dac_cast(pInterpCode);
+ }
+ }
+#endif
+
+ return start;
}
/****************************************************************************/
@@ -5607,10 +5625,10 @@ bool EventPipeHelper::IsEnabled(DOTNET_TRACE_CONTEXT Context, UCHAR Level, ULONG
}
#endif // FEATURE_PERFTRACING
-#if defined(HOST_UNIX) && defined(FEATURE_PERFTRACING)
+#if defined(HOST_UNIX) && !defined(HOST_BROWSER) && !defined(HOST_WASI) && defined(FEATURE_PERFTRACING)
// This is a wrapper method for LTTng. See https://github.com/dotnet/coreclr/pull/27273 for details.
extern "C" bool XplatEventLoggerIsEnabled()
{
return XplatEventLogger::IsEventLoggingEnabled();
}
-#endif // HOST_UNIX && FEATURE_PERFTRACING
+#endif // HOST_UNIX && !HOST_BROWSER && !HOST_WASI && FEATURE_PERFTRACING
diff --git a/src/coreclr/vm/eventtrace_gcheap.cpp b/src/coreclr/vm/eventtrace_gcheap.cpp
index d8b395941218f8..a1d415b53c4b21 100644
--- a/src/coreclr/vm/eventtrace_gcheap.cpp
+++ b/src/coreclr/vm/eventtrace_gcheap.cpp
@@ -408,6 +408,24 @@ VOID ETW::GCLog::EndMovedReferences(size_t profilingContext, BOOL fAllowProfApiN
delete pContext;
}
+#if defined(TARGET_BROWSER)
+extern "C" void SystemJS_DiagnosticServerQueueJob(size_t (*cb)(void*), void* data);
+
+static size_t ForceGCForDiagnosticsJob(void* data)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_TRIGGERS;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ ETW::GCLog::ForceGCForDiagnostics();
+ return 1; // done
+}
+#endif // TARGET_BROWSER
+
// This implements the public runtime provider's GCHeapCollectKeyword. It
// performs a full, gen-2, blocking GC.
VOID ETW::GCLog::ForceGC(LONGLONG l64ClientSequenceNumber)
@@ -427,7 +445,14 @@ VOID ETW::GCLog::ForceGC(LONGLONG l64ClientSequenceNumber)
InterlockedExchange64(&s_l64LastClientSequenceNumber, l64ClientSequenceNumber);
+#if defined(TARGET_BROWSER)
+ // On single-threaded browser, we cannot call ForceGCForDiagnostics synchronously
+ // from within the provider callback (which runs during ep_enable_3 under the
+ // EventPipe lock). Defer the GC to the next event loop turn.
+ SystemJS_DiagnosticServerQueueJob(ForceGCForDiagnosticsJob, NULL);
+#else
ForceGCForDiagnostics();
+#endif // TARGET_BROWSER
}
//---------------------------------------------------------------------------------------
diff --git a/src/coreclr/vm/gcenv.ee.cpp b/src/coreclr/vm/gcenv.ee.cpp
index 6e7d047e6aa2a0..8b47e993400feb 100644
--- a/src/coreclr/vm/gcenv.ee.cpp
+++ b/src/coreclr/vm/gcenv.ee.cpp
@@ -864,7 +864,7 @@ void GCToEEInterface::DiagUpdateGenerationBounds()
void GCToEEInterface::DiagGCEnd(size_t index, int gen, int reason, bool fConcurrent)
{
-#ifdef GC_PROFILING
+#if defined(GC_PROFILING) || defined(FEATURE_EVENT_TRACE)
// We were only doing generation bounds and GC finish callback for non concurrent GCs so
// I am keeping that behavior to not break profilers. But if BasicGC monitoring is enabled
// we will do these for all GCs.
@@ -872,7 +872,9 @@ void GCToEEInterface::DiagGCEnd(size_t index, int gen, int reason, bool fConcurr
{
GCProfileWalkHeap(false);
}
+#endif // defined(GC_PROFILING) || defined(FEATURE_EVENT_TRACE)
+#ifdef GC_PROFILING
if (CORProfilerTrackBasicGC() || (!fConcurrent && CORProfilerTrackGC()))
{
DiagUpdateGenerationBounds();
diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp
index d36bb50efde8d6..350b439055878a 100644
--- a/src/coreclr/vm/interpexec.cpp
+++ b/src/coreclr/vm/interpexec.cpp
@@ -12,6 +12,15 @@
#include "gchelpers.inl"
#include "arraynative.inl"
+#ifdef TARGET_WASM
+extern void SamplingProfiler_OnSamplepoint();
+#endif
+
+#ifdef TARGET_BROWSER
+extern void BrowserProfiler_OnMethodEnter(void *pMethodDesc);
+extern void BrowserProfiler_OnMethodLeave();
+#endif
+
// for numeric_limits
#include
#include
@@ -1904,6 +1913,37 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr
ip++;
break;
+#ifdef TARGET_WASM
+ case INTOP_PROF_SAMPLEPOINT:
+ SamplingProfiler_OnSamplepoint();
+ ip++;
+ break;
+#else
+ case INTOP_PROF_SAMPLEPOINT:
+ ip++;
+ break;
+#endif
+
+#ifdef TARGET_BROWSER
+ case INTOP_PROF_ENTER:
+ BrowserProfiler_OnMethodEnter(pMethod->pDataItems[ip[1]]);
+ ip += 2;
+ break;
+
+ case INTOP_PROF_LEAVE:
+ BrowserProfiler_OnMethodLeave();
+ ip++;
+ break;
+#else
+ case INTOP_PROF_ENTER:
+ ip += 2;
+ break;
+
+ case INTOP_PROF_LEAVE:
+ ip++;
+ break;
+#endif
+
case INTOP_BR:
ip += ip[1];
break;
@@ -3380,6 +3420,9 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr
if (frameNeedsTailcallUpdate)
{
+#ifdef TARGET_BROWSER
+ BrowserProfiler_OnMethodLeave();
+#endif
InterpMethod* pTargetMethod = targetIp->Method;
UpdateFrameForTailCall(pFrame, targetIp, callArgsAddress);
frameNeedsTailcallUpdate = false;
@@ -4606,6 +4649,9 @@ do \
// Thus, we need to rethrow it to let it propagate further.
throw;
}
+#ifdef TARGET_BROWSER
+ BrowserProfiler_OnMethodLeave();
+#endif
pThreadContext->frameDataAllocator.PopInfo(pFrame);
pFrame->ip = 0;
pFrame = pFrame->pParent;
diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp
index 73ae13ab6e5ac9..de537bec1e727a 100644
--- a/src/coreclr/vm/prestub.cpp
+++ b/src/coreclr/vm/prestub.cpp
@@ -810,8 +810,12 @@ PCODE MethodDesc::JitCompileCodeLockedEventWrapper(PrepareCodeConfig* pConfig, J
if (isInterpreterCode)
{
// If this is interpreter code, we need to get the native code start address from the interpreter Precode
+#ifdef FEATURE_PORTABLE_ENTRYPOINTS
+ InterpByteCodeStart* interpreterCode = (InterpByteCodeStart*)PortableEntryPoint::GetInterpreterData(pCode);
+#else // !FEATURE_PORTABLE_ENTRYPOINTS
InterpreterPrecode* pPrecode = InterpreterPrecode::FromEntryPoint(pCode);
InterpByteCodeStart* interpreterCode = dac_cast(pPrecode->GetData()->ByteCodeAddr);
+#endif // FEATURE_PORTABLE_ENTRYPOINTS
pNativeCodeStartAddress = PINSTRToPCODE(dac_cast(interpreterCode));
}
#endif // FEATURE_INTERPRETER
diff --git a/src/coreclr/vm/wasm/browserprofiler.cpp b/src/coreclr/vm/wasm/browserprofiler.cpp
new file mode 100644
index 00000000000000..3fc4689356e93c
--- /dev/null
+++ b/src/coreclr/vm/wasm/browserprofiler.cpp
@@ -0,0 +1,99 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#include "common.h"
+
+#ifdef TARGET_BROWSER
+
+#include
+#include "method.hpp"
+#include "typestring.h"
+#include "wasm/browserprofiler.h"
+
+// Inline JS for performance.measure() — records a timing entry to the
+// browser DevTools Performance tab. UTF8ToString is an Emscripten
+// runtime method exported via EMCC_EXPORTED_RUNTIME_METHODS.
+EM_JS(void, BrowserProfiler_RecordMeasure, (const char *name, double start), {
+ globalThis.performance.measure(UTF8ToString(name), { start : start });
+})
+
+static constexpr int MAX_STACK_DEPTH = 600;
+
+struct ProfilerStackFrame
+{
+ MethodDesc *pMethod;
+ double startMs;
+ bool shouldRecord;
+};
+
+static ProfilerStackFrame s_profilerStack[MAX_STACK_DEPTH];
+static int s_topStackFrameIndex = -1;
+
+// Adaptive sampling state — controls how often we actually call
+// performance.measure(). The shadow stack always tracks enter/leave
+// for correctness, but recording is rate-limited.
+static int32_t s_browserSkipsPerPeriod = 10;
+static int32_t s_browserSampleSkipCounter = 0;
+static double s_browserLastRecordTimeMs = 0.0;
+static constexpr double s_desiredRecordIntervalMs = 1.0;
+
+static bool ShouldRecordFrame()
+{
+ if (++s_browserSampleSkipCounter < s_browserSkipsPerPeriod)
+ return false;
+
+ double now = emscripten_get_now();
+ if (s_browserLastRecordTimeMs > 0.0)
+ {
+ double elapsed = now - s_browserLastRecordTimeMs;
+ if (elapsed > 0.0)
+ {
+ double ratio = s_desiredRecordIntervalMs / elapsed;
+ int32_t newSkips = (int32_t)((double)s_browserSampleSkipCounter * ratio);
+ if (newSkips < 1)
+ newSkips = 1;
+ s_browserSkipsPerPeriod = newSkips;
+ }
+ }
+
+ s_browserLastRecordTimeMs = now;
+ s_browserSampleSkipCounter = 0;
+
+ return true;
+}
+
+void BrowserProfiler_OnMethodEnter(void *pMethodDesc)
+{
+ MethodDesc *pMD = (MethodDesc *)pMethodDesc;
+
+ s_topStackFrameIndex++;
+ _ASSERTE(s_topStackFrameIndex < MAX_STACK_DEPTH);
+
+ ProfilerStackFrame *frame = &s_profilerStack[s_topStackFrameIndex];
+ frame->pMethod = pMD;
+ frame->startMs = emscripten_get_now();
+ frame->shouldRecord = ShouldRecordFrame();
+}
+
+void BrowserProfiler_OnMethodLeave()
+{
+ if (s_topStackFrameIndex < 0)
+ return;
+
+ ProfilerStackFrame *frame = &s_profilerStack[s_topStackFrameIndex];
+
+ if (frame->shouldRecord)
+ {
+ SString methodName;
+ TypeString::AppendMethodInternal(methodName, frame->pMethod, TypeString::FormatNamespace);
+ BrowserProfiler_RecordMeasure(methodName.GetUTF8(), frame->startMs);
+
+ // Mark parent frame for recording so the flame chart nests properly.
+ if (s_topStackFrameIndex > 0)
+ s_profilerStack[s_topStackFrameIndex - 1].shouldRecord = true;
+ }
+
+ s_topStackFrameIndex--;
+}
+
+#endif // TARGET_BROWSER
diff --git a/src/coreclr/vm/wasm/browserprofiler.h b/src/coreclr/vm/wasm/browserprofiler.h
new file mode 100644
index 00000000000000..091fad2ac606bb
--- /dev/null
+++ b/src/coreclr/vm/wasm/browserprofiler.h
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#ifndef BROWSERPROFILER_H
+#define BROWSERPROFILER_H
+
+#ifdef TARGET_BROWSER
+
+// Browser DevTools profiler for CoreCLR interpreter on WASM.
+// Records method enter/leave events to the browser's Performance tab
+// via performance.measure(). Uses a shadow stack to track method timing.
+
+void BrowserProfiler_OnMethodEnter(void *pMethodDesc);
+void BrowserProfiler_OnMethodLeave();
+
+#endif // TARGET_BROWSER
+
+#endif // BROWSERPROFILER_H
diff --git a/src/coreclr/vm/wasm/callhelpers-interp-to-managed.cpp b/src/coreclr/vm/wasm/callhelpers-interp-to-managed.cpp
index c0265475c3447d..0837f9eb84fb87 100644
--- a/src/coreclr/vm/wasm/callhelpers-interp-to-managed.cpp
+++ b/src/coreclr/vm/wasm/callhelpers-interp-to-managed.cpp
@@ -182,6 +182,12 @@ namespace
*((int32_t*)pRet) = (*fptr)(ARG_I32(0), ARG_I32(1), ARG_I64(2));
}
+ static void CallFunc_I32_I32_I64_I32_I32_I32_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet)
+ {
+ int32_t (*fptr)(int32_t, int32_t, int64_t, int32_t, int32_t, int32_t, int32_t) = (int32_t (*)(int32_t, int32_t, int64_t, int32_t, int32_t, int32_t, int32_t))pcode;
+ *((int32_t*)pRet) = (*fptr)(ARG_I32(0), ARG_I32(1), ARG_I64(2), ARG_I32(3), ARG_I32(4), ARG_I32(5), ARG_I32(6));
+ }
+
static void CallFunc_I32_I32_IND_I32_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet)
{
int32_t (*fptr)(int32_t, int32_t, int32_t, int32_t, int32_t) = (int32_t (*)(int32_t, int32_t, int32_t, int32_t, int32_t))pcode;
@@ -250,6 +256,18 @@ namespace
*((int32_t*)pRet) = (*fptr)(&framePointer, ARG_I32(0), pPortableEntryPointContext);
}
+ static void CallFunc_I64_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet)
+ {
+ int32_t (*fptr)(int64_t) = (int32_t (*)(int64_t))pcode;
+ *((int32_t*)pRet) = (*fptr)(ARG_I64(0));
+ }
+
+ static void CallFunc_I64_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet)
+ {
+ int32_t (*fptr)(int64_t, int32_t) = (int32_t (*)(int64_t, int32_t))pcode;
+ *((int32_t*)pRet) = (*fptr)(ARG_I64(0), ARG_I32(1));
+ }
+
static void CallFunc_I64_I32_I64_I32_RetI32(PCODE pcode, int8_t* pArgs, int8_t* pRet)
{
int32_t (*fptr)(int64_t, int32_t, int64_t, int32_t) = (int32_t (*)(int64_t, int32_t, int64_t, int32_t))pcode;
@@ -353,6 +371,12 @@ namespace
*((int64_t*)pRet) = (*fptr)(ARG_I32(0), ARG_I32(1), ARG_I32(2));
}
+ static void CallFunc_I32_I32_I32_I32_I32_RetI64(PCODE pcode, int8_t* pArgs, int8_t* pRet)
+ {
+ int64_t (*fptr)(int32_t, int32_t, int32_t, int32_t, int32_t) = (int64_t (*)(int32_t, int32_t, int32_t, int32_t, int32_t))pcode;
+ *((int64_t*)pRet) = (*fptr)(ARG_I32(0), ARG_I32(1), ARG_I32(2), ARG_I32(3), ARG_I32(4));
+ }
+
static void CallFunc_I32_I32_I32_I64_RetI64(PCODE pcode, int8_t* pArgs, int8_t* pRet)
{
int64_t (*fptr)(int32_t, int32_t, int32_t, int64_t) = (int64_t (*)(int32_t, int32_t, int32_t, int64_t))pcode;
@@ -406,6 +430,24 @@ namespace
(*fptr)();
}
+ static void CallFunc_F64_F64_F64_F64_F64_F64_F64_F64_F64_I32_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet)
+ {
+ void (*fptr)(double, double, double, double, double, double, double, double, double, int32_t, int32_t) = (void (*)(double, double, double, double, double, double, double, double, double, int32_t, int32_t))pcode;
+ (*fptr)(ARG_F64(0), ARG_F64(1), ARG_F64(2), ARG_F64(3), ARG_F64(4), ARG_F64(5), ARG_F64(6), ARG_F64(7), ARG_F64(8), ARG_I32(9), ARG_I32(10));
+ }
+
+ static void CallFunc_F64_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet)
+ {
+ void (*fptr)(double, int32_t) = (void (*)(double, int32_t))pcode;
+ (*fptr)(ARG_F64(0), ARG_I32(1));
+ }
+
+ static void CallFunc_F64_I32_I32_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet)
+ {
+ void (*fptr)(double, int32_t, int32_t, int32_t) = (void (*)(double, int32_t, int32_t, int32_t))pcode;
+ (*fptr)(ARG_F64(0), ARG_I32(1), ARG_I32(2), ARG_I32(3));
+ }
+
NOINLINE static void CallFunc_F64_I32_I32_RetVoid_PE(PCODE pcode, int8_t* pArgs, int8_t* pRet, PCODE pPortableEntryPointContext)
{
alignas(16) int framePointer = TERMINATE_R2R_STACK_WALK;
@@ -432,6 +474,12 @@ namespace
(*fptr)(ARG_I32(0), ARG_I32(1));
}
+ static void CallFunc_I32_I32_F64_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet)
+ {
+ void (*fptr)(int32_t, int32_t, double) = (void (*)(int32_t, int32_t, double))pcode;
+ (*fptr)(ARG_I32(0), ARG_I32(1), ARG_F64(2));
+ }
+
static void CallFunc_I32_I32_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet)
{
void (*fptr)(int32_t, int32_t, int32_t) = (void (*)(int32_t, int32_t, int32_t))pcode;
@@ -456,6 +504,12 @@ namespace
(*fptr)(ARG_I32(0), ARG_I32(1), ARG_I32(2), ARG_I32(3), ARG_I32(4), ARG_I32(5));
}
+ static void CallFunc_I32_I32_I32_I32_I64_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet)
+ {
+ void (*fptr)(int32_t, int32_t, int32_t, int32_t, int64_t) = (void (*)(int32_t, int32_t, int32_t, int32_t, int64_t))pcode;
+ (*fptr)(ARG_I32(0), ARG_I32(1), ARG_I32(2), ARG_I32(3), ARG_I64(4));
+ }
+
static void CallFunc_I32_I32_I32_IND_IND_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet)
{
void (*fptr)(int32_t, int32_t, int32_t, int32_t, int32_t) = (void (*)(int32_t, int32_t, int32_t, int32_t, int32_t))pcode;
@@ -607,6 +661,7 @@ const StringToWasmSigThunk g_wasmThunks[] = {
{ "iiiini", (void*)&CallFunc_I32_I32_I32_IND_I32_RetI32 },
{ "iiiip", (void*)&CallFunc_I32_I32_I32_RetI32_PE },
{ "iiil", (void*)&CallFunc_I32_I32_I64_RetI32 },
+ { "iiiliiii", (void*)&CallFunc_I32_I32_I64_I32_I32_I32_I32_RetI32 },
{ "iiinii", (void*)&CallFunc_I32_I32_IND_I32_I32_RetI32 },
{ "iiiniin", (void*)&CallFunc_I32_I32_IND_I32_I32_IND_RetI32 },
{ "iiip", (void*)&CallFunc_I32_I32_RetI32_PE },
@@ -618,6 +673,8 @@ const StringToWasmSigThunk g_wasmThunks[] = {
{ "iini", (void*)&CallFunc_I32_IND_I32_RetI32 },
{ "iiniii", (void*)&CallFunc_I32_IND_I32_I32_I32_RetI32 },
{ "iip", (void*)&CallFunc_I32_RetI32_PE },
+ { "il", (void*)&CallFunc_I64_RetI32 },
+ { "ili", (void*)&CallFunc_I64_I32_RetI32 },
{ "ilili", (void*)&CallFunc_I64_I32_I64_I32_RetI32 },
{ "in", (void*)&CallFunc_IND_RetI32 },
{ "ini", (void*)&CallFunc_IND_I32_RetI32 },
@@ -635,6 +692,7 @@ const StringToWasmSigThunk g_wasmThunks[] = {
{ "l", (void*)&CallFunc_Void_RetI64 },
{ "li", (void*)&CallFunc_I32_RetI64 },
{ "liii", (void*)&CallFunc_I32_I32_I32_RetI64 },
+ { "liiiii", (void*)&CallFunc_I32_I32_I32_I32_I32_RetI64 },
{ "liiil", (void*)&CallFunc_I32_I32_I32_I64_RetI64 },
{ "lili", (void*)&CallFunc_I32_I64_I32_RetI64 },
{ "lillp", (void*)&CallFunc_I32_I64_I64_RetI64_PE },
@@ -643,14 +701,19 @@ const StringToWasmSigThunk g_wasmThunks[] = {
{ "lllp", (void*)&CallFunc_I64_I64_RetI64_PE },
{ "lp", (void*)&CallFunc_Void_RetI64_PE },
{ "v", (void*)&CallFunc_Void_RetVoid },
+ { "vdddddddddii", (void*)&CallFunc_F64_F64_F64_F64_F64_F64_F64_F64_F64_I32_I32_RetVoid },
+ { "vdi", (void*)&CallFunc_F64_I32_RetVoid },
+ { "vdiii", (void*)&CallFunc_F64_I32_I32_I32_RetVoid },
{ "vdiip", (void*)&CallFunc_F64_I32_I32_RetVoid_PE },
{ "vfiip", (void*)&CallFunc_F32_I32_I32_RetVoid_PE },
{ "vi", (void*)&CallFunc_I32_RetVoid },
{ "vii", (void*)&CallFunc_I32_I32_RetVoid },
+ { "viid", (void*)&CallFunc_I32_I32_F64_RetVoid },
{ "viii", (void*)&CallFunc_I32_I32_I32_RetVoid },
{ "viiii", (void*)&CallFunc_I32_I32_I32_I32_RetVoid },
{ "viiiii", (void*)&CallFunc_I32_I32_I32_I32_I32_RetVoid },
{ "viiiiii", (void*)&CallFunc_I32_I32_I32_I32_I32_I32_RetVoid },
+ { "viiiil", (void*)&CallFunc_I32_I32_I32_I32_I64_RetVoid },
{ "viiinn", (void*)&CallFunc_I32_I32_I32_IND_IND_RetVoid },
{ "viiinni", (void*)&CallFunc_I32_I32_I32_IND_IND_I32_RetVoid },
{ "viiip", (void*)&CallFunc_I32_I32_I32_RetVoid_PE },
diff --git a/src/coreclr/vm/wasm/callhelpers-reverse.cpp b/src/coreclr/vm/wasm/callhelpers-reverse.cpp
index 25ccba61ded632..2f6ed3e6f4dbe3 100644
--- a/src/coreclr/vm/wasm/callhelpers-reverse.cpp
+++ b/src/coreclr/vm/wasm/callhelpers-reverse.cpp
@@ -162,6 +162,19 @@ static void Call_System_Private_CoreLib_System_Runtime_CompilerServices_RuntimeH
ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_System_Runtime_CompilerServices_RuntimeHelpers_CallToString_I32_I32_I32_RetVoid, (int8_t*)args, sizeof(args), nullptr, (PCODE)&Call_System_Private_CoreLib_System_Runtime_CompilerServices_RuntimeHelpers_CallToString_I32_I32_I32_RetVoid);
}
+static MethodDesc* MD_System_Private_CoreLib_System_Diagnostics_Tracing_EventPipeEventProvider_Callback_I32_I32_I32_I64_I64_I32_I32_RetVoid = nullptr;
+static void Call_System_Private_CoreLib_System_Diagnostics_Tracing_EventPipeEventProvider_Callback_I32_I32_I32_I64_I64_I32_I32_RetVoid(void * arg0, int32_t arg1, uint32_t arg2, int64_t arg3, int64_t arg4, void * arg5, void * arg6)
+{
+ int64_t args[7] = { (int64_t)arg0, (int64_t)arg1, (int64_t)arg2, (int64_t)arg3, (int64_t)arg4, (int64_t)arg5, (int64_t)arg6 };
+
+ // Lazy lookup of MethodDesc for the function export scenario.
+ if (!MD_System_Private_CoreLib_System_Diagnostics_Tracing_EventPipeEventProvider_Callback_I32_I32_I32_I64_I64_I32_I32_RetVoid)
+ {
+ LookupUnmanagedCallersOnlyMethodByName("System.Diagnostics.Tracing.EventPipeEventProvider, System.Private.CoreLib", "Callback", &MD_System_Private_CoreLib_System_Diagnostics_Tracing_EventPipeEventProvider_Callback_I32_I32_I32_I64_I64_I32_I32_RetVoid);
+ }
+ ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_System_Diagnostics_Tracing_EventPipeEventProvider_Callback_I32_I32_I32_I64_I64_I32_I32_RetVoid, (int8_t*)args, sizeof(args), nullptr, (PCODE)&Call_System_Private_CoreLib_System_Diagnostics_Tracing_EventPipeEventProvider_Callback_I32_I32_I32_I64_I64_I32_I32_RetVoid);
+}
+
static MethodDesc* MD_System_Private_CoreLib_System_StubHelpers_MngdRefCustomMarshaler_ClearManaged_I32_I32_I32_I32_RetVoid = nullptr;
static void Call_System_Private_CoreLib_System_StubHelpers_MngdRefCustomMarshaler_ClearManaged_I32_I32_I32_I32_RetVoid(void * arg0, void * arg1, void * arg2, void * arg3)
{
@@ -1207,6 +1220,7 @@ const ReverseThunkMapEntry g_ReverseThunks[] =
{ 433365813, "CallJSExport#2:System.Runtime.InteropServices.JavaScript:System.Runtime.InteropServices.JavaScript:JavaScriptExports", { &MD_System_Runtime_InteropServices_JavaScript_System_Runtime_InteropServices_JavaScript_JavaScriptExports_CallJSExport_I32_I32_RetVoid, (void*)&Call_System_Runtime_InteropServices_JavaScript_System_Runtime_InteropServices_JavaScript_JavaScriptExports_CallJSExport_I32_I32_RetVoid } },
{ 1821934012, "CallStartupHook#2:System.Private.CoreLib:System:StartupHookProvider", { &MD_System_Private_CoreLib_System_StartupHookProvider_CallStartupHook_I32_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_StartupHookProvider_CallStartupHook_I32_I32_RetVoid } },
{ 2915047114, "CallToString#3:System.Private.CoreLib:System.Runtime.CompilerServices:RuntimeHelpers", { &MD_System_Private_CoreLib_System_Runtime_CompilerServices_RuntimeHelpers_CallToString_I32_I32_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_Runtime_CompilerServices_RuntimeHelpers_CallToString_I32_I32_I32_RetVoid } },
+ { 4077371982, "Callback#7:System.Private.CoreLib:System.Diagnostics.Tracing:EventPipeEventProvider", { &MD_System_Private_CoreLib_System_Diagnostics_Tracing_EventPipeEventProvider_Callback_I32_I32_I32_I64_I64_I32_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_Diagnostics_Tracing_EventPipeEventProvider_Callback_I32_I32_I32_I64_I64_I32_I32_RetVoid } },
{ 3358042195, "ClearManaged#4:System.Private.CoreLib:System.StubHelpers:MngdRefCustomMarshaler", { &MD_System_Private_CoreLib_System_StubHelpers_MngdRefCustomMarshaler_ClearManaged_I32_I32_I32_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_StubHelpers_MngdRefCustomMarshaler_ClearManaged_I32_I32_I32_I32_RetVoid } },
{ 2311968855, "ClearNative#4:System.Private.CoreLib:System.StubHelpers:MngdRefCustomMarshaler", { &MD_System_Private_CoreLib_System_StubHelpers_MngdRefCustomMarshaler_ClearNative_I32_I32_I32_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_StubHelpers_MngdRefCustomMarshaler_ClearNative_I32_I32_I32_I32_RetVoid } },
{ 3113228365, "CompleteTask#1:System.Runtime.InteropServices.JavaScript:System.Runtime.InteropServices.JavaScript:JavaScriptExports", { &MD_System_Runtime_InteropServices_JavaScript_System_Runtime_InteropServices_JavaScript_JavaScriptExports_CompleteTask_I32_RetVoid, (void*)&Call_System_Runtime_InteropServices_JavaScript_System_Runtime_InteropServices_JavaScript_JavaScriptExports_CompleteTask_I32_RetVoid } },
diff --git a/src/mono/browser/build/BrowserWasmApp.CoreCLR.targets b/src/mono/browser/build/BrowserWasmApp.CoreCLR.targets
index 02f69b8e2be52c..0da4c31c91cb21 100644
--- a/src/mono/browser/build/BrowserWasmApp.CoreCLR.targets
+++ b/src/mono/browser/build/BrowserWasmApp.CoreCLR.targets
@@ -50,6 +50,23 @@
<_ExeExt Condition="$([MSBuild]::IsOSPlatform('windows'))">.exe
+
+
+ <_WasmPerfInstFilter>$(WasmPerformanceInstrumentation)
+ <_WasmPerfInstInterval>
+ <_WasmPerfInstInterval Condition="$(WasmPerformanceInstrumentation.Contains(',interval='))">$(WasmPerformanceInstrumentation.Substring($([MSBuild]::Add($(WasmPerformanceInstrumentation.IndexOf(',interval=')), 10))))
+ <_WasmPerfInstFilter Condition="$(WasmPerformanceInstrumentation.Contains(',interval='))">$(WasmPerformanceInstrumentation.Substring(0, $(WasmPerformanceInstrumentation.IndexOf(',interval='))))
+ <_WasmPerfInstInterval Condition="'$(_WasmPerfInstInterval)' != '' and $(_WasmPerfInstInterval.Contains(','))">$(_WasmPerfInstInterval.Substring(0, $(_WasmPerfInstInterval.IndexOf(','))))
+ <_WasmPerfInstFilter Condition="'$(_WasmPerfInstFilter)' == 'all'">*
+
+
+
+
+
+
diff --git a/src/mono/mono/eventpipe/ep-rt-mono.h b/src/mono/mono/eventpipe/ep-rt-mono.h
index ce5c7f7e95af96..89c796af904140 100644
--- a/src/mono/mono/eventpipe/ep-rt-mono.h
+++ b/src/mono/mono/eventpipe/ep-rt-mono.h
@@ -637,6 +637,19 @@ ep_rt_config_value_get_enable_stackwalk (void)
return value_uint32_t != 0;
}
+static
+inline
+uint32_t
+ep_rt_config_value_get_sampling_rate (void)
+{
+ uint32_t value_uint32_t = 0;
+ gchar *value = g_getenv ("DOTNET_EventPipeCpuSamplingRate");
+ if (value)
+ value_uint32_t = (uint32_t)atoi (value);
+ g_free (value);
+ return value_uint32_t;
+}
+
/*
* EventPipeSampleProfiler.
*/
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 52d869fca3df6a..7b8572d494976c 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
@@ -144,10 +144,12 @@ Copyright (c) .NET Foundation. All rights reserved.
ComputeWasmVfs;
ResolveWasmOutputs;
_AddWasmDiagnosticPortsEnvironmentVariable;
+ _AddWasmPerformanceInstrumentationEnvironmentVariables;
$(GeneratePublishWasmBootJsonDependsOn);
_AddWasmDiagnosticPortsEnvironmentVariable;
+ _AddWasmPerformanceInstrumentationEnvironmentVariables;
@@ -485,7 +487,26 @@ Copyright (c) .NET Foundation. All rights reserved.
-
+
+
+
+
+
+
+
+ <_WasmPerfInstFilter>$(WasmPerformanceInstrumentation)
+ <_WasmPerfInstInterval>
+ <_WasmPerfInstInterval Condition="$(WasmPerformanceInstrumentation.Contains(',interval='))">$(WasmPerformanceInstrumentation.Substring($([MSBuild]::Add($(WasmPerformanceInstrumentation.IndexOf(',interval=')), 10))))
+ <_WasmPerfInstFilter Condition="$(WasmPerformanceInstrumentation.Contains(',interval='))">$(WasmPerformanceInstrumentation.Substring(0, $(WasmPerformanceInstrumentation.IndexOf(',interval='))))
+ <_WasmPerfInstInterval Condition="'$(_WasmPerfInstInterval)' != '' and $(_WasmPerfInstInterval.Contains(','))">$(_WasmPerfInstInterval.Substring(0, $(_WasmPerfInstInterval.IndexOf(','))))
+ <_WasmPerfInstFilter Condition="'$(_WasmPerfInstFilter)' == 'all'">*
+
+
+
+
diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/EventPipeDiagnosticsTests.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/EventPipeDiagnosticsTests.cs
index 7f359c9e864a53..3ee0c013877bfb 100644
--- a/src/mono/wasm/Wasm.Build.Tests/Blazor/EventPipeDiagnosticsTests.cs
+++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/EventPipeDiagnosticsTests.cs
@@ -17,7 +17,6 @@
namespace Wasm.Build.Tests.Blazor;
-[TestCategory("mono")]
public class EventPipeDiagnosticsTests : BlazorWasmTestBase
{
private static readonly string uploadPattern = "^[a-zA-Z0-9_]+\\.nettrace$";
diff --git a/src/native/corehost/browserhost/CMakeLists.txt b/src/native/corehost/browserhost/CMakeLists.txt
index a5b68de643fc0f..46c3fc71f1a7a2 100644
--- a/src/native/corehost/browserhost/CMakeLists.txt
+++ b/src/native/corehost/browserhost/CMakeLists.txt
@@ -54,6 +54,8 @@ set(SHARED_LIB_DESTINATION
${CLR_ARTIFACTS_BIN_DIR}/native/net${CMAKE_NET_CORE_APP_CURRENT_VERSION}-browser-${CMAKE_BUILD_LIBRARIES_CONFIGURATION}-wasm/sharedFramework)
set(SHARED_CLR_DESTINATION
${CLR_ARTIFACTS_BIN_DIR}/coreclr/browser.wasm.${CMAKE_BUILD_RUNTIME_CONFIGURATION}/sharedFramework)
+set(CLR_LIB_DESTINATION
+ ${CLR_ARTIFACTS_BIN_DIR}/coreclr/browser.wasm.${CMAKE_BUILD_RUNTIME_CONFIGURATION}/lib)
# CoreCLR runtime .a libraries
LIST(APPEND NATIVE_LIBS
@@ -63,6 +65,7 @@ LIST(APPEND NATIVE_LIBS
${SHARED_CLR_DESTINATION}/libcoreclrminipal.a
${SHARED_CLR_DESTINATION}/libcoreclrpal.a
${SHARED_CLR_DESTINATION}/libminipal.a
+ ${CLR_LIB_DESTINATION}/libeventprovider.a
)
# Shared platform .a libraries
diff --git a/src/native/eventpipe/ds-ipc-pal-websocket.h b/src/native/eventpipe/ds-ipc-pal-websocket.h
index 1273d9367c9d41..9b948ab89ed4db 100644
--- a/src/native/eventpipe/ds-ipc-pal-websocket.h
+++ b/src/native/eventpipe/ds-ipc-pal-websocket.h
@@ -53,11 +53,17 @@ struct _DiagnosticsIpcStream {
};
#endif
+#ifdef __cplusplus
+extern "C" {
+#endif
extern int ds_rt_websocket_poll (int client_socket);
extern int ds_rt_websocket_create (const char* url);
-extern int ds_rt_websocket_recv (int client_socket, const uint8_t* buffer, uint32_t bytes_to_read);
+extern int ds_rt_websocket_recv (int client_socket, uint8_t* buffer, uint32_t bytes_to_read);
extern int ds_rt_websocket_send (int client_socket, const uint8_t* buffer, uint32_t bytes_to_write);
extern int ds_rt_websocket_close(int client_socket);
+#ifdef __cplusplus
+}
+#endif
#endif /* ENABLE_PERFTRACING */
#endif /* __DIAGNOSTICS_IPC_PAL_WEB_SOCKET_H__ */
diff --git a/src/native/eventpipe/ep-rt.h b/src/native/eventpipe/ep-rt.h
index ef28cb5147240c..d3cc68db3ffded 100644
--- a/src/native/eventpipe/ep-rt.h
+++ b/src/native/eventpipe/ep-rt.h
@@ -200,6 +200,11 @@ inline
bool
ep_rt_config_value_get_enable_stackwalk (void);
+static
+inline
+uint32_t
+ep_rt_config_value_get_sampling_rate (void);
+
/*
* EventPipeSampleProfiler.
*/
diff --git a/src/native/eventpipe/ep-session.c b/src/native/eventpipe/ep-session.c
index 19ff91952ce31e..7036d087ceb54f 100644
--- a/src/native/eventpipe/ep-session.c
+++ b/src/native/eventpipe/ep-session.c
@@ -156,6 +156,7 @@ static size_t streaming_loop_tick(EventPipeSession *const session) {
bool events_written = false;
bool ok;
if (!ep_session_get_streaming_enabled (session)){
+ EP_ASSERT (session->buffer_manager != NULL);
session->streaming_thread = NULL;
ep_session_dec_ref (session);
return 1; // done
diff --git a/src/native/eventpipe/ep.c b/src/native/eventpipe/ep.c
index 0b45cf280d3fdc..5336982ae774b1 100644
--- a/src/native/eventpipe/ep.c
+++ b/src/native/eventpipe/ep.c
@@ -1496,7 +1496,13 @@ ep_init (void)
#else // PERFTRACING_DISABLE_THREADS
const uint32_t default_profiler_sample_rate_in_nanoseconds = 5000000; // 5 msec.
#endif // PERFTRACING_DISABLE_THREADS
- ep_sample_profiler_set_sampling_rate (default_profiler_sample_rate_in_nanoseconds);
+
+ // Allow overriding the sampling rate via DOTNET_EventPipeCpuSamplingRate (in milliseconds).
+ uint32_t configured_rate_ms = ep_rt_config_value_get_sampling_rate ();
+ if (configured_rate_ms > 0)
+ ep_sample_profiler_set_sampling_rate ((uint64_t)configured_rate_ms * 1000000);
+ else
+ ep_sample_profiler_set_sampling_rate (default_profiler_sample_rate_in_nanoseconds);
_ep_deferred_enable_session_ids = dn_vector_alloc_t (EventPipeSessionID);
_ep_deferred_disable_session_ids = dn_vector_alloc_t (EventPipeSessionID);
diff --git a/src/native/libs/System.Native.Browser/diagnostics/diagnostic-server.ts b/src/native/libs/System.Native.Browser/diagnostics/diagnostic-server.ts
index 9b07c7d21eafc9..d65005a122c310 100644
--- a/src/native/libs/System.Native.Browser/diagnostics/diagnostic-server.ts
+++ b/src/native/libs/System.Native.Browser/diagnostics/diagnostic-server.ts
@@ -90,12 +90,10 @@ export function connectDSRouter(url: string): void {
}
export function initializeDS() {
- /* WASM-TODO, do this only when true
const loaderConfig = dotnetApi.getConfig();
const diagnosticPorts = "DOTNET_DiagnosticPorts";
if (!loaderConfig.environmentVariables![diagnosticPorts]) {
loaderConfig.environmentVariables![diagnosticPorts] = "js://ready";
}
- */
initializeJsClient();
}
diff --git a/src/native/rollup.config.plugins.js b/src/native/rollup.config.plugins.js
index efbcffaf6eb9a3..f3e66ed90e2501 100644
--- a/src/native/rollup.config.plugins.js
+++ b/src/native/rollup.config.plugins.js
@@ -167,7 +167,11 @@ export function onwarn(warning) {
if (warning.code === "CIRCULAR_DEPENDENCY" && warning.ids.findIndex(id => {
return id.includes("marshal-to-cs")
|| id.includes("marshal-to-js")
- || id.includes("diagnostics-js");
+ || id.includes("diagnostics-js")
+ || id.includes("dotnet-gcdump")
+ || id.includes("dotnet-cpu-profiler")
+ || id.includes("dotnet-counters")
+ || id.includes("diagnostic-server-js");
}) !== -1) {
// ignore circular dependency warnings from marshal-to-cs <-> marshal-to-js and diagnostics
return;