/home/runner/work/feoxdb/feoxdb/src/stats.rs
Line | Count | Source |
1 | | use std::sync::atomic::{AtomicU32, AtomicU64, AtomicUsize, Ordering}; |
2 | | |
3 | | /// Central statistics hub for FeoxStore |
4 | | #[derive(Debug)] |
5 | | pub struct Statistics { |
6 | | // Store metrics |
7 | | pub record_count: AtomicU32, |
8 | | pub memory_usage: AtomicUsize, |
9 | | pub disk_usage: AtomicU64, |
10 | | |
11 | | // Operation counters |
12 | | pub total_gets: AtomicU64, |
13 | | pub total_inserts: AtomicU64, |
14 | | pub total_updates: AtomicU64, |
15 | | pub total_deletes: AtomicU64, |
16 | | pub total_range_queries: AtomicU64, |
17 | | |
18 | | // Operation latencies (in nanoseconds) |
19 | | pub get_latency_ns: AtomicU64, |
20 | | pub insert_latency_ns: AtomicU64, |
21 | | pub delete_latency_ns: AtomicU64, |
22 | | |
23 | | // Cache metrics |
24 | | pub cache_hits: AtomicU64, |
25 | | pub cache_misses: AtomicU64, |
26 | | pub cache_evictions: AtomicU64, |
27 | | pub cache_memory: AtomicUsize, |
28 | | |
29 | | // Write buffer metrics |
30 | | pub writes_buffered: AtomicU64, |
31 | | pub writes_flushed: AtomicU64, |
32 | | pub write_failures: AtomicU64, |
33 | | pub flush_count: AtomicU64, |
34 | | |
35 | | // Disk I/O metrics |
36 | | pub disk_reads: AtomicU64, |
37 | | pub disk_writes: AtomicU64, |
38 | | pub disk_bytes_read: AtomicU64, |
39 | | pub disk_bytes_written: AtomicU64, |
40 | | |
41 | | // Error counters |
42 | | pub key_not_found_errors: AtomicU64, |
43 | | pub out_of_memory_errors: AtomicU64, |
44 | | pub io_errors: AtomicU64, |
45 | | |
46 | | // TTL metrics |
47 | | pub ttl_expired_lazy: AtomicU64, // Keys expired during GET |
48 | | pub ttl_expired_active: AtomicU64, // Keys expired by cleaner |
49 | | pub ttl_cleaner_runs: AtomicU64, // Cleaner iterations |
50 | | pub keys_with_ttl: AtomicU64, // Approximate count |
51 | | } |
52 | | |
53 | | impl Statistics { |
54 | 105 | pub fn new() -> Self { |
55 | 105 | Self { |
56 | 105 | // Store metrics |
57 | 105 | record_count: AtomicU32::new(0), |
58 | 105 | memory_usage: AtomicUsize::new(0), |
59 | 105 | disk_usage: AtomicU64::new(0), |
60 | 105 | |
61 | 105 | // Operation counters |
62 | 105 | total_gets: AtomicU64::new(0), |
63 | 105 | total_inserts: AtomicU64::new(0), |
64 | 105 | total_updates: AtomicU64::new(0), |
65 | 105 | total_deletes: AtomicU64::new(0), |
66 | 105 | total_range_queries: AtomicU64::new(0), |
67 | 105 | |
68 | 105 | // Operation latencies |
69 | 105 | get_latency_ns: AtomicU64::new(0), |
70 | 105 | insert_latency_ns: AtomicU64::new(0), |
71 | 105 | delete_latency_ns: AtomicU64::new(0), |
72 | 105 | |
73 | 105 | // Cache metrics |
74 | 105 | cache_hits: AtomicU64::new(0), |
75 | 105 | cache_misses: AtomicU64::new(0), |
76 | 105 | cache_evictions: AtomicU64::new(0), |
77 | 105 | cache_memory: AtomicUsize::new(0), |
78 | 105 | |
79 | 105 | // Write buffer metrics |
80 | 105 | writes_buffered: AtomicU64::new(0), |
81 | 105 | writes_flushed: AtomicU64::new(0), |
82 | 105 | write_failures: AtomicU64::new(0), |
83 | 105 | flush_count: AtomicU64::new(0), |
84 | 105 | |
85 | 105 | // Disk I/O metrics |
86 | 105 | disk_reads: AtomicU64::new(0), |
87 | 105 | disk_writes: AtomicU64::new(0), |
88 | 105 | disk_bytes_read: AtomicU64::new(0), |
89 | 105 | disk_bytes_written: AtomicU64::new(0), |
90 | 105 | |
91 | 105 | // Error counters |
92 | 105 | key_not_found_errors: AtomicU64::new(0), |
93 | 105 | out_of_memory_errors: AtomicU64::new(0), |
94 | 105 | io_errors: AtomicU64::new(0), |
95 | 105 | |
96 | 105 | // TTL metrics |
97 | 105 | ttl_expired_lazy: AtomicU64::new(0), |
98 | 105 | ttl_expired_active: AtomicU64::new(0), |
99 | 105 | ttl_cleaner_runs: AtomicU64::new(0), |
100 | 105 | keys_with_ttl: AtomicU64::new(0), |
101 | 105 | } |
102 | 105 | } |
103 | | |
104 | | /// Record a get operation |
105 | 3.52k | pub fn record_get(&self, latency_ns: u64, hit: bool) { |
106 | 3.52k | self.total_gets.fetch_add(1, Ordering::Relaxed); |
107 | 3.52k | self.get_latency_ns.fetch_add(latency_ns, Ordering::Relaxed); |
108 | | |
109 | 3.52k | if hit { |
110 | 1.01k | self.cache_hits.fetch_add(1, Ordering::Relaxed); |
111 | 2.51k | } else { |
112 | 2.51k | self.cache_misses.fetch_add(1, Ordering::Relaxed); |
113 | 2.51k | } |
114 | 3.52k | } |
115 | | |
116 | | /// Record an insert operation |
117 | 3.70k | pub fn record_insert(&self, latency_ns: u64, is_update: bool) { |
118 | 3.70k | if is_update { |
119 | 1.10k | self.total_updates.fetch_add(1, Ordering::Relaxed); |
120 | 2.60k | } else { |
121 | 2.60k | self.total_inserts.fetch_add(1, Ordering::Relaxed); |
122 | 2.60k | } |
123 | 3.70k | self.insert_latency_ns |
124 | 3.70k | .fetch_add(latency_ns, Ordering::Relaxed); |
125 | 3.70k | } |
126 | | |
127 | | /// Record a delete operation |
128 | 1.03k | pub fn record_delete(&self, latency_ns: u64) { |
129 | 1.03k | self.total_deletes.fetch_add(1, Ordering::Relaxed); |
130 | 1.03k | self.delete_latency_ns |
131 | 1.03k | .fetch_add(latency_ns, Ordering::Relaxed); |
132 | 1.03k | } |
133 | | |
134 | | /// Record a range query |
135 | 0 | pub fn record_range_query(&self) { |
136 | 0 | self.total_range_queries.fetch_add(1, Ordering::Relaxed); |
137 | 0 | } |
138 | | |
139 | | /// Record cache eviction |
140 | 9.42k | pub fn record_eviction(&self, count: u64) { |
141 | 9.42k | self.cache_evictions.fetch_add(count, Ordering::Relaxed); |
142 | 9.42k | } |
143 | | |
144 | | /// Record write buffer operation |
145 | 21.5k | pub fn record_write_buffered(&self) { |
146 | 21.5k | self.writes_buffered.fetch_add(1, Ordering::Relaxed); |
147 | 21.5k | } |
148 | | |
149 | 19 | pub fn record_write_flushed(&self, count: u64) { |
150 | 19 | self.writes_flushed.fetch_add(count, Ordering::Relaxed); |
151 | 19 | } |
152 | | |
153 | 1 | pub fn record_write_failed(&self) { |
154 | 1 | self.write_failures.fetch_add(1, Ordering::Relaxed); |
155 | 1 | } |
156 | | |
157 | | /// Record disk I/O |
158 | 0 | pub fn record_disk_read(&self, bytes: u64) { |
159 | 0 | self.disk_reads.fetch_add(1, Ordering::Relaxed); |
160 | 0 | self.disk_bytes_read.fetch_add(bytes, Ordering::Relaxed); |
161 | 0 | } |
162 | | |
163 | 0 | pub fn record_disk_write(&self, bytes: u64) { |
164 | 0 | self.disk_writes.fetch_add(1, Ordering::Relaxed); |
165 | 0 | self.disk_bytes_written.fetch_add(bytes, Ordering::Relaxed); |
166 | 0 | } |
167 | | |
168 | | /// Record errors |
169 | 0 | pub fn record_error(&self, error: &crate::error::FeoxError) { |
170 | | use crate::error::FeoxError; |
171 | 0 | match error { |
172 | 0 | FeoxError::KeyNotFound => { |
173 | 0 | self.key_not_found_errors.fetch_add(1, Ordering::Relaxed); |
174 | 0 | } |
175 | 0 | FeoxError::OutOfMemory => { |
176 | 0 | self.out_of_memory_errors.fetch_add(1, Ordering::Relaxed); |
177 | 0 | } |
178 | 0 | FeoxError::IoError(_) => { |
179 | 0 | self.io_errors.fetch_add(1, Ordering::Relaxed); |
180 | 0 | } |
181 | 0 | _ => {} |
182 | | } |
183 | 0 | } |
184 | | |
185 | | /// Get a snapshot of current statistics |
186 | 6 | pub fn snapshot(&self) -> StatsSnapshot { |
187 | 6 | let total_ops = self.total_gets.load(Ordering::Relaxed) |
188 | 6 | + self.total_inserts.load(Ordering::Relaxed) |
189 | 6 | + self.total_updates.load(Ordering::Relaxed) |
190 | 6 | + self.total_deletes.load(Ordering::Relaxed); |
191 | | |
192 | 6 | let avg_get_latency = if self.total_gets.load(Ordering::Relaxed) > 0 { |
193 | 2 | self.get_latency_ns.load(Ordering::Relaxed) / self.total_gets.load(Ordering::Relaxed) |
194 | | } else { |
195 | 4 | 0 |
196 | | }; |
197 | | |
198 | 6 | let avg_insert_latency = { |
199 | 6 | let inserts = self.total_inserts.load(Ordering::Relaxed) |
200 | 6 | + self.total_updates.load(Ordering::Relaxed); |
201 | 6 | if inserts > 0 { |
202 | 3 | self.insert_latency_ns.load(Ordering::Relaxed) / inserts |
203 | | } else { |
204 | 3 | 0 |
205 | | } |
206 | | }; |
207 | | |
208 | 6 | let avg_delete_latency = if self.total_deletes.load(Ordering::Relaxed) > 0 { |
209 | 1 | self.delete_latency_ns.load(Ordering::Relaxed) |
210 | 1 | / self.total_deletes.load(Ordering::Relaxed) |
211 | | } else { |
212 | 5 | 0 |
213 | | }; |
214 | | |
215 | 6 | let cache_hit_rate = { |
216 | 6 | let total_cache_ops = |
217 | 6 | self.cache_hits.load(Ordering::Relaxed) + self.cache_misses.load(Ordering::Relaxed); |
218 | 6 | if total_cache_ops > 0 { |
219 | 3 | (self.cache_hits.load(Ordering::Relaxed) as f64 / total_cache_ops as f64) * 100.0 |
220 | | } else { |
221 | 3 | 0.0 |
222 | | } |
223 | | }; |
224 | | |
225 | 6 | StatsSnapshot { |
226 | 6 | record_count: self.record_count.load(Ordering::Relaxed), |
227 | 6 | memory_usage: self.memory_usage.load(Ordering::Relaxed), |
228 | 6 | total_operations: total_ops, |
229 | 6 | total_gets: self.total_gets.load(Ordering::Relaxed), |
230 | 6 | total_inserts: self.total_inserts.load(Ordering::Relaxed), |
231 | 6 | total_updates: self.total_updates.load(Ordering::Relaxed), |
232 | 6 | total_deletes: self.total_deletes.load(Ordering::Relaxed), |
233 | 6 | total_range_queries: self.total_range_queries.load(Ordering::Relaxed), |
234 | 6 | avg_get_latency_ns: avg_get_latency, |
235 | 6 | avg_insert_latency_ns: avg_insert_latency, |
236 | 6 | avg_delete_latency_ns: avg_delete_latency, |
237 | 6 | cache_hits: self.cache_hits.load(Ordering::Relaxed), |
238 | 6 | cache_misses: self.cache_misses.load(Ordering::Relaxed), |
239 | 6 | cache_hit_rate, |
240 | 6 | cache_evictions: self.cache_evictions.load(Ordering::Relaxed), |
241 | 6 | cache_memory: self.cache_memory.load(Ordering::Relaxed), |
242 | 6 | writes_buffered: self.writes_buffered.load(Ordering::Relaxed), |
243 | 6 | writes_flushed: self.writes_flushed.load(Ordering::Relaxed), |
244 | 6 | write_failures: self.write_failures.load(Ordering::Relaxed), |
245 | 6 | flush_count: self.flush_count.load(Ordering::Relaxed), |
246 | 6 | disk_reads: self.disk_reads.load(Ordering::Relaxed), |
247 | 6 | disk_writes: self.disk_writes.load(Ordering::Relaxed), |
248 | 6 | disk_bytes_read: self.disk_bytes_read.load(Ordering::Relaxed), |
249 | 6 | disk_bytes_written: self.disk_bytes_written.load(Ordering::Relaxed), |
250 | 6 | key_not_found_errors: self.key_not_found_errors.load(Ordering::Relaxed), |
251 | 6 | out_of_memory_errors: self.out_of_memory_errors.load(Ordering::Relaxed), |
252 | 6 | io_errors: self.io_errors.load(Ordering::Relaxed), |
253 | 6 | } |
254 | 6 | } |
255 | | |
256 | | /// Reset all statistics |
257 | 0 | pub fn reset(&self) { |
258 | 0 | self.total_gets.store(0, Ordering::Relaxed); |
259 | 0 | self.total_inserts.store(0, Ordering::Relaxed); |
260 | 0 | self.total_updates.store(0, Ordering::Relaxed); |
261 | 0 | self.total_deletes.store(0, Ordering::Relaxed); |
262 | 0 | self.total_range_queries.store(0, Ordering::Relaxed); |
263 | 0 | self.get_latency_ns.store(0, Ordering::Relaxed); |
264 | 0 | self.insert_latency_ns.store(0, Ordering::Relaxed); |
265 | 0 | self.delete_latency_ns.store(0, Ordering::Relaxed); |
266 | 0 | self.cache_hits.store(0, Ordering::Relaxed); |
267 | 0 | self.cache_misses.store(0, Ordering::Relaxed); |
268 | 0 | self.cache_evictions.store(0, Ordering::Relaxed); |
269 | 0 | self.writes_buffered.store(0, Ordering::Relaxed); |
270 | 0 | self.writes_flushed.store(0, Ordering::Relaxed); |
271 | 0 | self.write_failures.store(0, Ordering::Relaxed); |
272 | 0 | self.flush_count.store(0, Ordering::Relaxed); |
273 | 0 | self.disk_reads.store(0, Ordering::Relaxed); |
274 | 0 | self.disk_writes.store(0, Ordering::Relaxed); |
275 | 0 | self.disk_bytes_read.store(0, Ordering::Relaxed); |
276 | 0 | self.disk_bytes_written.store(0, Ordering::Relaxed); |
277 | 0 | self.key_not_found_errors.store(0, Ordering::Relaxed); |
278 | 0 | self.out_of_memory_errors.store(0, Ordering::Relaxed); |
279 | 0 | self.io_errors.store(0, Ordering::Relaxed); |
280 | 0 | } |
281 | | } |
282 | | |
283 | | impl Default for Statistics { |
284 | 0 | fn default() -> Self { |
285 | 0 | Self::new() |
286 | 0 | } |
287 | | } |
288 | | |
289 | | /// Snapshot of statistics at a point in time |
290 | | #[derive(Debug, Clone)] |
291 | | pub struct StatsSnapshot { |
292 | | // Store metrics |
293 | | pub record_count: u32, |
294 | | pub memory_usage: usize, |
295 | | |
296 | | // Operations |
297 | | pub total_operations: u64, |
298 | | pub total_gets: u64, |
299 | | pub total_inserts: u64, |
300 | | pub total_updates: u64, |
301 | | pub total_deletes: u64, |
302 | | pub total_range_queries: u64, |
303 | | |
304 | | // Latencies (nanoseconds) |
305 | | pub avg_get_latency_ns: u64, |
306 | | pub avg_insert_latency_ns: u64, |
307 | | pub avg_delete_latency_ns: u64, |
308 | | |
309 | | // Cache |
310 | | pub cache_hits: u64, |
311 | | pub cache_misses: u64, |
312 | | pub cache_hit_rate: f64, |
313 | | pub cache_evictions: u64, |
314 | | pub cache_memory: usize, |
315 | | |
316 | | // Write buffer |
317 | | pub writes_buffered: u64, |
318 | | pub writes_flushed: u64, |
319 | | pub write_failures: u64, |
320 | | pub flush_count: u64, |
321 | | |
322 | | // Disk I/O |
323 | | pub disk_reads: u64, |
324 | | pub disk_writes: u64, |
325 | | pub disk_bytes_read: u64, |
326 | | pub disk_bytes_written: u64, |
327 | | |
328 | | // Errors |
329 | | pub key_not_found_errors: u64, |
330 | | pub out_of_memory_errors: u64, |
331 | | pub io_errors: u64, |
332 | | } |
333 | | |
334 | | impl StatsSnapshot { |
335 | | /// Format statistics as a human-readable string |
336 | 0 | pub fn format(&self) -> String { |
337 | 0 | format!( |
338 | 0 | "=== FeOxDB Statistics ===\n\ |
339 | 0 | Store:\n\ |
340 | 0 | - Records: {}\n\ |
341 | 0 | - Memory: {:.2} MB\n\n\ |
342 | 0 | Operations:\n\ |
343 | 0 | - Total: {}\n\ |
344 | 0 | - Gets: {} (avg latency: {:.2}μs)\n\ |
345 | 0 | - Inserts: {} (avg latency: {:.2}μs)\n\ |
346 | 0 | - Updates: {}\n\ |
347 | 0 | - Deletes: {} (avg latency: {:.2}μs)\n\ |
348 | 0 | - Range Queries: {}\n\n\ |
349 | 0 | Cache:\n\ |
350 | 0 | - Hit Rate: {:.1}%\n\ |
351 | 0 | - Hits: {}\n\ |
352 | 0 | - Misses: {}\n\ |
353 | 0 | - Evictions: {}\n\ |
354 | 0 | - Memory: {:.2} MB\n\n\ |
355 | 0 | Write Buffer:\n\ |
356 | 0 | - Buffered: {}\n\ |
357 | 0 | - Flushed: {}\n\ |
358 | 0 | - Failures: {}\n\ |
359 | 0 | - Flush Count: {}\n\n\ |
360 | 0 | Disk I/O:\n\ |
361 | 0 | - Reads: {} ({:.2} MB)\n\ |
362 | 0 | - Writes: {} ({:.2} MB)\n\n\ |
363 | 0 | Errors:\n\ |
364 | 0 | - Key Not Found: {}\n\ |
365 | 0 | - Out of Memory: {}\n\ |
366 | 0 | - I/O Errors: {}", |
367 | | self.record_count, |
368 | 0 | self.memory_usage as f64 / 1_048_576.0, |
369 | | self.total_operations, |
370 | | self.total_gets, |
371 | 0 | self.avg_get_latency_ns as f64 / 1000.0, |
372 | | self.total_inserts, |
373 | 0 | self.avg_insert_latency_ns as f64 / 1000.0, |
374 | | self.total_updates, |
375 | | self.total_deletes, |
376 | 0 | self.avg_delete_latency_ns as f64 / 1000.0, |
377 | | self.total_range_queries, |
378 | | self.cache_hit_rate, |
379 | | self.cache_hits, |
380 | | self.cache_misses, |
381 | | self.cache_evictions, |
382 | 0 | self.cache_memory as f64 / 1_048_576.0, |
383 | | self.writes_buffered, |
384 | | self.writes_flushed, |
385 | | self.write_failures, |
386 | | self.flush_count, |
387 | | self.disk_reads, |
388 | 0 | self.disk_bytes_read as f64 / 1_048_576.0, |
389 | | self.disk_writes, |
390 | 0 | self.disk_bytes_written as f64 / 1_048_576.0, |
391 | | self.key_not_found_errors, |
392 | | self.out_of_memory_errors, |
393 | | self.io_errors |
394 | | ) |
395 | 0 | } |
396 | | } |