1pub const BYTES_PER_MEGABYTE: u64 = 1024 * 1024;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub struct CacheStats {
12 pub total_entries: usize,
13 pub expired_entries: usize,
14 pub valid_entries: usize,
15 pub cache_size_mb: u64,
16}
17
18impl CacheStats {
19 #[must_use]
27 pub fn from_raw_counts(
28 total_entries: i64,
29 expired_entries: i64,
30 cache_size_bytes: i64,
31 ) -> Self {
32 let total_raw = total_entries.max(0) as u64;
33 let expired_raw = expired_entries.max(0) as u64;
34 let clamped_expired_raw = expired_raw.min(total_raw);
35
36 let total_entries = clamp_u64_to_usize(total_raw);
37 let expired_entries = clamp_u64_to_usize(clamped_expired_raw).min(total_entries);
38 let valid_entries = total_entries.saturating_sub(expired_entries);
39 let cache_size_mb = (cache_size_bytes.max(0) as u64) / BYTES_PER_MEGABYTE;
40
41 Self {
42 total_entries,
43 expired_entries,
44 valid_entries,
45 cache_size_mb,
46 }
47 }
48
49 #[must_use]
51 pub fn is_empty(&self) -> bool {
52 self.total_entries == 0
53 }
54}
55
56fn clamp_u64_to_usize(value: u64) -> usize {
57 let max = usize::MAX as u64;
58 value.min(max) as usize
59}
60
61#[cfg(test)]
62mod tests {
63 use super::{BYTES_PER_MEGABYTE, CacheStats};
64
65 #[test]
66 fn from_raw_counts_maps_normal_values() {
67 let stats = CacheStats::from_raw_counts(9, 2, 5_242_880);
68 assert_eq!(stats.total_entries, 9);
69 assert_eq!(stats.expired_entries, 2);
70 assert_eq!(stats.valid_entries, 7);
71 assert_eq!(stats.cache_size_mb, 5);
72 }
73
74 #[test]
75 fn from_raw_counts_clamps_negative_values() {
76 let stats = CacheStats::from_raw_counts(-10, -3, -99);
77 assert_eq!(stats.total_entries, 0);
78 assert_eq!(stats.expired_entries, 0);
79 assert_eq!(stats.valid_entries, 0);
80 assert_eq!(stats.cache_size_mb, 0);
81 }
82
83 #[test]
84 fn from_raw_counts_clamps_expired_entries_to_total() {
85 let stats = CacheStats::from_raw_counts(3, 99, 0);
86 assert_eq!(stats.total_entries, 3);
87 assert_eq!(stats.expired_entries, 3);
88 assert_eq!(stats.valid_entries, 0);
89 }
90
91 #[test]
92 fn from_raw_counts_rounds_down_megabytes() {
93 let stats = CacheStats::from_raw_counts(1, 0, (BYTES_PER_MEGABYTE * 2 + 123) as i64);
94 assert_eq!(stats.cache_size_mb, 2);
95 }
96
97 #[test]
98 fn is_empty_is_driven_by_total_entries() {
99 assert!(CacheStats::from_raw_counts(0, 0, 0).is_empty());
100 assert!(!CacheStats::from_raw_counts(1, 0, 0).is_empty());
101 }
102}