diff --git a/src/benchmarks/gc/GC.Infrastructure/GC.Infrastructure.Core/CommandBuilders/GCPerfSim.CommandBuilder.cs b/src/benchmarks/gc/GC.Infrastructure/GC.Infrastructure.Core/CommandBuilders/GCPerfSim.CommandBuilder.cs
index 7643f48711b..2195f099b2d 100644
--- a/src/benchmarks/gc/GC.Infrastructure/GC.Infrastructure.Core/CommandBuilders/GCPerfSim.CommandBuilder.cs
+++ b/src/benchmarks/gc/GC.Infrastructure/GC.Infrastructure.Core/CommandBuilders/GCPerfSim.CommandBuilder.cs
@@ -104,7 +104,11 @@ public static (string, string) BuildForServer(GCPerfSimConfiguration configurati
if (configuration.TraceConfigurations != null && !string.Equals(configuration.TraceConfigurations.Type, "none", StringComparison.OrdinalIgnoreCase))
{
CollectType collectType = TraceCollector.StringToCollectTypeMap[configuration.TraceConfigurations.Type];
- string collectionCommand = os == OS.Windows ? TraceCollector.WindowsCollectTypeMap[collectType] : TraceCollector.LinuxCollectTypeMap[collectType];
+ if (os == OS.Linux && !TraceCollector.LinuxServerRunCollectTypeMap.Keys.Contains(collectType))
+ {
+ throw new Exception($"{nameof(GCPerfSimCommandBuilder)}: Trace collect type {configuration.TraceConfigurations.Type} is not supported for GCPerfsim Linux server run.");
+ }
+ string collectionCommand = os == OS.Windows ? TraceCollector.WindowsCollectTypeMap[collectType] : TraceCollector.LinuxServerRunCollectTypeMap[collectType];
collectionCommand = collectionCommand.Replace(" ", ";").Replace("/", "");
diff --git a/src/benchmarks/gc/GC.Infrastructure/GC.Infrastructure.Core/GC.Infrastructure.Core.csproj b/src/benchmarks/gc/GC.Infrastructure/GC.Infrastructure.Core/GC.Infrastructure.Core.csproj
index 3c3ad5c0fba..cda42b8cc22 100644
--- a/src/benchmarks/gc/GC.Infrastructure/GC.Infrastructure.Core/GC.Infrastructure.Core.csproj
+++ b/src/benchmarks/gc/GC.Infrastructure/GC.Infrastructure.Core/GC.Infrastructure.Core.csproj
@@ -5,8 +5,8 @@
enable
enable
false
- false
-
+ false
+
diff --git a/src/benchmarks/gc/GC.Infrastructure/GC.Infrastructure.Core/TraceCollection/TraceCollector.cs b/src/benchmarks/gc/GC.Infrastructure/GC.Infrastructure.Core/TraceCollection/TraceCollector.cs
index 752657610b5..dee739f21e8 100644
--- a/src/benchmarks/gc/GC.Infrastructure/GC.Infrastructure.Core/TraceCollection/TraceCollector.cs
+++ b/src/benchmarks/gc/GC.Infrastructure/GC.Infrastructure.Core/TraceCollection/TraceCollector.cs
@@ -1,4 +1,5 @@
-using System.Diagnostics;
+using Microsoft.Diagnostics.Utilities;
+using System.Diagnostics;
using System.Net;
namespace GC.Infrastructure.Core.TraceCollection
@@ -19,12 +20,10 @@ public sealed class TraceCollector : IDisposable
{
private bool disposedValue;
- private static readonly Lazy _client = new();
-
- // TODO: Make this URL configurable.
- private const string PERFVIEW_URL = "https://github.com/microsoft/perfview/releases/download/v3.0.0/PerfView.exe";
+ private static readonly string DependenciesFolder = "./dependencies";
+
+ public string Name { get; init; }
- private readonly string ALWAYS_ARGS = @$" /AcceptEULA /NoGUI /Merge:true";
internal static readonly Dictionary WindowsCollectTypeMap = new()
{
{ CollectType.gc, "/GCCollectOnly" },
@@ -36,11 +35,18 @@ public sealed class TraceCollector : IDisposable
{ CollectType.join, " /BufferSizeMB:4096 /CircularMB:4096 /KernelEvents:Process+Thread+ImageLoad /ClrEvents:GC+Threading /ClrEventLevel=Verbose " },
};
- internal static readonly Dictionary LinuxCollectTypeMap = new()
+ internal static readonly Dictionary LinuxServerRunCollectTypeMap = new()
{
{ CollectType.gc, "gcCollectOnly" },
{ CollectType.cpu, "collect_cpu" },
- { CollectType.threadtime, "collect_threadTime" },
+ { CollectType.threadtime, "collect_threadTime" }
+ };
+
+ internal static readonly Dictionary LinuxLocalRunCollectTypeMap = new()
+ {
+ { CollectType.gc, "--profile gc-collect" },
+ { CollectType.cpu, "" },
+ { CollectType.verbose, "--clrevents gc+stack --clreventlevel verbose" }
};
internal static readonly Dictionary StringToCollectTypeMap = new(StringComparer.OrdinalIgnoreCase)
@@ -55,44 +61,94 @@ public sealed class TraceCollector : IDisposable
{ "none", CollectType.none }
};
+ private readonly CollectType _collectType;
+ private readonly Process _traceProcess;
private readonly string arguments;
+ private readonly string _collectorPath;
private readonly Guid _sessionName;
- private readonly Process _traceProcess;
- private readonly CollectType _collectType;
- public TraceCollector(string name, string collectType, string outputPath)
+ private static void InstallTraceCollector(string dependenciesFolder)
{
- // Get Perfview if it doesn't exist.
- if (!Directory.Exists("./dependencies"))
+ if (OperatingSystem.IsWindows())
{
- Directory.CreateDirectory("./dependencies");
+ string perfviewPath = Path.Combine(DependenciesFolder, "Perfview.exe");
+ if (File.Exists(perfviewPath))
+ {
+ return;
+ }
+
+ // TODO: Make this URL configurable.
+ const string perfviewUrl = "https://github.com/microsoft/perfview/releases/download/v3.0.0/PerfView.exe";
+ using (HttpClient client = new())
+ {
+ HttpResponseMessage response = client.GetAsync(perfviewUrl).Result;
+ response.EnsureSuccessStatusCode();
+
+ using (FileStream writer = File.OpenWrite(perfviewPath))
+ {
+ response.Content.ReadAsStream().CopyTo(writer);
+ }
+ }
}
+ if (OperatingSystem.IsLinux())
+ {
+ string dotNetTracePath = Path.Combine(DependenciesFolder, "dotnet-trace");
+ if (File.Exists(dotNetTracePath))
+ {
+ return;
+ }
+
+ using (Process dotNetTraceInstaller = new())
+ {
+ dotNetTraceInstaller.StartInfo.FileName = "dotnet";
+ dotNetTraceInstaller.StartInfo.Arguments = $"tool install dotnet-trace --tool-path {dependenciesFolder}";
+ dotNetTraceInstaller.StartInfo.UseShellExecute = false;
+ dotNetTraceInstaller.StartInfo.CreateNoWindow = true;
+ dotNetTraceInstaller.StartInfo.RedirectStandardError = true;
+ dotNetTraceInstaller.StartInfo.RedirectStandardOutput = true;
+ dotNetTraceInstaller.Start();
+ Console.WriteLine("install dotnet-trace");
+ dotNetTraceInstaller.WaitForExit();
+ }
+ }
+ }
- if (!File.Exists(Path.Combine("./dependencies", "PerfView.exe")))
+ public TraceCollector(string name, string collectType, string outputPath, int? pid = null)
+ {
+ if (!Directory.Exists(DependenciesFolder))
{
- _client.Value.DownloadFile(PERFVIEW_URL, Path.Combine("./dependencies", "PerfView.exe"));
+ Directory.CreateDirectory(DependenciesFolder);
}
+ InstallTraceCollector(DependenciesFolder);
+
_collectType = StringToCollectTypeMap[collectType];
- if (_collectType != CollectType.none)
+ if (_collectType == CollectType.none)
{
- _sessionName = Guid.NewGuid();
- foreach (var invalid in Path.GetInvalidPathChars())
- {
- name = name.Replace(invalid.ToString(), string.Empty);
- }
+ return;
+ }
- name = name.Replace("<", "");
- name = name.Replace(">", "");
+ foreach (var invalid in Path.GetInvalidPathChars())
+ {
+ name = name.Replace(invalid.ToString(), string.Empty);
+ }
- Name = Path.Combine(outputPath, $"{name}.etl");
+ name = name.Replace("<", "");
+ name = name.Replace(">", "");
+
+ if (OperatingSystem.IsWindows())
+ {
+ _collectorPath = Path.Combine(DependenciesFolder, "Perfview.exe");
+ _sessionName = Guid.NewGuid();
- arguments = $"{ALWAYS_ARGS} /sessionName:{_sessionName} {WindowsCollectTypeMap[_collectType]} /LogFile:{Path.Combine(outputPath, name)}.txt /DataFile:{Path.Combine(outputPath, $"{name}.etl")}";
+ Name = Path.Combine(outputPath, $"{name}.etl");
+ string ALWAYS_ARGS = @$" /AcceptEULA /NoGUI /Merge:true";
+ arguments = $"{ALWAYS_ARGS} /sessionName:{_sessionName} {WindowsCollectTypeMap[_collectType]} /LogFile:{Path.Combine(outputPath, name)}.txt /DataFile:{Name}";
string command = $"start {arguments}";
_traceProcess = new();
- _traceProcess.StartInfo.FileName = "./dependencies/PerfView.exe";
+ _traceProcess.StartInfo.FileName = _collectorPath;
_traceProcess.StartInfo.Arguments = command;
_traceProcess.StartInfo.UseShellExecute = false;
_traceProcess.StartInfo.CreateNoWindow = true;
@@ -103,6 +159,34 @@ public TraceCollector(string name, string collectType, string outputPath)
// Give PerfView about a second to get started.
Thread.Sleep(1000);
}
+
+ if (OperatingSystem.IsLinux())
+ {
+ if (pid == null)
+ {
+ throw new Exception($"{nameof(TraceCollector)}: Must provide prcoess id in Linux case");
+ }
+
+ if (_collectType != CollectType.none && !LinuxLocalRunCollectTypeMap.Keys.Contains(_collectType))
+ {
+ throw new Exception($"{nameof(TraceCollector)}: Trace collect type {collectType} is not supported for Linux local run.");
+ }
+
+ _collectorPath = Path.Combine(DependenciesFolder, "dotnet-trace");
+
+ Name = Path.Combine(outputPath, $"{name}.nettrace");
+ arguments = $"-p {pid} -o {Name} {LinuxLocalRunCollectTypeMap[_collectType]}";
+ string command = $"collect {arguments}";
+
+ _traceProcess = new();
+ _traceProcess.StartInfo.FileName = _collectorPath;
+ _traceProcess.StartInfo.Arguments = command;
+ _traceProcess.StartInfo.UseShellExecute = false;
+ _traceProcess.StartInfo.CreateNoWindow = true;
+ _traceProcess.StartInfo.RedirectStandardError = true;
+ _traceProcess.StartInfo.RedirectStandardOutput = true;
+ _traceProcess.Start();
+ }
}
private void Dispose(bool disposing)
@@ -114,55 +198,65 @@ private void Dispose(bool disposing)
}
// TODO: Parameterize the wait for exit time.
-
- if (!disposedValue)
+ if (OperatingSystem.IsWindows())
{
- using (Process stopProcess = new())
+ if (!disposedValue)
{
- stopProcess.StartInfo.FileName = Path.Combine("./dependencies", "PerfView.exe");
- string command = $"stop {arguments}";
- stopProcess.StartInfo.Arguments = command;
- stopProcess.StartInfo.UseShellExecute = false;
- stopProcess.StartInfo.CreateNoWindow = true;
- stopProcess.StartInfo.RedirectStandardInput = true;
- stopProcess.StartInfo.RedirectStandardError = true;
- stopProcess.Start();
- stopProcess.WaitForExit(200_000);
- _traceProcess?.Dispose();
- }
+ using (Process stopProcess = new())
+ {
+ stopProcess.StartInfo.FileName = _collectorPath;
+ string command = $"stop {arguments}";
+ stopProcess.StartInfo.Arguments = command;
+ stopProcess.StartInfo.UseShellExecute = false;
+ stopProcess.StartInfo.CreateNoWindow = true;
+ stopProcess.StartInfo.RedirectStandardInput = true;
+ stopProcess.StartInfo.RedirectStandardError = true;
+ stopProcess.Start();
+ stopProcess.WaitForExit(200_000);
+ _traceProcess?.Dispose();
+ }
- // Clean up any dangling ETW sessions for both the Kernel and the session.
- using (Process stopLogmanKernelProcess = new())
- {
- stopLogmanKernelProcess.StartInfo.FileName = "logman";
- string etsStopCommand = $"-ets stop {_sessionName}Kernel";
- stopLogmanKernelProcess.StartInfo.Arguments = etsStopCommand;
- stopLogmanKernelProcess.StartInfo.UseShellExecute = false;
- stopLogmanKernelProcess.StartInfo.RedirectStandardOutput = false;
- stopLogmanKernelProcess.StartInfo.RedirectStandardError = false;
- stopLogmanKernelProcess.StartInfo.CreateNoWindow = true;
- stopLogmanKernelProcess.Start();
- stopLogmanKernelProcess.WaitForExit(5_000);
+ // Clean up any dangling ETW sessions for both the Kernel and the session.
+ using (Process stopLogmanKernelProcess = new())
+ {
+ stopLogmanKernelProcess.StartInfo.FileName = "logman";
+ string etsStopCommand = $"-ets stop {_sessionName}Kernel";
+ stopLogmanKernelProcess.StartInfo.Arguments = etsStopCommand;
+ stopLogmanKernelProcess.StartInfo.UseShellExecute = false;
+ stopLogmanKernelProcess.StartInfo.RedirectStandardOutput = false;
+ stopLogmanKernelProcess.StartInfo.RedirectStandardError = false;
+ stopLogmanKernelProcess.StartInfo.CreateNoWindow = true;
+ stopLogmanKernelProcess.Start();
+ stopLogmanKernelProcess.WaitForExit(5_000);
+ }
+
+ using (Process stopLogmanProcess = new())
+ {
+ stopLogmanProcess.StartInfo.FileName = "logman";
+ string etsStopCommand = $"-ets stop {_sessionName}";
+ stopLogmanProcess.StartInfo.Arguments = etsStopCommand;
+ stopLogmanProcess.StartInfo.UseShellExecute = false;
+ stopLogmanProcess.StartInfo.RedirectStandardOutput = false;
+ stopLogmanProcess.StartInfo.RedirectStandardError = false;
+ stopLogmanProcess.StartInfo.CreateNoWindow = true;
+ stopLogmanProcess.Start();
+ stopLogmanProcess.WaitForExit(5_000);
+ }
+
+ disposedValue = true;
}
+ }
- using (Process stopLogmanProcess = new())
+ if (OperatingSystem.IsLinux())
+ {
+ if (!disposedValue)
{
- stopLogmanProcess.StartInfo.FileName = "logman";
- string etsStopCommand = $"-ets stop {_sessionName}";
- stopLogmanProcess.StartInfo.Arguments = etsStopCommand;
- stopLogmanProcess.StartInfo.UseShellExecute = false;
- stopLogmanProcess.StartInfo.RedirectStandardOutput = false;
- stopLogmanProcess.StartInfo.RedirectStandardError = false;
- stopLogmanProcess.StartInfo.CreateNoWindow = true;
- stopLogmanProcess.Start();
- stopLogmanProcess.WaitForExit(5_000);
+ _traceProcess.WaitForExit();
+ _traceProcess.Dispose();
}
-
- disposedValue = true;
}
}
- public string Name { get; init; }
~TraceCollector()
{
diff --git a/src/benchmarks/gc/GC.Infrastructure/GC.Infrastructure/Commands/GCPerfSim/GCPerfSimCommand.cs b/src/benchmarks/gc/GC.Infrastructure/GC.Infrastructure/Commands/GCPerfSim/GCPerfSimCommand.cs
index 87509b7eaea..f16385e5b43 100644
--- a/src/benchmarks/gc/GC.Infrastructure/GC.Infrastructure/Commands/GCPerfSim/GCPerfSimCommand.cs
+++ b/src/benchmarks/gc/GC.Infrastructure/GC.Infrastructure/Commands/GCPerfSim/GCPerfSimCommand.cs
@@ -176,20 +176,36 @@ internal static Dictionary ExecuteLocally(GCPer
string key = $"{runInfo.RunDetails.Key}.{runInfo.CorerunDetails.Key}.{iterationIdx}";
string traceName = $"{runInfo.RunDetails.Key}.{runInfo.CorerunDetails.Key}.{iterationIdx}";
- using (TraceCollector traceCollector = new TraceCollector(traceName, collectType, outputPath))
+
+ if (OperatingSystem.IsWindows())
+ {
+ using (TraceCollector traceCollector = new TraceCollector(traceName, collectType, outputPath))
+ {
+ gcperfsimProcess.Start();
+ output = gcperfsimProcess.StandardOutput.ReadToEnd();
+ error = gcperfsimProcess.StandardError.ReadToEnd();
+ gcperfsimProcess.WaitForExit((int)configuration.Environment.default_max_seconds * 1000);
+ File.WriteAllText(Path.Combine(outputPath, key + ".txt"), "Standard Out: \n" + output + "\n Standard Error: \n" + error);
+ }
+ }
+ else
{
gcperfsimProcess.Start();
- output = gcperfsimProcess.StandardOutput.ReadToEnd();
- error = gcperfsimProcess.StandardError.ReadToEnd();
- gcperfsimProcess.WaitForExit((int)configuration.Environment.default_max_seconds * 1000);
- File.WriteAllText(Path.Combine(outputPath, key + ".txt"), "Standard Out: \n" + output + "\n Standard Error: \n" + error);
+ using (TraceCollector traceCollector = new TraceCollector(traceName, collectType, outputPath, gcperfsimProcess.Id))
+ {
+ output = gcperfsimProcess.StandardOutput.ReadToEnd();
+ error = gcperfsimProcess.StandardError.ReadToEnd();
+ gcperfsimProcess.WaitForExit((int)configuration.Environment.default_max_seconds * 1000);
+ File.WriteAllText(Path.Combine(outputPath, key + ".txt"), "Standard Out: \n" + output + "\n Standard Error: \n" + error);
+ }
}
-
+
+
// If a trace is requested, ensure the file exists. If not, there is was an error and alert the user.
if (configuration.TraceConfigurations?.Type != "none")
{
- // Not checking Linux here since the local run only allows for Windows.
- if (!File.Exists(Path.Combine(outputPath, traceName + ".etl.zip")))
+ // On Windows, the trace file path ends with ".etl.zip"; On Linux, it ends with ".nettrace".
+ if (!File.Exists(Path.Combine(outputPath, traceName + ".etl.zip")) && !File.Exists(Path.Combine(outputPath, traceName + ".nettrace")))
{
AnsiConsole.MarkupLine($"[yellow bold] ({DateTime.Now}) The trace for the run wasn't successfully captured. Please check the log file for more details: {Markup.Escape(output)} Full run details: {Path.GetFileNameWithoutExtension(configuration.Name)}: {runInfo.CorerunDetails.Key} for {runInfo.RunDetails.Key} [/]");
}
diff --git a/src/benchmarks/gc/GC.Infrastructure/GC.Infrastructure/Program.cs b/src/benchmarks/gc/GC.Infrastructure/GC.Infrastructure/Program.cs
index 0a505869860..29ba80d0a45 100644
--- a/src/benchmarks/gc/GC.Infrastructure/GC.Infrastructure/Program.cs
+++ b/src/benchmarks/gc/GC.Infrastructure/GC.Infrastructure/Program.cs
@@ -20,6 +20,7 @@ internal static void Main(string[] args)
if (OperatingSystem.IsWindows())
{
+ bool IsAdministrator = new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator);
if (!IsAdministrator)
{
AnsiConsole.WriteLine("Not running in admin mode - please elevate privileges to run this process.");
@@ -79,9 +80,5 @@ internal static void Main(string[] args)
}
}
}
-
- [SupportedOSPlatform("windows")]
- internal static bool IsAdministrator =>
- new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator);
}
}
\ No newline at end of file