Skip to content

Commit c7b7abf

Browse files
authoredApr 1, 2024··
DotMemoryDiagnoser implementation (#2549)
* DotMemoryDiagnoser implementation * Typo fix, less alloc in test * attempt to fix reinit logic for tool * Simplify implementation
1 parent 4ab69be commit c7b7abf

File tree

18 files changed

+567
-1
lines changed

18 files changed

+567
-1
lines changed
 

‎BenchmarkDotNet.sln

+7
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BenchmarkDotNet.Integration
5353
EndProject
5454
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BenchmarkDotNet.TestAdapter", "src\BenchmarkDotNet.TestAdapter\BenchmarkDotNet.TestAdapter.csproj", "{4C9C89B8-7C4E-4ECF-B3C9-324C8772EDAC}"
5555
EndProject
56+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BenchmarkDotNet.Diagnostics.dotMemory", "src\BenchmarkDotNet.Diagnostics.dotMemory\BenchmarkDotNet.Diagnostics.dotMemory.csproj", "{2E2283A3-6DA6-4482-8518-99D6D9F689AB}"
57+
EndProject
5658
Global
5759
GlobalSection(SolutionConfigurationPlatforms) = preSolution
5860
Debug|Any CPU = Debug|Any CPU
@@ -143,6 +145,10 @@ Global
143145
{4C9C89B8-7C4E-4ECF-B3C9-324C8772EDAC}.Debug|Any CPU.Build.0 = Debug|Any CPU
144146
{4C9C89B8-7C4E-4ECF-B3C9-324C8772EDAC}.Release|Any CPU.ActiveCfg = Release|Any CPU
145147
{4C9C89B8-7C4E-4ECF-B3C9-324C8772EDAC}.Release|Any CPU.Build.0 = Release|Any CPU
148+
{2E2283A3-6DA6-4482-8518-99D6D9F689AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
149+
{2E2283A3-6DA6-4482-8518-99D6D9F689AB}.Debug|Any CPU.Build.0 = Debug|Any CPU
150+
{2E2283A3-6DA6-4482-8518-99D6D9F689AB}.Release|Any CPU.ActiveCfg = Release|Any CPU
151+
{2E2283A3-6DA6-4482-8518-99D6D9F689AB}.Release|Any CPU.Build.0 = Release|Any CPU
146152
EndGlobalSection
147153
GlobalSection(SolutionProperties) = preSolution
148154
HideSolutionNode = FALSE
@@ -169,6 +175,7 @@ Global
169175
{C5BDA61F-3A56-4B59-901D-0A17E78F4076} = {D6597E3A-6892-4A68-8E14-042FC941FDA2}
170176
{AACA2C63-A85B-47AB-99FC-72C3FF408B14} = {14195214-591A-45B7-851A-19D3BA2413F9}
171177
{4C9C89B8-7C4E-4ECF-B3C9-324C8772EDAC} = {D6597E3A-6892-4A68-8E14-042FC941FDA2}
178+
{2E2283A3-6DA6-4482-8518-99D6D9F689AB} = {D6597E3A-6892-4A68-8E14-042FC941FDA2}
172179
EndGlobalSection
173180
GlobalSection(ExtensibilityGlobals) = postSolution
174181
SolutionGuid = {4D9AF12B-1F7F-45A7-9E8C-E4E46ADCBD1F}

‎docs/articles/guides/nuget.md

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ We have the following set of NuGet packages (you can install it directly from `n
1313
* `BenchmarkDotNet.Annotations`: Basic BenchmarkDotNet annotations for your benchmarks.
1414
* `BenchmarkDotNet.Diagnostics.Windows`: an additional optional package that provides a set of Windows diagnosers.
1515
* `BenchmarkDotNet.Diagnostics.dotTrace`: an additional optional package that provides DotTraceDiagnoser.
16+
* `BenchmarkDotNet.Diagnostics.dotMemory`: an additional optional package that provides DotMemoryDiagnoser.
1617
* `BenchmarkDotNet.Templates`: Templates for BenchmarkDotNet.
1718

1819
You might find other NuGet packages that start with `BenchmarkDotNet` name, but they are internal BDN packages that should not be installed manually. All that matters are the three packages mentioned above.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
uid: BenchmarkDotNet.Samples.IntroDotMemoryDiagnoser
3+
---
4+
5+
## Sample: IntroDotMemoryDiagnoser
6+
7+
If you want to get a memory allocation profile of your benchmarks, just add the `[DotMemoryDiagnoser]` attribute, as shown below.
8+
As a result, BenchmarkDotNet performs bonus benchmark runs using attached
9+
[dotMemory Command-Line Profiler](https://www.jetbrains.com/help/dotmemory/Working_with_dotMemory_Command-Line_Profiler.html).
10+
The obtained dotMemory workspaces are saved to the `artifacts` folder.
11+
These dotMemory workspaces can be opened using the [standalone dotMemory](https://www.jetbrains.com/dotmemory/),
12+
or [dotMemory in Rider](https://www.jetbrains.com/help/rider/Memory_profiling_of_.NET_code.html).
13+
14+
### Source code
15+
16+
[!code-csharp[IntroDotMemoryDiagnoser.cs](../../../samples/BenchmarkDotNet.Samples/IntroDotMemoryDiagnoser.cs)]
17+
18+
### Links
19+
20+
* The permanent link to this sample: @BenchmarkDotNet.Samples.IntroDotMemoryDiagnoser
21+
22+
---

‎docs/articles/samples/toc.yml

+2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
href: IntroDisassemblyRyuJit.md
3939
- name: IntroDotTraceDiagnoser
4040
href: IntroDotTraceDiagnoser.md
41+
- name: IntroDotMemoryDiagnoser
42+
href: IntroDotMemoryDiagnoser.md
4143
- name: IntroEnvVars
4244
href: IntroEnvVars.md
4345
- name: IntroEventPipeProfiler

‎samples/BenchmarkDotNet.Samples/BenchmarkDotNet.Samples.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
</ItemGroup>
2727
<ItemGroup>
2828
<ProjectReference Include="..\..\src\BenchmarkDotNet.Diagnostics.dotTrace\BenchmarkDotNet.Diagnostics.dotTrace.csproj" />
29+
<ProjectReference Include="..\..\src\BenchmarkDotNet.Diagnostics.dotMemory\BenchmarkDotNet.Diagnostics.dotMemory.csproj" />
2930
<ProjectReference Include="..\..\src\BenchmarkDotNet\BenchmarkDotNet.csproj" />
3031
<ProjectReference Include="..\..\src\BenchmarkDotNet.Diagnostics.Windows\BenchmarkDotNet.Diagnostics.Windows.csproj" />
3132
<ProjectReference Include="..\..\src\BenchmarkDotNet.TestAdapter\BenchmarkDotNet.TestAdapter.csproj" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using BenchmarkDotNet.Attributes;
2+
using BenchmarkDotNet.Diagnostics.dotMemory;
3+
using System.Collections.Generic;
4+
5+
namespace BenchmarkDotNet.Samples
6+
{
7+
// Enables dotMemory profiling for all jobs
8+
[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]
17+
public class IntroDotMemoryDiagnoser
18+
{
19+
[Params(1024)]
20+
public int Size;
21+
22+
private byte[] dataArray;
23+
private IEnumerable<byte> dataEnumerable;
24+
25+
[GlobalSetup]
26+
public void Setup()
27+
{
28+
dataArray = new byte[Size];
29+
dataEnumerable = dataArray;
30+
}
31+
32+
[Benchmark]
33+
public int IterateArray()
34+
{
35+
var count = 0;
36+
foreach (var _ in dataArray)
37+
count++;
38+
39+
return count;
40+
}
41+
42+
[Benchmark]
43+
public int IterateEnumerable()
44+
{
45+
var count = 0;
46+
foreach (var _ in dataEnumerable)
47+
count++;
48+
49+
return count;
50+
}
51+
}
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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.dotMemory</AssemblyTitle>
7+
<AssemblyName>BenchmarkDotNet.Diagnostics.dotMemory</AssemblyName>
8+
<PackageId>BenchmarkDotNet.Diagnostics.dotMemory</PackageId>
9+
<Nullable>enable</Nullable>
10+
</PropertyGroup>
11+
12+
<ItemGroup>
13+
<ProjectReference Include="..\BenchmarkDotNet\BenchmarkDotNet.csproj" />
14+
</ItemGroup>
15+
16+
<ItemGroup>
17+
<PackageReference Include="JetBrains.Profiler.SelfApi" Version="2.5.0" />
18+
</ItemGroup>
19+
20+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Collections.Immutable;
4+
using System.Linq;
5+
using BenchmarkDotNet.Analysers;
6+
using BenchmarkDotNet.Diagnosers;
7+
using BenchmarkDotNet.Engines;
8+
using BenchmarkDotNet.Exporters;
9+
using BenchmarkDotNet.Jobs;
10+
using BenchmarkDotNet.Loggers;
11+
using BenchmarkDotNet.Portability;
12+
using BenchmarkDotNet.Reports;
13+
using BenchmarkDotNet.Running;
14+
using BenchmarkDotNet.Validators;
15+
using RunMode = BenchmarkDotNet.Diagnosers.RunMode;
16+
17+
namespace BenchmarkDotNet.Diagnostics.dotMemory
18+
{
19+
public class DotMemoryDiagnoser : IProfiler
20+
{
21+
private readonly Uri? nugetUrl;
22+
private readonly string? toolsDownloadFolder;
23+
24+
private DotMemoryTool? tool;
25+
26+
public DotMemoryDiagnoser(Uri? nugetUrl = null, string? toolsDownloadFolder = null)
27+
{
28+
this.nugetUrl = nugetUrl;
29+
this.toolsDownloadFolder = toolsDownloadFolder;
30+
}
31+
32+
public IEnumerable<string> Ids => new[] { "DotMemory" };
33+
public string ShortName => "dotMemory";
34+
35+
public RunMode GetRunMode(BenchmarkCase benchmarkCase)
36+
{
37+
return IsSupported(benchmarkCase.Job.Environment.GetRuntime().RuntimeMoniker) ? RunMode.ExtraRun : RunMode.None;
38+
}
39+
40+
private readonly List<string> snapshotFilePaths = new ();
41+
42+
public void Handle(HostSignal signal, DiagnoserActionParameters parameters)
43+
{
44+
var logger = parameters.Config.GetCompositeLogger();
45+
var job = parameters.BenchmarkCase.Job;
46+
47+
var runtimeMoniker = job.Environment.GetRuntime().RuntimeMoniker;
48+
if (!IsSupported(runtimeMoniker))
49+
{
50+
logger.WriteLineError($"Runtime '{runtimeMoniker}' is not supported by dotMemory");
51+
return;
52+
}
53+
54+
switch (signal)
55+
{
56+
case HostSignal.BeforeAnythingElse:
57+
if (tool is not null)
58+
throw new InvalidOperationException("DotMemory tool is already initialized");
59+
tool = new DotMemoryTool(logger, nugetUrl, downloadTo: toolsDownloadFolder);
60+
tool.Init();
61+
break;
62+
case HostSignal.BeforeActualRun:
63+
if (tool is null)
64+
throw new InvalidOperationException("DotMemory tool is not initialized");
65+
snapshotFilePaths.Add(tool.Start(parameters));
66+
break;
67+
case HostSignal.AfterActualRun:
68+
if (tool is null)
69+
throw new InvalidOperationException("DotMemory tool is not initialized");
70+
tool.Stop();
71+
tool = null;
72+
break;
73+
}
74+
}
75+
76+
public IEnumerable<IExporter> Exporters => Enumerable.Empty<IExporter>();
77+
public IEnumerable<IAnalyser> Analysers => Enumerable.Empty<IAnalyser>();
78+
79+
public IEnumerable<ValidationError> Validate(ValidationParameters validationParameters)
80+
{
81+
var runtimeMonikers = validationParameters.Benchmarks.Select(b => b.Job.Environment.GetRuntime().RuntimeMoniker).Distinct();
82+
foreach (var runtimeMoniker in runtimeMonikers)
83+
{
84+
if (!IsSupported(runtimeMoniker))
85+
yield return new ValidationError(true, $"Runtime '{runtimeMoniker}' is not supported by dotMemory");
86+
}
87+
}
88+
89+
internal static bool IsSupported(RuntimeMoniker runtimeMoniker)
90+
{
91+
switch (runtimeMoniker)
92+
{
93+
case RuntimeMoniker.HostProcess:
94+
case RuntimeMoniker.Net461:
95+
case RuntimeMoniker.Net462:
96+
case RuntimeMoniker.Net47:
97+
case RuntimeMoniker.Net471:
98+
case RuntimeMoniker.Net472:
99+
case RuntimeMoniker.Net48:
100+
case RuntimeMoniker.Net481:
101+
case RuntimeMoniker.Net50:
102+
case RuntimeMoniker.Net60:
103+
case RuntimeMoniker.Net70:
104+
case RuntimeMoniker.Net80:
105+
case RuntimeMoniker.Net90:
106+
return true;
107+
case RuntimeMoniker.NotRecognized:
108+
case RuntimeMoniker.Mono:
109+
case RuntimeMoniker.NativeAot60:
110+
case RuntimeMoniker.NativeAot70:
111+
case RuntimeMoniker.NativeAot80:
112+
case RuntimeMoniker.NativeAot90:
113+
case RuntimeMoniker.Wasm:
114+
case RuntimeMoniker.WasmNet50:
115+
case RuntimeMoniker.WasmNet60:
116+
case RuntimeMoniker.WasmNet70:
117+
case RuntimeMoniker.WasmNet80:
118+
case RuntimeMoniker.WasmNet90:
119+
case RuntimeMoniker.MonoAOTLLVM:
120+
case RuntimeMoniker.MonoAOTLLVMNet60:
121+
case RuntimeMoniker.MonoAOTLLVMNet70:
122+
case RuntimeMoniker.MonoAOTLLVMNet80:
123+
case RuntimeMoniker.MonoAOTLLVMNet90:
124+
case RuntimeMoniker.Mono60:
125+
case RuntimeMoniker.Mono70:
126+
case RuntimeMoniker.Mono80:
127+
case RuntimeMoniker.Mono90:
128+
#pragma warning disable CS0618 // Type or member is obsolete
129+
case RuntimeMoniker.NetCoreApp50:
130+
#pragma warning restore CS0618 // Type or member is obsolete
131+
return false;
132+
case RuntimeMoniker.NetCoreApp20:
133+
case RuntimeMoniker.NetCoreApp21:
134+
case RuntimeMoniker.NetCoreApp22:
135+
return RuntimeInformation.IsWindows();
136+
case RuntimeMoniker.NetCoreApp30:
137+
case RuntimeMoniker.NetCoreApp31:
138+
return RuntimeInformation.IsWindows() || RuntimeInformation.IsLinux();
139+
default:
140+
throw new ArgumentOutOfRangeException(nameof(runtimeMoniker), runtimeMoniker, $"Runtime moniker {runtimeMoniker} is not supported");
141+
}
142+
}
143+
144+
public IEnumerable<Metric> ProcessResults(DiagnoserResults results) => ImmutableArray<Metric>.Empty;
145+
146+
public void DisplayResults(ILogger logger)
147+
{
148+
if (snapshotFilePaths.Any())
149+
{
150+
logger.WriteLineInfo("The following dotMemory snapshots were generated:");
151+
foreach (string snapshotFilePath in snapshotFilePaths)
152+
logger.WriteLineInfo($"* {snapshotFilePath}");
153+
}
154+
}
155+
}
156+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using System;
2+
using BenchmarkDotNet.Configs;
3+
4+
namespace BenchmarkDotNet.Diagnostics.dotMemory
5+
{
6+
[AttributeUsage(AttributeTargets.Class)]
7+
public class DotMemoryDiagnoserAttribute : Attribute, IConfigSource
8+
{
9+
public IConfig Config { get; }
10+
11+
public DotMemoryDiagnoserAttribute()
12+
{
13+
Config = ManualConfig.CreateEmpty().AddDiagnoser(new DotMemoryDiagnoser());
14+
}
15+
16+
public DotMemoryDiagnoserAttribute(Uri? nugetUrl = null, string? toolsDownloadFolder = null)
17+
{
18+
Config = ManualConfig.CreateEmpty().AddDiagnoser(new DotMemoryDiagnoser(nugetUrl, toolsDownloadFolder));
19+
}
20+
}
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
using System;
2+
using System.Diagnostics;
3+
using System.IO;
4+
using System.Reflection;
5+
using BenchmarkDotNet.Diagnosers;
6+
using BenchmarkDotNet.Helpers;
7+
using BenchmarkDotNet.Loggers;
8+
using JetBrains.Profiler.SelfApi;
9+
10+
namespace BenchmarkDotNet.Diagnostics.dotMemory
11+
{
12+
internal sealed class DotMemoryTool
13+
{
14+
private readonly ILogger logger;
15+
private readonly Uri? nugetUrl;
16+
private readonly NuGetApi nugetApi;
17+
private readonly string? downloadTo;
18+
19+
public DotMemoryTool(ILogger logger, Uri? nugetUrl = null, NuGetApi nugetApi = NuGetApi.V3, string? downloadTo = null)
20+
{
21+
this.logger = logger;
22+
this.nugetUrl = nugetUrl;
23+
this.nugetApi = nugetApi;
24+
this.downloadTo = downloadTo;
25+
}
26+
27+
public void Init()
28+
{
29+
try
30+
{
31+
logger.WriteLineInfo("Ensuring that dotMemory prerequisite is installed...");
32+
var progress = new Progress(logger, "Installing DotMemory");
33+
DotMemory.EnsurePrerequisiteAsync(progress, nugetUrl, nugetApi, downloadTo).Wait();
34+
logger.WriteLineInfo("dotMemory prerequisite is installed");
35+
logger.WriteLineInfo($"dotMemory runner path: {GetRunnerPath()}");
36+
}
37+
catch (Exception e)
38+
{
39+
logger.WriteLineError(e.ToString());
40+
}
41+
}
42+
43+
public string Start(DiagnoserActionParameters parameters)
44+
{
45+
string snapshotFile = ArtifactFileNameHelper.GetFilePath(parameters, "snapshots", DateTime.Now, "dmw", ".0000".Length);
46+
string? snapshotDirectory = Path.GetDirectoryName(snapshotFile);
47+
logger.WriteLineInfo($"Target snapshot file: {snapshotFile}");
48+
if (!Directory.Exists(snapshotDirectory) && snapshotDirectory != null)
49+
{
50+
try
51+
{
52+
Directory.CreateDirectory(snapshotDirectory);
53+
}
54+
catch (Exception e)
55+
{
56+
logger.WriteLineError($"Failed to create directory: {snapshotDirectory}");
57+
logger.WriteLineError(e.ToString());
58+
}
59+
}
60+
61+
try
62+
{
63+
logger.WriteLineInfo("Attaching dotMemory to the process...");
64+
Attach(parameters, snapshotFile);
65+
logger.WriteLineInfo("dotMemory is successfully attached");
66+
}
67+
catch (Exception e)
68+
{
69+
logger.WriteLineError(e.ToString());
70+
return snapshotFile;
71+
}
72+
73+
return snapshotFile;
74+
}
75+
76+
public void Stop()
77+
{
78+
try
79+
{
80+
logger.WriteLineInfo("Taking dotMemory snapshot...");
81+
Snapshot();
82+
logger.WriteLineInfo("dotMemory snapshot is successfully taken");
83+
}
84+
catch (Exception e)
85+
{
86+
logger.WriteLineError(e.ToString());
87+
}
88+
89+
try
90+
{
91+
logger.WriteLineInfo("Detaching dotMemory from the process...");
92+
Detach();
93+
logger.WriteLineInfo("dotMemory is successfully detached");
94+
}
95+
catch (Exception e)
96+
{
97+
logger.WriteLineError(e.ToString());
98+
}
99+
}
100+
101+
private void Attach(DiagnoserActionParameters parameters, string snapshotFile)
102+
{
103+
var config = new DotMemory.Config();
104+
105+
var pid = parameters.Process.Id;
106+
var currentPid = Process.GetCurrentProcess().Id;
107+
if (pid != currentPid)
108+
config = config.ProfileExternalProcess(pid);
109+
110+
config = config.SaveToFile(snapshotFile);
111+
DotMemory.Attach(config);
112+
}
113+
114+
private void Snapshot() => DotMemory.GetSnapshot();
115+
116+
private void Detach() => DotMemory.Detach();
117+
118+
private string GetRunnerPath()
119+
{
120+
var consoleRunnerPackageField = typeof(DotMemory).GetField("ConsoleRunnerPackage", BindingFlags.NonPublic | BindingFlags.Static);
121+
if (consoleRunnerPackageField == null)
122+
throw new InvalidOperationException("Field 'ConsoleRunnerPackage' not found.");
123+
124+
object? consoleRunnerPackage = consoleRunnerPackageField.GetValue(null);
125+
if (consoleRunnerPackage == null)
126+
throw new InvalidOperationException("Unable to get value of 'ConsoleRunnerPackage'.");
127+
128+
var consoleRunnerPackageType = consoleRunnerPackage.GetType();
129+
var getRunnerPathMethod = consoleRunnerPackageType.GetMethod("GetRunnerPath");
130+
if (getRunnerPathMethod == null)
131+
throw new InvalidOperationException("Method 'GetRunnerPath' not found.");
132+
133+
string? runnerPath = getRunnerPathMethod.Invoke(consoleRunnerPackage, null) as string;
134+
if (runnerPath == null)
135+
throw new InvalidOperationException("Unable to invoke 'GetRunnerPath'.");
136+
137+
return runnerPath;
138+
}
139+
}
140+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using System;
2+
using System.Diagnostics;
3+
using BenchmarkDotNet.Loggers;
4+
5+
namespace BenchmarkDotNet.Diagnostics.dotMemory
6+
{
7+
public class Progress : IProgress<double>
8+
{
9+
private static readonly TimeSpan ReportInterval = TimeSpan.FromSeconds(0.1);
10+
11+
private readonly ILogger logger;
12+
private readonly string title;
13+
14+
public Progress(ILogger logger, string title)
15+
{
16+
this.logger = logger;
17+
this.title = title;
18+
}
19+
20+
private int lastProgress;
21+
private Stopwatch? stopwatch;
22+
23+
public void Report(double value)
24+
{
25+
int progress = (int)Math.Floor(value);
26+
bool needToReport = stopwatch == null ||
27+
(stopwatch != null && stopwatch?.Elapsed > ReportInterval) ||
28+
progress == 100;
29+
30+
if (lastProgress != progress && needToReport)
31+
{
32+
logger.WriteLineInfo($"{title}: {progress}%");
33+
lastProgress = progress;
34+
stopwatch = Stopwatch.StartNew();
35+
}
36+
}
37+
}
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using System;
2+
using System.Runtime.CompilerServices;
3+
using BenchmarkDotNet.Properties;
4+
5+
[assembly: CLSCompliant(true)]
6+
7+
#if RELEASE
8+
[assembly: InternalsVisibleTo("BenchmarkDotNet.Tests,PublicKey=" + BenchmarkDotNetInfo.PublicKey)]
9+
#else
10+
[assembly: InternalsVisibleTo("BenchmarkDotNet.Tests")]
11+
#endif

‎src/BenchmarkDotNet.Diagnostics.dotTrace/ExternalDotTraceTool.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ protected override void Attach(DiagnoserActionParameters parameters, string snap
7070
}
7171

7272
if (!attachWaitingTask.Task.Wait(AttachTimeout))
73-
throw new Exception($"Failed to attach dotTrace to the target process (timeout: {AttachTimeout.TotalSeconds} sec");
73+
throw new Exception($"Failed to attach dotTrace to the target process (timeout: {AttachTimeout.TotalSeconds} sec)");
7474
if (!attachWaitingTask.Task.Result)
7575
throw new Exception($"Failed to attach dotTrace to the target process (ExitCode={process.ExitCode})");
7676
}

‎src/BenchmarkDotNet/Properties/AssemblyInfo.cs

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
[assembly: InternalsVisibleTo("BenchmarkDotNet.IntegrationTests,PublicKey=" + BenchmarkDotNetInfo.PublicKey)]
1515
[assembly: InternalsVisibleTo("BenchmarkDotNet.Diagnostics.Windows,PublicKey=" + BenchmarkDotNetInfo.PublicKey)]
1616
[assembly: InternalsVisibleTo("BenchmarkDotNet.Diagnostics.dotTrace,PublicKey=" + BenchmarkDotNetInfo.PublicKey)]
17+
[assembly: InternalsVisibleTo("BenchmarkDotNet.Diagnostics.dotMemory,PublicKey=" + BenchmarkDotNetInfo.PublicKey)]
1718
[assembly: InternalsVisibleTo("BenchmarkDotNet.IntegrationTests.ManualRunning,PublicKey=" + BenchmarkDotNetInfo.PublicKey)]
1819
[assembly: InternalsVisibleTo("BenchmarkDotNet.IntegrationTests.ManualRunning.MultipleFrameworks,PublicKey=" + BenchmarkDotNetInfo.PublicKey)]
1920
[assembly: InternalsVisibleTo("BenchmarkDotNet.TestAdapter,PublicKey=" + BenchmarkDotNetInfo.PublicKey)]
@@ -22,6 +23,7 @@
2223
[assembly: InternalsVisibleTo("BenchmarkDotNet.IntegrationTests")]
2324
[assembly: InternalsVisibleTo("BenchmarkDotNet.Diagnostics.Windows")]
2425
[assembly: InternalsVisibleTo("BenchmarkDotNet.Diagnostics.dotTrace")]
26+
[assembly: InternalsVisibleTo("BenchmarkDotNet.Diagnostics.dotMemory")]
2527
[assembly: InternalsVisibleTo("BenchmarkDotNet.IntegrationTests.ManualRunning")]
2628
[assembly: InternalsVisibleTo("BenchmarkDotNet.IntegrationTests.ManualRunning.MultipleFrameworks")]
2729
[assembly: InternalsVisibleTo("BenchmarkDotNet.TestAdapter")]

‎tests/BenchmarkDotNet.IntegrationTests/BenchmarkDotNet.IntegrationTests.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
<ProjectReference Include="..\BenchmarkDotNet.Tests\BenchmarkDotNet.Tests.csproj" />
3030
<ProjectReference Include="..\..\src\BenchmarkDotNet\BenchmarkDotNet.csproj" />
3131
<ProjectReference Include="..\..\src\BenchmarkDotNet.Diagnostics.dotTrace\BenchmarkDotNet.Diagnostics.dotTrace.csproj" />
32+
<ProjectReference Include="..\..\src\BenchmarkDotNet.Diagnostics.dotMemory\BenchmarkDotNet.Diagnostics.dotMemory.csproj" />
3233
</ItemGroup>
3334
<ItemGroup>
3435
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using BenchmarkDotNet.Attributes;
6+
using BenchmarkDotNet.Configs;
7+
using BenchmarkDotNet.Diagnostics.dotMemory;
8+
using BenchmarkDotNet.Jobs;
9+
using BenchmarkDotNet.Portability;
10+
using BenchmarkDotNet.Toolchains.InProcess.Emit;
11+
using Xunit;
12+
using Xunit.Abstractions;
13+
14+
namespace BenchmarkDotNet.IntegrationTests
15+
{
16+
public class DotMemoryTests : BenchmarkTestExecutor
17+
{
18+
public DotMemoryTests(ITestOutputHelper output) : base(output) { }
19+
20+
[Fact]
21+
public void DotMemorySmokeTest()
22+
{
23+
if (!RuntimeInformation.IsWindows() && RuntimeInformation.IsMono)
24+
{
25+
Output.WriteLine("Skip Mono on non-Windows");
26+
return;
27+
}
28+
29+
var config = new ManualConfig().AddJob(
30+
Job.Dry.WithId("ExternalProcess"),
31+
Job.Dry.WithToolchain(InProcessEmitToolchain.Instance).WithId("InProcess")
32+
);
33+
string snapshotDirectory = Path.Combine(Directory.GetCurrentDirectory(), "BenchmarkDotNet.Artifacts", "snapshots");
34+
if (Directory.Exists(snapshotDirectory))
35+
Directory.Delete(snapshotDirectory, true);
36+
37+
CanExecute<Benchmarks>(config);
38+
39+
Output.WriteLine("---------------------------------------------");
40+
Output.WriteLine("SnapshotDirectory:" + snapshotDirectory);
41+
var snapshots = Directory.EnumerateFiles(snapshotDirectory)
42+
.Where(filePath => Path.GetExtension(filePath).Equals(".dmw", StringComparison.OrdinalIgnoreCase))
43+
.Select(Path.GetFileName)
44+
.OrderBy(fileName => fileName)
45+
.ToList();
46+
Output.WriteLine("Snapshots:");
47+
foreach (string snapshot in snapshots)
48+
Output.WriteLine("* " + snapshot);
49+
Assert.Equal(4, snapshots.Count);
50+
}
51+
52+
[DotMemoryDiagnoser]
53+
public class Benchmarks
54+
{
55+
[Benchmark]
56+
public int Foo0()
57+
{
58+
var list = new List<object>();
59+
for (int i = 0; i < 1000; i++)
60+
list.Add(new object());
61+
return list.Count;
62+
}
63+
64+
[Benchmark]
65+
public int Foo1()
66+
{
67+
var list = new List<object>();
68+
for (int i = 0; i < 1000; i++)
69+
list.Add(new object());
70+
return list.Count;
71+
}
72+
}
73+
}
74+
}

‎tests/BenchmarkDotNet.Tests/BenchmarkDotNet.Tests.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
</ItemGroup>
3737
<ItemGroup>
3838
<ProjectReference Include="..\..\src\BenchmarkDotNet.Diagnostics.dotTrace\BenchmarkDotNet.Diagnostics.dotTrace.csproj" />
39+
<ProjectReference Include="..\..\src\BenchmarkDotNet.Diagnostics.dotMemory\BenchmarkDotNet.Diagnostics.dotMemory.csproj" />
3940
<ProjectReference Include="..\..\src\BenchmarkDotNet\BenchmarkDotNet.csproj" />
4041
</ItemGroup>
4142
<ItemGroup>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System;
2+
using BenchmarkDotNet.Diagnostics.dotMemory;
3+
using BenchmarkDotNet.Jobs;
4+
using Xunit;
5+
6+
namespace BenchmarkDotNet.Tests.dotMemory
7+
{
8+
public class DotMemoryTests
9+
{
10+
[Fact]
11+
public void AllRuntimeMonikerAreKnown()
12+
{
13+
foreach (RuntimeMoniker moniker in Enum.GetValues(typeof(RuntimeMoniker)))
14+
DotMemoryDiagnoser.IsSupported(moniker); // Just check that it doesn't throw exceptions
15+
}
16+
}
17+
}

0 commit comments

Comments
 (0)
Please sign in to comment.