formualizer_eval/engine/
metrics.rs

1//! Lightweight metrics for warmup performance tracking
2
3use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
4use std::time::{Duration, Instant};
5
6/// Metrics collected during warmup and evaluation
7#[derive(Default)]
8pub struct WarmupMetrics {
9    // Timing
10    pub warmup_time_ns: AtomicU64,
11
12    // Candidates
13    pub candidates_refs_considered: AtomicUsize,
14    pub candidates_refs_selected: AtomicUsize,
15    pub candidates_criteria_considered: AtomicUsize,
16    pub candidates_criteria_selected: AtomicUsize,
17
18    // Built artifacts
19    pub flats_built: AtomicUsize,
20    pub masks_built: AtomicUsize,
21    pub indexes_built: AtomicUsize,
22
23    // Cache performance
24    pub flat_cache_hits: AtomicUsize,
25    pub flat_cache_misses: AtomicUsize,
26    pub mask_cache_hits: AtomicUsize,
27    pub mask_cache_misses: AtomicUsize,
28    pub index_hits: AtomicUsize,
29    pub index_misses: AtomicUsize,
30}
31
32impl WarmupMetrics {
33    pub fn new() -> Self {
34        Self::default()
35    }
36
37    /// Record warmup timing
38    pub fn record_warmup_time(&self, duration: Duration) {
39        self.warmup_time_ns
40            .store(duration.as_nanos() as u64, Ordering::Relaxed);
41    }
42
43    /// Increment a counter
44    pub fn inc_candidates_refs_considered(&self, count: usize) {
45        self.candidates_refs_considered
46            .fetch_add(count, Ordering::Relaxed);
47    }
48
49    pub fn inc_candidates_refs_selected(&self, count: usize) {
50        self.candidates_refs_selected
51            .fetch_add(count, Ordering::Relaxed);
52    }
53
54    pub fn inc_flats_built(&self, count: usize) {
55        self.flats_built.fetch_add(count, Ordering::Relaxed);
56    }
57
58    pub fn inc_flat_cache_hit(&self) {
59        self.flat_cache_hits.fetch_add(1, Ordering::Relaxed);
60    }
61
62    pub fn inc_flat_cache_miss(&self) {
63        self.flat_cache_misses.fetch_add(1, Ordering::Relaxed);
64    }
65
66    // Phase 2 additions for tests
67    pub fn record_flat_build(&self, _cell_count: usize, build_time_ms: u64) {
68        self.flats_built.fetch_add(1, Ordering::Relaxed);
69        self.warmup_time_ns
70            .fetch_add(build_time_ms * 1_000_000, Ordering::Relaxed);
71    }
72
73    pub fn record_flat_reuse(&self) {
74        self.flat_cache_hits.fetch_add(1, Ordering::Relaxed);
75    }
76
77    // Phase 3 additions for mask metrics
78    pub fn record_mask_reuse(&self) {
79        self.mask_cache_hits.fetch_add(1, Ordering::Relaxed);
80    }
81
82    pub fn record_mask_build(&self, _row_count: usize, _build_time_ms: u64) {
83        self.masks_built.fetch_add(1, Ordering::Relaxed);
84    }
85
86    pub fn flats_built(&self) -> usize {
87        self.flats_built.load(Ordering::Relaxed)
88    }
89
90    pub fn flats_reused(&self) -> usize {
91        self.flat_cache_hits.load(Ordering::Relaxed)
92    }
93
94    pub fn total_build_time_ms(&self) -> u64 {
95        self.warmup_time_ns.load(Ordering::Relaxed) / 1_000_000
96    }
97
98    /// Reset all metrics to zero
99    pub fn reset(&self) {
100        self.warmup_time_ns.store(0, Ordering::Relaxed);
101        self.candidates_refs_considered.store(0, Ordering::Relaxed);
102        self.candidates_refs_selected.store(0, Ordering::Relaxed);
103        self.candidates_criteria_considered
104            .store(0, Ordering::Relaxed);
105        self.candidates_criteria_selected
106            .store(0, Ordering::Relaxed);
107        self.flats_built.store(0, Ordering::Relaxed);
108        self.masks_built.store(0, Ordering::Relaxed);
109        self.indexes_built.store(0, Ordering::Relaxed);
110        self.flat_cache_hits.store(0, Ordering::Relaxed);
111        self.flat_cache_misses.store(0, Ordering::Relaxed);
112        self.mask_cache_hits.store(0, Ordering::Relaxed);
113        self.mask_cache_misses.store(0, Ordering::Relaxed);
114        self.index_hits.store(0, Ordering::Relaxed);
115        self.index_misses.store(0, Ordering::Relaxed);
116    }
117
118    /// Get a summary for debugging
119    #[cfg(test)]
120    pub fn summary(&self) -> String {
121        format!(
122            "WarmupMetrics {{ warmup_time_ms: {:.1}, refs_selected: {}/{}, flats_built: {}, cache_hits: {}/{} }}",
123            self.warmup_time_ns.load(Ordering::Relaxed) as f64 / 1_000_000.0,
124            self.candidates_refs_selected.load(Ordering::Relaxed),
125            self.candidates_refs_considered.load(Ordering::Relaxed),
126            self.flats_built.load(Ordering::Relaxed),
127            self.flat_cache_hits.load(Ordering::Relaxed),
128            self.flat_cache_hits.load(Ordering::Relaxed)
129                + self.flat_cache_misses.load(Ordering::Relaxed),
130        )
131    }
132}
133
134/// Timer helper for measuring warmup phases
135pub struct WarmupTimer {
136    start: Instant,
137}
138
139impl WarmupTimer {
140    pub fn start() -> Self {
141        Self {
142            start: Instant::now(),
143        }
144    }
145
146    pub fn elapsed(&self) -> Duration {
147        self.start.elapsed()
148    }
149}