use std::sync::Mutex;
use std::time::{Duration, Instant};
#[derive(Clone, Debug)]
pub struct StartupStep {
pub kind: &'static str,
pub name: String,
pub level: usize,
pub offset: Duration,
pub duration: Duration,
}
struct Recorder {
origin: Option<Instant>,
steps: Vec<StartupStep>,
depth: usize,
}
static RECORDER: Mutex<Recorder> = Mutex::new(Recorder { origin: None, steps: Vec::new(), depth: 0 });
const MAX_STEPS: usize = 1000;
pub fn mark_process_start() {
let mut r = RECORDER.lock().unwrap_or_else(|e| e.into_inner());
r.origin.get_or_insert_with(Instant::now);
}
pub fn elapsed() -> Duration {
let r = RECORDER.lock().unwrap_or_else(|e| e.into_inner());
r.origin.map(|o| o.elapsed()).unwrap_or_default()
}
pub fn step<T>(kind: &'static str, name: impl Into<String>, f: impl FnOnce() -> T) -> T {
let start = Instant::now();
let level = {
let mut r = RECORDER.lock().unwrap_or_else(|e| e.into_inner());
r.origin.get_or_insert(start);
r.depth += 1;
r.depth - 1
};
let out = f();
let duration = start.elapsed();
let mut r = RECORDER.lock().unwrap_or_else(|e| e.into_inner());
r.depth = r.depth.saturating_sub(1);
if r.steps.len() < MAX_STEPS {
let offset = start.saturating_duration_since(r.origin.unwrap_or(start));
r.steps.push(StartupStep { kind, name: name.into(), level, offset, duration });
}
out
}
pub fn steps() -> Vec<StartupStep> {
let r = RECORDER.lock().unwrap_or_else(|e| e.into_inner());
let mut v = r.steps.clone();
v.sort_by_key(|s| s.offset);
v
}
pub fn format_duration(d: Duration) -> String {
let us = d.as_secs_f64() * 1e6;
if us < 1000.0 {
format!("{:.0} µs", us)
} else {
format!("{:.2} ms", us / 1000.0)
}
}
pub fn report_lines() -> Vec<String> {
steps()
.iter()
.map(|s| {
format!(
"{:>10} {:>10} {}{} {}",
format_duration(s.offset),
format_duration(s.duration),
" ".repeat(s.level),
s.kind,
s.name
)
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn records_nested_steps_with_levels() {
mark_process_start();
let v = step("test-outer", "a", || step("test-inner", "b", || 41) + 1);
assert_eq!(v, 42);
let s = steps();
let outer = s.iter().find(|s| s.kind == "test-outer").unwrap();
let inner = s.iter().find(|s| s.kind == "test-inner").unwrap();
assert_eq!(inner.level, outer.level + 1);
assert!(inner.offset >= outer.offset);
assert!(outer.duration >= inner.duration);
assert!(report_lines().iter().any(|l| l.contains("test-inner b")));
}
#[test]
fn formats_micro_and_milliseconds() {
assert_eq!(format_duration(Duration::from_micros(850)), "850 µs");
assert_eq!(format_duration(Duration::from_micros(12_340)), "12.34 ms");
}
}