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 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); } } }