1use super::AdvancedCompressionType;
7use std::sync::atomic::{AtomicU64, Ordering};
8use std::sync::Arc;
9use std::time::{Duration, Instant};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum CompressionType {
14 None,
16 Lz4,
18 Zstd,
20 Snappy,
22 Brotli,
24 Zlib,
26}
27
28impl From<CompressionType> for AdvancedCompressionType {
29 fn from(ct: CompressionType) -> Self {
30 match ct {
31 CompressionType::None => AdvancedCompressionType::RunLength, CompressionType::Lz4 => AdvancedCompressionType::Lz4,
33 CompressionType::Zstd => AdvancedCompressionType::Zstd,
34 CompressionType::Snappy => AdvancedCompressionType::Snappy,
35 CompressionType::Brotli => AdvancedCompressionType::Brotli,
36 CompressionType::Zlib => AdvancedCompressionType::RunLength, }
38 }
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub enum SelectionStrategy {
44 MaximizeRatio,
46 MinimizeTime,
48 Balanced,
50 Adaptive,
52}
53
54#[derive(Debug)]
56pub struct AlgorithmMetrics {
57 pub bytes_compressed: AtomicU64,
59 pub total_time_us: AtomicU64,
61 pub total_compressed_size: AtomicU64,
63 pub operations: AtomicU64,
65}
66
67impl Default for AlgorithmMetrics {
68 fn default() -> Self {
69 Self {
70 bytes_compressed: AtomicU64::new(0),
71 total_time_us: AtomicU64::new(0),
72 total_compressed_size: AtomicU64::new(0),
73 operations: AtomicU64::new(0),
74 }
75 }
76}
77
78impl AlgorithmMetrics {
79 pub fn record(&self, input_size: usize, compressed_size: usize, duration: Duration) {
81 self.bytes_compressed
82 .fetch_add(input_size as u64, Ordering::Relaxed);
83 self.total_compressed_size
84 .fetch_add(compressed_size as u64, Ordering::Relaxed);
85 self.total_time_us
86 .fetch_add(duration.as_micros() as u64, Ordering::Relaxed);
87 self.operations.fetch_add(1, Ordering::Relaxed);
88 }
89
90 pub fn avg_ratio(&self) -> f64 {
92 let total_in = self.bytes_compressed.load(Ordering::Relaxed) as f64;
93 let total_out = self.total_compressed_size.load(Ordering::Relaxed) as f64;
94 if total_out == 0.0 {
95 1.0
96 } else {
97 total_in / total_out
98 }
99 }
100
101 pub fn avg_throughput_mbps(&self) -> f64 {
103 let bytes = self.bytes_compressed.load(Ordering::Relaxed) as f64;
104 let time_s = self.total_time_us.load(Ordering::Relaxed) as f64 / 1_000_000.0;
105 if time_s == 0.0 {
106 0.0
107 } else {
108 (bytes / (1024.0 * 1024.0)) / time_s
109 }
110 }
111
112 pub fn avg_time_us(&self) -> f64 {
114 let ops = self.operations.load(Ordering::Relaxed);
115 if ops == 0 {
116 0.0
117 } else {
118 self.total_time_us.load(Ordering::Relaxed) as f64 / ops as f64
119 }
120 }
121}
122
123pub struct AdaptiveCompressionSelector {
125 strategy: SelectionStrategy,
127 metrics: Vec<(AdvancedCompressionType, Arc<AlgorithmMetrics>)>,
129 min_compression_size: usize,
131}
132
133impl AdaptiveCompressionSelector {
134 pub fn new(strategy: SelectionStrategy) -> Self {
136 let compression_types = vec![
137 AdvancedCompressionType::Lz4,
138 AdvancedCompressionType::Zstd,
139 AdvancedCompressionType::Snappy,
140 AdvancedCompressionType::Brotli,
141 AdvancedCompressionType::RunLength, ];
143
144 let metrics = compression_types
145 .into_iter()
146 .map(|ctype| (ctype, Arc::new(AlgorithmMetrics::default())))
147 .collect();
148
149 Self {
150 strategy,
151 metrics,
152 min_compression_size: 128, }
154 }
155
156 pub fn select(&self, data_size: usize) -> AdvancedCompressionType {
158 if data_size < self.min_compression_size {
160 return AdvancedCompressionType::RunLength;
161 }
162
163 match self.strategy {
164 SelectionStrategy::MaximizeRatio => self.select_max_ratio(),
165 SelectionStrategy::MinimizeTime => self.select_min_time(),
166 SelectionStrategy::Balanced => self.select_balanced(),
167 SelectionStrategy::Adaptive => self.select_adaptive(data_size),
168 }
169 }
170
171 fn select_max_ratio(&self) -> AdvancedCompressionType {
173 self.metrics
174 .iter()
175 .max_by(|(_, a), (_, b)| {
176 a.avg_ratio()
177 .partial_cmp(&b.avg_ratio())
178 .unwrap_or(std::cmp::Ordering::Equal)
179 })
180 .map(|(ctype, _)| *ctype)
181 .unwrap_or(AdvancedCompressionType::Zstd)
182 }
183
184 fn select_min_time(&self) -> AdvancedCompressionType {
186 self.metrics
187 .iter()
188 .max_by(|(_, a), (_, b)| {
189 a.avg_throughput_mbps()
190 .partial_cmp(&b.avg_throughput_mbps())
191 .unwrap_or(std::cmp::Ordering::Equal)
192 })
193 .map(|(ctype, _)| *ctype)
194 .unwrap_or(AdvancedCompressionType::Lz4)
195 }
196
197 fn select_balanced(&self) -> AdvancedCompressionType {
199 self.metrics
200 .iter()
201 .max_by(|(_, a), (_, b)| {
202 let score_a = a.avg_ratio() * a.avg_throughput_mbps().sqrt();
203 let score_b = b.avg_ratio() * b.avg_throughput_mbps().sqrt();
204 score_a
205 .partial_cmp(&score_b)
206 .unwrap_or(std::cmp::Ordering::Equal)
207 })
208 .map(|(ctype, _)| *ctype)
209 .unwrap_or(AdvancedCompressionType::Snappy)
210 }
211
212 fn select_adaptive(&self, data_size: usize) -> AdvancedCompressionType {
214 match data_size {
215 0..=1024 => AdvancedCompressionType::Lz4, 1025..=10240 => AdvancedCompressionType::Snappy, 10241..=102400 => AdvancedCompressionType::Zstd, _ => AdvancedCompressionType::Zstd, }
220 }
221
222 pub fn record_compression(
224 &self,
225 compression_type: AdvancedCompressionType,
226 input_size: usize,
227 compressed_size: usize,
228 duration: Duration,
229 ) {
230 if let Some((_, metrics)) = self.metrics.iter().find(|(ct, _)| *ct == compression_type) {
231 metrics.record(input_size, compressed_size, duration);
232 }
233 }
234
235 pub fn get_metrics(
237 &self,
238 compression_type: AdvancedCompressionType,
239 ) -> Option<Arc<AlgorithmMetrics>> {
240 self.metrics
241 .iter()
242 .find(|(ct, _)| *ct == compression_type)
243 .map(|(_, m)| Arc::clone(m))
244 }
245
246 pub fn stats(&self) -> AdaptiveCompressionStats {
248 let algorithm_stats = self
249 .metrics
250 .iter()
251 .map(|(ctype, metrics)| AlgorithmStats {
252 compression_type: *ctype,
253 avg_ratio: metrics.avg_ratio(),
254 avg_throughput_mbps: metrics.avg_throughput_mbps(),
255 operations: metrics.operations.load(Ordering::Relaxed),
256 total_bytes: metrics.bytes_compressed.load(Ordering::Relaxed),
257 })
258 .collect();
259
260 AdaptiveCompressionStats {
261 strategy: self.strategy,
262 algorithm_stats,
263 }
264 }
265}
266
267#[derive(Debug)]
269pub struct AdaptiveCompressionStats {
270 pub strategy: SelectionStrategy,
272 pub algorithm_stats: Vec<AlgorithmStats>,
274}
275
276#[derive(Debug)]
278pub struct AlgorithmStats {
279 pub compression_type: AdvancedCompressionType,
281 pub avg_ratio: f64,
283 pub avg_throughput_mbps: f64,
285 pub operations: u64,
287 pub total_bytes: u64,
289}
290
291#[cfg(test)]
292mod tests {
293 use super::*;
294
295 #[test]
296 fn test_algorithm_metrics() {
297 let metrics = AlgorithmMetrics::default();
298
299 metrics.record(1000, 500, Duration::from_micros(100));
300 metrics.record(2000, 800, Duration::from_micros(200));
301
302 assert_eq!(metrics.operations.load(Ordering::Relaxed), 2);
303 assert_eq!(metrics.bytes_compressed.load(Ordering::Relaxed), 3000);
304 assert_eq!(metrics.total_compressed_size.load(Ordering::Relaxed), 1300);
305
306 assert!((metrics.avg_ratio() - 2.31).abs() < 0.01);
308
309 assert_eq!(metrics.avg_time_us(), 150.0);
311 }
312
313 #[test]
314 fn test_adaptive_selector_creation() {
315 let selector = AdaptiveCompressionSelector::new(SelectionStrategy::Balanced);
316
317 assert_eq!(selector.strategy, SelectionStrategy::Balanced);
318 assert_eq!(selector.metrics.len(), 5); }
320
321 #[test]
322 fn test_small_data_not_compressed() {
323 let selector = AdaptiveCompressionSelector::new(SelectionStrategy::MaximizeRatio);
324
325 let selected = selector.select(50); assert_eq!(selected, AdvancedCompressionType::RunLength); }
328
329 #[test]
330 fn test_adaptive_selection_by_size() {
331 let selector = AdaptiveCompressionSelector::new(SelectionStrategy::Adaptive);
332
333 assert_eq!(selector.select(500), AdvancedCompressionType::Lz4);
335
336 assert_eq!(selector.select(5000), AdvancedCompressionType::Snappy);
338
339 assert_eq!(selector.select(50000), AdvancedCompressionType::Zstd);
341
342 assert_eq!(selector.select(500000), AdvancedCompressionType::Zstd);
344 }
345
346 #[test]
347 fn test_record_and_select() {
348 let selector = AdaptiveCompressionSelector::new(SelectionStrategy::MaximizeRatio);
349
350 selector.record_compression(
352 AdvancedCompressionType::Lz4,
353 1000,
354 800,
355 Duration::from_micros(50),
356 );
357 selector.record_compression(
358 AdvancedCompressionType::Zstd,
359 1000,
360 400,
361 Duration::from_micros(200),
362 );
363
364 let selected = selector.select_max_ratio();
366 assert_eq!(selected, AdvancedCompressionType::Zstd);
367 }
368
369 #[test]
370 fn test_min_time_selection() {
371 let selector = AdaptiveCompressionSelector::new(SelectionStrategy::MinimizeTime);
372
373 selector.record_compression(
375 AdvancedCompressionType::Lz4,
376 10000,
377 5000,
378 Duration::from_micros(100), );
380 selector.record_compression(
381 AdvancedCompressionType::Zstd,
382 10000,
383 3000,
384 Duration::from_micros(500), );
386
387 let selected = selector.select_min_time();
389 assert_eq!(selected, AdvancedCompressionType::Lz4);
390 }
391
392 #[test]
393 fn test_get_metrics() {
394 let selector = AdaptiveCompressionSelector::new(SelectionStrategy::Balanced);
395
396 selector.record_compression(
397 AdvancedCompressionType::Lz4,
398 1000,
399 500,
400 Duration::from_micros(100),
401 );
402
403 let metrics = selector.get_metrics(AdvancedCompressionType::Lz4).unwrap();
404 assert_eq!(metrics.operations.load(Ordering::Relaxed), 1);
405 }
406
407 #[test]
408 fn test_stats() {
409 let selector = AdaptiveCompressionSelector::new(SelectionStrategy::Balanced);
410
411 selector.record_compression(
412 AdvancedCompressionType::Lz4,
413 1000,
414 500,
415 Duration::from_micros(100),
416 );
417 selector.record_compression(
418 AdvancedCompressionType::Zstd,
419 2000,
420 600,
421 Duration::from_micros(300),
422 );
423
424 let stats = selector.stats();
425 assert_eq!(stats.strategy, SelectionStrategy::Balanced);
426 assert!(stats.algorithm_stats.len() >= 2);
427
428 let lz4_stats = stats
430 .algorithm_stats
431 .iter()
432 .find(|s| s.compression_type == AdvancedCompressionType::Lz4)
433 .unwrap();
434
435 assert_eq!(lz4_stats.operations, 1);
436 assert_eq!(lz4_stats.total_bytes, 1000);
437 }
438}