lsp_max/primitives/
rule_latency.rs1use std::collections::VecDeque;
7use std::time::Duration;
8
9const WINDOW: usize = 3;
10const THRESHOLD: Duration = Duration::from_millis(50);
11
12#[derive(Debug, Default)]
14pub struct RuleLatencyTracker {
15 pub recent_durations: VecDeque<Duration>,
17 pub reclassified: bool,
19}
20
21impl RuleLatencyTracker {
22 pub fn record(&mut self, duration: Duration) -> bool {
24 if self.reclassified {
25 return false;
26 }
27 if self.recent_durations.len() >= WINDOW {
28 self.recent_durations.pop_front();
29 }
30 self.recent_durations.push_back(duration);
31
32 if self.recent_durations.len() == WINDOW
33 && self.recent_durations.iter().all(|d| *d > THRESHOLD)
34 {
35 self.reclassified = true;
36 return true;
37 }
38 false
39 }
40
41 #[inline]
42 pub fn is_reclassified(&self) -> bool {
44 self.reclassified
45 }
46}
47
48#[cfg(test)]
49mod tests {
50 use super::*;
51
52 #[test]
53 fn reclassifies_after_three_slow_evaluations() {
54 let mut t = RuleLatencyTracker::default();
55 let slow = Duration::from_millis(60);
56 assert!(!t.record(slow));
57 assert!(!t.record(slow));
58 assert!(t.record(slow)); assert!(t.is_reclassified());
60 assert!(!t.record(slow)); }
62
63 #[test]
64 fn does_not_reclassify_on_mixed_durations() {
65 let mut t = RuleLatencyTracker::default();
66 let fast = Duration::from_millis(10);
67 let slow = Duration::from_millis(60);
68 t.record(slow);
69 t.record(fast); t.record(slow);
71 assert!(!t.is_reclassified());
72 }
73}