use std::path::PathBuf;
pub fn tree_bytes(root: &str) -> Option<u64> {
if std::fs::symlink_metadata(root).is_err() {
return None;
}
let mut total = 0;
let mut pending = vec![PathBuf::from(root)];
while let Some(directory) = pending.pop() {
let Ok(entries) = std::fs::read_dir(&directory) else {
continue;
};
for entry in entries.flatten() {
let path = entry.path();
match entry.file_type() {
Ok(file_type) if file_type.is_dir() => pending.push(path),
_ => {
if let Ok(meta) = std::fs::symlink_metadata(&path) {
total += meta.len();
}
}
}
}
}
Some(total)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Verdict {
Under,
Close,
Over,
}
impl Verdict {
#[allow(
dead_code,
reason = "read by this module's tests, which assert on state the daemon never asks for"
)]
pub fn as_str(self) -> &'static str {
match self {
Verdict::Under => "under",
Verdict::Close => "close",
Verdict::Over => "over",
}
}
}
pub const WARN_AT: f64 = 0.8;
pub fn verdict(used: u64, budget: u64) -> Verdict {
if budget == 0 {
return Verdict::Under;
}
if used >= budget {
return Verdict::Over;
}
#[expect(clippy::cast_precision_loss)] let close = {
let used = used as f64;
let budget = budget as f64;
used >= budget * WARN_AT
};
if close {
Verdict::Close
} else {
Verdict::Under
}
}
pub const MIN_CHECK_MS: u64 = 1_000;
pub fn next_check_ms(
written: u64,
last_written: u64,
budget: u64,
since_last: u64,
slowest: u64,
) -> u64 {
#[expect(
clippy::cast_precision_loss,
reason = "byte counts and their ratios live far below f64's exact range; only the ratio matters here, not the last bits of a petabyte-scale count"
)]
let grew = written as f64 - last_written as f64;
if grew <= 0.0 || since_last == 0 {
return slowest;
}
#[expect(clippy::cast_precision_loss)]
let projected = {
let per_ms = grew / since_last as f64;
let remaining = budget.saturating_sub(written) as f64;
(remaining / per_ms) * 0.5
};
#[expect(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
reason = "float to integer casts saturate, so a projection beyond every u64 lands on the slowest interval rather than wrapping"
)]
let rounded = projected.round() as u64;
MIN_CHECK_MS.max(slowest.min(rounded))
}
#[cfg(test)]
mod tests;