Skip to main content

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