Files
ReLibreHardwareMonitor/Hardware/HDD/AbstractStorage.cs
T

267 lines
9.3 KiB
C#

// Mozilla Public License 2.0
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors
// All Rights Reserved
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Management;
using System.Text;
using OpenHardwareMonitor.Interop;
namespace OpenHardwareMonitor.Hardware.HDD {
public abstract class AbstractStorage : Hardware {
private readonly PerformanceValue perfTotal = new PerformanceValue();
private readonly PerformanceValue perfWrite = new PerformanceValue();
private readonly StorageInfo storageInfo;
private readonly TimeSpan updateInterval = TimeSpan.FromSeconds(60);
private bool canUpdateWmi = true;
private double lastTime;
private DateTime lastUpdate = DateTime.MinValue;
private ulong readRateCounterlast;
private Sensor sensorDiskReadRate;
private Sensor sensorDiskTotalActivity;
private Sensor sensorDiskWriteActivity;
private Sensor sensorDiskWriteRate;
private Sensor usageSensor;
private ulong writeRateCounterlast;
internal AbstractStorage(StorageInfo storageInfo, string name, string firmwareRevision, string id, int index, ISettings settings)
: base(name, new Identifier(id, index.ToString(CultureInfo.InvariantCulture)), settings) {
this.storageInfo = storageInfo;
FirmwareRevision = firmwareRevision;
Index = index;
string[] logicalDrives = WindowsStorage.GetLogicalDrives(index);
var driveInfoList = new List<DriveInfo>(logicalDrives.Length);
foreach (string logicalDrive in logicalDrives) {
try {
var di = new DriveInfo(logicalDrive);
if (di.TotalSize > 0)
driveInfoList.Add(new DriveInfo(logicalDrive));
} catch (ArgumentException) { } catch (IOException) { } catch (UnauthorizedAccessException) { }
}
DriveInfos = driveInfoList.ToArray();
}
public DriveInfo[] DriveInfos { get; }
public string FirmwareRevision { get; }
public override HardwareType HardwareType => HardwareType.HDD;
public int Index { get; }
public static AbstractStorage CreateInstance(string deviceId, uint driveNumber, ulong diskSize, int scsiPort, ISettings settings) {
StorageInfo info = WindowsStorage.GetStorageInfo(deviceId, driveNumber);
info.DiskSize = diskSize;
info.DeviceId = deviceId;
info.Scsi = $@"\\.\SCSI{scsiPort}:";
if (info.Removable || info.BusType == Kernel32.STORAGE_BUS_TYPE.BusTypeVirtual || info.BusType == 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) {
var x = NVMeGeneric.CreateInstance(info, settings);
if (x != null)
return x;
}
if (info.BusType == Kernel32.STORAGE_BUS_TYPE.BusTypeAta ||
info.BusType == Kernel32.STORAGE_BUS_TYPE.BusTypeSata ||
info.BusType == Kernel32.STORAGE_BUS_TYPE.BusTypeNvme) {
return ATAStorage.CreateInstance(info, settings);
}
return StorageGeneric.CreateInstance(info, settings);
}
protected virtual void CreateSensors() {
if (DriveInfos.Length > 0) {
usageSensor = new Sensor("Used Space", 0, SensorType.Load, this, settings);
ActivateSensor(usageSensor);
}
sensorDiskWriteActivity = new Sensor("Write Activity", 32, SensorType.Load, this, settings);
ActivateSensor(sensorDiskWriteActivity);
sensorDiskTotalActivity = new Sensor("Total Activity", 33, SensorType.Load, this, settings);
ActivateSensor(sensorDiskTotalActivity);
sensorDiskReadRate = new Sensor("Read Rate", 34, SensorType.Throughput, this, settings);
ActivateSensor(sensorDiskReadRate);
sensorDiskWriteRate = new Sensor("Write Rate", 35, SensorType.Throughput, this, settings);
ActivateSensor(sensorDiskWriteRate);
}
protected abstract void UpdateSensors();
private void UpdateStatisticsFromWmi(int driveIndex) {
string query = $"SELECT * FROM Win32_PerfRawData_PerfDisk_PhysicalDisk Where Name LIKE \"{driveIndex}%\"";
var perfData = new ManagementObjectSearcher(query);
var data = perfData.Get().OfType<ManagementObject>().FirstOrDefault();
if (data == null) {
perfData.Dispose();
return;
}
var value = (ulong) data.Properties["PercentDiskWriteTime"].Value;
var 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 - readRateCounterlast;
readRateCounterlast = readRateCounter;
ulong writeRateCounter = (ulong) data.Properties["DiskWriteBytesPerSec"].Value;
ulong writeRate = writeRateCounter - writeRateCounterlast;
writeRateCounterlast = 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;
writeSpeed *= (1 / timeDeltaSeconds);
sensorDiskWriteRate.Value = (float) writeSpeed;
double readSpeed = readRate;
readSpeed *= (1 / timeDeltaSeconds);
sensorDiskReadRate.Value = (float) readSpeed;
}
if (lastTime == 0 || timeDeltaSeconds > 0.2) {
lastTime = currentTime;
}
perfData.Dispose();
}
public override void Update() {
//update statistics from WMI on every update
if (storageInfo != null && canUpdateWmi) {
try {
UpdateStatisticsFromWmi(storageInfo.Index);
} catch (ManagementException managementException) {
// Invalid query.
if (managementException.HResult == -2146233087) {
DeactivateSensor(sensorDiskTotalActivity);
DeactivateSensor(sensorDiskWriteActivity);
DeactivateSensor(sensorDiskReadRate);
DeactivateSensor(sensorDiskWriteRate);
canUpdateWmi = false;
}
} catch { }
}
//read out with updateInterval
var tDiff = DateTime.UtcNow - lastUpdate;
if (tDiff > updateInterval) {
lastUpdate = DateTime.UtcNow;
UpdateSensors();
if (usageSensor != null) {
long totalSize = 0;
long totalFreeSpace = 0;
for (int i = 0; i < DriveInfos.Length; i++) {
if (!DriveInfos[i].IsReady)
continue;
try {
totalSize += DriveInfos[i].TotalSize;
totalFreeSpace += DriveInfos[i].TotalFreeSpace;
} catch (IOException) { } catch (UnauthorizedAccessException) { }
}
if (totalSize > 0) {
usageSensor.Value = 100.0f - (100.0f * totalFreeSpace) / totalSize;
} else {
usageSensor.Value = null;
}
}
}
}
protected abstract void GetReport(StringBuilder r);
public override string GetReport() {
var r = new StringBuilder();
r.AppendLine("Storage");
r.AppendLine();
r.AppendLine("Drive Name: " + name);
r.AppendLine("Firmware Version: " + FirmwareRevision);
r.AppendLine();
GetReport(r);
foreach (DriveInfo di in DriveInfos) {
if (!di.IsReady)
continue;
try {
r.AppendLine("Logical Drive Name: " + di.Name);
r.AppendLine("Format: " + di.DriveFormat);
r.AppendLine("Total Size: " + di.TotalSize);
r.AppendLine("Total Free Space: " + di.TotalFreeSpace);
r.AppendLine();
} catch (IOException) { } catch (UnauthorizedAccessException) { }
}
return r.ToString();
}
public override void Traverse(IVisitor visitor) {
foreach (ISensor sensor in Sensors)
sensor.Accept(visitor);
}
/// <summary>
/// Helper to calculate the disk performance with base timestamps
/// https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/win32-perfrawdata
/// </summary>
private class PerformanceValue {
public double Result { get; private set; }
private ulong Time { get; set; }
private ulong Value { get; set; }
public void Update(ulong val, ulong valBase) {
ulong diffValue = val - Value;
ulong diffTime = valBase - Time;
Value = val;
Time = valBase;
Result = 100.0 / diffTime * diffValue;
//sometimes it is possible that diff_value > diff_timebase
//limit result to 100%, this is because timing issues during read from pcie controller an latency between IO operation
if (Result > 100)
Result = 100;
}
}
}
}