feat: use Alpine AOT images, static SQLite and Podman host metrics [CI]

This commit is contained in:
geruipu committed 2026-10-07 01:03:31 +08:00
1 parent 6e945657c1
commit 103ef27ed3
37 files changed
+719 -309

No files matched your search

+4
View File
@@ -8,6 +8,10 @@
<EventSourceSupport>false</EventSourceSupport>
<DebuggerSupport>false</DebuggerSupport>
<HttpActivityPropagationSupport>false</HttpActivityPropagationSupport>
<Http3Support>false</Http3Support>
<MetricsSupport>false</MetricsSupport>
<UseSizeOptimizedLinq>true</UseSizeOptimizedLinq>
<JsonSerializerIsReflectionEnabledByDefault>false</JsonSerializerIsReflectionEnabledByDefault>
<ServerGarbageCollection>false</ServerGarbageCollection>
<ConcurrentGarbageCollection>false</ConcurrentGarbageCollection>
</PropertyGroup>
@@ -6,23 +6,30 @@ namespace Monitor.Agent;
/// <summary>
/// 主机资源占用的只读快照:CPU 占用、内存、根分区、网卡累计流量与瞬时网速、系统运行时长。
/// 只有汇总的数字:<b>不采集 CPU 型号、主机名、网卡名和任何 IP 地址</b>,所以面板和展示页想泄漏也泄漏不出去。
/// Linux 读 <c>/proc</c>;读不到的项是 null(例如 Windows 上的 CPU)。不执行任何命令,不写文件。
/// Linux 读 <c>/proc</c> 和 <c>/sys</c>;容器通过 HOST_PROC、HOST_SYS、HOST_ROOT 指定宿主路径。
/// 读不到的项是 null(例如 Windows 上的 CPU)。不执行任何命令,不写文件。
/// </summary>
public sealed class HostSampler
{
private static readonly string[] s_virtualPrefixes = ["lo", "veth", "docker", "br-", "virbr", "cni", "flannel", "cali", "tun", "tap"];
private static readonly string[] s_virtualPrefixes = ["lo", "veth", "docker", "podman", "br-", "virbr", "cni", "flannel", "cali", "tun", "tap"];
private readonly TimeProvider _time;
private readonly Func<string, string?> _read;
private readonly Func<(long Total, long Used)?> _disk;
private readonly string _procRoot;
private readonly string _sysRoot;
private (long Total, long Idle)? _previousCpu;
private (long At, long Rx, long Tx)? _previousNet;
public HostSampler(TimeProvider time, Func<string, string?>? read = null, Func<(long Total, long Used)?>? disk = null)
public HostSampler(TimeProvider time, Func<string, string?>? read = null, Func<(long Total, long Used)?>? disk = null,
string? procRoot = null, string? sysRoot = null, string? diskRoot = null)
{
_time = time;
_read = read ?? ReadProc;
_disk = disk ?? ReadRootDisk;
_procRoot = Root(procRoot, "HOST_PROC", "/proc");
_sysRoot = Root(sysRoot, "HOST_SYS", "/sys");
string root = Root(diskRoot, "HOST_ROOT", OperatingSystem.IsWindows() ? Path.GetPathRoot(Environment.SystemDirectory) ?? "C:\\" : "/");
_disk = disk ?? (() => ReadRootDisk(root));
}
public HostSnapshot Sample()
@@ -30,7 +37,7 @@ public sealed class HostSampler
long now = _time.GetTimestamp();
var snapshot = new HostSnapshot { AtUtcMs = _time.GetUtcNow().ToUnixTimeMilliseconds() };
if (_read("/proc/stat") is { } stat && ParseCpu(stat) is { } cpu)
if (_read(Path.Combine(_procRoot, "stat")) is { } stat && ParseCpu(stat) is { } cpu)
{
if (_previousCpu is { } before && cpu.Total > before.Total)
{
@@ -41,7 +48,7 @@ public sealed class HostSampler
_previousCpu = cpu;
}
if (_read("/proc/meminfo") is { } meminfo && ParseMemory(meminfo) is { } memory)
if (_read(Path.Combine(_procRoot, "meminfo")) is { } meminfo && ParseMemory(meminfo) is { } memory)
{
snapshot.MemTotal = memory.Total;
snapshot.MemUsed = memory.Used;
@@ -53,10 +60,11 @@ public sealed class HostSampler
snapshot.DiskUsed = disk.Used;
}
if (_read("/proc/net/dev") is { } dev && ParseNetwork(dev) is { } net)
if (_read(Path.Combine(_procRoot, "net/dev")) is { } dev && ParseNetwork(dev) is { } net)
{
snapshot.NetRxTotal = net.Rx;
snapshot.NetTxTotal = net.Tx;
snapshot.NetLinkMbps = ReadLinkSpeed(dev);
if (_previousNet is { } before && net.Rx >= before.Rx && net.Tx >= before.Tx)
{
double seconds = Math.Max(0.001, _time.GetElapsedTime(before.At, now).TotalSeconds);
@@ -67,7 +75,11 @@ public sealed class HostSampler
_previousNet = (now, net.Rx, net.Tx);
}
snapshot.UptimeSeconds = _read("/proc/uptime") is { } uptime && ParseUptime(uptime) is { } up ? up : Environment.TickCount64 / 1000;
snapshot.UptimeSeconds = _read(Path.Combine(_procRoot, "uptime")) is { } uptime && ParseUptime(uptime) is { } up ? up : null;
if (!OperatingSystem.IsLinux() && snapshot.UptimeSeconds is null)
{
snapshot.UptimeSeconds = Environment.TickCount64 / 1000;
}
return snapshot;
}
@@ -165,11 +177,54 @@ public sealed class HostSampler
}
}
private static (long Total, long Used)? ReadRootDisk()
private static string Root(string? value, string variable, string fallback)
{
value ??= Environment.GetEnvironmentVariable(variable);
return string.IsNullOrWhiteSpace(value) ? fallback : value;
}
private long? ReadLinkSpeed(string dev)
{
long total = 0;
bool any = false;
foreach (string line in dev.Split('\n'))
{
int colon = line.IndexOf(':', StringComparison.Ordinal);
if (colon < 0)
{
continue;
}
string name = line[..colon].Trim();
if (s_virtualPrefixes.Any(p => name.StartsWith(p, StringComparison.Ordinal)))
{
continue;
}
string? speed = _read(Path.Combine(_sysRoot, "class/net", name, "speed"));
if (long.TryParse(speed?.Trim(), CultureInfo.InvariantCulture, out long mbps) && mbps > 0)
{
total += mbps;
any = true;
}
else
{
return null;
}
}
return any ? total : null;
}
private static (long Total, long Used)? ReadRootDisk(string root)
{
try
{
string root = OperatingSystem.IsWindows() ? Path.GetPathRoot(Environment.SystemDirectory) ?? "C:\\" : "/";
if (!Directory.Exists(root))
{
return null;
}
var drive = new DriveInfo(root);
return drive.IsReady ? (drive.TotalSize, drive.TotalSize - drive.AvailableFreeSpace) : null;
}
@@ -7,6 +7,12 @@
<AssemblyName>monitor-agent</AssemblyName>
<RootNamespace>Monitor.Agent</RootNamespace>
<IsAotCompatible>true</IsAotCompatible>
<PublishAot>true</PublishAot>
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>full</TrimMode>
<StripSymbols>true</StripSymbols>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<MetadataUpdaterSupport>false</MetadataUpdaterSupport>
</PropertyGroup>
<ItemGroup>
<InternalsVisibleTo Include="Monitor.Backend.Tests" />