Skip to main content

vtcode_core/metrics/
execution_metrics.rs

1use chrono::{DateTime, Utc};
2use hashbrown::HashMap;
3use serde::{Deserialize, Serialize};
4use std::collections::VecDeque;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct ExecutionMetrics {
8    pub total_executions: u64,
9    pub successful_executions: u64,
10    pub failed_executions: u64,
11    pub timeouts: u64,
12    pub retry_attempts: u64,
13    pub retry_successes: u64,
14    pub retry_exhausted: u64,
15    pub circuit_open_events: u64,
16    pub half_open_events: u64,
17    pub breaker_denials: u64,
18    pub total_duration_ms: u64,
19    pub memory_peak_mb: u64,
20    pub memory_total_mb: u64,
21    pub language_distribution: HashMap<String, u64>,
22    pub recent_executions: VecDeque<ExecutionRecord>,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct ExecutionRecord {
27    pub language: String,
28    pub duration_ms: u64,
29    pub success: bool,
30    pub memory_used_mb: u64,
31    pub timestamp: DateTime<Utc>,
32}
33
34impl ExecutionMetrics {
35    pub fn new() -> Self {
36        Self {
37            total_executions: 0,
38            successful_executions: 0,
39            failed_executions: 0,
40            timeouts: 0,
41            retry_attempts: 0,
42            retry_successes: 0,
43            retry_exhausted: 0,
44            circuit_open_events: 0,
45            half_open_events: 0,
46            breaker_denials: 0,
47            total_duration_ms: 0,
48            memory_peak_mb: 0,
49            memory_total_mb: 0,
50            language_distribution: HashMap::new(),
51            recent_executions: VecDeque::with_capacity(100),
52        }
53    }
54
55    pub fn record_start(&mut self, _language: String) {
56        // Placeholder for tracking execution start if needed
57    }
58
59    pub fn record_complete(&mut self, language: String, duration_ms: u64, memory_mb: u64, success: bool) {
60        self.total_executions += 1;
61        if success {
62            self.successful_executions += 1;
63        } else {
64            self.failed_executions += 1;
65        }
66
67        self.total_duration_ms += duration_ms;
68        self.memory_total_mb += memory_mb;
69        if memory_mb > self.memory_peak_mb {
70            self.memory_peak_mb = memory_mb;
71        }
72
73        *self.language_distribution.entry(language.clone()).or_insert(0) += 1;
74
75        let record = ExecutionRecord {
76            language,
77            duration_ms,
78            success,
79            memory_used_mb: memory_mb,
80            timestamp: Utc::now(),
81        };
82
83        if self.recent_executions.len() >= 100 {
84            self.recent_executions.pop_front();
85        }
86        self.recent_executions.push_back(record);
87    }
88
89    pub fn record_failure(&mut self, language: String, duration_ms: u64) {
90        self.record_complete(language, duration_ms, 0, false);
91    }
92
93    pub fn record_timeout(&mut self, language: String, duration_ms: u64) {
94        self.timeouts += 1;
95        self.record_failure(language, duration_ms);
96    }
97
98    pub fn record_retry_attempt(&mut self) {
99        self.retry_attempts += 1;
100    }
101
102    pub fn record_retry_success(&mut self) {
103        self.retry_successes += 1;
104    }
105
106    pub fn record_retry_exhausted(&mut self) {
107        self.retry_exhausted += 1;
108    }
109
110    pub fn record_circuit_open(&mut self) {
111        self.circuit_open_events += 1;
112    }
113
114    pub fn record_half_open(&mut self) {
115        self.half_open_events += 1;
116    }
117
118    pub fn record_breaker_denial(&mut self) {
119        self.breaker_denials += 1;
120    }
121
122    pub fn record_result_size(&mut self, _size_bytes: usize) {
123        // Can be used to track result sizes for token estimation
124    }
125
126    pub fn avg_duration_ms(&self) -> u64 {
127        self.total_duration_ms.checked_div(self.total_executions).unwrap_or(0)
128    }
129
130    pub fn avg_memory_mb(&self) -> u64 {
131        self.memory_total_mb.checked_div(self.total_executions).unwrap_or(0)
132    }
133
134    pub fn success_rate(&self) -> f64 {
135        if self.total_executions > 0 {
136            self.successful_executions as f64 / self.total_executions as f64
137        } else {
138            0.0
139        }
140    }
141
142    pub fn timeout_rate(&self) -> f64 {
143        if self.total_executions > 0 {
144            self.timeouts as f64 / self.total_executions as f64
145        } else {
146            0.0
147        }
148    }
149}
150
151impl Default for ExecutionMetrics {
152    fn default() -> Self {
153        Self::new()
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160
161    #[test]
162    fn test_record_complete() {
163        let mut metrics = ExecutionMetrics::new();
164        metrics.record_complete("python3".to_owned(), 1000, 50, true);
165
166        assert_eq!(metrics.total_executions, 1);
167        assert_eq!(metrics.successful_executions, 1);
168        assert_eq!(metrics.avg_duration_ms(), 1000);
169        assert_eq!(metrics.memory_peak_mb, 50);
170    }
171
172    #[test]
173    fn test_record_failure() {
174        let mut metrics = ExecutionMetrics::new();
175        metrics.record_failure("javascript".to_owned(), 500);
176
177        assert_eq!(metrics.total_executions, 1);
178        assert_eq!(metrics.failed_executions, 1);
179        assert!((metrics.success_rate() - 0.0).abs() < f64::EPSILON);
180    }
181
182    #[test]
183    fn test_record_timeout() {
184        let mut metrics = ExecutionMetrics::new();
185        metrics.record_timeout("python3".to_owned(), 5000);
186
187        assert_eq!(metrics.timeouts, 1);
188        assert!((metrics.timeout_rate() - 1.0).abs() < f64::EPSILON);
189    }
190
191    #[test]
192    fn test_success_rate() {
193        let mut metrics = ExecutionMetrics::new();
194        metrics.record_complete("python3".to_owned(), 100, 40, true);
195        metrics.record_complete("python3".to_owned(), 100, 42, true);
196        metrics.record_failure("python3".to_owned(), 100);
197
198        assert_eq!(metrics.total_executions, 3);
199        assert!((metrics.success_rate() - 2.0 / 3.0).abs() < f64::EPSILON);
200    }
201
202    #[test]
203    fn test_language_distribution() {
204        let mut metrics = ExecutionMetrics::new();
205        metrics.record_complete("python3".to_owned(), 100, 40, true);
206        metrics.record_complete("javascript".to_owned(), 150, 45, true);
207        metrics.record_complete("python3".to_owned(), 120, 42, true);
208
209        assert_eq!(metrics.language_distribution.get("python3"), Some(&2));
210        assert_eq!(metrics.language_distribution.get("javascript"), Some(&1));
211    }
212
213    #[test]
214    fn test_memory_peak() {
215        let mut metrics = ExecutionMetrics::new();
216        metrics.record_complete("python3".to_owned(), 100, 40, true);
217        metrics.record_complete("python3".to_owned(), 100, 60, true);
218        metrics.record_complete("python3".to_owned(), 100, 30, true);
219
220        assert_eq!(metrics.memory_peak_mb, 60);
221        assert_eq!(metrics.avg_memory_mb(), (40 + 60 + 30) / 3);
222    }
223
224    #[test]
225    fn test_recent_executions_limit() {
226        let mut metrics = ExecutionMetrics::new();
227        for _ in 0..150 {
228            metrics.record_complete("python3".to_owned(), 100, 40, true);
229        }
230
231        assert_eq!(metrics.total_executions, 150);
232        assert_eq!(metrics.recent_executions.len(), 100);
233    }
234
235    #[test]
236    fn test_retry_and_breaker_counters() {
237        let mut metrics = ExecutionMetrics::new();
238        metrics.record_retry_attempt();
239        metrics.record_retry_attempt();
240        metrics.record_retry_success();
241        metrics.record_retry_exhausted();
242        metrics.record_circuit_open();
243        metrics.record_half_open();
244        metrics.record_breaker_denial();
245
246        assert_eq!(metrics.retry_attempts, 2);
247        assert_eq!(metrics.retry_successes, 1);
248        assert_eq!(metrics.retry_exhausted, 1);
249        assert_eq!(metrics.circuit_open_events, 1);
250        assert_eq!(metrics.half_open_events, 1);
251        assert_eq!(metrics.breaker_denials, 1);
252    }
253}