using System.Diagnostics; using System.Runtime.InteropServices; using LibreHardwareMonitor.Hardware; using SpdUp.Core; using SpdUp.Models; namespace SpdUp.Services; /// /// Live system monitoring using performance counters + LibreHardwareMonitor. /// Designed for low overhead – one shared timer drives all consumers. /// public sealed class MonitoringService : IDisposable { private readonly Timer _timer; private readonly Computer _computer; private readonly PerformanceCounter _cpuCounter; private readonly NetworkService _networkService = new(); private readonly string _pingTarget; private SystemMetrics _latest = new(); private bool _disposed; public event Action? MetricsUpdated; public SystemMetrics Latest => _latest; public MonitoringService(string pingTarget = "8.8.8.8") { _pingTarget = pingTarget; // CPU % counter _cpuCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total", readOnly: true); _cpuCounter.NextValue(); // prime the counter // LibreHardwareMonitor for temps + GPU _computer = new Computer { IsCpuEnabled = true, IsGpuEnabled = true, IsMemoryEnabled = true, }; try { _computer.Open(); } catch (Exception ex) { Logger.Warn($"LibreHardwareMonitor init warning: {ex.Message}"); } _timer = new Timer(Tick, null, Timeout.Infinite, Timeout.Infinite); } public void Start(int intervalMs = 1000) { _timer.Change(0, intervalMs); Logger.Info($"Monitoring started ({intervalMs} ms interval)."); } public void Stop() { _timer.Change(Timeout.Infinite, Timeout.Infinite); Logger.Info("Monitoring stopped."); } public void SetInterval(int intervalMs) { _timer.Change(0, intervalMs); } private async void Tick(object? state) { if (_disposed) return; try { float cpuUsage = _cpuCounter.NextValue(); // Memory var mem = new NativeMethods.MEMORYSTATUSEX { dwLength = (uint)Marshal.SizeOf() }; NativeMethods.GlobalMemoryStatusEx(ref mem); float ramTotal = mem.ullTotalPhys / 1048576f; float ramUsed = ramTotal - (mem.ullAvailPhys / 1048576f); // Hardware sensors float cpuTemp = 0, gpuUsage = 0, gpuTemp = 0; try { foreach (var hw in _computer.Hardware) { hw.Update(); foreach (var sensor in hw.Sensors) { if (hw.HardwareType == HardwareType.Cpu && sensor.SensorType == SensorType.Temperature && sensor.Name.Contains("Package", StringComparison.OrdinalIgnoreCase)) { cpuTemp = sensor.Value ?? 0; } if ((hw.HardwareType == HardwareType.GpuNvidia || hw.HardwareType == HardwareType.GpuAmd || hw.HardwareType == HardwareType.GpuIntel)) { if (sensor.SensorType == SensorType.Load && sensor.Name.Contains("Core", StringComparison.OrdinalIgnoreCase)) gpuUsage = sensor.Value ?? 0; if (sensor.SensorType == SensorType.Temperature && sensor.Name.Contains("Core", StringComparison.OrdinalIgnoreCase)) gpuTemp = sensor.Value ?? 0; } } foreach (var sub in hw.SubHardware) { sub.Update(); foreach (var sensor in sub.Sensors) { if (sensor.SensorType == SensorType.Temperature && sensor.Name.Contains("Package", StringComparison.OrdinalIgnoreCase)) cpuTemp = sensor.Value ?? cpuTemp; } } } } catch { /* sensor read failure – continue with zeros */ } // Ping long pingMs = await NetworkService.PingAsync(_pingTarget, 1500); _latest = new SystemMetrics { CpuUsage = cpuUsage, RamUsageMB = ramUsed, RamTotalMB = ramTotal, GpuUsage = gpuUsage, CpuTemperature = cpuTemp, GpuTemperature = gpuTemp, NetworkPingMs = pingMs, Fps = 0, // FPS hook not implemented without injection }; MetricsUpdated?.Invoke(_latest); } catch (Exception ex) { Logger.Error("Monitoring tick error", ex); } } public void Dispose() { if (_disposed) return; _disposed = true; _timer.Dispose(); _cpuCounter.Dispose(); try { _computer.Close(); } catch { } } }