Skip to main content

vtcode_core/diagnostics/
health.rs

1use std::collections::VecDeque;
2use std::time::SystemTime;
3
4/// Individual health datapoint for predictive detection.
5#[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/// Sliding-window monitor that surfaces degradation.
23#[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/// Structured diagnostic summary for external reporting.
78#[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}