agentic_robotics_rt/
latency.rs

1//! High-precision latency tracking using HDR histogram
2
3use hdrhistogram::Histogram;
4use parking_lot::Mutex;
5use std::sync::Arc;
6use std::time::{Duration, Instant};
7
8/// Latency tracker with HDR histogram
9pub struct LatencyTracker {
10    histogram: Arc<Mutex<Histogram<u64>>>,
11    name: String,
12}
13
14impl LatencyTracker {
15    /// Create a new latency tracker
16    pub fn new(name: impl Into<String>) -> Self {
17        // 3 significant digits, max value 1 hour in microseconds
18        let histogram = Histogram::<u64>::new(3)
19            .expect("Failed to create histogram");
20
21        Self {
22            histogram: Arc::new(Mutex::new(histogram)),
23            name: name.into(),
24        }
25    }
26
27    /// Record a latency measurement
28    pub fn record(&self, duration: Duration) {
29        let micros = duration.as_micros() as u64;
30        if let Some(mut hist) = self.histogram.try_lock() {
31            let _ = hist.record(micros);
32        }
33    }
34
35    /// Get latency statistics
36    pub fn stats(&self) -> LatencyStats {
37        let hist = self.histogram.lock();
38
39        LatencyStats {
40            name: self.name.clone(),
41            count: hist.len(),
42            min: hist.min(),
43            max: hist.max(),
44            mean: hist.mean(),
45            p50: hist.value_at_quantile(0.50),
46            p90: hist.value_at_quantile(0.90),
47            p99: hist.value_at_quantile(0.99),
48            p999: hist.value_at_quantile(0.999),
49        }
50    }
51
52    /// Reset the histogram
53    pub fn reset(&self) {
54        self.histogram.lock().reset();
55    }
56
57    /// Create a measurement guard
58    pub fn measure(&self) -> LatencyMeasurement {
59        LatencyMeasurement {
60            tracker: self.clone(),
61            start: Instant::now(),
62        }
63    }
64}
65
66impl Clone for LatencyTracker {
67    fn clone(&self) -> Self {
68        Self {
69            histogram: self.histogram.clone(),
70            name: self.name.clone(),
71        }
72    }
73}
74
75/// Latency statistics
76#[derive(Debug, Clone)]
77pub struct LatencyStats {
78    pub name: String,
79    pub count: u64,
80    pub min: u64,
81    pub max: u64,
82    pub mean: f64,
83    pub p50: u64,
84    pub p90: u64,
85    pub p99: u64,
86    pub p999: u64,
87}
88
89impl std::fmt::Display for LatencyStats {
90    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91        write!(
92            f,
93            "{}: count={}, min={}µs, max={}µs, mean={:.2}µs, p50={}µs, p90={}µs, p99={}µs, p99.9={}µs",
94            self.name, self.count, self.min, self.max, self.mean, self.p50, self.p90, self.p99, self.p999
95        )
96    }
97}
98
99/// RAII guard for automatic latency measurement
100pub struct LatencyMeasurement {
101    tracker: LatencyTracker,
102    start: Instant,
103}
104
105impl Drop for LatencyMeasurement {
106    fn drop(&mut self) {
107        let duration = self.start.elapsed();
108        self.tracker.record(duration);
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115
116    #[test]
117    fn test_latency_tracker() {
118        let tracker = LatencyTracker::new("test");
119
120        // Record some measurements
121        tracker.record(Duration::from_micros(100));
122        tracker.record(Duration::from_micros(200));
123        tracker.record(Duration::from_micros(300));
124
125        let stats = tracker.stats();
126        assert_eq!(stats.count, 3);
127        assert!(stats.min >= 100);
128        assert!(stats.max <= 300);
129        assert!(stats.mean > 0.0);
130    }
131
132    #[test]
133    fn test_latency_measurement() {
134        let tracker = LatencyTracker::new("measurement");
135
136        {
137            let _measurement = tracker.measure();
138            std::thread::sleep(Duration::from_micros(100));
139        }
140
141        let stats = tracker.stats();
142        assert_eq!(stats.count, 1);
143        assert!(stats.min >= 100);
144    }
145}