From ffce52e3b45792a064b8be10342ff3266bdb91df Mon Sep 17 00:00:00 2001 From: Andrey Akinshin Date: Mon, 16 Jun 2025 14:52:28 +0200 Subject: [PATCH 01/37] Set next BenchmarkDotNet version: 0.15.3 --- README.md | 2 +- build/common.props | 2 +- build/versions.txt | 3 ++- .../.template.config/template.json | 2 +- .../.template.config/template.json | 2 +- .../.template.config/template.json | 2 +- 6 files changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 6dea3ab02b..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 [26300+ 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), diff --git a/build/common.props b/build/common.props index ee40dbf5ef..e9502fcad8 100644 --- a/build/common.props +++ b/build/common.props @@ -42,7 +42,7 @@ - 0.15.2 + 0.15.3 diff --git a/build/versions.txt b/build/versions.txt index 98f8be3bf4..df0365a030 100644 --- a/build/versions.txt +++ b/build/versions.txt @@ -60,4 +60,5 @@ 0.14.0 0.15.0 0.15.1 -0.15.2 \ No newline at end of file +0.15.2 +0.15.3 \ No newline at end of file diff --git a/templates/templates/BenchmarkDotNet.BenchmarkProjectTemplate.CSharp/.template.config/template.json b/templates/templates/BenchmarkDotNet.BenchmarkProjectTemplate.CSharp/.template.config/template.json index 4f2dcda09b..1d1a4bce6f 100644 --- a/templates/templates/BenchmarkDotNet.BenchmarkProjectTemplate.CSharp/.template.config/template.json +++ b/templates/templates/BenchmarkDotNet.BenchmarkProjectTemplate.CSharp/.template.config/template.json @@ -139,7 +139,7 @@ "type": "parameter", "datatype": "string", "description": "Version of BenchmarkDotNet that will be referenced.", - "defaultValue": "0.15.2", + "defaultValue": "0.15.3", "replaces": "$(BenchmarkDotNetVersion)" } }, diff --git a/templates/templates/BenchmarkDotNet.BenchmarkProjectTemplate.FSharp/.template.config/template.json b/templates/templates/BenchmarkDotNet.BenchmarkProjectTemplate.FSharp/.template.config/template.json index cd6075564f..3a242758c0 100644 --- a/templates/templates/BenchmarkDotNet.BenchmarkProjectTemplate.FSharp/.template.config/template.json +++ b/templates/templates/BenchmarkDotNet.BenchmarkProjectTemplate.FSharp/.template.config/template.json @@ -139,7 +139,7 @@ "type": "parameter", "datatype": "string", "description": "Version of BenchmarkDotNet that will be referenced.", - "defaultValue": "0.15.2", + "defaultValue": "0.15.3", "replaces": "$(BenchmarkDotNetVersion)" } }, diff --git a/templates/templates/BenchmarkDotNet.BenchmarkProjectTemplate.VB/.template.config/template.json b/templates/templates/BenchmarkDotNet.BenchmarkProjectTemplate.VB/.template.config/template.json index c02efa671d..42929a3e01 100644 --- a/templates/templates/BenchmarkDotNet.BenchmarkProjectTemplate.VB/.template.config/template.json +++ b/templates/templates/BenchmarkDotNet.BenchmarkProjectTemplate.VB/.template.config/template.json @@ -139,7 +139,7 @@ "type": "parameter", "datatype": "string", "description": "Version of BenchmarkDotNet that will be referenced.", - "defaultValue": "0.15.2", + "defaultValue": "0.15.3", "replaces": "$(BenchmarkDotNetVersion)" } }, From 7d4210d65feb632cd77d7e00685a5b89322be18a Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Mon, 16 Jun 2025 21:41:24 +0100 Subject: [PATCH 02/37] Fix "Unknown Processor" on Windows if WMIC is not present (#2749) * allow MosCpuDetector to run on .NET 5+ * Revert "allow MosCpuDetector to run on .NET 5+" This reverts commit 495855a002ddee957993525ec933fe2ab9dcee63. * add WmiLightCpu Detector * Fix WmicCpuDetector being chosen if not available * enable NativeAOT * simplify IsApplicable * Update spacing of SupportedOsPlatform attribute Co-authored-by: Tim Cassell * Revert "Fix WmicCpuDetector being chosen if not available" This reverts commit 5972f77faf4673f38195d16865f334afc44bd986. * add WMIC deprecation remarks * remove WmiLight code * update WmiCpuInfoParser to return null if Processor Name isn't detected * remove WmiLightCpuDetector reference from WindowsCpuDetector * Update WmicCpuInfoParser.cs * check if wmicOutput is null or empty instead * add PowershellWmiCpuDetector (parser still not complete) * return null if there's no version of powershell installed * fix Powershell 7+ check Co-authored-by: Tim Cassell * rework search statement given that regex isn't supported * add parser code * add PowershellWmiCpuDetector to WindowsCpuDetector * rename variable to lower case * improve checking of latest powershell 7+ version * use explicit typing * fix frequency addition issue * revert to how WMIC parser handles processor frequency * add string is null or empty check to WmiCpuDetector * invoke Powershell as "PowerShell" if the file isn't found * add nominal Frequency detection and improve max frequency detection * fix issue with detecting latest Powershell * update comment * Update PowershellWmiCpuDetector.cs * refactor Powershell locating code to PowershellLocator * simplify frequency checks * Create PowershellWmiParserTests.cs * fix null being returned when object is expected. * rename test * simplify max frequency check Co-authored-by: Tim Cassell * use """ for string in parser test * Update PowershellWmiCpuInfoParserTests.cs * reduce indentation with """ * remove unnecessary test info * move string null check to caller * add nominal frequency support for MosCpuDetector * Update src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs Co-authored-by: Tim Cassell * remove nullability of parser Co-authored-by: Tim Cassell * check if tempMaxFrequency > 0 before assignment Co-authored-by: Tim Cassell * remove nullability of WmicCpuInfoParser * use file scoped namespace * use double instead of int * add null check to LinuxCpuDetector * change nullability of LinuxCpuParser * update LinuxCpuInfoParser nominal and max frequency detection * Update LinuxCpuInfoParser.cs * fix nullability check * simplify nominal frequency comparison Co-authored-by: Tim Cassell * add null check to macOS * don't accept null string from detector * fix LinuxCpuInfo parser test issues and improve robustness of Linux Cpu checks * fix Powershell Wmi Parser parsing issues * fix .net framework cpu parsing issue * do implicit conversion to double from uint --------- Co-authored-by: Tim Cassell --- .../Detectors/Cpu/Linux/LinuxCpuDetector.cs | 8 +- .../Detectors/Cpu/Linux/LinuxCpuInfoParser.cs | 75 ++++++++--- .../Detectors/Cpu/Windows/MosCpuDetector.cs | 25 +++- .../Cpu/Windows/PowershellWmiCpuDetector.cs | 44 +++++++ .../Cpu/Windows/PowershellWmiCpuInfoParser.cs | 67 ++++++++++ .../Cpu/Windows/WindowsCpuDetector.cs | 3 +- .../Detectors/Cpu/Windows/WmicCpuDetector.cs | 8 +- .../Cpu/Windows/WmicCpuInfoParser.cs | 27 ++-- .../Detectors/Cpu/macOS/MacOsCpuDetector.cs | 6 +- .../Cpu/macOS/SysctlCpuInfoParser.cs | 2 +- .../Helpers/PowerShellLocator.cs | 81 ++++++++++++ src/BenchmarkDotNet/Helpers/SectionsHelper.cs | 9 ++ .../Detectors/Cpu/LinuxCpuInfoParserTests.cs | 12 +- .../Cpu/PowershellWmiCpuInfoParserTests.cs | 122 ++++++++++++++++++ 14 files changed, 444 insertions(+), 45 deletions(-) create mode 100644 src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs create mode 100644 src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs create mode 100644 src/BenchmarkDotNet/Helpers/PowerShellLocator.cs create mode 100644 tests/BenchmarkDotNet.Tests/Detectors/Cpu/PowershellWmiCpuInfoParserTests.cs diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuDetector.cs index 12c3b188d4..44973b27e9 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuDetector.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuDetector.cs @@ -24,8 +24,12 @@ internal class LinuxCpuDetector : ICpuDetector ["LANGUAGE"] = "C" }; - string cpuInfo = ProcessHelper.RunAndReadOutput("cat", "/proc/cpuinfo") ?? ""; - string lscpu = ProcessHelper.RunAndReadOutput("/bin/bash", "-c \"lscpu\"", environmentVariables: languageInvariantEnvironment); + 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 index ed93999653..7d79c3262f 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuInfoParser.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuInfoParser.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using BenchmarkDotNet.Extensions; @@ -17,6 +18,8 @@ private static class ProcCpu 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 @@ -28,12 +31,13 @@ private static class Lscpu /// Output of `cat /proc/cpuinfo` /// Output of `lscpu` - internal static CpuInfo Parse(string? cpuInfo, string? lscpu) + internal static CpuInfo Parse(string cpuInfo, string lscpu) { var processorModelNames = new HashSet(); var processorsToPhysicalCoreCount = new Dictionary(); int logicalCoreCount = 0; - Frequency? maxFrequency = null; + double maxFrequency = 0.0; + double nominalFrequency = 0.0; var logicalCores = SectionsHelper.ParseSections(cpuInfo, ':'); foreach (var logicalCore in logicalCores) @@ -51,14 +55,43 @@ internal static CpuInfo Parse(string? cpuInfo, string? lscpu) } if (logicalCore.TryGetValue(ProcCpu.MaxFrequency, out string maxCpuFreqValue) && - Frequency.TryParseMHz(maxCpuFreqValue, out var maxCpuFreq)) + Frequency.TryParseMHz(maxCpuFreqValue.Replace(',', '.'), out Frequency maxCpuFreq) + && maxCpuFreq > 0) { - maxFrequency = maxCpuFreq; + 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 (lscpu != null) + if (string.IsNullOrEmpty(lscpu) == false) { var lscpuParts = lscpu.Split('\n') .Where(line => line.Contains(':')) @@ -70,8 +103,8 @@ internal static CpuInfo Parse(string? cpuInfo, string? lscpu) string value = lscpuParts[i + 1].Trim(); if (name.EqualsWithIgnoreCase(Lscpu.MaxFrequency) && - Frequency.TryParseMHz(value.Replace(',', '.'), out var maxFrequencyParsed)) // Example: `CPU max MHz: 3200,0000` - maxFrequency = maxFrequencyParsed; + 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); @@ -82,21 +115,33 @@ internal static CpuInfo Parse(string? cpuInfo, string? lscpu) } } - var nominalFrequency = processorModelNames - .Select(ParseFrequencyFromBrandString) - .WhereNotNull() - .FirstOrDefault() ?? maxFrequency; 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 = nominalFrequency?.Hertz.RoundToLong(), - MaxFrequencyHz = maxFrequency?.Hertz.RoundToLong() + NominalFrequencyHz = nominalFrequencyActual?.Hertz.RoundToLong(), + MaxFrequencyHz = maxFrequencyActual?.Hertz.RoundToLong() }; } @@ -107,7 +152,7 @@ internal static CpuInfo Parse(string? cpuInfo, string? lscpu) 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 var result) ? result : null; + return Frequency.TryParseGHz(match, out Frequency result) ? result : null; } return null; diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/MosCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/MosCpuDetector.cs index 8ab16535f6..edc48ba25d 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/MosCpuDetector.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/MosCpuDetector.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Management; using BenchmarkDotNet.Extensions; @@ -28,7 +29,8 @@ public bool IsApplicable() => OsDetector.IsWindows() && int physicalCoreCount = 0; int logicalCoreCount = 0; int processorsCount = 0; - int sumMaxFrequency = 0; + double maxFrequency = 0; + double nominalFrequency = 0; using (var mosProcessor = new ManagementObjectSearcher("SELECT * FROM Win32_Processor")) { @@ -41,14 +43,23 @@ public bool IsApplicable() => OsDetector.IsWindows() && processorsCount++; physicalCoreCount += (int)(uint)moProcessor[WmicCpuInfoKeyNames.NumberOfCores]; logicalCoreCount += (int)(uint)moProcessor[WmicCpuInfoKeyNames.NumberOfLogicalProcessors]; - sumMaxFrequency = (int)(uint)moProcessor[WmicCpuInfoKeyNames.MaxClockSpeed]; + 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? maxFrequency = sumMaxFrequency > 0 && processorsCount > 0 - ? Frequency.FromMHz(sumMaxFrequency * 1.0 / processorsCount) + Frequency? maxFrequencyActual = maxFrequency > 0 && processorsCount > 0 + ? Frequency.FromMHz(maxFrequency) + : null; + Frequency? nominalFrequencyActual = nominalFrequency > 0 && processorsCount > 0 + ? Frequency.FromMHz(nominalFrequency) : null; return new CpuInfo @@ -57,8 +68,8 @@ public bool IsApplicable() => OsDetector.IsWindows() && PhysicalProcessorCount = processorsCount > 0 ? processorsCount : null, PhysicalCoreCount = physicalCoreCount > 0 ? physicalCoreCount : null, LogicalCoreCount = logicalCoreCount > 0 ? logicalCoreCount : null, - NominalFrequencyHz = maxFrequency?.Hertz.RoundToLong(), - MaxFrequencyHz = maxFrequency?.Hertz.RoundToLong() + 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 index 9969a1bca0..1de238b93f 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WindowsCpuDetector.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WindowsCpuDetector.cs @@ -1,3 +1,4 @@ namespace BenchmarkDotNet.Detectors.Cpu.Windows; -internal class WindowsCpuDetector() : CpuDetector(new MosCpuDetector(), new WmicCpuDetector()); \ No newline at end of file +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 index 3a92d180b8..fe3434ae7f 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuDetector.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuDetector.cs @@ -9,6 +9,8 @@ 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"; @@ -24,7 +26,11 @@ internal class WmicCpuDetector : ICpuDetector $"{WmicCpuInfoKeyNames.NumberOfLogicalProcessors}, " + $"{WmicCpuInfoKeyNames.MaxClockSpeed}"; string wmicPath = File.Exists(DefaultWmicPath) ? DefaultWmicPath : "wmic"; - string wmicOutput = ProcessHelper.RunAndReadOutput(wmicPath, $"cpu get {argList} /Format:List"); + 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/WmicCpuInfoParser.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuInfoParser.cs index a139d39711..295d76262e 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuInfoParser.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuInfoParser.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using BenchmarkDotNet.Extensions; using BenchmarkDotNet.Helpers; using Perfolizer.Horology; @@ -12,15 +13,16 @@ 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) + internal static CpuInfo Parse(string wmicOutput) { - var processorModelNames = new HashSet(); + HashSet processorModelNames = new HashSet(); int physicalCoreCount = 0; int logicalCoreCount = 0; int processorsCount = 0; - var sumMaxFrequency = Frequency.Zero; + double maxFrequency = 0.0; + double nominalFrequency = 0.0; - var processors = SectionsHelper.ParseSections(wmicOutput, '='); + List> processors = SectionsHelper.ParseSections(wmicOutput, '='); foreach (var processor in processors) { if (processor.TryGetValue(WmicCpuInfoKeyNames.NumberOfCores, out string numberOfCoresValue) && @@ -40,26 +42,29 @@ internal static CpuInfo Parse(string? wmicOutput) } if (processor.TryGetValue(WmicCpuInfoKeyNames.MaxClockSpeed, out string frequencyValue) - && int.TryParse(frequencyValue, out int frequency) + && double.TryParse(frequencyValue, out double frequency) && frequency > 0) { - sumMaxFrequency += frequency; + nominalFrequency = nominalFrequency == 0 ? frequency : Math.Min(nominalFrequency, frequency); + maxFrequency = Math.Max(maxFrequency, frequency); } } string? processorName = processorModelNames.Count > 0 ? string.Join(", ", processorModelNames) : null; - Frequency? maxFrequency = sumMaxFrequency > 0 && processorsCount > 0 - ? Frequency.FromMHz(sumMaxFrequency / processorsCount) + 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 = maxFrequency?.Hertz.RoundToLong(), - MaxFrequencyHz = maxFrequency?.Hertz.RoundToLong() + 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 index f34de47ac9..2f540e82d3 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/macOS/MacOsCpuDetector.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/macOS/MacOsCpuDetector.cs @@ -15,7 +15,11 @@ internal class MacOsCpuDetector : ICpuDetector { if (!IsApplicable()) return null; - string sysctlOutput = ProcessHelper.RunAndReadOutput("sysctl", "-a"); + 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 index 58d1b53766..5cd4d11c8a 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/macOS/SysctlCpuInfoParser.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/macOS/SysctlCpuInfoParser.cs @@ -20,7 +20,7 @@ private static class Sysctl /// Output of `sysctl -a` [SuppressMessage("ReSharper", "StringLiteralTypo")] - internal static CpuInfo Parse(string? sysctlOutput) + internal static CpuInfo Parse(string sysctlOutput) { var sysctl = SectionsHelper.ParseSection(sysctlOutput, ':'); string processorName = sysctl.GetValueOrDefault(Sysctl.ProcessorName); diff --git a/src/BenchmarkDotNet/Helpers/PowerShellLocator.cs b/src/BenchmarkDotNet/Helpers/PowerShellLocator.cs new file mode 100644 index 0000000000..28b8e910f7 --- /dev/null +++ b/src/BenchmarkDotNet/Helpers/PowerShellLocator.cs @@ -0,0 +1,81 @@ +using System; +using System.IO; +using System.Linq; +using System.Runtime.Versioning; +using System.Text.RegularExpressions; +using BenchmarkDotNet.Detectors; + +namespace BenchmarkDotNet.Helpers; + +/// +/// Locates PowerShell on a system, currently only supports on Windows. +/// +internal class PowerShellLocator +{ + private static readonly string WindowsPowershellPath = + $"{Environment.SystemDirectory}{Path.DirectorySeparatorChar}WindowsPowerShell{Path.DirectorySeparatorChar}" + + $"v1.0{Path.DirectorySeparatorChar}powershell.exe"; + +#if NET6_0_OR_GREATER + [SupportedOSPlatform("windows")] +#endif + internal static string? LocateOnWindows() + { + if (OsDetector.IsWindows() == false) + return null; + + string powershellPath; + + try + { + string programFiles = Environment.Is64BitOperatingSystem + ? Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) + : Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86); + + string powershell7PlusPath = $"{programFiles}{Path.DirectorySeparatorChar}Powershell{Path.DirectorySeparatorChar}"; + + bool checkForPowershell7Plus = true; + + if (Directory.Exists(powershell7PlusPath)) + { + string[] subDirectories = Directory.EnumerateDirectories(powershell7PlusPath, "*", SearchOption.AllDirectories) + .ToArray(); + + //Use the highest number string directory for PowerShell so that we get the newest major PowerShell version + // Example version directories are 6, 7, and in the future 8. + string? subDirectory = subDirectories.Where(x => Regex.IsMatch(x, "[0-9]")) + .Select(x => x) + .OrderByDescending(x => x) + .FirstOrDefault(); + + if (subDirectory is not null) + { + powershell7PlusPath = $"{subDirectory}{Path.DirectorySeparatorChar}pwsh.exe"; + } + else + { + checkForPowershell7Plus = false; + } + } + + // Optimistically, use Cross-platform new PowerShell when available but fallback to Windows PowerShell if not available. + if (checkForPowershell7Plus) + { + powershellPath = File.Exists(powershell7PlusPath) ? powershell7PlusPath : WindowsPowershellPath; + } + else + { + powershellPath = WindowsPowershellPath; + } + + if (File.Exists(powershellPath) == false) + powershellPath = "PowerShell"; + } + catch + { + powershellPath = "PowerShell"; + } + + return powershellPath; + } +} \ No newline at end of file diff --git a/src/BenchmarkDotNet/Helpers/SectionsHelper.cs b/src/BenchmarkDotNet/Helpers/SectionsHelper.cs index 15c54fd8f9..e16442d0fb 100644 --- a/src/BenchmarkDotNet/Helpers/SectionsHelper.cs +++ b/src/BenchmarkDotNet/Helpers/SectionsHelper.cs @@ -32,5 +32,14 @@ public static List> ParseSections(string? content, ch .Where(s => s.Count > 0) .ToList(); } + + public static List> ParseSectionsForPowershellWmi(string? content, char separator) + { + return + Regex.Split(content ?? "", "(\r*\n)") + .Select(s => ParseSection(s, separator)) + .Where(s => s.Count > 0) + .ToList(); + } } } \ No newline at end of file diff --git a/tests/BenchmarkDotNet.Tests/Detectors/Cpu/LinuxCpuInfoParserTests.cs b/tests/BenchmarkDotNet.Tests/Detectors/Cpu/LinuxCpuInfoParserTests.cs index d229a2b06d..0d4de2d9cc 100644 --- a/tests/BenchmarkDotNet.Tests/Detectors/Cpu/LinuxCpuInfoParserTests.cs +++ b/tests/BenchmarkDotNet.Tests/Detectors/Cpu/LinuxCpuInfoParserTests.cs @@ -22,7 +22,7 @@ public void EmptyTest() [Fact] public void MalformedTest() { - var actual = LinuxCpuInfoParser.Parse("malformedkey: malformedvalue\n\nmalformedkey2: malformedvalue2", null); + var actual = LinuxCpuInfoParser.Parse("malformedkey: malformedvalue\n\nmalformedkey2: malformedvalue2", string.Empty); var expected = new CpuInfo(); Output.AssertEqual(expected, actual); } @@ -31,7 +31,7 @@ public void MalformedTest() public void TwoProcessorWithDifferentCoresCountTest() { string cpuInfo = TestHelper.ReadTestFile("ProcCpuInfoProcessorWithDifferentCoresCount.txt"); - var actual = LinuxCpuInfoParser.Parse(cpuInfo, null); + var actual = LinuxCpuInfoParser.Parse(cpuInfo, string.Empty); var expected = new CpuInfo { ProcessorName = "Unknown processor with 2 cores and hyper threading, Unknown processor with 4 cores", @@ -49,7 +49,7 @@ public void TwoProcessorWithDifferentCoresCountTest() public void RealOneProcessorTwoCoresTest() { string cpuInfo = TestHelper.ReadTestFile("ProcCpuInfoRealOneProcessorTwoCores.txt"); - var actual = LinuxCpuInfoParser.Parse(cpuInfo, null); + var actual = LinuxCpuInfoParser.Parse(cpuInfo, string.Empty); var expected = new CpuInfo { ProcessorName = "Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz", @@ -66,14 +66,14 @@ public void RealOneProcessorTwoCoresTest() public void RealOneProcessorFourCoresTest() { string cpuInfo = TestHelper.ReadTestFile("ProcCpuInfoRealOneProcessorFourCores.txt"); - var actual = LinuxCpuInfoParser.Parse(cpuInfo, null); + var actual = LinuxCpuInfoParser.Parse(cpuInfo, string.Empty); var expected = new CpuInfo { ProcessorName = "Intel(R) Core(TM) i7-4710MQ CPU @ 2.50GHz", PhysicalProcessorCount = 1, PhysicalCoreCount = 4, LogicalCoreCount = 8, - NominalFrequencyHz = 2_500_000_000, + NominalFrequencyHz = 2_494_300_000, MaxFrequencyHz = 2_500_000_000 }; Output.AssertEqual(expected, actual); @@ -154,7 +154,7 @@ r smca fsrm flush_l1d PhysicalProcessorCount = 1, PhysicalCoreCount = 16, LogicalCoreCount = 32, - NominalFrequencyHz = 5_881_000_000, + NominalFrequencyHz = 400_000_000, MaxFrequencyHz = 5_881_000_000 }; Output.AssertEqual(expected, actual); diff --git a/tests/BenchmarkDotNet.Tests/Detectors/Cpu/PowershellWmiCpuInfoParserTests.cs b/tests/BenchmarkDotNet.Tests/Detectors/Cpu/PowershellWmiCpuInfoParserTests.cs new file mode 100644 index 0000000000..a781d70021 --- /dev/null +++ b/tests/BenchmarkDotNet.Tests/Detectors/Cpu/PowershellWmiCpuInfoParserTests.cs @@ -0,0 +1,122 @@ +using BenchmarkDotNet.Detectors.Cpu.Windows; +using Perfolizer.Models; +using Xunit; +using Xunit.Abstractions; + +namespace BenchmarkDotNet.Tests.Detectors.Cpu; + +public class PowershellWmiCpuInfoParserTests(ITestOutputHelper output) +{ + private ITestOutputHelper Output { get; } = output; + + + [Fact] + public void EmptyTest() + { + CpuInfo? actual = PowershellWmiCpuInfoParser.Parse(string.Empty); + CpuInfo expected = new CpuInfo(); + Output.AssertEqual(expected, actual); + } + + + [Fact] + public void MalformedTest() + { + CpuInfo? actual = PowershellWmiCpuInfoParser + .Parse("malformedkey=malformedvalue\n\nmalformedkey2=malformedvalue2"); + CpuInfo expected = new CpuInfo(); + Output.AssertEqual(expected, actual); + } + + [Fact] + public void RealTwoProcessorEightCoresTest() + { + const string cpuInfo = + """ + MaxClockSpeed:2400 + Name:Intel(R) Xeon(R) CPU E5-2630 v3 + NumberOfCores:8 + NumberOfLogicalProcessors:16 + + + MaxClockSpeed:2400 + Name:Intel(R) Xeon(R) CPU E5-2630 v3 + NumberOfCores:8 + NumberOfLogicalProcessors:16 + + """; + CpuInfo? actual = PowershellWmiCpuInfoParser.Parse(cpuInfo); + + CpuInfo expected = new CpuInfo + { + ProcessorName = "Intel(R) Xeon(R) CPU E5-2630 v3", + PhysicalProcessorCount = 2, + PhysicalCoreCount = 16, + LogicalCoreCount = 32, + NominalFrequencyHz = 2_400_000_000, + MaxFrequencyHz = 2_400_000_000, + }; + + Output.AssertEqual(expected, actual); + } + + [Fact] + public void RealTwoProcessorEightCoresWithWmicBugTest() + { + const string cpuInfo = + "\r\r\n" + + "\r\r\n" + + "MaxClockSpeed:3111\r\r\n" + + "Name:Intel(R) Xeon(R) CPU E5-2687W 0\r\r\n" + + "NumberOfCores:8\r\r\n" + + "NumberOfLogicalProcessors:16\r\r\n" + + "\r\r\n" + + "\r\r\n" + + "MaxClockSpeed:3111\r\r\n" + + "Name:Intel(R) Xeon(R) CPU E5-2687W 0\r\r\n" + + "NumberOfCores:8\r\r\n" + + "NumberOfLogicalProcessors:16\r\r\n" + + "\r\r\n" + + "\r\r\n" + + "\r\r\n"; + + CpuInfo? actual = PowershellWmiCpuInfoParser.Parse(cpuInfo); + + CpuInfo expected = new CpuInfo + { + ProcessorName = "Intel(R) Xeon(R) CPU E5-2687W 0", + PhysicalProcessorCount = 2, + PhysicalCoreCount = 16, + LogicalCoreCount = 32, + NominalFrequencyHz = 3_111_000_000, + MaxFrequencyHz = 3_111_000_000, + }; + + Output.AssertEqual(expected, actual); + } + + [Fact] + public void RealOneProcessorFourCoresTest() + { + const string cpuInfo = """ + + MaxClockSpeed:2500 + Name:Intel(R) Core(TM) i7-4710MQ + NumberOfCores:4 + NumberOfLogicalProcessors:8 + """; + + CpuInfo? actual = PowershellWmiCpuInfoParser.Parse(cpuInfo); + CpuInfo expected = new CpuInfo + { + ProcessorName = "Intel(R) Core(TM) i7-4710MQ", + PhysicalProcessorCount = 1, + PhysicalCoreCount = 4, + LogicalCoreCount = 8, + NominalFrequencyHz = 2_500_000_000, + MaxFrequencyHz = 2_500_000_000, + }; + + Output.AssertEqual(expected, actual); + } +} \ No newline at end of file From f8390f8ff1af22928d094c58773723fb8b099019 Mon Sep 17 00:00:00 2001 From: filzrev <103790468+filzrev@users.noreply.github.com> Date: Wed, 18 Jun 2025 00:25:52 +0900 Subject: [PATCH 03/37] chore: enable assembly signing for debug build (#2774) --- build/common.props | 5 ++++- .../Properties/AssemblyInfo.cs | 4 ---- .../Properties/AssemblyInfo.cs | 4 ---- .../Properties/AssemblyInfo.cs | 4 ---- src/BenchmarkDotNet/Properties/AssemblyInfo.cs | 13 ------------- 5 files changed, 4 insertions(+), 26 deletions(-) diff --git a/build/common.props b/build/common.props index e9502fcad8..b3e5fa2561 100644 --- a/build/common.props +++ b/build/common.props @@ -31,9 +31,12 @@ - + $(MSBuildThisFileDirectory)strongNameKey.snk true + + + true 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.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/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/Properties/AssemblyInfo.cs b/src/BenchmarkDotNet/Properties/AssemblyInfo.cs index ee7077a55e..bec0dbbc48 100644 --- a/src/BenchmarkDotNet/Properties/AssemblyInfo.cs +++ b/src/BenchmarkDotNet/Properties/AssemblyInfo.cs @@ -1,15 +1,12 @@ using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -#if RELEASE using BenchmarkDotNet.Properties; -#endif [assembly: Guid("cbba82d3-e650-407f-a0f0-767891d4f04c")] [assembly: CLSCompliant(true)] -#if RELEASE [assembly: InternalsVisibleTo("BenchmarkDotNet.Tests,PublicKey=" + BenchmarkDotNetInfo.PublicKey)] [assembly: InternalsVisibleTo("BenchmarkDotNet.IntegrationTests,PublicKey=" + BenchmarkDotNetInfo.PublicKey)] [assembly: InternalsVisibleTo("BenchmarkDotNet.Diagnostics.Windows,PublicKey=" + BenchmarkDotNetInfo.PublicKey)] @@ -18,13 +15,3 @@ [assembly: InternalsVisibleTo("BenchmarkDotNet.IntegrationTests.ManualRunning,PublicKey=" + BenchmarkDotNetInfo.PublicKey)] [assembly: InternalsVisibleTo("BenchmarkDotNet.IntegrationTests.ManualRunning.MultipleFrameworks,PublicKey=" + BenchmarkDotNetInfo.PublicKey)] [assembly: InternalsVisibleTo("BenchmarkDotNet.TestAdapter,PublicKey=" + BenchmarkDotNetInfo.PublicKey)] -#else -[assembly: InternalsVisibleTo("BenchmarkDotNet.Tests")] -[assembly: InternalsVisibleTo("BenchmarkDotNet.IntegrationTests")] -[assembly: InternalsVisibleTo("BenchmarkDotNet.Diagnostics.Windows")] -[assembly: InternalsVisibleTo("BenchmarkDotNet.Diagnostics.dotTrace")] -[assembly: InternalsVisibleTo("BenchmarkDotNet.Diagnostics.dotMemory")] -[assembly: InternalsVisibleTo("BenchmarkDotNet.IntegrationTests.ManualRunning")] -[assembly: InternalsVisibleTo("BenchmarkDotNet.IntegrationTests.ManualRunning.MultipleFrameworks")] -[assembly: InternalsVisibleTo("BenchmarkDotNet.TestAdapter")] -#endif \ No newline at end of file From 799ecfc514f310efcd93d274199795f2ae4e276e Mon Sep 17 00:00:00 2001 From: filzrev <103790468+filzrev@users.noreply.github.com> Date: Mon, 23 Jun 2025 07:03:13 +0900 Subject: [PATCH 04/37] fix: Console logs are outputted twice when using TestAdapter (#2790) * chore: fix issue that console logs are outputted twice when using testadapter * chore: fix code that are pointed out by code review --- src/BenchmarkDotNet.TestAdapter/BenchmarkExecutor.cs | 6 +++++- src/BenchmarkDotNet/Configs/ManualConfig.cs | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/BenchmarkDotNet.TestAdapter/BenchmarkExecutor.cs b/src/BenchmarkDotNet.TestAdapter/BenchmarkExecutor.cs index f1cd64f34e..dc3e7ee832 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; @@ -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/Configs/ManualConfig.cs b/src/BenchmarkDotNet/Configs/ManualConfig.cs index cdfb64a234..4ececd28f1 100644 --- a/src/BenchmarkDotNet/Configs/ManualConfig.cs +++ b/src/BenchmarkDotNet/Configs/ManualConfig.cs @@ -327,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(); From b6de7258d5dcff845b9e1ecce023c1796c3ba168 Mon Sep 17 00:00:00 2001 From: mriehm <3916550+mriehm@users.noreply.github.com> Date: Wed, 25 Jun 2025 12:52:48 -0500 Subject: [PATCH 05/37] Lowercase programName in NativeMemoryLogParser.cs (#2795) Co-authored-by: mriehm --- .../Tracing/NativeMemoryLogParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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), From aeedf36e92e7b107f5f999e08fa33a00210a7b67 Mon Sep 17 00:00:00 2001 From: filzrev <103790468+filzrev@users.noreply.github.com> Date: Thu, 26 Jun 2025 15:41:16 +0900 Subject: [PATCH 06/37] chore: fix x86 disassembler error for net462 (#2792) --- .../DisassemblyDiagnoserTests.cs | 47 ++++++++++++------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/tests/BenchmarkDotNet.IntegrationTests/DisassemblyDiagnoserTests.cs b/tests/BenchmarkDotNet.IntegrationTests/DisassemblyDiagnoserTests.cs index dbd220f007..488d76ca31 100644 --- a/tests/BenchmarkDotNet.IntegrationTests/DisassemblyDiagnoserTests.cs +++ b/tests/BenchmarkDotNet.IntegrationTests/DisassemblyDiagnoserTests.cs @@ -94,16 +94,20 @@ public void CanDisassembleAllMethodCalls(Jit jit, Platform platform, Runtime run { if (OsDetector.IsMacOS()) return; // currently not supported + var printSource = IsPrintSourceSupported(platform); var disassemblyDiagnoser = new DisassemblyDiagnoser( - new DisassemblyDiagnoserConfig(printSource: true, maxDepth: 3)); + new DisassemblyDiagnoserConfig(printSource: printSource, maxDepth: 3)); CanExecute(CreateConfig(jit, platform, runtime, disassemblyDiagnoser, RunStrategy.ColdStart)); - AssertDisassembled(disassemblyDiagnoser, $"{nameof(WithCalls.Benchmark)}(Int32)"); - AssertDisassembled(disassemblyDiagnoser, $"{nameof(WithCalls.Benchmark)}(Boolean)"); - AssertDisassembled(disassemblyDiagnoser, $"{nameof(WithCalls.Static)}()"); - AssertDisassembled(disassemblyDiagnoser, $"{nameof(WithCalls.Instance)}()"); - AssertDisassembled(disassemblyDiagnoser, $"{nameof(WithCalls.Recursive)}()"); + DisassemblyResult result = disassemblyDiagnoser.Results.Single().Value; + + Assert.Empty(result.Errors); + AssertDisassemblyResult(result, $"{nameof(WithCalls.Benchmark)}(Int32)"); + AssertDisassemblyResult(result, $"{nameof(WithCalls.Benchmark)}(Boolean)"); + AssertDisassemblyResult(result, $"{nameof(WithCalls.Static)}()"); + AssertDisassemblyResult(result, $"{nameof(WithCalls.Instance)}()"); + AssertDisassemblyResult(result, $"{nameof(WithCalls.Recursive)}()"); } [Theory] @@ -113,16 +117,20 @@ public void CanDisassembleAllMethodCallsUsingFilters(Jit jit, Platform platform, { if (OsDetector.IsMacOS()) return; // currently not supported + var printSource = IsPrintSourceSupported(platform); var disassemblyDiagnoser = new DisassemblyDiagnoser( - new DisassemblyDiagnoserConfig(printSource: true, maxDepth: 1, filters: new[] { "*WithCalls*" })); + new DisassemblyDiagnoserConfig(printSource: printSource, maxDepth: 1, filters: new[] { "*WithCalls*" })); CanExecute(CreateConfig(jit, platform, runtime, disassemblyDiagnoser, RunStrategy.ColdStart)); - AssertDisassembled(disassemblyDiagnoser, $"{nameof(WithCalls.Benchmark)}(Int32)"); - AssertDisassembled(disassemblyDiagnoser, $"{nameof(WithCalls.Benchmark)}(Boolean)"); - AssertDisassembled(disassemblyDiagnoser, $"{nameof(WithCalls.Static)}()"); - AssertDisassembled(disassemblyDiagnoser, $"{nameof(WithCalls.Instance)}()"); - AssertDisassembled(disassemblyDiagnoser, $"{nameof(WithCalls.Recursive)}()"); + DisassemblyResult result = disassemblyDiagnoser.Results.Single().Value; + + Assert.Empty(result.Errors); + AssertDisassemblyResult(result, $"{nameof(WithCalls.Benchmark)}(Int32)"); + AssertDisassemblyResult(result, $"{nameof(WithCalls.Benchmark)}(Boolean)"); + AssertDisassemblyResult(result, $"{nameof(WithCalls.Static)}()"); + AssertDisassemblyResult(result, $"{nameof(WithCalls.Instance)}()"); + AssertDisassemblyResult(result, $"{nameof(WithCalls.Recursive)}()"); } public class Generic where T : new() @@ -138,13 +146,15 @@ public void CanDisassembleGenericTypes(Jit jit, Platform platform, Runtime runti { if (OsDetector.IsMacOS()) return; // currently not supported + var printSource = IsPrintSourceSupported(platform); var disassemblyDiagnoser = new DisassemblyDiagnoser( - new DisassemblyDiagnoserConfig(printSource: true, maxDepth: 3)); + new DisassemblyDiagnoserConfig(printSource: printSource, maxDepth: 3)); CanExecute>(CreateConfig(jit, platform, runtime, disassemblyDiagnoser, RunStrategy.Monitoring)); var result = disassemblyDiagnoser.Results.Values.Single(); + Assert.Empty(result.Errors); Assert.Contains(result.Methods, method => method.Maps.Any(map => map.SourceCodes.OfType().Any())); } @@ -160,13 +170,15 @@ public void CanDisassembleInlinableBenchmarks(Jit jit, Platform platform, Runtim { if (OsDetector.IsMacOS()) return; // currently not supported + var printSource = IsPrintSourceSupported(platform); var disassemblyDiagnoser = new DisassemblyDiagnoser( - new DisassemblyDiagnoserConfig(printSource: true, maxDepth: 3)); + new DisassemblyDiagnoserConfig(printSource: printSource, maxDepth: 3)); CanExecute(CreateConfig(jit, platform, runtime, disassemblyDiagnoser, RunStrategy.Monitoring)); var disassemblyResult = disassemblyDiagnoser.Results.Values.Single(result => result.Methods.Count(method => method.Name.Contains(nameof(WithInlineable.JustReturn))) == 1); + Assert.Empty(disassemblyResult.Errors); Assert.Contains(disassemblyResult.Methods, method => method.Maps.Any(map => map.SourceCodes.OfType().All(asm => asm.ToString().Contains("ret")))); } @@ -181,12 +193,13 @@ private IConfig CreateConfig(Jit jit, Platform platform, Runtime runtime, IDiagn .AddDiagnoser(disassemblyDiagnoser) .AddLogger(new OutputLogger(Output)); - private void AssertDisassembled(DisassemblyDiagnoser diagnoser, string methodSignature) + private void AssertDisassemblyResult(DisassemblyResult result, string methodSignature) { - DisassemblyResult result = diagnoser.Results.Single().Value; - Assert.Contains(methodSignature, result.Methods.Select(m => m.Name.Split('.').Last()).ToArray()); Assert.Contains(result.Methods.Single(m => m.Name.EndsWith(methodSignature)).Maps, map => map.SourceCodes.Any()); } + + private static bool IsPrintSourceSupported(Platform platform) + => platform != Platform.X86; // Workaround for https://github.com/dotnet/BenchmarkDotNet/issues/2789 } } \ No newline at end of file From da43e0bca46c6d4a6b7ba8389a333d980149e88c Mon Sep 17 00:00:00 2001 From: filzrev <103790468+filzrev@users.noreply.github.com> Date: Sat, 28 Jun 2025 14:19:04 +0900 Subject: [PATCH 07/37] chore: Replace StyleCop.Analyzer library to unstable version (#2796) * chore: replace stylecop to unstable package * chore: fix target-typed new statement spacing that raise SA1000 errors by stylecop * fix: SA1141 value tuple error that raised by stylecop --- build/common.props | 3 ++- .../JitStatsDiagnoser.cs | 6 ++--- .../BenchmarkExecutor.cs | 2 +- .../Remoting/BenchmarkExecutorWrapper.cs | 2 +- .../VSTestEventProcessor.cs | 6 ++--- .../Columns/StatisticalTestColumn.cs | 4 ++-- .../ConsoleArguments/ConfigParser.cs | 2 +- .../Detectors/Cpu/HardwareIntrinsics.cs | 2 +- src/BenchmarkDotNet/Detectors/CpuDetector.cs | 4 ++-- src/BenchmarkDotNet/Detectors/OsDetector.cs | 4 ++-- .../Diagnosers/PerfCollectProfiler.cs | 8 +++---- .../Disassemblers/Arm64Disassembler.cs | 2 +- .../Arm64InstructionFormatter.cs | 2 +- .../Disassemblers/IntelDisassembler.cs | 2 +- .../SameArchitectureDisassembler.cs | 2 +- src/BenchmarkDotNet/Engines/Engine.cs | 6 ++--- .../Engines/EngineActualStage.cs | 4 ++-- .../Engines/EngineWarmupStage.cs | 4 ++-- .../Environments/HostEnvironmentInfo.cs | 2 +- .../Environments/Runtimes/CoreRuntime.cs | 22 +++++++++---------- .../Environments/Runtimes/MonoRuntime.cs | 12 +++++----- src/BenchmarkDotNet/Helpers/AwaitHelper.cs | 2 +- src/BenchmarkDotNet/Helpers/UnitHelper.cs | 2 +- src/BenchmarkDotNet/Helpers/XUnitHelper.cs | 2 +- src/BenchmarkDotNet/Jobs/EnvironmentMode.cs | 2 +- src/BenchmarkDotNet/Jobs/RunMode.cs | 2 +- src/BenchmarkDotNet/Loggers/Broker.cs | 4 ++-- src/BenchmarkDotNet/Models/BdnSchema.cs | 2 +- src/BenchmarkDotNet/Order/CategoryComparer.cs | 2 +- .../Properties/BenchmarkDotNetInfo.cs | 4 ++-- .../Running/BenchmarkRunnerClean.cs | 4 ++-- .../Toolchains/DotNetCli/DotNetCliCommand.cs | 4 ++-- .../DotNetCli/DotNetCliCommandExecutor.cs | 2 +- .../Toolchains/DotNetCli/DotNetCliExecutor.cs | 8 +++---- .../DotNetCli/NetCoreAppSettings.cs | 22 +++++++++---------- src/BenchmarkDotNet/Toolchains/Executor.cs | 12 +++++----- .../Emitters/RunnableEmitter.cs | 2 +- .../Toolchains/MonoWasm/WasmExecutor.cs | 4 ++-- .../Validators/ParamsValidator.cs | 2 +- .../AllSetupAndCleanupTest.cs | 4 ++-- .../AsyncBenchmarksTests.cs | 6 ++--- .../CustomEngineTests.cs | 4 ++-- .../InProcessEmitTest.cs | 2 +- .../MemoryDiagnoserTests.cs | 4 ++-- .../Builders/HostEnvironmentInfoBuilder.cs | 4 ++-- tests/BenchmarkDotNet.Tests/KnownIssue.cs | 2 +- .../Mocks/Toolchain/MockToolchain.cs | 2 +- .../Order/DefaultOrdererTests.cs | 4 ++-- .../ParamsSourceTests.cs | 2 +- .../Perfonar/Infra/PerfonarMock.cs | 2 +- .../Perfonar/PerfonarTests.cs | 2 +- .../BenchmarkDotNet.Tests/ReflectionTests.cs | 2 +- .../Validators/ExecutionValidatorTests.cs | 4 ++-- 53 files changed, 114 insertions(+), 113 deletions(-) diff --git a/build/common.props b/build/common.props index b3e5fa2561..b33bc4d863 100644 --- a/build/common.props +++ b/build/common.props @@ -65,8 +65,9 @@ all runtime; build; native; contentfiles; analyzers - + all + runtime; build; native; contentfiles; analyzers 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.TestAdapter/BenchmarkExecutor.cs b/src/BenchmarkDotNet.TestAdapter/BenchmarkExecutor.cs index dc3e7ee832..aad574ed76 100644 --- a/src/BenchmarkDotNet.TestAdapter/BenchmarkExecutor.cs +++ b/src/BenchmarkDotNet.TestAdapter/BenchmarkExecutor.cs @@ -15,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. 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/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/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/ConsoleArguments/ConfigParser.cs b/src/BenchmarkDotNet/ConsoleArguments/ConfigParser.cs index 9448dc409d..ac61ddb5f1 100644 --- a/src/BenchmarkDotNet/ConsoleArguments/ConfigParser.cs +++ b/src/BenchmarkDotNet/ConsoleArguments/ConfigParser.cs @@ -95,7 +95,7 @@ public static (bool isSuccess, IConfig config, CommandLineOptions options) Parse private static (bool Success, string[] ExpandedTokens) ExpandResponseFile(string[] args, ILogger logger) { - List result = new (); + List result = new(); foreach (var arg in args) { if (arg.StartsWith("@")) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/HardwareIntrinsics.cs b/src/BenchmarkDotNet/Detectors/Cpu/HardwareIntrinsics.cs index a8c7655272..265143a356 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/HardwareIntrinsics.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/HardwareIntrinsics.cs @@ -97,7 +97,7 @@ static IEnumerable GetCurrentProcessInstructionSets(Platform platform) private static string GetShortAvx512Representation() { - StringBuilder avx512 = new ("AVX-512F"); + StringBuilder avx512 = new("AVX-512F"); if (IsX86Avx512CDSupported) avx512.Append("+CD"); if (IsX86Avx512BWSupported) avx512.Append("+BW"); if (IsX86Avx512DQSupported) avx512.Append("+DQ"); diff --git a/src/BenchmarkDotNet/Detectors/CpuDetector.cs b/src/BenchmarkDotNet/Detectors/CpuDetector.cs index 73d371a9fa..0cb583a95a 100644 --- a/src/BenchmarkDotNet/Detectors/CpuDetector.cs +++ b/src/BenchmarkDotNet/Detectors/CpuDetector.cs @@ -11,12 +11,12 @@ namespace BenchmarkDotNet.Detectors; public class CpuDetector(params ICpuDetector[] detectors) : ICpuDetector { - public static CpuDetector CrossPlatform => new ( + public static CpuDetector CrossPlatform => new( new WindowsCpuDetector(), new LinuxCpuDetector(), new MacOsCpuDetector()); - private static readonly Lazy LazyCpu = new (() => CrossPlatform.Detect()); + private static readonly Lazy LazyCpu = new(() => CrossPlatform.Detect()); public static CpuInfo? Cpu => LazyCpu.Value; public bool IsApplicable() => detectors.Any(loader => loader.IsApplicable()); diff --git a/src/BenchmarkDotNet/Detectors/OsDetector.cs b/src/BenchmarkDotNet/Detectors/OsDetector.cs index 6d47db1d4a..5deeea03a6 100644 --- a/src/BenchmarkDotNet/Detectors/OsDetector.cs +++ b/src/BenchmarkDotNet/Detectors/OsDetector.cs @@ -13,13 +13,13 @@ namespace BenchmarkDotNet.Detectors; public class OsDetector { - public static readonly OsDetector Instance = new (); + 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); + private readonly Lazy os = new(ResolveOs); public static OsInfo GetOs() => Instance.os.Value; private static OsInfo ResolveOs() diff --git a/src/BenchmarkDotNet/Diagnosers/PerfCollectProfiler.cs b/src/BenchmarkDotNet/Diagnosers/PerfCollectProfiler.cs index f1b584ce09..9a3d348665 100644 --- a/src/BenchmarkDotNet/Diagnosers/PerfCollectProfiler.cs +++ b/src/BenchmarkDotNet/Diagnosers/PerfCollectProfiler.cs @@ -32,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; @@ -228,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, @@ -253,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/Disassemblers/Arm64Disassembler.cs b/src/BenchmarkDotNet/Disassemblers/Arm64Disassembler.cs index ce5176c4ca..7c10765b8e 100644 --- a/src/BenchmarkDotNet/Disassemblers/Arm64Disassembler.cs +++ b/src/BenchmarkDotNet/Disassemblers/Arm64Disassembler.cs @@ -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/IntelDisassembler.cs b/src/BenchmarkDotNet/Disassemblers/IntelDisassembler.cs index 3fd541528a..eb0d5ce3f6 100644 --- a/src/BenchmarkDotNet/Disassemblers/IntelDisassembler.cs +++ b/src/BenchmarkDotNet/Disassemblers/IntelDisassembler.cs @@ -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 8ce026a5dd..1a4835484d 100644 --- a/src/BenchmarkDotNet/Disassemblers/SameArchitectureDisassembler.cs +++ b/src/BenchmarkDotNet/Disassemblers/SameArchitectureDisassembler.cs @@ -26,7 +26,7 @@ private static ClrMdV3Disassembler 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/Engines/Engine.cs b/src/BenchmarkDotNet/Engines/Engine.cs index 44077fcc38..1fc1b96dfd 100644 --- a/src/BenchmarkDotNet/Engines/Engine.cs +++ b/src/BenchmarkDotNet/Engines/Engine.cs @@ -42,7 +42,7 @@ public class Engine : IEngine private bool EvaluateOverhead { get; } private bool MemoryRandomization { get; } - private readonly List jittingMeasurements = new (10); + private readonly List jittingMeasurements = new(10); private readonly bool includeExtraStats; private readonly Random random; @@ -351,8 +351,8 @@ 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); + private readonly object hangLock = new(); + private readonly ManualResetEventSlim enteredFinalizerEvent = new(false); ~Impl() { diff --git a/src/BenchmarkDotNet/Engines/EngineActualStage.cs b/src/BenchmarkDotNet/Engines/EngineActualStage.cs index 482208ac68..173f4e3432 100644 --- a/src/BenchmarkDotNet/Engines/EngineActualStage.cs +++ b/src/BenchmarkDotNet/Engines/EngineActualStage.cs @@ -47,7 +47,7 @@ public EngineActualStageAuto(Job targetJob, IResolver resolver, IterationMode it measurementsForStatistics = GetMeasurementList(); } - internal override List GetMeasurementList() => new (maxIterationCount); + internal override List GetMeasurementList() => new(maxIterationCount); internal override bool GetShouldRunIteration(List measurements, ref long invokeCount) { @@ -88,7 +88,7 @@ internal sealed class EngineActualStageSpecific(int maxIterationCount, Iteration { private int iterationCount = 0; - internal override List GetMeasurementList() => new (maxIterationCount); + internal override List GetMeasurementList() => new(maxIterationCount); internal override bool GetShouldRunIteration(List measurements, ref long invokeCount) => ++iterationCount <= maxIterationCount; diff --git a/src/BenchmarkDotNet/Engines/EngineWarmupStage.cs b/src/BenchmarkDotNet/Engines/EngineWarmupStage.cs index a82ca61a91..26a1f1f68b 100644 --- a/src/BenchmarkDotNet/Engines/EngineWarmupStage.cs +++ b/src/BenchmarkDotNet/Engines/EngineWarmupStage.cs @@ -35,7 +35,7 @@ internal sealed class EngineWarmupStageAuto(IterationMode iterationMode, int min private readonly int minIterationCount = minIterationCount; private readonly int maxIterationCount = maxIterationCount; - internal override List GetMeasurementList() => new (maxIterationCount); + internal override List GetMeasurementList() => new(maxIterationCount); internal override bool GetShouldRunIteration(List measurements, ref long invokeCount) { @@ -70,7 +70,7 @@ internal sealed class EngineWarmupStageSpecific(int maxIterationCount, Iteration { private int iterationCount = 0; - internal override List GetMeasurementList() => new (maxIterationCount); + internal override List GetMeasurementList() => new(maxIterationCount); internal override bool GetShouldRunIteration(List measurements, ref long invokeCount) => ++iterationCount <= maxIterationCount; diff --git a/src/BenchmarkDotNet/Environments/HostEnvironmentInfo.cs b/src/BenchmarkDotNet/Environments/HostEnvironmentInfo.cs index 0abb7b8e49..8291354090 100644 --- a/src/BenchmarkDotNet/Environments/HostEnvironmentInfo.cs +++ b/src/BenchmarkDotNet/Environments/HostEnvironmentInfo.cs @@ -133,7 +133,7 @@ public static string GetInformation() return sb.ToString(); } - internal BdnHostInfo ToPerfonar() => new () + internal BdnHostInfo ToPerfonar() => new() { Cpu = Cpu.Value, Os = Os.Value, diff --git a/src/BenchmarkDotNet/Environments/Runtimes/CoreRuntime.cs b/src/BenchmarkDotNet/Environments/Runtimes/CoreRuntime.cs index 53476adbc8..954a8ac7dc 100644 --- a/src/BenchmarkDotNet/Environments/Runtimes/CoreRuntime.cs +++ b/src/BenchmarkDotNet/Environments/Runtimes/CoreRuntime.cs @@ -11,17 +11,17 @@ namespace BenchmarkDotNet.Environments { public class CoreRuntime : Runtime { - public static readonly CoreRuntime Core20 = new (RuntimeMoniker.NetCoreApp20, "netcoreapp2.0", ".NET Core 2.0"); - public static readonly CoreRuntime Core21 = new (RuntimeMoniker.NetCoreApp21, "netcoreapp2.1", ".NET Core 2.1"); - public static readonly CoreRuntime Core22 = new (RuntimeMoniker.NetCoreApp22, "netcoreapp2.2", ".NET Core 2.2"); - public static readonly CoreRuntime Core30 = new (RuntimeMoniker.NetCoreApp30, "netcoreapp3.0", ".NET Core 3.0"); - public static readonly CoreRuntime Core31 = new (RuntimeMoniker.NetCoreApp31, "netcoreapp3.1", ".NET Core 3.1"); - public static readonly CoreRuntime Core50 = new (RuntimeMoniker.Net50, "net5.0", ".NET 5.0"); - public static readonly CoreRuntime Core60 = new (RuntimeMoniker.Net60, "net6.0", ".NET 6.0"); - public static readonly CoreRuntime Core70 = new (RuntimeMoniker.Net70, "net7.0", ".NET 7.0"); - public static readonly CoreRuntime Core80 = new (RuntimeMoniker.Net80, "net8.0", ".NET 8.0"); - public static readonly CoreRuntime Core90 = new (RuntimeMoniker.Net90, "net9.0", ".NET 9.0"); - public static readonly CoreRuntime Core10_0 = new (RuntimeMoniker.Net10_0, "net10.0", ".NET 10.0"); + public static readonly CoreRuntime Core20 = new(RuntimeMoniker.NetCoreApp20, "netcoreapp2.0", ".NET Core 2.0"); + public static readonly CoreRuntime Core21 = new(RuntimeMoniker.NetCoreApp21, "netcoreapp2.1", ".NET Core 2.1"); + public static readonly CoreRuntime Core22 = new(RuntimeMoniker.NetCoreApp22, "netcoreapp2.2", ".NET Core 2.2"); + public static readonly CoreRuntime Core30 = new(RuntimeMoniker.NetCoreApp30, "netcoreapp3.0", ".NET Core 3.0"); + public static readonly CoreRuntime Core31 = new(RuntimeMoniker.NetCoreApp31, "netcoreapp3.1", ".NET Core 3.1"); + public static readonly CoreRuntime Core50 = new(RuntimeMoniker.Net50, "net5.0", ".NET 5.0"); + public static readonly CoreRuntime Core60 = new(RuntimeMoniker.Net60, "net6.0", ".NET 6.0"); + public static readonly CoreRuntime Core70 = new(RuntimeMoniker.Net70, "net7.0", ".NET 7.0"); + public static readonly CoreRuntime Core80 = new(RuntimeMoniker.Net80, "net8.0", ".NET 8.0"); + public static readonly CoreRuntime Core90 = new(RuntimeMoniker.Net90, "net9.0", ".NET 9.0"); + public static readonly CoreRuntime Core10_0 = new(RuntimeMoniker.Net10_0, "net10.0", ".NET 10.0"); public static CoreRuntime Latest => Core10_0; // when dotnet/runtime branches for 11.0, this will need to get updated diff --git a/src/BenchmarkDotNet/Environments/Runtimes/MonoRuntime.cs b/src/BenchmarkDotNet/Environments/Runtimes/MonoRuntime.cs index 15b5a02669..c1c37db2cd 100644 --- a/src/BenchmarkDotNet/Environments/Runtimes/MonoRuntime.cs +++ b/src/BenchmarkDotNet/Environments/Runtimes/MonoRuntime.cs @@ -5,12 +5,12 @@ namespace BenchmarkDotNet.Environments { public class MonoRuntime : Runtime, IEquatable { - public static readonly MonoRuntime Default = new ("Mono"); - public static readonly MonoRuntime Mono60 = new ("Mono with .NET 6.0", RuntimeMoniker.Mono60, "net6.0", isDotNetBuiltIn: true); - public static readonly MonoRuntime Mono70 = new ("Mono with .NET 7.0", RuntimeMoniker.Mono70, "net7.0", isDotNetBuiltIn: true); - public static readonly MonoRuntime Mono80 = new ("Mono with .NET 8.0", RuntimeMoniker.Mono80, "net8.0", isDotNetBuiltIn: true); - public static readonly MonoRuntime Mono90 = new ("Mono with .NET 9.0", RuntimeMoniker.Mono90, "net9.0", isDotNetBuiltIn: true); - public static readonly MonoRuntime Mono10_0 = new ("Mono with .NET 10.0", RuntimeMoniker.Mono10_0, "net10.0", isDotNetBuiltIn: true); + public static readonly MonoRuntime Default = new("Mono"); + public static readonly MonoRuntime Mono60 = new("Mono with .NET 6.0", RuntimeMoniker.Mono60, "net6.0", isDotNetBuiltIn: true); + public static readonly MonoRuntime Mono70 = new("Mono with .NET 7.0", RuntimeMoniker.Mono70, "net7.0", isDotNetBuiltIn: true); + public static readonly MonoRuntime Mono80 = new("Mono with .NET 8.0", RuntimeMoniker.Mono80, "net8.0", isDotNetBuiltIn: true); + public static readonly MonoRuntime Mono90 = new("Mono with .NET 9.0", RuntimeMoniker.Mono90, "net9.0", isDotNetBuiltIn: true); + public static readonly MonoRuntime Mono10_0 = new("Mono with .NET 10.0", RuntimeMoniker.Mono10_0, "net10.0", isDotNetBuiltIn: true); public string CustomPath { get; } diff --git a/src/BenchmarkDotNet/Helpers/AwaitHelper.cs b/src/BenchmarkDotNet/Helpers/AwaitHelper.cs index 8d16fb716a..98837a7ea6 100644 --- a/src/BenchmarkDotNet/Helpers/AwaitHelper.cs +++ b/src/BenchmarkDotNet/Helpers/AwaitHelper.cs @@ -22,7 +22,7 @@ private class ValueTaskWaiter private ValueTaskWaiter() { - resetEvent = new (); + resetEvent = new(); awaiterCallback = resetEvent.Set; } diff --git a/src/BenchmarkDotNet/Helpers/UnitHelper.cs b/src/BenchmarkDotNet/Helpers/UnitHelper.cs index ae76faf101..9575401776 100644 --- a/src/BenchmarkDotNet/Helpers/UnitHelper.cs +++ b/src/BenchmarkDotNet/Helpers/UnitHelper.cs @@ -5,7 +5,7 @@ namespace BenchmarkDotNet.Helpers; public static class UnitHelper { - public static readonly UnitPresentation DefaultPresentation = new (true, 0, gap: true); + public static readonly UnitPresentation DefaultPresentation = new(true, 0, gap: true); public static string ToDefaultString(this TimeInterval timeInterval, string? format = null) => timeInterval.ToString(format, null, DefaultPresentation); } \ No newline at end of file diff --git a/src/BenchmarkDotNet/Helpers/XUnitHelper.cs b/src/BenchmarkDotNet/Helpers/XUnitHelper.cs index 559113a8f4..7f7415bdfa 100644 --- a/src/BenchmarkDotNet/Helpers/XUnitHelper.cs +++ b/src/BenchmarkDotNet/Helpers/XUnitHelper.cs @@ -6,5 +6,5 @@ namespace BenchmarkDotNet.Helpers; internal static class XUnitHelper { public static Lazy IsIntegrationTest = - new (() => AppDomain.CurrentDomain.GetAssemblies().Any(assembly => assembly.GetName().Name == "BenchmarkDotNet.IntegrationTests")); + new(() => AppDomain.CurrentDomain.GetAssemblies().Any(assembly => assembly.GetName().Name == "BenchmarkDotNet.IntegrationTests")); } \ No newline at end of file diff --git a/src/BenchmarkDotNet/Jobs/EnvironmentMode.cs b/src/BenchmarkDotNet/Jobs/EnvironmentMode.cs index 104ec25150..0d5113db48 100644 --- a/src/BenchmarkDotNet/Jobs/EnvironmentMode.cs +++ b/src/BenchmarkDotNet/Jobs/EnvironmentMode.cs @@ -139,7 +139,7 @@ public void SetEnvironmentVariable(EnvironmentVariable variable) internal Runtime GetRuntime() => HasValue(RuntimeCharacteristic) ? Runtime : RuntimeInformation.GetCurrentRuntime(); - internal BdnEnvironment ToPerfonar() => new () + internal BdnEnvironment ToPerfonar() => new() { Jit = HasValue(JitCharacteristic) ? Jit : null, Runtime = HasValue(RuntimeCharacteristic) ? Runtime?.RuntimeMoniker : null, diff --git a/src/BenchmarkDotNet/Jobs/RunMode.cs b/src/BenchmarkDotNet/Jobs/RunMode.cs index 66ab07edb1..3e1e56c22f 100644 --- a/src/BenchmarkDotNet/Jobs/RunMode.cs +++ b/src/BenchmarkDotNet/Jobs/RunMode.cs @@ -191,7 +191,7 @@ public bool MemoryRandomization set => MemoryRandomizationCharacteristic[this] = value; } - internal BdnExecution ToPerfonar() => new () + internal BdnExecution ToPerfonar() => new() { LaunchCount = HasValue(LaunchCountCharacteristic) ? LaunchCount : null, WarmupCount = HasValue(WarmupCountCharacteristic) ? WarmupCount : null, diff --git a/src/BenchmarkDotNet/Loggers/Broker.cs b/src/BenchmarkDotNet/Loggers/Broker.cs index dcec4c0d56..277cd3c430 100644 --- a/src/BenchmarkDotNet/Loggers/Broker.cs +++ b/src/BenchmarkDotNet/Loggers/Broker.cs @@ -76,8 +76,8 @@ private void OnProcessExited(object sender, EventArgs e) private void ProcessDataBlocking() { - using StreamReader reader = new (inputFromBenchmark, AnonymousPipesHost.UTF8NoBOM, detectEncodingFromByteOrderMarks: false); - using StreamWriter writer = new (acknowledgments, AnonymousPipesHost.UTF8NoBOM, bufferSize: 1); + using StreamReader reader = new(inputFromBenchmark, AnonymousPipesHost.UTF8NoBOM, detectEncodingFromByteOrderMarks: false); + using StreamWriter writer = new(acknowledgments, AnonymousPipesHost.UTF8NoBOM, bufferSize: 1); // Flush the data to the Stream after each write, otherwise the client will wait for input endlessly! writer.AutoFlush = true; diff --git a/src/BenchmarkDotNet/Models/BdnSchema.cs b/src/BenchmarkDotNet/Models/BdnSchema.cs index 862bda09c6..3006b702de 100644 --- a/src/BenchmarkDotNet/Models/BdnSchema.cs +++ b/src/BenchmarkDotNet/Models/BdnSchema.cs @@ -4,7 +4,7 @@ namespace BenchmarkDotNet.Models; internal class BdnSchema : PerfonarSchema { - public static readonly BdnSchema Instance = new (); + public static readonly BdnSchema Instance = new(); private BdnSchema() : base("bdn") { diff --git a/src/BenchmarkDotNet/Order/CategoryComparer.cs b/src/BenchmarkDotNet/Order/CategoryComparer.cs index a64fb7c588..ac11695a59 100644 --- a/src/BenchmarkDotNet/Order/CategoryComparer.cs +++ b/src/BenchmarkDotNet/Order/CategoryComparer.cs @@ -7,7 +7,7 @@ namespace BenchmarkDotNet.Order internal class CategoryComparer : IComparer { private const string Separator = "§"; - public static readonly CategoryComparer Instance = new (); + public static readonly CategoryComparer Instance = new(); public int Compare(string[] x, string[] y) { diff --git a/src/BenchmarkDotNet/Properties/BenchmarkDotNetInfo.cs b/src/BenchmarkDotNet/Properties/BenchmarkDotNetInfo.cs index eac67a37eb..0941fa1cc3 100644 --- a/src/BenchmarkDotNet/Properties/BenchmarkDotNetInfo.cs +++ b/src/BenchmarkDotNet/Properties/BenchmarkDotNetInfo.cs @@ -9,7 +9,7 @@ public class BenchmarkDotNetInfo { public const string BenchmarkDotNetCaption = "BenchmarkDotNet"; - private static readonly Lazy LazyInstance = new (() => + private static readonly Lazy LazyInstance = new(() => { var assembly = typeof(BenchmarkDotNetInfo).GetTypeInfo().Assembly; var assemblyVersion = assembly.GetName().Version; @@ -19,7 +19,7 @@ public class BenchmarkDotNetInfo public static BenchmarkDotNetInfo Instance { get; } = LazyInstance.Value; - public EngineInfo GetBdnEngineInfo() => new () + public EngineInfo GetBdnEngineInfo() => new() { Name = BenchmarkDotNetCaption, Version = BrandVersion diff --git a/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs b/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs index 810e79ab8a..8ed930c1f4 100644 --- a/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs +++ b/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs @@ -622,8 +622,8 @@ private static void LogTotalTime(ILogger logger, TimeSpan time, int executedBenc private static (BenchmarkRunInfo[], List) GetSupportedBenchmarks(BenchmarkRunInfo[] benchmarkRunInfos, IResolver resolver) { - List validationErrors = new (); - List runInfos = new (benchmarkRunInfos.Length); + List validationErrors = new(); + List runInfos = new(benchmarkRunInfos.Length); if (benchmarkRunInfos.Length == 0) { diff --git a/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommand.cs b/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommand.cs index 81f48b4413..bcd921fdd1 100644 --- a/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommand.cs +++ b/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommand.cs @@ -47,10 +47,10 @@ public DotNetCliCommand(string cliPath, string arguments, GenerateResult generat } public DotNetCliCommand WithArguments(string arguments) - => new (CliPath, arguments, GenerateResult, Logger, BuildPartition, EnvironmentVariables, Timeout, logOutput: LogOutput); + => new(CliPath, arguments, GenerateResult, Logger, BuildPartition, EnvironmentVariables, Timeout, logOutput: LogOutput); public DotNetCliCommand WithCliPath(string cliPath) - => new (cliPath, Arguments, GenerateResult, Logger, BuildPartition, EnvironmentVariables, Timeout, logOutput: LogOutput); + => new(cliPath, Arguments, GenerateResult, Logger, BuildPartition, EnvironmentVariables, Timeout, logOutput: LogOutput); [PublicAPI] public BuildResult RestoreThenBuild() diff --git a/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommandExecutor.cs b/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommandExecutor.cs index 5cbff89b47..10df4af9b3 100644 --- a/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommandExecutor.cs +++ b/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommandExecutor.cs @@ -157,7 +157,7 @@ private static string GetDefaultDotNetCliPath() internal static string GetSdkPath(string cliPath) { - DotNetCliCommand cliCommand = new ( + DotNetCliCommand cliCommand = new( cliPath: cliPath, arguments: "--info", generateResult: null, diff --git a/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliExecutor.cs b/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliExecutor.cs index f8bea8236e..d0595fe166 100644 --- a/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliExecutor.cs +++ b/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliExecutor.cs @@ -75,11 +75,11 @@ private ExecuteResult Execute(BenchmarkCase benchmarkCase, startInfo.SetEnvironmentVariables(benchmarkCase, resolver); - using (Process process = new () { StartInfo = startInfo }) - using (ConsoleExitHandler consoleExitHandler = new (process, logger)) - using (AsyncProcessOutputReader processOutputReader = new (process, logOutput: true, logger, readStandardError: false)) + using (Process process = new() { StartInfo = startInfo }) + using (ConsoleExitHandler consoleExitHandler = new(process, logger)) + using (AsyncProcessOutputReader processOutputReader = new(process, logOutput: true, logger, readStandardError: false)) { - Broker broker = new (logger, process, diagnoser, benchmarkCase, benchmarkId, inputFromBenchmark, acknowledgments); + Broker broker = new(logger, process, diagnoser, benchmarkCase, benchmarkId, inputFromBenchmark, acknowledgments); logger.WriteLineInfo($"// Execute: {process.StartInfo.FileName} {process.StartInfo.Arguments} in {process.StartInfo.WorkingDirectory}"); diff --git a/src/BenchmarkDotNet/Toolchains/DotNetCli/NetCoreAppSettings.cs b/src/BenchmarkDotNet/Toolchains/DotNetCli/NetCoreAppSettings.cs index 028e4f2bd3..807e394682 100644 --- a/src/BenchmarkDotNet/Toolchains/DotNetCli/NetCoreAppSettings.cs +++ b/src/BenchmarkDotNet/Toolchains/DotNetCli/NetCoreAppSettings.cs @@ -9,17 +9,17 @@ namespace BenchmarkDotNet.Toolchains.DotNetCli [PublicAPI] public class NetCoreAppSettings { - [PublicAPI] public static readonly NetCoreAppSettings NetCoreApp20 = new ("netcoreapp2.0", null, ".NET Core 2.0"); - [PublicAPI] public static readonly NetCoreAppSettings NetCoreApp21 = new ("netcoreapp2.1", null, ".NET Core 2.1"); - [PublicAPI] public static readonly NetCoreAppSettings NetCoreApp22 = new ("netcoreapp2.2", null, ".NET Core 2.2"); - [PublicAPI] public static readonly NetCoreAppSettings NetCoreApp30 = new ("netcoreapp3.0", null, ".NET Core 3.0"); - [PublicAPI] public static readonly NetCoreAppSettings NetCoreApp31 = new ("netcoreapp3.1", null, ".NET Core 3.1"); - [PublicAPI] public static readonly NetCoreAppSettings NetCoreApp50 = new ("net5.0", null, ".NET 5.0"); - [PublicAPI] public static readonly NetCoreAppSettings NetCoreApp60 = new ("net6.0", null, ".NET 6.0"); - [PublicAPI] public static readonly NetCoreAppSettings NetCoreApp70 = new ("net7.0", null, ".NET 7.0"); - [PublicAPI] public static readonly NetCoreAppSettings NetCoreApp80 = new ("net8.0", null, ".NET 8.0"); - [PublicAPI] public static readonly NetCoreAppSettings NetCoreApp90 = new ("net9.0", null, ".NET 9.0"); - [PublicAPI] public static readonly NetCoreAppSettings NetCoreApp10_0 = new ("net10.0", null, ".NET 10.0"); + [PublicAPI] public static readonly NetCoreAppSettings NetCoreApp20 = new("netcoreapp2.0", null, ".NET Core 2.0"); + [PublicAPI] public static readonly NetCoreAppSettings NetCoreApp21 = new("netcoreapp2.1", null, ".NET Core 2.1"); + [PublicAPI] public static readonly NetCoreAppSettings NetCoreApp22 = new("netcoreapp2.2", null, ".NET Core 2.2"); + [PublicAPI] public static readonly NetCoreAppSettings NetCoreApp30 = new("netcoreapp3.0", null, ".NET Core 3.0"); + [PublicAPI] public static readonly NetCoreAppSettings NetCoreApp31 = new("netcoreapp3.1", null, ".NET Core 3.1"); + [PublicAPI] public static readonly NetCoreAppSettings NetCoreApp50 = new("net5.0", null, ".NET 5.0"); + [PublicAPI] public static readonly NetCoreAppSettings NetCoreApp60 = new("net6.0", null, ".NET 6.0"); + [PublicAPI] public static readonly NetCoreAppSettings NetCoreApp70 = new("net7.0", null, ".NET 7.0"); + [PublicAPI] public static readonly NetCoreAppSettings NetCoreApp80 = new("net8.0", null, ".NET 8.0"); + [PublicAPI] public static readonly NetCoreAppSettings NetCoreApp90 = new("net9.0", null, ".NET 9.0"); + [PublicAPI] public static readonly NetCoreAppSettings NetCoreApp10_0 = new("net10.0", null, ".NET 10.0"); /// /// diff --git a/src/BenchmarkDotNet/Toolchains/Executor.cs b/src/BenchmarkDotNet/Toolchains/Executor.cs index 8fbce03516..adc05e12e6 100644 --- a/src/BenchmarkDotNet/Toolchains/Executor.cs +++ b/src/BenchmarkDotNet/Toolchains/Executor.cs @@ -41,16 +41,16 @@ private static ExecuteResult Execute(BenchmarkCase benchmarkCase, BenchmarkId be { try { - using AnonymousPipeServerStream inputFromBenchmark = new (PipeDirection.In, HandleInheritability.Inheritable); - using AnonymousPipeServerStream acknowledgments = new (PipeDirection.Out, HandleInheritability.Inheritable); + using AnonymousPipeServerStream inputFromBenchmark = new(PipeDirection.In, HandleInheritability.Inheritable); + using AnonymousPipeServerStream acknowledgments = new(PipeDirection.Out, HandleInheritability.Inheritable); string args = benchmarkId.ToArguments(inputFromBenchmark.GetClientHandleAsString(), acknowledgments.GetClientHandleAsString()); - using (Process process = new () { StartInfo = CreateStartInfo(benchmarkCase, artifactsPaths, args, resolver) }) - using (ConsoleExitHandler consoleExitHandler = new (process, logger)) - using (AsyncProcessOutputReader processOutputReader = new (process, logOutput: true, logger, readStandardError: false)) + using (Process process = new() { StartInfo = CreateStartInfo(benchmarkCase, artifactsPaths, args, resolver) }) + using (ConsoleExitHandler consoleExitHandler = new(process, logger)) + using (AsyncProcessOutputReader processOutputReader = new(process, logOutput: true, logger, readStandardError: false)) { - Broker broker = new (logger, process, diagnoser, benchmarkCase, benchmarkId, inputFromBenchmark, acknowledgments); + Broker broker = new(logger, process, diagnoser, benchmarkCase, benchmarkId, inputFromBenchmark, acknowledgments); diagnoser?.Handle(HostSignal.BeforeProcessStart, new DiagnoserActionParameters(process, benchmarkCase, benchmarkId)); diff --git a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/RunnableEmitter.cs b/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/RunnableEmitter.cs index 0e9d386a24..4796c84a77 100644 --- a/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/RunnableEmitter.cs +++ b/src/BenchmarkDotNet/Toolchains/InProcess/Emit/Implementation/Emitters/RunnableEmitter.cs @@ -1046,7 +1046,7 @@ private MethodBuilder EmitRunMethod() { var prepareForRunMethodTemplate = typeof(RunnableReuse).GetMethod(nameof(RunnableReuse.PrepareForRun)) ?? throw new MissingMemberException(nameof(RunnableReuse.PrepareForRun)); - var resultTuple = new ValueTuple(); + (Job, EngineParameters, IEngineFactory) resultTuple = new(); /* .method public hidebysig static diff --git a/src/BenchmarkDotNet/Toolchains/MonoWasm/WasmExecutor.cs b/src/BenchmarkDotNet/Toolchains/MonoWasm/WasmExecutor.cs index 7e857ff364..e6756ebcbe 100644 --- a/src/BenchmarkDotNet/Toolchains/MonoWasm/WasmExecutor.cs +++ b/src/BenchmarkDotNet/Toolchains/MonoWasm/WasmExecutor.cs @@ -38,8 +38,8 @@ private static ExecuteResult Execute(BenchmarkCase benchmarkCase, BenchmarkId be try { using (Process process = CreateProcess(benchmarkCase, artifactsPaths, benchmarkId.ToArguments(), resolver)) - using (ConsoleExitHandler consoleExitHandler = new (process, logger)) - using (AsyncProcessOutputReader processOutputReader = new (process, logOutput: true, logger, readStandardError: false)) + using (ConsoleExitHandler consoleExitHandler = new(process, logger)) + using (AsyncProcessOutputReader processOutputReader = new(process, logOutput: true, logger, readStandardError: false)) { diagnoser?.Handle(HostSignal.BeforeProcessStart, new DiagnoserActionParameters(process, benchmarkCase, benchmarkId)); diff --git a/src/BenchmarkDotNet/Validators/ParamsValidator.cs b/src/BenchmarkDotNet/Validators/ParamsValidator.cs index 5a503148d5..37dc645af5 100644 --- a/src/BenchmarkDotNet/Validators/ParamsValidator.cs +++ b/src/BenchmarkDotNet/Validators/ParamsValidator.cs @@ -9,7 +9,7 @@ namespace BenchmarkDotNet.Validators { public class ParamsValidator : IValidator { - public static readonly ParamsValidator FailOnError = new (); + public static readonly ParamsValidator FailOnError = new(); public bool TreatsWarningsAsErrors => true; diff --git a/tests/BenchmarkDotNet.IntegrationTests/AllSetupAndCleanupTest.cs b/tests/BenchmarkDotNet.IntegrationTests/AllSetupAndCleanupTest.cs index 459c23c2c2..36ea5688ad 100644 --- a/tests/BenchmarkDotNet.IntegrationTests/AllSetupAndCleanupTest.cs +++ b/tests/BenchmarkDotNet.IntegrationTests/AllSetupAndCleanupTest.cs @@ -196,7 +196,7 @@ public async ValueTask GlobalCleanup() public class AllSetupAndCleanupAttributeBenchmarksValueTaskSource { - private readonly ValueTaskSource valueTaskSource = new (); + private readonly ValueTaskSource valueTaskSource = new(); private int setupCounter; private int cleanupCounter; @@ -228,7 +228,7 @@ public ValueTask GlobalCleanup() public class AllSetupAndCleanupAttributeBenchmarksGenericValueTaskSource { - private readonly ValueTaskSource valueTaskSource = new (); + private readonly ValueTaskSource valueTaskSource = new(); private int setupCounter; private int cleanupCounter; diff --git a/tests/BenchmarkDotNet.IntegrationTests/AsyncBenchmarksTests.cs b/tests/BenchmarkDotNet.IntegrationTests/AsyncBenchmarksTests.cs index d795b1a102..a2158362ef 100644 --- a/tests/BenchmarkDotNet.IntegrationTests/AsyncBenchmarksTests.cs +++ b/tests/BenchmarkDotNet.IntegrationTests/AsyncBenchmarksTests.cs @@ -63,7 +63,7 @@ public void TaskReturningMethodsAreAwaited() public class TaskDelayMethods { - private readonly ValueTaskSource valueTaskSource = new (); + private readonly ValueTaskSource valueTaskSource = new(); private const int MillisecondsDelay = 100; @@ -112,8 +112,8 @@ public ValueTask ReturningGenericValueTaskBackByIValueTaskSource() public class TaskImmediateMethods { - private readonly ValueTaskSource valueTaskSource = new (); - private readonly ValueTaskSourceCallbackOnly valueTaskSourceCallbackOnly = new (); + private readonly ValueTaskSource valueTaskSource = new(); + private readonly ValueTaskSourceCallbackOnly valueTaskSourceCallbackOnly = new(); [Benchmark] public Task ReturningTask() => Task.CompletedTask; diff --git a/tests/BenchmarkDotNet.IntegrationTests/CustomEngineTests.cs b/tests/BenchmarkDotNet.IntegrationTests/CustomEngineTests.cs index 43a7bac9ee..f6d0a10f83 100644 --- a/tests/BenchmarkDotNet.IntegrationTests/CustomEngineTests.cs +++ b/tests/BenchmarkDotNet.IntegrationTests/CustomEngineTests.cs @@ -74,8 +74,8 @@ public RunResults Run() return new RunResults( new List { - new (1, IterationMode.Overhead, IterationStage.Actual, 1, 1, 1), - new (1, IterationMode.Workload, IterationStage.Actual, 1, 1, 1) + new(1, IterationMode.Overhead, IterationStage.Actual, 1, 1, 1), + new(1, IterationMode.Workload, IterationStage.Actual, 1, 1, 1) }, OutlierMode.DontRemove, default, diff --git a/tests/BenchmarkDotNet.IntegrationTests/InProcessEmitTest.cs b/tests/BenchmarkDotNet.IntegrationTests/InProcessEmitTest.cs index 538384b999..4b9ea2005b 100644 --- a/tests/BenchmarkDotNet.IntegrationTests/InProcessEmitTest.cs +++ b/tests/BenchmarkDotNet.IntegrationTests/InProcessEmitTest.cs @@ -331,7 +331,7 @@ public void InvokeOnceVoid() public class GlobalSetupCleanupValueTaskSource { - private readonly static ValueTaskSource valueTaskSource = new (); + private readonly static ValueTaskSource valueTaskSource = new(); [GlobalSetup] public static ValueTask GlobalSetup() diff --git a/tests/BenchmarkDotNet.IntegrationTests/MemoryDiagnoserTests.cs b/tests/BenchmarkDotNet.IntegrationTests/MemoryDiagnoserTests.cs index 9d92b38755..4cd2a39c23 100755 --- a/tests/BenchmarkDotNet.IntegrationTests/MemoryDiagnoserTests.cs +++ b/tests/BenchmarkDotNet.IntegrationTests/MemoryDiagnoserTests.cs @@ -257,8 +257,8 @@ public class MultiThreadedAllocation // to avoid measuring the cost of thread start and join, which varies across different runtimes. private Thread[] threads; private volatile bool keepRunning = true; - private readonly Barrier barrier = new (ThreadsCount + 1); - private readonly CountdownEvent countdownEvent = new (ThreadsCount); + private readonly Barrier barrier = new(ThreadsCount + 1); + private readonly CountdownEvent countdownEvent = new(ThreadsCount); [GlobalSetup] public void Setup() diff --git a/tests/BenchmarkDotNet.Tests/Builders/HostEnvironmentInfoBuilder.cs b/tests/BenchmarkDotNet.Tests/Builders/HostEnvironmentInfoBuilder.cs index 85e31352ec..29d690892b 100644 --- a/tests/BenchmarkDotNet.Tests/Builders/HostEnvironmentInfoBuilder.cs +++ b/tests/BenchmarkDotNet.Tests/Builders/HostEnvironmentInfoBuilder.cs @@ -23,10 +23,10 @@ public class HostEnvironmentInfoBuilder private bool isServerGC = false; private string jitInfo = "RyuJIT-v4.6.x.mock"; private string jitModules = "clrjit-v4.6.x.mock"; - private OsInfo os = new () { Display = "Microsoft Windows NT 10.0.x.mock" }; + private OsInfo os = new() { Display = "Microsoft Windows NT 10.0.x.mock" }; private string runtimeVersion = "Clr 4.0.x.mock"; - private readonly CpuInfo cpu = new () + private readonly CpuInfo cpu = new() { ProcessorName = "MockIntel(R) Core(TM) i7-6700HQ CPU 2.60GHz", PhysicalProcessorCount = 1, diff --git a/tests/BenchmarkDotNet.Tests/KnownIssue.cs b/tests/BenchmarkDotNet.Tests/KnownIssue.cs index fe6cb83ee5..1831ad3b08 100644 --- a/tests/BenchmarkDotNet.Tests/KnownIssue.cs +++ b/tests/BenchmarkDotNet.Tests/KnownIssue.cs @@ -2,7 +2,7 @@ namespace BenchmarkDotNet.Tests { public class KnownIssue { - public static KnownIssue Issue2299 => new (2299, "Non-supported Mono on Linux", false); + public static KnownIssue Issue2299 => new(2299, "Non-supported Mono on Linux", false); public int Number { get; } public string Description { get; } diff --git a/tests/BenchmarkDotNet.Tests/Mocks/Toolchain/MockToolchain.cs b/tests/BenchmarkDotNet.Tests/Mocks/Toolchain/MockToolchain.cs index c25929854e..365a55cb88 100644 --- a/tests/BenchmarkDotNet.Tests/Mocks/Toolchain/MockToolchain.cs +++ b/tests/BenchmarkDotNet.Tests/Mocks/Toolchain/MockToolchain.cs @@ -43,7 +43,7 @@ private class MockExecutor : IExecutor public MockExecutor(Func> measurer) => this.measurer = measurer; - public ExecuteResult Execute(ExecuteParameters executeParameters) => new (measurer(executeParameters.BenchmarkCase)); + public ExecuteResult Execute(ExecuteParameters executeParameters) => new(measurer(executeParameters.BenchmarkCase)); } } } \ No newline at end of file diff --git a/tests/BenchmarkDotNet.Tests/Order/DefaultOrdererTests.cs b/tests/BenchmarkDotNet.Tests/Order/DefaultOrdererTests.cs index d9d2e7490e..38b9167e99 100644 --- a/tests/BenchmarkDotNet.Tests/Order/DefaultOrdererTests.cs +++ b/tests/BenchmarkDotNet.Tests/Order/DefaultOrdererTests.cs @@ -19,10 +19,10 @@ namespace BenchmarkDotNet.Tests.Order { public class DefaultOrdererTests { - private static Summary CreateMockSummary() => new ("", ImmutableArray.Empty, HostEnvironmentInfo.GetCurrent(), + private static Summary CreateMockSummary() => new("", ImmutableArray.Empty, HostEnvironmentInfo.GetCurrent(), "", "", TimeSpan.Zero, CultureInfo.InvariantCulture, ImmutableArray.Empty, ImmutableArray.Empty); - private static BenchmarkCase CreateBenchmarkCase(string category, int parameter, params BenchmarkLogicalGroupRule[] rules) => new ( + private static BenchmarkCase CreateBenchmarkCase(string category, int parameter, params BenchmarkLogicalGroupRule[] rules) => new( new Descriptor(MockFactory.MockType, MockFactory.MockMethodInfo, categories: new[] { category }), new Job(), new ParameterInstances(new[] diff --git a/tests/BenchmarkDotNet.Tests/ParamsSourceTests.cs b/tests/BenchmarkDotNet.Tests/ParamsSourceTests.cs index 7ee8695475..c32b48d721 100644 --- a/tests/BenchmarkDotNet.Tests/ParamsSourceTests.cs +++ b/tests/BenchmarkDotNet.Tests/ParamsSourceTests.cs @@ -21,7 +21,7 @@ public static IEnumerable Values() { yield return null; yield return ValueTuple.Create(10); - yield return ValueTuple.Create(10, 20); + yield return (10, 20); yield return (10, 20, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9); } diff --git a/tests/BenchmarkDotNet.Tests/Perfonar/Infra/PerfonarMock.cs b/tests/BenchmarkDotNet.Tests/Perfonar/Infra/PerfonarMock.cs index 3da08dac61..f63caeafa4 100644 --- a/tests/BenchmarkDotNet.Tests/Perfonar/Infra/PerfonarMock.cs +++ b/tests/BenchmarkDotNet.Tests/Perfonar/Infra/PerfonarMock.cs @@ -5,7 +5,7 @@ namespace BenchmarkDotNet.Tests.Perfonar.Infra; public static class PerfonarMock { - public static readonly EngineInfo Engine = new () + public static readonly EngineInfo Engine = new() { Name = BenchmarkDotNetInfo.BenchmarkDotNetCaption, Version = "0.1729.0-mock" diff --git a/tests/BenchmarkDotNet.Tests/Perfonar/PerfonarTests.cs b/tests/BenchmarkDotNet.Tests/Perfonar/PerfonarTests.cs index c6d2f586cd..81869534a1 100644 --- a/tests/BenchmarkDotNet.Tests/Perfonar/PerfonarTests.cs +++ b/tests/BenchmarkDotNet.Tests/Perfonar/PerfonarTests.cs @@ -137,7 +137,7 @@ public Task PerfonarTableTest(string key) Host = new HostEnvironmentInfoBuilder().Build().ToPerfonar() }; - private static PerfonarTableConfig GetDefaultTableConfig() => new () + private static PerfonarTableConfig GetDefaultTableConfig() => new() { ColumnDefinitions = [ diff --git a/tests/BenchmarkDotNet.Tests/ReflectionTests.cs b/tests/BenchmarkDotNet.Tests/ReflectionTests.cs index a32993a034..0d1ea69673 100644 --- a/tests/BenchmarkDotNet.Tests/ReflectionTests.cs +++ b/tests/BenchmarkDotNet.Tests/ReflectionTests.cs @@ -38,7 +38,7 @@ public void GetCorrectCSharpTypeNameSupportsGenericTypesPassedByReference() public class GenericByRef { - public void TheMethod(ref ValueTuple _) { } + public void TheMethod(ref (int, short) _) { } } [Fact] diff --git a/tests/BenchmarkDotNet.Tests/Validators/ExecutionValidatorTests.cs b/tests/BenchmarkDotNet.Tests/Validators/ExecutionValidatorTests.cs index fece3b3939..70195fd959 100644 --- a/tests/BenchmarkDotNet.Tests/Validators/ExecutionValidatorTests.cs +++ b/tests/BenchmarkDotNet.Tests/Validators/ExecutionValidatorTests.cs @@ -591,7 +591,7 @@ public void AsyncValueTaskBackedByIValueTaskSourceIsAwaitedProperly() public class AsyncValueTaskSource { - private readonly ValueTaskSource valueTaskSource = new (); + private readonly ValueTaskSource valueTaskSource = new(); public static bool WasCalled; @@ -622,7 +622,7 @@ public void AsyncGenericValueTaskBackedByIValueTaskSourceIsAwaitedProperly() public class AsyncGenericValueTaskSource { - private readonly ValueTaskSource valueTaskSource = new (); + private readonly ValueTaskSource valueTaskSource = new(); public static bool WasCalled; From ab703aa85361a002be756616d26253e3485e8383 Mon Sep 17 00:00:00 2001 From: filzrev <103790468+filzrev@users.noreply.github.com> Date: Sat, 28 Jun 2025 16:03:18 +0900 Subject: [PATCH 08/37] chore: add workflow to run selected tests (#2797) --- .github/workflows/run-tests-selected.yaml | 89 +++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 .github/workflows/run-tests-selected.yaml 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/**/* From 4f646d3663a5947cddee2ace92219b1755dee962 Mon Sep 17 00:00:00 2001 From: Tim Cassell Date: Wed, 2 Jul 2025 10:36:59 -0400 Subject: [PATCH 09/37] Fix `IsNetCore` and `IsNativeAOT` for single-file apps without AOT (#2799) * Fix `IsNetCore` check for single-file apps without AOT, and `IsNativeAOT` check. * Handle `TryGetVersion` for single-file exe in netcoreapp3.x. * Simplify some checks. * Get version from FrameworkDescription. * Don't include confusing FrameworkDescription in netcoreapp2.x. * Rename GetVersionFromFrameworkDescription. --- .../Environments/Runtimes/CoreRuntime.cs | 31 ++++++++-- .../Portability/RuntimeInformation.cs | 60 ++++++++----------- 2 files changed, 52 insertions(+), 39 deletions(-) diff --git a/src/BenchmarkDotNet/Environments/Runtimes/CoreRuntime.cs b/src/BenchmarkDotNet/Environments/Runtimes/CoreRuntime.cs index 954a8ac7dc..472b629405 100644 --- a/src/BenchmarkDotNet/Environments/Runtimes/CoreRuntime.cs +++ b/src/BenchmarkDotNet/Environments/Runtimes/CoreRuntime.cs @@ -99,11 +99,26 @@ internal static bool TryGetVersion(out Version? version) return true; } - var systemPrivateCoreLib = FileVersionInfo.GetVersionInfo(typeof(object).Assembly.Location); - // systemPrivateCoreLib.Product*Part properties return 0 so we have to implement some ugly parsing... - if (TryGetVersionFromProductInfo(systemPrivateCoreLib.ProductVersion, systemPrivateCoreLib.ProductName, out version)) + string coreclrLocation = typeof(object).Assembly.Location; + // Single-file publish has empty assembly location. + if (!string.IsNullOrEmpty(coreclrLocation)) { - return true; + var systemPrivateCoreLib = FileVersionInfo.GetVersionInfo(coreclrLocation); + // systemPrivateCoreLib.Product*Part properties return 0 so we have to implement some ugly parsing... + if (TryGetVersionFromProductInfo(systemPrivateCoreLib.ProductVersion, systemPrivateCoreLib.ProductName, out version)) + { + return true; + } + } + else + { + // .Net Core 3.X supports single-file publish, .Net Core 2.X does not. + // .Net Core 3.X fixed the version in FrameworkDescription, so we don't need to handle the case of 4.6.x in this branch. + var frameworkDescriptionVersion = GetParsableVersionPart(GetVersionFromFrameworkDescription()); + if (Version.TryParse(frameworkDescriptionVersion, out version)) + { + return true; + } } // it's OK to use this method only after checking the previous ones @@ -125,6 +140,14 @@ internal static bool TryGetVersion(out Version? version) return false; } + internal static string GetVersionFromFrameworkDescription() + { + // .NET 10.0.0-preview.5.25277.114 -> 10.0.0-preview.5.25277.114 + // .NET Core 3.1.32 -> 3.1.32 + string frameworkDescription = System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription; + return new string(frameworkDescription.SkipWhile(c => !char.IsDigit(c)).ToArray()); + } + // sample input: // for dotnet run: C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.1.12\ // for dotnet publish: C:\Users\adsitnik\source\repos\ConsoleApp25\ConsoleApp25\bin\Release\netcoreapp2.0\win-x64\publish\ diff --git a/src/BenchmarkDotNet/Portability/RuntimeInformation.cs b/src/BenchmarkDotNet/Portability/RuntimeInformation.cs index fb5150db2d..9b8348a125 100644 --- a/src/BenchmarkDotNet/Portability/RuntimeInformation.cs +++ b/src/BenchmarkDotNet/Portability/RuntimeInformation.cs @@ -2,22 +2,16 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.IO; using System.Linq; using System.Management; using System.Reflection; using System.Runtime.InteropServices; using System.Text.RegularExpressions; using BenchmarkDotNet.Detectors; -using BenchmarkDotNet.Detectors.Cpu; using BenchmarkDotNet.Environments; using BenchmarkDotNet.Extensions; using BenchmarkDotNet.Helpers; -using JetBrains.Annotations; -using Microsoft.Win32; -using Perfolizer.Helpers; using static System.Runtime.InteropServices.RuntimeInformation; -using RuntimeEnvironment = Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment; namespace BenchmarkDotNet.Portability { @@ -48,12 +42,6 @@ internal static class RuntimeInformation FrameworkDescription.StartsWith(".NET Framework", StringComparison.OrdinalIgnoreCase); #endif - public static readonly bool IsNetNative = FrameworkDescription.StartsWith(".NET Native", StringComparison.OrdinalIgnoreCase); - - public static readonly bool IsNetCore = - ((Environment.Version.Major >= 5) || FrameworkDescription.StartsWith(".NET Core", StringComparison.OrdinalIgnoreCase)) - && !string.IsNullOrEmpty(typeof(object).Assembly.Location); - #if NET6_0_OR_GREATER [System.Runtime.Versioning.SupportedOSPlatformGuard("browser")] public static readonly bool IsWasm = OperatingSystem.IsBrowser(); @@ -61,13 +49,8 @@ internal static class RuntimeInformation public static readonly bool IsWasm = IsOSPlatform(OSPlatform.Create("BROWSER")); #endif - public static readonly bool IsNativeAOT = - Environment.Version.Major >= 5 - && string.IsNullOrEmpty(typeof(object).Assembly.Location) // it's merged to a single .exe and .Location returns null - && !IsWasm; // Wasm also returns "" for assembly locations - #if NETSTANDARD2_0 - public static readonly bool IsAot = IsAotMethod(); + public static readonly bool IsAot = IsAotMethod() || FrameworkDescription.StartsWith(".NET Native", StringComparison.OrdinalIgnoreCase); private static bool IsAotMethod() { @@ -88,6 +71,16 @@ private static bool IsAotMethod() public static readonly bool IsAot = !System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeCompiled; #endif + public static bool IsNetCore + => ((Environment.Version.Major >= 5) || FrameworkDescription.StartsWith(".NET Core", StringComparison.OrdinalIgnoreCase)) + && !IsAot; + + public static bool IsNativeAOT + => Environment.Version.Major >= 5 + && IsAot + && !IsWasm && !IsMono; // Wasm and MonoAOTLLVM are also AOT + + public static readonly bool IsTieredJitEnabled = IsNetCore && (Environment.Version.Major < 3 @@ -171,24 +164,21 @@ private static string GetNetCoreVersion() { return $".NET {Environment.Version}"; } - else - { - var coreclrAssemblyInfo = FileVersionInfo.GetVersionInfo(typeof(object).GetTypeInfo().Assembly.Location); - var corefxAssemblyInfo = FileVersionInfo.GetVersionInfo(typeof(Regex).GetTypeInfo().Assembly.Location); - if (CoreRuntime.TryGetVersion(out var version) && version >= new Version(5, 0)) - { - // after the merge of dotnet/corefx and dotnet/coreclr into dotnet/runtime the version should always be the same - Debug.Assert(coreclrAssemblyInfo.FileVersion == corefxAssemblyInfo.FileVersion); - - return $".NET {version} ({coreclrAssemblyInfo.FileVersion})"; - } - else - { - string runtimeVersion = version != default ? version.ToString() : Unknown; + return CoreRuntime.TryGetVersion(out var version) && version.Major >= 5 + ? $".NET {version} ({GetDetailedVersion()})" + : $".NET Core {version?.ToString() ?? Unknown} ({GetDetailedVersion()})"; - return $".NET Core {runtimeVersion} (CoreCLR {coreclrAssemblyInfo.FileVersion}, CoreFX {corefxAssemblyInfo.FileVersion})"; - } + string GetDetailedVersion() + { + string coreclrLocation = typeof(object).GetTypeInfo().Assembly.Location; + // Single-file publish has empty assembly location. + if (string.IsNullOrEmpty(coreclrLocation)) + return CoreRuntime.GetVersionFromFrameworkDescription(); + // .Net Core 2.X has confusing FrameworkDescription like 4.6.X. + if (version?.Major >= 3) + return $"{CoreRuntime.GetVersionFromFrameworkDescription()}, {FileVersionInfo.GetVersionInfo(coreclrLocation).FileVersion}"; + return FileVersionInfo.GetVersionInfo(coreclrLocation).FileVersion; } } @@ -271,7 +261,7 @@ internal static string GetJitInfo() { if (IsNativeAOT) return "NativeAOT"; - if (IsNetNative || IsAot) + if (IsAot) return "AOT"; if (IsMono || IsWasm) return ""; // There is no helpful information about JIT on Mono From d990f104cd27ae9af12210b4e21a31ceb5986464 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Sat, 5 Jul 2025 00:40:05 +0200 Subject: [PATCH 10/37] Fix comment in package props about GenerateProgramFile (#2802) --- .../build/BenchmarkDotNet.TestAdapter.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/BenchmarkDotNet.TestAdapter/build/BenchmarkDotNet.TestAdapter.props b/src/BenchmarkDotNet.TestAdapter/build/BenchmarkDotNet.TestAdapter.props index a9e8340b30..8c716bb3bc 100644 --- a/src/BenchmarkDotNet.TestAdapter/build/BenchmarkDotNet.TestAdapter.props +++ b/src/BenchmarkDotNet.TestAdapter/build/BenchmarkDotNet.TestAdapter.props @@ -14,8 +14,8 @@ - + false - \ No newline at end of file + From a15782f7682a384eeb7e52bc010cc793f7900a1b Mon Sep 17 00:00:00 2001 From: Tim Cassell Date: Fri, 4 Jul 2025 22:49:24 -0400 Subject: [PATCH 11/37] Fix workload warmup mode. --- src/BenchmarkDotNet/Engines/EngineWarmupStage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BenchmarkDotNet/Engines/EngineWarmupStage.cs b/src/BenchmarkDotNet/Engines/EngineWarmupStage.cs index 26a1f1f68b..822ea69101 100644 --- a/src/BenchmarkDotNet/Engines/EngineWarmupStage.cs +++ b/src/BenchmarkDotNet/Engines/EngineWarmupStage.cs @@ -24,7 +24,7 @@ internal static EngineWarmupStage GetWorkload(IEngine engine, RunStrategy runStr int minIterationCount = job.ResolveValue(RunMode.MinWarmupIterationCountCharacteristic, engine.Resolver); int maxIterationCount = job.ResolveValue(RunMode.MaxWarmupIterationCountCharacteristic, engine.Resolver); - return new EngineWarmupStageAuto(IterationMode.Overhead, minIterationCount, maxIterationCount); + return new EngineWarmupStageAuto(IterationMode.Workload, minIterationCount, maxIterationCount); } } From 604ff55c00edb3d778e484f56cbc9b50d66245ea Mon Sep 17 00:00:00 2001 From: filzrev <103790468+filzrev@users.noreply.github.com> Date: Sat, 5 Jul 2025 12:55:37 +0900 Subject: [PATCH 12/37] deps: update BenchmarkDotNetDiagnosers package version (#2805) --- samples/BenchmarkDotNet.Samples/BenchmarkDotNet.Samples.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/BenchmarkDotNet.Samples/BenchmarkDotNet.Samples.csproj b/samples/BenchmarkDotNet.Samples/BenchmarkDotNet.Samples.csproj index 729895ed5b..6c861ed1f6 100644 --- a/samples/BenchmarkDotNet.Samples/BenchmarkDotNet.Samples.csproj +++ b/samples/BenchmarkDotNet.Samples/BenchmarkDotNet.Samples.csproj @@ -26,7 +26,7 @@ - + From 197d8ed98fd6e373d268baac977997ca3a6b5ab4 Mon Sep 17 00:00:00 2001 From: Tim Cassell Date: Thu, 10 Jul 2025 12:30:50 -0400 Subject: [PATCH 13/37] Split `TimeConsumingBenchmark` class to reduce test time. --- .../MemoryDiagnoserTests.cs | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/tests/BenchmarkDotNet.IntegrationTests/MemoryDiagnoserTests.cs b/tests/BenchmarkDotNet.IntegrationTests/MemoryDiagnoserTests.cs index 4cd2a39c23..80bda6a6dc 100755 --- a/tests/BenchmarkDotNet.IntegrationTests/MemoryDiagnoserTests.cs +++ b/tests/BenchmarkDotNet.IntegrationTests/MemoryDiagnoserTests.cs @@ -119,10 +119,23 @@ public void MemoryDiagnoserDoesNotIncludeAllocationsFromSetupAndCleanup(IToolcha }); } - public class NoAllocationsAtAll + public class EmptyBenchmark { [Benchmark] public void EmptyMethod() { } + } + + [Theory, MemberData(nameof(GetToolchains), DisableDiscoveryEnumeration = true)] + [Trait(Constants.Category, Constants.BackwardCompatibilityCategory)] + public void EngineShouldNotInterfereAllocationResults(IToolchain toolchain) + { + AssertAllocations(toolchain, typeof(EmptyBenchmark), new Dictionary + { + { nameof(EmptyBenchmark.EmptyMethod), 0 } + }); + } + public class TimeConsumingBenchmark + { [Benchmark] public ulong TimeConsuming() { @@ -135,24 +148,14 @@ public ulong TimeConsuming() } } - [Theory, MemberData(nameof(GetToolchains), DisableDiscoveryEnumeration = true)] - [Trait(Constants.Category, Constants.BackwardCompatibilityCategory)] - public void EngineShouldNotInterfereAllocationResults(IToolchain toolchain) - { - AssertAllocations(toolchain, typeof(NoAllocationsAtAll), new Dictionary - { - { nameof(NoAllocationsAtAll.EmptyMethod), 0 } - }); - } - // #1542 [Theory, MemberData(nameof(GetToolchains), DisableDiscoveryEnumeration = true)] [Trait(Constants.Category, Constants.BackwardCompatibilityCategory)] public void TieredJitShouldNotInterfereAllocationResults(IToolchain toolchain) { - AssertAllocations(toolchain, typeof(NoAllocationsAtAll), new Dictionary + AssertAllocations(toolchain, typeof(TimeConsumingBenchmark), new Dictionary { - { nameof(NoAllocationsAtAll.TimeConsuming), 0 } + { nameof(TimeConsumingBenchmark.TimeConsuming), 0 } }, disableTieredJit: false, iterationCount: 10); // 1 iteration is not enough to repro the problem } From 81a4e552005a24f01788f4aae861398da77021c3 Mon Sep 17 00:00:00 2001 From: mriehm <3916550+mriehm@users.noreply.github.com> Date: Fri, 11 Jul 2025 10:04:11 -0500 Subject: [PATCH 14/37] Fix EtwProfiler for file paths slightly under 260 chars (#2808) --- src/BenchmarkDotNet/Helpers/ArtifactFileNameHelper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/BenchmarkDotNet/Helpers/ArtifactFileNameHelper.cs b/src/BenchmarkDotNet/Helpers/ArtifactFileNameHelper.cs index 6873bb834b..d98ca46e58 100644 --- a/src/BenchmarkDotNet/Helpers/ArtifactFileNameHelper.cs +++ b/src/BenchmarkDotNet/Helpers/ArtifactFileNameHelper.cs @@ -18,7 +18,7 @@ internal static class ArtifactFileNameHelper internal static string GetTraceFilePath(DiagnoserActionParameters details, DateTime creationTime, string fileExtension) { - return GetFilePath(details, null, creationTime, fileExtension, "userheap.etl".Length); + return GetFilePath(details, null, creationTime, fileExtension, "userheap.etl".Length - fileExtension.Length); } internal static string GetFilePath(DiagnoserActionParameters details, string? subfolder, DateTime? creationTime, string fileExtension, int reserve) @@ -27,7 +27,7 @@ internal static string GetFilePath(DiagnoserActionParameters details, string? su // long paths can be enabled on Windows but it does not mean that everything is going to work fine.. // so we always use 260 as limit on Windows - int limit = OsDetector.IsWindows() + int limit = OsDetector.IsWindows() ? WindowsOldPathLimit - reserve : CommonSenseLimit; From 8b62787c79f1513b2868b4d4d685d79a7f3acd51 Mon Sep 17 00:00:00 2001 From: filzrev <103790468+filzrev@users.noreply.github.com> Date: Tue, 22 Jul 2025 06:35:40 +0900 Subject: [PATCH 15/37] feat: add vstestadapter filter support (#2788) --- .../BenchmarkDotNet.Samples.FSharp.fsproj | 2 + .../BenchmarkDotNet.Samples.csproj | 2 + .../Utility/LoggerHelper.cs | 57 ++++++ .../Utility/TestCaseFilter.cs | 166 ++++++++++++++++++ .../VSTestAdapter.cs | 40 ++++- .../build/BenchmarkDotNet.TestAdapter.props | 2 + 6 files changed, 267 insertions(+), 2 deletions(-) create mode 100644 src/BenchmarkDotNet.TestAdapter/Utility/LoggerHelper.cs create mode 100644 src/BenchmarkDotNet.TestAdapter/Utility/TestCaseFilter.cs diff --git a/samples/BenchmarkDotNet.Samples.FSharp/BenchmarkDotNet.Samples.FSharp.fsproj b/samples/BenchmarkDotNet.Samples.FSharp/BenchmarkDotNet.Samples.FSharp.fsproj index ee4eb0c8b8..6359340234 100644 --- a/samples/BenchmarkDotNet.Samples.FSharp/BenchmarkDotNet.Samples.FSharp.fsproj +++ b/samples/BenchmarkDotNet.Samples.FSharp/BenchmarkDotNet.Samples.FSharp.fsproj @@ -7,6 +7,8 @@ Exe net462;net8.0 false + + false diff --git a/samples/BenchmarkDotNet.Samples/BenchmarkDotNet.Samples.csproj b/samples/BenchmarkDotNet.Samples/BenchmarkDotNet.Samples.csproj index 6c861ed1f6..33275eb7ed 100644 --- a/samples/BenchmarkDotNet.Samples/BenchmarkDotNet.Samples.csproj +++ b/samples/BenchmarkDotNet.Samples/BenchmarkDotNet.Samples.csproj @@ -13,6 +13,8 @@ $(NoWarn);CA1018;CA5351;CA1825 false + + false 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/build/BenchmarkDotNet.TestAdapter.props b/src/BenchmarkDotNet.TestAdapter/build/BenchmarkDotNet.TestAdapter.props index 8c716bb3bc..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 + 9.0.0 + - - + diff --git a/samples/BenchmarkDotNet.Samples/IntroNuGet.cs b/samples/BenchmarkDotNet.Samples/IntroNuGet.cs index a368b62b56..850a9ceebf 100644 --- a/samples/BenchmarkDotNet.Samples/IntroNuGet.cs +++ b/samples/BenchmarkDotNet.Samples/IntroNuGet.cs @@ -11,21 +11,26 @@ 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 13.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", @@ -34,8 +39,10 @@ public Config() foreach (var version in targetVersions) { - AddJob(baseJob.WithNuGet("System.Collections.Immutable", version) - .WithId($"v{version}")); + AddJob(Job.MediumRun + .WithMsBuildArguments($"/p:SciVersion={version}") + .WithId($"v{version}") + ); } } } diff --git a/src/BenchmarkDotNet/Environments/InfrastructureResolver.cs b/src/BenchmarkDotNet/Environments/InfrastructureResolver.cs index 865c6f64f3..dfeaf3fd47 100644 --- a/src/BenchmarkDotNet/Environments/InfrastructureResolver.cs +++ b/src/BenchmarkDotNet/Environments/InfrastructureResolver.cs @@ -17,7 +17,10 @@ private InfrastructureResolver() Register(InfrastructureMode.BuildConfigurationCharacteristic, () => InfrastructureMode.ReleaseConfigurationName); Register(InfrastructureMode.ArgumentsCharacteristic, Array.Empty); + +#pragma warning disable CS0618 // Type or member is obsolete Register(InfrastructureMode.NuGetReferencesCharacteristic, Array.Empty); +#pragma warning restore CS0618 // Type or member is obsolete } } } \ No newline at end of file diff --git a/src/BenchmarkDotNet/Jobs/InfrastructureMode.cs b/src/BenchmarkDotNet/Jobs/InfrastructureMode.cs index 5e5f8b5369..277627d2e9 100644 --- a/src/BenchmarkDotNet/Jobs/InfrastructureMode.cs +++ b/src/BenchmarkDotNet/Jobs/InfrastructureMode.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using BenchmarkDotNet.Characteristics; using BenchmarkDotNet.Engines; @@ -18,6 +20,9 @@ public sealed class InfrastructureMode : JobMode public static readonly Characteristic EngineFactoryCharacteristic = CreateCharacteristic(nameof(EngineFactory)); public static readonly Characteristic BuildConfigurationCharacteristic = CreateCharacteristic(nameof(BuildConfiguration)); public static readonly Characteristic> ArgumentsCharacteristic = CreateCharacteristic>(nameof(Arguments)); + + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("This will soon be removed")] public static readonly Characteristic> NuGetReferencesCharacteristic = CreateCharacteristic>(nameof(NuGetReferences)); public static readonly InfrastructureMode InProcess = new InfrastructureMode(InProcessEmitToolchain.Instance); @@ -64,6 +69,8 @@ public IReadOnlyList Arguments set => ArgumentsCharacteristic[this] = value; } + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("This will soon be removed")] public IReadOnlyCollection NuGetReferences { get => NuGetReferencesCharacteristic[this]; diff --git a/src/BenchmarkDotNet/Jobs/JobExtensions.cs b/src/BenchmarkDotNet/Jobs/JobExtensions.cs index 2c778df589..0424528ad4 100644 --- a/src/BenchmarkDotNet/Jobs/JobExtensions.cs +++ b/src/BenchmarkDotNet/Jobs/JobExtensions.cs @@ -339,6 +339,8 @@ public static Job WithEnvironmentVariable(this Job job, string key, string value /// (optional)Indicate the URI of the NuGet package source to use during the restore operation. /// (optional)Allows prerelease packages to be installed. /// + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("This method will soon be removed, please start using .WithMsBuildArguments() instead.")] public static Job WithNuGet(this Job job, string packageName, string? packageVersion = null, Uri? source = null, bool prerelease = false) => job.WithCore(j => j.Infrastructure.NuGetReferences = new NuGetReferenceList(j.Infrastructure.NuGetReferences ?? Array.Empty()) @@ -352,9 +354,14 @@ public static Job WithNuGet(this Job job, string packageName, string? packageVer /// /// A collection of NuGet dependencies /// + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("This method will soon be removed, please start using .WithMsBuildArguments() instead.")] public static Job WithNuGet(this Job job, NuGetReferenceList nuGetReferences) => job.WithCore(j => j.Infrastructure.NuGetReferences = nuGetReferences); + public static Job WithMsBuildArguments(this Job job, params string[] msBuildArguments) + => job.WithArguments([.. msBuildArguments.Select(a => new MsBuildArgument(a))]); + // Accuracy /// /// Maximum acceptable error for a benchmark (by default, BenchmarkDotNet continue iterations until the actual error is less than the specified error). @@ -437,8 +444,10 @@ private static Job WithCore(this Job job, Action updateCallback) return newJob; } - internal static bool HasDynamicBuildCharacteristic(this Job job) => - job.HasValue(InfrastructureMode.NuGetReferencesCharacteristic) + internal static bool HasDynamicBuildCharacteristic(this Job job) +#pragma warning disable CS0618 // Type or member is obsolete + => job.HasValue(InfrastructureMode.NuGetReferencesCharacteristic) +#pragma warning restore CS0618 // Type or member is obsolete || job.HasValue(InfrastructureMode.BuildConfigurationCharacteristic) || job.HasValue(InfrastructureMode.ArgumentsCharacteristic); } diff --git a/src/BenchmarkDotNet/Jobs/NugetReference.cs b/src/BenchmarkDotNet/Jobs/NugetReference.cs index 0db3a028de..89dc4c3734 100644 --- a/src/BenchmarkDotNet/Jobs/NugetReference.cs +++ b/src/BenchmarkDotNet/Jobs/NugetReference.cs @@ -1,8 +1,11 @@ using System; +using System.ComponentModel; using System.Text.RegularExpressions; namespace BenchmarkDotNet.Jobs { + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("This type will soon be removed")] public class NuGetReference : IEquatable { public NuGetReference(string packageName, string packageVersion, Uri? source = null, bool prerelease = false) diff --git a/src/BenchmarkDotNet/Jobs/NugetReferenceList.cs b/src/BenchmarkDotNet/Jobs/NugetReferenceList.cs index 13c727c28f..025353b24f 100644 --- a/src/BenchmarkDotNet/Jobs/NugetReferenceList.cs +++ b/src/BenchmarkDotNet/Jobs/NugetReferenceList.cs @@ -1,12 +1,15 @@ using System; using System.Collections; using System.Collections.Generic; +using System.ComponentModel; namespace BenchmarkDotNet.Jobs { /// /// An ordered list of NuGet references. Does not allow duplicate references with the same PackageName. /// + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("This type will soon be removed")] public class NuGetReferenceList : IReadOnlyCollection { private readonly List references = new List(); diff --git a/src/BenchmarkDotNet/Running/BenchmarkPartitioner.cs b/src/BenchmarkDotNet/Running/BenchmarkPartitioner.cs index ada1b2b78e..4892de7f0c 100644 --- a/src/BenchmarkDotNet/Running/BenchmarkPartitioner.cs +++ b/src/BenchmarkDotNet/Running/BenchmarkPartitioner.cs @@ -47,8 +47,10 @@ public bool Equals(BenchmarkCase x, BenchmarkCase y) return false; if (AreDifferent(jobX.Infrastructure.Arguments, jobY.Infrastructure.Arguments)) // arguments can be anything (Mono runtime settings or MsBuild parameters) return false; +#pragma warning disable CS0618 // Type or member is obsolete if (AreDifferent(jobX.Infrastructure.NuGetReferences, jobY.Infrastructure.NuGetReferences)) return false; +#pragma warning restore CS0618 // Type or member is obsolete if (!jobX.Environment.Gc.Equals(jobY.Environment.Gc)) // GC settings are per .config/.csproj return false; @@ -81,8 +83,10 @@ public int GetHashCode(BenchmarkCase obj) hashCode.Add(job.Infrastructure.BuildConfiguration); foreach (var arg in job.Infrastructure.Arguments ?? Array.Empty()) hashCode.Add(arg); +#pragma warning disable CS0618 // Type or member is obsolete foreach (var reference in job.Infrastructure.NuGetReferences ?? Array.Empty()) hashCode.Add(reference); +#pragma warning restore CS0618 // Type or member is obsolete return hashCode.ToHashCode(); } diff --git a/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommand.cs b/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommand.cs index bcd921fdd1..607ff6f974 100644 --- a/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommand.cs +++ b/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommand.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.IO; using System.Linq; using System.Text; @@ -57,7 +58,9 @@ public BuildResult RestoreThenBuild() { DotNetCliCommandExecutor.LogEnvVars(WithArguments(null)); +#pragma warning disable CS0618 // Type or member is obsolete var packagesResult = AddPackages(); +#pragma warning restore CS0618 // Type or member is obsolete if (!packagesResult.IsSuccess) return BuildResult.Failure(GenerateResult, packagesResult.AllInformation); @@ -97,7 +100,9 @@ public BuildResult RestoreThenBuildThenPublish() { DotNetCliCommandExecutor.LogEnvVars(WithArguments(null)); +#pragma warning disable CS0618 // Type or member is obsolete var packagesResult = AddPackages(); +#pragma warning restore CS0618 // Type or member is obsolete if (!packagesResult.IsSuccess) return BuildResult.Failure(GenerateResult, packagesResult.AllInformation); @@ -115,6 +120,8 @@ public BuildResult RestoreThenBuildThenPublish() return PublishNoRestore().ToBuildResult(GenerateResult); } + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("This method will soon be removed")] public DotNetCliCommandResult AddPackages() { var executionTime = new TimeSpan(0); @@ -150,6 +157,7 @@ public DotNetCliCommandResult PublishNoRestore() => DotNetCliCommandExecutor.Execute(WithArguments( GetPublishCommand(GenerateResult.ArtifactsPaths, BuildPartition, $"{Arguments} --no-restore", "publish-no-restore"))); + [Obsolete] internal static IEnumerable GetAddPackagesCommands(BuildPartition buildPartition) => GetNuGetAddPackageCommands(buildPartition.RepresentativeBenchmarkCase, buildPartition.Resolver); @@ -204,6 +212,7 @@ private static string GetCustomMsBuildArguments(BenchmarkCase benchmarkCase, IRe return string.Join(" ", msBuildArguments.Select(arg => arg.TextRepresentation)); } + [Obsolete] private static IEnumerable GetNuGetAddPackageCommands(BenchmarkCase benchmarkCase, IResolver resolver) { if (!benchmarkCase.Job.HasValue(InfrastructureMode.NuGetReferencesCharacteristic)) @@ -229,6 +238,7 @@ private static string GetMandatoryMsBuildSettings(string buildConfiguration) return $"{NoMsBuildZombieProcesses} {EnforceOptimizations}"; } + [Obsolete] private static string BuildAddPackageCommand(NuGetReference reference) { var commandBuilder = new StringBuilder(); diff --git a/src/BenchmarkDotNet/Toolchains/Mono/MonoAotToolchain.cs b/src/BenchmarkDotNet/Toolchains/Mono/MonoAotToolchain.cs index acbeeffe38..30135c15cd 100644 --- a/src/BenchmarkDotNet/Toolchains/Mono/MonoAotToolchain.cs +++ b/src/BenchmarkDotNet/Toolchains/Mono/MonoAotToolchain.cs @@ -48,12 +48,14 @@ public override IEnumerable Validate(BenchmarkCase benchmarkCas benchmarkCase); } +#pragma warning disable CS0618 // Type or member is obsolete if (benchmarkCase.Job.HasValue(InfrastructureMode.NuGetReferencesCharacteristic)) { yield return new ValidationError(true, "The MonoAOT toolchain does not allow specifying NuGet package dependencies", benchmarkCase); } +#pragma warning restore CS0618 // Type or member is obsolete } } } \ No newline at end of file diff --git a/src/BenchmarkDotNet/Toolchains/Roslyn/RoslynToolchain.cs b/src/BenchmarkDotNet/Toolchains/Roslyn/RoslynToolchain.cs index 31c4d68c29..6774268dcc 100644 --- a/src/BenchmarkDotNet/Toolchains/Roslyn/RoslynToolchain.cs +++ b/src/BenchmarkDotNet/Toolchains/Roslyn/RoslynToolchain.cs @@ -51,12 +51,14 @@ public override IEnumerable Validate(BenchmarkCase benchmarkCas benchmarkCase); } +#pragma warning disable CS0618 // Type or member is obsolete if (benchmarkCase.Job.HasValue(InfrastructureMode.NuGetReferencesCharacteristic)) { yield return new ValidationError(true, "The Roslyn toolchain does not allow specifying NuGet package dependencies", benchmarkCase); } +#pragma warning restore CS0618 // Type or member is obsolete } } } \ No newline at end of file diff --git a/tests/BenchmarkDotNet.IntegrationTests/NugetReferenceTests.cs b/tests/BenchmarkDotNet.IntegrationTests/NugetReferenceTests.cs index 65655bf3f6..e9f9469f13 100644 --- a/tests/BenchmarkDotNet.IntegrationTests/NugetReferenceTests.cs +++ b/tests/BenchmarkDotNet.IntegrationTests/NugetReferenceTests.cs @@ -21,6 +21,7 @@ public NuGetReferenceTests(ITestOutputHelper output) : base(output) } [Fact] + [Obsolete] public void UserCanSpecifyCustomNuGetPackageDependency() { var toolchain = RuntimeInformation.GetCurrentRuntime().GetToolchain(preferMsBuildToolchains: true); @@ -38,6 +39,7 @@ public void UserCanSpecifyCustomNuGetPackageDependency() } [FactEnvSpecific("Roslyn toolchain does not support .NET Core", EnvRequirement.FullFrameworkOnly)] + [Obsolete] public void RoslynToolchainDoesNotSupportNuGetPackageDependency() { var toolchain = RoslynToolchain.Instance; diff --git a/tests/BenchmarkDotNet.Tests/Configs/JobTests.cs b/tests/BenchmarkDotNet.Tests/Configs/JobTests.cs index fd47e9fd83..d7fb7519f3 100644 --- a/tests/BenchmarkDotNet.Tests/Configs/JobTests.cs +++ b/tests/BenchmarkDotNet.Tests/Configs/JobTests.cs @@ -441,6 +441,7 @@ public static void AllJobModesPropertyNamesMatchCharacteristicNames() // it's ma } [Fact] + [Obsolete] public static void WithNuGet() { var j = new Job("SomeId"); diff --git a/tests/BenchmarkDotNet.Tests/Running/JobRuntimePropertiesComparerTests.cs b/tests/BenchmarkDotNet.Tests/Running/JobRuntimePropertiesComparerTests.cs index e04409beb9..4ace61168b 100644 --- a/tests/BenchmarkDotNet.Tests/Running/JobRuntimePropertiesComparerTests.cs +++ b/tests/BenchmarkDotNet.Tests/Running/JobRuntimePropertiesComparerTests.cs @@ -95,6 +95,7 @@ public void CustomClrBuildJobsAreGroupedByVersion() } [Fact] + [System.Obsolete] public void CustomNuGetJobsAreGroupedByPackageVersion() { var config = ManualConfig.Create(DefaultConfig.Instance) From daa233906cc5aba9aa94d4bc1b3dc449e42806e9 Mon Sep 17 00:00:00 2001 From: Tim Cassell Date: Tue, 22 Jul 2025 14:36:49 -0400 Subject: [PATCH 18/37] Use `--nodeReuse:false`. (#2814) --- src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommand.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommand.cs b/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommand.cs index 607ff6f974..70f959121e 100644 --- a/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommand.cs +++ b/src/BenchmarkDotNet/Toolchains/DotNetCli/DotNetCliCommand.cs @@ -227,7 +227,7 @@ private static string GetMandatoryMsBuildSettings(string buildConfiguration) { // we use these settings to make sure that MSBuild does the job and simply quits without spawning any long living processes // we want to avoid "file in use" and "zombie processes" issues - const string NoMsBuildZombieProcesses = "/p:UseSharedCompilation=false /p:BuildInParallel=false /m:1 /p:Deterministic=true"; + const string NoMsBuildZombieProcesses = "--nodeReuse:false /p:UseSharedCompilation=false /p:Deterministic=true"; const string EnforceOptimizations = "/p:Optimize=true"; if (string.Equals(buildConfiguration, RuntimeInformation.DebugConfigurationName, StringComparison.OrdinalIgnoreCase)) From e2d30d37433444b0f43088727a14885d76c56bfc Mon Sep 17 00:00:00 2001 From: filzrev <103790468+filzrev@users.noreply.github.com> Date: Thu, 31 Jul 2025 14:53:18 +0900 Subject: [PATCH 19/37] chore: ensure EventProcessor::OnEndValidationStage is called when critical validation contained (#2816) --- src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs | 4 ++-- .../BenchmarkDotNet.IntegrationTests/EventProcessorTests.cs | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs b/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs index 8ed930c1f4..fefd88a906 100644 --- a/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs +++ b/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs @@ -71,11 +71,11 @@ internal static Summary[] Run(BenchmarkRunInfo[] benchmarkRunInfos) PrintValidationErrors(compositeLogger, validationErrors); + eventProcessor.OnEndValidationStage(); // Ensure that OnEndValidationStage() is called when a critical validation error exists. + if (validationErrors.Any(validationError => validationError.IsCritical)) return new[] { Summary.ValidationFailed(title, resultsFolderPath, logFilePath, validationErrors.ToImmutableArray()) }; - eventProcessor.OnEndValidationStage(); - int totalBenchmarkCount = supportedBenchmarks.Sum(benchmarkInfo => benchmarkInfo.BenchmarksCases.Length); int benchmarksToRunCount = totalBenchmarkCount - (idToResume + 1); // ids are indexed from 0 compositeLogger.WriteLineHeader("// ***** BenchmarkRunner: Start *****"); diff --git a/tests/BenchmarkDotNet.IntegrationTests/EventProcessorTests.cs b/tests/BenchmarkDotNet.IntegrationTests/EventProcessorTests.cs index f9272edd2d..d188be01d4 100644 --- a/tests/BenchmarkDotNet.IntegrationTests/EventProcessorTests.cs +++ b/tests/BenchmarkDotNet.IntegrationTests/EventProcessorTests.cs @@ -23,9 +23,10 @@ public class EventProcessorTests public void WhenUsingEventProcessorAndNoBenchmarks() { var events = RunBenchmarksAndRecordEvents(new[] { typeof(ClassEmpty) }); - Assert.Equal(2, events.Count); + Assert.Equal(3, events.Count); Assert.Equal(nameof(EventProcessor.OnStartValidationStage), events[0].EventType); Assert.Equal(nameof(EventProcessor.OnValidationError), events[1].EventType); + Assert.Equal(nameof(EventProcessor.OnEndValidationStage), events[2].EventType); } [Fact] @@ -135,12 +136,13 @@ public void WhenUsingEventProcessorWithUnsupportedBenchmark() var toolchain = new AllUnsupportedToolchain(); var events = RunBenchmarksAndRecordEvents(new[] { typeof(ClassA) }, toolchain: toolchain); - Assert.Equal(3, events.Count); + Assert.Equal(4, events.Count); Assert.Equal(nameof(EventProcessor.OnStartValidationStage), events[0].EventType); Assert.Equal(nameof(EventProcessor.OnValidationError), events[1].EventType); Assert.Equal(typeof(ClassA).GetMethod(nameof(ClassA.Method1)), (events[1].Args[0] as ValidationError).BenchmarkCase.Descriptor.WorkloadMethod); Assert.Equal(nameof(EventProcessor.OnValidationError), events[2].EventType); Assert.Equal(typeof(ClassA).GetMethod(nameof(ClassA.Method2)), (events[2].Args[0] as ValidationError).BenchmarkCase.Descriptor.WorkloadMethod); + Assert.Equal(nameof(EventProcessor.OnEndValidationStage), events[3].EventType); } [Fact] From e7cf8b4a25aec2c561029e6e665c0942c2c042da Mon Sep 17 00:00:00 2001 From: filzrev <103790468+filzrev@users.noreply.github.com> Date: Sat, 23 Aug 2025 17:09:09 +0900 Subject: [PATCH 20/37] chore: suppress XmlException thrown when TextReader.Null passed (#2817) --- .../Toolchains/AppConfigGenerator.cs | 50 ++++++++++++------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/src/BenchmarkDotNet/Toolchains/AppConfigGenerator.cs b/src/BenchmarkDotNet/Toolchains/AppConfigGenerator.cs index c1db452d2b..fcaf3824b8 100644 --- a/src/BenchmarkDotNet/Toolchains/AppConfigGenerator.cs +++ b/src/BenchmarkDotNet/Toolchains/AppConfigGenerator.cs @@ -8,6 +8,8 @@ using BenchmarkDotNet.Extensions; using BenchmarkDotNet.Jobs; +#nullable enable + namespace BenchmarkDotNet.Toolchains { internal static class AppConfigGenerator @@ -26,22 +28,32 @@ internal static class AppConfigGenerator internal static void Generate(Job job, TextReader source, TextWriter destination, IResolver resolver) { - using (var xmlReader = XmlReader.Create(source)) - { - var xmlDocument = new XmlDocument(); + var xmlDocument = new XmlDocument(); + + XmlNode configurationElement; - var configurationElement = GetOrCreateConfigurationElement(xmlDocument, xmlReader); + if (source == TextReader.Null) + { + // Create a new configuration node. + configurationElement = xmlDocument.CreateNode(XmlNodeType.Element, "configuration", string.Empty); + xmlDocument.AppendChild(configurationElement); + } + else + { + // Try to get configuration node from specified TextReader. + using var xmlReader = XmlReader.Create(source); + configurationElement = GetOrCreateConfigurationElement(xmlDocument, xmlReader); + } - var runtimeElement = GetOrCreateRuntimeElement(xmlDocument, configurationElement); + var runtimeElement = GetOrCreateRuntimeElement(xmlDocument, configurationElement); - ClearStartupSettingsForCustomClr(configurationElement, job.Environment.Runtime); - ClearAllRuntimeSettingsThatCanBeSetOnlyByJobConfiguration(runtimeElement); + ClearStartupSettingsForCustomClr(configurationElement, job.Environment.Runtime); + ClearAllRuntimeSettingsThatCanBeSetOnlyByJobConfiguration(runtimeElement); - GenerateJitSettings(xmlDocument, runtimeElement, job.Environment); - GenerateGCSettings(xmlDocument, runtimeElement, job.Environment.Gc, resolver); + GenerateJitSettings(xmlDocument, runtimeElement, job.Environment); + GenerateGCSettings(xmlDocument, runtimeElement, job.Environment.Gc, resolver); - xmlDocument.Save(destination); - } + xmlDocument.Save(destination); } private static XmlNode GetOrCreateConfigurationElement(XmlDocument xmlDocument, XmlReader xmlReader) @@ -49,19 +61,23 @@ private static XmlNode GetOrCreateConfigurationElement(XmlDocument xmlDocument, try { xmlDocument.Load(xmlReader); - - return xmlDocument.SelectSingleNode("/configuration"); + var configurationNode = xmlDocument.SelectSingleNode("/configuration"); + if (configurationNode != null) + return configurationNode; } - catch // empty document + catch (XmlException) { - return xmlDocument.AppendChild(xmlDocument.CreateNode(XmlNodeType.Element, "configuration", string.Empty)); + // Failed to load XML content. } + + // If the XML is invalid or configuration node is not exists. Create a new configuration element + return xmlDocument.AppendChild(xmlDocument.CreateNode(XmlNodeType.Element, "configuration", string.Empty))!; } private static XmlNode GetOrCreateRuntimeElement(XmlDocument xmlDocument, XmlNode configurationElement) { return configurationElement.SelectSingleNode("runtime") - ?? configurationElement.AppendChild(xmlDocument.CreateNode(XmlNodeType.Element, "runtime", string.Empty)); + ?? configurationElement.AppendChild(xmlDocument.CreateNode(XmlNodeType.Element, "runtime", string.Empty))!; } private static void ClearAllRuntimeSettingsThatCanBeSetOnlyByJobConfiguration(XmlNode runtimeElement) @@ -75,7 +91,7 @@ private static void ClearAllRuntimeSettingsThatCanBeSetOnlyByJobConfiguration(Xm } } - private static void ClearStartupSettingsForCustomClr(XmlNode configurationElement, Runtime runtime) + private static void ClearStartupSettingsForCustomClr(XmlNode configurationElement, Runtime? runtime) { if (!(runtime is ClrRuntime clrRuntime) || string.IsNullOrEmpty(clrRuntime.Version)) return; From e704adb6926f4743a361800676817094ca1787d0 Mon Sep 17 00:00:00 2001 From: Jimmy Cushnie Date: Sat, 23 Aug 2025 05:16:29 -0400 Subject: [PATCH 21/37] Fix ArgumentsSource on external types not working if the argument type is not primitive (#2820) * Fix generated code breaking when using ArgumentsSource on an external type * IntroArgumentsSource: use static type for external arguments * Add more test cases for ArgumentsSource in external class Catches some issues not previously caught. * Add test cases for ParamsSource with non-primitive param types * Fix casts Co-authored-by: Tim Cassell --------- Co-authored-by: Tim Cassell --- .../IntroArgumentsSource.cs | 4 +- .../Parameters/SmartParamBuilder.cs | 14 ++- .../ArgumentsTests.cs | 112 ++++++++++++++++-- 3 files changed, 114 insertions(+), 16 deletions(-) diff --git a/samples/BenchmarkDotNet.Samples/IntroArgumentsSource.cs b/samples/BenchmarkDotNet.Samples/IntroArgumentsSource.cs index 54dccecc84..493422e51f 100644 --- a/samples/BenchmarkDotNet.Samples/IntroArgumentsSource.cs +++ b/samples/BenchmarkDotNet.Samples/IntroArgumentsSource.cs @@ -24,9 +24,9 @@ public class IntroArgumentsSource public void SingleArgument(TimeSpan time) => Thread.Sleep(time); } - public class BenchmarkArguments + public static class BenchmarkArguments { - public IEnumerable TimeSpans() // for single argument it's an IEnumerable of objects (object) + 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/src/BenchmarkDotNet/Parameters/SmartParamBuilder.cs b/src/BenchmarkDotNet/Parameters/SmartParamBuilder.cs index 29814d8179..49ea1b162f 100644 --- a/src/BenchmarkDotNet/Parameters/SmartParamBuilder.cs +++ b/src/BenchmarkDotNet/Parameters/SmartParamBuilder.cs @@ -99,8 +99,20 @@ public string ToSourceCode() ? $"[{argumentIndex}]" // IEnumerable : string.Empty; // IEnumerable + string methodCall; + if ((source as MethodInfo)?.IsStatic ?? (source as PropertyInfo)?.GetMethod.IsStatic ?? throw new Exception($"{nameof(source)} was not {nameof(MethodInfo)} nor {nameof(PropertyInfo)}")) + { + // If the source member is static, we need to place the fully qualified type name before it, in case the source member is from another type that this generated type does not inherit from. + methodCall = $"{source.DeclaringType.GetCorrectCSharpTypeName()}.{source.Name}"; + } + else + { + // If the source member is non-static, we mustn't include the type name, as this would be a compiler error when accessing a non-static source member in the base class of this generated type. + methodCall = source.Name; + } + // we do something like enumerable.ElementAt(sourceIndex)[argumentIndex]; - return $"{cast}BenchmarkDotNet.Parameters.ParameterExtractor.GetParameter({source.Name}{callPostfix}, {sourceIndex}){indexPostfix};"; + return $"{cast}BenchmarkDotNet.Parameters.ParameterExtractor.GetParameter({methodCall}{callPostfix}, {sourceIndex}){indexPostfix};"; } } diff --git a/tests/BenchmarkDotNet.IntegrationTests/ArgumentsTests.cs b/tests/BenchmarkDotNet.IntegrationTests/ArgumentsTests.cs index 77d8ecb931..021ff5d96d 100644 --- a/tests/BenchmarkDotNet.IntegrationTests/ArgumentsTests.cs +++ b/tests/BenchmarkDotNet.IntegrationTests/ArgumentsTests.cs @@ -1,13 +1,13 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; -using System.Threading.Tasks; -using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Tests.XUnit; using BenchmarkDotNet.Toolchains; using BenchmarkDotNet.Toolchains.InProcess.Emit; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; @@ -87,20 +87,85 @@ public IEnumerable ArgumentsProvider() public class WithArgumentsSourceInAnotherClass { [Benchmark] - [ArgumentsSource(typeof(ExternalClassWithArgumentsSource), nameof(ExternalClassWithArgumentsSource.ArgumentsProvider))] - public void Simple(bool boolean, int number) + [ArgumentsSource(typeof(ExternalClassWithArgumentsSource), nameof(ExternalClassWithArgumentsSource.OnePrimitiveType))] + public void OnePrimitiveType(int number) + { + if (number % 2 != 1) + throw new InvalidOperationException("Incorrect values were passed"); + } + + [Benchmark] + [ArgumentsSource(typeof(ExternalClassWithArgumentsSource), nameof(ExternalClassWithArgumentsSource.TwoPrimitiveTypes))] + public void TwoPrimitiveTypes(bool boolean, int number) { if (boolean && number != 1 || !boolean && number != 2) throw new InvalidOperationException("Incorrect values were passed"); } + + [Benchmark] + [ArgumentsSource(typeof(ExternalClassWithArgumentsSource), nameof(ExternalClassWithArgumentsSource.OneNonPrimitiveType))] + public void OneNonPrimitiveType(Version version) + { + int[] versionNumbers = { version.Major, version.Minor, version.MinorRevision, version.Build }; + if (versionNumbers.Distinct().Count() != 4) + throw new InvalidOperationException("Incorrect values were passed"); + } + + [Benchmark] + [ArgumentsSource(typeof(ExternalClassWithArgumentsSource), nameof(ExternalClassWithArgumentsSource.TwoNonPrimitiveTypes))] + public void TwoNonPrimitiveTypes(Version version, DateTime dateTime) + { + int[] versionNumbers = { version.Major, version.Minor, version.MinorRevision, version.Build }; + if (versionNumbers.Distinct().Count() != 4) + throw new InvalidOperationException("Incorrect values were passed"); + + if (dateTime.Month != dateTime.Day) + throw new InvalidOperationException("Incorrect values were passed"); + } + + [Benchmark] + [ArgumentsSource(typeof(ExternalClassWithArgumentsSource), nameof(ExternalClassWithArgumentsSource.OnePrimitiveAndOneNonPrimitive))] + public void OnePrimitiveAndOneNonPrimitive(Version version, int number) + { + int[] versionNumbers = { version.Major, version.Minor, version.MinorRevision, version.Build }; + if (versionNumbers.Distinct().Count() != 4) + throw new InvalidOperationException("Incorrect values were passed"); + + if (number != version.Major) + throw new InvalidOperationException("Incorrect values were passed"); + } } public static class ExternalClassWithArgumentsSource { - public static IEnumerable ArgumentsProvider() + public static IEnumerable OnePrimitiveType() + { + yield return 3; + yield return 5; + } + + public static IEnumerable TwoPrimitiveTypes() { yield return new object[] { true, 1 }; yield return new object[] { false, 2 }; } + + public static IEnumerable OneNonPrimitiveType() + { + yield return new Version(1, 2, 3, 4); + yield return new Version(5, 6, 7, 8); + } + + public static IEnumerable TwoNonPrimitiveTypes() + { + yield return new object[] { new Version(1, 2, 3, 4), new DateTime(2011, 11, 11) }; + yield return new object[] { new Version(5, 6, 7, 8), new DateTime(2002, 02, 02) }; + } + + public static IEnumerable OnePrimitiveAndOneNonPrimitive() + { + yield return new object[] { new Version(1, 2, 3, 4), 1 }; + yield return new object[] { new Version(5, 6, 7, 8), 5 }; + } } [Theory, MemberData(nameof(GetToolchains), DisableDiscoveryEnumeration = true)] @@ -775,12 +840,18 @@ public void MethodsAndPropertiesFromAnotherClassCanBeUsedAsSources(IToolchain to public class ParamsSourcePointingToAnotherClass { - [ParamsSource(typeof(ExternalClassWithParamsSource), nameof(ExternalClassWithParamsSource.Method))] + [ParamsSource(typeof(ExternalClassWithParamsSource), nameof(ExternalClassWithParamsSource.PrimitiveTypeMethod))] public int ParamOne { get; set; } - [ParamsSource(typeof(ExternalClassWithParamsSource), nameof(ExternalClassWithParamsSource.Property))] + [ParamsSource(typeof(ExternalClassWithParamsSource), nameof(ExternalClassWithParamsSource.PrimitiveTypeProperty))] public int ParamTwo { get; set; } + [ParamsSource(typeof(ExternalClassWithParamsSource), nameof(ExternalClassWithParamsSource.NonPrimitiveTypeMethod))] + public Version ParamThree { get; set; } + + [ParamsSource(typeof(ExternalClassWithParamsSource), nameof(ExternalClassWithParamsSource.NonPrimitiveTypeProperty))] + public Version ParamFour { get; set; } + [Benchmark] public void Test() { @@ -788,21 +859,36 @@ public void Test() throw new ArgumentException("The ParamOne value is incorrect!"); if (ParamTwo != 456) throw new ArgumentException("The ParamTwo value is incorrect!"); + if (ParamThree != new Version(1, 2, 3, 4)) + throw new ArgumentException("The ParamThree value is incorrect!"); + if (ParamFour != new Version(5, 6, 7, 8)) + throw new ArgumentException("The ParamFour value is incorrect!"); } } public static class ExternalClassWithParamsSource { - public static IEnumerable Method() + public static IEnumerable PrimitiveTypeMethod() { yield return 123; } - public static IEnumerable Property + public static IEnumerable PrimitiveTypeProperty { get { yield return 456; } } + public static IEnumerable NonPrimitiveTypeMethod() + { + yield return new Version(1, 2, 3, 4); + } + public static IEnumerable NonPrimitiveTypeProperty + { + get + { + yield return new Version(5, 6, 7, 8); + } + } } [Theory, MemberData(nameof(GetToolchains), DisableDiscoveryEnumeration = true)] From 0806c67ffb4029e0043d66cb23a12b15dfb0051f Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Wed, 10 Sep 2025 23:00:20 -0700 Subject: [PATCH 22/37] Update the naot instruction set support for .NET 10+ (#2828) * Update the naot instruction set support for .NET 10+ * Apply suggestions from code review * Apply suggestions from code review Co-authored-by: Tim Cassell * Apply suggestions from code review --------- Co-authored-by: Tim Cassell --- .../Detectors/Cpu/HardwareIntrinsics.cs | 290 +++++++----------- .../Toolchains/NativeAot/Generator.cs | 84 +++-- 2 files changed, 179 insertions(+), 195 deletions(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/HardwareIntrinsics.cs b/src/BenchmarkDotNet/Detectors/Cpu/HardwareIntrinsics.cs index 265143a356..5b2815cc43 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/HardwareIntrinsics.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/HardwareIntrinsics.cs @@ -11,38 +11,40 @@ 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) @@ -55,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"; @@ -89,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 => @@ -165,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 @@ -279,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 @@ -332,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/Toolchains/NativeAot/Generator.cs b/src/BenchmarkDotNet/Toolchains/NativeAot/Generator.cs index f1e32d6c6b..e9c64a82f5 100644 --- a/src/BenchmarkDotNet/Toolchains/NativeAot/Generator.cs +++ b/src/BenchmarkDotNet/Toolchains/NativeAot/Generator.cs @@ -236,7 +236,7 @@ private void GenerateReflectionFile(ArtifactsPaths artifactsPaths) private string GetCurrentInstructionSet(Platform platform) => string.Join(",", GetCurrentProcessInstructionSets(platform)); - // based on https://github.com/dotnet/runtime/blob/ce61c09a5f6fc71d8f717d3fc4562f42171869a0/src/coreclr/tools/Common/JitInterface/CorInfoInstructionSet.cs#L727 + // based on https://github.com/dotnet/runtime/tree/v10.0.0-rc.1.25451.107/src/coreclr/tools/Common/JitInterface/ThunkGenerator/InstructionSetDesc.txt private IEnumerable GetCurrentProcessInstructionSets(Platform platform) { if (!ConfigParser.TryParse(TargetFrameworkMoniker, out RuntimeMoniker runtimeMoniker)) @@ -256,39 +256,77 @@ private IEnumerable GetCurrentProcessInstructionSets(Platform platform) case Platform.X86: case Platform.X64: if (HardwareIntrinsics.IsX86BaseSupported) yield return "base"; - if (HardwareIntrinsics.IsX86SseSupported) yield return "sse"; - if (HardwareIntrinsics.IsX86Sse2Supported) yield return "sse2"; - if (HardwareIntrinsics.IsX86Sse3Supported) yield return "sse3"; - if (HardwareIntrinsics.IsX86Ssse3Supported) yield return "ssse3"; - if (HardwareIntrinsics.IsX86Sse41Supported) yield return "sse4.1"; - if (HardwareIntrinsics.IsX86Sse42Supported) yield return "sse4.2"; + if (HardwareIntrinsics.IsX86Sse42Supported) + { + if (runtimeMoniker <= RuntimeMoniker.NativeAot10_0) yield return "sse4.2"; + if (runtimeMoniker <= RuntimeMoniker.NativeAot90) yield return "popcnt"; + } if (HardwareIntrinsics.IsX86AvxSupported) yield return "avx"; - if (HardwareIntrinsics.IsX86Avx2Supported) yield return "avx2"; - if (HardwareIntrinsics.IsX86Avx512FSupported) yield return "avx512f"; - if (HardwareIntrinsics.IsX86Avx512BWSupported) yield return "avx512bw"; - if (HardwareIntrinsics.IsX86Avx512CDSupported) yield return "avx512cd"; - if (HardwareIntrinsics.IsX86Avx512DQSupported) yield return "avx512dq"; - if (HardwareIntrinsics.IsX86Avx512VbmiSupported) yield return "avx512vbmi"; - if (HardwareIntrinsics.IsX86AesSupported) yield return "aes"; - if (HardwareIntrinsics.IsX86Bmi1Supported) yield return "bmi"; - if (HardwareIntrinsics.IsX86Bmi2Supported) yield return "bmi2"; - if (HardwareIntrinsics.IsX86FmaSupported) yield return "fma"; - if (HardwareIntrinsics.IsX86LzcntSupported) yield return "lzcnt"; - if (HardwareIntrinsics.IsX86PclmulqdqSupported) yield return "pclmul"; - if (HardwareIntrinsics.IsX86PopcntSupported) yield return "popcnt"; + if (HardwareIntrinsics.IsX86Avx2Supported) + { + yield return "avx2"; + + if (runtimeMoniker <= RuntimeMoniker.NativeAot90) + { + yield return "bmi"; + yield return "bmi2"; + yield return "fma"; + yield return "lzcnt"; + } + } + if (HardwareIntrinsics.IsX86Avx512Supported && (runtimeMoniker > RuntimeMoniker.NativeAot80)) + { + if (runtimeMoniker >= RuntimeMoniker.NativeAot10_0) + { + yield return "avx512"; + } + else + { + yield return "avx512f"; + yield return "avx512f_vl"; + yield return "avx512bw"; + yield return "avx512bw_vl"; + yield return "avx512cd"; + yield return "avx512cd_vl"; + yield return "avx512dq"; + yield return "avx512dq_vl"; + } + } + if (HardwareIntrinsics.IsX86Avx512v2Supported && (runtimeMoniker > RuntimeMoniker.NativeAot80)) + { + if (runtimeMoniker >= RuntimeMoniker.NativeAot10_0) + { + yield return "avx512v2"; + } + else + { + yield return "avx512vbmi"; + yield return "avx512vbmi_vl"; + } + } + if (HardwareIntrinsics.IsX86Avx512v3Supported && (runtimeMoniker >= RuntimeMoniker.NativeAot10_0)) yield return "avx512v3"; + if (HardwareIntrinsics.IsX86Avx10v1Supported && (runtimeMoniker >= RuntimeMoniker.NativeAot90)) yield return "avx10v1"; + if (HardwareIntrinsics.IsX86Avx10v2Supported && (runtimeMoniker >= RuntimeMoniker.NativeAot10_0)) yield return "avx10v2"; + if (HardwareIntrinsics.IsX86AesSupported) + { + yield return "aes"; + if (runtimeMoniker <= RuntimeMoniker.NativeAot90) yield return "pclmul"; + } if (HardwareIntrinsics.IsX86AvxVnniSupported) yield return "avxvnni"; if (HardwareIntrinsics.IsX86SerializeSupported && runtimeMoniker > RuntimeMoniker.NativeAot70) yield return "serialize"; // https://github.com/dotnet/BenchmarkDotNet/issues/2463#issuecomment-1809625008 break; case Platform.Arm64: - if (HardwareIntrinsics.IsArmBaseSupported) yield return "base"; - if (HardwareIntrinsics.IsArmAdvSimdSupported) yield return "neon"; + if (HardwareIntrinsics.IsArmBaseSupported) + { + yield return "base"; + yield return "neon"; + } if (HardwareIntrinsics.IsArmAesSupported) yield return "aes"; if (HardwareIntrinsics.IsArmCrc32Supported) yield return "crc"; if (HardwareIntrinsics.IsArmDpSupported) yield return "dotprod"; if (HardwareIntrinsics.IsArmRdmSupported) yield return "rdma"; if (HardwareIntrinsics.IsArmSha1Supported) yield return "sha1"; if (HardwareIntrinsics.IsArmSha256Supported) yield return "sha2"; - // todo: handle "lse" break; default: yield break; From 96620b63b1e3633a7b51ea699ae940254aa365d1 Mon Sep 17 00:00:00 2001 From: Yuhang Date: Tue, 15 Jul 2025 18:10:17 -0400 Subject: [PATCH 23/37] Fix typo in BuildPlots.R --- src/BenchmarkDotNet/Templates/BuildPlots.R | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/BenchmarkDotNet/Templates/BuildPlots.R b/src/BenchmarkDotNet/Templates/BuildPlots.R index da071d8d41..c5dd6118f6 100644 --- a/src/BenchmarkDotNet/Templates/BuildPlots.R +++ b/src/BenchmarkDotNet/Templates/BuildPlots.R @@ -105,7 +105,7 @@ for (file in files) { densityPlot <- ggplot(df, aes(x=Measurement_Value, fill=Job_Id)) + ggtitle(paste(title, "/", target)) + xlab(paste("Time,", timeUnit)) + - geom_density(alpha=.5, bw="SJ") + geom_density(alpha=.5, bw="sj") printNice(densityPlot) ggsaveNice(gsub("-measurements.csv", paste0("-", target, "-density.png"), file), densityPlot) @@ -118,7 +118,7 @@ for (file in files) { paramsDensityPlot <- ggplot(paramsDf, aes(x=Measurement_Value, fill=Job_Id)) + ggtitle(paste(title, "/", target, "/", params)) + xlab(paste("Time,", timeUnit)) + - geom_density(alpha=.5, bw="SJ") + geom_density(alpha=.5, bw="sj") printNice(paramsDensityPlot) prefix <- createPrefix(c(target,params)) ggsaveNice(gsub("-measurements.csv", paste0(prefix, "-density.png"), file), paramsDensityPlot) @@ -155,7 +155,7 @@ for (file in files) { densityPlotJob <- ggplot(jobDf, aes(x=Measurement_Value, fill="red")) + ggtitle(paste(title, "/", target, "/", job)) + xlab(paste("Time,", timeUnit)) + - geom_density(alpha=.5, bw="SJ") + geom_density(alpha=.5, bw="sj") printNice(densityPlotJob) ggsaveNice(gsub("-measurements.csv", paste0("-", target, "-", job, "-density.png"), file), densityPlotJob) } From f1152216b4f83b1585b05ab9764c0dec46ee2734 Mon Sep 17 00:00:00 2001 From: Andrey Akinshin Date: Wed, 17 Sep 2025 17:08:03 +0200 Subject: [PATCH 24/37] Set next BenchmarkDotNet version: 0.15.4 --- README.md | 2 +- build/common.props | 2 +- build/versions.txt | 3 ++- .../.template.config/template.json | 2 +- .../.template.config/template.json | 2 +- .../.template.config/template.json | 2 +- 6 files changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 1995deff62..a9b8a76e9b 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 [26400+ GitHub projects](https://github.com/dotnet/BenchmarkDotNet/network/dependents) including +BenchmarkDotNet is already adopted by [27000+ 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), diff --git a/build/common.props b/build/common.props index b33bc4d863..6fc04d2938 100644 --- a/build/common.props +++ b/build/common.props @@ -45,7 +45,7 @@ - 0.15.3 + 0.15.4 diff --git a/build/versions.txt b/build/versions.txt index df0365a030..4b22ce0ad0 100644 --- a/build/versions.txt +++ b/build/versions.txt @@ -61,4 +61,5 @@ 0.15.0 0.15.1 0.15.2 -0.15.3 \ No newline at end of file +0.15.3 +0.15.4 \ No newline at end of file diff --git a/templates/templates/BenchmarkDotNet.BenchmarkProjectTemplate.CSharp/.template.config/template.json b/templates/templates/BenchmarkDotNet.BenchmarkProjectTemplate.CSharp/.template.config/template.json index 1d1a4bce6f..db2ededcbb 100644 --- a/templates/templates/BenchmarkDotNet.BenchmarkProjectTemplate.CSharp/.template.config/template.json +++ b/templates/templates/BenchmarkDotNet.BenchmarkProjectTemplate.CSharp/.template.config/template.json @@ -139,7 +139,7 @@ "type": "parameter", "datatype": "string", "description": "Version of BenchmarkDotNet that will be referenced.", - "defaultValue": "0.15.3", + "defaultValue": "0.15.4", "replaces": "$(BenchmarkDotNetVersion)" } }, diff --git a/templates/templates/BenchmarkDotNet.BenchmarkProjectTemplate.FSharp/.template.config/template.json b/templates/templates/BenchmarkDotNet.BenchmarkProjectTemplate.FSharp/.template.config/template.json index 3a242758c0..e18fc9a0a6 100644 --- a/templates/templates/BenchmarkDotNet.BenchmarkProjectTemplate.FSharp/.template.config/template.json +++ b/templates/templates/BenchmarkDotNet.BenchmarkProjectTemplate.FSharp/.template.config/template.json @@ -139,7 +139,7 @@ "type": "parameter", "datatype": "string", "description": "Version of BenchmarkDotNet that will be referenced.", - "defaultValue": "0.15.3", + "defaultValue": "0.15.4", "replaces": "$(BenchmarkDotNetVersion)" } }, diff --git a/templates/templates/BenchmarkDotNet.BenchmarkProjectTemplate.VB/.template.config/template.json b/templates/templates/BenchmarkDotNet.BenchmarkProjectTemplate.VB/.template.config/template.json index 42929a3e01..67c362a8fb 100644 --- a/templates/templates/BenchmarkDotNet.BenchmarkProjectTemplate.VB/.template.config/template.json +++ b/templates/templates/BenchmarkDotNet.BenchmarkProjectTemplate.VB/.template.config/template.json @@ -139,7 +139,7 @@ "type": "parameter", "datatype": "string", "description": "Version of BenchmarkDotNet that will be referenced.", - "defaultValue": "0.15.3", + "defaultValue": "0.15.4", "replaces": "$(BenchmarkDotNetVersion)" } }, From 4071cae35d27a2be1d42fb4b59972294dfadc0a7 Mon Sep 17 00:00:00 2001 From: Michael Niksa Date: Thu, 18 Sep 2025 15:05:54 -0700 Subject: [PATCH 25/37] Fix condition for TestTfmsInParallel property (#2831) Condition needs double equals to be a comparison. As it is, it won't load in Visual Studio right now. --- .../build/BenchmarkDotNet.TestAdapter.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BenchmarkDotNet.TestAdapter/build/BenchmarkDotNet.TestAdapter.props b/src/BenchmarkDotNet.TestAdapter/build/BenchmarkDotNet.TestAdapter.props index b672c62139..7184842aba 100644 --- a/src/BenchmarkDotNet.TestAdapter/build/BenchmarkDotNet.TestAdapter.props +++ b/src/BenchmarkDotNet.TestAdapter/build/BenchmarkDotNet.TestAdapter.props @@ -2,7 +2,7 @@ $(MSBuildThisFileDirectory)..\entrypoints\ - false + false + $(RuntimeIdentifier) + + + + + + linux + win + osx + + $(BenchmarkDotNetTargetPlatform)-x64 + $(BenchmarkDotNetTargetPlatform)-x86 + $(BenchmarkDotNetTargetPlatform)-arm + $(BenchmarkDotNetTargetPlatform)-arm64 + + + + + + + + + + + + + + From e099c2eb0cb9fb49f8c2e9d1f9d94cb5e8affb4b Mon Sep 17 00:00:00 2001 From: Tim Cassell Date: Thu, 2 Oct 2025 14:21:21 -0400 Subject: [PATCH 30/37] Update run-tests.yaml (#2835) --- .github/workflows/run-tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yaml b/.github/workflows/run-tests.yaml index 9d3f25a9e8..06025c4880 100644 --- a/.github/workflows/run-tests.yaml +++ b/.github/workflows/run-tests.yaml @@ -99,7 +99,7 @@ jobs: - runs-on: 'macos-latest' jsvu-os: 'mac64arm' arch: 'arm64' - - runs-on: 'macos-13' + - runs-on: 'macos-15-intel' jsvu-os: 'mac64' arch: 'x64' steps: From 1c3faa21ab9aac8aab277e1fd19e151768c57cba Mon Sep 17 00:00:00 2001 From: Ritoban Dutta <124308320+ritoban23@users.noreply.github.com> Date: Tue, 14 Oct 2025 01:10:50 +0530 Subject: [PATCH 31/37] Fix job names consistency between SimpleJobAttribute and --runtimes (#2841) --- .../ConsoleArguments/ConfigParser.cs | 34 +++++++++++++------ 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/src/BenchmarkDotNet/ConsoleArguments/ConfigParser.cs b/src/BenchmarkDotNet/ConsoleArguments/ConfigParser.cs index ac61ddb5f1..e985ce09e4 100644 --- a/src/BenchmarkDotNet/ConsoleArguments/ConfigParser.cs +++ b/src/BenchmarkDotNet/ConsoleArguments/ConfigParser.cs @@ -468,7 +468,8 @@ private static IEnumerable Expand(Job baseJob, CommandLineOptions options, } else if (!string.IsNullOrEmpty(options.ClrVersion)) { - yield return baseJob.WithRuntime(ClrRuntime.CreateForLocalFullNetFrameworkBuild(options.ClrVersion)); // local builds of .NET Runtime + var runtime = ClrRuntime.CreateForLocalFullNetFrameworkBuild(options.ClrVersion); + yield return baseJob.WithRuntime(runtime).WithId(runtime.Name); // local builds of .NET Runtime } else if (options.CliPath != null && options.Runtimes.IsEmpty() && options.CoreRunPaths.IsEmpty()) { @@ -518,9 +519,13 @@ private static Job CreateJobForGivenRuntime(Job baseJob, string runtimeId, Comma case RuntimeMoniker.Net472: case RuntimeMoniker.Net48: case RuntimeMoniker.Net481: - return baseJob - .WithRuntime(runtimeMoniker.GetRuntime()) - .WithToolchain(CsProjClassicNetToolchain.From(runtimeId, options.RestorePath?.FullName, options.CliPath?.FullName)); + { + var runtime = runtimeMoniker.GetRuntime(); + return baseJob + .WithRuntime(runtime) + .WithId(runtime.Name) + .WithToolchain(CsProjClassicNetToolchain.From(runtimeId, options.RestorePath?.FullName, options.CliPath?.FullName)); + } case RuntimeMoniker.NetCoreApp20: case RuntimeMoniker.NetCoreApp21: @@ -536,12 +541,19 @@ private static Job CreateJobForGivenRuntime(Job baseJob, string runtimeId, Comma 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))); + { + var runtime = runtimeMoniker.GetRuntime(); + return baseJob + .WithRuntime(runtime) + .WithId(runtime.Name) + .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)); + { + var runtime = new MonoRuntime("Mono", options.MonoPath?.FullName); + return baseJob.WithRuntime(runtime).WithId(runtime.Name); + } 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"); @@ -636,7 +648,7 @@ private static Job CreateAotJob(Job baseJob, CommandLineOptions options, Runtime var runtime = runtimeMoniker.GetRuntime(); builder.TargetFrameworkMoniker(runtime.MsBuildMoniker); - return baseJob.WithRuntime(runtime).WithToolchain(builder.ToToolchain()); + return baseJob.WithRuntime(runtime).WithToolchain(builder.ToToolchain()).WithId(runtime.Name); } private static Job MakeMonoJob(Job baseJob, CommandLineOptions options, MonoRuntime runtime) @@ -667,7 +679,7 @@ private static Job MakeMonoAOTLLVMJob(Job baseJob, CommandLineOptions options, s aotCompilerPath: options.AOTCompilerPath.ToString(), aotCompilerMode: options.AOTCompilerMode)); - return baseJob.WithRuntime(monoAotLLVMRuntime).WithToolchain(toolChain); + return baseJob.WithRuntime(monoAotLLVMRuntime).WithToolchain(toolChain).WithId(monoAotLLVMRuntime.Name); } private static Job MakeWasmJob(Job baseJob, CommandLineOptions options, string msBuildMoniker, RuntimeMoniker moniker) @@ -691,7 +703,7 @@ private static Job MakeWasmJob(Job baseJob, CommandLineOptions options, string m customRuntimePack: options.CustomRuntimePack, aotCompilerMode: options.AOTCompilerMode)); - return baseJob.WithRuntime(wasmRuntime).WithToolchain(toolChain); + return baseJob.WithRuntime(wasmRuntime).WithToolchain(toolChain).WithId(wasmRuntime.Name); } private static IEnumerable GetFilters(CommandLineOptions options) From 5cae925c0374dae3eb671fe9dec9818fd62cc3db Mon Sep 17 00:00:00 2001 From: Andrey Akinshin Date: Wed, 29 Oct 2025 11:11:08 +0100 Subject: [PATCH 32/37] Enable workflow_dispatch for run-tests.yaml --- .github/workflows/run-tests.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/run-tests.yaml b/.github/workflows/run-tests.yaml index 06025c4880..f893d46147 100644 --- a/.github/workflows/run-tests.yaml +++ b/.github/workflows/run-tests.yaml @@ -4,6 +4,7 @@ run-name: Run tests / ${{ github.event.head_commit.message }} on: pull_request: push: + workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.ref || github.run_id }} From 40a28961766174c9eb4791794b30e762a53828fb Mon Sep 17 00:00:00 2001 From: Andrey Akinshin Date: Wed, 29 Oct 2025 12:11:51 +0100 Subject: [PATCH 33/37] Bump Perfolizer: 0.5.3->0.5.4, see #2773 --- src/BenchmarkDotNet/BenchmarkDotNet.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BenchmarkDotNet/BenchmarkDotNet.csproj b/src/BenchmarkDotNet/BenchmarkDotNet.csproj index ee31783012..b3c16d72b0 100644 --- a/src/BenchmarkDotNet/BenchmarkDotNet.csproj +++ b/src/BenchmarkDotNet/BenchmarkDotNet.csproj @@ -22,7 +22,7 @@ - + From ded3900937d896a9892363f2d38aba6218d2d855 Mon Sep 17 00:00:00 2001 From: Andrey Akinshin Date: Wed, 29 Oct 2025 16:48:38 +0100 Subject: [PATCH 34/37] Clamp histogram bin lower bounds to non-negative values, fix #1821 --- .../Extensions/StatisticsExtensions.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/BenchmarkDotNet/Extensions/StatisticsExtensions.cs b/src/BenchmarkDotNet/Extensions/StatisticsExtensions.cs index 922dc50d44..4d6216743b 100644 --- a/src/BenchmarkDotNet/Extensions/StatisticsExtensions.cs +++ b/src/BenchmarkDotNet/Extensions/StatisticsExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Globalization; +using System.Linq; using System.Text; using BenchmarkDotNet.Helpers; using BenchmarkDotNet.Mathematics; @@ -101,11 +102,27 @@ public static string ToString(this Statistics? s, CultureInfo cultureInfo, Func< if (calcHistogram) { var histogram = HistogramBuilder.Adaptive.Build(s.Sample.Values); + MakePositive(histogram); builder.AppendLine("-------------------- Histogram --------------------"); builder.AppendLine(histogram.ToString(formatter)); builder.AppendLine("---------------------------------------------------"); } return builder.ToString().Trim(); } + + // At the moment, `HistogramBuilder.Adaptive` may extend bin edges leading to negative lower value for the first bin. + // This could be fine in a generic case, but looks confusing for non-negative measurement values. + // To avoid confusing summary, we post-process the obtained bins. + // This workaround could be removed once a new histogram algorithm is introduced in perfolizer. + // See also: https://github.com/dotnet/BenchmarkDotNet/issues/1821 + private static void MakePositive(Histogram histogram) + { + for (int i = 0; i < histogram.Bins.Length; i++) + { + var bin = histogram.Bins[i]; + if (bin.Lower < 0) + histogram.Bins[i] = new HistogramBin(bin.Values.Min(), bin.Upper, bin.Values); + } + } } } \ No newline at end of file From 0be519c2dc16dd979decb0e96a8770f97f114e8c Mon Sep 17 00:00:00 2001 From: Andrey Akinshin Date: Thu, 30 Oct 2025 19:36:18 +0100 Subject: [PATCH 35/37] [build] Update doc generation workflow --- .github/workflows/generate-changelog.yaml | 2 +- .github/workflows/generate-gh-pages.yaml | 2 +- build/BenchmarkDotNet.Build/Meta/Repo.cs | 7 ++-- build/BenchmarkDotNet.Build/Program.cs | 2 +- .../Runners/Changelog/ChangelogBuilder.cs | 28 +++++++++++---- .../Runners/GitRunner.cs | 36 ++++++++++++++++--- .../Runners/ReleaseRunner.cs | 2 +- 7 files changed, 62 insertions(+), 17 deletions(-) diff --git a/.github/workflows/generate-changelog.yaml b/.github/workflows/generate-changelog.yaml index a0538e4eac..9eb3b1a4c0 100644 --- a/.github/workflows/generate-changelog.yaml +++ b/.github/workflows/generate-changelog.yaml @@ -25,7 +25,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Push changelog - uses: JamesIves/github-pages-deploy-action@3.7.1 + uses: JamesIves/github-pages-deploy-action@v4 with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} BRANCH: docs-changelog diff --git a/.github/workflows/generate-gh-pages.yaml b/.github/workflows/generate-gh-pages.yaml index 8983ed711f..48851b0285 100644 --- a/.github/workflows/generate-gh-pages.yaml +++ b/.github/workflows/generate-gh-pages.yaml @@ -57,7 +57,7 @@ jobs: run: tree $GITHUB_WORKSPACE - name: Deploy documentation - uses: JamesIves/github-pages-deploy-action@3.7.1 + uses: JamesIves/github-pages-deploy-action@v4 with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} BRANCH: gh-pages diff --git a/build/BenchmarkDotNet.Build/Meta/Repo.cs b/build/BenchmarkDotNet.Build/Meta/Repo.cs index 22a02d51bb..bbc5c9ad93 100644 --- a/build/BenchmarkDotNet.Build/Meta/Repo.cs +++ b/build/BenchmarkDotNet.Build/Meta/Repo.cs @@ -9,12 +9,15 @@ public static class Repo { public const string Owner = "dotnet"; public const string Name = "BenchmarkDotNet"; - public const string HttpsUrlBase = $"https://github.com/{Owner}/{Name}"; - public const string HttpsGitUrl = $"{HttpsUrlBase}.git"; + private const string HttpsUrlBase = $"https://github.com/{Owner}/{Name}"; + public const string SshGitUrl = $"git@github.com:{Owner}/{Name}.git"; public const string ChangelogBranch = "docs-changelog"; public const string DocsStableBranch = "docs-stable"; public const string MasterBranch = "master"; + + public const string MaintainerAuthorName = "Andrey Akinshin "; + public const string MaintainerAuthorEmail = "andrey.akinshin@gmail.com"; public static async Task GetDependentProjectsNumber() { diff --git a/build/BenchmarkDotNet.Build/Program.cs b/build/BenchmarkDotNet.Build/Program.cs index 5fd25cc298..834ccb89f3 100644 --- a/build/BenchmarkDotNet.Build/Program.cs +++ b/build/BenchmarkDotNet.Build/Program.cs @@ -168,7 +168,7 @@ public HelpInfo GetHelp() return new HelpInfo { Description = $"This task updates the following files:\n" + - $"* Clones branch 'docs-changelog' to docs/_changelog\n" + + $"* Clones or fetches 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.ChangelogBranch})", diff --git a/build/BenchmarkDotNet.Build/Runners/Changelog/ChangelogBuilder.cs b/build/BenchmarkDotNet.Build/Runners/Changelog/ChangelogBuilder.cs index 5bfc8c91b2..56c9a3d8c0 100644 --- a/build/BenchmarkDotNet.Build/Runners/Changelog/ChangelogBuilder.cs +++ b/build/BenchmarkDotNet.Build/Runners/Changelog/ChangelogBuilder.cs @@ -77,6 +77,9 @@ public void Fetch() history.CurrentVersion, history.StableVersions.Last(), "HEAD"); + + context.GitRunner.AddAll(SrcDirectory); + context.GitRunner.Commit("Update changelog", SrcDirectory); } private void FetchDetails(string version, string versionPrevious, string lastCommit = "") @@ -89,6 +92,7 @@ private void FetchDetails(string version, string versionPrevious, string lastCom public void Generate() { + GenerateLastHeader(); GenerateLastFooter(); foreach (var version in context.VersionHistory.StableVersions) @@ -101,6 +105,18 @@ public void Generate() GenerateToc(); } + public void GenerateLastHeader() + { + var fileName = "v" + context.VersionHistory.CurrentVersion + ".md"; + var filePath = SrcDirectory.Combine("header").CombineWithFilePath(fileName); + if (!context.FileExists(filePath)) + { + var relativePath = context.RootDirectory.GetRelativePath(filePath); + context.FileWriteText(filePath, ""); + context.Information("[Created] " + relativePath); + } + } + public void GenerateLastFooter() { var version = context.VersionHistory.CurrentVersion; @@ -230,8 +246,6 @@ 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. " + @@ -244,13 +258,15 @@ private void EnsureSrcDirectoryExist(bool forceClone = false) if (!context.DirectoryExists(SrcDirectory)) { - Log($"Cloning branch '{Repo.ChangelogBranch}' from '{Repo.HttpsGitUrl}' to '{SrcDirectory}'."); - context.GitRunner.Clone(SrcDirectory, Repo.HttpsGitUrl, Repo.ChangelogBranch); + Log($"Cloning branch '{Repo.ChangelogBranch}' from '{Repo.SshGitUrl}' to '{SrcDirectory}'."); + context.GitRunner.Clone(SrcDirectory, Repo.SshGitUrl, Repo.ChangelogBranch); Log($"Clone completed: '{Repo.ChangelogBranch}' -> '{SrcDirectory}'."); } - else + else if (forceClone) { - Log($"Directory '{SrcDirectory}' already exists. Skipping clone."); + Log($"Fetching branch '{Repo.ChangelogBranch}' from '{Repo.SshGitUrl}' to '{SrcDirectory}'."); + context.GitRunner.Pull(SrcDirectory); + Log($"Fetch completed: '{Repo.ChangelogBranch}' -> '{SrcDirectory}'."); } } } \ No newline at end of file diff --git a/build/BenchmarkDotNet.Build/Runners/GitRunner.cs b/build/BenchmarkDotNet.Build/Runners/GitRunner.cs index 37ab8d5c0b..eefff045a6 100644 --- a/build/BenchmarkDotNet.Build/Runners/GitRunner.cs +++ b/build/BenchmarkDotNet.Build/Runners/GitRunner.cs @@ -1,4 +1,5 @@ using System; +using BenchmarkDotNet.Build.Meta; using Cake.Common; using Cake.Common.Diagnostics; using Cake.Core.IO; @@ -32,6 +33,16 @@ public void Clone(DirectoryPath workDirectoryPath, string sourceUrl, string bran $"clone -b {branchName} {sourceUrl} {workDirectoryPath}"); } + public void Pull(DirectoryPath workDirectoryPath) + { + context.Information($"[GitPull]"); + context.Information($" Path: {workDirectoryPath}"); + + RunCommand( + () => context.GitPull(workDirectoryPath, null, null), + $"-C {workDirectoryPath} pull"); + } + public void Tag(string tagName) { context.Information("[GitTag]"); @@ -51,14 +62,28 @@ public void BranchMove(string branchName, string target) RunCommand($"branch -f {branchName} {target}"); } - public void Commit(string message) + public void AddAll(DirectoryPath? repoDirectory = null) + { + var repoFlag = repoDirectory != null ? $"-C {repoDirectory}" : ""; + context.Information("[GitAddAll]"); + RunCommand($"{repoFlag} add -A"); + } + + public void Commit(string message, DirectoryPath? repoDirectory = null) { context.Information("[GitCommit]"); context.Information($" Message: {message}"); - RunCommand($"commit --all --message \"{message}\""); + var repoFlag = repoDirectory != null ? $"-C {repoDirectory}" : ""; + RunCommand($"{repoFlag} " + + $"-c user.name=\"{Repo.MaintainerAuthorName}\" " + + $"-c user.email=\"{Repo.MaintainerAuthorEmail}\" " + + $"commit " + + $"--author=\"{Repo.MaintainerAuthorName} <{Repo.MaintainerAuthorEmail}>\" " + + $"--all " + + $"--message \"{message}\"".Trim()); } - public void Push(string target, bool force = false) + public void Push(string target, bool force = false, DirectoryPath? repoDirectory = null) { context.Information("[GitPush]"); context.Information($" Target: {target}"); @@ -66,7 +91,8 @@ public void Push(string target, bool force = false) context.RunOnlyInPushMode(() => { var forceFlag = force ? " --force" : ""; - RunCommand($"push origin {target}{forceFlag}"); + var repoFlag = repoDirectory != null ? $"-C {repoDirectory}" : ""; + RunCommand($"{repoFlag} push origin {target}{forceFlag}".Trim()); }); } @@ -84,7 +110,7 @@ private void RunCommand(Action? call, string commandLineArgs) catch (Exception e) { if (e is not NotImplementedException) - context.Information($" Failed to perform operation via API ({e.Message})"); + context.Warning($" Failed to perform operation via API ({e.Message})"); try { context.Information($" Run command in terminal: 'git {commandLineArgs}'"); diff --git a/build/BenchmarkDotNet.Build/Runners/ReleaseRunner.cs b/build/BenchmarkDotNet.Build/Runners/ReleaseRunner.cs index e9d1fcb9de..c41209644a 100644 --- a/build/BenchmarkDotNet.Build/Runners/ReleaseRunner.cs +++ b/build/BenchmarkDotNet.Build/Runners/ReleaseRunner.cs @@ -56,7 +56,7 @@ public void Run() context.GitRunner.BranchMove(Repo.DocsStableBranch, "HEAD"); context.GitRunner.Push(Repo.MasterBranch); - context.GitRunner.Push(Repo.DocsStableBranch, true); + context.GitRunner.Push(Repo.DocsStableBranch, true, context.DocumentationRunner.ChangelogSrcDirectory); context.GitRunner.Push(tag); PushNupkg(); From 8397ad0c90a344c6773ef8c8329915ac47a6f77e Mon Sep 17 00:00:00 2001 From: Andrey Akinshin Date: Thu, 30 Oct 2025 19:51:05 +0100 Subject: [PATCH 36/37] [build] Update generate-changelog.yaml --- .github/workflows/generate-changelog.yaml | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/.github/workflows/generate-changelog.yaml b/.github/workflows/generate-changelog.yaml index 9eb3b1a4c0..613d6f5f27 100644 --- a/.github/workflows/generate-changelog.yaml +++ b/.github/workflows/generate-changelog.yaml @@ -15,21 +15,26 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v5 with: ref: master + - name: Checkout changelog + uses: actions/checkout@v5 + with: + ref: docs-changelog + path: docs/_changelog + - name: Fetch changelog - run: ./build.cmd docs-fetch --depth 1 --preview --force-clone + run: ./build.cmd docs-fetch --depth 1 --preview env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Push changelog uses: JamesIves/github-pages-deploy-action@v4 with: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - BRANCH: docs-changelog - FOLDER: docs/_changelog - GIT_CONFIG_NAME: Andrey Akinshin - GIT_CONFIG_EMAIL: andrey.akinshin@gmail.com - CLEAN: true + branch: docs-changelog + folder: docs/_changelog + git-config-name: Andrey Akinshin + git-config-email: andrey.akinshin@gmail.com + clean: true From 36579eedd2902bba6dae2632d84738d6a6aba0c2 Mon Sep 17 00:00:00 2001 From: Andrey Akinshin Date: Thu, 30 Oct 2025 19:54:35 +0100 Subject: [PATCH 37/37] [build] Update generate-gh-pages.yaml --- .github/workflows/generate-gh-pages.yaml | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/.github/workflows/generate-gh-pages.yaml b/.github/workflows/generate-gh-pages.yaml index 48851b0285..c5bc67db46 100644 --- a/.github/workflows/generate-gh-pages.yaml +++ b/.github/workflows/generate-gh-pages.yaml @@ -15,15 +15,21 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v5 with: ref: docs-stable + - name: Checkout changelog + uses: actions/checkout@v5 + with: + ref: docs-changelog + path: docs/_changelog + - name: Build BenchmarkDotNet run: ./build.cmd build - name: Fetch changelog - run: ./build.cmd docs-fetch --depth 1 --preview --force-clone + run: ./build.cmd docs-fetch --depth 1 --preview env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -43,7 +49,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v5 with: ref: docs-stable @@ -59,9 +65,8 @@ jobs: - name: Deploy documentation uses: JamesIves/github-pages-deploy-action@v4 with: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - BRANCH: gh-pages - FOLDER: site - GIT_CONFIG_NAME: Andrey Akinshin - GIT_CONFIG_EMAIL: andrey.akinshin@gmail.com - CLEAN: true \ No newline at end of file + branch: gh-pages + folder: site + git-config-name: Andrey Akinshin + git-config-email: andrey.akinshin@gmail.com + clean: true \ No newline at end of file