1use crate::cache::CacheStats;
6use std::sync::RwLock;
7use std::time::{Duration, Instant};
8
9#[derive(Debug, Clone)]
11pub struct ExecutionMetrics {
12 pub execution_count: usize,
14 pub total_duration: Duration,
16 pub average_duration: Duration,
18 pub min_duration: Duration,
20 pub max_duration: Duration,
22}
23
24impl Default for ExecutionMetrics {
25 fn default() -> Self {
26 Self {
27 execution_count: 0,
28 total_duration: Duration::ZERO,
29 average_duration: Duration::ZERO,
30 min_duration: Duration::MAX,
31 max_duration: Duration::ZERO,
32 }
33 }
34}
35
36#[derive(Debug, Clone)]
38pub struct ModuleMetrics {
39 pub load_count: usize,
41 pub cache_hits: usize,
43 pub cache_misses: usize,
45 pub hit_rate: f64,
47}
48
49impl Default for ModuleMetrics {
50 fn default() -> Self {
51 Self {
52 load_count: 0,
53 cache_hits: 0,
54 cache_misses: 0,
55 hit_rate: 0.0,
56 }
57 }
58}
59
60#[derive(Debug, Clone)]
62pub struct MetricsSnapshot {
63 pub execution: ExecutionMetrics,
65 pub modules: ModuleMetrics,
67 pub trace_entries: usize,
69 pub module_cache_size: usize,
71 pub ast_cache: CacheStats,
73}
74
75pub struct MetricsCollector {
77 enabled: RwLock<bool>,
79 execution_start: RwLock<Option<Instant>>,
81 execution: RwLock<ExecutionMetrics>,
83 modules: RwLock<ModuleMetrics>,
85 module_loads: RwLock<std::collections::HashMap<String, usize>>,
87}
88
89impl MetricsCollector {
90 pub fn new() -> Self {
92 Self {
93 enabled: RwLock::new(false),
94 execution_start: RwLock::new(None),
95 execution: RwLock::new(ExecutionMetrics::default()),
96 modules: RwLock::new(ModuleMetrics::default()),
97 module_loads: RwLock::new(std::collections::HashMap::new()),
98 }
99 }
100
101 pub fn enable(&self) {
103 *self.enabled.write().unwrap() = true;
104 }
105
106 pub fn disable(&self) {
108 *self.enabled.write().unwrap() = false;
109 }
110
111 pub fn is_enabled(&self) -> bool {
113 *self.enabled.read().unwrap()
114 }
115
116 pub fn record_execution_start(&self) {
118 if !self.is_enabled() {
119 return;
120 }
121 *self.execution_start.write().unwrap() = Some(Instant::now());
122 }
123
124 pub fn record_execution_end(&self) {
126 if !self.is_enabled() {
127 return;
128 }
129
130 let start = self.execution_start.write().unwrap().take();
131 if let Some(start_time) = start {
132 let duration = start_time.elapsed();
133
134 let mut exec = self.execution.write().unwrap();
135 exec.execution_count += 1;
136 exec.total_duration += duration;
137 exec.average_duration = exec.total_duration / exec.execution_count as u32;
138 exec.min_duration = exec.min_duration.min(duration);
139 exec.max_duration = exec.max_duration.max(duration);
140 }
141 }
142
143 pub fn record_module_load(&self, module_id: &str, cached: bool) {
145 if !self.is_enabled() {
146 return;
147 }
148
149 let mut modules = self.modules.write().unwrap();
150 modules.load_count += 1;
151 if cached {
152 modules.cache_hits += 1;
153 } else {
154 modules.cache_misses += 1;
155 }
156 if modules.load_count > 0 {
157 modules.hit_rate = modules.cache_hits as f64 / modules.load_count as f64;
158 }
159
160 let mut loads = self.module_loads.write().unwrap();
161 *loads.entry(module_id.to_string()).or_insert(0) += 1;
162 }
163
164 pub fn snapshot(
166 &self,
167 trace_entries: usize,
168 module_cache_size: usize,
169 ast_cache: &CacheStats,
170 ) -> MetricsSnapshot {
171 MetricsSnapshot {
172 execution: self.execution.read().unwrap().clone(),
173 modules: self.modules.read().unwrap().clone(),
174 trace_entries,
175 module_cache_size,
176 ast_cache: ast_cache.clone(),
177 }
178 }
179
180 pub fn reset(&self) {
182 *self.execution.write().unwrap() = ExecutionMetrics::default();
183 *self.modules.write().unwrap() = ModuleMetrics::default();
184 self.module_loads.write().unwrap().clear();
185 }
186
187 pub fn module_load_count(&self, module_id: &str) -> usize {
189 self.module_loads
190 .read()
191 .unwrap()
192 .get(module_id)
193 .copied()
194 .unwrap_or(0)
195 }
196
197 pub fn all_module_loads(&self) -> std::collections::HashMap<String, usize> {
199 self.module_loads.read().unwrap().clone()
200 }
201}
202
203impl Default for MetricsCollector {
204 fn default() -> Self {
205 Self::new()
206 }
207}
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212
213 #[test]
214 fn test_metrics_collector_enable_disable() {
215 let collector = MetricsCollector::new();
216 assert!(!collector.is_enabled());
217
218 collector.enable();
219 assert!(collector.is_enabled());
220
221 collector.disable();
222 assert!(!collector.is_enabled());
223 }
224
225 #[test]
226 fn test_execution_metrics() {
227 let collector = MetricsCollector::new();
228 collector.enable();
229
230 collector.record_execution_start();
232 std::thread::sleep(Duration::from_millis(10));
233 collector.record_execution_end();
234
235 let snapshot = collector.snapshot(
236 0,
237 0,
238 &CacheStats {
239 size: 0,
240 max_size: 0,
241 hits: 0,
242 misses: 0,
243 hit_rate: 0.0,
244 },
245 );
246
247 assert_eq!(snapshot.execution.execution_count, 1);
248 assert!(snapshot.execution.total_duration.as_millis() >= 10);
249 assert_eq!(
250 snapshot.execution.min_duration,
251 snapshot.execution.max_duration
252 );
253 }
254
255 #[test]
256 fn test_module_metrics() {
257 let collector = MetricsCollector::new();
258 collector.enable();
259
260 collector.record_module_load("test_module", false); collector.record_module_load("test_module", true); collector.record_module_load("other_module", true); let snapshot = collector.snapshot(
266 0,
267 0,
268 &CacheStats {
269 size: 0,
270 max_size: 0,
271 hits: 0,
272 misses: 0,
273 hit_rate: 0.0,
274 },
275 );
276
277 assert_eq!(snapshot.modules.load_count, 3);
278 assert_eq!(snapshot.modules.cache_hits, 2);
279 assert_eq!(snapshot.modules.cache_misses, 1);
280 assert!((snapshot.modules.hit_rate - 0.666).abs() < 0.01); }
282
283 #[test]
284 fn test_metrics_disabled_no_collect() {
285 let collector = MetricsCollector::new();
286 collector.record_execution_start();
289 collector.record_execution_end();
290 collector.record_module_load("test", true);
291
292 let snapshot = collector.snapshot(
293 0,
294 0,
295 &CacheStats {
296 size: 0,
297 max_size: 0,
298 hits: 0,
299 misses: 0,
300 hit_rate: 0.0,
301 },
302 );
303
304 assert_eq!(snapshot.execution.execution_count, 0);
306 assert_eq!(snapshot.modules.load_count, 0);
307 }
308
309 #[test]
310 fn test_metrics_reset() {
311 let collector = MetricsCollector::new();
312 collector.enable();
313
314 collector.record_execution_start();
315 collector.record_execution_end();
316 collector.record_module_load("test", true);
317
318 collector.reset();
319
320 let snapshot = collector.snapshot(
321 0,
322 0,
323 &CacheStats {
324 size: 0,
325 max_size: 0,
326 hits: 0,
327 misses: 0,
328 hit_rate: 0.0,
329 },
330 );
331
332 assert_eq!(snapshot.execution.execution_count, 0);
333 assert_eq!(snapshot.modules.load_count, 0);
334 assert_eq!(collector.all_module_loads().len(), 0);
335 }
336
337 #[test]
338 fn test_module_load_counts() {
339 let collector = MetricsCollector::new();
340 collector.enable();
341
342 collector.record_module_load("module_a", false);
343 collector.record_module_load("module_a", false);
344 collector.record_module_load("module_b", true);
345
346 assert_eq!(collector.module_load_count("module_a"), 2);
347 assert_eq!(collector.module_load_count("module_b"), 1);
348 assert_eq!(collector.module_load_count("module_c"), 0);
349
350 let loads = collector.all_module_loads();
351 assert_eq!(loads.len(), 2);
352 }
353}