Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
495855a
allow MosCpuDetector to run on .NET 5+
alastairlundy Jun 4, 2025
3a75c1e
Revert "allow MosCpuDetector to run on .NET 5+"
alastairlundy Jun 4, 2025
3fbf726
add WmiLightCpu Detector
alastairlundy Jun 4, 2025
5972f77
Fix WmicCpuDetector being chosen if not available
alastairlundy Jun 4, 2025
7dc92b0
enable NativeAOT
alastairlundy Jun 5, 2025
fd5f096
simplify IsApplicable
alastairlundy Jun 5, 2025
9b9a5eb
Update spacing of SupportedOsPlatform attribute
alastairlundy Jun 5, 2025
8762345
Merge branch 'master' into fix-unknown-processor-windows
alastairlundy Jun 6, 2025
589f23a
Revert "Fix WmicCpuDetector being chosen if not available"
alastairlundy Jun 7, 2025
2bfbd44
Merge branch 'fix-unknown-processor-windows' of https://github.com/al…
alastairlundy Jun 7, 2025
a62fe32
add WMIC deprecation remarks
alastairlundy Jun 7, 2025
56aa5ca
remove WmiLight code
alastairlundy Jun 7, 2025
e7ad945
update WmiCpuInfoParser to return null if Processor Name isn't detected
alastairlundy Jun 7, 2025
29e2f96
remove WmiLightCpuDetector reference from WindowsCpuDetector
alastairlundy Jun 7, 2025
f9a5c34
Update WmicCpuInfoParser.cs
alastairlundy Jun 7, 2025
c181db9
check if wmicOutput is null or empty instead
alastairlundy Jun 7, 2025
18f44b8
add PowershellWmiCpuDetector (parser still not complete)
alastairlundy Jun 7, 2025
fa77462
return null if there's no version of powershell installed
alastairlundy Jun 7, 2025
0f3dd87
fix Powershell 7+ check
alastairlundy Jun 7, 2025
b6d9fb4
rework search statement given that regex isn't supported
alastairlundy Jun 7, 2025
8d0fbb9
add parser code
alastairlundy Jun 7, 2025
aca21d0
add PowershellWmiCpuDetector to WindowsCpuDetector
alastairlundy Jun 7, 2025
74c1b6c
rename variable to lower case
alastairlundy Jun 7, 2025
52155fa
improve checking of latest powershell 7+ version
alastairlundy Jun 7, 2025
d974b1a
use explicit typing
alastairlundy Jun 7, 2025
c7aa4e2
fix frequency addition issue
alastairlundy Jun 7, 2025
fa4ea57
revert to how WMIC parser handles processor frequency
alastairlundy Jun 7, 2025
3a8d884
add string is null or empty check to WmiCpuDetector
alastairlundy Jun 8, 2025
b4ceb96
invoke Powershell as "PowerShell" if the file isn't found
alastairlundy Jun 8, 2025
bc3b8e7
add nominal Frequency detection and improve max frequency detection
alastairlundy Jun 8, 2025
06e82da
fix issue with detecting latest Powershell
alastairlundy Jun 8, 2025
32f0d57
update comment
alastairlundy Jun 9, 2025
ef2385b
Update PowershellWmiCpuDetector.cs
alastairlundy Jun 9, 2025
fb87c98
refactor Powershell locating code to PowershellLocator
alastairlundy Jun 9, 2025
59b36be
simplify frequency checks
alastairlundy Jun 9, 2025
be3fbfe
Create PowershellWmiParserTests.cs
alastairlundy Jun 9, 2025
677bd1b
fix null being returned when object is expected.
alastairlundy Jun 9, 2025
c856ab0
rename test
alastairlundy Jun 9, 2025
8fbccd5
Merge branch 'dotnet:master' into fix-unknown-processor-windows
alastairlundy Jun 9, 2025
c54ee51
simplify max frequency check
alastairlundy Jun 9, 2025
1d86ff2
use """ for string in parser test
alastairlundy Jun 9, 2025
a13283b
Merge branch 'fix-unknown-processor-windows' of https://github.com/al…
alastairlundy Jun 9, 2025
589566f
Update PowershellWmiCpuInfoParserTests.cs
alastairlundy Jun 9, 2025
3e3d67b
reduce indentation with """
alastairlundy Jun 9, 2025
106cac1
remove unnecessary test info
alastairlundy Jun 9, 2025
2413529
move string null check to caller
alastairlundy Jun 9, 2025
476cad5
add nominal frequency support for MosCpuDetector
alastairlundy Jun 9, 2025
caf07ad
Update src/BenchmarkDotNet/Detectors/Cpu/Windows/PowershellWmiCpuDete…
alastairlundy Jun 9, 2025
2e0867a
remove nullability of parser
alastairlundy Jun 9, 2025
30768de
check if tempMaxFrequency > 0 before assignment
alastairlundy Jun 9, 2025
afb3220
remove nullability of WmicCpuInfoParser
alastairlundy Jun 9, 2025
ffc2b75
Merge branch 'fix-unknown-processor-windows' of https://github.com/al…
alastairlundy Jun 9, 2025
b60ad9f
use file scoped namespace
alastairlundy Jun 9, 2025
f300e4e
use double instead of int
alastairlundy Jun 9, 2025
85d6f1b
add null check to LinuxCpuDetector
alastairlundy Jun 9, 2025
f6d6eaf
change nullability of LinuxCpuParser
alastairlundy Jun 9, 2025
a5f7153
update LinuxCpuInfoParser nominal and max frequency detection
alastairlundy Jun 9, 2025
65c45b9
Update LinuxCpuInfoParser.cs
alastairlundy Jun 9, 2025
d2741e1
fix nullability check
alastairlundy Jun 9, 2025
d1172a3
simplify nominal frequency comparison
alastairlundy Jun 9, 2025
8307016
add null check to macOS
alastairlundy Jun 9, 2025
f7a6d37
Merge branch 'fix-unknown-processor-windows' of https://github.com/al…
alastairlundy Jun 9, 2025
cca1e5b
Merge branch 'dotnet:master' into fix-unknown-processor-windows
alastairlundy Jun 9, 2025
d5eb7e4
don't accept null string from detector
alastairlundy Jun 9, 2025
bbd6a8a
Merge branch 'fix-unknown-processor-windows' of https://github.com/al…
alastairlundy Jun 9, 2025
b85d12d
fix LinuxCpuInfo parser test issues and improve robustness of Linux C…
alastairlundy Jun 11, 2025
9609f83
fix Powershell Wmi Parser parsing issues
alastairlundy Jun 11, 2025
8059686
fix .net framework cpu parsing issue
alastairlundy Jun 11, 2025
183f353
do implicit conversion to double from uint
alastairlundy Jun 13, 2025
5f1328f
Merge branch 'master' into fix-unknown-processor-windows
alastairlundy Jun 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuDetector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
75 changes: 60 additions & 15 deletions src/BenchmarkDotNet/Detectors/Cpu/Linux/LinuxCpuInfoParser.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using BenchmarkDotNet.Extensions;
Expand All @@ -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
Expand All @@ -28,12 +31,13 @@ private static class Lscpu

/// <param name="cpuInfo">Output of `cat /proc/cpuinfo`</param>
/// <param name="lscpu">Output of `lscpu`</param>
internal static CpuInfo Parse(string? cpuInfo, string? lscpu)
internal static CpuInfo Parse(string cpuInfo, string lscpu)
{
var processorModelNames = new HashSet<string>();
var processorsToPhysicalCoreCount = new Dictionary<string, int>();
int logicalCoreCount = 0;
Frequency? maxFrequency = null;
double maxFrequency = 0.0;
double nominalFrequency = 0.0;

var logicalCores = SectionsHelper.ParseSections(cpuInfo, ':');
foreach (var logicalCore in logicalCores)
Expand All @@ -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(':'))
Expand All @@ -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);
Expand All @@ -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()
};
}

Expand All @@ -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;
Expand Down
25 changes: 18 additions & 7 deletions src/BenchmarkDotNet/Detectors/Cpu/Windows/MosCpuDetector.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management;
using BenchmarkDotNet.Extensions;
Expand Down Expand Up @@ -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"))
{
Expand All @@ -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
Expand All @@ -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()
};
}
}
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// CPU information from output of the `wmic cpu get Name, NumberOfCores, NumberOfLogicalProcessors /Format:List` command.
/// Windows only.
/// </summary>
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);
}
}
Original file line number Diff line number Diff line change
@@ -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<string> processorModelNames = new HashSet<string>();

int physicalCoreCount = 0;
int logicalCoreCount = 0;
int processorCount = 0;
double maxFrequency = 0.0;
double nominalFrequency = 0.0;

List<Dictionary<string, string>> processors = SectionsHelper.ParseSectionsForPowershellWmi(powershellWmiOutput, ':');
foreach (Dictionary<string, string> 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()
};
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
namespace BenchmarkDotNet.Detectors.Cpu.Windows;

internal class WindowsCpuDetector() : CpuDetector(new MosCpuDetector(), new WmicCpuDetector());
internal class WindowsCpuDetector() : CpuDetector(new MosCpuDetector(), new PowershellWmiCpuDetector(),
new WmicCpuDetector());
8 changes: 7 additions & 1 deletion src/BenchmarkDotNet/Detectors/Cpu/Windows/WmicCpuDetector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
/// </summary>
/// <remarks>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.
/// <para>WMIC may be removed in a future version of Windows. See <see href="https://learn.microsoft.com/en-us/windows/win32/wmisdk/wmic"/> </para></remarks>
internal class WmicCpuDetector : ICpuDetector
{
private const string DefaultWmicPath = @"C:\Windows\System32\wbem\WMIC.exe";
Expand All @@ -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);
}
}
Loading