use std::fmt::Display;
use std::path::{Path, PathBuf};
use std::sync::OnceLock;
use rig_tools::edit::apply;
use rig_tools::fs::file_change;
use rig_tools::workspace::Local;
use rig_tools::{Replacement, SearchArgs, Shell, ShellArgs, find};
#[global_allocator]
static ALLOC: divan::AllocProfiler = divan::AllocProfiler::system();
fn main() {
divan::main();
}
fn or_exit<T, E: Display>(result: Result<T, E>, what: &str) -> T {
result.unwrap_or_else(|error| {
eprintln!("{what}: {error}");
std::process::exit(1)
})
}
#[divan::bench(sample_count = 50)]
fn shell_true_roundtrip() -> String {
let shell = Shell::default();
or_exit(
futures::executor::block_on(shell.run(ShellArgs::new("true"))),
"sh -c true",
)
.output
}
const FILES: usize = 1_000;
const LINES: usize = 200;
fn tree() -> &'static Path {
static TREE: OnceLock<PathBuf> = OnceLock::new();
TREE.get_or_init(|| {
let root = Path::new(env!("CARGO_TARGET_TMPDIR")).join("rig-tools-bench-tree");
for file in 0..FILES {
let dir = root.join(format!("module_{}", file % 20));
let path = dir.join(format!("file_{file}.rs"));
if path.exists() {
continue;
}
or_exit(std::fs::create_dir_all(&dir), "the bench tree");
let text: String = (0..LINES)
.map(|line| match line == LINES / 2 {
true => format!("pub fn marker_{file}() -> usize {{ {file} }}\n"),
false => format!(
" let value_{line} = compute(input_{line}, {line}).wrapping_add({file});\n"
),
})
.collect();
or_exit(std::fs::write(&path, text), "a bench tree file");
}
root
})
}
#[divan::bench(args = ["nomatch", "manymatch"], sample_count = 20)]
fn search(bencher: divan::Bencher, case: &str) {
let pattern = match case {
"nomatch" => "no_such_identifier_[0-9]+",
_ => r"pub fn marker_\d+",
};
let args = SearchArgs::new(pattern).in_path(tree().to_string_lossy());
let found = or_exit(find(&Local, &args), "search");
if (case == "nomatch") != found.hits.is_empty() {
eprintln!("search {case}: {} hits", found.hits.len());
std::process::exit(1);
}
bencher.bench(|| find(&Local, &args).map(|found| found.hits.len()));
}
fn lockfile(packages: usize, minor: usize) -> String {
(0..packages)
.map(|package| {
format!(
"[[package]]\nname = \"crate-{package}\"\nversion = \"1.{minor}.{package}\"\n\
source = \"registry+https://github.com/rust-lang/crates.io-index\"\n\
checksum = \"{:016x}{:016x}\"\n\n",
package.wrapping_mul(0x9e37_79b9_7f4a_7c15) ^ minor,
package
.wrapping_add(minor)
.wrapping_mul(0xff51_afd7_ed55_8ccd),
)
})
.collect()
}
#[divan::bench(sample_count = 5, sample_size = 1)]
fn file_change_full_rewrite(bencher: divan::Bencher) {
let before = lockfile(20_000 / 6, 1);
let after = lockfile(20_000 / 6, 2);
bencher.bench(|| file_change("Cargo.lock", Some(&before), Some(&after)));
}
#[divan::bench(sample_count = 20)]
fn edit_apply_1mb(bencher: divan::Bencher) {
let lines = 1024 * 1024 / 64;
let text: String = (0..lines)
.map(|line| {
format!(" let value_{line:06} = compute(input, {line:06}).wrapping_add(1);\n")
})
.collect();
let middle = lines / 2;
let edit = [Replacement::new(
format!(" let value_{middle:06} = compute(input, {middle:06}).wrapping_add(1);\n"),
format!(" let value_{middle:06} = compute(input, {middle:06}).wrapping_sub(1);\n"),
)];
or_exit(apply("big.rs", &text, &edit), "the 1 MB edit");
bencher.bench(|| apply("big.rs", &text, &edit));
}