use std::{
env,
path::Path,
process::Command,
time::{Duration, Instant},
};
use weavatrix_git::{HistoryOptions, Repository};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args = env::args().skip(1).collect::<Vec<_>>();
let path = args.first().map_or(".", String::as_str);
let max_count = parse(&args, 1, 500)?;
let iterations = parse(&args, 2, 20)?;
let expected = git_ids(path, max_count)?;
let repository = Repository::open(path)?;
let start = repository.resolve("HEAD")?;
let options = HistoryOptions {
max_commits: max_count,
first_parent: true,
..HistoryOptions::default()
};
let actual = repository
.history_ids(start, options)?
.into_iter()
.map(|id| id.to_string())
.collect::<Vec<_>>();
if actual != expected {
return Err("history identifiers differ from git rev-list".into());
}
let warm = measure(iterations, || {
repository
.history_ids(start, options)
.map(|identifiers| identifiers.len())
})?;
let reopen = measure(iterations, || {
let repo = Repository::open(path)?;
repo.history_ids(repo.resolve("HEAD")?, options)
.map(|identifiers| identifiers.len())
})?;
let git = measure(iterations, || git_ids(path, max_count).map(|ids| ids.len()))?;
println!("engine,mode,p50_ms,p95_ms,commits");
print_row("weavatrix-git", "warm", &warm, actual.len());
print_row("weavatrix-git", "reopen", &reopen, actual.len());
print_row("git.exe", "process", &git, expected.len());
Ok(())
}
fn parse(
args: &[String],
index: usize,
fallback: usize,
) -> Result<usize, Box<dyn std::error::Error>> {
Ok(args
.get(index)
.map(|value| value.parse())
.transpose()?
.unwrap_or(fallback))
}
fn git_ids(path: impl AsRef<Path>, max: usize) -> Result<Vec<String>, Box<dyn std::error::Error>> {
let output = Command::new("git")
.arg("-C")
.arg(path.as_ref())
.args(["rev-list", "--first-parent", "--max-count"])
.arg(max.to_string())
.arg("HEAD")
.output()?;
if !output.status.success() {
return Err(String::from_utf8_lossy(&output.stderr).into_owned().into());
}
Ok(String::from_utf8(output.stdout)?
.lines()
.map(str::to_owned)
.collect())
}
fn measure<T, E>(
iterations: usize,
mut operation: impl FnMut() -> Result<T, E>,
) -> Result<Vec<Duration>, E> {
for _ in 0..3 {
let _ = operation()?;
}
let mut values = Vec::with_capacity(iterations);
for _ in 0..iterations {
let start = Instant::now();
let _ = operation()?;
values.push(start.elapsed());
}
values.sort_unstable();
Ok(values)
}
fn print_row(engine: &str, mode: &str, values: &[Duration], commits: usize) {
let p50 = values[values.len() / 2].as_secs_f64() * 1_000.0;
let p95 = values[(values.len() * 95 / 100).min(values.len() - 1)].as_secs_f64() * 1_000.0;
println!("{engine},{mode},{p50:.3},{p95:.3},{commits}");
}