feat(host_agent): a disk out of inodes reads as full (#5745)
CI / lint (push) Successful in 3s
CI / integration (push) Successful in 1m19s
CI / unit (push) Successful in 1m27s
CI / publish (push) Successful in 1m13s

VDNT-Util01's / ran out of inodes (956 free of 60.8M) with 274G of space
left; every file create failed and Steward's page read healthy at 66%.

- Agent 1.13.0 reports each mount's inodes (statvfs; a filesystem reporting
  none, like btrfs, is left without).
- Ingest records inodes_used_pct per mount, and disk_used_pct_worst is the
  fullest mount by space or inodes, so existing disk-full rules and the
  overview see it.
- The host's health strip, the Hosts widget and the host glance grade / by
  whichever is fuller and name inodes when they are ("Disk / 66% · inodes
  100%", "Inodes on / ... past its critical threshold").

Hosts pick up the inode figures once their agent is updated to 1.13.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-14 16:51:46 -04:00
co-authored by Claude Opus 5.5
parent 1b82d8ad83
commit 5dd9956f35
11 changed files with 85 additions and 16 deletions
@@ -78,10 +78,19 @@ def test_collect_storage():
r.free = free
return r
with patch.object(a.shutil, "disk_usage", side_effect=_fake_usage):
# Inodes (#5745): "/" has 100 with 1 free; /mnt/data (btrfs-like) reports none.
inodes = {"/": (100, 1), "/mnt/data": (0, 0)}
def _fake_statvfs(path):
files, ffree = inodes[path]
return type("S", (), {"f_files": files, "f_ffree": ffree})()
with patch.object(a.shutil, "disk_usage", side_effect=_fake_usage), \
patch.object(a.os, "statvfs", side_effect=_fake_statvfs):
out = a.collect_storage(["/", "/mnt/data"])
assert out == [
{"mount": "/", "total_bytes": 1000, "used_bytes": 400},
{"mount": "/", "total_bytes": 1000, "used_bytes": 400,
"inodes_total": 100, "inodes_used": 99},
{"mount": "/mnt/data", "total_bytes": 2000, "used_bytes": 500},
]