Skip to content

Commit 6726fae

Browse files
authored
[wasm-ep] Implement EventSource counters support; add an EventPipeSessionOptions builder to TS (dotnet#69567)
1. Implements support for creating event counters in WebAssembly apps. **Important change** adds `Thread.IsInternalThreadStartSupported` and `Thread.InternalUnsafeStart()` to `System.Threading.Thread` that can be used by System.Private.CoreLib to start threads on a runtime where `Thread.IsThreadStartSupported` is false. This is used to create the event counters polling thread. This is in addition to the native runtime being able to create threads using `mono_thread_create_internal` 2. Coop thread suspend: STW calls `mono_threads_platform_stw_defer_initial_suspend` which postpones suspending a thread until the next GC suspend phase. Doesn't do anything on most platforms. On WASM, suspend the main browser thread after all the other threads are suspended. This helps prevent deadlocks where the main thread is suspended while another thread is doing a call that is proxied to the main thread and cannot complete. By suspending the main thread last, we give it a chance to service the other threads' requests. 2. Adds a `MONO.diagnostics.SessionOptionsBuilder` class that can be used to create an `EventPipeSessionOptions` instance and populate the provider string for an event pipe session. 3. Updated the `browser-eventpipe` sample to create an EventSource and some counters. The sample is now interactive so you have to click "Start Work" in a browser for things to happen. There's an outstanding issue dotnet#69568 that will be investigated in a follow-up PR * Add custom EventSource and counter - Added a method for coop GC to suspend the main browser thread in the second phase (so that other threads are less likely to deadlock if they delegate work to it) - Added an event provider builder - Added a Thread.InternalUnsafeStart() method and a IsInternalThreadStartSupported property. This allows EventSource to start a thread to poll the counter values. - The sample with counters can now record counter values. But not at the same time as the default configuration (runtime, runtime private, sample profiler). Not sure if it's a wasm issue or a limitation of EventPipe. * Change ProvidersConfigBuilder to SessionOptionsBuilder it creates an entire EventPipeSessionOptions object, not just the providers config * checkout interactive demo * Add docs; fix whitespace * more whitespace fixes * add default arg value to ThrowIfNoThreadStart * fix build * Review feedback
1 parent 1631928 commit 6726fae

File tree

15 files changed

+379
-68
lines changed

15 files changed

+379
-68
lines changed

src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/CounterGroup.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ private void EnableTimer(float pollingIntervalInSeconds)
158158
#if ES_BUILD_STANDALONE
159159
s_pollingThread.Start();
160160
#else
161-
s_pollingThread.UnsafeStart();
161+
s_pollingThread.InternalUnsafeStart();
162162
#endif
163163
}
164164

src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -153,20 +153,24 @@ public Thread(ParameterizedThreadStart start, int maxStackSize)
153153
Initialize();
154154
}
155155

156-
#if !TARGET_BROWSER
157-
internal static bool IsThreadStartSupported => true;
158-
#else
159-
#if FEATURE_WASM_THREADS
156+
#if !TARGET_BROWSER || FEATURE_WASM_THREADS
160157
internal static bool IsThreadStartSupported => true;
158+
internal static bool IsInternalThreadStartSupported => true;
159+
#elif FEATURE_WASM_PERFTRACING
160+
internal static bool IsThreadStartSupported => false;
161+
internal static bool IsInternalThreadStartSupported => true;
161162
#else
162163
internal static bool IsThreadStartSupported => false;
163-
#endif
164+
internal static bool IsInternalThreadStartSupported => false;
164165
#endif
165166

166-
internal static void ThrowIfNoThreadStart()
167+
internal static void ThrowIfNoThreadStart(bool internalThread = false)
167168
{
168-
if (!IsThreadStartSupported)
169-
throw new PlatformNotSupportedException();
169+
if (IsThreadStartSupported)
170+
return;
171+
if (IsInternalThreadStartSupported && internalThread)
172+
return;
173+
throw new PlatformNotSupportedException();
170174
}
171175

172176
/// <summary>Causes the operating system to change the state of the current instance to <see cref="ThreadState.Running"/>, and optionally supplies an object containing data to be used by the method the thread executes.</summary>
@@ -187,9 +191,9 @@ internal static void ThrowIfNoThreadStart()
187191
/// </remarks>
188192
public void UnsafeStart(object? parameter) => Start(parameter, captureContext: false);
189193

190-
private void Start(object? parameter, bool captureContext)
194+
private void Start(object? parameter, bool captureContext, bool internalThread = false)
191195
{
192-
ThrowIfNoThreadStart();
196+
ThrowIfNoThreadStart(internalThread);
193197

194198
StartHelper? startHelper = _startHelper;
195199

@@ -224,9 +228,11 @@ private void Start(object? parameter, bool captureContext)
224228
/// </remarks>
225229
public void UnsafeStart() => Start(captureContext: false);
226230

227-
private void Start(bool captureContext)
231+
internal void InternalUnsafeStart() => Start(captureContext: false, internalThread: true);
232+
233+
private void Start(bool captureContext, bool internalThread = false)
228234
{
229-
ThrowIfNoThreadStart();
235+
ThrowIfNoThreadStart(internalThread);
230236
StartHelper? startHelper = _startHelper;
231237

232238
// In the case of a null startHelper (second call to start on same thread)

src/mono/mono/metadata/threads.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4819,7 +4819,11 @@ ves_icall_System_Threading_Thread_StartInternal (MonoThreadObjectHandle thread_h
48194819
MonoThread *internal = MONO_HANDLE_RAW (thread_handle);
48204820
gboolean res;
48214821

4822-
#if defined (DISABLE_THREADS) || defined (DISABLE_WASM_USER_THREADS)
4822+
#if defined (DISABLE_THREADS)
4823+
/*
4824+
* N.B. not checking DISABLE_WASM_USER_THREADS - managed utility threads are allowed; we assume all
4825+
* callers of System.Thread.StartCore called System.Thread.ThrowIfNotSupported(bool internalThread)
4826+
*/
48234827
mono_error_set_platform_not_supported (error, "Cannot start threads on this runtime.");
48244828
return;
48254829
#endif

src/mono/mono/utils/mono-threads-wasm.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,23 @@ mono_threads_wasm_is_browser_thread (void)
393393
#endif
394394
}
395395

396+
MonoNativeThreadId
397+
mono_threads_wasm_browser_thread_tid (void)
398+
{
399+
#ifdef DISABLE_THREADS
400+
return (MonoNativeThreadId)1;
401+
#else
402+
return (MonoNativeThreadId)emscripten_main_browser_thread_id ();
403+
#endif
404+
}
405+
406+
gboolean
407+
mono_threads_platform_stw_defer_initial_suspend (MonoThreadInfo *info)
408+
{
409+
/* Suspend the browser thread after all the other threads are suspended already. */
410+
return mono_native_thread_id_equals (mono_thread_info_get_tid (info), mono_threads_wasm_browser_thread_tid ());
411+
}
412+
396413
#ifndef DISABLE_THREADS
397414
void
398415
mono_threads_wasm_async_run_in_main_thread (void (*func) (void))

src/mono/mono/utils/mono-threads-wasm.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#define __MONO_THREADS_WASM_H__
1010

1111
#include <glib.h>
12+
#include <mono/utils/mono-threads.h>
1213

1314
#ifdef HOST_WASM
1415

@@ -22,6 +23,9 @@
2223
gboolean
2324
mono_threads_wasm_is_browser_thread (void);
2425

26+
MonoNativeThreadId
27+
mono_threads_wasm_browser_thread_tid (void);
28+
2529
#ifndef DISABLE_THREADS
2630
/**
2731
* Runs the given function asynchronously on the main thread.

src/mono/mono/utils/mono-threads.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1099,6 +1099,8 @@ begin_suspend_peek_and_preempt (MonoThreadInfo *info);
10991099
MonoThreadBeginSuspendResult
11001100
mono_thread_info_begin_suspend (MonoThreadInfo *info, MonoThreadSuspendPhase phase)
11011101
{
1102+
if (phase == MONO_THREAD_SUSPEND_PHASE_INITIAL && mono_threads_platform_stw_defer_initial_suspend (info))
1103+
return MONO_THREAD_BEGIN_SUSPEND_NEXT_PHASE;
11021104
if (phase == MONO_THREAD_SUSPEND_PHASE_MOPUP && mono_threads_is_hybrid_suspension_enabled ())
11031105
return begin_suspend_peek_and_preempt (info);
11041106
else
@@ -2171,3 +2173,10 @@ mono_thread_info_get_tools_data (void)
21712173
return info ? info->tools_data : NULL;
21722174
}
21732175

2176+
#ifndef HOST_WASM
2177+
gboolean
2178+
mono_threads_platform_stw_defer_initial_suspend (MonoThreadInfo *info)
2179+
{
2180+
return FALSE;
2181+
}
2182+
#endif

src/mono/mono/utils/mono-threads.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,7 @@ void mono_threads_platform_init (void);
634634
gboolean mono_threads_platform_in_critical_region (THREAD_INFO_TYPE *info);
635635
gboolean mono_threads_platform_yield (void);
636636
void mono_threads_platform_exit (gsize exit_code);
637+
gboolean mono_threads_platform_stw_defer_initial_suspend (THREAD_INFO_TYPE *info);
637638

638639
void mono_threads_coop_begin_global_suspend (void);
639640
void mono_threads_coop_end_global_suspend (void);

src/mono/sample/wasm/browser-eventpipe/Program.cs

Lines changed: 85 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,65 @@
55
using System.Runtime.CompilerServices;
66
using System.Threading;
77
using System.Threading.Tasks;
8+
using System.Diagnostics.Tracing;
89

910

1011
namespace Sample
1112
{
13+
14+
[EventSource(Name = "WasmHello")]
15+
public class WasmHelloEventSource : EventSource
16+
{
17+
public static readonly WasmHelloEventSource Instance = new ();
18+
19+
private IncrementingEventCounter _calls;
20+
21+
private WasmHelloEventSource ()
22+
{
23+
}
24+
25+
[NonEvent]
26+
public void NewCallsCounter()
27+
{
28+
_calls?.Dispose();
29+
_calls = new ("fib-calls", this)
30+
{
31+
DisplayName = "Recursive Fib calls",
32+
};
33+
}
34+
35+
[NonEvent]
36+
public void CountCall() {
37+
_calls?.Increment(1.0);
38+
}
39+
40+
protected override void Dispose (bool disposing)
41+
{
42+
_calls?.Dispose();
43+
_calls = null;
44+
45+
base.Dispose(disposing);
46+
}
47+
48+
[Event(1, Message="Started Fib({0})", Level = EventLevel.Informational)]
49+
public void StartFib(int n)
50+
{
51+
if (!IsEnabled())
52+
return;
53+
54+
WriteEvent(1, n);
55+
}
56+
57+
[Event(2, Message="Stopped Fib({0}) = {1}", Level = EventLevel.Informational)]
58+
public void StopFib(int n, string result)
59+
{
60+
if (!IsEnabled())
61+
return;
62+
63+
WriteEvent(2, n, result);
64+
}
65+
}
66+
1267
public class Test
1368
{
1469
public static void Main(string[] args)
@@ -34,24 +89,34 @@ private static long recursiveFib (int n)
3489
return 0;
3590
if (n == 1)
3691
return 1;
92+
WasmHelloEventSource.Instance.CountCall();
3793
return recursiveFib (n - 1) + recursiveFib (n - 2);
3894
}
3995

40-
public static async Task<int> StartAsyncWork()
96+
public static async Task<double> StartAsyncWork(int N)
4197
{
4298
CancellationToken ct = GetCancellationToken();
99+
await Task.Delay(1);
43100
long b;
44-
const int N = 35;
45-
const long expected = 9227465;
101+
WasmHelloEventSource.Instance.NewCallsCounter();
102+
iterations = 0;
46103
while (true)
47104
{
48-
await Task.Delay(1).ConfigureAwait(false);
105+
WasmHelloEventSource.Instance.StartFib(N);
106+
await Task.Delay(1);
49107
b = recursiveFib (N);
108+
WasmHelloEventSource.Instance.StopFib(N, b.ToString());
109+
iterations++;
50110
if (ct.IsCancellationRequested)
51111
break;
52-
iterations++;
53112
}
54-
return b == expected ? 42 : 0;
113+
long expected = fastFib(N);
114+
if (expected == b)
115+
return (double)b;
116+
else {
117+
Console.Error.WriteLine ("expected {0}, but got {1}", expected, b);
118+
return 0.0;
119+
}
55120
}
56121

57122
public static void StopWork()
@@ -63,5 +128,19 @@ public static string GetIterationsDone()
63128
{
64129
return iterations.ToString();
65130
}
131+
132+
private static long fastFib(int N) {
133+
if (N < 1)
134+
return 0;
135+
long a = 0;
136+
long b = 1;
137+
for (int i = 1; i < N; ++i) {
138+
long tmp = a+b;
139+
a = b;
140+
b = tmp;
141+
}
142+
return b;
143+
}
144+
66145
}
67146
}

src/mono/sample/wasm/browser-eventpipe/Wasm.Browser.EventPipe.Sample.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
<GenerateRunScriptForSample Condition="'$(ArchiveTests)' == 'true'">true</GenerateRunScriptForSample>
1111
<RunScriptCommand>$(ExecXHarnessCmd) wasm test-browser --app=. --browser=Chrome $(XHarnessBrowserPathArg) --html-file=index.html --output-directory=$(XHarnessOutput) -- $(MSBuildProjectName).dll</RunScriptCommand>
1212
<FeatureWasmPerfTracing>true</FeatureWasmPerfTracing>
13+
<NoWarn>CA2007</NoWarn> <!-- consider ConfigureAwait() -->
1314
</PropertyGroup>
1415

1516
<ItemGroup>

src/mono/sample/wasm/browser-eventpipe/index.html

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@
1111

1212
<body>
1313
<h3 id="header">Wasm Browser EventPipe profiling Sample</h3>
14-
Computing Fib repeatedly: <span id="out"></span>
14+
<div>
15+
<button id="startWork">Start Work</button>
16+
<label for="inputN">N:</label> <input type="number" id="inputN" value="10"/>
17+
</div>
18+
<div>Computing Fib(N) repeatedly: <span id="out"></span>
1519
<script type="text/javascript" src="./dotnet.js"></script>
1620
<script type="text/javascript" src="./dotnet.worker.js"></script>
1721
<script type="text/javascript" src="./main.js"></script>

0 commit comments

Comments
 (0)