vtcode_core/diagnostics/
health.rs1use std::collections::VecDeque;
2use std::time::SystemTime;
3
4#[derive(Debug, Clone)]
6pub struct HealthSample {
7 pub timestamp: SystemTime,
8 pub latency_ms: u64,
9 pub success: bool,
10}
11
12impl HealthSample {
13 pub fn new(latency_ms: u64, success: bool) -> Self {
14 Self { timestamp: SystemTime::now(), latency_ms, success }
15 }
16}
17
18#[derive(Debug)]
20pub struct PredictiveMonitor {
21 samples: VecDeque<HealthSample>,
22 max_samples: usize,
23 failure_threshold: f32,
24 latency_budget_ms: u64,
25}
26
27impl PredictiveMonitor {
28 pub fn new(max_samples: usize, failure_threshold: f32, latency_budget_ms: u64) -> Self {
29 Self {
30 samples: VecDeque::with_capacity(max_samples),
31 max_samples,
32 failure_threshold,
33 latency_budget_ms,
34 }
35 }
36
37 pub fn record(&mut self, sample: HealthSample) {
38 self.samples.push_back(sample);
39 if self.samples.len() > self.max_samples {
40 self.samples.pop_front();
41 }
42 }
43
44 pub fn failure_rate(&self) -> f32 {
45 if self.samples.is_empty() {
46 return 0.0;
47 }
48
49 let failures = self.samples.iter().filter(|s| !s.success).count();
50 failures as f32 / self.samples.len() as f32
51 }
52
53 pub fn latency_p95(&self) -> Option<u64> {
54 if self.samples.is_empty() {
55 return None;
56 }
57
58 let mut latencies: Vec<u64> = self.samples.iter().map(|s| s.latency_ms).collect();
59 latencies.sort_unstable();
60
61 #[allow(
62 clippy::cast_sign_loss,
63 reason = "Intentional compatibility, platform, or test-only suppression."
64 )]
65 let idx = ((latencies.len() as f32) * 0.95).ceil() as usize - 1;
66 latencies.get(idx).cloned()
67 }
68
69 pub fn is_degrading(&self) -> bool {
70 let failure_rate = self.failure_rate();
71 let latency_p95 = self.latency_p95().unwrap_or(0);
72
73 failure_rate >= self.failure_threshold || latency_p95 > self.latency_budget_ms
74 }
75}
76
77#[derive(Debug, Clone)]
79pub struct DiagnosticReport {
80 pub failure_rate: f32,
81 pub latency_p95: Option<u64>,
82 pub degrading: bool,
83}
84
85impl DiagnosticReport {
86 pub fn from_monitor(monitor: &PredictiveMonitor) -> Self {
87 Self {
88 failure_rate: monitor.failure_rate(),
89 latency_p95: monitor.latency_p95(),
90 degrading: monitor.is_degrading(),
91 }
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98
99 #[test]
100 fn detects_degradation() {
101 let mut monitor = PredictiveMonitor::new(10, 0.2, 100);
102 for _ in 0..5 {
103 monitor.record(HealthSample::new(50, true));
104 }
105 for _ in 0..3 {
106 monitor.record(HealthSample::new(120, false));
107 }
108
109 assert!(monitor.is_degrading());
110 }
111}