Skip to main content

aria2_core/selector/
server_stat.rs

1use serde::{Deserialize, Serialize};
2use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
3use std::time::{SystemTime, UNIX_EPOCH};
4
5const EMA_ALPHA: f64 = 0.7;
6
7/// Serializable snapshot of ServerStat for persistence.
8///
9/// This struct contains all the persistent fields of ServerStat in a
10/// serde-compatible format (no atomic types). Used for saving and loading
11/// server performance statistics across restarts.
12///
13/// # Example
14///
15/// ```
16/// use aria2_core::selector::server_stat::{ServerStat, ServerStatSnapshot};
17///
18/// let stat = ServerStat::new("mirror.example.com");
19/// stat.update_speed(5000, false);
20///
21/// let snapshot = stat.to_snapshot();
22/// let restored = ServerStat::from_snapshot(&snapshot);
23///
24/// assert_eq!(restored.host, stat.host);
25/// ```
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct ServerStatSnapshot {
28    /// Server hostname (e.g., "mirror1.example.com")
29    pub host: String,
30    /// Current download speed in bytes/sec
31    pub download_speed: u64,
32    /// Exponential moving average of single-connection download speed
33    pub single_connection_avg_speed: u64,
34    /// Exponential moving average of multi-connection download speed
35    pub multi_connection_avg_speed: u64,
36    /// Unix timestamp of last update
37    pub last_updated: u64,
38    /// Server status: 0 = OK, 1 = Error
39    pub status: u32,
40    /// Usage counter (number of times this server was selected)
41    pub counter: u32,
42    /// Unix timestamp of last error (None if never failed)
43    pub last_error_time: Option<u64>,
44    /// HTTP error code of last error (0 if never failed)
45    pub last_error_code: u16,
46    /// Number of consecutive failures (reset on success)
47    pub consecutive_failures: u32,
48}
49
50#[derive(Debug)]
51pub struct ServerStat {
52    pub host: String,
53    download_speed: AtomicU64,
54    single_connection_avg_speed: AtomicU64,
55    multi_connection_avg_speed: AtomicU64,
56    last_updated: AtomicU64,
57    status: AtomicU32,
58    counter: AtomicU32,
59    // Fields for failure tracking and availability cooldown
60    // Note: These use plain types (not atomic) because ServerStat is already
61    // protected by RwLock in ServerStatMan's internal HashMap
62    pub last_error_time: Option<SystemTime>,
63    pub last_error_code: Option<u16>,
64    pub consecutive_failures: u32,
65}
66
67impl Clone for ServerStat {
68    fn clone(&self) -> Self {
69        Self {
70            host: self.host.clone(),
71            download_speed: AtomicU64::new(self.download_speed.load(Ordering::Relaxed)),
72            single_connection_avg_speed: AtomicU64::new(
73                self.single_connection_avg_speed.load(Ordering::Relaxed),
74            ),
75            multi_connection_avg_speed: AtomicU64::new(
76                self.multi_connection_avg_speed.load(Ordering::Relaxed),
77            ),
78            last_updated: AtomicU64::new(self.last_updated.load(Ordering::Relaxed)),
79            status: AtomicU32::new(self.status.load(Ordering::Relaxed)),
80            counter: AtomicU32::new(self.counter.load(Ordering::Relaxed)),
81            // Clone new fields
82            last_error_time: self.last_error_time,
83            last_error_code: self.last_error_code,
84            consecutive_failures: self.consecutive_failures,
85        }
86    }
87}
88
89impl ServerStat {
90    pub fn new(host: &str) -> Self {
91        Self {
92            host: host.to_string(),
93            download_speed: AtomicU64::new(0),
94            single_connection_avg_speed: AtomicU64::new(0),
95            multi_connection_avg_speed: AtomicU64::new(0),
96            last_updated: AtomicU64::new(0),
97            status: AtomicU32::new(0),
98            counter: AtomicU32::new(0),
99            // Initialize new fields
100            last_error_time: None,
101            last_error_code: None,
102            consecutive_failures: 0,
103        }
104    }
105
106    pub fn update_speed(&self, speed: u64, is_multi: bool) {
107        self.download_speed.store(speed, Ordering::Relaxed);
108        if is_multi {
109            let old = self.multi_connection_avg_speed.load(Ordering::Relaxed);
110            let new_val = ema(old, speed);
111            self.multi_connection_avg_speed
112                .store(new_val, Ordering::Relaxed);
113        } else {
114            let old = self.single_connection_avg_speed.load(Ordering::Relaxed);
115            let new_val = ema(old, speed);
116            self.single_connection_avg_speed
117                .store(new_val, Ordering::Relaxed);
118        }
119        self.touch();
120    }
121
122    pub fn get_download_speed(&self) -> u64 {
123        self.download_speed.load(Ordering::Relaxed)
124    }
125
126    pub fn get_single_avg_speed(&self) -> u64 {
127        self.single_connection_avg_speed.load(Ordering::Relaxed)
128    }
129
130    pub fn get_multi_avg_speed(&self) -> u64 {
131        self.multi_connection_avg_speed.load(Ordering::Relaxed)
132    }
133
134    pub fn get_avg_speed(&self) -> u64 {
135        let s = self.single_connection_avg_speed.load(Ordering::Relaxed);
136        let m = self.multi_connection_avg_speed.load(Ordering::Relaxed);
137        if s > 0 && m > 0 {
138            (s + m) / 2
139        } else {
140            s.max(m)
141        }
142    }
143
144    pub fn is_ok(&self) -> bool {
145        self.status.load(Ordering::Relaxed) == 0
146    }
147
148    pub fn set_error(&self) {
149        self.status.store(1, Ordering::Relaxed);
150    }
151
152    pub fn reset_status(&self) {
153        self.status.store(0, Ordering::Relaxed);
154    }
155
156    pub fn increment_counter(&self) -> u32 {
157        self.counter.fetch_add(1, Ordering::Relaxed).wrapping_add(1)
158    }
159
160    pub fn get_counter(&self) -> u32 {
161        self.counter.load(Ordering::Relaxed)
162    }
163
164    pub fn reset_counter(&self) {
165        self.counter.store(0, Ordering::Relaxed);
166    }
167
168    pub fn is_fresh(&self, duration_secs: u64) -> bool {
169        let last = self.last_updated.load(Ordering::Relaxed);
170        if last == 0 {
171            return false;
172        }
173        let now = SystemTime::now()
174            .duration_since(UNIX_EPOCH)
175            .unwrap_or_default()
176            .as_secs();
177        now.saturating_sub(last) < duration_secs
178    }
179
180    /// Get the last update timestamp as unix timestamp (0 if never updated).
181    pub fn get_last_updated(&self) -> u64 {
182        self.last_updated.load(Ordering::Relaxed)
183    }
184
185    fn touch(&self) {
186        let now = SystemTime::now()
187            .duration_since(UNIX_EPOCH)
188            .unwrap_or_default()
189            .as_secs();
190        self.last_updated.store(now, Ordering::Relaxed);
191    }
192
193    /// Check if server is available (not in cooldown due to consecutive failures)
194    pub fn is_available(&self) -> bool {
195        if self.consecutive_failures >= 3
196            && let Some(error_time) = self.last_error_time
197            && let Ok(elapsed) = error_time.elapsed()
198        {
199            return elapsed.as_secs() > 60; // cooldown expired?
200        }
201        true
202    }
203
204    /// Get the last error time as unix timestamp (0 if never failed)
205    pub fn get_last_error_time(&self) -> u64 {
206        self.last_error_time
207            .map(|t| t.duration_since(UNIX_EPOCH).unwrap_or_default().as_secs())
208            .unwrap_or(0)
209    }
210
211    /// Get the last error code (0 if never failed)
212    pub fn get_last_error_code(&self) -> u16 {
213        self.last_error_code.unwrap_or(0)
214    }
215
216    /// Get consecutive failure count
217    pub fn get_consecutive_failures(&self) -> u32 {
218        self.consecutive_failures
219    }
220
221    /// Set error tracking fields (for use by ServerStatMan::mark_failure)
222    /// Note: Requires &mut self because these fields are not atomic
223    pub fn set_failure_info(&mut self, error_code: u16) {
224        self.last_error_time = Some(SystemTime::now());
225        self.last_error_code = Some(error_code);
226        self.consecutive_failures += 1;
227    }
228
229    // ==================== Persistence Methods ====================
230
231    /// Convert to a serializable snapshot for persistence.
232    ///
233    /// Extracts all atomic values into a plain struct suitable for
234    /// JSON serialization. The snapshot captures the current state
235    /// of all performance metrics and error tracking.
236    ///
237    /// # Example
238    ///
239    /// ```
240    /// use aria2_core::selector::server_stat::ServerStat;
241    ///
242    /// let stat = ServerStat::new("fast.mirror.com");
243    /// stat.update_speed(10000, false);
244    /// stat.increment_counter();
245    ///
246    /// let snapshot = stat.to_snapshot();
247    /// assert_eq!(snapshot.host, "fast.mirror.com");
248    /// assert_eq!(snapshot.counter, 1);
249    /// ```
250    pub fn to_snapshot(&self) -> ServerStatSnapshot {
251        ServerStatSnapshot {
252            host: self.host.clone(),
253            download_speed: self.download_speed.load(Ordering::Relaxed),
254            single_connection_avg_speed: self.single_connection_avg_speed.load(Ordering::Relaxed),
255            multi_connection_avg_speed: self.multi_connection_avg_speed.load(Ordering::Relaxed),
256            last_updated: self.last_updated.load(Ordering::Relaxed),
257            status: self.status.load(Ordering::Relaxed),
258            counter: self.counter.load(Ordering::Relaxed),
259            last_error_time: self
260                .last_error_time
261                .and_then(|t| t.duration_since(UNIX_EPOCH).ok().map(|d| d.as_secs())),
262            last_error_code: self.last_error_code.unwrap_or(0),
263            consecutive_failures: self.consecutive_failures,
264        }
265    }
266
267    /// Restore from a serialized snapshot.
268    ///
269    /// Creates a new ServerStat instance with all fields initialized
270    /// from the snapshot data. Atomic fields are set to the snapshot values.
271    ///
272    /// # Arguments
273    ///
274    /// * `snapshot` - The serialized server statistics to restore from
275    ///
276    /// # Example
277    ///
278    /// ```
279    /// use aria2_core::selector::server_stat::{ServerStat, ServerStatSnapshot};
280    ///
281    /// let snapshot = ServerStatSnapshot {
282    ///     host: "restored.mirror.com".to_string(),
283    ///     download_speed: 5000,
284    ///     single_connection_avg_speed: 4500,
285    ///     multi_connection_avg_speed: 8000,
286    ///     last_updated: 1700000000,
287    ///     status: 0,
288    ///     counter: 10,
289    ///     last_error_time: None,
290    ///     last_error_code: 0,
291    ///     consecutive_failures: 0,
292    /// };
293    ///
294    /// let stat = ServerStat::from_snapshot(&snapshot);
295    /// assert_eq!(stat.host, "restored.mirror.com");
296    /// assert_eq!(stat.get_counter(), 10);
297    /// ```
298    pub fn from_snapshot(snapshot: &ServerStatSnapshot) -> Self {
299        Self {
300            host: snapshot.host.clone(),
301            download_speed: AtomicU64::new(snapshot.download_speed),
302            single_connection_avg_speed: AtomicU64::new(snapshot.single_connection_avg_speed),
303            multi_connection_avg_speed: AtomicU64::new(snapshot.multi_connection_avg_speed),
304            last_updated: AtomicU64::new(snapshot.last_updated),
305            status: AtomicU32::new(snapshot.status),
306            counter: AtomicU32::new(snapshot.counter),
307            last_error_time: snapshot
308                .last_error_time
309                .and_then(|ts| UNIX_EPOCH.checked_add(std::time::Duration::from_secs(ts))),
310            last_error_code: if snapshot.last_error_code > 0 {
311                Some(snapshot.last_error_code)
312            } else {
313                None
314            },
315            consecutive_failures: snapshot.consecutive_failures,
316        }
317    }
318}
319
320fn ema(old: u64, new: u64) -> u64 {
321    (old as f64 * (1.0 - EMA_ALPHA) + new as f64 * EMA_ALPHA) as u64
322}
323
324#[cfg(test)]
325mod tests {
326    use super::*;
327
328    #[test]
329    fn test_creation() {
330        let stat = ServerStat::new("example.com");
331        assert_eq!(stat.host, "example.com");
332        assert_eq!(stat.get_download_speed(), 0);
333        assert_eq!(stat.get_single_avg_speed(), 0);
334        assert!(stat.is_ok());
335        assert_eq!(stat.get_counter(), 0);
336    }
337
338    #[test]
339    fn test_update_single_speed() {
340        let stat = ServerStat::new("example.com");
341        stat.update_speed(1000, false);
342        assert_eq!(stat.get_download_speed(), 1000);
343        assert_eq!(stat.get_single_avg_speed(), 700); // 0*0.3 + 1000*0.7
344
345        stat.update_speed(2000, false);
346        assert_eq!(stat.get_single_avg_speed(), 1610); // 700*0.3 + 2000*0.7
347    }
348
349    #[test]
350    fn test_update_multi_speed_independent() {
351        let stat = ServerStat::new("example.com");
352        stat.update_speed(1000, true);
353        assert_eq!(stat.get_multi_avg_speed(), 700);
354        assert_eq!(stat.get_single_avg_speed(), 0);
355
356        stat.update_speed(500, false);
357        assert_eq!(stat.get_single_avg_speed(), 350);
358        assert_eq!(stat.get_multi_avg_speed(), 700);
359    }
360
361    #[test]
362    fn test_get_avg_speed_combines_both() {
363        let stat = ServerStat::new("example.com");
364        stat.update_speed(1000, false);
365        stat.update_speed(2000, true);
366        let avg = stat.get_avg_speed();
367        assert!(avg > 0);
368        assert!((350..=1400).contains(&avg));
369    }
370
371    #[test]
372    fn test_status_toggle() {
373        let stat = ServerStat::new("example.com");
374        assert!(stat.is_ok());
375
376        stat.set_error();
377        assert!(!stat.is_ok());
378
379        stat.reset_status();
380        assert!(stat.is_ok());
381    }
382
383    #[test]
384    fn test_counter_operations() {
385        let stat = ServerStat::new("example.com");
386        assert_eq!(stat.get_counter(), 0);
387
388        let c1 = stat.increment_counter();
389        assert_eq!(c1, 1);
390        assert_eq!(stat.get_counter(), 1);
391
392        let c2 = stat.increment_counter();
393        assert_eq!(c2, 2);
394
395        stat.reset_counter();
396        assert_eq!(stat.get_counter(), 0);
397    }
398
399    #[test]
400    fn test_is_fresh_after_update() {
401        let stat = ServerStat::new("example.com");
402        assert!(!stat.is_fresh(60));
403
404        stat.update_speed(1000, false);
405        assert!(stat.is_fresh(60));
406        assert!(!stat.is_fresh(0));
407    }
408
409    #[test]
410    fn test_concurrent_updates() {
411        use std::sync::Arc;
412        use std::thread;
413
414        let stat = Arc::new(ServerStat::new("concurrent.test"));
415        let mut handles = Vec::new();
416
417        for i in 0..10u64 {
418            let s = Arc::clone(&stat);
419            handles.push(thread::spawn(move || {
420                s.update_speed((i + 1) * 1000, i % 2 == 0);
421            }));
422        }
423
424        for h in handles {
425            h.join().unwrap();
426        }
427
428        assert!(stat.get_download_speed() > 0);
429        assert!(stat.is_fresh(60));
430    }
431
432    // ======================================================================
433    // Tests for Availability Cooldown
434    // ======================================================================
435
436    #[test]
437    fn test_server_available_initially() {
438        let stat = ServerStat::new("fresh.server");
439        assert!(stat.is_available(), "New server should be available");
440    }
441
442    #[test]
443    fn test_server_available_with_few_failures() {
444        let mut stat = ServerStat::new("some.failures");
445        stat.consecutive_failures = 2;
446        stat.last_error_time = Some(SystemTime::now());
447        assert!(
448            stat.is_available(),
449            "Server with <3 failures should still be available"
450        );
451    }
452
453    #[test]
454    fn test_server_unavailable_after_3_failures() {
455        let mut stat = ServerStat::new("cooldown.server");
456        stat.consecutive_failures = 3;
457        stat.last_error_time = Some(SystemTime::now());
458        assert!(
459            !stat.is_available(),
460            "Server with 3+ recent failures should be unavailable"
461        );
462    }
463
464    #[test]
465    fn test_server_available_after_cooldown_expires() {
466        let mut stat = ServerStat::new("recovered.server");
467        stat.consecutive_failures = 5;
468        // Simulate error that happened more than 60 seconds ago
469        stat.last_error_time = Some(SystemTime::now() - std::time::Duration::from_secs(61));
470        assert!(
471            stat.is_available(),
472            "Server should become available after cooldown expires"
473        );
474    }
475
476    #[test]
477    fn test_set_failure_info() {
478        let mut stat = ServerStat::new("failure.test");
479
480        // Initially no failures
481        assert_eq!(stat.get_consecutive_failures(), 0);
482        assert_eq!(stat.get_last_error_code(), 0);
483        assert_eq!(stat.get_last_error_time(), 0);
484
485        // Set failure info
486        stat.set_failure_info(500);
487
488        assert_eq!(stat.get_consecutive_failures(), 1);
489        assert_eq!(stat.get_last_error_code(), 500);
490        assert!(stat.get_last_error_time() > 0);
491
492        // Set another failure
493        stat.set_failure_info(503);
494        assert_eq!(stat.get_consecutive_failures(), 2);
495        assert_eq!(stat.get_last_error_code(), 503);
496    }
497
498    // ======================================================================
499    // Tests for Persistence (Snapshot Roundtrip)
500    // ======================================================================
501
502    #[test]
503    fn test_snapshot_roundtrip_basic() {
504        let stat = ServerStat::new("snapshot.test");
505        stat.update_speed(5000, false);
506        stat.update_speed(10000, true);
507        stat.increment_counter();
508        stat.increment_counter();
509
510        let snapshot = stat.to_snapshot();
511        let restored = ServerStat::from_snapshot(&snapshot);
512
513        assert_eq!(restored.host, "snapshot.test");
514        assert_eq!(restored.get_download_speed(), 10000);
515        assert_eq!(restored.get_counter(), 2);
516        assert!(restored.get_single_avg_speed() > 0);
517        assert!(restored.get_multi_avg_speed() > 0);
518    }
519
520    #[test]
521    fn test_snapshot_roundtrip_with_failures() {
522        let mut stat = ServerStat::new("failed.snapshot.test");
523        stat.update_speed(3000, false);
524        stat.set_failure_info(500);
525        stat.set_failure_info(503);
526
527        let snapshot = stat.to_snapshot();
528        assert_eq!(snapshot.consecutive_failures, 2);
529        assert_eq!(snapshot.last_error_code, 503);
530        assert!(snapshot.last_error_time.is_some());
531
532        let restored = ServerStat::from_snapshot(&snapshot);
533        assert_eq!(restored.get_consecutive_failures(), 2);
534        assert_eq!(restored.get_last_error_code(), 503);
535        assert!(restored.get_last_error_time() > 0);
536    }
537
538    #[test]
539    fn test_snapshot_preserves_all_fields() {
540        let mut stat = ServerStat::new("complete.snapshot.test");
541        stat.update_speed(12345, false);
542        stat.update_speed(67890, true);
543        for _ in 0..5 {
544            stat.increment_counter();
545        }
546        stat.set_error();
547        stat.set_failure_info(502);
548
549        let snapshot = stat.to_snapshot();
550
551        // Verify all fields are captured
552        assert_eq!(snapshot.host, "complete.snapshot.test");
553        assert_eq!(snapshot.download_speed, 67890);
554        assert!(snapshot.single_connection_avg_speed > 0);
555        assert!(snapshot.multi_connection_avg_speed > 0);
556        assert!(snapshot.last_updated > 0);
557        assert_eq!(snapshot.status, 1); // Error status
558        assert_eq!(snapshot.counter, 5);
559        assert!(snapshot.last_error_time.is_some());
560        assert_eq!(snapshot.last_error_code, 502);
561        assert_eq!(snapshot.consecutive_failures, 1);
562
563        // Verify restoration preserves all fields
564        let restored = ServerStat::from_snapshot(&snapshot);
565        assert_eq!(restored.host, snapshot.host);
566        assert_eq!(restored.get_download_speed(), snapshot.download_speed);
567        assert_eq!(
568            restored.get_single_avg_speed(),
569            snapshot.single_connection_avg_speed
570        );
571        assert_eq!(
572            restored.get_multi_avg_speed(),
573            snapshot.multi_connection_avg_speed
574        );
575        assert_eq!(restored.get_counter(), snapshot.counter);
576        assert!(!restored.is_ok()); // Should have error status
577    }
578
579    #[test]
580    fn test_snapshot_json_serialization() {
581        let stat = ServerStat::new("json.test");
582        stat.update_speed(10000, false);
583        stat.increment_counter();
584
585        let snapshot = stat.to_snapshot();
586
587        // Serialize to JSON
588        let json = serde_json::to_string(&snapshot).expect("Should serialize to JSON");
589        assert!(json.contains("json.test"));
590        assert!(json.contains("10000"));
591
592        // Deserialize from JSON
593        let deserialized: ServerStatSnapshot =
594            serde_json::from_str(&json).expect("Should deserialize from JSON");
595        assert_eq!(deserialized.host, "json.test");
596        assert_eq!(deserialized.download_speed, 10000);
597        assert_eq!(deserialized.counter, 1);
598    }
599
600    #[test]
601    fn test_snapshot_zero_values() {
602        let stat = ServerStat::new("zero.test");
603        // No updates - all values should be zero/default
604
605        let snapshot = stat.to_snapshot();
606        assert_eq!(snapshot.download_speed, 0);
607        assert_eq!(snapshot.single_connection_avg_speed, 0);
608        assert_eq!(snapshot.multi_connection_avg_speed, 0);
609        assert_eq!(snapshot.counter, 0);
610        assert_eq!(snapshot.status, 0);
611        assert!(snapshot.last_error_time.is_none());
612        assert_eq!(snapshot.last_error_code, 0);
613        assert_eq!(snapshot.consecutive_failures, 0);
614
615        let restored = ServerStat::from_snapshot(&snapshot);
616        assert_eq!(restored.get_download_speed(), 0);
617        assert!(restored.is_ok());
618    }
619}