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

nordisk/edda

crates/storage-git-gix/parked-gix-2/gunnar-store-gix/examples/gc_ratio.rs

//! **What fraction of a real `gc` actually deflates anything?**
//!
//! `repack()` runs gitoxide's `Mode::PackCopyAndBaseObjects`, so an object whose
//! pack entry can be carried across is a `memcpy` and never meets a compressor
//! (`P-001`). Only the remainder — loose objects, and deltas whose base did not
//! come along — is decoded and deflated again. `RepackOutcome` reports both
//! counts, and they are the ONLY thing that distinguishes a pack-copy from a
//! re-deflate.
//!
//! That ratio decides whether a faster DEFLATE compressor can move `gc` at all.
//! A compressor that is infinitely fast saves exactly the recompression share
//! and not one byte more, so this must be measured before anything is optimised
//! for it.
//!
//! ```sh
//! gc_ratio <bare-git-dir> [<bare-git-dir> ...]
//! ```
//!
//! Prints one row per repository plus wall and CPU-seconds, so the "would a
//! faster compressor help" question is answered with an Amdahl bound rather
//! than a hope.

use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::Instant;

use gunnar_store::fs::FsStore;
use gunnar_store::RepackOptions;

fn cpu_seconds() -> f64 {
    let s = std::fs::read_to_string("/proc/self/stat").unwrap_or_default();
    let Some((_, rest)) = s.rsplit_once(')') else {
        return 0.0;
    };
    let f: Vec<&str> = rest.split_whitespace().collect();
    let u: f64 = f.get(11).and_then(|x| x.parse().ok()).unwrap_or(0.0);
    let k: f64 = f.get(12).and_then(|x| x.parse().ok()).unwrap_or(0.0);
    let ticks: f64 = Command::new("getconf")
        .arg("CLK_TCK")
        .output()
        .ok()
        .and_then(|o| String::from_utf8_lossy(&o.stdout).trim().parse().ok())
        .unwrap_or(100.0);
    (u + k) / ticks
}

fn loadavg() -> f64 {
    std::fs::read_to_string("/proc/loadavg")
        .ok()
        .and_then(|s| s.split_whitespace().next().and_then(|x| x.parse().ok()))
        .unwrap_or(f64::NAN)
}

fn main() {
    let dirs: Vec<PathBuf> = std::env::args().skip(1).map(PathBuf::from).collect();
    assert!(!dirs.is_empty(), "usage: gc_ratio <bare-git-dir>...");

    println!("Load at start: {:.1}\n", loadavg());
    println!(
        "| repository | objects | **copied from pack** | **recompressed** | recompressed share | wall s | cpu s |"
    );
    println!("|---|---|---|---|---|---|---|");

    let mut tot_obj = 0usize;
    let mut tot_copy = 0usize;
    let mut tot_recomp = 0usize;
    let mut tot_wall = 0.0f64;
    let mut tot_cpu = 0.0f64;

    for d in &dirs {
        let store = FsStore::open_or_init(d, gix_hash::Kind::Sha1).expect("open the repository");
        let c0 = cpu_seconds();
        let t0 = Instant::now();
        let outcome = store.repack(&RepackOptions::new()).expect("repack");
        let wall = t0.elapsed().as_secs_f64();
        let cpu = cpu_seconds() - c0;

        let n = outcome.objects as usize;
        let share = if n == 0 {
            0.0
        } else {
            outcome.objects_recompressed as f64 / n as f64
        };
        println!(
            "| {} | {} | {} | **{}** | **{:.2}%** | {:.3} | {:.2} |",
            name(d),
            n,
            outcome.objects_copied_from_pack,
            outcome.objects_recompressed,
            share * 100.0,
            wall,
            cpu,
        );
        tot_obj += n;
        tot_copy += outcome.objects_copied_from_pack;
        tot_recomp += outcome.objects_recompressed;
        tot_wall += wall;
        tot_cpu += cpu;
    }

    let share = if tot_obj == 0 {
        0.0
    } else {
        tot_recomp as f64 / tot_obj as f64
    };
    println!(
        "| **TOTAL** | {tot_obj} | {tot_copy} | **{tot_recomp}** | **{:.2}%** | {tot_wall:.3} | {tot_cpu:.2} |",
        share * 100.0
    );
    println!("\nLoad at end: {:.1}", loadavg());
    println!(
        "\n**Amdahl ceiling.** A compressor that took ZERO time could remove at most the \
         deflate of {tot_recomp} objects ({:.2}% of the {tot_obj} a gc carries). Everything \
         else is pack-copy, index writing, connectivity and I/O, and no compressor touches it.",
        share * 100.0
    );
}

fn name(p: &Path) -> String {
    p.file_name()
        .map(|s| s.to_string_lossy().into_owned())
        .unwrap_or_else(|| p.display().to_string())
}