rtc_interceptor/gcc/
overuse.rs1use super::threshold::AdaptiveThreshold;
4use std::time::{Duration, Instant};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
8pub enum Usage {
9 #[default]
11 Normal,
12 Over,
14 Under,
16}
17
18pub const DEFAULT_OVERUSE_TIME: Duration = Duration::from_millis(10);
23
24#[derive(Debug, Clone, Copy)]
36pub struct OveruseDetector {
37 threshold: AdaptiveThreshold,
38 overuse_time: Duration,
39 outside_since: Option<Instant>,
41 consecutive: u32,
43 previous_estimate_ms: f64,
45 usage: Usage,
46}
47
48impl Default for OveruseDetector {
49 fn default() -> Self {
50 Self {
51 threshold: AdaptiveThreshold::new(),
52 overuse_time: DEFAULT_OVERUSE_TIME,
53 outside_since: None,
54 consecutive: 0,
55 previous_estimate_ms: 0.0,
56 usage: Usage::Normal,
57 }
58 }
59}
60
61impl OveruseDetector {
62 pub fn new() -> Self {
64 Self::default()
65 }
66
67 pub fn usage(&self) -> Usage {
69 self.usage
70 }
71
72 pub fn threshold_ms(&self) -> f64 {
74 self.threshold.value_ms()
75 }
76
77 pub fn update(&mut self, now: Instant, estimate_ms: f64) -> Usage {
81 let threshold_ms = self.threshold.value_ms();
82
83 self.usage = if estimate_ms > threshold_ms {
84 let since = *self.outside_since.get_or_insert(now);
87 self.consecutive += 1;
88
89 let long_enough = now.saturating_duration_since(since) >= self.overuse_time;
90 let still_growing = estimate_ms >= self.previous_estimate_ms;
91
92 if long_enough && still_growing && self.consecutive > 1 {
93 Usage::Over
94 } else {
95 self.usage
97 }
98 } else if estimate_ms < -threshold_ms {
99 self.outside_since = None;
100 self.consecutive = 0;
101 Usage::Under
102 } else {
103 self.outside_since = None;
104 self.consecutive = 0;
105 Usage::Normal
106 };
107
108 self.previous_estimate_ms = estimate_ms;
109 self.threshold.update(now, estimate_ms);
113
114 self.usage
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 #[test]
124 fn a_quiet_path_reads_normal() {
125 let epoch = Instant::now();
126 let mut detector = OveruseDetector::new();
127
128 for step in 0..100u64 {
129 let usage = detector.update(epoch + Duration::from_millis(step * 20), 1.0);
130 assert_eq!(Usage::Normal, usage, "at step {step}");
131 }
132 }
133
134 #[test]
136 fn one_spike_is_not_overuse() {
137 let epoch = Instant::now();
138 let mut detector = OveruseDetector::new();
139
140 detector.update(epoch, 0.0);
141 let usage = detector.update(epoch + Duration::from_millis(20), 40.0);
142
143 assert_eq!(
144 Usage::Normal,
145 usage,
146 "a lone reading outside the threshold is noise, not congestion"
147 );
148 }
149
150 #[test]
152 fn a_sustained_growing_trend_is_overuse() {
153 let epoch = Instant::now();
154 let mut detector = OveruseDetector::new();
155
156 let mut usage = Usage::Normal;
157 for step in 0..10u64 {
158 usage = detector.update(
160 epoch + Duration::from_millis(step * 20),
161 20.0 + step as f64 * 2.0,
162 );
163 }
164
165 assert_eq!(
166 Usage::Over,
167 usage,
168 "a queue that keeps growing must eventually be declared"
169 );
170 }
171
172 #[test]
175 fn recovery_is_not_debounced() {
176 let epoch = Instant::now();
177 let mut detector = OveruseDetector::new();
178
179 let mut at = epoch;
180 for step in 0..10u64 {
181 at = epoch + Duration::from_millis(step * 20);
182 detector.update(at, 20.0 + step as f64 * 2.0);
183 }
184 assert_eq!(Usage::Over, detector.usage());
185
186 let usage = detector.update(at + Duration::from_millis(20), 0.0);
187 assert_eq!(
188 Usage::Normal,
189 usage,
190 "back inside the threshold must be believed at once"
191 );
192 }
193
194 #[test]
196 fn a_draining_queue_reads_under() {
197 let epoch = Instant::now();
198 let mut detector = OveruseDetector::new();
199
200 let usage = detector.update(epoch, -40.0);
201 assert_eq!(Usage::Under, usage);
202 }
203
204 #[test]
207 fn a_high_but_flat_trend_does_not_re_declare_overuse() {
208 let epoch = Instant::now();
209 let mut detector = OveruseDetector::new();
210
211 let mut at = epoch;
213 for step in 0..10u64 {
214 at = epoch + Duration::from_millis(step * 20);
215 detector.update(at, 20.0 + step as f64 * 2.0);
216 }
217 assert_eq!(Usage::Over, detector.usage());
218
219 for step in 0..200u64 {
221 at += Duration::from_millis(20);
222 detector.update(at, 1.0);
223 let _ = step;
224 }
225 assert_eq!(
226 Usage::Normal,
227 detector.usage(),
228 "a settled path must return to normal"
229 );
230 }
231}