use std::time::Duration;
use sealed::sealed;
use crate::config::{Backends, config};
use crate::profiling::instant::{self, SpanRecord};
pub use divan;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SpanAgg {
pub name: &'static str,
pub total: Duration,
pub calls: u64,
pub avg: Duration,
}
#[derive(Debug, Clone, Default)]
pub struct ProfiledRun {
pub spans: Vec<SpanAgg>,
pub errors_delta: Vec<(&'static str, u64)>,
}
impl ProfiledRun {
pub fn print(&self) {
println!("{self}");
}
}
impl std::fmt::Display for ProfiledRun {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use humantime::format_duration;
writeln!(f, "profiled breakdown:")?;
for s in &self.spans {
writeln!(
f,
" {:>20}: {} ({} calls, total {})",
s.name,
format_duration(s.avg),
s.calls,
format_duration(s.total),
)?;
}
for (ty, n) in &self.errors_delta {
writeln!(f, "errors: {ty} +{n}")?;
}
Ok(())
}
}
fn aggregate(spans: &[SpanRecord]) -> Vec<SpanAgg> {
let groups = instant::group_by_name(spans);
let mut out: Vec<SpanAgg> = groups
.into_iter()
.map(|(name, durations)| {
let total_ns: u128 = durations.iter().map(Duration::as_nanos).sum();
let calls = durations.len() as u64;
let total = Duration::from_nanos(u64::try_from(total_ns).unwrap_or(u64::MAX));
let divisor = u32::try_from(calls).unwrap_or(u32::MAX);
SpanAgg {
name,
total,
calls,
avg: total / divisor,
}
})
.collect();
out.sort_by_key(|s| std::cmp::Reverse(s.total));
out
}
fn error_delta(
before: &[(&'static str, u64)],
after: Vec<(&'static str, u64)>,
) -> Vec<(&'static str, u64)> {
let mut out: Vec<(&'static str, u64)> = after
.into_iter()
.filter_map(|(ty, n)| {
let prev = before.iter().find(|(t, _)| *t == ty).map_or(0, |(_, c)| *c);
(n > prev).then_some((ty, n - prev))
})
.collect();
out.sort_by(|a, b| b.1.cmp(&a.1).then_with(|| a.0.cmp(b.0)));
out
}
struct Restore(Backends);
impl Drop for Restore {
fn drop(&mut self) {
config().set_backends(self.0);
}
}
fn run_profiled(run: impl FnOnce()) -> ProfiledRun {
let cfg = config();
let saved = cfg.backends();
cfg.set_backends(saved | Backends::INSTANT);
let _restore = Restore(saved);
let errors_before = crate::error_counts();
instant::clear();
run();
let spans = instant::drain();
let errors_after = crate::error_counts();
ProfiledRun {
spans: aggregate(&spans),
errors_delta: error_delta(&errors_before, errors_after),
}
}
#[must_use]
pub fn measure_breakdown(iterations: usize, mut f: impl FnMut()) -> ProfiledRun {
run_profiled(|| {
for _ in 0..iterations {
f();
}
})
}
#[sealed]
pub trait BenchExt {
fn bench_profiled<T>(self, f: impl FnMut() -> T) -> ProfiledRun;
}
impl __seal_bench_ext::Sealed for divan::Bencher<'_, '_> {}
impl BenchExt for divan::Bencher<'_, '_> {
fn bench_profiled<T>(self, f: impl FnMut() -> T) -> ProfiledRun {
run_profiled(|| {
self.bench_local(f);
})
}
}