diff --git a/src/BenchmarkDotNet/Environments/ProcessorBrandStringHelper.cs b/src/BenchmarkDotNet/Environments/ProcessorBrandStringHelper.cs
index 882078df74..1f715f9cbe 100644
--- a/src/BenchmarkDotNet/Environments/ProcessorBrandStringHelper.cs
+++ b/src/BenchmarkDotNet/Environments/ProcessorBrandStringHelper.cs
@@ -3,153 +3,152 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text.RegularExpressions;
+using BenchmarkDotNet.Extensions;
using BenchmarkDotNet.Helpers;
using BenchmarkDotNet.Portability.Cpu;
using Perfolizer.Horology;
-namespace BenchmarkDotNet.Environments
+namespace BenchmarkDotNet.Environments;
+
+public static class ProcessorBrandStringHelper
{
- public static class ProcessorBrandStringHelper
+ ///
+ /// Transform a processor brand string to a nice form for summary.
+ ///
+ /// The CPU information
+ /// Whether to include determined max frequency information
+ /// Prettified version
+ public static string Prettify(CpuInfo? cpuInfo, bool includeMaxFrequency = false)
{
- ///
- /// Transform a processor brand string to a nice form for summary.
- ///
- /// The CPU information
- /// Whether to include determined max frequency information
- /// Prettified version
- public static string Prettify(CpuInfo cpuInfo, bool includeMaxFrequency = false)
- {
- if (cpuInfo == null || string.IsNullOrEmpty(cpuInfo.ProcessorName))
- {
- return "Unknown processor";
- }
+ string? processorName = cpuInfo?.ProcessorName;
+ if (processorName == null || processorName.IsBlank())
+ return "Unknown processor";
- // Remove parts which don't provide any useful information for user
- var processorName = cpuInfo.ProcessorName.Replace("@", "").Replace("(R)", "").Replace("(TM)", "");
+ // Remove parts which don't provide any useful information for user
+ processorName = processorName.Replace("@", "").Replace("(R)", "").Replace("(TM)", "");
- // If we have found physical core(s), we can safely assume we can drop extra info from brand
- if (cpuInfo.PhysicalCoreCount.HasValue && cpuInfo.PhysicalCoreCount.Value > 0)
- processorName = Regex.Replace(processorName, @"(\w+?-Core Processor)", "").Trim();
+ // If we have found physical core(s), we can safely assume we can drop extra info from brand
+ if (cpuInfo.PhysicalCoreCount is > 0)
+ processorName = Regex.Replace(processorName, @"(\w+?-Core Processor)", "").Trim();
- string frequencyString = GetBrandStyledActualFrequency(cpuInfo.NominalFrequency);
- if (includeMaxFrequency && frequencyString != null && !processorName.Contains(frequencyString))
- {
- // show Max only if there's already a frequency name to differentiate the two
- string maxFrequency = processorName.Contains("Hz") ? $"(Max: {frequencyString})" : frequencyString;
- processorName = $"{processorName} {maxFrequency}";
- }
+ string frequencyString = GetBrandStyledNominalFrequency(cpuInfo.NominalFrequency);
+ if (includeMaxFrequency && frequencyString != null && !processorName.Contains(frequencyString))
+ {
+ // show Max only if there's already a frequency name to differentiate the two
+ string maxFrequency = processorName.Contains("Hz") ? $"(Max: {frequencyString})" : frequencyString;
+ processorName = $"{processorName} {maxFrequency}";
+ }
- // Remove double spaces
- processorName = Regex.Replace(processorName.Trim(), @"\s+", " ");
+ // Remove double spaces
+ processorName = Regex.Replace(processorName.Trim(), @"\s+", " ");
- // Add microarchitecture name if known
- string microarchitecture = ParseMicroarchitecture(processorName);
- if (microarchitecture != null)
- processorName = $"{processorName} ({microarchitecture})";
+ // Add microarchitecture name if known
+ string microarchitecture = ParseMicroarchitecture(processorName);
+ if (microarchitecture != null)
+ processorName = $"{processorName} ({microarchitecture})";
- return processorName;
- }
+ return processorName;
+ }
- ///
- /// Presents actual processor's frequency into brand string format
- ///
- ///
- private static string GetBrandStyledActualFrequency(Frequency? frequency)
- {
- if (frequency == null)
- return null;
- return $"{frequency.Value.ToGHz().ToString("N2", DefaultCultureInfo.Instance)}GHz";
- }
+ ///
+ /// Presents actual processor's frequency into brand string format
+ ///
+ ///
+ private static string? GetBrandStyledNominalFrequency(Frequency? frequency)
+ {
+ if (frequency == null)
+ return null;
+ return $"{frequency.Value.ToGHz().ToString("N2", DefaultCultureInfo.Instance)}GHz";
+ }
- ///
- /// Parse a processor name and tries to return a microarchitecture name.
- /// Works only for well-known microarchitectures.
- ///
- private static string? ParseMicroarchitecture(string processorName)
+ ///
+ /// Parse a processor name and tries to return a microarchitecture name.
+ /// Works only for well-known microarchitectures.
+ ///
+ private static string? ParseMicroarchitecture(string processorName)
+ {
+ if (processorName.StartsWith("Intel Core"))
{
- if (processorName.StartsWith("Intel Core"))
+ string model = processorName.Substring("Intel Core".Length).Trim();
+
+ // Core i3/5/7/9
+ if (
+ model.Length > 4 &&
+ model[0] == 'i' &&
+ (model[1] == '3' || model[1] == '5' || model[1] == '7' || model[1] == '9') &&
+ (model[2] == '-' || model[2] == ' '))
{
- string model = processorName.Substring("Intel Core".Length).Trim();
-
- // Core i3/5/7/9
- if (
- model.Length > 4 &&
- model[0] == 'i' &&
- (model[1] == '3' || model[1] == '5' || model[1] == '7' || model[1] == '9') &&
- (model[2] == '-' || model[2] == ' '))
- {
- string modelNumber = model.Substring(3);
- if (modelNumber.StartsWith("CPU"))
- modelNumber = modelNumber.Substring(3).Trim();
- if (modelNumber.Contains("CPU"))
- modelNumber = modelNumber.Substring(0, modelNumber.IndexOf("CPU", StringComparison.Ordinal)).Trim();
- return ParseIntelCoreMicroarchitecture(modelNumber);
- }
+ string modelNumber = model.Substring(3);
+ if (modelNumber.StartsWith("CPU"))
+ modelNumber = modelNumber.Substring(3).Trim();
+ if (modelNumber.Contains("CPU"))
+ modelNumber = modelNumber.Substring(0, modelNumber.IndexOf("CPU", StringComparison.Ordinal)).Trim();
+ return ParseIntelCoreMicroarchitecture(modelNumber);
}
-
- return null;
}
- private static readonly Lazy> KnownMicroarchitectures = new Lazy>(() =>
+ return null;
+ }
+
+ private static readonly Lazy> KnownMicroarchitectures = new Lazy>(() =>
+ {
+ var data = ResourceHelper.LoadResource("BenchmarkDotNet.Environments.microarchitectures.txt").Split('\r', '\n');
+ var dictionary = new Dictionary();
+ string? currentMicroarchitecture = null;
+ foreach (string line in data)
{
- var data = ResourceHelper.LoadResource("BenchmarkDotNet.Environments.microarchitectures.txt").Split('\r', '\n');
- var dictionary = new Dictionary();
- string? currentMicroarchitecture = null;
- foreach (string line in data)
+ if (line.StartsWith("//") || string.IsNullOrWhiteSpace(line))
+ continue;
+ if (line.StartsWith("#"))
{
- if (line.StartsWith("//") || string.IsNullOrWhiteSpace(line))
- continue;
- if (line.StartsWith("#"))
- {
- currentMicroarchitecture = line.Substring(1).Trim();
- continue;
- }
-
- string modelNumber = line.Trim();
- if (dictionary.ContainsKey(modelNumber))
- throw new Exception($"{modelNumber} is defined twice in microarchitectures.txt");
- if (currentMicroarchitecture == null)
- throw new Exception($"{modelNumber} doesn't have defined microarchitecture in microarchitectures.txt");
- dictionary[modelNumber] = currentMicroarchitecture;
+ currentMicroarchitecture = line.Substring(1).Trim();
+ continue;
}
- return dictionary;
- });
+ string modelNumber = line.Trim();
+ if (dictionary.ContainsKey(modelNumber))
+ throw new Exception($"{modelNumber} is defined twice in microarchitectures.txt");
+ if (currentMicroarchitecture == null)
+ throw new Exception($"{modelNumber} doesn't have defined microarchitecture in microarchitectures.txt");
+ dictionary[modelNumber] = currentMicroarchitecture;
+ }
- // see http://www.intel.com/content/www/us/en/processors/processor-numbers.html
- [SuppressMessage("ReSharper", "StringLiteralTypo")]
- internal static string? ParseIntelCoreMicroarchitecture(string modelNumber)
- {
- if (KnownMicroarchitectures.Value.TryGetValue(modelNumber, out string? microarchitecture))
- return microarchitecture;
+ return dictionary;
+ });
+
+ // see http://www.intel.com/content/www/us/en/processors/processor-numbers.html
+ [SuppressMessage("ReSharper", "StringLiteralTypo")]
+ internal static string? ParseIntelCoreMicroarchitecture(string modelNumber)
+ {
+ if (KnownMicroarchitectures.Value.TryGetValue(modelNumber, out string? microarchitecture))
+ return microarchitecture;
- if (modelNumber.Length >= 3 && modelNumber.Substring(0, 3).All(char.IsDigit) &&
- (modelNumber.Length == 3 || !char.IsDigit(modelNumber[3])))
- return "Nehalem";
- if (modelNumber.Length >= 4 && modelNumber.Substring(0, 4).All(char.IsDigit))
+ if (modelNumber.Length >= 3 && modelNumber.Substring(0, 3).All(char.IsDigit) &&
+ (modelNumber.Length == 3 || !char.IsDigit(modelNumber[3])))
+ return "Nehalem";
+ if (modelNumber.Length >= 4 && modelNumber.Substring(0, 4).All(char.IsDigit))
+ {
+ char generation = modelNumber[0];
+ switch (generation)
{
- char generation = modelNumber[0];
- switch (generation)
- {
- case '2':
- return "Sandy Bridge";
- case '3':
- return "Ivy Bridge";
- case '4':
- return "Haswell";
- case '5':
- return "Broadwell";
- case '6':
- return "Skylake";
- case '7':
- return "Kaby Lake";
- case '8':
- return "Coffee Lake";
- default:
- return null;
- }
+ case '2':
+ return "Sandy Bridge";
+ case '3':
+ return "Ivy Bridge";
+ case '4':
+ return "Haswell";
+ case '5':
+ return "Broadwell";
+ case '6':
+ return "Skylake";
+ case '7':
+ return "Kaby Lake";
+ case '8':
+ return "Coffee Lake";
+ default:
+ return null;
}
- return null;
}
+ return null;
}
}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Portability/Cpu/CompositeCpuInfoDetector.cs b/src/BenchmarkDotNet/Portability/Cpu/CompositeCpuInfoDetector.cs
new file mode 100644
index 0000000000..d6919d938f
--- /dev/null
+++ b/src/BenchmarkDotNet/Portability/Cpu/CompositeCpuInfoDetector.cs
@@ -0,0 +1,18 @@
+using System.Linq;
+using BenchmarkDotNet.Extensions;
+using BenchmarkDotNet.Portability.Cpu.Linux;
+using BenchmarkDotNet.Portability.Cpu.macOS;
+using BenchmarkDotNet.Portability.Cpu.Windows;
+
+namespace BenchmarkDotNet.Portability.Cpu;
+
+internal class CompositeCpuInfoDetector(params ICpuInfoDetector[] detectors) : ICpuInfoDetector
+{
+ public bool IsApplicable() => detectors.Any(loader => loader.IsApplicable());
+
+ public CpuInfo? Detect() => detectors
+ .Where(loader => loader.IsApplicable())
+ .Select(loader => loader.Detect())
+ .WhereNotNull()
+ .FirstOrDefault();
+}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Portability/Cpu/CpuInfo.cs b/src/BenchmarkDotNet/Portability/Cpu/CpuInfo.cs
index 8c3227a25d..ee7cf310c7 100644
--- a/src/BenchmarkDotNet/Portability/Cpu/CpuInfo.cs
+++ b/src/BenchmarkDotNet/Portability/Cpu/CpuInfo.cs
@@ -1,37 +1,33 @@
-using Perfolizer.Horology;
+using BenchmarkDotNet.Portability.Cpu.Linux;
+using BenchmarkDotNet.Portability.Cpu.macOS;
+using BenchmarkDotNet.Portability.Cpu.Windows;
+using Perfolizer.Horology;
-namespace BenchmarkDotNet.Portability.Cpu
+namespace BenchmarkDotNet.Portability.Cpu;
+
+public class CpuInfo(
+ string? processorName,
+ int? physicalProcessorCount,
+ int? physicalCoreCount,
+ int? logicalCoreCount,
+ Frequency? nominalFrequency,
+ Frequency? maxFrequency = null)
{
- public class CpuInfo
- {
- public string ProcessorName { get; }
- public int? PhysicalProcessorCount { get; }
- public int? PhysicalCoreCount { get; }
- public int? LogicalCoreCount { get; }
- public Frequency? NominalFrequency { get; }
- public Frequency? MinFrequency { get; }
- public Frequency? MaxFrequency { get; }
+ public static readonly CpuInfo Empty = new (null, null, null, null, null, null);
+ public static CpuInfo FromName(string processorName) => new (processorName, null, null, null, null);
+ public static CpuInfo FromNameAndFrequency(string processorName, Frequency nominalFrequency) => new (processorName, null, null, null, nominalFrequency);
+
+ private static readonly CompositeCpuInfoDetector Detector = new (
+ new WindowsCpuInfoDetector(),
+ new LinuxCpuInfoDetector(),
+ new MacOsCpuInfoDetector());
- internal CpuInfo(string processorName, Frequency? nominalFrequency)
- : this(processorName, null, null, null, nominalFrequency, null, null)
- {
- }
+ public static CpuInfo? DetectCurrent() => Detector.Detect();
- public CpuInfo(string processorName,
- int? physicalProcessorCount,
- int? physicalCoreCount,
- int? logicalCoreCount,
- Frequency? nominalFrequency,
- Frequency? minFrequency,
- Frequency? maxFrequency)
- {
- ProcessorName = processorName;
- PhysicalProcessorCount = physicalProcessorCount;
- PhysicalCoreCount = physicalCoreCount;
- LogicalCoreCount = logicalCoreCount;
- NominalFrequency = nominalFrequency;
- MinFrequency = minFrequency;
- MaxFrequency = maxFrequency;
- }
- }
+ public string? ProcessorName { get; } = processorName;
+ public int? PhysicalProcessorCount { get; } = physicalProcessorCount;
+ public int? PhysicalCoreCount { get; } = physicalCoreCount;
+ public int? LogicalCoreCount { get; } = logicalCoreCount;
+ public Frequency? NominalFrequency { get; } = nominalFrequency ?? maxFrequency;
+ public Frequency? MaxFrequency { get; } = maxFrequency ?? nominalFrequency;
}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Portability/Cpu/CpuInfoFormatter.cs b/src/BenchmarkDotNet/Portability/Cpu/CpuInfoFormatter.cs
index e016ad1a4b..88301271e4 100644
--- a/src/BenchmarkDotNet/Portability/Cpu/CpuInfoFormatter.cs
+++ b/src/BenchmarkDotNet/Portability/Cpu/CpuInfoFormatter.cs
@@ -1,50 +1,56 @@
using System.Collections.Generic;
using BenchmarkDotNet.Environments;
-namespace BenchmarkDotNet.Portability.Cpu
+namespace BenchmarkDotNet.Portability.Cpu;
+
+public static class CpuInfoFormatter
{
- public static class CpuInfoFormatter
+ public static string Format(CpuInfo? cpuInfo)
{
- public static string Format(CpuInfo cpuInfo)
- {
- if (cpuInfo == null)
- {
- return "Unknown processor";
- }
+ if (cpuInfo == null)
+ return "Unknown processor";
- var parts = new List
- {
- ProcessorBrandStringHelper.Prettify(cpuInfo, includeMaxFrequency: true)
- };
+ var parts = new List
+ {
+ ProcessorBrandStringHelper.Prettify(cpuInfo, includeMaxFrequency: true)
+ };
- if (cpuInfo.PhysicalProcessorCount > 0)
- parts.Add($", {cpuInfo.PhysicalProcessorCount} CPU");
+ if (cpuInfo.PhysicalProcessorCount > 0)
+ parts.Add($", {cpuInfo.PhysicalProcessorCount} CPU");
- if (cpuInfo.LogicalCoreCount == 1)
+ switch (cpuInfo.LogicalCoreCount)
+ {
+ case 1:
parts.Add(", 1 logical core");
-
- if (cpuInfo.LogicalCoreCount > 1)
+ break;
+ case > 1:
parts.Add($", {cpuInfo.LogicalCoreCount} logical cores");
+ break;
+ }
- if (cpuInfo.LogicalCoreCount > 0 && cpuInfo.PhysicalCoreCount > 0)
- parts.Add(" and ");
- else if (cpuInfo.PhysicalCoreCount > 0)
- parts.Add(", ");
+ if (cpuInfo.LogicalCoreCount > 0 && cpuInfo.PhysicalCoreCount > 0)
+ parts.Add(" and ");
+ else if (cpuInfo.PhysicalCoreCount > 0)
+ parts.Add(", ");
- if (cpuInfo.PhysicalCoreCount == 1)
+ switch (cpuInfo.PhysicalCoreCount)
+ {
+ case 1:
parts.Add("1 physical core");
- if (cpuInfo.PhysicalCoreCount > 1)
+ break;
+ case > 1:
parts.Add($"{cpuInfo.PhysicalCoreCount} physical cores");
+ break;
+ }
- string result = string.Join("", parts);
- // The line with ProcessorBrandString is one of the longest lines in the summary.
- // When people past in on GitHub, it can be a reason of an ugly horizontal scrollbar.
- // To avoid this, we are trying to minimize this line and use the minimum possible number of characters.
- // Here we are removing the repetitive "cores" word.
- if (result.Contains("logical cores") && result.Contains("physical cores"))
- result = result.Replace("logical cores", "logical");
+ string result = string.Join("", parts);
+ // The line with ProcessorBrandString is one of the longest lines in the summary.
+ // When people past in on GitHub, it can be a reason of an ugly horizontal scrollbar.
+ // To avoid this, we are trying to minimize this line and use the minimum possible number of characters.
+ // Here we are removing the repetitive "cores" word.
+ if (result.Contains("logical cores") && result.Contains("physical cores"))
+ result = result.Replace("logical cores", "logical");
- return result;
- }
+ return result;
}
}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Portability/Cpu/ICpuInfoDetector.cs b/src/BenchmarkDotNet/Portability/Cpu/ICpuInfoDetector.cs
new file mode 100644
index 0000000000..748f9ce186
--- /dev/null
+++ b/src/BenchmarkDotNet/Portability/Cpu/ICpuInfoDetector.cs
@@ -0,0 +1,10 @@
+namespace BenchmarkDotNet.Portability.Cpu;
+
+///
+/// Loads the for the current hardware
+///
+internal interface ICpuInfoDetector
+{
+ bool IsApplicable();
+ CpuInfo? Detect();
+}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Portability/Cpu/Linux/LinuxCpuInfoDetector.cs b/src/BenchmarkDotNet/Portability/Cpu/Linux/LinuxCpuInfoDetector.cs
new file mode 100644
index 0000000000..b88b41c6be
--- /dev/null
+++ b/src/BenchmarkDotNet/Portability/Cpu/Linux/LinuxCpuInfoDetector.cs
@@ -0,0 +1,21 @@
+using BenchmarkDotNet.Helpers;
+
+namespace BenchmarkDotNet.Portability.Cpu.Linux;
+
+///
+/// CPU information from output of the `cat /proc/cpuinfo` and `lscpu` command.
+/// Linux only.
+///
+internal class LinuxCpuInfoDetector : ICpuInfoDetector
+{
+ public bool IsApplicable() => RuntimeInformation.IsLinux();
+
+ public CpuInfo? Detect()
+ {
+ if (!IsApplicable()) return null;
+
+ string cpuInfo = ProcessHelper.RunAndReadOutput("cat", "/proc/cpuinfo") ?? "";
+ string lscpu = ProcessHelper.RunAndReadOutput("/bin/bash", "-c \"lscpu\"");
+ return LinuxCpuInfoParser.Parse(cpuInfo, lscpu);
+ }
+}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Portability/Cpu/Linux/LinuxCpuInfoParser.cs b/src/BenchmarkDotNet/Portability/Cpu/Linux/LinuxCpuInfoParser.cs
new file mode 100644
index 0000000000..4533d60fee
--- /dev/null
+++ b/src/BenchmarkDotNet/Portability/Cpu/Linux/LinuxCpuInfoParser.cs
@@ -0,0 +1,111 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.RegularExpressions;
+using BenchmarkDotNet.Extensions;
+using BenchmarkDotNet.Helpers;
+using Perfolizer.Horology;
+
+namespace BenchmarkDotNet.Portability.Cpu.Linux;
+
+internal static class LinuxCpuInfoParser
+{
+ private static class ProcCpu
+ {
+ internal const string PhysicalId = "physical id";
+ internal const string CpuCores = "cpu cores";
+ internal const string ModelName = "model name";
+ internal const string MaxFrequency = "max freq";
+ }
+
+ private static class Lscpu
+ {
+ internal const string MaxFrequency = "CPU max MHz";
+ internal const string ModelName = "Model name";
+ internal const string CoresPerSocket = "Core(s) per socket";
+ }
+
+ /// Output of `cat /proc/cpuinfo`
+ /// Output of `lscpu`
+ internal static CpuInfo Parse(string? cpuInfo, string? lscpu)
+ {
+ var processorModelNames = new HashSet();
+ var processorsToPhysicalCoreCount = new Dictionary();
+ int logicalCoreCount = 0;
+ Frequency? maxFrequency = null;
+
+ var logicalCores = SectionsHelper.ParseSections(cpuInfo, ':');
+ foreach (var logicalCore in logicalCores)
+ {
+ if (logicalCore.TryGetValue(ProcCpu.PhysicalId, out string physicalId) &&
+ logicalCore.TryGetValue(ProcCpu.CpuCores, out string cpuCoresValue) &&
+ int.TryParse(cpuCoresValue, out int cpuCoreCount) &&
+ cpuCoreCount > 0)
+ processorsToPhysicalCoreCount[physicalId] = cpuCoreCount;
+
+ if (logicalCore.TryGetValue(ProcCpu.ModelName, out string modelName))
+ {
+ processorModelNames.Add(modelName);
+ logicalCoreCount++;
+ }
+
+ if (logicalCore.TryGetValue(ProcCpu.MaxFrequency, out string maxCpuFreqValue) &&
+ Frequency.TryParseMHz(maxCpuFreqValue, out var maxCpuFreq))
+ {
+ maxFrequency = maxCpuFreq;
+ }
+ }
+
+ int? coresPerSocket = null;
+ if (lscpu != null)
+ {
+ var lscpuParts = lscpu.Split('\n')
+ .Where(line => line.Contains(':'))
+ .SelectMany(line => line.Split([':'], 2))
+ .ToList();
+ for (int i = 0; i + 1 < lscpuParts.Count; i += 2)
+ {
+ string name = lscpuParts[i].Trim();
+ string value = lscpuParts[i + 1].Trim();
+
+ if (name.EqualsWithIgnoreCase(Lscpu.MaxFrequency) &&
+ Frequency.TryParseMHz(value.Replace(',', '.'), out var maxFrequencyMHz)) // Example: `CPU max MHz: 3200,0000`
+ maxFrequency = Frequency.FromMHz(maxFrequencyMHz);
+
+ if (name.EqualsWithIgnoreCase(Lscpu.ModelName))
+ processorModelNames.Add(value);
+
+ if (name.EqualsWithIgnoreCase(Lscpu.CoresPerSocket) &&
+ int.TryParse(value, out int coreCount))
+ 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;
+ return new CpuInfo(
+ processorName,
+ physicalProcessorCount,
+ physicalCoreCount,
+ logicalCoreCount > 0 ? logicalCoreCount : null,
+ nominalFrequency,
+ maxFrequency);
+ }
+
+ internal static Frequency? ParseFrequencyFromBrandString(string brandString)
+ {
+ const string pattern = "(\\d.\\d+)GHz";
+ var matches = Regex.Matches(brandString, pattern, RegexOptions.IgnoreCase);
+ 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 null;
+ }
+}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Portability/Cpu/MosCpuInfoProvider.cs b/src/BenchmarkDotNet/Portability/Cpu/MosCpuInfoProvider.cs
deleted file mode 100644
index 2edc7cabcb..0000000000
--- a/src/BenchmarkDotNet/Portability/Cpu/MosCpuInfoProvider.cs
+++ /dev/null
@@ -1,56 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Management;
-using Perfolizer.Horology;
-
-namespace BenchmarkDotNet.Portability.Cpu
-{
- internal static class MosCpuInfoProvider
- {
-#if NET6_0_OR_GREATER
- [System.Runtime.Versioning.SupportedOSPlatform("windows")]
-#endif
- internal static readonly Lazy MosCpuInfo = new Lazy(Load);
-
-#if NET6_0_OR_GREATER
- [System.Runtime.Versioning.SupportedOSPlatform("windows")]
-#endif
- private static CpuInfo Load()
- {
- var processorModelNames = new HashSet();
- uint physicalCoreCount = 0;
- uint logicalCoreCount = 0;
- int processorsCount = 0;
- uint nominalClockSpeed = 0;
- uint maxClockSpeed = 0;
- uint minClockSpeed = 0;
-
-
- using (var mosProcessor = new ManagementObjectSearcher("SELECT * FROM Win32_Processor"))
- {
- foreach (var moProcessor in mosProcessor.Get().Cast())
- {
- string name = moProcessor[WmicCpuInfoKeyNames.Name]?.ToString();
- if (!string.IsNullOrEmpty(name))
- {
- processorModelNames.Add(name);
- processorsCount++;
- physicalCoreCount += (uint) moProcessor[WmicCpuInfoKeyNames.NumberOfCores];
- logicalCoreCount += (uint) moProcessor[WmicCpuInfoKeyNames.NumberOfLogicalProcessors];
- maxClockSpeed = (uint) moProcessor[WmicCpuInfoKeyNames.MaxClockSpeed];
- }
- }
- }
-
- return new CpuInfo(
- processorModelNames.Count > 0 ? string.Join(", ", processorModelNames) : null,
- processorsCount > 0 ? processorsCount : (int?) null,
- physicalCoreCount > 0 ? (int?) physicalCoreCount : null,
- logicalCoreCount > 0 ? (int?) logicalCoreCount : null,
- nominalClockSpeed > 0 && logicalCoreCount > 0 ? Frequency.FromMHz(nominalClockSpeed) : (Frequency?) null,
- minClockSpeed > 0 && logicalCoreCount > 0 ? Frequency.FromMHz(minClockSpeed) : (Frequency?) null,
- maxClockSpeed > 0 && logicalCoreCount > 0 ? Frequency.FromMHz(maxClockSpeed) : (Frequency?) null);
- }
- }
-}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Portability/Cpu/ProcCpuInfoKeyNames.cs b/src/BenchmarkDotNet/Portability/Cpu/ProcCpuInfoKeyNames.cs
deleted file mode 100644
index 60284c5363..0000000000
--- a/src/BenchmarkDotNet/Portability/Cpu/ProcCpuInfoKeyNames.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-namespace BenchmarkDotNet.Portability.Cpu
-{
- internal static class ProcCpuInfoKeyNames
- {
- internal const string PhysicalId = "physical id";
- internal const string CpuCores = "cpu cores";
- internal const string ModelName = "model name";
- internal const string MaxFrequency = "max freq";
- internal const string MinFrequency = "min freq";
- }
-}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Portability/Cpu/ProcCpuInfoParser.cs b/src/BenchmarkDotNet/Portability/Cpu/ProcCpuInfoParser.cs
deleted file mode 100644
index e3d0a14c46..0000000000
--- a/src/BenchmarkDotNet/Portability/Cpu/ProcCpuInfoParser.cs
+++ /dev/null
@@ -1,73 +0,0 @@
-using System.Collections.Generic;
-using System.Linq;
-using System.Text.RegularExpressions;
-using BenchmarkDotNet.Helpers;
-using Perfolizer.Horology;
-
-namespace BenchmarkDotNet.Portability.Cpu
-{
- internal static class ProcCpuInfoParser
- {
- internal static CpuInfo ParseOutput(string? content)
- {
- var logicalCores = SectionsHelper.ParseSections(content, ':');
- var processorModelNames = new HashSet();
- var processorsToPhysicalCoreCount = new Dictionary();
-
- int logicalCoreCount = 0;
- var nominalFrequency = Frequency.Zero;
- var minFrequency = Frequency.Zero;
- var maxFrequency = Frequency.Zero;
-
- foreach (var logicalCore in logicalCores)
- {
- if (logicalCore.TryGetValue(ProcCpuInfoKeyNames.PhysicalId, out string physicalId) &&
- logicalCore.TryGetValue(ProcCpuInfoKeyNames.CpuCores, out string cpuCoresValue) &&
- int.TryParse(cpuCoresValue, out int cpuCoreCount) &&
- cpuCoreCount > 0)
- processorsToPhysicalCoreCount[physicalId] = cpuCoreCount;
-
- if (logicalCore.TryGetValue(ProcCpuInfoKeyNames.ModelName, out string modelName))
- {
- processorModelNames.Add(modelName);
- nominalFrequency = ParseFrequencyFromBrandString(modelName);
- logicalCoreCount++;
- }
-
- if (logicalCore.TryGetValue(ProcCpuInfoKeyNames.MinFrequency, out string minCpuFreqValue)
- && Frequency.TryParseMHz(minCpuFreqValue, out var minCpuFreq))
- {
- minFrequency = minCpuFreq;
- }
-
- if (logicalCore.TryGetValue(ProcCpuInfoKeyNames.MaxFrequency, out string maxCpuFreqValue)
- && Frequency.TryParseMHz(maxCpuFreqValue, out var maxCpuFreq))
- {
- maxFrequency = maxCpuFreq;
- }
- }
-
- return new CpuInfo(
- processorModelNames.Count > 0 ? string.Join(", ", processorModelNames) : null,
- processorsToPhysicalCoreCount.Count > 0 ? processorsToPhysicalCoreCount.Count : (int?) null,
- processorsToPhysicalCoreCount.Count > 0 ? processorsToPhysicalCoreCount.Values.Sum() : (int?) null,
- logicalCoreCount > 0 ? logicalCoreCount : (int?) null,
- nominalFrequency > 0 ? nominalFrequency : (Frequency?) null,
- minFrequency > 0 ? minFrequency : (Frequency?) null,
- maxFrequency > 0 ? maxFrequency : (Frequency?) null);
- }
-
- internal static Frequency ParseFrequencyFromBrandString(string brandString)
- {
- const string pattern = "(\\d.\\d+)GHz";
- var matches = Regex.Matches(brandString, pattern, RegexOptions.IgnoreCase);
- 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 : Frequency.Zero;
- }
-
- return 0d;
- }
- }
-}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Portability/Cpu/ProcCpuInfoProvider.cs b/src/BenchmarkDotNet/Portability/Cpu/ProcCpuInfoProvider.cs
deleted file mode 100644
index 7cadad1ff2..0000000000
--- a/src/BenchmarkDotNet/Portability/Cpu/ProcCpuInfoProvider.cs
+++ /dev/null
@@ -1,75 +0,0 @@
-using System;
-using System.Linq;
-using System.Text;
-using BenchmarkDotNet.Helpers;
-using Perfolizer.Horology;
-
-namespace BenchmarkDotNet.Portability.Cpu
-{
- ///
- /// CPU information from output of the `cat /proc/info` command.
- /// Linux only.
- ///
- internal static class ProcCpuInfoProvider
- {
- internal static readonly Lazy ProcCpuInfo = new (Load);
-
- private static CpuInfo? Load()
- {
- if (RuntimeInformation.IsLinux())
- {
- string content = ProcessHelper.RunAndReadOutput("cat", "/proc/cpuinfo") ?? "";
- string output = GetCpuSpeed() ?? "";
- content += output;
- return ProcCpuInfoParser.ParseOutput(content);
- }
- return null;
- }
-
- private static string? GetCpuSpeed()
- {
- try
- {
- string[]? output = ProcessHelper.RunAndReadOutput("/bin/bash", "-c \"lscpu | grep MHz\"")?
- .Split('\n')
- .SelectMany(x => x.Split(':'))
- .ToArray();
-
- return ParseCpuFrequencies(output);
- }
- catch (Exception)
- {
- return null;
- }
- }
-
- private static string? ParseCpuFrequencies(string[]? input)
- {
- // Example of output we trying to parse:
- //
- // CPU MHz: 949.154
- // CPU max MHz: 3200,0000
- // CPU min MHz: 800,0000
-
- if (input == null)
- return null;
-
- var output = new StringBuilder();
- for (int i = 0; i + 1 < input.Length; i += 2)
- {
- string name = input[i].Trim();
- string value = input[i + 1].Trim();
-
- if (name.EqualsWithIgnoreCase("CPU min MHz"))
- if (Frequency.TryParseMHz(value.Replace(',', '.'), out var minFrequency))
- output.Append($"\n{ProcCpuInfoKeyNames.MinFrequency}\t:{minFrequency.ToMHz()}");
-
- if (name.EqualsWithIgnoreCase("CPU max MHz"))
- if (Frequency.TryParseMHz(value.Replace(',', '.'), out var maxFrequency))
- output.Append($"\n{ProcCpuInfoKeyNames.MaxFrequency}\t:{maxFrequency.ToMHz()}");
- }
-
- return output.ToString();
- }
- }
-}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Portability/Cpu/SysctlCpuInfoParser.cs b/src/BenchmarkDotNet/Portability/Cpu/SysctlCpuInfoParser.cs
deleted file mode 100644
index 7e61bbd3ec..0000000000
--- a/src/BenchmarkDotNet/Portability/Cpu/SysctlCpuInfoParser.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
-using BenchmarkDotNet.Helpers;
-using BenchmarkDotNet.Extensions;
-
-namespace BenchmarkDotNet.Portability.Cpu
-{
- internal static class SysctlCpuInfoParser
- {
- [SuppressMessage("ReSharper", "StringLiteralTypo")]
- internal static CpuInfo ParseOutput(string? content)
- {
- var sysctl = SectionsHelper.ParseSection(content, ':');
- string processorName = sysctl.GetValueOrDefault("machdep.cpu.brand_string");
- var physicalProcessorCount = GetPositiveIntValue(sysctl, "hw.packages");
- var physicalCoreCount = GetPositiveIntValue(sysctl, "hw.physicalcpu");
- var logicalCoreCount = GetPositiveIntValue(sysctl, "hw.logicalcpu");
- var nominalFrequency = GetPositiveLongValue(sysctl, "hw.cpufrequency");
- var minFrequency = GetPositiveLongValue(sysctl, "hw.cpufrequency_min");
- var maxFrequency = GetPositiveLongValue(sysctl, "hw.cpufrequency_max");
- return new CpuInfo(processorName, physicalProcessorCount, physicalCoreCount, logicalCoreCount, nominalFrequency, minFrequency, maxFrequency);
- }
-
- private static int? GetPositiveIntValue(Dictionary sysctl, string keyName)
- {
- if (sysctl.TryGetValue(keyName, out string value) &&
- int.TryParse(value, out int result) &&
- result > 0)
- return result;
- return null;
- }
-
- private static long? GetPositiveLongValue(Dictionary sysctl, string keyName)
- {
- if (sysctl.TryGetValue(keyName, out string value) &&
- long.TryParse(value, out long result) &&
- result > 0)
- return result;
- return null;
- }
- }
-}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Portability/Cpu/SysctlCpuInfoProvider.cs b/src/BenchmarkDotNet/Portability/Cpu/SysctlCpuInfoProvider.cs
deleted file mode 100644
index 32a207c1c8..0000000000
--- a/src/BenchmarkDotNet/Portability/Cpu/SysctlCpuInfoProvider.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using System;
-using BenchmarkDotNet.Helpers;
-
-namespace BenchmarkDotNet.Portability.Cpu
-{
- ///
- /// CPU information from output of the `sysctl -a` command.
- /// MacOSX only.
- ///
- internal static class SysctlCpuInfoProvider
- {
- internal static readonly Lazy SysctlCpuInfo = new Lazy(Load);
-
- private static CpuInfo? Load()
- {
- if (RuntimeInformation.IsMacOS())
- {
- string content = ProcessHelper.RunAndReadOutput("sysctl", "-a");
- return SysctlCpuInfoParser.ParseOutput(content);
- }
- return null;
- }
- }
-}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Portability/Cpu/Windows/MosCpuInfoDetector.cs b/src/BenchmarkDotNet/Portability/Cpu/Windows/MosCpuInfoDetector.cs
new file mode 100644
index 0000000000..5a1c6f2cdb
--- /dev/null
+++ b/src/BenchmarkDotNet/Portability/Cpu/Windows/MosCpuInfoDetector.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Management;
+using Perfolizer.Horology;
+
+namespace BenchmarkDotNet.Portability.Cpu.Windows;
+
+internal class MosCpuInfoDetector : ICpuInfoDetector
+{
+#if NET6_0_OR_GREATER
+ [System.Runtime.Versioning.SupportedOSPlatform("windows")]
+#endif
+ public bool IsApplicable() => RuntimeInformation.IsWindows() &&
+ RuntimeInformation.IsFullFramework &&
+ !RuntimeInformation.IsMono;
+
+#if NET6_0_OR_GREATER
+ [System.Runtime.Versioning.SupportedOSPlatform("windows")]
+#endif
+ public CpuInfo? Detect()
+ {
+ if (!IsApplicable()) return null;
+
+ var processorModelNames = new HashSet();
+ int physicalCoreCount = 0;
+ int logicalCoreCount = 0;
+ int processorsCount = 0;
+ int sumMaxFrequency = 0;
+
+ using (var mosProcessor = new ManagementObjectSearcher("SELECT * FROM Win32_Processor"))
+ {
+ foreach (var moProcessor in mosProcessor.Get().Cast())
+ {
+ string name = moProcessor[WmicCpuInfoKeyNames.Name]?.ToString();
+ if (!string.IsNullOrEmpty(name))
+ {
+ processorModelNames.Add(name);
+ processorsCount++;
+ physicalCoreCount += (int)(uint)moProcessor[WmicCpuInfoKeyNames.NumberOfCores];
+ logicalCoreCount += (int)(uint)moProcessor[WmicCpuInfoKeyNames.NumberOfLogicalProcessors];
+ sumMaxFrequency = (int)(uint)moProcessor[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,
+ GetCount(processorsCount), GetCount(physicalCoreCount), GetCount(logicalCoreCount),
+ maxFrequency, maxFrequency);
+
+ int? GetCount(int count) => count > 0 ? count : null;
+ }
+}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Portability/Cpu/Windows/WindowsCpuInfoDetector.cs b/src/BenchmarkDotNet/Portability/Cpu/Windows/WindowsCpuInfoDetector.cs
new file mode 100644
index 0000000000..9846c8b12a
--- /dev/null
+++ b/src/BenchmarkDotNet/Portability/Cpu/Windows/WindowsCpuInfoDetector.cs
@@ -0,0 +1,3 @@
+namespace BenchmarkDotNet.Portability.Cpu.Windows;
+
+internal class WindowsCpuInfoDetector() : CompositeCpuInfoDetector(new MosCpuInfoDetector(), new WmicCpuInfoDetector());
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Portability/Cpu/Windows/WmicCpuInfoDetector.cs b/src/BenchmarkDotNet/Portability/Cpu/Windows/WmicCpuInfoDetector.cs
new file mode 100644
index 0000000000..6dbf1fad8d
--- /dev/null
+++ b/src/BenchmarkDotNet/Portability/Cpu/Windows/WmicCpuInfoDetector.cs
@@ -0,0 +1,28 @@
+using System.IO;
+using BenchmarkDotNet.Helpers;
+
+namespace BenchmarkDotNet.Portability.Cpu.Windows;
+
+///
+/// CPU information from output of the `wmic cpu get Name, NumberOfCores, NumberOfLogicalProcessors /Format:List` command.
+/// Windows only.
+///
+internal class WmicCpuInfoDetector : ICpuInfoDetector
+{
+ private const string DefaultWmicPath = @"C:\Windows\System32\wbem\WMIC.exe";
+
+ public bool IsApplicable() => RuntimeInformation.IsWindows();
+
+ public CpuInfo? Detect()
+ {
+ if (!IsApplicable()) return null;
+
+ const string argList = $"{WmicCpuInfoKeyNames.Name}, " +
+ $"{WmicCpuInfoKeyNames.NumberOfCores}, " +
+ $"{WmicCpuInfoKeyNames.NumberOfLogicalProcessors}, " +
+ $"{WmicCpuInfoKeyNames.MaxClockSpeed}";
+ string wmicPath = File.Exists(DefaultWmicPath) ? DefaultWmicPath : "wmic";
+ string wmicOutput = ProcessHelper.RunAndReadOutput(wmicPath, $"cpu get {argList} /Format:List");
+ return WmicCpuInfoParser.Parse(wmicOutput);
+ }
+}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Portability/Cpu/Windows/WmicCpuInfoKeyNames.cs b/src/BenchmarkDotNet/Portability/Cpu/Windows/WmicCpuInfoKeyNames.cs
new file mode 100644
index 0000000000..1e08cc0bfb
--- /dev/null
+++ b/src/BenchmarkDotNet/Portability/Cpu/Windows/WmicCpuInfoKeyNames.cs
@@ -0,0 +1,9 @@
+namespace BenchmarkDotNet.Portability.Cpu.Windows;
+
+internal static class WmicCpuInfoKeyNames
+{
+ internal const string NumberOfLogicalProcessors = "NumberOfLogicalProcessors";
+ internal const string NumberOfCores = "NumberOfCores";
+ internal const string Name = "Name";
+ internal const string MaxClockSpeed = "MaxClockSpeed";
+}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Portability/Cpu/Windows/WmicCpuInfoParser.cs b/src/BenchmarkDotNet/Portability/Cpu/Windows/WmicCpuInfoParser.cs
new file mode 100644
index 0000000000..47ede5f0cc
--- /dev/null
+++ b/src/BenchmarkDotNet/Portability/Cpu/Windows/WmicCpuInfoParser.cs
@@ -0,0 +1,60 @@
+using System.Collections.Generic;
+using BenchmarkDotNet.Helpers;
+using Perfolizer.Horology;
+
+namespace BenchmarkDotNet.Portability.Cpu.Windows;
+
+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)
+ {
+ var processorModelNames = new HashSet();
+ int physicalCoreCount = 0;
+ int logicalCoreCount = 0;
+ int processorsCount = 0;
+ var sumMaxFrequency = Frequency.Zero;
+
+ var processors = SectionsHelper.ParseSections(wmicOutput, '=');
+ 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,
+ GetCount(processorsCount), GetCount(physicalCoreCount), GetCount(logicalCoreCount),
+ maxFrequency, maxFrequency);
+
+ int? GetCount(int count) => count > 0 ? count : null;
+ }
+}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Portability/Cpu/WmicCpuInfoKeyNames.cs b/src/BenchmarkDotNet/Portability/Cpu/WmicCpuInfoKeyNames.cs
deleted file mode 100644
index 9dd2209304..0000000000
--- a/src/BenchmarkDotNet/Portability/Cpu/WmicCpuInfoKeyNames.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace BenchmarkDotNet.Portability.Cpu
-{
- internal static class WmicCpuInfoKeyNames
- {
- internal const string NumberOfLogicalProcessors = "NumberOfLogicalProcessors";
- internal const string NumberOfCores = "NumberOfCores";
- internal const string Name = "Name";
- internal const string MaxClockSpeed = "MaxClockSpeed";
- }
-}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Portability/Cpu/WmicCpuInfoParser.cs b/src/BenchmarkDotNet/Portability/Cpu/WmicCpuInfoParser.cs
deleted file mode 100644
index 441b1a8b10..0000000000
--- a/src/BenchmarkDotNet/Portability/Cpu/WmicCpuInfoParser.cs
+++ /dev/null
@@ -1,58 +0,0 @@
-using System.Collections.Generic;
-using BenchmarkDotNet.Helpers;
-using Perfolizer.Horology;
-
-namespace BenchmarkDotNet.Portability.Cpu
-{
- internal static class WmicCpuInfoParser
- {
- internal static CpuInfo ParseOutput(string? content)
- {
- var processors = SectionsHelper.ParseSections(content, '=');
-
- var processorModelNames = new HashSet();
- int physicalCoreCount = 0;
- int logicalCoreCount = 0;
- int processorsCount = 0;
-
- var currentClockSpeed = Frequency.Zero;
- var maxClockSpeed = Frequency.Zero;
- var minClockSpeed = Frequency.Zero;
-
- 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)
- {
- maxClockSpeed += frequency;
- }
- }
-
- return new CpuInfo(
- processorModelNames.Count > 0 ? string.Join(", ", processorModelNames) : null,
- processorsCount > 0 ? processorsCount : (int?) null,
- physicalCoreCount > 0 ? physicalCoreCount : (int?) null,
- logicalCoreCount > 0 ? logicalCoreCount : (int?) null,
- currentClockSpeed > 0 && processorsCount > 0 ? Frequency.FromMHz(currentClockSpeed / processorsCount) : (Frequency?) null,
- minClockSpeed > 0 && processorsCount > 0 ? Frequency.FromMHz(minClockSpeed / processorsCount) : (Frequency?) null,
- maxClockSpeed > 0 && processorsCount > 0 ? Frequency.FromMHz(maxClockSpeed / processorsCount) : (Frequency?) null);
- }
- }
-}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Portability/Cpu/WmicCpuInfoProvider.cs b/src/BenchmarkDotNet/Portability/Cpu/WmicCpuInfoProvider.cs
deleted file mode 100644
index d259200701..0000000000
--- a/src/BenchmarkDotNet/Portability/Cpu/WmicCpuInfoProvider.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using System;
-using System.IO;
-using BenchmarkDotNet.Helpers;
-
-namespace BenchmarkDotNet.Portability.Cpu
-{
- ///
- /// CPU information from output of the `wmic cpu get Name, NumberOfCores, NumberOfLogicalProcessors /Format:List` command.
- /// Windows only.
- ///
- internal static class WmicCpuInfoProvider
- {
- internal static readonly Lazy WmicCpuInfo = new (Load);
-
- private const string DefaultWmicPath = @"C:\Windows\System32\wbem\WMIC.exe";
-
- private static CpuInfo? Load()
- {
- if (RuntimeInformation.IsWindows())
- {
- const string argList = $"{WmicCpuInfoKeyNames.Name}, {WmicCpuInfoKeyNames.NumberOfCores}, " +
- $"{WmicCpuInfoKeyNames.NumberOfLogicalProcessors}, {WmicCpuInfoKeyNames.MaxClockSpeed}";
- string wmicPath = File.Exists(DefaultWmicPath) ? DefaultWmicPath : "wmic";
- string content = ProcessHelper.RunAndReadOutput(wmicPath, $"cpu get {argList} /Format:List");
- return WmicCpuInfoParser.ParseOutput(content);
- }
- return null;
- }
- }
-}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Portability/Cpu/macOS/MacOsCpuInfoDetector.cs b/src/BenchmarkDotNet/Portability/Cpu/macOS/MacOsCpuInfoDetector.cs
new file mode 100644
index 0000000000..40b675b299
--- /dev/null
+++ b/src/BenchmarkDotNet/Portability/Cpu/macOS/MacOsCpuInfoDetector.cs
@@ -0,0 +1,20 @@
+using BenchmarkDotNet.Helpers;
+
+namespace BenchmarkDotNet.Portability.Cpu.macOS;
+
+///
+/// CPU information from output of the `sysctl -a` command.
+/// MacOSX only.
+///
+internal class MacOsCpuInfoDetector : ICpuInfoDetector
+{
+ public bool IsApplicable() => RuntimeInformation.IsMacOS();
+
+ public CpuInfo? Detect()
+ {
+ if (!IsApplicable()) return null;
+
+ string sysctlOutput = ProcessHelper.RunAndReadOutput("sysctl", "-a");
+ return SysctlCpuInfoParser.Parse(sysctlOutput);
+ }
+}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Portability/Cpu/macOS/SysctlCpuInfoParser.cs b/src/BenchmarkDotNet/Portability/Cpu/macOS/SysctlCpuInfoParser.cs
new file mode 100644
index 0000000000..f29afc1c8e
--- /dev/null
+++ b/src/BenchmarkDotNet/Portability/Cpu/macOS/SysctlCpuInfoParser.cs
@@ -0,0 +1,54 @@
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using BenchmarkDotNet.Extensions;
+using BenchmarkDotNet.Helpers;
+
+namespace BenchmarkDotNet.Portability.Cpu.macOS;
+
+internal static class SysctlCpuInfoParser
+{
+ private static class Sysctl
+ {
+ internal const string ProcessorName = "machdep.cpu.brand_string";
+ internal const string PhysicalProcessorCount = "hw.packages";
+ internal const string PhysicalCoreCount = "hw.physicalcpu";
+ internal const string LogicalCoreCount = "hw.logicalcpu";
+ internal const string NominalFrequency = "hw.cpufrequency";
+ internal const string MaxFrequency = "hw.cpufrequency_max";
+ }
+
+ /// Output of `sysctl -a`
+ [SuppressMessage("ReSharper", "StringLiteralTypo")]
+ internal static CpuInfo Parse(string? sysctlOutput)
+ {
+ var sysctl = SectionsHelper.ParseSection(sysctlOutput, ':');
+ string processorName = sysctl.GetValueOrDefault(Sysctl.ProcessorName);
+ int? physicalProcessorCount = GetPositiveIntValue(sysctl, Sysctl.PhysicalProcessorCount);
+ int? physicalCoreCount = GetPositiveIntValue(sysctl, Sysctl.PhysicalCoreCount);
+ int? logicalCoreCount = GetPositiveIntValue(sysctl, Sysctl.LogicalCoreCount);
+ long? nominalFrequency = GetPositiveLongValue(sysctl, Sysctl.NominalFrequency);
+ long? maxFrequency = GetPositiveLongValue(sysctl, Sysctl.MaxFrequency);
+ return new CpuInfo(
+ processorName,
+ physicalProcessorCount, physicalCoreCount, logicalCoreCount,
+ nominalFrequency, maxFrequency);
+ }
+
+ private static int? GetPositiveIntValue(Dictionary sysctl, string keyName)
+ {
+ if (sysctl.TryGetValue(keyName, out string value) &&
+ int.TryParse(value, out int result) &&
+ result > 0)
+ return result;
+ return null;
+ }
+
+ private static long? GetPositiveLongValue(Dictionary sysctl, string keyName)
+ {
+ if (sysctl.TryGetValue(keyName, out string value) &&
+ long.TryParse(value, out long result) &&
+ result > 0)
+ return result;
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/BenchmarkDotNet/Portability/RuntimeInformation.cs b/src/BenchmarkDotNet/Portability/RuntimeInformation.cs
index 6064b24dfa..95a3874d87 100644
--- a/src/BenchmarkDotNet/Portability/RuntimeInformation.cs
+++ b/src/BenchmarkDotNet/Portability/RuntimeInformation.cs
@@ -12,6 +12,9 @@
using BenchmarkDotNet.Extensions;
using BenchmarkDotNet.Helpers;
using BenchmarkDotNet.Portability.Cpu;
+using BenchmarkDotNet.Portability.Cpu.Linux;
+using BenchmarkDotNet.Portability.Cpu.macOS;
+using BenchmarkDotNet.Portability.Cpu.Windows;
using JetBrains.Annotations;
using Microsoft.Win32;
using static System.Runtime.InteropServices.RuntimeInformation;
@@ -238,19 +241,8 @@ private static bool IsUnderWsl()
return null;
}
- internal static CpuInfo GetCpuInfo()
- {
- if (IsWindows() && IsFullFramework && !IsMono)
- return MosCpuInfoProvider.MosCpuInfo.Value;
- if (IsWindows())
- return WmicCpuInfoProvider.WmicCpuInfo.Value;
- if (IsLinux())
- return ProcCpuInfoProvider.ProcCpuInfo.Value;
- if (IsMacOS())
- return SysctlCpuInfoProvider.SysctlCpuInfo.Value;
-
- return null;
- }
+ private static readonly Lazy LazyCpuInfo = new (CpuInfo.DetectCurrent);
+ internal static CpuInfo? GetCpuInfo() => LazyCpuInfo.Value;
internal static string GetRuntimeVersion()
{
diff --git a/tests/BenchmarkDotNet.IntegrationTests.ManualRunning/ExpectedBenchmarkResultsTests.cs b/tests/BenchmarkDotNet.IntegrationTests.ManualRunning/ExpectedBenchmarkResultsTests.cs
index 7f950136fb..b1309efdf0 100644
--- a/tests/BenchmarkDotNet.IntegrationTests.ManualRunning/ExpectedBenchmarkResultsTests.cs
+++ b/tests/BenchmarkDotNet.IntegrationTests.ManualRunning/ExpectedBenchmarkResultsTests.cs
@@ -110,7 +110,7 @@ private void AssertZeroResults(Type benchmarkType, IConfig config)
.AddDiagnoser(new MemoryDiagnoser(new MemoryDiagnoserConfig(false)))
);
- var cpuResolution = RuntimeInformation.GetCpuInfo().MaxFrequency?.ToResolution() ?? FallbackCpuResolutionValue;
+ var cpuResolution = RuntimeInformation.GetCpuInfo()?.MaxFrequency?.ToResolution() ?? FallbackCpuResolutionValue;
var threshold = new NumberValue(cpuResolution.Nanoseconds).ToThreshold();
foreach (var report in summary.Reports)
diff --git a/tests/BenchmarkDotNet.Tests/Builders/HostEnvironmentInfoBuilder.cs b/tests/BenchmarkDotNet.Tests/Builders/HostEnvironmentInfoBuilder.cs
index d22256f0a9..aadebcab6d 100644
--- a/tests/BenchmarkDotNet.Tests/Builders/HostEnvironmentInfoBuilder.cs
+++ b/tests/BenchmarkDotNet.Tests/Builders/HostEnvironmentInfoBuilder.cs
@@ -23,13 +23,13 @@ public class HostEnvironmentInfoBuilder
private string osVersion = "Microsoft Windows NT 10.0.x.mock";
private string runtimeVersion = "Clr 4.0.x.mock";
- private CpuInfo cpuInfo = new CpuInfo("MockIntel(R) Core(TM) i7-6700HQ CPU 2.60GHz",
- physicalProcessorCount: 1,
- physicalCoreCount: 4,
- logicalCoreCount: 8,
- nominalFrequency: Frequency.FromMHz(3100),
- maxFrequency: Frequency.FromMHz(3100),
- minFrequency: Frequency.FromMHz(3100));
+ private CpuInfo cpuInfo =
+ new ("MockIntel(R) Core(TM) i7-6700HQ CPU 2.60GHz",
+ physicalProcessorCount: 1,
+ physicalCoreCount: 4,
+ logicalCoreCount: 8,
+ nominalFrequency: Frequency.FromMHz(3100),
+ maxFrequency: Frequency.FromMHz(3100));
private VirtualMachineHypervisor? virtualMachineHypervisor = HyperV.Default;
diff --git a/tests/BenchmarkDotNet.Tests/Environments/ProcessorBrandStringTests.cs b/tests/BenchmarkDotNet.Tests/Environments/ProcessorBrandStringTests.cs
index 0bd9fbf608..127c96ca1d 100644
--- a/tests/BenchmarkDotNet.Tests/Environments/ProcessorBrandStringTests.cs
+++ b/tests/BenchmarkDotNet.Tests/Environments/ProcessorBrandStringTests.cs
@@ -19,8 +19,11 @@ public class ProcessorBrandStringTests
[InlineData("Intel(R) Core(TM) i7-7700 CPU @ 3.60GHz", "Intel Core i7-7700 CPU 3.60GHz (Kaby Lake)")]
[InlineData("Intel(R) Core(TM) i7-8650U CPU @ 1.90GHz ", "Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R)")]
[InlineData("Intel(R) Core(TM) i7-8700K CPU @ 3.70GHz", "Intel Core i7-8700K CPU 3.70GHz (Coffee Lake)")]
- public void IntelCoreIsPrettified(string originalName, string prettifiedName) =>
- Assert.Equal(prettifiedName, ProcessorBrandStringHelper.Prettify(new CpuInfo(originalName, nominalFrequency: null)));
+ public void IntelCoreIsPrettified(string originalName, string prettifiedName)
+ {
+ var actual = CpuInfo.FromName(originalName);
+ Assert.Equal(prettifiedName, ProcessorBrandStringHelper.Prettify(actual));
+ }
[Theory]
[InlineData("Intel(R) Pentium(TM) G4560 CPU @ 3.50GHz", "Intel Pentium G4560 CPU 3.50GHz (Max: 3.70GHz)", 3.7)]
@@ -31,7 +34,8 @@ public void IntelCoreIsPrettified(string originalName, string prettifiedName) =>
[InlineData("Intel(R) Core(TM) i7-5775R CPU @ 3.30GHz", "Intel Core i7-5775R CPU 3.30GHz (Max: 3.40GHz) (Broadwell)", 3.4)]
public void CoreIsPrettifiedWithDiffFrequencies(string originalName, string prettifiedName, double actualFrequency)
{
- Assert.Equal(prettifiedName, ProcessorBrandStringHelper.Prettify(new CpuInfo(originalName, nominalFrequency: Frequency.FromGHz(actualFrequency)), includeMaxFrequency: true));
+ var actual = CpuInfo.FromNameAndFrequency(originalName, Frequency.FromGHz(actualFrequency));
+ Assert.Equal(prettifiedName, ProcessorBrandStringHelper.Prettify(actual, includeMaxFrequency: true));
}
[Theory]
@@ -45,7 +49,6 @@ public void AmdIsPrettifiedWithDiffFrequencies(string originalName, string prett
physicalCoreCount,
logicalCoreCount,
Frequency.FromGHz(actualFrequency),
- minFrequency: null,
maxFrequency: null);
Assert.Equal(prettifiedName, ProcessorBrandStringHelper.Prettify(cpuInfo, includeMaxFrequency: true));
@@ -65,7 +68,7 @@ public void IntelCoreMicroarchitecture(string modelNumber, string microarchitect
[InlineData(null, "Unknown processor")]
public void UnknownProcessorDoesNotThrow(string? originalName, string prettifiedName)
{
- var cpuInfo = new CpuInfo(originalName, nominalFrequency: null);
+ var cpuInfo = new CpuInfo(originalName, null, null, null, null);
Assert.Equal(prettifiedName, ProcessorBrandStringHelper.Prettify(cpuInfo, includeMaxFrequency: true));
}
diff --git a/tests/BenchmarkDotNet.Tests/Portability/Cpu/CpuInfoFormatterTests.cs b/tests/BenchmarkDotNet.Tests/Portability/Cpu/CpuInfoFormatterTests.cs
index 83d14685a4..1fd7b14545 100644
--- a/tests/BenchmarkDotNet.Tests/Portability/Cpu/CpuInfoFormatterTests.cs
+++ b/tests/BenchmarkDotNet.Tests/Portability/Cpu/CpuInfoFormatterTests.cs
@@ -5,27 +5,26 @@
using VerifyXunit;
using Xunit;
-namespace BenchmarkDotNet.Tests.Portability.Cpu
+namespace BenchmarkDotNet.Tests.Portability.Cpu;
+
+[Collection("VerifyTests")]
+[UsesVerify]
+public class CpuInfoFormatterTests
{
- [Collection("VerifyTests")]
- [UsesVerify]
- public class CpuInfoFormatterTests
+ [Fact]
+ public Task FormatTest()
{
- [Fact]
- public Task FormatTest()
+ var captions = new StringBuilder();
+ foreach (string? processorName in new[] { null, "", "Intel" })
+ foreach (int? physicalProcessorCount in new int?[] { null, 0, 1, 2 })
+ foreach (int? physicalCoreCount in new int?[] { null, 0, 1, 2 })
+ foreach (int? logicalCoreCount in new int?[] { null, 0, 1, 2 })
{
- var captions = new StringBuilder();
- foreach (var processorName in new[] { null, "", "Intel" })
- foreach (var physicalProcessorCount in new int?[] { null, 0, 1, 2 })
- foreach (var physicalCoreCount in new int?[] { null, 0, 1, 2 })
- foreach (var logicalCoreCount in new int?[] { null, 0, 1, 2 })
- {
- var mockCpuInfo = new CpuInfo(processorName, physicalProcessorCount, physicalCoreCount, logicalCoreCount, null, null, null);
- captions.AppendLine(CpuInfoFormatter.Format(mockCpuInfo));
- }
-
- var settings = VerifySettingsFactory.Create();
- return Verifier.Verify(captions.ToString(), settings);
+ var mockCpuInfo = new CpuInfo(processorName, physicalProcessorCount, physicalCoreCount, logicalCoreCount, null, null);
+ captions.AppendLine(CpuInfoFormatter.Format(mockCpuInfo));
}
+
+ var settings = VerifySettingsFactory.Create();
+ return Verifier.Verify(captions.ToString(), settings);
}
-}
+}
\ No newline at end of file
diff --git a/tests/BenchmarkDotNet.Tests/Portability/Cpu/LinuxCpuInfoParserTests.cs b/tests/BenchmarkDotNet.Tests/Portability/Cpu/LinuxCpuInfoParserTests.cs
new file mode 100644
index 0000000000..a3b6f2b3ce
--- /dev/null
+++ b/tests/BenchmarkDotNet.Tests/Portability/Cpu/LinuxCpuInfoParserTests.cs
@@ -0,0 +1,111 @@
+using BenchmarkDotNet.Portability.Cpu;
+using BenchmarkDotNet.Portability.Cpu.Linux;
+using Xunit;
+using Xunit.Abstractions;
+using static Perfolizer.Horology.Frequency;
+
+namespace BenchmarkDotNet.Tests.Portability.Cpu;
+
+// ReSharper disable StringLiteralTypo
+public class LinuxCpuInfoParserTests(ITestOutputHelper output)
+{
+ private ITestOutputHelper Output { get; } = output;
+
+ [Fact]
+ public void EmptyTest()
+ {
+ var actual = LinuxCpuInfoParser.Parse("", "");
+ var expected = CpuInfo.Empty;
+ Output.AssertEqual(expected, actual);
+ }
+
+ [Fact]
+ public void MalformedTest()
+ {
+ var actual = LinuxCpuInfoParser.Parse("malformedkey: malformedvalue\n\nmalformedkey2: malformedvalue2", null);
+ var expected = CpuInfo.Empty;
+ Output.AssertEqual(expected, actual);
+ }
+
+ [Fact]
+ public void TwoProcessorWithDifferentCoresCountTest()
+ {
+ string cpuInfo = TestHelper.ReadTestFile("ProcCpuInfoProcessorWithDifferentCoresCount.txt");
+ var actual = LinuxCpuInfoParser.Parse(cpuInfo, null);
+ var expected = new CpuInfo(
+ "Unknown processor with 2 cores and hyper threading, Unknown processor with 4 cores",
+ 2, 6, 8, 2.5 * GHz, 2.5 * GHz);
+ Output.AssertEqual(expected, actual);
+ }
+
+
+ [Fact]
+ public void RealOneProcessorTwoCoresTest()
+ {
+ string cpuInfo = TestHelper.ReadTestFile("ProcCpuInfoRealOneProcessorTwoCores.txt");
+ var actual = LinuxCpuInfoParser.Parse(cpuInfo, null);
+ var expected = new CpuInfo(
+ "Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz",
+ 1, 2, 4, 2.3 * GHz, 2.3 * GHz);
+ Output.AssertEqual(expected, actual);
+ }
+
+ [Fact]
+ public void RealOneProcessorFourCoresTest()
+ {
+ string cpuInfo = TestHelper.ReadTestFile("ProcCpuInfoRealOneProcessorFourCores.txt");
+ var actual = LinuxCpuInfoParser.Parse(cpuInfo, null);
+ var expected = new CpuInfo(
+ "Intel(R) Core(TM) i7-4710MQ CPU @ 2.50GHz",
+ 1, 4, 8, 2.5 * GHz, 2.5 * GHz);
+ Output.AssertEqual(expected, actual);
+ }
+
+ // https://github.com/dotnet/BenchmarkDotNet/issues/2577
+ [Fact]
+ public void Issue2577Test()
+ {
+ const string cpuInfo =
+ """
+ processor : 0
+ BogoMIPS : 50.00
+ Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm lrcpc dcpop asimddp
+ CPU implementer : 0x41
+ CPU architecture: 8
+ CPU variant : 0x3
+ CPU part : 0xd0c
+ CPU revision : 1
+ """;
+ const string lscpu =
+ """
+ Architecture: aarch64
+ CPU op-mode(s): 32-bit, 64-bit
+ Byte Order: Little Endian
+ CPU(s): 16
+ On-line CPU(s) list: 0-15
+ Vendor ID: ARM
+ Model name: Neoverse-N1
+ Model: 1
+ Thread(s) per core: 1
+ Core(s) per socket: 16
+ Socket(s): 1
+ Stepping: r3p1
+ BogoMIPS: 50.00
+ Flags: fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm lrcpc dcpop asimddp
+ """;
+ var actual = LinuxCpuInfoParser.Parse(cpuInfo, lscpu);
+ var expected = new CpuInfo("Neoverse-N1", null, 16, null, null, null);
+ Output.AssertEqual(expected, actual);
+ }
+
+ [Theory]
+ [InlineData("Intel(R) Core(TM) i7-4710MQ CPU @ 2.50GHz", 2.50)]
+ [InlineData("Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz", 2.30)]
+ [InlineData("Unknown processor with 2 cores and hyper threading, Unknown processor with 4 cores", 0)]
+ [InlineData("Intel(R) Core(TM) i5-2500 CPU @ 3.30GHz", 3.30)]
+ public void ParseFrequencyFromBrandStringTests(string brandString, double expectedGHz)
+ {
+ var frequency = LinuxCpuInfoParser.ParseFrequencyFromBrandString(brandString) ?? Zero;
+ Assert.Equal(FromGHz(expectedGHz), frequency);
+ }
+}
\ No newline at end of file
diff --git a/tests/BenchmarkDotNet.Tests/Portability/Cpu/ProcCpuInfoParserTests.cs b/tests/BenchmarkDotNet.Tests/Portability/Cpu/ProcCpuInfoParserTests.cs
deleted file mode 100644
index 6d2faa423e..0000000000
--- a/tests/BenchmarkDotNet.Tests/Portability/Cpu/ProcCpuInfoParserTests.cs
+++ /dev/null
@@ -1,90 +0,0 @@
-using BenchmarkDotNet.Portability.Cpu;
-using Perfolizer.Horology;
-using Xunit;
-
-namespace BenchmarkDotNet.Tests.Portability.Cpu
-{
- public class ProcCpuInfoParserTests
- {
- [Fact]
- public void EmptyTest()
- {
- var parser = ProcCpuInfoParser.ParseOutput(string.Empty);
- Assert.Null(parser.ProcessorName);
- Assert.Null(parser.PhysicalProcessorCount);
- Assert.Null(parser.PhysicalCoreCount);
- Assert.Null(parser.LogicalCoreCount);
- Assert.Null(parser.NominalFrequency);
-
- Assert.Null(parser.MaxFrequency);
- Assert.Null(parser.MinFrequency);
- }
-
- [Fact]
- public void MalformedTest()
- {
- var parser = ProcCpuInfoParser.ParseOutput("malformedkey: malformedvalue\n\nmalformedkey2: malformedvalue2");
- Assert.Null(parser.ProcessorName);
- Assert.Null(parser.PhysicalProcessorCount);
- Assert.Null(parser.PhysicalCoreCount);
- Assert.Null(parser.LogicalCoreCount);
- Assert.Null(parser.NominalFrequency);
- Assert.Null(parser.MaxFrequency);
- Assert.Null(parser.MinFrequency);
- }
-
- [Fact]
- public void TwoProcessorWithDifferentCoresCountTest()
- {
- string cpuInfo = TestHelper.ReadTestFile("ProcCpuInfoProcessorWithDifferentCoresCount.txt");
- var parser = ProcCpuInfoParser.ParseOutput(cpuInfo);
- Assert.Equal("Unknown processor with 2 cores and hyper threading, Unknown processor with 4 cores", parser.ProcessorName);
- Assert.Equal(2, parser.PhysicalProcessorCount);
- Assert.Equal(6, parser.PhysicalCoreCount);
- Assert.Equal(8, parser.LogicalCoreCount);
- Assert.Null(parser.NominalFrequency);
- Assert.Equal(0.8 * Frequency.GHz, parser.MinFrequency);
- Assert.Equal(2.5 * Frequency.GHz, parser.MaxFrequency);
- }
-
-
- [Fact]
- public void RealOneProcessorTwoCoresTest()
- {
- string cpuInfo = TestHelper.ReadTestFile("ProcCpuInfoRealOneProcessorTwoCores.txt");
- var parser = ProcCpuInfoParser.ParseOutput(cpuInfo);
- Assert.Equal("Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz", parser.ProcessorName);
- Assert.Equal(1, parser.PhysicalProcessorCount);
- Assert.Equal(2, parser.PhysicalCoreCount);
- Assert.Equal(4, parser.LogicalCoreCount);
- Assert.Equal(2.3 * Frequency.GHz, parser.NominalFrequency);
- Assert.Equal(0.8 * Frequency.GHz, parser.MinFrequency);
- Assert.Equal(2.3 * Frequency.GHz, parser.MaxFrequency);
- }
-
- [Fact]
- public void RealOneProcessorFourCoresTest()
- {
- string cpuInfo = TestHelper.ReadTestFile("ProcCpuInfoRealOneProcessorFourCores.txt");
- var parser = ProcCpuInfoParser.ParseOutput(cpuInfo);
- Assert.Equal("Intel(R) Core(TM) i7-4710MQ CPU @ 2.50GHz", parser.ProcessorName);
- Assert.Equal(1, parser.PhysicalProcessorCount);
- Assert.Equal(4, parser.PhysicalCoreCount);
- Assert.Equal(8, parser.LogicalCoreCount);
- Assert.Equal(2.5 * Frequency.GHz, parser.NominalFrequency);
- Assert.Equal(0.8 * Frequency.GHz, parser.MinFrequency);
- Assert.Equal(2.5 * Frequency.GHz, parser.MaxFrequency);
- }
-
- [Theory]
- [InlineData("Intel(R) Core(TM) i7-4710MQ CPU @ 2.50GHz", 2.50)]
- [InlineData("Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz", 2.30)]
- [InlineData("Unknown processor with 2 cores and hyper threading, Unknown processor with 4 cores", 0)]
- [InlineData("Intel(R) Core(TM) i5-2500 CPU @ 3.30GHz", 3.30)]
- public void ParseFrequencyFromBrandStringTests(string brandString, double expectedGHz)
- {
- var frequency = ProcCpuInfoParser.ParseFrequencyFromBrandString(brandString);
- Assert.Equal(Frequency.FromGHz(expectedGHz), frequency);
- }
- }
-}
\ No newline at end of file
diff --git a/tests/BenchmarkDotNet.Tests/Portability/Cpu/SysctlCpuInfoParserTests.cs b/tests/BenchmarkDotNet.Tests/Portability/Cpu/SysctlCpuInfoParserTests.cs
index 5d2ba82520..2aca9acb89 100644
--- a/tests/BenchmarkDotNet.Tests/Portability/Cpu/SysctlCpuInfoParserTests.cs
+++ b/tests/BenchmarkDotNet.Tests/Portability/Cpu/SysctlCpuInfoParserTests.cs
@@ -1,43 +1,40 @@
using BenchmarkDotNet.Portability.Cpu;
-using Perfolizer.Horology;
+using BenchmarkDotNet.Portability.Cpu.macOS;
using Xunit;
+using Xunit.Abstractions;
+using static Perfolizer.Horology.Frequency;
-namespace BenchmarkDotNet.Tests.Portability.Cpu
+namespace BenchmarkDotNet.Tests.Portability.Cpu;
+
+// ReSharper disable StringLiteralTypo
+public class SysctlCpuInfoParserTests(ITestOutputHelper output)
{
- public class SysctlCpuInfoParserTests
+ private ITestOutputHelper Output { get; } = output;
+
+ [Fact]
+ public void EmptyTest()
{
- [Fact]
- public void EmptyTest()
- {
- var parser = SysctlCpuInfoParser.ParseOutput(string.Empty);
- Assert.Null(parser.ProcessorName);
- Assert.Null(parser.PhysicalProcessorCount);
- Assert.Null(parser.PhysicalCoreCount);
- Assert.Null(parser.LogicalCoreCount);
- Assert.Null(parser.NominalFrequency);
- }
+ var actual = SysctlCpuInfoParser.Parse(string.Empty);
+ var expected = CpuInfo.Empty;
+ Output.AssertEqual(expected, actual);
+ }
- [Fact]
- public void MalformedTest()
- {
- var parser = SysctlCpuInfoParser.ParseOutput("malformedkey=malformedvalue\n\nmalformedkey2=malformedvalue2");
- Assert.Null(parser.ProcessorName);
- Assert.Null(parser.PhysicalProcessorCount);
- Assert.Null(parser.PhysicalCoreCount);
- Assert.Null(parser.LogicalCoreCount);
- Assert.Null(parser.NominalFrequency);
- }
+ [Fact]
+ public void MalformedTest()
+ {
+ var actual = SysctlCpuInfoParser.Parse("malformedkey=malformedvalue\n\nmalformedkey2=malformedvalue2");
+ var expected = CpuInfo.Empty;
+ Output.AssertEqual(expected, actual);
+ }
- [Fact]
- public void RealOneProcessorFourCoresTest()
- {
- string cpuInfo = TestHelper.ReadTestFile("SysctlRealOneProcessorFourCores.txt");
- var parser = SysctlCpuInfoParser.ParseOutput(cpuInfo);
- Assert.Equal("Intel(R) Core(TM) i7-4770HQ CPU @ 2.20GHz", parser.ProcessorName);
- Assert.Equal(1, parser.PhysicalProcessorCount);
- Assert.Equal(4, parser.PhysicalCoreCount);
- Assert.Equal(8, parser.LogicalCoreCount);
- Assert.Equal(2200 * Frequency.MHz, parser.NominalFrequency);
- }
+ [Fact]
+ public void RealOneProcessorFourCoresTest()
+ {
+ string cpuInfo = TestHelper.ReadTestFile("SysctlRealOneProcessorFourCores.txt");
+ var actual = SysctlCpuInfoParser.Parse(cpuInfo);
+ var expected = new CpuInfo(
+ "Intel(R) Core(TM) i7-4770HQ CPU @ 2.20GHz",
+ 1, 4, 8, 2200 * MHz, 2200 * MHz);
+ Output.AssertEqual(expected, actual);
}
}
\ No newline at end of file
diff --git a/tests/BenchmarkDotNet.Tests/Portability/Cpu/TestHelper.cs b/tests/BenchmarkDotNet.Tests/Portability/Cpu/TestHelper.cs
index e4af5025c0..d95a8b5dfb 100644
--- a/tests/BenchmarkDotNet.Tests/Portability/Cpu/TestHelper.cs
+++ b/tests/BenchmarkDotNet.Tests/Portability/Cpu/TestHelper.cs
@@ -1,20 +1,37 @@
using System.IO;
using System.Reflection;
+using BenchmarkDotNet.Portability.Cpu;
+using JetBrains.Annotations;
+using Xunit;
+using Xunit.Abstractions;
-namespace BenchmarkDotNet.Tests.Portability.Cpu
+namespace BenchmarkDotNet.Tests.Portability.Cpu;
+
+public static class TestHelper
{
- public static class TestHelper
+ public static string ReadTestFile(string name)
{
- public static string ReadTestFile(string name)
- {
- var assembly = typeof(TestHelper).GetTypeInfo().Assembly;
- string resourceName = $"{typeof(TestHelper).Namespace}.TestFiles.{name}";
+ var assembly = typeof(TestHelper).GetTypeInfo().Assembly;
+ string resourceName = $"{typeof(TestHelper).Namespace}.TestFiles.{name}";
+
+ using var stream = assembly.GetManifestResourceStream(resourceName);
+ if (stream == null)
+ throw new FileNotFoundException($"Resource {resourceName} not found in {assembly.FullName}");
- using (var stream = assembly.GetManifestResourceStream(resourceName))
- using (var reader = new StreamReader(stream))
- {
- return reader.ReadToEnd();
- }
- }
+ using var reader = new StreamReader(stream);
+ return reader.ReadToEnd();
+ }
+
+ [AssertionMethod]
+ public static void AssertEqual(this ITestOutputHelper output, CpuInfo expected, CpuInfo actual)
+ {
+ output.WriteLine($"Expected : {CpuInfoFormatter.Format(expected)}");
+ output.WriteLine($"Actual : {CpuInfoFormatter.Format(actual)}");
+ Assert.Equal(expected.ProcessorName, actual.ProcessorName);
+ Assert.Equal(expected.PhysicalProcessorCount, actual.PhysicalProcessorCount);
+ Assert.Equal(expected.PhysicalCoreCount, actual.PhysicalCoreCount);
+ Assert.Equal(expected.LogicalCoreCount, actual.LogicalCoreCount);
+ Assert.Equal(expected.NominalFrequency, actual.NominalFrequency);
+ Assert.Equal(expected.MaxFrequency, actual.MaxFrequency);
}
}
\ No newline at end of file
diff --git a/tests/BenchmarkDotNet.Tests/Portability/Cpu/WmicCpuInfoParserTests.cs b/tests/BenchmarkDotNet.Tests/Portability/Cpu/WmicCpuInfoParserTests.cs
index b1faa4b521..98f170ed03 100644
--- a/tests/BenchmarkDotNet.Tests/Portability/Cpu/WmicCpuInfoParserTests.cs
+++ b/tests/BenchmarkDotNet.Tests/Portability/Cpu/WmicCpuInfoParserTests.cs
@@ -1,37 +1,36 @@
using BenchmarkDotNet.Portability.Cpu;
-using Perfolizer.Horology;
+using BenchmarkDotNet.Portability.Cpu.Windows;
using Xunit;
+using Xunit.Abstractions;
+using static Perfolizer.Horology.Frequency;
-namespace BenchmarkDotNet.Tests.Portability.Cpu
+namespace BenchmarkDotNet.Tests.Portability.Cpu;
+
+// ReSharper disable StringLiteralTypo
+public class WmicCpuInfoParserTests(ITestOutputHelper output)
{
- public class WmicCpuInfoParserTests
+ private ITestOutputHelper Output { get; } = output;
+
+ [Fact]
+ public void EmptyTest()
{
- [Fact]
- public void EmptyTest()
- {
- var parser = WmicCpuInfoParser.ParseOutput(string.Empty);
- Assert.Null(parser.ProcessorName);
- Assert.Null(parser.PhysicalProcessorCount);
- Assert.Null(parser.PhysicalCoreCount);
- Assert.Null(parser.LogicalCoreCount);
- Assert.Null(parser.NominalFrequency);
- }
+ var actual = WmicCpuInfoParser.Parse(string.Empty);
+ var expected = CpuInfo.Empty;
+ Output.AssertEqual(expected, actual);
+ }
- [Fact]
- public void MalformedTest()
- {
- var parser = WmicCpuInfoParser.ParseOutput("malformedkey=malformedvalue\n\nmalformedkey2=malformedvalue2");
- Assert.Null(parser.ProcessorName);
- Assert.Null(parser.PhysicalProcessorCount);
- Assert.Null(parser.PhysicalCoreCount);
- Assert.Null(parser.LogicalCoreCount);
- Assert.Null(parser.NominalFrequency);
- }
+ [Fact]
+ public void MalformedTest()
+ {
+ var actual = WmicCpuInfoParser.Parse("malformedkey=malformedvalue\n\nmalformedkey2=malformedvalue2");
+ var expected = CpuInfo.Empty;
+ Output.AssertEqual(expected, actual);
+ }
- [Fact]
- public void RealTwoProcessorEightCoresTest()
- {
- const string cpuInfo = @"
+ [Fact]
+ public void RealTwoProcessorEightCoresTest()
+ {
+ const string cpuInfo = @"
MaxClockSpeed=2400
Name=Intel(R) Xeon(R) CPU E5-2630 v3 @ 2.40GHz
@@ -45,45 +44,43 @@ public void RealTwoProcessorEightCoresTest()
NumberOfLogicalProcessors=16
";
- var parser = WmicCpuInfoParser.ParseOutput(cpuInfo);
- Assert.Equal("Intel(R) Xeon(R) CPU E5-2630 v3 @ 2.40GHz", parser.ProcessorName);
- Assert.Equal(2, parser.PhysicalProcessorCount);
- Assert.Equal(16, parser.PhysicalCoreCount);
- Assert.Equal(32, parser.LogicalCoreCount);
- Assert.Equal(2400 * Frequency.MHz, parser.MaxFrequency);
- }
+ var actual = WmicCpuInfoParser.Parse(cpuInfo);
+ var expected = new CpuInfo(
+ "Intel(R) Xeon(R) CPU E5-2630 v3 @ 2.40GHz",
+ 2, 16, 32, 2400 * MHz, 2400 * MHz);
+ 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";
- var parser = WmicCpuInfoParser.ParseOutput(cpuInfo);
- Assert.Equal("Intel(R) Xeon(R) CPU E5-2687W 0 @ 3.10GHz", parser.ProcessorName);
- Assert.Equal(2, parser.PhysicalProcessorCount);
- Assert.Equal(16, parser.PhysicalCoreCount);
- Assert.Equal(32, parser.LogicalCoreCount);
- Assert.Equal(3111 * Frequency.MHz, parser.MaxFrequency);
- }
+ [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";
+ var actual = WmicCpuInfoParser.Parse(cpuInfo);
+ var expected = new CpuInfo(
+ "Intel(R) Xeon(R) CPU E5-2687W 0 @ 3.10GHz",
+ 2, 16, 32, 3111 * MHz, 3111 * MHz);
+ Output.AssertEqual(expected, actual);
+ }
- [Fact]
- public void RealOneProcessorFourCoresTest()
- {
- const string cpuInfo = @"
+ [Fact]
+ public void RealOneProcessorFourCoresTest()
+ {
+ const string cpuInfo = @"
MaxClockSpeed=2500
Name=Intel(R) Core(TM) i7-4710MQ CPU @ 2.50GHz
@@ -92,12 +89,10 @@ public void RealOneProcessorFourCoresTest()
";
- var parser = WmicCpuInfoParser.ParseOutput(cpuInfo);
- Assert.Equal("Intel(R) Core(TM) i7-4710MQ CPU @ 2.50GHz", parser.ProcessorName);
- Assert.Equal(1, parser.PhysicalProcessorCount);
- Assert.Equal(4, parser.PhysicalCoreCount);
- Assert.Equal(8, parser.LogicalCoreCount);
- Assert.Equal(2500 * Frequency.MHz, parser.MaxFrequency);
- }
+ var actual = WmicCpuInfoParser.Parse(cpuInfo);
+ var expected = new CpuInfo(
+ "Intel(R) Core(TM) i7-4710MQ CPU @ 2.50GHz",
+ 1, 4, 8, 2500 * MHz, 2500 * MHz);
+ Output.AssertEqual(expected, actual);
}
}
\ No newline at end of file