Skip to main content

turbo_cdn/
adaptive_speed_controller.rs

1// Licensed under the MIT License
2// Copyright (c) 2025 Hal <hal.long@outlook.com>
3
4//! Adaptive speed controller with dynamic optimization
5//!
6//! This module implements real-time speed detection and adaptive
7//! parameter adjustment for optimal download performance.
8
9use std::collections::VecDeque;
10use std::sync::Arc;
11use std::time::{Duration, Instant};
12use tokio::sync::RwLock;
13use tracing::{debug, info};
14
15/// Speed measurement sample
16#[derive(Debug, Clone)]
17pub struct SpeedSample {
18    /// Timestamp of measurement
19    pub timestamp: Instant,
20    /// Download speed in bytes per second
21    pub speed: f64,
22    /// Bytes downloaded in this sample
23    pub bytes: u64,
24    /// Duration of this sample
25    pub duration: Duration,
26    /// Concurrent connections used
27    pub concurrent_connections: u32,
28    /// Chunk size used
29    pub chunk_size: u64,
30    /// Server URL
31    pub server_url: String,
32}
33
34/// Network condition assessment
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum NetworkCondition {
37    /// Excellent network (>50 MB/s)
38    Excellent,
39    /// Good network (10-50 MB/s)
40    Good,
41    /// Fair network (1-10 MB/s)
42    Fair,
43    /// Poor network (100KB-1MB/s)
44    Poor,
45    /// Very poor network (<100KB/s)
46    VeryPoor,
47}
48
49impl NetworkCondition {
50    /// Determine network condition from speed
51    pub fn from_speed(speed_mbps: f64) -> Self {
52        match speed_mbps {
53            s if s >= 50.0 => NetworkCondition::Excellent,
54            s if s >= 10.0 => NetworkCondition::Good,
55            s if s >= 1.0 => NetworkCondition::Fair,
56            s if s >= 0.1 => NetworkCondition::Poor,
57            _ => NetworkCondition::VeryPoor,
58        }
59    }
60
61    /// Get recommended concurrent connections for this condition
62    pub fn recommended_concurrency(&self) -> u32 {
63        match self {
64            NetworkCondition::Excellent => 64,
65            NetworkCondition::Good => 32,
66            NetworkCondition::Fair => 16,
67            NetworkCondition::Poor => 8,
68            NetworkCondition::VeryPoor => 4,
69        }
70    }
71
72    /// Get recommended chunk size for this condition
73    pub fn recommended_chunk_size(&self) -> u64 {
74        match self {
75            NetworkCondition::Excellent => 8 * 1024 * 1024, // 8MB
76            NetworkCondition::Good => 4 * 1024 * 1024,      // 4MB
77            NetworkCondition::Fair => 2 * 1024 * 1024,      // 2MB
78            NetworkCondition::Poor => 1024 * 1024,          // 1MB
79            NetworkCondition::VeryPoor => 512 * 1024,       // 512KB
80        }
81    }
82}
83
84/// Adaptive download parameters
85#[derive(Debug, Clone)]
86pub struct AdaptiveParams {
87    /// Current concurrent connections
88    pub concurrent_connections: u32,
89    /// Current chunk size
90    pub chunk_size: u64,
91    /// Current timeout
92    pub timeout: Duration,
93    /// Current retry attempts
94    pub retry_attempts: u32,
95    /// Network condition assessment
96    pub network_condition: NetworkCondition,
97    /// Confidence in current parameters (0.0 to 1.0)
98    pub confidence: f64,
99    /// Last adjustment time
100    pub last_adjusted: Instant,
101}
102
103impl Default for AdaptiveParams {
104    fn default() -> Self {
105        Self {
106            concurrent_connections: 16,
107            chunk_size: 2 * 1024 * 1024, // 2MB
108            timeout: Duration::from_secs(30),
109            retry_attempts: 3,
110            network_condition: NetworkCondition::Fair,
111            confidence: 0.5,
112            last_adjusted: Instant::now(),
113        }
114    }
115}
116
117/// Adaptive speed controller
118pub struct AdaptiveSpeedController {
119    /// Recent speed samples
120    samples: Arc<RwLock<VecDeque<SpeedSample>>>,
121    /// Current adaptive parameters
122    params: Arc<RwLock<AdaptiveParams>>,
123    /// Maximum samples to keep
124    max_samples: usize,
125    /// Minimum samples needed for adaptation
126    min_samples_for_adaptation: usize,
127    /// Adaptation interval
128    adaptation_interval: Duration,
129    /// Speed trend analyzer
130    trend_analyzer: SpeedTrendAnalyzer,
131}
132
133impl AdaptiveSpeedController {
134    /// Create a new adaptive speed controller
135    pub fn new() -> Self {
136        Self {
137            samples: Arc::new(RwLock::new(VecDeque::new())),
138            params: Arc::new(RwLock::new(AdaptiveParams::default())),
139            max_samples: 100,
140            min_samples_for_adaptation: 5,
141            adaptation_interval: Duration::from_secs(10),
142            trend_analyzer: SpeedTrendAnalyzer::new(),
143        }
144    }
145
146    /// Record a speed sample
147    pub async fn record_sample(&self, sample: SpeedSample) {
148        let mut samples = self.samples.write().await;
149
150        // Add new sample
151        samples.push_back(sample.clone());
152
153        // Remove old samples if we exceed max
154        while samples.len() > self.max_samples {
155            samples.pop_front();
156        }
157
158        debug!(
159            "Recorded speed sample: {:.2} MB/s ({} bytes in {:?})",
160            sample.speed / 1024.0 / 1024.0,
161            sample.bytes,
162            sample.duration
163        );
164
165        // Trigger adaptation if enough samples and time has passed
166        let params = self.params.read().await;
167        if samples.len() >= self.min_samples_for_adaptation
168            && params.last_adjusted.elapsed() >= self.adaptation_interval
169        {
170            drop(params);
171            drop(samples);
172            self.adapt_parameters().await;
173        }
174    }
175
176    /// Adapt parameters based on recent performance
177    async fn adapt_parameters(&self) {
178        let samples = self.samples.read().await;
179        if samples.len() < self.min_samples_for_adaptation {
180            return;
181        }
182
183        // Analyze recent performance
184        let recent_samples: Vec<_> = samples.iter().rev().take(10).cloned().collect();
185        let avg_speed =
186            recent_samples.iter().map(|s| s.speed).sum::<f64>() / recent_samples.len() as f64;
187        let speed_mbps = avg_speed / 1024.0 / 1024.0;
188
189        // Determine network condition
190        let network_condition = NetworkCondition::from_speed(speed_mbps);
191
192        // Analyze speed trend
193        let trend = self.trend_analyzer.analyze_trend(&recent_samples);
194
195        let mut params = self.params.write().await;
196        let old_params = params.clone();
197
198        // Update network condition
199        params.network_condition = network_condition;
200
201        // Adapt concurrent connections based on performance
202        let new_concurrency = self
203            .calculate_optimal_concurrency(&recent_samples, &trend)
204            .await;
205        params.concurrent_connections = new_concurrency;
206
207        // Adapt chunk size based on network condition and performance
208        let new_chunk_size = self
209            .calculate_optimal_chunk_size(&recent_samples, &trend)
210            .await;
211        params.chunk_size = new_chunk_size;
212
213        // Adapt timeout based on response times
214        let avg_response_time = recent_samples
215            .iter()
216            .map(|s| s.duration.as_millis() as f64)
217            .sum::<f64>()
218            / recent_samples.len() as f64;
219        params.timeout =
220            Duration::from_millis((avg_response_time * 3.0) as u64).max(Duration::from_secs(10));
221
222        // Update confidence based on performance stability
223        params.confidence = self.calculate_confidence(&recent_samples);
224        params.last_adjusted = Instant::now();
225
226        info!("Adapted parameters: concurrency {} -> {}, chunk_size {}KB -> {}KB, condition {:?}, confidence {:.2}",
227              old_params.concurrent_connections, params.concurrent_connections,
228              old_params.chunk_size / 1024, params.chunk_size / 1024,
229              params.network_condition, params.confidence);
230    }
231
232    /// Calculate optimal concurrency based on recent performance
233    async fn calculate_optimal_concurrency(
234        &self,
235        samples: &[SpeedSample],
236        trend: &SpeedTrend,
237    ) -> u32 {
238        if samples.is_empty() {
239            return 16; // Default
240        }
241
242        // Group samples by concurrency level
243        let mut concurrency_performance: std::collections::HashMap<u32, Vec<f64>> =
244            std::collections::HashMap::new();
245        for sample in samples {
246            concurrency_performance
247                .entry(sample.concurrent_connections)
248                .or_default()
249                .push(sample.speed);
250        }
251
252        // Find the concurrency level with best average performance
253        let mut best_concurrency = 16;
254        let mut best_speed = 0.0;
255
256        for (concurrency, speeds) in concurrency_performance {
257            let avg_speed = speeds.iter().sum::<f64>() / speeds.len() as f64;
258            if avg_speed > best_speed {
259                best_speed = avg_speed;
260                best_concurrency = concurrency;
261            }
262        }
263
264        // Adjust based on trend
265        match trend {
266            SpeedTrend::Improving => (best_concurrency as f64 * 1.2) as u32,
267            SpeedTrend::Declining => (best_concurrency as f64 * 0.8) as u32,
268            SpeedTrend::Stable => best_concurrency,
269        }
270        .clamp(4, 128) // Reasonable bounds
271    }
272
273    /// Calculate optimal chunk size based on recent performance
274    async fn calculate_optimal_chunk_size(
275        &self,
276        samples: &[SpeedSample],
277        _trend: &SpeedTrend,
278    ) -> u64 {
279        if samples.is_empty() {
280            return 2 * 1024 * 1024; // Default 2MB
281        }
282
283        // Group samples by chunk size
284        let mut chunk_performance: std::collections::HashMap<u64, Vec<f64>> =
285            std::collections::HashMap::new();
286        for sample in samples {
287            chunk_performance
288                .entry(sample.chunk_size)
289                .or_default()
290                .push(sample.speed);
291        }
292
293        // Find the chunk size with best average performance
294        let mut best_chunk_size = 2 * 1024 * 1024;
295        let mut best_speed = 0.0;
296
297        for (chunk_size, speeds) in chunk_performance {
298            let avg_speed = speeds.iter().sum::<f64>() / speeds.len() as f64;
299            if avg_speed > best_speed {
300                best_speed = avg_speed;
301                best_chunk_size = chunk_size;
302            }
303        }
304
305        // Adjust based on network condition
306        let params = self.params.read().await;
307        let recommended = params.network_condition.recommended_chunk_size();
308
309        // Blend optimal and recommended
310        let blended = (best_chunk_size + recommended) / 2;
311        blended.clamp(128 * 1024, 16 * 1024 * 1024) // 128KB to 16MB
312    }
313
314    /// Calculate confidence in current parameters
315    fn calculate_confidence(&self, samples: &[SpeedSample]) -> f64 {
316        if samples.len() < 3 {
317            return 0.3; // Low confidence with few samples
318        }
319
320        // Calculate coefficient of variation (stability measure)
321        let speeds: Vec<f64> = samples.iter().map(|s| s.speed).collect();
322        let mean = speeds.iter().sum::<f64>() / speeds.len() as f64;
323        let variance = speeds.iter().map(|s| (s - mean).powi(2)).sum::<f64>() / speeds.len() as f64;
324        let std_dev = variance.sqrt();
325        let cv = std_dev / mean;
326
327        // Lower coefficient of variation = higher confidence
328        (1.0 - cv.min(1.0)).max(0.1)
329    }
330
331    /// Get current adaptive parameters
332    pub async fn get_params(&self) -> AdaptiveParams {
333        self.params.read().await.clone()
334    }
335
336    /// Get current average speed
337    pub async fn get_current_speed(&self) -> Option<f64> {
338        let samples = self.samples.read().await;
339        if samples.is_empty() {
340            return None;
341        }
342
343        let recent_samples: Vec<_> = samples.iter().rev().take(5).collect();
344        let avg_speed =
345            recent_samples.iter().map(|s| s.speed).sum::<f64>() / recent_samples.len() as f64;
346        Some(avg_speed)
347    }
348
349    /// Get speed statistics
350    pub async fn get_speed_stats(&self) -> SpeedStats {
351        let samples = self.samples.read().await;
352
353        if samples.is_empty() {
354            return SpeedStats::default();
355        }
356
357        let speeds: Vec<f64> = samples.iter().map(|s| s.speed).collect();
358        let min_speed = speeds.iter().cloned().fold(f64::INFINITY, f64::min);
359        let max_speed = speeds.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
360        let avg_speed = speeds.iter().sum::<f64>() / speeds.len() as f64;
361
362        SpeedStats {
363            min_speed,
364            max_speed,
365            avg_speed,
366            sample_count: samples.len(),
367            latest_speed: samples.back().map(|s| s.speed),
368        }
369    }
370
371    /// Clear all samples and reset parameters
372    pub async fn reset(&self) {
373        let mut samples = self.samples.write().await;
374        samples.clear();
375
376        let mut params = self.params.write().await;
377        *params = AdaptiveParams::default();
378
379        info!("Adaptive speed controller reset");
380    }
381}
382
383/// Speed trend analysis
384#[derive(Debug, Clone, Copy, PartialEq, Eq)]
385pub enum SpeedTrend {
386    Improving,
387    Stable,
388    Declining,
389}
390
391/// Speed trend analyzer
392struct SpeedTrendAnalyzer;
393
394impl SpeedTrendAnalyzer {
395    fn new() -> Self {
396        Self
397    }
398
399    fn analyze_trend(&self, samples: &[SpeedSample]) -> SpeedTrend {
400        if samples.len() < 3 {
401            return SpeedTrend::Stable;
402        }
403
404        let speeds: Vec<f64> = samples.iter().map(|s| s.speed).collect();
405        let first_half_avg =
406            speeds[..speeds.len() / 2].iter().sum::<f64>() / (speeds.len() / 2) as f64;
407        let second_half_avg = speeds[speeds.len() / 2..].iter().sum::<f64>()
408            / (speeds.len() - speeds.len() / 2) as f64;
409
410        let change_ratio = (second_half_avg - first_half_avg) / first_half_avg;
411
412        if change_ratio > 0.1 {
413            SpeedTrend::Improving
414        } else if change_ratio < -0.1 {
415            SpeedTrend::Declining
416        } else {
417            SpeedTrend::Stable
418        }
419    }
420}
421
422/// Speed statistics
423#[derive(Debug, Clone)]
424pub struct SpeedStats {
425    pub min_speed: f64,
426    pub max_speed: f64,
427    pub avg_speed: f64,
428    pub sample_count: usize,
429    pub latest_speed: Option<f64>,
430}
431
432impl Default for SpeedStats {
433    fn default() -> Self {
434        Self {
435            min_speed: 0.0,
436            max_speed: 0.0,
437            avg_speed: 0.0,
438            sample_count: 0,
439            latest_speed: None,
440        }
441    }
442}
443
444impl Default for AdaptiveSpeedController {
445    fn default() -> Self {
446        Self::new()
447    }
448}
449
450#[cfg(test)]
451mod tests {
452    use super::*;
453
454    #[tokio::test]
455    async fn test_speed_recording() {
456        let controller = AdaptiveSpeedController::new();
457
458        let sample = SpeedSample {
459            timestamp: Instant::now(),
460            speed: 10.0 * 1024.0 * 1024.0, // 10 MB/s
461            bytes: 1024 * 1024,
462            duration: Duration::from_millis(100),
463            concurrent_connections: 16,
464            chunk_size: 2 * 1024 * 1024,
465            server_url: "https://example.com".to_string(),
466        };
467
468        controller.record_sample(sample).await;
469
470        let stats = controller.get_speed_stats().await;
471        assert_eq!(stats.sample_count, 1);
472        assert!(stats.avg_speed > 0.0);
473    }
474
475    #[test]
476    fn test_network_condition_classification() {
477        assert_eq!(
478            NetworkCondition::from_speed(100.0),
479            NetworkCondition::Excellent
480        );
481        assert_eq!(NetworkCondition::from_speed(20.0), NetworkCondition::Good);
482        assert_eq!(NetworkCondition::from_speed(5.0), NetworkCondition::Fair);
483        assert_eq!(NetworkCondition::from_speed(0.5), NetworkCondition::Poor);
484        assert_eq!(
485            NetworkCondition::from_speed(0.05),
486            NetworkCondition::VeryPoor
487        );
488    }
489}