1use std::sync::atomic::{AtomicU64, Ordering};
11
12#[derive(Debug)]
18pub struct CompilationMetrics {
19 queries_compiled: AtomicU64,
21 queries_fallback: AtomicU64,
23 queries_error: AtomicU64,
25 compile_time_total_ns: AtomicU64,
27}
28
29impl CompilationMetrics {
30 #[must_use]
32 pub fn new() -> Self {
33 Self {
34 queries_compiled: AtomicU64::new(0),
35 queries_fallback: AtomicU64::new(0),
36 queries_error: AtomicU64::new(0),
37 compile_time_total_ns: AtomicU64::new(0),
38 }
39 }
40
41 pub fn record_compiled(&self, compile_time_ns: u64) {
43 self.queries_compiled.fetch_add(1, Ordering::Relaxed);
44 self.compile_time_total_ns
45 .fetch_add(compile_time_ns, Ordering::Relaxed);
46 }
47
48 pub fn record_fallback(&self) {
50 self.queries_fallback.fetch_add(1, Ordering::Relaxed);
51 }
52
53 pub fn record_error(&self) {
55 self.queries_error.fetch_add(1, Ordering::Relaxed);
56 }
57
58 #[must_use]
60 pub fn compiled_count(&self) -> u64 {
61 self.queries_compiled.load(Ordering::Relaxed)
62 }
63
64 #[must_use]
66 pub fn fallback_count(&self) -> u64 {
67 self.queries_fallback.load(Ordering::Relaxed)
68 }
69
70 #[must_use]
72 pub fn error_count(&self) -> u64 {
73 self.queries_error.load(Ordering::Relaxed)
74 }
75
76 #[must_use]
78 pub fn compile_time_total_ns(&self) -> u64 {
79 self.compile_time_total_ns.load(Ordering::Relaxed)
80 }
81
82 #[must_use]
84 pub fn total_queries(&self) -> u64 {
85 self.compiled_count() + self.fallback_count() + self.error_count()
86 }
87
88 #[must_use]
90 pub fn snapshot(&self) -> MetricsSnapshot {
91 MetricsSnapshot {
92 queries_compiled: self.compiled_count(),
93 queries_fallback: self.fallback_count(),
94 queries_error: self.error_count(),
95 compile_time_total_ns: self.compile_time_total_ns(),
96 }
97 }
98}
99
100impl Default for CompilationMetrics {
101 fn default() -> Self {
102 Self::new()
103 }
104}
105
106#[derive(Debug, Clone, Copy, PartialEq, Eq)]
108pub struct MetricsSnapshot {
109 pub queries_compiled: u64,
111 pub queries_fallback: u64,
113 pub queries_error: u64,
115 pub compile_time_total_ns: u64,
117}
118
119impl MetricsSnapshot {
120 #[must_use]
122 pub fn total_queries(&self) -> u64 {
123 self.queries_compiled + self.queries_fallback + self.queries_error
124 }
125
126 #[must_use]
130 #[allow(clippy::cast_precision_loss)]
131 pub fn compilation_rate(&self) -> f64 {
132 let total = self.total_queries();
133 if total == 0 {
134 return 0.0;
135 }
136 self.queries_compiled as f64 / total as f64
137 }
138}
139
140#[derive(Debug, Clone, Copy, PartialEq, Eq)]
142pub struct CacheSnapshot {
143 pub entries: usize,
145 pub capacity: usize,
147 pub hits: u64,
149 pub misses: u64,
151 pub evictions: u64,
153}
154
155impl CacheSnapshot {
156 #[must_use]
160 #[allow(clippy::cast_precision_loss)]
161 pub fn hit_rate(&self) -> f64 {
162 let total = self.hits + self.misses;
163 if total == 0 {
164 return 0.0;
165 }
166 self.hits as f64 / total as f64
167 }
168
169 #[must_use]
171 #[allow(clippy::cast_precision_loss)]
172 pub fn fill_ratio(&self) -> f64 {
173 if self.capacity == 0 {
174 return 0.0;
175 }
176 self.entries as f64 / self.capacity as f64
177 }
178}
179
180#[cfg(test)]
181#[allow(clippy::float_cmp)]
182mod tests {
183 use super::*;
184
185 #[test]
188 fn metrics_initial_zero() {
189 let m = CompilationMetrics::new();
190 assert_eq!(m.compiled_count(), 0);
191 assert_eq!(m.fallback_count(), 0);
192 assert_eq!(m.error_count(), 0);
193 assert_eq!(m.compile_time_total_ns(), 0);
194 assert_eq!(m.total_queries(), 0);
195 }
196
197 #[test]
198 fn metrics_record_compiled() {
199 let m = CompilationMetrics::new();
200 m.record_compiled(1_000_000);
201 m.record_compiled(2_000_000);
202 assert_eq!(m.compiled_count(), 2);
203 assert_eq!(m.compile_time_total_ns(), 3_000_000);
204 }
205
206 #[test]
207 fn metrics_record_fallback() {
208 let m = CompilationMetrics::new();
209 m.record_fallback();
210 m.record_fallback();
211 m.record_fallback();
212 assert_eq!(m.fallback_count(), 3);
213 }
214
215 #[test]
216 fn metrics_record_error() {
217 let m = CompilationMetrics::new();
218 m.record_error();
219 assert_eq!(m.error_count(), 1);
220 }
221
222 #[test]
223 fn metrics_total_queries() {
224 let m = CompilationMetrics::new();
225 m.record_compiled(100);
226 m.record_compiled(200);
227 m.record_fallback();
228 m.record_error();
229 assert_eq!(m.total_queries(), 4);
230 }
231
232 #[test]
233 fn metrics_default() {
234 let m = CompilationMetrics::default();
235 assert_eq!(m.compiled_count(), 0);
236 }
237
238 #[test]
241 fn snapshot_captures_state() {
242 let m = CompilationMetrics::new();
243 m.record_compiled(500);
244 m.record_fallback();
245
246 let snap = m.snapshot();
247 assert_eq!(snap.queries_compiled, 1);
248 assert_eq!(snap.queries_fallback, 1);
249 assert_eq!(snap.queries_error, 0);
250 assert_eq!(snap.compile_time_total_ns, 500);
251 assert_eq!(snap.total_queries(), 2);
252 }
253
254 #[test]
255 fn snapshot_compilation_rate() {
256 let snap = MetricsSnapshot {
257 queries_compiled: 3,
258 queries_fallback: 1,
259 queries_error: 0,
260 compile_time_total_ns: 0,
261 };
262 let rate = snap.compilation_rate();
263 assert!((rate - 0.75).abs() < f64::EPSILON);
264 }
265
266 #[test]
267 fn snapshot_compilation_rate_zero() {
268 let snap = MetricsSnapshot {
269 queries_compiled: 0,
270 queries_fallback: 0,
271 queries_error: 0,
272 compile_time_total_ns: 0,
273 };
274 assert_eq!(snap.compilation_rate(), 0.0);
275 }
276
277 #[test]
280 fn cache_snapshot_hit_rate() {
281 let snap = CacheSnapshot {
282 entries: 10,
283 capacity: 64,
284 hits: 80,
285 misses: 20,
286 evictions: 5,
287 };
288 assert!((snap.hit_rate() - 0.8).abs() < f64::EPSILON);
289 }
290
291 #[test]
292 fn cache_snapshot_hit_rate_zero() {
293 let snap = CacheSnapshot {
294 entries: 0,
295 capacity: 64,
296 hits: 0,
297 misses: 0,
298 evictions: 0,
299 };
300 assert_eq!(snap.hit_rate(), 0.0);
301 }
302
303 #[test]
304 fn cache_snapshot_fill_ratio() {
305 let snap = CacheSnapshot {
306 entries: 32,
307 capacity: 64,
308 hits: 0,
309 misses: 0,
310 evictions: 0,
311 };
312 assert!((snap.fill_ratio() - 0.5).abs() < f64::EPSILON);
313 }
314
315 #[test]
316 fn cache_snapshot_fill_ratio_zero_capacity() {
317 let snap = CacheSnapshot {
318 entries: 0,
319 capacity: 0,
320 hits: 0,
321 misses: 0,
322 evictions: 0,
323 };
324 assert_eq!(snap.fill_ratio(), 0.0);
325 }
326
327 #[test]
328 fn cache_snapshot_debug() {
329 let snap = CacheSnapshot {
330 entries: 5,
331 capacity: 64,
332 hits: 10,
333 misses: 2,
334 evictions: 1,
335 };
336 let s = format!("{snap:?}");
337 assert!(s.contains("CacheSnapshot"));
338 assert!(s.contains("entries: 5"));
339 }
340}