Skip to content

Commit ff6e8d9

Browse files
Add BenchmarkDotNet.Diagnostics.dotTrace
1 parent a260bd3 commit ff6e8d9

20 files changed

+661
-11
lines changed

BenchmarkDotNet.sln

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "templates", "templates", "{
4747
EndProject
4848
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BenchmarkDotNet.Templates", "templates\BenchmarkDotNet.Templates.csproj", "{B620D10A-CD8E-4A34-8B27-FD6257E63AD0}"
4949
EndProject
50+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BenchmarkDotNet.Diagnostics.dotTrace", "src\BenchmarkDotNet.Diagnostics.dotTrace\BenchmarkDotNet.Diagnostics.dotTrace.csproj", "{C5BDA61F-3A56-4B59-901D-0A17E78F4076}"
51+
EndProject
5052
Global
5153
GlobalSection(SolutionConfigurationPlatforms) = preSolution
5254
Debug|Any CPU = Debug|Any CPU
@@ -125,6 +127,10 @@ Global
125127
{B620D10A-CD8E-4A34-8B27-FD6257E63AD0}.Debug|Any CPU.Build.0 = Debug|Any CPU
126128
{B620D10A-CD8E-4A34-8B27-FD6257E63AD0}.Release|Any CPU.ActiveCfg = Release|Any CPU
127129
{B620D10A-CD8E-4A34-8B27-FD6257E63AD0}.Release|Any CPU.Build.0 = Release|Any CPU
130+
{C5BDA61F-3A56-4B59-901D-0A17E78F4076}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
131+
{C5BDA61F-3A56-4B59-901D-0A17E78F4076}.Debug|Any CPU.Build.0 = Debug|Any CPU
132+
{C5BDA61F-3A56-4B59-901D-0A17E78F4076}.Release|Any CPU.ActiveCfg = Release|Any CPU
133+
{C5BDA61F-3A56-4B59-901D-0A17E78F4076}.Release|Any CPU.Build.0 = Release|Any CPU
128134
EndGlobalSection
129135
GlobalSection(SolutionProperties) = preSolution
130136
HideSolutionNode = FALSE
@@ -148,6 +154,7 @@ Global
148154
{B4405781-40D3-42B8-B168-00E711FABA15} = {14195214-591A-45B7-851A-19D3BA2413F9}
149155
{D9F5065B-6190-431B-850C-117E3D64AB33} = {D6597E3A-6892-4A68-8E14-042FC941FDA2}
150156
{B620D10A-CD8E-4A34-8B27-FD6257E63AD0} = {63B94FD6-3F3D-4E04-9727-48E86AC4384C}
157+
{C5BDA61F-3A56-4B59-901D-0A17E78F4076} = {D6597E3A-6892-4A68-8E14-042FC941FDA2}
151158
EndGlobalSection
152159
GlobalSection(ExtensibilityGlobals) = postSolution
153160
SolutionGuid = {4D9AF12B-1F7F-45A7-9E8C-E4E46ADCBD1F}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
uid: BenchmarkDotNet.Samples.IntroDotTraceDiagnoser
3+
---
4+
5+
## Sample: IntroDotTraceDiagnoser
6+
7+
If you want to get a performance profile of your benchmarks, just add the `[DotTraceDiagnoser]` attribute, as shown below.
8+
As a result, BenchmarkDotNet performs bonus benchmark runs using attached
9+
[dotTrace Command-Line Profiler](https://www.jetbrains.com/help/profiler/Performance_Profiling__Profiling_Using_the_Command_Line.html).
10+
The obtained snapshots are saved to the `artifacts` folder.
11+
These snapshots can be opened using the [standalone dotTrace](https://www.jetbrains.com/profiler/),
12+
or [dotTrace in Rider](https://www.jetbrains.com/help/rider/Performance_Profiling.html).
13+
14+
### Source code
15+
16+
[!code-csharp[IntroDotTraceDiagnoser.cs](../../../samples/BenchmarkDotNet.Samples/IntroDotTraceDiagnoser.cs)]
17+
18+
### Links
19+
20+
* The permanent link to this sample: @BenchmarkDotNet.Samples.IntroDotTraceDiagnoser
21+
22+
---

docs/articles/samples/toc.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
href: IntroDisassemblyDry.md
3737
- name: IntroDisassemblyRyuJit
3838
href: IntroDisassemblyRyuJit.md
39+
- name: IntroDotTraceDiagnoser
40+
href: IntroDotTraceDiagnoser.md
3941
- name: IntroEnvVars
4042
href: IntroEnvVars.md
4143
- name: IntroEventPipeProfiler

samples/BenchmarkDotNet.Samples/BenchmarkDotNet.Samples.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
<PackageReference Include="System.Drawing.Common" Version="4.7.2" />
2222
</ItemGroup>
2323
<ItemGroup>
24+
<ProjectReference Include="..\..\src\BenchmarkDotNet.Diagnostics.dotTrace\BenchmarkDotNet.Diagnostics.dotTrace.csproj" />
2425
<ProjectReference Include="..\..\src\BenchmarkDotNet\BenchmarkDotNet.csproj" />
2526
<ProjectReference Include="..\..\src\BenchmarkDotNet.Diagnostics.Windows\BenchmarkDotNet.Diagnostics.Windows.csproj" />
2627
</ItemGroup>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using BenchmarkDotNet.Attributes;
2+
using BenchmarkDotNet.Diagnostics.dotTrace;
3+
4+
namespace BenchmarkDotNet.Samples
5+
{
6+
// Enables dotTrace profiling for all jobs
7+
[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]
16+
public class IntroDotTraceDiagnoser
17+
{
18+
[Benchmark]
19+
public void Fibonacci() => Fibonacci(30);
20+
21+
private static int Fibonacci(int n)
22+
{
23+
return n <= 1 ? n : Fibonacci(n - 1) + Fibonacci(n - 2);
24+
}
25+
}
26+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<Import Project="..\..\build\common.props" />
3+
<PropertyGroup>
4+
<TargetFrameworks>net6.0;net462;netcoreapp3.1</TargetFrameworks>
5+
<NoWarn>$(NoWarn);1591</NoWarn>
6+
<AssemblyTitle>BenchmarkDotNet.Diagnostics.dotTrace</AssemblyTitle>
7+
<AssemblyName>BenchmarkDotNet.Diagnostics.dotTrace</AssemblyName>
8+
<PackageId>BenchmarkDotNet.Diagnostics.dotTrace</PackageId>
9+
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<ProjectReference Include="..\BenchmarkDotNet\BenchmarkDotNet.csproj" />
13+
</ItemGroup>
14+
15+
<ItemGroup>
16+
<PackageReference Include="JetBrains.Profiler.SelfApi" Version="2.4.2" />
17+
</ItemGroup>
18+
19+
</Project>
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Collections.Immutable;
4+
using System.IO;
5+
using System.Linq;
6+
using BenchmarkDotNet.Analysers;
7+
using BenchmarkDotNet.Diagnosers;
8+
using BenchmarkDotNet.Engines;
9+
using BenchmarkDotNet.Exporters;
10+
using BenchmarkDotNet.Extensions;
11+
using BenchmarkDotNet.Jobs;
12+
using BenchmarkDotNet.Loggers;
13+
using BenchmarkDotNet.Portability;
14+
using BenchmarkDotNet.Reports;
15+
using BenchmarkDotNet.Running;
16+
using BenchmarkDotNet.Toolchains;
17+
using BenchmarkDotNet.Validators;
18+
using RunMode = BenchmarkDotNet.Diagnosers.RunMode;
19+
20+
namespace BenchmarkDotNet.Diagnostics.dotTrace
21+
{
22+
public class DotTraceDiagnoser : IProfiler
23+
{
24+
private readonly Uri nugetUrl;
25+
private readonly string toolsDownloadFolder;
26+
27+
public DotTraceDiagnoser(Uri nugetUrl = null, string toolsDownloadFolder = null)
28+
{
29+
this.nugetUrl = nugetUrl;
30+
this.toolsDownloadFolder = toolsDownloadFolder;
31+
}
32+
33+
public IEnumerable<string> Ids => new[] { "DotTrace" };
34+
public string ShortName => "dotTrace";
35+
36+
public RunMode GetRunMode(BenchmarkCase benchmarkCase)
37+
{
38+
return IsSupported(benchmarkCase.Job.Environment.GetRuntime().RuntimeMoniker) ? RunMode.ExtraRun : RunMode.None;
39+
}
40+
41+
private readonly List<string> snapshotFilePaths = new ();
42+
43+
public void Handle(HostSignal signal, DiagnoserActionParameters parameters)
44+
{
45+
var job = parameters.BenchmarkCase.Job;
46+
bool isInProcess = job.GetToolchain().IsInProcess;
47+
var logger = parameters.Config.GetCompositeLogger();
48+
DotTraceToolBase tool = isInProcess
49+
? new InProcessDotTraceTool(logger, nugetUrl, downloadTo: toolsDownloadFolder)
50+
: new ExternalDotTraceTool(logger, nugetUrl, downloadTo: toolsDownloadFolder);
51+
52+
var runtimeMoniker = job.Environment.GetRuntime().RuntimeMoniker;
53+
if (!IsSupported(runtimeMoniker))
54+
{
55+
logger.WriteLineError($"Runtime '{runtimeMoniker}' is not supported by dotTrace");
56+
return;
57+
}
58+
59+
switch (signal)
60+
{
61+
case HostSignal.BeforeAnythingElse:
62+
tool.Init(parameters);
63+
break;
64+
case HostSignal.BeforeActualRun:
65+
snapshotFilePaths.Add(tool.Start(parameters));
66+
break;
67+
case HostSignal.AfterActualRun:
68+
tool.Stop(parameters);
69+
break;
70+
}
71+
}
72+
73+
public IEnumerable<IExporter> Exporters => Enumerable.Empty<IExporter>();
74+
public IEnumerable<IAnalyser> Analysers => Enumerable.Empty<IAnalyser>();
75+
76+
public IEnumerable<ValidationError> Validate(ValidationParameters validationParameters)
77+
{
78+
var runtimeMonikers = validationParameters.Benchmarks.Select(b => b.Job.Environment.GetRuntime().RuntimeMoniker).Distinct();
79+
foreach (var runtimeMoniker in runtimeMonikers)
80+
{
81+
if (!IsSupported(runtimeMoniker))
82+
yield return new ValidationError(true, $"Runtime '{runtimeMoniker}' is not supported by dotTrace");
83+
}
84+
}
85+
86+
internal static bool IsSupported(RuntimeMoniker runtimeMoniker)
87+
{
88+
switch (runtimeMoniker)
89+
{
90+
case RuntimeMoniker.HostProcess:
91+
case RuntimeMoniker.Net461:
92+
case RuntimeMoniker.Net462:
93+
case RuntimeMoniker.Net47:
94+
case RuntimeMoniker.Net471:
95+
case RuntimeMoniker.Net472:
96+
case RuntimeMoniker.Net48:
97+
case RuntimeMoniker.Net481:
98+
case RuntimeMoniker.Net50:
99+
case RuntimeMoniker.Net60:
100+
case RuntimeMoniker.Net70:
101+
case RuntimeMoniker.Net80:
102+
return true;
103+
case RuntimeMoniker.NotRecognized:
104+
case RuntimeMoniker.Mono:
105+
case RuntimeMoniker.NativeAot60:
106+
case RuntimeMoniker.NativeAot70:
107+
case RuntimeMoniker.NativeAot80:
108+
case RuntimeMoniker.Wasm:
109+
case RuntimeMoniker.WasmNet50:
110+
case RuntimeMoniker.WasmNet60:
111+
case RuntimeMoniker.WasmNet70:
112+
case RuntimeMoniker.WasmNet80:
113+
case RuntimeMoniker.MonoAOTLLVM:
114+
case RuntimeMoniker.MonoAOTLLVMNet60:
115+
case RuntimeMoniker.MonoAOTLLVMNet70:
116+
case RuntimeMoniker.MonoAOTLLVMNet80:
117+
case RuntimeMoniker.Mono60:
118+
case RuntimeMoniker.Mono70:
119+
case RuntimeMoniker.Mono80:
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 RuntimeInformation.IsWindows();
128+
case RuntimeMoniker.NetCoreApp30:
129+
case RuntimeMoniker.NetCoreApp31:
130+
return RuntimeInformation.IsWindows() || RuntimeInformation.IsLinux();
131+
default:
132+
throw new ArgumentOutOfRangeException(nameof(runtimeMoniker), runtimeMoniker, $"Runtime moniker {runtimeMoniker} is not supported");
133+
}
134+
}
135+
136+
public IEnumerable<Metric> ProcessResults(DiagnoserResults results) => ImmutableArray<Metric>.Empty;
137+
138+
public void DisplayResults(ILogger logger)
139+
{
140+
if (snapshotFilePaths.Any())
141+
{
142+
logger.WriteLineInfo("The following dotTrace snapshots were generated:");
143+
foreach (string snapshotFilePath in snapshotFilePaths)
144+
logger.WriteLineInfo($"* {snapshotFilePath}");
145+
}
146+
}
147+
}
148+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using System;
2+
using BenchmarkDotNet.Configs;
3+
4+
namespace BenchmarkDotNet.Diagnostics.dotTrace
5+
{
6+
[AttributeUsage(AttributeTargets.Class)]
7+
public class DotTraceDiagnoserAttribute : Attribute, IConfigSource
8+
{
9+
public IConfig Config { get; }
10+
11+
public DotTraceDiagnoserAttribute()
12+
{
13+
Config = ManualConfig.CreateEmpty().AddDiagnoser(new DotTraceDiagnoser());
14+
}
15+
16+
public DotTraceDiagnoserAttribute(Uri nugetUrl = null, string toolsDownloadFolder = null)
17+
{
18+
Config = ManualConfig.CreateEmpty().AddDiagnoser(new DotTraceDiagnoser(nugetUrl, toolsDownloadFolder));
19+
}
20+
}
21+
}

0 commit comments

Comments
 (0)