From 495855a002ddee957993525ec933fe2ab9dcee63 Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Wed, 4 Jun 2025 18:20:29 +0100 Subject: [PATCH 01/61] allow MosCpuDetector to run on .NET 5+ --- src/BenchmarkDotNet/Detectors/Cpu/Windows/MosCpuDetector.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/MosCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/MosCpuDetector.cs index 8ab16535f6..fa47395bdc 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; @@ -14,7 +15,8 @@ internal class MosCpuDetector : ICpuDetector [System.Runtime.Versioning.SupportedOSPlatform("windows")] #endif public bool IsApplicable() => OsDetector.IsWindows() && - RuntimeInformation.IsFullFramework && + (RuntimeInformation.IsFullFramework || + (RuntimeInformation.IsNetCore && Environment.Version.Major >= 5)) && !RuntimeInformation.IsMono; #if NET6_0_OR_GREATER From 3a75c1e8c92dce07c610eda611cf3c037d82dc5b Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Wed, 4 Jun 2025 20:03:53 +0100 Subject: [PATCH 02/61] Revert "allow MosCpuDetector to run on .NET 5+" This reverts commit 495855a002ddee957993525ec933fe2ab9dcee63. --- src/BenchmarkDotNet/Detectors/Cpu/Windows/MosCpuDetector.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/MosCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/MosCpuDetector.cs index fa47395bdc..8ab16535f6 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/MosCpuDetector.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/MosCpuDetector.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Management; using BenchmarkDotNet.Extensions; @@ -15,8 +14,7 @@ internal class MosCpuDetector : ICpuDetector [System.Runtime.Versioning.SupportedOSPlatform("windows")] #endif public bool IsApplicable() => OsDetector.IsWindows() && - (RuntimeInformation.IsFullFramework || - (RuntimeInformation.IsNetCore && Environment.Version.Major >= 5)) && + RuntimeInformation.IsFullFramework && !RuntimeInformation.IsMono; #if NET6_0_OR_GREATER From 3fbf726c6f8f2edacedb353d542188fb5b245761 Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Wed, 4 Jun 2025 22:15:30 +0100 Subject: [PATCH 03/61] add WmiLightCpu Detector --- src/BenchmarkDotNet/BenchmarkDotNet.csproj | 1 + .../Cpu/Windows/WindowsCpuDetector.cs | 3 +- .../Cpu/Windows/WmiLightCpuDetector.cs | 70 +++++++++++++++++++ 3 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 src/BenchmarkDotNet/Detectors/Cpu/Windows/WmiLightCpuDetector.cs diff --git a/src/BenchmarkDotNet/BenchmarkDotNet.csproj b/src/BenchmarkDotNet/BenchmarkDotNet.csproj index 52479a7fab..a22ff8f4e4 100644 --- a/src/BenchmarkDotNet/BenchmarkDotNet.csproj +++ b/src/BenchmarkDotNet/BenchmarkDotNet.csproj @@ -24,6 +24,7 @@ + diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WindowsCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WindowsCpuDetector.cs index 9969a1bca0..8196ad2832 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 WmiLightCpuDetector(), new WmicCpuDetector()); \ No newline at end of file diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmiLightCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmiLightCpuDetector.cs new file mode 100644 index 0000000000..3b522b673f --- /dev/null +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmiLightCpuDetector.cs @@ -0,0 +1,70 @@ +using BenchmarkDotNet.Extensions; +using BenchmarkDotNet.Portability; + +using Perfolizer.Horology; +using Perfolizer.Models; + +using System.Collections.Generic; + +using WmiLight; + +namespace BenchmarkDotNet.Detectors.Cpu.Windows +{ + internal class WmiLightCpuDetector : ICpuDetector + { + +#if NET6_0_OR_GREATER + [System.Runtime.Versioning.SupportedOSPlatform("windows")] +#endif + public CpuInfo? Detect() + { + if (!IsApplicable()) return null; + + HashSet processorModelNames = new HashSet(); + int physicalCoreCount = 0; + int logicalCoreCount = 0; + int processorsCount = 0; + int sumMaxFrequency = 0; + + using (WmiConnection connection = new WmiConnection()) + { + foreach (WmiObject processor in connection.CreateQuery("SELECT * FROM Win32_Processor")) + { + string name = processor[WmicCpuInfoKeyNames.Name]?.ToString(); + if (!string.IsNullOrEmpty(name)) + { + processorModelNames.Add(name); + processorsCount++; + physicalCoreCount += (int)(uint)processor[WmicCpuInfoKeyNames.NumberOfCores]; + logicalCoreCount += (int)(uint)processor[WmicCpuInfoKeyNames.NumberOfLogicalProcessors]; + sumMaxFrequency = (int)(uint)processor[WmicCpuInfoKeyNames.MaxClockSpeed]; + } + } + } + + string processorName = processorModelNames.Count > 0 ? string.Join(", ", processorModelNames) : null; + Frequency? maxFrequency = sumMaxFrequency > 0 && processorsCount > 0 + ? Frequency.FromMHz(sumMaxFrequency * 1.0 / processorsCount) + : 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() + }; + } + +#if NET6_0_OR_GREATER + [System.Runtime.Versioning.SupportedOSPlatform("windows")] +#endif + public bool IsApplicable() + { + return OsDetector.IsWindows() && (RuntimeInformation.IsNetCore || + RuntimeInformation.IsFullFramework) && !RuntimeInformation.IsMono; + } + } +} From 5972f77faf4673f38195d16865f334afc44bd986 Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Wed, 4 Jun 2025 22:16:10 +0100 Subject: [PATCH 04/61] Fix WmicCpuDetector being chosen if not available --- src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuDetector.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuDetector.cs index 3a92d180b8..d590a5a096 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuDetector.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuDetector.cs @@ -13,7 +13,7 @@ internal class WmicCpuDetector : ICpuDetector { private const string DefaultWmicPath = @"C:\Windows\System32\wbem\WMIC.exe"; - public bool IsApplicable() => OsDetector.IsWindows(); + public bool IsApplicable() => OsDetector.IsWindows() && File.Exists(DefaultWmicPath); public CpuInfo? Detect() { From 7dc92b07f82b44b787f9f2f8270195aa23f090bc Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Thu, 5 Jun 2025 10:34:53 +0100 Subject: [PATCH 05/61] enable NativeAOT --- .../Detectors/Cpu/Windows/WmiLightCpuDetector.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmiLightCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmiLightCpuDetector.cs index 3b522b673f..4276c00599 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmiLightCpuDetector.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmiLightCpuDetector.cs @@ -64,7 +64,7 @@ internal class WmiLightCpuDetector : ICpuDetector public bool IsApplicable() { return OsDetector.IsWindows() && (RuntimeInformation.IsNetCore || - RuntimeInformation.IsFullFramework) && !RuntimeInformation.IsMono; + RuntimeInformation.IsFullFramework || RuntimeInformation.IsNativeAOT) && !RuntimeInformation.IsMono; } } } From fd5f0964a5a0e7f86111ba7f284da8ff445361b2 Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Thu, 5 Jun 2025 10:48:05 +0100 Subject: [PATCH 06/61] simplify IsApplicable --- .../Detectors/Cpu/Windows/WmiLightCpuDetector.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmiLightCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmiLightCpuDetector.cs index 4276c00599..95ce1cb247 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmiLightCpuDetector.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmiLightCpuDetector.cs @@ -63,8 +63,7 @@ internal class WmiLightCpuDetector : ICpuDetector #endif public bool IsApplicable() { - return OsDetector.IsWindows() && (RuntimeInformation.IsNetCore || - RuntimeInformation.IsFullFramework || RuntimeInformation.IsNativeAOT) && !RuntimeInformation.IsMono; + return OsDetector.IsWindows() && !RuntimeInformation.IsMono; } } } From 9b9a5ebb68a2e99ee799c0b5fb43530ada5cb6e0 Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Thu, 5 Jun 2025 18:20:31 +0100 Subject: [PATCH 07/61] Update spacing of SupportedOsPlatform attribute Co-authored-by: Tim Cassell --- .../Detectors/Cpu/Windows/WmiLightCpuDetector.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmiLightCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmiLightCpuDetector.cs index 95ce1cb247..353b7ad6c5 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmiLightCpuDetector.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmiLightCpuDetector.cs @@ -59,7 +59,7 @@ internal class WmiLightCpuDetector : ICpuDetector } #if NET6_0_OR_GREATER - [System.Runtime.Versioning.SupportedOSPlatform("windows")] + [System.Runtime.Versioning.SupportedOSPlatform("windows")] #endif public bool IsApplicable() { From 589f23a2be0218c894e6ed3821e69a1b8411e0c0 Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Sat, 7 Jun 2025 16:06:10 +0100 Subject: [PATCH 08/61] Revert "Fix WmicCpuDetector being chosen if not available" This reverts commit 5972f77faf4673f38195d16865f334afc44bd986. --- src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuDetector.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuDetector.cs index d590a5a096..3a92d180b8 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuDetector.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuDetector.cs @@ -13,7 +13,7 @@ internal class WmicCpuDetector : ICpuDetector { private const string DefaultWmicPath = @"C:\Windows\System32\wbem\WMIC.exe"; - public bool IsApplicable() => OsDetector.IsWindows() && File.Exists(DefaultWmicPath); + public bool IsApplicable() => OsDetector.IsWindows(); public CpuInfo? Detect() { From a62fe32f22a4ee3d6cd8b4232427085466a59b64 Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Sat, 7 Jun 2025 16:44:49 +0100 Subject: [PATCH 09/61] add WMIC deprecation remarks --- src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuDetector.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuDetector.cs index 3a92d180b8..8c8f277915 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"; From 56aa5ca1829318c5af98fd577d3879af48c6af36 Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Sat, 7 Jun 2025 16:48:00 +0100 Subject: [PATCH 10/61] remove WmiLight code --- src/BenchmarkDotNet/BenchmarkDotNet.csproj | 1 - .../Cpu/Windows/WmiLightCpuDetector.cs | 69 ------------------- 2 files changed, 70 deletions(-) delete mode 100644 src/BenchmarkDotNet/Detectors/Cpu/Windows/WmiLightCpuDetector.cs diff --git a/src/BenchmarkDotNet/BenchmarkDotNet.csproj b/src/BenchmarkDotNet/BenchmarkDotNet.csproj index ef2d2cd148..7953a036ae 100644 --- a/src/BenchmarkDotNet/BenchmarkDotNet.csproj +++ b/src/BenchmarkDotNet/BenchmarkDotNet.csproj @@ -22,7 +22,6 @@ - diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmiLightCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmiLightCpuDetector.cs deleted file mode 100644 index 353b7ad6c5..0000000000 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmiLightCpuDetector.cs +++ /dev/null @@ -1,69 +0,0 @@ -using BenchmarkDotNet.Extensions; -using BenchmarkDotNet.Portability; - -using Perfolizer.Horology; -using Perfolizer.Models; - -using System.Collections.Generic; - -using WmiLight; - -namespace BenchmarkDotNet.Detectors.Cpu.Windows -{ - internal class WmiLightCpuDetector : ICpuDetector - { - -#if NET6_0_OR_GREATER - [System.Runtime.Versioning.SupportedOSPlatform("windows")] -#endif - public CpuInfo? Detect() - { - if (!IsApplicable()) return null; - - HashSet processorModelNames = new HashSet(); - int physicalCoreCount = 0; - int logicalCoreCount = 0; - int processorsCount = 0; - int sumMaxFrequency = 0; - - using (WmiConnection connection = new WmiConnection()) - { - foreach (WmiObject processor in connection.CreateQuery("SELECT * FROM Win32_Processor")) - { - string name = processor[WmicCpuInfoKeyNames.Name]?.ToString(); - if (!string.IsNullOrEmpty(name)) - { - processorModelNames.Add(name); - processorsCount++; - physicalCoreCount += (int)(uint)processor[WmicCpuInfoKeyNames.NumberOfCores]; - logicalCoreCount += (int)(uint)processor[WmicCpuInfoKeyNames.NumberOfLogicalProcessors]; - sumMaxFrequency = (int)(uint)processor[WmicCpuInfoKeyNames.MaxClockSpeed]; - } - } - } - - string processorName = processorModelNames.Count > 0 ? string.Join(", ", processorModelNames) : null; - Frequency? maxFrequency = sumMaxFrequency > 0 && processorsCount > 0 - ? Frequency.FromMHz(sumMaxFrequency * 1.0 / processorsCount) - : 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() - }; - } - -#if NET6_0_OR_GREATER - [System.Runtime.Versioning.SupportedOSPlatform("windows")] -#endif - public bool IsApplicable() - { - return OsDetector.IsWindows() && !RuntimeInformation.IsMono; - } - } -} From e7ad9453338900d98206cee28d8a294a3e05ecb7 Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Sat, 7 Jun 2025 17:30:47 +0100 Subject: [PATCH 11/61] update WmiCpuInfoParser to return null if Processor Name isn't detected --- .../Detectors/Cpu/Windows/WmicCpuInfoParser.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuInfoParser.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuInfoParser.cs index a139d39711..982023f563 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuInfoParser.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuInfoParser.cs @@ -12,7 +12,7 @@ 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(); int physicalCoreCount = 0; @@ -52,6 +52,9 @@ internal static CpuInfo Parse(string? wmicOutput) ? Frequency.FromMHz(sumMaxFrequency / processorsCount) : null; + if (string.IsNullOrEmpty(processorName) || processorName.ToLower().Equals("unknown processor")) + return null; + return new CpuInfo { ProcessorName = processorName, From 29e2f96788e11ee730941ad805c884e2d9e486b3 Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Sat, 7 Jun 2025 17:31:02 +0100 Subject: [PATCH 12/61] remove WmiLightCpuDetector reference from WindowsCpuDetector --- .../Detectors/Cpu/Windows/WindowsCpuDetector.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WindowsCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WindowsCpuDetector.cs index 8196ad2832..9969a1bca0 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WindowsCpuDetector.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WindowsCpuDetector.cs @@ -1,4 +1,3 @@ namespace BenchmarkDotNet.Detectors.Cpu.Windows; -internal class WindowsCpuDetector() : CpuDetector(new MosCpuDetector(), - new WmiLightCpuDetector(), new WmicCpuDetector()); \ No newline at end of file +internal class WindowsCpuDetector() : CpuDetector(new MosCpuDetector(), new WmicCpuDetector()); \ No newline at end of file From f9a5c34657aebee54658e591a0c12b54e5276ba6 Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Sat, 7 Jun 2025 17:37:48 +0100 Subject: [PATCH 13/61] Update WmicCpuInfoParser.cs --- src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuInfoParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuInfoParser.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuInfoParser.cs index 982023f563..88d3ae9011 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuInfoParser.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuInfoParser.cs @@ -52,7 +52,7 @@ internal static class WmicCpuInfoParser ? Frequency.FromMHz(sumMaxFrequency / processorsCount) : null; - if (string.IsNullOrEmpty(processorName) || processorName.ToLower().Equals("unknown processor")) + if (string.IsNullOrEmpty(processorName)) return null; return new CpuInfo From c181db9d4ece571334033aeefb63d7860972e887 Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Sat, 7 Jun 2025 17:42:57 +0100 Subject: [PATCH 14/61] check if wmicOutput is null or empty instead --- .../Detectors/Cpu/Windows/WmicCpuInfoParser.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuInfoParser.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuInfoParser.cs index 88d3ae9011..9a691fb45a 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuInfoParser.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuInfoParser.cs @@ -14,6 +14,9 @@ internal static class WmicCpuInfoParser /// Output of `wmic cpu get Name, NumberOfCores, NumberOfLogicalProcessors /Format:List` internal static CpuInfo? Parse(string? wmicOutput) { + if (string.IsNullOrEmpty(wmicOutput)) + return null; + var processorModelNames = new HashSet(); int physicalCoreCount = 0; int logicalCoreCount = 0; @@ -52,9 +55,6 @@ internal static class WmicCpuInfoParser ? Frequency.FromMHz(sumMaxFrequency / processorsCount) : null; - if (string.IsNullOrEmpty(processorName)) - return null; - return new CpuInfo { ProcessorName = processorName, From 18f44b8628cf02b3297d63d8295270c9e325bb58 Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Sat, 7 Jun 2025 18:05:56 +0100 Subject: [PATCH 15/61] add PowershellWmiCpuDetector (parser still not complete) --- .../Cpu/Windows/PowershellWmiCpuDetector.cs | 46 +++++++++++++++++++ .../Cpu/Windows/PowershellWmiCpuInfoParser.cs | 13 ++++++ 2 files changed, 59 insertions(+) create mode 100644 src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs create mode 100644 src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs new file mode 100644 index 0000000000..0475338123 --- /dev/null +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs @@ -0,0 +1,46 @@ +using System; +using System.IO; +using System.Linq; +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(); + + public CpuInfo? Detect() + { + if (!IsApplicable()) return null; + + string programFiles = Environment.Is64BitOperatingSystem ? Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) : Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86); + + string powershell7PlusPath = $"{programFiles}{Path.DirectorySeparatorChar}Powershell{Path.DirectorySeparatorChar}"; + + if (Directory.Exists(powershell7PlusPath)) + { + //Use .Last so that we get the newest major PowerShell version + string subDirectory = Directory.EnumerateDirectories(powershell7PlusPath, "^[a-zA-Z]", SearchOption.AllDirectories).Last(); + + powershell7PlusPath = $"{subDirectory}{Path.DirectorySeparatorChar}pwsh.exe"; + } + const string argList = $"{WmicCpuInfoKeyNames.Name}, " + + $"{WmicCpuInfoKeyNames.NumberOfCores}, " + + $"{WmicCpuInfoKeyNames.NumberOfLogicalProcessors}, " + + $"{WmicCpuInfoKeyNames.MaxClockSpeed}"; + + // Optimistically, use Cross-platform new PowerShell when available but fallback to Windows PowerShell if not available. + string powershellPath = File.Exists(windowsPowershellPath) ? powershell7PlusPath : windowsPowershellPath; + string output = ProcessHelper.RunAndReadOutput(powershellPath, "Get-CimInstance Win32_Processor -Property " + argList); + 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..add9188074 --- /dev/null +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs @@ -0,0 +1,13 @@ +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) + { +}} \ No newline at end of file From fa774626941848e3221daff940a15d27f7f09814 Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Sat, 7 Jun 2025 21:33:54 +0100 Subject: [PATCH 16/61] return null if there's no version of powershell installed --- .../Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs index 0475338123..a7b445f9d5 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs @@ -40,6 +40,10 @@ internal class PowershellWmiCpuDetector : ICpuDetector // Optimistically, use Cross-platform new PowerShell when available but fallback to Windows PowerShell if not available. string powershellPath = File.Exists(windowsPowershellPath) ? powershell7PlusPath : windowsPowershellPath; + + if (File.Exists(powershellPath) == false) + return null; + string output = ProcessHelper.RunAndReadOutput(powershellPath, "Get-CimInstance Win32_Processor -Property " + argList); return PowershellWmiCpuInfoParser.Parse(output); } From 0f3dd87a6074bc176818df9a67942ab114bcaf48 Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Sat, 7 Jun 2025 22:17:59 +0100 Subject: [PATCH 17/61] fix Powershell 7+ check Co-authored-by: Tim Cassell --- .../Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs index a7b445f9d5..5c550f502a 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs @@ -39,7 +39,7 @@ internal class PowershellWmiCpuDetector : ICpuDetector $"{WmicCpuInfoKeyNames.MaxClockSpeed}"; // Optimistically, use Cross-platform new PowerShell when available but fallback to Windows PowerShell if not available. - string powershellPath = File.Exists(windowsPowershellPath) ? powershell7PlusPath : windowsPowershellPath; + string powershellPath = File.Exists(powershell7PlusPath) ? powershell7PlusPath : windowsPowershellPath; if (File.Exists(powershellPath) == false) return null; From b6d9fb47d59449139b8134d03177332029eebce7 Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Sat, 7 Jun 2025 22:57:27 +0100 Subject: [PATCH 18/61] rework search statement given that regex isn't supported --- .../Cpu/Windows/PowershellWmiCpuDetector.cs | 34 +++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs index 5c550f502a..8e377a8916 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using BenchmarkDotNet.Helpers; @@ -26,20 +27,47 @@ internal class PowershellWmiCpuDetector : ICpuDetector string powershell7PlusPath = $"{programFiles}{Path.DirectorySeparatorChar}Powershell{Path.DirectorySeparatorChar}"; + bool CheckForPowershell7Plus = true; + if (Directory.Exists(powershell7PlusPath)) { //Use .Last so that we get the newest major PowerShell version - string subDirectory = Directory.EnumerateDirectories(powershell7PlusPath, "^[a-zA-Z]", SearchOption.AllDirectories).Last(); + string[] subDirectories = Directory.EnumerateDirectories(powershell7PlusPath, "*", SearchOption.AllDirectories) + .ToArray(); + + string? subDirectory = null; + + if (subDirectories.Any()) + { + subDirectory = subDirectories.Last(); + } - powershell7PlusPath = $"{subDirectory}{Path.DirectorySeparatorChar}pwsh.exe"; + if (subDirectory is not null) + { + powershell7PlusPath = $"{subDirectory}{Path.DirectorySeparatorChar}pwsh.exe"; + } + else + { + CheckForPowershell7Plus = false; + } } + const string argList = $"{WmicCpuInfoKeyNames.Name}, " + $"{WmicCpuInfoKeyNames.NumberOfCores}, " + $"{WmicCpuInfoKeyNames.NumberOfLogicalProcessors}, " + $"{WmicCpuInfoKeyNames.MaxClockSpeed}"; + string powershellPath; + // Optimistically, use Cross-platform new PowerShell when available but fallback to Windows PowerShell if not available. - string powershellPath = File.Exists(powershell7PlusPath) ? powershell7PlusPath : windowsPowershellPath; + if (CheckForPowershell7Plus) + { + powershellPath = File.Exists(powershell7PlusPath) ? powershell7PlusPath : windowsPowershellPath; + } + else + { + powershellPath = windowsPowershellPath; + } if (File.Exists(powershellPath) == false) return null; From 8d0fbb9d68a0de82cee16105833c55c89cd69001 Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Sat, 7 Jun 2025 23:03:11 +0100 Subject: [PATCH 19/61] add parser code --- .../Cpu/Windows/PowershellWmiCpuInfoParser.cs | 55 ++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs index add9188074..2db62ba6e0 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs @@ -10,4 +10,57 @@ internal static class PowershellWmiCpuInfoParser { internal static CpuInfo? Parse(string? powershellWmiOutput) { -}} \ No newline at end of file + if (string.IsNullOrEmpty(powershellWmiOutput)) + return null; + + HashSet processorModelNames = new HashSet(); + + int physicalCoreCount = 0; + int logicalCoreCount = 0; + int processorsCount = 0; + var sumMaxFrequency = Frequency.Zero; + + + var processors = SectionsHelper.ParseSections(powershellWmiOutput, ':'); + foreach (var processor in processors) + { + if (processor.TryGetValue(WmicCpuInfoKeyNames.NumberOfCores, out string numberOfCoresValue) && + int.TryParse(numberOfCoresValue, out int numberOfCores) && + numberOfCores > 0) + physicalCoreCount += numberOfCores; + + if (processor.TryGetValue(WmicCpuInfoKeyNames.NumberOfLogicalProcessors, out string numberOfLogicalValue) && + int.TryParse(numberOfLogicalValue, out int numberOfLogical) && + numberOfLogical > 0) + logicalCoreCount += numberOfLogical; + + if (processor.TryGetValue(WmicCpuInfoKeyNames.Name, out string name)) + { + processorModelNames.Add(name); + processorsCount++; + } + + if (processor.TryGetValue(WmicCpuInfoKeyNames.MaxClockSpeed, out string frequencyValue) + && int.TryParse(frequencyValue, out int frequency) + && frequency > 0) + { + sumMaxFrequency += frequency; + } + } + + string? processorName = processorModelNames.Count > 0 ? string.Join(", ", processorModelNames) : null; + Frequency? maxFrequency = sumMaxFrequency > 0 && processorsCount > 0 + ? Frequency.FromMHz(sumMaxFrequency / processorsCount) + : 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() + }; + } +} \ No newline at end of file From aca21d0c67f84ab3a9b628375fc059f0f188f734 Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Sat, 7 Jun 2025 23:22:11 +0100 Subject: [PATCH 20/61] add PowershellWmiCpuDetector to WindowsCpuDetector --- .../Detectors/Cpu/Windows/WindowsCpuDetector.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 From 74c1b6c53874660013674b11e747eeb1f38ec05f Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Sat, 7 Jun 2025 23:23:58 +0100 Subject: [PATCH 21/61] rename variable to lower case --- .../Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs index 8e377a8916..675694df36 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs @@ -27,7 +27,7 @@ internal class PowershellWmiCpuDetector : ICpuDetector string powershell7PlusPath = $"{programFiles}{Path.DirectorySeparatorChar}Powershell{Path.DirectorySeparatorChar}"; - bool CheckForPowershell7Plus = true; + bool checkForPowershell7Plus = true; if (Directory.Exists(powershell7PlusPath)) { @@ -48,7 +48,7 @@ internal class PowershellWmiCpuDetector : ICpuDetector } else { - CheckForPowershell7Plus = false; + checkForPowershell7Plus = false; } } @@ -60,7 +60,7 @@ internal class PowershellWmiCpuDetector : ICpuDetector string powershellPath; // Optimistically, use Cross-platform new PowerShell when available but fallback to Windows PowerShell if not available. - if (CheckForPowershell7Plus) + if (checkForPowershell7Plus) { powershellPath = File.Exists(powershell7PlusPath) ? powershell7PlusPath : windowsPowershellPath; } From 52155fae9831f753051b5d6c16533447012b2b5e Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Sat, 7 Jun 2025 23:50:17 +0100 Subject: [PATCH 22/61] improve checking of latest powershell 7+ version --- .../Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs index 675694df36..66cc23b710 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text.RegularExpressions; using BenchmarkDotNet.Helpers; using Perfolizer.Models; @@ -39,7 +40,10 @@ internal class PowershellWmiCpuDetector : ICpuDetector if (subDirectories.Any()) { - subDirectory = subDirectories.Last(); + subDirectory = subDirectories.Where(x => Regex.IsMatch(x, "[0-9]")) + .Select(numStr => new { Value = numStr, Number = int.Parse(numStr) }) + .OrderByDescending(x => x.Number) + .Select(x => x.Value).FirstOrDefault(); } if (subDirectory is not null) From d974b1a60b7058d8956eef1e2c52f1e8381bfdae Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Sat, 7 Jun 2025 23:50:41 +0100 Subject: [PATCH 23/61] use explicit typing --- .../Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs index 2db62ba6e0..86902ca2e8 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs @@ -18,11 +18,11 @@ internal static class PowershellWmiCpuInfoParser int physicalCoreCount = 0; int logicalCoreCount = 0; int processorsCount = 0; - var sumMaxFrequency = Frequency.Zero; + Frequency sumMaxFrequency = Frequency.Zero; - var processors = SectionsHelper.ParseSections(powershellWmiOutput, ':'); - foreach (var processor in processors) + List> processors = SectionsHelper.ParseSections(powershellWmiOutput, ':'); + foreach (Dictionary processor in processors) { if (processor.TryGetValue(WmicCpuInfoKeyNames.NumberOfCores, out string numberOfCoresValue) && int.TryParse(numberOfCoresValue, out int numberOfCores) && From c7aa4e2ca92bd02e43783713a90ad5e9e868356f Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Sat, 7 Jun 2025 23:51:36 +0100 Subject: [PATCH 24/61] fix frequency addition issue --- .../Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs index 86902ca2e8..6777090a69 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs @@ -44,7 +44,10 @@ internal static class PowershellWmiCpuInfoParser && int.TryParse(frequencyValue, out int frequency) && frequency > 0) { - sumMaxFrequency += frequency; + if (frequency > sumMaxFrequency) + { + sumMaxFrequency = frequency; + } } } From fa4ea57341a52dffac134b99482986dd78f1ceda Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Sun, 8 Jun 2025 00:24:15 +0100 Subject: [PATCH 25/61] revert to how WMIC parser handles processor frequency --- .../Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs index 6777090a69..86902ca2e8 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs @@ -44,10 +44,7 @@ internal static class PowershellWmiCpuInfoParser && int.TryParse(frequencyValue, out int frequency) && frequency > 0) { - if (frequency > sumMaxFrequency) - { - sumMaxFrequency = frequency; - } + sumMaxFrequency += frequency; } } From 3a8d884bec6167712171731ae2156c2e49e57708 Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Sun, 8 Jun 2025 15:48:41 +0100 Subject: [PATCH 26/61] add string is null or empty check to WmiCpuDetector --- .../Detectors/Cpu/Windows/WmicCpuDetector.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuDetector.cs index 8c8f277915..fe3434ae7f 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuDetector.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuDetector.cs @@ -26,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 From b4ceb968b86412f998c94c5450b26634d04f39bb Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Sun, 8 Jun 2025 15:55:41 +0100 Subject: [PATCH 27/61] invoke Powershell as "PowerShell" if the file isn't found --- .../Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs index 66cc23b710..80fcc7fd7c 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs @@ -74,7 +74,7 @@ internal class PowershellWmiCpuDetector : ICpuDetector } if (File.Exists(powershellPath) == false) - return null; + powershellPath = "PowerShell"; string output = ProcessHelper.RunAndReadOutput(powershellPath, "Get-CimInstance Win32_Processor -Property " + argList); return PowershellWmiCpuInfoParser.Parse(output); From bc3b8e7f5306e1e5a6021aa5e2deabb83013245f Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Sun, 8 Jun 2025 18:09:56 +0100 Subject: [PATCH 28/61] add nominal Frequency detection and improve max frequency detection --- .../Cpu/Windows/PowershellWmiCpuInfoParser.cs | 31 ++++++++++++++----- .../Cpu/Windows/WmicCpuInfoParser.cs | 29 +++++++++++++---- 2 files changed, 47 insertions(+), 13 deletions(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs index 86902ca2e8..27196fb5a5 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs @@ -18,8 +18,8 @@ internal static class PowershellWmiCpuInfoParser int physicalCoreCount = 0; int logicalCoreCount = 0; int processorsCount = 0; - Frequency sumMaxFrequency = Frequency.Zero; - + Frequency maxFrequency = Frequency.Zero; + Frequency nominalFrequency = Frequency.Zero; List> processors = SectionsHelper.ParseSections(powershellWmiOutput, ':'); foreach (Dictionary processor in processors) @@ -44,23 +44,40 @@ internal static class PowershellWmiCpuInfoParser && int.TryParse(frequencyValue, out int frequency) && frequency > 0) { - sumMaxFrequency += frequency; + if (frequency > maxFrequency) + { + maxFrequency = frequency; + } + + if (frequency < maxFrequency) + { + if (nominalFrequency != Frequency.Zero) + { + if (frequency < nominalFrequency) + nominalFrequency = frequency; + } + else + nominalFrequency = 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/Windows/WmicCpuInfoParser.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuInfoParser.cs index 9a691fb45a..bea7c47c28 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuInfoParser.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuInfoParser.cs @@ -21,7 +21,8 @@ internal static class WmicCpuInfoParser int physicalCoreCount = 0; int logicalCoreCount = 0; int processorsCount = 0; - var sumMaxFrequency = Frequency.Zero; + Frequency maxFrequency = Frequency.Zero; + Frequency nominalFrequency = Frequency.Zero; var processors = SectionsHelper.ParseSections(wmicOutput, '='); foreach (var processor in processors) @@ -46,23 +47,39 @@ internal static class WmicCpuInfoParser && int.TryParse(frequencyValue, out int frequency) && frequency > 0) { - sumMaxFrequency += frequency; + if (frequency > maxFrequency) + { + maxFrequency = frequency; + } + + if (frequency < maxFrequency) + { + if (nominalFrequency != Frequency.Zero) + { + if (frequency < nominalFrequency) + nominalFrequency = frequency; + } + else + nominalFrequency = 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 From 06e82da3c2cf91f9b6a142bebb5c25b70d86b891 Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Sun, 8 Jun 2025 18:38:15 +0100 Subject: [PATCH 29/61] fix issue with detecting latest Powershell --- .../Cpu/Windows/PowershellWmiCpuDetector.cs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs index 80fcc7fd7c..72fe3ef122 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs @@ -36,15 +36,10 @@ internal class PowershellWmiCpuDetector : ICpuDetector string[] subDirectories = Directory.EnumerateDirectories(powershell7PlusPath, "*", SearchOption.AllDirectories) .ToArray(); - string? subDirectory = null; - - if (subDirectories.Any()) - { - subDirectory = subDirectories.Where(x => Regex.IsMatch(x, "[0-9]")) - .Select(numStr => new { Value = numStr, Number = int.Parse(numStr) }) - .OrderByDescending(x => x.Number) - .Select(x => x.Value).FirstOrDefault(); - } + string? subDirectory = subDirectories.Where(x => Regex.IsMatch(x, "[0-9]")) + .Select(x => x) + .OrderByDescending(x => x) + .FirstOrDefault(); if (subDirectory is not null) { From 32f0d57a4960677327b14cef4585fd189c98dbcf Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Mon, 9 Jun 2025 10:34:11 +0100 Subject: [PATCH 30/61] update comment --- .../Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs index 72fe3ef122..0b3e101974 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs @@ -32,10 +32,11 @@ internal class PowershellWmiCpuDetector : ICpuDetector if (Directory.Exists(powershell7PlusPath)) { - //Use .Last so that we get the newest major PowerShell version string[] subDirectories = Directory.EnumerateDirectories(powershell7PlusPath, "*", SearchOption.AllDirectories) .ToArray(); + //Use the highest numbered 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) From ef2385b89621b709b72931131eb54a37d9786906 Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Mon, 9 Jun 2025 10:35:41 +0100 Subject: [PATCH 31/61] Update PowershellWmiCpuDetector.cs --- .../Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs index 0b3e101974..e5a3d15d74 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs @@ -35,7 +35,7 @@ internal class PowershellWmiCpuDetector : ICpuDetector string[] subDirectories = Directory.EnumerateDirectories(powershell7PlusPath, "*", SearchOption.AllDirectories) .ToArray(); - //Use the highest numbered directory for PowerShell so that we get the newest major PowerShell version + //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) From fb87c983a7f8dfc935da47595e642bdf9c3543c5 Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Mon, 9 Jun 2025 10:47:41 +0100 Subject: [PATCH 32/61] refactor Powershell locating code to PowershellLocator --- .../Cpu/Windows/PowershellWmiCpuDetector.cs | 50 ++--------- .../Helpers/PowerShellLocator.cs | 82 +++++++++++++++++++ 2 files changed, 88 insertions(+), 44 deletions(-) create mode 100644 src/BenchmarkDotNet/Helpers/PowerShellLocator.cs diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs index e5a3d15d74..028d16ebe6 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Runtime.Versioning; using System.Text.RegularExpressions; using BenchmarkDotNet.Helpers; using Perfolizer.Models; @@ -20,59 +21,20 @@ internal class PowershellWmiCpuDetector : ICpuDetector public bool IsApplicable() => OsDetector.IsWindows(); + #if NET6_0_OR_GREATER + [SupportedOSPlatform("windows")] + #endif public CpuInfo? Detect() { if (!IsApplicable()) return null; - 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; - } - } - const string argList = $"{WmicCpuInfoKeyNames.Name}, " + $"{WmicCpuInfoKeyNames.NumberOfCores}, " + $"{WmicCpuInfoKeyNames.NumberOfLogicalProcessors}, " + $"{WmicCpuInfoKeyNames.MaxClockSpeed}"; - string powershellPath; - - // 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"; - - string output = ProcessHelper.RunAndReadOutput(powershellPath, "Get-CimInstance Win32_Processor -Property " + argList); + string output = ProcessHelper.RunAndReadOutput(PowerShellLocator.LocateOnWindows() ?? "PowerShell", + "Get-CimInstance Win32_Processor -Property " + argList); return PowershellWmiCpuInfoParser.Parse(output); } } \ No newline at end of file diff --git a/src/BenchmarkDotNet/Helpers/PowerShellLocator.cs b/src/BenchmarkDotNet/Helpers/PowerShellLocator.cs new file mode 100644 index 0000000000..8f8a910980 --- /dev/null +++ b/src/BenchmarkDotNet/Helpers/PowerShellLocator.cs @@ -0,0 +1,82 @@ +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 From 59b36be50382b32c91e90c83c4ba8ea9852b186a Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Mon, 9 Jun 2025 11:03:37 +0100 Subject: [PATCH 33/61] simplify frequency checks --- .../Cpu/Windows/PowershellWmiCpuInfoParser.cs | 34 ++++++------------- .../Cpu/Windows/WmicCpuInfoParser.cs | 28 +++++---------- 2 files changed, 19 insertions(+), 43 deletions(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs index 27196fb5a5..d92e74a0d9 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using BenchmarkDotNet.Extensions; using BenchmarkDotNet.Helpers; using Perfolizer.Horology; @@ -17,9 +18,9 @@ internal static class PowershellWmiCpuInfoParser int physicalCoreCount = 0; int logicalCoreCount = 0; - int processorsCount = 0; - Frequency maxFrequency = Frequency.Zero; - Frequency nominalFrequency = Frequency.Zero; + int processorCount = 0; + double maxFrequency = 0.0; + double nominalFrequency = 0.0; List> processors = SectionsHelper.ParseSections(powershellWmiOutput, ':'); foreach (Dictionary processor in processors) @@ -37,43 +38,30 @@ internal static class PowershellWmiCpuInfoParser if (processor.TryGetValue(WmicCpuInfoKeyNames.Name, out string name)) { processorModelNames.Add(name); - processorsCount++; + processorCount++; } if (processor.TryGetValue(WmicCpuInfoKeyNames.MaxClockSpeed, out string frequencyValue) && int.TryParse(frequencyValue, out int frequency) && frequency > 0) { - if (frequency > maxFrequency) - { - maxFrequency = frequency; - } - - if (frequency < maxFrequency) - { - if (nominalFrequency != Frequency.Zero) - { - if (frequency < nominalFrequency) - nominalFrequency = frequency; - } - else - nominalFrequency = frequency; - } + nominalFrequency = nominalFrequency == 0 ? frequency : Math.Min(nominalFrequency, frequency); + maxFrequency = maxFrequency == 0 ? frequency : Math.Max(maxFrequency, nominalFrequency); } } string? processorName = processorModelNames.Count > 0 ? string.Join(", ", processorModelNames) : null; - Frequency? maxFrequencyActual = maxFrequency > 0 && processorsCount > 0 + Frequency? maxFrequencyActual = maxFrequency > 0 && processorCount > 0 ? Frequency.FromMHz(maxFrequency) : null; - Frequency? nominalFrequencyActual = nominalFrequency > 0 && processorsCount > 0 ? + Frequency? nominalFrequencyActual = nominalFrequency > 0 && processorCount > 0 ? Frequency.FromMHz(nominalFrequency) : null; return new CpuInfo { ProcessorName = processorName, - PhysicalProcessorCount = processorsCount > 0 ? processorsCount : null, + PhysicalProcessorCount = processorCount > 0 ? processorCount : null, PhysicalCoreCount = physicalCoreCount > 0 ? physicalCoreCount : null, LogicalCoreCount = logicalCoreCount > 0 ? logicalCoreCount : null, NominalFrequencyHz = nominalFrequencyActual?.Hertz.RoundToLong(), diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuInfoParser.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuInfoParser.cs index bea7c47c28..dde304df8f 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; @@ -17,14 +18,14 @@ internal static class WmicCpuInfoParser if (string.IsNullOrEmpty(wmicOutput)) return null; - var processorModelNames = new HashSet(); + HashSet processorModelNames = new HashSet(); int physicalCoreCount = 0; int logicalCoreCount = 0; int processorsCount = 0; - Frequency maxFrequency = Frequency.Zero; - Frequency nominalFrequency = 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) && @@ -47,21 +48,8 @@ internal static class WmicCpuInfoParser && int.TryParse(frequencyValue, out int frequency) && frequency > 0) { - if (frequency > maxFrequency) - { - maxFrequency = frequency; - } - - if (frequency < maxFrequency) - { - if (nominalFrequency != Frequency.Zero) - { - if (frequency < nominalFrequency) - nominalFrequency = frequency; - } - else - nominalFrequency = frequency; - } + nominalFrequency = nominalFrequency == 0 ? frequency : Math.Min(nominalFrequency, frequency); + maxFrequency = Math.Max(maxFrequency, frequency); } } From be3fbfe86039d905cab940f4cd1c55ef03deb8df Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Mon, 9 Jun 2025 11:15:14 +0100 Subject: [PATCH 34/61] Create PowershellWmiParserTests.cs --- .../Detectors/Cpu/PowershellWmiParserTests.cs | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 tests/BenchmarkDotNet.Tests/Detectors/Cpu/PowershellWmiParserTests.cs diff --git a/tests/BenchmarkDotNet.Tests/Detectors/Cpu/PowershellWmiParserTests.cs b/tests/BenchmarkDotNet.Tests/Detectors/Cpu/PowershellWmiParserTests.cs new file mode 100644 index 0000000000..edb8ec8c83 --- /dev/null +++ b/tests/BenchmarkDotNet.Tests/Detectors/Cpu/PowershellWmiParserTests.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 PowershellWmiParserTests(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 @ 2.40GHz +NumberOfCores:8 +NumberOfLogicalProcessors:16 + + +MaxClockSpeed:2400 +Name:Intel(R) Xeon(R) CPU E5-2630 v3 @ 2.40GHz +NumberOfCores:8 +NumberOfLogicalProcessors:16 + +"; + CpuInfo? actual = PowershellWmiCpuInfoParser.Parse(cpuInfo); + + CpuInfo expected = new CpuInfo + { + ProcessorName = "Intel(R) Xeon(R) CPU E5-2630 v3 @ 2.40GHz", + 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 @ 3.10GHz\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 @ 3.10GHz\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 @ 3.10GHz", + 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 CPU @ 2.50GHz +NumberOfCores:4 +NumberOfLogicalProcessors:8 + +"; + + CpuInfo? actual = PowershellWmiCpuInfoParser.Parse(cpuInfo); + CpuInfo expected = new CpuInfo + { + ProcessorName = "Intel(R) Core(TM) i7-4710MQ CPU @ 2.50GHz", + 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 677bd1b9cdd1be92ee351f291b1d2193d7a32753 Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Mon, 9 Jun 2025 11:15:33 +0100 Subject: [PATCH 35/61] fix null being returned when object is expected. --- .../Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs | 2 +- src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuInfoParser.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs index d92e74a0d9..0c8c81036a 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs @@ -12,7 +12,7 @@ internal static class PowershellWmiCpuInfoParser internal static CpuInfo? Parse(string? powershellWmiOutput) { if (string.IsNullOrEmpty(powershellWmiOutput)) - return null; + return CpuInfo.Unknown; HashSet processorModelNames = new HashSet(); diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuInfoParser.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuInfoParser.cs index dde304df8f..7279b79cf8 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuInfoParser.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuInfoParser.cs @@ -16,7 +16,7 @@ internal static class WmicCpuInfoParser internal static CpuInfo? Parse(string? wmicOutput) { if (string.IsNullOrEmpty(wmicOutput)) - return null; + return CpuInfo.Unknown; HashSet processorModelNames = new HashSet(); int physicalCoreCount = 0; From c856ab05923c4bc1c30baabda323bb8c9b09126f Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Mon, 9 Jun 2025 11:18:17 +0100 Subject: [PATCH 36/61] rename test --- ...hellWmiParserTests.cs => PowershellWmiCpuInfoParserTests.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename tests/BenchmarkDotNet.Tests/Detectors/Cpu/{PowershellWmiParserTests.cs => PowershellWmiCpuInfoParserTests.cs} (97%) diff --git a/tests/BenchmarkDotNet.Tests/Detectors/Cpu/PowershellWmiParserTests.cs b/tests/BenchmarkDotNet.Tests/Detectors/Cpu/PowershellWmiCpuInfoParserTests.cs similarity index 97% rename from tests/BenchmarkDotNet.Tests/Detectors/Cpu/PowershellWmiParserTests.cs rename to tests/BenchmarkDotNet.Tests/Detectors/Cpu/PowershellWmiCpuInfoParserTests.cs index edb8ec8c83..435aedf76f 100644 --- a/tests/BenchmarkDotNet.Tests/Detectors/Cpu/PowershellWmiParserTests.cs +++ b/tests/BenchmarkDotNet.Tests/Detectors/Cpu/PowershellWmiCpuInfoParserTests.cs @@ -5,7 +5,7 @@ namespace BenchmarkDotNet.Tests.Detectors.Cpu; -public class PowershellWmiParserTests(ITestOutputHelper output) +public class PowershellWmiCpuInfoParserTests(ITestOutputHelper output) { private ITestOutputHelper Output { get; } = output; From c54ee5189f9aa81da727034d0fcebff6d2625f25 Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Mon, 9 Jun 2025 19:54:38 +0100 Subject: [PATCH 37/61] simplify max frequency check Co-authored-by: Tim Cassell --- .../Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs index 0c8c81036a..7a0148cb16 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs @@ -46,7 +46,7 @@ internal static class PowershellWmiCpuInfoParser && frequency > 0) { nominalFrequency = nominalFrequency == 0 ? frequency : Math.Min(nominalFrequency, frequency); - maxFrequency = maxFrequency == 0 ? frequency : Math.Max(maxFrequency, nominalFrequency); + maxFrequency = Math.Max(maxFrequency, frequency); } } From 1d86ff251b4e6b4d1873cd595b5ee5cee4b78114 Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Mon, 9 Jun 2025 19:55:12 +0100 Subject: [PATCH 38/61] use """ for string in parser test --- .../Cpu/PowershellWmiCpuInfoParserTests.cs | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/tests/BenchmarkDotNet.Tests/Detectors/Cpu/PowershellWmiCpuInfoParserTests.cs b/tests/BenchmarkDotNet.Tests/Detectors/Cpu/PowershellWmiCpuInfoParserTests.cs index 435aedf76f..17e09b1d7f 100644 --- a/tests/BenchmarkDotNet.Tests/Detectors/Cpu/PowershellWmiCpuInfoParserTests.cs +++ b/tests/BenchmarkDotNet.Tests/Detectors/Cpu/PowershellWmiCpuInfoParserTests.cs @@ -18,6 +18,7 @@ public void EmptyTest() Output.AssertEqual(expected, actual); } + [Fact] public void MalformedTest() { @@ -30,20 +31,20 @@ public void MalformedTest() [Fact] public void RealTwoProcessorEightCoresTest() { - const string cpuInfo = @" + const string cpuInfo = """ -MaxClockSpeed:2400 -Name:Intel(R) Xeon(R) CPU E5-2630 v3 @ 2.40GHz -NumberOfCores:8 -NumberOfLogicalProcessors:16 + MaxClockSpeed:2400 + Name:Intel(R) Xeon(R) CPU E5-2630 v3 @ 2.40GHz + NumberOfCores:8 + NumberOfLogicalProcessors:16 -MaxClockSpeed:2400 -Name:Intel(R) Xeon(R) CPU E5-2630 v3 @ 2.40GHz -NumberOfCores:8 -NumberOfLogicalProcessors:16 + MaxClockSpeed:2400 + Name:Intel(R) Xeon(R) CPU E5-2630 v3 @ 2.40GHz + NumberOfCores:8 + NumberOfLogicalProcessors:16 -"; + """; CpuInfo? actual = PowershellWmiCpuInfoParser.Parse(cpuInfo); CpuInfo expected = new CpuInfo From 589566fbe191ca2823a1fdfb02b50060de8af36e Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Mon, 9 Jun 2025 19:56:21 +0100 Subject: [PATCH 39/61] Update PowershellWmiCpuInfoParserTests.cs --- .../Cpu/PowershellWmiCpuInfoParserTests.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/tests/BenchmarkDotNet.Tests/Detectors/Cpu/PowershellWmiCpuInfoParserTests.cs b/tests/BenchmarkDotNet.Tests/Detectors/Cpu/PowershellWmiCpuInfoParserTests.cs index 17e09b1d7f..d8c3539275 100644 --- a/tests/BenchmarkDotNet.Tests/Detectors/Cpu/PowershellWmiCpuInfoParserTests.cs +++ b/tests/BenchmarkDotNet.Tests/Detectors/Cpu/PowershellWmiCpuInfoParserTests.cs @@ -98,14 +98,13 @@ public void RealTwoProcessorEightCoresWithWmicBugTest() [Fact] public void RealOneProcessorFourCoresTest() { - const string cpuInfo = @" - -MaxClockSpeed:2500 -Name:Intel(R) Core(TM) i7-4710MQ CPU @ 2.50GHz -NumberOfCores:4 -NumberOfLogicalProcessors:8 + const string cpuInfo = """ -"; + MaxClockSpeed:2500 + Name:Intel(R) Core(TM) i7-4710MQ CPU @ 2.50GHz + NumberOfCores:4 + NumberOfLogicalProcessors:8 + """; CpuInfo? actual = PowershellWmiCpuInfoParser.Parse(cpuInfo); CpuInfo expected = new CpuInfo From 3e3d67bd099b9fd1c392838ed5096b3ecf65ab1f Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Mon, 9 Jun 2025 20:07:08 +0100 Subject: [PATCH 40/61] reduce indentation with """ --- .../Cpu/PowershellWmiCpuInfoParserTests.cs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/BenchmarkDotNet.Tests/Detectors/Cpu/PowershellWmiCpuInfoParserTests.cs b/tests/BenchmarkDotNet.Tests/Detectors/Cpu/PowershellWmiCpuInfoParserTests.cs index d8c3539275..410019ba62 100644 --- a/tests/BenchmarkDotNet.Tests/Detectors/Cpu/PowershellWmiCpuInfoParserTests.cs +++ b/tests/BenchmarkDotNet.Tests/Detectors/Cpu/PowershellWmiCpuInfoParserTests.cs @@ -31,20 +31,20 @@ public void MalformedTest() [Fact] public void RealTwoProcessorEightCoresTest() { - const string cpuInfo = """ - - MaxClockSpeed:2400 - Name:Intel(R) Xeon(R) CPU E5-2630 v3 @ 2.40GHz - NumberOfCores:8 - NumberOfLogicalProcessors:16 - - - MaxClockSpeed:2400 - Name:Intel(R) Xeon(R) CPU E5-2630 v3 @ 2.40GHz - NumberOfCores:8 - NumberOfLogicalProcessors:16 - - """; + 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 From 106cac1d055a072fd191ad0626e8858c2ba2b901 Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Mon, 9 Jun 2025 20:07:22 +0100 Subject: [PATCH 41/61] remove unnecessary test info --- .../Detectors/Cpu/PowershellWmiCpuInfoParserTests.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/BenchmarkDotNet.Tests/Detectors/Cpu/PowershellWmiCpuInfoParserTests.cs b/tests/BenchmarkDotNet.Tests/Detectors/Cpu/PowershellWmiCpuInfoParserTests.cs index 410019ba62..a781d70021 100644 --- a/tests/BenchmarkDotNet.Tests/Detectors/Cpu/PowershellWmiCpuInfoParserTests.cs +++ b/tests/BenchmarkDotNet.Tests/Detectors/Cpu/PowershellWmiCpuInfoParserTests.cs @@ -49,7 +49,7 @@ public void RealTwoProcessorEightCoresTest() CpuInfo expected = new CpuInfo { - ProcessorName = "Intel(R) Xeon(R) CPU E5-2630 v3 @ 2.40GHz", + ProcessorName = "Intel(R) Xeon(R) CPU E5-2630 v3", PhysicalProcessorCount = 2, PhysicalCoreCount = 16, LogicalCoreCount = 32, @@ -67,13 +67,13 @@ public void RealTwoProcessorEightCoresWithWmicBugTest() "\r\r\n" + "\r\r\n" + "MaxClockSpeed:3111\r\r\n" + - "Name:Intel(R) Xeon(R) CPU E5-2687W 0 @ 3.10GHz\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 @ 3.10GHz\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" + @@ -84,7 +84,7 @@ public void RealTwoProcessorEightCoresWithWmicBugTest() CpuInfo expected = new CpuInfo { - ProcessorName = "Intel(R) Xeon(R) CPU E5-2687W 0 @ 3.10GHz", + ProcessorName = "Intel(R) Xeon(R) CPU E5-2687W 0", PhysicalProcessorCount = 2, PhysicalCoreCount = 16, LogicalCoreCount = 32, @@ -101,7 +101,7 @@ public void RealOneProcessorFourCoresTest() const string cpuInfo = """ MaxClockSpeed:2500 - Name:Intel(R) Core(TM) i7-4710MQ CPU @ 2.50GHz + Name:Intel(R) Core(TM) i7-4710MQ NumberOfCores:4 NumberOfLogicalProcessors:8 """; @@ -109,7 +109,7 @@ public void RealOneProcessorFourCoresTest() CpuInfo? actual = PowershellWmiCpuInfoParser.Parse(cpuInfo); CpuInfo expected = new CpuInfo { - ProcessorName = "Intel(R) Core(TM) i7-4710MQ CPU @ 2.50GHz", + ProcessorName = "Intel(R) Core(TM) i7-4710MQ", PhysicalProcessorCount = 1, PhysicalCoreCount = 4, LogicalCoreCount = 8, From 24135298baada62ce418363231464088a4d139dc Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Mon, 9 Jun 2025 20:08:59 +0100 Subject: [PATCH 42/61] move string null check to caller --- .../Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs | 4 ++++ .../Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs | 3 --- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs index 028d16ebe6..788757bcdf 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs @@ -35,6 +35,10 @@ internal class PowershellWmiCpuDetector : ICpuDetector string output = ProcessHelper.RunAndReadOutput(PowerShellLocator.LocateOnWindows() ?? "PowerShell", "Get-CimInstance Win32_Processor -Property " + argList); + + if (string.IsNullOrEmpty(output)) + return CpuInfo.Unknown; + 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 index 7a0148cb16..7f7b72cafe 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs @@ -11,9 +11,6 @@ internal static class PowershellWmiCpuInfoParser { internal static CpuInfo? Parse(string? powershellWmiOutput) { - if (string.IsNullOrEmpty(powershellWmiOutput)) - return CpuInfo.Unknown; - HashSet processorModelNames = new HashSet(); int physicalCoreCount = 0; From 476cad558ee60f54ed44b92e8b48db2d7f121c9c Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Mon, 9 Jun 2025 20:13:45 +0100 Subject: [PATCH 43/61] add nominal frequency support for MosCpuDetector --- .../Detectors/Cpu/Windows/MosCpuDetector.cs | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/MosCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/MosCpuDetector.cs index 8ab16535f6..39db4664fa 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,20 @@ 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 = (double)moProcessor[WmicCpuInfoKeyNames.MaxClockSpeed]; + + nominalFrequency = nominalFrequency == 0 ? maxFrequency : Math.Min(nominalFrequency, maxFrequency); + 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 +65,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 From caf07ad817c7cb911c01d44f1a75b3bbef5f8c93 Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Mon, 9 Jun 2025 20:15:55 +0100 Subject: [PATCH 44/61] Update src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs Co-authored-by: Tim Cassell --- .../Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs index 788757bcdf..7f61c1b8fc 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs @@ -37,7 +37,7 @@ internal class PowershellWmiCpuDetector : ICpuDetector "Get-CimInstance Win32_Processor -Property " + argList); if (string.IsNullOrEmpty(output)) - return CpuInfo.Unknown; + return null; return PowershellWmiCpuInfoParser.Parse(output); } From 2e0867aeb8f970ccda82a126df53d15eac7456a7 Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Mon, 9 Jun 2025 20:16:47 +0100 Subject: [PATCH 45/61] remove nullability of parser Co-authored-by: Tim Cassell --- .../Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs index 7f7b72cafe..3e0a0f9ac7 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs @@ -9,7 +9,7 @@ namespace BenchmarkDotNet.Detectors.Cpu.Windows; internal static class PowershellWmiCpuInfoParser { - internal static CpuInfo? Parse(string? powershellWmiOutput) + internal static CpuInfo Parse(string powershellWmiOutput) { HashSet processorModelNames = new HashSet(); From 30768de1af940dd1c3e48bbacffb15e1880ab9fd Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Mon, 9 Jun 2025 20:22:19 +0100 Subject: [PATCH 46/61] check if tempMaxFrequency > 0 before assignment Co-authored-by: Tim Cassell --- src/BenchmarkDotNet/Detectors/Cpu/Windows/MosCpuDetector.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/MosCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/MosCpuDetector.cs index 39db4664fa..eeab5fa0fb 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/MosCpuDetector.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/MosCpuDetector.cs @@ -45,7 +45,10 @@ public bool IsApplicable() => OsDetector.IsWindows() && logicalCoreCount += (int)(uint)moProcessor[WmicCpuInfoKeyNames.NumberOfLogicalProcessors]; double tempMaxFrequency = (double)moProcessor[WmicCpuInfoKeyNames.MaxClockSpeed]; - nominalFrequency = nominalFrequency == 0 ? maxFrequency : Math.Min(nominalFrequency, maxFrequency); + if (tempMaxFrequency > 0) + { + nominalFrequency = nominalFrequency == 0 ? tempMaxFrequency : Math.Min(nominalFrequency, tempMaxFrequency); + } maxFrequency = Math.Max(maxFrequency, tempMaxFrequency); } } From afb3220ffb03a50f16b7787375abde242534d4a1 Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Mon, 9 Jun 2025 20:35:08 +0100 Subject: [PATCH 47/61] remove nullability of WmicCpuInfoParser --- .../Detectors/Cpu/Windows/WmicCpuInfoParser.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuInfoParser.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuInfoParser.cs index 7279b79cf8..9d774326a4 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuInfoParser.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuInfoParser.cs @@ -13,11 +13,8 @@ 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) { - if (string.IsNullOrEmpty(wmicOutput)) - return CpuInfo.Unknown; - HashSet processorModelNames = new HashSet(); int physicalCoreCount = 0; int logicalCoreCount = 0; From b60ad9f093e4d5567adf4aeb4901d4a722001d0a Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Mon, 9 Jun 2025 20:46:11 +0100 Subject: [PATCH 48/61] use file scoped namespace --- .../Helpers/PowerShellLocator.cs | 103 +++++++++--------- 1 file changed, 51 insertions(+), 52 deletions(-) diff --git a/src/BenchmarkDotNet/Helpers/PowerShellLocator.cs b/src/BenchmarkDotNet/Helpers/PowerShellLocator.cs index 8f8a910980..28b8e910f7 100644 --- a/src/BenchmarkDotNet/Helpers/PowerShellLocator.cs +++ b/src/BenchmarkDotNet/Helpers/PowerShellLocator.cs @@ -5,78 +5,77 @@ using System.Text.RegularExpressions; using BenchmarkDotNet.Detectors; -namespace BenchmarkDotNet.Helpers +namespace BenchmarkDotNet.Helpers; + +/// +/// Locates PowerShell on a system, currently only supports on Windows. +/// +internal class PowerShellLocator { - /// - /// 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"; + private static readonly string WindowsPowershellPath = + $"{Environment.SystemDirectory}{Path.DirectorySeparatorChar}WindowsPowerShell{Path.DirectorySeparatorChar}" + + $"v1.0{Path.DirectorySeparatorChar}powershell.exe"; - #if NET6_0_OR_GREATER +#if NET6_0_OR_GREATER [SupportedOSPlatform("windows")] - #endif - internal static string? LocateOnWindows() - { - if (OsDetector.IsWindows() == false) - return null; +#endif + internal static string? LocateOnWindows() + { + if (OsDetector.IsWindows() == false) + return null; - string powershellPath; + string powershellPath; - try - { - string programFiles = Environment.Is64BitOperatingSystem - ? Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) - : Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86); + try + { + string programFiles = Environment.Is64BitOperatingSystem + ? Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) + : Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86); - string powershell7PlusPath = $"{programFiles}{Path.DirectorySeparatorChar}Powershell{Path.DirectorySeparatorChar}"; + string powershell7PlusPath = $"{programFiles}{Path.DirectorySeparatorChar}Powershell{Path.DirectorySeparatorChar}"; - bool checkForPowershell7Plus = true; + bool checkForPowershell7Plus = true; - if (Directory.Exists(powershell7PlusPath)) - { - string[] subDirectories = Directory.EnumerateDirectories(powershell7PlusPath, "*", SearchOption.AllDirectories) - .ToArray(); + 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(); + //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) + if (subDirectory is not null) { - powershellPath = File.Exists(powershell7PlusPath) ? powershell7PlusPath : WindowsPowershellPath; + powershell7PlusPath = $"{subDirectory}{Path.DirectorySeparatorChar}pwsh.exe"; } else { - powershellPath = WindowsPowershellPath; + checkForPowershell7Plus = false; } + } - if (File.Exists(powershellPath) == false) - powershellPath = "PowerShell"; + // Optimistically, use Cross-platform new PowerShell when available but fallback to Windows PowerShell if not available. + if (checkForPowershell7Plus) + { + powershellPath = File.Exists(powershell7PlusPath) ? powershell7PlusPath : WindowsPowershellPath; } - catch + else { - powershellPath = "PowerShell"; + powershellPath = WindowsPowershellPath; } - return powershellPath; + if (File.Exists(powershellPath) == false) + powershellPath = "PowerShell"; + } + catch + { + powershellPath = "PowerShell"; } + + return powershellPath; } } \ No newline at end of file From f300e4e3895e2851ddccd5581d96ca0ba78e1d8f Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Mon, 9 Jun 2025 20:46:33 +0100 Subject: [PATCH 49/61] use double instead of int --- .../Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs | 2 +- src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuInfoParser.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs index 3e0a0f9ac7..d15b3eaa5f 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs @@ -39,7 +39,7 @@ internal static CpuInfo Parse(string powershellWmiOutput) } if (processor.TryGetValue(WmicCpuInfoKeyNames.MaxClockSpeed, out string frequencyValue) - && int.TryParse(frequencyValue, out int frequency) + && double.TryParse(frequencyValue, out double frequency) && frequency > 0) { nominalFrequency = nominalFrequency == 0 ? frequency : Math.Min(nominalFrequency, frequency); diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuInfoParser.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuInfoParser.cs index 9d774326a4..295d76262e 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuInfoParser.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuInfoParser.cs @@ -42,7 +42,7 @@ 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) { nominalFrequency = nominalFrequency == 0 ? frequency : Math.Min(nominalFrequency, frequency); From 85d6f1b4cd0df94777afa68c991d1f5e399c7176 Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Mon, 9 Jun 2025 20:47:07 +0100 Subject: [PATCH 50/61] add null check to LinuxCpuDetector --- .../Detectors/Cpu/Linux/LinuxCpuDetector.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuDetector.cs index 12c3b188d4..2ddad3dd41 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? lscpu = ProcessHelper.RunAndReadOutput("/bin/bash", "-c \"lscpu\"", environmentVariables: languageInvariantEnvironment); + + if (cpuInfo is null || lscpu is null) + return null; + return LinuxCpuInfoParser.Parse(cpuInfo, lscpu); } } \ No newline at end of file From f6d6eafd9417b92aff453db22b9e546934e0dde2 Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Mon, 9 Jun 2025 20:47:37 +0100 Subject: [PATCH 51/61] change nullability of LinuxCpuParser --- src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuInfoParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuInfoParser.cs b/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuInfoParser.cs index ed93999653..937dc98df6 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuInfoParser.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuInfoParser.cs @@ -28,7 +28,7 @@ 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(); From a5f715325ef788f57242b7c9404b6a5627007b17 Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Mon, 9 Jun 2025 21:01:17 +0100 Subject: [PATCH 52/61] update LinuxCpuInfoParser nominal and max frequency detection --- .../Detectors/Cpu/Linux/LinuxCpuInfoParser.cs | 47 ++++++++++++++----- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuInfoParser.cs b/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuInfoParser.cs index 937dc98df6..30a33cc0e8 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,7 @@ 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 NominalFrequency = "cpu MHz"; } private static class Lscpu @@ -33,7 +35,8 @@ 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,9 +54,17 @@ internal static CpuInfo Parse(string cpuInfo, string lscpu) } if (logicalCore.TryGetValue(ProcCpu.MaxFrequency, out string maxCpuFreqValue) && - Frequency.TryParseMHz(maxCpuFreqValue, out var maxCpuFreq)) + double.TryParse(maxCpuFreqValue, out double maxCpuFreq) + && maxCpuFreq > 0) { - maxFrequency = maxCpuFreq; + maxFrequency = Math.Max(maxFrequency, maxCpuFreq); + } + + if (logicalCore.TryGetValue(ProcCpu.NominalFrequency, out string nominalFreqValue) && + double.TryParse(nominalFreqValue, out double nominalCpuFreq)) + { + if (nominalCpuFreq > 0) + nominalFrequency = Math.Min(nominalFrequency, nominalCpuFreq); } } @@ -71,7 +82,7 @@ internal static CpuInfo Parse(string cpuInfo, string lscpu) if (name.EqualsWithIgnoreCase(Lscpu.MaxFrequency) && Frequency.TryParseMHz(value.Replace(',', '.'), out var maxFrequencyParsed)) // Example: `CPU max MHz: 3200,0000` - maxFrequency = maxFrequencyParsed; + maxFrequency = Math.Max(maxFrequency, maxFrequencyParsed); if (name.EqualsWithIgnoreCase(Lscpu.ModelName)) processorModelNames.Add(value); @@ -81,22 +92,34 @@ internal static CpuInfo Parse(string cpuInfo, string lscpu) coresPerSocket = coreCount; } } - - 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() }; } From 65c45b9f8cb5ec84c03e984c187c5cffb560ddd3 Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Mon, 9 Jun 2025 21:01:33 +0100 Subject: [PATCH 53/61] Update LinuxCpuInfoParser.cs --- src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuInfoParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuInfoParser.cs b/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuInfoParser.cs index 30a33cc0e8..7f3def59f2 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuInfoParser.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuInfoParser.cs @@ -92,7 +92,7 @@ internal static CpuInfo Parse(string cpuInfo, string lscpu) coresPerSocket = coreCount; } } - + string processorName = processorModelNames.Count > 0 ? string.Join(", ", processorModelNames) : null; int? physicalProcessorCount = processorsToPhysicalCoreCount.Count > 0 ? processorsToPhysicalCoreCount.Count : null; int? physicalCoreCount = processorsToPhysicalCoreCount.Count > 0 ? processorsToPhysicalCoreCount.Values.Sum() : coresPerSocket; From d2741e1f77f43034e91f6d30c01b798c4e83f17a Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Mon, 9 Jun 2025 21:03:50 +0100 Subject: [PATCH 54/61] fix nullability check --- src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuDetector.cs | 6 +++--- .../Detectors/Cpu/Linux/LinuxCpuInfoParser.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuDetector.cs index 2ddad3dd41..44973b27e9 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuDetector.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuDetector.cs @@ -24,10 +24,10 @@ 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 is null || lscpu is null) + if (cpuInfo == string.Empty && lscpu == string.Empty) return null; return LinuxCpuInfoParser.Parse(cpuInfo, lscpu); diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuInfoParser.cs b/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuInfoParser.cs index 7f3def59f2..bb248b3842 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuInfoParser.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuInfoParser.cs @@ -69,7 +69,7 @@ internal static CpuInfo Parse(string cpuInfo, string lscpu) } int? coresPerSocket = null; - if (lscpu != null) + if (string.IsNullOrEmpty(lscpu) == false) { var lscpuParts = lscpu.Split('\n') .Where(line => line.Contains(':')) From d1172a39fe331c3d730a98c23bb58a7150245028 Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Mon, 9 Jun 2025 21:42:06 +0100 Subject: [PATCH 55/61] simplify nominal frequency comparison Co-authored-by: Tim Cassell --- .../Detectors/Cpu/Linux/LinuxCpuInfoParser.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuInfoParser.cs b/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuInfoParser.cs index bb248b3842..88124ca3cf 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuInfoParser.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuInfoParser.cs @@ -61,10 +61,10 @@ internal static CpuInfo Parse(string cpuInfo, string lscpu) } if (logicalCore.TryGetValue(ProcCpu.NominalFrequency, out string nominalFreqValue) && - double.TryParse(nominalFreqValue, out double nominalCpuFreq)) + double.TryParse(nominalFreqValue, out double nominalCpuFreq) + && nominalCpuFreq > 0) { - if (nominalCpuFreq > 0) - nominalFrequency = Math.Min(nominalFrequency, nominalCpuFreq); + nominalFrequency = nominalFrequency == 0 ? nominalCpuFreq : Math.Min(nominalFrequency, nominalCpuFreq); } } From 8307016f7b6dcb0cc05804cd3ad43f078a1a7d47 Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Mon, 9 Jun 2025 21:49:29 +0100 Subject: [PATCH 56/61] add null check to macOS --- src/BenchmarkDotNet/Detectors/Cpu/macOS/MacOsCpuDetector.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 From d5eb7e49c15c125cff6303ab387e9a38620a895e Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Mon, 9 Jun 2025 22:05:43 +0100 Subject: [PATCH 57/61] don't accept null string from detector --- src/BenchmarkDotNet/Detectors/Cpu/macOS/SysctlCpuInfoParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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); From b85d12d9ea13c6037461137ed63af364a6896928 Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Wed, 11 Jun 2025 14:00:39 +0100 Subject: [PATCH 58/61] fix LinuxCpuInfo parser test issues and improve robustness of Linux Cpu checks --- .../Detectors/Cpu/Linux/LinuxCpuInfoParser.cs | 44 ++++++++++++++----- .../Detectors/Cpu/LinuxCpuInfoParserTests.cs | 12 ++--- 2 files changed, 39 insertions(+), 17 deletions(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuInfoParser.cs b/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuInfoParser.cs index 88124ca3cf..7d79c3262f 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuInfoParser.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuInfoParser.cs @@ -18,6 +18,7 @@ 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"; } @@ -54,17 +55,38 @@ internal static CpuInfo Parse(string cpuInfo, string lscpu) } if (logicalCore.TryGetValue(ProcCpu.MaxFrequency, out string maxCpuFreqValue) && - double.TryParse(maxCpuFreqValue, out double maxCpuFreq) + Frequency.TryParseMHz(maxCpuFreqValue.Replace(',', '.'), out Frequency maxCpuFreq) && maxCpuFreq > 0) { - maxFrequency = Math.Max(maxFrequency, maxCpuFreq); + maxFrequency = Math.Max(maxFrequency, maxCpuFreq.ToMHz()); } - if (logicalCore.TryGetValue(ProcCpu.NominalFrequency, out string nominalFreqValue) && - double.TryParse(nominalFreqValue, out double nominalCpuFreq) + 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) { - nominalFrequency = nominalFrequency == 0 ? nominalCpuFreq : Math.Min(nominalFrequency, nominalCpuFreq); + 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; } } @@ -81,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 = Math.Max(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); @@ -98,10 +120,10 @@ internal static CpuInfo Parse(string cpuInfo, string lscpu) int? physicalCoreCount = processorsToPhysicalCoreCount.Count > 0 ? processorsToPhysicalCoreCount.Values.Sum() : coresPerSocket; Frequency? maxFrequencyActual = maxFrequency > 0 && physicalProcessorCount > 0 - ? Frequency.FromMHz(maxFrequency) - : null; + ? Frequency.FromMHz(maxFrequency) : null; - Frequency? nominalFrequencyActual = nominalFrequency > 0 && physicalProcessorCount > 0 ? Frequency.FromMHz(nominalFrequency) : null; + Frequency? nominalFrequencyActual = nominalFrequency > 0 && physicalProcessorCount > 0 + ? Frequency.FromMHz(nominalFrequency) : null; if (nominalFrequencyActual is null) { @@ -130,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/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); From 9609f83f60252c79e7c652c2ac78122367c422e5 Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Wed, 11 Jun 2025 14:08:06 +0100 Subject: [PATCH 59/61] fix Powershell Wmi Parser parsing issues --- .../Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs | 5 ++--- src/BenchmarkDotNet/Helpers/SectionsHelper.cs | 9 +++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs index d15b3eaa5f..15ad4d6c77 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs @@ -19,7 +19,7 @@ internal static CpuInfo Parse(string powershellWmiOutput) double maxFrequency = 0.0; double nominalFrequency = 0.0; - List> processors = SectionsHelper.ParseSections(powershellWmiOutput, ':'); + List> processors = SectionsHelper.ParseSectionsForPowershellWmi(powershellWmiOutput, ':'); foreach (Dictionary processor in processors) { if (processor.TryGetValue(WmicCpuInfoKeyNames.NumberOfCores, out string numberOfCoresValue) && @@ -49,8 +49,7 @@ internal static CpuInfo Parse(string powershellWmiOutput) string? processorName = processorModelNames.Count > 0 ? string.Join(", ", processorModelNames) : null; Frequency? maxFrequencyActual = maxFrequency > 0 && processorCount > 0 - ? Frequency.FromMHz(maxFrequency) - : null; + ? Frequency.FromMHz(maxFrequency) : null; Frequency? nominalFrequencyActual = nominalFrequency > 0 && processorCount > 0 ? Frequency.FromMHz(nominalFrequency) : null; 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 From 8059686d971cf7b4e2b5be516bd5c3c749551af9 Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Wed, 11 Jun 2025 14:19:09 +0100 Subject: [PATCH 60/61] fix .net framework cpu parsing issue --- src/BenchmarkDotNet/Detectors/Cpu/Windows/MosCpuDetector.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/MosCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/MosCpuDetector.cs index eeab5fa0fb..b3bc6922cd 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/MosCpuDetector.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/MosCpuDetector.cs @@ -43,11 +43,11 @@ public bool IsApplicable() => OsDetector.IsWindows() && processorsCount++; physicalCoreCount += (int)(uint)moProcessor[WmicCpuInfoKeyNames.NumberOfCores]; logicalCoreCount += (int)(uint)moProcessor[WmicCpuInfoKeyNames.NumberOfLogicalProcessors]; - double tempMaxFrequency = (double)moProcessor[WmicCpuInfoKeyNames.MaxClockSpeed]; + double tempMaxFrequency = Convert.ToDouble((uint)moProcessor[WmicCpuInfoKeyNames.MaxClockSpeed]); if (tempMaxFrequency > 0) { - nominalFrequency = nominalFrequency == 0 ? tempMaxFrequency : Math.Min(nominalFrequency, tempMaxFrequency); + nominalFrequency = nominalFrequency == 0 ? Convert.ToDouble(tempMaxFrequency) : Math.Min(nominalFrequency, tempMaxFrequency); } maxFrequency = Math.Max(maxFrequency, tempMaxFrequency); } From 183f35325e8b59baf3a195a629767c5d1e27b094 Mon Sep 17 00:00:00 2001 From: Alastair Lundy <133523182+alastairlundy@users.noreply.github.com> Date: Fri, 13 Jun 2025 23:50:12 +0100 Subject: [PATCH 61/61] do implicit conversion to double from uint --- src/BenchmarkDotNet/Detectors/Cpu/Windows/MosCpuDetector.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/MosCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/MosCpuDetector.cs index b3bc6922cd..edc48ba25d 100644 --- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/MosCpuDetector.cs +++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/MosCpuDetector.cs @@ -43,11 +43,11 @@ public bool IsApplicable() => OsDetector.IsWindows() && processorsCount++; physicalCoreCount += (int)(uint)moProcessor[WmicCpuInfoKeyNames.NumberOfCores]; logicalCoreCount += (int)(uint)moProcessor[WmicCpuInfoKeyNames.NumberOfLogicalProcessors]; - double tempMaxFrequency = Convert.ToDouble((uint)moProcessor[WmicCpuInfoKeyNames.MaxClockSpeed]); + double tempMaxFrequency = (uint)moProcessor[WmicCpuInfoKeyNames.MaxClockSpeed]; if (tempMaxFrequency > 0) { - nominalFrequency = nominalFrequency == 0 ? Convert.ToDouble(tempMaxFrequency) : Math.Min(nominalFrequency, tempMaxFrequency); + nominalFrequency = nominalFrequency == 0 ? tempMaxFrequency : Math.Min(nominalFrequency, tempMaxFrequency); } maxFrequency = Math.Max(maxFrequency, tempMaxFrequency); }