forked from dotnet/BenchmarkDotNet
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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.
- Loading branch information
1 parent
296c996
commit 92f33f2
Showing
15 changed files
with
448 additions
and
782 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
225 changes: 99 additions & 126 deletions
225
src/BenchmarkDotNet.Diagnostics.dotMemory/DotMemoryDiagnoser.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
{ | ||
return IsSupported(benchmarkCase.Job.Environment.GetRuntime().RuntimeMoniker) ? RunMode.ExtraRun : RunMode.None; | ||
} | ||
protected override void AttachToProcessByPid(int pid, string snapshotFile) | ||
{ | ||
DotMemory.Attach(new DotMemory.Config().ProfileExternalProcess(pid).SaveToFile(snapshotFile)); | ||
} | ||
|
||
private readonly List<string> snapshotFilePaths = new (); | ||
protected override void TakeSnapshot() | ||
{ | ||
DotMemory.GetSnapshot(); | ||
} | ||
|
||
public void Handle(HostSignal signal, DiagnoserActionParameters parameters) | ||
{ | ||
var logger = parameters.Config.GetCompositeLogger(); | ||
var job = parameters.BenchmarkCase.Job; | ||
protected override void Detach() | ||
{ | ||
DotMemory.Detach(); | ||
} | ||
|
||
var runtimeMoniker = job.Environment.GetRuntime().RuntimeMoniker; | ||
if (!IsSupported(runtimeMoniker)) | ||
{ | ||
logger.WriteLineError($"Runtime '{runtimeMoniker}' is not supported by dotMemory"); | ||
return; | ||
} | ||
protected override string CreateSnapshotFilePath(DiagnoserActionParameters parameters) | ||
{ | ||
return ArtifactFileNameHelper.GetFilePath(parameters, "snapshots", DateTime.Now, "dmw", ".0000".Length); | ||
} | ||
|
||
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; | ||
} | ||
} | ||
protected override string GetRunnerPath() | ||
{ | ||
var consoleRunnerPackageField = typeof(DotMemory).GetField("ConsoleRunnerPackage", BindingFlags.NonPublic | BindingFlags.Static); | ||
if (consoleRunnerPackageField == null) | ||
throw new InvalidOperationException("Field 'ConsoleRunnerPackage' not found."); | ||
|
||
public IEnumerable<IExporter> Exporters => Enumerable.Empty<IExporter>(); | ||
public IEnumerable<IAnalyser> Analysers => Enumerable.Empty<IAnalyser>(); | ||
object? consoleRunnerPackage = consoleRunnerPackageField.GetValue(null); | ||
if (consoleRunnerPackage == null) | ||
throw new InvalidOperationException("Unable to get value of 'ConsoleRunnerPackage'."); | ||
|
||
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"); | ||
} | ||
} | ||
var consoleRunnerPackageType = consoleRunnerPackage.GetType(); | ||
var getRunnerPathMethod = consoleRunnerPackageType.GetMethod("GetRunnerPath"); | ||
if (getRunnerPathMethod == null) | ||
throw new InvalidOperationException("Method 'GetRunnerPath' not found."); | ||
|
||
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: | ||
#pragma warning disable CS0618 // Type or member is obsolete | ||
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"); | ||
} | ||
} | ||
string? runnerPath = getRunnerPathMethod.Invoke(consoleRunnerPackage, null) as string; | ||
if (runnerPath == null) | ||
throw new InvalidOperationException("Unable to invoke 'GetRunnerPath'."); | ||
|
||
public IEnumerable<Metric> ProcessResults(DiagnoserResults results) => ImmutableArray<Metric>.Empty; | ||
return runnerPath; | ||
} | ||
|
||
public void DisplayResults(ILogger logger) | ||
internal override bool IsSupported(RuntimeMoniker runtimeMoniker) | ||
{ | ||
switch (runtimeMoniker) | ||
{ | ||
if (snapshotFilePaths.Any()) | ||
{ | ||
logger.WriteLineInfo("The following dotMemory snapshots were generated:"); | ||
foreach (string snapshotFilePath in snapshotFilePaths) | ||
logger.WriteLineInfo($"* {snapshotFilePath}"); | ||
} | ||
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: | ||
#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"); | ||
} | ||
} | ||
} |
28 changes: 14 additions & 14 deletions
28
src/BenchmarkDotNet.Diagnostics.dotMemory/DotMemoryDiagnoserAttribute.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 IConfig Config { get; } | ||
|
||
public DotMemoryDiagnoserAttribute() | ||
{ | ||
Config = ManualConfig.CreateEmpty().AddDiagnoser(new DotMemoryDiagnoser()); | ||
} | ||
public DotMemoryDiagnoserAttribute() | ||
{ | ||
var diagnoser = new DotMemoryDiagnoser(); | ||
Config = ManualConfig.CreateEmpty().AddDiagnoser(diagnoser); | ||
} | ||
|
||
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); | ||
} | ||
} |
Oops, something went wrong.