tracing-calltree 0.1.2

Always-on hierarchical profiling for Rust tracing spans with rolling latency statistics.
Documentation
use std::time::Duration;

#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct TimingStats {
    pub samples: usize,
    pub min: Duration,
    pub max: Duration,
    pub mean: Duration,
    pub p95: Duration,
}

impl TimingStats {
    pub(crate) fn from_nanos(values: impl IntoIterator<Item = u64>) -> Self {
        let mut sorted: Vec<u64> = values.into_iter().collect();

        if sorted.is_empty() {
            return Self {
                samples: 0,
                min: Duration::ZERO,
                max: Duration::ZERO,
                mean: Duration::ZERO,
                p95: Duration::ZERO,
            };
        }

        sorted.sort_unstable();

        let samples = sorted.len();
        let min = sorted[0];
        let max = sorted[samples - 1];
        let sum = sorted
            .iter()
            .fold(0_u128, |acc, value| acc + u128::from(*value));
        let mean = (sum / samples as u128).min(u64::MAX as u128) as u64;
        let rank = ((samples * 95).div_ceil(100)).saturating_sub(1);
        let p95 = sorted[rank];

        Self {
            samples,
            min: Duration::from_nanos(min),
            max: Duration::from_nanos(max),
            mean: Duration::from_nanos(mean),
            p95: Duration::from_nanos(p95),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::TimingStats;
    use std::time::Duration;

    #[test]
    fn calculates_exact_nearest_rank_statistics() {
        let stats = TimingStats::from_nanos(1..=100);

        assert_eq!(stats.samples, 100);
        assert_eq!(stats.min, Duration::from_nanos(1));
        assert_eq!(stats.max, Duration::from_nanos(100));
        assert_eq!(stats.mean, Duration::from_nanos(50));
        assert_eq!(stats.p95, Duration::from_nanos(95));
    }

    #[test]
    fn empty_input_returns_zeroed_stats() {
        let stats = TimingStats::from_nanos(std::iter::empty());

        assert_eq!(stats.samples, 0);
        assert_eq!(stats.min, Duration::ZERO);
        assert_eq!(stats.max, Duration::ZERO);
        assert_eq!(stats.mean, Duration::ZERO);
        assert_eq!(stats.p95, Duration::ZERO);
    }
}