//! threads is the most a 100us sampler saw, not the count at exit.
use std::io::Read;
use std::os::unix::process::ExitStatusExt;
use std::process::{Command, Stdio};
use std::time::Instant;
fn threads(pid: i32) -> i64 {
#[cfg(target_os = "macos")]
unsafe {
let mut info: libc::proc_taskinfo = std::mem::zeroed();
let size = std::mem::size_of::<libc::proc_taskinfo>() as i32;
let n = libc::proc_pidinfo(
pid,
libc::PROC_PIDTASKINFO,
0,
&mut info as *mut _ as *mut _,
size,
);
if n == size {
info.pti_threadnum as i64
} else {
-1
}
}
#[cfg(target_os = "linux")]
{
std::fs::read_dir(format!("/proc/{pid}/task"))
.map(|d| d.count() as i64)
.unwrap_or(-1)
}
}
fn main() {
let args: Vec<String> = std::env::args().collect();
let n: usize = args[1].parse().unwrap();
let stdin_mode = args[2].clone();
let hold_ms: u64 = args[3].parse().unwrap();
assert_eq!(args[4], "--");
let program = &args[5];
let rest = &args[6..];
for _ in 0..n {
let mut cmd = Command::new(program);
cmd.args(rest).stdout(Stdio::piped()).stderr(Stdio::null());
let hold = stdin_mode == "hold";
if hold {
cmd.stdin(Stdio::piped());
} else if stdin_mode == "-" {
cmd.stdin(Stdio::null());
} else {
cmd.stdin(std::fs::File::open(&stdin_mode).unwrap());
}
let start = Instant::now();
let mut child = cmd.spawn().unwrap();
let pid = child.id() as i32;
let done = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
let sampler = {
let done = done.clone();
std::thread::spawn(move || {
let mut most = -1;
while !done.load(std::sync::atomic::Ordering::Relaxed) {
most = most.max(threads(pid));
std::thread::sleep(std::time::Duration::from_micros(100));
}
most
})
};
if hold {
std::thread::sleep(std::time::Duration::from_millis(hold_ms));
drop(child.stdin.take());
}
let mut out = child.stdout.take().unwrap();
let mut first = [0u8; 1];
let got = out.read(&mut first).unwrap();
let first_us = if got == 1 {
start.elapsed().as_micros() as i64
} else {
-1
};
let mut sink = Vec::new();
let _ = out.read_to_end(&mut sink);
let mut status = 0i32;
let mut usage: libc::rusage = unsafe { std::mem::zeroed() };
unsafe { libc::wait4(pid, &mut status, 0, &mut usage) };
let exit_us = start.elapsed().as_micros() as i64;
done.store(true, std::sync::atomic::Ordering::Relaxed);
let nthreads = sampler.join().unwrap();
let tv = |t: libc::timeval| t.tv_sec as i64 * 1_000_000 + t.tv_usec as i64;
let code = std::process::ExitStatus::from_raw(status)
.code()
.unwrap_or(-1);
println!(
"{first_us}\t{exit_us}\t{nthreads}\t{}\t{}\t{}\t{}\t{}\t{}\t{code}",
tv(usage.ru_utime),
tv(usage.ru_stime),
usage.ru_maxrss,
usage.ru_minflt,
usage.ru_nvcsw,
usage.ru_nivcsw
);
std::mem::forget(child);
}
}