Merge lp:~percona-toolkit-dev/percona-toolkit/pt-kill-log-dsn into lp:percona-toolkit/2.1
- pt-kill-log-dsn
- Merge into 2.1
Proposed by
Daniel Nichter
Status: | Merged |
---|---|
Approved by: | Daniel Nichter |
Approved revision: | 315 |
Merged at revision: | 315 |
Proposed branch: | lp:~percona-toolkit-dev/percona-toolkit/pt-kill-log-dsn |
Merge into: | lp:percona-toolkit/2.1 |
Diff against target: |
1155 lines (+915/-33) 4 files modified
bin/pt-kill (+720/-7) lib/Processlist.pm (+16/-3) t/lib/Processlist.t (+23/-21) t/pt-kill/kill.t (+156/-2) |
To merge this branch: | bzr merge lp:~percona-toolkit-dev/percona-toolkit/pt-kill-log-dsn |
Related bugs: | |
Related blueprints: |
Make pt-kill log its actions
(High)
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Daniel Nichter | Approve | ||
Review via email:
|
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message

Daniel Nichter (daniel-nichter) : | # |
review:
Needs Fixing
Revision history for this message

Daniel Nichter (daniel-nichter) wrote : | # |
- 313. By Brian Fraser
-
t/pt-kill/kill.t: Make a test 5.0 compatible
- 314. By Daniel Nichter
-
Move certain vars to outer scope to avoid Perl 5.8 scoping bug.
- 315. By Brian Fraser
-
Really make a test 5.0 compatible
Revision history for this message

Daniel Nichter (daniel-nichter) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'bin/pt-kill' |
2 | --- bin/pt-kill 2012-07-15 02:58:17 +0000 |
3 | +++ bin/pt-kill 2012-07-19 16:42:35 +0000 |
4 | @@ -1280,7 +1280,7 @@ |
5 | } |
6 | |
7 | foreach my $key ( keys %given_props ) { |
8 | - die "Unknown DSN option '$key' in '$dsn'. For more details, " |
9 | + die "DSN option '$key' in '$dsn'. For more details, " |
10 | . "please use the --help option, or try 'perldoc $PROGRAM_NAME' " |
11 | . "for complete documentation." |
12 | unless exists $opts->{$key}; |
13 | @@ -2087,6 +2087,436 @@ |
14 | # ########################################################################### |
15 | |
16 | # ########################################################################### |
17 | +# TableParser package |
18 | +# This package is a copy without comments from the original. The original |
19 | +# with comments and its test file can be found in the Bazaar repository at, |
20 | +# lib/TableParser.pm |
21 | +# t/lib/TableParser.t |
22 | +# See https://launchpad.net/percona-toolkit for more information. |
23 | +# ########################################################################### |
24 | +{ |
25 | +package TableParser; |
26 | + |
27 | +use strict; |
28 | +use warnings FATAL => 'all'; |
29 | +use English qw(-no_match_vars); |
30 | +use constant PTDEBUG => $ENV{PTDEBUG} || 0; |
31 | + |
32 | +use Data::Dumper; |
33 | +$Data::Dumper::Indent = 1; |
34 | +$Data::Dumper::Sortkeys = 1; |
35 | +$Data::Dumper::Quotekeys = 0; |
36 | + |
37 | +sub new { |
38 | + my ( $class, %args ) = @_; |
39 | + my @required_args = qw(Quoter); |
40 | + foreach my $arg ( @required_args ) { |
41 | + die "I need a $arg argument" unless $args{$arg}; |
42 | + } |
43 | + my $self = { %args }; |
44 | + return bless $self, $class; |
45 | +} |
46 | + |
47 | +sub get_create_table { |
48 | + my ( $self, $dbh, $db, $tbl ) = @_; |
49 | + die "I need a dbh parameter" unless $dbh; |
50 | + die "I need a db parameter" unless $db; |
51 | + die "I need a tbl parameter" unless $tbl; |
52 | + my $q = $self->{Quoter}; |
53 | + |
54 | + my $new_sql_mode |
55 | + = '/*!40101 SET @OLD_SQL_MODE := @@SQL_MODE, ' |
56 | + . q{@@SQL_MODE := REPLACE(REPLACE(@@SQL_MODE, 'ANSI_QUOTES', ''), ',,', ','), } |
57 | + . '@OLD_QUOTE := @@SQL_QUOTE_SHOW_CREATE, ' |
58 | + . '@@SQL_QUOTE_SHOW_CREATE := 1 */'; |
59 | + |
60 | + my $old_sql_mode = '/*!40101 SET @@SQL_MODE := @OLD_SQL_MODE, ' |
61 | + . '@@SQL_QUOTE_SHOW_CREATE := @OLD_QUOTE */'; |
62 | + |
63 | + PTDEBUG && _d($new_sql_mode); |
64 | + eval { $dbh->do($new_sql_mode); }; |
65 | + PTDEBUG && $EVAL_ERROR && _d($EVAL_ERROR); |
66 | + |
67 | + my $use_sql = 'USE ' . $q->quote($db); |
68 | + PTDEBUG && _d($dbh, $use_sql); |
69 | + $dbh->do($use_sql); |
70 | + |
71 | + my $show_sql = "SHOW CREATE TABLE " . $q->quote($db, $tbl); |
72 | + PTDEBUG && _d($show_sql); |
73 | + my $href; |
74 | + eval { $href = $dbh->selectrow_hashref($show_sql); }; |
75 | + if ( $EVAL_ERROR ) { |
76 | + PTDEBUG && _d($EVAL_ERROR); |
77 | + |
78 | + PTDEBUG && _d($old_sql_mode); |
79 | + $dbh->do($old_sql_mode); |
80 | + |
81 | + return; |
82 | + } |
83 | + |
84 | + PTDEBUG && _d($old_sql_mode); |
85 | + $dbh->do($old_sql_mode); |
86 | + |
87 | + my ($key) = grep { m/create (?:table|view)/i } keys %$href; |
88 | + if ( !$key ) { |
89 | + die "Error: no 'Create Table' or 'Create View' in result set from " |
90 | + . "$show_sql: " . Dumper($href); |
91 | + } |
92 | + |
93 | + return $href->{$key}; |
94 | +} |
95 | + |
96 | +sub parse { |
97 | + my ( $self, $ddl, $opts ) = @_; |
98 | + return unless $ddl; |
99 | + |
100 | + if ( $ddl !~ m/CREATE (?:TEMPORARY )?TABLE `/ ) { |
101 | + die "Cannot parse table definition; is ANSI quoting " |
102 | + . "enabled or SQL_QUOTE_SHOW_CREATE disabled?"; |
103 | + } |
104 | + |
105 | + my ($name) = $ddl =~ m/CREATE (?:TEMPORARY )?TABLE\s+(`.+?`)/; |
106 | + (undef, $name) = $self->{Quoter}->split_unquote($name) if $name; |
107 | + |
108 | + $ddl =~ s/(`[^`]+`)/\L$1/g; |
109 | + |
110 | + my $engine = $self->get_engine($ddl); |
111 | + |
112 | + my @defs = $ddl =~ m/^(\s+`.*?),?$/gm; |
113 | + my @cols = map { $_ =~ m/`([^`]+)`/ } @defs; |
114 | + PTDEBUG && _d('Table cols:', join(', ', map { "`$_`" } @cols)); |
115 | + |
116 | + my %def_for; |
117 | + @def_for{@cols} = @defs; |
118 | + |
119 | + my (@nums, @null); |
120 | + my (%type_for, %is_nullable, %is_numeric, %is_autoinc); |
121 | + foreach my $col ( @cols ) { |
122 | + my $def = $def_for{$col}; |
123 | + my ( $type ) = $def =~ m/`[^`]+`\s([a-z]+)/; |
124 | + die "Can't determine column type for $def" unless $type; |
125 | + $type_for{$col} = $type; |
126 | + if ( $type =~ m/(?:(?:tiny|big|medium|small)?int|float|double|decimal|year)/ ) { |
127 | + push @nums, $col; |
128 | + $is_numeric{$col} = 1; |
129 | + } |
130 | + if ( $def !~ m/NOT NULL/ ) { |
131 | + push @null, $col; |
132 | + $is_nullable{$col} = 1; |
133 | + } |
134 | + $is_autoinc{$col} = $def =~ m/AUTO_INCREMENT/i ? 1 : 0; |
135 | + } |
136 | + |
137 | + my ($keys, $clustered_key) = $self->get_keys($ddl, $opts, \%is_nullable); |
138 | + |
139 | + my ($charset) = $ddl =~ m/DEFAULT CHARSET=(\w+)/; |
140 | + |
141 | + return { |
142 | + name => $name, |
143 | + cols => \@cols, |
144 | + col_posn => { map { $cols[$_] => $_ } 0..$#cols }, |
145 | + is_col => { map { $_ => 1 } @cols }, |
146 | + null_cols => \@null, |
147 | + is_nullable => \%is_nullable, |
148 | + is_autoinc => \%is_autoinc, |
149 | + clustered_key => $clustered_key, |
150 | + keys => $keys, |
151 | + defs => \%def_for, |
152 | + numeric_cols => \@nums, |
153 | + is_numeric => \%is_numeric, |
154 | + engine => $engine, |
155 | + type_for => \%type_for, |
156 | + charset => $charset, |
157 | + }; |
158 | +} |
159 | + |
160 | +sub sort_indexes { |
161 | + my ( $self, $tbl ) = @_; |
162 | + |
163 | + my @indexes |
164 | + = sort { |
165 | + (($a ne 'PRIMARY') <=> ($b ne 'PRIMARY')) |
166 | + || ( !$tbl->{keys}->{$a}->{is_unique} <=> !$tbl->{keys}->{$b}->{is_unique} ) |
167 | + || ( $tbl->{keys}->{$a}->{is_nullable} <=> $tbl->{keys}->{$b}->{is_nullable} ) |
168 | + || ( scalar(@{$tbl->{keys}->{$a}->{cols}}) <=> scalar(@{$tbl->{keys}->{$b}->{cols}}) ) |
169 | + } |
170 | + grep { |
171 | + $tbl->{keys}->{$_}->{type} eq 'BTREE' |
172 | + } |
173 | + sort keys %{$tbl->{keys}}; |
174 | + |
175 | + PTDEBUG && _d('Indexes sorted best-first:', join(', ', @indexes)); |
176 | + return @indexes; |
177 | +} |
178 | + |
179 | +sub find_best_index { |
180 | + my ( $self, $tbl, $index ) = @_; |
181 | + my $best; |
182 | + if ( $index ) { |
183 | + ($best) = grep { uc $_ eq uc $index } keys %{$tbl->{keys}}; |
184 | + } |
185 | + if ( !$best ) { |
186 | + if ( $index ) { |
187 | + die "Index '$index' does not exist in table"; |
188 | + } |
189 | + else { |
190 | + ($best) = $self->sort_indexes($tbl); |
191 | + } |
192 | + } |
193 | + PTDEBUG && _d('Best index found is', $best); |
194 | + return $best; |
195 | +} |
196 | + |
197 | +sub find_possible_keys { |
198 | + my ( $self, $dbh, $database, $table, $quoter, $where ) = @_; |
199 | + return () unless $where; |
200 | + my $sql = 'EXPLAIN SELECT * FROM ' . $quoter->quote($database, $table) |
201 | + . ' WHERE ' . $where; |
202 | + PTDEBUG && _d($sql); |
203 | + my $expl = $dbh->selectrow_hashref($sql); |
204 | + $expl = { map { lc($_) => $expl->{$_} } keys %$expl }; |
205 | + if ( $expl->{possible_keys} ) { |
206 | + PTDEBUG && _d('possible_keys =', $expl->{possible_keys}); |
207 | + my @candidates = split(',', $expl->{possible_keys}); |
208 | + my %possible = map { $_ => 1 } @candidates; |
209 | + if ( $expl->{key} ) { |
210 | + PTDEBUG && _d('MySQL chose', $expl->{key}); |
211 | + unshift @candidates, grep { $possible{$_} } split(',', $expl->{key}); |
212 | + PTDEBUG && _d('Before deduping:', join(', ', @candidates)); |
213 | + my %seen; |
214 | + @candidates = grep { !$seen{$_}++ } @candidates; |
215 | + } |
216 | + PTDEBUG && _d('Final list:', join(', ', @candidates)); |
217 | + return @candidates; |
218 | + } |
219 | + else { |
220 | + PTDEBUG && _d('No keys in possible_keys'); |
221 | + return (); |
222 | + } |
223 | +} |
224 | + |
225 | +sub check_table { |
226 | + my ( $self, %args ) = @_; |
227 | + my @required_args = qw(dbh db tbl); |
228 | + foreach my $arg ( @required_args ) { |
229 | + die "I need a $arg argument" unless $args{$arg}; |
230 | + } |
231 | + my ($dbh, $db, $tbl) = @args{@required_args}; |
232 | + my $q = $self->{Quoter}; |
233 | + my $db_tbl = $q->quote($db, $tbl); |
234 | + PTDEBUG && _d('Checking', $db_tbl); |
235 | + |
236 | + my $sql = "SHOW TABLES FROM " . $q->quote($db) |
237 | + . ' LIKE ' . $q->literal_like($tbl); |
238 | + PTDEBUG && _d($sql); |
239 | + my $row; |
240 | + eval { |
241 | + $row = $dbh->selectrow_arrayref($sql); |
242 | + }; |
243 | + if ( $EVAL_ERROR ) { |
244 | + PTDEBUG && _d($EVAL_ERROR); |
245 | + return 0; |
246 | + } |
247 | + if ( !$row->[0] || $row->[0] ne $tbl ) { |
248 | + PTDEBUG && _d('Table does not exist'); |
249 | + return 0; |
250 | + } |
251 | + |
252 | + PTDEBUG && _d('Table exists; no privs to check'); |
253 | + return 1 unless $args{all_privs}; |
254 | + |
255 | + $sql = "SHOW FULL COLUMNS FROM $db_tbl"; |
256 | + PTDEBUG && _d($sql); |
257 | + eval { |
258 | + $row = $dbh->selectrow_hashref($sql); |
259 | + }; |
260 | + if ( $EVAL_ERROR ) { |
261 | + PTDEBUG && _d($EVAL_ERROR); |
262 | + return 0; |
263 | + } |
264 | + if ( !scalar keys %$row ) { |
265 | + PTDEBUG && _d('Table has no columns:', Dumper($row)); |
266 | + return 0; |
267 | + } |
268 | + my $privs = $row->{privileges} || $row->{Privileges}; |
269 | + |
270 | + $sql = "DELETE FROM $db_tbl LIMIT 0"; |
271 | + PTDEBUG && _d($sql); |
272 | + eval { |
273 | + $dbh->do($sql); |
274 | + }; |
275 | + my $can_delete = $EVAL_ERROR ? 0 : 1; |
276 | + |
277 | + PTDEBUG && _d('User privs on', $db_tbl, ':', $privs, |
278 | + ($can_delete ? 'delete' : '')); |
279 | + |
280 | + if ( !($privs =~ m/select/ && $privs =~ m/insert/ && $privs =~ m/update/ |
281 | + && $can_delete) ) { |
282 | + PTDEBUG && _d('User does not have all privs'); |
283 | + return 0; |
284 | + } |
285 | + |
286 | + PTDEBUG && _d('User has all privs'); |
287 | + return 1; |
288 | +} |
289 | + |
290 | +sub get_engine { |
291 | + my ( $self, $ddl, $opts ) = @_; |
292 | + my ( $engine ) = $ddl =~ m/\).*?(?:ENGINE|TYPE)=(\w+)/; |
293 | + PTDEBUG && _d('Storage engine:', $engine); |
294 | + return $engine || undef; |
295 | +} |
296 | + |
297 | +sub get_keys { |
298 | + my ( $self, $ddl, $opts, $is_nullable ) = @_; |
299 | + my $engine = $self->get_engine($ddl); |
300 | + my $keys = {}; |
301 | + my $clustered_key = undef; |
302 | + |
303 | + KEY: |
304 | + foreach my $key ( $ddl =~ m/^ ((?:[A-Z]+ )?KEY .*)$/gm ) { |
305 | + |
306 | + next KEY if $key =~ m/FOREIGN/; |
307 | + |
308 | + my $key_ddl = $key; |
309 | + PTDEBUG && _d('Parsed key:', $key_ddl); |
310 | + |
311 | + if ( $engine !~ m/MEMORY|HEAP/ ) { |
312 | + $key =~ s/USING HASH/USING BTREE/; |
313 | + } |
314 | + |
315 | + my ( $type, $cols ) = $key =~ m/(?:USING (\w+))? \((.+)\)/; |
316 | + my ( $special ) = $key =~ m/(FULLTEXT|SPATIAL)/; |
317 | + $type = $type || $special || 'BTREE'; |
318 | + if ( $opts->{mysql_version} && $opts->{mysql_version} lt '004001000' |
319 | + && $engine =~ m/HEAP|MEMORY/i ) |
320 | + { |
321 | + $type = 'HASH'; # MySQL pre-4.1 supports only HASH indexes on HEAP |
322 | + } |
323 | + |
324 | + my ($name) = $key =~ m/(PRIMARY|`[^`]*`)/; |
325 | + my $unique = $key =~ m/PRIMARY|UNIQUE/ ? 1 : 0; |
326 | + my @cols; |
327 | + my @col_prefixes; |
328 | + foreach my $col_def ( $cols =~ m/`[^`]+`(?:\(\d+\))?/g ) { |
329 | + my ($name, $prefix) = $col_def =~ m/`([^`]+)`(?:\((\d+)\))?/; |
330 | + push @cols, $name; |
331 | + push @col_prefixes, $prefix; |
332 | + } |
333 | + $name =~ s/`//g; |
334 | + |
335 | + PTDEBUG && _d( $name, 'key cols:', join(', ', map { "`$_`" } @cols)); |
336 | + |
337 | + $keys->{$name} = { |
338 | + name => $name, |
339 | + type => $type, |
340 | + colnames => $cols, |
341 | + cols => \@cols, |
342 | + col_prefixes => \@col_prefixes, |
343 | + is_unique => $unique, |
344 | + is_nullable => scalar(grep { $is_nullable->{$_} } @cols), |
345 | + is_col => { map { $_ => 1 } @cols }, |
346 | + ddl => $key_ddl, |
347 | + }; |
348 | + |
349 | + if ( $engine =~ m/InnoDB/i && !$clustered_key ) { |
350 | + my $this_key = $keys->{$name}; |
351 | + if ( $this_key->{name} eq 'PRIMARY' ) { |
352 | + $clustered_key = 'PRIMARY'; |
353 | + } |
354 | + elsif ( $this_key->{is_unique} && !$this_key->{is_nullable} ) { |
355 | + $clustered_key = $this_key->{name}; |
356 | + } |
357 | + PTDEBUG && $clustered_key && _d('This key is the clustered key'); |
358 | + } |
359 | + } |
360 | + |
361 | + return $keys, $clustered_key; |
362 | +} |
363 | + |
364 | +sub get_fks { |
365 | + my ( $self, $ddl, $opts ) = @_; |
366 | + my $q = $self->{Quoter}; |
367 | + my $fks = {}; |
368 | + |
369 | + foreach my $fk ( |
370 | + $ddl =~ m/CONSTRAINT .* FOREIGN KEY .* REFERENCES [^\)]*\)/mg ) |
371 | + { |
372 | + my ( $name ) = $fk =~ m/CONSTRAINT `(.*?)`/; |
373 | + my ( $cols ) = $fk =~ m/FOREIGN KEY \(([^\)]+)\)/; |
374 | + my ( $parent, $parent_cols ) = $fk =~ m/REFERENCES (\S+) \(([^\)]+)\)/; |
375 | + |
376 | + my ($db, $tbl) = $q->split_unquote($parent, $opts->{database}); |
377 | + my %parent_tbl = (tbl => $tbl); |
378 | + $parent_tbl{db} = $db if $db; |
379 | + |
380 | + if ( $parent !~ m/\./ && $opts->{database} ) { |
381 | + $parent = $q->quote($opts->{database}) . ".$parent"; |
382 | + } |
383 | + |
384 | + $fks->{$name} = { |
385 | + name => $name, |
386 | + colnames => $cols, |
387 | + cols => [ map { s/[ `]+//g; $_; } split(',', $cols) ], |
388 | + parent_tbl => \%parent_tbl, |
389 | + parent_tblname => $parent, |
390 | + parent_cols => [ map { s/[ `]+//g; $_; } split(',', $parent_cols) ], |
391 | + parent_colnames=> $parent_cols, |
392 | + ddl => $fk, |
393 | + }; |
394 | + } |
395 | + |
396 | + return $fks; |
397 | +} |
398 | + |
399 | +sub remove_auto_increment { |
400 | + my ( $self, $ddl ) = @_; |
401 | + $ddl =~ s/(^\).*?) AUTO_INCREMENT=\d+\b/$1/m; |
402 | + return $ddl; |
403 | +} |
404 | + |
405 | +sub get_table_status { |
406 | + my ( $self, $dbh, $db, $like ) = @_; |
407 | + my $q = $self->{Quoter}; |
408 | + my $sql = "SHOW TABLE STATUS FROM " . $q->quote($db); |
409 | + my @params; |
410 | + if ( $like ) { |
411 | + $sql .= ' LIKE ?'; |
412 | + push @params, $like; |
413 | + } |
414 | + PTDEBUG && _d($sql, @params); |
415 | + my $sth = $dbh->prepare($sql); |
416 | + eval { $sth->execute(@params); }; |
417 | + if ($EVAL_ERROR) { |
418 | + PTDEBUG && _d($EVAL_ERROR); |
419 | + return; |
420 | + } |
421 | + my @tables = @{$sth->fetchall_arrayref({})}; |
422 | + @tables = map { |
423 | + my %tbl; # Make a copy with lowercased keys |
424 | + @tbl{ map { lc $_ } keys %$_ } = values %$_; |
425 | + $tbl{engine} ||= $tbl{type} || $tbl{comment}; |
426 | + delete $tbl{type}; |
427 | + \%tbl; |
428 | + } @tables; |
429 | + return @tables; |
430 | +} |
431 | + |
432 | +sub _d { |
433 | + my ($package, undef, $line) = caller 0; |
434 | + @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; } |
435 | + map { defined $_ ? $_ : 'undef' } |
436 | + @_; |
437 | + print STDERR "# $package:$line $PID ", join(' ', @_), "\n"; |
438 | +} |
439 | + |
440 | +1; |
441 | +} |
442 | +# ########################################################################### |
443 | +# End TableParser package |
444 | +# ########################################################################### |
445 | + |
446 | +# ########################################################################### |
447 | # Processlist package |
448 | # This package is a copy without comments from the original. The original |
449 | # with comments and its test file can be found in the Bazaar repository at, |
450 | @@ -2135,6 +2565,7 @@ |
451 | last_poll => 0, |
452 | active_cxn => {}, # keyed off ID |
453 | event_cache => [], |
454 | + _reasons_for_matching => {}, |
455 | }; |
456 | return bless $self, $class; |
457 | } |
458 | @@ -2345,7 +2776,9 @@ |
459 | PTDEBUG && _d("Query isn't running long enough"); |
460 | next QUERY; |
461 | } |
462 | - PTDEBUG && _d('Exceeds busy time'); |
463 | + my $reason = 'Exceeds busy time'; |
464 | + PTDEBUG && _d($reason); |
465 | + push @{$self->{_reasons_for_matching}->{$query} ||= []}, $reason; |
466 | $matched++; |
467 | } |
468 | |
469 | @@ -2355,7 +2788,9 @@ |
470 | PTDEBUG && _d("Query isn't idle long enough"); |
471 | next QUERY; |
472 | } |
473 | - PTDEBUG && _d('Exceeds idle time'); |
474 | + my $reason = 'Exceeds idle time'; |
475 | + PTDEBUG && _d($reason); |
476 | + push @{$self->{_reasons_for_matching}->{$query} ||= []}, $reason; |
477 | $matched++; |
478 | } |
479 | |
480 | @@ -2372,7 +2807,9 @@ |
481 | PTDEBUG && _d('Query does not match', $property, 'spec'); |
482 | next QUERY; |
483 | } |
484 | - PTDEBUG && _d('Query matches', $property, 'spec'); |
485 | + my $reason = 'Query matches ' . $property . ' spec'; |
486 | + PTDEBUG && _d($reason); |
487 | + push @{$self->{_reasons_for_matching}->{$query} ||= []}, $reason; |
488 | $matched++; |
489 | } |
490 | } |
491 | @@ -3295,6 +3732,125 @@ |
492 | # ########################################################################### |
493 | |
494 | # ########################################################################### |
495 | +# Quoter package |
496 | +# This package is a copy without comments from the original. The original |
497 | +# with comments and its test file can be found in the Bazaar repository at, |
498 | +# lib/Quoter.pm |
499 | +# t/lib/Quoter.t |
500 | +# See https://launchpad.net/percona-toolkit for more information. |
501 | +# ########################################################################### |
502 | +{ |
503 | +package Quoter; |
504 | + |
505 | +use strict; |
506 | +use warnings FATAL => 'all'; |
507 | +use English qw(-no_match_vars); |
508 | +use constant PTDEBUG => $ENV{PTDEBUG} || 0; |
509 | + |
510 | +sub new { |
511 | + my ( $class, %args ) = @_; |
512 | + return bless {}, $class; |
513 | +} |
514 | + |
515 | +sub quote { |
516 | + my ( $self, @vals ) = @_; |
517 | + foreach my $val ( @vals ) { |
518 | + $val =~ s/`/``/g; |
519 | + } |
520 | + return join('.', map { '`' . $_ . '`' } @vals); |
521 | +} |
522 | + |
523 | +sub quote_val { |
524 | + my ( $self, $val ) = @_; |
525 | + |
526 | + return 'NULL' unless defined $val; # undef = NULL |
527 | + return "''" if $val eq ''; # blank string = '' |
528 | + return $val if $val =~ m/^0x[0-9a-fA-F]+$/; # hex data |
529 | + |
530 | + $val =~ s/(['\\])/\\$1/g; |
531 | + return "'$val'"; |
532 | +} |
533 | + |
534 | +sub split_unquote { |
535 | + my ( $self, $db_tbl, $default_db ) = @_; |
536 | + $db_tbl =~ s/`//g; |
537 | + my ( $db, $tbl ) = split(/[.]/, $db_tbl); |
538 | + if ( !$tbl ) { |
539 | + $tbl = $db; |
540 | + $db = $default_db; |
541 | + } |
542 | + return ($db, $tbl); |
543 | +} |
544 | + |
545 | +sub literal_like { |
546 | + my ( $self, $like ) = @_; |
547 | + return unless $like; |
548 | + $like =~ s/([%_])/\\$1/g; |
549 | + return "'$like'"; |
550 | +} |
551 | + |
552 | +sub join_quote { |
553 | + my ( $self, $default_db, $db_tbl ) = @_; |
554 | + return unless $db_tbl; |
555 | + my ($db, $tbl) = split(/[.]/, $db_tbl); |
556 | + if ( !$tbl ) { |
557 | + $tbl = $db; |
558 | + $db = $default_db; |
559 | + } |
560 | + $db = "`$db`" if $db && $db !~ m/^`/; |
561 | + $tbl = "`$tbl`" if $tbl && $tbl !~ m/^`/; |
562 | + return $db ? "$db.$tbl" : $tbl; |
563 | +} |
564 | + |
565 | +sub serialize_list { |
566 | + my ( $self, @args ) = @_; |
567 | + return unless @args; |
568 | + |
569 | + return $args[0] if @args == 1 && !defined $args[0]; |
570 | + |
571 | + die "Cannot serialize multiple values with undef/NULL" |
572 | + if grep { !defined $_ } @args; |
573 | + |
574 | + return join ',', map { quotemeta } @args; |
575 | +} |
576 | + |
577 | +sub deserialize_list { |
578 | + my ( $self, $string ) = @_; |
579 | + return $string unless defined $string; |
580 | + my @escaped_parts = $string =~ / |
581 | + \G # Start of string, or end of previous match. |
582 | + ( # Each of these is an element in the original list. |
583 | + [^\\,]* # Anything not a backslash or a comma |
584 | + (?: # When we get here, we found one of the above. |
585 | + \\. # A backslash followed by something so we can continue |
586 | + [^\\,]* # Same as above. |
587 | + )* # Repeat zero of more times. |
588 | + ) |
589 | + , # Comma dividing elements |
590 | + /sxgc; |
591 | + |
592 | + push @escaped_parts, pos($string) ? substr( $string, pos($string) ) : $string; |
593 | + |
594 | + my @unescaped_parts = map { |
595 | + my $part = $_; |
596 | + |
597 | + my $char_class = utf8::is_utf8($part) # If it's a UTF-8 string, |
598 | + ? qr/(?=\p{ASCII})\W/ # We only care about non-word |
599 | + : qr/(?=\p{ASCII})\W|[\x{80}-\x{FF}]/; # Otherwise, |
600 | + $part =~ s/\\($char_class)/$1/g; |
601 | + $part; |
602 | + } @escaped_parts; |
603 | + |
604 | + return @unescaped_parts; |
605 | +} |
606 | + |
607 | +1; |
608 | +} |
609 | +# ########################################################################### |
610 | +# End Quoter package |
611 | +# ########################################################################### |
612 | + |
613 | +# ########################################################################### |
614 | # QueryRewriter package |
615 | # This package is a copy without comments from the original. The original |
616 | # with comments and its test file can be found in the Bazaar repository at, |
617 | @@ -4027,7 +4583,10 @@ |
618 | my $cxn; |
619 | my $dbh; # $cxn->dbh |
620 | my $get_proclist; # callback to SHOW PROCESSLIST |
621 | + my $proc_sth; |
622 | my $kill; # callback to KILL |
623 | + my $kill_sth; |
624 | + my $kill_sql = $o->get('kill-query') ? 'KILL QUERY ?' : 'KILL ?'; |
625 | my $files; |
626 | if ( $files = $o->get('test-matching') ) { |
627 | PTDEBUG && _d('Getting processlist from files:', @$files); |
628 | @@ -4079,7 +4638,7 @@ |
629 | # will need to be re-initialized. |
630 | my $retry = Retry->new(); |
631 | |
632 | - my $proc_sth = $dbh->prepare('SHOW FULL PROCESSLIST'); |
633 | + $proc_sth = $dbh->prepare('SHOW FULL PROCESSLIST'); |
634 | $get_proclist = sub { |
635 | return $retry->retry( |
636 | # Retry for an hour: 1,200 tries x 3 seconds = 3600s/1hr |
637 | @@ -4112,8 +4671,8 @@ |
638 | ); |
639 | }; |
640 | |
641 | - my $kill_sql = $o->get('kill-query') ? 'KILL QUERY ?' : 'KILL ?'; |
642 | - my $kill_sth = $dbh->prepare($kill_sql); |
643 | + |
644 | + $kill_sth = $dbh->prepare($kill_sql); |
645 | $kill = sub { |
646 | my ($id) = @_; |
647 | PTDEBUG && _d('Killing process', $id); |
648 | @@ -4146,6 +4705,93 @@ |
649 | }; |
650 | } |
651 | |
652 | + # Set up --log-dsn if specified. |
653 | + my ($log, $log_sth); |
654 | + my @processlist_columns = qw( |
655 | + Id User Host db Command |
656 | + Time State Info Time_ms |
657 | + ); |
658 | + if ( my $log_dsn = $o->get('log-dsn') ) { |
659 | + my $db = $log_dsn->{D}; |
660 | + my $table = $log_dsn->{t}; |
661 | + die "--log-dsn does not specify a database (D) " |
662 | + . "or a database-qualified table (t)" |
663 | + unless defined $table && defined $db; |
664 | + my $log_cxn = Cxn->new( |
665 | + dsn_string => ($dp->get_cxn_params($log_dsn))[0], |
666 | + NAME_lc => 0, |
667 | + DSNParser => $dp, |
668 | + OptionParser => $o, |
669 | + ); |
670 | + my $log_dbh = $log_cxn->connect(); |
671 | + my $log_table = Quoter->quote($db, $table); |
672 | + |
673 | + # Create the log-table table if it doesn't exist and --create-log-table |
674 | + # was passed in |
675 | + my $tp = TableParser->new( Quoter => "Quoter" ); |
676 | + if ( !$tp->check_table( dbh => $log_dbh, db => $db, tbl => $table ) ) { |
677 | + if ($o->get('create-log-table') ) { |
678 | + my $sql = $o->read_para_after( |
679 | + __FILE__, qr/MAGIC_create_log_table/); |
680 | + $sql =~ s/kill_log/IF NOT EXISTS $log_table/; |
681 | + PTDEBUG && _d($sql); |
682 | + $log_dbh->do($sql); |
683 | + } |
684 | + else { |
685 | + die "--log-dsn table does not exist. Please create it or specify " |
686 | + . "--create-log-table."; |
687 | + } |
688 | + } |
689 | + |
690 | + # All the columns of the table that we care about |
691 | + my @all_log_columns = ( qw( server_id timestamp reason kill_error ), |
692 | + @processlist_columns ); |
693 | + |
694 | + my $sql = 'SELECT @@SERVER_ID'; |
695 | + PTDEBUG && _d($sql); |
696 | + my ($server_id) = $dbh->selectrow_array($sql); |
697 | + |
698 | + $sql = "INSERT INTO $log_table (" |
699 | + . join(", ", @all_log_columns) |
700 | + . ") VALUES(" |
701 | + . join(", ", $server_id, ("?") x (@all_log_columns-1)) |
702 | + . ")"; |
703 | + PTDEBUG && _d($sql); |
704 | + $log_sth = $log_dbh->prepare($sql); |
705 | + |
706 | + my $retry = Retry->new(); |
707 | + |
708 | + $log = sub { |
709 | + my (@params) = @_; |
710 | + PTDEBUG && _d('Logging values:', @params); |
711 | + return $retry->retry( |
712 | + tries => 20, |
713 | + wait => sub { sleep 3; }, |
714 | + try => sub { return $log_sth->execute(@params); }, |
715 | + fail => sub { |
716 | + my (%args) = @_; |
717 | + my $error = $args{error}; |
718 | + # The 1st pattern means that MySQL itself died or was stopped. |
719 | + # The 2nd pattern means that our cxn was killed (KILL <id>). |
720 | + if ( $error =~ m/MySQL server has gone away/ |
721 | + || $error =~ m/Lost connection to MySQL server/ ) { |
722 | + eval { |
723 | + $log_dbh = $log_cxn->connect(); |
724 | + $log_sth = $log_dbh->prepare( $sql ); |
725 | + msg('Reconnected to ' . $cxn->name()); |
726 | + }; |
727 | + return 1 unless $EVAL_ERROR; # try again |
728 | + } |
729 | + return 0; # call final_fail |
730 | + }, |
731 | + final_fail => sub { |
732 | + my (%args) = @_; |
733 | + die $args{error}; |
734 | + }, |
735 | + ); |
736 | + }; |
737 | + } |
738 | + |
739 | # ######################################################################## |
740 | # Daemonize only after (potentially) asking for passwords for --ask-pass. |
741 | # ######################################################################## |
742 | @@ -4349,7 +4995,17 @@ |
743 | . " seconds before kill"); |
744 | sleep $o->get('wait-before-kill'); |
745 | } |
746 | + local $@; |
747 | eval { $kill->($query->{Id}) }; |
748 | + if ( $log ) { |
749 | + log_to_table( |
750 | + log => $log, |
751 | + query => $query, |
752 | + proclist => $pl, |
753 | + columns => \@processlist_columns, |
754 | + eval_error => $EVAL_ERROR, |
755 | + ); |
756 | + } |
757 | if ( $EVAL_ERROR ) { |
758 | msg("Error killing $query->{Id}: $EVAL_ERROR"); |
759 | } |
760 | @@ -4417,6 +5073,21 @@ |
761 | return; |
762 | } |
763 | |
764 | +sub log_to_table { |
765 | + my (%args) = @_; |
766 | + my ($log, $query, $pl, $processlist_columns) |
767 | + = @args{qw( log query proclist columns )}; |
768 | + |
769 | + my $ts = Transformers::ts(localtime); |
770 | + my $reasons = join "\n", map { |
771 | + defined($_) ? $_ : "Unkown reason" |
772 | + } @{ $pl->{_reasons_for_matching}->{$query} }; |
773 | + $log->( |
774 | + $ts, $reasons, $args{eval_error}, |
775 | + @{$query}{@$processlist_columns} |
776 | + ); |
777 | +} |
778 | + |
779 | sub group_queries { |
780 | my ( %args ) = @_; |
781 | my ($proclist, $group_by, $qr) = @args{qw(proclist group_by QueryRewriter)}; |
782 | @@ -4642,6 +5313,13 @@ |
783 | |
784 | The database to use for the connection. |
785 | |
786 | +=item --create-log-table |
787 | + |
788 | +Create the L<"--log-dsn"> table if it does not exist. |
789 | + |
790 | +This option causes the table specified by L<"--log-dsn"> to be created with the |
791 | +default structure shown in the documentation for that option. |
792 | + |
793 | =item --daemonize |
794 | |
795 | Fork to the background and detach from the shell. POSIX operating systems |
796 | @@ -4746,6 +5424,37 @@ |
797 | |
798 | Print all output to this file when daemonized. |
799 | |
800 | +=item --log-dsn |
801 | + |
802 | +type: DSN |
803 | + |
804 | +Store each query killed in this DSN. |
805 | + |
806 | +The argument specifies a table to store all killed queries. The DSN |
807 | +passed in must have the databse (D) and table (t) options. The |
808 | +table must have at least the following columns. You can add more columns for |
809 | +your own special purposes, but they won't be used by pt-kill. The |
810 | +following CREATE TABLE definition is also used for L<"--create-log-table">. |
811 | +MAGIC_create_log_table: |
812 | + |
813 | + CREATE TABLE kill_log ( |
814 | + kill_id int(10) unsigned NOT NULL AUTO_INCREMENT, |
815 | + server_id bigint(4) NOT NULL DEFAULT '0', |
816 | + timestamp DATETIME, |
817 | + reason TEXT, |
818 | + kill_error TEXT, |
819 | + Id bigint(4) NOT NULL DEFAULT '0', |
820 | + User varchar(16) NOT NULL DEFAULT '', |
821 | + Host varchar(64) NOT NULL DEFAULT '', |
822 | + db varchar(64) DEFAULT NULL, |
823 | + Command varchar(16) NOT NULL DEFAULT '', |
824 | + Time int(7) NOT NULL DEFAULT '0', |
825 | + State varchar(64) DEFAULT NULL, |
826 | + Info longtext, |
827 | + Time_ms bigint(21) DEFAULT '0', # NOTE, TODO: currently not used |
828 | + PRIMARY KEY (kill_id) |
829 | + ) DEFAULT CHARSET=utf8 |
830 | + |
831 | =item --password |
832 | |
833 | short form: -p; type: string |
834 | @@ -5251,6 +5960,10 @@ |
835 | |
836 | User for login if not current user. |
837 | |
838 | +=item * t |
839 | + |
840 | +Table to log actions in, if passed through --log-dsn. |
841 | + |
842 | =back |
843 | |
844 | =head1 ENVIRONMENT |
845 | |
846 | === modified file 'lib/Processlist.pm' |
847 | --- lib/Processlist.pm 2012-05-28 02:28:35 +0000 |
848 | +++ lib/Processlist.pm 2012-07-19 16:42:35 +0000 |
849 | @@ -75,6 +75,7 @@ |
850 | last_poll => 0, |
851 | active_cxn => {}, # keyed off ID |
852 | event_cache => [], |
853 | + _reasons_for_matching => {}, |
854 | }; |
855 | return bless $self, $class; |
856 | } |
857 | @@ -475,7 +476,15 @@ |
858 | PTDEBUG && _d("Query isn't running long enough"); |
859 | next QUERY; |
860 | } |
861 | - PTDEBUG && _d('Exceeds busy time'); |
862 | + my $reason = 'Exceeds busy time'; |
863 | + PTDEBUG && _d($reason); |
864 | + # Saving the reasons for each query in the objct is a bit nasty, |
865 | + # but the alternatives are worse: |
866 | + # - Saving internal data in the query |
867 | + # - Instead of using the stringified hashref as a key, using |
868 | + # a checksum of the hashes' contents. Which could occasionally |
869 | + # fail miserably due to timing-related issues. |
870 | + push @{$self->{_reasons_for_matching}->{$query} ||= []}, $reason; |
871 | $matched++; |
872 | } |
873 | |
874 | @@ -486,7 +495,9 @@ |
875 | PTDEBUG && _d("Query isn't idle long enough"); |
876 | next QUERY; |
877 | } |
878 | - PTDEBUG && _d('Exceeds idle time'); |
879 | + my $reason = 'Exceeds idle time'; |
880 | + PTDEBUG && _d($reason); |
881 | + push @{$self->{_reasons_for_matching}->{$query} ||= []}, $reason; |
882 | $matched++; |
883 | } |
884 | |
885 | @@ -507,7 +518,9 @@ |
886 | PTDEBUG && _d('Query does not match', $property, 'spec'); |
887 | next QUERY; |
888 | } |
889 | - PTDEBUG && _d('Query matches', $property, 'spec'); |
890 | + my $reason = 'Query matches ' . $property . ' spec'; |
891 | + PTDEBUG && _d($reason); |
892 | + push @{$self->{_reasons_for_matching}->{$query} ||= []}, $reason; |
893 | $matched++; |
894 | } |
895 | } |
896 | |
897 | === modified file 't/lib/Processlist.t' |
898 | --- t/lib/Processlist.t 2012-05-30 14:36:44 +0000 |
899 | +++ t/lib/Processlist.t 2012-07-19 16:42:35 +0000 |
900 | @@ -9,7 +9,7 @@ |
901 | use strict; |
902 | use warnings FATAL => 'all'; |
903 | use English qw(-no_match_vars); |
904 | -use Test::More tests => 34; |
905 | +use Test::More tests => 35; |
906 | |
907 | use Processlist; |
908 | use PerconaTest; |
909 | @@ -600,6 +600,17 @@ |
910 | }, |
911 | ); |
912 | |
913 | +my $matching_query = |
914 | + { 'Time' => '91', |
915 | + 'Command' => 'Query', |
916 | + 'db' => undef, |
917 | + 'Id' => '43', |
918 | + 'Info' => 'select * from foo', |
919 | + 'User' => 'msandbox', |
920 | + 'State' => 'executing', |
921 | + 'Host' => 'localhost' |
922 | + }; |
923 | + |
924 | my @queries = $pl->find( |
925 | [ { 'Time' => '488', |
926 | 'Command' => 'Connect', |
927 | @@ -675,33 +686,24 @@ |
928 | 'State' => 'Locked', |
929 | 'Host' => 'localhost' |
930 | }, |
931 | - { 'Time' => '91', |
932 | - 'Command' => 'Query', |
933 | - 'db' => undef, |
934 | - 'Id' => '43', |
935 | - 'Info' => 'select * from foo', |
936 | - 'User' => 'msandbox', |
937 | - 'State' => 'executing', |
938 | - 'Host' => 'localhost' |
939 | - }, |
940 | + $matching_query, |
941 | ], |
942 | %find_spec, |
943 | ); |
944 | |
945 | -my $expected = [ |
946 | - { 'Time' => '91', |
947 | - 'Command' => 'Query', |
948 | - 'db' => undef, |
949 | - 'Id' => '43', |
950 | - 'Info' => 'select * from foo', |
951 | - 'User' => 'msandbox', |
952 | - 'State' => 'executing', |
953 | - 'Host' => 'localhost' |
954 | - }, |
955 | - ]; |
956 | +my $expected = [ $matching_query ]; |
957 | |
958 | is_deeply(\@queries, $expected, 'Basic find()'); |
959 | |
960 | +{ |
961 | + # Internal, fragile test! |
962 | + is_deeply( |
963 | + $pl->{_reasons_for_matching}->{$matching_query}, |
964 | + [ 'Exceeds busy time', 'Query matches Command spec', 'Query matches Info spec', ], |
965 | + "_reasons_for_matching works" |
966 | + ); |
967 | +} |
968 | + |
969 | %find_spec = ( |
970 | busy_time => 1, |
971 | ignore => { |
972 | |
973 | === modified file 't/pt-kill/kill.t' |
974 | --- t/pt-kill/kill.t 2012-07-12 22:49:15 +0000 |
975 | +++ t/pt-kill/kill.t 2012-07-19 16:42:35 +0000 |
976 | @@ -29,7 +29,7 @@ |
977 | plan skip_all => 'Cannot connect to sandbox master'; |
978 | } |
979 | else { |
980 | - plan tests => 8; |
981 | + plan tests => 21; |
982 | } |
983 | |
984 | my $output; |
985 | @@ -56,8 +56,11 @@ |
986 | |
987 | $output = output( |
988 | sub { pt_kill::main('-F', $cnf, qw(--kill --print --run-time 1 --interval 1), |
989 | - '--match-info', 'select sleep\(4\)') }, |
990 | + "--match-info", 'select sleep\(4\)', |
991 | + ) |
992 | + }, |
993 | ); |
994 | + |
995 | like( |
996 | $output, |
997 | qr/KILL $pid /, |
998 | @@ -117,6 +120,157 @@ |
999 | ); |
1000 | |
1001 | # ############################################################################# |
1002 | +# Test that --log-dsn |
1003 | +# ############################################################################# |
1004 | + |
1005 | +$dbh->do("DROP DATABASE IF EXISTS `kill_test`"); |
1006 | +$dbh->do("CREATE DATABASE `kill_test`"); |
1007 | + |
1008 | +my $sql = OptionParser->read_para_after( |
1009 | + "$trunk/bin/pt-kill", qr/MAGIC_create_log_table/); |
1010 | +$sql =~ s/kill_log/`kill_test`.`log_table`/; |
1011 | + |
1012 | +$dbh->do($sql); |
1013 | + |
1014 | +{ |
1015 | + system("/tmp/12345/use -h127.1 -P12345 -umsandbox -pmsandbox -e 'select sleep(4)' >/dev/null&"); |
1016 | + sleep 0.5; |
1017 | + local $EVAL_ERROR; |
1018 | + eval { |
1019 | + pt_kill::main('-F', $cnf, qw(--kill --run-time 1 --interval 1), |
1020 | + "--match-info", 'select sleep\(4\)', |
1021 | + "--log-dsn", q!h=127.1,P=12345,u=msandbox,p=msandbox,D=kill_test,t=log_table!, |
1022 | + ) |
1023 | + }; |
1024 | + is( |
1025 | + $EVAL_ERROR, |
1026 | + '', |
1027 | + "--log-dsn works if the table exists and --create-log-table wasn't passed in." |
1028 | + ) or diag $EVAL_ERROR; |
1029 | + |
1030 | + local $EVAL_ERROR; |
1031 | + my $results = eval { $dbh->selectall_arrayref("SELECT * FROM `kill_test`.`log_table`", { Slice => {} } ) }; |
1032 | + is( |
1033 | + $EVAL_ERROR, |
1034 | + '', |
1035 | + "...and we can query the table" |
1036 | + ) or diag $EVAL_ERROR; |
1037 | + |
1038 | + is @{$results}, 1, "...which contains one entry"; |
1039 | + use Data::Dumper; |
1040 | + my $reason = $dbh->selectrow_array("SELECT reason FROM `kill_test`.`log_table` WHERE kill_id=1"); |
1041 | + is $reason, |
1042 | + 'Query matches Info spec', |
1043 | + 'reason gets set to something sensible'; |
1044 | + |
1045 | + TODO: { |
1046 | + local $::TODO = "Time_ms currently isn't reported"; |
1047 | + my $time_ms = $dbh->selectrow_array("SELECT Time_ms FROM `kill_test`.`log_table` WHERE kill_id=1"); |
1048 | + ok $time_ms; |
1049 | + } |
1050 | + |
1051 | + my $result = shift @$results; |
1052 | + my $against = { |
1053 | + user => 'msandbox', |
1054 | + host => 'localhost', |
1055 | + db => undef, |
1056 | + command => 'Query', |
1057 | + state => ($sandbox_version lt '5.1' ? "executing" : "User sleep"), |
1058 | + info => 'select sleep(4)', |
1059 | + }; |
1060 | + my %trimmed_result; |
1061 | + @trimmed_result{ keys %$against } = @{$result}{ keys %$against }; |
1062 | + $trimmed_result{host} =~ s/localhost:[0-9]+/localhost/; |
1063 | + is_deeply( |
1064 | + \%trimmed_result, |
1065 | + $against, |
1066 | + "...and was populated as expected", |
1067 | + ) or diag(Dumper($result)); |
1068 | + |
1069 | + system("/tmp/12345/use -h127.1 -P12345 -umsandbox -pmsandbox -e 'select sleep(4)' >/dev/null&"); |
1070 | + sleep 0.5; |
1071 | + local $EVAL_ERROR; |
1072 | + eval { |
1073 | + pt_kill::main('-F', $cnf, qw(--kill --run-time 1 --interval 1 --create-log-table), |
1074 | + "--match-info", 'select sleep\(4\)', |
1075 | + "--log-dsn", q!h=127.1,P=12345,u=msandbox,p=msandbox,D=kill_test,t=log_table!, |
1076 | + ) |
1077 | + }; |
1078 | + is( |
1079 | + $EVAL_ERROR, |
1080 | + '', |
1081 | + "--log-dsn works if the table exists and --create-log-table was passed in." |
1082 | + ); |
1083 | +} |
1084 | + |
1085 | +{ |
1086 | + $dbh->do("DROP TABLE `kill_test`.`log_table`"); |
1087 | + |
1088 | + system("/tmp/12345/use -h127.1 -P12345 -umsandbox -pmsandbox -e 'select sleep(4)' >/dev/null&"); |
1089 | + sleep 0.5; |
1090 | + local $EVAL_ERROR; |
1091 | + eval { |
1092 | + pt_kill::main('-F', $cnf, qw(--kill --run-time 1 --interval 1 --create-log-table), |
1093 | + "--match-info", 'select sleep\(4\)', |
1094 | + "--log-dsn", q!h=127.1,P=12345,u=msandbox,p=msandbox,D=kill_test,t=log_table!, |
1095 | + ) |
1096 | + }; |
1097 | + is( |
1098 | + $EVAL_ERROR, |
1099 | + '', |
1100 | + "--log-dsn works if the table doesn't exists and --create-log-table was passed in." |
1101 | + ); |
1102 | +} |
1103 | + |
1104 | +{ |
1105 | + $dbh->do("DROP TABLE `kill_test`.`log_table`"); |
1106 | + |
1107 | + local $EVAL_ERROR; |
1108 | + eval { |
1109 | + pt_kill::main('-F', $cnf, qw(--kill --run-time 1 --interval 1), |
1110 | + "--match-info", 'select sleep\(4\)', |
1111 | + "--log-dsn", q!h=127.1,P=12345,u=msandbox,p=msandbox,D=kill_test,t=log_table!, |
1112 | + ) |
1113 | + }; |
1114 | + like $EVAL_ERROR, |
1115 | + qr/\Q--log-dsn table does not exist. Please create it or specify\E/, |
1116 | + "By default, --log-dsn doesn't autogenerate a table"; |
1117 | +} |
1118 | + |
1119 | +for my $dsn ( |
1120 | + q!h=127.1,P=12345,u=msandbox,p=msandbox,t=log_table!, |
1121 | + q!h=127.1,P=12345,u=msandbox,p=msandbox,D=kill_test!, |
1122 | + q!h=127.1,P=12345,u=msandbox,p=msandbox!, |
1123 | +) { |
1124 | + local $EVAL_ERROR; |
1125 | + eval { |
1126 | + pt_kill::main('-F', $cnf, qw(--kill --run-time 1 --interval 1), |
1127 | + "--match-info", 'select sleep\(4\)', |
1128 | + "--log-dsn", $dsn, |
1129 | + ) |
1130 | + }; |
1131 | + like $EVAL_ERROR, |
1132 | + qr/\Q--log-dsn does not specify a database (D) or a database-qualified table (t)\E/, |
1133 | + "--log-dsn croaks if t= or D= are absent"; |
1134 | +} |
1135 | + |
1136 | +# Run it twice |
1137 | +for (1,2) { |
1138 | + system("/tmp/12345/use -h127.1 -P12345 -umsandbox -pmsandbox -e 'select sleep(4)' >/dev/null&"); |
1139 | + sleep 0.5; |
1140 | + pt_kill::main('-F', $cnf, qw(--kill --run-time 1 --interval 1 --create-log-table), |
1141 | + "--match-info", 'select sleep\(4\)', |
1142 | + "--log-dsn", q!h=127.1,P=12345,u=msandbox,p=msandbox,D=kill_test,t=log_table!, |
1143 | + ); |
1144 | +} |
1145 | + |
1146 | +my $results = $dbh->selectall_arrayref("SELECT * FROM `kill_test`.`log_table`"); |
1147 | + |
1148 | +is @{$results}, 2, "Different --log-dsn runs reuse the same table."; |
1149 | + |
1150 | +$dbh->do("DROP DATABASE kill_test"); |
1151 | + |
1152 | +# ############################################################################# |
1153 | # Done. |
1154 | # ############################################################################# |
1155 | $sb->wipe_clean($dbh); |
Changes for
bug 941469
branch https:/ /code.launchpad .net/~percona- toolkit- dev/percona- toolkit/ pt-kill- reconnect- bug-941469
merge https:/ /code.launchpad .net/~percona- toolkit- dev/percona- toolkit/ pt-kill- reconnect- bug-941469/ +merge/ 114748
conflict with the --log-dsn code. The code needs to be updated like those ^ changes, i.e. use Retry to try doing the INSERT, if that fails, reconnect and try again. I would say: tries=20, wait 3s (i.e. 1 minute). MySQL shouldn't stay away for long if the code just observed it, and if an INSERT fails that many times, it's no big deal, but it's worth making a good effort.
Also, please standardize the tests:
* Use English and indention,
is(
$EVAL_ERROR,
"",
"foo"
);
* Be more explicit, e.g.:
my $result = shift @$results; [0-9]+/ localhost/ ;
$result->[7] =~ s/localhost:
is_deeply(
[ @{$result}[6..9, 11, 12] ],
That's cryptic. Rather:
my $row = $dbh->selectrow _hashref( $sql);
is_deeply(
$row,
{
Id => 123,
user => 'foo',
...
},
"..."
) or diag(Dumper($row));