prune,ui: show the removal pass, prefix the sizing spinner
Confirming a 500 GiB prune went silent under "Removing:" for minutes: drive an indicatif bar from the plan's sizes through a new apply_with_progress observer, in the deb live view's layout — the running count behind the spinner on the first line, the byte bar with its counters on the second. The removed path is not rendered: it fights the bar on a narrow terminal, and the failed-removal errors name the paths that matter. --force plans no sizes and gets the count line only. The dry-run size spinner lacked the "> " prompt prefix the other one-line waits carry; wait_spinner renders it too now.
This commit is contained in:
+70
-2
@@ -810,18 +810,36 @@ pub fn plan_in_roots(
|
||||
PrunePlan { report, actionable }
|
||||
}
|
||||
|
||||
/// Progress observer of [`apply_with_progress`]: the removal of the
|
||||
/// `idx`-th of `count` artifact (0-based) starts now, and `done` bytes
|
||||
/// have been freed by the artifacts removed before it. Sizes are the
|
||||
/// plan's estimates (measured before the removal; `--force` plans no
|
||||
/// sizes and reports zeros).
|
||||
pub type RemovalProgress<'a> = &'a dyn Fn(usize, usize, &Path, u64);
|
||||
|
||||
/// Execute a plan: remove the planned artifacts (unmounting the
|
||||
/// filesystems under residual directories and session roots first) and
|
||||
/// report what was removed and what failed. A dry-run plan removes
|
||||
/// nothing and returns its digest unchanged.
|
||||
pub fn apply(plan: PrunePlan) -> PruneReport {
|
||||
apply_with_progress(plan, None)
|
||||
}
|
||||
|
||||
/// [`apply`], reporting the removal as it progresses (the big session
|
||||
/// trees take minutes): each artifact is announced just before it goes.
|
||||
pub fn apply_with_progress(plan: PrunePlan, progress: Option<RemovalProgress<'_>>) -> PruneReport {
|
||||
if plan.report.dry_run {
|
||||
return plan.report;
|
||||
}
|
||||
let mut report = plan.report;
|
||||
report.removed = Vec::new();
|
||||
for (artifact, _) in &plan.actionable {
|
||||
let count = plan.actionable.len();
|
||||
let mut done = 0u64;
|
||||
for (idx, (artifact, bytes)) in plan.actionable.iter().enumerate() {
|
||||
let path = artifact.path();
|
||||
if let Some(progress) = progress {
|
||||
progress(idx, count, path, done);
|
||||
}
|
||||
|
||||
// Residual build directories may hold bind-mounted /proc and overlay
|
||||
// filesystems that must be unmounted before the directory can be
|
||||
@@ -838,7 +856,10 @@ pub fn apply(plan: PrunePlan) -> PruneReport {
|
||||
}
|
||||
|
||||
match remove_path(path) {
|
||||
Ok(()) => report.removed.push(path.to_path_buf()),
|
||||
Ok(()) => {
|
||||
report.removed.push(path.to_path_buf());
|
||||
done += bytes;
|
||||
}
|
||||
Err(e) => report.failed.push((path.to_path_buf(), e.to_string())),
|
||||
}
|
||||
}
|
||||
@@ -1468,6 +1489,53 @@ none /tmp/other proc rw 0 0
|
||||
assert!(!temp_path.join("pkh-1700000001-2").exists());
|
||||
}
|
||||
|
||||
/// The removal progress announces every artifact just before it goes,
|
||||
/// with the cumulative bytes of the artifacts already removed: the
|
||||
/// terminal drives its bar from it while the big trees take minutes.
|
||||
#[test]
|
||||
fn apply_reports_each_removal_with_cumulative_sizes() {
|
||||
let temp = tempdir().unwrap();
|
||||
let temp_path = temp.path();
|
||||
|
||||
let big = temp_path.join("pkh-1700000000");
|
||||
fs::create_dir_all(&big).unwrap();
|
||||
fs::write(big.join("obj.o"), vec![0u8; 3000]).unwrap();
|
||||
let small = temp_path.join("pkh-1700000001");
|
||||
fs::create_dir_all(&small).unwrap();
|
||||
fs::write(small.join("o"), vec![0u8; 5]).unwrap();
|
||||
|
||||
let plan = plan_in_roots(temp_path, None, None, None, None, PruneOptions::default());
|
||||
assert_eq!(plan.report().total_bytes, 3005);
|
||||
|
||||
let events = std::sync::Mutex::new(Vec::new());
|
||||
let report = apply_with_progress(
|
||||
plan,
|
||||
Some(&|idx, count, path, done| {
|
||||
events
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push((idx, count, path.to_path_buf(), done));
|
||||
}),
|
||||
);
|
||||
assert_eq!(report.removed.len(), 2);
|
||||
assert!(!big.exists());
|
||||
assert!(!small.exists());
|
||||
|
||||
let events = events.into_inner().unwrap();
|
||||
assert_eq!(events.len(), 2);
|
||||
// The first announcement opens the pass; the second names the
|
||||
// other artifact and carries the first one's size (discovery
|
||||
// order is the temp dir's read order, not ours).
|
||||
assert_eq!(events[0].0, 0);
|
||||
assert_eq!(events[0].1, 2);
|
||||
assert_eq!(events[0].3, 0);
|
||||
assert_eq!(events[1].0, 1);
|
||||
assert_eq!(events[1].1, 2);
|
||||
assert_ne!(events[1].2, events[0].2);
|
||||
let expected_done = if events[0].2 == big { 3000 } else { 5 };
|
||||
assert_eq!(events[1].3, expected_done);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_prune_empty_report_when_nothing_found() {
|
||||
let temp = tempdir().unwrap();
|
||||
|
||||
Reference in New Issue
Block a user