oxirs_vec/compaction/
metrics.rs

1//! Metrics collection for compaction system
2
3use super::types::{CompactionResult, CompactionState, CompactionStatistics};
4use serde::{Deserialize, Serialize};
5use std::collections::VecDeque;
6use std::sync::{Arc, Mutex};
7use std::time::Duration;
8
9/// Compaction metrics collector
10#[derive(Debug, Clone)]
11pub struct CompactionMetrics {
12    /// Current state
13    state: Arc<Mutex<CompactionState>>,
14    /// Statistics
15    statistics: Arc<Mutex<CompactionStatistics>>,
16    /// Recent compaction history
17    history: Arc<Mutex<VecDeque<CompactionResult>>>,
18    /// Maximum history size
19    max_history_size: usize,
20}
21
22impl Default for CompactionMetrics {
23    fn default() -> Self {
24        Self::new(100)
25    }
26}
27
28impl CompactionMetrics {
29    /// Create a new metrics collector
30    pub fn new(max_history_size: usize) -> Self {
31        Self {
32            state: Arc::new(Mutex::new(CompactionState::Idle)),
33            statistics: Arc::new(Mutex::new(CompactionStatistics::default())),
34            history: Arc::new(Mutex::new(VecDeque::new())),
35            max_history_size,
36        }
37    }
38
39    /// Update state
40    pub fn update_state(&self, state: CompactionState) {
41        let mut s = self.state.lock().unwrap();
42        *s = state;
43    }
44
45    /// Get current state
46    pub fn get_state(&self) -> CompactionState {
47        *self.state.lock().unwrap()
48    }
49
50    /// Record compaction result
51    pub fn record_compaction(&self, result: CompactionResult) {
52        let mut stats = self.statistics.lock().unwrap();
53        let mut history = self.history.lock().unwrap();
54
55        // Update statistics
56        stats.total_compactions += 1;
57        if result.success {
58            stats.successful_compactions += 1;
59        } else {
60            stats.failed_compactions += 1;
61        }
62
63        stats.total_vectors_processed += result.vectors_processed;
64        stats.total_vectors_removed += result.vectors_removed;
65        stats.total_bytes_reclaimed += result.bytes_reclaimed;
66        stats.current_fragmentation = result.fragmentation_after;
67        stats.last_compaction_time = Some(result.end_time);
68        stats.last_compaction_result = Some(result.clone());
69
70        // Update average duration
71        if stats.total_compactions > 0 {
72            let total_duration = stats.avg_compaction_duration.as_secs_f64()
73                * (stats.total_compactions - 1) as f64
74                + result.duration.as_secs_f64();
75            stats.avg_compaction_duration =
76                Duration::from_secs_f64(total_duration / stats.total_compactions as f64);
77        } else {
78            stats.avg_compaction_duration = result.duration;
79        }
80
81        // Add to history
82        history.push_back(result);
83        while history.len() > self.max_history_size {
84            history.pop_front();
85        }
86    }
87
88    /// Update fragmentation
89    pub fn update_fragmentation(&self, fragmentation: f64) {
90        let mut stats = self.statistics.lock().unwrap();
91        stats.current_fragmentation = fragmentation;
92    }
93
94    /// Get statistics
95    pub fn get_statistics(&self) -> CompactionStatistics {
96        self.statistics.lock().unwrap().clone()
97    }
98
99    /// Get compaction history
100    pub fn get_history(&self, limit: Option<usize>) -> Vec<CompactionResult> {
101        let history = self.history.lock().unwrap();
102        if let Some(lim) = limit {
103            history.iter().rev().take(lim).cloned().collect()
104        } else {
105            history.iter().cloned().collect()
106        }
107    }
108
109    /// Calculate compaction efficiency
110    pub fn calculate_efficiency(&self) -> CompactionEfficiency {
111        let stats = self.statistics.lock().unwrap();
112
113        let success_rate = if stats.total_compactions > 0 {
114            stats.successful_compactions as f64 / stats.total_compactions as f64
115        } else {
116            0.0
117        };
118
119        let avg_space_reclaimed = if stats.successful_compactions > 0 {
120            stats.total_bytes_reclaimed as f64 / stats.successful_compactions as f64
121        } else {
122            0.0
123        };
124
125        let avg_vectors_removed = if stats.successful_compactions > 0 {
126            stats.total_vectors_removed as f64 / stats.successful_compactions as f64
127        } else {
128            0.0
129        };
130
131        CompactionEfficiency {
132            success_rate,
133            avg_space_reclaimed_bytes: avg_space_reclaimed as u64,
134            avg_vectors_removed: avg_vectors_removed as usize,
135            avg_duration: stats.avg_compaction_duration,
136            current_fragmentation: stats.current_fragmentation,
137        }
138    }
139
140    /// Reset metrics
141    pub fn reset(&self) {
142        let mut stats = self.statistics.lock().unwrap();
143        *stats = CompactionStatistics::default();
144
145        let mut history = self.history.lock().unwrap();
146        history.clear();
147
148        let mut state = self.state.lock().unwrap();
149        *state = CompactionState::Idle;
150    }
151}
152
153/// Compaction efficiency metrics
154#[derive(Debug, Clone, Serialize, Deserialize)]
155pub struct CompactionEfficiency {
156    /// Success rate (0.0 - 1.0)
157    pub success_rate: f64,
158    /// Average space reclaimed per compaction
159    pub avg_space_reclaimed_bytes: u64,
160    /// Average vectors removed per compaction
161    pub avg_vectors_removed: usize,
162    /// Average duration
163    pub avg_duration: Duration,
164    /// Current fragmentation
165    pub current_fragmentation: f64,
166}
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171    use crate::compaction::types::CompactionResult;
172    use std::time::SystemTime;
173
174    fn create_test_result(success: bool, bytes_reclaimed: u64) -> CompactionResult {
175        CompactionResult {
176            start_time: SystemTime::now(),
177            end_time: SystemTime::now(),
178            duration: Duration::from_secs(10),
179            vectors_processed: 1000,
180            vectors_removed: 100,
181            bytes_reclaimed,
182            fragmentation_before: 0.4,
183            fragmentation_after: 0.1,
184            success,
185            error: None,
186        }
187    }
188
189    #[test]
190    fn test_metrics_recording() {
191        let metrics = CompactionMetrics::new(10);
192
193        let result = create_test_result(true, 1_000_000);
194        metrics.record_compaction(result);
195
196        let stats = metrics.get_statistics();
197        assert_eq!(stats.total_compactions, 1);
198        assert_eq!(stats.successful_compactions, 1);
199        assert_eq!(stats.total_bytes_reclaimed, 1_000_000);
200    }
201
202    #[test]
203    fn test_efficiency_calculation() {
204        let metrics = CompactionMetrics::new(10);
205
206        metrics.record_compaction(create_test_result(true, 1_000_000));
207        metrics.record_compaction(create_test_result(true, 2_000_000));
208        metrics.record_compaction(create_test_result(false, 0));
209
210        let efficiency = metrics.calculate_efficiency();
211        assert!((efficiency.success_rate - 0.666).abs() < 0.01);
212        assert_eq!(efficiency.avg_space_reclaimed_bytes, 1_500_000);
213    }
214
215    #[test]
216    fn test_history_limit() {
217        let metrics = CompactionMetrics::new(5);
218
219        for i in 0..10 {
220            metrics.record_compaction(create_test_result(true, i * 1000));
221        }
222
223        let history = metrics.get_history(None);
224        assert_eq!(history.len(), 5);
225    }
226
227    #[test]
228    fn test_state_updates() {
229        let metrics = CompactionMetrics::new(10);
230
231        assert_eq!(metrics.get_state(), CompactionState::Idle);
232
233        metrics.update_state(CompactionState::Running);
234        assert_eq!(metrics.get_state(), CompactionState::Running);
235
236        metrics.update_state(CompactionState::Completed);
237        assert_eq!(metrics.get_state(), CompactionState::Completed);
238    }
239}