Merge lp:~percona-toolkit-dev/percona-toolkit/pt-kill-log-dsn into lp:percona-toolkit/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
Reviewer Review Type Date Requested Status
Daniel Nichter Approve
Review via email: mp+114910@code.launchpad.net
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 :

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;
   $result->[7] =~ s/localhost:[0-9]+/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));

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
=== modified file 'bin/pt-kill'
--- bin/pt-kill 2012-07-15 02:58:17 +0000
+++ bin/pt-kill 2012-07-19 16:42:35 +0000
@@ -1280,7 +1280,7 @@
1280 }1280 }
12811281
1282 foreach my $key ( keys %given_props ) {1282 foreach my $key ( keys %given_props ) {
1283 die "Unknown DSN option '$key' in '$dsn'. For more details, "1283 die "DSN option '$key' in '$dsn'. For more details, "
1284 . "please use the --help option, or try 'perldoc $PROGRAM_NAME' "1284 . "please use the --help option, or try 'perldoc $PROGRAM_NAME' "
1285 . "for complete documentation."1285 . "for complete documentation."
1286 unless exists $opts->{$key};1286 unless exists $opts->{$key};
@@ -2087,6 +2087,436 @@
2087# ###########################################################################2087# ###########################################################################
20882088
2089# ###########################################################################2089# ###########################################################################
2090# TableParser package
2091# This package is a copy without comments from the original. The original
2092# with comments and its test file can be found in the Bazaar repository at,
2093# lib/TableParser.pm
2094# t/lib/TableParser.t
2095# See https://launchpad.net/percona-toolkit for more information.
2096# ###########################################################################
2097{
2098package TableParser;
2099
2100use strict;
2101use warnings FATAL => 'all';
2102use English qw(-no_match_vars);
2103use constant PTDEBUG => $ENV{PTDEBUG} || 0;
2104
2105use Data::Dumper;
2106$Data::Dumper::Indent = 1;
2107$Data::Dumper::Sortkeys = 1;
2108$Data::Dumper::Quotekeys = 0;
2109
2110sub new {
2111 my ( $class, %args ) = @_;
2112 my @required_args = qw(Quoter);
2113 foreach my $arg ( @required_args ) {
2114 die "I need a $arg argument" unless $args{$arg};
2115 }
2116 my $self = { %args };
2117 return bless $self, $class;
2118}
2119
2120sub get_create_table {
2121 my ( $self, $dbh, $db, $tbl ) = @_;
2122 die "I need a dbh parameter" unless $dbh;
2123 die "I need a db parameter" unless $db;
2124 die "I need a tbl parameter" unless $tbl;
2125 my $q = $self->{Quoter};
2126
2127 my $new_sql_mode
2128 = '/*!40101 SET @OLD_SQL_MODE := @@SQL_MODE, '
2129 . q{@@SQL_MODE := REPLACE(REPLACE(@@SQL_MODE, 'ANSI_QUOTES', ''), ',,', ','), }
2130 . '@OLD_QUOTE := @@SQL_QUOTE_SHOW_CREATE, '
2131 . '@@SQL_QUOTE_SHOW_CREATE := 1 */';
2132
2133 my $old_sql_mode = '/*!40101 SET @@SQL_MODE := @OLD_SQL_MODE, '
2134 . '@@SQL_QUOTE_SHOW_CREATE := @OLD_QUOTE */';
2135
2136 PTDEBUG && _d($new_sql_mode);
2137 eval { $dbh->do($new_sql_mode); };
2138 PTDEBUG && $EVAL_ERROR && _d($EVAL_ERROR);
2139
2140 my $use_sql = 'USE ' . $q->quote($db);
2141 PTDEBUG && _d($dbh, $use_sql);
2142 $dbh->do($use_sql);
2143
2144 my $show_sql = "SHOW CREATE TABLE " . $q->quote($db, $tbl);
2145 PTDEBUG && _d($show_sql);
2146 my $href;
2147 eval { $href = $dbh->selectrow_hashref($show_sql); };
2148 if ( $EVAL_ERROR ) {
2149 PTDEBUG && _d($EVAL_ERROR);
2150
2151 PTDEBUG && _d($old_sql_mode);
2152 $dbh->do($old_sql_mode);
2153
2154 return;
2155 }
2156
2157 PTDEBUG && _d($old_sql_mode);
2158 $dbh->do($old_sql_mode);
2159
2160 my ($key) = grep { m/create (?:table|view)/i } keys %$href;
2161 if ( !$key ) {
2162 die "Error: no 'Create Table' or 'Create View' in result set from "
2163 . "$show_sql: " . Dumper($href);
2164 }
2165
2166 return $href->{$key};
2167}
2168
2169sub parse {
2170 my ( $self, $ddl, $opts ) = @_;
2171 return unless $ddl;
2172
2173 if ( $ddl !~ m/CREATE (?:TEMPORARY )?TABLE `/ ) {
2174 die "Cannot parse table definition; is ANSI quoting "
2175 . "enabled or SQL_QUOTE_SHOW_CREATE disabled?";
2176 }
2177
2178 my ($name) = $ddl =~ m/CREATE (?:TEMPORARY )?TABLE\s+(`.+?`)/;
2179 (undef, $name) = $self->{Quoter}->split_unquote($name) if $name;
2180
2181 $ddl =~ s/(`[^`]+`)/\L$1/g;
2182
2183 my $engine = $self->get_engine($ddl);
2184
2185 my @defs = $ddl =~ m/^(\s+`.*?),?$/gm;
2186 my @cols = map { $_ =~ m/`([^`]+)`/ } @defs;
2187 PTDEBUG && _d('Table cols:', join(', ', map { "`$_`" } @cols));
2188
2189 my %def_for;
2190 @def_for{@cols} = @defs;
2191
2192 my (@nums, @null);
2193 my (%type_for, %is_nullable, %is_numeric, %is_autoinc);
2194 foreach my $col ( @cols ) {
2195 my $def = $def_for{$col};
2196 my ( $type ) = $def =~ m/`[^`]+`\s([a-z]+)/;
2197 die "Can't determine column type for $def" unless $type;
2198 $type_for{$col} = $type;
2199 if ( $type =~ m/(?:(?:tiny|big|medium|small)?int|float|double|decimal|year)/ ) {
2200 push @nums, $col;
2201 $is_numeric{$col} = 1;
2202 }
2203 if ( $def !~ m/NOT NULL/ ) {
2204 push @null, $col;
2205 $is_nullable{$col} = 1;
2206 }
2207 $is_autoinc{$col} = $def =~ m/AUTO_INCREMENT/i ? 1 : 0;
2208 }
2209
2210 my ($keys, $clustered_key) = $self->get_keys($ddl, $opts, \%is_nullable);
2211
2212 my ($charset) = $ddl =~ m/DEFAULT CHARSET=(\w+)/;
2213
2214 return {
2215 name => $name,
2216 cols => \@cols,
2217 col_posn => { map { $cols[$_] => $_ } 0..$#cols },
2218 is_col => { map { $_ => 1 } @cols },
2219 null_cols => \@null,
2220 is_nullable => \%is_nullable,
2221 is_autoinc => \%is_autoinc,
2222 clustered_key => $clustered_key,
2223 keys => $keys,
2224 defs => \%def_for,
2225 numeric_cols => \@nums,
2226 is_numeric => \%is_numeric,
2227 engine => $engine,
2228 type_for => \%type_for,
2229 charset => $charset,
2230 };
2231}
2232
2233sub sort_indexes {
2234 my ( $self, $tbl ) = @_;
2235
2236 my @indexes
2237 = sort {
2238 (($a ne 'PRIMARY') <=> ($b ne 'PRIMARY'))
2239 || ( !$tbl->{keys}->{$a}->{is_unique} <=> !$tbl->{keys}->{$b}->{is_unique} )
2240 || ( $tbl->{keys}->{$a}->{is_nullable} <=> $tbl->{keys}->{$b}->{is_nullable} )
2241 || ( scalar(@{$tbl->{keys}->{$a}->{cols}}) <=> scalar(@{$tbl->{keys}->{$b}->{cols}}) )
2242 }
2243 grep {
2244 $tbl->{keys}->{$_}->{type} eq 'BTREE'
2245 }
2246 sort keys %{$tbl->{keys}};
2247
2248 PTDEBUG && _d('Indexes sorted best-first:', join(', ', @indexes));
2249 return @indexes;
2250}
2251
2252sub find_best_index {
2253 my ( $self, $tbl, $index ) = @_;
2254 my $best;
2255 if ( $index ) {
2256 ($best) = grep { uc $_ eq uc $index } keys %{$tbl->{keys}};
2257 }
2258 if ( !$best ) {
2259 if ( $index ) {
2260 die "Index '$index' does not exist in table";
2261 }
2262 else {
2263 ($best) = $self->sort_indexes($tbl);
2264 }
2265 }
2266 PTDEBUG && _d('Best index found is', $best);
2267 return $best;
2268}
2269
2270sub find_possible_keys {
2271 my ( $self, $dbh, $database, $table, $quoter, $where ) = @_;
2272 return () unless $where;
2273 my $sql = 'EXPLAIN SELECT * FROM ' . $quoter->quote($database, $table)
2274 . ' WHERE ' . $where;
2275 PTDEBUG && _d($sql);
2276 my $expl = $dbh->selectrow_hashref($sql);
2277 $expl = { map { lc($_) => $expl->{$_} } keys %$expl };
2278 if ( $expl->{possible_keys} ) {
2279 PTDEBUG && _d('possible_keys =', $expl->{possible_keys});
2280 my @candidates = split(',', $expl->{possible_keys});
2281 my %possible = map { $_ => 1 } @candidates;
2282 if ( $expl->{key} ) {
2283 PTDEBUG && _d('MySQL chose', $expl->{key});
2284 unshift @candidates, grep { $possible{$_} } split(',', $expl->{key});
2285 PTDEBUG && _d('Before deduping:', join(', ', @candidates));
2286 my %seen;
2287 @candidates = grep { !$seen{$_}++ } @candidates;
2288 }
2289 PTDEBUG && _d('Final list:', join(', ', @candidates));
2290 return @candidates;
2291 }
2292 else {
2293 PTDEBUG && _d('No keys in possible_keys');
2294 return ();
2295 }
2296}
2297
2298sub check_table {
2299 my ( $self, %args ) = @_;
2300 my @required_args = qw(dbh db tbl);
2301 foreach my $arg ( @required_args ) {
2302 die "I need a $arg argument" unless $args{$arg};
2303 }
2304 my ($dbh, $db, $tbl) = @args{@required_args};
2305 my $q = $self->{Quoter};
2306 my $db_tbl = $q->quote($db, $tbl);
2307 PTDEBUG && _d('Checking', $db_tbl);
2308
2309 my $sql = "SHOW TABLES FROM " . $q->quote($db)
2310 . ' LIKE ' . $q->literal_like($tbl);
2311 PTDEBUG && _d($sql);
2312 my $row;
2313 eval {
2314 $row = $dbh->selectrow_arrayref($sql);
2315 };
2316 if ( $EVAL_ERROR ) {
2317 PTDEBUG && _d($EVAL_ERROR);
2318 return 0;
2319 }
2320 if ( !$row->[0] || $row->[0] ne $tbl ) {
2321 PTDEBUG && _d('Table does not exist');
2322 return 0;
2323 }
2324
2325 PTDEBUG && _d('Table exists; no privs to check');
2326 return 1 unless $args{all_privs};
2327
2328 $sql = "SHOW FULL COLUMNS FROM $db_tbl";
2329 PTDEBUG && _d($sql);
2330 eval {
2331 $row = $dbh->selectrow_hashref($sql);
2332 };
2333 if ( $EVAL_ERROR ) {
2334 PTDEBUG && _d($EVAL_ERROR);
2335 return 0;
2336 }
2337 if ( !scalar keys %$row ) {
2338 PTDEBUG && _d('Table has no columns:', Dumper($row));
2339 return 0;
2340 }
2341 my $privs = $row->{privileges} || $row->{Privileges};
2342
2343 $sql = "DELETE FROM $db_tbl LIMIT 0";
2344 PTDEBUG && _d($sql);
2345 eval {
2346 $dbh->do($sql);
2347 };
2348 my $can_delete = $EVAL_ERROR ? 0 : 1;
2349
2350 PTDEBUG && _d('User privs on', $db_tbl, ':', $privs,
2351 ($can_delete ? 'delete' : ''));
2352
2353 if ( !($privs =~ m/select/ && $privs =~ m/insert/ && $privs =~ m/update/
2354 && $can_delete) ) {
2355 PTDEBUG && _d('User does not have all privs');
2356 return 0;
2357 }
2358
2359 PTDEBUG && _d('User has all privs');
2360 return 1;
2361}
2362
2363sub get_engine {
2364 my ( $self, $ddl, $opts ) = @_;
2365 my ( $engine ) = $ddl =~ m/\).*?(?:ENGINE|TYPE)=(\w+)/;
2366 PTDEBUG && _d('Storage engine:', $engine);
2367 return $engine || undef;
2368}
2369
2370sub get_keys {
2371 my ( $self, $ddl, $opts, $is_nullable ) = @_;
2372 my $engine = $self->get_engine($ddl);
2373 my $keys = {};
2374 my $clustered_key = undef;
2375
2376 KEY:
2377 foreach my $key ( $ddl =~ m/^ ((?:[A-Z]+ )?KEY .*)$/gm ) {
2378
2379 next KEY if $key =~ m/FOREIGN/;
2380
2381 my $key_ddl = $key;
2382 PTDEBUG && _d('Parsed key:', $key_ddl);
2383
2384 if ( $engine !~ m/MEMORY|HEAP/ ) {
2385 $key =~ s/USING HASH/USING BTREE/;
2386 }
2387
2388 my ( $type, $cols ) = $key =~ m/(?:USING (\w+))? \((.+)\)/;
2389 my ( $special ) = $key =~ m/(FULLTEXT|SPATIAL)/;
2390 $type = $type || $special || 'BTREE';
2391 if ( $opts->{mysql_version} && $opts->{mysql_version} lt '004001000'
2392 && $engine =~ m/HEAP|MEMORY/i )
2393 {
2394 $type = 'HASH'; # MySQL pre-4.1 supports only HASH indexes on HEAP
2395 }
2396
2397 my ($name) = $key =~ m/(PRIMARY|`[^`]*`)/;
2398 my $unique = $key =~ m/PRIMARY|UNIQUE/ ? 1 : 0;
2399 my @cols;
2400 my @col_prefixes;
2401 foreach my $col_def ( $cols =~ m/`[^`]+`(?:\(\d+\))?/g ) {
2402 my ($name, $prefix) = $col_def =~ m/`([^`]+)`(?:\((\d+)\))?/;
2403 push @cols, $name;
2404 push @col_prefixes, $prefix;
2405 }
2406 $name =~ s/`//g;
2407
2408 PTDEBUG && _d( $name, 'key cols:', join(', ', map { "`$_`" } @cols));
2409
2410 $keys->{$name} = {
2411 name => $name,
2412 type => $type,
2413 colnames => $cols,
2414 cols => \@cols,
2415 col_prefixes => \@col_prefixes,
2416 is_unique => $unique,
2417 is_nullable => scalar(grep { $is_nullable->{$_} } @cols),
2418 is_col => { map { $_ => 1 } @cols },
2419 ddl => $key_ddl,
2420 };
2421
2422 if ( $engine =~ m/InnoDB/i && !$clustered_key ) {
2423 my $this_key = $keys->{$name};
2424 if ( $this_key->{name} eq 'PRIMARY' ) {
2425 $clustered_key = 'PRIMARY';
2426 }
2427 elsif ( $this_key->{is_unique} && !$this_key->{is_nullable} ) {
2428 $clustered_key = $this_key->{name};
2429 }
2430 PTDEBUG && $clustered_key && _d('This key is the clustered key');
2431 }
2432 }
2433
2434 return $keys, $clustered_key;
2435}
2436
2437sub get_fks {
2438 my ( $self, $ddl, $opts ) = @_;
2439 my $q = $self->{Quoter};
2440 my $fks = {};
2441
2442 foreach my $fk (
2443 $ddl =~ m/CONSTRAINT .* FOREIGN KEY .* REFERENCES [^\)]*\)/mg )
2444 {
2445 my ( $name ) = $fk =~ m/CONSTRAINT `(.*?)`/;
2446 my ( $cols ) = $fk =~ m/FOREIGN KEY \(([^\)]+)\)/;
2447 my ( $parent, $parent_cols ) = $fk =~ m/REFERENCES (\S+) \(([^\)]+)\)/;
2448
2449 my ($db, $tbl) = $q->split_unquote($parent, $opts->{database});
2450 my %parent_tbl = (tbl => $tbl);
2451 $parent_tbl{db} = $db if $db;
2452
2453 if ( $parent !~ m/\./ && $opts->{database} ) {
2454 $parent = $q->quote($opts->{database}) . ".$parent";
2455 }
2456
2457 $fks->{$name} = {
2458 name => $name,
2459 colnames => $cols,
2460 cols => [ map { s/[ `]+//g; $_; } split(',', $cols) ],
2461 parent_tbl => \%parent_tbl,
2462 parent_tblname => $parent,
2463 parent_cols => [ map { s/[ `]+//g; $_; } split(',', $parent_cols) ],
2464 parent_colnames=> $parent_cols,
2465 ddl => $fk,
2466 };
2467 }
2468
2469 return $fks;
2470}
2471
2472sub remove_auto_increment {
2473 my ( $self, $ddl ) = @_;
2474 $ddl =~ s/(^\).*?) AUTO_INCREMENT=\d+\b/$1/m;
2475 return $ddl;
2476}
2477
2478sub get_table_status {
2479 my ( $self, $dbh, $db, $like ) = @_;
2480 my $q = $self->{Quoter};
2481 my $sql = "SHOW TABLE STATUS FROM " . $q->quote($db);
2482 my @params;
2483 if ( $like ) {
2484 $sql .= ' LIKE ?';
2485 push @params, $like;
2486 }
2487 PTDEBUG && _d($sql, @params);
2488 my $sth = $dbh->prepare($sql);
2489 eval { $sth->execute(@params); };
2490 if ($EVAL_ERROR) {
2491 PTDEBUG && _d($EVAL_ERROR);
2492 return;
2493 }
2494 my @tables = @{$sth->fetchall_arrayref({})};
2495 @tables = map {
2496 my %tbl; # Make a copy with lowercased keys
2497 @tbl{ map { lc $_ } keys %$_ } = values %$_;
2498 $tbl{engine} ||= $tbl{type} || $tbl{comment};
2499 delete $tbl{type};
2500 \%tbl;
2501 } @tables;
2502 return @tables;
2503}
2504
2505sub _d {
2506 my ($package, undef, $line) = caller 0;
2507 @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
2508 map { defined $_ ? $_ : 'undef' }
2509 @_;
2510 print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
2511}
2512
25131;
2514}
2515# ###########################################################################
2516# End TableParser package
2517# ###########################################################################
2518
2519# ###########################################################################
2090# Processlist package2520# Processlist package
2091# This package is a copy without comments from the original. The original2521# This package is a copy without comments from the original. The original
2092# with comments and its test file can be found in the Bazaar repository at,2522# with comments and its test file can be found in the Bazaar repository at,
@@ -2135,6 +2565,7 @@
2135 last_poll => 0,2565 last_poll => 0,
2136 active_cxn => {}, # keyed off ID2566 active_cxn => {}, # keyed off ID
2137 event_cache => [],2567 event_cache => [],
2568 _reasons_for_matching => {},
2138 };2569 };
2139 return bless $self, $class;2570 return bless $self, $class;
2140}2571}
@@ -2345,7 +2776,9 @@
2345 PTDEBUG && _d("Query isn't running long enough");2776 PTDEBUG && _d("Query isn't running long enough");
2346 next QUERY;2777 next QUERY;
2347 }2778 }
2348 PTDEBUG && _d('Exceeds busy time');2779 my $reason = 'Exceeds busy time';
2780 PTDEBUG && _d($reason);
2781 push @{$self->{_reasons_for_matching}->{$query} ||= []}, $reason;
2349 $matched++;2782 $matched++;
2350 }2783 }
23512784
@@ -2355,7 +2788,9 @@
2355 PTDEBUG && _d("Query isn't idle long enough");2788 PTDEBUG && _d("Query isn't idle long enough");
2356 next QUERY;2789 next QUERY;
2357 }2790 }
2358 PTDEBUG && _d('Exceeds idle time');2791 my $reason = 'Exceeds idle time';
2792 PTDEBUG && _d($reason);
2793 push @{$self->{_reasons_for_matching}->{$query} ||= []}, $reason;
2359 $matched++;2794 $matched++;
2360 }2795 }
2361 2796
@@ -2372,7 +2807,9 @@
2372 PTDEBUG && _d('Query does not match', $property, 'spec');2807 PTDEBUG && _d('Query does not match', $property, 'spec');
2373 next QUERY;2808 next QUERY;
2374 }2809 }
2375 PTDEBUG && _d('Query matches', $property, 'spec');2810 my $reason = 'Query matches ' . $property . ' spec';
2811 PTDEBUG && _d($reason);
2812 push @{$self->{_reasons_for_matching}->{$query} ||= []}, $reason;
2376 $matched++;2813 $matched++;
2377 }2814 }
2378 }2815 }
@@ -3295,6 +3732,125 @@
3295# ###########################################################################3732# ###########################################################################
32963733
3297# ###########################################################################3734# ###########################################################################
3735# Quoter package
3736# This package is a copy without comments from the original. The original
3737# with comments and its test file can be found in the Bazaar repository at,
3738# lib/Quoter.pm
3739# t/lib/Quoter.t
3740# See https://launchpad.net/percona-toolkit for more information.
3741# ###########################################################################
3742{
3743package Quoter;
3744
3745use strict;
3746use warnings FATAL => 'all';
3747use English qw(-no_match_vars);
3748use constant PTDEBUG => $ENV{PTDEBUG} || 0;
3749
3750sub new {
3751 my ( $class, %args ) = @_;
3752 return bless {}, $class;
3753}
3754
3755sub quote {
3756 my ( $self, @vals ) = @_;
3757 foreach my $val ( @vals ) {
3758 $val =~ s/`/``/g;
3759 }
3760 return join('.', map { '`' . $_ . '`' } @vals);
3761}
3762
3763sub quote_val {
3764 my ( $self, $val ) = @_;
3765
3766 return 'NULL' unless defined $val; # undef = NULL
3767 return "''" if $val eq ''; # blank string = ''
3768 return $val if $val =~ m/^0x[0-9a-fA-F]+$/; # hex data
3769
3770 $val =~ s/(['\\])/\\$1/g;
3771 return "'$val'";
3772}
3773
3774sub split_unquote {
3775 my ( $self, $db_tbl, $default_db ) = @_;
3776 $db_tbl =~ s/`//g;
3777 my ( $db, $tbl ) = split(/[.]/, $db_tbl);
3778 if ( !$tbl ) {
3779 $tbl = $db;
3780 $db = $default_db;
3781 }
3782 return ($db, $tbl);
3783}
3784
3785sub literal_like {
3786 my ( $self, $like ) = @_;
3787 return unless $like;
3788 $like =~ s/([%_])/\\$1/g;
3789 return "'$like'";
3790}
3791
3792sub join_quote {
3793 my ( $self, $default_db, $db_tbl ) = @_;
3794 return unless $db_tbl;
3795 my ($db, $tbl) = split(/[.]/, $db_tbl);
3796 if ( !$tbl ) {
3797 $tbl = $db;
3798 $db = $default_db;
3799 }
3800 $db = "`$db`" if $db && $db !~ m/^`/;
3801 $tbl = "`$tbl`" if $tbl && $tbl !~ m/^`/;
3802 return $db ? "$db.$tbl" : $tbl;
3803}
3804
3805sub serialize_list {
3806 my ( $self, @args ) = @_;
3807 return unless @args;
3808
3809 return $args[0] if @args == 1 && !defined $args[0];
3810
3811 die "Cannot serialize multiple values with undef/NULL"
3812 if grep { !defined $_ } @args;
3813
3814 return join ',', map { quotemeta } @args;
3815}
3816
3817sub deserialize_list {
3818 my ( $self, $string ) = @_;
3819 return $string unless defined $string;
3820 my @escaped_parts = $string =~ /
3821 \G # Start of string, or end of previous match.
3822 ( # Each of these is an element in the original list.
3823 [^\\,]* # Anything not a backslash or a comma
3824 (?: # When we get here, we found one of the above.
3825 \\. # A backslash followed by something so we can continue
3826 [^\\,]* # Same as above.
3827 )* # Repeat zero of more times.
3828 )
3829 , # Comma dividing elements
3830 /sxgc;
3831
3832 push @escaped_parts, pos($string) ? substr( $string, pos($string) ) : $string;
3833
3834 my @unescaped_parts = map {
3835 my $part = $_;
3836
3837 my $char_class = utf8::is_utf8($part) # If it's a UTF-8 string,
3838 ? qr/(?=\p{ASCII})\W/ # We only care about non-word
3839 : qr/(?=\p{ASCII})\W|[\x{80}-\x{FF}]/; # Otherwise,
3840 $part =~ s/\\($char_class)/$1/g;
3841 $part;
3842 } @escaped_parts;
3843
3844 return @unescaped_parts;
3845}
3846
38471;
3848}
3849# ###########################################################################
3850# End Quoter package
3851# ###########################################################################
3852
3853# ###########################################################################
3298# QueryRewriter package3854# QueryRewriter package
3299# This package is a copy without comments from the original. The original3855# This package is a copy without comments from the original. The original
3300# with comments and its test file can be found in the Bazaar repository at,3856# with comments and its test file can be found in the Bazaar repository at,
@@ -4027,7 +4583,10 @@
4027 my $cxn;4583 my $cxn;
4028 my $dbh; # $cxn->dbh4584 my $dbh; # $cxn->dbh
4029 my $get_proclist; # callback to SHOW PROCESSLIST4585 my $get_proclist; # callback to SHOW PROCESSLIST
4586 my $proc_sth;
4030 my $kill; # callback to KILL4587 my $kill; # callback to KILL
4588 my $kill_sth;
4589 my $kill_sql = $o->get('kill-query') ? 'KILL QUERY ?' : 'KILL ?';
4031 my $files;4590 my $files;
4032 if ( $files = $o->get('test-matching') ) {4591 if ( $files = $o->get('test-matching') ) {
4033 PTDEBUG && _d('Getting processlist from files:', @$files);4592 PTDEBUG && _d('Getting processlist from files:', @$files);
@@ -4079,7 +4638,7 @@
4079 # will need to be re-initialized.4638 # will need to be re-initialized.
4080 my $retry = Retry->new();4639 my $retry = Retry->new();
40814640
4082 my $proc_sth = $dbh->prepare('SHOW FULL PROCESSLIST');4641 $proc_sth = $dbh->prepare('SHOW FULL PROCESSLIST');
4083 $get_proclist = sub {4642 $get_proclist = sub {
4084 return $retry->retry(4643 return $retry->retry(
4085 # Retry for an hour: 1,200 tries x 3 seconds = 3600s/1hr4644 # Retry for an hour: 1,200 tries x 3 seconds = 3600s/1hr
@@ -4112,8 +4671,8 @@
4112 );4671 );
4113 };4672 };
41144673
4115 my $kill_sql = $o->get('kill-query') ? 'KILL QUERY ?' : 'KILL ?';4674
4116 my $kill_sth = $dbh->prepare($kill_sql);4675 $kill_sth = $dbh->prepare($kill_sql);
4117 $kill = sub {4676 $kill = sub {
4118 my ($id) = @_;4677 my ($id) = @_;
4119 PTDEBUG && _d('Killing process', $id);4678 PTDEBUG && _d('Killing process', $id);
@@ -4146,6 +4705,93 @@
4146 };4705 };
4147 }4706 }
41484707
4708 # Set up --log-dsn if specified.
4709 my ($log, $log_sth);
4710 my @processlist_columns = qw(
4711 Id User Host db Command
4712 Time State Info Time_ms
4713 );
4714 if ( my $log_dsn = $o->get('log-dsn') ) {
4715 my $db = $log_dsn->{D};
4716 my $table = $log_dsn->{t};
4717 die "--log-dsn does not specify a database (D) "
4718 . "or a database-qualified table (t)"
4719 unless defined $table && defined $db;
4720 my $log_cxn = Cxn->new(
4721 dsn_string => ($dp->get_cxn_params($log_dsn))[0],
4722 NAME_lc => 0,
4723 DSNParser => $dp,
4724 OptionParser => $o,
4725 );
4726 my $log_dbh = $log_cxn->connect();
4727 my $log_table = Quoter->quote($db, $table);
4728
4729 # Create the log-table table if it doesn't exist and --create-log-table
4730 # was passed in
4731 my $tp = TableParser->new( Quoter => "Quoter" );
4732 if ( !$tp->check_table( dbh => $log_dbh, db => $db, tbl => $table ) ) {
4733 if ($o->get('create-log-table') ) {
4734 my $sql = $o->read_para_after(
4735 __FILE__, qr/MAGIC_create_log_table/);
4736 $sql =~ s/kill_log/IF NOT EXISTS $log_table/;
4737 PTDEBUG && _d($sql);
4738 $log_dbh->do($sql);
4739 }
4740 else {
4741 die "--log-dsn table does not exist. Please create it or specify "
4742 . "--create-log-table.";
4743 }
4744 }
4745
4746 # All the columns of the table that we care about
4747 my @all_log_columns = ( qw( server_id timestamp reason kill_error ),
4748 @processlist_columns );
4749
4750 my $sql = 'SELECT @@SERVER_ID';
4751 PTDEBUG && _d($sql);
4752 my ($server_id) = $dbh->selectrow_array($sql);
4753
4754 $sql = "INSERT INTO $log_table ("
4755 . join(", ", @all_log_columns)
4756 . ") VALUES("
4757 . join(", ", $server_id, ("?") x (@all_log_columns-1))
4758 . ")";
4759 PTDEBUG && _d($sql);
4760 $log_sth = $log_dbh->prepare($sql);
4761
4762 my $retry = Retry->new();
4763
4764 $log = sub {
4765 my (@params) = @_;
4766 PTDEBUG && _d('Logging values:', @params);
4767 return $retry->retry(
4768 tries => 20,
4769 wait => sub { sleep 3; },
4770 try => sub { return $log_sth->execute(@params); },
4771 fail => sub {
4772 my (%args) = @_;
4773 my $error = $args{error};
4774 # The 1st pattern means that MySQL itself died or was stopped.
4775 # The 2nd pattern means that our cxn was killed (KILL <id>).
4776 if ( $error =~ m/MySQL server has gone away/
4777 || $error =~ m/Lost connection to MySQL server/ ) {
4778 eval {
4779 $log_dbh = $log_cxn->connect();
4780 $log_sth = $log_dbh->prepare( $sql );
4781 msg('Reconnected to ' . $cxn->name());
4782 };
4783 return 1 unless $EVAL_ERROR; # try again
4784 }
4785 return 0; # call final_fail
4786 },
4787 final_fail => sub {
4788 my (%args) = @_;
4789 die $args{error};
4790 },
4791 );
4792 };
4793 }
4794
4149 # ########################################################################4795 # ########################################################################
4150 # Daemonize only after (potentially) asking for passwords for --ask-pass.4796 # Daemonize only after (potentially) asking for passwords for --ask-pass.
4151 # ########################################################################4797 # ########################################################################
@@ -4349,7 +4995,17 @@
4349 . " seconds before kill");4995 . " seconds before kill");
4350 sleep $o->get('wait-before-kill');4996 sleep $o->get('wait-before-kill');
4351 }4997 }
4998 local $@;
4352 eval { $kill->($query->{Id}) };4999 eval { $kill->($query->{Id}) };
5000 if ( $log ) {
5001 log_to_table(
5002 log => $log,
5003 query => $query,
5004 proclist => $pl,
5005 columns => \@processlist_columns,
5006 eval_error => $EVAL_ERROR,
5007 );
5008 }
4353 if ( $EVAL_ERROR ) {5009 if ( $EVAL_ERROR ) {
4354 msg("Error killing $query->{Id}: $EVAL_ERROR");5010 msg("Error killing $query->{Id}: $EVAL_ERROR");
4355 }5011 }
@@ -4417,6 +5073,21 @@
4417 return;5073 return;
4418}5074}
44195075
5076sub log_to_table {
5077 my (%args) = @_;
5078 my ($log, $query, $pl, $processlist_columns)
5079 = @args{qw( log query proclist columns )};
5080
5081 my $ts = Transformers::ts(localtime);
5082 my $reasons = join "\n", map {
5083 defined($_) ? $_ : "Unkown reason"
5084 } @{ $pl->{_reasons_for_matching}->{$query} };
5085 $log->(
5086 $ts, $reasons, $args{eval_error},
5087 @{$query}{@$processlist_columns}
5088 );
5089}
5090
4420sub group_queries {5091sub group_queries {
4421 my ( %args ) = @_;5092 my ( %args ) = @_;
4422 my ($proclist, $group_by, $qr) = @args{qw(proclist group_by QueryRewriter)};5093 my ($proclist, $group_by, $qr) = @args{qw(proclist group_by QueryRewriter)};
@@ -4642,6 +5313,13 @@
46425313
4643The database to use for the connection.5314The database to use for the connection.
46445315
5316=item --create-log-table
5317
5318Create the L<"--log-dsn"> table if it does not exist.
5319
5320This option causes the table specified by L<"--log-dsn"> to be created with the
5321default structure shown in the documentation for that option.
5322
4645=item --daemonize5323=item --daemonize
46465324
4647Fork to the background and detach from the shell. POSIX operating systems5325Fork to the background and detach from the shell. POSIX operating systems
@@ -4746,6 +5424,37 @@
47465424
4747Print all output to this file when daemonized.5425Print all output to this file when daemonized.
47485426
5427=item --log-dsn
5428
5429type: DSN
5430
5431Store each query killed in this DSN.
5432
5433The argument specifies a table to store all killed queries. The DSN
5434passed in must have the databse (D) and table (t) options. The
5435table must have at least the following columns. You can add more columns for
5436your own special purposes, but they won't be used by pt-kill. The
5437following CREATE TABLE definition is also used for L<"--create-log-table">.
5438MAGIC_create_log_table:
5439
5440 CREATE TABLE kill_log (
5441 kill_id int(10) unsigned NOT NULL AUTO_INCREMENT,
5442 server_id bigint(4) NOT NULL DEFAULT '0',
5443 timestamp DATETIME,
5444 reason TEXT,
5445 kill_error TEXT,
5446 Id bigint(4) NOT NULL DEFAULT '0',
5447 User varchar(16) NOT NULL DEFAULT '',
5448 Host varchar(64) NOT NULL DEFAULT '',
5449 db varchar(64) DEFAULT NULL,
5450 Command varchar(16) NOT NULL DEFAULT '',
5451 Time int(7) NOT NULL DEFAULT '0',
5452 State varchar(64) DEFAULT NULL,
5453 Info longtext,
5454 Time_ms bigint(21) DEFAULT '0', # NOTE, TODO: currently not used
5455 PRIMARY KEY (kill_id)
5456 ) DEFAULT CHARSET=utf8
5457
4749=item --password5458=item --password
47505459
4751short form: -p; type: string5460short form: -p; type: string
@@ -5251,6 +5960,10 @@
52515960
5252User for login if not current user.5961User for login if not current user.
52535962
5963=item * t
5964
5965Table to log actions in, if passed through --log-dsn.
5966
5254=back5967=back
52555968
5256=head1 ENVIRONMENT5969=head1 ENVIRONMENT
52575970
=== modified file 'lib/Processlist.pm'
--- lib/Processlist.pm 2012-05-28 02:28:35 +0000
+++ lib/Processlist.pm 2012-07-19 16:42:35 +0000
@@ -75,6 +75,7 @@
75 last_poll => 0,75 last_poll => 0,
76 active_cxn => {}, # keyed off ID76 active_cxn => {}, # keyed off ID
77 event_cache => [],77 event_cache => [],
78 _reasons_for_matching => {},
78 };79 };
79 return bless $self, $class;80 return bless $self, $class;
80}81}
@@ -475,7 +476,15 @@
475 PTDEBUG && _d("Query isn't running long enough");476 PTDEBUG && _d("Query isn't running long enough");
476 next QUERY;477 next QUERY;
477 }478 }
478 PTDEBUG && _d('Exceeds busy time');479 my $reason = 'Exceeds busy time';
480 PTDEBUG && _d($reason);
481 # Saving the reasons for each query in the objct is a bit nasty,
482 # but the alternatives are worse:
483 # - Saving internal data in the query
484 # - Instead of using the stringified hashref as a key, using
485 # a checksum of the hashes' contents. Which could occasionally
486 # fail miserably due to timing-related issues.
487 push @{$self->{_reasons_for_matching}->{$query} ||= []}, $reason;
479 $matched++;488 $matched++;
480 }489 }
481490
@@ -486,7 +495,9 @@
486 PTDEBUG && _d("Query isn't idle long enough");495 PTDEBUG && _d("Query isn't idle long enough");
487 next QUERY;496 next QUERY;
488 }497 }
489 PTDEBUG && _d('Exceeds idle time');498 my $reason = 'Exceeds idle time';
499 PTDEBUG && _d($reason);
500 push @{$self->{_reasons_for_matching}->{$query} ||= []}, $reason;
490 $matched++;501 $matched++;
491 }502 }
492 503
@@ -507,7 +518,9 @@
507 PTDEBUG && _d('Query does not match', $property, 'spec');518 PTDEBUG && _d('Query does not match', $property, 'spec');
508 next QUERY;519 next QUERY;
509 }520 }
510 PTDEBUG && _d('Query matches', $property, 'spec');521 my $reason = 'Query matches ' . $property . ' spec';
522 PTDEBUG && _d($reason);
523 push @{$self->{_reasons_for_matching}->{$query} ||= []}, $reason;
511 $matched++;524 $matched++;
512 }525 }
513 }526 }
514527
=== modified file 't/lib/Processlist.t'
--- t/lib/Processlist.t 2012-05-30 14:36:44 +0000
+++ t/lib/Processlist.t 2012-07-19 16:42:35 +0000
@@ -9,7 +9,7 @@
9use strict;9use strict;
10use warnings FATAL => 'all';10use warnings FATAL => 'all';
11use English qw(-no_match_vars);11use English qw(-no_match_vars);
12use Test::More tests => 34;12use Test::More tests => 35;
1313
14use Processlist;14use Processlist;
15use PerconaTest;15use PerconaTest;
@@ -600,6 +600,17 @@
600 },600 },
601);601);
602602
603my $matching_query =
604 { 'Time' => '91',
605 'Command' => 'Query',
606 'db' => undef,
607 'Id' => '43',
608 'Info' => 'select * from foo',
609 'User' => 'msandbox',
610 'State' => 'executing',
611 'Host' => 'localhost'
612 };
613
603my @queries = $pl->find(614my @queries = $pl->find(
604 [ { 'Time' => '488',615 [ { 'Time' => '488',
605 'Command' => 'Connect',616 'Command' => 'Connect',
@@ -675,33 +686,24 @@
675 'State' => 'Locked',686 'State' => 'Locked',
676 'Host' => 'localhost'687 'Host' => 'localhost'
677 },688 },
678 { 'Time' => '91',689 $matching_query,
679 'Command' => 'Query',
680 'db' => undef,
681 'Id' => '43',
682 'Info' => 'select * from foo',
683 'User' => 'msandbox',
684 'State' => 'executing',
685 'Host' => 'localhost'
686 },
687 ],690 ],
688 %find_spec,691 %find_spec,
689);692);
690693
691my $expected = [694my $expected = [ $matching_query ];
692 { 'Time' => '91',
693 'Command' => 'Query',
694 'db' => undef,
695 'Id' => '43',
696 'Info' => 'select * from foo',
697 'User' => 'msandbox',
698 'State' => 'executing',
699 'Host' => 'localhost'
700 },
701 ];
702695
703is_deeply(\@queries, $expected, 'Basic find()');696is_deeply(\@queries, $expected, 'Basic find()');
704697
698{
699 # Internal, fragile test!
700 is_deeply(
701 $pl->{_reasons_for_matching}->{$matching_query},
702 [ 'Exceeds busy time', 'Query matches Command spec', 'Query matches Info spec', ],
703 "_reasons_for_matching works"
704 );
705}
706
705%find_spec = (707%find_spec = (
706 busy_time => 1,708 busy_time => 1,
707 ignore => {709 ignore => {
708710
=== modified file 't/pt-kill/kill.t'
--- t/pt-kill/kill.t 2012-07-12 22:49:15 +0000
+++ t/pt-kill/kill.t 2012-07-19 16:42:35 +0000
@@ -29,7 +29,7 @@
29 plan skip_all => 'Cannot connect to sandbox master';29 plan skip_all => 'Cannot connect to sandbox master';
30}30}
31else {31else {
32 plan tests => 8;32 plan tests => 21;
33}33}
3434
35my $output;35my $output;
@@ -56,8 +56,11 @@
5656
57$output = output(57$output = output(
58 sub { pt_kill::main('-F', $cnf, qw(--kill --print --run-time 1 --interval 1),58 sub { pt_kill::main('-F', $cnf, qw(--kill --print --run-time 1 --interval 1),
59 '--match-info', 'select sleep\(4\)') },59 "--match-info", 'select sleep\(4\)',
60 )
61 },
60);62);
63
61like(64like(
62 $output,65 $output,
63 qr/KILL $pid /,66 qr/KILL $pid /,
@@ -117,6 +120,157 @@
117);120);
118121
119# #############################################################################122# #############################################################################
123# Test that --log-dsn
124# #############################################################################
125
126$dbh->do("DROP DATABASE IF EXISTS `kill_test`");
127$dbh->do("CREATE DATABASE `kill_test`");
128
129my $sql = OptionParser->read_para_after(
130 "$trunk/bin/pt-kill", qr/MAGIC_create_log_table/);
131$sql =~ s/kill_log/`kill_test`.`log_table`/;
132
133$dbh->do($sql);
134
135{
136 system("/tmp/12345/use -h127.1 -P12345 -umsandbox -pmsandbox -e 'select sleep(4)' >/dev/null&");
137 sleep 0.5;
138 local $EVAL_ERROR;
139 eval {
140 pt_kill::main('-F', $cnf, qw(--kill --run-time 1 --interval 1),
141 "--match-info", 'select sleep\(4\)',
142 "--log-dsn", q!h=127.1,P=12345,u=msandbox,p=msandbox,D=kill_test,t=log_table!,
143 )
144 };
145 is(
146 $EVAL_ERROR,
147 '',
148 "--log-dsn works if the table exists and --create-log-table wasn't passed in."
149 ) or diag $EVAL_ERROR;
150
151 local $EVAL_ERROR;
152 my $results = eval { $dbh->selectall_arrayref("SELECT * FROM `kill_test`.`log_table`", { Slice => {} } ) };
153 is(
154 $EVAL_ERROR,
155 '',
156 "...and we can query the table"
157 ) or diag $EVAL_ERROR;
158
159 is @{$results}, 1, "...which contains one entry";
160 use Data::Dumper;
161 my $reason = $dbh->selectrow_array("SELECT reason FROM `kill_test`.`log_table` WHERE kill_id=1");
162 is $reason,
163 'Query matches Info spec',
164 'reason gets set to something sensible';
165
166 TODO: {
167 local $::TODO = "Time_ms currently isn't reported";
168 my $time_ms = $dbh->selectrow_array("SELECT Time_ms FROM `kill_test`.`log_table` WHERE kill_id=1");
169 ok $time_ms;
170 }
171
172 my $result = shift @$results;
173 my $against = {
174 user => 'msandbox',
175 host => 'localhost',
176 db => undef,
177 command => 'Query',
178 state => ($sandbox_version lt '5.1' ? "executing" : "User sleep"),
179 info => 'select sleep(4)',
180 };
181 my %trimmed_result;
182 @trimmed_result{ keys %$against } = @{$result}{ keys %$against };
183 $trimmed_result{host} =~ s/localhost:[0-9]+/localhost/;
184 is_deeply(
185 \%trimmed_result,
186 $against,
187 "...and was populated as expected",
188 ) or diag(Dumper($result));
189
190 system("/tmp/12345/use -h127.1 -P12345 -umsandbox -pmsandbox -e 'select sleep(4)' >/dev/null&");
191 sleep 0.5;
192 local $EVAL_ERROR;
193 eval {
194 pt_kill::main('-F', $cnf, qw(--kill --run-time 1 --interval 1 --create-log-table),
195 "--match-info", 'select sleep\(4\)',
196 "--log-dsn", q!h=127.1,P=12345,u=msandbox,p=msandbox,D=kill_test,t=log_table!,
197 )
198 };
199 is(
200 $EVAL_ERROR,
201 '',
202 "--log-dsn works if the table exists and --create-log-table was passed in."
203 );
204}
205
206{
207 $dbh->do("DROP TABLE `kill_test`.`log_table`");
208
209 system("/tmp/12345/use -h127.1 -P12345 -umsandbox -pmsandbox -e 'select sleep(4)' >/dev/null&");
210 sleep 0.5;
211 local $EVAL_ERROR;
212 eval {
213 pt_kill::main('-F', $cnf, qw(--kill --run-time 1 --interval 1 --create-log-table),
214 "--match-info", 'select sleep\(4\)',
215 "--log-dsn", q!h=127.1,P=12345,u=msandbox,p=msandbox,D=kill_test,t=log_table!,
216 )
217 };
218 is(
219 $EVAL_ERROR,
220 '',
221 "--log-dsn works if the table doesn't exists and --create-log-table was passed in."
222 );
223}
224
225{
226 $dbh->do("DROP TABLE `kill_test`.`log_table`");
227
228 local $EVAL_ERROR;
229 eval {
230 pt_kill::main('-F', $cnf, qw(--kill --run-time 1 --interval 1),
231 "--match-info", 'select sleep\(4\)',
232 "--log-dsn", q!h=127.1,P=12345,u=msandbox,p=msandbox,D=kill_test,t=log_table!,
233 )
234 };
235 like $EVAL_ERROR,
236 qr/\Q--log-dsn table does not exist. Please create it or specify\E/,
237 "By default, --log-dsn doesn't autogenerate a table";
238}
239
240for my $dsn (
241 q!h=127.1,P=12345,u=msandbox,p=msandbox,t=log_table!,
242 q!h=127.1,P=12345,u=msandbox,p=msandbox,D=kill_test!,
243 q!h=127.1,P=12345,u=msandbox,p=msandbox!,
244) {
245 local $EVAL_ERROR;
246 eval {
247 pt_kill::main('-F', $cnf, qw(--kill --run-time 1 --interval 1),
248 "--match-info", 'select sleep\(4\)',
249 "--log-dsn", $dsn,
250 )
251 };
252 like $EVAL_ERROR,
253 qr/\Q--log-dsn does not specify a database (D) or a database-qualified table (t)\E/,
254 "--log-dsn croaks if t= or D= are absent";
255}
256
257# Run it twice
258for (1,2) {
259 system("/tmp/12345/use -h127.1 -P12345 -umsandbox -pmsandbox -e 'select sleep(4)' >/dev/null&");
260 sleep 0.5;
261 pt_kill::main('-F', $cnf, qw(--kill --run-time 1 --interval 1 --create-log-table),
262 "--match-info", 'select sleep\(4\)',
263 "--log-dsn", q!h=127.1,P=12345,u=msandbox,p=msandbox,D=kill_test,t=log_table!,
264 );
265}
266
267my $results = $dbh->selectall_arrayref("SELECT * FROM `kill_test`.`log_table`");
268
269is @{$results}, 2, "Different --log-dsn runs reuse the same table.";
270
271$dbh->do("DROP DATABASE kill_test");
272
273# #############################################################################
120# Done.274# Done.
121# #############################################################################275# #############################################################################
122$sb->wipe_clean($dbh);276$sb->wipe_clean($dbh);

Subscribers

People subscribed via source and target branches