Skip to main content

embeddenator_obs/obs/
metrics.rs

1use std::sync::atomic::{AtomicU64, Ordering};
2use std::time::Duration;
3
4#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
5pub struct MetricsSnapshot {
6    pub poison_recoveries_total: u64,
7
8    pub poison_path_inodes: u64,
9    pub poison_inodes: u64,
10    pub poison_inode_paths: u64,
11    pub poison_directories: u64,
12    pub poison_file_cache: u64,
13
14    pub sub_cache_hits: u64,
15    pub sub_cache_misses: u64,
16    pub sub_cache_evictions: u64,
17
18    pub index_cache_hits: u64,
19    pub index_cache_misses: u64,
20    pub index_cache_evictions: u64,
21
22    pub retrieval_query_calls: u64,
23    pub retrieval_query_ns_total: u64,
24    pub retrieval_query_ns_max: u64,
25
26    pub rerank_calls: u64,
27    pub rerank_ns_total: u64,
28    pub rerank_ns_max: u64,
29
30    pub hier_query_calls: u64,
31    pub hier_query_ns_total: u64,
32    pub hier_query_ns_max: u64,
33}
34
35pub struct Metrics {
36    poison_recoveries_total: AtomicU64,
37
38    poison_path_inodes: AtomicU64,
39    poison_inodes: AtomicU64,
40    poison_inode_paths: AtomicU64,
41    poison_directories: AtomicU64,
42    poison_file_cache: AtomicU64,
43
44    sub_cache_hits: AtomicU64,
45    sub_cache_misses: AtomicU64,
46    sub_cache_evictions: AtomicU64,
47
48    index_cache_hits: AtomicU64,
49    index_cache_misses: AtomicU64,
50    index_cache_evictions: AtomicU64,
51
52    retrieval_query_calls: AtomicU64,
53    retrieval_query_ns_total: AtomicU64,
54    retrieval_query_ns_max: AtomicU64,
55
56    rerank_calls: AtomicU64,
57    rerank_ns_total: AtomicU64,
58    rerank_ns_max: AtomicU64,
59
60    hier_query_calls: AtomicU64,
61    hier_query_ns_total: AtomicU64,
62    hier_query_ns_max: AtomicU64,
63}
64
65impl Default for Metrics {
66    fn default() -> Self {
67        Self::new()
68    }
69}
70
71impl Metrics {
72    pub const fn new() -> Self {
73        Self {
74            poison_recoveries_total: AtomicU64::new(0),
75
76            poison_path_inodes: AtomicU64::new(0),
77            poison_inodes: AtomicU64::new(0),
78            poison_inode_paths: AtomicU64::new(0),
79            poison_directories: AtomicU64::new(0),
80            poison_file_cache: AtomicU64::new(0),
81
82            sub_cache_hits: AtomicU64::new(0),
83            sub_cache_misses: AtomicU64::new(0),
84            sub_cache_evictions: AtomicU64::new(0),
85
86            index_cache_hits: AtomicU64::new(0),
87            index_cache_misses: AtomicU64::new(0),
88            index_cache_evictions: AtomicU64::new(0),
89
90            retrieval_query_calls: AtomicU64::new(0),
91            retrieval_query_ns_total: AtomicU64::new(0),
92            retrieval_query_ns_max: AtomicU64::new(0),
93
94            rerank_calls: AtomicU64::new(0),
95            rerank_ns_total: AtomicU64::new(0),
96            rerank_ns_max: AtomicU64::new(0),
97
98            hier_query_calls: AtomicU64::new(0),
99            hier_query_ns_total: AtomicU64::new(0),
100            hier_query_ns_max: AtomicU64::new(0),
101        }
102    }
103
104    pub fn snapshot(&self) -> MetricsSnapshot {
105        MetricsSnapshot {
106            poison_recoveries_total: self.poison_recoveries_total.load(Ordering::Relaxed),
107
108            poison_path_inodes: self.poison_path_inodes.load(Ordering::Relaxed),
109            poison_inodes: self.poison_inodes.load(Ordering::Relaxed),
110            poison_inode_paths: self.poison_inode_paths.load(Ordering::Relaxed),
111            poison_directories: self.poison_directories.load(Ordering::Relaxed),
112            poison_file_cache: self.poison_file_cache.load(Ordering::Relaxed),
113
114            sub_cache_hits: self.sub_cache_hits.load(Ordering::Relaxed),
115            sub_cache_misses: self.sub_cache_misses.load(Ordering::Relaxed),
116            sub_cache_evictions: self.sub_cache_evictions.load(Ordering::Relaxed),
117
118            index_cache_hits: self.index_cache_hits.load(Ordering::Relaxed),
119            index_cache_misses: self.index_cache_misses.load(Ordering::Relaxed),
120            index_cache_evictions: self.index_cache_evictions.load(Ordering::Relaxed),
121
122            retrieval_query_calls: self.retrieval_query_calls.load(Ordering::Relaxed),
123            retrieval_query_ns_total: self.retrieval_query_ns_total.load(Ordering::Relaxed),
124            retrieval_query_ns_max: self.retrieval_query_ns_max.load(Ordering::Relaxed),
125
126            rerank_calls: self.rerank_calls.load(Ordering::Relaxed),
127            rerank_ns_total: self.rerank_ns_total.load(Ordering::Relaxed),
128            rerank_ns_max: self.rerank_ns_max.load(Ordering::Relaxed),
129
130            hier_query_calls: self.hier_query_calls.load(Ordering::Relaxed),
131            hier_query_ns_total: self.hier_query_ns_total.load(Ordering::Relaxed),
132            hier_query_ns_max: self.hier_query_ns_max.load(Ordering::Relaxed),
133        }
134    }
135
136    pub fn inc_poison_path_inodes(&self) {
137        #[cfg(feature = "metrics")]
138        {
139            self.poison_recoveries_total.fetch_add(1, Ordering::Relaxed);
140            self.poison_path_inodes.fetch_add(1, Ordering::Relaxed);
141        }
142    }
143
144    pub fn inc_poison_inodes(&self) {
145        #[cfg(feature = "metrics")]
146        {
147            self.poison_recoveries_total.fetch_add(1, Ordering::Relaxed);
148            self.poison_inodes.fetch_add(1, Ordering::Relaxed);
149        }
150    }
151
152    pub fn inc_poison_inode_paths(&self) {
153        #[cfg(feature = "metrics")]
154        {
155            self.poison_recoveries_total.fetch_add(1, Ordering::Relaxed);
156            self.poison_inode_paths.fetch_add(1, Ordering::Relaxed);
157        }
158    }
159
160    pub fn inc_poison_directories(&self) {
161        #[cfg(feature = "metrics")]
162        {
163            self.poison_recoveries_total.fetch_add(1, Ordering::Relaxed);
164            self.poison_directories.fetch_add(1, Ordering::Relaxed);
165        }
166    }
167
168    pub fn inc_poison_file_cache(&self) {
169        #[cfg(feature = "metrics")]
170        {
171            self.poison_recoveries_total.fetch_add(1, Ordering::Relaxed);
172            self.poison_file_cache.fetch_add(1, Ordering::Relaxed);
173        }
174    }
175
176    pub fn inc_sub_cache_hit(&self) {
177        #[cfg(feature = "metrics")]
178        {
179            self.sub_cache_hits.fetch_add(1, Ordering::Relaxed);
180        }
181    }
182
183    pub fn inc_sub_cache_miss(&self) {
184        #[cfg(feature = "metrics")]
185        {
186            self.sub_cache_misses.fetch_add(1, Ordering::Relaxed);
187        }
188    }
189
190    pub fn inc_sub_cache_eviction(&self) {
191        #[cfg(feature = "metrics")]
192        {
193            self.sub_cache_evictions.fetch_add(1, Ordering::Relaxed);
194        }
195    }
196
197    pub fn inc_index_cache_hit(&self) {
198        #[cfg(feature = "metrics")]
199        {
200            self.index_cache_hits.fetch_add(1, Ordering::Relaxed);
201        }
202    }
203
204    pub fn inc_index_cache_miss(&self) {
205        #[cfg(feature = "metrics")]
206        {
207            self.index_cache_misses.fetch_add(1, Ordering::Relaxed);
208        }
209    }
210
211    pub fn inc_index_cache_eviction(&self) {
212        #[cfg(feature = "metrics")]
213        {
214            self.index_cache_evictions.fetch_add(1, Ordering::Relaxed);
215        }
216    }
217
218    pub fn record_retrieval_query(&self, _dur: Duration) {
219        #[cfg(feature = "metrics")]
220        {
221            record_duration(
222                &self.retrieval_query_calls,
223                &self.retrieval_query_ns_total,
224                &self.retrieval_query_ns_max,
225                _dur,
226            );
227        }
228    }
229
230    pub fn record_rerank(&self, _dur: Duration) {
231        #[cfg(feature = "metrics")]
232        {
233            record_duration(
234                &self.rerank_calls,
235                &self.rerank_ns_total,
236                &self.rerank_ns_max,
237                _dur,
238            );
239        }
240    }
241
242    pub fn record_hier_query(&self, _dur: Duration) {
243        #[cfg(feature = "metrics")]
244        {
245            record_duration(
246                &self.hier_query_calls,
247                &self.hier_query_ns_total,
248                &self.hier_query_ns_max,
249                _dur,
250            );
251        }
252    }
253}
254
255#[cfg(feature = "metrics")]
256fn record_duration(calls: &AtomicU64, total_ns: &AtomicU64, max_ns: &AtomicU64, dur: Duration) {
257    let ns = dur.as_nanos().min(u128::from(u64::MAX)) as u64;
258    calls.fetch_add(1, Ordering::Relaxed);
259    total_ns.fetch_add(ns, Ordering::Relaxed);
260
261    let mut cur = max_ns.load(Ordering::Relaxed);
262    while ns > cur {
263        match max_ns.compare_exchange_weak(cur, ns, Ordering::Relaxed, Ordering::Relaxed) {
264            Ok(_) => break,
265            Err(next) => cur = next,
266        }
267    }
268}
269
270static METRICS: Metrics = Metrics::new();
271
272pub fn metrics() -> &'static Metrics {
273    &METRICS
274}
275
276#[cfg(test)]
277mod tests {
278    use super::*;
279
280    #[test]
281    fn metrics_snapshot_delta_behaves_under_feature_gate() {
282        let before = metrics().snapshot();
283
284        metrics().inc_poison_inodes();
285        metrics().inc_sub_cache_hit();
286        metrics().record_retrieval_query(Duration::from_millis(2));
287
288        let after = metrics().snapshot();
289
290        #[cfg(feature = "metrics")]
291        {
292            assert!(after.poison_inodes >= before.poison_inodes + 1);
293            assert!(after.poison_recoveries_total >= before.poison_recoveries_total + 1);
294            assert!(after.sub_cache_hits >= before.sub_cache_hits + 1);
295            assert!(after.retrieval_query_calls >= before.retrieval_query_calls + 1);
296            assert!(after.retrieval_query_ns_total >= before.retrieval_query_ns_total);
297            assert!(after.retrieval_query_ns_max >= before.retrieval_query_ns_max);
298        }
299
300        #[cfg(not(feature = "metrics"))]
301        {
302            assert_eq!(after, before);
303        }
304    }
305}