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

Proposed by Daniel Nichter
Status: Merged
Merged at revision: 534
Proposed branch: lp:~percona-toolkit-dev/percona-toolkit/pt-deadlock-logger-2.2
Merge into: lp:~percona-toolkit-dev/percona-toolkit/pt-fke-logger-2.2
Diff against target: 1800 lines (+900/-452)
8 files modified
bin/pt-deadlock-logger (+724/-342)
bin/pt-fk-error-logger (+2/-1)
lib/Cxn.pm (+32/-14)
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)
To merge this branch: bzr merge lp:~percona-toolkit-dev/percona-toolkit/pt-deadlock-logger-2.2
Reviewer Review Type Date Requested Status
Daniel Nichter Approve
Review via email: mp+150471@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Daniel Nichter (daniel-nichter) wrote :

Passes all its tests.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bin/pt-deadlock-logger'
2--- bin/pt-deadlock-logger 2013-02-22 17:47:57 +0000
3+++ bin/pt-deadlock-logger 2013-02-26 01:08:24 +0000
4@@ -23,9 +23,11 @@
5 VersionParser
6 Quoter
7 DSNParser
8+ Cxn
9 Daemon
10 HTTPMicro
11 VersionCheck
12+ Runtime
13 ));
14 }
15
16@@ -2439,6 +2441,177 @@
17 # ###########################################################################
18
19 # ###########################################################################
20+# Cxn package
21+# This package is a copy without comments from the original. The original
22+# with comments and its test file can be found in the Bazaar repository at,
23+# lib/Cxn.pm
24+# t/lib/Cxn.t
25+# See https://launchpad.net/percona-toolkit for more information.
26+# ###########################################################################
27+{
28+package Cxn;
29+
30+use strict;
31+use warnings FATAL => 'all';
32+use English qw(-no_match_vars);
33+use Scalar::Util qw(blessed);
34+use constant {
35+ PTDEBUG => $ENV{PTDEBUG} || 0,
36+ PERCONA_TOOLKIT_TEST_USE_DSN_NAMES => $ENV{PERCONA_TOOLKIT_TEST_USE_DSN_NAMES} || 0,
37+};
38+
39+sub new {
40+ my ( $class, %args ) = @_;
41+ my @required_args = qw(DSNParser OptionParser);
42+ foreach my $arg ( @required_args ) {
43+ die "I need a $arg argument" unless $args{$arg};
44+ };
45+ my ($dp, $o) = @args{@required_args};
46+
47+ my $dsn_defaults = $dp->parse_options($o);
48+ my $prev_dsn = $args{prev_dsn};
49+ my $dsn = $args{dsn};
50+ if ( !$dsn ) {
51+ $args{dsn_string} ||= 'h=' . ($dsn_defaults->{h} || 'localhost');
52+
53+ $dsn = $dp->parse(
54+ $args{dsn_string}, $prev_dsn, $dsn_defaults);
55+ }
56+ elsif ( $prev_dsn ) {
57+ $dsn = $dp->copy($prev_dsn, $dsn);
58+ }
59+
60+ my $self = {
61+ dsn => $dsn,
62+ dbh => $args{dbh},
63+ dsn_name => $dp->as_string($dsn, [qw(h P S)]),
64+ hostname => '',
65+ set => $args{set},
66+ NAME_lc => defined($args{NAME_lc}) ? $args{NAME_lc} : 1,
67+ dbh_set => 0,
68+ OptionParser => $o,
69+ DSNParser => $dp,
70+ is_cluster_node => undef,
71+ parent => $args{parent},
72+ };
73+
74+ return bless $self, $class;
75+}
76+
77+sub connect {
78+ my ( $self, %opts ) = @_;
79+ my $dsn = $self->{dsn};
80+ my $dp = $self->{DSNParser};
81+ my $o = $self->{OptionParser};
82+
83+ my $dbh = $self->{dbh};
84+ if ( !$dbh || !$dbh->ping() ) {
85+ if ( $o->get('ask-pass') && !$self->{asked_for_pass} ) {
86+ $dsn->{p} = OptionParser::prompt_noecho("Enter MySQL password: ");
87+ $self->{asked_for_pass} = 1;
88+ }
89+ $dbh = $dp->get_dbh(
90+ $dp->get_cxn_params($dsn),
91+ {
92+ AutoCommit => 1,
93+ %opts,
94+ },
95+ );
96+ }
97+ PTDEBUG && _d($dbh, 'Connected dbh to', $self->{name});
98+
99+ return $self->set_dbh($dbh);
100+}
101+
102+sub set_dbh {
103+ my ($self, $dbh) = @_;
104+
105+ if ( $self->{dbh} && $self->{dbh} == $dbh && $self->{dbh_set} ) {
106+ PTDEBUG && _d($dbh, 'Already set dbh');
107+ return $dbh;
108+ }
109+
110+ PTDEBUG && _d($dbh, 'Setting dbh');
111+
112+ $dbh->{FetchHashKeyName} = 'NAME_lc' if $self->{NAME_lc};
113+
114+ my $sql = 'SELECT @@hostname, @@server_id';
115+ PTDEBUG && _d($dbh, $sql);
116+ my ($hostname, $server_id) = $dbh->selectrow_array($sql);
117+ PTDEBUG && _d($dbh, 'hostname:', $hostname, $server_id);
118+ if ( $hostname ) {
119+ $self->{hostname} = $hostname;
120+ }
121+
122+ if ( $self->{parent} ) {
123+ PTDEBUG && _d($dbh, 'Setting InactiveDestroy=1 in parent');
124+ $dbh->{InactiveDestroy} = 1;
125+ }
126+
127+ if ( my $set = $self->{set}) {
128+ $set->($dbh);
129+ }
130+
131+ $self->{dbh} = $dbh;
132+ $self->{dbh_set} = 1;
133+ return $dbh;
134+}
135+
136+sub lost_connection {
137+ my ($self, $e) = @_;
138+ return 0 unless $e;
139+ return $e =~ m/MySQL server has gone away/
140+ || $e =~ m/Lost connection to MySQL server/;
141+}
142+
143+sub dbh {
144+ my ($self) = @_;
145+ return $self->{dbh};
146+}
147+
148+sub dsn {
149+ my ($self) = @_;
150+ return $self->{dsn};
151+}
152+
153+sub name {
154+ my ($self) = @_;
155+ return $self->{dsn_name} if PERCONA_TOOLKIT_TEST_USE_DSN_NAMES;
156+ return $self->{hostname} || $self->{dsn_name} || 'unknown host';
157+}
158+
159+sub DESTROY {
160+ my ($self) = @_;
161+
162+ if ( $self->{parent} ) {
163+ PTDEBUG && _d('Not disconnecting dbh in parent');
164+ }
165+ elsif ( $self->{dbh}
166+ && blessed($self->{dbh})
167+ && $self->{dbh}->can("disconnect") )
168+ {
169+ PTDEBUG && _d('Disconnecting dbh', $self->{dbh}, $self->{name});
170+ $self->{dbh}->disconnect();
171+ }
172+
173+ return;
174+}
175+
176+sub _d {
177+ my ($package, undef, $line) = caller 0;
178+ @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
179+ map { defined $_ ? $_ : 'undef' }
180+ @_;
181+ print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
182+}
183+
184+1;
185+}
186+# ###########################################################################
187+# End Cxn package
188+# ###########################################################################
189+
190+# ###########################################################################
191 # Daemon package
192 # This package is a copy without comments from the original. The original
193 # with comments and its test file can be found in the Bazaar repository at,
194@@ -3865,6 +4038,139 @@
195 # ###########################################################################
196
197 # ###########################################################################
198+# Runtime package
199+# This package is a copy without comments from the original. The original
200+# with comments and its test file can be found in the Bazaar repository at,
201+# lib/Runtime.pm
202+# t/lib/Runtime.t
203+# See https://launchpad.net/percona-toolkit for more information.
204+# ###########################################################################
205+{
206+package Runtime;
207+
208+use strict;
209+use warnings FATAL => 'all';
210+use English qw(-no_match_vars);
211+use constant PTDEBUG => $ENV{PTDEBUG} || 0;
212+
213+sub new {
214+ my ( $class, %args ) = @_;
215+ my @required_args = qw(run_time now);
216+ foreach my $arg ( @required_args ) {
217+ die "I need a $arg argument" unless exists $args{$arg};
218+ }
219+
220+ my $run_time = $args{run_time};
221+ if ( defined $run_time ) {
222+ die "run_time must be > 0" if $run_time <= 0;
223+ }
224+
225+ my $now = $args{now};
226+ die "now must be a callback" unless ref $now eq 'CODE';
227+
228+ my $self = {
229+ run_time => $run_time,
230+ now => $now,
231+ start_time => undef,
232+ end_time => undef,
233+ time_left => undef,
234+ stop => 0,
235+ };
236+
237+ return bless $self, $class;
238+}
239+
240+sub time_left {
241+ my ( $self, %args ) = @_;
242+
243+ if ( $self->{stop} ) {
244+ PTDEBUG && _d("No time left because stop was called");
245+ return 0;
246+ }
247+
248+ my $now = $self->{now}->(%args);
249+ PTDEBUG && _d("Current time:", $now);
250+
251+ if ( !defined $self->{start_time} ) {
252+ $self->{start_time} = $now;
253+ }
254+
255+ return unless defined $now;
256+
257+ my $run_time = $self->{run_time};
258+ return unless defined $run_time;
259+
260+ if ( !$self->{end_time} ) {
261+ $self->{end_time} = $now + $run_time;
262+ PTDEBUG && _d("End time:", $self->{end_time});
263+ }
264+
265+ $self->{time_left} = $self->{end_time} - $now;
266+ PTDEBUG && _d("Time left:", $self->{time_left});
267+ return $self->{time_left};
268+}
269+
270+sub have_time {
271+ my ( $self, %args ) = @_;
272+ my $time_left = $self->time_left(%args);
273+ return 1 if !defined $time_left; # run forever
274+ return $time_left <= 0 ? 0 : 1; # <=0s means run time has elapsed
275+}
276+
277+sub time_elapsed {
278+ my ( $self, %args ) = @_;
279+
280+ my $start_time = $self->{start_time};
281+ return 0 unless $start_time;
282+
283+ my $now = $self->{now}->(%args);
284+ PTDEBUG && _d("Current time:", $now);
285+
286+ my $time_elapsed = $now - $start_time;
287+ PTDEBUG && _d("Time elapsed:", $time_elapsed);
288+ if ( $time_elapsed < 0 ) {
289+ warn "Current time $now is earlier than start time $start_time";
290+ }
291+ return $time_elapsed;
292+}
293+
294+sub reset {
295+ my ( $self ) = @_;
296+ $self->{start_time} = undef;
297+ $self->{end_time} = undef;
298+ $self->{time_left} = undef;
299+ $self->{stop} = 0;
300+ PTDEBUG && _d("Reset run time");
301+ return;
302+}
303+
304+sub stop {
305+ my ( $self ) = @_;
306+ $self->{stop} = 1;
307+ return;
308+}
309+
310+sub start {
311+ my ( $self ) = @_;
312+ $self->{stop} = 0;
313+ return;
314+}
315+
316+sub _d {
317+ my ($package, undef, $line) = caller 0;
318+ @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
319+ map { defined $_ ? $_ : 'undef' }
320+ @_;
321+ print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
322+}
323+
324+1;
325+}
326+# ###########################################################################
327+# End Runtime package
328+# ###########################################################################
329+
330+# ###########################################################################
331 # This is a combination of modules and programs in one -- a runnable module.
332 # http://www.perl.com/pub/a/2006/07/13/lightning-articles.html?page=last
333 # Or, look it up in the Camel book on pages 642 and 643 in the 3rd edition.
334@@ -3877,21 +4183,15 @@
335 use English qw(-no_match_vars);
336 use List::Util qw(max);
337 use Socket qw(inet_aton);
338-use sigtrap qw(handler finish untrapped normal-signals);
339+use Time::HiRes qw(sleep);
340+use File::Temp qw(tempfile);
341+use File::Spec;
342+
343+use sigtrap 'handler', \&sig_int, 'normal-signals';
344
345 use Percona::Toolkit;
346 use constant PTDEBUG => $ENV{PTDEBUG} || 0;
347
348-my $o;
349-my $oktorun;
350-my $dp;
351-
352-# ########################################################################
353-# Configuration info.
354-# ########################################################################
355-my $source_dsn;
356-my $dest_dsn;
357-
358 # Some common patterns and variables
359 my $d = qr/(\d+)/; # Digit
360 my $t = qr/((?:\d+ \d+)|(?:[A-Fa-f0-9]+))/; # Transaction ID
361@@ -3934,48 +4234,54 @@
362 'update' => 1,
363 );
364
365+my $oktorun = 1;
366+my $exit_status = 0;
367+
368 sub main {
369- local @ARGV = @_; # set global ARGV for this package
370-
371- my $q = new Quoter();
372+ local @ARGV = @_; # set global ARGV for this package
373+ $oktorun = 1;
374+ $exit_status = 0;
375
376 # ########################################################################
377 # Get configuration information.
378 # ########################################################################
379- $o = new OptionParser();
380+ my $o = new OptionParser();
381 $o->get_specs();
382 $o->get_opts();
383
384- $o->set('collapse', $o->get('print')) unless $o->got('collapse');
385-
386- $dp = $o->DSNParser();
387- my $dsn_defaults = $dp->parse_options($o);
388- $source_dsn = @ARGV ? $dp->parse(shift @ARGV,$dsn_defaults) : $dsn_defaults;
389- $dest_dsn = $o->get('dest');
390-
391- # The source dsn is not an option so --dest cannot use OptionParser
392- # to inherit values from it. Thus, we do it manually. --dest will
393- # inherit from --user, --port, etc.
394- if ( $source_dsn && $dest_dsn ) {
395- # If dest DSN only has D and t, this will copy h, P, S, etc.
396- # from the source DSN.
397- $dest_dsn = $dp->copy($source_dsn, $dest_dsn);
398+ my $dp = $o->DSNParser();
399+ $dp->prop('set-vars', $o->get('set-vars'));
400+
401+ my $src;
402+ if ( my $src_dsn_string = shift @ARGV ) {
403+ $src = Cxn->new(
404+ dsn_string => $src_dsn_string,
405+ DSNParser => $dp,
406+ OptionParser => $o,
407+ parent => $o->get('daemonize'),
408+ );
409+ }
410+
411+ my $dst;
412+ if ( my $dst_dsn = $o->get('dest') ) {
413+ $dst = Cxn->new(
414+ dsn => $dst_dsn,
415+ prev_dsn => ($src ? $src->dsn : undef),
416+ DSNParser => $dp,
417+ OptionParser => $o,
418+ parent => $o->get('daemonize'),
419+ );
420 }
421
422 if ( !$o->get('help') ) {
423- if ( !$source_dsn ) {
424- $o->save_error('Missing or invalid source host');
425- }
426- if ( $dest_dsn && !$dest_dsn->{D} ) {
427- $o->save_error("--dest requires a 'D' (database) part");
428- }
429- if ( $dest_dsn && !$dest_dsn->{t} ) {
430- $o->save_error("--dest requires a 't' (table) part");
431- }
432-
433- # Avoid running forever with zero second interval.
434- if ( $o->get('run-time') && !$o->get('interval') ) {
435- $o->set('interval', 1);
436+ if ( !$src ) {
437+ $o->save_error('No DSN was specified.');
438+ }
439+ if ( $dst && !$dst->dsn->{D} ) {
440+ $o->save_error("--dest requires a 'D' (database) part.");
441+ }
442+ if ( $dst && !$dst->dsn->{t} ) {
443+ $o->save_error("--dest requires a 't' (table) part.");
444 }
445 }
446
447@@ -3984,43 +4290,29 @@
448 # ########################################################################
449 # Connect to MySQL and set up the --dest, if any.
450 # ########################################################################
451- my $dbh = get_cxn($source_dsn, 1);
452- my $dest_dbh;
453- my $sth;
454+ my $q = new Quoter();
455+
456+ $src->connect();
457+
458+ my @cols = @{ $o->get('columns') };
459 my $ins_sth;
460-
461- # Since the user might not have specified a hostname for the connection,
462- # try to extract it from the $dbh
463- if ( !$source_dsn->{h} ) {
464- ($source_dsn->{h}) = $dbh->{mysql_hostinfo} =~ m/(\w+) via/;
465- PTDEBUG && _d('Got source host from dbh:', $source_dsn->{h});
466- }
467-
468- my @cols = qw( server ts thread txn_id txn_time user hostname ip db tbl idx
469- lock_type lock_mode wait_hold victim query );
470- if ( $o->got('columns') ) {
471- @cols = grep { $o->get('columns')->{$_} } @cols;
472- }
473-
474- if ( $dest_dsn ) {
475- my $db_tbl =
476- join('.',
477- map { $q->quote($_) }
478- grep { $_ }
479- ( $dest_dsn->{D}, $dest_dsn->{t} ));
480- $dest_dbh = get_cxn($dest_dsn, 0);
481+ my $ins_sql;
482+ if ( $dst ) {
483+ $dst->connect(AutoCommit => 0);
484+
485+ my $db_tbl = $q->join_quote($dst->dsn->{D}, $dst->dsn->{t});
486 my $cols = join(',', map { $q->quote($_) } @cols);
487- my $parms = join(',', map { '?' } @cols);
488- my $sql = "INSERT IGNORE INTO $db_tbl($cols) VALUES($parms)";
489- PTDEBUG && _d($sql);
490- $ins_sth = $dest_dbh->prepare($sql);
491+ my $parms = join(',', map { '?' } @cols);
492+ $ins_sql = "INSERT IGNORE INTO $db_tbl ($cols) VALUES ($parms) "
493+ . "/* pt-deadlock-logger */";
494+ PTDEBUG && _d($ins_sql);
495+ $ins_sth = $dst->dbh->prepare($ins_sql);
496
497 if ( $o->get('create-dest-table') ) {
498- my $db_tbl = $q->quote($dest_dsn->{D}, $dest_dsn->{t});
499- $sql = $o->read_para_after(__FILE__, qr/MAGIC_dest_table/);
500- $sql =~ s/deadlocks/IF NOT EXISTS $db_tbl/;
501+ my $sql = $o->read_para_after(__FILE__, qr/MAGIC_dest_table/);
502+ $sql =~ s/deadlocks/IF NOT EXISTS $db_tbl/;
503 PTDEBUG && _d($sql);
504- $dest_dbh->do($sql);
505+ $dst->dbh->do($sql);
506 }
507 }
508
509@@ -4039,6 +4331,10 @@
510 $daemon->make_PID_file();
511 }
512
513+ # Let Cxn::DESTROY() disconenct the dbh.
514+ $src->{parent} = 0;
515+ $dst->{parent} = 0 if $dst;
516+
517 # ########################################################################
518 # Do the version-check
519 # ########################################################################
520@@ -4046,117 +4342,155 @@
521 VersionCheck::version_check(
522 force => $o->got('version-check'),
523 instances => [
524- { dbh => $dbh, dsn => $source_dsn },
525- ($dest_dsn ? { dbh => $dest_dsn, dsn => $dest_dsn } : ()),
526+ { dbh => $src->dbh, dsn => $src->dsn },
527+ ($dst ? { dbh => $dst->dbh, dsn => $dst->dsn } : ())
528 ],
529 );
530 }
531
532 # ########################################################################
533+ # Set upt the --clear-deadlocks table.
534+ # ########################################################################
535+ my $clear_deadlocks_table_def;
536+ my $clear_deadlocks_table = $o->get('clear-deadlocks');
537+ if ( $clear_deadlocks_table ) {
538+ $clear_deadlocks_table_def
539+ = $o->read_para_after(__FILE__, qr/MAGIC_clear_deadlocks/);
540+ if ( VersionParser->new($src->dbh) < '4.1.2') {
541+ $clear_deadlocks_table_def =~ s/ENGINE=/TYPE=/;
542+ }
543+ $clear_deadlocks_table_def
544+ =~ s/percona_schema.clear_deadlocks/$clear_deadlocks_table/;
545+ PTDEBUG && _d('--clear-deadlocks table:', $clear_deadlocks_table_def);
546+ }
547+
548+ # ########################################################################
549 # Start looking for and logging deadlocks.
550 # ########################################################################
551- my $last_fingerprint = '';
552-
553- $oktorun = 1;
554- my $start = time();
555- my $end = $start + ($o->get('run-time') || 0); # When we should exit
556- my $now = $start;
557- while ( # Quit if:
558- ($start == $end || $now < $end) # time is exceeded
559- && $oktorun # or instructed to quit
560- )
561- {
562- my $text = $dbh->selectrow_hashref("SHOW /*!40100 ENGINE*/ INNODB STATUS")->{Status};
563- my $parse_deadlocks_options = {
564- 'numeric-ip' => $o->got('numeric-ip'),
565- 'collapse' => $o->got('collapse'),
566+ my $sep = $o->get('tab') ? "\t" : ' ';
567+ my $last_fingerprint = '';
568+ my $parse_deadlocks_options = {
569+ 'server' => $src->dsn->{h} || $src->{hostname},
570+ 'numeric-ip' => $o->got('numeric-ip'),
571+ };
572+
573+ my $run_time = Runtime->new(
574+ run_time => $o->get('run-time'),
575+ now => sub { return time },
576+ );
577+
578+ my $interval = $o->get('interval');
579+ my $iters = $o->get('iterations');
580+ PTDEBUG && _d('iterations:', $iters, 'interval:', $interval);
581+
582+ ITERATION:
583+ while (
584+ $oktorun
585+ && $run_time->have_time()
586+ && (!defined $iters || $iters--)
587+ ) {
588+
589+ my %txns;
590+ my $fingerprint;
591+ eval {
592+ my $sql = "SHOW /*!40100 ENGINE*/ INNODB STATUS "
593+ . "/* pt-deadlock-logger */";
594+ my $text = $src->dbh->selectrow_hashref($sql)->{status};
595+
596+ %txns = %{parse_deadlocks($text, $parse_deadlocks_options)};
597+ $fingerprint = fingerprint(\%txns);
598 };
599- my %txns = %{parse_deadlocks($text, $parse_deadlocks_options)};
600- my $fingerprint = fingerprint(\%txns);
601-
602- if ( $ins_sth ) {
603- foreach my $txn (sort { $a->{thread} <=> $b->{thread} } values %txns) {
604- $ins_sth->execute(@{$txn}{@cols});
605- }
606- $dest_dbh->commit();
607+ if ( my $e = $EVAL_ERROR ) {
608+ PTDEBUG && _d('Error getting InnoDB status:', $e);
609+ if ( $src->lost_connection($e) ) {
610+ eval { $src->connect() };
611+ if ( $EVAL_ERROR ) {
612+ warn "Lost connection to " . $src->name . ". Will try "
613+ . "to reconnect in the next iteration.\n";
614+ }
615+ else {
616+ PTDEBUG && _d('Reconnected to MySQL');
617+ redo ITERATION;
618+ }
619+ }
620+ else {
621+ warn "Error getting SHOW ENGINE INNODB STATUS: $EVAL_ERROR";
622+ $exit_status |= 1;
623+ }
624 }
625+ else {
626+ if ( $ins_sth ) {
627+ eval {
628+ PTDEBUG && _d('Saving deadlock to --dest');
629+ foreach my $txn (
630+ sort { $a->{thread} <=> $b->{thread} } values %txns
631+ ) {
632+ $ins_sth->execute(@{$txn}{@cols});
633+ }
634+ $dst->dbh->commit();
635+ };
636+ if ( my $e = $EVAL_ERROR ) {
637+ PTDEBUG && _d('Error saving to --dest:', $e);
638+ if ( $dst->lost_connection($e) ) {
639+ eval {
640+ $ins_sth->finish() if $ins_sth;
641+ $dst->dbh->disconnect() if $dst->dbh;
642+ $dst->connect(AutoCommit => 0);
643+ $ins_sth = $dst->dbh->prepare($ins_sql);
644+ };
645+ if ( $EVAL_ERROR ) {
646+ warn "Lost connection to " . $dst->name . ". Will try "
647+ . "to reconnect in the next iteration.\n";
648+ }
649+ else {
650+ PTDEBUG && _d('Reconnected to MySQL (--dest)');
651+ redo ITERATION;
652+ }
653+ }
654+ else {
655+ warn "Error saving to --dest: $EVAL_ERROR";
656+ $exit_status |= 1;
657+ }
658+ }
659+ }
660
661- if ( $fingerprint ne $last_fingerprint ) {
662- PTDEBUG && _d('New deadlock');
663- if ( $o->got('print') || !$o->got('dest') ) {
664- my $sep = $o->get('tab') ? "\t" : ' ';
665- print join($sep, @cols), "\n";
666- foreach my $txn (sort {$a->{thread}<=>$b->{thread}} values %txns) {
667- # If 'collapse' is on, it's already been taken care of,
668- # but if it's unset, by default strip whitespace.
669- if ( !$o->got('collapse') ) {
670+ if ( $fingerprint ne $last_fingerprint ) {
671+ PTDEBUG && _d('New deadlock');
672+ if ( !$o->get('quiet') ) {
673+ print join($sep, @cols), "\n";
674+ foreach my $txn (
675+ sort { $a->{thread} <=> $b->{thread} } values %txns
676+ ) {
677 $txn->{query} =~ s/\s+/ /g;
678+ print join($sep, map { $txn->{$_} } @cols), "\n";
679 }
680- print join($sep, map { $txn->{$_} } @cols), "\n";
681 }
682 }
683- }
684- else {
685- PTDEBUG && _d('Same deadlock, not printing');
686- }
687- # Save deadlock's fingerprint for next interval.
688- $last_fingerprint = $fingerprint;
689-
690- # If specified, clear the deadlock...
691- if ( my $db_tbl = $o->get('clear-deadlocks') ) {
692- PTDEBUG && _d('Creating --clear-deadlocks table', $db_tbl);
693- $dbh->{AutoCommit} = 0;
694- my $sql = $o->read_para_after(__FILE__, qr/MAGIC_clear_deadlocks/);
695-
696- if ( VersionParser->new($dbh) < '4.1.2') {
697- $sql =~ s/ENGINE=/TYPE=/;
698- }
699- $sql =~ s/test.deadlock_maker/$db_tbl/;
700- PTDEBUG && _d($sql);
701- $dbh->do($sql);
702- $sql = "INSERT INTO $db_tbl(a) VALUES(1)";
703- PTDEBUG && _d($sql);
704- $dbh->do($sql); # I'm holding locks on the table now.
705-
706- # Fork off a child to try to take a lock on the table.
707- my $pid = fork();
708- if ( defined($pid) && $pid == 0 ) { # I am a child
709- my $dbh_child = get_cxn($source_dsn, 0);
710- $sql = "SELECT * FROM $db_tbl FOR UPDATE";
711- PTDEBUG && _d($sql);
712- eval { $dbh_child->do($sql); }; # Should block against parent.
713- PTDEBUG && _d($EVAL_ERROR); # Parent inserted value 0.
714- $sql = "COMMIT";
715- PTDEBUG && _d($sql);
716- $dbh_child->do($sql);
717- exit;
718- }
719- elsif ( !defined($pid) ) {
720- die("Unable to fork for clearing deadlocks!\n");
721- }
722- sleep 1;
723- $sql = "INSERT INTO $db_tbl(a) VALUES(0)";# Will make child deadlock
724- PTDEBUG && _d($sql);
725- eval { $dbh->do($sql); };
726- PTDEBUG && _d($EVAL_ERROR);
727- waitpid($pid, 0);
728- $sql = "DROP TABLE $db_tbl";
729- PTDEBUG && _d($sql);
730- $dbh->do($sql);
731- }
732-
733- # If there's an --interval argument, run forever or till specified.
734- # Otherwise just run once.
735- if ( $o->get('interval') ) {
736- sleep($o->get('interval'));
737- $now = time();
738- }
739- else {
740- $oktorun = 0;
741+ else {
742+ PTDEBUG && _d('Same deadlock, not printing');
743+ }
744+
745+ $last_fingerprint = $fingerprint;
746+
747+ if ( $clear_deadlocks_table ) {
748+ clear_deadlocks(
749+ dsn => $src->dsn,
750+ table => $clear_deadlocks_table,
751+ table_def => $clear_deadlocks_table_def,
752+ DSNParser => $dp,
753+ );
754+ }
755+ }
756+
757+ # Sleep if there's an --iteration left.
758+ if ( !defined $iters || $iters ) {
759+ PTDEBUG && _d('Sleeping', $interval, 'seconds');
760+ sleep $interval;
761 }
762 }
763
764- return 0;
765+ PTDEBUG && _d('Done running, exiting', $exit_status);
766+ return $exit_status;
767 }
768
769 # ############################################################################
770@@ -4244,7 +4578,7 @@
771
772 my ( $query_text ) = $body =~ m/\nMySQL thread id .*\n((?s).*)/;
773 $query_text =~ s/\s+$//;
774- $query_text =~ s/\s+/ /g if $args->{'collapse'};
775+ $query_text =~ s/\s+/ /g;
776
777 @{$hash}{qw(thread hostname ip user query)}
778 = ($mysql_thread_id, $hostname, $ip, $user, $query_text);
779@@ -4290,13 +4624,101 @@
780 foreach my $txn ( values %txns ) {
781 $txn->{victim} = $txn->{id} == $victim ? 1 : 0;
782 $txn->{ts} = $ts;
783- $txn->{server} = $source_dsn->{h} || '';
784+ $txn->{server} = $args->{server} || '';
785 $txn->{ip} = inet_aton($txn->{ip}) if $args->{'numeric-ip'};
786 }
787
788 return \%txns;
789 }
790
791+sub clear_deadlocks {
792+ my (%args) = @_;
793+ my @required_args = qw(dsn table table_def DSNParser);
794+ foreach my $arg ( @required_args ) {
795+ die "I need a $arg argument" unless $args{$arg};
796+ }
797+ my $dsn = $args{dsn};
798+ my $table = $args{table};
799+ my $table_def = $args{table_def};
800+ my $dp = $args{DSNParser};
801+ PTDEBUG && _d('Clearing deadlocks with table', $table, $table_def);
802+
803+ my $parent_dbh = $dp->get_dbh($dp->get_cxn_params($dsn), { AutoCommit=>0 });
804+ $parent_dbh->{AutoCommit} = 0;
805+ $parent_dbh->{InactiveDestroy} = 1; # because of forking
806+
807+ # Create the deadlocks table.
808+ PTDEBUG && _d($table_def);
809+ $parent_dbh->do($table_def);
810+
811+ # Get a lock on it.
812+ my $sql = "INSERT INTO $table (a) VALUES (1) "
813+ . "/* pt-deadlock-logger clear deadlocks parent */";
814+ PTDEBUG && _d($sql);
815+ $parent_dbh->do($sql);
816+
817+ my ($sync_fh, $sync_file) = tempfile(
818+ 'pt-deadlock-logger-clear-deadlocks.XXXXXXX',
819+ DIR => File::Spec->tmpdir(),
820+ );
821+ PTDEBUG && _d('Sync file:', $sync_file);
822+ close $sync_fh;
823+ unlink $sync_file;
824+
825+ # Fork a child to try to take a lock on the table.
826+ my $pid = fork();
827+ if ( defined($pid) && $pid == 0 ) {
828+ # I am the child
829+ PTDEBUG && _d('Clear deadlocks child', $PID);
830+ my $child_dbh = $dp->get_dbh($dp->get_cxn_params($dsn), {AutoCommit=>0});
831+ my $sql = "SELECT * FROM $table FOR UPDATE "
832+ . "/* pt-deadlock-logger clear deadlocks child */";
833+ PTDEBUG && _d($sql);
834+ open my $fh, '>', $sync_file
835+ or die "Error creating $sync_file: $OS_ERROR";
836+ close $fh;
837+ PTDEBUG && _d('Clear deadlocks child ready (child)');
838+ eval { $child_dbh->do($sql); }; # Should block against parent.
839+ PTDEBUG && _d($EVAL_ERROR); # Parent inserted value 0.
840+ $child_dbh->commit();
841+ $child_dbh->disconnect();
842+ exit;
843+ }
844+ elsif ( !defined($pid) ) {
845+ die "Failed to fork for --clear-deadlocks: " . ($OS_ERROR || '');
846+ }
847+
848+ # Wait up to 10s for the child to connect and become ready.
849+ for ( 1..40 ) {
850+ last if -f $sync_file;
851+ PTDEBUG && _d('Waiting for the clear deadlocks child');
852+ sleep 0.25;
853+ }
854+ PTDEBUG && _d('Clear deadlocks child ready (parent)');
855+ sleep 0.25; # wait for child to exec its SELECT statement
856+
857+ # Make the child deadlock.
858+ $sql = "INSERT INTO $table (a) VALUES (0) "
859+ . "/* pt-deadlock-logger clear deadlocks parent */";
860+ PTDEBUG && _d($sql);
861+ eval { $parent_dbh->do($sql); };
862+ PTDEBUG && _d($EVAL_ERROR);
863+
864+ # Reap the child.
865+ waitpid($pid, 0);
866+
867+ # Drop the table.
868+ $sql = "DROP TABLE IF EXISTS $table";
869+ PTDEBUG && _d($sql);
870+ $parent_dbh->do($sql);
871+
872+ $parent_dbh->disconnect();
873+
874+ unlink $sync_file;
875+
876+ return;
877+}
878+
879 sub fingerprint {
880 my ( $txns ) = @_;
881 my $fingerprint = '';
882@@ -4308,21 +4730,11 @@
883 return $fingerprint;
884 }
885
886-# Catches signals so the program can exit gracefully.
887-sub finish {
888- my ($signal) = @_;
889- print STDERR "Exiting on SIG$signal.\n";
890+sub sig_int {
891+ my ( $signal ) = @_;
892 $oktorun = 0;
893-}
894-
895-sub get_cxn {
896- my ( $dsn, $ac ) = @_;
897- if ( $o->get('ask-pass') ) {
898- $dsn->{p} = OptionParser::prompt_noecho("Enter password: ");
899- }
900- my $dbh = $dp->get_dbh($dp->get_cxn_params($dsn), {AutoCommit => $ac});
901- $dbh->{InactiveDestroy} = 1; # Because of forking.
902- return $dbh;
903+ print STDERR "# Caught SIG$signal. Use 'kill -ABRT $PID' if "
904+ . "the tool does not exit normally in a few seconds.\n";
905 }
906
907 sub _d {
908@@ -4347,32 +4759,28 @@
909
910 =head1 NAME
911
912-pt-deadlock-logger - Extract and log MySQL deadlock information.
913+pt-deadlock-logger - Log MySQL deadlocks.
914
915 =head1 SYNOPSIS
916
917-Usage: pt-deadlock-logger [OPTION...] SOURCE_DSN
918-
919-pt-deadlock-logger extracts and saves information about the most recent deadlock
920-in a MySQL server.
921-
922-Print deadlocks on SOURCE_DSN:
923-
924- pt-deadlock-logger SOURCE_DSN
925-
926-Store deadlock information from SOURCE_DSN in test.deadlocks table on SOURCE_DSN
927-(source and destination are the same host):
928-
929- pt-deadlock-logger SOURCE_DSN --dest D=test,t=deadlocks
930-
931-Store deadlock information from SOURCE_DSN in test.deadlocks table on DEST_DSN
932-(source and destination are different hosts):
933-
934- pt-deadlock-logger SOURCE_DSN --dest DEST_DSN,D=test,t=deadlocks
935-
936-Daemonize and check for deadlocks on SOURCE_DSN every 30 seconds for 4 hours:
937-
938- pt-deadlock-logger SOURCE_DSN --dest D=test,t=deadlocks --daemonize --run-time 4h --interval 30s
939+Usage: pt-deadlock-logger [OPTIONS] DSN
940+
941+pt-deadlock-logger logs information about MySQL deadlocks on the given
942+DSN. Information is printed to C<STDOUT>, and it can also be saved to a
943+table by specifying L<"--dest">. The tool runs for forever unless
944+L<"--run-time"> or L<"--iterations"> is specified.
945+
946+Print deadlocks on host1:
947+
948+ pt-fk-error-logger h=host1
949+
950+Print deadlocks on host1 once then exit:
951+
952+ pt-fk-error-logger h=host1 --iterations 1
953+
954+Save deadlocks on host1 to percona_schema.fke on host2:
955+
956+ pt-fk-error-logger h=host1 --dest h=host2,D=percona_schema,t=deadlocks
957
958 =head1 RISKS
959
960@@ -4398,116 +4806,24 @@
961
962 =head1 DESCRIPTION
963
964-pt-deadlock-logger extracts deadlock data from a MySQL server. Currently only
965-InnoDB deadlock information is available. You can print the information to
966-standard output, store it in a database table, or both. If neither
967-L<"--print"> nor L<"--dest"> are given, then the deadlock information is
968-printed by default. If only L<"--dest"> is given, then the deadlock
969-information is only stored. If both options are given, then the deadlock
970-information is printed and stored.
971-
972-The source host can be specified using one of two methods. The first method is
973-to use at least one of the standard connection-related command line options:
974-L<"--defaults-file">, L<"--password">, L<"--host">, L<"--port">, L<"--socket">
975-or L<"--user">. These options only apply to the source host; they cannot be
976-used to specify the destination host.
977-
978-The second method to specify the source host, or the optional destination host
979-using L<"--dest">, is a DSN. A DSN is a special syntax that can be either just
980-a hostname (like C<server.domain.com> or C<1.2.3.4>), or a
981-C<key=value,key=value> string. Keys are a single letter:
982-
983- KEY MEANING
984- === =======
985- h Connect to host
986- P Port number to use for connection
987- S Socket file to use for connection
988- u User for login if not current user
989- p Password to use when connecting
990- F Only read default options from the given file
991-
992-If you omit any values from the destination host DSN, they are filled in with
993-values from the source host, so you don't need to specify them in both places.
994-C<pt-deadlock-logger> reads all normal MySQL option files, such as ~/.my.cnf, so
995-you may not need to specify username, password and other common options at all.
996+pt-deadlock-logger prints information about MySQL deadlocks by polling
997+and parsing C<SHOW ENGINE INNODB STATUS>. When a new deadlock occurs,
998+it's printed to C<STDOUT> and, if specified, saved to L<"--dest">.
999+
1000+Only new deadlocks are printed. A fingerprint for each deadlock is created
1001+using the deadlock's server, ts, and thread values (even if these
1002+columns are not specified by L<"--columns">). A deadlock is printed if
1003+its fingerprint is different than the last deadlock's fingerprint.
1004+
1005+The L<"--dest"> statement uses C<INSERT IGNORE> to eliminate duplicate
1006+deadlocks, so every deadlock is saved for every L<"--iterations">.
1007
1008 =head1 OUTPUT
1009
1010-You can choose which columns are output and/or saved to L<"--dest"> with the
1011-L<"--columns"> argument. The default columns are as follows:
1012-
1013-=over
1014-
1015-=item server
1016-
1017-The (source) server on which the deadlock occurred. This might be useful if
1018-you're tracking deadlocks on many servers.
1019-
1020-=item ts
1021-
1022-The date and time of the last detected deadlock.
1023-
1024-=item thread
1025-
1026-The MySQL thread number, which is the same as the connection ID in SHOW FULL
1027-PROCESSLIST.
1028-
1029-=item txn_id
1030-
1031-The InnoDB transaction ID, which InnoDB expresses as two unsigned integers. I
1032-have multiplied them out to be one number.
1033-
1034-=item txn_time
1035-
1036-How long the transaction was active when the deadlock happened.
1037-
1038-=item user
1039-
1040-The connection's database username.
1041-
1042-=item hostname
1043-
1044-The connection's host.
1045-
1046-=item ip
1047-
1048-The connection's IP address. If you specify L<"--numeric-ip">, this is
1049-converted to an unsigned integer.
1050-
1051-=item db
1052-
1053-The database in which the deadlock occurred.
1054-
1055-=item tbl
1056-
1057-The table on which the deadlock occurred.
1058-
1059-=item idx
1060-
1061-The index on which the deadlock occurred.
1062-
1063-=item lock_type
1064-
1065-The lock type the transaction held on the lock that caused the deadlock.
1066-
1067-=item lock_mode
1068-
1069-The lock mode of the lock that caused the deadlock.
1070-
1071-=item wait_hold
1072-
1073-Whether the transaction was waiting for the lock or holding the lock. Usually
1074-you will see the two waited-for locks.
1075-
1076-=item victim
1077-
1078-Whether the transaction was selected as the deadlock victim and rolled back.
1079-
1080-=item query
1081-
1082-The query that caused the deadlock.
1083-
1084-=back
1085+New deadlocks are printed to C<STDOUT>, unless L<"--quiet"> is specified.
1086+Errors and warnings are printed to C<STDERR>.
1087+
1088+See also L<"--columns"> and L<"--tab">.
1089
1090 =head1 INNODB CAVEATS AND DETAILS
1091
1092@@ -4562,24 +4878,89 @@
1093 C<SHOW INNODB STATUS>. The table must not exist. pt-deadlock-logger will
1094 create it with the following MAGIC_clear_deadlocks structure:
1095
1096- CREATE TABLE test.deadlock_maker(a INT PRIMARY KEY) ENGINE=InnoDB;
1097+ CREATE TABLE percona_schema.clear_deadlocks (a INT PRIMARY KEY) ENGINE=InnoDB;
1098
1099 After creating the table and causing a small deadlock, the tool will drop the
1100 table again.
1101
1102-=item --[no]collapse
1103-
1104-Collapse whitespace in queries to a single space. This might make it easier to
1105-inspect on the command line or in a query. By default, whitespace is collapsed
1106-when printing with L<"--print">, but not modified when storing to L<"--dest">.
1107-(That is, the default is different for each action).
1108-
1109 =item --columns
1110
1111-type: hash
1112-
1113-Output only this comma-separated list of columns. See L<"OUTPUT"> for more
1114-details on columns.
1115+type: Array; default: server, ts, thread, txn_id, txn_time, user, hostname, ip, db, tbl, idx, lock_type, lock_mode, wait_hold, victim, query
1116+
1117+The columns are:
1118+
1119+=over
1120+
1121+=item server
1122+
1123+The (source) server on which the deadlock occurred. This might be useful if
1124+you're tracking deadlocks on many servers.
1125+
1126+=item ts
1127+
1128+The date and time of the last detected deadlock.
1129+
1130+=item thread
1131+
1132+The MySQL thread number, which is the same as the connection ID in SHOW FULL
1133+PROCESSLIST.
1134+
1135+=item txn_id
1136+
1137+The InnoDB transaction ID, which InnoDB expresses as two unsigned integers. I
1138+have multiplied them out to be one number.
1139+
1140+=item txn_time
1141+
1142+How long the transaction was active when the deadlock happened.
1143+
1144+=item user
1145+
1146+The connection's database username.
1147+
1148+=item hostname
1149+
1150+The connection's host.
1151+
1152+=item ip
1153+
1154+The connection's IP address. If you specify L<"--numeric-ip">, this is
1155+converted to an unsigned integer.
1156+
1157+=item db
1158+
1159+The database in which the deadlock occurred.
1160+
1161+=item tbl
1162+
1163+The table on which the deadlock occurred.
1164+
1165+=item idx
1166+
1167+The index on which the deadlock occurred.
1168+
1169+=item lock_type
1170+
1171+The lock type the transaction held on the lock that caused the deadlock.
1172+
1173+=item lock_mode
1174+
1175+The lock mode of the lock that caused the deadlock.
1176+
1177+=item wait_hold
1178+
1179+Whether the transaction was waiting for the lock or holding the lock. Usually
1180+you will see the two waited-for locks.
1181+
1182+=item victim
1183+
1184+Whether the transaction was selected as the deadlock victim and rolled back.
1185+
1186+=item query
1187+
1188+The query that caused the deadlock.
1189+
1190+=back
1191
1192 =item --config
1193
1194@@ -4617,9 +4998,6 @@
1195 can usually omit most parts of this argument if you're storing deadlocks on the
1196 same server on which they happen.
1197
1198-By default, whitespace in the query column is left intact;
1199-use L<"--[no]collapse"> if you want whitespace collapsed.
1200-
1201 The following MAGIC_dest_table is suggested if you want to store all the
1202 information pt-deadlock-logger can extract about deadlocks:
1203
1204@@ -4658,12 +5036,23 @@
1205
1206 =item --interval
1207
1208-type: time
1209+type: time; default: 30
1210
1211 How often to check for deadlocks. If no L<"--run-time"> is specified,
1212 pt-deadlock-logger runs forever, checking for deadlocks at every interval.
1213 See also L<"--run-time">.
1214
1215+=item --iterations
1216+
1217+type: int
1218+
1219+How many times to check for deadlocks. By default, this option
1220+is undefined which means an infinite number of iterations. The tool always
1221+exits for L<"--run-time">, regardless of the value specified for this option.
1222+For example, the tool will exit after 1 minute with
1223+C<--run-time 1m --iterations 4 --interval 30> because 4 iterations at 30
1224+second intervals would take 2 minutes, longer than the 1 mintue run-time.
1225+
1226 =item --log
1227
1228 type: string
1229@@ -4695,16 +5084,9 @@
1230
1231 Port number to use for connection.
1232
1233-=item --print
1234-
1235-Print results on standard output. See L<"OUTPUT"> for more. By default,
1236-enables L<"--[no]collapse"> unless you explicitly disable it.
1237-
1238-If L<"--interval"> or L<"--run-time"> is specified, only new deadlocks are
1239-printed at each interval. A fingerprint for each deadlock is created using
1240-L<"--columns"> server, ts and thread (even if those columns were not specified
1241-by L<"--columns">) and if the current deadlock's fingerprint is different from
1242-the last deadlock's fingerprint, then it is printed.
1243+=item --quiet
1244+
1245+Do not deadlocks; only print errors and warnings to C<STDERR>.
1246
1247 =item --run-time
1248
1249@@ -4729,7 +5111,7 @@
1250
1251 =item --tab
1252
1253-Print tab-separated columns, instead of aligned.
1254+Use tabs to separate columns instead of spaces.
1255
1256 =item --user
1257
1258@@ -4886,7 +5268,7 @@
1259
1260 =head1 AUTHORS
1261
1262-Baron Schwartz
1263+Baron Schwartz and Daniel Nichter
1264
1265 =head1 ABOUT PERCONA TOOLKIT
1266
1267
1268=== modified file 'bin/pt-fk-error-logger'
1269--- bin/pt-fk-error-logger 2013-02-25 18:55:06 +0000
1270+++ bin/pt-fk-error-logger 2013-02-26 01:08:24 +0000
1271@@ -3915,7 +3915,8 @@
1272
1273 pt-fk-error-logger logs information about foreign key errors on the given
1274 DSN. Information is printed to C<STDOUT>, and it can also be saved to a
1275-table by specifying L<"--dest">.
1276+table by specifying L<"--dest">. The tool runs for forever unless
1277+L<"--run-time"> or L<"--iterations"> is specified.
1278
1279 Print foreign key errors on host1:
1280
1281
1282=== modified file 'lib/Cxn.pm'
1283--- lib/Cxn.pm 2013-02-25 18:52:22 +0000
1284+++ lib/Cxn.pm 2013-02-26 01:08:24 +0000
1285@@ -98,23 +98,24 @@
1286 }
1287
1288 my $self = {
1289- dsn => $dsn,
1290- dbh => $args{dbh},
1291- dsn_name => $dp->as_string($dsn, [qw(h P S)]),
1292- hostname => '',
1293- set => $args{set},
1294- NAME_lc => defined($args{NAME_lc}) ? $args{NAME_lc} : 1,
1295- dbh_set => 0,
1296- OptionParser => $o,
1297- DSNParser => $dp,
1298+ dsn => $dsn,
1299+ dbh => $args{dbh},
1300+ dsn_name => $dp->as_string($dsn, [qw(h P S)]),
1301+ hostname => '',
1302+ set => $args{set},
1303+ NAME_lc => defined($args{NAME_lc}) ? $args{NAME_lc} : 1,
1304+ dbh_set => 0,
1305+ OptionParser => $o,
1306+ DSNParser => $dp,
1307 is_cluster_node => undef,
1308+ parent => $args{parent},
1309 };
1310
1311 return bless $self, $class;
1312 }
1313
1314 sub connect {
1315- my ( $self ) = @_;
1316+ my ( $self, %opts ) = @_;
1317 my $dsn = $self->{dsn};
1318 my $dp = $self->{DSNParser};
1319 my $o = $self->{OptionParser};
1320@@ -126,7 +127,13 @@
1321 $dsn->{p} = OptionParser::prompt_noecho("Enter MySQL password: ");
1322 $self->{asked_for_pass} = 1;
1323 }
1324- $dbh = $dp->get_dbh($dp->get_cxn_params($dsn), { AutoCommit => 1 });
1325+ $dbh = $dp->get_dbh(
1326+ $dp->get_cxn_params($dsn),
1327+ {
1328+ AutoCommit => 1,
1329+ %opts,
1330+ },
1331+ );
1332 }
1333 PTDEBUG && _d($dbh, 'Connected dbh to', $self->{name});
1334
1335@@ -163,6 +170,11 @@
1336 $self->{hostname} = $hostname;
1337 }
1338
1339+ if ( $self->{parent} ) {
1340+ PTDEBUG && _d($dbh, 'Setting InactiveDestroy=1 in parent');
1341+ $dbh->{InactiveDestroy} = 1;
1342+ }
1343+
1344 # Call the set callback to let the caller SET any MySQL variables.
1345 if ( my $set = $self->{set}) {
1346 $set->($dbh);
1347@@ -206,12 +218,18 @@
1348
1349 sub DESTROY {
1350 my ($self) = @_;
1351- if ( $self->{dbh}
1352- && blessed($self->{dbh})
1353- && $self->{dbh}->can("disconnect") ) {
1354+
1355+ if ( $self->{parent} ) {
1356+ PTDEBUG && _d('Not disconnecting dbh in parent');
1357+ }
1358+ elsif ( $self->{dbh}
1359+ && blessed($self->{dbh})
1360+ && $self->{dbh}->can("disconnect") )
1361+ {
1362 PTDEBUG && _d('Disconnecting dbh', $self->{dbh}, $self->{name});
1363 $self->{dbh}->disconnect();
1364 }
1365+
1366 return;
1367 }
1368
1369
1370=== modified file 't/pt-deadlock-logger/basics.t'
1371--- t/pt-deadlock-logger/basics.t 2012-10-22 21:30:29 +0000
1372+++ t/pt-deadlock-logger/basics.t 2013-02-26 01:08:24 +0000
1373@@ -11,6 +11,8 @@
1374 use English qw(-no_match_vars);
1375 use Test::More;
1376
1377+$ENV{PERCONA_TOOLKIT_TEST_USE_DSN_NAMES} = 1;
1378+
1379 use PerconaTest;
1380 use Sandbox;
1381 require "$trunk/bin/pt-deadlock-logger";
1382@@ -25,8 +27,8 @@
1383 }
1384
1385 my $output;
1386-my $cnf = "/tmp/12345/my.sandbox.cnf";
1387-my $cmd = "$trunk/bin/pt-deadlock-logger -F $cnf h=127.1";
1388+my $dsn = $sb->dsn_for('master');
1389+my @args = ($dsn, qw(--iterations 1));
1390
1391 $dbh1->commit;
1392 $dbh2->commit;
1393@@ -90,21 +92,30 @@
1394 $output = $dbh1->selectrow_hashref('show /*!40101 engine*/ innodb status')->{status};
1395 like($output, qr/WE ROLL BACK/, 'There was a deadlock');
1396
1397-$output = `$cmd --print`;
1398+$output = output(
1399+ sub {
1400+ pt_deadlock_logger::main(@args);
1401+ }
1402+);
1403+
1404 like(
1405 $output,
1406 qr/127\.1.+msandbox.+GEN_CLUST_INDEX/,
1407 'Deadlock logger prints the output'
1408 );
1409
1410-$output = `$cmd`;
1411-like(
1412+$output = output(
1413+ sub {
1414+ pt_deadlock_logger::main(@args, qw(--quiet));
1415+ }
1416+);
1417+
1418+is(
1419 $output,
1420- qr/127\.1.+msandbox.+GEN_CLUST_INDEX/,
1421- '--print is implicit'
1422+ "",
1423+ "No output with --quiet"
1424 );
1425
1426-
1427 # #############################################################################
1428 # Issue 943: mk-deadlock-logger reports the same deadlock with --interval
1429 # #############################################################################
1430@@ -112,55 +123,59 @@
1431 # The deadlock from above won't be re-printed so even after running for
1432 # 3 seconds and checking multiple times only the single, 3 line deadlock
1433 # should be reported.
1434-chomp($output = `$cmd --run-time 3 | wc -l`);
1435+
1436+$output = output(
1437+ sub {
1438+ pt_deadlock_logger::main(@args, qw(--run-time 3));
1439+ }
1440+);
1441 $output =~ s/^\s+//;
1442+my @lines = split("\n", $output);
1443 is(
1444- $output,
1445+ scalar @lines,
1446 3,
1447 "Doesn't re-print same deadlock (issue 943)"
1448-);
1449+) or diag($output);
1450
1451 # #############################################################################
1452 # Check that deadlocks from previous test were stored in table.
1453 # #############################################################################
1454-`$cmd --dest D=test,t=deadlocks --create-dest-table`;
1455+$output = output(
1456+ sub {
1457+ pt_deadlock_logger::main(@args, '--dest', 'D=test,t=deadlocks',
1458+ qw(--create-dest-table))
1459+ }
1460+);
1461+
1462 my $res = $dbh1->selectall_arrayref('SELECT * FROM test.deadlocks');
1463 ok(
1464 scalar @$res,
1465- 'Deadlocks recorded in --dest table'
1466-);
1467-
1468-# #############################################################################
1469-# Check that --dest suppress --print output unless --print is explicit.
1470-# #############################################################################
1471-$output = 'foo';
1472-$dbh1->do('TRUNCATE TABLE test.deadlocks');
1473-$output = `$cmd --dest D=test,t=deadlocks`;
1474-is(
1475- $output,
1476- '',
1477- 'No output with --dest'
1478-);
1479-
1480-$res = $dbh1->selectall_arrayref('SELECT * FROM test.deadlocks');
1481-ok(
1482- scalar @$res,
1483- 'Deadlocks still recorded in table'
1484-);
1485-
1486+ 'Deadlock saved in --dest table'
1487+) or diag($output);
1488+
1489+# #############################################################################
1490+# In 2.1, --dest suppressed output (--print). In 2.2, output is only
1491+# suppressed by --quiet.
1492+# #############################################################################
1493 $output = '';
1494 $dbh1->do('TRUNCATE TABLE test.deadlocks');
1495-$output = `$trunk/bin/pt-deadlock-logger --print --dest D=test,t=deadlocks --host 127.1 --port 12345 --user msandbox --password msandbox`;
1496-like(
1497+$output = output(
1498+ sub {
1499+ pt_deadlock_logger::main(@args, '--dest', 'D=test,t=deadlocks',
1500+ qw(--quiet))
1501+ }
1502+);
1503+
1504+is(
1505 $output,
1506- qr/127\.1.+msandbox.+GEN_CLUST_INDEX/,
1507- 'Prints output with --dest and explicit --print'
1508+ "",
1509+ "No output with --dest and --quiet"
1510 );
1511
1512 $res = $dbh1->selectall_arrayref('SELECT * FROM test.deadlocks');
1513 ok(
1514 scalar @$res,
1515- 'Deadlocks recorded in table again'
1516+ "... deadlock still saved in the table"
1517 );
1518
1519 # #############################################################################
1520@@ -180,9 +195,7 @@
1521 make_deadlock();
1522
1523 $output = output(
1524- sub { pt_deadlock_logger::main("F=/tmp/12345/my.sandbox.cnf",
1525- qw(--print) );
1526- }
1527+ sub { pt_deadlock_logger::main(@args) }
1528 );
1529
1530 like(
1531@@ -200,4 +213,3 @@
1532 $sb->wipe_clean($dbh1);
1533 ok($sb->ok(), "Sandbox servers") or BAIL_OUT(__FILE__ . " broke the sandbox");
1534 done_testing;
1535-exit;
1536
1537=== modified file 't/pt-deadlock-logger/clear_deadlocks.t'
1538--- t/pt-deadlock-logger/clear_deadlocks.t 2012-06-03 19:14:30 +0000
1539+++ t/pt-deadlock-logger/clear_deadlocks.t 2013-02-26 01:08:24 +0000
1540@@ -22,9 +22,6 @@
1541 if ( !$dbh1 ) {
1542 plan skip_all => 'Cannot connect to sandbox master';
1543 }
1544-else {
1545- plan tests => 4;
1546-}
1547
1548 my $output;
1549 my $cnf = "/tmp/12345/my.sandbox.cnf";
1550@@ -39,7 +36,7 @@
1551
1552 # The clear-deadlocks table comes and goes quickly so we can really
1553 # only search the debug output for evidence that it was created.
1554-$output = `PTDEBUG=1 $trunk/bin/pt-deadlock-logger F=$cnf,D=test --clear-deadlocks test.make_deadlock 2>&1`;
1555+$output = `PTDEBUG=1 $trunk/bin/pt-deadlock-logger F=$cnf,D=test --clear-deadlocks test.make_deadlock --iterations 1 2>&1`;
1556 like(
1557 $output,
1558 qr/INSERT INTO test.make_deadlock/,
1559@@ -67,4 +64,4 @@
1560 # #############################################################################
1561 $sb->wipe_clean($dbh1);
1562 ok($sb->ok(), "Sandbox servers") or BAIL_OUT(__FILE__ . " broke the sandbox");
1563-exit;
1564+done_testing;
1565
1566=== modified file 't/pt-deadlock-logger/create_dest_table.t'
1567--- t/pt-deadlock-logger/create_dest_table.t 2012-06-09 18:43:33 +0000
1568+++ t/pt-deadlock-logger/create_dest_table.t 2013-02-26 01:08:24 +0000
1569@@ -22,15 +22,10 @@
1570 if ( !$dbh1 ) {
1571 plan skip_all => 'Cannot connect to sandbox master';
1572 }
1573-else {
1574- plan tests => 3;
1575-}
1576
1577 my $output;
1578-my $cnf = "/tmp/12345/my.sandbox.cnf";
1579-my $cmd = "$trunk/bin/pt-deadlock-logger -F $cnf h=127.1";
1580+my $dsn = $sb->dsn_for('master');
1581
1582-$sb->wipe_clean($dbh1);
1583 $sb->create_dbs($dbh1, ['test']);
1584
1585 # #############################################################################
1586@@ -42,17 +37,25 @@
1587 'Deadlocks table does not exit (issue 386)'
1588 );
1589
1590-`$cmd --dest D=test,t=issue_386 --run-time 1s --interval 1s --create-dest-table`;
1591+$output = output(
1592+ sub {
1593+ pt_deadlock_logger::main($dsn,
1594+ '--dest', 'D=test,t=issue_386',
1595+ qw(--iterations 1 --create-dest-table)
1596+ )
1597+ },
1598+ stderr => 1,
1599+);
1600
1601 is_deeply(
1602 $dbh1->selectall_arrayref(q{show tables from `test` like 'issue_386'}),
1603 [['issue_386']],
1604 'Deadlocks table created with --create-dest-table (issue 386)'
1605-);
1606+) or diag($output);
1607
1608 # #############################################################################
1609 # Done.
1610 # #############################################################################
1611 $sb->wipe_clean($dbh1);
1612 ok($sb->ok(), "Sandbox servers") or BAIL_OUT(__FILE__ . " broke the sandbox");
1613-exit;
1614+done_testing;
1615
1616=== modified file 't/pt-deadlock-logger/option_sanity.t'
1617--- t/pt-deadlock-logger/option_sanity.t 2012-10-22 18:16:42 +0000
1618+++ t/pt-deadlock-logger/option_sanity.t 2013-02-26 01:08:24 +0000
1619@@ -21,7 +21,7 @@
1620 $output = `$trunk/bin/pt-deadlock-logger --dest D=test,t=deadlocks 2>&1`;
1621 like(
1622 $output,
1623- qr/Missing or invalid source host/,
1624+ qr/No DSN was specified/,
1625 'Requires source host'
1626 );
1627
1628
1629=== renamed file 't/pt-deadlock-logger/deadlocks_tbl.sql' => 't/pt-deadlock-logger/samples/deadlocks_tbl.sql'
1630=== modified file 't/pt-deadlock-logger/standard_options.t'
1631--- t/pt-deadlock-logger/standard_options.t 2012-07-23 04:52:41 +0000
1632+++ t/pt-deadlock-logger/standard_options.t 2013-02-26 01:08:24 +0000
1633@@ -17,69 +17,96 @@
1634
1635 my $dp = new DSNParser(opts=>$dsn_opts);
1636 my $sb = new Sandbox(basedir => '/tmp', DSNParser => $dp);
1637-my $dbh1 = $sb->get_dbh_for('master');
1638+my $dbh = $sb->get_dbh_for('master');
1639
1640-if ( !$dbh1 ) {
1641+if ( !$dbh ) {
1642 plan skip_all => 'Cannot connect to sandbox master';
1643 }
1644-else {
1645- plan tests => 10;
1646-}
1647
1648 my $output;
1649-my $cnf = "/tmp/12345/my.sandbox.cnf";
1650-my $cmd = "$trunk/bin/pt-deadlock-logger -F $cnf h=127.1";
1651+my $dsn = $sb->dsn_for('master');
1652+my @args = ($dsn, qw(--iterations 1));
1653
1654-$sb->wipe_clean($dbh1);
1655-$sb->create_dbs($dbh1, ['test']);
1656+$sb->wipe_clean($dbh);
1657+$sb->create_dbs($dbh, ['test']);
1658
1659 # #############################################################################
1660 # Issue 248: Add --user, --pass, --host, etc to all tools
1661 # #############################################################################
1662
1663 # Test that source DSN inherits from --user, etc.
1664-$output = `$trunk/bin/pt-deadlock-logger h=127.1,D=test,u=msandbox,p=msandbox --clear-deadlocks test.make_deadlock --port 12345 2>&1`;
1665+$output = output(
1666+ sub {
1667+ pt_deadlock_logger::main(
1668+ "h=127.1,D=test,u=msandbox,p=msandbox",
1669+ qw(--clear-deadlocks test.make_deadlock --port 12345),
1670+ qw(--iterations 1)
1671+ )
1672+ }
1673+);
1674+
1675 unlike(
1676 $output,
1677 qr/failed/,
1678 'Source DSN inherits from standard connection options (issue 248)'
1679 );
1680
1681-# #########################################################################
1682+# #############################################################################
1683 # Issue 391: Add --pid option to all scripts
1684-# #########################################################################
1685-`touch /tmp/mk-script.pid`;
1686-$output = `$cmd --clear-deadlocks test.make_deadlock --port 12345 --pid /tmp/mk-script.pid 2>&1`;
1687+# #############################################################################
1688+
1689+my $pid_file = "/tmp/pt-deadlock-logger-test.pid.$PID";
1690+diag(`touch $pid_file`);
1691+
1692+$output = output(
1693+ sub {
1694+ pt_deadlock_logger::main(@args, '--pid', $pid_file)
1695+ },
1696+ stderr => 1,
1697+);
1698+
1699 like(
1700 $output,
1701- qr{PID file /tmp/mk-script.pid already exists},
1702+ qr{PID file $pid_file already exists},
1703 'Dies if PID file already exists (--pid without --daemonize) (issue 391)'
1704 );
1705-`rm -rf /tmp/mk-script.pid`;
1706+
1707+unlink $pid_file if -f $pid_file;
1708
1709 # #############################################################################
1710 # Check daemonization
1711 # #############################################################################
1712-my $deadlocks_tbl = load_file('t/pt-deadlock-logger/deadlocks_tbl.sql');
1713-$dbh1->do('USE test');
1714-$dbh1->do('DROP TABLE IF EXISTS deadlocks');
1715-$dbh1->do("$deadlocks_tbl");
1716-
1717-my $pid_file = '/tmp/mk-deadlock-logger.pid';
1718-unlink $pid_file
1719- and diag("Unlinked existing $pid_file");
1720-
1721-`$cmd --dest D=test,t=deadlocks --daemonize --run-time 6s --interval 1s --pid $pid_file 1>/dev/null 2>/dev/null`;
1722-$output = `ps -eaf | grep '$cmd \-\-dest '`;
1723-like($output, qr/\Q$cmd/, 'It lives daemonized');
1724+$dbh->do('USE test');
1725+$dbh->do('DROP TABLE IF EXISTS deadlocks');
1726+$sb->load_file('master', 't/pt-deadlock-logger/samples/deadlocks_tbl.sql', 'test');
1727+
1728+$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`;
1729
1730 PerconaTest::wait_for_files($pid_file);
1731-ok(-f $pid_file, 'PID file created');
1732-my ($pid) = $output =~ /\s+(\d+)\s+/;
1733+
1734+$output = `ps x | grep 'pt-deadlock-logger $dsn' | grep -v grep`;
1735+like(
1736+ $output,
1737+ qr/\Qpt-deadlock-logger $dsn/,
1738+ 'It lives daemonized'
1739+) or diag($output);
1740+
1741+my ($pid) = $output =~ /(\d+)/;
1742+
1743+ok(
1744+ -f $pid_file,
1745+ 'PID file created'
1746+) or diag($output);
1747+
1748 chomp($output = slurp_file($pid_file));
1749-is($output, $pid, 'PID file has correct PID');
1750+is(
1751+ $output,
1752+ $pid,
1753+ 'PID file has correct PID'
1754+);
1755
1756 # Kill it
1757+kill 2, $pid;
1758 PerconaTest::wait_until(sub { !kill 0, $pid });
1759 ok(! -f $pid_file, 'PID file removed');
1760
1761@@ -90,17 +117,25 @@
1762 'PID file already exists'
1763 );
1764
1765-$output = `$cmd --dest D=test,t=deadlocks --daemonize --run-time 1s --interval 1s --pid $pid_file 2>&1`;
1766+$output = output(
1767+ sub {
1768+ pt_deadlock_logger::main(@args, '--pid', $pid_file,
1769+ qw(--daemonize))
1770+ },
1771+ stderr => 1,
1772+);
1773+
1774 like(
1775 $output,
1776- qr/PID file .+ already exists/,
1777+ qr/PID file $pid_file already exists/,
1778 'Does not run if PID file already exists'
1779 );
1780
1781-$output = `ps -eaf | grep 'pt-deadlock-logger \-\-dest '`;
1782-unlike(
1783+$output = `ps x | grep 'pt-deadlock-logger $dsn' | grep -v grep`;
1784+
1785+is(
1786 $output,
1787- qr/$cmd/,
1788+ "",
1789 'It does not lived daemonized'
1790 );
1791
1792@@ -109,6 +144,6 @@
1793 # #############################################################################
1794 # Done.
1795 # #############################################################################
1796-$sb->wipe_clean($dbh1);
1797+$sb->wipe_clean($dbh);
1798 ok($sb->ok(), "Sandbox servers") or BAIL_OUT(__FILE__ . " broke the sandbox");
1799-exit;
1800+done_testing;

Subscribers

People subscribed via source and target branches

to all changes: