guts_storage/
compression.rs1use std::sync::atomic::{AtomicU64, Ordering};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
10pub enum CompressionLevel {
11 None,
13 Fast,
15 #[default]
17 Default,
18 Best,
20}
21
22impl CompressionLevel {
23 pub fn to_flate2(self) -> flate2::Compression {
25 match self {
26 CompressionLevel::None => flate2::Compression::none(),
27 CompressionLevel::Fast => flate2::Compression::fast(),
28 CompressionLevel::Default => flate2::Compression::default(),
29 CompressionLevel::Best => flate2::Compression::best(),
30 }
31 }
32}
33
34#[derive(Debug, Default)]
36pub struct CompressionStats {
37 pub input_bytes: AtomicU64,
39 pub output_bytes: AtomicU64,
41 pub compress_count: AtomicU64,
43 pub decompress_count: AtomicU64,
45}
46
47impl CompressionStats {
48 pub fn new() -> Self {
50 Self::default()
51 }
52
53 pub fn record_compress(&self, input_size: u64, output_size: u64) {
55 self.input_bytes.fetch_add(input_size, Ordering::Relaxed);
56 self.output_bytes.fetch_add(output_size, Ordering::Relaxed);
57 self.compress_count.fetch_add(1, Ordering::Relaxed);
58 }
59
60 pub fn record_decompress(&self) {
62 self.decompress_count.fetch_add(1, Ordering::Relaxed);
63 }
64
65 pub fn compression_ratio(&self) -> f64 {
67 let input = self.input_bytes.load(Ordering::Relaxed);
68 let output = self.output_bytes.load(Ordering::Relaxed);
69 if input == 0 {
70 1.0
71 } else {
72 output as f64 / input as f64
73 }
74 }
75
76 pub fn space_savings(&self) -> f64 {
78 1.0 - self.compression_ratio()
79 }
80
81 pub fn snapshot(&self) -> CompressionStatsSnapshot {
83 CompressionStatsSnapshot {
84 input_bytes: self.input_bytes.load(Ordering::Relaxed),
85 output_bytes: self.output_bytes.load(Ordering::Relaxed),
86 compress_count: self.compress_count.load(Ordering::Relaxed),
87 decompress_count: self.decompress_count.load(Ordering::Relaxed),
88 }
89 }
90}
91
92#[derive(Debug, Clone)]
94pub struct CompressionStatsSnapshot {
95 pub input_bytes: u64,
96 pub output_bytes: u64,
97 pub compress_count: u64,
98 pub decompress_count: u64,
99}
100
101impl CompressionStatsSnapshot {
102 pub fn compression_ratio(&self) -> f64 {
104 if self.input_bytes == 0 {
105 1.0
106 } else {
107 self.output_bytes as f64 / self.input_bytes as f64
108 }
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
117 fn test_compression_level_default() {
118 let level = CompressionLevel::default();
119 assert_eq!(level, CompressionLevel::Default);
120 }
121
122 #[test]
123 fn test_compression_stats() {
124 let stats = CompressionStats::new();
125
126 stats.record_compress(1000, 500);
127 stats.record_compress(1000, 500);
128 stats.record_decompress();
129
130 let snapshot = stats.snapshot();
131 assert_eq!(snapshot.input_bytes, 2000);
132 assert_eq!(snapshot.output_bytes, 1000);
133 assert_eq!(snapshot.compress_count, 2);
134 assert_eq!(snapshot.decompress_count, 1);
135 assert!((snapshot.compression_ratio() - 0.5).abs() < 0.001);
136 }
137
138 #[test]
139 fn test_space_savings() {
140 let stats = CompressionStats::new();
141 stats.record_compress(1000, 250);
142
143 assert!((stats.space_savings() - 0.75).abs() < 0.001);
144 }
145
146 #[test]
147 fn test_compression_ratio_zero_input() {
148 let stats = CompressionStats::new();
149 assert_eq!(stats.compression_ratio(), 1.0);
150 }
151}