fix(metrics): host memory usage excludes reclaimable page cache (#1297)

* fix(metrics): host memory usage excludes reclaimable page cache

Host RAM was computed as mem.used / mem.total via systeminformation, but mem.used counts
reclaimable buffers/cache as used, so a busy Linux host read ~99%. Switch the dashboard stats,
the fleet node self-report, and the host-RAM alert threshold to mem.active / mem.total
(cache-excluded), and report used: mem.active and free: mem.available so the byte readout stays
consistent with the percentage. Add regression tests for a cache-heavy host (no false alert) and
a genuinely busy host (alert still fires).

* test(metrics): assert system stats memory excludes reclaimable cache

The cached /api/system/stats test mocked si.mem() without active/available, so after
the route switched to the cache-excluded fields it produced a NaN percentage that the
shape-only assertions did not catch. Fill the mock with a realistic shape and assert the
route reports the active working set, not the cache-inclusive used/free.
This commit is contained in:
Anso
2026-06-03 16:11:00 -04:00
committed by GitHub
parent 612e3391e8
commit 42fc8048fe
5 changed files with 80 additions and 17 deletions
+6 -3
View File
@@ -233,9 +233,12 @@ async function fetchLocalNodeOverview(node: Node): Promise<FleetNodeOverview> {
cpu: { usage: currentLoad.currentLoad.toFixed(1), cores: currentLoad.cpus.length },
memory: {
total: mem.total,
used: mem.used,
free: mem.free,
usagePercent: ((mem.used / mem.total) * 100).toFixed(1),
// Percentage is keyed off mem.active (the real working set), not mem.used:
// on Linux/BSD/macOS mem.used counts reclaimable page cache and reads ~99%
// on a busy host. mem.available is total - active, so used + free = total.
used: mem.active,
free: mem.available,
usagePercent: ((mem.active / mem.total) * 100).toFixed(1),
},
disk: mainDisk ? {
total: mainDisk.size,
+6 -3
View File
@@ -311,9 +311,12 @@ metricsRouter.get('/system/stats', authMiddleware, async (req: Request, res: Res
},
memory: {
total: mem.total,
used: mem.used,
free: mem.free,
usagePercent: ((mem.used / mem.total) * 100).toFixed(1),
// Percentage is keyed off mem.active (the real working set), not mem.used:
// on Linux/BSD/macOS mem.used counts reclaimable page cache and reads ~99%
// on a busy host. mem.available is total - active, so used + free = total.
used: mem.active,
free: mem.available,
usagePercent: ((mem.active / mem.total) * 100).toFixed(1),
},
disk: mainDisk ? {
fs: mainDisk.fs,