server/repository/znippy-rust/tests/bench_znippy_throughput.rs
//! RESURRECTED BENCHMARK — the "terribly fast" number, restored for lane A2.
//!
//! Original: `8aa6159` (2026-05-22) "Add throughput benchmark: 523k crates/s and
//! 341k JARs/s single-thread"; last refreshed at `b91017f` (2026-05-29) with
//! znippy 0.8.0 and recorded in the README as **417,602 crates/s (1 thread)** and
//! **10,084,953 ops/s (32 threads)** on a 32-core AMD box.
//!
//! DELETED at `135b8c8` (2026-05-31) — "benches owned by
//! xtask/examples/nornir-bench.rs". They were not: nornir-bench has no
//! artifact-retrieval throughput row to this day, so the headline number has had
//! no runnable source since. This file is the Rust-crate half restored verbatim
//! (the JAR/wheel halves moved into sibling crates at `b5a435c` and are left out).
//!
//! Run: `cargo test -p holger-rust-znippy-repository --release --test
//! bench_znippy_throughput -- --ignored --nocapture`
//! Size: `HOLGER_BENCH_CRATES=500000` (default; the original count).
use std::path::PathBuf;
use std::sync::Arc;
use std::time::{Duration, Instant};
use repository_znippy_rust::RustRepoZnippy;
use traits::RepositoryBackendTrait;
use znippy_compress::{compress_stream, ArchiveEntry};
const BENCH_DIR: &str = "/tmp/holger_bench_throughput";
/// Generate a synthetic .crate file (gzipped tarball with a Cargo.toml + lib.rs)
fn make_fake_crate(name: &str, version: &str, extra_size: usize) -> Vec<u8> {
let cargo_toml = format!(
r#"[package]
name = "{}"
version = "{}"
edition = "2021"
"#,
name, version
);
let lib_rs = format!(
"//! Crate {} v{}\npub fn hello() {{ println!(\"Hello from {} {}\"); }}\n{}",
name,
version,
name,
version,
"// padding\n".repeat(extra_size / 12 + 1),
);
// Build a tar.gz in memory
let mut tar_data = Vec::new();
{
let enc = flate2::write::GzEncoder::new(&mut tar_data, flate2::Compression::fast());
let mut tar = tar::Builder::new(enc);
let prefix = format!("{}-{}", name, version);
// Add Cargo.toml
let cargo_bytes = cargo_toml.as_bytes();
let mut header = tar::Header::new_gnu();
header.set_path(format!("{}/Cargo.toml", prefix)).unwrap();
header.set_size(cargo_bytes.len() as u64);
header.set_mode(0o644);
header.set_cksum();
tar.append(&header, cargo_bytes).unwrap();
// Add src/lib.rs
let lib_bytes = lib_rs.as_bytes();
let mut header = tar::Header::new_gnu();
header.set_path(format!("{}/src/lib.rs", prefix)).unwrap();
header.set_size(lib_bytes.len() as u64);
header.set_mode(0o644);
header.set_cksum();
tar.append(&header, lib_bytes).unwrap();
tar.finish().unwrap();
}
tar_data
}
/// Build znippy archive with N synthetic crates
fn build_crate_archive(num_crates: usize, sizes: &[usize]) -> PathBuf {
let dir = PathBuf::from(BENCH_DIR);
std::fs::create_dir_all(&dir).unwrap();
let archive_base = dir.join(format!("bench_crates_{}", num_crates));
let index_path = archive_base.with_extension("znippy");
if index_path.exists() {
eprintln!(
" ♻️ Reusing cached crate archive ({} entries)",
num_crates
);
return index_path;
}
eprintln!(" 🔧 Building crate archive with {} entries...", num_crates);
let start = Instant::now();
let compressor = compress_stream(&archive_base, true).expect("Failed to create compressor");
let sender = compressor.sender().clone();
for i in 0..num_crates {
let size = sizes[i % sizes.len()];
let name = format!("bench-crate-{:06}", i);
let version = "1.0.0";
let data = make_fake_crate(&name, version, size);
let filename = format!("{}-{}.crate", name, version);
sender
.send(ArchiveEntry {
relative_path: filename,
data,
pkg_type: None,
repo: None,
})
.unwrap();
}
drop(sender);
let _report = compressor.finish().unwrap();
eprintln!(" ✅ Built in {:.1}s", start.elapsed().as_secs_f64());
index_path
}
fn append_bench_result(suite: &str, ops_sec: f64) {
use std::io::Write;
if let Ok(mut f) = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open("/tmp/holger_bench_last.json")
{
let _ = writeln!(f, r#"{{"name":"{}","ops_sec":{:.0}}}"#, suite, ops_sec);
}
}
/// Run a benchmark loop, returns (ops, elapsed, bytes)
fn bench_loop<F>(name: &str, duration: Duration, mut f: F) -> (u64, Duration, u64)
where
F: FnMut(u64) -> usize,
{
// Warmup
for i in 0..100 {
f(i);
}
let start = Instant::now();
let mut ops: u64 = 0;
let mut total_bytes: u64 = 0;
while start.elapsed() < duration {
// Do batches of 1000 to reduce clock overhead
for _ in 0..1000 {
let bytes = f(ops);
total_bytes += bytes as u64;
ops += 1;
}
}
let elapsed = start.elapsed();
let ops_per_sec = ops as f64 / elapsed.as_secs_f64();
let mb_per_sec = total_bytes as f64 / elapsed.as_secs_f64() / 1_048_576.0;
eprintln!(
" 📊 {}: {:>10.0} ops/s | {:>8.1} MB/s | {} ops in {:.2}s",
name,
ops_per_sec,
mb_per_sec,
ops,
elapsed.as_secs_f64()
);
(ops, elapsed, total_bytes)
}
/// Multi-threaded benchmark
fn bench_threaded<F>(name: &str, duration: Duration, threads: usize, f: F) -> (u64, Duration, u64)
where
F: Fn(u64, usize) -> usize + Send + Sync + 'static,
{
let f = Arc::new(f);
let barrier = Arc::new(std::sync::Barrier::new(threads));
let handles: Vec<_> = (0..threads)
.map(|tid| {
let f = Arc::clone(&f);
let barrier = Arc::clone(&barrier);
std::thread::spawn(move || {
// Warmup
for i in 0..50 {
f(i, tid);
}
barrier.wait();
let start = Instant::now();
let mut ops: u64 = 0;
let mut bytes: u64 = 0;
while start.elapsed() < duration {
for _ in 0..500 {
let b = f(ops, tid);
bytes += b as u64;
ops += 1;
}
}
(ops, start.elapsed(), bytes)
})
})
.collect();
let results: Vec<_> = handles.into_iter().map(|h| h.join().unwrap()).collect();
let total_ops: u64 = results.iter().map(|(o, _, _)| o).sum();
let total_bytes: u64 = results.iter().map(|(_, _, b)| b).sum();
let max_elapsed = results.iter().map(|(_, e, _)| *e).max().unwrap();
let ops_per_sec = total_ops as f64 / max_elapsed.as_secs_f64();
let mb_per_sec = total_bytes as f64 / max_elapsed.as_secs_f64() / 1_048_576.0;
eprintln!(
" 📊 {} ({} threads): {:>10.0} ops/s | {:>8.1} MB/s | {} ops in {:.2}s",
name,
threads,
ops_per_sec,
mb_per_sec,
total_ops,
max_elapsed.as_secs_f64()
);
(total_ops, max_elapsed, total_bytes)
}
#[test]
#[ignore]
fn bench_rust_crate_retrieval() {
let _ = env_logger::try_init();
let num_crates: usize = std::env::var("HOLGER_BENCH_CRATES")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(500_000);
// Mix of sizes: tiny (1KB padding), small (10KB), medium (100KB)
let sizes = vec![1_000, 5_000, 10_000, 50_000, 100_000];
let bench_duration = Duration::from_secs(5);
eprintln!("\n═══════════════════════════════════════════════════════════════");
eprintln!(" 🦀 Rust Crate Retrieval Benchmark");
eprintln!(" Archive: {} crates, mixed sizes", num_crates);
eprintln!("═══════════════════════════════════════════════════════════════\n");
let archive_path = build_crate_archive(num_crates, &sizes);
let repo = RustRepoZnippy::with_archive("bench-rust".to_string(), archive_path)
.expect("Failed to open archive");
let repo = Arc::new(repo);
// Benchmark 1: Raw get_file() — single thread
let (st_ops, st_elapsed, _st_bytes) = {
let repo = Arc::clone(&repo);
bench_loop("get_file (single-thread)", bench_duration, |i| {
let idx = (i as usize) % num_crates;
let filename = format!("bench-crate-{:06}-1.0.0.crate", idx);
let data = repo.get_file(&filename).unwrap();
data.len()
})
};
append_bench_result("rust_crate_st", st_ops as f64 / st_elapsed.as_secs_f64());
// Benchmark 2: HTTP handler — single thread
{
let repo = Arc::clone(&repo);
bench_loop("http_handler (single-thread)", bench_duration, |i| {
let idx = (i as usize) % num_crates;
let url = format!("/bench-rust/crates/bench-crate-{:06}/1.0.0/download", idx);
let (status, _, body) = repo.handle_http2_request("GET", &url, &[]).unwrap();
assert_eq!(status, 200);
body.len()
});
}
// Benchmark 3: Multi-threaded get_file()
let num_cpus = std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(4);
for threads in [2, 4, num_cpus].iter().copied().filter(|&t| t <= num_cpus) {
let repo = Arc::clone(&repo);
let (mt_ops, mt_elapsed, _) = bench_threaded(
"get_file (multi-thread)",
bench_duration,
threads,
move |i, tid| {
let idx = ((i as usize) * 7 + tid * 1337) % num_crates;
let filename = format!("bench-crate-{:06}-1.0.0.crate", idx);
let data = repo.get_file(&filename).unwrap();
data.len()
},
);
if threads == num_cpus {
append_bench_result(
&format!("rust_crate_mt{}", num_cpus),
mt_ops as f64 / mt_elapsed.as_secs_f64(),
);
}
}
// Benchmark 4: Multi-threaded HTTP handler
{
let repo = Arc::clone(&repo);
bench_threaded(
"http_handler (multi-thread)",
bench_duration,
num_cpus,
move |i, tid| {
let idx = ((i as usize) * 13 + tid * 997) % num_crates;
let url = format!("/bench-rust/crates/bench-crate-{:06}/1.0.0/download", idx);
let (status, _, body) = repo.handle_http2_request("GET", &url, &[]).unwrap();
assert_eq!(status, 200);
body.len()
},
);
}
eprintln!("\n═══════════════════════════════════════════════════════════════");
eprintln!(" ✅ Rust crate benchmark complete");
eprintln!("═══════════════════════════════════════════════════════════════\n");
}