using System.IO; using System.Runtime.InteropServices; namespace AxCopilot.Services; internal sealed class PerformanceMonitorService { public static readonly PerformanceMonitorService Instance = new(); [StructLayout(LayoutKind.Sequential)] private struct FILETIME { public uint Low; public uint High; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] private struct MEMORYSTATUSEX { public uint dwLength; public uint dwMemoryLoad; public ulong ullTotalPhys; public ulong ullAvailPhys; public ulong ullTotalPageFile; public ulong ullAvailPageFile; public ulong ullTotalVirtual; public ulong ullAvailVirtual; public ulong ullAvailExtendedVirtual; } [DllImport("kernel32.dll", SetLastError = true)] private static extern bool GetSystemTimes(out FILETIME idle, out FILETIME kernel, out FILETIME user); [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern bool GlobalMemoryStatusEx(ref MEMORYSTATUSEX lpBuffer); public double CpuPercent { get; private set; } public double RamPercent { get; private set; } public string RamText { get; private set; } = ""; public double DiskCPercent { get; private set; } public string DiskCText { get; private set; } = ""; private System.Threading.Timer? _timer; private FILETIME _prevIdle; private FILETIME _prevKernel; private FILETIME _prevUser; private bool _hasPrev; private PerformanceMonitorService() { } public void StartPolling() { if (_timer != null) return; _timer = new System.Threading.Timer(_ => Sample(), null, 0, 2000); } public void StopPolling() { _timer?.Dispose(); _timer = null; _hasPrev = false; } private void Sample() { SampleCpu(); SampleRam(); SampleDisk(); } private void SampleCpu() { try { if (!GetSystemTimes(out var idle, out var kernel, out var user)) return; if (_hasPrev) { var idleDelta = ToUlong(idle) - ToUlong(_prevIdle); var kernelDelta = ToUlong(kernel) - ToUlong(_prevKernel); var userDelta = ToUlong(user) - ToUlong(_prevUser); var total = kernelDelta + userDelta; var busy = total - idleDelta; CpuPercent = total > 0 ? Math.Clamp(100.0 * busy / total, 0, 100) : 0; } _prevIdle = idle; _prevKernel = kernel; _prevUser = user; _hasPrev = true; } catch { } } private void SampleRam() { try { var mem = new MEMORYSTATUSEX { dwLength = (uint)Marshal.SizeOf() }; if (!GlobalMemoryStatusEx(ref mem)) return; RamPercent = mem.dwMemoryLoad; var usedGb = (mem.ullTotalPhys - mem.ullAvailPhys) / (1024.0 * 1024 * 1024); var totalGb = mem.ullTotalPhys / (1024.0 * 1024 * 1024); RamText = $"{usedGb:F1}/{totalGb:F0}GB"; } catch { } } private void SampleDisk() { try { var drive = DriveInfo.GetDrives() .FirstOrDefault(d => d.IsReady && d.Name.StartsWith("C", StringComparison.OrdinalIgnoreCase)); if (drive == null) return; var usedBytes = drive.TotalSize - drive.AvailableFreeSpace; DiskCPercent = 100.0 * usedBytes / drive.TotalSize; var usedGb = usedBytes / (1024.0 * 1024 * 1024); var totalGb = drive.TotalSize / (1024.0 * 1024 * 1024); DiskCText = $"C:{usedGb:F0}/{totalGb:F0}GB"; } catch { } } private static ulong ToUlong(FILETIME ft) => ((ulong)ft.High << 32) | ft.Low; }