use std::time::Instant;
pub struct PhaseTimer {
enabled: bool,
start: Option<Instant>,
marks: Vec<(&'static str, Instant)>,
label: &'static str,
emit_interval: u64,
}
impl PhaseTimer {
pub fn from_env(env_var: &'static str, label: &'static str) -> Self {
let enabled = std::env::var(env_var).map(|v| v == "1").unwrap_or(false);
Self {
enabled,
start: None,
marks: Vec::with_capacity(if enabled { 8 } else { 0 }),
label,
emit_interval: 0,
}
}
#[cfg(test)]
pub fn enabled(label: &'static str) -> Self {
Self {
enabled: true,
start: None,
marks: Vec::with_capacity(8),
label,
emit_interval: 0,
}
}
#[inline]
pub fn start(&mut self) {
if self.enabled {
self.marks.clear();
self.start = Some(Instant::now());
}
}
#[inline]
pub fn mark(&mut self, phase: &'static str) {
if self.enabled {
self.marks.push((phase, Instant::now()));
}
}
pub fn emit(&self, iteration: u64, batch_size: usize) {
if !self.enabled {
return;
}
let should_emit = if self.emit_interval == 0 {
iteration <= 5 || iteration % 100 == 0
} else {
iteration % self.emit_interval == 0
};
if !should_emit {
return;
}
let Some(start) = self.start else { return };
let mut parts = Vec::with_capacity(self.marks.len());
let mut prev = start;
for (name, ts) in &self.marks {
let dur_us = ts.duration_since(prev).as_micros();
parts.push(format!("{name}={dur_us}µs"));
prev = *ts;
}
let total_us = prev.duration_since(start).as_micros();
eprintln!(
"[{}] iter={}, m={}: {} total={}µs",
self.label,
iteration,
batch_size,
parts.join(" "),
total_us,
);
}
#[inline]
#[must_use]
pub fn is_enabled(&self) -> bool {
self.enabled
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_disabled_is_noop() {
let mut t = PhaseTimer::from_env("NONEXISTENT_ENV_VAR_12345", "test");
assert!(!t.is_enabled());
t.start();
t.mark("a");
t.mark("b");
t.emit(1, 4); }
#[test]
fn test_enabled_captures_phases() {
let mut t = PhaseTimer::enabled("test");
t.start();
std::thread::sleep(std::time::Duration::from_micros(100));
t.mark("phase_a");
std::thread::sleep(std::time::Duration::from_micros(100));
t.mark("phase_b");
assert_eq!(t.marks.len(), 2);
assert_eq!(t.marks[0].0, "phase_a");
assert_eq!(t.marks[1].0, "phase_b");
}
}