#[cfg(feature = "profiling-allocator")]
use crate::ALLOCATOR;
use {
provekit_common::utils::human,
std::{
cmp::max,
fmt::{self, Write as _},
time::Instant,
},
tracing::{
field::{Field, Visit},
span::{Attributes, Id},
Level, Subscriber,
},
tracing_subscriber::{self, layer::Context, registry::LookupSpan, Layer},
};
const DIM: &str = "\x1b[2m";
const UNDIM: &str = "\x1b[22m";
pub struct Data {
depth: usize,
time: Instant,
#[cfg(feature = "profiling-allocator")]
memory: usize,
#[cfg(feature = "profiling-allocator")]
allocations: usize,
#[cfg(feature = "profiling-allocator")]
peak_memory: usize,
children: bool,
kvs: Vec<(&'static str, String)>,
}
impl Data {
pub fn new(attrs: &Attributes<'_>, depth: usize) -> Self {
let mut span = Self {
depth,
time: Instant::now(),
#[cfg(feature = "profiling-allocator")]
memory: ALLOCATOR.current(),
#[cfg(feature = "profiling-allocator")]
allocations: ALLOCATOR.count(),
#[cfg(feature = "profiling-allocator")]
peak_memory: ALLOCATOR.current(),
children: false,
kvs: Vec::new(),
};
attrs.record(&mut span);
span
}
}
impl Visit for Data {
fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
self.kvs.push((field.name(), format!("{value:?}")));
}
}
pub struct FmtEvent<'a>(&'a mut String);
impl Visit for FmtEvent<'_> {
fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
match field.name() {
"message" => {
write!(self.0, " {value:?}").unwrap();
}
name => {
write!(self.0, " {name}={value:?}").unwrap();
}
}
}
}
pub struct SpanStats;
impl<S> Layer<S> for SpanStats
where
S: Subscriber + for<'span> LookupSpan<'span>,
{
fn on_new_span(&self, attrs: &Attributes, id: &Id, ctx: Context<S>) {
let span = ctx.span(id).expect("invalid span in on_new_span");
if let Some(parent) = span.parent() {
if let Some(data) = parent.extensions_mut().get_mut::<Data>() {
data.children = true;
#[cfg(feature = "profiling-allocator")]
{
data.peak_memory = max(data.peak_memory, ALLOCATOR.max());
}
}
}
#[cfg(feature = "profiling-allocator")]
ALLOCATOR.reset_max();
if span.extensions().get::<Data>().is_none() {
let depth = span.parent().map_or(0, |s| {
s.extensions()
.get::<Data>()
.expect("parent span has no data")
.depth
+ 1
});
let data = Data::new(attrs, depth);
span.extensions_mut().insert(data);
}
let ext = span.extensions();
let data = ext.get::<Data>().expect("span does not have data");
let mut buffer = String::with_capacity(100);
if data.depth >= 1 {
for _ in 0..(data.depth - 1) {
let _ = write!(&mut buffer, "│ ");
}
let _ = write!(&mut buffer, "├─");
}
let _ = write!(&mut buffer, "╮ ");
let _ = write!(
&mut buffer,
"{DIM}{}::{UNDIM}{}",
span.metadata().target(),
span.metadata().name()
);
for (key, val) in &data.kvs {
let _ = write!(&mut buffer, " {key}={val}");
}
#[cfg(feature = "profiling-allocator")]
let _ = write!(
&mut buffer,
" {DIM}start:{UNDIM} {}B{DIM} current, {UNDIM}{:#}{DIM} allocations{UNDIM}",
human(ALLOCATOR.current() as f64),
human(ALLOCATOR.count() as f64)
);
eprintln!("{buffer}");
}
fn on_event(&self, event: &tracing::Event<'_>, ctx: Context<'_, S>) {
let span = ctx.current_span().id().and_then(|id| ctx.span(id));
let mut buffer = String::with_capacity(100);
if let Some(span) = &span {
if let Some(parent) = span.parent() {
if let Some(data) = parent.extensions_mut().get_mut::<Data>() {
data.children = true;
}
}
if let Some(data) = span.extensions().get::<Data>() {
for _ in 0..=data.depth {
let _ = write!(&mut buffer, "│ ");
}
let elapsed = data.time.elapsed();
let _ = write!(
&mut buffer,
"{DIM}{:6}s {UNDIM}",
human(elapsed.as_secs_f64())
);
}
}
match *event.metadata().level() {
Level::TRACE => write!(&mut buffer, "TRACE"),
Level::DEBUG => write!(&mut buffer, "DEBUG"),
Level::INFO => write!(&mut buffer, "\x1b[1;32mINFO\x1b[0m"),
Level::WARN => write!(&mut buffer, "\x1b[1;38;5;208mWARN\x1b[0m"),
Level::ERROR => write!(&mut buffer, "\x1b[1;31mERROR\x1b[0m"),
}
.unwrap();
let mut visitor = FmtEvent(&mut buffer);
event.record(&mut visitor);
eprintln!("{buffer}");
}
fn on_close(&self, id: Id, ctx: Context<S>) {
let span = ctx.span(&id).expect("invalid span in on_close");
let ext = span.extensions();
let data = ext.get::<Data>().expect("span does not have data");
let duration = data.time.elapsed();
let mut buffer = String::with_capacity(100);
if data.depth >= 1 {
for _ in 0..(data.depth - 1) {
let _ = write!(&mut buffer, "│ ");
}
let _ = write!(&mut buffer, "├─");
}
let _ = write!(&mut buffer, "╯ ");
if data.children {
let _ = write!(&mut buffer, "{DIM}{}: {UNDIM}", span.metadata().name());
}
let _ = write!(
&mut buffer,
"{}s{DIM} duration",
human(duration.as_secs_f64()),
);
#[cfg(feature = "profiling-allocator")]
{
let peak_memory: usize = std::cmp::max(ALLOCATOR.max(), data.peak_memory);
let allocations = ALLOCATOR.count() - data.allocations;
let own = peak_memory - data.memory;
if let Some(parent) = span.parent() {
if let Some(data) = parent.extensions_mut().get_mut::<Data>() {
data.peak_memory = max(data.peak_memory, peak_memory);
}
}
let current_now = ALLOCATOR.current();
let _ = write!(
&mut buffer,
", {UNDIM}{}B{DIM} peak memory, {UNDIM}{}B{DIM} local, {UNDIM}{}B{DIM} current, \
{UNDIM}{:#}{DIM} allocations{UNDIM}",
human(peak_memory as f64),
human(own as f64),
human(current_now as f64),
human(allocations as f64)
);
}
eprintln!("{buffer}");
}
}