From 76245c9a324ebd2f19e0ef844ecbf2af6ab772d8 Mon Sep 17 00:00:00 2001 From: PhyxionNL <7643972+PhyxionNL@users.noreply.github.com> Date: Wed, 15 Mar 2023 18:00:38 +0100 Subject: [PATCH] Read storage performance sensors without WMI --- .../Hardware/Gpu/AmdGpu.cs | 1 + .../Hardware/Storage/AbstractStorage.cs | 139 ++++++++---------- .../Hardware/Storage/NVMeIntel.cs | 5 +- .../Hardware/Storage/NVMeIntelRst.cs | 3 +- .../Hardware/Storage/NVMeSamsung.cs | 5 +- .../Hardware/Storage/NVMeWindows.cs | 5 +- .../Hardware/Storage/StorageInfo.cs | 5 +- .../Hardware/Storage/WindowsStorage.cs | 3 +- LibreHardwareMonitorLib/Interop/Kernel32.cs | 62 +++++++- 9 files changed, 143 insertions(+), 85 deletions(-) diff --git a/LibreHardwareMonitorLib/Hardware/Gpu/AmdGpu.cs b/LibreHardwareMonitorLib/Hardware/Gpu/AmdGpu.cs index b3e6e34..5d62daf 100644 --- a/LibreHardwareMonitorLib/Hardware/Gpu/AmdGpu.cs +++ b/LibreHardwareMonitorLib/Hardware/Gpu/AmdGpu.cs @@ -442,6 +442,7 @@ internal sealed class AmdGpu : GenericGpu /// Type of the sensor. /// The sensor. /// The factor. + /// If set to true, resets the sensor value to null. private void GetPMLog(AtiAdlxx.ADLPMLogDataOutput data, AtiAdlxx.ADLSensorType sensorType, Sensor sensor, float factor = 1.0f, bool reset = true) { int i = (int)sensorType; diff --git a/LibreHardwareMonitorLib/Hardware/Storage/AbstractStorage.cs b/LibreHardwareMonitorLib/Hardware/Storage/AbstractStorage.cs index 82a6113..6bd81ef 100644 --- a/LibreHardwareMonitorLib/Hardware/Storage/AbstractStorage.cs +++ b/LibreHardwareMonitorLib/Hardware/Storage/AbstractStorage.cs @@ -5,10 +5,10 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.IO; -using System.Linq; -using System.Management; +using System.Runtime.InteropServices; using System.Text; using LibreHardwareMonitor.Interop; @@ -21,16 +21,15 @@ public abstract class AbstractStorage : Hardware private readonly StorageInfo _storageInfo; private readonly TimeSpan _updateInterval = TimeSpan.FromSeconds(60); - private ulong _lastReadRateCounter; - private double _lastTime; + private ulong _lastReadCount; + private long _lastTime; private DateTime _lastUpdate = DateTime.MinValue; - private ulong _lastWriteRateCounter; + private ulong _lastWriteCount; private Sensor _sensorDiskReadRate; private Sensor _sensorDiskTotalActivity; private Sensor _sensorDiskWriteActivity; private Sensor _sensorDiskWriteRate; private Sensor _usageSensor; - private int _wmiFailureCount; internal AbstractStorage(StorageInfo storageInfo, string name, string firmwareRevision, string id, int index, ISettings settings) : base(name, new Identifier(id, index.ToString(CultureInfo.InvariantCulture)), settings) @@ -69,19 +68,24 @@ public abstract class AbstractStorage : Hardware public int Index { get; } + /// + public override void Close() + { + _storageInfo.Handle?.Close(); + base.Close(); + } + public static AbstractStorage CreateInstance(string deviceId, uint driveNumber, ulong diskSize, int scsiPort, ISettings settings) { StorageInfo info = WindowsStorage.GetStorageInfo(deviceId, driveNumber); - if (info == null) + if (info == null || info.Removable || info.BusType is Kernel32.STORAGE_BUS_TYPE.BusTypeVirtual or Kernel32.STORAGE_BUS_TYPE.BusTypeFileBackedVirtual) return null; info.DiskSize = diskSize; info.DeviceId = deviceId; + info.Handle = Kernel32.OpenDevice(deviceId); info.Scsi = $@"\\.\SCSI{scsiPort}:"; - - if (info.Removable || info.BusType is Kernel32.STORAGE_BUS_TYPE.BusTypeVirtual or Kernel32.STORAGE_BUS_TYPE.BusTypeFileBackedVirtual) - return null; - + //fallback, when it is not possible to read out with the nvme implementation, //try it with the sata smart implementation if (info.BusType == Kernel32.STORAGE_BUS_TYPE.BusTypeNvme) @@ -119,78 +123,22 @@ public abstract class AbstractStorage : Hardware protected abstract void UpdateSensors(); - private void UpdateStatisticsFromWmi(int driveIndex) - { - string query = $"SELECT * FROM Win32_PerfRawData_PerfDisk_PhysicalDisk Where Name LIKE \"{driveIndex}%\""; - - using var perfData = new ManagementObjectSearcher(query) { Options = { Timeout = TimeSpan.FromSeconds(7.5) } }; - using ManagementObjectCollection collection = perfData.Get(); - using ManagementObject data = collection.OfType().FirstOrDefault(); - - if (data == null) - return; - - ulong value = (ulong)data.Properties["PercentDiskWriteTime"].Value; - ulong valueBase = (ulong)data.Properties["PercentDiskWriteTime_Base"].Value; - _perfWrite.Update(value, valueBase); - _sensorDiskWriteActivity.Value = (float)_perfWrite.Result; - - value = (ulong)data.Properties["PercentIdleTime"].Value; - valueBase = (ulong)data.Properties["PercentIdleTime_Base"].Value; - _perfTotal.Update(value, valueBase); - _sensorDiskTotalActivity.Value = (float)(100.0 - _perfTotal.Result); - - ulong readRateCounter = (ulong)data.Properties["DiskReadBytesPerSec"].Value; - ulong readRate = readRateCounter - _lastReadRateCounter; - _lastReadRateCounter = readRateCounter; - - ulong writeRateCounter = (ulong)data.Properties["DiskWriteBytesPerSec"].Value; - ulong writeRate = writeRateCounter - _lastWriteRateCounter; - _lastWriteRateCounter = writeRateCounter; - - ulong timestampPerfTime = (ulong)data.Properties["Timestamp_PerfTime"].Value; - ulong frequencyPerfTime = (ulong)data.Properties["Frequency_Perftime"].Value; - double currentTime = (double)timestampPerfTime / frequencyPerfTime; - - double timeDeltaSeconds = currentTime - _lastTime; - if (_lastTime == 0 || timeDeltaSeconds > 0.2) - { - double writeSpeed = writeRate * (1 / timeDeltaSeconds); - _sensorDiskWriteRate.Value = (float)writeSpeed; - - double readSpeed = readRate * (1 / timeDeltaSeconds); - _sensorDiskReadRate.Value = (float)readSpeed; - } - - if (_lastTime == 0 || timeDeltaSeconds > 0.2) - _lastTime = currentTime; - } - public override void Update() { - const int wmiRetries = 10; - - //update statistics from WMI on every update - if (_storageInfo != null && _wmiFailureCount <= wmiRetries) + // Update statistics. + if (_storageInfo != null) { try { - UpdateStatisticsFromWmi(_storageInfo.Index); - _wmiFailureCount = 0; + UpdatePerformanceSensors(); } catch { - if (++_wmiFailureCount == wmiRetries) - { - DeactivateSensor(_sensorDiskTotalActivity); - DeactivateSensor(_sensorDiskWriteActivity); - DeactivateSensor(_sensorDiskReadRate); - DeactivateSensor(_sensorDiskWriteRate); - } + // Ignored. } } - //read out with updateInterval + // Read out at update interval. TimeSpan tDiff = DateTime.UtcNow - _lastUpdate; if (tDiff > _updateInterval) { @@ -220,13 +168,56 @@ public abstract class AbstractStorage : Hardware } if (totalSize > 0) - _usageSensor.Value = 100.0f - ((100.0f * totalFreeSpace) / totalSize); + _usageSensor.Value = 100.0f - (100.0f * totalFreeSpace / totalSize); else _usageSensor.Value = null; } } } + private void UpdatePerformanceSensors() + { + if (!Kernel32.DeviceIoControl(_storageInfo.Handle, + Kernel32.IOCTL.IOCTL_DISK_PERFORMANCE, + IntPtr.Zero, + 0, + out Kernel32.DISK_PERFORMANCE diskPerformance, + Marshal.SizeOf(), + out _, + IntPtr.Zero)) + { + return; + } + + _perfWrite.Update(diskPerformance.WriteTime, diskPerformance.QueryTime); + _sensorDiskWriteActivity.Value = (float)_perfWrite.Result; + + _perfTotal.Update(diskPerformance.IdleTime, diskPerformance.QueryTime); + _sensorDiskTotalActivity.Value = (float)(100 - _perfTotal.Result); + + ulong readCount = diskPerformance.BytesRead; + ulong readDiff = readCount - _lastReadCount; + _lastReadCount = readCount; + + ulong writeCount = diskPerformance.BytesWritten; + ulong writeDiff = writeCount - _lastWriteCount; + _lastWriteCount = writeCount; + + long currentTime = Stopwatch.GetTimestamp(); + if (_lastTime != 0) + { + double timeDeltaSeconds = TimeSpan.FromTicks(currentTime - _lastTime).TotalSeconds; + + double writeSpeed = writeDiff * (1 / timeDeltaSeconds); + _sensorDiskWriteRate.Value = (float)writeSpeed; + + double readSpeed = readDiff * (1 / timeDeltaSeconds); + _sensorDiskReadRate.Value = (float)readSpeed; + } + + _lastTime = currentTime; + } + protected abstract void GetReport(StringBuilder r); public override string GetReport() @@ -294,4 +285,4 @@ public abstract class AbstractStorage : Hardware Result = 100; } } -} \ No newline at end of file +} diff --git a/LibreHardwareMonitorLib/Hardware/Storage/NVMeIntel.cs b/LibreHardwareMonitorLib/Hardware/Storage/NVMeIntel.cs index e3ab2d6..7de9218 100644 --- a/LibreHardwareMonitorLib/Hardware/Storage/NVMeIntel.cs +++ b/LibreHardwareMonitorLib/Hardware/Storage/NVMeIntel.cs @@ -7,6 +7,7 @@ using System; using System.Runtime.InteropServices; using System.Text; using LibreHardwareMonitor.Interop; +using Microsoft.Win32.SafeHandles; namespace LibreHardwareMonitor.Hardware.Storage; @@ -117,7 +118,7 @@ internal class NVMeIntel : INVMeDrive public static SafeHandle IdentifyDevice(StorageInfo storageInfo) { - SafeHandle handle = Kernel32.OpenDevice(storageInfo.Scsi); + SafeFileHandle handle = Kernel32.OpenDevice(storageInfo.Scsi); if (handle?.IsInvalid != false) return null; @@ -152,4 +153,4 @@ internal class NVMeIntel : INVMeDrive } return handle; } -} \ No newline at end of file +} diff --git a/LibreHardwareMonitorLib/Hardware/Storage/NVMeIntelRst.cs b/LibreHardwareMonitorLib/Hardware/Storage/NVMeIntelRst.cs index 344c0c5..ea574df 100644 --- a/LibreHardwareMonitorLib/Hardware/Storage/NVMeIntelRst.cs +++ b/LibreHardwareMonitorLib/Hardware/Storage/NVMeIntelRst.cs @@ -7,6 +7,7 @@ using System; using System.Runtime.InteropServices; using System.Text; using LibreHardwareMonitor.Interop; +using Microsoft.Win32.SafeHandles; namespace LibreHardwareMonitor.Hardware.Storage; @@ -118,7 +119,7 @@ internal class NVMeIntelRst : INVMeDrive public static SafeHandle IdentifyDevice(StorageInfo storageInfo) { - SafeHandle handle = Kernel32.OpenDevice(storageInfo.Scsi); + SafeFileHandle handle = Kernel32.OpenDevice(storageInfo.Scsi); if (handle?.IsInvalid != false) return null; diff --git a/LibreHardwareMonitorLib/Hardware/Storage/NVMeSamsung.cs b/LibreHardwareMonitorLib/Hardware/Storage/NVMeSamsung.cs index 152a0fd..0f3c824 100644 --- a/LibreHardwareMonitorLib/Hardware/Storage/NVMeSamsung.cs +++ b/LibreHardwareMonitorLib/Hardware/Storage/NVMeSamsung.cs @@ -7,6 +7,7 @@ using System; using System.Linq; using System.Runtime.InteropServices; using LibreHardwareMonitor.Interop; +using Microsoft.Win32.SafeHandles; namespace LibreHardwareMonitor.Hardware.Storage; @@ -179,7 +180,7 @@ internal class NVMeSamsung : INVMeDrive public static SafeHandle IdentifyDevice(StorageInfo storageInfo) { - SafeHandle handle = Kernel32.OpenDevice(storageInfo.DeviceId); + SafeFileHandle handle = Kernel32.OpenDevice(storageInfo.DeviceId); if (handle?.IsInvalid != false) return null; @@ -256,4 +257,4 @@ internal class NVMeSamsung : INVMeDrive return handle; } -} \ No newline at end of file +} diff --git a/LibreHardwareMonitorLib/Hardware/Storage/NVMeWindows.cs b/LibreHardwareMonitorLib/Hardware/Storage/NVMeWindows.cs index 5b161f9..add8653 100644 --- a/LibreHardwareMonitorLib/Hardware/Storage/NVMeWindows.cs +++ b/LibreHardwareMonitorLib/Hardware/Storage/NVMeWindows.cs @@ -6,6 +6,7 @@ using System; using System.Runtime.InteropServices; using LibreHardwareMonitor.Interop; +using Microsoft.Win32.SafeHandles; namespace LibreHardwareMonitor.Hardware.Storage; @@ -94,7 +95,7 @@ internal class NVMeWindows : INVMeDrive public static SafeHandle IdentifyDevice(StorageInfo storageInfo) { - SafeHandle handle = Kernel32.OpenDevice(storageInfo.DeviceId); + SafeFileHandle handle = Kernel32.OpenDevice(storageInfo.DeviceId); if (handle?.IsInvalid != false) return null; @@ -124,4 +125,4 @@ internal class NVMeWindows : INVMeDrive return handle; } -} \ No newline at end of file +} diff --git a/LibreHardwareMonitorLib/Hardware/Storage/StorageInfo.cs b/LibreHardwareMonitorLib/Hardware/Storage/StorageInfo.cs index 9971eaa..33fd147 100644 --- a/LibreHardwareMonitorLib/Hardware/Storage/StorageInfo.cs +++ b/LibreHardwareMonitorLib/Hardware/Storage/StorageInfo.cs @@ -5,6 +5,7 @@ // All Rights Reserved. using LibreHardwareMonitor.Interop; +using Microsoft.Win32.SafeHandles; namespace LibreHardwareMonitor.Hardware.Storage; @@ -16,6 +17,8 @@ internal abstract class StorageInfo public ulong DiskSize { get; set; } + public SafeFileHandle Handle { get; set; } + public int Index { get; protected set; } public string Name => (Vendor + " " + Product).Trim(); @@ -33,4 +36,4 @@ internal abstract class StorageInfo public string Serial { get; protected set; } public string Vendor { get; protected set; } -} \ No newline at end of file +} diff --git a/LibreHardwareMonitorLib/Hardware/Storage/WindowsStorage.cs b/LibreHardwareMonitorLib/Hardware/Storage/WindowsStorage.cs index 7e1d821..c56491e 100644 --- a/LibreHardwareMonitorLib/Hardware/Storage/WindowsStorage.cs +++ b/LibreHardwareMonitorLib/Hardware/Storage/WindowsStorage.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.Management; using System.Runtime.InteropServices; using LibreHardwareMonitor.Interop; +using Microsoft.Win32.SafeHandles; namespace LibreHardwareMonitor.Hardware.Storage; @@ -15,7 +16,7 @@ internal static class WindowsStorage { public static Storage.StorageInfo GetStorageInfo(string deviceId, uint driveIndex) { - using SafeHandle handle = Kernel32.OpenDevice(deviceId); + using SafeFileHandle handle = Kernel32.OpenDevice(deviceId); if (handle?.IsInvalid != false) return null; diff --git a/LibreHardwareMonitorLib/Interop/Kernel32.cs b/LibreHardwareMonitorLib/Interop/Kernel32.cs index 5d9faba..7ace0c7 100644 --- a/LibreHardwareMonitorLib/Interop/Kernel32.cs +++ b/LibreHardwareMonitorLib/Interop/Kernel32.cs @@ -80,9 +80,9 @@ public class Kernel32 return result; } - internal static SafeHandle OpenDevice(string devicePath) + internal static SafeFileHandle OpenDevice(string devicePath) { - SafeHandle hDevice = CreateFile(devicePath, FileAccess.ReadWrite, FileShare.ReadWrite, IntPtr.Zero, FileMode.Open, FileAttributes.Normal, IntPtr.Zero); + SafeFileHandle hDevice = CreateFile(devicePath, FileAccess.ReadWrite, FileShare.ReadWrite, IntPtr.Zero, FileMode.Open, FileAttributes.Normal, IntPtr.Zero); if (hDevice.IsInvalid || hDevice.IsClosed) hDevice = null; @@ -218,6 +218,20 @@ public class Kernel32 out uint lpBytesReturned, IntPtr lpOverlapped); + [DllImport(DllName, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool DeviceIoControl + ( + SafeHandle hDevice, + IOCTL dwIoControlCode, + IntPtr lpInBuffer, + int nInBufferSize, + out DISK_PERFORMANCE lpOutBuffer, + int nOutBufferSize, + out uint lpBytesReturned, + IntPtr lpOverlapped); + [DllImport(DllName, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)] [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] [return: MarshalAs(UnmanagedType.Bool)] @@ -395,6 +409,7 @@ public class Kernel32 IOCTL_SCSI_MINIPORT = 0x04d008, IOCTL_SCSI_PASS_THROUGH_DIRECT = 0x04d014, IOCTL_SCSI_GET_ADDRESS = 0x41018, + IOCTL_DISK_PERFORMANCE = 0x70020, IOCTL_STORAGE_QUERY_PROPERTY = 0x2D1400, IOCTL_BATTERY_QUERY_TAG = 0x294040, IOCTL_BATTERY_QUERY_INFORMATION = 0x294044, @@ -881,6 +896,49 @@ public class Kernel32 public STORAGE_BUS_TYPE BusType; public uint RawPropertiesLength; } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct DISK_PERFORMANCE + { + /// + public ulong BytesRead; + + /// + public ulong BytesWritten; + + /// + public ulong ReadTime; + + /// + public ulong WriteTime; + + /// + public ulong IdleTime; + + /// + public uint ReadCount; + + /// + public uint WriteCount; + + /// + public uint QueueDepth; + + /// + public uint SplitCount; + + /// + public ulong QueryTime; + + /// + public int StorageDeviceNumber; + + /// + public long StorageManagerName0; + + /// + public long StorageManagerName1; + } [StructLayout(LayoutKind.Sequential)] internal struct SRB_IO_CONTROL