[-]
[+]
|
Changed |
percona-toolkit.changes
|
|
[-]
[+]
|
Changed |
percona-toolkit.spec
^
|
|
[-]
[+]
|
Changed |
percona-toolkit-2.2.2.tar.gz/Changelog
^
|
@@ -1,5 +1,21 @@
Changelog for Percona Toolkit
+v2.2.2 released 2013-04-24
+
+ * Added --show-all to pt-query-digest
+ * Added --recursion-method=cluster to pt-table-checksum
+ * Fixed bug 1127450: pt-archiver --bulk-insert may corrupt data
+ * Fixed bug 1163372: pt-heartbeat --utc --check always returns 0
+ * Fixed bug 1156901: pt-query-digest --processlist reports duplicate queries for replication thread
+ * Fixed bug 1160338: pt-query-digest 2.2 prints unwanted debug info on tcpdump parsing errors
+ * Fixed bug 1160918: pt-query-digest 2.2 prints too many string values
+ * Fixed bug 1156867: pt-stalk prints the wrong variable name in verbose mode when --function is used
+ * Fixed bug 1081733: pt-stalk plugins can't access the real --prefix
+ * Fixed bug 1099845: pt-table-checksum pxc same_node function incorrectly uses wsrep_sst_receive_address
+ * Fixed bug 821502: Some tools don't have --help or --version
+ * Fixed bug 947893: Some tools use @@hostname without /*!50038*/
+ * Fixed bug 1082406: An explicitly set wsrep_node_incoming_address may make SHOW STATUS LIKE 'wsrep_incoming_addresses' return a portless address
+
v2.2.1 released 2013-03-14
* Official support for MySQL 5.6
|
[-]
[+]
|
Changed |
percona-toolkit-2.2.2.tar.gz/Makefile.PL
^
|
@@ -2,7 +2,7 @@
WriteMakefile(
NAME => 'percona-toolkit',
- VERSION => '2.2.1',
+ VERSION => '2.2.2',
EXE_FILES => [ <bin/*> ],
MAN1PODS => {
'docs/percona-toolkit.pod' => 'blib/man1/percona-toolkit.1p',
|
[-]
[+]
|
Changed |
percona-toolkit-2.2.2.tar.gz/bin/pt-align
^
|
@@ -4,6 +4,1084 @@
# See "COPYRIGHT, LICENSE, AND WARRANTY" at the end of this file for legal
# notices and disclaimers.
+use strict;
+use warnings FATAL => 'all';
+
+# This tool is "fat-packed": most of its dependent modules are embedded
+# in this file. Setting %INC to this file for each module makes Perl aware
+# of this so it will not try to load the module from @INC. See the tool's
+# documentation for a full list of dependencies.
+BEGIN {
+ $INC{$_} = __FILE__ for map { (my $pkg = "$_.pm") =~ s!::!/!g; $pkg } (qw(
+ OptionParser
+ ));
+}
+
+# ###########################################################################
+# OptionParser package
+# This package is a copy without comments from the original. The original
+# with comments and its test file can be found in the Bazaar repository at,
+# lib/OptionParser.pm
+# t/lib/OptionParser.t
+# See https://launchpad.net/percona-toolkit for more information.
+# ###########################################################################
+{
+package OptionParser;
+
+use strict;
+use warnings FATAL => 'all';
+use English qw(-no_match_vars);
+use constant PTDEBUG => $ENV{PTDEBUG} || 0;
+
+use List::Util qw(max);
+use Getopt::Long;
+use Data::Dumper;
+
+my $POD_link_re = '[LC]<"?([^">]+)"?>';
+
+sub new {
+ my ( $class, %args ) = @_;
+ my @required_args = qw();
+ foreach my $arg ( @required_args ) {
+ die "I need a $arg argument" unless $args{$arg};
+ }
+
+ my ($program_name) = $PROGRAM_NAME =~ m/([.A-Za-z-]+)$/;
+ $program_name ||= $PROGRAM_NAME;
+ my $home = $ENV{HOME} || $ENV{HOMEPATH} || $ENV{USERPROFILE} || '.';
+
+ my %attributes = (
+ 'type' => 1,
+ 'short form' => 1,
+ 'group' => 1,
+ 'default' => 1,
+ 'cumulative' => 1,
+ 'negatable' => 1,
+ );
+
+ my $self = {
+ head1 => 'OPTIONS', # These args are used internally
+ skip_rules => 0, # to instantiate another Option-
+ item => '--(.*)', # Parser obj that parses the
+ attributes => \%attributes, # DSN OPTIONS section. Tools
+ parse_attributes => \&_parse_attribs, # don't tinker with these args.
+
+ %args,
+
+ strict => 1, # disabled by a special rule
+ program_name => $program_name,
+ opts => {},
+ got_opts => 0,
+ short_opts => {},
+ defaults => {},
+ groups => {},
+ allowed_groups => {},
+ errors => [],
+ rules => [], # desc of rules for --help
+ mutex => [], # rule: opts are mutually exclusive
+ atleast1 => [], # rule: at least one opt is required
+ disables => {}, # rule: opt disables other opts
+ defaults_to => {}, # rule: opt defaults to value of other opt
+ DSNParser => undef,
+ default_files => [
+ "/etc/percona-toolkit/percona-toolkit.conf",
+ "/etc/percona-toolkit/$program_name.conf",
+ "$home/.percona-toolkit.conf",
+ "$home/.$program_name.conf",
+ ],
+ types => {
+ string => 's', # standard Getopt type
+ int => 'i', # standard Getopt type
+ float => 'f', # standard Getopt type
+ Hash => 'H', # hash, formed from a comma-separated list
+ hash => 'h', # hash as above, but only if a value is given
+ Array => 'A', # array, similar to Hash
+ array => 'a', # array, similar to hash
+ DSN => 'd', # DSN
+ size => 'z', # size with kMG suffix (powers of 2^10)
+ time => 'm', # time, with an optional suffix of s/h/m/d
+ },
+ };
+
+ return bless $self, $class;
+}
+
+sub get_specs {
+ my ( $self, $file ) = @_;
+ $file ||= $self->{file} || __FILE__;
+ my @specs = $self->_pod_to_specs($file);
+ $self->_parse_specs(@specs);
+
+ open my $fh, "<", $file or die "Cannot open $file: $OS_ERROR";
+ my $contents = do { local $/ = undef; <$fh> };
+ close $fh;
+ if ( $contents =~ m/^=head1 DSN OPTIONS/m ) {
+ PTDEBUG && _d('Parsing DSN OPTIONS');
+ my $dsn_attribs = {
+ dsn => 1,
+ copy => 1,
+ };
+ my $parse_dsn_attribs = sub {
+ my ( $self, $option, $attribs ) = @_;
+ map {
+ my $val = $attribs->{$_};
+ if ( $val ) {
+ $val = $val eq 'yes' ? 1
+ : $val eq 'no' ? 0
+ : $val;
+ $attribs->{$_} = $val;
+ }
+ } keys %$attribs;
+ return {
+ key => $option,
+ %$attribs,
+ };
+ };
+ my $dsn_o = new OptionParser(
+ description => 'DSN OPTIONS',
+ head1 => 'DSN OPTIONS',
+ dsn => 0, # XXX don't infinitely recurse!
+ item => '\* (.)', # key opts are a single character
+ skip_rules => 1, # no rules before opts
+ attributes => $dsn_attribs,
+ parse_attributes => $parse_dsn_attribs,
+ );
+ my @dsn_opts = map {
+ my $opts = {
+ key => $_->{spec}->{key},
+ dsn => $_->{spec}->{dsn},
+ copy => $_->{spec}->{copy},
+ desc => $_->{desc},
+ };
+ $opts;
+ } $dsn_o->_pod_to_specs($file);
+ $self->{DSNParser} = DSNParser->new(opts => \@dsn_opts);
+ }
+
+ if ( $contents =~ m/^=head1 VERSION\n\n^(.+)$/m ) {
+ $self->{version} = $1;
+ PTDEBUG && _d($self->{version});
+ }
+
+ return;
+}
+
+sub DSNParser {
+ my ( $self ) = @_;
+ return $self->{DSNParser};
+};
+
+sub get_defaults_files {
+ my ( $self ) = @_;
+ return @{$self->{default_files}};
+}
+
+sub _pod_to_specs {
+ my ( $self, $file ) = @_;
+ $file ||= $self->{file} || __FILE__;
+ open my $fh, '<', $file or die "Cannot open $file: $OS_ERROR";
+
+ my @specs = ();
+ my @rules = ();
+ my $para;
+
+ local $INPUT_RECORD_SEPARATOR = '';
+ while ( $para = <$fh> ) {
+ next unless $para =~ m/^=head1 $self->{head1}/;
+ last;
+ }
+
+ while ( $para = <$fh> ) {
+ last if $para =~ m/^=over/;
+ next if $self->{skip_rules};
+ chomp $para;
+ $para =~ s/\s+/ /g;
+ $para =~ s/$POD_link_re/$1/go;
+ PTDEBUG && _d('Option rule:', $para);
+ push @rules, $para;
+ }
+
+ die "POD has no $self->{head1} section" unless $para;
+
+ do {
+ if ( my ($option) = $para =~ m/^=item $self->{item}/ ) {
+ chomp $para;
+ PTDEBUG && _d($para);
+ my %attribs;
+
+ $para = <$fh>; # read next paragraph, possibly attributes
+
+ if ( $para =~ m/: / ) { # attributes
+ $para =~ s/\s+\Z//g;
+ %attribs = map {
+ my ( $attrib, $val) = split(/: /, $_);
+ die "Unrecognized attribute for --$option: $attrib"
+ unless $self->{attributes}->{$attrib};
+ ($attrib, $val);
+ } split(/; /, $para);
+ if ( $attribs{'short form'} ) {
+ $attribs{'short form'} =~ s/-//;
+ }
+ $para = <$fh>; # read next paragraph, probably short help desc
+ }
+ else {
+ PTDEBUG && _d('Option has no attributes');
+ }
+
+ $para =~ s/\s+\Z//g;
+ $para =~ s/\s+/ /g;
+ $para =~ s/$POD_link_re/$1/go;
+
+ $para =~ s/\.(?:\n.*| [A-Z].*|\Z)//s;
+ PTDEBUG && _d('Short help:', $para);
+
+ die "No description after option spec $option" if $para =~ m/^=item/;
+
+ if ( my ($base_option) = $option =~ m/^\[no\](.*)/ ) {
+ $option = $base_option;
+ $attribs{'negatable'} = 1;
+ }
+
+ push @specs, {
+ spec => $self->{parse_attributes}->($self, $option, \%attribs),
+ desc => $para
+ . (defined $attribs{default} ? " (default $attribs{default})" : ''),
+ group => ($attribs{'group'} ? $attribs{'group'} : 'default'),
+ };
+ }
+ while ( $para = <$fh> ) {
+ last unless $para;
+ if ( $para =~ m/^=head1/ ) {
+ $para = undef; # Can't 'last' out of a do {} block.
+ last;
+ }
+ last if $para =~ m/^=item /;
+ }
+ } while ( $para );
+
+ die "No valid specs in $self->{head1}" unless @specs;
+
+ close $fh;
+ return @specs, @rules;
+}
+
+sub _parse_specs {
+ my ( $self, @specs ) = @_;
+ my %disables; # special rule that requires deferred checking
+
+ foreach my $opt ( @specs ) {
+ if ( ref $opt ) { # It's an option spec, not a rule.
+ PTDEBUG && _d('Parsing opt spec:',
+ map { ($_, '=>', $opt->{$_}) } keys %$opt);
+
+ my ( $long, $short ) = $opt->{spec} =~ m/^([\w-]+)(?:\|([^!+=]*))?/;
+ if ( !$long ) {
+ die "Cannot parse long option from spec $opt->{spec}";
+ }
+ $opt->{long} = $long;
+
+ die "Duplicate long option --$long" if exists $self->{opts}->{$long};
+ $self->{opts}->{$long} = $opt;
+
+ if ( length $long == 1 ) {
+ PTDEBUG && _d('Long opt', $long, 'looks like short opt');
+ $self->{short_opts}->{$long} = $long;
+ }
+
+ if ( $short ) {
+ die "Duplicate short option -$short"
+ if exists $self->{short_opts}->{$short};
+ $self->{short_opts}->{$short} = $long;
+ $opt->{short} = $short;
+ }
+ else {
+ $opt->{short} = undef;
+ }
+
+ $opt->{is_negatable} = $opt->{spec} =~ m/!/ ? 1 : 0;
+ $opt->{is_cumulative} = $opt->{spec} =~ m/\+/ ? 1 : 0;
+ $opt->{is_required} = $opt->{desc} =~ m/required/ ? 1 : 0;
+
+ $opt->{group} ||= 'default';
+ $self->{groups}->{ $opt->{group} }->{$long} = 1;
+
+ $opt->{value} = undef;
+ $opt->{got} = 0;
+
+ my ( $type ) = $opt->{spec} =~ m/=(.)/;
+ $opt->{type} = $type;
+ PTDEBUG && _d($long, 'type:', $type);
+
+
+ $opt->{spec} =~ s/=./=s/ if ( $type && $type =~ m/[HhAadzm]/ );
+
+ if ( (my ($def) = $opt->{desc} =~ m/default\b(?: ([^)]+))?/) ) {
+ $self->{defaults}->{$long} = defined $def ? $def : 1;
+ PTDEBUG && _d($long, 'default:', $def);
+ }
+
+ if ( $long eq 'config' ) {
+ $self->{defaults}->{$long} = join(',', $self->get_defaults_files());
+ }
+
+ if ( (my ($dis) = $opt->{desc} =~ m/(disables .*)/) ) {
+ $disables{$long} = $dis;
+ PTDEBUG && _d('Deferring check of disables rule for', $opt, $dis);
+ }
+
+ $self->{opts}->{$long} = $opt;
+ }
+ else { # It's an option rule, not a spec.
+ PTDEBUG && _d('Parsing rule:', $opt);
+ push @{$self->{rules}}, $opt;
+ my @participants = $self->_get_participants($opt);
+ my $rule_ok = 0;
+
+ if ( $opt =~ m/mutually exclusive|one and only one/ ) {
+ $rule_ok = 1;
+ push @{$self->{mutex}}, \@participants;
+ PTDEBUG && _d(@participants, 'are mutually exclusive');
+ }
+ if ( $opt =~ m/at least one|one and only one/ ) {
+ $rule_ok = 1;
+ push @{$self->{atleast1}}, \@participants;
+ PTDEBUG && _d(@participants, 'require at least one');
+ }
+ if ( $opt =~ m/default to/ ) {
+ $rule_ok = 1;
+ $self->{defaults_to}->{$participants[0]} = $participants[1];
+ PTDEBUG && _d($participants[0], 'defaults to', $participants[1]);
+ }
+ if ( $opt =~ m/restricted to option groups/ ) {
+ $rule_ok = 1;
+ my ($groups) = $opt =~ m/groups ([\w\s\,]+)/;
+ my @groups = split(',', $groups);
+ %{$self->{allowed_groups}->{$participants[0]}} = map {
+ s/\s+//;
+ $_ => 1;
+ } @groups;
+ }
+ if( $opt =~ m/accepts additional command-line arguments/ ) {
+ $rule_ok = 1;
+ $self->{strict} = 0;
+ PTDEBUG && _d("Strict mode disabled by rule");
+ }
+
+ die "Unrecognized option rule: $opt" unless $rule_ok;
+ }
+ }
+
+ foreach my $long ( keys %disables ) {
+ my @participants = $self->_get_participants($disables{$long});
+ $self->{disables}->{$long} = \@participants;
+ PTDEBUG && _d('Option', $long, 'disables', @participants);
+ }
+
+ return;
+}
+
+sub _get_participants {
+ my ( $self, $str ) = @_;
+ my @participants;
+ foreach my $long ( $str =~ m/--(?:\[no\])?([\w-]+)/g ) {
+ die "Option --$long does not exist while processing rule $str"
+ unless exists $self->{opts}->{$long};
+ push @participants, $long;
+ }
+ PTDEBUG && _d('Participants for', $str, ':', @participants);
+ return @participants;
+}
+
+sub opts {
+ my ( $self ) = @_;
+ my %opts = %{$self->{opts}};
+ return %opts;
+}
+
+sub short_opts {
+ my ( $self ) = @_;
+ my %short_opts = %{$self->{short_opts}};
+ return %short_opts;
+}
+
+sub set_defaults {
+ my ( $self, %defaults ) = @_;
+ $self->{defaults} = {};
+ foreach my $long ( keys %defaults ) {
+ die "Cannot set default for nonexistent option $long"
+ unless exists $self->{opts}->{$long};
+ $self->{defaults}->{$long} = $defaults{$long};
+ PTDEBUG && _d('Default val for', $long, ':', $defaults{$long});
+ }
+ return;
+}
+
+sub get_defaults {
+ my ( $self ) = @_;
+ return $self->{defaults};
+}
+
+sub get_groups {
+ my ( $self ) = @_;
+ return $self->{groups};
+}
+
+sub _set_option {
+ my ( $self, $opt, $val ) = @_;
+ my $long = exists $self->{opts}->{$opt} ? $opt
+ : exists $self->{short_opts}->{$opt} ? $self->{short_opts}->{$opt}
+ : die "Getopt::Long gave a nonexistent option: $opt";
+
+ $opt = $self->{opts}->{$long};
+ if ( $opt->{is_cumulative} ) {
+ $opt->{value}++;
+ }
+ else {
+ $opt->{value} = $val;
+ }
+ $opt->{got} = 1;
+ PTDEBUG && _d('Got option', $long, '=', $val);
+}
+
+sub get_opts {
+ my ( $self ) = @_;
+
+ foreach my $long ( keys %{$self->{opts}} ) {
+ $self->{opts}->{$long}->{got} = 0;
+ $self->{opts}->{$long}->{value}
+ = exists $self->{defaults}->{$long} ? $self->{defaults}->{$long}
+ : $self->{opts}->{$long}->{is_cumulative} ? 0
+ : undef;
+ }
+ $self->{got_opts} = 0;
+
+ $self->{errors} = [];
+
+ if ( @ARGV && $ARGV[0] eq "--config" ) {
+ shift @ARGV;
+ $self->_set_option('config', shift @ARGV);
+ }
+ if ( $self->has('config') ) {
+ my @extra_args;
+ foreach my $filename ( split(',', $self->get('config')) ) {
+ eval {
+ push @extra_args, $self->_read_config_file($filename);
+ };
+ if ( $EVAL_ERROR ) {
+ if ( $self->got('config') ) {
+ die $EVAL_ERROR;
+ }
+ elsif ( PTDEBUG ) {
+ _d($EVAL_ERROR);
+ }
+ }
+ }
+ unshift @ARGV, @extra_args;
+ }
+
+ Getopt::Long::Configure('no_ignore_case', 'bundling');
+ GetOptions(
+ map { $_->{spec} => sub { $self->_set_option(@_); } }
+ grep { $_->{long} ne 'config' } # --config is handled specially above.
+ values %{$self->{opts}}
+ ) or $self->save_error('Error parsing options');
+
+ if ( exists $self->{opts}->{version} && $self->{opts}->{version}->{got} ) {
+ if ( $self->{version} ) {
+ print $self->{version}, "\n";
+ }
+ else {
+ print "Error parsing version. See the VERSION section of the tool's documentation.\n";
+ }
+ exit 1;
+ }
+
+ if ( @ARGV && $self->{strict} ) {
+ $self->save_error("Unrecognized command-line options @ARGV");
+ }
+
+ foreach my $mutex ( @{$self->{mutex}} ) {
+ my @set = grep { $self->{opts}->{$_}->{got} } @$mutex;
+ if ( @set > 1 ) {
+ my $err = join(', ', map { "--$self->{opts}->{$_}->{long}" }
+ @{$mutex}[ 0 .. scalar(@$mutex) - 2] )
+ . ' and --'.$self->{opts}->{$mutex->[-1]}->{long}
+ . ' are mutually exclusive.';
+ $self->save_error($err);
+ }
+ }
+
+ foreach my $required ( @{$self->{atleast1}} ) {
+ my @set = grep { $self->{opts}->{$_}->{got} } @$required;
+ if ( @set == 0 ) {
+ my $err = join(', ', map { "--$self->{opts}->{$_}->{long}" }
+ @{$required}[ 0 .. scalar(@$required) - 2] )
+ .' or --'.$self->{opts}->{$required->[-1]}->{long};
+ $self->save_error("Specify at least one of $err");
+ }
+ }
+
+ $self->_check_opts( keys %{$self->{opts}} );
+ $self->{got_opts} = 1;
+ return;
+}
+
+sub _check_opts {
+ my ( $self, @long ) = @_;
+ my $long_last = scalar @long;
+ while ( @long ) {
+ foreach my $i ( 0..$#long ) {
+ my $long = $long[$i];
+ next unless $long;
+ my $opt = $self->{opts}->{$long};
+ if ( $opt->{got} ) {
+ if ( exists $self->{disables}->{$long} ) {
+ my @disable_opts = @{$self->{disables}->{$long}};
+ map { $self->{opts}->{$_}->{value} = undef; } @disable_opts;
+ PTDEBUG && _d('Unset options', @disable_opts,
+ 'because', $long,'disables them');
+ }
+
+ if ( exists $self->{allowed_groups}->{$long} ) {
+
+ my @restricted_groups = grep {
+ !exists $self->{allowed_groups}->{$long}->{$_}
+ } keys %{$self->{groups}};
+
+ my @restricted_opts;
+ foreach my $restricted_group ( @restricted_groups ) {
+ RESTRICTED_OPT:
+ foreach my $restricted_opt (
+ keys %{$self->{groups}->{$restricted_group}} )
+ {
+ next RESTRICTED_OPT if $restricted_opt eq $long;
+ push @restricted_opts, $restricted_opt
+ if $self->{opts}->{$restricted_opt}->{got};
+ }
+ }
+
+ if ( @restricted_opts ) {
+ my $err;
+ if ( @restricted_opts == 1 ) {
+ $err = "--$restricted_opts[0]";
+ }
+ else {
+ $err = join(', ',
+ map { "--$self->{opts}->{$_}->{long}" }
+ grep { $_ }
+ @restricted_opts[0..scalar(@restricted_opts) - 2]
+ )
+ . ' or --'.$self->{opts}->{$restricted_opts[-1]}->{long};
+ }
+ $self->save_error("--$long is not allowed with $err");
+ }
+ }
+
+ }
+ elsif ( $opt->{is_required} ) {
+ $self->save_error("Required option --$long must be specified");
+ }
+
+ $self->_validate_type($opt);
+ if ( $opt->{parsed} ) {
+ delete $long[$i];
+ }
+ else {
+ PTDEBUG && _d('Temporarily failed to parse', $long);
+ }
+ }
+
+ die "Failed to parse options, possibly due to circular dependencies"
+ if @long == $long_last;
+ $long_last = @long;
+ }
+
+ return;
+}
+
+sub _validate_type {
+ my ( $self, $opt ) = @_;
+ return unless $opt;
+
+ if ( !$opt->{type} ) {
+ $opt->{parsed} = 1;
+ return;
+ }
+
+ my $val = $opt->{value};
+
+ if ( $val && $opt->{type} eq 'm' ) { # type time
+ PTDEBUG && _d('Parsing option', $opt->{long}, 'as a time value');
+ my ( $prefix, $num, $suffix ) = $val =~ m/([+-]?)(\d+)([a-z])?$/;
+ if ( !$suffix ) {
+ my ( $s ) = $opt->{desc} =~ m/\(suffix (.)\)/;
+ $suffix = $s || 's';
+ PTDEBUG && _d('No suffix given; using', $suffix, 'for',
+ $opt->{long}, '(value:', $val, ')');
+ }
+ if ( $suffix =~ m/[smhd]/ ) {
+ $val = $suffix eq 's' ? $num # Seconds
+ : $suffix eq 'm' ? $num * 60 # Minutes
+ : $suffix eq 'h' ? $num * 3600 # Hours
+ : $num * 86400; # Days
+ $opt->{value} = ($prefix || '') . $val;
+ PTDEBUG && _d('Setting option', $opt->{long}, 'to', $val);
+ }
+ else {
+ $self->save_error("Invalid time suffix for --$opt->{long}");
+ }
+ }
+ elsif ( $val && $opt->{type} eq 'd' ) { # type DSN
+ PTDEBUG && _d('Parsing option', $opt->{long}, 'as a DSN');
+ my $prev = {};
+ my $from_key = $self->{defaults_to}->{ $opt->{long} };
+ if ( $from_key ) {
+ PTDEBUG && _d($opt->{long}, 'DSN copies from', $from_key, 'DSN');
+ if ( $self->{opts}->{$from_key}->{parsed} ) {
+ $prev = $self->{opts}->{$from_key}->{value};
+ }
+ else {
+ PTDEBUG && _d('Cannot parse', $opt->{long}, 'until',
+ $from_key, 'parsed');
+ return;
+ }
+ }
+ my $defaults = $self->{DSNParser}->parse_options($self);
+ $opt->{value} = $self->{DSNParser}->parse($val, $prev, $defaults);
+ }
+ elsif ( $val && $opt->{type} eq 'z' ) { # type size
+ PTDEBUG && _d('Parsing option', $opt->{long}, 'as a size value');
+ $self->_parse_size($opt, $val);
+ }
+ elsif ( $opt->{type} eq 'H' || (defined $val && $opt->{type} eq 'h') ) {
+ $opt->{value} = { map { $_ => 1 } split(/(?<!\\),\s*/, ($val || '')) };
+ }
+ elsif ( $opt->{type} eq 'A' || (defined $val && $opt->{type} eq 'a') ) {
+ $opt->{value} = [ split(/(?<!\\),\s*/, ($val || '')) ];
+ }
+ else {
+ PTDEBUG && _d('Nothing to validate for option',
+ $opt->{long}, 'type', $opt->{type}, 'value', $val);
+ }
+
+ $opt->{parsed} = 1;
+ return;
+}
+
+sub get {
+ my ( $self, $opt ) = @_;
+ my $long = (length $opt == 1 ? $self->{short_opts}->{$opt} : $opt);
+ die "Option $opt does not exist"
+ unless $long && exists $self->{opts}->{$long};
+ return $self->{opts}->{$long}->{value};
+}
+
+sub got {
+ my ( $self, $opt ) = @_;
+ my $long = (length $opt == 1 ? $self->{short_opts}->{$opt} : $opt);
+ die "Option $opt does not exist"
+ unless $long && exists $self->{opts}->{$long};
+ return $self->{opts}->{$long}->{got};
+}
+
+sub has {
+ my ( $self, $opt ) = @_;
+ my $long = (length $opt == 1 ? $self->{short_opts}->{$opt} : $opt);
+ return defined $long ? exists $self->{opts}->{$long} : 0;
+}
+
+sub set {
+ my ( $self, $opt, $val ) = @_;
+ my $long = (length $opt == 1 ? $self->{short_opts}->{$opt} : $opt);
+ die "Option $opt does not exist"
+ unless $long && exists $self->{opts}->{$long};
+ $self->{opts}->{$long}->{value} = $val;
+ return;
+}
+
+sub save_error {
+ my ( $self, $error ) = @_;
+ push @{$self->{errors}}, $error;
+ return;
+}
+
+sub errors {
+ my ( $self ) = @_;
+ return $self->{errors};
+}
+
+sub usage {
+ my ( $self ) = @_;
+ warn "No usage string is set" unless $self->{usage}; # XXX
+ return "Usage: " . ($self->{usage} || '') . "\n";
+}
+
+sub descr {
+ my ( $self ) = @_;
+ warn "No description string is set" unless $self->{description}; # XXX
+ my $descr = ($self->{description} || $self->{program_name} || '')
+ . " For more details, please use the --help option, "
+ . "or try 'perldoc $PROGRAM_NAME' "
+ . "for complete documentation.";
+ $descr = join("\n", $descr =~ m/(.{0,80})(?:\s+|$)/g)
+ unless $ENV{DONT_BREAK_LINES};
+ $descr =~ s/ +$//mg;
+ return $descr;
+}
+
+sub usage_or_errors {
+ my ( $self, $file, $return ) = @_;
+ $file ||= $self->{file} || __FILE__;
+
+ if ( !$self->{description} || !$self->{usage} ) {
+ PTDEBUG && _d("Getting description and usage from SYNOPSIS in", $file);
+ my %synop = $self->_parse_synopsis($file);
+ $self->{description} ||= $synop{description};
+ $self->{usage} ||= $synop{usage};
+ PTDEBUG && _d("Description:", $self->{description},
+ "\nUsage:", $self->{usage});
+ }
+
+ if ( $self->{opts}->{help}->{got} ) {
+ print $self->print_usage() or die "Cannot print usage: $OS_ERROR";
+ exit 0 unless $return;
+ }
+ elsif ( scalar @{$self->{errors}} ) {
+ print $self->print_errors() or die "Cannot print errors: $OS_ERROR";
+ exit 1 unless $return;
+ }
+
+ return;
+}
+
+sub print_errors {
+ my ( $self ) = @_;
+ my $usage = $self->usage() . "\n";
+ if ( (my @errors = @{$self->{errors}}) ) {
+ $usage .= join("\n * ", 'Errors in command-line arguments:', @errors)
+ . "\n";
+ }
+ return $usage . "\n" . $self->descr();
+}
+
+sub print_usage {
+ my ( $self ) = @_;
+ die "Run get_opts() before print_usage()" unless $self->{got_opts};
+ my @opts = values %{$self->{opts}};
+
+ my $maxl = max(
+ map {
+ length($_->{long}) # option long name
+ + ($_->{is_negatable} ? 4 : 0) # "[no]" if opt is negatable
+ + ($_->{type} ? 2 : 0) # "=x" where x is the opt type
+ }
+ @opts);
+
+ my $maxs = max(0,
+ map {
+ length($_)
+ + ($self->{opts}->{$_}->{is_negatable} ? 4 : 0)
+ + ($self->{opts}->{$_}->{type} ? 2 : 0)
+ }
+ values %{$self->{short_opts}});
+
+ my $lcol = max($maxl, ($maxs + 3));
+ my $rcol = 80 - $lcol - 6;
+ my $rpad = ' ' x ( 80 - $rcol );
+
+ $maxs = max($lcol - 3, $maxs);
+
+ my $usage = $self->descr() . "\n" . $self->usage();
+
+ my @groups = reverse sort grep { $_ ne 'default'; } keys %{$self->{groups}};
+ push @groups, 'default';
+
+ foreach my $group ( reverse @groups ) {
+ $usage .= "\n".($group eq 'default' ? 'Options' : $group).":\n\n";
+ foreach my $opt (
+ sort { $a->{long} cmp $b->{long} }
+ grep { $_->{group} eq $group }
+ @opts )
+ {
+ my $long = $opt->{is_negatable} ? "[no]$opt->{long}" : $opt->{long};
+ my $short = $opt->{short};
+ my $desc = $opt->{desc};
+
+ $long .= $opt->{type} ? "=$opt->{type}" : "";
+
+ if ( $opt->{type} && $opt->{type} eq 'm' ) {
+ my ($s) = $desc =~ m/\(suffix (.)\)/;
+ $s ||= 's';
+ $desc =~ s/\s+\(suffix .\)//;
+ $desc .= ". Optional suffix s=seconds, m=minutes, h=hours, "
+ . "d=days; if no suffix, $s is used.";
+ }
+ $desc = join("\n$rpad", grep { $_ } $desc =~ m/(.{0,$rcol}(?!\W))(?:\s+|(?<=\W)|$)/g);
+ $desc =~ s/ +$//mg;
+ if ( $short ) {
+ $usage .= sprintf(" --%-${maxs}s -%s %s\n", $long, $short, $desc);
+ }
+ else {
+ $usage .= sprintf(" --%-${lcol}s %s\n", $long, $desc);
+ }
+ }
+ }
+
+ $usage .= "\nOption types: s=string, i=integer, f=float, h/H/a/A=comma-separated list, d=DSN, z=size, m=time\n";
+
+ if ( (my @rules = @{$self->{rules}}) ) {
+ $usage .= "\nRules:\n\n";
+ $usage .= join("\n", map { " $_" } @rules) . "\n";
+ }
+ if ( $self->{DSNParser} ) {
+ $usage .= "\n" . $self->{DSNParser}->usage();
+ }
+ $usage .= "\nOptions and values after processing arguments:\n\n";
+ foreach my $opt ( sort { $a->{long} cmp $b->{long} } @opts ) {
+ my $val = $opt->{value};
+ my $type = $opt->{type} || '';
+ my $bool = $opt->{spec} =~ m/^[\w-]+(?:\|[\w-])?!?$/;
+ $val = $bool ? ( $val ? 'TRUE' : 'FALSE' )
+ : !defined $val ? '(No value)'
+ : $type eq 'd' ? $self->{DSNParser}->as_string($val)
+ : $type =~ m/H|h/ ? join(',', sort keys %$val)
+ : $type =~ m/A|a/ ? join(',', @$val)
+ : $val;
+ $usage .= sprintf(" --%-${lcol}s %s\n", $opt->{long}, $val);
+ }
+ return $usage;
+}
+
+sub prompt_noecho {
+ shift @_ if ref $_[0] eq __PACKAGE__;
+ my ( $prompt ) = @_;
+ local $OUTPUT_AUTOFLUSH = 1;
+ print $prompt
+ or die "Cannot print: $OS_ERROR";
+ my $response;
+ eval {
+ require Term::ReadKey;
+ Term::ReadKey::ReadMode('noecho');
+ chomp($response = <STDIN>);
+ Term::ReadKey::ReadMode('normal');
+ print "\n"
+ or die "Cannot print: $OS_ERROR";
+ };
+ if ( $EVAL_ERROR ) {
+ die "Cannot read response; is Term::ReadKey installed? $EVAL_ERROR";
+ }
+ return $response;
+}
+
+sub _read_config_file {
+ my ( $self, $filename ) = @_;
+ open my $fh, "<", $filename or die "Cannot open $filename: $OS_ERROR\n";
+ my @args;
+ my $prefix = '--';
+ my $parse = 1;
+
+ LINE:
+ while ( my $line = <$fh> ) {
+ chomp $line;
+ next LINE if $line =~ m/^\s*(?:\#|\;|$)/;
+ $line =~ s/\s+#.*$//g;
+ $line =~ s/^\s+|\s+$//g;
+ if ( $line eq '--' ) {
+ $prefix = '';
+ $parse = 0;
+ next LINE;
+ }
+ if ( $parse
+ && (my($opt, $arg) = $line =~ m/^\s*([^=\s]+?)(?:\s*=\s*(.*?)\s*)?$/)
+ ) {
+ push @args, grep { defined $_ } ("$prefix$opt", $arg);
+ }
+ elsif ( $line =~ m/./ ) {
+ push @args, $line;
+ }
+ else {
+ die "Syntax error in file $filename at line $INPUT_LINE_NUMBER";
+ }
+ }
+ close $fh;
+ return @args;
+}
+
+sub read_para_after {
+ my ( $self, $file, $regex ) = @_;
+ open my $fh, "<", $file or die "Can't open $file: $OS_ERROR";
+ local $INPUT_RECORD_SEPARATOR = '';
+ my $para;
+ while ( $para = <$fh> ) {
+ next unless $para =~ m/^=pod$/m;
+ last;
+ }
+ while ( $para = <$fh> ) {
+ next unless $para =~ m/$regex/;
+ last;
+ }
+ $para = <$fh>;
+ chomp($para);
+ close $fh or die "Can't close $file: $OS_ERROR";
+ return $para;
+}
+
+sub clone {
+ my ( $self ) = @_;
+
+ my %clone = map {
+ my $hashref = $self->{$_};
+ my $val_copy = {};
+ foreach my $key ( keys %$hashref ) {
+ my $ref = ref $hashref->{$key};
+ $val_copy->{$key} = !$ref ? $hashref->{$key}
+ : $ref eq 'HASH' ? { %{$hashref->{$key}} }
+ : $ref eq 'ARRAY' ? [ @{$hashref->{$key}} ]
+ : $hashref->{$key};
+ }
+ $_ => $val_copy;
+ } qw(opts short_opts defaults);
+
+ foreach my $scalar ( qw(got_opts) ) {
+ $clone{$scalar} = $self->{$scalar};
+ }
+
+ return bless \%clone;
+}
+
+sub _parse_size {
+ my ( $self, $opt, $val ) = @_;
+
+ if ( lc($val || '') eq 'null' ) {
+ PTDEBUG && _d('NULL size for', $opt->{long});
+ $opt->{value} = 'null';
+ return;
+ }
+
+ my %factor_for = (k => 1_024, M => 1_048_576, G => 1_073_741_824);
+ my ($pre, $num, $factor) = $val =~ m/^([+-])?(\d+)([kMG])?$/;
+ if ( defined $num ) {
+ if ( $factor ) {
+ $num *= $factor_for{$factor};
+ PTDEBUG && _d('Setting option', $opt->{y},
+ 'to num', $num, '* factor', $factor);
+ }
+ $opt->{value} = ($pre || '') . $num;
+ }
+ else {
+ $self->save_error("Invalid size for --$opt->{long}: $val");
+ }
+ return;
+}
+
+sub _parse_attribs {
+ my ( $self, $option, $attribs ) = @_;
+ my $types = $self->{types};
+ return $option
+ . ($attribs->{'short form'} ? '|' . $attribs->{'short form'} : '' )
+ . ($attribs->{'negatable'} ? '!' : '' )
+ . ($attribs->{'cumulative'} ? '+' : '' )
+ . ($attribs->{'type'} ? '=' . $types->{$attribs->{type}} : '' );
+}
+
+sub _parse_synopsis {
+ my ( $self, $file ) = @_;
+ $file ||= $self->{file} || __FILE__;
+ PTDEBUG && _d("Parsing SYNOPSIS in", $file);
+
+ local $INPUT_RECORD_SEPARATOR = ''; # read paragraphs
+ open my $fh, "<", $file or die "Cannot open $file: $OS_ERROR";
+ my $para;
+ 1 while defined($para = <$fh>) && $para !~ m/^=head1 SYNOPSIS/;
+ die "$file does not contain a SYNOPSIS section" unless $para;
+ my @synop;
+ for ( 1..2 ) { # 1 for the usage, 2 for the description
+ my $para = <$fh>;
+ push @synop, $para;
+ }
+ close $fh;
+ PTDEBUG && _d("Raw SYNOPSIS text:", @synop);
+ my ($usage, $desc) = @synop;
+ die "The SYNOPSIS section in $file is not formatted properly"
+ unless $usage && $desc;
+
+ $usage =~ s/^\s*Usage:\s+(.+)/$1/;
+ chomp $usage;
+
+ $desc =~ s/\n/ /g;
+ $desc =~ s/\s{2,}/ /g;
+ $desc =~ s/\. ([A-Z][a-z])/. $1/g;
+ $desc =~ s/\s+$//;
+
+ return (
+ description => $desc,
+ usage => $usage,
+ );
+};
+
+sub set_vars {
+ my ($self, $file) = @_;
+ $file ||= $self->{file} || __FILE__;
+
+ my %user_vars;
+ my $user_vars = $self->has('set-vars') ? $self->get('set-vars') : undef;
+ if ( $user_vars ) {
+ foreach my $var_val ( @$user_vars ) {
+ my ($var, $val) = $var_val =~ m/([^\s=]+)=(\S+)/;
+ die "Invalid --set-vars value: $var_val\n" unless $var && $val;
+ $user_vars{$var} = {
+ val => $val,
+ default => 0,
+ };
+ }
+ }
+
+ my %default_vars;
+ my $default_vars = $self->read_para_after($file, qr/MAGIC_set_vars/);
+ if ( $default_vars ) {
+ %default_vars = map {
+ my $var_val = $_;
+ my ($var, $val) = $var_val =~ m/([^\s=]+)=(\S+)/;
+ die "Invalid --set-vars value: $var_val\n" unless $var && $val;
+ $var => {
+ val => $val,
+ default => 1,
+ };
+ } split("\n", $default_vars);
+ }
+
+ my %vars = (
+ %default_vars, # first the tool's defaults
+ %user_vars, # then the user's which overwrite the defaults
+ );
+ PTDEBUG && _d('--set-vars:', Dumper(\%vars));
+ return \%vars;
+}
+
+sub _d {
+ my ($package, undef, $line) = caller 0;
+ @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
+ map { defined $_ ? $_ : 'undef' }
+ @_;
+ print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
+}
+
+if ( PTDEBUG ) {
+ print '# ', $^X, ' ', $], "\n";
+ if ( my $uname = `uname -a` ) {
+ $uname =~ s/\s+/ /g;
+ print "# $uname\n";
+ }
+ print '# Arguments: ',
+ join(' ', map { my $a = "_[$_]_"; $a =~ s/\n/\n# /g; $a; } @ARGV), "\n";
+}
+
+1;
+}
+# ###########################################################################
+# End OptionParser package
+# ###########################################################################
+
# ###########################################################################
# This is a combination of modules and programs in one -- a runnable module.
# http://www.perl.com/pub/a/2006/07/13/lightning-articles.html?page=last
@@ -25,6 +1103,11 @@
@ARGV = @_; # set global ARGV for this package
+ my $o = OptionParser->new();
+ $o->get_specs();
+ $o->get_opts();
+ $o->usage_or_errors();
+
# Read all lines
my @lines;
my %word_count;
@@ -130,7 +1213,20 @@
=head1 OPTIONS
-This tool does not have any command-line options.
+This tool accepts additional command-line arguments. Refer to the
+L<"SYNOPSIS"> and usage information for details.
+
+=over
+
+=item --help
+
+Show help and exit.
+
+=item --version
+
+Show version and exit.
+
+=back
=head1 ENVIRONMENT
@@ -217,6 +1313,6 @@
=head1 VERSION
-pt-align 2.2.1
+pt-align 2.2.2
=cut
|
[-]
[+]
|
Changed |
percona-toolkit-2.2.2.tar.gz/bin/pt-archiver
^
|
@@ -42,7 +42,7 @@
# ###########################################################################
{
package Percona::Toolkit;
-our $VERSION = '2.2.1';
+our $VERSION = '2.2.2';
1;
}
@@ -60,6 +60,7 @@
# ###########################################################################
{
package Lmo::Utils;
+
use strict;
use warnings qw( FATAL all );
require Exporter;
@@ -67,7 +68,12 @@
BEGIN {
@ISA = qw(Exporter);
- @EXPORT = @EXPORT_OK = qw(_install_coderef _unimport_coderefs _glob_for _stash_for);
+ @EXPORT = @EXPORT_OK = qw(
+ _install_coderef
+ _unimport_coderefs
+ _glob_for
+ _stash_for
+ );
}
{
@@ -251,7 +257,6 @@
return Lmo::Meta->new(class => $class);
}
-
1;
}
# ###########################################################################
@@ -3462,6 +3467,8 @@
my $dp = $self->{DSNParser};
my $methods = $self->_resolve_recursion_methods($args{dsn});
+ return $slaves unless @$methods;
+
if ( grep { m/processlist|hosts/i } @$methods ) {
my @required_args = qw(dbh dsn);
foreach my $arg ( @required_args ) {
@@ -5450,6 +5457,7 @@
my $archive_file = $o->get('file');
my $txnsize = $o->get('txn-size');
my $quiet = $o->get('quiet');
+ my $got_charset = $o->get('charset');
# First things first: if --stop was given, create the sentinel file.
if ( $o->get('stop') ) {
@@ -5833,7 +5841,9 @@
. ' LOCAL INFILE ?'
. ($o->get('replace') ? ' REPLACE' : '')
. ($o->get('ignore') ? ' IGNORE' : '')
- . " INTO TABLE $dst->{db_tbl}("
+ . " INTO TABLE $dst->{db_tbl}"
+ . ($got_charset ? "CHARACTER SET $got_charset" : "")
+ . "("
. join(",", map { $q->quote($_) } @{$ins_stmt->{cols}} )
. ")";
}
@@ -5942,28 +5952,49 @@
return 0;
}
- # Open the file and print the header to it.
- if ( $archive_file ) {
- my $need_hdr = $o->get('header') && !-f $archive_file;
- my $charset = $o->get('charset') || '';
- if ($charset eq 'utf8') {
- $charset = ":$charset";
- }
- elsif ($charset) {
- eval { require Encode }
+ my $charset = $got_charset || '';
+ if ($charset eq 'utf8') {
+ $charset = ":$charset";
+ }
+ elsif ($charset) {
+ eval { require Encode }
or (PTDEBUG &&
_d("Couldn't load Encode: ", $EVAL_ERROR,
"Going to try using the charset ",
"passed in without checking it."));
- # No need to punish a user if they did their
- # homework and passed in an official charset,
- # rather than an alias.
- $charset = ":encoding("
- . (defined &Encode::resolve_alias
- ? Encode::resolve_alias($charset) || $charset
- : $charset)
- . ")";
- }
+ # No need to punish a user if they did their
+ # homework and passed in an official charset,
+ # rather than an alias.
+ $charset = ":encoding("
+ . (defined &Encode::resolve_alias
+ ? Encode::resolve_alias($charset) || $charset
+ : $charset)
+ . ")";
+ }
+
+ if ( $charset eq ':utf8' && $DBD::mysql::VERSION lt '4'
+ && ( $archive_file || $o->get('bulk-insert') ) )
+ {
+ my $plural = '';
+ my $files = $archive_file ? '--file' : '';
+ if ( $o->get('bulk-insert') ) {
+ if ($files) {
+ $plural = 's';
+ $files .= $files ? ' and ' : '';
+ }
+ $files .= '--bulk-insert'
+ }
+ warn "Setting binmode :raw instead of :utf8 on $files file$plural "
+ . "because DBD::mysql 3.0007 has a bug with UTF-8. "
+ . "Verify the $files file$plural, as the bug may lead to "
+ . "data being double-encoded. Update DBD::mysql to avoid "
+ . "this warning.";
+ $charset = ":raw";
+ }
+
+ # Open the file and print the header to it.
+ if ( $archive_file ) {
+ my $need_hdr = $o->get('header') && !-f $archive_file;
$archive_fh = IO::File->new($archive_file, ">>$charset")
or die "Cannot open $charset $archive_file: $OS_ERROR\n";
$archive_fh->autoflush(1) unless $o->get('buffer');
@@ -5979,6 +6010,9 @@
require File::Temp;
$bulkins_file = File::Temp->new( SUFFIX => 'pt-archiver' )
or die "Cannot open temp file: $OS_ERROR\n";
+ binmode($bulkins_file, $charset)
+ or die "Cannot set $charset as an encoding for the bulk-insert "
+ . "file: $OS_ERROR";
}
# This row is the first row fetched from each 'chunk'.
@@ -6205,6 +6239,9 @@
if ( $o->get('bulk-insert') ) {
$bulkins_file = File::Temp->new( SUFFIX => 'pt-archiver' )
or die "Cannot open temp file: $OS_ERROR\n";
+ binmode($bulkins_file, $charset)
+ or die "Cannot set $charset as an encoding for the bulk-insert "
+ . "file: $OS_ERROR";
}
} # no next row (do bulk operations)
else {
@@ -7817,6 +7854,6 @@
=head1 VERSION
-pt-archiver 2.2.1
+pt-archiver 2.2.2
=cut
|
[-]
[+]
|
Changed |
percona-toolkit-2.2.2.tar.gz/bin/pt-config-diff
^
|
@@ -42,7 +42,7 @@
# ###########################################################################
{
package Percona::Toolkit;
-our $VERSION = '2.2.1';
+our $VERSION = '2.2.2';
1;
}
@@ -60,6 +60,7 @@
# ###########################################################################
{
package Lmo::Utils;
+
use strict;
use warnings qw( FATAL all );
require Exporter;
@@ -67,7 +68,12 @@
BEGIN {
@ISA = qw(Exporter);
- @EXPORT = @EXPORT_OK = qw(_install_coderef _unimport_coderefs _glob_for _stash_for);
+ @EXPORT = @EXPORT_OK = qw(
+ _install_coderef
+ _unimport_coderefs
+ _glob_for
+ _stash_for
+ );
}
{
@@ -251,7 +257,6 @@
return Lmo::Meta->new(class => $class);
}
-
1;
}
# ###########################################################################
@@ -2281,9 +2286,9 @@
$dbh->{FetchHashKeyName} = 'NAME_lc' if $self->{NAME_lc};
- my $sql = 'SELECT @@hostname, @@server_id';
+ my $sql = 'SELECT @@server_id /*!50038 , @@hostname*/';
PTDEBUG && _d($dbh, $sql);
- my ($hostname, $server_id) = $dbh->selectrow_array($sql);
+ my ($server_id, $hostname) = $dbh->selectrow_array($sql);
PTDEBUG && _d($dbh, 'hostname:', $hostname, $server_id);
if ( $hostname ) {
$self->{hostname} = $hostname;
@@ -2326,6 +2331,32 @@
return $self->{hostname} || $self->{dsn_name} || 'unknown host';
}
+sub remove_duplicate_cxns {
+ my ($self, %args) = @_;
+ my @cxns = @{$args{cxns}};
+ my $seen_ids = $args{seen_ids} || {};
+ PTDEBUG && _d("Removing duplicates from ", join(" ", map { $_->name } @cxns));
+ my @trimmed_cxns;
+
+ for my $cxn ( @cxns ) {
+ my $dbh = $cxn->dbh();
+ my $sql = q{SELECT @@server_id};
+ PTDEBUG && _d($sql);
+ my ($id) = $dbh->selectrow_array($sql);
+ PTDEBUG && _d('Server ID for ', $cxn->name, ': ', $id);
+
+ if ( ! $seen_ids->{$id}++ ) {
+ push @trimmed_cxns, $cxn
+ }
+ else {
+ PTDEBUG && _d("Removing ", $cxn->name,
+ ", ID ", $id, ", because we've already seen it");
+ }
+ }
+
+ return \@trimmed_cxns;
+}
+
sub DESTROY {
my ($self) = @_;
@@ -5664,6 +5695,6 @@
=head1 VERSION
-pt-config-diff 2.2.1
+pt-config-diff 2.2.2
=cut
|
[-]
[+]
|
Changed |
percona-toolkit-2.2.2.tar.gz/bin/pt-deadlock-logger
^
|
@@ -41,7 +41,7 @@
# ###########################################################################
{
package Percona::Toolkit;
-our $VERSION = '2.2.1';
+our $VERSION = '2.2.2';
1;
}
@@ -1124,6 +1124,7 @@
# ###########################################################################
{
package Lmo::Utils;
+
use strict;
use warnings qw( FATAL all );
require Exporter;
@@ -1131,7 +1132,12 @@
BEGIN {
@ISA = qw(Exporter);
- @EXPORT = @EXPORT_OK = qw(_install_coderef _unimport_coderefs _glob_for _stash_for);
+ @EXPORT = @EXPORT_OK = qw(
+ _install_coderef
+ _unimport_coderefs
+ _glob_for
+ _stash_for
+ );
}
{
@@ -1315,7 +1321,6 @@
return Lmo::Meta->new(class => $class);
}
-
1;
}
# ###########################################################################
@@ -2625,9 +2630,9 @@
$dbh->{FetchHashKeyName} = 'NAME_lc' if $self->{NAME_lc};
- my $sql = 'SELECT @@hostname, @@server_id';
+ my $sql = 'SELECT @@server_id /*!50038 , @@hostname*/';
PTDEBUG && _d($dbh, $sql);
- my ($hostname, $server_id) = $dbh->selectrow_array($sql);
+ my ($server_id, $hostname) = $dbh->selectrow_array($sql);
PTDEBUG && _d($dbh, 'hostname:', $hostname, $server_id);
if ( $hostname ) {
$self->{hostname} = $hostname;
@@ -2670,6 +2675,32 @@
return $self->{hostname} || $self->{dsn_name} || 'unknown host';
}
+sub remove_duplicate_cxns {
+ my ($self, %args) = @_;
+ my @cxns = @{$args{cxns}};
+ my $seen_ids = $args{seen_ids} || {};
+ PTDEBUG && _d("Removing duplicates from ", join(" ", map { $_->name } @cxns));
+ my @trimmed_cxns;
+
+ for my $cxn ( @cxns ) {
+ my $dbh = $cxn->dbh();
+ my $sql = q{SELECT @@server_id};
+ PTDEBUG && _d($sql);
+ my ($id) = $dbh->selectrow_array($sql);
+ PTDEBUG && _d('Server ID for ', $cxn->name, ': ', $id);
+
+ if ( ! $seen_ids->{$id}++ ) {
+ push @trimmed_cxns, $cxn
+ }
+ else {
+ PTDEBUG && _d("Removing ", $cxn->name,
+ ", ID ", $id, ", because we've already seen it");
+ }
+ }
+
+ return \@trimmed_cxns;
+}
+
sub DESTROY {
my ($self) = @_;
@@ -5437,6 +5468,6 @@
=head1 VERSION
-pt-deadlock-logger 2.2.1
+pt-deadlock-logger 2.2.2
=cut
|
[-]
[+]
|
Changed |
percona-toolkit-2.2.2.tar.gz/bin/pt-diskstats
^
|
@@ -37,7 +37,7 @@
# ###########################################################################
{
package Percona::Toolkit;
-our $VERSION = '2.2.1';
+our $VERSION = '2.2.2';
1;
}
@@ -63,6 +63,7 @@
use List::Util qw(max);
use Getopt::Long;
+use Data::Dumper;
my $POD_link_re = '[LC]<"?([^">]+)"?>';
@@ -1046,6 +1047,45 @@
);
};
+sub set_vars {
+ my ($self, $file) = @_;
+ $file ||= $self->{file} || __FILE__;
+
+ my %user_vars;
+ my $user_vars = $self->has('set-vars') ? $self->get('set-vars') : undef;
+ if ( $user_vars ) {
+ foreach my $var_val ( @$user_vars ) {
+ my ($var, $val) = $var_val =~ m/([^\s=]+)=(\S+)/;
+ die "Invalid --set-vars value: $var_val\n" unless $var && $val;
+ $user_vars{$var} = {
+ val => $val,
+ default => 0,
+ };
+ }
+ }
+
+ my %default_vars;
+ my $default_vars = $self->read_para_after($file, qr/MAGIC_set_vars/);
+ if ( $default_vars ) {
+ %default_vars = map {
+ my $var_val = $_;
+ my ($var, $val) = $var_val =~ m/([^\s=]+)=(\S+)/;
+ die "Invalid --set-vars value: $var_val\n" unless $var && $val;
+ $var => {
+ val => $val,
+ default => 1,
+ };
+ } split("\n", $default_vars);
+ }
+
+ my %vars = (
+ %default_vars, # first the tool's defaults
+ %user_vars, # then the user's which overwrite the defaults
+ );
+ PTDEBUG && _d('--set-vars:', Dumper(\%vars));
+ return \%vars;
+}
+
sub _d {
my ($package, undef, $line) = caller 0;
@_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
@@ -5501,6 +5541,6 @@
=head1 VERSION
-pt-diskstats 2.2.1
+pt-diskstats 2.2.2
=cut
|
[-]
[+]
|
Changed |
percona-toolkit-2.2.2.tar.gz/bin/pt-duplicate-key-checker
^
|
@@ -38,7 +38,7 @@
# ###########################################################################
{
package Percona::Toolkit;
-our $VERSION = '2.2.1';
+our $VERSION = '2.2.2';
1;
}
@@ -5480,6 +5480,6 @@
=head1 VERSION
-pt-duplicate-key-checker 2.2.1
+pt-duplicate-key-checker 2.2.2
=cut
|
[-]
[+]
|
Changed |
percona-toolkit-2.2.2.tar.gz/bin/pt-fifo-split
^
|
@@ -36,6 +36,7 @@
use List::Util qw(max);
use Getopt::Long;
+use Data::Dumper;
my $POD_link_re = '[LC]<"?([^">]+)"?>';
@@ -1019,6 +1020,45 @@
);
};
+sub set_vars {
+ my ($self, $file) = @_;
+ $file ||= $self->{file} || __FILE__;
+
+ my %user_vars;
+ my $user_vars = $self->has('set-vars') ? $self->get('set-vars') : undef;
+ if ( $user_vars ) {
+ foreach my $var_val ( @$user_vars ) {
+ my ($var, $val) = $var_val =~ m/([^\s=]+)=(\S+)/;
+ die "Invalid --set-vars value: $var_val\n" unless $var && $val;
+ $user_vars{$var} = {
+ val => $val,
+ default => 0,
+ };
+ }
+ }
+
+ my %default_vars;
+ my $default_vars = $self->read_para_after($file, qr/MAGIC_set_vars/);
+ if ( $default_vars ) {
+ %default_vars = map {
+ my $var_val = $_;
+ my ($var, $val) = $var_val =~ m/([^\s=]+)=(\S+)/;
+ die "Invalid --set-vars value: $var_val\n" unless $var && $val;
+ $var => {
+ val => $val,
+ default => 1,
+ };
+ } split("\n", $default_vars);
+ }
+
+ my %vars = (
+ %default_vars, # first the tool's defaults
+ %user_vars, # then the user's which overwrite the defaults
+ );
+ PTDEBUG && _d('--set-vars:', Dumper(\%vars));
+ return \%vars;
+}
+
sub _d {
my ($package, undef, $line) = caller 0;
@_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
@@ -1562,6 +1602,6 @@
=head1 VERSION
-pt-fifo-split 2.2.1
+pt-fifo-split 2.2.2
=cut
|
[-]
[+]
|
Changed |
percona-toolkit-2.2.2.tar.gz/bin/pt-find
^
|
@@ -34,7 +34,7 @@
# ###########################################################################
{
package Percona::Toolkit;
-our $VERSION = '2.2.1';
+our $VERSION = '2.2.2';
1;
}
@@ -4946,6 +4946,6 @@
=head1 VERSION
-pt-find 2.2.1
+pt-find 2.2.2
=cut
|
[-]
[+]
|
Changed |
percona-toolkit-2.2.2.tar.gz/bin/pt-fingerprint
^
|
@@ -37,6 +37,7 @@
use List::Util qw(max);
use Getopt::Long;
+use Data::Dumper;
my $POD_link_re = '[LC]<"?([^">]+)"?>';
@@ -1020,6 +1021,45 @@
);
};
+sub set_vars {
+ my ($self, $file) = @_;
+ $file ||= $self->{file} || __FILE__;
+
+ my %user_vars;
+ my $user_vars = $self->has('set-vars') ? $self->get('set-vars') : undef;
+ if ( $user_vars ) {
+ foreach my $var_val ( @$user_vars ) {
+ my ($var, $val) = $var_val =~ m/([^\s=]+)=(\S+)/;
+ die "Invalid --set-vars value: $var_val\n" unless $var && $val;
+ $user_vars{$var} = {
+ val => $val,
+ default => 0,
+ };
+ }
+ }
+
+ my %default_vars;
+ my $default_vars = $self->read_para_after($file, qr/MAGIC_set_vars/);
+ if ( $default_vars ) {
+ %default_vars = map {
+ my $var_val = $_;
+ my ($var, $val) = $var_val =~ m/([^\s=]+)=(\S+)/;
+ die "Invalid --set-vars value: $var_val\n" unless $var && $val;
+ $var => {
+ val => $val,
+ default => 1,
+ };
+ } split("\n", $default_vars);
+ }
+
+ my %vars = (
+ %default_vars, # first the tool's defaults
+ %user_vars, # then the user's which overwrite the defaults
+ );
+ PTDEBUG && _d('--set-vars:', Dumper(\%vars));
+ return \%vars;
+}
+
sub _d {
my ($package, undef, $line) = caller 0;
@_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
@@ -2153,6 +2193,6 @@
=head1 VERSION
-pt-fingerprint 2.2.1
+pt-fingerprint 2.2.2
=cut
|
[-]
[+]
|
Changed |
percona-toolkit-2.2.2.tar.gz/bin/pt-fk-error-logger
^
|
@@ -36,7 +36,7 @@
# ###########################################################################
{
package Percona::Toolkit;
-our $VERSION = '2.2.1';
+our $VERSION = '2.2.2';
1;
}
@@ -1782,9 +1782,9 @@
$dbh->{FetchHashKeyName} = 'NAME_lc' if $self->{NAME_lc};
- my $sql = 'SELECT @@hostname, @@server_id';
+ my $sql = 'SELECT @@server_id /*!50038 , @@hostname*/';
PTDEBUG && _d($dbh, $sql);
- my ($hostname, $server_id) = $dbh->selectrow_array($sql);
+ my ($server_id, $hostname) = $dbh->selectrow_array($sql);
PTDEBUG && _d($dbh, 'hostname:', $hostname, $server_id);
if ( $hostname ) {
$self->{hostname} = $hostname;
@@ -1827,6 +1827,32 @@
return $self->{hostname} || $self->{dsn_name} || 'unknown host';
}
+sub remove_duplicate_cxns {
+ my ($self, %args) = @_;
+ my @cxns = @{$args{cxns}};
+ my $seen_ids = $args{seen_ids} || {};
+ PTDEBUG && _d("Removing duplicates from ", join(" ", map { $_->name } @cxns));
+ my @trimmed_cxns;
+
+ for my $cxn ( @cxns ) {
+ my $dbh = $cxn->dbh();
+ my $sql = q{SELECT @@server_id};
+ PTDEBUG && _d($sql);
+ my ($id) = $dbh->selectrow_array($sql);
+ PTDEBUG && _d('Server ID for ', $cxn->name, ': ', $id);
+
+ if ( ! $seen_ids->{$id}++ ) {
+ push @trimmed_cxns, $cxn
+ }
+ else {
+ PTDEBUG && _d("Removing ", $cxn->name,
+ ", ID ", $id, ", because we've already seen it");
+ }
+ }
+
+ return \@trimmed_cxns;
+}
+
sub DESTROY {
my ($self) = @_;
@@ -4445,6 +4471,6 @@
=head1 VERSION
-pt-fk-error-logger 2.2.1
+pt-fk-error-logger 2.2.2
=cut
|
[-]
[+]
|
Changed |
percona-toolkit-2.2.2.tar.gz/bin/pt-heartbeat
^
|
@@ -37,7 +37,7 @@
# ###########################################################################
{
package Percona::Toolkit;
-our $VERSION = '2.2.1';
+our $VERSION = '2.2.2';
1;
}
@@ -105,6 +105,8 @@
my $dp = $self->{DSNParser};
my $methods = $self->_resolve_recursion_methods($args{dsn});
+ return $slaves unless @$methods;
+
if ( grep { m/processlist|hosts/i } @$methods ) {
my @required_args = qw(dbh dsn);
foreach my $arg ( @required_args ) {
@@ -3047,6 +3049,8 @@
use English qw(-no_match_vars);
use constant PTDEBUG => $ENV{PTDEBUG} || 0;
+use Time::HiRes qw(sleep);
+
sub new {
my ( $class, %args ) = @_;
my $self = {
@@ -5119,6 +5123,14 @@
$sth->finish();
return ($delay, $hostname, $pk_val);
};
+
+ # https://bugs.launchpad.net/percona-toolkit/+bug/1163372
+ # "pt-heartbeat --utc --check always returns 0"
+ if ( $utc ) {
+ my $sql = "SET time_zone='+0:00'";
+ PTDEBUG && _d($sql);
+ $dbh->do($sql);
+ }
}
# Do a little check just to make sure the table is there, so there's one last
@@ -5947,7 +5959,7 @@
cause the tool to compute the lag incorrectly. Specifying this option is
a good idea because it ensures that the tool works correctly regardless of
time zones, but it also makes the tool backwards-incompatible with
-pt-heartbeat 2.2.1 and older (unless the older version of pt-heartbeat
+pt-heartbeat 2.2.2 and older (unless the older version of pt-heartbeat
is running on a system that uses UTC).
=item --version
@@ -6129,6 +6141,6 @@
=head1 VERSION
-pt-heartbeat 2.2.1
+pt-heartbeat 2.2.2
=cut
|
[-]
[+]
|
Changed |
percona-toolkit-2.2.2.tar.gz/bin/pt-index-usage
^
|
@@ -44,7 +44,7 @@
# ###########################################################################
{
package Percona::Toolkit;
-our $VERSION = '2.2.1';
+our $VERSION = '2.2.2';
1;
}
@@ -7476,6 +7476,6 @@
=head1 VERSION
-pt-index-usage 2.2.1
+pt-index-usage 2.2.2
=cut
|
[-]
[+]
|
Changed |
percona-toolkit-2.2.2.tar.gz/bin/pt-ioprofile
^
|
@@ -345,14 +345,14 @@
if [ "$next_opt_is_val" ]; then
next_opt_is_val=""
- if [ $# -eq 0 ] || [ $(expr "$opt" : "-") -eq 1 ]; then
+ if [ $# -eq 0 ] || [ $(expr "$opt" : "\-") -eq 1 ]; then
option_error "$real_opt requires a $required_arg argument"
continue
fi
val="$opt"
opt_is_ok=1
else
- if [ $(expr "$opt" : "-") -eq 0 ]; then
+ if [ $(expr "$opt" : "\-") -eq 0 ]; then
if [ -z "$ARGV" ]; then
ARGV="$opt"
else
@@ -1115,7 +1115,7 @@
=head1 VERSION
-pt-ioprofile 2.2.1
+pt-ioprofile 2.2.2
=cut
|
[-]
[+]
|
Changed |
percona-toolkit-2.2.2.tar.gz/bin/pt-kill
^
|
@@ -46,7 +46,7 @@
# ###########################################################################
{
package Percona::Toolkit;
-our $VERSION = '2.2.1';
+our $VERSION = '2.2.2';
1;
}
@@ -1129,6 +1129,7 @@
# ###########################################################################
{
package Lmo::Utils;
+
use strict;
use warnings qw( FATAL all );
require Exporter;
@@ -1136,7 +1137,12 @@
BEGIN {
@ISA = qw(Exporter);
- @EXPORT = @EXPORT_OK = qw(_install_coderef _unimport_coderefs _glob_for _stash_for);
+ @EXPORT = @EXPORT_OK = qw(
+ _install_coderef
+ _unimport_coderefs
+ _glob_for
+ _stash_for
+ );
}
{
@@ -1320,7 +1326,6 @@
return Lmo::Meta->new(class => $class);
}
-
1;
}
# ###########################################################################
@@ -3264,10 +3269,24 @@
$new_query = 1;
}
elsif ( $curr->[INFO] && defined $curr->[TIME]
- && $query_start - $etime - $prev->[START] > $fudge ) {
- PTDEBUG && _d('Query restarted; new query',
- $query_start, $etime, $prev->[START], $fudge);
- $new_query = 1;
+ && $query_start - $etime - $prev->[START] > $fudge)
+ {
+ my $ms = $self->{MasterSlave};
+
+ my $is_repl_thread = $ms->is_replication_thread({
+ Command => $curr->[COMMAND],
+ User => $curr->[USER],
+ State => $curr->[STATE],
+ Id => $curr->[ID]});
+ if ( $is_repl_thread ) {
+ PTDEBUG &&
+ _d(q{Query has restarted but it's a replication thread, ignoring});
+ }
+ else {
+ PTDEBUG && _d('Query restarted; new query',
+ $query_start, $etime, $prev->[START], $fudge);
+ $new_query = 1;
+ }
}
if ( $new_query ) {
@@ -3719,6 +3738,8 @@
my $dp = $self->{DSNParser};
my $methods = $self->_resolve_recursion_methods($args{dsn});
+ return $slaves unless @$methods;
+
if ( grep { m/processlist|hosts/i } @$methods ) {
my @required_args = qw(dbh dsn);
foreach my $arg ( @required_args ) {
@@ -4951,6 +4972,8 @@
use English qw(-no_match_vars);
use constant PTDEBUG => $ENV{PTDEBUG} || 0;
+use Time::HiRes qw(sleep);
+
sub new {
my ( $class, %args ) = @_;
my $self = {
@@ -5112,9 +5135,9 @@
$dbh->{FetchHashKeyName} = 'NAME_lc' if $self->{NAME_lc};
- my $sql = 'SELECT @@hostname, @@server_id';
+ my $sql = 'SELECT @@server_id /*!50038 , @@hostname*/';
PTDEBUG && _d($dbh, $sql);
- my ($hostname, $server_id) = $dbh->selectrow_array($sql);
+ my ($server_id, $hostname) = $dbh->selectrow_array($sql);
PTDEBUG && _d($dbh, 'hostname:', $hostname, $server_id);
if ( $hostname ) {
$self->{hostname} = $hostname;
@@ -5157,6 +5180,32 @@
return $self->{hostname} || $self->{dsn_name} || 'unknown host';
}
+sub remove_duplicate_cxns {
+ my ($self, %args) = @_;
+ my @cxns = @{$args{cxns}};
+ my $seen_ids = $args{seen_ids} || {};
+ PTDEBUG && _d("Removing duplicates from ", join(" ", map { $_->name } @cxns));
+ my @trimmed_cxns;
+
+ for my $cxn ( @cxns ) {
+ my $dbh = $cxn->dbh();
+ my $sql = q{SELECT @@server_id};
+ PTDEBUG && _d($sql);
+ my ($id) = $dbh->selectrow_array($sql);
+ PTDEBUG && _d('Server ID for ', $cxn->name, ': ', $id);
+
+ if ( ! $seen_ids->{$id}++ ) {
+ push @trimmed_cxns, $cxn
+ }
+ else {
+ PTDEBUG && _d("Removing ", $cxn->name,
+ ", ID ", $id, ", because we've already seen it");
+ }
+ }
+
+ return \@trimmed_cxns;
+}
+
sub DESTROY {
my ($self) = @_;
@@ -8077,6 +8126,6 @@
=head1 VERSION
-pt-kill 2.2.1
+pt-kill 2.2.2
=cut
|
[-]
[+]
|
Changed |
percona-toolkit-2.2.2.tar.gz/bin/pt-mext
^
|
@@ -4,18 +4,53 @@
# See "COPYRIGHT, LICENSE, AND WARRANTY" at the end of this file for legal
# notices and disclaimers.
-usage() {
- if [ "${OPT_ERR}" ]; then
- echo "${OPT_ERR}" >&2
- fi
- echo "Usage: pt-mext [OPTIONS] -- COMMAND" >&2
- echo "For more information, 'man pt-mext' or 'perldoc $0'" >&2
+# ###########################################################################
+# log_warn_die package
+# This package is a copy without comments from the original. The original
+# with comments and its test file can be found in the Bazaar repository at,
+# lib/bash/log_warn_die.sh
+# t/lib/bash/log_warn_die.sh
+# See https://launchpad.net/percona-toolkit for more information.
+# ###########################################################################
+
+
+set -u
+
+PTFUNCNAME=""
+PTDEBUG="${PTDEBUG:-""}"
+EXIT_STATUS=0
+
+ts() {
+ TS=$(date +%F-%T | tr ':-' '_')
+ echo "$TS $*"
+}
+
+info() {
+ [ ${OPT_VERBOSE:-3} -ge 3 ] && ts "$*"
+}
+
+log() {
+ [ ${OPT_VERBOSE:-3} -ge 2 ] && ts "$*"
+}
+
+warn() {
+ [ ${OPT_VERBOSE:-3} -ge 1 ] && ts "$*" >&2
+ EXIT_STATUS=1
+}
+
+die() {
+ ts "$*" >&2
+ EXIT_STATUS=1
exit 1
}
-if [ -z "$1" ]; then
- usage;
-fi
+_d () {
+ [ "$PTDEBUG" ] && echo "# $PTFUNCNAME: $(ts "$*")" >&2
+}
+
+# ###########################################################################
+# End log_warn_die package
+# ###########################################################################
# ###########################################################################
# tmpdir package
@@ -58,51 +93,497 @@
# End tmpdir package
# ###########################################################################
-mk_tmpdir
+# ###########################################################################
+# parse_options package
+# This package is a copy without comments from the original. The original
+# with comments and its test file can be found in the Bazaar repository at,
+# lib/bash/parse_options.sh
+# t/lib/bash/parse_options.sh
+# See https://launchpad.net/percona-toolkit for more information.
+# ###########################################################################
+
+
+
+
+
+set -u
+
+ARGV="" # Non-option args (probably input files)
+EXT_ARGV="" # Everything after -- (args for an external command)
+HAVE_EXT_ARGV="" # Got --, everything else is put into EXT_ARGV
+OPT_ERRS=0 # How many command line option errors
+OPT_VERSION="" # If --version was specified
+OPT_HELP="" # If --help was specified
+PO_DIR="" # Directory with program option spec files
+
+usage() {
+ local file="$1"
+
+ local usage="$(grep '^Usage: ' "$file")"
+ echo $usage
+ echo
+ echo "For more information, 'man $TOOL' or 'perldoc $file'."
+}
+
+usage_or_errors() {
+ local file="$1"
+
+ if [ "$OPT_VERSION" ]; then
+ local version=$(grep '^pt-[^ ]\+ [0-9]' "$file")
+ echo "$version"
+ return 1
+ fi
+
+ if [ "$OPT_HELP" ]; then
+ usage "$file"
+ echo
+ echo "Command line options:"
+ echo
+ perl -e '
+ use strict;
+ use warnings FATAL => qw(all);
+ my $lcol = 20; # Allow this much space for option names.
+ my $rcol = 80 - $lcol; # The terminal is assumed to be 80 chars wide.
+ my $name;
+ while ( <> ) {
+ my $line = $_;
+ chomp $line;
+ if ( $line =~ s/^long:/ --/ ) {
+ $name = $line;
+ }
+ elsif ( $line =~ s/^desc:// ) {
+ $line =~ s/ +$//mg;
+ my @lines = grep { $_ }
+ $line =~ m/(.{0,$rcol})(?:\s+|\Z)/g;
+ if ( length($name) >= $lcol ) {
+ print $name, "\n", (q{ } x $lcol);
+ }
+ else {
+ printf "%-${lcol}s", $name;
+ }
+ print join("\n" . (q{ } x $lcol), @lines);
+ print "\n";
+ }
+ }
+ ' "$PO_DIR"/*
+ echo
+ echo "Options and values after processing arguments:"
+ echo
+ (
+ cd "$PO_DIR"
+ for opt in *; do
+ local varname="OPT_$(echo "$opt" | tr a-z- A-Z_)"
+ eval local varvalue=\$$varname
+ if ! grep -q "type:" "$PO_DIR/$opt" >/dev/null; then
+ if [ "$varvalue" -a "$varvalue" = "yes" ];
+ then varvalue="TRUE"
+ else
+ varvalue="FALSE"
+ fi
+ fi
+ printf -- " --%-30s %s" "$opt" "${varvalue:-(No value)}"
+ echo
+ done
+ )
+ return 1
+ fi
+
+ if [ $OPT_ERRS -gt 0 ]; then
+ echo
+ usage "$file"
+ return 1
+ fi
+
+ return 0
+}
+
+option_error() {
+ local err="$1"
+ OPT_ERRS=$(($OPT_ERRS + 1))
+ echo "$err" >&2
+}
+
+parse_options() {
+ local file="$1"
+ shift
+
+ ARGV=""
+ EXT_ARGV=""
+ HAVE_EXT_ARGV=""
+ OPT_ERRS=0
+ OPT_VERSION=""
+ OPT_HELP=""
+ PO_DIR="$PT_TMPDIR/po"
+
+ if [ ! -d "$PO_DIR" ]; then
+ mkdir "$PO_DIR"
+ if [ $? -ne 0 ]; then
+ echo "Cannot mkdir $PO_DIR" >&2
+ exit 1
+ fi
+ fi
+
+ rm -rf "$PO_DIR"/*
+ if [ $? -ne 0 ]; then
+ echo "Cannot rm -rf $PO_DIR/*" >&2
+ exit 1
+ fi
+
+ _parse_pod "$file" # Parse POD into program option (po) spec files
+ _eval_po # Eval po into existence with default values
+
+ if [ $# -ge 2 ] && [ "$1" = "--config" ]; then
+ shift # --config
+ local user_config_files="$1"
+ shift # that ^
+ local IFS=","
+ for user_config_file in $user_config_files; do
+ _parse_config_files "$user_config_file"
+ done
+ else
+ _parse_config_files "/etc/percona-toolkit/percona-toolkit.conf" "/etc/percona-toolkit/$TOOL.conf" "$HOME/.percona-toolkit.conf" "$HOME/.$TOOL.conf"
+ fi
+
+ _parse_command_line "${@:-""}"
+}
+
+_parse_pod() {
+ local file="$1"
+
+ cat "$file" | PO_DIR="$PO_DIR" perl -ne '
+ BEGIN { $/ = ""; }
+ next unless $_ =~ m/^=head1 OPTIONS/;
+ while ( defined(my $para = <>) ) {
+ last if $para =~ m/^=head1/;
+ chomp;
+ if ( $para =~ m/^=item --(\S+)/ ) {
+ my $opt = $1;
+ my $file = "$ENV{PO_DIR}/$opt";
+ open my $opt_fh, ">", $file or die "Cannot open $file: $!";
+ print $opt_fh "long:$opt\n";
+ $para = <>;
+ chomp;
+ if ( $para =~ m/^[a-z ]+:/ ) {
+ map {
+ chomp;
+ my ($attrib, $val) = split(/: /, $_);
+ print $opt_fh "$attrib:$val\n";
+ } split(/; /, $para);
+ $para = <>;
+ chomp;
+ }
+ my ($desc) = $para =~ m/^([^?.]+)/;
+ print $opt_fh "desc:$desc.\n";
+ close $opt_fh;
+ }
+ }
+ last;
+ '
+}
+
+_eval_po() {
+ local IFS=":"
+ for opt_spec in "$PO_DIR"/*; do
+ local opt=""
+ local default_val=""
+ local neg=0
+ local size=0
+ while read key val; do
+ case "$key" in
+ long)
+ opt=$(echo $val | sed 's/-/_/g' | tr '[:lower:]' '[:upper:]')
+ ;;
+ default)
+ default_val="$val"
+ ;;
+ "short form")
+ ;;
+ type)
+ [ "$val" = "size" ] && size=1
+ ;;
+ desc)
+ ;;
+ negatable)
+ if [ "$val" = "yes" ]; then
+ neg=1
+ fi
+ ;;
+ *)
+ echo "Invalid attribute in $opt_spec: $line" >&2
+ exit 1
+ esac
+ done < "$opt_spec"
+
+ if [ -z "$opt" ]; then
+ echo "No long attribute in option spec $opt_spec" >&2
+ exit 1
+ fi
+
+ if [ $neg -eq 1 ]; then
+ if [ -z "$default_val" ] || [ "$default_val" != "yes" ]; then
+ echo "Option $opt_spec is negatable but not default: yes" >&2
+ exit 1
+ fi
+ fi
+
+ if [ $size -eq 1 -a -n "$default_val" ]; then
+ default_val=$(size_to_bytes $default_val)
+ fi
+
+ eval "OPT_${opt}"="$default_val"
+ done
+}
+
+_parse_config_files() {
+
+ for config_file in "${@:-""}"; do
+ test -f "$config_file" || continue
+
+ while read config_opt; do
+
+ echo "$config_opt" | grep '^[ ]*[^#]' >/dev/null 2>&1 || continue
+
+ config_opt="$(echo "$config_opt" | sed -e 's/^ *//g' -e 's/ *$//g' -e 's/[ ]*=[ ]*/=/' -e 's/[ ]*#.*$//')"
+
+ [ "$config_opt" = "" ] && continue
+
+ if ! [ "$HAVE_EXT_ARGV" ]; then
+ config_opt="--$config_opt"
+ fi
+
+ _parse_command_line "$config_opt"
+
+ done < "$config_file"
+
+ HAVE_EXT_ARGV="" # reset for each file
+
+ done
+}
+
+_parse_command_line() {
+ local opt=""
+ local val=""
+ local next_opt_is_val=""
+ local opt_is_ok=""
+ local opt_is_negated=""
+ local real_opt=""
+ local required_arg=""
+ local spec=""
+
+ for opt in "${@:-""}"; do
+ if [ "$opt" = "--" -o "$opt" = "----" ]; then
+ HAVE_EXT_ARGV=1
+ continue
+ fi
+ if [ "$HAVE_EXT_ARGV" ]; then
+ if [ "$EXT_ARGV" ]; then
+ EXT_ARGV="$EXT_ARGV $opt"
+ else
+ EXT_ARGV="$opt"
+ fi
+ continue
+ fi
+
+ if [ "$next_opt_is_val" ]; then
+ next_opt_is_val=""
+ if [ $# -eq 0 ] || [ $(expr "$opt" : "\-") -eq 1 ]; then
+ option_error "$real_opt requires a $required_arg argument"
+ continue
+ fi
+ val="$opt"
+ opt_is_ok=1
+ else
+ if [ $(expr "$opt" : "\-") -eq 0 ]; then
+ if [ -z "$ARGV" ]; then
+ ARGV="$opt"
+ else
+ ARGV="$ARGV $opt"
+ fi
+ continue
+ fi
+
+ real_opt="$opt"
+
+ if $(echo $opt | grep '^--no[^-]' >/dev/null); then
+ local base_opt=$(echo $opt | sed 's/^--no//')
+ if [ -f "$PT_TMPDIR/po/$base_opt" ]; then
+ opt_is_negated=1
+ opt="$base_opt"
+ else
+ opt_is_negated=""
+ opt=$(echo $opt | sed 's/^-*//')
+ fi
+ else
+ if $(echo $opt | grep '^--no-' >/dev/null); then
+ opt_is_negated=1
+ opt=$(echo $opt | sed 's/^--no-//')
+ else
+ opt_is_negated=""
+ opt=$(echo $opt | sed 's/^-*//')
+ fi
+ fi
+
+ if $(echo $opt | grep '^[a-z-][a-z-]*=' >/dev/null 2>&1); then
+ val="$(echo $opt | awk -F= '{print $2}')"
+ opt="$(echo $opt | awk -F= '{print $1}')"
+ fi
+
+ if [ -f "$PT_TMPDIR/po/$opt" ]; then
+ spec="$PT_TMPDIR/po/$opt"
+ else
+ spec=$(grep "^short form:-$opt\$" "$PT_TMPDIR"/po/* | cut -d ':' -f 1)
+ if [ -z "$spec" ]; then
+ option_error "Unknown option: $real_opt"
+ continue
+ fi
+ fi
+
+ required_arg=$(cat "$spec" | awk -F: '/^type:/{print $2}')
+ if [ "$required_arg" ]; then
+ if [ "$val" ]; then
+ opt_is_ok=1
+ else
+ next_opt_is_val=1
+ fi
+ else
+ if [ "$val" ]; then
+ option_error "Option $real_opt does not take a value"
+ continue
+ fi
+ if [ "$opt_is_negated" ]; then
+ val=""
+ else
+ val="yes"
+ fi
+ opt_is_ok=1
+ fi
+ fi
+
+ if [ "$opt_is_ok" ]; then
+ opt=$(cat "$spec" | grep '^long:' | cut -d':' -f2 | sed 's/-/_/g' | tr '[:lower:]' '[:upper:]')
+
+ if grep "^type:size" "$spec" >/dev/null; then
+ val=$(size_to_bytes $val)
+ fi
+
+ eval "OPT_$opt"="'$val'"
+
+ opt=""
+ val=""
+ next_opt_is_val=""
+ opt_is_ok=""
+ opt_is_negated=""
+ real_opt=""
+ required_arg=""
+ spec=""
+ fi
+ done
+}
+
+size_to_bytes() {
+ local size="$1"
+ 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")};'
+}
+
+# ###########################################################################
+# End parse_options package
+# ###########################################################################
+
+# ###########################################################################
+# alt_cmds package
+# This package is a copy without comments from the original. The original
+# with comments and its test file can be found in the Bazaar repository at,
+# lib/bash/alt_cmds.sh
+# t/lib/bash/alt_cmds.sh
+# See https://launchpad.net/percona-toolkit for more information.
+# ###########################################################################
-FILE="$PT_TMPDIR/mext_temp_file";
-NUM=0;
-REL=0;
-# Command-line parsing.
-args=`getopt -u -n mext r "$@"`;
-if [ "$?" = "1" ]; then
- usage;
+set -u
+
+_seq() {
+ local i="$1"
+ awk "BEGIN { for(i=1; i<=$i; i++) print i; }"
+}
+
+_pidof() {
+ local cmd="$1"
+ if ! pidof "$cmd" 2>/dev/null; then
+ ps -eo pid,ucomm | awk -v comm="$cmd" '$2 == comm { print $1 }'
+ fi
+}
+
+_lsof() {
+ local pid="$1"
+ if ! lsof -p $pid 2>/dev/null; then
+ /bin/ls -l /proc/$pid/fd 2>/dev/null
+ fi
+}
+
+
+
+_which() {
+ if [ -x /usr/bin/which ]; then
+ /usr/bin/which "$1" 2>/dev/null | awk '{print $1}'
+ elif which which 1>/dev/null 2>&1; then
+ which "$1" 2>/dev/null | awk '{print $1}'
+ else
+ echo "$1"
+ fi
+}
+
+# ###########################################################################
+# End alt_cmds package
+# ###########################################################################
+
+TOOL="pt-mext"
+
+# Parse command line options.
+mk_tmpdir
+parse_options "$0" "${@:-""}"
+
+if [ -z "$OPT_HELP" -a -z "$OPT_VERSION" ]; then
+ if [ -z "$EXT_ARGV" ]; then
+ option_error "No COMMAND was given."
+ fi
fi
-set -- $args
-for o; do
- case "$o" in
- -r) REL="1"; shift;;
- --) shift; break;;
- esac
-done
-if [ -z "$1" ]; then
- usage;
+usage_or_errors "$0"
+po_status=$?
+
+if [ $po_status -ne 0 ]; then
+ [ $OPT_ERRS -gt 0 ] && exit 1
+ exit 0
fi
+FILE="$PT_TMPDIR/mext_temp_file";
+NUM=1;
+
# Split the output on empty lines and put each into a different file; eliminate
# lines that don't have "real" content.
-$@ | grep -v '+' | grep -v Variable_name | sed 's/|//g' \
+$EXT_ARGV | grep -v '+' | grep -v Variable_name | sed 's/|//g' \
| while read line; do
if [ "$line" = "" ]; then
- NUM=`expr $NUM + 1`;
+ NUM=$(($NUM + 1))
echo "" > "$FILE$NUM"
fi
echo "$line" >> "$FILE$NUM"
done
-# Count how many files there are and prepare to format the output
SPEC="%-33s %13d"
AWKS=""
+
+# Count how many files there are and prepare to format the output, but...
NUM=`ls "$FILE"* | wc -l`;
-# The last file will be empty...
-NUM=`expr $NUM - 3`;
+
+# ... iterate through files 1..(N-2) because the last file is empty and
+# we join N to N+1 so also don't read the last real file.
+NUM=$((NUM - 2))
# Join each file with the next file, joining on the first field. Build a printf
# spec and awk spec at the same time.
-for i in `seq 0 $NUM`; do
- NEXTFILE=`expr $i + 1`;
+for i in `_seq $NUM`; do
+ NEXTFILE=$(($i + 1))
# Sort each file and eliminate empty lines, so 'join' doesn't complain.
sort "$FILE$i" | grep . > "$FILE$i.tmp"
@@ -119,10 +600,29 @@
MAXLEN=`awk '{print $2}' "$FILE${NEXTFILE}" | grep -v '[^0-9]' | awk '{print length($1)}' | sort -rn | head -n1`
mv "$FILE" "$FILE${NEXTFILE}"
SPEC="$SPEC %${MAXLEN}d";
- if [ "$REL" = "1" ]; then
- AWKS="$AWKS, \$`expr $i + 3` - \$`expr $i + 2`";
+
+ # The final file will contain lines like:
+ #
+ # Bytes_received 100 200 50 300
+ #
+ # For each such line in awk, $1 is the var name and $2 is the first value
+ # of the var, so these are fixed when we build AWKCMD after this loop.
+ # When i=1, we're comparing file1 to file2, and the resulting value becomes
+ # awk $3. Hence $i + 2=$3 below. Then incr and repeat for subsequent files.
+ #
+ # With --relative, the $i and awk field numbers are the same, but we print
+ # differences $3-$2, $4-$3, $5-$4 from the input line for awk fields $3, $4,
+ # and $5 respectively. Here's a table:
+ #
+ # i awk Input line fields
+ # == === =================
+ # 1 $3 $3-$2
+ # 2 $4 $4-$3
+ # 3 $5 $5-$4
+ if [ "$OPT_RELATIVE" ]; then
+ AWKS="$AWKS, \$`expr $i + 2` - \$`expr $i + 1`";
else
- AWKS="$AWKS, \$`expr $i + 3`";
+ AWKS="$AWKS, \$$(($i + 2))";
fi
done
@@ -153,7 +653,7 @@
Get output from C<mysqladmin>:
- pt-mext -r -- mysqladmin ext -i10 -c3"
+ pt-mext -r -- mysqladmin ext -i10 -c3
Get output from a file:
@@ -191,9 +691,19 @@
=over
-=item -r
+=item --help
+
+Show help and exit.
+
+=item --relative
+
+short form: -r
+
+Subtract each column from the previous column.
+
+=item --version
-Relative: subtract each column from the previous column.
+Show version and exit.
=back
@@ -281,7 +791,7 @@
=head1 VERSION
-pt-mext 2.2.1
+pt-mext 2.2.2
=cut
|
[-]
[+]
|
Changed |
percona-toolkit-2.2.2.tar.gz/bin/pt-mysql-summary
^
|
@@ -3111,7 +3111,7 @@
=head1 VERSION
-pt-mysql-summary 2.2.1
+pt-mysql-summary 2.2.2
=cut
|
[-]
[+]
|
Changed |
percona-toolkit-2.2.2.tar.gz/bin/pt-online-schema-change
^
|
@@ -53,7 +53,7 @@
# ###########################################################################
{
package Percona::Toolkit;
-our $VERSION = '2.2.1';
+our $VERSION = '2.2.2';
1;
}
@@ -1137,6 +1137,7 @@
# ###########################################################################
{
package Lmo::Utils;
+
use strict;
use warnings qw( FATAL all );
require Exporter;
@@ -1144,7 +1145,12 @@
BEGIN {
@ISA = qw(Exporter);
- @EXPORT = @EXPORT_OK = qw(_install_coderef _unimport_coderefs _glob_for _stash_for);
+ @EXPORT = @EXPORT_OK = qw(
+ _install_coderef
+ _unimport_coderefs
+ _glob_for
+ _stash_for
+ );
}
{
@@ -1328,7 +1334,6 @@
return Lmo::Meta->new(class => $class);
}
-
1;
}
# ###########################################################################
@@ -3741,9 +3746,9 @@
$dbh->{FetchHashKeyName} = 'NAME_lc' if $self->{NAME_lc};
- my $sql = 'SELECT @@hostname, @@server_id';
+ my $sql = 'SELECT @@server_id /*!50038 , @@hostname*/';
PTDEBUG && _d($dbh, $sql);
- my ($hostname, $server_id) = $dbh->selectrow_array($sql);
+ my ($server_id, $hostname) = $dbh->selectrow_array($sql);
PTDEBUG && _d($dbh, 'hostname:', $hostname, $server_id);
if ( $hostname ) {
$self->{hostname} = $hostname;
@@ -3786,6 +3791,32 @@
return $self->{hostname} || $self->{dsn_name} || 'unknown host';
}
+sub remove_duplicate_cxns {
+ my ($self, %args) = @_;
+ my @cxns = @{$args{cxns}};
+ my $seen_ids = $args{seen_ids} || {};
+ PTDEBUG && _d("Removing duplicates from ", join(" ", map { $_->name } @cxns));
+ my @trimmed_cxns;
+
+ for my $cxn ( @cxns ) {
+ my $dbh = $cxn->dbh();
+ my $sql = q{SELECT @@server_id};
+ PTDEBUG && _d($sql);
+ my ($id) = $dbh->selectrow_array($sql);
+ PTDEBUG && _d('Server ID for ', $cxn->name, ': ', $id);
+
+ if ( ! $seen_ids->{$id}++ ) {
+ push @trimmed_cxns, $cxn
+ }
+ else {
+ PTDEBUG && _d("Removing ", $cxn->name,
+ ", ID ", $id, ", because we've already seen it");
+ }
+ }
+
+ return \@trimmed_cxns;
+}
+
sub DESTROY {
my ($self) = @_;
@@ -3880,6 +3911,8 @@
my $dp = $self->{DSNParser};
my $methods = $self->_resolve_recursion_methods($args{dsn});
+ return $slaves unless @$methods;
+
if ( grep { m/processlist|hosts/i } @$methods ) {
my @required_args = qw(dbh dsn);
foreach my $arg ( @required_args ) {
@@ -7481,6 +7514,8 @@
use Lmo;
use Data::Dumper;
+{ local $EVAL_ERROR; eval { require Cxn } };
+
sub get_cluster_name {
my ($self, $cxn) = @_;
my $sql = "SHOW VARIABLES LIKE 'wsrep\_cluster\_name'";
@@ -7505,13 +7540,67 @@
sub same_node {
my ($self, $cxn1, $cxn2) = @_;
- my $sql = "SHOW VARIABLES LIKE 'wsrep\_sst\_receive\_address'";
- PTDEBUG && _d($cxn1->name, $sql);
- my (undef, $val1) = $cxn1->dbh->selectrow_array($sql);
- PTDEBUG && _d($cxn2->name, $sql);
- my (undef, $val2) = $cxn2->dbh->selectrow_array($sql);
+ foreach my $val ('wsrep\_sst\_receive\_address', 'wsrep\_node\_name', 'wsrep\_node\_address') {
+ my $sql = "SHOW VARIABLES LIKE '$val'";
+ PTDEBUG && _d($cxn1->name, $cxn2->name, $sql);
+ my (undef, $val1) = $cxn1->dbh->selectrow_array($sql);
+ my (undef, $val2) = $cxn2->dbh->selectrow_array($sql);
+
+ return unless ($val1 || '') eq ($val2 || '');
+ }
- return ($val1 || '') eq ($val2 || '');
+ return 1;
+}
+
+sub find_cluster_nodes {
+ my ($self, %args) = @_;
+
+ my $dbh = $args{dbh};
+ my $dsn = $args{dsn};
+ my $dp = $args{DSNParser};
+ my $make_cxn = $args{make_cxn};
+
+
+ my $sql = q{SHOW STATUS LIKE 'wsrep\_incoming\_addresses'};
+ PTDEBUG && _d($sql);
+ my (undef, $addresses) = $dbh->selectrow_array($sql);
+ PTDEBUG && _d("Cluster nodes found: ", $addresses);
+ return unless $addresses;
+
+ my @addresses = grep { !/\Aunspecified\z/i }
+ split /,\s*/, $addresses;
+
+ my @nodes;
+ foreach my $address ( @addresses ) {
+ my ($host, $port) = split /:/, $address;
+ my $spec = "h=$host"
+ . ($port ? ",P=$port" : "");
+ my $node_dsn = $dp->parse($spec, $dsn);
+ my $node_dbh = eval { $dp->get_dbh(
+ $dp->get_cxn_params($node_dsn), { AutoCommit => 1 }) };
+ if ( $EVAL_ERROR ) {
+ print STDERR "Cannot connect to ", $dp->as_string($node_dsn),
+ ", discovered through $sql: $EVAL_ERROR\n";
+ if ( !$port && $dsn->{P} != 3306 ) {
+ $address .= ":3306";
+ redo;
+ }
+ next;
+ }
+ PTDEBUG && _d('Connected to', $dp->as_string($node_dsn));
+ $node_dbh->disconnect();
+
+ push @nodes, $make_cxn->(dsn => $node_dsn);
+ }
+
+ return \@nodes;
+}
+
+sub remove_duplicate_cxns {
+ my ($self, %args) = @_;
+ my @cxns = @{$args{cxns}};
+ my $seen_ids = $args{seen_ids};
+ return Cxn->remove_duplicate_cxns(%args);
}
sub same_cluster {
@@ -7525,6 +7614,59 @@
return ($cluster1 || '') eq ($cluster2 || '');
}
+sub autodetect_nodes {
+ my ($self, %args) = @_;
+ my $ms = $args{MasterSlave};
+ my $dp = $args{DSNParser};
+ my $make_cxn = $args{make_cxn};
+ my $nodes = $args{nodes};
+ my $seen_ids = $args{seen_ids};
+
+ my $new_nodes = [];
+
+ return $new_nodes unless @$nodes;
+
+ for my $node ( @$nodes ) {
+ my $nodes_found = $self->find_cluster_nodes(
+ dbh => $node->dbh(),
+ dsn => $node->dsn(),
+ make_cxn => $make_cxn,
+ DSNParser => $dp,
+ );
+ push @$new_nodes, @$nodes_found;
+ }
+
+ $new_nodes = $self->remove_duplicate_cxns(
+ cxns => $new_nodes,
+ seen_ids => $seen_ids
+ );
+
+ my $new_slaves = [];
+ foreach my $node (@$new_nodes) {
+ my $node_slaves = $ms->get_slaves(
+ dbh => $node->dbh(),
+ dsn => $node->dsn(),
+ make_cxn => $make_cxn,
+ );
+ push @$new_slaves, @$node_slaves;
+ }
+
+ $new_slaves = $self->remove_duplicate_cxns(
+ cxns => $new_slaves,
+ seen_ids => $seen_ids
+ );
+
+ my @new_slave_nodes = grep { $self->is_cluster_node($_) } @$new_slaves;
+
+ my $slaves_of_slaves = $self->autodetect_nodes(
+ %args,
+ nodes => \@new_slave_nodes,
+ );
+
+ my @autodetected_nodes = ( @$new_nodes, @$new_slaves, @$slaves_of_slaves );
+ return \@autodetected_nodes;
+}
+
sub _d {
my ($package, undef, $line) = caller 0;
@_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
@@ -11255,6 +11397,6 @@
=head1 VERSION
-pt-online-schema-change 2.2.1
+pt-online-schema-change 2.2.2
=cut
|
[-]
[+]
|
Changed |
percona-toolkit-2.2.2.tar.gz/bin/pt-pmp
^
|
@@ -7,6 +7,54 @@
TOOL="pt-pmp"
# ###########################################################################
+# log_warn_die package
+# This package is a copy without comments from the original. The original
+# with comments and its test file can be found in the Bazaar repository at,
+# lib/bash/log_warn_die.sh
+# t/lib/bash/log_warn_die.sh
+# See https://launchpad.net/percona-toolkit for more information.
+# ###########################################################################
+
+
+set -u
+
+PTFUNCNAME=""
+PTDEBUG="${PTDEBUG:-""}"
+EXIT_STATUS=0
+
+ts() {
+ TS=$(date +%F-%T | tr ':-' '_')
+ echo "$TS $*"
+}
+
+info() {
+ [ ${OPT_VERBOSE:-3} -ge 3 ] && ts "$*"
+}
+
+log() {
+ [ ${OPT_VERBOSE:-3} -ge 2 ] && ts "$*"
+}
+
+warn() {
+ [ ${OPT_VERBOSE:-3} -ge 1 ] && ts "$*" >&2
+ EXIT_STATUS=1
+}
+
+die() {
+ ts "$*" >&2
+ EXIT_STATUS=1
+ exit 1
+}
+
+_d () {
+ [ "$PTDEBUG" ] && echo "# $PTFUNCNAME: $(ts "$*")" >&2
+}
+
+# ###########################################################################
+# End log_warn_die package
+# ###########################################################################
+
+# ###########################################################################
# tmpdir package
# This package is a copy without comments from the original. The original
# with comments and its test file can be found in the Bazaar repository at,
@@ -47,17 +95,451 @@
# End tmpdir package
# ###########################################################################
-set +u
+# ###########################################################################
+# parse_options package
+# This package is a copy without comments from the original. The original
+# with comments and its test file can be found in the Bazaar repository at,
+# lib/bash/parse_options.sh
+# t/lib/bash/parse_options.sh
+# See https://launchpad.net/percona-toolkit for more information.
+# ###########################################################################
+
+
+
+
+
+set -u
+
+ARGV="" # Non-option args (probably input files)
+EXT_ARGV="" # Everything after -- (args for an external command)
+HAVE_EXT_ARGV="" # Got --, everything else is put into EXT_ARGV
+OPT_ERRS=0 # How many command line option errors
+OPT_VERSION="" # If --version was specified
+OPT_HELP="" # If --help was specified
+PO_DIR="" # Directory with program option spec files
usage() {
- if [ "${OPT_ERR}" ]; then
- echo "${OPT_ERR}" >&2
+ local file="$1"
+
+ local usage="$(grep '^Usage: ' "$file")"
+ echo $usage
+ echo
+ echo "For more information, 'man $TOOL' or 'perldoc $file'."
+}
+
+usage_or_errors() {
+ local file="$1"
+
+ if [ "$OPT_VERSION" ]; then
+ local version=$(grep '^pt-[^ ]\+ [0-9]' "$file")
+ echo "$version"
+ return 1
fi
- echo "Usage: pt-pmp [OPTIONS] [FILES]" >&2
- echo "For more information, 'man pt-pmp' or 'perldoc $0'" >&2
- exit 1
+
+ if [ "$OPT_HELP" ]; then
+ usage "$file"
+ echo
+ echo "Command line options:"
+ echo
+ perl -e '
+ use strict;
+ use warnings FATAL => qw(all);
+ my $lcol = 20; # Allow this much space for option names.
+ my $rcol = 80 - $lcol; # The terminal is assumed to be 80 chars wide.
+ my $name;
+ while ( <> ) {
+ my $line = $_;
+ chomp $line;
+ if ( $line =~ s/^long:/ --/ ) {
+ $name = $line;
+ }
+ elsif ( $line =~ s/^desc:// ) {
+ $line =~ s/ +$//mg;
+ my @lines = grep { $_ }
+ $line =~ m/(.{0,$rcol})(?:\s+|\Z)/g;
+ if ( length($name) >= $lcol ) {
+ print $name, "\n", (q{ } x $lcol);
+ }
+ else {
+ printf "%-${lcol}s", $name;
+ }
+ print join("\n" . (q{ } x $lcol), @lines);
+ print "\n";
+ }
+ }
+ ' "$PO_DIR"/*
+ echo
+ echo "Options and values after processing arguments:"
+ echo
+ (
+ cd "$PO_DIR"
+ for opt in *; do
+ local varname="OPT_$(echo "$opt" | tr a-z- A-Z_)"
+ eval local varvalue=\$$varname
+ if ! grep -q "type:" "$PO_DIR/$opt" >/dev/null; then
+ if [ "$varvalue" -a "$varvalue" = "yes" ];
+ then varvalue="TRUE"
+ else
+ varvalue="FALSE"
+ fi
+ fi
+ printf -- " --%-30s %s" "$opt" "${varvalue:-(No value)}"
+ echo
+ done
+ )
+ return 1
+ fi
+
+ if [ $OPT_ERRS -gt 0 ]; then
+ echo
+ usage "$file"
+ return 1
+ fi
+
+ return 0
+}
+
+option_error() {
+ local err="$1"
+ OPT_ERRS=$(($OPT_ERRS + 1))
+ echo "$err" >&2
+}
+
+parse_options() {
+ local file="$1"
+ shift
+
+ ARGV=""
+ EXT_ARGV=""
+ HAVE_EXT_ARGV=""
+ OPT_ERRS=0
+ OPT_VERSION=""
+ OPT_HELP=""
+ PO_DIR="$PT_TMPDIR/po"
+
+ if [ ! -d "$PO_DIR" ]; then
+ mkdir "$PO_DIR"
+ if [ $? -ne 0 ]; then
+ echo "Cannot mkdir $PO_DIR" >&2
+ exit 1
+ fi
+ fi
+
+ rm -rf "$PO_DIR"/*
+ if [ $? -ne 0 ]; then
+ echo "Cannot rm -rf $PO_DIR/*" >&2
+ exit 1
+ fi
+
+ _parse_pod "$file" # Parse POD into program option (po) spec files
+ _eval_po # Eval po into existence with default values
+
+ if [ $# -ge 2 ] && [ "$1" = "--config" ]; then
+ shift # --config
+ local user_config_files="$1"
+ shift # that ^
+ local IFS=","
+ for user_config_file in $user_config_files; do
+ _parse_config_files "$user_config_file"
+ done
+ else
+ _parse_config_files "/etc/percona-toolkit/percona-toolkit.conf" "/etc/percona-toolkit/$TOOL.conf" "$HOME/.percona-toolkit.conf" "$HOME/.$TOOL.conf"
+ fi
+
+ _parse_command_line "${@:-""}"
+}
+
+_parse_pod() {
+ local file="$1"
+
+ cat "$file" | PO_DIR="$PO_DIR" perl -ne '
+ BEGIN { $/ = ""; }
+ next unless $_ =~ m/^=head1 OPTIONS/;
+ while ( defined(my $para = <>) ) {
+ last if $para =~ m/^=head1/;
+ chomp;
+ if ( $para =~ m/^=item --(\S+)/ ) {
+ my $opt = $1;
+ my $file = "$ENV{PO_DIR}/$opt";
+ open my $opt_fh, ">", $file or die "Cannot open $file: $!";
+ print $opt_fh "long:$opt\n";
+ $para = <>;
+ chomp;
+ if ( $para =~ m/^[a-z ]+:/ ) {
+ map {
+ chomp;
+ my ($attrib, $val) = split(/: /, $_);
+ print $opt_fh "$attrib:$val\n";
+ } split(/; /, $para);
+ $para = <>;
+ chomp;
+ }
+ my ($desc) = $para =~ m/^([^?.]+)/;
+ print $opt_fh "desc:$desc.\n";
+ close $opt_fh;
+ }
+ }
+ last;
+ '
+}
+
+_eval_po() {
+ local IFS=":"
+ for opt_spec in "$PO_DIR"/*; do
+ local opt=""
+ local default_val=""
+ local neg=0
+ local size=0
+ while read key val; do
+ case "$key" in
+ long)
+ opt=$(echo $val | sed 's/-/_/g' | tr '[:lower:]' '[:upper:]')
+ ;;
+ default)
+ default_val="$val"
+ ;;
+ "short form")
+ ;;
+ type)
+ [ "$val" = "size" ] && size=1
+ ;;
+ desc)
+ ;;
+ negatable)
+ if [ "$val" = "yes" ]; then
+ neg=1
+ fi
+ ;;
+ *)
+ echo "Invalid attribute in $opt_spec: $line" >&2
+ exit 1
+ esac
+ done < "$opt_spec"
+
+ if [ -z "$opt" ]; then
+ echo "No long attribute in option spec $opt_spec" >&2
+ exit 1
+ fi
+
+ if [ $neg -eq 1 ]; then
+ if [ -z "$default_val" ] || [ "$default_val" != "yes" ]; then
+ echo "Option $opt_spec is negatable but not default: yes" >&2
+ exit 1
+ fi
+ fi
+
+ if [ $size -eq 1 -a -n "$default_val" ]; then
+ default_val=$(size_to_bytes $default_val)
+ fi
+
+ eval "OPT_${opt}"="$default_val"
+ done
}
+_parse_config_files() {
+
+ for config_file in "${@:-""}"; do
+ test -f "$config_file" || continue
+
+ while read config_opt; do
+
+ echo "$config_opt" | grep '^[ ]*[^#]' >/dev/null 2>&1 || continue
+
+ config_opt="$(echo "$config_opt" | sed -e 's/^ *//g' -e 's/ *$//g' -e 's/[ ]*=[ ]*/=/' -e 's/[ ]*#.*$//')"
+
+ [ "$config_opt" = "" ] && continue
+
+ if ! [ "$HAVE_EXT_ARGV" ]; then
+ config_opt="--$config_opt"
+ fi
+
+ _parse_command_line "$config_opt"
+
+ done < "$config_file"
+
+ HAVE_EXT_ARGV="" # reset for each file
+
+ done
+}
+
+_parse_command_line() {
+ local opt=""
+ local val=""
+ local next_opt_is_val=""
+ local opt_is_ok=""
+ local opt_is_negated=""
+ local real_opt=""
+ local required_arg=""
+ local spec=""
+
+ for opt in "${@:-""}"; do
+ if [ "$opt" = "--" -o "$opt" = "----" ]; then
+ HAVE_EXT_ARGV=1
+ continue
+ fi
+ if [ "$HAVE_EXT_ARGV" ]; then
+ if [ "$EXT_ARGV" ]; then
+ EXT_ARGV="$EXT_ARGV $opt"
+ else
+ EXT_ARGV="$opt"
+ fi
+ continue
+ fi
+
+ if [ "$next_opt_is_val" ]; then
+ next_opt_is_val=""
+ if [ $# -eq 0 ] || [ $(expr "$opt" : "\-") -eq 1 ]; then
+ option_error "$real_opt requires a $required_arg argument"
+ continue
+ fi
+ val="$opt"
+ opt_is_ok=1
+ else
+ if [ $(expr "$opt" : "\-") -eq 0 ]; then
+ if [ -z "$ARGV" ]; then
+ ARGV="$opt"
+ else
+ ARGV="$ARGV $opt"
+ fi
+ continue
+ fi
+
+ real_opt="$opt"
+
+ if $(echo $opt | grep '^--no[^-]' >/dev/null); then
+ local base_opt=$(echo $opt | sed 's/^--no//')
+ if [ -f "$PT_TMPDIR/po/$base_opt" ]; then
+ opt_is_negated=1
+ opt="$base_opt"
+ else
+ opt_is_negated=""
+ opt=$(echo $opt | sed 's/^-*//')
+ fi
+ else
+ if $(echo $opt | grep '^--no-' >/dev/null); then
+ opt_is_negated=1
+ opt=$(echo $opt | sed 's/^--no-//')
+ else
+ opt_is_negated=""
+ opt=$(echo $opt | sed 's/^-*//')
+ fi
+ fi
+
+ if $(echo $opt | grep '^[a-z-][a-z-]*=' >/dev/null 2>&1); then
+ val="$(echo $opt | awk -F= '{print $2}')"
+ opt="$(echo $opt | awk -F= '{print $1}')"
+ fi
+
+ if [ -f "$PT_TMPDIR/po/$opt" ]; then
+ spec="$PT_TMPDIR/po/$opt"
+ else
+ spec=$(grep "^short form:-$opt\$" "$PT_TMPDIR"/po/* | cut -d ':' -f 1)
+ if [ -z "$spec" ]; then
+ option_error "Unknown option: $real_opt"
+ continue
+ fi
+ fi
+
+ required_arg=$(cat "$spec" | awk -F: '/^type:/{print $2}')
+ if [ "$required_arg" ]; then
+ if [ "$val" ]; then
+ opt_is_ok=1
+ else
+ next_opt_is_val=1
+ fi
+ else
+ if [ "$val" ]; then
+ option_error "Option $real_opt does not take a value"
+ continue
+ fi
+ if [ "$opt_is_negated" ]; then
+ val=""
+ else
+ val="yes"
+ fi
+ opt_is_ok=1
+ fi
+ fi
+
+ if [ "$opt_is_ok" ]; then
+ opt=$(cat "$spec" | grep '^long:' | cut -d':' -f2 | sed 's/-/_/g' | tr '[:lower:]' '[:upper:]')
+
+ if grep "^type:size" "$spec" >/dev/null; then
+ val=$(size_to_bytes $val)
+ fi
+
+ eval "OPT_$opt"="'$val'"
+
+ opt=""
+ val=""
+ next_opt_is_val=""
+ opt_is_ok=""
+ opt_is_negated=""
+ real_opt=""
+ required_arg=""
+ spec=""
+ fi
+ done
+}
+
+size_to_bytes() {
+ local size="$1"
+ 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")};'
+}
+
+# ###########################################################################
+# End parse_options package
+# ###########################################################################
+
+# ###########################################################################
+# alt_cmds package
+# This package is a copy without comments from the original. The original
+# with comments and its test file can be found in the Bazaar repository at,
+# lib/bash/alt_cmds.sh
+# t/lib/bash/alt_cmds.sh
+# See https://launchpad.net/percona-toolkit for more information.
+# ###########################################################################
+
+
+set -u
+
+_seq() {
+ local i="$1"
+ awk "BEGIN { for(i=1; i<=$i; i++) print i; }"
+}
+
+_pidof() {
+ local cmd="$1"
+ if ! pidof "$cmd" 2>/dev/null; then
+ ps -eo pid,ucomm | awk -v comm="$cmd" '$2 == comm { print $1 }'
+ fi
+}
+
+_lsof() {
+ local pid="$1"
+ if ! lsof -p $pid 2>/dev/null; then
+ /bin/ls -l /proc/$pid/fd 2>/dev/null
+ fi
+}
+
+
+
+_which() {
+ if [ -x /usr/bin/which ]; then
+ /usr/bin/which "$1" 2>/dev/null | awk '{print $1}'
+ elif which which 1>/dev/null 2>&1; then
+ which "$1" 2>/dev/null | awk '{print $1}'
+ else
+ echo "$1"
+ fi
+}
+
+# ###########################################################################
+# End alt_cmds package
+# ###########################################################################
+
+set +u
+
# Actually does the aggregation. The arguments are the max number of functions
# to aggregate, and the files to read. If maxlen=0, it means infinity. We have
# to pass the maxlen argument into this function to make maxlen testable.
@@ -153,70 +635,35 @@
# The main program to run.
main() {
+ local output_file="${OPT_SAVE_SAMPLES:-"$PT_TMPDIR/percona-toolkit"}"
- # Get command-line options
- for o; do
- case "${o}" in
- --)
- shift; break;
- ;;
- --help)
- usage;
- ;;
- -b)
- shift; OPT_b="${1}"; shift;
- ;;
- -i)
- shift; OPT_i="${1}"; shift;
- ;;
- -k)
- shift; OPT_k="${1}"; shift;
- ;;
- -l)
- shift; OPT_l="${1}"; shift;
- ;;
- -p)
- shift; OPT_p="${1}"; shift;
- ;;
- -s)
- shift; OPT_s="${1}"; shift;
- ;;
- -*)
- OPT_ERR="Unknown option ${o}."
- usage
- ;;
- esac
- done
- export OPT_i="${OPT_i:-1}";
- export OPT_k="${OPT_k:-}";
- export OPT_l="${OPT_l:-0}";
- export OPT_b="${OPT_b:-mysqld}";
- export OPT_p="${OPT_p:-}";
- export OPT_s="${OPT_s:-0}";
-
- if [ -z "${1}" ]; then
- # There's no file to analyze, so we'll make one.
- if [ -z "${OPT_p}" ]; then
- OPT_p=$(pidof -s "${OPT_b}" 2>/dev/null);
- if [ -z "${OPT_p}" ]; then
- OPT_p=$(pgrep -o -x "${OPT_b}" 2>/dev/null)
+ if [ -z "$ARGV" ]; then
+ # There are no files to analyze, so we'll make one.
+ if [ -z "$OPT_PID" ]; then
+ OPT_PID=$(pidof -s "$OPT_BINARY" 2>/dev/null);
+ if [ -z "$OPT_PID" ]; then
+ OPT_PID=$(pgrep -o -x "$OPT_BINARY" 2>/dev/null)
fi
- if [ -z "${OPT_p}" ]; then
- OPT_p=$(ps -eaf | grep "${OPT_b}" | grep -v grep | awk '{print $2}' | head -n1);
+ if [ -z "$OPT_PID" ]; then
+ OPT_PID=$(ps -eaf | grep "$OPT_BINARY" | grep -v grep | awk '{print $2}' | head -n1);
fi
fi
- date;
- for x in $(seq 1 $OPT_i); do
- gdb -ex "set pagination 0" -ex "thread apply all bt" -batch -p $OPT_p >> "${OPT_k:-$PT_TMPDIR/percona-toolkit}"
- date +'TS %N.%s %F %T' >> "${OPT_k:-$PT_TMPDIR/percona-toolkit}"
- sleep $OPT_s
+ date
+ for x in $(_seq $OPT_ITERATIONS); do
+ gdb -ex "set pagination 0" \
+ -ex "thread apply all bt" \
+ -batch \
+ -p $OPT_PID \
+ >> "$output_file"
+ date +'TS %N.%s %F %T' >> "$output_file"
+ sleep $OPT_INTERVAL
done
fi
- if [ $# -eq 0 ]; then
- aggregate_stacktrace "${OPT_l}" "${OPT_k:-$PT_TMPDIR/percona-toolkit}"
+ if [ -z "$ARGV" ]; then
+ aggregate_stacktrace "$OPT_LINES" "$output_file"
else
- aggregate_stacktrace "${OPT_l}" "$@"
+ aggregate_stacktrace "$OPT_LINES" $ARGV
fi
}
@@ -224,8 +671,23 @@
# possible to include without executing, and thus test.
if [ "${0##*/}" = "$TOOL" ] \
|| [ "${0##*/}" = "bash" -a "${_:-""}" = "$0" ]; then
+
mk_tmpdir
- main "$@"
+
+ parse_options "$0" "${@:-""}"
+ if [ -z "$OPT_HELP" -a -z "$OPT_VERSION" ]; then
+ # Validate options
+ :
+ fi
+ usage_or_errors "$0"
+ po_status=$?
+ if [ $po_status -ne 0 ]; then
+ [ $OPT_ERRS -gt 0 ] && exit 1
+ exit 0
+ fi
+
+ main $ARGV
+
rm_tmpdir
fi
@@ -289,33 +751,51 @@
=head1 OPTIONS
-Options must precede files on the command line.
-
=over
-=item -b BINARY
+=item --binary
+
+short form: -b; type: string; default: mysqld
+
+Which binary to trace.
+
+=item --help
+
+Show help and exit.
+
+=item --interval
+
+short form: -s; type: int; default: 0
+
+Number of seconds to sleep between L<"--iterations">.
+
+=item --iterations
+
+short form: -i; type: int; default: 1
+
+How many traces to gather and aggregate.
-Which binary to trace (default mysqld)
+=item --lines
-=item -i ITERATIONS
+short form: -l; type: int; default: 0
-How many traces to gather and aggregate (default 1)
+Aggregate only first specified number of many functions; 0=infinity.
-=item -k KEEPFILE
+=item --pid
-Keep the raw traces in this file after aggregation
+short form: -p; type: int
-=item -l NUMBER
+Process ID of the process to trace; overrides L<"--binary">.
-Aggregate only first NUMBER functions; 0=infinity (default 0)
+=item --save-samples
-=item -p PID
+short form: -k; type: string
-Process ID of the process to trace; overrides -b
+Keep the raw traces in this file after aggregation.
-=item -s SLEEPTIME
+=item --version
-Number of seconds to sleep between iterations (default 0)
+Show version and exit.
=back
@@ -405,7 +885,7 @@
=head1 VERSION
-pt-pmp 2.2.1
+pt-pmp 2.2.2
=cut
|
[-]
[+]
|
Changed |
percona-toolkit-2.2.2.tar.gz/bin/pt-query-digest
^
|
@@ -63,7 +63,7 @@
# ###########################################################################
{
package Percona::Toolkit;
-our $VERSION = '2.2.1';
+our $VERSION = '2.2.2';
1;
}
@@ -81,6 +81,7 @@
# ###########################################################################
{
package Lmo::Utils;
+
use strict;
use warnings qw( FATAL all );
require Exporter;
@@ -88,7 +89,12 @@
BEGIN {
@ISA = qw(Exporter);
- @EXPORT = @EXPORT_OK = qw(_install_coderef _unimport_coderefs _glob_for _stash_for);
+ @EXPORT = @EXPORT_OK = qw(
+ _install_coderef
+ _unimport_coderefs
+ _glob_for
+ _stash_for
+ );
}
{
@@ -272,7 +278,6 @@
return Lmo::Meta->new(class => $class);
}
-
1;
}
# ###########################################################################
@@ -3210,10 +3215,24 @@
$new_query = 1;
}
elsif ( $curr->[INFO] && defined $curr->[TIME]
- && $query_start - $etime - $prev->[START] > $fudge ) {
- PTDEBUG && _d('Query restarted; new query',
- $query_start, $etime, $prev->[START], $fudge);
- $new_query = 1;
+ && $query_start - $etime - $prev->[START] > $fudge)
+ {
+ my $ms = $self->{MasterSlave};
+
+ my $is_repl_thread = $ms->is_replication_thread({
+ Command => $curr->[COMMAND],
+ User => $curr->[USER],
+ State => $curr->[STATE],
+ Id => $curr->[ID]});
+ if ( $is_repl_thread ) {
+ PTDEBUG &&
+ _d(q{Query has restarted but it's a replication thread, ignoring});
+ }
+ else {
+ PTDEBUG && _d('Query restarted; new query',
+ $query_start, $etime, $prev->[START], $fudge);
+ $new_query = 1;
+ }
}
if ( $new_query ) {
@@ -3789,6 +3808,7 @@
sessions => {},
o => $args{o},
fake_thread_id => 2**32, # see _make_event()
+ null_event => $args{null_event},
};
PTDEBUG && $self->{server} && _d('Watching only server', $self->{server});
return bless $self, $class;
@@ -3809,7 +3829,7 @@
$server .= ":$self->{port}";
if ( $src_host ne $server && $dst_host ne $server ) {
PTDEBUG && _d('Packet is not to or from', $server);
- return;
+ return $self->{null_event};
}
}
@@ -3825,7 +3845,7 @@
}
else {
PTDEBUG && _d('Packet is not to or from a MySQL server');
- return;
+ return $self->{null_event};
}
PTDEBUG && _d('Client', $client);
@@ -3843,7 +3863,7 @@
else {
PTDEBUG && _d('Ignoring mid-stream', $packet_from, 'data,',
'packetno', $packetno);
- return;
+ return $self->{null_event};
}
$self->{sessions}->{$client} = {
@@ -3886,7 +3906,7 @@
delete $self->{sessions}->{$session->{client}};
return $event;
}
- return;
+ return $self->{null_event};
}
if ( $session->{compress} ) {
@@ -3912,7 +3932,7 @@
PTDEBUG && _d('remove_mysql_header() failed; failing session');
$session->{EVAL_ERROR} = $EVAL_ERROR;
$self->fail_session($session, 'remove_mysql_header() failed');
- return;
+ return $self->{null_event};
}
}
@@ -3927,7 +3947,7 @@
$self->_delete_buff($session);
}
else {
- return; # waiting for more data; buff_left was reported earlier
+ return $self->{null_event}; # waiting for more data; buff_left was reported earlier
}
}
elsif ( $packet->{mysql_data_len} > ($packet->{data_len} - 4) ) {
@@ -3948,7 +3968,7 @@
PTDEBUG && _d('Data not complete; expecting',
$session->{buff_left}, 'more bytes');
- return;
+ return $self->{null_event};
}
if ( $session->{cmd} && ($session->{state} || '') eq 'awaiting_reply' ) {
@@ -3971,7 +3991,7 @@
}
$args{stats}->{events_parsed}++ if $args{stats};
- return $event;
+ return $event || $self->{null_event};
}
sub _packet_from_server {
@@ -6519,6 +6539,12 @@
default => sub { [qw(pct total min max avg 95% stddev median)] },
);
+has show_all => (
+ is => 'ro',
+ isa => 'HashRef',
+ default => sub { {} },
+);
+
has ReportFormatter => (
is => 'ro',
isa => 'ReportFormatter',
@@ -7398,11 +7424,13 @@
sub format_string_list {
my ( $self, $attrib, $vals, $class_cnt ) = @_;
-
+
if ( !exists $vals->{unq} ) {
return ($vals->{cnt});
}
+ my $show_all = $self->show_all();
+
my $cnt_for = $vals->{unq};
if ( 1 == keys %$cnt_for ) {
my ($str) = keys %$cnt_for;
@@ -7427,6 +7455,9 @@
}
my $p = percentage_of($cnt_for->{$str}, $class_cnt);
$print_str .= " ($cnt_for->{$str}/$p%)";
+ if ( !$show_all->{$attrib} ) {
+ last if (length $line) + (length $print_str) > LINE_LENGTH - 27;
+ }
$line .= "$print_str, ";
$i++;
}
@@ -9831,7 +9862,15 @@
return $self->{errors_fh} if $self->{errors_fh};
my $exec = basename($0);
- my ($errors_fh, $filename) = tempfile("/tmp/$exec-errors.XXXXXXX", UNLINK => 0);
+ my ($errors_fh, $filename);
+ if ( $filename = $ENV{PERCONA_TOOLKIT_TCP_ERRORS_FILE} ) {
+ open $errors_fh, ">", $filename
+ or die "Cannot open $filename for writing (supplied from "
+ . "PERCONA_TOOLKIT_TCP_ERRORS_FILE): $OS_ERROR";
+ }
+ else {
+ ($errors_fh, $filename) = tempfile("/tmp/$exec-errors.XXXXXXX", UNLINK => 0);
+ }
$self->{errors_file} = $filename;
$self->{errors_fh} = $errors_fh;
@@ -9847,7 +9886,8 @@
my $errors_fh = $self->_get_errors_fh();
- print "Session $session->{client} had errors, will save them in $self->{errors_file}\n";
+ warn "TCP session $session->{client} had errors, will save them in $self->{errors_file}\n"
+ unless $self->{_warned_for}->{$self->{errors_file}}++;
my $raw_packets = delete $session->{raw_packets};
$session->{reason_for_failure} = $reason;
@@ -9971,6 +10011,8 @@
my $dp = $self->{DSNParser};
my $methods = $self->_resolve_recursion_methods($args{dsn});
+ return $slaves unless @$methods;
+
if ( grep { m/processlist|hosts/i } @$methods ) {
my @required_args = qw(dbh dsn);
foreach my $arg ( @required_args ) {
@@ -13749,6 +13791,8 @@
my @groupby = @{$args{groupby}};
my @orderby = @{$args{orderby}};
+ my $show_all = $o->get('show-all');
+
for my $i ( 0..$#groupby ) {
if ( $o->get('report') || $qv || $qh ) {
$eas->[$i]->calculate_statistical_metrics();
@@ -13799,6 +13843,7 @@
OptionParser => $args{OptionParser},
QueryParser => $args{QueryParser},
Quoter => $args{Quoter},
+ show_all => $show_all,
);
$qrf->print_reports(
@@ -15502,6 +15547,18 @@
The tool prints a warning and continues if a variable cannot be set.
+=item --show-all
+
+type: Hash
+
+Show all values for these attributes.
+
+By default pt-query-digest only shows as many of an attribute's value that
+fit on a single line. This option allows you to specify attributes for which
+all values will be shown (line width is ignored). This only works for
+attributes with string values like user, host, db, etc. Multiple attributes
+can be specified, comma-separated.
+
=item --since
type: string
@@ -16027,6 +16084,6 @@
=head1 VERSION
-pt-query-digest 2.2.1
+pt-query-digest 2.2.2
=cut
|
[-]
[+]
|
Changed |
percona-toolkit-2.2.2.tar.gz/bin/pt-show-grants
^
|
@@ -2396,6 +2396,6 @@
=head1 VERSION
-pt-show-grants 2.2.1
+pt-show-grants 2.2.2
=cut
|
[-]
[+]
|
Changed |
percona-toolkit-2.2.2.tar.gz/bin/pt-sift
^
|
@@ -4,15 +4,54 @@
# See "COPYRIGHT, LICENSE, AND WARRANTY" at the end of this file for legal
# notices and disclaimers.
-usage() {
- if [ "${OPT_ERR}" ]; then
- echo "Error: $OPT_ERR" >&2
- fi
- echo "Usage: pt-sift FILE|PREFIX|DIRECTORY" >&2
- echo "For more information, 'man pt-sift' or 'perldoc $0'." >&2
+# ###########################################################################
+# log_warn_die package
+# This package is a copy without comments from the original. The original
+# with comments and its test file can be found in the Bazaar repository at,
+# lib/bash/log_warn_die.sh
+# t/lib/bash/log_warn_die.sh
+# See https://launchpad.net/percona-toolkit for more information.
+# ###########################################################################
+
+
+set -u
+
+PTFUNCNAME=""
+PTDEBUG="${PTDEBUG:-""}"
+EXIT_STATUS=0
+
+ts() {
+ TS=$(date +%F-%T | tr ':-' '_')
+ echo "$TS $*"
+}
+
+info() {
+ [ ${OPT_VERBOSE:-3} -ge 3 ] && ts "$*"
+}
+
+log() {
+ [ ${OPT_VERBOSE:-3} -ge 2 ] && ts "$*"
+}
+
+warn() {
+ [ ${OPT_VERBOSE:-3} -ge 1 ] && ts "$*" >&2
+ EXIT_STATUS=1
+}
+
+die() {
+ ts "$*" >&2
+ EXIT_STATUS=1
exit 1
}
+_d () {
+ [ "$PTDEBUG" ] && echo "# $PTFUNCNAME: $(ts "$*")" >&2
+}
+
+# ###########################################################################
+# End log_warn_die package
+# ###########################################################################
+
# ###########################################################################
# tmpdir package
# This package is a copy without comments from the original. The original
@@ -55,6 +94,402 @@
# ###########################################################################
# ###########################################################################
+# parse_options package
+# This package is a copy without comments from the original. The original
+# with comments and its test file can be found in the Bazaar repository at,
+# lib/bash/parse_options.sh
+# t/lib/bash/parse_options.sh
+# See https://launchpad.net/percona-toolkit for more information.
+# ###########################################################################
+
+
+
+
+
+set -u
+
+ARGV="" # Non-option args (probably input files)
+EXT_ARGV="" # Everything after -- (args for an external command)
+HAVE_EXT_ARGV="" # Got --, everything else is put into EXT_ARGV
+OPT_ERRS=0 # How many command line option errors
+OPT_VERSION="" # If --version was specified
+OPT_HELP="" # If --help was specified
+PO_DIR="" # Directory with program option spec files
+
+usage() {
+ local file="$1"
+
+ local usage="$(grep '^Usage: ' "$file")"
+ echo $usage
+ echo
+ echo "For more information, 'man $TOOL' or 'perldoc $file'."
+}
+
+usage_or_errors() {
+ local file="$1"
+
+ if [ "$OPT_VERSION" ]; then
+ local version=$(grep '^pt-[^ ]\+ [0-9]' "$file")
+ echo "$version"
+ return 1
+ fi
+
+ if [ "$OPT_HELP" ]; then
+ usage "$file"
+ echo
+ echo "Command line options:"
+ echo
+ perl -e '
+ use strict;
+ use warnings FATAL => qw(all);
+ my $lcol = 20; # Allow this much space for option names.
+ my $rcol = 80 - $lcol; # The terminal is assumed to be 80 chars wide.
+ my $name;
+ while ( <> ) {
+ my $line = $_;
+ chomp $line;
+ if ( $line =~ s/^long:/ --/ ) {
+ $name = $line;
+ }
+ elsif ( $line =~ s/^desc:// ) {
+ $line =~ s/ +$//mg;
+ my @lines = grep { $_ }
+ $line =~ m/(.{0,$rcol})(?:\s+|\Z)/g;
+ if ( length($name) >= $lcol ) {
+ print $name, "\n", (q{ } x $lcol);
+ }
+ else {
+ printf "%-${lcol}s", $name;
+ }
+ print join("\n" . (q{ } x $lcol), @lines);
+ print "\n";
+ }
+ }
+ ' "$PO_DIR"/*
+ echo
+ echo "Options and values after processing arguments:"
+ echo
+ (
+ cd "$PO_DIR"
+ for opt in *; do
+ local varname="OPT_$(echo "$opt" | tr a-z- A-Z_)"
+ eval local varvalue=\$$varname
+ if ! grep -q "type:" "$PO_DIR/$opt" >/dev/null; then
+ if [ "$varvalue" -a "$varvalue" = "yes" ];
+ then varvalue="TRUE"
+ else
+ varvalue="FALSE"
+ fi
+ fi
+ printf -- " --%-30s %s" "$opt" "${varvalue:-(No value)}"
+ echo
+ done
+ )
+ return 1
+ fi
+
+ if [ $OPT_ERRS -gt 0 ]; then
+ echo
+ usage "$file"
+ return 1
+ fi
+
+ return 0
+}
+
+option_error() {
+ local err="$1"
+ OPT_ERRS=$(($OPT_ERRS + 1))
+ echo "$err" >&2
+}
+
+parse_options() {
+ local file="$1"
+ shift
+
+ ARGV=""
+ EXT_ARGV=""
+ HAVE_EXT_ARGV=""
+ OPT_ERRS=0
+ OPT_VERSION=""
+ OPT_HELP=""
+ PO_DIR="$PT_TMPDIR/po"
+
+ if [ ! -d "$PO_DIR" ]; then
+ mkdir "$PO_DIR"
+ if [ $? -ne 0 ]; then
+ echo "Cannot mkdir $PO_DIR" >&2
+ exit 1
+ fi
+ fi
+
+ rm -rf "$PO_DIR"/*
+ if [ $? -ne 0 ]; then
+ echo "Cannot rm -rf $PO_DIR/*" >&2
+ exit 1
+ fi
+
+ _parse_pod "$file" # Parse POD into program option (po) spec files
+ _eval_po # Eval po into existence with default values
+
+ if [ $# -ge 2 ] && [ "$1" = "--config" ]; then
+ shift # --config
+ local user_config_files="$1"
+ shift # that ^
+ local IFS=","
+ for user_config_file in $user_config_files; do
+ _parse_config_files "$user_config_file"
+ done
+ else
+ _parse_config_files "/etc/percona-toolkit/percona-toolkit.conf" "/etc/percona-toolkit/$TOOL.conf" "$HOME/.percona-toolkit.conf" "$HOME/.$TOOL.conf"
+ fi
+
+ _parse_command_line "${@:-""}"
+}
+
+_parse_pod() {
+ local file="$1"
+
+ cat "$file" | PO_DIR="$PO_DIR" perl -ne '
+ BEGIN { $/ = ""; }
+ next unless $_ =~ m/^=head1 OPTIONS/;
+ while ( defined(my $para = <>) ) {
+ last if $para =~ m/^=head1/;
+ chomp;
+ if ( $para =~ m/^=item --(\S+)/ ) {
+ my $opt = $1;
+ my $file = "$ENV{PO_DIR}/$opt";
+ open my $opt_fh, ">", $file or die "Cannot open $file: $!";
+ print $opt_fh "long:$opt\n";
+ $para = <>;
+ chomp;
+ if ( $para =~ m/^[a-z ]+:/ ) {
+ map {
+ chomp;
+ my ($attrib, $val) = split(/: /, $_);
+ print $opt_fh "$attrib:$val\n";
+ } split(/; /, $para);
+ $para = <>;
+ chomp;
+ }
+ my ($desc) = $para =~ m/^([^?.]+)/;
+ print $opt_fh "desc:$desc.\n";
+ close $opt_fh;
+ }
+ }
+ last;
+ '
+}
+
+_eval_po() {
+ local IFS=":"
+ for opt_spec in "$PO_DIR"/*; do
+ local opt=""
+ local default_val=""
+ local neg=0
+ local size=0
+ while read key val; do
+ case "$key" in
+ long)
+ opt=$(echo $val | sed 's/-/_/g' | tr '[:lower:]' '[:upper:]')
+ ;;
+ default)
+ default_val="$val"
+ ;;
+ "short form")
+ ;;
+ type)
+ [ "$val" = "size" ] && size=1
+ ;;
+ desc)
+ ;;
+ negatable)
+ if [ "$val" = "yes" ]; then
+ neg=1
+ fi
+ ;;
+ *)
+ echo "Invalid attribute in $opt_spec: $line" >&2
+ exit 1
+ esac
+ done < "$opt_spec"
+
+ if [ -z "$opt" ]; then
+ echo "No long attribute in option spec $opt_spec" >&2
+ exit 1
+ fi
+
+ if [ $neg -eq 1 ]; then
+ if [ -z "$default_val" ] || [ "$default_val" != "yes" ]; then
+ echo "Option $opt_spec is negatable but not default: yes" >&2
+ exit 1
+ fi
+ fi
+
+ if [ $size -eq 1 -a -n "$default_val" ]; then
+ default_val=$(size_to_bytes $default_val)
+ fi
+
+ eval "OPT_${opt}"="$default_val"
+ done
+}
+
+_parse_config_files() {
+
+ for config_file in "${@:-""}"; do
+ test -f "$config_file" || continue
+
+ while read config_opt; do
+
+ echo "$config_opt" | grep '^[ ]*[^#]' >/dev/null 2>&1 || continue
+
+ config_opt="$(echo "$config_opt" | sed -e 's/^ *//g' -e 's/ *$//g' -e 's/[ ]*=[ ]*/=/' -e 's/[ ]*#.*$//')"
+
+ [ "$config_opt" = "" ] && continue
+
+ if ! [ "$HAVE_EXT_ARGV" ]; then
+ config_opt="--$config_opt"
+ fi
+
+ _parse_command_line "$config_opt"
+
+ done < "$config_file"
+
+ HAVE_EXT_ARGV="" # reset for each file
+
+ done
+}
+
+_parse_command_line() {
+ local opt=""
+ local val=""
+ local next_opt_is_val=""
+ local opt_is_ok=""
+ local opt_is_negated=""
+ local real_opt=""
+ local required_arg=""
+ local spec=""
+
+ for opt in "${@:-""}"; do
+ if [ "$opt" = "--" -o "$opt" = "----" ]; then
+ HAVE_EXT_ARGV=1
+ continue
+ fi
+ if [ "$HAVE_EXT_ARGV" ]; then
+ if [ "$EXT_ARGV" ]; then
+ EXT_ARGV="$EXT_ARGV $opt"
+ else
+ EXT_ARGV="$opt"
+ fi
+ continue
+ fi
+
+ if [ "$next_opt_is_val" ]; then
+ next_opt_is_val=""
+ if [ $# -eq 0 ] || [ $(expr "$opt" : "\-") -eq 1 ]; then
+ option_error "$real_opt requires a $required_arg argument"
+ continue
+ fi
+ val="$opt"
+ opt_is_ok=1
+ else
+ if [ $(expr "$opt" : "\-") -eq 0 ]; then
+ if [ -z "$ARGV" ]; then
+ ARGV="$opt"
+ else
+ ARGV="$ARGV $opt"
+ fi
+ continue
+ fi
+
+ real_opt="$opt"
+
+ if $(echo $opt | grep '^--no[^-]' >/dev/null); then
+ local base_opt=$(echo $opt | sed 's/^--no//')
+ if [ -f "$PT_TMPDIR/po/$base_opt" ]; then
+ opt_is_negated=1
+ opt="$base_opt"
+ else
+ opt_is_negated=""
+ opt=$(echo $opt | sed 's/^-*//')
+ fi
+ else
+ if $(echo $opt | grep '^--no-' >/dev/null); then
+ opt_is_negated=1
+ opt=$(echo $opt | sed 's/^--no-//')
+ else
+ opt_is_negated=""
+ opt=$(echo $opt | sed 's/^-*//')
+ fi
+ fi
+
+ if $(echo $opt | grep '^[a-z-][a-z-]*=' >/dev/null 2>&1); then
+ val="$(echo $opt | awk -F= '{print $2}')"
+ opt="$(echo $opt | awk -F= '{print $1}')"
+ fi
+
+ if [ -f "$PT_TMPDIR/po/$opt" ]; then
+ spec="$PT_TMPDIR/po/$opt"
+ else
+ spec=$(grep "^short form:-$opt\$" "$PT_TMPDIR"/po/* | cut -d ':' -f 1)
+ if [ -z "$spec" ]; then
+ option_error "Unknown option: $real_opt"
+ continue
+ fi
+ fi
+
+ required_arg=$(cat "$spec" | awk -F: '/^type:/{print $2}')
+ if [ "$required_arg" ]; then
+ if [ "$val" ]; then
+ opt_is_ok=1
+ else
+ next_opt_is_val=1
+ fi
+ else
+ if [ "$val" ]; then
+ option_error "Option $real_opt does not take a value"
+ continue
+ fi
+ if [ "$opt_is_negated" ]; then
+ val=""
+ else
+ val="yes"
+ fi
+ opt_is_ok=1
+ fi
+ fi
+
+ if [ "$opt_is_ok" ]; then
+ opt=$(cat "$spec" | grep '^long:' | cut -d':' -f2 | sed 's/-/_/g' | tr '[:lower:]' '[:upper:]')
+
+ if grep "^type:size" "$spec" >/dev/null; then
+ val=$(size_to_bytes $val)
+ fi
+
+ eval "OPT_$opt"="'$val'"
+
+ opt=""
+ val=""
+ next_opt_is_val=""
+ opt_is_ok=""
+ opt_is_negated=""
+ real_opt=""
+ required_arg=""
+ spec=""
+ fi
+ done
+}
+
+size_to_bytes() {
+ local size="$1"
+ 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")};'
+}
+
+# ###########################################################################
+# End parse_options package
+# ###########################################################################
+
+# ###########################################################################
# Global variables
# ###########################################################################
@@ -106,11 +541,6 @@
# prefix. The outcome of this block of code should be that BASEDIR is the
# directory where the files live, without a trailing slash; and PREFIX is
# either empty or a timestamp, such as "2011_02_08_16_58_07".
- if [ $# -gt 1 ]; then
- OPT_ERR="Specify only one PREFIX or DIR"
- usage
- fi
-
if [ $# -eq 1 ]; then
if [ -d "$1" ]; then
BASEDIR="$1"
@@ -147,9 +577,6 @@
fi
done
- # Make a secure tmpdir.
- mk_tmpdir
-
# We need to generate a list of timestamps, and ask the user to choose one if
# there is no PREFIX yet. NOTE: we rely on the "-df" files here.
(
@@ -580,15 +1007,31 @@
;;
esac
done
-
- rm_tmpdir
}
# Execute the program if it was not included from another file. This makes it
# possible to include without executing, and thus test.
if [ "${0##*/}" = "$TOOL" ] \
|| [ "${0##*/}" = "bash" -a "${_:-""}" = "$0" ]; then
- main "${@:-""}"
+
+ mk_tmpdir
+
+ parse_options "$0" "${@:-""}"
+ if [ -z "$OPT_HELP" -a -z "$OPT_VERSION" ]; then
+ if [ $# -gt 1 ]; then
+ option_error "Specify only one PREFIX or DIR"
+ fi
+ fi
+ usage_or_errors "$0"
+ po_status=$?
+ if [ $po_status -ne 0 ]; then
+ [ $OPT_ERRS -gt 0 ] && exit 1
+ exit 0
+ fi
+
+ main "${@:-""}"
+
+ rm_tmpdir
fi
# ############################################################################
@@ -644,47 +1087,47 @@
=over
-=item d
+=item * d
Sets the action to start the L<pt-diskstats> tool on the sample's disk
performance statistics.
-=item i
+=item * i
Sets the action to view the first INNODB STATUS sample in less.
-=item m
+=item * m
Displays the first 4 samples of SHOW STATUS counters side by side with the
L<pt-mext> tool.
-=item n
+=item * n
Summarizes the first sample of netstat data in two ways: by originating host,
and by connection state.
-=item j
+=item * j
Select the next timestamp as the active sample.
-=item k
+=item * k
Select the previous timestamp as the active sample.
-=item q
+=item * q
Quit the program.
-=item 1
+=item * 1
Sets the action for each sample to the default, which is to view a summary
of the sample.
-=item 0
+=item * 0
Sets the action to just list the files in the sample.
-=item *
+=item * *
Sets the action to view all of the sample's files in the less program.
@@ -692,7 +1135,17 @@
=head1 OPTIONS
-This tool does not have any command-line options.
+=over
+
+=item --help
+
+Show help and exit.
+
+=item --version
+
+Show version and exit.
+
+=back
=head1 ENVIRONMENT
@@ -780,7 +1233,7 @@
=head1 VERSION
-pt-sift 2.2.1
+pt-sift 2.2.2
=cut
|
[-]
[+]
|
Changed |
percona-toolkit-2.2.2.tar.gz/bin/pt-slave-delay
^
|
@@ -39,7 +39,7 @@
# ###########################################################################
{
package Percona::Toolkit;
-our $VERSION = '2.2.1';
+our $VERSION = '2.2.2';
1;
}
@@ -1122,6 +1122,7 @@
# ###########################################################################
{
package Lmo::Utils;
+
use strict;
use warnings qw( FATAL all );
require Exporter;
@@ -1129,7 +1130,12 @@
BEGIN {
@ISA = qw(Exporter);
- @EXPORT = @EXPORT_OK = qw(_install_coderef _unimport_coderefs _glob_for _stash_for);
+ @EXPORT = @EXPORT_OK = qw(
+ _install_coderef
+ _unimport_coderefs
+ _glob_for
+ _stash_for
+ );
}
{
@@ -1313,7 +1319,6 @@
return Lmo::Meta->new(class => $class);
}
-
1;
}
# ###########################################################################
@@ -2750,6 +2755,8 @@
use English qw(-no_match_vars);
use constant PTDEBUG => $ENV{PTDEBUG} || 0;
+use Time::HiRes qw(sleep);
+
sub new {
my ( $class, %args ) = @_;
my $self = {
@@ -4824,6 +4831,6 @@
=head1 VERSION
-pt-slave-delay 2.2.1
+pt-slave-delay 2.2.2
=cut
|
[-]
[+]
|
Changed |
percona-toolkit-2.2.2.tar.gz/bin/pt-slave-find
^
|
@@ -1102,6 +1102,7 @@
# ###########################################################################
{
package Lmo::Utils;
+
use strict;
use warnings qw( FATAL all );
require Exporter;
@@ -1109,7 +1110,12 @@
BEGIN {
@ISA = qw(Exporter);
- @EXPORT = @EXPORT_OK = qw(_install_coderef _unimport_coderefs _glob_for _stash_for);
+ @EXPORT = @EXPORT_OK = qw(
+ _install_coderef
+ _unimport_coderefs
+ _glob_for
+ _stash_for
+ );
}
{
@@ -1293,7 +1299,6 @@
return Lmo::Meta->new(class => $class);
}
-
1;
}
# ###########################################################################
@@ -2219,6 +2224,8 @@
my $dp = $self->{DSNParser};
my $methods = $self->_resolve_recursion_methods($args{dsn});
+ return $slaves unless @$methods;
+
if ( grep { m/processlist|hosts/i } @$methods ) {
my @required_args = qw(dbh dsn);
foreach my $arg ( @required_args ) {
@@ -4317,6 +4324,6 @@
=head1 VERSION
-pt-slave-find 2.2.1
+pt-slave-find 2.2.2
=cut
|
[-]
[+]
|
Changed |
percona-toolkit-2.2.2.tar.gz/bin/pt-slave-restart
^
|
@@ -40,7 +40,7 @@
# ###########################################################################
{
package Percona::Toolkit;
-our $VERSION = '2.2.1';
+our $VERSION = '2.2.2';
1;
}
@@ -1274,6 +1274,7 @@
# ###########################################################################
{
package Lmo::Utils;
+
use strict;
use warnings qw( FATAL all );
require Exporter;
@@ -1281,7 +1282,12 @@
BEGIN {
@ISA = qw(Exporter);
- @EXPORT = @EXPORT_OK = qw(_install_coderef _unimport_coderefs _glob_for _stash_for);
+ @EXPORT = @EXPORT_OK = qw(
+ _install_coderef
+ _unimport_coderefs
+ _glob_for
+ _stash_for
+ );
}
{
@@ -1465,7 +1471,6 @@
return Lmo::Meta->new(class => $class);
}
-
1;
}
# ###########################################################################
@@ -2585,6 +2590,8 @@
my $dp = $self->{DSNParser};
my $methods = $self->_resolve_recursion_methods($args{dsn});
+ return $slaves unless @$methods;
+
if ( grep { m/processlist|hosts/i } @$methods ) {
my @required_args = qw(dbh dsn);
foreach my $arg ( @required_args ) {
@@ -5766,6 +5773,6 @@
=head1 VERSION
-pt-slave-restart 2.2.1
+pt-slave-restart 2.2.2
=cut
|
[-]
[+]
|
Changed |
percona-toolkit-2.2.2.tar.gz/bin/pt-stalk
^
|
@@ -1255,7 +1255,7 @@
cycles_true=0
fi
- local msg="Check results: $OPT_VARIABLE=$value, matched=${matched:-no}, cycles_true=$cycles_true"
+ local msg="Check results: $OPT_FUNCTION($OPT_VARIABLE)=$value, matched=${matched:-no}, cycles_true=$cycles_true"
if [ "$matched" ]; then
log "$msg"
else
@@ -1434,7 +1434,6 @@
if [ -z "$OPT_STALK" -a "$OPT_COLLECT" ]; then
# Not stalking; do immediate collect once.
OPT_CYCLES=0
- echo "[iter=$OPT_ITERATIONS] [cycle=$OPT_CYCLES] [sleep=$OPT_SLEEP] [interval=$OPT_INTERVAL]"
fi
usage_or_errors "$0"
@@ -1936,6 +1935,10 @@
to C<1>. In this case, the global variable C<EXIT_REASON> should also
be set to indicate why the tool was stopped.
+Plugin writers should keep in mind that the file destination prefix currently
+in use should be accessed through the C<$prefix> variable, rather than
+C<$OPT_PREFIX>.
+
=item --port
short form: -P; type: int
@@ -2197,7 +2200,7 @@
=head1 VERSION
-pt-stalk 2.2.1
+pt-stalk 2.2.2
=cut
|
[-]
[+]
|
Changed |
percona-toolkit-2.2.2.tar.gz/bin/pt-summary
^
|
@@ -2682,7 +2682,7 @@
=head1 VERSION
-pt-summary 2.2.1
+pt-summary 2.2.2
=cut
|
[-]
[+]
|
Changed |
percona-toolkit-2.2.2.tar.gz/bin/pt-table-checksum
^
|
@@ -56,7 +56,7 @@
# ###########################################################################
{
package Percona::Toolkit;
-our $VERSION = '2.2.1';
+our $VERSION = '2.2.2';
1;
}
@@ -2803,6 +2803,7 @@
# ###########################################################################
{
package Lmo::Utils;
+
use strict;
use warnings qw( FATAL all );
require Exporter;
@@ -2810,7 +2811,12 @@
BEGIN {
@ISA = qw(Exporter);
- @EXPORT = @EXPORT_OK = qw(_install_coderef _unimport_coderefs _glob_for _stash_for);
+ @EXPORT = @EXPORT_OK = qw(
+ _install_coderef
+ _unimport_coderefs
+ _glob_for
+ _stash_for
+ );
}
{
@@ -2994,7 +3000,6 @@
return Lmo::Meta->new(class => $class);
}
-
1;
}
# ###########################################################################
@@ -3536,9 +3541,9 @@
$dbh->{FetchHashKeyName} = 'NAME_lc' if $self->{NAME_lc};
- my $sql = 'SELECT @@hostname, @@server_id';
+ my $sql = 'SELECT @@server_id /*!50038 , @@hostname*/';
PTDEBUG && _d($dbh, $sql);
- my ($hostname, $server_id) = $dbh->selectrow_array($sql);
+ my ($server_id, $hostname) = $dbh->selectrow_array($sql);
PTDEBUG && _d($dbh, 'hostname:', $hostname, $server_id);
if ( $hostname ) {
$self->{hostname} = $hostname;
@@ -3581,6 +3586,32 @@
return $self->{hostname} || $self->{dsn_name} || 'unknown host';
}
+sub remove_duplicate_cxns {
+ my ($self, %args) = @_;
+ my @cxns = @{$args{cxns}};
+ my $seen_ids = $args{seen_ids} || {};
+ PTDEBUG && _d("Removing duplicates from ", join(" ", map { $_->name } @cxns));
+ my @trimmed_cxns;
+
+ for my $cxn ( @cxns ) {
+ my $dbh = $cxn->dbh();
+ my $sql = q{SELECT @@server_id};
+ PTDEBUG && _d($sql);
+ my ($id) = $dbh->selectrow_array($sql);
+ PTDEBUG && _d('Server ID for ', $cxn->name, ': ', $id);
+
+ if ( ! $seen_ids->{$id}++ ) {
+ push @trimmed_cxns, $cxn
+ }
+ else {
+ PTDEBUG && _d("Removing ", $cxn->name,
+ ", ID ", $id, ", because we've already seen it");
+ }
+ }
+
+ return \@trimmed_cxns;
+}
+
sub DESTROY {
my ($self) = @_;
@@ -3634,6 +3665,8 @@
use Lmo;
use Data::Dumper;
+{ local $EVAL_ERROR; eval { require Cxn } };
+
sub get_cluster_name {
my ($self, $cxn) = @_;
my $sql = "SHOW VARIABLES LIKE 'wsrep\_cluster\_name'";
@@ -3658,13 +3691,67 @@
sub same_node {
my ($self, $cxn1, $cxn2) = @_;
- my $sql = "SHOW VARIABLES LIKE 'wsrep\_sst\_receive\_address'";
- PTDEBUG && _d($cxn1->name, $sql);
- my (undef, $val1) = $cxn1->dbh->selectrow_array($sql);
- PTDEBUG && _d($cxn2->name, $sql);
- my (undef, $val2) = $cxn2->dbh->selectrow_array($sql);
+ foreach my $val ('wsrep\_sst\_receive\_address', 'wsrep\_node\_name', 'wsrep\_node\_address') {
+ my $sql = "SHOW VARIABLES LIKE '$val'";
+ PTDEBUG && _d($cxn1->name, $cxn2->name, $sql);
+ my (undef, $val1) = $cxn1->dbh->selectrow_array($sql);
+ my (undef, $val2) = $cxn2->dbh->selectrow_array($sql);
- return ($val1 || '') eq ($val2 || '');
+ return unless ($val1 || '') eq ($val2 || '');
+ }
+
+ return 1;
+}
+
+sub find_cluster_nodes {
+ my ($self, %args) = @_;
+
+ my $dbh = $args{dbh};
+ my $dsn = $args{dsn};
+ my $dp = $args{DSNParser};
+ my $make_cxn = $args{make_cxn};
+
+
+ my $sql = q{SHOW STATUS LIKE 'wsrep\_incoming\_addresses'};
+ PTDEBUG && _d($sql);
+ my (undef, $addresses) = $dbh->selectrow_array($sql);
+ PTDEBUG && _d("Cluster nodes found: ", $addresses);
+ return unless $addresses;
+
+ my @addresses = grep { !/\Aunspecified\z/i }
+ split /,\s*/, $addresses;
+
+ my @nodes;
+ foreach my $address ( @addresses ) {
+ my ($host, $port) = split /:/, $address;
+ my $spec = "h=$host"
+ . ($port ? ",P=$port" : "");
+ my $node_dsn = $dp->parse($spec, $dsn);
+ my $node_dbh = eval { $dp->get_dbh(
+ $dp->get_cxn_params($node_dsn), { AutoCommit => 1 }) };
+ if ( $EVAL_ERROR ) {
+ print STDERR "Cannot connect to ", $dp->as_string($node_dsn),
+ ", discovered through $sql: $EVAL_ERROR\n";
+ if ( !$port && $dsn->{P} != 3306 ) {
+ $address .= ":3306";
+ redo;
+ }
+ next;
+ }
+ PTDEBUG && _d('Connected to', $dp->as_string($node_dsn));
+ $node_dbh->disconnect();
+
+ push @nodes, $make_cxn->(dsn => $node_dsn);
+ }
+
+ return \@nodes;
+}
+
+sub remove_duplicate_cxns {
+ my ($self, %args) = @_;
+ my @cxns = @{$args{cxns}};
+ my $seen_ids = $args{seen_ids};
+ return Cxn->remove_duplicate_cxns(%args);
}
sub same_cluster {
@@ -3678,6 +3765,59 @@
return ($cluster1 || '') eq ($cluster2 || '');
}
+sub autodetect_nodes {
+ my ($self, %args) = @_;
+ my $ms = $args{MasterSlave};
+ my $dp = $args{DSNParser};
+ my $make_cxn = $args{make_cxn};
+ my $nodes = $args{nodes};
+ my $seen_ids = $args{seen_ids};
+
+ my $new_nodes = [];
+
+ return $new_nodes unless @$nodes;
+
+ for my $node ( @$nodes ) {
+ my $nodes_found = $self->find_cluster_nodes(
+ dbh => $node->dbh(),
+ dsn => $node->dsn(),
+ make_cxn => $make_cxn,
+ DSNParser => $dp,
+ );
+ push @$new_nodes, @$nodes_found;
+ }
+
+ $new_nodes = $self->remove_duplicate_cxns(
+ cxns => $new_nodes,
+ seen_ids => $seen_ids
+ );
+
+ my $new_slaves = [];
+ foreach my $node (@$new_nodes) {
+ my $node_slaves = $ms->get_slaves(
+ dbh => $node->dbh(),
+ dsn => $node->dsn(),
+ make_cxn => $make_cxn,
+ );
+ push @$new_slaves, @$node_slaves;
+ }
+
+ $new_slaves = $self->remove_duplicate_cxns(
+ cxns => $new_slaves,
+ seen_ids => $seen_ids
+ );
+
+ my @new_slave_nodes = grep { $self->is_cluster_node($_) } @$new_slaves;
+
+ my $slaves_of_slaves = $self->autodetect_nodes(
+ %args,
+ nodes => \@new_slave_nodes,
+ );
+
+ my @autodetected_nodes = ( @$new_nodes, @$new_slaves, @$slaves_of_slaves );
+ return \@autodetected_nodes;
+}
+
sub _d {
my ($package, undef, $line) = caller 0;
@_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
@@ -4770,6 +4910,8 @@
my $dp = $self->{DSNParser};
my $methods = $self->_resolve_recursion_methods($args{dsn});
+ return $slaves unless @$methods;
+
if ( grep { m/processlist|hosts/i } @$methods ) {
my @required_args = qw(dbh dsn);
foreach my $arg ( @required_args ) {
@@ -7403,6 +7545,8 @@
use English qw(-no_match_vars);
use constant PTDEBUG => $ENV{PTDEBUG} || 0;
+use Time::HiRes qw(sleep);
+
sub new {
my ( $class, %args ) = @_;
my $self = {
@@ -8776,11 +8920,22 @@
}
}
+ my $autodiscover_cluster;
+ my $recursion_method = [];
+ foreach my $method ( @{$o->get('recursion-method')} ) {
+ if ( $method eq 'cluster' ) {
+ $autodiscover_cluster = 1;
+ }
+ else {
+ push @$recursion_method, $method
+ }
+ }
+ $o->set('recursion-method', $recursion_method);
eval {
MasterSlave::check_recursion_method($o->get('recursion-method'));
};
if ( $EVAL_ERROR ) {
- $o->save_error("Invalid --recursion-method: $EVAL_ERROR")
+ $o->save_error($EVAL_ERROR)
}
$o->usage_or_errors();
@@ -8959,33 +9114,44 @@
# #####################################################################
# Find and connect to slaves.
# #####################################################################
+ my $make_cxn_cluster = sub {
+ my $cxn = $make_cxn->(@_, prev_dsn => $master_cxn->dsn());
+ $cluster_name_for{$cxn} = $cluster->is_cluster_node($cxn);
+ return $cxn;
+ };
+
$slaves = $ms->get_slaves(
dbh => $master_dbh,
dsn => $master_dsn,
- make_cxn => sub {
- my $cxn = $make_cxn->(@_, prev_dsn => $master_cxn->dsn());
- $cluster_name_for{$cxn} = $cluster->is_cluster_node($cxn);
- return $cxn;
- },
+ make_cxn => $make_cxn_cluster,
);
- # If the "master" is a cluster node, then a DSN table should have been
- # used, and it may have all nodes' DSNs so the user can run the tool
- # on any node, in which case it has the "master" node, the DSN given
- # on the command line. So detect and remove this dupe.
- if ( $cluster_name_for{$master_cxn} ) {
- @$slaves = grep {
- my $slave_cxn = $_;
- if ( $cluster->same_node($master_cxn, $slave_cxn) ) {
- PTDEBUG && _d('Removing ', $slave_cxn->name, 'from slaves',
- 'because it is the master');
- 0;
- }
- else {
- $slave_cxn;
- }
- } @$slaves;
- }
+ my %seen_ids;
+ for my $cxn ($master_cxn, @$slaves) {
+ my $dbh = $cxn->dbh();
+ my $sql = q{SELECT @@server_id};
+ PTDEBUG && _d($cxn, $dbh, $sql);
+ my ($id) = $dbh->selectrow_array($sql);
+ $seen_ids{$id}++;
+ }
+
+ if ( $autodiscover_cluster ) {
+ my @known_nodes = grep { $cluster_name_for{$_} } $master_cxn, @$slaves;
+ my $new_cxns = $cluster->autodetect_nodes(
+ nodes => \@known_nodes,
+ MasterSlave => $ms,
+ DSNParser => $dp,
+ make_cxn => $make_cxn_cluster,
+ seen_ids => \%seen_ids,
+ );
+ push @$slaves, @$new_cxns;
+ }
+
+ my $trimmed_nodes = Cxn->remove_duplicate_cxns(
+ cxns => [ $master_cxn, @$slaves ],
+ );
+ ($master_cxn, @$slaves) = @$trimmed_nodes;
+
PTDEBUG && _d(scalar @$slaves, 'slaves found');
if ( !@$slaves && $o->get('recursion-method')->[0] ne 'none' ) {
$exit_status |= 1;
@@ -9090,7 +9256,7 @@
warn "Diffs will only be detected if the cluster is "
. "consistent with " . $direct_slave->name . " because "
. $master_cxn->name . " is a traditional replication master "
- . " but these replicas are cluster nodes:\n"
+ . "but these replicas are cluster nodes:\n"
. join("\n", map { ' ' . $_->name } @nodes) . "\n"
. "For more information, please read the Percona XtraDB "
. "Cluster section of the tool's documentation.\n";
@@ -11762,9 +11928,10 @@
Possible methods are:
METHOD USES
- =========== ==================
+ =========== =============================================
processlist SHOW PROCESSLIST
hosts SHOW SLAVE HOSTS
+ cluster SHOW STATUS LIKE 'wsrep\_incoming\_addresses'
dsn=DSN DSNs from a table
none Do not find slaves
@@ -11775,6 +11942,13 @@
The C<hosts> method requires replicas to be configured with C<report_host>,
C<report_port>, etc.
+The C<cluster> method requires a cluster based on Galera 23.7.3 or newer,
+such as Percona XtraDB Cluster versions 5.5.29 and above. This will
+auto-discover nodes in a cluster using
+C<SHOW STATUS LIKE 'wsrep\_incoming\_addresses'>. You can combine C<cluster>
+with C<processlist> and C<hosts> to auto-discover cluster nodes and replicas,
+but this functionality is experimental.
+
The C<dsn> method is special: rather than automatically discovering replicas,
this method specifies a table with replica DSNs. The tool will only connect
to these replicas. This method works best when replicas do not use the same
@@ -12211,6 +12385,6 @@
=head1 VERSION
-pt-table-checksum 2.2.1
+pt-table-checksum 2.2.2
=cut
|
[-]
[+]
|
Changed |
percona-toolkit-2.2.2.tar.gz/bin/pt-table-sync
^
|
@@ -54,7 +54,7 @@
# ###########################################################################
{
package Percona::Toolkit;
-our $VERSION = '2.2.1';
+our $VERSION = '2.2.2';
1;
}
@@ -1137,6 +1137,7 @@
# ###########################################################################
{
package Lmo::Utils;
+
use strict;
use warnings qw( FATAL all );
require Exporter;
@@ -1144,7 +1145,12 @@
BEGIN {
@ISA = qw(Exporter);
- @EXPORT = @EXPORT_OK = qw(_install_coderef _unimport_coderefs _glob_for _stash_for);
+ @EXPORT = @EXPORT_OK = qw(
+ _install_coderef
+ _unimport_coderefs
+ _glob_for
+ _stash_for
+ );
}
{
@@ -1328,7 +1334,6 @@
return Lmo::Meta->new(class => $class);
}
-
1;
}
# ###########################################################################
@@ -6526,6 +6531,8 @@
my $dp = $self->{DSNParser};
my $methods = $self->_resolve_recursion_methods($args{dsn});
+ return $slaves unless @$methods;
+
if ( grep { m/processlist|hosts/i } @$methods ) {
my @required_args = qw(dbh dsn);
foreach my $arg ( @required_args ) {
@@ -8237,6 +8244,8 @@
use English qw(-no_match_vars);
use constant PTDEBUG => $ENV{PTDEBUG} || 0;
+use Time::HiRes qw(sleep);
+
sub new {
my ( $class, %args ) = @_;
my $self = {
@@ -12616,6 +12625,6 @@
=head1 VERSION
-pt-table-sync 2.2.1
+pt-table-sync 2.2.2
=cut
|
[-]
[+]
|
Changed |
percona-toolkit-2.2.2.tar.gz/bin/pt-table-usage
^
|
@@ -7512,6 +7512,6 @@
=head1 VERSION
-pt-table-usage 2.2.1
+pt-table-usage 2.2.2
=cut
|
[-]
[+]
|
Changed |
percona-toolkit-2.2.2.tar.gz/bin/pt-upgrade
^
|
@@ -60,7 +60,7 @@
# ###########################################################################
{
package Percona::Toolkit;
-our $VERSION = '2.2.1';
+our $VERSION = '2.2.2';
1;
}
@@ -78,6 +78,7 @@
# ###########################################################################
{
package Lmo::Utils;
+
use strict;
use warnings qw( FATAL all );
require Exporter;
@@ -85,7 +86,12 @@
BEGIN {
@ISA = qw(Exporter);
- @EXPORT = @EXPORT_OK = qw(_install_coderef _unimport_coderefs _glob_for _stash_for);
+ @EXPORT = @EXPORT_OK = qw(
+ _install_coderef
+ _unimport_coderefs
+ _glob_for
+ _stash_for
+ );
}
{
@@ -269,7 +275,6 @@
return Lmo::Meta->new(class => $class);
}
-
1;
}
# ###########################################################################
@@ -2450,9 +2455,9 @@
$dbh->{FetchHashKeyName} = 'NAME_lc' if $self->{NAME_lc};
- my $sql = 'SELECT @@hostname, @@server_id';
+ my $sql = 'SELECT @@server_id /*!50038 , @@hostname*/';
PTDEBUG && _d($dbh, $sql);
- my ($hostname, $server_id) = $dbh->selectrow_array($sql);
+ my ($server_id, $hostname) = $dbh->selectrow_array($sql);
PTDEBUG && _d($dbh, 'hostname:', $hostname, $server_id);
if ( $hostname ) {
$self->{hostname} = $hostname;
@@ -2495,6 +2500,32 @@
return $self->{hostname} || $self->{dsn_name} || 'unknown host';
}
+sub remove_duplicate_cxns {
+ my ($self, %args) = @_;
+ my @cxns = @{$args{cxns}};
+ my $seen_ids = $args{seen_ids} || {};
+ PTDEBUG && _d("Removing duplicates from ", join(" ", map { $_->name } @cxns));
+ my @trimmed_cxns;
+
+ for my $cxn ( @cxns ) {
+ my $dbh = $cxn->dbh();
+ my $sql = q{SELECT @@server_id};
+ PTDEBUG && _d($sql);
+ my ($id) = $dbh->selectrow_array($sql);
+ PTDEBUG && _d('Server ID for ', $cxn->name, ': ', $id);
+
+ if ( ! $seen_ids->{$id}++ ) {
+ push @trimmed_cxns, $cxn
+ }
+ else {
+ PTDEBUG && _d("Removing ", $cxn->name,
+ ", ID ", $id, ", because we've already seen it");
+ }
+ }
+
+ return \@trimmed_cxns;
+}
+
sub DESTROY {
my ($self) = @_;
@@ -7309,7 +7340,15 @@
return $self->{errors_fh} if $self->{errors_fh};
my $exec = basename($0);
- my ($errors_fh, $filename) = tempfile("/tmp/$exec-errors.XXXXXXX", UNLINK => 0);
+ my ($errors_fh, $filename);
+ if ( $filename = $ENV{PERCONA_TOOLKIT_TCP_ERRORS_FILE} ) {
+ open $errors_fh, ">", $filename
+ or die "Cannot open $filename for writing (supplied from "
+ . "PERCONA_TOOLKIT_TCP_ERRORS_FILE): $OS_ERROR";
+ }
+ else {
+ ($errors_fh, $filename) = tempfile("/tmp/$exec-errors.XXXXXXX", UNLINK => 0);
+ }
$self->{errors_file} = $filename;
$self->{errors_fh} = $errors_fh;
@@ -7325,7 +7364,8 @@
my $errors_fh = $self->_get_errors_fh();
- print "Session $session->{client} had errors, will save them in $self->{errors_file}\n";
+ warn "TCP session $session->{client} had errors, will save them in $self->{errors_file}\n"
+ unless $self->{_warned_for}->{$self->{errors_file}}++;
my $raw_packets = delete $session->{raw_packets};
$session->{reason_for_failure} = $reason;
@@ -11128,6 +11168,6 @@
=head1 VERSION
-pt-upgrade 2.2.1
+pt-upgrade 2.2.2
=cut
|
[-]
[+]
|
Changed |
percona-toolkit-2.2.2.tar.gz/bin/pt-variable-advisor
^
|
@@ -43,7 +43,7 @@
# ###########################################################################
{
package Percona::Toolkit;
-our $VERSION = '2.2.1';
+our $VERSION = '2.2.2';
1;
}
@@ -1126,6 +1126,7 @@
# ###########################################################################
{
package Lmo::Utils;
+
use strict;
use warnings qw( FATAL all );
require Exporter;
@@ -1133,7 +1134,12 @@
BEGIN {
@ISA = qw(Exporter);
- @EXPORT = @EXPORT_OK = qw(_install_coderef _unimport_coderefs _glob_for _stash_for);
+ @EXPORT = @EXPORT_OK = qw(
+ _install_coderef
+ _unimport_coderefs
+ _glob_for
+ _stash_for
+ );
}
{
@@ -1317,7 +1323,6 @@
return Lmo::Meta->new(class => $class);
}
-
1;
}
# ###########################################################################
@@ -6094,6 +6099,6 @@
=head1 VERSION
-pt-variable-advisor 2.2.1
+pt-variable-advisor 2.2.2
=cut
|
[-]
[+]
|
Changed |
percona-toolkit-2.2.2.tar.gz/bin/pt-visual-explain
^
|
@@ -3233,6 +3233,6 @@
=head1 VERSION
-pt-visual-explain 2.2.1
+pt-visual-explain 2.2.2
=cut
|
[-]
[+]
|
Changed |
percona-toolkit-2.2.2.tar.gz/docs/percona-toolkit.pod
^
|
@@ -558,6 +558,6 @@
=head1 VERSION
-Percona Toolkit v2.2.1 released 2013-03-14
+Percona Toolkit v2.2.2 released 2013-04-19
=cut
|