Skip to main content

heliosdb_proxy/distribcache/
metrics.rs

1//! DistribCache metrics
2//!
3//! Prometheus-compatible metrics for cache observability.
4
5use std::sync::atomic::{AtomicU64, Ordering};
6use std::time::{Duration, Instant};
7
8use super::classifier::WorkloadType;
9use super::tiers::CacheTier;
10
11/// Comprehensive metrics for DistribCache
12#[derive(Debug)]
13pub struct DistribCacheMetrics {
14    /// Start time for uptime calculation
15    start_time: Instant,
16
17    // Cache operations
18    pub cache_hits: AtomicU64,
19    pub cache_misses: AtomicU64,
20    pub cache_puts: AtomicU64,
21    pub cache_evictions: AtomicU64,
22    pub cache_invalidations: AtomicU64,
23
24    // Per-tier metrics
25    pub l1_hits: AtomicU64,
26    pub l1_misses: AtomicU64,
27    pub l1_size_bytes: AtomicU64,
28    pub l1_entries: AtomicU64,
29
30    pub l2_hits: AtomicU64,
31    pub l2_misses: AtomicU64,
32    pub l2_size_bytes: AtomicU64,
33    pub l2_entries: AtomicU64,
34
35    pub l3_hits: AtomicU64,
36    pub l3_misses: AtomicU64,
37    pub l3_size_bytes: AtomicU64,
38    pub l3_entries: AtomicU64,
39
40    // Latency buckets (count of operations in each bucket)
41    pub latency_under_100us: AtomicU64,
42    pub latency_100us_1ms: AtomicU64,
43    pub latency_1ms_10ms: AtomicU64,
44    pub latency_10ms_100ms: AtomicU64,
45    pub latency_over_100ms: AtomicU64,
46    pub latency_total_us: AtomicU64,
47    pub latency_count: AtomicU64,
48
49    // Workload metrics
50    pub oltp_queries: AtomicU64,
51    pub olap_queries: AtomicU64,
52    pub vector_queries: AtomicU64,
53    pub ai_agent_queries: AtomicU64,
54    pub rag_queries: AtomicU64,
55    pub mixed_queries: AtomicU64,
56
57    // AI cache metrics
58    pub conversation_cache_hits: AtomicU64,
59    pub conversation_cache_misses: AtomicU64,
60    pub rag_cache_hits: AtomicU64,
61    pub rag_cache_misses: AtomicU64,
62    pub tool_cache_hits: AtomicU64,
63    pub tool_cache_misses: AtomicU64,
64    pub semantic_cache_hits: AtomicU64,
65    pub semantic_cache_misses: AtomicU64,
66
67    // Prefetch metrics
68    pub prefetch_hits: AtomicU64,
69    pub prefetch_misses: AtomicU64,
70    pub prefetch_predictions: AtomicU64,
71
72    // Invalidation metrics
73    pub wal_invalidations: AtomicU64,
74    pub ttl_invalidations: AtomicU64,
75    pub manual_invalidations: AtomicU64,
76
77    // Scheduler metrics
78    pub scheduled_queries: AtomicU64,
79    pub queued_queries: AtomicU64,
80    pub rejected_queries: AtomicU64,
81
82    // Error metrics
83    pub cache_errors: AtomicU64,
84    pub timeout_errors: AtomicU64,
85    pub serialization_errors: AtomicU64,
86}
87
88impl Default for DistribCacheMetrics {
89    fn default() -> Self {
90        Self::new()
91    }
92}
93
94impl DistribCacheMetrics {
95    /// Create new metrics instance
96    pub fn new() -> Self {
97        Self {
98            start_time: Instant::now(),
99            cache_hits: AtomicU64::new(0),
100            cache_misses: AtomicU64::new(0),
101            cache_puts: AtomicU64::new(0),
102            cache_evictions: AtomicU64::new(0),
103            cache_invalidations: AtomicU64::new(0),
104            l1_hits: AtomicU64::new(0),
105            l1_misses: AtomicU64::new(0),
106            l1_size_bytes: AtomicU64::new(0),
107            l1_entries: AtomicU64::new(0),
108            l2_hits: AtomicU64::new(0),
109            l2_misses: AtomicU64::new(0),
110            l2_size_bytes: AtomicU64::new(0),
111            l2_entries: AtomicU64::new(0),
112            l3_hits: AtomicU64::new(0),
113            l3_misses: AtomicU64::new(0),
114            l3_size_bytes: AtomicU64::new(0),
115            l3_entries: AtomicU64::new(0),
116            latency_under_100us: AtomicU64::new(0),
117            latency_100us_1ms: AtomicU64::new(0),
118            latency_1ms_10ms: AtomicU64::new(0),
119            latency_10ms_100ms: AtomicU64::new(0),
120            latency_over_100ms: AtomicU64::new(0),
121            latency_total_us: AtomicU64::new(0),
122            latency_count: AtomicU64::new(0),
123            oltp_queries: AtomicU64::new(0),
124            olap_queries: AtomicU64::new(0),
125            vector_queries: AtomicU64::new(0),
126            ai_agent_queries: AtomicU64::new(0),
127            rag_queries: AtomicU64::new(0),
128            mixed_queries: AtomicU64::new(0),
129            conversation_cache_hits: AtomicU64::new(0),
130            conversation_cache_misses: AtomicU64::new(0),
131            rag_cache_hits: AtomicU64::new(0),
132            rag_cache_misses: AtomicU64::new(0),
133            tool_cache_hits: AtomicU64::new(0),
134            tool_cache_misses: AtomicU64::new(0),
135            semantic_cache_hits: AtomicU64::new(0),
136            semantic_cache_misses: AtomicU64::new(0),
137            prefetch_hits: AtomicU64::new(0),
138            prefetch_misses: AtomicU64::new(0),
139            prefetch_predictions: AtomicU64::new(0),
140            wal_invalidations: AtomicU64::new(0),
141            ttl_invalidations: AtomicU64::new(0),
142            manual_invalidations: AtomicU64::new(0),
143            scheduled_queries: AtomicU64::new(0),
144            queued_queries: AtomicU64::new(0),
145            rejected_queries: AtomicU64::new(0),
146            cache_errors: AtomicU64::new(0),
147            timeout_errors: AtomicU64::new(0),
148            serialization_errors: AtomicU64::new(0),
149        }
150    }
151
152    /// Record cache hit
153    pub fn record_hit(&self, tier: CacheTier) {
154        self.cache_hits.fetch_add(1, Ordering::Relaxed);
155        match tier {
156            CacheTier::L1 => { self.l1_hits.fetch_add(1, Ordering::Relaxed); }
157            CacheTier::L2 => { self.l2_hits.fetch_add(1, Ordering::Relaxed); }
158            CacheTier::L3 => { self.l3_hits.fetch_add(1, Ordering::Relaxed); }
159        }
160    }
161
162    /// Record cache miss
163    pub fn record_miss(&self, tier: CacheTier) {
164        self.cache_misses.fetch_add(1, Ordering::Relaxed);
165        match tier {
166            CacheTier::L1 => { self.l1_misses.fetch_add(1, Ordering::Relaxed); }
167            CacheTier::L2 => { self.l2_misses.fetch_add(1, Ordering::Relaxed); }
168            CacheTier::L3 => { self.l3_misses.fetch_add(1, Ordering::Relaxed); }
169        }
170    }
171
172    /// Record cache put
173    pub fn record_put(&self) {
174        self.cache_puts.fetch_add(1, Ordering::Relaxed);
175    }
176
177    /// Record eviction
178    pub fn record_eviction(&self) {
179        self.cache_evictions.fetch_add(1, Ordering::Relaxed);
180    }
181
182    /// Record invalidation
183    pub fn record_invalidation(&self, source: InvalidationSource) {
184        self.cache_invalidations.fetch_add(1, Ordering::Relaxed);
185        match source {
186            InvalidationSource::WAL => { self.wal_invalidations.fetch_add(1, Ordering::Relaxed); }
187            InvalidationSource::TTL => { self.ttl_invalidations.fetch_add(1, Ordering::Relaxed); }
188            InvalidationSource::Manual => { self.manual_invalidations.fetch_add(1, Ordering::Relaxed); }
189        }
190    }
191
192    /// Record latency
193    pub fn record_latency(&self, duration: Duration) {
194        let us = duration.as_micros() as u64;
195        self.latency_total_us.fetch_add(us, Ordering::Relaxed);
196        self.latency_count.fetch_add(1, Ordering::Relaxed);
197
198        if us < 100 {
199            self.latency_under_100us.fetch_add(1, Ordering::Relaxed);
200        } else if us < 1000 {
201            self.latency_100us_1ms.fetch_add(1, Ordering::Relaxed);
202        } else if us < 10_000 {
203            self.latency_1ms_10ms.fetch_add(1, Ordering::Relaxed);
204        } else if us < 100_000 {
205            self.latency_10ms_100ms.fetch_add(1, Ordering::Relaxed);
206        } else {
207            self.latency_over_100ms.fetch_add(1, Ordering::Relaxed);
208        }
209    }
210
211    /// Record workload type
212    pub fn record_workload(&self, workload: WorkloadType) {
213        match workload {
214            WorkloadType::OLTP => { self.oltp_queries.fetch_add(1, Ordering::Relaxed); }
215            WorkloadType::OLAP => { self.olap_queries.fetch_add(1, Ordering::Relaxed); }
216            WorkloadType::Vector => { self.vector_queries.fetch_add(1, Ordering::Relaxed); }
217            WorkloadType::AIAgent => { self.ai_agent_queries.fetch_add(1, Ordering::Relaxed); }
218            WorkloadType::RAG => { self.rag_queries.fetch_add(1, Ordering::Relaxed); }
219            WorkloadType::Mixed => { self.mixed_queries.fetch_add(1, Ordering::Relaxed); }
220        }
221    }
222
223    /// Update tier size
224    pub fn update_tier_size(&self, tier: CacheTier, size_bytes: u64, entries: u64) {
225        match tier {
226            CacheTier::L1 => {
227                self.l1_size_bytes.store(size_bytes, Ordering::Relaxed);
228                self.l1_entries.store(entries, Ordering::Relaxed);
229            }
230            CacheTier::L2 => {
231                self.l2_size_bytes.store(size_bytes, Ordering::Relaxed);
232                self.l2_entries.store(entries, Ordering::Relaxed);
233            }
234            CacheTier::L3 => {
235                self.l3_size_bytes.store(size_bytes, Ordering::Relaxed);
236                self.l3_entries.store(entries, Ordering::Relaxed);
237            }
238        }
239    }
240
241    /// Record error
242    pub fn record_error(&self, error_type: ErrorType) {
243        self.cache_errors.fetch_add(1, Ordering::Relaxed);
244        match error_type {
245            ErrorType::Timeout => { self.timeout_errors.fetch_add(1, Ordering::Relaxed); }
246            ErrorType::Serialization => { self.serialization_errors.fetch_add(1, Ordering::Relaxed); }
247            ErrorType::Other => {}
248        }
249    }
250
251    /// Get uptime
252    pub fn uptime(&self) -> Duration {
253        self.start_time.elapsed()
254    }
255
256    /// Get overall hit rate
257    pub fn hit_rate(&self) -> f64 {
258        let hits = self.cache_hits.load(Ordering::Relaxed);
259        let misses = self.cache_misses.load(Ordering::Relaxed);
260        let total = hits + misses;
261        if total > 0 {
262            hits as f64 / total as f64
263        } else {
264            0.0
265        }
266    }
267
268    /// Get average latency in microseconds
269    pub fn avg_latency_us(&self) -> f64 {
270        let total = self.latency_total_us.load(Ordering::Relaxed);
271        let count = self.latency_count.load(Ordering::Relaxed);
272        if count > 0 {
273            total as f64 / count as f64
274        } else {
275            0.0
276        }
277    }
278
279    /// Export as Prometheus text format
280    pub fn to_prometheus(&self) -> String {
281        let mut output = String::with_capacity(4096);
282
283        // Uptime
284        output.push_str(&format!(
285            "# HELP distribcache_uptime_seconds Cache uptime in seconds\n\
286             # TYPE distribcache_uptime_seconds gauge\n\
287             distribcache_uptime_seconds {}\n\n",
288            self.uptime().as_secs()
289        ));
290
291        // Cache operations
292        output.push_str(&format!(
293            "# HELP distribcache_operations_total Total cache operations\n\
294             # TYPE distribcache_operations_total counter\n\
295             distribcache_operations_total{{operation=\"hit\"}} {}\n\
296             distribcache_operations_total{{operation=\"miss\"}} {}\n\
297             distribcache_operations_total{{operation=\"put\"}} {}\n\
298             distribcache_operations_total{{operation=\"eviction\"}} {}\n\
299             distribcache_operations_total{{operation=\"invalidation\"}} {}\n\n",
300            self.cache_hits.load(Ordering::Relaxed),
301            self.cache_misses.load(Ordering::Relaxed),
302            self.cache_puts.load(Ordering::Relaxed),
303            self.cache_evictions.load(Ordering::Relaxed),
304            self.cache_invalidations.load(Ordering::Relaxed),
305        ));
306
307        // Hit rate
308        output.push_str(&format!(
309            "# HELP distribcache_hit_rate Cache hit rate\n\
310             # TYPE distribcache_hit_rate gauge\n\
311             distribcache_hit_rate {:.4}\n\n",
312            self.hit_rate()
313        ));
314
315        // Per-tier metrics
316        output.push_str(&format!(
317            "# HELP distribcache_tier_hits_total Hits per tier\n\
318             # TYPE distribcache_tier_hits_total counter\n\
319             distribcache_tier_hits_total{{tier=\"l1\"}} {}\n\
320             distribcache_tier_hits_total{{tier=\"l2\"}} {}\n\
321             distribcache_tier_hits_total{{tier=\"l3\"}} {}\n\n",
322            self.l1_hits.load(Ordering::Relaxed),
323            self.l2_hits.load(Ordering::Relaxed),
324            self.l3_hits.load(Ordering::Relaxed),
325        ));
326
327        output.push_str(&format!(
328            "# HELP distribcache_tier_size_bytes Size per tier in bytes\n\
329             # TYPE distribcache_tier_size_bytes gauge\n\
330             distribcache_tier_size_bytes{{tier=\"l1\"}} {}\n\
331             distribcache_tier_size_bytes{{tier=\"l2\"}} {}\n\
332             distribcache_tier_size_bytes{{tier=\"l3\"}} {}\n\n",
333            self.l1_size_bytes.load(Ordering::Relaxed),
334            self.l2_size_bytes.load(Ordering::Relaxed),
335            self.l3_size_bytes.load(Ordering::Relaxed),
336        ));
337
338        output.push_str(&format!(
339            "# HELP distribcache_tier_entries Entries per tier\n\
340             # TYPE distribcache_tier_entries gauge\n\
341             distribcache_tier_entries{{tier=\"l1\"}} {}\n\
342             distribcache_tier_entries{{tier=\"l2\"}} {}\n\
343             distribcache_tier_entries{{tier=\"l3\"}} {}\n\n",
344            self.l1_entries.load(Ordering::Relaxed),
345            self.l2_entries.load(Ordering::Relaxed),
346            self.l3_entries.load(Ordering::Relaxed),
347        ));
348
349        // Latency histogram
350        output.push_str(&format!(
351            "# HELP distribcache_latency_bucket Latency distribution\n\
352             # TYPE distribcache_latency_bucket histogram\n\
353             distribcache_latency_bucket{{le=\"0.0001\"}} {}\n\
354             distribcache_latency_bucket{{le=\"0.001\"}} {}\n\
355             distribcache_latency_bucket{{le=\"0.01\"}} {}\n\
356             distribcache_latency_bucket{{le=\"0.1\"}} {}\n\
357             distribcache_latency_bucket{{le=\"+Inf\"}} {}\n\n",
358            self.latency_under_100us.load(Ordering::Relaxed),
359            self.latency_under_100us.load(Ordering::Relaxed) +
360                self.latency_100us_1ms.load(Ordering::Relaxed),
361            self.latency_under_100us.load(Ordering::Relaxed) +
362                self.latency_100us_1ms.load(Ordering::Relaxed) +
363                self.latency_1ms_10ms.load(Ordering::Relaxed),
364            self.latency_under_100us.load(Ordering::Relaxed) +
365                self.latency_100us_1ms.load(Ordering::Relaxed) +
366                self.latency_1ms_10ms.load(Ordering::Relaxed) +
367                self.latency_10ms_100ms.load(Ordering::Relaxed),
368            self.latency_count.load(Ordering::Relaxed),
369        ));
370
371        output.push_str(&format!(
372            "# HELP distribcache_latency_avg_us Average latency in microseconds\n\
373             # TYPE distribcache_latency_avg_us gauge\n\
374             distribcache_latency_avg_us {:.2}\n\n",
375            self.avg_latency_us()
376        ));
377
378        // Workload distribution
379        output.push_str(&format!(
380            "# HELP distribcache_workload_total Queries by workload type\n\
381             # TYPE distribcache_workload_total counter\n\
382             distribcache_workload_total{{type=\"oltp\"}} {}\n\
383             distribcache_workload_total{{type=\"olap\"}} {}\n\
384             distribcache_workload_total{{type=\"vector\"}} {}\n\
385             distribcache_workload_total{{type=\"ai_agent\"}} {}\n\
386             distribcache_workload_total{{type=\"rag\"}} {}\n\
387             distribcache_workload_total{{type=\"mixed\"}} {}\n\n",
388            self.oltp_queries.load(Ordering::Relaxed),
389            self.olap_queries.load(Ordering::Relaxed),
390            self.vector_queries.load(Ordering::Relaxed),
391            self.ai_agent_queries.load(Ordering::Relaxed),
392            self.rag_queries.load(Ordering::Relaxed),
393            self.mixed_queries.load(Ordering::Relaxed),
394        ));
395
396        // AI cache metrics
397        output.push_str(&format!(
398            "# HELP distribcache_ai_cache_hits AI cache hits\n\
399             # TYPE distribcache_ai_cache_hits counter\n\
400             distribcache_ai_cache_hits{{cache=\"conversation\"}} {}\n\
401             distribcache_ai_cache_hits{{cache=\"rag\"}} {}\n\
402             distribcache_ai_cache_hits{{cache=\"tool\"}} {}\n\
403             distribcache_ai_cache_hits{{cache=\"semantic\"}} {}\n\n",
404            self.conversation_cache_hits.load(Ordering::Relaxed),
405            self.rag_cache_hits.load(Ordering::Relaxed),
406            self.tool_cache_hits.load(Ordering::Relaxed),
407            self.semantic_cache_hits.load(Ordering::Relaxed),
408        ));
409
410        // Invalidation by source
411        output.push_str(&format!(
412            "# HELP distribcache_invalidations_total Invalidations by source\n\
413             # TYPE distribcache_invalidations_total counter\n\
414             distribcache_invalidations_total{{source=\"wal\"}} {}\n\
415             distribcache_invalidations_total{{source=\"ttl\"}} {}\n\
416             distribcache_invalidations_total{{source=\"manual\"}} {}\n\n",
417            self.wal_invalidations.load(Ordering::Relaxed),
418            self.ttl_invalidations.load(Ordering::Relaxed),
419            self.manual_invalidations.load(Ordering::Relaxed),
420        ));
421
422        // Errors
423        output.push_str(&format!(
424            "# HELP distribcache_errors_total Cache errors\n\
425             # TYPE distribcache_errors_total counter\n\
426             distribcache_errors_total{{type=\"timeout\"}} {}\n\
427             distribcache_errors_total{{type=\"serialization\"}} {}\n\
428             distribcache_errors_total{{type=\"total\"}} {}\n",
429            self.timeout_errors.load(Ordering::Relaxed),
430            self.serialization_errors.load(Ordering::Relaxed),
431            self.cache_errors.load(Ordering::Relaxed),
432        ));
433
434        output
435    }
436
437    /// Export as JSON
438    pub fn to_json(&self) -> serde_json::Value {
439        serde_json::json!({
440            "uptime_secs": self.uptime().as_secs(),
441            "operations": {
442                "hits": self.cache_hits.load(Ordering::Relaxed),
443                "misses": self.cache_misses.load(Ordering::Relaxed),
444                "puts": self.cache_puts.load(Ordering::Relaxed),
445                "evictions": self.cache_evictions.load(Ordering::Relaxed),
446                "invalidations": self.cache_invalidations.load(Ordering::Relaxed),
447            },
448            "hit_rate": self.hit_rate(),
449            "tiers": {
450                "l1": {
451                    "hits": self.l1_hits.load(Ordering::Relaxed),
452                    "misses": self.l1_misses.load(Ordering::Relaxed),
453                    "size_bytes": self.l1_size_bytes.load(Ordering::Relaxed),
454                    "entries": self.l1_entries.load(Ordering::Relaxed),
455                },
456                "l2": {
457                    "hits": self.l2_hits.load(Ordering::Relaxed),
458                    "misses": self.l2_misses.load(Ordering::Relaxed),
459                    "size_bytes": self.l2_size_bytes.load(Ordering::Relaxed),
460                    "entries": self.l2_entries.load(Ordering::Relaxed),
461                },
462                "l3": {
463                    "hits": self.l3_hits.load(Ordering::Relaxed),
464                    "misses": self.l3_misses.load(Ordering::Relaxed),
465                    "size_bytes": self.l3_size_bytes.load(Ordering::Relaxed),
466                    "entries": self.l3_entries.load(Ordering::Relaxed),
467                },
468            },
469            "latency": {
470                "avg_us": self.avg_latency_us(),
471                "buckets": {
472                    "under_100us": self.latency_under_100us.load(Ordering::Relaxed),
473                    "100us_1ms": self.latency_100us_1ms.load(Ordering::Relaxed),
474                    "1ms_10ms": self.latency_1ms_10ms.load(Ordering::Relaxed),
475                    "10ms_100ms": self.latency_10ms_100ms.load(Ordering::Relaxed),
476                    "over_100ms": self.latency_over_100ms.load(Ordering::Relaxed),
477                },
478            },
479            "workloads": {
480                "oltp": self.oltp_queries.load(Ordering::Relaxed),
481                "olap": self.olap_queries.load(Ordering::Relaxed),
482                "vector": self.vector_queries.load(Ordering::Relaxed),
483                "ai_agent": self.ai_agent_queries.load(Ordering::Relaxed),
484                "rag": self.rag_queries.load(Ordering::Relaxed),
485                "mixed": self.mixed_queries.load(Ordering::Relaxed),
486            },
487            "ai_caches": {
488                "conversation": {
489                    "hits": self.conversation_cache_hits.load(Ordering::Relaxed),
490                    "misses": self.conversation_cache_misses.load(Ordering::Relaxed),
491                },
492                "rag": {
493                    "hits": self.rag_cache_hits.load(Ordering::Relaxed),
494                    "misses": self.rag_cache_misses.load(Ordering::Relaxed),
495                },
496                "tool": {
497                    "hits": self.tool_cache_hits.load(Ordering::Relaxed),
498                    "misses": self.tool_cache_misses.load(Ordering::Relaxed),
499                },
500                "semantic": {
501                    "hits": self.semantic_cache_hits.load(Ordering::Relaxed),
502                    "misses": self.semantic_cache_misses.load(Ordering::Relaxed),
503                },
504            },
505            "errors": {
506                "total": self.cache_errors.load(Ordering::Relaxed),
507                "timeout": self.timeout_errors.load(Ordering::Relaxed),
508                "serialization": self.serialization_errors.load(Ordering::Relaxed),
509            },
510        })
511    }
512
513    /// Reset all metrics
514    pub fn reset(&self) {
515        self.cache_hits.store(0, Ordering::Relaxed);
516        self.cache_misses.store(0, Ordering::Relaxed);
517        self.cache_puts.store(0, Ordering::Relaxed);
518        self.cache_evictions.store(0, Ordering::Relaxed);
519        self.cache_invalidations.store(0, Ordering::Relaxed);
520
521        self.l1_hits.store(0, Ordering::Relaxed);
522        self.l1_misses.store(0, Ordering::Relaxed);
523        self.l2_hits.store(0, Ordering::Relaxed);
524        self.l2_misses.store(0, Ordering::Relaxed);
525        self.l3_hits.store(0, Ordering::Relaxed);
526        self.l3_misses.store(0, Ordering::Relaxed);
527
528        self.latency_under_100us.store(0, Ordering::Relaxed);
529        self.latency_100us_1ms.store(0, Ordering::Relaxed);
530        self.latency_1ms_10ms.store(0, Ordering::Relaxed);
531        self.latency_10ms_100ms.store(0, Ordering::Relaxed);
532        self.latency_over_100ms.store(0, Ordering::Relaxed);
533        self.latency_total_us.store(0, Ordering::Relaxed);
534        self.latency_count.store(0, Ordering::Relaxed);
535
536        self.oltp_queries.store(0, Ordering::Relaxed);
537        self.olap_queries.store(0, Ordering::Relaxed);
538        self.vector_queries.store(0, Ordering::Relaxed);
539        self.ai_agent_queries.store(0, Ordering::Relaxed);
540        self.rag_queries.store(0, Ordering::Relaxed);
541        self.mixed_queries.store(0, Ordering::Relaxed);
542
543        self.cache_errors.store(0, Ordering::Relaxed);
544        self.timeout_errors.store(0, Ordering::Relaxed);
545        self.serialization_errors.store(0, Ordering::Relaxed);
546    }
547}
548
549// CacheTier is imported from super::tiers
550
551/// Invalidation source
552#[derive(Debug, Clone, Copy, PartialEq, Eq)]
553pub enum InvalidationSource {
554    WAL,
555    TTL,
556    Manual,
557}
558
559// WorkloadType is imported from super::classifier
560
561/// Error type for metrics
562#[derive(Debug, Clone, Copy, PartialEq, Eq)]
563pub enum ErrorType {
564    Timeout,
565    Serialization,
566    Other,
567}
568
569#[cfg(test)]
570mod tests {
571    use super::*;
572
573    #[test]
574    fn test_metrics_creation() {
575        let metrics = DistribCacheMetrics::new();
576        assert_eq!(metrics.cache_hits.load(Ordering::Relaxed), 0);
577        assert_eq!(metrics.cache_misses.load(Ordering::Relaxed), 0);
578    }
579
580    #[test]
581    fn test_record_hit() {
582        let metrics = DistribCacheMetrics::new();
583
584        metrics.record_hit(CacheTier::L1);
585        metrics.record_hit(CacheTier::L2);
586        metrics.record_hit(CacheTier::L1);
587
588        assert_eq!(metrics.cache_hits.load(Ordering::Relaxed), 3);
589        assert_eq!(metrics.l1_hits.load(Ordering::Relaxed), 2);
590        assert_eq!(metrics.l2_hits.load(Ordering::Relaxed), 1);
591    }
592
593    #[test]
594    fn test_hit_rate() {
595        let metrics = DistribCacheMetrics::new();
596
597        metrics.record_hit(CacheTier::L1);
598        metrics.record_hit(CacheTier::L1);
599        metrics.record_miss(CacheTier::L1);
600        metrics.record_miss(CacheTier::L1);
601
602        assert!((metrics.hit_rate() - 0.5).abs() < 0.001);
603    }
604
605    #[test]
606    fn test_record_latency() {
607        let metrics = DistribCacheMetrics::new();
608
609        metrics.record_latency(Duration::from_micros(50));   // under 100us
610        metrics.record_latency(Duration::from_micros(500));  // 100us-1ms
611        metrics.record_latency(Duration::from_millis(5));    // 1ms-10ms
612
613        assert_eq!(metrics.latency_under_100us.load(Ordering::Relaxed), 1);
614        assert_eq!(metrics.latency_100us_1ms.load(Ordering::Relaxed), 1);
615        assert_eq!(metrics.latency_1ms_10ms.load(Ordering::Relaxed), 1);
616        assert_eq!(metrics.latency_count.load(Ordering::Relaxed), 3);
617    }
618
619    #[test]
620    fn test_prometheus_export() {
621        let metrics = DistribCacheMetrics::new();
622
623        metrics.record_hit(CacheTier::L1);
624        metrics.record_miss(CacheTier::L2);
625        metrics.record_workload(WorkloadType::OLTP);
626
627        let prometheus = metrics.to_prometheus();
628
629        assert!(prometheus.contains("distribcache_operations_total"));
630        assert!(prometheus.contains("distribcache_hit_rate"));
631        assert!(prometheus.contains("distribcache_tier_hits_total"));
632    }
633
634    #[test]
635    fn test_json_export() {
636        let metrics = DistribCacheMetrics::new();
637
638        metrics.record_hit(CacheTier::L1);
639        metrics.record_put();
640
641        let json = metrics.to_json();
642
643        assert_eq!(json["operations"]["hits"], 1);
644        assert_eq!(json["operations"]["puts"], 1);
645    }
646
647    #[test]
648    fn test_reset() {
649        let metrics = DistribCacheMetrics::new();
650
651        metrics.record_hit(CacheTier::L1);
652        metrics.record_miss(CacheTier::L2);
653        metrics.record_put();
654
655        metrics.reset();
656
657        assert_eq!(metrics.cache_hits.load(Ordering::Relaxed), 0);
658        assert_eq!(metrics.cache_misses.load(Ordering::Relaxed), 0);
659        assert_eq!(metrics.cache_puts.load(Ordering::Relaxed), 0);
660    }
661
662    #[test]
663    fn test_workload_tracking() {
664        let metrics = DistribCacheMetrics::new();
665
666        metrics.record_workload(WorkloadType::OLTP);
667        metrics.record_workload(WorkloadType::OLTP);
668        metrics.record_workload(WorkloadType::OLAP);
669        metrics.record_workload(WorkloadType::AIAgent);
670
671        assert_eq!(metrics.oltp_queries.load(Ordering::Relaxed), 2);
672        assert_eq!(metrics.olap_queries.load(Ordering::Relaxed), 1);
673        assert_eq!(metrics.ai_agent_queries.load(Ordering::Relaxed), 1);
674    }
675
676    #[test]
677    fn test_error_tracking() {
678        let metrics = DistribCacheMetrics::new();
679
680        metrics.record_error(ErrorType::Timeout);
681        metrics.record_error(ErrorType::Timeout);
682        metrics.record_error(ErrorType::Serialization);
683
684        assert_eq!(metrics.cache_errors.load(Ordering::Relaxed), 3);
685        assert_eq!(metrics.timeout_errors.load(Ordering::Relaxed), 2);
686        assert_eq!(metrics.serialization_errors.load(Ordering::Relaxed), 1);
687    }
688}