Implemented node system statistics

This commit is contained in:
2026-03-21 18:21:09 +00:00
parent ba5e364c05
commit 6d447a0ff9
28 changed files with 1402 additions and 156 deletions

View File

@@ -0,0 +1,101 @@
namespace MoonlightServers.Daemon.Helpers;
public partial class SystemMetrics
{
/// <summary>Network throughput for a single interface, computed between two samples.</summary>
/// <param name="Name">Interface name, e.g. <c>eth0</c>, <c>ens3</c>.</param>
/// <param name="RxBytesPerSec">Received bytes per second.</param>
/// <param name="TxBytesPerSec">Transmitted bytes per second.</param>
/// <param name="RxPacketsPerSec">Received packets per second.</param>
/// <param name="TxPacketsPerSec">Transmitted packets per second.</param>
/// <param name="RxErrors">Cumulative receive error count (not a rate).</param>
/// <param name="TxErrors">Cumulative transmit error count (not a rate).</param>
public record NetworkInterfaceInfo(
string Name,
long RxBytesPerSec,
long TxBytesPerSec,
long RxPacketsPerSec,
long TxPacketsPerSec,
long RxErrors,
long TxErrors
);
// Network
private record RawNetLine(
string Iface,
long RxBytes,
long RxPackets,
long RxErrors,
long TxBytes,
long TxPackets,
long TxErrors
);
private static async Task<List<RawNetLine>> ReadRawNetStatsAsync()
{
var lines = await File.ReadAllLinesAsync("/proc/net/dev");
var result = new List<RawNetLine>();
// The first two lines are the column-header banner.
foreach (var line in lines.Skip(2))
{
var colon = line.IndexOf(':');
if (colon < 0)
continue;
var iface = line[..colon].Trim();
// Skip loopback and ephemeral veth pairs created by container runtimes.
if (iface == "lo" || iface.StartsWith("veth", StringComparison.Ordinal))
continue;
var f = line[(colon + 1)..].Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (f.Length < 16)
continue;
// Column layout (after the colon, 0-based):
// RX: [0]bytes [1]packets [2]errs [3]drop [4]fifo [5]frame [6]compressed [7]multicast
// TX: [8]bytes [9]packets [10]errs [11]drop [12]fifo [13]colls [14]carrier [15]compressed
result.Add(new RawNetLine(
Iface: iface,
RxBytes: long.Parse(f[0]),
RxPackets: long.Parse(f[1]),
RxErrors: long.Parse(f[2]),
TxBytes: long.Parse(f[8]),
TxPackets: long.Parse(f[9]),
TxErrors: long.Parse(f[10])
));
}
return result;
}
private static IReadOnlyList<NetworkInterfaceInfo> ComputeNetworkRates(
List<RawNetLine> s1,
List<RawNetLine> s2,
double intervalSecs
)
{
var prev = s1.ToDictionary(x => x.Iface);
var result = new List<NetworkInterfaceInfo>();
var div = intervalSecs > 0 ? intervalSecs : 1.0;
foreach (var cur in s2)
{
if (!prev.TryGetValue(cur.Iface, out var p))
continue;
result.Add(new NetworkInterfaceInfo(
Name: cur.Iface,
RxBytesPerSec: (long)((cur.RxBytes - p.RxBytes) / div),
TxBytesPerSec: (long)((cur.TxBytes - p.TxBytes) / div),
RxPacketsPerSec: (long)((cur.RxPackets - p.RxPackets) / div),
TxPacketsPerSec: (long)((cur.TxPackets - p.TxPackets) / div),
RxErrors: cur.RxErrors,
TxErrors: cur.TxErrors
));
}
return result;
}
}