Coverage Report

Created: 2026-01-04 13:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}