rtc_interceptor/gcc/
rate_calc.rs1use std::collections::VecDeque;
4use std::time::{Duration, Instant};
5
6pub const DEFAULT_WINDOW: Duration = Duration::from_millis(500);
11
12#[derive(Debug, Clone)]
21pub struct RateCalculator {
22 window: Duration,
23 samples: VecDeque<(Instant, usize)>,
25 bytes_in_window: usize,
26}
27
28impl Default for RateCalculator {
29 fn default() -> Self {
30 Self::new(DEFAULT_WINDOW)
31 }
32}
33
34impl RateCalculator {
35 pub fn new(window: Duration) -> Self {
37 Self {
38 window,
39 samples: VecDeque::new(),
40 bytes_in_window: 0,
41 }
42 }
43
44 pub fn add(&mut self, now: Instant, size: usize) {
46 self.samples.push_back((now, size));
47 self.bytes_in_window += size;
48 self.expire(now);
49 }
50
51 pub fn rate_bits_per_second(&mut self, now: Instant) -> Option<f64> {
56 self.expire(now);
57
58 if self.samples.len() < 2 {
60 return None;
61 }
62
63 let oldest = self.samples.front()?.0;
64 let span = now.saturating_duration_since(oldest);
65 if span.is_zero() {
66 return None;
67 }
68
69 Some((self.bytes_in_window * 8) as f64 / span.as_secs_f64())
70 }
71
72 fn expire(&mut self, now: Instant) {
73 let cutoff = now.checked_sub(self.window).unwrap_or(now);
74 while let Some((at, size)) = self.samples.front().copied() {
75 if at >= cutoff {
76 break;
77 }
78 self.samples.pop_front();
79 self.bytes_in_window -= size;
80 }
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87
88 #[test]
90 fn it_measures_a_steady_stream() {
91 let epoch = Instant::now();
92 let mut calculator = RateCalculator::default();
93
94 for step in 0..50u64 {
96 calculator.add(epoch + Duration::from_millis(step * 10), 1500);
97 }
98
99 let rate = calculator
100 .rate_bits_per_second(epoch + Duration::from_millis(490))
101 .expect("a full window has a rate");
102 assert!(
103 (rate - 1_200_000.0).abs() < 100_000.0,
104 "expected about 1.2 Mb/s, got {rate}"
105 );
106 }
107
108 #[test]
111 fn an_empty_window_has_no_rate() {
112 let epoch = Instant::now();
113 let mut calculator = RateCalculator::default();
114
115 assert_eq!(None, calculator.rate_bits_per_second(epoch));
116 calculator.add(epoch, 1500);
117 assert_eq!(
118 None,
119 calculator.rate_bits_per_second(epoch),
120 "one sample is not a rate"
121 );
122 }
123
124 #[test]
127 fn old_samples_expire() {
128 let epoch = Instant::now();
129 let mut calculator = RateCalculator::new(Duration::from_millis(200));
130
131 for step in 0..20u64 {
132 calculator.add(epoch + Duration::from_millis(step * 10), 1500);
133 }
134 let busy = calculator
135 .rate_bits_per_second(epoch + Duration::from_millis(190))
136 .expect("rate");
137
138 calculator.add(epoch + Duration::from_millis(1_000), 1500);
140 calculator.add(epoch + Duration::from_millis(1_100), 1500);
141 let quiet = calculator
142 .rate_bits_per_second(epoch + Duration::from_millis(1_100))
143 .expect("rate");
144
145 assert!(
146 quiet < busy / 2.0,
147 "the window should have forgotten the busy period: {busy} then {quiet}"
148 );
149 }
150
151 #[test]
154 fn it_follows_a_halved_rate() {
155 let epoch = Instant::now();
156 let mut fast = RateCalculator::default();
157 let mut slow = RateCalculator::default();
158
159 for step in 0..50u64 {
160 fast.add(epoch + Duration::from_millis(step * 10), 1500);
161 }
162 for step in 0..25u64 {
163 slow.add(epoch + Duration::from_millis(step * 20), 1500);
164 }
165
166 let at = epoch + Duration::from_millis(490);
167 let fast_rate = fast.rate_bits_per_second(at).expect("rate");
168 let slow_rate = slow.rate_bits_per_second(at).expect("rate");
169
170 assert!(
171 (fast_rate / slow_rate - 2.0).abs() < 0.2,
172 "one should be twice the other: {fast_rate} vs {slow_rate}"
173 );
174 }
175}