feat(core): add system metrics text parsers

This commit is contained in:
dualfroz committed 2026-08-24 18:49:34 +02:00
1 parent daf94de8e0
commit 9e09780cc0
6 files changed
+171

No files matched your search

@@ -0,0 +1,16 @@
namespace Vantage.Core.Metrics.Parsing;
public static class CpuUsage
{
public static double Compute(CpuTimes previous, CpuTimes current)
{
var totalDelta = current.TotalTime - previous.TotalTime;
var idleDelta = current.IdleTime - previous.IdleTime;
if (totalDelta <= 0)
return 0;
var busyRatio = (double)(totalDelta - idleDelta) / totalDelta;
return Math.Clamp(Math.Round(busyRatio * 100.0, 1), 0, 100);
}
}
@@ -0,0 +1,30 @@
using System.Globalization;
namespace Vantage.Core.Metrics.Parsing;
public readonly record struct DiskUsage(long UsedBytes, long TotalBytes);
public static class DfParser
{
public static DiskUsage Parse(string dfOutput)
{
var lines = dfOutput
.Split('\n', StringSplitOptions.RemoveEmptyEntries)
.Select(l => l.Trim())
.Where(l => l.Length > 0)
.ToArray();
if (lines.Length < 2)
throw new FormatException("df output has no data row");
var dataLine = lines[^1];
var fields = dataLine.Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (fields.Length < 5)
throw new FormatException($"Unexpected df row: '{dataLine}'");
var totalBytes = long.Parse(fields[^5], CultureInfo.InvariantCulture);
var usedBytes = long.Parse(fields[^4], CultureInfo.InvariantCulture);
return new DiskUsage(usedBytes, totalBytes);
}
}
@@ -0,0 +1,20 @@
using System.Globalization;
namespace Vantage.Core.Metrics.Parsing;
public readonly record struct LoadAverage(double Load1, double Load5, double Load15);
public static class LoadAvgParser
{
public static LoadAverage Parse(string procLoadavg)
{
var fields = procLoadavg.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
if (fields.Length < 3)
throw new FormatException($"Unexpected /proc/loadavg content: '{procLoadavg}'");
return new LoadAverage(
double.Parse(fields[0], CultureInfo.InvariantCulture),
double.Parse(fields[1], CultureInfo.InvariantCulture),
double.Parse(fields[2], CultureInfo.InvariantCulture));
}
}
@@ -0,0 +1,56 @@
using System.Globalization;
namespace Vantage.Core.Metrics.Parsing;
public readonly record struct MemoryInfo(long UsedBytes, long TotalBytes);
public static class MemInfoParser
{
public static MemoryInfo Parse(string procMeminfo)
{
long? totalKb = null;
long? availableKb = null;
long? freeKb = null;
foreach (var rawLine in procMeminfo.Split('\n'))
{
var line = rawLine.Trim();
if (line.Length == 0)
continue;
var colon = line.IndexOf(':');
if (colon <= 0)
continue;
var key = line[..colon].Trim();
var value = ParseKilobytes(line[(colon + 1)..]);
switch (key)
{
case "MemTotal":
totalKb = value;
break;
case "MemAvailable":
availableKb = value;
break;
case "MemFree":
freeKb = value;
break;
}
}
if (totalKb is null)
throw new FormatException("MemTotal not found in /proc/meminfo");
var availabilityKb = availableKb ?? freeKb ?? 0;
var usedKb = Math.Max(0, totalKb.Value - availabilityKb);
return new MemoryInfo(usedKb * 1024, totalKb.Value * 1024);
}
private static long ParseKilobytes(string valueWithUnit)
{
var fields = valueWithUnit.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
return long.Parse(fields[0], CultureInfo.InvariantCulture);
}
}
@@ -0,0 +1,33 @@
using System.Globalization;
namespace Vantage.Core.Metrics.Parsing;
public readonly record struct CpuTimes(long IdleTime, long TotalTime);
public static class ProcStatParser
{
public static CpuTimes Parse(string procStat)
{
foreach (var rawLine in procStat.Split('\n'))
{
var line = rawLine.Trim();
if (!line.StartsWith("cpu ", StringComparison.Ordinal) && line != "cpu")
continue;
var fields = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
long total = 0;
long idle = 0;
for (var i = 1; i < fields.Length; i++)
{
var value = long.Parse(fields[i], CultureInfo.InvariantCulture);
total += value;
if (i == 4 || i == 5)
idle += value;
}
return new CpuTimes(idle, total);
}
throw new FormatException("Aggregate 'cpu' line not found in /proc/stat");
}
}
@@ -0,0 +1,16 @@
using System.Globalization;
namespace Vantage.Core.Metrics.Parsing;
public static class UptimeParser
{
public static TimeSpan Parse(string procUptime)
{
var fields = procUptime.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
if (fields.Length < 1)
throw new FormatException($"Unexpected /proc/uptime content: '{procUptime}'");
var seconds = double.Parse(fields[0], CultureInfo.InvariantCulture);
return TimeSpan.FromSeconds(seconds);
}
}