This commit is contained in:
Andrey Akinshin 2024-08-25 16:16:34 +02:00
Родитель d2f73e8a65
Коммит 64b3d85222
33 изменённых файлов: 870 добавлений и 831 удалений

Просмотреть файл

@ -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);
}
}