1use std::time::Duration;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7pub enum CacheTier {
8 L1Memory,
10 L2Redis,
12}
13
14impl CacheTier {
15 pub fn as_str(&self) -> &'static str {
17 match self {
18 CacheTier::L1Memory => "l1_memory",
19 CacheTier::L2Redis => "l2_redis",
20 }
21 }
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
26pub enum CacheOperation {
27 Get,
28 Set,
29 Delete,
30 Serialize,
31 Deserialize,
32 Invalidate,
33}
34
35impl CacheOperation {
36 pub fn as_str(&self) -> &'static str {
38 match self {
39 CacheOperation::Get => "get",
40 CacheOperation::Set => "set",
41 CacheOperation::Delete => "delete",
42 CacheOperation::Serialize => "serialize",
43 CacheOperation::Deserialize => "deserialize",
44 CacheOperation::Invalidate => "invalidate",
45 }
46 }
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
51pub enum EvictionReason {
52 Expired,
54 Capacity,
56 Invalidated,
58 Replaced,
60 DependencyInvalidated,
62}
63
64impl EvictionReason {
65 pub fn as_str(&self) -> &'static str {
67 match self {
68 EvictionReason::Expired => "expired",
69 EvictionReason::Capacity => "capacity",
70 EvictionReason::Invalidated => "invalidated",
71 EvictionReason::Replaced => "replaced",
72 EvictionReason::DependencyInvalidated => "dependency",
73 }
74 }
75}
76
77pub trait CacheMetrics: Send + Sync + 'static {
81 fn record_hit(&self, key: &str, tier: CacheTier);
83
84 fn record_miss(&self, key: &str);
86
87 fn record_stale_hit(&self, key: &str);
89
90 fn record_latency(&self, operation: CacheOperation, duration: Duration);
92
93 fn record_eviction(&self, reason: EvictionReason);
95
96 fn record_size(&self, size: usize, memory_bytes: usize);
98}
99
100#[derive(Debug, Clone, Copy, Default)]
104pub struct NoopMetrics;
105
106impl CacheMetrics for NoopMetrics {
107 #[inline]
108 fn record_hit(&self, _key: &str, _tier: CacheTier) {}
109
110 #[inline]
111 fn record_miss(&self, _key: &str) {}
112
113 #[inline]
114 fn record_stale_hit(&self, _key: &str) {}
115
116 #[inline]
117 fn record_latency(&self, _operation: CacheOperation, _duration: Duration) {}
118
119 #[inline]
120 fn record_eviction(&self, _reason: EvictionReason) {}
121
122 #[inline]
123 fn record_size(&self, _size: usize, _memory_bytes: usize) {}
124}
125
126#[cfg(feature = "metrics")]
141#[derive(Debug, Clone)]
142pub struct MetricsCrateAdapter {
143 prefix: String,
144}
145
146#[cfg(feature = "metrics")]
147impl MetricsCrateAdapter {
148 pub fn new(prefix: impl Into<String>) -> Self {
150 Self {
151 prefix: prefix.into(),
152 }
153 }
154
155 fn metric_name(&self, name: &str) -> String {
156 format!("{}_{}", self.prefix, name)
157 }
158}
159
160#[cfg(feature = "metrics")]
161impl CacheMetrics for MetricsCrateAdapter {
162 fn record_hit(&self, _key: &str, tier: CacheTier) {
163 metrics::counter!(self.metric_name("hits_total"), "tier" => tier.as_str()).increment(1);
164 }
165
166 fn record_miss(&self, _key: &str) {
167 metrics::counter!(self.metric_name("misses_total")).increment(1);
168 }
169
170 fn record_stale_hit(&self, _key: &str) {
171 metrics::counter!(self.metric_name("stale_hits_total")).increment(1);
172 }
173
174 fn record_latency(&self, operation: CacheOperation, duration: Duration) {
175 metrics::histogram!(
176 self.metric_name("operation_duration_seconds"),
177 "operation" => operation.as_str()
178 )
179 .record(duration.as_secs_f64());
180 }
181
182 fn record_eviction(&self, reason: EvictionReason) {
183 metrics::counter!(
184 self.metric_name("evictions_total"),
185 "reason" => reason.as_str()
186 )
187 .increment(1);
188 }
189
190 fn record_size(&self, size: usize, memory_bytes: usize) {
191 metrics::gauge!(self.metric_name("entries")).set(size as f64);
192 metrics::gauge!(self.metric_name("memory_bytes")).set(memory_bytes as f64);
193 }
194}
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199
200 #[test]
201 fn test_tier_as_str() {
202 assert_eq!(CacheTier::L1Memory.as_str(), "l1_memory");
203 assert_eq!(CacheTier::L2Redis.as_str(), "l2_redis");
204 }
205
206 #[test]
207 fn test_operation_as_str() {
208 assert_eq!(CacheOperation::Get.as_str(), "get");
209 assert_eq!(CacheOperation::Set.as_str(), "set");
210 }
211
212 #[test]
213 fn test_eviction_reason_as_str() {
214 assert_eq!(EvictionReason::Expired.as_str(), "expired");
215 assert_eq!(EvictionReason::Capacity.as_str(), "capacity");
216 }
217
218 #[test]
219 fn test_noop_metrics() {
220 let metrics = NoopMetrics;
221 metrics.record_hit("key", CacheTier::L1Memory);
223 metrics.record_miss("key");
224 metrics.record_latency(CacheOperation::Get, Duration::from_millis(1));
225 }
226}
227