diff --git a/.gitignore b/.gitignore
index fb19bcffa..cbf0016dd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -89,3 +89,6 @@ StyleCop.Cache
# Ignore SelfSignedCertificate autogenerated files
test/tools/Modules/SelfSignedCertificate/
+
+# BenchmarkDotNet artifacts
+test/perf/BenchmarkDotNet.Artifacts/
diff --git a/.spelling b/.spelling
index cd6170b1a..d512585a6 100644
--- a/.spelling
+++ b/.spelling
@@ -1310,3 +1310,6 @@ codesign
release-BuildJson
yml
centos-7
+ - test/perf/benchmarks/README.md
+benchmarked
+BenchmarkDotNet
diff --git a/.vsts-ci/linux.yml b/.vsts-ci/linux.yml
index b9dcf05eb..bed0c4fd2 100644
--- a/.vsts-ci/linux.yml
+++ b/.vsts-ci/linux.yml
@@ -14,6 +14,7 @@ trigger:
- /.vsts-ci/misc-analysis.yml
- /.github/ISSUE_TEMPLATE/*
- /.dependabot/config.yml
+ - test/perf/*
pr:
branches:
include:
@@ -30,6 +31,7 @@ pr:
- .vsts-ci/windows.yml
- .vsts-ci/windows/*
- test/common/markdown/*
+ - test/perf/*
- tools/releaseBuild/*
- tools/releaseBuild/azureDevOps/templates/*
diff --git a/.vsts-ci/mac.yml b/.vsts-ci/mac.yml
index 6b050122f..3cd35335b 100644
--- a/.vsts-ci/mac.yml
+++ b/.vsts-ci/mac.yml
@@ -15,6 +15,7 @@ trigger:
- /.vsts-ci/misc-analysis.yml
- /.github/ISSUE_TEMPLATE/*
- /.dependabot/config.yml
+ - test/perf/*
pr:
branches:
include:
@@ -31,6 +32,7 @@ pr:
- /.vsts-ci/windows.yml
- /.vsts-ci/windows/*
- test/common/markdown/*
+ - test/perf/*
- tools/packaging/*
- tools/releaseBuild/*
- tools/releaseBuild/azureDevOps/templates/*
diff --git a/.vsts-ci/misc-analysis.yml b/.vsts-ci/misc-analysis.yml
index 8c81c6042..d760a6e49 100644
--- a/.vsts-ci/misc-analysis.yml
+++ b/.vsts-ci/misc-analysis.yml
@@ -85,7 +85,7 @@ jobs:
condition: succeededOrFailed()
- bash: |
- mdspell '**/*.md' '!**/Pester/**/*.md' --ignore-numbers --ignore-acronyms --report --en-us;
+ mdspell '**/*.md' '!**/Pester/**/*.md' '!**/dotnet-tools/**/*.md' --ignore-numbers --ignore-acronyms --report --en-us;
displayName: Test Spelling in Markdown
condition: succeededOrFailed()
workingDirectory: '$(repoPath)'
diff --git a/.vsts-ci/windows.yml b/.vsts-ci/windows.yml
index ac6d350af..e96b320d7 100644
--- a/.vsts-ci/windows.yml
+++ b/.vsts-ci/windows.yml
@@ -14,6 +14,7 @@ trigger:
- /.vsts-ci/misc-analysis.yml
- /.github/ISSUE_TEMPLATE/*
- /.dependabot/config.yml
+ - test/perf/*
pr:
branches:
include:
@@ -28,6 +29,7 @@ pr:
- .github/ISSUE_TEMPLATE/*
- .vsts-ci/misc-analysis.yml
- test/common/markdown/*
+ - test/perf/*
- tools/packaging/*
- tools/releaseBuild/*
- tools/releaseBuild/azureDevOps/templates/*
diff --git a/src/System.Management.Automation/AssemblyInfo.cs b/src/System.Management.Automation/AssemblyInfo.cs
index 1963b331e..fe3ee107e 100644
--- a/src/System.Management.Automation/AssemblyInfo.cs
+++ b/src/System.Management.Automation/AssemblyInfo.cs
@@ -6,24 +6,13 @@ using System.Resources;
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("powershell-tests,PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
+[assembly: InternalsVisibleTo("powershell-perf,PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
-[assembly: InternalsVisibleTo("Microsoft.Test.Management.Automation.GPowershell.Analyzers,PublicKey=00240000048000009400000006020000002400005253413100040000010001003f8c902c8fe7ac83af7401b14c1bd103973b26dfafb2b77eda478a2539b979b56ce47f36336741b4ec52bbc51fecd51ba23810cec47070f3e29a2261a2d1d08e4b2b4b457beaa91460055f78cc89f21cd028377af0cc5e6c04699b6856a1e49d5fad3ef16d3c3d6010f40df0a7d6cc2ee11744b5cfb42e0f19a52b8a29dc31b0")]
-
-#if NOT_SIGNED
-// These attributes aren't every used, it's just a hack to get VS to not complain
-// about access when editing using the project files that don't actually build.
-[assembly: InternalsVisibleTo(@"Microsoft.PowerShell.Commands.Utility")]
-[assembly: InternalsVisibleTo(@"Microsoft.PowerShell.Commands.Management")]
-[assembly: InternalsVisibleTo(@"Microsoft.PowerShell.Security")]
-[assembly: InternalsVisibleTo(@"System.Management.Automation.Remoting")]
-[assembly: InternalsVisibleTo(@"Microsoft.PowerShell.ConsoleHost")]
-#else
[assembly: InternalsVisibleTo(@"Microsoft.PowerShell.Commands.Utility" + @",PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
[assembly: InternalsVisibleTo(@"Microsoft.PowerShell.Commands.Management" + @",PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
[assembly: InternalsVisibleTo(@"Microsoft.PowerShell.Security" + @",PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
[assembly: InternalsVisibleTo(@"System.Management.Automation.Remoting" + @",PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
[assembly: InternalsVisibleTo(@"Microsoft.PowerShell.ConsoleHost" + @",PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
-#endif
namespace System.Management.Automation
{
diff --git a/test/perf/benchmarks/Categories.cs b/test/perf/benchmarks/Categories.cs
new file mode 100644
index 000000000..09f710649
--- /dev/null
+++ b/test/perf/benchmarks/Categories.cs
@@ -0,0 +1,28 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace MicroBenchmarks
+{
+ public static class Categories
+ {
+ ///
+ /// Benchmarks belonging to this category are executed for CI jobs.
+ ///
+ public const string Components = "Components";
+
+ ///
+ /// Benchmarks belonging to this category are executed for CI jobs.
+ ///
+ public const string Engine = "Engine";
+
+ ///
+ /// Benchmarks belonging to this category are targeting internal APIs.
+ ///
+ public const string Internal = "Internal";
+
+ ///
+ /// Benchmarks belonging to this category are targeting public APIs.
+ ///
+ public const string Public = "Public";
+ }
+}
diff --git a/test/perf/benchmarks/Engine.Parser.cs b/test/perf/benchmarks/Engine.Parser.cs
new file mode 100644
index 000000000..10538e320
--- /dev/null
+++ b/test/perf/benchmarks/Engine.Parser.cs
@@ -0,0 +1,23 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Management.Automation.Language;
+using BenchmarkDotNet.Attributes;
+using MicroBenchmarks;
+
+namespace Engine
+{
+ [BenchmarkCategory(Categories.Engine, Categories.Public)]
+ public class Parsing
+ {
+ [Benchmark]
+ public Ast UsingStatement()
+ {
+ const string Script = @"
+ using module moduleA
+ using Assembly assemblyA
+ using namespace System.IO";
+ return Parser.ParseInput(Script, out _, out _);
+ }
+ }
+}
diff --git a/test/perf/benchmarks/Engine.ScriptBlock.cs b/test/perf/benchmarks/Engine.ScriptBlock.cs
new file mode 100644
index 000000000..ad373dd5f
--- /dev/null
+++ b/test/perf/benchmarks/Engine.ScriptBlock.cs
@@ -0,0 +1,77 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Management.Automation;
+using System.Management.Automation.Runspaces;
+using System.Runtime.InteropServices;
+using BenchmarkDotNet.Attributes;
+using MicroBenchmarks;
+
+namespace Engine
+{
+ [BenchmarkCategory(Categories.Engine, Categories.Public)]
+ public class Scripting
+ {
+ private Runspace runspace;
+ private ScriptBlock scriptBlock;
+
+ private void SetupRunspace()
+ {
+ // Unless you want to run commands from any built-in modules, using 'CreateDefault2' is enough.
+ runspace = RunspaceFactory.CreateRunspace(InitialSessionState.CreateDefault2());
+ runspace.Open();
+ Runspace.DefaultRunspace = runspace;
+ }
+
+ #region Invoke-Method
+
+ [ParamsSource(nameof(ValuesForScript))]
+ public string InvokeMethodScript { get; set; }
+
+ public IEnumerable ValuesForScript()
+ {
+ yield return @"'String'.GetType()";
+ yield return @"[System.IO.Path]::HasExtension('')";
+
+ // Test on COM method invocation.
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ yield return @"$sh=New-Object -ComObject Shell.Application; $sh.Namespace('c:\')";
+ yield return @"$fs=New-Object -ComObject scripting.filesystemobject; $fs.Drives";
+ }
+ }
+
+ [GlobalSetup(Target = nameof(InvokeMethod))]
+ public void GlobalSetup()
+ {
+ SetupRunspace();
+ scriptBlock = ScriptBlock.Create(InvokeMethodScript);
+
+ // Run it once to get the C# code jitted and the script compiled.
+ // The first call to this takes relatively too long, which makes the BDN's heuristic incorrectly
+ // believe that there is no need to run many ops in each interation. However, the subsequent runs
+ // of this method is much faster than the first run, and this causes 'MinIterationTime' warnings
+ // to our benchmarks and make the benchmark results not reliable.
+ // Calling this method once in 'GlobalSetup' is a workaround.
+ // See https://github.com/dotnet/BenchmarkDotNet/issues/837#issuecomment-828600157
+ scriptBlock.Invoke();
+ }
+
+ [Benchmark]
+ public Collection InvokeMethod()
+ {
+ return scriptBlock.Invoke();
+ }
+
+ #endregion
+
+ [GlobalCleanup]
+ public void GlobalCleanup()
+ {
+ runspace.Dispose();
+ Runspace.DefaultRunspace = null;
+ }
+ }
+}
diff --git a/test/perf/benchmarks/Program.cs b/test/perf/benchmarks/Program.cs
new file mode 100644
index 000000000..2b3aafdb1
--- /dev/null
+++ b/test/perf/benchmarks/Program.cs
@@ -0,0 +1,56 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.IO;
+using BenchmarkDotNet.Running;
+using BenchmarkDotNet.Extensions;
+
+namespace MicroBenchmarks
+{
+ public class Program
+ {
+ public static int Main(string[] args)
+ {
+ var argsList = new List(args);
+ int? partitionCount;
+ int? partitionIndex;
+ List exclusionFilterValue;
+ List categoryExclusionFilterValue;
+ bool getDiffableDisasm;
+
+ // Parse and remove any additional parameters that we need that aren't part of BDN (BenchmarkDotnet)
+ try
+ {
+ CommandLineOptions.ParseAndRemoveIntParameter(argsList, "--partition-count", out partitionCount);
+ CommandLineOptions.ParseAndRemoveIntParameter(argsList, "--partition-index", out partitionIndex);
+ CommandLineOptions.ParseAndRemoveStringsParameter(argsList, "--exclusion-filter", out exclusionFilterValue);
+ CommandLineOptions.ParseAndRemoveStringsParameter(argsList, "--category-exclusion-filter", out categoryExclusionFilterValue);
+ CommandLineOptions.ParseAndRemoveBooleanParameter(argsList, "--disasm-diff", out getDiffableDisasm);
+
+ CommandLineOptions.ValidatePartitionParameters(partitionCount, partitionIndex);
+ }
+ catch (ArgumentException e)
+ {
+ Console.WriteLine("ArgumentException: {0}", e.Message);
+ return 1;
+ }
+
+ return BenchmarkSwitcher
+ .FromAssembly(typeof(Program).Assembly)
+ .Run(
+ argsList.ToArray(),
+ RecommendedConfig.Create(
+ artifactsPath: new DirectoryInfo(Path.Combine(Path.GetDirectoryName(typeof(Program).Assembly.Location), "BenchmarkDotNet.Artifacts")),
+ mandatoryCategories: ImmutableHashSet.Create(Categories.Components, Categories.Engine),
+ partitionCount: partitionCount,
+ partitionIndex: partitionIndex,
+ exclusionFilterValue: exclusionFilterValue,
+ categoryExclusionFilterValue: categoryExclusionFilterValue,
+ getDiffableDisasm: getDiffableDisasm))
+ .ToExitCode();
+ }
+ }
+}
diff --git a/test/perf/benchmarks/README.md b/test/perf/benchmarks/README.md
new file mode 100644
index 000000000..0da436163
--- /dev/null
+++ b/test/perf/benchmarks/README.md
@@ -0,0 +1,88 @@
+## Micro Benchmarks
+
+This folder contains micro benchmarks that test the performance of PowerShell Engine.
+
+### Requirement
+
+1. A good suite of benchmarks
+ Something that measures only the thing that we are interested in and _produces accurate, stable and repeatable results_.
+2. A set of machine with the same configurations.
+3. Automation for regression detection.
+
+### Design Decision
+
+1. This project is internal visible to `System.Management.Automation`.
+ We want to be able to target some internal APIs to get measurements on specific scoped scenarios,
+ such as measuring the time to compile AST to a delegate by the compiler.
+2. This project makes `ProjectReference` to other PowerShell assemblies.
+ This makes it easy to run benchmarks with the changes made in the codebase.
+ To run benchmarks with a specific version of PowerShell,
+ just replace the `ProjectReference` with a `PackageReference` to the `Microsoft.PowerShell.SDK` NuGet package of the corresponding version.
+
+### Quick Start
+
+You can run the benchmarks directly using `dotnet run` in this directory:
+1. To run the benchmarks in Interactive Mode, where you will be asked which benchmark(s) to run:
+ ```
+ dotnet run -c release
+ ```
+
+2. To list all available benchmarks ([read more](https://github.com/dotnet/performance/blob/main/docs/benchmarkdotnet.md#Listing-the-Benchmarks)):
+ ```
+ dotnet run -c release --list [flat/tree]
+ ```
+
+3. To filter the benchmarks using a glob pattern applied to `namespace.typeName.methodName` ([read more](https://github.com/dotnet/performance/blob/main/docs/benchmarkdotnet.md#Filtering-the-Benchmarks)]):
+ ```
+ dotnet run -c Release -f net6.0 --filter *script* --list flat
+ ```
+
+4. To profile the benchmarked code and produce an ETW Trace file ([read more](https://github.com/dotnet/performance/blob/main/docs/benchmarkdotnet.md#Profiling))
+ ```
+ dotnet run -c Release -f net6.0 --filter *script* --profiler ETW
+ ```
+
+You can also use the function `Start-Benchmarking` from the module [`perf.psm1`](../perf.psm1) to run the benchmarks:
+```powershell
+Start-Benchmarking [[-TargetPSVersion] ] [[-List] ] [[-Filter] ] [[-Artifacts] ] [-KeepFiles] []
+```
+Run `Get-Help Start-Benchmarking -Full` to see the description of each parameter.
+
+### Regression Detection
+
+We use the tool [`ResultsComparer`](../dotnet-tools/ResultsComparer) to compare the provided benchmark results.
+See the [README.md](../dotnet-tools/ResultsComparer/README.md) for `ResultsComparer` for more details.
+
+The module `perf.psm1` also provides `Compare-BenchmarkResult` that wraps `ResultsComparer`.
+Here is an example of using it:
+
+```
+## Run benchmarks targeting the current code base
+PS:1> Start-Benchmarking -Filter *script* -Artifacts C:\arena\tmp\BenchmarkDotNet.Artifacts\current\
+
+## Run benchmarks targeting the 7.1.3 version of PS package
+PS:2> Start-Benchmarking -Filter *script* -Artifacts C:\arena\tmp\BenchmarkDotNet.Artifacts\7.1.3 -TargetPSVersion 7.1.3
+
+## Compare the results using 5% threshold
+PS:3> Compare-BenchmarkResult -BaseResultPath C:\arena\tmp\BenchmarkDotNet.Artifacts\7.1.3\ -DiffResultPath C:\arena\tmp\BenchmarkDotNet.Artifacts\current\ -Threshold 1%
+summary:
+better: 4, geomean: 1.057
+total diff: 4
+
+No Slower results for the provided threshold = 1% and noise filter = 0.3ns.
+
+| Faster | base/diff | Base Median (ns) | Diff Median (ns) | Modality|
+| -------------------------------------------------------------------------------- | ---------:| ----------------:| ----------------:| --------:|
+| Engine.Scripting.InvokeMethod(Script: "$fs=New-Object -ComObject scripting.files | 1.07 | 50635.77 | 47116.42 | |
+| Engine.Scripting.InvokeMethod(Script: "$sh=New-Object -ComObject Shell.Applicati | 1.07 | 1063085.23 | 991602.08 | |
+| Engine.Scripting.InvokeMethod(Script: "'String'.GetType()") | 1.06 | 1329.93 | 1252.51 | |
+| Engine.Scripting.InvokeMethod(Script: "[System.IO.Path]::HasExtension('')") | 1.02 | 1322.04 | 1297.72 | |
+
+No file given
+```
+
+## References
+
+- [Getting started with BenchmarkDotNet](https://benchmarkdotnet.org/articles/guides/getting-started.html)
+- [Micro-benchmark Design Guidelines](https://github.com/dotnet/performance/blob/main/docs/microbenchmark-design-guidelines.md)
+- [Adam SITNIK: Powerful benchmarking in .NET](https://www.youtube.com/watch?v=pdcrSG4tOLI&t=351s)
diff --git a/test/perf/benchmarks/powershell-perf.csproj b/test/perf/benchmarks/powershell-perf.csproj
new file mode 100644
index 000000000..860948202
--- /dev/null
+++ b/test/perf/benchmarks/powershell-perf.csproj
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+ PowerShell Performance Tests
+ powershell-perf
+ Exe
+
+ $(NoWarn);CS8002
+ true
+
+
+ $(PERF_TARGET_VERSION)
+
+ AnyCPU
+ portable
+ true
+
+
+
+ true
+ ../../../src/signing/visualstudiopublic.snk
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/BenchmarkDotNet.Extensions.csproj b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/BenchmarkDotNet.Extensions.csproj
new file mode 100644
index 000000000..1383cfc1d
--- /dev/null
+++ b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/BenchmarkDotNet.Extensions.csproj
@@ -0,0 +1,17 @@
+
+
+
+ Library
+ netstandard2.0
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/CommandLineOptions.cs b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/CommandLineOptions.cs
new file mode 100644
index 000000000..3c8b343fc
--- /dev/null
+++ b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/CommandLineOptions.cs
@@ -0,0 +1,97 @@
+using System;
+using System.Collections.Generic;
+
+namespace BenchmarkDotNet.Extensions
+{
+ public class CommandLineOptions
+ {
+ // Find and parse given parameter with expected int value, then remove it and its value from the list of arguments to then pass to BenchmarkDotNet
+ // Throws ArgumentException if the parameter does not have a value or that value is not parsable as an int
+ public static List ParseAndRemoveIntParameter(List argsList, string parameter, out int? parameterValue)
+ {
+ int parameterIndex = argsList.IndexOf(parameter);
+ parameterValue = null;
+
+ if (parameterIndex != -1)
+ {
+ if (parameterIndex + 1 < argsList.Count && Int32.TryParse(argsList[parameterIndex+1], out int parsedParameterValue))
+ {
+ // remove --partition-count args
+ parameterValue = parsedParameterValue;
+ argsList.RemoveAt(parameterIndex+1);
+ argsList.RemoveAt(parameterIndex);
+ }
+ else
+ {
+ throw new ArgumentException(String.Format("{0} must be followed by an integer", parameter));
+ }
+ }
+
+ return argsList;
+ }
+
+ public static List ParseAndRemoveStringsParameter(List argsList, string parameter, out List parameterValue)
+ {
+ int parameterIndex = argsList.IndexOf(parameter);
+ parameterValue = new List();
+
+ if (parameterIndex + 1 < argsList.Count)
+ {
+ while (parameterIndex + 1 < argsList.Count && !argsList[parameterIndex + 1].StartsWith("-"))
+ {
+ // remove each filter string and stop when we get to the next argument flag
+ parameterValue.Add(argsList[parameterIndex + 1]);
+ argsList.RemoveAt(parameterIndex + 1);
+ }
+ }
+ //We only want to remove the --exclusion-filter if it exists
+ if (parameterIndex != -1)
+ {
+ argsList.RemoveAt(parameterIndex);
+ }
+
+ return argsList;
+ }
+
+ public static void ParseAndRemoveBooleanParameter(List argsList, string parameter, out bool parameterValue)
+ {
+ int parameterIndex = argsList.IndexOf(parameter);
+
+ if (parameterIndex != -1)
+ {
+ argsList.RemoveAt(parameterIndex);
+
+ parameterValue = true;
+ }
+ else
+ {
+ parameterValue = false;
+ }
+ }
+
+ public static void ValidatePartitionParameters(int? count, int? index)
+ {
+ // Either count and index must both be specified or neither specified
+ if (!(count.HasValue == index.HasValue))
+ {
+ throw new ArgumentException("If either --partition-count or --partition-index is specified, both must be specified");
+ }
+ // Check values of count and index parameters
+ else if (count.HasValue && index.HasValue)
+ {
+ if (count < 2)
+ {
+ throw new ArgumentException("When specified, value of --partition-count must be greater than 1");
+ }
+ else if (!(index < count))
+ {
+ throw new ArgumentException("Value of --partition-index must be less than --partition-count");
+ }
+ else if (index < 0)
+ {
+ throw new ArgumentException("Value of --partition-index must be greater than or equal to 0");
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/DiffableDisassemblyExporter.cs b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/DiffableDisassemblyExporter.cs
new file mode 100644
index 000000000..d45977ed5
--- /dev/null
+++ b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/DiffableDisassemblyExporter.cs
@@ -0,0 +1,90 @@
+using BenchmarkDotNet.Diagnosers;
+using BenchmarkDotNet.Disassemblers;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+
+namespace BenchmarkDotNet.Extensions
+{
+ // a simplified copy of internal BDN type: https://github.com/dotnet/BenchmarkDotNet/blob/0445917bf93059f17cb09e7d48cdb5e27a096c37/src/BenchmarkDotNet/Disassemblers/Exporters/GithubMarkdownDisassemblyExporter.cs#L35-L80
+ internal static class DiffableDisassemblyExporter
+ {
+ private static readonly Lazy> GetSource = new Lazy>(() => GetElementGetter("Source"));
+ private static readonly Lazy> GetTextRepresentation = new Lazy>(() => GetElementGetter("TextRepresentation"));
+
+ private static readonly Lazy>> Prettify
+ = new Lazy>>(GetPrettifyMethod);
+
+ internal static string BuildDisassemblyString(DisassemblyResult disassemblyResult, DisassemblyDiagnoserConfig config)
+ {
+ StringBuilder sb = new StringBuilder();
+
+ int methodIndex = 0;
+ foreach (var method in disassemblyResult.Methods.Where(method => string.IsNullOrEmpty(method.Problem)))
+ {
+ sb.AppendLine("```assembly");
+
+ sb.AppendLine($"; {method.Name}");
+
+ var pretty = Prettify.Value.Invoke(method, disassemblyResult, config, $"M{methodIndex++:00}");
+
+ ulong totalSizeInBytes = 0;
+ foreach (var element in pretty)
+ {
+ if (element.Source() is Asm asm)
+ {
+ checked
+ {
+ totalSizeInBytes += (uint)asm.Instruction.Length;
+ }
+
+ sb.AppendLine($" {element.TextRepresentation()}");
+ }
+ else // it's a DisassemblyPrettifier.Label (internal type..)
+ {
+ sb.AppendLine($"{element.TextRepresentation()}:");
+ }
+ }
+
+ sb.AppendLine($"; Total bytes of code {totalSizeInBytes}");
+ sb.AppendLine("```");
+ }
+
+ return sb.ToString();
+ }
+
+ private static SourceCode Source(this object element) => GetSource.Value.Invoke(element);
+
+ private static string TextRepresentation(this object element) => GetTextRepresentation.Value.Invoke(element);
+
+ private static Func