1use noxu_cleaner::CleanerStatsSnapshot;
4use noxu_dbi::ThroughputStatsSnapshot;
5use noxu_evictor::EvictorStats;
6use noxu_log::LogManagerStats;
7use noxu_recovery::CheckpointStatsSnapshot;
8use noxu_txn::{LockStats, TxnStats};
9use std::sync::atomic::Ordering;
10
11#[derive(Debug, Clone)]
18pub struct EnvironmentStats {
19 pub cache_size: u64,
22 pub cache_usage: u64,
24
25 pub n_databases: u32,
28
29 pub evictor: EvictorStatsSnapshot,
31
32 pub log: LogStatsSnapshot,
34
35 pub lock: LockStatsSnapshot,
37
38 pub txn: TxnStatsSnapshot,
40
41 pub cleaner: CleanerStatsSnapshot,
43
44 pub checkpoint: CheckpointStatsSnapshot,
46
47 pub throughput: ThroughputStatsSnapshot,
49}
50
51impl EnvironmentStats {
52 pub fn new() -> Self {
53 Self::default()
54 }
55
56 pub fn cache_utilization_percent(&self) -> f64 {
59 if self.cache_size == 0 {
60 0.0
61 } else {
62 (self.cache_usage as f64 / self.cache_size as f64) * 100.0
63 }
64 }
65
66 pub fn is_cache_over_budget(&self) -> bool {
67 self.cache_usage > self.cache_size
68 }
69
70 pub fn total_eviction_runs(&self) -> u64 {
71 self.evictor.eviction_runs
72 }
73
74 pub fn total_nodes_evicted(&self) -> u64 {
75 self.evictor.nodes_evicted
76 }
77
78 pub fn total_bytes_evicted(&self) -> u64 {
79 self.evictor.bytes_evicted
80 }
81
82 pub fn total_cleaning_runs(&self) -> u64 {
83 self.cleaner.runs
84 }
85
86 pub fn total_files_cleaned(&self) -> u64 {
87 self.cleaner.deletions
88 }
89
90 pub fn total_checkpoint_runs(&self) -> u64 {
91 self.checkpoint.checkpoints
92 }
93
94 pub fn total_full_ins_checkpointed(&self) -> u64 {
95 self.checkpoint.full_in_flush
96 }
97
98 pub fn bin_fetch_miss_ratio(&self) -> f64 {
100 let total = self.evictor.bin_fetch;
101 if total == 0 {
102 0.0
103 } else {
104 self.evictor.bin_fetch_miss as f64 / total as f64
105 }
106 }
107}
108
109impl Default for EnvironmentStats {
110 fn default() -> Self {
111 Self {
112 cache_size: 0,
113 cache_usage: 0,
114 n_databases: 0,
115 evictor: EvictorStatsSnapshot::default(),
116 log: LogStatsSnapshot::default(),
117 lock: LockStatsSnapshot::default(),
118 txn: TxnStatsSnapshot::default(),
119 cleaner: CleanerStatsSnapshot {
120 runs: 0,
121 two_pass_runs: 0,
122 revisal_runs: 0,
123 deletions: 0,
124 entries_read: 0,
125 disk_reads: 0,
126 ins_cleaned: 0,
127 ins_dead: 0,
128 ins_migrated: 0,
129 ins_obsolete: 0,
130 lns_cleaned: 0,
131 lns_dead: 0,
132 lns_migrated: 0,
133 lns_obsolete: 0,
134 lns_locked: 0,
135 lns_marked: 0,
136 lns_expired: 0,
137 lnqueue_hits: 0,
138 pending_lns_processed: 0,
139 pending_lns_locked: 0,
140 cluster_lns_processed: 0,
141 marked_lns_processed: 0,
142 to_be_cleaned_lns_processed: 0,
143 bin_deltas_cleaned: 0,
144 bin_deltas_dead: 0,
145 bin_deltas_migrated: 0,
146 bin_deltas_obsolete: 0,
147 pending_ln_queue_size: 0,
148 total_log_size: 0,
149 active_log_size: 0,
150 reserved_log_size: 0,
151 protected_log_size: 0,
152 available_log_size: 0,
153 min_utilization: 0,
154 max_utilization: 0,
155 probe_runs: 0,
156 repeat_iterator_reads: 0,
157 },
158 checkpoint: CheckpointStatsSnapshot {
159 checkpoints: 0,
160 full_in_flush: 0,
161 full_bin_flush: 0,
162 delta_in_flush: 0,
163 last_ckpt_id: 0,
164 last_ckpt_start: 0,
165 last_ckpt_end: 0,
166 last_ckpt_interval: 0,
167 },
168 throughput: ThroughputStatsSnapshot::default(),
169 }
170 }
171}
172
173#[derive(Debug, Clone, Default)]
181pub struct EvictorStatsSnapshot {
182 pub eviction_runs: u64,
184 pub nodes_targeted: u64,
185 pub nodes_evicted: u64,
186 pub nodes_skipped: u64,
187 pub nodes_mutated: u64,
188 pub nodes_stripped: u64,
189 pub nodes_put_back: u64,
190 pub nodes_moved_to_pri2_lru: u64,
191 pub root_nodes_evicted: u64,
192 pub dirty_nodes_evicted: u64,
193 pub lns_evicted: u64,
194
195 pub bytes_evicted_daemon: u64,
197 pub bytes_evicted_critical: u64,
198 pub bytes_evicted_manual: u64,
199 pub bytes_evicted_cachemode: u64,
200 pub bytes_evicted: u64,
202
203 pub bin_fetch: u64,
205 pub bin_fetch_miss: u64,
206 pub ln_fetch: u64,
207 pub ln_fetch_miss: u64,
208 pub upper_in_fetch: u64,
209 pub upper_in_fetch_miss: u64,
210 pub bin_delta_fetch_miss: u64,
211 pub full_bin_miss: u64,
212 pub bin_delta_blind_ops: u64,
213
214 pub pri1_lru_size: u64,
216 pub pri2_lru_size: u64,
217 pub lru_size: u64,
219
220 pub thread_unavailable: u64,
223
224 pub cached_upper_ins: u64,
227 pub cached_bins: u64,
229 pub cached_bin_deltas: u64,
231 pub cached_in_sparse_target: u64,
233 pub cached_in_no_target: u64,
235 pub cached_in_compact_key: u64,
237}
238
239impl From<&EvictorStats> for EvictorStatsSnapshot {
240 fn from(stats: &EvictorStats) -> Self {
241 let d = stats.bytes_evicted_daemon.load(Ordering::Relaxed);
242 let c = stats.bytes_evicted_critical.load(Ordering::Relaxed);
243 let m = stats.bytes_evicted_manual.load(Ordering::Relaxed);
244 let cm = stats.bytes_evicted_cachemode.load(Ordering::Relaxed);
245 let p1 = stats.pri1_lru_size.load(Ordering::Relaxed);
246 let p2 = stats.pri2_lru_size.load(Ordering::Relaxed);
247 Self {
248 eviction_runs: stats.eviction_runs.load(Ordering::Relaxed),
249 nodes_targeted: stats.nodes_targeted.load(Ordering::Relaxed),
250 nodes_evicted: stats.nodes_evicted.load(Ordering::Relaxed),
251 nodes_skipped: stats.nodes_skipped.load(Ordering::Relaxed),
252 nodes_mutated: stats.nodes_mutated.load(Ordering::Relaxed),
253 nodes_stripped: stats.nodes_stripped.load(Ordering::Relaxed),
254 nodes_put_back: stats.nodes_put_back.load(Ordering::Relaxed),
255 nodes_moved_to_pri2_lru: stats
256 .nodes_moved_to_pri2_lru
257 .load(Ordering::Relaxed),
258 root_nodes_evicted: stats
259 .root_nodes_evicted
260 .load(Ordering::Relaxed),
261 dirty_nodes_evicted: stats
262 .dirty_nodes_evicted
263 .load(Ordering::Relaxed),
264 lns_evicted: stats.lns_evicted.load(Ordering::Relaxed),
265 bytes_evicted_daemon: d,
266 bytes_evicted_critical: c,
267 bytes_evicted_manual: m,
268 bytes_evicted_cachemode: cm,
269 bytes_evicted: d + c + m + cm,
270 bin_fetch: stats.bin_fetch.load(Ordering::Relaxed),
271 bin_fetch_miss: stats.bin_fetch_miss.load(Ordering::Relaxed),
272 ln_fetch: stats.ln_fetch.load(Ordering::Relaxed),
273 ln_fetch_miss: stats.ln_fetch_miss.load(Ordering::Relaxed),
274 upper_in_fetch: stats.upper_in_fetch.load(Ordering::Relaxed),
275 upper_in_fetch_miss: stats
276 .upper_in_fetch_miss
277 .load(Ordering::Relaxed),
278 bin_delta_fetch_miss: stats
279 .bin_delta_fetch_miss
280 .load(Ordering::Relaxed),
281 full_bin_miss: stats.full_bin_miss.load(Ordering::Relaxed),
282 bin_delta_blind_ops: stats
283 .bin_delta_blind_ops
284 .load(Ordering::Relaxed),
285 pri1_lru_size: p1,
286 pri2_lru_size: p2,
287 lru_size: p1 + p2,
288 thread_unavailable: stats
289 .thread_unavailable
290 .load(Ordering::Relaxed),
291 cached_upper_ins: stats.cached_upper_ins.load(Ordering::Relaxed),
292 cached_bins: stats.cached_bins.load(Ordering::Relaxed),
293 cached_bin_deltas: stats.cached_bin_deltas.load(Ordering::Relaxed),
294 cached_in_sparse_target: stats
295 .cached_in_sparse_target
296 .load(Ordering::Relaxed),
297 cached_in_no_target: stats
298 .cached_in_no_target
299 .load(Ordering::Relaxed),
300 cached_in_compact_key: stats
301 .cached_in_compact_key
302 .load(Ordering::Relaxed),
303 }
304 }
305}
306
307#[derive(Debug, Clone, Default)]
315pub struct LogStatsSnapshot {
316 pub end_of_log: u64,
318 pub last_flush_lsn: u64,
320 pub n_repeat_fault_reads: u64,
322 pub n_temp_buffer_writes: u64,
324 pub n_log_buffers: u64,
326 pub n_log_buffer_bytes: u64,
328 pub n_log_fsyncs: u64,
330 pub n_fsync_requests: u64,
332 pub n_file_opens: u64,
334 pub n_sequential_reads: u64,
336 pub n_sequential_read_bytes: u64,
338 pub n_sequential_writes: u64,
340 pub n_sequential_write_bytes: u64,
342 pub n_random_reads: u64,
344 pub n_random_read_bytes: u64,
346 pub n_fsync_timeouts: u64,
348 pub n_group_commits: u64,
350 pub fsync_time_ms: u64,
352 pub n_fsync_batch_size_sum: u64,
354}
355
356impl From<&LogManagerStats> for LogStatsSnapshot {
357 fn from(s: &LogManagerStats) -> Self {
358 Self {
359 end_of_log: s.end_of_log.as_u64(),
360 last_flush_lsn: s.last_flush_lsn.as_u64(),
361 n_repeat_fault_reads: s.n_repeat_fault_reads,
362 n_temp_buffer_writes: s.n_temp_buffer_writes,
363 n_log_buffers: s.buffer_pool_stats.num_buffers as u64,
364 n_log_buffer_bytes: (s.buffer_pool_stats.num_buffers as u64)
365 * (s.buffer_pool_stats.buffer_size as u64),
366 n_log_fsyncs: s.n_log_fsyncs,
367 n_fsync_requests: s.n_fsync_requests,
368 n_file_opens: s.n_file_opens,
369 n_sequential_reads: s.n_sequential_reads,
370 n_sequential_read_bytes: s.n_sequential_read_bytes,
371 n_sequential_writes: s.n_sequential_writes,
372 n_sequential_write_bytes: s.n_sequential_write_bytes,
373 n_random_reads: s.n_random_reads,
374 n_random_read_bytes: s.n_random_read_bytes,
375 n_fsync_timeouts: s.n_fsync_timeouts,
376 n_group_commits: s.n_group_commits,
377 fsync_time_ms: s.fsync_time_ms,
378 n_fsync_batch_size_sum: s.n_fsync_batch_size_sum,
379 }
380 }
381}
382
383#[derive(Debug, Clone, Default)]
391pub struct LockStatsSnapshot {
392 pub n_requests: u64,
394 pub n_waits: u64,
396 pub n_total_locks: u64,
398 pub n_read_locks: u64,
400 pub n_write_locks: u64,
402 pub n_owners: u64,
404 pub n_waiters: u64,
406 pub n_lock_tables: u64,
408 pub n_lock_timeouts: u64,
410}
411
412impl From<&LockStats> for LockStatsSnapshot {
413 fn from(s: &LockStats) -> Self {
414 Self {
415 n_requests: s.lock_requests,
416 n_waits: s.lock_waits,
417 n_total_locks: s.n_total_locks,
418 n_read_locks: s.n_read_locks,
419 n_write_locks: s.n_write_locks,
420 n_owners: s.n_owners,
421 n_waiters: s.n_waiters,
422 n_lock_tables: 0, n_lock_timeouts: s.n_lock_timeouts,
424 }
425 }
426}
427
428#[derive(Debug, Clone, Default)]
434pub struct TxnStatsSnapshot {
435 pub n_begins: u64,
436 pub n_commits: u64,
437 pub n_aborts: u64,
438 pub n_active: u64,
439}
440
441impl From<&TxnStats> for TxnStatsSnapshot {
442 fn from(s: &TxnStats) -> Self {
443 Self {
444 n_begins: s.n_begins,
445 n_commits: s.n_commits,
446 n_aborts: s.n_aborts,
447 n_active: s.n_active,
448 }
449 }
450}
451
452#[cfg(test)]
457mod tests {
458 use super::*;
459
460 #[test]
461 fn test_new_stats() {
462 let stats = EnvironmentStats::new();
463 assert_eq!(stats.cache_size, 0);
464 assert_eq!(stats.cache_usage, 0);
465 assert_eq!(stats.n_databases, 0);
466 }
467
468 #[test]
469 fn test_cache_utilization() {
470 let mut stats = EnvironmentStats::new();
471 stats.cache_size = 1000;
472 stats.cache_usage = 500;
473 assert_eq!(stats.cache_utilization_percent(), 50.0);
474
475 stats.cache_usage = 1000;
476 assert_eq!(stats.cache_utilization_percent(), 100.0);
477
478 stats.cache_usage = 1200;
479 assert_eq!(stats.cache_utilization_percent(), 120.0);
480 }
481
482 #[test]
483 fn test_cache_utilization_zero_size() {
484 let mut stats = EnvironmentStats::new();
485 stats.cache_size = 0;
486 stats.cache_usage = 100;
487 assert_eq!(stats.cache_utilization_percent(), 0.0);
488 }
489
490 #[test]
491 fn test_is_cache_over_budget() {
492 let mut stats = EnvironmentStats::new();
493 stats.cache_size = 1000;
494 stats.cache_usage = 999;
495 assert!(!stats.is_cache_over_budget());
496 stats.cache_usage = 1001;
497 assert!(stats.is_cache_over_budget());
498 }
499
500 #[test]
501 fn test_evictor_aggregates() {
502 let mut stats = EnvironmentStats::new();
503 stats.evictor.eviction_runs = 10;
504 stats.evictor.nodes_evicted = 100;
505 stats.evictor.bytes_evicted = 10000;
506 assert_eq!(stats.total_eviction_runs(), 10);
507 assert_eq!(stats.total_nodes_evicted(), 100);
508 assert_eq!(stats.total_bytes_evicted(), 10000);
509 }
510
511 #[test]
512 fn test_cleaner_aggregates() {
513 let mut stats = EnvironmentStats::new();
514 stats.cleaner.runs = 5;
515 stats.cleaner.deletions = 20;
516 assert_eq!(stats.total_cleaning_runs(), 5);
517 assert_eq!(stats.total_files_cleaned(), 20);
518 }
519
520 #[test]
521 fn test_checkpoint_aggregates() {
522 let mut stats = EnvironmentStats::new();
523 stats.checkpoint.checkpoints = 3;
524 stats.checkpoint.full_in_flush = 50;
525 assert_eq!(stats.total_checkpoint_runs(), 3);
526 assert_eq!(stats.total_full_ins_checkpointed(), 50);
527 }
528
529 #[test]
530 fn test_log_stats_snapshot_default() {
531 let s = LogStatsSnapshot::default();
532 assert_eq!(s.n_log_fsyncs, 0);
533 assert_eq!(s.n_sequential_writes, 0);
534 }
535
536 #[test]
537 fn test_lock_stats_snapshot_default() {
538 let s = LockStatsSnapshot::default();
539 assert_eq!(s.n_requests, 0);
540 assert_eq!(s.n_total_locks, 0);
541 }
542
543 #[test]
544 fn test_txn_stats_snapshot_default() {
545 let s = TxnStatsSnapshot::default();
546 assert_eq!(s.n_begins, 0);
547 assert_eq!(s.n_active, 0);
548 }
549
550 #[test]
551 fn test_throughput_stats_snapshot_default() {
552 let s = ThroughputStatsSnapshot::default();
553 assert_eq!(s.n_pri_inserts, 0);
554 assert_eq!(s.n_pri_searches, 0);
555 }
556
557 #[test]
558 fn test_bin_fetch_miss_ratio_zero_denominator() {
559 let stats = EnvironmentStats::new();
560 assert_eq!(stats.bin_fetch_miss_ratio(), 0.0);
561 }
562
563 #[test]
564 fn test_bin_fetch_miss_ratio() {
565 let mut stats = EnvironmentStats::new();
566 stats.evictor.bin_fetch = 100;
567 stats.evictor.bin_fetch_miss = 25;
568 assert_eq!(stats.bin_fetch_miss_ratio(), 0.25);
569 }
570
571 #[test]
572 fn test_evictor_snapshot_all_fields() {
573 let snap = EvictorStatsSnapshot {
574 eviction_runs: 1,
575 nodes_targeted: 2,
576 nodes_evicted: 3,
577 nodes_skipped: 4,
578 nodes_mutated: 5,
579 nodes_stripped: 6,
580 nodes_put_back: 7,
581 nodes_moved_to_pri2_lru: 8,
582 root_nodes_evicted: 9,
583 dirty_nodes_evicted: 10,
584 lns_evicted: 11,
585 bytes_evicted_daemon: 100,
586 bytes_evicted_critical: 200,
587 bytes_evicted_manual: 300,
588 bytes_evicted_cachemode: 400,
589 bytes_evicted: 1000,
590 bin_fetch: 50,
591 bin_fetch_miss: 5,
592 ln_fetch: 40,
593 ln_fetch_miss: 4,
594 upper_in_fetch: 30,
595 upper_in_fetch_miss: 3,
596 bin_delta_fetch_miss: 2,
597 full_bin_miss: 1,
598 bin_delta_blind_ops: 20,
599 pri1_lru_size: 100,
600 pri2_lru_size: 200,
601 lru_size: 300,
602 thread_unavailable: 0,
603 cached_upper_ins: 10,
604 cached_bins: 20,
605 cached_bin_deltas: 5,
606 cached_in_sparse_target: 3,
607 cached_in_no_target: 2,
608 cached_in_compact_key: 1,
609 };
610 assert_eq!(snap.bytes_evicted, 1000);
611 assert_eq!(snap.lru_size, 300);
612 assert_eq!(snap.cached_bins, 20);
613 }
614
615 #[test]
616 fn test_default_stats() {
617 let stats = EnvironmentStats::default();
618 assert_eq!(stats.cache_size, 0);
619 assert_eq!(stats.n_databases, 0);
620 assert_eq!(stats.total_eviction_runs(), 0);
621 assert_eq!(stats.total_cleaning_runs(), 0);
622 assert_eq!(stats.total_checkpoint_runs(), 0);
623 }
624}