Merge lp:~abychko/percona-server/ps-5.1-bug1032139 into lp:percona-server/5.1

Proposed by Alexey Bychko
Status: Merged
Approved by: Alexey Kopytov
Approved revision: no longer in the source branch.
Merged at revision: 541
Proposed branch: lp:~abychko/percona-server/ps-5.1-bug1032139
Merge into: lp:percona-server/5.1
Diff against target: 13050 lines (+0/-12986)
8 files modified
build/debian/additions/innotop/InnoDBParser.pm (+0/-1089)
build/debian/additions/innotop/changelog.innotop (+0/-318)
build/debian/additions/innotop/innotop (+0/-9485)
build/debian/additions/innotop/innotop.1 (+0/-2086)
build/debian/percona-server-client-5.1.dirs (+0/-1)
build/debian/percona-server-client-5.1.docs (+0/-1)
build/debian/percona-server-client-5.1.files (+0/-3)
build/debian/rules (+0/-3)
To merge this branch: bzr merge lp:~abychko/percona-server/ps-5.1-bug1032139
Reviewer Review Type Date Requested Status
Alexey Kopytov (community) Approve
Ignacio Nin (community) Approve
Review via email: mp+147021@code.launchpad.net

Description of the change

[-] removed innotop and its InnoDBParser perl package from source and debian installation

To post a comment you must log in.
Revision history for this message
Ignacio Nin (ignacio-nin) wrote :

Seems good to me, tested working in squeeze

review: Approve
Revision history for this message
Alexey Kopytov (akopytov) wrote :

Should also reference bug #1050536 as this fix also fixes that bug. And resubmitted as a proper upmerge for 5.5.

review: Needs Fixing
Revision history for this message
Alexey Bychko (abychko) wrote :
Revision history for this message
Alexey Kopytov (akopytov) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== removed directory 'build/debian/additions/innotop'
=== removed file 'build/debian/additions/innotop/InnoDBParser.pm'
--- build/debian/additions/innotop/InnoDBParser.pm 2010-05-31 13:57:21 +0000
+++ build/debian/additions/innotop/InnoDBParser.pm 1970-01-01 00:00:00 +0000
@@ -1,1089 +0,0 @@
1use strict;
2use warnings FATAL => 'all';
3
4package InnoDBParser;
5
6# This program is copyright (c) 2006 Baron Schwartz, baron at xaprb dot com.
7# Feedback and improvements are gratefully received.
8#
9# THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
10# WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
11# MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
12#
13# This program is free software; you can redistribute it and/or modify it under
14# the terms of the GNU General Public License as published by the Free Software
15# Foundation, version 2; OR the Perl Artistic License. On UNIX and similar
16# systems, you can issue `man perlgpl' or `man perlartistic' to read these
17
18# You should have received a copy of the GNU General Public License along with
19# this program; if not, write to the Free Software Foundation, Inc., 59 Temple
20# Place, Suite 330, Boston, MA 02111-1307 USA
21
22our $VERSION = '1.6.0';
23
24use Data::Dumper;
25$Data::Dumper::Sortkeys = 1;
26use English qw(-no_match_vars);
27use List::Util qw(max);
28
29# Some common patterns
30my $d = qr/(\d+)/; # Digit
31my $f = qr/(\d+\.\d+)/; # Float
32my $t = qr/(\d+ \d+)/; # Transaction ID
33my $i = qr/((?:\d{1,3}\.){3}\d+)/; # IP address
34my $n = qr/([^`\s]+)/; # MySQL object name
35my $w = qr/(\w+)/; # Words
36my $fl = qr/([\w\.\/]+) line $d/; # Filename and line number
37my $h = qr/((?:0x)?[0-9a-f]*)/; # Hex
38my $s = qr/(\d{6} .\d:\d\d:\d\d)/; # InnoDB timestamp
39
40# If you update this variable, also update the SYNOPSIS in the pod.
41my %innodb_section_headers = (
42 "TRANSACTIONS" => "tx",
43 "BUFFER POOL AND MEMORY" => "bp",
44 "SEMAPHORES" => "sm",
45 "LOG" => "lg",
46 "ROW OPERATIONS" => "ro",
47 "INSERT BUFFER AND ADAPTIVE HASH INDEX" => "ib",
48 "FILE I/O" => "io",
49 "LATEST DETECTED DEADLOCK" => "dl",
50 "LATEST FOREIGN KEY ERROR" => "fk",
51);
52
53my %parser_for = (
54 tx => \&parse_tx_section,
55 bp => \&parse_bp_section,
56 sm => \&parse_sm_section,
57 lg => \&parse_lg_section,
58 ro => \&parse_ro_section,
59 ib => \&parse_ib_section,
60 io => \&parse_io_section,
61 dl => \&parse_dl_section,
62 fk => \&parse_fk_section,
63);
64
65my %fk_parser_for = (
66 Transaction => \&parse_fk_transaction_error,
67 Error => \&parse_fk_bad_constraint_error,
68 Cannot => \&parse_fk_cant_drop_parent_error,
69);
70
71# A thread's proc_info can be at least 98 different things I've found in the
72# source. Fortunately, most of them begin with a gerunded verb. These are
73# the ones that don't.
74my %is_proc_info = (
75 'After create' => 1,
76 'Execution of init_command' => 1,
77 'FULLTEXT initialization' => 1,
78 'Reopen tables' => 1,
79 'Repair done' => 1,
80 'Repair with keycache' => 1,
81 'System lock' => 1,
82 'Table lock' => 1,
83 'Thread initialized' => 1,
84 'User lock' => 1,
85 'copy to tmp table' => 1,
86 'discard_or_import_tablespace' => 1,
87 'end' => 1,
88 'got handler lock' => 1,
89 'got old table' => 1,
90 'init' => 1,
91 'key cache' => 1,
92 'locks' => 1,
93 'malloc' => 1,
94 'query end' => 1,
95 'rename result table' => 1,
96 'rename' => 1,
97 'setup' => 1,
98 'statistics' => 1,
99 'status' => 1,
100 'table cache' => 1,
101 'update' => 1,
102);
103
104sub new {
105 bless {}, shift;
106}
107
108# Parse the status and return it.
109# See srv_printf_innodb_monitor in innobase/srv/srv0srv.c
110# Pass in the text to parse, whether to be in debugging mode, which sections
111# to parse (hashref; if empty, parse all), and whether to parse full info from
112# locks and such (probably shouldn't unless you need to).
113sub parse_status_text {
114 my ( $self, $fulltext, $debug, $sections, $full ) = @_;
115
116 die "I can't parse undef" unless defined $fulltext;
117 $fulltext =~ s/[\r\n]+/\n/g;
118
119 $sections ||= {};
120 die '$sections must be a hashref' unless ref($sections) eq 'HASH';
121
122 my %innodb_data = (
123 got_all => 0, # Whether I was able to get the whole thing
124 ts => '', # Timestamp the server put on it
125 last_secs => 0, # Num seconds the averages are over
126 sections => {}, # Parsed values from each section
127 );
128
129 if ( $debug ) {
130 $innodb_data{'fulltext'} = $fulltext;
131 }
132
133 # Get the most basic info about the status: beginning and end, and whether
134 # I got the whole thing (if there has been a big deadlock and there are
135 # too many locks to print, the output might be truncated)
136 my ( $time_text ) = $fulltext =~ m/^$s INNODB MONITOR OUTPUT$/m;
137 $innodb_data{'ts'} = [ parse_innodb_timestamp( $time_text ) ];
138 $innodb_data{'timestring'} = ts_to_string($innodb_data{'ts'});
139 ( $innodb_data{'last_secs'} ) = $fulltext
140 =~ m/Per second averages calculated from the last $d seconds/;
141
142 ( my $got_all ) = $fulltext =~ m/END OF INNODB MONITOR OUTPUT/;
143 $innodb_data{'got_all'} = $got_all || 0;
144
145 # Split it into sections. Each section begins with
146 # -----
147 # LABEL
148 # -----
149 my %innodb_sections;
150 my @matches = $fulltext
151 =~ m#\n(---+)\n([A-Z /]+)\n\1\n(.*?)(?=\n(---+)\n[A-Z /]+\n\4\n|$)#gs;
152 while ( my ( $start, $name, $text, $end ) = splice(@matches, 0, 4) ) {
153 $innodb_sections{$name} = [ $text, $end ? 1 : 0 ];
154 }
155 # The Row Operations section is a special case, because instead of ending
156 # with the beginning of another section, it ends with the end of the file.
157 # So this section is complete if the entire file is complete.
158 $innodb_sections{'ROW OPERATIONS'}->[1] ||= $innodb_data{'got_all'};
159
160 # Just for sanity's sake, make sure I understand what to do with each
161 # section
162 eval {
163 foreach my $section ( keys %innodb_sections ) {
164 my $header = $innodb_section_headers{$section};
165 die "Unknown section $section in $fulltext\n"
166 unless $header;
167 $innodb_data{'sections'}->{ $header }
168 ->{'fulltext'} = $innodb_sections{$section}->[0];
169 $innodb_data{'sections'}->{ $header }
170 ->{'complete'} = $innodb_sections{$section}->[1];
171 }
172 };
173 if ( $EVAL_ERROR ) {
174 _debug( $debug, $EVAL_ERROR);
175 }
176
177 # ################################################################
178 # Parse the detailed data out of the sections.
179 # ################################################################
180 eval {
181 foreach my $section ( keys %parser_for ) {
182 if ( defined $innodb_data{'sections'}->{$section}
183 && (!%$sections || (defined($sections->{$section} && $sections->{$section})) )) {
184 $parser_for{$section}->(
185 $innodb_data{'sections'}->{$section},
186 $innodb_data{'sections'}->{$section}->{'complete'},
187 $debug,
188 $full )
189 or delete $innodb_data{'sections'}->{$section};
190 }
191 else {
192 delete $innodb_data{'sections'}->{$section};
193 }
194 }
195 };
196 if ( $EVAL_ERROR ) {
197 _debug( $debug, $EVAL_ERROR);
198 }
199
200 return \%innodb_data;
201}
202
203# Parses the status text and returns it flattened out as a single hash.
204sub get_status_hash {
205 my ( $self, $fulltext, $debug, $sections, $full ) = @_;
206
207 # Parse the status text...
208 my $innodb_status
209 = $self->parse_status_text($fulltext, $debug, $sections, $full );
210
211 # Flatten the hierarchical structure into a single list by grabbing desired
212 # sections from it.
213 return
214 (map { 'IB_' . $_ => $innodb_status->{$_} } qw(timestring last_secs got_all)),
215 (map { 'IB_bp_' . $_ => $innodb_status->{'sections'}->{'bp'}->{$_} }
216 qw( writes_pending buf_pool_hit_rate total_mem_alloc buf_pool_reads
217 awe_mem_alloc pages_modified writes_pending_lru page_creates_sec
218 reads_pending pages_total buf_pool_hits writes_pending_single_page
219 page_writes_sec pages_read pages_written page_reads_sec
220 writes_pending_flush_list buf_pool_size add_pool_alloc
221 dict_mem_alloc pages_created buf_free complete )),
222 (map { 'IB_tx_' . $_ => $innodb_status->{'sections'}->{'tx'}->{$_} }
223 qw( num_lock_structs history_list_len purge_done_for transactions
224 purge_undo_for is_truncated trx_id_counter complete )),
225 (map { 'IB_ib_' . $_ => $innodb_status->{'sections'}->{'ib'}->{$_} }
226 qw( hash_table_size hash_searches_s non_hash_searches_s
227 bufs_in_node_heap used_cells size free_list_len seg_size inserts
228 merged_recs merges complete )),
229 (map { 'IB_lg_' . $_ => $innodb_status->{'sections'}->{'lg'}->{$_} }
230 qw( log_ios_done pending_chkp_writes last_chkp log_ios_s
231 log_flushed_to log_seq_no pending_log_writes complete )),
232 (map { 'IB_sm_' . $_ => $innodb_status->{'sections'}->{'sm'}->{$_} }
233 qw( wait_array_size rw_shared_spins rw_excl_os_waits mutex_os_waits
234 mutex_spin_rounds mutex_spin_waits rw_excl_spins rw_shared_os_waits
235 waits signal_count reservation_count complete )),
236 (map { 'IB_ro_' . $_ => $innodb_status->{'sections'}->{'ro'}->{$_} }
237 qw( queries_in_queue n_reserved_extents main_thread_state
238 main_thread_proc_no main_thread_id read_sec del_sec upd_sec ins_sec
239 read_views_open num_rows_upd num_rows_ins num_rows_read
240 queries_inside num_rows_del complete )),
241 (map { 'IB_fk_' . $_ => $innodb_status->{'sections'}->{'fk'}->{$_} }
242 qw( trigger parent_table child_index parent_index attempted_op
243 child_db timestring fk_name records col_name reason txn parent_db
244 type child_table parent_col complete )),
245 (map { 'IB_io_' . $_ => $innodb_status->{'sections'}->{'io'}->{$_} }
246 qw( pending_buffer_pool_flushes pending_pwrites pending_preads
247 pending_normal_aio_reads fsyncs_s os_file_writes pending_sync_ios
248 reads_s flush_type avg_bytes_s pending_ibuf_aio_reads writes_s
249 threads os_file_reads pending_aio_writes pending_log_ios os_fsyncs
250 pending_log_flushes complete )),
251 (map { 'IB_dl_' . $_ => $innodb_status->{'sections'}->{'dl'}->{$_} }
252 qw( timestring rolled_back txns complete ));
253
254}
255
256sub ts_to_string {
257 my $parts = shift;
258 return sprintf('%02d-%02d-%02d %02d:%02d:%02d', @$parts);
259}
260
261sub parse_innodb_timestamp {
262 my $text = shift;
263 my ( $y, $m, $d, $h, $i, $s )
264 = $text =~ m/^(\d\d)(\d\d)(\d\d) +(\d+):(\d+):(\d+)$/;
265 die("Can't get timestamp from $text\n") unless $y;
266 $y += 2000;
267 return ( $y, $m, $d, $h, $i, $s );
268}
269
270sub parse_fk_section {
271 my ( $section, $complete, $debug, $full ) = @_;
272 my $fulltext = $section->{'fulltext'};
273
274 return 0 unless $fulltext;
275
276 my ( $ts, $type ) = $fulltext =~ m/^$s\s+(\w+)/m;
277 $section->{'ts'} = [ parse_innodb_timestamp( $ts ) ];
278 $section->{'timestring'} = ts_to_string($section->{'ts'});
279 $section->{'type'} = $type;
280
281 # Decide which type of FK error happened, and dispatch to the right parser.
282 if ( $type && $fk_parser_for{$type} ) {
283 $fk_parser_for{$type}->( $section, $complete, $debug, $fulltext, $full );
284 }
285
286 delete $section->{'fulltext'} unless $debug;
287
288 return 1;
289}
290
291sub parse_fk_cant_drop_parent_error {
292 my ( $section, $complete, $debug, $fulltext, $full ) = @_;
293
294 # Parse the parent/child table info out
295 @{$section}{ qw(attempted_op parent_db parent_table) } = $fulltext
296 =~ m{Cannot $w table `(.*)/(.*)`}m;
297 @{$section}{ qw(child_db child_table) } = $fulltext
298 =~ m{because it is referenced by `(.*)/(.*)`}m;
299
300 ( $section->{'reason'} ) = $fulltext =~ m/(Cannot .*)/s;
301 $section->{'reason'} =~ s/\n(?:InnoDB: )?/ /gm
302 if $section->{'reason'};
303
304 # Certain data may not be present. Make them '' if not present.
305 map { $section->{$_} ||= "" }
306 qw(child_index fk_name col_name parent_col);
307}
308
309# See dict/dict0dict.c, function dict_foreign_error_report
310# I don't care much about these. There are lots of different messages, and
311# they come from someone trying to create a foreign key, or similar
312# statements. They aren't indicative of some transaction trying to insert,
313# delete or update data. Sometimes it is possible to parse out a lot of
314# information about the tables and indexes involved, but often the message
315# contains the DDL string the user entered, which is way too much for this
316# module to try to handle.
317sub parse_fk_bad_constraint_error {
318 my ( $section, $complete, $debug, $fulltext, $full ) = @_;
319
320 # Parse the parent/child table and index info out
321 @{$section}{ qw(child_db child_table) } = $fulltext
322 =~ m{Error in foreign key constraint of table (.*)/(.*):$}m;
323 $section->{'attempted_op'} = 'DDL';
324
325 # FK name, parent info... if possible.
326 @{$section}{ qw(fk_name col_name parent_db parent_table parent_col) }
327 = $fulltext
328 =~ m/CONSTRAINT `?$n`? FOREIGN KEY \(`?$n`?\) REFERENCES (?:`?$n`?\.)?`?$n`? \(`?$n`?\)/;
329
330 if ( !defined($section->{'fk_name'}) ) {
331 # Try to parse SQL a user might have typed in a CREATE statement or such
332 @{$section}{ qw(col_name parent_db parent_table parent_col) }
333 = $fulltext
334 =~ m/FOREIGN\s+KEY\s*\(`?$n`?\)\s+REFERENCES\s+(?:`?$n`?\.)?`?$n`?\s*\(`?$n`?\)/i;
335 }
336 $section->{'parent_db'} ||= $section->{'child_db'};
337
338 # Name of the child index (index in the same table where the FK is, see
339 # definition of dict_foreign_struct in include/dict0mem.h, where it is
340 # called foreign_index, as opposed to referenced_index which is in the
341 # parent table. This may not be possible to find.
342 @{$section}{ qw(child_index) } = $fulltext
343 =~ m/^The index in the foreign key in table is $n$/m;
344
345 @{$section}{ qw(reason) } = $fulltext =~ m/:\s*([^:]+)(?= Constraint:|$)/ms;
346 $section->{'reason'} =~ s/\s+/ /g
347 if $section->{'reason'};
348
349 # Certain data may not be present. Make them '' if not present.
350 map { $section->{$_} ||= "" }
351 qw(child_index fk_name col_name parent_table parent_col);
352}
353
354# see source file row/row0ins.c
355sub parse_fk_transaction_error {
356 my ( $section, $complete, $debug, $fulltext, $full ) = @_;
357
358 # Parse the txn info out
359 my ( $txn ) = $fulltext
360 =~ m/Transaction:\n(TRANSACTION.*)\nForeign key constraint fails/s;
361 if ( $txn ) {
362 $section->{'txn'} = parse_tx_text( $txn, $complete, $debug, $full );
363 }
364
365 # Parse the parent/child table and index info out. There are two types: an
366 # update or a delete of a parent record leaves a child orphaned
367 # (row_ins_foreign_report_err), and an insert or update of a child record has
368 # no matching parent record (row_ins_foreign_report_add_err).
369
370 @{$section}{ qw(reason child_db child_table) }
371 = $fulltext =~ m{^(Foreign key constraint fails for table `(.*)/(.*)`:)$}m;
372
373 @{$section}{ qw(fk_name col_name parent_db parent_table parent_col) }
374 = $fulltext
375 =~ m/CONSTRAINT `$n` FOREIGN KEY \(`$n`\) REFERENCES (?:`$n`\.)?`$n` \(`$n`\)/;
376 $section->{'parent_db'} ||= $section->{'child_db'};
377
378 # Special case, which I don't know how to trigger, but see
379 # innobase/row/row0ins.c row_ins_check_foreign_constraint
380 if ( $fulltext =~ m/ibd file does not currently exist!/ ) {
381 my ( $attempted_op, $index, $records )
382 = $fulltext =~ m/^Trying to (add to index) `$n` tuple:\n(.*))?/sm;
383 $section->{'child_index'} = $index;
384 $section->{'attempted_op'} = $attempted_op || '';
385 if ( $records && $full ) {
386 ( $section->{'records'} )
387 = parse_innodb_record_dump( $records, $complete, $debug );
388 }
389 @{$section}{qw(parent_db parent_table)}
390 =~ m/^But the parent table `$n`\.`$n`$/m;
391 }
392 else {
393 my ( $attempted_op, $which, $index )
394 = $fulltext =~ m/^Trying to ([\w ]*) in (child|parent) table, in index `$n` tuple:$/m;
395 if ( $which ) {
396 $section->{$which . '_index'} = $index;
397 $section->{'attempted_op'} = $attempted_op || '';
398
399 # Parse out the related records in the other table.
400 my ( $search_index, $records );
401 if ( $which eq 'child' ) {
402 ( $search_index, $records ) = $fulltext
403 =~ m/^But in parent table [^,]*, in index `$n`,\nthe closest match we can find is record:\n(.*)/ms;
404 $section->{'parent_index'} = $search_index;
405 }
406 else {
407 ( $search_index, $records ) = $fulltext
408 =~ m/^But in child table [^,]*, in index `$n`, (?:the record is not available|there is a record:\n(.*))?/ms;
409 $section->{'child_index'} = $search_index;
410 }
411 if ( $records && $full ) {
412 $section->{'records'}
413 = parse_innodb_record_dump( $records, $complete, $debug );
414 }
415 else {
416 $section->{'records'} = '';
417 }
418 }
419 }
420
421 # Parse out the tuple trying to be updated, deleted or inserted.
422 my ( $trigger ) = $fulltext =~ m/^(DATA TUPLE: \d+ fields;\n.*)$/m;
423 if ( $trigger ) {
424 $section->{'trigger'} = parse_innodb_record_dump( $trigger, $complete, $debug );
425 }
426
427 # Certain data may not be present. Make them '' if not present.
428 map { $section->{$_} ||= "" }
429 qw(child_index fk_name col_name parent_table parent_col);
430}
431
432# There are new-style and old-style record formats. See rem/rem0rec.c
433# TODO: write some tests for this
434sub parse_innodb_record_dump {
435 my ( $dump, $complete, $debug ) = @_;
436 return undef unless $dump;
437
438 my $result = {};
439
440 if ( $dump =~ m/PHYSICAL RECORD/ ) {
441 my $style = $dump =~ m/compact format/ ? 'new' : 'old';
442 $result->{'style'} = $style;
443
444 # This is a new-style record.
445 if ( $style eq 'new' ) {
446 @{$result}{qw( heap_no type num_fields info_bits )}
447 = $dump
448 =~ m/^(?:Record lock, heap no $d )?([A-Z ]+): n_fields $d; compact format; info bits $d$/m;
449 }
450
451 # OK, it's old-style. Unfortunately there are variations here too.
452 elsif ( $dump =~ m/-byte offs / ) {
453 # Older-old style.
454 @{$result}{qw( heap_no type num_fields byte_offset info_bits )}
455 = $dump
456 =~ m/^(?:Record lock, heap no $d )?([A-Z ]+): n_fields $d; $d-byte offs [A-Z]+; info bits $d$/m;
457 if ( $dump !~ m/-byte offs TRUE/ ) {
458 $result->{'byte_offset'} = 0;
459 }
460 }
461 else {
462 # Newer-old style.
463 @{$result}{qw( heap_no type num_fields byte_offset info_bits )}
464 = $dump
465 =~ m/^(?:Record lock, heap no $d )?([A-Z ]+): n_fields $d; $d-byte offsets; info bits $d$/m;
466 }
467
468 }
469 else {
470 $result->{'style'} = 'tuple';
471 @{$result}{qw( type num_fields )}
472 = $dump =~ m/^(DATA TUPLE): $d fields;$/m;
473 }
474
475 # Fill in default values for things that couldn't be parsed.
476 map { $result->{$_} ||= 0 }
477 qw(heap_no num_fields byte_offset info_bits);
478 map { $result->{$_} ||= '' }
479 qw(style type );
480
481 my @fields = $dump =~ m/ (\d+:.*?;?);(?=$| \d+:)/gm;
482 $result->{'fields'} = [ map { parse_field($_, $complete, $debug ) } @fields ];
483
484 return $result;
485}
486
487# New/old-style applies here. See rem/rem0rec.c
488# $text should not include the leading space or the second trailing semicolon.
489sub parse_field {
490 my ( $text, $complete, $debug ) = @_;
491
492 # Sample fields:
493 # '4: SQL NULL, size 4 '
494 # '1: len 6; hex 000000005601; asc V ;'
495 # '6: SQL NULL'
496 # '5: len 30; hex 687474703a2f2f7777772e737765657477617465722e636f6d2f73746f72; asc http://www.sweetwater.com/stor;...(truncated)'
497 my ( $id, $nullsize, $len, $hex, $asc, $truncated );
498 ( $id, $nullsize ) = $text =~ m/^$d: SQL NULL, size $d $/;
499 if ( !defined($id) ) {
500 ( $id ) = $text =~ m/^$d: SQL NULL$/;
501 }
502 if ( !defined($id) ) {
503 ( $id, $len, $hex, $asc, $truncated )
504 = $text =~ m/^$d: len $d; hex $h; asc (.*);(\.\.\.\(truncated\))?$/;
505 }
506
507 die "Could not parse this field: '$text'" unless defined $id;
508 return {
509 id => $id,
510 len => defined($len) ? $len : defined($nullsize) ? $nullsize : 0,
511 'hex' => defined($hex) ? $hex : '',
512 asc => defined($asc) ? $asc : '',
513 trunc => $truncated ? 1 : 0,
514 };
515
516}
517
518sub parse_dl_section {
519 my ( $dl, $complete, $debug, $full ) = @_;
520 return unless $dl;
521 my $fulltext = $dl->{'fulltext'};
522 return 0 unless $fulltext;
523
524 my ( $ts ) = $fulltext =~ m/^$s$/m;
525 return 0 unless $ts;
526
527 $dl->{'ts'} = [ parse_innodb_timestamp( $ts ) ];
528 $dl->{'timestring'} = ts_to_string($dl->{'ts'});
529 $dl->{'txns'} = {};
530
531 my @sections
532 = $fulltext
533 =~ m{
534 ^\*{3}\s([^\n]*) # *** (1) WAITING FOR THIS...
535 (.*?) # Followed by anything, non-greedy
536 (?=(?:^\*{3})|\z) # Followed by another three stars or EOF
537 }gmsx;
538
539
540 # Loop through each section. There are no assumptions about how many
541 # there are, who holds and wants what locks, and who gets rolled back.
542 while ( my ($header, $body) = splice(@sections, 0, 2) ) {
543 my ( $txn_id, $what ) = $header =~ m/^\($d\) (.*):$/;
544 next unless $txn_id;
545 $dl->{'txns'}->{$txn_id} ||= {};
546 my $txn = $dl->{'txns'}->{$txn_id};
547
548 if ( $what eq 'TRANSACTION' ) {
549 $txn->{'tx'} = parse_tx_text( $body, $complete, $debug, $full );
550 }
551 else {
552 push @{$txn->{'locks'}}, parse_innodb_record_locks( $body, $complete, $debug, $full );
553 }
554 }
555
556 @{ $dl }{ qw(rolled_back) }
557 = $fulltext =~ m/^\*\*\* WE ROLL BACK TRANSACTION \($d\)$/m;
558
559 # Make sure certain values aren't undef
560 map { $dl->{$_} ||= '' } qw(rolled_back);
561
562 delete $dl->{'fulltext'} unless $debug;
563 return 1;
564}
565
566sub parse_innodb_record_locks {
567 my ( $text, $complete, $debug, $full ) = @_;
568 my @result;
569
570 foreach my $lock ( $text =~ m/(^(?:RECORD|TABLE) LOCKS?.*$)/gm ) {
571 my $hash = {};
572 @{$hash}{ qw(lock_type space_id page_no n_bits index db table txn_id lock_mode) }
573 = $lock
574 =~ m{^(RECORD|TABLE) LOCKS? (?:space id $d page no $d n bits $d index `?$n`? of )?table `$n(?:/|`\.`)$n` trx id $t lock.mode (\S+)}m;
575 ( $hash->{'special'} )
576 = $lock =~ m/^(?:RECORD|TABLE) .*? locks (rec but not gap|gap before rec)/m;
577 $hash->{'insert_intention'}
578 = $lock =~ m/^(?:RECORD|TABLE) .*? insert intention/m ? 1 : 0;
579 $hash->{'waiting'}
580 = $lock =~ m/^(?:RECORD|TABLE) .*? waiting/m ? 1 : 0;
581
582 # Some things may not be in the text, so make sure they are not
583 # undef.
584 map { $hash->{$_} ||= 0 } qw(n_bits page_no space_id);
585 map { $hash->{$_} ||= "" } qw(index special);
586 push @result, $hash;
587 }
588
589 return @result;
590}
591
592sub parse_tx_text {
593 my ( $txn, $complete, $debug, $full ) = @_;
594
595 my ( $txn_id, $txn_status, $active_secs, $proc_no, $os_thread_id )
596 = $txn
597 =~ m/^(?:---)?TRANSACTION $t, (\D*?)(?: $d sec)?, (?:process no $d, )?OS thread id $d/m;
598 my ( $thread_status, $thread_decl_inside )
599 = $txn
600 =~ m/OS thread id \d+(?: ([^,]+?))?(?:, thread declared inside InnoDB $d)?$/m;
601
602 # Parsing the line that begins 'MySQL thread id' is complicated. The only
603 # thing always in the line is the thread and query id. See function
604 # innobase_mysql_print_thd in InnoDB source file sql/ha_innodb.cc.
605 my ( $thread_line ) = $txn =~ m/^(MySQL thread id .*)$/m;
606 my ( $mysql_thread_id, $query_id, $hostname, $ip, $user, $query_status );
607
608 if ( $thread_line ) {
609 # These parts can always be gotten.
610 ( $mysql_thread_id, $query_id ) = $thread_line =~ m/^MySQL thread id $d, query id $d/m;
611
612 # If it's a master/slave thread, "Has (read|sent) all" may be the thread's
613 # proc_info. In these cases, there won't be any host/ip/user info
614 ( $query_status ) = $thread_line =~ m/(Has (?:read|sent) all .*$)/m;
615 if ( defined($query_status) ) {
616 $user = 'system user';
617 }
618
619 # It may be the case that the query id is the last thing in the line.
620 elsif ( $thread_line =~ m/query id \d+ / ) {
621 # The IP address is the only non-word thing left, so it's the most
622 # useful marker for where I have to start guessing.
623 ( $hostname, $ip ) = $thread_line =~ m/query id \d+(?: ([A-Za-z]\S+))? $i/m;
624 if ( defined $ip ) {
625 ( $user, $query_status ) = $thread_line =~ m/$ip $w(?: (.*))?$/;
626 }
627 else { # OK, there wasn't an IP address.
628 # There might not be ANYTHING except the query status.
629 ( $query_status ) = $thread_line =~ m/query id \d+ (.*)$/;
630 if ( $query_status !~ m/^\w+ing/ && !exists($is_proc_info{$query_status}) ) {
631 # The remaining tokens are, in order: hostname, user, query_status.
632 # It's basically impossible to know which is which.
633 ( $hostname, $user, $query_status ) = $thread_line
634 =~ m/query id \d+(?: ([A-Za-z]\S+))?(?: $w(?: (.*))?)?$/m;
635 }
636 else {
637 $user = 'system user';
638 }
639 }
640 }
641 }
642
643 my ( $lock_wait_status, $lock_structs, $heap_size, $row_locks, $undo_log_entries )
644 = $txn
645 =~ m/^(?:(\D*) )?$d lock struct\(s\), heap size $d(?:, $d row lock\(s\))?(?:, undo log entries $d)?$/m;
646 my ( $lock_wait_time )
647 = $txn
648 =~ m/^------- TRX HAS BEEN WAITING $d SEC/m;
649
650 my $locks;
651 # If the transaction has locks, grab the locks.
652 if ( $txn =~ m/^TABLE LOCK|RECORD LOCKS/ ) {
653 $locks = [parse_innodb_record_locks($txn, $complete, $debug, $full)];
654 }
655
656 my ( $tables_in_use, $tables_locked )
657 = $txn
658 =~ m/^mysql tables in use $d, locked $d$/m;
659 my ( $txn_doesnt_see_ge, $txn_sees_lt )
660 = $txn
661 =~ m/^Trx read view will not see trx with id >= $t, sees < $t$/m;
662 my $has_read_view = defined($txn_doesnt_see_ge);
663 # Only a certain number of bytes of the query text are included here, at least
664 # under some circumstances. Some versions include 300, some 600.
665 my ( $query_text )
666 = $txn
667 =~ m{
668 ^MySQL\sthread\sid\s[^\n]+\n # This comes before the query text
669 (.*?) # The query text
670 (?= # Followed by any of...
671 ^Trx\sread\sview
672 |^-------\sTRX\sHAS\sBEEN\sWAITING
673 |^TABLE\sLOCK
674 |^RECORD\sLOCKS\sspace\sid
675 |^(?:---)?TRANSACTION
676 |^\*\*\*\s\(\d\)
677 |\Z
678 )
679 }xms;
680 if ( $query_text ) {
681 $query_text =~ s/\s+$//;
682 }
683 else {
684 $query_text = '';
685 }
686
687 my %stuff = (
688 active_secs => $active_secs,
689 has_read_view => $has_read_view,
690 heap_size => $heap_size,
691 hostname => $hostname,
692 ip => $ip,
693 lock_structs => $lock_structs,
694 lock_wait_status => $lock_wait_status,
695 lock_wait_time => $lock_wait_time,
696 mysql_thread_id => $mysql_thread_id,
697 os_thread_id => $os_thread_id,
698 proc_no => $proc_no,
699 query_id => $query_id,
700 query_status => $query_status,
701 query_text => $query_text,
702 row_locks => $row_locks,
703 tables_in_use => $tables_in_use,
704 tables_locked => $tables_locked,
705 thread_decl_inside => $thread_decl_inside,
706 thread_status => $thread_status,
707 txn_doesnt_see_ge => $txn_doesnt_see_ge,
708 txn_id => $txn_id,
709 txn_sees_lt => $txn_sees_lt,
710 txn_status => $txn_status,
711 undo_log_entries => $undo_log_entries,
712 user => $user,
713 );
714 $stuff{'fulltext'} = $txn if $debug;
715 $stuff{'locks'} = $locks if $locks;
716
717 # Some things may not be in the txn text, so make sure they are not
718 # undef.
719 map { $stuff{$_} ||= 0 } qw(active_secs heap_size lock_structs
720 tables_in_use undo_log_entries tables_locked has_read_view
721 thread_decl_inside lock_wait_time proc_no row_locks);
722 map { $stuff{$_} ||= "" } qw(thread_status txn_doesnt_see_ge
723 txn_sees_lt query_status ip query_text lock_wait_status user);
724 $stuff{'hostname'} ||= $stuff{'ip'};
725
726 return \%stuff;
727}
728
729sub parse_tx_section {
730 my ( $section, $complete, $debug, $full ) = @_;
731 return unless $section && $section->{'fulltext'};
732 my $fulltext = $section->{'fulltext'};
733 $section->{'transactions'} = [];
734
735 # Handle the individual transactions
736 my @transactions = $fulltext =~ m/(---TRANSACTION \d.*?)(?=\n---TRANSACTION|$)/gs;
737 foreach my $txn ( @transactions ) {
738 my $stuff = parse_tx_text( $txn, $complete, $debug, $full );
739 delete $stuff->{'fulltext'} unless $debug;
740 push @{$section->{'transactions'}}, $stuff;
741 }
742
743 # Handle the general info
744 @{$section}{ 'trx_id_counter' }
745 = $fulltext =~ m/^Trx id counter $t$/m;
746 @{$section}{ 'purge_done_for', 'purge_undo_for' }
747 = $fulltext =~ m/^Purge done for trx's n:o < $t undo n:o < $t$/m;
748 @{$section}{ 'history_list_len' } # This isn't present in some 4.x versions
749 = $fulltext =~ m/^History list length $d$/m;
750 @{$section}{ 'num_lock_structs' }
751 = $fulltext =~ m/^Total number of lock structs in row lock hash table $d$/m;
752 @{$section}{ 'is_truncated' }
753 = $fulltext =~ m/^\.\.\. truncated\.\.\.$/m ? 1 : 0;
754
755 # Fill in things that might not be present
756 foreach ( qw(history_list_len) ) {
757 $section->{$_} ||= 0;
758 }
759
760 delete $section->{'fulltext'} unless $debug;
761 return 1;
762}
763
764# I've read the source for this section.
765sub parse_ro_section {
766 my ( $section, $complete, $debug, $full ) = @_;
767 return unless $section && $section->{'fulltext'};
768 my $fulltext = $section->{'fulltext'};
769
770 # Grab the info
771 @{$section}{ 'queries_inside', 'queries_in_queue' }
772 = $fulltext =~ m/^$d queries inside InnoDB, $d queries in queue$/m;
773 ( $section->{ 'read_views_open' } )
774 = $fulltext =~ m/^$d read views open inside InnoDB$/m;
775 ( $section->{ 'n_reserved_extents' } )
776 = $fulltext =~ m/^$d tablespace extents now reserved for B-tree/m;
777 @{$section}{ 'main_thread_proc_no', 'main_thread_id', 'main_thread_state' }
778 = $fulltext =~ m/^Main thread (?:process no. $d, )?id $d, state: (.*)$/m;
779 @{$section}{ 'num_rows_ins', 'num_rows_upd', 'num_rows_del', 'num_rows_read' }
780 = $fulltext =~ m/^Number of rows inserted $d, updated $d, deleted $d, read $d$/m;
781 @{$section}{ 'ins_sec', 'upd_sec', 'del_sec', 'read_sec' }
782 = $fulltext =~ m#^$f inserts/s, $f updates/s, $f deletes/s, $f reads/s$#m;
783 $section->{'main_thread_proc_no'} ||= 0;
784
785 map { $section->{$_} ||= 0 } qw(read_views_open n_reserved_extents);
786 delete $section->{'fulltext'} unless $debug;
787 return 1;
788}
789
790sub parse_lg_section {
791 my ( $section, $complete, $debug, $full ) = @_;
792 return unless $section;
793 my $fulltext = $section->{'fulltext'};
794
795 # Grab the info
796 ( $section->{ 'log_seq_no' } )
797 = $fulltext =~ m/Log sequence number \s*(\d.*)$/m;
798 ( $section->{ 'log_flushed_to' } )
799 = $fulltext =~ m/Log flushed up to \s*(\d.*)$/m;
800 ( $section->{ 'last_chkp' } )
801 = $fulltext =~ m/Last checkpoint at \s*(\d.*)$/m;
802 @{$section}{ 'pending_log_writes', 'pending_chkp_writes' }
803 = $fulltext =~ m/$d pending log writes, $d pending chkp writes/;
804 @{$section}{ 'log_ios_done', 'log_ios_s' }
805 = $fulltext =~ m#$d log i/o's done, $f log i/o's/second#;
806
807 delete $section->{'fulltext'} unless $debug;
808 return 1;
809}
810
811sub parse_ib_section {
812 my ( $section, $complete, $debug, $full ) = @_;
813 return unless $section && $section->{'fulltext'};
814 my $fulltext = $section->{'fulltext'};
815
816 # Some servers will output ibuf information for tablespace 0, as though there
817 # might be many tablespaces with insert buffers. (In practice I believe
818 # the source code shows there will only ever be one). I have to parse both
819 # cases here, but I assume there will only be one.
820 @{$section}{ 'size', 'free_list_len', 'seg_size' }
821 = $fulltext =~ m/^Ibuf(?: for space 0)?: size $d, free list len $d, seg size $d,$/m;
822 @{$section}{ 'inserts', 'merged_recs', 'merges' }
823 = $fulltext =~ m/^$d inserts, $d merged recs, $d merges$/m;
824
825 @{$section}{ 'hash_table_size', 'used_cells', 'bufs_in_node_heap' }
826 = $fulltext =~ m/^Hash table size $d, used cells $d, node heap has $d buffer\(s\)$/m;
827 @{$section}{ 'hash_searches_s', 'non_hash_searches_s' }
828 = $fulltext =~ m{^$f hash searches/s, $f non-hash searches/s$}m;
829
830 delete $section->{'fulltext'} unless $debug;
831 return 1;
832}
833
834sub parse_wait_array {
835 my ( $text, $complete, $debug, $full ) = @_;
836 my %result;
837
838 @result{ qw(thread waited_at_filename waited_at_line waited_secs) }
839 = $text =~ m/^--Thread $d has waited at $fl for $f seconds/m;
840
841 # Depending on whether it's a SYNC_MUTEX,RW_LOCK_EX,RW_LOCK_SHARED,
842 # there will be different text output
843 if ( $text =~ m/^Mutex at/m ) {
844 $result{'request_type'} = 'M';
845 @result{ qw( lock_mem_addr lock_cfile_name lock_cline lock_var) }
846 = $text =~ m/^Mutex at $h created file $fl, lock var $d$/m;
847 @result{ qw( waiters_flag )}
848 = $text =~ m/^waiters flag $d$/m;
849 }
850 else {
851 @result{ qw( request_type lock_mem_addr lock_cfile_name lock_cline) }
852 = $text =~ m/^(.)-lock on RW-latch at $h created in file $fl$/m;
853 @result{ qw( writer_thread writer_lock_mode ) }
854 = $text =~ m/^a writer \(thread id $d\) has reserved it in mode (.*)$/m;
855 @result{ qw( num_readers waiters_flag )}
856 = $text =~ m/^number of readers $d, waiters flag $d$/m;
857 @result{ qw(last_s_file_name last_s_line ) }
858 = $text =~ m/Last time read locked in file $fl$/m;
859 @result{ qw(last_x_file_name last_x_line ) }
860 = $text =~ m/Last time write locked in file $fl$/m;
861 }
862
863 $result{'cell_waiting'} = $text =~ m/^wait has ended$/m ? 0 : 1;
864 $result{'cell_event_set'} = $text =~ m/^wait is ending$/m ? 1 : 0;
865
866 # Because there are two code paths, some things won't get set.
867 map { $result{$_} ||= '' }
868 qw(last_s_file_name last_x_file_name writer_lock_mode);
869 map { $result{$_} ||= 0 }
870 qw(num_readers lock_var last_s_line last_x_line writer_thread);
871
872 return \%result;
873}
874
875sub parse_sm_section {
876 my ( $section, $complete, $debug, $full ) = @_;
877 return 0 unless $section && $section->{'fulltext'};
878 my $fulltext = $section->{'fulltext'};
879
880 # Grab the info
881 @{$section}{ 'reservation_count', 'signal_count' }
882 = $fulltext =~ m/^OS WAIT ARRAY INFO: reservation count $d, signal count $d$/m;
883 @{$section}{ 'mutex_spin_waits', 'mutex_spin_rounds', 'mutex_os_waits' }
884 = $fulltext =~ m/^Mutex spin waits $d, rounds $d, OS waits $d$/m;
885 @{$section}{ 'rw_shared_spins', 'rw_shared_os_waits', 'rw_excl_spins', 'rw_excl_os_waits' }
886 = $fulltext =~ m/^RW-shared spins $d, OS waits $d; RW-excl spins $d, OS waits $d$/m;
887
888 # Look for info on waits.
889 my @waits = $fulltext =~ m/^(--Thread.*?)^(?=Mutex spin|--Thread)/gms;
890 $section->{'waits'} = [ map { parse_wait_array($_, $complete, $debug) } @waits ];
891 $section->{'wait_array_size'} = scalar(@waits);
892
893 delete $section->{'fulltext'} unless $debug;
894 return 1;
895}
896
897# I've read the source for this section.
898sub parse_bp_section {
899 my ( $section, $complete, $debug, $full ) = @_;
900 return unless $section && $section->{'fulltext'};
901 my $fulltext = $section->{'fulltext'};
902
903 # Grab the info
904 @{$section}{ 'total_mem_alloc', 'add_pool_alloc' }
905 = $fulltext =~ m/^Total memory allocated $d; in additional pool allocated $d$/m;
906 @{$section}{'dict_mem_alloc'} = $fulltext =~ m/Dictionary memory allocated $d/;
907 @{$section}{'awe_mem_alloc'} = $fulltext =~ m/$d MB of AWE memory/;
908 @{$section}{'buf_pool_size'} = $fulltext =~ m/^Buffer pool size\s*$d$/m;
909 @{$section}{'buf_free'} = $fulltext =~ m/^Free buffers\s*$d$/m;
910 @{$section}{'pages_total'} = $fulltext =~ m/^Database pages\s*$d$/m;
911 @{$section}{'pages_modified'} = $fulltext =~ m/^Modified db pages\s*$d$/m;
912 @{$section}{'pages_read', 'pages_created', 'pages_written'}
913 = $fulltext =~ m/^Pages read $d, created $d, written $d$/m;
914 @{$section}{'page_reads_sec', 'page_creates_sec', 'page_writes_sec'}
915 = $fulltext =~ m{^$f reads/s, $f creates/s, $f writes/s$}m;
916 @{$section}{'buf_pool_hits', 'buf_pool_reads'}
917 = $fulltext =~ m{Buffer pool hit rate $d / $d$}m;
918 if ($fulltext =~ m/^No buffer pool page gets since the last printout$/m) {
919 @{$section}{'buf_pool_hits', 'buf_pool_reads'} = (0, 0);
920 @{$section}{'buf_pool_hit_rate'} = '--';
921 }
922 else {
923 @{$section}{'buf_pool_hit_rate'}
924 = $fulltext =~ m{Buffer pool hit rate (\d+ / \d+)$}m;
925 }
926 @{$section}{'reads_pending'} = $fulltext =~ m/^Pending reads $d/m;
927 @{$section}{'writes_pending_lru', 'writes_pending_flush_list', 'writes_pending_single_page' }
928 = $fulltext =~ m/^Pending writes: LRU $d, flush list $d, single page $d$/m;
929
930 map { $section->{$_} ||= 0 }
931 qw(writes_pending_lru writes_pending_flush_list writes_pending_single_page
932 awe_mem_alloc dict_mem_alloc);
933 @{$section}{'writes_pending'} = List::Util::sum(
934 @{$section}{ qw(writes_pending_lru writes_pending_flush_list writes_pending_single_page) });
935
936 delete $section->{'fulltext'} unless $debug;
937 return 1;
938}
939
940# I've read the source for this.
941sub parse_io_section {
942 my ( $section, $complete, $debug, $full ) = @_;
943 return unless $section && $section->{'fulltext'};
944 my $fulltext = $section->{'fulltext'};
945 $section->{'threads'} = {};
946
947 # Grab the I/O thread info
948 my @threads = $fulltext =~ m<^(I/O thread \d+ .*)$>gm;
949 foreach my $thread (@threads) {
950 my ( $tid, $state, $purpose, $event_set )
951 = $thread =~ m{I/O thread $d state: (.+?) \((.*)\)(?: ev set)?$}m;
952 if ( defined $tid ) {
953 $section->{'threads'}->{$tid} = {
954 thread => $tid,
955 state => $state,
956 purpose => $purpose,
957 event_set => $event_set ? 1 : 0,
958 };
959 }
960 }
961
962 # Grab the reads/writes/flushes info
963 @{$section}{ 'pending_normal_aio_reads', 'pending_aio_writes' }
964 = $fulltext =~ m/^Pending normal aio reads: $d, aio writes: $d,$/m;
965 @{$section}{ 'pending_ibuf_aio_reads', 'pending_log_ios', 'pending_sync_ios' }
966 = $fulltext =~ m{^ ibuf aio reads: $d, log i/o's: $d, sync i/o's: $d$}m;
967 @{$section}{ 'flush_type', 'pending_log_flushes', 'pending_buffer_pool_flushes' }
968 = $fulltext =~ m/^Pending flushes \($w\) log: $d; buffer pool: $d$/m;
969 @{$section}{ 'os_file_reads', 'os_file_writes', 'os_fsyncs' }
970 = $fulltext =~ m/^$d OS file reads, $d OS file writes, $d OS fsyncs$/m;
971 @{$section}{ 'reads_s', 'avg_bytes_s', 'writes_s', 'fsyncs_s' }
972 = $fulltext =~ m{^$f reads/s, $d avg bytes/read, $f writes/s, $f fsyncs/s$}m;
973 @{$section}{ 'pending_preads', 'pending_pwrites' }
974 = $fulltext =~ m/$d pending preads, $d pending pwrites$/m;
975 @{$section}{ 'pending_preads', 'pending_pwrites' } = (0, 0)
976 unless defined($section->{'pending_preads'});
977
978 delete $section->{'fulltext'} unless $debug;
979 return 1;
980}
981
982sub _debug {
983 my ( $debug, $msg ) = @_;
984 if ( $debug ) {
985 die $msg;
986 }
987 else {
988 warn $msg;
989 }
990 return 1;
991}
992
9931;
994
995# end_of_package
996# ############################################################################
997# Perldoc section. I put this last as per the Dog book.
998# ############################################################################
999=pod
1000
1001=head1 NAME
1002
1003InnoDBParser - Parse InnoDB monitor text.
1004
1005=head1 DESCRIPTION
1006
1007InnoDBParser tries to parse the output of the InnoDB monitor. One way to get
1008this output is to connect to a MySQL server and issue the command SHOW ENGINE
1009INNODB STATUS (omit 'ENGINE' on earlier versions of MySQL). The goal is to
1010turn text into data that something else (e.g. innotop) can use.
1011
1012The output comes from all over, but the place to start in the source is
1013innobase/srv/srv0srv.c.
1014
1015=head1 SYNOPSIS
1016
1017 use InnoDBParser;
1018 use DBI;
1019
1020 # Get the status text.
1021 my $dbh = DBI->connect(
1022 "DBI::mysql:test;host=localhost",
1023 'user',
1024 'password'
1025 );
1026 my $query = 'SHOW /*!5 ENGINE */ INNODB STATUS';
1027 my $text = $dbh->selectcol_arrayref($query)->[0];
1028
1029 # 1 or 0
1030 my $debug = 1;
1031
1032 # Choose sections of the monitor text you want. Possible values:
1033 # TRANSACTIONS => tx
1034 # BUFFER POOL AND MEMORY => bp
1035 # SEMAPHORES => sm
1036 # LOG => lg
1037 # ROW OPERATIONS => ro
1038 # INSERT BUFFER AND ADAPTIVE HASH INDEX => ib
1039 # FILE I/O => io
1040 # LATEST DETECTED DEADLOCK => dl
1041 # LATEST FOREIGN KEY ERROR => fk
1042
1043 my $required_sections = {
1044 tx => 1,
1045 };
1046
1047 # Parse the status text.
1048 my $parser = InnoDBParser->new;
1049 $innodb_status = $parser->parse_status_text(
1050 $text,
1051 $debug,
1052 # Omit the following parameter to get all sections.
1053 $required_sections,
1054 );
1055
1056=head1 COPYRIGHT, LICENSE AND WARRANTY
1057
1058This package is copyright (c) 2006 Baron Schwartz, baron at xaprb dot com.
1059Feedback and improvements are gratefully received.
1060
1061THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
1062WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
1063MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
1064
1065This program is free software; you can redistribute it and/or modify it under
1066the terms of the GNU General Public License as published by the Free Software
1067Foundation, version 2; OR the Perl Artistic License. On UNIX and similar
1068systems, you can issue `man perlgpl' or `man perlartistic' to read these
1069licenses.
1070
1071You should have received a copy of the GNU General Public License along with
1072this program; if not, write to the Free Software Foundation, Inc., 59 Temple
1073Place, Suite 330, Boston, MA 02111-1307 USA
1074
1075=head1 AUTHOR
1076
1077Baron Schwartz, baron at xaprb dot com.
1078
1079=head1 BUGS
1080
1081None known, but I bet there are some. The InnoDB monitor text wasn't really
1082designed to be parsable.
1083
1084=head1 SEE ALSO
1085
1086innotop - a program that can format the parsed status information for humans
1087to read and enjoy.
1088
1089=cut
10900
=== removed file 'build/debian/additions/innotop/changelog.innotop'
--- build/debian/additions/innotop/changelog.innotop 2010-05-31 13:57:21 +0000
+++ build/debian/additions/innotop/changelog.innotop 1970-01-01 00:00:00 +0000
@@ -1,318 +0,0 @@
1Changelog for innotop and InnoDBParser:
2
32007-11-09: version 1.6.0
4
5 * S mode crashed on non-numeric values.
6 * New user-defined columns crashed upon restart.
7 * Added --color option to control terminal coloring.
8
92007-09-18: version 1.5.2
10
11 * Added the ability to monitor InnoDB status from a file.
12 * Changed W mode to L mode; it monitors all locks, not just lock waits.
13
142007-09-16: version 1.5.1
15
16 * Added C (Command Summary) mode.
17 * Fixed a bug in the 'avg' aggregate function.
18
192007-09-10: version 1.5.0
20
21 Changes:
22 * Added plugin functionality.
23 * Added group-by functionality.
24 * Moved the configuration file to a directory.
25 * Enhanced filtering and sorting on pivoted tables.
26 * Many small bug fixes.
27
282007-07-16: version 1.4.3
29
30 Changes:
31 * Added standard --version command-line option
32 * Changed colors to cyan instead of blue; more visible on dark terminals.
33 * Added information to the filter-choosing dialog.
34 * Added column auto-completion when entering a filter expression.
35 * Changed Term::ReadKey from optional to mandatory.
36 * Clarified username in password prompting.
37 * Ten thousand words of documentation!
38
39 Bugs fixed:
40 * innotop crashed in W mode when InnoDB status data was truncated.
41 * innotop didn't display errors in tables if debug was enabled.
42 * The colored() subroutine wasn't being created in non-interactive mode.
43 * Don't prompt to save password except the first time.
44
452007-05-03: version 1.4.2
46
47 This version contains all changes to the trunk until revision 239; some
48 changes in revisions 240:250 are included.
49
50 MAJOR CHANGES:
51
52 * Quick-filters to easily filter any column in any display
53 * Compatibility with MySQL 3.23 through 6.0
54 * Improved error handling when a server is down, permissions denied, etc
55 * Use additional SHOW INNODB STATUS information in 5.1.x
56 * Make all modes use tables consistently, so they can all be edited,
57 filtered, colored and sorted consistently
58 * Combine V, G and S modes into S mode, with v, g, and s hot-keys
59 * Let DBD driver read MySQL option files; permit connections without
60 user/pass/etc
61 * Compile SQL-like expressions into Perl subroutines; eliminate need to
62 know Perl
63 * Do not save all config data to config file, only save user's customizations
64 * Rewritten and improved command-line option handling
65 * Added --count, --delay, and other command-line options to support
66 run-and-exit operation
67 * Improve built-in variable sets
68 * Improve help screen with three-part balanced-column layout
69 * Simplify table-editor and improve hotkey support
70 * Require Perl to have high-resolution time support (Time::HiRes)
71 * Help the user choose a query to analyze or kill
72 * Enable EXPLAIN, show-full-query in T mode just like Q mode
73 * Let data-extraction access current, previous and incremental data sets
74 all at once
75
76 MINOR CHANGES:
77
78 * Column stabilizing for Q mode
79 * New color rules for T, Q, W modes
80 * Apply slave I/O filter to Q mode
81 * Improve detection of server version and other meta-data
82 * Make connection timeout a config variable
83 * Improve cross-version-compatible SQL syntax
84 * Get some information from the DBD driver instead of asking MySQL for it
85 * Improved error messages
86 * Improve server group creation/editing
87 * Improve connection/thread killing
88 * Fix broken key bindings and restore previously mapped hot-keys for
89 choosing columns
90 * Some documentation updates (but not nearly enough)
91 * Allow the user to specify graphing char in S mode (formerly G mode)
92 * Allow easy switching between variable sets in S mode
93 * Bind 'n' key globally to choose the 'next' server connection
94 * Bind '%' key globally to filter displayed tables
95 * Allow aligning columns on the decimal place for easy readability
96 * Add hide_hdr config variable to hide column headers in tables
97 * Add a feature to smartly run PURGE MASTER LOGS in Replication mode
98 * Enable debug mode as a globally configurable variable
99 * Improve error messages when an expression or filter doesn't compile or has
100 a run-time error; die on error when debug is enabled
101 * Allow user-configurable delays after executing SQL (to let the server
102 settle down before taking another measurement)
103 * Add an expression to show how long until a transaction is finished
104 * Add skip_innodb as a global config variable
105 * Add '%' after percentages to help disambiguate (user-configurable)
106 * Add column to M mode to help see how fast slave is catching up to master
107
108 BUG FIXES:
109
110 * T and W modes had wrong value for wait_status column
111 * Error tracking on connections didn't reset when the connection recovered
112 * wait_timeout on connections couldn't be set before MySQL 4.0.3
113 * There was a crash on 3.23 when wiping deadlocks
114 * Lettercase changes in some result sets (SHOW MASTER/SLAVE STATUS) between
115 MySQL versions crashed innotop
116 * Inactive connections crashed innotop upon access to DBD driver
117 * set_precision did not respect user defaults for number of digits
118 * --inc command-line option could not be negated
119 * InnoDB status parsing was not always parsing all needed information
120 * S mode (formerly G mode) could crash trying to divide non-numeric data
121 * M table didn't show Slave_open_temp_tables variable; incorrect lettercase
122 * DBD drivers with broken AutoCommit would crash innotop
123 * Some key bindings had incorrect labels
124 * Some config-file loading routines could load data for things that didn't
125 exist
126 * Headers printed too often in S mode
127 * High-resolution time was not used even when the user had it
128 * Non-interactive mode printed blank lines sometimes
129 * Q-mode header and statusbar showed different QPS numbers
130 * Formulas for key-cache and query-cache hit ratios were wrong
131 * Mac OS "Darwin" machines were mis-identified as Microsoft Windows
132 * Some multiplications crashed when given undefined input
133 * The commify transformation did not check its input and could crash
134 * Specifying an invalid mode on the command line or config file could crash
135 innotop
136
1372007-03-29: version 1.4.1
138
139 * More tweaks to display of connection errors.
140 * Fixed a problem with skip-innodb in MySQL 5.1.
141 * Fix a bug with dead connections in single-connection mode.
142 * Fix a regex to allow parsing more data from truncated deadlocks.
143 * Don't load active cxns from the config file if the cxn isn't defined.
144
1452007-03-03: version 1.4.0
146
147 * Further tweak error handling and display of connection errors
148 * More centralization of querying
149 * Fix forking so it doesn't kill all database connections
150 * Allow user to run innotop without permissions for GLOBAL variables and status
151
1522007-02-11: version 1.3.6
153
154 * Handle some connection failures so innotop doesn't crash because of one server.
155 * Enable incremental display in more modes.
156 * Tweaks to colorizing, color editor, and default color rules.
157 * Tweaks to default sorting rules.
158 * Use prepared statements for efficiency.
159 * Bug fixes and code cleanups.
160 * Data storage is keyed on clock ticks now.
161
1622007-02-03: version 1.3.5
163
164 * Bug fixes.
165 * More tools for editing configuration from within innotop.
166 * Filters and transformations are constrained to valid values.
167 * Support for colorizing rows.
168 * Sorting by multiple columns.
169 * Compress headers when display is very wide.
170 * Stabilize and limit column widths.
171 * Check config file formats when upgrading so upgrades go smoothly.
172 * Make D mode handle many connections at once.
173 * Extract simple expressions from data sets in column src property.
174 This makes innotop more awk-ish.
175
1762007-01-16: version 1.3
177
178 * Readline support.
179 * Can be used unattended, or in a pipe-and-filter mode
180 where it outputs tab-separated data to standard output.
181 * You can specify a config file on the command line.
182 Config files can be marked read-only.
183 * Monitor multiple servers simultaneously.
184 * Server groups to help manage many servers conveniently.
185 * Monitor master/slave status, and control slaves.
186 * Columns can have user-defined expressions as their data sources.
187 * Better configuration tools.
188 * InnoDB status information is merged into SHOW VARIABLES and
189 SHOW STATUS information, so you can access it all together.
190 * High-precision time support in more places.
191 * Lots of tweaks to make things display more readably and compactly.
192 * Column transformations and filters.
193
1942007-01-16: version 1.0.1
195 * NOTE: innotop is now hosted at Sourceforge, in Subversion not CVS.
196 The new project homepage is http://sourceforge.net/projects/innotop/
197 * Tweak default T/Q mode sort columns to match what people expect.
198 * Fix broken InnoDBParser.pm documentation (and hence man page).
199
2002007-01-06: version 1.0
201 * NOTE: innotop is now hosted at Sourceforge, in Subversion not CVS.
202 The new project homepage is http://sourceforge.net/projects/innotop/
203 * Prevent control characters from freaking terminal out.
204 * Set timeout to keep busy servers from closing connection.
205 * There is only one InnoDB insert buffer.
206 * Make licenses clear and consistent.
207
2082006-11-14: innotop 0.1.160, InnoDBParser version 1.69
209 * Support for ANSI color on Microsoft Windows (more readable, compact
210 display; thanks Gisbert W. Selke).
211 * Better handling of $ENV{HOME} on Windows.
212 * Added a LICENSE file to the package as per Gentoo bug:
213 http://bugs.gentoo.org/show_bug.cgi?id=147600
214
2152006-11-11: innotop 0.1.157, InnoDBParser version 1.69
216 * Add Microsoft Windows support.
217
2182006-10-19: innotop 0.1.154, InnoDBParser version 1.69
219 * Add O (Open Tables) mode
220 * Add some more checks to handle incomplete InnoDB status information
221
2222006-09-30: innotop 0.1.152, InnoDBParser version 1.69
223 * Figured out what was wrong with package $VERSION variable: it wasn't
224 after the package declaration!
225
2262006-09-28: innotop 0.1.152, InnoDBParser version 1.67
227 * Make more efforts towards crash-resistance and tolerance of completely
228 messed-up inputs. If innotop itself is broken, it is now much harder to
229 tell, because it just keeps on running without complaining.
230 * Fix a small bug parsing out some information and displaying it.
231
2322006-09-05: innotop 0.1.149, InnoDBParser version 1.64
233 * Try to find and eliminate any parsing code that assumes pattern matches
234 will succeed.
235
2362006-09-05: innotop 0.1.149, InnoDBParser version 1.62
237 * Make innotop crash-resistant, so I can declare it STABLE finally.
238 * Instead of using SQL conditional comments, detect MySQL version.
239
2402006-08-22: innotop 0.1.147, InnoDBParser version 1.60
241 * Fix some innotop bugs with undefined values, bad formatting etc.
242
2432006-08-19: innotop 0.1.146, InnoDBParser version 1.60
244 * Make innotop handle some unexpected NULL values in Q mode.
245 * Add OS wait information to W mode, so it is now "everything that waits."
246 * Center section captions better.
247 * Make R mode more readable and compact.
248 * Make InnoDBParser parse lock waits even when they've been waiting 0 secs.
249
2502006-08-12: innotop 0.1.139, InnoDBParser version 1.59
251 * Add more documentation
252 * Tweak V mode to show more info in less space.
253 * Fix a bug in G mode.
254
2552006-08-10: innotop 0.1.132, InnoDBParser version 1.58
256 * Handle yet more types of FK error... it will never end!
257 * Handle some special cases when DEADLOCK info truncated
258 * Add a bit more FK info to F mode in innotop
259 * More tests added to the test suite
260
2612006-08-07: innotop 0.1.131, InnoDBParser version 1.55
262 * Fix another issue with configuration
263 * Handle another type of FK error
264
2652006-08-03: innotop 0.1.130, InnoDBParser version 1.54
266 * Fix an issue loading config file
267 * Add heap_no to 'D' (InnoDB Deadlock) mode to ease deadlock debugging.
268
2692006-08-02: innotop 0.1.128, InnoDBParser version 1.54
270 * Parse lock wait information from the TRANSACTION section.
271 * Even more OS-specific parsing... pain in the butt...
272 * Add 'W' (InnoDB Lock Wait) mode.
273 * Fix some minor display issues with statusbar.
274
2752006-08-02: innotop 0.1.125, InnoDBParser version 1.50
276 * Don't try to get references to Perl built-in functions like time()
277 * Handle more OS-specific variations of InnoDB status text
278 * Add some more information to various places in innotop
279
2802006-08-01: innotop 0.1.123, InnoDBParser version 1.47
281
282 * Enhance S and G modes: clear screen and re-print headers
283 * Don't crash when deadlock data is truncated
284 * Make Analyze mode say how to get back to whatever you came from
285 * Display 'nothing to display' when there is nothing
286 * Add ability to read InnoDB status text from a file (mostly helps test)
287 * Add table of Wait Array Information in Row Op/Semaphore mode
288 * Add table of lock information in InnoDB deadlock mode
289 * Ensure new features in upgrades don't get masked by existing config files
290 * Tweak default column choices for T mode
291 * Enhance foreign key parsing
292 * Enhance physical record and data tuple parsing
293 * Enhance lock parsing (handle old-style and new-style formats)
294
2952006-07-24: innotop 0.1.112, InnoDBParser version 1.36
296
297 * InnoDBParser enhancements for FK error messages.
298 * A fix to innotop to prevent it from crashing while trying to display a FK
299 error message.
300 * Some minor cosmetic changes to number formatting in innotop.
301
3022006-07-22: innotop 0.1.106, InnoDBParser version 1.35
303
304 * InnoDBParser is much more complete and accurate.
305 * Tons of bug fixes.
306 * Add partitions to EXPLAIN mode.
307 * Enhance Q mode header, add T mode header.
308 * Share some configuration variables across modes.
309 * Add formatted time columns to Q, T modes.
310 * Add command-line argument parsing.
311 * Turn off echo when asking for password.
312 * Add option to specify port when connecting.
313 * Let display-optimized-query display multiple notes.
314 * Lots of small improvements, such as showing more info in statusbar.
315
3162006-07-02: innotop 0.1.74, InnoDBParser version 1.24
317
318 * Initial release for public consumption.
3190
=== removed file 'build/debian/additions/innotop/innotop'
--- build/debian/additions/innotop/innotop 2010-05-31 13:57:21 +0000
+++ build/debian/additions/innotop/innotop 1970-01-01 00:00:00 +0000
@@ -1,9485 +0,0 @@
1#!/usr/bin/perl
2
3# vim: tw=160:nowrap:expandtab:tabstop=3:shiftwidth=3:softtabstop=3
4
5use strict;
6use warnings FATAL => 'all';
7use sigtrap qw(handler finish untrapped normal-signals);
8
9use Data::Dumper;
10use DBI;
11use English qw(-no_match_vars);
12use File::Basename qw(dirname);
13use Getopt::Long;
14use List::Util qw(max min maxstr sum);
15use InnoDBParser;
16use POSIX qw(ceil);
17use Time::HiRes qw(time sleep);
18use Term::ReadKey qw(ReadMode ReadKey);
19
20# Version, license and warranty information. {{{1
21# ###########################################################################
22our $VERSION = '1.6.0';
23our $SVN_REV = sprintf("%d", q$Revision: 383 $ =~ m/(\d+)/g);
24our $SVN_URL = sprintf("%s", q$URL: https://innotop.svn.sourceforge.net/svnroot/innotop/trunk/innotop $ =~ m$svnroot/innotop/(\S+)$g);
25
26my $innotop_license = <<"LICENSE";
27
28This is innotop version $VERSION, a MySQL and InnoDB monitor.
29
30This program is copyright (c) 2006 Baron Schwartz.
31Feedback and improvements are welcome.
32
33THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
34WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
35MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
36
37This program is free software; you can redistribute it and/or modify it under
38the terms of the GNU General Public License as published by the Free Software
39Foundation, version 2; OR the Perl Artistic License. On UNIX and similar
40systems, you can issue `man perlgpl' or `man perlartistic' to read these
41licenses.
42
43You should have received a copy of the GNU General Public License along with
44this program; if not, write to the Free Software Foundation, Inc., 59 Temple
45Place, Suite 330, Boston, MA 02111-1307 USA.
46LICENSE
47
48# Configuration information and global setup {{{1
49# ###########################################################################
50
51# Really, really, super-global variables.
52my @config_versions = (
53 "000-000-000", "001-003-000", # config file was one big name-value hash.
54 "001-003-000", "001-004-002", # config file contained non-user-defined stuff.
55);
56
57my $clear_screen_sub;
58
59# This defines expected properties and defaults for the column definitions that
60# eventually end up in tbl_meta.
61my %col_props = (
62 hdr => '',
63 just => '-',
64 dec => 0, # Whether to align the column on the decimal point
65 num => 0,
66 label => '',
67 user => 0,
68 src => '',
69 tbl => '', # Helps when writing/reading custom columns in config files
70 minw => 0,
71 maxw => 0,
72 trans => [],
73 agg => 'first', # Aggregate function
74 aggonly => 0, # Whether to show only when tbl_meta->{aggregate} is true
75);
76
77# Actual DBI connections to MySQL servers.
78my %dbhs;
79
80# Command-line parameters {{{2
81# ###########################################################################
82
83my @opt_spec = (
84 { s => 'help', d => 'Show this help message' },
85 { s => 'color|C!', d => 'Use terminal coloring (default)', c => 'color' },
86 { s => 'config|c=s', d => 'Config file to read' },
87 { s => 'nonint|n', d => 'Non-interactive, output tab-separated fields' },
88 { s => 'count=i', d => 'Number of updates before exiting' },
89 { s => 'delay|d=f', d => 'Delay between updates in seconds', c => 'interval' },
90 { s => 'mode|m=s', d => 'Operating mode to start in', c => 'mode' },
91 { s => 'inc|i!', d => 'Measure incremental differences', c => 'status_inc' },
92 { s => 'version', d => 'Output version information and exit' },
93);
94
95# This is the container for the command-line options' values to be stored in
96# after processing. Initial values are defaults.
97my %opts = (
98 n => !( -t STDIN && -t STDOUT ), # If in/out aren't to terminals, we're interactive
99);
100# Post-process...
101my %opt_seen;
102foreach my $spec ( @opt_spec ) {
103 my ( $long, $short ) = $spec->{s} =~ m/^(\w+)(?:\|([^!+=]*))?/;
104 $spec->{k} = $short || $long;
105 $spec->{l} = $long;
106 $spec->{t} = $short;
107 $spec->{n} = $spec->{s} =~ m/!/;
108 $opts{$spec->{k}} = undef unless defined $opts{$spec->{k}};
109 die "Duplicate option $spec->{k}" if $opt_seen{$spec->{k}}++;
110}
111
112Getopt::Long::Configure('no_ignore_case', 'bundling');
113GetOptions( map { $_->{s} => \$opts{$_->{k}} } @opt_spec) or $opts{help} = 1;
114
115if ( $opts{version} ) {
116 print "innotop Ver $VERSION Changeset $SVN_REV from $SVN_URL\n";
117 exit(0);
118}
119
120if ( $opts{'help'} ) {
121 print "Usage: innotop <options> <innodb-status-file>\n\n";
122 my $maxw = max(map { length($_->{l}) + ($_->{n} ? 4 : 0)} @opt_spec);
123 foreach my $spec ( sort { $a->{l} cmp $b->{l} } @opt_spec ) {
124 my $long = $spec->{n} ? "[no]$spec->{l}" : $spec->{l};
125 my $short = $spec->{t} ? "-$spec->{t}" : '';
126 printf(" --%-${maxw}s %-4s %s\n", $long, $short, $spec->{d});
127 }
128 print <<USAGE;
129
130innotop is a MySQL and InnoDB transaction/status monitor, like 'top' for
131MySQL. It displays queries, InnoDB transactions, lock waits, deadlocks,
132foreign key errors, open tables, replication status, buffer information,
133row operations, logs, I/O operations, load graph, and more. You can
134monitor many servers at once with innotop.
135
136USAGE
137 exit(1);
138}
139
140# Meta-data (table definitions etc) {{{2
141# ###########################################################################
142
143# Expressions {{{3
144# Convenience so I can copy/paste these in several places...
145# ###########################################################################
146my %exprs = (
147 Host => q{my $host = host || hostname || ''; ($host) = $host =~ m/^((?:[\d.]+(?=:))|(?:[a-zA-Z]\w+))/; return $host || ''},
148 Port => q{my ($p) = host =~ m/:(.*)$/; return $p || 0},
149 OldVersions => q{dulint_to_int(IB_tx_trx_id_counter) - dulint_to_int(IB_tx_purge_done_for)},
150 MaxTxnTime => q/max(map{ $_->{active_secs} } @{ IB_tx_transactions }) || 0/,
151 NumTxns => q{scalar @{ IB_tx_transactions } },
152 DirtyBufs => q{ $cur->{IB_bp_pages_modified} / ($cur->{IB_bp_buf_pool_size} || 1) },
153 BufPoolFill => q{ $cur->{IB_bp_pages_total} / ($cur->{IB_bp_buf_pool_size} || 1) },
154 ServerLoad => q{ $cur->{Threads_connected}/(Questions||1)/Uptime_hires },
155 TxnTimeRemain => q{ defined undo_log_entries && defined $pre->{undo_log_entries} && undo_log_entries < $pre->{undo_log_entries} ? undo_log_entries / (($pre->{undo_log_entries} - undo_log_entries)/((active_secs-$pre->{active_secs})||1))||1 : 0},
156 SlaveCatchupRate => ' defined $cur->{seconds_behind_master} && defined $pre->{seconds_behind_master} && $cur->{seconds_behind_master} < $pre->{seconds_behind_master} ? ($pre->{seconds_behind_master}-$cur->{seconds_behind_master})/($cur->{Uptime_hires}-$pre->{Uptime_hires}) : 0',
157 QcacheHitRatio => q{(Qcache_hits||0)/(((Com_select||0)+(Qcache_hits||0))||1)},
158);
159
160# ###########################################################################
161# Column definitions {{{3
162# Defines every column in every table. A named column has the following
163# properties:
164# * hdr Column header/title
165# * label Documentation for humans.
166# * num Whether it's numeric (for sorting).
167# * just Alignment; generated from num, user-overridable in tbl_meta
168# * minw, maxw Auto-generated, user-overridable.
169# Values from this hash are just copied to tbl_meta, which is where everything
170# else in the program should read from.
171# ###########################################################################
172
173my %columns = (
174 active_secs => { hdr => 'SecsActive', num => 1, label => 'Seconds transaction has been active', },
175 add_pool_alloc => { hdr => 'Add\'l Pool', num => 1, label => 'Additonal pool allocated' },
176 attempted_op => { hdr => 'Action', num => 0, label => 'The action that caused the error' },
177 awe_mem_alloc => { hdr => 'AWE Memory', num => 1, label => '[Windows] AWE memory allocated' },
178 binlog_cache_overflow => { hdr => 'Binlog Cache', num => 1, label => 'Transactions too big for binlog cache that went to disk' },
179 binlog_do_db => { hdr => 'Binlog Do DB', num => 0, label => 'binlog-do-db setting' },
180 binlog_ignore_db => { hdr => 'Binlog Ignore DB', num => 0, label => 'binlog-ignore-db setting' },
181 bps_in => { hdr => 'BpsIn', num => 1, label => 'Bytes per second received by the server', },
182 bps_out => { hdr => 'BpsOut', num => 1, label => 'Bytes per second sent by the server', },
183 buf_free => { hdr => 'Free Bufs', num => 1, label => 'Buffers free in the buffer pool' },
184 buf_pool_hit_rate => { hdr => 'Hit Rate', num => 0, label => 'Buffer pool hit rate' },
185 buf_pool_hits => { hdr => 'Hits', num => 1, label => 'Buffer pool hits' },
186 buf_pool_reads => { hdr => 'Reads', num => 1, label => 'Buffer pool reads' },
187 buf_pool_size => { hdr => 'Size', num => 1, label => 'Buffer pool size' },
188 bufs_in_node_heap => { hdr => 'Node Heap Bufs', num => 1, label => 'Buffers in buffer pool node heap' },
189 bytes_behind_master => { hdr => 'ByteLag', num => 1, label => 'Bytes the slave lags the master in binlog' },
190 cell_event_set => { hdr => 'Ending?', num => 1, label => 'Whether the cell event is set' },
191 cell_waiting => { hdr => 'Waiting?', num => 1, label => 'Whether the cell is waiting' },
192 child_db => { hdr => 'Child DB', num => 0, label => 'The database of the child table' },
193 child_index => { hdr => 'Child Index', num => 0, label => 'The index in the child table' },
194 child_table => { hdr => 'Child Table', num => 0, label => 'The child table' },
195 cmd => { hdr => 'Cmd', num => 0, label => 'Type of command being executed', },
196 cnt => { hdr => 'Cnt', num => 0, label => 'Count', agg => 'count', aggonly => 1 },
197 connect_retry => { hdr => 'Connect Retry', num => 1, label => 'Slave connect-retry timeout' },
198 cxn => { hdr => 'CXN', num => 0, label => 'Connection from which the data came', },
199 db => { hdr => 'DB', num => 0, label => 'Current database', },
200 dict_mem_alloc => { hdr => 'Dict Mem', num => 1, label => 'Dictionary memory allocated' },
201 dirty_bufs => { hdr => 'Dirty Buf', num => 1, label => 'Dirty buffer pool pages' },
202 dl_txn_num => { hdr => 'Num', num => 0, label => 'Deadlocked transaction number', },
203 event_set => { hdr => 'Evt Set?', num => 1, label => '[Win32] if a wait event is set', },
204 exec_master_log_pos => { hdr => 'Exec Master Log Pos', num => 1, label => 'Exec Master Log Position' },
205 fk_name => { hdr => 'Constraint', num => 0, label => 'The name of the FK constraint' },
206 free_list_len => { hdr => 'Free List Len', num => 1, label => 'Length of the free list' },
207 has_read_view => { hdr => 'Rd View', num => 1, label => 'Whether the transaction has a read view' },
208 hash_searches_s => { hdr => 'Hash/Sec', num => 1, label => 'Number of hash searches/sec' },
209 hash_table_size => { hdr => 'Size', num => 1, label => 'Number of non-hash searches/sec' },
210 heap_no => { hdr => 'Heap', num => 1, label => 'Heap number' },
211 heap_size => { hdr => 'Heap', num => 1, label => 'Heap size' },
212 history_list_len => { hdr => 'History', num => 1, label => 'History list length' },
213 host_and_domain => { hdr => 'Host', num => 0, label => 'Hostname/IP and domain' },
214 host_and_port => { hdr => 'Host/IP', num => 0, label => 'Hostname or IP address, and port number', },
215 hostname => { hdr => 'Host', num => 0, label => 'Hostname' },
216 index => { hdr => 'Index', num => 0, label => 'The index involved' },
217 index_ref => { hdr => 'Index Ref', num => 0, label => 'Index referenced' },
218 info => { hdr => 'Query', num => 0, label => 'Info or the current query', },
219 insert_intention => { hdr => 'Ins Intent', num => 1, label => 'Whether the thread was trying to insert' },
220 inserts => { hdr => 'Inserts', num => 1, label => 'Inserts' },
221 io_bytes_s => { hdr => 'Bytes/Sec', num => 1, label => 'Average I/O bytes/sec' },
222 io_flush_type => { hdr => 'Flush Type', num => 0, label => 'I/O Flush Type' },
223 io_fsyncs_s => { hdr => 'fsyncs/sec', num => 1, label => 'I/O fsyncs/sec' },
224 io_reads_s => { hdr => 'Reads/Sec', num => 1, label => 'Average I/O reads/sec' },
225 io_writes_s => { hdr => 'Writes/Sec', num => 1, label => 'Average I/O writes/sec' },
226 ip => { hdr => 'IP', num => 0, label => 'IP address' },
227 is_name_locked => { hdr => 'Locked', num => 1, label => 'Whether table is name locked', },
228 key_buffer_hit => { hdr => 'KCacheHit', num => 1, label => 'Key cache hit ratio', },
229 key_len => { hdr => 'Key Length', num => 1, label => 'Number of bytes used in the key' },
230 last_chkp => { hdr => 'Last Checkpoint', num => 0, label => 'Last log checkpoint' },
231 last_errno => { hdr => 'Last Errno', num => 1, label => 'Last error number' },
232 last_error => { hdr => 'Last Error', num => 0, label => 'Last error' },
233 last_s_file_name => { hdr => 'S-File', num => 0, label => 'Filename where last read locked' },
234 last_s_line => { hdr => 'S-Line', num => 1, label => 'Line where last read locked' },
235 last_x_file_name => { hdr => 'X-File', num => 0, label => 'Filename where last write locked' },
236 last_x_line => { hdr => 'X-Line', num => 1, label => 'Line where last write locked' },
237 last_pct => { hdr => 'Pct', num => 1, label => 'Last Percentage' },
238 last_total => { hdr => 'Last Total', num => 1, label => 'Last Total' },
239 last_value => { hdr => 'Last Incr', num => 1, label => 'Last Value' },
240 load => { hdr => 'Load', num => 1, label => 'Server load' },
241 lock_cfile_name => { hdr => 'Crtd File', num => 0, label => 'Filename where lock created' },
242 lock_cline => { hdr => 'Crtd Line', num => 1, label => 'Line where lock created' },
243 lock_mem_addr => { hdr => 'Addr', num => 0, label => 'The lock memory address' },
244 lock_mode => { hdr => 'Mode', num => 0, label => 'The lock mode' },
245 lock_structs => { hdr => 'LStrcts', num => 1, label => 'Number of lock structs' },
246 lock_type => { hdr => 'Type', num => 0, label => 'The lock type' },
247 lock_var => { hdr => 'Lck Var', num => 1, label => 'The lock variable' },
248 lock_wait_time => { hdr => 'Wait', num => 1, label => 'How long txn has waited for a lock' },
249 log_flushed_to => { hdr => 'Flushed To', num => 0, label => 'Log position flushed to' },
250 log_ios_done => { hdr => 'IO Done', num => 1, label => 'Log I/Os done' },
251 log_ios_s => { hdr => 'IO/Sec', num => 1, label => 'Average log I/Os per sec' },
252 log_seq_no => { hdr => 'Sequence No.', num => 0, label => 'Log sequence number' },
253 main_thread_id => { hdr => 'Main Thread ID', num => 1, label => 'Main thread ID' },
254 main_thread_proc_no => { hdr => 'Main Thread Proc', num => 1, label => 'Main thread process number' },
255 main_thread_state => { hdr => 'Main Thread State', num => 0, label => 'Main thread state' },
256 master_file => { hdr => 'File', num => 0, label => 'Master file' },
257 master_host => { hdr => 'Master', num => 0, label => 'Master server hostname' },
258 master_log_file => { hdr => 'Master Log File', num => 0, label => 'Master log file' },
259 master_port => { hdr => 'Master Port', num => 1, label => 'Master port' },
260 master_pos => { hdr => 'Position', num => 1, label => 'Master position' },
261 master_ssl_allowed => { hdr => 'Master SSL Allowed', num => 0, label => 'Master SSL Allowed' },
262 master_ssl_ca_file => { hdr => 'Master SSL CA File', num => 0, label => 'Master SSL Cert Auth File' },
263 master_ssl_ca_path => { hdr => 'Master SSL CA Path', num => 0, label => 'Master SSL Cert Auth Path' },
264 master_ssl_cert => { hdr => 'Master SSL Cert', num => 0, label => 'Master SSL Cert' },
265 master_ssl_cipher => { hdr => 'Master SSL Cipher', num => 0, label => 'Master SSL Cipher' },
266 master_ssl_key => { hdr => 'Master SSL Key', num => 0, label => 'Master SSL Key' },
267 master_user => { hdr => 'Master User', num => 0, label => 'Master username' },
268 max_txn => { hdr => 'MaxTxnTime', num => 1, label => 'MaxTxn' },
269 merged_recs => { hdr => 'Merged Recs', num => 1, label => 'Merged records' },
270 merges => { hdr => 'Merges', num => 1, label => 'Merges' },
271 mutex_os_waits => { hdr => 'Waits', num => 1, label => 'Mutex OS Waits' },
272 mutex_spin_rounds => { hdr => 'Rounds', num => 1, label => 'Mutex Spin Rounds' },
273 mutex_spin_waits => { hdr => 'Spins', num => 1, label => 'Mutex Spin Waits' },
274 mysql_thread_id => { hdr => 'ID', num => 1, label => 'MySQL connection (thread) ID', },
275 name => { hdr => 'Name', num => 0, label => 'Variable Name' },
276 n_bits => { hdr => '# Bits', num => 1, label => 'Number of bits' },
277 non_hash_searches_s => { hdr => 'Non-Hash/Sec', num => 1, label => 'Non-hash searches/sec' },
278 num_deletes => { hdr => 'Del', num => 1, label => 'Number of deletes' },
279 num_deletes_sec => { hdr => 'Del/Sec', num => 1, label => 'Number of deletes' },
280 num_inserts => { hdr => 'Ins', num => 1, label => 'Number of inserts' },
281 num_inserts_sec => { hdr => 'Ins/Sec', num => 1, label => 'Number of inserts' },
282 num_readers => { hdr => 'Readers', num => 1, label => 'Number of readers' },
283 num_reads => { hdr => 'Read', num => 1, label => 'Number of reads' },
284 num_reads_sec => { hdr => 'Read/Sec', num => 1, label => 'Number of reads' },
285 num_res_ext => { hdr => 'BTree Extents', num => 1, label => 'Number of extents reserved for B-Tree' },
286 num_rows => { hdr => 'Row Count', num => 1, label => 'Number of rows estimated to examine' },
287 num_times_open => { hdr => 'In Use', num => 1, label => '# times table is opened', },
288 num_txns => { hdr => 'Txns', num => 1, label => 'Number of transactions' },
289 num_updates => { hdr => 'Upd', num => 1, label => 'Number of updates' },
290 num_updates_sec => { hdr => 'Upd/Sec', num => 1, label => 'Number of updates' },
291 os_file_reads => { hdr => 'OS Reads', num => 1, label => 'OS file reads' },
292 os_file_writes => { hdr => 'OS Writes', num => 1, label => 'OS file writes' },
293 os_fsyncs => { hdr => 'OS fsyncs', num => 1, label => 'OS fsyncs' },
294 os_thread_id => { hdr => 'OS Thread', num => 1, label => 'The operating system thread ID' },
295 p_aio_writes => { hdr => 'Async Wrt', num => 1, label => 'Pending asynchronous I/O writes' },
296 p_buf_pool_flushes => { hdr => 'Buffer Pool Flushes', num => 1, label => 'Pending buffer pool flushes' },
297 p_ibuf_aio_reads => { hdr => 'IBuf Async Rds', num => 1, label => 'Pending insert buffer asynch I/O reads' },
298 p_log_flushes => { hdr => 'Log Flushes', num => 1, label => 'Pending log flushes' },
299 p_log_ios => { hdr => 'Log I/Os', num => 1, label => 'Pending log I/O operations' },
300 p_normal_aio_reads => { hdr => 'Async Rds', num => 1, label => 'Pending asynchronous I/O reads' },
301 p_preads => { hdr => 'preads', num => 1, label => 'Pending p-reads' },
302 p_pwrites => { hdr => 'pwrites', num => 1, label => 'Pending p-writes' },
303 p_sync_ios => { hdr => 'Sync I/Os', num => 1, label => 'Pending synchronous I/O operations' },
304 page_creates_sec => { hdr => 'Creates/Sec', num => 1, label => 'Page creates/sec' },
305 page_no => { hdr => 'Page', num => 1, label => 'Page number' },
306 page_reads_sec => { hdr => 'Reads/Sec', num => 1, label => 'Page reads per second' },
307 page_writes_sec => { hdr => 'Writes/Sec', num => 1, label => 'Page writes per second' },
308 pages_created => { hdr => 'Created', num => 1, label => 'Pages created' },
309 pages_modified => { hdr => 'Dirty Pages', num => 1, label => 'Pages modified (dirty)' },
310 pages_read => { hdr => 'Reads', num => 1, label => 'Pages read' },
311 pages_total => { hdr => 'Pages', num => 1, label => 'Pages total' },
312 pages_written => { hdr => 'Writes', num => 1, label => 'Pages written' },
313 parent_col => { hdr => 'Parent Column', num => 0, label => 'The referred column in the parent table', },
314 parent_db => { hdr => 'Parent DB', num => 0, label => 'The database of the parent table' },
315 parent_index => { hdr => 'Parent Index', num => 0, label => 'The referred index in the parent table' },
316 parent_table => { hdr => 'Parent Table', num => 0, label => 'The parent table' },
317 part_id => { hdr => 'Part ID', num => 1, label => 'Sub-part ID of the query' },
318 partitions => { hdr => 'Partitions', num => 0, label => 'Query partitions used' },
319 pct => { hdr => 'Pct', num => 1, label => 'Percentage' },
320 pending_chkp_writes => { hdr => 'Chkpt Writes', num => 1, label => 'Pending log checkpoint writes' },
321 pending_log_writes => { hdr => 'Log Writes', num => 1, label => 'Pending log writes' },
322 port => { hdr => 'Port', num => 1, label => 'Client port number', },
323 possible_keys => { hdr => 'Poss. Keys', num => 0, label => 'Possible keys' },
324 proc_no => { hdr => 'Proc', num => 1, label => 'Process number' },
325 q_cache_hit => { hdr => 'QCacheHit', num => 1, label => 'Query cache hit ratio', },
326 qps => { hdr => 'QPS', num => 1, label => 'How many queries/sec', },
327 queries_in_queue => { hdr => 'Queries Queued', num => 1, label => 'Queries in queue' },
328 queries_inside => { hdr => 'Queries Inside', num => 1, label => 'Queries inside InnoDB' },
329 query_id => { hdr => 'Query ID', num => 1, label => 'Query ID' },
330 query_status => { hdr => 'Query Status', num => 0, label => 'The query status' },
331 query_text => { hdr => 'Query Text', num => 0, label => 'The query text' },
332 questions => { hdr => 'Questions', num => 1, label => 'How many queries the server has gotten', },
333 read_master_log_pos => { hdr => 'Read Master Pos', num => 1, label => 'Read master log position' },
334 read_views_open => { hdr => 'Rd Views', num => 1, label => 'Number of read views open' },
335 reads_pending => { hdr => 'Pending Reads', num => 1, label => 'Reads pending' },
336 relay_log_file => { hdr => 'Relay File', num => 0, label => 'Relay log file' },
337 relay_log_pos => { hdr => 'Relay Pos', num => 1, label => 'Relay log position' },
338 relay_log_size => { hdr => 'Relay Size', num => 1, label => 'Relay log size' },
339 relay_master_log_file => { hdr => 'Relay Master File', num => 0, label => 'Relay master log file' },
340 replicate_do_db => { hdr => 'Do DB', num => 0, label => 'Replicate-do-db setting' },
341 replicate_do_table => { hdr => 'Do Table', num => 0, label => 'Replicate-do-table setting' },
342 replicate_ignore_db => { hdr => 'Ignore DB', num => 0, label => 'Replicate-ignore-db setting' },
343 replicate_ignore_table => { hdr => 'Ignore Table', num => 0, label => 'Replicate-do-table setting' },
344 replicate_wild_do_table => { hdr => 'Wild Do Table', num => 0, label => 'Replicate-wild-do-table setting' },
345 replicate_wild_ignore_table => { hdr => 'Wild Ignore Table', num => 0, label => 'Replicate-wild-ignore-table setting' },
346 request_type => { hdr => 'Type', num => 0, label => 'Type of lock the thread waits for' },
347 reservation_count => { hdr => 'ResCnt', num => 1, label => 'Reservation Count' },
348 row_locks => { hdr => 'RLocks', num => 1, label => 'Number of row locks' },
349 rw_excl_os_waits => { hdr => 'RW Waits', num => 1, label => 'R/W Excl. OS Waits' },
350 rw_excl_spins => { hdr => 'RW Spins', num => 1, label => 'R/W Excl. Spins' },
351 rw_shared_os_waits => { hdr => 'Sh Waits', num => 1, label => 'R/W Shared OS Waits' },
352 rw_shared_spins => { hdr => 'Sh Spins', num => 1, label => 'R/W Shared Spins' },
353 scan_type => { hdr => 'Type', num => 0, label => 'Scan type in chosen' },
354 seg_size => { hdr => 'Seg. Size', num => 1, label => 'Segment size' },
355 select_type => { hdr => 'Select Type', num => 0, label => 'Type of select used' },
356 signal_count => { hdr => 'Signals', num => 1, label => 'Signal Count' },
357 size => { hdr => 'Size', num => 1, label => 'Size of the tablespace' },
358 skip_counter => { hdr => 'Skip Counter', num => 1, label => 'Skip counter' },
359 slave_catchup_rate => { hdr => 'Catchup', num => 1, label => 'How fast the slave is catching up in the binlog' },
360 slave_io_running => { hdr => 'Slave-IO', num => 0, label => 'Whether the slave I/O thread is running' },
361 slave_io_state => { hdr => 'Slave IO State', num => 0, label => 'Slave I/O thread state' },
362 slave_open_temp_tables => { hdr => 'Temp', num => 1, label => 'Slave open temp tables' },
363 slave_sql_running => { hdr => 'Slave-SQL', num => 0, label => 'Whether the slave SQL thread is running' },
364 slow => { hdr => 'Slow', num => 1, label => 'How many slow queries', },
365 space_id => { hdr => 'Space', num => 1, label => 'Tablespace ID' },
366 special => { hdr => 'Special', num => 0, label => 'Special/Other info' },
367 state => { hdr => 'State', num => 0, label => 'Connection state', maxw => 18, },
368 tables_in_use => { hdr => 'Tbl Used', num => 1, label => 'Number of tables in use' },
369 tables_locked => { hdr => 'Tbl Lck', num => 1, label => 'Number of tables locked' },
370 tbl => { hdr => 'Table', num => 0, label => 'Table', },
371 thread => { hdr => 'Thread', num => 1, label => 'Thread number' },
372 thread_decl_inside => { hdr => 'Thread Inside', num => 0, label => 'What the thread is declared inside' },
373 thread_purpose => { hdr => 'Purpose', num => 0, label => "The thread's purpose" },
374 thread_status => { hdr => 'Thread Status', num => 0, label => 'The thread status' },
375 time => { hdr => 'Time', num => 1, label => 'Time since the last event', },
376 time_behind_master => { hdr => 'TimeLag', num => 1, label => 'Time slave lags master' },
377 timestring => { hdr => 'Timestring', num => 0, label => 'Time the event occurred' },
378 total => { hdr => 'Total', num => 1, label => 'Total' },
379 total_mem_alloc => { hdr => 'Memory', num => 1, label => 'Total memory allocated' },
380 truncates => { hdr => 'Trunc', num => 0, label => 'Whether the deadlock is truncating InnoDB status' },
381 txn_doesnt_see_ge => { hdr => "Txn Won't See", num => 0, label => 'Where txn read view is limited' },
382 txn_id => { hdr => 'ID', num => 0, label => 'Transaction ID' },
383 txn_sees_lt => { hdr => 'Txn Sees', num => 1, label => 'Where txn read view is limited' },
384 txn_status => { hdr => 'Txn Status', num => 0, label => 'Transaction status' },
385 txn_time_remain => { hdr => 'Remaining', num => 1, label => 'Time until txn rollback/commit completes' },
386 undo_log_entries => { hdr => 'Undo', num => 1, label => 'Number of undo log entries' },
387 undo_for => { hdr => 'Undo', num => 0, label => 'Undo for' },
388 until_condition => { hdr => 'Until Condition', num => 0, label => 'Slave until condition' },
389 until_log_file => { hdr => 'Until Log File', num => 0, label => 'Slave until log file' },
390 until_log_pos => { hdr => 'Until Log Pos', num => 1, label => 'Slave until log position' },
391 used_cells => { hdr => 'Cells Used', num => 1, label => 'Number of cells used' },
392 used_bufs => { hdr => 'Used Bufs', num => 1, label => 'Number of buffer pool pages used' },
393 user => { hdr => 'User', num => 0, label => 'Database username', },
394 value => { hdr => 'Value', num => 1, label => 'Value' },
395 versions => { hdr => 'Versions', num => 1, label => 'Number of InnoDB MVCC versions unpurged' },
396 victim => { hdr => 'Victim', num => 0, label => 'Whether this txn was the deadlock victim' },
397 wait_array_size => { hdr => 'Wait Array Size', num => 1, label => 'Wait Array Size' },
398 wait_status => { hdr => 'Lock Status', num => 0, label => 'Status of txn locks' },
399 waited_at_filename => { hdr => 'File', num => 0, label => 'Filename at which thread waits' },
400 waited_at_line => { hdr => 'Line', num => 1, label => 'Line at which thread waits' },
401 waiters_flag => { hdr => 'Waiters', num => 1, label => 'Waiters Flag' },
402 waiting => { hdr => 'Waiting', num => 1, label => 'Whether lock is being waited for' },
403 when => { hdr => 'When', num => 0, label => 'Time scale' },
404 writer_lock_mode => { hdr => 'Wrtr Lck Mode', num => 0, label => 'Writer lock mode' },
405 writer_thread => { hdr => 'Wrtr Thread', num => 1, label => 'Writer thread ID' },
406 writes_pending => { hdr => 'Writes', num => 1, label => 'Number of writes pending' },
407 writes_pending_flush_list => { hdr => 'Flush List Writes', num => 1, label => 'Number of flush list writes pending' },
408 writes_pending_lru => { hdr => 'LRU Writes', num => 1, label => 'Number of LRU writes pending' },
409 writes_pending_single_page => { hdr => '1-Page Writes', num => 1, label => 'Number of 1-page writes pending' },
410);
411
412# Apply a default property or three. By default, columns are not width-constrained,
413# aligned left, and sorted alphabetically, not numerically.
414foreach my $col ( values %columns ) {
415 map { $col->{$_} ||= 0 } qw(num minw maxw);
416 $col->{just} = $col->{num} ? '' : '-';
417}
418
419# Filters {{{3
420# This hash defines every filter that can be applied to a table. These
421# become part of tbl_meta as well. Each filter is just an expression that
422# returns true or false.
423# Properties of each entry:
424# * func: the subroutine
425# * name: the name, repeated
426# * user: whether it's a user-defined filter (saved in config)
427# * text: text of the subroutine
428# * note: explanation
429my %filters = ();
430
431# These are pre-processed to live in %filters above, by compiling them.
432my %builtin_filters = (
433 hide_self => {
434 text => <<' END',
435 return ( !$set->{info} || $set->{info} ne 'SHOW FULL PROCESSLIST' )
436 && ( !$set->{query_text} || $set->{query_text} !~ m/INNODB STATUS$/ );
437 END
438 note => 'Removes the innotop processes from the list',
439 tbls => [qw(innodb_transactions processlist)],
440 },
441 hide_inactive => {
442 text => <<' END',
443 return ( !defined($set->{txn_status}) || $set->{txn_status} ne 'not started' )
444 && ( !defined($set->{cmd}) || $set->{cmd} !~ m/Sleep|Binlog Dump/ )
445 && ( !defined($set->{info}) || $set->{info} =~ m/\S/ );
446 END
447 note => 'Removes processes which are not doing anything',
448 tbls => [qw(innodb_transactions processlist)],
449 },
450 hide_slave_io => {
451 text => <<' END',
452 return !$set->{state} || $set->{state} !~ m/^(?:Waiting for master|Has read all relay)/;
453 END
454 note => 'Removes slave I/O threads from the list',
455 tbls => [qw(processlist slave_io_status)],
456 },
457 table_is_open => {
458 text => <<' END',
459 return $set->{num_times_open} + $set->{is_name_locked};
460 END
461 note => 'Removes tables that are not in use or locked',
462 tbls => [qw(open_tables)],
463 },
464 cxn_is_master => {
465 text => <<' END',
466 return $set->{master_file} ? 1 : 0;
467 END
468 note => 'Removes servers that are not masters',
469 tbls => [qw(master_status)],
470 },
471 cxn_is_slave => {
472 text => <<' END',
473 return $set->{master_host} ? 1 : 0;
474 END
475 note => 'Removes servers that are not slaves',
476 tbls => [qw(slave_io_status slave_sql_status)],
477 },
478 thd_is_not_waiting => {
479 text => <<' END',
480 return $set->{thread_status} !~ m#waiting for i/o request#;
481 END
482 note => 'Removes idle I/O threads',
483 tbls => [qw(io_threads)],
484 },
485);
486foreach my $key ( keys %builtin_filters ) {
487 my ( $sub, $err ) = compile_filter($builtin_filters{$key}->{text});
488 $filters{$key} = {
489 func => $sub,
490 text => $builtin_filters{$key}->{text},
491 user => 0,
492 name => $key, # useful for later
493 note => $builtin_filters{$key}->{note},
494 tbls => $builtin_filters{$key}->{tbls},
495 }
496}
497
498# Variable sets {{{3
499# Sets (arrayrefs) of variables that are used in S mode. They are read/written to
500# the config file.
501my %var_sets = (
502 general => {
503 text => join(
504 ', ',
505 'set_precision(Questions/Uptime_hires) as QPS',
506 'set_precision(Com_commit/Uptime_hires) as Commit_PS',
507 'set_precision((Com_rollback||0)/(Com_commit||1)) as Rollback_Commit',
508 'set_precision(('
509 . join('+', map { "($_||0)" }
510 qw(Com_delete Com_delete_multi Com_insert Com_insert_select Com_replace
511 Com_replace_select Com_select Com_update Com_update_multi))
512 . ')/(Com_commit||1)) as Write_Commit',
513 'set_precision((Com_select+(Qcache_hits||0))/(('
514 . join('+', map { "($_||0)" }
515 qw(Com_delete Com_delete_multi Com_insert Com_insert_select Com_replace
516 Com_replace_select Com_select Com_update Com_update_multi))
517 . ')||1)) as R_W_Ratio',
518 'set_precision(Opened_tables/Uptime_hires) as Opens_PS',
519 'percent($cur->{Open_tables}/($cur->{table_cache})) as Table_Cache_Used',
520 'set_precision(Threads_created/Uptime_hires) as Threads_PS',
521 'percent($cur->{Threads_cached}/($cur->{thread_cache_size}||1)) as Thread_Cache_Used',
522 'percent($cur->{Max_used_connections}/($cur->{max_connections}||1)) as CXN_Used_Ever',
523 'percent($cur->{Threads_connected}/($cur->{max_connections}||1)) as CXN_Used_Now',
524 ),
525 },
526 commands => {
527 text => join(
528 ', ',
529 qw(Uptime Questions Com_delete Com_delete_multi Com_insert
530 Com_insert_select Com_replace Com_replace_select Com_select Com_update
531 Com_update_multi)
532 ),
533 },
534 query_status => {
535 text => join(
536 ',',
537 qw( Uptime Select_full_join Select_full_range_join Select_range
538 Select_range_check Select_scan Slow_queries Sort_merge_passes
539 Sort_range Sort_rows Sort_scan)
540 ),
541 },
542 innodb => {
543 text => join(
544 ',',
545 qw( Uptime Innodb_row_lock_current_waits Innodb_row_lock_time
546 Innodb_row_lock_time_avg Innodb_row_lock_time_max Innodb_row_lock_waits
547 Innodb_rows_deleted Innodb_rows_inserted Innodb_rows_read
548 Innodb_rows_updated)
549 ),
550 },
551 txn => {
552 text => join(
553 ',',
554 qw( Uptime Com_begin Com_commit Com_rollback Com_savepoint
555 Com_xa_commit Com_xa_end Com_xa_prepare Com_xa_recover Com_xa_rollback
556 Com_xa_start)
557 ),
558 },
559 key_cache => {
560 text => join(
561 ',',
562 qw( Uptime Key_blocks_not_flushed Key_blocks_unused Key_blocks_used
563 Key_read_requests Key_reads Key_write_requests Key_writes )
564 ),
565 },
566 query_cache => {
567 text => join(
568 ',',
569 "percent($exprs{QcacheHitRatio}) as Hit_Pct",
570 'set_precision((Qcache_hits||0)/(Qcache_inserts||1)) as Hit_Ins',
571 'set_precision((Qcache_lowmem_prunes||0)/Uptime_hires) as Lowmem_Prunes_sec',
572 'percent(1-((Qcache_free_blocks||0)/(Qcache_total_blocks||1))) as Blocks_used',
573 qw( Qcache_free_blocks Qcache_free_memory Qcache_not_cached Qcache_queries_in_cache)
574 ),
575 },
576 handler => {
577 text => join(
578 ',',
579 qw( Uptime Handler_read_key Handler_read_first Handler_read_next
580 Handler_read_prev Handler_read_rnd Handler_read_rnd_next Handler_delete
581 Handler_update Handler_write)
582 ),
583 },
584 cxns_files_threads => {
585 text => join(
586 ',',
587 qw( Uptime Aborted_clients Aborted_connects Bytes_received Bytes_sent
588 Compression Connections Created_tmp_disk_tables Created_tmp_files
589 Created_tmp_tables Max_used_connections Open_files Open_streams
590 Open_tables Opened_tables Table_locks_immediate Table_locks_waited
591 Threads_cached Threads_connected Threads_created Threads_running)
592 ),
593 },
594 prep_stmt => {
595 text => join(
596 ',',
597 qw( Uptime Com_dealloc_sql Com_execute_sql Com_prepare_sql Com_reset
598 Com_stmt_close Com_stmt_execute Com_stmt_fetch Com_stmt_prepare
599 Com_stmt_reset Com_stmt_send_long_data )
600 ),
601 },
602 innodb_health => {
603 text => join(
604 ',',
605 "$exprs{OldVersions} as OldVersions",
606 qw(IB_sm_mutex_spin_waits IB_sm_mutex_spin_rounds IB_sm_mutex_os_waits),
607 "$exprs{NumTxns} as NumTxns",
608 "$exprs{MaxTxnTime} as MaxTxnTime",
609 qw(IB_ro_queries_inside IB_ro_queries_in_queue),
610 "set_precision($exprs{DirtyBufs} * 100) as dirty_bufs",
611 "set_precision($exprs{BufPoolFill} * 100) as buf_fill",
612 qw(IB_bp_pages_total IB_bp_pages_read IB_bp_pages_written IB_bp_pages_created)
613 ),
614 },
615 innodb_health2 => {
616 text => join(
617 ', ',
618 'percent(1-((Innodb_buffer_pool_pages_free||0)/($cur->{Innodb_buffer_pool_pages_total}||1))) as BP_page_cache_usage',
619 'percent(1-((Innodb_buffer_pool_reads||0)/(Innodb_buffer_pool_read_requests||1))) as BP_cache_hit_ratio',
620 'Innodb_buffer_pool_wait_free',
621 'Innodb_log_waits',
622 ),
623 },
624 slow_queries => {
625 text => join(
626 ', ',
627 'set_precision(Slow_queries/Uptime_hires) as Slow_PS',
628 'set_precision(Select_full_join/Uptime_hires) as Full_Join_PS',
629 'percent(Select_full_join/(Com_select||1)) as Full_Join_Ratio',
630 ),
631 },
632);
633
634# Server sets {{{3
635# Defines sets of servers between which the user can quickly switch.
636my %server_groups;
637
638# Connections {{{3
639# This hash defines server connections. Each connection is a string that can be passed to
640# the DBI connection. These are saved in the connections section in the config file.
641my %connections;
642# Defines the parts of connections.
643my @conn_parts = qw(user have_user pass have_pass dsn savepass dl_table);
644
645# Graph widths {{{3
646# This hash defines the max values seen for various status/variable values, for graphing.
647# These are stored in their own section in the config file. These are just initial values:
648my %mvs = (
649 Com_select => 50,
650 Com_insert => 50,
651 Com_update => 50,
652 Com_delete => 50,
653 Questions => 100,
654);
655
656# ###########################################################################
657# Valid Term::ANSIColor color strings.
658# ###########################################################################
659my %ansicolors = map { $_ => 1 }
660 qw( black blink blue bold clear concealed cyan dark green magenta on_black
661 on_blue on_cyan on_green on_magenta on_red on_white on_yellow red reset
662 reverse underline underscore white yellow);
663
664# ###########################################################################
665# Valid comparison operators for color rules
666# ###########################################################################
667my %comp_ops = (
668 '==' => 'Numeric equality',
669 '>' => 'Numeric greater-than',
670 '<' => 'Numeric less-than',
671 '>=' => 'Numeric greater-than/equal',
672 '<=' => 'Numeric less-than/equal',
673 '!=' => 'Numeric not-equal',
674 'eq' => 'String equality',
675 'gt' => 'String greater-than',
676 'lt' => 'String less-than',
677 'ge' => 'String greater-than/equal',
678 'le' => 'String less-than/equal',
679 'ne' => 'String not-equal',
680 '=~' => 'Pattern match',
681 '!~' => 'Negated pattern match',
682);
683
684# ###########################################################################
685# Valid aggregate functions.
686# ###########################################################################
687my %agg_funcs = (
688 first => sub {
689 return $_[0]
690 },
691 count => sub {
692 return 0 + @_;
693 },
694 avg => sub {
695 my @args = grep { defined $_ } @_;
696 return (sum(map { m/([\d\.-]+)/g } @args) || 0) / (scalar(@args) || 1);
697 },
698 sum => \&sum,
699);
700
701# ###########################################################################
702# Valid functions for transformations.
703# ###########################################################################
704my %trans_funcs = (
705 shorten => \&shorten,
706 secs_to_time => \&secs_to_time,
707 no_ctrl_char => \&no_ctrl_char,
708 percent => \&percent,
709 commify => \&commify,
710 dulint_to_int => \&dulint_to_int,
711 set_precision => \&set_precision,
712);
713
714# Table definitions {{{3
715# This hash defines every table that can get displayed in every mode. Each
716# table specifies columns and column data sources. The column is
717# defined by the %columns hash.
718#
719# Example: foo => { src => 'bar' } means the foo column (look at
720# $columns{foo} for its definition) gets its data from the 'bar' element of
721# the current data set, whatever that is.
722#
723# These columns are post-processed after being defined, because they get stuff
724# from %columns. After all the config is loaded for columns, there's more
725# post-processing too; the subroutines compiled from src get added to
726# the hash elements for extract_values to use.
727# ###########################################################################
728
729my %tbl_meta = (
730 adaptive_hash_index => {
731 capt => 'Adaptive Hash Index',
732 cust => {},
733 cols => {
734 cxn => { src => 'cxn' },
735 hash_table_size => { src => 'IB_ib_hash_table_size', trans => [qw(shorten)], },
736 used_cells => { src => 'IB_ib_used_cells' },
737 bufs_in_node_heap => { src => 'IB_ib_bufs_in_node_heap' },
738 hash_searches_s => { src => 'IB_ib_hash_searches_s' },
739 non_hash_searches_s => { src => 'IB_ib_non_hash_searches_s' },
740 },
741 visible => [ qw(cxn hash_table_size used_cells bufs_in_node_heap hash_searches_s non_hash_searches_s) ],
742 filters => [],
743 sort_cols => 'cxn',
744 sort_dir => '1',
745 innodb => 'ib',
746 group_by => [],
747 aggregate => 0,
748 },
749 buffer_pool => {
750 capt => 'Buffer Pool',
751 cust => {},
752 cols => {
753 cxn => { src => 'cxn' },
754 total_mem_alloc => { src => 'IB_bp_total_mem_alloc', trans => [qw(shorten)], },
755 awe_mem_alloc => { src => 'IB_bp_awe_mem_alloc', trans => [qw(shorten)], },
756 add_pool_alloc => { src => 'IB_bp_add_pool_alloc', trans => [qw(shorten)], },
757 buf_pool_size => { src => 'IB_bp_buf_pool_size', trans => [qw(shorten)], },
758 buf_free => { src => 'IB_bp_buf_free' },
759 buf_pool_hit_rate => { src => 'IB_bp_buf_pool_hit_rate' },
760 buf_pool_reads => { src => 'IB_bp_buf_pool_reads' },
761 buf_pool_hits => { src => 'IB_bp_buf_pool_hits' },
762 dict_mem_alloc => { src => 'IB_bp_dict_mem_alloc' },
763 pages_total => { src => 'IB_bp_pages_total' },
764 pages_modified => { src => 'IB_bp_pages_modified' },
765 reads_pending => { src => 'IB_bp_reads_pending' },
766 writes_pending => { src => 'IB_bp_writes_pending' },
767 writes_pending_lru => { src => 'IB_bp_writes_pending_lru' },
768 writes_pending_flush_list => { src => 'IB_bp_writes_pending_flush_list' },
769 writes_pending_single_page => { src => 'IB_bp_writes_pending_single_page' },
770 page_creates_sec => { src => 'IB_bp_page_creates_sec' },
771 page_reads_sec => { src => 'IB_bp_page_reads_sec' },
772 page_writes_sec => { src => 'IB_bp_page_writes_sec' },
773 pages_created => { src => 'IB_bp_pages_created' },
774 pages_read => { src => 'IB_bp_pages_read' },
775 pages_written => { src => 'IB_bp_pages_written' },
776 },
777 visible => [ qw(cxn buf_pool_size buf_free pages_total pages_modified buf_pool_hit_rate total_mem_alloc add_pool_alloc)],
778 filters => [],
779 sort_cols => 'cxn',
780 sort_dir => '1',
781 innodb => 'bp',
782 group_by => [],
783 aggregate => 0,
784 },
785 # TODO: a new step in set_to_tbl: join result to itself, grouped?
786 # TODO: this would also enable pulling Q and T data together.
787 # TODO: using a SQL-ish language would also allow pivots to be easier -- treat the pivoted data as a view and SELECT from it.
788 cmd_summary => {
789 capt => 'Command Summary',
790 cust => {},
791 cols => {
792 name => { src => 'name' },
793 total => { src => 'total' },
794 value => { src => 'value', agg => 'sum'},
795 pct => { src => 'value/total', trans => [qw(percent)] },
796 last_total => { src => 'last_total' },
797 last_value => { src => 'last_value', agg => 'sum'},
798 last_pct => { src => 'last_value/last_total', trans => [qw(percent)] },
799 },
800 visible => [qw(name value pct last_value last_pct)],
801 filters => [qw()],
802 sort_cols => '-value',
803 sort_dir => '1',
804 innodb => '',
805 group_by => [qw(name)],
806 aggregate => 1,
807 },
808 deadlock_locks => {
809 capt => 'Deadlock Locks',
810 cust => {},
811 cols => {
812 cxn => { src => 'cxn' },
813 mysql_thread_id => { src => 'mysql_thread_id' },
814 dl_txn_num => { src => 'dl_txn_num' },
815 lock_type => { src => 'lock_type' },
816 space_id => { src => 'space_id' },
817 page_no => { src => 'page_no' },
818 heap_no => { src => 'heap_no' },
819 n_bits => { src => 'n_bits' },
820 index => { src => 'index' },
821 db => { src => 'db' },
822 tbl => { src => 'table' },
823 lock_mode => { src => 'lock_mode' },
824 special => { src => 'special' },
825 insert_intention => { src => 'insert_intention' },
826 waiting => { src => 'waiting' },
827 },
828 visible => [ qw(cxn mysql_thread_id waiting lock_mode db tbl index special insert_intention)],
829 filters => [],
830 sort_cols => 'cxn mysql_thread_id',
831 sort_dir => '1',
832 innodb => 'dl',
833 group_by => [],
834 aggregate => 0,
835 },
836 deadlock_transactions => {
837 capt => 'Deadlock Transactions',
838 cust => {},
839 cols => {
840 cxn => { src => 'cxn' },
841 active_secs => { src => 'active_secs' },
842 dl_txn_num => { src => 'dl_txn_num' },
843 has_read_view => { src => 'has_read_view' },
844 heap_size => { src => 'heap_size' },
845 host_and_domain => { src => 'hostname' },
846 hostname => { src => $exprs{Host} },
847 ip => { src => 'ip' },
848 lock_structs => { src => 'lock_structs' },
849 lock_wait_time => { src => 'lock_wait_time', trans => [ qw(secs_to_time) ] },
850 mysql_thread_id => { src => 'mysql_thread_id' },
851 os_thread_id => { src => 'os_thread_id' },
852 proc_no => { src => 'proc_no' },
853 query_id => { src => 'query_id' },
854 query_status => { src => 'query_status' },
855 query_text => { src => 'query_text', trans => [ qw(no_ctrl_char) ] },
856 row_locks => { src => 'row_locks' },
857 tables_in_use => { src => 'tables_in_use' },
858 tables_locked => { src => 'tables_locked' },
859 thread_decl_inside => { src => 'thread_decl_inside' },
860 thread_status => { src => 'thread_status' },
861 'time' => { src => 'active_secs', trans => [ qw(secs_to_time) ] },
862 timestring => { src => 'timestring' },
863 txn_doesnt_see_ge => { src => 'txn_doesnt_see_ge' },
864 txn_id => { src => 'txn_id' },
865 txn_sees_lt => { src => 'txn_sees_lt' },
866 txn_status => { src => 'txn_status' },
867 truncates => { src => 'truncates' },
868 undo_log_entries => { src => 'undo_log_entries' },
869 user => { src => 'user' },
870 victim => { src => 'victim' },
871 wait_status => { src => 'lock_wait_status' },
872 },
873 visible => [ qw(cxn mysql_thread_id timestring user hostname victim time undo_log_entries lock_structs query_text)],
874 filters => [],
875 sort_cols => 'cxn mysql_thread_id',
876 sort_dir => '1',
877 innodb => 'dl',
878 group_by => [],
879 aggregate => 0,
880 },
881 explain => {
882 capt => 'EXPLAIN Results',
883 cust => {},
884 cols => {
885 part_id => { src => 'id' },
886 select_type => { src => 'select_type' },
887 tbl => { src => 'table' },
888 partitions => { src => 'partitions' },
889 scan_type => { src => 'type' },
890 possible_keys => { src => 'possible_keys' },
891 index => { src => 'key' },
892 key_len => { src => 'key_len' },
893 index_ref => { src => 'ref' },
894 num_rows => { src => 'rows' },
895 special => { src => 'extra' },
896 },
897 visible => [ qw(select_type tbl partitions scan_type possible_keys index key_len index_ref num_rows special)],
898 filters => [],
899 sort_cols => '',
900 sort_dir => '1',
901 innodb => '',
902 group_by => [],
903 aggregate => 0,
904 },
905 file_io_misc => {
906 capt => 'File I/O Misc',
907 cust => {},
908 cols => {
909 cxn => { src => 'cxn' },
910 io_bytes_s => { src => 'IB_io_avg_bytes_s' },
911 io_flush_type => { src => 'IB_io_flush_type' },
912 io_fsyncs_s => { src => 'IB_io_fsyncs_s' },
913 io_reads_s => { src => 'IB_io_reads_s' },
914 io_writes_s => { src => 'IB_io_writes_s' },
915 os_file_reads => { src => 'IB_io_os_file_reads' },
916 os_file_writes => { src => 'IB_io_os_file_writes' },
917 os_fsyncs => { src => 'IB_io_os_fsyncs' },
918 },
919 visible => [ qw(cxn os_file_reads os_file_writes os_fsyncs io_reads_s io_writes_s io_bytes_s)],
920 filters => [],
921 sort_cols => 'cxn',
922 sort_dir => '1',
923 innodb => 'io',
924 group_by => [],
925 aggregate => 0,
926 },
927 fk_error => {
928 capt => 'Foreign Key Error Info',
929 cust => {},
930 cols => {
931 timestring => { src => 'IB_fk_timestring' },
932 child_db => { src => 'IB_fk_child_db' },
933 child_table => { src => 'IB_fk_child_table' },
934 child_index => { src => 'IB_fk_child_index' },
935 fk_name => { src => 'IB_fk_fk_name' },
936 parent_db => { src => 'IB_fk_parent_db' },
937 parent_table => { src => 'IB_fk_parent_table' },
938 parent_col => { src => 'IB_fk_parent_col' },
939 parent_index => { src => 'IB_fk_parent_index' },
940 attempted_op => { src => 'IB_fk_attempted_op' },
941 },
942 visible => [ qw(timestring child_db child_table child_index parent_db parent_table parent_col parent_index fk_name attempted_op)],
943 filters => [],
944 sort_cols => '',
945 sort_dir => '1',
946 innodb => 'fk',
947 group_by => [],
948 aggregate => 0,
949 },
950 insert_buffers => {
951 capt => 'Insert Buffers',
952 cust => {},
953 cols => {
954 cxn => { src => 'cxn' },
955 inserts => { src => 'IB_ib_inserts' },
956 merged_recs => { src => 'IB_ib_merged_recs' },
957 merges => { src => 'IB_ib_merges' },
958 size => { src => 'IB_ib_size' },
959 free_list_len => { src => 'IB_ib_free_list_len' },
960 seg_size => { src => 'IB_ib_seg_size' },
961 },
962 visible => [ qw(cxn inserts merged_recs merges size free_list_len seg_size)],
963 filters => [],
964 sort_cols => 'cxn',
965 sort_dir => '1',
966 innodb => 'ib',
967 group_by => [],
968 aggregate => 0,
969 },
970 innodb_locks => {
971 capt => 'InnoDB Locks',
972 cust => {},
973 cols => {
974 cxn => { src => 'cxn' },
975 db => { src => 'db' },
976 index => { src => 'index' },
977 insert_intention => { src => 'insert_intention' },
978 lock_mode => { src => 'lock_mode' },
979 lock_type => { src => 'lock_type' },
980 lock_wait_time => { src => 'lock_wait_time', trans => [ qw(secs_to_time) ] },
981 mysql_thread_id => { src => 'mysql_thread_id' },
982 n_bits => { src => 'n_bits' },
983 page_no => { src => 'page_no' },
984 space_id => { src => 'space_id' },
985 special => { src => 'special' },
986 tbl => { src => 'table' },
987 'time' => { src => 'active_secs', hdr => 'Active', trans => [ qw(secs_to_time) ] },
988 txn_id => { src => 'txn_id' },
989 waiting => { src => 'waiting' },
990 },
991 visible => [ qw(cxn mysql_thread_id lock_type waiting lock_wait_time time lock_mode db tbl index insert_intention special)],
992 filters => [],
993 sort_cols => 'cxn -lock_wait_time',
994 sort_dir => '1',
995 innodb => 'tx',
996 colors => [
997 { col => 'lock_wait_time', op => '>', arg => 60, color => 'red' },
998 { col => 'lock_wait_time', op => '>', arg => 30, color => 'yellow' },
999 { col => 'lock_wait_time', op => '>', arg => 10, color => 'green' },
1000 ],
1001 group_by => [],
1002 aggregate => 0,
1003 },
1004 innodb_transactions => {
1005 capt => 'InnoDB Transactions',
1006 cust => {},
1007 cols => {
1008 cxn => { src => 'cxn' },
1009 active_secs => { src => 'active_secs' },
1010 has_read_view => { src => 'has_read_view' },
1011 heap_size => { src => 'heap_size' },
1012 hostname => { src => $exprs{Host} },
1013 ip => { src => 'ip' },
1014 wait_status => { src => 'lock_wait_status' },
1015 lock_wait_time => { src => 'lock_wait_time', trans => [ qw(secs_to_time) ] },
1016 lock_structs => { src => 'lock_structs' },
1017 mysql_thread_id => { src => 'mysql_thread_id' },
1018 os_thread_id => { src => 'os_thread_id' },
1019 proc_no => { src => 'proc_no' },
1020 query_id => { src => 'query_id' },
1021 query_status => { src => 'query_status' },
1022 query_text => { src => 'query_text', trans => [ qw(no_ctrl_char) ] },
1023 txn_time_remain => { src => $exprs{TxnTimeRemain}, trans => [ qw(secs_to_time) ] },
1024 row_locks => { src => 'row_locks' },
1025 tables_in_use => { src => 'tables_in_use' },
1026 tables_locked => { src => 'tables_locked' },
1027 thread_decl_inside => { src => 'thread_decl_inside' },
1028 thread_status => { src => 'thread_status' },
1029 'time' => { src => 'active_secs', trans => [ qw(secs_to_time) ], agg => 'sum' },
1030 txn_doesnt_see_ge => { src => 'txn_doesnt_see_ge' },
1031 txn_id => { src => 'txn_id' },
1032 txn_sees_lt => { src => 'txn_sees_lt' },
1033 txn_status => { src => 'txn_status', minw => 10, maxw => 10 },
1034 undo_log_entries => { src => 'undo_log_entries' },
1035 user => { src => 'user', maxw => 10 },
1036 cnt => { src => 'mysql_thread_id', minw => 0 },
1037 },
1038 visible => [ qw(cxn cnt mysql_thread_id user hostname txn_status time undo_log_entries query_text)],
1039 filters => [ qw( hide_self hide_inactive ) ],
1040 sort_cols => '-active_secs txn_status cxn mysql_thread_id',
1041 sort_dir => '1',
1042 innodb => 'tx',
1043 hide_caption => 1,
1044 colors => [
1045 { col => 'wait_status', op => 'eq', arg => 'LOCK WAIT', color => 'black on_red' },
1046 { col => 'time', op => '>', arg => 600, color => 'red' },
1047 { col => 'time', op => '>', arg => 300, color => 'yellow' },
1048 { col => 'time', op => '>', arg => 60, color => 'green' },
1049 { col => 'time', op => '>', arg => 30, color => 'cyan' },
1050 { col => 'txn_status', op => 'eq', arg => 'not started', color => 'white' },
1051 ],
1052 group_by => [ qw(cxn txn_status) ],
1053 aggregate => 0,
1054 },
1055 io_threads => {
1056 capt => 'I/O Threads',
1057 cust => {},
1058 cols => {
1059 cxn => { src => 'cxn' },
1060 thread => { src => 'thread' },
1061 thread_purpose => { src => 'purpose' },
1062 event_set => { src => 'event_set' },
1063 thread_status => { src => 'state' },
1064 },
1065 visible => [ qw(cxn thread thread_purpose thread_status)],
1066 filters => [ qw() ],
1067 sort_cols => 'cxn thread',
1068 sort_dir => '1',
1069 innodb => 'io',
1070 group_by => [],
1071 aggregate => 0,
1072 },
1073 log_statistics => {
1074 capt => 'Log Statistics',
1075 cust => {},
1076 cols => {
1077 cxn => { src => 'cxn' },
1078 last_chkp => { src => 'IB_lg_last_chkp' },
1079 log_flushed_to => { src => 'IB_lg_log_flushed_to' },
1080 log_ios_done => { src => 'IB_lg_log_ios_done' },
1081 log_ios_s => { src => 'IB_lg_log_ios_s' },
1082 log_seq_no => { src => 'IB_lg_log_seq_no' },
1083 pending_chkp_writes => { src => 'IB_lg_pending_chkp_writes' },
1084 pending_log_writes => { src => 'IB_lg_pending_log_writes' },
1085 },
1086 visible => [ qw(cxn log_seq_no log_flushed_to last_chkp log_ios_done log_ios_s)],
1087 filters => [],
1088 sort_cols => 'cxn',
1089 sort_dir => '1',
1090 innodb => 'lg',
1091 group_by => [],
1092 aggregate => 0,
1093 },
1094 master_status => {
1095 capt => 'Master Status',
1096 cust => {},
1097 cols => {
1098 cxn => { src => 'cxn' },
1099 binlog_do_db => { src => 'binlog_do_db' },
1100 binlog_ignore_db => { src => 'binlog_ignore_db' },
1101 master_file => { src => 'file' },
1102 master_pos => { src => 'position' },
1103 binlog_cache_overflow => { src => '(Binlog_cache_disk_use||0)/(Binlog_cache_use||1)', trans => [ qw(percent) ] },
1104 },
1105 visible => [ qw(cxn master_file master_pos binlog_cache_overflow)],
1106 filters => [ qw(cxn_is_master) ],
1107 sort_cols => 'cxn',
1108 sort_dir => '1',
1109 innodb => '',
1110 group_by => [],
1111 aggregate => 0,
1112 },
1113 pending_io => {
1114 capt => 'Pending I/O',
1115 cust => {},
1116 cols => {
1117 cxn => { src => 'cxn' },
1118 p_normal_aio_reads => { src => 'IB_io_pending_normal_aio_reads' },
1119 p_aio_writes => { src => 'IB_io_pending_aio_writes' },
1120 p_ibuf_aio_reads => { src => 'IB_io_pending_ibuf_aio_reads' },
1121 p_sync_ios => { src => 'IB_io_pending_sync_ios' },
1122 p_buf_pool_flushes => { src => 'IB_io_pending_buffer_pool_flushes' },
1123 p_log_flushes => { src => 'IB_io_pending_log_flushes' },
1124 p_log_ios => { src => 'IB_io_pending_log_ios' },
1125 p_preads => { src => 'IB_io_pending_preads' },
1126 p_pwrites => { src => 'IB_io_pending_pwrites' },
1127 },
1128 visible => [ qw(cxn p_normal_aio_reads p_aio_writes p_ibuf_aio_reads p_sync_ios p_log_flushes p_log_ios)],
1129 filters => [],
1130 sort_cols => 'cxn',
1131 sort_dir => '1',
1132 innodb => 'io',
1133 group_by => [],
1134 aggregate => 0,
1135 },
1136 open_tables => {
1137 capt => 'Open Tables',
1138 cust => {},
1139 cols => {
1140 cxn => { src => 'cxn' },
1141 db => { src => 'database' },
1142 tbl => { src => 'table' },
1143 num_times_open => { src => 'in_use' },
1144 is_name_locked => { src => 'name_locked' },
1145 },
1146 visible => [ qw(cxn db tbl num_times_open is_name_locked)],
1147 filters => [ qw(table_is_open) ],
1148 sort_cols => '-num_times_open cxn db tbl',
1149 sort_dir => '1',
1150 innodb => '',
1151 group_by => [],
1152 aggregate => 0,
1153 },
1154 page_statistics => {
1155 capt => 'Page Statistics',
1156 cust => {},
1157 cols => {
1158 cxn => { src => 'cxn' },
1159 pages_read => { src => 'IB_bp_pages_read' },
1160 pages_written => { src => 'IB_bp_pages_written' },
1161 pages_created => { src => 'IB_bp_pages_created' },
1162 page_reads_sec => { src => 'IB_bp_page_reads_sec' },
1163 page_writes_sec => { src => 'IB_bp_page_writes_sec' },
1164 page_creates_sec => { src => 'IB_bp_page_creates_sec' },
1165 },
1166 visible => [ qw(cxn pages_read pages_written pages_created page_reads_sec page_writes_sec page_creates_sec)],
1167 filters => [],
1168 sort_cols => 'cxn',
1169 sort_dir => '1',
1170 innodb => 'bp',
1171 group_by => [],
1172 aggregate => 0,
1173 },
1174 processlist => {
1175 capt => 'MySQL Process List',
1176 cust => {},
1177 cols => {
1178 cxn => { src => 'cxn', minw => 6, maxw => 10 },
1179 mysql_thread_id => { src => 'id', minw => 6, maxw => 0 },
1180 user => { src => 'user', minw => 5, maxw => 8 },
1181 hostname => { src => $exprs{Host}, minw => 13, maxw => 8, },
1182 port => { src => $exprs{Port}, minw => 0, maxw => 0, },
1183 host_and_port => { src => 'host', minw => 0, maxw => 0 },
1184 db => { src => 'db', minw => 6, maxw => 12 },
1185 cmd => { src => 'command', minw => 5, maxw => 0 },
1186 time => { src => 'time', minw => 5, maxw => 0, trans => [ qw(secs_to_time) ], agg => 'sum' },
1187 state => { src => 'state', minw => 0, maxw => 0 },
1188 info => { src => 'info', minw => 0, maxw => 0, trans => [ qw(no_ctrl_char) ] },
1189 cnt => { src => 'id', minw => 0, maxw => 0 },
1190 },
1191 visible => [ qw(cxn cmd cnt mysql_thread_id user hostname db time info)],
1192 filters => [ qw(hide_self hide_inactive hide_slave_io) ],
1193 sort_cols => '-time cxn hostname mysql_thread_id',
1194 sort_dir => '1',
1195 innodb => '',
1196 hide_caption => 1,
1197 colors => [
1198 { col => 'state', op => 'eq', arg => 'Locked', color => 'black on_red' },
1199 { col => 'cmd', op => 'eq', arg => 'Sleep', color => 'white' },
1200 { col => 'user', op => 'eq', arg => 'system user', color => 'white' },
1201 { col => 'cmd', op => 'eq', arg => 'Connect', color => 'white' },
1202 { col => 'cmd', op => 'eq', arg => 'Binlog Dump', color => 'white' },
1203 { col => 'time', op => '>', arg => 600, color => 'red' },
1204 { col => 'time', op => '>', arg => 120, color => 'yellow' },
1205 { col => 'time', op => '>', arg => 60, color => 'green' },
1206 { col => 'time', op => '>', arg => 30, color => 'cyan' },
1207 ],
1208 group_by => [qw(cxn cmd)],
1209 aggregate => 0,
1210 },
1211
1212 # TODO: some more columns:
1213 # kb_used=hdr='BufUsed' minw='0' num='0' src='percent(1 - ((Key_blocks_unused * key_cache_block_size) / (key_buffer_size||1)))' dec='0' trans='' tbl='q_header' just='-' user='1' maxw='0' label='User-defined'
1214 # retries=hdr='Retries' minw='0' num='0' src='Slave_retried_transactions' dec='0' trans='' tbl='slave_sql_status' just='-' user='1' maxw='0' label='User-defined'
1215 # thd=hdr='Thd' minw='0' num='0' src='Threads_connected' dec='0' trans='' tbl='slave_sql_status' just='-' user='1' maxw='0' label='User-defined'
1216
1217 q_header => {
1218 capt => 'Q-mode Header',
1219 cust => {},
1220 cols => {
1221 cxn => { src => 'cxn' },
1222 questions => { src => 'Questions' },
1223 qps => { src => 'Questions/Uptime_hires', dec => 1, trans => [qw(shorten)] },
1224 load => { src => $exprs{ServerLoad}, dec => 1, trans => [qw(shorten)] },
1225 slow => { src => 'Slow_queries', dec => 1, trans => [qw(shorten)] },
1226 q_cache_hit => { src => $exprs{QcacheHitRatio}, dec => 1, trans => [qw(percent)] },
1227 key_buffer_hit => { src => '1-(Key_reads/(Key_read_requests||1))', dec => 1, trans => [qw(percent)] },
1228 bps_in => { src => 'Bytes_received/Uptime_hires', dec => 1, trans => [qw(shorten)] },
1229 bps_out => { src => 'Bytes_sent/Uptime_hires', dec => 1, trans => [qw(shorten)] },
1230 when => { src => 'when' },
1231 },
1232 visible => [ qw(cxn when load qps slow q_cache_hit key_buffer_hit bps_in bps_out)],
1233 filters => [],
1234 sort_cols => 'when cxn',
1235 sort_dir => '1',
1236 innodb => '',
1237 hide_caption => 1,
1238 group_by => [],
1239 aggregate => 0,
1240 },
1241 row_operations => {
1242 capt => 'InnoDB Row Operations',
1243 cust => {},
1244 cols => {
1245 cxn => { src => 'cxn' },
1246 num_inserts => { src => 'IB_ro_num_rows_ins' },
1247 num_updates => { src => 'IB_ro_num_rows_upd' },
1248 num_reads => { src => 'IB_ro_num_rows_read' },
1249 num_deletes => { src => 'IB_ro_num_rows_del' },
1250 num_inserts_sec => { src => 'IB_ro_ins_sec' },
1251 num_updates_sec => { src => 'IB_ro_upd_sec' },
1252 num_reads_sec => { src => 'IB_ro_read_sec' },
1253 num_deletes_sec => { src => 'IB_ro_del_sec' },
1254 },
1255 visible => [ qw(cxn num_inserts num_updates num_reads num_deletes num_inserts_sec
1256 num_updates_sec num_reads_sec num_deletes_sec)],
1257 filters => [],
1258 sort_cols => 'cxn',
1259 sort_dir => '1',
1260 innodb => 'ro',
1261 group_by => [],
1262 aggregate => 0,
1263 },
1264 row_operation_misc => {
1265 capt => 'Row Operation Misc',
1266 cust => {},
1267 cols => {
1268 cxn => { src => 'cxn' },
1269 queries_in_queue => { src => 'IB_ro_queries_in_queue' },
1270 queries_inside => { src => 'IB_ro_queries_inside' },
1271 read_views_open => { src => 'IB_ro_read_views_open' },
1272 main_thread_id => { src => 'IB_ro_main_thread_id' },
1273 main_thread_proc_no => { src => 'IB_ro_main_thread_proc_no' },
1274 main_thread_state => { src => 'IB_ro_main_thread_state' },
1275 num_res_ext => { src => 'IB_ro_n_reserved_extents' },
1276 },
1277 visible => [ qw(cxn queries_in_queue queries_inside read_views_open main_thread_state)],
1278 filters => [],
1279 sort_cols => 'cxn',
1280 sort_dir => '1',
1281 innodb => 'ro',
1282 group_by => [],
1283 aggregate => 0,
1284 },
1285 semaphores => {
1286 capt => 'InnoDB Semaphores',
1287 cust => {},
1288 cols => {
1289 cxn => { src => 'cxn' },
1290 mutex_os_waits => { src => 'IB_sm_mutex_os_waits' },
1291 mutex_spin_rounds => { src => 'IB_sm_mutex_spin_rounds' },
1292 mutex_spin_waits => { src => 'IB_sm_mutex_spin_waits' },
1293 reservation_count => { src => 'IB_sm_reservation_count' },
1294 rw_excl_os_waits => { src => 'IB_sm_rw_excl_os_waits' },
1295 rw_excl_spins => { src => 'IB_sm_rw_excl_spins' },
1296 rw_shared_os_waits => { src => 'IB_sm_rw_shared_os_waits' },
1297 rw_shared_spins => { src => 'IB_sm_rw_shared_spins' },
1298 signal_count => { src => 'IB_sm_signal_count' },
1299 wait_array_size => { src => 'IB_sm_wait_array_size' },
1300 },
1301 visible => [ qw(cxn mutex_os_waits mutex_spin_waits mutex_spin_rounds
1302 rw_excl_os_waits rw_excl_spins rw_shared_os_waits rw_shared_spins
1303 signal_count reservation_count )],
1304 filters => [],
1305 sort_cols => 'cxn',
1306 sort_dir => '1',
1307 innodb => 'sm',
1308 group_by => [],
1309 aggregate => 0,
1310 },
1311 slave_io_status => {
1312 capt => 'Slave I/O Status',
1313 cust => {},
1314 cols => {
1315 cxn => { src => 'cxn' },
1316 connect_retry => { src => 'connect_retry' },
1317 master_host => { src => 'master_host', hdr => 'Master'},
1318 master_log_file => { src => 'master_log_file', hdr => 'File' },
1319 master_port => { src => 'master_port' },
1320 master_ssl_allowed => { src => 'master_ssl_allowed' },
1321 master_ssl_ca_file => { src => 'master_ssl_ca_file' },
1322 master_ssl_ca_path => { src => 'master_ssl_ca_path' },
1323 master_ssl_cert => { src => 'master_ssl_cert' },
1324 master_ssl_cipher => { src => 'master_ssl_cipher' },
1325 master_ssl_key => { src => 'master_ssl_key' },
1326 master_user => { src => 'master_user' },
1327 read_master_log_pos => { src => 'read_master_log_pos', hdr => 'Pos' },
1328 relay_log_size => { src => 'relay_log_space', trans => [qw(shorten)] },
1329 slave_io_running => { src => 'slave_io_running', hdr => 'On?' },
1330 slave_io_state => { src => 'slave_io_state', hdr => 'State' },
1331 },
1332 visible => [ qw(cxn master_host slave_io_running master_log_file relay_log_size read_master_log_pos slave_io_state)],
1333 filters => [ qw( cxn_is_slave ) ],
1334 sort_cols => 'slave_io_running cxn',
1335 colors => [
1336 { col => 'slave_io_running', op => 'ne', arg => 'Yes', color => 'black on_red' },
1337 ],
1338 sort_dir => '1',
1339 innodb => '',
1340 group_by => [],
1341 aggregate => 0,
1342 },
1343 slave_sql_status => {
1344 capt => 'Slave SQL Status',
1345 cust => {},
1346 cols => {
1347 cxn => { src => 'cxn' },
1348 exec_master_log_pos => { src => 'exec_master_log_pos', hdr => 'Master Pos' },
1349 last_errno => { src => 'last_errno' },
1350 last_error => { src => 'last_error' },
1351 master_host => { src => 'master_host', hdr => 'Master' },
1352 relay_log_file => { src => 'relay_log_file' },
1353 relay_log_pos => { src => 'relay_log_pos' },
1354 relay_log_size => { src => 'relay_log_space', trans => [qw(shorten)] },
1355 relay_master_log_file => { src => 'relay_master_log_file', hdr => 'Master File' },
1356 replicate_do_db => { src => 'replicate_do_db' },
1357 replicate_do_table => { src => 'replicate_do_table' },
1358 replicate_ignore_db => { src => 'replicate_ignore_db' },
1359 replicate_ignore_table => { src => 'replicate_ignore_table' },
1360 replicate_wild_do_table => { src => 'replicate_wild_do_table' },
1361 replicate_wild_ignore_table => { src => 'replicate_wild_ignore_table' },
1362 skip_counter => { src => 'skip_counter' },
1363 slave_sql_running => { src => 'slave_sql_running', hdr => 'On?' },
1364 until_condition => { src => 'until_condition' },
1365 until_log_file => { src => 'until_log_file' },
1366 until_log_pos => { src => 'until_log_pos' },
1367 time_behind_master => { src => 'seconds_behind_master', trans => [ qw(secs_to_time) ] },
1368 bytes_behind_master => { src => 'master_log_file && master_log_file eq relay_master_log_file ? read_master_log_pos - exec_master_log_pos : 0', trans => [qw(shorten)] },
1369 slave_catchup_rate => { src => $exprs{SlaveCatchupRate}, trans => [ qw(set_precision) ] },
1370 slave_open_temp_tables => { src => 'Slave_open_temp_tables' },
1371 },
1372 visible => [ qw(cxn master_host slave_sql_running time_behind_master slave_catchup_rate slave_open_temp_tables relay_log_pos last_error)],
1373 filters => [ qw( cxn_is_slave ) ],
1374 sort_cols => 'slave_sql_running cxn',
1375 sort_dir => '1',
1376 innodb => '',
1377 colors => [
1378 { col => 'slave_sql_running', op => 'ne', arg => 'Yes', color => 'black on_red' },
1379 { col => 'time_behind_master', op => '>', arg => 600, color => 'red' },
1380 { col => 'time_behind_master', op => '>', arg => 60, color => 'yellow' },
1381 { col => 'time_behind_master', op => '==', arg => 0, color => 'white' },
1382 ],
1383 group_by => [],
1384 aggregate => 0,
1385 },
1386 t_header => {
1387 capt => 'T-Mode Header',
1388 cust => {},
1389 cols => {
1390 cxn => { src => 'cxn' },
1391 dirty_bufs => { src => $exprs{DirtyBufs}, trans => [qw(percent)] },
1392 history_list_len => { src => 'IB_tx_history_list_len' },
1393 lock_structs => { src => 'IB_tx_num_lock_structs' },
1394 num_txns => { src => $exprs{NumTxns} },
1395 max_txn => { src => $exprs{MaxTxnTime}, trans => [qw(secs_to_time)] },
1396 undo_for => { src => 'IB_tx_purge_undo_for' },
1397 used_bufs => { src => $exprs{BufPoolFill}, trans => [qw(percent)]},
1398 versions => { src => $exprs{OldVersions} },
1399 },
1400 visible => [ qw(cxn history_list_len versions undo_for dirty_bufs used_bufs num_txns max_txn lock_structs)],
1401 filters => [ ],
1402 sort_cols => 'cxn',
1403 sort_dir => '1',
1404 innodb => '',
1405 colors => [],
1406 hide_caption => 1,
1407 group_by => [],
1408 aggregate => 0,
1409 },
1410 var_status => {
1411 capt => 'Variables & Status',
1412 cust => {},
1413 cols => {}, # Generated from current varset
1414 visible => [], # Generated from current varset
1415 filters => [],
1416 sort_cols => '',
1417 sort_dir => 1,
1418 innodb => '',
1419 temp => 1, # Do not persist to config file.
1420 hide_caption => 1,
1421 pivot => 0,
1422 group_by => [],
1423 aggregate => 0,
1424 },
1425 wait_array => {
1426 capt => 'InnoDB Wait Array',
1427 cust => {},
1428 cols => {
1429 cxn => { src => 'cxn' },
1430 thread => { src => 'thread' },
1431 waited_at_filename => { src => 'waited_at_filename' },
1432 waited_at_line => { src => 'waited_at_line' },
1433 'time' => { src => 'waited_secs', trans => [ qw(secs_to_time) ] },
1434 request_type => { src => 'request_type' },
1435 lock_mem_addr => { src => 'lock_mem_addr' },
1436 lock_cfile_name => { src => 'lock_cfile_name' },
1437 lock_cline => { src => 'lock_cline' },
1438 writer_thread => { src => 'writer_thread' },
1439 writer_lock_mode => { src => 'writer_lock_mode' },
1440 num_readers => { src => 'num_readers' },
1441 lock_var => { src => 'lock_var' },
1442 waiters_flag => { src => 'waiters_flag' },
1443 last_s_file_name => { src => 'last_s_file_name' },
1444 last_s_line => { src => 'last_s_line' },
1445 last_x_file_name => { src => 'last_x_file_name' },
1446 last_x_line => { src => 'last_x_line' },
1447 cell_waiting => { src => 'cell_waiting' },
1448 cell_event_set => { src => 'cell_event_set' },
1449 },
1450 visible => [ qw(cxn thread time waited_at_filename waited_at_line request_type num_readers lock_var waiters_flag cell_waiting cell_event_set)],
1451 filters => [],
1452 sort_cols => 'cxn -time',
1453 sort_dir => '1',
1454 innodb => 'sm',
1455 group_by => [],
1456 aggregate => 0,
1457 },
1458);
1459
1460# Initialize %tbl_meta from %columns and do some checks.
1461foreach my $table_name ( keys %tbl_meta ) {
1462 my $table = $tbl_meta{$table_name};
1463 my $cols = $table->{cols};
1464
1465 foreach my $col_name ( keys %$cols ) {
1466 my $col_def = $table->{cols}->{$col_name};
1467 die "I can't find a column named '$col_name' for '$table_name'" unless $columns{$col_name};
1468 $columns{$col_name}->{referenced} = 1;
1469
1470 foreach my $prop ( keys %col_props ) {
1471 # Each column gets non-existing values set from %columns or defaults from %col_props.
1472 if ( !$col_def->{$prop} ) {
1473 $col_def->{$prop}
1474 = defined($columns{$col_name}->{$prop})
1475 ? $columns{$col_name}->{$prop}
1476 : $col_props{$prop};
1477 }
1478 }
1479
1480 # Ensure transformations and aggregate functions are valid
1481 die "Unknown aggregate function '$col_def->{agg}' "
1482 . "for column '$col_name' in table '$table_name'"
1483 unless exists $agg_funcs{$col_def->{agg}};
1484 foreach my $trans ( @{$col_def->{trans}} ) {
1485 die "Unknown transformation '$trans' "
1486 . "for column '$col_name' in table '$table_name'"
1487 unless exists $trans_funcs{$trans};
1488 }
1489 }
1490
1491 # Ensure each column in visible and group_by exists in cols
1492 foreach my $place ( qw(visible group_by) ) {
1493 foreach my $col_name ( @{$table->{$place}} ) {
1494 if ( !exists $cols->{$col_name} ) {
1495 die "Column '$col_name' is listed in '$place' for '$table_name', but doesn't exist";
1496 }
1497 }
1498 }
1499
1500 # Compile sort and color subroutines
1501 $table->{sort_func} = make_sort_func($table);
1502 $table->{color_func} = make_color_func($table);
1503}
1504
1505# This is for code cleanup:
1506{
1507 my @unused_cols = grep { !$columns{$_}->{referenced} } sort keys %columns;
1508 if ( @unused_cols ) {
1509 die "The following columns are not used: "
1510 . join(' ', @unused_cols);
1511 }
1512}
1513
1514# ###########################################################################
1515# Operating modes {{{3
1516# ###########################################################################
1517my %modes = (
1518 B => {
1519 hdr => 'InnoDB Buffers',
1520 cust => {},
1521 note => 'Shows buffer info from InnoDB',
1522 action_for => {
1523 i => {
1524 action => sub { toggle_config('status_inc') },
1525 label => 'Toggle incremental status display',
1526 },
1527 },
1528 display_sub => \&display_B,
1529 connections => [],
1530 server_group => '',
1531 one_connection => 0,
1532 tables => [qw(buffer_pool page_statistics insert_buffers adaptive_hash_index)],
1533 visible_tables => [qw(buffer_pool page_statistics insert_buffers adaptive_hash_index)],
1534 },
1535 C => {
1536 hdr => 'Command Summary',
1537 cust => {},
1538 note => 'Shows relative magnitude of variables',
1539 action_for => {
1540 s => {
1541 action => sub { get_config_interactive('cmd_filter') },
1542 label => 'Choose variable prefix',
1543 },
1544 },
1545 display_sub => \&display_C,
1546 connections => [],
1547 server_group => '',
1548 one_connection => 0,
1549 tables => [qw(cmd_summary)],
1550 visible_tables => [qw(cmd_summary)],
1551 },
1552 D => {
1553 hdr => 'InnoDB Deadlocks',
1554 cust => {},
1555 note => 'View InnoDB deadlock information',
1556 action_for => {
1557 c => {
1558 action => sub { edit_table('deadlock_transactions') },
1559 label => 'Choose visible columns',
1560 },
1561 w => {
1562 action => \&create_deadlock,
1563 label => 'Wipe deadlock status info by creating a deadlock',
1564 },
1565 },
1566 display_sub => \&display_D,
1567 connections => [],
1568 server_group => '',
1569 one_connection => 0,
1570 tables => [qw(deadlock_transactions deadlock_locks)],
1571 visible_tables => [qw(deadlock_transactions deadlock_locks)],
1572 },
1573 F => {
1574 hdr => 'InnoDB FK Err',
1575 cust => {},
1576 note => 'View the latest InnoDB foreign key error',
1577 action_for => {},
1578 display_sub => \&display_F,
1579 connections => [],
1580 server_group => '',
1581 one_connection => 1,
1582 tables => [qw(fk_error)],
1583 visible_tables => [qw(fk_error)],
1584 },
1585 I => {
1586 hdr => 'InnoDB I/O Info',
1587 cust => {},
1588 note => 'Shows I/O info (i/o, log...) from InnoDB',
1589 action_for => {
1590 i => {
1591 action => sub { toggle_config('status_inc') },
1592 label => 'Toggle incremental status display',
1593 },
1594 },
1595 display_sub => \&display_I,
1596 connections => [],
1597 server_group => '',
1598 one_connection => 0,
1599 tables => [qw(io_threads pending_io file_io_misc log_statistics)],
1600 visible_tables => [qw(io_threads pending_io file_io_misc log_statistics)],
1601 },
1602 L => {
1603 hdr => 'Locks',
1604 cust => {},
1605 note => 'Shows transaction locks',
1606 action_for => {
1607 a => {
1608 action => sub { send_cmd_to_servers('CREATE TABLE IF NOT EXISTS test.innodb_lock_monitor(a int) ENGINE=InnoDB', 0, '', []); },
1609 label => 'Start the InnoDB Lock Monitor',
1610 },
1611 o => {
1612 action => sub { send_cmd_to_servers('DROP TABLE IF EXISTS test.innodb_lock_monitor', 0, '', []); },
1613 label => 'Stop the InnoDB Lock Monitor',
1614 },
1615 },
1616 display_sub => \&display_L,
1617 connections => [],
1618 server_group => '',
1619 one_connection => 0,
1620 tables => [qw(innodb_locks)],
1621 visible_tables => [qw(innodb_locks)],
1622 },
1623 M => {
1624 hdr => 'Replication Status',
1625 cust => {},
1626 note => 'Shows replication (master and slave) status',
1627 action_for => {
1628 a => {
1629 action => sub { send_cmd_to_servers('START SLAVE', 0, 'START SLAVE SQL_THREAD UNTIL MASTER_LOG_FILE = ?, MASTER_LOG_POS = ?', []); },
1630 label => 'Start slave(s)',
1631 },
1632 i => {
1633 action => sub { toggle_config('status_inc') },
1634 label => 'Toggle incremental status display',
1635 },
1636 o => {
1637 action => sub { send_cmd_to_servers('STOP SLAVE', 0, '', []); },
1638 label => 'Stop slave(s)',
1639 },
1640 b => {
1641 action => sub { purge_master_logs() },
1642 label => 'Purge unused master logs',
1643 },
1644 },
1645 display_sub => \&display_M,
1646 connections => [],
1647 server_group => '',
1648 one_connection => 0,
1649 tables => [qw(slave_sql_status slave_io_status master_status)],
1650 visible_tables => [qw(slave_sql_status slave_io_status master_status)],
1651 },
1652 O => {
1653 hdr => 'Open Tables',
1654 cust => {},
1655 note => 'Shows open tables in MySQL',
1656 action_for => {
1657 r => {
1658 action => sub { reverse_sort('open_tables'); },
1659 label => 'Reverse sort order',
1660 },
1661 s => {
1662 action => sub { choose_sort_cols('open_tables'); },
1663 label => "Choose sort column",
1664 },
1665 },
1666 display_sub => \&display_O,
1667 connections => [],
1668 server_group => '',
1669 one_connection => 0,
1670 tables => [qw(open_tables)],
1671 visible_tables => [qw(open_tables)],
1672 },
1673 Q => {
1674 hdr => 'Query List',
1675 cust => {},
1676 note => 'Shows queries from SHOW FULL PROCESSLIST',
1677 action_for => {
1678 a => {
1679 action => sub { toggle_filter('processlist', 'hide_self') },
1680 label => 'Toggle the innotop process',
1681 },
1682 c => {
1683 action => sub { edit_table('processlist') },
1684 label => 'Choose visible columns',
1685 },
1686 e => {
1687 action => sub { analyze_query('e'); },
1688 label => "Explain a thread's query",
1689 },
1690 f => {
1691 action => sub { analyze_query('f'); },
1692 label => "Show a thread's full query",
1693 },
1694 h => {
1695 action => sub { toggle_visible_table('Q', 'q_header') },
1696 label => 'Toggle the header on and off',
1697 },
1698 i => {
1699 action => sub { toggle_filter('processlist', 'hide_inactive') },
1700 label => 'Toggle idle processes',
1701 },
1702 k => {
1703 action => sub { kill_query('CONNECTION') },
1704 label => "Kill a query's connection",
1705 },
1706 r => {
1707 action => sub { reverse_sort('processlist'); },
1708 label => 'Reverse sort order',
1709 },
1710 s => {
1711 action => sub { choose_sort_cols('processlist'); },
1712 label => "Change the display's sort column",
1713 },
1714 x => {
1715 action => sub { kill_query('QUERY') },
1716 label => "Kill a query",
1717 },
1718 },
1719 display_sub => \&display_Q,
1720 connections => [],
1721 server_group => '',
1722 one_connection => 0,
1723 tables => [qw(q_header processlist)],
1724 visible_tables => [qw(q_header processlist)],
1725 },
1726 R => {
1727 hdr => 'InnoDB Row Ops',
1728 cust => {},
1729 note => 'Shows InnoDB row operation and semaphore info',
1730 action_for => {
1731 i => {
1732 action => sub { toggle_config('status_inc') },
1733 label => 'Toggle incremental status display',
1734 },
1735 },
1736 display_sub => \&display_R,
1737 connections => [],
1738 server_group => '',
1739 one_connection => 0,
1740 tables => [qw(row_operations row_operation_misc semaphores wait_array)],
1741 visible_tables => [qw(row_operations row_operation_misc semaphores wait_array)],
1742 },
1743 S => {
1744 hdr => 'Variables & Status',
1745 cust => {},
1746 note => 'Shows query load statistics a la vmstat',
1747 action_for => {
1748 '>' => {
1749 action => sub { switch_var_set('S_set', 1) },
1750 label => 'Switch to next variable set',
1751 },
1752 '<' => {
1753 action => sub { switch_var_set('S_set', -1) },
1754 label => 'Switch to prev variable set',
1755 },
1756 c => {
1757 action => sub {
1758 choose_var_set('S_set');
1759 start_S_mode();
1760 },
1761 label => "Choose which set to display",
1762 },
1763 e => {
1764 action => \&edit_current_var_set,
1765 label => 'Edit the current set of variables',
1766 },
1767 i => {
1768 action => sub { $clear_screen_sub->(); toggle_config('status_inc') },
1769 label => 'Toggle incremental status display',
1770 },
1771 '-' => {
1772 action => sub { set_display_precision(-1) },
1773 label => 'Decrease fractional display precision',
1774 },
1775 '+' => {
1776 action => sub { set_display_precision(1) },
1777 label => 'Increase fractional display precision',
1778 },
1779 g => {
1780 action => sub { set_s_mode('g') },
1781 label => 'Switch to graph (tload) view',
1782 },
1783 s => {
1784 action => sub { set_s_mode('s') },
1785 label => 'Switch to standard (vmstat) view',
1786 },
1787 v => {
1788 action => sub { set_s_mode('v') },
1789 label => 'Switch to pivoted view',
1790 },
1791 },
1792 display_sub => \&display_S,
1793 no_clear_screen => 1,
1794 connections => [],
1795 server_group => '',
1796 one_connection => 0,
1797 tables => [qw(var_status)],
1798 visible_tables => [qw(var_status)],
1799 },
1800 T => {
1801 hdr => 'InnoDB Txns',
1802 cust => {},
1803 note => 'Shows InnoDB transactions in top-like format',
1804 action_for => {
1805 a => {
1806 action => sub { toggle_filter('innodb_transactions', 'hide_self') },
1807 label => 'Toggle the innotop process',
1808 },
1809 c => {
1810 action => sub { edit_table('innodb_transactions') },
1811 label => 'Choose visible columns',
1812 },
1813 e => {
1814 action => sub { analyze_query('e'); },
1815 label => "Explain a thread's query",
1816 },
1817 f => {
1818 action => sub { analyze_query('f'); },
1819 label => "Show a thread's full query",
1820 },
1821 h => {
1822 action => sub { toggle_visible_table('T', 't_header') },
1823 label => 'Toggle the header on and off',
1824 },
1825 i => {
1826 action => sub { toggle_filter('innodb_transactions', 'hide_inactive') },
1827 label => 'Toggle inactive transactions',
1828 },
1829 k => {
1830 action => sub { kill_query('CONNECTION') },
1831 label => "Kill a transaction's connection",
1832 },
1833 r => {
1834 action => sub { reverse_sort('innodb_transactions'); },
1835 label => 'Reverse sort order',
1836 },
1837 s => {
1838 action => sub { choose_sort_cols('innodb_transactions'); },
1839 label => "Change the display's sort column",
1840 },
1841 x => {
1842 action => sub { kill_query('QUERY') },
1843 label => "Kill a query",
1844 },
1845 },
1846 display_sub => \&display_T,
1847 connections => [],
1848 server_group => '',
1849 one_connection => 0,
1850 tables => [qw(t_header innodb_transactions)],
1851 visible_tables => [qw(t_header innodb_transactions)],
1852 },
1853);
1854
1855# ###########################################################################
1856# Global key mappings {{{3
1857# Keyed on a single character, which is read from the keyboard. Uppercase
1858# letters switch modes. Lowercase letters access commands when in a mode.
1859# These can be overridden by action_for in %modes.
1860# ###########################################################################
1861my %action_for = (
1862 '$' => {
1863 action => \&edit_configuration,
1864 label => 'Edit configuration settings',
1865 },
1866 '?' => {
1867 action => \&display_help,
1868 label => 'Show help',
1869 },
1870 '!' => {
1871 action => \&display_license,
1872 label => 'Show license and warranty',
1873 },
1874 '^' => {
1875 action => \&edit_table,
1876 label => "Edit the displayed table(s)",
1877 },
1878 '#' => {
1879 action => \&choose_server_groups,
1880 label => 'Select/create server groups',
1881 },
1882 '@' => {
1883 action => \&choose_servers,
1884 label => 'Select/create server connections',
1885 },
1886 '/' => {
1887 action => \&add_quick_filter,
1888 label => 'Quickly filter what you see',
1889 },
1890 '\\' => {
1891 action => \&clear_quick_filters,
1892 label => 'Clear quick-filters',
1893 },
1894 '%' => {
1895 action => \&choose_filters,
1896 label => 'Choose and edit table filters',
1897 },
1898 "\t" => {
1899 action => \&next_server_group,
1900 label => 'Switch to the next server group',
1901 key => 'TAB',
1902 },
1903 '=' => {
1904 action => \&toggle_aggregate,
1905 label => 'Toggle aggregation',
1906 },
1907 # TODO: can these be auto-generated from %modes?
1908 B => {
1909 action => sub { switch_mode('B') },
1910 label => '',
1911 },
1912 C => {
1913 action => sub { switch_mode('C') },
1914 label => '',
1915 },
1916 D => {
1917 action => sub { switch_mode('D') },
1918 label => '',
1919 },
1920 F => {
1921 action => sub { switch_mode('F') },
1922 label => '',
1923 },
1924 I => {
1925 action => sub { switch_mode('I') },
1926 label => '',
1927 },
1928 L => {
1929 action => sub { switch_mode('L') },
1930 label => '',
1931 },
1932 M => {
1933 action => sub { switch_mode('M') },
1934 label => '',
1935 },
1936 O => {
1937 action => sub { switch_mode('O') },
1938 label => '',
1939 },
1940 Q => {
1941 action => sub { switch_mode('Q') },
1942 label => '',
1943 },
1944 R => {
1945 action => sub { switch_mode('R') },
1946 label => '',
1947 },
1948 S => {
1949 action => \&start_S_mode,
1950 label => '',
1951 },
1952 T => {
1953 action => sub { switch_mode('T') },
1954 label => '',
1955 },
1956 d => {
1957 action => sub { get_config_interactive('interval') },
1958 label => 'Change refresh interval',
1959 },
1960 n => { action => \&next_server, label => 'Switch to the next connection' },
1961 p => { action => \&pause, label => 'Pause innotop', },
1962 q => { action => \&finish, label => 'Quit innotop', },
1963);
1964
1965# ###########################################################################
1966# Sleep times after certain statements {{{3
1967# ###########################################################################
1968my %stmt_sleep_time_for = ();
1969
1970# ###########################################################################
1971# Config editor key mappings {{{3
1972# ###########################################################################
1973my %cfg_editor_action = (
1974 c => {
1975 note => 'Edit columns, etc in the displayed table(s)',
1976 func => \&edit_table,
1977 },
1978 g => {
1979 note => 'Edit general configuration',
1980 func => \&edit_configuration_variables,
1981 },
1982 k => {
1983 note => 'Edit row-coloring rules',
1984 func => \&edit_color_rules,
1985 },
1986 p => {
1987 note => 'Manage plugins',
1988 func => \&edit_plugins,
1989 },
1990 s => {
1991 note => 'Edit server groups',
1992 func => \&edit_server_groups,
1993 },
1994 S => {
1995 note => 'Edit SQL statement sleep delays',
1996 func => \&edit_stmt_sleep_times,
1997 },
1998 t => {
1999 note => 'Choose which table(s) to display in this mode',
2000 func => \&choose_mode_tables,
2001 },
2002);
2003
2004# ###########################################################################
2005# Color editor key mappings {{{3
2006# ###########################################################################
2007my %color_editor_action = (
2008 n => {
2009 note => 'Create a new color rule',
2010 func => sub {
2011 my ( $tbl, $idx ) = @_;
2012 my $meta = $tbl_meta{$tbl};
2013
2014 $clear_screen_sub->();
2015 my $col;
2016 do {
2017 $col = prompt_list(
2018 'Choose the target column for the rule',
2019 '',
2020 sub { return keys %{$meta->{cols}} },
2021 { map { $_ => $meta->{cols}->{$_}->{label} } keys %{$meta->{cols}} });
2022 } while ( !$col );
2023 ( $col ) = grep { $_ } split(/\W+/, $col);
2024 return $idx unless $col && exists $meta->{cols}->{$col};
2025
2026 $clear_screen_sub->();
2027 my $op;
2028 do {
2029 $op = prompt_list(
2030 'Choose the comparison operator for the rule',
2031 '',
2032 sub { return keys %comp_ops },
2033 { map { $_ => $comp_ops{$_} } keys %comp_ops } );
2034 } until ( $op );
2035 $op =~ s/\s+//g;
2036 return $idx unless $op && exists $comp_ops{$op};
2037
2038 my $arg;
2039 do {
2040 $arg = prompt('Specify an argument for the comparison');
2041 } until defined $arg;
2042
2043 my $color;
2044 do {
2045 $color = prompt_list(
2046 'Choose the color(s) the row should be when the rule matches',
2047 '',
2048 sub { return keys %ansicolors },
2049 { map { $_ => $_ } keys %ansicolors } );
2050 } until defined $color;
2051 $color = join(' ', unique(grep { exists $ansicolors{$_} } split(/\W+/, $color)));
2052 return $idx unless $color;
2053
2054 push @{$tbl_meta{$tbl}->{colors}}, {
2055 col => $col,
2056 op => $op,
2057 arg => $arg,
2058 color => $color
2059 };
2060 $tbl_meta{$tbl}->{cust}->{colors} = 1;
2061
2062 return $idx;
2063 },
2064 },
2065 d => {
2066 note => 'Remove the selected rule',
2067 func => sub {
2068 my ( $tbl, $idx ) = @_;
2069 my @rules = @{ $tbl_meta{$tbl}->{colors} };
2070 return 0 unless @rules > 0 && $idx < @rules && $idx >= 0;
2071 splice(@{$tbl_meta{$tbl}->{colors}}, $idx, 1);
2072 $tbl_meta{$tbl}->{cust}->{colors} = 1;
2073 return $idx == @rules ? $#rules : $idx;
2074 },
2075 },
2076 j => {
2077 note => 'Move highlight down one',
2078 func => sub {
2079 my ( $tbl, $idx ) = @_;
2080 my $num_rules = scalar @{$tbl_meta{$tbl}->{colors}};
2081 return ($idx + 1) % $num_rules;
2082 },
2083 },
2084 k => {
2085 note => 'Move highlight up one',
2086 func => sub {
2087 my ( $tbl, $idx ) = @_;
2088 my $num_rules = scalar @{$tbl_meta{$tbl}->{colors}};
2089 return ($idx - 1) % $num_rules;
2090 },
2091 },
2092 '+' => {
2093 note => 'Move selected rule up one',
2094 func => sub {
2095 my ( $tbl, $idx ) = @_;
2096 my $meta = $tbl_meta{$tbl};
2097 my $dest = $idx == 0 ? scalar(@{$meta->{colors}} - 1) : $idx - 1;
2098 my $temp = $meta->{colors}->[$idx];
2099 $meta->{colors}->[$idx] = $meta->{colors}->[$dest];
2100 $meta->{colors}->[$dest] = $temp;
2101 $meta->{cust}->{colors} = 1;
2102 return $dest;
2103 },
2104 },
2105 '-' => {
2106 note => 'Move selected rule down one',
2107 func => sub {
2108 my ( $tbl, $idx ) = @_;
2109 my $meta = $tbl_meta{$tbl};
2110 my $dest = $idx == scalar(@{$meta->{colors}} - 1) ? 0 : $idx + 1;
2111 my $temp = $meta->{colors}->[$idx];
2112 $meta->{colors}->[$idx] = $meta->{colors}->[$dest];
2113 $meta->{colors}->[$dest] = $temp;
2114 $meta->{cust}->{colors} = 1;
2115 return $dest;
2116 },
2117 },
2118);
2119
2120# ###########################################################################
2121# Plugin editor key mappings {{{3
2122# ###########################################################################
2123my %plugin_editor_action = (
2124 '*' => {
2125 note => 'Toggle selected plugin active/inactive',
2126 func => sub {
2127 my ( $plugins, $idx ) = @_;
2128 my $plugin = $plugins->[$idx];
2129 $plugin->{active} = $plugin->{active} ? 0 : 1;
2130 return $idx;
2131 },
2132 },
2133 j => {
2134 note => 'Move highlight down one',
2135 func => sub {
2136 my ( $plugins, $idx ) = @_;
2137 return ($idx + 1) % scalar(@$plugins);
2138 },
2139 },
2140 k => {
2141 note => 'Move highlight up one',
2142 func => sub {
2143 my ( $plugins, $idx ) = @_;
2144 return $idx == 0 ? @$plugins - 1 : $idx - 1;
2145 },
2146 },
2147);
2148
2149# ###########################################################################
2150# Table editor key mappings {{{3
2151# ###########################################################################
2152my %tbl_editor_action = (
2153 a => {
2154 note => 'Add a column to the table',
2155 func => sub {
2156 my ( $tbl, $col ) = @_;
2157 my @visible_cols = @{ $tbl_meta{$tbl}->{visible} };
2158 my %all_cols = %{ $tbl_meta{$tbl}->{cols} };
2159 delete @all_cols{@visible_cols};
2160 my $choice = prompt_list(
2161 'Choose a column',
2162 '',
2163 sub { return keys %all_cols; },
2164 { map { $_ => $all_cols{$_}->{label} || $all_cols{$_}->{hdr} } keys %all_cols });
2165 if ( $all_cols{$choice} ) {
2166 push @{$tbl_meta{$tbl}->{visible}}, $choice;
2167 $tbl_meta{$tbl}->{cust}->{visible} = 1;
2168 return $choice;
2169 }
2170 return $col;
2171 },
2172 },
2173 n => {
2174 note => 'Create a new column and add it to the table',
2175 func => sub {
2176 my ( $tbl, $col ) = @_;
2177
2178 $clear_screen_sub->();
2179 print word_wrap("Choose a name for the column. This name is not displayed, and is used only "
2180 . "for internal reference. It can contain only lowercase letters, numbers, "
2181 . "and underscores.");
2182 print "\n\n";
2183 do {
2184 $col = prompt("Enter column name");
2185 $col = '' if $col =~ m/[^a-z0-9_]/;
2186 } while ( !$col );
2187
2188 $clear_screen_sub->();
2189 my $hdr;
2190 do {
2191 $hdr = prompt("Enter column header");
2192 } while ( !$hdr );
2193
2194 $clear_screen_sub->();
2195 print "Choose a source for the column's data\n\n";
2196 my ( $src, $sub, $err );
2197 do {
2198 if ( $err ) {
2199 print "Error: $err\n\n";
2200 }
2201 $src = prompt("Enter column source");
2202 if ( $src ) {
2203 ( $sub, $err ) = compile_expr($src);
2204 }
2205 } until ( !$err);
2206
2207 # TODO: this duplicates %col_props.
2208 $tbl_meta{$tbl}->{cols}->{$col} = {
2209 hdr => $hdr,
2210 src => $src,
2211 just => '-',
2212 num => 0,
2213 label => 'User-defined',
2214 user => 1,
2215 tbl => $tbl,
2216 minw => 0,
2217 maxw => 0,
2218 trans => [],
2219 func => $sub,
2220 dec => 0,
2221 agg => 0,
2222 aggonly => 0,
2223 };
2224
2225 $tbl_meta{$tbl}->{visible} = [ unique(@{$tbl_meta{$tbl}->{visible}}, $col) ];
2226 $tbl_meta{$tbl}->{cust}->{visible} = 1;
2227 return $col;
2228 },
2229 },
2230 d => {
2231 note => 'Remove selected column',
2232 func => sub {
2233 my ( $tbl, $col ) = @_;
2234 my @visible_cols = @{ $tbl_meta{$tbl}->{visible} };
2235 my $idx = 0;
2236 return $col unless @visible_cols > 1;
2237 while ( $visible_cols[$idx] ne $col ) {
2238 $idx++;
2239 }
2240 $tbl_meta{$tbl}->{visible} = [ grep { $_ ne $col } @visible_cols ];
2241 $tbl_meta{$tbl}->{cust}->{visible} = 1;
2242 return $idx == $#visible_cols ? $visible_cols[$idx - 1] : $visible_cols[$idx + 1];
2243 },
2244 },
2245 e => {
2246 note => 'Edit selected column',
2247 func => sub {
2248 # TODO: make this editor hotkey-driven and give readline support.
2249 my ( $tbl, $col ) = @_;
2250 $clear_screen_sub->();
2251 my $meta = $tbl_meta{$tbl}->{cols}->{$col};
2252 my @prop = qw(hdr label src just num minw maxw trans agg); # TODO redundant
2253
2254 my $answer;
2255 do {
2256 # Do what the user asked...
2257 if ( $answer && grep { $_ eq $answer } @prop ) {
2258 # Some properties are arrays, others scalars.
2259 my $ini = ref $col_props{$answer} ? join(' ', @{$meta->{$answer}}) : $meta->{$answer};
2260 my $val = prompt("New value for $answer", undef, $ini);
2261 $val = [ split(' ', $val) ] if ref($col_props{$answer});
2262 if ( $answer eq 'trans' ) {
2263 $val = [ unique(grep{ exists $trans_funcs{$_} } @$val) ];
2264 }
2265 @{$meta}{$answer, 'user', 'tbl' } = ( $val, 1, $tbl );
2266 }
2267
2268 my @display_lines = (
2269 '',
2270 "You are editing column $tbl.$col.\n",
2271 );
2272
2273 push @display_lines, create_table2(
2274 \@prop,
2275 { map { $_ => $_ } @prop },
2276 { map { $_ => ref $meta->{$_} eq 'ARRAY' ? join(' ', @{$meta->{$_}})
2277 : ref $meta->{$_} ? '[expression code]'
2278 : $meta->{$_}
2279 } @prop
2280 },
2281 { sep => ' ' });
2282 draw_screen(\@display_lines, { raw => 1 });
2283 print "\n\n"; # One to add space, one to clear readline artifacts
2284 $answer = prompt('Edit what? (q to quit)');
2285 } while ( $answer ne 'q' );
2286
2287 return $col;
2288 },
2289 },
2290 j => {
2291 note => 'Move highlight down one',
2292 func => sub {
2293 my ( $tbl, $col ) = @_;
2294 my @visible_cols = @{ $tbl_meta{$tbl}->{visible} };
2295 my $idx = 0;
2296 while ( $visible_cols[$idx] ne $col ) {
2297 $idx++;
2298 }
2299 return $visible_cols[ ($idx + 1) % @visible_cols ];
2300 },
2301 },
2302 k => {
2303 note => 'Move highlight up one',
2304 func => sub {
2305 my ( $tbl, $col ) = @_;
2306 my @visible_cols = @{ $tbl_meta{$tbl}->{visible} };
2307 my $idx = 0;
2308 while ( $visible_cols[$idx] ne $col ) {
2309 $idx++;
2310 }
2311 return $visible_cols[ $idx - 1 ];
2312 },
2313 },
2314 '+' => {
2315 note => 'Move selected column up one',
2316 func => sub {
2317 my ( $tbl, $col ) = @_;
2318 my $meta = $tbl_meta{$tbl};
2319 my @visible_cols = @{$meta->{visible}};
2320 my $idx = 0;
2321 while ( $visible_cols[$idx] ne $col ) {
2322 $idx++;
2323 }
2324 if ( $idx ) {
2325 $visible_cols[$idx] = $visible_cols[$idx - 1];
2326 $visible_cols[$idx - 1] = $col;
2327 $meta->{visible} = \@visible_cols;
2328 }
2329 else {
2330 shift @{$meta->{visible}};
2331 push @{$meta->{visible}}, $col;
2332 }
2333 $meta->{cust}->{visible} = 1;
2334 return $col;
2335 },
2336 },
2337 '-' => {
2338 note => 'Move selected column down one',
2339 func => sub {
2340 my ( $tbl, $col ) = @_;
2341 my $meta = $tbl_meta{$tbl};
2342 my @visible_cols = @{$meta->{visible}};
2343 my $idx = 0;
2344 while ( $visible_cols[$idx] ne $col ) {
2345 $idx++;
2346 }
2347 if ( $idx == $#visible_cols ) {
2348 unshift @{$meta->{visible}}, $col;
2349 pop @{$meta->{visible}};
2350 }
2351 else {
2352 $visible_cols[$idx] = $visible_cols[$idx + 1];
2353 $visible_cols[$idx + 1] = $col;
2354 $meta->{visible} = \@visible_cols;
2355 }
2356 $meta->{cust}->{visible} = 1;
2357 return $col;
2358 },
2359 },
2360 f => {
2361 note => 'Choose filters',
2362 func => sub {
2363 my ( $tbl, $col ) = @_;
2364 choose_filters($tbl);
2365 return $col;
2366 },
2367 },
2368 o => {
2369 note => 'Edit color rules',
2370 func => sub {
2371 my ( $tbl, $col ) = @_;
2372 edit_color_rules($tbl);
2373 return $col;
2374 },
2375 },
2376 s => {
2377 note => 'Choose sort columns',
2378 func => sub {
2379 my ( $tbl, $col ) = @_;
2380 choose_sort_cols($tbl);
2381 return $col;
2382 },
2383 },
2384 g => {
2385 note => 'Choose group-by (aggregate) columns',
2386 func => sub {
2387 my ( $tbl, $col ) = @_;
2388 choose_group_cols($tbl);
2389 return $col;
2390 },
2391 },
2392);
2393
2394# ###########################################################################
2395# Global variables and environment {{{2
2396# ###########################################################################
2397
2398my @this_term_size; # w_chars, h_chars, w_pix, h_pix
2399my @last_term_size; # w_chars, h_chars, w_pix, h_pix
2400my $char;
2401my $windows = $OSNAME =~ m/MSWin/;
2402my $have_color = 0;
2403my $MAX_ULONG = 4294967295; # 2^32-1
2404my $num_regex = qr/^[+-]?(?=\d|\.)\d*(?:\.\d+)?(?:E[+-]?\d+|)$/i;
2405my $int_regex = qr/^\d+$/;
2406my $bool_regex = qr/^[01]$/;
2407my $term = undef;
2408my $file = undef; # File to watch for InnoDB monitor output
2409my $file_mtime = undef; # Status of watched file
2410my $file_data = undef; # Last chunk of text read from file
2411my $innodb_parser = InnoDBParser->new;
2412
2413my $nonfatal_errs = join('|',
2414 'Access denied for user',
2415 'Unknown MySQL server host',
2416 'Unknown database',
2417 'Can\'t connect to local MySQL server through socket',
2418 'Can\'t connect to MySQL server on',
2419 'MySQL server has gone away',
2420 'Cannot call SHOW INNODB STATUS',
2421 'Access denied',
2422 'AutoCommit',
2423);
2424
2425if ( !$opts{n} ) {
2426 require Term::ReadLine;
2427 $term = Term::ReadLine->new('innotop');
2428}
2429
2430# Stores status, variables, innodb status, master/slave status etc.
2431# Keyed on connection name. Each entry is a hashref of current and past data sets,
2432# keyed on clock tick.
2433my %vars;
2434my %info_gotten = (); # Which things have been retrieved for the current clock tick.
2435
2436# Stores info on currently displayed queries: cxn, connection ID, query text.
2437my @current_queries;
2438
2439my $lines_printed = 0;
2440my $clock = 0; # Incremented with every wake-sleep cycle
2441my $clearing_deadlocks = 0;
2442
2443# Find the home directory; it's different on different OSes.
2444my $homepath = $ENV{HOME} || $ENV{HOMEPATH} || $ENV{USERPROFILE} || '.';
2445
2446# If terminal coloring is available, use it. The only function I want from
2447# the module is the colored() function.
2448eval {
2449 if ( !$opts{n} ) {
2450 if ( $windows ) {
2451 require Win32::Console::ANSI;
2452 }
2453 require Term::ANSIColor;
2454 import Term::ANSIColor qw(colored);
2455 $have_color = 1;
2456 }
2457};
2458if ( $EVAL_ERROR || $opts{n} ) {
2459 # If there was an error, manufacture my own colored() function that does no
2460 # coloring.
2461 *colored = sub { pop @_; @_; };
2462}
2463
2464if ( $opts{n} ) {
2465 $clear_screen_sub = sub {};
2466}
2467elsif ( $windows ) {
2468 $clear_screen_sub = sub { $lines_printed = 0; system("cls") };
2469}
2470else {
2471 my $clear = `clear`;
2472 $clear_screen_sub = sub { $lines_printed = 0; print $clear };
2473}
2474
2475# ###########################################################################
2476# Config storage. {{{2
2477# ###########################################################################
2478my %config = (
2479 color => {
2480 val => $have_color,
2481 note => 'Whether to use terminal coloring',
2482 conf => 'ALL',
2483 pat => $bool_regex,
2484 },
2485 cmd_filter => {
2486 val => 'Com_',
2487 note => 'Prefix for values in C mode',
2488 conf => [qw(C)],
2489 },
2490 plugin_dir => {
2491 val => "$homepath/.innotop/plugins",
2492 note => 'Directory where plugins can be found',
2493 conf => 'ALL',
2494 },
2495 show_percent => {
2496 val => 1,
2497 note => 'Show the % symbol after percentages',
2498 conf => 'ALL',
2499 pat => $bool_regex,
2500 },
2501 skip_innodb => {
2502 val => 0,
2503 note => 'Disable SHOW INNODB STATUS',
2504 conf => 'ALL',
2505 pat => $bool_regex,
2506 },
2507 S_func => {
2508 val => 's',
2509 note => 'What to display in S mode: graph, status, pivoted status',
2510 conf => [qw(S)],
2511 pat => qr/^[gsv]$/,
2512 },
2513 cxn_timeout => {
2514 val => 28800,
2515 note => 'Connection timeout for keeping unused connections alive',
2516 conf => 'ALL',
2517 pat => $int_regex,
2518 },
2519 graph_char => {
2520 val => '*',
2521 note => 'Character for drawing graphs',
2522 conf => [ qw(S) ],
2523 pat => qr/^.$/,
2524 },
2525 show_cxn_errors_in_tbl => {
2526 val => 1,
2527 note => 'Whether to display connection errors as rows in the table',
2528 conf => 'ALL',
2529 pat => $bool_regex,
2530 },
2531 hide_hdr => {
2532 val => 0,
2533 note => 'Whether to show column headers',
2534 conf => 'ALL',
2535 pat => $bool_regex,
2536 },
2537 show_cxn_errors => {
2538 val => 1,
2539 note => 'Whether to print connection errors to STDOUT',
2540 conf => 'ALL',
2541 pat => $bool_regex,
2542 },
2543 readonly => {
2544 val => 0,
2545 note => 'Whether the config file is read-only',
2546 conf => [ qw() ],
2547 pat => $bool_regex,
2548 },
2549 global => {
2550 val => 1,
2551 note => 'Whether to show GLOBAL variables and status',
2552 conf => 'ALL',
2553 pat => $bool_regex,
2554 },
2555 header_highlight => {
2556 val => 'bold',
2557 note => 'How to highlight table column headers',
2558 conf => 'ALL',
2559 pat => qr/^(?:bold|underline)$/,
2560 },
2561 display_table_captions => {
2562 val => 1,
2563 note => 'Whether to put captions on tables',
2564 conf => 'ALL',
2565 pat => $bool_regex,
2566 },
2567 charset => {
2568 val => 'ascii',
2569 note => 'What type of characters should be displayed in queries (ascii, unicode, none)',
2570 conf => 'ALL',
2571 pat => qr/^(?:ascii|unicode|none)$/,
2572 },
2573 auto_wipe_dl => {
2574 val => 0,
2575 note => 'Whether to auto-wipe InnoDB deadlocks',
2576 conf => 'ALL',
2577 pat => $bool_regex,
2578 },
2579 max_height => {
2580 val => 30,
2581 note => '[Win32] Max window height',
2582 conf => 'ALL',
2583 },
2584 debug => {
2585 val => 0,
2586 pat => $bool_regex,
2587 note => 'Debug mode (more verbose errors, uses more memory)',
2588 conf => 'ALL',
2589 },
2590 num_digits => {
2591 val => 2,
2592 pat => $int_regex,
2593 note => 'How many digits to show in fractional numbers and percents',
2594 conf => 'ALL',
2595 },
2596 debugfile => {
2597 val => "$homepath/.innotop/core_dump",
2598 note => 'A debug file in case you are interested in error output',
2599 },
2600 show_statusbar => {
2601 val => 1,
2602 pat => $bool_regex,
2603 note => 'Whether to show the status bar in the display',
2604 conf => 'ALL',
2605 },
2606 mode => {
2607 val => "T",
2608 note => "Which mode to start in",
2609 cmdline => 1,
2610 },
2611 status_inc => {
2612 val => 0,
2613 note => 'Whether to show raw or incremental values for status variables',
2614 pat => $bool_regex,
2615 },
2616 interval => {
2617 val => 10,
2618 pat => qr/^(?:(?:\d*?[1-9]\d*(?:\.\d*)?)|(?:\d*\.\d*?[1-9]\d*))$/,
2619 note => "The interval at which the display will be refreshed. Fractional values allowed.",
2620 },
2621 num_status_sets => {
2622 val => 9,
2623 pat => $int_regex,
2624 note => 'How many sets of STATUS and VARIABLES values to show',
2625 conf => [ qw(S) ],
2626 },
2627 S_set => {
2628 val => 'general',
2629 pat => qr/^\w+$/,
2630 note => 'Which set of variables to display in S (Variables & Status) mode',
2631 conf => [ qw(S) ],
2632 },
2633);
2634
2635# ###########################################################################
2636# Config file sections {{{2
2637# The configuration file is broken up into sections like a .ini file. This
2638# variable defines those sections and the subroutines responsible for reading
2639# and writing them.
2640# ###########################################################################
2641my %config_file_sections = (
2642 plugins => {
2643 reader => \&load_config_plugins,
2644 writer => \&save_config_plugins,
2645 },
2646 group_by => {
2647 reader => \&load_config_group_by,
2648 writer => \&save_config_group_by,
2649 },
2650 filters => {
2651 reader => \&load_config_filters,
2652 writer => \&save_config_filters,
2653 },
2654 active_filters => {
2655 reader => \&load_config_active_filters,
2656 writer => \&save_config_active_filters,
2657 },
2658 visible_tables => {
2659 reader => \&load_config_visible_tables,
2660 writer => \&save_config_visible_tables,
2661 },
2662 sort_cols => {
2663 reader => \&load_config_sort_cols,
2664 writer => \&save_config_sort_cols,
2665 },
2666 active_columns => {
2667 reader => \&load_config_active_columns,
2668 writer => \&save_config_active_columns,
2669 },
2670 tbl_meta => {
2671 reader => \&load_config_tbl_meta,
2672 writer => \&save_config_tbl_meta,
2673 },
2674 general => {
2675 reader => \&load_config_config,
2676 writer => \&save_config_config,
2677 },
2678 connections => {
2679 reader => \&load_config_connections,
2680 writer => \&save_config_connections,
2681 },
2682 active_connections => {
2683 reader => \&load_config_active_connections,
2684 writer => \&save_config_active_connections,
2685 },
2686 server_groups => {
2687 reader => \&load_config_server_groups,
2688 writer => \&save_config_server_groups,
2689 },
2690 active_server_groups => {
2691 reader => \&load_config_active_server_groups,
2692 writer => \&save_config_active_server_groups,
2693 },
2694 max_values_seen => {
2695 reader => \&load_config_mvs,
2696 writer => \&save_config_mvs,
2697 },
2698 varsets => {
2699 reader => \&load_config_varsets,
2700 writer => \&save_config_varsets,
2701 },
2702 colors => {
2703 reader => \&load_config_colors,
2704 writer => \&save_config_colors,
2705 },
2706 stmt_sleep_times => {
2707 reader => \&load_config_stmt_sleep_times,
2708 writer => \&save_config_stmt_sleep_times,
2709 },
2710);
2711
2712# Config file sections have some dependencies, so they have to be read/written in order.
2713my @ordered_config_file_sections = qw(general plugins filters active_filters tbl_meta
2714 connections active_connections server_groups active_server_groups max_values_seen
2715 active_columns sort_cols visible_tables varsets colors stmt_sleep_times
2716 group_by);
2717
2718# All events for which plugins may register themselves. Entries are arrayrefs.
2719my %event_listener_for = map { $_ => [] }
2720 qw(
2721 extract_values
2722 set_to_tbl_pre_filter set_to_tbl_pre_sort set_to_tbl_pre_group
2723 set_to_tbl_pre_colorize set_to_tbl_pre_transform set_to_tbl_pre_pivot
2724 set_to_tbl_pre_create set_to_tbl_post_create
2725 draw_screen
2726 );
2727
2728# All variables to which plugins have access.
2729my %pluggable_vars = (
2730 action_for => \%action_for,
2731 agg_funcs => \%agg_funcs,
2732 config => \%config,
2733 connections => \%connections,
2734 dbhs => \%dbhs,
2735 filters => \%filters,
2736 modes => \%modes,
2737 server_groups => \%server_groups,
2738 tbl_meta => \%tbl_meta,
2739 trans_funcs => \%trans_funcs,
2740 var_sets => \%var_sets,
2741);
2742
2743# ###########################################################################
2744# Contains logic to generate prepared statements for a given function for a
2745# given DB connection. Returns a $sth.
2746# ###########################################################################
2747my %stmt_maker_for = (
2748 INNODB_STATUS => sub {
2749 my ( $dbh ) = @_;
2750 return $dbh->prepare(version_ge( $dbh, '5.0.0' )
2751 ? 'SHOW ENGINE INNODB STATUS'
2752 : 'SHOW INNODB STATUS');
2753 },
2754 SHOW_VARIABLES => sub {
2755 my ( $dbh ) = @_;
2756 return $dbh->prepare($config{global}->{val} && version_ge( $dbh, '4.0.3' )
2757 ? 'SHOW GLOBAL VARIABLES'
2758 : 'SHOW VARIABLES');
2759 },
2760 SHOW_STATUS => sub {
2761 my ( $dbh ) = @_;
2762 return $dbh->prepare($config{global}->{val} && version_ge( $dbh, '5.0.2' )
2763 ? 'SHOW GLOBAL STATUS'
2764 : 'SHOW STATUS');
2765 },
2766 KILL_QUERY => sub {
2767 my ( $dbh ) = @_;
2768 return $dbh->prepare(version_ge( $dbh, '5.0.0' )
2769 ? 'KILL QUERY ?'
2770 : 'KILL ?');
2771 },
2772 SHOW_MASTER_LOGS => sub {
2773 my ( $dbh ) = @_;
2774 return $dbh->prepare('SHOW MASTER LOGS');
2775 },
2776 SHOW_MASTER_STATUS => sub {
2777 my ( $dbh ) = @_;
2778 return $dbh->prepare('SHOW MASTER STATUS');
2779 },
2780 SHOW_SLAVE_STATUS => sub {
2781 my ( $dbh ) = @_;
2782 return $dbh->prepare('SHOW SLAVE STATUS');
2783 },
2784 KILL_CONNECTION => sub {
2785 my ( $dbh ) = @_;
2786 return $dbh->prepare(version_ge( $dbh, '5.0.0' )
2787 ? 'KILL CONNECTION ?'
2788 : 'KILL ?');
2789 },
2790 OPEN_TABLES => sub {
2791 my ( $dbh ) = @_;
2792 return version_ge($dbh, '4.0.0')
2793 ? $dbh->prepare('SHOW OPEN TABLES')
2794 : undef;
2795 },
2796 PROCESSLIST => sub {
2797 my ( $dbh ) = @_;
2798 return $dbh->prepare('SHOW FULL PROCESSLIST');
2799 },
2800);
2801
2802# Plugins!
2803my %plugins = (
2804);
2805
2806# ###########################################################################
2807# Run the program {{{1
2808# ###########################################################################
2809
2810# This config variable is only useful for MS Windows because its terminal
2811# can't tell how tall it is.
2812if ( !$windows ) {
2813 delete $config{max_height};
2814}
2815
2816# Try to lower my priority.
2817eval { setpriority(0, 0, getpriority(0, 0) + 10); };
2818
2819# Print stuff to the screen immediately, don't wait for a newline.
2820$OUTPUT_AUTOFLUSH = 1;
2821
2822# Clear the screen and load the configuration.
2823$clear_screen_sub->();
2824load_config();
2825post_process_tbl_meta();
2826
2827# Make sure no changes are written to config file in non-interactive mode.
2828if ( $opts{n} ) {
2829 $config{readonly}->{val} = 1;
2830}
2831
2832eval {
2833
2834 # Open the file for InnoDB status
2835 if ( @ARGV ) {
2836 my $filename = shift @ARGV;
2837 open $file, "<", $filename
2838 or die "Cannot open '$filename': $OS_ERROR";
2839 }
2840
2841 # In certain modes we might have to collect data for two cycles
2842 # before printing anything out, so we need to bump up the count one.
2843 if ( $opts{n} && $opts{count} && $config{status_inc}->{val}
2844 && $config{mode}->{val} =~ m/[S]/ )
2845 {
2846 $opts{count}++;
2847 }
2848
2849 while (++$clock) {
2850
2851 my $mode = $config{mode}->{val} || 'T';
2852 if ( !$modes{$mode} ) {
2853 die "Mode '$mode' doesn't exist; try one of these:\n"
2854 . join("\n", map { " $_ $modes{$_}->{hdr}" } sort keys %modes)
2855 . "\n";
2856 }
2857
2858 if ( !$opts{n} ) {
2859 @last_term_size = @this_term_size;
2860 @this_term_size = Term::ReadKey::GetTerminalSize(\*STDOUT);
2861 if ( $windows ) {
2862 $this_term_size[0]--;
2863 $this_term_size[1]
2864 = min($this_term_size[1], $config{max_height}->{val});
2865 }
2866 die("Can't read terminal size") unless @this_term_size;
2867 }
2868
2869 # If there's no connection to a database server, we need to fix that...
2870 if ( !%connections ) {
2871 print "You have not defined any database connections.\n\n";
2872 add_new_dsn();
2873 }
2874
2875 # See whether there are any connections defined for this mode. If there's only one
2876 # connection total, assume the user wants to just use innotop for a single server
2877 # and don't ask which server to connect to. Also, if we're monitoring from a file,
2878 # we just use the first connection.
2879 if ( !get_connections() ) {
2880 if ( $file || 1 == scalar keys %connections ) {
2881 $modes{$config{mode}->{val}}->{connections} = [ keys %connections ];
2882 }
2883 else {
2884 choose_connections();
2885 }
2886 }
2887
2888 # Term::ReadLine might have re-set $OUTPUT_AUTOFLUSH.
2889 $OUTPUT_AUTOFLUSH = 1;
2890
2891 # Prune old data
2892 my $sets = $config{num_status_sets}->{val};
2893 foreach my $store ( values %vars ) {
2894 delete @{$store}{ grep { $_ < $clock - $sets } keys %$store };
2895 }
2896 %info_gotten = ();
2897
2898 # Call the subroutine to display this mode.
2899 $modes{$mode}->{display_sub}->();
2900
2901 # It may be time to quit now.
2902 if ( $opts{count} && $clock >= $opts{count} ) {
2903 finish();
2904 }
2905
2906 # Wait for a bit.
2907 if ( $opts{n} ) {
2908 sleep($config{interval}->{val});
2909 }
2910 else {
2911 ReadMode('cbreak');
2912 $char = ReadKey($config{interval}->{val});
2913 ReadMode('normal');
2914 }
2915
2916 # Handle whatever action the key indicates.
2917 do_key_action();
2918
2919 }
2920};
2921if ( $EVAL_ERROR ) {
2922 core_dump( $EVAL_ERROR );
2923}
2924finish();
2925
2926# Subroutines {{{1
2927# Mode functions{{{2
2928# switch_mode {{{3
2929sub switch_mode {
2930 my $mode = shift;
2931 $config{mode}->{val} = $mode;
2932}
2933
2934# Prompting functions {{{2
2935# prompt_list {{{3
2936# Prompts the user for a value, given a question, initial value,
2937# a completion function and a hashref of hints.
2938sub prompt_list {
2939 die "Can't call in non-interactive mode" if $opts{n};
2940 my ( $question, $init, $completion, $hints ) = @_;
2941 if ( $hints ) {
2942 # Figure out how wide the table will be
2943 my $max_name = max(map { length($_) } keys %$hints );
2944 $max_name ||= 0;
2945 $max_name += 3;
2946 my @meta_rows = create_table2(
2947 [ sort keys %$hints ],
2948 { map { $_ => $_ } keys %$hints },
2949 { map { $_ => trunc($hints->{$_}, $this_term_size[0] - $max_name) } keys %$hints },
2950 { sep => ' ' });
2951 if (@meta_rows > 10) {
2952 # Try to split and stack the meta rows next to each other
2953 my $split = int(@meta_rows / 2);
2954 @meta_rows = stack_next(
2955 [@meta_rows[0..$split - 1]],
2956 [@meta_rows[$split..$#meta_rows]],
2957 { pad => ' | '},
2958 );
2959 }
2960 print join( "\n",
2961 '',
2962 map { ref $_ ? colored(@$_) : $_ } create_caption('Choose from', @meta_rows), ''),
2963 "\n";
2964 }
2965 $term->Attribs->{completion_function} = $completion;
2966 my $answer = $term->readline("$question: ", $init);
2967 $OUTPUT_AUTOFLUSH = 1;
2968 $answer = '' if !defined($answer);
2969 $answer =~ s/\s+$//;
2970 return $answer;
2971}
2972
2973# prompt {{{3
2974# Prints out a prompt and reads from the keyboard, then validates with the
2975# validation regex until the input is correct.
2976sub prompt {
2977 die "Can't call in non-interactive mode" if $opts{n};
2978 my ( $prompt, $regex, $init, $completion ) = @_;
2979 my $response;
2980 my $success = 0;
2981 do {
2982 if ( $completion ) {
2983 $term->Attribs->{completion_function} = $completion;
2984 }
2985 $response = $term->readline("$prompt: ", $init);
2986 if ( $regex && $response !~ m/$regex/ ) {
2987 print "Invalid response.\n\n";
2988 }
2989 else {
2990 $success = 1;
2991 }
2992 } while ( !$success );
2993 $OUTPUT_AUTOFLUSH = 1;
2994 $response =~ s/\s+$//;
2995 return $response;
2996}
2997
2998# prompt_noecho {{{3
2999# Unfortunately, suppressing echo with Term::ReadLine isn't reliable; the user might not
3000# have that library, or it might not support that feature.
3001sub prompt_noecho {
3002 my ( $prompt ) = @_;
3003 print colored("$prompt: ", 'underline');
3004 my $response;
3005 ReadMode('noecho');
3006 $response = <STDIN>;
3007 chomp($response);
3008 ReadMode('normal');
3009 return $response;
3010}
3011
3012# do_key_action {{{3
3013# Depending on whether a key was read, do something. Keys have certain
3014# actions defined in lookup tables. Each mode may have its own lookup table,
3015# which trumps the global table -- so keys can be context-sensitive. The key
3016# may be read and written in a subroutine, so it's a global.
3017sub do_key_action {
3018 if ( defined $char ) {
3019 my $mode = $config{mode}->{val};
3020 my $action
3021 = defined($modes{$mode}->{action_for}->{$char})
3022 ? $modes{$mode}->{action_for}->{$char}->{action}
3023 : defined($action_for{$char})
3024 ? $action_for{$char}->{action}
3025 : sub{};
3026 $action->();
3027 }
3028}
3029
3030# pause {{{3
3031sub pause {
3032 die "Can't call in non-interactive mode" if $opts{n};
3033 my $msg = shift;
3034 print defined($msg) ? "\n$msg" : "\nPress any key to continue";
3035 ReadMode('cbreak');
3036 my $char = ReadKey(0);
3037 ReadMode('normal');
3038 return $char;
3039}
3040
3041# reverse_sort {{{3
3042sub reverse_sort {
3043 my $tbl = shift;
3044 $tbl_meta{$tbl}->{sort_dir} *= -1;
3045}
3046
3047# select_cxn {{{3
3048# Selects connection(s). If the mode (or argument list) has only one, returns
3049# it without prompt.
3050sub select_cxn {
3051 my ( $prompt, @cxns ) = @_;
3052 if ( !@cxns ) {
3053 @cxns = get_connections();
3054 }
3055 if ( @cxns == 1 ) {
3056 return $cxns[0];
3057 }
3058 my $choices = prompt_list(
3059 $prompt,
3060 $cxns[0],
3061 sub{ return @cxns },
3062 { map { $_ => $connections{$_}->{dsn} } @cxns });
3063 my @result = unique(grep { my $a = $_; grep { $_ eq $a } @cxns } split(/\s+/, $choices));
3064 return @result;
3065}
3066
3067# kill_query {{{3
3068# Kills a connection, or on new versions, optionally a query but not connection.
3069sub kill_query {
3070 my ( $q_or_c ) = @_;
3071
3072 my $info = choose_thread(
3073 sub { 1 },
3074 'Select a thread to kill the ' . $q_or_c,
3075 );
3076 return unless $info;
3077 return unless pause("Kill $info->{id}?") =~ m/y/i;
3078
3079 eval {
3080 do_stmt($info->{cxn}, $q_or_c eq 'QUERY' ? 'KILL_QUERY' : 'KILL_CONNECTION', $info->{id} );
3081 };
3082
3083 if ( $EVAL_ERROR ) {
3084 print "\nError: $EVAL_ERROR";
3085 pause();
3086 }
3087}
3088
3089# set_display_precision {{{3
3090sub set_display_precision {
3091 my $dir = shift;
3092 $config{num_digits}->{val} = min(9, max(0, $config{num_digits}->{val} + $dir));
3093}
3094
3095sub toggle_visible_table {
3096 my ( $mode, $table ) = @_;
3097 my $visible = $modes{$mode}->{visible_tables};
3098 if ( grep { $_ eq $table } @$visible ) {
3099 $modes{$mode}->{visible_tables} = [ grep { $_ ne $table } @$visible ];
3100 }
3101 else {
3102 unshift @$visible, $table;
3103 }
3104 $modes{$mode}->{cust}->{visible_tables} = 1;
3105}
3106
3107# toggle_filter{{{3
3108sub toggle_filter {
3109 my ( $tbl, $filter ) = @_;
3110 my $filters = $tbl_meta{$tbl}->{filters};
3111 if ( grep { $_ eq $filter } @$filters ) {
3112 $tbl_meta{$tbl}->{filters} = [ grep { $_ ne $filter } @$filters ];
3113 }
3114 else {
3115 push @$filters, $filter;
3116 }
3117 $tbl_meta{$tbl}->{cust}->{filters} = 1;
3118}
3119
3120# toggle_config {{{3
3121sub toggle_config {
3122 my ( $key ) = @_;
3123 $config{$key}->{val} ^= 1;
3124}
3125
3126# create_deadlock {{{3
3127sub create_deadlock {
3128 $clear_screen_sub->();
3129
3130 print "This function will deliberately cause a small deadlock, "
3131 . "clearing deadlock information from the InnoDB monitor.\n\n";
3132
3133 my $answer = prompt("Are you sure you want to proceed? Say 'y' if you do");
3134 return 0 unless $answer eq 'y';
3135
3136 my ( $cxn ) = select_cxn('Clear on which server? ');
3137 return unless $cxn && exists($connections{$cxn});
3138
3139 clear_deadlock($cxn);
3140}
3141
3142# deadlock_thread {{{3
3143sub deadlock_thread {
3144 my ( $id, $tbl, $cxn ) = @_;
3145
3146 eval {
3147 my $dbh = get_new_db_connection($cxn, 1);
3148 my @stmts = (
3149 "set transaction isolation level serializable",
3150 (version_ge($dbh, '4.0.11') ? "start transaction" : 'begin'),
3151 "select * from $tbl where a = $id",
3152 "update $tbl set a = $id where a <> $id",
3153 );
3154
3155 foreach my $stmt (@stmts[0..2]) {
3156 $dbh->do($stmt);
3157 }
3158 sleep(1 + $id);
3159 $dbh->do($stmts[-1]);
3160 };
3161 if ( $EVAL_ERROR ) {
3162 if ( $EVAL_ERROR !~ m/Deadlock found/ ) {
3163 die $EVAL_ERROR;
3164 }
3165 }
3166 exit(0);
3167}
3168
3169# Purges unused binlogs on the master, up to but not including the latest log.
3170# TODO: guess which connections are slaves of a given master.
3171sub purge_master_logs {
3172 my @cxns = get_connections();
3173
3174 get_master_slave_status(@cxns);
3175
3176 # Toss out the rows that don't have master/slave status...
3177 my @vars =
3178 grep { $_ && ($_->{file} || $_->{master_host}) }
3179 map { $vars{$_}->{$clock} } @cxns;
3180 @cxns = map { $_->{cxn} } @vars;
3181
3182 # Figure out which master to purge ons.
3183 my @masters = map { $_->{cxn} } grep { $_->{file} } @vars;
3184 my ( $master ) = select_cxn('Which master?', @masters );
3185 return unless $master;
3186 my ($master_status) = grep { $_->{cxn} eq $master } @vars;
3187
3188 # Figure out the result order (not lexical order) of master logs.
3189 my @master_logs = get_master_logs($master);
3190 my $i = 0;
3191 my %master_logs = map { $_->{log_name} => $i++ } @master_logs;
3192
3193 # Ask which slave(s) are reading from this master.
3194 my @slave_status = grep { $_->{master_host} } @vars;
3195 my @slaves = map { $_->{cxn} } @slave_status;
3196 @slaves = select_cxn("Which slaves are reading from $master?", @slaves);
3197 @slave_status = grep { my $item = $_; grep { $item->{cxn} eq $_ } @slaves } @slave_status;
3198 return unless @slave_status;
3199
3200 # Find the minimum binary log in use.
3201 my $min_log = min(map { $master_logs{$_->{master_log_file}} } @slave_status);
3202 my $log_name = $master_logs[$min_log]->{log_name};
3203
3204 my $stmt = "PURGE MASTER LOGS TO '$log_name'";
3205 send_cmd_to_servers($stmt, 0, 'PURGE {MASTER | BINARY} LOGS {TO "log_name" | BEFORE "date"}', [$master]);
3206}
3207
3208sub send_cmd_to_servers {
3209 my ( $cmd, $all, $hint, $cxns ) = @_;
3210 if ( $all ) {
3211 @$cxns = get_connections();
3212 }
3213 elsif ( !@$cxns ) {
3214 @$cxns = select_cxn('Which servers?', @$cxns);
3215 }
3216 if ( $hint ) {
3217 print "\nHint: $hint\n";
3218 }
3219 $cmd = prompt('Command to send', undef, $cmd);
3220 foreach my $cxn ( @$cxns ) {
3221 eval {
3222 my $sth = do_query($cxn, $cmd);
3223 };
3224 if ( $EVAL_ERROR ) {
3225 print "Error from $cxn: $EVAL_ERROR\n";
3226 }
3227 else {
3228 print "Success on $cxn\n";
3229 }
3230 }
3231 pause();
3232}
3233
3234# Display functions {{{2
3235
3236sub set_s_mode {
3237 my ( $func ) = @_;
3238 $clear_screen_sub->();
3239 $config{S_func}->{val} = $func;
3240}
3241
3242# start_S_mode {{{3
3243sub start_S_mode {
3244 $clear_screen_sub->();
3245 switch_mode('S');
3246}
3247
3248# display_B {{{3
3249sub display_B {
3250 my @display_lines;
3251 my @cxns = get_connections();
3252 get_innodb_status(\@cxns);
3253
3254 my @buffer_pool;
3255 my @page_statistics;
3256 my @insert_buffers;
3257 my @adaptive_hash_index;
3258 my %rows_for = (
3259 buffer_pool => \@buffer_pool,
3260 page_statistics => \@page_statistics,
3261 insert_buffers => \@insert_buffers,
3262 adaptive_hash_index => \@adaptive_hash_index,
3263 );
3264
3265 my @visible = get_visible_tables();
3266 my %wanted = map { $_ => 1 } @visible;
3267
3268 foreach my $cxn ( @cxns ) {
3269 my $set = $vars{$cxn}->{$clock};
3270 my $pre = $vars{$cxn}->{$clock-1} || $set;
3271
3272 if ( $set->{IB_bp_complete} ) {
3273 if ( $wanted{buffer_pool} ) {
3274 push @buffer_pool, extract_values($set, $set, $pre, 'buffer_pool');
3275 }
3276 if ( $wanted{page_statistics} ) {
3277 push @page_statistics, extract_values($set, $set, $pre, 'page_statistics');
3278 }
3279 }
3280 if ( $set->{IB_ib_complete} ) {
3281 if ( $wanted{insert_buffers} ) {
3282 push @insert_buffers, extract_values(
3283 $config{status_inc}->{val} ? inc(0, $cxn) : $set, $set, $pre,
3284 'insert_buffers');
3285 }
3286 if ( $wanted{adaptive_hash_index} ) {
3287 push @adaptive_hash_index, extract_values($set, $set, $pre, 'adaptive_hash_index');
3288 }
3289 }
3290 }
3291
3292 my $first_table = 0;
3293 foreach my $tbl ( @visible ) {
3294 push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl);
3295 push @display_lines, get_cxn_errors(@cxns)
3296 if ( $config{debug}->{val} || !$first_table++ );
3297 }
3298
3299 draw_screen(\@display_lines);
3300}
3301
3302# display_C {{{3
3303sub display_C {
3304 my @display_lines;
3305 my @cxns = get_connections();
3306 get_status_info(@cxns);
3307
3308 my @cmd_summary;
3309 my %rows_for = (
3310 cmd_summary => \@cmd_summary,
3311 );
3312
3313 my @visible = get_visible_tables();
3314 my %wanted = map { $_ => 1 } @visible;
3315
3316 # For now, I'm manually pulling these variables out and pivoting. Eventually a SQL-ish
3317 # dialect should let me join a table to a grouped and pivoted table and do this more easily.
3318 # TODO: make it so.
3319 my $prefix = qr/^$config{cmd_filter}->{val}/; # TODO: this is a total hack
3320 my @values;
3321 my ($total, $last_total) = (0, 0);
3322 foreach my $cxn ( @cxns ) {
3323 my $set = $vars{$cxn}->{$clock};
3324 my $pre = $vars{$cxn}->{$clock-1} || $set;
3325 foreach my $key ( keys %$set ) {
3326 next unless $key =~ m/$prefix/i;
3327 my $val = $set->{$key};
3328 next unless defined $val && $val =~ m/^\d+$/;
3329 my $last_val = $val - ($pre->{$key} || 0);
3330 $total += $val;
3331 $last_total += $last_val;
3332 push @values, {
3333 name => $key,
3334 value => $val,
3335 last_value => $last_val,
3336 };
3337 }
3338 }
3339
3340 # Add aggregation and turn into a real set TODO: total hack
3341 if ( $wanted{cmd_summary} ) {
3342 foreach my $value ( @values ) {
3343 @{$value}{qw(total last_total)} = ($total, $last_total);
3344 push @cmd_summary, extract_values($value, $value, $value, 'cmd_summary');
3345 }
3346 }
3347
3348 my $first_table = 0;
3349 foreach my $tbl ( @visible ) {
3350 push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl);
3351 push @display_lines, get_cxn_errors(@cxns)
3352 if ( $config{debug}->{val} || !$first_table++ );
3353 }
3354
3355 draw_screen(\@display_lines);
3356}
3357
3358# display_D {{{3
3359sub display_D {
3360 my @display_lines;
3361 my @cxns = get_connections();
3362 get_innodb_status(\@cxns);
3363
3364 my @deadlock_transactions;
3365 my @deadlock_locks;
3366 my %rows_for = (
3367 deadlock_transactions => \@deadlock_transactions,
3368 deadlock_locks => \@deadlock_locks,
3369 );
3370
3371 my @visible = get_visible_tables();
3372 my %wanted = map { $_ => 1 } @visible;
3373
3374 foreach my $cxn ( @cxns ) {
3375 my $innodb_status = $vars{$cxn}->{$clock};
3376 my $prev_status = $vars{$cxn}->{$clock-1} || $innodb_status;
3377
3378 if ( $innodb_status->{IB_dl_timestring} ) {
3379
3380 my $victim = $innodb_status->{IB_dl_rolled_back} || 0;
3381
3382 if ( %wanted ) {
3383 foreach my $txn_id ( keys %{$innodb_status->{IB_dl_txns}} ) {
3384 my $txn = $innodb_status->{IB_dl_txns}->{$txn_id};
3385 my $pre = $prev_status->{IB_dl_txns}->{$txn_id} || $txn;
3386
3387 if ( $wanted{deadlock_transactions} ) {
3388 my $hash = extract_values($txn->{tx}, $txn->{tx}, $pre->{tx}, 'deadlock_transactions');
3389 $hash->{cxn} = $cxn;
3390 $hash->{dl_txn_num} = $txn_id;
3391 $hash->{victim} = $txn_id == $victim ? 'Yes' : 'No';
3392 $hash->{timestring} = $innodb_status->{IB_dl_timestring};
3393 $hash->{truncates} = $innodb_status->{IB_dl_complete} ? 'No' : 'Yes';
3394 push @deadlock_transactions, $hash;
3395 }
3396
3397 if ( $wanted{deadlock_locks} ) {
3398 foreach my $lock ( @{$txn->{locks}} ) {
3399 my $hash = extract_values($lock, $lock, $lock, 'deadlock_locks');
3400 $hash->{dl_txn_num} = $txn_id;
3401 $hash->{cxn} = $cxn;
3402 $hash->{mysql_thread_id} = $txn->{tx}->{mysql_thread_id};
3403 push @deadlock_locks, $hash;
3404 }
3405 }
3406
3407 }
3408 }
3409 }
3410 }
3411
3412 my $first_table = 0;
3413 foreach my $tbl ( @visible ) {
3414 push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl);
3415 push @display_lines, get_cxn_errors(@cxns)
3416 if ( $config{debug}->{val} || !$first_table++ );
3417 }
3418
3419 draw_screen(\@display_lines);
3420}
3421
3422# display_F {{{3
3423sub display_F {
3424 my @display_lines;
3425 my ( $cxn ) = get_connections();
3426 get_innodb_status([$cxn]);
3427 my $innodb_status = $vars{$cxn}->{$clock};
3428
3429 if ( $innodb_status->{IB_fk_timestring} ) {
3430
3431 push @display_lines, 'Reason: ' . $innodb_status->{IB_fk_reason};
3432
3433 # Display FK errors caused by invalid DML.
3434 if ( $innodb_status->{IB_fk_txn} ) {
3435 my $txn = $innodb_status->{IB_fk_txn};
3436 push @display_lines,
3437 '',
3438 "User $txn->{user} from $txn->{hostname}, thread $txn->{mysql_thread_id} was executing:",
3439 '', no_ctrl_char($txn->{query_text});
3440 }
3441
3442 my @fk_table = create_table2(
3443 $tbl_meta{fk_error}->{visible},
3444 meta_to_hdr('fk_error'),
3445 extract_values($innodb_status, $innodb_status, $innodb_status, 'fk_error'),
3446 { just => '-', sep => ' '});
3447 push @display_lines, '', @fk_table;
3448
3449 }
3450 else {
3451 push @display_lines, '', 'No foreign key error data.';
3452 }
3453 draw_screen(\@display_lines, { raw => 1 } );
3454}
3455
3456# display_I {{{3
3457sub display_I {
3458 my @display_lines;
3459 my @cxns = get_connections();
3460 get_innodb_status(\@cxns);
3461
3462 my @io_threads;
3463 my @pending_io;
3464 my @file_io_misc;
3465 my @log_statistics;
3466 my %rows_for = (
3467 io_threads => \@io_threads,
3468 pending_io => \@pending_io,
3469 file_io_misc => \@file_io_misc,
3470 log_statistics => \@log_statistics,
3471 );
3472
3473 my @visible = get_visible_tables();
3474 my %wanted = map { $_ => 1 } @visible;
3475
3476 foreach my $cxn ( @cxns ) {
3477 my $set = $vars{$cxn}->{$clock};
3478 my $pre = $vars{$cxn}->{$clock-1} || $set;
3479
3480 if ( $set->{IB_io_complete} ) {
3481 if ( $wanted{io_threads} ) {
3482 my $cur_threads = $set->{IB_io_threads};
3483 my $pre_threads = $pre->{IB_io_threads} || $cur_threads;
3484 foreach my $key ( sort keys %$cur_threads ) {
3485 my $cur_thd = $cur_threads->{$key};
3486 my $pre_thd = $pre_threads->{$key} || $cur_thd;
3487 my $hash = extract_values($cur_thd, $cur_thd, $pre_thd, 'io_threads');
3488 $hash->{cxn} = $cxn;
3489 push @io_threads, $hash;
3490 }
3491 }
3492 if ( $wanted{pending_io} ) {
3493 push @pending_io, extract_values($set, $set, $pre, 'pending_io');
3494 }
3495 if ( $wanted{file_io_misc} ) {
3496 push @file_io_misc, extract_values(
3497 $config{status_inc}->{val} ? inc(0, $cxn) : $set,
3498 $set, $pre, 'file_io_misc');
3499 }
3500 }
3501 if ( $set->{IB_lg_complete} && $wanted{log_statistics} ) {
3502 push @log_statistics, extract_values($set, $set, $pre, 'log_statistics');
3503 }
3504 }
3505
3506 my $first_table = 0;
3507 foreach my $tbl ( @visible ) {
3508 push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl);
3509 push @display_lines, get_cxn_errors(@cxns)
3510 if ( $config{debug}->{val} || !$first_table++ );
3511 }
3512
3513 draw_screen(\@display_lines);
3514}
3515
3516# display_L {{{3
3517sub display_L {
3518 my @display_lines;
3519 my @cxns = get_connections();
3520 get_innodb_status(\@cxns);
3521
3522 my @innodb_locks;
3523 my %rows_for = (
3524 innodb_locks => \@innodb_locks,
3525 );
3526
3527 my @visible = get_visible_tables();
3528 my %wanted = map { $_ => 1 } @visible;
3529
3530 # Get info on locks
3531 foreach my $cxn ( @cxns ) {
3532 my $set = $vars{$cxn}->{$clock} or next;
3533 my $pre = $vars{$cxn}->{$clock-1} || $set;
3534
3535 if ( $wanted{innodb_locks} && defined $set->{IB_tx_transactions} && @{$set->{IB_tx_transactions}} ) {
3536
3537 my $cur_txns = $set->{IB_tx_transactions};
3538 my $pre_txns = $pre->{IB_tx_transactions} || $cur_txns;
3539 my %cur_txns = map { $_->{mysql_thread_id} => $_ } @$cur_txns;
3540 my %pre_txns = map { $_->{mysql_thread_id} => $_ } @$pre_txns;
3541 foreach my $txn ( @$cur_txns ) {
3542 foreach my $lock ( @{$txn->{locks}} ) {
3543 my %hash = map { $_ => $txn->{$_} } qw(txn_id mysql_thread_id lock_wait_time active_secs);
3544 map { $hash{$_} = $lock->{$_} } qw(lock_type space_id page_no n_bits index db table txn_id lock_mode special insert_intention waiting);
3545 $hash{cxn} = $cxn;
3546 push @innodb_locks, extract_values(\%hash, \%hash, \%hash, 'innodb_locks');
3547 }
3548 }
3549 }
3550 }
3551
3552 my $first_table = 0;
3553 foreach my $tbl ( @visible ) {
3554 push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl);
3555 push @display_lines, get_cxn_errors(@cxns)
3556 if ( $config{debug}->{val} || !$first_table++ );
3557 }
3558
3559 draw_screen(\@display_lines);
3560}
3561
3562# display_M {{{3
3563sub display_M {
3564 my @display_lines;
3565 my @cxns = get_connections();
3566 get_master_slave_status(@cxns);
3567 get_status_info(@cxns);
3568
3569 my @slave_sql_status;
3570 my @slave_io_status;
3571 my @master_status;
3572 my %rows_for = (
3573 slave_sql_status => \@slave_sql_status,
3574 slave_io_status => \@slave_io_status,
3575 master_status => \@master_status,
3576 );
3577
3578 my @visible = get_visible_tables();
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches