feat(core): add system metrics text parsers
This commit is contained in:
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);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user