qk-cache 0.3.0

Task input hashing and caching for the qk task runner
Documentation
//! Opt-in elapsed timings for cache operations, written to stderr.

use std::sync::OnceLock;
use std::time::Instant;

static ENABLED: OnceLock<bool> = OnceLock::new();

/// Enable timings for this process before its first cache operation.
/// `QK_PROFILE_CACHE=1` also enables them when no caller configures profiling.
pub fn enable() {
    let _ = ENABLED.set(true);
}

pub(crate) struct Span<'a> {
    task: &'a str,
    stage: &'static str,
    started: Option<Instant>,
}

pub(crate) fn span<'a>(task: &'a str, stage: &'static str) -> Span<'a> {
    let enabled = *ENABLED
        .get_or_init(|| std::env::var_os("QK_PROFILE_CACHE").is_some_and(|value| value == "1"));
    Span {
        task,
        stage,
        started: enabled.then(Instant::now),
    }
}

impl Drop for Span<'_> {
    fn drop(&mut self) {
        if let Some(started) = self.started {
            qk_executor::report::output(
                true,
                format!(
                    "qk profile: task={} stage={} ms={:.3}\n",
                    self.task,
                    self.stage,
                    started.elapsed().as_secs_f64() * 1000.0
                )
                .as_bytes(),
            );
        }
    }
}