Skip to content

Commit

Permalink
Add some public API comments (#3)
Browse files Browse the repository at this point in the history
  • Loading branch information
xoofx committed Nov 28, 2024
1 parent 4b521de commit 44642b0
Show file tree
Hide file tree
Showing 2 changed files with 165 additions and 56 deletions.
136 changes: 83 additions & 53 deletions src/Ultra.Core/EtwUltraProfiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@

namespace Ultra.Core;

/// <summary>
/// A profiler that uses Event Tracing for Windows (ETW) to collect performance data.
/// </summary>
public class EtwUltraProfiler : IDisposable
{
private TraceEventSession? _userSession;
Expand All @@ -23,13 +26,18 @@ public class EtwUltraProfiler : IDisposable
private readonly Stopwatch _profilerClock;
private TimeSpan _lastTimeProgress;


/// <summary>
/// Initializes a new instance of the <see cref="EtwUltraProfiler"/> class.
/// </summary>
public EtwUltraProfiler()
{
_profilerClock = new Stopwatch();
}


/// <summary>
/// Requests to cancel the profiling session.
/// </summary>
/// <returns>True if the profiling session was already canceled; otherwise, false.</returns>
public bool Cancel()
{
if (!_cancelRequested)
Expand All @@ -48,22 +56,36 @@ public bool Cancel()
}
}

private void WaitForCleanCancel()
/// <summary>
/// Releases all resources used by the <see cref="EtwUltraProfiler"/> class.
/// </summary>
public void Dispose()
{
if (_cleanCancel is not null)
{
_cleanCancel.WaitOne();
_cleanCancel.Dispose();
_cleanCancel = null;
}
_userSession?.Dispose();
_userSession = null;
_kernelSession?.Dispose();
_kernelSession = null;
_cleanCancel?.Dispose();
_cleanCancel = null;
}

/// <summary>
/// Determines whether the current process is running with elevated privileges.
/// </summary>
/// <returns>True if the current process is running with elevated privileges; otherwise, false.</returns>
public static bool IsElevated()
{
var isElevated = TraceEventSession.IsElevated();
return isElevated.HasValue && isElevated.Value;
}

/// <summary>
/// Runs the profiler with the specified options.
/// </summary>
/// <param name="ultraProfilerOptions">The options for the profiler.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the path to the generated JSON file.</returns>
/// <exception cref="ArgumentException">Thrown when the options are invalid.</exception>
/// <exception cref="InvalidOperationException">Thrown when a cancel request is received.</exception>
public async Task<string> Run(EtwUltraProfilerOptions ultraProfilerOptions)
{
if (ultraProfilerOptions.Paused && ultraProfilerOptions.ShouldStartProfiling is null)
Expand Down Expand Up @@ -97,7 +119,7 @@ public async Task<string> Run(EtwUltraProfilerOptions ultraProfilerOptions)
}
}
}

if (processList.Count == 0 && ultraProfilerOptions.ProgramPath is null)
{
throw new ArgumentException("pid is required or an executable with optional arguments");
Expand All @@ -106,7 +128,7 @@ public async Task<string> Run(EtwUltraProfilerOptions ultraProfilerOptions)
string? processName = null;

System.Diagnostics.Process? singleProcess = null;

if (processList.Count == 1 && ultraProfilerOptions.ProgramPath is null)
{
singleProcess = processList[0];
Expand All @@ -130,12 +152,12 @@ public async Task<string> Run(EtwUltraProfilerOptions ultraProfilerOptions)
{
baseName = $"{baseName}_pid_{singleProcess.Id}";
}

var options = new TraceEventProviderOptions()
{
StacksEnabled = true,
};

// Filter the requested process ids
if (processList.Count > 0)
{
Expand All @@ -151,7 +173,7 @@ public async Task<string> Run(EtwUltraProfilerOptions ultraProfilerOptions)
{
options.ProcessNameFilter = [Path.GetFileName(ultraProfilerOptions.ProgramPath)];
}

var kernelFileName = $"{baseName}.kernel.etl";
var userFileName = $"{baseName}.user.etl";

Expand Down Expand Up @@ -203,7 +225,7 @@ public async Task<string> Run(EtwUltraProfilerOptions ultraProfilerOptions)
throw new InvalidOperationException("CTRL+C requested");
}
}

await EnableProfiling(options, ultraProfilerOptions);

// If we haven't started the program yet, we start it now (for explicit program path)
Expand Down Expand Up @@ -255,7 +277,7 @@ public async Task<string> Run(EtwUltraProfilerOptions ultraProfilerOptions)
break;
}

} // Needed for JIT Compile code that was already compiled.
} // Needed for JIT Compile code that was already compiled.

_kernelSession.Stop();
_userSession.Stop();
Expand Down Expand Up @@ -307,7 +329,7 @@ public async Task<string> Run(EtwUltraProfilerOptions ultraProfilerOptions)
ClrRundownTraceEventParser.ProviderGuid,
TraceEventLevel.Verbose,
(ulong)(ClrRundownTraceEventParser.Keywords.Default & ~ClrRundownTraceEventParser.Keywords.Loader), options);

await WaitForStaleFile(rundownSession, ultraProfilerOptions);
}

Expand Down Expand Up @@ -347,7 +369,37 @@ public async Task<string> Run(EtwUltraProfilerOptions ultraProfilerOptions)
var etlxFinalFile = Path.ChangeExtension(etlFinalFile, ".etlx");
File.Delete(etlxFinalFile);
}


return jsonFinalFile;
}

/// <summary>
/// Converts the ETL file to a compressed JSON file in the Firefox Profiler format.
/// </summary>
/// <param name="etlFile">The path to the ETL file.</param>
/// <param name="pIds">The list of process IDs to include in the conversion.</param>
/// <param name="ultraProfilerOptions">The options for the profiler.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the path to the generated JSON file.</returns>
/// <exception cref="InvalidOperationException">Thrown when a stop request is received.</exception>
public async Task<string> Convert(string etlFile, List<int> pIds, EtwUltraProfilerOptions ultraProfilerOptions)
{
var etlProcessor = new EtwConverterToFirefox();
var profile = etlProcessor.Convert(etlFile, pIds, ultraProfilerOptions);

if (_stopRequested)
{
throw new InvalidOperationException("CTRL+C requested");
}

var directory = Path.GetDirectoryName(etlFile);
var etlFileNameWithoutExtension = Path.GetFileNameWithoutExtension(etlFile);
var jsonFinalFile = $"{ultraProfilerOptions.BaseOutputFileName ?? etlFileNameWithoutExtension}.json.gz";
ultraProfilerOptions.LogProgress?.Invoke($"Converting to Firefox Profiler JSON");
await using var stream = File.Create(jsonFinalFile);
await using var gzipStream = new GZipStream(stream, CompressionLevel.Optimal);
await JsonSerializer.SerializeAsync(gzipStream, profile, FirefoxProfiler.JsonProfilerContext.Default.Profile);
gzipStream.Flush();

return jsonFinalFile;
}

Expand Down Expand Up @@ -422,27 +474,6 @@ private async Task EnableProfiling(TraceEventProviderOptions options, EtwUltraPr
_profilerClock.Restart();
}

public async Task<string> Convert(string etlFile, List<int> pIds, EtwUltraProfilerOptions ultraProfilerOptions)
{
var etlProcessor = new EtwConverterToFirefox();
var profile = etlProcessor.Convert(etlFile, pIds, ultraProfilerOptions);

if (_stopRequested)
{
throw new InvalidOperationException("CTRL+C requested");
}

var directory = Path.GetDirectoryName(etlFile);
var etlFileNameWithoutExtension = Path.GetFileNameWithoutExtension(etlFile);
var jsonFinalFile = $"{ultraProfilerOptions.BaseOutputFileName ?? etlFileNameWithoutExtension}.json.gz";
ultraProfilerOptions.LogProgress?.Invoke($"Converting to Firefox Profiler JSON");
await using var stream = File.Create(jsonFinalFile);
await using var gzipStream = new GZipStream(stream, CompressionLevel.Optimal);
await JsonSerializer.SerializeAsync(gzipStream, profile, FirefoxProfiler.JsonProfilerContext.Default.Profile);
gzipStream.Flush();

return jsonFinalFile;
}

private async Task WaitForStaleFile(string file, EtwUltraProfilerOptions options)
{
Expand Down Expand Up @@ -481,7 +512,6 @@ private async Task WaitForStaleFile(string file, EtwUltraProfilerOptions options
}
}


private static ProcessState StartProcess(EtwUltraProfilerOptions ultraProfilerOptions)
{
var mode = ultraProfilerOptions.ConsoleMode;
Expand All @@ -497,7 +527,7 @@ private static ProcessState StartProcess(EtwUltraProfilerOptions ultraProfilerOp
}

ultraProfilerOptions.LogProgress?.Invoke($"Starting Process {startInfo.FileName} {string.Join(" ", startInfo.ArgumentList)}");

if (mode == EtwUltraProfilerConsoleMode.Silent)
{
startInfo.UseShellExecute = true;
Expand All @@ -513,7 +543,7 @@ private static ProcessState StartProcess(EtwUltraProfilerOptions ultraProfilerOp
startInfo.RedirectStandardOutput = true;
startInfo.RedirectStandardError = true;
startInfo.RedirectStandardInput = true;

process.OutputDataReceived += (sender, args) =>
{
if (args.Data != null)
Expand Down Expand Up @@ -559,7 +589,17 @@ private static ProcessState StartProcess(EtwUltraProfilerOptions ultraProfilerOp

return state;
}


private void WaitForCleanCancel()
{
if (_cleanCancel is not null)
{
_cleanCancel.WaitOne();
_cleanCancel.Dispose();
_cleanCancel = null;
}
}

private class ProcessState
{
public ProcessState(Process process)
Expand All @@ -571,14 +611,4 @@ public ProcessState(Process process)

public bool HasExited;
}

public void Dispose()
{
_userSession?.Dispose();
_userSession = null;
_kernelSession?.Dispose();
_kernelSession = null;
_cleanCancel?.Dispose();
_cleanCancel = null;
}
}
Loading

0 comments on commit 44642b0

Please sign in to comment.