Changes of Revision 3
[-] | Changed | percona-toolkit.changes |
1
2 ------------------------------------------------------------------- 3 +Mon May 6 18:37:04 UTC 2013 - cs@linux-administrator.com 4 + 5 +- update to release 2.2.2 6 + 7 +------------------------------------------------------------------- 8 Fri Apr 12 15:54:22 UTC 2013 - cs@linux-administrator.com 9 10 - re-import 11 |
||
[-] | Changed | percona-toolkit.spec ^ |
10 1
2 Summary: Advanced MySQL and system command-line tools 3 License: GPL-2.0 4 Group: Productivity/Databases/Tools 5 -Version: 2.2.1 6 +Version: 2.2.2 7 Release: 15.1 8 Url: https://www.percona.com/software/percona-toolkit/ 9 Source: https://www.percona.com/redir/downloads/%{name}/%{version}/%{name}-%{version}.tar.gz 10 |
||
[+] | 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 |