use std::sync::OnceLock;
use std::time::Instant;
use crate::event::StageTimings;
const MCPR_STAGE_TIMING_ENV: &str = "MCPR_STAGE_TIMING";
fn timing_enabled() -> bool {
static ENABLED: OnceLock<bool> = OnceLock::new();
*ENABLED.get_or_init(|| {
matches!(
std::env::var(MCPR_STAGE_TIMING_ENV).as_deref(),
Ok("1") | Ok("true") | Ok("yes")
)
})
}
#[derive(Clone, Copy, Debug)]
pub enum Stage {
Buffer,
SseUnwrap,
JsonParse,
Schema,
WidgetOverlay,
MarkerScan,
Rewrite,
Reserialize,
UrlMap,
SideEffects,
}
pub struct StageTimer {
state: State,
}
enum State {
Disabled,
Enabled {
last: Instant,
timings: StageTimings,
},
}
impl Default for StageTimer {
fn default() -> Self {
Self::new()
}
}
impl StageTimer {
pub fn new() -> Self {
let state = if timing_enabled() {
State::Enabled {
last: Instant::now(),
timings: StageTimings::default(),
}
} else {
State::Disabled
};
Self { state }
}
pub fn mark(&mut self, stage: Stage) {
let State::Enabled { last, timings } = &mut self.state else {
return;
};
let us = last.elapsed().as_micros() as u32;
match stage {
Stage::Buffer => timings.buffer_us = Some(us),
Stage::SseUnwrap => timings.sse_unwrap_us = Some(us),
Stage::JsonParse => timings.json_parse_us = Some(us),
Stage::Schema => timings.schema_us = Some(us),
Stage::WidgetOverlay => timings.widget_overlay_us = Some(us),
Stage::MarkerScan => timings.marker_scan_us = Some(us),
Stage::Rewrite => timings.rewrite_us = Some(us),
Stage::Reserialize => timings.reserialize_us = Some(us),
Stage::UrlMap => timings.url_map_us = Some(us),
Stage::SideEffects => timings.side_effects_us = Some(us),
}
*last = Instant::now();
}
pub fn finish(self) -> Option<StageTimings> {
match self.state {
State::Enabled { timings, .. } => Some(timings),
State::Disabled => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn disabled_timer_is_noop() {
let mut t = StageTimer {
state: State::Disabled,
};
t.mark(Stage::Schema); assert!(t.finish().is_none());
}
#[test]
fn enabled_timer_records_marks() {
let mut t = StageTimer {
state: State::Enabled {
last: Instant::now(),
timings: StageTimings::default(),
},
};
std::thread::sleep(std::time::Duration::from_micros(50));
t.mark(Stage::Schema);
let out = t.finish().expect("enabled timer should yield Some");
assert!(out.schema_us.is_some(), "schema_us should be recorded");
assert!(
out.json_parse_us.is_none(),
"only marked stages should be populated"
);
}
}