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 {
15 timestamp: SystemTime::now(),
16 latency_ms,
17 success,
18 }
19 }
20}
21
22#[derive(Debug)]
24pub struct PredictiveMonitor {
25 samples: VecDeque<HealthSample>,
26 max_samples: usize,
27 failure_threshold: f32,
28 latency_budget_ms: u64,
29}
30
31impl PredictiveMonitor {
32 pub fn new(max_samples: usize, failure_threshold: f32, latency_budget_ms: u64) -> Self {
33 Self {
34 samples: VecDeque::with_capacity(max_samples),
35 max_samples,
36 failure_threshold,
37 latency_budget_ms,
38 }
39 }
40
41 pub fn record(&mut self, sample: HealthSample) {
42 self.samples.push_back(sample);
43 if self.samples.len() > self.max_samples {
44 self.samples.pop_front();
45 }
46 }
47
48 pub fn failure_rate(&self) -> f32 {
49 if self.samples.is_empty() {
50 return 0.0;
51 }
52
53 let failures = self.samples.iter().filter(|s| !s.success).count();
54 failures as f32 / self.samples.len() as f32
55 }
56
57 pub fn latency_p95(&self) -> Option<u64> {
58 if self.samples.is_empty() {
59 return None;
60 }
61
62 let mut latencies: Vec<u64> = self.samples.iter().map(|s| s.latency_ms).collect();
63 latencies.sort_unstable();
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}