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