Merge lp:~percona-toolkit-dev/percona-toolkit/version-in-all-bash-tools-bug-821502 into lp:percona-toolkit/2.2
- version-in-all-bash-tools-bug-821502
- Merge into 2.2
Proposed by
Daniel Nichter
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Daniel Nichter | ||||
Approved revision: | 571 | ||||
Merged at revision: | 569 | ||||
Proposed branch: | lp:~percona-toolkit-dev/percona-toolkit/version-in-all-bash-tools-bug-821502 | ||||
Merge into: | lp:percona-toolkit/2.2 | ||||
Diff against target: |
3154 lines (+2722/-166) 6 files modified
bin/pt-align (+1094/-1) bin/pt-mext (+552/-42) bin/pt-pmp (+572/-92) bin/pt-sift (+481/-28) t/pt-mext/pt-mext.t (+21/-1) t/pt-pmp/pt-pmp.t (+2/-2) |
||||
To merge this branch: | bzr merge lp:~percona-toolkit-dev/percona-toolkit/version-in-all-bash-tools-bug-821502 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Daniel Nichter | Approve | ||
Review via email: mp+157232@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
Daniel Nichter (daniel-nichter) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'bin/pt-align' |
2 | --- bin/pt-align 2013-03-14 17:20:35 +0000 |
3 | +++ bin/pt-align 2013-04-04 21:05:27 +0000 |
4 | @@ -4,6 +4,1084 @@ |
5 | # See "COPYRIGHT, LICENSE, AND WARRANTY" at the end of this file for legal |
6 | # notices and disclaimers. |
7 | |
8 | +use strict; |
9 | +use warnings FATAL => 'all'; |
10 | + |
11 | +# This tool is "fat-packed": most of its dependent modules are embedded |
12 | +# in this file. Setting %INC to this file for each module makes Perl aware |
13 | +# of this so it will not try to load the module from @INC. See the tool's |
14 | +# documentation for a full list of dependencies. |
15 | +BEGIN { |
16 | + $INC{$_} = __FILE__ for map { (my $pkg = "$_.pm") =~ s!::!/!g; $pkg } (qw( |
17 | + OptionParser |
18 | + )); |
19 | +} |
20 | + |
21 | +# ########################################################################### |
22 | +# OptionParser package |
23 | +# This package is a copy without comments from the original. The original |
24 | +# with comments and its test file can be found in the Bazaar repository at, |
25 | +# lib/OptionParser.pm |
26 | +# t/lib/OptionParser.t |
27 | +# See https://launchpad.net/percona-toolkit for more information. |
28 | +# ########################################################################### |
29 | +{ |
30 | +package OptionParser; |
31 | + |
32 | +use strict; |
33 | +use warnings FATAL => 'all'; |
34 | +use English qw(-no_match_vars); |
35 | +use constant PTDEBUG => $ENV{PTDEBUG} || 0; |
36 | + |
37 | +use List::Util qw(max); |
38 | +use Getopt::Long; |
39 | +use Data::Dumper; |
40 | + |
41 | +my $POD_link_re = '[LC]<"?([^">]+)"?>'; |
42 | + |
43 | +sub new { |
44 | + my ( $class, %args ) = @_; |
45 | + my @required_args = qw(); |
46 | + foreach my $arg ( @required_args ) { |
47 | + die "I need a $arg argument" unless $args{$arg}; |
48 | + } |
49 | + |
50 | + my ($program_name) = $PROGRAM_NAME =~ m/([.A-Za-z-]+)$/; |
51 | + $program_name ||= $PROGRAM_NAME; |
52 | + my $home = $ENV{HOME} || $ENV{HOMEPATH} || $ENV{USERPROFILE} || '.'; |
53 | + |
54 | + my %attributes = ( |
55 | + 'type' => 1, |
56 | + 'short form' => 1, |
57 | + 'group' => 1, |
58 | + 'default' => 1, |
59 | + 'cumulative' => 1, |
60 | + 'negatable' => 1, |
61 | + ); |
62 | + |
63 | + my $self = { |
64 | + head1 => 'OPTIONS', # These args are used internally |
65 | + skip_rules => 0, # to instantiate another Option- |
66 | + item => '--(.*)', # Parser obj that parses the |
67 | + attributes => \%attributes, # DSN OPTIONS section. Tools |
68 | + parse_attributes => \&_parse_attribs, # don't tinker with these args. |
69 | + |
70 | + %args, |
71 | + |
72 | + strict => 1, # disabled by a special rule |
73 | + program_name => $program_name, |
74 | + opts => {}, |
75 | + got_opts => 0, |
76 | + short_opts => {}, |
77 | + defaults => {}, |
78 | + groups => {}, |
79 | + allowed_groups => {}, |
80 | + errors => [], |
81 | + rules => [], # desc of rules for --help |
82 | + mutex => [], # rule: opts are mutually exclusive |
83 | + atleast1 => [], # rule: at least one opt is required |
84 | + disables => {}, # rule: opt disables other opts |
85 | + defaults_to => {}, # rule: opt defaults to value of other opt |
86 | + DSNParser => undef, |
87 | + default_files => [ |
88 | + "/etc/percona-toolkit/percona-toolkit.conf", |
89 | + "/etc/percona-toolkit/$program_name.conf", |
90 | + "$home/.percona-toolkit.conf", |
91 | + "$home/.$program_name.conf", |
92 | + ], |
93 | + types => { |
94 | + string => 's', # standard Getopt type |
95 | + int => 'i', # standard Getopt type |
96 | + float => 'f', # standard Getopt type |
97 | + Hash => 'H', # hash, formed from a comma-separated list |
98 | + hash => 'h', # hash as above, but only if a value is given |
99 | + Array => 'A', # array, similar to Hash |
100 | + array => 'a', # array, similar to hash |
101 | + DSN => 'd', # DSN |
102 | + size => 'z', # size with kMG suffix (powers of 2^10) |
103 | + time => 'm', # time, with an optional suffix of s/h/m/d |
104 | + }, |
105 | + }; |
106 | + |
107 | + return bless $self, $class; |
108 | +} |
109 | + |
110 | +sub get_specs { |
111 | + my ( $self, $file ) = @_; |
112 | + $file ||= $self->{file} || __FILE__; |
113 | + my @specs = $self->_pod_to_specs($file); |
114 | + $self->_parse_specs(@specs); |
115 | + |
116 | + open my $fh, "<", $file or die "Cannot open $file: $OS_ERROR"; |
117 | + my $contents = do { local $/ = undef; <$fh> }; |
118 | + close $fh; |
119 | + if ( $contents =~ m/^=head1 DSN OPTIONS/m ) { |
120 | + PTDEBUG && _d('Parsing DSN OPTIONS'); |
121 | + my $dsn_attribs = { |
122 | + dsn => 1, |
123 | + copy => 1, |
124 | + }; |
125 | + my $parse_dsn_attribs = sub { |
126 | + my ( $self, $option, $attribs ) = @_; |
127 | + map { |
128 | + my $val = $attribs->{$_}; |
129 | + if ( $val ) { |
130 | + $val = $val eq 'yes' ? 1 |
131 | + : $val eq 'no' ? 0 |
132 | + : $val; |
133 | + $attribs->{$_} = $val; |
134 | + } |
135 | + } keys %$attribs; |
136 | + return { |
137 | + key => $option, |
138 | + %$attribs, |
139 | + }; |
140 | + }; |
141 | + my $dsn_o = new OptionParser( |
142 | + description => 'DSN OPTIONS', |
143 | + head1 => 'DSN OPTIONS', |
144 | + dsn => 0, # XXX don't infinitely recurse! |
145 | + item => '\* (.)', # key opts are a single character |
146 | + skip_rules => 1, # no rules before opts |
147 | + attributes => $dsn_attribs, |
148 | + parse_attributes => $parse_dsn_attribs, |
149 | + ); |
150 | + my @dsn_opts = map { |
151 | + my $opts = { |
152 | + key => $_->{spec}->{key}, |
153 | + dsn => $_->{spec}->{dsn}, |
154 | + copy => $_->{spec}->{copy}, |
155 | + desc => $_->{desc}, |
156 | + }; |
157 | + $opts; |
158 | + } $dsn_o->_pod_to_specs($file); |
159 | + $self->{DSNParser} = DSNParser->new(opts => \@dsn_opts); |
160 | + } |
161 | + |
162 | + if ( $contents =~ m/^=head1 VERSION\n\n^(.+)$/m ) { |
163 | + $self->{version} = $1; |
164 | + PTDEBUG && _d($self->{version}); |
165 | + } |
166 | + |
167 | + return; |
168 | +} |
169 | + |
170 | +sub DSNParser { |
171 | + my ( $self ) = @_; |
172 | + return $self->{DSNParser}; |
173 | +}; |
174 | + |
175 | +sub get_defaults_files { |
176 | + my ( $self ) = @_; |
177 | + return @{$self->{default_files}}; |
178 | +} |
179 | + |
180 | +sub _pod_to_specs { |
181 | + my ( $self, $file ) = @_; |
182 | + $file ||= $self->{file} || __FILE__; |
183 | + open my $fh, '<', $file or die "Cannot open $file: $OS_ERROR"; |
184 | + |
185 | + my @specs = (); |
186 | + my @rules = (); |
187 | + my $para; |
188 | + |
189 | + local $INPUT_RECORD_SEPARATOR = ''; |
190 | + while ( $para = <$fh> ) { |
191 | + next unless $para =~ m/^=head1 $self->{head1}/; |
192 | + last; |
193 | + } |
194 | + |
195 | + while ( $para = <$fh> ) { |
196 | + last if $para =~ m/^=over/; |
197 | + next if $self->{skip_rules}; |
198 | + chomp $para; |
199 | + $para =~ s/\s+/ /g; |
200 | + $para =~ s/$POD_link_re/$1/go; |
201 | + PTDEBUG && _d('Option rule:', $para); |
202 | + push @rules, $para; |
203 | + } |
204 | + |
205 | + die "POD has no $self->{head1} section" unless $para; |
206 | + |
207 | + do { |
208 | + if ( my ($option) = $para =~ m/^=item $self->{item}/ ) { |
209 | + chomp $para; |
210 | + PTDEBUG && _d($para); |
211 | + my %attribs; |
212 | + |
213 | + $para = <$fh>; # read next paragraph, possibly attributes |
214 | + |
215 | + if ( $para =~ m/: / ) { # attributes |
216 | + $para =~ s/\s+\Z//g; |
217 | + %attribs = map { |
218 | + my ( $attrib, $val) = split(/: /, $_); |
219 | + die "Unrecognized attribute for --$option: $attrib" |
220 | + unless $self->{attributes}->{$attrib}; |
221 | + ($attrib, $val); |
222 | + } split(/; /, $para); |
223 | + if ( $attribs{'short form'} ) { |
224 | + $attribs{'short form'} =~ s/-//; |
225 | + } |
226 | + $para = <$fh>; # read next paragraph, probably short help desc |
227 | + } |
228 | + else { |
229 | + PTDEBUG && _d('Option has no attributes'); |
230 | + } |
231 | + |
232 | + $para =~ s/\s+\Z//g; |
233 | + $para =~ s/\s+/ /g; |
234 | + $para =~ s/$POD_link_re/$1/go; |
235 | + |
236 | + $para =~ s/\.(?:\n.*| [A-Z].*|\Z)//s; |
237 | + PTDEBUG && _d('Short help:', $para); |
238 | + |
239 | + die "No description after option spec $option" if $para =~ m/^=item/; |
240 | + |
241 | + if ( my ($base_option) = $option =~ m/^\[no\](.*)/ ) { |
242 | + $option = $base_option; |
243 | + $attribs{'negatable'} = 1; |
244 | + } |
245 | + |
246 | + push @specs, { |
247 | + spec => $self->{parse_attributes}->($self, $option, \%attribs), |
248 | + desc => $para |
249 | + . (defined $attribs{default} ? " (default $attribs{default})" : ''), |
250 | + group => ($attribs{'group'} ? $attribs{'group'} : 'default'), |
251 | + }; |
252 | + } |
253 | + while ( $para = <$fh> ) { |
254 | + last unless $para; |
255 | + if ( $para =~ m/^=head1/ ) { |
256 | + $para = undef; # Can't 'last' out of a do {} block. |
257 | + last; |
258 | + } |
259 | + last if $para =~ m/^=item /; |
260 | + } |
261 | + } while ( $para ); |
262 | + |
263 | + die "No valid specs in $self->{head1}" unless @specs; |
264 | + |
265 | + close $fh; |
266 | + return @specs, @rules; |
267 | +} |
268 | + |
269 | +sub _parse_specs { |
270 | + my ( $self, @specs ) = @_; |
271 | + my %disables; # special rule that requires deferred checking |
272 | + |
273 | + foreach my $opt ( @specs ) { |
274 | + if ( ref $opt ) { # It's an option spec, not a rule. |
275 | + PTDEBUG && _d('Parsing opt spec:', |
276 | + map { ($_, '=>', $opt->{$_}) } keys %$opt); |
277 | + |
278 | + my ( $long, $short ) = $opt->{spec} =~ m/^([\w-]+)(?:\|([^!+=]*))?/; |
279 | + if ( !$long ) { |
280 | + die "Cannot parse long option from spec $opt->{spec}"; |
281 | + } |
282 | + $opt->{long} = $long; |
283 | + |
284 | + die "Duplicate long option --$long" if exists $self->{opts}->{$long}; |
285 | + $self->{opts}->{$long} = $opt; |
286 | + |
287 | + if ( length $long == 1 ) { |
288 | + PTDEBUG && _d('Long opt', $long, 'looks like short opt'); |
289 | + $self->{short_opts}->{$long} = $long; |
290 | + } |
291 | + |
292 | + if ( $short ) { |
293 | + die "Duplicate short option -$short" |
294 | + if exists $self->{short_opts}->{$short}; |
295 | + $self->{short_opts}->{$short} = $long; |
296 | + $opt->{short} = $short; |
297 | + } |
298 | + else { |
299 | + $opt->{short} = undef; |
300 | + } |
301 | + |
302 | + $opt->{is_negatable} = $opt->{spec} =~ m/!/ ? 1 : 0; |
303 | + $opt->{is_cumulative} = $opt->{spec} =~ m/\+/ ? 1 : 0; |
304 | + $opt->{is_required} = $opt->{desc} =~ m/required/ ? 1 : 0; |
305 | + |
306 | + $opt->{group} ||= 'default'; |
307 | + $self->{groups}->{ $opt->{group} }->{$long} = 1; |
308 | + |
309 | + $opt->{value} = undef; |
310 | + $opt->{got} = 0; |
311 | + |
312 | + my ( $type ) = $opt->{spec} =~ m/=(.)/; |
313 | + $opt->{type} = $type; |
314 | + PTDEBUG && _d($long, 'type:', $type); |
315 | + |
316 | + |
317 | + $opt->{spec} =~ s/=./=s/ if ( $type && $type =~ m/[HhAadzm]/ ); |
318 | + |
319 | + if ( (my ($def) = $opt->{desc} =~ m/default\b(?: ([^)]+))?/) ) { |
320 | + $self->{defaults}->{$long} = defined $def ? $def : 1; |
321 | + PTDEBUG && _d($long, 'default:', $def); |
322 | + } |
323 | + |
324 | + if ( $long eq 'config' ) { |
325 | + $self->{defaults}->{$long} = join(',', $self->get_defaults_files()); |
326 | + } |
327 | + |
328 | + if ( (my ($dis) = $opt->{desc} =~ m/(disables .*)/) ) { |
329 | + $disables{$long} = $dis; |
330 | + PTDEBUG && _d('Deferring check of disables rule for', $opt, $dis); |
331 | + } |
332 | + |
333 | + $self->{opts}->{$long} = $opt; |
334 | + } |
335 | + else { # It's an option rule, not a spec. |
336 | + PTDEBUG && _d('Parsing rule:', $opt); |
337 | + push @{$self->{rules}}, $opt; |
338 | + my @participants = $self->_get_participants($opt); |
339 | + my $rule_ok = 0; |
340 | + |
341 | + if ( $opt =~ m/mutually exclusive|one and only one/ ) { |
342 | + $rule_ok = 1; |
343 | + push @{$self->{mutex}}, \@participants; |
344 | + PTDEBUG && _d(@participants, 'are mutually exclusive'); |
345 | + } |
346 | + if ( $opt =~ m/at least one|one and only one/ ) { |
347 | + $rule_ok = 1; |
348 | + push @{$self->{atleast1}}, \@participants; |
349 | + PTDEBUG && _d(@participants, 'require at least one'); |
350 | + } |
351 | + if ( $opt =~ m/default to/ ) { |
352 | + $rule_ok = 1; |
353 | + $self->{defaults_to}->{$participants[0]} = $participants[1]; |
354 | + PTDEBUG && _d($participants[0], 'defaults to', $participants[1]); |
355 | + } |
356 | + if ( $opt =~ m/restricted to option groups/ ) { |
357 | + $rule_ok = 1; |
358 | + my ($groups) = $opt =~ m/groups ([\w\s\,]+)/; |
359 | + my @groups = split(',', $groups); |
360 | + %{$self->{allowed_groups}->{$participants[0]}} = map { |
361 | + s/\s+//; |
362 | + $_ => 1; |
363 | + } @groups; |
364 | + } |
365 | + if( $opt =~ m/accepts additional command-line arguments/ ) { |
366 | + $rule_ok = 1; |
367 | + $self->{strict} = 0; |
368 | + PTDEBUG && _d("Strict mode disabled by rule"); |
369 | + } |
370 | + |
371 | + die "Unrecognized option rule: $opt" unless $rule_ok; |
372 | + } |
373 | + } |
374 | + |
375 | + foreach my $long ( keys %disables ) { |
376 | + my @participants = $self->_get_participants($disables{$long}); |
377 | + $self->{disables}->{$long} = \@participants; |
378 | + PTDEBUG && _d('Option', $long, 'disables', @participants); |
379 | + } |
380 | + |
381 | + return; |
382 | +} |
383 | + |
384 | +sub _get_participants { |
385 | + my ( $self, $str ) = @_; |
386 | + my @participants; |
387 | + foreach my $long ( $str =~ m/--(?:\[no\])?([\w-]+)/g ) { |
388 | + die "Option --$long does not exist while processing rule $str" |
389 | + unless exists $self->{opts}->{$long}; |
390 | + push @participants, $long; |
391 | + } |
392 | + PTDEBUG && _d('Participants for', $str, ':', @participants); |
393 | + return @participants; |
394 | +} |
395 | + |
396 | +sub opts { |
397 | + my ( $self ) = @_; |
398 | + my %opts = %{$self->{opts}}; |
399 | + return %opts; |
400 | +} |
401 | + |
402 | +sub short_opts { |
403 | + my ( $self ) = @_; |
404 | + my %short_opts = %{$self->{short_opts}}; |
405 | + return %short_opts; |
406 | +} |
407 | + |
408 | +sub set_defaults { |
409 | + my ( $self, %defaults ) = @_; |
410 | + $self->{defaults} = {}; |
411 | + foreach my $long ( keys %defaults ) { |
412 | + die "Cannot set default for nonexistent option $long" |
413 | + unless exists $self->{opts}->{$long}; |
414 | + $self->{defaults}->{$long} = $defaults{$long}; |
415 | + PTDEBUG && _d('Default val for', $long, ':', $defaults{$long}); |
416 | + } |
417 | + return; |
418 | +} |
419 | + |
420 | +sub get_defaults { |
421 | + my ( $self ) = @_; |
422 | + return $self->{defaults}; |
423 | +} |
424 | + |
425 | +sub get_groups { |
426 | + my ( $self ) = @_; |
427 | + return $self->{groups}; |
428 | +} |
429 | + |
430 | +sub _set_option { |
431 | + my ( $self, $opt, $val ) = @_; |
432 | + my $long = exists $self->{opts}->{$opt} ? $opt |
433 | + : exists $self->{short_opts}->{$opt} ? $self->{short_opts}->{$opt} |
434 | + : die "Getopt::Long gave a nonexistent option: $opt"; |
435 | + |
436 | + $opt = $self->{opts}->{$long}; |
437 | + if ( $opt->{is_cumulative} ) { |
438 | + $opt->{value}++; |
439 | + } |
440 | + else { |
441 | + $opt->{value} = $val; |
442 | + } |
443 | + $opt->{got} = 1; |
444 | + PTDEBUG && _d('Got option', $long, '=', $val); |
445 | +} |
446 | + |
447 | +sub get_opts { |
448 | + my ( $self ) = @_; |
449 | + |
450 | + foreach my $long ( keys %{$self->{opts}} ) { |
451 | + $self->{opts}->{$long}->{got} = 0; |
452 | + $self->{opts}->{$long}->{value} |
453 | + = exists $self->{defaults}->{$long} ? $self->{defaults}->{$long} |
454 | + : $self->{opts}->{$long}->{is_cumulative} ? 0 |
455 | + : undef; |
456 | + } |
457 | + $self->{got_opts} = 0; |
458 | + |
459 | + $self->{errors} = []; |
460 | + |
461 | + if ( @ARGV && $ARGV[0] eq "--config" ) { |
462 | + shift @ARGV; |
463 | + $self->_set_option('config', shift @ARGV); |
464 | + } |
465 | + if ( $self->has('config') ) { |
466 | + my @extra_args; |
467 | + foreach my $filename ( split(',', $self->get('config')) ) { |
468 | + eval { |
469 | + push @extra_args, $self->_read_config_file($filename); |
470 | + }; |
471 | + if ( $EVAL_ERROR ) { |
472 | + if ( $self->got('config') ) { |
473 | + die $EVAL_ERROR; |
474 | + } |
475 | + elsif ( PTDEBUG ) { |
476 | + _d($EVAL_ERROR); |
477 | + } |
478 | + } |
479 | + } |
480 | + unshift @ARGV, @extra_args; |
481 | + } |
482 | + |
483 | + Getopt::Long::Configure('no_ignore_case', 'bundling'); |
484 | + GetOptions( |
485 | + map { $_->{spec} => sub { $self->_set_option(@_); } } |
486 | + grep { $_->{long} ne 'config' } # --config is handled specially above. |
487 | + values %{$self->{opts}} |
488 | + ) or $self->save_error('Error parsing options'); |
489 | + |
490 | + if ( exists $self->{opts}->{version} && $self->{opts}->{version}->{got} ) { |
491 | + if ( $self->{version} ) { |
492 | + print $self->{version}, "\n"; |
493 | + } |
494 | + else { |
495 | + print "Error parsing version. See the VERSION section of the tool's documentation.\n"; |
496 | + } |
497 | + exit 1; |
498 | + } |
499 | + |
500 | + if ( @ARGV && $self->{strict} ) { |
501 | + $self->save_error("Unrecognized command-line options @ARGV"); |
502 | + } |
503 | + |
504 | + foreach my $mutex ( @{$self->{mutex}} ) { |
505 | + my @set = grep { $self->{opts}->{$_}->{got} } @$mutex; |
506 | + if ( @set > 1 ) { |
507 | + my $err = join(', ', map { "--$self->{opts}->{$_}->{long}" } |
508 | + @{$mutex}[ 0 .. scalar(@$mutex) - 2] ) |
509 | + . ' and --'.$self->{opts}->{$mutex->[-1]}->{long} |
510 | + . ' are mutually exclusive.'; |
511 | + $self->save_error($err); |
512 | + } |
513 | + } |
514 | + |
515 | + foreach my $required ( @{$self->{atleast1}} ) { |
516 | + my @set = grep { $self->{opts}->{$_}->{got} } @$required; |
517 | + if ( @set == 0 ) { |
518 | + my $err = join(', ', map { "--$self->{opts}->{$_}->{long}" } |
519 | + @{$required}[ 0 .. scalar(@$required) - 2] ) |
520 | + .' or --'.$self->{opts}->{$required->[-1]}->{long}; |
521 | + $self->save_error("Specify at least one of $err"); |
522 | + } |
523 | + } |
524 | + |
525 | + $self->_check_opts( keys %{$self->{opts}} ); |
526 | + $self->{got_opts} = 1; |
527 | + return; |
528 | +} |
529 | + |
530 | +sub _check_opts { |
531 | + my ( $self, @long ) = @_; |
532 | + my $long_last = scalar @long; |
533 | + while ( @long ) { |
534 | + foreach my $i ( 0..$#long ) { |
535 | + my $long = $long[$i]; |
536 | + next unless $long; |
537 | + my $opt = $self->{opts}->{$long}; |
538 | + if ( $opt->{got} ) { |
539 | + if ( exists $self->{disables}->{$long} ) { |
540 | + my @disable_opts = @{$self->{disables}->{$long}}; |
541 | + map { $self->{opts}->{$_}->{value} = undef; } @disable_opts; |
542 | + PTDEBUG && _d('Unset options', @disable_opts, |
543 | + 'because', $long,'disables them'); |
544 | + } |
545 | + |
546 | + if ( exists $self->{allowed_groups}->{$long} ) { |
547 | + |
548 | + my @restricted_groups = grep { |
549 | + !exists $self->{allowed_groups}->{$long}->{$_} |
550 | + } keys %{$self->{groups}}; |
551 | + |
552 | + my @restricted_opts; |
553 | + foreach my $restricted_group ( @restricted_groups ) { |
554 | + RESTRICTED_OPT: |
555 | + foreach my $restricted_opt ( |
556 | + keys %{$self->{groups}->{$restricted_group}} ) |
557 | + { |
558 | + next RESTRICTED_OPT if $restricted_opt eq $long; |
559 | + push @restricted_opts, $restricted_opt |
560 | + if $self->{opts}->{$restricted_opt}->{got}; |
561 | + } |
562 | + } |
563 | + |
564 | + if ( @restricted_opts ) { |
565 | + my $err; |
566 | + if ( @restricted_opts == 1 ) { |
567 | + $err = "--$restricted_opts[0]"; |
568 | + } |
569 | + else { |
570 | + $err = join(', ', |
571 | + map { "--$self->{opts}->{$_}->{long}" } |
572 | + grep { $_ } |
573 | + @restricted_opts[0..scalar(@restricted_opts) - 2] |
574 | + ) |
575 | + . ' or --'.$self->{opts}->{$restricted_opts[-1]}->{long}; |
576 | + } |
577 | + $self->save_error("--$long is not allowed with $err"); |
578 | + } |
579 | + } |
580 | + |
581 | + } |
582 | + elsif ( $opt->{is_required} ) { |
583 | + $self->save_error("Required option --$long must be specified"); |
584 | + } |
585 | + |
586 | + $self->_validate_type($opt); |
587 | + if ( $opt->{parsed} ) { |
588 | + delete $long[$i]; |
589 | + } |
590 | + else { |
591 | + PTDEBUG && _d('Temporarily failed to parse', $long); |
592 | + } |
593 | + } |
594 | + |
595 | + die "Failed to parse options, possibly due to circular dependencies" |
596 | + if @long == $long_last; |
597 | + $long_last = @long; |
598 | + } |
599 | + |
600 | + return; |
601 | +} |
602 | + |
603 | +sub _validate_type { |
604 | + my ( $self, $opt ) = @_; |
605 | + return unless $opt; |
606 | + |
607 | + if ( !$opt->{type} ) { |
608 | + $opt->{parsed} = 1; |
609 | + return; |
610 | + } |
611 | + |
612 | + my $val = $opt->{value}; |
613 | + |
614 | + if ( $val && $opt->{type} eq 'm' ) { # type time |
615 | + PTDEBUG && _d('Parsing option', $opt->{long}, 'as a time value'); |
616 | + my ( $prefix, $num, $suffix ) = $val =~ m/([+-]?)(\d+)([a-z])?$/; |
617 | + if ( !$suffix ) { |
618 | + my ( $s ) = $opt->{desc} =~ m/\(suffix (.)\)/; |
619 | + $suffix = $s || 's'; |
620 | + PTDEBUG && _d('No suffix given; using', $suffix, 'for', |
621 | + $opt->{long}, '(value:', $val, ')'); |
622 | + } |
623 | + if ( $suffix =~ m/[smhd]/ ) { |
624 | + $val = $suffix eq 's' ? $num # Seconds |
625 | + : $suffix eq 'm' ? $num * 60 # Minutes |
626 | + : $suffix eq 'h' ? $num * 3600 # Hours |
627 | + : $num * 86400; # Days |
628 | + $opt->{value} = ($prefix || '') . $val; |
629 | + PTDEBUG && _d('Setting option', $opt->{long}, 'to', $val); |
630 | + } |
631 | + else { |
632 | + $self->save_error("Invalid time suffix for --$opt->{long}"); |
633 | + } |
634 | + } |
635 | + elsif ( $val && $opt->{type} eq 'd' ) { # type DSN |
636 | + PTDEBUG && _d('Parsing option', $opt->{long}, 'as a DSN'); |
637 | + my $prev = {}; |
638 | + my $from_key = $self->{defaults_to}->{ $opt->{long} }; |
639 | + if ( $from_key ) { |
640 | + PTDEBUG && _d($opt->{long}, 'DSN copies from', $from_key, 'DSN'); |
641 | + if ( $self->{opts}->{$from_key}->{parsed} ) { |
642 | + $prev = $self->{opts}->{$from_key}->{value}; |
643 | + } |
644 | + else { |
645 | + PTDEBUG && _d('Cannot parse', $opt->{long}, 'until', |
646 | + $from_key, 'parsed'); |
647 | + return; |
648 | + } |
649 | + } |
650 | + my $defaults = $self->{DSNParser}->parse_options($self); |
651 | + $opt->{value} = $self->{DSNParser}->parse($val, $prev, $defaults); |
652 | + } |
653 | + elsif ( $val && $opt->{type} eq 'z' ) { # type size |
654 | + PTDEBUG && _d('Parsing option', $opt->{long}, 'as a size value'); |
655 | + $self->_parse_size($opt, $val); |
656 | + } |
657 | + elsif ( $opt->{type} eq 'H' || (defined $val && $opt->{type} eq 'h') ) { |
658 | + $opt->{value} = { map { $_ => 1 } split(/(?<!\\),\s*/, ($val || '')) }; |
659 | + } |
660 | + elsif ( $opt->{type} eq 'A' || (defined $val && $opt->{type} eq 'a') ) { |
661 | + $opt->{value} = [ split(/(?<!\\),\s*/, ($val || '')) ]; |
662 | + } |
663 | + else { |
664 | + PTDEBUG && _d('Nothing to validate for option', |
665 | + $opt->{long}, 'type', $opt->{type}, 'value', $val); |
666 | + } |
667 | + |
668 | + $opt->{parsed} = 1; |
669 | + return; |
670 | +} |
671 | + |
672 | +sub get { |
673 | + my ( $self, $opt ) = @_; |
674 | + my $long = (length $opt == 1 ? $self->{short_opts}->{$opt} : $opt); |
675 | + die "Option $opt does not exist" |
676 | + unless $long && exists $self->{opts}->{$long}; |
677 | + return $self->{opts}->{$long}->{value}; |
678 | +} |
679 | + |
680 | +sub got { |
681 | + my ( $self, $opt ) = @_; |
682 | + my $long = (length $opt == 1 ? $self->{short_opts}->{$opt} : $opt); |
683 | + die "Option $opt does not exist" |
684 | + unless $long && exists $self->{opts}->{$long}; |
685 | + return $self->{opts}->{$long}->{got}; |
686 | +} |
687 | + |
688 | +sub has { |
689 | + my ( $self, $opt ) = @_; |
690 | + my $long = (length $opt == 1 ? $self->{short_opts}->{$opt} : $opt); |
691 | + return defined $long ? exists $self->{opts}->{$long} : 0; |
692 | +} |
693 | + |
694 | +sub set { |
695 | + my ( $self, $opt, $val ) = @_; |
696 | + my $long = (length $opt == 1 ? $self->{short_opts}->{$opt} : $opt); |
697 | + die "Option $opt does not exist" |
698 | + unless $long && exists $self->{opts}->{$long}; |
699 | + $self->{opts}->{$long}->{value} = $val; |
700 | + return; |
701 | +} |
702 | + |
703 | +sub save_error { |
704 | + my ( $self, $error ) = @_; |
705 | + push @{$self->{errors}}, $error; |
706 | + return; |
707 | +} |
708 | + |
709 | +sub errors { |
710 | + my ( $self ) = @_; |
711 | + return $self->{errors}; |
712 | +} |
713 | + |
714 | +sub usage { |
715 | + my ( $self ) = @_; |
716 | + warn "No usage string is set" unless $self->{usage}; # XXX |
717 | + return "Usage: " . ($self->{usage} || '') . "\n"; |
718 | +} |
719 | + |
720 | +sub descr { |
721 | + my ( $self ) = @_; |
722 | + warn "No description string is set" unless $self->{description}; # XXX |
723 | + my $descr = ($self->{description} || $self->{program_name} || '') |
724 | + . " For more details, please use the --help option, " |
725 | + . "or try 'perldoc $PROGRAM_NAME' " |
726 | + . "for complete documentation."; |
727 | + $descr = join("\n", $descr =~ m/(.{0,80})(?:\s+|$)/g) |
728 | + unless $ENV{DONT_BREAK_LINES}; |
729 | + $descr =~ s/ +$//mg; |
730 | + return $descr; |
731 | +} |
732 | + |
733 | +sub usage_or_errors { |
734 | + my ( $self, $file, $return ) = @_; |
735 | + $file ||= $self->{file} || __FILE__; |
736 | + |
737 | + if ( !$self->{description} || !$self->{usage} ) { |
738 | + PTDEBUG && _d("Getting description and usage from SYNOPSIS in", $file); |
739 | + my %synop = $self->_parse_synopsis($file); |
740 | + $self->{description} ||= $synop{description}; |
741 | + $self->{usage} ||= $synop{usage}; |
742 | + PTDEBUG && _d("Description:", $self->{description}, |
743 | + "\nUsage:", $self->{usage}); |
744 | + } |
745 | + |
746 | + if ( $self->{opts}->{help}->{got} ) { |
747 | + print $self->print_usage() or die "Cannot print usage: $OS_ERROR"; |
748 | + exit 0 unless $return; |
749 | + } |
750 | + elsif ( scalar @{$self->{errors}} ) { |
751 | + print $self->print_errors() or die "Cannot print errors: $OS_ERROR"; |
752 | + exit 1 unless $return; |
753 | + } |
754 | + |
755 | + return; |
756 | +} |
757 | + |
758 | +sub print_errors { |
759 | + my ( $self ) = @_; |
760 | + my $usage = $self->usage() . "\n"; |
761 | + if ( (my @errors = @{$self->{errors}}) ) { |
762 | + $usage .= join("\n * ", 'Errors in command-line arguments:', @errors) |
763 | + . "\n"; |
764 | + } |
765 | + return $usage . "\n" . $self->descr(); |
766 | +} |
767 | + |
768 | +sub print_usage { |
769 | + my ( $self ) = @_; |
770 | + die "Run get_opts() before print_usage()" unless $self->{got_opts}; |
771 | + my @opts = values %{$self->{opts}}; |
772 | + |
773 | + my $maxl = max( |
774 | + map { |
775 | + length($_->{long}) # option long name |
776 | + + ($_->{is_negatable} ? 4 : 0) # "[no]" if opt is negatable |
777 | + + ($_->{type} ? 2 : 0) # "=x" where x is the opt type |
778 | + } |
779 | + @opts); |
780 | + |
781 | + my $maxs = max(0, |
782 | + map { |
783 | + length($_) |
784 | + + ($self->{opts}->{$_}->{is_negatable} ? 4 : 0) |
785 | + + ($self->{opts}->{$_}->{type} ? 2 : 0) |
786 | + } |
787 | + values %{$self->{short_opts}}); |
788 | + |
789 | + my $lcol = max($maxl, ($maxs + 3)); |
790 | + my $rcol = 80 - $lcol - 6; |
791 | + my $rpad = ' ' x ( 80 - $rcol ); |
792 | + |
793 | + $maxs = max($lcol - 3, $maxs); |
794 | + |
795 | + my $usage = $self->descr() . "\n" . $self->usage(); |
796 | + |
797 | + my @groups = reverse sort grep { $_ ne 'default'; } keys %{$self->{groups}}; |
798 | + push @groups, 'default'; |
799 | + |
800 | + foreach my $group ( reverse @groups ) { |
801 | + $usage .= "\n".($group eq 'default' ? 'Options' : $group).":\n\n"; |
802 | + foreach my $opt ( |
803 | + sort { $a->{long} cmp $b->{long} } |
804 | + grep { $_->{group} eq $group } |
805 | + @opts ) |
806 | + { |
807 | + my $long = $opt->{is_negatable} ? "[no]$opt->{long}" : $opt->{long}; |
808 | + my $short = $opt->{short}; |
809 | + my $desc = $opt->{desc}; |
810 | + |
811 | + $long .= $opt->{type} ? "=$opt->{type}" : ""; |
812 | + |
813 | + if ( $opt->{type} && $opt->{type} eq 'm' ) { |
814 | + my ($s) = $desc =~ m/\(suffix (.)\)/; |
815 | + $s ||= 's'; |
816 | + $desc =~ s/\s+\(suffix .\)//; |
817 | + $desc .= ". Optional suffix s=seconds, m=minutes, h=hours, " |
818 | + . "d=days; if no suffix, $s is used."; |
819 | + } |
820 | + $desc = join("\n$rpad", grep { $_ } $desc =~ m/(.{0,$rcol}(?!\W))(?:\s+|(?<=\W)|$)/g); |
821 | + $desc =~ s/ +$//mg; |
822 | + if ( $short ) { |
823 | + $usage .= sprintf(" --%-${maxs}s -%s %s\n", $long, $short, $desc); |
824 | + } |
825 | + else { |
826 | + $usage .= sprintf(" --%-${lcol}s %s\n", $long, $desc); |
827 | + } |
828 | + } |
829 | + } |
830 | + |
831 | + $usage .= "\nOption types: s=string, i=integer, f=float, h/H/a/A=comma-separated list, d=DSN, z=size, m=time\n"; |
832 | + |
833 | + if ( (my @rules = @{$self->{rules}}) ) { |
834 | + $usage .= "\nRules:\n\n"; |
835 | + $usage .= join("\n", map { " $_" } @rules) . "\n"; |
836 | + } |
837 | + if ( $self->{DSNParser} ) { |
838 | + $usage .= "\n" . $self->{DSNParser}->usage(); |
839 | + } |
840 | + $usage .= "\nOptions and values after processing arguments:\n\n"; |
841 | + foreach my $opt ( sort { $a->{long} cmp $b->{long} } @opts ) { |
842 | + my $val = $opt->{value}; |
843 | + my $type = $opt->{type} || ''; |
844 | + my $bool = $opt->{spec} =~ m/^[\w-]+(?:\|[\w-])?!?$/; |
845 | + $val = $bool ? ( $val ? 'TRUE' : 'FALSE' ) |
846 | + : !defined $val ? '(No value)' |
847 | + : $type eq 'd' ? $self->{DSNParser}->as_string($val) |
848 | + : $type =~ m/H|h/ ? join(',', sort keys %$val) |
849 | + : $type =~ m/A|a/ ? join(',', @$val) |
850 | + : $val; |
851 | + $usage .= sprintf(" --%-${lcol}s %s\n", $opt->{long}, $val); |
852 | + } |
853 | + return $usage; |
854 | +} |
855 | + |
856 | +sub prompt_noecho { |
857 | + shift @_ if ref $_[0] eq __PACKAGE__; |
858 | + my ( $prompt ) = @_; |
859 | + local $OUTPUT_AUTOFLUSH = 1; |
860 | + print $prompt |
861 | + or die "Cannot print: $OS_ERROR"; |
862 | + my $response; |
863 | + eval { |
864 | + require Term::ReadKey; |
865 | + Term::ReadKey::ReadMode('noecho'); |
866 | + chomp($response = <STDIN>); |
867 | + Term::ReadKey::ReadMode('normal'); |
868 | + print "\n" |
869 | + or die "Cannot print: $OS_ERROR"; |
870 | + }; |
871 | + if ( $EVAL_ERROR ) { |
872 | + die "Cannot read response; is Term::ReadKey installed? $EVAL_ERROR"; |
873 | + } |
874 | + return $response; |
875 | +} |
876 | + |
877 | +sub _read_config_file { |
878 | + my ( $self, $filename ) = @_; |
879 | + open my $fh, "<", $filename or die "Cannot open $filename: $OS_ERROR\n"; |
880 | + my @args; |
881 | + my $prefix = '--'; |
882 | + my $parse = 1; |
883 | + |
884 | + LINE: |
885 | + while ( my $line = <$fh> ) { |
886 | + chomp $line; |
887 | + next LINE if $line =~ m/^\s*(?:\#|\;|$)/; |
888 | + $line =~ s/\s+#.*$//g; |
889 | + $line =~ s/^\s+|\s+$//g; |
890 | + if ( $line eq '--' ) { |
891 | + $prefix = ''; |
892 | + $parse = 0; |
893 | + next LINE; |
894 | + } |
895 | + if ( $parse |
896 | + && (my($opt, $arg) = $line =~ m/^\s*([^=\s]+?)(?:\s*=\s*(.*?)\s*)?$/) |
897 | + ) { |
898 | + push @args, grep { defined $_ } ("$prefix$opt", $arg); |
899 | + } |
900 | + elsif ( $line =~ m/./ ) { |
901 | + push @args, $line; |
902 | + } |
903 | + else { |
904 | + die "Syntax error in file $filename at line $INPUT_LINE_NUMBER"; |
905 | + } |
906 | + } |
907 | + close $fh; |
908 | + return @args; |
909 | +} |
910 | + |
911 | +sub read_para_after { |
912 | + my ( $self, $file, $regex ) = @_; |
913 | + open my $fh, "<", $file or die "Can't open $file: $OS_ERROR"; |
914 | + local $INPUT_RECORD_SEPARATOR = ''; |
915 | + my $para; |
916 | + while ( $para = <$fh> ) { |
917 | + next unless $para =~ m/^=pod$/m; |
918 | + last; |
919 | + } |
920 | + while ( $para = <$fh> ) { |
921 | + next unless $para =~ m/$regex/; |
922 | + last; |
923 | + } |
924 | + $para = <$fh>; |
925 | + chomp($para); |
926 | + close $fh or die "Can't close $file: $OS_ERROR"; |
927 | + return $para; |
928 | +} |
929 | + |
930 | +sub clone { |
931 | + my ( $self ) = @_; |
932 | + |
933 | + my %clone = map { |
934 | + my $hashref = $self->{$_}; |
935 | + my $val_copy = {}; |
936 | + foreach my $key ( keys %$hashref ) { |
937 | + my $ref = ref $hashref->{$key}; |
938 | + $val_copy->{$key} = !$ref ? $hashref->{$key} |
939 | + : $ref eq 'HASH' ? { %{$hashref->{$key}} } |
940 | + : $ref eq 'ARRAY' ? [ @{$hashref->{$key}} ] |
941 | + : $hashref->{$key}; |
942 | + } |
943 | + $_ => $val_copy; |
944 | + } qw(opts short_opts defaults); |
945 | + |
946 | + foreach my $scalar ( qw(got_opts) ) { |
947 | + $clone{$scalar} = $self->{$scalar}; |
948 | + } |
949 | + |
950 | + return bless \%clone; |
951 | +} |
952 | + |
953 | +sub _parse_size { |
954 | + my ( $self, $opt, $val ) = @_; |
955 | + |
956 | + if ( lc($val || '') eq 'null' ) { |
957 | + PTDEBUG && _d('NULL size for', $opt->{long}); |
958 | + $opt->{value} = 'null'; |
959 | + return; |
960 | + } |
961 | + |
962 | + my %factor_for = (k => 1_024, M => 1_048_576, G => 1_073_741_824); |
963 | + my ($pre, $num, $factor) = $val =~ m/^([+-])?(\d+)([kMG])?$/; |
964 | + if ( defined $num ) { |
965 | + if ( $factor ) { |
966 | + $num *= $factor_for{$factor}; |
967 | + PTDEBUG && _d('Setting option', $opt->{y}, |
968 | + 'to num', $num, '* factor', $factor); |
969 | + } |
970 | + $opt->{value} = ($pre || '') . $num; |
971 | + } |
972 | + else { |
973 | + $self->save_error("Invalid size for --$opt->{long}: $val"); |
974 | + } |
975 | + return; |
976 | +} |
977 | + |
978 | +sub _parse_attribs { |
979 | + my ( $self, $option, $attribs ) = @_; |
980 | + my $types = $self->{types}; |
981 | + return $option |
982 | + . ($attribs->{'short form'} ? '|' . $attribs->{'short form'} : '' ) |
983 | + . ($attribs->{'negatable'} ? '!' : '' ) |
984 | + . ($attribs->{'cumulative'} ? '+' : '' ) |
985 | + . ($attribs->{'type'} ? '=' . $types->{$attribs->{type}} : '' ); |
986 | +} |
987 | + |
988 | +sub _parse_synopsis { |
989 | + my ( $self, $file ) = @_; |
990 | + $file ||= $self->{file} || __FILE__; |
991 | + PTDEBUG && _d("Parsing SYNOPSIS in", $file); |
992 | + |
993 | + local $INPUT_RECORD_SEPARATOR = ''; # read paragraphs |
994 | + open my $fh, "<", $file or die "Cannot open $file: $OS_ERROR"; |
995 | + my $para; |
996 | + 1 while defined($para = <$fh>) && $para !~ m/^=head1 SYNOPSIS/; |
997 | + die "$file does not contain a SYNOPSIS section" unless $para; |
998 | + my @synop; |
999 | + for ( 1..2 ) { # 1 for the usage, 2 for the description |
1000 | + my $para = <$fh>; |
1001 | + push @synop, $para; |
1002 | + } |
1003 | + close $fh; |
1004 | + PTDEBUG && _d("Raw SYNOPSIS text:", @synop); |
1005 | + my ($usage, $desc) = @synop; |
1006 | + die "The SYNOPSIS section in $file is not formatted properly" |
1007 | + unless $usage && $desc; |
1008 | + |
1009 | + $usage =~ s/^\s*Usage:\s+(.+)/$1/; |
1010 | + chomp $usage; |
1011 | + |
1012 | + $desc =~ s/\n/ /g; |
1013 | + $desc =~ s/\s{2,}/ /g; |
1014 | + $desc =~ s/\. ([A-Z][a-z])/. $1/g; |
1015 | + $desc =~ s/\s+$//; |
1016 | + |
1017 | + return ( |
1018 | + description => $desc, |
1019 | + usage => $usage, |
1020 | + ); |
1021 | +}; |
1022 | + |
1023 | +sub set_vars { |
1024 | + my ($self, $file) = @_; |
1025 | + $file ||= $self->{file} || __FILE__; |
1026 | + |
1027 | + my %user_vars; |
1028 | + my $user_vars = $self->has('set-vars') ? $self->get('set-vars') : undef; |
1029 | + if ( $user_vars ) { |
1030 | + foreach my $var_val ( @$user_vars ) { |
1031 | + my ($var, $val) = $var_val =~ m/([^\s=]+)=(\S+)/; |
1032 | + die "Invalid --set-vars value: $var_val\n" unless $var && $val; |
1033 | + $user_vars{$var} = { |
1034 | + val => $val, |
1035 | + default => 0, |
1036 | + }; |
1037 | + } |
1038 | + } |
1039 | + |
1040 | + my %default_vars; |
1041 | + my $default_vars = $self->read_para_after($file, qr/MAGIC_set_vars/); |
1042 | + if ( $default_vars ) { |
1043 | + %default_vars = map { |
1044 | + my $var_val = $_; |
1045 | + my ($var, $val) = $var_val =~ m/([^\s=]+)=(\S+)/; |
1046 | + die "Invalid --set-vars value: $var_val\n" unless $var && $val; |
1047 | + $var => { |
1048 | + val => $val, |
1049 | + default => 1, |
1050 | + }; |
1051 | + } split("\n", $default_vars); |
1052 | + } |
1053 | + |
1054 | + my %vars = ( |
1055 | + %default_vars, # first the tool's defaults |
1056 | + %user_vars, # then the user's which overwrite the defaults |
1057 | + ); |
1058 | + PTDEBUG && _d('--set-vars:', Dumper(\%vars)); |
1059 | + return \%vars; |
1060 | +} |
1061 | + |
1062 | +sub _d { |
1063 | + my ($package, undef, $line) = caller 0; |
1064 | + @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; } |
1065 | + map { defined $_ ? $_ : 'undef' } |
1066 | + @_; |
1067 | + print STDERR "# $package:$line $PID ", join(' ', @_), "\n"; |
1068 | +} |
1069 | + |
1070 | +if ( PTDEBUG ) { |
1071 | + print '# ', $^X, ' ', $], "\n"; |
1072 | + if ( my $uname = `uname -a` ) { |
1073 | + $uname =~ s/\s+/ /g; |
1074 | + print "# $uname\n"; |
1075 | + } |
1076 | + print '# Arguments: ', |
1077 | + join(' ', map { my $a = "_[$_]_"; $a =~ s/\n/\n# /g; $a; } @ARGV), "\n"; |
1078 | +} |
1079 | + |
1080 | +1; |
1081 | +} |
1082 | +# ########################################################################### |
1083 | +# End OptionParser package |
1084 | +# ########################################################################### |
1085 | + |
1086 | # ########################################################################### |
1087 | # This is a combination of modules and programs in one -- a runnable module. |
1088 | # http://www.perl.com/pub/a/2006/07/13/lightning-articles.html?page=last |
1089 | @@ -25,6 +1103,11 @@ |
1090 | |
1091 | @ARGV = @_; # set global ARGV for this package |
1092 | |
1093 | + my $o = OptionParser->new(); |
1094 | + $o->get_specs(); |
1095 | + $o->get_opts(); |
1096 | + $o->usage_or_errors(); |
1097 | + |
1098 | # Read all lines |
1099 | my @lines; |
1100 | my %word_count; |
1101 | @@ -130,7 +1213,17 @@ |
1102 | |
1103 | =head1 OPTIONS |
1104 | |
1105 | -This tool does not have any command-line options. |
1106 | +=over |
1107 | + |
1108 | +=item --help |
1109 | + |
1110 | +Show help and exit. |
1111 | + |
1112 | +=item --version |
1113 | + |
1114 | +Show version and exit. |
1115 | + |
1116 | +=back |
1117 | |
1118 | =head1 ENVIRONMENT |
1119 | |
1120 | |
1121 | === modified file 'bin/pt-mext' |
1122 | --- bin/pt-mext 2013-03-14 17:20:35 +0000 |
1123 | +++ bin/pt-mext 2013-04-04 21:05:27 +0000 |
1124 | @@ -4,18 +4,53 @@ |
1125 | # See "COPYRIGHT, LICENSE, AND WARRANTY" at the end of this file for legal |
1126 | # notices and disclaimers. |
1127 | |
1128 | -usage() { |
1129 | - if [ "${OPT_ERR}" ]; then |
1130 | - echo "${OPT_ERR}" >&2 |
1131 | - fi |
1132 | - echo "Usage: pt-mext [OPTIONS] -- COMMAND" >&2 |
1133 | - echo "For more information, 'man pt-mext' or 'perldoc $0'" >&2 |
1134 | +# ########################################################################### |
1135 | +# log_warn_die package |
1136 | +# This package is a copy without comments from the original. The original |
1137 | +# with comments and its test file can be found in the Bazaar repository at, |
1138 | +# lib/bash/log_warn_die.sh |
1139 | +# t/lib/bash/log_warn_die.sh |
1140 | +# See https://launchpad.net/percona-toolkit for more information. |
1141 | +# ########################################################################### |
1142 | + |
1143 | + |
1144 | +set -u |
1145 | + |
1146 | +PTFUNCNAME="" |
1147 | +PTDEBUG="${PTDEBUG:-""}" |
1148 | +EXIT_STATUS=0 |
1149 | + |
1150 | +ts() { |
1151 | + TS=$(date +%F-%T | tr ':-' '_') |
1152 | + echo "$TS $*" |
1153 | +} |
1154 | + |
1155 | +info() { |
1156 | + [ ${OPT_VERBOSE:-3} -ge 3 ] && ts "$*" |
1157 | +} |
1158 | + |
1159 | +log() { |
1160 | + [ ${OPT_VERBOSE:-3} -ge 2 ] && ts "$*" |
1161 | +} |
1162 | + |
1163 | +warn() { |
1164 | + [ ${OPT_VERBOSE:-3} -ge 1 ] && ts "$*" >&2 |
1165 | + EXIT_STATUS=1 |
1166 | +} |
1167 | + |
1168 | +die() { |
1169 | + ts "$*" >&2 |
1170 | + EXIT_STATUS=1 |
1171 | exit 1 |
1172 | } |
1173 | |
1174 | -if [ -z "$1" ]; then |
1175 | - usage; |
1176 | -fi |
1177 | +_d () { |
1178 | + [ "$PTDEBUG" ] && echo "# $PTFUNCNAME: $(ts "$*")" >&2 |
1179 | +} |
1180 | + |
1181 | +# ########################################################################### |
1182 | +# End log_warn_die package |
1183 | +# ########################################################################### |
1184 | |
1185 | # ########################################################################### |
1186 | # tmpdir package |
1187 | @@ -58,51 +93,497 @@ |
1188 | # End tmpdir package |
1189 | # ########################################################################### |
1190 | |
1191 | +# ########################################################################### |
1192 | +# parse_options package |
1193 | +# This package is a copy without comments from the original. The original |
1194 | +# with comments and its test file can be found in the Bazaar repository at, |
1195 | +# lib/bash/parse_options.sh |
1196 | +# t/lib/bash/parse_options.sh |
1197 | +# See https://launchpad.net/percona-toolkit for more information. |
1198 | +# ########################################################################### |
1199 | + |
1200 | + |
1201 | + |
1202 | + |
1203 | + |
1204 | +set -u |
1205 | + |
1206 | +ARGV="" # Non-option args (probably input files) |
1207 | +EXT_ARGV="" # Everything after -- (args for an external command) |
1208 | +HAVE_EXT_ARGV="" # Got --, everything else is put into EXT_ARGV |
1209 | +OPT_ERRS=0 # How many command line option errors |
1210 | +OPT_VERSION="" # If --version was specified |
1211 | +OPT_HELP="" # If --help was specified |
1212 | +PO_DIR="" # Directory with program option spec files |
1213 | + |
1214 | +usage() { |
1215 | + local file="$1" |
1216 | + |
1217 | + local usage="$(grep '^Usage: ' "$file")" |
1218 | + echo $usage |
1219 | + echo |
1220 | + echo "For more information, 'man $TOOL' or 'perldoc $file'." |
1221 | +} |
1222 | + |
1223 | +usage_or_errors() { |
1224 | + local file="$1" |
1225 | + |
1226 | + if [ "$OPT_VERSION" ]; then |
1227 | + local version=$(grep '^pt-[^ ]\+ [0-9]' "$file") |
1228 | + echo "$version" |
1229 | + return 1 |
1230 | + fi |
1231 | + |
1232 | + if [ "$OPT_HELP" ]; then |
1233 | + usage "$file" |
1234 | + echo |
1235 | + echo "Command line options:" |
1236 | + echo |
1237 | + perl -e ' |
1238 | + use strict; |
1239 | + use warnings FATAL => qw(all); |
1240 | + my $lcol = 20; # Allow this much space for option names. |
1241 | + my $rcol = 80 - $lcol; # The terminal is assumed to be 80 chars wide. |
1242 | + my $name; |
1243 | + while ( <> ) { |
1244 | + my $line = $_; |
1245 | + chomp $line; |
1246 | + if ( $line =~ s/^long:/ --/ ) { |
1247 | + $name = $line; |
1248 | + } |
1249 | + elsif ( $line =~ s/^desc:// ) { |
1250 | + $line =~ s/ +$//mg; |
1251 | + my @lines = grep { $_ } |
1252 | + $line =~ m/(.{0,$rcol})(?:\s+|\Z)/g; |
1253 | + if ( length($name) >= $lcol ) { |
1254 | + print $name, "\n", (q{ } x $lcol); |
1255 | + } |
1256 | + else { |
1257 | + printf "%-${lcol}s", $name; |
1258 | + } |
1259 | + print join("\n" . (q{ } x $lcol), @lines); |
1260 | + print "\n"; |
1261 | + } |
1262 | + } |
1263 | + ' "$PO_DIR"/* |
1264 | + echo |
1265 | + echo "Options and values after processing arguments:" |
1266 | + echo |
1267 | + ( |
1268 | + cd "$PO_DIR" |
1269 | + for opt in *; do |
1270 | + local varname="OPT_$(echo "$opt" | tr a-z- A-Z_)" |
1271 | + eval local varvalue=\$$varname |
1272 | + if ! grep -q "type:" "$PO_DIR/$opt" >/dev/null; then |
1273 | + if [ "$varvalue" -a "$varvalue" = "yes" ]; |
1274 | + then varvalue="TRUE" |
1275 | + else |
1276 | + varvalue="FALSE" |
1277 | + fi |
1278 | + fi |
1279 | + printf -- " --%-30s %s" "$opt" "${varvalue:-(No value)}" |
1280 | + echo |
1281 | + done |
1282 | + ) |
1283 | + return 1 |
1284 | + fi |
1285 | + |
1286 | + if [ $OPT_ERRS -gt 0 ]; then |
1287 | + echo |
1288 | + usage "$file" |
1289 | + return 1 |
1290 | + fi |
1291 | + |
1292 | + return 0 |
1293 | +} |
1294 | + |
1295 | +option_error() { |
1296 | + local err="$1" |
1297 | + OPT_ERRS=$(($OPT_ERRS + 1)) |
1298 | + echo "$err" >&2 |
1299 | +} |
1300 | + |
1301 | +parse_options() { |
1302 | + local file="$1" |
1303 | + shift |
1304 | + |
1305 | + ARGV="" |
1306 | + EXT_ARGV="" |
1307 | + HAVE_EXT_ARGV="" |
1308 | + OPT_ERRS=0 |
1309 | + OPT_VERSION="" |
1310 | + OPT_HELP="" |
1311 | + PO_DIR="$PT_TMPDIR/po" |
1312 | + |
1313 | + if [ ! -d "$PO_DIR" ]; then |
1314 | + mkdir "$PO_DIR" |
1315 | + if [ $? -ne 0 ]; then |
1316 | + echo "Cannot mkdir $PO_DIR" >&2 |
1317 | + exit 1 |
1318 | + fi |
1319 | + fi |
1320 | + |
1321 | + rm -rf "$PO_DIR"/* |
1322 | + if [ $? -ne 0 ]; then |
1323 | + echo "Cannot rm -rf $PO_DIR/*" >&2 |
1324 | + exit 1 |
1325 | + fi |
1326 | + |
1327 | + _parse_pod "$file" # Parse POD into program option (po) spec files |
1328 | + _eval_po # Eval po into existence with default values |
1329 | + |
1330 | + if [ $# -ge 2 ] && [ "$1" = "--config" ]; then |
1331 | + shift # --config |
1332 | + local user_config_files="$1" |
1333 | + shift # that ^ |
1334 | + local IFS="," |
1335 | + for user_config_file in $user_config_files; do |
1336 | + _parse_config_files "$user_config_file" |
1337 | + done |
1338 | + else |
1339 | + _parse_config_files "/etc/percona-toolkit/percona-toolkit.conf" "/etc/percona-toolkit/$TOOL.conf" "$HOME/.percona-toolkit.conf" "$HOME/.$TOOL.conf" |
1340 | + fi |
1341 | + |
1342 | + _parse_command_line "${@:-""}" |
1343 | +} |
1344 | + |
1345 | +_parse_pod() { |
1346 | + local file="$1" |
1347 | + |
1348 | + cat "$file" | PO_DIR="$PO_DIR" perl -ne ' |
1349 | + BEGIN { $/ = ""; } |
1350 | + next unless $_ =~ m/^=head1 OPTIONS/; |
1351 | + while ( defined(my $para = <>) ) { |
1352 | + last if $para =~ m/^=head1/; |
1353 | + chomp; |
1354 | + if ( $para =~ m/^=item --(\S+)/ ) { |
1355 | + my $opt = $1; |
1356 | + my $file = "$ENV{PO_DIR}/$opt"; |
1357 | + open my $opt_fh, ">", $file or die "Cannot open $file: $!"; |
1358 | + print $opt_fh "long:$opt\n"; |
1359 | + $para = <>; |
1360 | + chomp; |
1361 | + if ( $para =~ m/^[a-z ]+:/ ) { |
1362 | + map { |
1363 | + chomp; |
1364 | + my ($attrib, $val) = split(/: /, $_); |
1365 | + print $opt_fh "$attrib:$val\n"; |
1366 | + } split(/; /, $para); |
1367 | + $para = <>; |
1368 | + chomp; |
1369 | + } |
1370 | + my ($desc) = $para =~ m/^([^?.]+)/; |
1371 | + print $opt_fh "desc:$desc.\n"; |
1372 | + close $opt_fh; |
1373 | + } |
1374 | + } |
1375 | + last; |
1376 | + ' |
1377 | +} |
1378 | + |
1379 | +_eval_po() { |
1380 | + local IFS=":" |
1381 | + for opt_spec in "$PO_DIR"/*; do |
1382 | + local opt="" |
1383 | + local default_val="" |
1384 | + local neg=0 |
1385 | + local size=0 |
1386 | + while read key val; do |
1387 | + case "$key" in |
1388 | + long) |
1389 | + opt=$(echo $val | sed 's/-/_/g' | tr '[:lower:]' '[:upper:]') |
1390 | + ;; |
1391 | + default) |
1392 | + default_val="$val" |
1393 | + ;; |
1394 | + "short form") |
1395 | + ;; |
1396 | + type) |
1397 | + [ "$val" = "size" ] && size=1 |
1398 | + ;; |
1399 | + desc) |
1400 | + ;; |
1401 | + negatable) |
1402 | + if [ "$val" = "yes" ]; then |
1403 | + neg=1 |
1404 | + fi |
1405 | + ;; |
1406 | + *) |
1407 | + echo "Invalid attribute in $opt_spec: $line" >&2 |
1408 | + exit 1 |
1409 | + esac |
1410 | + done < "$opt_spec" |
1411 | + |
1412 | + if [ -z "$opt" ]; then |
1413 | + echo "No long attribute in option spec $opt_spec" >&2 |
1414 | + exit 1 |
1415 | + fi |
1416 | + |
1417 | + if [ $neg -eq 1 ]; then |
1418 | + if [ -z "$default_val" ] || [ "$default_val" != "yes" ]; then |
1419 | + echo "Option $opt_spec is negatable but not default: yes" >&2 |
1420 | + exit 1 |
1421 | + fi |
1422 | + fi |
1423 | + |
1424 | + if [ $size -eq 1 -a -n "$default_val" ]; then |
1425 | + default_val=$(size_to_bytes $default_val) |
1426 | + fi |
1427 | + |
1428 | + eval "OPT_${opt}"="$default_val" |
1429 | + done |
1430 | +} |
1431 | + |
1432 | +_parse_config_files() { |
1433 | + |
1434 | + for config_file in "${@:-""}"; do |
1435 | + test -f "$config_file" || continue |
1436 | + |
1437 | + while read config_opt; do |
1438 | + |
1439 | + echo "$config_opt" | grep '^[ ]*[^#]' >/dev/null 2>&1 || continue |
1440 | + |
1441 | + config_opt="$(echo "$config_opt" | sed -e 's/^ *//g' -e 's/ *$//g' -e 's/[ ]*=[ ]*/=/' -e 's/[ ]*#.*$//')" |
1442 | + |
1443 | + [ "$config_opt" = "" ] && continue |
1444 | + |
1445 | + if ! [ "$HAVE_EXT_ARGV" ]; then |
1446 | + config_opt="--$config_opt" |
1447 | + fi |
1448 | + |
1449 | + _parse_command_line "$config_opt" |
1450 | + |
1451 | + done < "$config_file" |
1452 | + |
1453 | + HAVE_EXT_ARGV="" # reset for each file |
1454 | + |
1455 | + done |
1456 | +} |
1457 | + |
1458 | +_parse_command_line() { |
1459 | + local opt="" |
1460 | + local val="" |
1461 | + local next_opt_is_val="" |
1462 | + local opt_is_ok="" |
1463 | + local opt_is_negated="" |
1464 | + local real_opt="" |
1465 | + local required_arg="" |
1466 | + local spec="" |
1467 | + |
1468 | + for opt in "${@:-""}"; do |
1469 | + if [ "$opt" = "--" -o "$opt" = "----" ]; then |
1470 | + HAVE_EXT_ARGV=1 |
1471 | + continue |
1472 | + fi |
1473 | + if [ "$HAVE_EXT_ARGV" ]; then |
1474 | + if [ "$EXT_ARGV" ]; then |
1475 | + EXT_ARGV="$EXT_ARGV $opt" |
1476 | + else |
1477 | + EXT_ARGV="$opt" |
1478 | + fi |
1479 | + continue |
1480 | + fi |
1481 | + |
1482 | + if [ "$next_opt_is_val" ]; then |
1483 | + next_opt_is_val="" |
1484 | + if [ $# -eq 0 ] || [ $(expr "$opt" : "\-") -eq 1 ]; then |
1485 | + option_error "$real_opt requires a $required_arg argument" |
1486 | + continue |
1487 | + fi |
1488 | + val="$opt" |
1489 | + opt_is_ok=1 |
1490 | + else |
1491 | + if [ $(expr "$opt" : "\-") -eq 0 ]; then |
1492 | + if [ -z "$ARGV" ]; then |
1493 | + ARGV="$opt" |
1494 | + else |
1495 | + ARGV="$ARGV $opt" |
1496 | + fi |
1497 | + continue |
1498 | + fi |
1499 | + |
1500 | + real_opt="$opt" |
1501 | + |
1502 | + if $(echo $opt | grep '^--no[^-]' >/dev/null); then |
1503 | + local base_opt=$(echo $opt | sed 's/^--no//') |
1504 | + if [ -f "$PT_TMPDIR/po/$base_opt" ]; then |
1505 | + opt_is_negated=1 |
1506 | + opt="$base_opt" |
1507 | + else |
1508 | + opt_is_negated="" |
1509 | + opt=$(echo $opt | sed 's/^-*//') |
1510 | + fi |
1511 | + else |
1512 | + if $(echo $opt | grep '^--no-' >/dev/null); then |
1513 | + opt_is_negated=1 |
1514 | + opt=$(echo $opt | sed 's/^--no-//') |
1515 | + else |
1516 | + opt_is_negated="" |
1517 | + opt=$(echo $opt | sed 's/^-*//') |
1518 | + fi |
1519 | + fi |
1520 | + |
1521 | + if $(echo $opt | grep '^[a-z-][a-z-]*=' >/dev/null 2>&1); then |
1522 | + val="$(echo $opt | awk -F= '{print $2}')" |
1523 | + opt="$(echo $opt | awk -F= '{print $1}')" |
1524 | + fi |
1525 | + |
1526 | + if [ -f "$PT_TMPDIR/po/$opt" ]; then |
1527 | + spec="$PT_TMPDIR/po/$opt" |
1528 | + else |
1529 | + spec=$(grep "^short form:-$opt\$" "$PT_TMPDIR"/po/* | cut -d ':' -f 1) |
1530 | + if [ -z "$spec" ]; then |
1531 | + option_error "Unknown option: $real_opt" |
1532 | + continue |
1533 | + fi |
1534 | + fi |
1535 | + |
1536 | + required_arg=$(cat "$spec" | awk -F: '/^type:/{print $2}') |
1537 | + if [ "$required_arg" ]; then |
1538 | + if [ "$val" ]; then |
1539 | + opt_is_ok=1 |
1540 | + else |
1541 | + next_opt_is_val=1 |
1542 | + fi |
1543 | + else |
1544 | + if [ "$val" ]; then |
1545 | + option_error "Option $real_opt does not take a value" |
1546 | + continue |
1547 | + fi |
1548 | + if [ "$opt_is_negated" ]; then |
1549 | + val="" |
1550 | + else |
1551 | + val="yes" |
1552 | + fi |
1553 | + opt_is_ok=1 |
1554 | + fi |
1555 | + fi |
1556 | + |
1557 | + if [ "$opt_is_ok" ]; then |
1558 | + opt=$(cat "$spec" | grep '^long:' | cut -d':' -f2 | sed 's/-/_/g' | tr '[:lower:]' '[:upper:]') |
1559 | + |
1560 | + if grep "^type:size" "$spec" >/dev/null; then |
1561 | + val=$(size_to_bytes $val) |
1562 | + fi |
1563 | + |
1564 | + eval "OPT_$opt"="'$val'" |
1565 | + |
1566 | + opt="" |
1567 | + val="" |
1568 | + next_opt_is_val="" |
1569 | + opt_is_ok="" |
1570 | + opt_is_negated="" |
1571 | + real_opt="" |
1572 | + required_arg="" |
1573 | + spec="" |
1574 | + fi |
1575 | + done |
1576 | +} |
1577 | + |
1578 | +size_to_bytes() { |
1579 | + local size="$1" |
1580 | + echo $size | perl -ne '%f=(B=>1, K=>1_024, M=>1_048_576, G=>1_073_741_824, T=>1_099_511_627_776); m/^(\d+)([kMGT])?/i; print $1 * $f{uc($2 || "B")};' |
1581 | +} |
1582 | + |
1583 | +# ########################################################################### |
1584 | +# End parse_options package |
1585 | +# ########################################################################### |
1586 | + |
1587 | +# ########################################################################### |
1588 | +# alt_cmds package |
1589 | +# This package is a copy without comments from the original. The original |
1590 | +# with comments and its test file can be found in the Bazaar repository at, |
1591 | +# lib/bash/alt_cmds.sh |
1592 | +# t/lib/bash/alt_cmds.sh |
1593 | +# See https://launchpad.net/percona-toolkit for more information. |
1594 | +# ########################################################################### |
1595 | + |
1596 | + |
1597 | +set -u |
1598 | + |
1599 | +_seq() { |
1600 | + local i="$1" |
1601 | + awk "BEGIN { for(i=1; i<=$i; i++) print i; }" |
1602 | +} |
1603 | + |
1604 | +_pidof() { |
1605 | + local cmd="$1" |
1606 | + if ! pidof "$cmd" 2>/dev/null; then |
1607 | + ps -eo pid,ucomm | awk -v comm="$cmd" '$2 == comm { print $1 }' |
1608 | + fi |
1609 | +} |
1610 | + |
1611 | +_lsof() { |
1612 | + local pid="$1" |
1613 | + if ! lsof -p $pid 2>/dev/null; then |
1614 | + /bin/ls -l /proc/$pid/fd 2>/dev/null |
1615 | + fi |
1616 | +} |
1617 | + |
1618 | + |
1619 | + |
1620 | +_which() { |
1621 | + if [ -x /usr/bin/which ]; then |
1622 | + /usr/bin/which "$1" 2>/dev/null | awk '{print $1}' |
1623 | + elif which which 1>/dev/null 2>&1; then |
1624 | + which "$1" 2>/dev/null | awk '{print $1}' |
1625 | + else |
1626 | + echo "$1" |
1627 | + fi |
1628 | +} |
1629 | + |
1630 | +# ########################################################################### |
1631 | +# End alt_cmds package |
1632 | +# ########################################################################### |
1633 | + |
1634 | +TOOL="pt-mext" |
1635 | + |
1636 | +# Parse command line options. |
1637 | mk_tmpdir |
1638 | +parse_options "$0" "${@:-""}" |
1639 | + |
1640 | +if [ -z "$OPT_HELP" -a -z "$OPT_VERSION" ]; then |
1641 | + if [ -z "$EXT_ARGV" ]; then |
1642 | + option_error "No COMMAND was given." |
1643 | + fi |
1644 | +fi |
1645 | + |
1646 | +usage_or_errors "$0" |
1647 | +po_status=$? |
1648 | + |
1649 | +if [ $po_status -ne 0 ]; then |
1650 | + [ $OPT_ERRS -gt 0 ] && exit 1 |
1651 | + exit 0 |
1652 | +fi |
1653 | |
1654 | FILE="$PT_TMPDIR/mext_temp_file"; |
1655 | -NUM=0; |
1656 | -REL=0; |
1657 | - |
1658 | -# Command-line parsing. |
1659 | -args=`getopt -u -n mext r "$@"`; |
1660 | -if [ "$?" = "1" ]; then |
1661 | - usage; |
1662 | -fi |
1663 | -set -- $args |
1664 | -for o; do |
1665 | - case "$o" in |
1666 | - -r) REL="1"; shift;; |
1667 | - --) shift; break;; |
1668 | - esac |
1669 | -done |
1670 | - |
1671 | -if [ -z "$1" ]; then |
1672 | - usage; |
1673 | -fi |
1674 | +NUM=1; |
1675 | |
1676 | # Split the output on empty lines and put each into a different file; eliminate |
1677 | # lines that don't have "real" content. |
1678 | -$@ | grep -v '+' | grep -v Variable_name | sed 's/|//g' \ |
1679 | +$EXT_ARGV | grep -v '+' | grep -v Variable_name | sed 's/|//g' \ |
1680 | | while read line; do |
1681 | if [ "$line" = "" ]; then |
1682 | - NUM=`expr $NUM + 1`; |
1683 | + NUM=$(($NUM + 1)) |
1684 | echo "" > "$FILE$NUM" |
1685 | fi |
1686 | echo "$line" >> "$FILE$NUM" |
1687 | done |
1688 | |
1689 | -# Count how many files there are and prepare to format the output |
1690 | SPEC="%-33s %13d" |
1691 | AWKS="" |
1692 | + |
1693 | +# Count how many files there are and prepare to format the output, but... |
1694 | NUM=`ls "$FILE"* | wc -l`; |
1695 | -# The last file will be empty... |
1696 | -NUM=`expr $NUM - 3`; |
1697 | + |
1698 | +# ... iterate through files 1..(N-2) because the last file is empty and |
1699 | +# we join N to N+1 so also don't read the last real file. |
1700 | +NUM=$((NUM - 2)) |
1701 | |
1702 | # Join each file with the next file, joining on the first field. Build a printf |
1703 | # spec and awk spec at the same time. |
1704 | -for i in `seq 0 $NUM`; do |
1705 | - NEXTFILE=`expr $i + 1`; |
1706 | +for i in `_seq $NUM`; do |
1707 | + NEXTFILE=$(($i + 1)) |
1708 | |
1709 | # Sort each file and eliminate empty lines, so 'join' doesn't complain. |
1710 | sort "$FILE$i" | grep . > "$FILE$i.tmp" |
1711 | @@ -119,10 +600,29 @@ |
1712 | MAXLEN=`awk '{print $2}' "$FILE${NEXTFILE}" | grep -v '[^0-9]' | awk '{print length($1)}' | sort -rn | head -n1` |
1713 | mv "$FILE" "$FILE${NEXTFILE}" |
1714 | SPEC="$SPEC %${MAXLEN}d"; |
1715 | - if [ "$REL" = "1" ]; then |
1716 | - AWKS="$AWKS, \$`expr $i + 3` - \$`expr $i + 2`"; |
1717 | + |
1718 | + # The final file will contain lines like: |
1719 | + # |
1720 | + # Bytes_received 100 200 50 300 |
1721 | + # |
1722 | + # For each such line in awk, $1 is the var name and $2 is the first value |
1723 | + # of the var, so these are fixed when we build AWKCMD after this loop. |
1724 | + # When i=1, we're comparing file1 to file2, and the resulting value becomes |
1725 | + # awk $3. Hence $i + 2=$3 below. Then incr and repeat for subsequent files. |
1726 | + # |
1727 | + # With --relative, the $i and awk field numbers are the same, but we print |
1728 | + # differences $3-$2, $4-$3, $5-$4 from the input line for awk fields $3, $4, |
1729 | + # and $5 respectively. Here's a table: |
1730 | + # |
1731 | + # i awk Input line fields |
1732 | + # == === ================= |
1733 | + # 1 $3 $3-$2 |
1734 | + # 2 $4 $4-$3 |
1735 | + # 3 $5 $5-$4 |
1736 | + if [ "$OPT_RELATIVE" ]; then |
1737 | + AWKS="$AWKS, \$`expr $i + 2` - \$`expr $i + 1`"; |
1738 | else |
1739 | - AWKS="$AWKS, \$`expr $i + 3`"; |
1740 | + AWKS="$AWKS, \$$(($i + 2))"; |
1741 | fi |
1742 | done |
1743 | |
1744 | @@ -153,7 +653,7 @@ |
1745 | |
1746 | Get output from C<mysqladmin>: |
1747 | |
1748 | - pt-mext -r -- mysqladmin ext -i10 -c3" |
1749 | + pt-mext -r -- mysqladmin ext -i10 -c3 |
1750 | |
1751 | Get output from a file: |
1752 | |
1753 | @@ -191,9 +691,19 @@ |
1754 | |
1755 | =over |
1756 | |
1757 | -=item -r |
1758 | - |
1759 | -Relative: subtract each column from the previous column. |
1760 | +=item --help |
1761 | + |
1762 | +Show help and exit. |
1763 | + |
1764 | +=item --relative |
1765 | + |
1766 | +short form: -r |
1767 | + |
1768 | +Subtract each column from the previous column. |
1769 | + |
1770 | +=item --version |
1771 | + |
1772 | +Show version and exit. |
1773 | |
1774 | =back |
1775 | |
1776 | |
1777 | === modified file 'bin/pt-pmp' |
1778 | --- bin/pt-pmp 2013-03-14 17:20:35 +0000 |
1779 | +++ bin/pt-pmp 2013-04-04 21:05:27 +0000 |
1780 | @@ -7,6 +7,54 @@ |
1781 | TOOL="pt-pmp" |
1782 | |
1783 | # ########################################################################### |
1784 | +# log_warn_die package |
1785 | +# This package is a copy without comments from the original. The original |
1786 | +# with comments and its test file can be found in the Bazaar repository at, |
1787 | +# lib/bash/log_warn_die.sh |
1788 | +# t/lib/bash/log_warn_die.sh |
1789 | +# See https://launchpad.net/percona-toolkit for more information. |
1790 | +# ########################################################################### |
1791 | + |
1792 | + |
1793 | +set -u |
1794 | + |
1795 | +PTFUNCNAME="" |
1796 | +PTDEBUG="${PTDEBUG:-""}" |
1797 | +EXIT_STATUS=0 |
1798 | + |
1799 | +ts() { |
1800 | + TS=$(date +%F-%T | tr ':-' '_') |
1801 | + echo "$TS $*" |
1802 | +} |
1803 | + |
1804 | +info() { |
1805 | + [ ${OPT_VERBOSE:-3} -ge 3 ] && ts "$*" |
1806 | +} |
1807 | + |
1808 | +log() { |
1809 | + [ ${OPT_VERBOSE:-3} -ge 2 ] && ts "$*" |
1810 | +} |
1811 | + |
1812 | +warn() { |
1813 | + [ ${OPT_VERBOSE:-3} -ge 1 ] && ts "$*" >&2 |
1814 | + EXIT_STATUS=1 |
1815 | +} |
1816 | + |
1817 | +die() { |
1818 | + ts "$*" >&2 |
1819 | + EXIT_STATUS=1 |
1820 | + exit 1 |
1821 | +} |
1822 | + |
1823 | +_d () { |
1824 | + [ "$PTDEBUG" ] && echo "# $PTFUNCNAME: $(ts "$*")" >&2 |
1825 | +} |
1826 | + |
1827 | +# ########################################################################### |
1828 | +# End log_warn_die package |
1829 | +# ########################################################################### |
1830 | + |
1831 | +# ########################################################################### |
1832 | # tmpdir package |
1833 | # This package is a copy without comments from the original. The original |
1834 | # with comments and its test file can be found in the Bazaar repository at, |
1835 | @@ -47,17 +95,451 @@ |
1836 | # End tmpdir package |
1837 | # ########################################################################### |
1838 | |
1839 | +# ########################################################################### |
1840 | +# parse_options package |
1841 | +# This package is a copy without comments from the original. The original |
1842 | +# with comments and its test file can be found in the Bazaar repository at, |
1843 | +# lib/bash/parse_options.sh |
1844 | +# t/lib/bash/parse_options.sh |
1845 | +# See https://launchpad.net/percona-toolkit for more information. |
1846 | +# ########################################################################### |
1847 | + |
1848 | + |
1849 | + |
1850 | + |
1851 | + |
1852 | +set -u |
1853 | + |
1854 | +ARGV="" # Non-option args (probably input files) |
1855 | +EXT_ARGV="" # Everything after -- (args for an external command) |
1856 | +HAVE_EXT_ARGV="" # Got --, everything else is put into EXT_ARGV |
1857 | +OPT_ERRS=0 # How many command line option errors |
1858 | +OPT_VERSION="" # If --version was specified |
1859 | +OPT_HELP="" # If --help was specified |
1860 | +PO_DIR="" # Directory with program option spec files |
1861 | + |
1862 | +usage() { |
1863 | + local file="$1" |
1864 | + |
1865 | + local usage="$(grep '^Usage: ' "$file")" |
1866 | + echo $usage |
1867 | + echo |
1868 | + echo "For more information, 'man $TOOL' or 'perldoc $file'." |
1869 | +} |
1870 | + |
1871 | +usage_or_errors() { |
1872 | + local file="$1" |
1873 | + |
1874 | + if [ "$OPT_VERSION" ]; then |
1875 | + local version=$(grep '^pt-[^ ]\+ [0-9]' "$file") |
1876 | + echo "$version" |
1877 | + return 1 |
1878 | + fi |
1879 | + |
1880 | + if [ "$OPT_HELP" ]; then |
1881 | + usage "$file" |
1882 | + echo |
1883 | + echo "Command line options:" |
1884 | + echo |
1885 | + perl -e ' |
1886 | + use strict; |
1887 | + use warnings FATAL => qw(all); |
1888 | + my $lcol = 20; # Allow this much space for option names. |
1889 | + my $rcol = 80 - $lcol; # The terminal is assumed to be 80 chars wide. |
1890 | + my $name; |
1891 | + while ( <> ) { |
1892 | + my $line = $_; |
1893 | + chomp $line; |
1894 | + if ( $line =~ s/^long:/ --/ ) { |
1895 | + $name = $line; |
1896 | + } |
1897 | + elsif ( $line =~ s/^desc:// ) { |
1898 | + $line =~ s/ +$//mg; |
1899 | + my @lines = grep { $_ } |
1900 | + $line =~ m/(.{0,$rcol})(?:\s+|\Z)/g; |
1901 | + if ( length($name) >= $lcol ) { |
1902 | + print $name, "\n", (q{ } x $lcol); |
1903 | + } |
1904 | + else { |
1905 | + printf "%-${lcol}s", $name; |
1906 | + } |
1907 | + print join("\n" . (q{ } x $lcol), @lines); |
1908 | + print "\n"; |
1909 | + } |
1910 | + } |
1911 | + ' "$PO_DIR"/* |
1912 | + echo |
1913 | + echo "Options and values after processing arguments:" |
1914 | + echo |
1915 | + ( |
1916 | + cd "$PO_DIR" |
1917 | + for opt in *; do |
1918 | + local varname="OPT_$(echo "$opt" | tr a-z- A-Z_)" |
1919 | + eval local varvalue=\$$varname |
1920 | + if ! grep -q "type:" "$PO_DIR/$opt" >/dev/null; then |
1921 | + if [ "$varvalue" -a "$varvalue" = "yes" ]; |
1922 | + then varvalue="TRUE" |
1923 | + else |
1924 | + varvalue="FALSE" |
1925 | + fi |
1926 | + fi |
1927 | + printf -- " --%-30s %s" "$opt" "${varvalue:-(No value)}" |
1928 | + echo |
1929 | + done |
1930 | + ) |
1931 | + return 1 |
1932 | + fi |
1933 | + |
1934 | + if [ $OPT_ERRS -gt 0 ]; then |
1935 | + echo |
1936 | + usage "$file" |
1937 | + return 1 |
1938 | + fi |
1939 | + |
1940 | + return 0 |
1941 | +} |
1942 | + |
1943 | +option_error() { |
1944 | + local err="$1" |
1945 | + OPT_ERRS=$(($OPT_ERRS + 1)) |
1946 | + echo "$err" >&2 |
1947 | +} |
1948 | + |
1949 | +parse_options() { |
1950 | + local file="$1" |
1951 | + shift |
1952 | + |
1953 | + ARGV="" |
1954 | + EXT_ARGV="" |
1955 | + HAVE_EXT_ARGV="" |
1956 | + OPT_ERRS=0 |
1957 | + OPT_VERSION="" |
1958 | + OPT_HELP="" |
1959 | + PO_DIR="$PT_TMPDIR/po" |
1960 | + |
1961 | + if [ ! -d "$PO_DIR" ]; then |
1962 | + mkdir "$PO_DIR" |
1963 | + if [ $? -ne 0 ]; then |
1964 | + echo "Cannot mkdir $PO_DIR" >&2 |
1965 | + exit 1 |
1966 | + fi |
1967 | + fi |
1968 | + |
1969 | + rm -rf "$PO_DIR"/* |
1970 | + if [ $? -ne 0 ]; then |
1971 | + echo "Cannot rm -rf $PO_DIR/*" >&2 |
1972 | + exit 1 |
1973 | + fi |
1974 | + |
1975 | + _parse_pod "$file" # Parse POD into program option (po) spec files |
1976 | + _eval_po # Eval po into existence with default values |
1977 | + |
1978 | + if [ $# -ge 2 ] && [ "$1" = "--config" ]; then |
1979 | + shift # --config |
1980 | + local user_config_files="$1" |
1981 | + shift # that ^ |
1982 | + local IFS="," |
1983 | + for user_config_file in $user_config_files; do |
1984 | + _parse_config_files "$user_config_file" |
1985 | + done |
1986 | + else |
1987 | + _parse_config_files "/etc/percona-toolkit/percona-toolkit.conf" "/etc/percona-toolkit/$TOOL.conf" "$HOME/.percona-toolkit.conf" "$HOME/.$TOOL.conf" |
1988 | + fi |
1989 | + |
1990 | + _parse_command_line "${@:-""}" |
1991 | +} |
1992 | + |
1993 | +_parse_pod() { |
1994 | + local file="$1" |
1995 | + |
1996 | + cat "$file" | PO_DIR="$PO_DIR" perl -ne ' |
1997 | + BEGIN { $/ = ""; } |
1998 | + next unless $_ =~ m/^=head1 OPTIONS/; |
1999 | + while ( defined(my $para = <>) ) { |
2000 | + last if $para =~ m/^=head1/; |
2001 | + chomp; |
2002 | + if ( $para =~ m/^=item --(\S+)/ ) { |
2003 | + my $opt = $1; |
2004 | + my $file = "$ENV{PO_DIR}/$opt"; |
2005 | + open my $opt_fh, ">", $file or die "Cannot open $file: $!"; |
2006 | + print $opt_fh "long:$opt\n"; |
2007 | + $para = <>; |
2008 | + chomp; |
2009 | + if ( $para =~ m/^[a-z ]+:/ ) { |
2010 | + map { |
2011 | + chomp; |
2012 | + my ($attrib, $val) = split(/: /, $_); |
2013 | + print $opt_fh "$attrib:$val\n"; |
2014 | + } split(/; /, $para); |
2015 | + $para = <>; |
2016 | + chomp; |
2017 | + } |
2018 | + my ($desc) = $para =~ m/^([^?.]+)/; |
2019 | + print $opt_fh "desc:$desc.\n"; |
2020 | + close $opt_fh; |
2021 | + } |
2022 | + } |
2023 | + last; |
2024 | + ' |
2025 | +} |
2026 | + |
2027 | +_eval_po() { |
2028 | + local IFS=":" |
2029 | + for opt_spec in "$PO_DIR"/*; do |
2030 | + local opt="" |
2031 | + local default_val="" |
2032 | + local neg=0 |
2033 | + local size=0 |
2034 | + while read key val; do |
2035 | + case "$key" in |
2036 | + long) |
2037 | + opt=$(echo $val | sed 's/-/_/g' | tr '[:lower:]' '[:upper:]') |
2038 | + ;; |
2039 | + default) |
2040 | + default_val="$val" |
2041 | + ;; |
2042 | + "short form") |
2043 | + ;; |
2044 | + type) |
2045 | + [ "$val" = "size" ] && size=1 |
2046 | + ;; |
2047 | + desc) |
2048 | + ;; |
2049 | + negatable) |
2050 | + if [ "$val" = "yes" ]; then |
2051 | + neg=1 |
2052 | + fi |
2053 | + ;; |
2054 | + *) |
2055 | + echo "Invalid attribute in $opt_spec: $line" >&2 |
2056 | + exit 1 |
2057 | + esac |
2058 | + done < "$opt_spec" |
2059 | + |
2060 | + if [ -z "$opt" ]; then |
2061 | + echo "No long attribute in option spec $opt_spec" >&2 |
2062 | + exit 1 |
2063 | + fi |
2064 | + |
2065 | + if [ $neg -eq 1 ]; then |
2066 | + if [ -z "$default_val" ] || [ "$default_val" != "yes" ]; then |
2067 | + echo "Option $opt_spec is negatable but not default: yes" >&2 |
2068 | + exit 1 |
2069 | + fi |
2070 | + fi |
2071 | + |
2072 | + if [ $size -eq 1 -a -n "$default_val" ]; then |
2073 | + default_val=$(size_to_bytes $default_val) |
2074 | + fi |
2075 | + |
2076 | + eval "OPT_${opt}"="$default_val" |
2077 | + done |
2078 | +} |
2079 | + |
2080 | +_parse_config_files() { |
2081 | + |
2082 | + for config_file in "${@:-""}"; do |
2083 | + test -f "$config_file" || continue |
2084 | + |
2085 | + while read config_opt; do |
2086 | + |
2087 | + echo "$config_opt" | grep '^[ ]*[^#]' >/dev/null 2>&1 || continue |
2088 | + |
2089 | + config_opt="$(echo "$config_opt" | sed -e 's/^ *//g' -e 's/ *$//g' -e 's/[ ]*=[ ]*/=/' -e 's/[ ]*#.*$//')" |
2090 | + |
2091 | + [ "$config_opt" = "" ] && continue |
2092 | + |
2093 | + if ! [ "$HAVE_EXT_ARGV" ]; then |
2094 | + config_opt="--$config_opt" |
2095 | + fi |
2096 | + |
2097 | + _parse_command_line "$config_opt" |
2098 | + |
2099 | + done < "$config_file" |
2100 | + |
2101 | + HAVE_EXT_ARGV="" # reset for each file |
2102 | + |
2103 | + done |
2104 | +} |
2105 | + |
2106 | +_parse_command_line() { |
2107 | + local opt="" |
2108 | + local val="" |
2109 | + local next_opt_is_val="" |
2110 | + local opt_is_ok="" |
2111 | + local opt_is_negated="" |
2112 | + local real_opt="" |
2113 | + local required_arg="" |
2114 | + local spec="" |
2115 | + |
2116 | + for opt in "${@:-""}"; do |
2117 | + if [ "$opt" = "--" -o "$opt" = "----" ]; then |
2118 | + HAVE_EXT_ARGV=1 |
2119 | + continue |
2120 | + fi |
2121 | + if [ "$HAVE_EXT_ARGV" ]; then |
2122 | + if [ "$EXT_ARGV" ]; then |
2123 | + EXT_ARGV="$EXT_ARGV $opt" |
2124 | + else |
2125 | + EXT_ARGV="$opt" |
2126 | + fi |
2127 | + continue |
2128 | + fi |
2129 | + |
2130 | + if [ "$next_opt_is_val" ]; then |
2131 | + next_opt_is_val="" |
2132 | + if [ $# -eq 0 ] || [ $(expr "$opt" : "\-") -eq 1 ]; then |
2133 | + option_error "$real_opt requires a $required_arg argument" |
2134 | + continue |
2135 | + fi |
2136 | + val="$opt" |
2137 | + opt_is_ok=1 |
2138 | + else |
2139 | + if [ $(expr "$opt" : "\-") -eq 0 ]; then |
2140 | + if [ -z "$ARGV" ]; then |
2141 | + ARGV="$opt" |
2142 | + else |
2143 | + ARGV="$ARGV $opt" |
2144 | + fi |
2145 | + continue |
2146 | + fi |
2147 | + |
2148 | + real_opt="$opt" |
2149 | + |
2150 | + if $(echo $opt | grep '^--no[^-]' >/dev/null); then |
2151 | + local base_opt=$(echo $opt | sed 's/^--no//') |
2152 | + if [ -f "$PT_TMPDIR/po/$base_opt" ]; then |
2153 | + opt_is_negated=1 |
2154 | + opt="$base_opt" |
2155 | + else |
2156 | + opt_is_negated="" |
2157 | + opt=$(echo $opt | sed 's/^-*//') |
2158 | + fi |
2159 | + else |
2160 | + if $(echo $opt | grep '^--no-' >/dev/null); then |
2161 | + opt_is_negated=1 |
2162 | + opt=$(echo $opt | sed 's/^--no-//') |
2163 | + else |
2164 | + opt_is_negated="" |
2165 | + opt=$(echo $opt | sed 's/^-*//') |
2166 | + fi |
2167 | + fi |
2168 | + |
2169 | + if $(echo $opt | grep '^[a-z-][a-z-]*=' >/dev/null 2>&1); then |
2170 | + val="$(echo $opt | awk -F= '{print $2}')" |
2171 | + opt="$(echo $opt | awk -F= '{print $1}')" |
2172 | + fi |
2173 | + |
2174 | + if [ -f "$PT_TMPDIR/po/$opt" ]; then |
2175 | + spec="$PT_TMPDIR/po/$opt" |
2176 | + else |
2177 | + spec=$(grep "^short form:-$opt\$" "$PT_TMPDIR"/po/* | cut -d ':' -f 1) |
2178 | + if [ -z "$spec" ]; then |
2179 | + option_error "Unknown option: $real_opt" |
2180 | + continue |
2181 | + fi |
2182 | + fi |
2183 | + |
2184 | + required_arg=$(cat "$spec" | awk -F: '/^type:/{print $2}') |
2185 | + if [ "$required_arg" ]; then |
2186 | + if [ "$val" ]; then |
2187 | + opt_is_ok=1 |
2188 | + else |
2189 | + next_opt_is_val=1 |
2190 | + fi |
2191 | + else |
2192 | + if [ "$val" ]; then |
2193 | + option_error "Option $real_opt does not take a value" |
2194 | + continue |
2195 | + fi |
2196 | + if [ "$opt_is_negated" ]; then |
2197 | + val="" |
2198 | + else |
2199 | + val="yes" |
2200 | + fi |
2201 | + opt_is_ok=1 |
2202 | + fi |
2203 | + fi |
2204 | + |
2205 | + if [ "$opt_is_ok" ]; then |
2206 | + opt=$(cat "$spec" | grep '^long:' | cut -d':' -f2 | sed 's/-/_/g' | tr '[:lower:]' '[:upper:]') |
2207 | + |
2208 | + if grep "^type:size" "$spec" >/dev/null; then |
2209 | + val=$(size_to_bytes $val) |
2210 | + fi |
2211 | + |
2212 | + eval "OPT_$opt"="'$val'" |
2213 | + |
2214 | + opt="" |
2215 | + val="" |
2216 | + next_opt_is_val="" |
2217 | + opt_is_ok="" |
2218 | + opt_is_negated="" |
2219 | + real_opt="" |
2220 | + required_arg="" |
2221 | + spec="" |
2222 | + fi |
2223 | + done |
2224 | +} |
2225 | + |
2226 | +size_to_bytes() { |
2227 | + local size="$1" |
2228 | + echo $size | perl -ne '%f=(B=>1, K=>1_024, M=>1_048_576, G=>1_073_741_824, T=>1_099_511_627_776); m/^(\d+)([kMGT])?/i; print $1 * $f{uc($2 || "B")};' |
2229 | +} |
2230 | + |
2231 | +# ########################################################################### |
2232 | +# End parse_options package |
2233 | +# ########################################################################### |
2234 | + |
2235 | +# ########################################################################### |
2236 | +# alt_cmds package |
2237 | +# This package is a copy without comments from the original. The original |
2238 | +# with comments and its test file can be found in the Bazaar repository at, |
2239 | +# lib/bash/alt_cmds.sh |
2240 | +# t/lib/bash/alt_cmds.sh |
2241 | +# See https://launchpad.net/percona-toolkit for more information. |
2242 | +# ########################################################################### |
2243 | + |
2244 | + |
2245 | +set -u |
2246 | + |
2247 | +_seq() { |
2248 | + local i="$1" |
2249 | + awk "BEGIN { for(i=1; i<=$i; i++) print i; }" |
2250 | +} |
2251 | + |
2252 | +_pidof() { |
2253 | + local cmd="$1" |
2254 | + if ! pidof "$cmd" 2>/dev/null; then |
2255 | + ps -eo pid,ucomm | awk -v comm="$cmd" '$2 == comm { print $1 }' |
2256 | + fi |
2257 | +} |
2258 | + |
2259 | +_lsof() { |
2260 | + local pid="$1" |
2261 | + if ! lsof -p $pid 2>/dev/null; then |
2262 | + /bin/ls -l /proc/$pid/fd 2>/dev/null |
2263 | + fi |
2264 | +} |
2265 | + |
2266 | + |
2267 | + |
2268 | +_which() { |
2269 | + if [ -x /usr/bin/which ]; then |
2270 | + /usr/bin/which "$1" 2>/dev/null | awk '{print $1}' |
2271 | + elif which which 1>/dev/null 2>&1; then |
2272 | + which "$1" 2>/dev/null | awk '{print $1}' |
2273 | + else |
2274 | + echo "$1" |
2275 | + fi |
2276 | +} |
2277 | + |
2278 | +# ########################################################################### |
2279 | +# End alt_cmds package |
2280 | +# ########################################################################### |
2281 | + |
2282 | set +u |
2283 | |
2284 | -usage() { |
2285 | - if [ "${OPT_ERR}" ]; then |
2286 | - echo "${OPT_ERR}" >&2 |
2287 | - fi |
2288 | - echo "Usage: pt-pmp [OPTIONS] [FILES]" >&2 |
2289 | - echo "For more information, 'man pt-pmp' or 'perldoc $0'" >&2 |
2290 | - exit 1 |
2291 | -} |
2292 | - |
2293 | # Actually does the aggregation. The arguments are the max number of functions |
2294 | # to aggregate, and the files to read. If maxlen=0, it means infinity. We have |
2295 | # to pass the maxlen argument into this function to make maxlen testable. |
2296 | @@ -153,70 +635,35 @@ |
2297 | |
2298 | # The main program to run. |
2299 | main() { |
2300 | - |
2301 | - # Get command-line options |
2302 | - for o; do |
2303 | - case "${o}" in |
2304 | - --) |
2305 | - shift; break; |
2306 | - ;; |
2307 | - --help) |
2308 | - usage; |
2309 | - ;; |
2310 | - -b) |
2311 | - shift; OPT_b="${1}"; shift; |
2312 | - ;; |
2313 | - -i) |
2314 | - shift; OPT_i="${1}"; shift; |
2315 | - ;; |
2316 | - -k) |
2317 | - shift; OPT_k="${1}"; shift; |
2318 | - ;; |
2319 | - -l) |
2320 | - shift; OPT_l="${1}"; shift; |
2321 | - ;; |
2322 | - -p) |
2323 | - shift; OPT_p="${1}"; shift; |
2324 | - ;; |
2325 | - -s) |
2326 | - shift; OPT_s="${1}"; shift; |
2327 | - ;; |
2328 | - -*) |
2329 | - OPT_ERR="Unknown option ${o}." |
2330 | - usage |
2331 | - ;; |
2332 | - esac |
2333 | - done |
2334 | - export OPT_i="${OPT_i:-1}"; |
2335 | - export OPT_k="${OPT_k:-}"; |
2336 | - export OPT_l="${OPT_l:-0}"; |
2337 | - export OPT_b="${OPT_b:-mysqld}"; |
2338 | - export OPT_p="${OPT_p:-}"; |
2339 | - export OPT_s="${OPT_s:-0}"; |
2340 | - |
2341 | - if [ -z "${1}" ]; then |
2342 | - # There's no file to analyze, so we'll make one. |
2343 | - if [ -z "${OPT_p}" ]; then |
2344 | - OPT_p=$(pidof -s "${OPT_b}" 2>/dev/null); |
2345 | - if [ -z "${OPT_p}" ]; then |
2346 | - OPT_p=$(pgrep -o -x "${OPT_b}" 2>/dev/null) |
2347 | + local output_file="${OPT_SAVE_SAMPLES:-"$PT_TMPDIR/percona-toolkit"}" |
2348 | + |
2349 | + if [ -z "$ARGV" ]; then |
2350 | + # There are no files to analyze, so we'll make one. |
2351 | + if [ -z "$OPT_PID" ]; then |
2352 | + OPT_PID=$(pidof -s "$OPT_BINARY" 2>/dev/null); |
2353 | + if [ -z "$OPT_PID" ]; then |
2354 | + OPT_PID=$(pgrep -o -x "$OPT_BINARY" 2>/dev/null) |
2355 | fi |
2356 | - if [ -z "${OPT_p}" ]; then |
2357 | - OPT_p=$(ps -eaf | grep "${OPT_b}" | grep -v grep | awk '{print $2}' | head -n1); |
2358 | + if [ -z "$OPT_PID" ]; then |
2359 | + OPT_PID=$(ps -eaf | grep "$OPT_BINARY" | grep -v grep | awk '{print $2}' | head -n1); |
2360 | fi |
2361 | fi |
2362 | - date; |
2363 | - for x in $(seq 1 $OPT_i); do |
2364 | - gdb -ex "set pagination 0" -ex "thread apply all bt" -batch -p $OPT_p >> "${OPT_k:-$PT_TMPDIR/percona-toolkit}" |
2365 | - date +'TS %N.%s %F %T' >> "${OPT_k:-$PT_TMPDIR/percona-toolkit}" |
2366 | - sleep $OPT_s |
2367 | + date |
2368 | + for x in $(_seq $OPT_ITERATIONS); do |
2369 | + gdb -ex "set pagination 0" \ |
2370 | + -ex "thread apply all bt" \ |
2371 | + -batch \ |
2372 | + -p $OPT_PID \ |
2373 | + >> "$output_file" |
2374 | + date +'TS %N.%s %F %T' >> "$output_file" |
2375 | + sleep $OPT_INTERVAL |
2376 | done |
2377 | fi |
2378 | |
2379 | - if [ $# -eq 0 ]; then |
2380 | - aggregate_stacktrace "${OPT_l}" "${OPT_k:-$PT_TMPDIR/percona-toolkit}" |
2381 | + if [ -z "$ARGV" ]; then |
2382 | + aggregate_stacktrace "$OPT_LINES" "$output_file" |
2383 | else |
2384 | - aggregate_stacktrace "${OPT_l}" "$@" |
2385 | + aggregate_stacktrace "$OPT_LINES" $ARGV |
2386 | fi |
2387 | } |
2388 | |
2389 | @@ -224,8 +671,23 @@ |
2390 | # possible to include without executing, and thus test. |
2391 | if [ "${0##*/}" = "$TOOL" ] \ |
2392 | || [ "${0##*/}" = "bash" -a "${_:-""}" = "$0" ]; then |
2393 | + |
2394 | mk_tmpdir |
2395 | - main "$@" |
2396 | + |
2397 | + parse_options "$0" "${@:-""}" |
2398 | + if [ -z "$OPT_HELP" -a -z "$OPT_VERSION" ]; then |
2399 | + # Validate options |
2400 | + : |
2401 | + fi |
2402 | + usage_or_errors "$0" |
2403 | + po_status=$? |
2404 | + if [ $po_status -ne 0 ]; then |
2405 | + [ $OPT_ERRS -gt 0 ] && exit 1 |
2406 | + exit 0 |
2407 | + fi |
2408 | + |
2409 | + main $ARGV |
2410 | + |
2411 | rm_tmpdir |
2412 | fi |
2413 | |
2414 | @@ -289,33 +751,51 @@ |
2415 | |
2416 | =head1 OPTIONS |
2417 | |
2418 | -Options must precede files on the command line. |
2419 | - |
2420 | =over |
2421 | |
2422 | -=item -b BINARY |
2423 | - |
2424 | -Which binary to trace (default mysqld) |
2425 | - |
2426 | -=item -i ITERATIONS |
2427 | - |
2428 | -How many traces to gather and aggregate (default 1) |
2429 | - |
2430 | -=item -k KEEPFILE |
2431 | - |
2432 | -Keep the raw traces in this file after aggregation |
2433 | - |
2434 | -=item -l NUMBER |
2435 | - |
2436 | -Aggregate only first NUMBER functions; 0=infinity (default 0) |
2437 | - |
2438 | -=item -p PID |
2439 | - |
2440 | -Process ID of the process to trace; overrides -b |
2441 | - |
2442 | -=item -s SLEEPTIME |
2443 | - |
2444 | -Number of seconds to sleep between iterations (default 0) |
2445 | +=item --binary |
2446 | + |
2447 | +short form: -b; type: string; default: mysqld |
2448 | + |
2449 | +Which binary to trace. |
2450 | + |
2451 | +=item --help |
2452 | + |
2453 | +Show help and exit. |
2454 | + |
2455 | +=item --interval |
2456 | + |
2457 | +short form: -s; type: int; default: 0 |
2458 | + |
2459 | +Number of seconds to sleep between L<"--iterations">. |
2460 | + |
2461 | +=item --iterations |
2462 | + |
2463 | +short form: -i; type: int; default: 1 |
2464 | + |
2465 | +How many traces to gather and aggregate. |
2466 | + |
2467 | +=item --lines |
2468 | + |
2469 | +short form: -l; type: int; default: 0 |
2470 | + |
2471 | +Aggregate only first specified number of many functions; 0=infinity. |
2472 | + |
2473 | +=item --pid |
2474 | + |
2475 | +short form: -p; type: int |
2476 | + |
2477 | +Process ID of the process to trace; overrides L<"--binary">. |
2478 | + |
2479 | +=item --save-samples |
2480 | + |
2481 | +short form: -k; type: string |
2482 | + |
2483 | +Keep the raw traces in this file after aggregation. |
2484 | + |
2485 | +=item --version |
2486 | + |
2487 | +Show version and exit. |
2488 | |
2489 | =back |
2490 | |
2491 | |
2492 | === modified file 'bin/pt-sift' |
2493 | --- bin/pt-sift 2013-03-14 17:20:35 +0000 |
2494 | +++ bin/pt-sift 2013-04-04 21:05:27 +0000 |
2495 | @@ -4,15 +4,54 @@ |
2496 | # See "COPYRIGHT, LICENSE, AND WARRANTY" at the end of this file for legal |
2497 | # notices and disclaimers. |
2498 | |
2499 | -usage() { |
2500 | - if [ "${OPT_ERR}" ]; then |
2501 | - echo "Error: $OPT_ERR" >&2 |
2502 | - fi |
2503 | - echo "Usage: pt-sift FILE|PREFIX|DIRECTORY" >&2 |
2504 | - echo "For more information, 'man pt-sift' or 'perldoc $0'." >&2 |
2505 | +# ########################################################################### |
2506 | +# log_warn_die package |
2507 | +# This package is a copy without comments from the original. The original |
2508 | +# with comments and its test file can be found in the Bazaar repository at, |
2509 | +# lib/bash/log_warn_die.sh |
2510 | +# t/lib/bash/log_warn_die.sh |
2511 | +# See https://launchpad.net/percona-toolkit for more information. |
2512 | +# ########################################################################### |
2513 | + |
2514 | + |
2515 | +set -u |
2516 | + |
2517 | +PTFUNCNAME="" |
2518 | +PTDEBUG="${PTDEBUG:-""}" |
2519 | +EXIT_STATUS=0 |
2520 | + |
2521 | +ts() { |
2522 | + TS=$(date +%F-%T | tr ':-' '_') |
2523 | + echo "$TS $*" |
2524 | +} |
2525 | + |
2526 | +info() { |
2527 | + [ ${OPT_VERBOSE:-3} -ge 3 ] && ts "$*" |
2528 | +} |
2529 | + |
2530 | +log() { |
2531 | + [ ${OPT_VERBOSE:-3} -ge 2 ] && ts "$*" |
2532 | +} |
2533 | + |
2534 | +warn() { |
2535 | + [ ${OPT_VERBOSE:-3} -ge 1 ] && ts "$*" >&2 |
2536 | + EXIT_STATUS=1 |
2537 | +} |
2538 | + |
2539 | +die() { |
2540 | + ts "$*" >&2 |
2541 | + EXIT_STATUS=1 |
2542 | exit 1 |
2543 | } |
2544 | |
2545 | +_d () { |
2546 | + [ "$PTDEBUG" ] && echo "# $PTFUNCNAME: $(ts "$*")" >&2 |
2547 | +} |
2548 | + |
2549 | +# ########################################################################### |
2550 | +# End log_warn_die package |
2551 | +# ########################################################################### |
2552 | + |
2553 | # ########################################################################### |
2554 | # tmpdir package |
2555 | # This package is a copy without comments from the original. The original |
2556 | @@ -55,6 +94,402 @@ |
2557 | # ########################################################################### |
2558 | |
2559 | # ########################################################################### |
2560 | +# parse_options package |
2561 | +# This package is a copy without comments from the original. The original |
2562 | +# with comments and its test file can be found in the Bazaar repository at, |
2563 | +# lib/bash/parse_options.sh |
2564 | +# t/lib/bash/parse_options.sh |
2565 | +# See https://launchpad.net/percona-toolkit for more information. |
2566 | +# ########################################################################### |
2567 | + |
2568 | + |
2569 | + |
2570 | + |
2571 | + |
2572 | +set -u |
2573 | + |
2574 | +ARGV="" # Non-option args (probably input files) |
2575 | +EXT_ARGV="" # Everything after -- (args for an external command) |
2576 | +HAVE_EXT_ARGV="" # Got --, everything else is put into EXT_ARGV |
2577 | +OPT_ERRS=0 # How many command line option errors |
2578 | +OPT_VERSION="" # If --version was specified |
2579 | +OPT_HELP="" # If --help was specified |
2580 | +PO_DIR="" # Directory with program option spec files |
2581 | + |
2582 | +usage() { |
2583 | + local file="$1" |
2584 | + |
2585 | + local usage="$(grep '^Usage: ' "$file")" |
2586 | + echo $usage |
2587 | + echo |
2588 | + echo "For more information, 'man $TOOL' or 'perldoc $file'." |
2589 | +} |
2590 | + |
2591 | +usage_or_errors() { |
2592 | + local file="$1" |
2593 | + |
2594 | + if [ "$OPT_VERSION" ]; then |
2595 | + local version=$(grep '^pt-[^ ]\+ [0-9]' "$file") |
2596 | + echo "$version" |
2597 | + return 1 |
2598 | + fi |
2599 | + |
2600 | + if [ "$OPT_HELP" ]; then |
2601 | + usage "$file" |
2602 | + echo |
2603 | + echo "Command line options:" |
2604 | + echo |
2605 | + perl -e ' |
2606 | + use strict; |
2607 | + use warnings FATAL => qw(all); |
2608 | + my $lcol = 20; # Allow this much space for option names. |
2609 | + my $rcol = 80 - $lcol; # The terminal is assumed to be 80 chars wide. |
2610 | + my $name; |
2611 | + while ( <> ) { |
2612 | + my $line = $_; |
2613 | + chomp $line; |
2614 | + if ( $line =~ s/^long:/ --/ ) { |
2615 | + $name = $line; |
2616 | + } |
2617 | + elsif ( $line =~ s/^desc:// ) { |
2618 | + $line =~ s/ +$//mg; |
2619 | + my @lines = grep { $_ } |
2620 | + $line =~ m/(.{0,$rcol})(?:\s+|\Z)/g; |
2621 | + if ( length($name) >= $lcol ) { |
2622 | + print $name, "\n", (q{ } x $lcol); |
2623 | + } |
2624 | + else { |
2625 | + printf "%-${lcol}s", $name; |
2626 | + } |
2627 | + print join("\n" . (q{ } x $lcol), @lines); |
2628 | + print "\n"; |
2629 | + } |
2630 | + } |
2631 | + ' "$PO_DIR"/* |
2632 | + echo |
2633 | + echo "Options and values after processing arguments:" |
2634 | + echo |
2635 | + ( |
2636 | + cd "$PO_DIR" |
2637 | + for opt in *; do |
2638 | + local varname="OPT_$(echo "$opt" | tr a-z- A-Z_)" |
2639 | + eval local varvalue=\$$varname |
2640 | + if ! grep -q "type:" "$PO_DIR/$opt" >/dev/null; then |
2641 | + if [ "$varvalue" -a "$varvalue" = "yes" ]; |
2642 | + then varvalue="TRUE" |
2643 | + else |
2644 | + varvalue="FALSE" |
2645 | + fi |
2646 | + fi |
2647 | + printf -- " --%-30s %s" "$opt" "${varvalue:-(No value)}" |
2648 | + echo |
2649 | + done |
2650 | + ) |
2651 | + return 1 |
2652 | + fi |
2653 | + |
2654 | + if [ $OPT_ERRS -gt 0 ]; then |
2655 | + echo |
2656 | + usage "$file" |
2657 | + return 1 |
2658 | + fi |
2659 | + |
2660 | + return 0 |
2661 | +} |
2662 | + |
2663 | +option_error() { |
2664 | + local err="$1" |
2665 | + OPT_ERRS=$(($OPT_ERRS + 1)) |
2666 | + echo "$err" >&2 |
2667 | +} |
2668 | + |
2669 | +parse_options() { |
2670 | + local file="$1" |
2671 | + shift |
2672 | + |
2673 | + ARGV="" |
2674 | + EXT_ARGV="" |
2675 | + HAVE_EXT_ARGV="" |
2676 | + OPT_ERRS=0 |
2677 | + OPT_VERSION="" |
2678 | + OPT_HELP="" |
2679 | + PO_DIR="$PT_TMPDIR/po" |
2680 | + |
2681 | + if [ ! -d "$PO_DIR" ]; then |
2682 | + mkdir "$PO_DIR" |
2683 | + if [ $? -ne 0 ]; then |
2684 | + echo "Cannot mkdir $PO_DIR" >&2 |
2685 | + exit 1 |
2686 | + fi |
2687 | + fi |
2688 | + |
2689 | + rm -rf "$PO_DIR"/* |
2690 | + if [ $? -ne 0 ]; then |
2691 | + echo "Cannot rm -rf $PO_DIR/*" >&2 |
2692 | + exit 1 |
2693 | + fi |
2694 | + |
2695 | + _parse_pod "$file" # Parse POD into program option (po) spec files |
2696 | + _eval_po # Eval po into existence with default values |
2697 | + |
2698 | + if [ $# -ge 2 ] && [ "$1" = "--config" ]; then |
2699 | + shift # --config |
2700 | + local user_config_files="$1" |
2701 | + shift # that ^ |
2702 | + local IFS="," |
2703 | + for user_config_file in $user_config_files; do |
2704 | + _parse_config_files "$user_config_file" |
2705 | + done |
2706 | + else |
2707 | + _parse_config_files "/etc/percona-toolkit/percona-toolkit.conf" "/etc/percona-toolkit/$TOOL.conf" "$HOME/.percona-toolkit.conf" "$HOME/.$TOOL.conf" |
2708 | + fi |
2709 | + |
2710 | + _parse_command_line "${@:-""}" |
2711 | +} |
2712 | + |
2713 | +_parse_pod() { |
2714 | + local file="$1" |
2715 | + |
2716 | + cat "$file" | PO_DIR="$PO_DIR" perl -ne ' |
2717 | + BEGIN { $/ = ""; } |
2718 | + next unless $_ =~ m/^=head1 OPTIONS/; |
2719 | + while ( defined(my $para = <>) ) { |
2720 | + last if $para =~ m/^=head1/; |
2721 | + chomp; |
2722 | + if ( $para =~ m/^=item --(\S+)/ ) { |
2723 | + my $opt = $1; |
2724 | + my $file = "$ENV{PO_DIR}/$opt"; |
2725 | + open my $opt_fh, ">", $file or die "Cannot open $file: $!"; |
2726 | + print $opt_fh "long:$opt\n"; |
2727 | + $para = <>; |
2728 | + chomp; |
2729 | + if ( $para =~ m/^[a-z ]+:/ ) { |
2730 | + map { |
2731 | + chomp; |
2732 | + my ($attrib, $val) = split(/: /, $_); |
2733 | + print $opt_fh "$attrib:$val\n"; |
2734 | + } split(/; /, $para); |
2735 | + $para = <>; |
2736 | + chomp; |
2737 | + } |
2738 | + my ($desc) = $para =~ m/^([^?.]+)/; |
2739 | + print $opt_fh "desc:$desc.\n"; |
2740 | + close $opt_fh; |
2741 | + } |
2742 | + } |
2743 | + last; |
2744 | + ' |
2745 | +} |
2746 | + |
2747 | +_eval_po() { |
2748 | + local IFS=":" |
2749 | + for opt_spec in "$PO_DIR"/*; do |
2750 | + local opt="" |
2751 | + local default_val="" |
2752 | + local neg=0 |
2753 | + local size=0 |
2754 | + while read key val; do |
2755 | + case "$key" in |
2756 | + long) |
2757 | + opt=$(echo $val | sed 's/-/_/g' | tr '[:lower:]' '[:upper:]') |
2758 | + ;; |
2759 | + default) |
2760 | + default_val="$val" |
2761 | + ;; |
2762 | + "short form") |
2763 | + ;; |
2764 | + type) |
2765 | + [ "$val" = "size" ] && size=1 |
2766 | + ;; |
2767 | + desc) |
2768 | + ;; |
2769 | + negatable) |
2770 | + if [ "$val" = "yes" ]; then |
2771 | + neg=1 |
2772 | + fi |
2773 | + ;; |
2774 | + *) |
2775 | + echo "Invalid attribute in $opt_spec: $line" >&2 |
2776 | + exit 1 |
2777 | + esac |
2778 | + done < "$opt_spec" |
2779 | + |
2780 | + if [ -z "$opt" ]; then |
2781 | + echo "No long attribute in option spec $opt_spec" >&2 |
2782 | + exit 1 |
2783 | + fi |
2784 | + |
2785 | + if [ $neg -eq 1 ]; then |
2786 | + if [ -z "$default_val" ] || [ "$default_val" != "yes" ]; then |
2787 | + echo "Option $opt_spec is negatable but not default: yes" >&2 |
2788 | + exit 1 |
2789 | + fi |
2790 | + fi |
2791 | + |
2792 | + if [ $size -eq 1 -a -n "$default_val" ]; then |
2793 | + default_val=$(size_to_bytes $default_val) |
2794 | + fi |
2795 | + |
2796 | + eval "OPT_${opt}"="$default_val" |
2797 | + done |
2798 | +} |
2799 | + |
2800 | +_parse_config_files() { |
2801 | + |
2802 | + for config_file in "${@:-""}"; do |
2803 | + test -f "$config_file" || continue |
2804 | + |
2805 | + while read config_opt; do |
2806 | + |
2807 | + echo "$config_opt" | grep '^[ ]*[^#]' >/dev/null 2>&1 || continue |
2808 | + |
2809 | + config_opt="$(echo "$config_opt" | sed -e 's/^ *//g' -e 's/ *$//g' -e 's/[ ]*=[ ]*/=/' -e 's/[ ]*#.*$//')" |
2810 | + |
2811 | + [ "$config_opt" = "" ] && continue |
2812 | + |
2813 | + if ! [ "$HAVE_EXT_ARGV" ]; then |
2814 | + config_opt="--$config_opt" |
2815 | + fi |
2816 | + |
2817 | + _parse_command_line "$config_opt" |
2818 | + |
2819 | + done < "$config_file" |
2820 | + |
2821 | + HAVE_EXT_ARGV="" # reset for each file |
2822 | + |
2823 | + done |
2824 | +} |
2825 | + |
2826 | +_parse_command_line() { |
2827 | + local opt="" |
2828 | + local val="" |
2829 | + local next_opt_is_val="" |
2830 | + local opt_is_ok="" |
2831 | + local opt_is_negated="" |
2832 | + local real_opt="" |
2833 | + local required_arg="" |
2834 | + local spec="" |
2835 | + |
2836 | + for opt in "${@:-""}"; do |
2837 | + if [ "$opt" = "--" -o "$opt" = "----" ]; then |
2838 | + HAVE_EXT_ARGV=1 |
2839 | + continue |
2840 | + fi |
2841 | + if [ "$HAVE_EXT_ARGV" ]; then |
2842 | + if [ "$EXT_ARGV" ]; then |
2843 | + EXT_ARGV="$EXT_ARGV $opt" |
2844 | + else |
2845 | + EXT_ARGV="$opt" |
2846 | + fi |
2847 | + continue |
2848 | + fi |
2849 | + |
2850 | + if [ "$next_opt_is_val" ]; then |
2851 | + next_opt_is_val="" |
2852 | + if [ $# -eq 0 ] || [ $(expr "$opt" : "\-") -eq 1 ]; then |
2853 | + option_error "$real_opt requires a $required_arg argument" |
2854 | + continue |
2855 | + fi |
2856 | + val="$opt" |
2857 | + opt_is_ok=1 |
2858 | + else |
2859 | + if [ $(expr "$opt" : "\-") -eq 0 ]; then |
2860 | + if [ -z "$ARGV" ]; then |
2861 | + ARGV="$opt" |
2862 | + else |
2863 | + ARGV="$ARGV $opt" |
2864 | + fi |
2865 | + continue |
2866 | + fi |
2867 | + |
2868 | + real_opt="$opt" |
2869 | + |
2870 | + if $(echo $opt | grep '^--no[^-]' >/dev/null); then |
2871 | + local base_opt=$(echo $opt | sed 's/^--no//') |
2872 | + if [ -f "$PT_TMPDIR/po/$base_opt" ]; then |
2873 | + opt_is_negated=1 |
2874 | + opt="$base_opt" |
2875 | + else |
2876 | + opt_is_negated="" |
2877 | + opt=$(echo $opt | sed 's/^-*//') |
2878 | + fi |
2879 | + else |
2880 | + if $(echo $opt | grep '^--no-' >/dev/null); then |
2881 | + opt_is_negated=1 |
2882 | + opt=$(echo $opt | sed 's/^--no-//') |
2883 | + else |
2884 | + opt_is_negated="" |
2885 | + opt=$(echo $opt | sed 's/^-*//') |
2886 | + fi |
2887 | + fi |
2888 | + |
2889 | + if $(echo $opt | grep '^[a-z-][a-z-]*=' >/dev/null 2>&1); then |
2890 | + val="$(echo $opt | awk -F= '{print $2}')" |
2891 | + opt="$(echo $opt | awk -F= '{print $1}')" |
2892 | + fi |
2893 | + |
2894 | + if [ -f "$PT_TMPDIR/po/$opt" ]; then |
2895 | + spec="$PT_TMPDIR/po/$opt" |
2896 | + else |
2897 | + spec=$(grep "^short form:-$opt\$" "$PT_TMPDIR"/po/* | cut -d ':' -f 1) |
2898 | + if [ -z "$spec" ]; then |
2899 | + option_error "Unknown option: $real_opt" |
2900 | + continue |
2901 | + fi |
2902 | + fi |
2903 | + |
2904 | + required_arg=$(cat "$spec" | awk -F: '/^type:/{print $2}') |
2905 | + if [ "$required_arg" ]; then |
2906 | + if [ "$val" ]; then |
2907 | + opt_is_ok=1 |
2908 | + else |
2909 | + next_opt_is_val=1 |
2910 | + fi |
2911 | + else |
2912 | + if [ "$val" ]; then |
2913 | + option_error "Option $real_opt does not take a value" |
2914 | + continue |
2915 | + fi |
2916 | + if [ "$opt_is_negated" ]; then |
2917 | + val="" |
2918 | + else |
2919 | + val="yes" |
2920 | + fi |
2921 | + opt_is_ok=1 |
2922 | + fi |
2923 | + fi |
2924 | + |
2925 | + if [ "$opt_is_ok" ]; then |
2926 | + opt=$(cat "$spec" | grep '^long:' | cut -d':' -f2 | sed 's/-/_/g' | tr '[:lower:]' '[:upper:]') |
2927 | + |
2928 | + if grep "^type:size" "$spec" >/dev/null; then |
2929 | + val=$(size_to_bytes $val) |
2930 | + fi |
2931 | + |
2932 | + eval "OPT_$opt"="'$val'" |
2933 | + |
2934 | + opt="" |
2935 | + val="" |
2936 | + next_opt_is_val="" |
2937 | + opt_is_ok="" |
2938 | + opt_is_negated="" |
2939 | + real_opt="" |
2940 | + required_arg="" |
2941 | + spec="" |
2942 | + fi |
2943 | + done |
2944 | +} |
2945 | + |
2946 | +size_to_bytes() { |
2947 | + local size="$1" |
2948 | + echo $size | perl -ne '%f=(B=>1, K=>1_024, M=>1_048_576, G=>1_073_741_824, T=>1_099_511_627_776); m/^(\d+)([kMGT])?/i; print $1 * $f{uc($2 || "B")};' |
2949 | +} |
2950 | + |
2951 | +# ########################################################################### |
2952 | +# End parse_options package |
2953 | +# ########################################################################### |
2954 | + |
2955 | +# ########################################################################### |
2956 | # Global variables |
2957 | # ########################################################################### |
2958 | |
2959 | @@ -106,11 +541,6 @@ |
2960 | # prefix. The outcome of this block of code should be that BASEDIR is the |
2961 | # directory where the files live, without a trailing slash; and PREFIX is |
2962 | # either empty or a timestamp, such as "2011_02_08_16_58_07". |
2963 | - if [ $# -gt 1 ]; then |
2964 | - OPT_ERR="Specify only one PREFIX or DIR" |
2965 | - usage |
2966 | - fi |
2967 | - |
2968 | if [ $# -eq 1 ]; then |
2969 | if [ -d "$1" ]; then |
2970 | BASEDIR="$1" |
2971 | @@ -147,9 +577,6 @@ |
2972 | fi |
2973 | done |
2974 | |
2975 | - # Make a secure tmpdir. |
2976 | - mk_tmpdir |
2977 | - |
2978 | # We need to generate a list of timestamps, and ask the user to choose one if |
2979 | # there is no PREFIX yet. NOTE: we rely on the "-df" files here. |
2980 | ( |
2981 | @@ -580,15 +1007,31 @@ |
2982 | ;; |
2983 | esac |
2984 | done |
2985 | - |
2986 | - rm_tmpdir |
2987 | } |
2988 | |
2989 | # Execute the program if it was not included from another file. This makes it |
2990 | # possible to include without executing, and thus test. |
2991 | if [ "${0##*/}" = "$TOOL" ] \ |
2992 | || [ "${0##*/}" = "bash" -a "${_:-""}" = "$0" ]; then |
2993 | - main "${@:-""}" |
2994 | + |
2995 | + mk_tmpdir |
2996 | + |
2997 | + parse_options "$0" "${@:-""}" |
2998 | + if [ -z "$OPT_HELP" -a -z "$OPT_VERSION" ]; then |
2999 | + if [ $# -gt 1 ]; then |
3000 | + option_error "Specify only one PREFIX or DIR" |
3001 | + fi |
3002 | + fi |
3003 | + usage_or_errors "$0" |
3004 | + po_status=$? |
3005 | + if [ $po_status -ne 0 ]; then |
3006 | + [ $OPT_ERRS -gt 0 ] && exit 1 |
3007 | + exit 0 |
3008 | + fi |
3009 | + |
3010 | + main "${@:-""}" |
3011 | + |
3012 | + rm_tmpdir |
3013 | fi |
3014 | |
3015 | # ############################################################################ |
3016 | @@ -644,47 +1087,47 @@ |
3017 | |
3018 | =over |
3019 | |
3020 | -=item d |
3021 | +=item * d |
3022 | |
3023 | Sets the action to start the L<pt-diskstats> tool on the sample's disk |
3024 | performance statistics. |
3025 | |
3026 | -=item i |
3027 | +=item * i |
3028 | |
3029 | Sets the action to view the first INNODB STATUS sample in less. |
3030 | |
3031 | -=item m |
3032 | +=item * m |
3033 | |
3034 | Displays the first 4 samples of SHOW STATUS counters side by side with the |
3035 | L<pt-mext> tool. |
3036 | |
3037 | -=item n |
3038 | +=item * n |
3039 | |
3040 | Summarizes the first sample of netstat data in two ways: by originating host, |
3041 | and by connection state. |
3042 | |
3043 | -=item j |
3044 | +=item * j |
3045 | |
3046 | Select the next timestamp as the active sample. |
3047 | |
3048 | -=item k |
3049 | +=item * k |
3050 | |
3051 | Select the previous timestamp as the active sample. |
3052 | |
3053 | -=item q |
3054 | +=item * q |
3055 | |
3056 | Quit the program. |
3057 | |
3058 | -=item 1 |
3059 | +=item * 1 |
3060 | |
3061 | Sets the action for each sample to the default, which is to view a summary |
3062 | of the sample. |
3063 | |
3064 | -=item 0 |
3065 | +=item * 0 |
3066 | |
3067 | Sets the action to just list the files in the sample. |
3068 | |
3069 | -=item * |
3070 | +=item * * |
3071 | |
3072 | Sets the action to view all of the sample's files in the less program. |
3073 | |
3074 | @@ -692,7 +1135,17 @@ |
3075 | |
3076 | =head1 OPTIONS |
3077 | |
3078 | -This tool does not have any command-line options. |
3079 | +=over |
3080 | + |
3081 | +=item --help |
3082 | + |
3083 | +Show help and exit. |
3084 | + |
3085 | +=item --version |
3086 | + |
3087 | +Show version and exit. |
3088 | + |
3089 | +=back |
3090 | |
3091 | =head1 ENVIRONMENT |
3092 | |
3093 | |
3094 | === modified file 't/pt-mext/pt-mext.t' |
3095 | --- t/pt-mext/pt-mext.t 2011-08-02 21:14:06 +0000 |
3096 | +++ t/pt-mext/pt-mext.t 2013-04-04 21:05:27 +0000 |
3097 | @@ -9,7 +9,7 @@ |
3098 | use strict; |
3099 | use warnings FATAL => 'all'; |
3100 | use English qw(-no_match_vars); |
3101 | -use Test::More tests => 1; |
3102 | +use Test::More; |
3103 | |
3104 | use PerconaTest; |
3105 | |
3106 | @@ -19,7 +19,27 @@ |
3107 | 'It runs' |
3108 | ); |
3109 | |
3110 | +my $cmd = "$trunk/bin/pt-mext"; |
3111 | +my $sample = "$trunk/t/pt-mext/samples"; |
3112 | + |
3113 | +ok( |
3114 | + no_diff( |
3115 | + "$cmd -- cat $sample/mext-001.txt", |
3116 | + "t/pt-mext/samples/mext-001-result.txt", |
3117 | + ), |
3118 | + "mext-001" |
3119 | +) or diag($test_diff); |
3120 | + |
3121 | +ok( |
3122 | + no_diff( |
3123 | + "$cmd -r -- cat $sample/mext-002.txt", |
3124 | + "t/pt-mext/samples/mext-002-result.txt", |
3125 | + ), |
3126 | + "mext-002 -r" |
3127 | +) or diag($test_diff); |
3128 | + |
3129 | # ############################################################################# |
3130 | # Done. |
3131 | # ############################################################################# |
3132 | +done_testing; |
3133 | exit; |
3134 | |
3135 | === modified file 't/pt-pmp/pt-pmp.t' |
3136 | --- t/pt-pmp/pt-pmp.t 2013-02-04 17:04:30 +0000 |
3137 | +++ t/pt-pmp/pt-pmp.t 2013-04-04 21:05:27 +0000 |
3138 | @@ -25,7 +25,7 @@ |
3139 | "t/pt-pmp/samples/$outfile", |
3140 | ), |
3141 | "$file" |
3142 | - ); |
3143 | + ) or diag($test_diff); |
3144 | } |
3145 | closedir $dh; |
3146 | |
3147 | @@ -35,6 +35,6 @@ |
3148 | "t/pt-pmp/samples/stacktrace003-limit2.out", |
3149 | ), |
3150 | "Limit 2 (stacktrace003-limit2.out)" |
3151 | -); |
3152 | +) or diag($test_diff); |
3153 | |
3154 | done_testing; |