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(
60        &mut self,
61        language: String,
62        duration_ms: u64,
63        memory_mb: u64,
64        success: bool,
65    ) {
66        self.total_executions += 1;
67        if success {
68            self.successful_executions += 1;
69        } else {
70            self.failed_executions += 1;
71        }
72
73        self.total_duration_ms += duration_ms;
74        self.memory_total_mb += memory_mb;
75        if memory_mb > self.memory_peak_mb {
76            self.memory_peak_mb = memory_mb;
77        }
78
79        *self
80            .language_distribution
81            .entry(language.clone())
82            .or_insert(0) += 1;
83
84        let record = ExecutionRecord {
85            language,
86            duration_ms,
87            success,
88            memory_used_mb: memory_mb,
89            timestamp: Utc::now(),
90        };
91
92        if self.recent_executions.len() >= 100 {
93            self.recent_executions.pop_front();
94        }
95        self.recent_executions.push_back(record);
96    }
97
98    pub fn record_failure(&mut self, language: String, duration_ms: u64) {
99        self.record_complete(language, duration_ms, 0, false);
100    }
101
102    pub fn record_timeout(&mut self, language: String, duration_ms: u64) {
103        self.timeouts += 1;
104        self.record_failure(language, duration_ms);
105    }
106
107    pub fn record_retry_attempt(&mut self) {
108        self.retry_attempts += 1;
109    }
110
111    pub fn record_retry_success(&mut self) {
112        self.retry_successes += 1;
113    }
114
115    pub fn record_retry_exhausted(&mut self) {
116        self.retry_exhausted += 1;
117    }
118
119    pub fn record_circuit_open(&mut self) {
120        self.circuit_open_events += 1;
121    }
122
123    pub fn record_half_open(&mut self) {
124        self.half_open_events += 1;
125    }
126
127    pub fn record_breaker_denial(&mut self) {
128        self.breaker_denials += 1;
129    }
130
131    pub fn record_result_size(&mut self, _size_bytes: usize) {
132        // Can be used to track result sizes for token estimation
133    }
134
135    pub fn avg_duration_ms(&self) -> u64 {
136        if self.total_executions > 0 {
137            self.total_duration_ms / self.total_executions
138        } else {
139            0
140        }
141    }
142
143    pub fn avg_memory_mb(&self) -> u64 {
144        if self.total_executions > 0 {
145            self.memory_total_mb / self.total_executions
146        } else {
147            0
148        }
149    }
150
151    pub fn success_rate(&self) -> f64 {
152        if self.total_executions > 0 {
153            self.successful_executions as f64 / self.total_executions as f64
154        } else {
155            0.0
156        }
157    }
158
159    pub fn timeout_rate(&self) -> f64 {
160        if self.total_executions > 0 {
161            self.timeouts as f64 / self.total_executions as f64
162        } else {
163            0.0
164        }
165    }
166}
167
168impl Default for ExecutionMetrics {
169    fn default() -> Self {
170        Self::new()
171    }
172}
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177
178    #[test]
179    fn test_record_complete() {
180        let mut metrics = ExecutionMetrics::new();
181        metrics.record_complete("python3".to_owned(), 1000, 50, true);
182
183        assert_eq!(metrics.total_executions, 1);
184        assert_eq!(metrics.successful_executions, 1);
185        assert_eq!(metrics.avg_duration_ms(), 1000);
186        assert_eq!(metrics.memory_peak_mb, 50);
187    }
188
189    #[test]
190    fn test_record_failure() {
191        let mut metrics = ExecutionMetrics::new();
192        metrics.record_failure("javascript".to_owned(), 500);
193
194        assert_eq!(metrics.total_executions, 1);
195        assert_eq!(metrics.failed_executions, 1);
196        assert_eq!(metrics.success_rate(), 0.0);
197    }
198
199    #[test]
200    fn test_record_timeout() {
201        let mut metrics = ExecutionMetrics::new();
202        metrics.record_timeout("python3".to_owned(), 5000);
203
204        assert_eq!(metrics.timeouts, 1);
205        assert_eq!(metrics.timeout_rate(), 1.0);
206    }
207
208    #[test]
209    fn test_success_rate() {
210        let mut metrics = ExecutionMetrics::new();
211        metrics.record_complete("python3".to_owned(), 100, 40, true);
212        metrics.record_complete("python3".to_owned(), 100, 42, true);
213        metrics.record_failure("python3".to_owned(), 100);
214
215        assert_eq!(metrics.total_executions, 3);
216        assert_eq!(metrics.success_rate(), 2.0 / 3.0);
217    }
218
219    #[test]
220    fn test_language_distribution() {
221        let mut metrics = ExecutionMetrics::new();
222        metrics.record_complete("python3".to_owned(), 100, 40, true);
223        metrics.record_complete("javascript".to_owned(), 150, 45, true);
224        metrics.record_complete("python3".to_owned(), 120, 42, true);
225
226        assert_eq!(metrics.language_distribution.get("python3"), Some(&2));
227        assert_eq!(metrics.language_distribution.get("javascript"), Some(&1));
228    }
229
230    #[test]
231    fn test_memory_peak() {
232        let mut metrics = ExecutionMetrics::new();
233        metrics.record_complete("python3".to_owned(), 100, 40, true);
234        metrics.record_complete("python3".to_owned(), 100, 60, true);
235        metrics.record_complete("python3".to_owned(), 100, 30, true);
236
237        assert_eq!(metrics.memory_peak_mb, 60);
238        assert_eq!(metrics.avg_memory_mb(), (40 + 60 + 30) / 3);
239    }
240
241    #[test]
242    fn test_recent_executions_limit() {
243        let mut metrics = ExecutionMetrics::new();
244        for _ in 0..150 {
245            metrics.record_complete("python3".to_owned(), 100, 40, true);
246        }
247
248        assert_eq!(metrics.total_executions, 150);
249        assert_eq!(metrics.recent_executions.len(), 100);
250    }
251
252    #[test]
253    fn test_retry_and_breaker_counters() {
254        let mut metrics = ExecutionMetrics::new();
255        metrics.record_retry_attempt();
256        metrics.record_retry_attempt();
257        metrics.record_retry_success();
258        metrics.record_retry_exhausted();
259        metrics.record_circuit_open();
260        metrics.record_half_open();
261        metrics.record_breaker_denial();
262
263        assert_eq!(metrics.retry_attempts, 2);
264        assert_eq!(metrics.retry_successes, 1);
265        assert_eq!(metrics.retry_exhausted, 1);
266        assert_eq!(metrics.circuit_open_events, 1);
267        assert_eq!(metrics.half_open_events, 1);
268        assert_eq!(metrics.breaker_denials, 1);
269    }
270}