Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 65 additions & 25 deletions src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -289,26 +289,26 @@ impl ProgressState {

/// The expected ETA
pub fn eta(&self) -> Duration {
// If done set ETA to 0
if self.is_finished() {
return Duration::new(0, 0);
}

// If no bar length set then return 0, otherwise get progress bar length
let len = match self.len {
Some(len) => len,
None => return Duration::new(0, 0),
};

// get current position
let pos = self.pos.pos.load(Ordering::Relaxed);
let steps_remaining = len.saturating_sub(pos) as f64;

let sps = self.est.steps_per_second(Instant::now());

// Infinite duration should only ever happen at the beginning, so in this case it's okay to
// just show an ETA of 0 until progress starts to occur.
if sps == 0.0 {
return Duration::new(0, 0);
}

secs_to_duration(len.saturating_sub(pos) as f64 / sps)
// estimate seconds remaining
let secs_remaining = secs_to_duration(self.est.sec_per_step * steps_remaining)
.saturating_sub(Instant::now() - self.est.prev_time);
// Return estimated time remaining in seconds
secs_remaining
}

/// The expected total duration (that is, elapsed time + expected ETA)
Expand Down Expand Up @@ -421,39 +421,77 @@ impl TabExpandedString {
/// slow asymptotic approach to zero (until the next spike).
#[derive(Debug)]
pub(crate) struct Estimator {
sec_per_step: f64, // Estimate for how many seconds each step takes on average
smoothed_steps_per_sec: f64,
double_smoothed_steps_per_sec: f64,
prev_steps: u64,
prev_time: Instant,
start_time: Instant,
steps_done: u64, // How many steps have already been accomplished
prev_time: Instant, // The last time a step was made
start_time: Instant, // The instant the process started
}

impl Estimator {
fn new(now: Instant) -> Self {
Self {
sec_per_step: 0.0,
smoothed_steps_per_sec: 0.0,
double_smoothed_steps_per_sec: 0.0,
prev_steps: 0,
steps_done: 0,
prev_time: now,
start_time: now,
}
}

fn record(&mut self, new_steps: u64, now: Instant) {
// sanity check: don't record data if time or steps have not advanced
if new_steps <= self.prev_steps || now <= self.prev_time {
// Reset on backwards seek to prevent breakage from seeking to the end for length determination
// See https://github.com/console-rs/indicatif/issues/480
if new_steps < self.prev_steps {
self.prev_steps = new_steps;
self.reset(now);
}
// Function that updates the estimator parameters based on the number of new steps and the current time
fn record(&mut self, new_steps_done: u64, now: Instant) {
// Reset on backwards seek to prevent breakage from seeking to the end for length determination
// See https://github.com/console-rs/indicatif/issues/480
if new_steps_done < self.steps_done {
self.steps_done = new_steps_done;
self.reset(now);
return;
}

let delta_steps = new_steps - self.prev_steps;
// We use avg_sec_per_step as the average secs per step so far only counting the completed steps before this one
let avg_sec_per_step = self
.prev_time
.saturating_duration_since(self.start_time)
.as_secs_f64()
/ self.steps_done as f64;

let delta_steps = new_steps_done - self.steps_done;
let delta_t = duration_to_secs(now - self.prev_time);

// If no progress has been made, assume the possibility of progress in the next second
// Meaning that steps per second is however many seconds have passed since last progress was made
// Note: When the first progress happens the ETA will already be set to what the ETA will be after the progress happens
if delta_steps == 0 {
// If first step in progress, assume progress could happen any second
if self.steps_done == 0 {
self.sec_per_step = (now - self.start_time).as_secs_f64();
// Set log prev time
self.prev_time = now;
return;
}
// If the time since last progress is more than the average estimated time per step
// Assume the possibility of a full stop having happened and increase the sec_per_step
// To show an everincreasing time the estimated time per step increases by 2% (picked because it seemed to work well)
// every time the function is run
if delta_t > avg_sec_per_step {
self.sec_per_step *= 1.02;
return;
}
// Otherwise, assume we're still counting down and change nothing
return;
}

// let time_elapsed_before = self.prev_time - self.start_time;
// self.etr = (self.etr * time_elapsed_before.as_secs_f64() + delta_t*delta_t) / (now - self.start_time).as_secs_f64();
// self.sec_per_step = (avg_sec_per_step * self.steps_done as f64 + (new_steps_done - self.steps_done) as f64 * delta_t/delta_steps as f64) / new_steps_done as f64;
self.sec_per_step =
now.saturating_duration_since(self.start_time).as_secs_f64() / new_steps_done as f64;

// 2025-07-09 G0rocks: Old code not used for ETA estimation anymore but could be used elsewhere. Would like to remove but won't dare as of now. See https://github.com/console-rs/indicatif/pull/721
//---------------------------------------------------------------------
// the rate of steps we saw in this update
let new_steps_per_second = delta_steps as f64 / delta_t;

Expand All @@ -476,7 +514,8 @@ impl Estimator {
self.double_smoothed_steps_per_sec = self.double_smoothed_steps_per_sec * weight
+ normalized_smoothed_steps_per_sec * (1.0 - weight);

self.prev_steps = new_steps;
// Update how many steps done and set prev_time to the instant the last progress was made
self.steps_done = new_steps_done;
self.prev_time = now;
}

Expand All @@ -485,8 +524,9 @@ impl Estimator {
pub(crate) fn reset(&mut self, now: Instant) {
self.smoothed_steps_per_sec = 0.0;
self.double_smoothed_steps_per_sec = 0.0;
self.sec_per_step = 0.0;

// only reset prev_time, not prev_steps
// only reset prev_time, not steps_done
self.prev_time = now;
self.start_time = now;
}
Expand Down