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#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct ServerStatSnapshot {
28 pub host: String,
30 pub download_speed: u64,
32 pub single_connection_avg_speed: u64,
34 pub multi_connection_avg_speed: u64,
36 pub last_updated: u64,
38 pub status: u32,
40 pub counter: u32,
42 pub last_error_time: Option<u64>,
44 pub last_error_code: u16,
46 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 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 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 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 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 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; }
201 true
202 }
203
204 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 pub fn get_last_error_code(&self) -> u16 {
213 self.last_error_code.unwrap_or(0)
214 }
215
216 pub fn get_consecutive_failures(&self) -> u32 {
218 self.consecutive_failures
219 }
220
221 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 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 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); stat.update_speed(2000, false);
346 assert_eq!(stat.get_single_avg_speed(), 1610); }
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 #[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 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 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 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 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 #[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 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); 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 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()); }
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 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 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 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}