mitts 0.3.28

Rust-first build and package tooling with a shared artifact cache
Documentation
/*
 * Mitts Build — Rust-first build and package tooling with shared artifact cache
 * Copyright (c) 2026 Alexander Panasenko
 *
 * Contact: alex@prod.codes
 * Author: https://prod.codes/about/
 * Project: https://github.com/alex09x/mitts
 * SPDX-License-Identifier: MIT OR Apache-2.0
 */

use std::sync::atomic::{AtomicU64, Ordering};
use std::time::Duration;

static BYTES_CLONED: AtomicU64 = AtomicU64::new(0);
static BYTES_COPIED: AtomicU64 = AtomicU64::new(0);
static BYTES_HARDLINKED: AtomicU64 = AtomicU64::new(0);

static COUNT_CLONED: AtomicU64 = AtomicU64::new(0);
static COUNT_COPIED: AtomicU64 = AtomicU64::new(0);
static COUNT_HARDLINKED: AtomicU64 = AtomicU64::new(0);

static NANOS_RESTORE: AtomicU64 = AtomicU64::new(0);
static NANOS_RESTORE_MISS: AtomicU64 = AtomicU64::new(0);
static NANOS_PUBLISH: AtomicU64 = AtomicU64::new(0);
static NANOS_RUSTC: AtomicU64 = AtomicU64::new(0);
static NANOS_BUILD_SCRIPT: AtomicU64 = AtomicU64::new(0);
static NANOS_UNIT_KEY: AtomicU64 = AtomicU64::new(0);
static COUNT_BUILD_SCRIPT_HIT: AtomicU64 = AtomicU64::new(0);
static COUNT_BUILD_SCRIPT_MISS: AtomicU64 = AtomicU64::new(0);
static NANOS_INPUT_VALIDATION: AtomicU64 = AtomicU64::new(0);
static COUNT_INPUT_VALIDATION: AtomicU64 = AtomicU64::new(0);

/// Measures an input-validation boundary, including early error returns.
pub(crate) struct InputValidationTimer(std::time::Instant);

impl InputValidationTimer {
    pub(crate) fn start() -> Self {
        Self(std::time::Instant::now())
    }
}

impl Drop for InputValidationTimer {
    fn drop(&mut self) {
        NANOS_INPUT_VALIDATION.fetch_add(self.0.elapsed().as_nanos() as u64, Ordering::Relaxed);
        COUNT_INPUT_VALIDATION.fetch_add(1, Ordering::Relaxed);
    }
}

/// Records duration spent in a unit's build-script step (cache lookup, or compile and run).
pub fn record_build_script_duration(dur: Duration) {
    NANOS_BUILD_SCRIPT.fetch_add(dur.as_nanos() as u64, Ordering::Relaxed);
}

/// Records duration spent deriving a unit's effective action key.
pub fn record_unit_key_duration(dur: Duration) {
    NANOS_UNIT_KEY.fetch_add(dur.as_nanos() as u64, Ordering::Relaxed);
}

/// Records a build-script execution served from the CAS.
pub fn record_build_script_hit() {
    COUNT_BUILD_SCRIPT_HIT.fetch_add(1, Ordering::Relaxed);
}

/// Records a build script that had to be compiled and run.
pub fn record_build_script_miss() {
    COUNT_BUILD_SCRIPT_MISS.fetch_add(1, Ordering::Relaxed);
}

/// Snapshot of the build-script and key-derivation counters:
/// (build_script_nanos, unit_key_nanos, build_script_hits, build_script_misses).
pub fn get_phase_metrics_snapshot() -> (u64, u64, u64, u64) {
    (
        NANOS_BUILD_SCRIPT.load(Ordering::Relaxed),
        NANOS_UNIT_KEY.load(Ordering::Relaxed),
        COUNT_BUILD_SCRIPT_HIT.load(Ordering::Relaxed),
        COUNT_BUILD_SCRIPT_MISS.load(Ordering::Relaxed),
    )
}

/// Records a successful file clone (CoW reflink) and its byte size.
pub fn record_cloned(bytes: u64) {
    COUNT_CLONED.fetch_add(1, Ordering::Relaxed);
    BYTES_CLONED.fetch_add(bytes, Ordering::Relaxed);
}

/// Records a file byte copy and its size.
pub fn record_copied(bytes: u64) {
    COUNT_COPIED.fetch_add(1, Ordering::Relaxed);
    BYTES_COPIED.fetch_add(bytes, Ordering::Relaxed);
}

/// Records an intra-workspace hardlink and its file size.
pub fn record_hardlinked(bytes: u64) {
    COUNT_HARDLINKED.fetch_add(1, Ordering::Relaxed);
    BYTES_HARDLINKED.fetch_add(bytes, Ordering::Relaxed);
}

/// Records duration spent in successfully restoring artifacts from CAS.
pub fn record_restore_duration(dur: Duration) {
    NANOS_RESTORE.fetch_add(dur.as_nanos() as u64, Ordering::Relaxed);
}

/// Records duration spent in checking and looking up un-cached artifacts (cache miss).
pub fn record_restore_miss_duration(dur: Duration) {
    NANOS_RESTORE_MISS.fetch_add(dur.as_nanos() as u64, Ordering::Relaxed);
}

/// Records duration spent in publishing/staging artifacts to CAS.
pub fn record_publish_duration(dur: Duration) {
    NANOS_PUBLISH.fetch_add(dur.as_nanos() as u64, Ordering::Relaxed);
}

/// Records duration spent in rustc compilation.
pub fn record_rustc_duration(dur: Duration) {
    NANOS_RUSTC.fetch_add(dur.as_nanos() as u64, Ordering::Relaxed);
}

/// Returns a snapshot of metrics counters:
/// (bytes_cloned, bytes_copied, bytes_hardlinked, files_cloned, files_copied, files_hardlinked,
///  restore_nanos, restore_miss_nanos, publish_nanos, rustc_nanos).
pub fn get_metrics_snapshot() -> (u64, u64, u64, u64, u64, u64, u64, u64, u64, u64) {
    (
        BYTES_CLONED.load(Ordering::Relaxed),
        BYTES_COPIED.load(Ordering::Relaxed),
        BYTES_HARDLINKED.load(Ordering::Relaxed),
        COUNT_CLONED.load(Ordering::Relaxed),
        COUNT_COPIED.load(Ordering::Relaxed),
        COUNT_HARDLINKED.load(Ordering::Relaxed),
        NANOS_RESTORE.load(Ordering::Relaxed),
        NANOS_RESTORE_MISS.load(Ordering::Relaxed),
        NANOS_PUBLISH.load(Ordering::Relaxed),
        NANOS_RUSTC.load(Ordering::Relaxed),
    )
}

/// Resets all counters for a new benchmark or build pass.
pub fn reset_metrics() {
    BYTES_CLONED.store(0, Ordering::Relaxed);
    BYTES_COPIED.store(0, Ordering::Relaxed);
    BYTES_HARDLINKED.store(0, Ordering::Relaxed);
    COUNT_CLONED.store(0, Ordering::Relaxed);
    COUNT_COPIED.store(0, Ordering::Relaxed);
    COUNT_HARDLINKED.store(0, Ordering::Relaxed);
    NANOS_RESTORE.store(0, Ordering::Relaxed);
    NANOS_RESTORE_MISS.store(0, Ordering::Relaxed);
    NANOS_PUBLISH.store(0, Ordering::Relaxed);
    NANOS_RUSTC.store(0, Ordering::Relaxed);
    NANOS_BUILD_SCRIPT.store(0, Ordering::Relaxed);
    NANOS_UNIT_KEY.store(0, Ordering::Relaxed);
    COUNT_BUILD_SCRIPT_HIT.store(0, Ordering::Relaxed);
    COUNT_BUILD_SCRIPT_MISS.store(0, Ordering::Relaxed);
    NANOS_INPUT_VALIDATION.store(0, Ordering::Relaxed);
    COUNT_INPUT_VALIDATION.store(0, Ordering::Relaxed);
}

/// Prints structured timings to stderr when MITTS_TIMINGS=1 or verbose mode is enabled.
pub fn print_timings(compile_wall: Duration, test_exec_wall: Duration, total_wall: Duration) {
    let (
        cloned_b,
        copied_b,
        linked_b,
        cloned_n,
        copied_n,
        linked_n,
        rest_ns,
        rest_miss_ns,
        pub_ns,
        rustc_ns,
    ) = get_metrics_snapshot();
    eprintln!(
        "[mitts::phases] compile_s={:.3} test_exec_s={:.3} total_s={:.3}",
        compile_wall.as_secs_f64(),
        test_exec_wall.as_secs_f64(),
        total_wall.as_secs_f64()
    );
    eprintln!(
        "[mitts::io] bytes_cloned={} bytes_copied={} bytes_hardlinked={} files_cloned={} files_copied={} files_hardlinked={}",
        cloned_b, copied_b, linked_b, cloned_n, copied_n, linked_n
    );
    eprintln!(
        "[mitts::timings_sum] restore_sum_s={:.3} restore_miss_sum_s={:.3} publish_sum_s={:.3} rustc_sum_s={:.3}",
        rest_ns as f64 / 1_000_000_000.0,
        rest_miss_ns as f64 / 1_000_000_000.0,
        pub_ns as f64 / 1_000_000_000.0,
        rustc_ns as f64 / 1_000_000_000.0
    );
    let (bs_ns, key_ns, bs_hits, bs_misses) = get_phase_metrics_snapshot();
    eprintln!(
        "[mitts::input_validation] calls={} elapsed_s={:.3}",
        COUNT_INPUT_VALIDATION.load(Ordering::Relaxed),
        NANOS_INPUT_VALIDATION.load(Ordering::Relaxed) as f64 / 1_000_000_000.0
    );
    eprintln!(
        "[mitts::timings_sum] build_script_sum_s={:.3} unit_key_sum_s={:.3} build_script_hits={} build_script_misses={}",
        bs_ns as f64 / 1_000_000_000.0,
        key_ns as f64 / 1_000_000_000.0,
        bs_hits,
        bs_misses
    );
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_metrics_recording_and_reset() {
        reset_metrics();
        record_cloned(1000);
        record_copied(2000);
        record_hardlinked(3000);
        record_restore_duration(Duration::from_millis(50));
        record_restore_miss_duration(Duration::from_millis(15));
        record_publish_duration(Duration::from_millis(60));
        record_rustc_duration(Duration::from_millis(70));

        let snap = get_metrics_snapshot();
        assert_eq!(snap.0, 1000);
        assert_eq!(snap.1, 2000);
        assert_eq!(snap.2, 3000);
        assert_eq!(snap.3, 1);
        assert_eq!(snap.4, 1);
        assert_eq!(snap.5, 1);
        assert!(snap.6 >= 50_000_000);
        assert!(snap.7 >= 15_000_000);
        assert!(snap.8 >= 60_000_000);
        assert!(snap.9 >= 70_000_000);

        reset_metrics();
        let snap2 = get_metrics_snapshot();
        assert_eq!(snap2.0, 0);
        assert_eq!(snap2.1, 0);
        assert_eq!(snap2.2, 0);
    }
}