1use std::sync::atomic::{AtomicU64, Ordering};
2use std::time::Instant;
3
4#[derive(Debug, Default)]
6pub struct MetricsCollector {
7 query_count: AtomicU64,
8 insert_count: AtomicU64,
9 total_candidates_examined: AtomicU64,
10 total_query_time_ns: AtomicU64,
11 bucket_hits: AtomicU64,
12 bucket_misses: AtomicU64,
13}
14
15impl MetricsCollector {
16 pub fn new() -> Self {
17 Self::default()
18 }
19
20 pub fn record_query(&self, candidates: u64, duration_ns: u64) {
21 self.query_count.fetch_add(1, Ordering::Relaxed);
22 self.total_candidates_examined
23 .fetch_add(candidates, Ordering::Relaxed);
24 self.total_query_time_ns
25 .fetch_add(duration_ns, Ordering::Relaxed);
26 }
27
28 pub fn record_insert(&self) {
29 self.insert_count.fetch_add(1, Ordering::Relaxed);
30 }
31
32 pub fn record_bucket_hit(&self) {
33 self.bucket_hits.fetch_add(1, Ordering::Relaxed);
34 }
35
36 pub fn record_bucket_miss(&self) {
37 self.bucket_misses.fetch_add(1, Ordering::Relaxed);
38 }
39
40 pub fn snapshot(&self) -> MetricsSnapshot {
42 let query_count = self.query_count.load(Ordering::Relaxed);
43 let total_query_time_ns = self.total_query_time_ns.load(Ordering::Relaxed);
44 let total_candidates = self.total_candidates_examined.load(Ordering::Relaxed);
45 let hits = self.bucket_hits.load(Ordering::Relaxed);
46 let misses = self.bucket_misses.load(Ordering::Relaxed);
47
48 MetricsSnapshot {
49 query_count,
50 insert_count: self.insert_count.load(Ordering::Relaxed),
51 avg_query_time_us: if query_count > 0 {
52 total_query_time_ns as f64 / query_count as f64 / 1000.0
53 } else {
54 0.0
55 },
56 avg_candidates_per_query: if query_count > 0 {
57 total_candidates as f64 / query_count as f64
58 } else {
59 0.0
60 },
61 hit_rate: if hits + misses > 0 {
62 hits as f64 / (hits + misses) as f64
63 } else {
64 0.0
65 },
66 }
67 }
68
69 pub fn reset(&self) {
71 self.query_count.store(0, Ordering::Relaxed);
72 self.insert_count.store(0, Ordering::Relaxed);
73 self.total_candidates_examined.store(0, Ordering::Relaxed);
74 self.total_query_time_ns.store(0, Ordering::Relaxed);
75 self.bucket_hits.store(0, Ordering::Relaxed);
76 self.bucket_misses.store(0, Ordering::Relaxed);
77 }
78}
79
80#[derive(Debug, Clone)]
82pub struct MetricsSnapshot {
83 pub query_count: u64,
84 pub insert_count: u64,
85 pub avg_query_time_us: f64,
86 pub avg_candidates_per_query: f64,
87 pub hit_rate: f64,
89}
90
91impl std::fmt::Display for MetricsSnapshot {
92 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93 write!(
94 f,
95 "Queries: {}, Inserts: {}, Avg query: {:.2}us, Avg candidates: {:.1}, Hit rate: {:.1}%",
96 self.query_count,
97 self.insert_count,
98 self.avg_query_time_us,
99 self.avg_candidates_per_query,
100 self.hit_rate * 100.0,
101 )
102 }
103}
104
105pub(crate) struct QueryTimer {
107 start: Instant,
108}
109
110impl QueryTimer {
111 pub fn new() -> Self {
112 Self {
113 start: Instant::now(),
114 }
115 }
116
117 pub fn elapsed_ns(&self) -> u64 {
118 self.start.elapsed().as_nanos() as u64
119 }
120}