Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/holger

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");
}