4
4
5
5
using System . Diagnostics ;
6
6
using System . IO . Compression ;
7
+ using System . Runtime . InteropServices ;
7
8
using System . Text . Json ;
8
9
using ByteSizeLib ;
9
10
using Microsoft . Diagnostics . Tracing . Session ;
@@ -20,15 +21,19 @@ public abstract class UltraProfiler : IDisposable
20
21
private protected bool StopRequested ;
21
22
private protected readonly Stopwatch ProfilerClock ;
22
23
private protected TimeSpan LastTimeProgress ;
24
+ private readonly CancellationTokenSource _cancellationTokenSource ;
23
25
24
26
/// <summary>
25
27
/// Initializes a new instance of the <see cref="UltraProfiler"/> class.
26
28
/// </summary>
27
29
protected UltraProfiler ( )
28
30
{
29
31
ProfilerClock = new Stopwatch ( ) ;
32
+ _cancellationTokenSource = new CancellationTokenSource ( ) ;
30
33
}
31
34
35
+ protected CancellationToken CancellationToken => _cancellationTokenSource . Token ;
36
+
32
37
/// <summary>
33
38
/// Creates a new instance of the <see cref="UltraProfiler"/> class.
34
39
/// </summary>
@@ -41,7 +46,12 @@ public static UltraProfiler Create()
41
46
return new UltraProfilerEtw ( ) ;
42
47
}
43
48
44
- throw new PlatformNotSupportedException ( "Only Windows is supported" ) ;
49
+ if ( OperatingSystem . IsMacOS ( ) && RuntimeInformation . ProcessArchitecture == Architecture . Arm64 )
50
+ {
51
+ return new UltraProfilerEventPipe ( ) ;
52
+ }
53
+
54
+ throw new PlatformNotSupportedException ( "Only Windows or macOS+ARM64 are supported" ) ;
45
55
}
46
56
47
57
/// <summary>
@@ -170,12 +180,12 @@ public async Task<string> Run(UltraProfilerOptions ultraProfilerOptions)
170
180
{
171
181
await runner . OnStart ( ) ;
172
182
{
173
- var startTheRequestedProgramIfRequired = ( ) =>
183
+ var startTheRequestedProgramIfRequired = async ( ) =>
174
184
{
175
185
// Start a command line process if needed
176
186
if ( ultraProfilerOptions . ProgramPath is not null )
177
187
{
178
- var processState = StartProcess ( ultraProfilerOptions ) ;
188
+ var processState = await StartProcess ( runner , ultraProfilerOptions ) ;
179
189
processList . Add ( processState . Process ) ;
180
190
// Append the pid for a single process that we are attaching to
181
191
if ( singleProcess is null )
@@ -188,10 +198,11 @@ public async Task<string> Run(UltraProfilerOptions ultraProfilerOptions)
188
198
} ;
189
199
190
200
// If we have a delay, or we are asked to start paused, we start the process before the profiling starts
191
- bool hasExplicitProgramHasStarted = ultraProfilerOptions . DelayInSeconds != 0.0 || ultraProfilerOptions . Paused ;
201
+ // On macOS we always need to start the program before enabling profiling
202
+ bool hasExplicitProgramHasStarted = ultraProfilerOptions . DelayInSeconds != 0.0 || ultraProfilerOptions . Paused || OperatingSystem . IsMacOS ( ) ;
192
203
if ( hasExplicitProgramHasStarted )
193
204
{
194
- startTheRequestedProgramIfRequired ( ) ;
205
+ await startTheRequestedProgramIfRequired ( ) ;
195
206
}
196
207
197
208
// Wait for the process to start
@@ -213,7 +224,7 @@ public async Task<string> Run(UltraProfilerOptions ultraProfilerOptions)
213
224
// If we haven't started the program yet, we start it now (for explicit program path)
214
225
if ( ! hasExplicitProgramHasStarted )
215
226
{
216
- startTheRequestedProgramIfRequired ( ) ;
227
+ await startTheRequestedProgramIfRequired ( ) ;
217
228
}
218
229
219
230
foreach ( var process in processList )
@@ -282,32 +293,17 @@ public async Task<string> Run(UltraProfilerOptions ultraProfilerOptions)
282
293
283
294
var fileToConvert = await runner . FinishFileToConvert ( ) ;
284
295
285
- var jsonFinalFile = await Convert ( fileToConvert , processList . Select ( x => x . Id ) . ToList ( ) , ultraProfilerOptions ) ;
296
+ string jsonFinalFile = string . Empty ;
297
+ if ( ! string . IsNullOrEmpty ( fileToConvert ) )
298
+ {
299
+ jsonFinalFile = await Convert ( fileToConvert , processList . Select ( x => x . Id ) . ToList ( ) , ultraProfilerOptions ) ;
300
+ }
286
301
287
302
await runner . OnFinalCleanup ( ) ;
288
303
289
304
return jsonFinalFile ;
290
305
}
291
306
292
-
293
- private protected class ProfilerRunner
294
- {
295
- public required Func < Task > OnStart ;
296
-
297
- public required Func < Task > OnEnablingProfiling ;
298
-
299
- public required Func < long > OnProfiling ;
300
-
301
- public required Func < Task > OnStop ;
302
-
303
- public required Func < Task > OnCatch ;
304
-
305
- public required Func < Task > OnFinally ;
306
-
307
- public required Func < Task < string > > FinishFileToConvert ;
308
-
309
- public required Func < Task > OnFinalCleanup ;
310
- }
311
307
312
308
private protected abstract ProfilerRunner CreateRunner ( UltraProfilerOptions ultraProfilerOptions , List < Process > processList , string baseName , Process ? singleProcess ) ;
313
309
@@ -415,7 +411,7 @@ private protected async Task WaitForStaleFile(string file, UltraProfilerOptions
415
411
}
416
412
}
417
413
418
- private protected static ProcessState StartProcess ( UltraProfilerOptions ultraProfilerOptions )
414
+ private protected static async Task < ProcessState > StartProcess ( ProfilerRunner runner , UltraProfilerOptions ultraProfilerOptions )
419
415
{
420
416
var mode = ultraProfilerOptions . ConsoleMode ;
421
417
@@ -437,6 +433,11 @@ private protected static ProcessState StartProcess(UltraProfilerOptions ultraPro
437
433
startInfo . CreateNoWindow = true ;
438
434
startInfo . WindowStyle = ProcessWindowStyle . Hidden ;
439
435
436
+ if ( runner . OnPrepareStartProcess != null )
437
+ {
438
+ await runner . OnPrepareStartProcess ( startInfo ) ;
439
+ }
440
+
440
441
process . Start ( ) ;
441
442
}
442
443
else
@@ -463,6 +464,11 @@ private protected static ProcessState StartProcess(UltraProfilerOptions ultraPro
463
464
}
464
465
} ;
465
466
467
+ if ( runner . OnPrepareStartProcess != null )
468
+ {
469
+ await runner . OnPrepareStartProcess ( startInfo ) ;
470
+ }
471
+
466
472
process . Start ( ) ;
467
473
468
474
process . BeginOutputReadLine ( ) ;
@@ -490,6 +496,11 @@ private protected static ProcessState StartProcess(UltraProfilerOptions ultraPro
490
496
} ;
491
497
thread . Start ( ) ;
492
498
499
+ if ( runner . OnProcessStarted != null )
500
+ {
501
+ await runner . OnProcessStarted ( process ) ;
502
+ }
503
+
493
504
return state ;
494
505
}
495
506
@@ -503,6 +514,31 @@ private void WaitForCleanCancel()
503
514
}
504
515
}
505
516
517
+ private protected class ProfilerRunner ( string baseFileName )
518
+ {
519
+ public string BaseFileName { get ; } = baseFileName ;
520
+
521
+ public required Func < Task > OnStart ;
522
+
523
+ public required Func < Task > OnEnablingProfiling ;
524
+
525
+ public required Func < long > OnProfiling ;
526
+
527
+ public required Func < Task > OnStop ;
528
+
529
+ public Func < ProcessStartInfo , Task > ? OnPrepareStartProcess ;
530
+
531
+ public Func < Process , Task > ? OnProcessStarted ;
532
+
533
+ public required Func < Task > OnCatch ;
534
+
535
+ public required Func < Task > OnFinally ;
536
+
537
+ public required Func < Task < string > > FinishFileToConvert ;
538
+
539
+ public required Func < Task > OnFinalCleanup ;
540
+ }
541
+
506
542
private protected class ProcessState
507
543
{
508
544
public ProcessState ( Process process )
0 commit comments