use std::sync::{Mutex, OnceLock, PoisonError};
use std::time::Instant;
#[derive(Default)]
struct Stats {
expansions: u64,
nanos: u128,
reads: u64,
}
fn stats() -> Option<&'static Mutex<Stats>> {
static STATS: OnceLock<Option<Mutex<Stats>>> = OnceLock::new();
STATS
.get_or_init(|| std::env::var_os("MF2_MACRO_STATS").map(|_| Mutex::new(Stats::default())))
.as_ref()
}
fn with(f: impl FnOnce(&mut Stats)) {
if let Some(stats) = stats() {
let mut stats = stats.lock().unwrap_or_else(PoisonError::into_inner);
f(&mut stats);
}
}
pub(crate) fn start() -> Option<Instant> {
stats().map(|_| Instant::now())
}
pub(crate) fn expansion(started: Option<Instant>) {
let Some(started) = started else {
return;
};
let nanos = started.elapsed().as_nanos();
with(|s| {
s.expansions += 1;
s.nanos += nanos;
});
flush();
}
pub(crate) fn read() {
with(|s| s.reads += 1);
}
fn flush() {
let Some(path) = std::env::var_os("MF2_MACRO_STATS") else {
return;
};
with(|s| {
let line = format!(
"pid {} expansions {} nanos {} reads {}\n",
std::process::id(),
s.expansions,
s.nanos,
s.reads
);
let mut path = std::path::PathBuf::from(&path);
let name = format!(
"{}.{}",
path.file_name()
.map(|n| n.to_string_lossy().into_owned())
.unwrap_or_default(),
std::process::id()
);
path.set_file_name(name);
let _ = std::fs::write(path, line);
});
}