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