console-rs / indicatif

A command line progress reporting library for Rust
MIT License
4.22k stars 238 forks source link

`elapsed` doesn't update during single long-running step or some parallel steps #558

Closed mtkennerly closed 3 months ago

mtkennerly commented 12 months ago

I'm not sure if this is a bug or an intentional design, but I noticed that elapsed/elapsed_precise don't update while a single step is running. I can definitely understand that for single-threaded iterators, but I was surprised that it's also true for parallel iterators. What would be the recommended way to handle this?

Also, with the parallel iterator, 16 of the iterations don't cause the progress bar to update. Incidentally, my system has 16 cores. It seems like I have to have more than 16 items in the iterator to see any updates.

Single-threaded

Cargo.toml

[package]
name = "prog-bar"
version = "0.1.0"
edition = "2021"

[dependencies]
indicatif = "0.17.5"

main.rs

use std::{thread, time::Duration};
use indicatif::{ProgressIterator, ProgressBar, ProgressStyle};

fn main() {
    let xs = vec![1, 2, 3];

    let template = "{elapsed}, {elapsed_precise} {wide_bar} {pos} / {len}";
    let style = ProgressStyle::default_bar().template(&template).unwrap();
    let bar = ProgressBar::new(xs.len() as u64).with_style(style);

    _ = xs.iter().progress_with(bar).map(|_| {
        thread::sleep(Duration::from_secs(3));
    }).collect::<Vec<_>>();
}

Output

https://github.com/console-rs/indicatif/assets/4934192/6dcc17a4-4b2f-4051-aa6c-48c11adc2ff7

Parallel

Cargo.toml

[package]
name = "prog-bar"
version = "0.1.0"
edition = "2021"

[dependencies]
indicatif = { version = "0.17.5", features = ["rayon"] }
rayon = "1.7.0"

main.rs

use std::{thread, time::Duration};
use indicatif::{ParallelProgressIterator, ProgressBar, ProgressStyle};
use rayon::prelude::{IntoParallelIterator, ParallelIterator};

fn main() {
    println!("Cores: {}", thread::available_parallelism().unwrap());

    let xs = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20];

    let template = "{elapsed}, {elapsed_precise} {wide_bar} {pos} / {len}";
    let style = ProgressStyle::default_bar().template(&template).unwrap();
    let bar = ProgressBar::new(xs.len() as u64).with_style(style);

    _ = xs.into_par_iter().progress_with(bar).map(|x| {
        thread::sleep(Duration::from_secs(x));
    }).collect::<Vec<_>>();
}

Output

https://github.com/console-rs/indicatif/assets/4934192/2d651360-42b3-434b-8b5e-5ace0e70b603

djc commented 11 months ago

So, (a) we only update the progress bars when some method on the ProgressBar is called (for the non-parallel case, inc(1) for every call to Iterator::next()). Then we also rate limit draws (by default, to 20 times/s), so if inc() is called a bunch of times in a short time window, not all of those trigger a redraw.

For parallel iterators, the situation is a bit more complex, I suggest reading the code at https://github.com/console-rs/indicatif/blob/main/src/rayon.rs and maybe inserting some debug prints to see what's going on. I could see how rayon might be pushing a chunk at a time and the progress bar only getting in one draw per chunk.

(Sorry for the slow response, you caught me in a busy week so took some time to loop back to this.)

mtkennerly commented 3 months ago

I just noticed the enable_steady_tick option, which solves this problem for me. I guess it was there the whole time and I just missed it 😅

In this example, elapsed and elapsed_precise update at different times, which seems a bit odd, but you probably wouldn't use both together anyway.

diff --git a/src/main.rs b/src/main.rs
index 4f49d00..49ce520 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -7,6 +7,7 @@ fn main() {
     let template = "{elapsed}, {elapsed_precise} {wide_bar} {pos} / {len}";
     let style = ProgressStyle::default_bar().template(&template).unwrap();
     let bar = ProgressBar::new(xs.len() as u64).with_style(style);
+    bar.enable_steady_tick(Duration::from_millis(100));

     _ = xs.iter().progress_with(bar).map(|_| {
         thread::sleep(Duration::from_secs(3));

https://github.com/console-rs/indicatif/assets/4934192/395a9b9f-080a-4feb-8193-b9528b5f00dc