Skip to main content

cbtop/latency_distribution/
mod.rs

1//! Latency Distribution Analysis Module (PMAT-026)
2//!
3//! Enhanced latency distribution analysis with tail latency detection,
4//! jitter calculation, and histogram statistics for identifying performance anomalies.
5//!
6//! # Components
7//!
8//! | Component | Formula | Use Case |
9//! |-----------|---------|----------|
10//! | Jitter (IPDV) | std_dev(\|latency[i] - latency[i-1]\|) | Connection stability |
11//! | Tail Ratio | P99/P50 | Tail latency severity |
12//! | Bimodality Coefficient | (skewness² + 1) / kurtosis | Distribution shape |
13//! | Histogram Entropy | -Σ(p × log(p)) | Distribution uniformity |
14
15mod classification;
16mod histogram;
17mod moments;
18
19pub use classification::{DistributionShape, TailSeverity};
20pub use histogram::{HistogramBucket, LatencyHistogram};
21
22use crate::statistics::percentile;
23use moments::{calculate_jitter, calculate_moments};
24
25/// Latency distribution analysis result
26#[derive(Debug, Clone)]
27pub struct LatencyDistribution {
28    /// Median latency (50th percentile)
29    pub p50: f64,
30    /// 90th percentile latency
31    pub p90: f64,
32    /// 99th percentile latency
33    pub p99: f64,
34    /// 99.9th percentile latency
35    pub p999: f64,
36    /// Jitter (inter-packet delay variation)
37    pub jitter: f64,
38    /// Tail ratio (P99/P50) - higher means worse tail latency
39    pub tail_ratio: f64,
40    /// Bimodality coefficient - >0.555 suggests bimodal distribution
41    pub bimodality_coefficient: f64,
42    /// Latency histogram
43    pub histogram: LatencyHistogram,
44    /// Number of samples
45    pub sample_count: usize,
46    /// Minimum latency
47    pub min: f64,
48    /// Maximum latency
49    pub max: f64,
50    /// Mean latency
51    pub mean: f64,
52    /// Standard deviation
53    pub std_dev: f64,
54    /// Skewness (asymmetry)
55    pub skewness: f64,
56    /// Kurtosis (tail heaviness)
57    pub kurtosis: f64,
58    /// Outlier ratio (% beyond 3σ)
59    pub outlier_ratio: f64,
60}
61
62impl LatencyDistribution {
63    /// Analyze latency samples to produce distribution statistics
64    pub fn analyze(samples: &[f64]) -> Option<Self> {
65        if samples.is_empty() {
66            return None;
67        }
68
69        let n = samples.len();
70
71        // Basic statistics
72        let mean = samples.iter().sum::<f64>() / n as f64;
73        let min = samples.iter().cloned().fold(f64::INFINITY, f64::min);
74        let max = samples.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
75
76        // Standard deviation
77        let variance = if n > 1 {
78            samples.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / (n - 1) as f64
79        } else {
80            0.0
81        };
82        let std_dev = variance.sqrt();
83
84        // Percentiles
85        let p50 = percentile(samples, 0.50);
86        let p90 = percentile(samples, 0.90);
87        let p99 = percentile(samples, 0.99);
88        let p999 = percentile(samples, 0.999);
89
90        // Jitter (inter-sample delay variation)
91        let jitter = calculate_jitter(samples);
92
93        // Tail ratio
94        let tail_ratio = if p50 > 0.0 { p99 / p50 } else { 1.0 };
95
96        // Higher moments (skewness, kurtosis)
97        let (skewness, kurtosis) = calculate_moments(samples, mean, std_dev);
98
99        // Bimodality coefficient: (skewness² + 1) / kurtosis
100        let bimodality_coefficient = if kurtosis > 0.0 {
101            (skewness.powi(2) + 1.0) / kurtosis
102        } else {
103            0.0
104        };
105
106        // Outlier ratio (beyond 3σ)
107        let outlier_count = samples
108            .iter()
109            .filter(|&&x| (x - mean).abs() > 3.0 * std_dev)
110            .count();
111        let outlier_ratio = outlier_count as f64 / n as f64 * 100.0;
112
113        // Build histogram
114        let histogram = LatencyHistogram::build(samples, 20);
115
116        Some(Self {
117            p50,
118            p90,
119            p99,
120            p999,
121            jitter,
122            tail_ratio,
123            bimodality_coefficient,
124            histogram,
125            sample_count: n,
126            min,
127            max,
128            mean,
129            std_dev,
130            skewness,
131            kurtosis,
132            outlier_ratio,
133        })
134    }
135
136    /// Classify tail latency severity
137    pub fn tail_severity(&self) -> TailSeverity {
138        TailSeverity::from_ratio(self.tail_ratio)
139    }
140
141    /// Classify distribution shape
142    pub fn distribution_shape(&self) -> DistributionShape {
143        DistributionShape::classify(self.bimodality_coefficient, self.histogram.entropy)
144    }
145
146    /// Check if tail latency is problematic (P99/P50 > 3)
147    pub fn has_tail_problem(&self) -> bool {
148        self.tail_ratio > 3.0
149    }
150
151    /// Check if distribution appears bimodal
152    pub fn is_bimodal(&self) -> bool {
153        self.bimodality_coefficient > 0.555
154    }
155
156    /// Get summary string
157    pub fn summary(&self) -> String {
158        format!(
159            "n={} p50={:.2}µs p99={:.2}µs tail_ratio={:.2} jitter={:.2}µs shape={}",
160            self.sample_count,
161            self.p50,
162            self.p99,
163            self.tail_ratio,
164            self.jitter,
165            self.distribution_shape().name()
166        )
167    }
168}
169
170#[cfg(test)]
171mod tests;