fix(disk): count reserved blocks as used, not free
Disk usage was computed as used/size, where size is the raw device size. ext4 reserves 5% of the filesystem for root — 92.4 GiB of this node's 1.8 TiB — which size includes but nothing can allocate. Two consequences, both live on archi-dev-box today: The dashboard advertised 251 GiB free when only 159 GiB could actually be written, and reported 86.2% usage against df's 90.8%. Worse, disk_monitor triggers automatic cleanup (podman image prune) at 90%. The disk has been genuinely above that threshold while this returned 86.2%, so the cleanup never once fired — which is exactly how ~72 GB of dangling images accumulated unnoticed, and why deleting apps appeared to free nothing. Both call sites now ask df for avail and use used/(used+avail): the same figure df itself prints, and the space an operator can actually spend. Callers deriving free as total - used now get avail. Note this shifts disk_total_bytes in the analytics series down by the reserve; historical samples are not comparable across this change. Tests updated for the three-column output, plus a regression test built from this box's real numbers asserting the corrected math crosses the 90% threshold the old math missed. 15/15 disk_monitor tests pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
9c5164372e
commit
a9a30406df
@@ -168,7 +168,7 @@ pub(super) async fn read_disk_usage() -> Result<(u64, u64)> {
|
||||
/// Read disk usage via `df` for a given path.
|
||||
pub(super) async fn read_disk_usage_path(path: &str) -> Result<(u64, u64)> {
|
||||
let output = tokio::process::Command::new("df")
|
||||
.args(["--block-size=1", "--output=used,size", path])
|
||||
.args(["--block-size=1", "--output=used,size,avail", path])
|
||||
.output()
|
||||
.await
|
||||
.context("Failed to run df")?;
|
||||
@@ -189,11 +189,22 @@ pub(super) async fn read_disk_usage_path(path: &str) -> Result<(u64, u64)> {
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing used"))?
|
||||
.parse()
|
||||
.context("parse df used")?;
|
||||
let total: u64 = parts
|
||||
// Raw `size` includes the filesystem's root-reserved blocks (5% by default
|
||||
// on ext4 — 92 GiB of this node's 1.8 TiB), which nothing can allocate.
|
||||
// Reporting it as capacity told the dashboard there were 251 GiB free when
|
||||
// only 159 GiB were writable. Callers derive free as total - used, so total
|
||||
// must mean "what can actually be used".
|
||||
let _size: u64 = parts
|
||||
.next()
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing total"))?
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing size"))?
|
||||
.parse()
|
||||
.context("parse df total")?;
|
||||
.context("parse df size")?;
|
||||
let avail: u64 = parts
|
||||
.next()
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing avail"))?
|
||||
.parse()
|
||||
.context("parse df avail")?;
|
||||
let total = used.saturating_add(avail);
|
||||
|
||||
Ok((used, total))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user