use std::sync::{Mutex, OnceLock};
use std::time::Instant;
use fallow_types::pipeline_spans::ProcessTimings;
static START: OnceLock<Instant> = OnceLock::new();
static SPANS: Mutex<ProcessTimings> = Mutex::new(ProcessTimings {
wall_ms: 0.0,
startup_ms: 0.0,
thread_pool_ms: 0.0,
config_ms: 0.0,
git_ms: 0.0,
analysis_ms: 0.0,
post_analysis_ms: 0.0,
output_ms: 0.0,
});
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProcessSpan {
Startup,
ThreadPool,
Config,
Git,
Analysis,
PostAnalysis,
Output,
}
pub fn mark_process_start() {
START.get_or_init(Instant::now);
}
fn elapsed_ms(since: Instant) -> f64 {
since.elapsed().as_secs_f64() * 1000.0
}
pub fn record(span: ProcessSpan, ms: f64) {
let Ok(mut spans) = SPANS.lock() else {
return;
};
let slot = match span {
ProcessSpan::Startup => &mut spans.startup_ms,
ProcessSpan::ThreadPool => &mut spans.thread_pool_ms,
ProcessSpan::Config => &mut spans.config_ms,
ProcessSpan::Git => &mut spans.git_ms,
ProcessSpan::Analysis => &mut spans.analysis_ms,
ProcessSpan::PostAnalysis => &mut spans.post_analysis_ms,
ProcessSpan::Output => &mut spans.output_ms,
};
*slot += ms;
}
pub fn time<T>(span: ProcessSpan, work: impl FnOnce() -> T) -> T {
let start = Instant::now();
let output = work();
record(span, elapsed_ms(start));
output
}
pub struct SpanTimer {
span: ProcessSpan,
start: Instant,
}
impl Drop for SpanTimer {
fn drop(&mut self) {
record(self.span, elapsed_ms(self.start));
}
}
#[must_use]
pub fn start(span: ProcessSpan) -> SpanTimer {
SpanTimer {
span,
start: Instant::now(),
}
}
pub fn record_startup() {
if let Some(start) = START.get() {
record(ProcessSpan::Startup, elapsed_ms(*start));
}
}
pub fn snapshot() -> Option<ProcessTimings> {
let start = START.get()?;
let mut timings = *SPANS.lock().ok()?;
timings.wall_ms = elapsed_ms(*start);
Some(timings)
}