Skip to content

Commit

Permalink
Add support for raw and live output (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
xoofx committed Nov 24, 2024
1 parent f15d1ec commit 31e7fae
Show file tree
Hide file tree
Showing 7 changed files with 391 additions and 60 deletions.
Binary file added doc/profile_mode_live.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
43 changes: 38 additions & 5 deletions doc/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,22 @@ $ ultra.exe profile -- my_command.exe arg0 arg1 arg2...

This will create a `ultra_my_command_..._.json.gz` trace file in the current directory.

By default, ultra won't show the stdout/stderr of the program launched. You can change this behavior by specifying the `--mode` option:

- `silent` (default): won't mix program's output
- `raw`: will mix ultra and program output together in a raw output. Ultra output will be prefixed at the start of a line with `>>ultra::`
- `live`: will mix ultra and program output within a live table

For example, a profile with `live` mode:

```console
$ ultra.exe profile --mode live -- my_command.exe arg0 arg1 arg2...
```

will display the following live table when running your process:

![Live ultra mode](profile_mode_live.png)

When attaching an existing process, you can pass directly a PID to ultra.exe:

```console
Expand Down Expand Up @@ -116,11 +132,24 @@ Usage: ultra profile [Options] <pid | -- execName arg0 arg1...>

-h, -?, --help Show this message and exit
--pid=PID The PID of the process to attach the profiler to.
--sampling-interval=VALUE The VALUE of the sample interval in ms. Default is 8190Hz = 0.122ms.
--symbol-path=VALUE The VALUE of symbol path. The default value is `;SRV*C:\Users\xoofx\AppData\Local\Temp\SymbolCache*https://msdl.microsoft.com/download/symbols;SRV*C:\Users\xoofx\AppData\Local\Temp\SymbolCache*https://
symbols.nuget.org/download/symbols`.
--sampling-interval=VALUE The VALUE of the sample interval in ms. Default
is 8190Hz = 0.122ms.
--symbol-path=VALUE The VALUE of symbol path. The default value is `;
SRV*C:\Users\alexa\AppData\Local\Temp\
SymbolCache*https://msdl.microsoft.com/download/
symbols;SRV*C:\Users\alexa\AppData\Local\Temp\
SymbolCache*https://symbols.nuget.org/download/
symbols`.
--keep-merged-etl-file Keep the merged ETL file.
--keep-intermediate-etl-files Keep the intermediate ETL files before merging.
--mode=VALUE 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.
```

## Convert
Expand All @@ -134,6 +163,10 @@ Usage: ultra convert --pid xxx <etl_file_name.etl>

-h, -?, --help Show this message and exit
--pid=PID The PID of the process
--symbol-path=VALUE The VALUE of symbol path. The default value is `;SRV*C:\Users\xoofx\AppData\Local\Temp\SymbolCache*https://msdl.microsoft.com/download/symbols;SRV*C:\Users\xoofx\AppData\Local\Temp\SymbolCache*https://
symbols.nuget.org/download/symbols`.
--symbol-path=VALUE The VALUE of symbol path. The default value is `;
SRV*C:\Users\alexa\AppData\Local\Temp\
SymbolCache*https://msdl.microsoft.com/download/
symbols;SRV*C:\Users\alexa\AppData\Local\Temp\
SymbolCache*https://symbols.nuget.org/download/
symbols`.
```
114 changes: 98 additions & 16 deletions src/Ultra.Core/EtwUltraProfiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ public async Task<string> Run(EtwUltraProfilerOptions ultraProfilerOptions)
// Append the pid for a single process that we are attaching to
if (singleProcess is not null)
{
baseName = $"{baseName}_{singleProcess.Id}";
baseName = $"{baseName}_pid_{singleProcess.Id}";
}

var options = new TraceEventProviderOptions()
Expand Down Expand Up @@ -181,23 +181,14 @@ public async Task<string> Run(EtwUltraProfilerOptions ultraProfilerOptions)
// Start a command line process if needed
if (ultraProfilerOptions.ProgramPath is not null)
{
var startInfo = new ProcessStartInfo
var processState = StartProcess(ultraProfilerOptions);
processList.Add(processState.Process);
// Append the pid for a single process that we are attaching to
if (singleProcess is null)
{
FileName = ultraProfilerOptions.ProgramPath,
UseShellExecute = true,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden
};

foreach (var arg in ultraProfilerOptions.Arguments)
{
startInfo.ArgumentList.Add(arg);
baseName = $"{baseName}_pid_{processState.Process.Id}";
}

ultraProfilerOptions.LogProgress?.Invoke($"Starting Process {startInfo.FileName} {string.Join(" ", startInfo.ArgumentList)}");
var process = System.Diagnostics.Process.Start(startInfo)!;
processList.Add(process);
singleProcess ??= process;
singleProcess ??= processState.Process;
}

foreach (var process in processList)
Expand Down Expand Up @@ -391,6 +382,97 @@ private async Task WaitForStaleFile(string file, EtwUltraProfilerOptions options
}
}


private static ProcessState StartProcess(EtwUltraProfilerOptions ultraProfilerOptions)
{
var mode = ultraProfilerOptions.ConsoleMode;

var process = new Process();

var startInfo = process.StartInfo;
startInfo.FileName = ultraProfilerOptions.ProgramPath;

foreach (var arg in ultraProfilerOptions.Arguments)
{
startInfo.ArgumentList.Add(arg);
}

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

if (mode == EtwUltraProfilerConsoleMode.Silent)
{
startInfo.UseShellExecute = true;
startInfo.CreateNoWindow = true;
startInfo.WindowStyle = ProcessWindowStyle.Hidden;

process.Start();
}
else
{
startInfo.UseShellExecute = false;
startInfo.CreateNoWindow = true;
startInfo.RedirectStandardOutput = true;
startInfo.RedirectStandardError = true;
startInfo.RedirectStandardInput = true;

process.OutputDataReceived += (sender, args) =>
{
if (args.Data != null)
{
ultraProfilerOptions.ProgramLogStdout?.Invoke(args.Data);
}
};

process.ErrorDataReceived += (sender, args) =>
{
if (args.Data != null)
{
ultraProfilerOptions.ProgramLogStderr?.Invoke(args.Data);
}
};

process.Start();

process.BeginOutputReadLine();
process.BeginErrorReadLine();
}

var state = new ProcessState(process);

// Make sure to call WaitForExit
var thread = new Thread(() =>
{
try
{
process.WaitForExit();
state.HasExited = true;
}
catch
{
// ignore
}
})
{
Name = "Ultra-ProcessWaitForExit",
IsBackground = true
};
thread.Start();

return state;
}

private class ProcessState
{
public ProcessState(Process process)
{
Process = process;
}

public readonly Process Process;

public bool HasExited;
}

public void Dispose()
{
_userSession?.Dispose();
Expand Down
26 changes: 26 additions & 0 deletions src/Ultra.Core/EtwUltraProfilerConsoleMode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// Licensed under the BSD-Clause 2 license.
// See license.txt file in the project root for full license information.

namespace Ultra.Core;

/// <summary>
/// The mode of the console output.
/// </summary>
public enum EtwUltraProfilerConsoleMode
{
/// <summary>
/// No console output from the program started.
/// </summary>
Silent,

/// <summary>
/// Redirect the console output from the program started to the current console, but live progress using Spectre.Console is disabled.
/// </summary>
Raw,

/// <summary>
/// Redirect the last lines of the console output from the program started to the live progress using Spectre.Console.
/// </summary>
Live,
}
6 changes: 6 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 EtwUltraProfilerConsoleMode ConsoleMode { get; set; }

public Action<string>? LogProgress;

public Action<string>? LogStepProgress;
Expand All @@ -38,6 +40,10 @@ public EtwUltraProfilerOptions()

public Action<string>? WaitingFileToCompleteTimeOut;

public Action<string>? ProgramLogStdout;

public Action<string>? ProgramLogStderr;

public bool KeepEtlIntermediateFiles { get; set; }

public bool KeepMergedEtl { get; set; }
Expand Down
9 changes: 9 additions & 0 deletions src/Ultra.Example/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
for (int i = 0; i < countBenchMarkdig; i++)
{
var html = Markdig.Markdown.ToHtml(md);
if (i % 100 == 0 && i > 0)
{
Console.WriteLine($"Markdig {i} conversions done");
}
}
};

Expand All @@ -24,6 +28,11 @@
for (int i = 0; i < countBenchScriban; i++)
{
var text = template.Render(new { values = values });

if (i % 1000 == 0 && i > 0)
{
Console.WriteLine($"Scriban {i} conversions done");
}
}
};

Expand Down
Loading

0 comments on commit 31e7fae

Please sign in to comment.