InventoryAgent/Inventory.Core/HealthMonitor.cs

159 lines
7.0 KiB
C#

using LibreHardwareMonitor.Hardware;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Inventory.Core
{
public class HealthMonitor
{
private class UpdateVisitor : IVisitor
{
public void VisitComputer(IComputer computer)
{
computer.Traverse(this);
}
public void VisitHardware(IHardware hardware)
{
hardware.Update();
foreach (IHardware subHardware in hardware.SubHardware) subHardware.Accept(this);
}
public void VisitSensor(ISensor sensor) { }
public void VisitParameter(IParameter parameter) { }
}
private ISensor? FindSensor(IHardware hardware, SensorType type, params string[] names)
{
foreach (var name in names)
{
var sensor = hardware.Sensors.FirstOrDefault(s => s.SensorType == type && s.Name.Contains(name, StringComparison.OrdinalIgnoreCase));
if (sensor != null)
{
return sensor;
}
}
// If no name matches, return the first sensor of that type as a last resort.
return hardware.Sensors.FirstOrDefault(s => s.SensorType == type);
}
public HealthMetricsReport CollectHealthMetrics()
{
var report = new HealthMetricsReport();
try
{
var computer = new Computer
{
IsCpuEnabled = true,
IsGpuEnabled = true,
IsMemoryEnabled = true,
IsMotherboardEnabled = true, // For fan speeds
IsStorageEnabled = true,
IsBatteryEnabled = true
};
computer.Open();
computer.Accept(new UpdateVisitor());
// CPU Metrics
var cpu = computer.Hardware.FirstOrDefault(h => h.HardwareType == HardwareType.Cpu);
if (cpu != null)
{
report.CpuTemp = FindSensor(cpu, SensorType.Temperature, "CPU Package", "Core (Average)")?.Value;
report.CpuLoad = FindSensor(cpu, SensorType.Load, "CPU Total")?.Value;
report.CpuPower = FindSensor(cpu, SensorType.Power, "CPU Package", "CPU Total")?.Value;
}
// GPU Metrics
// Prioritize dedicated GPUs (Nvidia, AMD) over integrated (Intel)
var gpu = computer.Hardware.FirstOrDefault(h => h.HardwareType == HardwareType.GpuNvidia || h.HardwareType == HardwareType.GpuAmd)
?? computer.Hardware.FirstOrDefault(h => h.HardwareType == HardwareType.GpuIntel);
if (gpu != null)
{
report.GpuTemp = FindSensor(gpu, SensorType.Temperature, "GPU Core")?.Value;
report.GpuLoad = FindSensor(gpu, SensorType.Load, "GPU Core", "D3D 3D", "GPU Utilization")?.Value;
report.GpuPower = FindSensor(gpu, SensorType.Power, "GPU Package", "GPU Power", "GPU Total")?.Value;
report.GpuClock = FindSensor(gpu, SensorType.Clock, "GPU Core")?.Value;
}
// Memory Metrics
var memory = computer.Hardware.FirstOrDefault(h => h.HardwareType == HardwareType.Memory);
if (memory != null)
{
report.RamLoad = FindSensor(memory, SensorType.Load, "Memory")?.Value;
}
// Drive Health
foreach (var hardware in computer.Hardware.Where(h => h.HardwareType == HardwareType.Storage))
{
var tempSensor = hardware.Sensors.FirstOrDefault(s => s.SensorType == SensorType.Temperature);
var failureSensor = hardware.Sensors.FirstOrDefault(s => s.Name == "Predict Failure");
double? healthPercentage = null;
// Prioritize "Remaining Life" sensors, as they are a direct measure.
var remainingLifeSensor = hardware.Sensors.FirstOrDefault(s => s.SensorType == SensorType.Level && s.Name.Contains("Remaining Life"));
if (remainingLifeSensor?.Value != null)
{
healthPercentage = remainingLifeSensor.Value;
}
else
{
// Fallback to "Percentage Used" sensors.
var usedSensor = hardware.Sensors.FirstOrDefault(s => s.SensorType == SensorType.Level && s.Name.Contains("Used"));
if (usedSensor?.Value != null)
healthPercentage = 100 - usedSensor.Value;
}
report.DriveHealth.Add(new DriveHealthInfo {
Model = hardware.Name,
Temperature = tempSensor?.Value,
IsFailing = failureSensor?.Value == 1,
HealthPercentage = healthPercentage
});
}
// Fan Speeds (from all hardware)
// Recursively search for fan sensors in all hardware and sub-hardware.
foreach (var hardware in computer.Hardware)
{
CollectFanSpeedsRecursive(hardware, report.FanSpeeds);
}
// Battery Health
var battery = computer.Hardware.FirstOrDefault(h => h.HardwareType == HardwareType.Battery);
if (battery != null)
{
var fullChargeCapacity = FindSensor(battery, SensorType.Energy, "Full Charge Capacity", "Fully Charged Capacity")?.Value;
var designCapacity = FindSensor(battery, SensorType.Energy, "Design Capacity", "Designed Capacity")?.Value;
if (fullChargeCapacity.HasValue && designCapacity.HasValue && designCapacity > 0)
{
report.BatteryHealth = Math.Round((double)(fullChargeCapacity.Value / designCapacity.Value * 100));
}
}
computer.Close();
}
catch { /* Silently fail if LibreHardwareMonitor can't run */ }
return report;
}
private void CollectFanSpeedsRecursive(IHardware hardware, List<FanSpeedInfo> fanSpeeds)
{
// Add fans from the current hardware
foreach (var sensor in hardware.Sensors.Where(s => s.SensorType == SensorType.Fan && s.Value.HasValue))
{
fanSpeeds.Add(new FanSpeedInfo
{
Name = $"{hardware.Name} - {sensor.Name}",
Rpm = sensor.Value
});
}
// Recurse into sub-hardware
foreach (var subHardware in hardware.SubHardware)
CollectFanSpeedsRecursive(subHardware, fanSpeeds);
}
}
}