Skip to content

Commit

Permalink
Refactor dotTrace and dotMemory diagnosers
Browse files Browse the repository at this point in the history
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
AndreyAkinshin committed Aug 27, 2024
1 parent 296c996 commit 2322fb6
Show file tree
Hide file tree
Showing 15 changed files with 448 additions and 782 deletions.
12 changes: 3 additions & 9 deletions samples/BenchmarkDotNet.Samples/IntroDotMemoryDiagnoser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,10 @@

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)]
Expand Down
13 changes: 4 additions & 9 deletions samples/BenchmarkDotNet.Samples/IntroDotTraceDiagnoser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,11 @@

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]
Expand Down
225 changes: 99 additions & 126 deletions src/BenchmarkDotNet.Diagnostics.dotMemory/DotMemoryDiagnoser.cs
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");
}
}
}
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);
}
}
Loading

0 comments on commit 2322fb6

Please sign in to comment.