Skip to content

Commit 41bb9b0

Browse files
committed
Fix cache GC threshold: measure LV usage host-side via lvs data_percent
CacheDisk::usage_pct() ran df against the LV device path on the HOST, but the cache LV is only ever mounted inside the guest VM (as /dev/vdb). For an unmounted block device, df reports the filesystem containing the device node -- devtmpfs, ~0% -- and any parse failure fell back to unwrap_or(0). So usage_pct() always returned ~0 and try_clear_cache() never fired: caches were never trimmed regardless of max_cache_pct (observed in production: slot cache LVs at 92-98% data_percent, never cleared). Read the thin LV's data_percent via lvs instead, which the host can always see. It slightly overestimates usage after guest-side deletes (thin blocks aren't returned without discard), which errs on the side of clearing -- acceptable for a cache. Failures now surface as errors (logged as warnings by try_clear_cache) instead of being silently coerced to 0%, which is exactly how this bug stayed hidden.
1 parent ed24c3e commit 41bb9b0

1 file changed

Lines changed: 31 additions & 11 deletions

File tree

manager/src/disk.rs

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,20 +32,40 @@ impl CacheDisk {
3232
Ok(())
3333
}
3434

35-
/// Check cache usage percentage. Uses df on the mounted LV device.
35+
/// Check cache usage as a percentage of the LV's virtual size.
36+
///
37+
/// The cache LV is only ever mounted inside the guest VM (as /dev/vdb),
38+
/// never on the host — so `df` on the host cannot see its filesystem and
39+
/// reports the filesystem containing the device *node* (devtmpfs, ~0%)
40+
/// instead, meaning the clear threshold never fires. Read the thin LV's
41+
/// `data_percent` via lvs, which the host can always see. It slightly
42+
/// overestimates after guest-side deletes (thin blocks aren't returned
43+
/// without discard), which errs on the side of clearing — fine for a cache.
3644
pub fn usage_pct(&self) -> Result<u8> {
37-
let output = std::process::Command::new("df")
38-
.args(["--output=pcent", &self.device_path()])
45+
let output = std::process::Command::new("lvs")
46+
.args([
47+
"--noheadings",
48+
"--options",
49+
"data_percent",
50+
&format!("{}/{}", self.volume_group, self.lv_name),
51+
])
3952
.output()?;
40-
let stdout = String::from_utf8_lossy(&output.stdout);
41-
// Output: "Use%\n 42%\n"
42-
let pct_str = stdout
43-
.lines()
44-
.nth(1)
45-
.unwrap_or("0")
53+
if !output.status.success() {
54+
anyhow::bail!(
55+
"lvs failed for {}/{}: {}",
56+
self.volume_group,
57+
self.lv_name,
58+
String::from_utf8_lossy(&output.stderr).trim()
59+
);
60+
}
61+
// Output: " 92.98" (some locales emit a comma decimal separator)
62+
let raw = String::from_utf8_lossy(&output.stdout)
4663
.trim()
47-
.trim_end_matches('%');
48-
Ok(pct_str.parse().unwrap_or(0))
64+
.replace(',', ".");
65+
let pct: f64 = raw.parse().map_err(|e| {
66+
anyhow::anyhow!("failed to parse lvs data_percent {:?}: {}", raw, e)
67+
})?;
68+
Ok(pct.round().clamp(0.0, 100.0) as u8)
4969
}
5070

5171
/// Clear cache by removing and re-snapshotting from cache-empty.

0 commit comments

Comments
 (0)