use std::{ collections::VecDeque, sync::atomic::{AtomicU64, Ordering}, time::{Duration, Instant}, }; use parking_lot::Mutex; struct ProgressSnapshot { downloaded_bytes: u64, instant: Instant, } pub struct SpeedEstimator { latest_per_second_snapshots: Mutex>, download_bytes_per_second: AtomicU64, time_remaining_millis: AtomicU64, } impl SpeedEstimator { pub fn new(window_seconds: usize) -> Self { assert!(window_seconds > 1); Self { latest_per_second_snapshots: Mutex::new(VecDeque::with_capacity(window_seconds)), download_bytes_per_second: Default::default(), time_remaining_millis: Default::default(), } } pub fn time_remaining(&self) -> Option { let tr = self.time_remaining_millis.load(Ordering::Relaxed); if tr == 0 { return None; } Some(Duration::from_millis(tr)) } pub fn download_bps(&self) -> u64 { self.download_bytes_per_second.load(Ordering::Relaxed) } pub fn download_mbps(&self) -> f64 { self.download_bps() as f64 / 1024f64 / 1024f64 } pub fn add_snapshot(&self, downloaded_bytes: u64, remaining_bytes: u64, instant: Instant) { let mut g = self.latest_per_second_snapshots.lock(); if g.len() < g.capacity() { g.push_back(ProgressSnapshot { downloaded_bytes, instant, }); return; } let first = g.pop_front().unwrap(); let downloaded_bytes_diff = downloaded_bytes - first.downloaded_bytes; let elapsed = instant - first.instant; let bps = downloaded_bytes_diff as f64 / elapsed.as_secs_f64(); let time_remaining_millis_rounded: u64 = if downloaded_bytes_diff > 0 { let time_remaining_secs = remaining_bytes as f64 / bps; (time_remaining_secs * 1000f64) as u64 } else { 0 }; self.time_remaining_millis .store(time_remaining_millis_rounded, Ordering::Relaxed); self.download_bytes_per_second .store(bps as u64, Ordering::Relaxed); g.push_back(ProgressSnapshot { downloaded_bytes, instant, }); } }