1use crate::analyzer::{StorageAnalysis, WorkloadType};
7use crate::traits::BlockStore;
8use ipfrs_core::Result;
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11use std::time::Duration;
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct TuningRecommendation {
16 pub parameter: String,
18 pub current_value: String,
20 pub recommended_value: String,
22 pub rationale: String,
24 pub expected_impact: f64,
26 pub confidence: f64,
28}
29
30#[derive(Debug, Clone)]
32pub struct AutoTunerConfig {
33 pub observation_period: Duration,
35 pub confidence_threshold: f64,
37 pub target_cache_hit_rate: f64,
39 pub target_bloom_fp_rate: f64,
41 pub aggressive: bool,
43}
44
45impl Default for AutoTunerConfig {
46 fn default() -> Self {
47 Self {
48 observation_period: Duration::from_secs(300), confidence_threshold: 0.7,
50 target_cache_hit_rate: 0.85,
51 target_bloom_fp_rate: 0.01,
52 aggressive: false,
53 }
54 }
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct TuningReport {
60 pub analysis: Option<String>,
62 pub recommendations: Vec<TuningRecommendation>,
64 pub score: u8,
66 pub summary: String,
68}
69
70pub struct AutoTuner {
72 config: AutoTunerConfig,
73}
74
75impl AutoTuner {
76 pub fn new(config: AutoTunerConfig) -> Self {
78 Self { config }
79 }
80
81 pub fn default_config() -> Self {
83 Self::new(AutoTunerConfig::default())
84 }
85
86 #[allow(clippy::unused_async)]
88 pub async fn analyze_and_tune<S: BlockStore>(
89 &self,
90 _store: &S,
91 analysis: &StorageAnalysis,
92 ) -> Result<TuningReport> {
93 let mut recommendations = Vec::new();
94 let mut score = 100u8;
95
96 if let Some(cache_rec) = self.tune_cache(analysis) {
98 if cache_rec.confidence >= self.config.confidence_threshold {
99 score = score.saturating_sub(5);
100 recommendations.push(cache_rec);
101 }
102 }
103
104 if let Some(bloom_rec) = self.tune_bloom_filter(analysis) {
106 if bloom_rec.confidence >= self.config.confidence_threshold {
107 score = score.saturating_sub(5);
108 recommendations.push(bloom_rec);
109 }
110 }
111
112 if let Some(concurrency_rec) = self.tune_concurrency(analysis) {
114 if concurrency_rec.confidence >= self.config.confidence_threshold {
115 score = score.saturating_sub(5);
116 recommendations.push(concurrency_rec);
117 }
118 }
119
120 if let Some(compression_rec) = self.tune_compression(analysis) {
122 if compression_rec.confidence >= self.config.confidence_threshold {
123 score = score.saturating_sub(5);
124 recommendations.push(compression_rec);
125 }
126 }
127
128 if let Some(dedup_rec) = self.tune_deduplication(analysis) {
130 if dedup_rec.confidence >= self.config.confidence_threshold {
131 score = score.saturating_sub(5);
132 recommendations.push(dedup_rec);
133 }
134 }
135
136 if let Some(backend_rec) = self.tune_backend_selection(analysis) {
138 if backend_rec.confidence >= self.config.confidence_threshold {
139 score = score.saturating_sub(10);
140 recommendations.push(backend_rec);
141 }
142 }
143
144 let summary = self.generate_summary(&recommendations, &analysis.workload.workload_type);
145
146 Ok(TuningReport {
147 analysis: Some(format!("Workload: {:?}", analysis.workload.workload_type)),
148 recommendations,
149 score,
150 summary,
151 })
152 }
153
154 fn tune_cache(&self, analysis: &StorageAnalysis) -> Option<TuningRecommendation> {
156 let cache_hit_rate = analysis.diagnostics.health.success_rate * 0.7; if cache_hit_rate < self.config.target_cache_hit_rate {
161 let current_size = "current"; let increase_factor = if self.config.aggressive { 2.0 } else { 1.5 };
163 let recommended_size = format!("{increase_factor}x current");
164
165 let confidence = if analysis.workload.read_write_ratio > 0.7 {
166 0.9 } else {
168 0.7
169 };
170
171 Some(TuningRecommendation {
172 parameter: "cache_size".to_string(),
173 current_value: current_size.to_string(),
174 recommended_value: recommended_size,
175 rationale: format!(
176 "Cache hit rate ({:.1}%) is below target ({:.1}%). \
177 Increasing cache size will improve read performance.",
178 cache_hit_rate * 100.0,
179 self.config.target_cache_hit_rate * 100.0
180 ),
181 expected_impact: (self.config.target_cache_hit_rate - cache_hit_rate) * 50.0,
182 confidence,
183 })
184 } else {
185 None
186 }
187 }
188
189 fn tune_bloom_filter(&self, analysis: &StorageAnalysis) -> Option<TuningRecommendation> {
191 if analysis.workload.read_write_ratio > 0.7 {
193 Some(TuningRecommendation {
194 parameter: "bloom_filter_size".to_string(),
195 current_value: "current".to_string(),
196 recommended_value: "2x expected items".to_string(),
197 rationale: "Read-heavy workload benefits from larger bloom filter \
198 to reduce false positives and unnecessary disk lookups."
199 .to_string(),
200 expected_impact: 5.0,
201 confidence: 0.8,
202 })
203 } else {
204 None
205 }
206 }
207
208 fn tune_concurrency(&self, analysis: &StorageAnalysis) -> Option<TuningRecommendation> {
210 let avg_latency = analysis
212 .performance_breakdown
213 .values()
214 .map(|stats| stats.avg_latency_us)
215 .max()
216 .unwrap_or(0);
217
218 if avg_latency > 10_000 {
219 Some(TuningRecommendation {
221 parameter: "concurrency_limit".to_string(),
222 current_value: "unlimited".to_string(),
223 recommended_value: "8-16 concurrent operations".to_string(),
224 rationale: "High latency detected. Limiting concurrency \
225 can reduce contention and improve throughput."
226 .to_string(),
227 expected_impact: 15.0,
228 confidence: 0.75,
229 })
230 } else {
231 None
232 }
233 }
234
235 fn tune_compression(&self, analysis: &StorageAnalysis) -> Option<TuningRecommendation> {
237 let avg_block_size = analysis.workload.avg_block_size;
238
239 if avg_block_size > 16384 {
241 Some(TuningRecommendation {
243 parameter: "compression".to_string(),
244 current_value: "disabled".to_string(),
245 recommended_value: "Zstd level 3".to_string(),
246 rationale: format!(
247 "Average block size ({avg_block_size} bytes) is large enough to benefit \
248 from compression. Zstd level 3 provides good balance."
249 ),
250 expected_impact: 30.0, confidence: 0.85,
252 })
253 } else {
254 None
255 }
256 }
257
258 fn tune_deduplication(&self, analysis: &StorageAnalysis) -> Option<TuningRecommendation> {
260 if matches!(analysis.workload.workload_type, WorkloadType::WriteHeavy) {
262 Some(TuningRecommendation {
263 parameter: "deduplication".to_string(),
264 current_value: "disabled".to_string(),
265 recommended_value: "enabled with 16KB chunks".to_string(),
266 rationale: "Write-heavy workload likely has redundant data. \
267 Deduplication can significantly reduce storage."
268 .to_string(),
269 expected_impact: 25.0, confidence: 0.7,
271 })
272 } else {
273 None
274 }
275 }
276
277 fn tune_backend_selection(&self, analysis: &StorageAnalysis) -> Option<TuningRecommendation> {
279 match analysis.workload.workload_type {
280 WorkloadType::WriteHeavy if analysis.backend == "Sled" => {
281 Some(TuningRecommendation {
282 parameter: "backend".to_string(),
283 current_value: "Sled".to_string(),
284 recommended_value: "ParityDB".to_string(),
285 rationale: "Write-heavy workload. ParityDB offers 2-5x better \
286 write performance with lower write amplification."
287 .to_string(),
288 expected_impact: 100.0, confidence: 0.9,
290 })
291 }
292 WorkloadType::ReadHeavy if analysis.backend == "ParityDB" => {
293 Some(TuningRecommendation {
294 parameter: "backend".to_string(),
295 current_value: "ParityDB".to_string(),
296 recommended_value: "Sled".to_string(),
297 rationale: "Read-heavy workload. Sled offers better read \
298 performance with B-tree indexing."
299 .to_string(),
300 expected_impact: 50.0, confidence: 0.85,
302 })
303 }
304 _ => None,
305 }
306 }
307
308 fn generate_summary(
310 &self,
311 recommendations: &[TuningRecommendation],
312 workload: &WorkloadType,
313 ) -> String {
314 if recommendations.is_empty() {
315 return "Configuration is well-tuned for current workload. No changes recommended."
316 .to_string();
317 }
318
319 let high_impact: Vec<_> = recommendations
320 .iter()
321 .filter(|r| r.expected_impact > 20.0)
322 .collect();
323
324 if high_impact.is_empty() {
325 format!(
326 "Found {} minor optimization opportunities for {:?} workload.",
327 recommendations.len(),
328 workload
329 )
330 } else {
331 format!(
332 "Found {} optimization opportunities for {:?} workload, \
333 including {} high-impact changes. Implementing all recommendations \
334 could improve performance by up to {:.0}%.",
335 recommendations.len(),
336 workload,
337 high_impact.len(),
338 recommendations
339 .iter()
340 .map(|r| r.expected_impact)
341 .sum::<f64>()
342 )
343 }
344 }
345
346 pub fn apply_recommendations(&self, report: &TuningReport) -> HashMap<String, String> {
348 let mut config = HashMap::new();
349
350 for rec in &report.recommendations {
351 if rec.confidence >= self.config.confidence_threshold {
352 config.insert(rec.parameter.clone(), rec.recommended_value.clone());
353 }
354 }
355
356 config
357 }
358
359 pub fn quick_tune(&self, workload_type: WorkloadType) -> HashMap<String, String> {
361 let mut config = HashMap::new();
362
363 match workload_type {
364 WorkloadType::ReadHeavy => {
365 config.insert("cache_size".to_string(), "1GB".to_string());
366 config.insert("bloom_filter".to_string(), "large".to_string());
367 config.insert("backend".to_string(), "Sled".to_string());
368 config.insert("compression".to_string(), "disabled".to_string());
369 }
370 WorkloadType::WriteHeavy => {
371 config.insert("cache_size".to_string(), "256MB".to_string());
372 config.insert("backend".to_string(), "ParityDB".to_string());
373 config.insert("deduplication".to_string(), "enabled".to_string());
374 config.insert("batch_size".to_string(), "100".to_string());
375 }
376 WorkloadType::Balanced => {
377 config.insert("cache_size".to_string(), "512MB".to_string());
378 config.insert("bloom_filter".to_string(), "medium".to_string());
379 config.insert("backend".to_string(), "ParityDB".to_string());
380 }
381 WorkloadType::BatchOriented => {
382 config.insert("batch_size".to_string(), "1000".to_string());
383 config.insert("concurrency".to_string(), "16".to_string());
384 config.insert("backend".to_string(), "ParityDB".to_string());
385 }
386 WorkloadType::Mixed => {
387 config.insert("cache_size".to_string(), "512MB".to_string());
388 config.insert("backend".to_string(), "ParityDB".to_string());
389 }
390 }
391
392 config
393 }
394}
395
396pub struct TuningPresets;
398
399impl TuningPresets {
400 pub fn conservative() -> AutoTunerConfig {
402 AutoTunerConfig {
403 observation_period: Duration::from_secs(600), confidence_threshold: 0.85,
405 target_cache_hit_rate: 0.80,
406 target_bloom_fp_rate: 0.01,
407 aggressive: false,
408 }
409 }
410
411 pub fn balanced() -> AutoTunerConfig {
413 AutoTunerConfig::default()
414 }
415
416 pub fn aggressive() -> AutoTunerConfig {
418 AutoTunerConfig {
419 observation_period: Duration::from_secs(300), confidence_threshold: 0.6,
421 target_cache_hit_rate: 0.90,
422 target_bloom_fp_rate: 0.005,
423 aggressive: true,
424 }
425 }
426
427 pub fn performance() -> AutoTunerConfig {
429 AutoTunerConfig {
430 observation_period: Duration::from_secs(300),
431 confidence_threshold: 0.7,
432 target_cache_hit_rate: 0.95,
433 target_bloom_fp_rate: 0.001,
434 aggressive: true,
435 }
436 }
437
438 pub fn cost_optimized() -> AutoTunerConfig {
440 AutoTunerConfig {
441 observation_period: Duration::from_secs(900), confidence_threshold: 0.9,
443 target_cache_hit_rate: 0.75,
444 target_bloom_fp_rate: 0.02,
445 aggressive: false,
446 }
447 }
448}
449
450#[cfg(test)]
451mod tests {
452 use super::*;
453 use crate::analyzer::{SizeDistribution, StorageAnalysis, WorkloadCharacterization};
454 use crate::diagnostics::{DiagnosticsReport, HealthMetrics, PerformanceMetrics};
455
456 fn create_test_analysis(workload_type: WorkloadType) -> StorageAnalysis {
457 let diagnostics = DiagnosticsReport {
458 backend: "Sled".to_string(),
459 total_blocks: 10_000,
460 performance: PerformanceMetrics {
461 avg_write_latency: Duration::from_micros(1000),
462 avg_read_latency: Duration::from_micros(500),
463 avg_batch_write_latency: Duration::from_millis(50),
464 avg_batch_read_latency: Duration::from_millis(20),
465 write_throughput: 500.0,
466 read_throughput: 1000.0,
467 peak_memory_usage: 100_000_000,
468 },
469 health: HealthMetrics {
470 successful_ops: 9000,
471 failed_ops: 0,
472 success_rate: 1.0,
473 integrity_ok: true,
474 responsive: true,
475 },
476 recommendations: Vec::new(),
477 health_score: 85,
478 };
479
480 StorageAnalysis {
481 backend: "Sled".to_string(),
482 diagnostics,
483 performance_breakdown: HashMap::new(),
484 workload: WorkloadCharacterization {
485 read_write_ratio: 0.7,
486 avg_block_size: 32768,
487 size_distribution: SizeDistribution {
488 small_pct: 0.3,
489 medium_pct: 0.5,
490 large_pct: 0.2,
491 },
492 workload_type,
493 },
494 recommendations: Vec::new(),
495 grade: "B".to_string(),
496 }
497 }
498
499 #[tokio::test]
500 async fn test_auto_tuner_cache_recommendation() {
501 let tuner = AutoTuner::default_config();
502 let analysis = create_test_analysis(WorkloadType::ReadHeavy);
503
504 let cache_rec = tuner.tune_cache(&analysis);
506 assert!(cache_rec.is_some());
507
508 let rec = cache_rec.unwrap();
509 assert_eq!(rec.parameter, "cache_size");
510 assert!(rec.confidence > 0.0);
511 }
512
513 #[tokio::test]
514 async fn test_auto_tuner_backend_recommendation() {
515 let tuner = AutoTuner::default_config();
516 let mut analysis = create_test_analysis(WorkloadType::WriteHeavy);
517 analysis.backend = "Sled".to_string();
518
519 let backend_rec = tuner.tune_backend_selection(&analysis);
520 assert!(backend_rec.is_some());
521
522 let rec = backend_rec.unwrap();
523 assert_eq!(rec.parameter, "backend");
524 assert_eq!(rec.recommended_value, "ParityDB");
525 }
526
527 #[tokio::test]
528 async fn test_quick_tune() {
529 let tuner = AutoTuner::default_config();
530
531 let read_config = tuner.quick_tune(WorkloadType::ReadHeavy);
532 assert_eq!(read_config.get("backend"), Some(&"Sled".to_string()));
533
534 let write_config = tuner.quick_tune(WorkloadType::WriteHeavy);
535 assert_eq!(write_config.get("backend"), Some(&"ParityDB".to_string()));
536 }
537
538 #[test]
539 fn test_tuning_presets() {
540 let _conservative = TuningPresets::conservative();
541 let _balanced = TuningPresets::balanced();
542 let _aggressive = TuningPresets::aggressive();
543 let _performance = TuningPresets::performance();
544 let _cost = TuningPresets::cost_optimized();
545 }
546}