examples/zombie_probe.rs
//! TEMPORARY measurement probe — count zombie children left by gix worktree
//! operations. Delete after measuring.
//!
//! `cargo run --example zombie_probe -- <mode> <arg> <n>`
//! modes: `selftest`, `status <repo>`, `clone <src> <dest>`
fn zombies() -> Vec<(i32, String)> {
let me = std::process::id() as i32;
let mut out = Vec::new();
let Ok(rd) = std::fs::read_dir("/proc") else {
return out;
};
for e in rd.flatten() {
let name = e.file_name();
let Some(name) = name.to_str() else { continue };
let Ok(pid) = name.parse::<i32>() else { continue };
let Ok(stat) = std::fs::read_to_string(format!("/proc/{pid}/stat")) else {
continue;
};
let Some(rp) = stat.rfind(')') else { continue };
let comm = stat[stat.find('(').map(|i| i + 1).unwrap_or(0)..rp].to_string();
let rest: Vec<&str> = stat[rp + 1..].split_whitespace().collect();
if rest.len() < 2 {
continue;
}
if rest[1].parse::<i32>().unwrap_or(-1) == me && rest[0] == "Z" {
out.push((pid, comm));
}
}
out
}
fn main() -> anyhow::Result<()> {
let mut args = std::env::args().skip(1);
let mode = args.next().unwrap_or_else(|| "selftest".into());
println!("probe pid {} mode {mode}", std::process::id());
match mode.as_str() {
// Prove the COUNTER is not blind: leak one child on purpose.
"selftest" => {
let child = std::process::Command::new("/bin/sh")
.arg("-c")
.arg("exit 0")
.spawn()?;
let pid = child.id();
std::mem::forget(child); // never waited => zombie
std::thread::sleep(std::time::Duration::from_millis(300));
let z = zombies();
println!("selftest: leaked pid {pid}; counter sees {} {:?}", z.len(), z);
}
"status" => {
let repo = args.next().expect("status <repo> <n>");
let n: usize = args.next().unwrap_or_else(|| "5".into()).parse()?;
for i in 0..n {
match nornir::gitio::worktree_freshness(std::path::Path::new(&repo)) {
Ok(f) => println!(" [{i}] dirty={}", f.dirty),
Err(e) => println!(" [{i}] ERR {e:#}"),
}
println!(" [{i}] zombies: {}", zombies().len());
}
}
"clone" => {
let src = args.next().expect("clone <src> <dest>");
let dest = args.next().expect("clone <src> <dest>");
let n: usize = args.next().unwrap_or_else(|| "1".into()).parse()?;
for i in 0..n {
let d = if std::env::var("PROBE_REUSE").is_ok() { dest.clone() } else { format!("{dest}-{i}") };
if std::env::var("PROBE_REUSE").is_err() { let _ = std::fs::remove_dir_all(&d); }
match nornir::gitio::clone_or_fetch(&src, std::path::Path::new(&d), None) {
Ok(sha) => println!(" [{i}] cloned {}", &sha[..8.min(sha.len())]),
Err(e) => println!(" [{i}] ERR {e:#}"),
}
let z = zombies();
println!(" [{i}] zombies: {} {:?}", z.len(), z);
}
}
other => println!("unknown mode {other}"),
}
let z = zombies();
println!("FINAL zombies: {} {:?}", z.len(), z);
Ok(())
}