use rpki::data_model::manifest::ManifestObject; use std::hint::black_box; use std::path::PathBuf; use std::time::Instant; #[derive(Debug, Clone)] struct Config { sample: Option, manifest_path: Option, iterations: u64, warmup_iterations: u64, repeats: u32, } fn usage_and_exit() -> ! { eprintln!( "Usage:\n ours-manifest-bench (--sample | --manifest ) [--iterations N] [--warmup-iterations N] [--repeats N]\n\nExamples:\n cargo run --release -- --sample small-01 --iterations 20000 --warmup-iterations 2000 --repeats 3\n cargo run --release -- --manifest ../../tests/benchmark/selected_der/small-01.mft" ); std::process::exit(2); } fn parse_args() -> Config { let mut sample: Option = None; let mut manifest_path: Option = None; let mut iterations: u64 = 20_000; let mut warmup_iterations: u64 = 2_000; let mut repeats: u32 = 3; let mut args = std::env::args().skip(1); while let Some(arg) = args.next() { match arg.as_str() { "--sample" => sample = Some(args.next().unwrap_or_else(|| usage_and_exit())), "--manifest" => { manifest_path = Some(PathBuf::from(args.next().unwrap_or_else(|| usage_and_exit()))) } "--iterations" => { iterations = args .next() .unwrap_or_else(|| usage_and_exit()) .parse() .unwrap_or_else(|_| usage_and_exit()) } "--warmup-iterations" => { warmup_iterations = args .next() .unwrap_or_else(|| usage_and_exit()) .parse() .unwrap_or_else(|_| usage_and_exit()) } "--repeats" => { repeats = args .next() .unwrap_or_else(|| usage_and_exit()) .parse() .unwrap_or_else(|_| usage_and_exit()) } "-h" | "--help" => usage_and_exit(), _ => usage_and_exit(), } } if sample.is_none() && manifest_path.is_none() { usage_and_exit(); } if sample.is_some() && manifest_path.is_some() { usage_and_exit(); } Config { sample, manifest_path, iterations, warmup_iterations, repeats, } } fn derive_manifest_path(sample: &str) -> PathBuf { // Assumes current working directory is `rpki/benchmark/ours_manifest_bench`. PathBuf::from(format!("../../tests/benchmark/selected_der/{sample}.mft")) } fn main() { let cfg = parse_args(); let manifest_path = cfg .manifest_path .clone() .unwrap_or_else(|| derive_manifest_path(cfg.sample.as_deref().unwrap())); let bytes = std::fs::read(&manifest_path).unwrap_or_else(|e| { eprintln!("read manifest fixture failed: {e}; path={}", manifest_path.display()); std::process::exit(1); }); let decoded_once = ManifestObject::decode_der(&bytes).unwrap_or_else(|e| { eprintln!("decode failed: {e}; path={}", manifest_path.display()); std::process::exit(1); }); let file_count = decoded_once.manifest.file_count(); let mut round_ns_per_op: Vec = Vec::with_capacity(cfg.repeats as usize); let mut round_ops_per_s: Vec = Vec::with_capacity(cfg.repeats as usize); for _round in 0..cfg.repeats { for _ in 0..cfg.warmup_iterations { let obj = ManifestObject::decode_der(black_box(&bytes)).expect("warmup decode"); black_box(obj); } let start = Instant::now(); for _ in 0..cfg.iterations { let obj = ManifestObject::decode_der(black_box(&bytes)).expect("timed decode"); black_box(obj); } let elapsed = start.elapsed(); let ns_per_op = (elapsed.as_secs_f64() * 1e9) / (cfg.iterations as f64); let ops_per_s = (cfg.iterations as f64) / elapsed.as_secs_f64(); round_ns_per_op.push(ns_per_op); round_ops_per_s.push(ops_per_s); } let avg_ns_per_op = round_ns_per_op.iter().sum::() / (round_ns_per_op.len() as f64); let avg_ops_per_s = round_ops_per_s.iter().sum::() / (round_ops_per_s.len() as f64); let sample_name = cfg.sample.clone().unwrap_or_else(|| { manifest_path .file_name() .map(|s| s.to_string_lossy().to_string()) .unwrap_or_else(|| manifest_path.display().to_string()) }); let sample_name = sample_name .strip_suffix(".mft") .unwrap_or(&sample_name) .to_string(); println!("fixture: {}", manifest_path.display()); println!(); println!("| sample | avg ns/op | ops/s | file count |"); println!("|---|---:|---:|---:|"); println!( "| {} | {:.2} | {:.2} | {} |", sample_name, avg_ns_per_op, avg_ops_per_s, file_count ); }