Skip to content

Commit

Permalink
Add support for paused. Add that CTRL+C can be pressed (#12)
Browse files Browse the repository at this point in the history
  • Loading branch information
xoofx committed Nov 27, 2024
1 parent 67cba04 commit 96a2a20
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 34 deletions.
95 changes: 62 additions & 33 deletions src/Ultra.Core/EtwUltraProfiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ public static bool IsElevated()

public async Task<string> Run(EtwUltraProfilerOptions ultraProfilerOptions)
{
if (ultraProfilerOptions.Paused && ultraProfilerOptions.ShouldStartProfiling is null)
{
throw new ArgumentException("ShouldStartProfiling is required when Paused is set to true");
}

List<System.Diagnostics.Process> processList = new List<System.Diagnostics.Process>();
if (ultraProfilerOptions.ProcessIds.Count > 0)
{
Expand Down Expand Up @@ -142,39 +147,10 @@ public async Task<string> Run(EtwUltraProfilerOptions ultraProfilerOptions)
using (_userSession)
using (_kernelSession)
{
_kernelSession.StopOnDispose = true;
_kernelSession.CircularBufferMB = 0;
_kernelSession.CpuSampleIntervalMSec = ultraProfilerOptions.CpuSamplingIntervalInMs;
_kernelSession.StackCompression = false;

_userSession.StopOnDispose = true;
_userSession.CircularBufferMB = 0;
_userSession.CpuSampleIntervalMSec = ultraProfilerOptions.CpuSamplingIntervalInMs;
_userSession.StackCompression = false;

var kernelEvents = KernelTraceEventParser.Keywords.Profile
| KernelTraceEventParser.Keywords.ContextSwitch
| KernelTraceEventParser.Keywords.ImageLoad
| KernelTraceEventParser.Keywords.Process
| KernelTraceEventParser.Keywords.Thread;
_kernelSession.EnableKernelProvider(kernelEvents, KernelTraceEventParser.Keywords.Profile);

var jitEvents = ClrTraceEventParser.Keywords.JITSymbols |
ClrTraceEventParser.Keywords.Exception |
ClrTraceEventParser.Keywords.GC |
ClrTraceEventParser.Keywords.GCHeapAndTypeNames |
ClrTraceEventParser.Keywords.Interop |
ClrTraceEventParser.Keywords.JITSymbols |
ClrTraceEventParser.Keywords.Jit |
ClrTraceEventParser.Keywords.JittedMethodILToNativeMap |
ClrTraceEventParser.Keywords.Loader |
ClrTraceEventParser.Keywords.Stack |
ClrTraceEventParser.Keywords.StartEnumeration;

_userSession.EnableProvider(
ClrTraceEventParser.ProviderGuid,
TraceEventLevel.Verbose, // For call stacks.
(ulong)jitEvents, options);
if (!ultraProfilerOptions.Paused)
{
EnableProfiling(options, ultraProfilerOptions);
}

HashSet<Process> exitedProcessList = new();

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

// Wait for the process to start
if (ultraProfilerOptions.Paused)
{
while (!ultraProfilerOptions.ShouldStartProfiling!() && !_cancelRequested && !_stopRequested)
{
}

// If we have a cancel request, we don't start the profiling
if (_cancelRequested || _stopRequested)
{
throw new InvalidOperationException("CTRL+C requested");
}

EnableProfiling(options, ultraProfilerOptions);
}

foreach (var process in processList)
{
ultraProfilerOptions.LogProgress?.Invoke($"Start Profiling Process {process.ProcessName} ({process.Id})");
Expand Down Expand Up @@ -322,6 +314,43 @@ public async Task<string> Run(EtwUltraProfilerOptions ultraProfilerOptions)
return jsonFinalFile;
}

private void EnableProfiling(TraceEventProviderOptions options, EtwUltraProfilerOptions ultraProfilerOptions)
{
_kernelSession.StopOnDispose = true;
_kernelSession.CircularBufferMB = 0;
_kernelSession.CpuSampleIntervalMSec = ultraProfilerOptions.CpuSamplingIntervalInMs;
_kernelSession.StackCompression = false;

_userSession.StopOnDispose = true;
_userSession.CircularBufferMB = 0;
_userSession.CpuSampleIntervalMSec = ultraProfilerOptions.CpuSamplingIntervalInMs;
_userSession.StackCompression = false;

var kernelEvents = KernelTraceEventParser.Keywords.Profile
| KernelTraceEventParser.Keywords.ContextSwitch
| KernelTraceEventParser.Keywords.ImageLoad
| KernelTraceEventParser.Keywords.Process
| KernelTraceEventParser.Keywords.Thread;
_kernelSession.EnableKernelProvider(kernelEvents, KernelTraceEventParser.Keywords.Profile);

var jitEvents = ClrTraceEventParser.Keywords.JITSymbols |
ClrTraceEventParser.Keywords.Exception |
ClrTraceEventParser.Keywords.GC |
ClrTraceEventParser.Keywords.GCHeapAndTypeNames |
ClrTraceEventParser.Keywords.Interop |
ClrTraceEventParser.Keywords.JITSymbols |
ClrTraceEventParser.Keywords.Jit |
ClrTraceEventParser.Keywords.JittedMethodILToNativeMap |
ClrTraceEventParser.Keywords.Loader |
ClrTraceEventParser.Keywords.Stack |
ClrTraceEventParser.Keywords.StartEnumeration;

_userSession.EnableProvider(
ClrTraceEventParser.ProviderGuid,
TraceEventLevel.Verbose, // For call stacks.
(ulong)jitEvents, options);
}

public async Task<string> Convert(string etlFile, List<int> pIds, EtwUltraProfilerOptions ultraProfilerOptions)
{
var etlProcessor = new EtwConverterToFirefox();
Expand Down
4 changes: 4 additions & 0 deletions src/Ultra.Core/EtwUltraProfilerOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ public EtwUltraProfilerOptions()

public int TimeOutAfterInMs { get; set; }

public bool Paused { get; set; }

public EtwUltraProfilerConsoleMode ConsoleMode { get; set; }

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

public Action<string>? ProgramLogStderr;

public Func<bool>? ShouldStartProfiling { get; set; }

public bool KeepEtlIntermediateFiles { get; set; }

public bool KeepMergedEtl { get; set; }
Expand Down
38 changes: 37 additions & 1 deletion src/Ultra/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ static async Task<int> Main(string[] args)
{ "pid=", "The {PID} of the process to attach the profiler to.", (int pid) => { pidList.Add(pid); } },
{ "sampling-interval=", $"The {{VALUE}} of the sample interval in ms. Default is 8190Hz = {options.CpuSamplingIntervalInMs:0.000}ms.", (float v) => options.CpuSamplingIntervalInMs = v },
{ "symbol-path=", $"The {{VALUE}} of symbol path. The default value is `{options.GetCachedSymbolPath()}`.", v => options.SymbolPathText = v },
{ "paused", "Launch the profiler paused and wait for SPACE or ENTER keys to be pressed.", v => options.Paused = v is not null },
{ "keep-merged-etl-file", "Keep the merged ETL file.", v => options.KeepMergedEtl = v is not null },
{ "keep-intermediate-etl-files", "Keep the intermediate ETL files before merging.", v => options.KeepEtlIntermediateFiles = v is not null },
{ "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 =>
Expand Down Expand Up @@ -92,9 +93,12 @@ static async Task<int> Main(string[] args)
options.Arguments.AddRange(arguments.AsSpan().Slice(1));
}

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

options.EnsureDirectoryForBaseOutputFileName();

var etwProfiler = new EtwUltraProfiler();

Console.CancelKeyPress += (sender, eventArgs) =>
{
AnsiConsole.WriteLine();
Expand All @@ -106,7 +110,39 @@ static async Task<int> Main(string[] args)
AnsiConsole.MarkupLine("[red]Stopped via CTRL+C[/]");
}
};


// Handle paused
if (options.Paused)
{
Console.TreatControlCAsInput = true;

options.ShouldStartProfiling = () =>
{
AnsiConsole.MarkupLine("[green]Press SPACE or ENTER to start profiling[/]");
var key = Console.ReadKey(true);
bool startProfiling = key.Key == ConsoleKey.Spacebar || key.Key == ConsoleKey.Enter;

bool isCtrlC = key.Modifiers == ConsoleModifiers.Control && key.Key == ConsoleKey.C;
if (startProfiling || isCtrlC)
{
// Restore the default behavior so that CancelKeyPress will be called later if CTRL+C is pressed
Console.TreatControlCAsInput = false;
}

if (isCtrlC)
{
AnsiConsole.MarkupLine("[darkorange]Cancelled via CTRL+C[/]");
etwProfiler.Cancel();
}
else if (!startProfiling)
{
AnsiConsole.MarkupLine($"[darkorange]Key pressed {key.Modifiers} {key.Key}[/]");
}

return startProfiling;
};
}

if (options.ConsoleMode == EtwUltraProfilerConsoleMode.Silent)
{
await AnsiConsole.Status()
Expand Down

0 comments on commit 96a2a20

Please sign in to comment.