Implemented node system statistics
This commit is contained in:
149
MoonlightServers.Daemon/Helpers/SystemMetrics.Disk.cs
Normal file
149
MoonlightServers.Daemon/Helpers/SystemMetrics.Disk.cs
Normal file
@@ -0,0 +1,149 @@
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MoonlightServers.Daemon.Helpers;
|
||||
|
||||
public partial class SystemMetrics
|
||||
{
|
||||
public record DiskInfo(
|
||||
string MountPoint,
|
||||
string Device,
|
||||
string FileSystem,
|
||||
long TotalBytes,
|
||||
long UsedBytes,
|
||||
long FreeBytes,
|
||||
double UsedPercent,
|
||||
long InodesTotal,
|
||||
long InodesUsed,
|
||||
long InodesFree,
|
||||
double InodesUsedPercent
|
||||
);
|
||||
|
||||
private static readonly HashSet<string> IgnoredFileSystems = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"overlay", "aufs", // Container image layers
|
||||
"tmpfs", "devtmpfs", "ramfs", // RAM-backed virtual filesystems
|
||||
"sysfs", "proc", "devpts", // Kernel virtual filesystems
|
||||
"cgroup", "cgroup2",
|
||||
"pstore", "securityfs", "debugfs", "tracefs",
|
||||
"mqueue", "hugetlbfs", "fusectl", "configfs",
|
||||
"binfmt_misc", "nsfs", "rpc_pipefs",
|
||||
"squashfs", // Snap package loop mounts
|
||||
};
|
||||
|
||||
private static readonly string[] IgnoredMountPrefixes =
|
||||
{
|
||||
"/sys",
|
||||
"/proc",
|
||||
"/dev",
|
||||
"/run/docker",
|
||||
"/var/lib/docker",
|
||||
"/var/lib/containers",
|
||||
"/boot", "/boot/efi"
|
||||
};
|
||||
|
||||
private static async Task<IReadOnlyList<DiskInfo>> ReadDisksAsync()
|
||||
{
|
||||
var lines = await File.ReadAllLinesAsync("/proc/mounts");
|
||||
var results = new List<DiskInfo>();
|
||||
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var parts = line.Split(' ');
|
||||
if (parts.Length < 4) continue;
|
||||
|
||||
var device = parts[0];
|
||||
var mountPoint = UnescapeOctal(parts[1]);
|
||||
var fsType = parts[2];
|
||||
|
||||
if (IgnoredFileSystems.Contains(fsType)) continue;
|
||||
if (IgnoredMountPrefixes.Any(p => mountPoint.StartsWith(p, StringComparison.Ordinal))) continue;
|
||||
if (device == "none" || device.StartsWith("//", StringComparison.Ordinal)) continue;
|
||||
|
||||
var disk = ReadSingleDisk(device, mountPoint, fsType);
|
||||
|
||||
if (disk is not null)
|
||||
results.Add(disk);
|
||||
}
|
||||
|
||||
return results
|
||||
.GroupBy(d => d.Device)
|
||||
.Select(g => g.OrderBy(d => d.MountPoint.Length).First())
|
||||
.OrderBy(d => d.MountPoint)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static DiskInfo? ReadSingleDisk(string device, string mountPoint, string fsType)
|
||||
{
|
||||
if (NativeMethods.StatVfs(mountPoint, out var st) != 0)
|
||||
{
|
||||
var errno = Marshal.GetLastPInvokeError();
|
||||
Console.WriteLine($"statvfs({mountPoint}) failed: errno {errno} ({new Win32Exception(errno).Message})");
|
||||
return null;
|
||||
}
|
||||
|
||||
var blockSize = (long)st.frsize;
|
||||
var totalBytes = (long)st.blocks * blockSize;
|
||||
var freeBytes = (long)st.bavail * blockSize;
|
||||
|
||||
if (totalBytes <= 0) return null;
|
||||
|
||||
var usedBytes = totalBytes - ((long)st.bfree * blockSize);
|
||||
var usedPct = Math.Round((double)usedBytes / totalBytes * 100.0, 2);
|
||||
|
||||
long inodesTotal, inodesUsed, inodesFree;
|
||||
double inodePct;
|
||||
|
||||
if (st.files == 0)
|
||||
{
|
||||
// Filesystem doesn't expose inode counts (FAT, exFAT, NTFS via ntfs-3g, etc.)
|
||||
inodesTotal = inodesUsed = inodesFree = -1;
|
||||
inodePct = -1.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
inodesTotal = (long)st.files;
|
||||
inodesFree = (long)st.ffree;
|
||||
inodesUsed = inodesTotal - inodesFree;
|
||||
inodePct = Math.Round((double)inodesUsed / inodesTotal * 100.0, 2);
|
||||
}
|
||||
|
||||
return new DiskInfo(
|
||||
MountPoint: mountPoint,
|
||||
Device: device,
|
||||
FileSystem: fsType,
|
||||
TotalBytes: totalBytes,
|
||||
UsedBytes: usedBytes,
|
||||
FreeBytes: freeBytes,
|
||||
UsedPercent: usedPct,
|
||||
InodesTotal: inodesTotal,
|
||||
InodesUsed: inodesUsed,
|
||||
InodesFree: inodesFree,
|
||||
InodesUsedPercent: inodePct
|
||||
);
|
||||
}
|
||||
|
||||
private static string UnescapeOctal(string s)
|
||||
{
|
||||
if (!s.Contains('\\')) return s;
|
||||
|
||||
var sb = new System.Text.StringBuilder(s.Length);
|
||||
for (var i = 0; i < s.Length; i++)
|
||||
{
|
||||
if (s[i] == '\\' && i + 3 < s.Length
|
||||
&& s[i + 1] is >= '0' and <= '7'
|
||||
&& s[i + 2] is >= '0' and <= '7'
|
||||
&& s[i + 3] is >= '0' and <= '7')
|
||||
{
|
||||
sb.Append((char)((s[i + 1] - '0') * 64 + (s[i + 2] - '0') * 8 + (s[i + 3] - '0')));
|
||||
i += 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(s[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user