use std::sync::atomic::{AtomicU8, Ordering};
use std::time::{Duration, Instant};
const UNKNOWN: u8 = 0;
const OFF: u8 = 1;
const ON: u8 = 2;
static STATE: AtomicU8 = AtomicU8::new(UNKNOWN);
pub(crate) fn enabled() -> bool {
match STATE.load(Ordering::Relaxed) {
OFF => false,
ON => true,
_ => init(),
}
}
#[cold]
fn init() -> bool {
let on = std::env::var_os("ANNOUNCE_TIMING").map_or(false, |v| !v.is_empty() && v != "0");
STATE.store(if on { ON } else { OFF }, Ordering::Relaxed);
on
}
struct Active {
phase: &'static str,
subject: String,
start: Instant,
last: Instant,
steps: Vec<(String, Duration)>,
}
pub(crate) struct Timer(Option<Box<Active>>);
impl Timer {
#[cfg(test)]
pub(crate) fn off() -> Timer {
Timer(None)
}
pub(crate) fn new(phase: &'static str) -> Timer {
if !enabled() {
return Timer(None);
}
let now = Instant::now();
Timer(Some(Box::new(Active {
phase,
subject: String::new(),
start: now,
last: now,
steps: Vec::new(),
})))
}
pub(crate) fn subject(&mut self, name: impl FnOnce() -> String) {
if let Some(a) = &mut self.0 {
a.subject = name();
}
}
pub(crate) fn step(&mut self, name: &'static str) {
if let Some(a) = &mut self.0 {
let now = Instant::now();
a.steps.push((name.to_string(), now - a.last));
a.last = Instant::now();
}
}
pub(crate) fn step_labelled(&mut self, name: &'static str, label: &'static str) {
if let Some(a) = &mut self.0 {
let now = Instant::now();
a.steps.push((format!("{}({})", name, label), now - a.last));
a.last = Instant::now();
}
}
}
fn line(a: &Active, total: Duration) -> String {
let ms = |d: Duration| d.as_secs_f64() * 1e3;
let mut line = format!("announce-timing: {} {}:", a.phase, a.subject);
for (name, d) in &a.steps {
line.push_str(&format!(" {}={:.3}", name, ms(*d)));
}
line.push_str(&format!(" | total={:.3} ms", ms(total)));
line
}
impl Drop for Timer {
fn drop(&mut self) {
if let Some(a) = self.0.take() {
eprintln!("{}", line(&a, a.start.elapsed()));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_timer_that_is_off_records_nothing() {
let mut t = Timer::off();
t.subject(|| panic!("the name is made only when timing"));
t.step("a");
t.step_labelled("b", "fast");
assert!(t.0.is_none());
}
#[test]
fn a_line_lists_the_steps_in_order_with_milliseconds() {
let now = Instant::now();
let a = Active {
phase: "capture",
subject: "trait Big".to_string(),
start: now,
last: now,
steps: vec![
("parse".to_string(), Duration::from_micros(2500)),
("analysis(fast)".to_string(), Duration::from_micros(40)),
],
};
assert_eq!(
line(&a, Duration::from_millis(3)),
"announce-timing: capture trait Big: parse=2.500 analysis(fast)=0.040 | total=3.000 ms"
);
}
#[test]
fn an_active_timer_records_labelled_steps() {
let now = Instant::now();
let mut t = Timer(Some(Box::new(Active {
phase: "derive",
subject: String::new(),
start: now,
last: now,
steps: Vec::new(),
})));
t.subject(|| "struct S".to_string());
t.step("parse");
t.step_labelled("analysis", "full");
let a = t.0.take().unwrap();
assert_eq!(a.subject, "struct S");
let names: Vec<&str> = a.steps.iter().map(|(n, _)| n.as_str()).collect();
assert_eq!(names, ["parse", "analysis(full)"]);
}
}