zccache 1.11.21

Local-first compiler cache for C/C++/Rust/Emscripten
Documentation
//! Rust (rustc) warm-cache benchmark: zccache vs sccache vs bare rustc.

use std::time::Duration;
use zccache::protocol::{Request, Response};

use super::common::{
    find_sccache, fmt_dur, fmt_ratio, median, print_trials, start_daemon, RUSTC_NUM_FILES,
    RUSTC_WARM_TRIALS,
};
use super::rust_project::{
    generate_rust_project, run_rustc_batch, run_sccache_rustc_batch, run_zccache_rustc_batch,
    rust_source_names, rustc_args_for, rustc_check_args_for, warmup_rustc,
};

/// Rust compilation: bare rustc vs sccache vs zccache, 50 independent .rs lib files.
/// Tests both `cargo build` (emit link+metadata+dep-info) and `cargo check` (emit metadata+dep-info) modes.
#[tokio::test]
#[ignore]
async fn perf_rustc_zccache_vs_sccache() {
    let rustc_path = match zccache::test_support::find_rustc() {
        Some(p) => p,
        None => {
            eprintln!("SKIP: rustc not found");
            return;
        }
    };
    let rc = rustc_path.to_string_lossy().to_string();
    let srcs = rust_source_names();

    eprintln!();
    eprintln!("\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}");
    eprintln!("  RUST COMPILATION BENCHMARK");
    eprintln!("  {RUSTC_NUM_FILES} .rs files \u{00b7} {RUSTC_WARM_TRIALS} warm trials \u{00b7} each tool in its own tempdir");
    eprintln!("  Compiler: {rc}");
    eprintln!("  Modes: build (--emit=dep-info,metadata,link) + check (--emit=dep-info,metadata)");
    eprintln!("\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}\u{2550}");
    eprintln!();

    // Helper: run a mode (build or check) through all 3 tools.
    // ─── Build mode (--emit=dep-info,metadata,link) ─────────────────────
    eprintln!("  ─── Build mode (cargo build) ───");
    eprintln!();

    let bl_dir = zccache::test_support::temp_cache_dir().unwrap();
    generate_rust_project(bl_dir.path());
    eprintln!("  [1/3] Bare rustc");
    warmup_rustc(&rc, bl_dir.path());
    let build_bl_cold = run_rustc_batch(&rc, bl_dir.path(), &srcs, rustc_args_for);
    eprintln!("        cold:  {}", fmt_dur(build_bl_cold));
    let build_bl_warm = run_rustc_batch(&rc, bl_dir.path(), &srcs, rustc_args_for);
    eprintln!("        warm:  {}", fmt_dur(build_bl_warm));
    eprintln!();
    drop(bl_dir);

    let build_sc_cold;
    let build_sc_warm;
    if let Some(ref scc_bin) = find_sccache() {
        let sd = zccache::test_support::temp_cache_dir().unwrap();
        generate_rust_project(sd.path());
        let scd = zccache::test_support::temp_cache_dir().unwrap();
        let scd_s = scd.path().to_string_lossy().into_owned();
        std::env::set_var("SCCACHE_DIR", &scd_s);
        eprintln!("  [2/3] sccache");
        let _ = std::process::Command::new(scc_bin)
            .arg("--stop-server")
            .stdout(std::process::Stdio::null())
            .stderr(std::process::Stdio::null())
            .status();
        if scd.path().exists() {
            let _ = std::fs::remove_dir_all(scd.path());
            let _ = std::fs::create_dir_all(scd.path());
        }
        let _ = std::process::Command::new(scc_bin)
            .arg("--start-server")
            .env("SCCACHE_DIR", &scd_s)
            .stdout(std::process::Stdio::null())
            .stderr(std::process::Stdio::null())
            .status();
        warmup_rustc(&rc, sd.path());
        let c = run_sccache_rustc_batch(scc_bin, &rc, sd.path(), &srcs, rustc_args_for);
        eprintln!("        cold:  {}", fmt_dur(c));
        build_sc_cold = Some(c);
        let mut t = Vec::with_capacity(RUSTC_WARM_TRIALS);
        for _ in 0..RUSTC_WARM_TRIALS {
            t.push(run_sccache_rustc_batch(
                scc_bin,
                &rc,
                sd.path(),
                &srcs,
                rustc_args_for,
            ));
        }
        print_trials("warm:", &t);
        build_sc_warm = Some(t);
        let _ = std::process::Command::new(scc_bin)
            .arg("--stop-server")
            .stdout(std::process::Stdio::null())
            .stderr(std::process::Stdio::null())
            .status();
        std::env::remove_var("SCCACHE_DIR");
        eprintln!();
    } else {
        eprintln!("  [2/3] sccache: not found, skipping\n");
        build_sc_cold = None;
        build_sc_warm = None;
    }

    let zd = zccache::test_support::temp_cache_dir().unwrap();
    generate_rust_project(zd.path());
    let zc = zd.path().to_string_lossy().into_owned();
    eprintln!("  [3/3] zccache");
    let (_zccache_cache_dir, ep, sh, sd) = start_daemon().await;
    let mut cl = zccache::ipc::connect(&ep).await.unwrap();
    cl.send(&Request::SessionStart {
        client_pid: std::process::id(),
        working_dir: zc.clone().into(),
        log_file: None,
        track_stats: true,
        journal_path: None,
        profile: false,
        private_daemon: None,
    })
    .await
    .unwrap();
    let sid = match cl.recv().await.unwrap() {
        Some(Response::SessionStarted { session_id, .. }) => session_id,
        other => panic!("expected SessionStarted, got: {other:?}"),
    };
    warmup_rustc(&rc, zd.path());
    let build_zc_cold =
        run_zccache_rustc_batch(&mut cl, &sid, &rc, &zc, &srcs, rustc_args_for).await;
    eprintln!("        cold:  {}", fmt_dur(build_zc_cold));
    let mut build_zc_warm = Vec::with_capacity(RUSTC_WARM_TRIALS);
    for _ in 0..RUSTC_WARM_TRIALS {
        build_zc_warm
            .push(run_zccache_rustc_batch(&mut cl, &sid, &rc, &zc, &srcs, rustc_args_for).await);
    }
    print_trials("warm:", &build_zc_warm);
    eprintln!();

    // ─── Check mode (--emit=dep-info,metadata) ──────────────────────────
    eprintln!("  ─── Check mode (cargo check) ───");
    eprintln!();

    let bl_dir2 = zccache::test_support::temp_cache_dir().unwrap();
    generate_rust_project(bl_dir2.path());
    eprintln!("  [1/3] Bare rustc");
    warmup_rustc(&rc, bl_dir2.path());
    let check_bl_cold = run_rustc_batch(&rc, bl_dir2.path(), &srcs, rustc_check_args_for);
    eprintln!("        cold:  {}", fmt_dur(check_bl_cold));
    let check_bl_warm = run_rustc_batch(&rc, bl_dir2.path(), &srcs, rustc_check_args_for);
    eprintln!("        warm:  {}", fmt_dur(check_bl_warm));
    eprintln!();
    drop(bl_dir2);

    let check_sc_cold;
    let check_sc_warm;
    if let Some(ref scc_bin) = find_sccache() {
        let sd = zccache::test_support::temp_cache_dir().unwrap();
        generate_rust_project(sd.path());
        let scd = zccache::test_support::temp_cache_dir().unwrap();
        let scd_s = scd.path().to_string_lossy().into_owned();
        std::env::set_var("SCCACHE_DIR", &scd_s);
        eprintln!("  [2/3] sccache");
        let _ = std::process::Command::new(scc_bin)
            .arg("--stop-server")
            .stdout(std::process::Stdio::null())
            .stderr(std::process::Stdio::null())
            .status();
        if scd.path().exists() {
            let _ = std::fs::remove_dir_all(scd.path());
            let _ = std::fs::create_dir_all(scd.path());
        }
        let _ = std::process::Command::new(scc_bin)
            .arg("--start-server")
            .env("SCCACHE_DIR", &scd_s)
            .stdout(std::process::Stdio::null())
            .stderr(std::process::Stdio::null())
            .status();
        warmup_rustc(&rc, sd.path());
        let c = run_sccache_rustc_batch(scc_bin, &rc, sd.path(), &srcs, rustc_check_args_for);
        eprintln!("        cold:  {}", fmt_dur(c));
        check_sc_cold = Some(c);
        let mut t = Vec::with_capacity(RUSTC_WARM_TRIALS);
        for _ in 0..RUSTC_WARM_TRIALS {
            t.push(run_sccache_rustc_batch(
                scc_bin,
                &rc,
                sd.path(),
                &srcs,
                rustc_check_args_for,
            ));
        }
        print_trials("warm:", &t);
        check_sc_warm = Some(t);
        let _ = std::process::Command::new(scc_bin)
            .arg("--stop-server")
            .stdout(std::process::Stdio::null())
            .stderr(std::process::Stdio::null())
            .status();
        std::env::remove_var("SCCACHE_DIR");
        eprintln!();
    } else {
        eprintln!("  [2/3] sccache: not found, skipping\n");
        check_sc_cold = None;
        check_sc_warm = None;
    }

    // Reuse zccache daemon — clear cache for fresh check-mode measurement
    cl.send(&Request::Clear).await.unwrap();
    let _ = cl.recv::<Response>().await;
    generate_rust_project(zd.path());
    eprintln!("  [3/3] zccache");
    warmup_rustc(&rc, zd.path());
    let check_zc_cold =
        run_zccache_rustc_batch(&mut cl, &sid, &rc, &zc, &srcs, rustc_check_args_for).await;
    eprintln!("        cold:  {}", fmt_dur(check_zc_cold));
    let mut check_zc_warm = Vec::with_capacity(RUSTC_WARM_TRIALS);
    for _ in 0..RUSTC_WARM_TRIALS {
        check_zc_warm.push(
            run_zccache_rustc_batch(&mut cl, &sid, &rc, &zc, &srcs, rustc_check_args_for).await,
        );
    }
    print_trials("warm:", &check_zc_warm);

    cl.send(&Request::SessionEnd { session_id: sid })
        .await
        .unwrap();
    let _ = cl.recv::<Response>().await;
    sd.notify_one();
    sh.await.unwrap();

    // ── Results table ──────────────────────────────────────────────────
    let dash = "\u{2014}";
    let build_zm = median(&build_zc_warm);
    let check_zm = median(&check_zc_warm);

    eprintln!();
    eprintln!("\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}");
    eprintln!("  RESULTS");
    eprintln!("\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}");
    eprintln!();
    eprintln!("## Rust Benchmark: {RUSTC_NUM_FILES} .rs files, {RUSTC_WARM_TRIALS} warm trials");
    eprintln!();
    eprintln!("| Scenario | Bare rustc | sccache | zccache | vs sccache | vs bare rustc |");
    eprintln!("|:---------|----------:|--------:|--------:|-----------:|--------------:|");

    // Helper closure for table rows
    let row = |label: &str, bl: Duration, sc: Option<Duration>, zc: Duration, bold: bool| {
        let sc_s = sc.map(fmt_dur);
        let sc_str = sc_s.as_deref().unwrap_or(dash);
        let vs_sc = sc.map(|s| fmt_ratio(s, zc, bold));
        let vs_bl = fmt_ratio(bl, zc, bold);
        let zc_fmt = if bold {
            format!("**{}**", fmt_dur(zc))
        } else {
            fmt_dur(zc)
        };
        eprintln!(
            "| {} | {} | {} | {} | {} | {} |",
            label,
            fmt_dur(bl),
            sc_str,
            zc_fmt,
            vs_sc.as_deref().unwrap_or(dash),
            vs_bl
        );
    };

    row(
        "Build, Cold",
        build_bl_cold,
        build_sc_cold,
        build_zc_cold,
        false,
    );
    row(
        "Build, Warm",
        build_bl_warm,
        build_sc_warm.as_ref().map(|t| median(t)),
        build_zm,
        true,
    );
    row(
        "Check, Cold",
        check_bl_cold,
        check_sc_cold,
        check_zc_cold,
        false,
    );
    row(
        "Check, Warm",
        check_bl_warm,
        check_sc_warm.as_ref().map(|t| median(t)),
        check_zm,
        true,
    );

    eprintln!();
    eprintln!("> **Build** = `--emit=dep-info,metadata,link` (cargo build). **Check** = `--emit=dep-info,metadata` (cargo check).");
    eprintln!("> **Cold** = first compile (empty cache). **Warm** = median of {RUSTC_WARM_TRIALS} subsequent runs.");

    eprintln!();
    eprintln!("### Bottom Line");
    eprintln!();
    let bld_vs_rc = build_bl_warm.as_secs_f64() / build_zm.as_secs_f64();
    let chk_vs_rc = check_bl_warm.as_secs_f64() / check_zm.as_secs_f64();
    if let Some(ref t) = build_sc_warm {
        let bld_vs_sc = median(t).as_secs_f64() / build_zm.as_secs_f64();
        eprintln!("  Build warm:  {bld_vs_rc:.1}x faster than bare rustc, {bld_vs_sc:.1}x faster than sccache");
    } else {
        eprintln!("  Build warm:  {bld_vs_rc:.1}x faster than bare rustc");
    }
    if let Some(ref t) = check_sc_warm {
        let chk_vs_sc = median(t).as_secs_f64() / check_zm.as_secs_f64();
        eprintln!("  Check warm:  {chk_vs_rc:.1}x faster than bare rustc, {chk_vs_sc:.1}x faster than sccache");
    } else {
        eprintln!("  Check warm:  {chk_vs_rc:.1}x faster than bare rustc");
    }
    eprintln!();
}