File Coverage

blib/lib/Time/Progress.pm
Criterion Covered Total %
statement 89 112 79.4
branch 18 40 45.0
condition 16 54 29.6
subroutine 8 15 53.3
pod 10 11 90.9
total 141 232 60.7


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