- .NET 8 WPF app with full MVVM architecture - One-click boost: CPU, RAM, GPU, network, services, timer resolution - Real-time system monitor (LibreHardwareMonitor) - Auto game detection (30+ games) - RAM optimizer with kernel-level cleanup - Network optimizer (TCP registry tweaks) - Service manager, process manager, shader cache cleaner - Desktop widget overlay - Debug log viewer - Custom dark gaming theme with trans pride colors - 84 unit tests (xUnit) - Inno Setup installer script - Landing page website
158 lines
5.1 KiB
C#
158 lines
5.1 KiB
C#
using System.Diagnostics;
|
||
using System.Runtime.InteropServices;
|
||
using LibreHardwareMonitor.Hardware;
|
||
using SpdUp.Core;
|
||
using SpdUp.Models;
|
||
|
||
namespace SpdUp.Services;
|
||
|
||
/// <summary>
|
||
/// Live system monitoring using performance counters + LibreHardwareMonitor.
|
||
/// Designed for low overhead – one shared timer drives all consumers.
|
||
/// </summary>
|
||
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<SystemMetrics>? 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.MEMORYSTATUSEX>() };
|
||
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 { }
|
||
}
|
||
}
|