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
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;

Subscribers

People subscribed via source and target branches