use tracing::Subscriber;
use tracing_subscriber::Layer;
use tracing_subscriber::layer::Context;
use tracing_subscriber::registry::LookupSpan;
pub type AllocSnapshotFn = fn() -> (u64, u64, u64, u64);
struct AllocFrame {
alloc_count: u64,
alloc_bytes: u64,
dealloc_count: u64,
dealloc_bytes: u64,
}
struct AllocDelta {
alloc_count: u64,
alloc_bytes: u64,
dealloc_count: u64,
dealloc_bytes: u64,
}
pub struct AllocLayer {
snapshot_fn: AllocSnapshotFn,
}
impl AllocLayer {
#[must_use]
pub fn new(snapshot_fn: AllocSnapshotFn) -> Self {
Self { snapshot_fn }
}
}
impl<S> Layer<S> for AllocLayer
where
S: Subscriber + for<'a> LookupSpan<'a>,
{
fn on_enter(&self, id: &tracing::span::Id, ctx: Context<'_, S>) {
let (ac, ab, dc, db) = (self.snapshot_fn)();
if let Some(span) = ctx.span(id) {
span.extensions_mut().replace(AllocFrame {
alloc_count: ac,
alloc_bytes: ab,
dealloc_count: dc,
dealloc_bytes: db,
});
}
}
fn on_exit(&self, id: &tracing::span::Id, ctx: Context<'_, S>) {
let (now_alloc_count, now_alloc_bytes, now_dealloc_count, now_dealloc_bytes) =
(self.snapshot_fn)();
let Some(span) = ctx.span(id) else { return };
let frame = {
let mut exts = span.extensions_mut();
exts.remove::<AllocFrame>()
};
let Some(frame) = frame else { return };
let cycle_alloc_count = now_alloc_count.saturating_sub(frame.alloc_count);
let cycle_alloc_bytes = now_alloc_bytes.saturating_sub(frame.alloc_bytes);
let cycle_dealloc_count = now_dealloc_count.saturating_sub(frame.dealloc_count);
let cycle_dealloc_bytes = now_dealloc_bytes.saturating_sub(frame.dealloc_bytes);
let mut exts = span.extensions_mut();
if let Some(acc) = exts.get_mut::<AllocDelta>() {
acc.alloc_count = acc.alloc_count.saturating_add(cycle_alloc_count);
acc.alloc_bytes = acc.alloc_bytes.saturating_add(cycle_alloc_bytes);
acc.dealloc_count = acc.dealloc_count.saturating_add(cycle_dealloc_count);
acc.dealloc_bytes = acc.dealloc_bytes.saturating_add(cycle_dealloc_bytes);
} else {
exts.insert(AllocDelta {
alloc_count: cycle_alloc_count,
alloc_bytes: cycle_alloc_bytes,
dealloc_count: cycle_dealloc_count,
dealloc_bytes: cycle_dealloc_bytes,
});
}
}
fn on_close(&self, id: tracing::span::Id, ctx: Context<'_, S>) {
let Some(span) = ctx.span(&id) else { return };
let (span_name, alloc_count, alloc_bytes, dealloc_count, dealloc_bytes, net) = {
let exts = span.extensions();
let Some(delta) = exts.get::<AllocDelta>() else {
return;
};
let net = delta
.alloc_bytes
.cast_signed()
.saturating_sub(delta.dealloc_bytes.cast_signed());
(
span.name(),
delta.alloc_count,
delta.alloc_bytes,
delta.dealloc_count,
delta.dealloc_bytes,
net,
)
};
tracing::trace!(
target: "alloc.span",
span_name,
alloc.count = alloc_count,
alloc.bytes = alloc_bytes,
dealloc.count = dealloc_count,
dealloc.bytes = dealloc_bytes,
alloc.net_bytes = net,
);
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::cell::Cell;
use tracing_subscriber::{Registry, layer::SubscriberExt};
thread_local! {
static SNAP: Cell<(u64, u64, u64, u64)> = const { Cell::new((0, 0, 0, 0)) };
}
fn set_snap(ac: u64, ab: u64, dc: u64, db: u64) {
SNAP.with(|s| s.set((ac, ab, dc, db)));
}
fn snap() -> (u64, u64, u64, u64) {
SNAP.with(Cell::get)
}
fn run_with_subscriber<F: FnOnce()>(f: F) {
let subscriber = Registry::default().with(AllocLayer::new(snap));
tracing::subscriber::with_default(subscriber, f);
}
#[test]
fn alloc_layer_new_accepts_any_snapshot_fn() {
let _layer = AllocLayer::new(snap);
}
#[test]
fn on_enter_exit_does_not_panic() {
set_snap(10, 1024, 5, 512);
run_with_subscriber(|| {
let span = tracing::info_span!("enter_exit_span");
let guard = span.enter();
set_snap(15, 2048, 8, 768);
drop(guard);
});
}
#[test]
fn zero_delta_span_does_not_panic() {
set_snap(100, 9000, 50, 8000);
run_with_subscriber(|| {
let span = tracing::info_span!("zero_alloc_span");
let guard = span.enter();
drop(guard);
});
}
#[test]
fn multiple_cycles_does_not_panic() {
run_with_subscriber(|| {
let span = tracing::info_span!("multi_cycle");
set_snap(10, 100, 5, 50);
let g = span.enter();
set_snap(12, 200, 6, 100);
drop(g);
set_snap(20, 300, 7, 200);
let g2 = span.enter();
set_snap(25, 600, 9, 400);
drop(g2);
});
}
}