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

nordisk/holger

bench-scenarios/src/bin/holger_ds.rs

//! `holger-ds` - the artifact-delivery benchmark, as a BINARY of the bench crate.
//!
//! It used to be a subcommand of `holger-server`, compiled in through the `bench`
//! cargo feature, which pulled `holger-bench-scenarios` (and its reqwest
//! HTTP-bombardment client) into the SHIPPED binary. Cargo unifies features per
//! GRAPH, so that one edge wrote `front`, then `auth-passkey`, then native OpenSSL
//! + `auth-oidc` + `auth-oauth2` into production - three times, unnoticed. Owner
//! ruling 2026-09-20: the bench crate is a DEV DEPENDENCY and never reaches the
//! final binary. So the driver moved here, beside the scenarios it drives, where
//! its `reqwest` and its `server-lib` pin can decide nothing but themselves.
//!
//! The verb, its flags and its output are unchanged:
//!
//! ```text
//! cargo run -p holger-bench-scenarios --features served --bin holger-ds -- \
//!     --preset small --eco crate
//! ```
//!
//! `--target <url>` still benchmarks an ALREADY-RUNNING Holger / Nexus /
//! Artifactory over the real package protocol instead of standing one up.

use std::path::PathBuf;
use std::time::Duration;

use anyhow::{Context, Result};
use clap::Parser;

use holger_bench_scenarios::{
    build_crate_corpus, build_pip_corpus, cores, free_disk_bytes, propose_dataset_bytes, served,
};

/// Named dataset-scale presets for `holger-ds` (the demo-plan micro/small/large
/// slider). When set, `--preset` overrides the raw `--scale` factor.
#[derive(Clone, Copy, Debug, PartialEq, Eq, clap::ValueEnum)]
enum DsScale {
    /// Smallest corpus — smoke test / CI (≈1 GiB intent, clamped to free disk).
    Micro,
    /// A few GiB of crate data.
    Small,
    /// A broad multi-GiB corpus.
    Large,
}

impl DsScale {
    /// The `propose_dataset_bytes` scale factor (≈ N GiB intent) for this preset.
    fn factor(self) -> u64 {
        match self {
            DsScale::Micro => 1,
            DsScale::Small => 4,
            DsScale::Large => 16,
        }
    }
}

/// Knobs for the `holger-ds` artifact-delivery benchmark.
#[derive(Parser)]
#[command(name = "holger-ds")]
struct HolgerDsArgs {
    /// Dataset scale factor: ≈ N GiB of crate data, clamped to ≤ 25 % of the free
    /// disk on `--data`.
    #[arg(long, default_value_t = 1)]
    scale: u64,
    /// Named scale preset (micro|small|large). Overrides `--scale` when set —
    /// the demo-plan scale slider.
    #[arg(long, value_enum)]
    preset: Option<DsScale>,
    /// Directory to build the synthetic corpus in. Default: a temp dir.
    #[arg(long)]
    data: Option<PathBuf>,
    /// Benchmark an already-running Holger / Nexus / Artifactory at this base URL
    /// (e.g. `http://127.0.0.1:18444`, serving the matching `/rust` corpus) over
    /// the real package protocol, instead of standing one up. Point this at an
    /// EXTERNAL existing Nexus/Artifactory to compare without spawning containers.
    #[arg(long)]
    target: Option<String>,
    /// Ecosystem journey to drive: `crate` (Rust sparse download, the default) or
    /// `pip` (the holger-ds Python journey — a PEP 503 / PEP 691 wheel index the
    /// way `uv`/`pip` install from it). Both bombard Holger's native HTTP door.
    #[arg(long, value_enum, default_value = "crate")]
    eco: DsEco,
}

/// Which ecosystem the `holger-ds` journey drives.
#[derive(Clone, Copy, Debug, PartialEq, Eq, clap::ValueEnum)]
enum DsEco {
    /// Rust crates over the cargo sparse download path.
    Crate,
    /// Python wheels over the PEP 503 / PEP 691 simple index (the uv/Airflow journey).
    Pip,
}
/// 1 GiB in bytes, as `f64`, for the `holger-ds` human-readable sizing report.
const GIB_F: f64 = 1024.0 * 1024.0 * 1024.0;
/// How long the `holger-ds` GET bombardment runs.
const DS_BENCH_SECS: u64 = 5;

/// Drive the `holger-ds` artifact-delivery benchmark: autodetect free disk, propose
/// a corpus size, build the synthetic crate corpus, then fetch it over Holger's
/// package protocol and print throughput. Pure reuse of the bench-scenarios lib.
async fn run_holger_ds(a: HolgerDsArgs) -> Result<()> {
    let data_dir = a
        .data
        .unwrap_or_else(|| std::env::temp_dir().join("holger-ds"));
    std::fs::create_dir_all(&data_dir)
        .with_context(|| format!("create data dir {}", data_dir.display()))?;

    // A named preset overrides the raw scale factor (the demo-plan slider).
    let scale = a.preset.map(DsScale::factor).unwrap_or(a.scale);
    let scale_label = match a.preset {
        Some(p) => format!("{:?}(={scale})", p).to_lowercase(),
        None => scale.to_string(),
    };
    let free = free_disk_bytes(&data_dir);
    let budget = propose_dataset_bytes(scale, free);
    let free_report = free
        .map(|f| format!("{:.1} GiB", f as f64 / GIB_F))
        .unwrap_or_else(|| "unknown".to_string());
    println!(
        "holger-ds: scale={}  free-disk={}  -> corpus budget {:.2} GiB in {}",
        scale_label,
        free_report,
        budget as f64 / GIB_F,
        data_dir.display(),
    );

    let concurrency = cores();
    let dur = Duration::from_secs(DS_BENCH_SECS);
    let door = a
        .target
        .as_deref()
        .map(|t| format!("running Holger at {t}"))
        .unwrap_or_else(|| "stood-up Holger HTTP gateway".to_string());

    let outcome = match a.eco {
        DsEco::Crate => {
            let (archive, crates) = build_crate_corpus(&data_dir, budget)?;
            println!("  corpus: {crates} crates  archive {}", archive.display());
            let (files, o) =
                served::run_crate_fetch(&archive, "rust", a.target.as_deref(), concurrency, dur)
                    .await?;
            println!(
                "  served {files} crate files over the package protocol [{door}] @ concurrency {concurrency}:"
            );
            o
        }
        DsEco::Pip => {
            let (archive, packages) = build_pip_corpus(&data_dir, budget)?;
            println!("  corpus: {packages} wheels  archive {}", archive.display());
            let o =
                served::run_pip_index_fetch(&archive, "pip", a.target.as_deref(), concurrency, dur)
                    .await?;
            println!(
                "  served {} wheels over the PEP 503 index [{door}] @ concurrency {concurrency}:",
                o.files
            );
            println!(
                "    index resolve : {:.0} ops/sec ({} packages, /simple/<name>/)",
                o.index.ops_per_sec, o.packages,
            );
            // Report the wheel-download leg through the same fields below.
            o.wheel
        }
    };

    println!("    throughput : {:.1} MB/s", outcome.mbs);
    println!("    ops/sec    : {:.0}", outcome.ops_per_sec);
    println!(
        "    total      : {} ops, {:.2} GiB off the wire in {DS_BENCH_SECS}s",
        outcome.ops,
        outcome.bytes as f64 / GIB_F,
    );
    Ok(())
}
#[tokio::main(flavor = "multi_thread")]
async fn main() -> Result<()> {
    run_holger_ds(HolgerDsArgs::parse()).await
}