Merge lp:~percona-toolkit-dev/percona-toolkit/version-in-all-bash-tools-bug-821502 into lp:percona-toolkit/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
Reviewer Review Type Date Requested Status
Daniel Nichter Approve
Review via email: mp+157232@code.launchpad.net
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
=== modified file 'bin/pt-align'
--- bin/pt-align 2013-03-14 17:20:35 +0000
+++ bin/pt-align 2013-04-04 21:05:27 +0000
@@ -4,6 +4,1084 @@
4# See "COPYRIGHT, LICENSE, AND WARRANTY" at the end of this file for legal4# See "COPYRIGHT, LICENSE, AND WARRANTY" at the end of this file for legal
5# notices and disclaimers.5# notices and disclaimers.
66
7use strict;
8use warnings FATAL => 'all';
9
10# This tool is "fat-packed": most of its dependent modules are embedded
11# in this file. Setting %INC to this file for each module makes Perl aware
12# of this so it will not try to load the module from @INC. See the tool's
13# documentation for a full list of dependencies.
14BEGIN {
15 $INC{$_} = __FILE__ for map { (my $pkg = "$_.pm") =~ s!::!/!g; $pkg } (qw(
16 OptionParser
17 ));
18}
19
20# ###########################################################################
21# OptionParser package
22# This package is a copy without comments from the original. The original
23# with comments and its test file can be found in the Bazaar repository at,
24# lib/OptionParser.pm
25# t/lib/OptionParser.t
26# See https://launchpad.net/percona-toolkit for more information.
27# ###########################################################################
28{
29package OptionParser;
30
31use strict;
32use warnings FATAL => 'all';
33use English qw(-no_match_vars);
34use constant PTDEBUG => $ENV{PTDEBUG} || 0;
35
36use List::Util qw(max);
37use Getopt::Long;
38use Data::Dumper;
39
40my $POD_link_re = '[LC]<"?([^">]+)"?>';
41
42sub new {
43 my ( $class, %args ) = @_;
44 my @required_args = qw();
45 foreach my $arg ( @required_args ) {
46 die "I need a $arg argument" unless $args{$arg};
47 }
48
49 my ($program_name) = $PROGRAM_NAME =~ m/([.A-Za-z-]+)$/;
50 $program_name ||= $PROGRAM_NAME;
51 my $home = $ENV{HOME} || $ENV{HOMEPATH} || $ENV{USERPROFILE} || '.';
52
53 my %attributes = (
54 'type' => 1,
55 'short form' => 1,
56 'group' => 1,
57 'default' => 1,
58 'cumulative' => 1,
59 'negatable' => 1,
60 );
61
62 my $self = {
63 head1 => 'OPTIONS', # These args are used internally
64 skip_rules => 0, # to instantiate another Option-
65 item => '--(.*)', # Parser obj that parses the
66 attributes => \%attributes, # DSN OPTIONS section. Tools
67 parse_attributes => \&_parse_attribs, # don't tinker with these args.
68
69 %args,
70
71 strict => 1, # disabled by a special rule
72 program_name => $program_name,
73 opts => {},
74 got_opts => 0,
75 short_opts => {},
76 defaults => {},
77 groups => {},
78 allowed_groups => {},
79 errors => [],
80 rules => [], # desc of rules for --help
81 mutex => [], # rule: opts are mutually exclusive
82 atleast1 => [], # rule: at least one opt is required
83 disables => {}, # rule: opt disables other opts
84 defaults_to => {}, # rule: opt defaults to value of other opt
85 DSNParser => undef,
86 default_files => [
87 "/etc/percona-toolkit/percona-toolkit.conf",
88 "/etc/percona-toolkit/$program_name.conf",
89 "$home/.percona-toolkit.conf",
90 "$home/.$program_name.conf",
91 ],
92 types => {
93 string => 's', # standard Getopt type
94 int => 'i', # standard Getopt type
95 float => 'f', # standard Getopt type
96 Hash => 'H', # hash, formed from a comma-separated list
97 hash => 'h', # hash as above, but only if a value is given
98 Array => 'A', # array, similar to Hash
99 array => 'a', # array, similar to hash
100 DSN => 'd', # DSN
101 size => 'z', # size with kMG suffix (powers of 2^10)
102 time => 'm', # time, with an optional suffix of s/h/m/d
103 },
104 };
105
106 return bless $self, $class;
107}
108
109sub get_specs {
110 my ( $self, $file ) = @_;
111 $file ||= $self->{file} || __FILE__;
112 my @specs = $self->_pod_to_specs($file);
113 $self->_parse_specs(@specs);
114
115 open my $fh, "<", $file or die "Cannot open $file: $OS_ERROR";
116 my $contents = do { local $/ = undef; <$fh> };
117 close $fh;
118 if ( $contents =~ m/^=head1 DSN OPTIONS/m ) {
119 PTDEBUG && _d('Parsing DSN OPTIONS');
120 my $dsn_attribs = {
121 dsn => 1,
122 copy => 1,
123 };
124 my $parse_dsn_attribs = sub {
125 my ( $self, $option, $attribs ) = @_;
126 map {
127 my $val = $attribs->{$_};
128 if ( $val ) {
129 $val = $val eq 'yes' ? 1
130 : $val eq 'no' ? 0
131 : $val;
132 $attribs->{$_} = $val;
133 }
134 } keys %$attribs;
135 return {
136 key => $option,
137 %$attribs,
138 };
139 };
140 my $dsn_o = new OptionParser(
141 description => 'DSN OPTIONS',
142 head1 => 'DSN OPTIONS',
143 dsn => 0, # XXX don't infinitely recurse!
144 item => '\* (.)', # key opts are a single character
145 skip_rules => 1, # no rules before opts
146 attributes => $dsn_attribs,
147 parse_attributes => $parse_dsn_attribs,
148 );
149 my @dsn_opts = map {
150 my $opts = {
151 key => $_->{spec}->{key},
152 dsn => $_->{spec}->{dsn},
153 copy => $_->{spec}->{copy},
154 desc => $_->{desc},
155 };
156 $opts;
157 } $dsn_o->_pod_to_specs($file);
158 $self->{DSNParser} = DSNParser->new(opts => \@dsn_opts);
159 }
160
161 if ( $contents =~ m/^=head1 VERSION\n\n^(.+)$/m ) {
162 $self->{version} = $1;
163 PTDEBUG && _d($self->{version});
164 }
165
166 return;
167}
168
169sub DSNParser {
170 my ( $self ) = @_;
171 return $self->{DSNParser};
172};
173
174sub get_defaults_files {
175 my ( $self ) = @_;
176 return @{$self->{default_files}};
177}
178
179sub _pod_to_specs {
180 my ( $self, $file ) = @_;
181 $file ||= $self->{file} || __FILE__;
182 open my $fh, '<', $file or die "Cannot open $file: $OS_ERROR";
183
184 my @specs = ();
185 my @rules = ();
186 my $para;
187
188 local $INPUT_RECORD_SEPARATOR = '';
189 while ( $para = <$fh> ) {
190 next unless $para =~ m/^=head1 $self->{head1}/;
191 last;
192 }
193
194 while ( $para = <$fh> ) {
195 last if $para =~ m/^=over/;
196 next if $self->{skip_rules};
197 chomp $para;
198 $para =~ s/\s+/ /g;
199 $para =~ s/$POD_link_re/$1/go;
200 PTDEBUG && _d('Option rule:', $para);
201 push @rules, $para;
202 }
203
204 die "POD has no $self->{head1} section" unless $para;
205
206 do {
207 if ( my ($option) = $para =~ m/^=item $self->{item}/ ) {
208 chomp $para;
209 PTDEBUG && _d($para);
210 my %attribs;
211
212 $para = <$fh>; # read next paragraph, possibly attributes
213
214 if ( $para =~ m/: / ) { # attributes
215 $para =~ s/\s+\Z//g;
216 %attribs = map {
217 my ( $attrib, $val) = split(/: /, $_);
218 die "Unrecognized attribute for --$option: $attrib"
219 unless $self->{attributes}->{$attrib};
220 ($attrib, $val);
221 } split(/; /, $para);
222 if ( $attribs{'short form'} ) {
223 $attribs{'short form'} =~ s/-//;
224 }
225 $para = <$fh>; # read next paragraph, probably short help desc
226 }
227 else {
228 PTDEBUG && _d('Option has no attributes');
229 }
230
231 $para =~ s/\s+\Z//g;
232 $para =~ s/\s+/ /g;
233 $para =~ s/$POD_link_re/$1/go;
234
235 $para =~ s/\.(?:\n.*| [A-Z].*|\Z)//s;
236 PTDEBUG && _d('Short help:', $para);
237
238 die "No description after option spec $option" if $para =~ m/^=item/;
239
240 if ( my ($base_option) = $option =~ m/^\[no\](.*)/ ) {
241 $option = $base_option;
242 $attribs{'negatable'} = 1;
243 }
244
245 push @specs, {
246 spec => $self->{parse_attributes}->($self, $option, \%attribs),
247 desc => $para
248 . (defined $attribs{default} ? " (default $attribs{default})" : ''),
249 group => ($attribs{'group'} ? $attribs{'group'} : 'default'),
250 };
251 }
252 while ( $para = <$fh> ) {
253 last unless $para;
254 if ( $para =~ m/^=head1/ ) {
255 $para = undef; # Can't 'last' out of a do {} block.
256 last;
257 }
258 last if $para =~ m/^=item /;
259 }
260 } while ( $para );
261
262 die "No valid specs in $self->{head1}" unless @specs;
263
264 close $fh;
265 return @specs, @rules;
266}
267
268sub _parse_specs {
269 my ( $self, @specs ) = @_;
270 my %disables; # special rule that requires deferred checking
271
272 foreach my $opt ( @specs ) {
273 if ( ref $opt ) { # It's an option spec, not a rule.
274 PTDEBUG && _d('Parsing opt spec:',
275 map { ($_, '=>', $opt->{$_}) } keys %$opt);
276
277 my ( $long, $short ) = $opt->{spec} =~ m/^([\w-]+)(?:\|([^!+=]*))?/;
278 if ( !$long ) {
279 die "Cannot parse long option from spec $opt->{spec}";
280 }
281 $opt->{long} = $long;
282
283 die "Duplicate long option --$long" if exists $self->{opts}->{$long};
284 $self->{opts}->{$long} = $opt;
285
286 if ( length $long == 1 ) {
287 PTDEBUG && _d('Long opt', $long, 'looks like short opt');
288 $self->{short_opts}->{$long} = $long;
289 }
290
291 if ( $short ) {
292 die "Duplicate short option -$short"
293 if exists $self->{short_opts}->{$short};
294 $self->{short_opts}->{$short} = $long;
295 $opt->{short} = $short;
296 }
297 else {
298 $opt->{short} = undef;
299 }
300
301 $opt->{is_negatable} = $opt->{spec} =~ m/!/ ? 1 : 0;
302 $opt->{is_cumulative} = $opt->{spec} =~ m/\+/ ? 1 : 0;
303 $opt->{is_required} = $opt->{desc} =~ m/required/ ? 1 : 0;
304
305 $opt->{group} ||= 'default';
306 $self->{groups}->{ $opt->{group} }->{$long} = 1;
307
308 $opt->{value} = undef;
309 $opt->{got} = 0;
310
311 my ( $type ) = $opt->{spec} =~ m/=(.)/;
312 $opt->{type} = $type;
313 PTDEBUG && _d($long, 'type:', $type);
314
315
316 $opt->{spec} =~ s/=./=s/ if ( $type && $type =~ m/[HhAadzm]/ );
317
318 if ( (my ($def) = $opt->{desc} =~ m/default\b(?: ([^)]+))?/) ) {
319 $self->{defaults}->{$long} = defined $def ? $def : 1;
320 PTDEBUG && _d($long, 'default:', $def);
321 }
322
323 if ( $long eq 'config' ) {
324 $self->{defaults}->{$long} = join(',', $self->get_defaults_files());
325 }
326
327 if ( (my ($dis) = $opt->{desc} =~ m/(disables .*)/) ) {
328 $disables{$long} = $dis;
329 PTDEBUG && _d('Deferring check of disables rule for', $opt, $dis);
330 }
331
332 $self->{opts}->{$long} = $opt;
333 }
334 else { # It's an option rule, not a spec.
335 PTDEBUG && _d('Parsing rule:', $opt);
336 push @{$self->{rules}}, $opt;
337 my @participants = $self->_get_participants($opt);
338 my $rule_ok = 0;
339
340 if ( $opt =~ m/mutually exclusive|one and only one/ ) {
341 $rule_ok = 1;
342 push @{$self->{mutex}}, \@participants;
343 PTDEBUG && _d(@participants, 'are mutually exclusive');
344 }
345 if ( $opt =~ m/at least one|one and only one/ ) {
346 $rule_ok = 1;
347 push @{$self->{atleast1}}, \@participants;
348 PTDEBUG && _d(@participants, 'require at least one');
349 }
350 if ( $opt =~ m/default to/ ) {
351 $rule_ok = 1;
352 $self->{defaults_to}->{$participants[0]} = $participants[1];
353 PTDEBUG && _d($participants[0], 'defaults to', $participants[1]);
354 }
355 if ( $opt =~ m/restricted to option groups/ ) {
356 $rule_ok = 1;
357 my ($groups) = $opt =~ m/groups ([\w\s\,]+)/;
358 my @groups = split(',', $groups);
359 %{$self->{allowed_groups}->{$participants[0]}} = map {
360 s/\s+//;
361 $_ => 1;
362 } @groups;
363 }
364 if( $opt =~ m/accepts additional command-line arguments/ ) {
365 $rule_ok = 1;
366 $self->{strict} = 0;
367 PTDEBUG && _d("Strict mode disabled by rule");
368 }
369
370 die "Unrecognized option rule: $opt" unless $rule_ok;
371 }
372 }
373
374 foreach my $long ( keys %disables ) {
375 my @participants = $self->_get_participants($disables{$long});
376 $self->{disables}->{$long} = \@participants;
377 PTDEBUG && _d('Option', $long, 'disables', @participants);
378 }
379
380 return;
381}
382
383sub _get_participants {
384 my ( $self, $str ) = @_;
385 my @participants;
386 foreach my $long ( $str =~ m/--(?:\[no\])?([\w-]+)/g ) {
387 die "Option --$long does not exist while processing rule $str"
388 unless exists $self->{opts}->{$long};
389 push @participants, $long;
390 }
391 PTDEBUG && _d('Participants for', $str, ':', @participants);
392 return @participants;
393}
394
395sub opts {
396 my ( $self ) = @_;
397 my %opts = %{$self->{opts}};
398 return %opts;
399}
400
401sub short_opts {
402 my ( $self ) = @_;
403 my %short_opts = %{$self->{short_opts}};
404 return %short_opts;
405}
406
407sub set_defaults {
408 my ( $self, %defaults ) = @_;
409 $self->{defaults} = {};
410 foreach my $long ( keys %defaults ) {
411 die "Cannot set default for nonexistent option $long"
412 unless exists $self->{opts}->{$long};
413 $self->{defaults}->{$long} = $defaults{$long};
414 PTDEBUG && _d('Default val for', $long, ':', $defaults{$long});
415 }
416 return;
417}
418
419sub get_defaults {
420 my ( $self ) = @_;
421 return $self->{defaults};
422}
423
424sub get_groups {
425 my ( $self ) = @_;
426 return $self->{groups};
427}
428
429sub _set_option {
430 my ( $self, $opt, $val ) = @_;
431 my $long = exists $self->{opts}->{$opt} ? $opt
432 : exists $self->{short_opts}->{$opt} ? $self->{short_opts}->{$opt}
433 : die "Getopt::Long gave a nonexistent option: $opt";
434
435 $opt = $self->{opts}->{$long};
436 if ( $opt->{is_cumulative} ) {
437 $opt->{value}++;
438 }
439 else {
440 $opt->{value} = $val;
441 }
442 $opt->{got} = 1;
443 PTDEBUG && _d('Got option', $long, '=', $val);
444}
445
446sub get_opts {
447 my ( $self ) = @_;
448
449 foreach my $long ( keys %{$self->{opts}} ) {
450 $self->{opts}->{$long}->{got} = 0;
451 $self->{opts}->{$long}->{value}
452 = exists $self->{defaults}->{$long} ? $self->{defaults}->{$long}
453 : $self->{opts}->{$long}->{is_cumulative} ? 0
454 : undef;
455 }
456 $self->{got_opts} = 0;
457
458 $self->{errors} = [];
459
460 if ( @ARGV && $ARGV[0] eq "--config" ) {
461 shift @ARGV;
462 $self->_set_option('config', shift @ARGV);
463 }
464 if ( $self->has('config') ) {
465 my @extra_args;
466 foreach my $filename ( split(',', $self->get('config')) ) {
467 eval {
468 push @extra_args, $self->_read_config_file($filename);
469 };
470 if ( $EVAL_ERROR ) {
471 if ( $self->got('config') ) {
472 die $EVAL_ERROR;
473 }
474 elsif ( PTDEBUG ) {
475 _d($EVAL_ERROR);
476 }
477 }
478 }
479 unshift @ARGV, @extra_args;
480 }
481
482 Getopt::Long::Configure('no_ignore_case', 'bundling');
483 GetOptions(
484 map { $_->{spec} => sub { $self->_set_option(@_); } }
485 grep { $_->{long} ne 'config' } # --config is handled specially above.
486 values %{$self->{opts}}
487 ) or $self->save_error('Error parsing options');
488
489 if ( exists $self->{opts}->{version} && $self->{opts}->{version}->{got} ) {
490 if ( $self->{version} ) {
491 print $self->{version}, "\n";
492 }
493 else {
494 print "Error parsing version. See the VERSION section of the tool's documentation.\n";
495 }
496 exit 1;
497 }
498
499 if ( @ARGV && $self->{strict} ) {
500 $self->save_error("Unrecognized command-line options @ARGV");
501 }
502
503 foreach my $mutex ( @{$self->{mutex}} ) {
504 my @set = grep { $self->{opts}->{$_}->{got} } @$mutex;
505 if ( @set > 1 ) {
506 my $err = join(', ', map { "--$self->{opts}->{$_}->{long}" }
507 @{$mutex}[ 0 .. scalar(@$mutex) - 2] )
508 . ' and --'.$self->{opts}->{$mutex->[-1]}->{long}
509 . ' are mutually exclusive.';
510 $self->save_error($err);
511 }
512 }
513
514 foreach my $required ( @{$self->{atleast1}} ) {
515 my @set = grep { $self->{opts}->{$_}->{got} } @$required;
516 if ( @set == 0 ) {
517 my $err = join(', ', map { "--$self->{opts}->{$_}->{long}" }
518 @{$required}[ 0 .. scalar(@$required) - 2] )
519 .' or --'.$self->{opts}->{$required->[-1]}->{long};
520 $self->save_error("Specify at least one of $err");
521 }
522 }
523
524 $self->_check_opts( keys %{$self->{opts}} );
525 $self->{got_opts} = 1;
526 return;
527}
528
529sub _check_opts {
530 my ( $self, @long ) = @_;
531 my $long_last = scalar @long;
532 while ( @long ) {
533 foreach my $i ( 0..$#long ) {
534 my $long = $long[$i];
535 next unless $long;
536 my $opt = $self->{opts}->{$long};
537 if ( $opt->{got} ) {
538 if ( exists $self->{disables}->{$long} ) {
539 my @disable_opts = @{$self->{disables}->{$long}};
540 map { $self->{opts}->{$_}->{value} = undef; } @disable_opts;
541 PTDEBUG && _d('Unset options', @disable_opts,
542 'because', $long,'disables them');
543 }
544
545 if ( exists $self->{allowed_groups}->{$long} ) {
546
547 my @restricted_groups = grep {
548 !exists $self->{allowed_groups}->{$long}->{$_}
549 } keys %{$self->{groups}};
550
551 my @restricted_opts;
552 foreach my $restricted_group ( @restricted_groups ) {
553 RESTRICTED_OPT:
554 foreach my $restricted_opt (
555 keys %{$self->{groups}->{$restricted_group}} )
556 {
557 next RESTRICTED_OPT if $restricted_opt eq $long;
558 push @restricted_opts, $restricted_opt
559 if $self->{opts}->{$restricted_opt}->{got};
560 }
561 }
562
563 if ( @restricted_opts ) {
564 my $err;
565 if ( @restricted_opts == 1 ) {
566 $err = "--$restricted_opts[0]";
567 }
568 else {
569 $err = join(', ',
570 map { "--$self->{opts}->{$_}->{long}" }
571 grep { $_ }
572 @restricted_opts[0..scalar(@restricted_opts) - 2]
573 )
574 . ' or --'.$self->{opts}->{$restricted_opts[-1]}->{long};
575 }
576 $self->save_error("--$long is not allowed with $err");
577 }
578 }
579
580 }
581 elsif ( $opt->{is_required} ) {
582 $self->save_error("Required option --$long must be specified");
583 }
584
585 $self->_validate_type($opt);
586 if ( $opt->{parsed} ) {
587 delete $long[$i];
588 }
589 else {
590 PTDEBUG && _d('Temporarily failed to parse', $long);
591 }
592 }
593
594 die "Failed to parse options, possibly due to circular dependencies"
595 if @long == $long_last;
596 $long_last = @long;
597 }
598
599 return;
600}
601
602sub _validate_type {
603 my ( $self, $opt ) = @_;
604 return unless $opt;
605
606 if ( !$opt->{type} ) {
607 $opt->{parsed} = 1;
608 return;
609 }
610
611 my $val = $opt->{value};
612
613 if ( $val && $opt->{type} eq 'm' ) { # type time
614 PTDEBUG && _d('Parsing option', $opt->{long}, 'as a time value');
615 my ( $prefix, $num, $suffix ) = $val =~ m/([+-]?)(\d+)([a-z])?$/;
616 if ( !$suffix ) {
617 my ( $s ) = $opt->{desc} =~ m/\(suffix (.)\)/;
618 $suffix = $s || 's';
619 PTDEBUG && _d('No suffix given; using', $suffix, 'for',
620 $opt->{long}, '(value:', $val, ')');
621 }
622 if ( $suffix =~ m/[smhd]/ ) {
623 $val = $suffix eq 's' ? $num # Seconds
624 : $suffix eq 'm' ? $num * 60 # Minutes
625 : $suffix eq 'h' ? $num * 3600 # Hours
626 : $num * 86400; # Days
627 $opt->{value} = ($prefix || '') . $val;
628 PTDEBUG && _d('Setting option', $opt->{long}, 'to', $val);
629 }
630 else {
631 $self->save_error("Invalid time suffix for --$opt->{long}");
632 }
633 }
634 elsif ( $val && $opt->{type} eq 'd' ) { # type DSN
635 PTDEBUG && _d('Parsing option', $opt->{long}, 'as a DSN');
636 my $prev = {};
637 my $from_key = $self->{defaults_to}->{ $opt->{long} };
638 if ( $from_key ) {
639 PTDEBUG && _d($opt->{long}, 'DSN copies from', $from_key, 'DSN');
640 if ( $self->{opts}->{$from_key}->{parsed} ) {
641 $prev = $self->{opts}->{$from_key}->{value};
642 }
643 else {
644 PTDEBUG && _d('Cannot parse', $opt->{long}, 'until',
645 $from_key, 'parsed');
646 return;
647 }
648 }
649 my $defaults = $self->{DSNParser}->parse_options($self);
650 $opt->{value} = $self->{DSNParser}->parse($val, $prev, $defaults);
651 }
652 elsif ( $val && $opt->{type} eq 'z' ) { # type size
653 PTDEBUG && _d('Parsing option', $opt->{long}, 'as a size value');
654 $self->_parse_size($opt, $val);
655 }
656 elsif ( $opt->{type} eq 'H' || (defined $val && $opt->{type} eq 'h') ) {
657 $opt->{value} = { map { $_ => 1 } split(/(?<!\\),\s*/, ($val || '')) };
658 }
659 elsif ( $opt->{type} eq 'A' || (defined $val && $opt->{type} eq 'a') ) {
660 $opt->{value} = [ split(/(?<!\\),\s*/, ($val || '')) ];
661 }
662 else {
663 PTDEBUG && _d('Nothing to validate for option',
664 $opt->{long}, 'type', $opt->{type}, 'value', $val);
665 }
666
667 $opt->{parsed} = 1;
668 return;
669}
670
671sub get {
672 my ( $self, $opt ) = @_;
673 my $long = (length $opt == 1 ? $self->{short_opts}->{$opt} : $opt);
674 die "Option $opt does not exist"
675 unless $long && exists $self->{opts}->{$long};
676 return $self->{opts}->{$long}->{value};
677}
678
679sub got {
680 my ( $self, $opt ) = @_;
681 my $long = (length $opt == 1 ? $self->{short_opts}->{$opt} : $opt);
682 die "Option $opt does not exist"
683 unless $long && exists $self->{opts}->{$long};
684 return $self->{opts}->{$long}->{got};
685}
686
687sub has {
688 my ( $self, $opt ) = @_;
689 my $long = (length $opt == 1 ? $self->{short_opts}->{$opt} : $opt);
690 return defined $long ? exists $self->{opts}->{$long} : 0;
691}
692
693sub set {
694 my ( $self, $opt, $val ) = @_;
695 my $long = (length $opt == 1 ? $self->{short_opts}->{$opt} : $opt);
696 die "Option $opt does not exist"
697 unless $long && exists $self->{opts}->{$long};
698 $self->{opts}->{$long}->{value} = $val;
699 return;
700}
701
702sub save_error {
703 my ( $self, $error ) = @_;
704 push @{$self->{errors}}, $error;
705 return;
706}
707
708sub errors {
709 my ( $self ) = @_;
710 return $self->{errors};
711}
712
713sub usage {
714 my ( $self ) = @_;
715 warn "No usage string is set" unless $self->{usage}; # XXX
716 return "Usage: " . ($self->{usage} || '') . "\n";
717}
718
719sub descr {
720 my ( $self ) = @_;
721 warn "No description string is set" unless $self->{description}; # XXX
722 my $descr = ($self->{description} || $self->{program_name} || '')
723 . " For more details, please use the --help option, "
724 . "or try 'perldoc $PROGRAM_NAME' "
725 . "for complete documentation.";
726 $descr = join("\n", $descr =~ m/(.{0,80})(?:\s+|$)/g)
727 unless $ENV{DONT_BREAK_LINES};
728 $descr =~ s/ +$//mg;
729 return $descr;
730}
731
732sub usage_or_errors {
733 my ( $self, $file, $return ) = @_;
734 $file ||= $self->{file} || __FILE__;
735
736 if ( !$self->{description} || !$self->{usage} ) {
737 PTDEBUG && _d("Getting description and usage from SYNOPSIS in", $file);
738 my %synop = $self->_parse_synopsis($file);
739 $self->{description} ||= $synop{description};
740 $self->{usage} ||= $synop{usage};
741 PTDEBUG && _d("Description:", $self->{description},
742 "\nUsage:", $self->{usage});
743 }
744
745 if ( $self->{opts}->{help}->{got} ) {
746 print $self->print_usage() or die "Cannot print usage: $OS_ERROR";
747 exit 0 unless $return;
748 }
749 elsif ( scalar @{$self->{errors}} ) {
750 print $self->print_errors() or die "Cannot print errors: $OS_ERROR";
751 exit 1 unless $return;
752 }
753
754 return;
755}
756
757sub print_errors {
758 my ( $self ) = @_;
759 my $usage = $self->usage() . "\n";
760 if ( (my @errors = @{$self->{errors}}) ) {
761 $usage .= join("\n * ", 'Errors in command-line arguments:', @errors)
762 . "\n";
763 }
764 return $usage . "\n" . $self->descr();
765}
766
767sub print_usage {
768 my ( $self ) = @_;
769 die "Run get_opts() before print_usage()" unless $self->{got_opts};
770 my @opts = values %{$self->{opts}};
771
772 my $maxl = max(
773 map {
774 length($_->{long}) # option long name
775 + ($_->{is_negatable} ? 4 : 0) # "[no]" if opt is negatable
776 + ($_->{type} ? 2 : 0) # "=x" where x is the opt type
777 }
778 @opts);
779
780 my $maxs = max(0,
781 map {
782 length($_)
783 + ($self->{opts}->{$_}->{is_negatable} ? 4 : 0)
784 + ($self->{opts}->{$_}->{type} ? 2 : 0)
785 }
786 values %{$self->{short_opts}});
787
788 my $lcol = max($maxl, ($maxs + 3));
789 my $rcol = 80 - $lcol - 6;
790 my $rpad = ' ' x ( 80 - $rcol );
791
792 $maxs = max($lcol - 3, $maxs);
793
794 my $usage = $self->descr() . "\n" . $self->usage();
795
796 my @groups = reverse sort grep { $_ ne 'default'; } keys %{$self->{groups}};
797 push @groups, 'default';
798
799 foreach my $group ( reverse @groups ) {
800 $usage .= "\n".($group eq 'default' ? 'Options' : $group).":\n\n";
801 foreach my $opt (
802 sort { $a->{long} cmp $b->{long} }
803 grep { $_->{group} eq $group }
804 @opts )
805 {
806 my $long = $opt->{is_negatable} ? "[no]$opt->{long}" : $opt->{long};
807 my $short = $opt->{short};
808 my $desc = $opt->{desc};
809
810 $long .= $opt->{type} ? "=$opt->{type}" : "";
811
812 if ( $opt->{type} && $opt->{type} eq 'm' ) {
813 my ($s) = $desc =~ m/\(suffix (.)\)/;
814 $s ||= 's';
815 $desc =~ s/\s+\(suffix .\)//;
816 $desc .= ". Optional suffix s=seconds, m=minutes, h=hours, "
817 . "d=days; if no suffix, $s is used.";
818 }
819 $desc = join("\n$rpad", grep { $_ } $desc =~ m/(.{0,$rcol}(?!\W))(?:\s+|(?<=\W)|$)/g);
820 $desc =~ s/ +$//mg;
821 if ( $short ) {
822 $usage .= sprintf(" --%-${maxs}s -%s %s\n", $long, $short, $desc);
823 }
824 else {
825 $usage .= sprintf(" --%-${lcol}s %s\n", $long, $desc);
826 }
827 }
828 }
829
830 $usage .= "\nOption types: s=string, i=integer, f=float, h/H/a/A=comma-separated list, d=DSN, z=size, m=time\n";
831
832 if ( (my @rules = @{$self->{rules}}) ) {
833 $usage .= "\nRules:\n\n";
834 $usage .= join("\n", map { " $_" } @rules) . "\n";
835 }
836 if ( $self->{DSNParser} ) {
837 $usage .= "\n" . $self->{DSNParser}->usage();
838 }
839 $usage .= "\nOptions and values after processing arguments:\n\n";
840 foreach my $opt ( sort { $a->{long} cmp $b->{long} } @opts ) {
841 my $val = $opt->{value};
842 my $type = $opt->{type} || '';
843 my $bool = $opt->{spec} =~ m/^[\w-]+(?:\|[\w-])?!?$/;
844 $val = $bool ? ( $val ? 'TRUE' : 'FALSE' )
845 : !defined $val ? '(No value)'
846 : $type eq 'd' ? $self->{DSNParser}->as_string($val)
847 : $type =~ m/H|h/ ? join(',', sort keys %$val)
848 : $type =~ m/A|a/ ? join(',', @$val)
849 : $val;
850 $usage .= sprintf(" --%-${lcol}s %s\n", $opt->{long}, $val);
851 }
852 return $usage;
853}
854
855sub prompt_noecho {
856 shift @_ if ref $_[0] eq __PACKAGE__;
857 my ( $prompt ) = @_;
858 local $OUTPUT_AUTOFLUSH = 1;
859 print $prompt
860 or die "Cannot print: $OS_ERROR";
861 my $response;
862 eval {
863 require Term::ReadKey;
864 Term::ReadKey::ReadMode('noecho');
865 chomp($response = <STDIN>);
866 Term::ReadKey::ReadMode('normal');
867 print "\n"
868 or die "Cannot print: $OS_ERROR";
869 };
870 if ( $EVAL_ERROR ) {
871 die "Cannot read response; is Term::ReadKey installed? $EVAL_ERROR";
872 }
873 return $response;
874}
875
876sub _read_config_file {
877 my ( $self, $filename ) = @_;
878 open my $fh, "<", $filename or die "Cannot open $filename: $OS_ERROR\n";
879 my @args;
880 my $prefix = '--';
881 my $parse = 1;
882
883 LINE:
884 while ( my $line = <$fh> ) {
885 chomp $line;
886 next LINE if $line =~ m/^\s*(?:\#|\;|$)/;
887 $line =~ s/\s+#.*$//g;
888 $line =~ s/^\s+|\s+$//g;
889 if ( $line eq '--' ) {
890 $prefix = '';
891 $parse = 0;
892 next LINE;
893 }
894 if ( $parse
895 && (my($opt, $arg) = $line =~ m/^\s*([^=\s]+?)(?:\s*=\s*(.*?)\s*)?$/)
896 ) {
897 push @args, grep { defined $_ } ("$prefix$opt", $arg);
898 }
899 elsif ( $line =~ m/./ ) {
900 push @args, $line;
901 }
902 else {
903 die "Syntax error in file $filename at line $INPUT_LINE_NUMBER";
904 }
905 }
906 close $fh;
907 return @args;
908}
909
910sub read_para_after {
911 my ( $self, $file, $regex ) = @_;
912 open my $fh, "<", $file or die "Can't open $file: $OS_ERROR";
913 local $INPUT_RECORD_SEPARATOR = '';
914 my $para;
915 while ( $para = <$fh> ) {
916 next unless $para =~ m/^=pod$/m;
917 last;
918 }
919 while ( $para = <$fh> ) {
920 next unless $para =~ m/$regex/;
921 last;
922 }
923 $para = <$fh>;
924 chomp($para);
925 close $fh or die "Can't close $file: $OS_ERROR";
926 return $para;
927}
928
929sub clone {
930 my ( $self ) = @_;
931
932 my %clone = map {
933 my $hashref = $self->{$_};
934 my $val_copy = {};
935 foreach my $key ( keys %$hashref ) {
936 my $ref = ref $hashref->{$key};
937 $val_copy->{$key} = !$ref ? $hashref->{$key}
938 : $ref eq 'HASH' ? { %{$hashref->{$key}} }
939 : $ref eq 'ARRAY' ? [ @{$hashref->{$key}} ]
940 : $hashref->{$key};
941 }
942 $_ => $val_copy;
943 } qw(opts short_opts defaults);
944
945 foreach my $scalar ( qw(got_opts) ) {
946 $clone{$scalar} = $self->{$scalar};
947 }
948
949 return bless \%clone;
950}
951
952sub _parse_size {
953 my ( $self, $opt, $val ) = @_;
954
955 if ( lc($val || '') eq 'null' ) {
956 PTDEBUG && _d('NULL size for', $opt->{long});
957 $opt->{value} = 'null';
958 return;
959 }
960
961 my %factor_for = (k => 1_024, M => 1_048_576, G => 1_073_741_824);
962 my ($pre, $num, $factor) = $val =~ m/^([+-])?(\d+)([kMG])?$/;
963 if ( defined $num ) {
964 if ( $factor ) {
965 $num *= $factor_for{$factor};
966 PTDEBUG && _d('Setting option', $opt->{y},
967 'to num', $num, '* factor', $factor);
968 }
969 $opt->{value} = ($pre || '') . $num;
970 }
971 else {
972 $self->save_error("Invalid size for --$opt->{long}: $val");
973 }
974 return;
975}
976
977sub _parse_attribs {
978 my ( $self, $option, $attribs ) = @_;
979 my $types = $self->{types};
980 return $option
981 . ($attribs->{'short form'} ? '|' . $attribs->{'short form'} : '' )
982 . ($attribs->{'negatable'} ? '!' : '' )
983 . ($attribs->{'cumulative'} ? '+' : '' )
984 . ($attribs->{'type'} ? '=' . $types->{$attribs->{type}} : '' );
985}
986
987sub _parse_synopsis {
988 my ( $self, $file ) = @_;
989 $file ||= $self->{file} || __FILE__;
990 PTDEBUG && _d("Parsing SYNOPSIS in", $file);
991
992 local $INPUT_RECORD_SEPARATOR = ''; # read paragraphs
993 open my $fh, "<", $file or die "Cannot open $file: $OS_ERROR";
994 my $para;
995 1 while defined($para = <$fh>) && $para !~ m/^=head1 SYNOPSIS/;
996 die "$file does not contain a SYNOPSIS section" unless $para;
997 my @synop;
998 for ( 1..2 ) { # 1 for the usage, 2 for the description
999 my $para = <$fh>;
1000 push @synop, $para;
1001 }
1002 close $fh;
1003 PTDEBUG && _d("Raw SYNOPSIS text:", @synop);
1004 my ($usage, $desc) = @synop;
1005 die "The SYNOPSIS section in $file is not formatted properly"
1006 unless $usage && $desc;
1007
1008 $usage =~ s/^\s*Usage:\s+(.+)/$1/;
1009 chomp $usage;
1010
1011 $desc =~ s/\n/ /g;
1012 $desc =~ s/\s{2,}/ /g;
1013 $desc =~ s/\. ([A-Z][a-z])/. $1/g;
1014 $desc =~ s/\s+$//;
1015
1016 return (
1017 description => $desc,
1018 usage => $usage,
1019 );
1020};
1021
1022sub set_vars {
1023 my ($self, $file) = @_;
1024 $file ||= $self->{file} || __FILE__;
1025
1026 my %user_vars;
1027 my $user_vars = $self->has('set-vars') ? $self->get('set-vars') : undef;
1028 if ( $user_vars ) {
1029 foreach my $var_val ( @$user_vars ) {
1030 my ($var, $val) = $var_val =~ m/([^\s=]+)=(\S+)/;
1031 die "Invalid --set-vars value: $var_val\n" unless $var && $val;
1032 $user_vars{$var} = {
1033 val => $val,
1034 default => 0,
1035 };
1036 }
1037 }
1038
1039 my %default_vars;
1040 my $default_vars = $self->read_para_after($file, qr/MAGIC_set_vars/);
1041 if ( $default_vars ) {
1042 %default_vars = map {
1043 my $var_val = $_;
1044 my ($var, $val) = $var_val =~ m/([^\s=]+)=(\S+)/;
1045 die "Invalid --set-vars value: $var_val\n" unless $var && $val;
1046 $var => {
1047 val => $val,
1048 default => 1,
1049 };
1050 } split("\n", $default_vars);
1051 }
1052
1053 my %vars = (
1054 %default_vars, # first the tool's defaults
1055 %user_vars, # then the user's which overwrite the defaults
1056 );
1057 PTDEBUG && _d('--set-vars:', Dumper(\%vars));
1058 return \%vars;
1059}
1060
1061sub _d {
1062 my ($package, undef, $line) = caller 0;
1063 @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
1064 map { defined $_ ? $_ : 'undef' }
1065 @_;
1066 print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
1067}
1068
1069if ( PTDEBUG ) {
1070 print '# ', $^X, ' ', $], "\n";
1071 if ( my $uname = `uname -a` ) {
1072 $uname =~ s/\s+/ /g;
1073 print "# $uname\n";
1074 }
1075 print '# Arguments: ',
1076 join(' ', map { my $a = "_[$_]_"; $a =~ s/\n/\n# /g; $a; } @ARGV), "\n";
1077}
1078
10791;
1080}
1081# ###########################################################################
1082# End OptionParser package
1083# ###########################################################################
1084
7# ###########################################################################1085# ###########################################################################
8# This is a combination of modules and programs in one -- a runnable module.1086# This is a combination of modules and programs in one -- a runnable module.
9# http://www.perl.com/pub/a/2006/07/13/lightning-articles.html?page=last1087# http://www.perl.com/pub/a/2006/07/13/lightning-articles.html?page=last
@@ -25,6 +1103,11 @@
251103
26 @ARGV = @_; # set global ARGV for this package1104 @ARGV = @_; # set global ARGV for this package
271105
1106 my $o = OptionParser->new();
1107 $o->get_specs();
1108 $o->get_opts();
1109 $o->usage_or_errors();
1110
28 # Read all lines1111 # Read all lines
29 my @lines;1112 my @lines;
30 my %word_count;1113 my %word_count;
@@ -130,7 +1213,17 @@
1301213
131=head1 OPTIONS1214=head1 OPTIONS
1321215
133This tool does not have any command-line options.1216=over
1217
1218=item --help
1219
1220Show help and exit.
1221
1222=item --version
1223
1224Show version and exit.
1225
1226=back
1341227
135=head1 ENVIRONMENT1228=head1 ENVIRONMENT
1361229
1371230
=== modified file 'bin/pt-mext'
--- bin/pt-mext 2013-03-14 17:20:35 +0000
+++ bin/pt-mext 2013-04-04 21:05:27 +0000
@@ -4,18 +4,53 @@
4# See "COPYRIGHT, LICENSE, AND WARRANTY" at the end of this file for legal4# See "COPYRIGHT, LICENSE, AND WARRANTY" at the end of this file for legal
5# notices and disclaimers.5# notices and disclaimers.
66
7usage() {7# ###########################################################################
8 if [ "${OPT_ERR}" ]; then8# log_warn_die package
9 echo "${OPT_ERR}" >&29# This package is a copy without comments from the original. The original
10 fi10# with comments and its test file can be found in the Bazaar repository at,
11 echo "Usage: pt-mext [OPTIONS] -- COMMAND" >&211# lib/bash/log_warn_die.sh
12 echo "For more information, 'man pt-mext' or 'perldoc $0'" >&212# t/lib/bash/log_warn_die.sh
13# See https://launchpad.net/percona-toolkit for more information.
14# ###########################################################################
15
16
17set -u
18
19PTFUNCNAME=""
20PTDEBUG="${PTDEBUG:-""}"
21EXIT_STATUS=0
22
23ts() {
24 TS=$(date +%F-%T | tr ':-' '_')
25 echo "$TS $*"
26}
27
28info() {
29 [ ${OPT_VERBOSE:-3} -ge 3 ] && ts "$*"
30}
31
32log() {
33 [ ${OPT_VERBOSE:-3} -ge 2 ] && ts "$*"
34}
35
36warn() {
37 [ ${OPT_VERBOSE:-3} -ge 1 ] && ts "$*" >&2
38 EXIT_STATUS=1
39}
40
41die() {
42 ts "$*" >&2
43 EXIT_STATUS=1
13 exit 144 exit 1
14}45}
1546
16if [ -z "$1" ]; then47_d () {
17 usage;48 [ "$PTDEBUG" ] && echo "# $PTFUNCNAME: $(ts "$*")" >&2
18fi49}
50
51# ###########################################################################
52# End log_warn_die package
53# ###########################################################################
1954
20# ###########################################################################55# ###########################################################################
21# tmpdir package56# tmpdir package
@@ -58,51 +93,497 @@
58# End tmpdir package93# End tmpdir package
59# ###########################################################################94# ###########################################################################
6095
96# ###########################################################################
97# parse_options package
98# This package is a copy without comments from the original. The original
99# with comments and its test file can be found in the Bazaar repository at,
100# lib/bash/parse_options.sh
101# t/lib/bash/parse_options.sh
102# See https://launchpad.net/percona-toolkit for more information.
103# ###########################################################################
104
105
106
107
108
109set -u
110
111ARGV="" # Non-option args (probably input files)
112EXT_ARGV="" # Everything after -- (args for an external command)
113HAVE_EXT_ARGV="" # Got --, everything else is put into EXT_ARGV
114OPT_ERRS=0 # How many command line option errors
115OPT_VERSION="" # If --version was specified
116OPT_HELP="" # If --help was specified
117PO_DIR="" # Directory with program option spec files
118
119usage() {
120 local file="$1"
121
122 local usage="$(grep '^Usage: ' "$file")"
123 echo $usage
124 echo
125 echo "For more information, 'man $TOOL' or 'perldoc $file'."
126}
127
128usage_or_errors() {
129 local file="$1"
130
131 if [ "$OPT_VERSION" ]; then
132 local version=$(grep '^pt-[^ ]\+ [0-9]' "$file")
133 echo "$version"
134 return 1
135 fi
136
137 if [ "$OPT_HELP" ]; then
138 usage "$file"
139 echo
140 echo "Command line options:"
141 echo
142 perl -e '
143 use strict;
144 use warnings FATAL => qw(all);
145 my $lcol = 20; # Allow this much space for option names.
146 my $rcol = 80 - $lcol; # The terminal is assumed to be 80 chars wide.
147 my $name;
148 while ( <> ) {
149 my $line = $_;
150 chomp $line;
151 if ( $line =~ s/^long:/ --/ ) {
152 $name = $line;
153 }
154 elsif ( $line =~ s/^desc:// ) {
155 $line =~ s/ +$//mg;
156 my @lines = grep { $_ }
157 $line =~ m/(.{0,$rcol})(?:\s+|\Z)/g;
158 if ( length($name) >= $lcol ) {
159 print $name, "\n", (q{ } x $lcol);
160 }
161 else {
162 printf "%-${lcol}s", $name;
163 }
164 print join("\n" . (q{ } x $lcol), @lines);
165 print "\n";
166 }
167 }
168 ' "$PO_DIR"/*
169 echo
170 echo "Options and values after processing arguments:"
171 echo
172 (
173 cd "$PO_DIR"
174 for opt in *; do
175 local varname="OPT_$(echo "$opt" | tr a-z- A-Z_)"
176 eval local varvalue=\$$varname
177 if ! grep -q "type:" "$PO_DIR/$opt" >/dev/null; then
178 if [ "$varvalue" -a "$varvalue" = "yes" ];
179 then varvalue="TRUE"
180 else
181 varvalue="FALSE"
182 fi
183 fi
184 printf -- " --%-30s %s" "$opt" "${varvalue:-(No value)}"
185 echo
186 done
187 )
188 return 1
189 fi
190
191 if [ $OPT_ERRS -gt 0 ]; then
192 echo
193 usage "$file"
194 return 1
195 fi
196
197 return 0
198}
199
200option_error() {
201 local err="$1"
202 OPT_ERRS=$(($OPT_ERRS + 1))
203 echo "$err" >&2
204}
205
206parse_options() {
207 local file="$1"
208 shift
209
210 ARGV=""
211 EXT_ARGV=""
212 HAVE_EXT_ARGV=""
213 OPT_ERRS=0
214 OPT_VERSION=""
215 OPT_HELP=""
216 PO_DIR="$PT_TMPDIR/po"
217
218 if [ ! -d "$PO_DIR" ]; then
219 mkdir "$PO_DIR"
220 if [ $? -ne 0 ]; then
221 echo "Cannot mkdir $PO_DIR" >&2
222 exit 1
223 fi
224 fi
225
226 rm -rf "$PO_DIR"/*
227 if [ $? -ne 0 ]; then
228 echo "Cannot rm -rf $PO_DIR/*" >&2
229 exit 1
230 fi
231
232 _parse_pod "$file" # Parse POD into program option (po) spec files
233 _eval_po # Eval po into existence with default values
234
235 if [ $# -ge 2 ] && [ "$1" = "--config" ]; then
236 shift # --config
237 local user_config_files="$1"
238 shift # that ^
239 local IFS=","
240 for user_config_file in $user_config_files; do
241 _parse_config_files "$user_config_file"
242 done
243 else
244 _parse_config_files "/etc/percona-toolkit/percona-toolkit.conf" "/etc/percona-toolkit/$TOOL.conf" "$HOME/.percona-toolkit.conf" "$HOME/.$TOOL.conf"
245 fi
246
247 _parse_command_line "${@:-""}"
248}
249
250_parse_pod() {
251 local file="$1"
252
253 cat "$file" | PO_DIR="$PO_DIR" perl -ne '
254 BEGIN { $/ = ""; }
255 next unless $_ =~ m/^=head1 OPTIONS/;
256 while ( defined(my $para = <>) ) {
257 last if $para =~ m/^=head1/;
258 chomp;
259 if ( $para =~ m/^=item --(\S+)/ ) {
260 my $opt = $1;
261 my $file = "$ENV{PO_DIR}/$opt";
262 open my $opt_fh, ">", $file or die "Cannot open $file: $!";
263 print $opt_fh "long:$opt\n";
264 $para = <>;
265 chomp;
266 if ( $para =~ m/^[a-z ]+:/ ) {
267 map {
268 chomp;
269 my ($attrib, $val) = split(/: /, $_);
270 print $opt_fh "$attrib:$val\n";
271 } split(/; /, $para);
272 $para = <>;
273 chomp;
274 }
275 my ($desc) = $para =~ m/^([^?.]+)/;
276 print $opt_fh "desc:$desc.\n";
277 close $opt_fh;
278 }
279 }
280 last;
281 '
282}
283
284_eval_po() {
285 local IFS=":"
286 for opt_spec in "$PO_DIR"/*; do
287 local opt=""
288 local default_val=""
289 local neg=0
290 local size=0
291 while read key val; do
292 case "$key" in
293 long)
294 opt=$(echo $val | sed 's/-/_/g' | tr '[:lower:]' '[:upper:]')
295 ;;
296 default)
297 default_val="$val"
298 ;;
299 "short form")
300 ;;
301 type)
302 [ "$val" = "size" ] && size=1
303 ;;
304 desc)
305 ;;
306 negatable)
307 if [ "$val" = "yes" ]; then
308 neg=1
309 fi
310 ;;
311 *)
312 echo "Invalid attribute in $opt_spec: $line" >&2
313 exit 1
314 esac
315 done < "$opt_spec"
316
317 if [ -z "$opt" ]; then
318 echo "No long attribute in option spec $opt_spec" >&2
319 exit 1
320 fi
321
322 if [ $neg -eq 1 ]; then
323 if [ -z "$default_val" ] || [ "$default_val" != "yes" ]; then
324 echo "Option $opt_spec is negatable but not default: yes" >&2
325 exit 1
326 fi
327 fi
328
329 if [ $size -eq 1 -a -n "$default_val" ]; then
330 default_val=$(size_to_bytes $default_val)
331 fi
332
333 eval "OPT_${opt}"="$default_val"
334 done
335}
336
337_parse_config_files() {
338
339 for config_file in "${@:-""}"; do
340 test -f "$config_file" || continue
341
342 while read config_opt; do
343
344 echo "$config_opt" | grep '^[ ]*[^#]' >/dev/null 2>&1 || continue
345
346 config_opt="$(echo "$config_opt" | sed -e 's/^ *//g' -e 's/ *$//g' -e 's/[ ]*=[ ]*/=/' -e 's/[ ]*#.*$//')"
347
348 [ "$config_opt" = "" ] && continue
349
350 if ! [ "$HAVE_EXT_ARGV" ]; then
351 config_opt="--$config_opt"
352 fi
353
354 _parse_command_line "$config_opt"
355
356 done < "$config_file"
357
358 HAVE_EXT_ARGV="" # reset for each file
359
360 done
361}
362
363_parse_command_line() {
364 local opt=""
365 local val=""
366 local next_opt_is_val=""
367 local opt_is_ok=""
368 local opt_is_negated=""
369 local real_opt=""
370 local required_arg=""
371 local spec=""
372
373 for opt in "${@:-""}"; do
374 if [ "$opt" = "--" -o "$opt" = "----" ]; then
375 HAVE_EXT_ARGV=1
376 continue
377 fi
378 if [ "$HAVE_EXT_ARGV" ]; then
379 if [ "$EXT_ARGV" ]; then
380 EXT_ARGV="$EXT_ARGV $opt"
381 else
382 EXT_ARGV="$opt"
383 fi
384 continue
385 fi
386
387 if [ "$next_opt_is_val" ]; then
388 next_opt_is_val=""
389 if [ $# -eq 0 ] || [ $(expr "$opt" : "\-") -eq 1 ]; then
390 option_error "$real_opt requires a $required_arg argument"
391 continue
392 fi
393 val="$opt"
394 opt_is_ok=1
395 else
396 if [ $(expr "$opt" : "\-") -eq 0 ]; then
397 if [ -z "$ARGV" ]; then
398 ARGV="$opt"
399 else
400 ARGV="$ARGV $opt"
401 fi
402 continue
403 fi
404
405 real_opt="$opt"
406
407 if $(echo $opt | grep '^--no[^-]' >/dev/null); then
408 local base_opt=$(echo $opt | sed 's/^--no//')
409 if [ -f "$PT_TMPDIR/po/$base_opt" ]; then
410 opt_is_negated=1
411 opt="$base_opt"
412 else
413 opt_is_negated=""
414 opt=$(echo $opt | sed 's/^-*//')
415 fi
416 else
417 if $(echo $opt | grep '^--no-' >/dev/null); then
418 opt_is_negated=1
419 opt=$(echo $opt | sed 's/^--no-//')
420 else
421 opt_is_negated=""
422 opt=$(echo $opt | sed 's/^-*//')
423 fi
424 fi
425
426 if $(echo $opt | grep '^[a-z-][a-z-]*=' >/dev/null 2>&1); then
427 val="$(echo $opt | awk -F= '{print $2}')"
428 opt="$(echo $opt | awk -F= '{print $1}')"
429 fi
430
431 if [ -f "$PT_TMPDIR/po/$opt" ]; then
432 spec="$PT_TMPDIR/po/$opt"
433 else
434 spec=$(grep "^short form:-$opt\$" "$PT_TMPDIR"/po/* | cut -d ':' -f 1)
435 if [ -z "$spec" ]; then
436 option_error "Unknown option: $real_opt"
437 continue
438 fi
439 fi
440
441 required_arg=$(cat "$spec" | awk -F: '/^type:/{print $2}')
442 if [ "$required_arg" ]; then
443 if [ "$val" ]; then
444 opt_is_ok=1
445 else
446 next_opt_is_val=1
447 fi
448 else
449 if [ "$val" ]; then
450 option_error "Option $real_opt does not take a value"
451 continue
452 fi
453 if [ "$opt_is_negated" ]; then
454 val=""
455 else
456 val="yes"
457 fi
458 opt_is_ok=1
459 fi
460 fi
461
462 if [ "$opt_is_ok" ]; then
463 opt=$(cat "$spec" | grep '^long:' | cut -d':' -f2 | sed 's/-/_/g' | tr '[:lower:]' '[:upper:]')
464
465 if grep "^type:size" "$spec" >/dev/null; then
466 val=$(size_to_bytes $val)
467 fi
468
469 eval "OPT_$opt"="'$val'"
470
471 opt=""
472 val=""
473 next_opt_is_val=""
474 opt_is_ok=""
475 opt_is_negated=""
476 real_opt=""
477 required_arg=""
478 spec=""
479 fi
480 done
481}
482
483size_to_bytes() {
484 local size="$1"
485 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")};'
486}
487
488# ###########################################################################
489# End parse_options package
490# ###########################################################################
491
492# ###########################################################################
493# alt_cmds package
494# This package is a copy without comments from the original. The original
495# with comments and its test file can be found in the Bazaar repository at,
496# lib/bash/alt_cmds.sh
497# t/lib/bash/alt_cmds.sh
498# See https://launchpad.net/percona-toolkit for more information.
499# ###########################################################################
500
501
502set -u
503
504_seq() {
505 local i="$1"
506 awk "BEGIN { for(i=1; i<=$i; i++) print i; }"
507}
508
509_pidof() {
510 local cmd="$1"
511 if ! pidof "$cmd" 2>/dev/null; then
512 ps -eo pid,ucomm | awk -v comm="$cmd" '$2 == comm { print $1 }'
513 fi
514}
515
516_lsof() {
517 local pid="$1"
518 if ! lsof -p $pid 2>/dev/null; then
519 /bin/ls -l /proc/$pid/fd 2>/dev/null
520 fi
521}
522
523
524
525_which() {
526 if [ -x /usr/bin/which ]; then
527 /usr/bin/which "$1" 2>/dev/null | awk '{print $1}'
528 elif which which 1>/dev/null 2>&1; then
529 which "$1" 2>/dev/null | awk '{print $1}'
530 else
531 echo "$1"
532 fi
533}
534
535# ###########################################################################
536# End alt_cmds package
537# ###########################################################################
538
539TOOL="pt-mext"
540
541# Parse command line options.
61mk_tmpdir542mk_tmpdir
543parse_options "$0" "${@:-""}"
544
545if [ -z "$OPT_HELP" -a -z "$OPT_VERSION" ]; then
546 if [ -z "$EXT_ARGV" ]; then
547 option_error "No COMMAND was given."
548 fi
549fi
550
551usage_or_errors "$0"
552po_status=$?
553
554if [ $po_status -ne 0 ]; then
555 [ $OPT_ERRS -gt 0 ] && exit 1
556 exit 0
557fi
62558
63FILE="$PT_TMPDIR/mext_temp_file";559FILE="$PT_TMPDIR/mext_temp_file";
64NUM=0;560NUM=1;
65REL=0;
66
67# Command-line parsing.
68args=`getopt -u -n mext r "$@"`;
69if [ "$?" = "1" ]; then
70 usage;
71fi
72set -- $args
73for o; do
74 case "$o" in
75 -r) REL="1"; shift;;
76 --) shift; break;;
77 esac
78done
79
80if [ -z "$1" ]; then
81 usage;
82fi
83561
84# Split the output on empty lines and put each into a different file; eliminate562# Split the output on empty lines and put each into a different file; eliminate
85# lines that don't have "real" content.563# lines that don't have "real" content.
86$@ | grep -v '+' | grep -v Variable_name | sed 's/|//g' \564$EXT_ARGV | grep -v '+' | grep -v Variable_name | sed 's/|//g' \
87 | while read line; do565 | while read line; do
88 if [ "$line" = "" ]; then566 if [ "$line" = "" ]; then
89 NUM=`expr $NUM + 1`;567 NUM=$(($NUM + 1))
90 echo "" > "$FILE$NUM"568 echo "" > "$FILE$NUM"
91 fi569 fi
92 echo "$line" >> "$FILE$NUM"570 echo "$line" >> "$FILE$NUM"
93done571done
94572
95# Count how many files there are and prepare to format the output
96SPEC="%-33s %13d"573SPEC="%-33s %13d"
97AWKS=""574AWKS=""
575
576# Count how many files there are and prepare to format the output, but...
98NUM=`ls "$FILE"* | wc -l`;577NUM=`ls "$FILE"* | wc -l`;
99# The last file will be empty...578
100NUM=`expr $NUM - 3`;579# ... iterate through files 1..(N-2) because the last file is empty and
580# we join N to N+1 so also don't read the last real file.
581NUM=$((NUM - 2))
101582
102# Join each file with the next file, joining on the first field. Build a printf583# Join each file with the next file, joining on the first field. Build a printf
103# spec and awk spec at the same time.584# spec and awk spec at the same time.
104for i in `seq 0 $NUM`; do585for i in `_seq $NUM`; do
105 NEXTFILE=`expr $i + 1`;586 NEXTFILE=$(($i + 1))
106587
107 # Sort each file and eliminate empty lines, so 'join' doesn't complain.588 # Sort each file and eliminate empty lines, so 'join' doesn't complain.
108 sort "$FILE$i" | grep . > "$FILE$i.tmp"589 sort "$FILE$i" | grep . > "$FILE$i.tmp"
@@ -119,10 +600,29 @@
119 MAXLEN=`awk '{print $2}' "$FILE${NEXTFILE}" | grep -v '[^0-9]' | awk '{print length($1)}' | sort -rn | head -n1`600 MAXLEN=`awk '{print $2}' "$FILE${NEXTFILE}" | grep -v '[^0-9]' | awk '{print length($1)}' | sort -rn | head -n1`
120 mv "$FILE" "$FILE${NEXTFILE}"601 mv "$FILE" "$FILE${NEXTFILE}"
121 SPEC="$SPEC %${MAXLEN}d";602 SPEC="$SPEC %${MAXLEN}d";
122 if [ "$REL" = "1" ]; then603
123 AWKS="$AWKS, \$`expr $i + 3` - \$`expr $i + 2`";604 # The final file will contain lines like:
605 #
606 # Bytes_received 100 200 50 300
607 #
608 # For each such line in awk, $1 is the var name and $2 is the first value
609 # of the var, so these are fixed when we build AWKCMD after this loop.
610 # When i=1, we're comparing file1 to file2, and the resulting value becomes
611 # awk $3. Hence $i + 2=$3 below. Then incr and repeat for subsequent files.
612 #
613 # With --relative, the $i and awk field numbers are the same, but we print
614 # differences $3-$2, $4-$3, $5-$4 from the input line for awk fields $3, $4,
615 # and $5 respectively. Here's a table:
616 #
617 # i awk Input line fields
618 # == === =================
619 # 1 $3 $3-$2
620 # 2 $4 $4-$3
621 # 3 $5 $5-$4
622 if [ "$OPT_RELATIVE" ]; then
623 AWKS="$AWKS, \$`expr $i + 2` - \$`expr $i + 1`";
124 else624 else
125 AWKS="$AWKS, \$`expr $i + 3`";625 AWKS="$AWKS, \$$(($i + 2))";
126 fi626 fi
127done627done
128628
@@ -153,7 +653,7 @@
153653
154Get output from C<mysqladmin>:654Get output from C<mysqladmin>:
155655
156 pt-mext -r -- mysqladmin ext -i10 -c3"656 pt-mext -r -- mysqladmin ext -i10 -c3
157657
158Get output from a file:658Get output from a file:
159659
@@ -191,9 +691,19 @@
191691
192=over692=over
193693
194=item -r694=item --help
195695
196Relative: subtract each column from the previous column.696Show help and exit.
697
698=item --relative
699
700short form: -r
701
702Subtract each column from the previous column.
703
704=item --version
705
706Show version and exit.
197707
198=back708=back
199709
200710
=== modified file 'bin/pt-pmp'
--- bin/pt-pmp 2013-03-14 17:20:35 +0000
+++ bin/pt-pmp 2013-04-04 21:05:27 +0000
@@ -7,6 +7,54 @@
7TOOL="pt-pmp"7TOOL="pt-pmp"
88
9# ###########################################################################9# ###########################################################################
10# log_warn_die package
11# This package is a copy without comments from the original. The original
12# with comments and its test file can be found in the Bazaar repository at,
13# lib/bash/log_warn_die.sh
14# t/lib/bash/log_warn_die.sh
15# See https://launchpad.net/percona-toolkit for more information.
16# ###########################################################################
17
18
19set -u
20
21PTFUNCNAME=""
22PTDEBUG="${PTDEBUG:-""}"
23EXIT_STATUS=0
24
25ts() {
26 TS=$(date +%F-%T | tr ':-' '_')
27 echo "$TS $*"
28}
29
30info() {
31 [ ${OPT_VERBOSE:-3} -ge 3 ] && ts "$*"
32}
33
34log() {
35 [ ${OPT_VERBOSE:-3} -ge 2 ] && ts "$*"
36}
37
38warn() {
39 [ ${OPT_VERBOSE:-3} -ge 1 ] && ts "$*" >&2
40 EXIT_STATUS=1
41}
42
43die() {
44 ts "$*" >&2
45 EXIT_STATUS=1
46 exit 1
47}
48
49_d () {
50 [ "$PTDEBUG" ] && echo "# $PTFUNCNAME: $(ts "$*")" >&2
51}
52
53# ###########################################################################
54# End log_warn_die package
55# ###########################################################################
56
57# ###########################################################################
10# tmpdir package58# tmpdir package
11# This package is a copy without comments from the original. The original59# This package is a copy without comments from the original. The original
12# with comments and its test file can be found in the Bazaar repository at,60# with comments and its test file can be found in the Bazaar repository at,
@@ -47,17 +95,451 @@
47# End tmpdir package95# End tmpdir package
48# ###########################################################################96# ###########################################################################
4997
98# ###########################################################################
99# parse_options package
100# This package is a copy without comments from the original. The original
101# with comments and its test file can be found in the Bazaar repository at,
102# lib/bash/parse_options.sh
103# t/lib/bash/parse_options.sh
104# See https://launchpad.net/percona-toolkit for more information.
105# ###########################################################################
106
107
108
109
110
111set -u
112
113ARGV="" # Non-option args (probably input files)
114EXT_ARGV="" # Everything after -- (args for an external command)
115HAVE_EXT_ARGV="" # Got --, everything else is put into EXT_ARGV
116OPT_ERRS=0 # How many command line option errors
117OPT_VERSION="" # If --version was specified
118OPT_HELP="" # If --help was specified
119PO_DIR="" # Directory with program option spec files
120
121usage() {
122 local file="$1"
123
124 local usage="$(grep '^Usage: ' "$file")"
125 echo $usage
126 echo
127 echo "For more information, 'man $TOOL' or 'perldoc $file'."
128}
129
130usage_or_errors() {
131 local file="$1"
132
133 if [ "$OPT_VERSION" ]; then
134 local version=$(grep '^pt-[^ ]\+ [0-9]' "$file")
135 echo "$version"
136 return 1
137 fi
138
139 if [ "$OPT_HELP" ]; then
140 usage "$file"
141 echo
142 echo "Command line options:"
143 echo
144 perl -e '
145 use strict;
146 use warnings FATAL => qw(all);
147 my $lcol = 20; # Allow this much space for option names.
148 my $rcol = 80 - $lcol; # The terminal is assumed to be 80 chars wide.
149 my $name;
150 while ( <> ) {
151 my $line = $_;
152 chomp $line;
153 if ( $line =~ s/^long:/ --/ ) {
154 $name = $line;
155 }
156 elsif ( $line =~ s/^desc:// ) {
157 $line =~ s/ +$//mg;
158 my @lines = grep { $_ }
159 $line =~ m/(.{0,$rcol})(?:\s+|\Z)/g;
160 if ( length($name) >= $lcol ) {
161 print $name, "\n", (q{ } x $lcol);
162 }
163 else {
164 printf "%-${lcol}s", $name;
165 }
166 print join("\n" . (q{ } x $lcol), @lines);
167 print "\n";
168 }
169 }
170 ' "$PO_DIR"/*
171 echo
172 echo "Options and values after processing arguments:"
173 echo
174 (
175 cd "$PO_DIR"
176 for opt in *; do
177 local varname="OPT_$(echo "$opt" | tr a-z- A-Z_)"
178 eval local varvalue=\$$varname
179 if ! grep -q "type:" "$PO_DIR/$opt" >/dev/null; then
180 if [ "$varvalue" -a "$varvalue" = "yes" ];
181 then varvalue="TRUE"
182 else
183 varvalue="FALSE"
184 fi
185 fi
186 printf -- " --%-30s %s" "$opt" "${varvalue:-(No value)}"
187 echo
188 done
189 )
190 return 1
191 fi
192
193 if [ $OPT_ERRS -gt 0 ]; then
194 echo
195 usage "$file"
196 return 1
197 fi
198
199 return 0
200}
201
202option_error() {
203 local err="$1"
204 OPT_ERRS=$(($OPT_ERRS + 1))
205 echo "$err" >&2
206}
207
208parse_options() {
209 local file="$1"
210 shift
211
212 ARGV=""
213 EXT_ARGV=""
214 HAVE_EXT_ARGV=""
215 OPT_ERRS=0
216 OPT_VERSION=""
217 OPT_HELP=""
218 PO_DIR="$PT_TMPDIR/po"
219
220 if [ ! -d "$PO_DIR" ]; then
221 mkdir "$PO_DIR"
222 if [ $? -ne 0 ]; then
223 echo "Cannot mkdir $PO_DIR" >&2
224 exit 1
225 fi
226 fi
227
228 rm -rf "$PO_DIR"/*
229 if [ $? -ne 0 ]; then
230 echo "Cannot rm -rf $PO_DIR/*" >&2
231 exit 1
232 fi
233
234 _parse_pod "$file" # Parse POD into program option (po) spec files
235 _eval_po # Eval po into existence with default values
236
237 if [ $# -ge 2 ] && [ "$1" = "--config" ]; then
238 shift # --config
239 local user_config_files="$1"
240 shift # that ^
241 local IFS=","
242 for user_config_file in $user_config_files; do
243 _parse_config_files "$user_config_file"
244 done
245 else
246 _parse_config_files "/etc/percona-toolkit/percona-toolkit.conf" "/etc/percona-toolkit/$TOOL.conf" "$HOME/.percona-toolkit.conf" "$HOME/.$TOOL.conf"
247 fi
248
249 _parse_command_line "${@:-""}"
250}
251
252_parse_pod() {
253 local file="$1"
254
255 cat "$file" | PO_DIR="$PO_DIR" perl -ne '
256 BEGIN { $/ = ""; }
257 next unless $_ =~ m/^=head1 OPTIONS/;
258 while ( defined(my $para = <>) ) {
259 last if $para =~ m/^=head1/;
260 chomp;
261 if ( $para =~ m/^=item --(\S+)/ ) {
262 my $opt = $1;
263 my $file = "$ENV{PO_DIR}/$opt";
264 open my $opt_fh, ">", $file or die "Cannot open $file: $!";
265 print $opt_fh "long:$opt\n";
266 $para = <>;
267 chomp;
268 if ( $para =~ m/^[a-z ]+:/ ) {
269 map {
270 chomp;
271 my ($attrib, $val) = split(/: /, $_);
272 print $opt_fh "$attrib:$val\n";
273 } split(/; /, $para);
274 $para = <>;
275 chomp;
276 }
277 my ($desc) = $para =~ m/^([^?.]+)/;
278 print $opt_fh "desc:$desc.\n";
279 close $opt_fh;
280 }
281 }
282 last;
283 '
284}
285
286_eval_po() {
287 local IFS=":"
288 for opt_spec in "$PO_DIR"/*; do
289 local opt=""
290 local default_val=""
291 local neg=0
292 local size=0
293 while read key val; do
294 case "$key" in
295 long)
296 opt=$(echo $val | sed 's/-/_/g' | tr '[:lower:]' '[:upper:]')
297 ;;
298 default)
299 default_val="$val"
300 ;;
301 "short form")
302 ;;
303 type)
304 [ "$val" = "size" ] && size=1
305 ;;
306 desc)
307 ;;
308 negatable)
309 if [ "$val" = "yes" ]; then
310 neg=1
311 fi
312 ;;
313 *)
314 echo "Invalid attribute in $opt_spec: $line" >&2
315 exit 1
316 esac
317 done < "$opt_spec"
318
319 if [ -z "$opt" ]; then
320 echo "No long attribute in option spec $opt_spec" >&2
321 exit 1
322 fi
323
324 if [ $neg -eq 1 ]; then
325 if [ -z "$default_val" ] || [ "$default_val" != "yes" ]; then
326 echo "Option $opt_spec is negatable but not default: yes" >&2
327 exit 1
328 fi
329 fi
330
331 if [ $size -eq 1 -a -n "$default_val" ]; then
332 default_val=$(size_to_bytes $default_val)
333 fi
334
335 eval "OPT_${opt}"="$default_val"
336 done
337}
338
339_parse_config_files() {
340
341 for config_file in "${@:-""}"; do
342 test -f "$config_file" || continue
343
344 while read config_opt; do
345
346 echo "$config_opt" | grep '^[ ]*[^#]' >/dev/null 2>&1 || continue
347
348 config_opt="$(echo "$config_opt" | sed -e 's/^ *//g' -e 's/ *$//g' -e 's/[ ]*=[ ]*/=/' -e 's/[ ]*#.*$//')"
349
350 [ "$config_opt" = "" ] && continue
351
352 if ! [ "$HAVE_EXT_ARGV" ]; then
353 config_opt="--$config_opt"
354 fi
355
356 _parse_command_line "$config_opt"
357
358 done < "$config_file"
359
360 HAVE_EXT_ARGV="" # reset for each file
361
362 done
363}
364
365_parse_command_line() {
366 local opt=""
367 local val=""
368 local next_opt_is_val=""
369 local opt_is_ok=""
370 local opt_is_negated=""
371 local real_opt=""
372 local required_arg=""
373 local spec=""
374
375 for opt in "${@:-""}"; do
376 if [ "$opt" = "--" -o "$opt" = "----" ]; then
377 HAVE_EXT_ARGV=1
378 continue
379 fi
380 if [ "$HAVE_EXT_ARGV" ]; then
381 if [ "$EXT_ARGV" ]; then
382 EXT_ARGV="$EXT_ARGV $opt"
383 else
384 EXT_ARGV="$opt"
385 fi
386 continue
387 fi
388
389 if [ "$next_opt_is_val" ]; then
390 next_opt_is_val=""
391 if [ $# -eq 0 ] || [ $(expr "$opt" : "\-") -eq 1 ]; then
392 option_error "$real_opt requires a $required_arg argument"
393 continue
394 fi
395 val="$opt"
396 opt_is_ok=1
397 else
398 if [ $(expr "$opt" : "\-") -eq 0 ]; then
399 if [ -z "$ARGV" ]; then
400 ARGV="$opt"
401 else
402 ARGV="$ARGV $opt"
403 fi
404 continue
405 fi
406
407 real_opt="$opt"
408
409 if $(echo $opt | grep '^--no[^-]' >/dev/null); then
410 local base_opt=$(echo $opt | sed 's/^--no//')
411 if [ -f "$PT_TMPDIR/po/$base_opt" ]; then
412 opt_is_negated=1
413 opt="$base_opt"
414 else
415 opt_is_negated=""
416 opt=$(echo $opt | sed 's/^-*//')
417 fi
418 else
419 if $(echo $opt | grep '^--no-' >/dev/null); then
420 opt_is_negated=1
421 opt=$(echo $opt | sed 's/^--no-//')
422 else
423 opt_is_negated=""
424 opt=$(echo $opt | sed 's/^-*//')
425 fi
426 fi
427
428 if $(echo $opt | grep '^[a-z-][a-z-]*=' >/dev/null 2>&1); then
429 val="$(echo $opt | awk -F= '{print $2}')"
430 opt="$(echo $opt | awk -F= '{print $1}')"
431 fi
432
433 if [ -f "$PT_TMPDIR/po/$opt" ]; then
434 spec="$PT_TMPDIR/po/$opt"
435 else
436 spec=$(grep "^short form:-$opt\$" "$PT_TMPDIR"/po/* | cut -d ':' -f 1)
437 if [ -z "$spec" ]; then
438 option_error "Unknown option: $real_opt"
439 continue
440 fi
441 fi
442
443 required_arg=$(cat "$spec" | awk -F: '/^type:/{print $2}')
444 if [ "$required_arg" ]; then
445 if [ "$val" ]; then
446 opt_is_ok=1
447 else
448 next_opt_is_val=1
449 fi
450 else
451 if [ "$val" ]; then
452 option_error "Option $real_opt does not take a value"
453 continue
454 fi
455 if [ "$opt_is_negated" ]; then
456 val=""
457 else
458 val="yes"
459 fi
460 opt_is_ok=1
461 fi
462 fi
463
464 if [ "$opt_is_ok" ]; then
465 opt=$(cat "$spec" | grep '^long:' | cut -d':' -f2 | sed 's/-/_/g' | tr '[:lower:]' '[:upper:]')
466
467 if grep "^type:size" "$spec" >/dev/null; then
468 val=$(size_to_bytes $val)
469 fi
470
471 eval "OPT_$opt"="'$val'"
472
473 opt=""
474 val=""
475 next_opt_is_val=""
476 opt_is_ok=""
477 opt_is_negated=""
478 real_opt=""
479 required_arg=""
480 spec=""
481 fi
482 done
483}
484
485size_to_bytes() {
486 local size="$1"
487 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")};'
488}
489
490# ###########################################################################
491# End parse_options package
492# ###########################################################################
493
494# ###########################################################################
495# alt_cmds package
496# This package is a copy without comments from the original. The original
497# with comments and its test file can be found in the Bazaar repository at,
498# lib/bash/alt_cmds.sh
499# t/lib/bash/alt_cmds.sh
500# See https://launchpad.net/percona-toolkit for more information.
501# ###########################################################################
502
503
504set -u
505
506_seq() {
507 local i="$1"
508 awk "BEGIN { for(i=1; i<=$i; i++) print i; }"
509}
510
511_pidof() {
512 local cmd="$1"
513 if ! pidof "$cmd" 2>/dev/null; then
514 ps -eo pid,ucomm | awk -v comm="$cmd" '$2 == comm { print $1 }'
515 fi
516}
517
518_lsof() {
519 local pid="$1"
520 if ! lsof -p $pid 2>/dev/null; then
521 /bin/ls -l /proc/$pid/fd 2>/dev/null
522 fi
523}
524
525
526
527_which() {
528 if [ -x /usr/bin/which ]; then
529 /usr/bin/which "$1" 2>/dev/null | awk '{print $1}'
530 elif which which 1>/dev/null 2>&1; then
531 which "$1" 2>/dev/null | awk '{print $1}'
532 else
533 echo "$1"
534 fi
535}
536
537# ###########################################################################
538# End alt_cmds package
539# ###########################################################################
540
50set +u541set +u
51542
52usage() {
53 if [ "${OPT_ERR}" ]; then
54 echo "${OPT_ERR}" >&2
55 fi
56 echo "Usage: pt-pmp [OPTIONS] [FILES]" >&2
57 echo "For more information, 'man pt-pmp' or 'perldoc $0'" >&2
58 exit 1
59}
60
61# Actually does the aggregation. The arguments are the max number of functions543# Actually does the aggregation. The arguments are the max number of functions
62# to aggregate, and the files to read. If maxlen=0, it means infinity. We have544# to aggregate, and the files to read. If maxlen=0, it means infinity. We have
63# to pass the maxlen argument into this function to make maxlen testable.545# to pass the maxlen argument into this function to make maxlen testable.
@@ -153,70 +635,35 @@
153635
154# The main program to run.636# The main program to run.
155main() {637main() {
156638 local output_file="${OPT_SAVE_SAMPLES:-"$PT_TMPDIR/percona-toolkit"}"
157 # Get command-line options639
158 for o; do640 if [ -z "$ARGV" ]; then
159 case "${o}" in641 # There are no files to analyze, so we'll make one.
160 --)642 if [ -z "$OPT_PID" ]; then
161 shift; break;643 OPT_PID=$(pidof -s "$OPT_BINARY" 2>/dev/null);
162 ;;644 if [ -z "$OPT_PID" ]; then
163 --help)645 OPT_PID=$(pgrep -o -x "$OPT_BINARY" 2>/dev/null)
164 usage;
165 ;;
166 -b)
167 shift; OPT_b="${1}"; shift;
168 ;;
169 -i)
170 shift; OPT_i="${1}"; shift;
171 ;;
172 -k)
173 shift; OPT_k="${1}"; shift;
174 ;;
175 -l)
176 shift; OPT_l="${1}"; shift;
177 ;;
178 -p)
179 shift; OPT_p="${1}"; shift;
180 ;;
181 -s)
182 shift; OPT_s="${1}"; shift;
183 ;;
184 -*)
185 OPT_ERR="Unknown option ${o}."
186 usage
187 ;;
188 esac
189 done
190 export OPT_i="${OPT_i:-1}";
191 export OPT_k="${OPT_k:-}";
192 export OPT_l="${OPT_l:-0}";
193 export OPT_b="${OPT_b:-mysqld}";
194 export OPT_p="${OPT_p:-}";
195 export OPT_s="${OPT_s:-0}";
196
197 if [ -z "${1}" ]; then
198 # There's no file to analyze, so we'll make one.
199 if [ -z "${OPT_p}" ]; then
200 OPT_p=$(pidof -s "${OPT_b}" 2>/dev/null);
201 if [ -z "${OPT_p}" ]; then
202 OPT_p=$(pgrep -o -x "${OPT_b}" 2>/dev/null)
203 fi646 fi
204 if [ -z "${OPT_p}" ]; then647 if [ -z "$OPT_PID" ]; then
205 OPT_p=$(ps -eaf | grep "${OPT_b}" | grep -v grep | awk '{print $2}' | head -n1);648 OPT_PID=$(ps -eaf | grep "$OPT_BINARY" | grep -v grep | awk '{print $2}' | head -n1);
206 fi649 fi
207 fi650 fi
208 date;651 date
209 for x in $(seq 1 $OPT_i); do652 for x in $(_seq $OPT_ITERATIONS); do
210 gdb -ex "set pagination 0" -ex "thread apply all bt" -batch -p $OPT_p >> "${OPT_k:-$PT_TMPDIR/percona-toolkit}"653 gdb -ex "set pagination 0" \
211 date +'TS %N.%s %F %T' >> "${OPT_k:-$PT_TMPDIR/percona-toolkit}"654 -ex "thread apply all bt" \
212 sleep $OPT_s655 -batch \
656 -p $OPT_PID \
657 >> "$output_file"
658 date +'TS %N.%s %F %T' >> "$output_file"
659 sleep $OPT_INTERVAL
213 done660 done
214 fi661 fi
215662
216 if [ $# -eq 0 ]; then663 if [ -z "$ARGV" ]; then
217 aggregate_stacktrace "${OPT_l}" "${OPT_k:-$PT_TMPDIR/percona-toolkit}"664 aggregate_stacktrace "$OPT_LINES" "$output_file"
218 else665 else
219 aggregate_stacktrace "${OPT_l}" "$@"666 aggregate_stacktrace "$OPT_LINES" $ARGV
220 fi667 fi
221}668}
222669
@@ -224,8 +671,23 @@
224# possible to include without executing, and thus test.671# possible to include without executing, and thus test.
225if [ "${0##*/}" = "$TOOL" ] \672if [ "${0##*/}" = "$TOOL" ] \
226 || [ "${0##*/}" = "bash" -a "${_:-""}" = "$0" ]; then673 || [ "${0##*/}" = "bash" -a "${_:-""}" = "$0" ]; then
674
227 mk_tmpdir675 mk_tmpdir
228 main "$@"676
677 parse_options "$0" "${@:-""}"
678 if [ -z "$OPT_HELP" -a -z "$OPT_VERSION" ]; then
679 # Validate options
680 :
681 fi
682 usage_or_errors "$0"
683 po_status=$?
684 if [ $po_status -ne 0 ]; then
685 [ $OPT_ERRS -gt 0 ] && exit 1
686 exit 0
687 fi
688
689 main $ARGV
690
229 rm_tmpdir691 rm_tmpdir
230fi692fi
231693
@@ -289,33 +751,51 @@
289751
290=head1 OPTIONS752=head1 OPTIONS
291753
292Options must precede files on the command line.
293
294=over754=over
295755
296=item -b BINARY756=item --binary
297757
298Which binary to trace (default mysqld)758short form: -b; type: string; default: mysqld
299759
300=item -i ITERATIONS760Which binary to trace.
301761
302How many traces to gather and aggregate (default 1)762=item --help
303763
304=item -k KEEPFILE764Show help and exit.
305765
306Keep the raw traces in this file after aggregation766=item --interval
307767
308=item -l NUMBER768short form: -s; type: int; default: 0
309769
310Aggregate only first NUMBER functions; 0=infinity (default 0)770Number of seconds to sleep between L<"--iterations">.
311771
312=item -p PID772=item --iterations
313773
314Process ID of the process to trace; overrides -b774short form: -i; type: int; default: 1
315775
316=item -s SLEEPTIME776How many traces to gather and aggregate.
317777
318Number of seconds to sleep between iterations (default 0)778=item --lines
779
780short form: -l; type: int; default: 0
781
782Aggregate only first specified number of many functions; 0=infinity.
783
784=item --pid
785
786short form: -p; type: int
787
788Process ID of the process to trace; overrides L<"--binary">.
789
790=item --save-samples
791
792short form: -k; type: string
793
794Keep the raw traces in this file after aggregation.
795
796=item --version
797
798Show version and exit.
319799
320=back800=back
321801
322802
=== modified file 'bin/pt-sift'
--- bin/pt-sift 2013-03-14 17:20:35 +0000
+++ bin/pt-sift 2013-04-04 21:05:27 +0000
@@ -4,15 +4,54 @@
4# See "COPYRIGHT, LICENSE, AND WARRANTY" at the end of this file for legal4# See "COPYRIGHT, LICENSE, AND WARRANTY" at the end of this file for legal
5# notices and disclaimers.5# notices and disclaimers.
66
7usage() {7# ###########################################################################
8 if [ "${OPT_ERR}" ]; then8# log_warn_die package
9 echo "Error: $OPT_ERR" >&29# This package is a copy without comments from the original. The original
10 fi10# with comments and its test file can be found in the Bazaar repository at,
11 echo "Usage: pt-sift FILE|PREFIX|DIRECTORY" >&211# lib/bash/log_warn_die.sh
12 echo "For more information, 'man pt-sift' or 'perldoc $0'." >&212# t/lib/bash/log_warn_die.sh
13# See https://launchpad.net/percona-toolkit for more information.
14# ###########################################################################
15
16
17set -u
18
19PTFUNCNAME=""
20PTDEBUG="${PTDEBUG:-""}"
21EXIT_STATUS=0
22
23ts() {
24 TS=$(date +%F-%T | tr ':-' '_')
25 echo "$TS $*"
26}
27
28info() {
29 [ ${OPT_VERBOSE:-3} -ge 3 ] && ts "$*"
30}
31
32log() {
33 [ ${OPT_VERBOSE:-3} -ge 2 ] && ts "$*"
34}
35
36warn() {
37 [ ${OPT_VERBOSE:-3} -ge 1 ] && ts "$*" >&2
38 EXIT_STATUS=1
39}
40
41die() {
42 ts "$*" >&2
43 EXIT_STATUS=1
13 exit 144 exit 1
14}45}
1546
47_d () {
48 [ "$PTDEBUG" ] && echo "# $PTFUNCNAME: $(ts "$*")" >&2
49}
50
51# ###########################################################################
52# End log_warn_die package
53# ###########################################################################
54
16# ###########################################################################55# ###########################################################################
17# tmpdir package56# tmpdir package
18# This package is a copy without comments from the original. The original57# This package is a copy without comments from the original. The original
@@ -55,6 +94,402 @@
55# ###########################################################################94# ###########################################################################
5695
57# ###########################################################################96# ###########################################################################
97# parse_options package
98# This package is a copy without comments from the original. The original
99# with comments and its test file can be found in the Bazaar repository at,
100# lib/bash/parse_options.sh
101# t/lib/bash/parse_options.sh
102# See https://launchpad.net/percona-toolkit for more information.
103# ###########################################################################
104
105
106
107
108
109set -u
110
111ARGV="" # Non-option args (probably input files)
112EXT_ARGV="" # Everything after -- (args for an external command)
113HAVE_EXT_ARGV="" # Got --, everything else is put into EXT_ARGV
114OPT_ERRS=0 # How many command line option errors
115OPT_VERSION="" # If --version was specified
116OPT_HELP="" # If --help was specified
117PO_DIR="" # Directory with program option spec files
118
119usage() {
120 local file="$1"
121
122 local usage="$(grep '^Usage: ' "$file")"
123 echo $usage
124 echo
125 echo "For more information, 'man $TOOL' or 'perldoc $file'."
126}
127
128usage_or_errors() {
129 local file="$1"
130
131 if [ "$OPT_VERSION" ]; then
132 local version=$(grep '^pt-[^ ]\+ [0-9]' "$file")
133 echo "$version"
134 return 1
135 fi
136
137 if [ "$OPT_HELP" ]; then
138 usage "$file"
139 echo
140 echo "Command line options:"
141 echo
142 perl -e '
143 use strict;
144 use warnings FATAL => qw(all);
145 my $lcol = 20; # Allow this much space for option names.
146 my $rcol = 80 - $lcol; # The terminal is assumed to be 80 chars wide.
147 my $name;
148 while ( <> ) {
149 my $line = $_;
150 chomp $line;
151 if ( $line =~ s/^long:/ --/ ) {
152 $name = $line;
153 }
154 elsif ( $line =~ s/^desc:// ) {
155 $line =~ s/ +$//mg;
156 my @lines = grep { $_ }
157 $line =~ m/(.{0,$rcol})(?:\s+|\Z)/g;
158 if ( length($name) >= $lcol ) {
159 print $name, "\n", (q{ } x $lcol);
160 }
161 else {
162 printf "%-${lcol}s", $name;
163 }
164 print join("\n" . (q{ } x $lcol), @lines);
165 print "\n";
166 }
167 }
168 ' "$PO_DIR"/*
169 echo
170 echo "Options and values after processing arguments:"
171 echo
172 (
173 cd "$PO_DIR"
174 for opt in *; do
175 local varname="OPT_$(echo "$opt" | tr a-z- A-Z_)"
176 eval local varvalue=\$$varname
177 if ! grep -q "type:" "$PO_DIR/$opt" >/dev/null; then
178 if [ "$varvalue" -a "$varvalue" = "yes" ];
179 then varvalue="TRUE"
180 else
181 varvalue="FALSE"
182 fi
183 fi
184 printf -- " --%-30s %s" "$opt" "${varvalue:-(No value)}"
185 echo
186 done
187 )
188 return 1
189 fi
190
191 if [ $OPT_ERRS -gt 0 ]; then
192 echo
193 usage "$file"
194 return 1
195 fi
196
197 return 0
198}
199
200option_error() {
201 local err="$1"
202 OPT_ERRS=$(($OPT_ERRS + 1))
203 echo "$err" >&2
204}
205
206parse_options() {
207 local file="$1"
208 shift
209
210 ARGV=""
211 EXT_ARGV=""
212 HAVE_EXT_ARGV=""
213 OPT_ERRS=0
214 OPT_VERSION=""
215 OPT_HELP=""
216 PO_DIR="$PT_TMPDIR/po"
217
218 if [ ! -d "$PO_DIR" ]; then
219 mkdir "$PO_DIR"
220 if [ $? -ne 0 ]; then
221 echo "Cannot mkdir $PO_DIR" >&2
222 exit 1
223 fi
224 fi
225
226 rm -rf "$PO_DIR"/*
227 if [ $? -ne 0 ]; then
228 echo "Cannot rm -rf $PO_DIR/*" >&2
229 exit 1
230 fi
231
232 _parse_pod "$file" # Parse POD into program option (po) spec files
233 _eval_po # Eval po into existence with default values
234
235 if [ $# -ge 2 ] && [ "$1" = "--config" ]; then
236 shift # --config
237 local user_config_files="$1"
238 shift # that ^
239 local IFS=","
240 for user_config_file in $user_config_files; do
241 _parse_config_files "$user_config_file"
242 done
243 else
244 _parse_config_files "/etc/percona-toolkit/percona-toolkit.conf" "/etc/percona-toolkit/$TOOL.conf" "$HOME/.percona-toolkit.conf" "$HOME/.$TOOL.conf"
245 fi
246
247 _parse_command_line "${@:-""}"
248}
249
250_parse_pod() {
251 local file="$1"
252
253 cat "$file" | PO_DIR="$PO_DIR" perl -ne '
254 BEGIN { $/ = ""; }
255 next unless $_ =~ m/^=head1 OPTIONS/;
256 while ( defined(my $para = <>) ) {
257 last if $para =~ m/^=head1/;
258 chomp;
259 if ( $para =~ m/^=item --(\S+)/ ) {
260 my $opt = $1;
261 my $file = "$ENV{PO_DIR}/$opt";
262 open my $opt_fh, ">", $file or die "Cannot open $file: $!";
263 print $opt_fh "long:$opt\n";
264 $para = <>;
265 chomp;
266 if ( $para =~ m/^[a-z ]+:/ ) {
267 map {
268 chomp;
269 my ($attrib, $val) = split(/: /, $_);
270 print $opt_fh "$attrib:$val\n";
271 } split(/; /, $para);
272 $para = <>;
273 chomp;
274 }
275 my ($desc) = $para =~ m/^([^?.]+)/;
276 print $opt_fh "desc:$desc.\n";
277 close $opt_fh;
278 }
279 }
280 last;
281 '
282}
283
284_eval_po() {
285 local IFS=":"
286 for opt_spec in "$PO_DIR"/*; do
287 local opt=""
288 local default_val=""
289 local neg=0
290 local size=0
291 while read key val; do
292 case "$key" in
293 long)
294 opt=$(echo $val | sed 's/-/_/g' | tr '[:lower:]' '[:upper:]')
295 ;;
296 default)
297 default_val="$val"
298 ;;
299 "short form")
300 ;;
301 type)
302 [ "$val" = "size" ] && size=1
303 ;;
304 desc)
305 ;;
306 negatable)
307 if [ "$val" = "yes" ]; then
308 neg=1
309 fi
310 ;;
311 *)
312 echo "Invalid attribute in $opt_spec: $line" >&2
313 exit 1
314 esac
315 done < "$opt_spec"
316
317 if [ -z "$opt" ]; then
318 echo "No long attribute in option spec $opt_spec" >&2
319 exit 1
320 fi
321
322 if [ $neg -eq 1 ]; then
323 if [ -z "$default_val" ] || [ "$default_val" != "yes" ]; then
324 echo "Option $opt_spec is negatable but not default: yes" >&2
325 exit 1
326 fi
327 fi
328
329 if [ $size -eq 1 -a -n "$default_val" ]; then
330 default_val=$(size_to_bytes $default_val)
331 fi
332
333 eval "OPT_${opt}"="$default_val"
334 done
335}
336
337_parse_config_files() {
338
339 for config_file in "${@:-""}"; do
340 test -f "$config_file" || continue
341
342 while read config_opt; do
343
344 echo "$config_opt" | grep '^[ ]*[^#]' >/dev/null 2>&1 || continue
345
346 config_opt="$(echo "$config_opt" | sed -e 's/^ *//g' -e 's/ *$//g' -e 's/[ ]*=[ ]*/=/' -e 's/[ ]*#.*$//')"
347
348 [ "$config_opt" = "" ] && continue
349
350 if ! [ "$HAVE_EXT_ARGV" ]; then
351 config_opt="--$config_opt"
352 fi
353
354 _parse_command_line "$config_opt"
355
356 done < "$config_file"
357
358 HAVE_EXT_ARGV="" # reset for each file
359
360 done
361}
362
363_parse_command_line() {
364 local opt=""
365 local val=""
366 local next_opt_is_val=""
367 local opt_is_ok=""
368 local opt_is_negated=""
369 local real_opt=""
370 local required_arg=""
371 local spec=""
372
373 for opt in "${@:-""}"; do
374 if [ "$opt" = "--" -o "$opt" = "----" ]; then
375 HAVE_EXT_ARGV=1
376 continue
377 fi
378 if [ "$HAVE_EXT_ARGV" ]; then
379 if [ "$EXT_ARGV" ]; then
380 EXT_ARGV="$EXT_ARGV $opt"
381 else
382 EXT_ARGV="$opt"
383 fi
384 continue
385 fi
386
387 if [ "$next_opt_is_val" ]; then
388 next_opt_is_val=""
389 if [ $# -eq 0 ] || [ $(expr "$opt" : "\-") -eq 1 ]; then
390 option_error "$real_opt requires a $required_arg argument"
391 continue
392 fi
393 val="$opt"
394 opt_is_ok=1
395 else
396 if [ $(expr "$opt" : "\-") -eq 0 ]; then
397 if [ -z "$ARGV" ]; then
398 ARGV="$opt"
399 else
400 ARGV="$ARGV $opt"
401 fi
402 continue
403 fi
404
405 real_opt="$opt"
406
407 if $(echo $opt | grep '^--no[^-]' >/dev/null); then
408 local base_opt=$(echo $opt | sed 's/^--no//')
409 if [ -f "$PT_TMPDIR/po/$base_opt" ]; then
410 opt_is_negated=1
411 opt="$base_opt"
412 else
413 opt_is_negated=""
414 opt=$(echo $opt | sed 's/^-*//')
415 fi
416 else
417 if $(echo $opt | grep '^--no-' >/dev/null); then
418 opt_is_negated=1
419 opt=$(echo $opt | sed 's/^--no-//')
420 else
421 opt_is_negated=""
422 opt=$(echo $opt | sed 's/^-*//')
423 fi
424 fi
425
426 if $(echo $opt | grep '^[a-z-][a-z-]*=' >/dev/null 2>&1); then
427 val="$(echo $opt | awk -F= '{print $2}')"
428 opt="$(echo $opt | awk -F= '{print $1}')"
429 fi
430
431 if [ -f "$PT_TMPDIR/po/$opt" ]; then
432 spec="$PT_TMPDIR/po/$opt"
433 else
434 spec=$(grep "^short form:-$opt\$" "$PT_TMPDIR"/po/* | cut -d ':' -f 1)
435 if [ -z "$spec" ]; then
436 option_error "Unknown option: $real_opt"
437 continue
438 fi
439 fi
440
441 required_arg=$(cat "$spec" | awk -F: '/^type:/{print $2}')
442 if [ "$required_arg" ]; then
443 if [ "$val" ]; then
444 opt_is_ok=1
445 else
446 next_opt_is_val=1
447 fi
448 else
449 if [ "$val" ]; then
450 option_error "Option $real_opt does not take a value"
451 continue
452 fi
453 if [ "$opt_is_negated" ]; then
454 val=""
455 else
456 val="yes"
457 fi
458 opt_is_ok=1
459 fi
460 fi
461
462 if [ "$opt_is_ok" ]; then
463 opt=$(cat "$spec" | grep '^long:' | cut -d':' -f2 | sed 's/-/_/g' | tr '[:lower:]' '[:upper:]')
464
465 if grep "^type:size" "$spec" >/dev/null; then
466 val=$(size_to_bytes $val)
467 fi
468
469 eval "OPT_$opt"="'$val'"
470
471 opt=""
472 val=""
473 next_opt_is_val=""
474 opt_is_ok=""
475 opt_is_negated=""
476 real_opt=""
477 required_arg=""
478 spec=""
479 fi
480 done
481}
482
483size_to_bytes() {
484 local size="$1"
485 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")};'
486}
487
488# ###########################################################################
489# End parse_options package
490# ###########################################################################
491
492# ###########################################################################
58# Global variables493# Global variables
59# ###########################################################################494# ###########################################################################
60495
@@ -106,11 +541,6 @@
106 # prefix. The outcome of this block of code should be that BASEDIR is the541 # prefix. The outcome of this block of code should be that BASEDIR is the
107 # directory where the files live, without a trailing slash; and PREFIX is542 # directory where the files live, without a trailing slash; and PREFIX is
108 # either empty or a timestamp, such as "2011_02_08_16_58_07".543 # either empty or a timestamp, such as "2011_02_08_16_58_07".
109 if [ $# -gt 1 ]; then
110 OPT_ERR="Specify only one PREFIX or DIR"
111 usage
112 fi
113
114 if [ $# -eq 1 ]; then544 if [ $# -eq 1 ]; then
115 if [ -d "$1" ]; then545 if [ -d "$1" ]; then
116 BASEDIR="$1"546 BASEDIR="$1"
@@ -147,9 +577,6 @@
147 fi 577 fi
148 done578 done
149579
150 # Make a secure tmpdir.
151 mk_tmpdir
152
153 # We need to generate a list of timestamps, and ask the user to choose one if580 # We need to generate a list of timestamps, and ask the user to choose one if
154 # there is no PREFIX yet. NOTE: we rely on the "-df" files here.581 # there is no PREFIX yet. NOTE: we rely on the "-df" files here.
155 (582 (
@@ -580,15 +1007,31 @@
580 ;;1007 ;;
581 esac1008 esac
582 done1009 done
583
584 rm_tmpdir
585}1010}
5861011
587# Execute the program if it was not included from another file. This makes it1012# Execute the program if it was not included from another file. This makes it
588# possible to include without executing, and thus test.1013# possible to include without executing, and thus test.
589if [ "${0##*/}" = "$TOOL" ] \1014if [ "${0##*/}" = "$TOOL" ] \
590 || [ "${0##*/}" = "bash" -a "${_:-""}" = "$0" ]; then1015 || [ "${0##*/}" = "bash" -a "${_:-""}" = "$0" ]; then
591 main "${@:-""}"1016
1017 mk_tmpdir
1018
1019 parse_options "$0" "${@:-""}"
1020 if [ -z "$OPT_HELP" -a -z "$OPT_VERSION" ]; then
1021 if [ $# -gt 1 ]; then
1022 option_error "Specify only one PREFIX or DIR"
1023 fi
1024 fi
1025 usage_or_errors "$0"
1026 po_status=$?
1027 if [ $po_status -ne 0 ]; then
1028 [ $OPT_ERRS -gt 0 ] && exit 1
1029 exit 0
1030 fi
1031
1032 main "${@:-""}"
1033
1034 rm_tmpdir
592fi1035fi
5931036
594# ############################################################################1037# ############################################################################
@@ -644,47 +1087,47 @@
6441087
645=over1088=over
6461089
647=item d1090=item * d
6481091
649Sets the action to start the L<pt-diskstats> tool on the sample's disk1092Sets the action to start the L<pt-diskstats> tool on the sample's disk
650performance statistics.1093performance statistics.
6511094
652=item i1095=item * i
6531096
654Sets the action to view the first INNODB STATUS sample in less.1097Sets the action to view the first INNODB STATUS sample in less.
6551098
656=item m1099=item * m
6571100
658Displays the first 4 samples of SHOW STATUS counters side by side with the1101Displays the first 4 samples of SHOW STATUS counters side by side with the
659L<pt-mext> tool.1102L<pt-mext> tool.
6601103
661=item n1104=item * n
6621105
663Summarizes the first sample of netstat data in two ways: by originating host,1106Summarizes the first sample of netstat data in two ways: by originating host,
664and by connection state.1107and by connection state.
6651108
666=item j1109=item * j
6671110
668Select the next timestamp as the active sample.1111Select the next timestamp as the active sample.
6691112
670=item k1113=item * k
6711114
672Select the previous timestamp as the active sample.1115Select the previous timestamp as the active sample.
6731116
674=item q1117=item * q
6751118
676Quit the program.1119Quit the program.
6771120
678=item 11121=item * 1
6791122
680Sets the action for each sample to the default, which is to view a summary1123Sets the action for each sample to the default, which is to view a summary
681of the sample.1124of the sample.
6821125
683=item 01126=item * 0
6841127
685Sets the action to just list the files in the sample.1128Sets the action to just list the files in the sample.
6861129
687=item *1130=item * *
6881131
689Sets the action to view all of the sample's files in the less program.1132Sets the action to view all of the sample's files in the less program.
6901133
@@ -692,7 +1135,17 @@
6921135
693=head1 OPTIONS1136=head1 OPTIONS
6941137
695This tool does not have any command-line options.1138=over
1139
1140=item --help
1141
1142Show help and exit.
1143
1144=item --version
1145
1146Show version and exit.
1147
1148=back
6961149
697=head1 ENVIRONMENT1150=head1 ENVIRONMENT
6981151
6991152
=== modified file 't/pt-mext/pt-mext.t'
--- t/pt-mext/pt-mext.t 2011-08-02 21:14:06 +0000
+++ t/pt-mext/pt-mext.t 2013-04-04 21:05:27 +0000
@@ -9,7 +9,7 @@
9use strict;9use strict;
10use warnings FATAL => 'all';10use warnings FATAL => 'all';
11use English qw(-no_match_vars);11use English qw(-no_match_vars);
12use Test::More tests => 1;12use Test::More;
1313
14use PerconaTest;14use PerconaTest;
1515
@@ -19,7 +19,27 @@
19 'It runs'19 'It runs'
20);20);
2121
22my $cmd = "$trunk/bin/pt-mext";
23my $sample = "$trunk/t/pt-mext/samples";
24
25ok(
26 no_diff(
27 "$cmd -- cat $sample/mext-001.txt",
28 "t/pt-mext/samples/mext-001-result.txt",
29 ),
30 "mext-001"
31) or diag($test_diff);
32
33ok(
34 no_diff(
35 "$cmd -r -- cat $sample/mext-002.txt",
36 "t/pt-mext/samples/mext-002-result.txt",
37 ),
38 "mext-002 -r"
39) or diag($test_diff);
40
22# #############################################################################41# #############################################################################
23# Done.42# Done.
24# #############################################################################43# #############################################################################
44done_testing;
25exit;45exit;
2646
=== modified file 't/pt-pmp/pt-pmp.t'
--- t/pt-pmp/pt-pmp.t 2013-02-04 17:04:30 +0000
+++ t/pt-pmp/pt-pmp.t 2013-04-04 21:05:27 +0000
@@ -25,7 +25,7 @@
25 "t/pt-pmp/samples/$outfile",25 "t/pt-pmp/samples/$outfile",
26 ),26 ),
27 "$file"27 "$file"
28 );28 ) or diag($test_diff);
29}29}
30closedir $dh;30closedir $dh;
3131
@@ -35,6 +35,6 @@
35 "t/pt-pmp/samples/stacktrace003-limit2.out",35 "t/pt-pmp/samples/stacktrace003-limit2.out",
36 ),36 ),
37 "Limit 2 (stacktrace003-limit2.out)"37 "Limit 2 (stacktrace003-limit2.out)"
38);38) or diag($test_diff);
3939
40done_testing;40done_testing;

Subscribers

People subscribed via source and target branches