namespace MoonlightServers.Daemon.Helpers; public partial class SystemMetrics { public record CpuSnapshot( string ModelName, double TotalUsagePercent, IReadOnlyList CoreUsagePercents ); private record RawCpuLine( long User, long Nice, long System, long Idle, long Iowait, long Irq, long Softirq, long Steal ); private static async Task> ReadRawCpuStatsAsync() { var lines = await File.ReadAllLinesAsync("/proc/stat"); var result = new List(); foreach (var line in lines) { // All cpu* lines appear at the top of the file; stop on the first non-cpu line. if (!line.StartsWith("cpu", StringComparison.Ordinal)) break; var p = line.Split(' ', StringSplitOptions.RemoveEmptyEntries); if (p.Length < 8) continue; result.Add(new RawCpuLine( User: long.Parse(p[1]), Nice: long.Parse(p[2]), System: long.Parse(p[3]), Idle: long.Parse(p[4]), Iowait: long.Parse(p[5]), Irq: long.Parse(p[6]), Softirq: long.Parse(p[7]), Steal: p.Length > 8 ? long.Parse(p[8]) : 0L )); } return result; } private static async Task ReadCpuModelNameAsync() { var lines = await File.ReadAllLinesAsync("/proc/cpuinfo"); var line = lines.FirstOrDefault(l => l.StartsWith("model name", StringComparison.OrdinalIgnoreCase)); return line is not null ? line.Split(':')[1].Trim() : "Unknown"; } private static CpuSnapshot ComputeCpuUsage(string modelName, List s1, List s2) { // Index 0 = aggregate "cpu" row; indices 1+ = "cpu0", "cpu1" var totalUsage = s1.Count > 0 ? Usage(s1[0], s2[0]) : 0.0; var coreUsages = s1.Skip(1).Zip(s2.Skip(1), Usage).ToList(); return new CpuSnapshot(modelName, totalUsage, coreUsages); static double Usage(RawCpuLine a, RawCpuLine b) { var idleDelta = (b.Idle + b.Iowait) - (a.Idle + a.Iowait); var totalDelta = (b.User + b.Nice + b.System + b.Idle + b.Iowait + b.Irq + b.Softirq + b.Steal) - (a.User + a.Nice + a.System + a.Idle + a.Iowait + a.Irq + a.Softirq + a.Steal); return totalDelta <= 0 ? 0.0 : Math.Round((1.0 - (double)idleDelta / totalDelta) * 100.0, 2); } } }