disk watch: a footprint walk that ran out of budget counts as unknown, not small
This commit is contained in:
parent
041779d910
commit
8f8217100b
1 changed files with 32 additions and 6 deletions
|
|
@ -74,7 +74,8 @@ const REPORT_DEPTH: usize = 4;
|
||||||
/// depth on a pathological tree.
|
/// depth on a pathological tree.
|
||||||
const MAX_DEPTH: usize = 24;
|
const MAX_DEPTH: usize = 24;
|
||||||
/// Directory entries the scan will look at before giving up. Bounds the
|
/// Directory entries the scan will look at before giving up. Bounds the
|
||||||
/// worst case on a huge tree; a truncated scan just names fewer dirs.
|
/// worst case on a huge tree. A truncated scan names fewer dirs, and its
|
||||||
|
/// footprint total is treated as unknown rather than small.
|
||||||
const SCAN_BUDGET: u64 = 400_000;
|
const SCAN_BUDGET: u64 = 400_000;
|
||||||
|
|
||||||
/// Background loop: periodically reconcile the disk todo. Detached task —
|
/// Background loop: periodically reconcile the disk todo. Detached task —
|
||||||
|
|
@ -217,17 +218,27 @@ fn roots() -> Vec<PathBuf> {
|
||||||
/// walk trades a little redundant `read_dir` work for keeping this and
|
/// walk trades a little redundant `read_dir` work for keeping this and
|
||||||
/// [`big_dirs`] as two small, independently obvious functions instead of
|
/// [`big_dirs`] as two small, independently obvious functions instead of
|
||||||
/// one that returns a tuple nobody asked for.
|
/// one that returns a tuple nobody asked for.
|
||||||
|
///
|
||||||
|
/// A walk that runs out of its entry budget has only counted part of the
|
||||||
|
/// tree, so its sum is a lower bound. That is reported as `u64::MAX`
|
||||||
|
/// ("at least the floor") so a truncated scan can make the todo fire but
|
||||||
|
/// never silence it.
|
||||||
fn agent_footprint_bytes() -> u64 {
|
fn agent_footprint_bytes() -> u64 {
|
||||||
let mut budget = SCAN_BUDGET;
|
footprint_within(&roots(), SCAN_BUDGET)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [`agent_footprint_bytes`] over the given roots with an explicit entry
|
||||||
|
/// budget, so the truncation rule is testable without a huge tree.
|
||||||
|
fn footprint_within(roots: &[PathBuf], mut budget: u64) -> u64 {
|
||||||
let mut discard = Vec::new();
|
let mut discard = Vec::new();
|
||||||
let mut total: u64 = 0;
|
let mut total: u64 = 0;
|
||||||
for root in roots() {
|
for root in roots {
|
||||||
let Ok(md) = std::fs::metadata(&root) else {
|
let Ok(md) = std::fs::metadata(root) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
total = total.saturating_add(dir_bytes(&root, md.dev(), 0, &mut budget, &mut discard));
|
total = total.saturating_add(dir_bytes(root, md.dev(), 0, &mut budget, &mut discard));
|
||||||
}
|
}
|
||||||
total
|
if budget == 0 { u64::MAX } else { total }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Walk the agent's own tree and return the directories worth naming:
|
/// Walk the agent's own tree and return the directories worth naming:
|
||||||
|
|
@ -460,4 +471,19 @@ mod tests {
|
||||||
reconcile(&todos, &wake, None);
|
reconcile(&todos, &wake, None);
|
||||||
assert!(todos.list(Some(SUBSYSTEM)).expect("list").is_empty());
|
assert!(todos.list(Some(SUBSYSTEM)).expect("list").is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A walk that fits its budget reports the real byte total; one that
|
||||||
|
/// runs out reports "unknown, at least the floor", so a big tree that
|
||||||
|
/// is only partly counted cannot fall under the footprint floor.
|
||||||
|
#[test]
|
||||||
|
fn a_truncated_footprint_walk_is_not_taken_for_a_small_one() {
|
||||||
|
let dir = tempfile::tempdir().expect("tempdir");
|
||||||
|
for name in ["a", "b", "c", "d"] {
|
||||||
|
std::fs::write(dir.path().join(name), [0u8; 10]).expect("write");
|
||||||
|
}
|
||||||
|
let roots = [dir.path().to_path_buf()];
|
||||||
|
|
||||||
|
assert_eq!(footprint_within(&roots, 100), 40);
|
||||||
|
assert_eq!(footprint_within(&roots, 2), u64::MAX);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue