1use 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#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct ProfileReport {
20 pub backend: String,
22 pub diagnostics: DiagnosticsReport,
24 pub workload_results: Vec<WorkloadResult>,
26 pub analysis: StorageAnalysis,
28 pub tuning_report: TuningReport,
30 pub performance_score: u8,
32 pub duration: Duration,
34}
35
36#[derive(Debug, Clone)]
38pub struct ProfileConfig {
39 pub run_diagnostics: bool,
41 pub workload_configs: Vec<WorkloadConfig>,
43 pub tuner_config: AutoTunerConfig,
45 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 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 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 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
104pub 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 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 pub fn with_defaults(store: Arc<S>, backend_name: String) -> Self {
123 Self::new(store, backend_name, ProfileConfig::default())
124 }
125
126 pub async fn profile(&self) -> Result<ProfileReport> {
128 let start = Instant::now();
129
130 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 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 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 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 self.create_basic_analysis(&diagnostics, &workload_results)
177 };
178
179 let tuner = AutoTuner::new(self.config.tuner_config.clone());
181 let tuning_report = tuner.analyze_and_tune(&*self.store, &analysis).await?;
182
183 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 fn create_basic_analysis(
201 &self,
202 diagnostics: &DiagnosticsReport,
203 workload_results: &[WorkloadResult],
204 ) -> StorageAnalysis {
205 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, 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 fn calculate_performance_score(&self, analysis: &StorageAnalysis, tuning: &TuningReport) -> u8 {
248 let mut score = 100u8;
249
250 let health_penalty = (100 - analysis.diagnostics.health_score) / 2;
252 score = score.saturating_sub(health_penalty);
253
254 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 score = score.saturating_add(tuning.score / 10);
264
265 score.min(100)
266 }
267
268 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
281pub struct ComparativeProfiler;
283
284impl ComparativeProfiler {
285 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 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#[derive(Debug, Clone, Serialize, Deserialize)]
327pub struct ComparisonReport {
328 pub profiles: Vec<ProfileReport>,
330 pub winner: String,
332}
333
334pub struct RegressionDetector {
336 baseline: ProfileReport,
337 threshold: f64,
338}
339
340impl RegressionDetector {
341 pub fn new(baseline: ProfileReport, threshold: f64) -> Self {
343 Self {
344 baseline,
345 threshold,
346 }
347 }
348
349 pub fn check_regression(&self, current: &ProfileReport) -> RegressionResult {
351 let mut regressions = Vec::new();
352
353 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 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 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#[derive(Debug, Clone)]
394pub struct RegressionResult {
395 pub has_regression: bool,
397 pub regressions: Vec<String>,
399 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}