File Coverage

blib/lib/Time/Progress.pm
Criterion Covered Total %
statement 90 113 79.6
branch 19 42 45.2
condition 16 54 29.6
subroutine 8 15 53.3
pod 10 11 90.9
total 143 235 60.8


line stmt bran cond sub pod time code
1             ##############################################################################
2             #
3             # Time::Progress
4             # 2013-2023 (c) Vladi Belperchinov-Shabanski "Cade"
5             #
6             # DISTRIBUTED UNDER GPLv2
7             #
8             ##############################################################################
9             package Time::Progress;
10              
11 1     1   142492 use 5.006;
  1         5  
12 1     1   7 use strict;
  1         7  
  1         44  
13 1     1   6 use warnings;
  1         2  
  1         59  
14 1     1   6 use Carp;
  1         4  
  1         2161  
15              
16             our $VERSION = '2.15';
17              
18             our $SMOOTHING_DELTA_DEFAULT = '0.1';
19             our %ATTRS = (
20             min => 1,
21             max => 1,
22             format => 1,
23             smoothing => 1,
24             smoothing_delta => 1,
25             );
26              
27             sub new
28             {
29 2     2 1 206509 my $class = shift;
30 2         7 my $self = { min => 0, max => 100, smoothing => 0, smoothing_delta => $SMOOTHING_DELTA_DEFAULT };
31 2         3 bless $self;
32 2         6 $self->attr( @_ );
33 2         4 $self->restart();
34 2         4 return $self;
35             }
36              
37             sub attr
38             {
39 4     4 1 4 my $self = shift;
40 4 50 66     14 croak "bad number of attribute/value pairs" unless @_ == 0 or @_ % 2 == 0;
41 4         4 my @ret;
42 4         6 my %h = @_;
43 4         8 for( keys %h )
44             {
45 4 50       7 croak "invalid attribute name: $_" unless $ATTRS{ $_ };
46 4 50       12 $self->{ $_ } = $h{ $_ } if defined $h{ $_ };
47 4         6 push @ret, $self->{ $_ };
48             }
49 4         7 return @ret;
50             }
51              
52             sub restart
53             {
54 2     2 1 2 my $self = shift;
55 2         4 my @ret = $self->attr( @_ );
56 2         3 $self->{ 'start' } = time();
57 2         3 $self->{ 'stop' } = undef;
58 2         5 $self->{ 'min_speed' } = 'n';
59 2         2 $self->{ 'max_speed' } = 'a';
60 2         3 return @ret;
61             }
62              
63             sub stop
64             {
65 0     0 1 0 my $self = shift;
66 0         0 $self->{ 'stop' } = time();
67             }
68              
69             sub continue
70             {
71 0     0 1 0 my $self = shift;
72 0         0 $self->{ 'stop' } = undef;
73             }
74              
75             sub report
76             {
77 11     11 1 39 my $self = shift;
78 11   33     19 my $format = shift || $self->{ 'format' };
79 11         13 my $cur = shift;
80              
81 11         12 my $start = $self->{ 'start' };
82              
83 11   33     18 my $now = $self->{ 'stop' } || time();
84              
85 11 50       18 croak "use restart() first" unless $start > 0;
86 11 50       14 croak "time glitch (running backwards?)" if $now < $start;
87 11 50       15 croak "empty format, use format() first" unless $format;
88              
89 11         9 my $l = $now - $start;
90 11         31 my $L = sprintf "%3d:%02d", int( $l / 60 ), ( $l % 60 );
91              
92 11         11 my $min = $self->{ 'min' };
93 11         11 my $max = $self->{ 'max' };
94 11         11 my $last_e = $self->{ 'last_e' };
95 11         10 my $sdelta = $self->{ 'smoothing_delta' };
96            
97 11 50       18 $cur = $min unless defined $cur;
98 11 50 33     41 $sdelta = $SMOOTHING_DELTA_DEFAULT unless $sdelta > 0 and $sdelta < 1;
99              
100 11         12 my $b = 'n/a';
101 11         11 my $bl = 79;
102              
103 11 100       27 if ( $format =~ /%(\d*)[bB]/ )
104             {
105 5         8 $bl = $1;
106 5 50 33     14 $bl = 79 if $bl eq '' or $bl < 1;
107             }
108              
109 11         11 my $e = "n/a";
110 11         10 my $E = "n/a";
111 11         10 my $f = "n/a";
112 11         9 my $p = 0;
113 11         10 my $ps = "n/a";
114 11         9 my $s = 0;
115              
116 11 50 33     31 if ( (($min <= $cur and $cur <= $max) or ($min >= $cur and $cur >= $max)) )
      0        
      33        
117             {
118 11 100       16 if ( $cur - $min == 0 )
119             {
120 2         3 $e = 0;
121             }
122             else
123             {
124 9         12 $e = $l * ( $max - $min ) / ( $cur - $min );
125 9         7 $e = int( $e - $l );
126 9 0 33     18 if ( $self->{ 'smoothing' } && $last_e && $last_e < $e && ( ( $e - $last_e ) / $last_e ) < $sdelta )
      33        
      0        
127             {
128 0         0 $e = $last_e;
129             }
130 9 50       15 $e = 0 if $e < 0;
131 9 50       10 $self->{last_e} = $e if $self->{ 'smoothing' };
132             }
133 11         19 $E = sprintf "%3d:%02d", int( $e / 60 ), ( $e % 60 );
134              
135 11         11 $f = $now + $e;
136 11         126 $f = localtime( $f );
137              
138 11 50       20 if ( $max - $min != 0 )
139             {
140 11         12 $p = 100 * ( $cur - $min ) / ( $max - $min );
141 11         23 $b = '#' x int( $bl * $p / 100 ) . '.' x $bl;
142 11         13 $b = substr $b, 0, $bl;
143 11         35 $ps = sprintf "%5.1f%%", $p;
144             }
145 11 50       16 $s = int( ( $cur - $min ) / ( time() - $self->{ 'start' } ) ) if time() - $self->{ 'start' } > 0;
146 11 0 66     23 $self->{ 'min_speed' } = $s if $p > 1 and $s > 0 and ( $self->{ 'min_speed' } eq 'n' or $self->{ 'min_speed' } > $s );
      0        
      33        
147 11 0 66     21 $self->{ 'max_speed' } = $s if $p > 1 and $s > 0 and ( $self->{ 'max_speed' } eq 'a' or $self->{ 'max_speed' } < $s );
      0        
      33        
148             }
149              
150 11 50       30 $s = 'n/a' unless $s > 0;
151              
152 11         14 $format =~ s/%(\d*)l/$self->sp_format( $l, $1 )/ge;
  0         0  
153 11         12 $format =~ s/%(\d*)L/$self->sp_format( $L, $1 )/ge;
  0         0  
154 11         11 $format =~ s/%(\d*)e/$self->sp_format( $e, $1 )/ge;
  0         0  
155 11         12 $format =~ s/%(\d*)E/$self->sp_format( $E, $1 )/ge;
  0         0  
156 11         22 $format =~ s/%p/$ps/g;
157 11         13 $format =~ s/%f/$f/g;
158 11         16 $format =~ s/%\d*[bB]/$b/g;
159 11         12 $format =~ s/%s/$s/g;
160 11         9 $format =~ s/%S/$self->{ 'min_speed' } . "\/" . $self->{ 'max_speed' }/ge;
  0         0  
161              
162 11         27 return $format;
163             }
164              
165             sub sp_format
166             {
167 0     0 0   my $self = shift;
168              
169 0           my $val = shift;
170 0           my $len = shift;
171              
172 0 0 0       return $val unless $len ne '' and $len > 0;
173 0           return sprintf( "%${len}s", $val );
174             }
175              
176             sub elapsed
177 0     0 1   { my $self = shift; return $self->report("%l",@_); }
  0            
178              
179             sub elapsed_str
180 0     0 1   { my $self = shift; return $self->report("elapsed time is %L min.\n",@_); }
  0            
181              
182             sub estimate
183 0     0 1   { my $self = shift; return $self->report("%e",@_); }
  0            
184              
185             sub estimate_str
186 0     0 1   { my $self = shift; return $self->report("remaining time is %E min.\n",@_); }
  0            
187              
188             1;
189              
190             =pod
191              
192             =head1 NAME
193              
194             Time::Progress - Elapsed and estimated finish time reporting.
195              
196             =head1 SYNOPSIS
197              
198             use Time::Progress;
199              
200             my ($min, $max) = (0, 4);
201             my $p = Time::Progress->new(min => $min, max => $max);
202              
203             for (my $c = $min; $c <= $max; $c++) {
204             print STDERR $p->report("\r%20b ETA: %E", $c);
205             # do some work
206             }
207             print STDERR "\n";
208              
209             =head1 DESCRIPTION
210              
211             This module displays progress information for long-running processes.
212             This can be percentage complete, time elapsed, estimated time remaining,
213             an ASCII progress bar, or any combination of those.
214              
215             It is useful for code where you perform a number of steps,
216             or iterations of a loop,
217             where the number of iterations is known before you start the loop.
218              
219             The typical usage of this module is:
220              
221             =over 4
222              
223             =item *
224             Create an instance of C, specifying min and max count values.
225              
226             =item *
227             At the head of the loop, you call the C method with
228             a format specifier and the iteration count,
229             and get back a string that should be displayed.
230              
231             =back
232              
233             If you include a carriage return character (\r) in the format string,
234             then the message will be over-written at each step.
235             Putting \r at the start of the format string,
236             as in the SYNOPSIS,
237             results in the cursor sitting at the end of the message.
238              
239             If you display to STDOUT, then remember to enable auto-flushing:
240              
241             use IO::Handle;
242             STDOUT->autoflush(1);
243              
244             The shortest time interval that can be measured is 1 second.
245              
246             =head1 METHODS
247              
248             =head2 new
249              
250             my $p = Time::Progress->new(%options);
251              
252             Returns new object of Time::Progress class and starts the timer.
253             It also sets min and max values to 0 and 100,
254             so the next B calls will default to percents range.
255              
256             You can configure the instance with the following parameters:
257              
258             =over 4
259              
260             =item min
261              
262             Sets the B attribute, as described in the C section below.
263              
264             =item max
265              
266             Sets the B attribute, as described in the C section below.
267              
268             =item smoothing
269              
270             If set to a true value, then the estimated time remaining is smoothed
271             in a simplistic way: if the time remaining ever goes up, by less than
272             10% of the previous estimate, then we just stick with the previous
273             estimate. This prevents flickering estimates.
274             By default this feature is turned off.
275              
276             =item smoothing_delta
277              
278             Sets smoothing delta parameter. Default value is 0.1 (i.e. 10%).
279             See 'smoothing' parameter for more details.
280              
281             =back
282              
283             =head2 restart
284              
285             Restarts the timer and clears the stop mark.
286             Optionally restart() may act also
287             as attr() for setting attributes:
288              
289             $p->restart( min => 1, max => 5 );
290              
291             is the same as:
292              
293             $p->attr( min => 1, max => 5 );
294             $p->restart();
295              
296             If you need to count things, you can set just 'max' attribute since 'min' is
297             already set to 0 when object is constructed by new():
298              
299             $p->restart( max => 42 );
300              
301             =head2 stop
302              
303             Sets the stop mark. This is only useful if you do some work, then finish,
304             then do some work that shouldn't be timed and finally report. Something
305             like:
306              
307             $p->restart;
308             # do some work here...
309             $p->stop;
310             # do some post-work here
311             print $p->report;
312             # `post-work' will not be timed
313              
314             Stop is useless if you want to report time as soon as work is finished like:
315              
316             $p->restart;
317             # do some work here...
318             print $p->report;
319              
320             =head2 continue
321              
322             Clears the stop mark. (mostly useless, perhaps you need to B?)
323              
324             =head2 attr
325              
326             Sets and returns internal values for attributes. Available attributes are:
327              
328             =over 4
329              
330             =item min
331              
332             This is the min value of the items that will follow (used to calculate
333             estimated finish time)
334              
335             =item max
336              
337             This is the max value of all items in the even (also used to calculate
338             estimated finish time)
339              
340             =item format
341              
342             This is the default B format. It is used if B is called
343             without parameters.
344              
345             =back
346              
347             B returns array of the set attributes:
348              
349             my ( $new_min, $new_max ) = $p->attr( min => 1, max => 5 );
350              
351             If you want just to get values use undef:
352              
353             my $old_format = $p->attr( format => undef );
354              
355             This way of handling attributes is a bit heavy but saves a lot
356             of attribute handling functions. B will complain if you pass odd number
357             of parameters.
358              
359             =head2 report
360              
361             This is the most complex method in this package :)
362              
363             The expected arguments are:
364              
365             $p->report( format, [current_item] );
366              
367             I is string that will be used for the result string. Recognized
368             special sequences are:
369              
370             =over 4
371              
372             =item %l
373              
374             elapsed seconds
375              
376             =item %L
377              
378             elapsed time in minutes in format MM:SS
379              
380             =item %e
381              
382             remaining seconds
383              
384             =item %E
385              
386             remaining time in minutes in format MM:SS
387              
388             =item %p
389              
390             percentage done in format PPP.P%
391              
392             =item %f
393              
394             estimated finish time in format returned by B
395              
396             =item %b
397              
398             =item %B
399              
400             progress bar which looks like:
401              
402             ##############......................
403              
404             %b takes optional width:
405              
406             %40b -- 40-chars wide bar
407             %9b -- 9-chars wide bar
408             %b -- 79-chars wide bar (default)
409              
410             =item %s
411              
412             current speed in items per second
413              
414             =item %S
415              
416             current min/max speeds (calculated after first 1% of the progress)
417              
418             =back
419              
420             Parameters can be omitted and then default format set with B will
421             be used.
422              
423             Sequences 'L', 'l', 'E' and 'e' can have width also:
424              
425             %10e
426             %5l
427             ...
428              
429             Estimate time calculations can be used only if min and max values are set
430             (see B method) and current item is passed to B! if you want
431             to use the default format but still have estimates use it like this:
432              
433             $p->format( undef, 45 );
434              
435             If you don't give current item (step) or didn't set proper min/max value
436             then all estimate sequences will have value `n/a'.
437              
438             You can freely mix reports during the same event.
439              
440              
441             =head2 elapsed($item)
442              
443             Returns the time elapsed, in seconds.
444             This help function, and those described below,
445             take one argument: the current item number.
446              
447              
448             =head2 estimate($item)
449              
450             Returns an estimate of the time remaining, in seconds.
451              
452              
453             =head2 elapsed_str($item)
454              
455             Returns elapsed time as a formatted string:
456              
457             "elapsed time is MM:SS min.\n"
458              
459             =head2 estimate_str($item)
460              
461             Returns estimated remaining time, as a formatted string:
462              
463             "remaining time is MM:SS min.\n"
464              
465              
466              
467             =head1 FORMAT EXAMPLES
468              
469             # $c is current element (step) reached
470             # for the examples: min = 0, max = 100, $c = 33.3
471              
472             print $p->report( "done %p elapsed: %L (%l sec), ETA %E (%e sec)\n", $c );
473             # prints:
474             # done 33.3% elapsed time 0:05 (5 sec), ETA 0:07 (7 sec)
475              
476             print $p->report( "%45b %p\r", $c );
477             # prints:
478             # ###############.............................. 33.3%
479              
480             print $p->report( "done %p ETA %f\n", $c );
481             # prints:
482             # done 33.3% ETA Sun Oct 21 16:50:57 2001
483              
484             print $p->report( "%30b %p %s/sec (%S) %L ETA: %E" );
485             # .............................. 0.7% 924/sec (938/951) 1:13 ETA: 173:35
486              
487             =head1 SEE ALSO
488              
489             The first thing you need to know about L is that
490             it was written by Damian Conway, so you should expect to be a little
491             bit freaked out by it. It looks for certain format comments in your
492             code, and uses them to display progress messages. Includes support
493             for progress meters.
494              
495             L separates the calculation of stats from the display
496             of those stats, so you can have different back-ends which display
497             progress is different ways. There are a number of separate back-ends
498             on CPAN.
499              
500             L displays a progress meter to a standard terminal.
501              
502             L uses C if your code
503             is running in a terminal. If not running interactively, then no progress bar
504             is shown.
505              
506             L provides a simple interface where you
507             get a C<$progress> object that you can just increment in a long-running loop.
508             It builds on C, so displays nothing
509             when not running interactively.
510              
511             L displays a progress meter with timing information,
512             and two different skins.
513              
514             L is another customisable progress meter,
515             which comes with a number of 'widgets' for display progress
516             information in different ways.
517              
518             L handles the case where a long-running process
519             has a number of sub-processes, and you want to record progress
520             of those too.
521              
522             L provides a simple progress bar,
523             which shows progress using a bar of ASCII characters,
524             and the percentage complete.
525              
526             L is simpler than most of the other modules listed here,
527             as it just displays a 'spinner' to the terminal. This is useful if you
528             just want to show that something is happening, but can't predict how many
529             more operations will be required.
530              
531             L shows a pulsed progress bar in your terminal,
532             using a child process to pulse the progress bar until your job is complete.
533              
534             L a fork of C.
535              
536             L is another progress bar module, but it hasn't
537             seen a release in the last 12 years.
538              
539             =head1 GITHUB REPOSITORY
540              
541              
542             https://github.com/cade-vs/perl-time-progress
543            
544             git clone https://github.com/cade-vs/perl-time-progress
545              
546              
547             =head1 AUTHOR
548              
549             Vladi Belperchinov-Shabanski "Cade"
550              
551            
552              
553             http://cade.datamax.bg
554              
555             =head1 COPYRIGHT AND LICENSE
556              
557             This software is (c) 2001-2019 by Vladi Belperchinov-Shabanski Ecade@bis.bgE Ecade@cpan.orgE.
558              
559             This is free software; you can redistribute it and/or modify it under
560             the same terms as the Perl 5 programming language system itself.
561              
562             =cut
563