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

nordisk/edda

crates/gunnar-pack/src/oidprobe.rs

//! Counting every oid → pack-offset resolution a request makes, at the seams
//! gunnar owns — **and nothing at all** without the `oidprobe` feature.
//!
//! It answers one question: *how often does a real fetch resolve an oid to a
//! pack offset, and where*. That is a diagnostic, not a feature, so the counters
//! are behind a cargo feature that no gunnar crate turns on and only `bench/`
//! asks for — the same shape, and for the same reason, as `gunnar-wire/stages`.
//! With it off [`bump`] is an empty inline function and the statics are not
//! there to be loaded: the default build has **no probe**, rather than a probe
//! that is switched off.
//!
//! A relaxed `fetch_add` is on the order of a nanosecond against a lookup of
//! fifty, so leaving it in unconditionally would be a 2% tax on the exact path
//! `.nornir/fanout16-reasoning.md` was written to measure.
//!
//! Every counter names a **call site**, not an implementation. `gix_odb_*` are
//! calls that enter `gix-odb`, each of which does its own `.idx` fanout+bsearch
//! inside gix and which gunnar therefore cannot front without forking gix.
//! `bitmap_*` are the two that go through gunnar's own
//! `gunnar_store::PackBitmap`, holding a `gix_pack::index::File` gunnar opened
//! itself — the only ones a 16-bit fanout could actually sit in front of.

#[cfg(feature = "oidprobe")]
use std::sync::atomic::{AtomicU64, Ordering::Relaxed};

/// The counters, when there are any.
#[cfg(feature = "oidprobe")]
macro_rules! counters {
    ($($(#[$m:meta])* $name:ident),* $(,)?) => {
        $($(#[$m])* pub static $name: AtomicU64 = AtomicU64::new(0);)*

        /// Every counter, by name, as it stands.
        pub fn snapshot() -> Vec<(&'static str, u64)> {
            vec![$((stringify!($name), $name.load(Relaxed))),*]
        }

        /// Back to zero.
        pub fn reset() {
            $($name.store(0, Relaxed);)*
        }
    };
}

#[cfg(feature = "oidprobe")]
counters! {
    /// `ObjectStore::contains` → `gix_object::Exists::exists`.
    GIX_ODB_CONTAINS,
    /// `ObjectStore::contains_without_rescan` → `gix_object::Exists::exists`,
    /// i.e. the negotiation's membership test, which answers a miss instead of
    /// re-scanning `objects/pack` to disbelieve it.
    ///
    /// Counted **separately from `GIX_ODB_CONTAINS`, not as well as it**, so the
    /// split between the two call sites is visible rather than inferred. That
    /// matters here more than usual: the intervention is defined to leave every
    /// answer, every wire byte and every call *total* unchanged, so the only
    /// thing that can witness which of the two paths a request took is which of
    /// these two counters moved.
    GIX_ODB_CONTAINS_NO_RESCAN,
    /// `ObjectStore::read` → `gix_object::Find::try_find`.
    GIX_ODB_READ,
    /// `ObjectStore::header` → `gix_object::FindHeader::try_header`.
    GIX_ODB_HEADER,
    /// `gix_pack::Find::contains` on `PackedObjects`.
    GIX_PACK_CONTAINS,
    /// `gix_pack::Find::try_find_cached` on `PackedObjects`.
    GIX_PACK_TRY_FIND_CACHED,
    /// `gix_pack::Find::location_by_oid` on `PackedObjects` — the one
    /// `iter_from_counts` calls once per selected object.
    GIX_PACK_LOCATION_BY_OID,
    /// `gix_pack::Find::entry_by_location` — an OFFSET lookup, no oid search.
    /// Counted so the ratio of oid-keyed to offset-keyed work is visible.
    GIX_PACK_ENTRY_BY_LOCATION,

    /// `PackBitmap::pack_position` → `index::File::lookup`.
    BITMAP_PACK_POSITION,
    /// `PackBitmap::BITMAP_FOR_COMMIT` → `index::File::lookup`.
    BITMAP_FOR_COMMIT,

    // ── the census counters ─────────────────────────────────────────────────
    //
    // The nine above answer "how often does a request resolve an oid". These
    // answer "what else does a request ask gix for", which is the other half of
    // `.nornir/gix-census-reasoning.md` and the half that turned out to hold
    // the cycles. They are counted at the same seams and by the same macro, so
    // one `snapshot()` is the whole census (LAW 5).
    /// `gix_pack::output::entry::iter_from_counts`, once per pack written.
    /// The entry point that owns 75–98% of a fetch's CPU.
    GIX_PACK_ITER_FROM_COUNTS,
    /// Objects `iter_from_counts` copied out of a pack **verbatim** — no
    /// inflate, no deflate. `P-001`'s applied output.
    GIX_PACK_COPIED,
    /// Objects it inflated and deflated again instead, plus the ones gunnar's
    /// thin veto took back off the copied side. The zlib bill in one number.
    GIX_PACK_RECOMPRESSED,
    /// The sum of `output::Entry::decompressed_size` over every entry the
    /// generator emitted: the inflated volume of the pack gunnar just served.
    ///
    /// A count alone cannot tell one 40 MB blob from four hundred 100-byte
    /// trees, and both the inflate and the deflate bills are in the bytes. gix
    /// does not report a byte figure of its own — `iter_from_counts::Outcome`
    /// carries four object counts and nothing else — so this is summed at
    /// gunnar's own loop over the entries.
    GIX_PACK_ENTRY_BYTES,

    /// Entries `iter_from_counts` emitted as a whole `Kind::Base`. On a
    /// **copy** path this is not "an object with no delta": it is every object
    /// whose pack entry was itself a base, plus every object gix expanded
    /// because it could not name the base it had.
    GIX_PACK_KIND_BASE,
    /// Entries emitted as `Kind::DeltaRef` — a delta whose base travels in the
    /// same pack. The cheap case: header rewritten, payload copied.
    GIX_PACK_KIND_DELTA_REF,
    /// Thin deltas gunnar's own veto took back: gix produced a `DeltaOid` and
    /// `client_has` would not vouch for the base, so the object was written
    /// whole instead.
    ///
    /// Counted separately because the veto's fallback read goes through
    /// `PackedObjects::handle` **directly** (`copy.rs:451`) rather than through
    /// the `gix_pack::Find` impl, so `GIX_PACK_TRY_FIND_CACHED` does not see
    /// it. Without this counter a fetch reports `TRY_FIND_CACHED 0` beside
    /// `RECOMPRESSED 182` and the obvious reading — "the veto never fired" —
    /// is exactly backwards.
    GIX_PACK_THIN_VETOED,
    /// Entries emitted as `Kind::DeltaOid` — a delta gix named its base for by
    /// **object id** rather than by a position inside the pack. Non-zero only
    /// when the caller vouched for the receiver's contents, so it is also the
    /// check that `allow_thin_pack` really was on: a fetch that reports zero
    /// here served a non-thin pack whatever the request asked for.
    ///
    /// **It does not mean the base is outside the transfer**, and reading it
    /// that way cost a fetch on 2026-08-05. gix emits this whenever it could
    /// not re-point the delta inside the pack it is writing, which on a
    /// multi-pack store includes bases that *are* being sent — the base
    /// resolved to a copy in one pack while the delta was copied out of
    /// another. Whether the receiver holds the base is `client_has`'s question
    /// and nothing here answers it.
    GIX_PACK_KIND_DELTA_OID,

    /// `gix_pack::Bundle::write_to_directory` — the whole of `index_thin_pack`,
    /// once per push. Stream decode, CRC, delta-tree resolution, thin-base
    /// completion and `.idx` writing are all inside this one call.
    GIX_PACK_BUNDLE_WRITE,
    /// `gix_pack::index::File::at` — opening an `.idx` to read it back, which
    /// the push path does once more after `Bundle::write_to_directory` to list
    /// what arrived.
    GIX_PACK_INDEX_OPEN,
    /// `gix_pack::index::File::iter` — a full pass over every entry of an
    /// `.idx`. Cheap per entry and O(pack) per call, which is the shape worth
    /// counting.
    GIX_PACK_INDEX_ITER,

    /// `gix_object::Find::try_find` reaching gunnar from **inside** gix: the
    /// thin-base lookups `Bundle::write_to_directory` makes against the
    /// receiving store while completing a pushed pack.
    GIX_OBJECT_FIND_FROM_GIX,

    /// `gix_ref` reads: `file::Store::iter`, `try_find` and `find`. Being
    /// replaced by `ArchiveRefStore` in another lane; counted so the census can
    /// say what that lane is worth rather than assert it.
    GIX_REF_READ,
    /// `gix_ref` writes: one `file::Transaction::prepare` + `commit`.
    GIX_REF_TXN,
}

/// Add one to `c`.
#[cfg(feature = "oidprobe")]
#[inline(always)]
pub fn bump(c: &AtomicU64) {
    c.fetch_add(1, Relaxed);
}

/// Add `n` to `c`, for the counters that tally a batch rather than a call.
///
/// `GIX_PACK_COPIED` and `GIX_PACK_RECOMPRESSED` are gix's own per-pack totals,
/// corrected by gunnar's thin veto. Bumping them one at a time from inside the
/// entry loop would need a probe on the hot path to report a number that is
/// already sitting in the outcome.
#[cfg(feature = "oidprobe")]
#[inline(always)]
pub fn bump_by(c: &AtomicU64, n: u64) {
    c.fetch_add(n, Relaxed);
}

/// The uninstrumented [`bump`]: takes nothing, does nothing, compiles to
/// nothing. Call sites read identically in both builds, so the instrumented and
/// uninstrumented serve paths cannot come to differ.
#[cfg(not(feature = "oidprobe"))]
#[inline(always)]
pub fn bump(_c: &()) {}

/// The uninstrumented [`bump_by`].
#[cfg(not(feature = "oidprobe"))]
#[inline(always)]
pub fn bump_by(_c: &(), _n: u64) {}

/// Every counter, by name, as it stands. Empty without the feature.
#[cfg(not(feature = "oidprobe"))]
pub fn snapshot() -> Vec<(&'static str, u64)> {
    Vec::new()
}

/// Back to zero. Nothing to zero without the feature.
#[cfg(not(feature = "oidprobe"))]
pub fn reset() {}

/// The call-site names, when the counters are not compiled in.
///
/// A call site reads `bump(&GIX_ODB_READ)` in both builds; without the feature
/// these are the units it passes and `bump` discards.
#[cfg(not(feature = "oidprobe"))]
mod off {
    macro_rules! placeholders {
        ($($name:ident),* $(,)?) => {
            $(
                /// A call site's name with no counter behind it.
                pub static $name: () = ();
            )*
        };
    }
    placeholders! {
        GIX_ODB_CONTAINS,
        GIX_ODB_CONTAINS_NO_RESCAN,
        GIX_ODB_READ,
        GIX_ODB_HEADER,
        GIX_PACK_CONTAINS,
        GIX_PACK_TRY_FIND_CACHED,
        GIX_PACK_LOCATION_BY_OID,
        GIX_PACK_ENTRY_BY_LOCATION,
        BITMAP_PACK_POSITION,
        BITMAP_FOR_COMMIT,
        GIX_PACK_ITER_FROM_COUNTS,
        GIX_PACK_COPIED,
        GIX_PACK_RECOMPRESSED,
        GIX_PACK_ENTRY_BYTES,
        GIX_PACK_KIND_BASE,
        GIX_PACK_KIND_DELTA_REF,
        GIX_PACK_KIND_DELTA_OID,
        GIX_PACK_THIN_VETOED,
        GIX_PACK_BUNDLE_WRITE,
        GIX_PACK_INDEX_OPEN,
        GIX_PACK_INDEX_ITER,
        GIX_OBJECT_FIND_FROM_GIX,
        GIX_REF_READ,
        GIX_REF_TXN,
    }
}

#[cfg(not(feature = "oidprobe"))]
pub use off::*;