deb: do not retain staged trees over the retention size cap
The per-target retention kept the newest five staged trees however large they are: five carried kernel trees are three-digit GiB, most of it compiled objects whose resume value does not scale with their size. A staged tree larger than the retention cap (a user setting, see below) is now dropped at the end of its own build — the record degrades to metadata-only and the next build re-stages from scratch — and the retention sweep also degrades older oversized trees, covering records kept by older pkh versions and interrupted builds. The cap of 0 disables the size rule; the count cap is unchanged. The cap is the first setting of a user configuration file, ~/.config/pkh/config.json (the directory already holds contexts.json): missing file and missing keys mean defaults, a corrupt file is backed up and the defaults apply, and PKH_STAGED_MAXSIZE overrides the file. The default is 20G, the scale where a carried kernel tree stops earning its disk. Every retained tree's size is measured once at retention (the same concurrent, mount-crossing-safe walker the prune report uses) and stored in the record manifest, so sweeps reuse it instead of re-walking the trees on every build.
This commit is contained in:
@@ -111,6 +111,35 @@ pub(crate) fn format_bytes(bytes: u64) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a human size into bytes: an integer with an optional unit
|
||||
/// suffix — `B`, `K`, `M`, `G`, `T` (optionally `iB`, case-insensitive),
|
||||
/// the binary multiples ccache's configuration uses (`20G` = 20 GiB). A
|
||||
/// bare number is bytes. `None` on anything else (empty, negative,
|
||||
/// overflow, unknown unit).
|
||||
pub(crate) fn parse_size(text: &str) -> Option<u64> {
|
||||
let text = text.trim();
|
||||
let split = text
|
||||
.rfind(|c: char| c.is_ascii_digit())
|
||||
.map(|idx| idx + 1)
|
||||
.unwrap_or(0);
|
||||
let (digits, unit) = text.split_at(split);
|
||||
let power = match unit.trim().to_ascii_lowercase().as_str() {
|
||||
"" | "b" => 0,
|
||||
"k" | "kib" => 1,
|
||||
"m" | "mib" => 2,
|
||||
"g" | "gib" => 3,
|
||||
"t" | "tib" => 4,
|
||||
_ => return None,
|
||||
};
|
||||
if digits.is_empty() || !digits.bytes().all(|b| b.is_ascii_digit()) {
|
||||
return None;
|
||||
}
|
||||
digits
|
||||
.parse::<u64>()
|
||||
.ok()
|
||||
.and_then(|value| value.checked_mul(1u64.checked_shl(10 * power)?))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -159,4 +188,32 @@ mod tests {
|
||||
// this size; the mount-crossing walk keeps it out of the totals.
|
||||
assert_eq!(format_bytes(140_737_477_959_680), "128.0 TiB");
|
||||
}
|
||||
|
||||
/// Unit spellings and their byte values: ccache-style binary
|
||||
/// multiples, optional B/iB decorations, case-insensitive.
|
||||
#[test]
|
||||
fn parse_size_reads_the_unit_suffixes() {
|
||||
assert_eq!(parse_size("0"), Some(0));
|
||||
assert_eq!(parse_size("512"), Some(512));
|
||||
assert_eq!(parse_size("512B"), Some(512));
|
||||
assert_eq!(parse_size("512 b"), Some(512));
|
||||
assert_eq!(parse_size("10K"), Some(10 * 1024));
|
||||
assert_eq!(parse_size("512m"), Some(512 * 1024 * 1024));
|
||||
assert_eq!(parse_size("20G"), Some(20 * 1024 * 1024 * 1024));
|
||||
assert_eq!(parse_size("2TiB"), Some(2 * 1024 * 1024 * 1024 * 1024));
|
||||
assert_eq!(parse_size(" 20G "), Some(20 * 1024 * 1024 * 1024));
|
||||
}
|
||||
|
||||
/// Garbage is an error, not a guess: empty, negative, junk units and
|
||||
/// overflow all parse to None.
|
||||
#[test]
|
||||
fn parse_size_rejects_garbage() {
|
||||
assert_eq!(parse_size(""), None);
|
||||
assert_eq!(parse_size(" "), None);
|
||||
assert_eq!(parse_size("G"), None);
|
||||
assert_eq!(parse_size("-1"), None);
|
||||
assert_eq!(parse_size("20GB"), None);
|
||||
assert_eq!(parse_size("20 kg"), None);
|
||||
assert_eq!(parse_size("999999999999999999999999T"), None);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user