feoxdb/
stats.rs

1use std::sync::atomic::{AtomicU32, AtomicU64, AtomicUsize, Ordering};
2
3/// Central statistics hub for FeoxStore
4#[derive(Debug)]
5pub 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
53impl Statistics {
54    pub fn new() -> Self {
55        Self {
56            // Store metrics
57            record_count: AtomicU32::new(0),
58            memory_usage: AtomicUsize::new(0),
59            disk_usage: AtomicU64::new(0),
60
61            // Operation counters
62            total_gets: AtomicU64::new(0),
63            total_inserts: AtomicU64::new(0),
64            total_updates: AtomicU64::new(0),
65            total_deletes: AtomicU64::new(0),
66            total_range_queries: AtomicU64::new(0),
67
68            // Operation latencies
69            get_latency_ns: AtomicU64::new(0),
70            insert_latency_ns: AtomicU64::new(0),
71            delete_latency_ns: AtomicU64::new(0),
72
73            // Cache metrics
74            cache_hits: AtomicU64::new(0),
75            cache_misses: AtomicU64::new(0),
76            cache_evictions: AtomicU64::new(0),
77            cache_memory: AtomicUsize::new(0),
78
79            // Write buffer metrics
80            writes_buffered: AtomicU64::new(0),
81            writes_flushed: AtomicU64::new(0),
82            write_failures: AtomicU64::new(0),
83            flush_count: AtomicU64::new(0),
84
85            // Disk I/O metrics
86            disk_reads: AtomicU64::new(0),
87            disk_writes: AtomicU64::new(0),
88            disk_bytes_read: AtomicU64::new(0),
89            disk_bytes_written: AtomicU64::new(0),
90
91            // Error counters
92            key_not_found_errors: AtomicU64::new(0),
93            out_of_memory_errors: AtomicU64::new(0),
94            io_errors: AtomicU64::new(0),
95
96            // TTL metrics
97            ttl_expired_lazy: AtomicU64::new(0),
98            ttl_expired_active: AtomicU64::new(0),
99            ttl_cleaner_runs: AtomicU64::new(0),
100            keys_with_ttl: AtomicU64::new(0),
101        }
102    }
103
104    /// Record a get operation
105    pub fn record_get(&self, latency_ns: u64, hit: bool) {
106        self.total_gets.fetch_add(1, Ordering::Relaxed);
107        self.get_latency_ns.fetch_add(latency_ns, Ordering::Relaxed);
108
109        if hit {
110            self.cache_hits.fetch_add(1, Ordering::Relaxed);
111        } else {
112            self.cache_misses.fetch_add(1, Ordering::Relaxed);
113        }
114    }
115
116    /// Record an insert operation
117    pub fn record_insert(&self, latency_ns: u64, is_update: bool) {
118        if is_update {
119            self.total_updates.fetch_add(1, Ordering::Relaxed);
120        } else {
121            self.total_inserts.fetch_add(1, Ordering::Relaxed);
122        }
123        self.insert_latency_ns
124            .fetch_add(latency_ns, Ordering::Relaxed);
125    }
126
127    /// Record a delete operation
128    pub fn record_delete(&self, latency_ns: u64) {
129        self.total_deletes.fetch_add(1, Ordering::Relaxed);
130        self.delete_latency_ns
131            .fetch_add(latency_ns, Ordering::Relaxed);
132    }
133
134    /// Record a range query
135    pub fn record_range_query(&self) {
136        self.total_range_queries.fetch_add(1, Ordering::Relaxed);
137    }
138
139    /// Record cache eviction
140    pub fn record_eviction(&self, count: u64) {
141        self.cache_evictions.fetch_add(count, Ordering::Relaxed);
142    }
143
144    /// Record write buffer operation
145    pub fn record_write_buffered(&self) {
146        self.writes_buffered.fetch_add(1, Ordering::Relaxed);
147    }
148
149    pub fn record_write_flushed(&self, count: u64) {
150        self.writes_flushed.fetch_add(count, Ordering::Relaxed);
151    }
152
153    pub fn record_write_failed(&self) {
154        self.write_failures.fetch_add(1, Ordering::Relaxed);
155    }
156
157    /// Record disk I/O
158    pub fn record_disk_read(&self, bytes: u64) {
159        self.disk_reads.fetch_add(1, Ordering::Relaxed);
160        self.disk_bytes_read.fetch_add(bytes, Ordering::Relaxed);
161    }
162
163    pub fn record_disk_write(&self, bytes: u64) {
164        self.disk_writes.fetch_add(1, Ordering::Relaxed);
165        self.disk_bytes_written.fetch_add(bytes, Ordering::Relaxed);
166    }
167
168    /// Record errors
169    pub fn record_error(&self, error: &crate::error::FeoxError) {
170        use crate::error::FeoxError;
171        match error {
172            FeoxError::KeyNotFound => {
173                self.key_not_found_errors.fetch_add(1, Ordering::Relaxed);
174            }
175            FeoxError::OutOfMemory => {
176                self.out_of_memory_errors.fetch_add(1, Ordering::Relaxed);
177            }
178            FeoxError::IoError(_) => {
179                self.io_errors.fetch_add(1, Ordering::Relaxed);
180            }
181            _ => {}
182        }
183    }
184
185    /// Get a snapshot of current statistics
186    pub fn snapshot(&self) -> StatsSnapshot {
187        let total_ops = self.total_gets.load(Ordering::Relaxed)
188            + self.total_inserts.load(Ordering::Relaxed)
189            + self.total_updates.load(Ordering::Relaxed)
190            + self.total_deletes.load(Ordering::Relaxed);
191
192        let avg_get_latency = if self.total_gets.load(Ordering::Relaxed) > 0 {
193            self.get_latency_ns.load(Ordering::Relaxed) / self.total_gets.load(Ordering::Relaxed)
194        } else {
195            0
196        };
197
198        let avg_insert_latency = {
199            let inserts = self.total_inserts.load(Ordering::Relaxed)
200                + self.total_updates.load(Ordering::Relaxed);
201            if inserts > 0 {
202                self.insert_latency_ns.load(Ordering::Relaxed) / inserts
203            } else {
204                0
205            }
206        };
207
208        let avg_delete_latency = if self.total_deletes.load(Ordering::Relaxed) > 0 {
209            self.delete_latency_ns.load(Ordering::Relaxed)
210                / self.total_deletes.load(Ordering::Relaxed)
211        } else {
212            0
213        };
214
215        let cache_hit_rate = {
216            let total_cache_ops =
217                self.cache_hits.load(Ordering::Relaxed) + self.cache_misses.load(Ordering::Relaxed);
218            if total_cache_ops > 0 {
219                (self.cache_hits.load(Ordering::Relaxed) as f64 / total_cache_ops as f64) * 100.0
220            } else {
221                0.0
222            }
223        };
224
225        StatsSnapshot {
226            record_count: self.record_count.load(Ordering::Relaxed),
227            memory_usage: self.memory_usage.load(Ordering::Relaxed),
228            total_operations: total_ops,
229            total_gets: self.total_gets.load(Ordering::Relaxed),
230            total_inserts: self.total_inserts.load(Ordering::Relaxed),
231            total_updates: self.total_updates.load(Ordering::Relaxed),
232            total_deletes: self.total_deletes.load(Ordering::Relaxed),
233            total_range_queries: self.total_range_queries.load(Ordering::Relaxed),
234            avg_get_latency_ns: avg_get_latency,
235            avg_insert_latency_ns: avg_insert_latency,
236            avg_delete_latency_ns: avg_delete_latency,
237            cache_hits: self.cache_hits.load(Ordering::Relaxed),
238            cache_misses: self.cache_misses.load(Ordering::Relaxed),
239            cache_hit_rate,
240            cache_evictions: self.cache_evictions.load(Ordering::Relaxed),
241            cache_memory: self.cache_memory.load(Ordering::Relaxed),
242            writes_buffered: self.writes_buffered.load(Ordering::Relaxed),
243            writes_flushed: self.writes_flushed.load(Ordering::Relaxed),
244            write_failures: self.write_failures.load(Ordering::Relaxed),
245            flush_count: self.flush_count.load(Ordering::Relaxed),
246            disk_reads: self.disk_reads.load(Ordering::Relaxed),
247            disk_writes: self.disk_writes.load(Ordering::Relaxed),
248            disk_bytes_read: self.disk_bytes_read.load(Ordering::Relaxed),
249            disk_bytes_written: self.disk_bytes_written.load(Ordering::Relaxed),
250            key_not_found_errors: self.key_not_found_errors.load(Ordering::Relaxed),
251            out_of_memory_errors: self.out_of_memory_errors.load(Ordering::Relaxed),
252            io_errors: self.io_errors.load(Ordering::Relaxed),
253        }
254    }
255
256    /// Reset all statistics
257    pub fn reset(&self) {
258        self.total_gets.store(0, Ordering::Relaxed);
259        self.total_inserts.store(0, Ordering::Relaxed);
260        self.total_updates.store(0, Ordering::Relaxed);
261        self.total_deletes.store(0, Ordering::Relaxed);
262        self.total_range_queries.store(0, Ordering::Relaxed);
263        self.get_latency_ns.store(0, Ordering::Relaxed);
264        self.insert_latency_ns.store(0, Ordering::Relaxed);
265        self.delete_latency_ns.store(0, Ordering::Relaxed);
266        self.cache_hits.store(0, Ordering::Relaxed);
267        self.cache_misses.store(0, Ordering::Relaxed);
268        self.cache_evictions.store(0, Ordering::Relaxed);
269        self.writes_buffered.store(0, Ordering::Relaxed);
270        self.writes_flushed.store(0, Ordering::Relaxed);
271        self.write_failures.store(0, Ordering::Relaxed);
272        self.flush_count.store(0, Ordering::Relaxed);
273        self.disk_reads.store(0, Ordering::Relaxed);
274        self.disk_writes.store(0, Ordering::Relaxed);
275        self.disk_bytes_read.store(0, Ordering::Relaxed);
276        self.disk_bytes_written.store(0, Ordering::Relaxed);
277        self.key_not_found_errors.store(0, Ordering::Relaxed);
278        self.out_of_memory_errors.store(0, Ordering::Relaxed);
279        self.io_errors.store(0, Ordering::Relaxed);
280    }
281}
282
283impl Default for Statistics {
284    fn default() -> Self {
285        Self::new()
286    }
287}
288
289/// Snapshot of statistics at a point in time
290#[derive(Debug, Clone)]
291pub 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
334impl StatsSnapshot {
335    /// Format statistics as a human-readable string
336    pub fn format(&self) -> String {
337        format!(
338            "=== FeOxDB Statistics ===\n\
339            Store:\n\
340            - Records: {}\n\
341            - Memory: {:.2} MB\n\n\
342            Operations:\n\
343            - Total: {}\n\
344            - Gets: {} (avg latency: {:.2}μs)\n\
345            - Inserts: {} (avg latency: {:.2}μs)\n\
346            - Updates: {}\n\
347            - Deletes: {} (avg latency: {:.2}μs)\n\
348            - Range Queries: {}\n\n\
349            Cache:\n\
350            - Hit Rate: {:.1}%\n\
351            - Hits: {}\n\
352            - Misses: {}\n\
353            - Evictions: {}\n\
354            - Memory: {:.2} MB\n\n\
355            Write Buffer:\n\
356            - Buffered: {}\n\
357            - Flushed: {}\n\
358            - Failures: {}\n\
359            - Flush Count: {}\n\n\
360            Disk I/O:\n\
361            - Reads: {} ({:.2} MB)\n\
362            - Writes: {} ({:.2} MB)\n\n\
363            Errors:\n\
364            - Key Not Found: {}\n\
365            - Out of Memory: {}\n\
366            - I/O Errors: {}",
367            self.record_count,
368            self.memory_usage as f64 / 1_048_576.0,
369            self.total_operations,
370            self.total_gets,
371            self.avg_get_latency_ns as f64 / 1000.0,
372            self.total_inserts,
373            self.avg_insert_latency_ns as f64 / 1000.0,
374            self.total_updates,
375            self.total_deletes,
376            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            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            self.disk_bytes_read as f64 / 1_048_576.0,
389            self.disk_writes,
390            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    }
396}