~percona-toolkit-dev/percona-toolkit/fix-log-parser-writer-bug-963225

« back to all changes in this revision

Viewing changes to lib/Progress.pm

  • Committer: Daniel Nichter
  • Date: 2011-06-24 17:22:06 UTC
  • Revision ID: daniel@percona.com-20110624172206-c7q4s4ad6r260zz6
Add lib/, t/lib/, and sandbox/.  All modules are updated and passing on MySQL 5.1.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# This program is copyright 2010-2011 Percona Inc.
 
2
# Feedback and improvements are welcome.
 
3
#
 
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.
 
7
#
 
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
 
12
# licenses.
 
13
#
 
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
# ###########################################################################
 
20
package Progress;
 
21
 
 
22
use strict;
 
23
use warnings FATAL => 'all';
 
24
 
 
25
use English qw(-no_match_vars);
 
26
use Data::Dumper;
 
27
$Data::Dumper::Indent    = 1;
 
28
$Data::Dumper::Sortkeys  = 1;
 
29
$Data::Dumper::Quotekeys = 0;
 
30
 
 
31
use constant MKDEBUG => $ENV{MKDEBUG} || 0;
 
32
 
 
33
# This module encapsulates a progress report.  To create a new object, pass in
 
34
# the following:
 
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
 
42
#           iterations.
 
43
#
 
44
# The 'report' and 'interval' can also be omitted, as long the following option
 
45
# is passed:
 
46
#  spec     An arrayref of [report,interval].  This is convenient to use from a
 
47
#           --progress command-line option that is an array.
 
48
#
 
49
# Optional arguments:
 
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.
 
56
sub new {
 
57
   my ( $class, %args ) = @_;
 
58
   foreach my $arg (qw(jobsize)) {
 
59
      die "I need a $arg argument" unless defined $args{$arg};
 
60
   }
 
61
   if ( (!$args{report} || !$args{interval}) ) {
 
62
      if ( $args{spec} && @{$args{spec}} == 2 ) {
 
63
         @args{qw(report interval)} = @{$args{spec}};
 
64
      }
 
65
      else {
 
66
         die "I need either report and interval arguments, or a spec";
 
67
      }
 
68
   }
 
69
 
 
70
   my $name  = $args{name} || "Progress";
 
71
   $args{start} ||= time();
 
72
   my $self;
 
73
   $self = {
 
74
      last_reported => $args{start},
 
75
      fraction      => 0,       # How complete the job is
 
76
      callback      => sub {
 
77
         my ($fraction, $elapsed, $remaining, $eta) = @_;
 
78
         printf STDERR "$name: %3d%% %s remain\n",
 
79
            $fraction * 100,
 
80
            Transformers::secs_to_time($remaining),
 
81
            Transformers::ts($eta);
 
82
      },
 
83
      %args,
 
84
   };
 
85
   return bless $self, $class;
 
86
}
 
87
 
 
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.
 
90
sub validate_spec {
 
91
   shift @_ if $_[0] eq 'Progress'; # Permit calling as Progress-> or Progress::
 
92
   my ( $spec ) = @_;
 
93
   if ( @$spec != 2 ) {
 
94
      die "spec array requires a two-part argument\n";
 
95
   }
 
96
   if ( $spec->[0] !~ m/^(?:percentage|time|iterations)$/ ) {
 
97
      die "spec array's first element must be one of "
 
98
        . "percentage,time,iterations\n";
 
99
   }
 
100
   if ( $spec->[1] !~ m/^\d+$/ ) {
 
101
      die "spec array's second element must be an integer\n";
 
102
   }
 
103
}
 
104
 
 
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.
 
110
sub set_callback {
 
111
   my ( $self, $callback ) = @_;
 
112
   $self->{callback} = $callback;
 
113
}
 
114
 
 
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.
 
117
sub start {
 
118
   my ( $self, $start ) = @_;
 
119
   $self->{start} = $self->{last_reported} = $start || time();
 
120
}
 
121
 
 
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.
 
130
sub update {
 
131
   my ( $self, $callback, $now ) = @_;
 
132
   my $jobsize   = $self->{jobsize};
 
133
   $now        ||= time();
 
134
   $self->{iterations}++; # How many updates have happened;
 
135
 
 
136
   # Determine whether to just quit and return...
 
137
   if ( $self->{report} eq 'time'
 
138
         && $self->{interval} > $now - $self->{last_reported}
 
139
   ) {
 
140
      return;
 
141
   }
 
142
   elsif ( $self->{report} eq 'iterations'
 
143
         && ($self->{iterations} - 1) % $self->{interval} > 0
 
144
   ) {
 
145
      return;
 
146
   }
 
147
   $self->{last_reported} = $now;
 
148
 
 
149
   # Get the updated status of the job
 
150
   my $completed = $callback->();
 
151
   $self->{updates}++; # How many times we have run the update callback
 
152
 
 
153
   # Sanity check: can't go beyond 100%
 
154
   return if $completed > $jobsize;
 
155
 
 
156
   # Compute the fraction complete, between 0 and 1.
 
157
   my $fraction = $completed > 0 ? $completed / $jobsize : 0;
 
158
 
 
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)
 
165
   ) {
 
166
      # We're done; we haven't advanced progress enough to report.
 
167
      $self->{fraction} = $fraction;
 
168
      return;
 
169
   }
 
170
   $self->{fraction} = $fraction;
 
171
 
 
172
   # Continue computing the metrics, and call the callback with them.
 
173
   my $elapsed   = $now - $self->{start};
 
174
   my $remaining = 0;
 
175
   my $eta       = $now;
 
176
   if ( $completed > 0 && $completed <= $jobsize && $elapsed > 0 ) {
 
177
      my $rate = $completed / $elapsed;
 
178
      if ( $rate > 0 ) {
 
179
         $remaining = ($jobsize - $completed) / $rate;
 
180
         $eta       = $now + int($remaining);
 
181
      }
 
182
   }
 
183
   $self->{callback}->($fraction, $elapsed, $remaining, $eta, $completed);
 
184
}
 
185
 
 
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
 
193
   return sprintf('%d',
 
194
      sprintf('%d', $num / $self->{interval}) * $self->{interval});
 
195
}
 
196
 
 
197
sub _d {
 
198
   my ($package, undef, $line) = caller 0;
 
199
   @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
 
200
        map { defined $_ ? $_ : 'undef' }
 
201
        @_;
 
202
   print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
 
203
}
 
204
 
 
205
1;
 
206
 
 
207
# ###########################################################################
 
208
# End Progress package
 
209
# ###########################################################################