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 }
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 }
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}