use agbench::Bench;
use agplatform::CommandExt;
use agplatform::Env;
use agplatform::Platform;
use agplatform::TestDir;
use std::io::Write;
use std::path::PathBuf;
use std::process::Command;
use std::process::ExitCode;
use std::process::Stdio;
use std::time::Duration;
use tokio::io::AsyncWriteExt;
const DATA_LEN: usize = 1024 * 1024;
fn main() -> ExitCode {
agbench::main(&[
run,
run_timeout,
run_path,
output,
input,
run_async,
output_async,
input_async,
])
}
struct Fixture<P> {
platform: P,
current_dir: PathBuf,
_dir: TestDir,
}
fn fixture() -> Fixture<impl Platform> {
let dir = TestDir::temp();
std::fs::write(dir.path().join("data"), data()).expect("data should be writable");
let current_dir = std::env::current_dir().expect("current dir should be accessible");
std::env::set_current_dir(dir.path()).expect("test dir should be enterable");
Fixture {
platform: agplatform::platform(),
current_dir,
_dir: dir,
}
}
impl<P> Drop for Fixture<P> {
fn drop(&mut self) {
let _ = std::env::set_current_dir(&self.current_dir);
}
}
fn runtime() -> tokio::runtime::Runtime {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("runtime should be buildable")
}
fn data() -> Vec<u8> {
b"a\n".repeat(DATA_LEN / 2)
}
#[cfg(unix)]
fn noop() -> Command {
Command::new("true")
}
#[cfg(windows)]
fn noop() -> Command {
let mut command = Command::new("cmd");
command.args(["/C", "exit 0"]);
command
}
#[cfg(unix)]
fn print_data() -> Command {
let mut command = Command::new("cat");
command.arg("data");
command
}
#[cfg(windows)]
fn print_data() -> Command {
let mut command = Command::new("cmd");
command.args(["/C", "type data"]);
command
}
#[cfg(unix)]
fn echo_stdin() -> Command {
Command::new("cat")
}
#[cfg(windows)]
fn echo_stdin() -> Command {
let mut command = Command::new("findstr");
command.arg("^");
command
}
fn run(bench: &mut Bench) {
bench
.benchmark("process/run", fixture)
.fail_factor(1.5)
.target("std", |_| noop().output().unwrap())
.target("agplatform", |fx| {
noop().run(fx.platform.process()).unwrap()
})
.run();
}
fn run_timeout(bench: &mut Bench) {
bench
.benchmark("process/run/timeout", fixture)
.fail_factor(1.5)
.target("std", |_| noop().output().unwrap())
.target("agplatform", |fx| {
noop()
.timeout(Duration::from_secs(60))
.run(fx.platform.process())
.unwrap()
})
.run();
}
fn changed_path() -> String {
let path = std::env::var_os("PATH").unwrap_or_default();
let dirs = std::env::split_paths(&path).chain([std::env::temp_dir()]);
std::env::join_paths(dirs)
.expect("paths should be joinable")
.into_string()
.expect("PATH should be valid UTF-8")
}
fn run_path(bench: &mut Bench) {
bench
.benchmark("process/run/path", || {
let fx = fixture();
fx.platform.env().set_var("PATH", changed_path());
(fx, changed_path())
})
.fail_factor(1.5)
.target("std", |(_, path)| {
noop().env("PATH", path).output().unwrap()
})
.target("agplatform", |(fx, _)| {
noop().run(fx.platform.process()).unwrap()
})
.run();
}
fn output(bench: &mut Bench) {
bench
.benchmark("process/output", fixture)
.fail_factor(1.5)
.target("std", |_| print_data().output().unwrap().stdout.len())
.target("agplatform", |fx| {
print_data()
.run(fx.platform.process())
.unwrap()
.stdout
.len()
})
.run();
}
fn input(bench: &mut Bench) {
bench
.benchmark("process/input", || (fixture(), data()))
.fail_factor(1.5)
.target("std", |(_, data)| {
let mut child = echo_stdin()
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
let mut stdin = child.stdin.take().unwrap();
let data = data.clone();
let writer = std::thread::spawn(move || stdin.write_all(&data));
let output = child.wait_with_output().unwrap();
writer.join().unwrap().unwrap();
output.stdout.len()
})
.target("agplatform", |(fx, data)| {
echo_stdin()
.input(data.as_slice())
.run(fx.platform.process())
.unwrap()
.stdout
.len()
})
.run();
}
fn run_async(bench: &mut Bench) {
bench
.benchmark("process/run_async", || (fixture(), runtime()))
.fail_factor(1.5)
.target("tokio", |(_, runtime)| {
runtime
.block_on(async { tokio::process::Command::from(noop()).output().await })
.unwrap()
})
.target("agplatform", |(fx, runtime)| {
runtime
.block_on(noop().run_async(fx.platform.process()))
.unwrap()
})
.run();
}
fn output_async(bench: &mut Bench) {
bench
.benchmark("process/output_async", || (fixture(), runtime()))
.fail_factor(1.5)
.target("tokio", |(_, runtime)| {
runtime
.block_on(async { tokio::process::Command::from(print_data()).output().await })
.unwrap()
.stdout
.len()
})
.target("agplatform", |(fx, runtime)| {
runtime
.block_on(print_data().run_async(fx.platform.process()))
.unwrap()
.stdout
.len()
})
.run();
}
fn input_async(bench: &mut Bench) {
bench
.benchmark("process/input_async", || (fixture(), runtime(), data()))
.fail_factor(1.5)
.target("tokio", |(_, runtime, data)| {
runtime.block_on(async {
let mut child = tokio::process::Command::from(echo_stdin())
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
let mut stdin = child.stdin.take().unwrap();
let data = data.clone();
let writer = tokio::spawn(async move { stdin.write_all(&data).await });
let output = child.wait_with_output().await.unwrap();
writer.await.unwrap().unwrap();
output.stdout.len()
})
})
.target("agplatform", |(fx, runtime, data)| {
runtime
.block_on(
echo_stdin()
.input(data.as_slice())
.run_async(fx.platform.process()),
)
.unwrap()
.stdout
.len()
})
.run();
}