ipfrs_storage/
profiler.rs

1//! Comprehensive storage profiling and optimization
2//!
3//! This module provides a unified interface for profiling storage performance,
4//! analyzing workload characteristics, and generating optimization recommendations.
5
6use crate::analyzer::{StorageAnalysis, StorageAnalyzer, WorkloadType};
7use crate::auto_tuner::{AutoTuner, AutoTunerConfig, TuningReport};
8use crate::diagnostics::{DiagnosticsReport, StorageDiagnostics};
9use crate::traits::BlockStore;
10use crate::workload::{WorkloadConfig, WorkloadResult, WorkloadSimulator};
11use ipfrs_core::Result;
12use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14use std::sync::Arc;
15use std::time::{Duration, Instant};
16
17/// Comprehensive profiling report
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct ProfileReport {
20    /// Storage backend name
21    pub backend: String,
22    /// Diagnostics results
23    pub diagnostics: DiagnosticsReport,
24    /// Workload simulation results
25    pub workload_results: Vec<WorkloadResult>,
26    /// Storage analysis
27    pub analysis: StorageAnalysis,
28    /// Auto-tuning recommendations
29    pub tuning_report: TuningReport,
30    /// Overall performance score (0-100)
31    pub performance_score: u8,
32    /// Profiling duration
33    pub duration: Duration,
34}
35
36/// Profiling configuration
37#[derive(Debug, Clone)]
38pub struct ProfileConfig {
39    /// Run diagnostics tests
40    pub run_diagnostics: bool,
41    /// Workload configurations to test
42    pub workload_configs: Vec<WorkloadConfig>,
43    /// Auto-tuner configuration
44    pub tuner_config: AutoTunerConfig,
45    /// Include detailed analysis
46    pub detailed_analysis: bool,
47}
48
49impl Default for ProfileConfig {
50    fn default() -> Self {
51        Self {
52            run_diagnostics: true,
53            workload_configs: vec![
54                crate::workload::WorkloadPresets::light_test(),
55                crate::workload::WorkloadPresets::medium_stress(),
56            ],
57            tuner_config: AutoTunerConfig::default(),
58            detailed_analysis: true,
59        }
60    }
61}
62
63impl ProfileConfig {
64    /// Create a quick profiling configuration (fast, minimal tests)
65    pub fn quick() -> Self {
66        Self {
67            run_diagnostics: true,
68            workload_configs: vec![crate::workload::WorkloadPresets::light_test()],
69            tuner_config: AutoTunerConfig::default(),
70            detailed_analysis: false,
71        }
72    }
73
74    /// Create a comprehensive profiling configuration (thorough, all tests)
75    pub fn comprehensive() -> Self {
76        Self {
77            run_diagnostics: true,
78            workload_configs: vec![
79                crate::workload::WorkloadPresets::light_test(),
80                crate::workload::WorkloadPresets::medium_stress(),
81                crate::workload::WorkloadPresets::cdn_cache(),
82                crate::workload::WorkloadPresets::ingestion_pipeline(),
83                crate::workload::WorkloadPresets::time_series(),
84            ],
85            tuner_config: AutoTunerConfig::default(),
86            detailed_analysis: true,
87        }
88    }
89
90    /// Create a performance-focused profiling configuration
91    pub fn performance() -> Self {
92        Self {
93            run_diagnostics: true,
94            workload_configs: vec![
95                crate::workload::WorkloadPresets::medium_stress(),
96                crate::workload::WorkloadPresets::heavy_stress(),
97            ],
98            tuner_config: crate::auto_tuner::TuningPresets::performance(),
99            detailed_analysis: true,
100        }
101    }
102}
103
104/// Storage profiler for comprehensive performance analysis
105pub struct StorageProfiler<S: BlockStore> {
106    store: Arc<S>,
107    backend_name: String,
108    config: ProfileConfig,
109}
110
111impl<S: BlockStore + Send + Sync + 'static> StorageProfiler<S> {
112    /// Create a new storage profiler
113    pub fn new(store: Arc<S>, backend_name: String, config: ProfileConfig) -> Self {
114        Self {
115            store,
116            backend_name,
117            config,
118        }
119    }
120
121    /// Create a profiler with default configuration
122    pub fn with_defaults(store: Arc<S>, backend_name: String) -> Self {
123        Self::new(store, backend_name, ProfileConfig::default())
124    }
125
126    /// Run comprehensive profiling
127    pub async fn profile(&self) -> Result<ProfileReport> {
128        let start = Instant::now();
129
130        // Step 1: Run diagnostics
131        let diagnostics = if self.config.run_diagnostics {
132            let mut diagnostics_runner =
133                StorageDiagnostics::new(Arc::clone(&self.store), self.backend_name.clone());
134            diagnostics_runner.run().await?
135        } else {
136            // Create minimal diagnostics report
137            DiagnosticsReport {
138                backend: self.backend_name.clone(),
139                total_blocks: 0,
140                performance: crate::diagnostics::PerformanceMetrics {
141                    avg_write_latency: Duration::from_micros(0),
142                    avg_read_latency: Duration::from_micros(0),
143                    avg_batch_write_latency: Duration::from_micros(0),
144                    avg_batch_read_latency: Duration::from_micros(0),
145                    write_throughput: 0.0,
146                    read_throughput: 0.0,
147                    peak_memory_usage: 0,
148                },
149                health: crate::diagnostics::HealthMetrics {
150                    successful_ops: 0,
151                    failed_ops: 0,
152                    success_rate: 1.0,
153                    integrity_ok: true,
154                    responsive: true,
155                },
156                recommendations: Vec::new(),
157                health_score: 100,
158            }
159        };
160
161        // Step 2: Run workload simulations
162        let mut workload_results = Vec::new();
163        for workload_config in &self.config.workload_configs {
164            let mut simulator = WorkloadSimulator::new(workload_config.clone());
165            simulator.generate_dataset();
166            let result = simulator.run(self.store.clone()).await?;
167            workload_results.push(result);
168        }
169
170        // Step 3: Analyze storage characteristics
171        let mut analyzer = StorageAnalyzer::new(Arc::clone(&self.store), self.backend_name.clone());
172        let analysis = if self.config.detailed_analysis {
173            analyzer.analyze().await?
174        } else {
175            // Create basic analysis from workload results
176            self.create_basic_analysis(&diagnostics, &workload_results)
177        };
178
179        // Step 4: Generate tuning recommendations
180        let tuner = AutoTuner::new(self.config.tuner_config.clone());
181        let tuning_report = tuner.analyze_and_tune(&*self.store, &analysis).await?;
182
183        // Step 5: Calculate performance score
184        let performance_score = self.calculate_performance_score(&analysis, &tuning_report);
185
186        let duration = start.elapsed();
187
188        Ok(ProfileReport {
189            backend: self.backend_name.clone(),
190            diagnostics,
191            workload_results,
192            analysis,
193            tuning_report,
194            performance_score,
195            duration,
196        })
197    }
198
199    /// Create basic analysis from workload results
200    fn create_basic_analysis(
201        &self,
202        diagnostics: &DiagnosticsReport,
203        workload_results: &[WorkloadResult],
204    ) -> StorageAnalysis {
205        // Determine workload type based on operation counts
206        let mut total_gets = 0usize;
207        let mut total_puts = 0usize;
208        for result in workload_results {
209            total_gets += result.operation_counts.get("get").copied().unwrap_or(0);
210            total_puts += result.operation_counts.get("put").copied().unwrap_or(0);
211        }
212
213        let read_write_ratio = if total_puts > 0 {
214            total_gets as f64 / (total_gets + total_puts) as f64
215        } else {
216            1.0
217        };
218
219        let workload_type = if read_write_ratio > 0.7 {
220            WorkloadType::ReadHeavy
221        } else if read_write_ratio < 0.3 {
222            WorkloadType::WriteHeavy
223        } else {
224            WorkloadType::Balanced
225        };
226
227        StorageAnalysis {
228            backend: self.backend_name.clone(),
229            diagnostics: diagnostics.clone(),
230            performance_breakdown: HashMap::new(),
231            workload: crate::analyzer::WorkloadCharacterization {
232                read_write_ratio,
233                avg_block_size: 16384, // Default assumption
234                size_distribution: crate::analyzer::SizeDistribution {
235                    small_pct: 0.3,
236                    medium_pct: 0.5,
237                    large_pct: 0.2,
238                },
239                workload_type,
240            },
241            recommendations: Vec::new(),
242            grade: self.calculate_grade(diagnostics.health_score),
243        }
244    }
245
246    /// Calculate performance score
247    fn calculate_performance_score(&self, analysis: &StorageAnalysis, tuning: &TuningReport) -> u8 {
248        let mut score = 100u8;
249
250        // Penalize based on diagnostics health score
251        let health_penalty = (100 - analysis.diagnostics.health_score) / 2;
252        score = score.saturating_sub(health_penalty);
253
254        // Penalize based on number of high-priority recommendations
255        let high_priority_recs = tuning
256            .recommendations
257            .iter()
258            .filter(|r| r.expected_impact > 20.0)
259            .count();
260        score = score.saturating_sub((high_priority_recs * 5) as u8);
261
262        // Bonus for good tuning score
263        score = score.saturating_add(tuning.score / 10);
264
265        score.min(100)
266    }
267
268    /// Calculate grade from score
269    fn calculate_grade(&self, score: u8) -> String {
270        match score {
271            90..=100 => "A",
272            80..=89 => "B",
273            70..=79 => "C",
274            60..=69 => "D",
275            _ => "F",
276        }
277        .to_string()
278    }
279}
280
281/// Comparative profiling for multiple storage configurations
282pub struct ComparativeProfiler;
283
284impl ComparativeProfiler {
285    /// Compare multiple storage backends
286    pub async fn compare<S1, S2>(
287        store1: Arc<S1>,
288        name1: &str,
289        store2: Arc<S2>,
290        name2: &str,
291        config: ProfileConfig,
292    ) -> Result<ComparisonReport>
293    where
294        S1: BlockStore + Send + Sync + 'static,
295        S2: BlockStore + Send + Sync + 'static,
296    {
297        let profiler1 = StorageProfiler::new(store1, name1.to_string(), config.clone());
298        let profiler2 = StorageProfiler::new(store2, name2.to_string(), config);
299
300        let report1 = profiler1.profile().await?;
301        let report2 = profiler2.profile().await?;
302
303        Ok(ComparisonReport {
304            profiles: vec![report1, report2],
305            winner: Self::determine_winner(name1, name2, &[]),
306        })
307    }
308
309    /// Determine the better configuration
310    fn determine_winner(name1: &str, name2: &str, profiles: &[ProfileReport]) -> String {
311        if profiles.len() < 2 {
312            return "Insufficient data".to_string();
313        }
314
315        if profiles[0].performance_score > profiles[1].performance_score {
316            name1.to_string()
317        } else if profiles[1].performance_score > profiles[0].performance_score {
318            name2.to_string()
319        } else {
320            "Tie".to_string()
321        }
322    }
323}
324
325/// Comparison report for multiple configurations
326#[derive(Debug, Clone, Serialize, Deserialize)]
327pub struct ComparisonReport {
328    /// Individual profile reports
329    pub profiles: Vec<ProfileReport>,
330    /// Winner determination
331    pub winner: String,
332}
333
334/// Performance regression detector
335pub struct RegressionDetector {
336    baseline: ProfileReport,
337    threshold: f64,
338}
339
340impl RegressionDetector {
341    /// Create a new regression detector with baseline
342    pub fn new(baseline: ProfileReport, threshold: f64) -> Self {
343        Self {
344            baseline,
345            threshold,
346        }
347    }
348
349    /// Check for performance regression
350    pub fn check_regression(&self, current: &ProfileReport) -> RegressionResult {
351        let mut regressions = Vec::new();
352
353        // Check performance score regression
354        if current.performance_score < self.baseline.performance_score {
355            let diff = self.baseline.performance_score - current.performance_score;
356            if (diff as f64) > self.threshold {
357                regressions.push(format!("Performance score decreased by {diff} points"));
358            }
359        }
360
361        // Check workload throughput regression
362        for (i, baseline_result) in self.baseline.workload_results.iter().enumerate() {
363            if let Some(current_result) = current.workload_results.get(i) {
364                let throughput_ratio =
365                    current_result.ops_per_second / baseline_result.ops_per_second;
366                if throughput_ratio < (1.0 - self.threshold) {
367                    regressions.push(format!(
368                        "Workload {} throughput decreased by {:.1}%",
369                        i,
370                        (1.0 - throughput_ratio) * 100.0
371                    ));
372                }
373            }
374        }
375
376        RegressionResult {
377            has_regression: !regressions.is_empty(),
378            regressions,
379            improvement_pct: self.calculate_improvement(current),
380        }
381    }
382
383    /// Calculate overall improvement percentage
384    fn calculate_improvement(&self, current: &ProfileReport) -> f64 {
385        let score_improvement = (current.performance_score as f64
386            - self.baseline.performance_score as f64)
387            / self.baseline.performance_score as f64;
388        score_improvement * 100.0
389    }
390}
391
392/// Regression detection result
393#[derive(Debug, Clone)]
394pub struct RegressionResult {
395    /// Whether regression was detected
396    pub has_regression: bool,
397    /// List of detected regressions
398    pub regressions: Vec<String>,
399    /// Overall improvement percentage (negative if regression)
400    pub improvement_pct: f64,
401}
402
403#[cfg(test)]
404mod tests {
405    use super::*;
406    use crate::MemoryBlockStore;
407
408    #[tokio::test]
409    async fn test_quick_profile() {
410        let config = ProfileConfig::quick();
411        let profiler = StorageProfiler::new(
412            Arc::new(MemoryBlockStore::new()),
413            "MemoryBlockStore".to_string(),
414            config,
415        );
416
417        let report = profiler.profile().await.unwrap();
418
419        assert_eq!(report.backend, "MemoryBlockStore");
420        assert!(!report.workload_results.is_empty());
421        assert!(report.performance_score <= 100);
422    }
423
424    #[tokio::test]
425    async fn test_performance_score_calculation() {
426        let store = Arc::new(MemoryBlockStore::new());
427        let config = ProfileConfig::quick();
428        let profiler = StorageProfiler::new(store, "Test".to_string(), config);
429
430        let report = profiler.profile().await.unwrap();
431        assert!(report.performance_score > 0);
432        assert!(report.performance_score <= 100);
433    }
434
435    #[test]
436    fn test_profile_config_presets() {
437        let _quick = ProfileConfig::quick();
438        let _comprehensive = ProfileConfig::comprehensive();
439        let _performance = ProfileConfig::performance();
440    }
441}