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 { }
}
}