Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 15 additions & 8 deletions src/BenchmarkDotNet/Environments/HostEnvironmentInfo.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BenchmarkDotNet.Detectors;
using BenchmarkDotNet.Detectors;
using BenchmarkDotNet.Extensions;
using BenchmarkDotNet.Helpers;
using BenchmarkDotNet.Models;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Models;
using BenchmarkDotNet.Portability;
using BenchmarkDotNet.Properties;
using BenchmarkDotNet.Reports;
using BenchmarkDotNet.Toolchains.DotNetCli;
using JetBrains.Annotations;
using Perfolizer.Helpers;
using Perfolizer.Horology;
using Perfolizer.Models;
using Perfolizer.Metrology;
using BenchmarkDotNet.Extensions;
using Perfolizer.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BenchmarkDotNet.Environments
{
Expand Down Expand Up @@ -69,6 +69,8 @@ public class HostEnvironmentInfo : BenchmarkEnvironmentInfo
// TODO: Join with OsInfo
public Lazy<VirtualMachineHypervisor?> VirtualMachineHypervisor { get; protected set; }

public Lazy<PhysicalMemoryInfo?> PhysicalMemory { get; protected set; }

protected HostEnvironmentInfo()
{
BenchmarkDotNetVersion = BenchmarkDotNetInfo.Instance.BrandVersion;
Expand All @@ -80,6 +82,7 @@ protected HostEnvironmentInfo()
VirtualMachineHypervisor = new Lazy<VirtualMachineHypervisor?>(RuntimeInformation.GetVirtualMachineHypervisor);
Os = new Lazy<OsInfo>(OsDetector.GetOs);
Cpu = new Lazy<CpuInfo>(() => CpuDetector.CrossPlatform.Detect() ?? CpuInfo.Unknown);
PhysicalMemory = new Lazy<PhysicalMemoryInfo?>(SystemMemory.GetPhysicalMemory);
}

public new static HostEnvironmentInfo GetCurrent() => current ??= new HostEnvironmentInfo();
Expand All @@ -96,6 +99,7 @@ public override IEnumerable<string> ToFormattedString()
yield return $"{BenchmarkDotNetCaption} v{BenchmarkDotNetVersion}, {Os.Value.ToBrandString()}";

yield return Cpu.Value.ToFullBrandName();

if (HardwareTimerKind != HardwareTimerKind.Unknown)
{
string frequency = PerfolizerMeasurementFormatter.Instance.Format(
Expand All @@ -109,6 +113,9 @@ public override IEnumerable<string> ToFormattedString()
yield return $"Frequency: {frequency}, Resolution: {resolution}, Timer: {timer}";
}

if (PhysicalMemory.Value != null)
yield return $"Memory: {PhysicalMemory.Value.ToFormattedString()}";

if (RuntimeInformation.IsNetCore && IsDotNetCliInstalled())
{
// this wonderful version number contains words like "preview" and ... 5 segments, so it can not be parsed by Version.Parse. Example: "5.0.100-preview.8.20362.3"
Expand Down
178 changes: 178 additions & 0 deletions src/BenchmarkDotNet/Environments/PhysicalMemoryInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;

namespace BenchmarkDotNet.Environments
{
public class PhysicalMemoryInfo
{
public long TotalPhysicalBytes { get; }
public long? AvailablePhysicalBytes { get; }

public PhysicalMemoryInfo(long totalPhysicalBytes, long? availablePhysicalBytes = null)
{
TotalPhysicalBytes = totalPhysicalBytes;
AvailablePhysicalBytes = availablePhysicalBytes;
}

public string ToFormattedString()
{
double totalGb = TotalPhysicalBytes / (1024.0 * 1024.0 * 1024.0);

if (AvailablePhysicalBytes.HasValue)
{
double availableGb = AvailablePhysicalBytes.Value / (1024.0 * 1024.0 * 1024.0);
return $"{Math.Round(totalGb, 2)} GB Total, {Math.Round(availableGb, 2)} GB Available";
}

return $"{Math.Round(totalGb, 2)} GB";
}
}

public static class SystemMemory
{
public static PhysicalMemoryInfo? GetPhysicalMemory()
{
try
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return GetWindowsMemory();

if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
return GetLinuxMemory();

if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
return GetMacMemory();
}
catch (Exception)
{
// Ignore errors
}

return null;
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
private class MEMORYSTATUSEX
{
public uint dwLength;
public uint dwMemoryLoad;
public ulong ullTotalPhys;
public ulong ullAvailPhys;
public ulong ullTotalPageFile;
public ulong ullAvailPageFile;
public ulong ullTotalVirtual;
public ulong ullAvailVirtual;
public ulong ullAvailExtendedVirtual;

public MEMORYSTATUSEX()
{
dwLength = (uint)Marshal.SizeOf(typeof(MEMORYSTATUSEX));
}
}

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GlobalMemoryStatusEx([In, Out] MEMORYSTATUSEX lpBuffer);

private static PhysicalMemoryInfo? GetWindowsMemory()
{
var memStatus = new MEMORYSTATUSEX();
if (GlobalMemoryStatusEx(memStatus))
{
return new PhysicalMemoryInfo((long)memStatus.ullTotalPhys, (long)memStatus.ullAvailPhys);
}
return null;
}

private static PhysicalMemoryInfo? GetLinuxMemory()
{
const string path = "/proc/meminfo";
if (File.Exists(path))
{
long total = 0;
long? available = null;

foreach (var line in File.ReadAllLines(path))
{
if (line.StartsWith("MemTotal:"))
{
var match = Regex.Match(line, @"\d+");
if (match.Success && long.TryParse(match.Value, out long kb))
total = kb * 1024;
}
else if (line.StartsWith("MemAvailable:") || line.StartsWith("MemFree:"))
{
var match = Regex.Match(line, @"\d+");
if (match.Success && long.TryParse(match.Value, out long kb) && available == null)
available = kb * 1024;
}
}

if (total > 0)
return new PhysicalMemoryInfo(total, available);
}
return null;
}

private static PhysicalMemoryInfo? GetMacMemory()
{
long total = 0;
long? available = null;

// 1. Get Total Memory
var sysctlInfo = new ProcessStartInfo("sysctl", "-n hw.memsize")
{
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
};

using (var process = Process.Start(sysctlInfo))
{
if (process != null)
{
string output = process.StandardOutput.ReadToEnd();
process.WaitForExit();
long.TryParse(output.Trim(), out total);
}
}

if (total == 0) return null;

// 2. Get Free Memory using vm_stat
var vmStatInfo = new ProcessStartInfo("vm_stat")
{
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
};

using (var process = Process.Start(vmStatInfo))
{
if (process != null)
{
string output = process.StandardOutput.ReadToEnd();
process.WaitForExit();

long pageSize = 4096;
var pageSizeMatch = Regex.Match(output, @"page size of (\d+) bytes");
if (pageSizeMatch.Success && long.TryParse(pageSizeMatch.Groups[1].Value, out long parsedPageSize))
{
pageSize = parsedPageSize;
}

var match = Regex.Match(output, @"Pages free:\s+(\d+)");
if (match.Success && long.TryParse(match.Groups[1].Value, out long pagesFree))
{
available = pagesFree * pageSize;
}
}
}

return new PhysicalMemoryInfo(total, available);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
using System;
using System.Diagnostics.CodeAnalysis;
using BenchmarkDotNet.Environments;
using BenchmarkDotNet.Environments;
using BenchmarkDotNet.Extensions;
using BenchmarkDotNet.Portability;
using Perfolizer.Horology;
using Perfolizer.Models;
using System;
using System.Diagnostics.CodeAnalysis;

namespace BenchmarkDotNet.Tests.Builders
{
Expand Down Expand Up @@ -60,7 +60,7 @@ public HostEnvironmentInfo Build()
{
return new MockHostEnvironmentInfo(architecture, benchmarkDotNetVersion, chronometerFrequency, configuration,
dotNetSdkVersion, hardwareTimerKind, hasAttachedDebugger, hasRyuJit, isConcurrentGC, isServerGC,
jitInfo, jitModules, os, cpu, runtimeVersion, virtualMachineHypervisor);
jitInfo, jitModules, os, cpu, runtimeVersion, virtualMachineHypervisor, null);
}
}

Expand All @@ -70,7 +70,7 @@ public MockHostEnvironmentInfo(
string architecture, string benchmarkDotNetVersion, Frequency chronometerFrequency, string configuration, string dotNetSdkVersion,
HardwareTimerKind hardwareTimerKind, bool hasAttachedDebugger, bool hasRyuJit, bool isConcurrentGC, bool isServerGC,
string jitInfo, string jitModules, OsInfo os, CpuInfo cpu,
string runtimeVersion, VirtualMachineHypervisor? virtualMachineHypervisor)
string runtimeVersion, VirtualMachineHypervisor? virtualMachineHypervisor, PhysicalMemoryInfo? physicalMemory = null)
{
Architecture = architecture;
BenchmarkDotNetVersion = benchmarkDotNetVersion;
Expand All @@ -88,6 +88,7 @@ public MockHostEnvironmentInfo(
Cpu = new Lazy<CpuInfo>(() => cpu);
RuntimeVersion = runtimeVersion;
VirtualMachineHypervisor = new Lazy<VirtualMachineHypervisor?>(() => virtualMachineHypervisor);
PhysicalMemory = new Lazy<PhysicalMemoryInfo?>(() => physicalMemory);
}
}
}