use std::collections::HashMap;
use std::sync::{Mutex, OnceLock};
use std::time::Instant;
pub fn enabled() -> bool {
static ENABLED: OnceLock<bool> = OnceLock::new();
*ENABLED.get_or_init(|| {
std::env::var("RPI_TIMING")
.map(|v| v == "1")
.unwrap_or(false)
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TimingNamespace {
Main,
Extensions,
}
impl TimingNamespace {
fn label(self) -> &'static str {
match self {
Self::Main => "main",
Self::Extensions => "extensions",
}
}
}
struct Group {
timings: Vec<(String, i64)>,
last: Instant,
}
fn groups() -> &'static Mutex<HashMap<&'static str, Group>> {
static GROUPS: OnceLock<Mutex<HashMap<&'static str, Group>>> = OnceLock::new();
GROUPS.get_or_init(|| Mutex::new(HashMap::new()))
}
pub fn reset(namespace: TimingNamespace) {
if !enabled() {
return;
}
if let Ok(mut g) = groups().lock() {
g.insert(
namespace.label(),
Group {
timings: Vec::new(),
last: Instant::now(),
},
);
}
}
pub fn time(label: impl Into<String>, namespace: TimingNamespace) {
if !enabled() {
return;
}
let now = Instant::now();
if let Ok(mut g) = groups().lock() {
let group = g.entry(namespace.label()).or_insert_with(|| Group {
timings: Vec::new(),
last: now,
});
let ms = now.duration_since(group.last).as_millis() as i64;
group.timings.push((label.into(), ms));
group.last = now;
}
}
pub fn print_timings() {
if !enabled() {
return;
}
let Ok(g) = groups().lock() else {
return;
};
for ns in ["main", "extensions"] {
let Some(group) = g.get(ns) else { continue };
print_group(&format!("Startup Timings: {ns}"), &group.timings);
}
}
fn print_group(title: &str, timings: &[(String, i64)]) {
let printable: Vec<_> = timings.iter().filter(|(_, ms)| *ms >= 0).collect();
if printable.is_empty() {
return;
}
eprintln!("\n--- {title} ---");
for (label, ms) in &printable {
eprintln!(" {label}: {ms}ms");
}
let total: i64 = printable.iter().map(|(_, ms)| *ms).sum();
eprintln!(" TOTAL: {total}ms");
eprintln!("{}\n", "-".repeat(title.len() + 8));
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn disabled_by_default() {
if std::env::var_os("RPI_TIMING").is_none() {
assert!(!enabled());
}
}
#[test]
fn records_and_resets() {
if let Ok(mut g) = groups().lock() {
g.insert(
"test",
Group {
timings: Vec::new(),
last: Instant::now(),
},
);
}
if let Ok(mut g) = groups().lock() {
let group = g.get_mut("test").unwrap();
group.timings.push(("boot".to_string(), 12));
}
if let Ok(g) = groups().lock() {
assert_eq!(g.get("test").unwrap().timings.len(), 1);
}
}
}