use std::process::{Command, Stdio};
use std::time::Instant;
use kernal_api::{ProcessPriority, ProcessSessionOptions, SpawnAdmission, SpawnSpec, StreamMode};
const SPAWNS: u32 = 200;
const TRIVIAL_ARGS: [&str; 2] = ["--list", "__session_spawn_cost_matches_nothing__"];
fn trivial_child() -> std::path::PathBuf {
std::env::current_exe().expect("test binary path")
}
fn piped() -> SpawnSpec {
TRIVIAL_ARGS
.iter()
.fold(SpawnSpec::new(trivial_child()), |spec, arg| spec.arg(*arg))
.stdout(StreamMode::Piped)
.stderr(StreamMode::Piped)
}
async fn session_ms(spec: impl Fn() -> SpawnSpec) -> f64 {
let start = Instant::now();
for _ in 0..SPAWNS {
let session = spec()
.spawn_session(ProcessSessionOptions::default())
.await
.expect("start session");
while session.next_output().await.is_some() {}
session.wait().await.expect("wait child");
}
start.elapsed().as_secs_f64() * 1e3 / f64::from(SPAWNS)
}
#[tokio::test(flavor = "multi_thread")]
#[ignore = "measurement, not a pass/fail budget"]
async fn session_spawn_overhead_against_std_command() {
for round in 0..3 {
let start = Instant::now();
for _ in 0..SPAWNS {
let out = Command::new(trivial_child())
.args(TRIVIAL_ARGS)
.stdin(Stdio::null())
.output()
.expect("std spawn");
assert!(out.status.success());
}
let baseline = start.elapsed().as_secs_f64() * 1e3 / f64::from(SPAWNS);
let added = |label: &str, ms: f64| {
eprintln!(
"round {round}: {label:<22} +{:.3} ms over std {baseline:.3} ms",
ms - baseline
);
};
added("bare session", session_ms(piped).await);
added(
"kill_when_owner_dies",
session_ms(|| piped().kill_when_owner_dies(true)).await,
);
added(
"priority_best_effort",
session_ms(|| piped().priority_best_effort(ProcessPriority::Low)).await,
);
added(
"spawn_admission",
session_ms(|| piped().spawn_admission(SpawnAdmission::new(|| Ok(())))).await,
);
}
}