Merge lp:~percona-toolkit-dev/percona-toolkit/pt-fke-logger-2.2 into lp:percona-toolkit/2.2

Proposed by Daniel Nichter
Status: Merged
Approved by: Daniel Nichter
Approved revision: 539
Merged at revision: 534
Proposed branch: lp:~percona-toolkit-dev/percona-toolkit/pt-fke-logger-2.2
Merge into: lp:percona-toolkit/2.2
Diff against target: 3998 lines (+1891/-858)
35 files modified
bin/pt-archiver (+5/-4)
bin/pt-config-diff (+53/-34)
bin/pt-deadlock-logger (+734/-334)
bin/pt-duplicate-key-checker (+5/-8)
bin/pt-fifo-split (+5/-8)
bin/pt-find (+5/-8)
bin/pt-fk-error-logger (+489/-131)
bin/pt-heartbeat (+5/-4)
bin/pt-kill (+61/-21)
bin/pt-online-schema-change (+51/-21)
bin/pt-query-advisor (+5/-5)
bin/pt-query-digest (+5/-5)
bin/pt-show-grants (+5/-8)
bin/pt-slave-delay (+5/-5)
bin/pt-slave-find (+5/-8)
bin/pt-slave-restart (+5/-5)
bin/pt-stalk (+5/-1)
bin/pt-table-checksum (+51/-25)
bin/pt-table-sync (+5/-8)
bin/pt-table-usage (+5/-5)
bin/pt-upgrade (+5/-5)
bin/pt-variable-advisor (+5/-5)
bin/pt-visual-explain (+5/-8)
docs/percona-toolkit.pod (+28/-0)
lib/Cxn.pm (+48/-17)
lib/Runtime.pm (+23/-36)
t/lib/Cxn.t (+58/-2)
t/pt-deadlock-logger/basics.t (+54/-42)
t/pt-deadlock-logger/clear_deadlocks.t (+2/-5)
t/pt-deadlock-logger/create_dest_table.t (+12/-9)
t/pt-deadlock-logger/option_sanity.t (+1/-1)
t/pt-deadlock-logger/standard_options.t (+73/-38)
t/pt-fk-error-logger/basics.t (+66/-8)
t/pt-fk-error-logger/get_fk_error.t (+2/-2)
t/pt-fk-error-logger/standard_options.t (+0/-32)
To merge this branch: bzr merge lp:~percona-toolkit-dev/percona-toolkit/pt-fke-logger-2.2
Reviewer Review Type Date Requested Status
Brian Fraser (community) Approve
Daniel Nichter Approve
Review via email: mp+150704@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Daniel Nichter (daniel-nichter) wrote :
Revision history for this message
Daniel Nichter (daniel-nichter) :
review: Approve
Revision history for this message
Brian Fraser (fraserbn) :
review: Approve
539. By Daniel Nichter

Don't manually re-set AutoCommit. Use =for comment instead of inline MAGIC.

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 2013-02-22 17:47:57 +0000
3+++ bin/pt-archiver 2013-02-27 23:41:26 +0000
4@@ -6958,10 +6958,11 @@
5
6 type: string
7
8-Create the given PID file when daemonized. The file contains the process ID of
9-the daemonized instance. The PID file is removed when the daemonized instance
10-exits. The program checks for the existence of the PID file when starting; if
11-it exists and the process with the matching PID exists, the program exits.
12+Create the given PID file. The tool won't start if the PID file already
13+exists and the PID it contains is different than the current PID. However,
14+if the PID file exists and the PID it contains is no longer running, the
15+tool will overwrite the PID file with the current PID. The PID file is
16+removed automatically when the tool exits.
17
18 =item --plugin
19
20
21=== modified file 'bin/pt-config-diff'
22--- bin/pt-config-diff 2013-02-22 15:00:55 +0000
23+++ bin/pt-config-diff 2013-02-27 23:41:26 +0000
24@@ -2138,23 +2138,24 @@
25 }
26
27 my $self = {
28- dsn => $dsn,
29- dbh => $args{dbh},
30- dsn_name => $dp->as_string($dsn, [qw(h P S)]),
31- hostname => '',
32- set => $args{set},
33- NAME_lc => defined($args{NAME_lc}) ? $args{NAME_lc} : 1,
34- dbh_set => 0,
35- OptionParser => $o,
36- DSNParser => $dp,
37+ dsn => $dsn,
38+ dbh => $args{dbh},
39+ dsn_name => $dp->as_string($dsn, [qw(h P S)]),
40+ hostname => '',
41+ set => $args{set},
42+ NAME_lc => defined($args{NAME_lc}) ? $args{NAME_lc} : 1,
43+ dbh_set => 0,
44+ OptionParser => $o,
45+ DSNParser => $dp,
46 is_cluster_node => undef,
47+ parent => $args{parent},
48 };
49
50 return bless $self, $class;
51 }
52
53 sub connect {
54- my ( $self ) = @_;
55+ my ( $self, %opts ) = @_;
56 my $dsn = $self->{dsn};
57 my $dp = $self->{DSNParser};
58 my $o = $self->{OptionParser};
59@@ -2165,11 +2166,18 @@
60 $dsn->{p} = OptionParser::prompt_noecho("Enter MySQL password: ");
61 $self->{asked_for_pass} = 1;
62 }
63- $dbh = $dp->get_dbh($dp->get_cxn_params($dsn), { AutoCommit => 1 });
64+ $dbh = $dp->get_dbh(
65+ $dp->get_cxn_params($dsn),
66+ {
67+ AutoCommit => 1,
68+ %opts,
69+ },
70+ );
71 }
72- PTDEBUG && _d($dbh, 'Connected dbh to', $self->{name});
73
74- return $self->set_dbh($dbh);
75+ $dbh = $self->set_dbh($dbh);
76+ PTDEBUG && _d($dbh, 'Connected dbh to', $self->{hostname},$self->{dsn_name});
77+ return $dbh;
78 }
79
80 sub set_dbh {
81@@ -2192,6 +2200,11 @@
82 $self->{hostname} = $hostname;
83 }
84
85+ if ( $self->{parent} ) {
86+ PTDEBUG && _d($dbh, 'Setting InactiveDestroy=1 in parent');
87+ $dbh->{InactiveDestroy} = 1;
88+ }
89+
90 if ( my $set = $self->{set}) {
91 $set->($dbh);
92 }
93@@ -2201,6 +2214,13 @@
94 return $dbh;
95 }
96
97+sub lost_connection {
98+ my ($self, $e) = @_;
99+ return 0 unless $e;
100+ return $e =~ m/MySQL server has gone away/
101+ || $e =~ m/Lost connection to MySQL server/;
102+}
103+
104 sub dbh {
105 my ($self) = @_;
106 return $self->{dbh};
107@@ -2219,12 +2239,21 @@
108
109 sub DESTROY {
110 my ($self) = @_;
111- if ( $self->{dbh}
112- && blessed($self->{dbh})
113- && $self->{dbh}->can("disconnect") ) {
114- PTDEBUG && _d('Disconnecting dbh', $self->{dbh}, $self->{name});
115+
116+ PTDEBUG && _d('Destroying cxn');
117+
118+ if ( $self->{parent} ) {
119+ PTDEBUG && _d($self->{dbh}, 'Not disconnecting dbh in parent');
120+ }
121+ elsif ( $self->{dbh}
122+ && blessed($self->{dbh})
123+ && $self->{dbh}->can("disconnect") )
124+ {
125+ PTDEBUG && _d($self->{dbh}, 'Disconnecting dbh on', $self->{hostname},
126+ $self->{dsn_name});
127 $self->{dbh}->disconnect();
128 }
129+
130 return;
131 }
132
133@@ -5022,12 +5051,7 @@
134 # Daemonize now that everything is setup and ready to work.
135 # ########################################################################
136 my $daemon;
137- if ( $o->get('daemonize') ) {
138- $daemon = new Daemon(o=>$o);
139- $daemon->daemonize();
140- PTDEBUG && _d('I am a daemon now');
141- }
142- elsif ( $o->get('pid') ) {
143+ if ( $o->get('pid') ) {
144 # We're not daemoninzing, it just handles PID stuff.
145 $daemon = new Daemon(o=>$o);
146 $daemon->make_PID_file();
147@@ -5118,7 +5142,7 @@
148
149 =head1 SYNOPSIS
150
151-Usage: pt-config-diff [OPTION...] CONFIG CONFIG [CONFIG...]
152+Usage: pt-config-diff [OPTIONS] CONFIG CONFIG [CONFIG...]
153
154 pt-config-diff diffs MySQL configuration files and server variables.
155 CONFIG can be a filename or a DSN. At least two CONFIG sources must be given.
156@@ -5238,11 +5262,6 @@
157 first option on the command line. (This option does not specify a CONFIG;
158 it's equivalent to C<--defaults-file>.)
159
160-=item --daemonize
161-
162-Fork to the background and detach from the shell. POSIX
163-operating systems only.
164-
165 =item --defaults-file
166
167 short form: -F; type: string
168@@ -5276,11 +5295,11 @@
169
170 type: string
171
172-Create the given PID file when daemonized. The file contains the process
173-ID of the daemonized instance. The PID file is removed when the
174-daemonized instance exits. The program checks for the existence of the
175-PID file when starting; if it exists and the process with the matching PID
176-exists, the program exits.
177+Create the given PID file. The tool won't start if the PID file already
178+exists and the PID it contains is different than the current PID. However,
179+if the PID file exists and the PID it contains is no longer running, the
180+tool will overwrite the PID file with the current PID. The PID file is
181+removed automatically when the tool exits.
182
183 =item --port
184
185
186=== modified file 'bin/pt-deadlock-logger'
187--- bin/pt-deadlock-logger 2013-02-22 17:47:57 +0000
188+++ bin/pt-deadlock-logger 2013-02-27 23:41:26 +0000
189@@ -23,9 +23,11 @@
190 VersionParser
191 Quoter
192 DSNParser
193+ Cxn
194 Daemon
195 HTTPMicro
196 VersionCheck
197+ Runtime
198 ));
199 }
200
201@@ -2439,6 +2441,181 @@
202 # ###########################################################################
203
204 # ###########################################################################
205+# Cxn package
206+# This package is a copy without comments from the original. The original
207+# with comments and its test file can be found in the Bazaar repository at,
208+# lib/Cxn.pm
209+# t/lib/Cxn.t
210+# See https://launchpad.net/percona-toolkit for more information.
211+# ###########################################################################
212+{
213+package Cxn;
214+
215+use strict;
216+use warnings FATAL => 'all';
217+use English qw(-no_match_vars);
218+use Scalar::Util qw(blessed);
219+use constant {
220+ PTDEBUG => $ENV{PTDEBUG} || 0,
221+ PERCONA_TOOLKIT_TEST_USE_DSN_NAMES => $ENV{PERCONA_TOOLKIT_TEST_USE_DSN_NAMES} || 0,
222+};
223+
224+sub new {
225+ my ( $class, %args ) = @_;
226+ my @required_args = qw(DSNParser OptionParser);
227+ foreach my $arg ( @required_args ) {
228+ die "I need a $arg argument" unless $args{$arg};
229+ };
230+ my ($dp, $o) = @args{@required_args};
231+
232+ my $dsn_defaults = $dp->parse_options($o);
233+ my $prev_dsn = $args{prev_dsn};
234+ my $dsn = $args{dsn};
235+ if ( !$dsn ) {
236+ $args{dsn_string} ||= 'h=' . ($dsn_defaults->{h} || 'localhost');
237+
238+ $dsn = $dp->parse(
239+ $args{dsn_string}, $prev_dsn, $dsn_defaults);
240+ }
241+ elsif ( $prev_dsn ) {
242+ $dsn = $dp->copy($prev_dsn, $dsn);
243+ }
244+
245+ my $self = {
246+ dsn => $dsn,
247+ dbh => $args{dbh},
248+ dsn_name => $dp->as_string($dsn, [qw(h P S)]),
249+ hostname => '',
250+ set => $args{set},
251+ NAME_lc => defined($args{NAME_lc}) ? $args{NAME_lc} : 1,
252+ dbh_set => 0,
253+ OptionParser => $o,
254+ DSNParser => $dp,
255+ is_cluster_node => undef,
256+ parent => $args{parent},
257+ };
258+
259+ return bless $self, $class;
260+}
261+
262+sub connect {
263+ my ( $self, %opts ) = @_;
264+ my $dsn = $self->{dsn};
265+ my $dp = $self->{DSNParser};
266+ my $o = $self->{OptionParser};
267+
268+ my $dbh = $self->{dbh};
269+ if ( !$dbh || !$dbh->ping() ) {
270+ if ( $o->get('ask-pass') && !$self->{asked_for_pass} ) {
271+ $dsn->{p} = OptionParser::prompt_noecho("Enter MySQL password: ");
272+ $self->{asked_for_pass} = 1;
273+ }
274+ $dbh = $dp->get_dbh(
275+ $dp->get_cxn_params($dsn),
276+ {
277+ AutoCommit => 1,
278+ %opts,
279+ },
280+ );
281+ }
282+
283+ $dbh = $self->set_dbh($dbh);
284+ PTDEBUG && _d($dbh, 'Connected dbh to', $self->{hostname},$self->{dsn_name});
285+ return $dbh;
286+}
287+
288+sub set_dbh {
289+ my ($self, $dbh) = @_;
290+
291+ if ( $self->{dbh} && $self->{dbh} == $dbh && $self->{dbh_set} ) {
292+ PTDEBUG && _d($dbh, 'Already set dbh');
293+ return $dbh;
294+ }
295+
296+ PTDEBUG && _d($dbh, 'Setting dbh');
297+
298+ $dbh->{FetchHashKeyName} = 'NAME_lc' if $self->{NAME_lc};
299+
300+ my $sql = 'SELECT @@hostname, @@server_id';
301+ PTDEBUG && _d($dbh, $sql);
302+ my ($hostname, $server_id) = $dbh->selectrow_array($sql);
303+ PTDEBUG && _d($dbh, 'hostname:', $hostname, $server_id);
304+ if ( $hostname ) {
305+ $self->{hostname} = $hostname;
306+ }
307+
308+ if ( $self->{parent} ) {
309+ PTDEBUG && _d($dbh, 'Setting InactiveDestroy=1 in parent');
310+ $dbh->{InactiveDestroy} = 1;
311+ }
312+
313+ if ( my $set = $self->{set}) {
314+ $set->($dbh);
315+ }
316+
317+ $self->{dbh} = $dbh;
318+ $self->{dbh_set} = 1;
319+ return $dbh;
320+}
321+
322+sub lost_connection {
323+ my ($self, $e) = @_;
324+ return 0 unless $e;
325+ return $e =~ m/MySQL server has gone away/
326+ || $e =~ m/Lost connection to MySQL server/;
327+}
328+
329+sub dbh {
330+ my ($self) = @_;
331+ return $self->{dbh};
332+}
333+
334+sub dsn {
335+ my ($self) = @_;
336+ return $self->{dsn};
337+}
338+
339+sub name {
340+ my ($self) = @_;
341+ return $self->{dsn_name} if PERCONA_TOOLKIT_TEST_USE_DSN_NAMES;
342+ return $self->{hostname} || $self->{dsn_name} || 'unknown host';
343+}
344+
345+sub DESTROY {
346+ my ($self) = @_;
347+
348+ PTDEBUG && _d('Destroying cxn');
349+
350+ if ( $self->{parent} ) {
351+ PTDEBUG && _d($self->{dbh}, 'Not disconnecting dbh in parent');
352+ }
353+ elsif ( $self->{dbh}
354+ && blessed($self->{dbh})
355+ && $self->{dbh}->can("disconnect") )
356+ {
357+ PTDEBUG && _d($self->{dbh}, 'Disconnecting dbh on', $self->{hostname},
358+ $self->{dsn_name});
359+ $self->{dbh}->disconnect();
360+ }
361+
362+ return;
363+}
364+
365+sub _d {
366+ my ($package, undef, $line) = caller 0;
367+ @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
368+ map { defined $_ ? $_ : 'undef' }
369+ @_;
370+ print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
371+}
372+
373+1;
374+}
375+# ###########################################################################
376+# End Cxn package
377+# ###########################################################################
378+
379+# ###########################################################################
380 # Daemon package
381 # This package is a copy without comments from the original. The original
382 # with comments and its test file can be found in the Bazaar repository at,
383@@ -3865,6 +4042,139 @@
384 # ###########################################################################
385
386 # ###########################################################################
387+# Runtime package
388+# This package is a copy without comments from the original. The original
389+# with comments and its test file can be found in the Bazaar repository at,
390+# lib/Runtime.pm
391+# t/lib/Runtime.t
392+# See https://launchpad.net/percona-toolkit for more information.
393+# ###########################################################################
394+{
395+package Runtime;
396+
397+use strict;
398+use warnings FATAL => 'all';
399+use English qw(-no_match_vars);
400+use constant PTDEBUG => $ENV{PTDEBUG} || 0;
401+
402+sub new {
403+ my ( $class, %args ) = @_;
404+ my @required_args = qw(run_time now);
405+ foreach my $arg ( @required_args ) {
406+ die "I need a $arg argument" unless exists $args{$arg};
407+ }
408+
409+ my $run_time = $args{run_time};
410+ if ( defined $run_time ) {
411+ die "run_time must be > 0" if $run_time <= 0;
412+ }
413+
414+ my $now = $args{now};
415+ die "now must be a callback" unless ref $now eq 'CODE';
416+
417+ my $self = {
418+ run_time => $run_time,
419+ now => $now,
420+ start_time => undef,
421+ end_time => undef,
422+ time_left => undef,
423+ stop => 0,
424+ };
425+
426+ return bless $self, $class;
427+}
428+
429+sub time_left {
430+ my ( $self, %args ) = @_;
431+
432+ if ( $self->{stop} ) {
433+ PTDEBUG && _d("No time left because stop was called");
434+ return 0;
435+ }
436+
437+ my $now = $self->{now}->(%args);
438+ PTDEBUG && _d("Current time:", $now);
439+
440+ if ( !defined $self->{start_time} ) {
441+ $self->{start_time} = $now;
442+ }
443+
444+ return unless defined $now;
445+
446+ my $run_time = $self->{run_time};
447+ return unless defined $run_time;
448+
449+ if ( !$self->{end_time} ) {
450+ $self->{end_time} = $now + $run_time;
451+ PTDEBUG && _d("End time:", $self->{end_time});
452+ }
453+
454+ $self->{time_left} = $self->{end_time} - $now;
455+ PTDEBUG && _d("Time left:", $self->{time_left});
456+ return $self->{time_left};
457+}
458+
459+sub have_time {
460+ my ( $self, %args ) = @_;
461+ my $time_left = $self->time_left(%args);
462+ return 1 if !defined $time_left; # run forever
463+ return $time_left <= 0 ? 0 : 1; # <=0s means run time has elapsed
464+}
465+
466+sub time_elapsed {
467+ my ( $self, %args ) = @_;
468+
469+ my $start_time = $self->{start_time};
470+ return 0 unless $start_time;
471+
472+ my $now = $self->{now}->(%args);
473+ PTDEBUG && _d("Current time:", $now);
474+
475+ my $time_elapsed = $now - $start_time;
476+ PTDEBUG && _d("Time elapsed:", $time_elapsed);
477+ if ( $time_elapsed < 0 ) {
478+ warn "Current time $now is earlier than start time $start_time";
479+ }
480+ return $time_elapsed;
481+}
482+
483+sub reset {
484+ my ( $self ) = @_;
485+ $self->{start_time} = undef;
486+ $self->{end_time} = undef;
487+ $self->{time_left} = undef;
488+ $self->{stop} = 0;
489+ PTDEBUG && _d("Reset run time");
490+ return;
491+}
492+
493+sub stop {
494+ my ( $self ) = @_;
495+ $self->{stop} = 1;
496+ return;
497+}
498+
499+sub start {
500+ my ( $self ) = @_;
501+ $self->{stop} = 0;
502+ return;
503+}
504+
505+sub _d {
506+ my ($package, undef, $line) = caller 0;
507+ @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
508+ map { defined $_ ? $_ : 'undef' }
509+ @_;
510+ print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
511+}
512+
513+1;
514+}
515+# ###########################################################################
516+# End Runtime package
517+# ###########################################################################
518+
519+# ###########################################################################
520 # This is a combination of modules and programs in one -- a runnable module.
521 # http://www.perl.com/pub/a/2006/07/13/lightning-articles.html?page=last
522 # Or, look it up in the Camel book on pages 642 and 643 in the 3rd edition.
523@@ -3877,21 +4187,15 @@
524 use English qw(-no_match_vars);
525 use List::Util qw(max);
526 use Socket qw(inet_aton);
527-use sigtrap qw(handler finish untrapped normal-signals);
528+use Time::HiRes qw(sleep);
529+use File::Temp qw(tempfile);
530+use File::Spec;
531+
532+use sigtrap 'handler', \&sig_int, 'normal-signals';
533
534 use Percona::Toolkit;
535 use constant PTDEBUG => $ENV{PTDEBUG} || 0;
536
537-my $o;
538-my $oktorun;
539-my $dp;
540-
541-# ########################################################################
542-# Configuration info.
543-# ########################################################################
544-my $source_dsn;
545-my $dest_dsn;
546-
547 # Some common patterns and variables
548 my $d = qr/(\d+)/; # Digit
549 my $t = qr/((?:\d+ \d+)|(?:[A-Fa-f0-9]+))/; # Transaction ID
550@@ -3934,48 +4238,54 @@
551 'update' => 1,
552 );
553
554+my $oktorun = 1;
555+my $exit_status = 0;
556+
557 sub main {
558- local @ARGV = @_; # set global ARGV for this package
559-
560- my $q = new Quoter();
561+ local @ARGV = @_; # set global ARGV for this package
562+ $oktorun = 1;
563+ $exit_status = 0;
564
565 # ########################################################################
566 # Get configuration information.
567 # ########################################################################
568- $o = new OptionParser();
569+ my $o = new OptionParser();
570 $o->get_specs();
571 $o->get_opts();
572
573- $o->set('collapse', $o->get('print')) unless $o->got('collapse');
574-
575- $dp = $o->DSNParser();
576- my $dsn_defaults = $dp->parse_options($o);
577- $source_dsn = @ARGV ? $dp->parse(shift @ARGV,$dsn_defaults) : $dsn_defaults;
578- $dest_dsn = $o->get('dest');
579-
580- # The source dsn is not an option so --dest cannot use OptionParser
581- # to inherit values from it. Thus, we do it manually. --dest will
582- # inherit from --user, --port, etc.
583- if ( $source_dsn && $dest_dsn ) {
584- # If dest DSN only has D and t, this will copy h, P, S, etc.
585- # from the source DSN.
586- $dest_dsn = $dp->copy($source_dsn, $dest_dsn);
587+ my $dp = $o->DSNParser();
588+ $dp->prop('set-vars', $o->get('set-vars'));
589+
590+ my $src;
591+ if ( my $src_dsn_string = shift @ARGV ) {
592+ $src = Cxn->new(
593+ dsn_string => $src_dsn_string,
594+ parent => $o->get('daemonize'),
595+ DSNParser => $dp,
596+ OptionParser => $o,
597+ );
598+ }
599+
600+ my $dst;
601+ if ( my $dst_dsn = $o->get('dest') ) {
602+ $dst = Cxn->new(
603+ dsn => $dst_dsn,
604+ prev_dsn => ($src ? $src->dsn : undef),
605+ parent => $o->get('daemonize'),
606+ DSNParser => $dp,
607+ OptionParser => $o,
608+ );
609 }
610
611 if ( !$o->get('help') ) {
612- if ( !$source_dsn ) {
613- $o->save_error('Missing or invalid source host');
614- }
615- if ( $dest_dsn && !$dest_dsn->{D} ) {
616- $o->save_error("--dest requires a 'D' (database) part");
617- }
618- if ( $dest_dsn && !$dest_dsn->{t} ) {
619- $o->save_error("--dest requires a 't' (table) part");
620- }
621-
622- # Avoid running forever with zero second interval.
623- if ( $o->get('run-time') && !$o->get('interval') ) {
624- $o->set('interval', 1);
625+ if ( !$src ) {
626+ $o->save_error('No DSN was specified.');
627+ }
628+ if ( $dst && !$dst->dsn->{D} ) {
629+ $o->save_error("--dest requires a 'D' (database) part.");
630+ }
631+ if ( $dst && !$dst->dsn->{t} ) {
632+ $o->save_error("--dest requires a 't' (table) part.");
633 }
634 }
635
636@@ -3984,43 +4294,29 @@
637 # ########################################################################
638 # Connect to MySQL and set up the --dest, if any.
639 # ########################################################################
640- my $dbh = get_cxn($source_dsn, 1);
641- my $dest_dbh;
642- my $sth;
643+ my $q = new Quoter();
644+
645+ $src->connect();
646+
647+ my @cols = @{ $o->get('columns') };
648 my $ins_sth;
649-
650- # Since the user might not have specified a hostname for the connection,
651- # try to extract it from the $dbh
652- if ( !$source_dsn->{h} ) {
653- ($source_dsn->{h}) = $dbh->{mysql_hostinfo} =~ m/(\w+) via/;
654- PTDEBUG && _d('Got source host from dbh:', $source_dsn->{h});
655- }
656-
657- my @cols = qw( server ts thread txn_id txn_time user hostname ip db tbl idx
658- lock_type lock_mode wait_hold victim query );
659- if ( $o->got('columns') ) {
660- @cols = grep { $o->get('columns')->{$_} } @cols;
661- }
662-
663- if ( $dest_dsn ) {
664- my $db_tbl =
665- join('.',
666- map { $q->quote($_) }
667- grep { $_ }
668- ( $dest_dsn->{D}, $dest_dsn->{t} ));
669- $dest_dbh = get_cxn($dest_dsn, 0);
670+ my $ins_sql;
671+ if ( $dst ) {
672+ $dst->connect(AutoCommit => 0);
673+
674+ my $db_tbl = $q->join_quote($dst->dsn->{D}, $dst->dsn->{t});
675 my $cols = join(',', map { $q->quote($_) } @cols);
676- my $parms = join(',', map { '?' } @cols);
677- my $sql = "INSERT IGNORE INTO $db_tbl($cols) VALUES($parms)";
678- PTDEBUG && _d($sql);
679- $ins_sth = $dest_dbh->prepare($sql);
680+ my $parms = join(',', map { '?' } @cols);
681+ $ins_sql = "INSERT IGNORE INTO $db_tbl ($cols) VALUES ($parms) "
682+ . "/* pt-deadlock-logger */";
683+ PTDEBUG && _d($ins_sql);
684+ $ins_sth = $dst->dbh->prepare($ins_sql);
685
686 if ( $o->get('create-dest-table') ) {
687- my $db_tbl = $q->quote($dest_dsn->{D}, $dest_dsn->{t});
688- $sql = $o->read_para_after(__FILE__, qr/MAGIC_dest_table/);
689- $sql =~ s/deadlocks/IF NOT EXISTS $db_tbl/;
690+ my $sql = $o->read_para_after(__FILE__, qr/MAGIC_dest_table/);
691+ $sql =~ s/deadlocks/IF NOT EXISTS $db_tbl/;
692 PTDEBUG && _d($sql);
693- $dest_dbh->do($sql);
694+ $dst->dbh->do($sql);
695 }
696 }
697
698@@ -4039,6 +4335,16 @@
699 $daemon->make_PID_file();
700 }
701
702+ # If we daemonized, the parent has already exited and we're the child.
703+ # We shared a copy of every Cxn with the parent, and the parent's copies
704+ # were destroyed but the dbhs were not disconnected because the parent
705+ # attrib was true. Now, as the child, set it false so the dbhs will be
706+ # disconnected when our Cxn copies are destroyed. If we didn't daemonize,
707+ # then we're not really a parent (since we have no children), so set it
708+ # false to auto-disconnect the dbhs when our Cxns are destroyed.
709+ $src->{parent} = 0;
710+ $dst->{parent} = 0 if $dst;
711+
712 # ########################################################################
713 # Do the version-check
714 # ########################################################################
715@@ -4046,117 +4352,155 @@
716 VersionCheck::version_check(
717 force => $o->got('version-check'),
718 instances => [
719- { dbh => $dbh, dsn => $source_dsn },
720- ($dest_dsn ? { dbh => $dest_dsn, dsn => $dest_dsn } : ()),
721+ { dbh => $src->dbh, dsn => $src->dsn },
722+ ($dst ? { dbh => $dst->dbh, dsn => $dst->dsn } : ())
723 ],
724 );
725 }
726
727 # ########################################################################
728+ # Set upt the --clear-deadlocks table.
729+ # ########################################################################
730+ my $clear_deadlocks_table_def;
731+ my $clear_deadlocks_table = $o->get('clear-deadlocks');
732+ if ( $clear_deadlocks_table ) {
733+ $clear_deadlocks_table_def
734+ = $o->read_para_after(__FILE__, qr/MAGIC_clear_deadlocks/);
735+ if ( VersionParser->new($src->dbh) < '4.1.2') {
736+ $clear_deadlocks_table_def =~ s/ENGINE=/TYPE=/;
737+ }
738+ $clear_deadlocks_table_def
739+ =~ s/percona_schema.clear_deadlocks/$clear_deadlocks_table/;
740+ PTDEBUG && _d('--clear-deadlocks table:', $clear_deadlocks_table_def);
741+ }
742+
743+ # ########################################################################
744 # Start looking for and logging deadlocks.
745 # ########################################################################
746- my $last_fingerprint = '';
747-
748- $oktorun = 1;
749- my $start = time();
750- my $end = $start + ($o->get('run-time') || 0); # When we should exit
751- my $now = $start;
752- while ( # Quit if:
753- ($start == $end || $now < $end) # time is exceeded
754- && $oktorun # or instructed to quit
755- )
756- {
757- my $text = $dbh->selectrow_hashref("SHOW /*!40100 ENGINE*/ INNODB STATUS")->{Status};
758- my $parse_deadlocks_options = {
759- 'numeric-ip' => $o->got('numeric-ip'),
760- 'collapse' => $o->got('collapse'),
761+ my $sep = $o->get('tab') ? "\t" : ' ';
762+ my $last_fingerprint = '';
763+ my $parse_deadlocks_options = {
764+ 'server' => $src->dsn->{h} || $src->{hostname},
765+ 'numeric-ip' => $o->got('numeric-ip'),
766+ };
767+
768+ my $run_time = Runtime->new(
769+ run_time => $o->get('run-time'),
770+ now => sub { return time },
771+ );
772+
773+ my $interval = $o->get('interval');
774+ my $iters = $o->get('iterations');
775+ PTDEBUG && _d('iterations:', $iters, 'interval:', $interval);
776+
777+ ITERATION:
778+ while (
779+ $oktorun
780+ && $run_time->have_time()
781+ && (!defined $iters || $iters--)
782+ ) {
783+
784+ my %txns;
785+ my $fingerprint;
786+ eval {
787+ my $sql = "SHOW /*!40100 ENGINE*/ INNODB STATUS "
788+ . "/* pt-deadlock-logger */";
789+ my $text = $src->dbh->selectrow_hashref($sql)->{status};
790+
791+ %txns = %{parse_deadlocks($text, $parse_deadlocks_options)};
792+ $fingerprint = fingerprint(\%txns);
793 };
794- my %txns = %{parse_deadlocks($text, $parse_deadlocks_options)};
795- my $fingerprint = fingerprint(\%txns);
796-
797- if ( $ins_sth ) {
798- foreach my $txn (sort { $a->{thread} <=> $b->{thread} } values %txns) {
799- $ins_sth->execute(@{$txn}{@cols});
800- }
801- $dest_dbh->commit();
802+ if ( my $e = $EVAL_ERROR ) {
803+ PTDEBUG && _d('Error getting InnoDB status:', $e);
804+ if ( $src->lost_connection($e) ) {
805+ eval { $src->connect() };
806+ if ( $EVAL_ERROR ) {
807+ warn "Lost connection to " . $src->name . ". Will try "
808+ . "to reconnect in the next iteration.\n";
809+ }
810+ else {
811+ PTDEBUG && _d('Reconnected to MySQL');
812+ redo ITERATION;
813+ }
814+ }
815+ else {
816+ warn "Error getting SHOW ENGINE INNODB STATUS: $EVAL_ERROR";
817+ $exit_status |= 1;
818+ }
819 }
820+ else {
821+ if ( $ins_sth ) {
822+ eval {
823+ PTDEBUG && _d('Saving deadlock to --dest');
824+ foreach my $txn (
825+ sort { $a->{thread} <=> $b->{thread} } values %txns
826+ ) {
827+ $ins_sth->execute(@{$txn}{@cols});
828+ }
829+ $dst->dbh->commit();
830+ };
831+ if ( my $e = $EVAL_ERROR ) {
832+ PTDEBUG && _d('Error saving to --dest:', $e);
833+ if ( $dst->lost_connection($e) ) {
834+ eval {
835+ $ins_sth->finish() if $ins_sth;
836+ $dst->dbh->disconnect() if $dst->dbh;
837+ $dst->connect(AutoCommit => 0);
838+ $ins_sth = $dst->dbh->prepare($ins_sql);
839+ };
840+ if ( $EVAL_ERROR ) {
841+ warn "Lost connection to " . $dst->name . ". Will try "
842+ . "to reconnect in the next iteration.\n";
843+ }
844+ else {
845+ PTDEBUG && _d('Reconnected to MySQL (--dest)');
846+ redo ITERATION;
847+ }
848+ }
849+ else {
850+ warn "Error saving to --dest: $EVAL_ERROR";
851+ $exit_status |= 1;
852+ }
853+ }
854+ }
855
856- if ( $fingerprint ne $last_fingerprint ) {
857- PTDEBUG && _d('New deadlock');
858- if ( $o->got('print') || !$o->got('dest') ) {
859- my $sep = $o->get('tab') ? "\t" : ' ';
860- print join($sep, @cols), "\n";
861- foreach my $txn (sort {$a->{thread}<=>$b->{thread}} values %txns) {
862- # If 'collapse' is on, it's already been taken care of,
863- # but if it's unset, by default strip whitespace.
864- if ( !$o->got('collapse') ) {
865+ if ( $fingerprint ne $last_fingerprint ) {
866+ PTDEBUG && _d('New deadlock');
867+ if ( !$o->get('quiet') ) {
868+ print join($sep, @cols), "\n";
869+ foreach my $txn (
870+ sort { $a->{thread} <=> $b->{thread} } values %txns
871+ ) {
872 $txn->{query} =~ s/\s+/ /g;
873+ print join($sep, map { $txn->{$_} } @cols), "\n";
874 }
875- print join($sep, map { $txn->{$_} } @cols), "\n";
876 }
877 }
878- }
879- else {
880- PTDEBUG && _d('Same deadlock, not printing');
881- }
882- # Save deadlock's fingerprint for next interval.
883- $last_fingerprint = $fingerprint;
884-
885- # If specified, clear the deadlock...
886- if ( my $db_tbl = $o->get('clear-deadlocks') ) {
887- PTDEBUG && _d('Creating --clear-deadlocks table', $db_tbl);
888- $dbh->{AutoCommit} = 0;
889- my $sql = $o->read_para_after(__FILE__, qr/MAGIC_clear_deadlocks/);
890-
891- if ( VersionParser->new($dbh) < '4.1.2') {
892- $sql =~ s/ENGINE=/TYPE=/;
893- }
894- $sql =~ s/test.deadlock_maker/$db_tbl/;
895- PTDEBUG && _d($sql);
896- $dbh->do($sql);
897- $sql = "INSERT INTO $db_tbl(a) VALUES(1)";
898- PTDEBUG && _d($sql);
899- $dbh->do($sql); # I'm holding locks on the table now.
900-
901- # Fork off a child to try to take a lock on the table.
902- my $pid = fork();
903- if ( defined($pid) && $pid == 0 ) { # I am a child
904- my $dbh_child = get_cxn($source_dsn, 0);
905- $sql = "SELECT * FROM $db_tbl FOR UPDATE";
906- PTDEBUG && _d($sql);
907- eval { $dbh_child->do($sql); }; # Should block against parent.
908- PTDEBUG && _d($EVAL_ERROR); # Parent inserted value 0.
909- $sql = "COMMIT";
910- PTDEBUG && _d($sql);
911- $dbh_child->do($sql);
912- exit;
913- }
914- elsif ( !defined($pid) ) {
915- die("Unable to fork for clearing deadlocks!\n");
916- }
917- sleep 1;
918- $sql = "INSERT INTO $db_tbl(a) VALUES(0)";# Will make child deadlock
919- PTDEBUG && _d($sql);
920- eval { $dbh->do($sql); };
921- PTDEBUG && _d($EVAL_ERROR);
922- waitpid($pid, 0);
923- $sql = "DROP TABLE $db_tbl";
924- PTDEBUG && _d($sql);
925- $dbh->do($sql);
926- }
927-
928- # If there's an --interval argument, run forever or till specified.
929- # Otherwise just run once.
930- if ( $o->get('interval') ) {
931- sleep($o->get('interval'));
932- $now = time();
933- }
934- else {
935- $oktorun = 0;
936+ else {
937+ PTDEBUG && _d('Same deadlock, not printing');
938+ }
939+
940+ $last_fingerprint = $fingerprint;
941+
942+ if ( $clear_deadlocks_table ) {
943+ clear_deadlocks(
944+ dsn => $src->dsn,
945+ table => $clear_deadlocks_table,
946+ table_def => $clear_deadlocks_table_def,
947+ DSNParser => $dp,
948+ );
949+ }
950+ }
951+
952+ # Sleep if there's an --iteration left.
953+ if ( !defined $iters || $iters ) {
954+ PTDEBUG && _d('Sleeping', $interval, 'seconds');
955+ sleep $interval;
956 }
957 }
958
959- return 0;
960+ PTDEBUG && _d('Done running, exiting', $exit_status);
961+ return $exit_status;
962 }
963
964 # ############################################################################
965@@ -4244,7 +4588,7 @@
966
967 my ( $query_text ) = $body =~ m/\nMySQL thread id .*\n((?s).*)/;
968 $query_text =~ s/\s+$//;
969- $query_text =~ s/\s+/ /g if $args->{'collapse'};
970+ $query_text =~ s/\s+/ /g;
971
972 @{$hash}{qw(thread hostname ip user query)}
973 = ($mysql_thread_id, $hostname, $ip, $user, $query_text);
974@@ -4290,13 +4634,100 @@
975 foreach my $txn ( values %txns ) {
976 $txn->{victim} = $txn->{id} == $victim ? 1 : 0;
977 $txn->{ts} = $ts;
978- $txn->{server} = $source_dsn->{h} || '';
979+ $txn->{server} = $args->{server} || '';
980 $txn->{ip} = inet_aton($txn->{ip}) if $args->{'numeric-ip'};
981 }
982
983 return \%txns;
984 }
985
986+sub clear_deadlocks {
987+ my (%args) = @_;
988+ my @required_args = qw(dsn table table_def DSNParser);
989+ foreach my $arg ( @required_args ) {
990+ die "I need a $arg argument" unless $args{$arg};
991+ }
992+ my $dsn = $args{dsn};
993+ my $table = $args{table};
994+ my $table_def = $args{table_def};
995+ my $dp = $args{DSNParser};
996+ PTDEBUG && _d('Clearing deadlocks with table', $table, $table_def);
997+
998+ my $parent_dbh = $dp->get_dbh($dp->get_cxn_params($dsn), { AutoCommit=>0 });
999+ $parent_dbh->{InactiveDestroy} = 1; # because of forking
1000+
1001+ # Create the deadlocks table.
1002+ PTDEBUG && _d($table_def);
1003+ $parent_dbh->do($table_def);
1004+
1005+ # Get a lock on it.
1006+ my $sql = "INSERT INTO $table (a) VALUES (1) "
1007+ . "/* pt-deadlock-logger clear deadlocks parent */";
1008+ PTDEBUG && _d($sql);
1009+ $parent_dbh->do($sql);
1010+
1011+ my ($sync_fh, $sync_file) = tempfile(
1012+ 'pt-deadlock-logger-clear-deadlocks.XXXXXXX',
1013+ DIR => File::Spec->tmpdir(),
1014+ );
1015+ PTDEBUG && _d('Sync file:', $sync_file);
1016+ close $sync_fh;
1017+ unlink $sync_file;
1018+
1019+ # Fork a child to try to take a lock on the table.
1020+ my $pid = fork();
1021+ if ( defined($pid) && $pid == 0 ) {
1022+ # I am the child
1023+ PTDEBUG && _d('Clear deadlocks child', $PID);
1024+ my $child_dbh = $dp->get_dbh($dp->get_cxn_params($dsn), {AutoCommit=>0});
1025+ my $sql = "SELECT * FROM $table FOR UPDATE "
1026+ . "/* pt-deadlock-logger clear deadlocks child */";
1027+ PTDEBUG && _d($sql);
1028+ open my $fh, '>', $sync_file
1029+ or die "Error creating $sync_file: $OS_ERROR";
1030+ close $fh;
1031+ PTDEBUG && _d('Clear deadlocks child ready (child)');
1032+ eval { $child_dbh->do($sql); }; # Should block against parent.
1033+ PTDEBUG && _d($EVAL_ERROR); # Parent inserted value 0.
1034+ $child_dbh->commit();
1035+ $child_dbh->disconnect();
1036+ exit;
1037+ }
1038+ elsif ( !defined($pid) ) {
1039+ die "Failed to fork for --clear-deadlocks: " . ($OS_ERROR || '');
1040+ }
1041+
1042+ # Wait up to 10s for the child to connect and become ready.
1043+ for ( 1..40 ) {
1044+ last if -f $sync_file;
1045+ PTDEBUG && _d('Waiting for the clear deadlocks child');
1046+ sleep 0.25;
1047+ }
1048+ PTDEBUG && _d('Clear deadlocks child ready (parent)');
1049+ sleep 0.25; # wait for child to exec its SELECT statement
1050+
1051+ # Make the child deadlock.
1052+ $sql = "INSERT INTO $table (a) VALUES (0) "
1053+ . "/* pt-deadlock-logger clear deadlocks parent */";
1054+ PTDEBUG && _d($sql);
1055+ eval { $parent_dbh->do($sql); };
1056+ PTDEBUG && _d($EVAL_ERROR);
1057+
1058+ # Reap the child.
1059+ waitpid($pid, 0);
1060+
1061+ # Drop the table.
1062+ $sql = "DROP TABLE IF EXISTS $table";
1063+ PTDEBUG && _d($sql);
1064+ $parent_dbh->do($sql);
1065+
1066+ $parent_dbh->disconnect();
1067+
1068+ unlink $sync_file;
1069+
1070+ return;
1071+}
1072+
1073 sub fingerprint {
1074 my ( $txns ) = @_;
1075 my $fingerprint = '';
1076@@ -4308,21 +4739,11 @@
1077 return $fingerprint;
1078 }
1079
1080-# Catches signals so the program can exit gracefully.
1081-sub finish {
1082- my ($signal) = @_;
1083- print STDERR "Exiting on SIG$signal.\n";
1084+sub sig_int {
1085+ my ( $signal ) = @_;
1086 $oktorun = 0;
1087-}
1088-
1089-sub get_cxn {
1090- my ( $dsn, $ac ) = @_;
1091- if ( $o->get('ask-pass') ) {
1092- $dsn->{p} = OptionParser::prompt_noecho("Enter password: ");
1093- }
1094- my $dbh = $dp->get_dbh($dp->get_cxn_params($dsn), {AutoCommit => $ac});
1095- $dbh->{InactiveDestroy} = 1; # Because of forking.
1096- return $dbh;
1097+ print STDERR "# Caught SIG$signal. Use 'kill -ABRT $PID' if "
1098+ . "the tool does not exit normally in a few seconds.\n";
1099 }
1100
1101 sub _d {
1102@@ -4347,32 +4768,28 @@
1103
1104 =head1 NAME
1105
1106-pt-deadlock-logger - Extract and log MySQL deadlock information.
1107+pt-deadlock-logger - Log MySQL deadlocks.
1108
1109 =head1 SYNOPSIS
1110
1111-Usage: pt-deadlock-logger [OPTION...] SOURCE_DSN
1112-
1113-pt-deadlock-logger extracts and saves information about the most recent deadlock
1114-in a MySQL server.
1115-
1116-Print deadlocks on SOURCE_DSN:
1117-
1118- pt-deadlock-logger SOURCE_DSN
1119-
1120-Store deadlock information from SOURCE_DSN in test.deadlocks table on SOURCE_DSN
1121-(source and destination are the same host):
1122-
1123- pt-deadlock-logger SOURCE_DSN --dest D=test,t=deadlocks
1124-
1125-Store deadlock information from SOURCE_DSN in test.deadlocks table on DEST_DSN
1126-(source and destination are different hosts):
1127-
1128- pt-deadlock-logger SOURCE_DSN --dest DEST_DSN,D=test,t=deadlocks
1129-
1130-Daemonize and check for deadlocks on SOURCE_DSN every 30 seconds for 4 hours:
1131-
1132- pt-deadlock-logger SOURCE_DSN --dest D=test,t=deadlocks --daemonize --run-time 4h --interval 30s
1133+Usage: pt-deadlock-logger [OPTIONS] DSN
1134+
1135+pt-deadlock-logger logs information about MySQL deadlocks on the given
1136+DSN. Information is printed to C<STDOUT>, and it can also be saved to a
1137+table by specifying L<"--dest">. The tool runs for forever unless
1138+L<"--run-time"> or L<"--iterations"> is specified.
1139+
1140+Print deadlocks on host1:
1141+
1142+ pt-fk-error-logger h=host1
1143+
1144+Print deadlocks on host1 once then exit:
1145+
1146+ pt-fk-error-logger h=host1 --iterations 1
1147+
1148+Save deadlocks on host1 to percona_schema.fke on host2:
1149+
1150+ pt-fk-error-logger h=host1 --dest h=host2,D=percona_schema,t=deadlocks
1151
1152 =head1 RISKS
1153
1154@@ -4398,43 +4815,93 @@
1155
1156 =head1 DESCRIPTION
1157
1158-pt-deadlock-logger extracts deadlock data from a MySQL server. Currently only
1159-InnoDB deadlock information is available. You can print the information to
1160-standard output, store it in a database table, or both. If neither
1161-L<"--print"> nor L<"--dest"> are given, then the deadlock information is
1162-printed by default. If only L<"--dest"> is given, then the deadlock
1163-information is only stored. If both options are given, then the deadlock
1164-information is printed and stored.
1165-
1166-The source host can be specified using one of two methods. The first method is
1167-to use at least one of the standard connection-related command line options:
1168-L<"--defaults-file">, L<"--password">, L<"--host">, L<"--port">, L<"--socket">
1169-or L<"--user">. These options only apply to the source host; they cannot be
1170-used to specify the destination host.
1171-
1172-The second method to specify the source host, or the optional destination host
1173-using L<"--dest">, is a DSN. A DSN is a special syntax that can be either just
1174-a hostname (like C<server.domain.com> or C<1.2.3.4>), or a
1175-C<key=value,key=value> string. Keys are a single letter:
1176-
1177- KEY MEANING
1178- === =======
1179- h Connect to host
1180- P Port number to use for connection
1181- S Socket file to use for connection
1182- u User for login if not current user
1183- p Password to use when connecting
1184- F Only read default options from the given file
1185-
1186-If you omit any values from the destination host DSN, they are filled in with
1187-values from the source host, so you don't need to specify them in both places.
1188-C<pt-deadlock-logger> reads all normal MySQL option files, such as ~/.my.cnf, so
1189-you may not need to specify username, password and other common options at all.
1190+pt-deadlock-logger prints information about MySQL deadlocks by polling
1191+and parsing C<SHOW ENGINE INNODB STATUS>. When a new deadlock occurs,
1192+it's printed to C<STDOUT> and, if specified, saved to L<"--dest">.
1193+
1194+Only new deadlocks are printed. A fingerprint for each deadlock is created
1195+using the deadlock's server, ts, and thread values (even if these
1196+columns are not specified by L<"--columns">). A deadlock is printed if
1197+its fingerprint is different than the last deadlock's fingerprint.
1198+
1199+The L<"--dest"> statement uses C<INSERT IGNORE> to eliminate duplicate
1200+deadlocks, so every deadlock is saved for every L<"--iterations">.
1201
1202 =head1 OUTPUT
1203
1204-You can choose which columns are output and/or saved to L<"--dest"> with the
1205-L<"--columns"> argument. The default columns are as follows:
1206+New deadlocks are printed to C<STDOUT>, unless L<"--quiet"> is specified.
1207+Errors and warnings are printed to C<STDERR>.
1208+
1209+See also L<"--columns"> and L<"--tab">.
1210+
1211+=head1 INNODB CAVEATS AND DETAILS
1212+
1213+InnoDB's output is hard to parse and sometimes there's no way to do it right.
1214+
1215+Sometimes not all information (for example, username or IP address) is included
1216+in the deadlock information. In this case there's nothing for the script to put
1217+in those columns. It may also be the case that the deadlock output is so long
1218+(because there were a lot of locks) that the whole thing is truncated.
1219+
1220+Though there are usually two transactions involved in a deadlock, there are more
1221+locks than that; at a minimum, one more lock than transactions is necessary to
1222+create a cycle in the waits-for graph. pt-deadlock-logger prints the
1223+transactions (always two in the InnoDB output, even when there are more
1224+transactions in the waits-for graph than that) and fills in locks. It prefers
1225+waited-for over held when choosing lock information to output, but you can
1226+figure out the rest with a moment's thought. If you see one wait-for and one
1227+held lock, you're looking at the same lock, so of course you'd prefer to see
1228+both wait-for locks and get more information. If the two waited-for locks are
1229+not on the same table, more than two transactions were involved in the deadlock.
1230+
1231+Finally, keep in mind that, because usernames with spaces are not quoted by
1232+InnoDB, the tool will generally misreport the second word of these usernames
1233+as the hostname.
1234+
1235+=head1 OPTIONS
1236+
1237+This tool accepts additional command-line arguments. Refer to the
1238+L<"SYNOPSIS"> and usage information for details.
1239+
1240+=over
1241+
1242+=item --ask-pass
1243+
1244+Prompt for a password when connecting to MySQL.
1245+
1246+=item --charset
1247+
1248+short form: -A; type: string
1249+
1250+Default character set. If the value is utf8, sets Perl's binmode on
1251+STDOUT to utf8, passes the mysql_enable_utf8 option to DBD::mysql, and runs SET
1252+NAMES UTF8 after connecting to MySQL. Any other value sets binmode on STDOUT
1253+without the utf8 layer, and runs SET NAMES after connecting to MySQL.
1254+
1255+=item --clear-deadlocks
1256+
1257+type: string
1258+
1259+Use this table to create a small deadlock. This usually has the effect of
1260+clearing out a huge deadlock, which otherwise consumes the entire output of
1261+C<SHOW INNODB STATUS>. The table must not exist. pt-deadlock-logger will
1262+create it with the following structure:
1263+
1264+=for comment ignore-pt-internal-value
1265+MAGIC_clear_deadlocks
1266+
1267+ CREATE TABLE percona_schema.clear_deadlocks (
1268+ a INT PRIMARY KEY
1269+ ) ENGINE=InnoDB
1270+
1271+After creating the table and causing a small deadlock, the tool will drop the
1272+table again.
1273+
1274+=item --columns
1275+
1276+type: Array; default: server, ts, thread, txn_id, txn_time, user, hostname, ip, db, tbl, idx, lock_type, lock_mode, wait_hold, victim, query
1277+
1278+The columns are:
1279
1280 =over
1281
1282@@ -4509,78 +4976,6 @@
1283
1284 =back
1285
1286-=head1 INNODB CAVEATS AND DETAILS
1287-
1288-InnoDB's output is hard to parse and sometimes there's no way to do it right.
1289-
1290-Sometimes not all information (for example, username or IP address) is included
1291-in the deadlock information. In this case there's nothing for the script to put
1292-in those columns. It may also be the case that the deadlock output is so long
1293-(because there were a lot of locks) that the whole thing is truncated.
1294-
1295-Though there are usually two transactions involved in a deadlock, there are more
1296-locks than that; at a minimum, one more lock than transactions is necessary to
1297-create a cycle in the waits-for graph. pt-deadlock-logger prints the
1298-transactions (always two in the InnoDB output, even when there are more
1299-transactions in the waits-for graph than that) and fills in locks. It prefers
1300-waited-for over held when choosing lock information to output, but you can
1301-figure out the rest with a moment's thought. If you see one wait-for and one
1302-held lock, you're looking at the same lock, so of course you'd prefer to see
1303-both wait-for locks and get more information. If the two waited-for locks are
1304-not on the same table, more than two transactions were involved in the deadlock.
1305-
1306-Finally, keep in mind that, because usernames with spaces are not quoted by
1307-InnoDB, the tool will generally misreport the second word of these usernames
1308-as the hostname.
1309-
1310-=head1 OPTIONS
1311-
1312-This tool accepts additional command-line arguments. Refer to the
1313-L<"SYNOPSIS"> and usage information for details.
1314-
1315-=over
1316-
1317-=item --ask-pass
1318-
1319-Prompt for a password when connecting to MySQL.
1320-
1321-=item --charset
1322-
1323-short form: -A; type: string
1324-
1325-Default character set. If the value is utf8, sets Perl's binmode on
1326-STDOUT to utf8, passes the mysql_enable_utf8 option to DBD::mysql, and runs SET
1327-NAMES UTF8 after connecting to MySQL. Any other value sets binmode on STDOUT
1328-without the utf8 layer, and runs SET NAMES after connecting to MySQL.
1329-
1330-=item --clear-deadlocks
1331-
1332-type: string
1333-
1334-Use this table to create a small deadlock. This usually has the effect of
1335-clearing out a huge deadlock, which otherwise consumes the entire output of
1336-C<SHOW INNODB STATUS>. The table must not exist. pt-deadlock-logger will
1337-create it with the following MAGIC_clear_deadlocks structure:
1338-
1339- CREATE TABLE test.deadlock_maker(a INT PRIMARY KEY) ENGINE=InnoDB;
1340-
1341-After creating the table and causing a small deadlock, the tool will drop the
1342-table again.
1343-
1344-=item --[no]collapse
1345-
1346-Collapse whitespace in queries to a single space. This might make it easier to
1347-inspect on the command line or in a query. By default, whitespace is collapsed
1348-when printing with L<"--print">, but not modified when storing to L<"--dest">.
1349-(That is, the default is different for each action).
1350-
1351-=item --columns
1352-
1353-type: hash
1354-
1355-Output only this comma-separated list of columns. See L<"OUTPUT"> for more
1356-details on columns.
1357-
1358 =item --config
1359
1360 type: Array
1361@@ -4617,12 +5012,12 @@
1362 can usually omit most parts of this argument if you're storing deadlocks on the
1363 same server on which they happen.
1364
1365-By default, whitespace in the query column is left intact;
1366-use L<"--[no]collapse"> if you want whitespace collapsed.
1367-
1368-The following MAGIC_dest_table is suggested if you want to store all the
1369+The following table structure is suggested if you want to store all the
1370 information pt-deadlock-logger can extract about deadlocks:
1371
1372+=for comment ignore-pt-internal-value
1373+MAGIC_dest_table
1374+
1375 CREATE TABLE deadlocks (
1376 server char(20) NOT NULL,
1377 ts datetime NOT NULL,
1378@@ -4658,12 +5053,23 @@
1379
1380 =item --interval
1381
1382-type: time
1383+type: time; default: 30
1384
1385 How often to check for deadlocks. If no L<"--run-time"> is specified,
1386 pt-deadlock-logger runs forever, checking for deadlocks at every interval.
1387 See also L<"--run-time">.
1388
1389+=item --iterations
1390+
1391+type: int
1392+
1393+How many times to check for deadlocks. By default, this option
1394+is undefined which means an infinite number of iterations. The tool always
1395+exits for L<"--run-time">, regardless of the value specified for this option.
1396+For example, the tool will exit after 1 minute with
1397+C<--run-time 1m --iterations 4 --interval 30> because 4 iterations at 30
1398+second intervals would take 2 minutes, longer than the 1 mintue run-time.
1399+
1400 =item --log
1401
1402 type: string
1403@@ -4684,10 +5090,11 @@
1404
1405 type: string
1406
1407-Create the given PID file when daemonized. The file contains the process ID of
1408-the daemonized instance. The PID file is removed when the daemonized instance
1409-exits. The program checks for the existence of the PID file when starting; if
1410-it exists and the process with the matching PID exists, the program exits.
1411+Create the given PID file. The tool won't start if the PID file already
1412+exists and the PID it contains is different than the current PID. However,
1413+if the PID file exists and the PID it contains is no longer running, the
1414+tool will overwrite the PID file with the current PID. The PID file is
1415+removed automatically when the tool exits.
1416
1417 =item --port
1418
1419@@ -4695,16 +5102,9 @@
1420
1421 Port number to use for connection.
1422
1423-=item --print
1424-
1425-Print results on standard output. See L<"OUTPUT"> for more. By default,
1426-enables L<"--[no]collapse"> unless you explicitly disable it.
1427-
1428-If L<"--interval"> or L<"--run-time"> is specified, only new deadlocks are
1429-printed at each interval. A fingerprint for each deadlock is created using
1430-L<"--columns"> server, ts and thread (even if those columns were not specified
1431-by L<"--columns">) and if the current deadlock's fingerprint is different from
1432-the last deadlock's fingerprint, then it is printed.
1433+=item --quiet
1434+
1435+Do not deadlocks; only print errors and warnings to C<STDERR>.
1436
1437 =item --run-time
1438
1439@@ -4729,7 +5129,7 @@
1440
1441 =item --tab
1442
1443-Print tab-separated columns, instead of aligned.
1444+Use tabs to separate columns instead of spaces.
1445
1446 =item --user
1447
1448@@ -4886,7 +5286,7 @@
1449
1450 =head1 AUTHORS
1451
1452-Baron Schwartz
1453+Baron Schwartz and Daniel Nichter
1454
1455 =head1 ABOUT PERCONA TOOLKIT
1456
1457
1458=== modified file 'bin/pt-duplicate-key-checker'
1459--- bin/pt-duplicate-key-checker 2013-02-22 17:47:57 +0000
1460+++ bin/pt-duplicate-key-checker 2013-02-27 23:41:26 +0000
1461@@ -5130,14 +5130,11 @@
1462
1463 type: string
1464
1465-Create the given PID file. The file contains the process ID of the script.
1466-The PID file is removed when the script exits. Before starting, the script
1467-checks if the PID file already exists. If it does not, then the script creates
1468-and writes its own PID to it. If it does, then the script checks the following:
1469-if the file contains a PID and a process is running with that PID, then
1470-the script dies; or, if there is no process running with that PID, then the
1471-script overwrites the file with its own PID and starts; else, if the file
1472-contains no PID, then the script dies.
1473+Create the given PID file. The tool won't start if the PID file already
1474+exists and the PID it contains is different than the current PID. However,
1475+if the PID file exists and the PID it contains is no longer running, the
1476+tool will overwrite the PID file with the current PID. The PID file is
1477+removed automatically when the tool exits.
1478
1479 =item --port
1480
1481
1482=== modified file 'bin/pt-fifo-split'
1483--- bin/pt-fifo-split 2013-01-03 00:54:18 +0000
1484+++ bin/pt-fifo-split 2013-02-27 23:41:26 +0000
1485@@ -1456,14 +1456,11 @@
1486
1487 type: string
1488
1489-Create the given PID file. The file contains the process ID of the script.
1490-The PID file is removed when the script exits. Before starting, the script
1491-checks if the PID file already exists. If it does not, then the script creates
1492-and writes its own PID to it. If it does, then the script checks the following:
1493-if the file contains a PID and a process is running with that PID, then
1494-the script dies; or, if there is no process running with that PID, then the
1495-script overwrites the file with its own PID and starts; else, if the file
1496-contains no PID, then the script dies.
1497+Create the given PID file. The tool won't start if the PID file already
1498+exists and the PID it contains is different than the current PID. However,
1499+if the PID file exists and the PID it contains is no longer running, the
1500+tool will overwrite the PID file with the current PID. The PID file is
1501+removed automatically when the tool exits.
1502
1503 =item --statistics
1504
1505
1506=== modified file 'bin/pt-find'
1507--- bin/pt-find 2013-02-22 17:47:57 +0000
1508+++ bin/pt-find 2013-02-27 23:41:26 +0000
1509@@ -4241,14 +4241,11 @@
1510
1511 type: string
1512
1513-Create the given PID file. The file contains the process ID of the script.
1514-The PID file is removed when the script exits. Before starting, the script
1515-checks if the PID file already exists. If it does not, then the script creates
1516-and writes its own PID to it. If it does, then the script checks the following:
1517-if the file contains a PID and a process is running with that PID, then
1518-the script dies; or, if there is no process running with that PID, then the
1519-script overwrites the file with its own PID and starts; else, if the file
1520-contains no PID, then the script dies.
1521+Create the given PID file. The tool won't start if the PID file already
1522+exists and the PID it contains is different than the current PID. However,
1523+if the PID file exists and the PID it contains is no longer running, the
1524+tool will overwrite the PID file with the current PID. The PID file is
1525+removed automatically when the tool exits.
1526
1527 =item --port
1528
1529
1530=== modified file 'bin/pt-fk-error-logger'
1531--- bin/pt-fk-error-logger 2013-02-22 17:47:57 +0000
1532+++ bin/pt-fk-error-logger 2013-02-27 23:41:26 +0000
1533@@ -17,10 +17,12 @@
1534 OptionParser
1535 Quoter
1536 DSNParser
1537+ Cxn
1538 Daemon
1539 Transformers
1540 HTTPMicro
1541 VersionCheck
1542+ Runtime
1543 ));
1544 }
1545
1546@@ -1596,6 +1598,181 @@
1547 # ###########################################################################
1548
1549 # ###########################################################################
1550+# Cxn package
1551+# This package is a copy without comments from the original. The original
1552+# with comments and its test file can be found in the Bazaar repository at,
1553+# lib/Cxn.pm
1554+# t/lib/Cxn.t
1555+# See https://launchpad.net/percona-toolkit for more information.
1556+# ###########################################################################
1557+{
1558+package Cxn;
1559+
1560+use strict;
1561+use warnings FATAL => 'all';
1562+use English qw(-no_match_vars);
1563+use Scalar::Util qw(blessed);
1564+use constant {
1565+ PTDEBUG => $ENV{PTDEBUG} || 0,
1566+ PERCONA_TOOLKIT_TEST_USE_DSN_NAMES => $ENV{PERCONA_TOOLKIT_TEST_USE_DSN_NAMES} || 0,
1567+};
1568+
1569+sub new {
1570+ my ( $class, %args ) = @_;
1571+ my @required_args = qw(DSNParser OptionParser);
1572+ foreach my $arg ( @required_args ) {
1573+ die "I need a $arg argument" unless $args{$arg};
1574+ };
1575+ my ($dp, $o) = @args{@required_args};
1576+
1577+ my $dsn_defaults = $dp->parse_options($o);
1578+ my $prev_dsn = $args{prev_dsn};
1579+ my $dsn = $args{dsn};
1580+ if ( !$dsn ) {
1581+ $args{dsn_string} ||= 'h=' . ($dsn_defaults->{h} || 'localhost');
1582+
1583+ $dsn = $dp->parse(
1584+ $args{dsn_string}, $prev_dsn, $dsn_defaults);
1585+ }
1586+ elsif ( $prev_dsn ) {
1587+ $dsn = $dp->copy($prev_dsn, $dsn);
1588+ }
1589+
1590+ my $self = {
1591+ dsn => $dsn,
1592+ dbh => $args{dbh},
1593+ dsn_name => $dp->as_string($dsn, [qw(h P S)]),
1594+ hostname => '',
1595+ set => $args{set},
1596+ NAME_lc => defined($args{NAME_lc}) ? $args{NAME_lc} : 1,
1597+ dbh_set => 0,
1598+ OptionParser => $o,
1599+ DSNParser => $dp,
1600+ is_cluster_node => undef,
1601+ parent => $args{parent},
1602+ };
1603+
1604+ return bless $self, $class;
1605+}
1606+
1607+sub connect {
1608+ my ( $self, %opts ) = @_;
1609+ my $dsn = $self->{dsn};
1610+ my $dp = $self->{DSNParser};
1611+ my $o = $self->{OptionParser};
1612+
1613+ my $dbh = $self->{dbh};
1614+ if ( !$dbh || !$dbh->ping() ) {
1615+ if ( $o->get('ask-pass') && !$self->{asked_for_pass} ) {
1616+ $dsn->{p} = OptionParser::prompt_noecho("Enter MySQL password: ");
1617+ $self->{asked_for_pass} = 1;
1618+ }
1619+ $dbh = $dp->get_dbh(
1620+ $dp->get_cxn_params($dsn),
1621+ {
1622+ AutoCommit => 1,
1623+ %opts,
1624+ },
1625+ );
1626+ }
1627+
1628+ $dbh = $self->set_dbh($dbh);
1629+ PTDEBUG && _d($dbh, 'Connected dbh to', $self->{hostname},$self->{dsn_name});
1630+ return $dbh;
1631+}
1632+
1633+sub set_dbh {
1634+ my ($self, $dbh) = @_;
1635+
1636+ if ( $self->{dbh} && $self->{dbh} == $dbh && $self->{dbh_set} ) {
1637+ PTDEBUG && _d($dbh, 'Already set dbh');
1638+ return $dbh;
1639+ }
1640+
1641+ PTDEBUG && _d($dbh, 'Setting dbh');
1642+
1643+ $dbh->{FetchHashKeyName} = 'NAME_lc' if $self->{NAME_lc};
1644+
1645+ my $sql = 'SELECT @@hostname, @@server_id';
1646+ PTDEBUG && _d($dbh, $sql);
1647+ my ($hostname, $server_id) = $dbh->selectrow_array($sql);
1648+ PTDEBUG && _d($dbh, 'hostname:', $hostname, $server_id);
1649+ if ( $hostname ) {
1650+ $self->{hostname} = $hostname;
1651+ }
1652+
1653+ if ( $self->{parent} ) {
1654+ PTDEBUG && _d($dbh, 'Setting InactiveDestroy=1 in parent');
1655+ $dbh->{InactiveDestroy} = 1;
1656+ }
1657+
1658+ if ( my $set = $self->{set}) {
1659+ $set->($dbh);
1660+ }
1661+
1662+ $self->{dbh} = $dbh;
1663+ $self->{dbh_set} = 1;
1664+ return $dbh;
1665+}
1666+
1667+sub lost_connection {
1668+ my ($self, $e) = @_;
1669+ return 0 unless $e;
1670+ return $e =~ m/MySQL server has gone away/
1671+ || $e =~ m/Lost connection to MySQL server/;
1672+}
1673+
1674+sub dbh {
1675+ my ($self) = @_;
1676+ return $self->{dbh};
1677+}
1678+
1679+sub dsn {
1680+ my ($self) = @_;
1681+ return $self->{dsn};
1682+}
1683+
1684+sub name {
1685+ my ($self) = @_;
1686+ return $self->{dsn_name} if PERCONA_TOOLKIT_TEST_USE_DSN_NAMES;
1687+ return $self->{hostname} || $self->{dsn_name} || 'unknown host';
1688+}
1689+
1690+sub DESTROY {
1691+ my ($self) = @_;
1692+
1693+ PTDEBUG && _d('Destroying cxn');
1694+
1695+ if ( $self->{parent} ) {
1696+ PTDEBUG && _d($self->{dbh}, 'Not disconnecting dbh in parent');
1697+ }
1698+ elsif ( $self->{dbh}
1699+ && blessed($self->{dbh})
1700+ && $self->{dbh}->can("disconnect") )
1701+ {
1702+ PTDEBUG && _d($self->{dbh}, 'Disconnecting dbh on', $self->{hostname},
1703+ $self->{dsn_name});
1704+ $self->{dbh}->disconnect();
1705+ }
1706+
1707+ return;
1708+}
1709+
1710+sub _d {
1711+ my ($package, undef, $line) = caller 0;
1712+ @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
1713+ map { defined $_ ? $_ : 'undef' }
1714+ @_;
1715+ print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
1716+}
1717+
1718+1;
1719+}
1720+# ###########################################################################
1721+# End Cxn package
1722+# ###########################################################################
1723+
1724+# ###########################################################################
1725 # Daemon package
1726 # This package is a copy without comments from the original. The original
1727 # with comments and its test file can be found in the Bazaar repository at,
1728@@ -3375,6 +3552,139 @@
1729 # ###########################################################################
1730
1731 # ###########################################################################
1732+# Runtime package
1733+# This package is a copy without comments from the original. The original
1734+# with comments and its test file can be found in the Bazaar repository at,
1735+# lib/Runtime.pm
1736+# t/lib/Runtime.t
1737+# See https://launchpad.net/percona-toolkit for more information.
1738+# ###########################################################################
1739+{
1740+package Runtime;
1741+
1742+use strict;
1743+use warnings FATAL => 'all';
1744+use English qw(-no_match_vars);
1745+use constant PTDEBUG => $ENV{PTDEBUG} || 0;
1746+
1747+sub new {
1748+ my ( $class, %args ) = @_;
1749+ my @required_args = qw(run_time now);
1750+ foreach my $arg ( @required_args ) {
1751+ die "I need a $arg argument" unless exists $args{$arg};
1752+ }
1753+
1754+ my $run_time = $args{run_time};
1755+ if ( defined $run_time ) {
1756+ die "run_time must be > 0" if $run_time <= 0;
1757+ }
1758+
1759+ my $now = $args{now};
1760+ die "now must be a callback" unless ref $now eq 'CODE';
1761+
1762+ my $self = {
1763+ run_time => $run_time,
1764+ now => $now,
1765+ start_time => undef,
1766+ end_time => undef,
1767+ time_left => undef,
1768+ stop => 0,
1769+ };
1770+
1771+ return bless $self, $class;
1772+}
1773+
1774+sub time_left {
1775+ my ( $self, %args ) = @_;
1776+
1777+ if ( $self->{stop} ) {
1778+ PTDEBUG && _d("No time left because stop was called");
1779+ return 0;
1780+ }
1781+
1782+ my $now = $self->{now}->(%args);
1783+ PTDEBUG && _d("Current time:", $now);
1784+
1785+ if ( !defined $self->{start_time} ) {
1786+ $self->{start_time} = $now;
1787+ }
1788+
1789+ return unless defined $now;
1790+
1791+ my $run_time = $self->{run_time};
1792+ return unless defined $run_time;
1793+
1794+ if ( !$self->{end_time} ) {
1795+ $self->{end_time} = $now + $run_time;
1796+ PTDEBUG && _d("End time:", $self->{end_time});
1797+ }
1798+
1799+ $self->{time_left} = $self->{end_time} - $now;
1800+ PTDEBUG && _d("Time left:", $self->{time_left});
1801+ return $self->{time_left};
1802+}
1803+
1804+sub have_time {
1805+ my ( $self, %args ) = @_;
1806+ my $time_left = $self->time_left(%args);
1807+ return 1 if !defined $time_left; # run forever
1808+ return $time_left <= 0 ? 0 : 1; # <=0s means run time has elapsed
1809+}
1810+
1811+sub time_elapsed {
1812+ my ( $self, %args ) = @_;
1813+
1814+ my $start_time = $self->{start_time};
1815+ return 0 unless $start_time;
1816+
1817+ my $now = $self->{now}->(%args);
1818+ PTDEBUG && _d("Current time:", $now);
1819+
1820+ my $time_elapsed = $now - $start_time;
1821+ PTDEBUG && _d("Time elapsed:", $time_elapsed);
1822+ if ( $time_elapsed < 0 ) {
1823+ warn "Current time $now is earlier than start time $start_time";
1824+ }
1825+ return $time_elapsed;
1826+}
1827+
1828+sub reset {
1829+ my ( $self ) = @_;
1830+ $self->{start_time} = undef;
1831+ $self->{end_time} = undef;
1832+ $self->{time_left} = undef;
1833+ $self->{stop} = 0;
1834+ PTDEBUG && _d("Reset run time");
1835+ return;
1836+}
1837+
1838+sub stop {
1839+ my ( $self ) = @_;
1840+ $self->{stop} = 1;
1841+ return;
1842+}
1843+
1844+sub start {
1845+ my ( $self ) = @_;
1846+ $self->{stop} = 0;
1847+ return;
1848+}
1849+
1850+sub _d {
1851+ my ($package, undef, $line) = caller 0;
1852+ @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
1853+ map { defined $_ ? $_ : 'undef' }
1854+ @_;
1855+ print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
1856+}
1857+
1858+1;
1859+}
1860+# ###########################################################################
1861+# End Runtime package
1862+# ###########################################################################
1863+
1864+# ###########################################################################
1865 # This is a combination of modules and programs in one -- a runnable module.
1866 # http://www.perl.com/pub/a/2006/07/13/lightning-articles.html?page=last
1867 # Or, look it up in the Camel book on pages 642 and 643 in the 3rd edition.
1868@@ -3387,18 +3697,21 @@
1869 use strict;
1870 use warnings FATAL => 'all';
1871 use English qw(-no_match_vars);
1872-use sigtrap qw(handler finish untrapped normal-signals);
1873+
1874+use sigtrap 'handler', \&sig_int, 'normal-signals';
1875
1876 use Percona::Toolkit;
1877 use constant PTDEBUG => $ENV{PTDEBUG} || 0;
1878
1879 Transformers->import(qw(parse_timestamp));
1880
1881-my $oktorun;
1882+my $oktorun = 1;
1883+my $exit_status = 0;
1884
1885 sub main {
1886- local @ARGV = @_; # set global ARGV for this package
1887- $oktorun = 1;
1888+ local @ARGV = @_; # set global ARGV for this package
1889+ $oktorun = 1;
1890+ $exit_status = 0;
1891
1892 # ########################################################################
1893 # Get configuration information.
1894@@ -3409,69 +3722,56 @@
1895
1896 my $dp = $o->DSNParser();
1897 $dp->prop('set-vars', $o->get('set-vars'));
1898- my $dsn_defaults = $dp->parse_options($o);
1899- my $src_dsn = @ARGV ? $dp->parse(shift @ARGV, $dsn_defaults) : $dsn_defaults;
1900- my $dst_dsn = $o->get('dest');
1901-
1902- # The source dsn is not an option so --dest cannot use OptionParser
1903- # to inherit values from it. Thus, we do it manually. --dest will
1904- # inherit from --user, --port, etc.
1905- if ( $src_dsn && $dst_dsn ) {
1906- # If dest DSN only has D and t, this will copy h, P, S, etc.
1907- # from the source DSN.
1908- $dst_dsn = $dp->copy($src_dsn, $dst_dsn);
1909+
1910+ my $src;
1911+ if ( my $src_dsn_string = shift @ARGV ) {
1912+ $src = Cxn->new(
1913+ dsn_string => $src_dsn_string,
1914+ parent => $o->get('daemonize'),
1915+ DSNParser => $dp,
1916+ OptionParser => $o,
1917+ );
1918+ }
1919+
1920+ my $dst;
1921+ if ( my $dst_dsn = $o->get('dest') ) {
1922+ $dst = Cxn->new(
1923+ dsn => $dst_dsn,
1924+ prev_dsn => ($src ? $src->dsn : undef),
1925+ parent => $o->get('daemonize'),
1926+ DSNParser => $dp,
1927+ OptionParser => $o,
1928+ );
1929 }
1930
1931 if ( !$o->get('help') ) {
1932- if ( !$src_dsn ) {
1933- $o->save_error('Missing or invalid source host');
1934- }
1935- if ( $dst_dsn && !$dst_dsn->{D} ) {
1936- $o->save_error("--dest requires a 'D' (database) part");
1937- }
1938- if ( $dst_dsn && !$dst_dsn->{t} ) {
1939- $o->save_error("--dest requires a 't' (table) part");
1940+ if ( !$src ) {
1941+ $o->save_error('No DSN was specified.');
1942+ }
1943+ if ( $dst && !$dst->dsn->{D} ) {
1944+ $o->save_error("--dest requires a 'D' (database) part.");
1945+ }
1946+ if ( $dst && !$dst->dsn->{t} ) {
1947+ $o->save_error("--dest requires a 't' (table) part.");
1948 }
1949 }
1950
1951 $o->usage_or_errors();
1952
1953 # ########################################################################
1954- # Make common modules.
1955- # ########################################################################
1956- my $q = new Quoter();
1957- my %modules = (
1958- o => $o,
1959- dp => $dp,
1960- q => $q,
1961- );
1962-
1963- # ########################################################################
1964- # Start working.
1965- # ########################################################################
1966- my $dbh = get_cxn($src_dsn, 1, %modules);
1967- my $start = time();
1968- my $end = $start + ($o->get('run-time') || 0); # When we should exit
1969- my $now = $start;
1970- my $dst_dbh;
1971+ # Connect to MySQL.
1972+ # ########################################################################
1973+ my $q = Quoter->new();
1974+
1975+ $src->connect();
1976+
1977 my $ins_sth;
1978-
1979- # Since the user might not have specified a hostname for the connection,
1980- # try to extract it from the $dbh
1981- if ( !$src_dsn->{h} ) {
1982- ($src_dsn->{h}) = $dbh->{mysql_hostinfo} =~ m/(\w+) via/;
1983- PTDEBUG && _d('Got source host from dbh:', $src_dsn->{h});
1984- }
1985-
1986- if ( $dst_dsn ) {
1987- my $db_tbl = join('.',
1988- map { $q->quote($_) }
1989- grep { $_ }
1990- ( $dst_dsn->{D}, $dst_dsn->{t} ));
1991- $dst_dbh = get_cxn($dst_dsn, 1, %modules);
1992- my $sql = "INSERT IGNORE INTO $db_tbl VALUES (?, ?)";
1993- PTDEBUG && _d('insert sql:', $sql);
1994- $ins_sth = $dst_dbh->prepare($sql);
1995+ if ( $dst ) {
1996+ $dst->connect();
1997+ my $db_tbl = $q->join_quote($dst->dsn->{D}, $dst->dsn->{t});
1998+ my $sql = "INSERT IGNORE INTO $db_tbl VALUES (?, ?)";
1999+ PTDEBUG && _d('--dest INSERT SQL:', $sql);
2000+ $ins_sth = $dst->dbh->prepare($sql);
2001 }
2002
2003 # ########################################################################
2004@@ -3489,6 +3789,16 @@
2005 $daemon->make_PID_file();
2006 }
2007
2008+ # If we daemonized, the parent has already exited and we're the child.
2009+ # We shared a copy of every Cxn with the parent, and the parent's copies
2010+ # were destroyed but the dbhs were not disconnected because the parent
2011+ # attrib was true. Now, as the child, set it false so the dbhs will be
2012+ # disconnected when our Cxn copies are destroyed. If we didn't daemonize,
2013+ # then we're not really a parent (since we have no children), so set it
2014+ # false to auto-disconnect the dbhs when our Cxns are destroyed.
2015+ $src->{parent} = 0;
2016+ $dst->{parent} = 0 if $dst;
2017+
2018 # ########################################################################
2019 # Do the version-check
2020 # ########################################################################
2021@@ -3496,8 +3806,8 @@
2022 VersionCheck::version_check(
2023 force => $o->got('version-check'),
2024 instances => [
2025- { dbh => $dbh, dsn => $src_dsn },
2026- ($dst_dbh ? { dbh => $dst_dbh, dsn => $dst_dsn } : ())
2027+ { dbh => $src->dbh, dsn => $src->dsn },
2028+ ($dst ? { dbh => $dst->dbh, dsn => $dst->dsn } : ())
2029 ],
2030 );
2031 }
2032@@ -3505,43 +3815,77 @@
2033 # ########################################################################
2034 # Start finding and logging foreign key errors.
2035 # ########################################################################
2036- while ( # Quit if:
2037- ($start == $end || $now < $end) # time is exceeded
2038- && $oktorun # or instructed to quit
2039- )
2040- {
2041- my $text = $dbh->selectrow_hashref("SHOW /*!40100 ENGINE*/ INNODB STATUS")->{Status};
2042- my ($ts, $fk_error) = get_fk_error($text);
2043- PTDEBUG && _d('ts:', $ts, 'fk error:', $fk_error);
2044-
2045- if ( $ts && $fk_error ) {
2046- # Save and/or print the foreign key error.
2047- if ( $ins_sth ) {
2048- my $fk_ts = parse_timestamp($ts);
2049- PTDEBUG && _d('Saving fk error', $ts, $fk_error);
2050- eval {
2051- $ins_sth->execute($fk_ts, $fk_error);
2052- };
2053+ my $run_time = Runtime->new(
2054+ run_time => $o->get('run-time'),
2055+ now => sub { return time },
2056+ );
2057+
2058+ my $interval = $o->get('interval');
2059+ my $iters = $o->get('iterations');
2060+ PTDEBUG && _d('iterations:', $iters, 'interval:', $interval);
2061+
2062+ ITERATION:
2063+ while (
2064+ $oktorun
2065+ && $run_time->have_time()
2066+ && (!defined $iters || $iters--)
2067+ ) {
2068+ my ($ts, $fk_error);
2069+ eval {
2070+ my $sql = "SHOW /*!40100 ENGINE*/ INNODB STATUS "
2071+ . "/* pt-fk-error-logger */";
2072+ PTDEBUG && _d($sql);
2073+ my $text = $src->dbh->selectrow_hashref($sql)->{status};
2074+ ($ts, $fk_error) = get_fk_error($text);
2075+ };
2076+ if ( my $e = $EVAL_ERROR ) {
2077+ PTDEBUG && _d('Error getting InnoDB status:', $e);
2078+ if ( $src->lost_connection($e) ) {
2079+ eval { $src->connect() };
2080 if ( $EVAL_ERROR ) {
2081- warn $EVAL_ERROR;
2082- PTDEBUG && _d($EVAL_ERROR);
2083- }
2084- }
2085- print "$ts $fk_error\n\n" if $o->get('print') || !$o->got('dest');
2086- }
2087-
2088- # If there's an --interval argument, run forever or till specified.
2089- # Otherwise just run once.
2090- if ( $o->get('interval') ) {
2091- sleep($o->get('interval'));
2092- $now = time();
2093+ warn "Lost connection to MySQL. Will try to reconnect "
2094+ . "in the next iteration.\n";
2095+ }
2096+ else {
2097+ PTDEBUG && _d('Reconnected to MySQL');
2098+ redo ITERATION;
2099+ }
2100+ }
2101+ else {
2102+ warn "Error parsing SHOW ENGINE INNODB STATUS: $EVAL_ERROR";
2103+ $exit_status |= 1;
2104+ }
2105 }
2106 else {
2107- $oktorun = 0;
2108+ if ( $ts && $fk_error ) {
2109+ # Save and/or print the foreign key error.
2110+ if ( $ins_sth ) {
2111+ my $fk_ts = parse_timestamp($ts);
2112+ PTDEBUG && _d('Saving fk error', $ts, $fk_error);
2113+ eval {
2114+ $ins_sth->execute($fk_ts, $fk_error);
2115+ };
2116+ if ( $EVAL_ERROR ) {
2117+ warn $EVAL_ERROR;
2118+ PTDEBUG && _d($EVAL_ERROR);
2119+ }
2120+ }
2121+
2122+ if ( !$o->get('quiet') ) {
2123+ print "$ts $fk_error\n\n";
2124+ }
2125+ }
2126+ }
2127+
2128+ # Sleep if there's an --iteration left.
2129+ if ( !defined $iters || $iters ) {
2130+ PTDEBUG && _d('Sleeping', $interval, 'seconds');
2131+ sleep $interval;
2132 }
2133 }
2134
2135- return 0;
2136+ PTDEBUG && _d('Done running, exiting', $exit_status);
2137+ return $exit_status;
2138 }
2139
2140 # ############################################################################
2141@@ -3550,9 +3894,13 @@
2142
2143 sub get_fk_error {
2144 my ( $text ) = @_;
2145+ PTDEBUG && _d($text);
2146
2147 # Quick check if text even has a foreign key error.
2148- return unless $text =~ m/LATEST FOREIGN KEY ERROR/;
2149+ if ( $text !~ m/LATEST FOREIGN KEY ERROR/ ) {
2150+ PTDEBUG && _d('No fk error');
2151+ return;
2152+ }
2153
2154 # InnoDB timestamp
2155 my $idb_ts = qr/((?:\d{6}|\d{4}-\d\d-\d\d) .\d:\d\d:\d\d)/;
2156@@ -3564,24 +3912,11 @@
2157 return $ts, $fke;
2158 }
2159
2160-# Catches signals so the program can exit gracefully.
2161-sub finish {
2162- my ($signal) = @_;
2163- print STDERR "Exiting on SIG$signal.\n";
2164+sub sig_int {
2165+ my ( $signal ) = @_;
2166 $oktorun = 0;
2167-}
2168-
2169-sub get_cxn {
2170- my ( $dsn, $ac, %args ) = @_;
2171- my $o = $args{o};
2172- my $dp = $args{dp};
2173-
2174- if ( $o->get('ask-pass') ) {
2175- $dsn->{p} = OptionParser::prompt_noecho("Enter password: ");
2176- }
2177- my $dbh = $dp->get_dbh($dp->get_cxn_params($dsn), {AutoCommit => $ac});
2178- $dbh->{InactiveDestroy} = 1; # Because of forking.
2179- return $dbh;
2180+ print STDERR "# Caught SIG$signal. Use 'kill -ABRT $PID' if "
2181+ . "the tool does not exit normally in a few seconds.\n";
2182 }
2183
2184 sub _d {
2185@@ -3606,22 +3941,28 @@
2186
2187 =head1 NAME
2188
2189-pt-fk-error-logger - Extract and log MySQL foreign key errors.
2190+pt-fk-error-logger - Log MySQL foreign key errors.
2191
2192 =head1 SYNOPSIS
2193
2194-Usage: pt-fk-error-logger [OPTION...] SOURCE_DSN
2195+Usage: pt-fk-error-logger [OPTIONS] DSN
2196
2197-pt-fk-error-logger extracts and saves information about the most recent foreign
2198-key errors in a MySQL server.
2199+pt-fk-error-logger logs information about foreign key errors on the given
2200+DSN. Information is printed to C<STDOUT>, and it can also be saved to a
2201+table by specifying L<"--dest">. The tool runs for forever unless
2202+L<"--run-time"> or L<"--iterations"> is specified.
2203
2204 Print foreign key errors on host1:
2205
2206 pt-fk-error-logger h=host1
2207
2208-Save foreign key errors on host1 to db.foreign_key_errors table on host2:
2209-
2210- pt-fk-error-logger h=host1 --dest h=host1,D=db,t=foreign_key_errors
2211+Print foreign key errors on host1 once then exit:
2212+
2213+ pt-fk-error-logger h=host1 --iterations 1
2214+
2215+Save foreign key errors on host1 to percona_schema.fke on host2:
2216+
2217+ pt-fk-error-logger h=host1 --dest h=host2,D=percona_schema,t=fke
2218
2219 =head1 RISKS
2220
2221@@ -3650,11 +3991,15 @@
2222 way. Foreign key errors are uniquely identified by their timestamp.
2223 Only new (more recent) errors are printed or saved.
2224
2225+By default the tool runs forever, checking every L<"--interval"> seconds
2226+for new foreign key errors. Specify L<"--run-time"> and/or L<"--iterations">
2227+to limit how long the tool runs.
2228+
2229 =head1 OUTPUT
2230
2231-If L<"--print"> is given or no L<"--dest"> is given, then pt-fk-error-logger
2232-prints the foreign key error text to STDOUT exactly as it appeared in
2233-C<SHOW INNODB STATUS>.
2234+The foreign key error text from C<SHOW ENGINE INNODB STATUS> is printed
2235+to C<STDOUT>, unless L<"--quiet"> is specified. Errors and warnings
2236+are printed to C<STDERR>.
2237
2238 =head1 OPTIONS
2239
2240@@ -3698,11 +4043,12 @@
2241
2242 type: DSN
2243
2244-DSN for where to store foreign key errors; specify at least a database (D) and table (t).
2245+Save foreign key errors in this table. The DSN must specify a database (D)
2246+and table (t).
2247
2248-Missing values are filled in with the same values from the source host, so you
2249-can usually omit most parts of this argument if you're storing foreign key
2250-errors on the same server on which they happen.
2251+Missing DSN values are inherited from the DSN being monitored, so you
2252+can omit most values if you're saving foreign key errors on the same
2253+host.
2254
2255 The following table is suggested:
2256
2257@@ -3726,10 +4072,21 @@
2258
2259 =item --interval
2260
2261-type: time; default: 0
2262+type: time; default: 30
2263
2264 How often to check for foreign key errors.
2265
2266+=item --iterations
2267+
2268+type: int
2269+
2270+How many times to check for foreign key errors. By default, this option
2271+is undefined which means an infinite number of iterations. The tool always
2272+exits for L<"--run-time">, regardless of the value specified for this option.
2273+For example, the tool will exit after 1 minute with
2274+C<--run-time 1m --iterations 4 --interval 30> because 4 iterations at 30
2275+second intervals would take 2 minutes, longer than the 1 mintue run-time.
2276+
2277 =item --log
2278
2279 type: string
2280@@ -3746,10 +4103,11 @@
2281
2282 type: string
2283
2284-Create the given PID file when daemonized. The file contains the process ID of
2285-the daemonized instance. The PID file is removed when the daemonized instance
2286-exits. The program checks for the existence of the PID file when starting; if
2287-it exists and the process with the matching PID exists, the program exits.
2288+Create the given PID file. The tool won't start if the PID file already
2289+exists and the PID it contains is different than the current PID. However,
2290+if the PID file exists and the PID it contains is no longer running, the
2291+tool will overwrite the PID file with the current PID. The PID file is
2292+removed automatically when the tool exits.
2293
2294 =item --port
2295
2296@@ -3757,15 +4115,15 @@
2297
2298 Port number to use for connection.
2299
2300-=item --print
2301+=item --quiet
2302
2303-Print results on standard output. See L<"OUTPUT"> for more.
2304+Do not print foreign key errors; only print errors and warnings to C<STDERR>.
2305
2306 =item --run-time
2307
2308 type: time
2309
2310-How long to run before exiting.
2311+How long to run before exiting. By default, the tool runs forever.
2312
2313 =item --set-vars
2314
2315@@ -3948,7 +4306,7 @@
2316
2317 =head1 COPYRIGHT, LICENSE, AND WARRANTY
2318
2319-This program is copyright 2011-2012 Percona Ireland Ltd.
2320+This program is copyright 2011-2013 Percona Ireland Ltd.
2321
2322 THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
2323 WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
2324
2325=== modified file 'bin/pt-heartbeat'
2326--- bin/pt-heartbeat 2013-02-22 17:47:57 +0000
2327+++ bin/pt-heartbeat 2013-02-27 23:41:26 +0000
2328@@ -5664,10 +5664,11 @@
2329
2330 type: string
2331
2332-Create the given PID file when daemonized. The file contains the process ID of
2333-the daemonized instance. The PID file is removed when the daemonized instance
2334-exits. The program checks for the existence of the PID file when starting; if
2335-it exists and the process with the matching PID exists, the program exits.
2336+Create the given PID file. The tool won't start if the PID file already
2337+exists and the PID it contains is different than the current PID. However,
2338+if the PID file exists and the PID it contains is no longer running, the
2339+tool will overwrite the PID file with the current PID. The PID file is
2340+removed automatically when the tool exits.
2341
2342 =item --port
2343
2344
2345=== modified file 'bin/pt-kill'
2346--- bin/pt-kill 2013-02-22 17:47:57 +0000
2347+++ bin/pt-kill 2013-02-27 23:41:26 +0000
2348@@ -4969,23 +4969,24 @@
2349 }
2350
2351 my $self = {
2352- dsn => $dsn,
2353- dbh => $args{dbh},
2354- dsn_name => $dp->as_string($dsn, [qw(h P S)]),
2355- hostname => '',
2356- set => $args{set},
2357- NAME_lc => defined($args{NAME_lc}) ? $args{NAME_lc} : 1,
2358- dbh_set => 0,
2359- OptionParser => $o,
2360- DSNParser => $dp,
2361+ dsn => $dsn,
2362+ dbh => $args{dbh},
2363+ dsn_name => $dp->as_string($dsn, [qw(h P S)]),
2364+ hostname => '',
2365+ set => $args{set},
2366+ NAME_lc => defined($args{NAME_lc}) ? $args{NAME_lc} : 1,
2367+ dbh_set => 0,
2368+ OptionParser => $o,
2369+ DSNParser => $dp,
2370 is_cluster_node => undef,
2371+ parent => $args{parent},
2372 };
2373
2374 return bless $self, $class;
2375 }
2376
2377 sub connect {
2378- my ( $self ) = @_;
2379+ my ( $self, %opts ) = @_;
2380 my $dsn = $self->{dsn};
2381 my $dp = $self->{DSNParser};
2382 my $o = $self->{OptionParser};
2383@@ -4996,11 +4997,18 @@
2384 $dsn->{p} = OptionParser::prompt_noecho("Enter MySQL password: ");
2385 $self->{asked_for_pass} = 1;
2386 }
2387- $dbh = $dp->get_dbh($dp->get_cxn_params($dsn), { AutoCommit => 1 });
2388+ $dbh = $dp->get_dbh(
2389+ $dp->get_cxn_params($dsn),
2390+ {
2391+ AutoCommit => 1,
2392+ %opts,
2393+ },
2394+ );
2395 }
2396- PTDEBUG && _d($dbh, 'Connected dbh to', $self->{name});
2397
2398- return $self->set_dbh($dbh);
2399+ $dbh = $self->set_dbh($dbh);
2400+ PTDEBUG && _d($dbh, 'Connected dbh to', $self->{hostname},$self->{dsn_name});
2401+ return $dbh;
2402 }
2403
2404 sub set_dbh {
2405@@ -5023,6 +5031,11 @@
2406 $self->{hostname} = $hostname;
2407 }
2408
2409+ if ( $self->{parent} ) {
2410+ PTDEBUG && _d($dbh, 'Setting InactiveDestroy=1 in parent');
2411+ $dbh->{InactiveDestroy} = 1;
2412+ }
2413+
2414 if ( my $set = $self->{set}) {
2415 $set->($dbh);
2416 }
2417@@ -5032,6 +5045,13 @@
2418 return $dbh;
2419 }
2420
2421+sub lost_connection {
2422+ my ($self, $e) = @_;
2423+ return 0 unless $e;
2424+ return $e =~ m/MySQL server has gone away/
2425+ || $e =~ m/Lost connection to MySQL server/;
2426+}
2427+
2428 sub dbh {
2429 my ($self) = @_;
2430 return $self->{dbh};
2431@@ -5050,12 +5070,21 @@
2432
2433 sub DESTROY {
2434 my ($self) = @_;
2435- if ( $self->{dbh}
2436- && blessed($self->{dbh})
2437- && $self->{dbh}->can("disconnect") ) {
2438- PTDEBUG && _d('Disconnecting dbh', $self->{dbh}, $self->{name});
2439+
2440+ PTDEBUG && _d('Destroying cxn');
2441+
2442+ if ( $self->{parent} ) {
2443+ PTDEBUG && _d($self->{dbh}, 'Not disconnecting dbh in parent');
2444+ }
2445+ elsif ( $self->{dbh}
2446+ && blessed($self->{dbh})
2447+ && $self->{dbh}->can("disconnect") )
2448+ {
2449+ PTDEBUG && _d($self->{dbh}, 'Disconnecting dbh on', $self->{hostname},
2450+ $self->{dsn_name});
2451 $self->{dbh}->disconnect();
2452 }
2453+
2454 return;
2455 }
2456
2457@@ -6464,6 +6493,7 @@
2458 $cxn = Cxn->new(
2459 dsn_string => shift @ARGV,
2460 NAME_lc => 0,
2461+ parent => $o->get('daemonize'),
2462 DSNParser => $dp,
2463 OptionParser => $o,
2464 );
2465@@ -6643,6 +6673,15 @@
2466 $daemon->make_PID_file();
2467 }
2468
2469+ # If we daemonized, the parent has already exited and we're the child.
2470+ # We shared a copy of every Cxn with the parent, and the parent's copies
2471+ # were destroyed but the dbhs were not disconnected because the parent
2472+ # attrib was true. Now, as the child, set it false so the dbhs will be
2473+ # disconnected when our Cxn copies are destroyed. If we didn't daemonize,
2474+ # then we're not really a parent (since we have no children), so set it
2475+ # false to auto-disconnect the dbhs when our Cxns are destroyed.
2476+ $cxn->{parent} = 0 if $cxn;
2477+
2478 # ########################################################################
2479 # Do the version-check
2480 # ########################################################################
2481@@ -7311,10 +7350,11 @@
2482
2483 type: string
2484
2485-Create the given PID file when daemonized. The file contains the process ID of
2486-the daemonized instance. The PID file is removed when the daemonized instance
2487-exits. The program checks for the existence of the PID file when starting; if
2488-it exists and the process with the matching PID exists, the program exits.
2489+Create the given PID file. The tool won't start if the PID file already
2490+exists and the PID it contains is different than the current PID. However,
2491+if the PID file exists and the PID it contains is no longer running, the
2492+tool will overwrite the PID file with the current PID. The PID file is
2493+removed automatically when the tool exits.
2494
2495 =item --port
2496
2497
2498=== modified file 'bin/pt-online-schema-change'
2499--- bin/pt-online-schema-change 2013-02-22 17:47:57 +0000
2500+++ bin/pt-online-schema-change 2013-02-27 23:41:26 +0000
2501@@ -4018,23 +4018,24 @@
2502 }
2503
2504 my $self = {
2505- dsn => $dsn,
2506- dbh => $args{dbh},
2507- dsn_name => $dp->as_string($dsn, [qw(h P S)]),
2508- hostname => '',
2509- set => $args{set},
2510- NAME_lc => defined($args{NAME_lc}) ? $args{NAME_lc} : 1,
2511- dbh_set => 0,
2512- OptionParser => $o,
2513- DSNParser => $dp,
2514+ dsn => $dsn,
2515+ dbh => $args{dbh},
2516+ dsn_name => $dp->as_string($dsn, [qw(h P S)]),
2517+ hostname => '',
2518+ set => $args{set},
2519+ NAME_lc => defined($args{NAME_lc}) ? $args{NAME_lc} : 1,
2520+ dbh_set => 0,
2521+ OptionParser => $o,
2522+ DSNParser => $dp,
2523 is_cluster_node => undef,
2524+ parent => $args{parent},
2525 };
2526
2527 return bless $self, $class;
2528 }
2529
2530 sub connect {
2531- my ( $self ) = @_;
2532+ my ( $self, %opts ) = @_;
2533 my $dsn = $self->{dsn};
2534 my $dp = $self->{DSNParser};
2535 my $o = $self->{OptionParser};
2536@@ -4045,11 +4046,18 @@
2537 $dsn->{p} = OptionParser::prompt_noecho("Enter MySQL password: ");
2538 $self->{asked_for_pass} = 1;
2539 }
2540- $dbh = $dp->get_dbh($dp->get_cxn_params($dsn), { AutoCommit => 1 });
2541+ $dbh = $dp->get_dbh(
2542+ $dp->get_cxn_params($dsn),
2543+ {
2544+ AutoCommit => 1,
2545+ %opts,
2546+ },
2547+ );
2548 }
2549- PTDEBUG && _d($dbh, 'Connected dbh to', $self->{name});
2550
2551- return $self->set_dbh($dbh);
2552+ $dbh = $self->set_dbh($dbh);
2553+ PTDEBUG && _d($dbh, 'Connected dbh to', $self->{hostname},$self->{dsn_name});
2554+ return $dbh;
2555 }
2556
2557 sub set_dbh {
2558@@ -4072,6 +4080,11 @@
2559 $self->{hostname} = $hostname;
2560 }
2561
2562+ if ( $self->{parent} ) {
2563+ PTDEBUG && _d($dbh, 'Setting InactiveDestroy=1 in parent');
2564+ $dbh->{InactiveDestroy} = 1;
2565+ }
2566+
2567 if ( my $set = $self->{set}) {
2568 $set->($dbh);
2569 }
2570@@ -4081,6 +4094,13 @@
2571 return $dbh;
2572 }
2573
2574+sub lost_connection {
2575+ my ($self, $e) = @_;
2576+ return 0 unless $e;
2577+ return $e =~ m/MySQL server has gone away/
2578+ || $e =~ m/Lost connection to MySQL server/;
2579+}
2580+
2581 sub dbh {
2582 my ($self) = @_;
2583 return $self->{dbh};
2584@@ -4099,12 +4119,21 @@
2585
2586 sub DESTROY {
2587 my ($self) = @_;
2588- if ( $self->{dbh}
2589- && blessed($self->{dbh})
2590- && $self->{dbh}->can("disconnect") ) {
2591- PTDEBUG && _d('Disconnecting dbh', $self->{dbh}, $self->{name});
2592+
2593+ PTDEBUG && _d('Destroying cxn');
2594+
2595+ if ( $self->{parent} ) {
2596+ PTDEBUG && _d($self->{dbh}, 'Not disconnecting dbh in parent');
2597+ }
2598+ elsif ( $self->{dbh}
2599+ && blessed($self->{dbh})
2600+ && $self->{dbh}->can("disconnect") )
2601+ {
2602+ PTDEBUG && _d($self->{dbh}, 'Disconnecting dbh on', $self->{hostname},
2603+ $self->{dsn_name});
2604 $self->{dbh}->disconnect();
2605 }
2606+
2607 return;
2608 }
2609
2610@@ -10839,10 +10868,11 @@
2611
2612 type: string
2613
2614-Create the given PID file. The file contains the process ID of the tool's
2615-instance. The PID file is removed when the tool exits. The tool checks for
2616-the existence of the PID file when starting; if it exists and the process with
2617-the matching PID exists, the tool exits.
2618+Create the given PID file. The tool won't start if the PID file already
2619+exists and the PID it contains is different than the current PID. However,
2620+if the PID file exists and the PID it contains is no longer running, the
2621+tool will overwrite the PID file with the current PID. The PID file is
2622+removed automatically when the tool exits.
2623
2624 =item --port
2625
2626
2627=== modified file 'bin/pt-query-advisor'
2628--- bin/pt-query-advisor 2013-02-22 17:47:57 +0000
2629+++ bin/pt-query-advisor 2013-02-27 23:41:26 +0000
2630@@ -9092,11 +9092,11 @@
2631
2632 type: string
2633
2634-Create the given PID file when daemonized. The file contains the process
2635-ID of the daemonized instance. The PID file is removed when the
2636-daemonized instance exits. The program checks for the existence of the
2637-PID file when starting; if it exists and the process with the matching PID
2638-exists, the program exits.
2639+Create the given PID file. The tool won't start if the PID file already
2640+exists and the PID it contains is different than the current PID. However,
2641+if the PID file exists and the PID it contains is no longer running, the
2642+tool will overwrite the PID file with the current PID. The PID file is
2643+removed automatically when the tool exits.
2644
2645 =item --port
2646
2647
2648=== modified file 'bin/pt-query-digest'
2649--- bin/pt-query-digest 2013-02-25 15:13:35 +0000
2650+++ bin/pt-query-digest 2013-02-27 23:41:26 +0000
2651@@ -14967,11 +14967,11 @@
2652
2653 type: string
2654
2655-Create the given PID file when daemonized. The file contains the process
2656-ID of the daemonized instance. The PID file is removed when the
2657-daemonized instance exits. The program checks for the existence of the
2658-PID file when starting; if it exists and the process with the matching PID
2659-exists, the program exits.
2660+Create the given PID file. The tool won't start if the PID file already
2661+exists and the PID it contains is different than the current PID. However,
2662+if the PID file exists and the PID it contains is no longer running, the
2663+tool will overwrite the PID file with the current PID. The PID file is
2664+removed automatically when the tool exits.
2665
2666 =item --port
2667
2668
2669=== modified file 'bin/pt-show-grants'
2670--- bin/pt-show-grants 2013-01-03 00:54:18 +0000
2671+++ bin/pt-show-grants 2013-02-27 23:41:26 +0000
2672@@ -2093,14 +2093,11 @@
2673
2674 type: string
2675
2676-Create the given PID file. The file contains the process ID of the script.
2677-The PID file is removed when the script exits. Before starting, the script
2678-checks if the PID file already exists. If it does not, then the script creates
2679-and writes its own PID to it. If it does, then the script checks the following:
2680-if the file contains a PID and a process is running with that PID, then
2681-the script dies; or, if there is no process running with that PID, then the
2682-script overwrites the file with its own PID and starts; else, if the file
2683-contains no PID, then the script dies.
2684+Create the given PID file. The tool won't start if the PID file already
2685+exists and the PID it contains is different than the current PID. However,
2686+if the PID file exists and the PID it contains is no longer running, the
2687+tool will overwrite the PID file with the current PID. The PID file is
2688+removed automatically when the tool exits.
2689
2690 =item --port
2691
2692
2693=== modified file 'bin/pt-slave-delay'
2694--- bin/pt-slave-delay 2013-02-22 15:00:55 +0000
2695+++ bin/pt-slave-delay 2013-02-27 23:41:26 +0000
2696@@ -4478,11 +4478,11 @@
2697
2698 type: string
2699
2700-Create the given PID file when daemonized. The file contains the process
2701-ID of the daemonized instance. The PID file is removed when the
2702-daemonized instance exits. The program checks for the existence of the
2703-PID file when starting; if it exists and the process with the matching PID
2704-exists, the program exits.
2705+Create the given PID file. The tool won't start if the PID file already
2706+exists and the PID it contains is different than the current PID. However,
2707+if the PID file exists and the PID it contains is no longer running, the
2708+tool will overwrite the PID file with the current PID. The PID file is
2709+removed automatically when the tool exits.
2710
2711 =item --port
2712
2713
2714=== modified file 'bin/pt-slave-find'
2715--- bin/pt-slave-find 2013-02-01 17:06:13 +0000
2716+++ bin/pt-slave-find 2013-02-27 23:41:26 +0000
2717@@ -3960,14 +3960,11 @@
2718
2719 type: string
2720
2721-Create the given PID file. The file contains the process ID of the script.
2722-The PID file is removed when the script exits. Before starting, the script
2723-checks if the PID file already exists. If it does not, then the script creates
2724-and writes its own PID to it. If it does, then the script checks the following:
2725-if the file contains a PID and a process is running with that PID, then
2726-the script dies; or, if there is no process running with that PID, then the
2727-script overwrites the file with its own PID and starts; else, if the file
2728-contains no PID, then the script dies.
2729+Create the given PID file. The tool won't start if the PID file already
2730+exists and the PID it contains is different than the current PID. However,
2731+if the PID file exists and the PID it contains is no longer running, the
2732+tool will overwrite the PID file with the current PID. The PID file is
2733+removed automatically when the tool exits.
2734
2735 =item --port
2736
2737
2738=== modified file 'bin/pt-slave-restart'
2739--- bin/pt-slave-restart 2013-02-22 17:47:57 +0000
2740+++ bin/pt-slave-restart 2013-02-27 23:41:26 +0000
2741@@ -5312,11 +5312,11 @@
2742
2743 type: string
2744
2745-Create the given PID file when daemonized. The file contains the process
2746-ID of the daemonized instance. The PID file is removed when the
2747-daemonized instance exits. The program checks for the existence of the
2748-PID file when starting; if it exists and the process with the matching PID
2749-exists, the program exits.
2750+Create the given PID file. The tool won't start if the PID file already
2751+exists and the PID it contains is different than the current PID. However,
2752+if the PID file exists and the PID it contains is no longer running, the
2753+tool will overwrite the PID file with the current PID. The PID file is
2754+removed automatically when the tool exits.
2755
2756 =item --port
2757
2758
2759=== modified file 'bin/pt-stalk'
2760--- bin/pt-stalk 2013-01-24 19:08:09 +0000
2761+++ bin/pt-stalk 2013-02-27 23:41:26 +0000
2762@@ -1723,7 +1723,11 @@
2763
2764 type: string; default: /var/run/pt-stalk.pid
2765
2766-Create a PID file when daemonized.
2767+Create the given PID file. The tool won't start if the PID file already
2768+exists and the PID it contains is different than the current PID. However,
2769+if the PID file exists and the PID it contains is no longer running, the
2770+tool will overwrite the PID file with the current PID. The PID file is
2771+removed automatically when the tool exits.
2772
2773 =item --plugin
2774
2775
2776=== modified file 'bin/pt-table-checksum'
2777--- bin/pt-table-checksum 2013-02-22 17:47:57 +0000
2778+++ bin/pt-table-checksum 2013-02-27 23:41:26 +0000
2779@@ -3376,23 +3376,24 @@
2780 }
2781
2782 my $self = {
2783- dsn => $dsn,
2784- dbh => $args{dbh},
2785- dsn_name => $dp->as_string($dsn, [qw(h P S)]),
2786- hostname => '',
2787- set => $args{set},
2788- NAME_lc => defined($args{NAME_lc}) ? $args{NAME_lc} : 1,
2789- dbh_set => 0,
2790- OptionParser => $o,
2791- DSNParser => $dp,
2792+ dsn => $dsn,
2793+ dbh => $args{dbh},
2794+ dsn_name => $dp->as_string($dsn, [qw(h P S)]),
2795+ hostname => '',
2796+ set => $args{set},
2797+ NAME_lc => defined($args{NAME_lc}) ? $args{NAME_lc} : 1,
2798+ dbh_set => 0,
2799+ OptionParser => $o,
2800+ DSNParser => $dp,
2801 is_cluster_node => undef,
2802+ parent => $args{parent},
2803 };
2804
2805 return bless $self, $class;
2806 }
2807
2808 sub connect {
2809- my ( $self ) = @_;
2810+ my ( $self, %opts ) = @_;
2811 my $dsn = $self->{dsn};
2812 my $dp = $self->{DSNParser};
2813 my $o = $self->{OptionParser};
2814@@ -3403,11 +3404,18 @@
2815 $dsn->{p} = OptionParser::prompt_noecho("Enter MySQL password: ");
2816 $self->{asked_for_pass} = 1;
2817 }
2818- $dbh = $dp->get_dbh($dp->get_cxn_params($dsn), { AutoCommit => 1 });
2819+ $dbh = $dp->get_dbh(
2820+ $dp->get_cxn_params($dsn),
2821+ {
2822+ AutoCommit => 1,
2823+ %opts,
2824+ },
2825+ );
2826 }
2827- PTDEBUG && _d($dbh, 'Connected dbh to', $self->{name});
2828
2829- return $self->set_dbh($dbh);
2830+ $dbh = $self->set_dbh($dbh);
2831+ PTDEBUG && _d($dbh, 'Connected dbh to', $self->{hostname},$self->{dsn_name});
2832+ return $dbh;
2833 }
2834
2835 sub set_dbh {
2836@@ -3430,6 +3438,11 @@
2837 $self->{hostname} = $hostname;
2838 }
2839
2840+ if ( $self->{parent} ) {
2841+ PTDEBUG && _d($dbh, 'Setting InactiveDestroy=1 in parent');
2842+ $dbh->{InactiveDestroy} = 1;
2843+ }
2844+
2845 if ( my $set = $self->{set}) {
2846 $set->($dbh);
2847 }
2848@@ -3439,6 +3452,13 @@
2849 return $dbh;
2850 }
2851
2852+sub lost_connection {
2853+ my ($self, $e) = @_;
2854+ return 0 unless $e;
2855+ return $e =~ m/MySQL server has gone away/
2856+ || $e =~ m/Lost connection to MySQL server/;
2857+}
2858+
2859 sub dbh {
2860 my ($self) = @_;
2861 return $self->{dbh};
2862@@ -3457,12 +3477,21 @@
2863
2864 sub DESTROY {
2865 my ($self) = @_;
2866- if ( $self->{dbh}
2867- && blessed($self->{dbh})
2868- && $self->{dbh}->can("disconnect") ) {
2869- PTDEBUG && _d('Disconnecting dbh', $self->{dbh}, $self->{name});
2870+
2871+ PTDEBUG && _d('Destroying cxn');
2872+
2873+ if ( $self->{parent} ) {
2874+ PTDEBUG && _d($self->{dbh}, 'Not disconnecting dbh in parent');
2875+ }
2876+ elsif ( $self->{dbh}
2877+ && blessed($self->{dbh})
2878+ && $self->{dbh}->can("disconnect") )
2879+ {
2880+ PTDEBUG && _d($self->{dbh}, 'Disconnecting dbh on', $self->{hostname},
2881+ $self->{dsn_name});
2882 $self->{dbh}->disconnect();
2883 }
2884+
2885 return;
2886 }
2887
2888@@ -11590,14 +11619,11 @@
2889
2890 type: string
2891
2892-Create the given PID file. The file contains the process ID of the script.
2893-The PID file is removed when the script exits. Before starting, the script
2894-checks if the PID file already exists. If it does not, then the script creates
2895-and writes its own PID to it. If it does, then the script checks the following:
2896-if the file contains a PID and a process is running with that PID, then
2897-the script dies; or, if there is no process running with that PID, then the
2898-script overwrites the file with its own PID and starts; else, if the file
2899-contains no PID, then the script dies.
2900+Create the given PID file. The tool won't start if the PID file already
2901+exists and the PID it contains is different than the current PID. However,
2902+if the PID file exists and the PID it contains is no longer running, the
2903+tool will overwrite the PID file with the current PID. The PID file is
2904+removed automatically when the tool exits.
2905
2906 =item --port
2907
2908
2909=== modified file 'bin/pt-table-sync'
2910--- bin/pt-table-sync 2013-02-22 17:47:57 +0000
2911+++ bin/pt-table-sync 2013-02-27 23:41:26 +0000
2912@@ -12051,14 +12051,11 @@
2913
2914 type: string
2915
2916-Create the given PID file. The file contains the process ID of the script.
2917-The PID file is removed when the script exits. Before starting, the script
2918-checks if the PID file already exists. If it does not, then the script creates
2919-and writes its own PID to it. If it does, then the script checks the following:
2920-if the file contains a PID and a process is running with that PID, then
2921-the script dies; or, if there is no process running with that PID, then the
2922-script overwrites the file with its own PID and starts; else, if the file
2923-contains no PID, then the script dies.
2924+Create the given PID file. The tool won't start if the PID file already
2925+exists and the PID it contains is different than the current PID. However,
2926+if the PID file exists and the PID it contains is no longer running, the
2927+tool will overwrite the PID file with the current PID. The PID file is
2928+removed automatically when the tool exits.
2929
2930 =item --port
2931
2932
2933=== modified file 'bin/pt-table-usage'
2934--- bin/pt-table-usage 2013-02-19 20:01:58 +0000
2935+++ bin/pt-table-usage 2013-02-27 23:41:26 +0000
2936@@ -7192,11 +7192,11 @@
2937
2938 type: string
2939
2940-Create the given PID file when running. The file contains the process
2941-ID of the daemonized instance. The PID file is removed when the
2942-daemonized instance exits. The program checks for the existence of the
2943-PID file when starting; if it exists and the process with the matching PID
2944-exists, the program exits.
2945+Create the given PID file. The tool won't start if the PID file already
2946+exists and the PID it contains is different than the current PID. However,
2947+if the PID file exists and the PID it contains is no longer running, the
2948+tool will overwrite the PID file with the current PID. The PID file is
2949+removed automatically when the tool exits.
2950
2951 =item --port
2952
2953
2954=== modified file 'bin/pt-upgrade'
2955--- bin/pt-upgrade 2013-02-22 17:47:57 +0000
2956+++ bin/pt-upgrade 2013-02-27 23:41:26 +0000
2957@@ -13483,11 +13483,11 @@
2958
2959 type: string
2960
2961-Create the given PID file when daemonized. The file contains the process
2962-ID of the daemonized instance. The PID file is removed when the
2963-daemonized instance exits. The program checks for the existence of the
2964-PID file when starting; if it exists and the process with the matching PID
2965-exists, the program exits.
2966+Create the given PID file. The tool won't start if the PID file already
2967+exists and the PID it contains is different than the current PID. However,
2968+if the PID file exists and the PID it contains is no longer running, the
2969+tool will overwrite the PID file with the current PID. The PID file is
2970+removed automatically when the tool exits.
2971
2972 =item --port
2973
2974
2975=== modified file 'bin/pt-variable-advisor'
2976--- bin/pt-variable-advisor 2013-02-22 15:00:55 +0000
2977+++ bin/pt-variable-advisor 2013-02-27 23:41:26 +0000
2978@@ -5752,11 +5752,11 @@
2979
2980 type: string
2981
2982-Create the given PID file when daemonized. The file contains the process
2983-ID of the daemonized instance. The PID file is removed when the
2984-daemonized instance exits. The program checks for the existence of the
2985-PID file when starting; if it exists and the process with the matching PID
2986-exists, the program exits.
2987+Create the given PID file. The tool won't start if the PID file already
2988+exists and the PID it contains is different than the current PID. However,
2989+if the PID file exists and the PID it contains is no longer running, the
2990+tool will overwrite the PID file with the current PID. The PID file is
2991+removed automatically when the tool exits.
2992
2993 =item --port
2994
2995
2996=== modified file 'bin/pt-visual-explain'
2997--- bin/pt-visual-explain 2013-02-19 20:01:58 +0000
2998+++ bin/pt-visual-explain 2013-02-27 23:41:26 +0000
2999@@ -2952,14 +2952,11 @@
3000
3001 type: string
3002
3003-Create the given PID file. The file contains the process ID of the script.
3004-The PID file is removed when the script exits. Before starting, the script
3005-checks if the PID file already exists. If it does not, then the script creates
3006-and writes its own PID to it. If it does, then the script checks the following:
3007-if the file contains a PID and a process is running with that PID, then
3008-the script dies; or, if there is no process running with that PID, then the
3009-script overwrites the file with its own PID and starts; else, if the file
3010-contains no PID, then the script dies.
3011+Create the given PID file. The tool won't start if the PID file already
3012+exists and the PID it contains is different than the current PID. However,
3013+if the PID file exists and the PID it contains is no longer running, the
3014+tool will overwrite the PID file with the current PID. The PID file is
3015+removed automatically when the tool exits.
3016
3017 =item --port
3018
3019
3020=== modified file 'docs/percona-toolkit.pod'
3021--- docs/percona-toolkit.pod 2013-01-03 00:54:18 +0000
3022+++ docs/percona-toolkit.pod 2013-02-27 23:41:26 +0000
3023@@ -175,6 +175,34 @@
3024 For more free, open-source software developed Percona, visit
3025 L<http://www.percona.com/software/>.
3026
3027+=head1 SPECIAL OPTION TYPES
3028+
3029+=over
3030+
3031+=item time
3032+
3033+Time values are seconds by default. For example, C<--run-time 60> means
3034+60 seconds. Time values support an optional suffix: s (seconds),
3035+m (minutes), h (hours), d (days). C<--run-time 1m> means 1 minute
3036+(the same as 60 seconds).
3037+
3038+=item size
3039+
3040+Size values are bytes by default. For example, C<--disk-space-free 1024>
3041+means 1 Kibibyte. Size values support an optional suffix: k (Kibibyte),
3042+M (Mebibyte), G (Gibibyte).
3043+
3044+=item DSN
3045+
3046+See L<"DSN (DATA SOURCE NAME) SPECIFICATIONS">.
3047+
3048+=item Hash, hash, Array, array
3049+
3050+Hash, hash, Array, and array values are comma-separated lists of values.
3051+For example, C<--ignore-tables foo,bar> ignores tables C<foo> and C<bar>.
3052+
3053+=back
3054+
3055 =head1 CONFIGURATION FILES
3056
3057 Percona Toolkit tools can read options from configuration files. The
3058
3059=== modified file 'lib/Cxn.pm'
3060--- lib/Cxn.pm 2013-01-03 00:19:16 +0000
3061+++ lib/Cxn.pm 2013-02-27 23:41:26 +0000
3062@@ -98,23 +98,24 @@
3063 }
3064
3065 my $self = {
3066- dsn => $dsn,
3067- dbh => $args{dbh},
3068- dsn_name => $dp->as_string($dsn, [qw(h P S)]),
3069- hostname => '',
3070- set => $args{set},
3071- NAME_lc => defined($args{NAME_lc}) ? $args{NAME_lc} : 1,
3072- dbh_set => 0,
3073- OptionParser => $o,
3074- DSNParser => $dp,
3075+ dsn => $dsn,
3076+ dbh => $args{dbh},
3077+ dsn_name => $dp->as_string($dsn, [qw(h P S)]),
3078+ hostname => '',
3079+ set => $args{set},
3080+ NAME_lc => defined($args{NAME_lc}) ? $args{NAME_lc} : 1,
3081+ dbh_set => 0,
3082+ OptionParser => $o,
3083+ DSNParser => $dp,
3084 is_cluster_node => undef,
3085+ parent => $args{parent},
3086 };
3087
3088 return bless $self, $class;
3089 }
3090
3091 sub connect {
3092- my ( $self ) = @_;
3093+ my ( $self, %opts ) = @_;
3094 my $dsn = $self->{dsn};
3095 my $dp = $self->{DSNParser};
3096 my $o = $self->{OptionParser};
3097@@ -126,11 +127,18 @@
3098 $dsn->{p} = OptionParser::prompt_noecho("Enter MySQL password: ");
3099 $self->{asked_for_pass} = 1;
3100 }
3101- $dbh = $dp->get_dbh($dp->get_cxn_params($dsn), { AutoCommit => 1 });
3102+ $dbh = $dp->get_dbh(
3103+ $dp->get_cxn_params($dsn),
3104+ {
3105+ AutoCommit => 1,
3106+ %opts,
3107+ },
3108+ );
3109 }
3110- PTDEBUG && _d($dbh, 'Connected dbh to', $self->{name});
3111
3112- return $self->set_dbh($dbh);
3113+ $dbh = $self->set_dbh($dbh);
3114+ PTDEBUG && _d($dbh, 'Connected dbh to', $self->{hostname},$self->{dsn_name});
3115+ return $dbh;
3116 }
3117
3118 sub set_dbh {
3119@@ -163,6 +171,11 @@
3120 $self->{hostname} = $hostname;
3121 }
3122
3123+ if ( $self->{parent} ) {
3124+ PTDEBUG && _d($dbh, 'Setting InactiveDestroy=1 in parent');
3125+ $dbh->{InactiveDestroy} = 1;
3126+ }
3127+
3128 # Call the set callback to let the caller SET any MySQL variables.
3129 if ( my $set = $self->{set}) {
3130 $set->($dbh);
3131@@ -173,6 +186,15 @@
3132 return $dbh;
3133 }
3134
3135+sub lost_connection {
3136+ my ($self, $e) = @_;
3137+ return 0 unless $e;
3138+ return $e =~ m/MySQL server has gone away/
3139+ || $e =~ m/Lost connection to MySQL server/;
3140+ # The 1st pattern means that MySQL itself died or was stopped.
3141+ # The 2nd pattern means that our cxn was killed (KILL <id>).
3142+}
3143+
3144 # Sub: dbh
3145 # Return the cxn's dbh.
3146 sub dbh {
3147@@ -197,12 +219,21 @@
3148
3149 sub DESTROY {
3150 my ($self) = @_;
3151- if ( $self->{dbh}
3152- && blessed($self->{dbh})
3153- && $self->{dbh}->can("disconnect") ) {
3154- PTDEBUG && _d('Disconnecting dbh', $self->{dbh}, $self->{name});
3155+
3156+ PTDEBUG && _d('Destroying cxn');
3157+
3158+ if ( $self->{parent} ) {
3159+ PTDEBUG && _d($self->{dbh}, 'Not disconnecting dbh in parent');
3160+ }
3161+ elsif ( $self->{dbh}
3162+ && blessed($self->{dbh})
3163+ && $self->{dbh}->can("disconnect") )
3164+ {
3165+ PTDEBUG && _d($self->{dbh}, 'Disconnecting dbh on', $self->{hostname},
3166+ $self->{dsn_name});
3167 $self->{dbh}->disconnect();
3168 }
3169+
3170 return;
3171 }
3172
3173
3174=== modified file 'lib/Runtime.pm'
3175--- lib/Runtime.pm 2013-01-03 00:19:16 +0000
3176+++ lib/Runtime.pm 2013-02-27 23:41:26 +0000
3177@@ -18,13 +18,6 @@
3178 # Runtime package
3179 # ###########################################################################
3180 {
3181-# Package: Runtime
3182-# Runtime keeps track of time to control how long a tool's main loop runs.
3183-# This package was created to handle mk-query-digest --run-time-mode event.
3184-# In essence, we abstract time so that the tool doesn't know/care whether
3185-# now() comes from a clock, a log timestamp, or wherever. The creator of
3186-# Runtime object determines how, or from where, time is gotten so that the
3187-# caller of the object can simply ask, "What time is it?".
3188 package Runtime;
3189
3190 use strict;
3191@@ -32,30 +25,24 @@
3192 use English qw(-no_match_vars);
3193 use constant PTDEBUG => $ENV{PTDEBUG} || 0;
3194
3195-# Sub: new
3196-#
3197-# Parameters:
3198-# %args - Arguments
3199-#
3200-# Required Arguments:
3201-# now - Callback that sets current time.
3202-# runtime - Amount of time to run in seconds, or undef for forever.
3203-#
3204-# Returns:
3205-# Runtime object
3206 sub new {
3207 my ( $class, %args ) = @_;
3208- my @required_args = qw(now);
3209+ my @required_args = qw(run_time now);
3210 foreach my $arg ( @required_args ) {
3211- die "I need a $arg argument" unless $args{$arg};
3212- }
3213-
3214- if ( ($args{runtime} || 0) < 0 ) {
3215- die "runtime argument must be greater than zero"
3216- }
3217+ die "I need a $arg argument" unless exists $args{$arg};
3218+ }
3219+
3220+ my $run_time = $args{run_time};
3221+ if ( defined $run_time ) {
3222+ die "run_time must be > 0" if $run_time <= 0;
3223+ }
3224+
3225+ my $now = $args{now};
3226+ die "now must be a callback" unless ref $now eq 'CODE';
3227
3228 my $self = {
3229- %args,
3230+ run_time => $run_time,
3231+ now => $now,
3232 start_time => undef,
3233 end_time => undef,
3234 time_left => undef,
3235@@ -66,8 +53,8 @@
3236 }
3237
3238 # Sub: time_left
3239-# Return the number of runtime seconds left or undef for forever.
3240-# The return may be less than zero if the runtime has been exceeded.
3241+# Return the number of run time seconds left or undef for forever.
3242+# The return may be less than zero if the run time has been exceeded.
3243 # The first call to this subroutine "starts the clock", so to speak,
3244 # if the now callbackup returns a defined value.
3245 #
3246@@ -75,7 +62,7 @@
3247 # %args - Arguments passed to now callback.
3248 #
3249 # Returns:
3250-# Number of runtime seconds left, possibly less than zero, or undef
3251+# Number of run time seconds left, possibly less than zero, or undef
3252 # if running forever.
3253 sub time_left {
3254 my ( $self, %args ) = @_;
3255@@ -99,14 +86,14 @@
3256 # we know the current time.
3257 return unless defined $now;
3258
3259- # If runtime is also defined, then we can determine time left.
3260+ # If run_time is also defined, then we can determine time left.
3261 # If it's not defined, then we're running forever.
3262- my $runtime = $self->{runtime};
3263- return unless defined $runtime;
3264+ my $run_time = $self->{run_time};
3265+ return unless defined $run_time;
3266
3267 # Set the end time once.
3268 if ( !$self->{end_time} ) {
3269- $self->{end_time} = $now + $runtime;
3270+ $self->{end_time} = $now + $run_time;
3271 PTDEBUG && _d("End time:", $self->{end_time});
3272 }
3273
3274@@ -118,7 +105,7 @@
3275 }
3276
3277 # Sub: have_time
3278-# Return true or false if there's runtime left. This sub is a simpler
3279+# Return true or false if there's run time left. This sub is a simpler
3280 # wrapper around <time_left()> which returns true (1) if time left is
3281 # defined and greater than zero or undef, else returns false.
3282 #
3283@@ -131,7 +118,7 @@
3284 my ( $self, %args ) = @_;
3285 my $time_left = $self->time_left(%args);
3286 return 1 if !defined $time_left; # run forever
3287- return $time_left <= 0 ? 0 : 1; # <=0s means runtime has elapsed
3288+ return $time_left <= 0 ? 0 : 1; # <=0s means run time has elapsed
3289 }
3290
3291 # Sub: time_elapsed
3292@@ -173,7 +160,7 @@
3293 $self->{end_time} = undef;
3294 $self->{time_left} = undef;
3295 $self->{stop} = 0;
3296- PTDEBUG && _d("Reset runtime");
3297+ PTDEBUG && _d("Reset run time");
3298 return;
3299 }
3300
3301
3302=== modified file 't/lib/Cxn.t'
3303--- t/lib/Cxn.t 2012-11-08 20:47:00 +0000
3304+++ t/lib/Cxn.t 2013-02-27 23:41:26 +0000
3305@@ -9,7 +9,7 @@
3306 use strict;
3307 use warnings FATAL => 'all';
3308 use English qw(-no_match_vars);
3309-use Test::More tests => 19;
3310+use Test::More;
3311
3312 use Sandbox;
3313 use OptionParser;
3314@@ -252,8 +252,64 @@
3315 $o->get_opts();
3316
3317 # #############################################################################
3318+# The parent of a forked Cxn should not disconnect the dbh in DESTORY
3319+# because the child still has access to it.
3320+# #############################################################################
3321+
3322+my $sync_file = "/tmp/pt-cxn-sync.$PID";
3323+my $outfile = "/tmp/pt-cxn-outfile.$PID";
3324+
3325+my $pid;
3326+{
3327+ my $parent_cxn = make_cxn(
3328+ dsn_string => 'h=127.1,P=12345,u=msandbox,p=msandbox',
3329+ parent => 1,
3330+ );
3331+ $parent_cxn->connect();
3332+
3333+ $pid = fork();
3334+ if ( defined($pid) && $pid == 0 ) {
3335+ # I am the child.
3336+ # Wait for the parent to leave this code block which will cause
3337+ # the $parent_cxn to be destroyed.
3338+ PerconaTest::wait_for_files($sync_file);
3339+ $parent_cxn->{parent} = 0;
3340+ eval {
3341+ $parent_cxn->dbh->do("SELECT 123 INTO OUTFILE '$outfile'");
3342+ $parent_cxn->dbh->disconnect();
3343+ };
3344+ warn $EVAL_ERROR if $EVAL_ERROR;
3345+ exit;
3346+ }
3347+}
3348+
3349+# Let the child know that we (the parent) have left that ^ code block,
3350+# so our copy of $parent_cxn has been destroyed, but hopefully the child's
3351+# copy is still alive, i.e. has an active/not-disconnected dbh.
3352+diag(`touch $sync_file`);
3353+
3354+# Wait for the child.
3355+waitpid($pid, 0);
3356+
3357+ok(
3358+ -f $outfile,
3359+ "Child created outfile"
3360+);
3361+
3362+my $output = `cat $outfile 2>/dev/null`;
3363+
3364+is(
3365+ $output,
3366+ "123\n",
3367+ "Child executed query"
3368+);
3369+
3370+unlink $sync_file if -f $sync_file;
3371+unlink $outfile if -f $outfile;
3372+
3373+# #############################################################################
3374 # Done.
3375 # #############################################################################
3376 $master_dbh->disconnect() if $master_dbh;
3377 ok($sb->ok(), "Sandbox servers") or BAIL_OUT(__FILE__ . " broke the sandbox");
3378-exit;
3379+done_testing;
3380
3381=== modified file 't/pt-deadlock-logger/basics.t'
3382--- t/pt-deadlock-logger/basics.t 2012-10-22 21:30:29 +0000
3383+++ t/pt-deadlock-logger/basics.t 2013-02-27 23:41:26 +0000
3384@@ -11,6 +11,8 @@
3385 use English qw(-no_match_vars);
3386 use Test::More;
3387
3388+$ENV{PERCONA_TOOLKIT_TEST_USE_DSN_NAMES} = 1;
3389+
3390 use PerconaTest;
3391 use Sandbox;
3392 require "$trunk/bin/pt-deadlock-logger";
3393@@ -25,8 +27,8 @@
3394 }
3395
3396 my $output;
3397-my $cnf = "/tmp/12345/my.sandbox.cnf";
3398-my $cmd = "$trunk/bin/pt-deadlock-logger -F $cnf h=127.1";
3399+my $dsn = $sb->dsn_for('master');
3400+my @args = ($dsn, qw(--iterations 1));
3401
3402 $dbh1->commit;
3403 $dbh2->commit;
3404@@ -90,21 +92,30 @@
3405 $output = $dbh1->selectrow_hashref('show /*!40101 engine*/ innodb status')->{status};
3406 like($output, qr/WE ROLL BACK/, 'There was a deadlock');
3407
3408-$output = `$cmd --print`;
3409+$output = output(
3410+ sub {
3411+ pt_deadlock_logger::main(@args);
3412+ }
3413+);
3414+
3415 like(
3416 $output,
3417 qr/127\.1.+msandbox.+GEN_CLUST_INDEX/,
3418 'Deadlock logger prints the output'
3419 );
3420
3421-$output = `$cmd`;
3422-like(
3423+$output = output(
3424+ sub {
3425+ pt_deadlock_logger::main(@args, qw(--quiet));
3426+ }
3427+);
3428+
3429+is(
3430 $output,
3431- qr/127\.1.+msandbox.+GEN_CLUST_INDEX/,
3432- '--print is implicit'
3433+ "",
3434+ "No output with --quiet"
3435 );
3436
3437-
3438 # #############################################################################
3439 # Issue 943: mk-deadlock-logger reports the same deadlock with --interval
3440 # #############################################################################
3441@@ -112,55 +123,59 @@
3442 # The deadlock from above won't be re-printed so even after running for
3443 # 3 seconds and checking multiple times only the single, 3 line deadlock
3444 # should be reported.
3445-chomp($output = `$cmd --run-time 3 | wc -l`);
3446+
3447+$output = output(
3448+ sub {
3449+ pt_deadlock_logger::main(@args, qw(--run-time 3));
3450+ }
3451+);
3452 $output =~ s/^\s+//;
3453+my @lines = split("\n", $output);
3454 is(
3455- $output,
3456+ scalar @lines,
3457 3,
3458 "Doesn't re-print same deadlock (issue 943)"
3459-);
3460+) or diag($output);
3461
3462 # #############################################################################
3463 # Check that deadlocks from previous test were stored in table.
3464 # #############################################################################
3465-`$cmd --dest D=test,t=deadlocks --create-dest-table`;
3466+$output = output(
3467+ sub {
3468+ pt_deadlock_logger::main(@args, '--dest', 'D=test,t=deadlocks',
3469+ qw(--create-dest-table))
3470+ }
3471+);
3472+
3473 my $res = $dbh1->selectall_arrayref('SELECT * FROM test.deadlocks');
3474 ok(
3475 scalar @$res,
3476- 'Deadlocks recorded in --dest table'
3477-);
3478-
3479-# #############################################################################
3480-# Check that --dest suppress --print output unless --print is explicit.
3481-# #############################################################################
3482-$output = 'foo';
3483-$dbh1->do('TRUNCATE TABLE test.deadlocks');
3484-$output = `$cmd --dest D=test,t=deadlocks`;
3485-is(
3486- $output,
3487- '',
3488- 'No output with --dest'
3489-);
3490-
3491-$res = $dbh1->selectall_arrayref('SELECT * FROM test.deadlocks');
3492-ok(
3493- scalar @$res,
3494- 'Deadlocks still recorded in table'
3495-);
3496-
3497+ 'Deadlock saved in --dest table'
3498+) or diag($output);
3499+
3500+# #############################################################################
3501+# In 2.1, --dest suppressed output (--print). In 2.2, output is only
3502+# suppressed by --quiet.
3503+# #############################################################################
3504 $output = '';
3505 $dbh1->do('TRUNCATE TABLE test.deadlocks');
3506-$output = `$trunk/bin/pt-deadlock-logger --print --dest D=test,t=deadlocks --host 127.1 --port 12345 --user msandbox --password msandbox`;
3507-like(
3508+$output = output(
3509+ sub {
3510+ pt_deadlock_logger::main(@args, '--dest', 'D=test,t=deadlocks',
3511+ qw(--quiet))
3512+ }
3513+);
3514+
3515+is(
3516 $output,
3517- qr/127\.1.+msandbox.+GEN_CLUST_INDEX/,
3518- 'Prints output with --dest and explicit --print'
3519+ "",
3520+ "No output with --dest and --quiet"
3521 );
3522
3523 $res = $dbh1->selectall_arrayref('SELECT * FROM test.deadlocks');
3524 ok(
3525 scalar @$res,
3526- 'Deadlocks recorded in table again'
3527+ "... deadlock still saved in the table"
3528 );
3529
3530 # #############################################################################
3531@@ -180,9 +195,7 @@
3532 make_deadlock();
3533
3534 $output = output(
3535- sub { pt_deadlock_logger::main("F=/tmp/12345/my.sandbox.cnf",
3536- qw(--print) );
3537- }
3538+ sub { pt_deadlock_logger::main(@args) }
3539 );
3540
3541 like(
3542@@ -200,4 +213,3 @@
3543 $sb->wipe_clean($dbh1);
3544 ok($sb->ok(), "Sandbox servers") or BAIL_OUT(__FILE__ . " broke the sandbox");
3545 done_testing;
3546-exit;
3547
3548=== modified file 't/pt-deadlock-logger/clear_deadlocks.t'
3549--- t/pt-deadlock-logger/clear_deadlocks.t 2012-06-03 19:14:30 +0000
3550+++ t/pt-deadlock-logger/clear_deadlocks.t 2013-02-27 23:41:26 +0000
3551@@ -22,9 +22,6 @@
3552 if ( !$dbh1 ) {
3553 plan skip_all => 'Cannot connect to sandbox master';
3554 }
3555-else {
3556- plan tests => 4;
3557-}
3558
3559 my $output;
3560 my $cnf = "/tmp/12345/my.sandbox.cnf";
3561@@ -39,7 +36,7 @@
3562
3563 # The clear-deadlocks table comes and goes quickly so we can really
3564 # only search the debug output for evidence that it was created.
3565-$output = `PTDEBUG=1 $trunk/bin/pt-deadlock-logger F=$cnf,D=test --clear-deadlocks test.make_deadlock 2>&1`;
3566+$output = `PTDEBUG=1 $trunk/bin/pt-deadlock-logger F=$cnf,D=test --clear-deadlocks test.make_deadlock --iterations 1 2>&1`;
3567 like(
3568 $output,
3569 qr/INSERT INTO test.make_deadlock/,
3570@@ -67,4 +64,4 @@
3571 # #############################################################################
3572 $sb->wipe_clean($dbh1);
3573 ok($sb->ok(), "Sandbox servers") or BAIL_OUT(__FILE__ . " broke the sandbox");
3574-exit;
3575+done_testing;
3576
3577=== modified file 't/pt-deadlock-logger/create_dest_table.t'
3578--- t/pt-deadlock-logger/create_dest_table.t 2012-06-09 18:43:33 +0000
3579+++ t/pt-deadlock-logger/create_dest_table.t 2013-02-27 23:41:26 +0000
3580@@ -22,15 +22,10 @@
3581 if ( !$dbh1 ) {
3582 plan skip_all => 'Cannot connect to sandbox master';
3583 }
3584-else {
3585- plan tests => 3;
3586-}
3587
3588 my $output;
3589-my $cnf = "/tmp/12345/my.sandbox.cnf";
3590-my $cmd = "$trunk/bin/pt-deadlock-logger -F $cnf h=127.1";
3591+my $dsn = $sb->dsn_for('master');
3592
3593-$sb->wipe_clean($dbh1);
3594 $sb->create_dbs($dbh1, ['test']);
3595
3596 # #############################################################################
3597@@ -42,17 +37,25 @@
3598 'Deadlocks table does not exit (issue 386)'
3599 );
3600
3601-`$cmd --dest D=test,t=issue_386 --run-time 1s --interval 1s --create-dest-table`;
3602+$output = output(
3603+ sub {
3604+ pt_deadlock_logger::main($dsn,
3605+ '--dest', 'D=test,t=issue_386',
3606+ qw(--iterations 1 --create-dest-table)
3607+ )
3608+ },
3609+ stderr => 1,
3610+);
3611
3612 is_deeply(
3613 $dbh1->selectall_arrayref(q{show tables from `test` like 'issue_386'}),
3614 [['issue_386']],
3615 'Deadlocks table created with --create-dest-table (issue 386)'
3616-);
3617+) or diag($output);
3618
3619 # #############################################################################
3620 # Done.
3621 # #############################################################################
3622 $sb->wipe_clean($dbh1);
3623 ok($sb->ok(), "Sandbox servers") or BAIL_OUT(__FILE__ . " broke the sandbox");
3624-exit;
3625+done_testing;
3626
3627=== modified file 't/pt-deadlock-logger/option_sanity.t'
3628--- t/pt-deadlock-logger/option_sanity.t 2012-10-22 18:16:42 +0000
3629+++ t/pt-deadlock-logger/option_sanity.t 2013-02-27 23:41:26 +0000
3630@@ -21,7 +21,7 @@
3631 $output = `$trunk/bin/pt-deadlock-logger --dest D=test,t=deadlocks 2>&1`;
3632 like(
3633 $output,
3634- qr/Missing or invalid source host/,
3635+ qr/No DSN was specified/,
3636 'Requires source host'
3637 );
3638
3639
3640=== renamed file 't/pt-deadlock-logger/deadlocks_tbl.sql' => 't/pt-deadlock-logger/samples/deadlocks_tbl.sql'
3641=== modified file 't/pt-deadlock-logger/standard_options.t'
3642--- t/pt-deadlock-logger/standard_options.t 2012-07-23 04:52:41 +0000
3643+++ t/pt-deadlock-logger/standard_options.t 2013-02-27 23:41:26 +0000
3644@@ -17,69 +17,96 @@
3645
3646 my $dp = new DSNParser(opts=>$dsn_opts);
3647 my $sb = new Sandbox(basedir => '/tmp', DSNParser => $dp);
3648-my $dbh1 = $sb->get_dbh_for('master');
3649+my $dbh = $sb->get_dbh_for('master');
3650
3651-if ( !$dbh1 ) {
3652+if ( !$dbh ) {
3653 plan skip_all => 'Cannot connect to sandbox master';
3654 }
3655-else {
3656- plan tests => 10;
3657-}
3658
3659 my $output;
3660-my $cnf = "/tmp/12345/my.sandbox.cnf";
3661-my $cmd = "$trunk/bin/pt-deadlock-logger -F $cnf h=127.1";
3662+my $dsn = $sb->dsn_for('master');
3663+my @args = ($dsn, qw(--iterations 1));
3664
3665-$sb->wipe_clean($dbh1);
3666-$sb->create_dbs($dbh1, ['test']);
3667+$sb->wipe_clean($dbh);
3668+$sb->create_dbs($dbh, ['test']);
3669
3670 # #############################################################################
3671 # Issue 248: Add --user, --pass, --host, etc to all tools
3672 # #############################################################################
3673
3674 # Test that source DSN inherits from --user, etc.
3675-$output = `$trunk/bin/pt-deadlock-logger h=127.1,D=test,u=msandbox,p=msandbox --clear-deadlocks test.make_deadlock --port 12345 2>&1`;
3676+$output = output(
3677+ sub {
3678+ pt_deadlock_logger::main(
3679+ "h=127.1,D=test,u=msandbox,p=msandbox",
3680+ qw(--clear-deadlocks test.make_deadlock --port 12345),
3681+ qw(--iterations 1)
3682+ )
3683+ }
3684+);
3685+
3686 unlike(
3687 $output,
3688 qr/failed/,
3689 'Source DSN inherits from standard connection options (issue 248)'
3690 );
3691
3692-# #########################################################################
3693+# #############################################################################
3694 # Issue 391: Add --pid option to all scripts
3695-# #########################################################################
3696-`touch /tmp/mk-script.pid`;
3697-$output = `$cmd --clear-deadlocks test.make_deadlock --port 12345 --pid /tmp/mk-script.pid 2>&1`;
3698+# #############################################################################
3699+
3700+my $pid_file = "/tmp/pt-deadlock-logger-test.pid.$PID";
3701+diag(`touch $pid_file`);
3702+
3703+$output = output(
3704+ sub {
3705+ pt_deadlock_logger::main(@args, '--pid', $pid_file)
3706+ },
3707+ stderr => 1,
3708+);
3709+
3710 like(
3711 $output,
3712- qr{PID file /tmp/mk-script.pid already exists},
3713+ qr{PID file $pid_file already exists},
3714 'Dies if PID file already exists (--pid without --daemonize) (issue 391)'
3715 );
3716-`rm -rf /tmp/mk-script.pid`;
3717+
3718+unlink $pid_file if -f $pid_file;
3719
3720 # #############################################################################
3721 # Check daemonization
3722 # #############################################################################
3723-my $deadlocks_tbl = load_file('t/pt-deadlock-logger/deadlocks_tbl.sql');
3724-$dbh1->do('USE test');
3725-$dbh1->do('DROP TABLE IF EXISTS deadlocks');
3726-$dbh1->do("$deadlocks_tbl");
3727-
3728-my $pid_file = '/tmp/mk-deadlock-logger.pid';
3729-unlink $pid_file
3730- and diag("Unlinked existing $pid_file");
3731-
3732-`$cmd --dest D=test,t=deadlocks --daemonize --run-time 6s --interval 1s --pid $pid_file 1>/dev/null 2>/dev/null`;
3733-$output = `ps -eaf | grep '$cmd \-\-dest '`;
3734-like($output, qr/\Q$cmd/, 'It lives daemonized');
3735+$dbh->do('USE test');
3736+$dbh->do('DROP TABLE IF EXISTS deadlocks');
3737+$sb->load_file('master', 't/pt-deadlock-logger/samples/deadlocks_tbl.sql', 'test');
3738+
3739+$output = `$trunk/bin/pt-deadlock-logger $dsn --dest D=test,t=deadlocks --daemonize --run-time 10 --interval 1 --pid $pid_file 1>/dev/null 2>/dev/null`;
3740
3741 PerconaTest::wait_for_files($pid_file);
3742-ok(-f $pid_file, 'PID file created');
3743-my ($pid) = $output =~ /\s+(\d+)\s+/;
3744+
3745+$output = `ps x | grep 'pt-deadlock-logger $dsn' | grep -v grep`;
3746+like(
3747+ $output,
3748+ qr/\Qpt-deadlock-logger $dsn/,
3749+ 'It lives daemonized'
3750+) or diag($output);
3751+
3752+my ($pid) = $output =~ /(\d+)/;
3753+
3754+ok(
3755+ -f $pid_file,
3756+ 'PID file created'
3757+) or diag($output);
3758+
3759 chomp($output = slurp_file($pid_file));
3760-is($output, $pid, 'PID file has correct PID');
3761+is(
3762+ $output,
3763+ $pid,
3764+ 'PID file has correct PID'
3765+);
3766
3767 # Kill it
3768+kill 2, $pid;
3769 PerconaTest::wait_until(sub { !kill 0, $pid });
3770 ok(! -f $pid_file, 'PID file removed');
3771
3772@@ -90,17 +117,25 @@
3773 'PID file already exists'
3774 );
3775
3776-$output = `$cmd --dest D=test,t=deadlocks --daemonize --run-time 1s --interval 1s --pid $pid_file 2>&1`;
3777+$output = output(
3778+ sub {
3779+ pt_deadlock_logger::main(@args, '--pid', $pid_file,
3780+ qw(--daemonize))
3781+ },
3782+ stderr => 1,
3783+);
3784+
3785 like(
3786 $output,
3787- qr/PID file .+ already exists/,
3788+ qr/PID file $pid_file already exists/,
3789 'Does not run if PID file already exists'
3790 );
3791
3792-$output = `ps -eaf | grep 'pt-deadlock-logger \-\-dest '`;
3793-unlike(
3794+$output = `ps x | grep 'pt-deadlock-logger $dsn' | grep -v grep`;
3795+
3796+is(
3797 $output,
3798- qr/$cmd/,
3799+ "",
3800 'It does not lived daemonized'
3801 );
3802
3803@@ -109,6 +144,6 @@
3804 # #############################################################################
3805 # Done.
3806 # #############################################################################
3807-$sb->wipe_clean($dbh1);
3808+$sb->wipe_clean($dbh);
3809 ok($sb->ok(), "Sandbox servers") or BAIL_OUT(__FILE__ . " broke the sandbox");
3810-exit;
3811+done_testing;
3812
3813=== modified file 't/pt-fk-error-logger/basics.t'
3814--- t/pt-fk-error-logger/basics.t 2012-11-07 18:38:09 +0000
3815+++ t/pt-fk-error-logger/basics.t 2013-02-27 23:41:26 +0000
3816@@ -27,8 +27,9 @@
3817 $sb->create_dbs($dbh, [qw(test)]);
3818
3819 my $output;
3820-my $cnf = '/tmp/12345/my.sandbox.cnf';
3821-my $cmd = "$trunk/bin/pt-fk-error-logger -F $cnf ";
3822+my $cnf = '/tmp/12345/my.sandbox.cnf';
3823+my $cmd = "$trunk/bin/pt-fk-error-logger -F $cnf ";
3824+my @args = qw(--iterations 1);
3825
3826 $sb->load_file('master', 't/pt-fk-error-logger/samples/fke_tbl.sql', 'test');
3827
3828@@ -39,8 +40,45 @@
3829 # First, create a foreign key error.
3830 `/tmp/12345/use -D test < $trunk/t/pt-fk-error-logger/samples/fke.sql 1>/dev/null 2>/dev/null`;
3831
3832-# Then get and save that fke.
3833-output(sub { pt_fk_error_logger::main('h=127.1,P=12345,u=msandbox,p=msandbox', '--dest', 'h=127.1,P=12345,D=test,t=foreign_key_errors'); } );
3834+$output = output(
3835+ sub {
3836+ pt_fk_error_logger::main(@args, 'h=127.1,P=12345,u=msandbox,p=msandbox'),
3837+ }
3838+);
3839+
3840+like(
3841+ $output,
3842+ qr/Foreign key constraint fails/,
3843+ "Prints fk error by default"
3844+);
3845+
3846+$output = output(
3847+ sub {
3848+ pt_fk_error_logger::main(@args, 'h=127.1,P=12345,u=msandbox,p=msandbox',
3849+ qw(--quiet))
3850+ }
3851+);
3852+
3853+is(
3854+ $output,
3855+ "",
3856+ "No output with --quiet"
3857+);
3858+
3859+
3860+# #############################################################################
3861+# --dest
3862+# #############################################################################
3863+
3864+$output = output(
3865+ sub {
3866+ pt_fk_error_logger::main(@args,
3867+ 'h=127.1,P=12345,u=msandbox,p=msandbox',
3868+ '--dest', 'h=127.1,P=12345,D=test,t=foreign_key_errors',
3869+ )
3870+ }
3871+);
3872+
3873 sleep 0.1;
3874
3875 # And then test that it was actually saved.
3876@@ -61,7 +99,7 @@
3877
3878 # Check again to make sure that the same fke isn't saved twice.
3879 my $first_ts = $fke->[0]->[0];
3880-output(sub { pt_fk_error_logger::main('h=127.1,P=12345,u=msandbox,p=msandbox', '--dest', 'h=127.1,P=12345,D=test,t=foreign_key_errors'); } );
3881+output(sub { pt_fk_error_logger::main(@args, 'h=127.1,P=12345,u=msandbox,p=msandbox', '--dest', 'h=127.1,P=12345,D=test,t=foreign_key_errors'); } );
3882 sleep 0.1;
3883 $fke = $dbh->selectall_arrayref('SELECT * FROM test.foreign_key_errors');
3884 is(
3885@@ -82,7 +120,7 @@
3886 eval {
3887 $dbh->do('DELETE FROM parent WHERE id = 2'); # Causes foreign key error.
3888 };
3889-output( sub { pt_fk_error_logger::main('h=127.1,P=12345,u=msandbox,p=msandbox', '--dest', 'h=127.1,P=12345,D=test,t=foreign_key_errors'); } );
3890+output( sub { pt_fk_error_logger::main(@args, 'h=127.1,P=12345,u=msandbox,p=msandbox', '--dest', 'h=127.1,P=12345,D=test,t=foreign_key_errors'); } );
3891 sleep 0.1;
3892 $fke = $dbh->selectall_arrayref('SELECT * FROM test.foreign_key_errors');
3893 like(
3894@@ -99,11 +137,14 @@
3895 # ##########################################################################
3896 # Test printing the errors.
3897 # ##########################################################################
3898+
3899 $dbh->do('USE test');
3900 eval {
3901 $dbh->do('DELETE FROM parent WHERE id = 2'); # Causes foreign key error.
3902 };
3903-$output = output(sub { pt_fk_error_logger::main('h=127.1,P=12345,u=msandbox,p=msandbox'); });
3904+
3905+$output = output(sub { pt_fk_error_logger::main(@args, 'h=127.1,P=12345,u=msandbox,p=msandbox'); });
3906+
3907 like(
3908 $output,
3909 qr/DELETE FROM parent WHERE id = 2/,
3910@@ -127,7 +168,7 @@
3911
3912 $output = output(
3913 sub {
3914- pt_fk_error_logger::main('h=127.1,P=12348,u=msandbox,p=msandbox',
3915+ pt_fk_error_logger::main(@args, 'h=127.1,P=12348,u=msandbox,p=msandbox',
3916 '--dest', 'h=127.1,P=12348,D=test,t=foreign_key_errors')
3917 },
3918 stderr => 1,
3919@@ -142,6 +183,23 @@
3920 diag(`$trunk/sandbox/stop-sandbox 12348 >/dev/null`);
3921
3922 # #############################################################################
3923+# Test --pid
3924+# #############################################################################
3925+
3926+my $pid_file = "/tmp/pt-fk-error-log-test-$PID.pid";
3927+diag(`touch $pid_file`);
3928+
3929+$output = `$trunk/bin/pt-fk-error-logger h=127.1,P=12345,u=msandbox,p=msandbox --pid $pid_file --iterations 1 2>&1`;
3930+
3931+like(
3932+ $output,
3933+ qr{PID file $pid_file already exists},
3934+ 'Dies if PID file already exists (--pid without --daemonize) (issue 391)'
3935+);
3936+
3937+unlink $pid_file;
3938+
3939+# #############################################################################
3940 # Done.
3941 # #############################################################################
3942 $sb->wipe_clean($dbh);
3943
3944=== modified file 't/pt-fk-error-logger/get_fk_error.t'
3945--- t/pt-fk-error-logger/get_fk_error.t 2011-07-12 22:56:55 +0000
3946+++ t/pt-fk-error-logger/get_fk_error.t 2013-02-27 23:41:26 +0000
3947@@ -9,7 +9,7 @@
3948 use strict;
3949 use warnings FATAL => 'all';
3950 use English qw(-no_match_vars);
3951-use Test::More tests => 10;
3952+use Test::More;
3953
3954 use PerconaTest;
3955 require "$trunk/bin/pt-fk-error-logger";
3956@@ -70,4 +70,4 @@
3957 # #############################################################################
3958 # Done.
3959 # #############################################################################
3960-exit;
3961+done_testing;
3962
3963=== removed file 't/pt-fk-error-logger/standard_options.t'
3964--- t/pt-fk-error-logger/standard_options.t 2011-07-12 22:56:55 +0000
3965+++ t/pt-fk-error-logger/standard_options.t 1970-01-01 00:00:00 +0000
3966@@ -1,32 +0,0 @@
3967-#!/usr/bin/env perl
3968-
3969-BEGIN {
3970- die "The PERCONA_TOOLKIT_BRANCH environment variable is not set.\n"
3971- unless $ENV{PERCONA_TOOLKIT_BRANCH} && -d $ENV{PERCONA_TOOLKIT_BRANCH};
3972- unshift @INC, "$ENV{PERCONA_TOOLKIT_BRANCH}/lib";
3973-};
3974-
3975-use strict;
3976-use warnings FATAL => 'all';
3977-use English qw(-no_match_vars);
3978-use Test::More tests => 1;
3979-
3980-use PerconaTest;
3981-require "$trunk/bin/pt-fk-error-logger";
3982-
3983-# #########################################################################
3984-# Issue 391: Add --pid option to all scripts
3985-# #########################################################################
3986-`touch /tmp/mk-script.pid`;
3987-my $output = `$trunk/bin/pt-fk-error-logger h=127.1,P=12345,u=msandbox,p=msandbox --print --pid /tmp/mk-script.pid 2>&1`;
3988-like(
3989- $output,
3990- qr{PID file /tmp/mk-script.pid already exists},
3991- 'Dies if PID file already exists (--pid without --daemonize) (issue 391)'
3992-);
3993-`rm -rf /tmp/mk-script.pid`;
3994-
3995-# #############################################################################
3996-# Done.
3997-# #############################################################################
3998-exit;

Subscribers

People subscribed via source and target branches