-
Notifications
You must be signed in to change notification settings - Fork 281
Make TraceCollector work on Linux #4706
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 4 commits
4915c15
7456e69
97687bf
ab9190f
648256f
7cd598b
5f396c0
8177216
8ec6220
7136b1a
65c9d04
402e722
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
@@ -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<WebClient> _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<CollectType, string> WindowsCollectTypeMap = new() | ||||
{ | ||||
{ CollectType.gc, "/GCCollectOnly" }, | ||||
|
@@ -40,7 +39,14 @@ public sealed class TraceCollector : IDisposable | |||
{ | ||||
{ CollectType.gc, "gcCollectOnly" }, | ||||
{ CollectType.cpu, "collect_cpu" }, | ||||
{ CollectType.threadtime, "collect_threadTime" }, | ||||
{ CollectType.threadtime, "collect_threadTime" } | ||||
}; | ||||
|
||||
internal static readonly Dictionary<CollectType, string> LinuxLocalRunCollectTypeMap = new() | ||||
{ | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should probably rename: Line 38 in ab9190f
LinuxServerRunCollectTypeMap .
|
||||
{ CollectType.gc, "--clrevents gc" }, | ||||
{ CollectType.cpu, "--clrevents gc+stack --clreventlevel informational" }, | ||||
mrsharm marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please check: https://github.com/Maoni0/mem-doc/blob/master/doc/.NETMemoryPerformanceAnalysis.md#how-to-collect-top-level-gc-metrics - seems like the right params are: |
||||
{ CollectType.threadtime, "--clrevents gc --clreventlevel Verbose" }, | ||||
}; | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This configuration doesn't match with the threadtime based trace collection - let's get rid of this until we find a good enough alternative. |
||||
|
||||
internal static readonly Dictionary<string, CollectType> StringToCollectTypeMap = new(StringComparer.OrdinalIgnoreCase) | ||||
|
@@ -55,54 +61,122 @@ public sealed class TraceCollector : IDisposable | |||
{ "none", CollectType.none } | ||||
}; | ||||
|
||||
private readonly CollectType _collectType; | ||||
private readonly Process _traceProcess; | ||||
private readonly string arguments; | ||||
private readonly string _collectorPath; | ||||
#if Windows | ||||
private readonly Guid _sessionName; | ||||
private readonly Process _traceProcess; | ||||
private readonly CollectType _collectType; | ||||
#endif | ||||
|
||||
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 Windows | ||||
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); | ||||
} | ||||
} | ||||
#else | ||||
string dotNetTracePath = Path.Combine(DependenciesFolder, "dotnet-trace"); | ||||
if (File.Exists(dotNetTracePath)) | ||||
{ | ||||
return; | ||||
} | ||||
|
||||
using (Process dotNetTraceInstaller = new()) | ||||
{ | ||||
Directory.CreateDirectory("./dependencies"); | ||||
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(); | ||||
dotNetTraceInstaller.WaitForExit(); | ||||
} | ||||
#endif | ||||
} | ||||
|
||||
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; | ||||
} | ||||
|
||||
foreach (var invalid in Path.GetInvalidPathChars()) | ||||
{ | ||||
name = name.Replace(invalid.ToString(), string.Empty); | ||||
} | ||||
|
||||
name = name.Replace("<", ""); | ||||
name = name.Replace(">", ""); | ||||
name = name.Replace("<", ""); | ||||
name = name.Replace(">", ""); | ||||
|
||||
Name = Path.Combine(outputPath, $"{name}.etl"); | ||||
#if Windows | ||||
_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")}"; | ||||
string command = $"start {arguments}"; | ||||
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.Arguments = command; | ||||
_traceProcess.StartInfo.UseShellExecute = false; | ||||
_traceProcess.StartInfo.CreateNoWindow = true; | ||||
_traceProcess.StartInfo.RedirectStandardError = true; | ||||
_traceProcess.StartInfo.RedirectStandardOutput = true; | ||||
_traceProcess.Start(); | ||||
_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(); | ||||
|
||||
// Give PerfView about a second to get started. | ||||
Thread.Sleep(1000); | ||||
// Give PerfView about a second to get started. | ||||
Thread.Sleep(1000); | ||||
|
||||
#else | ||||
if (pid == null) | ||||
{ | ||||
throw new Exception($"{nameof(TraceCollector)}: Must provide prcoess id in Linux case"); | ||||
} | ||||
|
||||
_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(); | ||||
#endif | ||||
} | ||||
|
||||
private void Dispose(bool disposing) | ||||
|
@@ -115,11 +189,12 @@ private void Dispose(bool disposing) | |||
|
||||
// TODO: Parameterize the wait for exit time. | ||||
|
||||
#if Windows | ||||
if (!disposedValue) | ||||
{ | ||||
using (Process stopProcess = new()) | ||||
{ | ||||
stopProcess.StartInfo.FileName = Path.Combine("./dependencies", "PerfView.exe"); | ||||
stopProcess.StartInfo.FileName = _collectorPath; | ||||
string command = $"stop {arguments}"; | ||||
stopProcess.StartInfo.Arguments = command; | ||||
stopProcess.StartInfo.UseShellExecute = false; | ||||
|
@@ -160,9 +235,15 @@ private void Dispose(bool disposing) | |||
|
||||
disposedValue = true; | ||||
} | ||||
#else | ||||
if (!disposedValue) | ||||
{ | ||||
_traceProcess.WaitForExit(); | ||||
_traceProcess.Dispose(); | ||||
} | ||||
#endif | ||||
} | ||||
|
||||
public string Name { get; init; } | ||||
|
||||
~TraceCollector() | ||||
{ | ||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given that dotnet-trace only collect the first app(-- <command>) or specified process(--process-id <PID>), we start inProcess run for microbenchmarks