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