Skip to content

Commit 96a2a20

Browse files
committed
Add support for paused. Add that CTRL+C can be pressed (#12)
1 parent 67cba04 commit 96a2a20

File tree

3 files changed

+103
-34
lines changed

3 files changed

+103
-34
lines changed

src/Ultra.Core/EtwUltraProfiler.cs

+62-33
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ public static bool IsElevated()
5757

5858
public async Task<string> Run(EtwUltraProfilerOptions ultraProfilerOptions)
5959
{
60+
if (ultraProfilerOptions.Paused && ultraProfilerOptions.ShouldStartProfiling is null)
61+
{
62+
throw new ArgumentException("ShouldStartProfiling is required when Paused is set to true");
63+
}
64+
6065
List<System.Diagnostics.Process> processList = new List<System.Diagnostics.Process>();
6166
if (ultraProfilerOptions.ProcessIds.Count > 0)
6267
{
@@ -142,39 +147,10 @@ public async Task<string> Run(EtwUltraProfilerOptions ultraProfilerOptions)
142147
using (_userSession)
143148
using (_kernelSession)
144149
{
145-
_kernelSession.StopOnDispose = true;
146-
_kernelSession.CircularBufferMB = 0;
147-
_kernelSession.CpuSampleIntervalMSec = ultraProfilerOptions.CpuSamplingIntervalInMs;
148-
_kernelSession.StackCompression = false;
149-
150-
_userSession.StopOnDispose = true;
151-
_userSession.CircularBufferMB = 0;
152-
_userSession.CpuSampleIntervalMSec = ultraProfilerOptions.CpuSamplingIntervalInMs;
153-
_userSession.StackCompression = false;
154-
155-
var kernelEvents = KernelTraceEventParser.Keywords.Profile
156-
| KernelTraceEventParser.Keywords.ContextSwitch
157-
| KernelTraceEventParser.Keywords.ImageLoad
158-
| KernelTraceEventParser.Keywords.Process
159-
| KernelTraceEventParser.Keywords.Thread;
160-
_kernelSession.EnableKernelProvider(kernelEvents, KernelTraceEventParser.Keywords.Profile);
161-
162-
var jitEvents = ClrTraceEventParser.Keywords.JITSymbols |
163-
ClrTraceEventParser.Keywords.Exception |
164-
ClrTraceEventParser.Keywords.GC |
165-
ClrTraceEventParser.Keywords.GCHeapAndTypeNames |
166-
ClrTraceEventParser.Keywords.Interop |
167-
ClrTraceEventParser.Keywords.JITSymbols |
168-
ClrTraceEventParser.Keywords.Jit |
169-
ClrTraceEventParser.Keywords.JittedMethodILToNativeMap |
170-
ClrTraceEventParser.Keywords.Loader |
171-
ClrTraceEventParser.Keywords.Stack |
172-
ClrTraceEventParser.Keywords.StartEnumeration;
173-
174-
_userSession.EnableProvider(
175-
ClrTraceEventParser.ProviderGuid,
176-
TraceEventLevel.Verbose, // For call stacks.
177-
(ulong)jitEvents, options);
150+
if (!ultraProfilerOptions.Paused)
151+
{
152+
EnableProfiling(options, ultraProfilerOptions);
153+
}
178154

179155
HashSet<Process> exitedProcessList = new();
180156

@@ -191,6 +167,22 @@ public async Task<string> Run(EtwUltraProfilerOptions ultraProfilerOptions)
191167
singleProcess ??= processState.Process;
192168
}
193169

170+
// Wait for the process to start
171+
if (ultraProfilerOptions.Paused)
172+
{
173+
while (!ultraProfilerOptions.ShouldStartProfiling!() && !_cancelRequested && !_stopRequested)
174+
{
175+
}
176+
177+
// If we have a cancel request, we don't start the profiling
178+
if (_cancelRequested || _stopRequested)
179+
{
180+
throw new InvalidOperationException("CTRL+C requested");
181+
}
182+
183+
EnableProfiling(options, ultraProfilerOptions);
184+
}
185+
194186
foreach (var process in processList)
195187
{
196188
ultraProfilerOptions.LogProgress?.Invoke($"Start Profiling Process {process.ProcessName} ({process.Id})");
@@ -322,6 +314,43 @@ public async Task<string> Run(EtwUltraProfilerOptions ultraProfilerOptions)
322314
return jsonFinalFile;
323315
}
324316

317+
private void EnableProfiling(TraceEventProviderOptions options, EtwUltraProfilerOptions ultraProfilerOptions)
318+
{
319+
_kernelSession.StopOnDispose = true;
320+
_kernelSession.CircularBufferMB = 0;
321+
_kernelSession.CpuSampleIntervalMSec = ultraProfilerOptions.CpuSamplingIntervalInMs;
322+
_kernelSession.StackCompression = false;
323+
324+
_userSession.StopOnDispose = true;
325+
_userSession.CircularBufferMB = 0;
326+
_userSession.CpuSampleIntervalMSec = ultraProfilerOptions.CpuSamplingIntervalInMs;
327+
_userSession.StackCompression = false;
328+
329+
var kernelEvents = KernelTraceEventParser.Keywords.Profile
330+
| KernelTraceEventParser.Keywords.ContextSwitch
331+
| KernelTraceEventParser.Keywords.ImageLoad
332+
| KernelTraceEventParser.Keywords.Process
333+
| KernelTraceEventParser.Keywords.Thread;
334+
_kernelSession.EnableKernelProvider(kernelEvents, KernelTraceEventParser.Keywords.Profile);
335+
336+
var jitEvents = ClrTraceEventParser.Keywords.JITSymbols |
337+
ClrTraceEventParser.Keywords.Exception |
338+
ClrTraceEventParser.Keywords.GC |
339+
ClrTraceEventParser.Keywords.GCHeapAndTypeNames |
340+
ClrTraceEventParser.Keywords.Interop |
341+
ClrTraceEventParser.Keywords.JITSymbols |
342+
ClrTraceEventParser.Keywords.Jit |
343+
ClrTraceEventParser.Keywords.JittedMethodILToNativeMap |
344+
ClrTraceEventParser.Keywords.Loader |
345+
ClrTraceEventParser.Keywords.Stack |
346+
ClrTraceEventParser.Keywords.StartEnumeration;
347+
348+
_userSession.EnableProvider(
349+
ClrTraceEventParser.ProviderGuid,
350+
TraceEventLevel.Verbose, // For call stacks.
351+
(ulong)jitEvents, options);
352+
}
353+
325354
public async Task<string> Convert(string etlFile, List<int> pIds, EtwUltraProfilerOptions ultraProfilerOptions)
326355
{
327356
var etlProcessor = new EtwConverterToFirefox();

src/Ultra.Core/EtwUltraProfilerOptions.cs

+4
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ public EtwUltraProfilerOptions()
3030

3131
public int TimeOutAfterInMs { get; set; }
3232

33+
public bool Paused { get; set; }
34+
3335
public EtwUltraProfilerConsoleMode ConsoleMode { get; set; }
3436

3537
public Action<string>? LogProgress;
@@ -44,6 +46,8 @@ public EtwUltraProfilerOptions()
4446

4547
public Action<string>? ProgramLogStderr;
4648

49+
public Func<bool>? ShouldStartProfiling { get; set; }
50+
4751
public bool KeepEtlIntermediateFiles { get; set; }
4852

4953
public bool KeepMergedEtl { get; set; }

src/Ultra/Program.cs

+37-1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ static async Task<int> Main(string[] args)
4343
{ "pid=", "The {PID} of the process to attach the profiler to.", (int pid) => { pidList.Add(pid); } },
4444
{ "sampling-interval=", $"The {{VALUE}} of the sample interval in ms. Default is 8190Hz = {options.CpuSamplingIntervalInMs:0.000}ms.", (float v) => options.CpuSamplingIntervalInMs = v },
4545
{ "symbol-path=", $"The {{VALUE}} of symbol path. The default value is `{options.GetCachedSymbolPath()}`.", v => options.SymbolPathText = v },
46+
{ "paused", "Launch the profiler paused and wait for SPACE or ENTER keys to be pressed.", v => options.Paused = v is not null },
4647
{ "keep-merged-etl-file", "Keep the merged ETL file.", v => options.KeepMergedEtl = v is not null },
4748
{ "keep-intermediate-etl-files", "Keep the intermediate ETL files before merging.", v => options.KeepEtlIntermediateFiles = v is not null },
4849
{ "mode=", "Defines how the stdout/stderr of a program explicitly started by ultra should be integrated in its output. Default is `silent` which will not mix program's output. The other options are: `raw` is going to mix ultra and program output together in a raw output. `live` is going to mix ultra and program output within a live table.", v =>
@@ -92,9 +93,12 @@ static async Task<int> Main(string[] args)
9293
options.Arguments.AddRange(arguments.AsSpan().Slice(1));
9394
}
9495

96+
AnsiConsole.MarkupLine($"[green]You can press CTRL+C to stop profiling before the end of the process[/]");
97+
9598
options.EnsureDirectoryForBaseOutputFileName();
9699

97100
var etwProfiler = new EtwUltraProfiler();
101+
98102
Console.CancelKeyPress += (sender, eventArgs) =>
99103
{
100104
AnsiConsole.WriteLine();
@@ -106,7 +110,39 @@ static async Task<int> Main(string[] args)
106110
AnsiConsole.MarkupLine("[red]Stopped via CTRL+C[/]");
107111
}
108112
};
109-
113+
114+
// Handle paused
115+
if (options.Paused)
116+
{
117+
Console.TreatControlCAsInput = true;
118+
119+
options.ShouldStartProfiling = () =>
120+
{
121+
AnsiConsole.MarkupLine("[green]Press SPACE or ENTER to start profiling[/]");
122+
var key = Console.ReadKey(true);
123+
bool startProfiling = key.Key == ConsoleKey.Spacebar || key.Key == ConsoleKey.Enter;
124+
125+
bool isCtrlC = key.Modifiers == ConsoleModifiers.Control && key.Key == ConsoleKey.C;
126+
if (startProfiling || isCtrlC)
127+
{
128+
// Restore the default behavior so that CancelKeyPress will be called later if CTRL+C is pressed
129+
Console.TreatControlCAsInput = false;
130+
}
131+
132+
if (isCtrlC)
133+
{
134+
AnsiConsole.MarkupLine("[darkorange]Cancelled via CTRL+C[/]");
135+
etwProfiler.Cancel();
136+
}
137+
else if (!startProfiling)
138+
{
139+
AnsiConsole.MarkupLine($"[darkorange]Key pressed {key.Modifiers} {key.Key}[/]");
140+
}
141+
142+
return startProfiling;
143+
};
144+
}
145+
110146
if (options.ConsoleMode == EtwUltraProfilerConsoleMode.Silent)
111147
{
112148
await AnsiConsole.Status()

0 commit comments

Comments
 (0)