Refactor dotTrace and dotMemory diagnosers
All the common logic of profilers moved into `SnapshotProfilerBase` which is the base for `DotMemoryDiagnoser` and `DotTraceDiagnoser`. The common class is inside the main package, so it can be reused by other tools (not only by JetBrains, applicable for any command-line profiler). The dotTrace/dotMemory diagnoser classes have unique simple implementation. `IsSupported` is duplicated on purpose since future versions of dotTrace and dotMemory may have different sets of supported runtimes.
This commit is contained in:
Родитель
296c9962c1
Коммит
92f33f21cb
|
@ -4,16 +4,10 @@ using System.Collections.Generic;
|
|||
|
||||
namespace BenchmarkDotNet.Samples
|
||||
{
|
||||
// Enables dotMemory profiling for all jobs
|
||||
// Profile benchmarks via dotMemory SelfApi profiling for all jobs
|
||||
[DotMemoryDiagnoser]
|
||||
// Adds the default "external-process" job
|
||||
// Profiling is performed using dotMemory Command-Line Profiler
|
||||
// See: https://www.jetbrains.com/help/dotmemory/Working_with_dotMemory_Command-Line_Profiler.html
|
||||
[SimpleJob]
|
||||
// Adds an "in-process" job
|
||||
// Profiling is performed using dotMemory SelfApi
|
||||
// NuGet reference: https://www.nuget.org/packages/JetBrains.Profiler.SelfApi
|
||||
[InProcess]
|
||||
[SimpleJob] // external-process execution
|
||||
[InProcess] // in-process execution
|
||||
public class IntroDotMemoryDiagnoser
|
||||
{
|
||||
[Params(1024)]
|
||||
|
|
|
@ -3,16 +3,11 @@ using BenchmarkDotNet.Diagnostics.dotTrace;
|
|||
|
||||
namespace BenchmarkDotNet.Samples
|
||||
{
|
||||
// Enables dotTrace profiling for all jobs
|
||||
// Profile benchmarks via dotTrace SelfApi profiling for all jobs
|
||||
// See: https://www.nuget.org/packages/JetBrains.Profiler.SelfApi
|
||||
[DotTraceDiagnoser]
|
||||
// Adds the default "external-process" job
|
||||
// Profiling is performed using dotTrace command-line Tools
|
||||
// See: https://www.jetbrains.com/help/profiler/Performance_Profiling__Profiling_Using_the_Command_Line.html
|
||||
[SimpleJob]
|
||||
// Adds an "in-process" job
|
||||
// Profiling is performed using dotTrace SelfApi
|
||||
// NuGet reference: https://www.nuget.org/packages/JetBrains.Profiler.SelfApi
|
||||
[InProcess]
|
||||
[SimpleJob] // external-process execution
|
||||
[InProcess] // in-process execution
|
||||
public class IntroDotTraceDiagnoser
|
||||
{
|
||||
[Benchmark]
|
||||
|
|
|
@ -1,148 +1,121 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using BenchmarkDotNet.Analysers;
|
||||
using System.Reflection;
|
||||
using BenchmarkDotNet.Detectors;
|
||||
using BenchmarkDotNet.Diagnosers;
|
||||
using BenchmarkDotNet.Engines;
|
||||
using BenchmarkDotNet.Exporters;
|
||||
using BenchmarkDotNet.Helpers;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using BenchmarkDotNet.Loggers;
|
||||
using BenchmarkDotNet.Reports;
|
||||
using BenchmarkDotNet.Running;
|
||||
using BenchmarkDotNet.Validators;
|
||||
using RunMode = BenchmarkDotNet.Diagnosers.RunMode;
|
||||
using JetBrains.Profiler.SelfApi;
|
||||
|
||||
namespace BenchmarkDotNet.Diagnostics.dotMemory
|
||||
namespace BenchmarkDotNet.Diagnostics.dotMemory;
|
||||
|
||||
public class DotMemoryDiagnoser(Uri? nugetUrl = null, string? downloadTo = null) : SnapshotProfilerBase
|
||||
{
|
||||
public class DotMemoryDiagnoser(Uri? nugetUrl = null, string? toolsDownloadFolder = null) : IProfiler
|
||||
public override string ShortName => "dotMemory";
|
||||
|
||||
protected override void InitTool(Progress progress)
|
||||
{
|
||||
private DotMemoryTool? tool;
|
||||
DotMemory.InitAsync(progress, nugetUrl, NuGetApi.V3, downloadTo).Wait();
|
||||
}
|
||||
|
||||
public IEnumerable<string> Ids => new[] { "DotMemory" };
|
||||
public string ShortName => "dotMemory";
|
||||
protected override void AttachToCurrentProcess(string snapshotFile)
|
||||
{
|
||||
DotMemory.Attach(new DotMemory.Config().SaveToFile(snapshotFile));
|
||||
}
|
||||
|
||||
public RunMode GetRunMode(BenchmarkCase benchmarkCase)
|
||||
protected override void AttachToProcessByPid(int pid, string snapshotFile)
|
||||
{
|
||||
DotMemory.Attach(new DotMemory.Config().ProfileExternalProcess(pid).SaveToFile(snapshotFile));
|
||||
}
|
||||
|
||||
protected override void TakeSnapshot()
|
||||
{
|
||||
DotMemory.GetSnapshot();
|
||||
}
|
||||
|
||||
protected override void Detach()
|
||||
{
|
||||
DotMemory.Detach();
|
||||
}
|
||||
|
||||
protected override string CreateSnapshotFilePath(DiagnoserActionParameters parameters)
|
||||
{
|
||||
return ArtifactFileNameHelper.GetFilePath(parameters, "snapshots", DateTime.Now, "dmw", ".0000".Length);
|
||||
}
|
||||
|
||||
protected override string GetRunnerPath()
|
||||
{
|
||||
var consoleRunnerPackageField = typeof(DotMemory).GetField("ConsoleRunnerPackage", BindingFlags.NonPublic | BindingFlags.Static);
|
||||
if (consoleRunnerPackageField == null)
|
||||
throw new InvalidOperationException("Field 'ConsoleRunnerPackage' not found.");
|
||||
|
||||
object? consoleRunnerPackage = consoleRunnerPackageField.GetValue(null);
|
||||
if (consoleRunnerPackage == null)
|
||||
throw new InvalidOperationException("Unable to get value of 'ConsoleRunnerPackage'.");
|
||||
|
||||
var consoleRunnerPackageType = consoleRunnerPackage.GetType();
|
||||
var getRunnerPathMethod = consoleRunnerPackageType.GetMethod("GetRunnerPath");
|
||||
if (getRunnerPathMethod == null)
|
||||
throw new InvalidOperationException("Method 'GetRunnerPath' not found.");
|
||||
|
||||
string? runnerPath = getRunnerPathMethod.Invoke(consoleRunnerPackage, null) as string;
|
||||
if (runnerPath == null)
|
||||
throw new InvalidOperationException("Unable to invoke 'GetRunnerPath'.");
|
||||
|
||||
return runnerPath;
|
||||
}
|
||||
|
||||
internal override bool IsSupported(RuntimeMoniker runtimeMoniker)
|
||||
{
|
||||
switch (runtimeMoniker)
|
||||
{
|
||||
return IsSupported(benchmarkCase.Job.Environment.GetRuntime().RuntimeMoniker) ? RunMode.ExtraRun : RunMode.None;
|
||||
}
|
||||
|
||||
private readonly List<string> snapshotFilePaths = new ();
|
||||
|
||||
public void Handle(HostSignal signal, DiagnoserActionParameters parameters)
|
||||
{
|
||||
var logger = parameters.Config.GetCompositeLogger();
|
||||
var job = parameters.BenchmarkCase.Job;
|
||||
|
||||
var runtimeMoniker = job.Environment.GetRuntime().RuntimeMoniker;
|
||||
if (!IsSupported(runtimeMoniker))
|
||||
{
|
||||
logger.WriteLineError($"Runtime '{runtimeMoniker}' is not supported by dotMemory");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (signal)
|
||||
{
|
||||
case HostSignal.BeforeAnythingElse:
|
||||
if (tool is null)
|
||||
{
|
||||
tool = new DotMemoryTool(logger, nugetUrl, downloadTo: toolsDownloadFolder);
|
||||
tool.Init();
|
||||
}
|
||||
break;
|
||||
case HostSignal.BeforeActualRun:
|
||||
if (tool is null)
|
||||
throw new InvalidOperationException("DotMemory tool is not initialized");
|
||||
snapshotFilePaths.Add(tool.Start(parameters));
|
||||
break;
|
||||
case HostSignal.AfterActualRun:
|
||||
if (tool is null)
|
||||
throw new InvalidOperationException("DotMemory tool is not initialized");
|
||||
tool.Stop();
|
||||
tool = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<IExporter> Exporters => Enumerable.Empty<IExporter>();
|
||||
public IEnumerable<IAnalyser> Analysers => Enumerable.Empty<IAnalyser>();
|
||||
|
||||
public IEnumerable<ValidationError> Validate(ValidationParameters validationParameters)
|
||||
{
|
||||
var runtimeMonikers = validationParameters.Benchmarks.Select(b => b.Job.Environment.GetRuntime().RuntimeMoniker).Distinct();
|
||||
foreach (var runtimeMoniker in runtimeMonikers)
|
||||
{
|
||||
if (!IsSupported(runtimeMoniker))
|
||||
yield return new ValidationError(true, $"Runtime '{runtimeMoniker}' is not supported by dotMemory");
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool IsSupported(RuntimeMoniker runtimeMoniker)
|
||||
{
|
||||
switch (runtimeMoniker)
|
||||
{
|
||||
case RuntimeMoniker.HostProcess:
|
||||
case RuntimeMoniker.Net461:
|
||||
case RuntimeMoniker.Net462:
|
||||
case RuntimeMoniker.Net47:
|
||||
case RuntimeMoniker.Net471:
|
||||
case RuntimeMoniker.Net472:
|
||||
case RuntimeMoniker.Net48:
|
||||
case RuntimeMoniker.Net481:
|
||||
case RuntimeMoniker.Net50:
|
||||
case RuntimeMoniker.Net60:
|
||||
case RuntimeMoniker.Net70:
|
||||
case RuntimeMoniker.Net80:
|
||||
case RuntimeMoniker.Net90:
|
||||
return true;
|
||||
case RuntimeMoniker.NotRecognized:
|
||||
case RuntimeMoniker.Mono:
|
||||
case RuntimeMoniker.NativeAot60:
|
||||
case RuntimeMoniker.NativeAot70:
|
||||
case RuntimeMoniker.NativeAot80:
|
||||
case RuntimeMoniker.NativeAot90:
|
||||
case RuntimeMoniker.Wasm:
|
||||
case RuntimeMoniker.WasmNet50:
|
||||
case RuntimeMoniker.WasmNet60:
|
||||
case RuntimeMoniker.WasmNet70:
|
||||
case RuntimeMoniker.WasmNet80:
|
||||
case RuntimeMoniker.WasmNet90:
|
||||
case RuntimeMoniker.MonoAOTLLVM:
|
||||
case RuntimeMoniker.MonoAOTLLVMNet60:
|
||||
case RuntimeMoniker.MonoAOTLLVMNet70:
|
||||
case RuntimeMoniker.MonoAOTLLVMNet80:
|
||||
case RuntimeMoniker.MonoAOTLLVMNet90:
|
||||
case RuntimeMoniker.Mono60:
|
||||
case RuntimeMoniker.Mono70:
|
||||
case RuntimeMoniker.Mono80:
|
||||
case RuntimeMoniker.Mono90:
|
||||
case RuntimeMoniker.HostProcess:
|
||||
case RuntimeMoniker.Net461:
|
||||
case RuntimeMoniker.Net462:
|
||||
case RuntimeMoniker.Net47:
|
||||
case RuntimeMoniker.Net471:
|
||||
case RuntimeMoniker.Net472:
|
||||
case RuntimeMoniker.Net48:
|
||||
case RuntimeMoniker.Net481:
|
||||
case RuntimeMoniker.Net50:
|
||||
case RuntimeMoniker.Net60:
|
||||
case RuntimeMoniker.Net70:
|
||||
case RuntimeMoniker.Net80:
|
||||
case RuntimeMoniker.Net90:
|
||||
return true;
|
||||
case RuntimeMoniker.NotRecognized:
|
||||
case RuntimeMoniker.Mono:
|
||||
case RuntimeMoniker.NativeAot60:
|
||||
case RuntimeMoniker.NativeAot70:
|
||||
case RuntimeMoniker.NativeAot80:
|
||||
case RuntimeMoniker.NativeAot90:
|
||||
case RuntimeMoniker.Wasm:
|
||||
case RuntimeMoniker.WasmNet50:
|
||||
case RuntimeMoniker.WasmNet60:
|
||||
case RuntimeMoniker.WasmNet70:
|
||||
case RuntimeMoniker.WasmNet80:
|
||||
case RuntimeMoniker.WasmNet90:
|
||||
case RuntimeMoniker.MonoAOTLLVM:
|
||||
case RuntimeMoniker.MonoAOTLLVMNet60:
|
||||
case RuntimeMoniker.MonoAOTLLVMNet70:
|
||||
case RuntimeMoniker.MonoAOTLLVMNet80:
|
||||
case RuntimeMoniker.MonoAOTLLVMNet90:
|
||||
case RuntimeMoniker.Mono60:
|
||||
case RuntimeMoniker.Mono70:
|
||||
case RuntimeMoniker.Mono80:
|
||||
case RuntimeMoniker.Mono90:
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
case RuntimeMoniker.NetCoreApp50:
|
||||
case RuntimeMoniker.NetCoreApp50:
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
return false;
|
||||
case RuntimeMoniker.NetCoreApp20:
|
||||
case RuntimeMoniker.NetCoreApp21:
|
||||
case RuntimeMoniker.NetCoreApp22:
|
||||
return OsDetector.IsWindows();
|
||||
case RuntimeMoniker.NetCoreApp30:
|
||||
case RuntimeMoniker.NetCoreApp31:
|
||||
return OsDetector.IsWindows() || OsDetector.IsLinux();
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(runtimeMoniker), runtimeMoniker, $"Runtime moniker {runtimeMoniker} is not supported");
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<Metric> ProcessResults(DiagnoserResults results) => ImmutableArray<Metric>.Empty;
|
||||
|
||||
public void DisplayResults(ILogger logger)
|
||||
{
|
||||
if (snapshotFilePaths.Any())
|
||||
{
|
||||
logger.WriteLineInfo("The following dotMemory snapshots were generated:");
|
||||
foreach (string snapshotFilePath in snapshotFilePaths)
|
||||
logger.WriteLineInfo($"* {snapshotFilePath}");
|
||||
}
|
||||
return false;
|
||||
case RuntimeMoniker.NetCoreApp20:
|
||||
case RuntimeMoniker.NetCoreApp21:
|
||||
case RuntimeMoniker.NetCoreApp22:
|
||||
return OsDetector.IsWindows();
|
||||
case RuntimeMoniker.NetCoreApp30:
|
||||
case RuntimeMoniker.NetCoreApp31:
|
||||
return OsDetector.IsWindows() || OsDetector.IsLinux();
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(runtimeMoniker), runtimeMoniker, $"Runtime moniker {runtimeMoniker} is not supported");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,22 +1,22 @@
|
|||
using System;
|
||||
using BenchmarkDotNet.Configs;
|
||||
|
||||
namespace BenchmarkDotNet.Diagnostics.dotMemory
|
||||
namespace BenchmarkDotNet.Diagnostics.dotMemory;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class DotMemoryDiagnoserAttribute : Attribute, IConfigSource
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class DotMemoryDiagnoserAttribute : Attribute, IConfigSource
|
||||
public IConfig Config { get; }
|
||||
|
||||
public DotMemoryDiagnoserAttribute()
|
||||
{
|
||||
public IConfig Config { get; }
|
||||
var diagnoser = new DotMemoryDiagnoser();
|
||||
Config = ManualConfig.CreateEmpty().AddDiagnoser(diagnoser);
|
||||
}
|
||||
|
||||
public DotMemoryDiagnoserAttribute()
|
||||
{
|
||||
Config = ManualConfig.CreateEmpty().AddDiagnoser(new DotMemoryDiagnoser());
|
||||
}
|
||||
|
||||
public DotMemoryDiagnoserAttribute(string? nugetUrl = null, string? toolsDownloadFolder = null)
|
||||
{
|
||||
var nugetUri = nugetUrl == null ? null : new Uri(nugetUrl);
|
||||
Config = ManualConfig.CreateEmpty().AddDiagnoser(new DotMemoryDiagnoser(nugetUri, toolsDownloadFolder));
|
||||
}
|
||||
public DotMemoryDiagnoserAttribute(Uri? nugetUrl, string? downloadTo = null)
|
||||
{
|
||||
var diagnoser = new DotMemoryDiagnoser(nugetUrl, downloadTo);
|
||||
Config = ManualConfig.CreateEmpty().AddDiagnoser(diagnoser);
|
||||
}
|
||||
}
|
|
@ -1,140 +0,0 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using BenchmarkDotNet.Diagnosers;
|
||||
using BenchmarkDotNet.Helpers;
|
||||
using BenchmarkDotNet.Loggers;
|
||||
using JetBrains.Profiler.SelfApi;
|
||||
|
||||
namespace BenchmarkDotNet.Diagnostics.dotMemory
|
||||
{
|
||||
internal sealed class DotMemoryTool
|
||||
{
|
||||
private readonly ILogger logger;
|
||||
private readonly Uri? nugetUrl;
|
||||
private readonly NuGetApi nugetApi;
|
||||
private readonly string? downloadTo;
|
||||
|
||||
public DotMemoryTool(ILogger logger, Uri? nugetUrl = null, NuGetApi nugetApi = NuGetApi.V3, string? downloadTo = null)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.nugetUrl = nugetUrl;
|
||||
this.nugetApi = nugetApi;
|
||||
this.downloadTo = downloadTo;
|
||||
}
|
||||
|
||||
public void Init()
|
||||
{
|
||||
try
|
||||
{
|
||||
logger.WriteLineInfo("Ensuring that dotMemory prerequisite is installed...");
|
||||
var progress = new Progress(logger, "Installing DotMemory");
|
||||
DotMemory.InitAsync(progress, nugetUrl, nugetApi, downloadTo).Wait();
|
||||
logger.WriteLineInfo("dotMemory prerequisite is installed");
|
||||
logger.WriteLineInfo($"dotMemory runner path: {GetRunnerPath()}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.WriteLineError(e.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public string Start(DiagnoserActionParameters parameters)
|
||||
{
|
||||
string snapshotFile = ArtifactFileNameHelper.GetFilePath(parameters, "snapshots", DateTime.Now, "dmw", ".0000".Length);
|
||||
string? snapshotDirectory = Path.GetDirectoryName(snapshotFile);
|
||||
logger.WriteLineInfo($"Target snapshot file: {snapshotFile}");
|
||||
if (!Directory.Exists(snapshotDirectory) && snapshotDirectory != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(snapshotDirectory);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.WriteLineError($"Failed to create directory: {snapshotDirectory}");
|
||||
logger.WriteLineError(e.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
logger.WriteLineInfo("Attaching dotMemory to the process...");
|
||||
Attach(parameters, snapshotFile);
|
||||
logger.WriteLineInfo("dotMemory is successfully attached");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.WriteLineError(e.ToString());
|
||||
return snapshotFile;
|
||||
}
|
||||
|
||||
return snapshotFile;
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
try
|
||||
{
|
||||
logger.WriteLineInfo("Taking dotMemory snapshot...");
|
||||
Snapshot();
|
||||
logger.WriteLineInfo("dotMemory snapshot is successfully taken");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.WriteLineError(e.ToString());
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
logger.WriteLineInfo("Detaching dotMemory from the process...");
|
||||
Detach();
|
||||
logger.WriteLineInfo("dotMemory is successfully detached");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.WriteLineError(e.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private void Attach(DiagnoserActionParameters parameters, string snapshotFile)
|
||||
{
|
||||
var config = new DotMemory.Config();
|
||||
|
||||
var pid = parameters.Process.Id;
|
||||
var currentPid = Process.GetCurrentProcess().Id;
|
||||
if (pid != currentPid)
|
||||
config = config.ProfileExternalProcess(pid);
|
||||
|
||||
config = config.SaveToFile(snapshotFile);
|
||||
DotMemory.Attach(config);
|
||||
}
|
||||
|
||||
private void Snapshot() => DotMemory.GetSnapshot();
|
||||
|
||||
private void Detach() => DotMemory.Detach();
|
||||
|
||||
private string GetRunnerPath()
|
||||
{
|
||||
var consoleRunnerPackageField = typeof(DotMemory).GetField("ConsoleRunnerPackage", BindingFlags.NonPublic | BindingFlags.Static);
|
||||
if (consoleRunnerPackageField == null)
|
||||
throw new InvalidOperationException("Field 'ConsoleRunnerPackage' not found.");
|
||||
|
||||
object? consoleRunnerPackage = consoleRunnerPackageField.GetValue(null);
|
||||
if (consoleRunnerPackage == null)
|
||||
throw new InvalidOperationException("Unable to get value of 'ConsoleRunnerPackage'.");
|
||||
|
||||
var consoleRunnerPackageType = consoleRunnerPackage.GetType();
|
||||
var getRunnerPathMethod = consoleRunnerPackageType.GetMethod("GetRunnerPath");
|
||||
if (getRunnerPathMethod == null)
|
||||
throw new InvalidOperationException("Method 'GetRunnerPath' not found.");
|
||||
|
||||
string? runnerPath = getRunnerPathMethod.Invoke(consoleRunnerPackage, null) as string;
|
||||
if (runnerPath == null)
|
||||
throw new InvalidOperationException("Unable to invoke 'GetRunnerPath'.");
|
||||
|
||||
return runnerPath;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using BenchmarkDotNet.Loggers;
|
||||
|
||||
namespace BenchmarkDotNet.Diagnostics.dotMemory
|
||||
{
|
||||
public class Progress : IProgress<double>
|
||||
{
|
||||
private static readonly TimeSpan ReportInterval = TimeSpan.FromSeconds(0.1);
|
||||
|
||||
private readonly ILogger logger;
|
||||
private readonly string title;
|
||||
|
||||
public Progress(ILogger logger, string title)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
private int lastProgress;
|
||||
private Stopwatch? stopwatch;
|
||||
|
||||
public void Report(double value)
|
||||
{
|
||||
int progress = (int)Math.Floor(value);
|
||||
bool needToReport = stopwatch == null ||
|
||||
(stopwatch != null && stopwatch?.Elapsed > ReportInterval) ||
|
||||
progress == 100;
|
||||
|
||||
if (lastProgress != progress && needToReport)
|
||||
{
|
||||
logger.WriteLineInfo($"{title}: {progress}%");
|
||||
lastProgress = progress;
|
||||
stopwatch = Stopwatch.StartNew();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,142 +1,124 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using BenchmarkDotNet.Analysers;
|
||||
using System.Reflection;
|
||||
using BenchmarkDotNet.Detectors;
|
||||
using BenchmarkDotNet.Diagnosers;
|
||||
using BenchmarkDotNet.Engines;
|
||||
using BenchmarkDotNet.Exporters;
|
||||
using BenchmarkDotNet.Helpers;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using BenchmarkDotNet.Loggers;
|
||||
using BenchmarkDotNet.Reports;
|
||||
using BenchmarkDotNet.Running;
|
||||
using BenchmarkDotNet.Toolchains;
|
||||
using BenchmarkDotNet.Validators;
|
||||
using RunMode = BenchmarkDotNet.Diagnosers.RunMode;
|
||||
using JetBrains.Profiler.SelfApi;
|
||||
|
||||
namespace BenchmarkDotNet.Diagnostics.dotTrace
|
||||
namespace BenchmarkDotNet.Diagnostics.dotTrace;
|
||||
|
||||
public class DotTraceDiagnoser(Uri? nugetUrl = null, string? downloadTo = null) : SnapshotProfilerBase
|
||||
{
|
||||
public class DotTraceDiagnoser(Uri? nugetUrl = null, string? toolsDownloadFolder = null) : IProfiler
|
||||
public override string ShortName => "dotTrace";
|
||||
|
||||
protected override void InitTool(Progress progress)
|
||||
{
|
||||
public IEnumerable<string> Ids => new[] { "DotTrace" };
|
||||
public string ShortName => "dotTrace";
|
||||
DotTrace.InitAsync(progress, nugetUrl, NuGetApi.V3, downloadTo).Wait();
|
||||
}
|
||||
|
||||
public RunMode GetRunMode(BenchmarkCase benchmarkCase)
|
||||
protected override void AttachToCurrentProcess(string snapshotFile)
|
||||
{
|
||||
DotTrace.Attach(new DotTrace.Config().SaveToFile(snapshotFile));
|
||||
DotTrace.StartCollectingData();
|
||||
}
|
||||
|
||||
protected override void AttachToProcessByPid(int pid, string snapshotFile)
|
||||
{
|
||||
DotTrace.Attach(new DotTrace.Config().ProfileExternalProcess(pid).SaveToFile(snapshotFile));
|
||||
DotTrace.StartCollectingData();
|
||||
}
|
||||
|
||||
protected override void TakeSnapshot()
|
||||
{
|
||||
DotTrace.StopCollectingData();
|
||||
DotTrace.SaveData();
|
||||
}
|
||||
|
||||
protected override void Detach()
|
||||
{
|
||||
DotTrace.Detach();
|
||||
}
|
||||
|
||||
protected override string CreateSnapshotFilePath(DiagnoserActionParameters parameters)
|
||||
{
|
||||
return ArtifactFileNameHelper.GetFilePath(parameters, "snapshots", DateTime.Now, "dtp", ".0000".Length);
|
||||
}
|
||||
|
||||
protected override string GetRunnerPath()
|
||||
{
|
||||
var consoleRunnerPackageField = typeof(DotTrace).GetField("ConsoleRunnerPackage", BindingFlags.NonPublic | BindingFlags.Static);
|
||||
if (consoleRunnerPackageField == null)
|
||||
throw new InvalidOperationException("Field 'ConsoleRunnerPackage' not found.");
|
||||
|
||||
object? consoleRunnerPackage = consoleRunnerPackageField.GetValue(null);
|
||||
if (consoleRunnerPackage == null)
|
||||
throw new InvalidOperationException("Unable to get value of 'ConsoleRunnerPackage'.");
|
||||
|
||||
var consoleRunnerPackageType = consoleRunnerPackage.GetType();
|
||||
var getRunnerPathMethod = consoleRunnerPackageType.GetMethod("GetRunnerPath");
|
||||
if (getRunnerPathMethod == null)
|
||||
throw new InvalidOperationException("Method 'GetRunnerPath' not found.");
|
||||
|
||||
string? runnerPath = getRunnerPathMethod.Invoke(consoleRunnerPackage, null) as string;
|
||||
if (runnerPath == null)
|
||||
throw new InvalidOperationException("Unable to invoke 'GetRunnerPath'.");
|
||||
|
||||
return runnerPath;
|
||||
}
|
||||
|
||||
internal override bool IsSupported(RuntimeMoniker runtimeMoniker)
|
||||
{
|
||||
switch (runtimeMoniker)
|
||||
{
|
||||
return IsSupported(benchmarkCase.Job.Environment.GetRuntime().RuntimeMoniker) ? RunMode.ExtraRun : RunMode.None;
|
||||
}
|
||||
|
||||
private readonly List<string> snapshotFilePaths = new ();
|
||||
|
||||
public void Handle(HostSignal signal, DiagnoserActionParameters parameters)
|
||||
{
|
||||
var job = parameters.BenchmarkCase.Job;
|
||||
bool isInProcess = job.GetToolchain().IsInProcess;
|
||||
var logger = parameters.Config.GetCompositeLogger();
|
||||
DotTraceToolBase tool = isInProcess
|
||||
? new InProcessDotTraceTool(logger, nugetUrl, downloadTo: toolsDownloadFolder)
|
||||
: new ExternalDotTraceTool(logger, nugetUrl, downloadTo: toolsDownloadFolder);
|
||||
|
||||
var runtimeMoniker = job.Environment.GetRuntime().RuntimeMoniker;
|
||||
if (!IsSupported(runtimeMoniker))
|
||||
{
|
||||
logger.WriteLineError($"Runtime '{runtimeMoniker}' is not supported by dotTrace");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (signal)
|
||||
{
|
||||
case HostSignal.BeforeAnythingElse:
|
||||
tool.Init(parameters);
|
||||
break;
|
||||
case HostSignal.BeforeActualRun:
|
||||
snapshotFilePaths.Add(tool.Start(parameters));
|
||||
break;
|
||||
case HostSignal.AfterActualRun:
|
||||
tool.Stop(parameters);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<IExporter> Exporters => Enumerable.Empty<IExporter>();
|
||||
public IEnumerable<IAnalyser> Analysers => Enumerable.Empty<IAnalyser>();
|
||||
|
||||
public IEnumerable<ValidationError> Validate(ValidationParameters validationParameters)
|
||||
{
|
||||
var runtimeMonikers = validationParameters.Benchmarks.Select(b => b.Job.Environment.GetRuntime().RuntimeMoniker).Distinct();
|
||||
foreach (var runtimeMoniker in runtimeMonikers)
|
||||
{
|
||||
if (!IsSupported(runtimeMoniker))
|
||||
yield return new ValidationError(true, $"Runtime '{runtimeMoniker}' is not supported by dotTrace");
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool IsSupported(RuntimeMoniker runtimeMoniker)
|
||||
{
|
||||
switch (runtimeMoniker)
|
||||
{
|
||||
case RuntimeMoniker.HostProcess:
|
||||
case RuntimeMoniker.Net461:
|
||||
case RuntimeMoniker.Net462:
|
||||
case RuntimeMoniker.Net47:
|
||||
case RuntimeMoniker.Net471:
|
||||
case RuntimeMoniker.Net472:
|
||||
case RuntimeMoniker.Net48:
|
||||
case RuntimeMoniker.Net481:
|
||||
case RuntimeMoniker.Net50:
|
||||
case RuntimeMoniker.Net60:
|
||||
case RuntimeMoniker.Net70:
|
||||
case RuntimeMoniker.Net80:
|
||||
case RuntimeMoniker.Net90:
|
||||
return true;
|
||||
case RuntimeMoniker.NotRecognized:
|
||||
case RuntimeMoniker.Mono:
|
||||
case RuntimeMoniker.NativeAot60:
|
||||
case RuntimeMoniker.NativeAot70:
|
||||
case RuntimeMoniker.NativeAot80:
|
||||
case RuntimeMoniker.NativeAot90:
|
||||
case RuntimeMoniker.Wasm:
|
||||
case RuntimeMoniker.WasmNet50:
|
||||
case RuntimeMoniker.WasmNet60:
|
||||
case RuntimeMoniker.WasmNet70:
|
||||
case RuntimeMoniker.WasmNet80:
|
||||
case RuntimeMoniker.WasmNet90:
|
||||
case RuntimeMoniker.MonoAOTLLVM:
|
||||
case RuntimeMoniker.MonoAOTLLVMNet60:
|
||||
case RuntimeMoniker.MonoAOTLLVMNet70:
|
||||
case RuntimeMoniker.MonoAOTLLVMNet80:
|
||||
case RuntimeMoniker.MonoAOTLLVMNet90:
|
||||
case RuntimeMoniker.Mono60:
|
||||
case RuntimeMoniker.Mono70:
|
||||
case RuntimeMoniker.Mono80:
|
||||
case RuntimeMoniker.Mono90:
|
||||
case RuntimeMoniker.HostProcess:
|
||||
case RuntimeMoniker.Net461:
|
||||
case RuntimeMoniker.Net462:
|
||||
case RuntimeMoniker.Net47:
|
||||
case RuntimeMoniker.Net471:
|
||||
case RuntimeMoniker.Net472:
|
||||
case RuntimeMoniker.Net48:
|
||||
case RuntimeMoniker.Net481:
|
||||
case RuntimeMoniker.Net50:
|
||||
case RuntimeMoniker.Net60:
|
||||
case RuntimeMoniker.Net70:
|
||||
case RuntimeMoniker.Net80:
|
||||
case RuntimeMoniker.Net90:
|
||||
return true;
|
||||
case RuntimeMoniker.NotRecognized:
|
||||
case RuntimeMoniker.Mono:
|
||||
case RuntimeMoniker.NativeAot60:
|
||||
case RuntimeMoniker.NativeAot70:
|
||||
case RuntimeMoniker.NativeAot80:
|
||||
case RuntimeMoniker.NativeAot90:
|
||||
case RuntimeMoniker.Wasm:
|
||||
case RuntimeMoniker.WasmNet50:
|
||||
case RuntimeMoniker.WasmNet60:
|
||||
case RuntimeMoniker.WasmNet70:
|
||||
case RuntimeMoniker.WasmNet80:
|
||||
case RuntimeMoniker.WasmNet90:
|
||||
case RuntimeMoniker.MonoAOTLLVM:
|
||||
case RuntimeMoniker.MonoAOTLLVMNet60:
|
||||
case RuntimeMoniker.MonoAOTLLVMNet70:
|
||||
case RuntimeMoniker.MonoAOTLLVMNet80:
|
||||
case RuntimeMoniker.MonoAOTLLVMNet90:
|
||||
case RuntimeMoniker.Mono60:
|
||||
case RuntimeMoniker.Mono70:
|
||||
case RuntimeMoniker.Mono80:
|
||||
case RuntimeMoniker.Mono90:
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
case RuntimeMoniker.NetCoreApp50:
|
||||
case RuntimeMoniker.NetCoreApp50:
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
return false;
|
||||
case RuntimeMoniker.NetCoreApp20:
|
||||
case RuntimeMoniker.NetCoreApp21:
|
||||
case RuntimeMoniker.NetCoreApp22:
|
||||
return OsDetector.IsWindows();
|
||||
case RuntimeMoniker.NetCoreApp30:
|
||||
case RuntimeMoniker.NetCoreApp31:
|
||||
return OsDetector.IsWindows() || OsDetector.IsLinux();
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(runtimeMoniker), runtimeMoniker, $"Runtime moniker {runtimeMoniker} is not supported");
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<Metric> ProcessResults(DiagnoserResults results) => ImmutableArray<Metric>.Empty;
|
||||
|
||||
public void DisplayResults(ILogger logger)
|
||||
{
|
||||
if (snapshotFilePaths.Any())
|
||||
{
|
||||
logger.WriteLineInfo("The following dotTrace snapshots were generated:");
|
||||
foreach (string snapshotFilePath in snapshotFilePaths)
|
||||
logger.WriteLineInfo($"* {snapshotFilePath}");
|
||||
}
|
||||
return false;
|
||||
case RuntimeMoniker.NetCoreApp20:
|
||||
case RuntimeMoniker.NetCoreApp21:
|
||||
case RuntimeMoniker.NetCoreApp22:
|
||||
return OsDetector.IsWindows();
|
||||
case RuntimeMoniker.NetCoreApp30:
|
||||
case RuntimeMoniker.NetCoreApp31:
|
||||
return OsDetector.IsWindows() || OsDetector.IsLinux();
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(runtimeMoniker), runtimeMoniker, $"Runtime moniker {runtimeMoniker} is not supported");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,22 +1,22 @@
|
|||
using System;
|
||||
using BenchmarkDotNet.Configs;
|
||||
|
||||
namespace BenchmarkDotNet.Diagnostics.dotTrace
|
||||
namespace BenchmarkDotNet.Diagnostics.dotTrace;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class DotTraceDiagnoserAttribute : Attribute, IConfigSource
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class DotTraceDiagnoserAttribute : Attribute, IConfigSource
|
||||
public IConfig Config { get; }
|
||||
|
||||
public DotTraceDiagnoserAttribute()
|
||||
{
|
||||
public IConfig Config { get; }
|
||||
var diagnoser = new DotTraceDiagnoser();
|
||||
Config = ManualConfig.CreateEmpty().AddDiagnoser(diagnoser);
|
||||
}
|
||||
|
||||
public DotTraceDiagnoserAttribute()
|
||||
{
|
||||
Config = ManualConfig.CreateEmpty().AddDiagnoser(new DotTraceDiagnoser());
|
||||
}
|
||||
|
||||
public DotTraceDiagnoserAttribute(string? nugetUrl = null, string? toolsDownloadFolder = null)
|
||||
{
|
||||
var nugetUri = nugetUrl == null ? null : new Uri(nugetUrl);
|
||||
Config = ManualConfig.CreateEmpty().AddDiagnoser(new DotTraceDiagnoser(nugetUri, toolsDownloadFolder));
|
||||
}
|
||||
public DotTraceDiagnoserAttribute(Uri? nugetUrl, string? downloadTo = null)
|
||||
{
|
||||
var diagnoser = new DotTraceDiagnoser(nugetUrl, downloadTo);
|
||||
Config = ManualConfig.CreateEmpty().AddDiagnoser(diagnoser);
|
||||
}
|
||||
}
|
|
@ -1,145 +0,0 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using BenchmarkDotNet.Diagnosers;
|
||||
using BenchmarkDotNet.Helpers;
|
||||
using BenchmarkDotNet.Loggers;
|
||||
using JetBrains.Profiler.SelfApi;
|
||||
|
||||
namespace BenchmarkDotNet.Diagnostics.dotTrace
|
||||
{
|
||||
internal abstract class DotTraceToolBase
|
||||
{
|
||||
private readonly ILogger logger;
|
||||
private readonly Uri? nugetUrl;
|
||||
private readonly NuGetApi nugetApi;
|
||||
private readonly string? downloadTo;
|
||||
|
||||
protected DotTraceToolBase(ILogger logger, Uri? nugetUrl = null, NuGetApi nugetApi = NuGetApi.V3, string? downloadTo = null)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.nugetUrl = nugetUrl;
|
||||
this.nugetApi = nugetApi;
|
||||
this.downloadTo = downloadTo;
|
||||
}
|
||||
|
||||
public void Init(DiagnoserActionParameters parameters)
|
||||
{
|
||||
try
|
||||
{
|
||||
logger.WriteLineInfo("Ensuring that dotTrace prerequisite is installed...");
|
||||
var progress = new Progress(logger, "Installing DotTrace");
|
||||
DotTrace.InitAsync(progress, nugetUrl, nugetApi, downloadTo).Wait();
|
||||
logger.WriteLineInfo("dotTrace prerequisite is installed");
|
||||
logger.WriteLineInfo($"dotTrace runner path: {GetRunnerPath()}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.WriteLineError(e.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract bool AttachOnly { get; }
|
||||
protected abstract void Attach(DiagnoserActionParameters parameters, string snapshotFile);
|
||||
protected abstract void StartCollectingData();
|
||||
protected abstract void SaveData();
|
||||
protected abstract void Detach();
|
||||
|
||||
public string Start(DiagnoserActionParameters parameters)
|
||||
{
|
||||
string snapshotFile = ArtifactFileNameHelper.GetFilePath(parameters, "snapshots", DateTime.Now, "dtp", ".0000".Length);
|
||||
string? snapshotDirectory = Path.GetDirectoryName(snapshotFile);
|
||||
logger.WriteLineInfo($"Target snapshot file: {snapshotFile}");
|
||||
if (!Directory.Exists(snapshotDirectory) && snapshotDirectory != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(snapshotDirectory);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.WriteLineError($"Failed to create directory: {snapshotDirectory}");
|
||||
logger.WriteLineError(e.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
logger.WriteLineInfo("Attaching dotTrace to the process...");
|
||||
Attach(parameters, snapshotFile);
|
||||
logger.WriteLineInfo("dotTrace is successfully attached");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.WriteLineError(e.ToString());
|
||||
return snapshotFile;
|
||||
}
|
||||
|
||||
if (!AttachOnly)
|
||||
{
|
||||
try
|
||||
{
|
||||
logger.WriteLineInfo("Start collecting data using dataTrace...");
|
||||
StartCollectingData();
|
||||
logger.WriteLineInfo("Data collecting is successfully started");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.WriteLineError(e.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
return snapshotFile;
|
||||
}
|
||||
|
||||
public void Stop(DiagnoserActionParameters parameters)
|
||||
{
|
||||
if (!AttachOnly)
|
||||
{
|
||||
try
|
||||
{
|
||||
logger.WriteLineInfo("Saving dotTrace snapshot...");
|
||||
SaveData();
|
||||
logger.WriteLineInfo("dotTrace snapshot is successfully saved to the artifact folder");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.WriteLineError(e.ToString());
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
logger.WriteLineInfo("Detaching dotTrace from the process...");
|
||||
Detach();
|
||||
logger.WriteLineInfo("dotTrace is successfully detached");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.WriteLineError(e.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected string GetRunnerPath()
|
||||
{
|
||||
var consoleRunnerPackageField = typeof(DotTrace).GetField("ConsoleRunnerPackage", BindingFlags.NonPublic | BindingFlags.Static);
|
||||
if (consoleRunnerPackageField == null)
|
||||
throw new InvalidOperationException("Field 'ConsoleRunnerPackage' not found.");
|
||||
|
||||
object? consoleRunnerPackage = consoleRunnerPackageField.GetValue(null);
|
||||
if (consoleRunnerPackage == null)
|
||||
throw new InvalidOperationException("Unable to get value of 'ConsoleRunnerPackage'.");
|
||||
|
||||
var consoleRunnerPackageType = consoleRunnerPackage.GetType();
|
||||
var getRunnerPathMethod = consoleRunnerPackageType.GetMethod("GetRunnerPath");
|
||||
if (getRunnerPathMethod == null)
|
||||
throw new InvalidOperationException("Method 'GetRunnerPath' not found.");
|
||||
|
||||
string? runnerPath = getRunnerPathMethod.Invoke(consoleRunnerPackage, null) as string;
|
||||
if (runnerPath == null)
|
||||
throw new InvalidOperationException("Unable to invoke 'GetRunnerPath'.");
|
||||
|
||||
return runnerPath;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Diagnosers;
|
||||
using BenchmarkDotNet.Loggers;
|
||||
using JetBrains.Profiler.SelfApi;
|
||||
using ILogger = BenchmarkDotNet.Loggers.ILogger;
|
||||
|
||||
namespace BenchmarkDotNet.Diagnostics.dotTrace
|
||||
{
|
||||
internal class ExternalDotTraceTool : DotTraceToolBase
|
||||
{
|
||||
private static readonly TimeSpan AttachTimeout = TimeSpan.FromMinutes(5);
|
||||
|
||||
public ExternalDotTraceTool(ILogger logger, Uri? nugetUrl = null, NuGetApi nugetApi = NuGetApi.V3, string? downloadTo = null) :
|
||||
base(logger, nugetUrl, nugetApi, downloadTo) { }
|
||||
|
||||
protected override bool AttachOnly => true;
|
||||
|
||||
protected override void Attach(DiagnoserActionParameters parameters, string snapshotFile)
|
||||
{
|
||||
var logger = parameters.Config.GetCompositeLogger();
|
||||
|
||||
string runnerPath = GetRunnerPath();
|
||||
int pid = parameters.Process.Id;
|
||||
string arguments = $"attach {pid} --save-to=\"{snapshotFile}\" --service-output=on";
|
||||
|
||||
logger.WriteLineInfo($"Starting process: '{runnerPath} {arguments}'");
|
||||
|
||||
var processStartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = runnerPath,
|
||||
WorkingDirectory = "",
|
||||
Arguments = arguments,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true
|
||||
};
|
||||
|
||||
var attachWaitingTask = new TaskCompletionSource<bool>();
|
||||
var process = new Process { StartInfo = processStartInfo };
|
||||
try
|
||||
{
|
||||
process.OutputDataReceived += (_, args) =>
|
||||
{
|
||||
string? content = args.Data;
|
||||
if (content != null)
|
||||
{
|
||||
logger.WriteLineInfo("[dotTrace] " + content);
|
||||
if (content.Contains("##dotTrace[\"started\""))
|
||||
attachWaitingTask.TrySetResult(true);
|
||||
}
|
||||
};
|
||||
process.ErrorDataReceived += (_, args) =>
|
||||
{
|
||||
string? content = args.Data;
|
||||
if (content != null)
|
||||
logger.WriteLineError("[dotTrace] " + args.Data);
|
||||
};
|
||||
process.Exited += (_, _) => { attachWaitingTask.TrySetResult(false); };
|
||||
process.Start();
|
||||
process.BeginOutputReadLine();
|
||||
process.BeginErrorReadLine();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
attachWaitingTask.TrySetResult(false);
|
||||
logger.WriteLineError(e.ToString());
|
||||
}
|
||||
|
||||
if (!attachWaitingTask.Task.Wait(AttachTimeout))
|
||||
throw new Exception($"Failed to attach dotTrace to the target process (timeout: {AttachTimeout.TotalSeconds} sec)");
|
||||
if (!attachWaitingTask.Task.Result)
|
||||
throw new Exception($"Failed to attach dotTrace to the target process (ExitCode={process.ExitCode})");
|
||||
}
|
||||
|
||||
protected override void StartCollectingData() { }
|
||||
|
||||
protected override void SaveData() { }
|
||||
|
||||
protected override void Detach() { }
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
using System;
|
||||
using BenchmarkDotNet.Diagnosers;
|
||||
using BenchmarkDotNet.Loggers;
|
||||
using JetBrains.Profiler.SelfApi;
|
||||
|
||||
namespace BenchmarkDotNet.Diagnostics.dotTrace
|
||||
{
|
||||
internal class InProcessDotTraceTool : DotTraceToolBase
|
||||
{
|
||||
public InProcessDotTraceTool(ILogger logger, Uri? nugetUrl = null, NuGetApi nugetApi = NuGetApi.V3, string? downloadTo = null) :
|
||||
base(logger, nugetUrl, nugetApi, downloadTo) { }
|
||||
|
||||
protected override bool AttachOnly => false;
|
||||
|
||||
protected override void Attach(DiagnoserActionParameters parameters, string snapshotFile)
|
||||
{
|
||||
var config = new DotTrace.Config();
|
||||
config.SaveToFile(snapshotFile);
|
||||
DotTrace.Attach(config);
|
||||
}
|
||||
|
||||
protected override void StartCollectingData() => DotTrace.StartCollectingData();
|
||||
|
||||
protected override void SaveData() => DotTrace.SaveData();
|
||||
|
||||
protected override void Detach() => DotTrace.Detach();
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using BenchmarkDotNet.Loggers;
|
||||
|
||||
namespace BenchmarkDotNet.Diagnostics.dotTrace
|
||||
{
|
||||
public class Progress : IProgress<double>
|
||||
{
|
||||
private static readonly TimeSpan ReportInterval = TimeSpan.FromSeconds(0.1);
|
||||
|
||||
private readonly ILogger logger;
|
||||
private readonly string title;
|
||||
|
||||
public Progress(ILogger logger, string title)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
private int lastProgress;
|
||||
private Stopwatch? stopwatch;
|
||||
|
||||
public void Report(double value)
|
||||
{
|
||||
int progress = (int)Math.Floor(value);
|
||||
bool needToReport = stopwatch == null ||
|
||||
(stopwatch != null && stopwatch?.Elapsed > ReportInterval) ||
|
||||
progress == 100;
|
||||
|
||||
if (lastProgress != progress && needToReport)
|
||||
{
|
||||
logger.WriteLineInfo($"{title}: {progress}%");
|
||||
lastProgress = progress;
|
||||
stopwatch = Stopwatch.StartNew();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using BenchmarkDotNet.Analysers;
|
||||
using BenchmarkDotNet.Engines;
|
||||
using BenchmarkDotNet.Exporters;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using BenchmarkDotNet.Loggers;
|
||||
using BenchmarkDotNet.Reports;
|
||||
using BenchmarkDotNet.Running;
|
||||
using BenchmarkDotNet.Validators;
|
||||
|
||||
namespace BenchmarkDotNet.Diagnosers;
|
||||
|
||||
public abstract class SnapshotProfilerBase : IProfiler
|
||||
{
|
||||
public abstract string ShortName { get; }
|
||||
|
||||
protected abstract void InitTool(Progress progress);
|
||||
protected abstract void AttachToCurrentProcess(string snapshotFile);
|
||||
protected abstract void AttachToProcessByPid(int pid, string snapshotFile);
|
||||
protected abstract void TakeSnapshot();
|
||||
protected abstract void Detach();
|
||||
|
||||
protected abstract string CreateSnapshotFilePath(DiagnoserActionParameters parameters);
|
||||
protected abstract string GetRunnerPath();
|
||||
internal abstract bool IsSupported(RuntimeMoniker runtimeMoniker);
|
||||
|
||||
private readonly List<string> snapshotFilePaths = [];
|
||||
|
||||
public IEnumerable<string> Ids => [ShortName];
|
||||
public IEnumerable<IExporter> Exporters => [];
|
||||
public IEnumerable<IAnalyser> Analysers => [];
|
||||
|
||||
public RunMode GetRunMode(BenchmarkCase benchmarkCase) =>
|
||||
IsSupported(benchmarkCase.Job.Environment.GetRuntime().RuntimeMoniker) ? RunMode.ExtraRun : RunMode.None;
|
||||
|
||||
public void Handle(HostSignal signal, DiagnoserActionParameters parameters)
|
||||
{
|
||||
var logger = parameters.Config.GetCompositeLogger();
|
||||
var job = parameters.BenchmarkCase.Job;
|
||||
|
||||
var runtimeMoniker = job.Environment.GetRuntime().RuntimeMoniker;
|
||||
if (!IsSupported(runtimeMoniker))
|
||||
{
|
||||
logger.WriteLineError($"Runtime '{runtimeMoniker}' is not supported by dotMemory");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (signal)
|
||||
{
|
||||
case HostSignal.BeforeAnythingElse:
|
||||
Init(logger);
|
||||
break;
|
||||
case HostSignal.BeforeActualRun:
|
||||
string snapshotFilePath = Start(logger, parameters);
|
||||
snapshotFilePaths.Add(snapshotFilePath);
|
||||
break;
|
||||
case HostSignal.AfterActualRun:
|
||||
Stop(logger);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<ValidationError> Validate(ValidationParameters validationParameters)
|
||||
{
|
||||
var runtimeMonikers = validationParameters.Benchmarks.Select(b => b.Job.Environment.GetRuntime().RuntimeMoniker).Distinct();
|
||||
foreach (var runtimeMoniker in runtimeMonikers)
|
||||
if (!IsSupported(runtimeMoniker))
|
||||
yield return new ValidationError(true, $"Runtime '{runtimeMoniker}' is not supported by dotMemory");
|
||||
}
|
||||
|
||||
public IEnumerable<Metric> ProcessResults(DiagnoserResults results) => ImmutableArray<Metric>.Empty;
|
||||
|
||||
public void DisplayResults(ILogger logger)
|
||||
{
|
||||
if (snapshotFilePaths.Count != 0)
|
||||
{
|
||||
logger.WriteLineInfo($"The following {ShortName} snapshots were generated:");
|
||||
foreach (string snapshotFilePath in snapshotFilePaths)
|
||||
logger.WriteLineInfo($"* {snapshotFilePath}");
|
||||
}
|
||||
}
|
||||
|
||||
private void Init(ILogger logger)
|
||||
{
|
||||
try
|
||||
{
|
||||
logger.WriteLineInfo($"Ensuring that {ShortName} prerequisite is installed...");
|
||||
var progress = new Progress(logger, $"Installing {ShortName}");
|
||||
InitTool(progress);
|
||||
logger.WriteLineInfo($"{ShortName} prerequisite is installed");
|
||||
logger.WriteLineInfo($"{ShortName} runner path: {GetRunnerPath()}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.WriteLineError(e.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private string Start(ILogger logger, DiagnoserActionParameters parameters)
|
||||
{
|
||||
string snapshotFilePath = CreateSnapshotFilePath(parameters);
|
||||
string? snapshotDirectory = Path.GetDirectoryName(snapshotFilePath);
|
||||
logger.WriteLineInfo($"Target snapshot file: {snapshotFilePath}");
|
||||
if (!Directory.Exists(snapshotDirectory) && snapshotDirectory != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(snapshotDirectory);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.WriteLineError($"Failed to create directory: {snapshotDirectory}");
|
||||
logger.WriteLineError(e.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
logger.WriteLineInfo($"Attaching {ShortName} to the process...");
|
||||
Attach(parameters, snapshotFilePath);
|
||||
logger.WriteLineInfo($"{ShortName} is successfully attached");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.WriteLineError(e.ToString());
|
||||
return snapshotFilePath;
|
||||
}
|
||||
|
||||
return snapshotFilePath;
|
||||
}
|
||||
|
||||
private void Stop(ILogger logger)
|
||||
{
|
||||
try
|
||||
{
|
||||
logger.WriteLineInfo($"Taking {ShortName} snapshot...");
|
||||
TakeSnapshot();
|
||||
logger.WriteLineInfo($"{ShortName} snapshot is successfully taken");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.WriteLineError(e.ToString());
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
logger.WriteLineInfo($"Detaching {ShortName} from the process...");
|
||||
Detach();
|
||||
logger.WriteLineInfo($"{ShortName} is successfully detached");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.WriteLineError(e.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void Attach(DiagnoserActionParameters parameters, string snapshotFile)
|
||||
{
|
||||
int pid = parameters.Process.Id;
|
||||
int currentPid = Process.GetCurrentProcess().Id;
|
||||
if (pid != currentPid)
|
||||
AttachToProcessByPid(pid, snapshotFile);
|
||||
else
|
||||
AttachToCurrentProcess(snapshotFile);
|
||||
}
|
||||
|
||||
protected class Progress(ILogger logger, string title) : IProgress<double>
|
||||
{
|
||||
private static readonly TimeSpan ReportInterval = TimeSpan.FromSeconds(0.1);
|
||||
|
||||
private int lastProgress;
|
||||
private Stopwatch? stopwatch;
|
||||
|
||||
public void Report(double value)
|
||||
{
|
||||
int progress = (int)Math.Floor(value);
|
||||
bool needToReport = stopwatch == null ||
|
||||
(stopwatch != null && stopwatch?.Elapsed > ReportInterval) ||
|
||||
progress == 100;
|
||||
|
||||
if (lastProgress != progress && needToReport)
|
||||
{
|
||||
logger.WriteLineInfo($"{title}: {progress}%");
|
||||
lastProgress = progress;
|
||||
stopwatch = Stopwatch.StartNew();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,15 +3,15 @@ using BenchmarkDotNet.Diagnostics.dotMemory;
|
|||
using BenchmarkDotNet.Jobs;
|
||||
using Xunit;
|
||||
|
||||
namespace BenchmarkDotNet.Tests.dotMemory
|
||||
namespace BenchmarkDotNet.Tests.dotMemory;
|
||||
|
||||
public class DotMemoryTests
|
||||
{
|
||||
public class DotMemoryTests
|
||||
[Fact]
|
||||
public void AllRuntimeMonikerAreKnown()
|
||||
{
|
||||
[Fact]
|
||||
public void AllRuntimeMonikerAreKnown()
|
||||
{
|
||||
foreach (RuntimeMoniker moniker in Enum.GetValues(typeof(RuntimeMoniker)))
|
||||
DotMemoryDiagnoser.IsSupported(moniker); // Just check that it doesn't throw exceptions
|
||||
}
|
||||
var diagnoser = new DotMemoryDiagnoser();
|
||||
foreach (RuntimeMoniker moniker in Enum.GetValues(typeof(RuntimeMoniker)))
|
||||
diagnoser.IsSupported(moniker); // Just check that it doesn't throw exceptions
|
||||
}
|
||||
}
|
|
@ -3,15 +3,15 @@ using BenchmarkDotNet.Diagnostics.dotTrace;
|
|||
using BenchmarkDotNet.Jobs;
|
||||
using Xunit;
|
||||
|
||||
namespace BenchmarkDotNet.Tests.dotTrace
|
||||
namespace BenchmarkDotNet.Tests.dotTrace;
|
||||
|
||||
public class DotTraceTests
|
||||
{
|
||||
public class DotTraceTests
|
||||
[Fact]
|
||||
public void AllRuntimeMonikerAreKnown()
|
||||
{
|
||||
[Fact]
|
||||
public void AllRuntimeMonikerAreKnown()
|
||||
{
|
||||
foreach (RuntimeMoniker moniker in Enum.GetValues(typeof(RuntimeMoniker)))
|
||||
DotTraceDiagnoser.IsSupported(moniker); // Just check that it doesn't throw exceptions
|
||||
}
|
||||
var diagnoser = new DotTraceDiagnoser();
|
||||
foreach (RuntimeMoniker moniker in Enum.GetValues(typeof(RuntimeMoniker)))
|
||||
diagnoser.IsSupported(moniker); // Just check that it doesn't throw exceptions
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче