From 64b3d85222f6f7b5b8eccf81c6629e7f48a9b7f5 Mon Sep 17 00:00:00 2001 From: Andrey Akinshin Date: Sun, 25 Aug 2024 16:16:34 +0200 Subject: [PATCH] Refactor CpuInfo detection, fix #2577 --- .../ProcessorBrandStringHelper.cs | 239 +++++++++--------- .../Cpu/CompositeCpuInfoDetector.cs | 18 ++ .../Portability/Cpu/CpuInfo.cs | 60 ++--- .../Portability/Cpu/CpuInfoFormatter.cs | 70 ++--- .../Portability/Cpu/ICpuInfoDetector.cs | 10 + .../Cpu/Linux/LinuxCpuInfoDetector.cs | 21 ++ .../Cpu/Linux/LinuxCpuInfoParser.cs | 111 ++++++++ .../Portability/Cpu/MosCpuInfoProvider.cs | 56 ---- .../Portability/Cpu/ProcCpuInfoKeyNames.cs | 11 - .../Portability/Cpu/ProcCpuInfoParser.cs | 73 ------ .../Portability/Cpu/ProcCpuInfoProvider.cs | 75 ------ .../Portability/Cpu/SysctlCpuInfoParser.cs | 42 --- .../Portability/Cpu/SysctlCpuInfoProvider.cs | 24 -- .../Cpu/Windows/MosCpuInfoDetector.cs | 59 +++++ .../Cpu/Windows/WindowsCpuInfoDetector.cs | 3 + .../Cpu/Windows/WmicCpuInfoDetector.cs | 28 ++ .../Cpu/Windows/WmicCpuInfoKeyNames.cs | 9 + .../Cpu/Windows/WmicCpuInfoParser.cs | 60 +++++ .../Portability/Cpu/WmicCpuInfoKeyNames.cs | 10 - .../Portability/Cpu/WmicCpuInfoParser.cs | 58 ----- .../Portability/Cpu/WmicCpuInfoProvider.cs | 30 --- .../Cpu/macOS/MacOsCpuInfoDetector.cs | 20 ++ .../Cpu/macOS/SysctlCpuInfoParser.cs | 54 ++++ .../Portability/RuntimeInformation.cs | 18 +- .../ExpectedBenchmarkResultsTests.cs | 2 +- .../Builders/HostEnvironmentInfoBuilder.cs | 14 +- .../Environments/ProcessorBrandStringTests.cs | 13 +- .../Portability/Cpu/CpuInfoFormatterTests.cs | 37 ++- .../Cpu/LinuxCpuInfoParserTests.cs | 111 ++++++++ .../Portability/Cpu/ProcCpuInfoParserTests.cs | 90 ------- .../Cpu/SysctlCpuInfoParserTests.cs | 65 +++-- .../Portability/Cpu/TestHelper.cs | 41 ++- .../Portability/Cpu/WmicCpuInfoParserTests.cs | 137 +++++----- 33 files changed, 854 insertions(+), 815 deletions(-) create mode 100644 src/BenchmarkDotNet/Portability/Cpu/CompositeCpuInfoDetector.cs create mode 100644 src/BenchmarkDotNet/Portability/Cpu/ICpuInfoDetector.cs create mode 100644 src/BenchmarkDotNet/Portability/Cpu/Linux/LinuxCpuInfoDetector.cs create mode 100644 src/BenchmarkDotNet/Portability/Cpu/Linux/LinuxCpuInfoParser.cs delete mode 100644 src/BenchmarkDotNet/Portability/Cpu/MosCpuInfoProvider.cs delete mode 100644 src/BenchmarkDotNet/Portability/Cpu/ProcCpuInfoKeyNames.cs delete mode 100644 src/BenchmarkDotNet/Portability/Cpu/ProcCpuInfoParser.cs delete mode 100644 src/BenchmarkDotNet/Portability/Cpu/ProcCpuInfoProvider.cs delete mode 100644 src/BenchmarkDotNet/Portability/Cpu/SysctlCpuInfoParser.cs delete mode 100644 src/BenchmarkDotNet/Portability/Cpu/SysctlCpuInfoProvider.cs create mode 100644 src/BenchmarkDotNet/Portability/Cpu/Windows/MosCpuInfoDetector.cs create mode 100644 src/BenchmarkDotNet/Portability/Cpu/Windows/WindowsCpuInfoDetector.cs create mode 100644 src/BenchmarkDotNet/Portability/Cpu/Windows/WmicCpuInfoDetector.cs create mode 100644 src/BenchmarkDotNet/Portability/Cpu/Windows/WmicCpuInfoKeyNames.cs create mode 100644 src/BenchmarkDotNet/Portability/Cpu/Windows/WmicCpuInfoParser.cs delete mode 100644 src/BenchmarkDotNet/Portability/Cpu/WmicCpuInfoKeyNames.cs delete mode 100644 src/BenchmarkDotNet/Portability/Cpu/WmicCpuInfoParser.cs delete mode 100644 src/BenchmarkDotNet/Portability/Cpu/WmicCpuInfoProvider.cs create mode 100644 src/BenchmarkDotNet/Portability/Cpu/macOS/MacOsCpuInfoDetector.cs create mode 100644 src/BenchmarkDotNet/Portability/Cpu/macOS/SysctlCpuInfoParser.cs create mode 100644 tests/BenchmarkDotNet.Tests/Portability/Cpu/LinuxCpuInfoParserTests.cs delete mode 100644 tests/BenchmarkDotNet.Tests/Portability/Cpu/ProcCpuInfoParserTests.cs 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