use std::collections::BTreeMap;
use std::sync::{Mutex, OnceLock};
static LEDGER: OnceLock<Mutex<BTreeMap<String, u64>>> = OnceLock::new();
fn ledger() -> &'static Mutex<BTreeMap<String, u64>> {
LEDGER.get_or_init(|| Mutex::new(BTreeMap::new()))
}
pub fn ran(key: &str) {
if let Ok(mut g) = ledger().lock() {
*g.entry(key.to_string()).or_insert(0) += 1;
}
}
pub fn snapshot() -> serde_json::Value {
let g = match ledger().lock() {
Ok(g) => g,
Err(_) => return serde_json::json!({}),
};
let map: serde_json::Map<String, serde_json::Value> =
g.iter().map(|(k, v)| (k.clone(), serde_json::json!(v))).collect();
serde_json::Value::Object(map)
}
pub fn distinct() -> usize {
ledger().lock().map(|g| g.len()).unwrap_or(0)
}
pub fn count(key: &str) -> u64 {
ledger().lock().map(|g| g.get(key).copied().unwrap_or(0)).unwrap_or(0)
}
pub fn reset() {
if let Ok(mut g) = ledger().lock() {
g.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
static LEDGER_GUARD: std::sync::Mutex<()> = std::sync::Mutex::new(());
#[test]
fn records_and_snapshots_real_counts() {
let _guard = LEDGER_GUARD.lock().unwrap_or_else(|e| e.into_inner());
reset();
ran("RuntraceTest::ui");
ran("RuntraceTest::ui");
ran("RuntraceTest::ctl:11M");
let s = snapshot();
assert_eq!(s["RuntraceTest::ui"].as_u64(), Some(2), "ui ran twice");
assert_eq!(s["RuntraceTest::ctl:11M"].as_u64(), Some(1), "the 11M button handler ran once");
assert_eq!(count("RuntraceTest::ctl:11M"), 1);
assert_eq!(count("never"), 0, "an unrun key reads 0");
assert!(s.get("RuntraceTest::ui").is_some() && s.get("RuntraceTest::ctl:11M").is_some(), "both keys recorded: {s}");
assert!(s.is_object());
}
#[test]
fn reset_clears_the_ledger() {
let _guard = LEDGER_GUARD.lock().unwrap_or_else(|e| e.into_inner());
ran("RuntraceReset::a::b");
assert!(count("RuntraceReset::a::b") >= 1, "the key was recorded before reset");
reset();
assert_eq!(count("RuntraceReset::a::b"), 0, "reset clears the recorded key");
}
}