Skip to content

DotMemoryDiagnoser implementation #2549

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions BenchmarkDotNet.sln
Original file line number Diff line number Diff line change
@@ -53,6 +53,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BenchmarkDotNet.Integration
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BenchmarkDotNet.TestAdapter", "src\BenchmarkDotNet.TestAdapter\BenchmarkDotNet.TestAdapter.csproj", "{4C9C89B8-7C4E-4ECF-B3C9-324C8772EDAC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BenchmarkDotNet.Diagnostics.dotMemory", "src\BenchmarkDotNet.Diagnostics.dotMemory\BenchmarkDotNet.Diagnostics.dotMemory.csproj", "{2E2283A3-6DA6-4482-8518-99D6D9F689AB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -143,6 +145,10 @@ Global
{4C9C89B8-7C4E-4ECF-B3C9-324C8772EDAC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4C9C89B8-7C4E-4ECF-B3C9-324C8772EDAC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4C9C89B8-7C4E-4ECF-B3C9-324C8772EDAC}.Release|Any CPU.Build.0 = Release|Any CPU
{2E2283A3-6DA6-4482-8518-99D6D9F689AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2E2283A3-6DA6-4482-8518-99D6D9F689AB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2E2283A3-6DA6-4482-8518-99D6D9F689AB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2E2283A3-6DA6-4482-8518-99D6D9F689AB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -169,6 +175,7 @@ Global
{C5BDA61F-3A56-4B59-901D-0A17E78F4076} = {D6597E3A-6892-4A68-8E14-042FC941FDA2}
{AACA2C63-A85B-47AB-99FC-72C3FF408B14} = {14195214-591A-45B7-851A-19D3BA2413F9}
{4C9C89B8-7C4E-4ECF-B3C9-324C8772EDAC} = {D6597E3A-6892-4A68-8E14-042FC941FDA2}
{2E2283A3-6DA6-4482-8518-99D6D9F689AB} = {D6597E3A-6892-4A68-8E14-042FC941FDA2}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {4D9AF12B-1F7F-45A7-9E8C-E4E46ADCBD1F}
1 change: 1 addition & 0 deletions docs/articles/guides/nuget.md
Original file line number Diff line number Diff line change
@@ -13,6 +13,7 @@ We have the following set of NuGet packages (you can install it directly from `n
* `BenchmarkDotNet.Annotations`: Basic BenchmarkDotNet annotations for your benchmarks.
* `BenchmarkDotNet.Diagnostics.Windows`: an additional optional package that provides a set of Windows diagnosers.
* `BenchmarkDotNet.Diagnostics.dotTrace`: an additional optional package that provides DotTraceDiagnoser.
* `BenchmarkDotNet.Diagnostics.dotMemory`: an additional optional package that provides DotMemoryDiagnoser.
* `BenchmarkDotNet.Templates`: Templates for BenchmarkDotNet.

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.
22 changes: 22 additions & 0 deletions docs/articles/samples/IntroDotMemoryDiagnoser.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
uid: BenchmarkDotNet.Samples.IntroDotMemoryDiagnoser
---

## Sample: IntroDotMemoryDiagnoser

If you want to get a memory allocation profile of your benchmarks, just add the `[DotMemoryDiagnoser]` attribute, as shown below.
As a result, BenchmarkDotNet performs bonus benchmark runs using attached
[dotMemory Command-Line Profiler](https://www.jetbrains.com/help/dotmemory/Working_with_dotMemory_Command-Line_Profiler.html).
The obtained dotMemory workspaces are saved to the `artifacts` folder.
These dotMemory workspaces can be opened using the [standalone dotMemory](https://www.jetbrains.com/dotmemory/),
or [dotMemory in Rider](https://www.jetbrains.com/help/rider/Memory_profiling_of_.NET_code.html).

### Source code

[!code-csharp[IntroDotMemoryDiagnoser.cs](../../../samples/BenchmarkDotNet.Samples/IntroDotMemoryDiagnoser.cs)]

### Links

* The permanent link to this sample: @BenchmarkDotNet.Samples.IntroDotMemoryDiagnoser

---
2 changes: 2 additions & 0 deletions docs/articles/samples/toc.yml
Original file line number Diff line number Diff line change
@@ -38,6 +38,8 @@
href: IntroDisassemblyRyuJit.md
- name: IntroDotTraceDiagnoser
href: IntroDotTraceDiagnoser.md
- name: IntroDotMemoryDiagnoser
href: IntroDotMemoryDiagnoser.md
- name: IntroEnvVars
href: IntroEnvVars.md
- name: IntroEventPipeProfiler
Original file line number Diff line number Diff line change
@@ -26,6 +26,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\BenchmarkDotNet.Diagnostics.dotTrace\BenchmarkDotNet.Diagnostics.dotTrace.csproj" />
<ProjectReference Include="..\..\src\BenchmarkDotNet.Diagnostics.dotMemory\BenchmarkDotNet.Diagnostics.dotMemory.csproj" />
<ProjectReference Include="..\..\src\BenchmarkDotNet\BenchmarkDotNet.csproj" />
<ProjectReference Include="..\..\src\BenchmarkDotNet.Diagnostics.Windows\BenchmarkDotNet.Diagnostics.Windows.csproj" />
<ProjectReference Include="..\..\src\BenchmarkDotNet.TestAdapter\BenchmarkDotNet.TestAdapter.csproj" />
52 changes: 52 additions & 0 deletions samples/BenchmarkDotNet.Samples/IntroDotMemoryDiagnoser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Diagnostics.dotMemory;
using System.Collections.Generic;

namespace BenchmarkDotNet.Samples
{
// Enables dotMemory 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]
public class IntroDotMemoryDiagnoser
{
[Params(1024)]
public int Size;

private byte[] dataArray;
private IEnumerable<byte> dataEnumerable;

[GlobalSetup]
public void Setup()
{
dataArray = new byte[Size];
dataEnumerable = dataArray;
}

[Benchmark]
public int IterateArray()
{
var count = 0;
foreach (var _ in dataArray)
count++;

return count;
}

[Benchmark]
public int IterateEnumerable()
{
var count = 0;
foreach (var _ in dataEnumerable)
count++;

return count;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\build\common.props" />
<PropertyGroup>
<TargetFrameworks>net6.0;net462;netcoreapp3.1</TargetFrameworks>
<NoWarn>$(NoWarn);1591</NoWarn>
<AssemblyTitle>BenchmarkDotNet.Diagnostics.dotMemory</AssemblyTitle>
<AssemblyName>BenchmarkDotNet.Diagnostics.dotMemory</AssemblyName>
<PackageId>BenchmarkDotNet.Diagnostics.dotMemory</PackageId>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\BenchmarkDotNet\BenchmarkDotNet.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="JetBrains.Profiler.SelfApi" Version="2.5.0" />
</ItemGroup>

</Project>
156 changes: 156 additions & 0 deletions src/BenchmarkDotNet.Diagnostics.dotMemory/DotMemoryDiagnoser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using BenchmarkDotNet.Analysers;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Engines;
using BenchmarkDotNet.Exporters;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Portability;
using BenchmarkDotNet.Reports;
using BenchmarkDotNet.Running;
using BenchmarkDotNet.Validators;
using RunMode = BenchmarkDotNet.Diagnosers.RunMode;

namespace BenchmarkDotNet.Diagnostics.dotMemory
{
public class DotMemoryDiagnoser : IProfiler
{
private readonly Uri? nugetUrl;
private readonly string? toolsDownloadFolder;

private DotMemoryTool? tool;

public DotMemoryDiagnoser(Uri? nugetUrl = null, string? toolsDownloadFolder = null)
{
this.nugetUrl = nugetUrl;
this.toolsDownloadFolder = toolsDownloadFolder;
}

public IEnumerable<string> Ids => new[] { "DotMemory" };
public string ShortName => "dotMemory";

public RunMode GetRunMode(BenchmarkCase benchmarkCase)
{
return IsSupported(benchmarkCase.Job.Environment.GetRuntime().RuntimeMoniker) ? RunMode.ExtraRun : RunMode.None;
}

private readonly List<string> snapshotFilePaths = new ();

public void Handle(HostSignal signal, DiagnoserActionParameters parameters)
{
var logger = parameters.Config.GetCompositeLogger();
var job = parameters.BenchmarkCase.Job;

var runtimeMoniker = job.Environment.GetRuntime().RuntimeMoniker;
if (!IsSupported(runtimeMoniker))
{
logger.WriteLineError($"Runtime '{runtimeMoniker}' is not supported by dotMemory");
return;
}

switch (signal)
{
case HostSignal.BeforeAnythingElse:
if (tool is not null)
throw new InvalidOperationException("DotMemory tool is already initialized");
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;
}
}

public IEnumerable<IExporter> Exporters => Enumerable.Empty<IExporter>();
public IEnumerable<IAnalyser> Analysers => Enumerable.Empty<IAnalyser>();

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");
}
}

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 RuntimeInformation.IsWindows();
case RuntimeMoniker.NetCoreApp30:
case RuntimeMoniker.NetCoreApp31:
return RuntimeInformation.IsWindows() || RuntimeInformation.IsLinux();
default:
throw new ArgumentOutOfRangeException(nameof(runtimeMoniker), runtimeMoniker, $"Runtime moniker {runtimeMoniker} is not supported");
}
}

public IEnumerable<Metric> ProcessResults(DiagnoserResults results) => ImmutableArray<Metric>.Empty;

public void DisplayResults(ILogger logger)
{
if (snapshotFilePaths.Any())
{
logger.WriteLineInfo("The following dotMemory snapshots were generated:");
foreach (string snapshotFilePath in snapshotFilePaths)
logger.WriteLineInfo($"* {snapshotFilePath}");
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using BenchmarkDotNet.Configs;

namespace BenchmarkDotNet.Diagnostics.dotMemory
{
[AttributeUsage(AttributeTargets.Class)]
public class DotMemoryDiagnoserAttribute : Attribute, IConfigSource
{
public IConfig Config { get; }

public DotMemoryDiagnoserAttribute()
{
Config = ManualConfig.CreateEmpty().AddDiagnoser(new DotMemoryDiagnoser());
}

public DotMemoryDiagnoserAttribute(Uri? nugetUrl = null, string? toolsDownloadFolder = null)
{
Config = ManualConfig.CreateEmpty().AddDiagnoser(new DotMemoryDiagnoser(nugetUrl, toolsDownloadFolder));
}
}
}
140 changes: 140 additions & 0 deletions src/BenchmarkDotNet.Diagnostics.dotMemory/DotMemoryTool.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Helpers;
using BenchmarkDotNet.Loggers;
using JetBrains.Profiler.SelfApi;

namespace BenchmarkDotNet.Diagnostics.dotMemory
{
internal sealed class DotMemoryTool
{
private readonly ILogger logger;
private readonly Uri? nugetUrl;
private readonly NuGetApi nugetApi;
private readonly string? downloadTo;

public DotMemoryTool(ILogger logger, Uri? nugetUrl = null, NuGetApi nugetApi = NuGetApi.V3, string? downloadTo = null)
{
this.logger = logger;
this.nugetUrl = nugetUrl;
this.nugetApi = nugetApi;
this.downloadTo = downloadTo;
}

public void Init()
{
try
{
logger.WriteLineInfo("Ensuring that dotMemory prerequisite is installed...");
var progress = new Progress(logger, "Installing DotMemory");
DotMemory.EnsurePrerequisiteAsync(progress, nugetUrl, nugetApi, downloadTo).Wait();
logger.WriteLineInfo("dotMemory prerequisite is installed");
logger.WriteLineInfo($"dotMemory runner path: {GetRunnerPath()}");
}
catch (Exception e)
{
logger.WriteLineError(e.ToString());
}
}

public string Start(DiagnoserActionParameters parameters)
{
string snapshotFile = ArtifactFileNameHelper.GetFilePath(parameters, "snapshots", DateTime.Now, "dmw", ".0000".Length);
string? snapshotDirectory = Path.GetDirectoryName(snapshotFile);
logger.WriteLineInfo($"Target snapshot file: {snapshotFile}");
if (!Directory.Exists(snapshotDirectory) && snapshotDirectory != null)
{
try
{
Directory.CreateDirectory(snapshotDirectory);
}
catch (Exception e)
{
logger.WriteLineError($"Failed to create directory: {snapshotDirectory}");
logger.WriteLineError(e.ToString());
}
}

try
{
logger.WriteLineInfo("Attaching dotMemory to the process...");
Attach(parameters, snapshotFile);
logger.WriteLineInfo("dotMemory is successfully attached");
}
catch (Exception e)
{
logger.WriteLineError(e.ToString());
return snapshotFile;
}

return snapshotFile;
}

public void Stop()
{
try
{
logger.WriteLineInfo("Taking dotMemory snapshot...");
Snapshot();
logger.WriteLineInfo("dotMemory snapshot is successfully taken");
}
catch (Exception e)
{
logger.WriteLineError(e.ToString());
}

try
{
logger.WriteLineInfo("Detaching dotMemory from the process...");
Detach();
logger.WriteLineInfo("dotMemory is successfully detached");
}
catch (Exception e)
{
logger.WriteLineError(e.ToString());
}
}

private void Attach(DiagnoserActionParameters parameters, string snapshotFile)
{
var config = new DotMemory.Config();

var pid = parameters.Process.Id;
var currentPid = Process.GetCurrentProcess().Id;
if (pid != currentPid)
config = config.ProfileExternalProcess(pid);

config = config.SaveToFile(snapshotFile);
DotMemory.Attach(config);
}

private void Snapshot() => DotMemory.GetSnapshot();

private void Detach() => DotMemory.Detach();

private string GetRunnerPath()
{
var consoleRunnerPackageField = typeof(DotMemory).GetField("ConsoleRunnerPackage", BindingFlags.NonPublic | BindingFlags.Static);
if (consoleRunnerPackageField == null)
throw new InvalidOperationException("Field 'ConsoleRunnerPackage' not found.");

object? consoleRunnerPackage = consoleRunnerPackageField.GetValue(null);
if (consoleRunnerPackage == null)
throw new InvalidOperationException("Unable to get value of 'ConsoleRunnerPackage'.");

var consoleRunnerPackageType = consoleRunnerPackage.GetType();
var getRunnerPathMethod = consoleRunnerPackageType.GetMethod("GetRunnerPath");
if (getRunnerPathMethod == null)
throw new InvalidOperationException("Method 'GetRunnerPath' not found.");

string? runnerPath = getRunnerPathMethod.Invoke(consoleRunnerPackage, null) as string;
if (runnerPath == null)
throw new InvalidOperationException("Unable to invoke 'GetRunnerPath'.");

return runnerPath;
}
}
}
38 changes: 38 additions & 0 deletions src/BenchmarkDotNet.Diagnostics.dotMemory/Progress.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System;
using System.Diagnostics;
using BenchmarkDotNet.Loggers;

namespace BenchmarkDotNet.Diagnostics.dotMemory
{
public class Progress : IProgress<double>
{
private static readonly TimeSpan ReportInterval = TimeSpan.FromSeconds(0.1);

private readonly ILogger logger;
private readonly string title;

public Progress(ILogger logger, string title)
{
this.logger = logger;
this.title = title;
}

private int lastProgress;
private Stopwatch? stopwatch;

public void Report(double value)
{
int progress = (int)Math.Floor(value);
bool needToReport = stopwatch == null ||
(stopwatch != null && stopwatch?.Elapsed > ReportInterval) ||
progress == 100;

if (lastProgress != progress && needToReport)
{
logger.WriteLineInfo($"{title}: {progress}%");
lastProgress = progress;
stopwatch = Stopwatch.StartNew();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System;
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Properties;

[assembly: CLSCompliant(true)]

#if RELEASE
[assembly: InternalsVisibleTo("BenchmarkDotNet.Tests,PublicKey=" + BenchmarkDotNetInfo.PublicKey)]
#else
[assembly: InternalsVisibleTo("BenchmarkDotNet.Tests")]
#endif
Original file line number Diff line number Diff line change
@@ -70,7 +70,7 @@ protected override void Attach(DiagnoserActionParameters parameters, string snap
}

if (!attachWaitingTask.Task.Wait(AttachTimeout))
throw new Exception($"Failed to attach dotTrace to the target process (timeout: {AttachTimeout.TotalSeconds} sec");
throw new Exception($"Failed to attach dotTrace to the target process (timeout: {AttachTimeout.TotalSeconds} sec)");
if (!attachWaitingTask.Task.Result)
throw new Exception($"Failed to attach dotTrace to the target process (ExitCode={process.ExitCode})");
}
2 changes: 2 additions & 0 deletions src/BenchmarkDotNet/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -14,6 +14,7 @@
[assembly: InternalsVisibleTo("BenchmarkDotNet.IntegrationTests,PublicKey=" + BenchmarkDotNetInfo.PublicKey)]
[assembly: InternalsVisibleTo("BenchmarkDotNet.Diagnostics.Windows,PublicKey=" + BenchmarkDotNetInfo.PublicKey)]
[assembly: InternalsVisibleTo("BenchmarkDotNet.Diagnostics.dotTrace,PublicKey=" + BenchmarkDotNetInfo.PublicKey)]
[assembly: InternalsVisibleTo("BenchmarkDotNet.Diagnostics.dotMemory,PublicKey=" + BenchmarkDotNetInfo.PublicKey)]
[assembly: InternalsVisibleTo("BenchmarkDotNet.IntegrationTests.ManualRunning,PublicKey=" + BenchmarkDotNetInfo.PublicKey)]
[assembly: InternalsVisibleTo("BenchmarkDotNet.IntegrationTests.ManualRunning.MultipleFrameworks,PublicKey=" + BenchmarkDotNetInfo.PublicKey)]
[assembly: InternalsVisibleTo("BenchmarkDotNet.TestAdapter,PublicKey=" + BenchmarkDotNetInfo.PublicKey)]
@@ -22,6 +23,7 @@
[assembly: InternalsVisibleTo("BenchmarkDotNet.IntegrationTests")]
[assembly: InternalsVisibleTo("BenchmarkDotNet.Diagnostics.Windows")]
[assembly: InternalsVisibleTo("BenchmarkDotNet.Diagnostics.dotTrace")]
[assembly: InternalsVisibleTo("BenchmarkDotNet.Diagnostics.dotMemory")]
[assembly: InternalsVisibleTo("BenchmarkDotNet.IntegrationTests.ManualRunning")]
[assembly: InternalsVisibleTo("BenchmarkDotNet.IntegrationTests.ManualRunning.MultipleFrameworks")]
[assembly: InternalsVisibleTo("BenchmarkDotNet.TestAdapter")]
Original file line number Diff line number Diff line change
@@ -29,6 +29,7 @@
<ProjectReference Include="..\BenchmarkDotNet.Tests\BenchmarkDotNet.Tests.csproj" />
<ProjectReference Include="..\..\src\BenchmarkDotNet\BenchmarkDotNet.csproj" />
<ProjectReference Include="..\..\src\BenchmarkDotNet.Diagnostics.dotTrace\BenchmarkDotNet.Diagnostics.dotTrace.csproj" />
<ProjectReference Include="..\..\src\BenchmarkDotNet.Diagnostics.dotMemory\BenchmarkDotNet.Diagnostics.dotMemory.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
74 changes: 74 additions & 0 deletions tests/BenchmarkDotNet.IntegrationTests/DotMemoryTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnostics.dotMemory;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Portability;
using BenchmarkDotNet.Toolchains.InProcess.Emit;
using Xunit;
using Xunit.Abstractions;

namespace BenchmarkDotNet.IntegrationTests
{
public class DotMemoryTests : BenchmarkTestExecutor
{
public DotMemoryTests(ITestOutputHelper output) : base(output) { }

[Fact]
public void DotMemorySmokeTest()
{
if (!RuntimeInformation.IsWindows() && RuntimeInformation.IsMono)
{
Output.WriteLine("Skip Mono on non-Windows");
return;
}

var config = new ManualConfig().AddJob(
Job.Dry.WithId("ExternalProcess"),
Job.Dry.WithToolchain(InProcessEmitToolchain.Instance).WithId("InProcess")
);
string snapshotDirectory = Path.Combine(Directory.GetCurrentDirectory(), "BenchmarkDotNet.Artifacts", "snapshots");
if (Directory.Exists(snapshotDirectory))
Directory.Delete(snapshotDirectory, true);

CanExecute<Benchmarks>(config);

Output.WriteLine("---------------------------------------------");
Output.WriteLine("SnapshotDirectory:" + snapshotDirectory);
var snapshots = Directory.EnumerateFiles(snapshotDirectory)
.Where(filePath => Path.GetExtension(filePath).Equals(".dmw", StringComparison.OrdinalIgnoreCase))
.Select(Path.GetFileName)
.OrderBy(fileName => fileName)
.ToList();
Output.WriteLine("Snapshots:");
foreach (string snapshot in snapshots)
Output.WriteLine("* " + snapshot);
Assert.Equal(4, snapshots.Count);
}

[DotMemoryDiagnoser]
public class Benchmarks
{
[Benchmark]
public int Foo0()
{
var list = new List<object>();
for (int i = 0; i < 1000; i++)
list.Add(new object());
return list.Count;
}

[Benchmark]
public int Foo1()
{
var list = new List<object>();
for (int i = 0; i < 1000; i++)
list.Add(new object());
return list.Count;
}
}
}
}
1 change: 1 addition & 0 deletions tests/BenchmarkDotNet.Tests/BenchmarkDotNet.Tests.csproj
Original file line number Diff line number Diff line change
@@ -36,6 +36,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\BenchmarkDotNet.Diagnostics.dotTrace\BenchmarkDotNet.Diagnostics.dotTrace.csproj" />
<ProjectReference Include="..\..\src\BenchmarkDotNet.Diagnostics.dotMemory\BenchmarkDotNet.Diagnostics.dotMemory.csproj" />
<ProjectReference Include="..\..\src\BenchmarkDotNet\BenchmarkDotNet.csproj" />
</ItemGroup>
<ItemGroup>
17 changes: 17 additions & 0 deletions tests/BenchmarkDotNet.Tests/dotMemory/DotMemoryTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;
using BenchmarkDotNet.Diagnostics.dotMemory;
using BenchmarkDotNet.Jobs;
using Xunit;

namespace BenchmarkDotNet.Tests.dotMemory
{
public class DotMemoryTests
{
[Fact]
public void AllRuntimeMonikerAreKnown()
{
foreach (RuntimeMoniker moniker in Enum.GetValues(typeof(RuntimeMoniker)))
DotMemoryDiagnoser.IsSupported(moniker); // Just check that it doesn't throw exceptions
}
}
}