1use std::sync::{Arc, Mutex};
33use std::time::{Duration, Instant};
34
35#[derive(Debug, Clone)]
37pub struct SearchStats {
38 pub total_queries: u64,
40 pub qps: f64,
42 pub p50_latency: Duration,
44 pub p95_latency: Duration,
46 pub p99_latency: Duration,
48 pub avg_latency: Duration,
50 pub min_latency: Duration,
52 pub max_latency: Duration,
54}
55
56impl Default for SearchStats {
57 fn default() -> Self {
58 Self {
59 total_queries: 0,
60 qps: 0.0,
61 p50_latency: Duration::ZERO,
62 p95_latency: Duration::ZERO,
63 p99_latency: Duration::ZERO,
64 avg_latency: Duration::ZERO,
65 min_latency: Duration::MAX,
66 max_latency: Duration::ZERO,
67 }
68 }
69}
70
71#[derive(Debug, Clone)]
73pub struct IndexStats {
74 pub num_vectors: usize,
76 pub dimensions: usize,
78 pub build_time: Duration,
80 pub memory_bytes: usize,
82}
83
84impl Default for IndexStats {
85 fn default() -> Self {
86 Self {
87 num_vectors: 0,
88 dimensions: 0,
89 build_time: Duration::ZERO,
90 memory_bytes: 0,
91 }
92 }
93}
94
95#[derive(Clone)]
97pub struct Metrics {
98 search_metrics: Arc<Mutex<SearchMetrics>>,
99 index_stats: Arc<Mutex<IndexStats>>,
100}
101
102impl Metrics {
103 pub fn new() -> Self {
105 Self {
106 search_metrics: Arc::new(Mutex::new(SearchMetrics::new())),
107 index_stats: Arc::new(Mutex::new(IndexStats::default())),
108 }
109 }
110
111 pub fn record_search_latency(&self, latency: Duration) {
113 let mut metrics = self.search_metrics.lock().unwrap();
114 metrics.record_latency(latency);
115 }
116
117 pub fn get_search_stats(&self) -> SearchStats {
119 let metrics = self.search_metrics.lock().unwrap();
120 metrics.compute_stats()
121 }
122
123 pub fn set_index_stats(&self, stats: IndexStats) {
125 let mut index_stats = self.index_stats.lock().unwrap();
126 *index_stats = stats;
127 }
128
129 pub fn get_index_stats(&self) -> IndexStats {
131 let index_stats = self.index_stats.lock().unwrap();
132 index_stats.clone()
133 }
134
135 pub fn reset(&self) {
137 let mut search_metrics = self.search_metrics.lock().unwrap();
138 *search_metrics = SearchMetrics::new();
139
140 let mut index_stats = self.index_stats.lock().unwrap();
141 *index_stats = IndexStats::default();
142 }
143}
144
145impl Default for Metrics {
146 fn default() -> Self {
147 Self::new()
148 }
149}
150
151struct SearchMetrics {
153 latencies: Vec<Duration>,
154 start_time: Instant,
155}
156
157impl SearchMetrics {
158 fn new() -> Self {
159 Self {
160 latencies: Vec::new(),
161 start_time: Instant::now(),
162 }
163 }
164
165 fn record_latency(&mut self, latency: Duration) {
166 self.latencies.push(latency);
167 }
168
169 fn compute_stats(&self) -> SearchStats {
170 if self.latencies.is_empty() {
171 return SearchStats::default();
172 }
173
174 let mut sorted_latencies = self.latencies.clone();
175 sorted_latencies.sort();
176
177 let total_queries = sorted_latencies.len() as u64;
178
179 let p50_idx = ((total_queries as f64 * 0.50).ceil() as usize).saturating_sub(1);
181 let p95_idx = ((total_queries as f64 * 0.95).ceil() as usize).saturating_sub(1);
182 let p99_idx = ((total_queries as f64 * 0.99).ceil() as usize).saturating_sub(1);
183
184 let p50_latency = sorted_latencies
185 .get(p50_idx)
186 .copied()
187 .unwrap_or(Duration::ZERO);
188 let p95_latency = sorted_latencies
189 .get(p95_idx)
190 .copied()
191 .unwrap_or(Duration::ZERO);
192 let p99_latency = sorted_latencies
193 .get(p99_idx)
194 .copied()
195 .unwrap_or(Duration::ZERO);
196
197 let total_micros: u128 = sorted_latencies.iter().map(|d| d.as_micros()).sum();
199 let avg_micros = total_micros / total_queries as u128;
200 let avg_latency = Duration::from_micros(avg_micros as u64);
201
202 let min_latency = *sorted_latencies.first().unwrap();
204 let max_latency = *sorted_latencies.last().unwrap();
205
206 let elapsed = self.start_time.elapsed().as_secs_f64();
208 let qps = if elapsed > 0.0 {
209 total_queries as f64 / elapsed
210 } else {
211 0.0
212 };
213
214 SearchStats {
215 total_queries,
216 qps,
217 p50_latency,
218 p95_latency,
219 p99_latency,
220 avg_latency,
221 min_latency,
222 max_latency,
223 }
224 }
225}
226
227pub struct LatencyTimer {
229 start: Instant,
230 metrics: Option<Metrics>,
231}
232
233impl LatencyTimer {
234 pub fn new(metrics: Option<Metrics>) -> Self {
236 Self {
237 start: Instant::now(),
238 metrics,
239 }
240 }
241
242 pub fn finish(self) -> Duration {
244 let latency = self.start.elapsed();
245 if let Some(metrics) = self.metrics {
246 metrics.record_search_latency(latency);
247 }
248 latency
249 }
250
251 pub fn elapsed(&self) -> Duration {
253 self.start.elapsed()
254 }
255}
256
257#[cfg(test)]
258mod tests {
259 use super::*;
260 use std::thread;
261 use std::time::Duration;
262
263 #[test]
264 fn test_metrics_creation() {
265 let metrics = Metrics::new();
266 let stats = metrics.get_search_stats();
267 assert_eq!(stats.total_queries, 0);
268 assert_eq!(stats.qps, 0.0);
269 }
270
271 #[test]
272 fn test_record_search_latency() {
273 let metrics = Metrics::new();
274
275 metrics.record_search_latency(Duration::from_micros(100));
276 metrics.record_search_latency(Duration::from_micros(200));
277 metrics.record_search_latency(Duration::from_micros(300));
278
279 let stats = metrics.get_search_stats();
280 assert_eq!(stats.total_queries, 3);
281 assert_eq!(stats.min_latency, Duration::from_micros(100));
282 assert_eq!(stats.max_latency, Duration::from_micros(300));
283 assert_eq!(stats.p50_latency, Duration::from_micros(200));
284 }
285
286 #[test]
287 fn test_percentiles() {
288 let metrics = Metrics::new();
289
290 for i in 1..=100 {
292 metrics.record_search_latency(Duration::from_micros(i));
293 }
294
295 let stats = metrics.get_search_stats();
296 assert_eq!(stats.total_queries, 100);
297
298 assert!(stats.p50_latency >= Duration::from_micros(49));
300 assert!(stats.p50_latency <= Duration::from_micros(51));
301
302 assert!(stats.p95_latency >= Duration::from_micros(94));
304 assert!(stats.p95_latency <= Duration::from_micros(96));
305
306 assert!(stats.p99_latency >= Duration::from_micros(98));
308 assert!(stats.p99_latency <= Duration::from_micros(100));
309 }
310
311 #[test]
312 fn test_average_latency() {
313 let metrics = Metrics::new();
314
315 metrics.record_search_latency(Duration::from_micros(100));
316 metrics.record_search_latency(Duration::from_micros(200));
317 metrics.record_search_latency(Duration::from_micros(300));
318
319 let stats = metrics.get_search_stats();
320 assert_eq!(stats.avg_latency, Duration::from_micros(200));
321 }
322
323 #[test]
324 fn test_qps_calculation() {
325 let metrics = Metrics::new();
326
327 for _ in 0..10 {
329 metrics.record_search_latency(Duration::from_micros(100));
330 }
331
332 thread::sleep(Duration::from_millis(100));
334
335 let stats = metrics.get_search_stats();
336 assert!(stats.qps > 0.0);
337 assert_eq!(stats.total_queries, 10);
338 }
339
340 #[test]
341 fn test_index_stats() {
342 let metrics = Metrics::new();
343
344 let index_stats = IndexStats {
345 num_vectors: 1000,
346 dimensions: 768,
347 build_time: Duration::from_secs(5),
348 memory_bytes: 1024 * 1024, };
350
351 metrics.set_index_stats(index_stats.clone());
352
353 let retrieved = metrics.get_index_stats();
354 assert_eq!(retrieved.num_vectors, 1000);
355 assert_eq!(retrieved.dimensions, 768);
356 assert_eq!(retrieved.build_time, Duration::from_secs(5));
357 assert_eq!(retrieved.memory_bytes, 1024 * 1024);
358 }
359
360 #[test]
361 fn test_metrics_reset() {
362 let metrics = Metrics::new();
363
364 metrics.record_search_latency(Duration::from_micros(100));
366 metrics.record_search_latency(Duration::from_micros(200));
367
368 let stats = metrics.get_search_stats();
369 assert_eq!(stats.total_queries, 2);
370
371 metrics.reset();
373
374 let stats = metrics.get_search_stats();
375 assert_eq!(stats.total_queries, 0);
376 }
377
378 #[test]
379 fn test_latency_timer() {
380 let metrics = Metrics::new();
381
382 let timer = LatencyTimer::new(Some(metrics.clone()));
383 thread::sleep(Duration::from_millis(10));
384 let latency = timer.finish();
385
386 assert!(latency >= Duration::from_millis(10));
387
388 let stats = metrics.get_search_stats();
389 assert_eq!(stats.total_queries, 1);
390 assert!(stats.min_latency >= Duration::from_millis(10));
391 }
392
393 #[test]
394 fn test_latency_timer_without_metrics() {
395 let timer = LatencyTimer::new(None);
396 thread::sleep(Duration::from_millis(5));
397 let latency = timer.finish();
398
399 assert!(latency >= Duration::from_millis(5));
400 }
401
402 #[test]
403 fn test_latency_timer_elapsed() {
404 let timer = LatencyTimer::new(None);
405 thread::sleep(Duration::from_millis(5));
406 let elapsed = timer.elapsed();
407
408 assert!(elapsed >= Duration::from_millis(5));
409 }
410
411 #[test]
412 fn test_thread_safety() {
413 let metrics = Metrics::new();
414 let metrics_clone = metrics.clone();
415
416 let handle = thread::spawn(move || {
417 for _ in 0..100 {
418 metrics_clone.record_search_latency(Duration::from_micros(100));
419 }
420 });
421
422 for _ in 0..100 {
423 metrics.record_search_latency(Duration::from_micros(200));
424 }
425
426 handle.join().unwrap();
427
428 let stats = metrics.get_search_stats();
429 assert_eq!(stats.total_queries, 200);
430 }
431}