using System.Diagnostics; namespace ZB.MOM.NatsNet.Server.Internal; /// /// Provides cross-platform process CPU and memory usage statistics. /// Mirrors the Go pse (Process Status Emulation) package, replacing /// per-platform implementations (rusage, /proc/stat, PDH) with /// . /// public static class ProcessStatsProvider { private static readonly Process _self = Process.GetCurrentProcess(); private static readonly int _processorCount = Environment.ProcessorCount; private static readonly object _lock = new(); private static TimeSpan _lastCpuTime; private static DateTime _lastSampleTime; private static double _cachedPcpu; private static long _cachedRss; private static long _cachedVss; static ProcessStatsProvider() { UpdateUsage(); StartPeriodicSampling(); } /// /// Returns the current process CPU percentage, RSS (bytes), and VSS (bytes). /// Values are refreshed approximately every second by a background timer. /// /// Percent CPU utilization (0–100 × core count). /// Resident set size in bytes. /// Virtual memory size in bytes. public static void ProcUsage(out double pcpu, out long rss, out long vss) { lock (_lock) { pcpu = _cachedPcpu; rss = _cachedRss; vss = _cachedVss; } } private static void UpdateUsage() { try { _self.Refresh(); var now = DateTime.UtcNow; var cpuTime = _self.TotalProcessorTime; lock (_lock) { var elapsed = now - _lastSampleTime; if (elapsed >= TimeSpan.FromMilliseconds(500)) { var cpuDelta = (cpuTime - _lastCpuTime).TotalSeconds; // Normalize against elapsed wall time. // Result is 0–100; does not multiply by ProcessorCount to match Go behaviour. _cachedPcpu = elapsed.TotalSeconds > 0 ? Math.Round(cpuDelta / elapsed.TotalSeconds * 1000.0) / 10.0 : 0; _lastSampleTime = now; _lastCpuTime = cpuTime; } _cachedRss = _self.WorkingSet64; _cachedVss = _self.VirtualMemorySize64; } } catch { // Suppress — diagnostics should never crash the server. } } private static void StartPeriodicSampling() { var timer = new Timer(_ => UpdateUsage(), null, dueTime: TimeSpan.FromSeconds(1), period: TimeSpan.FromSeconds(1)); // Keep timer alive for the process lifetime. GC.KeepAlive(timer); } // --- Windows PDH helpers (replaced by Process class in .NET) --- // The following methods exist to satisfy the porting mapping but delegate // to the cross-platform Process API above. internal static string GetProcessImageName() => Path.GetFileNameWithoutExtension(Environment.ProcessPath ?? _self.ProcessName); internal static void InitCounters() { // No-op: .NET Process class initializes lazily. } internal static double PdhOpenQuery() => 0; // Mapped to Process API. internal static double PdhAddCounter() => 0; internal static double PdhCollectQueryData() => 0; internal static double PdhGetFormattedCounterArrayDouble() => 0; internal static double GetCounterArrayData() => 0; }