#![allow(clippy::unwrap_used)]
use criterion::{
criterion_group, criterion_main, measurement::WallTime, BenchmarkGroup, BenchmarkId, Criterion,
Throughput,
};
use std::hint::black_box;
const SMALL_HISTORY: &str = include_str!("fixtures/small_history.txt");
const MEDIUM_HISTORY: &str = include_str!("fixtures/medium_history.txt");
const LARGE_HISTORY: &str = include_str!("fixtures/large_history.txt");
fn parse_commands(history: &str) -> Vec<String> {
history
.lines()
.filter(|line| !line.trim().is_empty() && !line.trim().starts_with('#'))
.map(String::from)
.collect()
}
const DEV_PREFIXES: &[(&str, &str)] = &[
("git ", "Version control - most common"),
("cargo ", "Rust development"),
("docker ", "Container operations"),
("kubectl ", "Kubernetes management"),
("npm ", "Node.js package management"),
];
const PARTIAL_PREFIXES: &[(&str, &str)] = &[
("git co", "git commit/checkout"),
("cargo b", "cargo build/bench"),
("docker-c", "docker-compose"),
("kubectl g", "kubectl get"),
("npm r", "npm run"),
];
fn bench_suggestion_latency(c: &mut Criterion) {
let mut group = c.benchmark_group("suggestion_latency");
group.significance_level(0.01);
let small_cmds = parse_commands(SMALL_HISTORY);
let medium_cmds = parse_commands(MEDIUM_HISTORY);
let large_cmds = parse_commands(LARGE_HISTORY);
bench_model_size(&mut group, "small", &small_cmds, 1_000); bench_model_size(&mut group, "medium", &medium_cmds, 5_000); bench_model_size(&mut group, "large", &large_cmds, 10_000);
group.finish();
}
fn bench_model_size(
group: &mut BenchmarkGroup<WallTime>,
size_name: &str,
commands: &[String],
_target_us: u64,
) {
use aprender_shell::MarkovModel;
let mut model = MarkovModel::new(3);
model.train(commands);
for (prefix, _desc) in DEV_PREFIXES {
group.throughput(Throughput::Elements(1));
group.bench_with_input(
BenchmarkId::new(format!("{}/prefix", size_name), prefix.trim()),
&(&model, *prefix),
|bencher, (model, prefix)| {
bencher.iter(|| {
let suggestions = model.suggest(black_box(prefix), 5);
black_box(suggestions)
});
},
);
}
}
fn bench_partial_completion(c: &mut Criterion) {
use aprender_shell::MarkovModel;
let mut group = c.benchmark_group("partial_completion");
let cmds = parse_commands(MEDIUM_HISTORY);
let mut model = MarkovModel::new(3);
model.train(&cmds);
for (prefix, desc) in PARTIAL_PREFIXES {
group.bench_with_input(
BenchmarkId::new("medium", desc),
prefix,
|bencher, prefix| {
bencher.iter(|| {
let suggestions = model.suggest(black_box(prefix), 5);
black_box(suggestions)
});
},
);
}
group.finish();
}
fn bench_training_throughput(c: &mut Criterion) {
use aprender_shell::MarkovModel;
let mut group = c.benchmark_group("training_throughput");
for (size_name, history) in [
("small", SMALL_HISTORY),
("medium", MEDIUM_HISTORY),
("large", LARGE_HISTORY),
] {
let cmds = parse_commands(history);
group.throughput(Throughput::Elements(cmds.len() as u64));
group.bench_with_input(
BenchmarkId::new(size_name, format!("{} cmds", cmds.len())),
&cmds,
|bencher, cmds| {
bencher.iter(|| {
let mut model = MarkovModel::new(3);
model.train(black_box(cmds));
black_box(model)
});
},
);
}
group.finish();
}
fn bench_cold_start(c: &mut Criterion) {
use aprender_shell::MarkovModel;
use tempfile::NamedTempFile;
let mut group = c.benchmark_group("cold_start");
let cmds = parse_commands(MEDIUM_HISTORY);
let mut model = MarkovModel::new(3);
model.train(&cmds);
let tmp = NamedTempFile::new().expect("bench setup");
model.save(tmp.path()).expect("bench setup");
let model_path = tmp.path().to_owned();
group.bench_function("load_and_suggest", |bencher| {
bencher.iter(|| {
let loaded = MarkovModel::load(black_box(&model_path)).expect("bench setup");
let suggestions = loaded.suggest("git ", 5);
black_box(suggestions)
});
});
group.finish();
}
fn bench_serialization(c: &mut Criterion) {
use aprender_shell::MarkovModel;
let mut group = c.benchmark_group("serialization");
let cmds = parse_commands(MEDIUM_HISTORY);
let mut model = MarkovModel::new(3);
model.train(&cmds);
let json_bytes = serde_json::to_vec(&model).expect("bench setup");
group.throughput(Throughput::Bytes(json_bytes.len() as u64));
group.bench_function("serialize_json", |bencher| {
bencher.iter(|| {
let bytes = serde_json::to_vec(black_box(&model)).expect("bench setup");
black_box(bytes)
});
});
group.bench_function("deserialize_json", |bencher| {
bencher.iter(|| {
let loaded: MarkovModel =
serde_json::from_slice(black_box(&json_bytes)).expect("bench setup");
black_box(loaded)
});
});
group.finish();
}
fn bench_scalability(c: &mut Criterion) {
use aprender_shell::MarkovModel;
let mut group = c.benchmark_group("scalability");
let sizes = [100, 500, 1000, 2000, 3000, 3790];
let large_cmds = parse_commands(LARGE_HISTORY);
for &size in &sizes {
let subset: Vec<String> = large_cmds.iter().take(size).cloned().collect();
let mut model = MarkovModel::new(3);
model.train(&subset);
group.bench_with_input(
BenchmarkId::new("suggest_git", size),
&model,
|bencher, model| {
bencher.iter(|| {
let suggestions = model.suggest(black_box("git "), 5);
black_box(suggestions)
});
},
);
}
group.finish();
}
fn bench_paged_model(c: &mut Criterion) {
use aprender_shell::PagedMarkovModel;
let mut group = c.benchmark_group("paged_model");
let cmds = parse_commands(LARGE_HISTORY);
let mut paged = PagedMarkovModel::new(3, 1); paged.train(&cmds);
group.bench_function("paged_suggest", |bencher| {
bencher.iter(|| {
let suggestions = paged.suggest(black_box("git "), 5);
black_box(suggestions)
});
});
group.finish();
}
criterion_group!(
name = latency_benchmarks;
config = Criterion::default()
.sample_size(100)
.measurement_time(std::time::Duration::from_secs(5));
targets =
bench_suggestion_latency,
bench_partial_completion,
bench_training_throughput,
bench_cold_start,
bench_serialization,
bench_scalability,
bench_paged_model,
);
criterion_main!(latency_benchmarks);