Display upload speed in Web UI
This commit is contained in:
parent
4784f3f14b
commit
80df2c1001
8 changed files with 89 additions and 55 deletions
|
|
@ -188,7 +188,8 @@ pub struct TorrentStateLive {
|
|||
cancel_tx: tokio::sync::watch::Sender<()>,
|
||||
cancel_rx: tokio::sync::watch::Receiver<()>,
|
||||
|
||||
speed_estimator: SpeedEstimator,
|
||||
down_speed_estimator: SpeedEstimator,
|
||||
up_speed_estimator: SpeedEstimator,
|
||||
}
|
||||
|
||||
impl TorrentStateLive {
|
||||
|
|
@ -198,7 +199,8 @@ impl TorrentStateLive {
|
|||
) -> Arc<Self> {
|
||||
let (peer_queue_tx, peer_queue_rx) = unbounded_channel();
|
||||
|
||||
let speed_estimator = SpeedEstimator::new(5);
|
||||
let down_speed_estimator = SpeedEstimator::new(5);
|
||||
let up_speed_estimator = SpeedEstimator::new(5);
|
||||
|
||||
let have_bytes = paused.have_bytes;
|
||||
let needed_bytes = paused.info.lengths.total_length() - have_bytes;
|
||||
|
|
@ -225,7 +227,8 @@ impl TorrentStateLive {
|
|||
peer_semaphore: Semaphore::new(128),
|
||||
peer_queue_tx,
|
||||
finished_notify: Notify::new(),
|
||||
speed_estimator,
|
||||
down_speed_estimator,
|
||||
up_speed_estimator,
|
||||
cancel_rx,
|
||||
cancel_tx,
|
||||
});
|
||||
|
|
@ -249,6 +252,7 @@ impl TorrentStateLive {
|
|||
Some(state) => state,
|
||||
None => return Ok(()),
|
||||
};
|
||||
let now = Instant::now();
|
||||
let stats = state.stats_snapshot();
|
||||
let fetched = stats.fetched_bytes;
|
||||
let needed = state.initially_needed();
|
||||
|
|
@ -257,8 +261,11 @@ impl TorrentStateLive {
|
|||
.wrapping_sub(fetched)
|
||||
.min(needed - stats.downloaded_and_checked_bytes);
|
||||
state
|
||||
.speed_estimator
|
||||
.add_snapshot(fetched, remaining, Instant::now());
|
||||
.down_speed_estimator
|
||||
.add_snapshot(fetched, Some(remaining), now);
|
||||
state
|
||||
.up_speed_estimator
|
||||
.add_snapshot(stats.uploaded_bytes, None, now);
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
}
|
||||
}
|
||||
|
|
@ -291,8 +298,12 @@ impl TorrentStateLive {
|
|||
});
|
||||
}
|
||||
|
||||
pub fn speed_estimator(&self) -> &SpeedEstimator {
|
||||
&self.speed_estimator
|
||||
pub fn down_speed_estimator(&self) -> &SpeedEstimator {
|
||||
&self.down_speed_estimator
|
||||
}
|
||||
|
||||
pub fn up_speed_estimator(&self) -> &SpeedEstimator {
|
||||
&self.up_speed_estimator
|
||||
}
|
||||
|
||||
async fn tracker_one_request(&self, tracker_url: Url) -> anyhow::Result<u64> {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ pub struct LiveStats {
|
|||
pub snapshot: StatsSnapshot,
|
||||
pub average_piece_download_time: Option<Duration>,
|
||||
pub download_speed: Speed,
|
||||
pub upload_speed: Speed,
|
||||
pub time_remaining: Option<DurationWithHumanReadable>,
|
||||
}
|
||||
|
||||
|
|
@ -17,8 +18,9 @@ impl std::fmt::Display for LiveStats {
|
|||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "down speed: {}", self.download_speed)?;
|
||||
if let Some(time_remaining) = &self.time_remaining {
|
||||
write!(f, " eta: {time_remaining}")?;
|
||||
write!(f, ", eta: {time_remaining}")?;
|
||||
}
|
||||
write!(f, ", up speed: {}", self.upload_speed)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -26,13 +28,17 @@ impl std::fmt::Display for LiveStats {
|
|||
impl From<&TorrentStateLive> for LiveStats {
|
||||
fn from(live: &TorrentStateLive) -> Self {
|
||||
let snapshot = live.stats_snapshot();
|
||||
let estimator = live.speed_estimator();
|
||||
let down_estimator = live.down_speed_estimator();
|
||||
let up_estimator = live.up_speed_estimator();
|
||||
|
||||
Self {
|
||||
average_piece_download_time: snapshot.average_piece_download_time(),
|
||||
snapshot,
|
||||
download_speed: estimator.download_mbps().into(),
|
||||
time_remaining: estimator.time_remaining().map(DurationWithHumanReadable),
|
||||
download_speed: down_estimator.mbps().into(),
|
||||
upload_speed: up_estimator.mbps().into(),
|
||||
time_remaining: down_estimator
|
||||
.time_remaining()
|
||||
.map(DurationWithHumanReadable),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
18
crates/librqbit/webui/dist/assets/index.js
vendored
18
crates/librqbit/webui/dist/assets/index.js
vendored
File diff suppressed because one or more lines are too long
2
crates/librqbit/webui/dist/manifest.json
vendored
2
crates/librqbit/webui/dist/manifest.json
vendored
|
|
@ -4,7 +4,7 @@
|
|||
"src": "assets/logo.svg"
|
||||
},
|
||||
"index.html": {
|
||||
"file": "assets/index-6d4556f3.js",
|
||||
"file": "assets/index-713a95fc.js",
|
||||
"isEntry": true,
|
||||
"src": "index.html"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,11 @@ export interface ListTorrentsResponse {
|
|||
torrents: Array<TorrentId>;
|
||||
}
|
||||
|
||||
export interface Speed {
|
||||
mbps: number;
|
||||
human_readable: string;
|
||||
}
|
||||
|
||||
// Interface for the Torrent Stats API response
|
||||
export interface LiveTorrentStats {
|
||||
snapshot: {
|
||||
|
|
@ -52,10 +57,8 @@ export interface LiveTorrentStats {
|
|||
secs: number;
|
||||
nanos: number;
|
||||
};
|
||||
download_speed: {
|
||||
mbps: number;
|
||||
human_readable: string;
|
||||
};
|
||||
download_speed: Speed;
|
||||
upload_speed: Speed;
|
||||
all_time_download_speed: {
|
||||
mbps: number;
|
||||
human_readable: string;
|
||||
|
|
|
|||
|
|
@ -185,6 +185,27 @@ const TorrentActions: React.FC<{
|
|||
</Row>
|
||||
}
|
||||
|
||||
const Speed: React.FC<{ statsResponse: TorrentStats }> = ({ statsResponse }) => {
|
||||
switch (statsResponse.state) {
|
||||
case STATE_PAUSED: return 'Paused';
|
||||
case STATE_INITIALIZING: return 'Checking files';
|
||||
case STATE_ERROR: return 'Error';
|
||||
}
|
||||
// Unknown state
|
||||
if (statsResponse.state != 'live' || statsResponse.live === null) {
|
||||
return statsResponse.state;
|
||||
}
|
||||
|
||||
return <>
|
||||
{!statsResponse.finished && <p>↓ {statsResponse.live.download_speed.human_readable}</p>}
|
||||
<p>↑ {statsResponse.live.upload_speed.human_readable}</p>
|
||||
</>
|
||||
|
||||
if (statsResponse.finished) {
|
||||
return <span>Completed</span>;
|
||||
}
|
||||
}
|
||||
|
||||
const TorrentRow: React.FC<{
|
||||
id: number,
|
||||
detailsResponse: TorrentDetails | null,
|
||||
|
|
@ -208,19 +229,6 @@ const TorrentRow: React.FC<{
|
|||
return `${peer_stats.live} / ${peer_stats.seen}`;
|
||||
}
|
||||
|
||||
const formatDownloadSpeed = () => {
|
||||
if (finished) {
|
||||
return 'Completed';
|
||||
}
|
||||
switch (state) {
|
||||
case STATE_PAUSED: return 'Paused';
|
||||
case STATE_INITIALIZING: return 'Checking files';
|
||||
case STATE_ERROR: return 'Error';
|
||||
}
|
||||
|
||||
return statsResponse?.live?.download_speed.human_readable ?? "N/A";
|
||||
}
|
||||
|
||||
let classNames = [];
|
||||
|
||||
if (error) {
|
||||
|
|
@ -253,7 +261,9 @@ const TorrentRow: React.FC<{
|
|||
animated={isAnimated}
|
||||
variant={progressBarVariant} />
|
||||
</Column>
|
||||
<Column size={2} label="Down Speed">{formatDownloadSpeed()}</Column>
|
||||
<Column size={2} label="Speed">
|
||||
<Speed statsResponse={statsResponse} />
|
||||
</Column>
|
||||
<Column label="ETA">{getCompletionETA(statsResponse)}</Column>
|
||||
<Column size={2} label="Peers">{formatPeersString()}</Column >
|
||||
<Column label="Actions">
|
||||
|
|
|
|||
|
|
@ -8,14 +8,14 @@ use parking_lot::Mutex;
|
|||
|
||||
#[derive(Clone, Copy)]
|
||||
struct ProgressSnapshot {
|
||||
downloaded_bytes: u64,
|
||||
progress_bytes: u64,
|
||||
instant: Instant,
|
||||
}
|
||||
|
||||
/// Estimates download speed in a sliding time window.
|
||||
/// Estimates download/upload speed in a sliding time window.
|
||||
pub struct SpeedEstimator {
|
||||
latest_per_second_snapshots: Mutex<VecDeque<ProgressSnapshot>>,
|
||||
download_bytes_per_second: AtomicU64,
|
||||
bytes_per_second: AtomicU64,
|
||||
time_remaining_millis: AtomicU64,
|
||||
}
|
||||
|
||||
|
|
@ -24,7 +24,7 @@ impl SpeedEstimator {
|
|||
assert!(window_seconds > 1);
|
||||
Self {
|
||||
latest_per_second_snapshots: Mutex::new(VecDeque::with_capacity(window_seconds)),
|
||||
download_bytes_per_second: Default::default(),
|
||||
bytes_per_second: Default::default(),
|
||||
time_remaining_millis: Default::default(),
|
||||
}
|
||||
}
|
||||
|
|
@ -37,20 +37,25 @@ impl SpeedEstimator {
|
|||
Some(Duration::from_millis(tr))
|
||||
}
|
||||
|
||||
pub fn download_bps(&self) -> u64 {
|
||||
self.download_bytes_per_second.load(Ordering::Relaxed)
|
||||
pub fn bps(&self) -> u64 {
|
||||
self.bytes_per_second.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn download_mbps(&self) -> f64 {
|
||||
self.download_bps() as f64 / 1024f64 / 1024f64
|
||||
pub fn mbps(&self) -> f64 {
|
||||
self.bps() as f64 / 1024f64 / 1024f64
|
||||
}
|
||||
|
||||
pub fn add_snapshot(&self, downloaded_bytes: u64, remaining_bytes: u64, instant: Instant) {
|
||||
pub fn add_snapshot(
|
||||
&self,
|
||||
progress_bytes: u64,
|
||||
remaining_bytes: Option<u64>,
|
||||
instant: Instant,
|
||||
) {
|
||||
let first = {
|
||||
let mut g = self.latest_per_second_snapshots.lock();
|
||||
|
||||
let current = ProgressSnapshot {
|
||||
downloaded_bytes,
|
||||
progress_bytes,
|
||||
instant,
|
||||
};
|
||||
|
||||
|
|
@ -67,19 +72,18 @@ impl SpeedEstimator {
|
|||
}
|
||||
};
|
||||
|
||||
let downloaded_bytes_diff = downloaded_bytes - first.downloaded_bytes;
|
||||
let downloaded_bytes_diff = progress_bytes - first.progress_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;
|
||||
let time_remaining_secs = remaining_bytes.unwrap_or_default() 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);
|
||||
self.bytes_per_second.store(bps as u64, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -377,7 +377,7 @@ async fn async_main(opts: Opts) -> anyhow::Result<()> {
|
|||
None => continue
|
||||
};
|
||||
let stats = handle.stats_snapshot();
|
||||
let speed = handle.speed_estimator();
|
||||
let speed = handle.down_speed_estimator();
|
||||
let total = stats.total_bytes;
|
||||
let progress = stats.total_bytes - stats.remaining_bytes;
|
||||
let downloaded_pct = if stats.remaining_bytes == 0 {
|
||||
|
|
@ -390,7 +390,7 @@ async fn async_main(opts: Opts) -> anyhow::Result<()> {
|
|||
idx,
|
||||
downloaded_pct,
|
||||
SF::new(progress),
|
||||
speed.download_mbps(),
|
||||
speed.mbps(),
|
||||
SF::new(stats.fetched_bytes),
|
||||
SF::new(stats.remaining_bytes),
|
||||
SF::new(total),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue