1pub use linkrs_metrics::utils::CacheStats;
7
8use parking_lot::RwLock;
9use std::sync::atomic::{AtomicUsize, Ordering};
10use std::sync::Arc;
11
12#[derive(Debug, Default)]
14pub struct MemoryStats {
15 current_bytes: AtomicUsize,
16 max_bytes: AtomicUsize,
17 entry_count: AtomicUsize,
18}
19
20impl MemoryStats {
21 pub fn new(max_bytes: usize) -> Self {
22 Self {
23 current_bytes: AtomicUsize::new(0),
24 max_bytes: AtomicUsize::new(max_bytes),
25 entry_count: AtomicUsize::new(0),
26 }
27 }
28
29 pub fn update(&self, current_bytes: usize, entry_count: usize) {
30 self.current_bytes.store(current_bytes, Ordering::Relaxed);
31 self.entry_count.store(entry_count, Ordering::Relaxed);
32 }
33
34 pub fn set_max_bytes(&self, max_bytes: usize) {
35 self.max_bytes.store(max_bytes, Ordering::Relaxed);
36 }
37
38 pub fn current_bytes(&self) -> usize {
39 self.current_bytes.load(Ordering::Relaxed)
40 }
41
42 pub fn max_bytes(&self) -> usize {
43 self.max_bytes.load(Ordering::Relaxed)
44 }
45
46 pub fn entry_count(&self) -> usize {
47 self.entry_count.load(Ordering::Relaxed)
48 }
49
50 pub fn usage_ratio(&self) -> f64 {
51 let max = self.max_bytes();
52 if max == 0 {
53 0.0
54 } else {
55 self.current_bytes() as f64 / max as f64
56 }
57 }
58
59 pub fn reset(&self) {
60 self.current_bytes.store(0, Ordering::Relaxed);
61 self.entry_count.store(0, Ordering::Relaxed);
62 }
63}
64
65impl Clone for MemoryStats {
66 fn clone(&self) -> Self {
67 Self {
68 current_bytes: AtomicUsize::new(self.current_bytes()),
69 max_bytes: AtomicUsize::new(self.max_bytes()),
70 entry_count: AtomicUsize::new(self.entry_count()),
71 }
72 }
73}
74
75#[derive(Debug)]
77pub struct PlanCacheStats {
78 pub counters: CacheStats,
79 pub memory: MemoryStats,
80 pub avg_query_size: Arc<RwLock<usize>>,
81 pub total_query_size: AtomicUsize,
82}
83
84impl Clone for PlanCacheStats {
85 fn clone(&self) -> Self {
86 Self {
87 counters: self.counters.clone(),
88 memory: self.memory.clone(),
89 avg_query_size: Arc::new(RwLock::new(*self.avg_query_size.read())),
90 total_query_size: AtomicUsize::new(self.total_query_size.load(Ordering::Relaxed)),
91 }
92 }
93}
94
95impl PlanCacheStats {
96 pub fn new(memory_budget: usize) -> Self {
97 Self {
98 counters: CacheStats::new(),
99 memory: MemoryStats::new(memory_budget),
100 avg_query_size: Arc::new(RwLock::new(0)),
101 total_query_size: AtomicUsize::new(0),
102 }
103 }
104
105 pub fn record_query_size(&self, size: usize) {
106 self.total_query_size.fetch_add(size, Ordering::Relaxed);
107 let total = self.total_query_size.load(Ordering::Relaxed);
108 let count = self.memory.entry_count();
109 if let Some(avg) = total.checked_div(count) {
110 *self.avg_query_size.write() = avg;
111 }
112 }
113
114 pub fn avg_query_size(&self) -> usize {
115 *self.avg_query_size.read()
116 }
117
118 pub fn estimated_memory(&self) -> usize {
119 const PER_ENTRY_OVERHEAD: usize = 1024;
120 let total_query = self.total_query_size.load(Ordering::Relaxed);
121 let entries = self.memory.entry_count();
122 total_query + (entries * PER_ENTRY_OVERHEAD)
123 }
124
125 pub fn hit_rate(&self) -> f64 {
126 self.counters.hit_rate()
127 }
128
129 pub fn reset(&self) {
130 self.counters.reset();
131 self.memory.reset();
132 self.total_query_size.store(0, Ordering::Relaxed);
133 *self.avg_query_size.write() = 0;
134 }
135
136 pub fn snapshot(&self) -> PlanCacheStatsSnapshot {
137 PlanCacheStatsSnapshot {
138 hits: self.counters.hits(),
139 misses: self.counters.misses(),
140 evictions: self.counters.evictions(),
141 expirations: self.counters.expirations(),
142 entry_count: self.memory.entry_count(),
143 current_memory: self.memory.current_bytes(),
144 max_memory: self.memory.max_bytes(),
145 hit_rate: self.hit_rate(),
146 avg_query_size: self.avg_query_size(),
147 }
148 }
149}
150
151impl Default for PlanCacheStats {
152 fn default() -> Self {
153 Self::new(50 * 1024 * 1024)
154 }
155}
156
157#[derive(Debug, Clone, Default)]
159pub struct PlanCacheStatsSnapshot {
160 pub hits: u64,
161 pub misses: u64,
162 pub evictions: u64,
163 pub expirations: u64,
164 pub entry_count: usize,
165 pub current_memory: usize,
166 pub max_memory: usize,
167 pub hit_rate: f64,
168 pub avg_query_size: usize,
169}
170
171#[derive(Debug, Clone)]
173pub struct CteCacheStats {
174 pub counters: CacheStats,
175 pub memory: MemoryStats,
176}
177
178impl CteCacheStats {
179 pub fn new(max_size: usize) -> Self {
180 Self {
181 counters: CacheStats::new(),
182 memory: MemoryStats::new(max_size),
183 }
184 }
185
186 pub fn hit_rate(&self) -> f64 {
187 self.counters.hit_rate()
188 }
189
190 pub fn memory_usage_ratio(&self) -> f64 {
191 self.memory.usage_ratio()
192 }
193
194 pub fn reset(&self) {
195 self.counters.reset();
196 self.memory.reset();
197 }
198
199 pub fn snapshot(&self) -> CteCacheStatsSnapshot {
200 CteCacheStatsSnapshot {
201 hits: self.counters.hits(),
202 misses: self.counters.misses(),
203 evictions: self.counters.evictions(),
204 rejections: self.counters.rejections(),
205 entry_count: self.memory.entry_count(),
206 current_memory: self.memory.current_bytes(),
207 max_memory: self.memory.max_bytes(),
208 hit_rate: self.hit_rate(),
209 memory_usage_ratio: self.memory_usage_ratio(),
210 }
211 }
212}
213
214impl Default for CteCacheStats {
215 fn default() -> Self {
216 Self::new(64 * 1024 * 1024)
217 }
218}
219
220#[derive(Debug, Clone, Default)]
222pub struct CteCacheStatsSnapshot {
223 pub hits: u64,
224 pub misses: u64,
225 pub evictions: u64,
226 pub rejections: u64,
227 pub entry_count: usize,
228 pub current_memory: usize,
229 pub max_memory: usize,
230 pub hit_rate: f64,
231 pub memory_usage_ratio: f64,
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237
238 #[test]
239 fn test_cache_stats() {
240 let stats = CacheStats::new();
241
242 stats.record_hit();
243 stats.record_hit();
244 stats.record_miss();
245
246 assert_eq!(stats.hits(), 2);
247 assert_eq!(stats.misses(), 1);
248 assert_eq!(stats.total_requests(), 3);
249 assert!((stats.hit_rate() - 0.6666666666666666).abs() < 0.01);
250 }
251
252 #[test]
253 fn test_cache_stats_reset() {
254 let stats = CacheStats::new();
255 stats.record_hit();
256 stats.record_miss();
257 stats.reset();
258
259 assert_eq!(stats.hits(), 0);
260 assert_eq!(stats.misses(), 0);
261 }
262
263 #[test]
264 fn test_memory_stats() {
265 let stats = MemoryStats::new(1000);
266 stats.update(500, 10);
267
268 assert_eq!(stats.current_bytes(), 500);
269 assert_eq!(stats.entry_count(), 10);
270 assert_eq!(stats.usage_ratio(), 0.5);
271 }
272
273 #[test]
274 fn test_plan_cache_stats() {
275 let stats = PlanCacheStats::new(1024 * 1024);
276
277 stats.counters.record_hit();
278 stats.counters.record_miss();
279 stats.memory.update(512 * 1024, 5);
280
281 let snapshot = stats.snapshot();
282 assert_eq!(snapshot.hits, 1);
283 assert_eq!(snapshot.misses, 1);
284 assert_eq!(snapshot.entry_count, 5);
285 }
286
287 #[test]
288 fn test_cte_cache_stats() {
289 let stats = CteCacheStats::new(1024 * 1024);
290
291 stats.counters.record_hit();
292 stats.counters.record_hit();
293 stats.memory.update(256 * 1024, 3);
294
295 let snapshot = stats.snapshot();
296 assert_eq!(snapshot.hits, 2);
297 assert_eq!(snapshot.entry_count, 3);
298 assert!((snapshot.hit_rate - 1.0).abs() < 0.01);
299 }
300}