2506
2591
# ###########################################################################
2508
2593
# ###########################################################################
2509
# ReportFormatter package
2510
# This package is a copy without comments from the original. The original
2511
# with comments and its test file can be found in the Bazaar repository at,
2512
# lib/ReportFormatter.pm
2513
# t/lib/ReportFormatter.t
2514
# See https://launchpad.net/percona-toolkit for more information.
2515
# ###########################################################################
2517
package ReportFormatter;
2520
use English qw(-no_match_vars);
2521
use constant PTDEBUG => $ENV{PTDEBUG} || 0;
2523
use List::Util qw(min max);
2526
eval { require Term::ReadKey };
2527
my $have_term = $EVAL_ERROR ? 0 : 1;
2530
has underline_header => (
2533
default => sub { 1 },
2535
has line_prefix => (
2538
default => sub { '# ' },
2543
default => sub { 78 },
2545
has column_spacing => (
2548
default => sub { ' ' },
2550
has extend_right => (
2553
default => sub { '' },
2555
has truncate_line_mark => (
2558
default => sub { '...' },
2560
has column_errors => (
2563
default => sub { 'warn' },
2565
has truncate_header_side => (
2568
default => sub { 'left' },
2570
has strip_whitespace => (
2573
default => sub { 1 },
2578
predicate => 'has_title',
2585
default => sub { 0 },
2593
default => sub { [] },
2594
clearer => 'clear_cols',
2601
default => sub { [] },
2602
clearer => 'clear_lines',
2605
has truncate_headers => (
2608
default => sub { undef },
2610
clearer => 'clear_truncate_headers',
2615
my $args = $class->SUPER::BUILDARGS(@_);
2617
if ( ($args->{line_width} || '') eq 'auto' ) {
2618
die "Cannot auto-detect line width because the Term::ReadKey module "
2619
. "is not installed" unless $have_term;
2620
($args->{line_width}) = GetTerminalSize();
2621
PTDEBUG && _d('Line width:', $args->{line_width});
2628
my ( $self, @cols ) = @_;
2629
my $min_hdr_wid = 0; # check that header fits on line
2631
my @auto_width_cols;
2633
for my $i ( 0..$#cols ) {
2634
my $col = $cols[$i];
2635
my $col_name = $col->{name};
2636
my $col_len = length $col_name;
2637
die "Column does not have a name" unless defined $col_name;
2639
if ( $col->{width} ) {
2640
$col->{width_pct} = ceil(($col->{width} * 100) / $self->line_width());
2641
PTDEBUG && _d('col:', $col_name, 'width:', $col->{width}, 'chars =',
2642
$col->{width_pct}, '%');
2645
if ( $col->{width_pct} ) {
2646
$used_width += $col->{width_pct};
2649
PTDEBUG && _d('Auto width col:', $col_name);
2650
$col->{auto_width} = 1;
2651
push @auto_width_cols, $i;
2654
$col->{truncate} = 1 unless defined $col->{truncate};
2655
$col->{truncate_mark} = '...' unless defined $col->{truncate_mark};
2656
$col->{truncate_side} ||= 'right';
2657
$col->{undef_value} = '' unless defined $col->{undef_value};
2659
$col->{min_val} = 0;
2660
$col->{max_val} = 0;
2662
$min_hdr_wid += $col_len;
2663
$col->{header_width} = $col_len;
2665
$col->{right_most} = 1 if $i == $#cols;
2667
push @{$self->cols}, $col;
2670
$self->n_cols( scalar @cols );
2672
if ( ($used_width || 0) > 100 ) {
2673
die "Total width_pct for all columns is >100%";
2676
if ( @auto_width_cols ) {
2677
my $wid_per_col = int((100 - $used_width) / scalar @auto_width_cols);
2678
PTDEBUG && _d('Line width left:', (100-$used_width), '%;',
2679
'each auto width col:', $wid_per_col, '%');
2680
map { $self->cols->[$_]->{width_pct} = $wid_per_col } @auto_width_cols;
2683
$min_hdr_wid += ($self->n_cols() - 1) * length $self->column_spacing();
2684
PTDEBUG && _d('min header width:', $min_hdr_wid);
2685
if ( $min_hdr_wid > $self->line_width() ) {
2686
PTDEBUG && _d('Will truncate headers because min header width',
2687
$min_hdr_wid, '> line width', $self->line_width());
2688
$self->truncate_headers(1);
2695
my ( $self, @vals ) = @_;
2696
my $n_vals = scalar @vals;
2697
if ( $n_vals != $self->n_cols() ) {
2698
$self->_column_error("Number of values $n_vals does not match "
2699
. "number of columns " . $self->n_cols());
2701
for my $i ( 0..($n_vals-1) ) {
2702
my $col = $self->cols->[$i];
2703
my $val = defined $vals[$i] ? $vals[$i] : $col->{undef_value};
2704
if ( $self->strip_whitespace() ) {
2709
my $width = length $val;
2710
$col->{min_val} = min($width, ($col->{min_val} || $width));
2711
$col->{max_val} = max($width, ($col->{max_val} || $width));
2713
push @{$self->lines}, \@vals;
2718
my ( $self, %args ) = @_;
2720
$self->_calculate_column_widths();
2721
if ( $self->truncate_headers() ) {
2722
$self->_truncate_headers();
2724
$self->_truncate_line_values(%args);
2726
my @col_fmts = $self->_make_column_formats();
2727
my $fmt = $self->line_prefix()
2728
. join($self->column_spacing(), @col_fmts);
2729
PTDEBUG && _d('Format:', $fmt);
2731
(my $hdr_fmt = $fmt) =~ s/%([^-])/%-$1/g;
2734
push @lines, $self->line_prefix() . $self->title() if $self->has_title();
2735
push @lines, $self->_truncate_line(
2736
sprintf($hdr_fmt, map { $_->{name} } @{$self->cols}),
2741
if ( $self->underline_header() ) {
2742
my @underlines = map { '=' x $_->{print_width} } @{$self->cols};
2743
push @lines, $self->_truncate_line(
2744
sprintf($fmt, map { $_ || '' } @underlines),
2753
my $val = defined $_ ? $_ : $self->cols->[$i++]->{undef_value};
2754
$val = '' if !defined $val;
2758
my $line = sprintf($fmt, @vals);
2759
if ( $self->extend_right() ) {
2763
$self->_truncate_line($line);
2767
$self->clear_cols();
2768
$self->clear_lines();
2769
$self->clear_truncate_headers();
2771
return join("\n", @lines) . "\n";
2774
sub truncate_value {
2775
my ( $self, $col, $val, $width, $side ) = @_;
2776
return $val if length $val <= $width;
2777
return $val if $col->{right_most} && $self->extend_right();
2778
$side ||= $col->{truncate_side};
2779
my $mark = $col->{truncate_mark};
2780
if ( $side eq 'right' ) {
2781
$val = substr($val, 0, $width - length $mark);
2784
elsif ( $side eq 'left') {
2785
$val = $mark . substr($val, -1 * $width + length $mark);
2788
PTDEBUG && _d("I don't know how to", $side, "truncate values");
2793
sub _calculate_column_widths {
2796
my $extra_space = 0;
2797
foreach my $col ( @{$self->cols} ) {
2798
my $print_width = int($self->line_width() * ($col->{width_pct} / 100));
2800
PTDEBUG && _d('col:', $col->{name}, 'width pct:', $col->{width_pct},
2801
'char width:', $print_width,
2802
'min val:', $col->{min_val}, 'max val:', $col->{max_val});
2804
if ( $col->{auto_width} ) {
2805
if ( $col->{min_val} && $print_width < $col->{min_val} ) {
2806
PTDEBUG && _d('Increased to min val width:', $col->{min_val});
2807
$print_width = $col->{min_val};
2809
elsif ( $col->{max_val} && $print_width > $col->{max_val} ) {
2810
PTDEBUG && _d('Reduced to max val width:', $col->{max_val});
2811
$extra_space += $print_width - $col->{max_val};
2812
$print_width = $col->{max_val};
2816
$col->{print_width} = $print_width;
2817
PTDEBUG && _d('print width:', $col->{print_width});
2820
PTDEBUG && _d('Extra space:', $extra_space);
2821
while ( $extra_space-- ) {
2822
foreach my $col ( @{$self->cols} ) {
2823
if ( $col->{auto_width}
2824
&& ( $col->{print_width} < $col->{max_val}
2825
|| $col->{print_width} < $col->{header_width})
2827
$col->{print_width}++;
2835
sub _truncate_headers {
2836
my ( $self, $col ) = @_;
2837
my $side = $self->truncate_header_side();
2838
foreach my $col ( @{$self->cols} ) {
2839
my $col_name = $col->{name};
2840
my $print_width = $col->{print_width};
2841
next if length $col_name <= $print_width;
2842
$col->{name} = $self->truncate_value($col, $col_name, $print_width, $side);
2843
PTDEBUG && _d('Truncated hdr', $col_name, 'to', $col->{name},
2844
'max width:', $print_width);
2849
sub _truncate_line_values {
2850
my ( $self, %args ) = @_;
2851
my $n_vals = $self->n_cols() - 1;
2852
foreach my $vals ( @{$self->lines} ) {
2853
for my $i ( 0..$n_vals ) {
2854
my $col = $self->cols->[$i];
2855
my $val = defined $vals->[$i] ? $vals->[$i] : $col->{undef_value};
2856
my $width = length $val;
2858
if ( $col->{print_width} && $width > $col->{print_width} ) {
2859
if ( !$col->{truncate} ) {
2860
$self->_column_error("Value '$val' is too wide for column "
2864
my $callback = $args{truncate_callback};
2865
my $print_width = $col->{print_width};
2866
$val = $callback ? $callback->($col, $val, $print_width)
2867
: $self->truncate_value($col, $val, $print_width);
2868
PTDEBUG && _d('Truncated val', $vals->[$i], 'to', $val,
2869
'; max width:', $print_width);
2877
sub _make_column_formats {
2880
my $n_cols = $self->n_cols() - 1;
2881
for my $i ( 0..$n_cols ) {
2882
my $col = $self->cols->[$i];
2884
my $width = $col->{right_most} && !$col->{right_justify} ? ''
2885
: $col->{print_width};
2887
my $col_fmt = '%' . ($col->{right_justify} ? '' : '-') . $width . 's';
2888
push @col_fmts, $col_fmt;
2893
sub _truncate_line {
2894
my ( $self, $line, %args ) = @_;
2895
my $mark = defined $args{mark} ? $args{mark} : $self->truncate_line_mark();
2897
$line =~ s/\s+$// if $args{strip};
2898
my $len = length($line);
2899
if ( $len > $self->line_width() ) {
2900
$line = substr($line, 0, $self->line_width() - length $mark);
2901
$line .= $mark if $mark;
2908
my ( $self, $err ) = @_;
2909
my $msg = "Column error: $err";
2910
$self->column_errors() eq 'die' ? die $msg : warn $msg;
2915
my ($package, undef, $line) = caller 0;
2916
@_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
2917
map { defined $_ ? $_ : 'undef' }
2919
print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
2925
# ###########################################################################
2926
# End ReportFormatter package
2927
# ###########################################################################
2929
# ###########################################################################
2930
2594
# Quoter package
2931
2595
# This package is a copy without comments from the original. The original
2932
2596
# with comments and its test file can be found in the Bazaar repository at,
3007
2676
sub serialize_list {
3008
2677
my ( $self, @args ) = @_;
2678
PTDEBUG && _d('Serializing', Dumper(\@args));
3009
2679
return unless @args;
3011
return $args[0] if @args == 1 && !defined $args[0];
3013
die "Cannot serialize multiple values with undef/NULL"
3014
if grep { !defined $_ } @args;
3016
return join ',', map { quotemeta } @args;
2682
foreach my $arg ( @args ) {
2683
if ( defined $arg ) {
2684
$arg =~ s/,/\\,/g; # escape commas
2685
$arg =~ s/\\N/\\\\N/g; # escape literal \N
2693
my $string = join(',', @parts);
2694
PTDEBUG && _d('Serialized: <', $string, '>');
3019
2698
sub deserialize_list {
3020
2699
my ( $self, $string ) = @_;
3021
return $string unless defined $string;
3022
my @escaped_parts = $string =~ /
3023
\G # Start of string, or end of previous match.
3024
( # Each of these is an element in the original list.
3025
[^\\,]* # Anything not a backslash or a comma
3026
(?: # When we get here, we found one of the above.
3027
\\. # A backslash followed by something so we can continue
3028
[^\\,]* # Same as above.
3029
)* # Repeat zero of more times.
3031
, # Comma dividing elements
3034
push @escaped_parts, pos($string) ? substr( $string, pos($string) ) : $string;
3036
my @unescaped_parts = map {
3039
my $char_class = utf8::is_utf8($part) # If it's a UTF-8 string,
3040
? qr/(?=\p{ASCII})\W/ # We only care about non-word
3041
: qr/(?=\p{ASCII})\W|[\x{80}-\x{FF}]/; # Otherwise,
3042
$part =~ s/\\($char_class)/$1/g;
3046
return @unescaped_parts;
2700
PTDEBUG && _d('Deserializing <', $string, '>');
2701
die "Cannot deserialize an undefined string" unless defined $string;
2704
foreach my $arg ( split(/(?<!\\),/, $string) ) {
2705
if ( $arg eq '\N' ) {
2710
$arg =~ s/\\\\N/\\N/g;
2716
my $n_empty_strings = $string =~ tr/,//;
2718
PTDEBUG && _d($n_empty_strings, 'empty strings');
2719
map { push @parts, '' } 1..$n_empty_strings;
2721
elsif ( $string =~ m/(?<!\\),$/ ) {
2722
PTDEBUG && _d('Last value is an empty string');
2726
PTDEBUG && _d('Deserialized', Dumper(\@parts));
2731
my ($package, undef, $line) = caller 0;
2732
@_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
2733
map { defined $_ ? $_ : 'undef' }
2735
print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
7159
6879
package VersionCheck;
7162
6883
use warnings FATAL => 'all';
7163
6884
use English qw(-no_match_vars);
7165
6886
use constant PTDEBUG => $ENV{PTDEBUG} || 0;
7167
use Data::Dumper qw();
7168
use Digest::MD5 qw(md5_hex);
7169
use Sys::Hostname qw(hostname);
7170
use Fcntl qw(:DEFAULT);
6889
local $Data::Dumper::Indent = 1;
6890
local $Data::Dumper::Sortkeys = 1;
6891
local $Data::Dumper::Quotekeys = 0;
6893
use Digest::MD5 qw(md5_hex);
6894
use Sys::Hostname qw(hostname);
7171
6895
use File::Basename qw();
7172
6896
use File::Spec;
7174
my $dir = File::Spec->tmpdir();
7175
my $check_time_file = File::Spec->catfile($dir,'percona-toolkit-version-check');
7176
my $check_time_limit = 60 * 60 * 24; # one day
7179
local $Data::Dumper::Indent = 1;
7180
local $Data::Dumper::Sortkeys = 1;
7181
local $Data::Dumper::Quotekeys = 0;
7183
Data::Dumper::Dumper(@_);
7188
6900
require Percona::Toolkit;
7189
6901
require HTTPMicro;
6905
my $file = 'percona-version-check';
6906
my $home = $ENV{HOME} || $ENV{HOMEPATH} || $ENV{USERPROFILE} || '.';
6909
'/etc/percona-toolkit',
6914
sub version_check_file {
6915
foreach my $dir ( @vc_dirs ) {
6916
if ( -d $dir && -w $dir ) {
6917
PTDEBUG && _d('Version check file', $file, 'in', $dir);
6918
return $dir . '/' . $file;
6921
PTDEBUG && _d('Version check file', $file, 'in', $ENV{PWD});
6922
return $file; # in the CWD
6926
sub version_check_time_limit {
6927
return 60 * 60 * 24; # one day
7192
6931
sub version_check {
7194
my @instances = $args{instances} ? @{ $args{instances} } : ();
7196
if (exists $ENV{PERCONA_VERSION_CHECK} && !$ENV{PERCONA_VERSION_CHECK}) {
7197
warn '--version-check is disabled by the PERCONA_VERSION_CHECK ',
7198
"environment variable.\n\n";
6934
my $instances = $args{instances} || [];
6935
my $instances_to_check;
6937
PTDEBUG && _d('FindBin::Bin:', $FindBin::Bin);
6938
if ( !$args{force} ) {
6940
&& (-d "$FindBin::Bin/../.bzr" || -d "$FindBin::Bin/../../.bzr") ) {
6941
PTDEBUG && _d("$FindBin::Bin/../.bzr disables --version-check");
7202
$args{protocol} ||= 'https';
7203
my @protocols = $args{protocol} eq 'auto'
7207
my $instances_to_check = [];
7208
my $time = int(time());
7210
foreach my $instance ( @instances ) {
7211
my ($name, $id) = _generate_identifier($instance);
6947
foreach my $instance ( @$instances ) {
6948
my ($name, $id) = get_instance_id($instance);
7212
6949
$instance->{name} = $name;
7213
6950
$instance->{id} = $id;
7217
($time_to_check, $instances_to_check)
7218
= time_to_check($check_time_file, \@instances, $time);
7219
if ( !$time_to_check ) {
7220
warn 'It is not time to --version-check again; ',
7221
"only 1 check per day.\n\n";
7227
for my $protocol ( @protocols ) {
7228
$advice = eval { pingback(
7229
url => $ENV{PERCONA_VERSION_CHECK_URL} || "$protocol://v.percona.com",
7230
instances => $instances_to_check,
7231
protocol => $protocol,
7233
last if !$advice && !$EVAL_ERROR;
6953
push @$instances, { name => 'system', id => 0 };
6955
$instances_to_check = get_instances_to_check(
6956
instances => $instances,
6957
vc_file => $args{vc_file}, # testing
6958
now => $args{now}, # testing
6960
PTDEBUG && _d(scalar @$instances_to_check, 'instances to check');
6961
return unless @$instances_to_check;
6963
my $protocol = 'https'; # optimistic, but...
6964
eval { require IO::Socket::SSL; };
6965
if ( $EVAL_ERROR ) {
6966
PTDEBUG && _d($EVAL_ERROR);
6969
PTDEBUG && _d('Using', $protocol);
6971
my $advice = pingback(
6972
instances => $instances_to_check,
6973
protocol => $protocol,
6974
url => $args{url} # testing
6975
|| $ENV{PERCONA_VERSION_CHECK_URL} # testing
6976
|| "$protocol://v.percona.com",
7236
6978
if ( $advice ) {
7237
print "# Percona suggests these upgrades:\n";
6979
PTDEBUG && _d('Advice:', Dumper($advice));
6980
if ( scalar @$advice > 1) {
6981
print "\n# " . scalar @$advice . " software updates are "
6985
print "\n# A software update is available:\n";
7238
6987
print join("\n", map { "# * $_" } @$advice), "\n\n";
6990
if ( $EVAL_ERROR ) {
6991
PTDEBUG && _d('Version check failed:', $EVAL_ERROR);
6994
if ( @$instances_to_check ) {
6997
instances => $instances_to_check,
6998
vc_file => $args{vc_file}, # testing
6999
now => $args{now}, # testing
7002
if ( $EVAL_ERROR ) {
7003
PTDEBUG && _d('Error updating version check file:', $EVAL_ERROR);
7007
if ( $ENV{PTDEBUG_VERSION_CHECK} ) {
7008
warn "Exiting because the PTDEBUG_VERSION_CHECK "
7009
. "environment variable is defined.\n";
7016
sub get_instances_to_check {
7019
my $instances = $args{instances};
7020
my $now = $args{now} || int(time);
7021
my $vc_file = $args{vc_file} || version_check_file();
7023
if ( !-f $vc_file ) {
7024
PTDEBUG && _d('Version check file', $vc_file, 'does not exist;',
7025
'version checking all instances');
7029
open my $fh, '<', $vc_file or die "Cannot open $vc_file: $OS_ERROR";
7030
chomp(my $file_contents = do { local $/ = undef; <$fh> });
7031
PTDEBUG && _d('Version check file', $vc_file, 'contents:', $file_contents);
7033
my %last_check_time_for = $file_contents =~ /^([^,]+),(.+)$/mg;
7035
my $check_time_limit = version_check_time_limit();
7036
my @instances_to_check;
7037
foreach my $instance ( @$instances ) {
7038
my $last_check_time = $last_check_time_for{ $instance->{id} };
7039
PTDEBUG && _d('Intsance', $instance->{id}, 'last checked',
7040
$last_check_time, 'now', $now, 'diff', $now - ($last_check_time || 0),
7041
'hours until next check',
7043
($check_time_limit - ($now - ($last_check_time || 0))) / 3600);
7044
if ( !defined $last_check_time
7045
|| ($now - $last_check_time) >= $check_time_limit ) {
7046
PTDEBUG && _d('Time to check', Dumper($instance));
7047
push @instances_to_check, $instance;
7051
return \@instances_to_check;
7054
sub update_check_times {
7057
my $instances = $args{instances};
7058
my $now = $args{now} || int(time);
7059
my $vc_file = $args{vc_file} || version_check_file();
7060
PTDEBUG && _d('Updating last check time:', $now);
7062
open my $fh, '>', $vc_file or die "Cannot write to $vc_file: $OS_ERROR";
7063
foreach my $instance ( sort { $a->{id} cmp $b->{id} } @$instances ) {
7064
PTDEBUG && _d('Updated:', Dumper($instance));
7065
print { $fh } $instance->{id} . ',' . $now . "\n";
7072
sub get_instance_id {
7073
my ($instance) = @_;
7075
my $dbh = $instance->{dbh};
7076
my $dsn = $instance->{dsn};
7078
my $sql = q{SELECT CONCAT(@@hostname, @@port)};
7079
PTDEBUG && _d($sql);
7080
my ($name) = eval { $dbh->selectrow_array($sql) };
7081
if ( $EVAL_ERROR ) {
7082
PTDEBUG && _d($EVAL_ERROR);
7083
$sql = q{SELECT @@hostname};
7084
PTDEBUG && _d($sql);
7085
($name) = eval { $dbh->selectrow_array($sql) };
7086
if ( $EVAL_ERROR ) {
7087
PTDEBUG && _d($EVAL_ERROR);
7088
$name = ($dsn->{h} || 'localhost') . ($dsn->{P} || 3306);
7242
print "# No suggestions at this time.\n\n";
7243
($ENV{PTVCDEBUG} || PTDEBUG )
7244
&& _d('--version-check worked, but there were no suggestions');
7091
$sql = q{SHOW VARIABLES LIKE 'port'};
7092
PTDEBUG && _d($sql);
7093
my (undef, $port) = eval { $dbh->selectrow_array($sql) };
7094
PTDEBUG && _d('port:', $port);
7095
$name .= $port || '';
7247
if ( $EVAL_ERROR ) {
7248
warn "Error doing --version-check: $EVAL_ERROR";
7251
update_checks_file($check_time_file, $instances_to_check, $time);
7098
my $id = md5_hex($name);
7100
PTDEBUG && _d('MySQL instance:', $id, $name, $dsn);
7258
7107
my (%args) = @_;
7259
my @required_args = qw(url);
7108
my @required_args = qw(url instances);
7260
7109
foreach my $arg ( @required_args ) {
7261
7110
die "I need a $arg arugment" unless $args{$arg};
7263
my ($url) = @args{@required_args};
7265
my ($instances, $ua) = @args{qw(instances ua)};
7267
$ua ||= HTTPMicro->new( timeout => 5 );
7112
my $url = $args{url};
7113
my $instances = $args{instances};
7115
my $ua = $args{ua} || HTTPMicro->new( timeout => 3 );
7269
7117
my $response = $ua->request('GET', $url);
7270
($ENV{PTVCDEBUG} || PTDEBUG) && _d('Server response:', Dumper($response));
7118
PTDEBUG && _d('Server response:', Dumper($response));
7271
7119
die "No response from GET $url"
7273
7121
die("GET on $url returned HTTP status $response->{status}; expected 200\n",
7324
7170
return \@suggestions;
7328
my ($file, $instances, $time) = @_;
7329
die "I need a file argument" unless $file;
7330
$time ||= int(time()); # current time
7332
if ( @$instances ) {
7333
my $instances_to_check = instances_to_check($file, $instances, $time);
7334
return scalar @$instances_to_check, $instances_to_check;
7337
return 1 if !-f $file;
7339
my $mtime = (stat $file)[9];
7340
if ( !defined $mtime ) {
7341
PTDEBUG && _d('Error getting modified time of', $file);
7344
PTDEBUG && _d('time=', $time, 'mtime=', $mtime);
7345
if ( ($time - $mtime) > $check_time_limit ) {
7352
sub instances_to_check {
7353
my ($file, $instances, $time, %args) = @_;
7355
my $file_contents = '';
7356
if (open my $fh, '<', $file) {
7357
chomp($file_contents = do { local $/ = undef; <$fh> });
7360
my %cached_instances = $file_contents =~ /^([^,]+),(.+)$/mg;
7362
my @instances_to_check;
7363
foreach my $instance ( @$instances ) {
7364
my $mtime = $cached_instances{ $instance->{id} };
7365
if ( !$mtime || (($time - $mtime) > $check_time_limit) ) {
7366
if ( $ENV{PTVCDEBUG} || PTDEBUG ) {
7367
_d('Time to check MySQL instance', $instance->{name});
7369
push @instances_to_check, $instance;
7370
$cached_instances{ $instance->{id} } = $time;
7374
if ( $args{update_file} ) {
7375
open my $fh, '>', $file or die "Cannot open $file for writing: $OS_ERROR";
7376
while ( my ($id, $time) = each %cached_instances ) {
7377
print { $fh } "$id,$time\n";
7379
close $fh or die "Cannot close $file: $OS_ERROR";
7382
return \@instances_to_check;
7385
sub update_checks_file {
7386
my ($file, $instances, $time) = @_;
7389
if ( $ENV{PTVCDEBUG} || PTDEBUG ) {
7390
_d('Creating time limit file', $file);
7395
if ( $instances && @$instances ) {
7396
instances_to_check($file, $instances, $time, update_file => 1);
7400
my $mtime = (stat $file)[9];
7401
if ( !defined $mtime ) {
7405
PTDEBUG && _d('time=', $time, 'mtime=', $mtime);
7406
if ( ($time - $mtime) > $check_time_limit ) {
7416
sysopen my $fh, $file, O_WRONLY|O_CREAT
7417
or die "Cannot create $file : $!";
7418
close $fh or die "Cannot close $file : $!";
7419
utime(undef, undef, $file);
7422
sub _generate_identifier {
7423
my $instance = shift;
7424
my $dbh = $instance->{dbh};
7425
my $dsn = $instance->{dsn};
7427
my $sql = q{SELECT CONCAT(@@hostname, @@port)};
7428
PTDEBUG && _d($sql);
7429
my ($name) = eval { $dbh->selectrow_array($sql) };
7430
if ( $EVAL_ERROR ) {
7431
PTDEBUG && _d($EVAL_ERROR);
7432
$sql = q{SELECT @@hostname};
7433
PTDEBUG && _d($sql);
7434
($name) = eval { $dbh->selectrow_array($sql) };
7435
if ( $EVAL_ERROR ) {
7436
PTDEBUG && _d($EVAL_ERROR);
7437
$name = ($dsn->{h} || 'localhost') . ($dsn->{P} || 3306);
7440
$sql = q{SHOW VARIABLES LIKE 'port'};
7441
PTDEBUG && _d($sql);
7442
my (undef, $port) = eval { $dbh->selectrow_array($sql) };
7443
PTDEBUG && _d('port:', $port);
7444
$name .= $port || '';
7447
my $id = md5_hex($name);
7449
if ( $ENV{PTVCDEBUG} || PTDEBUG ) {
7450
_d('MySQL instance', $name, 'is', $id);
7456
7173
sub encode_client_response {
7457
7174
my (%args) = @_;
7458
7175
my @required_args = qw(items versions general_id);
9906
return 'undefined_error' unless $error;
9908
= $error =~ m/Lock wait timeout/ ? 'lock_wait_timeout'
9909
: $error =~ m/Deadlock found/ ? 'deadlock'
9910
: $error =~ m/execution was interrupted/ ? 'query_killed'
9911
: $error =~ m/server has gone away/ ? 'lost_connection'
9912
: $error =~ m/Lost connection/ ? 'connection_killed'
9919
my @required_args = qw(Cxn Retry tries code stats);
9920
foreach my $arg ( @required_args ) {
9921
die "I need a $arg argument" unless $args{$arg};
9923
my $cxn = $args{Cxn};
9924
my $retry = $args{Retry};
9925
my $tries = $args{tries};
9926
my $code = $args{code};
9927
my $stats = $args{stats};
9928
my $ignore_errors = $args{ignore_errors};
9930
return $retry->retry(
9931
tries => $tries->{tries},
9932
wait => sub { sleep ($tries->{wait} || 0.25) },
9936
my $error = $args{error};
9937
PTDEBUG && _d('Retry fail:', $error);
9939
if ( $ignore_errors ) {
9940
return 0 if grep { $error =~ $_ } @$ignore_errors;
9943
# The query failed/caused an error. If the error is one of these,
9944
# then we can possibly retry.
9945
if ( $error =~ m/Lock wait timeout exceeded/
9946
|| $error =~ m/Deadlock found/
9947
|| $error =~ m/Query execution was interrupted/
9949
# These errors/warnings can be retried, so don't print
9950
# a warning yet; do that in final_fail.
9951
$stats->{ error_event($error) }++;
9952
return 1; # try again
9954
elsif ( $error =~ m/MySQL server has gone away/
9955
|| $error =~ m/Lost connection to MySQL server/
9957
# The 1st pattern means that MySQL itself died or was stopped.
9958
# The 2nd pattern means that our cxn was killed (KILL <id>).
9959
$stats->{ error_event($error) }++;
9960
$cxn->connect(); # connect or die trying
9961
return 1; # reconnected, try again
9964
$stats->{retry_fail}++;
9966
# At this point, either the error/warning cannot be retried,
9967
# or we failed to reconnect. Don't retry; call final_fail.
9972
my $error = $args{error};
9973
# This die should be caught by the caller. Copying rows and
9974
# the tool will stop, which is probably good because by this
9975
# point the error or warning indicates that something is wrong.
9976
$stats->{ error_event($error) }++;
9972
9982
sub exec_nibble {
9973
9983
my (%args) = @_;
9974
my @required_args = qw(Cxn tbl stats NibbleIterator Retry Quoter OptionParser);
9984
my @required_args = qw(Cxn tbl stats tries Retry NibbleIterator Quoter);
9975
9985
foreach my $arg ( @required_args ) {
9976
9986
die "I need a $arg argument" unless $args{$arg};
9978
my ($cxn, $tbl, $stats, $nibble_iter, $retry, $q, $o)= @args{@required_args};
9988
my ($cxn, $tbl, $stats, $tries, $retry, $nibble_iter, $q)
9989
= @args{@required_args};
9980
my $dbh = $cxn->dbh();
9981
9991
my $sth = $nibble_iter->statements();
9982
9992
my $boundary = $nibble_iter->boundaries();
9983
9993
my $lb_quoted = $q->serialize_list(@{$boundary->{lower}});
10966
10994
Show version and exit.
10968
=item --version-check
10970
type: string; default: off
10972
Send program versions to Percona and print suggested upgrades and problems.
10973
Possible values for --version-check:
10975
=for comment ignore-pt-internal-value
10976
MAGIC_version_check
10978
https, http, auto, off
10980
C<auto> first tries using C<https>, and resorts to C<http> if that fails.
10981
Keep in mind that C<https> might not be available if
10982
C<IO::Socket::SSL> is not installed on your system, although
10983
C<--version-check http> should work everywhere.
10985
The version check feature causes the tool to send and receive data from
10986
Percona over the web. The data contains program versions from the local
10987
machine. Percona uses the data to focus development on the most widely
10988
used versions of programs, and to suggest to customers possible upgrades
10989
and known bad versions of programs.
10991
For more information, visit L<http://www.percona.com/version-check>.
10996
=item --[no]version-check
11000
Check for the latest version of Percona Toolkit, MySQL, and other programs.
11002
This is a standard "check for updates automatically" feature, with two
11003
additional features. First, the tool checks the version of other programs
11004
on the local system in addition to its own version. For example, it checks
11005
the version of every MySQL server it connects to, Perl, and the Perl module
11006
DBD::mysql. Second, it checks for and warns about versions with known
11007
problems. For example, MySQL 5.5.25 had a critical bug and was re-released
11010
Any updates or known problems are printed to STDOUT before the tool's normal
11011
output. This feature should never interfere with the normal operation of the
11014
For more information, visit L<https://www.percona.com/version-check>.
11020
The file specified by L<"--plugin"> must define a class (i.e. a package)
11021
called C<pt_online_schema_change_plugin> with a C<new()> subroutine.
11022
The tool will create an instance of this class and call any hooks that
11023
it defines. No hooks are required, but a plugin isn't very useful without
11026
These hooks, in this order, are called if defined:
11029
before_create_new_table
11030
after_create_new_table
11031
before_alter_new_table
11032
after_alter_new_table
11033
before_create_triggers
11034
after_create_triggers
11039
before_update_foreign_keys
11040
after_update_foreign_keys
11041
before_drop_old_table
11042
after_drop_old_table
11043
before_drop_triggers
11046
Each hook is passed different arguments. To see which arguments are passed
11047
to a hook, search for the hook's name in the tool's source code, like:
11050
if ( $plugin && $plugin->can('init') ) {
11052
orig_tbl => $orig_tbl,
11053
child_tables => $child_tables,
11054
renamed_cols => $renamed_cols,
11056
slave_lag_cxns => $slave_lag_cxns,
11060
The comment C<# --plugin hook> precedes every hook call.
11062
Please contact Percona if you have questions or need help.
10995
11064
=head1 DSN OPTIONS
10997
11066
These DSN options are used to create a DSN. Each option is given like