iter_progress/
lib.rs

1//! Wrap an iterator, and get progress data as it's executed. A more advanced
2//! [`.enumerate()`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.enumerate)
3//!
4//! # Usage
5//! Call `.progress()` on any Iterator, and get a new iterator that yields `(ProgressRecord, T)`, where `T`
6//! is the original value. A `ProgressRecord` has many helpful methods to query the current state
7//! of the iterator
8//!
9//! # Example
10//!
11//! ```
12//! use iter_progress::ProgressableIter;
13//! // Create an iterator that goes from 0 to 1,000
14//! let my_iter = 0..1_000;
15//! let mut progressor = my_iter.progress();
16//!
17//! // This new iterator returns a struct with the current state, and the inner object returned by
18//! // the iterator
19//! let (state, number) = progressor.next().unwrap();
20//! assert_eq!(number, 0);
21//!
22//! // We can now use methods on `state` to find out about this object
23//!
24//! // If we know the size of the iterator, we can query how far we are through it
25//! // How far through the iterator are we. 0 to 1
26//! assert_eq!(state.fraction(), Some(0.001));
27//!
28//! // We are 0.1% the way through
29//! assert_eq!(state.percent(), Some(0.1));
30//! ```
31//!
32//! Another usage:
33//!
34//! ```
35//! use iter_progress::ProgressableIter;
36//! # let my_big_vec = vec![false; 100];
37//!
38//! for (state, val) in my_big_vec.iter().progress() {
39//!     // Every 1 second, execute this function with the the `state`
40//!     state.do_every_n_sec(1., |state| {
41//!        println!("{}% the way though, and doing {} per sec.", state.percent().unwrap(), state.rate());
42//!     });
43//!
44//!     // Do something to process `val`
45//! }
46//! ```
47//!
48//! `.do_every_n_sec` is a "best effort" attempt. It's single threaded, so will be called if the
49//! last time that was called was more than N sec ago. `.do_every_n_items` is called every N items.
50
51use std::iter::Iterator;
52use std::ops::{Deref, DerefMut};
53use std::time::{Duration, Instant};
54
55#[cfg(test)]
56mod tests;
57
58/// Every step of the underlying iterator, one of these is generated. It contains all the
59/// information of how this iterator is progresing. Use the methods to access data on it.
60#[derive(Debug)]
61pub struct ProgressRecord {
62    /// How many elements before this
63    num: usize,
64
65    /// How long since we started iterating.
66    iterating_for: Duration,
67
68    /// Value of underlying iterator's `.size_hint()`
69    size_hint: (usize, Option<usize>),
70
71    /// If `.assumed_size(...)` was set on `ProgressableIter`, return that.
72    assumed_size: Option<usize>,
73
74    /// If we have overridden the calculated fraction
75    assumed_fraction: Option<f64>,
76
77    /// The timestamp of when the previous record was created. Will be None if this is first.
78    previous_record_tm: Option<Instant>,
79
80    /// When the iteration started
81    started_iterating: Instant,
82
83    /// The rolling average duration, if calculated
84    rolling_average_duration: Option<Duration>,
85
86    /// The exponential average duration, if calculated
87    exp_average_duration: Option<Duration>,
88}
89
90impl ProgressRecord {
91    /// Duration since iteration started
92    pub fn duration_since_start(&self) -> Duration {
93        self.iterating_for
94    }
95
96    /// Number of items we've generated so far. Will be 0 for the first element
97    ///
98    /// ```rust
99    /// # use iter_progress::ProgressableIter;
100    /// let mut progressor = (0..1_000).progress();
101    /// let (state, num) = progressor.next().unwrap();
102    /// assert_eq!(state.num_done(), 1);
103    /// ```
104    ///
105    ///
106    /// ```rust
107    /// # use iter_progress::ProgressableIter;
108    /// let mut progressor = (0..1_000).progress().skip(10);
109    /// let (state, num) = progressor.next().unwrap();
110    /// assert_eq!(state.num_done(), 11);
111    /// ```
112    pub fn num_done(&self) -> usize {
113        self.num
114    }
115
116    /// The `Instant` for when the previous record was generated. None if there was no previous
117    /// record.
118    ///
119    /// This can be useful for calculating fine-grained rates
120    pub fn previous_record_tm(&self) -> Option<Instant> {
121        self.previous_record_tm
122    }
123
124    /// Return the time `Instant` that this iterator started
125    pub fn started_iterating(&self) -> Instant {
126        self.started_iterating
127    }
128
129    /// Number of items per second, calculated from the start
130    pub fn rate(&self) -> f64 {
131        // number of items per second
132        (self.num_done() as f64) / self.duration_since_start().as_secs_f64()
133    }
134
135    /// How far through the iterator as a fraction, if known.
136    /// First looks at the `assumed_fraction` if you have overridden that.
137    /// Uses the underlying iterator's `.size_hint()` method if that is an exact value, falling
138    /// back to any assumed size (set with `.assume_size(...)`). Otherwise returns `None`.
139    ///
140    /// ```
141    /// use iter_progress::ProgressableIter;
142    /// let mut progressor = (0..1_000).progress().skip(120);
143    /// let (state, num) = progressor.next().unwrap();
144    /// assert_eq!(num, 120);
145    /// assert_eq!(state.fraction(), Some(0.121));
146    /// ```
147    ///
148    /// Returns `None` if we cannot know, e.g. for an infinite iterator
149    /// ```
150    /// # use iter_progress::ProgressableIter;
151    /// let mut progressor = (0..).progress().skip(120);
152    /// let (state, num) = progressor.next().unwrap();
153    /// assert_eq!(state.fraction(), None);
154    /// ```
155    pub fn fraction(&self) -> Option<f64> {
156        if self.assumed_fraction.is_some() {
157            return self.assumed_fraction;
158        }
159
160        let total = if self.size_hint.1 == Some(self.size_hint.0) {
161            // use that directly
162            Some(self.size_hint.0 + self.num_done())
163        } else if self.assumed_size.is_some() {
164            self.assumed_size
165        } else {
166            None
167        };
168
169        match total {
170            None => None,
171            Some(total) => {
172                let done = self.num_done();
173                Some((done as f64) / (total as f64))
174            }
175        }
176    }
177
178    /// Assume that this is actually at this fraction through
179    /// If the underlying Iterator doesn't provide a useful `size_hint`, but you "know" the real
180    /// fraction (e.g. if reading from a file), you can override the value for this
181    /// `ProgressRecord`. This new value is used for rate & time calculations.
182    ///
183    /// ```
184    /// # use iter_progress::ProgressableIter;
185    /// let mut progressor = (0..).progress();
186    /// let (mut state, _num) = progressor.next().unwrap();
187    /// assert_eq!(state.fraction(), None);     // No fraction possible
188    /// // Be we know we're 12% the way through
189    /// state.assume_fraction(0.12);
190    /// assert_eq!(state.fraction(), Some(0.12));
191    /// ```
192    pub fn assume_fraction(&mut self, f: impl Into<f64>) {
193        self.assumed_fraction = Some(f.into())
194    }
195
196    /// Percentage progress through the iterator, if known.
197    ///
198    /// ```
199    /// use iter_progress::ProgressableIter;
200    /// let mut progressor = (0..1_000).progress().skip(120);
201    /// let (state, num) = progressor.next().unwrap();
202    /// assert_eq!(state.percent(), Some(12.1));
203    /// ```
204    ///
205    /// Returns `None` if we cannot know, e.g. for an infinite iterator
206    /// ```
207    /// # use iter_progress::ProgressableIter;
208    /// let mut progressor = (0..).progress().skip(120);
209    /// let (state, num) = progressor.next().unwrap();
210    /// assert_eq!(state.percent(), None);
211    /// ```
212    pub fn percent(&self) -> Option<f64> {
213        self.fraction().map(|f| f * 100.)
214    }
215
216    /// Print out `msg`, but only if there has been `n` seconds since last printout. (uses
217    /// `print!()`, so newline not included)
218    pub fn print_every_n_sec<T: std::fmt::Display>(&self, n: f32, msg: T) {
219        if self.should_do_every_n_sec(n) {
220            print!("{}", msg);
221        }
222    }
223
224    /// Call this function, but only every n sec (as close as possible).
225    /// Could be a print statement.
226    pub fn do_every_n_sec<F: Fn(&Self)>(&self, n: impl Into<f32>, f: F) {
227        if self.should_do_every_n_sec(n) {
228            f(self);
229        }
230    }
231
232    /// If we want to do every `n` sec, should we do it now?
233    pub fn should_do_every_n_sec(&self, n: impl Into<f32>) -> bool {
234        let n: f32 = n.into();
235        // get the secs since start as a f32
236        let duration_since_start = self.duration_since_start();
237        let secs_since_start: f32 = duration_since_start.as_secs() as f32
238            + duration_since_start.subsec_nanos() as f32 / 1_000_000_000.0;
239
240        match self.previous_record_tm() {
241            None => {
242                // This iteration is the first time, so we should print if more than `n` seconds
243                // have gone past
244                secs_since_start > n
245            }
246            Some(last_time) => {
247                let last_time_offset = last_time - self.started_iterating();
248                let last_time_offset: f32 = last_time_offset.as_secs() as f32
249                    + last_time_offset.subsec_nanos() as f32 / 1_000_000_000.0;
250
251                let current_step = secs_since_start / n;
252                let last_step = last_time_offset / n;
253
254                current_step.trunc() > last_step.trunc()
255            }
256        }
257    }
258
259    /// If we want to do every `n` items, should we do it now?
260    pub fn should_do_every_n_items(&self, n: usize) -> bool {
261        (self.num_done() - 1) % n == 0
262    }
263
264    /// Print out `msg`, but only if there has been `n` items.
265    /// Often you want to print out a debug message every 1,000 items or so. This function does
266    /// that.
267    pub fn print_every_n_items<T: std::fmt::Display>(&self, n: usize, msg: T) {
268        if self.should_do_every_n_items(n) {
269            print!("{}", msg);
270        }
271    }
272
273    /// Do thing but only every `n` items.
274    /// Could be a print statement.
275    ///
276    /// takes 2 arguments, `n` and the function (`f`) which takes a `&ProgressState`. `f` will only
277    /// be called every `n` items that pass through the iterator.
278    ///
279    /// ```
280    /// # use iter_progress::ProgressableIter;
281    /// for (state, _) in (0..150).progress() {
282    ///    state.do_every_n_items(5, |state| {
283    ///        println!("Current progress: {}%", state.percent().unwrap());
284    ///    });
285    /// }
286    /// ```
287    pub fn do_every_n_items<F: Fn(&Self)>(&self, n: usize, f: F) {
288        if self.should_do_every_n_items(n) {
289            f(self);
290        }
291    }
292
293    /// Rolling average time to process each item this iterator is processing if it is recording
294    /// that. None if it's not being recorded, or it's too soon to know (e.g. for the first item).
295    pub fn rolling_average_duration(&self) -> &Option<Duration> {
296        &self.rolling_average_duration
297    }
298
299    /// Rolling average number of items per second this iterator is processing if it is recording
300    /// that. None if it's not being recorded, or it's too soon to know (e.g. for the first item).
301    pub fn rolling_average_rate(&self) -> Option<f64> {
302        self.rolling_average_duration.map(|d| 1. / d.as_secs_f64())
303    }
304
305    /// Exponential average time to process each item this iterator is processing if it is recording
306    /// that. None if it's not being recorded, or it's too soon to know (e.g. for the first item).
307    pub fn exp_average_duration(&self) -> &Option<Duration> {
308        &self.exp_average_duration
309    }
310
311    /// Exponential average number of items per second this iterator is processing if it is recording
312    /// that. None if it's not being recorded, or it's too soon to know (e.g. for the first item).
313    pub fn exp_average_rate(&self) -> Option<f64> {
314        self.exp_average_duration.map(|d| 1. / d.as_secs_f64())
315    }
316
317    /// If the total size is know (i.e. we know the `.fraction()`), calculate the estimated time
318    /// to arrival, i.e. how long before this is finished.
319    pub fn eta(&self) -> Option<Duration> {
320        self.fraction()
321            .map(|f| self.duration_since_start().div_f64(f) - self.duration_since_start())
322    }
323
324    /// If the total size is know (i.e. we know the `.fraction()`), calculate how long, in total,
325    /// this iterator would run for. i.e. how long it's run plus how much longer it has left
326    pub fn estimated_total_time(&self) -> Option<Duration> {
327        self.fraction()
328            .map(|f| self.duration_since_start().div_f64(f))
329    }
330}
331
332pub struct OptionalProgressRecorderIter<I> {
333    /// The iterator that we are iteating on
334    iter: I,
335
336    /// How many items have been seen
337    count: usize,
338
339    generate_every_count: usize,
340
341    /// When did we start iterating
342    started_iterating: Instant,
343
344    previous_record_tm: Option<Instant>,
345
346    rolling_average: Option<(usize, Vec<f64>)>,
347    exp_average: Option<(f64, Option<Duration>)>,
348    assumed_size: Option<usize>,
349
350    _fake_now: Option<Instant>,
351}
352
353/// Wraps an iterator and keeps track of state used for `ProgressRecord`'s
354pub struct ProgressRecorderIter<I>(OptionalProgressRecorderIter<I>);
355
356impl<I> AsRef<OptionalProgressRecorderIter<I>> for ProgressRecorderIter<I> {
357    fn as_ref(&self) -> &OptionalProgressRecorderIter<I> {
358        &self.0
359    }
360}
361
362impl<I> AsMut<OptionalProgressRecorderIter<I>> for ProgressRecorderIter<I> {
363    fn as_mut(&mut self) -> &mut OptionalProgressRecorderIter<I> {
364        &mut self.0
365    }
366}
367
368impl<I: Iterator> Deref for ProgressRecorderIter<I> {
369    type Target = OptionalProgressRecorderIter<I>;
370    fn deref(&self) -> &OptionalProgressRecorderIter<I> {
371        &self.0
372    }
373}
374
375impl<I: Iterator> DerefMut for ProgressRecorderIter<I> {
376    fn deref_mut(&mut self) -> &mut OptionalProgressRecorderIter<I> {
377        &mut self.0
378    }
379}
380
381impl<I: Iterator> ProgressRecorderIter<I> {
382    /// Create a new `ProgressRecorderIter` from another iterator.
383    pub fn new(iter: I) -> ProgressRecorderIter<I> {
384        ProgressRecorderIter(OptionalProgressRecorderIter::new(iter, 1))
385    }
386
387    pub fn assume_size(self, size: impl Into<Option<usize>>) -> Self {
388        let mut new = self;
389        new.0.assumed_size = size.into();
390        new
391    }
392}
393
394/// An iterator that records it's progress as it goes along
395pub trait ProgressableIter<I> {
396    fn progress(self) -> ProgressRecorderIter<I>;
397}
398
399impl<I> ProgressableIter<I> for I
400where
401    I: Iterator,
402{
403    /// Convert an iterator into a `ProgressRecorderIter`.
404    fn progress(self) -> ProgressRecorderIter<I> {
405        ProgressRecorderIter::new(self)
406    }
407}
408
409impl<I> Iterator for ProgressRecorderIter<I>
410where
411    I: Iterator,
412{
413    type Item = (ProgressRecord, <I as Iterator>::Item);
414
415    #[inline]
416    fn next(&mut self) -> Option<(ProgressRecord, <I as Iterator>::Item)> {
417        self.0.iter.next().map(|a| {
418            let fake_now = std::mem::take(&mut self.0._fake_now);
419            // we know there is always a record generated
420            (self.0.generate_record(fake_now).unwrap(), a)
421        })
422    }
423
424    #[inline]
425    fn size_hint(&self) -> (usize, Option<usize>) {
426        self.0.iter.size_hint()
427    }
428
429    #[inline]
430    fn count(self) -> usize {
431        self.0.iter.count()
432    }
433}
434
435impl<I: Iterator> OptionalProgressRecorderIter<I> {
436    pub fn new(iter: I, generate_every_count: usize) -> OptionalProgressRecorderIter<I> {
437        OptionalProgressRecorderIter {
438            iter,
439            count: 0,
440            generate_every_count,
441            started_iterating: Instant::now(),
442            previous_record_tm: None,
443            rolling_average: None,
444            exp_average: None,
445            assumed_size: None,
446            _fake_now: None,
447        }
448    }
449
450    /// Set the desired size of the rolling average window calculation (if any). `None` to
451    /// disable.
452    /// Larger values slow down each iteration (since the rolling average is calculated each
453    /// iteration).
454    pub fn with_rolling_average(self, size: impl Into<Option<usize>>) -> Self {
455        let mut res = self;
456        res.rolling_average = size.into().map(|size| (size, vec![0.; size]));
457        res
458    }
459
460    /// Set the desired exponential rate
461    /// 0.001 is a good value.
462    pub fn with_exp_average(self, rate: impl Into<Option<f64>>) -> Self {
463        let mut res = self;
464        res.exp_average = rate.into().map(|rate| (rate, None));
465        res
466    }
467
468    /// Add an 'assumed size' to this iterator. If the iterator doesn't return an exact value for
469    /// `.size_hint()`, you can use this to override
470    /// the `.size_hint()` from the iterator will override this if it returns an exact size (i.e.
471    /// `.size_hint().1 == Some(...size_hint().0).
472    /// Set to `None` to undo this.
473    ///
474    /// ```
475    /// # use iter_progress::ProgressableIter;
476    /// let mut progressor = (0..).progress().assume_size(10);
477    /// let (state, num) = progressor.next().unwrap();
478    /// assert_eq!(state.fraction(), Some(0.1));
479    /// ```
480    pub fn assume_size(self, size: impl Into<Option<usize>>) -> Self {
481        let mut new = self;
482        new.assumed_size = size.into();
483        new
484    }
485
486    /// Calculate the current `ProgressRecord` for where we are now.
487    fn generate_record(&mut self, fake_now: Option<Instant>) -> Option<ProgressRecord> {
488        self.count += 1;
489        if self.count % self.generate_every_count != 0 {
490            return None;
491        }
492
493        let now = fake_now.unwrap_or_else(Instant::now);
494
495        let exp_average_rate = if let Some((rate, last)) = self.exp_average {
496            if let Some(previous_tm) = self.previous_record_tm {
497                let this_duration = now - previous_tm;
498                let current_ema = match last {
499                    None => this_duration,
500                    Some(last) => this_duration.mul_f64(rate) + last.mul_f64(1. - rate),
501                };
502                self.exp_average = Some((rate, Some(current_ema)));
503                Some(current_ema)
504            } else {
505                None
506            }
507        } else {
508            None
509        };
510
511        let rolling_average_duration = match &mut self.rolling_average {
512            None => None,
513            Some((size, values)) => {
514                if let Some(previous_tm) = self.previous_record_tm {
515                    let this_duration = (now - previous_tm).as_secs_f64();
516                    values[self.count % *size] = this_duration;
517                    if self.count < *size {
518                        // We haven't filled up the buffer yet
519                        Some(Duration::from_secs_f64(
520                            values[0..=self.count].iter().sum::<f64>() / (self.count as f64),
521                        ))
522                    } else {
523                        Some(Duration::from_secs_f64(
524                            values.iter().sum::<f64>() / (*size as f64),
525                        ))
526                    }
527                } else {
528                    None
529                }
530            }
531        };
532
533        let res = ProgressRecord {
534            num: self.count,
535            iterating_for: now - self.started_iterating,
536            size_hint: self.iter.size_hint(),
537            assumed_size: self.assumed_size,
538            assumed_fraction: None,
539            started_iterating: self.started_iterating,
540            previous_record_tm: self.previous_record_tm,
541            rolling_average_duration,
542            exp_average_duration: exp_average_rate,
543        };
544
545        self.previous_record_tm = Some(now);
546
547        Some(res)
548    }
549
550    /// Returns referend to the inner iterator
551    pub fn inner(&self) -> &I {
552        &self.iter
553    }
554
555    /// Gets the original iterator back, consuming this.
556    pub fn into_inner(self) -> I {
557        self.iter
558    }
559
560    #[cfg(test)]
561    fn set_fake_now(&mut self, fake_now: impl Into<Option<Instant>>) {
562        self._fake_now = fake_now.into();
563    }
564}
565
566pub trait OptionalProgressableIter<I: Iterator> {
567    fn optional_progress(self, generate_every_count: usize) -> OptionalProgressRecorderIter<I>;
568}
569
570impl<I> OptionalProgressableIter<I> for I
571where
572    I: Iterator,
573{
574    /// Convert an iterator into an `OptionalProgressRecorderIter`.
575    fn optional_progress(self, generate_every_count: usize) -> OptionalProgressRecorderIter<I> {
576        OptionalProgressRecorderIter::new(self, generate_every_count)
577    }
578}
579
580impl<I: Iterator> Iterator for OptionalProgressRecorderIter<I> {
581    type Item = (Option<ProgressRecord>, <I as Iterator>::Item);
582
583    #[inline]
584    fn next(&mut self) -> Option<Self::Item> {
585        let fake_now = std::mem::take(&mut self._fake_now);
586        self.iter
587            .next()
588            .map(|a| (self.generate_record(fake_now), a))
589    }
590
591    #[inline]
592    fn size_hint(&self) -> (usize, Option<usize>) {
593        self.iter.size_hint()
594    }
595
596    #[inline]
597    fn count(self) -> usize {
598        self.iter.count()
599    }
600}