use crate::telemetry::MAX_RECORDED_DURATIONS;
#[derive(Debug)]
pub struct Report {
pub(crate) functions: Vec<FunctionReport>,
}
impl Report {
pub fn print_stdout(&self) {
let col = self.functions.iter().map(|f| f.name.len()).max().unwrap_or(4).max(4);
println!("\x1b[1m{:<col$} {:>10} {:>10} {:>10} {:>10} {:>10} {:>12}\x1b[0m", "name", "avg", "p50", "p95", "p99", "p99.9", "total calls");
println!("{}", "-".repeat(col + 74));
for f in &self.functions {
let avg = if f.recent_durations_ns.is_empty() {
0
} else {
f.recent_durations_ns.iter().sum::<u64>() / f.recent_durations_ns.len() as u64
};
let p50 = percentile(&f.recent_durations_ns, 50, 100);
let p95 = percentile(&f.recent_durations_ns, 95, 100);
let p99 = percentile(&f.recent_durations_ns, 99, 100);
let p999 = percentile(&f.recent_durations_ns, 999, 1000);
let unit = Unit::pick(&[avg, p50, p95, p99, p999]);
let name = format!("\x1b[1m{:<col$}\x1b[0m", f.name);
println!("{} {} {} {} {} {} {:>12}", name, unit.fmt(avg), unit.fmt(p50), unit.fmt(p95), unit.fmt(p99), unit.fmt(p999), f.lifetime_calls);
}
println!("* Timings reflect last {MAX_RECORDED_DURATIONS} calls only.");
}
#[must_use]
pub fn functions(&self) -> &[FunctionReport] {
&self.functions
}
}
#[derive(Debug)]
pub struct FunctionReport {
pub(crate) name: &'static str,
pub(crate) recent_durations_ns: Vec<u64>,
pub(crate) lifetime_calls: u64,
}
impl FunctionReport {
#[must_use]
pub fn name(&self) -> &'static str {
self.name
}
#[must_use]
pub fn recent_durations_ns(&self) -> &[u64] {
&self.recent_durations_ns
}
#[must_use]
pub fn lifetime_calls(&self) -> u64 {
self.lifetime_calls
}
}
const RESET: &str = "\x1b[0m";
#[derive(Clone, Copy)]
enum Unit {
Ns,
Us,
Ms,
S,
}
impl Unit {
fn pick(values: &[u64]) -> Self {
let max = values.iter().copied().max().unwrap_or(0);
if max <= 999 {
Self::Ns
} else if max <= 999_000 {
Self::Us
} else if max <= 999_000_000 {
Self::Ms
} else {
Self::S
}
}
fn factor(self) -> f64 {
match self {
Self::Ns => 1.0,
Self::Us => 1e3,
Self::Ms => 1e6,
Self::S => 1e9,
}
}
fn suffix(self) -> &'static str {
match self {
Self::Ns => "ns",
Self::Us => "ยตs",
Self::Ms => "ms",
Self::S => " s",
}
}
fn color(self) -> &'static str {
match self {
Self::Ns => "\x1b[32m", Self::Us => "\x1b[93m", Self::Ms => "\x1b[33m", Self::S => "\x1b[31m", }
}
fn fmt(self, ns: u64) -> String {
#[allow(clippy::cast_precision_loss)]
let v = ns as f64 / self.factor();
format!("{v:7.2} {}{}{}", self.color(), self.suffix(), RESET)
}
}
fn percentile(data: &[u64], num: usize, denom: usize) -> u64 {
if data.is_empty() {
return 0;
}
let mut sorted = data.to_vec();
sorted.sort_unstable();
sorted[((num * sorted.len()).saturating_sub(1)) / denom]
}