use clap_builder::Parser;
use criterion::{
BatchSize, BenchmarkId, Criterion, Throughput, black_box, criterion_group, criterion_main,
};
use luff::{
cli::Args,
config::Config,
printer::{MarkdownPrinter, SkipPatterns},
walker::{Walker, WalkerEntry},
};
use std::fs;
use std::path::PathBuf;
use tempfile::TempDir;
fn config_for_root(root: &std::path::Path, args: &[&str]) -> Config {
let original_cwd = std::env::current_dir().expect("Failed to get CWD");
std::env::set_current_dir(root).expect("Failed to set CWD");
let parsed_args = Args::parse_from(args);
let config = Config::from_args(&parsed_args).expect("Failed to create config");
std::env::set_current_dir(original_cwd).expect("Failed to restore CWD");
config
}
fn create_test_structure(depth: usize, files_per_dir: usize) -> TempDir {
let temp = TempDir::new().unwrap();
create_recursive(temp.path(), depth, files_per_dir);
temp
}
fn create_recursive(path: &std::path::Path, depth: usize, files_per_dir: usize) {
if depth == 0 {
return;
}
for i in 0..files_per_dir {
let file_path = path.join(format!("file_{i}.txt"));
fs::write(&file_path, format!("Content {i} - some data to read")).unwrap();
}
for i in 0..2 {
let dir_path = path.join(format!("dir_{i}"));
fs::create_dir(&dir_path).unwrap();
create_recursive(&dir_path, depth - 1, files_per_dir);
}
}
fn bench_walker_traversal(c: &mut Criterion) {
let mut group = c.benchmark_group("walker_traversal");
for depth in [1, 3] {
let files_per_dir = 5;
let total_items = (files_per_dir * 2_usize.pow(depth as u32 + 1)) as u64;
group.throughput(Throughput::Elements(total_items));
group.bench_with_input(BenchmarkId::from_parameter(depth), &depth, |b, &d| {
b.iter_batched(
|| {
let temp = create_test_structure(d, files_per_dir);
let config = config_for_root(temp.path(), &["luff", "--max-files", "10000"]);
(temp, config)
},
|(_temp, config)| {
let walker = Walker::from_dir(&config).unwrap();
let count = walker.count();
black_box(count);
},
BatchSize::SmallInput,
);
});
}
group.finish();
}
fn bench_file_list_walker(c: &mut Criterion) {
let mut group = c.benchmark_group("file_list_walker");
let temp = TempDir::new().unwrap();
let file_count = 50;
let files: Vec<PathBuf> = (0..file_count)
.map(|i| {
let path = temp.path().join(format!("file_{i}.txt"));
fs::write(&path, format!("Content {i}")).unwrap();
path
})
.collect();
let config = config_for_root(temp.path(), &["luff"]);
group.throughput(Throughput::Elements(file_count as u64));
group.bench_function("construction", |b| {
b.iter(|| {
let input_files = files.clone();
let walker = Walker::from_file_list(&input_files, &config).unwrap();
black_box(walker);
});
});
group.bench_function("iteration", |b| {
b.iter_batched(
|| Walker::from_file_list(&files, &config).unwrap(),
|walker| {
let count = walker.count();
black_box(count);
},
BatchSize::SmallInput,
);
});
group.finish();
}
fn bench_printer_markdown(c: &mut Criterion) {
let temp = TempDir::new().unwrap();
let file_path = temp.path().join("bench_test.rs");
let content = "fn main() { println!(\"Hello Benchmark\"); }".repeat(10);
fs::write(&file_path, &content).unwrap();
let entry = WalkerEntry {
path: file_path.clone(),
relative_path: PathBuf::from("bench_test.rs"),
is_dir: false,
};
let args = Args::parse_from(["luff"]);
let config = Config::from_args(&args).unwrap();
let options = config.printer_options();
let mut group = c.benchmark_group("printer");
group.throughput(Throughput::Bytes(content.len() as u64));
group.bench_function("markdown_format_into_memory", |b| {
b.iter(|| {
let mut buffer = String::with_capacity(1024);
let _ = MarkdownPrinter::format_entry_into(
black_box(&entry),
&mut buffer,
&options.patterns,
SkipPatterns::ENABLED,
);
black_box(buffer);
});
});
group.finish();
}
fn bench_printer_tree(c: &mut Criterion) {
let temp = TempDir::new().unwrap();
let root = temp.path();
let entries: Vec<WalkerEntry> = (0..100)
.map(|i| {
let rel = PathBuf::from(format!("src/component_{}/mod.rs", i % 10));
WalkerEntry {
path: root.join(&rel),
relative_path: rel,
is_dir: false,
}
})
.collect();
let mut group = c.benchmark_group("printer");
group.throughput(Throughput::Elements(entries.len() as u64));
group.bench_function("tree_format", |b| {
b.iter(|| {
let output = luff::printer::format_tree(black_box(&entries), root).unwrap();
black_box(output);
});
});
group.finish();
}
criterion_group!(
benches,
bench_walker_traversal,
bench_file_list_walker,
bench_printer_markdown,
bench_printer_tree
);
criterion_main!(benches);