1
# This program is copyright 2010-2011 Percona Inc.
2
# Feedback and improvements are welcome.
4
# THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
5
# WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
6
# MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
8
# This program is free software; you can redistribute it and/or modify it under
9
# the terms of the GNU General Public License as published by the Free Software
10
# Foundation, version 2; OR the Perl Artistic License. On UNIX and similar
11
# systems, you can issue `man perlgpl' or `man perlartistic' to read these
14
# You should have received a copy of the GNU General Public License along with
15
# this program; if not, write to the Free Software Foundation, Inc., 59 Temple
16
# Place, Suite 330, Boston, MA 02111-1307 USA.
17
# ###########################################################################
18
# Progress package $Revision: 7096 $
19
# ###########################################################################
23
use warnings FATAL => 'all';
25
use English qw(-no_match_vars);
27
$Data::Dumper::Indent = 1;
28
$Data::Dumper::Sortkeys = 1;
29
$Data::Dumper::Quotekeys = 0;
31
use constant MKDEBUG => $ENV{MKDEBUG} || 0;
33
# This module encapsulates a progress report. To create a new object, pass in
35
# jobsize Must be a number; defines the job's completion condition
36
# report How and when to report progress. Possible values:
37
# percentage: based on the percentage complete.
38
# time: based on how much time elapsed.
39
# iterations: based on how many progress updates have happened.
40
# interval How many of whatever's specified in 'report' to wait before
41
# reporting progress: report each X%, each X seconds, or each X
44
# The 'report' and 'interval' can also be omitted, as long the following option
46
# spec An arrayref of [report,interval]. This is convenient to use from a
47
# --progress command-line option that is an array.
50
# start The start time of the job; can also be set by calling start()
51
# fraction How complete the job is, as a number between 0 and 1. Updated by
52
# calling update(). Normally don't specify this.
53
# name If you want to use the default progress indicator, by default it
54
# just prints out "Progress: ..." but you can replace "Progress" with
55
# whatever you specify here.
57
my ( $class, %args ) = @_;
58
foreach my $arg (qw(jobsize)) {
59
die "I need a $arg argument" unless defined $args{$arg};
61
if ( (!$args{report} || !$args{interval}) ) {
62
if ( $args{spec} && @{$args{spec}} == 2 ) {
63
@args{qw(report interval)} = @{$args{spec}};
66
die "I need either report and interval arguments, or a spec";
70
my $name = $args{name} || "Progress";
71
$args{start} ||= time();
74
last_reported => $args{start},
75
fraction => 0, # How complete the job is
77
my ($fraction, $elapsed, $remaining, $eta) = @_;
78
printf STDERR "$name: %3d%% %s remain\n",
80
Transformers::secs_to_time($remaining),
81
Transformers::ts($eta);
85
return bless $self, $class;
88
# Validates the 'spec' argument passed in from --progress command-line option.
89
# It calls die with a trailing newline to avoid auto-adding the file/line.
91
shift @_ if $_[0] eq 'Progress'; # Permit calling as Progress-> or Progress::
94
die "spec array requires a two-part argument\n";
96
if ( $spec->[0] !~ m/^(?:percentage|time|iterations)$/ ) {
97
die "spec array's first element must be one of "
98
. "percentage,time,iterations\n";
100
if ( $spec->[1] !~ m/^\d+$/ ) {
101
die "spec array's second element must be an integer\n";
105
# Specify your own custom way to report the progress. The default is to print
106
# the percentage to STDERR. This is created in the call to new(). The
107
# callback is a subroutine that will receive the fraction complete from 0 to
108
# 1, seconds elapsed, seconds remaining, and the Unix timestamp of when we
109
# expect to be complete.
111
my ( $self, $callback ) = @_;
112
$self->{callback} = $callback;
115
# Set the start timer of when work began. You can either set it to time() which
116
# is the default, or pass in a value.
118
my ( $self, $start ) = @_;
119
$self->{start} = $self->{last_reported} = $start || time();
122
# Provide a progress update. Pass in a callback subroutine which this code can
123
# use to ask how complete the job is. This callback will be called as
124
# appropriate. For example, in time-lapse updating, it won't be called unless
125
# it's time to report the progress. The callback has to return a number that's
126
# of the same dimensions as the jobsize. For example, if a text file has 800
127
# lines to process, that's a jobsize of 800; the callback should return how
128
# many lines we're done processing -- a number between 0 and 800. You can also
129
# optionally pass in the current time, but this is only for testing.
131
my ( $self, $callback, $now ) = @_;
132
my $jobsize = $self->{jobsize};
134
$self->{iterations}++; # How many updates have happened;
136
# Determine whether to just quit and return...
137
if ( $self->{report} eq 'time'
138
&& $self->{interval} > $now - $self->{last_reported}
142
elsif ( $self->{report} eq 'iterations'
143
&& ($self->{iterations} - 1) % $self->{interval} > 0
147
$self->{last_reported} = $now;
149
# Get the updated status of the job
150
my $completed = $callback->();
151
$self->{updates}++; # How many times we have run the update callback
153
# Sanity check: can't go beyond 100%
154
return if $completed > $jobsize;
156
# Compute the fraction complete, between 0 and 1.
157
my $fraction = $completed > 0 ? $completed / $jobsize : 0;
159
# Now that we know the fraction completed, we can decide whether to continue
160
# on and report, for percentage-based reporting. Have we crossed an
161
# interval-percent boundary since the last update?
162
if ( $self->{report} eq 'percentage'
163
&& $self->fraction_modulo($self->{fraction})
164
>= $self->fraction_modulo($fraction)
166
# We're done; we haven't advanced progress enough to report.
167
$self->{fraction} = $fraction;
170
$self->{fraction} = $fraction;
172
# Continue computing the metrics, and call the callback with them.
173
my $elapsed = $now - $self->{start};
176
if ( $completed > 0 && $completed <= $jobsize && $elapsed > 0 ) {
177
my $rate = $completed / $elapsed;
179
$remaining = ($jobsize - $completed) / $rate;
180
$eta = $now + int($remaining);
183
$self->{callback}->($fraction, $elapsed, $remaining, $eta, $completed);
186
# Returns the number rounded to the nearest lower $self->{interval}, for use
187
# with interval-based reporting. For example, when you want to report every 5%,
188
# then 0% through 4% all return 0%; 5% through 9% return 5%; and so on. The
189
# number needs to be passed as a fraction from 0 to 1.
190
sub fraction_modulo {
191
my ( $self, $num ) = @_;
192
$num *= 100; # Convert from fraction to percentage
194
sprintf('%d', $num / $self->{interval}) * $self->{interval});
198
my ($package, undef, $line) = caller 0;
199
@_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
200
map { defined $_ ? $_ : 'undef' }
202
print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
207
# ###########################################################################
208
# End Progress package
209
# ###########################################################################