2018-01-13 16:50:02 +01:00
|
|
|
extern crate ansi_term;
|
|
|
|
|
extern crate clap;
|
2018-01-13 19:01:52 +01:00
|
|
|
extern crate indicatif;
|
2018-01-13 16:50:02 +01:00
|
|
|
|
|
|
|
|
use std::process::{Command, Stdio};
|
|
|
|
|
use std::time::Instant;
|
|
|
|
|
|
2018-01-13 19:01:52 +01:00
|
|
|
use indicatif::{ProgressBar, ProgressStyle};
|
2018-01-13 16:50:02 +01:00
|
|
|
use ansi_term::Colour::{Cyan, Green, Red};
|
|
|
|
|
use clap::{App, AppSettings, Arg};
|
|
|
|
|
|
|
|
|
|
struct CmdResult {
|
2018-01-13 19:47:59 +01:00
|
|
|
/// Execution time in seconds
|
|
|
|
|
execution_time_sec: f64,
|
|
|
|
|
|
|
|
|
|
/// True if the command finished with exit code zero
|
2018-01-13 16:50:02 +01:00
|
|
|
success: bool,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl CmdResult {
|
2018-01-13 19:47:59 +01:00
|
|
|
fn new(execution_time_sec: f64, success: bool) -> CmdResult {
|
2018-01-13 16:50:02 +01:00
|
|
|
CmdResult {
|
2018-01-13 19:47:59 +01:00
|
|
|
execution_time_sec,
|
2018-01-13 16:50:02 +01:00
|
|
|
success,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-13 19:47:59 +01:00
|
|
|
/// Run the given shell command and measure the execution time
|
|
|
|
|
fn time_shell_command(shell_cmd: &str) -> CmdResult {
|
2018-01-13 16:50:02 +01:00
|
|
|
let start = Instant::now();
|
|
|
|
|
|
|
|
|
|
let status = Command::new("sh")
|
|
|
|
|
.arg("-c")
|
|
|
|
|
.arg(shell_cmd)
|
|
|
|
|
.stdout(Stdio::null())
|
|
|
|
|
.stderr(Stdio::null())
|
|
|
|
|
.status()
|
|
|
|
|
.expect("failed to execute process");
|
|
|
|
|
|
|
|
|
|
let duration = start.elapsed();
|
|
|
|
|
|
2018-01-13 19:47:59 +01:00
|
|
|
let execution_time_sec = duration.as_secs() as f64 + duration.subsec_nanos() as f64 * 1e-9;
|
2018-01-13 16:50:02 +01:00
|
|
|
|
2018-01-13 19:47:59 +01:00
|
|
|
CmdResult::new(execution_time_sec, status.success())
|
2018-01-13 16:50:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
|
let matches = App::new("hyperfine")
|
|
|
|
|
.global_settings(&[AppSettings::ColoredHelp])
|
|
|
|
|
.version("0.1")
|
|
|
|
|
.about("A command-line benchmarking tool")
|
|
|
|
|
.author("David Peter <mail@david-peter.de>")
|
|
|
|
|
.arg(
|
|
|
|
|
Arg::with_name("command")
|
|
|
|
|
.help("Command to benchmark")
|
|
|
|
|
.required(true)
|
|
|
|
|
.multiple(true),
|
|
|
|
|
)
|
|
|
|
|
.get_matches();
|
|
|
|
|
|
2018-01-13 19:01:52 +01:00
|
|
|
let min_time_sec = 5.0;
|
|
|
|
|
let min_runs = 10;
|
|
|
|
|
|
2018-01-13 19:47:59 +01:00
|
|
|
let progressbar_style = ProgressStyle::default_spinner()
|
|
|
|
|
.tick_chars("⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏")
|
|
|
|
|
.template(" {spinner} {msg:<30} {wide_bar} ETA {eta_precise}");
|
|
|
|
|
|
2018-01-13 16:50:02 +01:00
|
|
|
let commands = matches.values_of("command").unwrap();
|
2018-01-13 19:47:59 +01:00
|
|
|
|
2018-01-13 16:50:02 +01:00
|
|
|
for cmd in commands {
|
2018-01-13 19:01:52 +01:00
|
|
|
println!("Command: {}", Cyan.paint(cmd));
|
|
|
|
|
println!();
|
2018-01-13 16:50:02 +01:00
|
|
|
|
|
|
|
|
let mut results = vec![];
|
2018-01-13 19:01:52 +01:00
|
|
|
|
|
|
|
|
// Set up progress bar
|
|
|
|
|
let bar = ProgressBar::new(min_runs);
|
2018-01-13 19:47:59 +01:00
|
|
|
bar.set_style(progressbar_style.clone());
|
2018-01-13 19:01:52 +01:00
|
|
|
bar.enable_steady_tick(80);
|
|
|
|
|
bar.set_message("Initial time measurement");
|
|
|
|
|
|
|
|
|
|
// Initial run
|
2018-01-13 19:47:59 +01:00
|
|
|
let res = time_shell_command(cmd);
|
2018-01-13 19:01:52 +01:00
|
|
|
|
2018-01-13 19:47:59 +01:00
|
|
|
let runs_in_min_time = (min_time_sec / res.execution_time_sec) as u64;
|
2018-01-13 19:01:52 +01:00
|
|
|
|
|
|
|
|
let count = if runs_in_min_time >= min_runs {
|
|
|
|
|
runs_in_min_time
|
|
|
|
|
} else {
|
|
|
|
|
min_runs
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
results.push(res);
|
|
|
|
|
|
|
|
|
|
bar.set_length(count);
|
|
|
|
|
bar.set_message("Collecting statistics");
|
|
|
|
|
|
|
|
|
|
for _ in 1..count {
|
|
|
|
|
bar.inc(1);
|
2018-01-13 19:47:59 +01:00
|
|
|
let res = time_shell_command(cmd);
|
2018-01-13 16:50:02 +01:00
|
|
|
results.push(res);
|
|
|
|
|
}
|
2018-01-13 19:01:52 +01:00
|
|
|
bar.finish_and_clear();
|
2018-01-13 16:50:02 +01:00
|
|
|
|
2018-01-13 19:47:59 +01:00
|
|
|
let t_sum: f64 = results.iter().map(|r| r.execution_time_sec).sum();
|
2018-01-13 19:01:52 +01:00
|
|
|
let t_mean = t_sum / (results.len() as f64);
|
|
|
|
|
|
2018-01-13 19:47:59 +01:00
|
|
|
let t2_sum: f64 = results.iter().map(|r| r.execution_time_sec.powi(2)).sum();
|
2018-01-13 19:01:52 +01:00
|
|
|
let t2_mean = t2_sum / (results.len() as f64);
|
|
|
|
|
|
|
|
|
|
let stddev = (t2_mean - t_mean.powi(2)).sqrt();
|
|
|
|
|
|
2018-01-13 19:47:59 +01:00
|
|
|
let time_fmt = format!("{:.3} s ± {:.3} s", t_mean, stddev);
|
2018-01-13 19:01:52 +01:00
|
|
|
|
|
|
|
|
println!(" Time: {}", Green.paint(time_fmt));
|
2018-01-13 16:50:02 +01:00
|
|
|
|
|
|
|
|
if !results.iter().all(|r| r.success) {
|
2018-01-13 19:01:52 +01:00
|
|
|
println!(
|
|
|
|
|
" {}: Program returned non-zero exit status",
|
|
|
|
|
Red.paint("Warning")
|
|
|
|
|
);
|
2018-01-13 16:50:02 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
println!();
|
|
|
|
|
}
|
|
|
|
|
}
|