use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};
use std::path::{Path, PathBuf};
use std::process::Command;
use wt_perf::{
RepoConfig, add_history_spread_branches, add_worktrees, clone_rust_repo, create_repo,
invalidate_caches_auto, isolate_cmd, run_git, setup_fake_remote,
};
#[derive(Clone)]
struct BenchConfig {
repo: RepoConfig,
cold_cache: bool,
}
impl BenchConfig {
const fn typical(worktrees: usize, cold_cache: bool) -> Self {
Self {
repo: RepoConfig::typical(worktrees),
cold_cache,
}
}
const fn branches(count: usize, commits_per_branch: usize, cold_cache: bool) -> Self {
Self {
repo: RepoConfig::branches(count, commits_per_branch),
cold_cache,
}
}
const fn many_divergent_branches(cold_cache: bool) -> Self {
Self {
repo: RepoConfig::many_divergent_branches(),
cold_cache,
}
}
fn label(&self) -> &'static str {
if self.cold_cache { "cold" } else { "warm" }
}
}
fn run_benchmark(
b: &mut criterion::Bencher,
binary: &Path,
repo_path: &Path,
config: &BenchConfig,
args: &[&str],
env: Option<(&str, &str)>,
) {
let cmd_factory = || {
let mut cmd = Command::new(binary);
cmd.args(args).current_dir(repo_path);
isolate_cmd(&mut cmd, None);
if let Some((key, value)) = env {
cmd.env(key, value);
}
cmd
};
if config.cold_cache {
b.iter_batched(
|| invalidate_caches_auto(repo_path),
|_| {
cmd_factory().output().unwrap();
},
criterion::BatchSize::SmallInput,
);
} else {
b.iter(|| {
cmd_factory().output().unwrap();
});
}
}
fn bench_skeleton(c: &mut Criterion) {
let mut group = c.benchmark_group("skeleton");
let binary = Path::new(env!("CARGO_BIN_EXE_wt"));
for worktrees in [1, 4, 8] {
for cold in [false, true] {
let config = BenchConfig::typical(worktrees, cold);
let temp = create_repo(&config.repo);
let repo_path = temp.path().join("repo");
setup_fake_remote(&repo_path);
group.bench_with_input(
BenchmarkId::new(config.label(), worktrees),
&config,
|b, config| {
run_benchmark(
b,
binary,
&repo_path,
config,
&["list"],
Some(("WORKTRUNK_SKELETON_ONLY", "1")),
);
},
);
}
}
group.finish();
}
fn bench_full(c: &mut Criterion) {
let mut group = c.benchmark_group("full");
let binary = Path::new(env!("CARGO_BIN_EXE_wt"));
for worktrees in [1, 4, 8] {
for cold in [false, true] {
let config = BenchConfig::typical(worktrees, cold);
let temp = create_repo(&config.repo);
let repo_path = temp.path().join("repo");
setup_fake_remote(&repo_path);
group.bench_with_input(
BenchmarkId::new(config.label(), worktrees),
&config,
|b, config| {
run_benchmark(b, binary, &repo_path, config, &["list"], None);
},
);
}
}
group.finish();
}
fn bench_worktree_scaling(c: &mut Criterion) {
let mut group = c.benchmark_group("worktree_scaling");
let binary = Path::new(env!("CARGO_BIN_EXE_wt"));
for worktrees in [1, 4, 8] {
for cold in [false, true] {
let config = BenchConfig::typical(worktrees, cold);
let temp = create_repo(&config.repo);
let repo_path = temp.path().join("repo");
run_git(&repo_path, &["status"]);
group.bench_with_input(
BenchmarkId::new(config.label(), worktrees),
&config,
|b, config| {
run_benchmark(b, binary, &repo_path, config, &["list"], None);
},
);
}
}
group.finish();
}
fn bench_real_repo(c: &mut Criterion) {
let mut group = c.benchmark_group("real_repo");
let binary = Path::new(env!("CARGO_BIN_EXE_wt"));
for worktrees in [1, 4, 8] {
for cold in [false, true] {
let label = if cold { "cold" } else { "warm" };
group.bench_with_input(
BenchmarkId::new(label, worktrees),
&(worktrees, cold),
|b, &(worktrees, cold)| {
let config = RepoConfig::typical(worktrees);
let temp = tempfile::tempdir().unwrap();
let workspace_main = clone_rust_repo(&temp);
add_worktrees(&config, &workspace_main);
run_git(&workspace_main, &["status"]);
let make_cmd = || {
let mut cmd = Command::new(binary);
cmd.arg("list").current_dir(&workspace_main);
isolate_cmd(&mut cmd, None);
cmd
};
if cold {
b.iter_batched(
|| invalidate_caches_auto(&workspace_main),
|_| {
make_cmd().output().unwrap();
},
criterion::BatchSize::SmallInput,
);
} else {
b.iter(|| {
make_cmd().output().unwrap();
});
}
},
);
}
}
group.finish();
}
fn bench_many_branches(c: &mut Criterion) {
let mut group = c.benchmark_group("many_branches");
let binary = Path::new(env!("CARGO_BIN_EXE_wt"));
for cold in [false, true] {
let config = BenchConfig::branches(100, 2, cold);
let temp = create_repo(&config.repo);
let repo_path = temp.path().join("repo");
run_git(&repo_path, &["status"]);
group.bench_function(config.label(), |b| {
run_benchmark(
b,
binary,
&repo_path,
&config,
&["list", "--branches", "--progressive"],
None,
);
});
}
group.finish();
}
fn bench_divergent_branches(c: &mut Criterion) {
let mut group = c.benchmark_group("divergent_branches");
group.measurement_time(std::time::Duration::from_secs(30));
group.sample_size(10);
let binary = Path::new(env!("CARGO_BIN_EXE_wt"));
for cold in [false, true] {
let config = BenchConfig::many_divergent_branches(cold);
let temp = create_repo(&config.repo);
let repo_path = temp.path().join("repo");
run_git(&repo_path, &["status"]);
group.bench_function(config.label(), |b| {
run_benchmark(
b,
binary,
&repo_path,
&config,
&["list", "--branches", "--progressive"],
None,
);
});
}
group.finish();
}
fn setup_rust_workspace_with_branches(temp: &tempfile::TempDir, num_branches: usize) -> PathBuf {
let workspace_main = clone_rust_repo(temp);
add_history_spread_branches(&workspace_main, num_branches);
run_git(&workspace_main, &["status"]);
workspace_main
}
fn bench_real_repo_many_branches(c: &mut Criterion) {
let mut group = c.benchmark_group("real_repo_many_branches");
group.measurement_time(std::time::Duration::from_secs(60));
group.sample_size(10);
let binary = Path::new(env!("CARGO_BIN_EXE_wt"));
let setup_workspace = || {
let temp = tempfile::tempdir().unwrap();
let workspace_main = setup_rust_workspace_with_branches(&temp, 50);
let wt_path = temp.path().join("wt-test");
run_git(
&workspace_main,
&[
"worktree",
"add",
"-b",
"test-worktree",
wt_path.to_str().unwrap(),
"HEAD",
],
);
(temp, workspace_main)
};
group.bench_function("warm", |b| {
let (_temp, workspace_main) = setup_workspace();
b.iter(|| {
let mut cmd = Command::new(binary);
cmd.args(["list", "--branches"])
.current_dir(&workspace_main);
isolate_cmd(&mut cmd, None);
cmd.output().unwrap();
});
});
group.bench_function("warm_optimized", |b| {
let (_temp, workspace_main) = setup_workspace();
b.iter(|| {
let mut cmd = Command::new(binary);
cmd.args(["list", "--branches"])
.current_dir(&workspace_main);
isolate_cmd(&mut cmd, None);
cmd.env("WORKTRUNK_TEST_SKIP_EXPENSIVE_THRESHOLD", "1");
cmd.output().unwrap();
});
});
group.bench_function("warm_worktrees_only", |b| {
let (_temp, workspace_main) = setup_workspace();
b.iter(|| {
let mut cmd = Command::new(binary);
cmd.arg("list").current_dir(&workspace_main); isolate_cmd(&mut cmd, None);
cmd.output().unwrap();
});
});
group.finish();
}
criterion_group! {
name = benches;
config = Criterion::default()
.sample_size(30)
.measurement_time(std::time::Duration::from_secs(15))
.warm_up_time(std::time::Duration::from_secs(3));
targets = bench_skeleton, bench_full, bench_worktree_scaling, bench_real_repo, bench_many_branches, bench_divergent_branches, bench_real_repo_many_branches
}
criterion_main!(benches);