Merge lp:~percona-toolkit-dev/percona-toolkit/masterslave-redesign-prototype into lp:percona-toolkit/2.1

Proposed by Brian Fraser
Status: Superseded
Proposed branch: lp:~percona-toolkit-dev/percona-toolkit/masterslave-redesign-prototype
Merge into: lp:percona-toolkit/2.1
Diff against target: 8531 lines (+2670/-3965)
17 files modified
bin/pt-archiver (+199/-245)
bin/pt-heartbeat (+644/-240)
bin/pt-kill (+79/-1197)
bin/pt-online-schema-change (+184/-240)
bin/pt-query-digest (+87/-750)
bin/pt-slave-find (+376/-256)
bin/pt-slave-restart (+191/-241)
bin/pt-table-checksum (+191/-242)
bin/pt-table-sync (+46/-0)
bin/pt-upgrade (+15/-5)
lib/Cxn.pm (+19/-1)
lib/MasterSlave.pm (+103/-296)
lib/Mo.pm (+43/-5)
lib/Percona/Object.pm (+84/-0)
lib/Processlist.pm (+103/-6)
t/lib/MasterSlave.t (+79/-234)
t/lib/Processlist.t (+227/-7)
To merge this branch: bzr merge lp:~percona-toolkit-dev/percona-toolkit/masterslave-redesign-prototype
Reviewer Review Type Date Requested Status
Daniel Nichter Needs Fixing
Review via email: mp+130197@code.launchpad.net
To post a comment you must log in.
417. By Daniel Nichter

Merge fix-pt-upgrade-select-bug-1060774

418. By Daniel Nichter

Merge fix-938068-ptc-slave-binlog-formats

419. By Daniel Nichter

Merge fix-978133-remove-pqd-priv-checks

Revision history for this message
Daniel Nichter (daniel-nichter) wrote :

Typo: http://bazaar.launchpad.net/~percona-toolkit-dev/percona-toolkit/masterslave-redesign-prototype/revision/356#lib/TableSyncer.pm

Moving is_replication_thread to Processlist.pm is good.

What does Percona::Object do? It looks like if an obj has => 'foo_bar' and it also has OptionParser then it sets its foo_bar = $o->get('foo-bar')?

# Internal caches
has [qw( _not_a_master _not_a_slave _sths )] => (
^ Get rid of all caching. Well not *all* caching: stuff like server_ids_seen is required. What I mean is "global" caches, i.e. stuff that returns cached results (problematic) vs. keeping the code from getting itself in loops (which is what server_ids_seen does).

_repl_posn() -- is now pseudo-private (leading underscore)? (Re _pos_cmp())

Re short_host(): I think pt-slave-find needs to use Cxn and then move short_hosts()'s logic into Cxn::name() and enable via an optional arg like name(short => 1);

Overall, looks good. Make sure to update MasterSlave in all tools and then run a full test because messing with MasterSlave can introduce subtle bugs that only come up in certain cases.

review: Needs Fixing
420. By Daniel Nichter

Redirect sys cmd 2>/dev/null in pt-kill tests to avoid false-positive errors.

421. By Daniel Nichter

Fix RawLogParser.t. Use diag in PerconaTest.pm.

422. By Daniel Nichter

Remove all_privs tests. Update Percona::Toolkit::VERSION.

423. By Daniel Nichter

Make CompareResults.t stable--yet another case of not waiting for replication.

424. By Daniel Nichter

Merge fix-pt-osc-del-trg-bug-1062324.

425. By Daniel Nichter

Merge opt-parsing-exit-status-bug-1039074.

426. By Daniel Nichter

Merge quiet-progress-bug-1039541.

427. By Daniel Nichter

Merge pdl-partitions-bug-1043528.

428. By Daniel Nichter

Merge pt-stalk-iter-1-bug-1070434.

429. By Daniel Nichter

Merge find_my_cnf_file-bug-1070916.

430. By Daniel Nichter

Remove $Sandbox::Percona::Toolkit::VERSION and use $Percona::Toolkit::VERSION instead since it's the authoritative version.

431. By Daniel Nichter

Add deprecation notice to pt-log-player for PT 2.2.

432. By Daniel Nichter

Fix t/pt-slave-delay/auto_restart.t and use direct call, no backticks.

433. By Daniel Nichter

Don't use literal values for t/pt-heartbeat/basics.t 'It is being updated' test. Use direct call rather than backticks.

434. By Daniel Nichter

Make pt-archiver --sleep tests more precise and reliable.

435. By Daniel Nichter

Merge fix-ptc-slave-binglog-formats-on-5.0.

436. By Daniel Nichter

Merge pt-find-docu-bug-1013407

437. By Daniel Nichter

Merge fix-995896-cat-in-daemon

438. By Brian Fraser

Merged fix-821715-enable-local-infile-in-dsn

439. By Brian Fraser

Merged fix-1052722-pt-fifo-split-n-minus-1-rows-initially

440. By Brian Fraser

Updated modules in all tools

441. By Daniel Nichter

Merge fix-938660-ptc-chunk-size-limit-0

442. By Daniel Nichter

Merge pt-show-grant-col-privs-bug-866075

443. By Brian Fraser

Merged fix-1059732-ptc-hash-functions

444. By Brian Fraser

bin/pt-table-checksum: Missing word in an error message

445. By Daniel Nichter

Merge fix-1009510-1039569-ptc-check-table-on-replicas

446. By Daniel Nichter

Merged fix-i26211-1058285-821722-implicit-ansi_quotes

447. By Brian Fraser

Merged fix-1073532-Mo-Scalar-Util-PP

448. By Brian Fraser

Merged sandbox-get_dbh_for-fix

449. By Brian Fraser

Removed the L option from the dsn_opts exported by PerconaTest, as it was useless and breaking tests, and added an L=1 to a leftover pt-archiver --bulk-insert call

450. By Daniel Nichter

Merge fix-pt-fel-bug-1075773

451. By Daniel Nichter

Merge pt-osc-data-loss-bug-1068562

452. By Daniel Nichter

Merge fix-1045317-pt-osc-statistics

453. By Daniel Nichter

Remove duplicate code block I created by merge bits and pieces of branches, thereby confusing bzr.

454. By Brian Fraser

Merged OptionParser-remove-optional_value & updated modules

455. By Brian Fraser

t/pt-online-schema-change/basics.t: --statistics test outputs an extra line in 5.5

456. By Brian Fraser

Merged fix-1047335-crashed-tables

457. By Brian Fraser

Merged fix-1062563-1063912-ptc-pxc-bugs

458. By Brian Fraser

Missing /g in a Quoter regex

459. By Brian Fraser

Missing sample file for a pt-osc test

460. By Brian Fraser

Merged masterslave-redesign-prototype and resolved conflicts

461. By Brian Fraser

lib/MasterSlave.pm: Make get_slave_status work under PTDEBUG

462. By Brian Fraser

Cxn: Incorporate the short_host logic from pt-slave-find into ->name(), enabled by the short_host attribute

463. By Brian Fraser

bin/pt-slave-find: Use Cxn, remove short_host

464. By Brian Fraser

MasterSlave: stop using one-off prepared statements

465. By Brian Fraser

Fixed tests broken during the redesign

466. By Brian Fraser

Updated modules for all tools

Unmerged revisions

466. By Brian Fraser

Updated modules for all tools

465. By Brian Fraser

Fixed tests broken during the redesign

464. By Brian Fraser

MasterSlave: stop using one-off prepared statements

463. By Brian Fraser

bin/pt-slave-find: Use Cxn, remove short_host

462. By Brian Fraser

Cxn: Incorporate the short_host logic from pt-slave-find into ->name(), enabled by the short_host attribute

461. By Brian Fraser

lib/MasterSlave.pm: Make get_slave_status work under PTDEBUG

460. By Brian Fraser

Merged masterslave-redesign-prototype and resolved conflicts

Preview Diff

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

Subscribers

People subscribed via source and target branches