Merge lp:~percona-toolkit-dev/percona-toolkit/pingback-feature into lp:percona-toolkit/2.1

Proposed by Daniel Nichter
Status: Merged
Merged at revision: 365
Proposed branch: lp:~percona-toolkit-dev/percona-toolkit/pingback-feature
Merge into: lp:percona-toolkit/2.1
Diff against target: 22225 lines (+20944/-95)
34 files modified
bin/pt-archiver (+999/-2)
bin/pt-config-diff (+998/-2)
bin/pt-deadlock-logger (+1002/-3)
bin/pt-diskstats (+1002/-8)
bin/pt-duplicate-key-checker (+1002/-4)
bin/pt-find (+995/-2)
bin/pt-fk-error-logger (+1003/-3)
bin/pt-heartbeat (+999/-2)
bin/pt-index-usage (+1012/-3)
bin/pt-kill (+1005/-2)
bin/pt-online-schema-change (+1010/-2)
bin/pt-query-advisor (+1006/-2)
bin/pt-query-digest (+1025/-4)
bin/pt-slave-delay (+997/-3)
bin/pt-slave-restart (+1003/-3)
bin/pt-table-checksum (+1015/-3)
bin/pt-table-sync (+1017/-5)
bin/pt-upgrade (+1021/-6)
bin/pt-variable-advisor (+1001/-2)
lib/HTTPMicro.pm (+460/-0)
lib/Percona/Toolkit.pm (+27/-0)
lib/PerconaTest.pm (+4/-0)
lib/Pingback.pm (+267/-0)
lib/QueryReportFormatter.pm (+1/-1)
lib/Sandbox.pm (+2/-0)
lib/TableChunker.pm (+3/-4)
lib/VersionCheck.pm (+296/-0)
t/lib/HTTPMicro.t (+34/-0)
t/lib/Pingback.t (+242/-0)
t/lib/TableChunker.t (+17/-0)
t/lib/VersionCheck.t (+204/-0)
t/pt-archiver/version_check.t (+143/-0)
util/tool-header (+89/-0)
util/update-modules (+43/-29)
To merge this branch: bzr merge lp:~percona-toolkit-dev/percona-toolkit/pingback-feature
Reviewer Review Type Date Requested Status
Daniel Nichter Approve
Review via email: mp+120901@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Daniel Nichter (daniel-nichter) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bin/pt-archiver'
2--- bin/pt-archiver 2012-08-03 18:41:41 +0000
3+++ bin/pt-archiver 2012-08-23 01:57:20 +0000
4@@ -6,7 +6,45 @@
5
6 use strict;
7 use warnings FATAL => 'all';
8-use constant PTDEBUG => $ENV{PTDEBUG} || 0;
9+
10+# This tool is "fat-packed": most of its dependent modules are embedded
11+# in this file. Setting %INC to this file for each module makes Perl aware
12+# of this so it will not try to load the module from @INC. See the tool's
13+# documentation for a full list of dependencies.
14+BEGIN {
15+ $INC{$_} = __FILE__ for map { (my $pkg = "$_.pm") =~ s!::!/!g; $pkg } (qw(
16+ Percona::Toolkit
17+ OptionParser
18+ Mo
19+ TableParser
20+ DSNParser
21+ VersionParser
22+ Quoter
23+ TableNibbler
24+ Daemon
25+ MasterSlave
26+ VersionCheck
27+ HTTPMicro
28+ Pingback
29+ ));
30+}
31+
32+# ###########################################################################
33+# Percona::Toolkit package
34+# This package is a copy without comments from the original. The original
35+# with comments and its test file can be found in the Bazaar repository at,
36+# lib/Percona/Toolkit.pm
37+# t/lib/Percona/Toolkit.t
38+# See https://launchpad.net/percona-toolkit for more information.
39+# ###########################################################################
40+{
41+package Percona::Toolkit;
42+our $VERSION = '2.1.3';
43+1;
44+}
45+# ###########################################################################
46+# End Percona::Toolkit package
47+# ###########################################################################
48
49 # ###########################################################################
50 # OptionParser package
51@@ -3797,6 +3835,939 @@
52 # ###########################################################################
53
54 # ###########################################################################
55+# VersionCheck package
56+# This package is a copy without comments from the original. The original
57+# with comments and its test file can be found in the Bazaar repository at,
58+# lib/VersionCheck.pm
59+# t/lib/VersionCheck.t
60+# See https://launchpad.net/percona-toolkit for more information.
61+# ###########################################################################
62+{
63+package VersionCheck;
64+
65+use strict;
66+use warnings FATAL => 'all';
67+use English qw(-no_match_vars);
68+
69+use constant PTDEBUG => $ENV{PTDEBUG} || 0;
70+
71+use File::Basename ();
72+use Data::Dumper ();
73+
74+sub Dumper {
75+ local $Data::Dumper::Indent = 1;
76+ local $Data::Dumper::Sortkeys = 1;
77+ local $Data::Dumper::Quotekeys = 0;
78+
79+ Data::Dumper::Dumper(@_);
80+}
81+
82+sub new {
83+ my ($class, %args) = @_;
84+ my $self = {
85+ valid_types => qr/
86+ ^(?:
87+ os_version
88+ |perl_version
89+ |perl_module_version
90+ |mysql_variable
91+ |bin_version
92+ )$/x,
93+ };
94+ return bless $self, $class;
95+}
96+
97+sub parse_server_response {
98+ my ($self, %args) = @_;
99+ my @required_args = qw(response);
100+ foreach my $arg ( @required_args ) {
101+ die "I need a $arg arugment" unless $args{$arg};
102+ }
103+ my ($response) = @args{@required_args};
104+
105+ my %items = map {
106+ my ($item, $type, $vars) = split(";", $_);
107+ if ( !defined $args{split_vars} || $args{split_vars} ) {
108+ $vars = [ split(",", ($vars || '')) ];
109+ }
110+ $item => {
111+ item => $item,
112+ type => $type,
113+ vars => $vars,
114+ };
115+ } split("\n", $response);
116+
117+ PTDEBUG && _d('Items:', Dumper(\%items));
118+
119+ return \%items;
120+}
121+
122+sub get_versions {
123+ my ($self, %args) = @_;
124+ my @required_args = qw(items);
125+ foreach my $arg ( @required_args ) {
126+ die "I need a $arg arugment" unless $args{$arg};
127+ }
128+ my ($items) = @args{@required_args};
129+ my $dbh = $args{dbh}; # optional
130+
131+ my %versions;
132+ foreach my $item ( values %$items ) {
133+ next unless $self->valid_item($item);
134+
135+ eval {
136+ my $func = 'get_' . $item->{type};
137+ my $version = $self->$func(
138+ item => $item,
139+ dbh => $dbh,
140+ );
141+ if ( $version ) {
142+ chomp $version;
143+ $versions{$item->{item}} = $version;
144+ }
145+ };
146+ if ( $EVAL_ERROR ) {
147+ PTDEBUG && _d('Error getting version for', Dumper($item), $EVAL_ERROR);
148+ }
149+ }
150+
151+ return \%versions;
152+}
153+
154+sub valid_item {
155+ my ($self, $item) = @_;
156+ return unless $item;
157+
158+ if ( ($item->{type} || '') !~ m/$self->{valid_types}/ ) {
159+ PTDEBUG && _d('Invalid type:', $item->{type});
160+ return;
161+ }
162+
163+ return 1;
164+}
165+
166+sub get_os_version {
167+ my ($self) = @_;
168+
169+ chomp(my $platform = `uname -s`);
170+ PTDEBUG && _d('platform:', $platform);
171+ return $OSNAME unless $platform;
172+
173+ chomp(my $lsb_release
174+ = `which lsb_release 2>/dev/null | awk '{print \$1}'` || '');
175+ PTDEBUG && _d('lsb_release:', $lsb_release);
176+
177+ my $release = "";
178+
179+ if ( $platform eq 'Linux' ) {
180+ if ( -f "/etc/fedora-release" ) {
181+ $release = `cat /etc/fedora-release`;
182+ }
183+ elsif ( -f "/etc/redhat-release" ) {
184+ $release = `cat /etc/redhat-release`;
185+ }
186+ elsif ( -f "/etc/system-release" ) {
187+ $release = `cat /etc/system-release`;
188+ }
189+ elsif ( $lsb_release ) {
190+ $release = `$lsb_release -ds`;
191+ }
192+ elsif ( -f "/etc/lsb-release" ) {
193+ $release = `grep DISTRIB_DESCRIPTION /etc/lsb-release`;
194+ $release =~ s/^\w+="([^"]+)".+/$1/;
195+ }
196+ elsif ( -f "/etc/debian_version" ) {
197+ chomp(my $rel = `cat /etc/debian_version`);
198+ $release = "Debian $rel";
199+ if ( -f "/etc/apt/sources.list" ) {
200+ chomp(my $code_name = `awk '/^deb/ {print \$3}' /etc/apt/sources.list | awk -F/ '{print \$1}'| awk 'BEGIN {FS="|"} {print \$1}' | sort | uniq -c | sort -rn | head -n1 | awk '{print \$2}'`);
201+ $release .= " ($code_name)" if $code_name;
202+ }
203+ }
204+ elsif ( `ls /etc/*release 2>/dev/null` ) {
205+ if ( `grep DISTRIB_DESCRIPTION /etc/*release 2>/dev/null` ) {
206+ $release = `grep DISTRIB_DESCRIPTION /etc/*release | head -n1`;
207+ }
208+ else {
209+ $release = `cat /etc/*release | head -n1`;
210+ }
211+ }
212+ }
213+ elsif ( $platform =~ m/^(BSD|Darwin)$/ ) {
214+ my $rel = `uname -r`;
215+ $release = "$platform $rel";
216+ }
217+ elsif ( $platform eq "SunOS" ) {
218+ my $rel = `head -n1 /etc/release` || `uname -r`;
219+ $release = "$platform $rel";
220+ }
221+
222+ if ( !$release ) {
223+ PTDEBUG && _d('Failed to get the release, using platform');
224+ $release = $platform;
225+ }
226+ chomp($release);
227+
228+ PTDEBUG && _d('OS version =', $release);
229+ return $release;
230+}
231+
232+sub get_perl_version {
233+ my ($self, %args) = @_;
234+ my $item = $args{item};
235+ return unless $item;
236+
237+ my $version = sprintf '%vd', $PERL_VERSION;
238+ PTDEBUG && _d('Perl version', $version);
239+ return $version;
240+}
241+
242+sub get_perl_module_version {
243+ my ($self, %args) = @_;
244+ my $item = $args{item};
245+ return unless $item;
246+
247+ my $var = $item->{item} . '::VERSION';
248+ my $version = _get_scalar($var);
249+ PTDEBUG && _d('Perl version for', $var, '=', "$version");
250+
251+ return $version ? "$version" : $version;
252+}
253+
254+sub _get_scalar {
255+ no strict;
256+ return ${*{shift()}};
257+}
258+
259+sub get_mysql_variable {
260+ my $self = shift;
261+ return $self->_get_from_mysql(
262+ show => 'VARIABLES',
263+ @_,
264+ );
265+}
266+
267+
268+sub _get_from_mysql {
269+ my ($self, %args) = @_;
270+ my $show = $args{show};
271+ my $item = $args{item};
272+ my $dbh = $args{dbh};
273+ return unless $show && $item && $dbh;
274+
275+ local $dbh->{FetchHashKeyName} = 'NAME_lc';
276+ my $sql = qq/SHOW $show/;
277+ PTDEBUG && _d($sql);
278+ my $rows = $dbh->selectall_hashref($sql, 'variable_name');
279+
280+ my @versions;
281+ foreach my $var ( @{$item->{vars}} ) {
282+ $var = lc($var);
283+ my $version = $rows->{$var}->{value};
284+ PTDEBUG && _d('MySQL version for', $item->{item}, '=', $version);
285+ push @versions, $version;
286+ }
287+
288+ return join(' ', @versions);
289+}
290+
291+sub get_bin_version {
292+ my ($self, %args) = @_;
293+ my $item = $args{item};
294+ my $cmd = $item->{item};
295+ return unless $cmd;
296+
297+ my $sanitized_command = File::Basename::basename($cmd);
298+ PTDEBUG && _d('cmd:', $cmd, 'sanitized:', $sanitized_command);
299+ return if $sanitized_command !~ /\A[a-zA-Z0-9_-]+\z/;
300+
301+ my $output = `$sanitized_command --version 2>&1`;
302+ PTDEBUG && _d('output:', $output);
303+
304+ my ($version) = $output =~ /v?([0-9]+\.[0-9]+(?:\.[\w-]+)?)/;
305+
306+ PTDEBUG && _d('Version for', $sanitized_command, '=', $version);
307+ return $version;
308+}
309+
310+sub _d {
311+ my ($package, undef, $line) = caller 0;
312+ @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
313+ map { defined $_ ? $_ : 'undef' }
314+ @_;
315+ print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
316+}
317+
318+1;
319+}
320+# ###########################################################################
321+# End VersionCheck package
322+# ###########################################################################
323+
324+# ###########################################################################
325+# HTTPMicro package
326+# This package is a copy without comments from the original. The original
327+# with comments and its test file can be found in the Bazaar repository at,
328+# lib/HTTPMicro.pm
329+# t/lib/HTTPMicro.t
330+# See https://launchpad.net/percona-toolkit for more information.
331+# ###########################################################################
332+{
333+
334+package HTTPMicro;
335+BEGIN {
336+ $HTTPMicro::VERSION = '0.001';
337+}
338+use strict;
339+use warnings;
340+
341+use Carp ();
342+
343+
344+my @attributes;
345+BEGIN {
346+ @attributes = qw(agent timeout);
347+ no strict 'refs';
348+ for my $accessor ( @attributes ) {
349+ *{$accessor} = sub {
350+ @_ > 1 ? $_[0]->{$accessor} = $_[1] : $_[0]->{$accessor};
351+ };
352+ }
353+}
354+
355+sub new {
356+ my($class, %args) = @_;
357+ (my $agent = $class) =~ s{::}{-}g;
358+ my $self = {
359+ agent => $agent . "/" . ($class->VERSION || 0),
360+ timeout => 60,
361+ };
362+ for my $key ( @attributes ) {
363+ $self->{$key} = $args{$key} if exists $args{$key}
364+ }
365+ return bless $self, $class;
366+}
367+
368+sub request {
369+ my ($self, $method, $url, $args) = @_;
370+ @_ == 3 || (@_ == 4 && ref $args eq 'HASH')
371+ or Carp::croak(q/Usage: $http->request(METHOD, URL, [HASHREF])/);
372+ $args ||= {}; # we keep some state in this during _request
373+
374+ my $response;
375+ for ( 0 .. 1 ) {
376+ $response = eval { $self->_request($method, $url, $args) };
377+ last unless $@ && $method eq 'GET'
378+ && $@ =~ m{^(?:Socket closed|Unexpected end)};
379+ }
380+
381+ if (my $e = "$@") {
382+ $response = {
383+ success => q{},
384+ status => 599,
385+ reason => 'Internal Exception',
386+ content => $e,
387+ headers => {
388+ 'content-type' => 'text/plain',
389+ 'content-length' => length $e,
390+ }
391+ };
392+ }
393+ return $response;
394+}
395+
396+sub _request {
397+ my ($self, $method, $url, $args) = @_;
398+
399+ my ($scheme, $host, $port, $path_query) = $self->_split_url($url);
400+
401+ my $request = {
402+ method => $method,
403+ scheme => $scheme,
404+ host_port => ($port == 80 ? $host : "$host:$port"),
405+ uri => $path_query,
406+ headers => {},
407+ };
408+
409+ my $handle = HTTPMicro::Handle->new(timeout => $self->{timeout});
410+
411+ $handle->connect($scheme, $host, $port);
412+
413+ $self->_prepare_headers_and_cb($request, $args);
414+ $handle->write_request_header(@{$request}{qw/method uri headers/});
415+ $handle->write_content_body($request) if $request->{content};
416+
417+ my $response;
418+ do { $response = $handle->read_response_header }
419+ until (substr($response->{status},0,1) ne '1');
420+
421+ if (!($method eq 'HEAD' || $response->{status} =~ /^[23]04/)) {
422+ $response->{content} = '';
423+ $handle->read_content_body(sub { $_[1]->{content} .= $_[0] }, $response);
424+ }
425+
426+ $handle->close;
427+ $response->{success} = substr($response->{status},0,1) eq '2';
428+ return $response;
429+}
430+
431+sub _prepare_headers_and_cb {
432+ my ($self, $request, $args) = @_;
433+
434+ for ($args->{headers}) {
435+ next unless defined;
436+ while (my ($k, $v) = each %$_) {
437+ $request->{headers}{lc $k} = $v;
438+ }
439+ }
440+ $request->{headers}{'host'} = $request->{host_port};
441+ $request->{headers}{'connection'} = "close";
442+ $request->{headers}{'user-agent'} ||= $self->{agent};
443+
444+ if (defined $args->{content}) {
445+ $request->{headers}{'content-type'} ||= "application/octet-stream";
446+ utf8::downgrade($args->{content}, 1)
447+ or Carp::croak(q/Wide character in request message body/);
448+ $request->{headers}{'content-length'} = length $args->{content};
449+ $request->{content} = $args->{content};
450+ }
451+ return;
452+}
453+
454+sub _split_url {
455+ my $url = pop;
456+
457+ my ($scheme, $authority, $path_query) = $url =~ m<\A([^:/?#]+)://([^/?#]*)([^#]*)>
458+ or Carp::croak(qq/Cannot parse URL: '$url'/);
459+
460+ $scheme = lc $scheme;
461+ $path_query = "/$path_query" unless $path_query =~ m<\A/>;
462+
463+ my $host = (length($authority)) ? lc $authority : 'localhost';
464+ $host =~ s/\A[^@]*@//; # userinfo
465+ my $port = do {
466+ $host =~ s/:([0-9]*)\z// && length $1
467+ ? $1
468+ : ($scheme eq 'http' ? 80 : undef);
469+ };
470+
471+ return ($scheme, $host, $port, $path_query);
472+}
473+
474+package
475+ HTTPMicro::Handle; # hide from PAUSE/indexers
476+use strict;
477+use warnings;
478+
479+use Carp qw[croak];
480+use Errno qw[EINTR EPIPE];
481+use IO::Socket qw[SOCK_STREAM];
482+
483+sub BUFSIZE () { 32768 }
484+
485+my $Printable = sub {
486+ local $_ = shift;
487+ s/\r/\\r/g;
488+ s/\n/\\n/g;
489+ s/\t/\\t/g;
490+ s/([^\x20-\x7E])/sprintf('\\x%.2X', ord($1))/ge;
491+ $_;
492+};
493+
494+sub new {
495+ my ($class, %args) = @_;
496+ return bless {
497+ rbuf => '',
498+ timeout => 60,
499+ max_line_size => 16384,
500+ %args
501+ }, $class;
502+}
503+
504+sub connect {
505+ @_ == 4 || croak(q/Usage: $handle->connect(scheme, host, port)/);
506+ my ($self, $scheme, $host, $port) = @_;
507+
508+ if ( $scheme ne 'http' ) {
509+ croak(qq/Unsupported URL scheme '$scheme'/);
510+ }
511+
512+ $self->{fh} = 'IO::Socket::INET'->new(
513+ PeerHost => $host,
514+ PeerPort => $port,
515+ Proto => 'tcp',
516+ Type => SOCK_STREAM,
517+ Timeout => $self->{timeout}
518+ ) or croak(qq/Could not connect to '$host:$port': $@/);
519+
520+ binmode($self->{fh})
521+ or croak(qq/Could not binmode() socket: '$!'/);
522+
523+ $self->{host} = $host;
524+ $self->{port} = $port;
525+
526+ return $self;
527+}
528+
529+sub close {
530+ @_ == 1 || croak(q/Usage: $handle->close()/);
531+ my ($self) = @_;
532+ CORE::close($self->{fh})
533+ or croak(qq/Could not close socket: '$!'/);
534+}
535+
536+sub write {
537+ @_ == 2 || croak(q/Usage: $handle->write(buf)/);
538+ my ($self, $buf) = @_;
539+
540+ my $len = length $buf;
541+ my $off = 0;
542+
543+ local $SIG{PIPE} = 'IGNORE';
544+
545+ while () {
546+ $self->can_write
547+ or croak(q/Timed out while waiting for socket to become ready for writing/);
548+ my $r = syswrite($self->{fh}, $buf, $len, $off);
549+ if (defined $r) {
550+ $len -= $r;
551+ $off += $r;
552+ last unless $len > 0;
553+ }
554+ elsif ($! == EPIPE) {
555+ croak(qq/Socket closed by remote server: $!/);
556+ }
557+ elsif ($! != EINTR) {
558+ croak(qq/Could not write to socket: '$!'/);
559+ }
560+ }
561+ return $off;
562+}
563+
564+sub read {
565+ @_ == 2 || @_ == 3 || croak(q/Usage: $handle->read(len)/);
566+ my ($self, $len) = @_;
567+
568+ my $buf = '';
569+ my $got = length $self->{rbuf};
570+
571+ if ($got) {
572+ my $take = ($got < $len) ? $got : $len;
573+ $buf = substr($self->{rbuf}, 0, $take, '');
574+ $len -= $take;
575+ }
576+
577+ while ($len > 0) {
578+ $self->can_read
579+ or croak(q/Timed out while waiting for socket to become ready for reading/);
580+ my $r = sysread($self->{fh}, $buf, $len, length $buf);
581+ if (defined $r) {
582+ last unless $r;
583+ $len -= $r;
584+ }
585+ elsif ($! != EINTR) {
586+ croak(qq/Could not read from socket: '$!'/);
587+ }
588+ }
589+ if ($len) {
590+ croak(q/Unexpected end of stream/);
591+ }
592+ return $buf;
593+}
594+
595+sub readline {
596+ @_ == 1 || croak(q/Usage: $handle->readline()/);
597+ my ($self) = @_;
598+
599+ while () {
600+ if ($self->{rbuf} =~ s/\A ([^\x0D\x0A]* \x0D?\x0A)//x) {
601+ return $1;
602+ }
603+ $self->can_read
604+ or croak(q/Timed out while waiting for socket to become ready for reading/);
605+ my $r = sysread($self->{fh}, $self->{rbuf}, BUFSIZE, length $self->{rbuf});
606+ if (defined $r) {
607+ last unless $r;
608+ }
609+ elsif ($! != EINTR) {
610+ croak(qq/Could not read from socket: '$!'/);
611+ }
612+ }
613+ croak(q/Unexpected end of stream while looking for line/);
614+}
615+
616+sub read_header_lines {
617+ @_ == 1 || @_ == 2 || croak(q/Usage: $handle->read_header_lines([headers])/);
618+ my ($self, $headers) = @_;
619+ $headers ||= {};
620+ my $lines = 0;
621+ my $val;
622+
623+ while () {
624+ my $line = $self->readline;
625+
626+ if ($line =~ /\A ([^\x00-\x1F\x7F:]+) : [\x09\x20]* ([^\x0D\x0A]*)/x) {
627+ my ($field_name) = lc $1;
628+ $val = \($headers->{$field_name} = $2);
629+ }
630+ elsif ($line =~ /\A [\x09\x20]+ ([^\x0D\x0A]*)/x) {
631+ $val
632+ or croak(q/Unexpected header continuation line/);
633+ next unless length $1;
634+ $$val .= ' ' if length $$val;
635+ $$val .= $1;
636+ }
637+ elsif ($line =~ /\A \x0D?\x0A \z/x) {
638+ last;
639+ }
640+ else {
641+ croak(q/Malformed header line: / . $Printable->($line));
642+ }
643+ }
644+ return $headers;
645+}
646+
647+sub write_header_lines {
648+ (@_ == 2 && ref $_[1] eq 'HASH') || croak(q/Usage: $handle->write_header_lines(headers)/);
649+ my($self, $headers) = @_;
650+
651+ my $buf = '';
652+ while (my ($k, $v) = each %$headers) {
653+ my $field_name = lc $k;
654+ $field_name =~ /\A [\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7A\x7C\x7E]+ \z/x
655+ or croak(q/Invalid HTTP header field name: / . $Printable->($field_name));
656+ $field_name =~ s/\b(\w)/\u$1/g;
657+ $buf .= "$field_name: $v\x0D\x0A";
658+ }
659+ $buf .= "\x0D\x0A";
660+ return $self->write($buf);
661+}
662+
663+sub read_content_body {
664+ @_ == 3 || @_ == 4 || croak(q/Usage: $handle->read_content_body(callback, response, [read_length])/);
665+ my ($self, $cb, $response, $len) = @_;
666+ $len ||= $response->{headers}{'content-length'};
667+
668+ croak("No content-length in the returned response, and this "
669+ . "UA doesn't implement chunking") unless defined $len;
670+
671+ while ($len > 0) {
672+ my $read = ($len > BUFSIZE) ? BUFSIZE : $len;
673+ $cb->($self->read($read), $response);
674+ $len -= $read;
675+ }
676+
677+ return;
678+}
679+
680+sub write_content_body {
681+ @_ == 2 || croak(q/Usage: $handle->write_content_body(request)/);
682+ my ($self, $request) = @_;
683+ my ($len, $content_length) = (0, $request->{headers}{'content-length'});
684+
685+ $len += $self->write($request->{content});
686+
687+ $len == $content_length
688+ or croak(qq/Content-Length missmatch (got: $len expected: $content_length)/);
689+
690+ return $len;
691+}
692+
693+sub read_response_header {
694+ @_ == 1 || croak(q/Usage: $handle->read_response_header()/);
695+ my ($self) = @_;
696+
697+ my $line = $self->readline;
698+
699+ $line =~ /\A (HTTP\/(0*\d+\.0*\d+)) [\x09\x20]+ ([0-9]{3}) [\x09\x20]+ ([^\x0D\x0A]*) \x0D?\x0A/x
700+ or croak(q/Malformed Status-Line: / . $Printable->($line));
701+
702+ my ($protocol, $version, $status, $reason) = ($1, $2, $3, $4);
703+
704+ return {
705+ status => $status,
706+ reason => $reason,
707+ headers => $self->read_header_lines,
708+ protocol => $protocol,
709+ };
710+}
711+
712+sub write_request_header {
713+ @_ == 4 || croak(q/Usage: $handle->write_request_header(method, request_uri, headers)/);
714+ my ($self, $method, $request_uri, $headers) = @_;
715+
716+ return $self->write("$method $request_uri HTTP/1.1\x0D\x0A")
717+ + $self->write_header_lines($headers);
718+}
719+
720+sub _do_timeout {
721+ my ($self, $type, $timeout) = @_;
722+ $timeout = $self->{timeout}
723+ unless defined $timeout && $timeout >= 0;
724+
725+ my $fd = fileno $self->{fh};
726+ defined $fd && $fd >= 0
727+ or croak(q/select(2): 'Bad file descriptor'/);
728+
729+ my $initial = time;
730+ my $pending = $timeout;
731+ my $nfound;
732+
733+ vec(my $fdset = '', $fd, 1) = 1;
734+
735+ while () {
736+ $nfound = ($type eq 'read')
737+ ? select($fdset, undef, undef, $pending)
738+ : select(undef, $fdset, undef, $pending) ;
739+ if ($nfound == -1) {
740+ $! == EINTR
741+ or croak(qq/select(2): '$!'/);
742+ redo if !$timeout || ($pending = $timeout - (time - $initial)) > 0;
743+ $nfound = 0;
744+ }
745+ last;
746+ }
747+ $! = 0;
748+ return $nfound;
749+}
750+
751+sub can_read {
752+ @_ == 1 || @_ == 2 || croak(q/Usage: $handle->can_read([timeout])/);
753+ my $self = shift;
754+ return $self->_do_timeout('read', @_)
755+}
756+
757+sub can_write {
758+ @_ == 1 || @_ == 2 || croak(q/Usage: $handle->can_write([timeout])/);
759+ my $self = shift;
760+ return $self->_do_timeout('write', @_)
761+}
762+
763+1;
764+}
765+# ###########################################################################
766+# End HTTPMicro package
767+# ###########################################################################
768+
769+# ###########################################################################
770+# Pingback package
771+# This package is a copy without comments from the original. The original
772+# with comments and its test file can be found in the Bazaar repository at,
773+# lib/Pingback.pm
774+# t/lib/Pingback.t
775+# See https://launchpad.net/percona-toolkit for more information.
776+# ###########################################################################
777+{
778+package Pingback;
779+
780+use strict;
781+use warnings FATAL => 'all';
782+use English qw(-no_match_vars);
783+
784+use constant PTDEBUG => $ENV{PTDEBUG} || 0;
785+
786+use File::Basename qw();
787+use Data::Dumper qw();
788+use Fcntl qw(:DEFAULT);
789+
790+use File::Spec;
791+
792+my $dir = File::Spec->tmpdir();
793+my $check_time_file = File::Spec->catfile($dir,'percona-toolkit-version-check');
794+my $check_time_limit = 60 * 60 * 24; # one day
795+
796+sub Dumper {
797+ local $Data::Dumper::Indent = 1;
798+ local $Data::Dumper::Sortkeys = 1;
799+ local $Data::Dumper::Quotekeys = 0;
800+
801+ Data::Dumper::Dumper(@_);
802+}
803+
804+local $EVAL_ERROR;
805+eval {
806+ require HTTPMicro;
807+ require VersionCheck;
808+};
809+
810+sub version_check {
811+ eval {
812+ if (exists $ENV{PERCONA_VERSION_CHECK} && !$ENV{PERCONA_VERSION_CHECK}) {
813+ if ( $ENV{PTVCDEBUG} || PTDEBUG ) {
814+ _d('--version-check is disabled by the PERCONA_VERSION_CHECK',
815+ 'environment variable');
816+ }
817+ return;
818+ }
819+
820+ if ( !time_to_check($check_time_file) ) {
821+ if ( $ENV{PTVCDEBUG} || PTDEBUG ) {
822+ _d('It is not time to --version-checka again;',
823+ 'only 1 check per', $check_time_limit, 'seconds, and the last',
824+ 'check was performed on the modified time of', $check_time_file);
825+ }
826+ return;
827+ }
828+
829+ my $dbh = shift; # optional
830+ my $advice = pingback(
831+ url => $ENV{PERCONA_VERSION_CHECK_URL} || 'http://v.percona.com',
832+ dbh => $dbh,
833+ );
834+ if ( $advice ) {
835+ print "# Percona suggests these upgrades:\n";
836+ print join("\n", map { "# * $_" } @$advice);
837+ print "\n# Specify --no-version-check to disable these suggestions.\n\n";
838+ }
839+ elsif ( $ENV{PTVCDEBUG} || PTDEBUG ) {
840+ _d('--version-check worked, but there were no suggestions');
841+ }
842+ };
843+ if ( $EVAL_ERROR ) {
844+ if ( $ENV{PTVCDEBUG} || PTDEBUG ) {
845+ _d('Error doing --version-check:', $EVAL_ERROR);
846+ }
847+ }
848+
849+ return;
850+}
851+
852+sub pingback {
853+ my (%args) = @_;
854+ my @required_args = qw(url);
855+ foreach my $arg ( @required_args ) {
856+ die "I need a $arg arugment" unless $args{$arg};
857+ }
858+ my ($url) = @args{@required_args};
859+
860+ my ($dbh, $ua, $vc) = @args{qw(dbh ua VersionCheck)};
861+
862+ $ua ||= HTTPMicro->new( timeout => 2 );
863+ $vc ||= VersionCheck->new();
864+
865+ my $response = $ua->request('GET', $url);
866+ PTDEBUG && _d('Server response:', Dumper($response));
867+ die "No response from GET $url"
868+ if !$response;
869+ die "GET $url returned HTTP status $response->{status}; expected 200"
870+ if $response->{status} != 200;
871+ die "GET $url did not return any programs to check"
872+ if !$response->{content};
873+
874+ my $items = $vc->parse_server_response(
875+ response => $response->{content}
876+ );
877+ die "Failed to parse server requested programs: $response->{content}"
878+ if !scalar keys %$items;
879+
880+ my $versions = $vc->get_versions(
881+ items => $items,
882+ dbh => $dbh,
883+ );
884+ die "Failed to get any program versions; should have at least gotten Perl"
885+ if !scalar keys %$versions;
886+
887+ my $client_content = encode_client_response(
888+ items => $items,
889+ versions => $versions,
890+ );
891+
892+ my $client_response = {
893+ headers => { "X-Percona-Toolkit-Tool" => File::Basename::basename($0) },
894+ content => $client_content,
895+ };
896+ PTDEBUG && _d('Client response:', Dumper($client_response));
897+
898+ $response = $ua->request('POST', $url, $client_response);
899+ PTDEBUG && _d('Server suggestions:', Dumper($response));
900+ die "No response from POST $url $client_response"
901+ if !$response;
902+ die "POST $url returned HTTP status $response->{status}; expected 200"
903+ if $response->{status} != 200;
904+
905+ return unless $response->{content};
906+
907+ $items = $vc->parse_server_response(
908+ response => $response->{content},
909+ split_vars => 0,
910+ );
911+ die "Failed to parse server suggestions: $response->{content}"
912+ if !scalar keys %$items;
913+ my @suggestions = map { $_->{vars} }
914+ sort { $a->{item} cmp $b->{item} }
915+ values %$items;
916+
917+ return \@suggestions;
918+}
919+
920+sub time_to_check {
921+ my ($file) = @_;
922+ die "I need a file argument" unless $file;
923+
924+ if ( !-f $file ) {
925+ PTDEBUG && _d('Creating', $file);
926+ _touch($file);
927+ return 1;
928+ }
929+
930+ my $mtime = (stat $file)[9];
931+ if ( !defined $mtime ) {
932+ PTDEBUG && _d('Error getting modified time of', $file);
933+ return 0;
934+ }
935+
936+ my $time = int(time());
937+ PTDEBUG && _d('time=', $time, 'mtime=', $mtime);
938+ if ( ($time - $mtime) > $check_time_limit ) {
939+ _touch($file);
940+ return 1;
941+ }
942+
943+ return 0;
944+}
945+
946+sub _touch {
947+ my ($file) = @_;
948+ sysopen my $fh, $file, O_WRONLY|O_CREAT|O_NONBLOCK
949+ or die "Cannot create $file : $!";
950+ close $fh or die "Cannot close $file : $!";
951+ utime(undef, undef, $file);
952+}
953+
954+sub encode_client_response {
955+ my (%args) = @_;
956+ my @required_args = qw(items versions);
957+ foreach my $arg ( @required_args ) {
958+ die "I need a $arg arugment" unless $args{$arg};
959+ }
960+ my ($items, $versions) = @args{@required_args};
961+
962+ my @lines;
963+ foreach my $item ( sort keys %$items ) {
964+ next unless exists $versions->{$item};
965+ push @lines, join(';', $item, $versions->{$item});
966+ }
967+
968+ my $client_response = join("\n", @lines) . "\n";
969+ PTDEBUG && _d('Client response:', $client_response);
970+ return $client_response;
971+}
972+
973+sub _d {
974+ my ($package, undef, $line) = caller 0;
975+ @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
976+ map { defined $_ ? $_ : 'undef' }
977+ @_;
978+ print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
979+}
980+
981+1;
982+}
983+# ###########################################################################
984+# End Pingback package
985+# ###########################################################################
986+
987+# ###########################################################################
988 # This is a combination of modules and programs in one -- a runnable module.
989 # http://www.perl.com/pub/a/2006/07/13/lightning-articles.html?page=last
990 # Or, look it up in the Camel book on pages 642 and 643 in the 3rd edition.
991@@ -3815,6 +4786,7 @@
992 $Data::Dumper::Indent = 1;
993 $Data::Dumper::Quotekeys = 0;
994
995+use Percona::Toolkit;
996 use constant PTDEBUG => $ENV{PTDEBUG} || 0;
997
998 # Global variables; as few as possible.
999@@ -3833,7 +4805,7 @@
1000 my $q = new Quoter;
1001
1002 sub main {
1003- @ARGV = @_; # set global ARGV for this package
1004+ local @ARGV = @_; # set global ARGV for this package
1005
1006 # Reset global vars else tests, which run this tool as a module,
1007 # may encounter weird results.
1008@@ -4320,6 +5292,13 @@
1009 }
1010
1011 # ########################################################################
1012+ # Do the version-check
1013+ # ########################################################################
1014+ if ( $o->get('version-check') && ($o->has('quiet') && !$o->get('quiet')) ) {
1015+ Pingback::version_check($src->{dbh});
1016+ }
1017+
1018+ # ########################################################################
1019 # Start archiving.
1020 # ########################################################################
1021 my $start = time();
1022@@ -5746,6 +6725,24 @@
1023
1024 Show version and exit.
1025
1026+=item --[no]version-check
1027+
1028+default: yes
1029+
1030+Send program versions to Percona and print suggested upgrades and problems.
1031+
1032+The version check feature causes the tool to send and receive data from
1033+Percona over the web. The data contains program versions from the local
1034+machine. Percona uses the data to focus development on the most widely
1035+used versions of programs, and to suggest to customers possible upgrades
1036+and known bad versions of programs.
1037+
1038+This feature can be disabled by specifying C<--no-version-check> on the
1039+command line or in one of several L<"--config"> files, or by setting the
1040+environment variable C<PERCONA_VERSION_CHECK=0>.
1041+
1042+For more information, visit L<http://www.percona.com/version-check>.
1043+
1044 =item --where
1045
1046 type: string
1047
1048=== modified file 'bin/pt-config-diff'
1049--- bin/pt-config-diff 2012-08-03 18:41:41 +0000
1050+++ bin/pt-config-diff 2012-08-23 01:57:20 +0000
1051@@ -6,7 +6,44 @@
1052
1053 use strict;
1054 use warnings FATAL => 'all';
1055-use constant PTDEBUG => $ENV{PTDEBUG} || 0;
1056+
1057+# This tool is "fat-packed": most of its dependent modules are embedded
1058+# in this file. Setting %INC to this file for each module makes Perl aware
1059+# of this so it will not try to load the module from @INC. See the tool's
1060+# documentation for a full list of dependencies.
1061+BEGIN {
1062+ $INC{$_} = __FILE__ for map { (my $pkg = "$_.pm") =~ s!::!/!g; $pkg } (qw(
1063+ Percona::Toolkit
1064+ OptionParser
1065+ DSNParser
1066+ Cxn
1067+ Daemon
1068+ TextResultSetParser
1069+ MySQLConfig
1070+ MySQLConfigComparer
1071+ ReportFormatter
1072+ VersionCheck
1073+ HTTPMicro
1074+ Pingback
1075+ ));
1076+}
1077+
1078+# ###########################################################################
1079+# Percona::Toolkit package
1080+# This package is a copy without comments from the original. The original
1081+# with comments and its test file can be found in the Bazaar repository at,
1082+# lib/Percona/Toolkit.pm
1083+# t/lib/Percona/Toolkit.t
1084+# See https://launchpad.net/percona-toolkit for more information.
1085+# ###########################################################################
1086+{
1087+package Percona::Toolkit;
1088+our $VERSION = '2.1.3';
1089+1;
1090+}
1091+# ###########################################################################
1092+# End Percona::Toolkit package
1093+# ###########################################################################
1094
1095 # ###########################################################################
1096 # OptionParser package
1097@@ -2900,6 +2937,939 @@
1098 # ###########################################################################
1099
1100 # ###########################################################################
1101+# VersionCheck package
1102+# This package is a copy without comments from the original. The original
1103+# with comments and its test file can be found in the Bazaar repository at,
1104+# lib/VersionCheck.pm
1105+# t/lib/VersionCheck.t
1106+# See https://launchpad.net/percona-toolkit for more information.
1107+# ###########################################################################
1108+{
1109+package VersionCheck;
1110+
1111+use strict;
1112+use warnings FATAL => 'all';
1113+use English qw(-no_match_vars);
1114+
1115+use constant PTDEBUG => $ENV{PTDEBUG} || 0;
1116+
1117+use File::Basename ();
1118+use Data::Dumper ();
1119+
1120+sub Dumper {
1121+ local $Data::Dumper::Indent = 1;
1122+ local $Data::Dumper::Sortkeys = 1;
1123+ local $Data::Dumper::Quotekeys = 0;
1124+
1125+ Data::Dumper::Dumper(@_);
1126+}
1127+
1128+sub new {
1129+ my ($class, %args) = @_;
1130+ my $self = {
1131+ valid_types => qr/
1132+ ^(?:
1133+ os_version
1134+ |perl_version
1135+ |perl_module_version
1136+ |mysql_variable
1137+ |bin_version
1138+ )$/x,
1139+ };
1140+ return bless $self, $class;
1141+}
1142+
1143+sub parse_server_response {
1144+ my ($self, %args) = @_;
1145+ my @required_args = qw(response);
1146+ foreach my $arg ( @required_args ) {
1147+ die "I need a $arg arugment" unless $args{$arg};
1148+ }
1149+ my ($response) = @args{@required_args};
1150+
1151+ my %items = map {
1152+ my ($item, $type, $vars) = split(";", $_);
1153+ if ( !defined $args{split_vars} || $args{split_vars} ) {
1154+ $vars = [ split(",", ($vars || '')) ];
1155+ }
1156+ $item => {
1157+ item => $item,
1158+ type => $type,
1159+ vars => $vars,
1160+ };
1161+ } split("\n", $response);
1162+
1163+ PTDEBUG && _d('Items:', Dumper(\%items));
1164+
1165+ return \%items;
1166+}
1167+
1168+sub get_versions {
1169+ my ($self, %args) = @_;
1170+ my @required_args = qw(items);
1171+ foreach my $arg ( @required_args ) {
1172+ die "I need a $arg arugment" unless $args{$arg};
1173+ }
1174+ my ($items) = @args{@required_args};
1175+ my $dbh = $args{dbh}; # optional
1176+
1177+ my %versions;
1178+ foreach my $item ( values %$items ) {
1179+ next unless $self->valid_item($item);
1180+
1181+ eval {
1182+ my $func = 'get_' . $item->{type};
1183+ my $version = $self->$func(
1184+ item => $item,
1185+ dbh => $dbh,
1186+ );
1187+ if ( $version ) {
1188+ chomp $version;
1189+ $versions{$item->{item}} = $version;
1190+ }
1191+ };
1192+ if ( $EVAL_ERROR ) {
1193+ PTDEBUG && _d('Error getting version for', Dumper($item), $EVAL_ERROR);
1194+ }
1195+ }
1196+
1197+ return \%versions;
1198+}
1199+
1200+sub valid_item {
1201+ my ($self, $item) = @_;
1202+ return unless $item;
1203+
1204+ if ( ($item->{type} || '') !~ m/$self->{valid_types}/ ) {
1205+ PTDEBUG && _d('Invalid type:', $item->{type});
1206+ return;
1207+ }
1208+
1209+ return 1;
1210+}
1211+
1212+sub get_os_version {
1213+ my ($self) = @_;
1214+
1215+ chomp(my $platform = `uname -s`);
1216+ PTDEBUG && _d('platform:', $platform);
1217+ return $OSNAME unless $platform;
1218+
1219+ chomp(my $lsb_release
1220+ = `which lsb_release 2>/dev/null | awk '{print \$1}'` || '');
1221+ PTDEBUG && _d('lsb_release:', $lsb_release);
1222+
1223+ my $release = "";
1224+
1225+ if ( $platform eq 'Linux' ) {
1226+ if ( -f "/etc/fedora-release" ) {
1227+ $release = `cat /etc/fedora-release`;
1228+ }
1229+ elsif ( -f "/etc/redhat-release" ) {
1230+ $release = `cat /etc/redhat-release`;
1231+ }
1232+ elsif ( -f "/etc/system-release" ) {
1233+ $release = `cat /etc/system-release`;
1234+ }
1235+ elsif ( $lsb_release ) {
1236+ $release = `$lsb_release -ds`;
1237+ }
1238+ elsif ( -f "/etc/lsb-release" ) {
1239+ $release = `grep DISTRIB_DESCRIPTION /etc/lsb-release`;
1240+ $release =~ s/^\w+="([^"]+)".+/$1/;
1241+ }
1242+ elsif ( -f "/etc/debian_version" ) {
1243+ chomp(my $rel = `cat /etc/debian_version`);
1244+ $release = "Debian $rel";
1245+ if ( -f "/etc/apt/sources.list" ) {
1246+ chomp(my $code_name = `awk '/^deb/ {print \$3}' /etc/apt/sources.list | awk -F/ '{print \$1}'| awk 'BEGIN {FS="|"} {print \$1}' | sort | uniq -c | sort -rn | head -n1 | awk '{print \$2}'`);
1247+ $release .= " ($code_name)" if $code_name;
1248+ }
1249+ }
1250+ elsif ( `ls /etc/*release 2>/dev/null` ) {
1251+ if ( `grep DISTRIB_DESCRIPTION /etc/*release 2>/dev/null` ) {
1252+ $release = `grep DISTRIB_DESCRIPTION /etc/*release | head -n1`;
1253+ }
1254+ else {
1255+ $release = `cat /etc/*release | head -n1`;
1256+ }
1257+ }
1258+ }
1259+ elsif ( $platform =~ m/^(BSD|Darwin)$/ ) {
1260+ my $rel = `uname -r`;
1261+ $release = "$platform $rel";
1262+ }
1263+ elsif ( $platform eq "SunOS" ) {
1264+ my $rel = `head -n1 /etc/release` || `uname -r`;
1265+ $release = "$platform $rel";
1266+ }
1267+
1268+ if ( !$release ) {
1269+ PTDEBUG && _d('Failed to get the release, using platform');
1270+ $release = $platform;
1271+ }
1272+ chomp($release);
1273+
1274+ PTDEBUG && _d('OS version =', $release);
1275+ return $release;
1276+}
1277+
1278+sub get_perl_version {
1279+ my ($self, %args) = @_;
1280+ my $item = $args{item};
1281+ return unless $item;
1282+
1283+ my $version = sprintf '%vd', $PERL_VERSION;
1284+ PTDEBUG && _d('Perl version', $version);
1285+ return $version;
1286+}
1287+
1288+sub get_perl_module_version {
1289+ my ($self, %args) = @_;
1290+ my $item = $args{item};
1291+ return unless $item;
1292+
1293+ my $var = $item->{item} . '::VERSION';
1294+ my $version = _get_scalar($var);
1295+ PTDEBUG && _d('Perl version for', $var, '=', "$version");
1296+
1297+ return $version ? "$version" : $version;
1298+}
1299+
1300+sub _get_scalar {
1301+ no strict;
1302+ return ${*{shift()}};
1303+}
1304+
1305+sub get_mysql_variable {
1306+ my $self = shift;
1307+ return $self->_get_from_mysql(
1308+ show => 'VARIABLES',
1309+ @_,
1310+ );
1311+}
1312+
1313+
1314+sub _get_from_mysql {
1315+ my ($self, %args) = @_;
1316+ my $show = $args{show};
1317+ my $item = $args{item};
1318+ my $dbh = $args{dbh};
1319+ return unless $show && $item && $dbh;
1320+
1321+ local $dbh->{FetchHashKeyName} = 'NAME_lc';
1322+ my $sql = qq/SHOW $show/;
1323+ PTDEBUG && _d($sql);
1324+ my $rows = $dbh->selectall_hashref($sql, 'variable_name');
1325+
1326+ my @versions;
1327+ foreach my $var ( @{$item->{vars}} ) {
1328+ $var = lc($var);
1329+ my $version = $rows->{$var}->{value};
1330+ PTDEBUG && _d('MySQL version for', $item->{item}, '=', $version);
1331+ push @versions, $version;
1332+ }
1333+
1334+ return join(' ', @versions);
1335+}
1336+
1337+sub get_bin_version {
1338+ my ($self, %args) = @_;
1339+ my $item = $args{item};
1340+ my $cmd = $item->{item};
1341+ return unless $cmd;
1342+
1343+ my $sanitized_command = File::Basename::basename($cmd);
1344+ PTDEBUG && _d('cmd:', $cmd, 'sanitized:', $sanitized_command);
1345+ return if $sanitized_command !~ /\A[a-zA-Z0-9_-]+\z/;
1346+
1347+ my $output = `$sanitized_command --version 2>&1`;
1348+ PTDEBUG && _d('output:', $output);
1349+
1350+ my ($version) = $output =~ /v?([0-9]+\.[0-9]+(?:\.[\w-]+)?)/;
1351+
1352+ PTDEBUG && _d('Version for', $sanitized_command, '=', $version);
1353+ return $version;
1354+}
1355+
1356+sub _d {
1357+ my ($package, undef, $line) = caller 0;
1358+ @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
1359+ map { defined $_ ? $_ : 'undef' }
1360+ @_;
1361+ print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
1362+}
1363+
1364+1;
1365+}
1366+# ###########################################################################
1367+# End VersionCheck package
1368+# ###########################################################################
1369+
1370+# ###########################################################################
1371+# HTTPMicro package
1372+# This package is a copy without comments from the original. The original
1373+# with comments and its test file can be found in the Bazaar repository at,
1374+# lib/HTTPMicro.pm
1375+# t/lib/HTTPMicro.t
1376+# See https://launchpad.net/percona-toolkit for more information.
1377+# ###########################################################################
1378+{
1379+
1380+package HTTPMicro;
1381+BEGIN {
1382+ $HTTPMicro::VERSION = '0.001';
1383+}
1384+use strict;
1385+use warnings;
1386+
1387+use Carp ();
1388+
1389+
1390+my @attributes;
1391+BEGIN {
1392+ @attributes = qw(agent timeout);
1393+ no strict 'refs';
1394+ for my $accessor ( @attributes ) {
1395+ *{$accessor} = sub {
1396+ @_ > 1 ? $_[0]->{$accessor} = $_[1] : $_[0]->{$accessor};
1397+ };
1398+ }
1399+}
1400+
1401+sub new {
1402+ my($class, %args) = @_;
1403+ (my $agent = $class) =~ s{::}{-}g;
1404+ my $self = {
1405+ agent => $agent . "/" . ($class->VERSION || 0),
1406+ timeout => 60,
1407+ };
1408+ for my $key ( @attributes ) {
1409+ $self->{$key} = $args{$key} if exists $args{$key}
1410+ }
1411+ return bless $self, $class;
1412+}
1413+
1414+sub request {
1415+ my ($self, $method, $url, $args) = @_;
1416+ @_ == 3 || (@_ == 4 && ref $args eq 'HASH')
1417+ or Carp::croak(q/Usage: $http->request(METHOD, URL, [HASHREF])/);
1418+ $args ||= {}; # we keep some state in this during _request
1419+
1420+ my $response;
1421+ for ( 0 .. 1 ) {
1422+ $response = eval { $self->_request($method, $url, $args) };
1423+ last unless $@ && $method eq 'GET'
1424+ && $@ =~ m{^(?:Socket closed|Unexpected end)};
1425+ }
1426+
1427+ if (my $e = "$@") {
1428+ $response = {
1429+ success => q{},
1430+ status => 599,
1431+ reason => 'Internal Exception',
1432+ content => $e,
1433+ headers => {
1434+ 'content-type' => 'text/plain',
1435+ 'content-length' => length $e,
1436+ }
1437+ };
1438+ }
1439+ return $response;
1440+}
1441+
1442+sub _request {
1443+ my ($self, $method, $url, $args) = @_;
1444+
1445+ my ($scheme, $host, $port, $path_query) = $self->_split_url($url);
1446+
1447+ my $request = {
1448+ method => $method,
1449+ scheme => $scheme,
1450+ host_port => ($port == 80 ? $host : "$host:$port"),
1451+ uri => $path_query,
1452+ headers => {},
1453+ };
1454+
1455+ my $handle = HTTPMicro::Handle->new(timeout => $self->{timeout});
1456+
1457+ $handle->connect($scheme, $host, $port);
1458+
1459+ $self->_prepare_headers_and_cb($request, $args);
1460+ $handle->write_request_header(@{$request}{qw/method uri headers/});
1461+ $handle->write_content_body($request) if $request->{content};
1462+
1463+ my $response;
1464+ do { $response = $handle->read_response_header }
1465+ until (substr($response->{status},0,1) ne '1');
1466+
1467+ if (!($method eq 'HEAD' || $response->{status} =~ /^[23]04/)) {
1468+ $response->{content} = '';
1469+ $handle->read_content_body(sub { $_[1]->{content} .= $_[0] }, $response);
1470+ }
1471+
1472+ $handle->close;
1473+ $response->{success} = substr($response->{status},0,1) eq '2';
1474+ return $response;
1475+}
1476+
1477+sub _prepare_headers_and_cb {
1478+ my ($self, $request, $args) = @_;
1479+
1480+ for ($args->{headers}) {
1481+ next unless defined;
1482+ while (my ($k, $v) = each %$_) {
1483+ $request->{headers}{lc $k} = $v;
1484+ }
1485+ }
1486+ $request->{headers}{'host'} = $request->{host_port};
1487+ $request->{headers}{'connection'} = "close";
1488+ $request->{headers}{'user-agent'} ||= $self->{agent};
1489+
1490+ if (defined $args->{content}) {
1491+ $request->{headers}{'content-type'} ||= "application/octet-stream";
1492+ utf8::downgrade($args->{content}, 1)
1493+ or Carp::croak(q/Wide character in request message body/);
1494+ $request->{headers}{'content-length'} = length $args->{content};
1495+ $request->{content} = $args->{content};
1496+ }
1497+ return;
1498+}
1499+
1500+sub _split_url {
1501+ my $url = pop;
1502+
1503+ my ($scheme, $authority, $path_query) = $url =~ m<\A([^:/?#]+)://([^/?#]*)([^#]*)>
1504+ or Carp::croak(qq/Cannot parse URL: '$url'/);
1505+
1506+ $scheme = lc $scheme;
1507+ $path_query = "/$path_query" unless $path_query =~ m<\A/>;
1508+
1509+ my $host = (length($authority)) ? lc $authority : 'localhost';
1510+ $host =~ s/\A[^@]*@//; # userinfo
1511+ my $port = do {
1512+ $host =~ s/:([0-9]*)\z// && length $1
1513+ ? $1
1514+ : ($scheme eq 'http' ? 80 : undef);
1515+ };
1516+
1517+ return ($scheme, $host, $port, $path_query);
1518+}
1519+
1520+package
1521+ HTTPMicro::Handle; # hide from PAUSE/indexers
1522+use strict;
1523+use warnings;
1524+
1525+use Carp qw[croak];
1526+use Errno qw[EINTR EPIPE];
1527+use IO::Socket qw[SOCK_STREAM];
1528+
1529+sub BUFSIZE () { 32768 }
1530+
1531+my $Printable = sub {
1532+ local $_ = shift;
1533+ s/\r/\\r/g;
1534+ s/\n/\\n/g;
1535+ s/\t/\\t/g;
1536+ s/([^\x20-\x7E])/sprintf('\\x%.2X', ord($1))/ge;
1537+ $_;
1538+};
1539+
1540+sub new {
1541+ my ($class, %args) = @_;
1542+ return bless {
1543+ rbuf => '',
1544+ timeout => 60,
1545+ max_line_size => 16384,
1546+ %args
1547+ }, $class;
1548+}
1549+
1550+sub connect {
1551+ @_ == 4 || croak(q/Usage: $handle->connect(scheme, host, port)/);
1552+ my ($self, $scheme, $host, $port) = @_;
1553+
1554+ if ( $scheme ne 'http' ) {
1555+ croak(qq/Unsupported URL scheme '$scheme'/);
1556+ }
1557+
1558+ $self->{fh} = 'IO::Socket::INET'->new(
1559+ PeerHost => $host,
1560+ PeerPort => $port,
1561+ Proto => 'tcp',
1562+ Type => SOCK_STREAM,
1563+ Timeout => $self->{timeout}
1564+ ) or croak(qq/Could not connect to '$host:$port': $@/);
1565+
1566+ binmode($self->{fh})
1567+ or croak(qq/Could not binmode() socket: '$!'/);
1568+
1569+ $self->{host} = $host;
1570+ $self->{port} = $port;
1571+
1572+ return $self;
1573+}
1574+
1575+sub close {
1576+ @_ == 1 || croak(q/Usage: $handle->close()/);
1577+ my ($self) = @_;
1578+ CORE::close($self->{fh})
1579+ or croak(qq/Could not close socket: '$!'/);
1580+}
1581+
1582+sub write {
1583+ @_ == 2 || croak(q/Usage: $handle->write(buf)/);
1584+ my ($self, $buf) = @_;
1585+
1586+ my $len = length $buf;
1587+ my $off = 0;
1588+
1589+ local $SIG{PIPE} = 'IGNORE';
1590+
1591+ while () {
1592+ $self->can_write
1593+ or croak(q/Timed out while waiting for socket to become ready for writing/);
1594+ my $r = syswrite($self->{fh}, $buf, $len, $off);
1595+ if (defined $r) {
1596+ $len -= $r;
1597+ $off += $r;
1598+ last unless $len > 0;
1599+ }
1600+ elsif ($! == EPIPE) {
1601+ croak(qq/Socket closed by remote server: $!/);
1602+ }
1603+ elsif ($! != EINTR) {
1604+ croak(qq/Could not write to socket: '$!'/);
1605+ }
1606+ }
1607+ return $off;
1608+}
1609+
1610+sub read {
1611+ @_ == 2 || @_ == 3 || croak(q/Usage: $handle->read(len)/);
1612+ my ($self, $len) = @_;
1613+
1614+ my $buf = '';
1615+ my $got = length $self->{rbuf};
1616+
1617+ if ($got) {
1618+ my $take = ($got < $len) ? $got : $len;
1619+ $buf = substr($self->{rbuf}, 0, $take, '');
1620+ $len -= $take;
1621+ }
1622+
1623+ while ($len > 0) {
1624+ $self->can_read
1625+ or croak(q/Timed out while waiting for socket to become ready for reading/);
1626+ my $r = sysread($self->{fh}, $buf, $len, length $buf);
1627+ if (defined $r) {
1628+ last unless $r;
1629+ $len -= $r;
1630+ }
1631+ elsif ($! != EINTR) {
1632+ croak(qq/Could not read from socket: '$!'/);
1633+ }
1634+ }
1635+ if ($len) {
1636+ croak(q/Unexpected end of stream/);
1637+ }
1638+ return $buf;
1639+}
1640+
1641+sub readline {
1642+ @_ == 1 || croak(q/Usage: $handle->readline()/);
1643+ my ($self) = @_;
1644+
1645+ while () {
1646+ if ($self->{rbuf} =~ s/\A ([^\x0D\x0A]* \x0D?\x0A)//x) {
1647+ return $1;
1648+ }
1649+ $self->can_read
1650+ or croak(q/Timed out while waiting for socket to become ready for reading/);
1651+ my $r = sysread($self->{fh}, $self->{rbuf}, BUFSIZE, length $self->{rbuf});
1652+ if (defined $r) {
1653+ last unless $r;
1654+ }
1655+ elsif ($! != EINTR) {
1656+ croak(qq/Could not read from socket: '$!'/);
1657+ }
1658+ }
1659+ croak(q/Unexpected end of stream while looking for line/);
1660+}
1661+
1662+sub read_header_lines {
1663+ @_ == 1 || @_ == 2 || croak(q/Usage: $handle->read_header_lines([headers])/);
1664+ my ($self, $headers) = @_;
1665+ $headers ||= {};
1666+ my $lines = 0;
1667+ my $val;
1668+
1669+ while () {
1670+ my $line = $self->readline;
1671+
1672+ if ($line =~ /\A ([^\x00-\x1F\x7F:]+) : [\x09\x20]* ([^\x0D\x0A]*)/x) {
1673+ my ($field_name) = lc $1;
1674+ $val = \($headers->{$field_name} = $2);
1675+ }
1676+ elsif ($line =~ /\A [\x09\x20]+ ([^\x0D\x0A]*)/x) {
1677+ $val
1678+ or croak(q/Unexpected header continuation line/);
1679+ next unless length $1;
1680+ $$val .= ' ' if length $$val;
1681+ $$val .= $1;
1682+ }
1683+ elsif ($line =~ /\A \x0D?\x0A \z/x) {
1684+ last;
1685+ }
1686+ else {
1687+ croak(q/Malformed header line: / . $Printable->($line));
1688+ }
1689+ }
1690+ return $headers;
1691+}
1692+
1693+sub write_header_lines {
1694+ (@_ == 2 && ref $_[1] eq 'HASH') || croak(q/Usage: $handle->write_header_lines(headers)/);
1695+ my($self, $headers) = @_;
1696+
1697+ my $buf = '';
1698+ while (my ($k, $v) = each %$headers) {
1699+ my $field_name = lc $k;
1700+ $field_name =~ /\A [\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7A\x7C\x7E]+ \z/x
1701+ or croak(q/Invalid HTTP header field name: / . $Printable->($field_name));
1702+ $field_name =~ s/\b(\w)/\u$1/g;
1703+ $buf .= "$field_name: $v\x0D\x0A";
1704+ }
1705+ $buf .= "\x0D\x0A";
1706+ return $self->write($buf);
1707+}
1708+
1709+sub read_content_body {
1710+ @_ == 3 || @_ == 4 || croak(q/Usage: $handle->read_content_body(callback, response, [read_length])/);
1711+ my ($self, $cb, $response, $len) = @_;
1712+ $len ||= $response->{headers}{'content-length'};
1713+
1714+ croak("No content-length in the returned response, and this "
1715+ . "UA doesn't implement chunking") unless defined $len;
1716+
1717+ while ($len > 0) {
1718+ my $read = ($len > BUFSIZE) ? BUFSIZE : $len;
1719+ $cb->($self->read($read), $response);
1720+ $len -= $read;
1721+ }
1722+
1723+ return;
1724+}
1725+
1726+sub write_content_body {
1727+ @_ == 2 || croak(q/Usage: $handle->write_content_body(request)/);
1728+ my ($self, $request) = @_;
1729+ my ($len, $content_length) = (0, $request->{headers}{'content-length'});
1730+
1731+ $len += $self->write($request->{content});
1732+
1733+ $len == $content_length
1734+ or croak(qq/Content-Length missmatch (got: $len expected: $content_length)/);
1735+
1736+ return $len;
1737+}
1738+
1739+sub read_response_header {
1740+ @_ == 1 || croak(q/Usage: $handle->read_response_header()/);
1741+ my ($self) = @_;
1742+
1743+ my $line = $self->readline;
1744+
1745+ $line =~ /\A (HTTP\/(0*\d+\.0*\d+)) [\x09\x20]+ ([0-9]{3}) [\x09\x20]+ ([^\x0D\x0A]*) \x0D?\x0A/x
1746+ or croak(q/Malformed Status-Line: / . $Printable->($line));
1747+
1748+ my ($protocol, $version, $status, $reason) = ($1, $2, $3, $4);
1749+
1750+ return {
1751+ status => $status,
1752+ reason => $reason,
1753+ headers => $self->read_header_lines,
1754+ protocol => $protocol,
1755+ };
1756+}
1757+
1758+sub write_request_header {
1759+ @_ == 4 || croak(q/Usage: $handle->write_request_header(method, request_uri, headers)/);
1760+ my ($self, $method, $request_uri, $headers) = @_;
1761+
1762+ return $self->write("$method $request_uri HTTP/1.1\x0D\x0A")
1763+ + $self->write_header_lines($headers);
1764+}
1765+
1766+sub _do_timeout {
1767+ my ($self, $type, $timeout) = @_;
1768+ $timeout = $self->{timeout}
1769+ unless defined $timeout && $timeout >= 0;
1770+
1771+ my $fd = fileno $self->{fh};
1772+ defined $fd && $fd >= 0
1773+ or croak(q/select(2): 'Bad file descriptor'/);
1774+
1775+ my $initial = time;
1776+ my $pending = $timeout;
1777+ my $nfound;
1778+
1779+ vec(my $fdset = '', $fd, 1) = 1;
1780+
1781+ while () {
1782+ $nfound = ($type eq 'read')
1783+ ? select($fdset, undef, undef, $pending)
1784+ : select(undef, $fdset, undef, $pending) ;
1785+ if ($nfound == -1) {
1786+ $! == EINTR
1787+ or croak(qq/select(2): '$!'/);
1788+ redo if !$timeout || ($pending = $timeout - (time - $initial)) > 0;
1789+ $nfound = 0;
1790+ }
1791+ last;
1792+ }
1793+ $! = 0;
1794+ return $nfound;
1795+}
1796+
1797+sub can_read {
1798+ @_ == 1 || @_ == 2 || croak(q/Usage: $handle->can_read([timeout])/);
1799+ my $self = shift;
1800+ return $self->_do_timeout('read', @_)
1801+}
1802+
1803+sub can_write {
1804+ @_ == 1 || @_ == 2 || croak(q/Usage: $handle->can_write([timeout])/);
1805+ my $self = shift;
1806+ return $self->_do_timeout('write', @_)
1807+}
1808+
1809+1;
1810+}
1811+# ###########################################################################
1812+# End HTTPMicro package
1813+# ###########################################################################
1814+
1815+# ###########################################################################
1816+# Pingback package
1817+# This package is a copy without comments from the original. The original
1818+# with comments and its test file can be found in the Bazaar repository at,
1819+# lib/Pingback.pm
1820+# t/lib/Pingback.t
1821+# See https://launchpad.net/percona-toolkit for more information.
1822+# ###########################################################################
1823+{
1824+package Pingback;
1825+
1826+use strict;
1827+use warnings FATAL => 'all';
1828+use English qw(-no_match_vars);
1829+
1830+use constant PTDEBUG => $ENV{PTDEBUG} || 0;
1831+
1832+use File::Basename qw();
1833+use Data::Dumper qw();
1834+use Fcntl qw(:DEFAULT);
1835+
1836+use File::Spec;
1837+
1838+my $dir = File::Spec->tmpdir();
1839+my $check_time_file = File::Spec->catfile($dir,'percona-toolkit-version-check');
1840+my $check_time_limit = 60 * 60 * 24; # one day
1841+
1842+sub Dumper {
1843+ local $Data::Dumper::Indent = 1;
1844+ local $Data::Dumper::Sortkeys = 1;
1845+ local $Data::Dumper::Quotekeys = 0;
1846+
1847+ Data::Dumper::Dumper(@_);
1848+}
1849+
1850+local $EVAL_ERROR;
1851+eval {
1852+ require HTTPMicro;
1853+ require VersionCheck;
1854+};
1855+
1856+sub version_check {
1857+ eval {
1858+ if (exists $ENV{PERCONA_VERSION_CHECK} && !$ENV{PERCONA_VERSION_CHECK}) {
1859+ if ( $ENV{PTVCDEBUG} || PTDEBUG ) {
1860+ _d('--version-check is disabled by the PERCONA_VERSION_CHECK',
1861+ 'environment variable');
1862+ }
1863+ return;
1864+ }
1865+
1866+ if ( !time_to_check($check_time_file) ) {
1867+ if ( $ENV{PTVCDEBUG} || PTDEBUG ) {
1868+ _d('It is not time to --version-checka again;',
1869+ 'only 1 check per', $check_time_limit, 'seconds, and the last',
1870+ 'check was performed on the modified time of', $check_time_file);
1871+ }
1872+ return;
1873+ }
1874+
1875+ my $dbh = shift; # optional
1876+ my $advice = pingback(
1877+ url => $ENV{PERCONA_VERSION_CHECK_URL} || 'http://v.percona.com',
1878+ dbh => $dbh,
1879+ );
1880+ if ( $advice ) {
1881+ print "# Percona suggests these upgrades:\n";
1882+ print join("\n", map { "# * $_" } @$advice);
1883+ print "\n# Specify --no-version-check to disable these suggestions.\n\n";
1884+ }
1885+ elsif ( $ENV{PTVCDEBUG} || PTDEBUG ) {
1886+ _d('--version-check worked, but there were no suggestions');
1887+ }
1888+ };
1889+ if ( $EVAL_ERROR ) {
1890+ if ( $ENV{PTVCDEBUG} || PTDEBUG ) {
1891+ _d('Error doing --version-check:', $EVAL_ERROR);
1892+ }
1893+ }
1894+
1895+ return;
1896+}
1897+
1898+sub pingback {
1899+ my (%args) = @_;
1900+ my @required_args = qw(url);
1901+ foreach my $arg ( @required_args ) {
1902+ die "I need a $arg arugment" unless $args{$arg};
1903+ }
1904+ my ($url) = @args{@required_args};
1905+
1906+ my ($dbh, $ua, $vc) = @args{qw(dbh ua VersionCheck)};
1907+
1908+ $ua ||= HTTPMicro->new( timeout => 2 );
1909+ $vc ||= VersionCheck->new();
1910+
1911+ my $response = $ua->request('GET', $url);
1912+ PTDEBUG && _d('Server response:', Dumper($response));
1913+ die "No response from GET $url"
1914+ if !$response;
1915+ die "GET $url returned HTTP status $response->{status}; expected 200"
1916+ if $response->{status} != 200;
1917+ die "GET $url did not return any programs to check"
1918+ if !$response->{content};
1919+
1920+ my $items = $vc->parse_server_response(
1921+ response => $response->{content}
1922+ );
1923+ die "Failed to parse server requested programs: $response->{content}"
1924+ if !scalar keys %$items;
1925+
1926+ my $versions = $vc->get_versions(
1927+ items => $items,
1928+ dbh => $dbh,
1929+ );
1930+ die "Failed to get any program versions; should have at least gotten Perl"
1931+ if !scalar keys %$versions;
1932+
1933+ my $client_content = encode_client_response(
1934+ items => $items,
1935+ versions => $versions,
1936+ );
1937+
1938+ my $client_response = {
1939+ headers => { "X-Percona-Toolkit-Tool" => File::Basename::basename($0) },
1940+ content => $client_content,
1941+ };
1942+ PTDEBUG && _d('Client response:', Dumper($client_response));
1943+
1944+ $response = $ua->request('POST', $url, $client_response);
1945+ PTDEBUG && _d('Server suggestions:', Dumper($response));
1946+ die "No response from POST $url $client_response"
1947+ if !$response;
1948+ die "POST $url returned HTTP status $response->{status}; expected 200"
1949+ if $response->{status} != 200;
1950+
1951+ return unless $response->{content};
1952+
1953+ $items = $vc->parse_server_response(
1954+ response => $response->{content},
1955+ split_vars => 0,
1956+ );
1957+ die "Failed to parse server suggestions: $response->{content}"
1958+ if !scalar keys %$items;
1959+ my @suggestions = map { $_->{vars} }
1960+ sort { $a->{item} cmp $b->{item} }
1961+ values %$items;
1962+
1963+ return \@suggestions;
1964+}
1965+
1966+sub time_to_check {
1967+ my ($file) = @_;
1968+ die "I need a file argument" unless $file;
1969+
1970+ if ( !-f $file ) {
1971+ PTDEBUG && _d('Creating', $file);
1972+ _touch($file);
1973+ return 1;
1974+ }
1975+
1976+ my $mtime = (stat $file)[9];
1977+ if ( !defined $mtime ) {
1978+ PTDEBUG && _d('Error getting modified time of', $file);
1979+ return 0;
1980+ }
1981+
1982+ my $time = int(time());
1983+ PTDEBUG && _d('time=', $time, 'mtime=', $mtime);
1984+ if ( ($time - $mtime) > $check_time_limit ) {
1985+ _touch($file);
1986+ return 1;
1987+ }
1988+
1989+ return 0;
1990+}
1991+
1992+sub _touch {
1993+ my ($file) = @_;
1994+ sysopen my $fh, $file, O_WRONLY|O_CREAT|O_NONBLOCK
1995+ or die "Cannot create $file : $!";
1996+ close $fh or die "Cannot close $file : $!";
1997+ utime(undef, undef, $file);
1998+}
1999+
2000+sub encode_client_response {
2001+ my (%args) = @_;
2002+ my @required_args = qw(items versions);
2003+ foreach my $arg ( @required_args ) {
2004+ die "I need a $arg arugment" unless $args{$arg};
2005+ }
2006+ my ($items, $versions) = @args{@required_args};
2007+
2008+ my @lines;
2009+ foreach my $item ( sort keys %$items ) {
2010+ next unless exists $versions->{$item};
2011+ push @lines, join(';', $item, $versions->{$item});
2012+ }
2013+
2014+ my $client_response = join("\n", @lines) . "\n";
2015+ PTDEBUG && _d('Client response:', $client_response);
2016+ return $client_response;
2017+}
2018+
2019+sub _d {
2020+ my ($package, undef, $line) = caller 0;
2021+ @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
2022+ map { defined $_ ? $_ : 'undef' }
2023+ @_;
2024+ print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
2025+}
2026+
2027+1;
2028+}
2029+# ###########################################################################
2030+# End Pingback package
2031+# ###########################################################################
2032+
2033+# ###########################################################################
2034 # This is a combination of modules and programs in one -- a runnable module.
2035 # http://www.perl.com/pub/a/2006/07/13/lightning-articles.html?page=last
2036 # Or, look it up in the Camel book on pages 642 and 643 in the 3rd edition.
2037@@ -2917,10 +3887,11 @@
2038 $Data::Dumper::Sortkeys = 1;
2039 $Data::Dumper::Quotekeys = 0;
2040
2041+use Percona::Toolkit;
2042 use constant PTDEBUG => $ENV{PTDEBUG} || 0;
2043
2044 sub main {
2045- @ARGV = @_; # set global ARGV for this package
2046+ local @ARGV = @_; # set global ARGV for this package
2047
2048 # ########################################################################
2049 # Get configuration information.
2050@@ -3004,6 +3975,13 @@
2051 $daemon->make_PID_file();
2052 }
2053
2054+ # ########################################################################
2055+ # Do the version-check
2056+ # ########################################################################
2057+ if ( $o->get('version-check') && ($o->has('quiet') && !$o->get('quiet')) ) {
2058+ Pingback::version_check($configs[0]->{dbh});
2059+ }
2060+
2061 # #########################################################################
2062 # Diff the given configs.
2063 # #########################################################################
2064@@ -3282,6 +4260,24 @@
2065
2066 Show version and exit.
2067
2068+=item --[no]version-check
2069+
2070+default: yes
2071+
2072+Send program versions to Percona and print suggested upgrades and problems.
2073+
2074+The version check feature causes the tool to send and receive data from
2075+Percona over the web. The data contains program versions from the local
2076+machine. Percona uses the data to focus development on the most widely
2077+used versions of programs, and to suggest to customers possible upgrades
2078+and known bad versions of programs.
2079+
2080+This feature can be disabled by specifying C<--no-version-check> on the
2081+command line or in one of several L<"--config"> files, or by setting the
2082+environment variable C<PERCONA_VERSION_CHECK=0>.
2083+
2084+For more information, visit L<http://www.percona.com/version-check>.
2085+
2086 =back
2087
2088 =head1 DSN OPTIONS
2089
2090=== modified file 'bin/pt-deadlock-logger'
2091--- bin/pt-deadlock-logger 2012-08-03 18:41:41 +0000
2092+++ bin/pt-deadlock-logger 2012-08-23 01:57:20 +0000
2093@@ -6,7 +6,42 @@
2094
2095 use strict;
2096 use warnings FATAL => 'all';
2097-use constant PTDEBUG => $ENV{PTDEBUG} || 0;
2098+
2099+# This tool is "fat-packed": most of its dependent modules are embedded
2100+# in this file. Setting %INC to this file for each module makes Perl aware
2101+# of this so it will not try to load the module from @INC. See the tool's
2102+# documentation for a full list of dependencies.
2103+BEGIN {
2104+ $INC{$_} = __FILE__ for map { (my $pkg = "$_.pm") =~ s!::!/!g; $pkg } (qw(
2105+ Percona::Toolkit
2106+ OptionParser
2107+ Mo
2108+ VersionParser
2109+ Quoter
2110+ DSNParser
2111+ Daemon
2112+ VersionCheck
2113+ HTTPMicro
2114+ Pingback
2115+ ));
2116+}
2117+
2118+# ###########################################################################
2119+# Percona::Toolkit package
2120+# This package is a copy without comments from the original. The original
2121+# with comments and its test file can be found in the Bazaar repository at,
2122+# lib/Percona/Toolkit.pm
2123+# t/lib/Percona/Toolkit.t
2124+# See https://launchpad.net/percona-toolkit for more information.
2125+# ###########################################################################
2126+{
2127+package Percona::Toolkit;
2128+our $VERSION = '2.1.3';
2129+1;
2130+}
2131+# ###########################################################################
2132+# End Percona::Toolkit package
2133+# ###########################################################################
2134
2135 # ###########################################################################
2136 # OptionParser package
2137@@ -2359,6 +2394,939 @@
2138 # ###########################################################################
2139
2140 # ###########################################################################
2141+# VersionCheck package
2142+# This package is a copy without comments from the original. The original
2143+# with comments and its test file can be found in the Bazaar repository at,
2144+# lib/VersionCheck.pm
2145+# t/lib/VersionCheck.t
2146+# See https://launchpad.net/percona-toolkit for more information.
2147+# ###########################################################################
2148+{
2149+package VersionCheck;
2150+
2151+use strict;
2152+use warnings FATAL => 'all';
2153+use English qw(-no_match_vars);
2154+
2155+use constant PTDEBUG => $ENV{PTDEBUG} || 0;
2156+
2157+use File::Basename ();
2158+use Data::Dumper ();
2159+
2160+sub Dumper {
2161+ local $Data::Dumper::Indent = 1;
2162+ local $Data::Dumper::Sortkeys = 1;
2163+ local $Data::Dumper::Quotekeys = 0;
2164+
2165+ Data::Dumper::Dumper(@_);
2166+}
2167+
2168+sub new {
2169+ my ($class, %args) = @_;
2170+ my $self = {
2171+ valid_types => qr/
2172+ ^(?:
2173+ os_version
2174+ |perl_version
2175+ |perl_module_version
2176+ |mysql_variable
2177+ |bin_version
2178+ )$/x,
2179+ };
2180+ return bless $self, $class;
2181+}
2182+
2183+sub parse_server_response {
2184+ my ($self, %args) = @_;
2185+ my @required_args = qw(response);
2186+ foreach my $arg ( @required_args ) {
2187+ die "I need a $arg arugment" unless $args{$arg};
2188+ }
2189+ my ($response) = @args{@required_args};
2190+
2191+ my %items = map {
2192+ my ($item, $type, $vars) = split(";", $_);
2193+ if ( !defined $args{split_vars} || $args{split_vars} ) {
2194+ $vars = [ split(",", ($vars || '')) ];
2195+ }
2196+ $item => {
2197+ item => $item,
2198+ type => $type,
2199+ vars => $vars,
2200+ };
2201+ } split("\n", $response);
2202+
2203+ PTDEBUG && _d('Items:', Dumper(\%items));
2204+
2205+ return \%items;
2206+}
2207+
2208+sub get_versions {
2209+ my ($self, %args) = @_;
2210+ my @required_args = qw(items);
2211+ foreach my $arg ( @required_args ) {
2212+ die "I need a $arg arugment" unless $args{$arg};
2213+ }
2214+ my ($items) = @args{@required_args};
2215+ my $dbh = $args{dbh}; # optional
2216+
2217+ my %versions;
2218+ foreach my $item ( values %$items ) {
2219+ next unless $self->valid_item($item);
2220+
2221+ eval {
2222+ my $func = 'get_' . $item->{type};
2223+ my $version = $self->$func(
2224+ item => $item,
2225+ dbh => $dbh,
2226+ );
2227+ if ( $version ) {
2228+ chomp $version;
2229+ $versions{$item->{item}} = $version;
2230+ }
2231+ };
2232+ if ( $EVAL_ERROR ) {
2233+ PTDEBUG && _d('Error getting version for', Dumper($item), $EVAL_ERROR);
2234+ }
2235+ }
2236+
2237+ return \%versions;
2238+}
2239+
2240+sub valid_item {
2241+ my ($self, $item) = @_;
2242+ return unless $item;
2243+
2244+ if ( ($item->{type} || '') !~ m/$self->{valid_types}/ ) {
2245+ PTDEBUG && _d('Invalid type:', $item->{type});
2246+ return;
2247+ }
2248+
2249+ return 1;
2250+}
2251+
2252+sub get_os_version {
2253+ my ($self) = @_;
2254+
2255+ chomp(my $platform = `uname -s`);
2256+ PTDEBUG && _d('platform:', $platform);
2257+ return $OSNAME unless $platform;
2258+
2259+ chomp(my $lsb_release
2260+ = `which lsb_release 2>/dev/null | awk '{print \$1}'` || '');
2261+ PTDEBUG && _d('lsb_release:', $lsb_release);
2262+
2263+ my $release = "";
2264+
2265+ if ( $platform eq 'Linux' ) {
2266+ if ( -f "/etc/fedora-release" ) {
2267+ $release = `cat /etc/fedora-release`;
2268+ }
2269+ elsif ( -f "/etc/redhat-release" ) {
2270+ $release = `cat /etc/redhat-release`;
2271+ }
2272+ elsif ( -f "/etc/system-release" ) {
2273+ $release = `cat /etc/system-release`;
2274+ }
2275+ elsif ( $lsb_release ) {
2276+ $release = `$lsb_release -ds`;
2277+ }
2278+ elsif ( -f "/etc/lsb-release" ) {
2279+ $release = `grep DISTRIB_DESCRIPTION /etc/lsb-release`;
2280+ $release =~ s/^\w+="([^"]+)".+/$1/;
2281+ }
2282+ elsif ( -f "/etc/debian_version" ) {
2283+ chomp(my $rel = `cat /etc/debian_version`);
2284+ $release = "Debian $rel";
2285+ if ( -f "/etc/apt/sources.list" ) {
2286+ chomp(my $code_name = `awk '/^deb/ {print \$3}' /etc/apt/sources.list | awk -F/ '{print \$1}'| awk 'BEGIN {FS="|"} {print \$1}' | sort | uniq -c | sort -rn | head -n1 | awk '{print \$2}'`);
2287+ $release .= " ($code_name)" if $code_name;
2288+ }
2289+ }
2290+ elsif ( `ls /etc/*release 2>/dev/null` ) {
2291+ if ( `grep DISTRIB_DESCRIPTION /etc/*release 2>/dev/null` ) {
2292+ $release = `grep DISTRIB_DESCRIPTION /etc/*release | head -n1`;
2293+ }
2294+ else {
2295+ $release = `cat /etc/*release | head -n1`;
2296+ }
2297+ }
2298+ }
2299+ elsif ( $platform =~ m/^(BSD|Darwin)$/ ) {
2300+ my $rel = `uname -r`;
2301+ $release = "$platform $rel";
2302+ }
2303+ elsif ( $platform eq "SunOS" ) {
2304+ my $rel = `head -n1 /etc/release` || `uname -r`;
2305+ $release = "$platform $rel";
2306+ }
2307+
2308+ if ( !$release ) {
2309+ PTDEBUG && _d('Failed to get the release, using platform');
2310+ $release = $platform;
2311+ }
2312+ chomp($release);
2313+
2314+ PTDEBUG && _d('OS version =', $release);
2315+ return $release;
2316+}
2317+
2318+sub get_perl_version {
2319+ my ($self, %args) = @_;
2320+ my $item = $args{item};
2321+ return unless $item;
2322+
2323+ my $version = sprintf '%vd', $PERL_VERSION;
2324+ PTDEBUG && _d('Perl version', $version);
2325+ return $version;
2326+}
2327+
2328+sub get_perl_module_version {
2329+ my ($self, %args) = @_;
2330+ my $item = $args{item};
2331+ return unless $item;
2332+
2333+ my $var = $item->{item} . '::VERSION';
2334+ my $version = _get_scalar($var);
2335+ PTDEBUG && _d('Perl version for', $var, '=', "$version");
2336+
2337+ return $version ? "$version" : $version;
2338+}
2339+
2340+sub _get_scalar {
2341+ no strict;
2342+ return ${*{shift()}};
2343+}
2344+
2345+sub get_mysql_variable {
2346+ my $self = shift;
2347+ return $self->_get_from_mysql(
2348+ show => 'VARIABLES',
2349+ @_,
2350+ );
2351+}
2352+
2353+
2354+sub _get_from_mysql {
2355+ my ($self, %args) = @_;
2356+ my $show = $args{show};
2357+ my $item = $args{item};
2358+ my $dbh = $args{dbh};
2359+ return unless $show && $item && $dbh;
2360+
2361+ local $dbh->{FetchHashKeyName} = 'NAME_lc';
2362+ my $sql = qq/SHOW $show/;
2363+ PTDEBUG && _d($sql);
2364+ my $rows = $dbh->selectall_hashref($sql, 'variable_name');
2365+
2366+ my @versions;
2367+ foreach my $var ( @{$item->{vars}} ) {
2368+ $var = lc($var);
2369+ my $version = $rows->{$var}->{value};
2370+ PTDEBUG && _d('MySQL version for', $item->{item}, '=', $version);
2371+ push @versions, $version;
2372+ }
2373+
2374+ return join(' ', @versions);
2375+}
2376+
2377+sub get_bin_version {
2378+ my ($self, %args) = @_;
2379+ my $item = $args{item};
2380+ my $cmd = $item->{item};
2381+ return unless $cmd;
2382+
2383+ my $sanitized_command = File::Basename::basename($cmd);
2384+ PTDEBUG && _d('cmd:', $cmd, 'sanitized:', $sanitized_command);
2385+ return if $sanitized_command !~ /\A[a-zA-Z0-9_-]+\z/;
2386+
2387+ my $output = `$sanitized_command --version 2>&1`;
2388+ PTDEBUG && _d('output:', $output);
2389+
2390+ my ($version) = $output =~ /v?([0-9]+\.[0-9]+(?:\.[\w-]+)?)/;
2391+
2392+ PTDEBUG && _d('Version for', $sanitized_command, '=', $version);
2393+ return $version;
2394+}
2395+
2396+sub _d {
2397+ my ($package, undef, $line) = caller 0;
2398+ @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
2399+ map { defined $_ ? $_ : 'undef' }
2400+ @_;
2401+ print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
2402+}
2403+
2404+1;
2405+}
2406+# ###########################################################################
2407+# End VersionCheck package
2408+# ###########################################################################
2409+
2410+# ###########################################################################
2411+# HTTPMicro package
2412+# This package is a copy without comments from the original. The original
2413+# with comments and its test file can be found in the Bazaar repository at,
2414+# lib/HTTPMicro.pm
2415+# t/lib/HTTPMicro.t
2416+# See https://launchpad.net/percona-toolkit for more information.
2417+# ###########################################################################
2418+{
2419+
2420+package HTTPMicro;
2421+BEGIN {
2422+ $HTTPMicro::VERSION = '0.001';
2423+}
2424+use strict;
2425+use warnings;
2426+
2427+use Carp ();
2428+
2429+
2430+my @attributes;
2431+BEGIN {
2432+ @attributes = qw(agent timeout);
2433+ no strict 'refs';
2434+ for my $accessor ( @attributes ) {
2435+ *{$accessor} = sub {
2436+ @_ > 1 ? $_[0]->{$accessor} = $_[1] : $_[0]->{$accessor};
2437+ };
2438+ }
2439+}
2440+
2441+sub new {
2442+ my($class, %args) = @_;
2443+ (my $agent = $class) =~ s{::}{-}g;
2444+ my $self = {
2445+ agent => $agent . "/" . ($class->VERSION || 0),
2446+ timeout => 60,
2447+ };
2448+ for my $key ( @attributes ) {
2449+ $self->{$key} = $args{$key} if exists $args{$key}
2450+ }
2451+ return bless $self, $class;
2452+}
2453+
2454+sub request {
2455+ my ($self, $method, $url, $args) = @_;
2456+ @_ == 3 || (@_ == 4 && ref $args eq 'HASH')
2457+ or Carp::croak(q/Usage: $http->request(METHOD, URL, [HASHREF])/);
2458+ $args ||= {}; # we keep some state in this during _request
2459+
2460+ my $response;
2461+ for ( 0 .. 1 ) {
2462+ $response = eval { $self->_request($method, $url, $args) };
2463+ last unless $@ && $method eq 'GET'
2464+ && $@ =~ m{^(?:Socket closed|Unexpected end)};
2465+ }
2466+
2467+ if (my $e = "$@") {
2468+ $response = {
2469+ success => q{},
2470+ status => 599,
2471+ reason => 'Internal Exception',
2472+ content => $e,
2473+ headers => {
2474+ 'content-type' => 'text/plain',
2475+ 'content-length' => length $e,
2476+ }
2477+ };
2478+ }
2479+ return $response;
2480+}
2481+
2482+sub _request {
2483+ my ($self, $method, $url, $args) = @_;
2484+
2485+ my ($scheme, $host, $port, $path_query) = $self->_split_url($url);
2486+
2487+ my $request = {
2488+ method => $method,
2489+ scheme => $scheme,
2490+ host_port => ($port == 80 ? $host : "$host:$port"),
2491+ uri => $path_query,
2492+ headers => {},
2493+ };
2494+
2495+ my $handle = HTTPMicro::Handle->new(timeout => $self->{timeout});
2496+
2497+ $handle->connect($scheme, $host, $port);
2498+
2499+ $self->_prepare_headers_and_cb($request, $args);
2500+ $handle->write_request_header(@{$request}{qw/method uri headers/});
2501+ $handle->write_content_body($request) if $request->{content};
2502+
2503+ my $response;
2504+ do { $response = $handle->read_response_header }
2505+ until (substr($response->{status},0,1) ne '1');
2506+
2507+ if (!($method eq 'HEAD' || $response->{status} =~ /^[23]04/)) {
2508+ $response->{content} = '';
2509+ $handle->read_content_body(sub { $_[1]->{content} .= $_[0] }, $response);
2510+ }
2511+
2512+ $handle->close;
2513+ $response->{success} = substr($response->{status},0,1) eq '2';
2514+ return $response;
2515+}
2516+
2517+sub _prepare_headers_and_cb {
2518+ my ($self, $request, $args) = @_;
2519+
2520+ for ($args->{headers}) {
2521+ next unless defined;
2522+ while (my ($k, $v) = each %$_) {
2523+ $request->{headers}{lc $k} = $v;
2524+ }
2525+ }
2526+ $request->{headers}{'host'} = $request->{host_port};
2527+ $request->{headers}{'connection'} = "close";
2528+ $request->{headers}{'user-agent'} ||= $self->{agent};
2529+
2530+ if (defined $args->{content}) {
2531+ $request->{headers}{'content-type'} ||= "application/octet-stream";
2532+ utf8::downgrade($args->{content}, 1)
2533+ or Carp::croak(q/Wide character in request message body/);
2534+ $request->{headers}{'content-length'} = length $args->{content};
2535+ $request->{content} = $args->{content};
2536+ }
2537+ return;
2538+}
2539+
2540+sub _split_url {
2541+ my $url = pop;
2542+
2543+ my ($scheme, $authority, $path_query) = $url =~ m<\A([^:/?#]+)://([^/?#]*)([^#]*)>
2544+ or Carp::croak(qq/Cannot parse URL: '$url'/);
2545+
2546+ $scheme = lc $scheme;
2547+ $path_query = "/$path_query" unless $path_query =~ m<\A/>;
2548+
2549+ my $host = (length($authority)) ? lc $authority : 'localhost';
2550+ $host =~ s/\A[^@]*@//; # userinfo
2551+ my $port = do {
2552+ $host =~ s/:([0-9]*)\z// && length $1
2553+ ? $1
2554+ : ($scheme eq 'http' ? 80 : undef);
2555+ };
2556+
2557+ return ($scheme, $host, $port, $path_query);
2558+}
2559+
2560+package
2561+ HTTPMicro::Handle; # hide from PAUSE/indexers
2562+use strict;
2563+use warnings;
2564+
2565+use Carp qw[croak];
2566+use Errno qw[EINTR EPIPE];
2567+use IO::Socket qw[SOCK_STREAM];
2568+
2569+sub BUFSIZE () { 32768 }
2570+
2571+my $Printable = sub {
2572+ local $_ = shift;
2573+ s/\r/\\r/g;
2574+ s/\n/\\n/g;
2575+ s/\t/\\t/g;
2576+ s/([^\x20-\x7E])/sprintf('\\x%.2X', ord($1))/ge;
2577+ $_;
2578+};
2579+
2580+sub new {
2581+ my ($class, %args) = @_;
2582+ return bless {
2583+ rbuf => '',
2584+ timeout => 60,
2585+ max_line_size => 16384,
2586+ %args
2587+ }, $class;
2588+}
2589+
2590+sub connect {
2591+ @_ == 4 || croak(q/Usage: $handle->connect(scheme, host, port)/);
2592+ my ($self, $scheme, $host, $port) = @_;
2593+
2594+ if ( $scheme ne 'http' ) {
2595+ croak(qq/Unsupported URL scheme '$scheme'/);
2596+ }
2597+
2598+ $self->{fh} = 'IO::Socket::INET'->new(
2599+ PeerHost => $host,
2600+ PeerPort => $port,
2601+ Proto => 'tcp',
2602+ Type => SOCK_STREAM,
2603+ Timeout => $self->{timeout}
2604+ ) or croak(qq/Could not connect to '$host:$port': $@/);
2605+
2606+ binmode($self->{fh})
2607+ or croak(qq/Could not binmode() socket: '$!'/);
2608+
2609+ $self->{host} = $host;
2610+ $self->{port} = $port;
2611+
2612+ return $self;
2613+}
2614+
2615+sub close {
2616+ @_ == 1 || croak(q/Usage: $handle->close()/);
2617+ my ($self) = @_;
2618+ CORE::close($self->{fh})
2619+ or croak(qq/Could not close socket: '$!'/);
2620+}
2621+
2622+sub write {
2623+ @_ == 2 || croak(q/Usage: $handle->write(buf)/);
2624+ my ($self, $buf) = @_;
2625+
2626+ my $len = length $buf;
2627+ my $off = 0;
2628+
2629+ local $SIG{PIPE} = 'IGNORE';
2630+
2631+ while () {
2632+ $self->can_write
2633+ or croak(q/Timed out while waiting for socket to become ready for writing/);
2634+ my $r = syswrite($self->{fh}, $buf, $len, $off);
2635+ if (defined $r) {
2636+ $len -= $r;
2637+ $off += $r;
2638+ last unless $len > 0;
2639+ }
2640+ elsif ($! == EPIPE) {
2641+ croak(qq/Socket closed by remote server: $!/);
2642+ }
2643+ elsif ($! != EINTR) {
2644+ croak(qq/Could not write to socket: '$!'/);
2645+ }
2646+ }
2647+ return $off;
2648+}
2649+
2650+sub read {
2651+ @_ == 2 || @_ == 3 || croak(q/Usage: $handle->read(len)/);
2652+ my ($self, $len) = @_;
2653+
2654+ my $buf = '';
2655+ my $got = length $self->{rbuf};
2656+
2657+ if ($got) {
2658+ my $take = ($got < $len) ? $got : $len;
2659+ $buf = substr($self->{rbuf}, 0, $take, '');
2660+ $len -= $take;
2661+ }
2662+
2663+ while ($len > 0) {
2664+ $self->can_read
2665+ or croak(q/Timed out while waiting for socket to become ready for reading/);
2666+ my $r = sysread($self->{fh}, $buf, $len, length $buf);
2667+ if (defined $r) {
2668+ last unless $r;
2669+ $len -= $r;
2670+ }
2671+ elsif ($! != EINTR) {
2672+ croak(qq/Could not read from socket: '$!'/);
2673+ }
2674+ }
2675+ if ($len) {
2676+ croak(q/Unexpected end of stream/);
2677+ }
2678+ return $buf;
2679+}
2680+
2681+sub readline {
2682+ @_ == 1 || croak(q/Usage: $handle->readline()/);
2683+ my ($self) = @_;
2684+
2685+ while () {
2686+ if ($self->{rbuf} =~ s/\A ([^\x0D\x0A]* \x0D?\x0A)//x) {
2687+ return $1;
2688+ }
2689+ $self->can_read
2690+ or croak(q/Timed out while waiting for socket to become ready for reading/);
2691+ my $r = sysread($self->{fh}, $self->{rbuf}, BUFSIZE, length $self->{rbuf});
2692+ if (defined $r) {
2693+ last unless $r;
2694+ }
2695+ elsif ($! != EINTR) {
2696+ croak(qq/Could not read from socket: '$!'/);
2697+ }
2698+ }
2699+ croak(q/Unexpected end of stream while looking for line/);
2700+}
2701+
2702+sub read_header_lines {
2703+ @_ == 1 || @_ == 2 || croak(q/Usage: $handle->read_header_lines([headers])/);
2704+ my ($self, $headers) = @_;
2705+ $headers ||= {};
2706+ my $lines = 0;
2707+ my $val;
2708+
2709+ while () {
2710+ my $line = $self->readline;
2711+
2712+ if ($line =~ /\A ([^\x00-\x1F\x7F:]+) : [\x09\x20]* ([^\x0D\x0A]*)/x) {
2713+ my ($field_name) = lc $1;
2714+ $val = \($headers->{$field_name} = $2);
2715+ }
2716+ elsif ($line =~ /\A [\x09\x20]+ ([^\x0D\x0A]*)/x) {
2717+ $val
2718+ or croak(q/Unexpected header continuation line/);
2719+ next unless length $1;
2720+ $$val .= ' ' if length $$val;
2721+ $$val .= $1;
2722+ }
2723+ elsif ($line =~ /\A \x0D?\x0A \z/x) {
2724+ last;
2725+ }
2726+ else {
2727+ croak(q/Malformed header line: / . $Printable->($line));
2728+ }
2729+ }
2730+ return $headers;
2731+}
2732+
2733+sub write_header_lines {
2734+ (@_ == 2 && ref $_[1] eq 'HASH') || croak(q/Usage: $handle->write_header_lines(headers)/);
2735+ my($self, $headers) = @_;
2736+
2737+ my $buf = '';
2738+ while (my ($k, $v) = each %$headers) {
2739+ my $field_name = lc $k;
2740+ $field_name =~ /\A [\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7A\x7C\x7E]+ \z/x
2741+ or croak(q/Invalid HTTP header field name: / . $Printable->($field_name));
2742+ $field_name =~ s/\b(\w)/\u$1/g;
2743+ $buf .= "$field_name: $v\x0D\x0A";
2744+ }
2745+ $buf .= "\x0D\x0A";
2746+ return $self->write($buf);
2747+}
2748+
2749+sub read_content_body {
2750+ @_ == 3 || @_ == 4 || croak(q/Usage: $handle->read_content_body(callback, response, [read_length])/);
2751+ my ($self, $cb, $response, $len) = @_;
2752+ $len ||= $response->{headers}{'content-length'};
2753+
2754+ croak("No content-length in the returned response, and this "
2755+ . "UA doesn't implement chunking") unless defined $len;
2756+
2757+ while ($len > 0) {
2758+ my $read = ($len > BUFSIZE) ? BUFSIZE : $len;
2759+ $cb->($self->read($read), $response);
2760+ $len -= $read;
2761+ }
2762+
2763+ return;
2764+}
2765+
2766+sub write_content_body {
2767+ @_ == 2 || croak(q/Usage: $handle->write_content_body(request)/);
2768+ my ($self, $request) = @_;
2769+ my ($len, $content_length) = (0, $request->{headers}{'content-length'});
2770+
2771+ $len += $self->write($request->{content});
2772+
2773+ $len == $content_length
2774+ or croak(qq/Content-Length missmatch (got: $len expected: $content_length)/);
2775+
2776+ return $len;
2777+}
2778+
2779+sub read_response_header {
2780+ @_ == 1 || croak(q/Usage: $handle->read_response_header()/);
2781+ my ($self) = @_;
2782+
2783+ my $line = $self->readline;
2784+
2785+ $line =~ /\A (HTTP\/(0*\d+\.0*\d+)) [\x09\x20]+ ([0-9]{3}) [\x09\x20]+ ([^\x0D\x0A]*) \x0D?\x0A/x
2786+ or croak(q/Malformed Status-Line: / . $Printable->($line));
2787+
2788+ my ($protocol, $version, $status, $reason) = ($1, $2, $3, $4);
2789+
2790+ return {
2791+ status => $status,
2792+ reason => $reason,
2793+ headers => $self->read_header_lines,
2794+ protocol => $protocol,
2795+ };
2796+}
2797+
2798+sub write_request_header {
2799+ @_ == 4 || croak(q/Usage: $handle->write_request_header(method, request_uri, headers)/);
2800+ my ($self, $method, $request_uri, $headers) = @_;
2801+
2802+ return $self->write("$method $request_uri HTTP/1.1\x0D\x0A")
2803+ + $self->write_header_lines($headers);
2804+}
2805+
2806+sub _do_timeout {
2807+ my ($self, $type, $timeout) = @_;
2808+ $timeout = $self->{timeout}
2809+ unless defined $timeout && $timeout >= 0;
2810+
2811+ my $fd = fileno $self->{fh};
2812+ defined $fd && $fd >= 0
2813+ or croak(q/select(2): 'Bad file descriptor'/);
2814+
2815+ my $initial = time;
2816+ my $pending = $timeout;
2817+ my $nfound;
2818+
2819+ vec(my $fdset = '', $fd, 1) = 1;
2820+
2821+ while () {
2822+ $nfound = ($type eq 'read')
2823+ ? select($fdset, undef, undef, $pending)
2824+ : select(undef, $fdset, undef, $pending) ;
2825+ if ($nfound == -1) {
2826+ $! == EINTR
2827+ or croak(qq/select(2): '$!'/);
2828+ redo if !$timeout || ($pending = $timeout - (time - $initial)) > 0;
2829+ $nfound = 0;
2830+ }
2831+ last;
2832+ }
2833+ $! = 0;
2834+ return $nfound;
2835+}
2836+
2837+sub can_read {
2838+ @_ == 1 || @_ == 2 || croak(q/Usage: $handle->can_read([timeout])/);
2839+ my $self = shift;
2840+ return $self->_do_timeout('read', @_)
2841+}
2842+
2843+sub can_write {
2844+ @_ == 1 || @_ == 2 || croak(q/Usage: $handle->can_write([timeout])/);
2845+ my $self = shift;
2846+ return $self->_do_timeout('write', @_)
2847+}
2848+
2849+1;
2850+}
2851+# ###########################################################################
2852+# End HTTPMicro package
2853+# ###########################################################################
2854+
2855+# ###########################################################################
2856+# Pingback package
2857+# This package is a copy without comments from the original. The original
2858+# with comments and its test file can be found in the Bazaar repository at,
2859+# lib/Pingback.pm
2860+# t/lib/Pingback.t
2861+# See https://launchpad.net/percona-toolkit for more information.
2862+# ###########################################################################
2863+{
2864+package Pingback;
2865+
2866+use strict;
2867+use warnings FATAL => 'all';
2868+use English qw(-no_match_vars);
2869+
2870+use constant PTDEBUG => $ENV{PTDEBUG} || 0;
2871+
2872+use File::Basename qw();
2873+use Data::Dumper qw();
2874+use Fcntl qw(:DEFAULT);
2875+
2876+use File::Spec;
2877+
2878+my $dir = File::Spec->tmpdir();
2879+my $check_time_file = File::Spec->catfile($dir,'percona-toolkit-version-check');
2880+my $check_time_limit = 60 * 60 * 24; # one day
2881+
2882+sub Dumper {
2883+ local $Data::Dumper::Indent = 1;
2884+ local $Data::Dumper::Sortkeys = 1;
2885+ local $Data::Dumper::Quotekeys = 0;
2886+
2887+ Data::Dumper::Dumper(@_);
2888+}
2889+
2890+local $EVAL_ERROR;
2891+eval {
2892+ require HTTPMicro;
2893+ require VersionCheck;
2894+};
2895+
2896+sub version_check {
2897+ eval {
2898+ if (exists $ENV{PERCONA_VERSION_CHECK} && !$ENV{PERCONA_VERSION_CHECK}) {
2899+ if ( $ENV{PTVCDEBUG} || PTDEBUG ) {
2900+ _d('--version-check is disabled by the PERCONA_VERSION_CHECK',
2901+ 'environment variable');
2902+ }
2903+ return;
2904+ }
2905+
2906+ if ( !time_to_check($check_time_file) ) {
2907+ if ( $ENV{PTVCDEBUG} || PTDEBUG ) {
2908+ _d('It is not time to --version-checka again;',
2909+ 'only 1 check per', $check_time_limit, 'seconds, and the last',
2910+ 'check was performed on the modified time of', $check_time_file);
2911+ }
2912+ return;
2913+ }
2914+
2915+ my $dbh = shift; # optional
2916+ my $advice = pingback(
2917+ url => $ENV{PERCONA_VERSION_CHECK_URL} || 'http://v.percona.com',
2918+ dbh => $dbh,
2919+ );
2920+ if ( $advice ) {
2921+ print "# Percona suggests these upgrades:\n";
2922+ print join("\n", map { "# * $_" } @$advice);
2923+ print "\n# Specify --no-version-check to disable these suggestions.\n\n";
2924+ }
2925+ elsif ( $ENV{PTVCDEBUG} || PTDEBUG ) {
2926+ _d('--version-check worked, but there were no suggestions');
2927+ }
2928+ };
2929+ if ( $EVAL_ERROR ) {
2930+ if ( $ENV{PTVCDEBUG} || PTDEBUG ) {
2931+ _d('Error doing --version-check:', $EVAL_ERROR);
2932+ }
2933+ }
2934+
2935+ return;
2936+}
2937+
2938+sub pingback {
2939+ my (%args) = @_;
2940+ my @required_args = qw(url);
2941+ foreach my $arg ( @required_args ) {
2942+ die "I need a $arg arugment" unless $args{$arg};
2943+ }
2944+ my ($url) = @args{@required_args};
2945+
2946+ my ($dbh, $ua, $vc) = @args{qw(dbh ua VersionCheck)};
2947+
2948+ $ua ||= HTTPMicro->new( timeout => 2 );
2949+ $vc ||= VersionCheck->new();
2950+
2951+ my $response = $ua->request('GET', $url);
2952+ PTDEBUG && _d('Server response:', Dumper($response));
2953+ die "No response from GET $url"
2954+ if !$response;
2955+ die "GET $url returned HTTP status $response->{status}; expected 200"
2956+ if $response->{status} != 200;
2957+ die "GET $url did not return any programs to check"
2958+ if !$response->{content};
2959+
2960+ my $items = $vc->parse_server_response(
2961+ response => $response->{content}
2962+ );
2963+ die "Failed to parse server requested programs: $response->{content}"
2964+ if !scalar keys %$items;
2965+
2966+ my $versions = $vc->get_versions(
2967+ items => $items,
2968+ dbh => $dbh,
2969+ );
2970+ die "Failed to get any program versions; should have at least gotten Perl"
2971+ if !scalar keys %$versions;
2972+
2973+ my $client_content = encode_client_response(
2974+ items => $items,
2975+ versions => $versions,
2976+ );
2977+
2978+ my $client_response = {
2979+ headers => { "X-Percona-Toolkit-Tool" => File::Basename::basename($0) },
2980+ content => $client_content,
2981+ };
2982+ PTDEBUG && _d('Client response:', Dumper($client_response));
2983+
2984+ $response = $ua->request('POST', $url, $client_response);
2985+ PTDEBUG && _d('Server suggestions:', Dumper($response));
2986+ die "No response from POST $url $client_response"
2987+ if !$response;
2988+ die "POST $url returned HTTP status $response->{status}; expected 200"
2989+ if $response->{status} != 200;
2990+
2991+ return unless $response->{content};
2992+
2993+ $items = $vc->parse_server_response(
2994+ response => $response->{content},
2995+ split_vars => 0,
2996+ );
2997+ die "Failed to parse server suggestions: $response->{content}"
2998+ if !scalar keys %$items;
2999+ my @suggestions = map { $_->{vars} }
3000+ sort { $a->{item} cmp $b->{item} }
3001+ values %$items;
3002+
3003+ return \@suggestions;
3004+}
3005+
3006+sub time_to_check {
3007+ my ($file) = @_;
3008+ die "I need a file argument" unless $file;
3009+
3010+ if ( !-f $file ) {
3011+ PTDEBUG && _d('Creating', $file);
3012+ _touch($file);
3013+ return 1;
3014+ }
3015+
3016+ my $mtime = (stat $file)[9];
3017+ if ( !defined $mtime ) {
3018+ PTDEBUG && _d('Error getting modified time of', $file);
3019+ return 0;
3020+ }
3021+
3022+ my $time = int(time());
3023+ PTDEBUG && _d('time=', $time, 'mtime=', $mtime);
3024+ if ( ($time - $mtime) > $check_time_limit ) {
3025+ _touch($file);
3026+ return 1;
3027+ }
3028+
3029+ return 0;
3030+}
3031+
3032+sub _touch {
3033+ my ($file) = @_;
3034+ sysopen my $fh, $file, O_WRONLY|O_CREAT|O_NONBLOCK
3035+ or die "Cannot create $file : $!";
3036+ close $fh or die "Cannot close $file : $!";
3037+ utime(undef, undef, $file);
3038+}
3039+
3040+sub encode_client_response {
3041+ my (%args) = @_;
3042+ my @required_args = qw(items versions);
3043+ foreach my $arg ( @required_args ) {
3044+ die "I need a $arg arugment" unless $args{$arg};
3045+ }
3046+ my ($items, $versions) = @args{@required_args};
3047+
3048+ my @lines;
3049+ foreach my $item ( sort keys %$items ) {
3050+ next unless exists $versions->{$item};
3051+ push @lines, join(';', $item, $versions->{$item});
3052+ }
3053+
3054+ my $client_response = join("\n", @lines) . "\n";
3055+ PTDEBUG && _d('Client response:', $client_response);
3056+ return $client_response;
3057+}
3058+
3059+sub _d {
3060+ my ($package, undef, $line) = caller 0;
3061+ @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
3062+ map { defined $_ ? $_ : 'undef' }
3063+ @_;
3064+ print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
3065+}
3066+
3067+1;
3068+}
3069+# ###########################################################################
3070+# End Pingback package
3071+# ###########################################################################
3072+
3073+# ###########################################################################
3074 # This is a combination of modules and programs in one -- a runnable module.
3075 # http://www.perl.com/pub/a/2006/07/13/lightning-articles.html?page=last
3076 # Or, look it up in the Camel book on pages 642 and 643 in the 3rd edition.
3077@@ -2373,6 +3341,7 @@
3078 use Socket qw(inet_aton);
3079 use sigtrap qw(handler finish untrapped normal-signals);
3080
3081+use Percona::Toolkit;
3082 use constant PTDEBUG => $ENV{PTDEBUG} || 0;
3083
3084 my $o;
3085@@ -2427,7 +3396,7 @@
3086 );
3087
3088 sub main {
3089- @ARGV = @_; # set global ARGV for this package
3090+ local @ARGV = @_; # set global ARGV for this package
3091
3092 my $q = new Quoter();
3093
3094@@ -2474,7 +3443,7 @@
3095 $o->usage_or_errors();
3096
3097 # ########################################################################
3098- # Start working.
3099+ # Connect to MySQL and set up the --dest, if any.
3100 # ########################################################################
3101 my $dbh = get_cxn($source_dsn, 1);
3102 my $dest_dbh;
3103@@ -2516,7 +3485,9 @@
3104 }
3105 }
3106
3107+ # ########################################################################
3108 # Daemonize only after (potentially) asking for passwords for --ask-pass.
3109+ # ########################################################################
3110 my $daemon;
3111 if ( $o->get('daemonize') ) {
3112 $daemon = new Daemon(o=>$o);
3113@@ -2529,6 +3500,16 @@
3114 $daemon->make_PID_file();
3115 }
3116
3117+ # ########################################################################
3118+ # Do the version-check
3119+ # ########################################################################
3120+ if ( $o->get('version-check') && ($o->has('quiet') && !$o->get('quiet')) ) {
3121+ Pingback::version_check($dbh);
3122+ }
3123+
3124+ # ########################################################################
3125+ # Start looking for and logging deadlocks.
3126+ # ########################################################################
3127 my $last_fingerprint = '';
3128
3129 $oktorun = 1;
3130@@ -3208,6 +4189,24 @@
3131
3132 Show version and exit.
3133
3134+=item --[no]version-check
3135+
3136+default: yes
3137+
3138+Send program versions to Percona and print suggested upgrades and problems.
3139+
3140+The version check feature causes the tool to send and receive data from
3141+Percona over the web. The data contains program versions from the local
3142+machine. Percona uses the data to focus development on the most widely
3143+used versions of programs, and to suggest to customers possible upgrades
3144+and known bad versions of programs.
3145+
3146+This feature can be disabled by specifying C<--no-version-check> on the
3147+command line or in one of several L<"--config"> files, or by setting the
3148+environment variable C<PERCONA_VERSION_CHECK=0>.
3149+
3150+For more information, visit L<http://www.percona.com/version-check>.
3151+
3152 =back
3153
3154 =head1 DSN OPTIONS
3155
3156=== modified file 'bin/pt-diskstats'
3157--- bin/pt-diskstats 2012-08-15 19:28:14 +0000
3158+++ bin/pt-diskstats 2012-08-23 01:57:20 +0000
3159@@ -1,4 +1,4 @@
3160-#!/usr/bin/env perl
3161+#!/usr/bin/env perl
3162
3163 # This program is part of Percona Toolkit: http://www.percona.com/software/
3164 # See "COPYRIGHT, LICENSE, AND WARRANTY" at the end of this file for legal
3165@@ -6,14 +6,44 @@
3166
3167 use strict;
3168 use warnings FATAL => 'all';
3169-use constant PTDEBUG => $ENV{PTDEBUG} || 0;
3170
3171-# %INC magic to allow us to require/use these even within the big file.
3172+# This tool is "fat-packed": most of its dependent modules are embedded
3173+# in this file. Setting %INC to this file for each module makes Perl aware
3174+# of this so it will not try to load the module from @INC. See the tool's
3175+# documentation for a full list of dependencies.
3176 BEGIN {
3177- $INC{$_} = __FILE__ for map {
3178- (my $t = $_ . ".pm") =~ s!::!/!g; $t
3179- } qw( OptionParser Transformers ReadKeyMini Diskstats DiskstatsGroupByAll DiskstatsGroupByDisk DiskstatsGroupBySample DiskstatsMenu pt_diskstats );
3180-}
3181+ $INC{$_} = __FILE__ for map { (my $pkg = "$_.pm") =~ s!::!/!g; $pkg } (qw(
3182+ Percona::Toolkit
3183+ OptionParser
3184+ Transformers
3185+ ReadKeyMini
3186+ Diskstats
3187+ DiskstatsGroupByAll
3188+ DiskstatsGroupByDisk
3189+ DiskstatsGroupBySample
3190+ DiskstatsMenu
3191+ VersionCheck
3192+ HTTPMicro
3193+ Pingback
3194+ ));
3195+}
3196+
3197+# ###########################################################################
3198+# Percona::Toolkit package
3199+# This package is a copy without comments from the original. The original
3200+# with comments and its test file can be found in the Bazaar repository at,
3201+# lib/Percona/Toolkit.pm
3202+# t/lib/Percona/Toolkit.t
3203+# See https://launchpad.net/percona-toolkit for more information.
3204+# ###########################################################################
3205+{
3206+package Percona::Toolkit;
3207+our $VERSION = '2.1.3';
3208+1;
3209+}
3210+# ###########################################################################
3211+# End Percona::Toolkit package
3212+# ###########################################################################
3213
3214 # ###########################################################################
3215 # OptionParser package
3216@@ -3477,6 +3507,939 @@
3217 # ###########################################################################
3218
3219 # ###########################################################################
3220+# VersionCheck package
3221+# This package is a copy without comments from the original. The original
3222+# with comments and its test file can be found in the Bazaar repository at,
3223+# lib/VersionCheck.pm
3224+# t/lib/VersionCheck.t
3225+# See https://launchpad.net/percona-toolkit for more information.
3226+# ###########################################################################
3227+{
3228+package VersionCheck;
3229+
3230+use strict;
3231+use warnings FATAL => 'all';
3232+use English qw(-no_match_vars);
3233+
3234+use constant PTDEBUG => $ENV{PTDEBUG} || 0;
3235+
3236+use File::Basename ();
3237+use Data::Dumper ();
3238+
3239+sub Dumper {
3240+ local $Data::Dumper::Indent = 1;
3241+ local $Data::Dumper::Sortkeys = 1;
3242+ local $Data::Dumper::Quotekeys = 0;
3243+
3244+ Data::Dumper::Dumper(@_);
3245+}
3246+
3247+sub new {
3248+ my ($class, %args) = @_;
3249+ my $self = {
3250+ valid_types => qr/
3251+ ^(?:
3252+ os_version
3253+ |perl_version
3254+ |perl_module_version
3255+ |mysql_variable
3256+ |bin_version
3257+ )$/x,
3258+ };
3259+ return bless $self, $class;
3260+}
3261+
3262+sub parse_server_response {
3263+ my ($self, %args) = @_;
3264+ my @required_args = qw(response);
3265+ foreach my $arg ( @required_args ) {
3266+ die "I need a $arg arugment" unless $args{$arg};
3267+ }
3268+ my ($response) = @args{@required_args};
3269+
3270+ my %items = map {
3271+ my ($item, $type, $vars) = split(";", $_);
3272+ if ( !defined $args{split_vars} || $args{split_vars} ) {
3273+ $vars = [ split(",", ($vars || '')) ];
3274+ }
3275+ $item => {
3276+ item => $item,
3277+ type => $type,
3278+ vars => $vars,
3279+ };
3280+ } split("\n", $response);
3281+
3282+ PTDEBUG && _d('Items:', Dumper(\%items));
3283+
3284+ return \%items;
3285+}
3286+
3287+sub get_versions {
3288+ my ($self, %args) = @_;
3289+ my @required_args = qw(items);
3290+ foreach my $arg ( @required_args ) {
3291+ die "I need a $arg arugment" unless $args{$arg};
3292+ }
3293+ my ($items) = @args{@required_args};
3294+ my $dbh = $args{dbh}; # optional
3295+
3296+ my %versions;
3297+ foreach my $item ( values %$items ) {
3298+ next unless $self->valid_item($item);
3299+
3300+ eval {
3301+ my $func = 'get_' . $item->{type};
3302+ my $version = $self->$func(
3303+ item => $item,
3304+ dbh => $dbh,
3305+ );
3306+ if ( $version ) {
3307+ chomp $version;
3308+ $versions{$item->{item}} = $version;
3309+ }
3310+ };
3311+ if ( $EVAL_ERROR ) {
3312+ PTDEBUG && _d('Error getting version for', Dumper($item), $EVAL_ERROR);
3313+ }
3314+ }
3315+
3316+ return \%versions;
3317+}
3318+
3319+sub valid_item {
3320+ my ($self, $item) = @_;
3321+ return unless $item;
3322+
3323+ if ( ($item->{type} || '') !~ m/$self->{valid_types}/ ) {
3324+ PTDEBUG && _d('Invalid type:', $item->{type});
3325+ return;
3326+ }
3327+
3328+ return 1;
3329+}
3330+
3331+sub get_os_version {
3332+ my ($self) = @_;
3333+
3334+ chomp(my $platform = `uname -s`);
3335+ PTDEBUG && _d('platform:', $platform);
3336+ return $OSNAME unless $platform;
3337+
3338+ chomp(my $lsb_release
3339+ = `which lsb_release 2>/dev/null | awk '{print \$1}'` || '');
3340+ PTDEBUG && _d('lsb_release:', $lsb_release);
3341+
3342+ my $release = "";
3343+
3344+ if ( $platform eq 'Linux' ) {
3345+ if ( -f "/etc/fedora-release" ) {
3346+ $release = `cat /etc/fedora-release`;
3347+ }
3348+ elsif ( -f "/etc/redhat-release" ) {
3349+ $release = `cat /etc/redhat-release`;
3350+ }
3351+ elsif ( -f "/etc/system-release" ) {
3352+ $release = `cat /etc/system-release`;
3353+ }
3354+ elsif ( $lsb_release ) {
3355+ $release = `$lsb_release -ds`;
3356+ }
3357+ elsif ( -f "/etc/lsb-release" ) {
3358+ $release = `grep DISTRIB_DESCRIPTION /etc/lsb-release`;
3359+ $release =~ s/^\w+="([^"]+)".+/$1/;
3360+ }
3361+ elsif ( -f "/etc/debian_version" ) {
3362+ chomp(my $rel = `cat /etc/debian_version`);
3363+ $release = "Debian $rel";
3364+ if ( -f "/etc/apt/sources.list" ) {
3365+ chomp(my $code_name = `awk '/^deb/ {print \$3}' /etc/apt/sources.list | awk -F/ '{print \$1}'| awk 'BEGIN {FS="|"} {print \$1}' | sort | uniq -c | sort -rn | head -n1 | awk '{print \$2}'`);
3366+ $release .= " ($code_name)" if $code_name;
3367+ }
3368+ }
3369+ elsif ( `ls /etc/*release 2>/dev/null` ) {
3370+ if ( `grep DISTRIB_DESCRIPTION /etc/*release 2>/dev/null` ) {
3371+ $release = `grep DISTRIB_DESCRIPTION /etc/*release | head -n1`;
3372+ }
3373+ else {
3374+ $release = `cat /etc/*release | head -n1`;
3375+ }
3376+ }
3377+ }
3378+ elsif ( $platform =~ m/^(BSD|Darwin)$/ ) {
3379+ my $rel = `uname -r`;
3380+ $release = "$platform $rel";
3381+ }
3382+ elsif ( $platform eq "SunOS" ) {
3383+ my $rel = `head -n1 /etc/release` || `uname -r`;
3384+ $release = "$platform $rel";
3385+ }
3386+
3387+ if ( !$release ) {
3388+ PTDEBUG && _d('Failed to get the release, using platform');
3389+ $release = $platform;
3390+ }
3391+ chomp($release);
3392+
3393+ PTDEBUG && _d('OS version =', $release);
3394+ return $release;
3395+}
3396+
3397+sub get_perl_version {
3398+ my ($self, %args) = @_;
3399+ my $item = $args{item};
3400+ return unless $item;
3401+
3402+ my $version = sprintf '%vd', $PERL_VERSION;
3403+ PTDEBUG && _d('Perl version', $version);
3404+ return $version;
3405+}
3406+
3407+sub get_perl_module_version {
3408+ my ($self, %args) = @_;
3409+ my $item = $args{item};
3410+ return unless $item;
3411+
3412+ my $var = $item->{item} . '::VERSION';
3413+ my $version = _get_scalar($var);
3414+ PTDEBUG && _d('Perl version for', $var, '=', "$version");
3415+
3416+ return $version ? "$version" : $version;
3417+}
3418+
3419+sub _get_scalar {
3420+ no strict;
3421+ return ${*{shift()}};
3422+}
3423+
3424+sub get_mysql_variable {
3425+ my $self = shift;
3426+ return $self->_get_from_mysql(
3427+ show => 'VARIABLES',
3428+ @_,
3429+ );
3430+}
3431+
3432+
3433+sub _get_from_mysql {
3434+ my ($self, %args) = @_;
3435+ my $show = $args{show};
3436+ my $item = $args{item};
3437+ my $dbh = $args{dbh};
3438+ return unless $show && $item && $dbh;
3439+
3440+ local $dbh->{FetchHashKeyName} = 'NAME_lc';
3441+ my $sql = qq/SHOW $show/;
3442+ PTDEBUG && _d($sql);
3443+ my $rows = $dbh->selectall_hashref($sql, 'variable_name');
3444+
3445+ my @versions;
3446+ foreach my $var ( @{$item->{vars}} ) {
3447+ $var = lc($var);
3448+ my $version = $rows->{$var}->{value};
3449+ PTDEBUG && _d('MySQL version for', $item->{item}, '=', $version);
3450+ push @versions, $version;
3451+ }
3452+
3453+ return join(' ', @versions);
3454+}
3455+
3456+sub get_bin_version {
3457+ my ($self, %args) = @_;
3458+ my $item = $args{item};
3459+ my $cmd = $item->{item};
3460+ return unless $cmd;
3461+
3462+ my $sanitized_command = File::Basename::basename($cmd);
3463+ PTDEBUG && _d('cmd:', $cmd, 'sanitized:', $sanitized_command);
3464+ return if $sanitized_command !~ /\A[a-zA-Z0-9_-]+\z/;
3465+
3466+ my $output = `$sanitized_command --version 2>&1`;
3467+ PTDEBUG && _d('output:', $output);
3468+
3469+ my ($version) = $output =~ /v?([0-9]+\.[0-9]+(?:\.[\w-]+)?)/;
3470+
3471+ PTDEBUG && _d('Version for', $sanitized_command, '=', $version);
3472+ return $version;
3473+}
3474+
3475+sub _d {
3476+ my ($package, undef, $line) = caller 0;
3477+ @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
3478+ map { defined $_ ? $_ : 'undef' }
3479+ @_;
3480+ print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
3481+}
3482+
3483+1;
3484+}
3485+# ###########################################################################
3486+# End VersionCheck package
3487+# ###########################################################################
3488+
3489+# ###########################################################################
3490+# HTTPMicro package
3491+# This package is a copy without comments from the original. The original
3492+# with comments and its test file can be found in the Bazaar repository at,
3493+# lib/HTTPMicro.pm
3494+# t/lib/HTTPMicro.t
3495+# See https://launchpad.net/percona-toolkit for more information.
3496+# ###########################################################################
3497+{
3498+
3499+package HTTPMicro;
3500+BEGIN {
3501+ $HTTPMicro::VERSION = '0.001';
3502+}
3503+use strict;
3504+use warnings;
3505+
3506+use Carp ();
3507+
3508+
3509+my @attributes;
3510+BEGIN {
3511+ @attributes = qw(agent timeout);
3512+ no strict 'refs';
3513+ for my $accessor ( @attributes ) {
3514+ *{$accessor} = sub {
3515+ @_ > 1 ? $_[0]->{$accessor} = $_[1] : $_[0]->{$accessor};
3516+ };
3517+ }
3518+}
3519+
3520+sub new {
3521+ my($class, %args) = @_;
3522+ (my $agent = $class) =~ s{::}{-}g;
3523+ my $self = {
3524+ agent => $agent . "/" . ($class->VERSION || 0),
3525+ timeout => 60,
3526+ };
3527+ for my $key ( @attributes ) {
3528+ $self->{$key} = $args{$key} if exists $args{$key}
3529+ }
3530+ return bless $self, $class;
3531+}
3532+
3533+sub request {
3534+ my ($self, $method, $url, $args) = @_;
3535+ @_ == 3 || (@_ == 4 && ref $args eq 'HASH')
3536+ or Carp::croak(q/Usage: $http->request(METHOD, URL, [HASHREF])/);
3537+ $args ||= {}; # we keep some state in this during _request
3538+
3539+ my $response;
3540+ for ( 0 .. 1 ) {
3541+ $response = eval { $self->_request($method, $url, $args) };
3542+ last unless $@ && $method eq 'GET'
3543+ && $@ =~ m{^(?:Socket closed|Unexpected end)};
3544+ }
3545+
3546+ if (my $e = "$@") {
3547+ $response = {
3548+ success => q{},
3549+ status => 599,
3550+ reason => 'Internal Exception',
3551+ content => $e,
3552+ headers => {
3553+ 'content-type' => 'text/plain',
3554+ 'content-length' => length $e,
3555+ }
3556+ };
3557+ }
3558+ return $response;
3559+}
3560+
3561+sub _request {
3562+ my ($self, $method, $url, $args) = @_;
3563+
3564+ my ($scheme, $host, $port, $path_query) = $self->_split_url($url);
3565+
3566+ my $request = {
3567+ method => $method,
3568+ scheme => $scheme,
3569+ host_port => ($port == 80 ? $host : "$host:$port"),
3570+ uri => $path_query,
3571+ headers => {},
3572+ };
3573+
3574+ my $handle = HTTPMicro::Handle->new(timeout => $self->{timeout});
3575+
3576+ $handle->connect($scheme, $host, $port);
3577+
3578+ $self->_prepare_headers_and_cb($request, $args);
3579+ $handle->write_request_header(@{$request}{qw/method uri headers/});
3580+ $handle->write_content_body($request) if $request->{content};
3581+
3582+ my $response;
3583+ do { $response = $handle->read_response_header }
3584+ until (substr($response->{status},0,1) ne '1');
3585+
3586+ if (!($method eq 'HEAD' || $response->{status} =~ /^[23]04/)) {
3587+ $response->{content} = '';
3588+ $handle->read_content_body(sub { $_[1]->{content} .= $_[0] }, $response);
3589+ }
3590+
3591+ $handle->close;
3592+ $response->{success} = substr($response->{status},0,1) eq '2';
3593+ return $response;
3594+}
3595+
3596+sub _prepare_headers_and_cb {
3597+ my ($self, $request, $args) = @_;
3598+
3599+ for ($args->{headers}) {
3600+ next unless defined;
3601+ while (my ($k, $v) = each %$_) {
3602+ $request->{headers}{lc $k} = $v;
3603+ }
3604+ }
3605+ $request->{headers}{'host'} = $request->{host_port};
3606+ $request->{headers}{'connection'} = "close";
3607+ $request->{headers}{'user-agent'} ||= $self->{agent};
3608+
3609+ if (defined $args->{content}) {
3610+ $request->{headers}{'content-type'} ||= "application/octet-stream";
3611+ utf8::downgrade($args->{content}, 1)
3612+ or Carp::croak(q/Wide character in request message body/);
3613+ $request->{headers}{'content-length'} = length $args->{content};
3614+ $request->{content} = $args->{content};
3615+ }
3616+ return;
3617+}
3618+
3619+sub _split_url {
3620+ my $url = pop;
3621+
3622+ my ($scheme, $authority, $path_query) = $url =~ m<\A([^:/?#]+)://([^/?#]*)([^#]*)>
3623+ or Carp::croak(qq/Cannot parse URL: '$url'/);
3624+
3625+ $scheme = lc $scheme;
3626+ $path_query = "/$path_query" unless $path_query =~ m<\A/>;
3627+
3628+ my $host = (length($authority)) ? lc $authority : 'localhost';
3629+ $host =~ s/\A[^@]*@//; # userinfo
3630+ my $port = do {
3631+ $host =~ s/:([0-9]*)\z// && length $1
3632+ ? $1
3633+ : ($scheme eq 'http' ? 80 : undef);
3634+ };
3635+
3636+ return ($scheme, $host, $port, $path_query);
3637+}
3638+
3639+package
3640+ HTTPMicro::Handle; # hide from PAUSE/indexers
3641+use strict;
3642+use warnings;
3643+
3644+use Carp qw[croak];
3645+use Errno qw[EINTR EPIPE];
3646+use IO::Socket qw[SOCK_STREAM];
3647+
3648+sub BUFSIZE () { 32768 }
3649+
3650+my $Printable = sub {
3651+ local $_ = shift;
3652+ s/\r/\\r/g;
3653+ s/\n/\\n/g;
3654+ s/\t/\\t/g;
3655+ s/([^\x20-\x7E])/sprintf('\\x%.2X', ord($1))/ge;
3656+ $_;
3657+};
3658+
3659+sub new {
3660+ my ($class, %args) = @_;
3661+ return bless {
3662+ rbuf => '',
3663+ timeout => 60,
3664+ max_line_size => 16384,
3665+ %args
3666+ }, $class;
3667+}
3668+
3669+sub connect {
3670+ @_ == 4 || croak(q/Usage: $handle->connect(scheme, host, port)/);
3671+ my ($self, $scheme, $host, $port) = @_;
3672+
3673+ if ( $scheme ne 'http' ) {
3674+ croak(qq/Unsupported URL scheme '$scheme'/);
3675+ }
3676+
3677+ $self->{fh} = 'IO::Socket::INET'->new(
3678+ PeerHost => $host,
3679+ PeerPort => $port,
3680+ Proto => 'tcp',
3681+ Type => SOCK_STREAM,
3682+ Timeout => $self->{timeout}
3683+ ) or croak(qq/Could not connect to '$host:$port': $@/);
3684+
3685+ binmode($self->{fh})
3686+ or croak(qq/Could not binmode() socket: '$!'/);
3687+
3688+ $self->{host} = $host;
3689+ $self->{port} = $port;
3690+
3691+ return $self;
3692+}
3693+
3694+sub close {
3695+ @_ == 1 || croak(q/Usage: $handle->close()/);
3696+ my ($self) = @_;
3697+ CORE::close($self->{fh})
3698+ or croak(qq/Could not close socket: '$!'/);
3699+}
3700+
3701+sub write {
3702+ @_ == 2 || croak(q/Usage: $handle->write(buf)/);
3703+ my ($self, $buf) = @_;
3704+
3705+ my $len = length $buf;
3706+ my $off = 0;
3707+
3708+ local $SIG{PIPE} = 'IGNORE';
3709+
3710+ while () {
3711+ $self->can_write
3712+ or croak(q/Timed out while waiting for socket to become ready for writing/);
3713+ my $r = syswrite($self->{fh}, $buf, $len, $off);
3714+ if (defined $r) {
3715+ $len -= $r;
3716+ $off += $r;
3717+ last unless $len > 0;
3718+ }
3719+ elsif ($! == EPIPE) {
3720+ croak(qq/Socket closed by remote server: $!/);
3721+ }
3722+ elsif ($! != EINTR) {
3723+ croak(qq/Could not write to socket: '$!'/);
3724+ }
3725+ }
3726+ return $off;
3727+}
3728+
3729+sub read {
3730+ @_ == 2 || @_ == 3 || croak(q/Usage: $handle->read(len)/);
3731+ my ($self, $len) = @_;
3732+
3733+ my $buf = '';
3734+ my $got = length $self->{rbuf};
3735+
3736+ if ($got) {
3737+ my $take = ($got < $len) ? $got : $len;
3738+ $buf = substr($self->{rbuf}, 0, $take, '');
3739+ $len -= $take;
3740+ }
3741+
3742+ while ($len > 0) {
3743+ $self->can_read
3744+ or croak(q/Timed out while waiting for socket to become ready for reading/);
3745+ my $r = sysread($self->{fh}, $buf, $len, length $buf);
3746+ if (defined $r) {
3747+ last unless $r;
3748+ $len -= $r;
3749+ }
3750+ elsif ($! != EINTR) {
3751+ croak(qq/Could not read from socket: '$!'/);
3752+ }
3753+ }
3754+ if ($len) {
3755+ croak(q/Unexpected end of stream/);
3756+ }
3757+ return $buf;
3758+}
3759+
3760+sub readline {
3761+ @_ == 1 || croak(q/Usage: $handle->readline()/);
3762+ my ($self) = @_;
3763+
3764+ while () {
3765+ if ($self->{rbuf} =~ s/\A ([^\x0D\x0A]* \x0D?\x0A)//x) {
3766+ return $1;
3767+ }
3768+ $self->can_read
3769+ or croak(q/Timed out while waiting for socket to become ready for reading/);
3770+ my $r = sysread($self->{fh}, $self->{rbuf}, BUFSIZE, length $self->{rbuf});
3771+ if (defined $r) {
3772+ last unless $r;
3773+ }
3774+ elsif ($! != EINTR) {
3775+ croak(qq/Could not read from socket: '$!'/);
3776+ }
3777+ }
3778+ croak(q/Unexpected end of stream while looking for line/);
3779+}
3780+
3781+sub read_header_lines {
3782+ @_ == 1 || @_ == 2 || croak(q/Usage: $handle->read_header_lines([headers])/);
3783+ my ($self, $headers) = @_;
3784+ $headers ||= {};
3785+ my $lines = 0;
3786+ my $val;
3787+
3788+ while () {
3789+ my $line = $self->readline;
3790+
3791+ if ($line =~ /\A ([^\x00-\x1F\x7F:]+) : [\x09\x20]* ([^\x0D\x0A]*)/x) {
3792+ my ($field_name) = lc $1;
3793+ $val = \($headers->{$field_name} = $2);
3794+ }
3795+ elsif ($line =~ /\A [\x09\x20]+ ([^\x0D\x0A]*)/x) {
3796+ $val
3797+ or croak(q/Unexpected header continuation line/);
3798+ next unless length $1;
3799+ $$val .= ' ' if length $$val;
3800+ $$val .= $1;
3801+ }
3802+ elsif ($line =~ /\A \x0D?\x0A \z/x) {
3803+ last;
3804+ }
3805+ else {
3806+ croak(q/Malformed header line: / . $Printable->($line));
3807+ }
3808+ }
3809+ return $headers;
3810+}
3811+
3812+sub write_header_lines {
3813+ (@_ == 2 && ref $_[1] eq 'HASH') || croak(q/Usage: $handle->write_header_lines(headers)/);
3814+ my($self, $headers) = @_;
3815+
3816+ my $buf = '';
3817+ while (my ($k, $v) = each %$headers) {
3818+ my $field_name = lc $k;
3819+ $field_name =~ /\A [\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7A\x7C\x7E]+ \z/x
3820+ or croak(q/Invalid HTTP header field name: / . $Printable->($field_name));
3821+ $field_name =~ s/\b(\w)/\u$1/g;
3822+ $buf .= "$field_name: $v\x0D\x0A";
3823+ }
3824+ $buf .= "\x0D\x0A";
3825+ return $self->write($buf);
3826+}
3827+
3828+sub read_content_body {
3829+ @_ == 3 || @_ == 4 || croak(q/Usage: $handle->read_content_body(callback, response, [read_length])/);
3830+ my ($self, $cb, $response, $len) = @_;
3831+ $len ||= $response->{headers}{'content-length'};
3832+
3833+ croak("No content-length in the returned response, and this "
3834+ . "UA doesn't implement chunking") unless defined $len;
3835+
3836+ while ($len > 0) {
3837+ my $read = ($len > BUFSIZE) ? BUFSIZE : $len;
3838+ $cb->($self->read($read), $response);
3839+ $len -= $read;
3840+ }
3841+
3842+ return;
3843+}
3844+
3845+sub write_content_body {
3846+ @_ == 2 || croak(q/Usage: $handle->write_content_body(request)/);
3847+ my ($self, $request) = @_;
3848+ my ($len, $content_length) = (0, $request->{headers}{'content-length'});
3849+
3850+ $len += $self->write($request->{content});
3851+
3852+ $len == $content_length
3853+ or croak(qq/Content-Length missmatch (got: $len expected: $content_length)/);
3854+
3855+ return $len;
3856+}
3857+
3858+sub read_response_header {
3859+ @_ == 1 || croak(q/Usage: $handle->read_response_header()/);
3860+ my ($self) = @_;
3861+
3862+ my $line = $self->readline;
3863+
3864+ $line =~ /\A (HTTP\/(0*\d+\.0*\d+)) [\x09\x20]+ ([0-9]{3}) [\x09\x20]+ ([^\x0D\x0A]*) \x0D?\x0A/x
3865+ or croak(q/Malformed Status-Line: / . $Printable->($line));
3866+
3867+ my ($protocol, $version, $status, $reason) = ($1, $2, $3, $4);
3868+
3869+ return {
3870+ status => $status,
3871+ reason => $reason,
3872+ headers => $self->read_header_lines,
3873+ protocol => $protocol,
3874+ };
3875+}
3876+
3877+sub write_request_header {
3878+ @_ == 4 || croak(q/Usage: $handle->write_request_header(method, request_uri, headers)/);
3879+ my ($self, $method, $request_uri, $headers) = @_;
3880+
3881+ return $self->write("$method $request_uri HTTP/1.1\x0D\x0A")
3882+ + $self->write_header_lines($headers);
3883+}
3884+
3885+sub _do_timeout {
3886+ my ($self, $type, $timeout) = @_;
3887+ $timeout = $self->{timeout}
3888+ unless defined $timeout && $timeout >= 0;
3889+
3890+ my $fd = fileno $self->{fh};
3891+ defined $fd && $fd >= 0
3892+ or croak(q/select(2): 'Bad file descriptor'/);
3893+
3894+ my $initial = time;
3895+ my $pending = $timeout;
3896+ my $nfound;
3897+
3898+ vec(my $fdset = '', $fd, 1) = 1;
3899+
3900+ while () {
3901+ $nfound = ($type eq 'read')
3902+ ? select($fdset, undef, undef, $pending)
3903+ : select(undef, $fdset, undef, $pending) ;
3904+ if ($nfound == -1) {
3905+ $! == EINTR
3906+ or croak(qq/select(2): '$!'/);
3907+ redo if !$timeout || ($pending = $timeout - (time - $initial)) > 0;
3908+ $nfound = 0;
3909+ }
3910+ last;
3911+ }
3912+ $! = 0;
3913+ return $nfound;
3914+}
3915+
3916+sub can_read {
3917+ @_ == 1 || @_ == 2 || croak(q/Usage: $handle->can_read([timeout])/);
3918+ my $self = shift;
3919+ return $self->_do_timeout('read', @_)
3920+}
3921+
3922+sub can_write {
3923+ @_ == 1 || @_ == 2 || croak(q/Usage: $handle->can_write([timeout])/);
3924+ my $self = shift;
3925+ return $self->_do_timeout('write', @_)
3926+}
3927+
3928+1;
3929+}
3930+# ###########################################################################
3931+# End HTTPMicro package
3932+# ###########################################################################
3933+
3934+# ###########################################################################
3935+# Pingback package
3936+# This package is a copy without comments from the original. The original
3937+# with comments and its test file can be found in the Bazaar repository at,
3938+# lib/Pingback.pm
3939+# t/lib/Pingback.t
3940+# See https://launchpad.net/percona-toolkit for more information.
3941+# ###########################################################################
3942+{
3943+package Pingback;
3944+
3945+use strict;
3946+use warnings FATAL => 'all';
3947+use English qw(-no_match_vars);
3948+
3949+use constant PTDEBUG => $ENV{PTDEBUG} || 0;
3950+
3951+use File::Basename qw();
3952+use Data::Dumper qw();
3953+use Fcntl qw(:DEFAULT);
3954+
3955+use File::Spec;
3956+
3957+my $dir = File::Spec->tmpdir();
3958+my $check_time_file = File::Spec->catfile($dir,'percona-toolkit-version-check');
3959+my $check_time_limit = 60 * 60 * 24; # one day
3960+
3961+sub Dumper {
3962+ local $Data::Dumper::Indent = 1;
3963+ local $Data::Dumper::Sortkeys = 1;
3964+ local $Data::Dumper::Quotekeys = 0;
3965+
3966+ Data::Dumper::Dumper(@_);
3967+}
3968+
3969+local $EVAL_ERROR;
3970+eval {
3971+ require HTTPMicro;
3972+ require VersionCheck;
3973+};
3974+
3975+sub version_check {
3976+ eval {
3977+ if (exists $ENV{PERCONA_VERSION_CHECK} && !$ENV{PERCONA_VERSION_CHECK}) {
3978+ if ( $ENV{PTVCDEBUG} || PTDEBUG ) {
3979+ _d('--version-check is disabled by the PERCONA_VERSION_CHECK',
3980+ 'environment variable');
3981+ }
3982+ return;
3983+ }
3984+
3985+ if ( !time_to_check($check_time_file) ) {
3986+ if ( $ENV{PTVCDEBUG} || PTDEBUG ) {
3987+ _d('It is not time to --version-checka again;',
3988+ 'only 1 check per', $check_time_limit, 'seconds, and the last',
3989+ 'check was performed on the modified time of', $check_time_file);
3990+ }
3991+ return;
3992+ }
3993+
3994+ my $dbh = shift; # optional
3995+ my $advice = pingback(
3996+ url => $ENV{PERCONA_VERSION_CHECK_URL} || 'http://v.percona.com',
3997+ dbh => $dbh,
3998+ );
3999+ if ( $advice ) {
4000+ print "# Percona suggests these upgrades:\n";
4001+ print join("\n", map { "# * $_" } @$advice);
4002+ print "\n# Specify --no-version-check to disable these suggestions.\n\n";
4003+ }
4004+ elsif ( $ENV{PTVCDEBUG} || PTDEBUG ) {
4005+ _d('--version-check worked, but there were no suggestions');
4006+ }
4007+ };
4008+ if ( $EVAL_ERROR ) {
4009+ if ( $ENV{PTVCDEBUG} || PTDEBUG ) {
4010+ _d('Error doing --version-check:', $EVAL_ERROR);
4011+ }
4012+ }
4013+
4014+ return;
4015+}
4016+
4017+sub pingback {
4018+ my (%args) = @_;
4019+ my @required_args = qw(url);
4020+ foreach my $arg ( @required_args ) {
4021+ die "I need a $arg arugment" unless $args{$arg};
4022+ }
4023+ my ($url) = @args{@required_args};
4024+
4025+ my ($dbh, $ua, $vc) = @args{qw(dbh ua VersionCheck)};
4026+
4027+ $ua ||= HTTPMicro->new( timeout => 2 );
4028+ $vc ||= VersionCheck->new();
4029+
4030+ my $response = $ua->request('GET', $url);
4031+ PTDEBUG && _d('Server response:', Dumper($response));
4032+ die "No response from GET $url"
4033+ if !$response;
4034+ die "GET $url returned HTTP status $response->{status}; expected 200"
4035+ if $response->{status} != 200;
4036+ die "GET $url did not return any programs to check"
4037+ if !$response->{content};
4038+
4039+ my $items = $vc->parse_server_response(
4040+ response => $response->{content}
4041+ );
4042+ die "Failed to parse server requested programs: $response->{content}"
4043+ if !scalar keys %$items;
4044+
4045+ my $versions = $vc->get_versions(
4046+ items => $items,
4047+ dbh => $dbh,
4048+ );
4049+ die "Failed to get any program versions; should have at least gotten Perl"
4050+ if !scalar keys %$versions;
4051+
4052+ my $client_content = encode_client_response(
4053+ items => $items,
4054+ versions => $versions,
4055+ );
4056+
4057+ my $client_response = {
4058+ headers => { "X-Percona-Toolkit-Tool" => File::Basename::basename($0) },
4059+ content => $client_content,
4060+ };
4061+ PTDEBUG && _d('Client response:', Dumper($client_response));
4062+
4063+ $response = $ua->request('POST', $url, $client_response);
4064+ PTDEBUG && _d('Server suggestions:', Dumper($response));
4065+ die "No response from POST $url $client_response"
4066+ if !$response;
4067+ die "POST $url returned HTTP status $response->{status}; expected 200"
4068+ if $response->{status} != 200;
4069+
4070+ return unless $response->{content};
4071+
4072+ $items = $vc->parse_server_response(
4073+ response => $response->{content},
4074+ split_vars => 0,
4075+ );
4076+ die "Failed to parse server suggestions: $response->{content}"
4077+ if !scalar keys %$items;
4078+ my @suggestions = map { $_->{vars} }
4079+ sort { $a->{item} cmp $b->{item} }
4080+ values %$items;
4081+
4082+ return \@suggestions;
4083+}
4084+
4085+sub time_to_check {
4086+ my ($file) = @_;
4087+ die "I need a file argument" unless $file;
4088+
4089+ if ( !-f $file ) {
4090+ PTDEBUG && _d('Creating', $file);
4091+ _touch($file);
4092+ return 1;
4093+ }
4094+
4095+ my $mtime = (stat $file)[9];
4096+ if ( !defined $mtime ) {
4097+ PTDEBUG && _d('Error getting modified time of', $file);
4098+ return 0;
4099+ }
4100+
4101+ my $time = int(time());
4102+ PTDEBUG && _d('time=', $time, 'mtime=', $mtime);
4103+ if ( ($time - $mtime) > $check_time_limit ) {
4104+ _touch($file);
4105+ return 1;
4106+ }
4107+
4108+ return 0;
4109+}
4110+
4111+sub _touch {
4112+ my ($file) = @_;
4113+ sysopen my $fh, $file, O_WRONLY|O_CREAT|O_NONBLOCK
4114+ or die "Cannot create $file : $!";
4115+ close $fh or die "Cannot close $file : $!";
4116+ utime(undef, undef, $file);
4117+}
4118+
4119+sub encode_client_response {
4120+ my (%args) = @_;
4121+ my @required_args = qw(items versions);
4122+ foreach my $arg ( @required_args ) {
4123+ die "I need a $arg arugment" unless $args{$arg};
4124+ }
4125+ my ($items, $versions) = @args{@required_args};
4126+
4127+ my @lines;
4128+ foreach my $item ( sort keys %$items ) {
4129+ next unless exists $versions->{$item};
4130+ push @lines, join(';', $item, $versions->{$item});
4131+ }
4132+
4133+ my $client_response = join("\n", @lines) . "\n";
4134+ PTDEBUG && _d('Client response:', $client_response);
4135+ return $client_response;
4136+}
4137+
4138+sub _d {
4139+ my ($package, undef, $line) = caller 0;
4140+ @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
4141+ map { defined $_ ? $_ : 'undef' }
4142+ @_;
4143+ print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
4144+}
4145+
4146+1;
4147+}
4148+# ###########################################################################
4149+# End Pingback package
4150+# ###########################################################################
4151+
4152+# ###########################################################################
4153 # This is a combination of modules and programs in one -- a runnable module.
4154 # http://www.perl.com/pub/a/2006/07/13/lightning-articles.html?page=last
4155 # Or, look it up in the Camel book on pages 642 and 643 in the 3rd edition.
4156@@ -3491,6 +4454,7 @@
4157 use warnings FATAL => 'all';
4158 use English qw(-no_match_vars);
4159 use constant PTDEBUG => $ENV{PTDEBUG} || 0;
4160+use Percona::Toolkit;
4161
4162 sub main {
4163 local @ARGV = @_; # set global ARGV for this package
4164@@ -3503,7 +4467,10 @@
4165 $o->get_opts();
4166
4167 # --sample-time only applies to --group-by sample.
4168- if ( PTDEBUG && $o->get('group-by') !~ m/sample/i && $o->get('sample-time') ) {
4169+ if ( PTDEBUG
4170+ && $o->get('group-by') !~ m/sample/i
4171+ && $o->get('sample-time') )
4172+ {
4173 _d("Possibly useless use of --sample-time without --group-by sample");
4174 }
4175
4176@@ -3515,7 +4482,16 @@
4177
4178 $o->usage_or_errors();
4179
4180+ # ########################################################################
4181+ # Do the version-check
4182+ # ########################################################################
4183+ if ( $o->get('version-check') && ($o->has('quiet') && !$o->get('quiet')) ) {
4184+ Pingback::version_check();
4185+ }
4186+
4187+ # ########################################################################
4188 # Interactive mode. Delegate to DiskstatsMenu::run_interactive
4189+ # ########################################################################
4190 my $diskstats = new DiskstatsMenu();
4191 return $diskstats->run_interactive(
4192 OptionParser => $o,
4193@@ -4099,6 +5075,24 @@
4194
4195 Show version and exit.
4196
4197+=item --[no]version-check
4198+
4199+default: yes
4200+
4201+Send program versions to Percona and print suggested upgrades and problems.
4202+
4203+The version check feature causes the tool to send and receive data from
4204+Percona over the web. The data contains program versions from the local
4205+machine. Percona uses the data to focus development on the most widely
4206+used versions of programs, and to suggest to customers possible upgrades
4207+and known bad versions of programs.
4208+
4209+This feature can be disabled by specifying C<--no-version-check> on the
4210+command line or in one of several L<"--config"> files, or by setting the
4211+environment variable C<PERCONA_VERSION_CHECK=0>.
4212+
4213+For more information, visit L<http://www.percona.com/version-check>.
4214+
4215 =back
4216
4217 =head1 ENVIRONMENT
4218
4219=== modified file 'bin/pt-duplicate-key-checker'
4220--- bin/pt-duplicate-key-checker 2012-08-15 22:35:45 +0000
4221+++ bin/pt-duplicate-key-checker 2012-08-23 01:57:20 +0000
4222@@ -6,7 +6,45 @@
4223
4224 use strict;
4225 use warnings FATAL => 'all';
4226-use constant PTDEBUG => $ENV{PTDEBUG} || 0;
4227+
4228+# This tool is "fat-packed": most of its dependent modules are embedded
4229+# in this file. Setting %INC to this file for each module makes Perl aware
4230+# of this so it will not try to load the module from @INC. See the tool's
4231+# documentation for a full list of dependencies.
4232+BEGIN {
4233+ $INC{$_} = __FILE__ for map { (my $pkg = "$_.pm") =~ s!::!/!g; $pkg } (qw(
4234+ Percona::Toolkit
4235+ Quoter
4236+ TableParser
4237+ DSNParser
4238+ OptionParser
4239+ KeySize
4240+ DuplicateKeyFinder
4241+ Daemon
4242+ Schema
4243+ SchemaIterator
4244+ VersionCheck
4245+ HTTPMicro
4246+ Pingback
4247+ ));
4248+}
4249+
4250+# ###########################################################################
4251+# Percona::Toolkit package
4252+# This package is a copy without comments from the original. The original
4253+# with comments and its test file can be found in the Bazaar repository at,
4254+# lib/Percona/Toolkit.pm
4255+# t/lib/Percona/Toolkit.t
4256+# See https://launchpad.net/percona-toolkit for more information.
4257+# ###########################################################################
4258+{
4259+package Percona::Toolkit;
4260+our $VERSION = '2.1.3';
4261+1;
4262+}
4263+# ###########################################################################
4264+# End Percona::Toolkit package
4265+# ###########################################################################
4266
4267 # ###########################################################################
4268 # Quoter package
4269@@ -3312,6 +3350,939 @@
4270 # End SchemaIterator package
4271 # ###########################################################################
4272
4273+# ###########################################################################
4274+# VersionCheck package
4275+# This package is a copy without comments from the original. The original
4276+# with comments and its test file can be found in the Bazaar repository at,
4277+# lib/VersionCheck.pm
4278+# t/lib/VersionCheck.t
4279+# See https://launchpad.net/percona-toolkit for more information.
4280+# ###########################################################################
4281+{
4282+package VersionCheck;
4283+
4284+use strict;
4285+use warnings FATAL => 'all';
4286+use English qw(-no_match_vars);
4287+
4288+use constant PTDEBUG => $ENV{PTDEBUG} || 0;
4289+
4290+use File::Basename ();
4291+use Data::Dumper ();
4292+
4293+sub Dumper {
4294+ local $Data::Dumper::Indent = 1;
4295+ local $Data::Dumper::Sortkeys = 1;
4296+ local $Data::Dumper::Quotekeys = 0;
4297+
4298+ Data::Dumper::Dumper(@_);
4299+}
4300+
4301+sub new {
4302+ my ($class, %args) = @_;
4303+ my $self = {
4304+ valid_types => qr/
4305+ ^(?:
4306+ os_version
4307+ |perl_version
4308+ |perl_module_version
4309+ |mysql_variable
4310+ |bin_version
4311+ )$/x,
4312+ };
4313+ return bless $self, $class;
4314+}
4315+
4316+sub parse_server_response {
4317+ my ($self, %args) = @_;
4318+ my @required_args = qw(response);
4319+ foreach my $arg ( @required_args ) {
4320+ die "I need a $arg arugment" unless $args{$arg};
4321+ }
4322+ my ($response) = @args{@required_args};
4323+
4324+ my %items = map {
4325+ my ($item, $type, $vars) = split(";", $_);
4326+ if ( !defined $args{split_vars} || $args{split_vars} ) {
4327+ $vars = [ split(",", ($vars || '')) ];
4328+ }
4329+ $item => {
4330+ item => $item,
4331+ type => $type,
4332+ vars => $vars,
4333+ };
4334+ } split("\n", $response);
4335+
4336+ PTDEBUG && _d('Items:', Dumper(\%items));
4337+
4338+ return \%items;
4339+}
4340+
4341+sub get_versions {
4342+ my ($self, %args) = @_;
4343+ my @required_args = qw(items);
4344+ foreach my $arg ( @required_args ) {
4345+ die "I need a $arg arugment" unless $args{$arg};
4346+ }
4347+ my ($items) = @args{@required_args};
4348+ my $dbh = $args{dbh}; # optional
4349+
4350+ my %versions;
4351+ foreach my $item ( values %$items ) {
4352+ next unless $self->valid_item($item);
4353+
4354+ eval {
4355+ my $func = 'get_' . $item->{type};
4356+ my $version = $self->$func(
4357+ item => $item,
4358+ dbh => $dbh,
4359+ );
4360+ if ( $version ) {
4361+ chomp $version;
4362+ $versions{$item->{item}} = $version;
4363+ }
4364+ };
4365+ if ( $EVAL_ERROR ) {
4366+ PTDEBUG && _d('Error getting version for', Dumper($item), $EVAL_ERROR);
4367+ }
4368+ }
4369+
4370+ return \%versions;
4371+}
4372+
4373+sub valid_item {
4374+ my ($self, $item) = @_;
4375+ return unless $item;
4376+
4377+ if ( ($item->{type} || '') !~ m/$self->{valid_types}/ ) {
4378+ PTDEBUG && _d('Invalid type:', $item->{type});
4379+ return;
4380+ }
4381+
4382+ return 1;
4383+}
4384+
4385+sub get_os_version {
4386+ my ($self) = @_;
4387+
4388+ chomp(my $platform = `uname -s`);
4389+ PTDEBUG && _d('platform:', $platform);
4390+ return $OSNAME unless $platform;
4391+
4392+ chomp(my $lsb_release
4393+ = `which lsb_release 2>/dev/null | awk '{print \$1}'` || '');
4394+ PTDEBUG && _d('lsb_release:', $lsb_release);
4395+
4396+ my $release = "";
4397+
4398+ if ( $platform eq 'Linux' ) {
4399+ if ( -f "/etc/fedora-release" ) {
4400+ $release = `cat /etc/fedora-release`;
4401+ }
4402+ elsif ( -f "/etc/redhat-release" ) {
4403+ $release = `cat /etc/redhat-release`;
4404+ }
4405+ elsif ( -f "/etc/system-release" ) {
4406+ $release = `cat /etc/system-release`;
4407+ }
4408+ elsif ( $lsb_release ) {
4409+ $release = `$lsb_release -ds`;
4410+ }
4411+ elsif ( -f "/etc/lsb-release" ) {
4412+ $release = `grep DISTRIB_DESCRIPTION /etc/lsb-release`;
4413+ $release =~ s/^\w+="([^"]+)".+/$1/;
4414+ }
4415+ elsif ( -f "/etc/debian_version" ) {
4416+ chomp(my $rel = `cat /etc/debian_version`);
4417+ $release = "Debian $rel";
4418+ if ( -f "/etc/apt/sources.list" ) {
4419+ chomp(my $code_name = `awk '/^deb/ {print \$3}' /etc/apt/sources.list | awk -F/ '{print \$1}'| awk 'BEGIN {FS="|"} {print \$1}' | sort | uniq -c | sort -rn | head -n1 | awk '{print \$2}'`);
4420+ $release .= " ($code_name)" if $code_name;
4421+ }
4422+ }
4423+ elsif ( `ls /etc/*release 2>/dev/null` ) {
4424+ if ( `grep DISTRIB_DESCRIPTION /etc/*release 2>/dev/null` ) {
4425+ $release = `grep DISTRIB_DESCRIPTION /etc/*release | head -n1`;
4426+ }
4427+ else {
4428+ $release = `cat /etc/*release | head -n1`;
4429+ }
4430+ }
4431+ }
4432+ elsif ( $platform =~ m/^(BSD|Darwin)$/ ) {
4433+ my $rel = `uname -r`;
4434+ $release = "$platform $rel";
4435+ }
4436+ elsif ( $platform eq "SunOS" ) {
4437+ my $rel = `head -n1 /etc/release` || `uname -r`;
4438+ $release = "$platform $rel";
4439+ }
4440+
4441+ if ( !$release ) {
4442+ PTDEBUG && _d('Failed to get the release, using platform');
4443+ $release = $platform;
4444+ }
4445+ chomp($release);
4446+
4447+ PTDEBUG && _d('OS version =', $release);
4448+ return $release;
4449+}
4450+
4451+sub get_perl_version {
4452+ my ($self, %args) = @_;
4453+ my $item = $args{item};
4454+ return unless $item;
4455+
4456+ my $version = sprintf '%vd', $PERL_VERSION;
4457+ PTDEBUG && _d('Perl version', $version);
4458+ return $version;
4459+}
4460+
4461+sub get_perl_module_version {
4462+ my ($self, %args) = @_;
4463+ my $item = $args{item};
4464+ return unless $item;
4465+
4466+ my $var = $item->{item} . '::VERSION';
4467+ my $version = _get_scalar($var);
4468+ PTDEBUG && _d('Perl version for', $var, '=', "$version");
4469+
4470+ return $version ? "$version" : $version;
4471+}
4472+
4473+sub _get_scalar {
4474+ no strict;
4475+ return ${*{shift()}};
4476+}
4477+
4478+sub get_mysql_variable {
4479+ my $self = shift;
4480+ return $self->_get_from_mysql(
4481+ show => 'VARIABLES',
4482+ @_,
4483+ );
4484+}
4485+
4486+
4487+sub _get_from_mysql {
4488+ my ($self, %args) = @_;
4489+ my $show = $args{show};
4490+ my $item = $args{item};
4491+ my $dbh = $args{dbh};
4492+ return unless $show && $item && $dbh;
4493+
4494+ local $dbh->{FetchHashKeyName} = 'NAME_lc';
4495+ my $sql = qq/SHOW $show/;
4496+ PTDEBUG && _d($sql);
4497+ my $rows = $dbh->selectall_hashref($sql, 'variable_name');
4498+
4499+ my @versions;
4500+ foreach my $var ( @{$item->{vars}} ) {
4501+ $var = lc($var);
4502+ my $version = $rows->{$var}->{value};
4503+ PTDEBUG && _d('MySQL version for', $item->{item}, '=', $version);
4504+ push @versions, $version;
4505+ }
4506+
4507+ return join(' ', @versions);
4508+}
4509+
4510+sub get_bin_version {
4511+ my ($self, %args) = @_;
4512+ my $item = $args{item};
4513+ my $cmd = $item->{item};
4514+ return unless $cmd;
4515+
4516+ my $sanitized_command = File::Basename::basename($cmd);
4517+ PTDEBUG && _d('cmd:', $cmd, 'sanitized:', $sanitized_command);
4518+ return if $sanitized_command !~ /\A[a-zA-Z0-9_-]+\z/;
4519+
4520+ my $output = `$sanitized_command --version 2>&1`;
4521+ PTDEBUG && _d('output:', $output);
4522+
4523+ my ($version) = $output =~ /v?([0-9]+\.[0-9]+(?:\.[\w-]+)?)/;
4524+
4525+ PTDEBUG && _d('Version for', $sanitized_command, '=', $version);
4526+ return $version;
4527+}
4528+
4529+sub _d {
4530+ my ($package, undef, $line) = caller 0;
4531+ @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
4532+ map { defined $_ ? $_ : 'undef' }
4533+ @_;
4534+ print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
4535+}
4536+
4537+1;
4538+}
4539+# ###########################################################################
4540+# End VersionCheck package
4541+# ###########################################################################
4542+
4543+# ###########################################################################
4544+# HTTPMicro package
4545+# This package is a copy without comments from the original. The original
4546+# with comments and its test file can be found in the Bazaar repository at,
4547+# lib/HTTPMicro.pm
4548+# t/lib/HTTPMicro.t
4549+# See https://launchpad.net/percona-toolkit for more information.
4550+# ###########################################################################
4551+{
4552+
4553+package HTTPMicro;
4554+BEGIN {
4555+ $HTTPMicro::VERSION = '0.001';
4556+}
4557+use strict;
4558+use warnings;
4559+
4560+use Carp ();
4561+
4562+
4563+my @attributes;
4564+BEGIN {
4565+ @attributes = qw(agent timeout);
4566+ no strict 'refs';
4567+ for my $accessor ( @attributes ) {
4568+ *{$accessor} = sub {
4569+ @_ > 1 ? $_[0]->{$accessor} = $_[1] : $_[0]->{$accessor};
4570+ };
4571+ }
4572+}
4573+
4574+sub new {
4575+ my($class, %args) = @_;
4576+ (my $agent = $class) =~ s{::}{-}g;
4577+ my $self = {
4578+ agent => $agent . "/" . ($class->VERSION || 0),
4579+ timeout => 60,
4580+ };
4581+ for my $key ( @attributes ) {
4582+ $self->{$key} = $args{$key} if exists $args{$key}
4583+ }
4584+ return bless $self, $class;
4585+}
4586+
4587+sub request {
4588+ my ($self, $method, $url, $args) = @_;
4589+ @_ == 3 || (@_ == 4 && ref $args eq 'HASH')
4590+ or Carp::croak(q/Usage: $http->request(METHOD, URL, [HASHREF])/);
4591+ $args ||= {}; # we keep some state in this during _request
4592+
4593+ my $response;
4594+ for ( 0 .. 1 ) {
4595+ $response = eval { $self->_request($method, $url, $args) };
4596+ last unless $@ && $method eq 'GET'
4597+ && $@ =~ m{^(?:Socket closed|Unexpected end)};
4598+ }
4599+
4600+ if (my $e = "$@") {
4601+ $response = {
4602+ success => q{},
4603+ status => 599,
4604+ reason => 'Internal Exception',
4605+ content => $e,
4606+ headers => {
4607+ 'content-type' => 'text/plain',
4608+ 'content-length' => length $e,
4609+ }
4610+ };
4611+ }
4612+ return $response;
4613+}
4614+
4615+sub _request {
4616+ my ($self, $method, $url, $args) = @_;
4617+
4618+ my ($scheme, $host, $port, $path_query) = $self->_split_url($url);
4619+
4620+ my $request = {
4621+ method => $method,
4622+ scheme => $scheme,
4623+ host_port => ($port == 80 ? $host : "$host:$port"),
4624+ uri => $path_query,
4625+ headers => {},
4626+ };
4627+
4628+ my $handle = HTTPMicro::Handle->new(timeout => $self->{timeout});
4629+
4630+ $handle->connect($scheme, $host, $port);
4631+
4632+ $self->_prepare_headers_and_cb($request, $args);
4633+ $handle->write_request_header(@{$request}{qw/method uri headers/});
4634+ $handle->write_content_body($request) if $request->{content};
4635+
4636+ my $response;
4637+ do { $response = $handle->read_response_header }
4638+ until (substr($response->{status},0,1) ne '1');
4639+
4640+ if (!($method eq 'HEAD' || $response->{status} =~ /^[23]04/)) {
4641+ $response->{content} = '';
4642+ $handle->read_content_body(sub { $_[1]->{content} .= $_[0] }, $response);
4643+ }
4644+
4645+ $handle->close;
4646+ $response->{success} = substr($response->{status},0,1) eq '2';
4647+ return $response;
4648+}
4649+
4650+sub _prepare_headers_and_cb {
4651+ my ($self, $request, $args) = @_;
4652+
4653+ for ($args->{headers}) {
4654+ next unless defined;
4655+ while (my ($k, $v) = each %$_) {
4656+ $request->{headers}{lc $k} = $v;
4657+ }
4658+ }
4659+ $request->{headers}{'host'} = $request->{host_port};
4660+ $request->{headers}{'connection'} = "close";
4661+ $request->{headers}{'user-agent'} ||= $self->{agent};
4662+
4663+ if (defined $args->{content}) {
4664+ $request->{headers}{'content-type'} ||= "application/octet-stream";
4665+ utf8::downgrade($args->{content}, 1)
4666+ or Carp::croak(q/Wide character in request message body/);
4667+ $request->{headers}{'content-length'} = length $args->{content};
4668+ $request->{content} = $args->{content};
4669+ }
4670+ return;
4671+}
4672+
4673+sub _split_url {
4674+ my $url = pop;
4675+
4676+ my ($scheme, $authority, $path_query) = $url =~ m<\A([^:/?#]+)://([^/?#]*)([^#]*)>
4677+ or Carp::croak(qq/Cannot parse URL: '$url'/);
4678+
4679+ $scheme = lc $scheme;
4680+ $path_query = "/$path_query" unless $path_query =~ m<\A/>;
4681+
4682+ my $host = (length($authority)) ? lc $authority : 'localhost';
4683+ $host =~ s/\A[^@]*@//; # userinfo
4684+ my $port = do {
4685+ $host =~ s/:([0-9]*)\z// && length $1
4686+ ? $1
4687+ : ($scheme eq 'http' ? 80 : undef);
4688+ };
4689+
4690+ return ($scheme, $host, $port, $path_query);
4691+}
4692+
4693+package
4694+ HTTPMicro::Handle; # hide from PAUSE/indexers
4695+use strict;
4696+use warnings;
4697+
4698+use Carp qw[croak];
4699+use Errno qw[EINTR EPIPE];
4700+use IO::Socket qw[SOCK_STREAM];
4701+
4702+sub BUFSIZE () { 32768 }
4703+
4704+my $Printable = sub {
4705+ local $_ = shift;
4706+ s/\r/\\r/g;
4707+ s/\n/\\n/g;
4708+ s/\t/\\t/g;
4709+ s/([^\x20-\x7E])/sprintf('\\x%.2X', ord($1))/ge;
4710+ $_;
4711+};
4712+
4713+sub new {
4714+ my ($class, %args) = @_;
4715+ return bless {
4716+ rbuf => '',
4717+ timeout => 60,
4718+ max_line_size => 16384,
4719+ %args
4720+ }, $class;
4721+}
4722+
4723+sub connect {
4724+ @_ == 4 || croak(q/Usage: $handle->connect(scheme, host, port)/);
4725+ my ($self, $scheme, $host, $port) = @_;
4726+
4727+ if ( $scheme ne 'http' ) {
4728+ croak(qq/Unsupported URL scheme '$scheme'/);
4729+ }
4730+
4731+ $self->{fh} = 'IO::Socket::INET'->new(
4732+ PeerHost => $host,
4733+ PeerPort => $port,
4734+ Proto => 'tcp',
4735+ Type => SOCK_STREAM,
4736+ Timeout => $self->{timeout}
4737+ ) or croak(qq/Could not connect to '$host:$port': $@/);
4738+
4739+ binmode($self->{fh})
4740+ or croak(qq/Could not binmode() socket: '$!'/);
4741+
4742+ $self->{host} = $host;
4743+ $self->{port} = $port;
4744+
4745+ return $self;
4746+}
4747+
4748+sub close {
4749+ @_ == 1 || croak(q/Usage: $handle->close()/);
4750+ my ($self) = @_;
4751+ CORE::close($self->{fh})
4752+ or croak(qq/Could not close socket: '$!'/);
4753+}
4754+
4755+sub write {
4756+ @_ == 2 || croak(q/Usage: $handle->write(buf)/);
4757+ my ($self, $buf) = @_;
4758+
4759+ my $len = length $buf;
4760+ my $off = 0;
4761+
4762+ local $SIG{PIPE} = 'IGNORE';
4763+
4764+ while () {
4765+ $self->can_write
4766+ or croak(q/Timed out while waiting for socket to become ready for writing/);
4767+ my $r = syswrite($self->{fh}, $buf, $len, $off);
4768+ if (defined $r) {
4769+ $len -= $r;
4770+ $off += $r;
4771+ last unless $len > 0;
4772+ }
4773+ elsif ($! == EPIPE) {
4774+ croak(qq/Socket closed by remote server: $!/);
4775+ }
4776+ elsif ($! != EINTR) {
4777+ croak(qq/Could not write to socket: '$!'/);
4778+ }
4779+ }
4780+ return $off;
4781+}
4782+
4783+sub read {
4784+ @_ == 2 || @_ == 3 || croak(q/Usage: $handle->read(len)/);
4785+ my ($self, $len) = @_;
4786+
4787+ my $buf = '';
4788+ my $got = length $self->{rbuf};
4789+
4790+ if ($got) {
4791+ my $take = ($got < $len) ? $got : $len;
4792+ $buf = substr($self->{rbuf}, 0, $take, '');
4793+ $len -= $take;
4794+ }
4795+
4796+ while ($len > 0) {
4797+ $self->can_read
4798+ or croak(q/Timed out while waiting for socket to become ready for reading/);
4799+ my $r = sysread($self->{fh}, $buf, $len, length $buf);
4800+ if (defined $r) {
4801+ last unless $r;
4802+ $len -= $r;
4803+ }
4804+ elsif ($! != EINTR) {
4805+ croak(qq/Could not read from socket: '$!'/);
4806+ }
4807+ }
4808+ if ($len) {
4809+ croak(q/Unexpected end of stream/);
4810+ }
4811+ return $buf;
4812+}
4813+
4814+sub readline {
4815+ @_ == 1 || croak(q/Usage: $handle->readline()/);
4816+ my ($self) = @_;
4817+
4818+ while () {
4819+ if ($self->{rbuf} =~ s/\A ([^\x0D\x0A]* \x0D?\x0A)//x) {
4820+ return $1;
4821+ }
4822+ $self->can_read
4823+ or croak(q/Timed out while waiting for socket to become ready for reading/);
4824+ my $r = sysread($self->{fh}, $self->{rbuf}, BUFSIZE, length $self->{rbuf});
4825+ if (defined $r) {
4826+ last unless $r;
4827+ }
4828+ elsif ($! != EINTR) {
4829+ croak(qq/Could not read from socket: '$!'/);
4830+ }
4831+ }
4832+ croak(q/Unexpected end of stream while looking for line/);
4833+}
4834+
4835+sub read_header_lines {
4836+ @_ == 1 || @_ == 2 || croak(q/Usage: $handle->read_header_lines([headers])/);
4837+ my ($self, $headers) = @_;
4838+ $headers ||= {};
4839+ my $lines = 0;
4840+ my $val;
4841+
4842+ while () {
4843+ my $line = $self->readline;
4844+
4845+ if ($line =~ /\A ([^\x00-\x1F\x7F:]+) : [\x09\x20]* ([^\x0D\x0A]*)/x) {
4846+ my ($field_name) = lc $1;
4847+ $val = \($headers->{$field_name} = $2);
4848+ }
4849+ elsif ($line =~ /\A [\x09\x20]+ ([^\x0D\x0A]*)/x) {
4850+ $val
4851+ or croak(q/Unexpected header continuation line/);
4852+ next unless length $1;
4853+ $$val .= ' ' if length $$val;
4854+ $$val .= $1;
4855+ }
4856+ elsif ($line =~ /\A \x0D?\x0A \z/x) {
4857+ last;
4858+ }
4859+ else {
4860+ croak(q/Malformed header line: / . $Printable->($line));
4861+ }
4862+ }
4863+ return $headers;
4864+}
4865+
4866+sub write_header_lines {
4867+ (@_ == 2 && ref $_[1] eq 'HASH') || croak(q/Usage: $handle->write_header_lines(headers)/);
4868+ my($self, $headers) = @_;
4869+
4870+ my $buf = '';
4871+ while (my ($k, $v) = each %$headers) {
4872+ my $field_name = lc $k;
4873+ $field_name =~ /\A [\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7A\x7C\x7E]+ \z/x
4874+ or croak(q/Invalid HTTP header field name: / . $Printable->($field_name));
4875+ $field_name =~ s/\b(\w)/\u$1/g;
4876+ $buf .= "$field_name: $v\x0D\x0A";
4877+ }
4878+ $buf .= "\x0D\x0A";
4879+ return $self->write($buf);
4880+}
4881+
4882+sub read_content_body {
4883+ @_ == 3 || @_ == 4 || croak(q/Usage: $handle->read_content_body(callback, response, [read_length])/);
4884+ my ($self, $cb, $response, $len) = @_;
4885+ $len ||= $response->{headers}{'content-length'};
4886+
4887+ croak("No content-length in the returned response, and this "
4888+ . "UA doesn't implement chunking") unless defined $len;
4889+
4890+ while ($len > 0) {
4891+ my $read = ($len > BUFSIZE) ? BUFSIZE : $len;
4892+ $cb->($self->read($read), $response);
4893+ $len -= $read;
4894+ }
4895+
4896+ return;
4897+}
4898+
4899+sub write_content_body {
4900+ @_ == 2 || croak(q/Usage: $handle->write_content_body(request)/);
4901+ my ($self, $request) = @_;
4902+ my ($len, $content_length) = (0, $request->{headers}{'content-length'});
4903+
4904+ $len += $self->write($request->{content});
4905+
4906+ $len == $content_length
4907+ or croak(qq/Content-Length missmatch (got: $len expected: $content_length)/);
4908+
4909+ return $len;
4910+}
4911+
4912+sub read_response_header {
4913+ @_ == 1 || croak(q/Usage: $handle->read_response_header()/);
4914+ my ($self) = @_;
4915+
4916+ my $line = $self->readline;
4917+
4918+ $line =~ /\A (HTTP\/(0*\d+\.0*\d+)) [\x09\x20]+ ([0-9]{3}) [\x09\x20]+ ([^\x0D\x0A]*) \x0D?\x0A/x
4919+ or croak(q/Malformed Status-Line: / . $Printable->($line));
4920+
4921+ my ($protocol, $version, $status, $reason) = ($1, $2, $3, $4);
4922+
4923+ return {
4924+ status => $status,
4925+ reason => $reason,
4926+ headers => $self->read_header_lines,
4927+ protocol => $protocol,
4928+ };
4929+}
4930+
4931+sub write_request_header {
4932+ @_ == 4 || croak(q/Usage: $handle->write_request_header(method, request_uri, headers)/);
4933+ my ($self, $method, $request_uri, $headers) = @_;
4934+
4935+ return $self->write("$method $request_uri HTTP/1.1\x0D\x0A")
4936+ + $self->write_header_lines($headers);
4937+}
4938+
4939+sub _do_timeout {
4940+ my ($self, $type, $timeout) = @_;
4941+ $timeout = $self->{timeout}
4942+ unless defined $timeout && $timeout >= 0;
4943+
4944+ my $fd = fileno $self->{fh};
4945+ defined $fd && $fd >= 0
4946+ or croak(q/select(2): 'Bad file descriptor'/);
4947+
4948+ my $initial = time;
4949+ my $pending = $timeout;
4950+ my $nfound;
4951+
4952+ vec(my $fdset = '', $fd, 1) = 1;
4953+
4954+ while () {
4955+ $nfound = ($type eq 'read')
4956+ ? select($fdset, undef, undef, $pending)
4957+ : select(undef, $fdset, undef, $pending) ;
4958+ if ($nfound == -1) {
4959+ $! == EINTR
4960+ or croak(qq/select(2): '$!'/);
4961+ redo if !$timeout || ($pending = $timeout - (time - $initial)) > 0;
4962+ $nfound = 0;
4963+ }
4964+ last;
4965+ }
4966+ $! = 0;
4967+ return $nfound;
4968+}
4969+
4970+sub can_read {
4971+ @_ == 1 || @_ == 2 || croak(q/Usage: $handle->can_read([timeout])/);
4972+ my $self = shift;
4973+ return $self->_do_timeout('read', @_)
4974+}
4975+
4976+sub can_write {
4977+ @_ == 1 || @_ == 2 || croak(q/Usage: $handle->can_write([timeout])/);
4978+ my $self = shift;
4979+ return $self->_do_timeout('write', @_)
4980+}
4981+
4982+1;
4983+}
4984+# ###########################################################################
4985+# End HTTPMicro package
4986+# ###########################################################################
4987+
4988+# ###########################################################################
4989+# Pingback package
4990+# This package is a copy without comments from the original. The original
4991+# with comments and its test file can be found in the Bazaar repository at,
4992+# lib/Pingback.pm
4993+# t/lib/Pingback.t
4994+# See https://launchpad.net/percona-toolkit for more information.
4995+# ###########################################################################
4996+{
4997+package Pingback;
4998+
4999+use strict;
5000+use warnings FATAL => 'all';
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches