From bab81cb96db44087973fd60aefd7bcfc03978d18 Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Fri, 2 Oct 2026 05:29:10 -0600 Subject: [PATCH 1/7] feat: Add RAM Frequency and Type to Environment Summary (#3041) --- .../Environments/PhysicalMemoryInfo.cs | 70 +++++++++++++++++-- .../Environments/PhysicalMemoryInfoTests.cs | 17 +++++ 2 files changed, 82 insertions(+), 5 deletions(-) create mode 100644 tests/BenchmarkDotNet.Tests/Environments/PhysicalMemoryInfoTests.cs diff --git a/src/BenchmarkDotNet/Environments/PhysicalMemoryInfo.cs b/src/BenchmarkDotNet/Environments/PhysicalMemoryInfo.cs index 114836c438..48dca14c1b 100644 --- a/src/BenchmarkDotNet/Environments/PhysicalMemoryInfo.cs +++ b/src/BenchmarkDotNet/Environments/PhysicalMemoryInfo.cs @@ -1,7 +1,11 @@ using BenchmarkDotNet.Detectors; +using BenchmarkDotNet.Extensions; +using System; using System.Diagnostics; +using System.IO; using System.Runtime.InteropServices; using System.Runtime.Versioning; +using System.Text; using System.Text.RegularExpressions; using Windows.Win32; using Windows.Win32.System.SystemInformation; @@ -12,24 +16,44 @@ public class PhysicalMemoryInfo { public long TotalPhysicalBytes { get; } public long? AvailablePhysicalBytes { get; } + public long? FrequencyMHz { get; } + public string? MemoryType { get; } - public PhysicalMemoryInfo(long totalPhysicalBytes, long? availablePhysicalBytes = null) + public PhysicalMemoryInfo(long totalPhysicalBytes, long? availablePhysicalBytes = null, long? frequencyMHz = null, string? memoryType = null) { TotalPhysicalBytes = totalPhysicalBytes; AvailablePhysicalBytes = availablePhysicalBytes; + FrequencyMHz = frequencyMHz; + MemoryType = memoryType; } public string ToFormattedString() { double totalGb = TotalPhysicalBytes / (1024.0 * 1024.0 * 1024.0); + var sb = new StringBuilder(); + sb.Append($"{Math.Round(totalGb, 2)} GB"); + + if (MemoryType.IsNotBlank() || FrequencyMHz.HasValue) + { + sb.Append(" ("); + if (MemoryType.IsNotBlank()) + sb.Append(MemoryType); + if (FrequencyMHz.HasValue) + { + if (MemoryType.IsNotBlank()) + sb.Append('-'); + sb.Append($"{FrequencyMHz}MHz"); + } + sb.Append(')'); + } 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"; + sb.Append($", {Math.Round(availableGb, 2)} GB Available"); } - return $"{Math.Round(totalGb, 2)} GB"; + return sb.ToString(); } } @@ -61,7 +85,10 @@ public static class SystemMemory { var memStatus = new MEMORYSTATUSEX { dwLength = (uint)Marshal.SizeOf() }; if (PInvoke.GlobalMemoryStatusEx(ref memStatus)) - return new PhysicalMemoryInfo((long)memStatus.ullTotalPhys, (long)memStatus.ullAvailPhys); + { + var (freq, type) = GetLinuxOrWindowsMemoryDetails(); + return new PhysicalMemoryInfo((long)memStatus.ullTotalPhys, (long)memStatus.ullAvailPhys, freq, type); + } return null; } @@ -91,11 +118,44 @@ public static class SystemMemory } if (total > 0) - return new PhysicalMemoryInfo(total, available); + { + var (freq, type) = GetLinuxOrWindowsMemoryDetails(); + return new PhysicalMemoryInfo(total, available, freq, type); + } } return null; } + private static (long? FrequencyMHz, string? MemoryType) GetLinuxOrWindowsMemoryDetails() + { + try + { + // Try reading /sys/devices/system/edac/mc/ on Linux + if (Directory.Exists("/sys/devices/system/edac/mc")) + { + foreach (var mcDir in Directory.GetDirectories("/sys/devices/system/edac/mc", "mc*")) + { + foreach (var dimmDir in Directory.GetDirectories(mcDir, "dimm*")) + { + string typePath = Path.Combine(dimmDir, "dimm_mem_type"); + if (File.Exists(typePath)) + { + string memType = File.ReadAllText(typePath).Trim(); + if (!string.IsNullOrEmpty(memType) && memType != "Unspecified") + return (null, memType); + } + } + } + } + } + catch + { + // Ignore fallback failures + } + + return (null, null); + } + private static PhysicalMemoryInfo? GetMacMemory() { long total = 0; diff --git a/tests/BenchmarkDotNet.Tests/Environments/PhysicalMemoryInfoTests.cs b/tests/BenchmarkDotNet.Tests/Environments/PhysicalMemoryInfoTests.cs new file mode 100644 index 0000000000..490f26670b --- /dev/null +++ b/tests/BenchmarkDotNet.Tests/Environments/PhysicalMemoryInfoTests.cs @@ -0,0 +1,17 @@ +using BenchmarkDotNet.Environments; +using Xunit; + +namespace BenchmarkDotNet.Tests.Environments +{ + public class PhysicalMemoryInfoTests + { + [Fact] + public void PhysicalMemoryInfo_ParsesAndConstructsCorrectly() + { + var memInfo = new PhysicalMemoryInfo(16000000000, null, 3200, "DDR4"); + Assert.Equal(16000000000L, memInfo.TotalPhysicalBytes); + Assert.Equal(3200L, memInfo.FrequencyMHz); + Assert.Equal("DDR4", memInfo.MemoryType); + } + } +} From 207e88f0e53757b47206d59bd775ef5daf8c548e Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Fri, 2 Oct 2026 11:20:56 -0600 Subject: [PATCH 2/7] feat: Add SMBIOS GetSystemFirmwareTable P/Invoke for Windows memory details --- .../Environments/PhysicalMemoryInfo.cs | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/src/BenchmarkDotNet/Environments/PhysicalMemoryInfo.cs b/src/BenchmarkDotNet/Environments/PhysicalMemoryInfo.cs index 48dca14c1b..7a1a2cd47c 100644 --- a/src/BenchmarkDotNet/Environments/PhysicalMemoryInfo.cs +++ b/src/BenchmarkDotNet/Environments/PhysicalMemoryInfo.cs @@ -130,6 +130,11 @@ private static (long? FrequencyMHz, string? MemoryType) GetLinuxOrWindowsMemoryD { try { + if (OsDetector.IsWindows7OrLater()) + { + return GetWindowsSmbiosMemoryDetails(); + } + // Try reading /sys/devices/system/edac/mc/ on Linux if (Directory.Exists("/sys/devices/system/edac/mc")) { @@ -156,6 +161,97 @@ private static (long? FrequencyMHz, string? MemoryType) GetLinuxOrWindowsMemoryD return (null, null); } + [SupportedOSPlatform("windows5.1.2600")] + private static (long? FrequencyMHz, string? MemoryType) GetWindowsSmbiosMemoryDetails() + { + try + { + const uint rsMb = 0x52534D42; // 'RSMB' + uint bufferSize = NativeMethods.GetSystemFirmwareTable(rsMb, 0, IntPtr.Zero, 0); + if (bufferSize == 0) + return (null, null); + + byte[] buffer = new byte[bufferSize]; + if (NativeMethods.GetSystemFirmwareTable(rsMb, 0, buffer, bufferSize) == 0) + return (null, null); + + // SMBIOS table header: Data at offset 8 + int offset = 8; + long? maxSpeed = null; + string? detectedType = null; + + while (offset + 4 <= buffer.Length) + { + byte type = buffer[offset]; + byte length = buffer[offset + 1]; + + if (length < 4 || offset + length > buffer.Length) + break; + + // Type 17: Memory Device + if (type == 17 && length >= 0x15) + { + // Memory Type at offset 0x12 + byte rawMemoryType = buffer[offset + 0x12]; + string? memTypeStr = MapSmbiosMemoryType(rawMemoryType); + if (memTypeStr != null && detectedType == null) + { + detectedType = memTypeStr; + } + + // Speed in MHz at offset 0x15 (WORD) + if (length >= 0x17) + { + ushort speed = (ushort)(buffer[offset + 0x15] | (buffer[offset + 0x16] << 8)); + if (speed > 0 && speed < 0xFFFF) + { + if (maxSpeed == null || speed > maxSpeed.Value) + { + maxSpeed = speed; + } + } + } + } + + // Move past formatted area + offset += length; + + // Skip unformatted string section (double null-terminated) + while (offset < buffer.Length - 1 && !(buffer[offset] == 0 && buffer[offset + 1] == 0)) + { + offset++; + } + offset += 2; + } + + return (maxSpeed, detectedType); + } + catch + { + return (null, null); + } + } + + private static class NativeMethods + { + [DllImport("kernel32.dll", SetLastError = true)] + public static extern uint GetSystemFirmwareTable(uint firmwareTableProviderSignature, uint firmwareTableID, byte[] pFirmwareTableBuffer, uint bufferSize); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern uint GetSystemFirmwareTable(uint firmwareTableProviderSignature, uint firmwareTableID, IntPtr pFirmwareTableBuffer, uint bufferSize); + } + + private static string? MapSmbiosMemoryType(byte type) => type switch + { + 0x1A => "DDR3", + 0x1A + 1 => "DDR4", + 0x1A + 2 => "LPDDR3", + 0x1A + 3 => "LPDDR4", + 0x22 => "DDR5", + 0x23 => "LPDDR5", + _ => null + }; + private static PhysicalMemoryInfo? GetMacMemory() { long total = 0; From 05eaa816397a3a41d07d4a808038393417db926a Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Fri, 2 Oct 2026 11:57:04 -0600 Subject: [PATCH 3/7] refactor: Use CsWin32 generated PInvoke GetSystemFirmwareTable --- .../Environments/PhysicalMemoryInfo.cs | 25 ++++++++----------- src/BenchmarkDotNet/NativeMethods.txt | 1 + 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/BenchmarkDotNet/Environments/PhysicalMemoryInfo.cs b/src/BenchmarkDotNet/Environments/PhysicalMemoryInfo.cs index 7a1a2cd47c..eaa37b5af4 100644 --- a/src/BenchmarkDotNet/Environments/PhysicalMemoryInfo.cs +++ b/src/BenchmarkDotNet/Environments/PhysicalMemoryInfo.cs @@ -80,7 +80,7 @@ public static class SystemMemory return null; } - [SupportedOSPlatform("windows5.1.2600")] + [SupportedOSPlatform("windows6.0.6000")] private static PhysicalMemoryInfo? GetWindowsMemory() { var memStatus = new MEMORYSTATUSEX { dwLength = (uint)Marshal.SizeOf() }; @@ -161,19 +161,23 @@ private static (long? FrequencyMHz, string? MemoryType) GetLinuxOrWindowsMemoryD return (null, null); } - [SupportedOSPlatform("windows5.1.2600")] - private static (long? FrequencyMHz, string? MemoryType) GetWindowsSmbiosMemoryDetails() + [SupportedOSPlatform("windows6.0.6000")] + private static unsafe (long? FrequencyMHz, string? MemoryType) GetWindowsSmbiosMemoryDetails() { try { const uint rsMb = 0x52534D42; // 'RSMB' - uint bufferSize = NativeMethods.GetSystemFirmwareTable(rsMb, 0, IntPtr.Zero, 0); + var provider = (FIRMWARE_TABLE_PROVIDER)rsMb; + uint bufferSize = PInvoke.GetSystemFirmwareTable(provider, rsMb, null, 0); if (bufferSize == 0) return (null, null); byte[] buffer = new byte[bufferSize]; - if (NativeMethods.GetSystemFirmwareTable(rsMb, 0, buffer, bufferSize) == 0) - return (null, null); + fixed (byte* pBuffer = buffer) + { + if (PInvoke.GetSystemFirmwareTable(provider, rsMb, pBuffer, bufferSize) == 0) + return (null, null); + } // SMBIOS table header: Data at offset 8 int offset = 8; @@ -232,15 +236,6 @@ private static (long? FrequencyMHz, string? MemoryType) GetWindowsSmbiosMemoryDe } } - private static class NativeMethods - { - [DllImport("kernel32.dll", SetLastError = true)] - public static extern uint GetSystemFirmwareTable(uint firmwareTableProviderSignature, uint firmwareTableID, byte[] pFirmwareTableBuffer, uint bufferSize); - - [DllImport("kernel32.dll", SetLastError = true)] - public static extern uint GetSystemFirmwareTable(uint firmwareTableProviderSignature, uint firmwareTableID, IntPtr pFirmwareTableBuffer, uint bufferSize); - } - private static string? MapSmbiosMemoryType(byte type) => type switch { 0x1A => "DDR3", diff --git a/src/BenchmarkDotNet/NativeMethods.txt b/src/BenchmarkDotNet/NativeMethods.txt index c687e02914..5b0e9f3d13 100644 --- a/src/BenchmarkDotNet/NativeMethods.txt +++ b/src/BenchmarkDotNet/NativeMethods.txt @@ -13,3 +13,4 @@ PowerGetActiveScheme LocalFree GlobalMemoryStatusEx GetLogicalProcessorInformationEx +GetSystemFirmwareTable From 0b9b9a5301e696b60aa2b7f39a3bbbd4cca89cf2 Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Fri, 2 Oct 2026 12:31:30 -0600 Subject: [PATCH 4/7] fix: Correct SMBIOS memory type enumeration mapping (DDR3=0x18, DDR4=0x1A) --- src/BenchmarkDotNet/Environments/PhysicalMemoryInfo.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/BenchmarkDotNet/Environments/PhysicalMemoryInfo.cs b/src/BenchmarkDotNet/Environments/PhysicalMemoryInfo.cs index eaa37b5af4..87c45001d7 100644 --- a/src/BenchmarkDotNet/Environments/PhysicalMemoryInfo.cs +++ b/src/BenchmarkDotNet/Environments/PhysicalMemoryInfo.cs @@ -238,10 +238,10 @@ private static unsafe (long? FrequencyMHz, string? MemoryType) GetWindowsSmbiosM private static string? MapSmbiosMemoryType(byte type) => type switch { - 0x1A => "DDR3", - 0x1A + 1 => "DDR4", - 0x1A + 2 => "LPDDR3", - 0x1A + 3 => "LPDDR4", + 0x18 => "DDR3", + 0x1A => "DDR4", + 0x1B => "LPDDR3", + 0x1C => "LPDDR4", 0x22 => "DDR5", 0x23 => "LPDDR5", _ => null From cb20cab9c8ac5f82f0854fd60852217e43247ebe Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Fri, 2 Oct 2026 14:18:05 -0600 Subject: [PATCH 5/7] fix: Pass 0 for FirmwareTableID in GetSystemFirmwareTable to parse full raw SMBIOS table --- src/BenchmarkDotNet/Environments/PhysicalMemoryInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/BenchmarkDotNet/Environments/PhysicalMemoryInfo.cs b/src/BenchmarkDotNet/Environments/PhysicalMemoryInfo.cs index 87c45001d7..9cece28471 100644 --- a/src/BenchmarkDotNet/Environments/PhysicalMemoryInfo.cs +++ b/src/BenchmarkDotNet/Environments/PhysicalMemoryInfo.cs @@ -168,14 +168,14 @@ private static unsafe (long? FrequencyMHz, string? MemoryType) GetWindowsSmbiosM { const uint rsMb = 0x52534D42; // 'RSMB' var provider = (FIRMWARE_TABLE_PROVIDER)rsMb; - uint bufferSize = PInvoke.GetSystemFirmwareTable(provider, rsMb, null, 0); + uint bufferSize = PInvoke.GetSystemFirmwareTable(provider, 0, null, 0); if (bufferSize == 0) return (null, null); byte[] buffer = new byte[bufferSize]; fixed (byte* pBuffer = buffer) { - if (PInvoke.GetSystemFirmwareTable(provider, rsMb, pBuffer, bufferSize) == 0) + if (PInvoke.GetSystemFirmwareTable(provider, 0, pBuffer, bufferSize) == 0) return (null, null); } From ab023a61cddad78a8d80d2bac40a75ff0c3d6816 Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Fri, 2 Oct 2026 14:25:38 -0600 Subject: [PATCH 6/7] fix: Update SMBIOS LPDDR, MRDIMM and LPDDR6 memory type mappings per DSP0134 spec --- src/BenchmarkDotNet/Environments/PhysicalMemoryInfo.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/BenchmarkDotNet/Environments/PhysicalMemoryInfo.cs b/src/BenchmarkDotNet/Environments/PhysicalMemoryInfo.cs index 9cece28471..da6899958e 100644 --- a/src/BenchmarkDotNet/Environments/PhysicalMemoryInfo.cs +++ b/src/BenchmarkDotNet/Environments/PhysicalMemoryInfo.cs @@ -240,10 +240,14 @@ private static unsafe (long? FrequencyMHz, string? MemoryType) GetWindowsSmbiosM { 0x18 => "DDR3", 0x1A => "DDR4", - 0x1B => "LPDDR3", - 0x1C => "LPDDR4", + 0x1B => "LPDDR", + 0x1C => "LPDDR2", + 0x1D => "LPDDR3", + 0x1E => "LPDDR4", 0x22 => "DDR5", 0x23 => "LPDDR5", + 0x25 => "MRDIMM", + 0x26 => "LPDDR6", _ => null }; From f80c348ef64bb762e0d0722deeac45cb345d91b4 Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Fri, 2 Oct 2026 14:29:25 -0600 Subject: [PATCH 7/7] fix: Expand SMBIOS memory type mappings per DMTF DSP0134 3.10.0 spec --- src/BenchmarkDotNet/Environments/PhysicalMemoryInfo.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/BenchmarkDotNet/Environments/PhysicalMemoryInfo.cs b/src/BenchmarkDotNet/Environments/PhysicalMemoryInfo.cs index da6899958e..5a16957f04 100644 --- a/src/BenchmarkDotNet/Environments/PhysicalMemoryInfo.cs +++ b/src/BenchmarkDotNet/Environments/PhysicalMemoryInfo.cs @@ -238,14 +238,19 @@ private static unsafe (long? FrequencyMHz, string? MemoryType) GetWindowsSmbiosM private static string? MapSmbiosMemoryType(byte type) => type switch { + 0x12 => "DDR", + 0x13 => "DDR2", 0x18 => "DDR3", 0x1A => "DDR4", 0x1B => "LPDDR", 0x1C => "LPDDR2", 0x1D => "LPDDR3", 0x1E => "LPDDR4", + 0x20 => "HBM", + 0x21 => "HBM2", 0x22 => "DDR5", 0x23 => "LPDDR5", + 0x24 => "HBM3", 0x25 => "MRDIMM", 0x26 => "LPDDR6", _ => null