guts_storage/
compression.rs

1//! Compression utilities for storage.
2//!
3//! Provides configurable compression levels and statistics
4//! for optimizing storage efficiency.
5
6use std::sync::atomic::{AtomicU64, Ordering};
7
8/// Compression level configuration.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
10pub enum CompressionLevel {
11    /// No compression
12    None,
13    /// Fast compression (lower ratio)
14    Fast,
15    /// Default compression (balanced)
16    #[default]
17    Default,
18    /// Best compression (slower, higher ratio)
19    Best,
20}
21
22impl CompressionLevel {
23    /// Converts to flate2 compression level.
24    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/// Compression statistics for monitoring.
35#[derive(Debug, Default)]
36pub struct CompressionStats {
37    /// Total bytes before compression.
38    pub input_bytes: AtomicU64,
39    /// Total bytes after compression.
40    pub output_bytes: AtomicU64,
41    /// Number of compression operations.
42    pub compress_count: AtomicU64,
43    /// Number of decompression operations.
44    pub decompress_count: AtomicU64,
45}
46
47impl CompressionStats {
48    /// Creates new compression stats.
49    pub fn new() -> Self {
50        Self::default()
51    }
52
53    /// Records a compression operation.
54    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    /// Records a decompression operation.
61    pub fn record_decompress(&self) {
62        self.decompress_count.fetch_add(1, Ordering::Relaxed);
63    }
64
65    /// Returns the compression ratio (output/input).
66    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    /// Returns the space savings percentage.
77    pub fn space_savings(&self) -> f64 {
78        1.0 - self.compression_ratio()
79    }
80
81    /// Returns a snapshot of the stats.
82    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/// Snapshot of compression statistics.
93#[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    /// Returns the compression ratio.
103    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}