use super::clock;
use std::cell::Cell;
use std::marker::PhantomData;
use std::time::Duration;
thread_local! {
static FINISHED: Cell<Vec<SpanRecord>> = Cell::new(Vec::new());
static STACK: Cell<Vec<SpanRecord>> = Cell::new(Vec::new());
static FRAME_BOUNDARIES: Cell<Vec<usize>> = Cell::new(Vec::new());
static DRAIN_BASE: Cell<usize> = const { Cell::new(0) };
}
fn with_tl<T>(cell: &'static std::thread::LocalKey<Cell<Vec<T>>>, f: impl FnOnce(&mut Vec<T>)) {
let _ = cell.try_with(|c| {
let mut v = c.take();
f(&mut v);
c.set(v);
});
}
#[derive(Debug, Clone)]
pub struct SpanRecord {
pub name: &'static str,
pub tag: Option<&'static str>,
pub start_ns: u64,
pub end_ns: u64,
pub depth: u32,
}
impl SpanRecord {
#[must_use]
pub fn duration(&self) -> Duration {
Duration::from_nanos(self.end_ns.saturating_sub(self.start_ns))
}
}
#[must_use]
pub fn enter(name: &'static str, tag: Option<&'static str>) -> InstantGuard {
let start_ns = clock::now_ns();
let mut depth = 0;
with_tl(&STACK, |s| {
#[allow(
clippy::cast_possible_truncation,
reason = "span nesting depth is bounded well below u32::MAX in practice"
)]
let d = s.len() as u32;
depth = d;
s.push(SpanRecord {
name,
tag,
start_ns,
end_ns: start_ns,
depth: d,
});
});
InstantGuard {
depth: Some(depth),
_not_send: PhantomData,
}
}
pub struct InstantGuard {
depth: Option<u32>,
_not_send: PhantomData<*const ()>,
}
#[must_use]
pub fn dummy() -> InstantGuard {
InstantGuard {
depth: None,
_not_send: PhantomData,
}
}
impl Drop for InstantGuard {
fn drop(&mut self) {
let Some(depth) = self.depth else {
return; };
let end_ns = clock::now_ns();
let mut done: Vec<SpanRecord> = Vec::new();
with_tl(&STACK, |s| {
while s.len() > depth as usize {
let Some(mut span) = s.pop() else { break };
span.end_ns = end_ns;
done.push(span);
}
});
with_tl(&FINISHED, |f| f.append(&mut done));
}
}
const RETAINED_FRAMES: usize = 60;
pub fn finish_frame() {
let base = DRAIN_BASE.with(Cell::get);
let mut count = 0;
with_tl(&FINISHED, |v| count = v.len());
let abs = base + count;
with_tl(&FRAME_BOUNDARIES, |boundaries| {
boundaries.push(abs);
if boundaries.len() > RETAINED_FRAMES {
let keep = boundaries.len() - RETAINED_FRAMES;
let cutoff = boundaries[keep];
let n = cutoff.saturating_sub(base);
if n > 0 {
with_tl(&FINISHED, |v| {
v.drain(0..n.min(v.len()));
});
}
DRAIN_BASE.with(|b| b.set(b.get().max(cutoff)));
boundaries.drain(0..keep);
}
});
}
pub fn drain() -> Vec<SpanRecord> {
let v = FINISHED.with(Cell::take);
DRAIN_BASE.with(|b| b.set(b.get() + v.len()));
v
}
#[must_use]
pub fn peek_recent(n: usize) -> Vec<SpanRecord> {
let mut out = Vec::new();
with_tl(&FINISHED, |v| {
let start = v.len().saturating_sub(n);
out.extend_from_slice(&v[start..]);
});
out
}
pub fn drain_frames() -> Vec<usize> {
let base = DRAIN_BASE.with(Cell::get);
let boundaries = FRAME_BOUNDARIES.with(Cell::take);
boundaries
.into_iter()
.filter(|b| *b >= base)
.map(|b| b - base)
.collect()
}
pub(crate) fn group_by_name(
spans: &[SpanRecord],
) -> std::collections::BTreeMap<&'static str, Vec<Duration>> {
let mut groups: std::collections::BTreeMap<&'static str, Vec<Duration>> =
std::collections::BTreeMap::new();
for s in spans {
groups.entry(s.name).or_default().push(s.duration());
}
groups
}
pub fn clear() {
FINISHED.with(|c| {
c.take();
});
FRAME_BOUNDARIES.with(|c| {
c.take();
});
DRAIN_BASE.with(|b| b.set(0));
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn spans_recorded_on_guard_drop() {
clear();
{
let _g = enter("outer", None);
{
let _g2 = enter("inner", Some("tag"));
} }
let spans = drain();
assert_eq!(spans.len(), 2);
assert_eq!(spans[0].name, "inner");
assert_eq!(spans[0].tag, Some("tag"));
assert_eq!(spans[0].depth, 1);
assert_eq!(spans[1].name, "outer");
assert_eq!(spans[1].depth, 0);
for s in &spans {
assert!(s.end_ns >= s.start_ns, "end must not precede start");
}
}
#[test]
fn drain_is_destructive_roundtrip() {
clear();
drop(enter("a", None));
assert_eq!(drain().len(), 1);
assert!(drain().is_empty());
}
#[test]
fn finish_frame_marks_boundaries() {
clear();
drop(enter("a", None));
drop(enter("b", None));
finish_frame();
drop(enter("c", None));
finish_frame();
let boundaries = drain_frames();
assert_eq!(boundaries, vec![2, 3]);
let _ = drain();
}
#[test]
fn dummy_on_empty_stack_records_nothing() {
clear();
drop(dummy());
assert!(drain().is_empty());
}
#[test]
fn dummy_guard_never_pops() {
clear();
let real = enter("real", None);
drop(dummy()); drop(real);
let spans = drain();
assert_eq!(spans.len(), 1);
assert_eq!(spans[0].name, "real");
}
#[test]
fn forgotten_child_is_finalized_not_corrupting() {
clear();
let a = enter("a", None);
let b = enter("b", None);
std::mem::forget(b); drop(a); let spans = drain();
let names: Vec<_> = spans.iter().map(|s| s.name).collect();
assert_eq!(names, vec!["b", "a"]);
for s in &spans {
assert!(s.end_ns >= s.start_ns, "end must not precede start");
}
drop(enter("c", None));
let spans = drain();
assert_eq!(spans.len(), 1);
assert_eq!(spans[0].name, "c");
assert_eq!(spans[0].depth, 0);
}
#[test]
fn boundaries_eviction_caps_at_60() {
clear();
for _ in 0..70 {
drop(enter("x", None));
finish_frame();
}
assert_eq!(drain_frames().len(), RETAINED_FRAMES);
assert_eq!(drain().len(), 59);
}
#[test]
fn duration_reflects_elapsed() {
clear();
{
let _g = enter("slow", None);
std::thread::sleep(Duration::from_millis(2));
}
let spans = drain();
assert_eq!(spans.len(), 1);
let d = spans[0].duration();
assert!(
d >= Duration::from_millis(1),
"2ms span should measure ≥1ms, got {d:?}"
);
}
}