diff --git a/.github/workflows/generate-changelog.yaml b/.github/workflows/generate-changelog.yaml
index 71592e4d46..a0538e4eac 100644
--- a/.github/workflows/generate-changelog.yaml
+++ b/.github/workflows/generate-changelog.yaml
@@ -19,8 +19,8 @@ jobs:
with:
ref: master
- - name: Download changelog
- run: ./build.cmd docs-update --depth 1 --preview
+ - name: Fetch changelog
+ run: ./build.cmd docs-fetch --depth 1 --preview --force-clone
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -28,8 +28,8 @@ jobs:
uses: JamesIves/github-pages-deploy-action@3.7.1
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- BRANCH: docs-changelog-details
- FOLDER: docs/_changelog/details
+ BRANCH: docs-changelog
+ FOLDER: docs/_changelog
GIT_CONFIG_NAME: Andrey Akinshin
GIT_CONFIG_EMAIL: andrey.akinshin@gmail.com
- CLEAN: true
\ No newline at end of file
+ CLEAN: true
diff --git a/.github/workflows/generate-gh-pages.yaml b/.github/workflows/generate-gh-pages.yaml
index cb01860995..8983ed711f 100644
--- a/.github/workflows/generate-gh-pages.yaml
+++ b/.github/workflows/generate-gh-pages.yaml
@@ -22,8 +22,8 @@ jobs:
- name: Build BenchmarkDotNet
run: ./build.cmd build
- - name: Download changelog
- run: ./build.cmd docs-update --depth 1 --preview
+ - name: Fetch changelog
+ run: ./build.cmd docs-fetch --depth 1 --preview --force-clone
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -31,7 +31,7 @@ jobs:
run: ./build.cmd docs-build
- name: Upload Artifacts
- uses: actions/upload-artifact@v1
+ uses: actions/upload-artifact@v4
with:
name: site
path: docs/_site
@@ -48,9 +48,13 @@ jobs:
ref: docs-stable
- name: Download Artifacts
- uses: actions/download-artifact@v1
+ uses: actions/download-artifact@v4
with:
name: site
+ path: site
+
+ - name: Print file tree
+ run: tree $GITHUB_WORKSPACE
- name: Deploy documentation
uses: JamesIves/github-pages-deploy-action@3.7.1
diff --git a/.github/workflows/publish-nightly.yaml b/.github/workflows/publish-nightly.yaml
index 3a3dc0bc9b..01f439f41e 100644
--- a/.github/workflows/publish-nightly.yaml
+++ b/.github/workflows/publish-nightly.yaml
@@ -5,19 +5,20 @@ on:
push:
branches:
- master
+ workflow_dispatch:
jobs:
publish:
runs-on: ubuntu-latest
if: ${{ github.repository == 'dotnet/BenchmarkDotNet' }}
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Set date
run: echo "DATE=$(date +'%Y%m%d')" >> $GITHUB_ENV
- name: Pack
run: ./build.cmd pack /p:VersionSuffix=nightly.$DATE.$GITHUB_RUN_NUMBER
- name: Upload nupkg to artifacts
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: nupkgs
path: "**/*.*nupkg"
diff --git a/.github/workflows/report-test-results.yaml b/.github/workflows/report-test-results.yaml
index 005465b329..f1c627295e 100644
--- a/.github/workflows/report-test-results.yaml
+++ b/.github/workflows/report-test-results.yaml
@@ -11,13 +11,26 @@ jobs:
report:
runs-on: ubuntu-latest
permissions: write-all
+ if: ${{ github.event.workflow_run.conclusion != 'cancelled' }}
steps:
+ # Cleanup Old Files
+ - name: Cleanup Old Files
+ run: rm -rf $GITHUB_WORKSPACE/*.trx
+
+ # Download the Latest Artifacts with Unique Name
- name: Download Artifacts
- uses: dawidd6/action-download-artifact@v2
+ uses: dawidd6/action-download-artifact@v6
with:
- workflow: ${{ github.event.workflow_run.workflow_id }}
+ run_id: ${{ github.event.workflow_run.id }}
+
+ # Display the Structure of Downloaded Files
- name: Display structure of downloaded files
run: ls -R
+
+ # Display the Contents of .trx Files
+ - name: Display .trx file contents
+ run: cat **/*.trx || echo "No .trx files found"
+
- name: Report tests results
uses: AndreyAkinshin/test-reporter@0e2c48ebec2007001dd77dd4bcbcd450b96d5a38
with:
diff --git a/.github/workflows/run-tests-selected.yaml b/.github/workflows/run-tests-selected.yaml
new file mode 100644
index 0000000000..ddbcc74e09
--- /dev/null
+++ b/.github/workflows/run-tests-selected.yaml
@@ -0,0 +1,89 @@
+name: run-tests-selected
+run-name: Run selected tests (${{ inputs.runs_on }} --framework ${{ inputs.framework}} --filter ${{ inputs.filter }})
+
+on:
+ workflow_dispatch:
+ inputs:
+ runs_on:
+ type: choice
+ description: GitHub Actions runner image name
+ required: true
+ default: ubuntu-latest
+ options:
+ - windows-latest
+ - ubuntu-latest
+ - macos-latest
+ - windows-11-arm
+ - ubuntu-24.04-arm
+ - macos-13
+ project:
+ type: string
+ description: Specify test project path
+ required: true
+ default: tests/BenchmarkDotNet.IntegrationTests
+ options:
+ - tests/BenchmarkDotNet.Tests
+ - tests/BenchmarkDotNet.IntegrationTests
+ - tests/BenchmarkDotNet.IntegrationTests.ManualRunning
+ framework:
+ type: choice
+ description: Specify target framework
+ required: true
+ options:
+ - net8.0
+ - net462
+ filter:
+ type: string
+ description: Test filter text (It's used for `dotnet test --filter`) Use default value when running all tests
+ required: true
+ default: "BenchmarkDotNet"
+ iteration_count:
+ type: number
+ description: Count of test loop (It's expected to be used for flaky tests)
+ required: true
+ default: 1
+
+jobs:
+ test:
+ name: test (${{ inputs.runs_on }} --framework ${{ inputs.framework}} --filter ${{ inputs.filter }})
+ runs-on: ${{ inputs.runs_on }}
+ timeout-minutes: 60 # Explicitly set timeout. When wrong input parameter is passed. It may continue to run until it times out (Default:360 minutes))
+ steps:
+ - uses: actions/checkout@v4
+
+ # Setup
+ - name: Setup
+ run: |
+ mkdir artifacts
+
+ # Build
+ - name: Run build
+ working-directory: ${{ github.event.inputs.project }}
+ run: |
+ dotnet build -c Release --framework ${{ inputs.framework }} -tl:off
+
+ # Test
+ - name: Run tests
+ shell: pwsh
+ working-directory: ${{ github.event.inputs.project }}
+ run: |
+ $PSNativeCommandUseErrorActionPreference = $true
+ $iterationCount = ${{ inputs.iteration_count }}
+
+ foreach($i in 1..$iterationCount) {
+ Write-Output ('##[group]Executing Iteration: {0}/${{ inputs.iteration_count }}' -f $i)
+
+ dotnet test -c Release --framework ${{ inputs.framework }} --filter ${{ inputs.filter }} -tl:off --no-build --logger "console;verbosity=normal"
+
+ Write-Output '##[endgroup]'
+ }
+
+ # Upload artifact files that are located at `$(GITHUB_WORKSPACE)/artifacts` directory
+ - name: Upload test results
+ uses: actions/upload-artifact@v4
+ if: always()
+ with:
+ name: results
+ if-no-files-found: ignore
+ path: |
+ artifacts/**/*
diff --git a/.github/workflows/run-tests.yaml b/.github/workflows/run-tests.yaml
index 698b429514..9d3f25a9e8 100644
--- a/.github/workflows/run-tests.yaml
+++ b/.github/workflows/run-tests.yaml
@@ -18,17 +18,19 @@ jobs:
run: Set-MpPreference -DisableRealtimeMonitoring $true
shell: powershell
- uses: actions/checkout@v4
+ # Build and Test
- name: Run task 'build'
shell: cmd
run: ./build.cmd build
- name: Run task 'in-tests-core'
shell: cmd
run: ./build.cmd in-tests-core -e
+ # Upload Artifacts with Unique Name
- name: Upload test results
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
if: always()
with:
- name: test-windows-core-trx
+ name: test-windows-core-trx-${{ github.run_id }}
path: "**/*.trx"
test-windows-full:
@@ -38,25 +40,28 @@ jobs:
run: Set-MpPreference -DisableRealtimeMonitoring $true
shell: powershell
- uses: actions/checkout@v4
+ # Build and Test
- name: Run task 'build'
shell: cmd
run: ./build.cmd build
- name: Run task 'in-tests-full'
shell: cmd
run: ./build.cmd in-tests-full -e
+ # Upload Artifacts with Unique Name
- name: Upload test results
uses: actions/upload-artifact@v4
if: always()
with:
- name: test-windows-full-trx
+ name: test-windows-full-trx-${{ github.run_id }}
path: "**/*.trx"
test-linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
+ # Set up the environment
- name: Set up Clang
- uses: egor-tensin/setup-clang@v1
+ uses: egor-tensin/setup-clang@ef434b41eb33a70396fb336b1bae39c76d740c3d # v1.4
with:
version: latest
platform: x64
@@ -65,47 +70,61 @@ jobs:
- name: Set up node
uses: actions/setup-node@v4
with:
- node-version: "20"
+ node-version: "22"
- name: Set up v8
run: npm install jsvu -g && jsvu --os=linux64 --engines=v8 && echo "$HOME/.jsvu/bin" >> $GITHUB_PATH
- name: Install wasm-tools workload
run: ./build.cmd install-wasm-tools
+ # Build and Test
- name: Run task 'build'
run: ./build.cmd build
- name: Run task 'unit-tests'
run: ./build.cmd unit-tests -e
- name: Run task 'in-tests-core'
run: ./build.cmd in-tests-core -e
+ # Upload Artifacts with Unique Name
- name: Upload test results
uses: actions/upload-artifact@v4
if: always()
with:
- name: test-linux-trx
+ name: test-linux-trx-${{ github.run_id }}
path: "**/*.trx"
test-macos:
- runs-on: macos-13
+ name: test-macos (${{ matrix.os.arch }})
+ runs-on: ${{ matrix.os.runs-on }}
+ strategy:
+ matrix:
+ os:
+ - runs-on: 'macos-latest'
+ jsvu-os: 'mac64arm'
+ arch: 'arm64'
+ - runs-on: 'macos-13'
+ jsvu-os: 'mac64'
+ arch: 'x64'
steps:
- uses: actions/checkout@v4
- name: Set up node
uses: actions/setup-node@v4
with:
- node-version: "20"
+ node-version: "22"
- name: Set up v8
- run: npm install jsvu -g && jsvu --os=mac64 --engines=v8 && echo "$HOME/.jsvu/bin" >> $GITHUB_PATH
+ run: npm install jsvu -g && jsvu --os=${{ matrix.os.jsvu-os }} --engines=v8 && echo "$HOME/.jsvu/bin" >> $GITHUB_PATH
- name: Install wasm-tools workload
run: ./build.cmd install-wasm-tools
+ # Build and Test
- name: Run task 'build'
run: ./build.cmd build
- name: Run task 'unit-tests'
run: ./build.cmd unit-tests -e
- name: Run task 'in-tests-core'
run: ./build.cmd in-tests-core -e
+ # Upload Artifacts with Unique Name
- name: Upload test results
uses: actions/upload-artifact@v4
if: always()
with:
- name: test-macos-trx
+ name: test-macos(${{ matrix.os.arch }})-trx-${{ github.run_id }}
path: "**/*.trx"
test-pack:
@@ -129,9 +148,9 @@ jobs:
- uses: actions/setup-node@v4
name: Setup node
with:
- node-version: "18"
+ node-version: "22"
- name: Install cSpell
- run: npm install -g cspell@8.0.0
+ run: npm install -g cspell@9.0.2
- name: Copy cSpell config
run: cp ./build/cSpell.json ./cSpell.json
- name: Run cSpell
diff --git a/BenchmarkDotNet.sln.DotSettings b/BenchmarkDotNet.sln.DotSettings
index a2d44733f3..53f5cf0a60 100644
--- a/BenchmarkDotNet.sln.DotSettings
+++ b/BenchmarkDotNet.sln.DotSettings
@@ -166,6 +166,7 @@
True
True
True
+ True
True
True
True
@@ -177,6 +178,7 @@
True
True
True
+ True
True
True
True
diff --git a/LICENSE.md b/LICENSE.md
index 039248a340..6272f97306 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -1,6 +1,6 @@
### The MIT License
-Copyright (c) 2013–2024 .NET Foundation and contributors
+Copyright (c) 2013–2025 .NET Foundation and contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/NuGet.Config b/NuGet.Config
index 855812b5a5..7507704b8b 100644
--- a/NuGet.Config
+++ b/NuGet.Config
@@ -13,5 +13,6 @@
+
diff --git a/README.md b/README.md
index 3022e0ebb3..1995deff62 100644
--- a/README.md
+++ b/README.md
@@ -30,7 +30,7 @@ It's no harder than writing unit tests!
Under the hood, it performs a lot of [magic](#automation) that guarantees [reliable and precise](#reliability) results thanks to the [perfolizer](https://github.com/AndreyAkinshin/perfolizer) statistical engine.
BenchmarkDotNet protects you from popular benchmarking mistakes and warns you if something is wrong with your benchmark design or obtained measurements.
The results are presented in a [user-friendly](#friendliness) form that highlights all the important facts about your experiment.
-BenchmarkDotNet is already adopted by [22000+ GitHub projects](https://github.com/dotnet/BenchmarkDotNet/network/dependents) including
+BenchmarkDotNet is already adopted by [26400+ GitHub projects](https://github.com/dotnet/BenchmarkDotNet/network/dependents) including
[.NET Runtime](https://github.com/dotnet/runtime),
[.NET Compiler](https://github.com/dotnet/roslyn),
[.NET Performance](https://github.com/dotnet/performance),
@@ -135,8 +135,8 @@ If you want to compare benchmarks with each other,
mark one of the benchmarks as the [baseline](https://benchmarkdotnet.org/articles/features/baselines.html)
via `[Benchmark(Baseline = true)]`: BenchmarkDotNet will compare it with all of the other benchmarks.
If you want to compare performance in different environments, use [jobs](https://benchmarkdotnet.org/articles/configs/jobs.html).
-For example, you can run all the benchmarks on .NET Core 3.0 and Mono via
- `[SimpleJob(RuntimeMoniker.NetCoreApp30)]` and `[SimpleJob(RuntimeMoniker.Mono)]`.
+For example, you can run all the benchmarks on .NET 8.0 and Mono via
+ `[SimpleJob(RuntimeMoniker.Net80)]` and `[SimpleJob(RuntimeMoniker.Mono)]`.
If you don't like attributes, you can call most of the APIs via the fluent style and write code like this:
@@ -268,13 +268,3 @@ Let's build the best tool for benchmarking together!
This project has adopted the code of conduct defined by the [Contributor Covenant](https://www.contributor-covenant.org/)
to clarify expected behavior in our community.
For more information, see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct).
-
-## Sponsors
-
-BenchmarkDotNet is supported by the [AWS Open Source Software Fund](https://github.com/aws/dotnet-foss).
-
-
-
- [](https://github.com/aws/dotnet-foss)
-
-
diff --git a/build/BenchmarkDotNet.Build/BenchmarkDotNet.Build.csproj b/build/BenchmarkDotNet.Build/BenchmarkDotNet.Build.csproj
index 6a41ef6e51..c7b97bc90a 100644
--- a/build/BenchmarkDotNet.Build/BenchmarkDotNet.Build.csproj
+++ b/build/BenchmarkDotNet.Build/BenchmarkDotNet.Build.csproj
@@ -6,10 +6,10 @@
enable
-
-
-
-
-
+
+
+
+
+
diff --git a/build/BenchmarkDotNet.Build/BuildContext.cs b/build/BenchmarkDotNet.Build/BuildContext.cs
index 0a165721e0..3e3c42c27d 100644
--- a/build/BenchmarkDotNet.Build/BuildContext.cs
+++ b/build/BenchmarkDotNet.Build/BuildContext.cs
@@ -157,6 +157,7 @@ public void GenerateFile(FilePath filePath, StringBuilder content)
public void GenerateFile(FilePath filePath, string content, bool reportNoChanges = false)
{
+ this.EnsureDirectoryExists(filePath.GetDirectory());
var relativePath = RootDirectory.GetRelativePath(filePath);
if (this.FileExists(filePath))
{
diff --git a/build/BenchmarkDotNet.Build/Meta/Repo.cs b/build/BenchmarkDotNet.Build/Meta/Repo.cs
index 38215fcce8..22a02d51bb 100644
--- a/build/BenchmarkDotNet.Build/Meta/Repo.cs
+++ b/build/BenchmarkDotNet.Build/Meta/Repo.cs
@@ -12,7 +12,7 @@ public static class Repo
public const string HttpsUrlBase = $"https://github.com/{Owner}/{Name}";
public const string HttpsGitUrl = $"{HttpsUrlBase}.git";
- public const string ChangelogDetailsBranch = "docs-changelog-details";
+ public const string ChangelogBranch = "docs-changelog";
public const string DocsStableBranch = "docs-stable";
public const string MasterBranch = "master";
diff --git a/build/BenchmarkDotNet.Build/Options/KnownOptions.cs b/build/BenchmarkDotNet.Build/Options/KnownOptions.cs
index c5101e1d52..6da4e390ed 100644
--- a/build/BenchmarkDotNet.Build/Options/KnownOptions.cs
+++ b/build/BenchmarkDotNet.Build/Options/KnownOptions.cs
@@ -29,6 +29,12 @@ public static class KnownOptions
Aliases = new[] { "-d" }
};
+ public static readonly BoolOption ForceClone = new("--force-clone")
+ {
+ Description = "Forces re-cloning of the changelog repository, deleting any existing directory.",
+ Aliases = new[] { "-f" }
+ };
+
public static readonly BoolOption Help = new("--help")
{
Description = "Prints help information",
diff --git a/build/BenchmarkDotNet.Build/Program.cs b/build/BenchmarkDotNet.Build/Program.cs
index 51dd875fbd..5fd25cc298 100644
--- a/build/BenchmarkDotNet.Build/Program.cs
+++ b/build/BenchmarkDotNet.Build/Program.cs
@@ -28,10 +28,10 @@ public HelpInfo GetHelp()
{
return new HelpInfo
{
- Examples = new[]
- {
+ Examples =
+ [
new Example(Name)
- }
+ ]
};
}
}
@@ -48,10 +48,10 @@ public HelpInfo GetHelp()
{
return new HelpInfo
{
- Examples = new[]
- {
+ Examples =
+ [
new Example(Name).WithMsBuildArgument("Configuration", "Debug")
- }
+ ]
};
}
}
@@ -68,10 +68,10 @@ public HelpInfo GetHelp()
{
return new HelpInfo
{
- Examples = new[]
- {
+ Examples =
+ [
new Example(Name)
- }
+ ]
};
}
}
@@ -88,12 +88,12 @@ public HelpInfo GetHelp()
{
return new HelpInfo
{
- Examples = new[]
- {
+ Examples =
+ [
new Example(Name)
.WithArgument(KnownOptions.Exclusive)
.WithArgument(KnownOptions.Verbosity, "Diagnostic")
- }
+ ]
};
}
}
@@ -113,7 +113,7 @@ public class InTestsFullTask : FrostingTask, IHelpProvider
}
[TaskName(Name)]
-[TaskDescription("Run integration tests using .NET 7 (slow)")]
+[TaskDescription("Run integration tests using .NET 8 (slow)")]
[IsDependentOn(typeof(BuildTask))]
public class InTestsCoreTask : FrostingTask, IHelpProvider
{
@@ -145,68 +145,69 @@ public HelpInfo GetHelp()
{
return new HelpInfo
{
- Examples = new[]
- {
+ Examples =
+ [
new Example(Name)
.WithMsBuildArgument("VersionPrefix", "0.1.1729")
.WithMsBuildArgument("VersionSuffix", "preview"),
new Example(Name).WithArgument(KnownOptions.Stable)
- }
+ ]
};
}
}
[TaskName(Name)]
-[TaskDescription("Update generated documentation files")]
-public class DocsUpdateTask : FrostingTask, IHelpProvider
+[TaskDescription("Fetch changelog files")]
+public class DocsFetchTask : FrostingTask, IHelpProvider
{
- private const string Name = "docs-update";
- public override void Run(BuildContext context) => context.DocumentationRunner.Update();
+ private const string Name = "docs-fetch";
+ public override void Run(BuildContext context) => context.DocumentationRunner.Fetch();
public HelpInfo GetHelp()
{
return new HelpInfo
{
Description = $"This task updates the following files:\n" +
- $"* README.md (the number of dependent projects number)\n" +
+ $"* Clones branch 'docs-changelog' to docs/_changelog\n" +
$"* Last changelog footer (if {KnownOptions.Stable.CommandLineName} is specified)\n" +
$"* All changelog details in docs/_changelog\n" +
- $" (This dir is a cloned version of this repo from branch {Repo.ChangelogDetailsBranch})",
- Options = new IOption[] { KnownOptions.DocsPreview, KnownOptions.DocsDepth },
- EnvironmentVariables = new[] { EnvVar.GitHubToken },
- Examples = new[]
- {
+ $" (This dir is a cloned version of this repo from branch {Repo.ChangelogBranch})",
+ Options = [KnownOptions.DocsPreview, KnownOptions.DocsDepth, KnownOptions.ForceClone],
+ EnvironmentVariables = [EnvVar.GitHubToken],
+ Examples =
+ [
new Example(Name)
.WithArgument(KnownOptions.DocsDepth, "3")
.WithArgument(KnownOptions.DocsPreview)
- }
+ .WithArgument(KnownOptions.ForceClone)
+ ]
};
}
}
[TaskName(Name)]
-[TaskDescription("Prepare auxiliary documentation files")]
-public class DocsPrepareTask : FrostingTask, IHelpProvider
+[TaskDescription("Generate auxiliary documentation files")]
+public class DocsGenerateTask : FrostingTask, IHelpProvider
{
- private const string Name = "docs-prepare";
- public override void Run(BuildContext context) => context.DocumentationRunner.Prepare();
+ private const string Name = "docs-generate";
+ public override void Run(BuildContext context) => context.DocumentationRunner.Generate();
public HelpInfo GetHelp()
{
return new HelpInfo
{
- Options = new IOption[] { KnownOptions.DocsPreview },
- Examples = new[]
- {
+ Options = [KnownOptions.DocsPreview],
+ Examples =
+ [
new Example(Name).WithArgument(KnownOptions.DocsPreview)
- }
+ ]
};
}
}
[TaskName(Name)]
[TaskDescription("Build final documentation")]
-[IsDependentOn(typeof(DocsPrepareTask))]
+[IsDependentOn(typeof(DocsGenerateTask))]
public class DocsBuildTask : FrostingTask, IHelpProvider
{
private const string Name = "docs-build";
@@ -215,11 +216,11 @@ public class DocsBuildTask : FrostingTask, IHelpProvider
public HelpInfo GetHelp() => new()
{
Description = "The 'build' task should be run manually to build api docs",
- Options = new IOption[] { KnownOptions.DocsPreview },
- Examples = new[]
- {
+ Options = [KnownOptions.DocsPreview],
+ Examples =
+ [
new Example(Name).WithArgument(KnownOptions.DocsPreview)
- }
+ ]
};
}
@@ -227,7 +228,8 @@ public class DocsBuildTask : FrostingTask, IHelpProvider
[TaskDescription("Release new version")]
[IsDependentOn(typeof(BuildTask))]
[IsDependentOn(typeof(PackTask))]
-[IsDependentOn(typeof(DocsUpdateTask))]
+[IsDependentOn(typeof(DocsFetchTask))]
+[IsDependentOn(typeof(DocsGenerateTask))]
[IsDependentOn(typeof(DocsBuildTask))]
public class ReleaseTask : FrostingTask, IHelpProvider
{
@@ -236,10 +238,10 @@ public class ReleaseTask : FrostingTask, IHelpProvider
public HelpInfo GetHelp() => new()
{
- Options = new IOption[] { KnownOptions.NextVersion, KnownOptions.Push },
- EnvironmentVariables = new[] { EnvVar.GitHubToken, EnvVar.NuGetToken },
- Examples = new[]
- {
+ Options = [KnownOptions.NextVersion, KnownOptions.Push],
+ EnvironmentVariables = [EnvVar.GitHubToken, EnvVar.NuGetToken],
+ Examples =
+ [
new Example(Name)
.WithArgument(KnownOptions.Stable)
.WithArgument(KnownOptions.NextVersion, "0.1.1729")
@@ -247,6 +249,6 @@ public class ReleaseTask : FrostingTask, IHelpProvider
new Example(Name)
.WithArgument(KnownOptions.Stable)
.WithArgument(KnownOptions.Push)
- }
+ ]
};
}
\ No newline at end of file
diff --git a/build/BenchmarkDotNet.Build/Runners/Changelog/ChangelogBuilder.cs b/build/BenchmarkDotNet.Build/Runners/Changelog/ChangelogBuilder.cs
new file mode 100644
index 0000000000..5bfc8c91b2
--- /dev/null
+++ b/build/BenchmarkDotNet.Build/Runners/Changelog/ChangelogBuilder.cs
@@ -0,0 +1,256 @@
+using System;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Text;
+using BenchmarkDotNet.Build.Meta;
+using BenchmarkDotNet.Build.Options;
+using Cake.Common.Diagnostics;
+using Cake.Common.IO;
+using Cake.Core.IO;
+using Cake.FileHelpers;
+
+namespace BenchmarkDotNet.Build.Runners.Changelog;
+
+public class ChangelogBuilder
+{
+ private readonly BuildContext context;
+ private readonly bool preview;
+ private readonly string depth;
+ private readonly bool forceClone;
+
+ ///
+ /// Directory with original changelog part files from branch 'docs-changelog'
+ ///
+ public DirectoryPath SrcDirectory { get; }
+
+ ///
+ /// Final changelog files to be used by docfx
+ ///
+ public DirectoryPath DocfxDirectory { get; }
+
+ public ChangelogBuilder(BuildContext context)
+ {
+ this.context = context;
+ preview = KnownOptions.DocsPreview.Resolve(context);
+ depth = KnownOptions.DocsDepth.Resolve(context);
+ forceClone = KnownOptions.ForceClone.Resolve(context);
+
+ var docsDirectory = context.RootDirectory.Combine("docs");
+ SrcDirectory = docsDirectory.Combine("_changelog");
+ DocfxDirectory = docsDirectory.Combine("changelog");
+ }
+
+ public void Fetch()
+ {
+ EnvVar.GitHubToken.AssertHasValue();
+
+ EnsureSrcDirectoryExist(forceClone);
+
+ var history = context.VersionHistory;
+ var stableVersionCount = history.StableVersions.Length;
+
+ if (depth.Equals("all", StringComparison.OrdinalIgnoreCase))
+ {
+ FetchDetails(
+ history.StableVersions.First(),
+ history.FirstCommit);
+
+ for (var i = 1; i < stableVersionCount; i++)
+ FetchDetails(
+ history.StableVersions[i],
+ history.StableVersions[i - 1]);
+ }
+ else if (depth != "")
+ {
+ if (!int.TryParse(depth, CultureInfo.InvariantCulture, out var depthValue))
+ throw new InvalidDataException($"Failed to parse the depth value: '{depth}'");
+
+ for (var i = Math.Max(stableVersionCount - depthValue, 1); i < stableVersionCount; i++)
+ FetchDetails(
+ history.StableVersions[i],
+ history.StableVersions[i - 1]);
+ }
+
+ if (preview)
+ FetchDetails(
+ history.CurrentVersion,
+ history.StableVersions.Last(),
+ "HEAD");
+ }
+
+ private void FetchDetails(string version, string versionPrevious, string lastCommit = "")
+ {
+ EnsureSrcDirectoryExist();
+ context.Information($"Downloading changelog details for v{version}");
+ var detailsDirectory = SrcDirectory.Combine("details");
+ ChangelogDetailsBuilder.Run(context, detailsDirectory, version, versionPrevious, lastCommit);
+ }
+
+ public void Generate()
+ {
+ GenerateLastFooter();
+
+ foreach (var version in context.VersionHistory.StableVersions)
+ GenerateVersion(version);
+ if (preview)
+ GenerateVersion(context.VersionHistory.CurrentVersion);
+
+ GenerateIndex();
+ GenerateFull();
+ GenerateToc();
+ }
+
+ public void GenerateLastFooter()
+ {
+ var version = context.VersionHistory.CurrentVersion;
+ var previousVersion = context.VersionHistory.StableVersions.Last();
+ var date = KnownOptions.Stable.Resolve(context)
+ ? DateTime.Now.ToString("MMMM dd, yyyy", CultureInfo.InvariantCulture)
+ : "TBA";
+
+ var content = new StringBuilder();
+ content.AppendLine($"_Date: {date}_");
+ content.AppendLine("");
+ content.AppendLine(
+ $"_Milestone: [v{version}](https://github.com/dotnet/BenchmarkDotNet/issues?q=milestone%3Av{version})_");
+ content.AppendLine(
+ $"([List of commits](https://github.com/dotnet/BenchmarkDotNet/compare/v{previousVersion}...v{version}))");
+ content.AppendLine("");
+ content.AppendLine("_NuGet Packages:_");
+ foreach (var packageName in context.NuGetPackageNames)
+ content.AppendLine($"* https://www.nuget.org/packages/{packageName}/{version}");
+
+ var fileName = "v" + context.VersionHistory.CurrentVersion + ".md";
+ var filePath = SrcDirectory.Combine("footer").CombineWithFilePath(fileName);
+ context.GenerateFile(filePath, content);
+ }
+
+ private void GenerateVersion(string version)
+ {
+ EnsureSrcDirectoryExist();
+ var md = $"v{version}.md";
+ var header = SrcDirectory.Combine("header").CombineWithFilePath(md);
+ var footer = SrcDirectory.Combine("footer").CombineWithFilePath(md);
+ var details = SrcDirectory.Combine("details").CombineWithFilePath(md);
+ var release = DocfxDirectory.CombineWithFilePath(md);
+
+ var content = new StringBuilder();
+ content.AppendLine("---");
+ content.AppendLine("uid: changelog.v" + version);
+ content.AppendLine("---");
+ content.AppendLine("");
+ content.AppendLine("# BenchmarkDotNet v" + version);
+ content.AppendLine("");
+ content.AppendLine("");
+
+ if (context.FileExists(header))
+ {
+ content.AppendLine(context.FileReadText(header));
+ content.AppendLine("");
+ content.AppendLine("");
+ }
+
+ if (context.FileExists(details))
+ {
+ content.AppendLine(context.FileReadText(details));
+ content.AppendLine("");
+ content.AppendLine("");
+ }
+
+ if (context.FileExists(footer))
+ {
+ content.AppendLine("## Additional details");
+ content.AppendLine("");
+ content.AppendLine(context.FileReadText(footer));
+ }
+
+ context.GenerateFile(release, content.ToString());
+ }
+
+ private void GenerateIndex()
+ {
+ var content = new StringBuilder();
+ content.AppendLine("---");
+ content.AppendLine("uid: changelog");
+ content.AppendLine("---");
+ content.AppendLine("");
+ content.AppendLine("# ChangeLog");
+ content.AppendLine("");
+ if (preview)
+ content.AppendLine($"* @changelog.v{context.VersionHistory.CurrentVersion}");
+ foreach (var version in context.VersionHistory.StableVersions.Reverse())
+ content.AppendLine($"* @changelog.v{version}");
+ content.AppendLine("* @changelog.full");
+
+ context.GenerateFile(DocfxDirectory.CombineWithFilePath("index.md"), content);
+ }
+
+ private void GenerateFull()
+ {
+ var content = new StringBuilder();
+ content.AppendLine("---");
+ content.AppendLine("uid: changelog.full");
+ content.AppendLine("---");
+ content.AppendLine("");
+ content.AppendLine("# Full ChangeLog");
+ content.AppendLine("");
+ if (preview)
+ content.AppendLine(
+ $"[!include[v{context.VersionHistory.CurrentVersion}](v{context.VersionHistory.CurrentVersion}.md)]");
+ foreach (var version in context.VersionHistory.StableVersions.Reverse())
+ content.AppendLine($"[!include[v{version}](v{version}.md)]");
+
+ context.GenerateFile(DocfxDirectory.CombineWithFilePath("full.md"), content);
+ }
+
+ private void GenerateToc()
+ {
+ var content = new StringBuilder();
+
+ if (preview)
+ {
+ content.AppendLine($"- name: v{context.VersionHistory.CurrentVersion}");
+ content.AppendLine($" href: v{context.VersionHistory.CurrentVersion}.md");
+ }
+
+ foreach (var version in context.VersionHistory.StableVersions.Reverse())
+ {
+ content.AppendLine($"- name: v{version}");
+ content.AppendLine($" href: v{version}.md");
+ }
+
+ content.AppendLine("- name: Full ChangeLog");
+ content.AppendLine(" href: full.md");
+
+ context.GenerateFile(DocfxDirectory.CombineWithFilePath("toc.yml"), content);
+ }
+
+ private void EnsureSrcDirectoryExist(bool forceClone = false)
+ {
+ void Log(string message) => context.Information($"[Changelog] {message}");
+
+ Log($"Preparing git sub-repository for changelog branch '{Repo.ChangelogBranch}'. " +
+ $"Target directory: '{SrcDirectory}'.");
+ if (context.DirectoryExists(SrcDirectory) && forceClone)
+ {
+ Log($"Directory '{SrcDirectory}' already exists and forceClean is specified. " +
+ $"Deleting the current directory...");
+ context.DeleteDirectory(
+ SrcDirectory,
+ new DeleteDirectorySettings { Force = true, Recursive = true });
+ Log($"Directory '{SrcDirectory}' deleted successfully.");
+ }
+
+ if (!context.DirectoryExists(SrcDirectory))
+ {
+ Log($"Cloning branch '{Repo.ChangelogBranch}' from '{Repo.HttpsGitUrl}' to '{SrcDirectory}'.");
+ context.GitRunner.Clone(SrcDirectory, Repo.HttpsGitUrl, Repo.ChangelogBranch);
+ Log($"Clone completed: '{Repo.ChangelogBranch}' -> '{SrcDirectory}'.");
+ }
+ else
+ {
+ Log($"Directory '{SrcDirectory}' already exists. Skipping clone.");
+ }
+ }
+}
\ No newline at end of file
diff --git a/build/BenchmarkDotNet.Build/ChangeLogBuilder.cs b/build/BenchmarkDotNet.Build/Runners/Changelog/ChangelogDetailsBuilder.cs
similarity index 93%
rename from build/BenchmarkDotNet.Build/ChangeLogBuilder.cs
rename to build/BenchmarkDotNet.Build/Runners/Changelog/ChangelogDetailsBuilder.cs
index e7769e1fbd..27540402e3 100644
--- a/build/BenchmarkDotNet.Build/ChangeLogBuilder.cs
+++ b/build/BenchmarkDotNet.Build/Runners/Changelog/ChangelogDetailsBuilder.cs
@@ -10,15 +10,15 @@
using Cake.Core.IO;
using Octokit;
-namespace BenchmarkDotNet.Build;
+namespace BenchmarkDotNet.Build.Runners.Changelog;
-public static class ChangeLogBuilder
+public static class ChangelogDetailsBuilder
{
- private class Config
+ private class Config(string currentVersion, string previousVersion, string lastCommit)
{
- public string CurrentVersion { get; }
- public string PreviousVersion { get; }
- public string LastCommit { get; }
+ public string CurrentVersion { get; } = currentVersion;
+ public string PreviousVersion { get; } = previousVersion;
+ public string LastCommit { get; } = lastCommit;
public void Deconstruct(out string currentMilestone, out string previousMilestone, out string lastCommit)
{
@@ -26,13 +26,6 @@ public void Deconstruct(out string currentMilestone, out string previousMileston
previousMilestone = PreviousVersion;
lastCommit = LastCommit;
}
-
- public Config(string currentVersion, string previousVersion, string lastCommit)
- {
- CurrentVersion = currentVersion;
- PreviousVersion = previousVersion;
- LastCommit = lastCommit;
- }
}
private class MarkdownBuilder
diff --git a/build/BenchmarkDotNet.Build/Runners/DocumentationRunner.cs b/build/BenchmarkDotNet.Build/Runners/DocumentationRunner.cs
index 2d2128a86f..e1d1af7469 100644
--- a/build/BenchmarkDotNet.Build/Runners/DocumentationRunner.cs
+++ b/build/BenchmarkDotNet.Build/Runners/DocumentationRunner.cs
@@ -1,11 +1,9 @@
-using System;
-using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using BenchmarkDotNet.Build.Helpers;
using BenchmarkDotNet.Build.Meta;
-using BenchmarkDotNet.Build.Options;
+using BenchmarkDotNet.Build.Runners.Changelog;
using Cake.Common.Diagnostics;
using Cake.Common.IO;
using Cake.Core.IO;
@@ -16,85 +14,47 @@ namespace BenchmarkDotNet.Build.Runners;
public class DocumentationRunner
{
private readonly BuildContext context;
- private readonly bool preview;
- private readonly string depth;
-
- public DirectoryPath ChangelogDirectory { get; }
- public DirectoryPath ChangelogSrcDirectory { get; }
- private readonly DirectoryPath changelogDetailsDirectory;
+ private readonly ChangelogBuilder changelogBuilder;
private readonly DirectoryPath docsGeneratedDirectory;
private readonly FilePath docfxJsonFile;
private readonly FilePath redirectFile;
private readonly FilePath readmeFile;
private readonly FilePath rootIndexFile;
- private readonly FilePath changelogIndexFile;
- private readonly FilePath changelogFullFile;
- private readonly FilePath changelogTocFile;
- private readonly FilePath lastFooterFile;
+
+ public DirectoryPath ChangelogSrcDirectory => changelogBuilder.SrcDirectory;
public DocumentationRunner(BuildContext context)
{
this.context = context;
- preview = KnownOptions.DocsPreview.Resolve(context);
- depth = KnownOptions.DocsDepth.Resolve(context);
+ changelogBuilder = new ChangelogBuilder(context);
var docsDirectory = context.RootDirectory.Combine("docs");
- ChangelogDirectory = docsDirectory.Combine("changelog");
- ChangelogSrcDirectory = docsDirectory.Combine("_changelog");
- changelogDetailsDirectory = ChangelogSrcDirectory.Combine("details");
docsGeneratedDirectory = docsDirectory.Combine("_site");
-
redirectFile = docsDirectory.Combine("_redirects").CombineWithFilePath("_redirects");
docfxJsonFile = docsDirectory.CombineWithFilePath("docfx.json");
readmeFile = context.RootDirectory.CombineWithFilePath("README.md");
rootIndexFile = docsDirectory.CombineWithFilePath("index.md");
- changelogIndexFile = ChangelogDirectory.CombineWithFilePath("index.md");
- changelogFullFile = ChangelogDirectory.CombineWithFilePath("full.md");
- changelogTocFile = ChangelogDirectory.CombineWithFilePath("toc.yml");
- lastFooterFile = ChangelogSrcDirectory.Combine("footer")
- .CombineWithFilePath("v" + context.VersionHistory.CurrentVersion + ".md");
}
- public void Update()
+ public void Fetch()
{
EnvVar.GitHubToken.AssertHasValue();
+ changelogBuilder.Fetch();
+ }
- UpdateReadme();
- UpdateLastFooter();
-
- EnsureChangelogDetailsExist();
-
- var history = context.VersionHistory;
- var stableVersionCount = history.StableVersions.Length;
-
- if (depth.Equals("all", StringComparison.OrdinalIgnoreCase))
- {
- DocfxChangelogDownload(
- history.StableVersions.First(),
- history.FirstCommit);
-
- for (var i = 1; i < stableVersionCount; i++)
- DocfxChangelogDownload(
- history.StableVersions[i],
- history.StableVersions[i - 1]);
- }
- else if (depth != "")
- {
- if (!int.TryParse(depth, CultureInfo.InvariantCulture, out var depthValue))
- throw new InvalidDataException($"Failed to parse the depth value: '{depth}'");
+ public void Generate()
+ {
+ changelogBuilder.Generate();
- for (var i = Math.Max(stableVersionCount - depthValue, 1); i < stableVersionCount; i++)
- DocfxChangelogDownload(
- history.StableVersions[i],
- history.StableVersions[i - 1]);
- }
+ UpdateReadme();
+ GenerateIndexMd();
+ }
- if (preview)
- DocfxChangelogDownload(
- history.CurrentVersion,
- history.StableVersions.Last(),
- "HEAD");
+ public void Build()
+ {
+ RunDocfx();
+ GenerateRedirects();
}
private void UpdateReadme()
@@ -108,24 +68,6 @@ private void UpdateReadme()
context.GenerateFile(context.ReadmeFile, content, true);
}
- public void Prepare()
- {
- foreach (var version in context.VersionHistory.StableVersions)
- DocfxChangelogGenerate(version);
- if (preview)
- DocfxChangelogGenerate(context.VersionHistory.CurrentVersion);
-
- GenerateIndexMd();
- GenerateChangelogIndex();
- GenerateChangelogFull();
- GenerateChangelogToc();
- }
-
- public void Build()
- {
- RunDocfx();
- GenerateRedirects();
- }
private void RunDocfx()
{
@@ -149,124 +91,6 @@ private void GenerateIndexMd()
context.GenerateFile(rootIndexFile, content);
}
- private void GenerateChangelogToc()
- {
- var content = new StringBuilder();
-
- if (preview)
- {
- content.AppendLine($"- name: v{context.VersionHistory.CurrentVersion}");
- content.AppendLine($" href: v{context.VersionHistory.CurrentVersion}.md");
- }
-
- foreach (var version in context.VersionHistory.StableVersions.Reverse())
- {
- content.AppendLine($"- name: v{version}");
- content.AppendLine($" href: v{version}.md");
- }
-
- content.AppendLine("- name: Full ChangeLog");
- content.AppendLine(" href: full.md");
-
- context.GenerateFile(changelogTocFile, content);
- }
-
- private void GenerateChangelogFull()
- {
- var content = new StringBuilder();
- content.AppendLine("---");
- content.AppendLine("uid: changelog.full");
- content.AppendLine("---");
- content.AppendLine("");
- content.AppendLine("# Full ChangeLog");
- content.AppendLine("");
- if (preview)
- content.AppendLine(
- $"[!include[v{context.VersionHistory.CurrentVersion}](v{context.VersionHistory.CurrentVersion}.md)]");
- foreach (var version in context.VersionHistory.StableVersions.Reverse())
- content.AppendLine($"[!include[v{version}](v{version}.md)]");
-
- context.GenerateFile(changelogFullFile, content);
- }
-
- private void GenerateChangelogIndex()
- {
- var content = new StringBuilder();
- content.AppendLine("---");
- content.AppendLine("uid: changelog");
- content.AppendLine("---");
- content.AppendLine("");
- content.AppendLine("# ChangeLog");
- content.AppendLine("");
- if (preview)
- content.AppendLine($"* @changelog.v{context.VersionHistory.CurrentVersion}");
- foreach (var version in context.VersionHistory.StableVersions.Reverse())
- content.AppendLine($"* @changelog.v{version}");
- content.AppendLine("* @changelog.full");
-
- context.GenerateFile(changelogIndexFile, content);
- }
-
- private void DocfxChangelogGenerate(string version)
- {
- EnsureChangelogDetailsExist();
- var md = $"v{version}.md";
- var header = ChangelogSrcDirectory.Combine("header").CombineWithFilePath(md);
- var footer = ChangelogSrcDirectory.Combine("footer").CombineWithFilePath(md);
- var details = ChangelogSrcDirectory.Combine("details").CombineWithFilePath(md);
- var release = ChangelogDirectory.CombineWithFilePath(md);
-
- var content = new StringBuilder();
- content.AppendLine("---");
- content.AppendLine("uid: changelog.v" + version);
- content.AppendLine("---");
- content.AppendLine("");
- content.AppendLine("# BenchmarkDotNet v" + version);
- content.AppendLine("");
- content.AppendLine("");
-
- if (context.FileExists(header))
- {
- content.AppendLine(context.FileReadText(header));
- content.AppendLine("");
- content.AppendLine("");
- }
-
- if (context.FileExists(details))
- {
- content.AppendLine(context.FileReadText(details));
- content.AppendLine("");
- content.AppendLine("");
- }
-
- if (context.FileExists(footer))
- {
- content.AppendLine("## Additional details");
- content.AppendLine("");
- content.AppendLine(context.FileReadText(footer));
- }
-
- context.GenerateFile(release, content.ToString());
- }
-
- private void EnsureChangelogDetailsExist(bool forceClean = false)
- {
- if (context.DirectoryExists(changelogDetailsDirectory) && forceClean)
- context.DeleteDirectory(
- changelogDetailsDirectory,
- new DeleteDirectorySettings { Force = true, Recursive = true });
-
- if (!context.DirectoryExists(changelogDetailsDirectory))
- context.GitRunner.Clone(changelogDetailsDirectory, Repo.HttpsGitUrl, Repo.ChangelogDetailsBranch);
- }
-
- private void DocfxChangelogDownload(string version, string versionPrevious, string lastCommit = "")
- {
- EnsureChangelogDetailsExist();
- context.Information($"Downloading changelog details for v{version}");
- ChangeLogBuilder.Run(context, changelogDetailsDirectory, version, versionPrevious, lastCommit);
- }
-
private void GenerateRedirects()
{
if (!context.FileExists(redirectFile))
@@ -300,27 +124,4 @@ private void GenerateRedirects()
context.GenerateFile(fullFilePath, content);
}
}
-
- private void UpdateLastFooter()
- {
- var version = context.VersionHistory.CurrentVersion;
- var previousVersion = context.VersionHistory.StableVersions.Last();
- var date = KnownOptions.Stable.Resolve(context)
- ? DateTime.Now.ToString("MMMM dd, yyyy", CultureInfo.InvariantCulture)
- : "TBA";
-
- var content = new StringBuilder();
- content.AppendLine($"_Date: {date}_");
- content.AppendLine("");
- content.AppendLine(
- $"_Milestone: [v{version}](https://github.com/dotnet/BenchmarkDotNet/issues?q=milestone%3Av{version})_");
- content.AppendLine(
- $"([List of commits](https://github.com/dotnet/BenchmarkDotNet/compare/v{previousVersion}...v{version}))");
- content.AppendLine("");
- content.AppendLine("_NuGet Packages:_");
- foreach (var packageName in context.NuGetPackageNames)
- content.AppendLine($"* https://www.nuget.org/packages/{packageName}/{version}");
-
- context.GenerateFile(lastFooterFile, content);
- }
}
\ No newline at end of file
diff --git a/build/BenchmarkDotNet.Build/Runners/UnitTestRunner.cs b/build/BenchmarkDotNet.Build/Runners/UnitTestRunner.cs
index 9b10d54ea4..89b777d09f 100644
--- a/build/BenchmarkDotNet.Build/Runners/UnitTestRunner.cs
+++ b/build/BenchmarkDotNet.Build/Runners/UnitTestRunner.cs
@@ -4,6 +4,7 @@
using Cake.Common.Tools.DotNet;
using Cake.Common.Tools.DotNet.Test;
using Cake.Core.IO;
+using System.Runtime.InteropServices;
namespace BenchmarkDotNet.Build.Runners;
@@ -47,7 +48,8 @@ private DotNetTestSettings GetTestSettingsParameters(FilePath logFile, string tf
private void RunTests(FilePath projectFile, string alias, string tfm)
{
var os = Utils.GetOs();
- var trxFileName = $"{os}-{alias}-{tfm}.trx";
+ var arch = RuntimeInformation.OSArchitecture.ToString().ToLower();
+ var trxFileName = $"{os}({arch})-{alias}-{tfm}.trx";
var trxFile = TestOutputDirectory.CombineWithFilePath(trxFileName);
var settings = GetTestSettingsParameters(trxFile, tfm);
diff --git a/build/cSpell.json b/build/cSpell.json
index 6bdc2226cc..a34d415fe3 100644
--- a/build/cSpell.json
+++ b/build/cSpell.json
@@ -12,6 +12,7 @@
"Cygwin",
"Diagnoser",
"diagnosers",
+ "diagsession",
"disassemblers",
"disassm",
"Jits",
@@ -29,6 +30,7 @@
"Pseudocode",
"runtimes",
"Serilog",
+ "vsprofiler",
"vstest",
"Tailcall",
"toolchains",
@@ -37,8 +39,10 @@
"ignoreWords": [
"Akinshin",
"Andrey",
+ "Cassell",
"Expecto",
"Jint",
+ "JITted",
"LoongArch64",
"macrobenchmark",
"MediatR",
@@ -47,6 +51,9 @@
"NodaTime",
"Npgsql",
"Sitnik's",
+ "Sitnik",
+ "Stepanov",
+ "Yegor",
"Wojciech",
"Avalonia",
"Gitter"
diff --git a/build/common.props b/build/common.props
index e74a0b8680..b33bc4d863 100644
--- a/build/common.props
+++ b/build/common.props
@@ -23,15 +23,20 @@
true
annotations
+
+ true
-
+
$(MSBuildThisFileDirectory)strongNameKey.snk
true
+
+
+
true
@@ -40,7 +45,7 @@
- 0.14.0
+ 0.15.3
@@ -56,12 +61,13 @@
-
+
all
runtime; build; native; contentfiles; analyzers
-
+
all
+ runtime; build; native; contentfiles; analyzers
diff --git a/build/sdk/global.json b/build/sdk/global.json
index 94c8cfbc04..be5c357f30 100644
--- a/build/sdk/global.json
+++ b/build/sdk/global.json
@@ -1,6 +1,6 @@
{
"sdk": {
- "version": "8.0.101",
+ "version": "8.0.410",
"rollForward": "disable"
}
}
diff --git a/build/versions.txt b/build/versions.txt
index 584dcda7c7..df0365a030 100644
--- a/build/versions.txt
+++ b/build/versions.txt
@@ -57,4 +57,8 @@
0.13.10
0.13.11
0.13.12
-0.14.0
\ No newline at end of file
+0.14.0
+0.15.0
+0.15.1
+0.15.2
+0.15.3
\ No newline at end of file
diff --git a/docs/.gitignore b/docs/.gitignore
index aad3551319..ebe91edcfe 100644
--- a/docs/.gitignore
+++ b/docs/.gitignore
@@ -11,4 +11,6 @@ _exported_templates
docfx-bin
api/*
!api/index.md
-/index.md
\ No newline at end of file
+/index.md
+_changelog
+changelog
diff --git a/docs/_changelog/.gitignore b/docs/_changelog/.gitignore
deleted file mode 100644
index 3bc49a8a60..0000000000
--- a/docs/_changelog/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-details
diff --git a/docs/_changelog/footer/v0.10.0.md b/docs/_changelog/footer/v0.10.0.md
deleted file mode 100644
index 823ad6954c..0000000000
--- a/docs/_changelog/footer/v0.10.0.md
+++ /dev/null
@@ -1,11 +0,0 @@
-_Date: November 10, 2016_
-
-_Milestone: [v0.10.0](https://github.com/PerfDotNet/BenchmarkDotNet/issues?q=milestone%3Av0.10.0)_
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.10.0
-* https://www.nuget.org/packages/BenchmarkDotNet.Core/0.10.0
-* https://www.nuget.org/packages/BenchmarkDotNet.Toolchains.Roslyn/0.10.0
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.Windows/0.10.0
-
-_Online Documentation:_ https://dotnet.github.io/BenchmarkDotNet/
\ No newline at end of file
diff --git a/docs/_changelog/footer/v0.10.1.md b/docs/_changelog/footer/v0.10.1.md
deleted file mode 100644
index 6a2f16d19d..0000000000
--- a/docs/_changelog/footer/v0.10.1.md
+++ /dev/null
@@ -1,9 +0,0 @@
-_Date: December 04, 2016_
-
-_Milestone: [v0.10.1](https://github.com/PerfDotNet/BenchmarkDotNet/issues?q=milestone%3Av0.10.1)_
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.10.1
-* https://www.nuget.org/packages/BenchmarkDotNet.Core/0.10.1
-* https://www.nuget.org/packages/BenchmarkDotNet.Toolchains.Roslyn/0.10.1
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.Windows/0.10.1
\ No newline at end of file
diff --git a/docs/_changelog/footer/v0.10.10.md b/docs/_changelog/footer/v0.10.10.md
deleted file mode 100644
index 9dde0a1cc3..0000000000
--- a/docs/_changelog/footer/v0.10.10.md
+++ /dev/null
@@ -1,10 +0,0 @@
-_Date: November 03, 2017_
-
-_Milestone: [v0.10.10](https://github.com/PerfDotNet/BenchmarkDotNet/issues?q=milestone%3Av0.10.10)_
-([List of commits](https://github.com/dotnet/BenchmarkDotNet/compare/v0.10.9...v0.10.10))
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.10.10
-* https://www.nuget.org/packages/BenchmarkDotNet.Core/0.10.10
-* https://www.nuget.org/packages/BenchmarkDotNet.Toolchains.Roslyn/0.10.10
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.Windows/0.10.10
\ No newline at end of file
diff --git a/docs/_changelog/footer/v0.10.11.md b/docs/_changelog/footer/v0.10.11.md
deleted file mode 100644
index 0de3715f93..0000000000
--- a/docs/_changelog/footer/v0.10.11.md
+++ /dev/null
@@ -1,10 +0,0 @@
-_Date: December 01, 2017_
-
-_Milestone: [v0.10.11](https://github.com/PerfDotNet/BenchmarkDotNet/issues?q=milestone%3Av0.10.11)_
-([List of commits](https://github.com/dotnet/BenchmarkDotNet/compare/v0.10.10...v0.10.11))
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.10.11
-* https://www.nuget.org/packages/BenchmarkDotNet.Core/0.10.11
-* https://www.nuget.org/packages/BenchmarkDotNet.Toolchains.Roslyn/0.10.11
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.Windows/0.10.11
\ No newline at end of file
diff --git a/docs/_changelog/footer/v0.10.12.md b/docs/_changelog/footer/v0.10.12.md
deleted file mode 100644
index 3106385452..0000000000
--- a/docs/_changelog/footer/v0.10.12.md
+++ /dev/null
@@ -1,10 +0,0 @@
-_Date: January 15, 2018_
-
-_Milestone: [v0.10.12](https://github.com/PerfDotNet/BenchmarkDotNet/issues?q=milestone%3Av0.10.12)_
-([List of commits](https://github.com/dotnet/BenchmarkDotNet/compare/v0.10.11...v0.10.12))
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.10.12
-* https://www.nuget.org/packages/BenchmarkDotNet.Core/0.10.12
-* https://www.nuget.org/packages/BenchmarkDotNet.Toolchains.Roslyn/0.10.12
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.Windows/0.10.12
\ No newline at end of file
diff --git a/docs/_changelog/footer/v0.10.13.md b/docs/_changelog/footer/v0.10.13.md
deleted file mode 100644
index 6eb70e1fc2..0000000000
--- a/docs/_changelog/footer/v0.10.13.md
+++ /dev/null
@@ -1,10 +0,0 @@
-_Date: March 02, 2018_
-
-_Milestone: [v0.10.13](https://github.com/PerfDotNet/BenchmarkDotNet/issues?q=milestone%3Av0.10.13)_
-([List of commits](https://github.com/dotnet/BenchmarkDotNet/compare/v0.10.12...v0.10.13))
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.10.13
-* https://www.nuget.org/packages/BenchmarkDotNet.Core/0.10.13
-* https://www.nuget.org/packages/BenchmarkDotNet.Toolchains.Roslyn/0.10.13
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.Windows/0.10.13
\ No newline at end of file
diff --git a/docs/_changelog/footer/v0.10.14.md b/docs/_changelog/footer/v0.10.14.md
deleted file mode 100644
index 2fd3ea5542..0000000000
--- a/docs/_changelog/footer/v0.10.14.md
+++ /dev/null
@@ -1,10 +0,0 @@
-_Date: April 09, 2018_
-
-_Milestone: [v0.10.14](https://github.com/PerfDotNet/BenchmarkDotNet/issues?q=milestone%3Av0.10.14)_
-([List of commits](https://github.com/dotnet/BenchmarkDotNet/compare/v0.10.13...v0.10.14))
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.10.14
-* https://www.nuget.org/packages/BenchmarkDotNet.Core/0.10.14
-* https://www.nuget.org/packages/BenchmarkDotNet.Toolchains.Roslyn/0.10.14
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.Windows/0.10.14
\ No newline at end of file
diff --git a/docs/_changelog/footer/v0.10.2.md b/docs/_changelog/footer/v0.10.2.md
deleted file mode 100644
index 46e50a038f..0000000000
--- a/docs/_changelog/footer/v0.10.2.md
+++ /dev/null
@@ -1,9 +0,0 @@
-_Date: January 21, 2017_
-
-_Milestone: [v0.10.2](https://github.com/PerfDotNet/BenchmarkDotNet/issues?q=milestone%3Av0.10.2)_
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.10.2
-* https://www.nuget.org/packages/BenchmarkDotNet.Core/0.10.2
-* https://www.nuget.org/packages/BenchmarkDotNet.Toolchains.Roslyn/0.10.2
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.Windows/0.10.2
\ No newline at end of file
diff --git a/docs/_changelog/footer/v0.10.3.md b/docs/_changelog/footer/v0.10.3.md
deleted file mode 100644
index 623439e439..0000000000
--- a/docs/_changelog/footer/v0.10.3.md
+++ /dev/null
@@ -1,9 +0,0 @@
-_Date: March 01, 2017_
-
-_Milestone: [v0.10.3](https://github.com/PerfDotNet/BenchmarkDotNet/issues?q=milestone%3Av0.10.3)_
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.10.3
-* https://www.nuget.org/packages/BenchmarkDotNet.Core/0.10.3
-* https://www.nuget.org/packages/BenchmarkDotNet.Toolchains.Roslyn/0.10.3
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.Windows/0.10.3
\ No newline at end of file
diff --git a/docs/_changelog/footer/v0.10.4.md b/docs/_changelog/footer/v0.10.4.md
deleted file mode 100644
index 9f497b878b..0000000000
--- a/docs/_changelog/footer/v0.10.4.md
+++ /dev/null
@@ -1,9 +0,0 @@
-_Date: April 21, 2017_
-
-_Milestone: [v0.10.4](https://github.com/PerfDotNet/BenchmarkDotNet/issues?q=milestone%3Av0.10.4)_
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.10.4
-* https://www.nuget.org/packages/BenchmarkDotNet.Core/0.10.4
-* https://www.nuget.org/packages/BenchmarkDotNet.Toolchains.Roslyn/0.10.4
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.Windows/0.10.4
\ No newline at end of file
diff --git a/docs/_changelog/footer/v0.10.5.md b/docs/_changelog/footer/v0.10.5.md
deleted file mode 100644
index b4d8d5b914..0000000000
--- a/docs/_changelog/footer/v0.10.5.md
+++ /dev/null
@@ -1,9 +0,0 @@
-_Date: April 26, 2017_
-
-_Milestone: [v0.10.5](https://github.com/PerfDotNet/BenchmarkDotNet/issues?q=milestone%3Av0.10.5)_
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.10.5
-* https://www.nuget.org/packages/BenchmarkDotNet.Core/0.10.5
-* https://www.nuget.org/packages/BenchmarkDotNet.Toolchains.Roslyn/0.10.5
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.Windows/0.10.5
\ No newline at end of file
diff --git a/docs/_changelog/footer/v0.10.6.md b/docs/_changelog/footer/v0.10.6.md
deleted file mode 100644
index b29dfc3939..0000000000
--- a/docs/_changelog/footer/v0.10.6.md
+++ /dev/null
@@ -1,9 +0,0 @@
-_Date: May 12, 2017_
-
-_Milestone: [v0.10.6](https://github.com/PerfDotNet/BenchmarkDotNet/issues?q=milestone%3Av0.10.6)_
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.10.6
-* https://www.nuget.org/packages/BenchmarkDotNet.Core/0.10.6
-* https://www.nuget.org/packages/BenchmarkDotNet.Toolchains.Roslyn/0.10.6
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.Windows/0.10.6
\ No newline at end of file
diff --git a/docs/_changelog/footer/v0.10.7.md b/docs/_changelog/footer/v0.10.7.md
deleted file mode 100644
index 5ee21fee21..0000000000
--- a/docs/_changelog/footer/v0.10.7.md
+++ /dev/null
@@ -1,11 +0,0 @@
-_Date: June 05, 2017_
-
-_Milestone: [v0.10.7](https://github.com/PerfDotNet/BenchmarkDotNet/issues?q=milestone%3Av0.10.7)_
-
-_Overview post: https://aakinshin.net/posts/bdn-v0_10_7/_
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.10.7
-* https://www.nuget.org/packages/BenchmarkDotNet.Core/0.10.7
-* https://www.nuget.org/packages/BenchmarkDotNet.Toolchains.Roslyn/0.10.7
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.Windows/0.10.7
\ No newline at end of file
diff --git a/docs/_changelog/footer/v0.10.8.md b/docs/_changelog/footer/v0.10.8.md
deleted file mode 100644
index 791022d425..0000000000
--- a/docs/_changelog/footer/v0.10.8.md
+++ /dev/null
@@ -1,9 +0,0 @@
-_Date: June 09, 2017_
-
-_Milestone: [v0.10.8](https://github.com/PerfDotNet/BenchmarkDotNet/issues?q=milestone%3Av0.10.8)_
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.10.8
-* https://www.nuget.org/packages/BenchmarkDotNet.Core/0.10.8
-* https://www.nuget.org/packages/BenchmarkDotNet.Toolchains.Roslyn/0.10.8
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.Windows/0.10.8
\ No newline at end of file
diff --git a/docs/_changelog/footer/v0.10.9.md b/docs/_changelog/footer/v0.10.9.md
deleted file mode 100644
index 8fc3fef03e..0000000000
--- a/docs/_changelog/footer/v0.10.9.md
+++ /dev/null
@@ -1,9 +0,0 @@
-_Date: July 28, 2017_
-
-_Milestone: [v0.10.9](https://github.com/PerfDotNet/BenchmarkDotNet/issues?q=milestone%3Av0.10.9)_
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.10.9
-* https://www.nuget.org/packages/BenchmarkDotNet.Core/0.10.9
-* https://www.nuget.org/packages/BenchmarkDotNet.Toolchains.Roslyn/0.10.9
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.Windows/0.10.9
\ No newline at end of file
diff --git a/docs/_changelog/footer/v0.11.0.md b/docs/_changelog/footer/v0.11.0.md
deleted file mode 100644
index f9637a79e6..0000000000
--- a/docs/_changelog/footer/v0.11.0.md
+++ /dev/null
@@ -1,8 +0,0 @@
-_Date: July 23, 2018_
-
-_Milestone: [v0.11.0](https://github.com/PerfDotNet/BenchmarkDotNet/issues?q=milestone%3Av0.11.0)_
-([List of commits](https://github.com/dotnet/BenchmarkDotNet/compare/v0.10.14...v0.11.0))
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.11.0
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.Windows/0.11.0
\ No newline at end of file
diff --git a/docs/_changelog/footer/v0.11.1.md b/docs/_changelog/footer/v0.11.1.md
deleted file mode 100644
index f6c73f9510..0000000000
--- a/docs/_changelog/footer/v0.11.1.md
+++ /dev/null
@@ -1,8 +0,0 @@
-_Date: August 22, 2018_
-
-_Milestone: [v0.11.1](https://github.com/PerfDotNet/BenchmarkDotNet/issues?q=milestone%3Av0.11.1)_
-([List of commits](https://github.com/dotnet/BenchmarkDotNet/compare/v0.11.0...v0.11.1))
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.11.1
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.Windows/0.11.1
\ No newline at end of file
diff --git a/docs/_changelog/footer/v0.11.2.md b/docs/_changelog/footer/v0.11.2.md
deleted file mode 100644
index 338170b8ae..0000000000
--- a/docs/_changelog/footer/v0.11.2.md
+++ /dev/null
@@ -1,8 +0,0 @@
-_Date: November 1, 2018_
-
-_Milestone: [v0.11.2](https://github.com/PerfDotNet/BenchmarkDotNet/issues?q=milestone%3Av0.11.2)_
-([List of commits](https://github.com/dotnet/BenchmarkDotNet/compare/v0.11.1...v0.11.2))
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.11.2
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.Windows/0.11.2
\ No newline at end of file
diff --git a/docs/_changelog/footer/v0.11.3.md b/docs/_changelog/footer/v0.11.3.md
deleted file mode 100644
index befc032994..0000000000
--- a/docs/_changelog/footer/v0.11.3.md
+++ /dev/null
@@ -1,8 +0,0 @@
-_Date: November 20, 2018_
-
-_Milestone: [v0.11.3](https://github.com/PerfDotNet/BenchmarkDotNet/issues?q=milestone%3Av0.11.3)_
-([List of commits](https://github.com/dotnet/BenchmarkDotNet/compare/v0.11.2...v0.11.3))
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.11.3
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.Windows/0.11.3
\ No newline at end of file
diff --git a/docs/_changelog/footer/v0.11.4.md b/docs/_changelog/footer/v0.11.4.md
deleted file mode 100644
index 5cbac89988..0000000000
--- a/docs/_changelog/footer/v0.11.4.md
+++ /dev/null
@@ -1,9 +0,0 @@
-_Date: February 15, 2019_
-
-_Milestone: [v0.11.4](https://github.com/PerfDotNet/BenchmarkDotNet/issues?q=milestone%3Av0.11.4)_
-([List of commits](https://github.com/dotnet/BenchmarkDotNet/compare/v0.11.3...v0.11.4))
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.11.4
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.Windows/0.11.4
-* https://www.nuget.org/packages/BenchmarkDotNet.Tool/0.11.4
\ No newline at end of file
diff --git a/docs/_changelog/footer/v0.11.5.md b/docs/_changelog/footer/v0.11.5.md
deleted file mode 100644
index be3282de78..0000000000
--- a/docs/_changelog/footer/v0.11.5.md
+++ /dev/null
@@ -1,10 +0,0 @@
-_Date: April 2, 2019_
-
-_Milestone: [v0.11.5](https://github.com/dotnet/BenchmarkDotNet/issues?q=milestone%3Av0.11.5)_
-([List of commits](https://github.com/dotnet/BenchmarkDotNet/compare/v0.11.4...v0.11.5))
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.11.5
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.Windows/0.11.5
-* https://www.nuget.org/packages/BenchmarkDotNet.Tool/0.11.5
-* https://www.nuget.org/packages/BenchmarkDotNet.Annotations/0.11.5
\ No newline at end of file
diff --git a/docs/_changelog/footer/v0.12.0.md b/docs/_changelog/footer/v0.12.0.md
deleted file mode 100644
index 565429ca6a..0000000000
--- a/docs/_changelog/footer/v0.12.0.md
+++ /dev/null
@@ -1,11 +0,0 @@
-_Date: October 24, 2019_
-
-_Milestone: [v0.12.0](https://github.com/dotnet/BenchmarkDotNet/issues?q=milestone%3Av0.12.0)_
-([List of commits](https://github.com/dotnet/BenchmarkDotNet/compare/v0.11.5...v0.12.0))
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.12.0
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.Windows/0.12.0
-* https://www.nuget.org/packages/BenchmarkDotNet.Tool/0.12.0
-* https://www.nuget.org/packages/BenchmarkDotNet.Annotations/0.12.0
-* https://www.nuget.org/packages/BenchmarkDotNet.Templates/0.12.0
\ No newline at end of file
diff --git a/docs/_changelog/footer/v0.12.1.md b/docs/_changelog/footer/v0.12.1.md
deleted file mode 100644
index af5fca9dc4..0000000000
--- a/docs/_changelog/footer/v0.12.1.md
+++ /dev/null
@@ -1,11 +0,0 @@
-_Date: April 6, 2020_
-
-_Milestone: [v0.12.1](https://github.com/dotnet/BenchmarkDotNet/issues?q=milestone%3Av0.12.1)_
-([List of commits](https://github.com/dotnet/BenchmarkDotNet/compare/v0.12.0...v0.12.1))
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.12.1
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.Windows/0.12.1
-* https://www.nuget.org/packages/BenchmarkDotNet.Tool/0.12.1
-* https://www.nuget.org/packages/BenchmarkDotNet.Annotations/0.12.1
-* https://www.nuget.org/packages/BenchmarkDotNet.Templates/0.12.1
\ No newline at end of file
diff --git a/docs/_changelog/footer/v0.13.0.md b/docs/_changelog/footer/v0.13.0.md
deleted file mode 100644
index 7a8ed327bc..0000000000
--- a/docs/_changelog/footer/v0.13.0.md
+++ /dev/null
@@ -1,10 +0,0 @@
-_Date: May 19, 2021_
-
-_Milestone: [v0.13.0](https://github.com/dotnet/BenchmarkDotNet/issues?q=milestone%3Av0.13.0)_
-([List of commits](https://github.com/dotnet/BenchmarkDotNet/compare/v0.12.1...v0.13.0))
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.13.0
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.Windows/0.13.0
-* https://www.nuget.org/packages/BenchmarkDotNet.Annotations/0.13.0
-* https://www.nuget.org/packages/BenchmarkDotNet.Templates/0.13.0
\ No newline at end of file
diff --git a/docs/_changelog/footer/v0.13.1.md b/docs/_changelog/footer/v0.13.1.md
deleted file mode 100644
index eb71d8729f..0000000000
--- a/docs/_changelog/footer/v0.13.1.md
+++ /dev/null
@@ -1,10 +0,0 @@
-_Date: August 11, 2021_
-
-_Milestone: [v0.13.1](https://github.com/dotnet/BenchmarkDotNet/issues?q=milestone%3Av0.13.1)_
-([List of commits](https://github.com/dotnet/BenchmarkDotNet/compare/v0.13.0...v0.13.1))
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.13.1
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.Windows/0.13.1
-* https://www.nuget.org/packages/BenchmarkDotNet.Annotations/0.13.1
-* https://www.nuget.org/packages/BenchmarkDotNet.Templates/0.13.1
\ No newline at end of file
diff --git a/docs/_changelog/footer/v0.13.10.md b/docs/_changelog/footer/v0.13.10.md
deleted file mode 100644
index 68ef4fd0db..0000000000
--- a/docs/_changelog/footer/v0.13.10.md
+++ /dev/null
@@ -1,11 +0,0 @@
-_Date: November 01, 2023_
-
-_Milestone: [v0.13.10](https://github.com/dotnet/BenchmarkDotNet/issues?q=milestone%3Av0.13.10)_
-([List of commits](https://github.com/dotnet/BenchmarkDotNet/compare/v0.13.9...v0.13.10))
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.13.10
-* https://www.nuget.org/packages/BenchmarkDotNet.Annotations/0.13.10
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.dotTrace/0.13.10
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.Windows/0.13.10
-* https://www.nuget.org/packages/BenchmarkDotNet.Templates/0.13.10
diff --git a/docs/_changelog/footer/v0.13.11.md b/docs/_changelog/footer/v0.13.11.md
deleted file mode 100644
index 03dff00d30..0000000000
--- a/docs/_changelog/footer/v0.13.11.md
+++ /dev/null
@@ -1,11 +0,0 @@
-_Date: December 06, 2023_
-
-_Milestone: [v0.13.11](https://github.com/dotnet/BenchmarkDotNet/issues?q=milestone%3Av0.13.11)_
-([List of commits](https://github.com/dotnet/BenchmarkDotNet/compare/v0.13.10...v0.13.11))
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.13.11
-* https://www.nuget.org/packages/BenchmarkDotNet.Annotations/0.13.11
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.dotTrace/0.13.11
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.Windows/0.13.11
-* https://www.nuget.org/packages/BenchmarkDotNet.Templates/0.13.11
diff --git a/docs/_changelog/footer/v0.13.12.md b/docs/_changelog/footer/v0.13.12.md
deleted file mode 100644
index 246864bb8a..0000000000
--- a/docs/_changelog/footer/v0.13.12.md
+++ /dev/null
@@ -1,12 +0,0 @@
-_Date: January 05, 2024_
-
-_Milestone: [v0.13.12](https://github.com/dotnet/BenchmarkDotNet/issues?q=milestone%3Av0.13.12)_
-([List of commits](https://github.com/dotnet/BenchmarkDotNet/compare/v0.13.11...v0.13.12))
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.13.12
-* https://www.nuget.org/packages/BenchmarkDotNet.Annotations/0.13.12
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.dotTrace/0.13.12
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.Windows/0.13.12
-* https://www.nuget.org/packages/BenchmarkDotNet.Templates/0.13.12
-* https://www.nuget.org/packages/BenchmarkDotNet.TestAdapter/0.13.12
diff --git a/docs/_changelog/footer/v0.13.2.md b/docs/_changelog/footer/v0.13.2.md
deleted file mode 100644
index c9022e590e..0000000000
--- a/docs/_changelog/footer/v0.13.2.md
+++ /dev/null
@@ -1,10 +0,0 @@
-_Date: August 26, 2022_
-
-_Milestone: [v0.13.2](https://github.com/dotnet/BenchmarkDotNet/issues?q=milestone%3Av0.13.2)_
-([List of commits](https://github.com/dotnet/BenchmarkDotNet/compare/v0.13.1...v0.13.2))
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.13.2
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.Windows/0.13.2
-* https://www.nuget.org/packages/BenchmarkDotNet.Annotations/0.13.2
-* https://www.nuget.org/packages/BenchmarkDotNet.Templates/0.13.2
\ No newline at end of file
diff --git a/docs/_changelog/footer/v0.13.3.md b/docs/_changelog/footer/v0.13.3.md
deleted file mode 100644
index f0fa6dff23..0000000000
--- a/docs/_changelog/footer/v0.13.3.md
+++ /dev/null
@@ -1,10 +0,0 @@
-_Date: December 26, 2022_
-
-_Milestone: [v0.13.3](https://github.com/dotnet/BenchmarkDotNet/issues?q=milestone%3Av0.13.3)_
-([List of commits](https://github.com/dotnet/BenchmarkDotNet/compare/v0.13.2...v0.13.3))
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.13.3
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.Windows/0.13.3
-* https://www.nuget.org/packages/BenchmarkDotNet.Annotations/0.13.3
-* https://www.nuget.org/packages/BenchmarkDotNet.Templates/0.13.3
\ No newline at end of file
diff --git a/docs/_changelog/footer/v0.13.4.md b/docs/_changelog/footer/v0.13.4.md
deleted file mode 100644
index b6ac2ad6af..0000000000
--- a/docs/_changelog/footer/v0.13.4.md
+++ /dev/null
@@ -1,10 +0,0 @@
-_Date: January 13, 2023_
-
-_Milestone: [v0.13.4](https://github.com/dotnet/BenchmarkDotNet/issues?q=milestone%3Av0.13.4)_
-([List of commits](https://github.com/dotnet/BenchmarkDotNet/compare/v0.13.3...v0.13.4))
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.13.4
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.Windows/0.13.4
-* https://www.nuget.org/packages/BenchmarkDotNet.Annotations/0.13.4
-* https://www.nuget.org/packages/BenchmarkDotNet.Templates/0.13.4
\ No newline at end of file
diff --git a/docs/_changelog/footer/v0.13.5.md b/docs/_changelog/footer/v0.13.5.md
deleted file mode 100644
index 577eeaea84..0000000000
--- a/docs/_changelog/footer/v0.13.5.md
+++ /dev/null
@@ -1,10 +0,0 @@
-_Date: February 17, 2023_
-
-_Milestone: [v0.13.5](https://github.com/dotnet/BenchmarkDotNet/issues?q=milestone%3Av0.13.5)_
-([List of commits](https://github.com/dotnet/BenchmarkDotNet/compare/v0.13.4...v0.13.5))
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.13.5
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.Windows/0.13.5
-* https://www.nuget.org/packages/BenchmarkDotNet.Annotations/0.13.5
-* https://www.nuget.org/packages/BenchmarkDotNet.Templates/0.13.5
\ No newline at end of file
diff --git a/docs/_changelog/footer/v0.13.6.md b/docs/_changelog/footer/v0.13.6.md
deleted file mode 100644
index 7235cd2302..0000000000
--- a/docs/_changelog/footer/v0.13.6.md
+++ /dev/null
@@ -1,11 +0,0 @@
-_Date: July 11, 2023_
-
-_Milestone: [v0.13.6](https://github.com/dotnet/BenchmarkDotNet/issues?q=milestone%3Av0.13.6)_
-([List of commits](https://github.com/dotnet/BenchmarkDotNet/compare/v0.13.5...v0.13.6))
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.13.6
-* https://www.nuget.org/packages/BenchmarkDotNet.Annotations/0.13.6
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.dotTrace/0.13.6
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.Windows/0.13.6
-* https://www.nuget.org/packages/BenchmarkDotNet.Templates/0.13.6
diff --git a/docs/_changelog/footer/v0.13.7.md b/docs/_changelog/footer/v0.13.7.md
deleted file mode 100644
index 1b256837d4..0000000000
--- a/docs/_changelog/footer/v0.13.7.md
+++ /dev/null
@@ -1,11 +0,0 @@
-_Date: August 04, 2023_
-
-_Milestone: [v0.13.7](https://github.com/dotnet/BenchmarkDotNet/issues?q=milestone%3Av0.13.7)_
-([List of commits](https://github.com/dotnet/BenchmarkDotNet/compare/v0.13.6...v0.13.7))
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.13.7
-* https://www.nuget.org/packages/BenchmarkDotNet.Annotations/0.13.7
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.dotTrace/0.13.7
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.Windows/0.13.7
-* https://www.nuget.org/packages/BenchmarkDotNet.Templates/0.13.7
diff --git a/docs/_changelog/footer/v0.13.8.md b/docs/_changelog/footer/v0.13.8.md
deleted file mode 100644
index a8551cc47f..0000000000
--- a/docs/_changelog/footer/v0.13.8.md
+++ /dev/null
@@ -1,11 +0,0 @@
-_Date: September 08, 2023_
-
-_Milestone: [v0.13.8](https://github.com/dotnet/BenchmarkDotNet/issues?q=milestone%3Av0.13.8)_
-([List of commits](https://github.com/dotnet/BenchmarkDotNet/compare/v0.13.7...v0.13.8))
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.13.8
-* https://www.nuget.org/packages/BenchmarkDotNet.Annotations/0.13.8
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.dotTrace/0.13.8
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.Windows/0.13.8
-* https://www.nuget.org/packages/BenchmarkDotNet.Templates/0.13.8
diff --git a/docs/_changelog/footer/v0.13.9.md b/docs/_changelog/footer/v0.13.9.md
deleted file mode 100644
index 962bc252cc..0000000000
--- a/docs/_changelog/footer/v0.13.9.md
+++ /dev/null
@@ -1,11 +0,0 @@
-_Date: October 05, 2023_
-
-_Milestone: [v0.13.9](https://github.com/dotnet/BenchmarkDotNet/issues?q=milestone%3Av0.13.9)_
-([List of commits](https://github.com/dotnet/BenchmarkDotNet/compare/v0.13.8...v0.13.9))
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.13.9
-* https://www.nuget.org/packages/BenchmarkDotNet.Annotations/0.13.9
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.dotTrace/0.13.9
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.Windows/0.13.9
-* https://www.nuget.org/packages/BenchmarkDotNet.Templates/0.13.9
diff --git a/docs/_changelog/footer/v0.14.0.md b/docs/_changelog/footer/v0.14.0.md
deleted file mode 100644
index cf7b8d6984..0000000000
--- a/docs/_changelog/footer/v0.14.0.md
+++ /dev/null
@@ -1,14 +0,0 @@
-_Date: TBA_
-
-_Milestone: [v0.14.0](https://github.com/dotnet/BenchmarkDotNet/issues?q=milestone%3Av0.14.0)_
-([List of commits](https://github.com/dotnet/BenchmarkDotNet/compare/v0.13.12...v0.14.0))
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.14.0
-* https://www.nuget.org/packages/BenchmarkDotNet.Annotations/0.14.0
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.dotMemory/0.14.0
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.dotTrace/0.14.0
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.Windows/0.14.0
-* https://www.nuget.org/packages/BenchmarkDotNet.Exporters.Plotting/0.14.0
-* https://www.nuget.org/packages/BenchmarkDotNet.Templates/0.14.0
-* https://www.nuget.org/packages/BenchmarkDotNet.TestAdapter/0.14.0
diff --git a/docs/_changelog/footer/v0.8.2.md b/docs/_changelog/footer/v0.8.2.md
deleted file mode 100644
index cbd405d3ae..0000000000
--- a/docs/_changelog/footer/v0.8.2.md
+++ /dev/null
@@ -1,4 +0,0 @@
-_Date: January 19, 2016_
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.8.2
\ No newline at end of file
diff --git a/docs/_changelog/footer/v0.9.0.md b/docs/_changelog/footer/v0.9.0.md
deleted file mode 100644
index 55346f6951..0000000000
--- a/docs/_changelog/footer/v0.9.0.md
+++ /dev/null
@@ -1,4 +0,0 @@
-_Date: February 9, 2016_
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.9.0
\ No newline at end of file
diff --git a/docs/_changelog/footer/v0.9.1.md b/docs/_changelog/footer/v0.9.1.md
deleted file mode 100644
index b38f8a6436..0000000000
--- a/docs/_changelog/footer/v0.9.1.md
+++ /dev/null
@@ -1,4 +0,0 @@
-_Date: February 10, 2016_
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.9.1
\ No newline at end of file
diff --git a/docs/_changelog/footer/v0.9.2.md b/docs/_changelog/footer/v0.9.2.md
deleted file mode 100644
index 73c081a4cd..0000000000
--- a/docs/_changelog/footer/v0.9.2.md
+++ /dev/null
@@ -1,6 +0,0 @@
-_Milestone: [v0.9.2](https://github.com/PerfDotNet/BenchmarkDotNet/issues?q=milestone%3Av0.9.2)_
-
-_Date: March 5, 2016_
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.9.2
\ No newline at end of file
diff --git a/docs/_changelog/footer/v0.9.3.md b/docs/_changelog/footer/v0.9.3.md
deleted file mode 100644
index da9b45b165..0000000000
--- a/docs/_changelog/footer/v0.9.3.md
+++ /dev/null
@@ -1,7 +0,0 @@
-_Milestone: [v0.9.3](https://github.com/PerfDotNet/BenchmarkDotNet/issues?q=milestone%3Av0.9.3)_
-
-_Date: March 13, 2016_
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.9.3
-* https://www.nuget.org/packages/BenchmarkDotNet/0.9.3-beta
\ No newline at end of file
diff --git a/docs/_changelog/footer/v0.9.4.md b/docs/_changelog/footer/v0.9.4.md
deleted file mode 100644
index 0927d0a62b..0000000000
--- a/docs/_changelog/footer/v0.9.4.md
+++ /dev/null
@@ -1,7 +0,0 @@
-_Milestone: [v0.9.4](https://github.com/PerfDotNet/BenchmarkDotNet/issues?q=milestone%3Av0.9.4)_
-
-_Date: March 24, 2016_
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.9.4
-* https://www.nuget.org/packages/BenchmarkDotNet/0.9.4-beta
\ No newline at end of file
diff --git a/docs/_changelog/footer/v0.9.5.md b/docs/_changelog/footer/v0.9.5.md
deleted file mode 100644
index ea7d920994..0000000000
--- a/docs/_changelog/footer/v0.9.5.md
+++ /dev/null
@@ -1,7 +0,0 @@
-_Milestone: [v0.9.5](https://github.com/PerfDotNet/BenchmarkDotNet/issues?q=milestone%3Av0.9.5)_
-
-_Date: May 02, 2016_
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.9.5
-* https://www.nuget.org/packages/BenchmarkDotNet/0.9.5-beta
\ No newline at end of file
diff --git a/docs/_changelog/footer/v0.9.6.md b/docs/_changelog/footer/v0.9.6.md
deleted file mode 100644
index cc71d2d927..0000000000
--- a/docs/_changelog/footer/v0.9.6.md
+++ /dev/null
@@ -1,8 +0,0 @@
-_Milestone: [v0.9.6](https://github.com/PerfDotNet/BenchmarkDotNet/issues?q=milestone%3Av0.9.6)_
-
-_Date: May 11, 2016_
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.9.6
-* https://www.nuget.org/packages/BenchmarkDotNet/0.9.6-beta
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.Windows/0.9.6
\ No newline at end of file
diff --git a/docs/_changelog/footer/v0.9.7.md b/docs/_changelog/footer/v0.9.7.md
deleted file mode 100644
index 21f1d109e5..0000000000
--- a/docs/_changelog/footer/v0.9.7.md
+++ /dev/null
@@ -1,8 +0,0 @@
-_Milestone: [v0.9.7](https://github.com/PerfDotNet/BenchmarkDotNet/issues?q=milestone%3Av0.9.7)_
-
-_Date: May 29, 2016_
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.9.7
-* https://www.nuget.org/packages/BenchmarkDotNet/0.9.7-beta
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.Windows/0.9.7
\ No newline at end of file
diff --git a/docs/_changelog/footer/v0.9.8.md b/docs/_changelog/footer/v0.9.8.md
deleted file mode 100644
index fa5bc33931..0000000000
--- a/docs/_changelog/footer/v0.9.8.md
+++ /dev/null
@@ -1,7 +0,0 @@
-_Milestone: [v0.9.8](https://github.com/PerfDotNet/BenchmarkDotNet/issues?q=milestone%3Av0.9.8)_
-
-_Date: July 07, 2016_
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.9.8
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.Windows/0.9.8
\ No newline at end of file
diff --git a/docs/_changelog/footer/v0.9.9.md b/docs/_changelog/footer/v0.9.9.md
deleted file mode 100644
index 2e6b340c90..0000000000
--- a/docs/_changelog/footer/v0.9.9.md
+++ /dev/null
@@ -1,11 +0,0 @@
-_Date: August 18, 2016_
-
-_Milestone: [v0.9.9](https://github.com/PerfDotNet/BenchmarkDotNet/issues?q=milestone%3Av0.9.9)_
-
-_NuGet Packages:_
-* https://www.nuget.org/packages/BenchmarkDotNet/0.9.9
-* https://www.nuget.org/packages/BenchmarkDotNet.Core/0.9.9
-* https://www.nuget.org/packages/BenchmarkDotNet.Toolchains.Roslyn/0.9.9
-* https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.Windows/0.9.9
-
-_Online Documentation:_ https://perfdotnet.github.io/BenchmarkDotNet/
\ No newline at end of file
diff --git a/docs/_changelog/header/v0.10.0.md b/docs/_changelog/header/v0.10.0.md
deleted file mode 100644
index 48c4103b0a..0000000000
--- a/docs/_changelog/header/v0.10.0.md
+++ /dev/null
@@ -1,12 +0,0 @@
-* Now BenchmarkDotNet is a part of .NET Foundation
-* Job and Column API refactoring (see new documentation)
-* Measurement engine improvements
-* Horology enhancement (see `TimeInterval` and `Frequency`)
-* Introduced `RankColumn` which is based on `WelchTTest` (see [157aabc3](https://github.com/PerfDotNet/BenchmarkDotNet/commit/cf839a0d7ecfdf93da709b63fe324fd2157aabc3))
-* JsonExporters refactoring (see the Exporters/Json section in the documentation)
- * Renamed JsonExporters classed and attributes
- * JsonExporters with custom settings
- * JsonExporters now includes information about the target type namespace (see [#246](https://github.com/PerfDotNet/BenchmarkDotNet/issues/246)).
-* Add `JetBrains.Annotations` (see [#253](https://github.com/PerfDotNet/BenchmarkDotNet/pull/253))
-* RFC 4180 support in CSV exporters (see [#241](https://github.com/PerfDotNet/BenchmarkDotNet/issues/241))
-* Many bugfixes
\ No newline at end of file
diff --git a/docs/_changelog/header/v0.10.1.md b/docs/_changelog/header/v0.10.1.md
deleted file mode 100644
index 70e3be82c8..0000000000
--- a/docs/_changelog/header/v0.10.1.md
+++ /dev/null
@@ -1,8 +0,0 @@
-* MemoryDiagnoser got improved. The changes:
- * Memory Diagnoser is now part of BenchmarkDotNet.Core.dll, and it's **enabled by default**
- * MemoryDiagnoser is **100% accurate** about allocated memory when using default settings or Job.ShortRun or any longer job. (see [#284](https://github.com/dotnet/BenchmarkDotNet/pull/284))
- * Memory Diagnoser no longer includes allocations from Cleanup/Setup methods (see [#186](https://github.com/dotnet/BenchmarkDotNet/issues/186))
- * the results are now scaled so they are stable across the runs. (see [#133](https://github.com/dotnet/BenchmarkDotNet/issues/133))
-* .NET Core 1.1+ support, we no longer support 1.0, we target netcoreapp1.1 now. Reason: we wanted to use `GC.GetAllocatedBytesForCurrentThread` in MemoryDiagnoser which is available only in 1.1+
-* Improved information about environment in summary
-* Minor bugfixes
\ No newline at end of file
diff --git a/docs/_changelog/header/v0.10.10.md b/docs/_changelog/header/v0.10.10.md
deleted file mode 100644
index ef34841958..0000000000
--- a/docs/_changelog/header/v0.10.10.md
+++ /dev/null
@@ -1,10 +0,0 @@
-Highlights:
-
-* Disassembly Diagnoser (read more here: [Disassembling .NET Code with BenchmarkDotNet](https://adamsitnik.com/Disassembly-Diagnoser/))
-* ParamsSources
-* .NET Core x86 support
-* Environment variables and Mono args support
-* Better environment description
-* More: additional sections in the documentation, bug fixes, build script improvements, internal refactoring.
-
-Overview post: [BenchmarkDotNet v0.10.10](https://aakinshin.net/posts/bdn-v0_10_10/)
\ No newline at end of file
diff --git a/docs/_changelog/header/v0.10.11.md b/docs/_changelog/header/v0.10.11.md
deleted file mode 100644
index 3e53cf54cb..0000000000
--- a/docs/_changelog/header/v0.10.11.md
+++ /dev/null
@@ -1,8 +0,0 @@
-## Highlights
-
-* ByRef and Stack-only support ([#492](https://github.com/dotnet/BenchmarkDotNet/issues/492), [sample](https://github.com/dotnet/BenchmarkDotNet/blob/edf20758871ec621fdbfd93d862769da46c4bf15/samples/BenchmarkDotNet.Samples/IL/IL_RefReturns.cs))
-* .NET Core 2.1 support ([#587](https://github.com/dotnet/BenchmarkDotNet/issues/587))
-* Improved LINQPad support
-* Smart logic for precision in ScaledColumn ([#509](https://github.com/dotnet/BenchmarkDotNet/issues/509), [#590](https://github.com/dotnet/BenchmarkDotNet/issues/590))
-* Better macOS version detection ([15d72388](https://github.com/dotnet/BenchmarkDotNet/commit/15d72388436c1060e87662b5f4519b9e7e071627))
-* Minor fixes and improvements
\ No newline at end of file
diff --git a/docs/_changelog/header/v0.10.12.md b/docs/_changelog/header/v0.10.12.md
deleted file mode 100644
index 43cff33827..0000000000
--- a/docs/_changelog/header/v0.10.12.md
+++ /dev/null
@@ -1,22 +0,0 @@
-Overview post: [BenchmarkDotNet v0.10.12](https://aakinshin.net/posts/bdn-v0_10_12/)
-
-### Highlights
-
-* **Improved DisassemblyDiagnoser:**
- BenchmarkDotNet contains an embedded disassembler so that it can print assembly code for all benchmarks;
- it's not easy, but the disassembler evolves in every release.
-* **Improved MemoryDiagnoser:**
- it has a better precision level, and it takes less time to evaluate memory allocations in a benchmark.
-* **New TailCallDiagnoser:**
- now you get notifications when JIT applies the tail call optimizations to your methods.
-* **Better environment info:**
- when your share performance results, it's very important to share information about your environment.
- The library generates the environment summary for you by default.
- Now it contains information about the amount of physical CPU, physical cores, and logic cores.
- If you run a benchmark on a virtual machine, you will get the name of the hypervisor
- (e.g., Hyper-V, VMware, or VirtualBox).
-* **Better summary table:**
- one of the greatest features of BenchmarkDotNet is the summary table.
- It shows all important information about results in a compact and understandable form.
- Now it has better customization options: you can display relative performance of different environments
- (e.g., compare .NET Framework and .NET Core) and group benchmarks by categories.
\ No newline at end of file
diff --git a/docs/_changelog/header/v0.10.13.md b/docs/_changelog/header/v0.10.13.md
deleted file mode 100644
index 73f5aa1e53..0000000000
--- a/docs/_changelog/header/v0.10.13.md
+++ /dev/null
@@ -1 +0,0 @@
-Overview post: [BenchmarkDotNet v0.10.13](https://aakinshin.net/posts/bdn-v0_10_13/)
\ No newline at end of file
diff --git a/docs/_changelog/header/v0.10.14.md b/docs/_changelog/header/v0.10.14.md
deleted file mode 100644
index aefb183459..0000000000
--- a/docs/_changelog/header/v0.10.14.md
+++ /dev/null
@@ -1,4 +0,0 @@
-* Per-method parameterization ([Read more](https://benchmarkdotnet.org/articles/features/parameterization.html))
-* Console histograms and multimodal disribution detection
-* Many improvements for Mono disassembly support on Windows ([Read more](https://aakinshin.net/posts/dotnet-crossruntime-disasm/))
-* Many bugfixes
diff --git a/docs/_changelog/header/v0.10.2.md b/docs/_changelog/header/v0.10.2.md
deleted file mode 100644
index a93c8c36ae..0000000000
--- a/docs/_changelog/header/v0.10.2.md
+++ /dev/null
@@ -1,9 +0,0 @@
-* Closed [#307](https://github.com/dotnet/BenchmarkDotNet/issues/307): culture invariant statistics output
-* Closed [#321](https://github.com/dotnet/BenchmarkDotNet/issues/321): persist optimized, auto-generated dll compiled from url/plain code
-* Closed [#322](https://github.com/dotnet/BenchmarkDotNet/issues/332): always restore the console foreground color
-* Closed [#337](https://github.com/dotnet/BenchmarkDotNet/issues/337): Better detection of Rscript.exe in RPlotExporter
-* Closed [#345](https://github.com/dotnet/BenchmarkDotNet/issues/345): fix bug in WelchTTestPValueColumn for DryJob
-* VS 2017 [compatibility fix](https://github.com/dotnet/BenchmarkDotNet/commit/f4bdae5b7e203e0f0a7d283db5faa78107674f31)
-* [fix](https://github.com/dotnet/BenchmarkDotNet/commit/24dea483b8312efba669d82a6fac3603e60050f5) bold markup for Atlassian exporter
-* Improved precision of nanobenchmarks
-* Minor infrastructure changes and misc fixes
\ No newline at end of file
diff --git a/docs/_changelog/header/v0.10.3.md b/docs/_changelog/header/v0.10.3.md
deleted file mode 100644
index 976a78eeaf..0000000000
--- a/docs/_changelog/header/v0.10.3.md
+++ /dev/null
@@ -1,8 +0,0 @@
-* **New .csprojs** support for .NET Core. Also for F# ([#366](https://github.com/dotnet/BenchmarkDotNet/issues/366))!
-* New plots and RPlotExporter (density plots for each job; cumulative mean plots)
-* Fixed exporter order (now RPlotExporer uses the actual measurements instead of previous version)
-* Xplat improvments in RuntimeInformation
-* Introduced `RunStrategy.Monitoring`
-* Possibility to set custom path for Mono ([#306](https://github.com/dotnet/BenchmarkDotNet/issues/306))
-* Possibility to set any .NET Core version >= 1.1 ([#336](https://github.com/dotnet/BenchmarkDotNet/issues/336))
-* **MemoryDiagnoser is now disabled by default (Breaking changes!!)** ([#369](https://github.com/dotnet/BenchmarkDotNet/issues/369))
\ No newline at end of file
diff --git a/docs/_changelog/header/v0.10.4.md b/docs/_changelog/header/v0.10.4.md
deleted file mode 100644
index f423a6567a..0000000000
--- a/docs/_changelog/header/v0.10.4.md
+++ /dev/null
@@ -1,23 +0,0 @@
-* New logo
-* Update to Roslyn 2.0, drop .NET 4.5 support ([#303](https://github.com/dotnet/BenchmarkDotNet/issues/303))
-* Initial support of HardwareCounters (Windows only)
-* Initial experimental support of in-process benchmarks
-* Optional configs for `BenchmarkSwitcher` ([#391](https://github.com/dotnet/BenchmarkDotNet/issues/391), [#392](https://github.com/dotnet/BenchmarkDotNet/pull/392))
-* Host API interface ([#356](https://github.com/dotnet/BenchmarkDotNet/pull/356))
-* Improved measurements for async benchmarks ([#415](https://github.com/dotnet/BenchmarkDotNet/issues/415))
-* Improved precision level (MinIterationTimes is 500ms instead of 200ms; introduced `AccuracyMode.MaxAbsoluteError` and `AccuracyMode.MaxRelativeError` instead of `AccuracyMode.MaxStdErrRelative`; logic which select amount of iterations uses confidence intervals instead of standard errors; the Error column (half of CI99.9%) is shown by default instead of StdErr)
-* Introduced `ISummaryStyle`, raw data in CSV reports ([#118](https://github.com/dotnet/BenchmarkDotNet/issues/118), [#146](https://github.com/dotnet/BenchmarkDotNet/issues/146), [#396](https://github.com/dotnet/BenchmarkDotNet/pull/396))
-* Handle cases when report files are existed and locked ([#414](https://github.com/dotnet/BenchmarkDotNet/issues/414), [#416](https://github.com/dotnet/BenchmarkDotNet/pull/416))
-* MarkdownExporter right-justifies numeric columns ([#421](https://github.com/dotnet/BenchmarkDotNet/pull/421))
-* Better colors for console output ([#376](https://github.com/dotnet/BenchmarkDotNet/issues/376))
-* Column legends
-* Add information about CPU microarchitecture for well-known processors to summary
-* Fix AssemblyInformationalVersionAttribute ([#382](https://github.com/dotnet/BenchmarkDotNet/issues/382))
-* Fix incorrect method filtering in BenchmarkSwitcher ([#365](https://github.com/dotnet/BenchmarkDotNet/issues/365))
-* Fix OS Version in Summary for Windows 10 ([#351](https://github.com/dotnet/BenchmarkDotNet/issues/351))
-* Fix OS Version on Mono
-* Fix --class and --method filtering ([#249](https://github.com/dotnet/BenchmarkDotNet/issues/249))
-* Fix --exporters option ([#189](https://github.com/dotnet/BenchmarkDotNet/issues/189))
-* Fix escaping logic in CsvExporter ([#294](https://github.com/dotnet/BenchmarkDotNet/issues/294), [#409](https://github.com/dotnet/BenchmarkDotNet/pull/409))
-* [Fix](https://github.com/dotnet/BenchmarkDotNet/commit/0a251b81b826876179740cc8b79c994a73a5cd51) MacOS detection
-* Minor bugfixes and API improvements
\ No newline at end of file
diff --git a/docs/_changelog/header/v0.10.5.md b/docs/_changelog/header/v0.10.5.md
deleted file mode 100644
index 3ed3541c70..0000000000
--- a/docs/_changelog/header/v0.10.5.md
+++ /dev/null
@@ -1,5 +0,0 @@
-* Fixed SizeUnit presentation in the summary table ([#434](https://github.com/dotnet/BenchmarkDotNet/issues/434))
-* In MemoryDiagnoser, now 1kB = 1024B (instead of 1000 in v0.10.4) ([#434](https://github.com/dotnet/BenchmarkDotNet/issues/434))
-* Fix false allocations detection ([#436](https://github.com/dotnet/BenchmarkDotNet/pull/436) [9b44de70](https://github.com/dotnet/BenchmarkDotNet/commit/9b44de704b96e2333d762b14daa152d859b1917d))
-* Hide ScaledSD column for small values ([da857ad7](https://github.com/dotnet/BenchmarkDotNet/commit/da857ad7eda77db813692d3c3678f8ad04f5af78))
-* Autoselecting amount of digits after the decimal point ([#404](https://github.com/dotnet/BenchmarkDotNet/issues/404))
\ No newline at end of file
diff --git a/docs/_changelog/header/v0.10.6.md b/docs/_changelog/header/v0.10.6.md
deleted file mode 100644
index 33948b2f80..0000000000
--- a/docs/_changelog/header/v0.10.6.md
+++ /dev/null
@@ -1,6 +0,0 @@
-* Removed buggy allocation from Engine which was spoiling the results of MemoryDiagnoser for micro benchmarks. This part of the code is now guarded with very strict integration tests, it should never happen again. We now also exclude the side effects of the Allocation Quantum. **This bug was serious, you must update to `0.10.6`** ([#439](https://github.com/dotnet/BenchmarkDotNet/issues/439))
-* Support of the `PackageTargetFallback` setting which allows to reference components that target old framework monikers (like `dotnet5.4` or `portable-net45+win8`) ([#438](https://github.com/dotnet/BenchmarkDotNet/issues/438))
-* Added `InstructionRetiredPerCycleColumn` which shows up automatically when `HardwareCounter.InstructionRetired` and `HardwareCounter.TotalCycles` are used.
-* Support benchmark classes without namespace ([#446](https://github.com/dotnet/BenchmarkDotNet/issues/446))
-* Fix problem with RPlotExporter and quoted directories in %PATH% ([#446](https://github.com/dotnet/BenchmarkDotNet/issues/446))
-* Show Windows brand version in summary
\ No newline at end of file
diff --git a/docs/_changelog/header/v0.10.7.md b/docs/_changelog/header/v0.10.7.md
deleted file mode 100644
index e58ad7f60e..0000000000
--- a/docs/_changelog/header/v0.10.7.md
+++ /dev/null
@@ -1,6 +0,0 @@
-* LINQPad support (5.22.05+) ([#66](https://github.com/dotnet/BenchmarkDotNet/issues/66), [#445](https://github.com/dotnet/BenchmarkDotNet/issues/445))
-* Benchmark filters and categories ([#248](https://github.com/dotnet/BenchmarkDotNet/issues/248))
-* Updated setup/cleanup attributes: `[GlobalSetup]`, `[GlobalCleanup]`, `[IterationSetup]`, `[IterationCleanup]` ([#270](https://github.com/dotnet/BenchmarkDotNet/issues/270), [#274](https://github.com/dotnet/BenchmarkDotNet/issues/274), [#325](https://github.com/dotnet/BenchmarkDotNet/issues/325), [#456](https://github.com/dotnet/BenchmarkDotNet/issues/456))
-* Better Value Types support ([afa803d0](https://github.com/dotnet/BenchmarkDotNet/commit/afa803d0e38c0e11864b2e4394d4a85d3801d944))
-* Building Sources on Linux: it's possible to build the solution (with unloaded F#/VB projects), run samples (for both net46/netcoreapp1.1), run unit tests (for netcoreapp1.1 only)
-* Fix minor bugs in `JsonExporter` ([#451](https://github.com/dotnet/BenchmarkDotNet/pull/451))
\ No newline at end of file
diff --git a/docs/_changelog/header/v0.10.8.md b/docs/_changelog/header/v0.10.8.md
deleted file mode 100644
index c93b24ee6e..0000000000
--- a/docs/_changelog/header/v0.10.8.md
+++ /dev/null
@@ -1,4 +0,0 @@
-* Legend for time units ([#349](https://github.com/dotnet/BenchmarkDotNet/issues/349), [#459](https://github.com/dotnet/BenchmarkDotNet/issues/459), [f14e508e](https://github.com/dotnet/BenchmarkDotNet/commit/f14e508e44b510a26cc3ec5aed30ee7843a92baf))
-* XML exporter ([#157](https://github.com/dotnet/BenchmarkDotNet/issues/157), [#452](https://github.com/dotnet/BenchmarkDotNet/pull/452), [a0148db8](https://github.com/dotnet/BenchmarkDotNet/commit/a0148db80c518a9d255f496534a8d1666be52c69))
-* .NET Framework 4.7 support ([#461](https://github.com/dotnet/BenchmarkDotNet/issues/461), [3f2b5c3c](https://github.com/dotnet/BenchmarkDotNet/commit/3f2b5c3c134c62f34f0ecf1a9c90d91ad37f2c6a), [5513873a](https://github.com/dotnet/BenchmarkDotNet/commit/5513873ac40d07583d6136e431e3b7c8cdf6c851))
-* Public API for AllocationQuantum ([#450](https://github.com/dotnet/BenchmarkDotNet/issues/450), [#462](https://github.com/dotnet/BenchmarkDotNet/pull/462), [a0148db8](https://github.com/dotnet/BenchmarkDotNet/commit/a0148db80c518a9d255f496534a8d1666be52c69))
\ No newline at end of file
diff --git a/docs/_changelog/header/v0.10.9.md b/docs/_changelog/header/v0.10.9.md
deleted file mode 100644
index 248e6b007e..0000000000
--- a/docs/_changelog/header/v0.10.9.md
+++ /dev/null
@@ -1,14 +0,0 @@
-* Migrate from custom build scripts to Cake (C# Make) ([#426](https://github.com/dotnet/BenchmarkDotNet/issues/426), [#475](https://github.com/dotnet/BenchmarkDotNet/pull/475), thanks [@Ky7m](https://github.com/Ky7m))
-* Target Setup methods for specific Benchmarks ([#469](https://github.com/dotnet/BenchmarkDotNet/issues/469), [#501](https://github.com/dotnet/BenchmarkDotNet/pull/501), thanks [@ipjohnson](https://github.com/ipjohnson))
-* Many improvements in XmlExporter ([#476](https://github.com/dotnet/BenchmarkDotNet/pull/476), [#488](https://github.com/dotnet/BenchmarkDotNet/pull/488), thanks [@Teknikaali](https://github.com/Teknikaali))
-* Add MemoryDiagnoser results to JsonExporter output ([#453](https://github.com/dotnet/BenchmarkDotNet/issues/453), [#478](https://github.com/dotnet/BenchmarkDotNet/pull/478), thanks [@Teknikaali](https://github.com/Teknikaali))
-* Detect correct version of .NET Core (+ improved presentation for information about runtime) ([#448](https://github.com/dotnet/BenchmarkDotNet/issues/448), [ed586585...ed586585](https://github.com/dotnet/BenchmarkDotNet/compare/dc6dc411b4d8703d0a1abafe64fb1e0b0a83af1f...cea199f74923c99f88f4bb4d53e37f86b10269b7))
-* Fix UnauthorizedAccessException ([#380](https://github.com/dotnet/BenchmarkDotNet/issues/380), [#390](https://github.com/dotnet/BenchmarkDotNet/issues/390), [#490](https://github.com/dotnet/BenchmarkDotNet/issues/490), [#491](https://github.com/dotnet/BenchmarkDotNet/issues/491), [8505abb5](https://github.com/dotnet/BenchmarkDotNet/commit/8505abb5416bad90cda03f4972b067f9ac44b304))
-* Fix app.config generation ([#499](https://github.com/dotnet/BenchmarkDotNet/issues/499), [dc6dc411](https://github.com/dotnet/BenchmarkDotNet/commit/dc6dc411b4d8703d0a1abafe64fb1e0b0a83af1f))
-* Fix incorrect order of IterationCleanup and Benchmark jitting ([#481](https://github.com/dotnet/BenchmarkDotNet/issues/481), [#503](https://github.com/dotnet/BenchmarkDotNet/pull/503))
-* Fix test scripts for MacOS+zsh ([1177c8](https://github.com/dotnet/BenchmarkDotNet/commit/1177c80e2dbe931439e44bb0ce2ce25cad8b9ba2))
-* Unix-related ProcessorAffinity fixes ([#474](https://github.com/dotnet/BenchmarkDotNet/issues/474), [26d44411](https://github.com/dotnet/BenchmarkDotNet/commit/26d44411ea47f28a9cc7df84b2df0ef89b2bbcf7))
-* Minor fixes in docs ([#465](https://github.com/dotnet/BenchmarkDotNet/pull/465), [#467](https://github.com/dotnet/BenchmarkDotNet/pull/467), [#473](https://github.com/dotnet/BenchmarkDotNet/pull/473), [#480](https://github.com/dotnet/BenchmarkDotNet/pull/480), [#483](https://github.com/dotnet/BenchmarkDotNet/pull/483), thanks [@mtschneiders](https://github.com/mtschneiders), [@davkean](https://github.com/davkean), [@aarondandy](https://github.com/aarondandy), [@AmadeusW](https://github.com/AmadeusW))
-* Temporary hacks for [appveyor connectivity incident](https://appveyor.statuspage.io/incidents/m2vdvw39kdk8) ([#497](https://github.com/dotnet/BenchmarkDotNet/pull/497), [#506](https://github.com/dotnet/BenchmarkDotNet/pull/506))
-* Additional warnings for incorrect Configs ([#482](https://github.com/dotnet/BenchmarkDotNet/issues/482), [eb84825f](https://github.com/dotnet/BenchmarkDotNet/commit/eb84825ff08aa5d23d2d512d4d4bde3e95ca0815))
-* Additional warnings for F# methods with spaces ([#479](https://github.com/dotnet/BenchmarkDotNet/issues/479), [3c2c8dec](https://github.com/dotnet/BenchmarkDotNet/commit/3c2c8dec28d6c570f2901001058cd9c6000e6ca2), [7ba1c809](https://github.com/dotnet/BenchmarkDotNet/commit/7ba1c809004e0b75eaa87724155480eaf623f8a9), [3ca39afe](https://github.com/dotnet/BenchmarkDotNet/commit/3ca39afe9f0d25359f9b092181beb02d57c5ad32))
\ No newline at end of file
diff --git a/docs/_changelog/header/v0.11.0.md b/docs/_changelog/header/v0.11.0.md
deleted file mode 100644
index 9760c809ba..0000000000
--- a/docs/_changelog/header/v0.11.0.md
+++ /dev/null
@@ -1,350 +0,0 @@
-This is one of the biggest releases of BenchmarkDotNet ever.
-There are so many improvements.
-We have
- new documentation,
- many performance improvements,
- Job Mutators,
- better user experience,
- correct Ctrl+C handling,
- better generic benchmarks support,
- more scenarios for passing arguments to benchmarks,
- awesome support of console arguments,
- unicode support,
- LLVM support in MonoDisassembler,
- and many-many other improvements and bug fixes!
-
-A big part of the features and bug fixes were implemented to meet the enterprise requirements of Microsoft to make it possible to port CoreCLR, CoreFX, and CoreFXLab to BenchmarkDotNet.
-
-The release would not be possible without many contributions from amazing community members. This release is a combined effort. We build BenchmarkDotNet together to make benchmarking .NET code easy and available to everyone for free!
-
-# New documentation
-
-We have many improvements in our documentation!
-The new docs include:
-
-* [DocFX](https://dotnet.github.io/docfx/) under the hood
-* Detailed changelogs which includes all commits, merged pull requests and resolved issues
-* API references
-* Code samples for main features:
- we generate it automatically based on the `BenchmarkDotNet.Samples` project;
- it means that all samples can always be compiled
- (no more samples with outdated API)
-* Better UI
-* Documentation versioning: now it's possible to look at the documentation for recent BenchmarkDotNet versions
-
-# Performance improvements
-
-BenchmarkDotNet needs to be capable of running few thousands of CoreFX and CoreCLR benchmarks in an acceptable amount of time. The code itself was already optimized so we needed architectural and design changes to meet this requirement.
-
-## Generate one executable per runtime settings
-
-To ensure that the side effects of one benchmark run does not affect another benchmark run BenchmarkDotNet generates, builds and runs every benchmark in a dedicated process. So far we were generating and building one executable per benchmark, now we generate and build one executable per runtime settings. So if you want to run ten thousands of benchmarks for .NET Core 2.1 we are going to generate and build single executable, not ten thousand. If you target multiple runtimes the build is going to be executed in parallel. Moreover, if one of the parallel builds fail it's going to be repeated in a sequential way.
-
-Previously the time to generate and build 650 benchmarks from our Samples project was **one hour**. Now it's something around **13 seconds** which means **276 X** improvement for this particular scenario. You can see the changes [here](https://github.com/dotnet/BenchmarkDotNet/issues/699).
-
-## Don't execute long operations more than once per iteration
-
-BenchmarkDotNet was designed to allow for very accurate and stable micro-benchmarking. One of the techniques that we use is manual loop unrolling. In practice, it meant that for every iteration we were executing the benchmark at least 16 times (the default `UnrollFactor` value). It was of course not desired for the very time-consuming benchmarks.
-
-So far this feature was always enabled by default and users would need to configure `UnrollFactor=1` to disable it. Now BenchmarkDotNet is going to discover such scenario and don't perform manual loop unrolling for the very time-consuming benchmarks. BenchmarkDotNet uses `Job.IterationTime` setting (the default is 0.5s) in the Pilot Experiment stage to determine how many times given benchmark should be executed per iteration.
-
-Example:
-
-```cs
-public class Program
-{
- static void Main() => BenchmarkRunner.Run();
-
- [Benchmark]
- public void Sleep1s() => Thread.Sleep(TimeSpan.FromSeconds(1));
-}
-```
-
-Time to run with the previous version: **374 seconds**. With `0.11.0` it's **27 seconds** which gives us almost **14 X** improvement. A good example of benchmarks that are going to benefit from this change are computer game benchmarks and ML.NET benchmarks. You can see the changes [here](https://github.com/dotnet/BenchmarkDotNet/pull/760) and [here](https://github.com/dotnet/BenchmarkDotNet/pull/771).
-
-## Exposing more configuration settings
-
-The default settings were configured to work well with every scenario. Before running the benchmark, BenchmarkDotNet does not know anything about it. This is why it performs many warmup iterations before running the benchmarks.
-
-When you author benchmarks and run them many times you can come up with custom settings that produce similar results but in a shorter manner of time. To allow you to do that we have exposed:
-
-* `Job.MinIterationCount` (default value is 15)
-* `Job.MaxIterationCount` (default value is 100)
-* `Job.MinWarmupIterationCount` (default value is 6)
-* `Job.MaxWarmupIterationCount` (default value is 50)
-
-# User Experience
-
-One of the biggest success factors of BenchmarkDotNet is a great user experience. The tool just works as expected and makes your life easy. We want to make it even better!
-
-## .NET Standard 2.0
-
-We have ported BenchmarkDotNet to .NET Standard 2.0 and thanks to that we were able to not only simplify our code and build process but also merge `BenchmarkDotNet.Core.dll` and `BenchmarkDotNet.Toolchains.Roslyn.dll` into `BenchmarkDotNet.dll`. We still support .NET 4.6 but we have dropped .NET Core 1.1 support. More information and full discussion can be found [here](https://github.com/dotnet/BenchmarkDotNet/pull/688).
-
-**Note:** Our `BenchmarkDotNet.Diagnostics.Windows` package which uses `EventTrace` to implement ETW-based diagnosers was also ported to .NET Standard 2.0 and you can now use all the ETW diagnosers with .NET Core on Windows. We plan to add EventPipe support and make this page fully cross-platform and Unix compatible soon.
-
-## Using complex types as benchmark arguments
-
-So far we have required the users to implement `IParam` interface to make the custom complex types work as benchmark arguments/parameters. This has changed, now the users can use any complex types as arguments and it will just work ([more](https://github.com/dotnet/BenchmarkDotNet/pull/754)).
-
-```cs
-public class Program
-{
- static void Main(string[] args) => BenchmarkRunner.Run();
-
- public IEnumerable Arguments()
- {
- yield return new Point2D(10, 200);
- }
-
- [Benchmark]
- [ArgumentsSource(nameof(Arguments))]
- public int WithArgument(Point2D point) => point.X + point.Y;
-}
-
-public class Point2D
-{
- public int X, Y;
-
- public Point2D(int x, int y)
- {
- X = x;
- Y = y;
- }
-
- public override string ToString() => $"[{X},{Y}]";
-}
-```
-
-**Note**: If you want to control what will be displayed in the summary you should override `ToString`.
-
-## If IterationSetup is provided run benchmark once per iteration
-
-When Stephen Toub says that something is [buggy](https://github.com/dotnet/BenchmarkDotNet/issues/730), it most probably is. BenchmarkDotNet performs multiple invocations of benchmark per every iteration. When we have exposed the `[IterationSetup]` attribute many users were expecting that the `IterationSetup` is going to be invoked before every benchmark execution.
-
-It was invoked before every iteration, and iteration was more than one benchmark call if the user did not configure that explicitly. We have changed that and now if you provide an `[IterationSetup]` method it is going to be executed before every iteration and iteration will invoke the benchmark just once.
-
-```cs
-public class Test
-{
- public static void Main() => BenchmarkRunner.Run();
-
- [IterationSetup]
- public void MySetup() => Console.WriteLine("MySetup");
-
- [Benchmark]
- public void MyBenchmark() => Console.WriteLine("MyBenchmark");
-}
-```
-
-Before:
-
-```log
-MySetup
-MyBenchmark
-MyBenchmark
-MyBenchmark
-MyBenchmark
-(...)
-```
-
-After:
-
-```log
-MySetup
-MyBenchmark
-MySetup
-MyBenchmark
-MySetup
-MyBenchmark
-(...)
-```
-
-**Note:** If you want to configure how many times benchmark should be invoked per iteration you can use the new `[InvocationCountAttribute]`.
-
-## Job Mutators
-
-`Job` represents a set of settings to run the benchmarks. We run every benchmark for every job defined by the user. The problem was that so far many jobs were just added to the config instead of being merged with other jobs.
-
-An example:
-
-```cs
-[ClrJob, CoreJob]
-[GcServer(true)]
-public class MyBenchmarkClass
-```
-
-Resulted in 3 jobs and 3 benchmark executions: `ClrJob`, `CoreJob` and `GcServer(true)` for current runtime.
-
-Now all Jobs and their corresponding attributes marked as mutators are going to be applied to other jobs, not just added to the config. So in this particular scenario, the benchmarks from `MyBenchmarkClass` are going to be executed for .NET with Server GC enabled and .NET Core with Server GC enabled.
-
-Mutators are great when you want to have a single, global config for all benchmarks and apply given settings only to selected types. You can find out more about mutators [here](https://github.com/dotnet/BenchmarkDotNet/pull/800).
-
-## Ctrl+C
-
-When the user:
-
-* presses `Ctrl+C`
-* presses `Ctrl+Break`
-* logs off
-* closes console window
-
-We are now going to close any existing ETW session created by BenchmarkDotNet and **restore console colors** ([read more](https://github.com/dotnet/BenchmarkDotNet/pull/761)).
-
-## Handle OutOfMemoryException more gracefully
-
-When our benchmark hits `OutOfMemoryException` we print some nice explanation:
-
-```cs
-public class Program
-{
- static void Main(string[] args) => BenchmarkRunner.Run();
-
- private List list = new List();
-
- [Benchmark]
- public void AntiPattern() => list.Add(new int[int.MaxValue / 2]);
-}
-```
-
-```log
-OutOfMemoryException!
-BenchmarkDotNet continues to run additional iterations until desired accuracy level is achieved. It's possible only if the benchmark method doesn't have any side-effects.
-If your benchmark allocates memory and keeps it alive, you are creating a memory leak.
-You should redesign your benchmark and remove the side-effects. You can use `OperationsPerInvoke`, `IterationSetup` and `IterationCleanup` to do that.
-```
-
-## Trimming long strings
-
-We used to display the values "as is" which was bad for long strings. Now the values are trimmed ([more](https://github.com/dotnet/BenchmarkDotNet/issues/748)).
-
-```cs
-public class Long
-{
- [Params("text/plain,text/html;q=0.9,application/xhtml+xml;q=0.9,application/xml;q=0.8,*/*;q=0.7")]
- public string Text;
-
- [Benchmark]
- public int HashCode() => Text.GetHashCode();
-}
-```
-
-| Method | Text |
-|--------- |--------------------- |
-| HashCode | text/(...)q=0.7 [86] |
-
-# More features
-
-## Generic benchmarks
-
-BenchmarkDotNet supports generic benchmarks, all you need to do is to tell it which types should be used as generic arguments ([read more](https://github.com/dotnet/BenchmarkDotNet/pull/758)).
-
-```cs
-[GenericTypeArguments(typeof(int))]
-[GenericTypeArguments(typeof(char))]
-public class IntroGenericTypeArguments
-{
- [Benchmark] public T Create() => Activator.CreateInstance();
-}
-```
-
-## Arguments
-
-We now support more scenarios for passing arguments to benchmarks:
-
-* passing arguments to asynchronous benchmarks ([more](https://github.com/dotnet/BenchmarkDotNet/issues/818))
-* passing generic types
-* passing arguments by reference
-* passing jagged arrays ([more](https://github.com/dotnet/BenchmarkDotNet/issues/769))
-* types with implicit cast operator to stack only types can be passed as given stack-only types to Benchmarks ([more](https://github.com/dotnet/BenchmarkDotNet/issues/774))
-
-Example:
-
-```cs
-public class WithStringToReadOnlySpan
-{
- [Benchmark]
- [Arguments("some string")]
- public void AcceptsReadOnlySpan(ReadOnlySpan notString)
-}
-```
-
-## Console Arguments
-
-`BenchmarkSwitcher` supports various console arguments ([PR](https://github.com/dotnet/BenchmarkDotNet/pull/824)), to make it work you need to pass the `args` to switcher:
-
-```cs
-class Program
-{
- static void Main(string[] args)
- => BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);
-}
-```
-
-**Note:** to get the most up-to-date info about supported console arguments run the benchmarks with `--help`.
-
-### Filter
-
-The `--filter` or just `-f` allows you to filter the benchmarks by their full name (`namespace.typeName.methodName`) using glob patterns.
-
-Examples:
-
-1. Run all benchmarks from System.Memory namespace: `-f System.Memory*`
-2. Run all benchmarks: `-f *`
-3. Run all benchmarks from ClassA and ClassB `-f *ClassA* *ClassB*`
-
-**Note**: If you would like to **join** all the results into a **single summary**, you need to use `--join`.
-
-### Categories
-
-You can also filter the benchmarks by categories:
-
-* `--anyCategories` - runs all benchmarks that belong to **any** of the provided categories
-* `--allCategories`- runs all benchmarks that belong to **all** provided categories
-
-### Diagnosers
-
-* `-m`, `--memory` - enables MemoryDiagnoser and prints memory statistics
-* `-d`, `--disassm`- enables DisassemblyDiagnoser and exports diassembly of benchmarked code
-
-### Runtimes
-
-The `--runtimes` or just `-r` allows you to run the benchmarks for selected Runtimes. Available options are: Clr, Mono, Core and CoreRT.
-
-Example: run the benchmarks for .NET and .NET Core:
-
-```log
-dotnet run -c Release -- --runtimes clr core
-```
-
-### More arguments
-
-* `-j`, `--job` (Default: Default) Dry/Short/Medium/Long or Default
-* `-e`, `--exporters` GitHub/StackOverflow/RPlot/CSV/JSON/HTML/XML
-* `-i`, `--inProcess` (Default: false) Run benchmarks in Process
-* `-a`, `--artifacts` Valid path to accessible directory
-* `--outliers` (Default: OnlyUpper) None/OnlyUpper/OnlyLower/All
-* `--affinity` Affinity mask to set for the benchmark process
-* `--allStats` (Default: false) Displays all statistics (min, max & more)
-* `--attribute` Run all methods with given attribute (applied to class or method)
-
-## Other small improvements
-
-* **Unicode support:**
- now you can enable support of Unicode symbols like `μ` or `±` with `[EncodingAttribute.Unicode]`,
- an example: BenchmarkDotNet.Samples.IntroEncoding
- (see [#735](https://github.com/dotnet/BenchmarkDotNet/pull/735))
-* **Better benchmark validation**
- (see [#693](https://github.com/dotnet/BenchmarkDotNet/pull/693), [#737](https://github.com/dotnet/BenchmarkDotNet/pull/737))
-* **Improve .NET Framework version detection**: now we support .NET Framework 4.7.2
- (see [#743](https://github.com/dotnet/BenchmarkDotNet/pull/743))
-* **OutlierModes:**
- now it's possible to control how to process outliers,
- an example @BenchmarkDotNet.Samples.IntroOutliers
- (see [#766](https://github.com/dotnet/BenchmarkDotNet/pull/766))
-* **LLVM support in MonoDisassembler**
- (see [a7426e](https://github.com/dotnet/BenchmarkDotNet/commit/a7426e84fde075503f489fdf096a95f694f77b85))
-* **Grand API renaming**
- we try not to change public API, but sometimes it's necessary because we want to get a consistent and understandable API in v1.0.0.
- (see [#787](https://github.com/dotnet/BenchmarkDotNet/issues/787))
-* **Many-many small improvements and bug fixes**
\ No newline at end of file
diff --git a/docs/_changelog/header/v0.11.1.md b/docs/_changelog/header/v0.11.1.md
deleted file mode 100644
index 97efdd26d2..0000000000
--- a/docs/_changelog/header/v0.11.1.md
+++ /dev/null
@@ -1,11 +0,0 @@
-This release includes some minor improvements and bug fixes:
-
-* Fixed `RPlotExporter` ([#852](https://github.com/dotnet/BenchmarkDotNet/issues/852), [#855](https://github.com/dotnet/BenchmarkDotNet/issues/855)).
- In v0.11.0, the plot generation was broken because of the huge renaming in [#787](https://github.com/dotnet/BenchmarkDotNet/issues/787).
-* [ArgumentsSource](xref:BenchmarkDotNet.Samples.IntroArgumentsSource) now supports additional types like
- `Type` ([#840](https://github.com/dotnet/BenchmarkDotNet/issues/840)),
- `BigInteger` ([#850](https://github.com/dotnet/BenchmarkDotNet/issues/850)),
- `DateTime` ([#853](https://github.com/dotnet/BenchmarkDotNet/issues/853)),
- and special double values like `double.NaN` ([#851](https://github.com/dotnet/BenchmarkDotNet/issues/851))
-* Generated projects ignore Directory.Build.props and Directory.Build.targets files [#854](https://github.com/dotnet/BenchmarkDotNet/pull/854)
-* Now it's possible to run benchmarks with CoreRun ([de152c](https://github.com/dotnet/BenchmarkDotNet/commit/de152c7acc71eddeaa304c846cc67e6a54ca7a0f), [#857](https://github.com/dotnet/BenchmarkDotNet/pull/857))
diff --git a/docs/_changelog/header/v0.11.2.md b/docs/_changelog/header/v0.11.2.md
deleted file mode 100644
index 2e6c435a92..0000000000
--- a/docs/_changelog/header/v0.11.2.md
+++ /dev/null
@@ -1,657 +0,0 @@
-This release includes many PRs from the Hacktoberfest.
-We are very grateful to all the contributors who spent their time to help us make BenchmarkDotNet even better!
-
-## Highlights
-
-In this release, we have many improvements in different areas:
-
-* **Diagnosers**
- * EtwProfiler (allows profiling benchmarks on Windows and exporting the data to a trace file)
-* **Execution:**
- * Comparing NuGet packages (now it's possible to compare different versions of the same package)
- * .NET Core 3.0 support
- * Deferred Execution Validator
-* **Command-line:**
- * `--list`: List of benchmarks
- * `--info`: Print environment info
- * `--runtimes`: Choosing execution runtimes (`--runtimes net472 netcoreapp2.1` will executed a benchmark on .NET 4.7.2 and .NET Core 2.1)
- * Options for number of invocations and iterations
- * Custom default settings for console argument parser
- * Case-insensitive filter
- * Benchmarking with different CoreRun instances
- * Hardware counters command-line support
-* **Exporters:**
- * Markdown output for DisassemblyDiagnoser
- * Diff view for disassembler output
- * Improved LINQPad support (colored monospaced logs)
- * Better CPU brand strings
-* **Attributes:**
- * Async `[GlobalSetup]` and `[GlobalCleanup]` support
- * Introduced `[ParamsAllValues]`
- * Selecting Baseline across Methods and Jobs
-* **Statistics:**
- * Better statistical tests (Welch's t-test and Mann-Whitney U-test)
- * ZeroMeasurementAnalyser
- * RatioColumn
-* **Other:**
- * Azure Pipelines support for internal builds
- * Many minor bug fixes
- * Improved documentation
- * New tests
-
----
-
-## Diagnosers
-
-### EtwProfiler
-
-`EtwProfiler` allows to profile the benchmarked .NET code on Windows and exports the data to a trace file which can be opened with [PerfView](https://github.com/Microsoft/perfview) or [Windows Performance Analyzer](https://learn.microsoft.com/windows-hardware/test/wpt/windows-performance-analyzer).
-
-`EtwProfiler` uses `TraceEvent` library which internally uses Event Tracing for Windows (ETW) to capture stack traces and important .NET Runtime events.
-Before the process with benchmarked code is started, EtwProfiler starts User and Kernel ETW sessions. Every session writes data to it's own file and captures different data. User session listens for the .NET Runtime events (GC, JIT etc) while the Kernel session gets CPU stacks and Hardware Counter events. After this, the process with benchmarked code is started. During the benchmark execution all the data is captured and written to a trace file. Moreover, BenchmarkDotNet Engine emits it's own events to be able to differentiate jitting, warmup, pilot and actual workload when analyzing the trace file. When the benchmarking is over, both sessions are closed and the two trace files are merged into one.
-
-
-
-You can find more details
- in the [documentation](xref:docs.etwprofiler) and
- in the [blog post](https://adamsitnik.com/ETW-Profiler/) by Adam Sitnik.
-
-* [#878](https://github.com/dotnet/BenchmarkDotNet/pull/878) EtwProfiler Diagnoser (by [@adamsitnik](https://github.com/adamsitnik))
-* [04a715](https://github.com/dotnet/BenchmarkDotNet/commit/04a71586206a822bca56f0abdacefdc2e5fc1b01) EtwProfiler Diagnoser (#878) (by [@adamsitnik](https://github.com/adamsitnik))
-
----
-
-## Execution
-
-### Comparing NuGet packages
-
-Now it's possible to compare performance of several versions of the same NuGet package.
-An example:
-
-```cs
-[Config(typeof(Config))]
-public class IntroNuGet
-{
- // Specify jobs with different versions of the same NuGet package to benchmark.
- // The NuGet versions referenced on these jobs must be greater or equal to the
- // same NuGet version referenced in this benchmark project.
- // Example: This benchmark project references Newtonsoft.Json 9.0.1
- private class Config : ManualConfig
- {
- public Config()
- {
- var baseJob = Job.MediumRun.With(CsProjCoreToolchain.Current.Value);
- Add(baseJob.WithNuGet("Newtonsoft.Json", "11.0.2").WithId("11.0.2"));
- Add(baseJob.WithNuGet("Newtonsoft.Json", "11.0.1").WithId("11.0.1"));
- Add(baseJob.WithNuGet("Newtonsoft.Json", "10.0.3").WithId("10.0.3"));
- Add(baseJob.WithNuGet("Newtonsoft.Json", "10.0.2").WithId("10.0.2"));
- Add(baseJob.WithNuGet("Newtonsoft.Json", "10.0.1").WithId("10.0.1"));
- Add(baseJob.WithNuGet("Newtonsoft.Json", "9.0.1").WithId("9.0.1"));
- }
- }
-
- [Benchmark]
- public void SerializeAnonymousObject()
- => JsonConvert.SerializeObject(
- new { hello = "world", price = 1.99, now = DateTime.UtcNow });
-}
-```
-
-See also: @BenchmarkDotNet.Samples.IntroNuGet
-
-* [#290](https://github.com/dotnet/BenchmarkDotNet/issues/290) Question: Any official way to benchmark same method between different assembly versions?
-* [#931](https://github.com/dotnet/BenchmarkDotNet/issues/931) Same NuGet version used when benchmarking different packages
-* [#922](https://github.com/dotnet/BenchmarkDotNet/pull/922) Enables benchmarking betweeen different Nuget packages (by [@Shazwazza](https://github.com/Shazwazza))
-* [#932](https://github.com/dotnet/BenchmarkDotNet/pull/932) Partition benchmark run info based on added nuget packages (by [@blairconrad](https://github.com/blairconrad))
-* [92a786](https://github.com/dotnet/BenchmarkDotNet/commit/92a7869aaa30aeacaf1da2dcc45bc65c8333ae73) Enables benchmarking betweeen different Nuget packages (#922) fixes #290 (by [@Shazwazza](https://github.com/Shazwazza))
-* [510685](https://github.com/dotnet/BenchmarkDotNet/commit/510685f48ce2baf57682aa82e18c6486989e9625) Partition benchmark run info based on added nuget packages (#932) (by [@blairconrad](https://github.com/blairconrad))
-* [cf84a4](https://github.com/dotnet/BenchmarkDotNet/commit/cf84a44d108d5bf3860129e0a2a78cace9c95626) NuGet casing fix (by [@AndreyAkinshin](https://github.com/AndreyAkinshin))
-
-### .NET Core 3.0 support
-
-Now it's possible to run benchmarks on preview versions of .NET Core 3.0.
-
-* [#874](https://github.com/dotnet/BenchmarkDotNet/issues/874) .NET Core 3.0 support (assignee: [@adamsitnik](https://github.com/adamsitnik))
-* [2e398c](https://github.com/dotnet/BenchmarkDotNet/commit/2e398c89561b3b1c89ec64b94f656ae20236efd1) detect .NET Core 3.0 and use the appropriate target framework moniker, fixes ... (by [@adamsitnik](https://github.com/adamsitnik))
-
-### Deferred Execution Validator
-
-In LINQ, execution of a query is usually [deferred](https://learn.microsoft.com/dotnet/standard/linq/deferred-execution-example) until the moment when you actually request the data. If your benchmark just returns `IEnumerable` or `IQueryable` it's not measuring the execution of the query, just the creation.
-
-This is why we decided to warn you about this issue whenever it happens:
-
-```log
-Benchmark IntroDeferredExecution.Wrong returns a deferred execution result (IEnumerable). You need to either change the method declaration to return a materialized result or consume it on your own. You can use .Consume() extension method to do that.
-```
-
-Don't worry! We are also providing you with a `Consume` extension method which can execute given `IEnumerable` or `IQueryable` and consume its results. All you need to do is to create a [`Consumer`](xref:BenchmarkDotNet.Engines.Consumer) instance, preferably store it in a field (to exclude the cost of creating Consumer from the benchmark itself) and pass it to `Consume` extension method.
-
-**Do not call `.ToArray()` because it's an expensive operation and it might dominate given benchmark!**
-
-See also: @BenchmarkDotNet.Samples.IntroDeferredExecution
-
-* [#858](https://github.com/dotnet/BenchmarkDotNet/issues/858) Should the Engine iterate over and consume IEnumerable and IQueryable results? (assignee: [@adamsitnik](https://github.com/adamsitnik))
-* [cebe2a](https://github.com/dotnet/BenchmarkDotNet/commit/cebe2a0f84fa21acb6db9613fe3a4326d635f129) Deferred Execution Validator, fixes #858 (by [@adamsitnik](https://github.com/adamsitnik))
-
----
-
-## Command-line
-
-In this release, we have tons of improvements for command-line experience.
-
-### `--list`: List of benchmarks
-
-The `--list` allows you to print all of the available benchmark names. Available options are:
-
-* `flat` - prints list of the available benchmarks: `--list flat`
-```ini
-BenchmarkDotNet.Samples.Algo_Md5VsSha256.Md5
-BenchmarkDotNet.Samples.Algo_Md5VsSha256.Sha256
-BenchmarkDotNet.Samples.IntroArguments.Benchmark
-BenchmarkDotNet.Samples.IntroArgumentsSource.SingleArgument
-BenchmarkDotNet.Samples.IntroArgumentsSource.ManyArguments
-BenchmarkDotNet.Samples.IntroArrayParam.ArrayIndexOf
-BenchmarkDotNet.Samples.IntroArrayParam.ManualIndexOf
-BenchmarkDotNet.Samples.IntroBasic.Sleep
-[...]
-```
-* `tree` - prints tree of the available benchmarks: `--list tree`
-```ini
-BenchmarkDotNet
- └─Samples
- ├─Algo_Md5VsSha256
- │ ├─Md5
- │ └─Sha256
- ├─IntroArguments
- │ └─Benchmark
- ├─IntroArgumentsSource
- │ ├─SingleArgument
- │ └─ManyArguments
- ├─IntroArrayParam
- │ ├─ArrayIndexOf
- │ └─ManualIndexOf
- ├─IntroBasic
- │ ├─Sleep
-[...]
-```
-
-The `--list` option works with the `--filter` option. Examples:
-
-* `--list flat --filter *IntroSetupCleanup*` prints:
-```ini
-BenchmarkDotNet.Samples.IntroSetupCleanupGlobal.Logic
-BenchmarkDotNet.Samples.IntroSetupCleanupIteration.Benchmark
-BenchmarkDotNet.Samples.IntroSetupCleanupTarget.BenchmarkA
-BenchmarkDotNet.Samples.IntroSetupCleanupTarget.BenchmarkB
-BenchmarkDotNet.Samples.IntroSetupCleanupTarget.BenchmarkC
-BenchmarkDotNet.Samples.IntroSetupCleanupTarget.BenchmarkD
-```
-* `--list tree --filter *IntroSetupCleanup*` prints:
-```ini
-BenchmarkDotNet
- └─Samples
- ├─IntroSetupCleanupGlobal
- │ └─Logic
- ├─IntroSetupCleanupIteration
- │ └─Benchmark
- └─IntroSetupCleanupTarget
- ├─BenchmarkA
- ├─BenchmarkB
- ├─BenchmarkC
- └─BenchmarkD
-```
-
-* [#905](https://github.com/dotnet/BenchmarkDotNet/issues/905) Implement `--list`
-* [#914](https://github.com/dotnet/BenchmarkDotNet/pull/914) Implement `--list` - fixes #905 (by [@wojtpl2](https://github.com/wojtpl2))
-* [#916](https://github.com/dotnet/BenchmarkDotNet/pull/916) Update console-args.md - add information about `--list` option (by [@wojtpl2](https://github.com/wojtpl2))
-* [330f66](https://github.com/dotnet/BenchmarkDotNet/commit/330f66c3a3d94d1369d5c0b629bbb0085d5db8eb) Implement `--list` - fixes #905 (#914) (by [@wojtpl2](https://github.com/wojtpl2))
-* [6c7521](https://github.com/dotnet/BenchmarkDotNet/commit/6c7521d4fd6776098667944321c8a65848382ae5) Update console-args.md - add information about `--list` option (#916) (by [@wojtpl2](https://github.com/wojtpl2))
-
-### `--info`: Print environment info
-
-Some of our users really like the info we print about hardware and OS.
-Now we have the `--info` console line argument which does not run the benchmarks, but simply prints the info.
-
-```ini
-BenchmarkDotNet=v0.11.1.786-nightly, OS=Windows 10.0.17134.285 (1803/April2018Update/Redstone4)
-Intel Xeon CPU E5-1650 v4 3.60GHz, 1 CPU, 12 logical and 6 physical cores
-Frequency=3507500 Hz, Resolution=285.1033 ns, Timer=TSC
-.NET Core SDK=3.0.100-alpha1-009642
- [Host] : .NET Core 3.0.0-preview1-27004-04 (CoreCLR 4.6.27003.04, CoreFX 4.6.27003.02), 64bit RyuJIT
-```
-
-* [#904](https://github.com/dotnet/BenchmarkDotNet/issues/904) Implement `--info`
-* [#907](https://github.com/dotnet/BenchmarkDotNet/pull/907) fixes #904 Implement `--info` (by [@lahma](https://github.com/lahma))
-* [4be28d](https://github.com/dotnet/BenchmarkDotNet/commit/4be28d25fa9ab79ca194c615783148042738bdad) fixes #904 Implement `--info` (#907) (by [@lahma](https://github.com/lahma))
-
-### `--runtimes`: Choosing execution runtimes
-
-The `--runtimes` or just `-r` allows you to run the benchmarks for selected Runtimes. Available options are: Mono, CoreRT, Core, Clr net46, net461, net462, net47, net471, net472, netcoreapp2.0, netcoreapp2.1, netcoreapp2.2, netcoreapp3.0.
-
-Example: run the benchmarks for .NET 4.7.2 and .NET Core 2.1:
-
-```log
-dotnet run -c Release -- --runtimes net472 netcoreapp2.1
-```
-
-* [#913](https://github.com/dotnet/BenchmarkDotNet/pull/913) .NET Core Toolchains improvements (by [@adamsitnik](https://github.com/adamsitnik))
-* [0f721c](https://github.com/dotnet/BenchmarkDotNet/commit/0f721c8e0e100fc951a54b6045eb7b58c55c2a1f) make it possible to specify runtimes using explicit tfms like net472 or netco... (by [@adamsitnik](https://github.com/adamsitnik))
-* [1c581e](https://github.com/dotnet/BenchmarkDotNet/commit/1c581e5bf5b4ba9f40d113ae09e0731a60523a60) .NET Core Toolchains improvements (#913) (by [@adamsitnik](https://github.com/adamsitnik))
-
-### Options for number of invocations and iterations
-
-* `--launchCount` - how many times we should launch process with target benchmark. The default is 1.
-* `--warmupCount` - how many warmup iterations should be performed. If you set it, the minWarmupCount and maxWarmupCount are ignored. By default calculated by the heuristic.
-* `--minWarmupCount` - minimum count of warmup iterations that should be performed. The default is 6.
-* `--maxWarmupCount` - maximum count of warmup iterations that should be performed. The default is 50.
-* `--iterationTime` - desired time of execution of an iteration. Used by Pilot stage to estimate the number of invocations per iteration. 500ms by default.
-* `--iterationCount` - how many target iterations should be performed. By default calculated by the heuristic.
-* `--minIterationCount` - minimum number of iterations to run. The default is 15.
-* `--maxIterationCount` - maximum number of iterations to run. The default is 100.
-* `--invocationCount` - invocation count in a single iteration. By default calculated by the heuristic.
-* `--unrollFactor` - how many times the benchmark method will be invoked per one iteration of a generated loop. 16 by default
-* `--runOncePerIteration` - run the benchmark exactly once per iteration. False by default.
-
-Example: run single warmup iteration, from 9 to 12 actual workload iterations.
-
-```log
-dotnet run -c Release -- --warmupCount 1 --minIterationCount 9 --maxIterationCount 12
-```
-
-* [#902](https://github.com/dotnet/BenchmarkDotNet/pull/902) More command line args (by [@adamsitnik](https://github.com/adamsitnik))
-* [ba0d22](https://github.com/dotnet/BenchmarkDotNet/commit/ba0d22b41fd25022e3a945fe5ef1ae8aea697cf7) allow to configure the number of invocations and iterations from command line (by [@adamsitnik](https://github.com/adamsitnik))
-
-### Custom default settings for console argument parser
-
-If you want to have a possibility to specify custom default Job settings programmatically and optionally overwrite it with console line arguments, then you should create a global config with single job marked as `.AsDefault` and pass it to `BenchmarkSwitcher` together with the console line arguments.
-
-Example: run single warmup iteration by default.
-
-```cs
-static void Main(string[] args)
- => BenchmarkSwitcher
- .FromAssembly(typeof(Program).Assembly)
- .Run(args, GetGlobalConfig());
-
-static IConfig GetGlobalConfig()
- => DefaultConfig.Instance
- .With(Job.Default
- .WithWarmupCount(1)
- .AsDefault()); // the KEY to get it working
-```
-
-Now, the default settings are: `WarmupCount=1` but you might still overwrite it from console args like in the example below:
-
-```log
-dotnet run -c Release -- --warmupCount 2
-```
-
-### Case-insensitive filter
-
-The `--filter` or just `-f` allows you to filter the benchmarks by their full name (`namespace.typeName.methodName`) using glob patterns.
-
-Examples:
-
-1. Run all benchmarks from System.Memory namespace: `-f System.Memory*`
-2. Run all benchmarks: `-f *`
-3. Run all benchmarks from ClassA and ClassB `-f *ClassA* *ClassB*`
-
-Now this filter expression is case-insensitive.
-
-* [#864](https://github.com/dotnet/BenchmarkDotNet/issues/864) Make the filter case insensitive (assignee: [@adamsitnik](https://github.com/adamsitnik))
-* [106777](https://github.com/dotnet/BenchmarkDotNet/commit/106777f7f575a8535f16292f1de80e8ffba2853a) make the filter case insensitive invariant culture, fixes #864 (by [@adamsitnik](https://github.com/adamsitnik))
-
-### Benchmarking with different CoreRun instances
-
-CoreRun is a simpler version of `dotnet run`, used for developing CoreCLR and CoreFX.
-
-Typically when working on the performance of .NET Core a developer has more than 1 copy of CoreRun.
-Example: CoreRun before my changes, and after my changes.
-This change allows to simply run same benchmark for few different CoreRuns to compare the perf in easy way.
-
-Sample usage:
-
-```log
-dotnet run -c Release -f netcoreapp2.1 -- -f *Empty.method --job dry --coreRun
-C:\Projects\coreclr_upstream\bin\tests\Windows_NT.x64.Release\Tests\Core_Root\CoreRun.exe
-C:\Projects\coreclr_upstream\bin\tests\Windows_NT.x64.Release\Tests\Core_Root_beforeMyChanges\CoreRun.exe
-```
-
-Sample output:
-
-
-
-* [#925](https://github.com/dotnet/BenchmarkDotNet/issues/925) Make it possible to run the benchmark with multiple CoreRun.exe (assignee: [@adamsitnik](https://github.com/adamsitnik))
-* [901616](https://github.com/dotnet/BenchmarkDotNet/commit/90161654725efd5639e0190638a3383d6a49e34c) when user provides CoreRun path and runtime in explicit way, we should use th... (by [@adamsitnik](https://github.com/adamsitnik))
-* [46bebf](https://github.com/dotnet/BenchmarkDotNet/commit/46bebf1497d4e9314c6dfd2d4e10df81332aa4fa) allow the users to run the same benchmarks using few different CoreRun.exe, f... (by [@adamsitnik](https://github.com/adamsitnik))
-
-### Hardware counters command-line support
-
-```log
---counters CacheMisses+InstructionRetired
-```
-
-* [1e3df7](https://github.com/dotnet/BenchmarkDotNet/commit/1e3df74b2f927f541bed723f65c2d571fa850c53) make it possible to specify hardware counters from command line (by [@adamsitnik](https://github.com/adamsitnik))
-* [a4f91a](https://github.com/dotnet/BenchmarkDotNet/commit/a4f91a392675e4851a785095af162b977d249ba3) better handling of edge cases for parsing hardware counters from the console ... (by [@adamsitnik](https://github.com/adamsitnik))
-
----
-
-## Exporters
-
-### Markdown output for DisassemblyDiagnoser
-
-Now `DisassemblyDiagnoser` generates markdown version of the assembly listing.
-
-* [#560](https://github.com/dotnet/BenchmarkDotNet/issues/560) Suggestion: markdown output for DisassemblyDiagnoser (assignee: [@adamsitnik](https://github.com/adamsitnik))
-* [1e6235](https://github.com/dotnet/BenchmarkDotNet/commit/1e62355f209a25c7a33f9ab7e7e03b0afe7d851f) github markdown exporter for Disassembler, fixes #560 (by [@adamsitnik](https://github.com/adamsitnik))
-
-### Diff view for disassembler output
-
-Now we have `PrettyGithubMarkdownDiffDisassemblyExporter` which can generates
- nice diffs between assembly listings.
-This mode can be activated via the `--disasmDiff` command line argument or
- the `printDiff: true` argument of `DisassemblyDiagnoserConfig`.
-An output example (Diff between SumLocal and SumField on .NET Core 2.1.4 (CoreCLR 4.6.26814.03, CoreFX 4.6.26814.02), 64bit RyuJIT)
-
-```diff
--; BenchmarkDotNet.Samples.IntroDisassemblyRyuJit.SumLocal()
-- var local = field; // we use local variable that points to the field
-- ^^^^^^^^^^^^^^^^^^
-- mov rax,qword ptr [rcx+8]
-+; BenchmarkDotNet.Samples.IntroDisassemblyRyuJit.SumField()
- int sum = 0;
- ^^^^^^^^^^^^
-- xor edx,edx
-- for (int i = 0; i < local.Length; i++)
-+ xor eax,eax
-+ for (int i = 0; i < field.Length; i++)
- ^^^^^^^^^
-- xor ecx,ecx
-- for (int i = 0; i < local.Length; i++)
-+ xor edx,edx
-+ for (int i = 0; i < field.Length; i++)
- ^^^^^^^^^^^^^^^^
-- mov r8d,dword ptr [rax+8]
-- test r8d,r8d
-+ mov rcx,qword ptr [rcx+8]
-+ cmp dword ptr [rcx+8],0
- jle M00_L01
-- sum += local[i];
-+ sum += field[i];
- ^^^^^^^^^^^^^^^^
- M00_L00:
-- movsxd r9,ecx
-- add edx,dword ptr [rax+r9*4+10h]
-- for (int i = 0; i < local.Length; i++)
-+ mov r8,rcx
-+ cmp edx,dword ptr [r8+8]
-+ jae 00007ff9`0c412c1f
-+ movsxd r9,edx
-+ add eax,dword ptr [r8+r9*4+10h]
-+ for (int i = 0; i < field.Length; i++)
- ^^^
-- inc ecx
-- cmp r8d,ecx
-+ inc edx
-+ cmp dword ptr [rcx+8],edx
- jg M00_L00
- return sum;
- ^^^^^^^^^^^
- M00_L01:
-- mov eax,edx
--; Total bytes of code 34
-+ add rsp,28h
-+; Total bytes of code 42
-```
-
-* [#544](https://github.com/dotnet/BenchmarkDotNet/issues/544) Diff view for disassembler output (assignee: [@wojtpl2](https://github.com/wojtpl2))
-* [#927](https://github.com/dotnet/BenchmarkDotNet/pull/927) Improve Disassembly exporters and add PrettyGithubMarkdownDiffDisassemblyExporter (by [@wojtpl2](https://github.com/wojtpl2))
-* [#936](https://github.com/dotnet/BenchmarkDotNet/issues/936) Producing the asm diff reports on demand
-* [#937](https://github.com/dotnet/BenchmarkDotNet/pull/937) Producing the asm diff reports on demand - fix for #936 (by [@wojtpl2](https://github.com/wojtpl2))
-* [1903a1](https://github.com/dotnet/BenchmarkDotNet/commit/1903a1bd96d207ed51611d1dc546920f5bfb0d86) Improve Disassembly exporters and add PrettyGithubMarkdownDiffDisassemblyExpo... (by [@wojtpl2](https://github.com/wojtpl2))
-* [dd103b](https://github.com/dotnet/BenchmarkDotNet/commit/dd103b60a4af0d3b9e7efb523c0923e7cbd8b62d) Producing the asm diff reports on demand - fixes #936 (#937) (by [@wojtpl2](https://github.com/wojtpl2))
-
-### Improved LINQPad support
-
-If you run BenchmarkDotNet v0.11.2+ in LINQPad, your logs will be colored and monospaced:
-
-
-
-* [#447](https://github.com/dotnet/BenchmarkDotNet/issues/447) Implement ColoredLogger for LinqPad
-* [#903](https://github.com/dotnet/BenchmarkDotNet/pull/903) Add LINQPad logging (by [@bgrainger](https://github.com/bgrainger))
-* [#915](https://github.com/dotnet/BenchmarkDotNet/pull/915) Use a monospaced font for LINQPad logging output (by [@bgrainger](https://github.com/bgrainger))
-* [c3b609](https://github.com/dotnet/BenchmarkDotNet/commit/c3b6095b933b132c1773ced3af126f282465b980) Add LINQPad logging (#903) (by [@bgrainger](https://github.com/bgrainger))
-* [10fdd0](https://github.com/dotnet/BenchmarkDotNet/commit/10fdd0998b46c4358f6fa38aacc21e57a7730724) Use a monospaced font for LINQPad logging output. (#915) (by [@bgrainger](https://github.com/bgrainger))
-
-### Better CPU brand strings
-
-We did a lot of changes which improve the presentation form of the CPU brand string.
-Here is an example of such string in the previous version of BenchmarkDotNet:
-
-```log
-AMD Ryzen 7 2700X Eight-Core Processor (Max: 4.10GHz), 1 CPU, 16 logical and 8 physical cores
-```
-
-Now it becomes:
-
-```log
-AMD Ryzen 7 2700X 4.10GHz, 1 CPU, 16 logical and 8 physical cores
-```
-
-As you can see, "Eight-Core Processor" was removed (because we already have "8 physical cores");
- "(Max: 4.10GHz)" was replaced by 4.10GHz (because the original CPU brand string doesn't contain the nominal frequency).
-
-* [#859](https://github.com/dotnet/BenchmarkDotNet/issues/859) Strange max frequency values on Windows (assignee: [@Rizzen](https://github.com/Rizzen))
-* [#909](https://github.com/dotnet/BenchmarkDotNet/issues/909) Improve CPU Brand Strings without frequency
-* [#860](https://github.com/dotnet/BenchmarkDotNet/pull/860) Fix strange CPU Frequency values (by [@Rizzen](https://github.com/Rizzen))
-* [#910](https://github.com/dotnet/BenchmarkDotNet/pull/910) Simplify AMD Ryzen CPU brand info (by [@lahma](https://github.com/lahma))
-* [a78b38](https://github.com/dotnet/BenchmarkDotNet/commit/a78b38b0e89d04ad3fe8934162c7adb42f81eabe) Fix strange CPU Frequency values (#860) (by [@Rizzen](https://github.com/Rizzen))
-* [5df1e6](https://github.com/dotnet/BenchmarkDotNet/commit/5df1e6434b791eb5da6f6ef42505fc6a94ebd008) Simplify AMD Ryzen CPU brand info (#910) (by [@lahma](https://github.com/lahma))
-
----
-
-## Attributes
-
-### Async GlobalSetup and GlobalCleanup
-
-Now GlobalSetup and GlobalCleanup methods can be async.
-
-See also: docs.setup-and-cleanup
-
-* [#521](https://github.com/dotnet/BenchmarkDotNet/issues/521) Support async Setup/Cleanup
-* [#892](https://github.com/dotnet/BenchmarkDotNet/pull/892) Added support for async GlobalSetup. (by [@dlemstra](https://github.com/dlemstra))
-* [#923](https://github.com/dotnet/BenchmarkDotNet/pull/923) async GlobalCleanup support (by [@dlemstra](https://github.com/dlemstra))
-* [#926](https://github.com/dotnet/BenchmarkDotNet/pull/926) Added support for async GlobalCleanup. (by [@dlemstra](https://github.com/dlemstra))
-* [e0f7a6](https://github.com/dotnet/BenchmarkDotNet/commit/e0f7a67681860ead87cef76fa0db349460b34eb0) Added support for async GlobalSetup. (#892) (by [@dlemstra](https://github.com/dlemstra))
-* [a971a4](https://github.com/dotnet/BenchmarkDotNet/commit/a971a435ce6e6ca25d246e5e2cd56c5b2cf4739d) async GlobalCleanup support (#923) (by [@dlemstra](https://github.com/dlemstra))
-* [e4c7b8](https://github.com/dotnet/BenchmarkDotNet/commit/e4c7b852e5593bb280881e28ece51d26687c5ba9) Added support for async GlobalCleanup. (#926), fixes #521 (by [@dlemstra](https://github.com/dlemstra))
-
-### Introduced ParamsAllValues
-
-If you want to use all possible values of an `enum` or another type with a small number of values, you can use the [`[ParamsAllValues]`](xref:BenchmarkDotNet.Attributes.ParamsAllValuesAttribute) attribute, instead of listing all the values by hand. The types supported by the attribute are:
-
-* `bool`
-* any `enum` that is not marked with `[Flags]`
-* `Nullable`, where `T` is an enum or boolean
-
-An example:
-
-```cs
-public class IntroParamsAllValues
-{
- public enum CustomEnum
- {
- A,
- BB,
- CCC
- }
-
- [ParamsAllValues]
- public CustomEnum E { get; set; }
-
- [ParamsAllValues]
- public bool? B { get; set; }
-
- [Benchmark]
- public void Benchmark()
- {
- Thread.Sleep(
- E.ToString().Length * 100 +
- (B == true ? 20 : B == false ? 10 : 0));
- }
-}
-```
-
-Output:
-
-```markdown
- Method | E | B | Mean | Error |
----------- |---- |------ |---------:|------:|
- Benchmark | A | ? | 101.9 ms | NA |
- Benchmark | A | False | 111.9 ms | NA |
- Benchmark | A | True | 122.3 ms | NA |
- Benchmark | BB | ? | 201.5 ms | NA |
- Benchmark | BB | False | 211.8 ms | NA |
- Benchmark | BB | True | 221.4 ms | NA |
- Benchmark | CCC | ? | 301.8 ms | NA |
- Benchmark | CCC | False | 312.3 ms | NA |
- Benchmark | CCC | True | 322.2 ms | NA |
-
-// * Legends *
- E : Value of the 'E' parameter
- B : Value of the 'B' parameter
-```
-
-* [#658](https://github.com/dotnet/BenchmarkDotNet/issues/658) [Params] for enums should include all values by default
-* [#908](https://github.com/dotnet/BenchmarkDotNet/pull/908) Added [ParamsAllValues] (by [@gsomix](https://github.com/gsomix))
-* [922dff](https://github.com/dotnet/BenchmarkDotNet/commit/922dfff62d6cf6fd808865e705a09eee63690a2e) Added [ParamsAllValues] (#908), fixes #658 (by [@gsomix](https://github.com/gsomix))
-* [846d08](https://github.com/dotnet/BenchmarkDotNet/commit/846d0863b6456d3e1e6ccab06d8e61c5cd064194) ParamsAllValuesValidator fixes (by [@AndreyAkinshin](https://github.com/AndreyAkinshin))
-
-### Selecting Baseline across Methods and Jobs
-
-Now it's possible to mark a method and a job as baselines at the same time:
-
-```cs
-public class TheBaselines
-{
- [Benchmark(Baseline = true)]
- public void Sleep100ms() => Thread.Sleep(TimeSpan.FromMilliseconds(100));
-
- [Benchmark]
- public void Sleep50ms() => Thread.Sleep(TimeSpan.FromMilliseconds(50));
-}
-
-static void Main(string[] args)
- => BenchmarkSwitcher
- .FromTypes(new[] { typeof(TheBaselines) })
- .Run(args,
- DefaultConfig.Instance
- .With(Job.Core.AsBaseline())
- .With(Job.Clr.WithId("CLR 4.7.2")));
-```
-
-* [#880](https://github.com/dotnet/BenchmarkDotNet/issues/880) Select Baseline across Methods and Jobs (assignee: [@AndreyAkinshin](https://github.com/AndreyAkinshin))
-* [21a007](https://github.com/dotnet/BenchmarkDotNet/commit/21a0073cc8b486f41b2e84deafacd00a1303013a) Support method-job baseline pairs, fixes #880 (by [@AndreyAkinshin](https://github.com/AndreyAkinshin))
-
----
-
-## Statistics
-
-### Statistical tests
-
-In this release, statistical testing was significantly improved.
-Now it's possible to compare all benchmarks against baseline with the help
- of Welch's t-test or Mann–Whitney U test.
-
-An example:
-
-```cs
-[StatisticalTestColumn(
- StatisticalTestKind.Welch, ThresholdUnit.Microseconds, 1, true)]
-[StatisticalTestColumn(
- StatisticalTestKind.MannWhitney, ThresholdUnit.Microseconds, 1, true)]
-[StatisticalTestColumn(
- StatisticalTestKind.Welch, ThresholdUnit.Ratio, 0.03, true)]
-[StatisticalTestColumn(
- StatisticalTestKind.MannWhitney, ThresholdUnit.Ratio, 0.03, true)]
-[SimpleJob(warmupCount: 0, targetCount: 5)]
-public class IntroStatisticalTesting
-{
- [Benchmark] public void Sleep50() => Thread.Sleep(50);
- [Benchmark] public void Sleep97() => Thread.Sleep(97);
- [Benchmark] public void Sleep99() => Thread.Sleep(99);
- [Benchmark(Baseline = true)] public void Sleep100() => Thread.Sleep(100);
- [Benchmark] public void Sleep101() => Thread.Sleep(101);
- [Benchmark] public void Sleep103() => Thread.Sleep(103);
- [Benchmark] public void Sleep150() => Thread.Sleep(150);
-}
-```
-
-Output:
-
-| Method | Mean | Error | StdDev | Ratio | Welch(1us)/p-values | Welch(3%)/p-values | MannWhitney(1us)/p-values | MannWhitney(3%)/p-values |
-|--------- |----------:|----------:|----------:|------:|---------------------- |---------------------- |-------------------------- |------------------------- |
-| Sleep50 | 53.13 ms | 0.5901 ms | 0.1532 ms | 0.51 | Faster: 1.0000/0.0000 | Faster: 1.0000/0.0000 | Faster: 1.0000/0.0040 | Faster: 1.0000/0.0040 |
-| Sleep97 | 100.07 ms | 0.9093 ms | 0.2361 ms | 0.97 | Faster: 1.0000/0.0000 | Same: 1.0000/0.1290 | Faster: 1.0000/0.0040 | Same: 1.0000/0.1111 |
-| Sleep99 | 102.23 ms | 2.4462 ms | 0.6353 ms | 0.99 | Faster: 0.9928/0.0072 | Same: 1.0000/0.9994 | Faster: 0.9960/0.0079 | Same: 1.0000/1.0000 |
-| Sleep100 | 103.34 ms | 0.8180 ms | 0.2124 ms | 1.00 | Base: 0.5029/0.5029 | Base: 1.0000/1.0000 | Base: 0.7262/0.7262 | Base: 1.0000/1.0000 |
-| Sleep101 | 103.73 ms | 2.1591 ms | 0.5607 ms | 1.00 | Same: 0.1041/0.8969 | Same: 0.9999/1.0000 | Same: 0.1111/0.9246 | Same: 1.0000/1.0000 |
-| Sleep103 | 106.21 ms | 1.2511 ms | 0.3249 ms | 1.03 | Slower: 0.0000/1.0000 | Same: 0.9447/1.0000 | Slower: 0.0040/1.0000 | Same: 0.9246/1.0000 |
-| Sleep150 | 153.16 ms | 3.4929 ms | 0.9071 ms | 1.48 | Slower: 0.0000/1.0000 | Slower: 0.0000/1.0000 | Slower: 0.0040/1.0000 | Slower: 0.0040/1.0000 |
-
-```log
-// * Legends *
- Mean : Arithmetic mean of all measurements
- Error : Half of 99.9% confidence interval
- StdDev : Standard deviation of all measurements
- Ratio : Mean of the ratio distribution ([Current]/[Baseline])
- Welch(1us)/p-values : Welch-based TOST equivalence test with 1 us threshold. Format: 'Result: p-value(Slower)|p-value(Faster)'
- Welch(3%)/p-values : Welch-based TOST equivalence test with 3% threshold. Format: 'Result: p-value(Slower)|p-value(Faster)'
- MannWhitney(1us)/p-values : MannWhitney-based TOST equivalence test with 1 us threshold. Format: 'Result: p-value(Slower)|p-value(Faster)'
- MannWhitney(3%)/p-values : MannWhitney-based TOST equivalence test with 3% threshold. Format: 'Result: p-value(Slower)|p-value(Faster)'
- 1 ms : 1 Millisecond (0.001 sec)
-```
-
-The statistical testing is a work-in-progress feature.
-In future versions of BenchmarkDotNet, we are planning to improve API, fill missed docs, and introduce more parameters for customization.
-
-See also: @BenchmarkDotNet.Samples.IntroStatisticalTesting
-
-* [60eca0](https://github.com/dotnet/BenchmarkDotNet/commit/60eca005326970202a33891e5aecd2ef6b7e4cd0) Threshold API for WelchTTest; Improve Student accuracy for small n (by [@AndreyAkinshin](https://github.com/AndreyAkinshin))
-* [05cc8d](https://github.com/dotnet/BenchmarkDotNet/commit/05cc8d15ef88e382bbb1827d766d7275c3e42abd) Statistical testing improvements (by [@AndreyAkinshin](https://github.com/AndreyAkinshin))
-
-### ZeroMeasurementAnalyser
-
-When you have an empty benchmark like this
-
-```cs
-[Benchmark]
-public void Empty() { }
-```
-
-The expected duration of this method is zero.
-However, you can get the mean value like `0.0023ns` because of the natural noise.
-It's a pretty confusing result for many developers.
-Since v0.11.2, we have `ZeroMeasurementAnalyser` which warn you about such methods.
-By default, BenchmarkDotNet automatically evaluate overhead.
-In this case, `ZeroMeasurementAnalyser` runs Welch's t-test and compare actual and overhead measurements.
-If the overhead evaluation is disabled, it runs one-sample Student's t-test against a half of CPU cycle.
-
-* [#906](https://github.com/dotnet/BenchmarkDotNet/pull/906) Zero measurement analyser (by [@Rizzen](https://github.com/Rizzen))
-* [48d193](https://github.com/dotnet/BenchmarkDotNet/commit/48d193e30c780eb43e65b21f892c48db5dab6f6b) Zero measurement analyser (#906) (by [@Rizzen](https://github.com/Rizzen))
-
-### RatioColumn
-
-The `Ratio` column was formerly known as `Scaled`.
-The old title was a source of misunderstanding and confusion because
- many developers interpreted it as the ratio of means (e.g., `50.46`/`100.39` for `Time50`).
-The ratio of distribution means and the mean of the ratio distribution are pretty close to each other in most cases,
- but they are not equal.
-
-See also:
- @BenchmarkDotNet.Samples.IntroBenchmarkBaseline,
- @BenchmarkDotNet.Samples.IntroRatioSD,
- @docs.baselines.
-
-* [4e64c9](https://github.com/dotnet/BenchmarkDotNet/commit/4e64c94cfe7b49bbdc06aabb6ee1f262bd370862) Ratio/RatioSD columns (by [@AndreyAkinshin](https://github.com/AndreyAkinshin))
-
diff --git a/docs/_changelog/header/v0.11.3.md b/docs/_changelog/header/v0.11.3.md
deleted file mode 100644
index f3be5d81af..0000000000
--- a/docs/_changelog/header/v0.11.3.md
+++ /dev/null
@@ -1,99 +0,0 @@
-
-
-## Highlights
-
-This release is focused mainly on bug fixes that were affecting user experience. But don't worry, we have some new features too!
-
-* **Diagnosers**
- * ConcurrencyVisualizerProfiler (allows profiling benchmarks on Windows and exporting the data to a trace file which can be opened with Concurrency Visualizer)
-* **Command-line:**
- * `--stopOnFirstError`: Stops the benchmarks execution on first error. [#947](https://github.com/dotnet/BenchmarkDotNet/pull/947)
- * `--statisticalTest`: Performs a Mann–Whitney Statistical Test for identifying regressions and improvements. [#960](https://github.com/dotnet/BenchmarkDotNet/pull/960)
-* **Bug fixes:**
- * Dry mode doesn't work because of the ZeroMeasurementHelper [#943](https://github.com/dotnet/BenchmarkDotNet/issues/943)
- * MannWhitneyTest fails when comparing statistics of different sample size [#948](https://github.com/dotnet/BenchmarkDotNet/issues/948) and [#950](https://github.com/dotnet/BenchmarkDotNet/issues/950)
- * Improve the dynamic loading of Diagnostics package [#955](https://github.com/dotnet/BenchmarkDotNet/issues/955)
- * BenchmarkRunner.RunUrl throws NRE when Config is not provided [#961](https://github.com/dotnet/BenchmarkDotNet/issues/961)
- * Don't require the users to do manual installation of TraceEvent when using Diagnostics package [#962](https://github.com/dotnet/BenchmarkDotNet/issues/962)
- * Stop benchmark after closing application + Flush log after stopping benchmark [#963](https://github.com/dotnet/BenchmarkDotNet/issues/963)
-
----
-
-## Diagnosers
-
-### ConcurrencyVisualizerProfiler
-
-`ConcurrencyVisualizerProfiler` allows to profile the benchmarked .NET code on Windows and exports the data to a CVTrace file which can be opened with [Concurrency Visualizer](https://learn.microsoft.com/visualstudio/profiling/concurrency-visualizer).
-
-`ConcurrencyVisualizerProfiler` uses `EtwProfiler` to get a `.etl` file which still can be opened with [PerfView](https://github.com/Microsoft/perfview) or [Windows Performance Analyzer](https://learn.microsoft.com/windows-hardware/test/wpt/windows-performance-analyzer). The difference is that it also enables all Task and Thread related ETW Providers and exports a simple `xml` which can be opened with Visual Studio if you install [Concurrency Visualizer plugin](https://marketplace.visualstudio.com/items?itemName=Diagnostics.ConcurrencyVisualizer2017)
-
-
-
-
-
-
-
-
-* [#964](https://github.com/dotnet/BenchmarkDotNet/issues/964) Concurrency Visualizer Profiler Diagnoser (by [@adamsitnik](https://github.com/adamsitnik))
-* [dfb3c89](https://github.com/dotnet/BenchmarkDotNet/commit/dfb3c8912505799a76b0eb5ae0c082bb44599fa7) ConcurrencyVisualizerProfiler diagnoser! (by [@adamsitnik](https://github.com/adamsitnik))
-
----
-
-## Command-line
-
-In this release, we have some new command-line arguments!
-
-### `--stopOnFirstError`: Stops the benchmarks execution on first error
-
-When provided, BenchmarkDotNet is going to stop the benchmarks execution on first error.
-
-* [#947](https://github.com/dotnet/BenchmarkDotNet/pull/947) Add option to stop running when the first benchmark fails (by [@wojtpl2](https://github.com/wojtpl2))
-
-### `--statisticalTest`: Statistical Test
-
-To perform a Mann–Whitney U Test and display the results in a dedicated column you need to provide the Threshold via
-`--statisticalTest`. Examples: 5%, 10ms, 100ns, 1s.
-
-Example: run Mann–Whitney U test with relative ratio of 1% for all benchmarks for .NET 4.6 (base), .NET Core 2.0 and .NET Core 2.1.
-
-```cs
-class Program
-{
- static void Main(string[] args) => BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);
-}
-
-public class MySample
-{
- [Benchmark]
- public void Sleep()
- {
-#if NETFRAMEWORK
- Thread.Sleep(50);
-#elif NETCOREAPP2_0
- Thread.Sleep(45);
-#elif NETCOREAPP2_1
- Thread.Sleep(55);
-#endif
- }
-
- [Benchmark]
- public void Same() => Thread.Sleep(50);
-}
-```
-
-```log
-dotnet run -c Release -f netcoreapp2.1 --filter * --runtimes net46 netcoreapp2.0 netcoreapp2.1 --statisticalTest 1%
-```
-
-**Note:** .NET 4.6 will be our baseline because it was provided as first on the runtimes list.
-
-
-| Method | Runtime | Toolchain | Mean | Error | StdDev | Ratio | MannWhitney(1%) |
-|------- |-------- |-------------- |---------:|----------:|----------:|------:|---------------- |
-| Sleep | Clr | net46 | 50.51 ms | 0.1833 ms | 0.1714 ms | 1.00 | Base |
-| Sleep | Core | netcoreapp2.0 | 45.53 ms | 0.1262 ms | 0.1181 ms | 0.90 | Faster |
-| Sleep | Core | netcoreapp2.1 | 55.50 ms | 0.1217 ms | 0.1138 ms | 1.10 | Slower |
-| | | | | | | | |
-| Same | Clr | net46 | 50.47 ms | 0.1795 ms | 0.1679 ms | 1.00 | Base |
-| Same | Core | netcoreapp2.0 | 50.55 ms | 0.1873 ms | 0.1752 ms | 1.00 | Same |
-| Same | Core | netcoreapp2.1 | 50.55 ms | 0.2162 ms | 0.2022 ms | 1.00 | Same |
diff --git a/docs/_changelog/header/v0.11.4.md b/docs/_changelog/header/v0.11.4.md
deleted file mode 100644
index 3cf8b79f55..0000000000
--- a/docs/_changelog/header/v0.11.4.md
+++ /dev/null
@@ -1,114 +0,0 @@
-It's been few months since our last release, but we have been working hard and have some new features for you!
-
-## Highlights
-
-* **Features**
- * **BenchmarkDotNet as a global tool**: a new global tool which allows you to run benchmarks from given library.
- Now you can run benchmarks from the command line via `dotnet benchmark`.
- You can find more information about it in the documentation
- [#1006](https://github.com/dotnet/BenchmarkDotNet/pull/1006)
- [#213](https://github.com/dotnet/BenchmarkDotNet/issues/213)
- * **InProcessEmitToolchain**: new, full-featured InProcess toolchain which allows executing benchmarks in the current process
- without spawning additional process per benchmark.
- It supports `[Arguments]`, `[ArgumentsSource]`, passing the arguments by `out`, `ref` and returning stack-only types like `Span`.
- [#919](https://github.com/dotnet/BenchmarkDotNet/issues/919),
- [#921](https://github.com/dotnet/BenchmarkDotNet/pull/921)
- [#843](https://github.com/dotnet/BenchmarkDotNet/issues/843)
- * **ARM support**: BenchmarkDotNet supports now ARM and ARM64.
- [#780](https://github.com/dotnet/BenchmarkDotNet/issues/780),
- [#979](https://github.com/dotnet/BenchmarkDotNet/pull/979)
- [#385](https://github.com/dotnet/BenchmarkDotNet/issues/385)
- * **Mono AOT support**: a new toolchain which allows running benchmarks using AOT version of Mono
- [#940](https://github.com/dotnet/BenchmarkDotNet/pull/940)
- * **NuGet symbol server support**: BenchmarkDotNet publishes now the symbols to NuGet.org symbol server and you can easily debug it.
- [#967](https://github.com/dotnet/BenchmarkDotNet/issues/967)
- [#968](https://github.com/dotnet/BenchmarkDotNet/pull/968)
- * **Experimental support for .NET Core 3.0 WPF benchmarks**
- [#1066](https://github.com/dotnet/BenchmarkDotNet/pull/1066)
- For a working example please go to [https://github.com/dotMorten/WPFBenchmarkTests](https://github.com/dotMorten/WPFBenchmarkTests)
-* **Improvements:**
- * CoreRT Toolchain improvements - thanks to help from CoreRT Team we were able to make
- the CoreRT Toolchain work with the latest version of CoreRT
- [#1001](https://github.com/dotnet/BenchmarkDotNet/pull/1001),
- [#1057](https://github.com/dotnet/BenchmarkDotNet/pull/1057)
- * Display the number of benchmarks to run: we now display how many benchmarks are going to be executed
- before running them and how many remained after running each of them
- [#1048](https://github.com/dotnet/BenchmarkDotNet/issues/1048)
- * Better list of suggested benchmarks for wrong filter
- [#834](https://github.com/dotnet/BenchmarkDotNet/issues/834)
- [#957](https://github.com/dotnet/BenchmarkDotNet/pull/957)
- * Invalid assembly binding redirects generated by VS were a pain to many of our users,
- we have now implemented an approach that tries to work around this issue.
- [#895](https://github.com/dotnet/BenchmarkDotNet/issues/895),
- [#667](https://github.com/dotnet/BenchmarkDotNet/issues/667),
- [#896](https://github.com/dotnet/BenchmarkDotNet/issues/896),
- [#942](https://github.com/dotnet/BenchmarkDotNet/issues/942)
- * Handling duplicates in IConfig
- [#912](https://github.com/dotnet/BenchmarkDotNet/pull/912),
- [#938](https://github.com/dotnet/BenchmarkDotNet/issues/938),
- [#360](https://github.com/dotnet/BenchmarkDotNet/issues/360),
- [#463](https://github.com/dotnet/BenchmarkDotNet/issues/463)
- * Disassembly diagnoser should be kept in a separate directory to avoid dependency conflicts
- [#1059](https://github.com/dotnet/BenchmarkDotNet/issues/1059)
- * Give a warning when the `[Benchmark]` method is static - we now produce an error when users fail into this common issue
- [#983](https://github.com/dotnet/BenchmarkDotNet/issues/983)
- [#985](https://github.com/dotnet/BenchmarkDotNet/pull/985)
- * C# keywords are prohibited as benchmark names
- [#849](https://github.com/dotnet/BenchmarkDotNet/issues/849)
- * File names should be consistent across all OSes - `<` and `>` are valid on Unix, but not on Windows.
- We have unified that and now files produced on Unix and Windows have the same names.
- [#981](https://github.com/dotnet/BenchmarkDotNet/issues/981)
- * Improve restore, build and publish projects
- [#1002](https://github.com/dotnet/BenchmarkDotNet/issues/1002),
- [#1013](https://github.com/dotnet/BenchmarkDotNet/pull/1013)
- * Make it possible to disable OptimizationsValidator
- [#988](https://github.com/dotnet/BenchmarkDotNet/issues/988)
- * Sort enum parameters by value instead of name
- [#977](https://github.com/dotnet/BenchmarkDotNet/pull/977)
- * Detect .NET Core benchmark failures from LINQPad
- [#980](https://github.com/dotnet/BenchmarkDotNet/pull/980)
- * Improved error logging
- [#1008](https://github.com/dotnet/BenchmarkDotNet/pull/1008)
- * Improved disassembly diff
- [#1022](https://github.com/dotnet/BenchmarkDotNet/pull/1022)
- * Using invariant culture for Roslyn Toolchain error messages
- [#1042](https://github.com/dotnet/BenchmarkDotNet/pull/1042)
- * Use only full names in the auto-generated code to avoid any possible conflicts with user code
- [#1007](https://github.com/dotnet/BenchmarkDotNet/issues/1007),
- [#1009](https://github.com/dotnet/BenchmarkDotNet/pull/1009)
- [#1010](https://github.com/dotnet/BenchmarkDotNet/issues/1010)
- * Write the GitHub table format to the console by default
- [#1062](https://github.com/dotnet/BenchmarkDotNet/issues/1062)
- * Proper cleanup on Ctrl+C/console Window exit
- [#1061](https://github.com/dotnet/BenchmarkDotNet/pull/1061)
- * Introduce StoppingCriteria - the first step to writing your own heuristic that determines when benchmarking should be stopped
- [#984](https://github.com/dotnet/BenchmarkDotNet/pull/984)
-* **Breaking changes:**
- * .NET Standard 2.0 only - BenchmarkDotNet has a single target now, which should help with some assembly resolving issues.
- We had to drop .NET 4.6 support because of that and .NET 4.6.1 is now the oldest supported .NET Framework.
- [#1032](https://github.com/dotnet/BenchmarkDotNet/pull/1032)
- * CustomCoreClrToolchain has been removed, it's recommended to use CoreRunToolchain instead
- [#928](https://github.com/dotnet/BenchmarkDotNet/issues/928)
-* **Bug fixes:**
- * NRE in `Summary` ctor
- [#986](https://github.com/dotnet/BenchmarkDotNet/issues/986)
- [#987](https://github.com/dotnet/BenchmarkDotNet/pull/987)
- * ArgumentNullException when running benchmarks from published .NET Core app
- [#1018](https://github.com/dotnet/BenchmarkDotNet/issues/1018)
- * Dry jobs can eat iteration failures
- [#1045](https://github.com/dotnet/BenchmarkDotNet/issues/1045)
- * NullReferenceException in BenchmarkDotNet.Reports.SummaryTable after iteration failure
- [#1046](https://github.com/dotnet/BenchmarkDotNet/issues/1046)
- * Running the example throws NullReference
- [#1049](https://github.com/dotnet/BenchmarkDotNet/issues/1049)
- * Fix race condition in process output reader
- [#1051](https://github.com/dotnet/BenchmarkDotNet/issues/1051)
- [#1053](https://github.com/dotnet/BenchmarkDotNet/pull/1053)
- * Fix a rare but really annoying bug where for some reason we were sometimes setting ForegroundColor
- to the same color as BackgroundColor and some parts of the logged output were invisible
- [commit](https://github.com/dotnet/BenchmarkDotNet/commit/ea3036810ef60b483d766a097e6f3edfde28a834)
- * StopOnFirstError must be respected
- [commit](https://github.com/dotnet/BenchmarkDotNet/commit/87d281d7dbf52036819efff52e6661e436648b73)
-
----
-
diff --git a/docs/_changelog/header/v0.11.5.md b/docs/_changelog/header/v0.11.5.md
deleted file mode 100644
index 98e31c0195..0000000000
--- a/docs/_changelog/header/v0.11.5.md
+++ /dev/null
@@ -1,170 +0,0 @@
-## Highlights
-
-* **Features and noticeable improvements**
- * **Power plan management**
- Now BenchmarkDotNet executes all benchmarks with enabled High-Performance power plan (configurable, Windows-only).
- You can find some details below.
- [#68](https://github.com/dotnet/BenchmarkDotNet/issues/68)
- [#952](https://github.com/dotnet/BenchmarkDotNet/pull/952)
- * **Better Environment Variables API**
- Now we have some additional extension methods which allow defining environment variables in user jobs.
- In the previous version, users always had to set an array of environment variables like this:
- `job.With(new[] { new EnvironmentVariable("a", "b") })`.
- Now it's possible to define an environment variable like
- `job.With(new EnvironmentVariable("a", "b"))` or
- `job.WithEnvironmentVariable("a", "b")`.
- Also, it's possible to clear the list of environment variables via
- `job.WithoutEnvironmentVariables()`.
- [#1069](https://github.com/dotnet/BenchmarkDotNet/issues/1069)
- [#1080](https://github.com/dotnet/BenchmarkDotNet/pull/1080)
- * **Better outlier messages**
- The previous version of BenchmarkDotNet printed information about detected or removed outliers like this:
- "3 outliers were detected".
- It was nice, but it didn't provide additional information about these outliers
- (users had to read the full log to find the outliers values).
- Now BenchmarkDotNet prints additional information about outlier values like this:
- "3 outliers were detected (2.50 us..2.70 us)".
- [e983cd31](https://github.com/dotnet/BenchmarkDotNet/commit/e983cd3126e64f82fe59bc1bc45d1a870a615e87)
- * **Support modern CPU architecture names**
- In the environment information section, BenchmarkDotNet prints not only the processor brand string, but also its architecture
- (e.g., "Intel Core i7-4770K CPU 3.50GHz (Haswell)").
- However, it failed to recognize some recent processors.
- Now it's able to detect the architecture for modern Intel processors correctly
- (Kaby Lake, Kaby Lake R, Kaby Lake G, Amber Lake Y, Coffee Lake, Cannon Lake, Whiskey Lake).
- [995e053d](https://github.com/dotnet/BenchmarkDotNet/commit/995e053d14a61cdadc417149480f23ebf679bcb7)
- * **Introduce BenchmarkDotNet.Annotations**
- Currently, BenchmarkDotNet targets .NET Standard 2.0.
- It makes some users unhappy because they want to define benchmarks in projects with lower target framework.
- We decided to start working on the `BenchmarkDotNet.Annotations` NuGet package which targets .NET Standard 1.0
- and contains classes that users need to define their benchmarks.
- However, it's not easy to refactor the full source code base and move all relevant public APIs to this package.
- In v0.11.5, we did the first step and moved some of these APIs to `BenchmarkDotNet.Annotations`.
- We want to continue moving classes to this package and get full-featured annotation package in the future.
- [#1084](https://github.com/dotnet/BenchmarkDotNet/pull/1084)
- [#1096](https://github.com/dotnet/BenchmarkDotNet/pull/1096)
- * **Use InProcessEmitToolchain by default in InProcess benchmarks**
- In BenchmarkDotNet 0.11.4, we introduced `InProcessEmitToolchain`.
- It's a new, full-featured InProcess toolchain which allows executing benchmarks in the current process
- without spawning additional process per benchmark.
- It supports `[Arguments]`, `[ArgumentsSource]`, passing the arguments by `out`, `ref` and returning stack-only types like `Span`.
- However, in v0.11.4, it can be activated only if `InProcessEmitToolchain` is declared explicitly.
- Now it's enabled by default when `[InProcessAttribute]` is used.
- [#1093](https://github.com/dotnet/BenchmarkDotNet/pull/1093)
- * **Introduce an option which prevents overwriting results**
- Currently, BenchmarkDotNet overwrites results each time when the benchmarks are executed.
- It allows avoiding tons of obsolete files in the `BenchmarkDotNet.Artifacts` folder.
- However, the behavior doesn't fit all use cases:
- sometimes users want to keep results for old benchmark runs.
- Now we have a special option for it.
- The option can be activated via `--noOverwrite` console line argument or
- `DontOverwriteResults` extension method for `IConfig`
- [#1074](https://github.com/dotnet/BenchmarkDotNet/issues/1074)
- [#1083](https://github.com/dotnet/BenchmarkDotNet/pull/1083)
-* **Other improvements and bug fixes**
- * **Diagnostics and validation**
- * **Better benchmark declaration error processing**
- In the previous version, BenchmarkDotNet threw an exception when some benchmark methods had an invalid declaration
- (e.g., invalid signature or invalid access modifiers).
- Now it prints a nice error message without ugly stack traces.
- [#1107](https://github.com/dotnet/BenchmarkDotNet/issues/1107)
- * **Better error message for users who want to debug benchmarks**
- [#1073](https://github.com/dotnet/BenchmarkDotNet/issues/1073)
- * **Don't show the same validation error multiple times**
- Now each error will be printed only once.
- [#1079](https://github.com/dotnet/BenchmarkDotNet/issues/1079)
- * **Restrict MemoryDiagnoserAttribute usage to class**
- Now it's impossible to accidentally mark a method with this attribute.
- [#1119](https://github.com/dotnet/BenchmarkDotNet/issues/1119)
- [#1122](https://github.com/dotnet/BenchmarkDotNet/pull/1122)
- * **Export**
- * **Better indentation in disassembly listings**
- Now DissassemblyDiagnoser correctly process source code which contains tab as the indentation symbol
- [#1110](https://github.com/dotnet/BenchmarkDotNet/issues/1110)
- * **Fix incorrect indentation for StackOverflow exporter**
- Previously, StackOverflow exporter doesn't have a proper indent for job runtimes in the environment information.
- Now it's fixed.
- [#826](https://github.com/dotnet/BenchmarkDotNet/issues/826)
- [#1104](https://github.com/dotnet/BenchmarkDotNet/pull/1104)
- * **Fix StackOverflowException in XmlExporter.Full**
- [#1086](https://github.com/dotnet/BenchmarkDotNet/issues/1086)
- [#1090](https://github.com/dotnet/BenchmarkDotNet/pull/1090)
- * **Shortify MemoryDiagnoser column titles**
- Now we use the following column titles:
- "Allocated" instead of "Allocated Memory/Op",
- "Gen 0" instead of "Gen 0/1k Op".
- The full description of each column can be found in the legend section below the summary table.
- [#1081](https://github.com/dotnet/BenchmarkDotNet/pull/1081)
- * **Benchmark generation and execution**
- * **Fixed broken Orderers**
- The previous version has a nasty bug with custom orderers.
- Now it's fixed.
- [#1070](https://github.com/dotnet/BenchmarkDotNet/issues/1070)
- [#1109](https://github.com/dotnet/BenchmarkDotNet/issues/1109)
- * **Better overhead evaluation**
- In the previous version, BenchmarkDotNet evaluated the benchmark overhead as a mean value of all overhead iteration.
- It was fine in most cases, but in some cases, the mean value can be spoiled by outliers.
- Now BenchmarkDotNet uses the median value.
- [#1116](https://github.com/dotnet/BenchmarkDotNet/issues/1116)
- * **Respect CopyLocalLockFileAssemblies**
- Now BenchmarkDotNet respect the `CopyLocalLockFileAssemblies` value and copies it
- to the generated benchmark project.
- [#1068](https://github.com/dotnet/BenchmarkDotNet/issues/1068)
- [#1108](https://github.com/dotnet/BenchmarkDotNet/pull/1108)
- * **Disable CodeAnalysisRuleSet for generated benchmarks**
- Previously, generated benchmarks may fail if the `CodeAnalysisRuleSet` is defined in `Directory.Build.Props`.
- [#1082](https://github.com/dotnet/BenchmarkDotNet/pull/1082)
- * **Supported undefined enum values**
- [#1020](https://github.com/dotnet/BenchmarkDotNet/issues/1020)
- [#1071](https://github.com/dotnet/BenchmarkDotNet/issues/1071)
- * **Other minor improvements and bug fixes**
-
-## Power plans
-
-In [#952](https://github.com/dotnet/BenchmarkDotNet/pull/952), power plan management was implemented.
-It resolves a pretty old issue [#68](https://github.com/dotnet/BenchmarkDotNet/issues/68) which was created more than three years ago.
-Now BenchmarkDotNet forces OS to execute a benchmark on the High-Performance power plan.
-You can disable this feature by modifying PowerPlanMode property.
-Here is an example where we are playing with this value:
-
-```cs
-[Config(typeof(Config))]
-public class IntroPowerPlan
-{
- private class Config : ManualConfig
- {
- public Config()
- {
- Add(Job.MediumRun.WithPowerPlan(PowerPlan.HighPerformance));
- Add(Job.MediumRun.WithPowerPlan(PowerPlan.UserPowerPlan));
- }
- }
-
- [Benchmark]
- public int IterationTest()
- {
- int j = 0;
- for (int i = 0; i < short.MaxValue; ++i)
- j = i;
- return j;
- }
-
- [Benchmark]
- public int SplitJoin()
- => string.Join(",", new string[1000]).Split(',').Length;
-}
-```
-
-And here is an example of the summary table on plugged-off laptop:
-
-```md
- Method | PowerPlan | Mean | Error | StdDev |
--------------- |---------------- |---------:|----------:|----------:|
- IterationTest | HighPerformance | 40.80 us | 0.4168 us | 0.6109 us |
- SplitJoin | HighPerformance | 13.24 us | 0.2514 us | 0.3763 us |
- IterationTest | UserPowerPlan | 79.72 us | 2.5623 us | 3.8352 us |
- SplitJoin | UserPowerPlan | 24.54 us | 2.1062 us | 3.1525 us |
-```
-
-As you can see, the power plan produces a noticeable effect on the benchmark results.
-
-This feature is available on Windows only.
\ No newline at end of file
diff --git a/docs/_changelog/header/v0.12.0.md b/docs/_changelog/header/v0.12.0.md
deleted file mode 100644
index 0e6a00582f..0000000000
--- a/docs/_changelog/header/v0.12.0.md
+++ /dev/null
@@ -1,469 +0,0 @@
-It's been several months since our last release, but we have been working hard and have some new features for you!
-
-## Highlights
-
-* **Features and major improvements**
- * **Advanced multiple target frameworks support**
- Now BenchmarkDotNet supports .NET Framework 4.8, .NET Core 3.1, and .NET Core 5.0.
- Also, we reworked our API that allows targeting several runtimes from the same config:
- the new API is more consistent, flexible, and powerful.
- For example, if you want to execute your benchmarking using .NET Framework 4.8 and .NET Core 3.1,
- you can use the `SimpleJob(RuntimeMoniker.Net48)`, `[SimpleJob(RuntimeMoniker.NetCoreApp31)]` attributes or
- `Job.Default.With(ClrRuntime.Net48)`, `Job.Default.With(CoreRuntime.Core31)` jobs in a manual config.
- You can find more details below.
- [#1188](https://github.com/dotnet/BenchmarkDotNet/pull/1188),
- [#1186](https://github.com/dotnet/BenchmarkDotNet/issues/1186),
- [#1236](https://github.com/dotnet/BenchmarkDotNet/issues/1236)
- * **Official templates for BenchmarkDotNet-based projects**
- With the help of the [BenchmarkDotNet.Templates](https://www.nuget.org/packages/BenchmarkDotNet.Templates/) NuGet package,
- you can easily create new projects from the command line via `dotnet new benchmark`.
- This command has a lot of useful options, so you can customize your new project as you want.
- [#1044](https://github.com/dotnet/BenchmarkDotNet/pull/1044)
- * **New NativeMemoryProfiler**
- `NativeMemoryProfiler` measure the native memory traffic and adds the extra columns `Allocated native memory` and `Native memory leak` to the summary table.
- Internally, it uses `EtwProfiler` to profile the code using ETW.
- [#457](https://github.com/dotnet/BenchmarkDotNet/issues/457),
- [#1131](https://github.com/dotnet/BenchmarkDotNet/pull/1131),
- [#1208](https://github.com/dotnet/BenchmarkDotNet/pull/1208),
- [#1214](https://github.com/dotnet/BenchmarkDotNet/pull/1214),
- [#1218](https://github.com/dotnet/BenchmarkDotNet/pull/1218),
- [#1219](https://github.com/dotnet/BenchmarkDotNet/pull/1219)
- * **New ThreadingDiagnoser**
- `ThreadingDiagnoser` also adds two extra columns to the summary table:
- `Completed Work Items` (the number of work items that have been processed in ThreadPool per single operation) and
- `Lock Contentions` (the number of times there *was contention* upon trying to take a Monitor's lock per single operation).
- Internally, it uses [new APIs](https://github.com/dotnet/corefx/issues/35500) exposed in .NET Core 3.0.
- [#1154](https://github.com/dotnet/BenchmarkDotNet/issues/1154),
- [#1227](https://github.com/dotnet/BenchmarkDotNet/pull/1227)
- * **Improved MemoryDiagnoser**
- Now `MemoryDiagnoser` includes memory allocated by *all threads* that were live during benchmark execution: a new GC API was exposed in .NET Core 3.0 preview6+.
- It allows to get the number of allocated bytes for all threads.
- [#1155](https://github.com/dotnet/BenchmarkDotNet/pull/1155),
- [#1153](https://github.com/dotnet/BenchmarkDotNet/issues/1153),
- [#723](https://github.com/dotnet/BenchmarkDotNet/issues/723)
- * **LINQPad 6 support**
- Now both LINQPad 5 and LINQPad 6 are supported!
- [#1241](https://github.com/dotnet/BenchmarkDotNet/issues/1241),
- [#1245](https://github.com/dotnet/BenchmarkDotNet/pull/1245)
- * **Fast documentation search**
- We continue to improve the usability of our documentation.
- In this release, we improved the search experience in the documentation: now it works almost instantly with the help of [Algolia](https://www.algolia.com/) engine!
- [#1148](https://github.com/dotnet/BenchmarkDotNet/pull/1148),
- [#1158](https://github.com/dotnet/BenchmarkDotNet/issues/1158)
-* **Minor summary and exporter improvements**
- * **Improved presentation of the current architecture in the environment information**
- In the previous version of BenchmarkDotNet, the reports always contained "64bit" or "32bit" which did not tell if it was ARM or not.
- Now it prints the full architecture name (`x64`, `x86`, `ARM`, or `ARM64`).
- For example, instead of `.NET Framework 4.8 (4.8.3815.0), 64bit RyuJIT` you will get `.NET Framework 4.8 (4.8.3815.0), X64 RyuJIT` or `.NET Framework 4.8 (4.8.3815.0), ARM64 RyuJIT`.
- [#1213](https://github.com/dotnet/BenchmarkDotNet/pull/1213)
- * **Simplified reports for Full .NET Framework version**
- Previous version: `.NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3324.0`.
- Current version: `.NET Framework 4.7.2 (4.7.3362.0), 64bit RyuJIT`.
- [#1114](https://github.com/dotnet/BenchmarkDotNet/pull/1114),
- [#1111](https://github.com/dotnet/BenchmarkDotNet/issues/1111)
- * **More reliable CPU info on Windows**
- We added a workaround to for a bug in wmic that uses `\r\r\n` as a line separator.
- [#1144](https://github.com/dotnet/BenchmarkDotNet/issues/1144),
- [#1145](https://github.com/dotnet/BenchmarkDotNet/pull/1145)
- * **Better naming for generated plots**
- When `[RPlotExporter]` is used, BenchmarkDotNet generates a lot of useful plots in the `BenchmarkDotNet.Artifacts` folder.
- The naming of the plot files was improved: benchmarks without `Params` doesn't include a double dash (`--`) in their names anymore.
- [1183](https://github.com/dotnet/BenchmarkDotNet/issues/1183),
- [1212](https://github.com/dotnet/BenchmarkDotNet/pull/1212)
- * **Better density plot precision**
- The previous version of BenchmarkDotNet used the rule-of-thumb bandwidth selector in `RPlotExporter` density plots.
- It was fine for unimodal distributions, but sometimes it produced misleading plots for multimodal distributions.
- Now, RPlotExporter uses the Sheather&Jones bandwidth selector that significantly improves the presentation of the density plots for complex distributions.
- [58fde64](https://github.com/dotnet/BenchmarkDotNet/commit/58fde64c809ceadb3fca9d677a7cec83071b833f)
- * **Better alignment in `HtmlExporter`**
- Now BenchmarkDotNet aligns the content exported by `HtmlExporter` to the right.
- [#1189](https://github.com/dotnet/BenchmarkDotNet/pull/1189)
- [dfa074](https://github.com/dotnet/BenchmarkDotNet/commit/dfa074b024cfa8bbe4fe175d3d65a3d9f85127ff)
- * **Better precision calculation in SummaryTable**
- [4e9eb43](https://github.com/dotnet/BenchmarkDotNet/commit/4e9eb4335eee05a95a3766f2c81ae260508021af)
- * **Better summary analysis**
- BenchmarkDotNet warns the user when benchmark baseline value is too close to zero and the columns derived from BaselineCustomColumn cannot be computed.
- [#1161](https://github.com/dotnet/BenchmarkDotNet/pull/1161),
- [#600](https://github.com/dotnet/BenchmarkDotNet/issues/600)
- * **Make log file datetime format 24-hour**
- [#1149](https://github.com/dotnet/BenchmarkDotNet/issues/1149)
- * **Improve AskUser prompt message**
- The error messages will surround `*` by quotes on Linux and macOS.
- [#1147](https://github.com/dotnet/BenchmarkDotNet/issues/1147)
-* **Minor API improvements**
- * **ED-PELT algorithm for changepoint detection is now available**
- You can find details in [this blog post](https://aakinshin.net/posts/edpelt/).
- [f89091](https://github.com/dotnet/BenchmarkDotNet/commit/f89091a2a9c1a4058dd8e32d5aa01271910dd7dc)
- * **Improved OutlierMode API**
- BenchmarkDotNet performs measurement postprocessing that may remove some of the outlier values (it can be useful to remove upper outliers that we get because of the natural CPU noise).
- In the previous version, naming for the `OutlierMode` values was pretty confusing: `None/OnlyUpper/OnlyLower/All`.
- Now, these values were renamed to `DontRemove/RemoveUpper/RemoveLower/RemoveAll`.
- For example, if you want to remove all the outliers, you can annotate your benchmark with the `[Outliers(OutlierMode.RemoveAll)]` attribute.
- The old names still exist (to make sure that the changes are backward compatible), but they are marked as obsolete, and they will be removed in the future versions of the library.
- [#1199](https://github.com/dotnet/BenchmarkDotNet/pull/1199),
- [0e4b8e](https://github.com/dotnet/BenchmarkDotNet/commit/0e4b8e69d10e73a83fce3ce3980497ee7798bc87)
- * **Add the possibility to pass `Config` to `BenchmarkSwitcher.RunAll` and `RunAllJoined`**
- [#1194](https://github.com/dotnet/BenchmarkDotNet/issues/1194),
- [ae23bd](https://github.com/dotnet/BenchmarkDotNet/commit/ae23bddebe49cd66a9627790c073b7bc45ccbf5c)
- * **Improved command line experience**
- When user uses `--packages $path`, the `$path` will be sent to the dotnet build command as well.
- [1187](https://github.com/dotnet/BenchmarkDotNet/issues/1187)
- * **Extend the list of supported power plans.**
- Now it supports "ultimate", "balanced", and "power saver" plans.
- [#1132](https://github.com/dotnet/BenchmarkDotNet/issues/1132),
- [#1139](https://github.com/dotnet/BenchmarkDotNet/pull/1139)
- * **Make it possible to not enforce power plan on Windows.**
- [1578c5c](https://github.com/dotnet/BenchmarkDotNet/commit/1578c5c60c3f59f9128f680e35d1db219aa60d8d)
- * **`Guid` support in benchmark arguments**
- Now you can use `Guid` instances as benchmark arguments.
- [04ec20b](https://github.com/dotnet/BenchmarkDotNet/commit/04ec20b5e0c0a514e8d158684864e4f9934ae8cc)
- * **Make `ArgumentsSource` support `IEnumerable` for benchmarks accepting a single argument to mimic `MemberData` behaviour.**
- [ec296dc](https://github.com/dotnet/BenchmarkDotNet/commit/ec296dc45de798f7407852d5ab7febe2b457eca4)
- * **Make `FullNameProvider` public**
- So it can be reused by the `dotnet/performance` repository.
- [6d71308](https://github.com/dotnet/BenchmarkDotNet/commit/6d71308f4c1d96bcf8a526bfcc61bb23307b4041)
- * **Extend `Summary` with `LogFilePath`**
- [#1135](https://github.com/dotnet/BenchmarkDotNet/issues/1135),
- [6e6559](https://github.com/dotnet/BenchmarkDotNet/commit/6e6559499652c312369fd223d0cf4747f65512d6)
- * **Allow namespace filtering for `InliningDiagnoser`**
- [#1106](https://github.com/dotnet/BenchmarkDotNet/issues/1106),
- [#1130](https://github.com/dotnet/BenchmarkDotNet/pull/1130)
- * **Option to configure `MaxParameterColumnWidth`**
- [#1269](https://github.com/dotnet/BenchmarkDotNet/issues/1269),
- [4ec888](https://github.com/dotnet/BenchmarkDotNet/commit/4ec88844547507474ccd0303b31f935b3463318c)
-* **Other improvements**
- * **Misc improvements in the documentation**
- [#1175](https://github.com/dotnet/BenchmarkDotNet/pull/1175),
- [#1173](https://github.com/dotnet/BenchmarkDotNet/pull/1173),
- [#1180](https://github.com/dotnet/BenchmarkDotNet/pull/1180),
- [#1203](https://github.com/dotnet/BenchmarkDotNet/pull/1203),
- [#1204](https://github.com/dotnet/BenchmarkDotNet/pull/1204),
- [#1206](https://github.com/dotnet/BenchmarkDotNet/pull/1206),
- [#1209](https://github.com/dotnet/BenchmarkDotNet/pull/1209),
- [#1219](https://github.com/dotnet/BenchmarkDotNet/pull/1219),
- [#1225](https://github.com/dotnet/BenchmarkDotNet/pull/1225),
- [#1279](https://github.com/dotnet/BenchmarkDotNet/pull/1279)
- * **Copy `PreserveCompilationContext` MSBuild setting from the project that defines benchmarks**
- [#1152](https://github.com/dotnet/BenchmarkDotNet/issues/1152),
- [063d1a](https://github.com/dotnet/BenchmarkDotNet/commit/063d1a56152fd5812cb6e9dd5095dc6e647e6938)
- * **Add `System.Buffers.ArrayPoolEventSource` to the list of default .NET Providers of `EtwProfiler`**
- [#1179](https://github.com/dotnet/BenchmarkDotNet/issues/1179),
- [a106b1](https://github.com/dotnet/BenchmarkDotNet/commit/a106b114b1f04fa1024be84a8969f5a168fa1c8b)
- * **Consume CoreRT from the new NuGet feed**
- Because CoreRT no longer publishes to MyGet.
- [#1129](https://github.com/dotnet/BenchmarkDotNet/pull/1129)
-* **Breaking changes:**
- * The `[ClrJob]`, `[CoreJob]` and `[CoreRtJob]` attributes got obsoleted and replaced by a `[SimpleJob]` which requires the user to provide target framework moniker in an explicit way.
- (See the "Advanced multiple target frameworks support" section for details.)
- [#1188](https://github.com/dotnet/BenchmarkDotNet/pull/1188),
- [#1182](https://github.com/dotnet/BenchmarkDotNet/issues/1182),
- [#1115](https://github.com/dotnet/BenchmarkDotNet/issues/1115),
- [#1056](https://github.com/dotnet/BenchmarkDotNet/issues/1056),
- [#993](https://github.com/dotnet/BenchmarkDotNet/issues/993),
- * The old `InProcessToolchain` is now obsolete. It's recommended to use `InProcessEmitToolchain`. If you want to use the old one on purpose, you have to use `InProcessNoEmitToolchain`.
- [#1123](https://github.com/dotnet/BenchmarkDotNet/pull/1123)
-* **Bug fixes:**
- * Invalid arg passing in StreamLogger constructor. The `append` arg was not passed to the `StreamWriter` .ctor.
- [#1185](https://github.com/dotnet/BenchmarkDotNet/pull/1185)
- * Improve the output path of `.etl` files produced by `EtwProfiler`. `EtwProfiler` was throwing NRE for users who were using `[ClrJob]` and `[CoreJob]` attributes.
- [#1156](https://github.com/dotnet/BenchmarkDotNet/issues/1156),
- [#1072](https://github.com/dotnet/BenchmarkDotNet/issues/1072)
- * Flush custom loggers at the end of benchmark session.
- [#1134](https://github.com/dotnet/BenchmarkDotNet/issues/1134)
- * Make ids for tag columns unique - when using multiple `TagColumns` only one `TagColumn` was printed in the results.
- [#1146](https://github.com/dotnet/BenchmarkDotNet/issues/1146)
-
-## Advanced multiple target frameworks support
-
-Now BenchmarkDotNet supports .NET Framework 4.8, .NET Core 3.1, and .NET Core 5.0.
-Also, we reworked our API that allows targeting several runtimes from the same config:
- the new API is more consistent, flexible, and powerful.
-For example, if you want to execute your benchmarking using .NET Framework 4.8 and .NET Core 3.1,
- you can use the `SimpleJob(RuntimeMoniker.Net48)`, `[SimpleJob(RuntimeMoniker.NetCoreApp31)]` attributes or
- `Job.Default.With(ClrRuntime.Net48)`, `Job.Default.With(CoreRuntime.Core31)` jobs in a manual config.
-
-Now let's discuss how to use it in detail.
-If you want to test multiple frameworks, your project file **MUST target all of them** and you **MUST install the corresponding SDKs**:
-
-```xml
-netcoreapp3.0;netcoreapp2.1;net48
-```
-
-If you run your benchmarks without specifying any custom settings, BenchmarkDotNet is going to run the benchmarks **using the same framework as the host process** (it corresponds to `RuntimeMoniker.HostProcess`):
-
-```cmd
-dotnet run -c Release -f netcoreapp2.1 # is going to run the benchmarks using .NET Core 2.1
-dotnet run -c Release -f netcoreapp3.0 # is going to run the benchmarks using .NET Core 3.0
-dotnet run -c Release -f net48 # is going to run the benchmarks using .NET 4.8
-mono $pathToExe # is going to run the benchmarks using Mono from your PATH
-```
-
-To run the benchmarks for multiple runtimes with a single command from the command line, you need to specify the runtime moniker names via `--runtimes|-r` console argument:
-
-```cmd
-dotnet run -c Release -f netcoreapp2.1 --runtimes netcoreapp2.1 netcoreapp3.0 # is going to run the benchmarks using .NET Core 2.1 and .NET Core 3.0
-dotnet run -c Release -f netcoreapp2.1 --runtimes netcoreapp2.1 net48 # is going to run the benchmarks using .NET Core 2.1 and .NET 4.8
-```
-
-What is going to happen if you provide multiple Full .NET Framework monikers?
-Let's say:
-
-```cmd
-dotnet run -c Release -f net461 net472 net48
-```
-
-Full .NET Framework always runs every .NET executable using the latest .NET Framework available on a given machine.
-If you try to run the benchmarks for a few .NET TFMs, they are all going to be executed using the latest .NET Framework from your machine.
-The only difference is that they are all going to have different features enabled depending on the target version they were compiled for.
-You can read more about this
- [here](https://learn.microsoft.com/dotnet/framework/migration-guide/version-compatibility) and
- [here](https://learn.microsoft.com/dotnet/framework/migration-guide/application-compatibility).
-This is **.NET Framework behavior which can not be controlled by BenchmarkDotNet or any other tool**.
-
-**Note:** Console arguments support works only if you pass the `args` to `BenchmarkSwitcher`:
-
-```cs
-class Program
-{
- static void Main(string[] args)
- => BenchmarkSwitcher
- .FromAssembly(typeof(Program).Assembly)
- .Run(args); // crucial to make it work
-}
-```
-
-You can achieve the same thing using `[SimpleJobAttribute]`:
-
-```cs
-using BenchmarkDotNet.Attributes;
-using BenchmarkDotNet.Jobs;
-
-namespace BenchmarkDotNet.Samples
-{
- [SimpleJob(RuntimeMoniker.Net48)]
- [SimpleJob(RuntimeMoniker.Mono)]
- [SimpleJob(RuntimeMoniker.NetCoreApp21)]
- [SimpleJob(RuntimeMoniker.NetCoreApp30)]
- public class TheClassWithBenchmarks
-```
-
-Or using a custom config:
-
-```cs
-using BenchmarkDotNet.Configs;
-using BenchmarkDotNet.Environments;
-using BenchmarkDotNet.Jobs;
-using BenchmarkDotNet.Running;
-
-namespace BenchmarkDotNet.Samples
-{
- class Program
- {
- static void Main(string[] args)
- {
- var config = DefaultConfig.Instance
- .With(Job.Default.With(CoreRuntime.Core21))
- .With(Job.Default.With(CoreRuntime.Core30))
- .With(Job.Default.With(ClrRuntime.Net48))
- .With(Job.Default.With(MonoRuntime.Default));
-
- BenchmarkSwitcher
- .FromAssembly(typeof(Program).Assembly)
- .Run(args, config);
- }
- }
-}
-```
-
-The recommended way of running the benchmarks for multiple runtimes is to use the `--runtimes` console line argument.
-By using the console line argument, you don't need to edit the source code anytime you want to change the list of runtimes.
-Moreover, if you share the source code of the benchmark, other people can run it even if they don't have the exact same framework version installed.
-
-## Official templates for BenchmarkDotNet-based projects
-
-Since v0.12.0, BenchmarkDotNet provides project templates to setup your benchmarks easily.
-The template exists for each major .NET language ([C#](https://learn.microsoft.com/dotnet/csharp/), [F#](https://learn.microsoft.com/dotnet/fsharp/) and [VB](https://learn.microsoft.com/dotnet/visual-basic/)) with equivalent features and structure.
-The templates require the [.NET Core SDK](https://www.microsoft.com/net/download). Once installed, run the following command to install the templates:
-
-```log
-dotnet new -i BenchmarkDotNet.Templates
-```
-
-If you want to uninstall all BenchmarkDotNet templates:
-
-```log
-dotnet new -u BenchmarkDotNet.Templates
-```
-
-The template is a NuGet package distributed over nuget.org: [BenchmarkDotNet.Templates](https://www.nuget.org/packages/BenchmarkDotNet.Templates/).
-To create a new C# benchmark library project from the template, run:
-
-```log
-dotnet new benchmark
-```
-
-If you'd like to create F# or VB project, you can specify project language with `-lang` option:
-
-```log
-dotnet new benchmark -lang F#
-dotnet new benchmark -lang VB
-```
-
-The template projects have five additional options - all of them are optional.
-By default, a class library project targeting netstandard2.0 is created.
-You can specify `-f` or `--frameworks` to change target to one or more frameworks:
-
-```log
-dotnet new benchmark -f netstandard2.0;net472
-```
-
-The option `--console-app` creates a console app project targeting `netcoreapp3.0` with an entry point:
-
-```log
-dotnet new benchmark --console-app
-```
-
-This lets you run the benchmarks from a console (`dotnet run`) or from your favorite IDE.
-The option `-f` or `--frameworks` will be ignored when `--console-app` is set.
-The option `-b` or `--benchmarkName` sets the name of the benchmark class:
-
-```log
-dotnet new benchmark -b Md5VsSha256
-```
-
-BenchmarkDotNet lets you create a dedicated configuration class (see [Configs](xref:docs.configs)) to customize the execution of your benchmarks.
-To create a benchmark project with a configuration class, use the option `-c` or `--config`:
-
-```log
-dotnet new benchmark -c
-```
-
-The option `--no-restore` if specified, skips the automatic NuGet restore after the project is created:
-
-```log
-dotnet new benchmark --no-restore
-```
-
-Use the `-h` or `--help` option to display all possible arguments with a description and the default values:
-
-```log
-dotnet new benchmark --help
-```
-
-The version of the template NuGet package is synced with the [BenchmarkDotNet](https://www.nuget.org/packages/BenchmarkDotNet/) package.
-For instance, the template version `0.12.0` is referencing [BenchmarkDotnet 0.12.0](https://www.nuget.org/packages/BenchmarkDotNet/0.12.0) - there is no floating version behavior.
-For more info about the `dotnet new` CLI, please read [the documentation](https://learn.microsoft.com/dotnet/core/tools/dotnet).
-
-## New NativeMemoryProfiler
-
-`NativeMemoryProfiler` measure the native memory traffic and adds the extra columns `Allocated native memory` and `Native memory leak` to the summary table.
-Internally, it uses `EtwProfiler` to profile the code using ETW.
-
-Consider the following benchmark:
-
-```cs
-[ShortRunJob]
-[NativeMemoryProfiler]
-[MemoryDiagnoser]
-public class IntroNativeMemory
-{
- [Benchmark]
- public void BitmapWithLeaks()
- {
- var flag = new Bitmap(200, 100);
- var graphics = Graphics.FromImage(flag);
- var blackPen = new Pen(Color.Black, 3);
- graphics.DrawLine(blackPen, 100, 100, 500, 100);
- }
-
- [Benchmark]
- public void Bitmap()
- {
- using (var flag = new Bitmap(200, 100))
- {
- using (var graphics = Graphics.FromImage(flag))
- {
- using (var blackPen = new Pen(Color.Black, 3))
- {
- graphics.DrawLine(blackPen, 100, 100, 500, 100);
- }
- }
- }
- }
-
- private const int Size = 20; // Greater value could cause System.OutOfMemoryException for test with memory leaks.
- private int ArraySize = Size * Marshal.SizeOf(typeof(int));
-
- [Benchmark]
- public unsafe void AllocHGlobal()
- {
- IntPtr unmanagedHandle = Marshal.AllocHGlobal(ArraySize);
- Span unmanaged = new Span(unmanagedHandle.ToPointer(), ArraySize);
- Marshal.FreeHGlobal(unmanagedHandle);
- }
-
- [Benchmark]
- public unsafe void AllocHGlobalWithLeaks()
- {
- IntPtr unmanagedHandle = Marshal.AllocHGlobal(ArraySize);
- Span unmanaged = new Span(unmanagedHandle.ToPointer(), ArraySize);
- }
-}
-```
-
-It will produce the summary table like this one:
-
-| Method | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | Allocated native memory | Native memory leak |
-|---------------------- |-------------:|--------------:|-------------:|------:|------:|------:|----------:|------------------------:|-------------------:|
-| BitmapWithLeaks | 73,456.43 ns | 22,498.10 ns | 1,233.197 ns | - | - | - | 177 B | 13183 B | 11615 B |
-| Bitmap | 91,590.08 ns | 101,468.12 ns | 5,561.810 ns | - | - | - | 180 B | 12624 B | - |
-| AllocHGlobal | 79.91 ns | 43.93 ns | 2.408 ns | - | - | - | - | 80 B | - |
-| AllocHGlobalWithLeaks | 103.50 ns | 153.21 ns | 8.398 ns | - | - | - | - | 80 B | 80 B |
-
-As you can see, we have two additional columns `Allocated native memory` and `Native memory leak` that contain some very useful numbers!
-
-## New ThreadingDiagnoser
-
-`ThreadingDiagnoser` also adds two extra columns to the summary table:
-
-* `Completed Work Items`: The number of work items that have been processed in ThreadPool (per single operation)
-* `Lock Contentions`: The number of times there **was contention** upon trying to take a Monitor's lock (per single operation)
-
-Internally, it uses [new APIs](https://github.com/dotnet/corefx/issues/35500) exposed in .NET Core 3.0.
-
-It can be activated with the help of the `[ThreadingDiagnoser]` attribute:
-
-```cs
-[ThreadingDiagnoser]
-public class IntroThreadingDiagnoser
-{
- [Benchmark]
- public void CompleteOneWorkItem()
- {
- ManualResetEvent done = new ManualResetEvent(initialState: false);
- ThreadPool.QueueUserWorkItem(m => (m as ManualResetEvent).Set(), done);
- done.WaitOne();
- }
-}
-```
-
-The above example will print a summary table like this one:
-
-| Method | Mean | StdDev | Median | Completed Work Items | Lock Contentions |
-|-------------------- |--------------:|-----------:|--------------:|---------------------:|-----------------:|
-| CompleteOneWorkItem | 8,073.5519 ns | 69.7261 ns | 8,111.6074 ns | 1.0000 | - |
-
-
-## LINQPad 6 support
-
-Now both LINQPad 5 and LINQPad 6 are supported:
-
-
-
-## Fast documentation search
-
-We continue to improve the usability of our documentation.
-In this release, we improved the search experience in the documentation: now it works almost instantly with the help of [Algolia](https://www.algolia.com/) engine!
-That's how it looks:
-
-
diff --git a/docs/_changelog/header/v0.12.1.md b/docs/_changelog/header/v0.12.1.md
deleted file mode 100644
index 8378e7da70..0000000000
--- a/docs/_changelog/header/v0.12.1.md
+++ /dev/null
@@ -1,294 +0,0 @@
-## Highlights
-
-* **.NET 5 support**
- As you probably know, .NET Core 5 was officially [rebranded](https://github.com/dotnet/runtime/pull/33694) to .NET 5.
- The new version of BenchmarkDotNet supports the new runtime after rebranding.
- [#1399](https://github.com/dotnet/BenchmarkDotNet/pull/1399)
- [465ebf](https://github.com/dotnet/BenchmarkDotNet/commit/465ebf3fdbf20f0e9219c4c957fb33c13256fdcd)
-* **Perfolizer adoption**
- The internal statistical engine of BenchmarkDotNet became mature enough to be transformed into an independent project.
- Meet [perfolizer](https://github.com/AndreyAkinshin/perfolizer) — a toolkit for performance analysis!
- While BenchmarkDotNet focuses on obtaining reliable measurements, perfolizer focuses on the decent analysis of measured data.
- You still can use all the statistical algorithms from BenchmarkDotNet,
- but you can also install perfolizer as a [standalone NuGet package](https://www.nuget.org/packages/Perfolizer/).
- You can find more details in the [official announcement](https://aakinshin.net/posts/introducing-perfolizer/).
- [#1386](https://github.com/dotnet/BenchmarkDotNet/pull/1386)
- [54a061](https://github.com/dotnet/BenchmarkDotNet/commit/54a06102a6e0cc4169d23c6f9cd2779ee408d2bf)
-* **Cross-platform disassembler**
- Now the `DisassemblyDiagnoser` is cross-platform!
- The disassembling logic was also improved, now it handles runtime helper methods and references to method tables properly.
- Internally, it uses the [Iced](https://github.com/0xd4d/iced) library for formatting assembly code.
- Special thanks to [@adamsitnik](https://github.com/adamsitnik) for the implementation and [@0xd4d](https://github.com/0xd4d) for Iced!
- [#1332](https://github.com/dotnet/BenchmarkDotNet/pull/1332)
- [#899](https://github.com/dotnet/BenchmarkDotNet/issues/899)
- [#1316](https://github.com/dotnet/BenchmarkDotNet/issues/1316)
- [#1364](https://github.com/dotnet/BenchmarkDotNet/issues/1364)
- [294320](https://github.com/dotnet/BenchmarkDotNet/commit/294320be9525b0ecfefd0351381756d3a3b77211)
-* **EventPipe-based cross-platform profiler**
- Now you can easily profiler your benchmarks on Windows, Linux, and macOS!
- Just mark your class with the `[EventPipeProfiler(...)]` attribute and get a `.speedscope.json` file that you can browse in [SpeedScope](https://www.speedscope.app/).
- Special thanks to [@WojciechNagorski](https://github.com/WojciechNagorski) for the implementation!
- [#1321](https://github.com/dotnet/BenchmarkDotNet/pull/1321)
- [#1315](https://github.com/dotnet/BenchmarkDotNet/issues/1315)
- [c648ff](https://github.com/dotnet/BenchmarkDotNet/commit/c648ff956662abae512c579ffa7f1dc12178f6c3)
-* **New fluent API**
- We continue to improve our API and make it easier for reading and writing.
- Special thanks to [@WojciechNagorski](https://github.com/WojciechNagorski) for the implementation!
- [#1273](https://github.com/dotnet/BenchmarkDotNet/pull/1273)
- [#1234](https://github.com/dotnet/BenchmarkDotNet/issues/1234)
- [640d88](https://github.com/dotnet/BenchmarkDotNet/commit/640d885ae0daddcee7c9ba9b5f1bf5790b9b5ae3)
-* **Ref readonly support**
- Now you can use `ref readonly` in benchmark signatures.
- Special thanks to [@adamsitnik](https://github.com/adamsitnik) for the implementation!
- [#1389](https://github.com/dotnet/BenchmarkDotNet/pull/1389)
- [#1388](https://github.com/dotnet/BenchmarkDotNet/issues/1388)
- [9ac777](https://github.com/dotnet/BenchmarkDotNet/commit/9ac7770682a45afb6cf4ec353f9fa3c69ece67ce)
-
-## Cross-platform disassembler
-
-Just mark your benchmark class with the `[DisassemblyDiagnoser]` attribute
- and you will get the disassembly listings for all the benchmarks.
-The formatting looks pretty nice thanks to [Iced](https://github.com/0xd4d/iced).
-It works like a charm on Windows, Linux, and macOS.
-
-```cs
-[DisassemblyDiagnoser]
-public class IntroDisassembly
-{
- private int[] field = Enumerable.Range(0, 100).ToArray();
-
- [Benchmark]
- public int SumLocal()
- {
- var local = field; // we use local variable that points to the field
-
- int sum = 0;
- for (int i = 0; i < local.Length; i++)
- sum += local[i];
-
- return sum;
- }
-
- [Benchmark]
- public int SumField()
- {
- int sum = 0;
- for (int i = 0; i < field.Length; i++)
- sum += field[i];
-
- return sum;
- }
-}
-```
-
-**.NET Core 2.1.16 (CoreCLR 4.6.28516.03, CoreFX 4.6.28516.10), X64 RyuJIT**
-
-```x86asm
-; BenchmarkDotNet.Samples.IntroDisassembly.SumLocal()
- mov rax,[rcx+8]
- xor edx,edx
- xor ecx,ecx
- mov r8d,[rax+8]
- test r8d,r8d
- jle short M00_L01
-M00_L00:
- movsxd r9,ecx
- add edx,[rax+r9*4+10]
- inc ecx
- cmp r8d,ecx
- jg short M00_L00
-M00_L01:
- mov eax,edx
- ret
-; Total bytes of code 35
-```
-
-**.NET Core 2.1.16 (CoreCLR 4.6.28516.03, CoreFX 4.6.28516.10), X64 RyuJIT**
-
-```x86asm
-; BenchmarkDotNet.Samples.IntroDisassembly.SumField()
- sub rsp,28
- xor eax,eax
- xor edx,edx
- mov rcx,[rcx+8]
- cmp dword ptr [rcx+8],0
- jle short M00_L01
-M00_L00:
- mov r8,rcx
- cmp edx,[r8+8]
- jae short M00_L02
- movsxd r9,edx
- add eax,[r8+r9*4+10]
- inc edx
- cmp [rcx+8],edx
- jg short M00_L00
-M00_L01:
- add rsp,28
- ret
-M00_L02:
- call CORINFO_HELP_RNGCHKFAIL
- int 3
-; Total bytes of code 53
-```
-
-Now we handle runtime helper methods and references to method tables properly. Example:
-
-Before:
-
-```x86asm
-; MicroBenchmarks.WithCallsAfter.Benchmark(Int32)
- push rsi
- sub rsp,20h
- mov rsi,rcx
- cmp edx,7FFFFFFFh
- jne M00_L00
- call MicroBenchmarks.WithCallsAfter.Static()
- mov rcx,rsi
- call MicroBenchmarks.WithCallsAfter.Instance()
- mov rcx,rsi
- call MicroBenchmarks.WithCallsAfter.Recursive()
- mov rcx,rsi
- mov rax,qword ptr [rsi]
- mov rax,qword ptr [rax+40h]
- call qword ptr [rax+20h]
- mov rcx,rsi
- mov edx,1
- mov rax,7FF8F4217050h
- add rsp,20h
- pop rsi
- jmp rax
-M00_L00:
- mov rcx,offset System_Private_CoreLib+0xa31d48
- call coreclr!MetaDataGetDispenser+0x322a0
- mov rsi,rax
- mov ecx,0ACFAh
- mov rdx,7FF8F42F4680h
- call coreclr!MetaDataGetDispenser+0x17140
- mov rdx,rax
- mov rcx,rsi
- call System.InvalidOperationException..ctor(System.String)
- mov rcx,rsi
- call coreclr!coreclr_shutdown_2+0x39f0
- int 3
- add byte ptr [rax],al
- sbb dword ptr [00007ff9`26284e30],eax
- add dword ptr [rax+40h],esp
- add byte ptr [rax],al
- add byte ptr [rax],al
- add byte ptr [rax],al
- add byte ptr [rax-70BC4CCh],ah
-; Total bytes of code 157
-```
-
-After:
-
-```x86asm
-; BenchmarkDotNet.Samples.WithCallsAfter.Benchmark(Int32)
- push rsi
- sub rsp,20
- mov rsi,rcx
- cmp edx,7FFFFFFF
- jne M00_L00
- call BenchmarkDotNet.Samples.WithCallsAfter.Static()
- mov rcx,rsi
- call BenchmarkDotNet.Samples.WithCallsAfter.Instance()
- mov rcx,rsi
- call BenchmarkDotNet.Samples.WithCallsAfter.Recursive()
- mov rcx,rsi
- mov rax,[rsi]
- mov rax,[rax+40]
- call qword ptr [rax+20]
- mov rcx,rsi
- mov edx,1
- mov rax BenchmarkDotNet.Samples.WithCallsAfter.Benchmark(Boolean)
- add rsp,20
- pop rsi
- jmp rax
-M00_L00:
- mov rcx MT_System.InvalidOperationException
- call CORINFO_HELP_NEWSFAST
- mov rsi,rax
- mov ecx,12D
- mov rdx,7FF954FF83F0
- call CORINFO_HELP_STRCNS
- mov rdx,rax
- mov rcx,rsi
- call System.InvalidOperationException..ctor(System.String)
- mov rcx,rsi
- call CORINFO_HELP_THROW
- int 3
-; Total bytes of code 134
-```
-
-See also: [Cross-runtime .NET disassembly with BenchmarkDotNet](https://aakinshin.net/posts/dotnet-crossruntime-disasm/).
-
-Special thanks to [@adamsitnik](https://github.com/adamsitnik) for the implementation and [@0xd4d](https://github.com/0xd4d) for Iced!
-
-## EventPipe-based cross-platform profiler
-
-Now you can easily profiler your benchmarks on Windows, Linux, and macOS!
-
-If you want to use the new profiler, you should just mark your benchmark class with the `[EventPipeProfiler(...)]` attribute:
-
-```
-[EventPipeProfiler(EventPipeProfile.CpuSampling)] // <-- Enables new profiler
-public class IntroEventPipeProfiler
-{
- [Benchmark]
- public void Sleep() => Thread.Sleep(2000);
-}
-```
-
-Once the benchmark run is finished, you get a `.speedscope.json` file that can be opened in [SpeedScope](https://www.speedscope.app/):
-
-
-
-The new profiler supports several modes:
-
-* `CpuSampling` - Useful for tracking CPU usage and general .NET runtime information. This is the default option.
-* `GcVerbose` - Tracks GC collections and samples object allocations.
-* `GcCollect` - Tracks GC collections only at very low overhead.
-* `Jit` - Logging when Just in time (JIT) compilation occurs. Logging of the internal workings of the Just In Time compiler. This is fairly verbose. It details decisions about interesting optimization (like inlining and tail call)
-
-Please see Wojciech Nagórski's [blog post](https://wojciechnagorski.com/2020/04/cross-platform-profiling-.net-code-with-benchmarkdotnet/) for all the details.
-
-Special thanks to [@WojciechNagorski](https://github.com/WojciechNagorski) for the implementation!
-
-## New fluent API
-
-We continue to improve our API and make it easier for reading and writing.
-The old API is still existing, but it is marked as obsolete and will be removed in the further library versions.
-The most significant changes:
-
-**Changes in Job configuration**
-
-
-
-**Changes in IConfig/ManualConfig**
-
-
-
-**Full fluent API**
-
-
-
-Special thanks to [@WojciechNagorski](https://github.com/WojciechNagorski) for the implementation!
-
-## Ref readonly support
-
-Now you can use `ref readonly` in benchmark signatures.
-Here is an example:
-
-```cs
-public class RefReadonlyBenchmark
-{
- static readonly int[] array = { 1 };
-
- [Benchmark]
- public ref readonly int RefReadonly() => ref RefReadonlyMethod();
-
- static ref readonly int RefReadonlyMethod() => ref array[0];
-}
-```
-
-Special thanks to [@adamsitnik](https://github.com/adamsitnik) for the implementation!
\ No newline at end of file
diff --git a/docs/_changelog/header/v0.13.0.md b/docs/_changelog/header/v0.13.0.md
deleted file mode 100644
index d0fca1471d..0000000000
--- a/docs/_changelog/header/v0.13.0.md
+++ /dev/null
@@ -1,368 +0,0 @@
-It's been a year since our last release. BenchmarkDotNet has been downloaded more than seven million times from [nuget.org](https://www.nuget.org/packages/BenchmarkDotNet/). It's more than we could have ever possibly imagined! Some could say, that it's also more than we can handle ;) That is why we wanted to once again thank [all the contributors](https://github.com/dotnet/BenchmarkDotNet/graphs/contributors) who helped us with `0.13.0` release!
-
-## Highlights
-
-In BenchmarkDotNet v0.13.0, we have supported various technologies:
-
-* .NET 5 and .NET 6 target framework monikers
-* .NET SDK installed via snap
-* SingleFile deployment
-* Xamarin applications
-* WASM applications
-* Mono AOT
-
-We have also introduced new features and improvements including:
-
-* Memory randomization
-* Method-specific job attributes
-* Sortable parameter columns
-* Customizable ratio column
-* Improved CoreRun and CoreRT support
-* Improved Hardware Counters support
-
-Of course, this release includes dozens of other improvements and bug fixes!
-
-## Supported technologies
-
-### .NET 5, .NET 6, SingleFile and snap
-
-At some point in time, `netcoreapp5.0` moniker was changed to `net5.0`, which required a fix on our side ([#1479](https://github.com/dotnet/BenchmarkDotNet/pull/1479), btw we love this kind of changes). Moreover, .NET 5 introduced platform-specific TMFs (example: `net5.0-windows10.0.19041.0`) which also required some extra work: [#1560](https://github.com/dotnet/BenchmarkDotNet/pull/1560), [#1691](https://github.com/dotnet/BenchmarkDotNet/pull/1691).
-
-
- In [#1523](https://github.com/dotnet/BenchmarkDotNet/pull/1523) support for .NET 6 was added.
-
-```xml
-net5.0;net5.0-windows10.0.19041.0;net6.0
-```
-
-In [#1686](https://github.com/dotnet/BenchmarkDotNet/pull/1686) [@am11](https://github.com/am11) has implemented support for **single file deployment** (supported in .NET 5 onwards).
-
-Last, but not least in [#1652](https://github.com/dotnet/BenchmarkDotNet/pull/1652) **snap** support has been implemented.
-
-```log
-adam@adsitnik-ubuntu:~/projects/BenchmarkDotNet/samples/BenchmarkDotNet.Samples$ dotnet50 run -c Release -f net5.0 --filter BenchmarkDotNet.Samples.IntroColdStart.Foo
-// Validating benchmarks:
-// ***** BenchmarkRunner: Start *****
-// ***** Found 1 benchmark(s) in total *****
-// ***** Building 1 exe(s) in Parallel: Start *****
-// start /snap/dotnet-sdk/112/dotnet restore /p:UseSharedCompilation=false /p:BuildInParallel=false /m:1 /p:Deterministic=true /p:Optimize=true in /home/adam/projects/BenchmarkDotNet/samples/BenchmarkDotNet.Samples/bin/Release/net5.0/9a018ee4-0f33-46dd-9093-01d3bf31233b
-// command took 1.49s and exited with 0
-// start /snap/dotnet-sdk/112/dotnet build -c Release --no-restore /p:UseSharedCompilation=false /p:BuildInParallel=false /m:1 /p:Deterministic=true /p:Optimize=true in /home/adam/projects/BenchmarkDotNet/samples/BenchmarkDotNet.Samples/bin/Release/net5.0/9a018ee4-0f33-46dd-9093-01d3bf31233b
-// command took 2.78s and exited with 0
-// ***** Done, took 00:00:04 (4.37 sec) *****
-// Found 1 benchmarks:
-// IntroColdStart.Foo: Job-NECTOD(IterationCount=5, RunStrategy=ColdStart)
-
-// **************************
-// Benchmark: IntroColdStart.Foo: Job-NECTOD(IterationCount=5, RunStrategy=ColdStart)
-// *** Execute ***
-// Launch: 1 / 1
-// Execute: /snap/dotnet-sdk/112/dotnet "9a018ee4-0f33-46dd-9093-01d3bf31233b.dll" --benchmarkName "BenchmarkDotNet.Samples.IntroColdStart.Foo" --job "IterationCount=5, RunStrategy=ColdStart" --benchmarkId 0 in /home/adam/projects/BenchmarkDotNet/samples/BenchmarkDotNet.Samples/bin/Release/net5.0/9a018ee4-0f33-46dd-9093-01d3bf31233b/bin/Release/net5.0
-```
-
-### Xamarin support
-
-Thanks to the contributions of the amazing [@jonathanpeppers](https://github.com/jonathanpeppers) BenchmarkDotNet supports Xamarin! The examples can be found in our repo: [iOS](https://github.com/dotnet/BenchmarkDotNet/tree/master/samples/BenchmarkDotNet.Samples.iOS), [Android](https://github.com/dotnet/BenchmarkDotNet/tree/master/samples/BenchmarkDotNet.Samples.Android).
-
-
-
-[#1360](https://github.com/dotnet/BenchmarkDotNet/pull/1360), [#1429](https://github.com/dotnet/BenchmarkDotNet/pull/1429), [#1434](https://github.com/dotnet/BenchmarkDotNet/pull/1434), [#1509](https://github.com/dotnet/BenchmarkDotNet/pull/1509)
-
-### WASM support
-
-Thanks to the work of [@naricc](https://github.com/naricc) you can now benchmark WASM using Mono Runtime! For more details, please refer to our [docs](https://benchmarkdotnet.org/articles/configs/toolchains.html#Wasm).
-
-
-
-[#1483](https://github.com/dotnet/BenchmarkDotNet/pull/1483), [#1498](https://github.com/dotnet/BenchmarkDotNet/pull/1498), [#1500](https://github.com/dotnet/BenchmarkDotNet/pull/1500), [#1501](https://github.com/dotnet/BenchmarkDotNet/pull/1501), [#1507](https://github.com/dotnet/BenchmarkDotNet/pull/1507), [#1592](https://github.com/dotnet/BenchmarkDotNet/pull/1592), [#1689](https://github.com/dotnet/BenchmarkDotNet/pull/1689).
-
-### Mono AOT support
-
-In another awesome contribution ([#1662](https://github.com/dotnet/BenchmarkDotNet/pull/1662)) [@naricc](https://github.com/naricc) has implemented Mono AOT support. The new toolchain supports doing Mono AOT runs with both the Mono-Mini compiler and the Mono-LLVM compiler (which uses LLVM on the back end).
-
- For more details, please go to our [docs](https://benchmarkdotnet.org/articles/configs/toolchains.html#MonoAotLLVM).
-
-## New features and improvements
-
-### Memory randomization
-
-In [#1587](https://github.com/dotnet/BenchmarkDotNet/pull/1587) [@adamsitnik](https://github.com/adamsitnik) has introduced a new, **experimental** feature called "Memory Randomization".
-
-This feature allows you to ask BenchmarkDotNet to randomize the memory alignment by allocating random-sized byte arrays between iterations and **call [GlobalSetup] before every benchmark iteration** and `[GlobalCleanup]` after every benchmark iteration.
-
-Sample benchmark:
-
-```cs
-public class IntroMemoryRandomization
-{
- [Params(512 * 4)]
- public int Size;
-
- private int[] _array;
- private int[] _destination;
-
- [GlobalSetup]
- public void Setup()
- {
- _array = new int[Size];
- _destination = new int[Size];
- }
-
- [Benchmark]
- public void Array() => System.Array.Copy(_array, _destination, Size);
-}
-```
-
-Without asking for the randomization, the objects are allocated in `[GlobalSetup]` and their unmodified addresses (and alignment) are used for all iterations (as long as they are not promoted to an older generation by the GC). This is typically the desired behavior, as it gives you very nice and flat distributions:
-
-```cmd
-dotnet run -c Release --filter IntroMemoryRandomization
-```
-
-```ini
--------------------- Histogram --------------------
-[502.859 ns ; 508.045 ns) | @@@@@@@@@@@@@@@
----------------------------------------------------
-```
-
-But if for some reason you are interested in getting a distribution that is better reflecting the "real-life" performance you can enable the randomization:
-
-```cmd
-dotnet run -c Release --filter IntroMemoryRandomization --memoryRandomization true
-```
-
-```ini
--------------------- Histogram --------------------
-[108.803 ns ; 213.537 ns) | @@@@@@@@@@@@@@@
-[213.537 ns ; 315.458 ns) |
-[315.458 ns ; 446.853 ns) | @@@@@@@@@@@@@@@@@@@@
-[446.853 ns ; 559.259 ns) | @@@@@@@@@@@@@@@
----------------------------------------------------
-```
-
-### Method-specific job attributes
-
-From now, all attributes that derive from `JobMutatorConfigBaseAttribute` ([full list](https://benchmarkdotnet.org/api/BenchmarkDotNet.Attributes.JobMutatorConfigBaseAttribute.html#BenchmarkDotNet_Attributes_JobMutatorConfigBaseAttribute)) can be applied to methods. You no longer have to move a method to a separate type to customize config for it.
-
-```cs
-[Benchmark]
-[WarmupCount(1)]
-public void SingleWarmupIteration()
-
-[Benchmark]
-[WarmupCount(9)]
-public void NineWarmupIterations()
-```
-
-### Sortable parameter columns
-
-In order to sort columns of parameters in the results table, you can use the Property `Priority` inside the params attribute. The priority range is `[Int32.MinValue;Int32.MaxValue]`, lower priorities will appear earlier in the column order. The default priority is set to `0`.
-
-```cs
-public class IntroParamsPriority
-{
- [Params(100)]
- public int A { get; set; }
-
- [Params(10, Priority = -100)]
- public int B { get; set; }
-
- [Benchmark]
- public void Benchmark() => Thread.Sleep(A + B + 5);
-}
-```
-
-| Method | B | A | Mean | Error | StdDev |
-|---------- |--- |---- |---------:|--------:|--------:|
-| Benchmark | 10 | 100 | 115.4 ms | 0.12 ms | 0.11 ms |
-
-
-This feature got implemented by [@JohannesDeml](https://github.com/JohannesDeml) in [#1612](https://github.com/dotnet/BenchmarkDotNet/pull/1612).
-
-### Customizable ratio column
-
-Now it's possible to customize the format of the ratio column.
-
-```cs
-[Config(typeof(Config))]
-public class IntroRatioStyle
-{
- [Benchmark(Baseline = true)]
- public void Baseline() => Thread.Sleep(1000);
-
- [Benchmark]
- public void Bar() => Thread.Sleep(150);
-
- [Benchmark]
- public void Foo() => Thread.Sleep(1150);
-
- private class Config : ManualConfig
- {
- public Config()
- {
- SummaryStyle = SummaryStyle.Default.WithRatioStyle(RatioStyle.Trend);
- }
- }
-}
-```
-
-```
-| Method | Mean | Error | StdDev | Ratio | RatioSD |
-|--------- |-----------:|--------:|--------:|-------------:|--------:|
-| Baseline | 1,000.6 ms | 2.48 ms | 0.14 ms | baseline | |
-| Bar | 150.9 ms | 1.30 ms | 0.07 ms | 6.63x faster | 0.00x |
-| Foo | 1,150.4 ms | 5.17 ms | 0.28 ms | 1.15x slower | 0.00x |
-```
-
-This feature was implemented in [#731](https://github.com/dotnet/BenchmarkDotNet/issues/721).
-
-### Improved CoreRun support
-
-BenchmarkDotNet was reporting invalid .NET Core version number when comparing performance using CoreRuns built from `dotnet/corefx` and `dotnet/runtime`. Fixed by [@adamsitnik](https://github.com/adamsitnik) in [#1580](https://github.com/dotnet/BenchmarkDotNet/pull/1580)
-
-In [#1552](https://github.com/dotnet/BenchmarkDotNet/pull/1552) [@stanciuadrian](https://github.com/stanciuadrian) has implemented support for all `GcMode` characteristics for `CoreRunToolchain`. Previously the settings were just ignored, now they are being translated to corresponding `COMPlus_*` env vars.
-
-### Improved CoreRT support
-
-CoreRT has moved from https://github.com/dotnet/corert/ to https://github.com/dotnet/runtimelab/tree/feature/NativeAOT and we had to update the default compiler version and nuget feed address. Moreover, there was a bug in `CoreRtToolchain` which was causing any additional native dependencies to not work.
-
-Big thanks to [@MichalStrehovsky](https://github.com/MichalStrehovsky), [@jkotas](https://github.com/jkotas) and [@kant2002](https://github.com/kant2002) for their help and support!
-
-[#1606](https://github.com/dotnet/BenchmarkDotNet/pull/1606), [#1643](https://github.com/dotnet/BenchmarkDotNet/pull/1643), [#1679](https://github.com/dotnet/BenchmarkDotNet/pull/1679)
-
-### Command-line argument support in BenchmarkRunner
-
-So far only `BenchmarkSwitcher` was capable of handling console line arguments. Thanks to [@chan18](https://github.com/chan18) `BenchmarkRunner` supports them as well ([#1292](https://github.com/dotnet/BenchmarkDotNet/pull/1292)):
-
-```cs
-public class Program
-{
- public static void Main(string[] args) => BenchmarkRunner.Run(typeof(Program).Assembly, args: args);
-}
-```
-
-### New API: ManualConfig.CreateMinimumViable
-
-`ManualConfig.CreateEmpty` creates a completely empty config. Without adding a column provider and a logger to the config the users won't see any results being printed. In [#1582](https://github.com/dotnet/BenchmarkDotNet/pull/1582) [@adamsitnik](https://github.com/adamsitnik) has introduced a new method that creates minimum viable config:
-
-```cs
-IConfig before = ManualConfig.CreateEmpty()
- .AddColumnProvider(DefaultColumnProviders.Instance)
- .AddLogger(ConsoleLogger.Default);
-
-IConfig after = ManualConfig.CreateMinimumViable();
-```
-
-### Benchmarking NuGet packages from custom feeds
-
-In [#1659](https://github.com/dotnet/BenchmarkDotNet/pull/1659/) [@workgroupengineering](https://github.com/workgroupengineering) added the possibility to indicate the source of the tested nuget package and whether it is a pre-release version.
-
-### Deterministic benchmark builds
-
-BenchmarkDotNet is now always enforcing Deterministic builds ([#1489](https://github.com/dotnet/BenchmarkDotNet/pull/1489)) and Optimizations enabled ([#1494](https://github.com/dotnet/BenchmarkDotNet/pull/1494)) which is a must-have if you are using custom build configurations. MSBuild enforces optimizations **only** for configurations that are named `Release` (the comparison is case-insensitive).
-
-```xml
-
-
-
-
-
-
-```
-
-```cs
-var config = DefaultConfig.Instance
- .AddJob(Job.Default.WithCustomBuildConfiguration("X").WithId("X").AsBaseline())
- .AddJob(Job.Default.WithCustomBuildConfiguration("Y").WithId("Y"));
-```
-
-[#1489](https://github.com/dotnet/BenchmarkDotNet/pull/1489), [#1494](https://github.com/dotnet/BenchmarkDotNet/pull/1494)
-
-### Improved Hardware Counters support
-
-BenchmarkDotNet is being used by the .NET Team to ensure that .NET is not regressing. More than three thousand benchmarks (they can be found [here](https://github.com/dotnet/performance/tree/main/src/benchmarks/micro)) are being executed multiple times a day on multiple hardware configs. Recently, .NET Team started to use `InstructionsRetired` to help to filter unstable benchmarks that report regressions despite not changing the number of instructions retired. This has exposed few bugs in Hardware Counters support in BenchmarkDotNet, which all got fixed by [@adamsitnik](https://github.com/adamsitnik) in [#1547](https://github.com/dotnet/BenchmarkDotNet/pull/1547) and [#1550](https://github.com/dotnet/BenchmarkDotNet/pull/1550). Moreover, we have **removed** the old [PmcDiagnoser](https://adamsitnik.com/Hardware-Counters-Diagnoser/) and extended [EtwProfiler](https://adamsitnik.com/ETW-Profiler/) with the hardware counters support. It's just much more accurate and futureproof. For details, please go to [#1548](https://github.com/dotnet/BenchmarkDotNet/pull/1548).
-
-How stable was `PmcDiagnoser` (same benchmarks run twice in a row on the same machine):
-
-| Method | Runtime | InstructionRetired/Op |
-|---------- |--------------------- |----------------------:|
-| Burgers_0 | .NET 5.0 | 845,746 |
-| Burgers_0 | .NET Core 2.1 | 30,154,151 |
-| Burgers_0 | .NET Framework 4.6.1 | 4,230,848 |
-
-| Method | Runtime | InstructionRetired/Op |
-|---------- |--------------------- |----------------------:|
-| Burgers_0 | .NET 5.0 | 34,154,524 |
-| Burgers_0 | .NET Core 2.1 | 246,534,203 |
-| Burgers_0 | .NET Framework 4.6.1 | 2,607,686 |
-
-How stable is `EtwProfiler`:
-
-| Method | Runtime | InstructionRetired/Op |
-|---------- |--------------------- |----------------------:|
-| Burgers_0 | .NET 5.0 | 3,069,978,261 |
-| Burgers_0 | .NET Core 2.1 | 3,676,000,000 |
-| Burgers_0 | .NET Framework 4.6.1 | 3,468,866,667 |
-
-| Method | Runtime | InstructionRetired/Op |
-|---------- |--------------------- |----------------------:|
-| Burgers_0 | .NET 5.0 | 3,066,810,000 |
-| Burgers_0 | .NET Core 2.1 | 3,674,666,667 |
-| Burgers_0 | .NET Framework 4.6.1 | 3,468,600,000 |
-
-Moreover, in [#1540](https://github.com/dotnet/BenchmarkDotNet/pull/1540) [@WojciechNagorski](https://github.com/WojciechNagorski) has added the removal of temporary files created by `EtwProfiler`.
-
-### Improved Troubleshooting
-
-We have the possibility to ask BDN to stop on the first error: `--stopOnFirstError true`.
-
-The problem was when the users had not asked for that, tried to run `n` benchmarks, all of them failed to build, and BDN was printing the same build error `n` times.
-
-In [#1672](https://github.com/dotnet/BenchmarkDotNet/pull/1672) [@adamsitnik](https://github.com/adamsitnik) has changed that, so when all the build fails, BDN stops after printing the first error.
-
-Moreover, we have also changed the default behavior for the failed builds of the boilerplate code. If the build fails, we don't remove the files. Previously we have required the users to pass `--keepFiles` to keep them. See [#1567](https://github.com/dotnet/BenchmarkDotNet/pull/1567) for more details and don't forget about the [Troubleshooting](https://benchmarkdotnet.org/articles/guides/troubleshooting.html) docs!
-
-### Docs and Samples improvements
-
-Big thanks to [@lukasz-pyrzyk](https://github.com/lukasz-pyrzyk), [@fleckert](https://github.com/fleckert), [@MarecekF](https://github.com/MarecekF), [@joostas](https://github.com/joostas), [@michalgalecki](https://github.com/michalgalecki), [@WojciechNagorski](https://github.com/WojciechNagorski), [@MendelMonteiro](https://github.com/MendelMonteiro), [@kevinsalimi](https://github.com/kevinsalimi), [@cedric-cf](https://github.com/cedric-cf), [@YohDeadfall](https://github.com/YohDeadfall), [@jeffhandley](https://github.com/jeffhandley) and [@JohannesDeml](https://github.com/JohannesDeml) who have improved our docs and samples!
-
-[#1463](https://github.com/dotnet/BenchmarkDotNet/pull/1463), [#1465](https://github.com/dotnet/BenchmarkDotNet/pull/1465), [#1508](https://github.com/dotnet/BenchmarkDotNet/pull/1508), [#1518](https://github.com/dotnet/BenchmarkDotNet/pull/1518), [#1554](https://github.com/dotnet/BenchmarkDotNet/pull/1554), [#1568](https://github.com/dotnet/BenchmarkDotNet/pull/1568), [#1601](https://github.com/dotnet/BenchmarkDotNet/pull/1601), [#1633](https://github.com/dotnet/BenchmarkDotNet/pull/1633), [#1645](https://github.com/dotnet/BenchmarkDotNet/pull/1645), [#1647](https://github.com/dotnet/BenchmarkDotNet/pull/1647), [#1657](https://github.com/dotnet/BenchmarkDotNet/pull/1657), [#1675](https://github.com/dotnet/BenchmarkDotNet/pull/1675), [#1676](https://github.com/dotnet/BenchmarkDotNet/pull/1676), [#1690](https://github.com/dotnet/BenchmarkDotNet/pull/1690).
-
-### Template improvements
-
-* Projects created out of our official templates might have been unexpectedly packed when running `dotnet pack` on the entire solution. In [#1584](https://github.com/dotnet/BenchmarkDotNet/pull/1584) [@kendaleiv](https://github.com/kendaleiv) has explicitly disabled packing for the template.
-* The template had `netcoreapp3.0` TFM hardcoded. This got fixed by [@https://github.com/ExceptionCaught](https://github.com/ExceptionCaught) in [#1630](https://github.com/dotnet/BenchmarkDotNet/pull/1630) and [#1632](https://github.com/dotnet/BenchmarkDotNet/pull/1632).
-* In [#1667](https://github.com/dotnet/BenchmarkDotNet/pull/1667) [@YohDeadfall](https://github.com/YohDeadfall) has changed the default debug type from `portable` to `pdbonly` (required by `DisassemblyDiagnoser`).
-
-## Bug fixes
-
- * Very long string `[Arguments]` and `[Params]` were causing BenchmarkDotNet to crash. Fixed by [@adamsitnik](https://github.com/adamsitnik) in [#1248](https://github.com/dotnet/BenchmarkDotNet/pull/1248) and [#1545](https://github.com/dotnet/BenchmarkDotNet/pull/1545/). So far trace file names were containing full benchmark names and arguments. Now if the name is too long, the trace file name is a hash (consistent for multiple runs of the same benchmark). The same goes for passing benchmark name by the host process to the benchmark process via command-line arguments.
-* `LangVersion` set to a non-numeric value like `latest` was crashing the build. Fixed by [@martincostello](https://github.com/martincostello) in [#1420](https://github.com/dotnet/BenchmarkDotNet/pull/1420).
-* Windows 10 November 201**9** was being recognized as 201**8**. Fixed by [@kapsiR](https://github.com/kapsiR) in [#1437](https://github.com/dotnet/BenchmarkDotNet/pull/1437).
-* Assemblies loaded via streams were not supported. Fixed by [@jeremyosterhoudt](https://github.com/jeremyosterhoudt) in [#1443](https://github.com/dotnet/BenchmarkDotNet/pull/1443).
-* [NativeMemoryProfiler](https://wojciechnagorski.com/2019/08/analyzing-native-memory-allocation-with-benchmarkdotnet/) was detecting small leaks that were false positives. Fixed by [@WojciechNagorski](https://github.com/WojciechNagorski) in [#1451](https://github.com/dotnet/BenchmarkDotNet/pull/1451) and [#1600](https://github.com/dotnet/BenchmarkDotNet/pull/1600).
-* [DisassemblyDiagnoser](https://adamsitnik.com/Disassembly-Diagnoser/) was crashing on Linux. Fixed by [@damageboy](https://github.com/damageboy) in [#1459](https://github.com/dotnet/BenchmarkDotNet/pull/1459).
-* Target framework moniker was being printed as toolchain name for Full .NET Framework benchmarks. Fixed by [@svick](https://github.com/svick) in [#1471](https://github.com/dotnet/BenchmarkDotNet/pull/1471).
-* `[ParamsSource]` returning `IEnumerable` was not working properly when combined with `[Arguments]`. Fixed by [@adamsitnik](https://github.com/adamsitnik) in [#1478](https://github.com/dotnet/BenchmarkDotNet/pull/1478).
-* `NullReferenceException` in `MultimodalDistributionAnalyzer`. Fixed by [@suslovk](https://github.com/suslovk) in [#1488](https://github.com/dotnet/BenchmarkDotNet/pull/1488).
-* `NotSupportedException` was being thrown if there was an encoding mismatch between host and benchmark process. Diagnosed by [@ChristophLindemann](https://github.com/ChristophLindemann) in [#1487](https://github.com/dotnet/BenchmarkDotNet/issues/1487), fixed by [@lovettchris](https://github.com/lovettchris) in [#1491](https://github.com/dotnet/BenchmarkDotNet/pull/1491).
-* `MissingMethodException` was being thrown in projects that referenced a newer version of [Iced](https://github.com/icedland/iced). Fixed by [@Symbai](https://github.com/Symbai) in [#1497](https://github.com/dotnet/BenchmarkDotNet/pull/1497) and in [#1502](https://github.com/dotnet/BenchmarkDotNet/pull/1502).
-* `AppendTargetFrameworkToOutputPath` set to `false` was not supported. Fixed by [@adamsitnik](https://github.com/adamsitnik) in [#1563](https://github.com/dotnet/BenchmarkDotNet/pull/1563)
-* A locking finalizer could have hanged benchmark process which would just print `// AfterAll` and never quit. Fixed by [@adamsitnik](https://github.com/adamsitnik) in [#1571](https://github.com/dotnet/BenchmarkDotNet/pull/1571). To prevent other hangs from happening, a timeout of `250ms` was added. If the process does not quit after running the benchmarks and waiting `250ms`, it's being force killed.
-* In some cases, `JsonExporter` was reporting `NaN` for some of the Statistics. This was breaking non-.NET JSON deserializers. Fixed by [@marcnet80](https://github.com/marcnet80) in [#1581](https://github.com/dotnet/BenchmarkDotNet/pull/1581).
-* `UnitType.Size` metrics were not using the provided number format. Fixed by [@jodydonetti](https://github.com/jodydonetti) in [#1569](https://github.com/dotnet/BenchmarkDotNet/pull/1569) and [#1618](https://github.com/dotnet/BenchmarkDotNet/pull/1618).
-* `MaxColumnWidth` setting was not used for type names. Fixed by [@JohannesDeml](https://github.com/JohannesDeml) in [#1609](https://github.com/dotnet/BenchmarkDotNet/pull/1609).
-* Current culture was not respected when formatting `Ratio` column values. Fixed by [@JohannesDeml](https://github.com/JohannesDeml) in [#1610](https://github.com/dotnet/BenchmarkDotNet/pull/1610).
-* BenchmarkDotNet was redirecting Standard Error of the benchmark process, which was causing deadlocks for benchmarks that were writing to it. Fixed by [@adamstinik](https://github.com/adamsitnik) in [#1631](https://github.com/dotnet/BenchmarkDotNet/pull/1631)
-* `DisassemblyDiagnoser` was failing to disassemble multiline source code. [@YohDeadfall](https://github.com/YohDeadfall) fixed that in [#1674](https://github.com/dotnet/BenchmarkDotNet/pull/1674).
-* In [#1644](https://github.com/dotnet/BenchmarkDotNet/pull/1644) [@adamstinik](https://github.com/adamsitnik) has fixed the inconsistency between benchmark filter and hint.
-
-## Removal of the dotnet global tool
-
-In [#1006](https://github.com/dotnet/BenchmarkDotNet/pull/1006) (0.11.4) we have introduced a new dotnet global tool.
-
-By looking at the number of reported bugs we got to the conclusion that the tool has not passed the test of time.
-
-Why? Because it was based entirely on dynamic assembly loading which is very hard to get right in .NET and the fact that we support all existing .NET Runtimes (.NET, .NET Core, Mono, CoreRT) made it even harder (if not impossible).
-
-**We have removed it and the old versions are no longer supported**. For more details, please refer to [#1572](https://github.com/dotnet/BenchmarkDotNet/pull/1572).
\ No newline at end of file
diff --git a/docs/_changelog/header/v0.13.1.md b/docs/_changelog/header/v0.13.1.md
deleted file mode 100644
index 5744a4f2e2..0000000000
--- a/docs/_changelog/header/v0.13.1.md
+++ /dev/null
@@ -1,15 +0,0 @@
-BenchmarkDotNet v0.13.1 is a service update with various bug fixes and improvements.
-
-## Highlights
-
-* S390x architecture support ([#1712](https://github.com/dotnet/BenchmarkDotNet/pull/1712))
-* Various WASM toolchain improvements ([#1719](https://github.com/dotnet/BenchmarkDotNet/pull/1719), [#1722](https://github.com/dotnet/BenchmarkDotNet/pull/1722), [#1729](https://github.com/dotnet/BenchmarkDotNet/pull/1729), [#1742](https://github.com/dotnet/BenchmarkDotNet/pull/1742), [#1743](https://github.com/dotnet/BenchmarkDotNet/pull/1743), [#1744](https://github.com/dotnet/BenchmarkDotNet/pull/1744), [#1746](https://github.com/dotnet/BenchmarkDotNet/pull/1746), [#1757](https://github.com/dotnet/BenchmarkDotNet/pull/1757), [#1763](https://github.com/dotnet/BenchmarkDotNet/pull/1763))
-* Support of CoreRT on 5.0.3XX SDK ([#1725](https://github.com/dotnet/BenchmarkDotNet/pull/1725))
-* Using Utf8 for reading from standard input (fixes a nasty encoding-related bug) ([#1735](https://github.com/dotnet/BenchmarkDotNet/pull/1735))
-* Adjusting WaitForExit timeouts ([#1745](https://github.com/dotnet/BenchmarkDotNet/pull/1745))
-* Support for returning unmanaged types from benchmarks with InProcessToolchain ([#1750](https://github.com/dotnet/BenchmarkDotNet/pull/1750))
-* Disabled Tiered JIT ([#1751](https://github.com/dotnet/BenchmarkDotNet/pull/1751))
-* A MemoryDiagnoser bug fix ([#1762](https://github.com/dotnet/BenchmarkDotNet/pull/1762))
-* Allow users to hide Gen X columns ([#1764](https://github.com/dotnet/BenchmarkDotNet/pull/1764))
-* Copy GC settings from host process to the benchmark process ([#1765](https://github.com/dotnet/BenchmarkDotNet/pull/1765))
-* Do not split surrogates in shortified parameter values ([#1705](https://github.com/dotnet/BenchmarkDotNet/pull/1705))
\ No newline at end of file
diff --git a/docs/_changelog/header/v0.13.10.md b/docs/_changelog/header/v0.13.10.md
deleted file mode 100644
index 9f55725d19..0000000000
--- a/docs/_changelog/header/v0.13.10.md
+++ /dev/null
@@ -1,3 +0,0 @@
-## Highlights
-
-Initial support of .NET 9 and minor bug fixes.
\ No newline at end of file
diff --git a/docs/_changelog/header/v0.13.11.md b/docs/_changelog/header/v0.13.11.md
deleted file mode 100644
index ef2d1b9579..0000000000
--- a/docs/_changelog/header/v0.13.11.md
+++ /dev/null
@@ -1,3 +0,0 @@
-## Highlights
-
-Small improvements.
\ No newline at end of file
diff --git a/docs/_changelog/header/v0.13.12.md b/docs/_changelog/header/v0.13.12.md
deleted file mode 100644
index c1d788166c..0000000000
--- a/docs/_changelog/header/v0.13.12.md
+++ /dev/null
@@ -1,10 +0,0 @@
-## Highlights
-
-The biggest highlight of this release if our new VSTest Adapter,
- which allows to run benchmarks as unit tests in your favorite IDE!
-The detailed guide can be found [here](xref:docs.vstest).
-
-This release also includes to a minor bug fix that caused incorrect job id generation:
- fixed job id generation ([#2491](https://github.com/dotnet/BenchmarkDotNet/pull/2491)).
-
-Also, the target framework in the BenchmarkDotNet templates was bumped to .NET 8.0.
\ No newline at end of file
diff --git a/docs/_changelog/header/v0.13.2.md b/docs/_changelog/header/v0.13.2.md
deleted file mode 100644
index dd5365e56e..0000000000
--- a/docs/_changelog/header/v0.13.2.md
+++ /dev/null
@@ -1,254 +0,0 @@
-## Highlights
-
-In BenchmarkDotNet v0.13.2, we have implemented support for:
-
-* .NET 7
-* NativeAOT
-* .NET Framework 4.8.1
-
-We have also introduced new features and improvements including:
-
-* Possibility to hide selected columns,
-* Allocation Ratio column,
-* Logging progress and estimated finish time,
-* ARM64 support for `BenchmarkDotNet.Diagnostics.Windows` package,
-* Printing Hardware Intrinsics information,
-* Glob filters support for DisassemblyDiagnoser.
-
-Of course, this release includes dozens of other improvements and bug fixes!
-
-Our special thanks go to [@mawosoft](https://github.com/mawosoft), [@YegorStepanov](https://github.com/YegorStepanov) and [@radical](https://github.com/radical) who fixed a LOT of really nasty bugs.
-
-## Supported technologies
-
-### .NET 7 and .NET Framework 4.8.1
-
-.NET 4.8.1 has been [released](https://devblogs.microsoft.com/dotnet/announcing-dotnet-framework-481/) earlier this month, while .NET 7 should land in autumn this year. Now you can use BenchmarkDotNet to compare both!
-
-```ini
-BenchmarkDotNet=v0.13.1.1845-nightly, OS=Windows 11 (10.0.22622.575)
-Microsoft SQ1 3.0 GHz, 1 CPU, 8 logical and 8 physical cores
-.NET SDK=7.0.100-preview.6.22352.1
- [Host] : .NET 7.0.0 (7.0.22.32404), Arm64 RyuJIT AdvSIMD
- Job-QJVIDT : .NET 7.0.0 (7.0.22.32404), Arm64 RyuJIT AdvSIMD
- Job-FNNXOY : .NET Framework 4.8.1 (4.8.9032.0), Arm64 RyuJIT
-```
-
-| Method | Runtime | Mean | Allocated |
-|-------------- |--------------------- |---------:|----------:|
-| BinaryTrees_2 | .NET 7.0 | 193.6 ms | 227.33 MB |
-| BinaryTrees_2 | .NET Framework 4.8.1 | 192.8 ms | 228.01 MB |
-
-Credit for adding .NET 7 support in [#1816](https://github.com/dotnet/BenchmarkDotNet/pull/1816) goes to [@am11](https://github.com/am11). [@adamsitnik](https://github.com/adamsitnik) implemented .NET 4.8.1 support in [#2044](https://github.com/dotnet/BenchmarkDotNet/pull/2044) and [#2067](https://github.com/dotnet/BenchmarkDotNet/pull/2067). Big thanks to [@MichalPetryka](https://github.com/MichalPetryka) who was using preview versions of BenchmarkDotNet and reported a bug related to .NET 4.8.1 support: [#2059](https://github.com/dotnet/BenchmarkDotNet/issues/2059) that got fixed before we released a new version.
-
-### NativeAOT
-
-We are really excited to see the experimental CoreRT project grow and become officially supported by Microsoft (under new name: NativeAOT)! You can read more about it [here](https://devblogs.microsoft.com/dotnet/announcing-dotnet-7-preview-3/#what-is-native-aot). Implementing and improving the support was a combined effort of multiple contributors that spawned across multiple repositories:
-* [@MichalStrehovsky](https://github.com/MichalStrehovsky): [#66290 in dotnet/runtime](https://github.com/dotnet/runtime/pull/66290), [#2020 in dotnet/BenchmarkDotNet](https://github.com/dotnet/BenchmarkDotNet/pull/2020), [#2046 in dotnet/BenchmarkDotNet](https://github.com/dotnet/BenchmarkDotNet/pull/2046)
-* [@hez2010](https://github.com/hez2010): [#66650 in dotnet/runtime](https://github.com/dotnet/runtime/pull/66650)
-* [@Beau-Gosse-dev](https://github.com/Beau-Gosse-dev): [#1955 in dotnet/BenchmarkDotNet](https://github.com/dotnet/BenchmarkDotNet/pull/1955)
-* [@adamsitnik](https://github.com/adamsitnik): [#1960 in dotnet/BenchmarkDotNet](https://github.com/dotnet/BenchmarkDotNet/pull/1960), [#1965 in dotnet/BenchmarkDotNet](https://github.com/dotnet/BenchmarkDotNet/pull/1965), [#1972 in dotnet/BenchmarkDotNet](https://github.com/dotnet/BenchmarkDotNet/pull/1972), [#1973 in dotnet/BenchmarkDotNet](https://github.com/dotnet/BenchmarkDotNet/pull/1973), [#1994 in dotnet/BenchmarkDotNet](https://github.com/dotnet/BenchmarkDotNet/pull/1994), [#1997 in dotnet/BenchmarkDotNet](https://github.com/dotnet/BenchmarkDotNet/pull/1997), [#2045 in dotnet/BenchmarkDotNet](https://github.com/dotnet/BenchmarkDotNet/pull/2045), [#2068 in dotnet/BenchmarkDotNet](https://github.com/dotnet/BenchmarkDotNet/pull/2068)
-* [@kant2002](https://github.com/kant2002): [#1976 in dotnet/BenchmarkDotNet](https://github.com/dotnet/BenchmarkDotNet/pull/1976), [#1979 in dotnet/BenchmarkDotNet](https://github.com/dotnet/BenchmarkDotNet/pull/1979)
-* [@jkotas](https://github.com/jkotas): [#68038 in dotnet/runtime](https://github.com/dotnet/runtime/pull/68038), [#68142 in dotnet/runtime](https://github.com/dotnet/runtime/pull/68142), [#68249 in dotnet/runtime](https://github.com/dotnet/runtime/pull/68249), [#68308 in dotnet/runtime](https://github.com/dotnet/runtime/pull/68308), [#68375 in dotnet/runtime](https://github.com/dotnet/runtime/pull/68375)
-* [@MichalPetryka](https://github.com/MichalPetryka): [#2065 in dotnet/BenchmarkDotNet](https://github.com/dotnet/BenchmarkDotNet/pull/2065)
-
-
-As every AOT solution, NativeAOT has some [limitations](https://github.com/dotnet/runtime/blob/main/src/coreclr/nativeaot/docs/limitations.md) like limited reflection support or lack of dynamic assembly loading. Because of that, the host process (what you run from command line) is never an AOT process, but just a regular .NET process. This process (called Host process) uses reflection to read benchmarks metadata (find all `[Benchmark]` methods etc.), generates a new project that references the benchmarks and compiles it using ILCompiler. The boilerplate code is not using reflection, so the project is built with `TrimmerDefaultAction=link` (we have greatly reduced build time thanks to that). Such compilation produces a native executable, which is later started by the Host process. This process (called Benchmark or Child process) performs the actual benchmarking and reports the results back to the Host process. By default BenchmarkDotNet uses the latest version of `Microsoft.DotNet.ILCompiler` to build the NativeAOT benchmark according to [this instructions](https://github.com/dotnet/runtime/blob/main/src/coreclr/nativeaot/docs/compiling.md). Moreover, BenchmarkDotNet by default uses current machines CPU features (change: [#1994](https://github.com/dotnet/BenchmarkDotNet/pull/1994), discussion: [#2061](https://github.com/dotnet/BenchmarkDotNet/issues/2061)) and if you don't like this behavior, you can [disable it](https://github.com/dotnet/BenchmarkDotNet/issues/2061#issuecomment-1203602177).
-
-This is why you need to:
-- install [pre-requisites](https://learn.microsoft.com/dotnet/core/deploying/native-aot/#prerequisites) required by NativeAOT compiler
-- target .NET to be able to run NativeAOT benchmarks (example: `net7.0 ` in the .csproj file)
-- run the app as a .NET process (example: `dotnet run -c Release -f net7.0`).
-- specify the NativeAOT runtime in an explicit way, either by using console line arguments `--runtimes nativeaot7.0` (the recommended approach), or by using`[SimpleJob]` attribute or by using the fluent Job config API `Job.ShortRun.With(NativeAotRuntime.Net70)`:
-
-```cmd
-dotnet run -c Release -f net7.0 --runtimes nativeaot7.0
-```
-
-For more examples please go to [docs](https://benchmarkdotnet.org/articles/configs/toolchains.html#nativeaot).
-
-```ini
-BenchmarkDotNet=v0.13.1.1845-nightly, OS=Windows 11 (10.0.22000.856/21H2)
-AMD Ryzen Threadripper PRO 3945WX 12-Cores, 1 CPU, 24 logical and 12 physical cores
-.NET SDK=7.0.100-rc.1.22423.16
- [Host] : .NET 7.0.0 (7.0.22.42223), X64 RyuJIT AVX2
- Job-KDVXET : .NET 7.0.0 (7.0.22.42223), X64 RyuJIT AVX2
- Job-HFRAGK : .NET 7.0.0-rc.1.22424.9, X64 NativeAOT AVX2
-```
-
-| Method | Runtime | Mean | Ratio | Allocated |
-|-------------- |-------------- |---------:|------:|----------:|
-| BinaryTrees_2 | .NET 7.0 | 95.06 ms | 1.00 | 227.33 MB |
-| BinaryTrees_2 | NativeAOT 7.0 | 90.32 ms | 0.96 | 227.33 MB |
-
-
-Some of .NET features are not supported by Native AOT, that is why you may want to filter them out using new `[AotFilter]` attribute:
-
-```cs
-[AotFilter("Currently not supported due to missing metadata.")]
-public class Xml_FromStream
-```
-
-## New features and improvements
-
-### Hiding Columns
-
-In [#1621](https://github.com/dotnet/BenchmarkDotNet/pull/1621) [@marcnet80](https://github.com/marcnet80) has reduced the number of columns displayed when multiple runtimes are being compared.
-
-
-
-In [#1890](https://github.com/dotnet/BenchmarkDotNet/pull/1890) [@YegorStepanov](https://github.com/YegorStepanov) has implemented a set of new APIs that allow for hiding columns. It's also exposed via `-h` and `--hide` command line arguments.
-
-```cs
-[MemoryDiagnoser] // adds Gen0, Gen1, Gen2 and Allocated Bytes columns
-[HideColumns(Column.Gen0, Column.Gen1, Column.Gen2)] // dont display GenX columns
-public class IntroHidingColumns
-{
- [Benchmark]
- public byte[] AllocateArray() => new byte[100_000];
-}
-```
-
-Sample results without `[HideColumns]`:
-
-
-| Method | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated |
-|-------------- |---------:|----------:|----------:|--------:|--------:|--------:|----------:|
-| AllocateArray | 3.303 us | 0.0465 us | 0.0435 us | 31.2462 | 31.2462 | 31.2462 | 97.69 KB |
-
-With:
-
-| Method | Mean | Error | StdDev | Allocated |
-|-------------- |---------:|----------:|----------:|----------:|
-| AllocateArray | 3.489 us | 0.0662 us | 0.0763 us | 97.69 KB |
-
-Imagine how much time [@YegorStepanov](https://github.com/YegorStepanov) has saved to all the people who so far were removing the columns manually from the results before publishing them on GitHub!
-
-### Allocation Ratio Column
-
-In [#1859](https://github.com/dotnet/BenchmarkDotNet/pull/1859) [@YegorStepanov](https://github.com/YegorStepanov) has added Allocation Ratio Column. It's enabled by default when `MemoryDiagnoser` is used and one of the benchmarks is marked as `[Benchmark(Baseline = true)]` or when there are multuple jobs defined and one of them is marked as baseline.
-
-```cs
-[MemoryDiagnoser]
-public class AllocationColumnSample
-{
- [Benchmark(Baseline = true)]
- [Arguments("test")]
- public string Builder(string value)
- {
- StringBuilder sb = new (value);
-
- for (int i = 0; i < 10; i++)
- sb.Append(value);
-
- return sb.ToString();
- }
-
- [Benchmark]
- [Arguments("test")]
- public string Concatenation(string value)
- {
- string result = value;
-
- for (int i = 0; i < 10; i++)
- result += value;
-
- return result;
- }
-}
-```
-
-| Method | value | Mean | Error | StdDev | Ratio | Gen 0 | Allocated | Alloc Ratio |
-|-------------- |------ |---------:|--------:|--------:|------:|-------:|----------:|------------:|
-| Builder | test | 127.9 ns | 0.49 ns | 0.43 ns | 1.00 | 0.0544 | 456 B | 1.00 |
-| Concatenation | test | 120.2 ns | 0.94 ns | 0.88 ns | 0.94 | 0.0908 | 760 B | 1.67 |
-
-### Progress and estimated finish time
-
-In [#1909](https://github.com/dotnet/BenchmarkDotNet/pull/1909) [@adamsitnik](https://github.com/adamsitnik) has added logging of progress and estimated finish time.
-
-```log
-// ** Remained 5211 (99.9%) benchmark(s) to run. Estimated finish 2022-08-25 22:26 (9h 7m from now) **
-```
-
-### arm64 support for BenchmarkDotNet.Diagnostics.Windows package
-
-Due to the [update](https://github.com/dotnet/BenchmarkDotNet/pull/2030) to [TraceEvent 3.0](https://www.nuget.org/packages/Microsoft.Diagnostics.Tracing.TraceEvent) `BenchmarkDotNet.Diagnostics.Windows` package has now arm64 support. Which means that you can use `EtwProfiler` and other ETW-based diagnosers on Windows arm64.
-
-It would not be possible without [@brianrob](https://github.com/brianrob) who implemented arm64 support for TraceEvent in [#1533](https://github.com/microsoft/perfview/pull/1533)!
-
-### Hardware Intrinsics information
-
-In [#2051](https://github.com/dotnet/BenchmarkDotNet/pull/2051) [@adamsitnik](https://github.com/adamsitnik) has extended the hardware information printed in the Summary table with Hardware Intrinsics information.
-
-
-
-Sine the space in Summary table is quite limited, we full information is printed only in the log:
-
-
-
-Special thanks to [@tannergooding](https://github.com/tannergooding) who provided a lot of [very valuable feedback](https://github.com/dotnet/BenchmarkDotNet/pull/2051#issuecomment-1194368152) and [@MichalPetryka](https://github.com/MichalPetryka) who contributed an improvement [#2066](https://github.com/dotnet/BenchmarkDotNet/pull/2066) for older runtimes.
-
-### Other improvements
-
-* WASM toolchain has received a lot of improvements from various .NET Team members: [#1769](https://github.com/dotnet/BenchmarkDotNet/pull/1769), [#1936](https://github.com/dotnet/BenchmarkDotNet/pull/1936), [#1938](https://github.com/dotnet/BenchmarkDotNet/pull/1938), [#1982](https://github.com/dotnet/BenchmarkDotNet/pull/1982).
-* Dependencies and TFMs updates: [#1805](https://github.com/dotnet/BenchmarkDotNet/pull/1805), [#1978](https://github.com/dotnet/BenchmarkDotNet/pull/1978), [#2012](https://github.com/dotnet/BenchmarkDotNet/pull/2012), [#2019](https://github.com/dotnet/BenchmarkDotNet/pull/2019), [#2035](https://github.com/dotnet/BenchmarkDotNet/pull/2035).
-* Ensure proper SummaryStyle handling implemented by [@mawosoft](https://github.com/mawosoft) in [#1828](https://github.com/dotnet/BenchmarkDotNet/pull/1828).
-* Preserving `EnablePreviewFeatures` project setting which gives the possibility to benchmark preview .NET features. Implemented by [@kkokosa](https://github.com/kkokosa) in [#1842](https://github.com/dotnet/BenchmarkDotNet/pull/1842).
-* CI: Using non-deprecated macOS pool on Azure Pipelines, implemented by [@akoeplinger](https://github.com/akoeplinger) in [#1847](https://github.com/dotnet/BenchmarkDotNet/pull/1847)
-* CI: Updating Cake to 2.0.0, adopting frosting project style. Implemented by [@AndreyAkinshin](https://github.com/AndreyAkinshin) in [#1865](https://github.com/dotnet/BenchmarkDotNet/pull/1865).
-* Detecting ReSharper's Dynamic Program Analysis. Implemented by [@adamsitnik](https://github.com/adamsitnik) in [#1874](https://github.com/dotnet/BenchmarkDotNet/pull/1874).
-* Preventing benchmark failure when some of the exporters fail. Implemented by [@epeshk](https://github.com/epeshk) in [#1902](https://github.com/dotnet/BenchmarkDotNet/pull/1902).
-* Don't use the diagnosers when benchmarking has failed. Implemented by [@adamsitnik](https://github.com/adamsitnik) in [#1903](https://github.com/dotnet/BenchmarkDotNet/pull/1903).
-* Ensuring the default order of benchmarks is the same as declared in source code. Implemented by [@adamsitnik](https://github.com/adamsitnik) in [#1907](https://github.com/dotnet/BenchmarkDotNet/pull/1907).
-* Making `BuildTimeout` configurable. Implemented by [@adamsitnik](https://github.com/adamsitnik) in [#1906](https://github.com/dotnet/BenchmarkDotNet/pull/1906).
-* Notify users about private methods with Setup/Cleanup attributes. Implemented by [@epeshk](https://github.com/epeshk) in [#1912](https://github.com/dotnet/BenchmarkDotNet/pull/1912).
-* Don't run Roslyn Analyzers for the generated code. Implemented by [@adamsitnik](https://github.com/adamsitnik) in [#1917](https://github.com/dotnet/BenchmarkDotNet/pull/1917).
-* Ensure `WorkloadActionUnroll` and similar are optimized if possible. Implemented by [@AndyAyersMS](https://github.com/AndyAyersMS) in [#1935](https://github.com/dotnet/BenchmarkDotNet/pull/1935).
-* Don't use blocking acknowledgments when there is no need to. Implemented by [@adamsitnik](https://github.com/adamsitnik) in [#1940](https://github.com/dotnet/BenchmarkDotNet/pull/1940).
-* Executor: Don't use Process.ExitCode, unless the process has exited. Implemented by [@radical](https://github.com/radical) in [#1947](https://github.com/dotnet/BenchmarkDotNet/pull/1947).
-* Revise heuristic for initial jitting. Implemented by [@AndyAyersMS](https://github.com/AndyAyersMS) in [#1949](https://github.com/dotnet/BenchmarkDotNet/pull/1949).
-* Allow logging build commands output. Implemented by [@radical](https://github.com/radical) in [#1950](https://github.com/dotnet/BenchmarkDotNet/pull/1950).
-* Change Mono AOT mode to Normal AOT with LLVM JIT fall back. Implemented by [@fanyang-mono](https://github.com/fanyang-mono) in [#1990](https://github.com/dotnet/BenchmarkDotNet/pull/1990).
-
-### Glob filters support for DisassemblyDiagnoser
-
-So far, the disassembler was always loading the type that was generated by BDN, searching for the benchmark method, disassembling it and when encountered direct method calls, disassembling the called methods as well (if their depth was lesser or equal to max configured depth).
-
-This was working fine, but only for direct method calls. For indirect, the disassembly was incomplete.
-
-In [#2072](https://github.com/dotnet/BenchmarkDotNet/pull/2072) [@adamsitnik](https://github.com/adamsitnik) has added the possibility to filter methods disassembled by the `DisassemblyDiagnoser`.
-
-The users can now pass `--disasmFilter $globPattern` and it's going to be applied to full signatures of all methods available for disassembling. Examples:
-* `--disasmFilter *System.Text*` - disassemble all `System.Text` methods.
-* `--disasmFilter *` - disassemble all possible methods.
-
-Moreover, [ClrMD](https://github.com/microsoft/clrmd) was updated to v2 ([#2040](https://github.com/dotnet/BenchmarkDotNet/pull/2040)) and few disassembler bugs have been fixed ([#2075](https://github.com/dotnet/BenchmarkDotNet/pull/2075), [#2078](https://github.com/dotnet/BenchmarkDotNet/pull/2078)). We are expecting that the disassembler will be more reliable now.
-
-### Docs and Samples improvements
-
-Big thanks to [@SnakyBeaky](https://github.com/SnakyBeaky), [@Distinctlyminty](https://github.com/Distinctlyminty), [@asaf92](https://github.com/asaf92), [@adamsitnik](https://github.com/adamsitnik) and [@eiriktsarpalis](https://github.com/eiriktsarpalis) who have improved our docs, samples and error messages!
-
-[#1776](https://github.com/dotnet/BenchmarkDotNet/pull/1776), [#1797](https://github.com/dotnet/BenchmarkDotNet/pull/1797), [#1850](https://github.com/dotnet/BenchmarkDotNet/pull/1850), [#1861](https://github.com/dotnet/BenchmarkDotNet/pull/1861), [#1939](https://github.com/dotnet/BenchmarkDotNet/pull/1939), [#1974](https://github.com/dotnet/BenchmarkDotNet/pull/1974), [#1997](https://github.com/dotnet/BenchmarkDotNet/pull/1997), [#2042](https://github.com/dotnet/BenchmarkDotNet/pull/2042), [#2050](https://github.com/dotnet/BenchmarkDotNet/pull/2050), [#2068](https://github.com/dotnet/BenchmarkDotNet/pull/2068).
-
-## Bug fixes
-
-* WASM: [#1811](https://github.com/dotnet/BenchmarkDotNet/pull/1811), [#1846](https://github.com/dotnet/BenchmarkDotNet/pull/1846), [#1916](https://github.com/dotnet/BenchmarkDotNet/pull/1916), [#1926](https://github.com/dotnet/BenchmarkDotNet/pull/1926), [#1932](https://github.com/dotnet/BenchmarkDotNet/pull/1932).
-* Diagnoser-provided Analysers weren't automatically added to Config. Fixed by [@mawosoft](https://github.com/mawosoft) in [#1790](https://github.com/dotnet/BenchmarkDotNet/pull/1790).
-* Exportes could been duplicated. Fixed by [@workgroupengineering](https://github.com/workgroupengineering) in [#1796](https://github.com/dotnet/BenchmarkDotNet/pull/1796).
-* Small bug in SummaryStyle. Fixed by [@mawosoft](https://github.com/mawosoft) in [#1801](https://github.com/dotnet/BenchmarkDotNet/pull/1801).
-* `InvalidOperationException/NullReferenceException` in `SmartParaemter`. Fixed by [@mawosoft](https://github.com/mawosoft) in [#1810](https://github.com/dotnet/BenchmarkDotNet/pull/1810).
-* Failures caused by colons in benchmark name. Fixed by [@ronbrogan](https://github.com/ronbrogan) in [#1823](https://github.com/dotnet/BenchmarkDotNet/pull/1823).
-* Some benchmark arugments were not properly escaped and were causing process launcher to crush. Fixed by [@adamsitnik](https://github.com/adamsitnik) in [#1841](https://github.com/dotnet/BenchmarkDotNet/pull/1841)
-* Invalid size specifiers for Memory and Disassembly diagnosers. Fixed by [@YegorStepanov](https://github.com/YegorStepanov) in [#1854](https://github.com/dotnet/BenchmarkDotNet/pull/1854) and [#1855](https://github.com/dotnet/BenchmarkDotNet/pull/1855).
-* Respect LogicalGroup order in DefaultOrderer. Fixed by [@AndreyAkinshin](https://github.com/AndreyAkinshin) in [#1866](https://github.com/dotnet/BenchmarkDotNet/pull/1866).
-* Endless loop in user interaction with redirected input. Fixed by [@tmds](https://github.com/tmds) in [#](https://github.com/dotnet/BenchmarkDotNet/pull/1870).
-* Broken power plan support. Fixed by [@YegorStepanov](https://github.com/YegorStepanov) in [#1885](https://github.com/dotnet/BenchmarkDotNet/pull/1885).
-* `BytesAllocatedPerOperation` was not being output by the JSON and XML exporters. Fixed by [#martincostello](https://github.com/martincostello) in [#1919](https://github.com/dotnet/BenchmarkDotNet/pull/1919).
-* Incorrect default InvocationCount in the summary table. Fixed by [@AndreyAkinshin](https://github.com/AndreyAkinshin) in [#1929](https://github.com/dotnet/BenchmarkDotNet/issues/1929).
-* Failed build output was printed in reverse order. Fixed by [@radical](https://github.com/radical) in [#1945](https://github.com/dotnet/BenchmarkDotNet/pull/1945).
-* Build failures due to `NETSDK1150`. Fixed by [@OlegOLK](https://github.com/OlegOLK) in [#1981](https://github.com/dotnet/BenchmarkDotNet/pull/1981).
-* `MetricCoumn` was not respecting provided units when formatting values. Fixed by [@mawosoft](https://github.com/mawosoft) in [#2033](https://github.com/dotnet/BenchmarkDotNet/pull/2033).
-* Generating invalid code that was causing benchmark failures. Fixed by [@mawosoft](https://github.com/mawosoft) in [#2041](https://github.com/dotnet/BenchmarkDotNet/pull/2041).
-* CI: non-master build branches were publishing artifacts to the CI feed. Fixed by [@mawosoft](https://github.com/mawosoft) in [#2047](https://github.com/dotnet/BenchmarkDotNet/pull/2047).
-* Comments in the project files were causing build failures. Fixed by [@mawosoft](https://github.com/mawosoft) in [#2056](https://github.com/dotnet/BenchmarkDotNet/pull/2056).
diff --git a/docs/_changelog/header/v0.13.3.md b/docs/_changelog/header/v0.13.3.md
deleted file mode 100644
index db1514edf6..0000000000
--- a/docs/_changelog/header/v0.13.3.md
+++ /dev/null
@@ -1,91 +0,0 @@
-## Highlights
-
-* New supported technologies
- * Add arm64 disassembler
- [#1422](https://github.com/dotnet/BenchmarkDotNet/issues/1422)
- [#2127](https://github.com/dotnet/BenchmarkDotNet/pull/2127)
- [#2107](https://github.com/dotnet/BenchmarkDotNet/pull/2107)
- [#2123](https://github.com/dotnet/BenchmarkDotNet/pull/2123)
- [#2070](https://github.com/dotnet/BenchmarkDotNet/issues/2070)
- [#2118](https://github.com/dotnet/BenchmarkDotNet/pull/2118)
- [#2119](https://github.com/dotnet/BenchmarkDotNet/pull/2119)
- [#2234](https://github.com/dotnet/BenchmarkDotNet/pull/2234)
- [#2222](https://github.com/dotnet/BenchmarkDotNet/pull/2222)
- [#2212](https://github.com/dotnet/BenchmarkDotNet/pull/2212)
- [9ee1/Capstone.NET#37](https://github.com/9ee1/Capstone.NET/pull/37)
- * Initial .NET 8 support
- [#2192](https://github.com/dotnet/BenchmarkDotNet/pull/2192)
- * .NET 6/7 MonoVM support
- [#2064](https://github.com/dotnet/BenchmarkDotNet/issues/2064)
- [#2142](https://github.com/dotnet/BenchmarkDotNet/pull/2142)
- [#2227](https://github.com/dotnet/BenchmarkDotNet/pull/2227)
- [#2230](https://github.com/dotnet/BenchmarkDotNet/pull/2230)
- * Armv6 and Ppc64le architectures support
- [#2216](https://github.com/dotnet/BenchmarkDotNet/issues/2216)
- [#2219](https://github.com/dotnet/BenchmarkDotNet/pull/2219)
-* Improved support
- * Improved WASM support
- [#2201](https://github.com/dotnet/BenchmarkDotNet/pull/2201)
- [#2099](https://github.com/dotnet/BenchmarkDotNet/issues/2099)
- [#2154](https://github.com/dotnet/BenchmarkDotNet/pull/2154)
- [#2112](https://github.com/dotnet/BenchmarkDotNet/pull/2112)
- * Improved NativeAOT support
- [#2095](https://github.com/dotnet/BenchmarkDotNet/pull/2095)
- [#2221](https://github.com/dotnet/BenchmarkDotNet/pull/2221)
- * Improved Android support
- [#2231](https://github.com/dotnet/BenchmarkDotNet/pull/2231)
- * 32-bit benchmarks can now handle addresses larger than 2GB with the help of `LargeAddressAware`
- [#1469](https://github.com/dotnet/BenchmarkDotNet/issues/1469)
- [#2145](https://github.com/dotnet/BenchmarkDotNet/pull/2145)
- * Support 64bit affinity masks
- [#2211](https://github.com/dotnet/BenchmarkDotNet/issues/2211)
- [#2228](https://github.com/dotnet/BenchmarkDotNet/pull/2228)
-* New features
- * Add `ExceptionDiagnoser`
- [#1736](https://github.com/dotnet/BenchmarkDotNet/issues/1736)
- [#2169](https://github.com/dotnet/BenchmarkDotNet/pull/2169)
- [#2182](https://github.com/dotnet/BenchmarkDotNet/pull/2182)
- * Add `PerfCollectProfiler`
- [#2117](https://github.com/dotnet/BenchmarkDotNet/pull/2117)
- * Incremental benchmark execution with the help of `--resume`
- [#1799](https://github.com/dotnet/BenchmarkDotNet/issues/1799)
- [#2164](https://github.com/dotnet/BenchmarkDotNet/pull/2164)
- * Taskbar progress
- [#2102](https://github.com/dotnet/BenchmarkDotNet/issues/2102)
- [#2158](https://github.com/dotnet/BenchmarkDotNet/pull/2158)
- [#2140](https://github.com/dotnet/BenchmarkDotNet/pull/2140)
- * Support `--noForcedGCs` to avoid forced GC between benchmark runs
- [#2101](https://github.com/dotnet/BenchmarkDotNet/pull/2101)
- * Added apples to apples comparison mode
- [#2116](https://github.com/dotnet/BenchmarkDotNet/pull/2116)
- [#2193](https://github.com/dotnet/BenchmarkDotNet/pull/2193)
- * Communication between the host process and the benchmark process is now using pipes instead of output parsing
- [#2092](https://github.com/dotnet/BenchmarkDotNet/pull/2092)
- [#1933](https://github.com/dotnet/BenchmarkDotNet/issues/1933)
- [#2189](https://github.com/dotnet/BenchmarkDotNet/issues/2189)
- [#2207](https://github.com/dotnet/BenchmarkDotNet/pull/2207)
- [#2200](https://github.com/dotnet/BenchmarkDotNet/pull/2200)
-* Dozens of bugfixes
-
-## Special Thanks
-
-We would like to highlight some important contributors who helped us with this release:
-
-1. [OpenHack'22 (devexperts.com)](https://code.devexperts.com/event/openhack22) hackathon sponsored by the DevExperts company.
- As part of this hackathon we have received following PRs:
- * [#2132](https://github.com/dotnet/BenchmarkDotNet/pull/2132) fix: include argument and param names in --filter (by [@blouflashdb](https://github.com/blouflashdb))
- * [#2140](https://github.com/dotnet/BenchmarkDotNet/pull/2140) Update console title with benchmark information (by [@franciscomoloureiro](https://github.com/franciscomoloureiro))
- * [#2142](https://github.com/dotnet/BenchmarkDotNet/pull/2142) Issue 2064: Mono70 moniker (by [@Serg046](https://github.com/Serg046))
- * [#2148](https://github.com/dotnet/BenchmarkDotNet/pull/2148) adding validation errors when the benchmarks are unsupported (by [@emanuel-v-r](https://github.com/emanuel-v-r))
- * [#2160](https://github.com/dotnet/BenchmarkDotNet/pull/2160) Corrected logic to restore foreground color in ConsoleLogger.cs (by [@farQtech](https://github.com/farQtech))
- * [#2164](https://github.com/dotnet/BenchmarkDotNet/pull/2164) 1799 adding resume arg (by [@melias](https://github.com/melias))
- * [#2169](https://github.com/dotnet/BenchmarkDotNet/pull/2169) Issue #1736: Add ExceptionDiagnoser (by [@Serg046](https://github.com/Serg046))
- * [#2161](https://github.com/dotnet/BenchmarkDotNet/pull/2161) add quiet logger (by [@franciscomoloureiro](https://github.com/franciscomoloureiro)) **(not merged yet)**
- * [#2171](https://github.com/dotnet/BenchmarkDotNet/pull/2171) Issue #1024: Calculate baseline by the fastest benchmark (by [@Serg046](https://github.com/Serg046)) **(not merged yet)**
-
-2. Jan Vorlicek helped to implement arm64 disassembler during an internal Microsoft Hackathon:
- * [#2107](https://github.com/dotnet/BenchmarkDotNet/pull/2107) Implement TryGetReferencedAddress for relative branches (by [@janvorli](https://github.com/janvorli))
- * [#2123](https://github.com/dotnet/BenchmarkDotNet/pull/2123) Added other arm64 constant form extraction plus other changes (by [@janvorli](https://github.com/janvorli))
-
-3. Ahmed Garhy (maintainer of Capstone.NET) helped to improve Capstone.NET, which was need to implement arm64 disassembler:
- * [9ee1/Capstone.NET#37](https://github.com/9ee1/Capstone.NET/pull/37) Sign Assembly with a Strong Name (by [@9ee1](https://github.com/9ee1))
\ No newline at end of file
diff --git a/docs/_changelog/header/v0.13.4.md b/docs/_changelog/header/v0.13.4.md
deleted file mode 100644
index 3ae8407ee4..0000000000
--- a/docs/_changelog/header/v0.13.4.md
+++ /dev/null
@@ -1,26 +0,0 @@
-## Highlights
-
-* Fixed LINQPad support
- [#2237](https://github.com/dotnet/BenchmarkDotNet/issues/2237)
- [#2238](https://github.com/dotnet/BenchmarkDotNet/pull/2238)
-* New `JitStatsDiagnoser`
- [#2243](https://github.com/dotnet/BenchmarkDotNet/pull/2243)
-* Minor documentation improvements
- [#2206](https://github.com/dotnet/BenchmarkDotNet/pull/2206)
- [#2218](https://github.com/dotnet/BenchmarkDotNet/pull/2218)
-
-## JitStatsDiagnoser
-
-This new diagnoser introduced in ([#2243](https://github.com/dotnet/BenchmarkDotNet/pull/2243)) allows getting advanced JIT statistics.
-
-Sample usage:
-
-```cmd
-dotnet run -c Release -f net7.0 --filter *IntroBasic.Sleep --profiler jit
-```
-
-Result:
-
-| Method | Mean | Error | StdDev | Methods JITted | Methods Tiered | JIT allocated memory |
-|------- |---------:|---------:|---------:|---------------:|---------------:|---------------------:|
-| Sleep | 15.53 ms | 0.034 ms | 0.032 ms | 1,102 | 15 | 221,736 B |
\ No newline at end of file
diff --git a/docs/_changelog/header/v0.13.5.md b/docs/_changelog/header/v0.13.5.md
deleted file mode 100644
index c3915a4f6d..0000000000
--- a/docs/_changelog/header/v0.13.5.md
+++ /dev/null
@@ -1,34 +0,0 @@
-## Highlights
-
-* Improved `JitStatsDiagnoser`.
- This diagnoser was added in v0.13.4, it shows various stats from the JIT compiler that were collected during entire benchmark run
- (amount of JITted methods,
- amount of [tiered methods](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-core-3-0#tiered-compilation),
- how much memory JIT allocated during the benchmark).
- In this release, we improved metric collection
- ([#2246](https://github.com/dotnet/BenchmarkDotNet/pull/2246),
- [e715d5](https://github.com/dotnet/BenchmarkDotNet/commit/e715d5bb63984fca65120d9a497f7d16395f9e5b))
- and added the `[JitStatsDiagnoser]` attribute
- ([#2250](https://github.com/dotnet/BenchmarkDotNet/pull/2250)
- [512413](https://github.com/dotnet/BenchmarkDotNet/commit/512413ceb24077154bdf6d6306138accffe64c7a)).
-* Enable strong-named assemblies in the released NuGet packages
- [#2258](https://github.com/dotnet/BenchmarkDotNet/issues/2258)
- [#2263](https://github.com/dotnet/BenchmarkDotNet/pull/2263)
- [5cd288](https://github.com/dotnet/BenchmarkDotNet/commit/5cd288996ca13292fcf638be299c097a600aea7b)
-* Avoid keeping referenced values returned from a benchmark in memory
- [#1942](https://github.com/dotnet/BenchmarkDotNet/issues/1942)
- [#2191](https://github.com/dotnet/BenchmarkDotNet/pull/2191)
- [ff5dbe](https://github.com/dotnet/BenchmarkDotNet/commit/ff5dbe662478f547e4be8d734eaeb6a106f40875)
-* Keep generated files when MSBuild bin log is requested
- [#2252](https://github.com/dotnet/BenchmarkDotNet/issues/2252)
- [#2254](https://github.com/dotnet/BenchmarkDotNet/pull/2254)
- [d3fbc0](https://github.com/dotnet/BenchmarkDotNet/commit/d3fbc03d6dabeb52f23c6b7e50287150e66957cc)
-* Add `Id` for `UnresolvedDiagnoser` (an exception fix)
- [#2251](https://github.com/dotnet/BenchmarkDotNet/pull/2251)
- [a992b5](https://github.com/dotnet/BenchmarkDotNet/commit/a992b57490e844acf587bc2e01b08a7040dbc8e2)
-* Add brand names for Windows 22H2 and macOS 13
- [86f212](https://github.com/dotnet/BenchmarkDotNet/commit/86f212b79e297d87d3942e4c50130fe6e214f3c8)
- [0c2699](https://github.com/dotnet/BenchmarkDotNet/commit/0c26996ea685a99068aca71e7ae547b0851d3c64)
-* Remove deprecated `InProcessToolchain`
- [#2248](https://github.com/dotnet/BenchmarkDotNet/pull/2248)
- [615384](https://github.com/dotnet/BenchmarkDotNet/commit/615384d2553434d7f35c03ab3174d761f82c6c2d)
diff --git a/docs/_changelog/header/v0.13.6.md b/docs/_changelog/header/v0.13.6.md
deleted file mode 100644
index 7667d404ce..0000000000
--- a/docs/_changelog/header/v0.13.6.md
+++ /dev/null
@@ -1,23 +0,0 @@
-## Highlights
-
-* New [BenchmarkDotNet.Diagnostics.dotTrace](https://www.nuget.org/packages/BenchmarkDotNet.Diagnostics.dotTrace) NuGet package.
- Once this package is installed, you can annotate your benchmarks with the `[DotTraceDiagnoser]` and get a [dotTrace](https://www.jetbrains.com/profiler/) performance snapshot at the end of the benchmark run.
- [#2328](https://github.com/dotnet/BenchmarkDotNet/pull/2328)
-* Updated documentation website.
- We migrated to [docfx](https://dotnet.github.io/docfx/) 2.67 and got the refreshed modern template based on bootstrap 5 with dark/light theme switcher.
-* Updated [BenchmarkDotNet.Templates](https://www.nuget.org/packages/BenchmarkDotNet.Templates).
- Multiple issues were resolved, now you can create new benchmark projects from terminal or your favorite IDE.
- [#1658](https://github.com/dotnet/BenchmarkDotNet/issues/1658)
- [#1881](https://github.com/dotnet/BenchmarkDotNet/issues/1881)
- [#2149](https://github.com/dotnet/BenchmarkDotNet/issues/2149)
- [#2338](https://github.com/dotnet/BenchmarkDotNet/pull/2338)
-* Response file support.
- Now it's possible to pass additional arguments to BenchmarkDotNet using `@filename` syntax.
- [#2320](https://github.com/dotnet/BenchmarkDotNet/pull/2320)
- [#2348](https://github.com/dotnet/BenchmarkDotNet/pull/2348)
-* Custom runtime support.
- [#2285](https://github.com/dotnet/BenchmarkDotNet/pull/2285)
-* Introduce CategoryDiscoverer, see [`IntroCategoryDiscoverer`](xref:BenchmarkDotNet.Samples.IntroCategoryDiscoverer).
- [#2306](https://github.com/dotnet/BenchmarkDotNet/issues/2306)
- [#2307](https://github.com/dotnet/BenchmarkDotNet/pull/2307)
-* Multiple bug fixes.
\ No newline at end of file
diff --git a/docs/_changelog/header/v0.13.7.md b/docs/_changelog/header/v0.13.7.md
deleted file mode 100644
index 5a8487b639..0000000000
--- a/docs/_changelog/header/v0.13.7.md
+++ /dev/null
@@ -1,3 +0,0 @@
-## Highlights
-
-This release contains important bug fixes listed below.
\ No newline at end of file
diff --git a/docs/_changelog/header/v0.13.8.md b/docs/_changelog/header/v0.13.8.md
deleted file mode 100644
index b9222853e2..0000000000
--- a/docs/_changelog/header/v0.13.8.md
+++ /dev/null
@@ -1,3 +0,0 @@
-## Highlights
-
-This release contains important bug fixes.
\ No newline at end of file
diff --git a/docs/_changelog/header/v0.13.9.md b/docs/_changelog/header/v0.13.9.md
deleted file mode 100644
index 07e4813c9f..0000000000
--- a/docs/_changelog/header/v0.13.9.md
+++ /dev/null
@@ -1,3 +0,0 @@
-## Highlights
-
-This release contains bug fixes.
\ No newline at end of file
diff --git a/docs/_changelog/header/v0.14.0.md b/docs/_changelog/header/v0.14.0.md
deleted file mode 100644
index 1c2f6693ce..0000000000
--- a/docs/_changelog/header/v0.14.0.md
+++ /dev/null
@@ -1,15 +0,0 @@
-## Highlights
-
-* Introduce `BenchmarkDotNet.Diagnostics.dotMemory` [#2549](https://github.com/dotnet/BenchmarkDotNet/pull/2549): memory allocation profile of your benchmarks using [dotMemory](https://www.jetbrains.com/dotmemory/), see @BenchmarkDotNet.Samples.IntroDotMemoryDiagnoser
-* Introduce `BenchmarkDotNet.Exporters.Plotting` [#2560](https://github.com/dotnet/BenchmarkDotNet/pull/2560): plotting via [ScottPlot](https://scottplot.net/) (initial version)
-* Multiple bugfixes
-* The default build toolchains have been updated to pass `IntermediateOutputPath`, `OutputPath`, and `OutDir` properties to the `dotnet build` command. This change forces all build outputs to be placed in a new directory generated by BenchmarkDotNet, and fixes many issues that have been reported with builds. You can also access these paths in your own `.csproj` and `.props` from those properties if you need to copy custom files to the output.
-
-## Bug fixes
-
-* Fixed multiple build-related bugs including passing MsBuildArguments and .Net 8's `UseArtifactsOutput`.
-
-## Breaking Changes
-
-* `DotNetCliBuilder` removed `retryFailedBuildWithNoDeps` constructor option.
-* `DotNetCliCommand` removed `RetryFailedBuildWithNoDeps` property and `BuildNoRestoreNoDependencies()` and `PublishNoBuildAndNoRestore()` methods (replaced with `PublishNoRestore()`).
\ No newline at end of file
diff --git a/docs/_changelog/header/v0.8.2.md b/docs/_changelog/header/v0.8.2.md
deleted file mode 100644
index b06d5ec1c2..0000000000
--- a/docs/_changelog/header/v0.8.2.md
+++ /dev/null
@@ -1,19 +0,0 @@
-* Changes in the Summary table
- * Summary table now supports ResultExtenders that can add new column to the table
- * Now we use [StandardError](https://en.wikipedia.org/wiki/Standard_error) (aka `Error`) as the main accuracy metric
- * Columns `op/s`, `StdDev` are disabled by default (you can add it via ResultExtenders)
-* Statistic improvements, now you have detailed statistic in the console log like follows:
-```
-Mean = 118.5298 us, StdError = 1.2863 us (N = 30, StdDev = 7.0454 us)
-Min = 109.1602 us, Median = 117.1794 us, Max = 132.5764 us
-IQR = 10.1244 us, LowerFence = 98.0834 us, UpperFence = 138.5810 us
-ConfidenceInterval = [116.0086 us; 121.0510 us] (CI 95%)
-```
-* Added the `Baseline` feature, see [#64](https://github.com/PerfDotNet/BenchmarkDotNet/issues/64)
-* Export improvements, now you have files `-report-github.md`, `-report-stackoverflow.md` for easy publishing results on GitHub and StackOverflow.
-* Basic plotting. Added new `BenchmarkRPlotExporter` that creates `BuildPlots.R` in the bin directory. It is an R script that generates boxplot and barplot for your benchmarks (you should have installed R with defined `R_HOME` environment variable)
-* Updated environment info
- * Added Stopwatch `Frequency` and `Resolution`
- * Split common benchmark properties (like `Mode`, `Platform`, `Runtime`) in several lines (3 properties per line)
-* Log improvements: add total time, statistics, list of exported files
-* Bug fixes
\ No newline at end of file
diff --git a/docs/_changelog/header/v0.9.0.md b/docs/_changelog/header/v0.9.0.md
deleted file mode 100644
index 3d14c4b30c..0000000000
--- a/docs/_changelog/header/v0.9.0.md
+++ /dev/null
@@ -1,2 +0,0 @@
-* New API
-* Autodetermination of amount iteration for warmup/target idle/main iterations, duration of iteration, amount of CLR launches.
\ No newline at end of file
diff --git a/docs/_changelog/header/v0.9.2.md b/docs/_changelog/header/v0.9.2.md
deleted file mode 100644
index 44d8bb5fe0..0000000000
--- a/docs/_changelog/header/v0.9.2.md
+++ /dev/null
@@ -1 +0,0 @@
-* Dnx451 support (Closed [#51](https://github.com/PerfDotNet/BenchmarkDotNet/issues/51), Merged [#87](https://github.com/PerfDotNet/BenchmarkDotNet/issues/87))
\ No newline at end of file
diff --git a/docs/_changelog/header/v0.9.3.md b/docs/_changelog/header/v0.9.3.md
deleted file mode 100644
index f7614fbe00..0000000000
--- a/docs/_changelog/header/v0.9.3.md
+++ /dev/null
@@ -1 +0,0 @@
-* CoreCLR support (Closed [#52](https://github.com/PerfDotNet/BenchmarkDotNet/issues/52), Merged [#113](https://github.com/PerfDotNet/BenchmarkDotNet/issues/113))
\ No newline at end of file
diff --git a/docs/_changelog/header/v0.9.4.md b/docs/_changelog/header/v0.9.4.md
deleted file mode 100644
index 63c47b49d0..0000000000
--- a/docs/_changelog/header/v0.9.4.md
+++ /dev/null
@@ -1,9 +0,0 @@
-* Improved messages about error in benchmarks, see [#104](https://github.com/PerfDotNet/BenchmarkDotNet/issues/104)
-* Natural sort order, see [#92](https://github.com/PerfDotNet/BenchmarkDotNet/issues/92), [#95](https://github.com/PerfDotNet/BenchmarkDotNet/issues/95), [#97](https://github.com/PerfDotNet/BenchmarkDotNet/issues/97)
-* Improved `double`/`float`/`decimal`/`enum` support for Params, see [#96](https://github.com/PerfDotNet/BenchmarkDotNet/issues/96), [#105](https://github.com/PerfDotNet/BenchmarkDotNet/issues/105), [#116](https://github.com/PerfDotNet/BenchmarkDotNet/issues/116)
-* Now environment info includes information about `HardwareTimerKind` and `JitModules`
-* Added `DryConfig`
-* Improved export performance, closed [#119](https://github.com/PerfDotNet/BenchmarkDotNet/issues/119), merged [#124](https://github.com/PerfDotNet/BenchmarkDotNet/pull/124)
-* Better cmd-line discoverability (see [#78](https://github.com/PerfDotNet/BenchmarkDotNet/issues/78)), e.g. run `Benchmark.exe --help` and some useful information will be printed
-* Supporting all kinds of references for generated project (exact version, custom paths, GAC, sub-folders, dependent assemblies not copied), closed [#41](https://github.com/PerfDotNet/BenchmarkDotNet/issues/41), [#49](https://github.com/PerfDotNet/BenchmarkDotNet/issues/49), [#72](https://github.com/PerfDotNet/BenchmarkDotNet/issues/72), [#123](https://github.com/PerfDotNet/BenchmarkDotNet/issues/123), merged [#125](https://github.com/PerfDotNet/BenchmarkDotNet/pull/125)
-* Friendliness to LinqPad restored, closed [#66](https://github.com/PerfDotNet/BenchmarkDotNet/issues/66), merged [#125](https://github.com/PerfDotNet/BenchmarkDotNet/pull/125)
\ No newline at end of file
diff --git a/docs/_changelog/header/v0.9.5.md b/docs/_changelog/header/v0.9.5.md
deleted file mode 100644
index 1d429dcc35..0000000000
--- a/docs/_changelog/header/v0.9.5.md
+++ /dev/null
@@ -1,11 +0,0 @@
-* Added validators, JitOptimizationsValidator detects all non-optimzied dlls that were referenced [#134](https://github.com/PerfDotNet/BenchmarkDotNet/issues/134)
-* **Strong naming** [#101](https://github.com/PerfDotNet/BenchmarkDotNet/issues/101)
-* Add `IOrderProvider` [#107](https://github.com/PerfDotNet/BenchmarkDotNet/issues/107)
-* **Putting all the generated artifacts in a separate folder: ./BenchmarkDotNet.Artifacts/results** and ./BenchmarkDotNet.Artifacts/bin [#94](https://github.com/PerfDotNet/BenchmarkDotNet/issues/94)
-* Printing dotnet cli version for .NET Core and Dnx451, informing user when not installed. Closed [#128](https://github.com/PerfDotNet/BenchmarkDotNet/issues/128)
-* Supporting assembly redirects [#67](https://github.com/PerfDotNet/BenchmarkDotNet/issues/67)
-* Changed used msbuild version: 12 for .NET 4.5 (VS 2013), 14 for .NET 4.6 (VS 2015). Closed [#132](https://github.com/PerfDotNet/BenchmarkDotNet/issues/132) and [#137](https://github.com/PerfDotNet/BenchmarkDotNet/issues/137)
-* Switched to new ‘dotnet’ target framework monikers (dotnet5.4 instead of dnxcore50), [why](https://github.com/aspnet/Announcements/issues/98)
-* dnx452, dnx46, net462 support added
-* Executing single Benchmark for multiple Runtimes also with Diagnoser attached (see [#117](https://github.com/PerfDotNet/BenchmarkDotNet/pull/117))
-* Misc minor changes
\ No newline at end of file
diff --git a/docs/_changelog/header/v0.9.6.md b/docs/_changelog/header/v0.9.6.md
deleted file mode 100644
index b69b6591bd..0000000000
--- a/docs/_changelog/header/v0.9.6.md
+++ /dev/null
@@ -1,3 +0,0 @@
-* Added Percentiles (see [#164](https://github.com/PerfDotNet/BenchmarkDotNet/pull/164))
-* Added support for Json export (see [#84](https://github.com/PerfDotNet/BenchmarkDotNet/issues/84))
-* Bugfixes
\ No newline at end of file
diff --git a/docs/_changelog/header/v0.9.7.md b/docs/_changelog/header/v0.9.7.md
deleted file mode 100644
index 560d956baf..0000000000
--- a/docs/_changelog/header/v0.9.7.md
+++ /dev/null
@@ -1,2 +0,0 @@
-* .NET Core RC2 support (see [#187](https://github.com/PerfDotNet/BenchmarkDotNet/pull/187))
-* Bugfixes
\ No newline at end of file
diff --git a/docs/_changelog/header/v0.9.8.md b/docs/_changelog/header/v0.9.8.md
deleted file mode 100644
index 055ca19bf6..0000000000
--- a/docs/_changelog/header/v0.9.8.md
+++ /dev/null
@@ -1,8 +0,0 @@
-* CoreCLR RTM support (see [#216](https://github.com/PerfDotNet/BenchmarkDotNet/issues/216)). **Breaking change:** we have dropped dnx451 and dnxcore50 support.
-* Migration from MSBuild to Roslyn, which supports Mono on Linux and MacOS (see [#149](https://github.com/PerfDotNet/BenchmarkDotNet/issues/149)). **Breaking change:** we have dropped .NET 4.0 support.
-* Ability to manage GC mode: turn on/off the Server/Concurrent GC modes, extend to CPU groups, set gcAllowVeryLargeObjects and avoid BenchmarkDotNet from forcing GC.Collect (see [#188](https://github.com/PerfDotNet/BenchmarkDotNet/issues/188), [#76](https://github.com/PerfDotNet/BenchmarkDotNet/issues/76) and [#211](https://github.com/PerfDotNet/BenchmarkDotNet/issues/211))
-* Support CopyToOutput (see [#212](https://github.com/PerfDotNet/BenchmarkDotNet/issues/212)). Now you can use native dependencies as well as custom files.
-* Copying custom settings from app.config (see [#108](https://github.com/PerfDotNet/BenchmarkDotNet/issues/108)). It means we support assembly binding redirects as well as custom connection strings etc.
-* AsciiDocExporter (see [#169](https://github.com/PerfDotNet/BenchmarkDotNet/pull/169))
-* Framework setting in Job has been removed (see [#194](https://github.com/PerfDotNet/BenchmarkDotNet/issues/194))
-* Minor bugfixes and improvements
\ No newline at end of file
diff --git a/docs/_changelog/header/v0.9.9.md b/docs/_changelog/header/v0.9.9.md
deleted file mode 100644
index a3352ca136..0000000000
--- a/docs/_changelog/header/v0.9.9.md
+++ /dev/null
@@ -1,9 +0,0 @@
-* Attribute config style (see [#166](https://github.com/PerfDotNet/BenchmarkDotNet/issues/166))
-* [Online documentation](https://perfdotnet.github.io/BenchmarkDotNet/index.htm) (see [#219](https://github.com/PerfDotNet/BenchmarkDotNet/issues/219))
-* Mono LLVM support (see [#226](https://github.com/PerfDotNet/BenchmarkDotNet/issues/226))
-* Async method support (see [#236](https://github.com/PerfDotNet/BenchmarkDotNet/issues/236))
-* NuGet packages and repo layout restructuring (see [#225](https://github.com/PerfDotNet/BenchmarkDotNet/issues/225), [#228](https://github.com/PerfDotNet/BenchmarkDotNet/issues/228))
-* `[Cleanup]` attribute (see [#215](https://github.com/PerfDotNet/BenchmarkDotNet/issues/215))
-* New statistics columns: `Skewness`, `Kurtosis`, `WelchTTestPValue`, Improved math for the `Scaled` column
-* Now current default branch is `master`
-* Minor improvements and bug fixes
\ No newline at end of file
diff --git a/docs/articles/configs/diagnosers.md b/docs/articles/configs/diagnosers.md
index cb0facc3a9..e81943310a 100644
--- a/docs/articles/configs/diagnosers.md
+++ b/docs/articles/configs/diagnosers.md
@@ -35,10 +35,10 @@ The current Diagnosers are:
Please see Adam Sitnik's [blog post](https://adamsitnik.com/ConcurrencyVisualizer-Profiler/) for all the details.
- Native Memory Profiler (`NativeMemoryProfiler`)
It uses `EtwProfiler` to profile the code using ETW and adds the extra columns `Allocated native memory` and `Native memory leak`.
- Please see Wojciech Nagórski's [blog post](https://wojciechnagorski.com/2019/08/analyzing-native-memory-allocation-with-benchmarkdotnet/) for all the details.
+ Please see Wojciech Nagórski's [blog post](https://wojciechnagorski.github.io/2019/08/analyzing-native-memory-allocation-with-benchmarkdotnet/) for all the details.
- Event Pipe Profiler (`EventPipeProfiler`).
It is a cross-platform profiler that allows profile .NET code on every platform - Windows, Linux, macOS.
- Please see Wojciech Nagórski's [blog post](https://wojciechnagorski.com/2020/04/cross-platform-profiling-.net-code-with-benchmarkdotnet/) for all the details.
+ Please see Wojciech Nagórski's [blog post](https://wojciechnagorski.github.io/2020/04/cross-platform-profiling-.net-code-with-benchmarkdotnet/) for all the details.
- Threading Diagnoser (`ThreadingDiagnoser`) - .NET Core 3.0+ diagnoser that reports some Threading statistics.
- Exception Diagnoser (`ExceptionDiagnoser`) - a diagnoser that reports the frequency of exceptions thrown during the operation.
@@ -59,11 +59,11 @@ private class Config : ManualConfig
{
public Config()
{
- Add(MemoryDiagnoser.Default);
- Add(new InliningDiagnoser());
- Add(new EtwProfiler());
- Add(ThreadingDiagnoser.Default);
- Add(ExceptionDiagnoser.Default);
+ AddDiagnoser(MemoryDiagnoser.Default);
+ AddDiagnoser(new InliningDiagnoser());
+ AddDiagnoser(new EtwProfiler());
+ AddDiagnoser(ThreadingDiagnoser.Default);
+ AddDiagnoser(ExceptionDiagnoser.Default);
}
}
```
diff --git a/docs/articles/configs/jobs.md b/docs/articles/configs/jobs.md
index a6085e0bdb..ad0cf4559c 100644
--- a/docs/articles/configs/jobs.md
+++ b/docs/articles/configs/jobs.md
@@ -98,20 +98,20 @@ public class MyBenchmarks
{
public Config()
{
- Add(
- new Job("MySuperJob", RunMode.Dry, EnvMode.RyuJitX64)
+ AddJob(
+ new Job("MySuperJob", RunMode.Dry, EnvironmentMode.RyuJitX64)
{
- Env = { Runtime = Runtime.Core },
+ Environment = { Runtime = CoreRuntime.Core90 },
Run = { LaunchCount = 5, IterationTime = TimeInterval.Millisecond * 200 },
- Accuracy = { MaxStdErrRelative = 0.01 }
+ Accuracy = { MaxRelativeError = 0.01 }
});
// The same, using the .With() factory methods:
- Add(
+ AddJob(
Job.Dry
.WithPlatform(Platform.X64)
.WithJit(Jit.RyuJit)
- .WithRuntime(Runtime.Core)
+ .WithRuntime(CoreRuntime.Core90)
.WithLaunchCount(5)
.WithIterationTime(TimeInterval.Millisecond * 200)
.WithMaxRelativeError(0.01)
@@ -122,26 +122,26 @@ public class MyBenchmarks
}
```
-Basically, it's a good idea to start with predefined values (e.g. `EnvMode.RyuJitX64` and `RunMode.Dry` passed as constructor args) and modify rest of the properties using property setters or with help of object initializer syntax.
+Basically, it's a good idea to start with predefined values (e.g. `EnvironmentMode.RyuJitX64` and `RunMode.Dry` passed as constructor args) and modify rest of the properties using property setters or with help of object initializer syntax.
Note that the job cannot be modified after it's added into config. Trying to set a value on property of the frozen job will throw an `InvalidOperationException`. Use the `Job.Frozen` property to determine if the code properties can be altered.
If you do want to create a new job based on frozen one (all predefined job values are frozen) you can use the `.With()` extension method
```cs
- var newJob = Job.Dry.With(Platform.X64);
+ var newJob = Job.Dry.WithPlatform(Platform.X64);
```
or pass the frozen value as a constructor argument
```c#
- var newJob = new Job(Job.Dry) { Env = { Platform = Platform.X64 } };
+ var newJob = new Job(Job.Dry) { Environment = { Platform = Platform.X64 } };
```
or use the `.Apply()` method on unfrozen job
```c#
- var newJob = new Job() { Env = { Platform = Platform.X64 } }.Apply(Job.Dry);
+ var newJob = new Job() { Environment = { Platform = Platform.X64 } }.Apply(Job.Dry);
```
in any case the Id property will not be transfered and you must pass it explicitly (using the .ctor id argument or the `.WithId()` extension method).
@@ -152,7 +152,9 @@ You can also add new jobs via attributes. Examples:
```cs
[DryJob]
-[ClrJob, CoreJob, MonoJob]
+[MonoJob]
+[SimpleJob(RuntimeMoniker.Net90)]
+[SimpleJob(RuntimeMoniker.NetCoreApp31)]
[LegacyJitX86Job, LegacyJitX64Job, RyuJitX64Job]
[SimpleJob(RunStrategy.ColdStart, launchCount: 1, warmupCount: 5, iterationCount: 5, id: "FastAndDirtyJob")]
public class MyBenchmarkClass
@@ -212,7 +214,7 @@ public class MySuperJobAttribute : Attribute, IConfigSource
{
var job = new Job("MySuperJob", RunMode.Core);
job.Env.Platform = Platform.X64;
- Config = ManualConfig.CreateEmpty().With(job);
+ Config = ManualConfig.CreateEmpty().AddJob(job);
}
public IConfig Config { get; }
diff --git a/docs/articles/configs/toolchains.md b/docs/articles/configs/toolchains.md
index 0bae3ffdff..b47e994a94 100644
--- a/docs/articles/configs/toolchains.md
+++ b/docs/articles/configs/toolchains.md
@@ -18,14 +18,14 @@ When you run your benchmarks without specifying the toolchain in an explicit way
If you want to test multiple frameworks, your project file **MUST target all of them** and you **MUST install the corresponding SDKs**:
```xml
-netcoreapp3.0;netcoreapp2.1;net48
+netcoreapp3.1;net8.0;net48
```
If you run your benchmarks without specifying any custom settings, BenchmarkDotNet is going to run the benchmarks **using the same framework as the host process**:
```cmd
-dotnet run -c Release -f netcoreapp2.1 # is going to run the benchmarks using .NET Core 2.1
-dotnet run -c Release -f netcoreapp3.0 # is going to run the benchmarks using .NET Core 3.0
+dotnet run -c Release -f netcoreapp3.1 # is going to run the benchmarks using .NET Core 3.1
+dotnet run -c Release -f net8.0 # is going to run the benchmarks using .NET 8.0
dotnet run -c Release -f net48 # is going to run the benchmarks using .NET 4.8
mono $pathToExe # is going to run the benchmarks using Mono from your PATH
```
@@ -33,8 +33,8 @@ mono $pathToExe # is going to run the benchmarks using Mo
To run the benchmarks for multiple runtimes with a single command, you need to specify the target framework moniker names via `--runtimes|-r` console argument:
```cmd
-dotnet run -c Release -f netcoreapp2.1 --runtimes netcoreapp2.1 netcoreapp3.0 # is going to run the benchmarks using .NET Core 2.1 and .NET Core 3.0
-dotnet run -c Release -f netcoreapp2.1 --runtimes netcoreapp2.1 net48 # is going to run the benchmarks using .NET Core 2.1 and .NET 4.8
+dotnet run -c Release -f net8.0 --runtimes net8.0 netcoreapp3.1 # is going to run the benchmarks using .NET 8.0 and .NET Core 3.1
+dotnet run -c Release -f net8.0 --runtimes net8.0 net48 # is going to run the benchmarks using .NET 8.0 and .NET 4.8
```
What is going to happen if you provide multiple Full .NET Framework monikers? Let's say:
@@ -67,8 +67,8 @@ namespace BenchmarkDotNet.Samples
{
[SimpleJob(RuntimeMoniker.Net48)]
[SimpleJob(RuntimeMoniker.Mono)]
- [SimpleJob(RuntimeMoniker.NetCoreApp21)]
- [SimpleJob(RuntimeMoniker.NetCoreApp30)]
+ [SimpleJob(RuntimeMoniker.NetCoreApp31)]
+ [SimpleJob(RuntimeMoniker.Net80)]
public class TheClassWithBenchmarks
```
@@ -87,10 +87,9 @@ namespace BenchmarkDotNet.Samples
static void Main(string[] args)
{
var config = DefaultConfig.Instance
- .With(Job.Default.With(CoreRuntime.Core21))
- .With(Job.Default.With(CoreRuntime.Core30))
- .With(Job.Default.With(ClrRuntime.Net48))
- .With(Job.Default.With(MonoRuntime.Default));
+ .AddJob(Job.Default.WithRuntime(CoreRuntime.Core80))
+ .AddJob(Job.Default.WithRuntime(ClrRuntime.Net48))
+ .AddJob(Job.Default.WithRuntime(MonoRuntime.Default));
BenchmarkSwitcher
.FromAssembly(typeof(Program).Assembly)
@@ -115,9 +114,9 @@ public class MyConfig : ManualConfig
Add(Job.Default.With(
CsProjCoreToolchain.From(
new NetCoreAppSettings(
- targetFrameworkMoniker: "netcoreapp2.1",
- runtimeFrameworkVersion: "2.1.0-preview2-25628-01",
- name: ".NET Core 2.1"))));
+ targetFrameworkMoniker: "net8.0-windows",
+ runtimeFrameworkVersion: "8.0.101",
+ name: ".NET 8.0 Windows"))));
}
}
```
@@ -130,8 +129,8 @@ It's possible to benchmark a private build of .NET Runtime. All you need to do i
BenchmarkSwitcher
.FromAssembly(typeof(Program).Assembly)
.Run(args,
- DefaultConfig.Instance.With(
- Job.ShortRun.With(ClrRuntime.CreateForLocalFullNetFrameworkBuild(version: "4.0"))));
+ DefaultConfig.Instance.AddJob(
+ Job.ShortRun.WithRuntime(ClrRuntime.CreateForLocalFullNetFrameworkBuild(version: "4.0"))));
```
This sends the provided version as a `COMPLUS_Version` env var to the benchmarked process.
@@ -146,11 +145,11 @@ public class CustomPathsConfig : ManualConfig
public CustomPathsConfig()
{
var dotnetCli32bit = NetCoreAppSettings
- .NetCoreApp20
+ .NetCoreApp31
.WithCustomDotNetCliPath(@"C:\Program Files (x86)\dotnet\dotnet.exe", "32 bit cli");
var dotnetCli64bit = NetCoreAppSettings
- .NetCoreApp20
+ .NetCoreApp31
.WithCustomDotNetCliPath(@"C:\Program Files\dotnet\dotnet.exe", "64 bit cli");
AddJob(Job.RyuJitX86.WithToolchain(CsProjCoreToolchain.From(dotnetCli32bit)).WithId("32 bit cli"));
@@ -208,7 +207,7 @@ or:
```cs
var config = DefaultConfig.Instance
- .With(Job.Default.With(NativeAotRuntime.Net70)); // compiles the benchmarks as net7.0 and uses the latest NativeAOT to build a native app
+ .AddJob(Job.Default.WithRuntime(NativeAotRuntime.Net70)); // compiles the benchmarks as net7.0 and uses the latest NativeAOT to build a native app
BenchmarkSwitcher
.FromAssembly(typeof(Program).Assembly)
@@ -231,8 +230,8 @@ If you want to benchmark some particular version of NativeAOT (or from a differe
```cs
var config = DefaultConfig.Instance
- .With(Job.ShortRun
- .With(NativeAotToolchain.CreateBuilder()
+ .AddJob(Job.ShortRun
+ .WithToolchain(NativeAotToolchain.CreateBuilder()
.UseNuGet(
microsoftDotNetILCompilerVersion: "7.0.0-*", // the version goes here
nuGetFeedUrl: "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet7/nuget/v3/index.json") // this address might change over time
@@ -338,8 +337,8 @@ or explicitly in the code:
```cs
var config = DefaultConfig.Instance
- .With(Job.ShortRun
- .With(NativeAotToolchain.CreateBuilder()
+ .AddJob(Job.ShortRun
+ .WithToolchain(NativeAotToolchain.CreateBuilder()
.UseLocalBuild(@"C:\Projects\runtime\artifacts\packages\Release\Shipping\")
.DisplayName("NativeAOT local build")
.TargetFrameworkMoniker("net7.0")
diff --git a/docs/articles/features/event-pipe-profiler.md b/docs/articles/features/event-pipe-profiler.md
index 3b0ed69bc6..25d1e90386 100644
--- a/docs/articles/features/event-pipe-profiler.md
+++ b/docs/articles/features/event-pipe-profiler.md
@@ -7,7 +7,7 @@ name: EventPipeProfiler
`EventPipeProfiler` is a cross-platform profiler that allows profile .NET code on every platform - Windows, Linux, macOS. Collected data are exported to trace files (`.speedscope.json` and `.nettrace`) which can be analyzed using [SpeedScope](https://www.speedscope.app/), [PerfView](https://github.com/Microsoft/perfview), and [Visual Studio Profiler](https://learn.microsoft.com/visualstudio/profiling/profiling-feature-tour). This new profiler is available from the 0.12.1 version.
-
+
# Configuration
diff --git a/docs/articles/features/toc.yml b/docs/articles/features/toc.yml
index ac29397743..61e3a2cd54 100644
--- a/docs/articles/features/toc.yml
+++ b/docs/articles/features/toc.yml
@@ -12,5 +12,7 @@
href: etwprofiler.md
- name: EventPipeProfiler
href: event-pipe-profiler.md
+- name: VSProfiler
+ href: vsprofiler.md
- name: VSTest
href: vstest.md
\ No newline at end of file
diff --git a/docs/articles/features/vsprofiler.md b/docs/articles/features/vsprofiler.md
new file mode 100644
index 0000000000..9e8f52712d
--- /dev/null
+++ b/docs/articles/features/vsprofiler.md
@@ -0,0 +1,71 @@
+---
+uid: docs.vsprofiler
+name: VS Profiler
+---
+
+# Running with Visual Studio profiler
+Visual Studio supports [profiler integration with BenchmarkDotNet](https://learn.microsoft.com/visualstudio/profiling/profiling-with-benchmark-dotnet) on Windows through its [Microsoft.VisualStudio.BenchmarkDotNetDiagnosers](https://www.nuget.org/packages/Microsoft.VisualStudio.DiagnosticsHub.BenchmarkDotNetDiagnosers) NuGet package. Once installed, Visual Studio specific diagnosers will capture performance data in runs and automatically open traces if launched through Visual Studio
+
+
+
+## How it works
+
+First, install the [Microsoft.VisualStudio.BenchmarkDotNetDiagnosers](https://www.nuget.org/packages/Microsoft.VisualStudio.DiagnosticsHub.BenchmarkDotNetDiagnosers) NuGet package in your benchmarking project. Next add one or more of the Visual Studio diagnosers to your benchmark to capture the relevant profiling information while benchmarking. Lastly, run your benchmarks and a diagsession will be generated. If run from Visual Studio the diagsession will automatically be opened.
+
+## Available Diagnosers
+
+* `[CPUUsageDiagnoser]` - Enables the [CPU Usage tool](https://learn.microsoft.com/visualstudio/profiling/cpu-usage).
+* `[DatabaseDiagnoser]` - Enables the [Database tool](https://learn.microsoft.com/visualstudio/profiling/analyze-database)
+* `[DotNetCountersDiagnoser]` - Enables the [.NET Counters tool](https://learn.microsoft.com/visualstudio/profiling/dotnet-counters-tool)
+* `[DotNetObjectAllocDiagnoser]` - Enables the [.NET Object Allocation tool](https://learn.microsoft.com/visualstudio/profiling/dotnet-alloc-tool). When using this tool, you must also specify `[DotNetObjectAllocJobConfiguration]` on the benchmark. If this is missing the run will fail and you will receive an error indicating you need to add it.
+* `[EventsDiagnoser]` - Enables the [Events tool](https://learn.microsoft.com/visualstudio/profiling/events-viewer)
+* `[FileIODiagnoser]` - Enables the [File IO tool](https://learn.microsoft.com/visualstudio/profiling/use-file-io)
+
+## How to use it?
+
+After installing the [Microsoft.VisualStudio.BenchmarkDotNetDiagnosers](https://www.nuget.org/packages/Microsoft.VisualStudio.DiagnosticsHub.BenchmarkDotNetDiagnosers) NuGet package add the following code as a benchmark:
+
+```cs
+using System;
+using System.Security.Cryptography;
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Running;
+using Microsoft.VSDiagnostics;
+
+namespace MyBenchmarks
+{
+ [CPUUsageDiagnoser]
+ public class Md5VsSha256
+ {
+ private const int N = 10000;
+ private readonly byte[] data;
+
+ private readonly SHA256 sha256 = SHA256.Create();
+ private readonly MD5 md5 = MD5.Create();
+
+ public Md5VsSha256()
+ {
+ data = new byte[N];
+ new Random(42).NextBytes(data);
+ }
+
+ [Benchmark]
+ public byte[] Sha256() => sha256.ComputeHash(data);
+
+ [Benchmark]
+ public byte[] Md5() => md5.ComputeHash(data);
+ }
+
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ var summary = BenchmarkRunner.Run(typeof(Program).Assembly);
+ }
+ }
+}
+```
+
+In this case we have added the `[CpuUsageDiagnoser]` to capture a CPU sampling trace. From here run the benchmark in Visual Studio (Ctrl+F5), and after the benchmark run the resulting diagsession will be displayed. Double clicking on one of the benchmark rows shown under the Benchmarks tab will filter the time selection to the specific benchmark allowing you to better isolate and investigate.
+
+
\ No newline at end of file
diff --git a/docs/articles/guides/console-args.md b/docs/articles/guides/console-args.md
index 6fc5ddb245..765ad2e2bb 100644
--- a/docs/articles/guides/console-args.md
+++ b/docs/articles/guides/console-args.md
@@ -117,28 +117,29 @@ You can also filter the benchmarks by categories:
The `--runtimes` or just `-r` allows you to run the benchmarks for selected Runtimes. Available options are:
* Clr - BDN will either use Roslyn (if you run it as .NET app) or latest installed .NET SDK to build the benchmarks (if you run it as .NET Core app).
-* Core - if you run it as .NET Core app, BDN will use the same target framework moniker, if you run it as .NET app it's going to use netcoreapp2.1.
+* Core - if you run it as .NET Core app, BDN will use the same target framework moniker, if you run it as .NET app it's going to use net8.0.
* Mono - it's going to use the Mono from `$Path`, you can override it with `--monoPath`.
-* net46, net461, net462, net47, net471, net472 - to build and run benchmarks against specific .NET framework version.
-* netcoreapp2.0, netcoreapp2.1, netcoreapp2.2, netcoreapp3.0, netcoreapp3.1, net5.0, net6.0, net7.0 - to build and run benchmarks against specific .NET Core version.
-* nativeaot5.0, nativeaot6.0, nativeaot7.0 - to build and run benchmarks using NativeAOT. Can be customized with additional options: `--ilcPath`, `--ilCompilerVersion`.
+* net46, net461, net462, net47, net471, net472, net48, net481 - to build and run benchmarks against specific .NET Framework version.
+* netcoreapp3.1, net5.0, net6.0, net7.0, net8.0 - to build and run benchmarks against specific .NET (Core) version.
+* nativeaot5.0, nativeaot6.0, nativeaot7.0, nativeaot8.0 - to build and run benchmarks using NativeAOT. Can be customized with additional options: `--ilcPackages`, `--ilCompilerVersion`.
+* mono6.0, mono7.0, mono8.0 - to build and run benchmarks with .Net 6+ using MonoVM.
-Example: run the benchmarks for .NET 4.7.2 and .NET Core 2.1:
+Example: run the benchmarks for .NET 4.7.2 and .NET 8.0:
```log
-dotnet run -c Release -- --runtimes net472 netcoreapp2.1
+dotnet run -c Release -- --runtimes net472 net8.0
```
-Example: run the benchmarks for .NET Core 3.0 and latest .NET SDK installed on your PC:
+Example: run the benchmarks for .NET Core 3.1 and latest .NET SDK installed on your PC:
```log
-dotnet run -c Release -f netcoreapp3.0 -- --runtimes clr core
+dotnet run -c Release -f netcoreapp3.1 -- --runtimes clr core
```
-But same command executed with `-f netcoreapp2.0` is going to run the benchmarks for .NET Core 2.0:
+But same command executed with `-f net6.0` is going to run the benchmarks for .NET 6.0:
```log
-dotnet run -c Release -f netcoreapp2.0 -- --runtimes clr core
+dotnet run -c Release -f net6.0 -- --runtimes clr core
```
## Number of invocations and iterations
@@ -207,10 +208,10 @@ To perform a Mann–Whitney U Test and display the results in a dedicated column
* `--statisticalTest`- Threshold for Mann–Whitney U Test. Examples: 5%, 10ms, 100ns, 1s
-Example: run Mann–Whitney U test with relative ratio of 5% for all benchmarks for .NET Core 2.0 (base) vs .NET Core 2.1 (diff). .NET Core 2.0 will be baseline because it was first.
+Example: run Mann–Whitney U test with relative ratio of 5% for all benchmarks for .NET 6.0 (base) vs .NET 8.0 (diff). .NET 6.0 will be baseline because it was first.
```log
-dotnet run -c Release -- --filter * --runtimes netcoreapp2.0 netcoreapp2.1 --statisticalTest 5%
+dotnet run -c Release -- --filter * --runtimes net6.0 net8.0 --statisticalTest 5%
```
## More
@@ -228,7 +229,7 @@ dotnet run -c Release -- --filter * --runtimes netcoreapp2.0 netcoreapp2.1 --sta
* `--cli` path to dotnet cli (optional).
* `--packages` the directory to restore packages to (optional).
* `--coreRun` path(s) to CoreRun (optional).
-* `--ilcPath` path to ILCompiler for NativeAOT.
+* `--ilcPackages` path to ILCompiler for NativeAOT.
* `--info` prints environment configuration including BenchmarkDotNet, OS, CPU and .NET version
* `--stopOnFirstError` stop on first error.
* `--help` display this help screen.
@@ -242,6 +243,7 @@ dotnet run -c Release -- --filter * --runtimes netcoreapp2.0 netcoreapp2.1 --sta
* `--platform` the Platform that should be used. If not specified, the host process platform is used (default). AnyCpu/X86/X64/Arm/Arm64/LoongArch64.
* `--runOncePerIteration` run the benchmark exactly once per iteration.
* `--buildTimeout` build timeout in seconds.
+* `--wakeLock` prevents the system from entering sleep or turning off the display. None/System/Display.
* `--wasmEngine` full path to a java script engine used to run the benchmarks, used by Wasm toolchain.
* `--wasmMainJS` path to the test-main.js file used by Wasm toolchain. Mandatory when using \"--runtimes wasm\"
* `--expose_wasm` arguments for the JavaScript engine used by Wasm toolchain.
diff --git a/docs/articles/guides/good-practices.md b/docs/articles/guides/good-practices.md
index 854ead5679..da4329de2b 100644
--- a/docs/articles/guides/good-practices.md
+++ b/docs/articles/guides/good-practices.md
@@ -4,8 +4,8 @@
Never use the Debug build for benchmarking. *Never*. The debug version of the target method can run 10–100 times slower.
The release mode means that you should have `true ` in your csproj file
-or use [/optimize](https://learn.microsoft.com/dotnet/csharp/language-reference/compiler-options/) for `csc`. Also your never
-should use an attached debugger (e.g. Visual Studio or WinDbg) during the benchmarking. The best way is
+or use [/optimize](https://learn.microsoft.com/dotnet/csharp/language-reference/compiler-options/) for `csc`. Also, never
+use an attached debugger (e.g. Visual Studio or WinDbg) during the benchmarking. The best way is
build our benchmark in the Release mode and run it from the command line.
## Try different environments
diff --git a/docs/articles/license.md b/docs/articles/license.md
index 039248a340..6272f97306 100644
--- a/docs/articles/license.md
+++ b/docs/articles/license.md
@@ -1,6 +1,6 @@
### The MIT License
-Copyright (c) 2013–2024 .NET Foundation and contributors
+Copyright (c) 2013–2025 .NET Foundation and contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/docs/articles/samples/IntroArgumentsSource.md b/docs/articles/samples/IntroArgumentsSource.md
index 9a0bff5cf4..a5b3692870 100644
--- a/docs/articles/samples/IntroArgumentsSource.md
+++ b/docs/articles/samples/IntroArgumentsSource.md
@@ -11,7 +11,7 @@ You can mark one or several fields or properties in your class by the
[`[ArgumentsSource]`](xref:BenchmarkDotNet.Attributes.ArgumentsSourceAttribute) attribute.
In this attribute, you have to specify the name of public method/property which is going to provide the values
(something that implements `IEnumerable`).
- The source must be within benchmarked type!
+The source may be instance or static. If the source is not in the same type as the benchmark, the type containing the source must be specified in the attribute constructor.
### Source code
diff --git a/docs/articles/samples/IntroNuGet.md b/docs/articles/samples/IntroNuGet.md
index 53d9ac54f8..1cc9af84a2 100644
--- a/docs/articles/samples/IntroNuGet.md
+++ b/docs/articles/samples/IntroNuGet.md
@@ -1,10 +1,10 @@
----
+---
uid: BenchmarkDotNet.Samples.IntroNuGet
---
## Sample: IntroNuGet
-You can set specific versions of NuGet dependencies for each job.
+You can set specific versions of NuGet dependencies for each job using MsBuild properties in your csproj.
It allows comparing different versions of the same package (if there are no breaking changes in API).
### Source code
@@ -13,14 +13,11 @@ It allows comparing different versions of the same package (if there are no brea
### Output
-| Method | Job | NuGetReferences | Mean | Error | StdDev |
-|------------------------- |------- |----------------------- |---------:|----------:|----------:|
-| SerializeAnonymousObject | 10.0.1 | Newtonsoft.Json 10.0.1 | 2.926 us | 0.0795 us | 0.0283 us |
-| SerializeAnonymousObject | 10.0.2 | Newtonsoft.Json 10.0.2 | 2.877 us | 0.5928 us | 0.2114 us |
-| SerializeAnonymousObject | 10.0.3 | Newtonsoft.Json 10.0.3 | 2.706 us | 0.1251 us | 0.0446 us |
-| SerializeAnonymousObject | 11.0.1 | Newtonsoft.Json 11.0.1 | 2.778 us | 0.5037 us | 0.1796 us |
-| SerializeAnonymousObject | 11.0.2 | Newtonsoft.Json 11.0.2 | 2.644 us | 0.0609 us | 0.0217 us |
-| SerializeAnonymousObject | 9.0.1 | Newtonsoft.Json 9.0.1 | 2.722 us | 0.3552 us | 0.1267 us |
+| Method | Job | Arguments | Mean | Error | StdDev |
+|-------------------------- |------- |-------------------- |---------:|----------:|----------:|
+| ToImmutableArrayBenchmark | v9.0.0 | /p:SciVersion=9.0.0 | 1.173 μs | 0.0057 μs | 0.0086 μs |
+| ToImmutableArrayBenchmark | v9.0.3 | /p:SciVersion=9.0.3 | 1.173 μs | 0.0038 μs | 0.0058 μs |
+| ToImmutableArrayBenchmark | v9.0.5 | /p:SciVersion=9.0.5 | 1.172 μs | 0.0107 μs | 0.0157 μs |
### Links
diff --git a/docs/articles/samples/IntroParamsSource.md b/docs/articles/samples/IntroParamsSource.md
index 5164d2514a..e631fbf1db 100644
--- a/docs/articles/samples/IntroParamsSource.md
+++ b/docs/articles/samples/IntroParamsSource.md
@@ -10,7 +10,7 @@ You can mark one or several fields or properties in your class by the
[`[Params]`](xref:BenchmarkDotNet.Attributes.ParamsAttribute) attribute.
In this attribute, you have to specify the name of public method/property which is going to provide the values
(something that implements `IEnumerable`).
-The source must be within benchmarked type!
+The source may be instance or static. If the source is not in the same type as the benchmark, the type containing the source must be specified in the attribute constructor.
### Source code
diff --git a/docs/articles/samples/IntroVisualStudioProfiler.md b/docs/articles/samples/IntroVisualStudioProfiler.md
new file mode 100644
index 0000000000..8d35498f21
--- /dev/null
+++ b/docs/articles/samples/IntroVisualStudioProfiler.md
@@ -0,0 +1,23 @@
+---
+uid: BenchmarkDotNet.Samples.IntroVisualStudioProfiler
+---
+
+## Sample: Visual Studio Profiler
+
+Using the [Microsoft.VisualStudio.BenchmarkDotNetDiagnosers](https://www.nuget.org/packages/Microsoft.VisualStudio.DiagnosticsHub.BenchmarkDotNetDiagnosers) NuGet package you can capture performance profiles of your benchmarks that can be opened in Visual Studio.
+
+### Source code
+
+[!code-csharp[IntroVisualStudioDiagnoser.cs](../../../samples/BenchmarkDotNet.Samples/IntroVisualStudioDiagnoser.cs)]
+
+### Output
+The output will contain a path to the collected diagsession and automatically open in Visual Studio when launched from it.
+
+```markdown
+// * Diagnostic Output - VSDiagnosticsDiagnoser *
+Collection result moved to 'C:\Work\BenchmarkDotNet\samples\BenchmarkDotNet.Samples\bin\Release\net8.0\BenchmarkDotNet.Artifacts\BenchmarkDotNet_IntroVisualStudioProfiler_20241205_192056.diagsession'.
+Session : {d54ebddb-2d6d-404f-b1da-10acbc89635f}
+ Stopped
+Exported diagsession file: C:\Work\BenchmarkDotNet\samples\BenchmarkDotNet.Samples\bin\Release\net8.0\BenchmarkDotNet.Artifacts\BenchmarkDotNet_IntroVisualStudioProfiler_20241205_192056.diagsession.
+Opening diagsession in VisualStudio: 15296
+```
\ No newline at end of file
diff --git a/docs/articles/samples/IntroWakeLock.md b/docs/articles/samples/IntroWakeLock.md
new file mode 100644
index 0000000000..333b3e4345
--- /dev/null
+++ b/docs/articles/samples/IntroWakeLock.md
@@ -0,0 +1,32 @@
+---
+uid: BenchmarkDotNet.Samples.IntroWakeLock
+---
+
+## Sample: IntroWakeLock
+
+Running benchmarks may sometimes take enough time such that the system enters sleep or turns off the display.
+
+Using a WakeLock prevents the Windows system doing so.
+
+### Source code
+
+[!code-csharp[IntroWakeLock.cs](../../../samples/BenchmarkDotNet.Samples/IntroWakeLock.cs)]
+
+### Command line
+
+```
+--wakeLock None
+```
+```
+--wakeLock System
+```
+```
+--wakeLock Display
+```
+
+### Links
+
+* @BenchmarkDotNet.Attributes.WakeLockAttribute
+* The permanent link to this sample: @BenchmarkDotNet.Samples.IntroWakeLock
+
+---
diff --git a/docs/articles/samples/toc.yml b/docs/articles/samples/toc.yml
index 7e4c7671e1..8dab3b5c6b 100644
--- a/docs/articles/samples/toc.yml
+++ b/docs/articles/samples/toc.yml
@@ -124,6 +124,10 @@
href: IntroTagColumn.md
- name: IntroTailcall
href: IntroTailcall.md
+- name: IntroVisualStudioProfiler
+ href: IntroVisualStudioProfiler.md
+- name: IntroWakeLock
+ href: IntroWakeLock.md
- name: IntroWasm
href: IntroWasm.md
- name: IntroUnicode
diff --git a/docs/changelog/.gitignore b/docs/changelog/.gitignore
deleted file mode 100644
index 9f626be1d8..0000000000
--- a/docs/changelog/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-*.md
-*.yml
\ No newline at end of file
diff --git a/docs/images/vs-profiler-demo.png b/docs/images/vs-profiler-demo.png
new file mode 100644
index 0000000000..928f012df1
Binary files /dev/null and b/docs/images/vs-profiler-demo.png differ
diff --git a/samples/BenchmarkDotNet.Samples.FSharp/BenchmarkDotNet.Samples.FSharp.fsproj b/samples/BenchmarkDotNet.Samples.FSharp/BenchmarkDotNet.Samples.FSharp.fsproj
index 2ae53f94d8..6359340234 100644
--- a/samples/BenchmarkDotNet.Samples.FSharp/BenchmarkDotNet.Samples.FSharp.fsproj
+++ b/samples/BenchmarkDotNet.Samples.FSharp/BenchmarkDotNet.Samples.FSharp.fsproj
@@ -7,13 +7,15 @@
Exe
net462;net8.0
false
+
+ false
-
+
@@ -21,8 +23,8 @@
-
-
-
+
+
+
diff --git a/samples/BenchmarkDotNet.Samples/BenchmarkDotNet.Samples.csproj b/samples/BenchmarkDotNet.Samples/BenchmarkDotNet.Samples.csproj
index 36c3f60b32..948849aa05 100644
--- a/samples/BenchmarkDotNet.Samples/BenchmarkDotNet.Samples.csproj
+++ b/samples/BenchmarkDotNet.Samples/BenchmarkDotNet.Samples.csproj
@@ -13,16 +13,25 @@
$(NoWarn);CA1018;CA5351;CA1825
false
+
+ false
-
+
+
+
+ 9.0.0
+
+
+
-
-
+
-
+
+
+
diff --git a/samples/BenchmarkDotNet.Samples/IntroArgumentsSource.cs b/samples/BenchmarkDotNet.Samples/IntroArgumentsSource.cs
index 33a8d90c0c..493422e51f 100644
--- a/samples/BenchmarkDotNet.Samples/IntroArgumentsSource.cs
+++ b/samples/BenchmarkDotNet.Samples/IntroArgumentsSource.cs
@@ -20,10 +20,13 @@ public class IntroArgumentsSource
}
[Benchmark]
- [ArgumentsSource(nameof(TimeSpans))]
+ [ArgumentsSource(typeof(BenchmarkArguments), nameof(BenchmarkArguments.TimeSpans))] // when the arguments come from a different type, specify that type here
public void SingleArgument(TimeSpan time) => Thread.Sleep(time);
+ }
- public IEnumerable TimeSpans() // for single argument it's an IEnumerable of objects (object)
+ public static class BenchmarkArguments
+ {
+ public static IEnumerable TimeSpans() // for single argument it's an IEnumerable of objects (object)
{
yield return TimeSpan.FromMilliseconds(10);
yield return TimeSpan.FromMilliseconds(100);
diff --git a/samples/BenchmarkDotNet.Samples/IntroDotMemoryDiagnoser.cs b/samples/BenchmarkDotNet.Samples/IntroDotMemoryDiagnoser.cs
index 846e2178f4..894bfc6c34 100644
--- a/samples/BenchmarkDotNet.Samples/IntroDotMemoryDiagnoser.cs
+++ b/samples/BenchmarkDotNet.Samples/IntroDotMemoryDiagnoser.cs
@@ -4,16 +4,10 @@
namespace BenchmarkDotNet.Samples
{
- // Enables dotMemory profiling for all jobs
+ // Profile benchmarks via dotMemory SelfApi 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]
+ [SimpleJob] // external-process execution
+ [InProcess] // in-process execution
public class IntroDotMemoryDiagnoser
{
[Params(1024)]
diff --git a/samples/BenchmarkDotNet.Samples/IntroDotTraceDiagnoser.cs b/samples/BenchmarkDotNet.Samples/IntroDotTraceDiagnoser.cs
index 351207c78b..047e6ee059 100644
--- a/samples/BenchmarkDotNet.Samples/IntroDotTraceDiagnoser.cs
+++ b/samples/BenchmarkDotNet.Samples/IntroDotTraceDiagnoser.cs
@@ -3,16 +3,11 @@
namespace BenchmarkDotNet.Samples
{
- // Enables dotTrace profiling for all jobs
+ // Profile benchmarks via dotTrace SelfApi profiling for all jobs
+ // See: https://www.nuget.org/packages/JetBrains.Profiler.SelfApi
[DotTraceDiagnoser]
- // Adds the default "external-process" job
- // Profiling is performed using dotTrace command-line Tools
- // See: https://www.jetbrains.com/help/profiler/Performance_Profiling__Profiling_Using_the_Command_Line.html
- [SimpleJob]
- // Adds an "in-process" job
- // Profiling is performed using dotTrace SelfApi
- // NuGet reference: https://www.nuget.org/packages/JetBrains.Profiler.SelfApi
- [InProcess]
+ [SimpleJob] // external-process execution
+ [InProcess] // in-process execution
public class IntroDotTraceDiagnoser
{
[Benchmark]
diff --git a/samples/BenchmarkDotNet.Samples/IntroEnvVars.cs b/samples/BenchmarkDotNet.Samples/IntroEnvVars.cs
index 59bd2db82f..66f5197119 100644
--- a/samples/BenchmarkDotNet.Samples/IntroEnvVars.cs
+++ b/samples/BenchmarkDotNet.Samples/IntroEnvVars.cs
@@ -10,13 +10,14 @@ public class IntroEnvVars
{
private class ConfigWithCustomEnvVars : ManualConfig
{
- private const string JitNoInline = "COMPlus_JitNoInline";
-
public ConfigWithCustomEnvVars()
{
- AddJob(Job.Default.WithRuntime(CoreRuntime.Core21).WithId("Inlining enabled"));
- AddJob(Job.Default.WithRuntime(CoreRuntime.Core21)
- .WithEnvironmentVariables(new EnvironmentVariable(JitNoInline, "1"))
+ AddJob(Job.Default.WithRuntime(CoreRuntime.Core80).WithId("Inlining enabled"));
+ AddJob(Job.Default.WithRuntime(CoreRuntime.Core80)
+ .WithEnvironmentVariables([
+ new EnvironmentVariable("DOTNET_JitNoInline", "1"),
+ new EnvironmentVariable("COMPlus_JitNoInline", "1")
+ ])
.WithId("Inlining disabled"));
}
}
@@ -27,4 +28,4 @@ public void Foo()
// Benchmark body
}
}
-}
\ No newline at end of file
+}
diff --git a/samples/BenchmarkDotNet.Samples/IntroFluentConfigBuilder.cs b/samples/BenchmarkDotNet.Samples/IntroFluentConfigBuilder.cs
index 94c34c1cbd..4ac29919ee 100644
--- a/samples/BenchmarkDotNet.Samples/IntroFluentConfigBuilder.cs
+++ b/samples/BenchmarkDotNet.Samples/IntroFluentConfigBuilder.cs
@@ -38,7 +38,7 @@ public static void Run()
.Run(
DefaultConfig.Instance
.AddJob(Job.Default.WithRuntime(ClrRuntime.Net462))
- .AddJob(Job.Default.WithRuntime(CoreRuntime.Core21))
+ .AddJob(Job.Default.WithRuntime(CoreRuntime.Core80))
.AddValidator(ExecutionValidator.FailOnError));
}
}
diff --git a/samples/BenchmarkDotNet.Samples/IntroNativeMemory.cs b/samples/BenchmarkDotNet.Samples/IntroNativeMemory.cs
index f968696b43..75f636e87e 100644
--- a/samples/BenchmarkDotNet.Samples/IntroNativeMemory.cs
+++ b/samples/BenchmarkDotNet.Samples/IntroNativeMemory.cs
@@ -3,6 +3,7 @@
using System.Runtime.InteropServices;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Diagnostics.Windows.Configs;
+using BenchmarkDotNet.Filters;
namespace BenchmarkDotNet.Samples
{
@@ -11,7 +12,8 @@ namespace BenchmarkDotNet.Samples
[MemoryDiagnoser]
public class IntroNativeMemory
{
- [Benchmark]
+#pragma warning disable CA1416
+ [Benchmark, WindowsOnly]
public void BitmapWithLeaks()
{
var flag = new Bitmap(200, 100);
@@ -20,7 +22,7 @@ public void BitmapWithLeaks()
graphics.DrawLine(blackPen, 100, 100, 500, 100);
}
- [Benchmark]
+ [Benchmark, WindowsOnly]
public void Bitmap()
{
using (var flag = new Bitmap(200, 100))
@@ -34,6 +36,7 @@ public void Bitmap()
}
}
}
+#pragma warning restore CA1416
private const int Size = 20; // Greater value could cause System.OutOfMemoryException for test with memory leaks.
private int ArraySize = Size * Marshal.SizeOf(typeof(int));
@@ -52,5 +55,13 @@ public unsafe void AllocHGlobalWithLeaks()
IntPtr unmanagedHandle = Marshal.AllocHGlobal(ArraySize);
Span unmanaged = new Span(unmanagedHandle.ToPointer(), ArraySize);
}
+
+ private class WindowsOnlyAttribute : FilterConfigBaseAttribute
+ {
+ public WindowsOnlyAttribute()
+ : base(new SimpleFilter(_ => RuntimeInformation.IsOSPlatform(OSPlatform.Windows)))
+ {
+ }
+ }
}
}
diff --git a/samples/BenchmarkDotNet.Samples/IntroNuGet.cs b/samples/BenchmarkDotNet.Samples/IntroNuGet.cs
index 2eb31cd89a..850a9ceebf 100644
--- a/samples/BenchmarkDotNet.Samples/IntroNuGet.cs
+++ b/samples/BenchmarkDotNet.Samples/IntroNuGet.cs
@@ -1,8 +1,9 @@
using System;
+using System.Collections.Immutable;
+using System.Linq;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Jobs;
-using Newtonsoft.Json;
namespace BenchmarkDotNet.Samples
{
@@ -10,33 +11,49 @@ namespace BenchmarkDotNet.Samples
/// Benchmarks between various versions of a NuGet package
///
///
- /// Only supported with the CsProjCoreToolchain toolchain
+ /// Only supported with CsProj toolchains.
///
[Config(typeof(Config))]
public class IntroNuGet
{
- // Specify jobs with different versions of the same NuGet package to benchmark.
- // The NuGet versions referenced on these jobs must be greater or equal to the
- // same NuGet version referenced in this benchmark project.
- // Example: This benchmark project references Newtonsoft.Json 9.0.1
+ // Setup your csproj like this:
+ /*
+
+
+ 9.0.0
+
+
+
+
+ */
+ // All versions of the package must be source-compatible with your benchmark code.
private class Config : ManualConfig
{
public Config()
{
- var baseJob = Job.MediumRun;
+ string[] targetVersions = [
+ "9.0.0",
+ "9.0.3",
+ "9.0.5",
+ ];
- AddJob(baseJob.WithNuGet("Newtonsoft.Json", "11.0.2").WithId("11.0.2"));
- AddJob(baseJob.WithNuGet("Newtonsoft.Json", "11.0.1").WithId("11.0.1"));
- AddJob(baseJob.WithNuGet("Newtonsoft.Json", "10.0.3").WithId("10.0.3"));
- AddJob(baseJob.WithNuGet("Newtonsoft.Json", "10.0.2").WithId("10.0.2"));
- AddJob(baseJob.WithNuGet("Newtonsoft.Json", "10.0.1").WithId("10.0.1"));
- AddJob(baseJob.WithNuGet("Newtonsoft.Json", "9.0.1").WithId("9.0.1"));
+ foreach (var version in targetVersions)
+ {
+ AddJob(Job.MediumRun
+ .WithMsBuildArguments($"/p:SciVersion={version}")
+ .WithId($"v{version}")
+ );
+ }
}
}
+ private static readonly Random rand = new Random(Seed: 0);
+ private static readonly double[] values = Enumerable.Range(1, 10_000).Select(x => rand.NextDouble()).ToArray();
+
[Benchmark]
- public void SerializeAnonymousObject()
- => JsonConvert.SerializeObject(
- new { hello = "world", price = 1.99, now = DateTime.UtcNow });
+ public void ToImmutableArrayBenchmark()
+ {
+ var results = values.ToImmutableArray();
+ }
}
}
\ No newline at end of file
diff --git a/samples/BenchmarkDotNet.Samples/IntroParamsSource.cs b/samples/BenchmarkDotNet.Samples/IntroParamsSource.cs
index cfd00d50f4..8257439ddf 100644
--- a/samples/BenchmarkDotNet.Samples/IntroParamsSource.cs
+++ b/samples/BenchmarkDotNet.Samples/IntroParamsSource.cs
@@ -20,7 +20,16 @@ public class IntroParamsSource
// public static method
public static IEnumerable ValuesForB() => new[] { 10, 20 };
+ // public field getting its params from a method in another type
+ [ParamsSource(typeof(ParamsValues), nameof(ParamsValues.ValuesForC))]
+ public int C;
+
[Benchmark]
- public void Benchmark() => Thread.Sleep(A + B + 5);
+ public void Benchmark() => Thread.Sleep(A + B + C + 5);
+ }
+
+ public static class ParamsValues
+ {
+ public static IEnumerable ValuesForC() => new[] { 1000, 2000 };
}
}
\ No newline at end of file
diff --git a/samples/BenchmarkDotNet.Samples/IntroSmokeEmptyBasic.cs b/samples/BenchmarkDotNet.Samples/IntroSmokeEmptyBasic.cs
new file mode 100644
index 0000000000..39783dc74a
--- /dev/null
+++ b/samples/BenchmarkDotNet.Samples/IntroSmokeEmptyBasic.cs
@@ -0,0 +1,82 @@
+using BenchmarkDotNet.Attributes;
+
+namespace BenchmarkDotNet.Samples;
+
+[DisassemblyDiagnoser]
+public class IntroSmokeEmptyBasic
+{
+ [Benchmark] public void Void1() {}
+ [Benchmark] public void Void2() {}
+ [Benchmark] public void Void3() {}
+ [Benchmark] public void Void4() {}
+
+ [Benchmark] public byte Byte1() => 0;
+ [Benchmark] public byte Byte2() => 0;
+ [Benchmark] public byte Byte3() => 0;
+ [Benchmark] public byte Byte4() => 0;
+
+ [Benchmark] public sbyte Sbyte1() => 0;
+ [Benchmark] public sbyte Sbyte2() => 0;
+ [Benchmark] public sbyte Sbyte3() => 0;
+ [Benchmark] public sbyte Sbyte4() => 0;
+
+ [Benchmark] public short Short1() => 0;
+ [Benchmark] public short Short2() => 0;
+ [Benchmark] public short Short3() => 0;
+ [Benchmark] public short Short4() => 0;
+
+ [Benchmark] public ushort Ushort1() => 0;
+ [Benchmark] public ushort Ushort2() => 0;
+ [Benchmark] public ushort Ushort3() => 0;
+ [Benchmark] public ushort Ushort4() => 0;
+
+ [Benchmark] public int Int1() => 0;
+ [Benchmark] public int Int2() => 0;
+ [Benchmark] public int Int3() => 0;
+ [Benchmark] public int Int4() => 0;
+
+ [Benchmark] public uint Uint1() => 0u;
+ [Benchmark] public uint Uint2() => 0u;
+ [Benchmark] public uint Uint3() => 0u;
+ [Benchmark] public uint Uint4() => 0u;
+
+ [Benchmark] public bool Bool1() => false;
+ [Benchmark] public bool Bool2() => false;
+ [Benchmark] public bool Bool3() => false;
+ [Benchmark] public bool Bool4() => false;
+
+ [Benchmark] public char Char1() => 'a';
+ [Benchmark] public char Char2() => 'a';
+ [Benchmark] public char Char3() => 'a';
+ [Benchmark] public char Char4() => 'a';
+
+ [Benchmark] public float Float1() => 0f;
+ [Benchmark] public float Float2() => 0f;
+ [Benchmark] public float Float3() => 0f;
+ [Benchmark] public float Float4() => 0f;
+
+ [Benchmark] public double Double1() => 0d;
+ [Benchmark] public double Double2() => 0d;
+ [Benchmark] public double Double3() => 0d;
+ [Benchmark] public double Double4() => 0d;
+
+ [Benchmark] public long Long1() => 0L;
+ [Benchmark] public long Long2() => 0L;
+ [Benchmark] public long Long3() => 0L;
+ [Benchmark] public long Long4() => 0L;
+
+ [Benchmark] public ulong Ulong1() => 0uL;
+ [Benchmark] public ulong Ulong2() => 0uL;
+ [Benchmark] public ulong Ulong3() => 0uL;
+ [Benchmark] public ulong Ulong4() => 0uL;
+
+ [Benchmark] public string String1() => "";
+ [Benchmark] public string String2() => "";
+ [Benchmark] public string String3() => "";
+ [Benchmark] public string String4() => "";
+
+ [Benchmark] public object? Object1() => null;
+ [Benchmark] public object? Object2() => null;
+ [Benchmark] public object? Object3() => null;
+ [Benchmark] public object? Object4() => null;
+}
\ No newline at end of file
diff --git a/samples/BenchmarkDotNet.Samples/IntroSmokeIncrements.cs b/samples/BenchmarkDotNet.Samples/IntroSmokeIncrements.cs
new file mode 100644
index 0000000000..6dfd15433d
--- /dev/null
+++ b/samples/BenchmarkDotNet.Samples/IntroSmokeIncrements.cs
@@ -0,0 +1,138 @@
+using BenchmarkDotNet.Attributes;
+
+namespace BenchmarkDotNet.Samples;
+
+public class IntroSmokeIncrements
+{
+ public int Field;
+
+ [Benchmark]
+ public void Increment01()
+ {
+ Field++;
+ }
+
+ [Benchmark]
+ public void Increment02()
+ {
+ Field++;
+ Field++;
+ }
+
+ [Benchmark]
+ public void Increment03()
+ {
+ Field++;
+ Field++;
+ Field++;
+ }
+
+ [Benchmark]
+ public void Increment04()
+ {
+ Field++;
+ Field++;
+ Field++;
+ Field++;
+ }
+
+ [Benchmark]
+ public void Increment05()
+ {
+ Field++;
+ Field++;
+ Field++;
+ Field++;
+ Field++;
+ }
+
+ [Benchmark]
+ public void Increment06()
+ {
+ Field++;
+ Field++;
+ Field++;
+ Field++;
+ Field++;
+ Field++;
+ }
+
+ [Benchmark]
+ public void Increment07()
+ {
+ Field++;
+ Field++;
+ Field++;
+ Field++;
+ Field++;
+ Field++;
+ Field++;
+ }
+
+ [Benchmark]
+ public void Increment08()
+ {
+ Field++;
+ Field++;
+ Field++;
+ Field++;
+ Field++;
+ Field++;
+ Field++;
+ Field++;
+ }
+
+ [Benchmark]
+ public void Increment09()
+ {
+ Field++;
+ Field++;
+ Field++;
+ Field++;
+ Field++;
+ Field++;
+ Field++;
+ Field++;
+ Field++;
+ }
+
+ [Benchmark]
+ public void Increment10()
+ {
+ Field++;
+ Field++;
+ Field++;
+ Field++;
+ Field++;
+ Field++;
+ Field++;
+ Field++;
+ Field++;
+ Field++;
+ }
+
+ [Benchmark]
+ public void Increment20()
+ {
+ Field++;
+ Field++;
+ Field++;
+ Field++;
+ Field++;
+ Field++;
+ Field++;
+ Field++;
+ Field++;
+ Field++;
+ Field++;
+ Field++;
+ Field++;
+ Field++;
+ Field++;
+ Field++;
+ Field++;
+ Field++;
+ Field++;
+ Field++;
+ }
+}
\ No newline at end of file
diff --git a/samples/BenchmarkDotNet.Samples/IntroSmokeValueTypes.cs b/samples/BenchmarkDotNet.Samples/IntroSmokeValueTypes.cs
new file mode 100644
index 0000000000..66bce4d921
--- /dev/null
+++ b/samples/BenchmarkDotNet.Samples/IntroSmokeValueTypes.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Threading.Tasks;
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Environments;
+
+namespace BenchmarkDotNet.Samples;
+
+[MemoryDiagnoser, DisassemblyDiagnoser]
+public class IntroSmokeValueTypes
+{
+ [Benchmark] public Jit ReturnEnum() => Jit.RyuJit;
+
+ [Benchmark] public DateTime ReturnDateTime() => new DateTime();
+
+ [Benchmark] public DateTime? ReturnNullableDateTime() => new DateTime();
+ [Benchmark] public int? ReturnNullableInt() => 0;
+
+ public struct StructWithReferencesOnly { public object _ref; }
+ [Benchmark] public StructWithReferencesOnly ReturnStructWithReferencesOnly() => new StructWithReferencesOnly();
+
+ public struct EmptyStruct { }
+ [Benchmark] public EmptyStruct ReturnEmptyStruct() => new EmptyStruct();
+
+ [Benchmark] public ValueTuple ReturnGenericStructOfValueType() => new ValueTuple(0);
+ [Benchmark] public ValueTuple ReturnGenericStructOfReferenceType() => new ValueTuple(null);
+
+ [Benchmark] public ValueTask ReturnValueTaskOfValueType() => new ValueTask(0);
+ [Benchmark] public ValueTask ReturnValueTaskOfReferenceType() => new ValueTask(result: null);
+
+ [Benchmark] public byte ReturnByte() => 0;
+ public struct Byte1 { public byte _1; }
+ [Benchmark] public Byte1 ReturnByte1() => new Byte1();
+ public struct Byte2 { public byte _1, _2; }
+ [Benchmark] public Byte2 ReturnByte2() => new Byte2();
+ public struct Byte3 { public byte _1, _2, _3; }
+ [Benchmark] public Byte3 ReturnByte3() => new Byte3();
+ public struct Byte4 { public byte _1, _2, _3, _4; }
+ [Benchmark] public Byte4 ReturnByte4() => new Byte4();
+
+ [Benchmark] public short ReturnShort() => 0;
+ public struct Short1 { public short _1; }
+ [Benchmark] public Short1 ReturnShort1() => new Short1();
+ public struct Short2 { public short _1, _2; }
+ [Benchmark] public Short2 ReturnShort2() => new Short2();
+ public struct Short3 { public short _1, _2, _3; }
+ [Benchmark] public Short3 ReturnShort3() => new Short3();
+ public struct Short4 { public short _1, _2, _3, _4; }
+ [Benchmark] public Short4 ReturnShort4() => new Short4();
+
+ [Benchmark] public int ReturnInt() => 0;
+ public struct Int1 { public int _1; }
+ [Benchmark] public Int1 ReturnInt1() => new Int1();
+ public struct Int2 { public int _1, _2; }
+ [Benchmark] public Int2 ReturnInt2() => new Int2();
+ public struct Int3 { public int _1, _2, _3; }
+ [Benchmark] public Int3 ReturnInt3() => new Int3();
+ public struct Int4 { public int _1, _2, _3, _4; }
+ [Benchmark] public Int4 ReturnInt4() => new Int4();
+
+ [Benchmark] public long ReturnLong() => 0;
+ public struct Long1 { public long _1; }
+ [Benchmark] public Long1 ReturnLong1() => new Long1();
+ public struct Long2 { public long _1, _2; }
+ [Benchmark] public Long2 ReturnLong2() => new Long2();
+ public struct Long3 { public long _1, _2, _3; }
+ [Benchmark] public Long3 ReturnLong3() => new Long3();
+ public struct Long4 { public long _1, _2, _3, _4; }
+ [Benchmark] public Long4 ReturnLong4() => new Long4();
+}
+// ReSharper restore InconsistentNaming
\ No newline at end of file
diff --git a/samples/BenchmarkDotNet.Samples/IntroVisualStudioDiagnoser.cs b/samples/BenchmarkDotNet.Samples/IntroVisualStudioDiagnoser.cs
new file mode 100644
index 0000000000..e513f39320
--- /dev/null
+++ b/samples/BenchmarkDotNet.Samples/IntroVisualStudioDiagnoser.cs
@@ -0,0 +1,23 @@
+using System;
+using BenchmarkDotNet.Attributes;
+using Microsoft.VSDiagnostics;
+
+namespace BenchmarkDotNet.Samples
+{
+ // Enables profiling with the CPU Usage tool
+ // See: https://learn.microsoft.com/visualstudio/profiling/profiling-with-benchmark-dotnet
+ [CPUUsageDiagnoser]
+ public class IntroVisualStudioProfiler
+ {
+ private readonly Random rand = new Random(42);
+
+ [Benchmark]
+ public void BurnCPU()
+ {
+ for (int i = 0; i < 100000; ++i)
+ {
+ rand.Next(1, 100);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/BenchmarkDotNet.Samples/IntroWakeLock.cs b/samples/BenchmarkDotNet.Samples/IntroWakeLock.cs
new file mode 100644
index 0000000000..099b347c7b
--- /dev/null
+++ b/samples/BenchmarkDotNet.Samples/IntroWakeLock.cs
@@ -0,0 +1,30 @@
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Configs;
+using System;
+using System.Threading;
+
+// *** Attribute Style applied to Assembly ***
+[assembly: WakeLock(WakeLockType.System)]
+
+namespace BenchmarkDotNet.Samples;
+
+// *** Attribute Style ***
+[WakeLock(WakeLockType.Display)]
+public class IntroWakeLock
+{
+ [Benchmark]
+ public void LongRunning() => Thread.Sleep(TimeSpan.FromSeconds(10));
+}
+
+// *** Object Style ***
+[Config(typeof(Config))]
+public class IntroWakeLockObjectStyle
+{
+ private class Config : ManualConfig
+ {
+ public Config() => WakeLock = WakeLockType.System;
+ }
+
+ [Benchmark]
+ public void LongRunning() => Thread.Sleep(TimeSpan.FromSeconds(10));
+}
diff --git a/src/BenchmarkDotNet.Annotations/Attributes/ArgumentsSourceAttribute.cs b/src/BenchmarkDotNet.Annotations/Attributes/ArgumentsSourceAttribute.cs
index a7d3fe38bb..f4836e329a 100644
--- a/src/BenchmarkDotNet.Annotations/Attributes/ArgumentsSourceAttribute.cs
+++ b/src/BenchmarkDotNet.Annotations/Attributes/ArgumentsSourceAttribute.cs
@@ -6,7 +6,18 @@ namespace BenchmarkDotNet.Attributes
public class ArgumentsSourceAttribute : PriorityAttribute
{
public string Name { get; }
+ public Type? Type { get; }
- public ArgumentsSourceAttribute(string name) => Name = name;
+ public ArgumentsSourceAttribute(string name)
+ {
+ Name = name;
+ Type = null;
+ }
+
+ public ArgumentsSourceAttribute(Type type, string name)
+ {
+ Name = name;
+ Type = type;
+ }
}
}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet.Annotations/Attributes/ParamsAttribute.cs b/src/BenchmarkDotNet.Annotations/Attributes/ParamsAttribute.cs
index 22148ffa64..aa47ebdf96 100644
--- a/src/BenchmarkDotNet.Annotations/Attributes/ParamsAttribute.cs
+++ b/src/BenchmarkDotNet.Annotations/Attributes/ParamsAttribute.cs
@@ -5,7 +5,7 @@ namespace BenchmarkDotNet.Attributes
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class ParamsAttribute : PriorityAttribute
{
- public object?[] Values { get; }
+ public object?[] Values { get; protected set; }
// CLS-Compliant Code requires a constructor without an array in the argument list
public ParamsAttribute() => Values = new object[0];
diff --git a/src/BenchmarkDotNet.Annotations/Attributes/ParamsSourceAttribute.cs b/src/BenchmarkDotNet.Annotations/Attributes/ParamsSourceAttribute.cs
index 570be32179..3587907d64 100644
--- a/src/BenchmarkDotNet.Annotations/Attributes/ParamsSourceAttribute.cs
+++ b/src/BenchmarkDotNet.Annotations/Attributes/ParamsSourceAttribute.cs
@@ -6,7 +6,18 @@ namespace BenchmarkDotNet.Attributes
public class ParamsSourceAttribute : PriorityAttribute
{
public string Name { get; }
+ public Type? Type { get; }
- public ParamsSourceAttribute(string name) => Name = name;
+ public ParamsSourceAttribute(string name)
+ {
+ Name = name;
+ Type = null;
+ }
+
+ public ParamsSourceAttribute(Type type, string name)
+ {
+ Name = name;
+ Type = type;
+ }
}
}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet.Annotations/BenchmarkDotNet.Annotations.csproj b/src/BenchmarkDotNet.Annotations/BenchmarkDotNet.Annotations.csproj
index 2e7361e163..ccc51bcd9a 100644
--- a/src/BenchmarkDotNet.Annotations/BenchmarkDotNet.Annotations.csproj
+++ b/src/BenchmarkDotNet.Annotations/BenchmarkDotNet.Annotations.csproj
@@ -2,7 +2,7 @@
Basic BenchmarkDotNet attributes that can be used to annotate your benchmarks
- netstandard1.0;netstandard2.0
+ netstandard2.0
$(NoWarn);1701;1702;1705;1591;3005;NU1702;CA1825
BenchmarkDotNet.Annotations
BenchmarkDotNet.Annotations
diff --git a/src/BenchmarkDotNet.Annotations/Jobs/RuntimeMoniker.cs b/src/BenchmarkDotNet.Annotations/Jobs/RuntimeMoniker.cs
index 4fc2db7388..16b242d2dc 100644
--- a/src/BenchmarkDotNet.Annotations/Jobs/RuntimeMoniker.cs
+++ b/src/BenchmarkDotNet.Annotations/Jobs/RuntimeMoniker.cs
@@ -110,6 +110,11 @@ public enum RuntimeMoniker
///
Net90,
+ ///
+ /// .NET 10.0
+ ///
+ Net10_0,
+
///
/// NativeAOT compiled as net6.0
///
@@ -130,6 +135,11 @@ public enum RuntimeMoniker
///
NativeAot90,
+ ///
+ /// NativeAOT compiled as net10.0
+ ///
+ NativeAot10_0,
+
///
/// WebAssembly with default .Net version
///
@@ -160,6 +170,11 @@ public enum RuntimeMoniker
///
WasmNet90,
+ ///
+ /// WebAssembly with net10.0
+ ///
+ WasmNet10_0,
+
///
/// Mono with the Ahead of Time LLVM Compiler backend
///
@@ -185,6 +200,11 @@ public enum RuntimeMoniker
///
MonoAOTLLVMNet90,
+ ///
+ /// Mono with the Ahead of Time LLVM Compiler backend and net10.0
+ ///
+ MonoAOTLLVMNet10_0,
+
///
/// .NET 6 using MonoVM (not CLR which is the default)
///
@@ -204,5 +224,10 @@ public enum RuntimeMoniker
/// .NET 9 using MonoVM (not CLR which is the default)
///
Mono90,
+
+ ///
+ /// .NET 10 using MonoVM (not CLR which is the default)
+ ///
+ Mono10_0,
}
}
diff --git a/src/BenchmarkDotNet.Diagnostics.Windows/BenchmarkDotNet.Diagnostics.Windows.csproj b/src/BenchmarkDotNet.Diagnostics.Windows/BenchmarkDotNet.Diagnostics.Windows.csproj
index 6c9e43a3f4..e2eb3d2b68 100644
--- a/src/BenchmarkDotNet.Diagnostics.Windows/BenchmarkDotNet.Diagnostics.Windows.csproj
+++ b/src/BenchmarkDotNet.Diagnostics.Windows/BenchmarkDotNet.Diagnostics.Windows.csproj
@@ -12,6 +12,6 @@
-
+
diff --git a/src/BenchmarkDotNet.Diagnostics.Windows/EtwDiagnoser.cs b/src/BenchmarkDotNet.Diagnostics.Windows/EtwDiagnoser.cs
index 80423d72cf..4205195dc5 100644
--- a/src/BenchmarkDotNet.Diagnostics.Windows/EtwDiagnoser.cs
+++ b/src/BenchmarkDotNet.Diagnostics.Windows/EtwDiagnoser.cs
@@ -8,6 +8,7 @@
using BenchmarkDotNet.Analysers;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Exporters;
+using BenchmarkDotNet.Helpers;
using BenchmarkDotNet.Loggers;
using Microsoft.Diagnostics.Tracing;
using Microsoft.Diagnostics.Tracing.Parsers;
@@ -15,7 +16,7 @@
namespace BenchmarkDotNet.Diagnostics.Windows
{
- public abstract class EtwDiagnoser where TStats : new()
+ public abstract class EtwDiagnoser : DisposeAtProcessTermination where TStats : new()
{
internal readonly LogCapture Logger = new LogCapture();
protected readonly Dictionary BenchmarkToProcess = new Dictionary();
@@ -39,11 +40,6 @@ protected void Start(DiagnoserActionParameters parameters)
BenchmarkToProcess.Add(parameters.BenchmarkCase, parameters.Process.Id);
StatsPerProcess.TryAdd(parameters.Process.Id, GetInitializedStats(parameters));
- // Important: Must wire-up clean-up events prior to acquiring IDisposable instance (Session property)
- // This is in effect the inverted sequence of actions in the Stop() method.
- Console.CancelKeyPress += OnConsoleCancelKeyPress;
- AppDomain.CurrentDomain.ProcessExit += OnProcessExit;
-
Session = CreateSession(parameters.BenchmarkCase);
EnableProvider();
@@ -80,11 +76,13 @@ protected virtual void EnableProvider()
protected void Stop()
{
WaitForDelayedEvents();
+ Dispose();
+ }
- Session.Dispose();
-
- Console.CancelKeyPress -= OnConsoleCancelKeyPress;
- AppDomain.CurrentDomain.ProcessExit -= OnProcessExit;
+ public override void Dispose()
+ {
+ Session?.Dispose();
+ base.Dispose();
}
private void Clear()
@@ -93,10 +91,6 @@ private void Clear()
StatsPerProcess.Clear();
}
- private void OnConsoleCancelKeyPress(object sender, ConsoleCancelEventArgs e) => Session?.Dispose();
-
- private void OnProcessExit(object sender, EventArgs e) => Session?.Dispose();
-
private static string GetSessionName(string prefix, BenchmarkCase benchmarkCase, ParameterInstances? parameters = null)
{
if (parameters != null && parameters.Items.Count > 0)
diff --git a/src/BenchmarkDotNet.Diagnostics.Windows/EtwProfiler.cs b/src/BenchmarkDotNet.Diagnostics.Windows/EtwProfiler.cs
index 6641dd5a45..57f57d9e1a 100644
--- a/src/BenchmarkDotNet.Diagnostics.Windows/EtwProfiler.cs
+++ b/src/BenchmarkDotNet.Diagnostics.Windows/EtwProfiler.cs
@@ -120,21 +120,10 @@ private void Start(DiagnoserActionParameters parameters)
private void Stop(DiagnoserActionParameters parameters)
{
WaitForDelayedEvents();
- string userSessionFile;
- try
- {
- kernelSession.Stop();
- heapSession?.Stop();
- userSession.Stop();
-
- userSessionFile = userSession.FilePath;
- }
- finally
- {
- kernelSession.Dispose();
- heapSession?.Dispose();
- userSession.Dispose();
- }
+ string userSessionFile = userSession.FilePath;
+ kernelSession.Dispose();
+ heapSession?.Dispose();
+ userSession.Dispose();
// Merge the 'primary' etl file X.etl (userSession) with any files that match .clr*.etl .user*.etl. and .kernel.etl.
TraceEventSession.MergeInPlace(userSessionFile, TextWriter.Null);
diff --git a/src/BenchmarkDotNet.Diagnostics.Windows/HardwareCounters.cs b/src/BenchmarkDotNet.Diagnostics.Windows/HardwareCounters.cs
index 8b3c46b4d5..d159e72a65 100644
--- a/src/BenchmarkDotNet.Diagnostics.Windows/HardwareCounters.cs
+++ b/src/BenchmarkDotNet.Diagnostics.Windows/HardwareCounters.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using BenchmarkDotNet.Detectors;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Portability;
using BenchmarkDotNet.Toolchains.InProcess.Emit;
@@ -31,7 +32,7 @@ private static readonly Dictionary EtwTranslations
public static IEnumerable Validate(ValidationParameters validationParameters, bool mandatory)
{
- if (!RuntimeInformation.IsWindows())
+ if (!OsDetector.IsWindows())
{
yield return new ValidationError(true, "Hardware Counters and EtwProfiler are supported only on Windows");
yield break;
diff --git a/src/BenchmarkDotNet.Diagnostics.Windows/JitDiagnoser.cs b/src/BenchmarkDotNet.Diagnostics.Windows/JitDiagnoser.cs
index b189af0acb..0d06faf75b 100644
--- a/src/BenchmarkDotNet.Diagnostics.Windows/JitDiagnoser.cs
+++ b/src/BenchmarkDotNet.Diagnostics.Windows/JitDiagnoser.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using BenchmarkDotNet.Detectors;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Engines;
using BenchmarkDotNet.Loggers;
@@ -33,7 +34,7 @@ public void Handle(HostSignal signal, DiagnoserActionParameters parameters)
public IEnumerable Validate(ValidationParameters validationParameters)
{
- if (!RuntimeInformation.IsWindows())
+ if (!OsDetector.IsWindows())
{
yield return new ValidationError(true, $"{GetType().Name} is supported only on Windows");
}
diff --git a/src/BenchmarkDotNet.Diagnostics.Windows/JitStatsDiagnoser.cs b/src/BenchmarkDotNet.Diagnostics.Windows/JitStatsDiagnoser.cs
index a101888a0e..b5d8a9c359 100644
--- a/src/BenchmarkDotNet.Diagnostics.Windows/JitStatsDiagnoser.cs
+++ b/src/BenchmarkDotNet.Diagnostics.Windows/JitStatsDiagnoser.cs
@@ -60,7 +60,7 @@ protected override void AttachToEvents(TraceEventSession session, BenchmarkCase
private sealed class MethodsJittedDescriptor : IMetricDescriptor
{
- internal static readonly MethodsJittedDescriptor Instance = new ();
+ internal static readonly MethodsJittedDescriptor Instance = new();
public string Id => nameof(MethodsJittedDescriptor);
public string DisplayName => "Methods JITted";
@@ -75,7 +75,7 @@ private sealed class MethodsJittedDescriptor : IMetricDescriptor
private sealed class MethodsTieredDescriptor : IMetricDescriptor
{
- internal static readonly MethodsTieredDescriptor Instance = new ();
+ internal static readonly MethodsTieredDescriptor Instance = new();
public string Id => nameof(MethodsTieredDescriptor);
public string DisplayName => "Methods Tiered";
@@ -90,7 +90,7 @@ private sealed class MethodsTieredDescriptor : IMetricDescriptor
private sealed class JitAllocatedMemoryDescriptor : IMetricDescriptor
{
- internal static readonly JitAllocatedMemoryDescriptor Instance = new ();
+ internal static readonly JitAllocatedMemoryDescriptor Instance = new();
public string Id => nameof(JitAllocatedMemoryDescriptor);
public string DisplayName => "JIT allocated memory";
diff --git a/src/BenchmarkDotNet.Diagnostics.Windows/Properties/AssemblyInfo.cs b/src/BenchmarkDotNet.Diagnostics.Windows/Properties/AssemblyInfo.cs
index a710492849..bf31db808e 100644
--- a/src/BenchmarkDotNet.Diagnostics.Windows/Properties/AssemblyInfo.cs
+++ b/src/BenchmarkDotNet.Diagnostics.Windows/Properties/AssemblyInfo.cs
@@ -7,8 +7,4 @@
[assembly: CLSCompliant(true)]
-#if RELEASE
[assembly: InternalsVisibleTo("BenchmarkDotNet.IntegrationTests,PublicKey=" + BenchmarkDotNetInfo.PublicKey)]
-#else
-[assembly: InternalsVisibleTo("BenchmarkDotNet.IntegrationTests")]
-#endif
\ No newline at end of file
diff --git a/src/BenchmarkDotNet.Diagnostics.Windows/Sessions.cs b/src/BenchmarkDotNet.Diagnostics.Windows/Sessions.cs
index cf0b6fa88f..f0f5dcc475 100644
--- a/src/BenchmarkDotNet.Diagnostics.Windows/Sessions.cs
+++ b/src/BenchmarkDotNet.Diagnostics.Windows/Sessions.cs
@@ -5,13 +5,13 @@
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Engines;
using BenchmarkDotNet.Exporters;
+using BenchmarkDotNet.Extensions;
using BenchmarkDotNet.Helpers;
using BenchmarkDotNet.Loggers;
-using BenchmarkDotNet.Extensions;
+using BenchmarkDotNet.Running;
using Microsoft.Diagnostics.Tracing;
using Microsoft.Diagnostics.Tracing.Parsers;
using Microsoft.Diagnostics.Tracing.Session;
-using BenchmarkDotNet.Running;
namespace BenchmarkDotNet.Diagnostics.Windows
{
@@ -90,7 +90,7 @@ internal override Session EnableProviders()
}
}
- internal abstract class Session : IDisposable
+ internal abstract class Session : DisposeAtProcessTermination
{
private const int MaxSessionNameLength = 128;
@@ -114,27 +114,16 @@ protected Session(string sessionName, DiagnoserActionParameters details, EtwProf
BufferSizeMB = config.BufferSizeInMb,
CpuSampleIntervalMSec = config.CpuSampleIntervalInMilliseconds,
};
-
- Console.CancelKeyPress += OnConsoleCancelKeyPress;
- AppDomain.CurrentDomain.ProcessExit += OnProcessExit;
}
- public void Dispose() => TraceEventSession.Dispose();
-
- internal void Stop()
+ public override void Dispose()
{
- TraceEventSession.Stop();
-
- Console.CancelKeyPress -= OnConsoleCancelKeyPress;
- AppDomain.CurrentDomain.ProcessExit -= OnProcessExit;
+ TraceEventSession.Dispose();
+ base.Dispose();
}
internal abstract Session EnableProviders();
- private void OnConsoleCancelKeyPress(object sender, ConsoleCancelEventArgs e) => Stop();
-
- private void OnProcessExit(object sender, EventArgs e) => Stop();
-
protected static string GetSessionName(BenchmarkCase benchmarkCase)
{
string benchmarkName = FullNameProvider.GetBenchmarkName(benchmarkCase);
diff --git a/src/BenchmarkDotNet.Diagnostics.Windows/Tracing/NativeMemoryLogParser.cs b/src/BenchmarkDotNet.Diagnostics.Windows/Tracing/NativeMemoryLogParser.cs
index 4d8b79c3d1..295ea1d46b 100644
--- a/src/BenchmarkDotNet.Diagnostics.Windows/Tracing/NativeMemoryLogParser.cs
+++ b/src/BenchmarkDotNet.Diagnostics.Windows/Tracing/NativeMemoryLogParser.cs
@@ -38,7 +38,7 @@ public NativeMemoryLogParser(string etlFilePath, BenchmarkCase benchmarkCase, IL
this.benchmarkCase = benchmarkCase;
this.logger = logger;
- moduleName = programName;
+ moduleName = programName.ToLowerInvariant();
functionNames = new[]
{
nameof(EngineParameters.WorkloadActionUnroll),
diff --git a/src/BenchmarkDotNet.Diagnostics.dotMemory/BenchmarkDotNet.Diagnostics.dotMemory.csproj b/src/BenchmarkDotNet.Diagnostics.dotMemory/BenchmarkDotNet.Diagnostics.dotMemory.csproj
index 414d927c0d..cc209f4258 100644
--- a/src/BenchmarkDotNet.Diagnostics.dotMemory/BenchmarkDotNet.Diagnostics.dotMemory.csproj
+++ b/src/BenchmarkDotNet.Diagnostics.dotMemory/BenchmarkDotNet.Diagnostics.dotMemory.csproj
@@ -14,7 +14,7 @@
-
+
diff --git a/src/BenchmarkDotNet.Diagnostics.dotMemory/DotMemoryDiagnoser.cs b/src/BenchmarkDotNet.Diagnostics.dotMemory/DotMemoryDiagnoser.cs
index 24e62feb31..7616ca468c 100644
--- a/src/BenchmarkDotNet.Diagnostics.dotMemory/DotMemoryDiagnoser.cs
+++ b/src/BenchmarkDotNet.Diagnostics.dotMemory/DotMemoryDiagnoser.cs
@@ -1,148 +1,126 @@
using System;
-using System.Collections.Generic;
-using System.Collections.Immutable;
-using System.Linq;
-using BenchmarkDotNet.Analysers;
+using System.Reflection;
+using BenchmarkDotNet.Detectors;
using BenchmarkDotNet.Diagnosers;
-using BenchmarkDotNet.Engines;
-using BenchmarkDotNet.Exporters;
+using BenchmarkDotNet.Helpers;
using BenchmarkDotNet.Jobs;
-using BenchmarkDotNet.Loggers;
-using BenchmarkDotNet.Portability;
-using BenchmarkDotNet.Reports;
-using BenchmarkDotNet.Running;
-using BenchmarkDotNet.Validators;
-using RunMode = BenchmarkDotNet.Diagnosers.RunMode;
+using JetBrains.Profiler.SelfApi;
-namespace BenchmarkDotNet.Diagnostics.dotMemory
+namespace BenchmarkDotNet.Diagnostics.dotMemory;
+
+public class DotMemoryDiagnoser(Uri? nugetUrl = null, string? downloadTo = null) : SnapshotProfilerBase
{
- public class DotMemoryDiagnoser(Uri? nugetUrl = null, string? toolsDownloadFolder = null) : IProfiler
+ public override string ShortName => "dotMemory";
+
+ protected override void InitTool(Progress progress)
{
- private DotMemoryTool? tool;
+ DotMemory.InitAsync(progress, nugetUrl, NuGetApi.V3, downloadTo).Wait();
+ }
- public IEnumerable Ids => new[] { "DotMemory" };
- public string ShortName => "dotMemory";
+ protected override void AttachToCurrentProcess(string snapshotFile)
+ {
+ DotMemory.Attach(new DotMemory.Config().SaveToFile(snapshotFile));
+ }
- public RunMode GetRunMode(BenchmarkCase benchmarkCase)
- {
- return IsSupported(benchmarkCase.Job.Environment.GetRuntime().RuntimeMoniker) ? RunMode.ExtraRun : RunMode.None;
- }
+ protected override void AttachToProcessByPid(int pid, string snapshotFile)
+ {
+ DotMemory.Attach(new DotMemory.Config().ProfileExternalProcess(pid).SaveToFile(snapshotFile));
+ }
- private readonly List snapshotFilePaths = new ();
+ protected override void TakeSnapshot()
+ {
+ DotMemory.GetSnapshot();
+ }
- public void Handle(HostSignal signal, DiagnoserActionParameters parameters)
- {
- var logger = parameters.Config.GetCompositeLogger();
- var job = parameters.BenchmarkCase.Job;
+ protected override void Detach()
+ {
+ DotMemory.Detach();
+ }
- var runtimeMoniker = job.Environment.GetRuntime().RuntimeMoniker;
- if (!IsSupported(runtimeMoniker))
- {
- logger.WriteLineError($"Runtime '{runtimeMoniker}' is not supported by dotMemory");
- return;
- }
+ protected override string CreateSnapshotFilePath(DiagnoserActionParameters parameters)
+ {
+ return ArtifactFileNameHelper.GetFilePath(parameters, "snapshots", DateTime.Now, "dmw", ".0000".Length);
+ }
- switch (signal)
- {
- case HostSignal.BeforeAnythingElse:
- if (tool is null)
- {
- 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;
- }
- }
+ protected override string GetRunnerPath()
+ {
+ var consoleRunnerPackageField = typeof(DotMemory).GetField("ConsoleRunnerPackage", BindingFlags.NonPublic | BindingFlags.Static);
+ if (consoleRunnerPackageField == null)
+ throw new InvalidOperationException("Field 'ConsoleRunnerPackage' not found.");
- public IEnumerable Exporters => Enumerable.Empty();
- public IEnumerable Analysers => Enumerable.Empty();
+ object? consoleRunnerPackage = consoleRunnerPackageField.GetValue(null);
+ if (consoleRunnerPackage == null)
+ throw new InvalidOperationException("Unable to get value of 'ConsoleRunnerPackage'.");
- public IEnumerable 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");
- }
- }
+ var consoleRunnerPackageType = consoleRunnerPackage.GetType();
+ var getRunnerPathMethod = consoleRunnerPackageType.GetMethod("GetRunnerPath");
+ if (getRunnerPathMethod == null)
+ throw new InvalidOperationException("Method 'GetRunnerPath' not found.");
- 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");
- }
- }
+ string? runnerPath = getRunnerPathMethod.Invoke(consoleRunnerPackage, null) as string;
+ if (runnerPath == null)
+ throw new InvalidOperationException("Unable to invoke 'GetRunnerPath'.");
- public IEnumerable ProcessResults(DiagnoserResults results) => ImmutableArray.Empty;
+ return runnerPath;
+ }
- public void DisplayResults(ILogger logger)
+ internal override bool IsSupported(RuntimeMoniker runtimeMoniker)
+ {
+ switch (runtimeMoniker)
{
- if (snapshotFilePaths.Any())
- {
- logger.WriteLineInfo("The following dotMemory snapshots were generated:");
- foreach (string snapshotFilePath in snapshotFilePaths)
- logger.WriteLineInfo($"* {snapshotFilePath}");
- }
+ 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:
+ case RuntimeMoniker.Net10_0:
+ return true;
+ case RuntimeMoniker.NotRecognized:
+ case RuntimeMoniker.Mono:
+ case RuntimeMoniker.NativeAot60:
+ case RuntimeMoniker.NativeAot70:
+ case RuntimeMoniker.NativeAot80:
+ case RuntimeMoniker.NativeAot90:
+ case RuntimeMoniker.NativeAot10_0:
+ case RuntimeMoniker.Wasm:
+ case RuntimeMoniker.WasmNet50:
+ case RuntimeMoniker.WasmNet60:
+ case RuntimeMoniker.WasmNet70:
+ case RuntimeMoniker.WasmNet80:
+ case RuntimeMoniker.WasmNet90:
+ case RuntimeMoniker.WasmNet10_0:
+ case RuntimeMoniker.MonoAOTLLVM:
+ case RuntimeMoniker.MonoAOTLLVMNet60:
+ case RuntimeMoniker.MonoAOTLLVMNet70:
+ case RuntimeMoniker.MonoAOTLLVMNet80:
+ case RuntimeMoniker.MonoAOTLLVMNet90:
+ case RuntimeMoniker.MonoAOTLLVMNet10_0:
+ case RuntimeMoniker.Mono60:
+ case RuntimeMoniker.Mono70:
+ case RuntimeMoniker.Mono80:
+ case RuntimeMoniker.Mono90:
+ case RuntimeMoniker.Mono10_0:
+#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 OsDetector.IsWindows();
+ case RuntimeMoniker.NetCoreApp30:
+ case RuntimeMoniker.NetCoreApp31:
+ return OsDetector.IsWindows() || OsDetector.IsLinux();
+ default:
+ throw new ArgumentOutOfRangeException(nameof(runtimeMoniker), runtimeMoniker, $"Runtime moniker {runtimeMoniker} is not supported");
}
}
-}
\ No newline at end of file
+}
diff --git a/src/BenchmarkDotNet.Diagnostics.dotMemory/DotMemoryDiagnoserAttribute.cs b/src/BenchmarkDotNet.Diagnostics.dotMemory/DotMemoryDiagnoserAttribute.cs
index f9a3612471..c0fb55d4f1 100644
--- a/src/BenchmarkDotNet.Diagnostics.dotMemory/DotMemoryDiagnoserAttribute.cs
+++ b/src/BenchmarkDotNet.Diagnostics.dotMemory/DotMemoryDiagnoserAttribute.cs
@@ -1,22 +1,22 @@
using System;
using BenchmarkDotNet.Configs;
-namespace BenchmarkDotNet.Diagnostics.dotMemory
+namespace BenchmarkDotNet.Diagnostics.dotMemory;
+
+[AttributeUsage(AttributeTargets.Class)]
+public class DotMemoryDiagnoserAttribute : Attribute, IConfigSource
{
- [AttributeUsage(AttributeTargets.Class)]
- public class DotMemoryDiagnoserAttribute : Attribute, IConfigSource
- {
- public IConfig Config { get; }
+ public IConfig Config { get; }
- public DotMemoryDiagnoserAttribute()
- {
- Config = ManualConfig.CreateEmpty().AddDiagnoser(new DotMemoryDiagnoser());
- }
+ public DotMemoryDiagnoserAttribute()
+ {
+ var diagnoser = new DotMemoryDiagnoser();
+ Config = ManualConfig.CreateEmpty().AddDiagnoser(diagnoser);
+ }
- public DotMemoryDiagnoserAttribute(string? nugetUrl = null, string? toolsDownloadFolder = null)
- {
- var nugetUri = nugetUrl == null ? null : new Uri(nugetUrl);
- Config = ManualConfig.CreateEmpty().AddDiagnoser(new DotMemoryDiagnoser(nugetUri, toolsDownloadFolder));
- }
+ public DotMemoryDiagnoserAttribute(Uri? nugetUrl, string? downloadTo = null)
+ {
+ var diagnoser = new DotMemoryDiagnoser(nugetUrl, downloadTo);
+ Config = ManualConfig.CreateEmpty().AddDiagnoser(diagnoser);
}
}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet.Diagnostics.dotMemory/DotMemoryTool.cs b/src/BenchmarkDotNet.Diagnostics.dotMemory/DotMemoryTool.cs
deleted file mode 100644
index c28e08fdb2..0000000000
--- a/src/BenchmarkDotNet.Diagnostics.dotMemory/DotMemoryTool.cs
+++ /dev/null
@@ -1,140 +0,0 @@
-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.InitAsync(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;
- }
- }
-}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet.Diagnostics.dotMemory/Progress.cs b/src/BenchmarkDotNet.Diagnostics.dotMemory/Progress.cs
deleted file mode 100644
index 738997bb6d..0000000000
--- a/src/BenchmarkDotNet.Diagnostics.dotMemory/Progress.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-using System;
-using System.Diagnostics;
-using BenchmarkDotNet.Loggers;
-
-namespace BenchmarkDotNet.Diagnostics.dotMemory
-{
- public class Progress : IProgress
- {
- 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();
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet.Diagnostics.dotMemory/Properties/AssemblyInfo.cs b/src/BenchmarkDotNet.Diagnostics.dotMemory/Properties/AssemblyInfo.cs
index 270fdc2c9c..4cc109a0a4 100644
--- a/src/BenchmarkDotNet.Diagnostics.dotMemory/Properties/AssemblyInfo.cs
+++ b/src/BenchmarkDotNet.Diagnostics.dotMemory/Properties/AssemblyInfo.cs
@@ -4,8 +4,4 @@
[assembly: CLSCompliant(true)]
-#if RELEASE
[assembly: InternalsVisibleTo("BenchmarkDotNet.Tests,PublicKey=" + BenchmarkDotNetInfo.PublicKey)]
-#else
-[assembly: InternalsVisibleTo("BenchmarkDotNet.Tests")]
-#endif
\ No newline at end of file
diff --git a/src/BenchmarkDotNet.Diagnostics.dotTrace/BenchmarkDotNet.Diagnostics.dotTrace.csproj b/src/BenchmarkDotNet.Diagnostics.dotTrace/BenchmarkDotNet.Diagnostics.dotTrace.csproj
index 4ea24eeb04..8206182d6a 100644
--- a/src/BenchmarkDotNet.Diagnostics.dotTrace/BenchmarkDotNet.Diagnostics.dotTrace.csproj
+++ b/src/BenchmarkDotNet.Diagnostics.dotTrace/BenchmarkDotNet.Diagnostics.dotTrace.csproj
@@ -14,7 +14,7 @@
-
+
diff --git a/src/BenchmarkDotNet.Diagnostics.dotTrace/DotTraceDiagnoser.cs b/src/BenchmarkDotNet.Diagnostics.dotTrace/DotTraceDiagnoser.cs
index 2aada424b7..8b5d3a858f 100644
--- a/src/BenchmarkDotNet.Diagnostics.dotTrace/DotTraceDiagnoser.cs
+++ b/src/BenchmarkDotNet.Diagnostics.dotTrace/DotTraceDiagnoser.cs
@@ -1,142 +1,129 @@
using System;
-using System.Collections.Generic;
-using System.Collections.Immutable;
-using System.Linq;
-using BenchmarkDotNet.Analysers;
+using System.Reflection;
+using BenchmarkDotNet.Detectors;
using BenchmarkDotNet.Diagnosers;
-using BenchmarkDotNet.Engines;
-using BenchmarkDotNet.Exporters;
+using BenchmarkDotNet.Helpers;
using BenchmarkDotNet.Jobs;
-using BenchmarkDotNet.Loggers;
-using BenchmarkDotNet.Portability;
-using BenchmarkDotNet.Reports;
-using BenchmarkDotNet.Running;
-using BenchmarkDotNet.Toolchains;
-using BenchmarkDotNet.Validators;
-using RunMode = BenchmarkDotNet.Diagnosers.RunMode;
+using JetBrains.Profiler.SelfApi;
-namespace BenchmarkDotNet.Diagnostics.dotTrace
+namespace BenchmarkDotNet.Diagnostics.dotTrace;
+
+public class DotTraceDiagnoser(Uri? nugetUrl = null, string? downloadTo = null) : SnapshotProfilerBase
{
- public class DotTraceDiagnoser(Uri? nugetUrl = null, string? toolsDownloadFolder = null) : IProfiler
+ public override string ShortName => "dotTrace";
+
+ protected override void InitTool(Progress progress)
{
- public IEnumerable Ids => new[] { "DotTrace" };
- public string ShortName => "dotTrace";
+ DotTrace.InitAsync(progress, nugetUrl, NuGetApi.V3, downloadTo).Wait();
+ }
- public RunMode GetRunMode(BenchmarkCase benchmarkCase)
- {
- return IsSupported(benchmarkCase.Job.Environment.GetRuntime().RuntimeMoniker) ? RunMode.ExtraRun : RunMode.None;
- }
+ protected override void AttachToCurrentProcess(string snapshotFile)
+ {
+ DotTrace.Attach(new DotTrace.Config().SaveToFile(snapshotFile));
+ DotTrace.StartCollectingData();
+ }
- private readonly List snapshotFilePaths = new ();
+ protected override void AttachToProcessByPid(int pid, string snapshotFile)
+ {
+ DotTrace.Attach(new DotTrace.Config().ProfileExternalProcess(pid).SaveToFile(snapshotFile));
+ DotTrace.StartCollectingData();
+ }
- public void Handle(HostSignal signal, DiagnoserActionParameters parameters)
- {
- var job = parameters.BenchmarkCase.Job;
- bool isInProcess = job.GetToolchain().IsInProcess;
- var logger = parameters.Config.GetCompositeLogger();
- DotTraceToolBase tool = isInProcess
- ? new InProcessDotTraceTool(logger, nugetUrl, downloadTo: toolsDownloadFolder)
- : new ExternalDotTraceTool(logger, nugetUrl, downloadTo: toolsDownloadFolder);
+ protected override void TakeSnapshot()
+ {
+ DotTrace.StopCollectingData();
+ DotTrace.SaveData();
+ }
- var runtimeMoniker = job.Environment.GetRuntime().RuntimeMoniker;
- if (!IsSupported(runtimeMoniker))
- {
- logger.WriteLineError($"Runtime '{runtimeMoniker}' is not supported by dotTrace");
- return;
- }
+ protected override void Detach()
+ {
+ DotTrace.Detach();
+ }
- switch (signal)
- {
- case HostSignal.BeforeAnythingElse:
- tool.Init(parameters);
- break;
- case HostSignal.BeforeActualRun:
- snapshotFilePaths.Add(tool.Start(parameters));
- break;
- case HostSignal.AfterActualRun:
- tool.Stop(parameters);
- break;
- }
- }
+ protected override string CreateSnapshotFilePath(DiagnoserActionParameters parameters)
+ {
+ return ArtifactFileNameHelper.GetFilePath(parameters, "snapshots", DateTime.Now, "dtp", ".0000".Length);
+ }
- public IEnumerable Exporters => Enumerable.Empty();
- public IEnumerable Analysers => Enumerable.Empty();
+ protected override string GetRunnerPath()
+ {
+ var consoleRunnerPackageField = typeof(DotTrace).GetField("ConsoleRunnerPackage", BindingFlags.NonPublic | BindingFlags.Static);
+ if (consoleRunnerPackageField == null)
+ throw new InvalidOperationException("Field 'ConsoleRunnerPackage' not found.");
- public IEnumerable 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 dotTrace");
- }
- }
+ object? consoleRunnerPackage = consoleRunnerPackageField.GetValue(null);
+ if (consoleRunnerPackage == null)
+ throw new InvalidOperationException("Unable to get value of 'ConsoleRunnerPackage'.");
- 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");
- }
- }
+ var consoleRunnerPackageType = consoleRunnerPackage.GetType();
+ var getRunnerPathMethod = consoleRunnerPackageType.GetMethod("GetRunnerPath");
+ if (getRunnerPathMethod == null)
+ throw new InvalidOperationException("Method 'GetRunnerPath' not found.");
- public IEnumerable ProcessResults(DiagnoserResults results) => ImmutableArray.Empty;
+ string? runnerPath = getRunnerPathMethod.Invoke(consoleRunnerPackage, null) as string;
+ if (runnerPath == null)
+ throw new InvalidOperationException("Unable to invoke 'GetRunnerPath'.");
+
+ return runnerPath;
+ }
- public void DisplayResults(ILogger logger)
+ internal override bool IsSupported(RuntimeMoniker runtimeMoniker)
+ {
+ switch (runtimeMoniker)
{
- if (snapshotFilePaths.Any())
- {
- logger.WriteLineInfo("The following dotTrace snapshots were generated:");
- foreach (string snapshotFilePath in snapshotFilePaths)
- logger.WriteLineInfo($"* {snapshotFilePath}");
- }
+ 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:
+ case RuntimeMoniker.Net10_0:
+ return true;
+ case RuntimeMoniker.NotRecognized:
+ case RuntimeMoniker.Mono:
+ case RuntimeMoniker.NativeAot60:
+ case RuntimeMoniker.NativeAot70:
+ case RuntimeMoniker.NativeAot80:
+ case RuntimeMoniker.NativeAot90:
+ case RuntimeMoniker.NativeAot10_0:
+ case RuntimeMoniker.Wasm:
+ case RuntimeMoniker.WasmNet50:
+ case RuntimeMoniker.WasmNet60:
+ case RuntimeMoniker.WasmNet70:
+ case RuntimeMoniker.WasmNet80:
+ case RuntimeMoniker.WasmNet90:
+ case RuntimeMoniker.WasmNet10_0:
+ case RuntimeMoniker.MonoAOTLLVM:
+ case RuntimeMoniker.MonoAOTLLVMNet60:
+ case RuntimeMoniker.MonoAOTLLVMNet70:
+ case RuntimeMoniker.MonoAOTLLVMNet80:
+ case RuntimeMoniker.MonoAOTLLVMNet90:
+ case RuntimeMoniker.MonoAOTLLVMNet10_0:
+ case RuntimeMoniker.Mono60:
+ case RuntimeMoniker.Mono70:
+ case RuntimeMoniker.Mono80:
+ case RuntimeMoniker.Mono90:
+ case RuntimeMoniker.Mono10_0:
+#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 OsDetector.IsWindows();
+ case RuntimeMoniker.NetCoreApp30:
+ case RuntimeMoniker.NetCoreApp31:
+ return OsDetector.IsWindows() || OsDetector.IsLinux();
+ default:
+ throw new ArgumentOutOfRangeException(nameof(runtimeMoniker), runtimeMoniker, $"Runtime moniker {runtimeMoniker} is not supported");
}
}
-}
\ No newline at end of file
+}
diff --git a/src/BenchmarkDotNet.Diagnostics.dotTrace/DotTraceDiagnoserAttribute.cs b/src/BenchmarkDotNet.Diagnostics.dotTrace/DotTraceDiagnoserAttribute.cs
index 90d4173016..f056a98cbd 100644
--- a/src/BenchmarkDotNet.Diagnostics.dotTrace/DotTraceDiagnoserAttribute.cs
+++ b/src/BenchmarkDotNet.Diagnostics.dotTrace/DotTraceDiagnoserAttribute.cs
@@ -1,22 +1,22 @@
using System;
using BenchmarkDotNet.Configs;
-namespace BenchmarkDotNet.Diagnostics.dotTrace
+namespace BenchmarkDotNet.Diagnostics.dotTrace;
+
+[AttributeUsage(AttributeTargets.Class)]
+public class DotTraceDiagnoserAttribute : Attribute, IConfigSource
{
- [AttributeUsage(AttributeTargets.Class)]
- public class DotTraceDiagnoserAttribute : Attribute, IConfigSource
- {
- public IConfig Config { get; }
+ public IConfig Config { get; }
- public DotTraceDiagnoserAttribute()
- {
- Config = ManualConfig.CreateEmpty().AddDiagnoser(new DotTraceDiagnoser());
- }
+ public DotTraceDiagnoserAttribute()
+ {
+ var diagnoser = new DotTraceDiagnoser();
+ Config = ManualConfig.CreateEmpty().AddDiagnoser(diagnoser);
+ }
- public DotTraceDiagnoserAttribute(string? nugetUrl = null, string? toolsDownloadFolder = null)
- {
- var nugetUri = nugetUrl == null ? null : new Uri(nugetUrl);
- Config = ManualConfig.CreateEmpty().AddDiagnoser(new DotTraceDiagnoser(nugetUri, toolsDownloadFolder));
- }
+ public DotTraceDiagnoserAttribute(Uri? nugetUrl, string? downloadTo = null)
+ {
+ var diagnoser = new DotTraceDiagnoser(nugetUrl, downloadTo);
+ Config = ManualConfig.CreateEmpty().AddDiagnoser(diagnoser);
}
}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet.Diagnostics.dotTrace/DotTraceToolBase.cs b/src/BenchmarkDotNet.Diagnostics.dotTrace/DotTraceToolBase.cs
deleted file mode 100644
index 2fb36e9c66..0000000000
--- a/src/BenchmarkDotNet.Diagnostics.dotTrace/DotTraceToolBase.cs
+++ /dev/null
@@ -1,145 +0,0 @@
-using System;
-using System.IO;
-using System.Reflection;
-using BenchmarkDotNet.Diagnosers;
-using BenchmarkDotNet.Helpers;
-using BenchmarkDotNet.Loggers;
-using JetBrains.Profiler.SelfApi;
-
-namespace BenchmarkDotNet.Diagnostics.dotTrace
-{
- internal abstract class DotTraceToolBase
- {
- private readonly ILogger logger;
- private readonly Uri? nugetUrl;
- private readonly NuGetApi nugetApi;
- private readonly string? downloadTo;
-
- protected DotTraceToolBase(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(DiagnoserActionParameters parameters)
- {
- try
- {
- logger.WriteLineInfo("Ensuring that dotTrace prerequisite is installed...");
- var progress = new Progress(logger, "Installing DotTrace");
- DotTrace.InitAsync(progress, nugetUrl, nugetApi, downloadTo).Wait();
- logger.WriteLineInfo("dotTrace prerequisite is installed");
- logger.WriteLineInfo($"dotTrace runner path: {GetRunnerPath()}");
- }
- catch (Exception e)
- {
- logger.WriteLineError(e.ToString());
- }
- }
-
- protected abstract bool AttachOnly { get; }
- protected abstract void Attach(DiagnoserActionParameters parameters, string snapshotFile);
- protected abstract void StartCollectingData();
- protected abstract void SaveData();
- protected abstract void Detach();
-
- public string Start(DiagnoserActionParameters parameters)
- {
- string snapshotFile = ArtifactFileNameHelper.GetFilePath(parameters, "snapshots", DateTime.Now, "dtp", ".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 dotTrace to the process...");
- Attach(parameters, snapshotFile);
- logger.WriteLineInfo("dotTrace is successfully attached");
- }
- catch (Exception e)
- {
- logger.WriteLineError(e.ToString());
- return snapshotFile;
- }
-
- if (!AttachOnly)
- {
- try
- {
- logger.WriteLineInfo("Start collecting data using dataTrace...");
- StartCollectingData();
- logger.WriteLineInfo("Data collecting is successfully started");
- }
- catch (Exception e)
- {
- logger.WriteLineError(e.ToString());
- }
- }
-
- return snapshotFile;
- }
-
- public void Stop(DiagnoserActionParameters parameters)
- {
- if (!AttachOnly)
- {
- try
- {
- logger.WriteLineInfo("Saving dotTrace snapshot...");
- SaveData();
- logger.WriteLineInfo("dotTrace snapshot is successfully saved to the artifact folder");
- }
- catch (Exception e)
- {
- logger.WriteLineError(e.ToString());
- }
-
- try
- {
- logger.WriteLineInfo("Detaching dotTrace from the process...");
- Detach();
- logger.WriteLineInfo("dotTrace is successfully detached");
- }
- catch (Exception e)
- {
- logger.WriteLineError(e.ToString());
- }
- }
- }
-
- protected string GetRunnerPath()
- {
- var consoleRunnerPackageField = typeof(DotTrace).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;
- }
- }
-}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet.Diagnostics.dotTrace/ExternalDotTraceTool.cs b/src/BenchmarkDotNet.Diagnostics.dotTrace/ExternalDotTraceTool.cs
deleted file mode 100644
index c7f1cf18c8..0000000000
--- a/src/BenchmarkDotNet.Diagnostics.dotTrace/ExternalDotTraceTool.cs
+++ /dev/null
@@ -1,84 +0,0 @@
-using System;
-using System.Diagnostics;
-using System.Threading.Tasks;
-using BenchmarkDotNet.Diagnosers;
-using BenchmarkDotNet.Loggers;
-using JetBrains.Profiler.SelfApi;
-using ILogger = BenchmarkDotNet.Loggers.ILogger;
-
-namespace BenchmarkDotNet.Diagnostics.dotTrace
-{
- internal class ExternalDotTraceTool : DotTraceToolBase
- {
- private static readonly TimeSpan AttachTimeout = TimeSpan.FromMinutes(5);
-
- public ExternalDotTraceTool(ILogger logger, Uri? nugetUrl = null, NuGetApi nugetApi = NuGetApi.V3, string? downloadTo = null) :
- base(logger, nugetUrl, nugetApi, downloadTo) { }
-
- protected override bool AttachOnly => true;
-
- protected override void Attach(DiagnoserActionParameters parameters, string snapshotFile)
- {
- var logger = parameters.Config.GetCompositeLogger();
-
- string runnerPath = GetRunnerPath();
- int pid = parameters.Process.Id;
- string arguments = $"attach {pid} --save-to=\"{snapshotFile}\" --service-output=on";
-
- logger.WriteLineInfo($"Starting process: '{runnerPath} {arguments}'");
-
- var processStartInfo = new ProcessStartInfo
- {
- FileName = runnerPath,
- WorkingDirectory = "",
- Arguments = arguments,
- UseShellExecute = false,
- CreateNoWindow = true,
- RedirectStandardOutput = true,
- RedirectStandardError = true
- };
-
- var attachWaitingTask = new TaskCompletionSource();
- var process = new Process { StartInfo = processStartInfo };
- try
- {
- process.OutputDataReceived += (_, args) =>
- {
- string? content = args.Data;
- if (content != null)
- {
- logger.WriteLineInfo("[dotTrace] " + content);
- if (content.Contains("##dotTrace[\"started\""))
- attachWaitingTask.TrySetResult(true);
- }
- };
- process.ErrorDataReceived += (_, args) =>
- {
- string? content = args.Data;
- if (content != null)
- logger.WriteLineError("[dotTrace] " + args.Data);
- };
- process.Exited += (_, _) => { attachWaitingTask.TrySetResult(false); };
- process.Start();
- process.BeginOutputReadLine();
- process.BeginErrorReadLine();
- }
- catch (Exception e)
- {
- attachWaitingTask.TrySetResult(false);
- logger.WriteLineError(e.ToString());
- }
-
- if (!attachWaitingTask.Task.Wait(AttachTimeout))
- 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})");
- }
-
- protected override void StartCollectingData() { }
-
- protected override void SaveData() { }
-
- protected override void Detach() { }
- }
-}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet.Diagnostics.dotTrace/InProcessDotTraceTool.cs b/src/BenchmarkDotNet.Diagnostics.dotTrace/InProcessDotTraceTool.cs
deleted file mode 100644
index a02c9c1995..0000000000
--- a/src/BenchmarkDotNet.Diagnostics.dotTrace/InProcessDotTraceTool.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-using System;
-using BenchmarkDotNet.Diagnosers;
-using BenchmarkDotNet.Loggers;
-using JetBrains.Profiler.SelfApi;
-
-namespace BenchmarkDotNet.Diagnostics.dotTrace
-{
- internal class InProcessDotTraceTool : DotTraceToolBase
- {
- public InProcessDotTraceTool(ILogger logger, Uri? nugetUrl = null, NuGetApi nugetApi = NuGetApi.V3, string? downloadTo = null) :
- base(logger, nugetUrl, nugetApi, downloadTo) { }
-
- protected override bool AttachOnly => false;
-
- protected override void Attach(DiagnoserActionParameters parameters, string snapshotFile)
- {
- var config = new DotTrace.Config();
- config.SaveToFile(snapshotFile);
- DotTrace.Attach(config);
- }
-
- protected override void StartCollectingData() => DotTrace.StartCollectingData();
-
- protected override void SaveData() => DotTrace.SaveData();
-
- protected override void Detach() => DotTrace.Detach();
- }
-}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet.Diagnostics.dotTrace/Progress.cs b/src/BenchmarkDotNet.Diagnostics.dotTrace/Progress.cs
deleted file mode 100644
index c353939f1f..0000000000
--- a/src/BenchmarkDotNet.Diagnostics.dotTrace/Progress.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-using System;
-using System.Diagnostics;
-using BenchmarkDotNet.Loggers;
-
-namespace BenchmarkDotNet.Diagnostics.dotTrace
-{
- public class Progress : IProgress
- {
- 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();
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet.Diagnostics.dotTrace/Properties/AssemblyInfo.cs b/src/BenchmarkDotNet.Diagnostics.dotTrace/Properties/AssemblyInfo.cs
index 270fdc2c9c..4cc109a0a4 100644
--- a/src/BenchmarkDotNet.Diagnostics.dotTrace/Properties/AssemblyInfo.cs
+++ b/src/BenchmarkDotNet.Diagnostics.dotTrace/Properties/AssemblyInfo.cs
@@ -4,8 +4,4 @@
[assembly: CLSCompliant(true)]
-#if RELEASE
[assembly: InternalsVisibleTo("BenchmarkDotNet.Tests,PublicKey=" + BenchmarkDotNetInfo.PublicKey)]
-#else
-[assembly: InternalsVisibleTo("BenchmarkDotNet.Tests")]
-#endif
\ No newline at end of file
diff --git a/src/BenchmarkDotNet.Disassembler.x64/BenchmarkDotNet.Disassembler.x64.csproj b/src/BenchmarkDotNet.Disassembler.x64/BenchmarkDotNet.Disassembler.x64.csproj
index 3162c74e5d..2f15efcc13 100644
--- a/src/BenchmarkDotNet.Disassembler.x64/BenchmarkDotNet.Disassembler.x64.csproj
+++ b/src/BenchmarkDotNet.Disassembler.x64/BenchmarkDotNet.Disassembler.x64.csproj
@@ -15,7 +15,7 @@
BenchmarkDotNet.Disassembler
-
-
+
+
diff --git a/src/BenchmarkDotNet.Disassembler.x86/BenchmarkDotNet.Disassembler.x86.csproj b/src/BenchmarkDotNet.Disassembler.x86/BenchmarkDotNet.Disassembler.x86.csproj
index 21051d6104..5410f6d77b 100644
--- a/src/BenchmarkDotNet.Disassembler.x86/BenchmarkDotNet.Disassembler.x86.csproj
+++ b/src/BenchmarkDotNet.Disassembler.x86/BenchmarkDotNet.Disassembler.x86.csproj
@@ -21,7 +21,7 @@
-
-
+
+
diff --git a/src/BenchmarkDotNet.Exporters.Plotting/BenchmarkDotNet.Exporters.Plotting.csproj b/src/BenchmarkDotNet.Exporters.Plotting/BenchmarkDotNet.Exporters.Plotting.csproj
index 799a055885..cefa91a7ca 100644
--- a/src/BenchmarkDotNet.Exporters.Plotting/BenchmarkDotNet.Exporters.Plotting.csproj
+++ b/src/BenchmarkDotNet.Exporters.Plotting/BenchmarkDotNet.Exporters.Plotting.csproj
@@ -1,19 +1,19 @@
-
-
-
- BenchmarkDotNet plotting export support.
- netstandard2.0
- BenchmarkDotNet.Exporters.Plotting
- BenchmarkDotNet.Exporters.Plotting
- BenchmarkDotNet.Exporters.Plotting
-
- True
- enable
-
-
-
-
-
-
-
-
+
+
+
+ BenchmarkDotNet plotting export support.
+ netstandard2.0
+ BenchmarkDotNet.Exporters.Plotting
+ BenchmarkDotNet.Exporters.Plotting
+ BenchmarkDotNet.Exporters.Plotting
+
+ True
+ enable
+
+
+
+
+
+
+
+
diff --git a/src/BenchmarkDotNet.Exporters.Plotting/ScottPlotExporter.cs b/src/BenchmarkDotNet.Exporters.Plotting/ScottPlotExporter.cs
index cfb7a735b7..df43599aec 100644
--- a/src/BenchmarkDotNet.Exporters.Plotting/ScottPlotExporter.cs
+++ b/src/BenchmarkDotNet.Exporters.Plotting/ScottPlotExporter.cs
@@ -1,256 +1,438 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using BenchmarkDotNet.Loggers;
-using BenchmarkDotNet.Properties;
-using BenchmarkDotNet.Reports;
-using ScottPlot;
-using ScottPlot.Plottables;
-
-namespace BenchmarkDotNet.Exporters.Plotting
-{
- ///
- /// Provides plot exports as .png files.
- ///
- public class ScottPlotExporter : IExporter
- {
- ///
- /// Default instance of the exporter with default configuration.
- ///
- public static readonly IExporter Default = new ScottPlotExporter();
-
- ///
- /// Gets the name of the Exporter type.
- ///
- public string Name => nameof(ScottPlotExporter);
-
- ///
- /// Initializes a new instance of ScottPlotExporter.
- ///
- /// The width of all plots in pixels (optional). Defaults to 1920.
- /// The height of all plots in pixels (optional). Defaults to 1080.
- public ScottPlotExporter(int width = 1920, int height = 1080)
- {
- this.Width = width;
- this.Height = height;
- this.IncludeBarPlot = true;
- this.RotateLabels = true;
- }
-
- ///
- /// Gets or sets the width of all plots in pixels.
- ///
- public int Width { get; set; }
-
- ///
- /// Gets or sets the height of all plots in pixels.
- ///
- public int Height { get; set; }
-
- ///
- /// Gets or sets a value indicating whether labels for Plot X-axis should be rotated.
- /// This allows for longer labels at the expense of chart height.
- ///
- public bool RotateLabels { get; set; }
-
- ///
- /// Gets or sets a value indicating whether a bar plot for time-per-op
- /// measurement values should be exported.
- ///
- public bool IncludeBarPlot { get; set; }
-
- ///
- /// Not supported.
- ///
- /// This parameter is not used.
- /// This parameter is not used.
- ///
- public void ExportToLog(Summary summary, ILogger logger)
- {
- throw new NotSupportedException();
- }
-
- ///
- /// Exports plots to .png file.
- ///
- /// The summary to be exported.
- /// Logger to output to.
- /// The file paths of every plot exported.
- public IEnumerable ExportToFiles(Summary summary, ILogger consoleLogger)
- {
- var title = summary.Title;
- var version = BenchmarkDotNetInfo.Instance.BrandTitle;
- var annotations = GetAnnotations(version);
-
- var (timeUnit, timeScale) = GetTimeUnit(summary.Reports.SelectMany(m => m.AllMeasurements));
-
- foreach (var benchmark in summary.Reports.GroupBy(r => r.BenchmarkCase.Descriptor.Type.Name))
- {
- var benchmarkName = benchmark.Key;
-
- // Get the measurement nanoseconds per op, divided by time scale, grouped by target and Job [param].
- var timeStats = from report in benchmark
- let jobId = report.BenchmarkCase.DisplayInfo.Replace(report.BenchmarkCase.Descriptor.DisplayInfo + ": ", string.Empty)
- from measurement in report.AllMeasurements
- let measurementValue = measurement.Nanoseconds / measurement.Operations
- group measurementValue / timeScale by (Target: report.BenchmarkCase.Descriptor.WorkloadMethodDisplayInfo, JobId: jobId) into g
- select (g.Key.Target, g.Key.JobId, Mean: g.Average(), StdError: StandardError(g.ToList()));
-
- if (this.IncludeBarPlot)
- {
- // -barplot.png
- yield return CreateBarPlot(
- $"{title} - {benchmarkName}",
- Path.Combine(summary.ResultsDirectoryPath, $"{title}-{benchmarkName}-barplot.png"),
- $"Time ({timeUnit})",
- "Target",
- timeStats,
- annotations);
- }
-
- /* TODO: Rest of the RPlotExporter plots.
- -boxplot.png
- --density.png
- --facetTimeline.png
- --facetTimelineSmooth.png
- ---timelineSmooth.png
- ---timelineSmooth.png*/
- }
- }
-
- ///
- /// Calculate Standard Deviation.
- ///
- /// Values to calculate from.
- /// Standard deviation of values.
- private static double StandardError(IReadOnlyList values)
- {
- double average = values.Average();
- double sumOfSquaresOfDifferences = values.Select(val => (val - average) * (val - average)).Sum();
- double standardDeviation = Math.Sqrt(sumOfSquaresOfDifferences / values.Count);
- return standardDeviation / Math.Sqrt(values.Count);
- }
-
- ///
- /// Gets the lowest appropriate time scale across all measurements.
- ///
- /// All measurements
- /// A unit and scaling factor to convert from nanoseconds.
- private (string Unit, double ScaleFactor) GetTimeUnit(IEnumerable values)
- {
- var minValue = values.Select(m => m.Nanoseconds / m.Operations).DefaultIfEmpty(0d).Min();
- if (minValue > 1000000000d)
- {
- return ("sec", 1000000000d);
- }
-
- if (minValue > 1000000d)
- {
- return ("ms", 1000000d);
- }
-
- if (minValue > 1000d)
- {
- return ("us", 1000d);
- }
-
- return ("ns", 1d);
- }
-
- private string CreateBarPlot(string title, string fileName, string yLabel, string xLabel, IEnumerable<(string Target, string JobId, double Mean, double StdError)> data, IReadOnlyList annotations)
- {
- Plot plt = new Plot();
- plt.Title(title, 28);
- plt.YLabel(yLabel);
- plt.XLabel(xLabel);
-
- var palette = new ScottPlot.Palettes.Category10();
-
- var legendPalette = data.Select(d => d.JobId)
- .Distinct()
- .Select((jobId, index) => (jobId, index))
- .ToDictionary(t => t.jobId, t => palette.GetColor(t.index));
-
- plt.Legend.IsVisible = true;
- plt.Legend.Location = Alignment.UpperRight;
- var legend = data.Select(d => d.JobId)
- .Distinct()
- .Select((label, index) => new LegendItem()
- {
- Label = label,
- FillColor = legendPalette[label]
- })
- .ToList();
-
- plt.Legend.ManualItems.AddRange(legend);
-
- var jobCount = plt.Legend.ManualItems.Count;
- var ticks = data
- .Select((d, index) => new Tick(index, d.Target))
- .ToArray();
- plt.Axes.Bottom.TickGenerator = new ScottPlot.TickGenerators.NumericManual(ticks);
- plt.Axes.Bottom.MajorTickStyle.Length = 0;
-
- if (this.RotateLabels)
- {
- plt.Axes.Bottom.TickLabelStyle.Rotation = 45;
- plt.Axes.Bottom.TickLabelStyle.Alignment = Alignment.MiddleLeft;
-
- // determine the width of the largest tick label
- float largestLabelWidth = 0;
- foreach (Tick tick in ticks)
- {
- PixelSize size = plt.Axes.Bottom.TickLabelStyle.Measure(tick.Label);
- largestLabelWidth = Math.Max(largestLabelWidth, size.Width);
- }
-
- // ensure axis panels do not get smaller than the largest label
- plt.Axes.Bottom.MinimumSize = largestLabelWidth;
- plt.Axes.Right.MinimumSize = largestLabelWidth;
- }
-
- var bars = data
- .Select((d, index) => new Bar()
- {
- Position = ticks[index].Position,
- Value = d.Mean,
- Error = d.StdError,
- FillColor = legendPalette[d.JobId]
- });
- plt.Add.Bars(bars);
-
- // Tell the plot to autoscale with no padding beneath the bars
- plt.Axes.Margins(bottom: 0, right: .2);
-
- plt.PlottableList.AddRange(annotations);
-
- plt.SavePng(fileName, this.Width, this.Height);
- return Path.GetFullPath(fileName);
- }
-
- ///
- /// Provides a list of annotations to put over the data area.
- ///
- /// The version to be displayed.
- /// A list of annotations for every plot.
- private IReadOnlyList GetAnnotations(string version)
- {
- var versionAnnotation = new Annotation()
- {
- Label =
- {
- Text = version,
- FontSize = 14,
- ForeColor = new Color(0, 0, 0, 100)
- },
- OffsetY = 10,
- OffsetX = 20,
- Alignment = Alignment.LowerRight
- };
-
-
- return new[] { versionAnnotation };
- }
- }
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using BenchmarkDotNet.Engines;
+using BenchmarkDotNet.Loggers;
+using BenchmarkDotNet.Properties;
+using BenchmarkDotNet.Reports;
+using ScottPlot;
+using ScottPlot.Plottables;
+
+namespace BenchmarkDotNet.Exporters.Plotting
+{
+ ///
+ /// Provides plot exports as .png files.
+ ///
+ public class ScottPlotExporter : IExporter
+ {
+ ///
+ /// Default instance of the exporter with default configuration.
+ ///
+ public static readonly IExporter Default = new ScottPlotExporter();
+
+ ///
+ /// Gets the name of the Exporter type.
+ ///
+ public string Name => nameof(ScottPlotExporter);
+
+ ///
+ /// Initializes a new instance of ScottPlotExporter.
+ ///
+ /// The width of all plots in pixels (optional). Defaults to 1920.
+ /// The height of all plots in pixels (optional). Defaults to 1080.
+ public ScottPlotExporter(int width = 1920, int height = 1080)
+ {
+ this.Width = width;
+ this.Height = height;
+ this.IncludeBarPlot = true;
+ this.IncludeBoxPlot = true;
+ this.RotateLabels = true;
+ }
+
+ ///
+ /// Gets or sets the width of all plots in pixels.
+ ///
+ public int Width { get; set; }
+
+ ///
+ /// Gets or sets the height of all plots in pixels.
+ ///
+ public int Height { get; set; }
+
+ ///
+ /// Gets or sets the common font size for ticks, labels etc. (defaults to 14).
+ ///
+ public int FontSize { get; set; } = 14;
+
+ ///
+ /// Gets or sets the font size for the chart title. (defaults to 28).
+ ///
+ public int TitleFontSize { get; set; } = 28;
+
+ ///
+ /// Gets or sets a value indicating whether labels for Plot X-axis should be rotated.
+ /// This allows for longer labels at the expense of chart height.
+ ///
+ public bool RotateLabels { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether a bar plot for time-per-op
+ /// measurement values should be exported.
+ ///
+ public bool IncludeBarPlot { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether a box plot or whisker plot for time-per-op
+ /// measurement values should be exported.
+ ///
+ public bool IncludeBoxPlot { get; set; }
+
+ ///
+ /// Not supported.
+ ///
+ /// This parameter is not used.
+ /// This parameter is not used.
+ ///
+ public void ExportToLog(Summary summary, ILogger logger)
+ {
+ throw new NotSupportedException();
+ }
+
+ ///
+ /// Exports plots to .png file.
+ ///
+ /// The summary to be exported.
+ /// Logger to output to.
+ /// The file paths of every plot exported.
+ public IEnumerable ExportToFiles(Summary summary, ILogger consoleLogger)
+ {
+ var title = summary.Title;
+ var version = BenchmarkDotNetInfo.Instance.BrandTitle;
+ var annotations = GetAnnotations(version);
+
+ var (timeUnit, timeScale) = GetTimeUnit(summary.Reports
+ .SelectMany(m => m.AllMeasurements.Where(m => m.Is(IterationMode.Workload, IterationStage.Result))));
+
+ foreach (var benchmark in summary.Reports.GroupBy(r => r.BenchmarkCase.Descriptor.Type.Name))
+ {
+ var benchmarkName = benchmark.Key;
+
+ // Get the measurement nanoseconds per op, divided by time scale, grouped by target and Job [param].
+ var timeStats = from report in benchmark
+ let jobId = report.BenchmarkCase.DisplayInfo.Replace(report.BenchmarkCase.Descriptor.DisplayInfo + ": ", string.Empty)
+ from measurement in report.AllMeasurements
+ where measurement.Is(IterationMode.Workload, IterationStage.Result)
+ let measurementValue = measurement.Nanoseconds / measurement.Operations
+ group measurementValue / timeScale by (Target: report.BenchmarkCase.Descriptor.WorkloadMethodDisplayInfo, JobId: jobId) into g
+ select new ChartStats(g.Key.Target, g.Key.JobId, g.ToList());
+
+ if (this.IncludeBarPlot)
+ {
+ // -barplot.png
+ yield return CreateBarPlot(
+ $"{title} - {benchmarkName}",
+ Path.Combine(summary.ResultsDirectoryPath, $"{title}-{benchmarkName}-barplot.png"),
+ $"Time ({timeUnit})",
+ "Target",
+ timeStats,
+ annotations);
+ }
+
+ if (this.IncludeBoxPlot)
+ {
+ // -boxplot.png
+ yield return CreateBoxPlot(
+ $"{title} - {benchmarkName}",
+ Path.Combine(summary.ResultsDirectoryPath, $"{title}-{benchmarkName}-boxplot.png"),
+ $"Time ({timeUnit})",
+ "Target",
+ timeStats,
+ annotations);
+ }
+
+ /* TODO: Rest of the RPlotExporter plots.
+ --density.png
+ --facetTimeline.png
+ --facetTimelineSmooth.png
+ ---timelineSmooth.png
+ ---timelineSmooth.png*/
+ }
+ }
+
+ ///
+ /// Calculate Standard Deviation.
+ ///
+ /// Values to calculate from.
+ /// Standard deviation of values.
+ private static double StandardError(IReadOnlyList values)
+ {
+ double average = values.Average();
+ double sumOfSquaresOfDifferences = values.Select(val => (val - average) * (val - average)).Sum();
+ double standardDeviation = Math.Sqrt(sumOfSquaresOfDifferences / values.Count);
+ return standardDeviation / Math.Sqrt(values.Count);
+ }
+
+ ///
+ /// Gets the lowest appropriate time scale across all measurements.
+ ///
+ /// All measurements
+ /// A unit and scaling factor to convert from nanoseconds.
+ private (string Unit, double ScaleFactor) GetTimeUnit(IEnumerable values)
+ {
+ var minValue = values.Select(m => m.Nanoseconds / m.Operations).DefaultIfEmpty(0d).Min();
+ if (minValue > 1000000000d)
+ {
+ return ("sec", 1000000000d);
+ }
+
+ if (minValue > 1000000d)
+ {
+ return ("ms", 1000000d);
+ }
+
+ if (minValue > 1000d)
+ {
+ return ("us", 1000d);
+ }
+
+ return ("ns", 1d);
+ }
+
+ private string CreateBarPlot(string title, string fileName, string yLabel, string xLabel, IEnumerable data, IReadOnlyList annotations)
+ {
+ Plot plt = new Plot();
+ plt.Title(title, this.TitleFontSize);
+ plt.YLabel(yLabel, this.FontSize);
+ plt.XLabel(xLabel, this.FontSize);
+
+ var palette = new ScottPlot.Palettes.Category10();
+
+ var legendPalette = data.Select(d => d.JobId)
+ .Distinct()
+ .Select((jobId, index) => (jobId, index))
+ .ToDictionary(t => t.jobId, t => palette.GetColor(t.index));
+
+ plt.Legend.IsVisible = true;
+ plt.Legend.Alignment = Alignment.UpperRight;
+ plt.Legend.FontSize = this.FontSize;
+ var legend = data.Select(d => d.JobId)
+ .Distinct()
+ .Select((label, index) => new LegendItem()
+ {
+ LabelText = label,
+ FillColor = legendPalette[label]
+ })
+ .ToList();
+
+ plt.Legend.ManualItems.AddRange(legend);
+
+ var jobCount = plt.Legend.ManualItems.Count;
+ var ticks = data
+ .Select((d, index) => new Tick(index, d.Target))
+ .ToArray();
+
+ plt.Axes.Left.TickLabelStyle.FontSize = this.FontSize;
+ plt.Axes.Bottom.TickGenerator = new ScottPlot.TickGenerators.NumericManual(ticks);
+ plt.Axes.Bottom.MajorTickStyle.Length = 0;
+ plt.Axes.Bottom.TickLabelStyle.FontSize = this.FontSize;
+
+ if (this.RotateLabels)
+ {
+ plt.Axes.Bottom.TickLabelStyle.Rotation = 45;
+ plt.Axes.Bottom.TickLabelStyle.Alignment = Alignment.MiddleLeft;
+
+ // determine the width of the largest tick label
+ float largestLabelWidth = 0;
+ foreach (Tick tick in ticks)
+ {
+ PixelSize size = plt.Axes.Bottom.TickLabelStyle.Measure(tick.Label).Size;
+ largestLabelWidth = Math.Max(largestLabelWidth, size.Width);
+ }
+
+ // ensure axis panels do not get smaller than the largest label
+ plt.Axes.Bottom.MinimumSize = largestLabelWidth * 2;
+ plt.Axes.Right.MinimumSize = largestLabelWidth;
+ }
+
+ var bars = data
+ .Select((d, index) => new Bar()
+ {
+ Position = ticks[index].Position,
+ Value = d.Mean,
+ Error = d.StdError,
+ FillColor = legendPalette[d.JobId]
+ });
+ plt.Add.Bars(bars.ToList());
+
+ // Tell the plot to autoscale with no padding beneath the bars
+ plt.Axes.Margins(bottom: 0, right: .2);
+
+ plt.PlottableList.AddRange(annotations);
+
+ plt.SavePng(fileName, this.Width, this.Height);
+ return Path.GetFullPath(fileName);
+ }
+
+ private string CreateBoxPlot(string title, string fileName, string yLabel, string xLabel, IEnumerable data, IReadOnlyList annotations)
+ {
+ Plot plt = new Plot();
+ plt.Title(title, this.TitleFontSize);
+ plt.YLabel(yLabel, this.FontSize);
+ plt.XLabel(xLabel, this.FontSize);
+
+ var palette = new ScottPlot.Palettes.Category10();
+
+ var legendPalette = data.Select(d => d.JobId)
+ .Distinct()
+ .Select((jobId, index) => (jobId, index))
+ .ToDictionary(t => t.jobId, t => palette.GetColor(t.index));
+
+ plt.Legend.IsVisible = true;
+ plt.Legend.Alignment = Alignment.UpperRight;
+ plt.Legend.FontSize = this.FontSize;
+ var legend = data.Select(d => d.JobId)
+ .Distinct()
+ .Select((label, index) => new LegendItem()
+ {
+ LabelText = label,
+ FillColor = legendPalette[label]
+ })
+ .ToList();
+
+ plt.Legend.ManualItems.AddRange(legend);
+
+ var jobCount = plt.Legend.ManualItems.Count;
+ var ticks = data
+ .Select((d, index) => new Tick(index, d.Target))
+ .ToArray();
+
+ plt.Axes.Left.TickLabelStyle.FontSize = this.FontSize;
+ plt.Axes.Bottom.TickGenerator = new ScottPlot.TickGenerators.NumericManual(ticks);
+ plt.Axes.Bottom.MajorTickStyle.Length = 0;
+ plt.Axes.Bottom.TickLabelStyle.FontSize = this.FontSize;
+
+ if (this.RotateLabels)
+ {
+ plt.Axes.Bottom.TickLabelStyle.Rotation = 45;
+ plt.Axes.Bottom.TickLabelStyle.Alignment = Alignment.MiddleLeft;
+
+ // determine the width of the largest tick label
+ float largestLabelWidth = 0;
+ foreach (Tick tick in ticks)
+ {
+ PixelSize size = plt.Axes.Bottom.TickLabelStyle.Measure(tick.Label).Size;
+ largestLabelWidth = Math.Max(largestLabelWidth, size.Width);
+ }
+
+ // ensure axis panels do not get smaller than the largest label
+ plt.Axes.Bottom.MinimumSize = largestLabelWidth * 2;
+ plt.Axes.Right.MinimumSize = largestLabelWidth;
+ }
+
+ int globalIndex = 0;
+ foreach (var (targetGroup, targetGroupIndex) in data.GroupBy(s => s.Target).Select((targetGroup, index) => (targetGroup, index)))
+ {
+ var boxes = targetGroup.Select(job => (job.JobId, Stats: job.CalculateBoxPlotStatistics())).Select((j, jobIndex) => new Box()
+ {
+ Position = ticks[globalIndex++].Position,
+ FillStyle = new FillStyle() { Color = legendPalette[j.JobId] },
+ LineStyle = new LineStyle() { Color = Colors.Black },
+ BoxMin = j.Stats.Q1,
+ BoxMax = j.Stats.Q3,
+ WhiskerMin = j.Stats.Min,
+ WhiskerMax = j.Stats.Max,
+ BoxMiddle = j.Stats.Median
+ })
+ .ToList();
+ plt.Add.Boxes(boxes);
+ }
+
+ // Tell the plot to autoscale with a small padding below the boxes.
+ plt.Axes.Margins(bottom: 0.05, right: .2);
+
+ plt.PlottableList.AddRange(annotations);
+
+ plt.SavePng(fileName, this.Width, this.Height);
+ return Path.GetFullPath(fileName);
+ }
+
+ ///
+ /// Provides a list of annotations to put over the data area.
+ ///
+ /// The version to be displayed.
+ /// A list of annotations for every plot.
+ private IReadOnlyList GetAnnotations(string version)
+ {
+ var versionAnnotation = new Annotation()
+ {
+ LabelStyle =
+ {
+ Text = version,
+ FontSize = 14,
+ ForeColor = new Color(0, 0, 0, 100)
+ },
+ OffsetY = 10,
+ OffsetX = 20,
+ Alignment = Alignment.LowerRight
+ };
+
+
+ return new[] { versionAnnotation };
+ }
+
+ private class ChartStats
+ {
+ public ChartStats(string Target, string JobId, IReadOnlyList Values)
+ {
+ this.Target = Target;
+ this.JobId = JobId;
+ this.Values = Values;
+ }
+
+ public string Target { get; }
+
+ public string JobId { get; }
+
+ public IReadOnlyList Values { get; }
+
+ public double Min => this.Values.DefaultIfEmpty(0d).Min();
+
+ public double Max => this.Values.DefaultIfEmpty(0d).Max();
+
+ public double Mean => this.Values.DefaultIfEmpty(0d).Average();
+
+ public double StdError => StandardError(this.Values);
+
+
+ private static (int MidPoint, double Median) CalculateMedian(ReadOnlySpan values)
+ {
+ int n = values.Length;
+ var midPoint = n / 2;
+
+ // Check if count is even, if so use average of the two middle values,
+ // otherwise take the middle value.
+ var median = n % 2 == 0 ? (values[midPoint - 1] + values[midPoint]) / 2d : values[midPoint];
+ return (midPoint, median);
+ }
+
+ ///
+ /// Calculate the mid points.
+ ///
+ ///
+ public (double Min, double Q1, double Median, double Q3, double Max, double[] Outliers) CalculateBoxPlotStatistics()
+ {
+ var values = this.Values.ToArray();
+ Array.Sort(values);
+ var s = values.AsSpan();
+ var (midPoint, median) = CalculateMedian(s);
+
+ var (q1Index, q1) = midPoint > 0 ? CalculateMedian(s.Slice(0, midPoint)) : (midPoint, median);
+ var (q3Index, q3) = midPoint + 1 < s.Length ? CalculateMedian(s.Slice(midPoint + 1)) : (midPoint, median);
+ var iqr = q3 - q1;
+ var lowerFence = q1 - 1.5d * iqr;
+ var upperFence = q3 + 1.5d * iqr;
+ var outliers = values.Where(v => v < lowerFence || v > upperFence).ToArray();
+ var nonOutliers = values.Where(v => v >= lowerFence && v <= upperFence).ToArray();
+ return (
+ nonOutliers.FirstOrDefault(),
+ q1,
+ median,
+ q3,
+ nonOutliers.LastOrDefault(),
+ outliers
+ );
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet.TestAdapter/BenchmarkDotNet.TestAdapter.csproj b/src/BenchmarkDotNet.TestAdapter/BenchmarkDotNet.TestAdapter.csproj
index bd6c053bab..5def448d9c 100644
--- a/src/BenchmarkDotNet.TestAdapter/BenchmarkDotNet.TestAdapter.csproj
+++ b/src/BenchmarkDotNet.TestAdapter/BenchmarkDotNet.TestAdapter.csproj
@@ -9,10 +9,12 @@
enable
+
+
-
-
-
+
+
+
diff --git a/src/BenchmarkDotNet.TestAdapter/BenchmarkEnumerator.cs b/src/BenchmarkDotNet.TestAdapter/BenchmarkEnumerator.cs
index daf3e2222e..d49c3d24e1 100644
--- a/src/BenchmarkDotNet.TestAdapter/BenchmarkEnumerator.cs
+++ b/src/BenchmarkDotNet.TestAdapter/BenchmarkEnumerator.cs
@@ -3,7 +3,7 @@
using BenchmarkDotNet.Running;
using BenchmarkDotNet.Toolchains;
using System;
-using System.Collections.Generic;
+using System.IO;
using System.Linq;
using System.Reflection;
@@ -21,6 +21,28 @@ internal static class BenchmarkEnumerator
/// The benchmarks inside the assembly.
public static BenchmarkRunInfo[] GetBenchmarksFromAssemblyPath(string assemblyPath)
{
+#if NET462
+ // Temporary workaround for BenchmarkDotNet assembly loading issue that occurred under the following conditions:
+ // 1. Run BenchmarkDotNet.Samples project with following command.
+ // > dotnet test -c Release --list-tests --framework net462 -tl:off
+ // 2. When using `BenchmarkDotNet.TestAdapter` package and targeting .NET Framework.
+ AppDomain.CurrentDomain.AssemblyResolve += (sender, eventArgs) =>
+ {
+ if (eventArgs.Name.StartsWith("BenchmarkDotNet, Version="))
+ {
+ var baseDir = Path.GetDirectoryName(assemblyPath);
+ var path = Path.Combine(baseDir, "BenchmarkDotNet.dll");
+ if (File.Exists(path))
+ {
+ return Assembly.LoadFrom(path);
+ }
+ }
+
+ // Fallback to default assembly resolver
+ return null;
+ };
+#endif
+
var assembly = Assembly.LoadFrom(assemblyPath);
var isDebugAssembly = assembly.IsJitOptimizationDisabled() ?? false;
diff --git a/src/BenchmarkDotNet.TestAdapter/BenchmarkExecutor.cs b/src/BenchmarkDotNet.TestAdapter/BenchmarkExecutor.cs
index f1cd64f34e..aad574ed76 100644
--- a/src/BenchmarkDotNet.TestAdapter/BenchmarkExecutor.cs
+++ b/src/BenchmarkDotNet.TestAdapter/BenchmarkExecutor.cs
@@ -1,4 +1,5 @@
using BenchmarkDotNet.Configs;
+using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Running;
using BenchmarkDotNet.TestAdapter.Remoting;
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
@@ -14,7 +15,7 @@ namespace BenchmarkDotNet.TestAdapter
///
internal class BenchmarkExecutor
{
- private readonly CancellationTokenSource cts = new ();
+ private readonly CancellationTokenSource cts = new();
///
/// Runs all the benchmarks in the given assembly, updating the TestExecutionRecorder as they get run.
@@ -67,7 +68,10 @@ public void RunBenchmarks(string assemblyPath, TestExecutionRecorderWrapper reco
.Select(b => new BenchmarkRunInfo(
b.BenchmarksCases,
b.Type,
- b.Config.AddEventProcessor(eventProcessor).AddLogger(logger).CreateImmutableConfig()))
+ b.Config.AddEventProcessor(eventProcessor)
+ .AddLogger(logger)
+ .RemoveLoggersOfType() // Console logs are also outputted by VSTestLogger.
+ .CreateImmutableConfig()))
.ToArray();
// Run all the benchmarks, and ensure that any tests that don't have a result yet are sent.
diff --git a/src/BenchmarkDotNet.TestAdapter/Remoting/BenchmarkExecutorWrapper.cs b/src/BenchmarkDotNet.TestAdapter/Remoting/BenchmarkExecutorWrapper.cs
index 646ae2f8be..28444ae8db 100644
--- a/src/BenchmarkDotNet.TestAdapter/Remoting/BenchmarkExecutorWrapper.cs
+++ b/src/BenchmarkDotNet.TestAdapter/Remoting/BenchmarkExecutorWrapper.cs
@@ -8,7 +8,7 @@ namespace BenchmarkDotNet.TestAdapter.Remoting
///
internal class BenchmarkExecutorWrapper : MarshalByRefObject
{
- private readonly BenchmarkExecutor benchmarkExecutor = new ();
+ private readonly BenchmarkExecutor benchmarkExecutor = new();
public void RunBenchmarks(string assemblyPath, TestExecutionRecorderWrapper recorder, HashSet? benchmarkIds = null)
{
diff --git a/src/BenchmarkDotNet.TestAdapter/Utility/LoggerHelper.cs b/src/BenchmarkDotNet.TestAdapter/Utility/LoggerHelper.cs
new file mode 100644
index 0000000000..08238c1927
--- /dev/null
+++ b/src/BenchmarkDotNet.TestAdapter/Utility/LoggerHelper.cs
@@ -0,0 +1,57 @@
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;
+using System.Diagnostics;
+using System.IO;
+
+namespace BenchmarkDotNet.TestAdapter;
+
+internal class LoggerHelper
+{
+ public LoggerHelper(IMessageLogger logger, Stopwatch stopwatch)
+ {
+ InnerLogger = logger;
+ Stopwatch = stopwatch;
+ }
+
+ public IMessageLogger InnerLogger { get; private set; }
+
+ public Stopwatch Stopwatch { get; private set; }
+
+ public void Log(string format, params object[] args)
+ {
+ SendMessage(TestMessageLevel.Informational, null, string.Format(format, args));
+ }
+
+ public void LogWithSource(string source, string format, params object[] args)
+ {
+ SendMessage(TestMessageLevel.Informational, source, string.Format(format, args));
+ }
+
+ public void LogError(string format, params object[] args)
+ {
+ SendMessage(TestMessageLevel.Error, null, string.Format(format, args));
+ }
+
+ public void LogErrorWithSource(string source, string format, params object[] args)
+ {
+ SendMessage(TestMessageLevel.Error, source, string.Format(format, args));
+ }
+
+ public void LogWarning(string format, params object[] args)
+ {
+ SendMessage(TestMessageLevel.Warning, null, string.Format(format, args));
+ }
+
+ public void LogWarningWithSource(string source, string format, params object[] args)
+ {
+ SendMessage(TestMessageLevel.Warning, source, string.Format(format, args));
+ }
+
+ private void SendMessage(TestMessageLevel level, string? assemblyName, string message)
+ {
+ var assemblyText = assemblyName == null
+ ? "" :
+ $"{Path.GetFileNameWithoutExtension(assemblyName)}: ";
+
+ InnerLogger.SendMessage(level, $"[BenchmarkDotNet {Stopwatch.Elapsed:hh\\:mm\\:ss\\.ff}] {assemblyText}{message}");
+ }
+}
diff --git a/src/BenchmarkDotNet.TestAdapter/Utility/TestCaseFilter.cs b/src/BenchmarkDotNet.TestAdapter/Utility/TestCaseFilter.cs
new file mode 100644
index 0000000000..ffd891bf61
--- /dev/null
+++ b/src/BenchmarkDotNet.TestAdapter/Utility/TestCaseFilter.cs
@@ -0,0 +1,166 @@
+using Microsoft.VisualStudio.TestPlatform.ObjectModel;
+using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+
+namespace BenchmarkDotNet.TestAdapter;
+
+internal class TestCaseFilter
+{
+ private const string DisplayNameString = "DisplayName";
+ private const string FullyQualifiedNameString = "FullyQualifiedName";
+
+ private readonly HashSet knownTraits;
+ private List supportedPropertyNames;
+ private readonly ITestCaseFilterExpression? filterExpression;
+ private readonly bool successfullyGotFilter;
+ private readonly bool isDiscovery;
+
+ public TestCaseFilter(IDiscoveryContext discoveryContext, LoggerHelper logger)
+ {
+ // Traits are not known at discovery time because we load them from benchmarks
+ isDiscovery = true;
+ knownTraits = [];
+ supportedPropertyNames = GetSupportedPropertyNames();
+ successfullyGotFilter = GetTestCaseFilterExpressionFromDiscoveryContext(discoveryContext, logger, out filterExpression);
+ }
+
+ public TestCaseFilter(IRunContext runContext, LoggerHelper logger, string assemblyFileName, HashSet knownTraits)
+ {
+ this.knownTraits = knownTraits;
+ supportedPropertyNames = GetSupportedPropertyNames();
+ successfullyGotFilter = GetTestCaseFilterExpression(runContext, logger, assemblyFileName, out filterExpression);
+ }
+
+ public string GetTestCaseFilterValue()
+ {
+ return successfullyGotFilter
+ ? filterExpression?.TestCaseFilterValue ?? ""
+ : "";
+ }
+
+ public bool MatchTestCase(TestCase testCase)
+ {
+ if (!successfullyGotFilter)
+ {
+ // Had an error while getting filter, match no testcase to ensure discovered test list is empty
+ return false;
+ }
+ else if (filterExpression == null)
+ {
+ // No filter specified, keep every testcase
+ return true;
+ }
+
+ return filterExpression.MatchTestCase(testCase, p => PropertyProvider(testCase, p));
+ }
+
+ public object? PropertyProvider(TestCase testCase, string name)
+ {
+ // Traits filtering
+ if (isDiscovery || knownTraits.Contains(name))
+ {
+ var result = new List();
+
+ foreach (var trait in GetTraits(testCase))
+ if (string.Equals(trait.Key, name, StringComparison.OrdinalIgnoreCase))
+ result.Add(trait.Value);
+
+ if (result.Count > 0)
+ return result.ToArray();
+ }
+
+ // Property filtering
+ switch (name.ToLowerInvariant())
+ {
+ // FullyQualifiedName
+ case "fullyqualifiedname":
+ return testCase.FullyQualifiedName;
+ // DisplayName
+ case "displayname":
+ return testCase.DisplayName;
+ default:
+ return null;
+ }
+ }
+
+ private bool GetTestCaseFilterExpression(IRunContext runContext, LoggerHelper logger, string assemblyFileName, out ITestCaseFilterExpression? filter)
+ {
+ filter = null;
+
+ try
+ {
+ filter = runContext.GetTestCaseFilter(supportedPropertyNames, null!);
+ return true;
+ }
+ catch (TestPlatformFormatException e)
+ {
+ logger.LogWarning("{0}: Exception filtering tests: {1}", Path.GetFileNameWithoutExtension(assemblyFileName), e.Message);
+ return false;
+ }
+ }
+
+ private bool GetTestCaseFilterExpressionFromDiscoveryContext(IDiscoveryContext discoveryContext, LoggerHelper logger, out ITestCaseFilterExpression? filter)
+ {
+ filter = null;
+
+ if (discoveryContext is IRunContext runContext)
+ {
+ try
+ {
+ filter = runContext.GetTestCaseFilter(supportedPropertyNames, null!);
+ return true;
+ }
+ catch (TestPlatformException e)
+ {
+ logger.LogWarning("Exception filtering tests: {0}", e.Message);
+ return false;
+ }
+ }
+ else
+ {
+ try
+ {
+ // GetTestCaseFilter is present on DiscoveryContext but not in IDiscoveryContext interface
+ var method = discoveryContext.GetType().GetRuntimeMethod("GetTestCaseFilter", [typeof(IEnumerable), typeof(Func)]);
+ filter = (ITestCaseFilterExpression)method?.Invoke(discoveryContext, [supportedPropertyNames, null])!;
+
+ return true;
+ }
+ catch (TargetInvocationException e)
+ {
+ if (e?.InnerException is TestPlatformException ex)
+ {
+ logger.LogWarning("Exception filtering tests: {0}", ex.InnerException.Message ?? "");
+ return false;
+ }
+
+ throw e!.InnerException;
+ }
+ }
+ }
+
+ private List GetSupportedPropertyNames()
+ {
+ // Returns the set of well-known property names usually used with the Test Plugins (Used Test Traits + DisplayName + FullyQualifiedName)
+ if (supportedPropertyNames == null)
+ {
+ supportedPropertyNames = knownTraits.ToList();
+ supportedPropertyNames.Add(DisplayNameString);
+ supportedPropertyNames.Add(FullyQualifiedNameString);
+ }
+
+ return supportedPropertyNames;
+ }
+
+ private static IEnumerable> GetTraits(TestCase testCase)
+ {
+ var traitProperty = TestProperty.Find("TestObject.Traits");
+ return traitProperty != null
+ ? testCase.GetPropertyValue(traitProperty, Array.Empty>())
+ : [];
+ }
+}
diff --git a/src/BenchmarkDotNet.TestAdapter/VSTestAdapter.cs b/src/BenchmarkDotNet.TestAdapter/VSTestAdapter.cs
index eb6695de1b..3a3e155bf4 100644
--- a/src/BenchmarkDotNet.TestAdapter/VSTestAdapter.cs
+++ b/src/BenchmarkDotNet.TestAdapter/VSTestAdapter.cs
@@ -4,6 +4,7 @@
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
@@ -42,11 +43,18 @@ public void DiscoverTests(
IMessageLogger logger,
ITestCaseDiscoverySink discoverySink)
{
+ var stopwatch = Stopwatch.StartNew();
+ var loggerHelper = new LoggerHelper(logger, stopwatch);
+ var testCaseFilter = new TestCaseFilter(discoveryContext, loggerHelper);
+
foreach (var source in sources)
{
ValidateSourceIsAssemblyOrThrow(source);
foreach (var testCase in GetVsTestCasesFromAssembly(source, logger))
{
+ if (!testCaseFilter.MatchTestCase(testCase))
+ continue;
+
discoverySink.SendTestCase(testCase);
}
}
@@ -67,14 +75,19 @@ public void RunTests(IEnumerable? tests, IRunContext? runContext, IFra
cts ??= new CancellationTokenSource();
+ var stopwatch = Stopwatch.StartNew();
+ var logger = new LoggerHelper(frameworkHandle, stopwatch);
+
foreach (var testsPerAssembly in tests.GroupBy(t => t.Source))
+ {
RunBenchmarks(testsPerAssembly.Key, frameworkHandle, testsPerAssembly);
+ }
cts = null;
}
///
- /// Runs all benchmarks in the given set of sources (assemblies).
+ /// Runs all/filtered benchmarks in the given set of sources (assemblies).
///
/// The assemblies to run.
/// A context that the run is performed in.
@@ -88,8 +101,31 @@ public void RunTests(IEnumerable? sources, IRunContext? runContext, IFra
cts ??= new CancellationTokenSource();
+ var stopwatch = Stopwatch.StartNew();
+ var logger = new LoggerHelper(frameworkHandle, stopwatch);
+
foreach (var source in sources)
- RunBenchmarks(source, frameworkHandle);
+ {
+ var filter = new TestCaseFilter(runContext!, logger, source, ["Category"]);
+ if (filter.GetTestCaseFilterValue() != "")
+ {
+ var discoveredBenchmarks = GetVsTestCasesFromAssembly(source, frameworkHandle);
+ var filteredTestCases = discoveredBenchmarks.Where(x => filter.MatchTestCase(x))
+ .ToArray();
+
+ if (filteredTestCases.Length == 0)
+ continue;
+
+ // Run filtered tests.
+ RunBenchmarks(source, frameworkHandle, filteredTestCases);
+ }
+ else
+ {
+ // Run all benchmarks
+ RunBenchmarks(source, frameworkHandle);
+ }
+ }
+
cts = null;
}
diff --git a/src/BenchmarkDotNet.TestAdapter/VSTestEventProcessor.cs b/src/BenchmarkDotNet.TestAdapter/VSTestEventProcessor.cs
index 35438d4d80..f002e7eb29 100644
--- a/src/BenchmarkDotNet.TestAdapter/VSTestEventProcessor.cs
+++ b/src/BenchmarkDotNet.TestAdapter/VSTestEventProcessor.cs
@@ -25,9 +25,9 @@ internal class VsTestEventProcessor : EventProcessor
private readonly Dictionary cases;
private readonly TestExecutionRecorderWrapper recorder;
private readonly CancellationToken cancellationToken;
- private readonly Stopwatch runTimerStopwatch = new ();
- private readonly Dictionary testResults = new ();
- private readonly HashSet sentTestResults = new ();
+ private readonly Stopwatch runTimerStopwatch = new();
+ private readonly Dictionary testResults = new();
+ private readonly HashSet sentTestResults = new();
public VsTestEventProcessor(
List cases,
diff --git a/src/BenchmarkDotNet.TestAdapter/build/BenchmarkDotNet.TestAdapter.props b/src/BenchmarkDotNet.TestAdapter/build/BenchmarkDotNet.TestAdapter.props
index a9e8340b30..b672c62139 100644
--- a/src/BenchmarkDotNet.TestAdapter/build/BenchmarkDotNet.TestAdapter.props
+++ b/src/BenchmarkDotNet.TestAdapter/build/BenchmarkDotNet.TestAdapter.props
@@ -1,6 +1,8 @@
$(MSBuildThisFileDirectory)..\entrypoints\
+
+ false
+
false
-
\ No newline at end of file
+
diff --git a/src/BenchmarkDotNet/Analysers/BaselineCustomAnalyzer.cs b/src/BenchmarkDotNet/Analysers/BaselineCustomAnalyzer.cs
index 300d520f15..9f2ff0028c 100644
--- a/src/BenchmarkDotNet/Analysers/BaselineCustomAnalyzer.cs
+++ b/src/BenchmarkDotNet/Analysers/BaselineCustomAnalyzer.cs
@@ -31,7 +31,8 @@ protected override IEnumerable AnalyseSummary(Summary summary)
continue;
var message = "A question mark '?' symbol indicates that it was not possible to compute the " +
- $"({columnNames}) column(s) because the baseline value is too close to zero.";
+ $"({columnNames}) column(s) because the baseline or benchmark could not be found, or " +
+ $"the baseline value is too close to zero.";
yield return Conclusion.CreateWarning(Id, message);
}
diff --git a/src/BenchmarkDotNet/Analysers/ZeroMeasurementAnalyser.cs b/src/BenchmarkDotNet/Analysers/ZeroMeasurementAnalyser.cs
index 9269b3ebf0..4d83a5d154 100644
--- a/src/BenchmarkDotNet/Analysers/ZeroMeasurementAnalyser.cs
+++ b/src/BenchmarkDotNet/Analysers/ZeroMeasurementAnalyser.cs
@@ -19,7 +19,7 @@ private ZeroMeasurementAnalyser() { }
protected override IEnumerable AnalyseReport(BenchmarkReport report, Summary summary)
{
- var currentFrequency = summary.HostEnvironmentInfo.CpuInfo.Value.MaxFrequency;
+ var currentFrequency = summary.HostEnvironmentInfo.Cpu.Value.MaxFrequency();
if (!currentFrequency.HasValue || currentFrequency <= 0)
currentFrequency = FallbackCpuResolutionValue.ToFrequency();
diff --git a/src/BenchmarkDotNet/Attributes/ExceptionDiagnoserAttribute.cs b/src/BenchmarkDotNet/Attributes/ExceptionDiagnoserAttribute.cs
index bff956e968..21f2903124 100644
--- a/src/BenchmarkDotNet/Attributes/ExceptionDiagnoserAttribute.cs
+++ b/src/BenchmarkDotNet/Attributes/ExceptionDiagnoserAttribute.cs
@@ -9,6 +9,10 @@ public class ExceptionDiagnoserAttribute : Attribute, IConfigSource
{
public IConfig Config { get; }
- public ExceptionDiagnoserAttribute() => Config = ManualConfig.CreateEmpty().AddDiagnoser(ExceptionDiagnoser.Default);
+ /// Display Exceptions column. True by default.
+ public ExceptionDiagnoserAttribute(bool displayExceptionsIfZeroValue = true)
+ {
+ Config = ManualConfig.CreateEmpty().AddDiagnoser(new ExceptionDiagnoser(new ExceptionDiagnoserConfig(displayExceptionsIfZeroValue)));
+ }
}
}
diff --git a/src/BenchmarkDotNet/Attributes/ExceptionDiagnoserConfig.cs b/src/BenchmarkDotNet/Attributes/ExceptionDiagnoserConfig.cs
new file mode 100644
index 0000000000..86f8d99be4
--- /dev/null
+++ b/src/BenchmarkDotNet/Attributes/ExceptionDiagnoserConfig.cs
@@ -0,0 +1,20 @@
+using JetBrains.Annotations;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace BenchmarkDotNet.Attributes
+{
+ public class ExceptionDiagnoserConfig
+ {
+ /// Determines whether the Exceptions column is displayed when its value is not calculated. True by default.
+
+ [PublicAPI]
+ public ExceptionDiagnoserConfig(bool displayExceptionsIfZeroValue = true)
+ {
+ DisplayExceptionsIfZeroValue = displayExceptionsIfZeroValue;
+ }
+
+ public bool DisplayExceptionsIfZeroValue { get; }
+ }
+}
diff --git a/src/BenchmarkDotNet/Attributes/Exporters/PerfonarExporterAttribute.cs b/src/BenchmarkDotNet/Attributes/Exporters/PerfonarExporterAttribute.cs
new file mode 100644
index 0000000000..e7547f3e7a
--- /dev/null
+++ b/src/BenchmarkDotNet/Attributes/Exporters/PerfonarExporterAttribute.cs
@@ -0,0 +1,10 @@
+using System;
+using BenchmarkDotNet.Exporters;
+
+namespace BenchmarkDotNet.Attributes;
+
+///
+/// IMPORTANT: Not fully implemented yet
+///
+[AttributeUsage(AttributeTargets.Class | AttributeTargets.Assembly, AllowMultiple = true)]
+internal class PerfonarExporterAttribute() : ExporterConfigBaseAttribute(new PerfonarJsonExporter(), new PerfonarMdExporter());
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Attributes/OrdererAttribute.cs b/src/BenchmarkDotNet/Attributes/OrdererAttribute.cs
index 83b5051895..b20a9108ca 100644
--- a/src/BenchmarkDotNet/Attributes/OrdererAttribute.cs
+++ b/src/BenchmarkDotNet/Attributes/OrdererAttribute.cs
@@ -9,9 +9,10 @@ public class OrdererAttribute : Attribute, IConfigSource
{
public OrdererAttribute(
SummaryOrderPolicy summaryOrderPolicy = SummaryOrderPolicy.Default,
- MethodOrderPolicy methodOrderPolicy = MethodOrderPolicy.Declared)
+ MethodOrderPolicy methodOrderPolicy = MethodOrderPolicy.Declared,
+ JobOrderPolicy jobOrderPolicy = JobOrderPolicy.Default)
{
- Config = ManualConfig.CreateEmpty().WithOrderer(new DefaultOrderer(summaryOrderPolicy, methodOrderPolicy));
+ Config = ManualConfig.CreateEmpty().WithOrderer(new DefaultOrderer(summaryOrderPolicy, methodOrderPolicy, jobOrderPolicy));
}
public IConfig Config { get; }
diff --git a/src/BenchmarkDotNet/Attributes/ThreadingDiagnoserAttribute.cs b/src/BenchmarkDotNet/Attributes/ThreadingDiagnoserAttribute.cs
index 7627170b8b..4ad7651bfc 100644
--- a/src/BenchmarkDotNet/Attributes/ThreadingDiagnoserAttribute.cs
+++ b/src/BenchmarkDotNet/Attributes/ThreadingDiagnoserAttribute.cs
@@ -1,6 +1,7 @@
using System;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnosers;
+using JetBrains.Annotations;
namespace BenchmarkDotNet.Attributes
{
@@ -9,6 +10,15 @@ public class ThreadingDiagnoserAttribute : Attribute, IConfigSource
{
public IConfig Config { get; }
- public ThreadingDiagnoserAttribute() => Config = ManualConfig.CreateEmpty().AddDiagnoser(ThreadingDiagnoser.Default);
+ //public ThreadingDiagnoserAttribute() => Config = ManualConfig.CreateEmpty().AddDiagnoser(ThreadingDiagnoser.Default);
+
+ /// Display configuration for 'LockContentionCount' when it is empty. True (displayed) by default.
+ /// Display configuration for 'CompletedWorkItemCount' when it is empty. True (displayed) by default.
+
+ [PublicAPI]
+ public ThreadingDiagnoserAttribute(bool displayLockContentionWhenZero = true, bool displayCompletedWorkItemCountWhenZero = true)
+ {
+ Config = ManualConfig.CreateEmpty().AddDiagnoser(new ThreadingDiagnoser(new ThreadingDiagnoserConfig(displayLockContentionWhenZero, displayCompletedWorkItemCountWhenZero)));
+ }
}
}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Attributes/WakeLockAttribute.cs b/src/BenchmarkDotNet/Attributes/WakeLockAttribute.cs
new file mode 100644
index 0000000000..1243ac8f8f
--- /dev/null
+++ b/src/BenchmarkDotNet/Attributes/WakeLockAttribute.cs
@@ -0,0 +1,18 @@
+using BenchmarkDotNet.Configs;
+using System;
+
+namespace BenchmarkDotNet.Attributes
+{
+ ///
+ /// Placing a on your assembly or class controls whether the
+ /// Windows system enters sleep or turns off the display while benchmarks run.
+ ///
+ [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class)]
+ public sealed class WakeLockAttribute : Attribute, IConfigSource
+ {
+ public WakeLockAttribute(WakeLockType wakeLockType) =>
+ Config = ManualConfig.CreateEmpty().WithWakeLock(wakeLockType);
+
+ public IConfig Config { get; }
+ }
+}
diff --git a/src/BenchmarkDotNet/BenchmarkDotNet.csproj b/src/BenchmarkDotNet/BenchmarkDotNet.csproj
index 4bdceb0f8a..7953a036ae 100644
--- a/src/BenchmarkDotNet/BenchmarkDotNet.csproj
+++ b/src/BenchmarkDotNet/BenchmarkDotNet.csproj
@@ -13,26 +13,25 @@
-
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
+
diff --git a/src/BenchmarkDotNet/Code/CodeGenerator.cs b/src/BenchmarkDotNet/Code/CodeGenerator.cs
index 005564b77c..9a6228dd88 100644
--- a/src/BenchmarkDotNet/Code/CodeGenerator.cs
+++ b/src/BenchmarkDotNet/Code/CodeGenerator.cs
@@ -56,6 +56,7 @@ internal static string Generate(BuildPartition buildPartition)
.Replace("$OverheadImplementation$", provider.OverheadImplementation)
.Replace("$ConsumeField$", provider.ConsumeField)
.Replace("$JobSetDefinition$", GetJobsSetDefinition(benchmark))
+ .Replace("$ParamsInitializer$", GetParamsInitializer(benchmark))
.Replace("$ParamsContent$", GetParamsContent(benchmark))
.Replace("$ArgumentsDefinition$", GetArgumentsDefinition(benchmark))
.Replace("$DeclareArgumentFields$", GetDeclareArgumentFields(benchmark))
@@ -186,7 +187,15 @@ private static DeclarationsProvider GetDeclarationsProvider(Descriptor descripto
return new NonVoidDeclarationsProvider(descriptor);
}
+ private static string GetParamsInitializer(BenchmarkCase benchmarkCase)
+ => string.Join(
+ ", ",
+ benchmarkCase.Parameters.Items
+ .Where(parameter => !parameter.IsArgument && !parameter.IsStatic)
+ .Select(parameter => $"{parameter.Name} = default"));
+
// internal for tests
+
internal static string GetParamsContent(BenchmarkCase benchmarkCase)
=> string.Join(
string.Empty,
diff --git a/src/BenchmarkDotNet/Columns/StatisticalTestColumn.cs b/src/BenchmarkDotNet/Columns/StatisticalTestColumn.cs
index 51edbb40f9..52a95bc04e 100644
--- a/src/BenchmarkDotNet/Columns/StatisticalTestColumn.cs
+++ b/src/BenchmarkDotNet/Columns/StatisticalTestColumn.cs
@@ -15,9 +15,9 @@ public class StatisticalTestColumn(Threshold threshold, SignificanceLevel? signi
{
private static readonly SignificanceLevel DefaultSignificanceLevel = SignificanceLevel.P1E5;
- public static StatisticalTestColumn CreateDefault() => new (new PercentValue(10).ToThreshold());
+ public static StatisticalTestColumn CreateDefault() => new(new PercentValue(10).ToThreshold());
- public static StatisticalTestColumn Create(Threshold threshold, SignificanceLevel? significanceLevel = null) => new (threshold, significanceLevel);
+ public static StatisticalTestColumn Create(Threshold threshold, SignificanceLevel? significanceLevel = null) => new(threshold, significanceLevel);
public static StatisticalTestColumn Create(string threshold, SignificanceLevel? significanceLevel = null)
{
diff --git a/src/BenchmarkDotNet/Configs/ConfigOptions.cs b/src/BenchmarkDotNet/Configs/ConfigOptions.cs
index c1ad75d642..06bc7768c7 100644
--- a/src/BenchmarkDotNet/Configs/ConfigOptions.cs
+++ b/src/BenchmarkDotNet/Configs/ConfigOptions.cs
@@ -48,7 +48,11 @@ public enum ConfigOptions
///
/// Continue the execution if the last run was stopped.
///
- Resume = 1 << 9
+ Resume = 1 << 9,
+ ///
+ /// Determines whether parallel build of benchmark projects should be disabled.
+ ///
+ DisableParallelBuild = 1 << 10,
}
internal static class ConfigOptionsExtensions
diff --git a/src/BenchmarkDotNet/Configs/DebugConfig.cs b/src/BenchmarkDotNet/Configs/DebugConfig.cs
index 0fdda7b08d..551c38fa09 100644
--- a/src/BenchmarkDotNet/Configs/DebugConfig.cs
+++ b/src/BenchmarkDotNet/Configs/DebugConfig.cs
@@ -71,6 +71,7 @@ public abstract class DebugConfig : IConfig
public SummaryStyle SummaryStyle => SummaryStyle.Default;
public ConfigUnionRule UnionRule => ConfigUnionRule.Union;
public TimeSpan BuildTimeout => DefaultConfig.Instance.BuildTimeout;
+ public WakeLockType WakeLock => WakeLockType.None;
public string ArtifactsPath => null; // DefaultConfig.ArtifactsPath will be used if the user does not specify it in explicit way
diff --git a/src/BenchmarkDotNet/Configs/DefaultConfig.cs b/src/BenchmarkDotNet/Configs/DefaultConfig.cs
index 2323d4fdc4..d188b67415 100644
--- a/src/BenchmarkDotNet/Configs/DefaultConfig.cs
+++ b/src/BenchmarkDotNet/Configs/DefaultConfig.cs
@@ -4,6 +4,7 @@
using System.IO;
using BenchmarkDotNet.Analysers;
using BenchmarkDotNet.Columns;
+using BenchmarkDotNet.Detectors;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.EventProcessors;
using BenchmarkDotNet.Exporters;
@@ -71,6 +72,7 @@ public IEnumerable GetValidators()
yield return DeferredExecutionValidator.FailOnError;
yield return ParamsAllValuesValidator.FailOnError;
yield return ParamsValidator.FailOnError;
+ yield return RuntimeValidator.DontFailOnError;
}
public IOrderer Orderer => null;
@@ -86,11 +88,13 @@ public IEnumerable GetValidators()
public TimeSpan BuildTimeout => TimeSpan.FromSeconds(120);
+ public WakeLockType WakeLock => WakeLockType.System;
+
public string ArtifactsPath
{
get
{
- var root = RuntimeInformation.IsAndroid() ?
+ var root = OsDetector.IsAndroid() ?
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) :
Directory.GetCurrentDirectory();
return Path.Combine(root, "BenchmarkDotNet.Artifacts");
diff --git a/src/BenchmarkDotNet/Configs/IConfig.cs b/src/BenchmarkDotNet/Configs/IConfig.cs
index b311c235f5..9e3bf50ce6 100644
--- a/src/BenchmarkDotNet/Configs/IConfig.cs
+++ b/src/BenchmarkDotNet/Configs/IConfig.cs
@@ -55,6 +55,8 @@ public interface IConfig
///
TimeSpan BuildTimeout { get; }
+ public WakeLockType WakeLock { get; }
+
///
/// Collect any errors or warnings when composing the configuration
///
diff --git a/src/BenchmarkDotNet/Configs/ImmutableConfig.cs b/src/BenchmarkDotNet/Configs/ImmutableConfig.cs
index b6e03126fd..0043ecbe4b 100644
--- a/src/BenchmarkDotNet/Configs/ImmutableConfig.cs
+++ b/src/BenchmarkDotNet/Configs/ImmutableConfig.cs
@@ -56,6 +56,7 @@ internal ImmutableConfig(
SummaryStyle summaryStyle,
ConfigOptions options,
TimeSpan buildTimeout,
+ WakeLockType wakeLock,
IReadOnlyList configAnalysisConclusion)
{
columnProviders = uniqueColumnProviders;
@@ -78,6 +79,7 @@ internal ImmutableConfig(
SummaryStyle = summaryStyle;
Options = options;
BuildTimeout = buildTimeout;
+ WakeLock = wakeLock;
ConfigAnalysisConclusion = configAnalysisConclusion;
}
@@ -89,6 +91,7 @@ internal ImmutableConfig(
public ICategoryDiscoverer CategoryDiscoverer { get; }
public SummaryStyle SummaryStyle { get; }
public TimeSpan BuildTimeout { get; }
+ public WakeLockType WakeLock { get; }
public IEnumerable GetColumnProviders() => columnProviders;
public IEnumerable GetExporters() => exporters;
diff --git a/src/BenchmarkDotNet/Configs/ImmutableConfigBuilder.cs b/src/BenchmarkDotNet/Configs/ImmutableConfigBuilder.cs
index d7c0b5eb0f..f93e5590d0 100644
--- a/src/BenchmarkDotNet/Configs/ImmutableConfigBuilder.cs
+++ b/src/BenchmarkDotNet/Configs/ImmutableConfigBuilder.cs
@@ -76,6 +76,7 @@ public static ImmutableConfig Create(IConfig source)
source.SummaryStyle ?? SummaryStyle.Default,
source.Options,
source.BuildTimeout,
+ source.WakeLock,
configAnalyse.AsReadOnly()
);
}
diff --git a/src/BenchmarkDotNet/Configs/ManualConfig.cs b/src/BenchmarkDotNet/Configs/ManualConfig.cs
index 5ea1be24e9..4ececd28f1 100644
--- a/src/BenchmarkDotNet/Configs/ManualConfig.cs
+++ b/src/BenchmarkDotNet/Configs/ManualConfig.cs
@@ -58,6 +58,7 @@ public class ManualConfig : IConfig
[PublicAPI] public ICategoryDiscoverer CategoryDiscoverer { get; set; }
[PublicAPI] public SummaryStyle SummaryStyle { get; set; }
[PublicAPI] public TimeSpan BuildTimeout { get; set; } = DefaultConfig.Instance.BuildTimeout;
+ [PublicAPI] public WakeLockType WakeLock { get; set; } = DefaultConfig.Instance.WakeLock;
public IReadOnlyList ConfigAnalysisConclusion => emptyConclusion;
@@ -109,6 +110,12 @@ public ManualConfig WithBuildTimeout(TimeSpan buildTimeout)
return this;
}
+ public ManualConfig WithWakeLock(WakeLockType wakeLockType)
+ {
+ WakeLock = wakeLockType;
+ return this;
+ }
+
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("This method will soon be removed, please start using .AddColumn() instead.")]
public void Add(params IColumn[] newColumns) => AddColumn(newColumns);
@@ -273,6 +280,7 @@ public void Add(IConfig config)
columnHidingRules.AddRange(config.GetColumnHidingRules());
Options |= config.Options;
BuildTimeout = GetBuildTimeout(BuildTimeout, config.BuildTimeout);
+ WakeLock = GetWakeLock(WakeLock, config.WakeLock);
}
///
@@ -319,6 +327,12 @@ public static ManualConfig Union(IConfig globalConfig, IConfig localConfig)
return manualConfig;
}
+ internal ManualConfig RemoveLoggersOfType()
+ {
+ loggers.RemoveAll(logger => logger is T);
+ return this;
+ }
+
internal void RemoveAllJobs() => jobs.Clear();
internal void RemoveAllDiagnosers() => diagnosers.Clear();
@@ -327,5 +341,12 @@ private static TimeSpan GetBuildTimeout(TimeSpan current, TimeSpan other)
=> current == DefaultConfig.Instance.BuildTimeout
? other
: TimeSpan.FromMilliseconds(Math.Max(current.TotalMilliseconds, other.TotalMilliseconds));
+
+ private static WakeLockType GetWakeLock(WakeLockType current, WakeLockType other)
+ {
+ if (current == DefaultConfig.Instance.WakeLock) { return other; }
+ if (other == DefaultConfig.Instance.WakeLock) { return current; }
+ return current.CompareTo(other) > 0 ? current : other;
+ }
}
}
diff --git a/src/BenchmarkDotNet/Configs/WakeLockType.cs b/src/BenchmarkDotNet/Configs/WakeLockType.cs
new file mode 100644
index 0000000000..f547e44764
--- /dev/null
+++ b/src/BenchmarkDotNet/Configs/WakeLockType.cs
@@ -0,0 +1,20 @@
+namespace BenchmarkDotNet.Configs
+{
+ public enum WakeLockType
+ {
+ ///
+ /// Allows the system to enter sleep and/or turn off the display while benchmarks are running.
+ ///
+ None,
+
+ ///
+ /// Forces the system to be in the working state while benchmarks are running.
+ ///
+ System,
+
+ ///
+ /// Forces the display to be on while benchmarks are running.
+ ///
+ Display
+ }
+}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/ConsoleArguments/CommandLineOptions.cs b/src/BenchmarkDotNet/ConsoleArguments/CommandLineOptions.cs
index e3aed1fedd..6c7b48b0e2 100644
--- a/src/BenchmarkDotNet/ConsoleArguments/CommandLineOptions.cs
+++ b/src/BenchmarkDotNet/ConsoleArguments/CommandLineOptions.cs
@@ -2,6 +2,7 @@
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
+using BenchmarkDotNet.Configs;
using BenchmarkDotNet.ConsoleArguments.ListBenchmarks;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Engines;
@@ -179,6 +180,9 @@ public bool UseDisassemblyDiagnoser
[Option("buildTimeout", Required = false, HelpText = "Build timeout in seconds.")]
public int? TimeOutInSeconds { get; set; }
+ [Option("wakeLock", Required = false, HelpText = "Prevents the system from entering sleep or turning off the display. None/System/Display.")]
+ public WakeLockType? WakeLock { get; set; }
+
[Option("stopOnFirstError", Required = false, Default = false, HelpText = "Stop on first error.")]
public bool StopOnFirstError { get; set; }
@@ -237,8 +241,8 @@ public static IEnumerable Examples
yield return new Example("Use Job.ShortRun for running the benchmarks", shortName, new CommandLineOptions { BaseJob = "short" });
yield return new Example("Run benchmarks in process", shortName, new CommandLineOptions { RunInProcess = true });
- yield return new Example("Run benchmarks for .NET 4.7.2, .NET Core 2.1 and Mono. .NET 4.7.2 will be baseline because it was first.", longName, new CommandLineOptions { Runtimes = new[] { "net472", "netcoreapp2.1", "Mono" } });
- yield return new Example("Run benchmarks for .NET Core 2.0, .NET Core 2.1 and .NET Core 2.2. .NET Core 2.0 will be baseline because it was first.", longName, new CommandLineOptions { Runtimes = new[] { "netcoreapp2.0", "netcoreapp2.1", "netcoreapp2.2" } });
+ yield return new Example("Run benchmarks for .NET 4.7.2, .NET 8.0 and Mono. .NET 4.7.2 will be baseline because it was first.", longName, new CommandLineOptions { Runtimes = new[] { "net472", "net8.0", "Mono" } });
+ yield return new Example("Run benchmarks for .NET Core 3.1, .NET 6.0 and .NET 8.0. .NET Core 3.1 will be baseline because it was first.", longName, new CommandLineOptions { Runtimes = new[] { "netcoreapp3.1", "net6.0", "net8.0" } });
yield return new Example("Use MemoryDiagnoser to get GC stats", shortName, new CommandLineOptions { UseMemoryDiagnoser = true });
yield return new Example("Use DisassemblyDiagnoser to get disassembly", shortName, new CommandLineOptions { UseDisassemblyDiagnoser = true });
yield return new Example("Use HardwareCountersDiagnoser to get hardware counter info", longName, new CommandLineOptions { HardwareCounters = new[] { nameof(HardwareCounter.CacheMisses), nameof(HardwareCounter.InstructionRetired) } });
@@ -250,8 +254,8 @@ public static IEnumerable Examples
yield return new Example("Run selected benchmarks once per iteration", longName, new CommandLineOptions { RunOncePerIteration = true });
yield return new Example("Run selected benchmarks 100 times per iteration. Perform single warmup iteration and 5 actual workload iterations", longName, new CommandLineOptions { InvocationCount = 100, WarmupIterationCount = 1, IterationCount = 5});
yield return new Example("Run selected benchmarks 250ms per iteration. Perform from 9 to 15 iterations", longName, new CommandLineOptions { IterationTimeInMilliseconds = 250, MinIterationCount = 9, MaxIterationCount = 15});
- yield return new Example("Run MannWhitney test with relative ratio of 5% for all benchmarks for .NET Core 2.0 (base) vs .NET Core 2.1 (diff). .NET Core 2.0 will be baseline because it was provided as first.", longName,
- new CommandLineOptions { Filters = new[] { "*"}, Runtimes = new[] { "netcoreapp2.0", "netcoreapp2.1" }, StatisticalTestThreshold = "5%" });
+ yield return new Example("Run MannWhitney test with relative ratio of 5% for all benchmarks for .NET 6.0 (base) vs .NET 8.0 (diff). .NET Core 6.0 will be baseline because it was provided as first.", longName,
+ new CommandLineOptions { Filters = new[] { "*"}, Runtimes = new[] { "net6.0", "net8.0" }, StatisticalTestThreshold = "5%" });
yield return new Example("Run benchmarks using environment variables 'ENV_VAR_KEY_1' with value 'value_1' and 'ENV_VAR_KEY_2' with value 'value_2'", longName,
new CommandLineOptions { EnvironmentVariables = new[] { "ENV_VAR_KEY_1:value_1", "ENV_VAR_KEY_2:value_2" } });
yield return new Example("Hide Mean and Ratio columns (use double quotes for multi-word columns: \"Alloc Ratio\")", shortName, new CommandLineOptions { HiddenColumns = new[] { "Mean", "Ratio" }, });
diff --git a/src/BenchmarkDotNet/ConsoleArguments/ConfigParser.cs b/src/BenchmarkDotNet/ConsoleArguments/ConfigParser.cs
index 355f1a6b75..ac61ddb5f1 100644
--- a/src/BenchmarkDotNet/ConsoleArguments/ConfigParser.cs
+++ b/src/BenchmarkDotNet/ConsoleArguments/ConfigParser.cs
@@ -1,774 +1,797 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
-using System.IO;
-using System.Linq;
-using System.Text;
-using BenchmarkDotNet.Columns;
-using BenchmarkDotNet.Configs;
-using BenchmarkDotNet.Diagnosers;
-using BenchmarkDotNet.Environments;
-using BenchmarkDotNet.Exporters;
-using BenchmarkDotNet.Exporters.Csv;
-using BenchmarkDotNet.Exporters.Json;
-using BenchmarkDotNet.Exporters.Xml;
-using BenchmarkDotNet.Extensions;
-using BenchmarkDotNet.Filters;
-using BenchmarkDotNet.Jobs;
-using BenchmarkDotNet.Loggers;
-using BenchmarkDotNet.Portability;
-using BenchmarkDotNet.Reports;
-using BenchmarkDotNet.Toolchains.CoreRun;
-using BenchmarkDotNet.Toolchains.CsProj;
-using BenchmarkDotNet.Toolchains.DotNetCli;
-using BenchmarkDotNet.Toolchains.InProcess.Emit;
-using BenchmarkDotNet.Toolchains.MonoAotLLVM;
-using BenchmarkDotNet.Toolchains.MonoWasm;
-using BenchmarkDotNet.Toolchains.NativeAot;
-using CommandLine;
-using Perfolizer.Horology;
-using Perfolizer.Mathematics.OutlierDetection;
-using BenchmarkDotNet.Toolchains.Mono;
-using Perfolizer.Metrology;
-
-namespace BenchmarkDotNet.ConsoleArguments
-{
- public static class ConfigParser
- {
- private const int MinimumDisplayWidth = 80;
- private const char EnvVarKeyValueSeparator = ':';
-
- private static readonly IReadOnlyDictionary AvailableJobs = new Dictionary(StringComparer.InvariantCultureIgnoreCase)
- {
- { "default", Job.Default },
- { "dry", Job.Dry },
- { "short", Job.ShortRun },
- { "medium", Job.MediumRun },
- { "long", Job.LongRun },
- { "verylong", Job.VeryLongRun }
- };
-
- [SuppressMessage("ReSharper", "StringLiteralTypo")]
- [SuppressMessage("ReSharper", "CoVariantArrayConversion")]
- private static readonly IReadOnlyDictionary AvailableExporters =
- new Dictionary(StringComparer.InvariantCultureIgnoreCase)
- {
- { "csv", new[] { CsvExporter.Default } },
- { "csvmeasurements", new[] { CsvMeasurementsExporter.Default } },
- { "html", new[] { HtmlExporter.Default } },
- { "markdown", new[] { MarkdownExporter.Default } },
- { "atlassian", new[] { MarkdownExporter.Atlassian } },
- { "stackoverflow", new[] { MarkdownExporter.StackOverflow } },
- { "github", new[] { MarkdownExporter.GitHub } },
- { "plain", new[] { PlainExporter.Default } },
- { "rplot", new[] { CsvMeasurementsExporter.Default, RPlotExporter.Default } }, // R Plots depends on having the full measurements available
- { "json", new[] { JsonExporter.Default } },
- { "briefjson", new[] { JsonExporter.Brief } },
- { "fulljson", new[] { JsonExporter.Full } },
- { "asciidoc", new[] { AsciiDocExporter.Default } },
- { "xml", new[] { XmlExporter.Default } },
- { "briefxml", new[] { XmlExporter.Brief } },
- { "fullxml", new[] { XmlExporter.Full } }
- };
-
- public static (bool isSuccess, IConfig config, CommandLineOptions options) Parse(string[] args, ILogger logger, IConfig? globalConfig = null)
- {
- (bool isSuccess, IConfig config, CommandLineOptions options) result = default;
-
- var (expandSuccess, expandedArgs) = ExpandResponseFile(args, logger);
- if (!expandSuccess)
- {
- return (false, default, default);
- }
-
- args = expandedArgs;
- using (var parser = CreateParser(logger))
- {
- parser
- .ParseArguments(args)
- .WithParsed(options => result = Validate(options, logger) ? (true, CreateConfig(options, globalConfig, args), options) : (false, default, default))
- .WithNotParsed(errors => result = (false, default, default));
- }
-
- return result;
- }
-
- private static (bool Success, string[] ExpandedTokens) ExpandResponseFile(string[] args, ILogger logger)
- {
- List result = new ();
- foreach (var arg in args)
- {
- if (arg.StartsWith("@"))
- {
- var fileName = arg.Substring(1);
- try
- {
- if (File.Exists(fileName))
- {
- var lines = File.ReadAllLines(fileName);
- foreach (var line in lines)
- {
- result.AddRange(ConsumeTokens(line));
- }
- }
- else
- {
- logger.WriteLineError($"Response file {fileName} does not exists.");
- return (false, Array.Empty());
- }
- }
- catch (Exception ex)
- {
- logger.WriteLineError($"Failed to parse RSP file: {fileName}, {ex.Message}");
- return (false, Array.Empty());
- }
- }
- else
- {
- result.Add(arg);
- }
- }
-
- return (true, result.ToArray());
- }
-
- private static IEnumerable ConsumeTokens(string line)
- {
- bool insideQuotes = false;
- var token = new StringBuilder();
- for (int i = 0; i < line.Length; i++)
- {
- char currentChar = line[i];
- if (currentChar == ' ' && !insideQuotes)
- {
- if (token.Length > 0)
- {
- yield return GetToken();
- token = new StringBuilder();
- }
-
- continue;
- }
-
- if (currentChar == '"')
- {
- insideQuotes = !insideQuotes;
- continue;
- }
-
- if (currentChar == '\\' && insideQuotes)
- {
- if (line[i + 1] == '"')
- {
- insideQuotes = false;
- i++;
- continue;
- }
-
- if (line[i + 1] == '\\')
- {
- token.Append('\\');
- i++;
- continue;
- }
- }
-
- token.Append(currentChar);
- }
-
- if (token.Length > 0)
- {
- yield return GetToken();
- }
-
- string GetToken()
- {
- var result = token.ToString();
- if (result.Contains(' '))
- {
- // Workaround for CommandLine library issue with parsing these kind of args.
- return " " + result;
- }
-
- return result;
- }
- }
-
- internal static bool TryUpdateArgs(string[] args, out string[]? updatedArgs, Action updater)
- {
- (bool isSuccess, CommandLineOptions options) result = default;
-
- ILogger logger = NullLogger.Instance;
- using (var parser = CreateParser(logger))
- {
- parser
- .ParseArguments(args)
- .WithParsed(options => result = Validate(options, logger) ? (true, options) : (false, default))
- .WithNotParsed(errors => result = (false, default));
-
- if (!result.isSuccess)
- {
- updatedArgs = null;
- return false;
- }
-
- updater(result.options);
-
- updatedArgs = parser.FormatCommandLine(result.options, settings => settings.SkipDefault = true).Split();
- return true;
- }
- }
-
- private static Parser CreateParser(ILogger logger)
- => new Parser(settings =>
- {
- settings.CaseInsensitiveEnumValues = true;
- settings.CaseSensitive = false;
- settings.EnableDashDash = true;
- settings.IgnoreUnknownArguments = false;
- settings.HelpWriter = new LoggerWrapper(logger);
- settings.MaximumDisplayWidth = Math.Max(MinimumDisplayWidth, GetMaximumDisplayWidth());
- });
-
- private static bool Validate(CommandLineOptions options, ILogger logger)
- {
- if (!AvailableJobs.ContainsKey(options.BaseJob))
- {
- logger.WriteLineError($"The provided base job \"{options.BaseJob}\" is invalid. Available options are: {string.Join(", ", AvailableJobs.Keys)}.");
- return false;
- }
-
- foreach (string runtime in options.Runtimes)
- {
- if (!TryParse(runtime, out RuntimeMoniker runtimeMoniker))
- {
- logger.WriteLineError($"The provided runtime \"{runtime}\" is invalid. Available options are: {string.Join(", ", Enum.GetNames(typeof(RuntimeMoniker)).Select(name => name.ToLower()))}.");
- return false;
- }
- else if (runtimeMoniker == RuntimeMoniker.MonoAOTLLVM && (options.AOTCompilerPath == null || options.AOTCompilerPath.IsNotNullButDoesNotExist()))
- {
- logger.WriteLineError($"The provided {nameof(options.AOTCompilerPath)} \"{options.AOTCompilerPath}\" does NOT exist. It MUST be provided.");
- }
- }
-
- foreach (string exporter in options.Exporters)
- if (!AvailableExporters.ContainsKey(exporter))
- {
- logger.WriteLineError($"The provided exporter \"{exporter}\" is invalid. Available options are: {string.Join(", ", AvailableExporters.Keys)}.");
- return false;
- }
-
- if (options.CliPath.IsNotNullButDoesNotExist())
- {
- logger.WriteLineError($"The provided {nameof(options.CliPath)} \"{options.CliPath}\" does NOT exist.");
- return false;
- }
-
- foreach (var coreRunPath in options.CoreRunPaths)
- if (coreRunPath.IsNotNullButDoesNotExist())
- {
- if (Directory.Exists(coreRunPath.FullName))
- {
- logger.WriteLineError($"The provided path to CoreRun: \"{coreRunPath}\" exists but it's a directory, not an executable. You need to include CoreRun.exe (corerun on Unix) in the path.");
- }
- else
- {
- logger.WriteLineError($"The provided path to CoreRun: \"{coreRunPath}\" does NOT exist.");
- }
-
- return false;
- }
-
- if (options.MonoPath.IsNotNullButDoesNotExist())
- {
- logger.WriteLineError($"The provided {nameof(options.MonoPath)} \"{options.MonoPath}\" does NOT exist.");
- return false;
- }
-
- if (options.WasmJavascriptEngine.IsNotNullButDoesNotExist())
- {
- logger.WriteLineError($"The provided {nameof(options.WasmJavascriptEngine)} \"{options.WasmJavascriptEngine}\" does NOT exist.");
- return false;
- }
-
- if (options.IlcPackages.IsNotNullButDoesNotExist())
- {
- logger.WriteLineError($"The provided {nameof(options.IlcPackages)} \"{options.IlcPackages}\" does NOT exist.");
- return false;
- }
-
- if (options.HardwareCounters.Count() > 3)
- {
- logger.WriteLineError("You can't use more than 3 HardwareCounters at the same time.");
- return false;
- }
-
- foreach (var counterName in options.HardwareCounters)
- if (!Enum.TryParse(counterName, ignoreCase: true, out HardwareCounter _))
- {
- logger.WriteLineError($"The provided hardware counter \"{counterName}\" is invalid. Available options are: {string.Join("+", Enum.GetNames(typeof(HardwareCounter)))}.");
- return false;
- }
-
- if (!string.IsNullOrEmpty(options.StatisticalTestThreshold) && !Threshold.TryParse(options.StatisticalTestThreshold, out _))
- {
- logger.WriteLineError("Invalid Threshold for Statistical Test. Use --help to see examples.");
- return false;
- }
-
- if (options.EnvironmentVariables.Any(envVar => envVar.IndexOf(EnvVarKeyValueSeparator) <= 0))
- {
- logger.WriteLineError($"Environment variable value must be separated from the key using '{EnvVarKeyValueSeparator}'. Use --help to see examples.");
- return false;
- }
-
- return true;
- }
-
- private static IConfig CreateConfig(CommandLineOptions options, IConfig globalConfig, string[] args)
- {
- var config = new ManualConfig();
-
- var baseJob = GetBaseJob(options, globalConfig);
- var expanded = Expand(baseJob.UnfreezeCopy(), options, args).ToArray(); // UnfreezeCopy ensures that each of the expanded jobs will have it's own ID
- if (expanded.Length > 1)
- expanded[0] = expanded[0].AsBaseline(); // if the user provides multiple jobs, then the first one should be a baseline
- config.AddJob(expanded);
- if (config.GetJobs().IsEmpty() && baseJob != Job.Default)
- config.AddJob(baseJob);
-
- config.AddExporter(options.Exporters.SelectMany(exporter => AvailableExporters[exporter]).ToArray());
-
- config.AddHardwareCounters(options.HardwareCounters
- .Select(counterName => (HardwareCounter)Enum.Parse(typeof(HardwareCounter), counterName, ignoreCase: true))
- .ToArray());
-
- if (options.UseMemoryDiagnoser)
- config.AddDiagnoser(MemoryDiagnoser.Default);
- if (options.UseThreadingDiagnoser)
- config.AddDiagnoser(ThreadingDiagnoser.Default);
- if (options.UseExceptionDiagnoser)
- config.AddDiagnoser(ExceptionDiagnoser.Default);
- if (options.UseDisassemblyDiagnoser)
- config.AddDiagnoser(new DisassemblyDiagnoser(new DisassemblyDiagnoserConfig(
- maxDepth: options.DisassemblerRecursiveDepth,
- filters: options.DisassemblerFilters.ToArray(),
- exportDiff: options.DisassemblerDiff)));
- if (!string.IsNullOrEmpty(options.Profiler))
- config.AddDiagnoser(DiagnosersLoader.GetImplementation(profiler => profiler.ShortName.EqualsWithIgnoreCase(options.Profiler)));
-
- if (options.DisplayAllStatistics)
- config.AddColumn(StatisticColumn.AllStatistics);
- if (!string.IsNullOrEmpty(options.StatisticalTestThreshold) && Threshold.TryParse(options.StatisticalTestThreshold, out var threshold))
- config.AddColumn(new StatisticalTestColumn(threshold));
-
- if (options.ArtifactsDirectory != null)
- config.ArtifactsPath = options.ArtifactsDirectory.FullName;
-
- var filters = GetFilters(options).ToArray();
- if (filters.Length > 1)
- config.AddFilter(new UnionFilter(filters));
- else
- config.AddFilter(filters);
-
- config.HideColumns(options.HiddenColumns.ToArray());
-
- config.WithOption(ConfigOptions.JoinSummary, options.Join);
- config.WithOption(ConfigOptions.KeepBenchmarkFiles, options.KeepBenchmarkFiles);
- config.WithOption(ConfigOptions.DontOverwriteResults, options.DontOverwriteResults);
- config.WithOption(ConfigOptions.StopOnFirstError, options.StopOnFirstError);
- config.WithOption(ConfigOptions.DisableLogFile, options.DisableLogFile);
- config.WithOption(ConfigOptions.LogBuildOutput, options.LogBuildOutput);
- config.WithOption(ConfigOptions.GenerateMSBuildBinLog, options.GenerateMSBuildBinLog);
- config.WithOption(ConfigOptions.ApplesToApples, options.ApplesToApples);
- config.WithOption(ConfigOptions.Resume, options.Resume);
-
- if (config.Options.IsSet(ConfigOptions.GenerateMSBuildBinLog))
- config.Options |= ConfigOptions.KeepBenchmarkFiles;
-
- if (options.MaxParameterColumnWidth.HasValue)
- config.WithSummaryStyle(SummaryStyle.Default.WithMaxParameterColumnWidth(options.MaxParameterColumnWidth.Value));
-
- if (options.TimeOutInSeconds.HasValue)
- config.WithBuildTimeout(TimeSpan.FromSeconds(options.TimeOutInSeconds.Value));
-
- return config;
- }
-
- private static Job GetBaseJob(CommandLineOptions options, IConfig globalConfig)
- {
- var baseJob =
- globalConfig?.GetJobs().SingleOrDefault(job => job.Meta.IsDefault) // global config might define single custom Default job
- ?? AvailableJobs[options.BaseJob.ToLowerInvariant()];
-
- if (baseJob != Job.Dry && options.Outliers != OutlierMode.RemoveUpper)
- baseJob = baseJob.WithOutlierMode(options.Outliers);
-
- if (options.Affinity.HasValue)
- baseJob = baseJob.WithAffinity((IntPtr)options.Affinity.Value);
-
- if (options.LaunchCount.HasValue)
- baseJob = baseJob.WithLaunchCount(options.LaunchCount.Value);
- if (options.WarmupIterationCount.HasValue)
- baseJob = baseJob.WithWarmupCount(options.WarmupIterationCount.Value);
- if (options.MinWarmupIterationCount.HasValue)
- baseJob = baseJob.WithMinWarmupCount(options.MinWarmupIterationCount.Value);
- if (options.MaxWarmupIterationCount.HasValue)
- baseJob = baseJob.WithMaxWarmupCount(options.MaxWarmupIterationCount.Value);
- if (options.IterationTimeInMilliseconds.HasValue)
- baseJob = baseJob.WithIterationTime(TimeInterval.FromMilliseconds(options.IterationTimeInMilliseconds.Value));
- if (options.IterationCount.HasValue)
- baseJob = baseJob.WithIterationCount(options.IterationCount.Value);
- if (options.MinIterationCount.HasValue)
- baseJob = baseJob.WithMinIterationCount(options.MinIterationCount.Value);
- if (options.MaxIterationCount.HasValue)
- baseJob = baseJob.WithMaxIterationCount(options.MaxIterationCount.Value);
- if (options.InvocationCount.HasValue)
- baseJob = baseJob.WithInvocationCount(options.InvocationCount.Value);
- if (options.UnrollFactor.HasValue)
- baseJob = baseJob.WithUnrollFactor(options.UnrollFactor.Value);
- if (options.RunStrategy.HasValue)
- baseJob = baseJob.WithStrategy(options.RunStrategy.Value);
- if (options.Platform.HasValue)
- baseJob = baseJob.WithPlatform(options.Platform.Value);
- if (options.RunOncePerIteration)
- baseJob = baseJob.RunOncePerIteration();
- if (options.MemoryRandomization)
- baseJob = baseJob.WithMemoryRandomization();
- if (options.NoForcedGCs)
- baseJob = baseJob.WithGcForce(false);
- if (options.NoEvaluationOverhead)
- baseJob = baseJob.WithEvaluateOverhead(false);
-
- if (options.EnvironmentVariables.Any())
- {
- baseJob = baseJob.WithEnvironmentVariables(options.EnvironmentVariables.Select(text =>
- {
- var separated = text.Split(new[] { EnvVarKeyValueSeparator }, 2);
- return new EnvironmentVariable(separated[0], separated[1]);
- }).ToArray());
- }
-
- if (AvailableJobs.Values.Contains(baseJob)) // no custom settings
- return baseJob;
-
- return baseJob
- .AsDefault(false) // after applying all settings from console args the base job is not default anymore
- .AsMutator(); // we mark it as mutator so it will be applied to other jobs defined via attributes and merged later in GetRunnableJobs method
- }
-
- private static IEnumerable Expand(Job baseJob, CommandLineOptions options, string[] args)
- {
- if (options.RunInProcess)
- {
- yield return baseJob.WithToolchain(InProcessEmitToolchain.Instance);
- }
- else if (!string.IsNullOrEmpty(options.ClrVersion))
- {
- yield return baseJob.WithRuntime(ClrRuntime.CreateForLocalFullNetFrameworkBuild(options.ClrVersion)); // local builds of .NET Runtime
- }
- else if (options.CliPath != null && options.Runtimes.IsEmpty() && options.CoreRunPaths.IsEmpty())
- {
- yield return CreateCoreJobWithCli(baseJob, options);
- }
- else
- {
- // in case both --runtimes and --corerun are specified, the first one is returned first and becomes a baseline job
- string first = args.FirstOrDefault(arg =>
- arg.Equals("--runtimes", StringComparison.OrdinalIgnoreCase)
- || arg.Equals("-r", StringComparison.OrdinalIgnoreCase)
-
- || arg.Equals("--corerun", StringComparison.OrdinalIgnoreCase));
-
- if (first is null || first.Equals("--corerun", StringComparison.OrdinalIgnoreCase))
- {
- foreach (var coreRunPath in options.CoreRunPaths)
- yield return CreateCoreRunJob(baseJob, options, coreRunPath); // local dotnet/runtime builds
-
- foreach (string runtime in options.Runtimes) // known runtimes
- yield return CreateJobForGivenRuntime(baseJob, runtime, options);
- }
- else
- {
- foreach (string runtime in options.Runtimes) // known runtimes
- yield return CreateJobForGivenRuntime(baseJob, runtime, options);
-
- foreach (var coreRunPath in options.CoreRunPaths)
- yield return CreateCoreRunJob(baseJob, options, coreRunPath); // local dotnet/runtime builds
- }
- }
- }
-
- private static Job CreateJobForGivenRuntime(Job baseJob, string runtimeId, CommandLineOptions options)
- {
- if (!TryParse(runtimeId, out RuntimeMoniker runtimeMoniker))
- {
- throw new InvalidOperationException("Impossible, already validated by the Validate method");
- }
-
- switch (runtimeMoniker)
- {
- case RuntimeMoniker.Net461:
- case RuntimeMoniker.Net462:
- case RuntimeMoniker.Net47:
- case RuntimeMoniker.Net471:
- case RuntimeMoniker.Net472:
- case RuntimeMoniker.Net48:
- case RuntimeMoniker.Net481:
- return baseJob
- .WithRuntime(runtimeMoniker.GetRuntime())
- .WithToolchain(CsProjClassicNetToolchain.From(runtimeId, options.RestorePath?.FullName, options.CliPath?.FullName));
-
- case RuntimeMoniker.NetCoreApp20:
- case RuntimeMoniker.NetCoreApp21:
- case RuntimeMoniker.NetCoreApp22:
- case RuntimeMoniker.NetCoreApp30:
- case RuntimeMoniker.NetCoreApp31:
-#pragma warning disable CS0618 // Type or member is obsolete
- case RuntimeMoniker.NetCoreApp50:
-#pragma warning restore CS0618 // Type or member is obsolete
- case RuntimeMoniker.Net50:
- case RuntimeMoniker.Net60:
- case RuntimeMoniker.Net70:
- case RuntimeMoniker.Net80:
- case RuntimeMoniker.Net90:
- return baseJob
- .WithRuntime(runtimeMoniker.GetRuntime())
- .WithToolchain(CsProjCoreToolchain.From(new NetCoreAppSettings(runtimeId, null, runtimeId, options.CliPath?.FullName, options.RestorePath?.FullName)));
-
- case RuntimeMoniker.Mono:
- return baseJob.WithRuntime(new MonoRuntime("Mono", options.MonoPath?.FullName));
-
- case RuntimeMoniker.NativeAot60:
- return CreateAotJob(baseJob, options, runtimeMoniker, "6.0.0-*", "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental/nuget/v3/index.json");
-
- case RuntimeMoniker.NativeAot70:
- return CreateAotJob(baseJob, options, runtimeMoniker, "", "https://api.nuget.org/v3/index.json");
-
- case RuntimeMoniker.NativeAot80:
- return CreateAotJob(baseJob, options, runtimeMoniker, "", "https://api.nuget.org/v3/index.json");
-
- case RuntimeMoniker.NativeAot90:
- return CreateAotJob(baseJob, options, runtimeMoniker, "", "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet9/nuget/v3/index.json");
-
- case RuntimeMoniker.Wasm:
- return MakeWasmJob(baseJob, options, RuntimeInformation.IsNetCore ? CoreRuntime.GetCurrentVersion().MsBuildMoniker : "net5.0", runtimeMoniker);
-
- case RuntimeMoniker.WasmNet50:
- return MakeWasmJob(baseJob, options, "net5.0", runtimeMoniker);
-
- case RuntimeMoniker.WasmNet60:
- return MakeWasmJob(baseJob, options, "net6.0", runtimeMoniker);
-
- case RuntimeMoniker.WasmNet70:
- return MakeWasmJob(baseJob, options, "net7.0", runtimeMoniker);
-
- case RuntimeMoniker.WasmNet80:
- return MakeWasmJob(baseJob, options, "net8.0", runtimeMoniker);
-
- case RuntimeMoniker.WasmNet90:
- return MakeWasmJob(baseJob, options, "net9.0", runtimeMoniker);
-
- case RuntimeMoniker.MonoAOTLLVM:
- return MakeMonoAOTLLVMJob(baseJob, options, RuntimeInformation.IsNetCore ? CoreRuntime.GetCurrentVersion().MsBuildMoniker : "net6.0", runtimeMoniker);
-
- case RuntimeMoniker.MonoAOTLLVMNet60:
- return MakeMonoAOTLLVMJob(baseJob, options, "net6.0", runtimeMoniker);
-
- case RuntimeMoniker.MonoAOTLLVMNet70:
- return MakeMonoAOTLLVMJob(baseJob, options, "net7.0", runtimeMoniker);
-
- case RuntimeMoniker.MonoAOTLLVMNet80:
- return MakeMonoAOTLLVMJob(baseJob, options, "net8.0", runtimeMoniker);
-
- case RuntimeMoniker.MonoAOTLLVMNet90:
- return MakeMonoAOTLLVMJob(baseJob, options, "net9.0", runtimeMoniker);
-
- case RuntimeMoniker.Mono60:
- return MakeMonoJob(baseJob, options, MonoRuntime.Mono60);
-
- case RuntimeMoniker.Mono70:
- return MakeMonoJob(baseJob, options, MonoRuntime.Mono70);
-
- case RuntimeMoniker.Mono80:
- return MakeMonoJob(baseJob, options, MonoRuntime.Mono80);
-
- case RuntimeMoniker.Mono90:
- return MakeMonoJob(baseJob, options, MonoRuntime.Mono90);
-
- default:
- throw new NotSupportedException($"Runtime {runtimeId} is not supported");
- }
- }
-
- private static Job CreateAotJob(Job baseJob, CommandLineOptions options, RuntimeMoniker runtimeMoniker, string ilCompilerVersion, string nuGetFeedUrl)
- {
- var builder = NativeAotToolchain.CreateBuilder();
-
- if (options.CliPath != null)
- builder.DotNetCli(options.CliPath.FullName);
- if (options.RestorePath != null)
- builder.PackagesRestorePath(options.RestorePath.FullName);
-
- if (options.IlcPackages != null)
- builder.UseLocalBuild(options.IlcPackages);
- else if (!string.IsNullOrEmpty(options.ILCompilerVersion))
- builder.UseNuGet(options.ILCompilerVersion, nuGetFeedUrl);
- else
- builder.UseNuGet(ilCompilerVersion, nuGetFeedUrl);
-
- var runtime = runtimeMoniker.GetRuntime();
- builder.TargetFrameworkMoniker(runtime.MsBuildMoniker);
-
- return baseJob.WithRuntime(runtime).WithToolchain(builder.ToToolchain());
- }
-
- private static Job MakeMonoJob(Job baseJob, CommandLineOptions options, MonoRuntime runtime)
- {
- return baseJob
- .WithRuntime(runtime)
- .WithToolchain(MonoToolchain.From(
- new NetCoreAppSettings(
- targetFrameworkMoniker: runtime.MsBuildMoniker,
- runtimeFrameworkVersion: null,
- name: runtime.Name,
- customDotNetCliPath: options.CliPath?.FullName,
- packagesPath: options.RestorePath?.FullName)));
- }
-
- private static Job MakeMonoAOTLLVMJob(Job baseJob, CommandLineOptions options, string msBuildMoniker, RuntimeMoniker moniker)
- {
- var monoAotLLVMRuntime = new MonoAotLLVMRuntime(aotCompilerPath: options.AOTCompilerPath, aotCompilerMode: options.AOTCompilerMode, msBuildMoniker: msBuildMoniker, moniker: moniker);
-
- var toolChain = MonoAotLLVMToolChain.From(
- new NetCoreAppSettings(
- targetFrameworkMoniker: monoAotLLVMRuntime.MsBuildMoniker,
- runtimeFrameworkVersion: null,
- name: monoAotLLVMRuntime.Name,
- customDotNetCliPath: options.CliPath?.FullName,
- packagesPath: options.RestorePath?.FullName,
- customRuntimePack: options.CustomRuntimePack,
- aotCompilerPath: options.AOTCompilerPath.ToString(),
- aotCompilerMode: options.AOTCompilerMode));
-
- return baseJob.WithRuntime(monoAotLLVMRuntime).WithToolchain(toolChain);
- }
-
- private static Job MakeWasmJob(Job baseJob, CommandLineOptions options, string msBuildMoniker, RuntimeMoniker moniker)
- {
- bool wasmAot = options.AOTCompilerMode == MonoAotCompilerMode.wasm;
-
- var wasmRuntime = new WasmRuntime(
- msBuildMoniker: msBuildMoniker,
- javaScriptEngine: options.WasmJavascriptEngine?.FullName ?? "v8",
- javaScriptEngineArguments: options.WasmJavaScriptEngineArguments,
- aot: wasmAot,
- wasmDataDir: options.WasmDataDirectory?.FullName,
- moniker: moniker);
-
- var toolChain = WasmToolchain.From(new NetCoreAppSettings(
- targetFrameworkMoniker: wasmRuntime.MsBuildMoniker,
- runtimeFrameworkVersion: null,
- name: wasmRuntime.Name,
- customDotNetCliPath: options.CliPath?.FullName,
- packagesPath: options.RestorePath?.FullName,
- customRuntimePack: options.CustomRuntimePack,
- aotCompilerMode: options.AOTCompilerMode));
-
- return baseJob.WithRuntime(wasmRuntime).WithToolchain(toolChain);
- }
-
- private static IEnumerable GetFilters(CommandLineOptions options)
- {
- if (options.Filters.Any())
- yield return new GlobFilter(options.Filters.ToArray());
- if (options.AllCategories.Any())
- yield return new AllCategoriesFilter(options.AllCategories.ToArray());
- if (options.AnyCategories.Any())
- yield return new AnyCategoriesFilter(options.AnyCategories.ToArray());
- if (options.AttributeNames.Any())
- yield return new AttributesFilter(options.AttributeNames.ToArray());
- }
-
- private static int GetMaximumDisplayWidth()
- {
- try
- {
- return Console.WindowWidth;
- }
- catch (IOException)
- {
- return MinimumDisplayWidth;
- }
- }
-
- private static Job CreateCoreRunJob(Job baseJob, CommandLineOptions options, FileInfo coreRunPath)
- => baseJob
- .WithToolchain(new CoreRunToolchain(
- coreRunPath,
- createCopy: true,
- targetFrameworkMoniker:
- RuntimeInformation.IsNetCore
- ? RuntimeInformation.GetCurrentRuntime().MsBuildMoniker
- : CoreRuntime.Latest.MsBuildMoniker, // use most recent tfm, as the toolchain is being used only by dotnet/runtime contributors
- customDotNetCliPath: options.CliPath,
- restorePath: options.RestorePath,
- displayName: GetCoreRunToolchainDisplayName(options.CoreRunPaths, coreRunPath)));
-
- private static Job CreateCoreJobWithCli(Job baseJob, CommandLineOptions options)
- => baseJob
- .WithToolchain(CsProjCoreToolchain.From(
- new NetCoreAppSettings(
- targetFrameworkMoniker: RuntimeInformation.GetCurrentRuntime().MsBuildMoniker,
- customDotNetCliPath: options.CliPath?.FullName,
- runtimeFrameworkVersion: null,
- name: RuntimeInformation.GetCurrentRuntime().Name,
- packagesPath: options.RestorePath?.FullName)));
-
- ///
- /// we have a limited amount of space when printing the output to the console, so we try to keep things small and simple
- ///
- /// for following paths:
- /// C:\Projects\coreclr_upstream\bin\tests\Windows_NT.x64.Release\Tests\Core_Root\CoreRun.exe
- /// C:\Projects\coreclr_upstream\bin\tests\Windows_NT.x64.Release\Tests\Core_Root_beforeMyChanges\CoreRun.exe
- ///
- /// we get:
- ///
- /// \Core_Root\CoreRun.exe
- /// \Core_Root_beforeMyChanges\CoreRun.exe
- ///
- private static string GetCoreRunToolchainDisplayName(IReadOnlyList paths, FileInfo coreRunPath)
- {
- if (paths.Count <= 1)
- return "CoreRun";
-
- int commonLongestPrefixIndex = paths[0].FullName.Length;
- for (int i = 1; i < paths.Count; i++)
- {
- commonLongestPrefixIndex = Math.Min(commonLongestPrefixIndex, paths[i].FullName.Length);
- for (int j = 0; j < commonLongestPrefixIndex; j++)
- if (paths[i].FullName[j] != paths[0].FullName[j])
- {
- commonLongestPrefixIndex = j;
- break;
- }
- }
-
- if (commonLongestPrefixIndex <= 1)
- return coreRunPath.FullName;
-
- var lastCommonDirectorySeparatorIndex = coreRunPath.FullName.LastIndexOf(Path.DirectorySeparatorChar, commonLongestPrefixIndex - 1);
-
- return coreRunPath.FullName.Substring(lastCommonDirectorySeparatorIndex);
- }
-
- internal static bool TryParse(string runtime, out RuntimeMoniker runtimeMoniker)
- {
- int index = runtime.IndexOf('-');
-
- return index < 0
- ? Enum.TryParse(runtime.Replace(".", string.Empty), ignoreCase: true, out runtimeMoniker)
- : Enum.TryParse(runtime.Substring(0, index).Replace(".", string.Empty), ignoreCase: true, out runtimeMoniker);
- }
- }
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.Linq;
+using System.Text;
+using BenchmarkDotNet.Columns;
+using BenchmarkDotNet.Configs;
+using BenchmarkDotNet.Diagnosers;
+using BenchmarkDotNet.Environments;
+using BenchmarkDotNet.Exporters;
+using BenchmarkDotNet.Exporters.Csv;
+using BenchmarkDotNet.Exporters.Json;
+using BenchmarkDotNet.Exporters.Xml;
+using BenchmarkDotNet.Extensions;
+using BenchmarkDotNet.Filters;
+using BenchmarkDotNet.Jobs;
+using BenchmarkDotNet.Loggers;
+using BenchmarkDotNet.Portability;
+using BenchmarkDotNet.Reports;
+using BenchmarkDotNet.Toolchains.CoreRun;
+using BenchmarkDotNet.Toolchains.CsProj;
+using BenchmarkDotNet.Toolchains.DotNetCli;
+using BenchmarkDotNet.Toolchains.InProcess.Emit;
+using BenchmarkDotNet.Toolchains.MonoAotLLVM;
+using BenchmarkDotNet.Toolchains.MonoWasm;
+using BenchmarkDotNet.Toolchains.NativeAot;
+using CommandLine;
+using Perfolizer.Horology;
+using Perfolizer.Mathematics.OutlierDetection;
+using BenchmarkDotNet.Toolchains.Mono;
+using Perfolizer.Metrology;
+
+namespace BenchmarkDotNet.ConsoleArguments
+{
+ public static class ConfigParser
+ {
+ private const int MinimumDisplayWidth = 80;
+ private const char EnvVarKeyValueSeparator = ':';
+
+ private static readonly IReadOnlyDictionary AvailableJobs = new Dictionary(StringComparer.InvariantCultureIgnoreCase)
+ {
+ { "default", Job.Default },
+ { "dry", Job.Dry },
+ { "short", Job.ShortRun },
+ { "medium", Job.MediumRun },
+ { "long", Job.LongRun },
+ { "verylong", Job.VeryLongRun }
+ };
+
+ [SuppressMessage("ReSharper", "StringLiteralTypo")]
+ [SuppressMessage("ReSharper", "CoVariantArrayConversion")]
+ private static readonly IReadOnlyDictionary AvailableExporters =
+ new Dictionary(StringComparer.InvariantCultureIgnoreCase)
+ {
+ { "csv", new[] { CsvExporter.Default } },
+ { "csvmeasurements", new[] { CsvMeasurementsExporter.Default } },
+ { "html", new[] { HtmlExporter.Default } },
+ { "markdown", new[] { MarkdownExporter.Default } },
+ { "atlassian", new[] { MarkdownExporter.Atlassian } },
+ { "stackoverflow", new[] { MarkdownExporter.StackOverflow } },
+ { "github", new[] { MarkdownExporter.GitHub } },
+ { "plain", new[] { PlainExporter.Default } },
+ { "rplot", new[] { CsvMeasurementsExporter.Default, RPlotExporter.Default } }, // R Plots depends on having the full measurements available
+ { "json", new[] { JsonExporter.Default } },
+ { "briefjson", new[] { JsonExporter.Brief } },
+ { "fulljson", new[] { JsonExporter.Full } },
+ { "asciidoc", new[] { AsciiDocExporter.Default } },
+ { "xml", new[] { XmlExporter.Default } },
+ { "briefxml", new[] { XmlExporter.Brief } },
+ { "fullxml", new[] { XmlExporter.Full } }
+ };
+
+ public static (bool isSuccess, IConfig config, CommandLineOptions options) Parse(string[] args, ILogger logger, IConfig? globalConfig = null)
+ {
+ (bool isSuccess, IConfig config, CommandLineOptions options) result = default;
+
+ var (expandSuccess, expandedArgs) = ExpandResponseFile(args, logger);
+ if (!expandSuccess)
+ {
+ return (false, default, default);
+ }
+
+ args = expandedArgs;
+ using (var parser = CreateParser(logger))
+ {
+ parser
+ .ParseArguments(args)
+ .WithParsed(options => result = Validate(options, logger) ? (true, CreateConfig(options, globalConfig, args), options) : (false, default, default))
+ .WithNotParsed(errors => result = (false, default, default));
+ }
+
+ return result;
+ }
+
+ private static (bool Success, string[] ExpandedTokens) ExpandResponseFile(string[] args, ILogger logger)
+ {
+ List result = new();
+ foreach (var arg in args)
+ {
+ if (arg.StartsWith("@"))
+ {
+ var fileName = arg.Substring(1);
+ try
+ {
+ if (File.Exists(fileName))
+ {
+ var lines = File.ReadAllLines(fileName);
+ foreach (var line in lines)
+ {
+ result.AddRange(ConsumeTokens(line));
+ }
+ }
+ else
+ {
+ logger.WriteLineError($"Response file {fileName} does not exists.");
+ return (false, Array.Empty());
+ }
+ }
+ catch (Exception ex)
+ {
+ logger.WriteLineError($"Failed to parse RSP file: {fileName}, {ex.Message}");
+ return (false, Array.Empty());
+ }
+ }
+ else
+ {
+ result.Add(arg);
+ }
+ }
+
+ return (true, result.ToArray());
+ }
+
+ private static IEnumerable ConsumeTokens(string line)
+ {
+ bool insideQuotes = false;
+ var token = new StringBuilder();
+ for (int i = 0; i < line.Length; i++)
+ {
+ char currentChar = line[i];
+ if (currentChar == ' ' && !insideQuotes)
+ {
+ if (token.Length > 0)
+ {
+ yield return GetToken();
+ token = new StringBuilder();
+ }
+
+ continue;
+ }
+
+ if (currentChar == '"')
+ {
+ insideQuotes = !insideQuotes;
+ continue;
+ }
+
+ if (currentChar == '\\' && insideQuotes)
+ {
+ if (line[i + 1] == '"')
+ {
+ insideQuotes = false;
+ i++;
+ continue;
+ }
+
+ if (line[i + 1] == '\\')
+ {
+ token.Append('\\');
+ i++;
+ continue;
+ }
+ }
+
+ token.Append(currentChar);
+ }
+
+ if (token.Length > 0)
+ {
+ yield return GetToken();
+ }
+
+ string GetToken()
+ {
+ var result = token.ToString();
+ if (result.Contains(' '))
+ {
+ // Workaround for CommandLine library issue with parsing these kind of args.
+ return " " + result;
+ }
+
+ return result;
+ }
+ }
+
+ internal static bool TryUpdateArgs(string[] args, out string[]? updatedArgs, Action updater)
+ {
+ (bool isSuccess, CommandLineOptions options) result = default;
+
+ ILogger logger = NullLogger.Instance;
+ using (var parser = CreateParser(logger))
+ {
+ parser
+ .ParseArguments(args)
+ .WithParsed(options => result = Validate(options, logger) ? (true, options) : (false, default))
+ .WithNotParsed(errors => result = (false, default));
+
+ if (!result.isSuccess)
+ {
+ updatedArgs = null;
+ return false;
+ }
+
+ updater(result.options);
+
+ updatedArgs = parser.FormatCommandLine(result.options, settings => settings.SkipDefault = true).Split();
+ return true;
+ }
+ }
+
+ private static Parser CreateParser(ILogger logger)
+ => new Parser(settings =>
+ {
+ settings.CaseInsensitiveEnumValues = true;
+ settings.CaseSensitive = false;
+ settings.EnableDashDash = true;
+ settings.IgnoreUnknownArguments = false;
+ settings.HelpWriter = new LoggerWrapper(logger);
+ settings.MaximumDisplayWidth = Math.Max(MinimumDisplayWidth, GetMaximumDisplayWidth());
+ });
+
+ private static bool Validate(CommandLineOptions options, ILogger logger)
+ {
+ if (!AvailableJobs.ContainsKey(options.BaseJob))
+ {
+ logger.WriteLineError($"The provided base job \"{options.BaseJob}\" is invalid. Available options are: {string.Join(", ", AvailableJobs.Keys)}.");
+ return false;
+ }
+
+ foreach (string runtime in options.Runtimes)
+ {
+ if (!TryParse(runtime, out RuntimeMoniker runtimeMoniker))
+ {
+ logger.WriteLineError($"The provided runtime \"{runtime}\" is invalid. Available options are: {string.Join(", ", Enum.GetNames(typeof(RuntimeMoniker)).Select(name => name.ToLower()))}.");
+ return false;
+ }
+ else if (runtimeMoniker == RuntimeMoniker.MonoAOTLLVM && (options.AOTCompilerPath == null || options.AOTCompilerPath.IsNotNullButDoesNotExist()))
+ {
+ logger.WriteLineError($"The provided {nameof(options.AOTCompilerPath)} \"{options.AOTCompilerPath}\" does NOT exist. It MUST be provided.");
+ }
+ }
+
+ foreach (string exporter in options.Exporters)
+ if (!AvailableExporters.ContainsKey(exporter))
+ {
+ logger.WriteLineError($"The provided exporter \"{exporter}\" is invalid. Available options are: {string.Join(", ", AvailableExporters.Keys)}.");
+ return false;
+ }
+
+ if (options.CliPath.IsNotNullButDoesNotExist())
+ {
+ logger.WriteLineError($"The provided {nameof(options.CliPath)} \"{options.CliPath}\" does NOT exist.");
+ return false;
+ }
+
+ foreach (var coreRunPath in options.CoreRunPaths)
+ if (coreRunPath.IsNotNullButDoesNotExist())
+ {
+ if (Directory.Exists(coreRunPath.FullName))
+ {
+ logger.WriteLineError($"The provided path to CoreRun: \"{coreRunPath}\" exists but it's a directory, not an executable. You need to include CoreRun.exe (corerun on Unix) in the path.");
+ }
+ else
+ {
+ logger.WriteLineError($"The provided path to CoreRun: \"{coreRunPath}\" does NOT exist.");
+ }
+
+ return false;
+ }
+
+ if (options.MonoPath.IsNotNullButDoesNotExist())
+ {
+ logger.WriteLineError($"The provided {nameof(options.MonoPath)} \"{options.MonoPath}\" does NOT exist.");
+ return false;
+ }
+
+ if (options.WasmJavascriptEngine.IsNotNullButDoesNotExist())
+ {
+ logger.WriteLineError($"The provided {nameof(options.WasmJavascriptEngine)} \"{options.WasmJavascriptEngine}\" does NOT exist.");
+ return false;
+ }
+
+ if (options.IlcPackages.IsNotNullButDoesNotExist())
+ {
+ logger.WriteLineError($"The provided {nameof(options.IlcPackages)} \"{options.IlcPackages}\" does NOT exist.");
+ return false;
+ }
+
+ if (options.HardwareCounters.Count() > 3)
+ {
+ logger.WriteLineError("You can't use more than 3 HardwareCounters at the same time.");
+ return false;
+ }
+
+ foreach (var counterName in options.HardwareCounters)
+ if (!Enum.TryParse(counterName, ignoreCase: true, out HardwareCounter _))
+ {
+ logger.WriteLineError($"The provided hardware counter \"{counterName}\" is invalid. Available options are: {string.Join("+", Enum.GetNames(typeof(HardwareCounter)))}.");
+ return false;
+ }
+
+ if (!string.IsNullOrEmpty(options.StatisticalTestThreshold) && !Threshold.TryParse(options.StatisticalTestThreshold, out _))
+ {
+ logger.WriteLineError("Invalid Threshold for Statistical Test. Use --help to see examples.");
+ return false;
+ }
+
+ if (options.EnvironmentVariables.Any(envVar => envVar.IndexOf(EnvVarKeyValueSeparator) <= 0))
+ {
+ logger.WriteLineError($"Environment variable value must be separated from the key using '{EnvVarKeyValueSeparator}'. Use --help to see examples.");
+ return false;
+ }
+
+ return true;
+ }
+
+ private static IConfig CreateConfig(CommandLineOptions options, IConfig globalConfig, string[] args)
+ {
+ var config = new ManualConfig();
+
+ var baseJob = GetBaseJob(options, globalConfig);
+ var expanded = Expand(baseJob.UnfreezeCopy(), options, args).ToArray(); // UnfreezeCopy ensures that each of the expanded jobs will have it's own ID
+ if (expanded.Length > 1)
+ expanded[0] = expanded[0].AsBaseline(); // if the user provides multiple jobs, then the first one should be a baseline
+ config.AddJob(expanded);
+ if (config.GetJobs().IsEmpty() && baseJob != Job.Default)
+ config.AddJob(baseJob);
+
+ config.AddExporter(options.Exporters.SelectMany(exporter => AvailableExporters[exporter]).ToArray());
+
+ config.AddHardwareCounters(options.HardwareCounters
+ .Select(counterName => (HardwareCounter)Enum.Parse(typeof(HardwareCounter), counterName, ignoreCase: true))
+ .ToArray());
+
+ if (options.UseMemoryDiagnoser)
+ config.AddDiagnoser(MemoryDiagnoser.Default);
+ if (options.UseThreadingDiagnoser)
+ config.AddDiagnoser(ThreadingDiagnoser.Default);
+ if (options.UseExceptionDiagnoser)
+ config.AddDiagnoser(ExceptionDiagnoser.Default);
+ if (options.UseDisassemblyDiagnoser)
+ config.AddDiagnoser(new DisassemblyDiagnoser(new DisassemblyDiagnoserConfig(
+ maxDepth: options.DisassemblerRecursiveDepth,
+ filters: options.DisassemblerFilters.ToArray(),
+ exportDiff: options.DisassemblerDiff)));
+ if (!string.IsNullOrEmpty(options.Profiler))
+ config.AddDiagnoser(DiagnosersLoader.GetImplementation(profiler => profiler.ShortName.EqualsWithIgnoreCase(options.Profiler)));
+
+ if (options.DisplayAllStatistics)
+ config.AddColumn(StatisticColumn.AllStatistics);
+ if (!string.IsNullOrEmpty(options.StatisticalTestThreshold) && Threshold.TryParse(options.StatisticalTestThreshold, out var threshold))
+ config.AddColumn(new StatisticalTestColumn(threshold));
+
+ if (options.ArtifactsDirectory != null)
+ config.ArtifactsPath = options.ArtifactsDirectory.FullName;
+
+ var filters = GetFilters(options).ToArray();
+ if (filters.Length > 1)
+ config.AddFilter(new UnionFilter(filters));
+ else
+ config.AddFilter(filters);
+
+ config.HideColumns(options.HiddenColumns.ToArray());
+
+ config.WithOption(ConfigOptions.JoinSummary, options.Join);
+ config.WithOption(ConfigOptions.KeepBenchmarkFiles, options.KeepBenchmarkFiles);
+ config.WithOption(ConfigOptions.DontOverwriteResults, options.DontOverwriteResults);
+ config.WithOption(ConfigOptions.StopOnFirstError, options.StopOnFirstError);
+ config.WithOption(ConfigOptions.DisableLogFile, options.DisableLogFile);
+ config.WithOption(ConfigOptions.LogBuildOutput, options.LogBuildOutput);
+ config.WithOption(ConfigOptions.GenerateMSBuildBinLog, options.GenerateMSBuildBinLog);
+ config.WithOption(ConfigOptions.ApplesToApples, options.ApplesToApples);
+ config.WithOption(ConfigOptions.Resume, options.Resume);
+
+ if (config.Options.IsSet(ConfigOptions.GenerateMSBuildBinLog))
+ config.Options |= ConfigOptions.KeepBenchmarkFiles;
+
+ if (options.MaxParameterColumnWidth.HasValue)
+ config.WithSummaryStyle(SummaryStyle.Default.WithMaxParameterColumnWidth(options.MaxParameterColumnWidth.Value));
+
+ if (options.TimeOutInSeconds.HasValue)
+ config.WithBuildTimeout(TimeSpan.FromSeconds(options.TimeOutInSeconds.Value));
+
+ if (options.WakeLock.HasValue)
+ config.WithWakeLock(options.WakeLock.Value);
+
+ return config;
+ }
+
+ private static Job GetBaseJob(CommandLineOptions options, IConfig globalConfig)
+ {
+ var baseJob =
+ globalConfig?.GetJobs().SingleOrDefault(job => job.Meta.IsDefault) // global config might define single custom Default job
+ ?? AvailableJobs[options.BaseJob.ToLowerInvariant()];
+
+ if (baseJob != Job.Dry && options.Outliers != OutlierMode.RemoveUpper)
+ baseJob = baseJob.WithOutlierMode(options.Outliers);
+
+ if (options.Affinity.HasValue)
+ baseJob = baseJob.WithAffinity((IntPtr)options.Affinity.Value);
+
+ if (options.LaunchCount.HasValue)
+ baseJob = baseJob.WithLaunchCount(options.LaunchCount.Value);
+ if (options.WarmupIterationCount.HasValue)
+ baseJob = baseJob.WithWarmupCount(options.WarmupIterationCount.Value);
+ if (options.MinWarmupIterationCount.HasValue)
+ baseJob = baseJob.WithMinWarmupCount(options.MinWarmupIterationCount.Value);
+ if (options.MaxWarmupIterationCount.HasValue)
+ baseJob = baseJob.WithMaxWarmupCount(options.MaxWarmupIterationCount.Value);
+ if (options.IterationTimeInMilliseconds.HasValue)
+ baseJob = baseJob.WithIterationTime(TimeInterval.FromMilliseconds(options.IterationTimeInMilliseconds.Value));
+ if (options.IterationCount.HasValue)
+ baseJob = baseJob.WithIterationCount(options.IterationCount.Value);
+ if (options.MinIterationCount.HasValue)
+ baseJob = baseJob.WithMinIterationCount(options.MinIterationCount.Value);
+ if (options.MaxIterationCount.HasValue)
+ baseJob = baseJob.WithMaxIterationCount(options.MaxIterationCount.Value);
+ if (options.InvocationCount.HasValue)
+ baseJob = baseJob.WithInvocationCount(options.InvocationCount.Value);
+ if (options.UnrollFactor.HasValue)
+ baseJob = baseJob.WithUnrollFactor(options.UnrollFactor.Value);
+ if (options.RunStrategy.HasValue)
+ baseJob = baseJob.WithStrategy(options.RunStrategy.Value);
+ if (options.Platform.HasValue)
+ baseJob = baseJob.WithPlatform(options.Platform.Value);
+ if (options.RunOncePerIteration)
+ baseJob = baseJob.RunOncePerIteration();
+ if (options.MemoryRandomization)
+ baseJob = baseJob.WithMemoryRandomization();
+ if (options.NoForcedGCs)
+ baseJob = baseJob.WithGcForce(false);
+ if (options.NoEvaluationOverhead)
+ baseJob = baseJob.WithEvaluateOverhead(false);
+
+ if (options.EnvironmentVariables.Any())
+ {
+ baseJob = baseJob.WithEnvironmentVariables(options.EnvironmentVariables.Select(text =>
+ {
+ var separated = text.Split(new[] { EnvVarKeyValueSeparator }, 2);
+ return new EnvironmentVariable(separated[0], separated[1]);
+ }).ToArray());
+ }
+
+ if (AvailableJobs.Values.Contains(baseJob)) // no custom settings
+ return baseJob;
+
+ return baseJob
+ .AsDefault(false) // after applying all settings from console args the base job is not default anymore
+ .AsMutator(); // we mark it as mutator so it will be applied to other jobs defined via attributes and merged later in GetRunnableJobs method
+ }
+
+ private static IEnumerable Expand(Job baseJob, CommandLineOptions options, string[] args)
+ {
+ if (options.RunInProcess)
+ {
+ yield return baseJob.WithToolchain(InProcessEmitToolchain.Instance);
+ }
+ else if (!string.IsNullOrEmpty(options.ClrVersion))
+ {
+ yield return baseJob.WithRuntime(ClrRuntime.CreateForLocalFullNetFrameworkBuild(options.ClrVersion)); // local builds of .NET Runtime
+ }
+ else if (options.CliPath != null && options.Runtimes.IsEmpty() && options.CoreRunPaths.IsEmpty())
+ {
+ yield return CreateCoreJobWithCli(baseJob, options);
+ }
+ else
+ {
+ // in case both --runtimes and --corerun are specified, the first one is returned first and becomes a baseline job
+ string first = args.FirstOrDefault(arg =>
+ arg.Equals("--runtimes", StringComparison.OrdinalIgnoreCase)
+ || arg.Equals("-r", StringComparison.OrdinalIgnoreCase)
+
+ || arg.Equals("--corerun", StringComparison.OrdinalIgnoreCase));
+
+ if (first is null || first.Equals("--corerun", StringComparison.OrdinalIgnoreCase))
+ {
+ foreach (var coreRunPath in options.CoreRunPaths)
+ yield return CreateCoreRunJob(baseJob, options, coreRunPath); // local dotnet/runtime builds
+
+ foreach (string runtime in options.Runtimes) // known runtimes
+ yield return CreateJobForGivenRuntime(baseJob, runtime, options);
+ }
+ else
+ {
+ foreach (string runtime in options.Runtimes) // known runtimes
+ yield return CreateJobForGivenRuntime(baseJob, runtime, options);
+
+ foreach (var coreRunPath in options.CoreRunPaths)
+ yield return CreateCoreRunJob(baseJob, options, coreRunPath); // local dotnet/runtime builds
+ }
+ }
+ }
+
+ private static Job CreateJobForGivenRuntime(Job baseJob, string runtimeId, CommandLineOptions options)
+ {
+ if (!TryParse(runtimeId, out RuntimeMoniker runtimeMoniker))
+ {
+ throw new InvalidOperationException("Impossible, already validated by the Validate method");
+ }
+
+ switch (runtimeMoniker)
+ {
+ case RuntimeMoniker.Net461:
+ case RuntimeMoniker.Net462:
+ case RuntimeMoniker.Net47:
+ case RuntimeMoniker.Net471:
+ case RuntimeMoniker.Net472:
+ case RuntimeMoniker.Net48:
+ case RuntimeMoniker.Net481:
+ return baseJob
+ .WithRuntime(runtimeMoniker.GetRuntime())
+ .WithToolchain(CsProjClassicNetToolchain.From(runtimeId, options.RestorePath?.FullName, options.CliPath?.FullName));
+
+ case RuntimeMoniker.NetCoreApp20:
+ case RuntimeMoniker.NetCoreApp21:
+ case RuntimeMoniker.NetCoreApp22:
+ case RuntimeMoniker.NetCoreApp30:
+ case RuntimeMoniker.NetCoreApp31:
+#pragma warning disable CS0618 // Type or member is obsolete
+ case RuntimeMoniker.NetCoreApp50:
+#pragma warning restore CS0618 // Type or member is obsolete
+ case RuntimeMoniker.Net50:
+ case RuntimeMoniker.Net60:
+ case RuntimeMoniker.Net70:
+ case RuntimeMoniker.Net80:
+ case RuntimeMoniker.Net90:
+ case RuntimeMoniker.Net10_0:
+ return baseJob
+ .WithRuntime(runtimeMoniker.GetRuntime())
+ .WithToolchain(CsProjCoreToolchain.From(new NetCoreAppSettings(runtimeId, null, runtimeId, options.CliPath?.FullName, options.RestorePath?.FullName)));
+
+ case RuntimeMoniker.Mono:
+ return baseJob.WithRuntime(new MonoRuntime("Mono", options.MonoPath?.FullName));
+
+ case RuntimeMoniker.NativeAot60:
+ return CreateAotJob(baseJob, options, runtimeMoniker, "6.0.0-*", "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental/nuget/v3/index.json");
+
+ case RuntimeMoniker.NativeAot70:
+ return CreateAotJob(baseJob, options, runtimeMoniker, "", "https://api.nuget.org/v3/index.json");
+
+ case RuntimeMoniker.NativeAot80:
+ return CreateAotJob(baseJob, options, runtimeMoniker, "", "https://api.nuget.org/v3/index.json");
+
+ case RuntimeMoniker.NativeAot90:
+ return CreateAotJob(baseJob, options, runtimeMoniker, "", "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet9/nuget/v3/index.json");
+
+ case RuntimeMoniker.NativeAot10_0:
+ return CreateAotJob(baseJob, options, runtimeMoniker, "", "https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet10/nuget/v3/index.json");
+
+ case RuntimeMoniker.Wasm:
+ return MakeWasmJob(baseJob, options, RuntimeInformation.IsNetCore ? CoreRuntime.GetCurrentVersion().MsBuildMoniker : "net5.0", runtimeMoniker);
+
+ case RuntimeMoniker.WasmNet50:
+ return MakeWasmJob(baseJob, options, "net5.0", runtimeMoniker);
+
+ case RuntimeMoniker.WasmNet60:
+ return MakeWasmJob(baseJob, options, "net6.0", runtimeMoniker);
+
+ case RuntimeMoniker.WasmNet70:
+ return MakeWasmJob(baseJob, options, "net7.0", runtimeMoniker);
+
+ case RuntimeMoniker.WasmNet80:
+ return MakeWasmJob(baseJob, options, "net8.0", runtimeMoniker);
+
+ case RuntimeMoniker.WasmNet90:
+ return MakeWasmJob(baseJob, options, "net9.0", runtimeMoniker);
+
+ case RuntimeMoniker.WasmNet10_0:
+ return MakeWasmJob(baseJob, options, "net10.0", runtimeMoniker);
+
+ case RuntimeMoniker.MonoAOTLLVM:
+ return MakeMonoAOTLLVMJob(baseJob, options, RuntimeInformation.IsNetCore ? CoreRuntime.GetCurrentVersion().MsBuildMoniker : "net6.0", runtimeMoniker);
+
+ case RuntimeMoniker.MonoAOTLLVMNet60:
+ return MakeMonoAOTLLVMJob(baseJob, options, "net6.0", runtimeMoniker);
+
+ case RuntimeMoniker.MonoAOTLLVMNet70:
+ return MakeMonoAOTLLVMJob(baseJob, options, "net7.0", runtimeMoniker);
+
+ case RuntimeMoniker.MonoAOTLLVMNet80:
+ return MakeMonoAOTLLVMJob(baseJob, options, "net8.0", runtimeMoniker);
+
+ case RuntimeMoniker.MonoAOTLLVMNet90:
+ return MakeMonoAOTLLVMJob(baseJob, options, "net9.0", runtimeMoniker);
+
+ case RuntimeMoniker.MonoAOTLLVMNet10_0:
+ return MakeMonoAOTLLVMJob(baseJob, options, "net10.0", runtimeMoniker);
+
+ case RuntimeMoniker.Mono60:
+ return MakeMonoJob(baseJob, options, MonoRuntime.Mono60);
+
+ case RuntimeMoniker.Mono70:
+ return MakeMonoJob(baseJob, options, MonoRuntime.Mono70);
+
+ case RuntimeMoniker.Mono80:
+ return MakeMonoJob(baseJob, options, MonoRuntime.Mono80);
+
+ case RuntimeMoniker.Mono90:
+ return MakeMonoJob(baseJob, options, MonoRuntime.Mono90);
+
+ case RuntimeMoniker.Mono10_0:
+ return MakeMonoJob(baseJob, options, MonoRuntime.Mono10_0);
+
+ default:
+ throw new NotSupportedException($"Runtime {runtimeId} is not supported");
+ }
+ }
+
+ private static Job CreateAotJob(Job baseJob, CommandLineOptions options, RuntimeMoniker runtimeMoniker, string ilCompilerVersion, string nuGetFeedUrl)
+ {
+ var builder = NativeAotToolchain.CreateBuilder();
+
+ if (options.CliPath != null)
+ builder.DotNetCli(options.CliPath.FullName);
+ if (options.RestorePath != null)
+ builder.PackagesRestorePath(options.RestorePath.FullName);
+
+ if (options.IlcPackages != null)
+ builder.UseLocalBuild(options.IlcPackages);
+ else if (!string.IsNullOrEmpty(options.ILCompilerVersion))
+ builder.UseNuGet(options.ILCompilerVersion, nuGetFeedUrl);
+ else
+ builder.UseNuGet(ilCompilerVersion, nuGetFeedUrl);
+
+ var runtime = runtimeMoniker.GetRuntime();
+ builder.TargetFrameworkMoniker(runtime.MsBuildMoniker);
+
+ return baseJob.WithRuntime(runtime).WithToolchain(builder.ToToolchain());
+ }
+
+ private static Job MakeMonoJob(Job baseJob, CommandLineOptions options, MonoRuntime runtime)
+ {
+ return baseJob
+ .WithRuntime(runtime)
+ .WithToolchain(MonoToolchain.From(
+ new NetCoreAppSettings(
+ targetFrameworkMoniker: runtime.MsBuildMoniker,
+ runtimeFrameworkVersion: null,
+ name: runtime.Name,
+ customDotNetCliPath: options.CliPath?.FullName,
+ packagesPath: options.RestorePath?.FullName)));
+ }
+
+ private static Job MakeMonoAOTLLVMJob(Job baseJob, CommandLineOptions options, string msBuildMoniker, RuntimeMoniker moniker)
+ {
+ var monoAotLLVMRuntime = new MonoAotLLVMRuntime(aotCompilerPath: options.AOTCompilerPath, aotCompilerMode: options.AOTCompilerMode, msBuildMoniker: msBuildMoniker, moniker: moniker);
+
+ var toolChain = MonoAotLLVMToolChain.From(
+ new NetCoreAppSettings(
+ targetFrameworkMoniker: monoAotLLVMRuntime.MsBuildMoniker,
+ runtimeFrameworkVersion: null,
+ name: monoAotLLVMRuntime.Name,
+ customDotNetCliPath: options.CliPath?.FullName,
+ packagesPath: options.RestorePath?.FullName,
+ customRuntimePack: options.CustomRuntimePack,
+ aotCompilerPath: options.AOTCompilerPath.ToString(),
+ aotCompilerMode: options.AOTCompilerMode));
+
+ return baseJob.WithRuntime(monoAotLLVMRuntime).WithToolchain(toolChain);
+ }
+
+ private static Job MakeWasmJob(Job baseJob, CommandLineOptions options, string msBuildMoniker, RuntimeMoniker moniker)
+ {
+ bool wasmAot = options.AOTCompilerMode == MonoAotCompilerMode.wasm;
+
+ var wasmRuntime = new WasmRuntime(
+ msBuildMoniker: msBuildMoniker,
+ javaScriptEngine: options.WasmJavascriptEngine?.FullName ?? "v8",
+ javaScriptEngineArguments: options.WasmJavaScriptEngineArguments,
+ aot: wasmAot,
+ wasmDataDir: options.WasmDataDirectory?.FullName,
+ moniker: moniker);
+
+ var toolChain = WasmToolchain.From(new NetCoreAppSettings(
+ targetFrameworkMoniker: wasmRuntime.MsBuildMoniker,
+ runtimeFrameworkVersion: null,
+ name: wasmRuntime.Name,
+ customDotNetCliPath: options.CliPath?.FullName,
+ packagesPath: options.RestorePath?.FullName,
+ customRuntimePack: options.CustomRuntimePack,
+ aotCompilerMode: options.AOTCompilerMode));
+
+ return baseJob.WithRuntime(wasmRuntime).WithToolchain(toolChain);
+ }
+
+ private static IEnumerable GetFilters(CommandLineOptions options)
+ {
+ if (options.Filters.Any())
+ yield return new GlobFilter(options.Filters.ToArray());
+ if (options.AllCategories.Any())
+ yield return new AllCategoriesFilter(options.AllCategories.ToArray());
+ if (options.AnyCategories.Any())
+ yield return new AnyCategoriesFilter(options.AnyCategories.ToArray());
+ if (options.AttributeNames.Any())
+ yield return new AttributesFilter(options.AttributeNames.ToArray());
+ }
+
+ private static int GetMaximumDisplayWidth()
+ {
+ try
+ {
+ return Console.WindowWidth;
+ }
+ catch (IOException)
+ {
+ return MinimumDisplayWidth;
+ }
+ }
+
+ private static Job CreateCoreRunJob(Job baseJob, CommandLineOptions options, FileInfo coreRunPath)
+ => baseJob
+ .WithToolchain(new CoreRunToolchain(
+ coreRunPath,
+ createCopy: true,
+ targetFrameworkMoniker:
+ RuntimeInformation.IsNetCore
+ ? RuntimeInformation.GetCurrentRuntime().MsBuildMoniker
+ : CoreRuntime.Latest.MsBuildMoniker, // use most recent tfm, as the toolchain is being used only by dotnet/runtime contributors
+ customDotNetCliPath: options.CliPath,
+ restorePath: options.RestorePath,
+ displayName: GetCoreRunToolchainDisplayName(options.CoreRunPaths, coreRunPath)));
+
+ private static Job CreateCoreJobWithCli(Job baseJob, CommandLineOptions options)
+ => baseJob
+ .WithToolchain(CsProjCoreToolchain.From(
+ new NetCoreAppSettings(
+ targetFrameworkMoniker: RuntimeInformation.GetCurrentRuntime().MsBuildMoniker,
+ customDotNetCliPath: options.CliPath?.FullName,
+ runtimeFrameworkVersion: null,
+ name: RuntimeInformation.GetCurrentRuntime().Name,
+ packagesPath: options.RestorePath?.FullName)));
+
+ ///
+ /// we have a limited amount of space when printing the output to the console, so we try to keep things small and simple
+ ///
+ /// for following paths:
+ /// C:\Projects\coreclr_upstream\bin\tests\Windows_NT.x64.Release\Tests\Core_Root\CoreRun.exe
+ /// C:\Projects\coreclr_upstream\bin\tests\Windows_NT.x64.Release\Tests\Core_Root_beforeMyChanges\CoreRun.exe
+ ///
+ /// we get:
+ ///
+ /// \Core_Root\CoreRun.exe
+ /// \Core_Root_beforeMyChanges\CoreRun.exe
+ ///
+ private static string GetCoreRunToolchainDisplayName(IReadOnlyList paths, FileInfo coreRunPath)
+ {
+ if (paths.Count <= 1)
+ return "CoreRun";
+
+ int commonLongestPrefixIndex = paths[0].FullName.Length;
+ for (int i = 1; i < paths.Count; i++)
+ {
+ commonLongestPrefixIndex = Math.Min(commonLongestPrefixIndex, paths[i].FullName.Length);
+ for (int j = 0; j < commonLongestPrefixIndex; j++)
+ if (paths[i].FullName[j] != paths[0].FullName[j])
+ {
+ commonLongestPrefixIndex = j;
+ break;
+ }
+ }
+
+ if (commonLongestPrefixIndex <= 1)
+ return coreRunPath.FullName;
+
+ var lastCommonDirectorySeparatorIndex = coreRunPath.FullName.LastIndexOf(Path.DirectorySeparatorChar, commonLongestPrefixIndex - 1);
+
+ return coreRunPath.FullName.Substring(lastCommonDirectorySeparatorIndex);
+ }
+
+ internal static bool TryParse(string runtime, out RuntimeMoniker runtimeMoniker)
+ {
+ int index = runtime.IndexOf('-');
+ if (index >= 0)
+ {
+ runtime = runtime.Substring(0, index);
+ }
+
+ // Monikers older than Net 10 don't use any version delimiter, newer monikers use _ delimiter.
+ if (Enum.TryParse(runtime.Replace(".", string.Empty), ignoreCase: true, out runtimeMoniker))
+ {
+ return true;
+ }
+ return Enum.TryParse(runtime.Replace('.', '_'), ignoreCase: true, out runtimeMoniker);
+ }
+ }
}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Portability/Cpu/HardwareIntrinsics.cs b/src/BenchmarkDotNet/Detectors/Cpu/HardwareIntrinsics.cs
similarity index 53%
rename from src/BenchmarkDotNet/Portability/Cpu/HardwareIntrinsics.cs
rename to src/BenchmarkDotNet/Detectors/Cpu/HardwareIntrinsics.cs
index be1d40c9ec..5b2815cc43 100644
--- a/src/BenchmarkDotNet/Portability/Cpu/HardwareIntrinsics.cs
+++ b/src/BenchmarkDotNet/Detectors/Cpu/HardwareIntrinsics.cs
@@ -2,48 +2,49 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
-using BenchmarkDotNet.Environments;
using System.Text;
-
+using BenchmarkDotNet.Environments;
#if NET6_0_OR_GREATER
using System.Runtime.Intrinsics.X86;
using System.Runtime.Intrinsics.Arm;
#endif
-namespace BenchmarkDotNet.Portability.Cpu
+namespace BenchmarkDotNet.Detectors.Cpu
{
+ // based on https://github.com/dotnet/runtime/tree/v10.0.0-rc.1.25451.107/src/coreclr/tools/Common/JitInterface/ThunkGenerator/InstructionSetDesc.txt
internal static class HardwareIntrinsics
{
internal static string GetVectorSize() => Vector.IsHardwareAccelerated ? $"VectorSize={Vector.Count * 8}" : string.Empty;
internal static string GetShortInfo()
{
- if (IsX86Avx512FSupported)
- return GetShortAvx512Representation();
- if (IsX86Avx2Supported)
- return "AVX2";
- else if (IsX86AvxSupported)
- return "AVX";
- else if (IsX86Sse42Supported)
- return "SSE4.2";
- else if (IsX86Sse41Supported)
- return "SSE4.1";
- else if (IsX86Ssse3Supported)
- return "SSSE3";
- else if (IsX86Sse3Supported)
- return "SSE3";
- else if (IsX86Sse2Supported)
- return "SSE2";
- else if (IsX86SseSupported)
- return "SSE";
- else if (IsX86BaseSupported)
- return "X86Base";
- else if (IsArmAdvSimdSupported)
- return "AdvSIMD";
+ if (IsX86BaseSupported)
+ {
+ if (IsX86Avx512Supported)
+ {
+ return "x86-64-v4";
+ }
+ else if (IsX86Avx2Supported)
+ {
+ return "x86-64-v3";
+ }
+ else if (IsX86Sse42Supported)
+ {
+ return "x86-64-v2";
+ }
+ else
+ {
+ return "x86-64-v1";
+ }
+ }
else if (IsArmBaseSupported)
- return "ArmBase";
+ {
+ return "armv8.0-a";
+ }
else
+ {
return GetVectorSize(); // Runtimes prior to .NET Core 3.0 (APIs did not exist so we print non-exact Vector info)
+ }
}
internal static string GetFullInfo(Platform platform)
@@ -56,32 +57,31 @@ static IEnumerable GetCurrentProcessInstructionSets(Platform platform)
{
case Platform.X86:
case Platform.X64:
-
- if (IsX86Avx512FSupported) yield return GetShortAvx512Representation();
- else if (IsX86Avx2Supported) yield return "AVX2";
- else if (IsX86AvxSupported) yield return "AVX";
- else if (IsX86Sse42Supported) yield return "SSE4.2";
- else if (IsX86Sse41Supported) yield return "SSE4.1";
- else if (IsX86Ssse3Supported) yield return "SSSE3";
- else if (IsX86Sse3Supported) yield return "SSE3";
- else if (IsX86Sse2Supported) yield return "SSE2";
- else if (IsX86SseSupported) yield return "SSE";
- else if (IsX86BaseSupported) yield return "X86Base";
-
- if (IsX86AesSupported) yield return "AES";
- if (IsX86Bmi1Supported) yield return "BMI1";
- if (IsX86Bmi2Supported) yield return "BMI2";
- if (IsX86FmaSupported) yield return "FMA";
- if (IsX86LzcntSupported) yield return "LZCNT";
- if (IsX86PclmulqdqSupported) yield return "PCLMUL";
- if (IsX86PopcntSupported) yield return "POPCNT";
+ {
+ if (IsX86Avx10v2Supported) yield return "AVX10v2";
+ if (IsX86Avx10v1Supported)
+ {
+ yield return "AVX10v1";
+ yield return "AVX512 BF16+FP16";
+ }
+ if (IsX86Avx512v3Supported) yield return "AVX512 BITALG+VBMI2+VNNI+VPOPCNTDQ";
+ if (IsX86Avx512v2Supported) yield return "AVX512 IFMA+VBMI";
+ if (IsX86Avx512Supported) yield return "AVX512 F+BW+CD+DQ+VL";
+ if (IsX86Avx2Supported) yield return "AVX2+BMI1+BMI2+F16C+FMA+LZCNT+MOVBE";
+ if (IsX86AvxSupported) yield return "AVX";
+ if (IsX86Sse42Supported) yield return "SSE3+SSSE3+SSE4.1+SSE4.2+POPCNT";
+ if (IsX86BaseSupported) yield return "X86Base+SSE+SSE2";
+ if (IsX86AesSupported) yield return "AES+PCLMUL";
if (IsX86AvxVnniSupported) yield return "AvxVnni";
if (IsX86SerializeSupported) yield return "SERIALIZE";
- // TODO: Add MOVBE when API is added.
break;
+ }
case Platform.Arm64:
- if (IsArmAdvSimdSupported) yield return "AdvSIMD";
- else if (IsArmBaseSupported) yield return "ArmBase";
+ {
+ if (IsArmBaseSupported)
+ {
+ yield return "ArmBase+AdvSimd";
+ }
if (IsArmAesSupported) yield return "AES";
if (IsArmCrc32Supported) yield return "CRC32";
@@ -90,71 +90,39 @@ static IEnumerable GetCurrentProcessInstructionSets(Platform platform)
if (IsArmSha1Supported) yield return "SHA1";
if (IsArmSha256Supported) yield return "SHA256";
break;
+ }
+
default:
yield break;
}
}
}
- private static string GetShortAvx512Representation()
- {
- StringBuilder avx512 = new ("AVX-512F");
- if (IsX86Avx512CDSupported) avx512.Append("+CD");
- if (IsX86Avx512BWSupported) avx512.Append("+BW");
- if (IsX86Avx512DQSupported) avx512.Append("+DQ");
- if (IsX86Avx512FVLSupported) avx512.Append("+VL");
- if (IsX86Avx512VbmiSupported) avx512.Append("+VBMI");
-
- return avx512.ToString();
- }
-
+#pragma warning disable CA2252 // Some APIs require opting into preview features
internal static bool IsX86BaseSupported =>
#if NET6_0_OR_GREATER
- X86Base.IsSupported;
-#elif NETSTANDARD
- GetIsSupported("System.Runtime.Intrinsics.X86.X86Base");
-#endif
-
- internal static bool IsX86SseSupported =>
-#if NET6_0_OR_GREATER
- Sse.IsSupported;
-#elif NETSTANDARD
- GetIsSupported("System.Runtime.Intrinsics.X86.Sse");
-#endif
-
- internal static bool IsX86Sse2Supported =>
-#if NET6_0_OR_GREATER
+ X86Base.IsSupported &&
+ Sse.IsSupported &&
Sse2.IsSupported;
#elif NETSTANDARD
+ GetIsSupported("System.Runtime.Intrinsics.X86.X86Base") &&
+ GetIsSupported("System.Runtime.Intrinsics.X86.Sse") &&
GetIsSupported("System.Runtime.Intrinsics.X86.Sse2");
#endif
- internal static bool IsX86Sse3Supported =>
-#if NET6_0_OR_GREATER
- Sse3.IsSupported;
-#elif NETSTANDARD
- GetIsSupported("System.Runtime.Intrinsics.X86.Sse3");
-#endif
-
- internal static bool IsX86Ssse3Supported =>
-#if NET6_0_OR_GREATER
- Ssse3.IsSupported;
-#elif NETSTANDARD
- GetIsSupported("System.Runtime.Intrinsics.X86.Ssse3");
-#endif
-
- internal static bool IsX86Sse41Supported =>
-#if NET6_0_OR_GREATER
- Sse41.IsSupported;
-#elif NETSTANDARD
- GetIsSupported("System.Runtime.Intrinsics.X86.Sse41");
-#endif
-
internal static bool IsX86Sse42Supported =>
#if NET6_0_OR_GREATER
- Sse42.IsSupported;
+ Sse3.IsSupported &&
+ Ssse3.IsSupported &&
+ Sse41.IsSupported &&
+ Sse42.IsSupported &&
+ Popcnt.IsSupported;
#elif NETSTANDARD
- GetIsSupported("System.Runtime.Intrinsics.X86.Sse42");
+ GetIsSupported("System.Runtime.Intrinsics.X86.Sse3") &&
+ GetIsSupported("System.Runtime.Intrinsics.X86.Ssse3") &&
+ GetIsSupported("System.Runtime.Intrinsics.X86.Sse41") &&
+ GetIsSupported("System.Runtime.Intrinsics.X86.Sse42") &&
+ GetIsSupported("System.Runtime.Intrinsics.X86.Popcnt");
#endif
internal static bool IsX86AvxSupported =>
@@ -166,107 +134,88 @@ private static string GetShortAvx512Representation()
internal static bool IsX86Avx2Supported =>
#if NET6_0_OR_GREATER
- Avx2.IsSupported;
+ Avx2.IsSupported &&
+ Bmi1.IsSupported &&
+ Bmi2.IsSupported &&
+ Fma.IsSupported &&
+ Lzcnt.IsSupported;
#elif NETSTANDARD
- GetIsSupported("System.Runtime.Intrinsics.X86.Avx2");
-#endif
-
- internal static bool IsX86Avx512FSupported =>
-#if NET8_0_OR_GREATER
- Avx512F.IsSupported;
-#else
- GetIsSupported("System.Runtime.Intrinsics.X86.Avx512F");
+ GetIsSupported("System.Runtime.Intrinsics.X86.Avx2") &&
+ GetIsSupported("System.Runtime.Intrinsics.X86.Bmi1") &&
+ GetIsSupported("System.Runtime.Intrinsics.X86.Bmi2") &&
+ GetIsSupported("System.Runtime.Intrinsics.X86.Fma") &&
+ GetIsSupported("System.Runtime.Intrinsics.X86.Lzcnt");
#endif
- internal static bool IsX86Avx512FVLSupported =>
+ internal static bool IsX86Avx512Supported =>
#if NET8_0_OR_GREATER
- Avx512F.VL.IsSupported;
+ Avx512F.IsSupported &&
+ Avx512F.VL.IsSupported &&
+ Avx512BW.IsSupported &&
+ Avx512BW.VL.IsSupported &&
+ Avx512CD.IsSupported &&
+ Avx512CD.VL.IsSupported &&
+ Avx512DQ.IsSupported &&
+ Avx512DQ.VL.IsSupported;
#else
- GetIsSupported("System.Runtime.Intrinsics.X86.Avx512F+VL");
+ GetIsSupported("System.Runtime.Intrinsics.X86.Avx512F") &&
+ GetIsSupported("System.Runtime.Intrinsics.X86.Avx512F+VL") &&
+ GetIsSupported("System.Runtime.Intrinsics.X86.Avx512BW") &&
+ GetIsSupported("System.Runtime.Intrinsics.X86.Avx512BW+VL") &&
+ GetIsSupported("System.Runtime.Intrinsics.X86.Avx512CD") &&
+ GetIsSupported("System.Runtime.Intrinsics.X86.Avx512CD+VL") &&
+ GetIsSupported("System.Runtime.Intrinsics.X86.Avx512DQ") &&
+ GetIsSupported("System.Runtime.Intrinsics.X86.Avx512DQ+VL");
#endif
- internal static bool IsX86Avx512BWSupported =>
+ internal static bool IsX86Avx512v2Supported =>
#if NET8_0_OR_GREATER
- Avx512BW.IsSupported;
+ Avx512Vbmi.IsSupported &&
+ Avx512Vbmi.VL.IsSupported;
#else
- GetIsSupported("System.Runtime.Intrinsics.X86.Avx512BW");
+ GetIsSupported("System.Runtime.Intrinsics.X86.Avx512Vbmi") &&
+ GetIsSupported("System.Runtime.Intrinsics.X86.Avx512Vbmi+VL");
#endif
- internal static bool IsX86Avx512CDSupported =>
-#if NET8_0_OR_GREATER
- Avx512CD.IsSupported;
+ internal static bool IsX86Avx512v3Supported =>
+#if NET10_0_OR_GREATER
+ Avx512Vbmi2.IsSupported &&
+ Avx512Vbmi2.VL.IsSupported;
#else
- GetIsSupported("System.Runtime.Intrinsics.X86.Avx512CD");
+ GetIsSupported("System.Runtime.Intrinsics.X86.Avx512Vbmi2") &&
+ GetIsSupported("System.Runtime.Intrinsics.X86.Avx512Vbmi2+VL");
#endif
- internal static bool IsX86Avx512DQSupported =>
-#if NET8_0_OR_GREATER
- Avx512DQ.IsSupported;
+ internal static bool IsX86Avx10v1Supported =>
+#if NET9_0_OR_GREATER
+ Avx10v1.IsSupported &&
+ Avx10v1.V512.IsSupported;
#else
- GetIsSupported("System.Runtime.Intrinsics.X86.Avx512DQ");
+ GetIsSupported("System.Runtime.Intrinsics.X86.Avx10v1") &&
+ GetIsSupported("System.Runtime.Intrinsics.X86.Avx10v1+V512");
#endif
- internal static bool IsX86Avx512VbmiSupported =>
-#if NET8_0_OR_GREATER
- Avx512Vbmi.IsSupported;
+ internal static bool IsX86Avx10v2Supported =>
+#if NET10_0_OR_GREATER
+ Avx10v2.IsSupported &&
+ Avx10v2.V512.IsSupported;
#else
- GetIsSupported("System.Runtime.Intrinsics.X86.Avx512Vbmi");
+ GetIsSupported("System.Runtime.Intrinsics.X86.Avx10v2") &&
+ GetIsSupported("System.Runtime.Intrinsics.X86.Avx10v2+V512");
#endif
internal static bool IsX86AesSupported =>
#if NET6_0_OR_GREATER
- System.Runtime.Intrinsics.X86.Aes.IsSupported;
-#elif NETSTANDARD
- GetIsSupported("System.Runtime.Intrinsics.X86.Aes");
-#endif
-
- internal static bool IsX86Bmi1Supported =>
-#if NET6_0_OR_GREATER
- Bmi1.IsSupported;
-#elif NETSTANDARD
- GetIsSupported("System.Runtime.Intrinsics.X86.Bmi1");
-#endif
-
- internal static bool IsX86Bmi2Supported =>
-#if NET6_0_OR_GREATER
- Bmi2.IsSupported;
-#elif NETSTANDARD
- GetIsSupported("System.Runtime.Intrinsics.X86.Bmi2");
-#endif
-
- internal static bool IsX86FmaSupported =>
-#if NET6_0_OR_GREATER
- Fma.IsSupported;
-#elif NETSTANDARD
- GetIsSupported("System.Runtime.Intrinsics.X86.Fma");
-#endif
-
- internal static bool IsX86LzcntSupported =>
-#if NET6_0_OR_GREATER
- Lzcnt.IsSupported;
-#elif NETSTANDARD
- GetIsSupported("System.Runtime.Intrinsics.X86.Lzcnt");
-#endif
-
- internal static bool IsX86PclmulqdqSupported =>
-#if NET6_0_OR_GREATER
+ System.Runtime.Intrinsics.X86.Aes.IsSupported &&
Pclmulqdq.IsSupported;
#elif NETSTANDARD
+ GetIsSupported("System.Runtime.Intrinsics.X86.Aes") &&
GetIsSupported("System.Runtime.Intrinsics.X86.Pclmulqdq");
#endif
- internal static bool IsX86PopcntSupported =>
-#if NET6_0_OR_GREATER
- Popcnt.IsSupported;
-#elif NETSTANDARD
- GetIsSupported("System.Runtime.Intrinsics.X86.Popcnt");
-#endif
-
internal static bool IsX86AvxVnniSupported =>
#if NET6_0_OR_GREATER
-#pragma warning disable CA2252 // This API requires opting into preview features
AvxVnni.IsSupported;
-#pragma warning restore CA2252 // This API requires opting into preview features
#elif NETSTANDARD
GetIsSupported("System.Runtime.Intrinsics.X86.AvxVnni");
#endif
@@ -280,15 +229,10 @@ private static string GetShortAvx512Representation()
internal static bool IsArmBaseSupported =>
#if NET6_0_OR_GREATER
- ArmBase.IsSupported;
-#elif NETSTANDARD
- GetIsSupported("System.Runtime.Intrinsics.Arm.ArmBase");
-#endif
-
- internal static bool IsArmAdvSimdSupported =>
-#if NET6_0_OR_GREATER
+ ArmBase.IsSupported &&
AdvSimd.IsSupported;
#elif NETSTANDARD
+ GetIsSupported("System.Runtime.Intrinsics.Arm.ArmBase") &&
GetIsSupported("System.Runtime.Intrinsics.Arm.AdvSimd");
#endif
@@ -333,6 +277,7 @@ private static string GetShortAvx512Representation()
#elif NETSTANDARD
GetIsSupported("System.Runtime.Intrinsics.Arm.Sha256");
#endif
+#pragma warning restore CA2252 // Some APIs require opting into preview features
private static bool GetIsSupported([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] string typeName)
{
diff --git a/src/BenchmarkDotNet/Detectors/Cpu/ICpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/ICpuDetector.cs
new file mode 100644
index 0000000000..72355b60e5
--- /dev/null
+++ b/src/BenchmarkDotNet/Detectors/Cpu/ICpuDetector.cs
@@ -0,0 +1,12 @@
+using Perfolizer.Models;
+
+namespace BenchmarkDotNet.Detectors.Cpu;
+
+///
+/// Loads the for the current hardware
+///
+public interface ICpuDetector
+{
+ bool IsApplicable();
+ CpuInfo? Detect();
+}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuDetector.cs
new file mode 100644
index 0000000000..44973b27e9
--- /dev/null
+++ b/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuDetector.cs
@@ -0,0 +1,35 @@
+using System.Collections.Generic;
+using BenchmarkDotNet.Helpers;
+using Perfolizer.Models;
+
+namespace BenchmarkDotNet.Detectors.Cpu.Linux;
+
+///
+/// CPU information from output of the `cat /proc/cpuinfo` and `lscpu` command.
+/// Linux only.
+///
+internal class LinuxCpuDetector : ICpuDetector
+{
+ public bool IsApplicable() => OsDetector.IsLinux();
+
+ public CpuInfo? Detect()
+ {
+ if (!IsApplicable()) return null;
+
+ // lscpu output respects the system locale, so we should force language invariant environment for correct parsing
+ var languageInvariantEnvironment = new Dictionary
+ {
+ ["LC_ALL"] = "C",
+ ["LANG"] = "C",
+ ["LANGUAGE"] = "C"
+ };
+
+ string? cpuInfo = ProcessHelper.RunAndReadOutput("cat", "/proc/cpuinfo") ?? string.Empty;
+ string? lscpu = ProcessHelper.RunAndReadOutput("/bin/bash", "-c \"lscpu\"", environmentVariables: languageInvariantEnvironment) ?? string.Empty;
+
+ if (cpuInfo == string.Empty && lscpu == string.Empty)
+ return null;
+
+ return LinuxCpuInfoParser.Parse(cpuInfo, lscpu);
+ }
+}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuInfoParser.cs b/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuInfoParser.cs
new file mode 100644
index 0000000000..7d79c3262f
--- /dev/null
+++ b/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuInfoParser.cs
@@ -0,0 +1,160 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.RegularExpressions;
+using BenchmarkDotNet.Extensions;
+using BenchmarkDotNet.Helpers;
+using BenchmarkDotNet.Portability;
+using Perfolizer.Horology;
+using Perfolizer.Models;
+
+namespace BenchmarkDotNet.Detectors.Cpu.Linux;
+
+internal static class LinuxCpuInfoParser
+{
+ private static class ProcCpu
+ {
+ internal const string PhysicalId = "physical id";
+ internal const string CpuCores = "cpu cores";
+ internal const string ModelName = "model name";
+ internal const string MaxFrequency = "max freq";
+ internal const string NominalFrequencyBackup = "nominal freq";
+ internal const string NominalFrequency = "cpu MHz";
+ }
+
+ private static class Lscpu
+ {
+ internal const string MaxFrequency = "CPU max MHz";
+ internal const string ModelName = "Model name";
+ internal const string CoresPerSocket = "Core(s) per socket";
+ }
+
+ /// Output of `cat /proc/cpuinfo`
+ /// Output of `lscpu`
+ internal static CpuInfo Parse(string cpuInfo, string lscpu)
+ {
+ var processorModelNames = new HashSet();
+ var processorsToPhysicalCoreCount = new Dictionary();
+ int logicalCoreCount = 0;
+ double maxFrequency = 0.0;
+ double nominalFrequency = 0.0;
+
+ var logicalCores = SectionsHelper.ParseSections(cpuInfo, ':');
+ foreach (var logicalCore in logicalCores)
+ {
+ if (logicalCore.TryGetValue(ProcCpu.PhysicalId, out string physicalId) &&
+ logicalCore.TryGetValue(ProcCpu.CpuCores, out string cpuCoresValue) &&
+ int.TryParse(cpuCoresValue, out int cpuCoreCount) &&
+ cpuCoreCount > 0)
+ processorsToPhysicalCoreCount[physicalId] = cpuCoreCount;
+
+ if (logicalCore.TryGetValue(ProcCpu.ModelName, out string modelName))
+ {
+ processorModelNames.Add(modelName);
+ logicalCoreCount++;
+ }
+
+ if (logicalCore.TryGetValue(ProcCpu.MaxFrequency, out string maxCpuFreqValue) &&
+ Frequency.TryParseMHz(maxCpuFreqValue.Replace(',', '.'), out Frequency maxCpuFreq)
+ && maxCpuFreq > 0)
+ {
+ maxFrequency = Math.Max(maxFrequency, maxCpuFreq.ToMHz());
+ }
+
+ bool nominalFrequencyHasValue = logicalCore.TryGetValue(ProcCpu.NominalFrequency, out string nominalFreqValue);
+ bool nominalFrequencyBackupHasValue = logicalCore.TryGetValue(ProcCpu.NominalFrequencyBackup, out string nominalFreqBackupValue);
+
+ double nominalCpuFreq = 0.0;
+ double nominalCpuBackupFreq = 0.0;
+
+ if (nominalFrequencyHasValue &&
+ double.TryParse(nominalFreqValue, out nominalCpuFreq)
+ && nominalCpuFreq > 0)
+ {
+ nominalCpuFreq = nominalFrequency == 0 ? nominalCpuFreq : Math.Min(nominalFrequency, nominalCpuFreq);
+ }
+ if (nominalFrequencyBackupHasValue &&
+ double.TryParse(nominalFreqBackupValue, out nominalCpuBackupFreq)
+ && nominalCpuBackupFreq > 0)
+ {
+ nominalCpuBackupFreq = nominalFrequency == 0 ? nominalCpuBackupFreq : Math.Min(nominalFrequency, nominalCpuBackupFreq);
+ }
+
+ if (nominalFrequencyHasValue && nominalFrequencyBackupHasValue)
+ {
+ nominalFrequency = Math.Min(nominalCpuFreq, nominalCpuBackupFreq);
+ }
+ else
+ {
+ nominalFrequency = nominalCpuFreq == 0.0 ? nominalCpuBackupFreq : nominalCpuFreq;
+ }
+ }
+
+ int? coresPerSocket = null;
+ if (string.IsNullOrEmpty(lscpu) == false)
+ {
+ var lscpuParts = lscpu.Split('\n')
+ .Where(line => line.Contains(':'))
+ .SelectMany(line => line.Split([':'], 2))
+ .ToList();
+ for (int i = 0; i + 1 < lscpuParts.Count; i += 2)
+ {
+ string name = lscpuParts[i].Trim();
+ string value = lscpuParts[i + 1].Trim();
+
+ if (name.EqualsWithIgnoreCase(Lscpu.MaxFrequency) &&
+ Frequency.TryParseMHz(value.Replace(',', '.'), out Frequency maxFrequencyParsed)) // Example: `CPU max MHz: 3200,0000`
+ maxFrequency = Math.Max(maxFrequency, maxFrequencyParsed.ToMHz());
+
+ if (name.EqualsWithIgnoreCase(Lscpu.ModelName))
+ processorModelNames.Add(value);
+
+ if (name.EqualsWithIgnoreCase(Lscpu.CoresPerSocket) &&
+ int.TryParse(value, out int coreCount))
+ coresPerSocket = coreCount;
+ }
+ }
+
+ string processorName = processorModelNames.Count > 0 ? string.Join(", ", processorModelNames) : null;
+ int? physicalProcessorCount = processorsToPhysicalCoreCount.Count > 0 ? processorsToPhysicalCoreCount.Count : null;
+ int? physicalCoreCount = processorsToPhysicalCoreCount.Count > 0 ? processorsToPhysicalCoreCount.Values.Sum() : coresPerSocket;
+
+ Frequency? maxFrequencyActual = maxFrequency > 0 && physicalProcessorCount > 0
+ ? Frequency.FromMHz(maxFrequency) : null;
+
+ Frequency? nominalFrequencyActual = nominalFrequency > 0 && physicalProcessorCount > 0
+ ? Frequency.FromMHz(nominalFrequency) : null;
+
+ if (nominalFrequencyActual is null)
+ {
+ bool nominalFrequencyInBrandString = processorModelNames.Any(x => ParseFrequencyFromBrandString(x) is not null);
+
+ if (nominalFrequencyInBrandString)
+ nominalFrequencyActual = processorModelNames.Select(x => ParseFrequencyFromBrandString(x))
+ .First(x => x is not null);
+ }
+
+ return new CpuInfo
+ {
+ ProcessorName = processorName,
+ PhysicalProcessorCount = physicalProcessorCount,
+ PhysicalCoreCount = physicalCoreCount,
+ LogicalCoreCount = logicalCoreCount > 0 ? logicalCoreCount : null,
+ NominalFrequencyHz = nominalFrequencyActual?.Hertz.RoundToLong(),
+ MaxFrequencyHz = maxFrequencyActual?.Hertz.RoundToLong()
+ };
+ }
+
+ internal static Frequency? ParseFrequencyFromBrandString(string brandString)
+ {
+ const string pattern = "(\\d.\\d+)GHz";
+ var matches = Regex.Matches(brandString, pattern, RegexOptions.IgnoreCase);
+ if (matches.Count > 0 && matches[0].Groups.Count > 1)
+ {
+ string match = Regex.Matches(brandString, pattern, RegexOptions.IgnoreCase)[0].Groups[1].ToString();
+ return Frequency.TryParseGHz(match, out Frequency result) ? result : null;
+ }
+
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/MosCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/MosCpuDetector.cs
new file mode 100644
index 0000000000..edc48ba25d
--- /dev/null
+++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/MosCpuDetector.cs
@@ -0,0 +1,75 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Management;
+using BenchmarkDotNet.Extensions;
+using BenchmarkDotNet.Portability;
+using Perfolizer.Horology;
+using Perfolizer.Models;
+
+namespace BenchmarkDotNet.Detectors.Cpu.Windows;
+
+internal class MosCpuDetector : ICpuDetector
+{
+#if NET6_0_OR_GREATER
+ [System.Runtime.Versioning.SupportedOSPlatform("windows")]
+#endif
+ public bool IsApplicable() => OsDetector.IsWindows() &&
+ RuntimeInformation.IsFullFramework &&
+ !RuntimeInformation.IsMono;
+
+#if NET6_0_OR_GREATER
+ [System.Runtime.Versioning.SupportedOSPlatform("windows")]
+#endif
+ public CpuInfo? Detect()
+ {
+ if (!IsApplicable()) return null;
+
+ var processorModelNames = new HashSet();
+ int physicalCoreCount = 0;
+ int logicalCoreCount = 0;
+ int processorsCount = 0;
+ double maxFrequency = 0;
+ double nominalFrequency = 0;
+
+ using (var mosProcessor = new ManagementObjectSearcher("SELECT * FROM Win32_Processor"))
+ {
+ foreach (var moProcessor in mosProcessor.Get().Cast())
+ {
+ string name = moProcessor[WmicCpuInfoKeyNames.Name]?.ToString();
+ if (!string.IsNullOrEmpty(name))
+ {
+ processorModelNames.Add(name);
+ processorsCount++;
+ physicalCoreCount += (int)(uint)moProcessor[WmicCpuInfoKeyNames.NumberOfCores];
+ logicalCoreCount += (int)(uint)moProcessor[WmicCpuInfoKeyNames.NumberOfLogicalProcessors];
+ double tempMaxFrequency = (uint)moProcessor[WmicCpuInfoKeyNames.MaxClockSpeed];
+
+ if (tempMaxFrequency > 0)
+ {
+ nominalFrequency = nominalFrequency == 0 ? tempMaxFrequency : Math.Min(nominalFrequency, tempMaxFrequency);
+ }
+ maxFrequency = Math.Max(maxFrequency, tempMaxFrequency);
+ }
+ }
+ }
+
+ string processorName = processorModelNames.Count > 0 ? string.Join(", ", processorModelNames) : null;
+ Frequency? maxFrequencyActual = maxFrequency > 0 && processorsCount > 0
+ ? Frequency.FromMHz(maxFrequency)
+ : null;
+ Frequency? nominalFrequencyActual = nominalFrequency > 0 && processorsCount > 0
+ ? Frequency.FromMHz(nominalFrequency)
+ : null;
+
+ return new CpuInfo
+ {
+ ProcessorName = processorName,
+ PhysicalProcessorCount = processorsCount > 0 ? processorsCount : null,
+ PhysicalCoreCount = physicalCoreCount > 0 ? physicalCoreCount : null,
+ LogicalCoreCount = logicalCoreCount > 0 ? logicalCoreCount : null,
+ NominalFrequencyHz = nominalFrequencyActual?.Hertz.RoundToLong(),
+ MaxFrequencyHz = maxFrequencyActual?.Hertz.RoundToLong()
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs
new file mode 100644
index 0000000000..7f61c1b8fc
--- /dev/null
+++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.Versioning;
+using System.Text.RegularExpressions;
+using BenchmarkDotNet.Helpers;
+using Perfolizer.Models;
+
+namespace BenchmarkDotNet.Detectors.Cpu.Windows;
+
+///
+/// CPU information from output of the `wmic cpu get Name, NumberOfCores, NumberOfLogicalProcessors /Format:List` command.
+/// Windows only.
+///
+internal class PowershellWmiCpuDetector : ICpuDetector
+{
+ private readonly string windowsPowershellPath =
+ $"{Environment.SystemDirectory}{Path.DirectorySeparatorChar}WindowsPowerShell{Path.DirectorySeparatorChar}" +
+ $"v1.0{Path.DirectorySeparatorChar}powershell.exe";
+
+ public bool IsApplicable() => OsDetector.IsWindows();
+
+ #if NET6_0_OR_GREATER
+ [SupportedOSPlatform("windows")]
+ #endif
+ public CpuInfo? Detect()
+ {
+ if (!IsApplicable()) return null;
+
+ const string argList = $"{WmicCpuInfoKeyNames.Name}, " +
+ $"{WmicCpuInfoKeyNames.NumberOfCores}, " +
+ $"{WmicCpuInfoKeyNames.NumberOfLogicalProcessors}, " +
+ $"{WmicCpuInfoKeyNames.MaxClockSpeed}";
+
+ string output = ProcessHelper.RunAndReadOutput(PowerShellLocator.LocateOnWindows() ?? "PowerShell",
+ "Get-CimInstance Win32_Processor -Property " + argList);
+
+ if (string.IsNullOrEmpty(output))
+ return null;
+
+ return PowershellWmiCpuInfoParser.Parse(output);
+ }
+}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs
new file mode 100644
index 0000000000..15ad4d6c77
--- /dev/null
+++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Collections.Generic;
+using BenchmarkDotNet.Extensions;
+using BenchmarkDotNet.Helpers;
+using Perfolizer.Horology;
+using Perfolizer.Models;
+
+namespace BenchmarkDotNet.Detectors.Cpu.Windows;
+
+internal static class PowershellWmiCpuInfoParser
+{
+ internal static CpuInfo Parse(string powershellWmiOutput)
+ {
+ HashSet processorModelNames = new HashSet();
+
+ int physicalCoreCount = 0;
+ int logicalCoreCount = 0;
+ int processorCount = 0;
+ double maxFrequency = 0.0;
+ double nominalFrequency = 0.0;
+
+ List> processors = SectionsHelper.ParseSectionsForPowershellWmi(powershellWmiOutput, ':');
+ foreach (Dictionary processor in processors)
+ {
+ if (processor.TryGetValue(WmicCpuInfoKeyNames.NumberOfCores, out string numberOfCoresValue) &&
+ int.TryParse(numberOfCoresValue, out int numberOfCores) &&
+ numberOfCores > 0)
+ physicalCoreCount += numberOfCores;
+
+ if (processor.TryGetValue(WmicCpuInfoKeyNames.NumberOfLogicalProcessors, out string numberOfLogicalValue) &&
+ int.TryParse(numberOfLogicalValue, out int numberOfLogical) &&
+ numberOfLogical > 0)
+ logicalCoreCount += numberOfLogical;
+
+ if (processor.TryGetValue(WmicCpuInfoKeyNames.Name, out string name))
+ {
+ processorModelNames.Add(name);
+ processorCount++;
+ }
+
+ if (processor.TryGetValue(WmicCpuInfoKeyNames.MaxClockSpeed, out string frequencyValue)
+ && double.TryParse(frequencyValue, out double frequency)
+ && frequency > 0)
+ {
+ nominalFrequency = nominalFrequency == 0 ? frequency : Math.Min(nominalFrequency, frequency);
+ maxFrequency = Math.Max(maxFrequency, frequency);
+ }
+ }
+
+ string? processorName = processorModelNames.Count > 0 ? string.Join(", ", processorModelNames) : null;
+ Frequency? maxFrequencyActual = maxFrequency > 0 && processorCount > 0
+ ? Frequency.FromMHz(maxFrequency) : null;
+
+ Frequency? nominalFrequencyActual = nominalFrequency > 0 && processorCount > 0 ?
+ Frequency.FromMHz(nominalFrequency) : null;
+
+ return new CpuInfo
+ {
+ ProcessorName = processorName,
+ PhysicalProcessorCount = processorCount > 0 ? processorCount : null,
+ PhysicalCoreCount = physicalCoreCount > 0 ? physicalCoreCount : null,
+ LogicalCoreCount = logicalCoreCount > 0 ? logicalCoreCount : null,
+ NominalFrequencyHz = nominalFrequencyActual?.Hertz.RoundToLong(),
+ MaxFrequencyHz = maxFrequencyActual?.Hertz.RoundToLong()
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WindowsCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WindowsCpuDetector.cs
new file mode 100644
index 0000000000..1de238b93f
--- /dev/null
+++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WindowsCpuDetector.cs
@@ -0,0 +1,4 @@
+namespace BenchmarkDotNet.Detectors.Cpu.Windows;
+
+internal class WindowsCpuDetector() : CpuDetector(new MosCpuDetector(), new PowershellWmiCpuDetector(),
+ new WmicCpuDetector());
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuDetector.cs
new file mode 100644
index 0000000000..fe3434ae7f
--- /dev/null
+++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuDetector.cs
@@ -0,0 +1,36 @@
+using System.IO;
+using BenchmarkDotNet.Helpers;
+using BenchmarkDotNet.Portability;
+using Perfolizer.Models;
+
+namespace BenchmarkDotNet.Detectors.Cpu.Windows;
+
+///
+/// CPU information from output of the `wmic cpu get Name, NumberOfCores, NumberOfLogicalProcessors /Format:List` command.
+/// Windows only.
+///
+/// WMIC is deprecated by Microsoft starting with Windows 10 21H1 (including Windows Server), and it is not known whether it still ships with Windows by default.
+/// WMIC may be removed in a future version of Windows. See
+internal class WmicCpuDetector : ICpuDetector
+{
+ private const string DefaultWmicPath = @"C:\Windows\System32\wbem\WMIC.exe";
+
+ public bool IsApplicable() => OsDetector.IsWindows();
+
+ public CpuInfo? Detect()
+ {
+ if (!IsApplicable()) return null;
+
+ const string argList = $"{WmicCpuInfoKeyNames.Name}, " +
+ $"{WmicCpuInfoKeyNames.NumberOfCores}, " +
+ $"{WmicCpuInfoKeyNames.NumberOfLogicalProcessors}, " +
+ $"{WmicCpuInfoKeyNames.MaxClockSpeed}";
+ string wmicPath = File.Exists(DefaultWmicPath) ? DefaultWmicPath : "wmic";
+ string? wmicOutput = ProcessHelper.RunAndReadOutput(wmicPath, $"cpu get {argList} /Format:List");
+
+ if (string.IsNullOrEmpty(wmicOutput))
+ return null;
+
+ return WmicCpuInfoParser.Parse(wmicOutput);
+ }
+}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuInfoKeyNames.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuInfoKeyNames.cs
new file mode 100644
index 0000000000..65d8ecf7d8
--- /dev/null
+++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuInfoKeyNames.cs
@@ -0,0 +1,9 @@
+namespace BenchmarkDotNet.Detectors.Cpu.Windows;
+
+internal static class WmicCpuInfoKeyNames
+{
+ internal const string NumberOfLogicalProcessors = "NumberOfLogicalProcessors";
+ internal const string NumberOfCores = "NumberOfCores";
+ internal const string Name = "Name";
+ internal const string MaxClockSpeed = "MaxClockSpeed";
+}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuInfoParser.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuInfoParser.cs
new file mode 100644
index 0000000000..295d76262e
--- /dev/null
+++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuInfoParser.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Collections.Generic;
+using BenchmarkDotNet.Extensions;
+using BenchmarkDotNet.Helpers;
+using Perfolizer.Horology;
+using Perfolizer.Models;
+
+namespace BenchmarkDotNet.Detectors.Cpu.Windows;
+
+internal static class WmicCpuInfoParser
+{
+ ///
+ /// Parses wmic output and returns
+ ///
+ /// Output of `wmic cpu get Name, NumberOfCores, NumberOfLogicalProcessors /Format:List`
+ internal static CpuInfo Parse(string wmicOutput)
+ {
+ HashSet processorModelNames = new HashSet();
+ int physicalCoreCount = 0;
+ int logicalCoreCount = 0;
+ int processorsCount = 0;
+ double maxFrequency = 0.0;
+ double nominalFrequency = 0.0;
+
+ List> processors = SectionsHelper.ParseSections(wmicOutput, '=');
+ foreach (var processor in processors)
+ {
+ if (processor.TryGetValue(WmicCpuInfoKeyNames.NumberOfCores, out string numberOfCoresValue) &&
+ int.TryParse(numberOfCoresValue, out int numberOfCores) &&
+ numberOfCores > 0)
+ physicalCoreCount += numberOfCores;
+
+ if (processor.TryGetValue(WmicCpuInfoKeyNames.NumberOfLogicalProcessors, out string numberOfLogicalValue) &&
+ int.TryParse(numberOfLogicalValue, out int numberOfLogical) &&
+ numberOfLogical > 0)
+ logicalCoreCount += numberOfLogical;
+
+ if (processor.TryGetValue(WmicCpuInfoKeyNames.Name, out string name))
+ {
+ processorModelNames.Add(name);
+ processorsCount++;
+ }
+
+ if (processor.TryGetValue(WmicCpuInfoKeyNames.MaxClockSpeed, out string frequencyValue)
+ && double.TryParse(frequencyValue, out double frequency)
+ && frequency > 0)
+ {
+ nominalFrequency = nominalFrequency == 0 ? frequency : Math.Min(nominalFrequency, frequency);
+ maxFrequency = Math.Max(maxFrequency, frequency);
+ }
+ }
+
+ string? processorName = processorModelNames.Count > 0 ? string.Join(", ", processorModelNames) : null;
+ Frequency? maxFrequencyActual = maxFrequency > 0 && processorsCount > 0
+ ? Frequency.FromMHz(maxFrequency)
+ : null;
+
+ Frequency? nominalFrequencyActual = nominalFrequency > 0 && processorsCount > 0 ? Frequency.FromMHz(nominalFrequency) : null;
+
+ return new CpuInfo
+ {
+ ProcessorName = processorName,
+ PhysicalProcessorCount = processorsCount > 0 ? processorsCount : null,
+ PhysicalCoreCount = physicalCoreCount > 0 ? physicalCoreCount : null,
+ LogicalCoreCount = logicalCoreCount > 0 ? logicalCoreCount : null,
+ NominalFrequencyHz = nominalFrequencyActual?.Hertz.RoundToLong(),
+ MaxFrequencyHz = maxFrequencyActual?.Hertz.RoundToLong()
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Detectors/Cpu/macOS/MacOsCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/macOS/MacOsCpuDetector.cs
new file mode 100644
index 0000000000..2f540e82d3
--- /dev/null
+++ b/src/BenchmarkDotNet/Detectors/Cpu/macOS/MacOsCpuDetector.cs
@@ -0,0 +1,25 @@
+using BenchmarkDotNet.Helpers;
+using Perfolizer.Models;
+
+namespace BenchmarkDotNet.Detectors.Cpu.macOS;
+
+///
+/// CPU information from output of the `sysctl -a` command.
+/// MacOSX only.
+///
+internal class MacOsCpuDetector : ICpuDetector
+{
+ public bool IsApplicable() => OsDetector.IsMacOS();
+
+ public CpuInfo? Detect()
+ {
+ if (!IsApplicable()) return null;
+
+ string? sysctlOutput = ProcessHelper.RunAndReadOutput("sysctl", "-a");
+
+ if (sysctlOutput is null)
+ return null;
+
+ return SysctlCpuInfoParser.Parse(sysctlOutput);
+ }
+}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Detectors/Cpu/macOS/SysctlCpuInfoParser.cs b/src/BenchmarkDotNet/Detectors/Cpu/macOS/SysctlCpuInfoParser.cs
new file mode 100644
index 0000000000..5cd4d11c8a
--- /dev/null
+++ b/src/BenchmarkDotNet/Detectors/Cpu/macOS/SysctlCpuInfoParser.cs
@@ -0,0 +1,60 @@
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using BenchmarkDotNet.Extensions;
+using BenchmarkDotNet.Helpers;
+using Perfolizer.Models;
+
+namespace BenchmarkDotNet.Detectors.Cpu.macOS;
+
+internal static class SysctlCpuInfoParser
+{
+ private static class Sysctl
+ {
+ internal const string ProcessorName = "machdep.cpu.brand_string";
+ internal const string PhysicalProcessorCount = "hw.packages";
+ internal const string PhysicalCoreCount = "hw.physicalcpu";
+ internal const string LogicalCoreCount = "hw.logicalcpu";
+ internal const string NominalFrequency = "hw.cpufrequency";
+ internal const string MaxFrequency = "hw.cpufrequency_max";
+ }
+
+ /// Output of `sysctl -a`
+ [SuppressMessage("ReSharper", "StringLiteralTypo")]
+ internal static CpuInfo Parse(string sysctlOutput)
+ {
+ var sysctl = SectionsHelper.ParseSection(sysctlOutput, ':');
+ string processorName = sysctl.GetValueOrDefault(Sysctl.ProcessorName);
+ int? physicalProcessorCount = PositiveIntValue(sysctl, Sysctl.PhysicalProcessorCount);
+ int? physicalCoreCount = PositiveIntValue(sysctl, Sysctl.PhysicalCoreCount);
+ int? logicalCoreCount = PositiveIntValue(sysctl, Sysctl.LogicalCoreCount);
+ long? nominalFrequency = PositiveLongValue(sysctl, Sysctl.NominalFrequency);
+ long? maxFrequency = PositiveLongValue(sysctl, Sysctl.MaxFrequency);
+ return new CpuInfo
+ {
+ ProcessorName = processorName,
+ PhysicalProcessorCount = physicalProcessorCount,
+ PhysicalCoreCount = physicalCoreCount,
+ LogicalCoreCount = logicalCoreCount,
+ NominalFrequencyHz = nominalFrequency,
+ MaxFrequencyHz = maxFrequency
+ };
+ }
+
+ private static int? PositiveIntValue(Dictionary sysctl, string keyName)
+ {
+ if (sysctl.TryGetValue(keyName, out string value) &&
+ int.TryParse(value, out int result) &&
+ result > 0)
+ return result;
+ return null;
+ }
+
+ private static long? PositiveLongValue(Dictionary sysctl, string keyName)
+ {
+ if (sysctl.TryGetValue(keyName, out string value) &&
+ long.TryParse(value, out long result) &&
+ result > 0)
+ return result;
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Detectors/CpuDetector.cs b/src/BenchmarkDotNet/Detectors/CpuDetector.cs
new file mode 100644
index 0000000000..0cb583a95a
--- /dev/null
+++ b/src/BenchmarkDotNet/Detectors/CpuDetector.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Linq;
+using BenchmarkDotNet.Detectors.Cpu;
+using BenchmarkDotNet.Detectors.Cpu.Linux;
+using BenchmarkDotNet.Detectors.Cpu.macOS;
+using BenchmarkDotNet.Detectors.Cpu.Windows;
+using BenchmarkDotNet.Extensions;
+using Perfolizer.Models;
+
+namespace BenchmarkDotNet.Detectors;
+
+public class CpuDetector(params ICpuDetector[] detectors) : ICpuDetector
+{
+ public static CpuDetector CrossPlatform => new(
+ new WindowsCpuDetector(),
+ new LinuxCpuDetector(),
+ new MacOsCpuDetector());
+
+ private static readonly Lazy LazyCpu = new(() => CrossPlatform.Detect());
+ public static CpuInfo? Cpu => LazyCpu.Value;
+
+ public bool IsApplicable() => detectors.Any(loader => loader.IsApplicable());
+
+ public CpuInfo? Detect() => detectors
+ .Where(loader => loader.IsApplicable())
+ .Select(loader => loader.Detect())
+ .WhereNotNull()
+ .FirstOrDefault();
+}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Detectors/OsDetector.cs b/src/BenchmarkDotNet/Detectors/OsDetector.cs
new file mode 100644
index 0000000000..5deeea03a6
--- /dev/null
+++ b/src/BenchmarkDotNet/Detectors/OsDetector.cs
@@ -0,0 +1,178 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using BenchmarkDotNet.Helpers;
+using Microsoft.Win32;
+using System.Runtime.InteropServices;
+using BenchmarkDotNet.Extensions;
+using Perfolizer.Models;
+using static System.Runtime.InteropServices.RuntimeInformation;
+using RuntimeEnvironment = Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment;
+
+namespace BenchmarkDotNet.Detectors;
+
+public class OsDetector
+{
+ public static readonly OsDetector Instance = new();
+ private OsDetector() { }
+
+ internal static string ExecutableExtension => IsWindows() ? ".exe" : string.Empty;
+ internal static string ScriptFileExtension => IsWindows() ? ".bat" : ".sh";
+
+ private readonly Lazy os = new(ResolveOs);
+ public static OsInfo GetOs() => Instance.os.Value;
+
+ private static OsInfo ResolveOs()
+ {
+ if (IsMacOS())
+ {
+ string systemVersion = ExternalToolsHelper.MacSystemProfilerData.Value.GetValueOrDefault("System Version") ?? "";
+ string kernelVersion = ExternalToolsHelper.MacSystemProfilerData.Value.GetValueOrDefault("Kernel Version") ?? "";
+ return new OsInfo
+ {
+ Name = "macOS",
+ Version = systemVersion,
+ KernelVersion = kernelVersion
+ };
+ }
+
+ if (IsLinux())
+ {
+ try
+ {
+ string version = LinuxOsReleaseHelper.GetNameByOsRelease(File.ReadAllLines("/etc/os-release"));
+ bool wsl = IsUnderWsl();
+ return new OsInfo
+ {
+ Name = "Linux",
+ Version = version,
+ Container = wsl ? "WSL" : null
+ };
+ }
+ catch (Exception)
+ {
+ // Ignore
+ }
+ }
+
+ string operatingSystem = RuntimeEnvironment.OperatingSystem;
+ string operatingSystemVersion = RuntimeEnvironment.OperatingSystemVersion;
+ if (IsWindows())
+ {
+ int? ubr = GetWindowsUbr();
+ if (ubr != null)
+ operatingSystemVersion += $".{ubr}";
+ }
+ return new OsInfo
+ {
+ Name = operatingSystem,
+ Version = operatingSystemVersion
+ };
+ }
+
+ private static bool IsUnderWsl()
+ {
+ if (!IsLinux())
+ return false;
+ try
+ {
+ return File.Exists("/proc/sys/fs/binfmt_misc/WSLInterop"); // https://superuser.com/a/1749811
+ }
+ catch (Exception)
+ {
+ return false;
+ }
+ }
+
+ // TODO: Introduce a common util API for registry calls, use it also in BenchmarkDotNet.Toolchains.CsProj.GetCurrentVersionBasedOnWindowsRegistry
+ ///
+ /// On Windows, this method returns UBR (Update Build Revision) based on Registry.
+ /// Returns null if the value is not available
+ ///
+ ///
+ private static int? GetWindowsUbr()
+ {
+ if (IsWindows())
+ {
+ try
+ {
+ using (var baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32))
+ using (var ndpKey = baseKey.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion"))
+ {
+ if (ndpKey == null)
+ return null;
+
+ return Convert.ToInt32(ndpKey.GetValue("UBR"));
+ }
+ }
+ catch (Exception)
+ {
+ return null;
+ }
+ }
+ return null;
+ }
+
+#if NET6_0_OR_GREATER
+ [System.Runtime.Versioning.SupportedOSPlatformGuard("windows")]
+#endif
+ internal static bool IsWindows() =>
+#if NET6_0_OR_GREATER
+ OperatingSystem.IsWindows(); // prefer linker-friendly OperatingSystem APIs
+#else
+ IsOSPlatform(OSPlatform.Windows);
+#endif
+
+#if NET6_0_OR_GREATER
+ [System.Runtime.Versioning.SupportedOSPlatformGuard("linux")]
+#endif
+ internal static bool IsLinux() =>
+#if NET6_0_OR_GREATER
+ OperatingSystem.IsLinux();
+#else
+ IsOSPlatform(OSPlatform.Linux);
+#endif
+
+#if NET6_0_OR_GREATER
+ [System.Runtime.Versioning.SupportedOSPlatformGuard("macos")]
+#endif
+ // ReSharper disable once InconsistentNaming
+ internal static bool IsMacOS() =>
+#if NET6_0_OR_GREATER
+ OperatingSystem.IsMacOS();
+#else
+ IsOSPlatform(OSPlatform.OSX);
+#endif
+
+#if NET6_0_OR_GREATER
+ [System.Runtime.Versioning.SupportedOSPlatformGuard("android")]
+#endif
+ internal static bool IsAndroid() =>
+#if NET6_0_OR_GREATER
+ OperatingSystem.IsAndroid();
+#else
+ Type.GetType("Java.Lang.Object, Mono.Android") != null;
+#endif
+
+#if NET6_0_OR_GREATER
+ [System.Runtime.Versioning.SupportedOSPlatformGuard("ios")]
+#endif
+ // ReSharper disable once InconsistentNaming
+ internal static bool IsIOS() =>
+#if NET6_0_OR_GREATER
+ OperatingSystem.IsIOS();
+#else
+ Type.GetType("Foundation.NSObject, Xamarin.iOS") != null;
+#endif
+
+#if NET6_0_OR_GREATER
+ [System.Runtime.Versioning.SupportedOSPlatformGuard("tvos")]
+#endif
+ // ReSharper disable once InconsistentNaming
+ internal static bool IsTvOS() =>
+#if NET6_0_OR_GREATER
+ OperatingSystem.IsTvOS();
+#else
+ IsOSPlatform(OSPlatform.Create("TVOS"));
+#endif
+}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Diagnosers/DiagnoserResults.cs b/src/BenchmarkDotNet/Diagnosers/DiagnoserResults.cs
index 68591ad8c8..6d7fb7b1db 100644
--- a/src/BenchmarkDotNet/Diagnosers/DiagnoserResults.cs
+++ b/src/BenchmarkDotNet/Diagnosers/DiagnoserResults.cs
@@ -2,6 +2,7 @@
using BenchmarkDotNet.Reports;
using BenchmarkDotNet.Running;
using BenchmarkDotNet.Toolchains.Results;
+using System.Collections.Generic;
using System.Linq;
namespace BenchmarkDotNet.Diagnosers
@@ -16,6 +17,7 @@ public DiagnoserResults(BenchmarkCase benchmarkCase, ExecuteResult executeResult
ThreadingStats = executeResult.ThreadingStats;
BuildResult = buildResult;
ExceptionFrequency = executeResult.ExceptionFrequency;
+ Measurements = executeResult.Measurements;
}
public BenchmarkCase BenchmarkCase { get; }
@@ -29,5 +31,7 @@ public DiagnoserResults(BenchmarkCase benchmarkCase, ExecuteResult executeResult
public double ExceptionFrequency { get; }
public BuildResult BuildResult { get; }
+
+ public IReadOnlyList Measurements { get; }
}
}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Diagnosers/DiagnosersLoader.cs b/src/BenchmarkDotNet/Diagnosers/DiagnosersLoader.cs
index 1e96f8e495..40e8f231e7 100644
--- a/src/BenchmarkDotNet/Diagnosers/DiagnosersLoader.cs
+++ b/src/BenchmarkDotNet/Diagnosers/DiagnosersLoader.cs
@@ -5,6 +5,7 @@
using System.Reflection;
using System.Threading;
using BenchmarkDotNet.Configs;
+using BenchmarkDotNet.Detectors;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Portability;
@@ -40,11 +41,11 @@ private static IEnumerable LoadDiagnosers()
{
yield return EventPipeProfiler.Default;
- if (RuntimeInformation.IsLinux())
+ if (OsDetector.IsLinux())
yield return PerfCollectProfiler.Default;
}
- if (!RuntimeInformation.IsWindows())
+ if (!OsDetector.IsWindows())
yield break;
foreach (var windowsDiagnoser in LoadWindowsDiagnosers())
diff --git a/src/BenchmarkDotNet/Diagnosers/ExceptionDiagnoser.cs b/src/BenchmarkDotNet/Diagnosers/ExceptionDiagnoser.cs
index 782e895d3e..574d1e54f1 100644
--- a/src/BenchmarkDotNet/Diagnosers/ExceptionDiagnoser.cs
+++ b/src/BenchmarkDotNet/Diagnosers/ExceptionDiagnoser.cs
@@ -1,4 +1,5 @@
using BenchmarkDotNet.Analysers;
+using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Columns;
using BenchmarkDotNet.Engines;
using BenchmarkDotNet.Exporters;
@@ -14,9 +15,11 @@ namespace BenchmarkDotNet.Diagnosers
{
public class ExceptionDiagnoser : IDiagnoser
{
- public static readonly ExceptionDiagnoser Default = new ExceptionDiagnoser();
+ public static readonly ExceptionDiagnoser Default = new ExceptionDiagnoser(new ExceptionDiagnoserConfig(displayExceptionsIfZeroValue: true));
- private ExceptionDiagnoser() { }
+ public ExceptionDiagnoser(ExceptionDiagnoserConfig config) => Config = config;
+
+ public ExceptionDiagnoserConfig Config { get; }
public IEnumerable Ids => new[] { nameof(ExceptionDiagnoser) };
@@ -32,14 +35,18 @@ public void Handle(HostSignal signal, DiagnoserActionParameters parameters) { }
public IEnumerable ProcessResults(DiagnoserResults results)
{
- yield return new Metric(ExceptionsFrequencyMetricDescriptor.Instance, results.ExceptionFrequency);
+ yield return new Metric(new ExceptionsFrequencyMetricDescriptor(Config), results.ExceptionFrequency);
}
public IEnumerable Validate(ValidationParameters validationParameters) => Enumerable.Empty();
- private class ExceptionsFrequencyMetricDescriptor : IMetricDescriptor
+ internal class ExceptionsFrequencyMetricDescriptor : IMetricDescriptor
{
- internal static readonly IMetricDescriptor Instance = new ExceptionsFrequencyMetricDescriptor();
+ public ExceptionDiagnoserConfig Config { get; }
+ public ExceptionsFrequencyMetricDescriptor(ExceptionDiagnoserConfig config = null)
+ {
+ Config = config;
+ }
public string Id => "ExceptionFrequency";
public string DisplayName => Column.Exceptions;
@@ -49,7 +56,13 @@ private class ExceptionsFrequencyMetricDescriptor : IMetricDescriptor
public string Unit => "Count";
public bool TheGreaterTheBetter => false;
public int PriorityInCategory => 0;
- public bool GetIsAvailable(Metric metric) => true;
+ public bool GetIsAvailable(Metric metric)
+ {
+ if (Config == null)
+ return metric.Value > 0;
+ else
+ return Config.DisplayExceptionsIfZeroValue || metric.Value > 0;
+ }
}
}
-}
+}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Diagnosers/PerfCollectProfiler.cs b/src/BenchmarkDotNet/Diagnosers/PerfCollectProfiler.cs
index 6dd5bdd2e9..9a3d348665 100644
--- a/src/BenchmarkDotNet/Diagnosers/PerfCollectProfiler.cs
+++ b/src/BenchmarkDotNet/Diagnosers/PerfCollectProfiler.cs
@@ -5,6 +5,7 @@
using System.Linq;
using System.Runtime.InteropServices;
using BenchmarkDotNet.Analysers;
+using BenchmarkDotNet.Detectors;
using BenchmarkDotNet.Engines;
using BenchmarkDotNet.Exporters;
using BenchmarkDotNet.Extensions;
@@ -31,8 +32,8 @@ public class PerfCollectProfiler : IProfiler
private readonly PerfCollectProfilerConfig config;
private readonly DateTime creationTime = DateTime.Now;
- private readonly Dictionary benchmarkToTraceFile = new ();
- private readonly HashSet cliPathWithSymbolsInstalled = new ();
+ private readonly Dictionary benchmarkToTraceFile = new();
+ private readonly HashSet cliPathWithSymbolsInstalled = new();
private FileInfo perfCollectFile;
private Process perfCollectProcess;
@@ -53,7 +54,7 @@ public class PerfCollectProfiler : IProfiler
public IEnumerable Validate(ValidationParameters validationParameters)
{
- if (!RuntimeInformation.IsLinux())
+ if (!OsDetector.IsLinux())
{
yield return new ValidationError(true, "The PerfCollectProfiler works only on Linux!");
yield break;
@@ -227,7 +228,7 @@ private void EnsureSymbolsForNativeRuntime(DiagnoserActionParameters parameters)
ILogger logger = parameters.Config.GetCompositeLogger();
// We install the tool in a dedicated directory in order to always use latest version and avoid issues with broken existing configs.
string toolPath = Path.Combine(Path.GetTempPath(), "BenchmarkDotNet", "symbols");
- DotNetCliCommand cliCommand = new (
+ DotNetCliCommand cliCommand = new(
cliPath: cliPath,
arguments: $"tool install dotnet-symbol --tool-path \"{toolPath}\"",
generateResult: null,
@@ -252,7 +253,7 @@ private void EnsureSymbolsForNativeRuntime(DiagnoserActionParameters parameters)
}
private FileInfo GetTraceFile(DiagnoserActionParameters parameters, string extension)
- => new (ArtifactFileNameHelper.GetTraceFilePath(parameters, creationTime, extension)
+ => new(ArtifactFileNameHelper.GetTraceFilePath(parameters, creationTime, extension)
.Replace(" ", "_")); // perfcollect does not allow for spaces in the trace file name
}
}
diff --git a/src/BenchmarkDotNet/Diagnosers/SnapshotProfilerBase.cs b/src/BenchmarkDotNet/Diagnosers/SnapshotProfilerBase.cs
new file mode 100644
index 0000000000..3105ceeeb9
--- /dev/null
+++ b/src/BenchmarkDotNet/Diagnosers/SnapshotProfilerBase.cs
@@ -0,0 +1,195 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using BenchmarkDotNet.Analysers;
+using BenchmarkDotNet.Engines;
+using BenchmarkDotNet.Exporters;
+using BenchmarkDotNet.Jobs;
+using BenchmarkDotNet.Loggers;
+using BenchmarkDotNet.Reports;
+using BenchmarkDotNet.Running;
+using BenchmarkDotNet.Validators;
+
+namespace BenchmarkDotNet.Diagnosers;
+
+public abstract class SnapshotProfilerBase : IProfiler
+{
+ public abstract string ShortName { get; }
+
+ protected abstract void InitTool(Progress progress);
+ protected abstract void AttachToCurrentProcess(string snapshotFile);
+ protected abstract void AttachToProcessByPid(int pid, string snapshotFile);
+ protected abstract void TakeSnapshot();
+ protected abstract void Detach();
+
+ protected abstract string CreateSnapshotFilePath(DiagnoserActionParameters parameters);
+ protected abstract string GetRunnerPath();
+ internal abstract bool IsSupported(RuntimeMoniker runtimeMoniker);
+
+ private readonly List snapshotFilePaths = [];
+
+ public IEnumerable Ids => [ShortName];
+ public IEnumerable Exporters => [];
+ public IEnumerable Analysers => [];
+
+ public RunMode GetRunMode(BenchmarkCase benchmarkCase) =>
+ IsSupported(benchmarkCase.Job.Environment.GetRuntime().RuntimeMoniker) ? RunMode.ExtraRun : RunMode.None;
+
+ 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:
+ Init(logger);
+ break;
+ case HostSignal.BeforeActualRun:
+ string snapshotFilePath = Start(logger, parameters);
+ snapshotFilePaths.Add(snapshotFilePath);
+ break;
+ case HostSignal.AfterActualRun:
+ Stop(logger);
+ break;
+ }
+ }
+
+ public IEnumerable 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");
+ }
+
+ public IEnumerable ProcessResults(DiagnoserResults results) => ImmutableArray.Empty;
+
+ public void DisplayResults(ILogger logger)
+ {
+ if (snapshotFilePaths.Count != 0)
+ {
+ logger.WriteLineInfo($"The following {ShortName} snapshots were generated:");
+ foreach (string snapshotFilePath in snapshotFilePaths)
+ logger.WriteLineInfo($"* {snapshotFilePath}");
+ }
+ }
+
+ private void Init(ILogger logger)
+ {
+ try
+ {
+ logger.WriteLineInfo($"Ensuring that {ShortName} prerequisite is installed...");
+ var progress = new Progress(logger, $"Installing {ShortName}");
+ InitTool(progress);
+ logger.WriteLineInfo($"{ShortName} prerequisite is installed");
+ logger.WriteLineInfo($"{ShortName} runner path: {GetRunnerPath()}");
+ }
+ catch (Exception e)
+ {
+ logger.WriteLineError(e.ToString());
+ }
+ }
+
+ private string Start(ILogger logger, DiagnoserActionParameters parameters)
+ {
+ string snapshotFilePath = CreateSnapshotFilePath(parameters);
+ string? snapshotDirectory = Path.GetDirectoryName(snapshotFilePath);
+ logger.WriteLineInfo($"Target snapshot file: {snapshotFilePath}");
+ 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 {ShortName} to the process...");
+ Attach(parameters, snapshotFilePath);
+ logger.WriteLineInfo($"{ShortName} is successfully attached");
+ }
+ catch (Exception e)
+ {
+ logger.WriteLineError(e.ToString());
+ return snapshotFilePath;
+ }
+
+ return snapshotFilePath;
+ }
+
+ private void Stop(ILogger logger)
+ {
+ try
+ {
+ logger.WriteLineInfo($"Taking {ShortName} snapshot...");
+ TakeSnapshot();
+ logger.WriteLineInfo($"{ShortName} snapshot is successfully taken");
+ }
+ catch (Exception e)
+ {
+ logger.WriteLineError(e.ToString());
+ }
+
+ try
+ {
+ logger.WriteLineInfo($"Detaching {ShortName} from the process...");
+ Detach();
+ logger.WriteLineInfo($"{ShortName} is successfully detached");
+ }
+ catch (Exception e)
+ {
+ logger.WriteLineError(e.ToString());
+ }
+ }
+
+
+ private void Attach(DiagnoserActionParameters parameters, string snapshotFile)
+ {
+ int pid = parameters.Process.Id;
+ int currentPid = Process.GetCurrentProcess().Id;
+ if (pid != currentPid)
+ AttachToProcessByPid(pid, snapshotFile);
+ else
+ AttachToCurrentProcess(snapshotFile);
+ }
+
+ protected class Progress(ILogger logger, string title) : IProgress
+ {
+ private static readonly TimeSpan ReportInterval = TimeSpan.FromSeconds(0.1);
+
+ 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();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Diagnosers/ThreadingDiagnoser.cs b/src/BenchmarkDotNet/Diagnosers/ThreadingDiagnoser.cs
index 057a0bb624..36b2f942c3 100644
--- a/src/BenchmarkDotNet/Diagnosers/ThreadingDiagnoser.cs
+++ b/src/BenchmarkDotNet/Diagnosers/ThreadingDiagnoser.cs
@@ -15,9 +15,10 @@ namespace BenchmarkDotNet.Diagnosers
{
public class ThreadingDiagnoser : IDiagnoser
{
- public static readonly ThreadingDiagnoser Default = new ThreadingDiagnoser();
+ public static readonly ThreadingDiagnoser Default = new ThreadingDiagnoser(new ThreadingDiagnoserConfig(displayCompletedWorkItemCountWhenZero: true, displayLockContentionWhenZero: true));
- private ThreadingDiagnoser() { }
+ public ThreadingDiagnoser(ThreadingDiagnoserConfig config) => Config = config;
+ public ThreadingDiagnoserConfig Config { get; }
public IEnumerable Ids => new[] { nameof(ThreadingDiagnoser) };
@@ -33,8 +34,9 @@ public void Handle(HostSignal signal, DiagnoserActionParameters parameters) { }
public IEnumerable ProcessResults(DiagnoserResults results)
{
- yield return new Metric(CompletedWorkItemCountMetricDescriptor.Instance, results.ThreadingStats.CompletedWorkItemCount / (double)results.ThreadingStats.TotalOperations);
- yield return new Metric(LockContentionCountMetricDescriptor.Instance, results.ThreadingStats.LockContentionCount / (double)results.ThreadingStats.TotalOperations);
+
+ yield return new Metric(new CompletedWorkItemCountMetricDescriptor(Config), results.ThreadingStats.CompletedWorkItemCount / (double)results.ThreadingStats.TotalOperations);
+ yield return new Metric(new LockContentionCountMetricDescriptor(Config), results.ThreadingStats.LockContentionCount / (double)results.ThreadingStats.TotalOperations);
}
public IEnumerable Validate(ValidationParameters validationParameters)
@@ -50,10 +52,15 @@ public IEnumerable Validate(ValidationParameters validationPara
}
}
- private class CompletedWorkItemCountMetricDescriptor : IMetricDescriptor
+ internal class CompletedWorkItemCountMetricDescriptor : IMetricDescriptor
{
internal static readonly IMetricDescriptor Instance = new CompletedWorkItemCountMetricDescriptor();
+ private ThreadingDiagnoserConfig Config { get; }
+ public CompletedWorkItemCountMetricDescriptor(ThreadingDiagnoserConfig config = null)
+ {
+ Config = config;
+ }
public string Id => "CompletedWorkItemCount";
public string DisplayName => Column.CompletedWorkItems;
public string Legend => "The number of work items that have been processed in ThreadPool (per single operation)";
@@ -62,13 +69,26 @@ private class CompletedWorkItemCountMetricDescriptor : IMetricDescriptor
public string Unit => "Count";
public bool TheGreaterTheBetter => false;
public int PriorityInCategory => 0;
- public bool GetIsAvailable(Metric metric) => true;
+ public bool GetIsAvailable(Metric metric)
+ {
+ if (Config == null)
+ return metric.Value > 0;
+ else
+ return Config.DisplayCompletedWorkItemCountWhenZero || metric.Value > 0;
+ }
}
- private class LockContentionCountMetricDescriptor : IMetricDescriptor
+ internal class LockContentionCountMetricDescriptor : IMetricDescriptor
{
internal static readonly IMetricDescriptor Instance = new LockContentionCountMetricDescriptor();
+ private ThreadingDiagnoserConfig Config { get; }
+
+ public LockContentionCountMetricDescriptor(ThreadingDiagnoserConfig config = null)
+ {
+ Config = config;
+ }
+
public string Id => "LockContentionCount";
public string DisplayName => Column.LockContentions;
public string Legend => "The number of times there was contention upon trying to take a Monitor's lock (per single operation)";
@@ -77,7 +97,13 @@ private class LockContentionCountMetricDescriptor : IMetricDescriptor
public string Unit => "Count";
public bool TheGreaterTheBetter => false;
public int PriorityInCategory => 0;
- public bool GetIsAvailable(Metric metric) => true;
+ public bool GetIsAvailable(Metric metric)
+ {
+ if (Config == null)
+ return metric.Value > 0;
+ else
+ return Config.DisplayLockContentionWhenZero || metric.Value > 0;
+ }
}
}
}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Diagnosers/ThreadingDiagnoserConfig.cs b/src/BenchmarkDotNet/Diagnosers/ThreadingDiagnoserConfig.cs
new file mode 100644
index 0000000000..6af3da25ac
--- /dev/null
+++ b/src/BenchmarkDotNet/Diagnosers/ThreadingDiagnoserConfig.cs
@@ -0,0 +1,23 @@
+using JetBrains.Annotations;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace BenchmarkDotNet.Diagnosers
+{
+ public class ThreadingDiagnoserConfig
+ {
+ /// Display configuration for 'LockContentionCount' when it is empty. True (displayed) by default.
+ /// Display configuration for 'CompletedWorkItemCount' when it is empty. True (displayed) by default.
+
+ [PublicAPI]
+ public ThreadingDiagnoserConfig(bool displayLockContentionWhenZero = true, bool displayCompletedWorkItemCountWhenZero = true)
+ {
+ DisplayLockContentionWhenZero = displayLockContentionWhenZero;
+ DisplayCompletedWorkItemCountWhenZero = displayCompletedWorkItemCountWhenZero;
+ }
+
+ public bool DisplayLockContentionWhenZero { get; }
+ public bool DisplayCompletedWorkItemCountWhenZero { get; }
+ }
+}
diff --git a/src/BenchmarkDotNet/Diagnosers/UnresolvedDiagnoser.cs b/src/BenchmarkDotNet/Diagnosers/UnresolvedDiagnoser.cs
index f00bb4ed67..43cc910559 100644
--- a/src/BenchmarkDotNet/Diagnosers/UnresolvedDiagnoser.cs
+++ b/src/BenchmarkDotNet/Diagnosers/UnresolvedDiagnoser.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using BenchmarkDotNet.Analysers;
+using BenchmarkDotNet.Detectors;
using BenchmarkDotNet.Engines;
using BenchmarkDotNet.Exporters;
using BenchmarkDotNet.Loggers;
@@ -31,10 +32,10 @@ public IEnumerable Validate(ValidationParameters validationPara
=> new[] { new ValidationError(false, GetErrorMessage()) };
private string GetErrorMessage() => $@"Unable to resolve {unresolved.Name} diagnoser using dynamic assembly loading.
- {(RuntimeInformation.IsFullFramework || RuntimeInformation.IsWindows()
+ {(RuntimeInformation.IsFullFramework || OsDetector.IsWindows()
? "Please make sure that you have installed the latest BenchmarkDotNet.Diagnostics.Windows package. " + Environment.NewLine
+ "If you are using `dotnet build` you also need to consume one of its public types to make sure that MSBuild copies it to the output directory. "
+ "The alternative is to use `true ` in your project file."
- : $"Please make sure that it's supported on {RuntimeInformation.GetOsVersion()}")}";
+ : $"Please make sure that it's supported on {OsDetector.GetOs()}")}";
}
}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Disassemblers/Arm64Disassembler.cs b/src/BenchmarkDotNet/Disassemblers/Arm64Disassembler.cs
index 16ebbde219..7c10765b8e 100644
--- a/src/BenchmarkDotNet/Disassemblers/Arm64Disassembler.cs
+++ b/src/BenchmarkDotNet/Disassemblers/Arm64Disassembler.cs
@@ -139,7 +139,7 @@ public void Feed(Arm64Instruction instruction)
public Arm64RegisterId RegisterId { get { return _registerId; } }
}
- internal class Arm64Disassembler : ClrMdV2Disassembler
+ internal class Arm64Disassembler : ClrMdV3Disassembler
{
internal sealed class RuntimeSpecificData
{
@@ -189,7 +189,7 @@ internal RuntimeSpecificData(State state)
}
}
- private static readonly Dictionary runtimeSpecificData = new ();
+ private static readonly Dictionary runtimeSpecificData = new();
protected override IEnumerable Decode(byte[] code, ulong startAddress, State state, int depth, ClrMethod currentMethod, DisassemblySyntax syntax)
{
diff --git a/src/BenchmarkDotNet/Disassemblers/Arm64InstructionFormatter.cs b/src/BenchmarkDotNet/Disassemblers/Arm64InstructionFormatter.cs
index deb557256c..5426dcfdd9 100644
--- a/src/BenchmarkDotNet/Disassemblers/Arm64InstructionFormatter.cs
+++ b/src/BenchmarkDotNet/Disassemblers/Arm64InstructionFormatter.cs
@@ -12,7 +12,7 @@ internal static class Arm64InstructionFormatter
internal static string Format(Arm64Asm asm, FormatterOptions formatterOptions,
bool printInstructionAddresses, uint pointerSize, IReadOnlyDictionary symbols)
{
- StringBuilder output = new ();
+ StringBuilder output = new();
Arm64Instruction instruction = asm.Instruction;
if (printInstructionAddresses)
diff --git a/src/BenchmarkDotNet/Disassemblers/ClrMdV2Disassembler.cs b/src/BenchmarkDotNet/Disassemblers/ClrMdV3Disassembler.cs
similarity index 76%
rename from src/BenchmarkDotNet/Disassemblers/ClrMdV2Disassembler.cs
rename to src/BenchmarkDotNet/Disassemblers/ClrMdV3Disassembler.cs
index 16b3891d24..6bd01426d8 100644
--- a/src/BenchmarkDotNet/Disassemblers/ClrMdV2Disassembler.cs
+++ b/src/BenchmarkDotNet/Disassemblers/ClrMdV3Disassembler.cs
@@ -7,23 +7,25 @@
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
+using BenchmarkDotNet.Detectors;
using BenchmarkDotNet.Portability;
namespace BenchmarkDotNet.Disassemblers
{
- // This Disassembler uses ClrMd v2x. Please keep it in sync with ClrMdV1Disassembler (if possible).
- internal abstract class ClrMdV2Disassembler
+ // This Disassembler uses ClrMd v3x. Please keep it in sync with ClrMdV1Disassembler (if possible).
+ internal abstract class ClrMdV3Disassembler
+
{
private static readonly ulong MinValidAddress = GetMinValidAddress();
private static ulong GetMinValidAddress()
{
// https://github.com/dotnet/BenchmarkDotNet/pull/2413#issuecomment-1688100117
- if (RuntimeInformation.IsWindows())
+ if (OsDetector.IsWindows())
return ushort.MaxValue + 1;
- if (RuntimeInformation.IsLinux())
+ if (OsDetector.IsLinux())
return (ulong) Environment.SystemPageSize;
- if (RuntimeInformation.IsMacOS())
+ if (OsDetector.IsMacOS())
return RuntimeInformation.GetCurrentPlatform() switch
{
Environments.Platform.X86 or Environments.Platform.X64 => 4096,
@@ -64,7 +66,7 @@ internal DisassemblyResult AttachAndDisassemble(Settings settings)
state.Todo.Enqueue(
new MethodInfo(
// the Disassembler Entry Method is always parameterless, so check by name is enough
- typeWithBenchmark.Methods.Single(method => method.IsPublic && method.Name == settings.MethodName),
+ typeWithBenchmark.Methods.Single(method => method.Attributes.HasFlag(System.Reflection.MethodAttributes.Public) && method.Name == settings.MethodName),
0));
}
@@ -149,9 +151,10 @@ private DisassembledMethod DisassembleMethod(MethodInfo methodInfo, State state,
if (!CanBeDisassembled(method))
{
- if (method.IsPInvoke)
+ if (method.Attributes.HasFlag(System.Reflection.MethodAttributes.PinvokeImpl))
return CreateEmpty(method, "PInvoke method");
- if (method.IL is null || method.IL.Length == 0)
+ var ilInfo = method.GetILInfo();
+ if (ilInfo is null || ilInfo.Length == 0)
return CreateEmpty(method, "Extern method");
if (method.CompilationType == MethodCompilationType.None)
return CreateEmpty(method, "Method was not JITted yet.");
@@ -214,60 +217,30 @@ private IEnumerable Decode(ILToNativeMap map, State state, int depth, ClrMe
private static ILToNativeMap[] GetCompleteNativeMap(ClrMethod method, ClrRuntime runtime)
{
- if (!TryReadNativeCodeAddresses(runtime, method, out ulong startAddress, out ulong endAddress))
+ // it's better to use one single map rather than few small ones
+ // it's simply easier to get next instruction when decoding ;)
+
+ var hotColdInfo = method.HotColdInfo;
+ if (hotColdInfo.HotSize > 0 && hotColdInfo.HotStart > 0)
{
- startAddress = method.NativeCode;
- endAddress = ulong.MaxValue;
+ return hotColdInfo.ColdSize <= 0
+ ? new[] { new ILToNativeMap() { StartAddress = hotColdInfo.HotStart, EndAddress = hotColdInfo.HotStart + hotColdInfo.HotSize, ILOffset = -1 } }
+ : new[]
+ {
+ new ILToNativeMap() { StartAddress = hotColdInfo.HotStart, EndAddress = hotColdInfo.HotStart + hotColdInfo.HotSize, ILOffset = -1 },
+ new ILToNativeMap() { StartAddress = hotColdInfo.ColdStart, EndAddress = hotColdInfo.ColdStart + hotColdInfo.ColdSize, ILOffset = -1 }
+ };
}
- ILToNativeMap[] sortedMaps = method.ILOffsetMap // CanBeDisassembled ensures that there is at least one map in ILOffsetMap
- .Where(map => map.StartAddress >= startAddress && map.StartAddress < endAddress) // can be false for Tier 0 maps, EndAddress is not checked on purpose here
- .Where(map => map.StartAddress < map.EndAddress) // some maps have 0 length (they don't have corresponding assembly code?)
+ return method.ILOffsetMap
+ .Where(map => map.StartAddress < map.EndAddress) // some maps have 0 length?
.OrderBy(map => map.StartAddress) // we need to print in the machine code order, not IL! #536
- .Select(map => new ILToNativeMap()
- {
- StartAddress = map.StartAddress,
- // some maps have EndAddress > codeHeaderData.MethodStart + codeHeaderData.MethodSize and contain garbage (#2074). They need to be fixed!
- EndAddress = Math.Min(map.EndAddress, endAddress),
- ILOffset = map.ILOffset
- })
.ToArray();
-
- if (sortedMaps.Length == 0)
- {
- // In such situation ILOffsetMap most likely describes Tier 0, while CodeHeaderData Tier 1.
- // Since we care about Tier 1 (if it's present), we "fake" a Tier 1 map.
- return new[] { new ILToNativeMap() { StartAddress = startAddress, EndAddress = endAddress } };
- }
- else if (sortedMaps[0].StartAddress != startAddress || (sortedMaps[sortedMaps.Length - 1].EndAddress != endAddress && endAddress != ulong.MaxValue))
- {
- // In such situation ILOffsetMap most likely is missing few bytes. We just "extend" it to avoid producing "bad" instructions.
- return new[] { new ILToNativeMap() { StartAddress = startAddress, EndAddress = endAddress } };
- }
-
- return sortedMaps;
}
private static DisassembledMethod CreateEmpty(ClrMethod method, string reason)
=> DisassembledMethod.Empty(method.Signature, method.NativeCode, reason);
- protected static bool TryReadNativeCodeAddresses(ClrRuntime runtime, ClrMethod method, out ulong startAddress, out ulong endAddress)
- {
- if (method is not null
- && runtime.DacLibrary.SOSDacInterface.GetCodeHeaderData(method.NativeCode, out var codeHeaderData) == HResult.S_OK
- && codeHeaderData.MethodSize > 0) // false for extern methods!
- {
- // HotSize can be missing or be invalid (https://github.com/microsoft/clrmd/issues/1036).
- // So we fetch the method size on our own.
- startAddress = codeHeaderData.MethodStart;
- endAddress = codeHeaderData.MethodStart + codeHeaderData.MethodSize;
- return true;
- }
-
- startAddress = endAddress = 0;
- return false;
- }
-
protected void TryTranslateAddressToName(ulong address, bool isAddressPrecodeMD, State state, int depth, ClrMethod currentMethod)
{
if (!IsValidAddress(address) || state.AddressToNameMapping.ContainsKey(address))
@@ -283,18 +256,10 @@ protected void TryTranslateAddressToName(ulong address, bool isAddressPrecodeMD,
}
var method = runtime.GetMethodByInstructionPointer(address);
- if (method is null && (address & ((uint) runtime.DataTarget.DataReader.PointerSize - 1)) == 0)
- {
- if (runtime.DataTarget.DataReader.ReadPointer(address, out ulong newAddress) && IsValidAddress(newAddress))
- {
- method = runtime.GetMethodByInstructionPointer(newAddress);
-
- method = WorkaroundGetMethodByInstructionPointerBug(runtime, method, newAddress);
- }
- }
- else
+ if (method is null && (address & ((uint) runtime.DataTarget.DataReader.PointerSize - 1)) == 0
+ && runtime.DataTarget.DataReader.ReadPointer(address, out ulong newAddress) && IsValidAddress(newAddress))
{
- method = WorkaroundGetMethodByInstructionPointerBug(runtime, method, address);
+ method = runtime.GetMethodByInstructionPointer(newAddress);
}
if (method is null)
@@ -313,7 +278,7 @@ protected void TryTranslateAddressToName(ulong address, bool isAddressPrecodeMD,
return;
}
- var methodTableName = runtime.DacLibrary.SOSDacInterface.GetMethodTableName(address);
+ var methodTableName = runtime.GetTypeByMethodTable(address)?.Name;
if (!string.IsNullOrEmpty(methodTableName))
{
state.AddressToNameMapping.Add(address, $"MT_{methodTableName}");
@@ -335,7 +300,7 @@ protected void TryTranslateAddressToName(ulong address, bool isAddressPrecodeMD,
protected void FlushCachedDataIfNeeded(IDataReader dataTargetDataReader, ulong address, byte[] buffer)
{
- if (!RuntimeInformation.IsWindows())
+ if (!OsDetector.IsWindows())
{
if (dataTargetDataReader.Read(address, buffer) <= 0)
{
@@ -349,13 +314,6 @@ protected void FlushCachedDataIfNeeded(IDataReader dataTargetDataReader, ulong a
}
}
- // GetMethodByInstructionPointer sometimes returns wrong methods.
- // In case given address does not belong to the methods range, null is returned.
- private static ClrMethod WorkaroundGetMethodByInstructionPointerBug(ClrRuntime runtime, ClrMethod method, ulong newAddress)
- => TryReadNativeCodeAddresses(runtime, method, out ulong startAddress, out ulong endAddress) && !(startAddress >= newAddress && newAddress <= endAddress)
- ? null
- : method;
-
private class SharpComparer : IEqualityComparer
{
public bool Equals(Sharp x, Sharp y)
diff --git a/src/BenchmarkDotNet/Disassemblers/DisassemblyDiagnoser.cs b/src/BenchmarkDotNet/Disassemblers/DisassemblyDiagnoser.cs
index 2dd18a1b61..7d0f95326c 100644
--- a/src/BenchmarkDotNet/Disassemblers/DisassemblyDiagnoser.cs
+++ b/src/BenchmarkDotNet/Disassemblers/DisassemblyDiagnoser.cs
@@ -3,6 +3,7 @@
using System.Linq;
using BenchmarkDotNet.Analysers;
using BenchmarkDotNet.Columns;
+using BenchmarkDotNet.Detectors;
using BenchmarkDotNet.Disassemblers;
using BenchmarkDotNet.Disassemblers.Exporters;
using BenchmarkDotNet.Engines;
@@ -75,7 +76,7 @@ public void Handle(HostSignal signal, DiagnoserActionParameters parameters)
case HostSignal.AfterAll when ShouldUseSameArchitectureDisassembler(benchmark, parameters):
results.Add(benchmark, sameArchitectureDisassembler.Disassemble(parameters));
break;
- case HostSignal.AfterAll when RuntimeInformation.IsWindows() && !ShouldUseMonoDisassembler(benchmark):
+ case HostSignal.AfterAll when OsDetector.IsWindows() && !ShouldUseMonoDisassembler(benchmark):
results.Add(benchmark, windowsDifferentArchitectureDisassembler.Disassemble(parameters));
break;
case HostSignal.SeparateLogic when ShouldUseMonoDisassembler(benchmark):
@@ -112,7 +113,7 @@ public IEnumerable Validate(ValidationParameters validationPara
if (ShouldUseClrMdDisassembler(benchmark))
{
- if (RuntimeInformation.IsLinux())
+ if (OsDetector.IsLinux())
{
var runtime = benchmark.Job.ResolveValue(EnvironmentMode.RuntimeCharacteristic, EnvironmentResolver.Instance);
@@ -143,13 +144,13 @@ private static bool ShouldUseMonoDisassembler(BenchmarkCase benchmarkCase)
// when we add macOS support, RuntimeInformation.IsMacOS() needs to be added here
private static bool ShouldUseClrMdDisassembler(BenchmarkCase benchmarkCase)
- => !ShouldUseMonoDisassembler(benchmarkCase) && (RuntimeInformation.IsWindows() || RuntimeInformation.IsLinux());
+ => !ShouldUseMonoDisassembler(benchmarkCase) && (OsDetector.IsWindows() || OsDetector.IsLinux());
private static bool ShouldUseSameArchitectureDisassembler(BenchmarkCase benchmarkCase, DiagnoserActionParameters parameters)
{
if (ShouldUseClrMdDisassembler(benchmarkCase))
{
- if (RuntimeInformation.IsWindows())
+ if (OsDetector.IsWindows())
{
return WindowsDisassembler.GetDisassemblerArchitecture(parameters.Process, benchmarkCase.Job.Environment.Platform)
== RuntimeInformation.GetCurrentPlatform();
diff --git a/src/BenchmarkDotNet/Disassemblers/IntelDisassembler.cs b/src/BenchmarkDotNet/Disassemblers/IntelDisassembler.cs
index f29faae037..eb0d5ce3f6 100644
--- a/src/BenchmarkDotNet/Disassemblers/IntelDisassembler.cs
+++ b/src/BenchmarkDotNet/Disassemblers/IntelDisassembler.cs
@@ -7,7 +7,7 @@
namespace BenchmarkDotNet.Disassemblers
{
- internal class IntelDisassembler : ClrMdV2Disassembler
+ internal class IntelDisassembler : ClrMdV3Disassembler
{
internal sealed class RuntimeSpecificData
{
@@ -46,7 +46,7 @@ internal RuntimeSpecificData(State state)
}
}
- private static readonly Dictionary runtimeSpecificData = new ();
+ private static readonly Dictionary runtimeSpecificData = new();
protected override IEnumerable Decode(byte[] code, ulong startAddress, State state, int depth, ClrMethod currentMethod, DisassemblySyntax syntax)
{
diff --git a/src/BenchmarkDotNet/Disassemblers/SameArchitectureDisassembler.cs b/src/BenchmarkDotNet/Disassemblers/SameArchitectureDisassembler.cs
index cf810cdd86..1a4835484d 100644
--- a/src/BenchmarkDotNet/Disassemblers/SameArchitectureDisassembler.cs
+++ b/src/BenchmarkDotNet/Disassemblers/SameArchitectureDisassembler.cs
@@ -8,16 +8,16 @@ namespace BenchmarkDotNet.Disassemblers
internal class SameArchitectureDisassembler
{
private readonly DisassemblyDiagnoserConfig config;
- private ClrMdV2Disassembler? clrMdV2Disassembler;
+ private ClrMdV3Disassembler? clrMdV3Disassembler;
internal SameArchitectureDisassembler(DisassemblyDiagnoserConfig config) => this.config = config;
internal DisassemblyResult Disassemble(DiagnoserActionParameters parameters)
// delay the creation to avoid exceptions at DisassemblyDiagnoser ctor
- => (clrMdV2Disassembler ??= CreateDisassemblerForCurrentArchitecture())
+ => (clrMdV3Disassembler ??= CreateDisassemblerForCurrentArchitecture())
.AttachAndDisassemble(BuildDisassemblerSettings(parameters));
- private static ClrMdV2Disassembler CreateDisassemblerForCurrentArchitecture()
+ private static ClrMdV3Disassembler CreateDisassemblerForCurrentArchitecture()
=> RuntimeInformation.GetCurrentPlatform() switch
{
Platform.X86 or Platform.X64 => new IntelDisassembler(),
@@ -26,7 +26,7 @@ private static ClrMdV2Disassembler CreateDisassemblerForCurrentArchitecture()
};
private Settings BuildDisassemblerSettings(DiagnoserActionParameters parameters)
- => new (
+ => new(
processId: parameters.Process.Id,
typeName: $"BenchmarkDotNet.Autogenerated.Runnable_{parameters.BenchmarkId.Value}",
methodName: DisassemblerConstants.DisassemblerEntryMethodName,
diff --git a/src/BenchmarkDotNet/Disassemblers/WindowsDisassembler.cs b/src/BenchmarkDotNet/Disassemblers/WindowsDisassembler.cs
index 6bec70162a..8051ba3463 100644
--- a/src/BenchmarkDotNet/Disassemblers/WindowsDisassembler.cs
+++ b/src/BenchmarkDotNet/Disassemblers/WindowsDisassembler.cs
@@ -7,6 +7,7 @@
using System.Text;
using System.Xml;
using System.Xml.Serialization;
+using BenchmarkDotNet.Detectors;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Environments;
using BenchmarkDotNet.Extensions;
@@ -165,7 +166,7 @@ public static bool Is64Bit(Process process)
if (Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE") == "x86")
return false;
- if (RuntimeInformation.IsWindows())
+ if (OsDetector.IsWindows())
{
IsWow64Process(process.Handle, out bool isWow64);
diff --git a/src/BenchmarkDotNet/Engines/Engine.cs b/src/BenchmarkDotNet/Engines/Engine.cs
index 9dc8cc616e..1fc1b96dfd 100644
--- a/src/BenchmarkDotNet/Engines/Engine.cs
+++ b/src/BenchmarkDotNet/Engines/Engine.cs
@@ -1,10 +1,14 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Runtime.CompilerServices;
+using System.Threading;
using BenchmarkDotNet.Characteristics;
+using BenchmarkDotNet.Environments;
using BenchmarkDotNet.Jobs;
+using BenchmarkDotNet.Mathematics;
using BenchmarkDotNet.Portability;
using BenchmarkDotNet.Reports;
using JetBrains.Annotations;
@@ -15,8 +19,6 @@ namespace BenchmarkDotNet.Engines
[UsedImplicitly]
public class Engine : IEngine
{
- public const int MinInvokeCount = 4;
-
[PublicAPI] public IHost Host { get; }
[PublicAPI] public Action WorkloadAction { get; }
[PublicAPI] public Action Dummy1Action { get; }
@@ -40,10 +42,7 @@ public class Engine : IEngine
private bool EvaluateOverhead { get; }
private bool MemoryRandomization { get; }
- private readonly List jittingMeasurements = new (10);
- private readonly EnginePilotStage pilotStage;
- private readonly EngineWarmupStage warmupStage;
- private readonly EngineActualStage actualStage;
+ private readonly List jittingMeasurements = new(10);
private readonly bool includeExtraStats;
private readonly Random random;
@@ -79,10 +78,6 @@ internal Engine(
EvaluateOverhead = targetJob.ResolveValue(AccuracyMode.EvaluateOverheadCharacteristic, Resolver);
MemoryRandomization = targetJob.ResolveValue(RunMode.MemoryRandomizationCharacteristic, Resolver);
- warmupStage = new EngineWarmupStage(this);
- pilotStage = new EnginePilotStage(this);
- actualStage = new EngineActualStage(this);
-
random = new Random(12345); // we are using constant seed to try to get repeatable results
}
@@ -102,6 +97,9 @@ public void Dispose()
}
}
+ // AggressiveOptimization forces the method to go straight to tier1 JIT, and will never be re-jitted,
+ // eliminating tiered JIT as a potential variable in measurements.
+ [MethodImpl(CodeGenHelper.AggressiveOptimizationOption)]
public RunResults Run()
{
var measurements = new List();
@@ -112,29 +110,33 @@ public RunResults Run()
if (EngineEventSource.Log.IsEnabled())
EngineEventSource.Log.BenchmarkStart(BenchmarkName);
- if (Strategy != RunStrategy.ColdStart)
+ // Enumerate the stages and run iterations in a loop to ensure each benchmark invocation is called with a constant stack size.
+ // #1120
+ foreach (var stage in EngineStage.EnumerateStages(this, Strategy, EvaluateOverhead))
{
- if (Strategy != RunStrategy.Monitoring)
+ if (stage.Stage == IterationStage.Actual && stage.Mode == IterationMode.Workload)
{
- var pilotStageResult = pilotStage.Run();
- invokeCount = pilotStageResult.PerfectInvocationCount;
- measurements.AddRange(pilotStageResult.Measurements);
-
- if (EvaluateOverhead)
- {
- measurements.AddRange(warmupStage.RunOverhead(invokeCount, UnrollFactor));
- measurements.AddRange(actualStage.RunOverhead(invokeCount, UnrollFactor));
- }
+ Host.BeforeMainRun();
}
- measurements.AddRange(warmupStage.RunWorkload(invokeCount, UnrollFactor, Strategy));
- }
-
- Host.BeforeMainRun();
+ var stageMeasurements = stage.GetMeasurementList();
+ // 1-based iterationIndex
+ int iterationIndex = 1;
+ while (stage.GetShouldRunIteration(stageMeasurements, ref invokeCount))
+ {
+ var measurement = RunIteration(new IterationData(stage.Mode, stage.Stage, iterationIndex, invokeCount, UnrollFactor));
+ stageMeasurements.Add(measurement);
+ ++iterationIndex;
+ }
+ measurements.AddRange(stageMeasurements);
- measurements.AddRange(actualStage.RunWorkload(invokeCount, UnrollFactor, forceSpecific: Strategy == RunStrategy.Monitoring));
+ WriteLine();
- Host.AfterMainRun();
+ if (stage.Stage == IterationStage.Actual && stage.Mode == IterationMode.Workload)
+ {
+ Host.AfterMainRun();
+ }
+ }
(GcStats workGcHasDone, ThreadingStats threadingStats, double exceptionFrequency) = includeExtraStats
? GetExtraStats(new IterationData(IterationMode.Workload, IterationStage.Actual, 0, invokeCount, UnrollFactor))
@@ -148,11 +150,15 @@ public RunResults Run()
return new RunResults(measurements, outlierMode, workGcHasDone, threadingStats, exceptionFrequency);
}
+ [MethodImpl(CodeGenHelper.AggressiveOptimizationOption)]
public Measurement RunIteration(IterationData data)
{
// Initialization
long invokeCount = data.InvokeCount;
int unrollFactor = data.UnrollFactor;
+ if (invokeCount % unrollFactor != 0)
+ throw new ArgumentOutOfRangeException(nameof(data), $"InvokeCount({invokeCount}) should be a multiple of UnrollFactor({unrollFactor}).");
+
long totalOperations = invokeCount * OperationsPerInvoke;
bool isOverhead = data.IterationMode == IterationMode.Overhead;
bool randomizeMemory = !isOverhead && MemoryRandomization;
@@ -167,7 +173,7 @@ public Measurement RunIteration(IterationData data)
EngineEventSource.Log.IterationStart(data.IterationMode, data.IterationStage, totalOperations);
var clockSpan = randomizeMemory
- ? MeasureWithRandomMemory(action, invokeCount / unrollFactor)
+ ? MeasureWithRandomStack(action, invokeCount / unrollFactor)
: Measure(action, invokeCount / unrollFactor);
if (EngineEventSource.Log.IsEnabled())
@@ -193,8 +199,8 @@ public Measurement RunIteration(IterationData data)
// This is in a separate method, because stackalloc can affect code alignment,
// resulting in unexpected measurements on some AMD cpus,
// even if the stackalloc branch isn't executed. (#2366)
- [MethodImpl(MethodImplOptions.NoInlining)]
- private unsafe ClockSpan MeasureWithRandomMemory(Action action, long invokeCount)
+ [MethodImpl(MethodImplOptions.NoInlining | CodeGenHelper.AggressiveOptimizationOption)]
+ private unsafe ClockSpan MeasureWithRandomStack(Action action, long invokeCount)
{
byte* stackMemory = stackalloc byte[random.Next(32)];
var clockSpan = Measure(action, invokeCount);
@@ -205,6 +211,7 @@ private unsafe ClockSpan MeasureWithRandomMemory(Action action, long invok
[MethodImpl(MethodImplOptions.NoInlining)]
private unsafe void Consume(byte* _) { }
+ [MethodImpl(MethodImplOptions.NoInlining | CodeGenHelper.AggressiveOptimizationOption)]
private ClockSpan Measure(Action action, long invokeCount)
{
var clock = Clock.Start();
@@ -214,31 +221,56 @@ private ClockSpan Measure(Action action, long invokeCount)
private (GcStats, ThreadingStats, double) GetExtraStats(IterationData data)
{
- // we enable monitoring after main target run, for this single iteration which is executed at the end
- // so even if we enable AppDomain monitoring in separate process
- // it does not matter, because we have already obtained the results!
- EnableMonitoring();
+ // Warm up the measurement functions before starting the actual measurement.
+ DeadCodeEliminationHelper.KeepAliveWithoutBoxing(GcStats.ReadInitial());
+ DeadCodeEliminationHelper.KeepAliveWithoutBoxing(GcStats.ReadFinal());
IterationSetupAction(); // we run iteration setup first, so even if it allocates, it is not included in the results
var initialThreadingStats = ThreadingStats.ReadInitial(); // this method might allocate
var exceptionsStats = new ExceptionsStats(); // allocates
exceptionsStats.StartListening(); // this method might allocate
- var initialGcStats = GcStats.ReadInitial();
- WorkloadAction(data.InvokeCount / data.UnrollFactor);
+#if !NET7_0_OR_GREATER
+ if (RuntimeInformation.IsNetCore && Environment.Version.Major is >= 3 and <= 6 && RuntimeInformation.IsTieredJitEnabled)
+ {
+ // #1542
+ // We put the current thread to sleep so tiered jit can kick in, compile its stuff,
+ // and NOT allocate anything on the background thread when we are measuring allocations.
+ // This is only an issue on netcoreapp3.0 to net6.0. Tiered jit allocations were "fixed" in net7.0
+ // (maybe not completely eliminated forever, but at least reduced to a point where measurements are much more stable),
+ // and netcoreapp2.X uses only GetAllocatedBytesForCurrentThread which doesn't capture the tiered jit allocations.
+ Thread.Sleep(TimeSpan.FromMilliseconds(500));
+ }
+#endif
- exceptionsStats.Stop();
- var finalGcStats = GcStats.ReadFinal();
+ // GC collect before measuring allocations.
+ ForceGcCollect();
+ GcStats gcStats;
+ using (FinalizerBlocker.MaybeStart())
+ {
+ gcStats = MeasureWithGc(data.InvokeCount / data.UnrollFactor);
+ }
+
+ exceptionsStats.Stop(); // this method might (de)allocate
var finalThreadingStats = ThreadingStats.ReadFinal();
IterationCleanupAction(); // we run iteration cleanup after collecting GC stats
var totalOperationsCount = data.InvokeCount * OperationsPerInvoke;
- GcStats gcStats = (finalGcStats - initialGcStats).WithTotalOperations(totalOperationsCount);
- ThreadingStats threadingStats = (finalThreadingStats - initialThreadingStats).WithTotalOperations(data.InvokeCount * OperationsPerInvoke);
+ return (gcStats.WithTotalOperations(totalOperationsCount),
+ (finalThreadingStats - initialThreadingStats).WithTotalOperations(totalOperationsCount),
+ exceptionsStats.ExceptionsCount / (double)totalOperationsCount);
+ }
- return (gcStats, threadingStats, exceptionsStats.ExceptionsCount / (double)totalOperationsCount);
+ // Isolate the allocation measurement and skip tier0 jit to make sure we don't get any unexpected allocations.
+ [MethodImpl(MethodImplOptions.NoInlining | CodeGenHelper.AggressiveOptimizationOption)]
+ private GcStats MeasureWithGc(long invokeCount)
+ {
+ var initialGcStats = GcStats.ReadInitial();
+ WorkloadAction(invokeCount);
+ var finalGcStats = GcStats.ReadFinal();
+ return finalGcStats - initialGcStats;
}
private void RandomizeManagedHeapMemory()
@@ -267,7 +299,7 @@ private void GcCollect()
ForceGcCollect();
}
- private static void ForceGcCollect()
+ internal static void ForceGcCollect()
{
GC.Collect();
GC.WaitForPendingFinalizers();
@@ -278,15 +310,6 @@ private static void ForceGcCollect()
public void WriteLine() => Host.WriteLine();
- private static void EnableMonitoring()
- {
- if (RuntimeInformation.IsOldMono) // Monitoring is not available in Mono, see http://stackoverflow.com/questions/40234948/how-to-get-the-number-of-allocated-bytes-in-mono
- return;
-
- if (RuntimeInformation.IsFullFramework)
- AppDomain.MonitoringIsEnabled = true;
- }
-
[UsedImplicitly]
public static class Signals
{
@@ -309,5 +332,71 @@ private static readonly Dictionary MessagesToSignals
public static bool TryGetSignal(string message, out HostSignal signal)
=> MessagesToSignals.TryGetValue(message, out signal);
}
+
+ // Very long key and value so this shouldn't be used outside of unit tests.
+ internal const string UnitTestBlockFinalizerEnvKey = "BENCHMARKDOTNET_UNITTEST_BLOCK_FINALIZER_FOR_MEMORYDIAGNOSER";
+ internal const string UnitTestBlockFinalizerEnvValue = UnitTestBlockFinalizerEnvKey + "_ACTIVE";
+
+ // To prevent finalizers interfering with allocation measurements for unit tests,
+ // we block the finalizer thread until we've completed the measurement.
+ // https://github.com/dotnet/runtime/issues/101536#issuecomment-2077647417
+ private readonly struct FinalizerBlocker : IDisposable
+ {
+ private readonly object hangLock;
+
+ private FinalizerBlocker(object hangLock) => this.hangLock = hangLock;
+
+ private sealed class Impl
+ {
+ // ManualResetEvent(Slim) allocates when it is waited and yields the thread,
+ // so we use Monitor.Wait instead which does not allocate managed memory.
+ // This behavior is not documented, but was observed with the VS Profiler.
+ private readonly object hangLock = new();
+ private readonly ManualResetEventSlim enteredFinalizerEvent = new(false);
+
+ ~Impl()
+ {
+ lock (hangLock)
+ {
+ enteredFinalizerEvent.Set();
+ Monitor.Wait(hangLock);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ internal static (object hangLock, ManualResetEventSlim enteredFinalizerEvent) CreateWeakly()
+ {
+ var impl = new Impl();
+ return (impl.hangLock, impl.enteredFinalizerEvent);
+ }
+ }
+
+ internal static FinalizerBlocker MaybeStart()
+ {
+ if (Environment.GetEnvironmentVariable(UnitTestBlockFinalizerEnvKey) != UnitTestBlockFinalizerEnvValue)
+ {
+ return default;
+ }
+ var (hangLock, enteredFinalizerEvent) = Impl.CreateWeakly();
+ do
+ {
+ GC.Collect();
+ // Do NOT call GC.WaitForPendingFinalizers.
+ }
+ while (!enteredFinalizerEvent.IsSet);
+ return new FinalizerBlocker(hangLock);
+ }
+
+ public void Dispose()
+ {
+ if (hangLock is not null)
+ {
+ lock (hangLock)
+ {
+ Monitor.Pulse(hangLock);
+ }
+ }
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Engines/EngineActualStage.cs b/src/BenchmarkDotNet/Engines/EngineActualStage.cs
new file mode 100644
index 0000000000..173f4e3432
--- /dev/null
+++ b/src/BenchmarkDotNet/Engines/EngineActualStage.cs
@@ -0,0 +1,96 @@
+using System;
+using System.Collections.Generic;
+using BenchmarkDotNet.Characteristics;
+using BenchmarkDotNet.Jobs;
+using BenchmarkDotNet.Mathematics;
+using BenchmarkDotNet.Reports;
+using Perfolizer.Horology;
+using Perfolizer.Mathematics.OutlierDetection;
+
+namespace BenchmarkDotNet.Engines
+{
+ internal abstract class EngineActualStage(IterationMode iterationMode) : EngineStage(IterationStage.Actual, iterationMode)
+ {
+ internal const int MaxOverheadIterationCount = 20;
+
+ internal static EngineActualStage GetOverhead(IEngine engine)
+ => new EngineActualStageAuto(engine.TargetJob, engine.Resolver, IterationMode.Overhead);
+
+ internal static EngineActualStage GetWorkload(IEngine engine, RunStrategy strategy)
+ {
+ var targetJob = engine.TargetJob;
+ int? iterationCount = targetJob.ResolveValueAsNullable(RunMode.IterationCountCharacteristic);
+ const int DefaultWorkloadCount = 10;
+ return iterationCount == null && strategy != RunStrategy.Monitoring
+ ? new EngineActualStageAuto(targetJob, engine.Resolver, IterationMode.Workload)
+ : new EngineActualStageSpecific(iterationCount ?? DefaultWorkloadCount, IterationMode.Workload);
+ }
+ }
+
+ internal sealed class EngineActualStageAuto : EngineActualStage
+ {
+ private readonly double maxRelativeError;
+ private readonly TimeInterval? maxAbsoluteError;
+ private readonly OutlierMode outlierMode;
+ private readonly int minIterationCount;
+ private readonly int maxIterationCount;
+ private readonly List measurementsForStatistics;
+ private int iterationCounter = 0;
+
+ public EngineActualStageAuto(Job targetJob, IResolver resolver, IterationMode iterationMode) : base(iterationMode)
+ {
+ maxRelativeError = targetJob.ResolveValue(AccuracyMode.MaxRelativeErrorCharacteristic, resolver);
+ maxAbsoluteError = targetJob.ResolveValueAsNullable(AccuracyMode.MaxAbsoluteErrorCharacteristic);
+ outlierMode = targetJob.ResolveValue(AccuracyMode.OutlierModeCharacteristic, resolver);
+ minIterationCount = targetJob.ResolveValue(RunMode.MinIterationCountCharacteristic, resolver);
+ maxIterationCount = targetJob.ResolveValue(RunMode.MaxIterationCountCharacteristic, resolver);
+ measurementsForStatistics = GetMeasurementList();
+ }
+
+ internal override List GetMeasurementList() => new(maxIterationCount);
+
+ internal override bool GetShouldRunIteration(List measurements, ref long invokeCount)
+ {
+ if (measurements.Count == 0)
+ {
+ return true;
+ }
+
+ const double MaxOverheadRelativeError = 0.05;
+ bool isOverhead = Mode == IterationMode.Overhead;
+ double effectiveMaxRelativeError = isOverhead ? MaxOverheadRelativeError : maxRelativeError;
+ iterationCounter++;
+ var measurement = measurements[measurements.Count - 1];
+ measurementsForStatistics.Add(measurement);
+
+ var statistics = MeasurementsStatistics.Calculate(measurementsForStatistics, outlierMode);
+ double actualError = statistics.LegacyConfidenceInterval.Margin;
+
+ double maxError1 = effectiveMaxRelativeError * statistics.Mean;
+ double maxError2 = maxAbsoluteError?.Nanoseconds ?? double.MaxValue;
+ double maxError = Math.Min(maxError1, maxError2);
+
+ if (iterationCounter >= minIterationCount && actualError < maxError)
+ {
+ return false;
+ }
+
+ if (iterationCounter >= maxIterationCount || isOverhead && iterationCounter >= MaxOverheadIterationCount)
+ {
+ return false;
+ }
+
+ return true;
+ }
+ }
+
+ internal sealed class EngineActualStageSpecific(int maxIterationCount, IterationMode iterationMode) : EngineActualStage(iterationMode)
+ {
+ private int iterationCount = 0;
+
+ internal override List GetMeasurementList() => new(maxIterationCount);
+
+ internal override bool GetShouldRunIteration(List measurements, ref long invokeCount)
+ => ++iterationCount <= maxIterationCount;
+ }
+}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Engines/EngineGeneralStage.cs b/src/BenchmarkDotNet/Engines/EngineGeneralStage.cs
deleted file mode 100644
index 6394db3d1e..0000000000
--- a/src/BenchmarkDotNet/Engines/EngineGeneralStage.cs
+++ /dev/null
@@ -1,90 +0,0 @@
-using System;
-using System.Collections.Generic;
-using BenchmarkDotNet.Jobs;
-using BenchmarkDotNet.Mathematics;
-using BenchmarkDotNet.Reports;
-using Perfolizer.Horology;
-using Perfolizer.Mathematics.OutlierDetection;
-
-namespace BenchmarkDotNet.Engines
-{
- public class EngineActualStage : EngineStage
- {
- internal const int MaxOverheadIterationCount = 20;
- private const double MaxOverheadRelativeError = 0.05;
- private const int DefaultWorkloadCount = 10;
-
- private readonly int? iterationCount;
- private readonly double maxRelativeError;
- private readonly TimeInterval? maxAbsoluteError;
- private readonly OutlierMode outlierMode;
- private readonly int minIterationCount;
- private readonly int maxIterationCount;
-
- public EngineActualStage(IEngine engine) : base(engine)
- {
- iterationCount = engine.TargetJob.ResolveValueAsNullable(RunMode.IterationCountCharacteristic);
- maxRelativeError = engine.TargetJob.ResolveValue(AccuracyMode.MaxRelativeErrorCharacteristic, engine.Resolver);
- maxAbsoluteError = engine.TargetJob.ResolveValueAsNullable(AccuracyMode.MaxAbsoluteErrorCharacteristic);
- outlierMode = engine.TargetJob.ResolveValue(AccuracyMode.OutlierModeCharacteristic, engine.Resolver);
- minIterationCount = engine.TargetJob.ResolveValue(RunMode.MinIterationCountCharacteristic, engine.Resolver);
- maxIterationCount = engine.TargetJob.ResolveValue(RunMode.MaxIterationCountCharacteristic, engine.Resolver);
- }
-
- public IReadOnlyList RunOverhead(long invokeCount, int unrollFactor)
- => RunAuto(invokeCount, IterationMode.Overhead, unrollFactor);
-
- public IReadOnlyList RunWorkload(long invokeCount, int unrollFactor, bool forceSpecific = false)
- => Run(invokeCount, IterationMode.Workload, false, unrollFactor, forceSpecific);
-
- internal IReadOnlyList Run(long invokeCount, IterationMode iterationMode, bool runAuto, int unrollFactor, bool forceSpecific = false)
- => (runAuto || iterationCount == null) && !forceSpecific
- ? RunAuto(invokeCount, iterationMode, unrollFactor)
- : RunSpecific(invokeCount, iterationMode, iterationCount ?? DefaultWorkloadCount, unrollFactor);
-
- private List RunAuto(long invokeCount, IterationMode iterationMode, int unrollFactor)
- {
- var measurements = new List(maxIterationCount);
- var measurementsForStatistics = new List(maxIterationCount);
-
- int iterationCounter = 0;
- bool isOverhead = iterationMode == IterationMode.Overhead;
- double effectiveMaxRelativeError = isOverhead ? MaxOverheadRelativeError : maxRelativeError;
- while (true)
- {
- iterationCounter++;
- var measurement = RunIteration(iterationMode, IterationStage.Actual, iterationCounter, invokeCount, unrollFactor);
- measurements.Add(measurement);
- measurementsForStatistics.Add(measurement);
-
- var statistics = MeasurementsStatistics.Calculate(measurementsForStatistics, outlierMode);
- double actualError = statistics.LegacyConfidenceInterval.Margin;
-
- double maxError1 = effectiveMaxRelativeError * statistics.Mean;
- double maxError2 = maxAbsoluteError?.Nanoseconds ?? double.MaxValue;
- double maxError = Math.Min(maxError1, maxError2);
-
- if (iterationCounter >= minIterationCount && actualError < maxError)
- break;
-
- if (iterationCounter >= maxIterationCount || isOverhead && iterationCounter >= MaxOverheadIterationCount)
- break;
- }
- WriteLine();
-
- return measurements;
- }
-
- private List RunSpecific(long invokeCount, IterationMode iterationMode, int iterationCount, int unrollFactor)
- {
- var measurements = new List(iterationCount);
-
- for (int i = 0; i < iterationCount; i++)
- measurements.Add(RunIteration(iterationMode, IterationStage.Actual, i + 1, invokeCount, unrollFactor));
-
- WriteLine();
-
- return measurements;
- }
- }
-}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Engines/EnginePilotStage.cs b/src/BenchmarkDotNet/Engines/EnginePilotStage.cs
index 6adf6d964f..43b543d5c1 100644
--- a/src/BenchmarkDotNet/Engines/EnginePilotStage.cs
+++ b/src/BenchmarkDotNet/Engines/EnginePilotStage.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using BenchmarkDotNet.Characteristics;
using BenchmarkDotNet.Environments;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Reports;
@@ -8,130 +9,105 @@
namespace BenchmarkDotNet.Engines
{
// TODO: use clockResolution
- internal class EnginePilotStage : EngineStage
+ internal abstract class EnginePilotStage(Job targetJob, IResolver resolver) : EngineStage(IterationStage.Pilot, IterationMode.Workload)
{
- public readonly struct PilotStageResult
- {
- public long PerfectInvocationCount { get; }
- public IReadOnlyList Measurements { get; }
+ internal const long MaxInvokeCount = (long.MaxValue / 2 + 1) / 2;
- public PilotStageResult(long perfectInvocationCount, List measurements)
- {
- PerfectInvocationCount = perfectInvocationCount;
- Measurements = measurements;
- }
+ protected readonly int unrollFactor = targetJob.ResolveValue(RunMode.UnrollFactorCharacteristic, resolver);
+ protected readonly int minInvokeCount = targetJob.ResolveValue(AccuracyMode.MinInvokeCountCharacteristic, resolver);
- public PilotStageResult(long perfectInvocationCount)
- {
- PerfectInvocationCount = perfectInvocationCount;
- Measurements = Array.Empty();
- }
+ protected long Autocorrect(long count) => (count + unrollFactor - 1) / unrollFactor * unrollFactor;
+
+ internal static EnginePilotStage GetStage(IEngine engine)
+ {
+ var targetJob = engine.TargetJob;
+ // If InvocationCount is specified, pilot stage should be skipped
+ return targetJob.HasValue(RunMode.InvocationCountCharacteristic) ? null
+ // Here we want to guess "perfect" amount of invocation
+ : targetJob.HasValue(RunMode.IterationTimeCharacteristic) ? new EnginePilotStageSpecific(targetJob, engine.Resolver)
+ : new EnginePilotStageAuto(targetJob, engine.Resolver);
}
+ }
- internal const long MaxInvokeCount = (long.MaxValue / 2 + 1) / 2;
+ internal sealed class EnginePilotStageAuto(Job targetJob, IResolver resolver) : EnginePilotStage(targetJob, resolver)
+ {
+ private readonly TimeInterval minIterationTime = targetJob.ResolveValue(AccuracyMode.MinIterationTimeCharacteristic, resolver);
+ private readonly double maxRelativeError = targetJob.ResolveValue(AccuracyMode.MaxRelativeErrorCharacteristic, resolver);
+ private readonly TimeInterval? maxAbsoluteError = targetJob.ResolveValueAsNullable(AccuracyMode.MaxAbsoluteErrorCharacteristic);
+ private readonly double resolution = targetJob.ResolveValue(InfrastructureMode.ClockCharacteristic, resolver).GetResolution().Nanoseconds;
- private readonly int unrollFactor;
- private readonly TimeInterval minIterationTime;
- private readonly int minInvokeCount;
- private readonly double maxRelativeError;
- private readonly TimeInterval? maxAbsoluteError;
- private readonly double targetIterationTime;
- private readonly double resolution;
+ internal override List