indicatif/
state.rs

1use std::borrow::Cow;
2use std::io;
3use std::sync::Arc;
4use std::time::Duration;
5#[cfg(not(target_arch = "wasm32"))]
6use std::time::Instant;
7
8#[cfg(target_arch = "wasm32")]
9use instant::Instant;
10use portable_atomic::{AtomicU64, AtomicU8, Ordering};
11
12use crate::draw_target::ProgressDrawTarget;
13use crate::style::ProgressStyle;
14
15pub(crate) struct BarState {
16    pub(crate) draw_target: ProgressDrawTarget,
17    pub(crate) on_finish: ProgressFinish,
18    pub(crate) style: ProgressStyle,
19    pub(crate) state: ProgressState,
20    pub(crate) tab_width: usize,
21}
22
23impl BarState {
24    pub(crate) fn new(
25        len: Option<u64>,
26        draw_target: ProgressDrawTarget,
27        pos: Arc<AtomicPosition>,
28    ) -> Self {
29        Self {
30            draw_target,
31            on_finish: ProgressFinish::default(),
32            style: ProgressStyle::default_bar(),
33            state: ProgressState::new(len, pos),
34            tab_width: DEFAULT_TAB_WIDTH,
35        }
36    }
37
38    /// Finishes the progress bar using the [`ProgressFinish`] behavior stored
39    /// in the [`ProgressStyle`].
40    pub(crate) fn finish_using_style(&mut self, now: Instant, finish: ProgressFinish) {
41        self.state.status = Status::DoneVisible;
42        match finish {
43            ProgressFinish::AndLeave => {
44                if let Some(len) = self.state.len {
45                    self.state.pos.set(len);
46                }
47            }
48            ProgressFinish::WithMessage(msg) => {
49                if let Some(len) = self.state.len {
50                    self.state.pos.set(len);
51                }
52                self.state.message = TabExpandedString::new(msg, self.tab_width);
53            }
54            ProgressFinish::AndClear => {
55                if let Some(len) = self.state.len {
56                    self.state.pos.set(len);
57                }
58                self.state.status = Status::DoneHidden;
59            }
60            ProgressFinish::Abandon => {}
61            ProgressFinish::AbandonWithMessage(msg) => {
62                self.state.message = TabExpandedString::new(msg, self.tab_width);
63            }
64        }
65
66        // There's no need to update the estimate here; once the `status` is no longer
67        // `InProgress`, we will use the length and elapsed time to estimate.
68        let _ = self.draw(true, now);
69    }
70
71    pub(crate) fn reset(&mut self, now: Instant, mode: Reset) {
72        // Always reset the estimator; this is the only reset that will occur if mode is
73        // `Reset::Eta`.
74        self.state.est.reset(now);
75
76        if let Reset::Elapsed | Reset::All = mode {
77            self.state.started = now;
78        }
79
80        if let Reset::All = mode {
81            self.state.pos.reset(now);
82            self.state.status = Status::InProgress;
83
84            for tracker in self.style.format_map.values_mut() {
85                tracker.reset(&self.state, now);
86            }
87
88            let _ = self.draw(false, now);
89        }
90    }
91
92    pub(crate) fn update(&mut self, now: Instant, f: impl FnOnce(&mut ProgressState), tick: bool) {
93        f(&mut self.state);
94        if tick {
95            self.tick(now);
96        }
97    }
98
99    pub(crate) fn set_length(&mut self, now: Instant, len: u64) {
100        self.state.len = Some(len);
101        self.update_estimate_and_draw(now);
102    }
103
104    pub(crate) fn inc_length(&mut self, now: Instant, delta: u64) {
105        if let Some(len) = self.state.len {
106            self.state.len = Some(len.saturating_add(delta));
107        }
108        self.update_estimate_and_draw(now);
109    }
110
111    pub(crate) fn set_tab_width(&mut self, tab_width: usize) {
112        self.tab_width = tab_width;
113        self.state.message.set_tab_width(tab_width);
114        self.state.prefix.set_tab_width(tab_width);
115        self.style.set_tab_width(tab_width);
116    }
117
118    pub(crate) fn set_style(&mut self, style: ProgressStyle) {
119        self.style = style;
120        self.style.set_tab_width(self.tab_width);
121    }
122
123    pub(crate) fn tick(&mut self, now: Instant) {
124        self.state.tick = self.state.tick.saturating_add(1);
125        self.update_estimate_and_draw(now);
126    }
127
128    pub(crate) fn update_estimate_and_draw(&mut self, now: Instant) {
129        let pos = self.state.pos.pos.load(Ordering::Relaxed);
130        self.state.est.record(pos, now);
131
132        for tracker in self.style.format_map.values_mut() {
133            tracker.tick(&self.state, now);
134        }
135
136        let _ = self.draw(false, now);
137    }
138
139    pub(crate) fn println(&mut self, now: Instant, msg: &str) {
140        let width = self.draw_target.width();
141        let mut drawable = match self.draw_target.drawable(true, now) {
142            Some(drawable) => drawable,
143            None => return,
144        };
145
146        let mut draw_state = drawable.state();
147        let lines: Vec<String> = msg.lines().map(Into::into).collect();
148        // Empty msg should trigger newline as we are in println
149        if lines.is_empty() {
150            draw_state.lines.push(String::new());
151        } else {
152            draw_state.lines.extend(lines);
153        }
154
155        draw_state.orphan_lines_count = draw_state.lines.len();
156        if let Some(width) = width {
157            if !matches!(self.state.status, Status::DoneHidden) {
158                self.style
159                    .format_state(&self.state, &mut draw_state.lines, width);
160            }
161        }
162
163        drop(draw_state);
164        let _ = drawable.draw();
165    }
166
167    pub(crate) fn suspend<F: FnOnce() -> R, R>(&mut self, now: Instant, f: F) -> R {
168        if let Some((state, _)) = self.draw_target.remote() {
169            return state.write().unwrap().suspend(f, now);
170        }
171
172        if let Some(drawable) = self.draw_target.drawable(true, now) {
173            let _ = drawable.clear();
174        }
175
176        let ret = f();
177        let _ = self.draw(true, Instant::now());
178        ret
179    }
180
181    pub(crate) fn draw(&mut self, mut force_draw: bool, now: Instant) -> io::Result<()> {
182        let width = self.draw_target.width();
183
184        // `|= self.is_finished()` should not be needed here, but we used to always draw for
185        // finished progress bars, so it's kept as to not cause compatibility issues in weird cases.
186        force_draw |= self.state.is_finished();
187        let mut drawable = match self.draw_target.drawable(force_draw, now) {
188            Some(drawable) => drawable,
189            None => return Ok(()),
190        };
191
192        let mut draw_state = drawable.state();
193
194        if let Some(width) = width {
195            if !matches!(self.state.status, Status::DoneHidden) {
196                self.style
197                    .format_state(&self.state, &mut draw_state.lines, width);
198            }
199        }
200
201        drop(draw_state);
202        drawable.draw()
203    }
204}
205
206impl Drop for BarState {
207    fn drop(&mut self) {
208        // Progress bar is already finished.  Do not need to do anything other than notify
209        // the `MultiProgress` that we're now a zombie.
210        if self.state.is_finished() {
211            self.draw_target.mark_zombie();
212            return;
213        }
214
215        self.finish_using_style(Instant::now(), self.on_finish.clone());
216
217        // Notify the `MultiProgress` that we're now a zombie.
218        self.draw_target.mark_zombie();
219    }
220}
221
222pub(crate) enum Reset {
223    Eta,
224    Elapsed,
225    All,
226}
227
228/// The state of a progress bar at a moment in time.
229#[non_exhaustive]
230pub struct ProgressState {
231    pos: Arc<AtomicPosition>,
232    len: Option<u64>,
233    pub(crate) tick: u64,
234    pub(crate) started: Instant,
235    status: Status,
236    est: Estimator,
237    pub(crate) message: TabExpandedString,
238    pub(crate) prefix: TabExpandedString,
239}
240
241impl ProgressState {
242    pub(crate) fn new(len: Option<u64>, pos: Arc<AtomicPosition>) -> Self {
243        let now = Instant::now();
244        Self {
245            pos,
246            len,
247            tick: 0,
248            status: Status::InProgress,
249            started: now,
250            est: Estimator::new(now),
251            message: TabExpandedString::NoTabs("".into()),
252            prefix: TabExpandedString::NoTabs("".into()),
253        }
254    }
255
256    /// Indicates that the progress bar finished.
257    pub fn is_finished(&self) -> bool {
258        match self.status {
259            Status::InProgress => false,
260            Status::DoneVisible => true,
261            Status::DoneHidden => true,
262        }
263    }
264
265    /// Returns the completion as a floating-point number between 0 and 1
266    pub fn fraction(&self) -> f32 {
267        let pos = self.pos.pos.load(Ordering::Relaxed);
268        let pct = match (pos, self.len) {
269            (_, None) => 0.0,
270            (_, Some(0)) => 1.0,
271            (0, _) => 0.0,
272            (pos, Some(len)) => pos as f32 / len as f32,
273        };
274        pct.clamp(0.0, 1.0)
275    }
276
277    /// The expected ETA
278    pub fn eta(&self) -> Duration {
279        if self.is_finished() {
280            return Duration::new(0, 0);
281        }
282
283        let len = match self.len {
284            Some(len) => len,
285            None => return Duration::new(0, 0),
286        };
287
288        let pos = self.pos.pos.load(Ordering::Relaxed);
289
290        let sps = self.est.steps_per_second(Instant::now());
291
292        // Infinite duration should only ever happen at the beginning, so in this case it's okay to
293        // just show an ETA of 0 until progress starts to occur.
294        if sps == 0.0 {
295            return Duration::new(0, 0);
296        }
297
298        secs_to_duration(len.saturating_sub(pos) as f64 / sps)
299    }
300
301    /// The expected total duration (that is, elapsed time + expected ETA)
302    pub fn duration(&self) -> Duration {
303        if self.len.is_none() || self.is_finished() {
304            return Duration::new(0, 0);
305        }
306        self.started.elapsed().saturating_add(self.eta())
307    }
308
309    /// The number of steps per second
310    pub fn per_sec(&self) -> f64 {
311        if let Status::InProgress = self.status {
312            self.est.steps_per_second(Instant::now())
313        } else {
314            self.pos() as f64 / self.started.elapsed().as_secs_f64()
315        }
316    }
317
318    pub fn elapsed(&self) -> Duration {
319        self.started.elapsed()
320    }
321
322    pub fn pos(&self) -> u64 {
323        self.pos.pos.load(Ordering::Relaxed)
324    }
325
326    pub fn set_pos(&mut self, pos: u64) {
327        self.pos.set(pos);
328    }
329
330    #[allow(clippy::len_without_is_empty)]
331    pub fn len(&self) -> Option<u64> {
332        self.len
333    }
334
335    pub fn set_len(&mut self, len: u64) {
336        self.len = Some(len);
337    }
338}
339
340#[derive(Debug, PartialEq, Eq, Clone)]
341pub(crate) enum TabExpandedString {
342    NoTabs(Cow<'static, str>),
343    WithTabs {
344        original: Cow<'static, str>,
345        expanded: String,
346        tab_width: usize,
347    },
348}
349
350impl TabExpandedString {
351    pub(crate) fn new(s: Cow<'static, str>, tab_width: usize) -> Self {
352        let expanded = s.replace('\t', &" ".repeat(tab_width));
353        if s == expanded {
354            Self::NoTabs(s)
355        } else {
356            Self::WithTabs {
357                original: s,
358                expanded,
359                tab_width,
360            }
361        }
362    }
363
364    pub(crate) fn expanded(&self) -> &str {
365        match &self {
366            Self::NoTabs(s) => {
367                debug_assert!(!s.contains('\t'));
368                s
369            }
370            Self::WithTabs { expanded, .. } => expanded,
371        }
372    }
373
374    pub(crate) fn set_tab_width(&mut self, new_tab_width: usize) {
375        if let Self::WithTabs {
376            original,
377            expanded,
378            tab_width,
379        } = self
380        {
381            if *tab_width != new_tab_width {
382                *tab_width = new_tab_width;
383                *expanded = original.replace('\t', &" ".repeat(new_tab_width));
384            }
385        }
386    }
387}
388
389/// Double-smoothed exponentially weighted estimator
390///
391/// This uses an exponentially weighted *time-based* estimator, meaning that it exponentially
392/// downweights old data based on its age. The rate at which this occurs is currently a constant
393/// value of 15 seconds for 90% weighting. This means that all data older than 15 seconds has a
394/// collective weight of 0.1 in the estimate, and all data older than 30 seconds has a collective
395/// weight of 0.01, and so on.
396///
397/// The primary value exposed by `Estimator` is `steps_per_second`. This value is doubly-smoothed,
398/// meaning that is the result of using an exponentially weighted estimator (as described above) to
399/// estimate the value of another exponentially weighted estimator, which estimates the value of
400/// the raw data.
401///
402/// The purpose of this extra smoothing step is to reduce instantaneous fluctations in the estimate
403/// when large updates are received. Without this, estimates might have a large spike followed by a
404/// slow asymptotic approach to zero (until the next spike).
405#[derive(Debug)]
406pub(crate) struct Estimator {
407    smoothed_steps_per_sec: f64,
408    double_smoothed_steps_per_sec: f64,
409    prev_steps: u64,
410    prev_time: Instant,
411    start_time: Instant,
412}
413
414impl Estimator {
415    fn new(now: Instant) -> Self {
416        Self {
417            smoothed_steps_per_sec: 0.0,
418            double_smoothed_steps_per_sec: 0.0,
419            prev_steps: 0,
420            prev_time: now,
421            start_time: now,
422        }
423    }
424
425    fn record(&mut self, new_steps: u64, now: Instant) {
426        // sanity check: don't record data if time or steps have not advanced
427        if new_steps <= self.prev_steps || now <= self.prev_time {
428            // Reset on backwards seek to prevent breakage from seeking to the end for length determination
429            // See https://github.com/console-rs/indicatif/issues/480
430            if new_steps < self.prev_steps {
431                self.prev_steps = new_steps;
432                self.reset(now);
433            }
434            return;
435        }
436
437        let delta_steps = new_steps - self.prev_steps;
438        let delta_t = duration_to_secs(now - self.prev_time);
439
440        // the rate of steps we saw in this update
441        let new_steps_per_second = delta_steps as f64 / delta_t;
442
443        // update the estimate: a weighted average of the old estimate and new data
444        let weight = estimator_weight(delta_t);
445        self.smoothed_steps_per_sec =
446            self.smoothed_steps_per_sec * weight + new_steps_per_second * (1.0 - weight);
447
448        // An iterative estimate like `smoothed_steps_per_sec` is supposed to be an exponentially
449        // weighted average from t=0 back to t=-inf; Since we initialize it to 0, we neglect the
450        // (non-existent) samples in the weighted average prior to the first one, so the resulting
451        // average must be normalized. We normalize the single estimate here in order to use it as
452        // a source for the double smoothed estimate. See comment on normalization in
453        // `steps_per_second` for details.
454        let delta_t_start = duration_to_secs(now - self.start_time);
455        let total_weight = 1.0 - estimator_weight(delta_t_start);
456        let normalized_smoothed_steps_per_sec = self.smoothed_steps_per_sec / total_weight;
457
458        // determine the double smoothed value (EWA smoothing of the single EWA)
459        self.double_smoothed_steps_per_sec = self.double_smoothed_steps_per_sec * weight
460            + normalized_smoothed_steps_per_sec * (1.0 - weight);
461
462        self.prev_steps = new_steps;
463        self.prev_time = now;
464    }
465
466    /// Reset the state of the estimator. Once reset, estimates will not depend on any data prior
467    /// to `now`. This does not reset the stored position of the progress bar.
468    pub(crate) fn reset(&mut self, now: Instant) {
469        self.smoothed_steps_per_sec = 0.0;
470        self.double_smoothed_steps_per_sec = 0.0;
471
472        // only reset prev_time, not prev_steps
473        self.prev_time = now;
474        self.start_time = now;
475    }
476
477    /// Average time per step in seconds, using double exponential smoothing
478    fn steps_per_second(&self, now: Instant) -> f64 {
479        // Because the value stored in the Estimator is only updated when the Estimator receives an
480        // update, this value will become stuck if progress stalls. To return an accurate estimate,
481        // we determine how much time has passed since the last update, and treat this as a
482        // pseudo-update with 0 steps.
483        let delta_t = duration_to_secs(now - self.prev_time);
484        let reweight = estimator_weight(delta_t);
485
486        // Normalization of estimates:
487        //
488        // The raw estimate is a single value (smoothed_steps_per_second) that is iteratively
489        // updated. At each update, the previous value of the estimate is downweighted according to
490        // its age, receiving the iterative weight W(t) = 0.1 ^ (t/15).
491        //
492        // Since W(Sum(t_n)) = Prod(W(t_n)), the total weight of a sample after a series of
493        // iterative steps is simply W(t_e) - W(t_b), where t_e is the time since the end of the
494        // sample, and t_b is the time since the beginning. The resulting estimate is therefore a
495        // weighted average with sample weights W(t_e) - W(t_b).
496        //
497        // Notice that the weighting function generates sample weights that sum to 1 only when the
498        // sample times span from t=0 to t=inf; but this is not the case. We have a first sample
499        // with finite, positive t_b = t_f. In the raw estimate, we handle times prior to t_f by
500        // setting an initial value of 0, meaning that these (non-existent) samples have no weight.
501        //
502        // Therefore, the raw estimate must be normalized by dividing it by the sum of the weights
503        // in the weighted average. This sum is just W(0) - W(t_f), where t_f is the time since the
504        // first sample, and W(0) = 1.
505        let delta_t_start = duration_to_secs(now - self.start_time);
506        let total_weight = 1.0 - estimator_weight(delta_t_start);
507
508        // Generate updated values for `smoothed_steps_per_sec` and `double_smoothed_steps_per_sec`
509        // (sps and dsps) without storing them. Note that we normalize sps when using it as a
510        // source to update dsps, and then normalize dsps itself before returning it.
511        let sps = self.smoothed_steps_per_sec * reweight / total_weight;
512        let dsps = self.double_smoothed_steps_per_sec * reweight + sps * (1.0 - reweight);
513        dsps / total_weight
514    }
515}
516
517pub(crate) struct AtomicPosition {
518    pub(crate) pos: AtomicU64,
519    capacity: AtomicU8,
520    prev: AtomicU64,
521    start: Instant,
522}
523
524impl AtomicPosition {
525    pub(crate) fn new() -> Self {
526        Self {
527            pos: AtomicU64::new(0),
528            capacity: AtomicU8::new(MAX_BURST),
529            prev: AtomicU64::new(0),
530            start: Instant::now(),
531        }
532    }
533
534    pub(crate) fn allow(&self, now: Instant) -> bool {
535        if now < self.start {
536            return false;
537        }
538
539        let mut capacity = self.capacity.load(Ordering::Acquire);
540        // `prev` is the number of ms after `self.started` we last returned `true`, in ns
541        let prev = self.prev.load(Ordering::Acquire);
542        // `elapsed` is the number of ns since `self.started`
543        let elapsed = (now - self.start).as_nanos() as u64;
544        // `diff` is the number of ns since we last returned `true`
545        let diff = elapsed.saturating_sub(prev);
546
547        // If `capacity` is 0 and not enough time (1ms) has passed since `prev`
548        // to add new capacity, return `false`. The goal of this method is to
549        // make this decision as efficient as possible.
550        if capacity == 0 && diff < INTERVAL {
551            return false;
552        }
553
554        // We now calculate `new`, the number of ms, in ns, since we last returned `true`,
555        // and `remainder`, which represents a number of ns less than 1ms which we cannot
556        // convert into capacity now, so we're saving it for later. We do this by
557        // substracting this from `elapsed` before storing it into `self.prev`.
558        let (new, remainder) = ((diff / INTERVAL), (diff % INTERVAL));
559        // We add `new` to `capacity`, subtract one for returning `true` from here,
560        // then make sure it does not exceed a maximum of `MAX_BURST`.
561        capacity = Ord::min(MAX_BURST as u128, (capacity as u128) + (new as u128) - 1) as u8;
562
563        // Then, we just store `capacity` and `prev` atomically for the next iteration
564        self.capacity.store(capacity, Ordering::Release);
565        self.prev.store(elapsed - remainder, Ordering::Release);
566        true
567    }
568
569    fn reset(&self, now: Instant) {
570        self.set(0);
571        let elapsed = (now.saturating_duration_since(self.start)).as_millis() as u64;
572        self.prev.store(elapsed, Ordering::Release);
573    }
574
575    pub(crate) fn inc(&self, delta: u64) {
576        self.pos.fetch_add(delta, Ordering::SeqCst);
577    }
578
579    pub(crate) fn set(&self, pos: u64) {
580        self.pos.store(pos, Ordering::Release);
581    }
582}
583
584const INTERVAL: u64 = 1_000_000;
585const MAX_BURST: u8 = 10;
586
587/// Behavior of a progress bar when it is finished
588///
589/// This is invoked when a [`ProgressBar`] or [`ProgressBarIter`] completes and
590/// [`ProgressBar::is_finished`] is false.
591///
592/// [`ProgressBar`]: crate::ProgressBar
593/// [`ProgressBarIter`]: crate::ProgressBarIter
594/// [`ProgressBar::is_finished`]: crate::ProgressBar::is_finished
595#[derive(Clone, Debug)]
596pub enum ProgressFinish {
597    /// Finishes the progress bar and leaves the current message
598    ///
599    /// Same behavior as calling [`ProgressBar::finish()`](crate::ProgressBar::finish).
600    AndLeave,
601    /// Finishes the progress bar and sets a message
602    ///
603    /// Same behavior as calling [`ProgressBar::finish_with_message()`](crate::ProgressBar::finish_with_message).
604    WithMessage(Cow<'static, str>),
605    /// Finishes the progress bar and completely clears it (this is the default)
606    ///
607    /// Same behavior as calling [`ProgressBar::finish_and_clear()`](crate::ProgressBar::finish_and_clear).
608    AndClear,
609    /// Finishes the progress bar and leaves the current message and progress
610    ///
611    /// Same behavior as calling [`ProgressBar::abandon()`](crate::ProgressBar::abandon).
612    Abandon,
613    /// Finishes the progress bar and sets a message, and leaves the current progress
614    ///
615    /// Same behavior as calling [`ProgressBar::abandon_with_message()`](crate::ProgressBar::abandon_with_message).
616    AbandonWithMessage(Cow<'static, str>),
617}
618
619impl Default for ProgressFinish {
620    fn default() -> Self {
621        Self::AndClear
622    }
623}
624
625/// Get the appropriate dilution weight for Estimator data given the data's age (in seconds)
626///
627/// Whenever an update occurs, we will create a new estimate using a weight `w_i` like so:
628///
629/// ```math
630/// <new estimate> = <previous estimate> * w_i + <new data> * (1 - w_i)
631/// ```
632///
633/// In other words, the new estimate is a weighted average of the previous estimate and the new
634/// data. We want to choose weights such that for any set of samples where `t_0, t_1, ...` are
635/// the durations of the samples:
636///
637/// ```math
638/// Sum(t_i) = ews ==> Prod(w_i) = 0.1
639/// ```
640///
641/// With this constraint it is easy to show that
642///
643/// ```math
644/// w_i = 0.1 ^ (t_i / ews)
645/// ```
646///
647/// Notice that the constraint implies that estimates are independent of the durations of the
648/// samples, a very useful feature.
649fn estimator_weight(age: f64) -> f64 {
650    const EXPONENTIAL_WEIGHTING_SECONDS: f64 = 15.0;
651    0.1_f64.powf(age / EXPONENTIAL_WEIGHTING_SECONDS)
652}
653
654fn duration_to_secs(d: Duration) -> f64 {
655    d.as_secs() as f64 + f64::from(d.subsec_nanos()) / 1_000_000_000f64
656}
657
658fn secs_to_duration(s: f64) -> Duration {
659    let secs = s.trunc() as u64;
660    let nanos = (s.fract() * 1_000_000_000f64) as u32;
661    Duration::new(secs, nanos)
662}
663
664#[derive(Debug)]
665pub(crate) enum Status {
666    InProgress,
667    DoneVisible,
668    DoneHidden,
669}
670
671pub(crate) const DEFAULT_TAB_WIDTH: usize = 8;
672
673#[cfg(test)]
674mod tests {
675    use super::*;
676    use crate::ProgressBar;
677
678    // https://github.com/rust-lang/rust-clippy/issues/10281
679    #[allow(clippy::uninlined_format_args)]
680    #[test]
681    fn test_steps_per_second() {
682        let test_rate = |items_per_second| {
683            let mut now = Instant::now();
684            let mut est = Estimator::new(now);
685            let mut pos = 0;
686
687            for _ in 0..20 {
688                pos += items_per_second;
689                now += Duration::from_secs(1);
690                est.record(pos, now);
691            }
692            let avg_steps_per_second = est.steps_per_second(now);
693
694            assert!(avg_steps_per_second > 0.0);
695            assert!(avg_steps_per_second.is_finite());
696
697            let absolute_error = (avg_steps_per_second - items_per_second as f64).abs();
698            let relative_error = absolute_error / items_per_second as f64;
699            assert!(
700                relative_error < 1.0 / 1e9,
701                "Expected rate: {}, actual: {}, relative error: {}",
702                items_per_second,
703                avg_steps_per_second,
704                relative_error
705            );
706        };
707
708        test_rate(1);
709        test_rate(1_000);
710        test_rate(1_000_000);
711        test_rate(1_000_000_000);
712        test_rate(1_000_000_001);
713        test_rate(100_000_000_000);
714        test_rate(1_000_000_000_000);
715        test_rate(100_000_000_000_000);
716        test_rate(1_000_000_000_000_000);
717    }
718
719    #[test]
720    fn test_double_exponential_ave() {
721        let mut now = Instant::now();
722        let mut est = Estimator::new(now);
723        let mut pos = 0;
724
725        // note: this is the default weight set in the Estimator
726        let weight = 15;
727
728        for _ in 0..weight {
729            pos += 1;
730            now += Duration::from_secs(1);
731            est.record(pos, now);
732        }
733        now += Duration::from_secs(weight);
734
735        // The first level EWA:
736        //   -> 90% weight @ 0 eps, 9% weight @ 1 eps, 1% weight @ 0 eps
737        //   -> then normalized by deweighting the 1% weight (before -30 seconds)
738        let single_target = 0.09 / 0.99;
739
740        // The second level EWA:
741        //   -> same logic as above, but using the first level EWA as the source
742        let double_target = (0.9 * single_target + 0.09) / 0.99;
743        assert_eq!(est.steps_per_second(now), double_target);
744    }
745
746    #[test]
747    fn test_estimator_rewind_position() {
748        let mut now = Instant::now();
749        let mut est = Estimator::new(now);
750
751        now += Duration::from_secs(1);
752        est.record(1, now);
753
754        // should not panic
755        now += Duration::from_secs(1);
756        est.record(0, now);
757
758        // check that reset occurred (estimator at 1 event per sec)
759        now += Duration::from_secs(1);
760        est.record(1, now);
761        assert_eq!(est.steps_per_second(now), 1.0);
762
763        // check that progress bar handles manual seeking
764        let pb = ProgressBar::hidden();
765        pb.set_length(10);
766        pb.set_position(1);
767        pb.tick();
768        // Should not panic.
769        pb.set_position(0);
770    }
771
772    #[test]
773    fn test_reset_eta() {
774        let mut now = Instant::now();
775        let mut est = Estimator::new(now);
776
777        // two per second, then reset
778        now += Duration::from_secs(1);
779        est.record(2, now);
780        est.reset(now);
781
782        // now one per second, and verify
783        now += Duration::from_secs(1);
784        est.record(3, now);
785        assert_eq!(est.steps_per_second(now), 1.0);
786    }
787
788    #[test]
789    fn test_duration_stuff() {
790        let duration = Duration::new(42, 100_000_000);
791        let secs = duration_to_secs(duration);
792        assert_eq!(secs_to_duration(secs), duration);
793    }
794
795    #[test]
796    fn test_atomic_position_large_time_difference() {
797        let atomic_position = AtomicPosition::new();
798        let later = atomic_position.start + Duration::from_nanos(INTERVAL * u64::from(u8::MAX));
799        // Should not panic.
800        atomic_position.allow(later);
801    }
802}