Skip to content

Commit 6701e33

Browse files
committed
Auto merge of #9395 - weihanglo:issue-8483, r=alexcrichton
Show transfer rate when fetching/updating registry index Possibly fixes #8483. To avoid blinking too frequently, update rate is throttled by one second. I am not sure how to write tests for it 😂 <img width="896" alt="image" src="https://user-images.githubusercontent.com/14314532/115879831-ac62fb00-a47c-11eb-9b12-735ce8192ebe.png"> # Updated (2020-04-28) Current looking ``` Updating crates.io index Fetch [==> ] 14.50%, 258.45KiB/s Updating crates.io index Fetch [======> ] 40.50%, (1234/282342) resolving deltas ```
2 parents 96be674 + e4d4347 commit 6701e33

File tree

4 files changed

+113
-6
lines changed

4 files changed

+113
-6
lines changed

src/cargo/sources/git/utils.rs

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
44
use crate::core::GitReference;
55
use crate::util::errors::CargoResult;
6-
use crate::util::{network, Config, IntoUrl, Progress};
6+
use crate::util::{network, Config, IntoUrl, MetricsCounter, Progress};
77
use anyhow::{anyhow, Context as _};
88
use cargo_util::{paths, ProcessBuilder};
99
use curl::easy::List;
@@ -15,6 +15,7 @@ use std::env;
1515
use std::fmt;
1616
use std::path::{Path, PathBuf};
1717
use std::process::Command;
18+
use std::time::{Duration, Instant};
1819
use url::Url;
1920

2021
fn serialize_str<T, S>(t: &T, s: S) -> Result<S::Ok, S::Error>
@@ -677,7 +678,7 @@ fn reset(repo: &git2::Repository, obj: &git2::Object<'_>, config: &Config) -> Ca
677678
let mut pb = Progress::new("Checkout", config);
678679
let mut opts = git2::build::CheckoutBuilder::new();
679680
opts.progress(|_, cur, max| {
680-
drop(pb.tick(cur, max));
681+
drop(pb.tick(cur, max, ""));
681682
});
682683
debug!("doing reset");
683684
repo.reset(obj, git2::ResetType::Hard, Some(&mut opts))?;
@@ -694,12 +695,49 @@ pub fn with_fetch_options(
694695
let mut progress = Progress::new("Fetch", config);
695696
network::with_retry(config, || {
696697
with_authentication(url, git_config, |f| {
698+
let mut last_update = Instant::now();
697699
let mut rcb = git2::RemoteCallbacks::new();
700+
// We choose `N=10` here to make a `300ms * 10slots ~= 3000ms`
701+
// sliding window for tracking the data transfer rate (in bytes/s).
702+
let mut counter = MetricsCounter::<10>::new(0, last_update);
698703
rcb.credentials(f);
699-
700704
rcb.transfer_progress(|stats| {
705+
let indexed_deltas = stats.indexed_deltas();
706+
let msg = if indexed_deltas > 0 {
707+
// Resolving deltas.
708+
format!(
709+
", ({}/{}) resolving deltas",
710+
indexed_deltas,
711+
stats.total_deltas()
712+
)
713+
} else {
714+
// Receiving objects.
715+
//
716+
// # Caveat
717+
//
718+
// Progress bar relies on git2 calling `transfer_progress`
719+
// to update its transfer rate, but we cannot guarantee a
720+
// periodic call of that callback. Thus if we don't receive
721+
// any data for, say, 10 seconds, the rate will get stuck
722+
// and never go down to 0B/s.
723+
// In the future, we need to find away to update the rate
724+
// even when the callback is not called.
725+
let now = Instant::now();
726+
// Scrape a `received_bytes` to the counter every 300ms.
727+
if now - last_update > Duration::from_millis(300) {
728+
counter.add(stats.received_bytes(), now);
729+
last_update = now;
730+
}
731+
fn format_bytes(bytes: f32) -> (&'static str, f32) {
732+
static UNITS: [&str; 5] = ["", "Ki", "Mi", "Gi", "Ti"];
733+
let i = (bytes.log2() / 10.0).min(4.0) as usize;
734+
(UNITS[i], bytes / 1024_f32.powi(i as i32))
735+
}
736+
let (unit, rate) = format_bytes(counter.rate());
737+
format!(", {:.2}{}B/s", rate, unit)
738+
};
701739
progress
702-
.tick(stats.indexed_objects(), stats.total_objects())
740+
.tick(stats.indexed_objects(), stats.total_objects(), &msg)
703741
.is_ok()
704742
});
705743

src/cargo/util/counter.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
use std::time::Instant;
2+
3+
/// A metrics counter storing only latest `N` records.
4+
pub struct MetricsCounter<const N: usize> {
5+
/// Slots to store metrics.
6+
slots: [(usize, Instant); N],
7+
/// The slot of the oldest record.
8+
/// Also the next slot to store the new record.
9+
index: usize,
10+
}
11+
12+
impl<const N: usize> MetricsCounter<N> {
13+
/// Creates a new counter with an initial value.
14+
pub fn new(init: usize, init_at: Instant) -> Self {
15+
debug_assert!(N > 0, "number of slots must be greater than zero");
16+
Self {
17+
slots: [(init, init_at); N],
18+
index: 0,
19+
}
20+
}
21+
22+
/// Adds record to the counter.
23+
pub fn add(&mut self, data: usize, added_at: Instant) {
24+
self.slots[self.index] = (data, added_at);
25+
self.index = (self.index + 1) % N;
26+
}
27+
28+
/// Calculates per-second average rate of all slots.
29+
pub fn rate(&self) -> f32 {
30+
let latest = self.slots[self.index.checked_sub(1).unwrap_or(N - 1)];
31+
let oldest = self.slots[self.index];
32+
let duration = (latest.1 - oldest.1).as_secs_f32();
33+
let avg = (latest.0 - oldest.0) as f32 / duration;
34+
if f32::is_nan(avg) {
35+
0f32
36+
} else {
37+
avg
38+
}
39+
}
40+
}
41+
42+
#[cfg(test)]
43+
mod tests {
44+
use super::MetricsCounter;
45+
use std::time::{Duration, Instant};
46+
47+
#[test]
48+
fn counter() {
49+
let now = Instant::now();
50+
let mut counter = MetricsCounter::<3>::new(0, now);
51+
assert_eq!(counter.rate(), 0f32);
52+
counter.add(1, now + Duration::from_secs(1));
53+
assert_eq!(counter.rate(), 1f32);
54+
counter.add(4, now + Duration::from_secs(2));
55+
assert_eq!(counter.rate(), 2f32);
56+
counter.add(7, now + Duration::from_secs(3));
57+
assert_eq!(counter.rate(), 3f32);
58+
counter.add(12, now + Duration::from_secs(4));
59+
assert_eq!(counter.rate(), 4f32);
60+
}
61+
62+
#[test]
63+
#[should_panic(expected = "number of slots must be greater than zero")]
64+
fn counter_zero_slot() {
65+
let _counter = MetricsCounter::<0>::new(0, Instant::now());
66+
}
67+
}

src/cargo/util/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::time::Duration;
33

44
pub use self::canonical_url::CanonicalUrl;
55
pub use self::config::{homedir, Config, ConfigValue};
6+
pub(crate) use self::counter::MetricsCounter;
67
pub use self::dependency_queue::DependencyQueue;
78
pub use self::diagnostic_server::RustfixDiagnosticServer;
89
pub use self::errors::{internal, CargoResult, CliResult, Test};
@@ -29,6 +30,7 @@ pub use self::workspace::{
2930
mod canonical_url;
3031
pub mod command_prelude;
3132
pub mod config;
33+
mod counter;
3234
pub mod cpu;
3335
mod dependency_queue;
3436
pub mod diagnostic_server;

src/cargo/util/progress.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ impl<'cfg> Progress<'cfg> {
9696
Self::with_style(name, ProgressStyle::Percentage, cfg)
9797
}
9898

99-
pub fn tick(&mut self, cur: usize, max: usize) -> CargoResult<()> {
99+
pub fn tick(&mut self, cur: usize, max: usize, msg: &str) -> CargoResult<()> {
100100
let s = match &mut self.state {
101101
Some(s) => s,
102102
None => return Ok(()),
@@ -118,7 +118,7 @@ impl<'cfg> Progress<'cfg> {
118118
return Ok(());
119119
}
120120

121-
s.tick(cur, max, "")
121+
s.tick(cur, max, msg)
122122
}
123123

124124
pub fn tick_now(&mut self, cur: usize, max: usize, msg: &str) -> CargoResult<()> {

0 commit comments

Comments
 (0)