diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuDetector.cs
index 12c3b188d4..44973b27e9 100644
--- a/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuDetector.cs
+++ b/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuDetector.cs
@@ -24,8 +24,12 @@ internal class LinuxCpuDetector : ICpuDetector
["LANGUAGE"] = "C"
};
- string cpuInfo = ProcessHelper.RunAndReadOutput("cat", "/proc/cpuinfo") ?? "";
- string lscpu = ProcessHelper.RunAndReadOutput("/bin/bash", "-c \"lscpu\"", environmentVariables: languageInvariantEnvironment);
+ string? cpuInfo = ProcessHelper.RunAndReadOutput("cat", "/proc/cpuinfo") ?? string.Empty;
+ string? lscpu = ProcessHelper.RunAndReadOutput("/bin/bash", "-c \"lscpu\"", environmentVariables: languageInvariantEnvironment) ?? string.Empty;
+
+ if (cpuInfo == string.Empty && lscpu == string.Empty)
+ return null;
+
return LinuxCpuInfoParser.Parse(cpuInfo, lscpu);
}
}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuInfoParser.cs b/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuInfoParser.cs
index ed93999653..7d79c3262f 100644
--- a/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuInfoParser.cs
+++ b/src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuInfoParser.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using BenchmarkDotNet.Extensions;
@@ -17,6 +18,8 @@ private static class ProcCpu
internal const string CpuCores = "cpu cores";
internal const string ModelName = "model name";
internal const string MaxFrequency = "max freq";
+ internal const string NominalFrequencyBackup = "nominal freq";
+ internal const string NominalFrequency = "cpu MHz";
}
private static class Lscpu
@@ -28,12 +31,13 @@ private static class Lscpu
/// Output of `cat /proc/cpuinfo`
/// Output of `lscpu`
- internal static CpuInfo Parse(string? cpuInfo, string? lscpu)
+ internal static CpuInfo Parse(string cpuInfo, string lscpu)
{
var processorModelNames = new HashSet();
var processorsToPhysicalCoreCount = new Dictionary();
int logicalCoreCount = 0;
- Frequency? maxFrequency = null;
+ double maxFrequency = 0.0;
+ double nominalFrequency = 0.0;
var logicalCores = SectionsHelper.ParseSections(cpuInfo, ':');
foreach (var logicalCore in logicalCores)
@@ -51,14 +55,43 @@ internal static CpuInfo Parse(string? cpuInfo, string? lscpu)
}
if (logicalCore.TryGetValue(ProcCpu.MaxFrequency, out string maxCpuFreqValue) &&
- Frequency.TryParseMHz(maxCpuFreqValue, out var maxCpuFreq))
+ Frequency.TryParseMHz(maxCpuFreqValue.Replace(',', '.'), out Frequency maxCpuFreq)
+ && maxCpuFreq > 0)
{
- maxFrequency = maxCpuFreq;
+ maxFrequency = Math.Max(maxFrequency, maxCpuFreq.ToMHz());
+ }
+
+ bool nominalFrequencyHasValue = logicalCore.TryGetValue(ProcCpu.NominalFrequency, out string nominalFreqValue);
+ bool nominalFrequencyBackupHasValue = logicalCore.TryGetValue(ProcCpu.NominalFrequencyBackup, out string nominalFreqBackupValue);
+
+ double nominalCpuFreq = 0.0;
+ double nominalCpuBackupFreq = 0.0;
+
+ if (nominalFrequencyHasValue &&
+ double.TryParse(nominalFreqValue, out nominalCpuFreq)
+ && nominalCpuFreq > 0)
+ {
+ nominalCpuFreq = nominalFrequency == 0 ? nominalCpuFreq : Math.Min(nominalFrequency, nominalCpuFreq);
+ }
+ if (nominalFrequencyBackupHasValue &&
+ double.TryParse(nominalFreqBackupValue, out nominalCpuBackupFreq)
+ && nominalCpuBackupFreq > 0)
+ {
+ nominalCpuBackupFreq = nominalFrequency == 0 ? nominalCpuBackupFreq : Math.Min(nominalFrequency, nominalCpuBackupFreq);
+ }
+
+ if (nominalFrequencyHasValue && nominalFrequencyBackupHasValue)
+ {
+ nominalFrequency = Math.Min(nominalCpuFreq, nominalCpuBackupFreq);
+ }
+ else
+ {
+ nominalFrequency = nominalCpuFreq == 0.0 ? nominalCpuBackupFreq : nominalCpuFreq;
}
}
int? coresPerSocket = null;
- if (lscpu != null)
+ if (string.IsNullOrEmpty(lscpu) == false)
{
var lscpuParts = lscpu.Split('\n')
.Where(line => line.Contains(':'))
@@ -70,8 +103,8 @@ internal static CpuInfo Parse(string? cpuInfo, string? lscpu)
string value = lscpuParts[i + 1].Trim();
if (name.EqualsWithIgnoreCase(Lscpu.MaxFrequency) &&
- Frequency.TryParseMHz(value.Replace(',', '.'), out var maxFrequencyParsed)) // Example: `CPU max MHz: 3200,0000`
- maxFrequency = maxFrequencyParsed;
+ Frequency.TryParseMHz(value.Replace(',', '.'), out Frequency maxFrequencyParsed)) // Example: `CPU max MHz: 3200,0000`
+ maxFrequency = Math.Max(maxFrequency, maxFrequencyParsed.ToMHz());
if (name.EqualsWithIgnoreCase(Lscpu.ModelName))
processorModelNames.Add(value);
@@ -82,21 +115,33 @@ internal static CpuInfo Parse(string? cpuInfo, string? lscpu)
}
}
- var nominalFrequency = processorModelNames
- .Select(ParseFrequencyFromBrandString)
- .WhereNotNull()
- .FirstOrDefault() ?? maxFrequency;
string processorName = processorModelNames.Count > 0 ? string.Join(", ", processorModelNames) : null;
int? physicalProcessorCount = processorsToPhysicalCoreCount.Count > 0 ? processorsToPhysicalCoreCount.Count : null;
int? physicalCoreCount = processorsToPhysicalCoreCount.Count > 0 ? processorsToPhysicalCoreCount.Values.Sum() : coresPerSocket;
+
+ Frequency? maxFrequencyActual = maxFrequency > 0 && physicalProcessorCount > 0
+ ? Frequency.FromMHz(maxFrequency) : null;
+
+ Frequency? nominalFrequencyActual = nominalFrequency > 0 && physicalProcessorCount > 0
+ ? Frequency.FromMHz(nominalFrequency) : null;
+
+ if (nominalFrequencyActual is null)
+ {
+ bool nominalFrequencyInBrandString = processorModelNames.Any(x => ParseFrequencyFromBrandString(x) is not null);
+
+ if (nominalFrequencyInBrandString)
+ nominalFrequencyActual = processorModelNames.Select(x => ParseFrequencyFromBrandString(x))
+ .First(x => x is not null);
+ }
+
return new CpuInfo
{
ProcessorName = processorName,
PhysicalProcessorCount = physicalProcessorCount,
PhysicalCoreCount = physicalCoreCount,
LogicalCoreCount = logicalCoreCount > 0 ? logicalCoreCount : null,
- NominalFrequencyHz = nominalFrequency?.Hertz.RoundToLong(),
- MaxFrequencyHz = maxFrequency?.Hertz.RoundToLong()
+ NominalFrequencyHz = nominalFrequencyActual?.Hertz.RoundToLong(),
+ MaxFrequencyHz = maxFrequencyActual?.Hertz.RoundToLong()
};
}
@@ -107,7 +152,7 @@ internal static CpuInfo Parse(string? cpuInfo, string? lscpu)
if (matches.Count > 0 && matches[0].Groups.Count > 1)
{
string match = Regex.Matches(brandString, pattern, RegexOptions.IgnoreCase)[0].Groups[1].ToString();
- return Frequency.TryParseGHz(match, out var result) ? result : null;
+ return Frequency.TryParseGHz(match, out Frequency result) ? result : null;
}
return null;
diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/MosCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/MosCpuDetector.cs
index 8ab16535f6..edc48ba25d 100644
--- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/MosCpuDetector.cs
+++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/MosCpuDetector.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.Linq;
using System.Management;
using BenchmarkDotNet.Extensions;
@@ -28,7 +29,8 @@ public bool IsApplicable() => OsDetector.IsWindows() &&
int physicalCoreCount = 0;
int logicalCoreCount = 0;
int processorsCount = 0;
- int sumMaxFrequency = 0;
+ double maxFrequency = 0;
+ double nominalFrequency = 0;
using (var mosProcessor = new ManagementObjectSearcher("SELECT * FROM Win32_Processor"))
{
@@ -41,14 +43,23 @@ public bool IsApplicable() => OsDetector.IsWindows() &&
processorsCount++;
physicalCoreCount += (int)(uint)moProcessor[WmicCpuInfoKeyNames.NumberOfCores];
logicalCoreCount += (int)(uint)moProcessor[WmicCpuInfoKeyNames.NumberOfLogicalProcessors];
- sumMaxFrequency = (int)(uint)moProcessor[WmicCpuInfoKeyNames.MaxClockSpeed];
+ double tempMaxFrequency = (uint)moProcessor[WmicCpuInfoKeyNames.MaxClockSpeed];
+
+ if (tempMaxFrequency > 0)
+ {
+ nominalFrequency = nominalFrequency == 0 ? tempMaxFrequency : Math.Min(nominalFrequency, tempMaxFrequency);
+ }
+ maxFrequency = Math.Max(maxFrequency, tempMaxFrequency);
}
}
}
string processorName = processorModelNames.Count > 0 ? string.Join(", ", processorModelNames) : null;
- Frequency? maxFrequency = sumMaxFrequency > 0 && processorsCount > 0
- ? Frequency.FromMHz(sumMaxFrequency * 1.0 / processorsCount)
+ Frequency? maxFrequencyActual = maxFrequency > 0 && processorsCount > 0
+ ? Frequency.FromMHz(maxFrequency)
+ : null;
+ Frequency? nominalFrequencyActual = nominalFrequency > 0 && processorsCount > 0
+ ? Frequency.FromMHz(nominalFrequency)
: null;
return new CpuInfo
@@ -57,8 +68,8 @@ public bool IsApplicable() => OsDetector.IsWindows() &&
PhysicalProcessorCount = processorsCount > 0 ? processorsCount : null,
PhysicalCoreCount = physicalCoreCount > 0 ? physicalCoreCount : null,
LogicalCoreCount = logicalCoreCount > 0 ? logicalCoreCount : null,
- NominalFrequencyHz = maxFrequency?.Hertz.RoundToLong(),
- MaxFrequencyHz = maxFrequency?.Hertz.RoundToLong()
+ NominalFrequencyHz = nominalFrequencyActual?.Hertz.RoundToLong(),
+ MaxFrequencyHz = maxFrequencyActual?.Hertz.RoundToLong()
};
}
}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs
new file mode 100644
index 0000000000..7f61c1b8fc
--- /dev/null
+++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDetector.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.Versioning;
+using System.Text.RegularExpressions;
+using BenchmarkDotNet.Helpers;
+using Perfolizer.Models;
+
+namespace BenchmarkDotNet.Detectors.Cpu.Windows;
+
+///
+/// CPU information from output of the `wmic cpu get Name, NumberOfCores, NumberOfLogicalProcessors /Format:List` command.
+/// Windows only.
+///
+internal class PowershellWmiCpuDetector : ICpuDetector
+{
+ private readonly string windowsPowershellPath =
+ $"{Environment.SystemDirectory}{Path.DirectorySeparatorChar}WindowsPowerShell{Path.DirectorySeparatorChar}" +
+ $"v1.0{Path.DirectorySeparatorChar}powershell.exe";
+
+ public bool IsApplicable() => OsDetector.IsWindows();
+
+ #if NET6_0_OR_GREATER
+ [SupportedOSPlatform("windows")]
+ #endif
+ public CpuInfo? Detect()
+ {
+ if (!IsApplicable()) return null;
+
+ const string argList = $"{WmicCpuInfoKeyNames.Name}, " +
+ $"{WmicCpuInfoKeyNames.NumberOfCores}, " +
+ $"{WmicCpuInfoKeyNames.NumberOfLogicalProcessors}, " +
+ $"{WmicCpuInfoKeyNames.MaxClockSpeed}";
+
+ string output = ProcessHelper.RunAndReadOutput(PowerShellLocator.LocateOnWindows() ?? "PowerShell",
+ "Get-CimInstance Win32_Processor -Property " + argList);
+
+ if (string.IsNullOrEmpty(output))
+ return null;
+
+ return PowershellWmiCpuInfoParser.Parse(output);
+ }
+}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs
new file mode 100644
index 0000000000..15ad4d6c77
--- /dev/null
+++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuInfoParser.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Collections.Generic;
+using BenchmarkDotNet.Extensions;
+using BenchmarkDotNet.Helpers;
+using Perfolizer.Horology;
+using Perfolizer.Models;
+
+namespace BenchmarkDotNet.Detectors.Cpu.Windows;
+
+internal static class PowershellWmiCpuInfoParser
+{
+ internal static CpuInfo Parse(string powershellWmiOutput)
+ {
+ HashSet processorModelNames = new HashSet();
+
+ int physicalCoreCount = 0;
+ int logicalCoreCount = 0;
+ int processorCount = 0;
+ double maxFrequency = 0.0;
+ double nominalFrequency = 0.0;
+
+ List> processors = SectionsHelper.ParseSectionsForPowershellWmi(powershellWmiOutput, ':');
+ foreach (Dictionary processor in processors)
+ {
+ if (processor.TryGetValue(WmicCpuInfoKeyNames.NumberOfCores, out string numberOfCoresValue) &&
+ int.TryParse(numberOfCoresValue, out int numberOfCores) &&
+ numberOfCores > 0)
+ physicalCoreCount += numberOfCores;
+
+ if (processor.TryGetValue(WmicCpuInfoKeyNames.NumberOfLogicalProcessors, out string numberOfLogicalValue) &&
+ int.TryParse(numberOfLogicalValue, out int numberOfLogical) &&
+ numberOfLogical > 0)
+ logicalCoreCount += numberOfLogical;
+
+ if (processor.TryGetValue(WmicCpuInfoKeyNames.Name, out string name))
+ {
+ processorModelNames.Add(name);
+ processorCount++;
+ }
+
+ if (processor.TryGetValue(WmicCpuInfoKeyNames.MaxClockSpeed, out string frequencyValue)
+ && double.TryParse(frequencyValue, out double frequency)
+ && frequency > 0)
+ {
+ nominalFrequency = nominalFrequency == 0 ? frequency : Math.Min(nominalFrequency, frequency);
+ maxFrequency = Math.Max(maxFrequency, frequency);
+ }
+ }
+
+ string? processorName = processorModelNames.Count > 0 ? string.Join(", ", processorModelNames) : null;
+ Frequency? maxFrequencyActual = maxFrequency > 0 && processorCount > 0
+ ? Frequency.FromMHz(maxFrequency) : null;
+
+ Frequency? nominalFrequencyActual = nominalFrequency > 0 && processorCount > 0 ?
+ Frequency.FromMHz(nominalFrequency) : null;
+
+ return new CpuInfo
+ {
+ ProcessorName = processorName,
+ PhysicalProcessorCount = processorCount > 0 ? processorCount : null,
+ PhysicalCoreCount = physicalCoreCount > 0 ? physicalCoreCount : null,
+ LogicalCoreCount = logicalCoreCount > 0 ? logicalCoreCount : null,
+ NominalFrequencyHz = nominalFrequencyActual?.Hertz.RoundToLong(),
+ MaxFrequencyHz = maxFrequencyActual?.Hertz.RoundToLong()
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WindowsCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WindowsCpuDetector.cs
index 9969a1bca0..1de238b93f 100644
--- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WindowsCpuDetector.cs
+++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WindowsCpuDetector.cs
@@ -1,3 +1,4 @@
namespace BenchmarkDotNet.Detectors.Cpu.Windows;
-internal class WindowsCpuDetector() : CpuDetector(new MosCpuDetector(), new WmicCpuDetector());
\ No newline at end of file
+internal class WindowsCpuDetector() : CpuDetector(new MosCpuDetector(), new PowershellWmiCpuDetector(),
+ new WmicCpuDetector());
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuDetector.cs
index 3a92d180b8..fe3434ae7f 100644
--- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuDetector.cs
+++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuDetector.cs
@@ -9,6 +9,8 @@ namespace BenchmarkDotNet.Detectors.Cpu.Windows;
/// CPU information from output of the `wmic cpu get Name, NumberOfCores, NumberOfLogicalProcessors /Format:List` command.
/// Windows only.
///
+/// WMIC is deprecated by Microsoft starting with Windows 10 21H1 (including Windows Server), and it is not known whether it still ships with Windows by default.
+/// WMIC may be removed in a future version of Windows. See
internal class WmicCpuDetector : ICpuDetector
{
private const string DefaultWmicPath = @"C:\Windows\System32\wbem\WMIC.exe";
@@ -24,7 +26,11 @@ internal class WmicCpuDetector : ICpuDetector
$"{WmicCpuInfoKeyNames.NumberOfLogicalProcessors}, " +
$"{WmicCpuInfoKeyNames.MaxClockSpeed}";
string wmicPath = File.Exists(DefaultWmicPath) ? DefaultWmicPath : "wmic";
- string wmicOutput = ProcessHelper.RunAndReadOutput(wmicPath, $"cpu get {argList} /Format:List");
+ string? wmicOutput = ProcessHelper.RunAndReadOutput(wmicPath, $"cpu get {argList} /Format:List");
+
+ if (string.IsNullOrEmpty(wmicOutput))
+ return null;
+
return WmicCpuInfoParser.Parse(wmicOutput);
}
}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuInfoParser.cs b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuInfoParser.cs
index a139d39711..295d76262e 100644
--- a/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuInfoParser.cs
+++ b/src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuInfoParser.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using BenchmarkDotNet.Extensions;
using BenchmarkDotNet.Helpers;
using Perfolizer.Horology;
@@ -12,15 +13,16 @@ internal static class WmicCpuInfoParser
/// Parses wmic output and returns
///
/// Output of `wmic cpu get Name, NumberOfCores, NumberOfLogicalProcessors /Format:List`
- internal static CpuInfo Parse(string? wmicOutput)
+ internal static CpuInfo Parse(string wmicOutput)
{
- var processorModelNames = new HashSet();
+ HashSet processorModelNames = new HashSet();
int physicalCoreCount = 0;
int logicalCoreCount = 0;
int processorsCount = 0;
- var sumMaxFrequency = Frequency.Zero;
+ double maxFrequency = 0.0;
+ double nominalFrequency = 0.0;
- var processors = SectionsHelper.ParseSections(wmicOutput, '=');
+ List> processors = SectionsHelper.ParseSections(wmicOutput, '=');
foreach (var processor in processors)
{
if (processor.TryGetValue(WmicCpuInfoKeyNames.NumberOfCores, out string numberOfCoresValue) &&
@@ -40,26 +42,29 @@ internal static CpuInfo Parse(string? wmicOutput)
}
if (processor.TryGetValue(WmicCpuInfoKeyNames.MaxClockSpeed, out string frequencyValue)
- && int.TryParse(frequencyValue, out int frequency)
+ && double.TryParse(frequencyValue, out double frequency)
&& frequency > 0)
{
- sumMaxFrequency += frequency;
+ nominalFrequency = nominalFrequency == 0 ? frequency : Math.Min(nominalFrequency, frequency);
+ maxFrequency = Math.Max(maxFrequency, frequency);
}
}
string? processorName = processorModelNames.Count > 0 ? string.Join(", ", processorModelNames) : null;
- Frequency? maxFrequency = sumMaxFrequency > 0 && processorsCount > 0
- ? Frequency.FromMHz(sumMaxFrequency / processorsCount)
+ Frequency? maxFrequencyActual = maxFrequency > 0 && processorsCount > 0
+ ? Frequency.FromMHz(maxFrequency)
: null;
+ Frequency? nominalFrequencyActual = nominalFrequency > 0 && processorsCount > 0 ? Frequency.FromMHz(nominalFrequency) : null;
+
return new CpuInfo
{
ProcessorName = processorName,
PhysicalProcessorCount = processorsCount > 0 ? processorsCount : null,
PhysicalCoreCount = physicalCoreCount > 0 ? physicalCoreCount : null,
LogicalCoreCount = logicalCoreCount > 0 ? logicalCoreCount : null,
- NominalFrequencyHz = maxFrequency?.Hertz.RoundToLong(),
- MaxFrequencyHz = maxFrequency?.Hertz.RoundToLong()
+ NominalFrequencyHz = nominalFrequencyActual?.Hertz.RoundToLong(),
+ MaxFrequencyHz = maxFrequencyActual?.Hertz.RoundToLong()
};
}
}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Detectors/Cpu/macOS/MacOsCpuDetector.cs b/src/BenchmarkDotNet/Detectors/Cpu/macOS/MacOsCpuDetector.cs
index f34de47ac9..2f540e82d3 100644
--- a/src/BenchmarkDotNet/Detectors/Cpu/macOS/MacOsCpuDetector.cs
+++ b/src/BenchmarkDotNet/Detectors/Cpu/macOS/MacOsCpuDetector.cs
@@ -15,7 +15,11 @@ internal class MacOsCpuDetector : ICpuDetector
{
if (!IsApplicable()) return null;
- string sysctlOutput = ProcessHelper.RunAndReadOutput("sysctl", "-a");
+ string? sysctlOutput = ProcessHelper.RunAndReadOutput("sysctl", "-a");
+
+ if (sysctlOutput is null)
+ return null;
+
return SysctlCpuInfoParser.Parse(sysctlOutput);
}
}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Detectors/Cpu/macOS/SysctlCpuInfoParser.cs b/src/BenchmarkDotNet/Detectors/Cpu/macOS/SysctlCpuInfoParser.cs
index 58d1b53766..5cd4d11c8a 100644
--- a/src/BenchmarkDotNet/Detectors/Cpu/macOS/SysctlCpuInfoParser.cs
+++ b/src/BenchmarkDotNet/Detectors/Cpu/macOS/SysctlCpuInfoParser.cs
@@ -20,7 +20,7 @@ private static class Sysctl
/// Output of `sysctl -a`
[SuppressMessage("ReSharper", "StringLiteralTypo")]
- internal static CpuInfo Parse(string? sysctlOutput)
+ internal static CpuInfo Parse(string sysctlOutput)
{
var sysctl = SectionsHelper.ParseSection(sysctlOutput, ':');
string processorName = sysctl.GetValueOrDefault(Sysctl.ProcessorName);
diff --git a/src/BenchmarkDotNet/Helpers/PowerShellLocator.cs b/src/BenchmarkDotNet/Helpers/PowerShellLocator.cs
new file mode 100644
index 0000000000..28b8e910f7
--- /dev/null
+++ b/src/BenchmarkDotNet/Helpers/PowerShellLocator.cs
@@ -0,0 +1,81 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Runtime.Versioning;
+using System.Text.RegularExpressions;
+using BenchmarkDotNet.Detectors;
+
+namespace BenchmarkDotNet.Helpers;
+
+///
+/// Locates PowerShell on a system, currently only supports on Windows.
+///
+internal class PowerShellLocator
+{
+ private static readonly string WindowsPowershellPath =
+ $"{Environment.SystemDirectory}{Path.DirectorySeparatorChar}WindowsPowerShell{Path.DirectorySeparatorChar}" +
+ $"v1.0{Path.DirectorySeparatorChar}powershell.exe";
+
+#if NET6_0_OR_GREATER
+ [SupportedOSPlatform("windows")]
+#endif
+ internal static string? LocateOnWindows()
+ {
+ if (OsDetector.IsWindows() == false)
+ return null;
+
+ string powershellPath;
+
+ try
+ {
+ string programFiles = Environment.Is64BitOperatingSystem
+ ? Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles)
+ : Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
+
+ string powershell7PlusPath = $"{programFiles}{Path.DirectorySeparatorChar}Powershell{Path.DirectorySeparatorChar}";
+
+ bool checkForPowershell7Plus = true;
+
+ if (Directory.Exists(powershell7PlusPath))
+ {
+ string[] subDirectories = Directory.EnumerateDirectories(powershell7PlusPath, "*", SearchOption.AllDirectories)
+ .ToArray();
+
+ //Use the highest number string directory for PowerShell so that we get the newest major PowerShell version
+ // Example version directories are 6, 7, and in the future 8.
+ string? subDirectory = subDirectories.Where(x => Regex.IsMatch(x, "[0-9]"))
+ .Select(x => x)
+ .OrderByDescending(x => x)
+ .FirstOrDefault();
+
+ if (subDirectory is not null)
+ {
+ powershell7PlusPath = $"{subDirectory}{Path.DirectorySeparatorChar}pwsh.exe";
+ }
+ else
+ {
+ checkForPowershell7Plus = false;
+ }
+ }
+
+ // Optimistically, use Cross-platform new PowerShell when available but fallback to Windows PowerShell if not available.
+ if (checkForPowershell7Plus)
+ {
+ powershellPath = File.Exists(powershell7PlusPath) ? powershell7PlusPath : WindowsPowershellPath;
+ }
+ else
+ {
+ powershellPath = WindowsPowershellPath;
+ }
+
+ if (File.Exists(powershellPath) == false)
+ powershellPath = "PowerShell";
+ }
+ catch
+ {
+ powershellPath = "PowerShell";
+ }
+
+ return powershellPath;
+ }
+}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Helpers/SectionsHelper.cs b/src/BenchmarkDotNet/Helpers/SectionsHelper.cs
index 15c54fd8f9..e16442d0fb 100644
--- a/src/BenchmarkDotNet/Helpers/SectionsHelper.cs
+++ b/src/BenchmarkDotNet/Helpers/SectionsHelper.cs
@@ -32,5 +32,14 @@ public static List> ParseSections(string? content, ch
.Where(s => s.Count > 0)
.ToList();
}
+
+ public static List> ParseSectionsForPowershellWmi(string? content, char separator)
+ {
+ return
+ Regex.Split(content ?? "", "(\r*\n)")
+ .Select(s => ParseSection(s, separator))
+ .Where(s => s.Count > 0)
+ .ToList();
+ }
}
}
\ No newline at end of file
diff --git a/tests/BenchmarkDotNet.Tests/Detectors/Cpu/LinuxCpuInfoParserTests.cs b/tests/BenchmarkDotNet.Tests/Detectors/Cpu/LinuxCpuInfoParserTests.cs
index d229a2b06d..0d4de2d9cc 100644
--- a/tests/BenchmarkDotNet.Tests/Detectors/Cpu/LinuxCpuInfoParserTests.cs
+++ b/tests/BenchmarkDotNet.Tests/Detectors/Cpu/LinuxCpuInfoParserTests.cs
@@ -22,7 +22,7 @@ public void EmptyTest()
[Fact]
public void MalformedTest()
{
- var actual = LinuxCpuInfoParser.Parse("malformedkey: malformedvalue\n\nmalformedkey2: malformedvalue2", null);
+ var actual = LinuxCpuInfoParser.Parse("malformedkey: malformedvalue\n\nmalformedkey2: malformedvalue2", string.Empty);
var expected = new CpuInfo();
Output.AssertEqual(expected, actual);
}
@@ -31,7 +31,7 @@ public void MalformedTest()
public void TwoProcessorWithDifferentCoresCountTest()
{
string cpuInfo = TestHelper.ReadTestFile("ProcCpuInfoProcessorWithDifferentCoresCount.txt");
- var actual = LinuxCpuInfoParser.Parse(cpuInfo, null);
+ var actual = LinuxCpuInfoParser.Parse(cpuInfo, string.Empty);
var expected = new CpuInfo
{
ProcessorName = "Unknown processor with 2 cores and hyper threading, Unknown processor with 4 cores",
@@ -49,7 +49,7 @@ public void TwoProcessorWithDifferentCoresCountTest()
public void RealOneProcessorTwoCoresTest()
{
string cpuInfo = TestHelper.ReadTestFile("ProcCpuInfoRealOneProcessorTwoCores.txt");
- var actual = LinuxCpuInfoParser.Parse(cpuInfo, null);
+ var actual = LinuxCpuInfoParser.Parse(cpuInfo, string.Empty);
var expected = new CpuInfo
{
ProcessorName = "Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz",
@@ -66,14 +66,14 @@ public void RealOneProcessorTwoCoresTest()
public void RealOneProcessorFourCoresTest()
{
string cpuInfo = TestHelper.ReadTestFile("ProcCpuInfoRealOneProcessorFourCores.txt");
- var actual = LinuxCpuInfoParser.Parse(cpuInfo, null);
+ var actual = LinuxCpuInfoParser.Parse(cpuInfo, string.Empty);
var expected = new CpuInfo
{
ProcessorName = "Intel(R) Core(TM) i7-4710MQ CPU @ 2.50GHz",
PhysicalProcessorCount = 1,
PhysicalCoreCount = 4,
LogicalCoreCount = 8,
- NominalFrequencyHz = 2_500_000_000,
+ NominalFrequencyHz = 2_494_300_000,
MaxFrequencyHz = 2_500_000_000
};
Output.AssertEqual(expected, actual);
@@ -154,7 +154,7 @@ r smca fsrm flush_l1d
PhysicalProcessorCount = 1,
PhysicalCoreCount = 16,
LogicalCoreCount = 32,
- NominalFrequencyHz = 5_881_000_000,
+ NominalFrequencyHz = 400_000_000,
MaxFrequencyHz = 5_881_000_000
};
Output.AssertEqual(expected, actual);
diff --git a/tests/BenchmarkDotNet.Tests/Detectors/Cpu/PowershellWmiCpuInfoParserTests.cs b/tests/BenchmarkDotNet.Tests/Detectors/Cpu/PowershellWmiCpuInfoParserTests.cs
new file mode 100644
index 0000000000..a781d70021
--- /dev/null
+++ b/tests/BenchmarkDotNet.Tests/Detectors/Cpu/PowershellWmiCpuInfoParserTests.cs
@@ -0,0 +1,122 @@
+using BenchmarkDotNet.Detectors.Cpu.Windows;
+using Perfolizer.Models;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace BenchmarkDotNet.Tests.Detectors.Cpu;
+
+public class PowershellWmiCpuInfoParserTests(ITestOutputHelper output)
+{
+ private ITestOutputHelper Output { get; } = output;
+
+
+ [Fact]
+ public void EmptyTest()
+ {
+ CpuInfo? actual = PowershellWmiCpuInfoParser.Parse(string.Empty);
+ CpuInfo expected = new CpuInfo();
+ Output.AssertEqual(expected, actual);
+ }
+
+
+ [Fact]
+ public void MalformedTest()
+ {
+ CpuInfo? actual = PowershellWmiCpuInfoParser
+ .Parse("malformedkey=malformedvalue\n\nmalformedkey2=malformedvalue2");
+ CpuInfo expected = new CpuInfo();
+ Output.AssertEqual(expected, actual);
+ }
+
+ [Fact]
+ public void RealTwoProcessorEightCoresTest()
+ {
+ const string cpuInfo =
+ """
+ MaxClockSpeed:2400
+ Name:Intel(R) Xeon(R) CPU E5-2630 v3
+ NumberOfCores:8
+ NumberOfLogicalProcessors:16
+
+
+ MaxClockSpeed:2400
+ Name:Intel(R) Xeon(R) CPU E5-2630 v3
+ NumberOfCores:8
+ NumberOfLogicalProcessors:16
+
+ """;
+ CpuInfo? actual = PowershellWmiCpuInfoParser.Parse(cpuInfo);
+
+ CpuInfo expected = new CpuInfo
+ {
+ ProcessorName = "Intel(R) Xeon(R) CPU E5-2630 v3",
+ PhysicalProcessorCount = 2,
+ PhysicalCoreCount = 16,
+ LogicalCoreCount = 32,
+ NominalFrequencyHz = 2_400_000_000,
+ MaxFrequencyHz = 2_400_000_000,
+ };
+
+ Output.AssertEqual(expected, actual);
+ }
+
+ [Fact]
+ public void RealTwoProcessorEightCoresWithWmicBugTest()
+ {
+ const string cpuInfo =
+ "\r\r\n" +
+ "\r\r\n" +
+ "MaxClockSpeed:3111\r\r\n" +
+ "Name:Intel(R) Xeon(R) CPU E5-2687W 0\r\r\n" +
+ "NumberOfCores:8\r\r\n" +
+ "NumberOfLogicalProcessors:16\r\r\n" +
+ "\r\r\n" +
+ "\r\r\n" +
+ "MaxClockSpeed:3111\r\r\n" +
+ "Name:Intel(R) Xeon(R) CPU E5-2687W 0\r\r\n" +
+ "NumberOfCores:8\r\r\n" +
+ "NumberOfLogicalProcessors:16\r\r\n" +
+ "\r\r\n" +
+ "\r\r\n" +
+ "\r\r\n";
+
+ CpuInfo? actual = PowershellWmiCpuInfoParser.Parse(cpuInfo);
+
+ CpuInfo expected = new CpuInfo
+ {
+ ProcessorName = "Intel(R) Xeon(R) CPU E5-2687W 0",
+ PhysicalProcessorCount = 2,
+ PhysicalCoreCount = 16,
+ LogicalCoreCount = 32,
+ NominalFrequencyHz = 3_111_000_000,
+ MaxFrequencyHz = 3_111_000_000,
+ };
+
+ Output.AssertEqual(expected, actual);
+ }
+
+ [Fact]
+ public void RealOneProcessorFourCoresTest()
+ {
+ const string cpuInfo = """
+
+ MaxClockSpeed:2500
+ Name:Intel(R) Core(TM) i7-4710MQ
+ NumberOfCores:4
+ NumberOfLogicalProcessors:8
+ """;
+
+ CpuInfo? actual = PowershellWmiCpuInfoParser.Parse(cpuInfo);
+ CpuInfo expected = new CpuInfo
+ {
+ ProcessorName = "Intel(R) Core(TM) i7-4710MQ",
+ PhysicalProcessorCount = 1,
+ PhysicalCoreCount = 4,
+ LogicalCoreCount = 8,
+ NominalFrequencyHz = 2_500_000_000,
+ MaxFrequencyHz = 2_500_000_000,
+ };
+
+ Output.AssertEqual(expected, actual);
+ }
+}
\ No newline at end of file