DotMemoryDiagnoser implementation (#2549)
* DotMemoryDiagnoser implementation * Typo fix, less alloc in test * attempt to fix reinit logic for tool * Simplify implementation
This commit is contained in:
Родитель
4ab69be430
Коммит
c7b7abf0de
|
@ -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}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
||||
---
|
|
@ -38,6 +38,8 @@
|
|||
href: IntroDisassemblyRyuJit.md
|
||||
- name: IntroDotTraceDiagnoser
|
||||
href: IntroDotTraceDiagnoser.md
|
||||
- name: IntroDotMemoryDiagnoser
|
||||
href: IntroDotMemoryDiagnoser.md
|
||||
- name: IntroEnvVars
|
||||
href: IntroEnvVars.md
|
||||
- name: IntroEventPipeProfiler
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -70,7 +70,7 @@ namespace BenchmarkDotNet.Diagnostics.dotTrace
|
|||
}
|
||||
|
||||
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})");
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ using BenchmarkDotNet.Properties;
|
|||
[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 @@ using BenchmarkDotNet.Properties;
|
|||
[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")]
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче