use clap::Parser;
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use keyhog::args::{Cli, Command, ScanArgs};
use keyhog::testing::hook::{find_hooks_dir_for_repo, install_at_repo, CANONICAL_SCAN_ARGS};
use keyhog::testing::{CliTestApi, API};
use keyhog_sources::StagedManifest;
use std::hint::black_box;
use std::path::{Path, PathBuf};
use std::process::Command as SysCommand;
use tempfile::TempDir;
fn init_git_repo(dir: &Path) {
let out = SysCommand::new("git")
.args(["init", "-q", "-b", "main"])
.current_dir(dir)
.output()
.expect("git init");
assert!(out.status.success(), "git init must succeed");
let email = SysCommand::new("git")
.args(["config", "user.email", "bench@test.local"])
.current_dir(dir)
.output()
.expect("git config user.email");
assert!(email.status.success(), "git config user.email must succeed");
let name = SysCommand::new("git")
.args(["config", "user.name", "Bench Runner"])
.current_dir(dir)
.output()
.expect("git config user.name");
assert!(name.status.success(), "git config user.name must succeed");
}
fn create_and_stage_files(repo: &Path, count: usize) {
for i in 0..count {
let rel_path = format!("src/pkg_{:03}/module_{:05}.rs", i / 100, i);
let full_path = repo.join(&rel_path);
if let Some(parent) = full_path.parent() {
std::fs::create_dir_all(parent).expect("create parent dir");
}
std::fs::write(
&full_path,
format!("pub fn process_event_{i}() -> usize {{ {i} * 42 }}\n"),
)
.expect("write synthetic file");
}
let add_out = SysCommand::new("git")
.args(["add", "."])
.current_dir(repo)
.output()
.expect("git add");
assert!(add_out.status.success(), "git add must succeed");
}
struct HookBenchFixture {
_dir: TempDir,
repo_path: PathBuf,
count: usize,
}
impl HookBenchFixture {
fn new(count: usize) -> Self {
let dir = TempDir::new().expect("tempdir");
let repo_path = dir.path().to_path_buf();
init_git_repo(&repo_path);
create_and_stage_files(&repo_path, count);
Self {
_dir: dir,
repo_path,
count,
}
}
}
fn bench_hook_canonical_args_resolution(c: &mut Criterion) {
let mut group = c.benchmark_group("hook_canonical_args_resolution");
let raw_tokens: Vec<&str> = std::iter::once("keyhog")
.chain(CANONICAL_SCAN_ARGS.split_whitespace())
.collect();
group.bench_function("parse_canonical_scan_args", |b| {
b.iter(|| {
let parsed = Cli::try_parse_from(black_box(&raw_tokens)).expect("parse canonical args");
if let Some(Command::Scan(scan_args)) = parsed.command {
black_box(scan_args);
} else {
panic!("expected Scan command");
}
});
});
group.finish();
}
fn bench_hook_install_lifecycle(c: &mut Criterion) {
let mut group = c.benchmark_group("hook_install_lifecycle");
let dir = TempDir::new().expect("tempdir");
let repo_path = dir.path().to_path_buf();
init_git_repo(&repo_path);
group.bench_function("find_hooks_dir_for_repo", |b| {
b.iter(|| {
let hooks_dir = find_hooks_dir_for_repo(black_box(&repo_path)).expect("find hooks dir");
black_box(hooks_dir);
});
});
group.bench_function("install_at_repo_fresh_or_update", |b| {
b.iter(|| {
let res = install_at_repo(black_box(&repo_path), false).expect("install at repo");
black_box(res);
});
});
group.finish();
}
fn bench_hook_staged_acquisition_and_sources(c: &mut Criterion) {
let mut group = c.benchmark_group("hook_staged_acquisition_and_sources");
let fixture_10 = HookBenchFixture::new(10);
let fixture_100 = HookBenchFixture::new(100);
let fixture_500 = HookBenchFixture::new(500);
for fixture in &[&fixture_10, &fixture_100, &fixture_500] {
group.bench_with_input(
BenchmarkId::new("staged_manifest_acquire", fixture.count),
&fixture.repo_path,
|b, repo_path| {
b.iter(|| {
let manifest = StagedManifest::acquire(black_box(repo_path))
.expect("acquire staged manifest");
black_box(manifest);
});
},
);
group.bench_with_input(
BenchmarkId::new("build_staged_sources", fixture.count),
&fixture.repo_path,
|b, repo_path| {
let args = ScanArgs::try_parse_from([
"scan",
"--path",
repo_path.to_str().unwrap(),
"--git-staged",
"--fast",
"--backend",
"cpu",
])
.expect("parse scan args");
b.iter(|| {
let sources = API
.build_sources(black_box(&args), Vec::new(), None)
.expect("build sources");
black_box(sources);
});
},
);
}
group.finish();
}
criterion_group!(
benches,
bench_hook_canonical_args_resolution,
bench_hook_install_lifecycle,
bench_hook_staged_acquisition_and_sources,
);
criterion_main!(benches);