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}