Files
Servers/MoonlightServers.Daemon/Helpers/SystemMetrics.Disk.cs

149 lines
4.6 KiB
C#

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();
}
}