Skip to content

Commit 92f33f2

Browse files
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.
1 parent 296c996 commit 92f33f2

File tree

15 files changed

+448
-782
lines changed

15 files changed

+448
-782
lines changed

samples/BenchmarkDotNet.Samples/IntroDotMemoryDiagnoser.cs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,10 @@
44

55
namespace BenchmarkDotNet.Samples
66
{
7-
// Enables dotMemory profiling for all jobs
7+
// Profile benchmarks via dotMemory SelfApi profiling for all jobs
88
[DotMemoryDiagnoser]
9-
// Adds the default "external-process" job
10-
// Profiling is performed using dotMemory Command-Line Profiler
11-
// See: https://www.jetbrains.com/help/dotmemory/Working_with_dotMemory_Command-Line_Profiler.html
12-
[SimpleJob]
13-
// Adds an "in-process" job
14-
// Profiling is performed using dotMemory SelfApi
15-
// NuGet reference: https://www.nuget.org/packages/JetBrains.Profiler.SelfApi
16-
[InProcess]
9+
[SimpleJob] // external-process execution
10+
[InProcess] // in-process execution
1711
public class IntroDotMemoryDiagnoser
1812
{
1913
[Params(1024)]

samples/BenchmarkDotNet.Samples/IntroDotTraceDiagnoser.cs

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,11 @@
33

44
namespace BenchmarkDotNet.Samples
55
{
6-
// Enables dotTrace profiling for all jobs
6+
// Profile benchmarks via dotTrace SelfApi profiling for all jobs
7+
// See: https://www.nuget.org/packages/JetBrains.Profiler.SelfApi
78
[DotTraceDiagnoser]
8-
// Adds the default "external-process" job
9-
// Profiling is performed using dotTrace command-line Tools
10-
// See: https://www.jetbrains.com/help/profiler/Performance_Profiling__Profiling_Using_the_Command_Line.html
11-
[SimpleJob]
12-
// Adds an "in-process" job
13-
// Profiling is performed using dotTrace SelfApi
14-
// NuGet reference: https://www.nuget.org/packages/JetBrains.Profiler.SelfApi
15-
[InProcess]
9+
[SimpleJob] // external-process execution
10+
[InProcess] // in-process execution
1611
public class IntroDotTraceDiagnoser
1712
{
1813
[Benchmark]
Lines changed: 99 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -1,148 +1,121 @@
11
using System;
2-
using System.Collections.Generic;
3-
using System.Collections.Immutable;
4-
using System.Linq;
5-
using BenchmarkDotNet.Analysers;
2+
using System.Reflection;
63
using BenchmarkDotNet.Detectors;
74
using BenchmarkDotNet.Diagnosers;
8-
using BenchmarkDotNet.Engines;
9-
using BenchmarkDotNet.Exporters;
5+
using BenchmarkDotNet.Helpers;
106
using BenchmarkDotNet.Jobs;
11-
using BenchmarkDotNet.Loggers;
12-
using BenchmarkDotNet.Reports;
13-
using BenchmarkDotNet.Running;
14-
using BenchmarkDotNet.Validators;
15-
using RunMode = BenchmarkDotNet.Diagnosers.RunMode;
7+
using JetBrains.Profiler.SelfApi;
168

17-
namespace BenchmarkDotNet.Diagnostics.dotMemory
9+
namespace BenchmarkDotNet.Diagnostics.dotMemory;
10+
11+
public class DotMemoryDiagnoser(Uri? nugetUrl = null, string? downloadTo = null) : SnapshotProfilerBase
1812
{
19-
public class DotMemoryDiagnoser(Uri? nugetUrl = null, string? toolsDownloadFolder = null) : IProfiler
13+
public override string ShortName => "dotMemory";
14+
15+
protected override void InitTool(Progress progress)
2016
{
21-
private DotMemoryTool? tool;
17+
DotMemory.InitAsync(progress, nugetUrl, NuGetApi.V3, downloadTo).Wait();
18+
}
2219

23-
public IEnumerable<string> Ids => new[] { "DotMemory" };
24-
public string ShortName => "dotMemory";
20+
protected override void AttachToCurrentProcess(string snapshotFile)
21+
{
22+
DotMemory.Attach(new DotMemory.Config().SaveToFile(snapshotFile));
23+
}
2524

26-
public RunMode GetRunMode(BenchmarkCase benchmarkCase)
27-
{
28-
return IsSupported(benchmarkCase.Job.Environment.GetRuntime().RuntimeMoniker) ? RunMode.ExtraRun : RunMode.None;
29-
}
25+
protected override void AttachToProcessByPid(int pid, string snapshotFile)
26+
{
27+
DotMemory.Attach(new DotMemory.Config().ProfileExternalProcess(pid).SaveToFile(snapshotFile));
28+
}
3029

31-
private readonly List<string> snapshotFilePaths = new ();
30+
protected override void TakeSnapshot()
31+
{
32+
DotMemory.GetSnapshot();
33+
}
3234

33-
public void Handle(HostSignal signal, DiagnoserActionParameters parameters)
34-
{
35-
var logger = parameters.Config.GetCompositeLogger();
36-
var job = parameters.BenchmarkCase.Job;
35+
protected override void Detach()
36+
{
37+
DotMemory.Detach();
38+
}
3739

38-
var runtimeMoniker = job.Environment.GetRuntime().RuntimeMoniker;
39-
if (!IsSupported(runtimeMoniker))
40-
{
41-
logger.WriteLineError($"Runtime '{runtimeMoniker}' is not supported by dotMemory");
42-
return;
43-
}
40+
protected override string CreateSnapshotFilePath(DiagnoserActionParameters parameters)
41+
{
42+
return ArtifactFileNameHelper.GetFilePath(parameters, "snapshots", DateTime.Now, "dmw", ".0000".Length);
43+
}
4444

45-
switch (signal)
46-
{
47-
case HostSignal.BeforeAnythingElse:
48-
if (tool is null)
49-
{
50-
tool = new DotMemoryTool(logger, nugetUrl, downloadTo: toolsDownloadFolder);
51-
tool.Init();
52-
}
53-
break;
54-
case HostSignal.BeforeActualRun:
55-
if (tool is null)
56-
throw new InvalidOperationException("DotMemory tool is not initialized");
57-
snapshotFilePaths.Add(tool.Start(parameters));
58-
break;
59-
case HostSignal.AfterActualRun:
60-
if (tool is null)
61-
throw new InvalidOperationException("DotMemory tool is not initialized");
62-
tool.Stop();
63-
tool = null;
64-
break;
65-
}
66-
}
45+
protected override string GetRunnerPath()
46+
{
47+
var consoleRunnerPackageField = typeof(DotMemory).GetField("ConsoleRunnerPackage", BindingFlags.NonPublic | BindingFlags.Static);
48+
if (consoleRunnerPackageField == null)
49+
throw new InvalidOperationException("Field 'ConsoleRunnerPackage' not found.");
6750

68-
public IEnumerable<IExporter> Exporters => Enumerable.Empty<IExporter>();
69-
public IEnumerable<IAnalyser> Analysers => Enumerable.Empty<IAnalyser>();
51+
object? consoleRunnerPackage = consoleRunnerPackageField.GetValue(null);
52+
if (consoleRunnerPackage == null)
53+
throw new InvalidOperationException("Unable to get value of 'ConsoleRunnerPackage'.");
7054

71-
public IEnumerable<ValidationError> Validate(ValidationParameters validationParameters)
72-
{
73-
var runtimeMonikers = validationParameters.Benchmarks.Select(b => b.Job.Environment.GetRuntime().RuntimeMoniker).Distinct();
74-
foreach (var runtimeMoniker in runtimeMonikers)
75-
{
76-
if (!IsSupported(runtimeMoniker))
77-
yield return new ValidationError(true, $"Runtime '{runtimeMoniker}' is not supported by dotMemory");
78-
}
79-
}
55+
var consoleRunnerPackageType = consoleRunnerPackage.GetType();
56+
var getRunnerPathMethod = consoleRunnerPackageType.GetMethod("GetRunnerPath");
57+
if (getRunnerPathMethod == null)
58+
throw new InvalidOperationException("Method 'GetRunnerPath' not found.");
8059

81-
internal static bool IsSupported(RuntimeMoniker runtimeMoniker)
82-
{
83-
switch (runtimeMoniker)
84-
{
85-
case RuntimeMoniker.HostProcess:
86-
case RuntimeMoniker.Net461:
87-
case RuntimeMoniker.Net462:
88-
case RuntimeMoniker.Net47:
89-
case RuntimeMoniker.Net471:
90-
case RuntimeMoniker.Net472:
91-
case RuntimeMoniker.Net48:
92-
case RuntimeMoniker.Net481:
93-
case RuntimeMoniker.Net50:
94-
case RuntimeMoniker.Net60:
95-
case RuntimeMoniker.Net70:
96-
case RuntimeMoniker.Net80:
97-
case RuntimeMoniker.Net90:
98-
return true;
99-
case RuntimeMoniker.NotRecognized:
100-
case RuntimeMoniker.Mono:
101-
case RuntimeMoniker.NativeAot60:
102-
case RuntimeMoniker.NativeAot70:
103-
case RuntimeMoniker.NativeAot80:
104-
case RuntimeMoniker.NativeAot90:
105-
case RuntimeMoniker.Wasm:
106-
case RuntimeMoniker.WasmNet50:
107-
case RuntimeMoniker.WasmNet60:
108-
case RuntimeMoniker.WasmNet70:
109-
case RuntimeMoniker.WasmNet80:
110-
case RuntimeMoniker.WasmNet90:
111-
case RuntimeMoniker.MonoAOTLLVM:
112-
case RuntimeMoniker.MonoAOTLLVMNet60:
113-
case RuntimeMoniker.MonoAOTLLVMNet70:
114-
case RuntimeMoniker.MonoAOTLLVMNet80:
115-
case RuntimeMoniker.MonoAOTLLVMNet90:
116-
case RuntimeMoniker.Mono60:
117-
case RuntimeMoniker.Mono70:
118-
case RuntimeMoniker.Mono80:
119-
case RuntimeMoniker.Mono90:
120-
#pragma warning disable CS0618 // Type or member is obsolete
121-
case RuntimeMoniker.NetCoreApp50:
122-
#pragma warning restore CS0618 // Type or member is obsolete
123-
return false;
124-
case RuntimeMoniker.NetCoreApp20:
125-
case RuntimeMoniker.NetCoreApp21:
126-
case RuntimeMoniker.NetCoreApp22:
127-
return OsDetector.IsWindows();
128-
case RuntimeMoniker.NetCoreApp30:
129-
case RuntimeMoniker.NetCoreApp31:
130-
return OsDetector.IsWindows() || OsDetector.IsLinux();
131-
default:
132-
throw new ArgumentOutOfRangeException(nameof(runtimeMoniker), runtimeMoniker, $"Runtime moniker {runtimeMoniker} is not supported");
133-
}
134-
}
60+
string? runnerPath = getRunnerPathMethod.Invoke(consoleRunnerPackage, null) as string;
61+
if (runnerPath == null)
62+
throw new InvalidOperationException("Unable to invoke 'GetRunnerPath'.");
13563

136-
public IEnumerable<Metric> ProcessResults(DiagnoserResults results) => ImmutableArray<Metric>.Empty;
64+
return runnerPath;
65+
}
13766

138-
public void DisplayResults(ILogger logger)
67+
internal override bool IsSupported(RuntimeMoniker runtimeMoniker)
68+
{
69+
switch (runtimeMoniker)
13970
{
140-
if (snapshotFilePaths.Any())
141-
{
142-
logger.WriteLineInfo("The following dotMemory snapshots were generated:");
143-
foreach (string snapshotFilePath in snapshotFilePaths)
144-
logger.WriteLineInfo($"* {snapshotFilePath}");
145-
}
71+
case RuntimeMoniker.HostProcess:
72+
case RuntimeMoniker.Net461:
73+
case RuntimeMoniker.Net462:
74+
case RuntimeMoniker.Net47:
75+
case RuntimeMoniker.Net471:
76+
case RuntimeMoniker.Net472:
77+
case RuntimeMoniker.Net48:
78+
case RuntimeMoniker.Net481:
79+
case RuntimeMoniker.Net50:
80+
case RuntimeMoniker.Net60:
81+
case RuntimeMoniker.Net70:
82+
case RuntimeMoniker.Net80:
83+
case RuntimeMoniker.Net90:
84+
return true;
85+
case RuntimeMoniker.NotRecognized:
86+
case RuntimeMoniker.Mono:
87+
case RuntimeMoniker.NativeAot60:
88+
case RuntimeMoniker.NativeAot70:
89+
case RuntimeMoniker.NativeAot80:
90+
case RuntimeMoniker.NativeAot90:
91+
case RuntimeMoniker.Wasm:
92+
case RuntimeMoniker.WasmNet50:
93+
case RuntimeMoniker.WasmNet60:
94+
case RuntimeMoniker.WasmNet70:
95+
case RuntimeMoniker.WasmNet80:
96+
case RuntimeMoniker.WasmNet90:
97+
case RuntimeMoniker.MonoAOTLLVM:
98+
case RuntimeMoniker.MonoAOTLLVMNet60:
99+
case RuntimeMoniker.MonoAOTLLVMNet70:
100+
case RuntimeMoniker.MonoAOTLLVMNet80:
101+
case RuntimeMoniker.MonoAOTLLVMNet90:
102+
case RuntimeMoniker.Mono60:
103+
case RuntimeMoniker.Mono70:
104+
case RuntimeMoniker.Mono80:
105+
case RuntimeMoniker.Mono90:
106+
#pragma warning disable CS0618 // Type or member is obsolete
107+
case RuntimeMoniker.NetCoreApp50:
108+
#pragma warning restore CS0618 // Type or member is obsolete
109+
return false;
110+
case RuntimeMoniker.NetCoreApp20:
111+
case RuntimeMoniker.NetCoreApp21:
112+
case RuntimeMoniker.NetCoreApp22:
113+
return OsDetector.IsWindows();
114+
case RuntimeMoniker.NetCoreApp30:
115+
case RuntimeMoniker.NetCoreApp31:
116+
return OsDetector.IsWindows() || OsDetector.IsLinux();
117+
default:
118+
throw new ArgumentOutOfRangeException(nameof(runtimeMoniker), runtimeMoniker, $"Runtime moniker {runtimeMoniker} is not supported");
146119
}
147120
}
148121
}
Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
using System;
22
using BenchmarkDotNet.Configs;
33

4-
namespace BenchmarkDotNet.Diagnostics.dotMemory
4+
namespace BenchmarkDotNet.Diagnostics.dotMemory;
5+
6+
[AttributeUsage(AttributeTargets.Class)]
7+
public class DotMemoryDiagnoserAttribute : Attribute, IConfigSource
58
{
6-
[AttributeUsage(AttributeTargets.Class)]
7-
public class DotMemoryDiagnoserAttribute : Attribute, IConfigSource
8-
{
9-
public IConfig Config { get; }
9+
public IConfig Config { get; }
1010

11-
public DotMemoryDiagnoserAttribute()
12-
{
13-
Config = ManualConfig.CreateEmpty().AddDiagnoser(new DotMemoryDiagnoser());
14-
}
11+
public DotMemoryDiagnoserAttribute()
12+
{
13+
var diagnoser = new DotMemoryDiagnoser();
14+
Config = ManualConfig.CreateEmpty().AddDiagnoser(diagnoser);
15+
}
1516

16-
public DotMemoryDiagnoserAttribute(string? nugetUrl = null, string? toolsDownloadFolder = null)
17-
{
18-
var nugetUri = nugetUrl == null ? null : new Uri(nugetUrl);
19-
Config = ManualConfig.CreateEmpty().AddDiagnoser(new DotMemoryDiagnoser(nugetUri, toolsDownloadFolder));
20-
}
17+
public DotMemoryDiagnoserAttribute(Uri? nugetUrl, string? downloadTo = null)
18+
{
19+
var diagnoser = new DotMemoryDiagnoser(nugetUrl, downloadTo);
20+
Config = ManualConfig.CreateEmpty().AddDiagnoser(diagnoser);
2121
}
2222
}

0 commit comments

Comments
 (0)