Refactor CpuInfo detection, fix #2577
This commit is contained in:
Родитель
d2f73e8a65
Коммит
64b3d85222
|
@ -3,153 +3,152 @@ using System.Collections.Generic;
|
|||
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
|
||||
/// <summary>
|
||||
/// Transform a processor brand string to a nice form for summary.
|
||||
/// </summary>
|
||||
/// <param name="cpuInfo">The CPU information</param>
|
||||
/// <param name="includeMaxFrequency">Whether to include determined max frequency information</param>
|
||||
/// <returns>Prettified version</returns>
|
||||
public static string Prettify(CpuInfo? cpuInfo, bool includeMaxFrequency = false)
|
||||
{
|
||||
/// <summary>
|
||||
/// Transform a processor brand string to a nice form for summary.
|
||||
/// </summary>
|
||||
/// <param name="cpuInfo">The CPU information</param>
|
||||
/// <param name="includeMaxFrequency">Whether to include determined max frequency information</param>
|
||||
/// <returns>Prettified version</returns>
|
||||
public static string Prettify(CpuInfo cpuInfo, bool includeMaxFrequency = false)
|
||||
string? processorName = cpuInfo?.ProcessorName;
|
||||
if (processorName == null || processorName.IsBlank())
|
||||
return "Unknown processor";
|
||||
|
||||
// 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 is > 0)
|
||||
processorName = Regex.Replace(processorName, @"(\w+?-Core Processor)", "").Trim();
|
||||
|
||||
string frequencyString = GetBrandStyledNominalFrequency(cpuInfo.NominalFrequency);
|
||||
if (includeMaxFrequency && frequencyString != null && !processorName.Contains(frequencyString))
|
||||
{
|
||||
if (cpuInfo == null || string.IsNullOrEmpty(cpuInfo.ProcessorName))
|
||||
{
|
||||
return "Unknown processor";
|
||||
}
|
||||
|
||||
// Remove parts which don't provide any useful information for user
|
||||
var processorName = cpuInfo.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();
|
||||
|
||||
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}";
|
||||
}
|
||||
|
||||
// Remove double spaces
|
||||
processorName = Regex.Replace(processorName.Trim(), @"\s+", " ");
|
||||
|
||||
// Add microarchitecture name if known
|
||||
string microarchitecture = ParseMicroarchitecture(processorName);
|
||||
if (microarchitecture != null)
|
||||
processorName = $"{processorName} ({microarchitecture})";
|
||||
|
||||
return processorName;
|
||||
// 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}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Presents actual processor's frequency into brand string format
|
||||
/// </summary>
|
||||
/// <param name="frequency"></param>
|
||||
private static string GetBrandStyledActualFrequency(Frequency? frequency)
|
||||
{
|
||||
if (frequency == null)
|
||||
return null;
|
||||
return $"{frequency.Value.ToGHz().ToString("N2", DefaultCultureInfo.Instance)}GHz";
|
||||
}
|
||||
// Remove double spaces
|
||||
processorName = Regex.Replace(processorName.Trim(), @"\s+", " ");
|
||||
|
||||
/// <summary>
|
||||
/// Parse a processor name and tries to return a microarchitecture name.
|
||||
/// Works only for well-known microarchitectures.
|
||||
/// </summary>
|
||||
private static string? ParseMicroarchitecture(string processorName)
|
||||
{
|
||||
if (processorName.StartsWith("Intel Core"))
|
||||
{
|
||||
string model = processorName.Substring("Intel Core".Length).Trim();
|
||||
// Add microarchitecture name if known
|
||||
string microarchitecture = ParseMicroarchitecture(processorName);
|
||||
if (microarchitecture != null)
|
||||
processorName = $"{processorName} ({microarchitecture})";
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
return processorName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Presents actual processor's frequency into brand string format
|
||||
/// </summary>
|
||||
/// <param name="frequency"></param>
|
||||
private static string? GetBrandStyledNominalFrequency(Frequency? frequency)
|
||||
{
|
||||
if (frequency == null)
|
||||
return null;
|
||||
return $"{frequency.Value.ToGHz().ToString("N2", DefaultCultureInfo.Instance)}GHz";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a processor name and tries to return a microarchitecture name.
|
||||
/// Works only for well-known microarchitectures.
|
||||
/// </summary>
|
||||
private static string? ParseMicroarchitecture(string processorName)
|
||||
{
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly Lazy<Dictionary<string, string>> KnownMicroarchitectures = new Lazy<Dictionary<string, string>>(() =>
|
||||
{
|
||||
var data = ResourceHelper.LoadResource("BenchmarkDotNet.Environments.microarchitectures.txt").Split('\r', '\n');
|
||||
var dictionary = new Dictionary<string, string>();
|
||||
string? currentMicroarchitecture = null;
|
||||
foreach (string line in data)
|
||||
{
|
||||
if (line.StartsWith("//") || string.IsNullOrWhiteSpace(line))
|
||||
continue;
|
||||
if (line.StartsWith("#"))
|
||||
{
|
||||
currentMicroarchitecture = line.Substring(1).Trim();
|
||||
continue;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
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;
|
||||
private static readonly Lazy<Dictionary<string, string>> KnownMicroarchitectures = new Lazy<Dictionary<string, string>>(() =>
|
||||
{
|
||||
var data = ResourceHelper.LoadResource("BenchmarkDotNet.Environments.microarchitectures.txt").Split('\r', '\n');
|
||||
var dictionary = new Dictionary<string, string>();
|
||||
string? currentMicroarchitecture = null;
|
||||
foreach (string line in data)
|
||||
{
|
||||
if (line.StartsWith("//") || string.IsNullOrWhiteSpace(line))
|
||||
continue;
|
||||
if (line.StartsWith("#"))
|
||||
{
|
||||
currentMicroarchitecture = line.Substring(1).Trim();
|
||||
continue;
|
||||
}
|
||||
|
||||
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))
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
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;
|
||||
}
|
||||
|
||||
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))
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
|
||||
internal CpuInfo(string processorName, Frequency? nominalFrequency)
|
||||
: this(processorName, null, null, null, nominalFrequency, null, null)
|
||||
{
|
||||
}
|
||||
private static readonly CompositeCpuInfoDetector Detector = new (
|
||||
new WindowsCpuInfoDetector(),
|
||||
new LinuxCpuInfoDetector(),
|
||||
new MacOsCpuInfoDetector());
|
||||
|
||||
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 static CpuInfo? DetectCurrent() => Detector.Detect();
|
||||
|
||||
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;
|
||||
}
|
|
@ -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";
|
||||
|
||||
var parts = new List<string>
|
||||
{
|
||||
if (cpuInfo == null)
|
||||
{
|
||||
return "Unknown processor";
|
||||
}
|
||||
ProcessorBrandStringHelper.Prettify(cpuInfo, includeMaxFrequency: true)
|
||||
};
|
||||
|
||||
var parts = new List<string>
|
||||
{
|
||||
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");
|
||||
|
||||
if (cpuInfo.LogicalCoreCount > 0 && cpuInfo.PhysicalCoreCount > 0)
|
||||
parts.Add(" and ");
|
||||
else if (cpuInfo.PhysicalCoreCount > 0)
|
||||
parts.Add(", ");
|
||||
|
||||
if (cpuInfo.PhysicalCoreCount == 1)
|
||||
parts.Add("1 physical core");
|
||||
if (cpuInfo.PhysicalCoreCount > 1)
|
||||
parts.Add($"{cpuInfo.PhysicalCoreCount} physical cores");
|
||||
|
||||
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;
|
||||
break;
|
||||
}
|
||||
|
||||
if (cpuInfo.LogicalCoreCount > 0 && cpuInfo.PhysicalCoreCount > 0)
|
||||
parts.Add(" and ");
|
||||
else if (cpuInfo.PhysicalCoreCount > 0)
|
||||
parts.Add(", ");
|
||||
|
||||
switch (cpuInfo.PhysicalCoreCount)
|
||||
{
|
||||
case 1:
|
||||
parts.Add("1 physical core");
|
||||
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");
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
namespace BenchmarkDotNet.Portability.Cpu;
|
||||
|
||||
/// <summary>
|
||||
/// Loads the <see cref="CpuInfo"/> for the current hardware
|
||||
/// </summary>
|
||||
internal interface ICpuInfoDetector
|
||||
{
|
||||
bool IsApplicable();
|
||||
CpuInfo? Detect();
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
using BenchmarkDotNet.Helpers;
|
||||
|
||||
namespace BenchmarkDotNet.Portability.Cpu.Linux;
|
||||
|
||||
/// <summary>
|
||||
/// CPU information from output of the `cat /proc/cpuinfo` and `lscpu` command.
|
||||
/// Linux only.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
|
||||
/// <param name="cpuInfo">Output of `cat /proc/cpuinfo`</param>
|
||||
/// <param name="lscpu">Output of `lscpu`</param>
|
||||
internal static CpuInfo Parse(string? cpuInfo, string? lscpu)
|
||||
{
|
||||
var processorModelNames = new HashSet<string>();
|
||||
var processorsToPhysicalCoreCount = new Dictionary<string, int>();
|
||||
int logicalCoreCount = 0;
|
||||
Frequency? maxFrequency = null;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -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<CpuInfo> MosCpuInfo = new Lazy<CpuInfo>(Load);
|
||||
|
||||
#if NET6_0_OR_GREATER
|
||||
[System.Runtime.Versioning.SupportedOSPlatform("windows")]
|
||||
#endif
|
||||
private static CpuInfo Load()
|
||||
{
|
||||
var processorModelNames = new HashSet<string>();
|
||||
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<ManagementObject>())
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -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<string>();
|
||||
var processorsToPhysicalCoreCount = new Dictionary<string, int>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using BenchmarkDotNet.Helpers;
|
||||
using Perfolizer.Horology;
|
||||
|
||||
namespace BenchmarkDotNet.Portability.Cpu
|
||||
{
|
||||
/// <summary>
|
||||
/// CPU information from output of the `cat /proc/info` command.
|
||||
/// Linux only.
|
||||
/// </summary>
|
||||
internal static class ProcCpuInfoProvider
|
||||
{
|
||||
internal static readonly Lazy<CpuInfo> 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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<string, string> 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<string, string> sysctl, string keyName)
|
||||
{
|
||||
if (sysctl.TryGetValue(keyName, out string value) &&
|
||||
long.TryParse(value, out long result) &&
|
||||
result > 0)
|
||||
return result;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
using System;
|
||||
using BenchmarkDotNet.Helpers;
|
||||
|
||||
namespace BenchmarkDotNet.Portability.Cpu
|
||||
{
|
||||
/// <summary>
|
||||
/// CPU information from output of the `sysctl -a` command.
|
||||
/// MacOSX only.
|
||||
/// </summary>
|
||||
internal static class SysctlCpuInfoProvider
|
||||
{
|
||||
internal static readonly Lazy<CpuInfo> SysctlCpuInfo = new Lazy<CpuInfo>(Load);
|
||||
|
||||
private static CpuInfo? Load()
|
||||
{
|
||||
if (RuntimeInformation.IsMacOS())
|
||||
{
|
||||
string content = ProcessHelper.RunAndReadOutput("sysctl", "-a");
|
||||
return SysctlCpuInfoParser.ParseOutput(content);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<string>();
|
||||
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<ManagementObject>())
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
namespace BenchmarkDotNet.Portability.Cpu.Windows;
|
||||
|
||||
internal class WindowsCpuInfoDetector() : CompositeCpuInfoDetector(new MosCpuInfoDetector(), new WmicCpuInfoDetector());
|
|
@ -0,0 +1,28 @@
|
|||
using System.IO;
|
||||
using BenchmarkDotNet.Helpers;
|
||||
|
||||
namespace BenchmarkDotNet.Portability.Cpu.Windows;
|
||||
|
||||
/// <summary>
|
||||
/// CPU information from output of the `wmic cpu get Name, NumberOfCores, NumberOfLogicalProcessors /Format:List` command.
|
||||
/// Windows only.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
using System.Collections.Generic;
|
||||
using BenchmarkDotNet.Helpers;
|
||||
using Perfolizer.Horology;
|
||||
|
||||
namespace BenchmarkDotNet.Portability.Cpu.Windows;
|
||||
|
||||
internal static class WmicCpuInfoParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Parses wmic output and returns <see cref="CpuInfo"/>
|
||||
/// </summary>
|
||||
/// <param name="wmicOutput">Output of `wmic cpu get Name, NumberOfCores, NumberOfLogicalProcessors /Format:List`</param>
|
||||
internal static CpuInfo Parse(string? wmicOutput)
|
||||
{
|
||||
var processorModelNames = new HashSet<string>();
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -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<string>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using BenchmarkDotNet.Helpers;
|
||||
|
||||
namespace BenchmarkDotNet.Portability.Cpu
|
||||
{
|
||||
/// <summary>
|
||||
/// CPU information from output of the `wmic cpu get Name, NumberOfCores, NumberOfLogicalProcessors /Format:List` command.
|
||||
/// Windows only.
|
||||
/// </summary>
|
||||
internal static class WmicCpuInfoProvider
|
||||
{
|
||||
internal static readonly Lazy<CpuInfo> 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
using BenchmarkDotNet.Helpers;
|
||||
|
||||
namespace BenchmarkDotNet.Portability.Cpu.macOS;
|
||||
|
||||
/// <summary>
|
||||
/// CPU information from output of the `sysctl -a` command.
|
||||
/// MacOSX only.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
|
||||
/// <param name="sysctlOutput">Output of `sysctl -a`</param>
|
||||
[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<string, string> 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<string, string> sysctl, string keyName)
|
||||
{
|
||||
if (sysctl.TryGetValue(keyName, out string value) &&
|
||||
long.TryParse(value, out long result) &&
|
||||
result > 0)
|
||||
return result;
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -12,6 +12,9 @@ using BenchmarkDotNet.Environments;
|
|||
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 @@ namespace BenchmarkDotNet.Portability
|
|||
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<CpuInfo?> LazyCpuInfo = new (CpuInfo.DetectCurrent);
|
||||
internal static CpuInfo? GetCpuInfo() => LazyCpuInfo.Value;
|
||||
|
||||
internal static string GetRuntimeVersion()
|
||||
{
|
||||
|
|
|
@ -110,7 +110,7 @@ namespace BenchmarkDotNet.IntegrationTests.ManualRunning
|
|||
.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)
|
||||
|
|
|
@ -23,13 +23,13 @@ namespace BenchmarkDotNet.Tests.Builders
|
|||
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;
|
||||
|
||||
|
|
|
@ -19,8 +19,11 @@ namespace BenchmarkDotNet.Tests.Environments
|
|||
[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 @@ namespace BenchmarkDotNet.Tests.Environments
|
|||
[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 @@ namespace BenchmarkDotNet.Tests.Environments
|
|||
physicalCoreCount,
|
||||
logicalCoreCount,
|
||||
Frequency.FromGHz(actualFrequency),
|
||||
minFrequency: null,
|
||||
maxFrequency: null);
|
||||
|
||||
Assert.Equal(prettifiedName, ProcessorBrandStringHelper.Prettify(cpuInfo, includeMaxFrequency: true));
|
||||
|
@ -65,7 +68,7 @@ namespace BenchmarkDotNet.Tests.Environments
|
|||
[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));
|
||||
}
|
||||
|
|
|
@ -5,27 +5,26 @@ using BenchmarkDotNet.Tests.Builders;
|
|||
using VerifyXunit;
|
||||
using Xunit;
|
||||
|
||||
namespace BenchmarkDotNet.Tests.Portability.Cpu
|
||||
{
|
||||
[Collection("VerifyTests")]
|
||||
[UsesVerify]
|
||||
public class CpuInfoFormatterTests
|
||||
{
|
||||
[Fact]
|
||||
public Task FormatTest()
|
||||
{
|
||||
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));
|
||||
}
|
||||
namespace BenchmarkDotNet.Tests.Portability.Cpu;
|
||||
|
||||
var settings = VerifySettingsFactory.Create();
|
||||
return Verifier.Verify(captions.ToString(), settings);
|
||||
[Collection("VerifyTests")]
|
||||
[UsesVerify]
|
||||
public class CpuInfoFormatterTests
|
||||
{
|
||||
[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 mockCpuInfo = new CpuInfo(processorName, physicalProcessorCount, physicalCoreCount, logicalCoreCount, null, null);
|
||||
captions.AppendLine(CpuInfoFormatter.Format(mockCpuInfo));
|
||||
}
|
||||
|
||||
var settings = VerifySettingsFactory.Create();
|
||||
return Verifier.Verify(captions.ToString(), settings);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
using (var reader = new StreamReader(stream))
|
||||
{
|
||||
return reader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
using var stream = assembly.GetManifestResourceStream(resourceName);
|
||||
if (stream == null)
|
||||
throw new FileNotFoundException($"Resource {resourceName} not found in {assembly.FullName}");
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -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 @@ NumberOfCores=8
|
|||
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 @@ NumberOfLogicalProcessors=8
|
|||
|
||||
";
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче