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 => {
157                self.l1_hits.fetch_add(1, Ordering::Relaxed);
158            }
159            CacheTier::L2 => {
160                self.l2_hits.fetch_add(1, Ordering::Relaxed);
161            }
162            CacheTier::L3 => {
163                self.l3_hits.fetch_add(1, Ordering::Relaxed);
164            }
165        }
166    }
167
168    /// Record cache miss
169    pub fn record_miss(&self, tier: CacheTier) {
170        self.cache_misses.fetch_add(1, Ordering::Relaxed);
171        match tier {
172            CacheTier::L1 => {
173                self.l1_misses.fetch_add(1, Ordering::Relaxed);
174            }
175            CacheTier::L2 => {
176                self.l2_misses.fetch_add(1, Ordering::Relaxed);
177            }
178            CacheTier::L3 => {
179                self.l3_misses.fetch_add(1, Ordering::Relaxed);
180            }
181        }
182    }
183
184    /// Record cache put
185    pub fn record_put(&self) {
186        self.cache_puts.fetch_add(1, Ordering::Relaxed);
187    }
188
189    /// Record eviction
190    pub fn record_eviction(&self) {
191        self.cache_evictions.fetch_add(1, Ordering::Relaxed);
192    }
193
194    /// Record invalidation
195    pub fn record_invalidation(&self, source: InvalidationSource) {
196        self.cache_invalidations.fetch_add(1, Ordering::Relaxed);
197        match source {
198            InvalidationSource::WAL => {
199                self.wal_invalidations.fetch_add(1, Ordering::Relaxed);
200            }
201            InvalidationSource::TTL => {
202                self.ttl_invalidations.fetch_add(1, Ordering::Relaxed);
203            }
204            InvalidationSource::Manual => {
205                self.manual_invalidations.fetch_add(1, Ordering::Relaxed);
206            }
207        }
208    }
209
210    /// Record latency
211    pub fn record_latency(&self, duration: Duration) {
212        let us = duration.as_micros() as u64;
213        self.latency_total_us.fetch_add(us, Ordering::Relaxed);
214        self.latency_count.fetch_add(1, Ordering::Relaxed);
215
216        if us < 100 {
217            self.latency_under_100us.fetch_add(1, Ordering::Relaxed);
218        } else if us < 1000 {
219            self.latency_100us_1ms.fetch_add(1, Ordering::Relaxed);
220        } else if us < 10_000 {
221            self.latency_1ms_10ms.fetch_add(1, Ordering::Relaxed);
222        } else if us < 100_000 {
223            self.latency_10ms_100ms.fetch_add(1, Ordering::Relaxed);
224        } else {
225            self.latency_over_100ms.fetch_add(1, Ordering::Relaxed);
226        }
227    }
228
229    /// Record workload type
230    pub fn record_workload(&self, workload: WorkloadType) {
231        match workload {
232            WorkloadType::OLTP => {
233                self.oltp_queries.fetch_add(1, Ordering::Relaxed);
234            }
235            WorkloadType::OLAP => {
236                self.olap_queries.fetch_add(1, Ordering::Relaxed);
237            }
238            WorkloadType::Vector => {
239                self.vector_queries.fetch_add(1, Ordering::Relaxed);
240            }
241            WorkloadType::AIAgent => {
242                self.ai_agent_queries.fetch_add(1, Ordering::Relaxed);
243            }
244            WorkloadType::RAG => {
245                self.rag_queries.fetch_add(1, Ordering::Relaxed);
246            }
247            WorkloadType::Mixed => {
248                self.mixed_queries.fetch_add(1, Ordering::Relaxed);
249            }
250        }
251    }
252
253    /// Update tier size
254    pub fn update_tier_size(&self, tier: CacheTier, size_bytes: u64, entries: u64) {
255        match tier {
256            CacheTier::L1 => {
257                self.l1_size_bytes.store(size_bytes, Ordering::Relaxed);
258                self.l1_entries.store(entries, Ordering::Relaxed);
259            }
260            CacheTier::L2 => {
261                self.l2_size_bytes.store(size_bytes, Ordering::Relaxed);
262                self.l2_entries.store(entries, Ordering::Relaxed);
263            }
264            CacheTier::L3 => {
265                self.l3_size_bytes.store(size_bytes, Ordering::Relaxed);
266                self.l3_entries.store(entries, Ordering::Relaxed);
267            }
268        }
269    }
270
271    /// Record error
272    pub fn record_error(&self, error_type: ErrorType) {
273        self.cache_errors.fetch_add(1, Ordering::Relaxed);
274        match error_type {
275            ErrorType::Timeout => {
276                self.timeout_errors.fetch_add(1, Ordering::Relaxed);
277            }
278            ErrorType::Serialization => {
279                self.serialization_errors.fetch_add(1, Ordering::Relaxed);
280            }
281            ErrorType::Other => {}
282        }
283    }
284
285    /// Get uptime
286    pub fn uptime(&self) -> Duration {
287        self.start_time.elapsed()
288    }
289
290    /// Get overall hit rate
291    pub fn hit_rate(&self) -> f64 {
292        let hits = self.cache_hits.load(Ordering::Relaxed);
293        let misses = self.cache_misses.load(Ordering::Relaxed);
294        let total = hits + misses;
295        if total > 0 {
296            hits as f64 / total as f64
297        } else {
298            0.0
299        }
300    }
301
302    /// Get average latency in microseconds
303    pub fn avg_latency_us(&self) -> f64 {
304        let total = self.latency_total_us.load(Ordering::Relaxed);
305        let count = self.latency_count.load(Ordering::Relaxed);
306        if count > 0 {
307            total as f64 / count as f64
308        } else {
309            0.0
310        }
311    }
312
313    /// Export as Prometheus text format
314    pub fn to_prometheus(&self) -> String {
315        let mut output = String::with_capacity(4096);
316
317        // Uptime
318        output.push_str(&format!(
319            "# HELP distribcache_uptime_seconds Cache uptime in seconds\n\
320             # TYPE distribcache_uptime_seconds gauge\n\
321             distribcache_uptime_seconds {}\n\n",
322            self.uptime().as_secs()
323        ));
324
325        // Cache operations
326        output.push_str(&format!(
327            "# HELP distribcache_operations_total Total cache operations\n\
328             # TYPE distribcache_operations_total counter\n\
329             distribcache_operations_total{{operation=\"hit\"}} {}\n\
330             distribcache_operations_total{{operation=\"miss\"}} {}\n\
331             distribcache_operations_total{{operation=\"put\"}} {}\n\
332             distribcache_operations_total{{operation=\"eviction\"}} {}\n\
333             distribcache_operations_total{{operation=\"invalidation\"}} {}\n\n",
334            self.cache_hits.load(Ordering::Relaxed),
335            self.cache_misses.load(Ordering::Relaxed),
336            self.cache_puts.load(Ordering::Relaxed),
337            self.cache_evictions.load(Ordering::Relaxed),
338            self.cache_invalidations.load(Ordering::Relaxed),
339        ));
340
341        // Hit rate
342        output.push_str(&format!(
343            "# HELP distribcache_hit_rate Cache hit rate\n\
344             # TYPE distribcache_hit_rate gauge\n\
345             distribcache_hit_rate {:.4}\n\n",
346            self.hit_rate()
347        ));
348
349        // Per-tier metrics
350        output.push_str(&format!(
351            "# HELP distribcache_tier_hits_total Hits per tier\n\
352             # TYPE distribcache_tier_hits_total counter\n\
353             distribcache_tier_hits_total{{tier=\"l1\"}} {}\n\
354             distribcache_tier_hits_total{{tier=\"l2\"}} {}\n\
355             distribcache_tier_hits_total{{tier=\"l3\"}} {}\n\n",
356            self.l1_hits.load(Ordering::Relaxed),
357            self.l2_hits.load(Ordering::Relaxed),
358            self.l3_hits.load(Ordering::Relaxed),
359        ));
360
361        output.push_str(&format!(
362            "# HELP distribcache_tier_size_bytes Size per tier in bytes\n\
363             # TYPE distribcache_tier_size_bytes gauge\n\
364             distribcache_tier_size_bytes{{tier=\"l1\"}} {}\n\
365             distribcache_tier_size_bytes{{tier=\"l2\"}} {}\n\
366             distribcache_tier_size_bytes{{tier=\"l3\"}} {}\n\n",
367            self.l1_size_bytes.load(Ordering::Relaxed),
368            self.l2_size_bytes.load(Ordering::Relaxed),
369            self.l3_size_bytes.load(Ordering::Relaxed),
370        ));
371
372        output.push_str(&format!(
373            "# HELP distribcache_tier_entries Entries per tier\n\
374             # TYPE distribcache_tier_entries gauge\n\
375             distribcache_tier_entries{{tier=\"l1\"}} {}\n\
376             distribcache_tier_entries{{tier=\"l2\"}} {}\n\
377             distribcache_tier_entries{{tier=\"l3\"}} {}\n\n",
378            self.l1_entries.load(Ordering::Relaxed),
379            self.l2_entries.load(Ordering::Relaxed),
380            self.l3_entries.load(Ordering::Relaxed),
381        ));
382
383        // Latency histogram
384        output.push_str(&format!(
385            "# HELP distribcache_latency_bucket Latency distribution\n\
386             # TYPE distribcache_latency_bucket histogram\n\
387             distribcache_latency_bucket{{le=\"0.0001\"}} {}\n\
388             distribcache_latency_bucket{{le=\"0.001\"}} {}\n\
389             distribcache_latency_bucket{{le=\"0.01\"}} {}\n\
390             distribcache_latency_bucket{{le=\"0.1\"}} {}\n\
391             distribcache_latency_bucket{{le=\"+Inf\"}} {}\n\n",
392            self.latency_under_100us.load(Ordering::Relaxed),
393            self.latency_under_100us.load(Ordering::Relaxed)
394                + self.latency_100us_1ms.load(Ordering::Relaxed),
395            self.latency_under_100us.load(Ordering::Relaxed)
396                + self.latency_100us_1ms.load(Ordering::Relaxed)
397                + self.latency_1ms_10ms.load(Ordering::Relaxed),
398            self.latency_under_100us.load(Ordering::Relaxed)
399                + self.latency_100us_1ms.load(Ordering::Relaxed)
400                + self.latency_1ms_10ms.load(Ordering::Relaxed)
401                + self.latency_10ms_100ms.load(Ordering::Relaxed),
402            self.latency_count.load(Ordering::Relaxed),
403        ));
404
405        output.push_str(&format!(
406            "# HELP distribcache_latency_avg_us Average latency in microseconds\n\
407             # TYPE distribcache_latency_avg_us gauge\n\
408             distribcache_latency_avg_us {:.2}\n\n",
409            self.avg_latency_us()
410        ));
411
412        // Workload distribution
413        output.push_str(&format!(
414            "# HELP distribcache_workload_total Queries by workload type\n\
415             # TYPE distribcache_workload_total counter\n\
416             distribcache_workload_total{{type=\"oltp\"}} {}\n\
417             distribcache_workload_total{{type=\"olap\"}} {}\n\
418             distribcache_workload_total{{type=\"vector\"}} {}\n\
419             distribcache_workload_total{{type=\"ai_agent\"}} {}\n\
420             distribcache_workload_total{{type=\"rag\"}} {}\n\
421             distribcache_workload_total{{type=\"mixed\"}} {}\n\n",
422            self.oltp_queries.load(Ordering::Relaxed),
423            self.olap_queries.load(Ordering::Relaxed),
424            self.vector_queries.load(Ordering::Relaxed),
425            self.ai_agent_queries.load(Ordering::Relaxed),
426            self.rag_queries.load(Ordering::Relaxed),
427            self.mixed_queries.load(Ordering::Relaxed),
428        ));
429
430        // AI cache metrics
431        output.push_str(&format!(
432            "# HELP distribcache_ai_cache_hits AI cache hits\n\
433             # TYPE distribcache_ai_cache_hits counter\n\
434             distribcache_ai_cache_hits{{cache=\"conversation\"}} {}\n\
435             distribcache_ai_cache_hits{{cache=\"rag\"}} {}\n\
436             distribcache_ai_cache_hits{{cache=\"tool\"}} {}\n\
437             distribcache_ai_cache_hits{{cache=\"semantic\"}} {}\n\n",
438            self.conversation_cache_hits.load(Ordering::Relaxed),
439            self.rag_cache_hits.load(Ordering::Relaxed),
440            self.tool_cache_hits.load(Ordering::Relaxed),
441            self.semantic_cache_hits.load(Ordering::Relaxed),
442        ));
443
444        // Invalidation by source
445        output.push_str(&format!(
446            "# HELP distribcache_invalidations_total Invalidations by source\n\
447             # TYPE distribcache_invalidations_total counter\n\
448             distribcache_invalidations_total{{source=\"wal\"}} {}\n\
449             distribcache_invalidations_total{{source=\"ttl\"}} {}\n\
450             distribcache_invalidations_total{{source=\"manual\"}} {}\n\n",
451            self.wal_invalidations.load(Ordering::Relaxed),
452            self.ttl_invalidations.load(Ordering::Relaxed),
453            self.manual_invalidations.load(Ordering::Relaxed),
454        ));
455
456        // Errors
457        output.push_str(&format!(
458            "# HELP distribcache_errors_total Cache errors\n\
459             # TYPE distribcache_errors_total counter\n\
460             distribcache_errors_total{{type=\"timeout\"}} {}\n\
461             distribcache_errors_total{{type=\"serialization\"}} {}\n\
462             distribcache_errors_total{{type=\"total\"}} {}\n",
463            self.timeout_errors.load(Ordering::Relaxed),
464            self.serialization_errors.load(Ordering::Relaxed),
465            self.cache_errors.load(Ordering::Relaxed),
466        ));
467
468        output
469    }
470
471    /// Export as JSON
472    pub fn to_json(&self) -> serde_json::Value {
473        serde_json::json!({
474            "uptime_secs": self.uptime().as_secs(),
475            "operations": {
476                "hits": self.cache_hits.load(Ordering::Relaxed),
477                "misses": self.cache_misses.load(Ordering::Relaxed),
478                "puts": self.cache_puts.load(Ordering::Relaxed),
479                "evictions": self.cache_evictions.load(Ordering::Relaxed),
480                "invalidations": self.cache_invalidations.load(Ordering::Relaxed),
481            },
482            "hit_rate": self.hit_rate(),
483            "tiers": {
484                "l1": {
485                    "hits": self.l1_hits.load(Ordering::Relaxed),
486                    "misses": self.l1_misses.load(Ordering::Relaxed),
487                    "size_bytes": self.l1_size_bytes.load(Ordering::Relaxed),
488                    "entries": self.l1_entries.load(Ordering::Relaxed),
489                },
490                "l2": {
491                    "hits": self.l2_hits.load(Ordering::Relaxed),
492                    "misses": self.l2_misses.load(Ordering::Relaxed),
493                    "size_bytes": self.l2_size_bytes.load(Ordering::Relaxed),
494                    "entries": self.l2_entries.load(Ordering::Relaxed),
495                },
496                "l3": {
497                    "hits": self.l3_hits.load(Ordering::Relaxed),
498                    "misses": self.l3_misses.load(Ordering::Relaxed),
499                    "size_bytes": self.l3_size_bytes.load(Ordering::Relaxed),
500                    "entries": self.l3_entries.load(Ordering::Relaxed),
501                },
502            },
503            "latency": {
504                "avg_us": self.avg_latency_us(),
505                "buckets": {
506                    "under_100us": self.latency_under_100us.load(Ordering::Relaxed),
507                    "100us_1ms": self.latency_100us_1ms.load(Ordering::Relaxed),
508                    "1ms_10ms": self.latency_1ms_10ms.load(Ordering::Relaxed),
509                    "10ms_100ms": self.latency_10ms_100ms.load(Ordering::Relaxed),
510                    "over_100ms": self.latency_over_100ms.load(Ordering::Relaxed),
511                },
512            },
513            "workloads": {
514                "oltp": self.oltp_queries.load(Ordering::Relaxed),
515                "olap": self.olap_queries.load(Ordering::Relaxed),
516                "vector": self.vector_queries.load(Ordering::Relaxed),
517                "ai_agent": self.ai_agent_queries.load(Ordering::Relaxed),
518                "rag": self.rag_queries.load(Ordering::Relaxed),
519                "mixed": self.mixed_queries.load(Ordering::Relaxed),
520            },
521            "ai_caches": {
522                "conversation": {
523                    "hits": self.conversation_cache_hits.load(Ordering::Relaxed),
524                    "misses": self.conversation_cache_misses.load(Ordering::Relaxed),
525                },
526                "rag": {
527                    "hits": self.rag_cache_hits.load(Ordering::Relaxed),
528                    "misses": self.rag_cache_misses.load(Ordering::Relaxed),
529                },
530                "tool": {
531                    "hits": self.tool_cache_hits.load(Ordering::Relaxed),
532                    "misses": self.tool_cache_misses.load(Ordering::Relaxed),
533                },
534                "semantic": {
535                    "hits": self.semantic_cache_hits.load(Ordering::Relaxed),
536                    "misses": self.semantic_cache_misses.load(Ordering::Relaxed),
537                },
538            },
539            "errors": {
540                "total": self.cache_errors.load(Ordering::Relaxed),
541                "timeout": self.timeout_errors.load(Ordering::Relaxed),
542                "serialization": self.serialization_errors.load(Ordering::Relaxed),
543            },
544        })
545    }
546
547    /// Reset all metrics
548    pub fn reset(&self) {
549        self.cache_hits.store(0, Ordering::Relaxed);
550        self.cache_misses.store(0, Ordering::Relaxed);
551        self.cache_puts.store(0, Ordering::Relaxed);
552        self.cache_evictions.store(0, Ordering::Relaxed);
553        self.cache_invalidations.store(0, Ordering::Relaxed);
554
555        self.l1_hits.store(0, Ordering::Relaxed);
556        self.l1_misses.store(0, Ordering::Relaxed);
557        self.l2_hits.store(0, Ordering::Relaxed);
558        self.l2_misses.store(0, Ordering::Relaxed);
559        self.l3_hits.store(0, Ordering::Relaxed);
560        self.l3_misses.store(0, Ordering::Relaxed);
561
562        self.latency_under_100us.store(0, Ordering::Relaxed);
563        self.latency_100us_1ms.store(0, Ordering::Relaxed);
564        self.latency_1ms_10ms.store(0, Ordering::Relaxed);
565        self.latency_10ms_100ms.store(0, Ordering::Relaxed);
566        self.latency_over_100ms.store(0, Ordering::Relaxed);
567        self.latency_total_us.store(0, Ordering::Relaxed);
568        self.latency_count.store(0, Ordering::Relaxed);
569
570        self.oltp_queries.store(0, Ordering::Relaxed);
571        self.olap_queries.store(0, Ordering::Relaxed);
572        self.vector_queries.store(0, Ordering::Relaxed);
573        self.ai_agent_queries.store(0, Ordering::Relaxed);
574        self.rag_queries.store(0, Ordering::Relaxed);
575        self.mixed_queries.store(0, Ordering::Relaxed);
576
577        self.cache_errors.store(0, Ordering::Relaxed);
578        self.timeout_errors.store(0, Ordering::Relaxed);
579        self.serialization_errors.store(0, Ordering::Relaxed);
580    }
581}
582
583// CacheTier is imported from super::tiers
584
585/// Invalidation source
586#[derive(Debug, Clone, Copy, PartialEq, Eq)]
587pub enum InvalidationSource {
588    WAL,
589    TTL,
590    Manual,
591}
592
593// WorkloadType is imported from super::classifier
594
595/// Error type for metrics
596#[derive(Debug, Clone, Copy, PartialEq, Eq)]
597pub enum ErrorType {
598    Timeout,
599    Serialization,
600    Other,
601}
602
603#[cfg(test)]
604mod tests {
605    use super::*;
606
607    #[test]
608    fn test_metrics_creation() {
609        let metrics = DistribCacheMetrics::new();
610        assert_eq!(metrics.cache_hits.load(Ordering::Relaxed), 0);
611        assert_eq!(metrics.cache_misses.load(Ordering::Relaxed), 0);
612    }
613
614    #[test]
615    fn test_record_hit() {
616        let metrics = DistribCacheMetrics::new();
617
618        metrics.record_hit(CacheTier::L1);
619        metrics.record_hit(CacheTier::L2);
620        metrics.record_hit(CacheTier::L1);
621
622        assert_eq!(metrics.cache_hits.load(Ordering::Relaxed), 3);
623        assert_eq!(metrics.l1_hits.load(Ordering::Relaxed), 2);
624        assert_eq!(metrics.l2_hits.load(Ordering::Relaxed), 1);
625    }
626
627    #[test]
628    fn test_hit_rate() {
629        let metrics = DistribCacheMetrics::new();
630
631        metrics.record_hit(CacheTier::L1);
632        metrics.record_hit(CacheTier::L1);
633        metrics.record_miss(CacheTier::L1);
634        metrics.record_miss(CacheTier::L1);
635
636        assert!((metrics.hit_rate() - 0.5).abs() < 0.001);
637    }
638
639    #[test]
640    fn test_record_latency() {
641        let metrics = DistribCacheMetrics::new();
642
643        metrics.record_latency(Duration::from_micros(50)); // under 100us
644        metrics.record_latency(Duration::from_micros(500)); // 100us-1ms
645        metrics.record_latency(Duration::from_millis(5)); // 1ms-10ms
646
647        assert_eq!(metrics.latency_under_100us.load(Ordering::Relaxed), 1);
648        assert_eq!(metrics.latency_100us_1ms.load(Ordering::Relaxed), 1);
649        assert_eq!(metrics.latency_1ms_10ms.load(Ordering::Relaxed), 1);
650        assert_eq!(metrics.latency_count.load(Ordering::Relaxed), 3);
651    }
652
653    #[test]
654    fn test_prometheus_export() {
655        let metrics = DistribCacheMetrics::new();
656
657        metrics.record_hit(CacheTier::L1);
658        metrics.record_miss(CacheTier::L2);
659        metrics.record_workload(WorkloadType::OLTP);
660
661        let prometheus = metrics.to_prometheus();
662
663        assert!(prometheus.contains("distribcache_operations_total"));
664        assert!(prometheus.contains("distribcache_hit_rate"));
665        assert!(prometheus.contains("distribcache_tier_hits_total"));
666    }
667
668    #[test]
669    fn test_json_export() {
670        let metrics = DistribCacheMetrics::new();
671
672        metrics.record_hit(CacheTier::L1);
673        metrics.record_put();
674
675        let json = metrics.to_json();
676
677        assert_eq!(json["operations"]["hits"], 1);
678        assert_eq!(json["operations"]["puts"], 1);
679    }
680
681    #[test]
682    fn test_reset() {
683        let metrics = DistribCacheMetrics::new();
684
685        metrics.record_hit(CacheTier::L1);
686        metrics.record_miss(CacheTier::L2);
687        metrics.record_put();
688
689        metrics.reset();
690
691        assert_eq!(metrics.cache_hits.load(Ordering::Relaxed), 0);
692        assert_eq!(metrics.cache_misses.load(Ordering::Relaxed), 0);
693        assert_eq!(metrics.cache_puts.load(Ordering::Relaxed), 0);
694    }
695
696    #[test]
697    fn test_workload_tracking() {
698        let metrics = DistribCacheMetrics::new();
699
700        metrics.record_workload(WorkloadType::OLTP);
701        metrics.record_workload(WorkloadType::OLTP);
702        metrics.record_workload(WorkloadType::OLAP);
703        metrics.record_workload(WorkloadType::AIAgent);
704
705        assert_eq!(metrics.oltp_queries.load(Ordering::Relaxed), 2);
706        assert_eq!(metrics.olap_queries.load(Ordering::Relaxed), 1);
707        assert_eq!(metrics.ai_agent_queries.load(Ordering::Relaxed), 1);
708    }
709
710    #[test]
711    fn test_error_tracking() {
712        let metrics = DistribCacheMetrics::new();
713
714        metrics.record_error(ErrorType::Timeout);
715        metrics.record_error(ErrorType::Timeout);
716        metrics.record_error(ErrorType::Serialization);
717
718        assert_eq!(metrics.cache_errors.load(Ordering::Relaxed), 3);
719        assert_eq!(metrics.timeout_errors.load(Ordering::Relaxed), 2);
720        assert_eq!(metrics.serialization_errors.load(Ordering::Relaxed), 1);
721    }
722}