1#!/usr/bin/env perl 2 3# vim: tw=160:nowrap:expandtab:tabstop=3:shiftwidth=3:softtabstop=3 4 5# This program is copyright (c) 2006 Baron Schwartz, baron at xaprb dot com. 6# Maintainers since 2013 : Kenny Gryp - Frédéric Descamps 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., 51 Franklin 20# Street, Fifth Floor, Boston, MA 02110-1301 USA 21 22use strict; 23use warnings FATAL => 'all'; 24 25our $VERSION = '1.13.0'; 26 27# Find the home directory; it's different on different OSes. 28our $homepath = $ENV{HOME} || $ENV{HOMEPATH} || $ENV{USERPROFILE} || '.'; 29 30# Configuration files 31our $default_home_conf = "$homepath/.innotop/innotop.conf"; 32our $default_central_conf = "/usr/local/etc/innotop.conf"; 33our $conf_file = ""; 34 35## Begin packages ## 36 37package DSNParser; 38 39use DBI; 40use Data::Dumper; 41$Data::Dumper::Indent = 0; 42$Data::Dumper::Quotekeys = 0; 43use English qw(-no_match_vars); 44 45use constant MKDEBUG => $ENV{MKDEBUG} || 0; 46 47# Defaults are built-in, but you can add/replace items by passing them as 48# hashrefs of {key, desc, copy, dsn}. The desc and dsn items are optional. 49# You can set properties with the prop() sub. Don't set the 'opts' property. 50sub new { 51 my ( $class, @opts ) = @_; 52 my $self = { 53 opts => { 54 A => { 55 desc => 'Default character set', 56 dsn => 'charset', 57 copy => 1, 58 }, 59 D => { 60 desc => 'Database to use', 61 dsn => 'database', 62 copy => 1, 63 }, 64 F => { 65 desc => 'Only read default options from the given file', 66 dsn => 'mysql_read_default_file', 67 copy => 1, 68 }, 69 h => { 70 desc => 'Connect to host', 71 dsn => 'host', 72 copy => 1, 73 }, 74 p => { 75 desc => 'Password to use when connecting', 76 dsn => 'password', 77 copy => 1, 78 }, 79 P => { 80 desc => 'Port number to use for connection', 81 dsn => 'port', 82 copy => 1, 83 }, 84 S => { 85 desc => 'Socket file to use for connection', 86 dsn => 'mysql_socket', 87 copy => 1, 88 }, 89 u => { 90 desc => 'User for login if not current user', 91 dsn => 'user', 92 copy => 1, 93 }, 94 }, 95 }; 96 foreach my $opt ( @opts ) { 97 if (MKDEBUG) { 98 _d('Adding extra property ' . $opt->{key}); 99 } 100 $self->{opts}->{$opt->{key}} = { desc => $opt->{desc}, copy => $opt->{copy} }; 101 } 102 return bless $self, $class; 103} 104 105# Recognized properties: 106# * autokey: which key to treat a bareword as (typically h=host). 107# * dbidriver: which DBI driver to use; assumes mysql, supports Pg. 108# * required: which parts are required (hashref). 109# * setvars: a list of variables to set after connecting 110sub prop { 111 my ( $self, $prop, $value ) = @_; 112 if ( @_ > 2 ) { 113 MKDEBUG && _d("Setting $prop property"); 114 $self->{$prop} = $value; 115 } 116 return $self->{$prop}; 117} 118 119sub parse { 120 my ( $self, $dsn, $prev, $defaults ) = @_; 121 if ( !$dsn ) { 122 MKDEBUG && _d('No DSN to parse'); 123 return; 124 } 125 MKDEBUG && _d("Parsing $dsn"); 126 $prev ||= {}; 127 $defaults ||= {}; 128 my %given_props; 129 my %final_props; 130 my %opts = %{$self->{opts}}; 131 my $prop_autokey = $self->prop('autokey'); 132 133 # Parse given props 134 foreach my $dsn_part ( split(/,/, $dsn) ) { 135 if ( my ($prop_key, $prop_val) = $dsn_part =~ m/^(.)=(.*)$/ ) { 136 # Handle the typical DSN parts like h=host, P=3306, etc. 137 $given_props{$prop_key} = $prop_val; 138 } 139 elsif ( $prop_autokey ) { 140 # Handle barewords 141 MKDEBUG && _d("Interpreting $dsn_part as $prop_autokey=$dsn_part"); 142 $given_props{$prop_autokey} = $dsn_part; 143 } 144 else { 145 MKDEBUG && _d("Bad DSN part: $dsn_part"); 146 } 147 } 148 149 # Fill in final props from given, previous, and/or default props 150 foreach my $key ( keys %opts ) { 151 MKDEBUG && _d("Finding value for $key"); 152 $final_props{$key} = $given_props{$key}; 153 if ( !defined $final_props{$key} 154 && defined $prev->{$key} && $opts{$key}->{copy} ) 155 { 156 $final_props{$key} = $prev->{$key}; 157 MKDEBUG && _d("Copying value for $key from previous DSN"); 158 } 159 if ( !defined $final_props{$key} ) { 160 $final_props{$key} = $defaults->{$key}; 161 MKDEBUG && _d("Copying value for $key from defaults"); 162 } 163 } 164 165 # Sanity check props 166 foreach my $key ( keys %given_props ) { 167 die "Unrecognized DSN part '$key' in '$dsn'\n" 168 unless exists $opts{$key}; 169 } 170 if ( (my $required = $self->prop('required')) ) { 171 foreach my $key ( keys %$required ) { 172 die "Missing DSN part '$key' in '$dsn'\n" unless $final_props{$key}; 173 } 174 } 175 176 return \%final_props; 177} 178 179sub as_string { 180 my ( $self, $dsn ) = @_; 181 return $dsn unless ref $dsn; 182 return join(',', 183 map { "$_=" . ($_ eq 'p' ? '...' : $dsn->{$_}) } 184 grep { defined $dsn->{$_} && $self->{opts}->{$_} } 185 sort keys %$dsn ); 186} 187 188sub usage { 189 my ( $self ) = @_; 190 my $usage 191 = "DSN syntax is key=value[,key=value...] Allowable DSN keys:\n" 192 . " KEY COPY MEANING\n" 193 . " === ==== =============================================\n"; 194 my %opts = %{$self->{opts}}; 195 foreach my $key ( sort keys %opts ) { 196 $usage .= " $key " 197 . ($opts{$key}->{copy} ? 'yes ' : 'no ') 198 . ($opts{$key}->{desc} || '[No description]') 199 . "\n"; 200 } 201 if ( (my $key = $self->prop('autokey')) ) { 202 $usage .= " If the DSN is a bareword, the word is treated as the '$key' key.\n"; 203 } 204 return $usage; 205} 206 207# Supports PostgreSQL via the dbidriver element of $info, but assumes MySQL by 208# default. 209sub get_cxn_params { 210 my ( $self, $info ) = @_; 211 my $dsn; 212 my %opts = %{$self->{opts}}; 213 my $driver = $self->prop('dbidriver') || ''; 214 if ( $driver eq 'Pg' ) { 215 $dsn = 'DBI:Pg:dbname=' . ( $info->{D} || '' ) . ';' 216 . join(';', map { "$opts{$_}->{dsn}=$info->{$_}" } 217 grep { defined $info->{$_} } 218 qw(h P)); 219 } 220 else { 221 $dsn = 'DBI:mysql:' . ( $info->{D} || '' ) . ';' 222 . join(';', map { "$opts{$_}->{dsn}=$info->{$_}" } 223 grep { defined $info->{$_} } 224 qw(F h P S A)) 225 . ';mysql_read_default_group=client'; 226 } 227 MKDEBUG && _d($dsn); 228 return ($dsn, $info->{u}, $info->{p}); 229} 230 231 232# Fills in missing info from a DSN after successfully connecting to the server. 233sub fill_in_dsn { 234 my ( $self, $dbh, $dsn ) = @_; 235 my $vars = $dbh->selectall_hashref('SHOW VARIABLES', 'Variable_name'); 236 my ($user, $db) = $dbh->selectrow_array('SELECT USER(), DATABASE()'); 237 $user =~ s/@.*//; 238 $dsn->{h} ||= $vars->{hostname}->{Value}; 239 $dsn->{S} ||= $vars->{'socket'}->{Value}; 240 $dsn->{P} ||= $vars->{port}->{Value}; 241 $dsn->{u} ||= $user; 242 $dsn->{D} ||= $db; 243} 244 245sub get_dbh { 246 my ( $self, $cxn_string, $user, $pass, $opts ) = @_; 247 $opts ||= {}; 248 my $defaults = { 249 AutoCommit => 0, 250 RaiseError => 1, 251 PrintError => 0, 252 mysql_enable_utf8 => ($cxn_string =~ m/charset=utf8/ ? 1 : 0), 253 }; 254 @{$defaults}{ keys %$opts } = values %$opts; 255 my $dbh; 256 my $tries = 2; 257 while ( !$dbh && $tries-- ) { 258 eval { 259 MKDEBUG && _d($cxn_string, ' ', $user, ' ', $pass, ' {', 260 join(', ', map { "$_=>$defaults->{$_}" } keys %$defaults ), '}'); 261 $dbh = DBI->connect($cxn_string, $user, $pass, $defaults); 262 # Immediately set character set and binmode on STDOUT. 263 if ( my ($charset) = $cxn_string =~ m/charset=(\w+)/ ) { 264 my $sql = "/*!40101 SET NAMES $charset*/"; 265 MKDEBUG && _d("$dbh: $sql"); 266 $dbh->do($sql); 267 MKDEBUG && _d('Enabling charset for STDOUT'); 268 if ( $charset eq 'utf8' ) { 269 binmode(STDOUT, ':utf8') 270 or die "Can't binmode(STDOUT, ':utf8'): $OS_ERROR"; 271 } 272 else { 273 binmode(STDOUT) or die "Can't binmode(STDOUT): $OS_ERROR"; 274 } 275 } 276 }; 277 if ( !$dbh && $EVAL_ERROR ) { 278 MKDEBUG && _d($EVAL_ERROR); 279 if ( $EVAL_ERROR =~ m/not a compiled character set|character set utf8/ ) { 280 MKDEBUG && _d("Going to try again without utf8 support"); 281 delete $defaults->{mysql_enable_utf8}; 282 } 283 if ( !$tries ) { 284 die $EVAL_ERROR; 285 } 286 } 287 } 288 # If setvars exists and it's MySQL connection, set them 289 my $setvars = $self->prop('setvars'); 290 if ( $cxn_string =~ m/mysql/i && $setvars ) { 291 my $sql = "SET $setvars"; 292 MKDEBUG && _d("$dbh: $sql"); 293 eval { 294 $dbh->do($sql); 295 }; 296 if ( $EVAL_ERROR ) { 297 MKDEBUG && _d($EVAL_ERROR); 298 } 299 } 300 MKDEBUG && _d('DBH info: ', 301 $dbh, 302 Dumper($dbh->selectrow_hashref( 303 'SELECT DATABASE(), CONNECTION_ID(), VERSION()/*!50038 , @@hostname*/')), 304 ' Connection info: ', ($dbh->{mysql_hostinfo} || 'undef'), 305 ' Character set info: ', 306 Dumper($dbh->selectall_arrayref( 307 'SHOW VARIABLES LIKE "character_set%"', { Slice => {}})), 308 ' $DBD::mysql::VERSION: ', $DBD::mysql::VERSION, 309 ' $DBI::VERSION: ', $DBI::VERSION, 310 ); 311 return $dbh; 312} 313 314# Tries to figure out a hostname for the connection. 315sub get_hostname { 316 my ( $self, $dbh ) = @_; 317 if ( my ($host) = ($dbh->{mysql_hostinfo} || '') =~ m/^(\w+) via/ ) { 318 return $host; 319 } 320 my ( $hostname, $one ) = $dbh->selectrow_array( 321 'SELECT /*!50038 @@hostname, */ 1'); 322 return $hostname; 323} 324 325# Disconnects a database handle, but complains verbosely if there are any active 326# children. These are usually $sth handles that haven't been finish()ed. 327sub disconnect { 328 my ( $self, $dbh ) = @_; 329 MKDEBUG && $self->print_active_handles($dbh); 330 $dbh->disconnect; 331} 332 333sub print_active_handles { 334 my ( $self, $thing, $level ) = @_; 335 $level ||= 0; 336 printf("# Active %sh: %s %s %s\n", ($thing->{Type} || 'undef'), "\t" x $level, 337 $thing, (($thing->{Type} || '') eq 'st' ? $thing->{Statement} || '' : '')) 338 or die "Cannot print: $OS_ERROR"; 339 foreach my $handle ( grep {defined} @{ $thing->{ChildHandles} } ) { 340 $self->print_active_handles( $handle, $level + 1 ); 341 } 342} 343 344sub _d { 345 my ($package, undef, $line) = caller 0; 346 @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; } 347 map { defined $_ ? $_ : 'undef' } 348 @_; 349 # Use $$ instead of $PID in case the package 350 # does not use English. 351 print "# $package:$line $$ ", @_, "\n"; 352} 353 3541; 355 356package InnoDBParser; 357 358use Data::Dumper; 359$Data::Dumper::Sortkeys = 1; 360use English qw(-no_match_vars); 361use List::Util qw(max); 362use POSIX qw(strftime); 363 364# Some common patterns 365my $d = qr/(\d+)/; # Digit 366my $f = qr/(\d+\.\d+)/; # Float 367my $t = qr/((?:\d+ \d+)|(?:[A-Fa-f0-9]+))/; # Transaction ID 368my $i = qr/((?:\d{1,3}\.){3}\d+)/; # IP address 369my $n = qr/([^`\s]+)/; # MySQL object name 370my $w = qr/(\w+)/; # Words 371my $fl = qr/([\w\.\/]+) line $d/; # Filename and line number 372my $h = qr/((?:0x)?[0-9a-f]*)/; # Hex 373my $s = qr/(\d{6} .?\d:\d\d:\d\d)/; # InnoDB timestamp 374 375# If you update this variable, also update the SYNOPSIS in the pod. 376my %innodb_section_headers = ( 377 "TRANSACTIONS" => "tx", 378 "BUFFER POOL AND MEMORY" => "bp", 379 "SEMAPHORES" => "sm", 380 "LOG" => "lg", 381 "ROW OPERATIONS" => "ro", 382 "INSERT BUFFER AND ADAPTIVE HASH INDEX" => "ib", 383 "FILE I/O" => "io", 384 "LATEST DETECTED DEADLOCK" => "dl", 385 "LATEST FOREIGN KEY ERROR" => "fk", 386 "BACKGROUND THREAD" => "bt", 387); 388 389my %parser_for = ( 390 tx => \&parse_tx_section, 391 bp => \&parse_bp_section, 392 sm => \&parse_sm_section, 393 lg => \&parse_lg_section, 394 ro => \&parse_ro_section, 395 ib => \&parse_ib_section, 396 io => \&parse_io_section, 397 dl => \&parse_dl_section, 398 fk => \&parse_fk_section, 399); 400 401my %fk_parser_for = ( 402 Transaction => \&parse_fk_transaction_error, 403 Error => \&parse_fk_bad_constraint_error, 404 Cannot => \&parse_fk_cant_drop_parent_error, 405); 406 407# A thread's proc_info can be at least 98 different things I've found in the 408# source. Fortunately, most of them begin with a gerunded verb. These are 409# the ones that don't. 410my %is_proc_info = ( 411 'After create' => 1, 412 'Execution of init_command' => 1, 413 'FULLTEXT initialization' => 1, 414 'Reopen tables' => 1, 415 'Repair done' => 1, 416 'Repair with keycache' => 1, 417 'System lock' => 1, 418 'Table lock' => 1, 419 'Thread initialized' => 1, 420 'User lock' => 1, 421 'copy to tmp table' => 1, 422 'discard_or_import_tablespace' => 1, 423 'end' => 1, 424 'got handler lock' => 1, 425 'got old table' => 1, 426 'init' => 1, 427 'key cache' => 1, 428 'locks' => 1, 429 'malloc' => 1, 430 'query end' => 1, 431 'rename result table' => 1, 432 'rename' => 1, 433 'setup' => 1, 434 'statistics' => 1, 435 'status' => 1, 436 'table cache' => 1, 437 'update' => 1, 438); 439 440sub new { 441 bless {}, shift; 442} 443 444# Parse the status and return it. 445# See srv_printf_innodb_monitor in innobase/srv/srv0srv.c 446# Pass in the text to parse, whether to be in debugging mode, which sections 447# to parse (hashref; if empty, parse all), and whether to parse full info from 448# locks and such (probably shouldn't unless you need to). 449sub parse_status_text { 450 my ( $self, $fulltext, $debug, $sections, $full, $mysqlversion ) = @_; 451 452 die "I can't parse undef" unless defined $fulltext; 453 $fulltext =~ s/[\r\n]+/\n/g; 454 455 $sections ||= {}; 456 die '$sections must be a hashref' unless ref($sections) eq 'HASH'; 457 458 my %innodb_data = ( 459 got_all => 0, # Whether I was able to get the whole thing 460 ts => '', # Timestamp the server put on it 461 last_secs => 0, # Num seconds the averages are over 462 sections => {}, # Parsed values from each section 463 ); 464 465 if ( $debug ) { 466 $innodb_data{'fulltext'} = $fulltext; 467 } 468 469 # Get the most basic info about the status: beginning and end, and whether 470 # I got the whole thing (if there has been a big deadlock and there are 471 # too many locks to print, the output might be truncated) 472 473 my $time_text; 474 if ( ($mysqlversion =~ /^5\.[67]\./) || ($mysqlversion =~ /^8\.0\./) || ($mysqlversion =~ /^10\.[0123]\./) ) { 475 ( $time_text ) = $fulltext =~ m/^([0-9-]* [0-9:]*) [0-9a-fx]* INNODB MONITOR OUTPUT/m; 476 $innodb_data{'ts'} = [ parse_innodb_timestamp_56( $time_text ) ]; 477 } else { 478 ( $time_text ) = $fulltext =~ m/^$s INNODB MONITOR OUTPUT$/m; 479 $innodb_data{'ts'} = [ parse_innodb_timestamp( $time_text ) ]; 480 } 481 482 $innodb_data{'timestring'} = ts_to_string($innodb_data{'ts'}); 483 ( $innodb_data{'last_secs'} ) = $fulltext 484 =~ m/Per second averages calculated from the last $d seconds/; 485 486 ( my $got_all ) = $fulltext =~ m/END OF INNODB MONITOR OUTPUT/; 487 $innodb_data{'got_all'} = $got_all || 0; 488 489 # Split it into sections. Each section begins with 490 # ----- 491 # LABEL 492 # ----- 493 my %innodb_sections; 494 my @matches = $fulltext 495 =~ m#\n(---+)\n([A-Z /]+)\n\1\n(.*?)(?=\n(---+)\n[A-Z /]+\n\4\n|$)#gs; 496 while ( my ( $start, $name, $text, $end ) = splice(@matches, 0, 4) ) { 497 $innodb_sections{$name} = [ $text, $end ? 1 : 0 ]; 498 } 499 500 # Just for sanity's sake, make sure I understand what to do with each 501 # section. 502 eval { 503 foreach my $section ( keys %innodb_sections ) { 504 my $header = $innodb_section_headers{$section}; 505 if ( !$header && $debug ) { 506 warn "Unknown section $section in $fulltext\n"; 507 } 508 509 # The last section in the file is a special case, because instead of 510 # ending with the beginning of another section, it ends with the end of 511 # the file. So this section is complete if the entire file is 512 # complete. In different versions of InnoDB, various sections are 513 # last. 514 if ( $innodb_sections{$section}->[0] =~ s/\n---+\nEND OF INNODB.+\n=+$// ) { 515 $innodb_sections{$section}->[1] ||= $innodb_data{'got_all'}; 516 } 517 518 if ( $header && $section ) { 519 $innodb_data{'sections'}->{ $header } 520 ->{'fulltext'} = $innodb_sections{$section}->[0]; 521 $innodb_data{'sections'}->{ $header } 522 ->{'complete'} = $innodb_sections{$section}->[1]; 523 } 524 else { 525 _debug( $debug, "header = " . ($header || 'undef') . ", section = " . ($section || 'undef')) if $debug; 526 } 527 } 528 }; 529 if ( $EVAL_ERROR ) { 530 _debug( $debug, $EVAL_ERROR); 531 } 532 533 # ################################################################ 534 # Parse the detailed data out of the sections. 535 # ################################################################ 536 eval { 537 foreach my $section ( keys %parser_for ) { 538 if ( defined $innodb_data{'sections'}->{$section} 539 && (!%$sections || (defined($sections->{$section} && $sections->{$section})) )) { 540 $parser_for{$section}->( 541 $innodb_data{'sections'}->{$section}, 542 $innodb_data{'sections'}->{$section}->{'complete'}, 543 $debug, 544 $full, 545 $mysqlversion) 546 or delete $innodb_data{'sections'}->{$section}; 547 } 548 else { 549 delete $innodb_data{'sections'}->{$section}; 550 } 551 } 552 }; 553 if ( $EVAL_ERROR ) { 554 _debug( $debug, $EVAL_ERROR); 555 } 556 557 return \%innodb_data; 558} 559 560# Parses the status text and returns it flattened out as a single hash. 561sub get_status_hash { 562 my ( $self, $fulltext, $debug, $sections, $full, $mysqlversion ) = @_; 563 564 # Parse the status text... 565 my $innodb_status 566 = $self->parse_status_text($fulltext, $debug, $sections, $full, $mysqlversion ); 567 568 # Flatten the hierarchical structure into a single list by grabbing desired 569 # sections from it. 570 return 571 (map { 'IB_' . $_ => $innodb_status->{$_} } qw(timestring last_secs got_all)), 572 (map { 'IB_bp_' . $_ => $innodb_status->{'sections'}->{'bp'}->{$_} } 573 qw( writes_pending buf_pool_hit_rate total_mem_alloc buf_pool_reads 574 awe_mem_alloc pages_modified writes_pending_lru page_creates_sec 575 reads_pending pages_total buf_pool_hits writes_pending_single_page 576 page_writes_sec pages_read pages_written page_reads_sec 577 writes_pending_flush_list buf_pool_size add_pool_alloc 578 dict_mem_alloc pages_created buf_free complete )), 579 (map { 'IB_tx_' . $_ => $innodb_status->{'sections'}->{'tx'}->{$_} } 580 qw( num_lock_structs history_list_len purge_done_for transactions 581 purge_undo_for is_truncated trx_id_counter complete )), 582 (map { 'IB_ib_' . $_ => $innodb_status->{'sections'}->{'ib'}->{$_} } 583 qw( hash_table_size hash_searches_s non_hash_searches_s 584 bufs_in_node_heap used_cells size free_list_len seg_size inserts 585 merged_recs merges complete )), 586 (map { 'IB_lg_' . $_ => $innodb_status->{'sections'}->{'lg'}->{$_} } 587 qw( log_ios_done pending_chkp_writes last_chkp log_ios_s 588 log_flushed_to log_seq_no pending_log_writes complete )), 589 (map { 'IB_sm_' . $_ => $innodb_status->{'sections'}->{'sm'}->{$_} } 590 qw( wait_array_size rw_shared_spins rw_excl_os_waits mutex_os_waits 591 mutex_spin_rounds mutex_spin_waits rw_excl_spins rw_shared_os_waits 592 waits signal_count reservation_count complete )), 593 (map { 'IB_ro_' . $_ => $innodb_status->{'sections'}->{'ro'}->{$_} } 594 qw( queries_in_queue n_reserved_extents main_thread_state 595 main_thread_proc_no main_thread_id read_sec del_sec upd_sec ins_sec 596 read_views_open num_rows_upd num_rows_ins num_rows_read 597 queries_inside num_rows_del complete )), 598 (map { 'IB_fk_' . $_ => $innodb_status->{'sections'}->{'fk'}->{$_} } 599 qw( trigger parent_table child_index parent_index attempted_op 600 child_db timestring fk_name records col_name reason txn parent_db 601 type child_table parent_col complete )), 602 (map { 'IB_io_' . $_ => $innodb_status->{'sections'}->{'io'}->{$_} } 603 qw( pending_buffer_pool_flushes pending_pwrites pending_preads 604 pending_normal_aio_reads fsyncs_s os_file_writes pending_sync_ios 605 reads_s flush_type avg_bytes_s pending_ibuf_aio_reads writes_s 606 threads os_file_reads pending_aio_writes pending_log_ios os_fsyncs 607 pending_log_flushes complete )), 608 (map { 'IB_dl_' . $_ => $innodb_status->{'sections'}->{'dl'}->{$_} } 609 qw( timestring rolled_back txns complete )); 610 611} 612 613sub ts_to_string { 614 my $parts = shift; 615 return sprintf('%02d-%02d-%02d %02d:%02d:%02d', @$parts); 616} 617 618sub parse_innodb_timestamp { 619 my $text = shift; 620 my ( $y, $m, $d, $h, $i, $s ) 621 = $text =~ m/^(\d\d)(\d\d)(\d\d) +(\d+):(\d+):(\d+)$/; 622 die("Can't get timestamp from $text\n") unless $y; 623 $y += 2000; 624 return ( $y, $m, $d, $h, $i, $s ); 625} 626 627sub parse_innodb_timestamp_56 { 628 my $text = shift; 629 my ( $y, $m, $d, $h, $i, $s ) 630 = $text =~ m/^(\d\d\d\d)-(\d\d)-(\d\d) +(\d+):(\d+):(\d+)$/; 631 die("Can't get timestamp from $text\n") unless $y; 632 return ( $y, $m, $d, $h, $i, $s ); 633} 634 635sub parse_fk_section { 636 my ( $section, $complete, $debug, $full, $mysqlversion ) = @_; 637 my $fulltext = $section->{'fulltext'}; 638 639 return 0 unless $fulltext; 640 641 my ( $ts, $type ); 642 if ( ($mysqlversion =~ /^5.[67]\./) || ($mysqlversion =~ /^10.[0123]\./) ) { 643 ( $ts, $type ) = $fulltext =~ m/^([0-9-]* [0-9:]*)\s[0-9a-fx]*\s+(\w+)/m; 644 $section->{'ts'} = [ parse_innodb_timestamp_56( $ts ) ]; 645 } else { 646 ( $ts, $type ) = $fulltext =~ m/^$s\s+(\w+)/m; 647 $section->{'ts'} = [ parse_innodb_timestamp( $ts ) ]; 648 } 649 650 $section->{'timestring'} = ts_to_string($section->{'ts'}); 651 $section->{'type'} = $type; 652 653 # Decide which type of FK error happened, and dispatch to the right parser. 654 if ( $type && $fk_parser_for{$type} ) { 655 $fk_parser_for{$type}->( $section, $complete, $debug, $fulltext, $full ); 656 } 657 658 delete $section->{'fulltext'} unless $debug; 659 660 return 1; 661} 662 663sub parse_fk_cant_drop_parent_error { 664 my ( $section, $complete, $debug, $fulltext, $full ) = @_; 665 666 # Parse the parent/child table info out 667 @{$section}{ qw(attempted_op parent_db parent_table) } = $fulltext 668 =~ m{Cannot $w table `(.*)/(.*)`}m; 669 @{$section}{ qw(child_db child_table) } = $fulltext 670 =~ m{because it is referenced by `(.*)/(.*)`}m; 671 672 ( $section->{'reason'} ) = $fulltext =~ m/(Cannot .*)/s; 673 if ( !defined $section->{reason} ) { 674 ( $section->{'reason'} ) = $fulltext =~ m/(Trying to add .*)/s; 675 } 676 $section->{'reason'} =~ s/\n(?:InnoDB: )?/ /gm 677 if $section->{'reason'}; 678 679 # Certain data may not be present. Make them '' if not present. 680 map { $section->{$_} ||= "" } 681 qw(child_index fk_name col_name parent_col); 682} 683 684# See dict/dict0dict.c, function dict_foreign_error_report 685# I don't care much about these. There are lots of different messages, and 686# they come from someone trying to create a foreign key, or similar 687# statements. They aren't indicative of some transaction trying to insert, 688# delete or update data. Sometimes it is possible to parse out a lot of 689# information about the tables and indexes involved, but often the message 690# contains the DDL string the user entered, which is way too much for this 691# module to try to handle. 692sub parse_fk_bad_constraint_error { 693 my ( $section, $complete, $debug, $fulltext, $full ) = @_; 694 695 # Parse the parent/child table and index info out 696 @{$section}{ qw(child_db child_table) } = $fulltext 697 =~ m{Error in foreign key constraint of table (.*)/(.*):$}m; 698 $section->{'attempted_op'} = 'DDL'; 699 700 # FK name, parent info... if possible. 701 @{$section}{ qw(fk_name col_name parent_db parent_table parent_col) } 702 = $fulltext 703 =~ m/CONSTRAINT `?$n`? FOREIGN KEY \(`?$n`?\) REFERENCES (?:`?$n`?\.)?`?$n`? \(`?$n`?\)/; 704 705 if ( !defined($section->{'fk_name'}) ) { 706 # Try to parse SQL a user might have typed in a CREATE statement or such 707 @{$section}{ qw(col_name parent_db parent_table parent_col) } 708 = $fulltext 709 =~ m/FOREIGN\s+KEY\s*\(`?$n`?\)\s+REFERENCES\s+(?:`?$n`?\.)?`?$n`?\s*\(`?$n`?\)/i; 710 } 711 $section->{'parent_db'} ||= $section->{'child_db'}; 712 713 # Name of the child index (index in the same table where the FK is, see 714 # definition of dict_foreign_struct in include/dict0mem.h, where it is 715 # called foreign_index, as opposed to referenced_index which is in the 716 # parent table. This may not be possible to find. 717 @{$section}{ qw(child_index) } = $fulltext 718 =~ m/^The index in the foreign key in table is $n$/m; 719 720 @{$section}{ qw(reason) } = $fulltext =~ m/:\s*([^:]+)(?= Constraint:|$)/ms; 721 $section->{'reason'} =~ s/\s+/ /g 722 if $section->{'reason'}; 723 724 # Certain data may not be present. Make them '' if not present. 725 map { $section->{$_} ||= "" } 726 qw(child_index fk_name col_name parent_table parent_col); 727} 728 729# see source file row/row0ins.c 730sub parse_fk_transaction_error { 731 my ( $section, $complete, $debug, $fulltext, $full ) = @_; 732 733 # Parse the txn info out 734 my ( $txn ) = $fulltext 735 =~ m/Transaction:\n(TRANSACTION.*)\nForeign key constraint fails/s; 736 if ( $txn ) { 737 $section->{'txn'} = parse_tx_text( $txn, $complete, $debug, $full ); 738 } 739 740 # Parse the parent/child table and index info out. There are two types: an 741 # update or a delete of a parent record leaves a child orphaned 742 # (row_ins_foreign_report_err), and an insert or update of a child record has 743 # no matching parent record (row_ins_foreign_report_add_err). 744 745 @{$section}{ qw(reason child_db child_table) } 746 = $fulltext =~ m{^(Foreign key constraint fails for table `(.*?)`?[/.]`?(.*)`:)$}m; 747 748 @{$section}{ qw(fk_name col_name parent_db parent_table parent_col) } 749 = $fulltext 750 =~ m/CONSTRAINT `$n` FOREIGN KEY \(`$n`\) REFERENCES (?:`$n`\.)?`$n` \(`$n`\)/; 751 $section->{'parent_db'} ||= $section->{'child_db'}; 752 753 # Special case, which I don't know how to trigger, but see 754 # innobase/row/row0ins.c row_ins_check_foreign_constraint 755 if ( $fulltext =~ m/ibd file does not currently exist!/ ) { 756 my ( $attempted_op, $index, $records ) 757 = $fulltext =~ m/^Trying to (add to index) `$n` tuple:\n(.*))?/sm; 758 $section->{'child_index'} = $index; 759 $section->{'attempted_op'} = $attempted_op || ''; 760 if ( $records && $full ) { 761 ( $section->{'records'} ) 762 = parse_innodb_record_dump( $records, $complete, $debug ); 763 } 764 @{$section}{qw(parent_db parent_table)} 765 =~ m/^But the parent table `$n`\.`$n`$/m; 766 } 767 else { 768 my ( $attempted_op, $which, $index ) 769 = $fulltext =~ m/^Trying to ([\w ]*) in (child|parent) table, in index `$n` tuple:$/m; 770 if ( $which ) { 771 $section->{$which . '_index'} = $index; 772 $section->{'attempted_op'} = $attempted_op || ''; 773 774 # Parse out the related records in the other table. 775 my ( $search_index, $records ); 776 if ( $which eq 'child' ) { 777 ( $search_index, $records ) = $fulltext 778 =~ m/^But in parent table [^,]*, in index `$n`,\nthe closest match we can find is record:\n(.*)/ms; 779 $section->{'parent_index'} = $search_index; 780 } 781 else { 782 ( $search_index, $records ) = $fulltext 783 =~ m/^But in child table [^,]*, in index `$n`, (?:the record is not available|there is a record:\n(.*))?/ms; 784 $section->{'child_index'} = $search_index; 785 } 786 if ( $records && $full ) { 787 $section->{'records'} 788 = parse_innodb_record_dump( $records, $complete, $debug ); 789 } 790 else { 791 $section->{'records'} = ''; 792 } 793 } 794 } 795 796 # Parse out the tuple trying to be updated, deleted or inserted. 797 my ( $trigger ) = $fulltext =~ m/^(DATA TUPLE: \d+ fields;\n.*)$/m; 798 if ( $trigger ) { 799 $section->{'trigger'} = parse_innodb_record_dump( $trigger, $complete, $debug ); 800 } 801 802 # Certain data may not be present. Make them '' if not present. 803 map { $section->{$_} ||= "" } 804 qw(child_index fk_name col_name parent_table parent_col); 805} 806 807# There are new-style and old-style record formats. See rem/rem0rec.c 808# TODO: write some tests for this 809sub parse_innodb_record_dump { 810 my ( $dump, $complete, $debug ) = @_; 811 return undef unless $dump; 812 813 my $result = {}; 814 815 if ( $dump =~ m/PHYSICAL RECORD/ ) { 816 my $style = $dump =~ m/compact format/ ? 'new' : 'old'; 817 $result->{'style'} = $style; 818 819 # This is a new-style record. 820 if ( $style eq 'new' ) { 821 @{$result}{qw( heap_no type num_fields info_bits )} 822 = $dump 823 =~ m/^(?:Record lock, heap no $d )?([A-Z ]+): n_fields $d; compact format; info bits $d$/m; 824 } 825 826 # OK, it's old-style. Unfortunately there are variations here too. 827 elsif ( $dump =~ m/-byte offs / ) { 828 # Older-old style. 829 @{$result}{qw( heap_no type num_fields byte_offset info_bits )} 830 = $dump 831 =~ m/^(?:Record lock, heap no $d )?([A-Z ]+): n_fields $d; $d-byte offs [A-Z]+; info bits $d$/m; 832 if ( $dump !~ m/-byte offs TRUE/ ) { 833 $result->{'byte_offset'} = 0; 834 } 835 } 836 else { 837 # Newer-old style. 838 @{$result}{qw( heap_no type num_fields byte_offset info_bits )} 839 = $dump 840 =~ m/^(?:Record lock, heap no $d )?([A-Z ]+): n_fields $d; $d-byte offsets; info bits $d$/m; 841 } 842 843 } 844 else { 845 $result->{'style'} = 'tuple'; 846 @{$result}{qw( type num_fields )} 847 = $dump =~ m/^(DATA TUPLE): $d fields;$/m; 848 } 849 850 # Fill in default values for things that couldn't be parsed. 851 map { $result->{$_} ||= 0 } 852 qw(heap_no num_fields byte_offset info_bits); 853 map { $result->{$_} ||= '' } 854 qw(style type ); 855 856 my @fields = $dump =~ m/ (\d+:.*?;?);(?=$| \d+:)/gm; 857 $result->{'fields'} = [ map { parse_field($_, $complete, $debug ) } @fields ]; 858 859 return $result; 860} 861 862# New/old-style applies here. See rem/rem0rec.c 863# $text should not include the leading space or the second trailing semicolon. 864sub parse_field { 865 my ( $text, $complete, $debug ) = @_; 866 867 # Sample fields: 868 # '4: SQL NULL, size 4 ' 869 # '1: len 6; hex 000000005601; asc V ;' 870 # '6: SQL NULL' 871 # '5: len 30; hex 687474703a2f2f7777772e737765657477617465722e636f6d2f73746f72; asc http://www.sweetwater.com/stor;...(truncated)' 872 my ( $id, $nullsize, $len, $hex, $asc, $truncated ); 873 ( $id, $nullsize ) = $text =~ m/^$d: SQL NULL, size $d $/; 874 if ( !defined($id) ) { 875 ( $id ) = $text =~ m/^$d: SQL NULL$/; 876 } 877 if ( !defined($id) ) { 878 ( $id, $len, $hex, $asc, $truncated ) 879 = $text =~ m/^$d: len $d; hex $h; asc (.*);(\.\.\.\(truncated\))?$/; 880 } 881 882 die "Could not parse this field: '$text'" unless defined $id; 883 return { 884 id => $id, 885 len => defined($len) ? $len : defined($nullsize) ? $nullsize : 0, 886 'hex' => defined($hex) ? $hex : '', 887 asc => defined($asc) ? $asc : '', 888 trunc => $truncated ? 1 : 0, 889 }; 890 891} 892 893sub parse_dl_section { 894 my ( $dl, $complete, $debug, $full, $mysqlversion ) = @_; 895 return unless $dl; 896 my $fulltext = $dl->{'fulltext'}; 897 return 0 unless $fulltext; 898 899 my ( $ts ) = $fulltext =~ m/^$s$/m; 900 return 0 unless $ts; 901 902 if ( ($mysqlversion =~ /^5\.[67]\./) || ($mysqlversion =~ /^8\.0\./) || ($mysqlversion =~ /^10\.[0123]\./) ) { 903 $dl->{'ts'} = [ parse_innodb_timestamp_56( $ts ) ]; 904 } 905 else { 906 $dl->{'ts'} = [ parse_innodb_timestamp( $ts ) ]; 907 } 908 $dl->{'timestring'} = ts_to_string($dl->{'ts'}); 909 $dl->{'txns'} = {}; 910 911 my @sections 912 = $fulltext 913 =~ m{ 914 ^\*{3}\s([^\n]*) # *** (1) WAITING FOR THIS... 915 (.*?) # Followed by anything, non-greedy 916 (?=(?:^\*{3})|\z) # Followed by another three stars or EOF 917 }gmsx; 918 919 920 # Loop through each section. There are no assumptions about how many 921 # there are, who holds and wants what locks, and who gets rolled back. 922 while ( my ($header, $body) = splice(@sections, 0, 2) ) { 923 my ( $txn_id, $what ) = $header =~ m/^\($d\) (.*):$/; 924 next unless $txn_id; 925 $dl->{'txns'}->{$txn_id} ||= {}; 926 my $txn = $dl->{'txns'}->{$txn_id}; 927 928 if ( $what eq 'TRANSACTION' ) { 929 $txn->{'tx'} = parse_tx_text( $body, $complete, $debug, $full ); 930 } 931 else { 932 push @{$txn->{'locks'}}, parse_innodb_record_locks( $body, $complete, $debug, $full ); 933 } 934 } 935 936 @{ $dl }{ qw(rolled_back) } 937 = $fulltext =~ m/^\*\*\* WE ROLL BACK TRANSACTION \($d\)$/m; 938 939 # Make sure certain values aren't undef 940 map { $dl->{$_} ||= '' } qw(rolled_back); 941 942 delete $dl->{'fulltext'} unless $debug; 943 return 1; 944} 945 946sub parse_innodb_record_locks { 947 my ( $text, $complete, $debug, $full ) = @_; 948 my @result; 949 950 foreach my $lock ( $text =~ m/(^(?:RECORD|TABLE) LOCKS?.*$)/gm ) { 951 my $hash = {}; 952 @{$hash}{ qw(lock_type space_id page_no n_bits index db table txn_id lock_mode) } 953 = $lock 954 =~ 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; 955 ( $hash->{'special'} ) 956 = $lock =~ m/^(?:RECORD|TABLE) .*? locks (rec but not gap|gap before rec)/m; 957 $hash->{'insert_intention'} 958 = $lock =~ m/^(?:RECORD|TABLE) .*? insert intention/m ? 1 : 0; 959 $hash->{'waiting'} 960 = $lock =~ m/^(?:RECORD|TABLE) .*? waiting/m ? 1 : 0; 961 962 # Some things may not be in the text, so make sure they are not 963 # undef. 964 map { $hash->{$_} ||= 0 } qw(n_bits page_no space_id); 965 map { $hash->{$_} ||= "" } qw(index special); 966 push @result, $hash; 967 } 968 969 return @result; 970} 971 972sub parse_tx_text { 973 my ( $txn, $complete, $debug, $full ) = @_; 974 975 my ( $txn_id, $txn_status ) 976 = $txn 977 =~ m/^(?:---)?TRANSACTION $t, ([^\n0-9,]*[^\s\d])/m; 978 $txn_status =~ s/,$// if $txn_status; 979 my ( $active_secs) 980 = $txn 981 =~ m/^[^\n]*\b$d sec\b/m; 982 my ( $proc_no ) 983 = $txn 984 =~ m/process no $d/m; 985 my ( $os_thread_id ) 986 = $txn 987 =~ m/OS thread id $d/m; 988 my ( $thread_status, $thread_decl_inside ) 989 = $txn 990 =~ m/(?:OS thread id \d+|\d sec)(?: ([^,]+?))?(?:, thread declared inside InnoDB $d)?$/m; 991 992 # Parsing the line that begins 'MySQL thread id' is complicated. The only 993 # thing always in the line is the thread and query id. See function 994 # innobase_mysql_print_thd in InnoDB source file sql/ha_innodb.cc. 995 my ( $thread_line ) = $txn =~ m/^(MySQL thread id .*)$/m; 996 my ( $mysql_thread_id, $query_id, $hostname, $ip, $user, $query_status ); 997 998 if ( $thread_line ) { 999 # These parts can always be gotten. 1000 ( $mysql_thread_id, $query_id ) = $thread_line =~ m/^MySQL thread id $d, .*?query id $d/m; 1001 1002 # If it's a master/slave thread, "Has (read|sent) all" may be the thread's 1003 # proc_info. In these cases, there won't be any host/ip/user info 1004 ( $query_status ) = $thread_line =~ m/(Has (?:read|sent) all .*$)/m; 1005 if ( defined($query_status) ) { 1006 $user = 'system user'; 1007 } 1008 1009 # It may be the case that the query id is the last thing in the line. 1010 elsif ( $thread_line =~ m/query id \d+ / ) { 1011 # The IP address is the only non-word thing left, so it's the most 1012 # useful marker for where I have to start guessing. 1013 ( $hostname, $ip ) = $thread_line =~ m/query id \d+(?: ([A-Za-z]\S+))? $i/m; 1014 if ( defined $ip ) { 1015 ( $user, $query_status ) = $thread_line =~ m/$ip $w(?: (.*))?$/; 1016 } 1017 else { # OK, there wasn't an IP address. 1018 # There might not be ANYTHING except the query status. 1019 ( $query_status ) = $thread_line =~ m/query id \d+ (.*)$/; 1020 if ( $query_status !~ m/^\w+ing/ && !exists($is_proc_info{$query_status}) ) { 1021 # The remaining tokens are, in order: hostname, user, query_status. 1022 # It's basically impossible to know which is which. 1023 ( $hostname, $user, $query_status ) = $thread_line 1024 =~ m/query id \d+(?: ([A-Za-z]\S+))?(?: $w(?: (.*))?)?$/m; 1025 if ( ($hostname || '') eq 'Slave' ) { 1026 $hostname = ''; 1027 $user = 'system user'; 1028 $query_status = "Slave has $query_status"; 1029 } 1030 } 1031 else { 1032 $user = 'system user'; 1033 } 1034 } 1035 } 1036 } 1037 1038 my ( $lock_wait_status, $lock_structs, $heap_size, $row_locks, $undo_log_entries ) 1039 = $txn 1040 =~ m/^(?:(\D*) )?$d lock struct\(s\), heap size $d(?:, $d row lock\(s\))?(?:, undo log entries $d)?$/m; 1041 my ( $lock_wait_time ) 1042 = $txn 1043 =~ m/^------- TRX HAS BEEN WAITING $d SEC/m; 1044 1045 my $locks; 1046 # If the transaction has locks, grab the locks. 1047 if ( $txn =~ m/^TABLE LOCK|RECORD LOCKS/ ) { 1048 $locks = [parse_innodb_record_locks($txn, $complete, $debug, $full)]; 1049 } 1050 1051 my ( $tables_in_use, $tables_locked ) 1052 = $txn 1053 =~ m/^mysql tables in use $d, locked $d$/m; 1054 my ( $txn_doesnt_see_ge, $txn_sees_lt ) 1055 = $txn 1056 =~ m/^Trx read view will not see trx with id >= $t, sees < $t$/m; 1057 my $has_read_view = defined($txn_doesnt_see_ge); 1058 # Only a certain number of bytes of the query text are included here, at least 1059 # under some circumstances. Some versions include 300, some 600. 1060 my ( $query_text ) 1061 = $txn 1062 =~ m{ 1063 ^MySQL\sthread\sid\s[^\n]+\n # This comes before the query text 1064 (.*?) # The query text 1065 (?= # Followed by any of... 1066 ^Trx\sread\sview 1067 |^-------\sTRX\sHAS\sBEEN\sWAITING 1068 |^TABLE\sLOCK 1069 |^RECORD\sLOCKS\sspace\sid 1070 |^(?:---)?TRANSACTION 1071 |^\*\*\*\s\(\d\) 1072 |\Z 1073 ) 1074 }xms; 1075 if ( $query_text ) { 1076 $query_text =~ s/\s+$//; 1077 } 1078 else { 1079 $query_text = ''; 1080 } 1081 1082 my %stuff = ( 1083 active_secs => $active_secs, 1084 has_read_view => $has_read_view, 1085 heap_size => $heap_size, 1086 hostname => $hostname, 1087 ip => $ip, 1088 lock_structs => $lock_structs, 1089 lock_wait_status => $lock_wait_status, 1090 lock_wait_time => $lock_wait_time, 1091 mysql_thread_id => $mysql_thread_id, 1092 os_thread_id => $os_thread_id, 1093 proc_no => $proc_no, 1094 query_id => $query_id, 1095 query_status => $query_status, 1096 query_text => $query_text, 1097 row_locks => $row_locks, 1098 tables_in_use => $tables_in_use, 1099 tables_locked => $tables_locked, 1100 thread_decl_inside => $thread_decl_inside, 1101 thread_status => $thread_status, 1102 txn_doesnt_see_ge => $txn_doesnt_see_ge, 1103 txn_id => $txn_id, 1104 txn_sees_lt => $txn_sees_lt, 1105 txn_status => $txn_status, 1106 undo_log_entries => $undo_log_entries, 1107 user => $user, 1108 ); 1109 $stuff{'fulltext'} = $txn if $debug; 1110 $stuff{'locks'} = $locks if $locks; 1111 1112 # Some things may not be in the txn text, so make sure they are not 1113 # undef. 1114 map { $stuff{$_} ||= 0 } qw(active_secs heap_size lock_structs 1115 tables_in_use undo_log_entries tables_locked has_read_view 1116 thread_decl_inside lock_wait_time proc_no row_locks); 1117 map { $stuff{$_} ||= "" } qw(thread_status txn_doesnt_see_ge 1118 txn_sees_lt query_status ip query_text lock_wait_status user); 1119 $stuff{'hostname'} ||= $stuff{'ip'}; 1120 1121 return \%stuff; 1122} 1123 1124sub parse_tx_section { 1125 my ( $section, $complete, $debug, $full ) = @_; 1126 return unless $section && $section->{'fulltext'}; 1127 my $fulltext = $section->{'fulltext'}; 1128 $section->{'transactions'} = []; 1129 1130 # Handle the individual transactions 1131 my @transactions = $fulltext =~ m/(---TRANSACTION [0-9A-Fa-f].*?)(?=\n---TRANSACTION|$)/gs; 1132 foreach my $txn ( @transactions ) { 1133 my $stuff = parse_tx_text( $txn, $complete, $debug, $full ); 1134 delete $stuff->{'fulltext'} unless $debug; 1135 push @{$section->{'transactions'}}, $stuff; 1136 } 1137 1138 # Handle the general info 1139 @{$section}{ 'trx_id_counter' } 1140 = $fulltext =~ m/^Trx id counter $t$/m; 1141 @{$section}{ 'purge_done_for', 'purge_undo_for' } 1142 = $fulltext =~ m/^Purge done for trx's n:o < $t undo n:o < $t$/m; 1143 @{$section}{ 'history_list_len' } # This isn't present in some 4.x versions 1144 = $fulltext =~ m/^History list length $d$/m; 1145 @{$section}{ 'num_lock_structs' } 1146 = $fulltext =~ m/^Total number of lock structs in row lock hash table $d$/m; 1147 @{$section}{ 'is_truncated' } 1148 = $fulltext =~ m/^\.\.\. truncated\.\.\.$/m ? 1 : 0; 1149 1150 # Fill in things that might not be present 1151 foreach ( qw(history_list_len) ) { 1152 $section->{$_} ||= 0; 1153 } 1154 1155 delete $section->{'fulltext'} unless $debug; 1156 return 1; 1157} 1158 1159# I've read the source for this section. 1160sub parse_ro_section { 1161 my ( $section, $complete, $debug, $full ) = @_; 1162 return unless $section && $section->{'fulltext'}; 1163 my $fulltext = $section->{'fulltext'}; 1164 1165 # Grab the info 1166 @{$section}{ 'queries_inside', 'queries_in_queue' } 1167 = $fulltext =~ m/^$d queries inside InnoDB, $d queries in queue$/m; 1168 ( $section->{ 'read_views_open' } ) 1169 = $fulltext =~ m/^$d read views open inside InnoDB$/m; 1170 ( $section->{ 'n_reserved_extents' } ) 1171 = $fulltext =~ m/^$d tablespace extents now reserved for B-tree/m; 1172 @{$section}{ 'main_thread_proc_no', 'main_thread_id', 'main_thread_state' } 1173 = $fulltext =~ m/^Main thread (?:process no. $d, )?id $d, state: (.*)$/m; 1174 @{$section}{ 'num_rows_ins', 'num_rows_upd', 'num_rows_del', 'num_rows_read' } 1175 = $fulltext =~ m/^Number of rows inserted $d, updated $d, deleted $d, read $d$/m; 1176 @{$section}{ 'ins_sec', 'upd_sec', 'del_sec', 'read_sec' } 1177 = $fulltext =~ m#^$f inserts/s, $f updates/s, $f deletes/s, $f reads/s$#m; 1178 $section->{'main_thread_proc_no'} ||= 0; 1179 1180 if (!(@{$section}{main_thread_proc_no}) && !(@{$section}{main_thread_id}) && !(@{$section}{main_thread_state})) 1181 { 1182 ### MySQL 8.0 style. 1183 @{$section}{ 'main_thread_proc_no', 'main_thread_id', 'main_thread_state' } 1184 = $fulltext =~ m/^Process ID=$d, Main thread ID=$d\s*,\s+state=\s?(.*)$/m; 1185 } 1186 map { $section->{$_} ||= 0 } qw(read_views_open n_reserved_extents); 1187 delete $section->{'fulltext'} unless $debug; 1188 return 1; 1189} 1190 1191sub parse_lg_section { 1192 my ( $section, $complete, $debug, $full ) = @_; 1193 return unless $section; 1194 my $fulltext = $section->{'fulltext'}; 1195 1196 # Grab the info 1197 ( $section->{ 'log_seq_no' } ) 1198 = $fulltext =~ m/Log sequence number \s*(\d.*)$/m; 1199 ( $section->{ 'log_flushed_to' } ) 1200 = $fulltext =~ m/Log flushed up to \s*(\d.*)$/m; 1201 ( $section->{ 'last_chkp' } ) 1202 = $fulltext =~ m/Last checkpoint at \s*(\d.*)$/m; 1203 @{$section}{ 'pending_log_writes', 'pending_chkp_writes' } 1204 = $fulltext =~ m/$d pending log (?:writes|flushes), $d pending chkp writes/; 1205 @{$section}{ 'log_ios_done', 'log_ios_s' } 1206 = $fulltext =~ m#$d log i/o's done, $f log i/o's/second#; 1207 1208 delete $section->{'fulltext'} unless $debug; 1209 return 1; 1210} 1211 1212sub parse_ib_section { 1213 my ( $section, $complete, $debug, $full ) = @_; 1214 return unless $section && $section->{'fulltext'}; 1215 my $fulltext = $section->{'fulltext'}; 1216 1217 # Some servers will output ibuf information for tablespace 0, as though there 1218 # might be many tablespaces with insert buffers. (In practice I believe 1219 # the source code shows there will only ever be one). I have to parse both 1220 # cases here, but I assume there will only be one. 1221 @{$section}{ 'size', 'free_list_len', 'seg_size' } 1222 = $fulltext =~ m/^Ibuf(?: for space 0)?: size $d, free list len $d, seg size $d/m; 1223 @{$section}{ 'inserts', 'merged_recs', 'merges' } 1224 = $fulltext =~ m/^$d inserts, $d merged recs, $d merges$/m; 1225 if ( ! defined $section->{inserts} ) { 1226 @{$section}{ 'inserts' } 1227 = $fulltext =~ m/merged operations:\n insert $d,/s; 1228 # This isn't really true, but it's not really important either. We already 1229 # aren't supporting the 'delete' operations. 1230 @{$section}{ 'merged_recs', 'merges' } = (0, 0); 1231 } 1232 1233 ### TODO: how to behave when innodb_adaptive_hash_index_parts > 1? 1234 @{$section}{ 'hash_table_size', 'used_cells', 'bufs_in_node_heap' } 1235 = $fulltext =~ m/^Hash table size $d(?:, used cells $d)?, node heap has $d buffer\(s\)$/m; 1236 @{$section}{ 'hash_searches_s', 'non_hash_searches_s' } 1237 = $fulltext =~ m{^$f hash searches/s, $f non-hash searches/s$}m; 1238 1239 delete $section->{'fulltext'} unless $debug; 1240 return 1; 1241} 1242 1243sub parse_wait_array { 1244 my ( $text, $complete, $debug, $full ) = @_; 1245 my %result; 1246 1247 @result{ qw(thread waited_at_filename waited_at_line waited_secs) } 1248 = $text =~ m/^--Thread $d has waited at $fl for $f seconds/m; 1249 1250 # Depending on whether it's a SYNC_MUTEX,RW_LOCK_EX,RW_LOCK_SHARED, 1251 # there will be different text output 1252 if ( $text =~ m/^Mutex at/m ) { 1253 $result{'request_type'} = 'M'; 1254 @result{ qw( lock_mem_addr lock_cfile_name lock_cline lock_var) } 1255 = $text =~ m/^Mutex at $h created file $fl, lock var $d$/m; 1256 @result{ qw( waiters_flag )} 1257 = $text =~ m/^waiters flag $d$/m; 1258 } 1259 else { 1260 @result{ qw( request_type lock_mem_addr lock_cfile_name lock_cline) } 1261 = $text =~ m/^(.)-lock on RW-latch at $h created in file $fl$/m; 1262 @result{ qw( writer_thread writer_lock_mode ) } 1263 = $text =~ m/^a writer \(thread id $d\) has reserved it in mode (.*)$/m; 1264 @result{ qw( num_readers waiters_flag )} 1265 = $text =~ m/^number of readers $d, waiters flag $d$/m; 1266 @result{ qw(last_s_file_name last_s_line ) } 1267 = $text =~ m/Last time read locked in file $fl$/m; 1268 @result{ qw(last_x_file_name last_x_line ) } 1269 = $text =~ m/Last time write locked in file $fl$/m; 1270 } 1271 1272 $result{'cell_waiting'} = $text =~ m/^wait has ended$/m ? 0 : 1; 1273 $result{'cell_event_set'} = $text =~ m/^wait is ending$/m ? 1 : 0; 1274 1275 # Because there are two code paths, some things won't get set. 1276 map { $result{$_} ||= '' } 1277 qw(last_s_file_name last_x_file_name writer_lock_mode); 1278 map { $result{$_} ||= 0 } 1279 qw(num_readers lock_var last_s_line last_x_line writer_thread); 1280 1281 return \%result; 1282} 1283 1284sub parse_sm_section { 1285 my ( $section, $complete, $debug, $full ) = @_; 1286 return 0 unless $section && $section->{'fulltext'}; 1287 my $fulltext = $section->{'fulltext'}; 1288 1289 # Grab the info 1290 @{$section}{ 'reservation_count', 'signal_count' } 1291 = $fulltext =~ m/^OS WAIT ARRAY INFO: reservation count $d, signal count $d$/m; 1292 @{$section}{ 'mutex_spin_waits', 'mutex_spin_rounds', 'mutex_os_waits' } 1293 = $fulltext =~ m/^Mutex spin waits $d, rounds $d, OS waits $d$/m; 1294 @{$section}{ 'rw_shared_spins', 'rw_shared_os_waits', 'rw_excl_spins', 'rw_excl_os_waits' } 1295 = $fulltext =~ m/^RW-shared spins $d, OS waits $d; RW-excl spins $d, OS waits $d$/m; 1296 if ( ! defined $section->{rw_shared_spins} ) { 1297 ### TODO: RW-sx 1298 @{$section}{ 'rw_shared_spins', 'rw_shared_os_waits'} 1299 = $fulltext =~ m/^RW-shared spins $d, rounds \d+, OS waits $d$/m; 1300 @{$section}{ 'rw_excl_spins', 'rw_excl_os_waits' } 1301 = $fulltext =~ m/^RW-excl spins $d, rounds \d+, OS waits $d$/m; 1302 } 1303 1304 # Look for info on waits. 1305 my @waits = $fulltext =~ m/^(--Thread.*?)^(?=Mutex spin|--Thread)/gms; 1306 $section->{'waits'} = [ map { parse_wait_array($_, $complete, $debug) } @waits ]; 1307 $section->{'wait_array_size'} = scalar(@waits); 1308 1309 delete $section->{'fulltext'} unless $debug; 1310 return 1; 1311} 1312 1313# I've read the source for this section. 1314sub parse_bp_section { 1315 my ( $section, $complete, $debug, $full ) = @_; 1316 return unless $section && $section->{'fulltext'}; 1317 my $fulltext = $section->{'fulltext'}; 1318 1319 # Grab the info 1320 @{$section}{ 'total_mem_alloc' } 1321 = $fulltext =~ m/^Total (?:large )?memory allocated $d/m; 1322 @{$section}{ 'add_pool_alloc' } 1323 = $fulltext =~ m/in additional pool allocated $d$/m; 1324 @{$section}{'dict_mem_alloc'} = $fulltext =~ m/Dictionary memory allocated $d/; 1325 @{$section}{'awe_mem_alloc'} = $fulltext =~ m/$d MB of AWE memory/; 1326 @{$section}{'buf_pool_size'} = $fulltext =~ m/^Buffer pool size\s*$d$/m; 1327 @{$section}{'buf_free'} = $fulltext =~ m/^Free buffers\s*$d$/m; 1328 @{$section}{'pages_total'} = $fulltext =~ m/^Database pages\s*$d$/m; 1329 @{$section}{'pages_modified'} = $fulltext =~ m/^Modified db pages\s*$d$/m; 1330 @{$section}{'pages_read', 'pages_created', 'pages_written'} 1331 = $fulltext =~ m/^Pages read $d, created $d, written $d$/m; 1332 @{$section}{'page_reads_sec', 'page_creates_sec', 'page_writes_sec'} 1333 = $fulltext =~ m{^$f reads/s, $f creates/s, $f writes/s$}m; 1334 @{$section}{'buf_pool_hits', 'buf_pool_reads'} 1335 = $fulltext =~ m{Buffer pool hit rate $d / $d}m; 1336 if ($fulltext =~ m/^No buffer pool page gets since the last printout$/m) { 1337 @{$section}{'buf_pool_hits', 'buf_pool_reads'} = (0, 0); 1338 @{$section}{'buf_pool_hit_rate'} = '--'; 1339 } 1340 else { 1341 @{$section}{'buf_pool_hit_rate'} 1342 = $fulltext =~ m{Buffer pool hit rate (\d+ / \d+)}m; 1343 } 1344 @{$section}{'reads_pending'} = $fulltext =~ m/^Pending reads\s+$d/m; 1345 @{$section}{'writes_pending_lru', 'writes_pending_flush_list', 'writes_pending_single_page' } 1346 = $fulltext =~ m/^Pending writes: LRU $d, flush list $d, single page $d$/m; 1347 1348 map { $section->{$_} ||= 0 } 1349 qw(writes_pending_lru writes_pending_flush_list writes_pending_single_page 1350 awe_mem_alloc dict_mem_alloc); 1351 @{$section}{'writes_pending'} = List::Util::sum( 1352 @{$section}{ qw(writes_pending_lru writes_pending_flush_list writes_pending_single_page) }); 1353 1354 delete $section->{'fulltext'} unless $debug; 1355 return 1; 1356} 1357 1358# I've read the source for this. 1359sub parse_io_section { 1360 my ( $section, $complete, $debug, $full ) = @_; 1361 return unless $section && $section->{'fulltext'}; 1362 my $fulltext = $section->{'fulltext'}; 1363 $section->{'threads'} = {}; 1364 1365 # Grab the I/O thread info 1366 my @threads = $fulltext =~ m<^(I/O thread \d+ .*)$>gm; 1367 foreach my $thread (@threads) { 1368 my ( $tid, $state, $purpose, $event_set ) 1369 = $thread =~ m{I/O thread $d state: (.+?) \((.*)\)(?: ev set)?$}m; 1370 if ( defined $tid ) { 1371 $section->{'threads'}->{$tid} = { 1372 thread => $tid, 1373 state => $state, 1374 purpose => $purpose, 1375 event_set => $event_set ? 1 : 0, 1376 }; 1377 } 1378 } 1379 1380 # Grab the reads/writes/flushes info 1381 @{$section}{ 'pending_normal_aio_reads', 'pending_aio_writes' } 1382 = $fulltext =~ m/^Pending normal aio reads: $d(?: [^\]]*\])?, aio writes: $d/m; 1383 @{$section}{ 'pending_ibuf_aio_reads', 'pending_log_ios', 'pending_sync_ios' } 1384 = $fulltext =~ m{^ ibuf aio reads:\s?(\d+)?, log i/o's:\s?(\d+)?, sync i/o's:\s?(\d+)?$}m; 1385 @{$section}{ 'flush_type', 'pending_log_flushes', 'pending_buffer_pool_flushes' } 1386 = $fulltext =~ m/^Pending flushes \($w\) log: $d; buffer pool: $d$/m; 1387 @{$section}{ 'os_file_reads', 'os_file_writes', 'os_fsyncs' } 1388 = $fulltext =~ m/^$d OS file reads, $d OS file writes, $d OS fsyncs$/m; 1389 @{$section}{ 'reads_s', 'avg_bytes_s', 'writes_s', 'fsyncs_s' } 1390 = $fulltext =~ m{^$f reads/s, $d avg bytes/read, $f writes/s, $f fsyncs/s$}m; 1391 @{$section}{ 'pending_preads', 'pending_pwrites' } 1392 = $fulltext =~ m/$d pending preads, $d pending pwrites$/m; 1393 @{$section}{ 'pending_preads', 'pending_pwrites' } = (0, 0) 1394 unless defined($section->{'pending_preads'}); 1395 1396 delete $section->{'fulltext'} unless $debug; 1397 return 1; 1398} 1399 1400sub _debug { 1401 my ( $debug, $msg ) = @_; 1402 if ( $debug ) { 1403 die $msg; 1404 } 1405 else { 1406 warn $msg; 1407 } 1408 return 1; 1409} 1410 14111; 1412 1413# end_of_package InnoDBParser 1414 1415package main; 1416 1417use sigtrap qw(handler finish untrapped normal-signals); 1418 1419use Data::Dumper; 1420use DBI; 1421use English qw(-no_match_vars); 1422use File::Basename qw(dirname); 1423use File::Temp; 1424use Getopt::Long; 1425use List::Util qw(max min maxstr sum); 1426use POSIX qw(ceil); 1427use Time::HiRes qw(time sleep); 1428use Term::ReadKey qw(ReadMode ReadKey); 1429 1430# License and warranty information. {{{1 1431# ########################################################################### 1432 1433my $innotop_license = <<"LICENSE"; 1434 1435This is innotop version $VERSION, a MySQL and InnoDB monitor. 1436 1437This program is copyright (c) 2006 Baron Schwartz. 1438Feedback and improvements are welcome. 1439 1440THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED 1441WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF 1442MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. 1443 1444This program is free software; you can redistribute it and/or modify it under 1445the terms of the GNU General Public License as published by the Free Software 1446Foundation, version 2; OR the Perl Artistic License. On UNIX and similar 1447systems, you can issue `man perlgpl' or `man perlartistic' to read these 1448licenses. 1449 1450You should have received a copy of the GNU General Public License along with 1451this program; if not, write to the Free Software Foundation, Inc., 59 Temple 1452Place, Suite 330, Boston, MA 02111-1307 USA. 1453LICENSE 1454 1455# Configuration information and global setup {{{1 1456# ########################################################################### 1457 1458# Really, really, super-global variables. 1459my @config_versions = ( 1460 "000-000-000", "001-003-000", # config file was one big name-value hash. 1461 "001-003-000", "001-004-002", # config file contained non-user-defined stuff. 1462); 1463 1464my $clear_screen_sub; 1465my $dsn_parser = new DSNParser(); 1466 1467# This defines expected properties and defaults for the column definitions that 1468# eventually end up in tbl_meta. 1469my %col_props = ( 1470 hdr => '', 1471 just => '-', 1472 dec => 0, # Whether to align the column on the decimal point 1473 num => 0, 1474 label => '', 1475 user => 0, 1476 src => '', 1477 tbl => '', # Helps when writing/reading custom columns in config files 1478 minw => 0, 1479 maxw => 0, 1480 trans => [], 1481 agg => 'first', # Aggregate function 1482 aggonly => 0, # Whether to show only when tbl_meta->{aggregate} is true 1483 agghide => 0, # Whether NOT to show when tbl_meta->{aggregate} is true 1484); 1485 1486# Actual DBI connections to MySQL servers. 1487my %dbhs; 1488 1489# Command-line parameters {{{2 1490# ########################################################################### 1491 1492my @opt_spec = ( 1493 { s => 'help', d => 'Show this help message' }, 1494 { s => 'color|C!', d => 'Use terminal coloring (default)', c => 'color' }, 1495 { s => 'config|c=s', d => 'Config file to read' }, 1496 { s => 'nonint|n', d => 'Non-interactive, output tab-separated fields' }, 1497 { s => 'count=i', d => 'Number of updates before exiting' }, 1498 { s => 'delay|d=f', d => 'Delay between updates in seconds', c => 'interval' }, 1499 { s => 'mode|m=s', d => 'Operating mode to start in', c => 'mode' }, 1500 { s => 'inc|i!', d => 'Measure incremental differences', c => 'status_inc' }, 1501 { s => 'spark=i', d => 'Length of status sparkline (default 10)', c=> 'spark' }, 1502 { s => 'write|w', d => 'Write running configuration into home directory if no config files were loaded' }, 1503 { s => 'skipcentral|s', d => 'Skip reading the central configuration file' }, 1504 { s => 'version', d => 'Output version information and exit' }, 1505 { s => 'user|u=s', d => 'User for login if not current user' }, 1506 { s => 'password|p=s', d => 'Password to use for connection' }, 1507 { s => 'askpass', d => 'Prompt for a password when connecting to MySQL'}, 1508 { s => 'host|h=s', d => 'Connect to host' }, 1509 { s => 'port|P=i', d => 'Port number to use for connection' }, 1510 { s => 'socket|S=s', d => 'MySQL socket to use for connection' }, 1511 { s => 'timestamp|t+', d => 'Print timestamp in -n mode (1: per iter; 2: per line)' }, 1512 { s => 'ssl', d => 'Passed to mysql_ssl', c => 'mysql_ssl' }, 1513 { s => 'ssl_ca_file=s', d => 'Passed to mysql_ssl_ca_file', c => 'mysql_ssl_ca_file' }, 1514 { s => 'ssl_ca_path=s', d => 'Passed to mysql_ssl_ca_path', c => 'mysql_ssl_ca_path' }, 1515 { s => 'ssl_verify_server_cert', d => 'Passed to mysql_ssl_verify_server_cert', c => 'mysql_ssl_verify_server_cert' }, 1516 { s => 'ssl_client_key=s', d => 'Passed to mysql_ssl_client_key', c => 'mysql_ssl_client_key' }, 1517 { s => 'ssl_client_cert=s', d => 'Passed to mysql_ssl_client_cert', c => 'mysql_ssl_client_cert' }, 1518 { s => 'ssl_cipher=s', d => 'Passed to mysql_ssl_cipher', c => 'mysql_ssl_cipher' }, 1519); 1520 1521# This is the container for the command-line options' values to be stored in 1522# after processing. Initial values are defaults. 1523my %opts = ( 1524 n => !( -t STDIN && -t STDOUT ), # If in/out aren't to terminals, we're interactive 1525); 1526# Post-process... 1527my %opt_seen; 1528foreach my $spec ( @opt_spec ) { 1529 my ( $long, $short ) = $spec->{s} =~ m/^(\w+)(?:\|([^!+=:]*))?/; 1530 $spec->{k} = $short || $long; 1531 $spec->{l} = $long; 1532 $spec->{t} = $short; 1533 $spec->{n} = $spec->{s} =~ m/!/; 1534 $opts{$spec->{k}} = undef unless defined $opts{$spec->{k}}; 1535 die "Duplicate option $spec->{k}" if $opt_seen{$spec->{k}}++; 1536} 1537 1538Getopt::Long::Configure('no_ignore_case', 'bundling'); 1539GetOptions( map { $_->{s} => \$opts{$_->{k}} } @opt_spec) or $opts{help} = 1; 1540 1541if ( $opts{version} ) { 1542 print "innotop Ver $VERSION\n"; 1543 exit(0); 1544} 1545 1546if ( $opts{c} and ! -f $opts{c} ) { 1547 print $opts{c} . " doesn't exist. Exiting.\n"; 1548 exit(1); 1549} 1550 1551if ( $opts{'askpass'} ) { 1552 $opts{'p'} = noecho_password("Enter password "); 1553} 1554 1555if ( $opts{'help'} ) { 1556 print "Usage: innotop <options> <innodb-status-file>\n\n"; 1557 my $maxw = max(map { length($_->{l}) + ($_->{n} ? 4 : 0)} @opt_spec); 1558 foreach my $spec ( sort { $a->{l} cmp $b->{l} } @opt_spec ) { 1559 my $long = $spec->{n} ? "[no]$spec->{l}" : $spec->{l}; 1560 my $short = $spec->{t} ? "-$spec->{t}" : ''; 1561 printf(" --%-${maxw}s %-4s %s\n", $long, $short, $spec->{d}); 1562 } 1563 print <<USAGE; 1564 1565innotop is a MySQL and InnoDB transaction/status monitor, like 'top' for 1566MySQL. It displays queries, InnoDB transactions, lock waits, deadlocks, 1567foreign key errors, open tables, replication status, buffer information, 1568row operations, logs, I/O operations, load graph, and more. You can 1569monitor many servers at once with innotop. 1570 1571USAGE 1572 exit(1); 1573} 1574 1575if ( defined($opts{p}) && length($opts{p}) == 0 ) { 1576 print "Enter password: "; 1577 ReadMode('noecho'); 1578 $opts{p} = <STDIN>; 1579 chomp($opts{p}); 1580 ReadMode('normal'); 1581} 1582 1583# Meta-data (table definitions etc) {{{2 1584# ########################################################################### 1585 1586# Expressions {{{3 1587# Convenience so I can copy/paste these in several places... 1588# ########################################################################### 1589my %exprs = ( 1590 Host => q{my $host = host || hostname || ''; ($host) = $host =~ m/^((?:[\d.]+(?=:|$))|(?:[a-z0-9-]+))/i; return $host || ''}, 1591 Port => q{my ($p) = host =~ m/:(.*)$/; return $p || 0}, 1592 OldVersions => q{dulint_to_int(IB_tx_trx_id_counter) - dulint_to_int(IB_tx_purge_done_for)}, 1593 MaxTxnTime => q/max(map{ $_->{active_secs} } @{ IB_tx_transactions }) || 0/, 1594 NumTxns => q{scalar @{ IB_tx_transactions } }, 1595 DirtyBufs => q{ $cur->{IB_bp_pages_modified} / ($cur->{IB_bp_buf_pool_size} || 1) }, 1596 BufPoolFill => q{ $cur->{IB_bp_pages_total} / ($cur->{IB_bp_buf_pool_size} || 1) }, 1597 ServerLoad => q{ $cur->{Threads_connected}/(Queries||1)/Uptime_hires }, 1598 Connection => q{ max_connections || $cur->{Threads_connected} }, 1599 chcxn_2_cxn => q{ if ( defined($cur->{cxn}) ) { return $cur->{cxn}; } else { my ($cha, $conn) = split ("=",$cur->{chcxn}) ; return $conn; } }, 1600 chcxn_2_ch => q{ if ( defined($cur->{channel_name}) ) { return $cur->{channel_name}; } else { my ($cha, $conn) = split ("=",$cur->{chcxn}) ; $cha = '' if ($cha = /no_channels/); return $cha || 'failed'; } }, 1601 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}, 1602 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', 1603 QcacheHitRatio => q{(Qcache_hits||0)/(((Com_select||0)+(Qcache_hits||0))||1)}, 1604 QueryDetail => q{sprintf("%2d/%2d/%2d/%2d", 1605 ((Com_select||0)+(Qcache_hits||0))/(Queries||1)*100, 1606 ((Com_insert||0)+(Com_replace||0))/(Queries||1)*100, 1607 (Com_update||0)/(Queries||1)*100, 1608 (Com_delete||0)/(Queries||1)*100, 1609)}, 1610); 1611 1612# ########################################################################### 1613# Column definitions {{{3 1614# Defines every column in every table. A named column has the following 1615# properties: 1616# * hdr Column header/title 1617# * label Documentation for humans. 1618# * num Whether it's numeric (for sorting). 1619# * just Alignment; generated from num, user-overridable in tbl_meta 1620# * minw, maxw Auto-generated, user-overridable. 1621# Values from this hash are just copied to tbl_meta, which is where everything 1622# else in the program should read from. 1623# ########################################################################### 1624 1625my %columns = ( 1626 active_secs => { hdr => 'SecsActive', num => 1, label => 'Seconds transaction has been active', }, 1627 add_pool_alloc => { hdr => 'Add\'l Pool', num => 1, label => 'Additional pool allocated' }, 1628 attempted_op => { hdr => 'Action', num => 0, label => 'The action that caused the error' }, 1629 awe_mem_alloc => { hdr => 'AWE Memory', num => 1, label => '[Windows] AWE memory allocated' }, 1630 binlog_cache_overflow => { hdr => 'Binlog Cache', num => 1, label => 'Transactions too big for binlog cache that went to disk' }, 1631 binlog_do_db => { hdr => 'Binlog Do DB', num => 0, label => 'binlog-do-db setting' }, 1632 binlog_ignore_db => { hdr => 'Binlog Ignore DB', num => 0, label => 'binlog-ignore-db setting' }, 1633 blocking_thread => { hdr => 'BThread', num => 1, label => 'Blocking thread' }, 1634 blocking_query => { hdr => 'Blocking Query', num => 0, label => 'Blocking query' }, 1635 blocking_rows_modified => { hdr => 'BRowsMod', num => 1, label => 'Blocking number rows modified' }, 1636 blocking_age => { hdr => 'BAge', num => 1, label => 'Blocking age' }, 1637 blocking_wait_secs => { hdr => 'BWait', num => 1, label => 'Blocking wait time' }, 1638 blocking_user => { hdr => 'BUser', num => 0, label => 'Blocking user' }, 1639 blocking_host => { hdr => 'BHost', num => 0, label => 'Blocking host' }, 1640 blocking_db => { hdr => 'BDB', num => 0, label => 'Blocking database' }, 1641 blocking_status => { hdr => 'BStatus', num => 0, label => 'Blocking thread status' }, 1642 bps_in => { hdr => 'BpsIn', num => 1, label => 'Bytes per second received by the server', }, 1643 bps_out => { hdr => 'BpsOut', num => 1, label => 'Bytes per second sent by the server', }, 1644 buf_free => { hdr => 'Free Bufs', num => 1, label => 'Buffers free in the buffer pool' }, 1645 buf_pool_hit_rate => { hdr => 'Hit Rate', num => 0, label => 'Buffer pool hit rate' }, 1646 buf_pool_hits => { hdr => 'Hits', num => 1, label => 'Buffer pool hits' }, 1647 buf_pool_reads => { hdr => 'Reads', num => 1, label => 'Buffer pool reads' }, 1648 buf_pool_size => { hdr => 'Size', num => 1, label => 'Buffer pool size' }, 1649 bufs_in_node_heap => { hdr => 'Node Heap Bufs', num => 1, label => 'Buffers in buffer pool node heap' }, 1650 bytes_behind_master => { hdr => 'ByteLag', num => 1, label => 'Bytes the slave lags the master in binlog' }, 1651 cell_event_set => { hdr => 'Ending?', num => 1, label => 'Whether the cell event is set' }, 1652 cell_waiting => { hdr => 'Waiting?', num => 1, label => 'Whether the cell is waiting' }, 1653 channel_name => { hdr => 'Channel', num => 0, label => 'The name of the replication channel' }, 1654 child_db => { hdr => 'Child DB', num => 0, label => 'The database of the child table' }, 1655 child_index => { hdr => 'Child Index', num => 0, label => 'The index in the child table' }, 1656 child_table => { hdr => 'Child Table', num => 0, label => 'The child table' }, 1657 cmd => { hdr => 'Cmd', num => 0, label => 'Type of command being executed', }, 1658 cnt => { hdr => 'Cnt', num => 0, label => 'Count', agg => 'count', aggonly => 1 }, 1659 connections => { hdr => 'Cxns', num => 1, label => 'Connections' }, 1660 connect_retry => { hdr => 'Connect Retry', num => 1, label => 'Slave connect-retry timeout' }, 1661 cxn => { hdr => 'CXN', num => 0, label => 'Connection from which the data came', }, 1662 db => { hdr => 'DB', num => 0, label => 'Current database', }, 1663 dict_mem_alloc => { hdr => 'Dict Mem', num => 1, label => 'Dictionary memory allocated' }, 1664 dirty_bufs => { hdr => 'Dirty Buf', num => 1, label => 'Dirty buffer pool pages' }, 1665 dl_txn_num => { hdr => 'Num', num => 0, label => 'Deadlocked transaction number', }, 1666 event_set => { hdr => 'Evt Set?', num => 1, label => '[Win32] if a wait event is set', }, 1667 exec_master_log_pos => { hdr => 'Exec Master Log Pos', num => 1, label => 'Exec Master Log Position' }, 1668 executed_gtid_set => { hdr => 'Executed GTID Set', num => 0, label => 'Executed GTID Set', }, 1669 fk_name => { hdr => 'Constraint', num => 0, label => 'The name of the FK constraint' }, 1670 free_list_len => { hdr => 'Free List Len', num => 1, label => 'Length of the free list' }, 1671 has_read_view => { hdr => 'Rd View', num => 1, label => 'Whether the transaction has a read view' }, 1672 hash_searches_s => { hdr => 'Hash/Sec', num => 1, label => 'Number of hash searches/sec' }, 1673 hash_table_size => { hdr => 'Size', num => 1, label => 'Number of non-hash searches/sec' }, 1674 heap_no => { hdr => 'Heap', num => 1, label => 'Heap number' }, 1675 heap_size => { hdr => 'Heap', num => 1, label => 'Heap size' }, 1676 history_list_len => { hdr => 'History', num => 1, label => 'History list length' }, 1677 host_and_domain => { hdr => 'Host', num => 0, label => 'Hostname/IP and domain' }, 1678 host_and_port => { hdr => 'Host/IP', num => 0, label => 'Hostname or IP address, and port number', }, 1679 hostname => { hdr => 'Host', num => 0, label => 'Hostname' }, 1680 index => { hdr => 'Index', num => 0, label => 'The index involved', agghide => 1 }, 1681 index_ref => { hdr => 'Index Ref', num => 0, label => 'Index referenced' }, 1682 info => { hdr => 'Query', num => 0, label => 'Info or the current query', }, 1683 insert_intention => { hdr => 'Ins Intent', num => 1, label => 'Whether the thread was trying to insert' }, 1684 inserts => { hdr => 'Inserts', num => 1, label => 'Inserts' }, 1685 io_bytes_s => { hdr => 'Bytes/Sec', num => 1, label => 'Average I/O bytes/sec' }, 1686 io_flush_type => { hdr => 'Flush Type', num => 0, label => 'I/O Flush Type' }, 1687 io_fsyncs_s => { hdr => 'fsyncs/sec', num => 1, label => 'I/O fsyncs/sec' }, 1688 io_reads_s => { hdr => 'Reads/Sec', num => 1, label => 'Average I/O reads/sec' }, 1689 io_writes_s => { hdr => 'Writes/Sec', num => 1, label => 'Average I/O writes/sec' }, 1690 ip => { hdr => 'IP', num => 0, label => 'IP address' }, 1691 is_name_locked => { hdr => 'Locked', num => 1, label => 'Whether table is name locked', }, 1692 key_buffer_hit => { hdr => 'KCacheHit', num => 1, label => 'Key cache hit ratio', }, 1693 key_len => { hdr => 'Key Length', num => 1, label => 'Number of bytes used in the key' }, 1694 last_chkp => { hdr => 'Last Checkpoint', num => 0, label => 'Last log checkpoint' }, 1695 last_errno => { hdr => 'Last Errno', num => 1, label => 'Last error number' }, 1696 last_error => { hdr => 'Last Error', num => 0, label => 'Last error' }, 1697 last_s_file_name => { hdr => 'S-File', num => 0, label => 'Filename where last read locked' }, 1698 last_s_line => { hdr => 'S-Line', num => 1, label => 'Line where last read locked' }, 1699 last_x_file_name => { hdr => 'X-File', num => 0, label => 'Filename where last write locked' }, 1700 last_x_line => { hdr => 'X-Line', num => 1, label => 'Line where last write locked' }, 1701 last_pct => { hdr => 'Pct', num => 1, label => 'Last Percentage' }, 1702 last_total => { hdr => 'Last Total', num => 1, label => 'Last Total' }, 1703 last_value => { hdr => 'Last Incr', num => 1, label => 'Last Value' }, 1704 load => { hdr => 'Load', num => 1, label => 'Server load' }, 1705 locked_count => { hdr => 'Lock', num => 1, label => 'Number of locked threads' }, 1706 lock_cfile_name => { hdr => 'Crtd File', num => 0, label => 'Filename where lock created' }, 1707 lock_cline => { hdr => 'Crtd Line', num => 1, label => 'Line where lock created' }, 1708 lock_info => { hdr => 'Lock Info', num => 0, label => 'Lock information' }, 1709 lock_mem_addr => { hdr => 'Addr', num => 0, label => 'The lock memory address' }, 1710 lock_mode => { hdr => 'Mode', num => 0, label => 'The lock mode' }, 1711 lock_structs => { hdr => 'LStrcts', num => 1, label => 'Number of lock structs' }, 1712 lock_type => { hdr => 'Type', num => 0, label => 'The lock type' }, 1713 lock_var => { hdr => 'Lck Var', num => 1, label => 'The lock variable' }, 1714 lock_wait_time => { hdr => 'Wait', num => 1, label => 'How long txn has waited for a lock' }, 1715 log_flushed_to => { hdr => 'Flushed To', num => 0, label => 'Log position flushed to' }, 1716 log_ios_done => { hdr => 'IO Done', num => 1, label => 'Log I/Os done' }, 1717 log_ios_s => { hdr => 'IO/Sec', num => 1, label => 'Average log I/Os per sec' }, 1718 log_seq_no => { hdr => 'Sequence No.', num => 0, label => 'Log sequence number' }, 1719 longest_sql => { hdr => 'SQL', num => 0, label => 'Longest-running SQL statement' }, 1720 main_thread_id => { hdr => 'Main Thread ID', num => 1, label => 'Main thread ID' }, 1721 main_thread_proc_no => { hdr => 'Main Thread Proc', num => 1, label => 'Main thread process number' }, 1722 main_thread_state => { hdr => 'Main Thread State', num => 0, label => 'Main thread state' }, 1723 master_file => { hdr => 'File', num => 0, label => 'Master file' }, 1724 master_host => { hdr => 'Master', num => 0, label => 'Master server hostname' }, 1725 master_log_file => { hdr => 'Master Log File', num => 0, label => 'Master log file' }, 1726 master_port => { hdr => 'Master Port', num => 1, label => 'Master port' }, 1727 master_pos => { hdr => 'Position', num => 1, label => 'Master position' }, 1728 master_ssl_allowed => { hdr => 'Master SSL Allowed', num => 0, label => 'Master SSL Allowed' }, 1729 master_ssl_ca_file => { hdr => 'Master SSL CA File', num => 0, label => 'Master SSL Cert Auth File' }, 1730 master_ssl_ca_path => { hdr => 'Master SSL CA Path', num => 0, label => 'Master SSL Cert Auth Path' }, 1731 master_ssl_cert => { hdr => 'Master SSL Cert', num => 0, label => 'Master SSL Cert' }, 1732 master_ssl_cipher => { hdr => 'Master SSL Cipher', num => 0, label => 'Master SSL Cipher' }, 1733 master_ssl_key => { hdr => 'Master SSL Key', num => 0, label => 'Master SSL Key' }, 1734 master_user => { hdr => 'Master User', num => 0, label => 'Master username' }, 1735 master_uuid => { hdr => 'Master UUID', num => 0, label => 'Master UUID', }, 1736 max_txn => { hdr => 'MaxTxnTime', num => 1, label => 'MaxTxn' }, 1737 max_query_time => { hdr => 'MaxSQL', num => 1, label => 'Longest running SQL' }, 1738 merged_recs => { hdr => 'Merged Recs', num => 1, label => 'Merged records' }, 1739 merges => { hdr => 'Merges', num => 1, label => 'Merges' }, 1740 miss_rate => { hdr => 'Miss', num => 1, label => 'InnoDB buffer pool miss rate' }, 1741 mutex_os_waits => { hdr => 'Waits', num => 1, label => 'Mutex OS Waits' }, 1742 mutex_spin_rounds => { hdr => 'Rounds', num => 1, label => 'Mutex Spin Rounds' }, 1743 mutex_spin_waits => { hdr => 'Spins', num => 1, label => 'Mutex Spin Waits' }, 1744 mysql_thread_id => { hdr => 'ID', num => 1, label => 'MySQL connection (thread) ID', }, 1745 name => { hdr => 'Name', num => 0, label => 'Variable Name' }, 1746 n_bits => { hdr => '# Bits', num => 1, label => 'Number of bits' }, 1747 non_hash_searches_s => { hdr => 'Non-Hash/Sec', num => 1, label => 'Non-hash searches/sec' }, 1748 num_deletes => { hdr => 'Del', num => 1, label => 'Number of deletes' }, 1749 num_deletes_sec => { hdr => 'Del/Sec', num => 1, label => 'Number of deletes' }, 1750 num_inserts => { hdr => 'Ins', num => 1, label => 'Number of inserts' }, 1751 num_inserts_sec => { hdr => 'Ins/Sec', num => 1, label => 'Number of inserts' }, 1752 num_readers => { hdr => 'Readers', num => 1, label => 'Number of readers' }, 1753 num_reads => { hdr => 'Read', num => 1, label => 'Number of reads' }, 1754 num_reads_sec => { hdr => 'Read/Sec', num => 1, label => 'Number of reads' }, 1755 num_res_ext => { hdr => 'BTree Extents', num => 1, label => 'Number of extents reserved for B-Tree' }, 1756 num_rows => { hdr => 'Row Count', num => 1, label => 'Number of rows estimated to examine' }, 1757 num_times_open => { hdr => 'In Use', num => 1, label => '# times table is opened', }, 1758 num_txns => { hdr => 'Txns', num => 1, label => 'Number of transactions' }, 1759 num_updates => { hdr => 'Upd', num => 1, label => 'Number of updates' }, 1760 num_updates_sec => { hdr => 'Upd/Sec', num => 1, label => 'Number of updates' }, 1761 'open' => { hdr => 'Tbls', num => 1, label => 'Number of open tables' }, 1762 os_file_reads => { hdr => 'OS Reads', num => 1, label => 'OS file reads' }, 1763 os_file_writes => { hdr => 'OS Writes', num => 1, label => 'OS file writes' }, 1764 os_fsyncs => { hdr => 'OS fsyncs', num => 1, label => 'OS fsyncs' }, 1765 os_thread_id => { hdr => 'OS Thread', num => 1, label => 'The operating system thread ID' }, 1766 p_aio_writes => { hdr => 'Async Wrt', num => 1, label => 'Pending asynchronous I/O writes' }, 1767 p_buf_pool_flushes => { hdr => 'Buffer Pool Flushes', num => 1, label => 'Pending buffer pool flushes' }, 1768 p_ibuf_aio_reads => { hdr => 'IBuf Async Rds', num => 1, label => 'Pending insert buffer asynch I/O reads' }, 1769 p_log_flushes => { hdr => 'Log Flushes', num => 1, label => 'Pending log flushes' }, 1770 p_log_ios => { hdr => 'Log I/Os', num => 1, label => 'Pending log I/O operations' }, 1771 p_normal_aio_reads => { hdr => 'Async Rds', num => 1, label => 'Pending asynchronous I/O reads' }, 1772 p_preads => { hdr => 'preads', num => 1, label => 'Pending p-reads' }, 1773 p_pwrites => { hdr => 'pwrites', num => 1, label => 'Pending p-writes' }, 1774 p_sync_ios => { hdr => 'Sync I/Os', num => 1, label => 'Pending synchronous I/O operations' }, 1775 page_creates_sec => { hdr => 'Creates/Sec', num => 1, label => 'Page creates/sec' }, 1776 page_no => { hdr => 'Page', num => 1, label => 'Page number' }, 1777 page_reads_sec => { hdr => 'Reads/Sec', num => 1, label => 'Page reads per second' }, 1778 page_writes_sec => { hdr => 'Writes/Sec', num => 1, label => 'Page writes per second' }, 1779 pages_created => { hdr => 'Created', num => 1, label => 'Pages created' }, 1780 pages_modified => { hdr => 'Dirty Pages', num => 1, label => 'Pages modified (dirty)' }, 1781 pages_read => { hdr => 'Reads', num => 1, label => 'Pages read' }, 1782 pages_total => { hdr => 'Pages', num => 1, label => 'Pages total' }, 1783 pages_written => { hdr => 'Writes', num => 1, label => 'Pages written' }, 1784 parent_col => { hdr => 'Parent Column', num => 0, label => 'The referred column in the parent table', }, 1785 parent_db => { hdr => 'Parent DB', num => 0, label => 'The database of the parent table' }, 1786 parent_index => { hdr => 'Parent Index', num => 0, label => 'The referred index in the parent table' }, 1787 parent_table => { hdr => 'Parent Table', num => 0, label => 'The parent table' }, 1788 part_id => { hdr => 'Part ID', num => 1, label => 'Sub-part ID of the query' }, 1789 partitions => { hdr => 'Partitions', num => 0, label => 'Query partitions used' }, 1790 pct => { hdr => 'Pct', num => 1, label => 'Percentage' }, 1791 pending_chkp_writes => { hdr => 'Chkpt Writes', num => 1, label => 'Pending log checkpoint writes' }, 1792 pending_log_writes => { hdr => 'Log Writes', num => 1, label => 'Pending log writes' }, 1793 port => { hdr => 'Port', num => 1, label => 'Client port number', }, 1794 possible_keys => { hdr => 'Poss. Keys', num => 0, label => 'Possible keys' }, 1795 proc_no => { hdr => 'Proc', num => 1, label => 'Process number' }, 1796 q_detail => { hdr => 'Se/In/Up/De%', num => 0, label => 'Detailed Query', }, 1797 q_cache_hit => { hdr => 'QCacheHit', num => 1, label => 'Query cache hit ratio', }, 1798 qps => { hdr => 'QPS', num => 1, label => 'How many queries/sec', }, 1799 queries_in_queue => { hdr => 'Queries Queued', num => 1, label => 'Queries in queue' }, 1800 queries_inside => { hdr => 'Queries Inside', num => 1, label => 'Queries inside InnoDB' }, 1801 query_id => { hdr => 'Query ID', num => 1, label => 'Query ID' }, 1802 query_status => { hdr => 'Query Status', num => 0, label => 'The query status' }, 1803 query_text => { hdr => 'Query Text', num => 0, label => 'The query text' }, 1804 queries => { hdr => 'Queries', num => 1, label => 'How many queries the server has gotten', }, 1805 read_master_log_pos => { hdr => 'Read Master Pos', num => 1, label => 'Read master log position' }, 1806 read_views_open => { hdr => 'Rd Views', num => 1, label => 'Number of read views open' }, 1807 reads_pending => { hdr => 'Pending Reads', num => 1, label => 'Reads pending' }, 1808 relay_log_file => { hdr => 'Relay File', num => 0, label => 'Relay log file' }, 1809 relay_log_pos => { hdr => 'Relay Pos', num => 1, label => 'Relay log position' }, 1810 relay_log_size => { hdr => 'Relay Size', num => 1, label => 'Relay log size' }, 1811 relay_master_log_file => { hdr => 'Relay Master File', num => 0, label => 'Relay master log file' }, 1812 replicate_do_db => { hdr => 'Do DB', num => 0, label => 'Replicate-do-db setting' }, 1813 replicate_do_table => { hdr => 'Do Table', num => 0, label => 'Replicate-do-table setting' }, 1814 replicate_ignore_db => { hdr => 'Ignore DB', num => 0, label => 'Replicate-ignore-db setting' }, 1815 replicate_ignore_table => { hdr => 'Ignore Table', num => 0, label => 'Replicate-do-table setting' }, 1816 replicate_wild_do_table => { hdr => 'Wild Do Table', num => 0, label => 'Replicate-wild-do-table setting' }, 1817 replicate_wild_ignore_table => { hdr => 'Wild Ignore Table', num => 0, label => 'Replicate-wild-ignore-table setting' }, 1818 request_type => { hdr => 'Type', num => 0, label => 'Type of lock the thread waits for' }, 1819 reservation_count => { hdr => 'ResCnt', num => 1, label => 'Reservation Count' }, 1820 retrieved_gtid_set => { hdr => 'Retrieved GTID Set', num => 0, label => 'Retrieved GTID Set', }, 1821 row_locks => { hdr => 'RLocks', num => 1, label => 'Number of row locks' }, 1822 rows_changed => { hdr => 'Changed', num => 1, label => 'Number of rows changed' }, 1823 rows_changed_x_indexes => { hdr => 'Chg X Idx', num => 1, label => 'Number of rows changed X indexes' }, 1824 rows_read => { hdr => 'Reads', num => 1, label => 'Number of rows read' }, 1825 rows_read_from_indexes => { hdr => 'Reads Via Idx', num => 1, label => 'Number of rows read from indexes' }, 1826 run => { hdr => 'Run', num => 1, label => 'Threads_running' }, 1827 rw_excl_os_waits => { hdr => 'RW Waits', num => 1, label => 'R/W Excl. OS Waits' }, 1828 rw_excl_spins => { hdr => 'RW Spins', num => 1, label => 'R/W Excl. Spins' }, 1829 rw_shared_os_waits => { hdr => 'Sh Waits', num => 1, label => 'R/W Shared OS Waits' }, 1830 rw_shared_spins => { hdr => 'Sh Spins', num => 1, label => 'R/W Shared Spins' }, 1831 spark_qps => { hdr => 'QPS', num => 0, label => 'QPS Sparkline' }, 1832 spark_run => { hdr => 'Run', num => 0, label => 'Threads_running Sparkline' }, 1833 scan_type => { hdr => 'Type', num => 0, label => 'Scan type in chosen' }, 1834 seg_size => { hdr => 'Seg. Size', num => 1, label => 'Segment size' }, 1835 select_type => { hdr => 'Select Type', num => 0, label => 'Type of select used' }, 1836 server_uuid => { hdr => 'Server UUID', num => 0, label => 'Server UUID', }, 1837 signal_count => { hdr => 'Signals', num => 1, label => 'Signal Count' }, 1838 size => { hdr => 'Size', num => 1, label => 'Size of the tablespace' }, 1839 skip_counter => { hdr => 'Skip Counter', num => 1, label => 'Skip counter' }, 1840 slave_catchup_rate => { hdr => 'Catchup', num => 1, label => 'How fast the slave is catching up in the binlog' }, 1841 slave_io_running => { hdr => 'Slave-IO', num => 0, label => 'Whether the slave I/O thread is running' }, 1842 slave_io_state => { hdr => 'Slave IO State', num => 0, label => 'Slave I/O thread state' }, 1843 slave_open_temp_tables => { hdr => 'Temp', num => 1, label => 'Slave open temp tables' }, 1844 slave_running => { hdr => 'Repl', num => 0, label => 'Slave running' }, 1845 slave_sql_running => { hdr => 'Slave-SQL', num => 0, label => 'Whether the slave SQL thread is running' }, 1846 sort_time => { hdr => 'SortTimeLag', num => 1, label => 'Time slave lags master sort' }, 1847 slow => { hdr => 'Slow', num => 1, label => 'How many slow queries', }, 1848 space_id => { hdr => 'Space', num => 1, label => 'Tablespace ID' }, 1849 special => { hdr => 'Special', num => 0, label => 'Special/Other info' }, 1850 state => { hdr => 'State', num => 0, label => 'Connection state', maxw => 18, }, 1851 tables_in_use => { hdr => 'Tbl Used', num => 1, label => 'Number of tables in use' }, 1852 tables_locked => { hdr => 'Tbl Lck', num => 1, label => 'Number of tables locked' }, 1853 tbl => { hdr => 'Table', num => 0, label => 'Table', }, 1854 thread => { hdr => 'Thread', num => 1, label => 'Thread number' }, 1855 thread_decl_inside => { hdr => 'Thread Inside', num => 0, label => 'What the thread is declared inside' }, 1856 thread_purpose => { hdr => 'Purpose', num => 0, label => "The thread's purpose" }, 1857 thread_status => { hdr => 'Thread Status', num => 0, label => 'The thread status' }, 1858 time => { hdr => 'Time', num => 1, label => 'Time since the last event', }, 1859 time_behind_master => { hdr => 'TimeLag', num => 1, label => 'Time slave lags master' }, 1860 timestring => { hdr => 'Timestring', num => 0, label => 'Time the event occurred' }, 1861 total => { hdr => 'Total', num => 1, label => 'Total' }, 1862 total_mem_alloc => { hdr => 'Memory', num => 1, label => 'Total memory allocated' }, 1863 truncates => { hdr => 'Trunc', num => 0, label => 'Whether the deadlock is truncating InnoDB status' }, 1864 txn_doesnt_see_ge => { hdr => "Txn Won't See", num => 0, label => 'Where txn read view is limited' }, 1865 txn_id => { hdr => 'ID', num => 0, label => 'Transaction ID' }, 1866 txn_sees_lt => { hdr => 'Txn Sees', num => 1, label => 'Where txn read view is limited' }, 1867 txn_status => { hdr => 'Txn Status', num => 0, label => 'Transaction status' }, 1868 txn_time_remain => { hdr => 'Remaining', num => 1, label => 'Time until txn rollback/commit completes' }, 1869 undo_log_entries => { hdr => 'Undo', num => 1, label => 'Number of undo log entries' }, 1870 undo_for => { hdr => 'Undo', num => 0, label => 'Undo for' }, 1871 until_condition => { hdr => 'Until Condition', num => 0, label => 'Slave until condition' }, 1872 until_log_file => { hdr => 'Until Log File', num => 0, label => 'Slave until log file' }, 1873 uptime => { hdr => 'Uptime', num => 1, label => 'Uptime' }, 1874 until_log_pos => { hdr => 'Until Log Pos', num => 1, label => 'Slave until log position' }, 1875 used_cells => { hdr => 'Cells Used', num => 1, label => 'Number of cells used' }, 1876 used_bufs => { hdr => 'Used Bufs', num => 1, label => 'Number of buffer pool pages used' }, 1877 user => { hdr => 'User', num => 0, label => 'Database username', }, 1878 value => { hdr => 'Value', num => 1, label => 'Value' }, 1879 versions => { hdr => 'Versions', num => 1, label => 'Number of InnoDB MVCC versions unpurged' }, 1880 victim => { hdr => 'Victim', num => 0, label => 'Whether this txn was the deadlock victim' }, 1881 wait_array_size => { hdr => 'Wait Array Size', num => 1, label => 'Wait Array Size' }, 1882 wait_status => { hdr => 'Lock Status', num => 0, label => 'Status of txn locks' }, 1883 waited_at_filename => { hdr => 'File', num => 0, label => 'Filename at which thread waits' }, 1884 waited_at_line => { hdr => 'Line', num => 1, label => 'Line at which thread waits' }, 1885 waiters_flag => { hdr => 'Waiters', num => 1, label => 'Waiters Flag' }, 1886 waiting => { hdr => 'Waiting', num => 1, label => 'Whether lock is being waited for' }, 1887 waiting_thread => { hdr => 'WThread', num => 1, label => 'Waiting thread' }, 1888 waiting_query => { hdr => 'Waiting Query', num => 0, label => 'Waiting query' }, 1889 waiting_rows_modified => { hdr => 'WRowsMod', num => 1, label => 'Waiting number rows modified' }, 1890 waiting_age => { hdr => 'WAge', num => 1, label => 'Waiting age' }, 1891 waiting_wait_secs => { hdr => 'WWait', num => 1, label => 'Waiting wait time' }, 1892 waiting_user => { hdr => 'WUser', num => 0, label => 'Waiting user' }, 1893 waiting_host => { hdr => 'WHost', num => 0, label => 'Waiting host' }, 1894 waiting_db => { hdr => 'WDB', num => 0, label => 'Waiting database' }, 1895 when => { hdr => 'When', num => 0, label => 'Time scale' }, 1896 writer_lock_mode => { hdr => 'Wrtr Lck Mode', num => 0, label => 'Writer lock mode' }, 1897 writer_thread => { hdr => 'Wrtr Thread', num => 1, label => 'Writer thread ID' }, 1898 writes_pending => { hdr => 'Writes', num => 1, label => 'Number of writes pending' }, 1899 writes_pending_flush_list => { hdr => 'Flush List Writes', num => 1, label => 'Number of flush list writes pending' }, 1900 writes_pending_lru => { hdr => 'LRU Writes', num => 1, label => 'Number of LRU writes pending' }, 1901 writes_pending_single_page => { hdr => '1-Page Writes', num => 1, label => 'Number of 1-page writes pending' }, 1902); 1903 1904# Apply a default property or three. By default, columns are not width-constrained, 1905# aligned left, and sorted alphabetically, not numerically. 1906foreach my $col ( values %columns ) { 1907 map { $col->{$_} ||= 0 } qw(num minw maxw); 1908 $col->{just} = $col->{num} ? '' : '-'; 1909} 1910 1911# Filters {{{3 1912# This hash defines every filter that can be applied to a table. These 1913# become part of tbl_meta as well. Each filter is just an expression that 1914# returns true or false. 1915# Properties of each entry: 1916# * func: the subroutine 1917# * name: the name, repeated 1918# * user: whether it's a user-defined filter (saved in config) 1919# * text: text of the subroutine 1920# * note: explanation 1921my %filters = (); 1922 1923# These are pre-processed to live in %filters above, by compiling them. 1924my %builtin_filters = ( 1925 hide_self => { 1926 text => <<' END', 1927 return ( ($set->{info} || '') !~ m#/\*innotop\*/# ); 1928 END 1929 note => 'Removes the innotop processes from the list', 1930 tbls => [qw(innodb_transactions processlist)], 1931 }, 1932 hide_inactive => { 1933 text => <<' END', 1934 return ( !defined($set->{txn_status}) || $set->{txn_status} ne 'not started' ) 1935 && ( !defined($set->{cmd}) || $set->{cmd} !~ m/Sleep|Binlog Dump/ ) 1936 && ( !defined($set->{state}) || $set->{state} !~ m/^handlersocket/ ) 1937 && ( !defined($set->{info}) || $set->{info} =~ m/\S/ ); 1938 END 1939 note => 'Removes processes which are not doing anything', 1940 tbls => [qw(innodb_transactions processlist)], 1941 }, 1942 hide_connect => { 1943 text => <<' END', 1944 return ( !defined($set->{cmd}) || $set->{cmd} !~ m/Connect/ ); 1945 END 1946 note => 'Removes the slave processes from the list', 1947 tbls => [qw(processlist)], 1948 }, 1949 hide_slave_io => { 1950 text => <<' END', 1951 return !$set->{state} || $set->{state} !~ m/^(?:Waiting for master|read all relay)/; 1952 END 1953 note => 'Removes slave I/O threads from the list', 1954 tbls => [qw(processlist slave_io_status)], 1955 }, 1956 hide_event => { 1957 text => <<' END', 1958 return (($set->{state} || '') !~ m/^Daemon/) || (($set->{info} || '') !~ m/\S/); 1959 END 1960 note => 'Removes idle event threads from the list', 1961 tbls => [qw(processlist)], 1962 }, 1963 table_is_open => { 1964 text => <<' END', 1965 return $set->{num_times_open} + $set->{is_name_locked}; 1966 END 1967 note => 'Removes tables that are not in use or locked', 1968 tbls => [qw(open_tables)], 1969 }, 1970 cxn_is_master => { 1971 text => <<' END', 1972 return $set->{master_file} ? 1 : 0; 1973 END 1974 note => 'Removes servers that are not masters', 1975 tbls => [qw(master_status)], 1976 }, 1977 cxn_is_slave => { 1978 text => <<' END', 1979 return $set->{master_host} ? 1 : 0; 1980 END 1981 note => 'Removes servers that are not slaves', 1982 tbls => [qw(slave_io_status slave_sql_status)], 1983 }, 1984 thd_is_not_waiting => { 1985 text => <<' END', 1986 return $set->{thread_status} !~ m#waiting for i/o request#; 1987 END 1988 note => 'Removes idle I/O threads', 1989 tbls => [qw(io_threads)], 1990 }, 1991); 1992foreach my $key ( keys %builtin_filters ) { 1993 my ( $sub, $err ) = compile_filter($builtin_filters{$key}->{text}); 1994 $filters{$key} = { 1995 func => $sub, 1996 text => $builtin_filters{$key}->{text}, 1997 user => 0, 1998 name => $key, # useful for later 1999 note => $builtin_filters{$key}->{note}, 2000 tbls => $builtin_filters{$key}->{tbls}, 2001 } 2002} 2003 2004# Variable sets {{{3 2005# Sets (arrayrefs) of variables that are used in S mode. They are read/written to 2006# the config file. 2007my %var_sets = ( 2008 general => { 2009 text => join( 2010 ', ', 2011 'set_precision(Queries/Uptime_hires) as QPS', 2012 'set_precision(Com_commit/Uptime_hires) as Commit_PS', 2013 'set_precision((Com_rollback||0)/(Com_commit||1)) as Rollback_Commit', 2014 'set_precision((' 2015 . join('+', map { "($_||0)" } 2016 qw(Com_delete Com_delete_multi Com_insert Com_insert_select Com_replace 2017 Com_replace_select Com_select Com_update Com_update_multi)) 2018 . ')/(Com_commit||1)) as Write_Commit', 2019 'set_precision((Com_select+(Qcache_hits||0))/((' 2020 . join('+', map { "($_||0)" } 2021 qw(Com_delete Com_delete_multi Com_insert Com_insert_select Com_replace 2022 Com_replace_select Com_select Com_update Com_update_multi)) 2023 . ')||1)) as R_W_Ratio', 2024 'set_precision(Opened_tables/Uptime_hires) as Opens_PS', 2025 'percent($cur->{Open_tables}/($cur->{table_cache})) as Table_Cache_Used', 2026 'set_precision(Threads_created/Uptime_hires) as Threads_PS', 2027 'percent($cur->{Threads_cached}/($cur->{thread_cache_size}||1)) as Thread_Cache_Used', 2028 'percent($cur->{Max_used_connections}/($cur->{max_connections}||1)) as CXN_Used_Ever', 2029 'percent($cur->{Threads_connected}/($cur->{max_connections}||1)) as CXN_Used_Now', 2030 ), 2031 }, 2032 commands => { 2033 text => join( 2034 ', ', 2035 qw(Uptime Queries Com_delete Com_delete_multi Com_insert 2036 Com_insert_select Com_replace Com_replace_select Com_select Com_update 2037 Com_update_multi) 2038 ), 2039 }, 2040 query_status => { 2041 text => join( 2042 ',', 2043 qw( Uptime Select_full_join Select_full_range_join Select_range 2044 Select_range_check Select_scan Slow_queries Sort_merge_passes 2045 Sort_range Sort_rows Sort_scan) 2046 ), 2047 }, 2048 innodb => { 2049 text => join( 2050 ',', 2051 qw( Uptime Innodb_row_lock_current_waits Innodb_row_lock_time 2052 Innodb_row_lock_time_avg Innodb_row_lock_time_max Innodb_row_lock_waits 2053 Innodb_rows_deleted Innodb_rows_inserted Innodb_rows_read 2054 Innodb_rows_updated) 2055 ), 2056 }, 2057 txn => { 2058 text => join( 2059 ',', 2060 qw( Uptime Com_begin Com_commit Com_rollback Com_savepoint 2061 Com_xa_commit Com_xa_end Com_xa_prepare Com_xa_recover Com_xa_rollback 2062 Com_xa_start) 2063 ), 2064 }, 2065 key_cache => { 2066 text => join( 2067 ',', 2068 qw( Uptime Key_blocks_not_flushed Key_blocks_unused Key_blocks_used 2069 Key_read_requests Key_reads Key_write_requests Key_writes ) 2070 ), 2071 }, 2072 query_cache => { 2073 text => join( 2074 ',', 2075 "percent($exprs{QcacheHitRatio}) as Hit_Pct", 2076 'set_precision((Qcache_hits||0)/(Qcache_inserts||1)) as Hit_Ins', 2077 'set_precision((Qcache_lowmem_prunes||0)/Uptime_hires) as Lowmem_Prunes_sec', 2078 'percent(1-((Qcache_free_blocks||0)/(Qcache_total_blocks||1))) as Blocks_used', 2079 qw( Qcache_free_blocks Qcache_free_memory Qcache_not_cached Qcache_queries_in_cache) 2080 ), 2081 }, 2082 handler => { 2083 text => join( 2084 ',', 2085 qw( Uptime Handler_read_key Handler_read_first Handler_read_next 2086 Handler_read_prev Handler_read_rnd Handler_read_rnd_next Handler_delete 2087 Handler_update Handler_write) 2088 ), 2089 }, 2090 cxns_files_threads => { 2091 text => join( 2092 ',', 2093 qw( Uptime Aborted_clients Aborted_connects Bytes_received Bytes_sent 2094 Compression Connections Created_tmp_disk_tables Created_tmp_files 2095 Created_tmp_tables Max_used_connections Open_files Open_streams 2096 Open_tables Opened_tables Table_locks_immediate Table_locks_waited 2097 Threads_cached Threads_connected Threads_created Threads_running) 2098 ), 2099 }, 2100 prep_stmt => { 2101 text => join( 2102 ',', 2103 qw( Uptime Com_dealloc_sql Com_execute_sql Com_prepare_sql Com_reset 2104 Com_stmt_close Com_stmt_execute Com_stmt_fetch Com_stmt_prepare 2105 Com_stmt_reset Com_stmt_send_long_data ) 2106 ), 2107 }, 2108 innodb_health => { 2109 text => join( 2110 ',', 2111 "$exprs{OldVersions} as OldVersions", 2112 qw(IB_sm_mutex_spin_waits IB_sm_mutex_spin_rounds IB_sm_mutex_os_waits), 2113 "$exprs{NumTxns} as NumTxns", 2114 "$exprs{MaxTxnTime} as MaxTxnTime", 2115 qw(IB_ro_queries_inside IB_ro_queries_in_queue), 2116 "set_precision($exprs{DirtyBufs} * 100) as dirty_bufs", 2117 "set_precision($exprs{BufPoolFill} * 100) as buf_fill", 2118 qw(IB_bp_pages_total IB_bp_pages_read IB_bp_pages_written IB_bp_pages_created) 2119 ), 2120 }, 2121 innodb_health2 => { 2122 text => join( 2123 ', ', 2124 'percent(1-((Innodb_buffer_pool_pages_free||0)/($cur->{Innodb_buffer_pool_pages_total}||1))) as BP_page_cache_usage', 2125 'percent(1-((Innodb_buffer_pool_reads||0)/(Innodb_buffer_pool_read_requests||1))) as BP_cache_hit_ratio', 2126 'Innodb_buffer_pool_wait_free', 2127 'Innodb_log_waits', 2128 ), 2129 }, 2130 slow_queries => { 2131 text => join( 2132 ', ', 2133 'set_precision(Slow_queries/Uptime_hires) as Slow_PS', 2134 'set_precision(Select_full_join/Uptime_hires) as Full_Join_PS', 2135 'percent(Select_full_join/(Com_select||1)) as Full_Join_Ratio', 2136 ), 2137 }, 2138); 2139 2140# Server sets {{{3 2141# Defines sets of servers between which the user can quickly switch. 2142my %server_groups; 2143 2144# Connections {{{3 2145# This hash defines server connections. Each connection is a string that can be passed to 2146# the DBI connection. These are saved in the connections section in the config file. 2147my %connections; 2148# Defines the parts of connections. 2149my @conn_parts = qw(user have_user pass have_pass dsn savepass dl_table); 2150 2151# Graph widths {{{3 2152# This hash defines the max values seen for various status/variable values, for graphing. 2153# These are stored in their own section in the config file. These are just initial values: 2154my %mvs = ( 2155 Com_select => 50, 2156 Com_insert => 50, 2157 Com_update => 50, 2158 Com_delete => 50, 2159 Queries => 100, 2160); 2161 2162# ########################################################################### 2163# Valid Term::ANSIColor color strings. 2164# ########################################################################### 2165my %ansicolors = map { $_ => 1 } 2166 qw( black blink blue bold clear concealed cyan dark green magenta on_black 2167 on_blue on_cyan on_green on_magenta on_red on_white on_yellow red reset 2168 reverse underline underscore white yellow); 2169 2170# ########################################################################### 2171# Valid comparison operators for color rules 2172# ########################################################################### 2173my %comp_ops = ( 2174 '==' => 'Numeric equality', 2175 '>' => 'Numeric greater-than', 2176 '<' => 'Numeric less-than', 2177 '>=' => 'Numeric greater-than/equal', 2178 '<=' => 'Numeric less-than/equal', 2179 '!=' => 'Numeric not-equal', 2180 'eq' => 'String equality', 2181 'gt' => 'String greater-than', 2182 'lt' => 'String less-than', 2183 'ge' => 'String greater-than/equal', 2184 'le' => 'String less-than/equal', 2185 'ne' => 'String not-equal', 2186 '=~' => 'Pattern match', 2187 '!~' => 'Negated pattern match', 2188); 2189 2190# ########################################################################### 2191# Valid aggregate functions. 2192# ########################################################################### 2193my %agg_funcs = ( 2194 first => sub { 2195 return $_[0] 2196 }, 2197 count => sub { 2198 return 0 + @_; 2199 }, 2200 avg => sub { 2201 my @args = grep { defined $_ } @_; 2202 return (sum(map { m/([\d\.-]+)/g } @args) || 0) / (scalar(@args) || 1); 2203 }, 2204 sum => sub { 2205 my @args = grep { defined $_ } @_; 2206 return sum(@args); 2207 } 2208); 2209 2210# ########################################################################### 2211# Valid functions for transformations. 2212# ########################################################################### 2213my %trans_funcs = ( 2214 shorten => \&shorten, 2215 secs_to_time => \&secs_to_time, 2216 distill => \&distill, 2217 no_ctrl_char => \&no_ctrl_char, 2218 percent => \&percent, 2219 commify => \&commify, 2220 dulint_to_int => \&dulint_to_int, 2221 set_precision => \&set_precision, 2222 fuzzy_time => \&fuzzy_time, 2223); 2224 2225# Table definitions {{{3 2226# This hash defines every table that can get displayed in every mode. Each 2227# table specifies columns and column data sources. The column is 2228# defined by the %columns hash. 2229# 2230# Example: foo => { src => 'bar' } means the foo column (look at 2231# $columns{foo} for its definition) gets its data from the 'bar' element of 2232# the current data set, whatever that is. 2233# 2234# These columns are post-processed after being defined, because they get stuff 2235# from %columns. After all the config is loaded for columns, there's more 2236# post-processing too; the subroutines compiled from src get added to 2237# the hash elements for extract_values to use. 2238# ########################################################################### 2239 2240my %tbl_meta = ( 2241 adaptive_hash_index => { 2242 capt => 'Adaptive Hash Index', 2243 cust => {}, 2244 cols => { 2245 cxn => { src => 'cxn' }, 2246 hash_table_size => { src => 'IB_ib_hash_table_size', trans => [qw(shorten)], }, 2247 used_cells => { src => 'IB_ib_used_cells' }, 2248 bufs_in_node_heap => { src => 'IB_ib_bufs_in_node_heap' }, 2249 hash_searches_s => { src => 'IB_ib_hash_searches_s' }, 2250 non_hash_searches_s => { src => 'IB_ib_non_hash_searches_s' }, 2251 }, 2252 visible => [ qw(cxn hash_table_size used_cells bufs_in_node_heap hash_searches_s non_hash_searches_s) ], 2253 filters => [], 2254 sort_cols => 'cxn', 2255 sort_dir => '1', 2256 innodb => 'ib', 2257 group_by => [], 2258 aggregate => 0, 2259 }, 2260 buffer_pool => { 2261 capt => 'Buffer Pool', 2262 cust => {}, 2263 cols => { 2264 cxn => { src => 'cxn' }, 2265 total_mem_alloc => { src => 'IB_bp_total_mem_alloc', trans => [qw(shorten)], }, 2266 awe_mem_alloc => { src => 'IB_bp_awe_mem_alloc', trans => [qw(shorten)], }, 2267 add_pool_alloc => { src => 'IB_bp_add_pool_alloc', trans => [qw(shorten)], }, 2268 buf_pool_size => { src => 'IB_bp_buf_pool_size', trans => [qw(shorten)], }, 2269 buf_free => { src => 'IB_bp_buf_free' }, 2270 buf_pool_hit_rate => { src => 'IB_bp_buf_pool_hit_rate' }, 2271 buf_pool_reads => { src => 'IB_bp_buf_pool_reads' }, 2272 buf_pool_hits => { src => 'IB_bp_buf_pool_hits' }, 2273 dict_mem_alloc => { src => 'IB_bp_dict_mem_alloc' }, 2274 pages_total => { src => 'IB_bp_pages_total' }, 2275 pages_modified => { src => 'IB_bp_pages_modified' }, 2276 reads_pending => { src => 'IB_bp_reads_pending' }, 2277 writes_pending => { src => 'IB_bp_writes_pending' }, 2278 writes_pending_lru => { src => 'IB_bp_writes_pending_lru' }, 2279 writes_pending_flush_list => { src => 'IB_bp_writes_pending_flush_list' }, 2280 writes_pending_single_page => { src => 'IB_bp_writes_pending_single_page' }, 2281 page_creates_sec => { src => 'IB_bp_page_creates_sec' }, 2282 page_reads_sec => { src => 'IB_bp_page_reads_sec' }, 2283 page_writes_sec => { src => 'IB_bp_page_writes_sec' }, 2284 pages_created => { src => 'IB_bp_pages_created' }, 2285 pages_read => { src => 'IB_bp_pages_read' }, 2286 pages_written => { src => 'IB_bp_pages_written' }, 2287 }, 2288 visible => [ qw(cxn buf_pool_size buf_free pages_total pages_modified buf_pool_hit_rate total_mem_alloc add_pool_alloc)], 2289 filters => [], 2290 sort_cols => 'cxn', 2291 sort_dir => '1', 2292 innodb => 'bp', 2293 group_by => [], 2294 aggregate => 0, 2295 }, 2296 # TODO: a new step in set_to_tbl: join result to itself, grouped? 2297 # TODO: this would also enable pulling Q and T data together. 2298 # TODO: using a SQL-ish language would also allow pivots to be easier -- treat the pivoted data as a view and SELECT from it. 2299 cmd_summary => { 2300 capt => 'Command Summary', 2301 cust => {}, 2302 cols => { 2303 cxn => { src => 'cxn' }, 2304 name => { src => 'name' }, 2305 total => { src => 'total' }, 2306 value => { src => 'value', agg => 'sum'}, 2307 pct => { src => 'value/total', trans => [qw(percent)] }, 2308 last_total => { src => 'last_total' }, 2309 last_value => { src => 'last_value', agg => 'sum'}, 2310 last_pct => { src => 'last_value/last_total', trans => [qw(percent)] }, 2311 }, 2312 visible => [qw(cxn name value pct last_value last_pct)], 2313 filters => [qw()], 2314 sort_cols => '-value', 2315 sort_dir => '1', 2316 innodb => '', 2317 group_by => [qw(name)], 2318 aggregate => 1, 2319 }, 2320 deadlock_locks => { 2321 capt => 'Deadlock Locks', 2322 cust => {}, 2323 cols => { 2324 cxn => { src => 'cxn' }, 2325 mysql_thread_id => { src => 'mysql_thread_id' }, 2326 dl_txn_num => { src => 'dl_txn_num' }, 2327 lock_type => { src => 'lock_type' }, 2328 space_id => { src => 'space_id' }, 2329 page_no => { src => 'page_no' }, 2330 heap_no => { src => 'heap_no' }, 2331 n_bits => { src => 'n_bits' }, 2332 index => { src => 'index' }, 2333 db => { src => 'db' }, 2334 tbl => { src => 'table' }, 2335 lock_mode => { src => 'lock_mode' }, 2336 special => { src => 'special' }, 2337 insert_intention => { src => 'insert_intention' }, 2338 waiting => { src => 'waiting' }, 2339 }, 2340 visible => [ qw(cxn mysql_thread_id waiting lock_mode db tbl index special insert_intention)], 2341 filters => [], 2342 sort_cols => 'cxn mysql_thread_id', 2343 sort_dir => '1', 2344 innodb => 'dl', 2345 group_by => [], 2346 aggregate => 0, 2347 }, 2348 deadlock_transactions => { 2349 capt => 'Deadlock Transactions', 2350 cust => {}, 2351 cols => { 2352 cxn => { src => 'cxn' }, 2353 active_secs => { src => 'active_secs' }, 2354 dl_txn_num => { src => 'dl_txn_num' }, 2355 has_read_view => { src => 'has_read_view' }, 2356 heap_size => { src => 'heap_size' }, 2357 host_and_domain => { src => 'hostname' }, 2358 hostname => { src => $exprs{Host} }, 2359 ip => { src => 'ip' }, 2360 lock_structs => { src => 'lock_structs' }, 2361 lock_wait_time => { src => 'lock_wait_time', trans => [ qw(secs_to_time) ] }, 2362 mysql_thread_id => { src => 'mysql_thread_id' }, 2363 os_thread_id => { src => 'os_thread_id' }, 2364 proc_no => { src => 'proc_no' }, 2365 query_id => { src => 'query_id' }, 2366 query_status => { src => 'query_status' }, 2367 query_text => { src => 'query_text', trans => [ qw(no_ctrl_char) ] }, 2368 row_locks => { src => 'row_locks' }, 2369 tables_in_use => { src => 'tables_in_use' }, 2370 tables_locked => { src => 'tables_locked' }, 2371 thread_decl_inside => { src => 'thread_decl_inside' }, 2372 thread_status => { src => 'thread_status' }, 2373 'time' => { src => 'active_secs', trans => [ qw(secs_to_time) ] }, 2374 timestring => { src => 'timestring' }, 2375 txn_doesnt_see_ge => { src => 'txn_doesnt_see_ge' }, 2376 txn_id => { src => 'txn_id' }, 2377 txn_sees_lt => { src => 'txn_sees_lt' }, 2378 txn_status => { src => 'txn_status' }, 2379 truncates => { src => 'truncates' }, 2380 undo_log_entries => { src => 'undo_log_entries' }, 2381 user => { src => 'user' }, 2382 victim => { src => 'victim' }, 2383 wait_status => { src => 'lock_wait_status' }, 2384 }, 2385 visible => [ qw(cxn mysql_thread_id timestring user hostname victim time undo_log_entries lock_structs query_text)], 2386 filters => [], 2387 sort_cols => 'cxn mysql_thread_id', 2388 sort_dir => '1', 2389 innodb => 'dl', 2390 group_by => [], 2391 aggregate => 0, 2392 }, 2393 explain => { 2394 capt => 'EXPLAIN Results', 2395 cust => {}, 2396 cols => { 2397 part_id => { src => 'id' }, 2398 select_type => { src => 'select_type' }, 2399 tbl => { src => 'table' }, 2400 partitions => { src => 'partitions' }, 2401 scan_type => { src => 'type' }, 2402 possible_keys => { src => 'possible_keys' }, 2403 index => { src => 'key' }, 2404 key_len => { src => 'key_len' }, 2405 index_ref => { src => 'ref' }, 2406 num_rows => { src => 'rows' }, 2407 special => { src => 'extra' }, 2408 }, 2409 visible => [ qw(select_type tbl partitions scan_type possible_keys index key_len index_ref num_rows special)], 2410 filters => [], 2411 sort_cols => '', 2412 sort_dir => '1', 2413 innodb => '', 2414 group_by => [], 2415 aggregate => 0, 2416 }, 2417 file_io_misc => { 2418 capt => 'File I/O Misc', 2419 cust => {}, 2420 cols => { 2421 cxn => { src => 'cxn' }, 2422 io_bytes_s => { src => 'IB_io_avg_bytes_s' }, 2423 io_flush_type => { src => 'IB_io_flush_type' }, 2424 io_fsyncs_s => { src => 'IB_io_fsyncs_s' }, 2425 io_reads_s => { src => 'IB_io_reads_s' }, 2426 io_writes_s => { src => 'IB_io_writes_s' }, 2427 os_file_reads => { src => 'IB_io_os_file_reads' }, 2428 os_file_writes => { src => 'IB_io_os_file_writes' }, 2429 os_fsyncs => { src => 'IB_io_os_fsyncs' }, 2430 }, 2431 visible => [ qw(cxn os_file_reads os_file_writes os_fsyncs io_reads_s io_writes_s io_bytes_s)], 2432 filters => [], 2433 sort_cols => 'cxn', 2434 sort_dir => '1', 2435 innodb => 'io', 2436 group_by => [], 2437 aggregate => 0, 2438 }, 2439 fk_error => { 2440 capt => 'Foreign Key Error Info', 2441 cust => {}, 2442 cols => { 2443 timestring => { src => 'IB_fk_timestring' }, 2444 child_db => { src => 'IB_fk_child_db' }, 2445 child_table => { src => 'IB_fk_child_table' }, 2446 child_index => { src => 'IB_fk_child_index' }, 2447 fk_name => { src => 'IB_fk_fk_name' }, 2448 parent_db => { src => 'IB_fk_parent_db' }, 2449 parent_table => { src => 'IB_fk_parent_table' }, 2450 parent_col => { src => 'IB_fk_parent_col' }, 2451 parent_index => { src => 'IB_fk_parent_index' }, 2452 attempted_op => { src => 'IB_fk_attempted_op' }, 2453 }, 2454 visible => [ qw(timestring child_db child_table child_index parent_db parent_table parent_col parent_index fk_name attempted_op)], 2455 filters => [], 2456 sort_cols => '', 2457 sort_dir => '1', 2458 innodb => 'fk', 2459 group_by => [], 2460 aggregate => 0, 2461 }, 2462 index_statistics => { 2463 capt => 'Data from INDEX_STATISTICS', 2464 cust => {}, 2465 cols => { 2466 cxn => { src => 'cxn', minw => 6, maxw => 10 }, 2467 db => { src => 'table_schema' }, 2468 tbl => { src => 'table_name' }, 2469 index => { src => 'index_name' }, 2470 rows_read => { src => 'rows_read', agg => 'sum' }, 2471 }, 2472 visible => [ qw(cxn db tbl index rows_read) ], 2473 filters => [ ], 2474 sort_cols => 'rows_read', 2475 sort_dir => '-1', 2476 innodb => '', 2477 colors => [], 2478 hide_caption => 1, 2479 group_by => [qw(cxn db tbl)], 2480 aggregate => 0, 2481 }, 2482 index_table_statistics => { 2483 capt => 'Data from {TABLE,INDEX}_STATISTICS', 2484 cust => {}, 2485 cols => { 2486 cxn => { src => 'cxn', minw => 6, maxw => 10 }, 2487 db => { src => 'table_schema' }, 2488 tbl => { src => 'table_name' }, 2489 index => { src => 'index_name' }, 2490 rows_read => { src => 'rows_read' }, 2491 rows_read_from_indexes => { src => 'rows_read_from_indexes' }, 2492 rows_changed => { src => 'rows_changed' }, 2493 rows_changed_x_indexes => { src => 'rows_changed_x_indexes' }, 2494 }, 2495 visible => [ qw(cxn db tbl rows_read rows_read_from_indexes rows_changed rows_changed_x_indexes) ], 2496 filters => [ ], 2497 sort_cols => 'rows_read', 2498 sort_dir => '-1', 2499 innodb => '', 2500 colors => [], 2501 hide_caption => 1, 2502 group_by => [qw(cxn db tbl)], 2503 aggregate => 0, 2504 }, 2505 insert_buffers => { 2506 capt => 'Insert Buffers', 2507 cust => {}, 2508 cols => { 2509 cxn => { src => 'cxn' }, 2510 inserts => { src => 'IB_ib_inserts' }, 2511 merged_recs => { src => 'IB_ib_merged_recs' }, 2512 merges => { src => 'IB_ib_merges' }, 2513 size => { src => 'IB_ib_size' }, 2514 free_list_len => { src => 'IB_ib_free_list_len' }, 2515 seg_size => { src => 'IB_ib_seg_size' }, 2516 }, 2517 visible => [ qw(cxn inserts merged_recs merges size free_list_len seg_size)], 2518 filters => [], 2519 sort_cols => 'cxn', 2520 sort_dir => '1', 2521 innodb => 'ib', 2522 group_by => [], 2523 aggregate => 0, 2524 }, 2525 innodb_blocked_blocker => { 2526 capt => 'InnoDB Blocked/Blocking', 2527 cust => {}, 2528 cols => { 2529 cxn => { src => 'cxn' }, 2530 waiting_thread => { src => 'waiting_thread' }, 2531 waiting_query => { src => 'waiting_query', trans => [qw(distill)] }, 2532 waiting_rows_modified => { src => 'waiting_rows_modified' }, 2533 waiting_age => { src => 'waiting_age', trans => [qw(fuzzy_time)] }, 2534 waiting_wait_secs => { src => 'waiting_wait_secs', trans => [qw(fuzzy_time)] }, 2535 waiting_user => { src => 'waiting_user' }, 2536 waiting_host => { src => 'waiting_host' }, 2537 waiting_db => { src => 'waiting_db' }, 2538 blocking_thread => { src => 'blocking_thread' }, 2539 blocking_query => { src => 'blocking_query', trans => [qw(distill)] }, 2540 blocking_rows_modified => { src => 'blocking_rows_modified' }, 2541 blocking_age => { src => 'blocking_age', trans => [qw(fuzzy_time)] }, 2542 blocking_wait_secs => { src => 'blocking_wait_secs', trans => [qw(fuzzy_time)] }, 2543 blocking_user => { src => 'blocking_user' }, 2544 blocking_host => { src => 'blocking_host' }, 2545 blocking_db => { src => 'blocking_db' }, 2546 blocking_status => { src => 'blocking_status' }, 2547 lock_info => { src => 'lock_info' }, 2548 }, 2549 visible => [ qw(cxn waiting_thread waiting_query waiting_wait_secs 2550 blocking_thread blocking_rows_modified blocking_age blocking_wait_secs 2551 blocking_status blocking_query)], 2552 filters => [], 2553 sort_cols => 'cxn -waiting_wait_secs', 2554 sort_dir => '1', 2555 innodb => 'tx', 2556 colors => [ 2557 ], 2558 group_by => [], 2559 aggregate => 0, 2560 hide_caption => 1, 2561 }, 2562 innodb_locks => { 2563 capt => 'InnoDB Locks', 2564 cust => {}, 2565 cols => { 2566 cxn => { src => 'cxn' }, 2567 db => { src => 'db' }, 2568 index => { src => 'index' }, 2569 insert_intention => { src => 'insert_intention' }, 2570 lock_mode => { src => 'lock_mode' }, 2571 lock_type => { src => 'lock_type' }, 2572 lock_wait_time => { src => 'lock_wait_time', trans => [ qw(secs_to_time) ] }, 2573 mysql_thread_id => { src => 'mysql_thread_id' }, 2574 n_bits => { src => 'n_bits' }, 2575 page_no => { src => 'page_no' }, 2576 space_id => { src => 'space_id' }, 2577 special => { src => 'special' }, 2578 tbl => { src => 'table' }, 2579 'time' => { src => 'active_secs', hdr => 'Active', trans => [ qw(secs_to_time) ] }, 2580 txn_id => { src => 'txn_id' }, 2581 waiting => { src => 'waiting' }, 2582 }, 2583 visible => [ qw(cxn mysql_thread_id lock_type waiting lock_wait_time time lock_mode db tbl index insert_intention special)], 2584 filters => [], 2585 sort_cols => 'cxn -lock_wait_time', 2586 sort_dir => '1', 2587 innodb => 'tx', 2588 colors => [ 2589 { col => 'lock_wait_time', op => '>', arg => 60, color => 'red' }, 2590 { col => 'lock_wait_time', op => '>', arg => 30, color => 'yellow' }, 2591 { col => 'lock_wait_time', op => '>', arg => 10, color => 'green' }, 2592 ], 2593 group_by => [], 2594 aggregate => 0, 2595 }, 2596 innodb_transactions => { 2597 capt => 'InnoDB Transactions', 2598 cust => {}, 2599 cols => { 2600 cxn => { src => 'cxn' }, 2601 active_secs => { src => 'active_secs' }, 2602 has_read_view => { src => 'has_read_view' }, 2603 heap_size => { src => 'heap_size' }, 2604 hostname => { src => $exprs{Host} }, 2605 ip => { src => 'ip' }, 2606 wait_status => { src => 'lock_wait_status' }, 2607 lock_wait_time => { src => 'lock_wait_time', trans => [ qw(secs_to_time) ] }, 2608 lock_structs => { src => 'lock_structs' }, 2609 mysql_thread_id => { src => 'mysql_thread_id' }, 2610 os_thread_id => { src => 'os_thread_id' }, 2611 proc_no => { src => 'proc_no' }, 2612 query_id => { src => 'query_id' }, 2613 query_status => { src => 'query_status' }, 2614 query_text => { src => 'query_text', trans => [ qw(no_ctrl_char) ] }, 2615 txn_time_remain => { src => $exprs{TxnTimeRemain}, trans => [ qw(secs_to_time) ] }, 2616 row_locks => { src => 'row_locks' }, 2617 tables_in_use => { src => 'tables_in_use' }, 2618 tables_locked => { src => 'tables_locked' }, 2619 thread_decl_inside => { src => 'thread_decl_inside' }, 2620 thread_status => { src => 'thread_status' }, 2621 'time' => { src => 'active_secs', trans => [ qw(secs_to_time) ], agg => 'sum' }, 2622 txn_doesnt_see_ge => { src => 'txn_doesnt_see_ge' }, 2623 txn_id => { src => 'txn_id' }, 2624 txn_sees_lt => { src => 'txn_sees_lt' }, 2625 txn_status => { src => 'txn_status', minw => 10, maxw => 10 }, 2626 undo_log_entries => { src => 'undo_log_entries' }, 2627 user => { src => 'user', maxw => 10 }, 2628 cnt => { src => 'mysql_thread_id', minw => 0 }, 2629 }, 2630 visible => [ qw(cxn cnt mysql_thread_id user hostname txn_status time undo_log_entries query_text)], 2631 filters => [ qw( hide_self hide_inactive ) ], 2632 sort_cols => '-active_secs txn_status cxn mysql_thread_id', 2633 sort_dir => '1', 2634 innodb => 'tx', 2635 hide_caption => 1, 2636 colors => [ 2637 { col => 'wait_status', op => 'eq', arg => 'LOCK WAIT', color => 'black on_red' }, 2638 { col => 'time', op => '>', arg => 600, color => 'red' }, 2639 { col => 'time', op => '>', arg => 300, color => 'yellow' }, 2640 { col => 'time', op => '>', arg => 60, color => 'green' }, 2641 { col => 'time', op => '>', arg => 30, color => 'cyan' }, 2642 { col => 'txn_status', op => 'eq', arg => 'not started', color => 'white' }, 2643 ], 2644 group_by => [ qw(cxn txn_status) ], 2645 aggregate => 0, 2646 }, 2647 io_threads => { 2648 capt => 'I/O Threads', 2649 cust => {}, 2650 cols => { 2651 cxn => { src => 'cxn' }, 2652 thread => { src => 'thread' }, 2653 thread_purpose => { src => 'purpose' }, 2654 event_set => { src => 'event_set' }, 2655 thread_status => { src => 'state' }, 2656 }, 2657 visible => [ qw(cxn thread thread_purpose thread_status)], 2658 filters => [ qw() ], 2659 sort_cols => 'cxn thread', 2660 sort_dir => '1', 2661 innodb => 'io', 2662 group_by => [], 2663 aggregate => 0, 2664 }, 2665 log_statistics => { 2666 capt => 'Log Statistics', 2667 cust => {}, 2668 cols => { 2669 cxn => { src => 'cxn' }, 2670 last_chkp => { src => 'IB_lg_last_chkp' }, 2671 log_flushed_to => { src => 'IB_lg_log_flushed_to' }, 2672 log_ios_done => { src => 'IB_lg_log_ios_done' }, 2673 log_ios_s => { src => 'IB_lg_log_ios_s' }, 2674 log_seq_no => { src => 'IB_lg_log_seq_no' }, 2675 pending_chkp_writes => { src => 'IB_lg_pending_chkp_writes' }, 2676 pending_log_writes => { src => 'IB_lg_pending_log_writes' }, 2677 }, 2678 visible => [ qw(cxn log_seq_no log_flushed_to last_chkp log_ios_done log_ios_s)], 2679 filters => [], 2680 sort_cols => 'cxn', 2681 sort_dir => '1', 2682 innodb => 'lg', 2683 group_by => [], 2684 aggregate => 0, 2685 }, 2686 master_status => { 2687 capt => 'Master Status', 2688 cust => {}, 2689 cols => { 2690 cxn => { src => $exprs{chcxn_2_cxn}, hdr => 'CXN' }, 2691 binlog_do_db => { src => 'binlog_do_db' }, 2692 binlog_ignore_db => { src => 'binlog_ignore_db' }, 2693 master_file => { src => 'file' }, 2694 master_pos => { src => 'position' }, 2695 binlog_cache_overflow => { src => '(Binlog_cache_disk_use||0)/(Binlog_cache_use||1)', trans => [ qw(percent) ] }, 2696 executed_gtid_set => { src => '(executed_gtid_set||"N/A")' }, 2697 server_uuid => { src => '(server_uuid||"N/A")' }, 2698 channel_name => { src => $exprs{chcxn_2_ch}, hdr => 'Channel'}, 2699 }, 2700 visible => [ qw(cxn channel_name master_file master_pos binlog_cache_overflow executed_gtid_set server_uuid)], 2701 filters => [ qw(cxn_is_master) ], 2702 sort_cols => 'cxn', 2703 sort_dir => '1', 2704 innodb => '', 2705 group_by => [qw(channel_name)], 2706 aggregate => 0, 2707 }, 2708 pending_io => { 2709 capt => 'Pending I/O', 2710 cust => {}, 2711 cols => { 2712 cxn => { src => 'cxn' }, 2713 p_normal_aio_reads => { src => 'IB_io_pending_normal_aio_reads' }, 2714 p_aio_writes => { src => 'IB_io_pending_aio_writes' }, 2715 p_ibuf_aio_reads => { src => 'IB_io_pending_ibuf_aio_reads' }, 2716 p_sync_ios => { src => 'IB_io_pending_sync_ios' }, 2717 p_buf_pool_flushes => { src => 'IB_io_pending_buffer_pool_flushes' }, 2718 p_log_flushes => { src => 'IB_io_pending_log_flushes' }, 2719 p_log_ios => { src => 'IB_io_pending_log_ios' }, 2720 p_preads => { src => 'IB_io_pending_preads' }, 2721 p_pwrites => { src => 'IB_io_pending_pwrites' }, 2722 }, 2723 visible => [ qw(cxn p_normal_aio_reads p_aio_writes p_ibuf_aio_reads p_sync_ios p_log_flushes p_log_ios)], 2724 filters => [], 2725 sort_cols => 'cxn', 2726 sort_dir => '1', 2727 innodb => 'io', 2728 group_by => [], 2729 aggregate => 0, 2730 }, 2731 open_tables => { 2732 capt => 'Open Tables', 2733 cust => {}, 2734 cols => { 2735 cxn => { src => 'cxn' }, 2736 db => { src => 'database' }, 2737 tbl => { src => 'table' }, 2738 num_times_open => { src => 'in_use' }, 2739 is_name_locked => { src => 'name_locked' }, 2740 }, 2741 visible => [ qw(cxn db tbl num_times_open is_name_locked)], 2742 filters => [ qw(table_is_open) ], 2743 sort_cols => '-num_times_open cxn db tbl', 2744 sort_dir => '1', 2745 innodb => '', 2746 group_by => [], 2747 aggregate => 0, 2748 }, 2749 page_statistics => { 2750 capt => 'Page Statistics', 2751 cust => {}, 2752 cols => { 2753 cxn => { src => 'cxn' }, 2754 pages_read => { src => 'IB_bp_pages_read' }, 2755 pages_written => { src => 'IB_bp_pages_written' }, 2756 pages_created => { src => 'IB_bp_pages_created' }, 2757 page_reads_sec => { src => 'IB_bp_page_reads_sec' }, 2758 page_writes_sec => { src => 'IB_bp_page_writes_sec' }, 2759 page_creates_sec => { src => 'IB_bp_page_creates_sec' }, 2760 }, 2761 visible => [ qw(cxn pages_read pages_written pages_created page_reads_sec page_writes_sec page_creates_sec)], 2762 filters => [], 2763 sort_cols => 'cxn', 2764 sort_dir => '1', 2765 innodb => 'bp', 2766 group_by => [], 2767 aggregate => 0, 2768 }, 2769 processlist => { 2770 capt => 'MySQL Process List', 2771 cust => {}, 2772 cols => { 2773 cxn => { src => 'cxn', minw => 6, maxw => 10 }, 2774 mysql_thread_id => { src => 'id', minw => 6, maxw => 0 }, 2775 user => { src => 'user', minw => 5, maxw => 8 }, 2776 hostname => { src => $exprs{Host}, minw => 7, maxw => 15, }, 2777 port => { src => $exprs{Port}, minw => 0, maxw => 0, }, 2778 host_and_port => { src => 'host', minw => 0, maxw => 0 }, 2779 db => { src => 'db', minw => 6, maxw => 12 }, 2780 cmd => { src => 'command', minw => 5, maxw => 0 }, 2781 time => { src => 'time', minw => 5, maxw => 0, trans => [ qw(secs_to_time) ], agg => 'sum' }, 2782 state => { src => 'state', minw => 0, maxw => 0 }, 2783 info => { src => 'info', minw => 0, maxw => 0, trans => [ qw(no_ctrl_char) ] }, 2784 cnt => { src => 'id', minw => 0, maxw => 0 }, 2785 }, 2786 visible => [ qw(cxn cmd cnt mysql_thread_id state user hostname db time info)], 2787 filters => [ qw(hide_self hide_inactive hide_slave_io hide_event hide_connect) ], 2788 sort_cols => '-time cxn hostname mysql_thread_id', 2789 sort_dir => '1', 2790 innodb => '', 2791 hide_caption => 1, 2792 colors => [ 2793 { col => 'state', op => 'eq', arg => 'Locked', color => 'black on_red' }, 2794 { col => 'cmd', op => 'eq', arg => 'Sleep', color => 'white' }, 2795 { col => 'user', op => 'eq', arg => 'system user', color => 'white' }, 2796 { col => 'cmd', op => 'eq', arg => 'Connect', color => 'white' }, 2797 { col => 'cmd', op => 'eq', arg => 'Binlog Dump', color => 'white' }, 2798 { col => 'time', op => '>', arg => 600, color => 'red' }, 2799 { col => 'time', op => '>', arg => 120, color => 'yellow' }, 2800 { col => 'time', op => '>', arg => 60, color => 'green' }, 2801 { col => 'time', op => '>', arg => 30, color => 'cyan' }, 2802 ], 2803 group_by => [qw(cxn cmd)], 2804 aggregate => 0, 2805 }, 2806 2807 # TODO: some more columns: 2808 # 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' 2809 # 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' 2810 # 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' 2811 2812 q_header => { 2813 capt => 'Q-mode Header', 2814 cust => {}, 2815 cols => { 2816 cxn => { src => 'cxn' }, 2817 queries => { src => 'Queries' }, 2818 qps => { src => 'Queries/Uptime_hires', dec => 1, trans => [qw(shorten)] }, 2819 load => { src => $exprs{ServerLoad}, dec => 1, trans => [qw(shorten)] }, 2820 connections => { src => $exprs{Connection}, dec => 1, trans => [qw(shorten)] }, 2821 slow => { src => 'Slow_queries', dec => 1, trans => [qw(shorten)] }, 2822 q_cache_hit => { src => $exprs{QcacheHitRatio}, dec => 1, trans => [qw(percent)] }, 2823 key_buffer_hit => { src => '1-(Key_reads/(Key_read_requests||1))', dec => 1, trans => [qw(percent)] }, 2824 bps_in => { src => 'Bytes_received/Uptime_hires', dec => 1, trans => [qw(shorten)] }, 2825 bps_out => { src => 'Bytes_sent/Uptime_hires', dec => 1, trans => [qw(shorten)] }, 2826 when => { src => 'when' }, 2827 q_detail => { src => $exprs{QueryDetail} }, 2828 }, 2829 visible => [ qw(cxn when load connections qps slow q_detail q_cache_hit key_buffer_hit bps_in bps_out)], 2830 filters => [], 2831 sort_cols => 'when cxn', 2832 sort_dir => '1', 2833 innodb => '', 2834 hide_caption => 1, 2835 group_by => [], 2836 aggregate => 0, 2837 }, 2838 row_operations => { 2839 capt => 'InnoDB Row Operations', 2840 cust => {}, 2841 cols => { 2842 cxn => { src => 'cxn' }, 2843 num_inserts => { src => 'IB_ro_num_rows_ins' }, 2844 num_updates => { src => 'IB_ro_num_rows_upd' }, 2845 num_reads => { src => 'IB_ro_num_rows_read' }, 2846 num_deletes => { src => 'IB_ro_num_rows_del' }, 2847 num_inserts_sec => { src => 'IB_ro_ins_sec' }, 2848 num_updates_sec => { src => 'IB_ro_upd_sec' }, 2849 num_reads_sec => { src => 'IB_ro_read_sec' }, 2850 num_deletes_sec => { src => 'IB_ro_del_sec' }, 2851 }, 2852 visible => [ qw(cxn num_inserts num_updates num_reads num_deletes num_inserts_sec 2853 num_updates_sec num_reads_sec num_deletes_sec)], 2854 filters => [], 2855 sort_cols => 'cxn', 2856 sort_dir => '1', 2857 innodb => 'ro', 2858 group_by => [], 2859 aggregate => 0, 2860 }, 2861 row_operation_misc => { 2862 capt => 'Row Operation Misc', 2863 cust => {}, 2864 cols => { 2865 cxn => { src => 'cxn' }, 2866 queries_in_queue => { src => 'IB_ro_queries_in_queue' }, 2867 queries_inside => { src => 'IB_ro_queries_inside' }, 2868 read_views_open => { src => 'IB_ro_read_views_open' }, 2869 main_thread_id => { src => 'IB_ro_main_thread_id' }, 2870 main_thread_proc_no => { src => 'IB_ro_main_thread_proc_no' }, 2871 main_thread_state => { src => 'IB_ro_main_thread_state' }, 2872 num_res_ext => { src => 'IB_ro_n_reserved_extents' }, 2873 }, 2874 visible => [ qw(cxn queries_in_queue queries_inside read_views_open main_thread_state)], 2875 filters => [], 2876 sort_cols => 'cxn', 2877 sort_dir => '1', 2878 innodb => 'ro', 2879 group_by => [], 2880 aggregate => 0, 2881 }, 2882 semaphores => { 2883 capt => 'InnoDB Semaphores', 2884 cust => {}, 2885 cols => { 2886 cxn => { src => 'cxn' }, 2887 mutex_os_waits => { src => 'IB_sm_mutex_os_waits' }, 2888 mutex_spin_rounds => { src => 'IB_sm_mutex_spin_rounds' }, 2889 mutex_spin_waits => { src => 'IB_sm_mutex_spin_waits' }, 2890 reservation_count => { src => 'IB_sm_reservation_count' }, 2891 rw_excl_os_waits => { src => 'IB_sm_rw_excl_os_waits' }, 2892 rw_excl_spins => { src => 'IB_sm_rw_excl_spins' }, 2893 rw_shared_os_waits => { src => 'IB_sm_rw_shared_os_waits' }, 2894 rw_shared_spins => { src => 'IB_sm_rw_shared_spins' }, 2895 signal_count => { src => 'IB_sm_signal_count' }, 2896 wait_array_size => { src => 'IB_sm_wait_array_size' }, 2897 }, 2898 visible => [ qw(cxn mutex_os_waits mutex_spin_waits mutex_spin_rounds 2899 rw_excl_os_waits rw_excl_spins rw_shared_os_waits rw_shared_spins 2900 signal_count reservation_count )], 2901 filters => [], 2902 sort_cols => 'cxn', 2903 sort_dir => '1', 2904 innodb => 'sm', 2905 group_by => [], 2906 aggregate => 0, 2907 }, 2908 slave_io_status => { 2909 capt => 'Slave I/O Status', 2910 cust => {}, 2911 cols => { 2912 cxn => { src => $exprs{chcxn_2_cxn}, hdr => 'CXN' }, 2913 connect_retry => { src => 'connect_retry' }, 2914 master_host => { src => 'master_host', hdr => 'Master'}, 2915 master_uuid => { src => '(master_uuid||"N/A")' }, 2916 master_log_file => { src => 'master_log_file', hdr => 'File' }, 2917 master_port => { src => 'master_port' }, 2918 master_ssl_allowed => { src => 'master_ssl_allowed' }, 2919 master_ssl_ca_file => { src => 'master_ssl_ca_file' }, 2920 master_ssl_ca_path => { src => 'master_ssl_ca_path' }, 2921 master_ssl_cert => { src => 'master_ssl_cert' }, 2922 master_ssl_cipher => { src => 'master_ssl_cipher' }, 2923 master_ssl_key => { src => 'master_ssl_key' }, 2924 master_user => { src => 'master_user' }, 2925 read_master_log_pos => { src => 'read_master_log_pos', hdr => 'Pos' }, 2926 relay_log_size => { src => 'relay_log_space', trans => [qw(shorten)] }, 2927 slave_io_running => { src => 'slave_io_running', hdr => 'On?' }, 2928 slave_io_state => { src => 'slave_io_state', hdr => 'State' }, 2929 channel_name => { src => $exprs{chcxn_2_ch}, hdr => 'Channel'}, 2930 }, 2931 visible => [ qw(cxn channel_name master_host master_uuid slave_io_running master_log_file relay_log_size read_master_log_pos slave_io_state)], 2932 filters => [ qw( cxn_is_slave ) ], 2933 sort_cols => 'slave_io_running channel_name cxn', 2934 colors => [ 2935 { col => 'slave_io_running', op => 'ne', arg => 'Yes', color => 'black on_red' }, 2936 ], 2937 sort_dir => '1', 2938 innodb => '', 2939 group_by => [qw(channel_name)], 2940 aggregate => 0, 2941 }, 2942 slave_sql_status => { 2943 capt => 'Slave SQL Status', 2944 cust => {}, 2945 cols => { 2946 cxn => { src => $exprs{chcxn_2_cxn}, hdr => 'CXN' }, 2947 exec_master_log_pos => { src => 'exec_master_log_pos', hdr => 'Master Pos' }, 2948 last_errno => { src => 'last_errno' }, 2949 last_error => { src => 'last_error' }, 2950 master_host => { src => 'master_host', hdr => 'Master' }, 2951 master_uuid => { src => '(master_uuid||"N/A")' }, 2952 relay_log_file => { src => 'relay_log_file' }, 2953 relay_log_pos => { src => 'relay_log_pos' }, 2954 relay_log_size => { src => 'relay_log_space', trans => [qw(shorten)] }, 2955 relay_master_log_file => { src => 'relay_master_log_file', hdr => 'Master File' }, 2956 replicate_do_db => { src => 'replicate_do_db' }, 2957 replicate_do_table => { src => 'replicate_do_table' }, 2958 replicate_ignore_db => { src => 'replicate_ignore_db' }, 2959 replicate_ignore_table => { src => 'replicate_ignore_table' }, 2960 replicate_wild_do_table => { src => 'replicate_wild_do_table' }, 2961 replicate_wild_ignore_table => { src => 'replicate_wild_ignore_table' }, 2962 skip_counter => { src => 'skip_counter' }, 2963 slave_sql_running => { src => 'slave_sql_running', hdr => 'On?' }, 2964 sort_time => { src => 'int(seconds_behind_master/60)' }, 2965 until_condition => { src => 'until_condition' }, 2966 until_log_file => { src => 'until_log_file' }, 2967 until_log_pos => { src => 'until_log_pos' }, 2968 time_behind_master => { src => 'seconds_behind_master', trans => [ qw(secs_to_time) ] }, 2969 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)] }, 2970 slave_catchup_rate => { src => $exprs{SlaveCatchupRate}, trans => [ qw(set_precision) ] }, 2971 slave_open_temp_tables => { src => 'Slave_open_temp_tables' }, 2972 retrieved_gtid_set => { src => '(retrieved_gtid_set||"N/A")' }, 2973 executed_gtid_set => { src => '(executed_gtid_set||"N/A")' }, 2974 channel_name => { src => $exprs{chcxn_2_ch}, hdr => 'Channel'}, 2975 }, 2976 visible => [ qw(cxn channel_name master_host master_uuid slave_sql_running time_behind_master slave_catchup_rate slave_open_temp_tables relay_log_pos last_error retrieved_gtid_set executed_gtid_set)], 2977 filters => [ qw( cxn_is_slave ) ], 2978 sort_cols => 'slave_sql_running -sort_time channel_name cxn', 2979 sort_dir => '1', 2980 innodb => '', 2981 colors => [ 2982 { col => 'slave_sql_running', op => 'ne', arg => 'Yes', color => 'black on_red' }, 2983 { col => 'time_behind_master', op => '>', arg => 600, color => 'red' }, 2984 { col => 'time_behind_master', op => '>', arg => 60, color => 'yellow' }, 2985 { col => 'time_behind_master', op => '==', arg => 0, color => 'white' }, 2986 ], 2987 group_by => [qw(channel_name)], 2988 aggregate => 0, 2989 }, 2990 table_statistics => { 2991 capt => 'Data from TABLE_STATISTICS', 2992 cust => {}, 2993 cols => { 2994 cxn => { src => 'cxn', minw => 6, maxw => 10 }, 2995 db => { src => 'table_schema' }, 2996 tbl => { src => 'table_name' }, 2997 rows_read => { src => 'rows_read' }, 2998 rows_changed => { src => 'rows_changed' }, 2999 rows_changed_x_indexes => { src => 'rows_changed_x_indexes' }, 3000 }, 3001 visible => [ qw(cxn db tbl rows_read rows_changed rows_changed_x_indexes) ], 3002 filters => [ ], 3003 sort_cols => 'rows_read', 3004 sort_dir => '-1', 3005 innodb => '', 3006 colors => [], 3007 hide_caption => 1, 3008 group_by => [], 3009 aggregate => 0, 3010 }, 3011 t_header => { 3012 capt => 'T-Mode Header', 3013 cust => {}, 3014 cols => { 3015 cxn => { src => 'cxn' }, 3016 dirty_bufs => { src => $exprs{DirtyBufs}, trans => [qw(percent)] }, 3017 history_list_len => { src => 'IB_tx_history_list_len' }, 3018 lock_structs => { src => 'IB_tx_num_lock_structs' }, 3019 num_txns => { src => $exprs{NumTxns} }, 3020 max_txn => { src => $exprs{MaxTxnTime}, trans => [qw(secs_to_time)] }, 3021 undo_for => { src => 'IB_tx_purge_undo_for' }, 3022 used_bufs => { src => $exprs{BufPoolFill}, trans => [qw(percent)]}, 3023 versions => { src => $exprs{OldVersions} }, 3024 }, 3025 visible => [ qw(cxn history_list_len versions undo_for dirty_bufs used_bufs num_txns max_txn lock_structs)], 3026 filters => [ ], 3027 sort_cols => 'cxn', 3028 sort_dir => '1', 3029 innodb => '', 3030 colors => [], 3031 hide_caption => 1, 3032 group_by => [], 3033 aggregate => 0, 3034 }, 3035 var_status => { 3036 capt => 'Variables & Status', 3037 cust => {}, 3038 cols => {}, # Generated from current varset 3039 visible => [], # Generated from current varset 3040 filters => [], 3041 sort_cols => '', 3042 sort_dir => 1, 3043 innodb => '', 3044 temp => 1, # Do not persist to config file. 3045 hide_caption => 1, 3046 pivot => 0, 3047 group_by => [], 3048 aggregate => 0, 3049 }, 3050 wait_array => { 3051 capt => 'InnoDB Wait Array', 3052 cust => {}, 3053 cols => { 3054 cxn => { src => 'cxn' }, 3055 thread => { src => 'thread' }, 3056 waited_at_filename => { src => 'waited_at_filename' }, 3057 waited_at_line => { src => 'waited_at_line' }, 3058 'time' => { src => 'waited_secs', trans => [ qw(secs_to_time) ] }, 3059 request_type => { src => 'request_type' }, 3060 lock_mem_addr => { src => 'lock_mem_addr' }, 3061 lock_cfile_name => { src => 'lock_cfile_name' }, 3062 lock_cline => { src => 'lock_cline' }, 3063 writer_thread => { src => 'writer_thread' }, 3064 writer_lock_mode => { src => 'writer_lock_mode' }, 3065 num_readers => { src => 'num_readers' }, 3066 lock_var => { src => 'lock_var' }, 3067 waiters_flag => { src => 'waiters_flag' }, 3068 last_s_file_name => { src => 'last_s_file_name' }, 3069 last_s_line => { src => 'last_s_line' }, 3070 last_x_file_name => { src => 'last_x_file_name' }, 3071 last_x_line => { src => 'last_x_line' }, 3072 cell_waiting => { src => 'cell_waiting' }, 3073 cell_event_set => { src => 'cell_event_set' }, 3074 }, 3075 visible => [ qw(cxn thread time waited_at_filename waited_at_line request_type num_readers lock_var waiters_flag cell_waiting cell_event_set)], 3076 filters => [], 3077 sort_cols => 'cxn -time', 3078 sort_dir => '1', 3079 innodb => 'sm', 3080 group_by => [], 3081 aggregate => 0, 3082 }, 3083 health_dashboard => { 3084 capt => 'Health Dashboard', 3085 hide_caption => 1, 3086 cust => {}, 3087 cols => { 3088 cxn => { src => 'cxn' }, 3089 uptime => { src => 'Uptime', trans => [qw(fuzzy_time)] }, 3090 qps => { src => 'Queries/Uptime_hires', dec => 1, trans => [qw(shorten)] }, 3091 spark_qps => { src => 'SPARK_qps' }, 3092 run => { src => 'User_threads_running' }, 3093 spark_run => { src => 'SPARK_run' }, 3094 max_query_time => { src => 'Max_query_time', trans => [qw(fuzzy_time)] }, 3095 connections => { src => 'Threads_connected' }, 3096 miss_rate => { src => 'Innodb_buffer_pool_reads/Uptime_hires', trans => [qw(set_precision)] }, 3097 locked_count => { src => 'Locked_count' }, 3098 'open' => { src => 'Open_tables' }, 3099 slave_running => { src => 'Slave_ok . " " . (Slaves || "")' }, 3100 time_behind_master => { src => 'seconds_behind_master', hdr => 'ReplLag' , trans => [qw(fuzzy_time)] }, 3101 longest_sql => { src => 'Longest_sql', trans => [qw(distill)] }, 3102 }, 3103 visible => [qw( 3104 cxn uptime max_query_time time_behind_master qps connections run 3105 miss_rate locked_count open slave_running longest_sql 3106 )], 3107 filters => [], 3108 sort_cols => 'cxn', 3109 sort_dir => '1', 3110 innodb => '', 3111 colors => [ 3112 { col => 'slave_running', op => '=~', arg => 'No', color => 'black on_red' }, 3113 { col => 'max_query_time', op => '>', arg => 30 * 60, color => 'red' }, 3114 { col => 'max_query_time', op => '>', arg => 600, color => 'yellow' }, 3115 { col => 'time_behind_master', op => '>', arg => 3600, color => 'cyan' }, 3116 ], 3117 group_by => [], 3118 aggregate => 0, 3119 }, 3120); 3121 3122# Initialize %tbl_meta from %columns and do some checks. 3123foreach my $table_name ( keys %tbl_meta ) { 3124 my $table = $tbl_meta{$table_name}; 3125 my $cols = $table->{cols}; 3126 3127 foreach my $col_name ( keys %$cols ) { 3128 my $col_def = $table->{cols}->{$col_name}; 3129 die "I can't find a column named '$col_name' for '$table_name'" unless $columns{$col_name}; 3130 $columns{$col_name}->{referenced} = 1; 3131 3132 foreach my $prop ( keys %col_props ) { 3133 # Each column gets non-existing values set from %columns or defaults from %col_props. 3134 if ( !$col_def->{$prop} ) { 3135 $col_def->{$prop} 3136 = defined($columns{$col_name}->{$prop}) 3137 ? $columns{$col_name}->{$prop} 3138 : $col_props{$prop}; 3139 } 3140 } 3141 3142 # Ensure transformations and aggregate functions are valid 3143 die "Unknown aggregate function '$col_def->{agg}' " 3144 . "for column '$col_name' in table '$table_name'" 3145 unless exists $agg_funcs{$col_def->{agg}}; 3146 foreach my $trans ( @{$col_def->{trans}} ) { 3147 die "Unknown transformation '$trans' " 3148 . "for column '$col_name' in table '$table_name'" 3149 unless exists $trans_funcs{$trans}; 3150 } 3151 } 3152 3153 # Ensure each column in visible and group_by exists in cols 3154 foreach my $place ( qw(visible group_by) ) { 3155 foreach my $col_name ( @{$table->{$place}} ) { 3156 if ( !exists $cols->{$col_name} ) { 3157 die "Column '$col_name' is listed in '$place' for '$table_name', but doesn't exist"; 3158 } 3159 } 3160 } 3161 3162 # Compile sort and color subroutines 3163 $table->{sort_func} = make_sort_func($table); 3164 $table->{color_func} = make_color_func($table); 3165} 3166 3167# This is for code cleanup: 3168{ 3169 my @unused_cols = grep { !$columns{$_}->{referenced} } sort keys %columns; 3170 if ( @unused_cols ) { 3171 die "The following columns are not used: " 3172 . join(' ', @unused_cols); 3173 } 3174} 3175 3176# ########################################################################### 3177# Operating modes {{{3 3178# ########################################################################### 3179my %modes = ( 3180 A => { 3181 hdr => 'Dashboard', 3182 cust => {}, 3183 note => 'Shows health/status dashboard', 3184 action_for => { 3185 k => { 3186 action => sub { kill_query('CONNECTION') }, 3187 label => "Kill a query's connection", 3188 }, 3189 x => { 3190 action => sub { kill_query('QUERY') }, 3191 label => "Kill a query", 3192 }, 3193 r => { 3194 action => sub { reverse_sort('health_dashboard'); }, 3195 label => 'Reverse sort order', 3196 }, 3197 s => { 3198 action => sub { choose_sort_cols('health_dashboard'); }, 3199 label => "Choose sort column", 3200 }, 3201 }, 3202 display_sub => \&display_A, 3203 connections => [], 3204 server_group => '', 3205 one_connection => 0, 3206 tables => [qw(health_dashboard)], 3207 visible_tables => [qw(health_dashboard)], 3208 }, 3209 B => { 3210 hdr => 'InnoDB Buffers', 3211 cust => {}, 3212 note => 'Shows buffer info from InnoDB', 3213 action_for => { 3214 i => { 3215 action => sub { toggle_config('status_inc') }, 3216 label => 'Toggle incremental status display', 3217 }, 3218 }, 3219 display_sub => \&display_B, 3220 connections => [], 3221 server_group => '', 3222 one_connection => 0, 3223 tables => [qw(buffer_pool page_statistics insert_buffers adaptive_hash_index)], 3224 visible_tables => [qw(buffer_pool page_statistics insert_buffers adaptive_hash_index)], 3225 }, 3226 C => { 3227 hdr => 'Command Summary', 3228 cust => {}, 3229 note => 'Shows relative magnitude of variables', 3230 action_for => { 3231 s => { 3232 action => sub { get_config_interactive('cmd_filter') }, 3233 label => 'Choose variable prefix', 3234 }, 3235 }, 3236 display_sub => \&display_C, 3237 connections => [], 3238 server_group => '', 3239 one_connection => 0, 3240 tables => [qw(cmd_summary)], 3241 visible_tables => [qw(cmd_summary)], 3242 }, 3243 D => { 3244 hdr => 'InnoDB Deadlocks', 3245 cust => {}, 3246 note => 'View InnoDB deadlock information', 3247 action_for => { 3248 c => { 3249 action => sub { edit_table('deadlock_transactions') }, 3250 label => 'Choose visible columns', 3251 }, 3252 w => { 3253 action => \&create_deadlock, 3254 label => 'Wipe deadlock status info by creating a deadlock', 3255 }, 3256 }, 3257 display_sub => \&display_D, 3258 connections => [], 3259 server_group => '', 3260 one_connection => 0, 3261 tables => [qw(deadlock_transactions deadlock_locks)], 3262 visible_tables => [qw(deadlock_transactions deadlock_locks)], 3263 }, 3264 F => { 3265 hdr => 'InnoDB FK Err', 3266 cust => {}, 3267 note => 'View the latest InnoDB foreign key error', 3268 action_for => {}, 3269 display_sub => \&display_F, 3270 connections => [], 3271 server_group => '', 3272 one_connection => 1, 3273 tables => [qw(fk_error)], 3274 visible_tables => [qw(fk_error)], 3275 }, 3276 I => { 3277 hdr => 'InnoDB I/O Info', 3278 cust => {}, 3279 note => 'Shows I/O info (i/o, log...) from InnoDB', 3280 action_for => { 3281 i => { 3282 action => sub { toggle_config('status_inc') }, 3283 label => 'Toggle incremental status display', 3284 }, 3285 }, 3286 display_sub => \&display_I, 3287 connections => [], 3288 server_group => '', 3289 one_connection => 0, 3290 tables => [qw(io_threads pending_io file_io_misc log_statistics)], 3291 visible_tables => [qw(io_threads pending_io file_io_misc log_statistics)], 3292 }, 3293 K => { 3294 hdr => 'InnoDB Lock Waits', 3295 cust => {}, 3296 note => 'Shows blocked and blocking transactions', 3297 action_for => { 3298 k => { 3299 action => sub { kill_query('CONNECTION') }, 3300 label => "Kill a query's connection", 3301 }, 3302 }, 3303 display_sub => \&display_K, 3304 connections => [], 3305 server_group => '', 3306 one_connection => 0, 3307 tables => [qw(innodb_blocked_blocker)], 3308 visible_tables => [qw(innodb_blocked_blocker)], 3309 }, 3310 L => { 3311 hdr => 'Locks', 3312 cust => {}, 3313 note => 'Shows transaction locks', 3314 action_for => { 3315 a => { 3316 action => sub { send_cmd_to_servers('CREATE TABLE IF NOT EXISTS test.innodb_lock_monitor(a int) ENGINE=InnoDB', 0, '', []); }, 3317 label => 'Start the InnoDB Lock Monitor', 3318 }, 3319 o => { 3320 action => sub { send_cmd_to_servers('DROP TABLE IF EXISTS test.innodb_lock_monitor', 0, '', []); }, 3321 label => 'Stop the InnoDB Lock Monitor', 3322 }, 3323 }, 3324 display_sub => \&display_L, 3325 connections => [], 3326 server_group => '', 3327 one_connection => 0, 3328 tables => [qw(innodb_locks)], 3329 visible_tables => [qw(innodb_locks)], 3330 }, 3331 M => { 3332 hdr => 'Replication Status', 3333 cust => {}, 3334 note => 'Shows replication (master and slave) status', 3335 action_for => { 3336 a => { 3337 action => sub { send_cmd_to_servers('START SLAVE', 0, 'START SLAVE SQL_THREAD UNTIL MASTER_LOG_FILE = ?, MASTER_LOG_POS = ?', []); }, 3338 label => 'Start slave(s)', 3339 }, 3340 i => { 3341 action => sub { toggle_config('status_inc') }, 3342 label => 'Toggle incremental status display', 3343 }, 3344 o => { 3345 action => sub { send_cmd_to_servers('STOP SLAVE', 0, '', []); }, 3346 label => 'Stop slave(s)', 3347 }, 3348 b => { 3349 action => sub { purge_master_logs() }, 3350 label => 'Purge unused master logs', 3351 }, 3352 }, 3353 display_sub => \&display_M, 3354 connections => [], 3355 server_group => '', 3356 one_connection => 0, 3357 tables => [qw(slave_sql_status slave_io_status master_status)], 3358 visible_tables => [qw(slave_sql_status slave_io_status master_status)], 3359 }, 3360 O => { 3361 hdr => 'Open Tables', 3362 cust => {}, 3363 note => 'Shows open tables in MySQL', 3364 action_for => { 3365 r => { 3366 action => sub { reverse_sort('open_tables'); }, 3367 label => 'Reverse sort order', 3368 }, 3369 s => { 3370 action => sub { choose_sort_cols('open_tables'); }, 3371 label => "Choose sort column", 3372 }, 3373 }, 3374 display_sub => \&display_O, 3375 connections => [], 3376 server_group => '', 3377 one_connection => 0, 3378 tables => [qw(open_tables)], 3379 visible_tables => [qw(open_tables)], 3380 }, 3381 U => { 3382 hdr => 'User Statistics', 3383 cust => {}, 3384 note => 'Displays Percona/MariaDB enhancements such as table statistics', 3385 action_for => { 3386 i => { 3387 action => sub { set_visible_table('index_statistics') }, 3388 label => 'Switch to INDEX_STATISTICS', 3389 }, 3390 s => { 3391 action => sub { $clear_screen_sub->(); send_cmd_to_servers('SET @@global.userstat_running := 1 - @@global.userstat_running', 1, undef, []); }, 3392 label => "Change the display's sort column", 3393 }, 3394 t => { 3395 action => sub { set_visible_table('table_statistics') }, 3396 label => 'Switch to TABLE_STATISTICS', 3397 }, 3398 x => { 3399 action => sub { set_visible_table('index_table_statistics') }, 3400 label => 'Switch to {INDEX,TABLE}_STATISTICS', 3401 }, 3402 }, 3403 display_sub => \&display_P, 3404 connections => [], 3405 server_group => '', 3406 one_connection => 0, 3407 tables => [qw(table_statistics index_statistics index_table_statistics)], 3408 visible_tables => [qw(index_table_statistics)], 3409 }, 3410 Q => { 3411 hdr => 'Query List', 3412 cust => {}, 3413 note => 'Shows queries from SHOW FULL PROCESSLIST', 3414 action_for => { 3415 a => { 3416 action => sub { toggle_filter('processlist', 'hide_self') }, 3417 label => 'Toggle the innotop process', 3418 }, 3419 c => { 3420 action => sub { edit_table('processlist') }, 3421 label => 'Choose visible columns', 3422 }, 3423 e => { 3424 action => sub { analyze_query('e'); }, 3425 label => "Explain a thread's query", 3426 }, 3427 f => { 3428 action => sub { analyze_query('f'); }, 3429 label => "Show a thread's full query", 3430 }, 3431 h => { 3432 action => sub { toggle_visible_table('Q', 'q_header') }, 3433 label => 'Toggle the header on and off', 3434 }, 3435 i => { 3436 action => sub { toggle_filter('processlist', 'hide_inactive') }, 3437 label => 'Toggle idle processes', 3438 }, 3439 k => { 3440 action => sub { kill_query('CONNECTION') }, 3441 label => "Kill a query's connection", 3442 }, 3443 r => { 3444 action => sub { reverse_sort('processlist'); }, 3445 label => 'Reverse sort order', 3446 }, 3447 s => { 3448 action => sub { choose_sort_cols('processlist'); }, 3449 label => "Change the display's sort column", 3450 }, 3451 t => { 3452 action => sub { toggle_filter('processlist', 'hide_connect') }, 3453 label => 'Toggle slave processes', 3454 }, 3455 x => { 3456 action => sub { kill_query('QUERY') }, 3457 label => "Kill a query", 3458 }, 3459 }, 3460 display_sub => \&display_Q, 3461 connections => [], 3462 server_group => '', 3463 one_connection => 0, 3464 tables => [qw(q_header processlist)], 3465 visible_tables => [qw(q_header processlist)], 3466 }, 3467 R => { 3468 hdr => 'InnoDB Row Ops', 3469 cust => {}, 3470 note => 'Shows InnoDB row operation and semaphore info', 3471 action_for => { 3472 i => { 3473 action => sub { toggle_config('status_inc') }, 3474 label => 'Toggle incremental status display', 3475 }, 3476 }, 3477 display_sub => \&display_R, 3478 connections => [], 3479 server_group => '', 3480 one_connection => 0, 3481 tables => [qw(row_operations row_operation_misc semaphores wait_array)], 3482 visible_tables => [qw(row_operations row_operation_misc semaphores wait_array)], 3483 }, 3484 S => { 3485 hdr => 'Variables & Status', 3486 cust => {}, 3487 note => 'Shows query load statistics a la vmstat', 3488 action_for => { 3489 '>' => { 3490 action => sub { switch_var_set('S_set', 1) }, 3491 label => 'Switch to next variable set', 3492 }, 3493 '<' => { 3494 action => sub { switch_var_set('S_set', -1) }, 3495 label => 'Switch to prev variable set', 3496 }, 3497 c => { 3498 action => sub { 3499 choose_var_set('S_set'); 3500 start_S_mode(); 3501 }, 3502 label => "Choose which set to display", 3503 }, 3504 e => { 3505 action => \&edit_current_var_set, 3506 label => 'Edit the current set of variables', 3507 }, 3508 i => { 3509 action => sub { $clear_screen_sub->(); toggle_config('status_inc') }, 3510 label => 'Toggle incremental status display', 3511 }, 3512 '-' => { 3513 action => sub { set_display_precision(-1) }, 3514 label => 'Decrease fractional display precision', 3515 }, 3516 '+' => { 3517 action => sub { set_display_precision(1) }, 3518 label => 'Increase fractional display precision', 3519 }, 3520 g => { 3521 action => sub { set_s_mode('g') }, 3522 label => 'Switch to graph (tload) view', 3523 }, 3524 s => { 3525 action => sub { set_s_mode('s') }, 3526 label => 'Switch to standard (vmstat) view', 3527 }, 3528 v => { 3529 action => sub { set_s_mode('v') }, 3530 label => 'Switch to pivoted view', 3531 }, 3532 }, 3533 display_sub => \&display_S, 3534 no_clear_screen => 1, 3535 connections => [], 3536 server_group => '', 3537 one_connection => 0, 3538 tables => [qw(var_status)], 3539 visible_tables => [qw(var_status)], 3540 }, 3541 T => { 3542 hdr => 'InnoDB Txns', 3543 cust => {}, 3544 note => 'Shows InnoDB transactions in top-like format', 3545 action_for => { 3546 a => { 3547 action => sub { toggle_filter('innodb_transactions', 'hide_self') }, 3548 label => 'Toggle the innotop process', 3549 }, 3550 c => { 3551 action => sub { edit_table('innodb_transactions') }, 3552 label => 'Choose visible columns', 3553 }, 3554 e => { 3555 action => sub { analyze_query('e'); }, 3556 label => "Explain a thread's query", 3557 }, 3558 f => { 3559 action => sub { analyze_query('f'); }, 3560 label => "Show a thread's full query", 3561 }, 3562 h => { 3563 action => sub { toggle_visible_table('T', 't_header') }, 3564 label => 'Toggle the header on and off', 3565 }, 3566 i => { 3567 action => sub { toggle_filter('innodb_transactions', 'hide_inactive') }, 3568 label => 'Toggle inactive transactions', 3569 }, 3570 k => { 3571 action => sub { kill_query('CONNECTION') }, 3572 label => "Kill a transaction's connection", 3573 }, 3574 r => { 3575 action => sub { reverse_sort('innodb_transactions'); }, 3576 label => 'Reverse sort order', 3577 }, 3578 s => { 3579 action => sub { choose_sort_cols('innodb_transactions'); }, 3580 label => "Change the display's sort column", 3581 }, 3582 x => { 3583 action => sub { kill_query('QUERY') }, 3584 label => "Kill a query", 3585 }, 3586 }, 3587 display_sub => \&display_T, 3588 connections => [], 3589 server_group => '', 3590 one_connection => 0, 3591 tables => [qw(t_header innodb_transactions)], 3592 visible_tables => [qw(t_header innodb_transactions)], 3593 }, 3594); 3595 3596# ########################################################################### 3597# Global key mappings {{{3 3598# Keyed on a single character, which is read from the keyboard. Uppercase 3599# letters switch modes. Lowercase letters access commands when in a mode. 3600# These can be overridden by action_for in %modes. 3601# ########################################################################### 3602my %action_for = ( 3603 '$' => { 3604 action => \&edit_configuration, 3605 label => 'Edit configuration settings', 3606 }, 3607 '?' => { 3608 action => \&display_help, 3609 label => 'Show help', 3610 }, 3611 '!' => { 3612 action => \&display_license, 3613 label => 'Show license and warranty', 3614 }, 3615 '^' => { 3616 action => \&edit_table, 3617 label => "Edit the displayed table(s)", 3618 }, 3619 '#' => { 3620 action => \&choose_server_groups, 3621 label => 'Select/create server groups', 3622 }, 3623 '@' => { 3624 action => \&choose_servers, 3625 label => 'Select/create server connections', 3626 }, 3627 '/' => { 3628 action => \&add_quick_filter, 3629 label => 'Quickly filter what you see', 3630 }, 3631 '\\' => { 3632 action => \&clear_quick_filters, 3633 label => 'Clear quick-filters', 3634 }, 3635 '%' => { 3636 action => \&choose_filters, 3637 label => 'Choose and edit table filters', 3638 }, 3639 "\t" => { 3640 action => \&next_server_group, 3641 label => 'Switch to the next server group', 3642 key => 'TAB', 3643 }, 3644 '=' => { 3645 action => \&toggle_aggregate, 3646 label => 'Toggle aggregation', 3647 }, 3648 # TODO: can these be auto-generated from %modes? 3649 A => { 3650 action => sub { switch_mode('A') }, 3651 label => '', 3652 }, 3653 B => { 3654 action => sub { switch_mode('B') }, 3655 label => '', 3656 }, 3657 C => { 3658 action => sub { switch_mode('C') }, 3659 label => '', 3660 }, 3661 D => { 3662 action => sub { switch_mode('D') }, 3663 label => '', 3664 }, 3665 F => { 3666 action => sub { switch_mode('F') }, 3667 label => '', 3668 }, 3669 I => { 3670 action => sub { switch_mode('I') }, 3671 label => '', 3672 }, 3673 K => { 3674 action => sub { switch_mode('K') }, 3675 label => '', 3676 }, 3677 L => { 3678 action => sub { switch_mode('L') }, 3679 label => '', 3680 }, 3681 M => { 3682 action => sub { switch_mode('M') }, 3683 label => '', 3684 }, 3685 O => { 3686 action => sub { switch_mode('O') }, 3687 label => '', 3688 }, 3689 Q => { 3690 action => sub { switch_mode('Q') }, 3691 label => '', 3692 }, 3693 R => { 3694 action => sub { switch_mode('R') }, 3695 label => '', 3696 }, 3697 S => { 3698 action => \&start_S_mode, 3699 label => '', 3700 }, 3701 T => { 3702 action => sub { switch_mode('T') }, 3703 label => '', 3704 }, 3705 U => { 3706 action => sub { switch_mode('U') }, 3707 label => '', 3708 }, 3709 d => { 3710 action => sub { get_config_interactive('interval') }, 3711 label => 'Change refresh interval', 3712 }, 3713 n => { action => \&next_server, label => 'Switch to the next connection' }, 3714 p => { action => \&pause, label => 'Pause innotop', }, 3715 q => { action => \&finish, label => 'Quit innotop', }, 3716); 3717 3718# ########################################################################### 3719# Sleep times after certain statements {{{3 3720# ########################################################################### 3721my %stmt_sleep_time_for = (); 3722 3723# ########################################################################### 3724# Config editor key mappings {{{3 3725# ########################################################################### 3726my %cfg_editor_action = ( 3727 c => { 3728 note => 'Edit columns, etc in the displayed table(s)', 3729 func => \&edit_table, 3730 }, 3731 g => { 3732 note => 'Edit general configuration', 3733 func => \&edit_configuration_variables, 3734 }, 3735 k => { 3736 note => 'Edit row-coloring rules', 3737 func => \&edit_color_rules, 3738 }, 3739 p => { 3740 note => 'Manage plugins', 3741 func => \&edit_plugins, 3742 }, 3743 s => { 3744 note => 'Edit server groups', 3745 func => \&edit_server_groups, 3746 }, 3747 S => { 3748 note => 'Edit SQL statement sleep delays', 3749 func => \&edit_stmt_sleep_times, 3750 }, 3751 t => { 3752 note => 'Choose which table(s) to display in this mode', 3753 func => \&choose_mode_tables, 3754 }, 3755); 3756 3757# ########################################################################### 3758# Color editor key mappings {{{3 3759# ########################################################################### 3760my %color_editor_action = ( 3761 n => { 3762 note => 'Create a new color rule', 3763 func => sub { 3764 my ( $tbl, $idx ) = @_; 3765 my $meta = $tbl_meta{$tbl}; 3766 3767 $clear_screen_sub->(); 3768 my $col; 3769 do { 3770 $col = prompt_list( 3771 'Choose the target column for the rule', 3772 '', 3773 sub { return keys %{$meta->{cols}} }, 3774 { map { $_ => $meta->{cols}->{$_}->{label} } keys %{$meta->{cols}} }); 3775 } while ( !$col ); 3776 ( $col ) = grep { $_ } split(/\W+/, $col); 3777 return $idx unless $col && exists $meta->{cols}->{$col}; 3778 3779 $clear_screen_sub->(); 3780 my $op; 3781 do { 3782 $op = prompt_list( 3783 'Choose the comparison operator for the rule', 3784 '', 3785 sub { return keys %comp_ops }, 3786 { map { $_ => $comp_ops{$_} } keys %comp_ops } ); 3787 } until ( $op ); 3788 $op =~ s/\s+//g; 3789 return $idx unless $op && exists $comp_ops{$op}; 3790 3791 my $arg; 3792 do { 3793 $arg = prompt('Specify an argument for the comparison'); 3794 } until defined $arg; 3795 3796 my $color; 3797 do { 3798 $color = prompt_list( 3799 'Choose the color(s) the row should be when the rule matches', 3800 '', 3801 sub { return keys %ansicolors }, 3802 { map { $_ => $_ } keys %ansicolors } ); 3803 } until defined $color; 3804 $color = join(' ', unique(grep { exists $ansicolors{$_} } split(/\W+/, $color))); 3805 return $idx unless $color; 3806 3807 push @{$tbl_meta{$tbl}->{colors}}, { 3808 col => $col, 3809 op => $op, 3810 arg => $arg, 3811 color => $color 3812 }; 3813 $tbl_meta{$tbl}->{cust}->{colors} = 1; 3814 3815 return $idx; 3816 }, 3817 }, 3818 d => { 3819 note => 'Remove the selected rule', 3820 func => sub { 3821 my ( $tbl, $idx ) = @_; 3822 my @rules = @{ $tbl_meta{$tbl}->{colors} }; 3823 return 0 unless @rules > 0 && $idx < @rules && $idx >= 0; 3824 splice(@{$tbl_meta{$tbl}->{colors}}, $idx, 1); 3825 $tbl_meta{$tbl}->{cust}->{colors} = 1; 3826 return $idx == @rules ? $#rules : $idx; 3827 }, 3828 }, 3829 j => { 3830 note => 'Move highlight down one', 3831 func => sub { 3832 my ( $tbl, $idx ) = @_; 3833 my $num_rules = scalar @{$tbl_meta{$tbl}->{colors}}; 3834 return ($idx + 1) % $num_rules; 3835 }, 3836 }, 3837 k => { 3838 note => 'Move highlight up one', 3839 func => sub { 3840 my ( $tbl, $idx ) = @_; 3841 my $num_rules = scalar @{$tbl_meta{$tbl}->{colors}}; 3842 return ($idx - 1) % $num_rules; 3843 }, 3844 }, 3845 '+' => { 3846 note => 'Move selected rule up one', 3847 func => sub { 3848 my ( $tbl, $idx ) = @_; 3849 my $meta = $tbl_meta{$tbl}; 3850 my $dest = $idx == 0 ? scalar(@{$meta->{colors}} - 1) : $idx - 1; 3851 my $temp = $meta->{colors}->[$idx]; 3852 $meta->{colors}->[$idx] = $meta->{colors}->[$dest]; 3853 $meta->{colors}->[$dest] = $temp; 3854 $meta->{cust}->{colors} = 1; 3855 return $dest; 3856 }, 3857 }, 3858 '-' => { 3859 note => 'Move selected rule down one', 3860 func => sub { 3861 my ( $tbl, $idx ) = @_; 3862 my $meta = $tbl_meta{$tbl}; 3863 my $dest = $idx == scalar(@{$meta->{colors}} - 1) ? 0 : $idx + 1; 3864 my $temp = $meta->{colors}->[$idx]; 3865 $meta->{colors}->[$idx] = $meta->{colors}->[$dest]; 3866 $meta->{colors}->[$dest] = $temp; 3867 $meta->{cust}->{colors} = 1; 3868 return $dest; 3869 }, 3870 }, 3871); 3872 3873# ########################################################################### 3874# Plugin editor key mappings {{{3 3875# ########################################################################### 3876my %plugin_editor_action = ( 3877 '*' => { 3878 note => 'Toggle selected plugin active/inactive', 3879 func => sub { 3880 my ( $plugins, $idx ) = @_; 3881 my $plugin = $plugins->[$idx]; 3882 $plugin->{active} = $plugin->{active} ? 0 : 1; 3883 return $idx; 3884 }, 3885 }, 3886 j => { 3887 note => 'Move highlight down one', 3888 func => sub { 3889 my ( $plugins, $idx ) = @_; 3890 return ($idx + 1) % scalar(@$plugins); 3891 }, 3892 }, 3893 k => { 3894 note => 'Move highlight up one', 3895 func => sub { 3896 my ( $plugins, $idx ) = @_; 3897 return $idx == 0 ? @$plugins - 1 : $idx - 1; 3898 }, 3899 }, 3900); 3901 3902# ########################################################################### 3903# Table editor key mappings {{{3 3904# ########################################################################### 3905my %tbl_editor_action = ( 3906 a => { 3907 note => 'Add a column to the table', 3908 func => sub { 3909 my ( $tbl, $col ) = @_; 3910 my @visible_cols = @{ $tbl_meta{$tbl}->{visible} }; 3911 my %all_cols = %{ $tbl_meta{$tbl}->{cols} }; 3912 delete @all_cols{@visible_cols}; 3913 my $choice = prompt_list( 3914 'Choose a column', 3915 '', 3916 sub { return keys %all_cols; }, 3917 { map { $_ => $all_cols{$_}->{label} || $all_cols{$_}->{hdr} } keys %all_cols }); 3918 if ( $all_cols{$choice} ) { 3919 push @{$tbl_meta{$tbl}->{visible}}, $choice; 3920 $tbl_meta{$tbl}->{cust}->{visible} = 1; 3921 return $choice; 3922 } 3923 return $col; 3924 }, 3925 }, 3926 n => { 3927 note => 'Create a new column and add it to the table', 3928 func => sub { 3929 my ( $tbl, $col ) = @_; 3930 3931 $clear_screen_sub->(); 3932 print word_wrap("Choose a name for the column. This name is not displayed, and is used only " 3933 . "for internal reference. It can contain only lowercase letters, numbers, " 3934 . "and underscores."); 3935 print "\n\n"; 3936 do { 3937 $col = prompt("Enter column name"); 3938 $col = '' if $col =~ m/[^a-z0-9_]/; 3939 } while ( !$col ); 3940 3941 $clear_screen_sub->(); 3942 my $hdr; 3943 do { 3944 $hdr = prompt("Enter column header"); 3945 } while ( !$hdr ); 3946 3947 $clear_screen_sub->(); 3948 print "Choose a source for the column's data\n\n"; 3949 my ( $src, $sub, $err ); 3950 do { 3951 if ( $err ) { 3952 print "Error: $err\n\n"; 3953 } 3954 $src = prompt("Enter column source"); 3955 if ( $src ) { 3956 ( $sub, $err ) = compile_expr($src); 3957 } 3958 } until ( !$err); 3959 3960 # TODO: this duplicates %col_props. 3961 $tbl_meta{$tbl}->{cols}->{$col} = { 3962 hdr => $hdr, 3963 src => $src, 3964 just => '-', 3965 num => 0, 3966 label => 'User-defined', 3967 user => 1, 3968 tbl => $tbl, 3969 minw => 0, 3970 maxw => 0, 3971 trans => [], 3972 func => $sub, 3973 dec => 0, 3974 agg => 0, 3975 aggonly => 0, 3976 agghide => 0, 3977 }; 3978 3979 $tbl_meta{$tbl}->{visible} = [ unique(@{$tbl_meta{$tbl}->{visible}}, $col) ]; 3980 $tbl_meta{$tbl}->{cust}->{visible} = 1; 3981 return $col; 3982 }, 3983 }, 3984 d => { 3985 note => 'Remove selected column', 3986 func => sub { 3987 my ( $tbl, $col ) = @_; 3988 my @visible_cols = @{ $tbl_meta{$tbl}->{visible} }; 3989 my $idx = 0; 3990 return $col unless @visible_cols > 1; 3991 while ( $visible_cols[$idx] ne $col ) { 3992 $idx++; 3993 } 3994 $tbl_meta{$tbl}->{visible} = [ grep { $_ ne $col } @visible_cols ]; 3995 $tbl_meta{$tbl}->{cust}->{visible} = 1; 3996 return $idx == $#visible_cols ? $visible_cols[$idx - 1] : $visible_cols[$idx + 1]; 3997 }, 3998 }, 3999 e => { 4000 note => 'Edit selected column', 4001 func => sub { 4002 # TODO: make this editor hotkey-driven and give readline support. 4003 my ( $tbl, $col ) = @_; 4004 $clear_screen_sub->(); 4005 my $meta = $tbl_meta{$tbl}->{cols}->{$col}; 4006 my @prop = qw(hdr label src just num minw maxw trans agg); # TODO redundant 4007 4008 my $answer; 4009 do { 4010 # Do what the user asked... 4011 if ( $answer && grep { $_ eq $answer } @prop ) { 4012 # Some properties are arrays, others scalars. 4013 my $ini = ref $col_props{$answer} ? join(' ', @{$meta->{$answer}}) : $meta->{$answer}; 4014 my $val = prompt("New value for $answer", undef, $ini); 4015 $val = [ split(' ', $val) ] if ref($col_props{$answer}); 4016 if ( $answer eq 'trans' ) { 4017 $val = [ unique(grep{ exists $trans_funcs{$_} } @$val) ]; 4018 } 4019 @{$meta}{$answer, 'user', 'tbl' } = ( $val, 1, $tbl ); 4020 } 4021 4022 my @display_lines = ( 4023 '', 4024 "You are editing column $tbl.$col.\n", 4025 ); 4026 4027 push @display_lines, create_table2( 4028 \@prop, 4029 { map { $_ => $_ } @prop }, 4030 { map { $_ => ref $meta->{$_} eq 'ARRAY' ? join(' ', @{$meta->{$_}}) 4031 : ref $meta->{$_} ? '[expression code]' 4032 : $meta->{$_} 4033 } @prop 4034 }, 4035 { sep => ' ' }); 4036 draw_screen(\@display_lines, { raw => 1 }); 4037 print "\n\n"; # One to add space, one to clear readline artifacts 4038 $answer = prompt('Edit what? (q to quit)'); 4039 } while ( $answer ne 'q' ); 4040 4041 return $col; 4042 }, 4043 }, 4044 j => { 4045 note => 'Move highlight down one', 4046 func => sub { 4047 my ( $tbl, $col ) = @_; 4048 my @visible_cols = @{ $tbl_meta{$tbl}->{visible} }; 4049 my $idx = 0; 4050 while ( $visible_cols[$idx] ne $col ) { 4051 $idx++; 4052 } 4053 return $visible_cols[ ($idx + 1) % @visible_cols ]; 4054 }, 4055 }, 4056 k => { 4057 note => 'Move highlight up one', 4058 func => sub { 4059 my ( $tbl, $col ) = @_; 4060 my @visible_cols = @{ $tbl_meta{$tbl}->{visible} }; 4061 my $idx = 0; 4062 while ( $visible_cols[$idx] ne $col ) { 4063 $idx++; 4064 } 4065 return $visible_cols[ $idx - 1 ]; 4066 }, 4067 }, 4068 '+' => { 4069 note => 'Move selected column up one', 4070 func => sub { 4071 my ( $tbl, $col ) = @_; 4072 my $meta = $tbl_meta{$tbl}; 4073 my @visible_cols = @{$meta->{visible}}; 4074 my $idx = 0; 4075 while ( $visible_cols[$idx] ne $col ) { 4076 $idx++; 4077 } 4078 if ( $idx ) { 4079 $visible_cols[$idx] = $visible_cols[$idx - 1]; 4080 $visible_cols[$idx - 1] = $col; 4081 $meta->{visible} = \@visible_cols; 4082 } 4083 else { 4084 shift @{$meta->{visible}}; 4085 push @{$meta->{visible}}, $col; 4086 } 4087 $meta->{cust}->{visible} = 1; 4088 return $col; 4089 }, 4090 }, 4091 '-' => { 4092 note => 'Move selected column down one', 4093 func => sub { 4094 my ( $tbl, $col ) = @_; 4095 my $meta = $tbl_meta{$tbl}; 4096 my @visible_cols = @{$meta->{visible}}; 4097 my $idx = 0; 4098 while ( $visible_cols[$idx] ne $col ) { 4099 $idx++; 4100 } 4101 if ( $idx == $#visible_cols ) { 4102 unshift @{$meta->{visible}}, $col; 4103 pop @{$meta->{visible}}; 4104 } 4105 else { 4106 $visible_cols[$idx] = $visible_cols[$idx + 1]; 4107 $visible_cols[$idx + 1] = $col; 4108 $meta->{visible} = \@visible_cols; 4109 } 4110 $meta->{cust}->{visible} = 1; 4111 return $col; 4112 }, 4113 }, 4114 f => { 4115 note => 'Choose filters', 4116 func => sub { 4117 my ( $tbl, $col ) = @_; 4118 choose_filters($tbl); 4119 return $col; 4120 }, 4121 }, 4122 o => { 4123 note => 'Edit color rules', 4124 func => sub { 4125 my ( $tbl, $col ) = @_; 4126 edit_color_rules($tbl); 4127 return $col; 4128 }, 4129 }, 4130 s => { 4131 note => 'Choose sort columns', 4132 func => sub { 4133 my ( $tbl, $col ) = @_; 4134 choose_sort_cols($tbl); 4135 return $col; 4136 }, 4137 }, 4138 g => { 4139 note => 'Choose group-by (aggregate) columns', 4140 func => sub { 4141 my ( $tbl, $col ) = @_; 4142 choose_group_cols($tbl); 4143 return $col; 4144 }, 4145 }, 4146); 4147 4148# ########################################################################### 4149# Global variables and environment {{{2 4150# ########################################################################### 4151 4152my @this_term_size; # w_chars, h_chars, w_pix, h_pix 4153my @last_term_size; # w_chars, h_chars, w_pix, h_pix 4154my $char; 4155my $windows = $OSNAME =~ m/MSWin/; 4156my $have_color = 0; 4157my $MAX_ULONG = 4294967295; # 2^32-1 4158my $num_regex = qr/^[+-]?(?=\d|\.)\d*(?:\.\d+)?(?:E[+-]?\d+|)$/i; 4159my $int_regex = qr/^\d+$/; 4160my $bool_regex = qr/^[01]$/; 4161my $term = undef; 4162my $file = undef; # File to watch for InnoDB monitor output 4163my $file_mtime = undef; # Status of watched file 4164my $file_data = undef; # Last chunk of text read from file 4165my $innodb_parser = InnoDBParser->new; 4166 4167my $nonfatal_errs = join('|', 4168 'Access denied for user', 4169 'Unknown MySQL server host', 4170 'Unknown database', 4171 'Can\'t connect to local MySQL server through socket', 4172 'Can\'t connect to MySQL server on', 4173 'MySQL server has gone away', 4174 'Cannot call SHOW INNODB STATUS', 4175 'Access denied', 4176 'AutoCommit', 4177 'Lost connection to MySQL server', 4178 'Too many connections', 4179); 4180 4181if ( !$opts{n} ) { 4182 require Term::ReadLine; 4183 $term = Term::ReadLine->new('innotop'); 4184} 4185 4186# Stores status, variables, innodb status, master/slave status etc. 4187# Keyed on connection name. Each entry is a hashref of current and past data sets, 4188# keyed on clock tick. 4189my %vars; 4190my %info_gotten = (); # Which things have been retrieved for the current clock tick. 4191my %show_variables; # Stores SHOW VARIABLES for each cxn so we don't re-fetch. 4192 4193# Stores info on currently displayed queries: cxn, connection ID, query text, 4194# user, and host. 4195my @current_queries; 4196 4197my $lines_printed = 0; 4198my $clock = 0; # Incremented with every wake-sleep cycle 4199my $clearing_deadlocks = 0; 4200 4201# If terminal coloring is available, use it. The only function I want from 4202# the module is the colored() function. 4203eval { 4204 if ( !$opts{n} ) { 4205 if ( $windows ) { 4206 require Win32::Console::ANSI; 4207 } 4208 require Term::ANSIColor; 4209 import Term::ANSIColor qw(colored); 4210 $have_color = 1; 4211 } 4212}; 4213if ( $EVAL_ERROR || $opts{n} ) { 4214 # If there was an error, manufacture my own colored() function that does no 4215 # coloring. 4216 undef &colored; 4217 *colored = sub { pop @_; @_; }; 4218} 4219 4220if ( $opts{n} ) { 4221 $clear_screen_sub = sub {}; 4222} 4223elsif ( $windows ) { 4224 $clear_screen_sub = sub { $lines_printed = 0; system("cls") }; 4225} 4226else { 4227 my $clear = `clear`; 4228 $clear_screen_sub = sub { $lines_printed = 0; print $clear }; 4229} 4230 4231# ########################################################################### 4232# Config storage. {{{2 4233# ########################################################################### 4234my %config = ( 4235 color => { 4236 val => $have_color, 4237 note => 'Whether to use terminal coloring', 4238 conf => 'ALL', 4239 pat => $bool_regex, 4240 }, 4241 cmd_filter => { 4242 val => 'Com_', 4243 note => 'Prefix for values in C mode', 4244 conf => [qw(C)], 4245 }, 4246 plugin_dir => { 4247 val => "$homepath/.innotop/plugins", 4248 note => 'Directory where plugins can be found', 4249 conf => 'ALL', 4250 }, 4251 show_percent => { 4252 val => 1, 4253 note => 'Show the % symbol after percentages', 4254 conf => 'ALL', 4255 pat => $bool_regex, 4256 }, 4257 skip_innodb => { 4258 val => 0, 4259 note => 'Disable SHOW INNODB STATUS', 4260 conf => 'ALL', 4261 pat => $bool_regex, 4262 }, 4263 S_func => { 4264 val => 's', 4265 note => 'What to display in S mode: graph, status, pivoted status', 4266 conf => [qw(S)], 4267 pat => qr/^[gsv]$/, 4268 }, 4269 cxn_timeout => { 4270 val => 28800, 4271 note => 'Connection timeout for keeping unused connections alive', 4272 conf => 'ALL', 4273 pat => $int_regex, 4274 }, 4275 graph_char => { 4276 val => '*', 4277 note => 'Character for drawing graphs', 4278 conf => [ qw(S) ], 4279 pat => qr/^.$/, 4280 }, 4281 show_cxn_errors_in_tbl => { 4282 val => 1, 4283 note => 'Whether to display connection errors as rows in the table', 4284 conf => 'ALL', 4285 pat => $bool_regex, 4286 }, 4287 hide_hdr => { 4288 val => 0, 4289 note => 'Whether to show column headers', 4290 conf => 'ALL', 4291 pat => $bool_regex, 4292 }, 4293 show_cxn_errors => { 4294 val => 1, 4295 note => 'Whether to print connection errors to STDOUT', 4296 conf => 'ALL', 4297 pat => $bool_regex, 4298 }, 4299 readonly => { 4300 val => 1, 4301 note => 'Whether the config file is read-only', 4302 conf => [ qw() ], 4303 pat => $bool_regex, 4304 }, 4305 global => { 4306 val => 1, 4307 note => 'Whether to show GLOBAL variables and status', 4308 conf => 'ALL', 4309 pat => $bool_regex, 4310 }, 4311 header_highlight => { 4312 val => 'bold', 4313 note => 'How to highlight table column headers', 4314 conf => 'ALL', 4315 pat => qr/^(?:bold|underline)$/, 4316 }, 4317 display_table_captions => { 4318 val => 1, 4319 note => 'Whether to put captions on tables', 4320 conf => 'ALL', 4321 pat => $bool_regex, 4322 }, 4323 charset => { 4324 val => 'ascii', 4325 note => 'What type of characters should be displayed in queries (ascii, unicode, none)', 4326 conf => 'ALL', 4327 pat => qr/^(?:ascii|unicode|none)$/, 4328 }, 4329 auto_wipe_dl => { 4330 val => 0, 4331 note => 'Whether to auto-wipe InnoDB deadlocks', 4332 conf => 'ALL', 4333 pat => $bool_regex, 4334 }, 4335 max_height => { 4336 val => 30, 4337 note => '[Win32] Max window height', 4338 conf => 'ALL', 4339 }, 4340 debug => { 4341 val => 0, 4342 pat => $bool_regex, 4343 note => 'Debug mode (more verbose errors, uses more memory)', 4344 conf => 'ALL', 4345 }, 4346 num_digits => { 4347 val => 2, 4348 pat => $int_regex, 4349 note => 'How many digits to show in fractional numbers and percents', 4350 conf => 'ALL', 4351 }, 4352 debugfile => { 4353 val => "$homepath/.innotop/core_dump", 4354 note => 'A debug file in case you are interested in error output', 4355 }, 4356 show_statusbar => { 4357 val => 1, 4358 pat => $bool_regex, 4359 note => 'Whether to show the status bar in the display', 4360 conf => 'ALL', 4361 }, 4362 mode => { 4363 val => "A", 4364 note => "Which mode to start in", 4365 cmdline => 1, 4366 }, 4367 status_inc => { 4368 val => 0, 4369 note => 'Whether to show raw or incremental values for status variables', 4370 pat => $bool_regex, 4371 }, 4372 interval => { 4373 val => 10, 4374 pat => qr/^(?:(?:\d*?[1-9]\d*(?:\.\d*)?)|(?:\d*\.\d*?[1-9]\d*))$/, 4375 note => "The interval at which the display will be refreshed. Fractional values allowed.", 4376 }, 4377 num_status_sets => { 4378 val => 9, 4379 pat => $int_regex, 4380 note => 'How many sets of STATUS and VARIABLES values to show', 4381 conf => [ qw(S) ], 4382 }, 4383 S_set => { 4384 val => 'general', 4385 pat => qr/^\w+$/, 4386 note => 'Which set of variables to display in S (Variables & Status) mode', 4387 conf => [ qw(S) ], 4388 }, 4389 timeformat => { 4390 val => '%Y-%m-%dT%H:%M:%S', 4391 pat => qr//, 4392 note => 'The strftime() timestamp format to write in -n mode', 4393 }, 4394 spark => { 4395 val => 10, 4396 note => 'How long to make status variable sparklines', 4397 conf => 'ALL', 4398 pat => $int_regex, 4399 }, 4400); 4401 4402# ########################################################################### 4403# Config file sections {{{2 4404# The configuration file is broken up into sections like a .ini file. This 4405# variable defines those sections and the subroutines responsible for reading 4406# and writing them. 4407# ########################################################################### 4408my %config_file_sections = ( 4409 plugins => { 4410 reader => \&load_config_plugins, 4411 writer => \&save_config_plugins, 4412 }, 4413 group_by => { 4414 reader => \&load_config_group_by, 4415 writer => \&save_config_group_by, 4416 }, 4417 filters => { 4418 reader => \&load_config_filters, 4419 writer => \&save_config_filters, 4420 }, 4421 active_filters => { 4422 reader => \&load_config_active_filters, 4423 writer => \&save_config_active_filters, 4424 }, 4425 visible_tables => { 4426 reader => \&load_config_visible_tables, 4427 writer => \&save_config_visible_tables, 4428 }, 4429 sort_cols => { 4430 reader => \&load_config_sort_cols, 4431 writer => \&save_config_sort_cols, 4432 }, 4433 active_columns => { 4434 reader => \&load_config_active_columns, 4435 writer => \&save_config_active_columns, 4436 }, 4437 tbl_meta => { 4438 reader => \&load_config_tbl_meta, 4439 writer => \&save_config_tbl_meta, 4440 }, 4441 general => { 4442 reader => \&load_config_config, 4443 writer => \&save_config_config, 4444 }, 4445 connections => { 4446 reader => \&load_config_connections, 4447 writer => \&save_config_connections, 4448 }, 4449 active_connections => { 4450 reader => \&load_config_active_connections, 4451 writer => \&save_config_active_connections, 4452 }, 4453 server_groups => { 4454 reader => \&load_config_server_groups, 4455 writer => \&save_config_server_groups, 4456 }, 4457 active_server_groups => { 4458 reader => \&load_config_active_server_groups, 4459 writer => \&save_config_active_server_groups, 4460 }, 4461 max_values_seen => { 4462 reader => \&load_config_mvs, 4463 writer => \&save_config_mvs, 4464 }, 4465 varsets => { 4466 reader => \&load_config_varsets, 4467 writer => \&save_config_varsets, 4468 }, 4469 colors => { 4470 reader => \&load_config_colors, 4471 writer => \&save_config_colors, 4472 }, 4473 stmt_sleep_times => { 4474 reader => \&load_config_stmt_sleep_times, 4475 writer => \&save_config_stmt_sleep_times, 4476 }, 4477); 4478 4479# Config file sections have some dependencies, so they have to be read/written in order. 4480my @ordered_config_file_sections = qw(general plugins filters active_filters tbl_meta 4481 connections active_connections server_groups active_server_groups max_values_seen 4482 active_columns sort_cols visible_tables varsets colors stmt_sleep_times 4483 group_by); 4484 4485# All events for which plugins may register themselves. Entries are arrayrefs. 4486my %event_listener_for = map { $_ => [] } 4487 qw( 4488 extract_values 4489 set_to_tbl_pre_filter set_to_tbl_pre_sort set_to_tbl_pre_group 4490 set_to_tbl_pre_colorize set_to_tbl_pre_transform set_to_tbl_pre_pivot 4491 set_to_tbl_pre_create set_to_tbl_post_create 4492 draw_screen 4493 ); 4494 4495# All variables to which plugins have access. 4496my %pluggable_vars = ( 4497 action_for => \%action_for, 4498 agg_funcs => \%agg_funcs, 4499 config => \%config, 4500 connections => \%connections, 4501 dbhs => \%dbhs, 4502 filters => \%filters, 4503 modes => \%modes, 4504 server_groups => \%server_groups, 4505 tbl_meta => \%tbl_meta, 4506 trans_funcs => \%trans_funcs, 4507 var_sets => \%var_sets, 4508); 4509 4510# ########################################################################### 4511# Contains logic to generate prepared statements for a given function for a 4512# given DB connection. Returns a $sth. 4513# ########################################################################### 4514my %stmt_maker_for = ( 4515 INDEX_STATISTICS => sub { 4516 my ( $dbh ) = @_; 4517 # Detect whether there's a Percona Server with INFORMATION_SCHEMA.INDEX_STATISTICS 4518 # and if not, just select nothing. 4519 my $sth; 4520 eval { # This can fail if the table doesn't exist, INFORMATION_SCHEMA doesn't exist, etc. 4521 my $cols = $dbh->selectall_arrayref(q{SHOW /*innotop*/ COLUMNS FROM INFORMATION_SCHEMA.INDEX_STATISTICS}); 4522 if ( @$cols ) { 4523 $sth = $dbh->prepare(q{SELECT /*innotop*/ * FROM INFORMATION_SCHEMA.INDEX_STATISTICS}); 4524 } 4525 }; 4526 $sth ||= $dbh->prepare(q{SELECT /*innotop*/ '' FROM DUAL WHERE 1 = 0}); 4527 return $sth; 4528 }, 4529 INDEX_TABLE_STATISTICS => sub { 4530 my ( $dbh ) = @_; 4531 # Detect whether there's a Percona Server with INFORMATION_SCHEMA.INDEX_STATISTICS 4532 # and if not, just select nothing. 4533 my $sth; 4534 eval { # This can fail if the table doesn't exist, INFORMATION_SCHEMA doesn't exist, etc. 4535 my $cols = $dbh->selectall_arrayref(q{SHOW /*innotop*/ COLUMNS FROM INFORMATION_SCHEMA.INDEX_STATISTICS}); 4536 if ( @$cols ) { 4537 $sth = $dbh->prepare(q{SELECT /*innotop*/ L.TABLE_SCHEMA, L.TABLE_NAME, } 4538 . q{SUM(L.ROWS_READ) AS ROWS_READ, SUM(R.ROWS_READ) AS ROWS_READ_FROM_INDEXES, } 4539 . q{SUM(L.ROWS_CHANGED) AS ROWS_CHANGED, } 4540 . q{SUM(L.ROWS_CHANGED_X_INDEXES) AS ROWS_CHANGED_X_INDEXES } 4541 . q{FROM INFORMATION_SCHEMA.TABLE_STATISTICS AS L LEFT JOIN INFORMATION_SCHEMA.INDEX_STATISTICS AS R } 4542 . q{USING(TABLE_SCHEMA, TABLE_NAME) GROUP BY L.TABLE_SCHEMA, L.TABLE_NAME}); 4543 } 4544 }; 4545 $sth ||= $dbh->prepare(q{SELECT /*innotop*/ '' FROM DUAL WHERE 1 = 0}); 4546 return $sth; 4547 }, 4548 INNODB_BLOCKED_BLOCKER => sub { 4549 my ( $dbh ) = @_; 4550 # Detect whether the server supports the I_S tables and if not, just select nothing. 4551 my $sth; 4552 eval { # This can fail if the table doesn't exist, INFORMATION_SCHEMA doesn't exist, etc. 4553 my $cols = $dbh->selectall_arrayref(q{SHOW /*innotop*/ COLUMNS FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS}); 4554 if ( @$cols ) { 4555 if ($dbh->{mysql_serverinfo} =~ /^5.1/) { 4556 $sth = $dbh->prepare(q{ 4557 SELECT /*innotop*/ 4558 r.trx_mysql_thread_id AS waiting_thread, 4559 r.trx_query AS waiting_query, 4560 "n/a" AS waiting_rows_modified, 4561 TIMESTAMPDIFF(SECOND, r.trx_started, NOW()) AS waiting_age, 4562 TIMESTAMPDIFF(SECOND, r.trx_wait_started, NOW()) AS waiting_wait_secs, 4563 rp.user AS waiting_user, 4564 rp.host AS waiting_host, 4565 rp.db AS waiting_db, 4566 b.trx_mysql_thread_id AS blocking_thread, 4567 b.trx_query AS blocking_query, 4568 "n/a" AS blocking_rows_modified, 4569 TIMESTAMPDIFF(SECOND, b.trx_started, NOW()) AS blocking_age, 4570 TIMESTAMPDIFF(SECOND, b.trx_wait_started, NOW()) AS blocking_wait_secs, 4571 bp.user AS blocking_user, 4572 bp.host AS blocking_host, 4573 bp.db AS blocking_db, 4574 CONCAT(bp.command, IF(bp.command = 'Sleep', CONCAT(' ', bp.time), '')) AS blocking_status, 4575 CONCAT(lock_mode, ' ', lock_type, ' ', lock_table, '(', lock_index, ')') AS lock_info 4576 FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS w 4577 JOIN INFORMATION_SCHEMA.INNODB_TRX b ON b.trx_id = w.blocking_trx_id 4578 JOIN INFORMATION_SCHEMA.INNODB_TRX r ON r.trx_id = w.requesting_trx_id 4579 JOIN INFORMATION_SCHEMA.INNODB_LOCKS l ON l.lock_id = w.requested_lock_id 4580 LEFT JOIN INFORMATION_SCHEMA.PROCESSLIST bp ON bp.id = b.trx_mysql_thread_id 4581 LEFT JOIN INFORMATION_SCHEMA.PROCESSLIST rp ON rp.id = r.trx_mysql_thread_id 4582 }); 4583 } else { 4584 $sth = $dbh->prepare(q{ 4585 SELECT /*innotop*/ 4586 r.trx_mysql_thread_id AS waiting_thread, 4587 r.trx_query AS waiting_query, 4588 r.trx_rows_modified AS waiting_rows_modified, 4589 TIMESTAMPDIFF(SECOND, r.trx_started, NOW()) AS waiting_age, 4590 TIMESTAMPDIFF(SECOND, r.trx_wait_started, NOW()) AS waiting_wait_secs, 4591 rp.user AS waiting_user, 4592 rp.host AS waiting_host, 4593 rp.db AS waiting_db, 4594 b.trx_mysql_thread_id AS blocking_thread, 4595 b.trx_query AS blocking_query, 4596 b.trx_rows_modified AS blocking_rows_modified, 4597 TIMESTAMPDIFF(SECOND, b.trx_started, NOW()) AS blocking_age, 4598 TIMESTAMPDIFF(SECOND, b.trx_wait_started, NOW()) AS blocking_wait_secs, 4599 bp.user AS blocking_user, 4600 bp.host AS blocking_host, 4601 bp.db AS blocking_db, 4602 CONCAT(bp.command, IF(bp.command = 'Sleep', CONCAT(' ', bp.time), '')) AS blocking_status, 4603 CONCAT(lock_mode, ' ', lock_type, ' ', lock_table, '(', lock_index, ')') AS lock_info 4604 FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS w 4605 JOIN INFORMATION_SCHEMA.INNODB_TRX b ON b.trx_id = w.blocking_trx_id 4606 JOIN INFORMATION_SCHEMA.INNODB_TRX r ON r.trx_id = w.requesting_trx_id 4607 JOIN INFORMATION_SCHEMA.INNODB_LOCKS l ON l.lock_id = w.requested_lock_id 4608 LEFT JOIN INFORMATION_SCHEMA.PROCESSLIST bp ON bp.id = b.trx_mysql_thread_id 4609 LEFT JOIN INFORMATION_SCHEMA.PROCESSLIST rp ON rp.id = r.trx_mysql_thread_id 4610 }); 4611 } 4612 } 4613 }; 4614 $sth ||= $dbh->prepare(q{SELECT /*innotop*/ '' FROM DUAL WHERE 1 = 0}); 4615 return $sth; 4616 }, 4617 INNODB_STATUS => sub { 4618 my ( $dbh ) = @_; 4619 return $dbh->prepare(version_ge( $dbh, '5.0.0' ) 4620 ? 'SHOW /*innotop*/ ENGINE INNODB STATUS' 4621 : 'SHOW /*innotop*/ INNODB STATUS'); 4622 }, 4623 SHOW_VARIABLES => sub { 4624 my ( $dbh ) = @_; 4625 return $dbh->prepare($config{global}->{val} && version_ge( $dbh, '4.0.3' ) 4626 ? 'SHOW /*innotop*/ GLOBAL VARIABLES' 4627 : 'SHOW /*innotop*/ VARIABLES'); 4628 }, 4629 SHOW_STATUS => sub { 4630 my ( $dbh ) = @_; 4631 return $dbh->prepare($config{global}->{val} && version_ge( $dbh, '5.0.2' ) 4632 ? 'SHOW /*innotop*/ GLOBAL STATUS' 4633 : 'SHOW /*innotop*/ STATUS'); 4634 }, 4635 KILL_QUERY => sub { 4636 my ( $dbh ) = @_; 4637 return $dbh->prepare(version_ge( $dbh, '5.0.0' ) 4638 ? 'KILL /*innotop*/ QUERY ?' 4639 : 'KILL /*innotop*/ ?'); 4640 }, 4641 SHOW_MASTER_LOGS => sub { 4642 my ( $dbh ) = @_; 4643 return $dbh->prepare('SHOW /*innotop*/ MASTER LOGS'); 4644 }, 4645 SHOW_MASTER_STATUS => sub { 4646 my ( $dbh ) = @_; 4647 return $dbh->prepare('SHOW /*innotop*/ MASTER STATUS'); 4648 }, 4649 SHOW_SLAVE_STATUS => sub { 4650 my ( $dbh ) = @_; 4651 return $dbh->prepare('SHOW /*innotop*/ SLAVE STATUS'); 4652 }, 4653 GET_CHANNELS => sub { 4654 my ( $dbh ) = @_; 4655 if (version_ge( $dbh, '10.0.2' )) { 4656 # Not supported or MariaDB specific way to get channel list. 4657 return $dbh->prepare('select "no_channels"'); 4658 } elsif ( ( $dbh->{mysql_serverinfo} =~ /^5.7/ ) || ( $dbh->{mysql_serverinfo} =~ /^8/ ) ) { 4659 return $dbh->prepare('select CHANNEL_NAME from performance_schema.replication_applier_status where CHANNEL_NAME regexp "^[a-zA-Z].*";'); 4660 } else { 4661 return $dbh->prepare('select "no_channels"'); 4662 } 4663 }, 4664 KILL_CONNECTION => sub { 4665 my ( $dbh ) = @_; 4666 return $dbh->prepare(version_ge( $dbh, '5.0.0' ) 4667 ? 'KILL /*innotop*/ CONNECTION ?' 4668 : 'KILL /*innotop*/ ?'); 4669 }, 4670 OPEN_TABLES => sub { 4671 my ( $dbh ) = @_; 4672 return version_ge($dbh, '4.0.0') 4673 ? $dbh->prepare('SHOW /*innotop*/ OPEN TABLES') 4674 : undef; 4675 }, 4676 PROCESSLIST => sub { 4677 my ( $dbh ) = @_; 4678 # In newer versions of the server, use INFORMATION_SCHEMA table if it exists, 4679 # and use the TIME_MS column (in Percona Server) if that exists. 4680 my $sth; 4681 eval { # This can fail if the table doesn't exist, INFORMATION_SCHEMA doesn't exist, etc. 4682 my $cols = $dbh->selectall_arrayref(q{SHOW /*innotop*/ COLUMNS FROM INFORMATION_SCHEMA.PROCESSLIST LIKE 'TIME_MS'}); 4683 if ( @$cols ) { # The TIME_MS colum exists 4684 $sth = $dbh->prepare(q{SELECT /*innotop*/ ID, USER, HOST, DB, COMMAND, CASE WHEN TIME_MS/1000 > 365*86400 THEN TIME ELSE TIME_MS/1000 END AS TIME, STATE, INFO FROM INFORMATION_SCHEMA.PROCESSLIST}); 4685 } 4686 }; 4687 $sth ||= $dbh->prepare('SHOW /*innotop*/ FULL PROCESSLIST'); 4688 return $sth; 4689 }, 4690 PROCESSLIST_NO_IS => sub { 4691 my ( $dbh ) = @_; 4692 # We do not use INFORMATION_SCHEMA table because it doesn't show slave 4693 # SQL statements. http://bugs.mysql.com/66401 4694 my $sth = $dbh->prepare('SHOW /*innotop*/ FULL PROCESSLIST'); 4695 return $sth; 4696 }, 4697 TABLE_STATISTICS => sub { 4698 my ( $dbh ) = @_; 4699 # Detect whether there's a Percona Server with INFORMATION_SCHEMA.TABLE_STATISTICS 4700 # and if not, just select nothing. 4701 my $sth; 4702 eval { # This can fail if the table doesn't exist, INFORMATION_SCHEMA doesn't exist, etc. 4703 my $cols = $dbh->selectall_arrayref(q{SHOW /*innotop*/ COLUMNS FROM INFORMATION_SCHEMA.TABLE_STATISTICS}); 4704 if ( @$cols ) { 4705 $sth = $dbh->prepare(q{SELECT /*innotop*/ * FROM INFORMATION_SCHEMA.TABLE_STATISTICS}); 4706 } 4707 }; 4708 $sth ||= $dbh->prepare(q{SELECT /*innotop*/ '' FROM DUAL WHERE 1 = 0}); 4709 return $sth; 4710 }, 4711); 4712 4713# Plugins! 4714my %plugins = ( 4715); 4716 4717# ########################################################################### 4718# Run the program {{{1 4719# ########################################################################### 4720sub main { 4721 # This config variable is only useful for MS Windows because its terminal 4722 # can't tell how tall it is. 4723 if ( !$windows ) { 4724 delete $config{max_height}; 4725 } 4726 4727 # Try to lower my priority. 4728 eval { setpriority(0, 0, getpriority(0, 0) + 10); }; 4729 4730 # Print stuff to the screen immediately, don't wait for a newline. 4731 $OUTPUT_AUTOFLUSH = 1; 4732 4733 # Clear the screen and load the configuration. 4734 $clear_screen_sub->(); 4735 load_config(); 4736 4737 # Override config variables with command-line options 4738 my %cmdline = 4739 map { $_->{c} => $opts{$_->{k}} } 4740 grep { exists $_->{c} && exists $opts{$_->{k}} } 4741 @opt_spec; 4742 4743 foreach my $name (keys %cmdline) { 4744 next if not defined $cmdline{$name}; 4745 my $val = $cmdline{$name}; 4746 if ( exists($config{$name}) and (!$config{$name}->{pat} or $val =~ m/$config{$name}->{pat}/ )) { 4747 $config{$name}->{val} = $val; 4748 } 4749 } 4750 4751 post_process_tbl_meta(); 4752 4753 # Make sure no changes are written to config file in non-interactive mode. 4754 if ( $opts{n} ) { 4755 $config{readonly}->{val} = 1; 4756 } 4757 4758 eval { 4759 4760 # Open the file for InnoDB status 4761 if ( @ARGV ) { 4762 my $filename = shift @ARGV; 4763 open $file, "<", $filename 4764 or die "Cannot open '$filename': $OS_ERROR"; 4765 } 4766 4767 # In certain modes we might have to collect data for two cycles 4768 # before printing anything out, so we need to bump up the count one. 4769 if ( $opts{n} && $opts{count} && $config{status_inc}->{val} 4770 && $config{mode}->{val} =~ m/[S]/ ) 4771 { 4772 $opts{count}++; 4773 } 4774 4775 while (++$clock) { 4776 4777 my $mode = $config{mode}->{val} || 'Q'; 4778 if ( !$modes{$mode} ) { 4779 die "Mode '$mode' doesn't exist; try one of these:\n" 4780 . join("\n", map { " $_ $modes{$_}->{hdr}" } sort keys %modes) 4781 . "\n"; 4782 } 4783 4784 if ( !$opts{n} ) { 4785 @last_term_size = @this_term_size; 4786 @this_term_size = Term::ReadKey::GetTerminalSize(\*STDOUT); 4787 if ( $windows ) { 4788 $this_term_size[0]--; 4789 $this_term_size[1] 4790 = min($this_term_size[1], $config{max_height}->{val}); 4791 } 4792 die("Can't read terminal size") unless @this_term_size; 4793 } 4794 4795 # If there's no connection to a database server, we need to fix that... 4796 if ( !%connections ) { 4797 print "You have not defined any database connections.\n\n"; 4798 add_new_dsn(); 4799 } 4800 4801 # See whether there are any connections defined for this mode. If there's only one 4802 # connection total, assume the user wants to just use innotop for a single server 4803 # and don't ask which server to connect to. Also, if we're monitoring from a file, 4804 # we just use the first connection. 4805 if ( !get_connections() ) { 4806 if ( $file || 1 == scalar keys %connections ) { 4807 $modes{$config{mode}->{val}}->{connections} = [ keys %connections ]; 4808 } 4809 else { 4810 choose_connections(); 4811 } 4812 } 4813 4814 # Term::ReadLine might have re-set $OUTPUT_AUTOFLUSH. 4815 $OUTPUT_AUTOFLUSH = 1; 4816 4817 # Prune old data 4818 my $sets = $config{num_status_sets}->{val}; 4819 foreach my $store ( values %vars ) { 4820 delete @{$store}{ grep { $_ < $clock - $sets } keys %$store }; 4821 } 4822 %info_gotten = (); 4823 4824 # Call the subroutine to display this mode. 4825 $modes{$mode}->{display_sub}->(); 4826 4827 # It may be time to quit now. 4828 if ( $opts{count} && $clock >= $opts{count} ) { 4829 finish(); 4830 } 4831 4832 # RECON: Try to reconnect failed connections, while the user sees no lag. 4833 foreach my $cxn ( grep { $dbhs{$_}->{failed} } keys %dbhs ) { 4834 eval { connect_to_db($cxn); }; # Ignore errors entirely here. 4835 } 4836 4837 # Wait for a bit. 4838 if ( $opts{n} ) { 4839 sleep($config{interval}->{val}); 4840 } 4841 else { 4842 ReadMode('cbreak'); 4843 $char = ReadKey($config{interval}->{val}); 4844 ReadMode('normal'); 4845 } 4846 4847 # Handle whatever action the key indicates. 4848 do_key_action(); 4849 4850 } 4851 }; 4852 if ( $EVAL_ERROR ) { 4853 core_dump( $EVAL_ERROR ); 4854 } 4855 finish(); 4856} 4857main() unless caller(); # make me testable! 4858 4859# Subroutines {{{1 4860# Mode functions{{{2 4861# switch_mode {{{3 4862sub switch_mode { 4863 my $mode = shift; 4864 $config{mode}->{val} = $mode; 4865} 4866 4867# Prompting functions {{{2 4868# prompt_list {{{3 4869# Prompts the user for a value, given a question, initial value, 4870# a completion function and a hashref of hints. 4871sub prompt_list { 4872 die "Can't call in non-interactive mode" if $opts{n}; 4873 my ( $question, $init, $completion, $hints ) = @_; 4874 if ( $hints ) { 4875 # Figure out how wide the table will be 4876 my $max_name = max(map { length($_) } keys %$hints ); 4877 $max_name ||= 0; 4878 $max_name += 3; 4879 my @meta_rows = create_table2( 4880 [ sort keys %$hints ], 4881 { map { $_ => $_ } keys %$hints }, 4882 { map { $_ => trunc($hints->{$_}, $this_term_size[0] - $max_name) } keys %$hints }, 4883 { sep => ' ' }); 4884 if (@meta_rows > 10) { 4885 # Try to split and stack the meta rows next to each other 4886 my $split = int(@meta_rows / 2); 4887 @meta_rows = stack_next( 4888 [@meta_rows[0..$split - 1]], 4889 [@meta_rows[$split..$#meta_rows]], 4890 { pad => ' | '}, 4891 ); 4892 } 4893 print join( "\n", 4894 '', 4895 map { ref $_ ? colored(@$_) : $_ } create_caption('Choose from', @meta_rows), ''), 4896 "\n"; 4897 } 4898 $term->Attribs->{completion_function} = $completion; 4899 my $answer = $term->readline("$question: ", $init); 4900 $OUTPUT_AUTOFLUSH = 1; 4901 $answer = '' if !defined($answer); 4902 $answer =~ s/\s+$//; 4903 return $answer; 4904} 4905 4906# prompt {{{3 4907# Prints out a prompt and reads from the keyboard, then validates with the 4908# validation regex until the input is correct. 4909sub prompt { 4910 die "Can't call in non-interactive mode" if $opts{n}; 4911 my ( $prompt, $regex, $init, $completion ) = @_; 4912 my $response; 4913 my $success = 0; 4914 do { 4915 if ( $completion ) { 4916 $term->Attribs->{completion_function} = $completion; 4917 } 4918 $response = $term->readline("$prompt: ", $init); 4919 if ( $regex && $response !~ m/$regex/ ) { 4920 print "Invalid response.\n\n"; 4921 } 4922 else { 4923 $success = 1; 4924 } 4925 } while ( !$success ); 4926 $OUTPUT_AUTOFLUSH = 1; 4927 $response =~ s/\s+$//; 4928 return $response; 4929} 4930 4931# prompt_noecho {{{3 4932# Unfortunately, suppressing echo with Term::ReadLine isn't reliable; the user might not 4933# have that library, or it might not support that feature. 4934sub prompt_noecho { 4935 my ( $prompt ) = @_; 4936 print colored("$prompt: ", 'underline'); 4937 my $response; 4938 ReadMode('noecho'); 4939 $response = <STDIN>; 4940 chomp($response); 4941 ReadMode('normal'); 4942 return $response; 4943} 4944 4945# noecho_password {{{3 4946# read password for command line parameters with noecho 4947sub noecho_password { 4948 my $prompt = shift @_; 4949 local $OUTPUT_AUTOFLUSH = 1; 4950 my $response; 4951 eval { 4952 if ( $windows ) { 4953 require Win32::Console::ANSI; 4954 } 4955 require Term::ANSIColor; 4956 import Term::ANSIColor qw(colored); 4957 $response = prompt_noecho($prompt); 4958 print "\n" or die 4959 "Cannot print: $OS_ERROR"; 4960 }; 4961 4962 if ( $EVAL_ERROR ) { 4963 die "Cannot read respose; is Term::ReadKey installed? $EVAL_ERROR"; 4964 } 4965 return $response; 4966} 4967 4968# do_key_action {{{3 4969# Depending on whether a key was read, do something. Keys have certain 4970# actions defined in lookup tables. Each mode may have its own lookup table, 4971# which trumps the global table -- so keys can be context-sensitive. The key 4972# may be read and written in a subroutine, so it's a global. 4973sub do_key_action { 4974 if ( defined $char ) { 4975 my $mode = $config{mode}->{val}; 4976 my $action 4977 = defined($modes{$mode}->{action_for}->{$char}) ? $modes{$mode}->{action_for}->{$char}->{action} 4978 : defined($action_for{$char}) ? $action_for{$char}->{action} 4979 : sub{}; 4980 $action->(); 4981 } 4982} 4983 4984# pause {{{3 4985sub pause { 4986 die "Can't call in non-interactive mode" if $opts{n}; 4987 my $msg = shift; 4988 print defined($msg) ? "\n$msg" : "\nPress any key to continue"; 4989 ReadMode('cbreak'); 4990 my $char = ReadKey(0); 4991 ReadMode('normal'); 4992 return $char; 4993} 4994 4995# reverse_sort {{{3 4996sub reverse_sort { 4997 my $tbl = shift; 4998 $tbl_meta{$tbl}->{sort_dir} *= -1; 4999} 5000 5001# select_cxn {{{3 5002# Selects connection(s). If the mode (or argument list) has only one, returns 5003# it without prompt. 5004sub select_cxn { 5005 my ( $prompt, @cxns ) = @_; 5006 if ( !@cxns ) { 5007 @cxns = get_connections(); 5008 } 5009 if ( @cxns == 1 ) { 5010 return $cxns[0]; 5011 } 5012 my $choices = prompt_list( 5013 $prompt, 5014 $cxns[0], 5015 sub{ return @cxns }, 5016 { map { $_ => $connections{$_}->{dsn} } @cxns }); 5017 my @result = unique(grep { my $a = $_; grep { $_ eq $a } @cxns } split(/\s+/, $choices)); 5018 return @result; 5019} 5020 5021# kill_query {{{3 5022# Kills a connection, or on new versions, optionally a query but not connection. 5023sub kill_query { 5024 my ( $q_or_c ) = @_; 5025 5026 my $info = choose_thread( 5027 sub { 1 }, 5028 'Select a thread to kill the ' . $q_or_c, 5029 ); 5030 return unless $info; 5031 my $distill = distill($info->{query} || ''); 5032 $distill = " running '$distill'" if $distill; 5033 return unless pause("Kill $info->{id} (" 5034 . ($info->{user} || '') 5035 . '@' 5036 . ($info->{host} || '') 5037 . ")$distill ? ") =~ m/y/i; 5038 5039 eval { 5040 do_stmt($info->{cxn}, $q_or_c eq 'QUERY' ? 'KILL_QUERY' : 'KILL_CONNECTION', $info->{id} ); 5041 }; 5042 5043 if ( $EVAL_ERROR ) { 5044 print "\nError: $EVAL_ERROR"; 5045 pause(); 5046 } 5047} 5048 5049# set_display_precision {{{3 5050sub set_display_precision { 5051 my $dir = shift; 5052 $config{num_digits}->{val} = min(9, max(0, $config{num_digits}->{val} + $dir)); 5053} 5054 5055sub toggle_visible_table { 5056 my ( $mode, $table ) = @_; 5057 my $visible = $modes{$mode}->{visible_tables}; 5058 if ( grep { $_ eq $table } @$visible ) { 5059 $modes{$mode}->{visible_tables} = [ grep { $_ ne $table } @$visible ]; 5060 } 5061 else { 5062 unshift @$visible, $table; 5063 } 5064 $modes{$mode}->{cust}->{visible_tables} = 1; 5065} 5066 5067# toggle_filter{{{3 5068sub toggle_filter { 5069 my ( $tbl, $filter ) = @_; 5070 my $filters = $tbl_meta{$tbl}->{filters}; 5071 if ( grep { $_ eq $filter } @$filters ) { 5072 $tbl_meta{$tbl}->{filters} = [ grep { $_ ne $filter } @$filters ]; 5073 } 5074 else { 5075 push @$filters, $filter; 5076 } 5077 $tbl_meta{$tbl}->{cust}->{filters} = 1; 5078} 5079 5080# toggle_config {{{3 5081sub toggle_config { 5082 my ( $key ) = @_; 5083 $config{$key}->{val} ^= 1; 5084} 5085 5086# create_deadlock {{{3 5087sub create_deadlock { 5088 $clear_screen_sub->(); 5089 5090 print "This function will deliberately cause a small deadlock, " 5091 . "clearing deadlock information from the InnoDB monitor.\n\n"; 5092 5093 my $answer = prompt("Are you sure you want to proceed? Say 'y' if you do"); 5094 return 0 unless $answer eq 'y'; 5095 5096 my ( $cxn ) = select_cxn('Clear on which server? '); 5097 return unless $cxn && exists($connections{$cxn}); 5098 5099 clear_deadlock($cxn); 5100} 5101 5102# deadlock_thread {{{3 5103sub deadlock_thread { 5104 my ( $id, $tbl, $cxn ) = @_; 5105 5106 eval { 5107 my $dbh = get_new_db_connection($cxn, 1); 5108 5109 # disable binary logging for this session 5110 $dbh->do("set SQL_LOG_BIN=0"); 5111 5112 my @stmts = ( 5113 "set transaction isolation level serializable", 5114 (version_ge($dbh, '4.0.11') ? "start transaction" : 'begin'), 5115 "select * from $tbl where a = $id", 5116 "update $tbl set a = $id where a <> $id", 5117 ); 5118 5119 foreach my $stmt (@stmts[0..2]) { 5120 $dbh->do($stmt); 5121 } 5122 sleep(1 + $id); 5123 $dbh->do($stmts[-1]); 5124 }; 5125 if ( $EVAL_ERROR ) { 5126 if ( $EVAL_ERROR !~ m/Deadlock found/ ) { 5127 die $EVAL_ERROR; 5128 } 5129 } 5130 exit(0); 5131} 5132 5133# Purges unused binlogs on the master, up to but not including the latest log. 5134# TODO: guess which connections are slaves of a given master. 5135sub purge_master_logs { 5136 my @cxns = get_connections(); 5137 5138 get_master_slave_status(@cxns); 5139 5140 # Toss out the rows that don't have master/slave status... 5141 my @vars = 5142 grep { $_ && ($_->{file} || $_->{master_host}) } 5143 map { $vars{$_}->{$clock} } @cxns; 5144 @cxns = map { $_->{cxn} } @vars; 5145 5146 # Figure out which master to purge ons. 5147 my @masters = map { $_->{cxn} } grep { $_->{file} } @vars; 5148 my ( $master ) = select_cxn('Which master?', @masters ); 5149 return unless $master; 5150 my ($master_status) = grep { $_->{cxn} eq $master } @vars; 5151 5152 # Figure out the result order (not lexical order) of master logs. 5153 my @master_logs = get_master_logs($master); 5154 my $i = 0; 5155 my %master_logs = map { $_->{log_name} => $i++ } @master_logs; 5156 5157 # Ask which slave(s) are reading from this master. 5158 my @slave_status = grep { $_->{master_host} } @vars; 5159 my @slaves = map { $_->{cxn} } @slave_status; 5160 @slaves = select_cxn("Which slaves are reading from $master?", @slaves); 5161 @slave_status = grep { my $item = $_; grep { $item->{cxn} eq $_ } @slaves } @slave_status; 5162 return unless @slave_status; 5163 5164 # Find the minimum binary log in use. 5165 my $min_log = min(map { $master_logs{$_->{master_log_file}} } @slave_status); 5166 my $log_name = $master_logs[$min_log]->{log_name}; 5167 5168 my $stmt = "PURGE MASTER LOGS TO '$log_name'"; 5169 send_cmd_to_servers($stmt, 0, 'PURGE {MASTER | BINARY} LOGS {TO "log_name" | BEFORE "date"}', [$master]); 5170} 5171 5172sub send_cmd_to_servers { 5173 my ( $cmd, $all, $hint, $cxns ) = @_; 5174 if ( $all ) { 5175 @$cxns = get_connections(); 5176 } 5177 elsif ( !@$cxns ) { 5178 @$cxns = select_cxn('Which servers?', @$cxns); 5179 } 5180 if ( $hint ) { 5181 print "\nHint: $hint\n"; 5182 } 5183 $cmd = prompt('Command to send', undef, $cmd); 5184 foreach my $cxn ( @$cxns ) { 5185 eval { 5186 my $sth = do_query($cxn, $cmd); 5187 }; 5188 if ( $EVAL_ERROR ) { 5189 print "Error from $cxn: $EVAL_ERROR\n"; 5190 } 5191 else { 5192 print "Success on $cxn\n"; 5193 } 5194 } 5195 pause(); 5196} 5197 5198# Display functions {{{2 5199 5200sub set_s_mode { 5201 my ( $func ) = @_; 5202 $clear_screen_sub->(); 5203 $config{S_func}->{val} = $func; 5204} 5205 5206# start_S_mode {{{3 5207sub start_S_mode { 5208 $clear_screen_sub->(); 5209 switch_mode('S'); 5210} 5211 5212# display_A {{{3 5213sub display_A { 5214 my @display_lines; 5215 my @cxns = get_connections(); 5216 get_processlist_stats(@cxns); 5217 get_status_info(@cxns); 5218 get_master_slave_status(@cxns); 5219 my @visible = get_visible_tables(); 5220 my %wanted = map { $_ => 1 } @visible; 5221 my @health_dashboard; 5222 my %rows_for = ( 5223 health_dashboard => \@health_dashboard, 5224 ); 5225 5226 foreach my $cxn ( @cxns ) { 5227 # Get the status variables 5228 my $set = $vars{$cxn}->{$clock}; 5229 my $pre = $vars{$cxn}->{$clock-1} || $set; 5230 my $hash = extract_values($set, $set, $pre, 'health_dashboard'); 5231 # Make QPS and Miss show now, not overall. 5232 if ( exists $vars{$cxn}->{$clock - 1} ) { 5233 my $inc = inc(0, $cxn); 5234 my $hash2 = extract_values($inc, $set, $pre, 'health_dashboard'); 5235 map { $hash->{$_} = $hash2->{$_} } qw(qps miss_rate); 5236 } 5237 push @health_dashboard, $hash; 5238 } 5239 5240 my $first_table = 0; 5241 foreach my $tbl ( @visible ) { 5242 push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl); 5243 push @display_lines, get_cxn_errors(@cxns) 5244 if ( $config{debug}->{val} || !$first_table++ ); 5245 } 5246 5247 draw_screen(\@display_lines); 5248} 5249 5250# display_B {{{3 5251sub display_B { 5252 my @display_lines; 5253 my @cxns = get_connections(); 5254 get_status_info(@cxns); 5255 get_innodb_status(\@cxns); 5256 5257 my @buffer_pool; 5258 my @page_statistics; 5259 my @insert_buffers; 5260 my @adaptive_hash_index; 5261 my %rows_for = ( 5262 buffer_pool => \@buffer_pool, 5263 page_statistics => \@page_statistics, 5264 insert_buffers => \@insert_buffers, 5265 adaptive_hash_index => \@adaptive_hash_index, 5266 ); 5267 5268 my @visible = get_visible_tables(); 5269 my %wanted = map { $_ => 1 } @visible; 5270 5271 foreach my $cxn ( @cxns ) { 5272 my $set = $vars{$cxn}->{$clock}; 5273 my $pre = $vars{$cxn}->{$clock-1} || $set; 5274 5275 if ( $set->{IB_bp_complete} ) { 5276 if ( $wanted{buffer_pool} ) { 5277 push @buffer_pool, extract_values($set, $set, $pre, 'buffer_pool'); 5278 } 5279 if ( $wanted{page_statistics} ) { 5280 push @page_statistics, extract_values($set, $set, $pre, 'page_statistics'); 5281 } 5282 } 5283 if ( $set->{IB_ib_complete} ) { 5284 if ( $wanted{insert_buffers} ) { 5285 push @insert_buffers, extract_values( 5286 $config{status_inc}->{val} ? inc(0, $cxn) : $set, $set, $pre, 5287 'insert_buffers'); 5288 } 5289 if ( $wanted{adaptive_hash_index} ) { 5290 push @adaptive_hash_index, extract_values($set, $set, $pre, 'adaptive_hash_index'); 5291 } 5292 } 5293 } 5294 5295 my $first_table = 0; 5296 foreach my $tbl ( @visible ) { 5297 push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl); 5298 push @display_lines, get_cxn_errors(@cxns) 5299 if ( $config{debug}->{val} || !$first_table++ ); 5300 } 5301 5302 draw_screen(\@display_lines); 5303} 5304 5305# display_C {{{3 5306sub display_C { 5307 my @display_lines; 5308 my @cxns = get_connections(); 5309 get_status_info(@cxns); 5310 5311 my @cmd_summary; 5312 my %rows_for = ( 5313 cmd_summary => \@cmd_summary, 5314 ); 5315 5316 my @visible = get_visible_tables(); 5317 my %wanted = map { $_ => 1 } @visible; 5318 5319 # For now, I'm manually pulling these variables out and pivoting. Eventually a SQL-ish 5320 # dialect should let me join a table to a grouped and pivoted table and do this more easily. 5321 # TODO: make it so. 5322 my $prefix = qr/^$config{cmd_filter}->{val}/; # TODO: this is a total hack 5323 my @values; 5324 my ($total, $last_total) = (0, 0); 5325 foreach my $cxn ( @cxns ) { 5326 my $set = $vars{$cxn}->{$clock}; 5327 my $pre = $vars{$cxn}->{$clock-1} || $set; 5328 foreach my $key ( keys %$set ) { 5329 next unless $key =~ m/$prefix/i; 5330 my $val = $set->{$key}; 5331 next unless defined $val && $val =~ m/^\d+$/; 5332 my $last_val = $val - ($pre->{$key} || 0); 5333 $total += $val; 5334 $last_total += $last_val; 5335 push @values, { 5336 name => $key, 5337 value => $val, 5338 last_value => $last_val, 5339 }; 5340 } 5341 } 5342 5343 # Add aggregation and turn into a real set TODO: total hack 5344 if ( $wanted{cmd_summary} ) { 5345 foreach my $value ( @values ) { 5346 @{$value}{qw(total last_total)} = ($total, $last_total); 5347 push @cmd_summary, extract_values($value, $value, $value, 'cmd_summary'); 5348 } 5349 } 5350 5351 my $first_table = 0; 5352 foreach my $tbl ( @visible ) { 5353 push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl); 5354 push @display_lines, get_cxn_errors(@cxns) 5355 if ( $config{debug}->{val} || !$first_table++ ); 5356 } 5357 5358 draw_screen(\@display_lines); 5359} 5360 5361# display_D {{{3 5362sub display_D { 5363 my @display_lines; 5364 my @cxns = get_connections(); 5365 get_status_info(@cxns); 5366 get_innodb_status(\@cxns); 5367 5368 my @deadlock_transactions; 5369 my @deadlock_locks; 5370 my %rows_for = ( 5371 deadlock_transactions => \@deadlock_transactions, 5372 deadlock_locks => \@deadlock_locks, 5373 ); 5374 5375 my @visible = get_visible_tables(); 5376 my %wanted = map { $_ => 1 } @visible; 5377 5378 foreach my $cxn ( @cxns ) { 5379 my $innodb_status = $vars{$cxn}->{$clock}; 5380 my $prev_status = $vars{$cxn}->{$clock-1} || $innodb_status; 5381 5382 if ( $innodb_status->{IB_dl_timestring} ) { 5383 5384 my $victim = $innodb_status->{IB_dl_rolled_back} || 0; 5385 5386 if ( %wanted ) { 5387 foreach my $txn_id ( keys %{$innodb_status->{IB_dl_txns}} ) { 5388 my $txn = $innodb_status->{IB_dl_txns}->{$txn_id}; 5389 my $pre = $prev_status->{IB_dl_txns}->{$txn_id} || $txn; 5390 5391 if ( $wanted{deadlock_transactions} ) { 5392 my $hash = extract_values($txn->{tx}, $txn->{tx}, $pre->{tx}, 'deadlock_transactions'); 5393 $hash->{cxn} = $cxn; 5394 $hash->{dl_txn_num} = $txn_id; 5395 $hash->{victim} = $txn_id == $victim ? 'Yes' : 'No'; 5396 $hash->{timestring} = $innodb_status->{IB_dl_timestring}; 5397 $hash->{truncates} = $innodb_status->{IB_dl_complete} ? 'No' : 'Yes'; 5398 push @deadlock_transactions, $hash; 5399 } 5400 5401 if ( $wanted{deadlock_locks} ) { 5402 foreach my $lock ( @{$txn->{locks}} ) { 5403 my $hash = extract_values($lock, $lock, $lock, 'deadlock_locks'); 5404 $hash->{dl_txn_num} = $txn_id; 5405 $hash->{cxn} = $cxn; 5406 $hash->{mysql_thread_id} = $txn->{tx}->{mysql_thread_id}; 5407 push @deadlock_locks, $hash; 5408 } 5409 } 5410 5411 } 5412 } 5413 } 5414 } 5415 5416 my $first_table = 0; 5417 foreach my $tbl ( @visible ) { 5418 push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl); 5419 push @display_lines, get_cxn_errors(@cxns) 5420 if ( $config{debug}->{val} || !$first_table++ ); 5421 } 5422 5423 draw_screen(\@display_lines); 5424} 5425 5426# display_F {{{3 5427sub display_F { 5428 my @display_lines; 5429 my ( $cxn ) = get_connections(); 5430 get_status_info($cxn); 5431 get_innodb_status([$cxn]); 5432 my $innodb_status = $vars{$cxn}->{$clock}; 5433 5434 if ( $innodb_status->{IB_fk_timestring} ) { 5435 5436 push @display_lines, 'Reason: ' . ($innodb_status->{IB_fk_reason} || 'unknown'); 5437 5438 # Display FK errors caused by invalid DML. 5439 if ( $innodb_status->{IB_fk_txn} ) { 5440 my $txn = $innodb_status->{IB_fk_txn}; 5441 push @display_lines, 5442 '', 5443 "User $txn->{user} from $txn->{hostname}, thread $txn->{mysql_thread_id} was executing:", 5444 '', no_ctrl_char($txn->{query_text}); 5445 } 5446 5447 my @fk_table = create_table2( 5448 $tbl_meta{fk_error}->{visible}, 5449 meta_to_hdr('fk_error'), 5450 extract_values($innodb_status, $innodb_status, $innodb_status, 'fk_error'), 5451 { just => '-', sep => ' '}); 5452 push @display_lines, '', @fk_table; 5453 5454 } 5455 else { 5456 push @display_lines, '', 'No foreign key error data.'; 5457 } 5458 draw_screen(\@display_lines, { raw => 1 } ); 5459} 5460 5461# display_I {{{3 5462sub display_I { 5463 my @display_lines; 5464 my @cxns = get_connections(); 5465 get_status_info(@cxns); 5466 get_innodb_status(\@cxns); 5467 5468 my @io_threads; 5469 my @pending_io; 5470 my @file_io_misc; 5471 my @log_statistics; 5472 my %rows_for = ( 5473 io_threads => \@io_threads, 5474 pending_io => \@pending_io, 5475 file_io_misc => \@file_io_misc, 5476 log_statistics => \@log_statistics, 5477 ); 5478 5479 my @visible = get_visible_tables(); 5480 my %wanted = map { $_ => 1 } @visible; 5481 5482 foreach my $cxn ( @cxns ) { 5483 my $set = $vars{$cxn}->{$clock}; 5484 my $pre = $vars{$cxn}->{$clock-1} || $set; 5485 5486 if ( $set->{IB_io_complete} ) { 5487 if ( $wanted{io_threads} ) { 5488 my $cur_threads = $set->{IB_io_threads}; 5489 my $pre_threads = $pre->{IB_io_threads} || $cur_threads; 5490 foreach my $key ( sort keys %$cur_threads ) { 5491 my $cur_thd = $cur_threads->{$key}; 5492 my $pre_thd = $pre_threads->{$key} || $cur_thd; 5493 my $hash = extract_values($cur_thd, $cur_thd, $pre_thd, 'io_threads'); 5494 $hash->{cxn} = $cxn; 5495 push @io_threads, $hash; 5496 } 5497 } 5498 if ( $wanted{pending_io} ) { 5499 push @pending_io, extract_values($set, $set, $pre, 'pending_io'); 5500 } 5501 if ( $wanted{file_io_misc} ) { 5502 push @file_io_misc, extract_values( 5503 $config{status_inc}->{val} ? inc(0, $cxn) : $set, 5504 $set, $pre, 'file_io_misc'); 5505 } 5506 } 5507 if ( $set->{IB_lg_complete} && $wanted{log_statistics} ) { 5508 push @log_statistics, extract_values($set, $set, $pre, 'log_statistics'); 5509 } 5510 } 5511 5512 my $first_table = 0; 5513 foreach my $tbl ( @visible ) { 5514 push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl); 5515 push @display_lines, get_cxn_errors(@cxns) 5516 if ( $config{debug}->{val} || !$first_table++ ); 5517 } 5518 5519 draw_screen(\@display_lines); 5520} 5521 5522# display_K {{{3 5523sub display_K { 5524 my @display_lines; 5525 my @cxns = get_connections(); 5526 5527 my %rows_for = ( 5528 innodb_blocked_blocker => [], 5529 ); 5530 5531 my @visible = get_visible_tables(); 5532 my %wanted = map { $_ => 1 } @visible; 5533 5534 # Get info on locks 5535 if ( $wanted{innodb_blocked_blocker} ) { 5536 my @rows = get_innodb_blocked_blocker(@cxns); 5537 push @{$rows_for{innodb_blocked_blocker}}, map { extract_values($_, $_, $_, 'innodb_blocked_blocker') } @rows; 5538 } 5539 5540 my $first_table = 0; 5541 foreach my $tbl ( @visible ) { 5542 push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl); 5543 push @display_lines, get_cxn_errors(@cxns) 5544 if ( $config{debug}->{val} || !$first_table++ ); 5545 } 5546 5547 # Save queries in global variable for analysis. The rows in %rows_for have been 5548 # filtered, etc as a side effect of set_to_tbl(), so they are the same as the rows 5549 # that get pushed to the screen. 5550 @current_queries = map { 5551 my %hash; 5552 @hash{ qw(cxn id user host db query time) } 5553 = @{$_}{ qw(cxn blocking_thread blocking_user blocking_host blocking_db blocking_query blocking_age) }; 5554 # time is in fuzzy-time format; convert into something ascii-sortable. 5555 $hash{time} = sprintf('%012s', fuzzy_to_secs($hash{time})); 5556 $hash{host} =~ s/:.*$// if $hash{host}; 5557 \%hash; 5558 } @{$rows_for{innodb_blocked_blocker}}; 5559 5560 draw_screen(\@display_lines); 5561} 5562 5563# display_L {{{3 5564sub display_L { 5565 my @display_lines; 5566 my @cxns = get_connections(); 5567 get_status_info(@cxns); 5568 get_innodb_status(\@cxns); 5569 5570 my @innodb_locks; 5571 my %rows_for = ( 5572 innodb_locks => \@innodb_locks, 5573 ); 5574 5575 my @visible = get_visible_tables(); 5576 my %wanted = map { $_ => 1 } @visible; 5577 5578 # Get info on locks 5579 foreach my $cxn ( @cxns ) { 5580 my $set = $vars{$cxn}->{$clock} or next; 5581 my $pre = $vars{$cxn}->{$clock-1} || $set; 5582 5583 if ( $wanted{innodb_locks} && defined $set->{IB_tx_transactions} && @{$set->{IB_tx_transactions}} ) { 5584 5585 my $cur_txns = $set->{IB_tx_transactions}; 5586 my $pre_txns = $pre->{IB_tx_transactions} || $cur_txns; 5587 my %cur_txns = map { $_->{mysql_thread_id} => $_ } grep { defined $_->{mysql_thread_id} } @$cur_txns; 5588 my %pre_txns = map { $_->{mysql_thread_id} => $_ } grep { defined $_->{mysql_thread_id} } @$pre_txns; 5589 foreach my $txn ( @$cur_txns ) { 5590 foreach my $lock ( @{$txn->{locks}} ) { 5591 my %hash = map { $_ => $txn->{$_} } qw(txn_id mysql_thread_id lock_wait_time active_secs); 5592 map { $hash{$_} = $lock->{$_} } qw(lock_type space_id page_no n_bits index db table txn_id lock_mode special insert_intention waiting); 5593 $hash{cxn} = $cxn; 5594 push @innodb_locks, extract_values(\%hash, \%hash, \%hash, 'innodb_locks'); 5595 } 5596 } 5597 } 5598 } 5599 5600 my $first_table = 0; 5601 foreach my $tbl ( @visible ) { 5602 push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl); 5603 push @display_lines, get_cxn_errors(@cxns) 5604 if ( $config{debug}->{val} || !$first_table++ ); 5605 } 5606 5607 draw_screen(\@display_lines); 5608} 5609 5610# display_M {{{3 5611sub display_M { 5612 my @display_lines; 5613 my @cxns = get_connections(); 5614 get_master_slave_status(@cxns); 5615 get_status_info(@cxns); 5616 5617 my @slave_sql_status; 5618 my @slave_io_status; 5619 my @master_status; 5620 my %rows_for = ( 5621 slave_sql_status => \@slave_sql_status, 5622 slave_io_status => \@slave_io_status, 5623 master_status => \@master_status, 5624 ); 5625 5626 my @visible = get_visible_tables(); 5627 my %wanted = map { $_ => 1 } @visible; 5628 5629 foreach my $cxn ( @cxns ) { 5630 my $linecount=0; 5631 my $sth = do_stmt($cxn, 'GET_CHANNELS'); 5632 my ( $channel ); 5633 next if (!$sth); 5634 $sth->execute(); 5635 $sth->bind_columns( \$channel ); 5636 while ( $sth->fetch() ) { 5637 $linecount=$linecount+1; 5638 if ( length $channel < 1 ) { 5639 $channel = 'no_channels'; 5640 } 5641 my $chcxn = $channel . '=' . $cxn; 5642 get_slave_status($cxn,$channel); 5643 my $set = $config{status_inc}->{val} ? inc(0, $chcxn) : $vars{$chcxn}->{$clock}; 5644 my $pre = $vars{$chcxn}->{$clock - 1} || $set; 5645 if ( $wanted{slave_sql_status} ) { 5646 push @slave_sql_status, extract_values($set, $set, $pre, 'slave_sql_status'); 5647 } 5648 if ( $wanted{slave_io_status} ) { 5649 push @slave_io_status, extract_values($set, $set, $pre, 'slave_io_status'); 5650 } 5651 } 5652 if ( $linecount < 1 ) { 5653 $channel = 'no_channels'; 5654 my $chcxn = $channel . '=' . $cxn; 5655 get_slave_status($cxn,$channel); 5656 my $set = $config{status_inc}->{val} ? inc(0, $chcxn) : $vars{$chcxn}->{$clock}; 5657 my $pre = $vars{$chcxn}->{$clock - 1} || $set; 5658 if ( $wanted{slave_sql_status} ) { 5659 push @slave_sql_status, extract_values($set, $set, $pre, 'slave_sql_status'); 5660 } 5661 if ( $wanted{slave_io_status} ) { 5662 push @slave_io_status, extract_values($set, $set, $pre, 'slave_io_status'); 5663 } 5664 } 5665 my $set = $config{status_inc}->{val} ? inc(0, $cxn) : $vars{$cxn}->{$clock}; 5666 my $pre = $vars{$cxn}->{$clock - 1} || $set; 5667 if ( $wanted{master_status} ) { 5668 push @master_status, extract_values($set, $set, $pre, 'master_status'); 5669 } 5670 } 5671 5672 my $first_table = 0; 5673 foreach my $tbl ( @visible ) { 5674 push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl); 5675 push @display_lines, get_cxn_errors(@cxns) 5676 if ( $config{debug}->{val} || !$first_table++ ); 5677 } 5678 5679 draw_screen(\@display_lines); 5680} 5681 5682# display_O {{{3 5683sub display_O { 5684 my @display_lines = (''); 5685 my @cxns = get_connections(); 5686 my @open_tables = get_open_tables(@cxns); 5687 my @tables = map { extract_values($_, $_, $_, 'open_tables') } @open_tables; 5688 push @display_lines, set_to_tbl(\@tables, 'open_tables'), get_cxn_errors(@cxns); 5689 draw_screen(\@display_lines); 5690} 5691 5692# display_P {{{3 5693sub display_P { 5694 my @display_lines; 5695 5696 my @table_statistics; 5697 my @index_statistics; 5698 my @index_table_statistics; 5699 my %rows_for = ( 5700 table_statistics => \@table_statistics, 5701 index_statistics => \@index_statistics, 5702 index_table_statistics => \@index_table_statistics, 5703 ); 5704 5705 my @visible = $opts{n} ? 'index_table_statistics' : get_visible_tables(); 5706 my %wanted = map { $_ => 1 } @visible; 5707 5708 # Get the data 5709 my @cxns = get_connections(); 5710 5711 if ( $wanted{table_statistics} ) { 5712 my @rows = get_table_statistics(@cxns); 5713 push @table_statistics, map { extract_values($_, $_, $_, 'table_statistics') } @rows; 5714 } 5715 elsif ( $wanted{index_statistics} ) { 5716 my @rows = get_index_statistics(@cxns); 5717 push @index_statistics, map { extract_values($_, $_, $_, 'index_statistics') } @rows; 5718 } 5719 elsif ( $wanted{index_table_statistics} ) { 5720 my @rows = get_index_table_statistics(@cxns); 5721 push @index_table_statistics, map { extract_values($_, $_, $_, 'index_table_statistics') } @rows; 5722 } 5723 5724 my $first_table = 0; 5725 foreach my $tbl ( @visible ) { 5726 next unless $wanted{$tbl}; 5727 push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl); 5728 push @display_lines, get_cxn_errors(@cxns) 5729 if ( $config{debug}->{val} || !$first_table++ ); 5730 } 5731 5732 draw_screen(\@display_lines); 5733} 5734 5735# display_Q {{{3 5736sub display_Q { 5737 my @display_lines; 5738 5739 my @q_header; 5740 my @processlist; 5741 my %rows_for = ( 5742 q_header => \@q_header, 5743 processlist => \@processlist, 5744 ); 5745 5746 my @visible = $opts{n} ? 'processlist' : get_visible_tables(); 5747 my %wanted = map { $_ => 1 } @visible; 5748 5749 # Get the data 5750 my @cxns = get_connections(); 5751 my @full_processlist = get_full_processlist(@cxns); 5752 5753 # Create header 5754 if ( $wanted{q_header} ) { 5755 get_status_info(@cxns); 5756 foreach my $cxn ( @cxns ) { 5757 my $set = $vars{$cxn}->{$clock}; 5758 my $pre = $vars{$cxn}->{$clock-1} || $set; 5759 my $hash = extract_values($set, $set, $pre, 'q_header'); 5760 $hash->{cxn} = $cxn; 5761 $hash->{when} = 'Total'; 5762 push @q_header, $hash; 5763 5764 if ( exists $vars{$cxn}->{$clock - 1} ) { 5765 my $inc = inc(0, $cxn); 5766 my $hash = extract_values($inc, $set, $pre, 'q_header'); 5767 $hash->{cxn} = $cxn; 5768 $hash->{when} = 'Now'; 5769 push @q_header, $hash; 5770 } 5771 } 5772 } 5773 5774 if ( $wanted{processlist} ) { 5775 # TODO: save prev values 5776 push @processlist, map { extract_values($_, $_, $_, 'processlist') } @full_processlist; 5777 } 5778 5779 my $first_table = 0; 5780 foreach my $tbl ( @visible ) { 5781 next unless $wanted{$tbl}; 5782 push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl); 5783 push @display_lines, get_cxn_errors(@cxns) 5784 if ( $config{debug}->{val} || !$first_table++ ); 5785 } 5786 5787 # Save queries in global variable for analysis. The rows in %rows_for have been 5788 # filtered, etc as a side effect of set_to_tbl(), so they are the same as the rows 5789 # that get pushed to the screen. 5790 @current_queries = map { 5791 my %hash; 5792 @hash{ qw(cxn id db query time user host) } 5793 = @{$_}{ qw(cxn mysql_thread_id db info time user hostname) }; 5794 # time is in seconds-to-time format; convert into something 5795 # ascii-sortable. 5796 $hash{time} = sprintf('%012s', $hash{time} =~ m/^([^.]*)/); 5797 \%hash; 5798 } @{$rows_for{processlist}}; 5799 5800 draw_screen(\@display_lines); 5801} 5802 5803# display_R {{{3 5804sub display_R { 5805 my @display_lines; 5806 my @cxns = get_connections(); 5807 get_status_info(@cxns); 5808 get_innodb_status(\@cxns); 5809 5810 my @row_operations; 5811 my @row_operation_misc; 5812 my @semaphores; 5813 my @wait_array; 5814 my %rows_for = ( 5815 row_operations => \@row_operations, 5816 row_operation_misc => \@row_operation_misc, 5817 semaphores => \@semaphores, 5818 wait_array => \@wait_array, 5819 ); 5820 5821 my @visible = get_visible_tables(); 5822 my %wanted = map { $_ => 1 } @visible; 5823 my $incvar = $config{status_inc}->{val}; 5824 5825 foreach my $cxn ( @cxns ) { 5826 my $set = $vars{$cxn}->{$clock}; 5827 my $pre = $vars{$cxn}->{$clock-1} || $set; 5828 my $inc; # Only assigned to if wanted 5829 5830 if ( $set->{IB_ro_complete} ) { 5831 if ( $wanted{row_operations} ) { 5832 $inc ||= $incvar ? inc(0, $cxn) : $set; 5833 push @row_operations, extract_values($inc, $set, $pre, 'row_operations'); 5834 } 5835 if ( $wanted{row_operation_misc} ) { 5836 push @row_operation_misc, extract_values($set, $set, $pre, 'row_operation_misc'), 5837 } 5838 } 5839 5840 if ( $set->{IB_sm_complete} && $wanted{semaphores} ) { 5841 $inc ||= $incvar ? inc(0, $cxn) : $set; 5842 push @semaphores, extract_values($inc, $set, $pre, 'semaphores'); 5843 } 5844 5845 if ( $set->{IB_sm_wait_array_size} && $wanted{wait_array} ) { 5846 foreach my $wait ( @{$set->{IB_sm_waits}} ) { 5847 my $hash = extract_values($wait, $wait, $wait, 'wait_array'); 5848 $hash->{cxn} = $cxn; 5849 push @wait_array, $hash; 5850 } 5851 } 5852 } 5853 5854 my $first_table = 0; 5855 foreach my $tbl ( @visible ) { 5856 push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl); 5857 push @display_lines, get_cxn_errors(@cxns) 5858 if ( $config{debug}->{val} || !$first_table++ ); 5859 } 5860 5861 draw_screen(\@display_lines); 5862} 5863 5864# display_T {{{3 5865sub display_T { 5866 my @display_lines; 5867 5868 my @t_header; 5869 my @innodb_transactions; 5870 my %rows_for = ( 5871 t_header => \@t_header, 5872 innodb_transactions => \@innodb_transactions, 5873 ); 5874 5875 my @visible = $opts{n} ? 'innodb_transactions' : get_visible_tables(); 5876 my %wanted = map { $_ => 1 } @visible; 5877 5878 my @cxns = get_connections(); 5879 5880 get_status_info(@cxns); 5881 5882 # If the header is to be shown, buffer pool data is required. 5883 get_innodb_status( \@cxns, [ $wanted{t_header} ? qw(bp) : () ] ); 5884 5885 foreach my $cxn ( get_connections() ) { 5886 my $set = $vars{$cxn}->{$clock}; 5887 my $pre = $vars{$cxn}->{$clock-1} || $set; 5888 5889 next unless $set->{IB_tx_transactions}; 5890 5891 if ( $wanted{t_header} ) { 5892 my $hash = extract_values($set, $set, $pre, 't_header'); 5893 push @t_header, $hash; 5894 } 5895 5896 if ( $wanted{innodb_transactions} ) { 5897 my $cur_txns = $set->{IB_tx_transactions}; 5898 my $pre_txns = $pre->{IB_tx_transactions} || $cur_txns; 5899 my %cur_txns = map { $_->{mysql_thread_id} => $_ } grep { defined $_->{mysql_thread_id} } @$cur_txns; 5900 my %pre_txns = map { $_->{mysql_thread_id} => $_ } grep { defined $_->{mysql_thread_id} } @$pre_txns; 5901 foreach my $thd_id ( sort keys %cur_txns ) { 5902 my $cur_txn = $cur_txns{$thd_id}; 5903 my $pre_txn = $pre_txns{$thd_id} || $cur_txn; 5904 my $hash = extract_values($cur_txn, $cur_txn, $pre_txn, 'innodb_transactions'); 5905 $hash->{cxn} = $cxn; 5906 push @innodb_transactions, $hash; 5907 } 5908 } 5909 5910 } 5911 5912 my $first_table = 0; 5913 foreach my $tbl ( @visible ) { 5914 push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl); 5915 push @display_lines, get_cxn_errors(@cxns) 5916 if ( $config{debug}->{val} || !$first_table++ ); 5917 } 5918 5919 # Save queries in global variable for analysis. The rows in %rows_for have been 5920 # filtered, etc as a side effect of set_to_tbl(), so they are the same as the rows 5921 # that get pushed to the screen. 5922 @current_queries = map { 5923 my %hash; 5924 @hash{ qw(cxn id db query time user host) } 5925 = @{$_}{ qw(cxn mysql_thread_id db query_text active_secs user hostname) }; 5926 \%hash; 5927 } @{$rows_for{innodb_transactions}}; 5928 5929 draw_screen(\@display_lines); 5930} 5931 5932# display_S {{{3 5933sub display_S { 5934 my $fmt = get_var_set('S_set'); 5935 my $func = $config{S_func}->{val}; 5936 my $inc = $func eq 'g' || $config{status_inc}->{val}; 5937 5938 # The table's meta-data is generated from the compiled var_set. 5939 my ( $cols, $visible ); 5940 if ( $tbl_meta{var_status}->{fmt} && $fmt eq $tbl_meta{var_status}->{fmt} ) { 5941 ( $cols, $visible ) = @{$tbl_meta{var_status}}{qw(cols visible)}; 5942 } 5943 else { 5944 ( $cols, $visible ) = compile_select_stmt($fmt); 5945 5946 # Apply missing values to columns. Always apply averages across all connections. 5947 map { 5948 $_->{agg} = 'avg'; 5949 $_->{label} = $_->{hdr}; 5950 } values %$cols; 5951 5952 $tbl_meta{var_status}->{cols} = $cols; 5953 $tbl_meta{var_status}->{visible} = $visible; 5954 $tbl_meta{var_status}->{fmt} = $fmt; 5955 map { $tbl_meta{var_status}->{cols}->{$_}->{just} = ''} @$visible; 5956 } 5957 5958 my @var_status; 5959 my %rows_for = ( 5960 var_status => \@var_status, 5961 ); 5962 5963 my @visible = get_visible_tables(); 5964 my %wanted = map { $_ => 1 } @visible; 5965 my @cxns = get_connections(); 5966 5967 get_status_info(@cxns); 5968 get_innodb_status(\@cxns); 5969 5970 # Set up whether to pivot and how many sets to extract. 5971 $tbl_meta{var_status}->{pivot} = $func eq 'v'; 5972 5973 my $num_sets 5974 = $func eq 'v' 5975 ? $config{num_status_sets}->{val} 5976 : 0; 5977 foreach my $set ( 0 .. $num_sets ) { 5978 my @rows; 5979 foreach my $cxn ( @cxns ) { 5980 my $vars = $inc ? inc($set, $cxn) : $vars{$cxn}->{$clock - $set}; 5981 my $cur = $vars{$cxn}->{$clock-$set}; 5982 my $pre = $vars{$cxn}->{$clock-$set-1} || $cur; 5983 next unless $vars && %$vars; 5984 my $hash = extract_values($vars, $cur, $pre, 'var_status'); 5985 push @rows, $hash; 5986 } 5987 @rows = apply_group_by('var_status', [], @rows); 5988 push @var_status, @rows; 5989 } 5990 5991 # Recompile the sort func. TODO: avoid recompiling at every refresh. 5992 # Figure out whether the data is all numeric and decide on a sort type. 5993 # my $cmp 5994 # = scalar( 5995 # grep { !defined $_ || $_ !~ m/^\d+$/ } 5996 # map { my $col = $_; map { $_->{$col} } @var_status } 5997 # $tbl_meta{var_status}->{sort_cols} =~ m/(\w+)/g) 5998 # ? 'cmp' 5999 # : '<=>'; 6000 $tbl_meta{var_status}->{sort_func} = make_sort_func($tbl_meta{var_status}); 6001 6002 # ################################################################ 6003 # Now there is specific display code based on $config{S_func} 6004 # ################################################################ 6005 if ( $func =~ m/s|g/ ) { 6006 my $min_width = 4; 6007 6008 # Clear the screen if the display width changed. 6009 if ( @last_term_size && $this_term_size[0] != $last_term_size[0] ) { 6010 $lines_printed = 0; 6011 $clear_screen_sub->(); 6012 } 6013 6014 if ( $func eq 's' ) { 6015 # Decide how wide columns should be. 6016 my $num_cols = scalar(@$visible); 6017 my $width = $opts{n} ? 0 : max($min_width, int(($this_term_size[0] - $num_cols + 1) / $num_cols)); 6018 my $g_format = $opts{n} ? ( "%s\t" x $num_cols ) : ( "%-${width}s " x $num_cols ); 6019 6020 # Print headers every now and then. Headers can get really long, so compact them. 6021 my @hdr = @$visible; 6022 if ( $opts{n} ) { 6023 if ( $lines_printed == 0 ) { 6024 print join("\t", @hdr), "\n"; 6025 $lines_printed++; 6026 } 6027 } 6028 elsif ( $lines_printed == 0 || $lines_printed > $this_term_size[1] - 2 ) { 6029 @hdr = map { donut(crunch($_, $width), $width) } @hdr; 6030 print join(' ', map { sprintf( "%${width}s", donut($_, $width)) } @hdr) . "\n"; 6031 $lines_printed = 1; 6032 } 6033 6034 # Design a column format for the values. 6035 my $format 6036 = $opts{n} 6037 ? join("\t", map { '%s' } @$visible) . "\n" 6038 : join(' ', map { "%${width}s" } @hdr) . "\n"; 6039 6040 foreach my $row ( @var_status ) { 6041 printf($format, map { defined $_ ? $_ : '' } @{$row}{ @$visible }); 6042 $lines_printed++; 6043 } 6044 } 6045 else { # 'g' mode 6046 # Design a column format for the values. 6047 my $num_cols = scalar(@$visible); 6048 my $width = $opts{n} ? 0 : int(($this_term_size[0] - $num_cols + 1) / $num_cols); 6049 my $format = $opts{n} ? ( "%s\t" x $num_cols ) : ( "%-${width}s " x $num_cols ); 6050 $format =~ s/\s$/\n/; 6051 6052 # Print headers every now and then. 6053 if ( $opts{n} ) { 6054 if ( $lines_printed == 0 ) { 6055 print join("\t", @$visible), "\n"; 6056 print join("\t", map { shorten($mvs{$_}) } @$visible), "\n"; 6057 } 6058 } 6059 elsif ( $lines_printed == 0 || $lines_printed > $this_term_size[1] - 2 ) { 6060 printf($format, map { donut(crunch($_, $width), $width) } @$visible); 6061 printf($format, map { shorten($mvs{$_} || 0) } @$visible); 6062 $lines_printed = 2; 6063 } 6064 6065 # Update the max ever seen, and scale by the max ever seen. 6066 my $set = $var_status[0]; 6067 foreach my $col ( @$visible ) { 6068 $set->{$col} = 1 unless defined $set->{$col} && $set->{$col} =~ m/$num_regex/; 6069 $set->{$col} = ($set->{$col} || 1) / ($set->{Uptime_hires} || 1); 6070 $mvs{$col} = max($mvs{$col} || 1, $set->{$col}); 6071 $set->{$col} /= $mvs{$col}; 6072 } 6073 printf($format, map { ( $config{graph_char}->{val} x int( $width * $set->{$_} )) || '.' } @$visible ); 6074 $lines_printed++; 6075 6076 } 6077 } 6078 else { # 'v' 6079 my $first_table = 0; 6080 my @display_lines; 6081 foreach my $tbl ( @visible ) { 6082 push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl); 6083 push @display_lines, get_cxn_errors(@cxns) 6084 if ( $config{debug}->{val} || !$first_table++ ); 6085 } 6086 $clear_screen_sub->(); 6087 draw_screen( \@display_lines ); 6088 } 6089} 6090 6091# display_explain {{{3 6092sub display_explain { 6093 my $info = shift; 6094 my $cxn = $info->{cxn}; 6095 my $db = $info->{db}; 6096 6097 my ( $mods, $query ) = rewrite_for_explain($info->{query}); 6098 6099 my @display_lines; 6100 6101 if ( $query ) { 6102 6103 my $part = version_ge($dbhs{$cxn}->{dbh}, '5.1.5') && ($dbhs{$cxn}->{dbh}->{mysql_serverinfo} !~ /^8\.0\./) ? 'PARTITIONS' : ''; 6104 $query = "EXPLAIN $part\n" . $query; 6105 6106 eval { 6107 if ( $db ) { 6108 do_query($cxn, "use $db"); 6109 } 6110 my $sth = do_query($cxn, $query); 6111 6112 my $res; 6113 while ( $res = $sth->fetchrow_hashref() ) { 6114 map { $res->{$_} ||= '' } ( 'partitions', keys %$res); 6115 my @this_table = create_caption("Sub-Part $res->{id}", 6116 create_table2( 6117 $tbl_meta{explain}->{visible}, 6118 meta_to_hdr('explain'), 6119 extract_values($res, $res, $res, 'explain'))); 6120 @display_lines = stack_next(\@display_lines, \@this_table, { pad => ' ', vsep => 2 }); 6121 } 6122 }; 6123 6124 if ( $EVAL_ERROR ) { 6125 push @display_lines, 6126 '', 6127 "The query could not be explained. Only SELECT queries can be " 6128 . "explained; innotop tries to rewrite certain REPLACE and INSERT queries " 6129 . "into SELECT, but this doesn't always succeed."; 6130 } 6131 6132 } 6133 else { 6134 push @display_lines, '', 'The query could not be explained.'; 6135 } 6136 6137 if ( $mods ) { 6138 push @display_lines, '', '[This query has been re-written to be explainable]'; 6139 } 6140 6141 unshift @display_lines, no_ctrl_char($query); 6142 draw_screen(\@display_lines, { raw => 1 } ); 6143} 6144 6145# rewrite_for_explain {{{3 6146sub rewrite_for_explain { 6147 my $query = shift; 6148 6149 my $mods = 0; 6150 my $orig = $query; 6151 $mods += $query =~ s/^\s*(?:replace|insert).*?select/select/is; 6152 $mods += $query =~ s/^ 6153 \s*create\s+(?:temporary\s+)?table 6154 \s+(?:\S+\s+)as\s+select/select/xis; 6155 $mods += $query =~ s/\s+on\s+duplicate\s+key\s+update.*$//is; 6156 return ( $mods, $query ); 6157} 6158 6159# show_optimized_query {{{3 6160sub show_optimized_query { 6161 my $info = shift; 6162 my $cxn = $info->{cxn}; 6163 my $db = $info->{db}; 6164 my $meta = $dbhs{$cxn}; 6165 6166 my @display_lines; 6167 6168 my ( $mods, $query ) = rewrite_for_explain($info->{query}); 6169 6170 if ( $mods ) { 6171 push @display_lines, '[This query has been re-written to be explainable]'; 6172 } 6173 6174 if ( $query ) { 6175 push @display_lines, no_ctrl_char($info->{query}); 6176 6177 eval { 6178 if ( $db ) { 6179 do_query($cxn, "use $db"); 6180 } 6181 do_query( $cxn, 'EXPLAIN EXTENDED ' . $query ) or die "Can't explain query"; 6182 my $sth = do_query($cxn, 'SHOW WARNINGS'); 6183 my $res = $sth->fetchall_arrayref({}); 6184 6185 if ( $res ) { 6186 foreach my $result ( @$res ) { 6187 push @display_lines, 'Note:', no_ctrl_char($result->{message}); 6188 } 6189 } 6190 else { 6191 push @display_lines, '', 'The query optimization could not be generated.'; 6192 } 6193 }; 6194 6195 if ( $EVAL_ERROR ) { 6196 push @display_lines, '', "The optimization could not be generated: $EVAL_ERROR"; 6197 } 6198 6199 } 6200 else { 6201 push @display_lines, '', 'The query optimization could not be generated.'; 6202 } 6203 6204 draw_screen(\@display_lines, { raw => 1 } ); 6205} 6206 6207# display_help {{{3 6208sub display_help { 6209 my $mode = $config{mode}->{val}; 6210 6211 # Get globally mapped keys, then overwrite them with mode-specific ones. 6212 my %keys = map { 6213 $_ => $action_for{$_}->{label} 6214 } keys %action_for; 6215 foreach my $key ( keys %{$modes{$mode}->{action_for}} ) { 6216 $keys{$key} = $modes{$mode}->{action_for}->{$key}->{label}; 6217 } 6218 delete $keys{'?'}; 6219 6220 # Split them into three kinds of keys: MODE keys, action keys, and 6221 # magic (special character) keys. 6222 my @modes = sort grep { m/[A-Z]/ } keys %keys; 6223 my @actions = sort grep { m/[a-z]/ } keys %keys; 6224 my @magic = sort grep { m/[^A-Z]/i } keys %keys; 6225 6226 my @display_lines = ( '', 'Switch to a different mode:' ); 6227 6228 # Mode keys 6229 my @all_modes = map { "$_ $modes{$_}->{hdr}" } @modes; 6230 my @col1 = splice(@all_modes, 0, ceil(@all_modes/3)); 6231 my @col2 = splice(@all_modes, 0, ceil(@all_modes/2)); 6232 my $max1 = max(map {length($_)} @col1); 6233 my $max2 = max(map {length($_)} @col2); 6234 while ( @col1 ) { 6235 push @display_lines, sprintf(" %-${max1}s %-${max2}s %s", 6236 (shift @col1 || ''), 6237 (shift @col2 || ''), 6238 (shift @all_modes || '')); 6239 } 6240 6241 # Action keys 6242 my @all_actions = map { "$_ $keys{$_}" } @actions; 6243 @col1 = splice(@all_actions, 0, ceil(@all_actions/2)); 6244 $max1 = max(map {length($_)} @col1); 6245 push @display_lines, '', 'Actions:'; 6246 while ( @col1 ) { 6247 push @display_lines, sprintf(" %-${max1}s %s", 6248 (shift @col1 || ''), 6249 (shift @all_actions || '')); 6250 } 6251 6252 # Magic keys 6253 my @all_magic = map { 6254 my $k = $action_for{$_} ? ($action_for{$_}->{key} || $_) : $_; 6255 sprintf('%4s %s', $k, $keys{$_}); 6256 } @magic; 6257 @col1 = splice(@all_magic, 0, ceil(@all_magic/2)); 6258 $max1 = max(map {length($_)} @col1); 6259 push @display_lines, '', 'Other:'; 6260 while ( @col1 ) { 6261 push @display_lines, sprintf("%-${max1}s%s", 6262 (shift @col1 || ''), 6263 (shift @all_magic || '')); 6264 } 6265 6266 $clear_screen_sub->(); 6267 draw_screen(\@display_lines, { show_all => 1 } ); 6268 pause(); 6269 $clear_screen_sub->(); 6270} 6271 6272# show_full_query {{{3 6273sub show_full_query { 6274 my $info = shift; 6275 my @display_lines = no_ctrl_char($info->{query}); 6276 draw_screen(\@display_lines, { raw => 1 }); 6277} 6278 6279# Formatting functions {{{2 6280 6281# create_table2 {{{3 6282# Makes a two-column table, labels on left, data on right. 6283# Takes refs of @cols, %labels and %data, %user_prefs 6284sub create_table2 { 6285 my ( $cols, $labels, $data, $user_prefs ) = @_; 6286 my @rows; 6287 6288 if ( @$cols && %$data ) { 6289 6290 # Override defaults 6291 my $p = { 6292 just => '', 6293 sep => ':', 6294 just1 => '-', 6295 }; 6296 if ( $user_prefs ) { 6297 map { $p->{$_} = $user_prefs->{$_} } keys %$user_prefs; 6298 } 6299 6300 # Fix undef values 6301 map { $data->{$_} = '' unless defined $data->{$_} } @$cols; 6302 6303 # Format the table 6304 my $max_l = max(map{ length($labels->{$_}) } @$cols); 6305 my $max_v = max(map{ length($data->{$_}) } @$cols); 6306 my $format = "%$p->{just}${max_l}s$p->{sep} %$p->{just1}${max_v}s"; 6307 foreach my $col ( @$cols ) { 6308 push @rows, sprintf($format, $labels->{$col}, $data->{$col}); 6309 } 6310 } 6311 return @rows; 6312} 6313 6314# stack_next {{{3 6315# Stacks one display section next to the other. Accepts left-hand arrayref, 6316# right-hand arrayref, and options hashref. Tries to stack as high as 6317# possible, so 6318# aaaaaa 6319# bbb 6320# can stack ccc next to the bbb. 6321# NOTE: this DOES modify its arguments, even though it returns a new array. 6322sub stack_next { 6323 my ( $left, $right, $user_prefs ) = @_; 6324 my @result; 6325 6326 my $p = { 6327 pad => ' ', 6328 vsep => 0, 6329 }; 6330 if ( $user_prefs ) { 6331 map { $p->{$_} = $user_prefs->{$_} } keys %$user_prefs; 6332 } 6333 6334 # Find out how wide the LHS can be and still let the RHS fit next to it. 6335 my $pad = $p->{pad}; 6336 my $max_r = max( map { length($_) } @$right) || 0; 6337 my $max_l = $this_term_size[0] - $max_r - length($pad); 6338 6339 # Find the minimum row on the LHS that the RHS will fit next to. 6340 my $i = scalar(@$left) - 1; 6341 while ( $i >= 0 && length($left->[$i]) <= $max_l ) { 6342 $i--; 6343 } 6344 $i++; 6345 my $offset = $i; 6346 6347 if ( $i < scalar(@$left) ) { 6348 # Find the max width of the section of the LHS against which the RHS 6349 # will sit. 6350 my $max_i_in_common = min($i + scalar(@$right) - 1, scalar(@$left) - 1); 6351 my $max_width = max( map { length($_) } @{$left}[$i..$max_i_in_common]); 6352 6353 # Append the RHS onto the LHS until one runs out. 6354 while ( $i < @$left && $i - $offset < @$right ) { 6355 my $format = "%-${max_width}s$pad%${max_r}s"; 6356 $left->[$i] = sprintf($format, $left->[$i], $right->[$i - $offset]); 6357 $i++; 6358 } 6359 while ( $i - $offset < @$right ) { 6360 # There is more RHS to push on the end of the array 6361 push @$left, 6362 sprintf("%${max_width}s$pad%${max_r}s", ' ', $right->[$i - $offset]); 6363 $i++; 6364 } 6365 push @result, @$left; 6366 } 6367 else { 6368 # There is no room to put them side by side. Add them below, with 6369 # a blank line above them if specified. 6370 push @result, @$left; 6371 push @result, (' ' x $this_term_size[0]) if $p->{vsep} && @$left; 6372 push @result, @$right; 6373 } 6374 return @result; 6375} 6376 6377# create_caption {{{3 6378sub create_caption { 6379 my ( $caption, @rows ) = @_; 6380 if ( @rows ) { 6381 6382 # Calculate the width of what will be displayed, so it can be centered 6383 # in that space. When the thing is wider than the display, center the 6384 # caption in the display. 6385 my $width = min($this_term_size[0], max(map { length(ref($_) ? $_->[0] : $_) } @rows)); 6386 6387 my $cap_len = length($caption); 6388 6389 # It may be narrow enough to pad the sides with underscores and save a 6390 # line on the screen. 6391 if ( $cap_len <= $width - 6 ) { 6392 my $left = int(($width - 2 - $cap_len) / 2); 6393 unshift @rows, 6394 ("_" x $left) . " $caption " . ("_" x ($width - $left - $cap_len - 2)); 6395 } 6396 6397 # The caption is too wide to add underscores on each side. 6398 else { 6399 6400 # Color is supported, so we can use terminal underlining. 6401 if ( $config{color}->{val} ) { 6402 my $left = int(($width - $cap_len) / 2); 6403 unshift @rows, [ 6404 (" " x $left) . $caption . (" " x ($width - $left - $cap_len)), 6405 'underline', 6406 ]; 6407 } 6408 6409 # Color is not supported, so we have to add a line underneath to separate the 6410 # caption from whatever it's captioning. 6411 else { 6412 my $left = int(($width - $cap_len) / 2); 6413 unshift @rows, ('-' x $width); 6414 unshift @rows, (" " x $left) . $caption . (" " x ($width - $left - $cap_len)); 6415 } 6416 6417 # The caption is wider than the thing it labels, so we have to pad the 6418 # thing it labels to a consistent width. 6419 if ( $cap_len > $width ) { 6420 @rows = map { 6421 ref($_) 6422 ? [ sprintf('%-' . $cap_len . 's', $_->[0]), $_->[1] ] 6423 : sprintf('%-' . $cap_len . 's', $_); 6424 } @rows; 6425 } 6426 6427 } 6428 } 6429 return @rows; 6430} 6431 6432# create_table {{{3 6433# Input: an arrayref of columns, hashref of col info, and an arrayref of hashes 6434# Example: [ 'a', 'b' ] 6435# { a => spec, b => spec } 6436# [ { a => 1, b => 2}, { a => 3, b => 4 } ] 6437# The 'spec' is a hashref of hdr => label, just => ('-' or ''). It also supports min and max-widths 6438# vi the minw and maxw params. 6439# Output: an array of strings, one per row. 6440# Example: 6441# Column One Column Two 6442# ---------- ---------- 6443# 1 2 6444# 3 4 6445sub create_table { 6446 my ( $cols, $info, $data, $prefs ) = @_; 6447 $prefs ||= {}; 6448 $prefs->{no_hdr} ||= ($opts{n} && $clock != 1); 6449 6450 # Truncate rows that will surely be off screen even if this is the only table. 6451 if ( !$opts{n} && !$prefs->{raw} && !$prefs->{show_all} && $this_term_size[1] < @$data-1 ) { 6452 $data = [ @$data[0..$this_term_size[1] - 1] ]; 6453 } 6454 6455 my @rows = (); 6456 6457 if ( @$cols && %$info ) { 6458 6459 # Fix undef values, collapse whitespace. 6460 foreach my $row ( @$data ) { 6461 map { $row->{$_} = collapse_ws($row->{$_}) } @$cols; 6462 } 6463 6464 my $col_sep = $opts{n} ? "\t" : ' '; 6465 6466 # Find each column's max width. 6467 my %width_for; 6468 if ( !$opts{n} ) { 6469 %width_for = map { 6470 my $col_name = $_; 6471 if ( $info->{$_}->{dec} ) { 6472 # Align along the decimal point 6473 my $max_rodp = max(0, map { $_->{$col_name} =~ m/([^\s\d-].*)$/ ? length($1) : 0 } @$data); 6474 foreach my $row ( @$data ) { 6475 my $col = $row->{$col_name}; 6476 my ( $l, $r ) = $col =~ m/^([\s\d]*)(.*)$/; 6477 $row->{$col_name} = sprintf("%s%-${max_rodp}s", $l, $r); 6478 } 6479 } 6480 my $max_width = max( length($info->{$_}->{hdr}), map { length($_->{$col_name}) } @$data); 6481 if ( $info->{$col_name}->{maxw} ) { 6482 $max_width = min( $max_width, $info->{$col_name}->{maxw} ); 6483 } 6484 if ( $info->{$col_name}->{minw} ) { 6485 $max_width = max( $max_width, $info->{$col_name}->{minw} ); 6486 } 6487 $col_name => $max_width; 6488 } @$cols; 6489 } 6490 6491 # The table header. 6492 if ( !$config{hide_hdr}->{val} && !$prefs->{no_hdr} ) { 6493 push @rows, $opts{n} 6494 ? join( $col_sep, @$cols ) 6495 : join( $col_sep, map { sprintf( "%-$width_for{$_}s", trunc($info->{$_}->{hdr}, $width_for{$_}) ) } @$cols ); 6496 if ( $config{color}->{val} && $config{header_highlight}->{val} ) { 6497 push @rows, [ pop @rows, $config{header_highlight}->{val} ]; 6498 } 6499 elsif ( !$opts{n} ) { 6500 push @rows, join( $col_sep, map { "-" x $width_for{$_} } @$cols ); 6501 } 6502 } 6503 6504 # The table data. 6505 if ( $opts{n} ) { 6506 foreach my $item ( @$data ) { 6507 push @rows, join($col_sep, map { $item->{$_} } @$cols ); 6508 } 6509 } 6510 else { 6511 my $format = join( $col_sep, 6512 map { "%$info->{$_}->{just}$width_for{$_}s" } @$cols ); 6513 foreach my $item ( @$data ) { 6514 my $row = sprintf($format, map { trunc($item->{$_}, $width_for{$_}) } @$cols ); 6515 if ( $config{color}->{val} && $item->{_color} ) { 6516 push @rows, [ $row, $item->{_color} ]; 6517 } 6518 else { 6519 push @rows, $row; 6520 } 6521 } 6522 } 6523 } 6524 6525 return @rows; 6526} 6527 6528# Aggregates a table. If $group_by is an arrayref of columns, the grouping key 6529# is the specified columns; otherwise it's just the empty string (e.g. 6530# everything is grouped as one group). 6531sub apply_group_by { 6532 my ( $tbl, $group_by, @rows ) = @_; 6533 my $meta = $tbl_meta{$tbl}; 6534 my %is_group = map { $_ => 1 } @$group_by; 6535 my @non_grp = grep { !$is_group{$_} } keys %{$meta->{cols}}; 6536 6537 my %temp_table; 6538 foreach my $row ( @rows ) { 6539 my $group_key 6540 = @$group_by 6541 ? '{' . join('}{', map { defined $_ ? $_ : '' } @{$row}{@$group_by}) . '}' 6542 : ''; 6543 $temp_table{$group_key} ||= []; 6544 push @{$temp_table{$group_key}}, $row; 6545 } 6546 6547 # Crush the rows together... 6548 my @new_rows; 6549 foreach my $key ( sort keys %temp_table ) { 6550 my $group = $temp_table{$key}; 6551 my %new_row; 6552 @new_row{@$group_by} = @{$group->[0]}{@$group_by}; 6553 foreach my $col ( @non_grp ) { 6554 my $agg = $meta->{cols}->{$col}->{agg} || 'first'; 6555 $new_row{$col} = $agg_funcs{$agg}->( map { $_->{$col} } @$group ); 6556 } 6557 push @new_rows, \%new_row; 6558 } 6559 return @new_rows; 6560} 6561 6562# set_to_tbl {{{3 6563# Unifies all the work of filtering, sorting etc. Alters the input. 6564# TODO: pull all the little pieces out into subroutines and stick events in each of them. 6565sub set_to_tbl { 6566 my ( $rows, $tbl ) = @_; 6567 my $meta = $tbl_meta{$tbl} or die "No such table $tbl in tbl_meta"; 6568 6569 # don't show / hide cxn if there's only one connection being displayed 6570 my (@visible, @group_by); 6571 my $num_cxn = scalar get_connections(); 6572 if ($num_cxn <= 1) { 6573 map { push @visible, $_ if $_ !~ /^cxn$/ } @{$meta->{visible}}; 6574 $meta->{visible} = \@visible; 6575 map { push @group_by, $_ if $_ !~ /^cxn$/ } @{$meta->{group_by}}; 6576 $meta->{group_by} = \@group_by; 6577 } 6578 # if cxn is not visible and there is now more than one connection, 6579 # make cxn visible again. assume it's not in group_by if it's not 6580 # visible 6581 else { 6582 my $has_cxn = 0; 6583 foreach my $column (@{$meta->{visible}}) { 6584 if ($column eq "cxn") { 6585 $has_cxn = 1; 6586 last; 6587 } 6588 } 6589 if (not $has_cxn) { 6590 map { push @visible, $_ if $_ !~ /^cxn$/ } @{$meta->{visible}}; 6591 $meta->{visible} = \@visible; 6592 map { push @group_by, $_ if $_ !~ /^cxn$/ } @{$meta->{group_by}}; 6593 $meta->{group_by} = \@group_by; 6594 } 6595 } 6596 6597 if ( !$meta->{pivot} ) { 6598 6599 # Hook in event listeners 6600 foreach my $listener ( @{$event_listener_for{set_to_tbl_pre_filter}} ) { 6601 $listener->set_to_tbl_pre_filter($rows, $tbl); 6602 } 6603 6604 # Apply filters. Note that if the table is pivoted, filtering and sorting 6605 # are applied later. 6606 foreach my $filter ( @{$meta->{filters}} ) { 6607 eval { 6608 @$rows = grep { $filters{$filter}->{func}->($_) } @$rows; 6609 }; 6610 if ( $EVAL_ERROR && $config{debug}->{val} ) { 6611 die $EVAL_ERROR; 6612 } 6613 } 6614 6615 foreach my $listener ( @{$event_listener_for{set_to_tbl_pre_sort}} ) { 6616 $listener->set_to_tbl_pre_sort($rows, $tbl); 6617 } 6618 6619 # Sort. Note that if the table is pivoted, sorting might have the wrong 6620 # columns and it could crash. This will only be an issue if it's possible 6621 # to toggle pivoting on and off, which it's not at the moment. 6622 if ( @$rows && $meta->{sort_func} && !$meta->{aggregate} ) { 6623 if ( $meta->{sort_dir} > 0 ) { 6624 @$rows = $meta->{sort_func}->( @$rows ); 6625 } 6626 else { 6627 @$rows = reverse $meta->{sort_func}->( @$rows ); 6628 } 6629 } 6630 6631 } 6632 6633 # Stop altering arguments now. 6634 my @rows = @$rows; 6635 6636 foreach my $listener ( @{$event_listener_for{set_to_tbl_pre_group}} ) { 6637 $listener->set_to_tbl_pre_group(\@rows, $tbl); 6638 } 6639 6640 # Apply group-by. 6641 if ( $meta->{aggregate} ) { 6642 @rows = apply_group_by($tbl, $meta->{group_by}, @rows); 6643 6644 # Sort. Note that if the table is pivoted, sorting might have the wrong 6645 # columns and it could crash. This will only be an issue if it's possible 6646 # to toggle pivoting on and off, which it's not at the moment. 6647 if ( @rows && $meta->{sort_func} ) { 6648 if ( $meta->{sort_dir} > 0 ) { 6649 @rows = $meta->{sort_func}->( @rows ); 6650 } 6651 else { 6652 @rows = reverse $meta->{sort_func}->( @rows ); 6653 } 6654 } 6655 6656 } 6657 6658 foreach my $listener ( @{$event_listener_for{set_to_tbl_pre_colorize}} ) { 6659 $listener->set_to_tbl_pre_colorize(\@rows, $tbl); 6660 } 6661 6662 if ( !$meta->{pivot} ) { 6663 # Colorize. Adds a _color column to rows. 6664 if ( @rows && $meta->{color_func} ) { 6665 eval { 6666 foreach my $row ( @rows ) { 6667 $row->{_color} = $meta->{color_func}->($row); 6668 } 6669 }; 6670 if ( $EVAL_ERROR ) { 6671 pause($EVAL_ERROR); 6672 } 6673 } 6674 } 6675 6676 foreach my $listener ( @{$event_listener_for{set_to_tbl_pre_transform}} ) { 6677 $listener->set_to_tbl_pre_transform(\@rows, $tbl); 6678 } 6679 6680 # Apply_transformations. 6681 if ( @rows ) { 6682 my $cols = $meta->{cols}; 6683 foreach my $col ( keys %{$rows->[0]} ) { 6684 # Don't auto-vivify $tbl_meta{tbl}-{cols}->{_color}->{trans} 6685 next if $col eq '_color'; 6686 foreach my $trans ( @{$cols->{$col}->{trans}} ) { 6687 map { $_->{$col} = $trans_funcs{$trans}->($_->{$col}) } @rows; 6688 } 6689 } 6690 } 6691 6692 my ($fmt_cols, $fmt_meta); 6693 6694 # Pivot. 6695 if ( $meta->{pivot} ) { 6696 6697 foreach my $listener ( @{$event_listener_for{set_to_tbl_pre_pivot}} ) { 6698 $listener->set_to_tbl_pre_pivot(\@rows, $tbl); 6699 } 6700 6701 my @vars = @{$meta->{visible}}; 6702 my @tmp = map { { name => $_ } } @vars; 6703 my @cols = 'name'; 6704 foreach my $i ( 0..@$rows-1 ) { 6705 my $col = "set_$i"; 6706 push @cols, $col; 6707 foreach my $j ( 0..@vars-1 ) { 6708 $tmp[$j]->{$col} = $rows[$i]->{$vars[$j]}; 6709 } 6710 } 6711 $fmt_meta = { map { $_ => { hdr => $_, just => '-' } } @cols }; 6712 $fmt_cols = \@cols; 6713 @rows = @tmp; 6714 6715 # Hook in event listeners 6716 foreach my $listener ( @{$event_listener_for{set_to_tbl_pre_filter}} ) { 6717 $listener->set_to_tbl_pre_filter($rows, $tbl); 6718 } 6719 6720 # Apply filters. 6721 foreach my $filter ( @{$meta->{filters}} ) { 6722 eval { 6723 @rows = grep { $filters{$filter}->{func}->($_) } @rows; 6724 }; 6725 if ( $EVAL_ERROR && $config{debug}->{val} ) { 6726 die $EVAL_ERROR; 6727 } 6728 } 6729 6730 foreach my $listener ( @{$event_listener_for{set_to_tbl_pre_sort}} ) { 6731 $listener->set_to_tbl_pre_sort($rows, $tbl); 6732 } 6733 6734 # Sort. 6735 if ( @rows && $meta->{sort_func} ) { 6736 if ( $meta->{sort_dir} > 0 ) { 6737 @rows = $meta->{sort_func}->( @rows ); 6738 } 6739 else { 6740 @rows = reverse $meta->{sort_func}->( @rows ); 6741 } 6742 } 6743 6744 } 6745 else { 6746 # If the table isn't pivoted, just show all columns that are supposed to 6747 # be shown; but eliminate aggonly columns if the table isn't aggregated. 6748 my $aggregated = $meta->{aggregate}; 6749 $fmt_cols = [ grep { $aggregated || !$meta->{cols}->{$_}->{aggonly} } @{$meta->{visible}} ]; 6750 $fmt_meta = { map { $_ => $meta->{cols}->{$_} } @$fmt_cols }; 6751 6752 # If the table is aggregated, re-order the group_by columns to the left of 6753 # the display, and suppress 'agghide' columns. 6754 if ( $aggregated ) { 6755 my %is_group = map { $_ => 1 } @{$meta->{group_by}}; 6756 $fmt_cols = [ @{$meta->{group_by}}, grep { !$is_group{$_} } @$fmt_cols ]; 6757 $fmt_cols = [ grep { !$meta->{cols}->{$_}->{agghide} } @$fmt_cols ]; 6758 } 6759 } 6760 6761 foreach my $listener ( @{$event_listener_for{set_to_tbl_pre_create}} ) { 6762 $listener->set_to_tbl_pre_create(\@rows, $tbl); 6763 } 6764 6765 @rows = create_table( $fmt_cols, $fmt_meta, \@rows); 6766 if ( !$meta->{hide_caption} && !$opts{n} && $config{display_table_captions}->{val} ) { 6767 @rows = create_caption($meta->{capt}, @rows) 6768 } 6769 6770 foreach my $listener ( @{$event_listener_for{set_to_tbl_post_create}} ) { 6771 $listener->set_to_tbl_post_create(\@rows, $tbl); 6772 } 6773 6774 return @rows; 6775} 6776 6777# meta_to_hdr {{{3 6778sub meta_to_hdr { 6779 my $tbl = shift; 6780 my $meta = $tbl_meta{$tbl}; 6781 my %labels = map { $_ => $meta->{cols}->{$_}->{hdr} } @{$meta->{visible}}; 6782 return \%labels; 6783} 6784 6785# commify {{{3 6786# From perlfaq5: add commas. 6787sub commify { 6788 my ( $num ) = @_; 6789 $num = 0 unless defined $num; 6790 $num =~ s/(^[-+]?\d+?(?=(?>(?:\d{3})+)(?!\d))|\G\d{3}(?=\d))/$1,/g; 6791 return $num; 6792} 6793 6794# set_precision {{{3 6795# Trim to desired precision. 6796sub set_precision { 6797 my ( $num, $precision ) = @_; 6798 $num = 0 unless defined $num; 6799 $precision = $config{num_digits}->{val} if !defined $precision; 6800 sprintf("%.${precision}f", $num); 6801} 6802 6803# percent {{{3 6804# Convert to percent 6805sub percent { 6806 my ( $num ) = @_; 6807 $num = 0 unless defined $num; 6808 my $digits = $config{num_digits}->{val}; 6809 return sprintf("%.${digits}f", $num * 100) 6810 . ($config{show_percent}->{val} ? '%' : ''); 6811} 6812 6813# sparkify {{{3 6814# Find the range (min to max) and divide it up. Each value then gets put into 6815# a bucket and represented by one of these characters: _.-=^ 6816sub sparkify { 6817 my @vals = @_; 6818 my @chars = qw(_ . - = ^); 6819 my $min = min(@vals); 6820 my $max = max(@vals); 6821 my $range = ($max - $min) / 4; 6822 return "_" x scalar(@vals) if !$min || !$max || $max == $min || !$range; 6823 my $result = ""; 6824 foreach my $v ( @vals ) { 6825 $result .= $chars[ int(($v - $min) / $range) ]; 6826 } 6827 return $result; 6828} 6829 6830# shorten {{{3 6831sub shorten { 6832 my ( $num, $opts ) = @_; 6833 6834 return $num if !defined($num) || $opts{n} || $num !~ m/$num_regex/; 6835 6836 $opts ||= {}; 6837 my $pad = defined $opts->{pad} ? $opts->{pad} : ''; 6838 my $num_digits = defined $opts->{num_digits} 6839 ? $opts->{num_digits} 6840 : $config{num_digits}->{val}; 6841 my $force = defined $opts->{force}; 6842 6843 my $n = 0; 6844 while ( $num >= 1_024 ) { 6845 $num /= 1_024; 6846 ++$n; 6847 } 6848 return $num =~ m/\./ || $n || $force 6849 ? sprintf("%.${num_digits}f%s", $num, ($pad,'k','M','G','T')[$n]) 6850 : $num; 6851} 6852 6853# Utility functions {{{2 6854# unique {{{3 6855sub unique { 6856 my %seen; 6857 return grep { !$seen{$_}++ } @_; 6858} 6859 6860# make_color_func {{{3 6861sub make_color_func { 6862 my ( $tbl ) = @_; 6863 my @criteria; 6864 foreach my $spec ( @{$tbl->{colors}} ) { 6865 next unless exists $comp_ops{$spec->{op}}; 6866 my $val = $spec->{op} =~ m/^(?:eq|ne|le|ge|lt|gt)$/ ? "'$spec->{arg}'" 6867 : $spec->{op} =~ m/^(?:=~|!~)$/ ? "m/" . quotemeta($spec->{arg}) . "/" 6868 : $spec->{arg}; 6869 push @criteria, 6870 "( defined \$set->{$spec->{col}} && \$set->{$spec->{col}} $spec->{op} $val ) { return '$spec->{color}'; }"; 6871 } 6872 return undef unless @criteria; 6873 my $sub = eval 'sub { my ( $set ) = @_; if ' . join(" elsif ", @criteria) . '}'; 6874 die if $EVAL_ERROR; 6875 return $sub; 6876} 6877 6878# make_sort_func {{{3 6879# Gets a list of sort columns from the table, like "+cxn -time" and returns a 6880# subroutine that will sort that way. 6881sub make_sort_func { 6882 my ( $tbl ) = @_; 6883 my @criteria; 6884 6885 # Pivoted tables can be sorted by 'name' and set_x columns; others must be 6886 # sorted by existing columns. TODO: this will crash if you toggle between 6887 # pivoted and nonpivoted. I have several other 'crash' notes about this if 6888 # this ever becomes possible. 6889 6890 if ( $tbl->{pivot} ) { 6891 # Sort type is not really possible on pivoted columns, because a 'column' 6892 # contains data from an entire non-pivoted row, so there could be a mix of 6893 # numeric and non-numeric data. Thus everything has to be 'cmp' type. 6894 foreach my $col ( split(/\s+/, $tbl->{sort_cols} ) ) { 6895 next unless $col; 6896 my ( $dir, $name ) = $col =~ m/([+-])?(\w+)$/; 6897 next unless $name && $name =~ m/^(?:name|set_\d+)$/; 6898 $dir ||= '+'; 6899 my $op = 'cmp'; 6900 my $df = "''"; 6901 push @criteria, 6902 $dir eq '+' 6903 ? "(\$a->{$name} || $df) $op (\$b->{$name} || $df)" 6904 : "(\$b->{$name} || $df) $op (\$a->{$name} || $df)"; 6905 } 6906 } 6907 else { 6908 foreach my $col ( split(/\s+/, $tbl->{sort_cols} ) ) { 6909 next unless $col; 6910 my ( $dir, $name ) = $col =~ m/([+-])?(\w+)$/; 6911 next unless $name && $tbl->{cols}->{$name}; 6912 $dir ||= '+'; 6913 my $op = $tbl->{cols}->{$name}->{num} ? "<=>" : "cmp"; 6914 my $df = $tbl->{cols}->{$name}->{num} ? "0" : "''"; 6915 push @criteria, 6916 $dir eq '+' 6917 ? "(\$a->{$name} || $df) $op (\$b->{$name} || $df)" 6918 : "(\$b->{$name} || $df) $op (\$a->{$name} || $df)"; 6919 } 6920 } 6921 return sub { return @_ } unless @criteria; 6922 my $sub = eval 'sub { sort {' . join("||", @criteria) . '} @_; }'; 6923 die if $EVAL_ERROR; 6924 return $sub; 6925} 6926 6927# trunc {{{3 6928# Shortens text to specified length. 6929sub trunc { 6930 my ( $text, $len ) = @_; 6931 if ( length($text) <= $len ) { 6932 return $text; 6933 } 6934 return substr($text, 0, $len); 6935} 6936 6937# donut {{{3 6938# Takes out the middle of text to shorten it. 6939sub donut { 6940 my ( $text, $len ) = @_; 6941 return $text if length($text) <= $len; 6942 my $max = length($text) - $len; 6943 my $min = $max - 1; 6944 6945 # Try to remove a single "word" from somewhere in the center 6946 if ( $text =~ s/_[^_]{$min,$max}_/_/ ) { 6947 return $text; 6948 } 6949 6950 # Prefer removing the end of a "word" 6951 if ( $text =~ s/([^_]+)[^_]{$max}_/$1_/ ) { 6952 return $text; 6953 } 6954 6955 $text = substr($text, 0, int($len/2)) 6956 . "_" 6957 . substr($text, int($len/2) + $max + 1); 6958 return $text; 6959} 6960 6961# crunch {{{3 6962# Removes vowels and compacts repeated letters to shorten text. 6963sub crunch { 6964 my ( $text, $len ) = @_; 6965 return $text if $len && length($text) <= $len; 6966 $text =~ s/^IB_\w\w_//; 6967 $text =~ s/(?<![_ ])[aeiou]//g; 6968 $text =~ s/(.)\1+/$1/g; 6969 return $text; 6970} 6971 6972# collapse_ws {{{3 6973# Collapses all whitespace to a single space. 6974sub collapse_ws { 6975 my ( $text ) = @_; 6976 return '' unless defined $text; 6977 $text =~ s/\s+/ /g; 6978 return $text; 6979} 6980 6981# Strips out non-printable characters within fields, which freak terminals out. 6982sub no_ctrl_char { 6983 my ( $text ) = @_; 6984 return '' unless defined $text; 6985 my $charset = $config{charset}->{val}; 6986 if ( $charset && $charset eq 'unicode' ) { 6987 $text =~ s/ 6988 ("(?:(?!(?<!\\)").)*" # Double-quoted string 6989 |'(?:(?!(?<!\\)').)*') # Or single-quoted string 6990 /$1 =~ m#\p{IsC}# ? "[BINARY]" : $1/egx; 6991 } 6992 elsif ( $charset && $charset eq 'none' ) { 6993 $text =~ s/ 6994 ("(?:(?!(?<!\\)").)*" 6995 |'(?:(?!(?<!\\)').)*') 6996 /[TEXT]/gx; 6997 } 6998 else { # The default is 'ascii' 6999 $text =~ s/ 7000 ("(?:(?!(?<!\\)").)*" 7001 |'(?:(?!(?<!\\)').)*') 7002 /$1 =~ m#[^\040-\176]# ? "[BINARY]" : $1/egx; 7003 } 7004 return $text; 7005} 7006 7007# word_wrap {{{3 7008# Wraps text at word boundaries so it fits the screen. 7009sub word_wrap { 7010 my ( $text, $width) = @_; 7011 $width ||= $this_term_size[0]; 7012 $text =~ s/(.{0,$width})(?:\s+|$)/$1\n/g; 7013 $text =~ s/ +$//mg; 7014 return $text; 7015} 7016 7017# draw_screen {{{3 7018# Prints lines to the screen. The first argument is an arrayref. Each 7019# element of the array is either a string or an arrayref. If it's a string it 7020# just gets printed. If it's an arrayref, the first element is the string to 7021# print, and the second is args to colored(). 7022sub draw_screen { 7023 my ( $display_lines, $prefs ) = @_; 7024 if ( !$opts{n} && $config{show_statusbar}->{val} ) { 7025 unshift @$display_lines, create_statusbar(); 7026 } 7027 7028 foreach my $listener ( @{$event_listener_for{draw_screen}} ) { 7029 $listener->draw_screen($display_lines); 7030 } 7031 7032 $clear_screen_sub->() 7033 if $prefs->{clear} || !$modes{$config{mode}->{val}}->{no_clear_screen}; 7034 if ( $opts{n} || $prefs->{raw} ) { 7035 my $num_lines = 0; 7036 my $ts = $opts{t} ? POSIX::strftime($config{timeformat}->{val}, localtime) : ''; 7037 if ( $opts{t} ) { 7038 if ( $opts{t} == 1 ) { 7039 print "\n$ts\n\n"; 7040 $ts = ""; # Prevent it from being written on every line. 7041 $num_lines++; 7042 } 7043 else { 7044 $ts .= " "; 7045 } 7046 } 7047 print join("\n", 7048 map { 7049 $num_lines++; 7050 ref $_ 7051 ? colored($ts . $_->[0], $_->[1]) 7052 : $ts . $_; 7053 } 7054 grep { !$opts{n} || $_ } # Suppress empty lines 7055 @$display_lines); 7056 if ( $opts{n} && $num_lines ) { 7057 print "\n"; 7058 } 7059 } 7060 else { 7061 my $max_lines = $prefs->{show_all} 7062 ? scalar(@$display_lines)- 1 7063 : min(scalar(@$display_lines), $this_term_size[1]); 7064 print join("\n", 7065 map { 7066 ref $_ 7067 ? colored(substr($_->[0], 0, $this_term_size[0]), $_->[1]) 7068 : substr($_, 0, $this_term_size[0]); 7069 } @$display_lines[0..$max_lines - 1]); 7070 } 7071} 7072 7073# fuzzy_time {{{3 7074sub fuzzy_time { 7075 my ( $secs ) = @_; 7076 return '' unless $secs; 7077 return sprintf('%.2f', $secs) if $secs =~ m/^.\./; 7078 $secs =~ s/\..*$//; 7079 return $secs < 180 ? "${secs}s" 7080 : $secs < 3600 ? sprintf("%dm", $secs / 60) 7081 : $secs < 3600 * 3 ? sprintf("%dh%dm", $secs / 3600, ($secs % 3600) / 60) 7082 : $secs < 86400 ? sprintf("%dh", $secs / 3600) 7083 : $secs < 86400* 3 ? sprintf("%dd%dh", $secs / 86400, ($secs % 86400) / 3600) 7084 : sprintf("%dd", $secs / 86400); 7085} 7086 7087sub fuzzy_to_secs { 7088 my ($t) = @_; 7089 return 0 unless $t; 7090 my ($num, $suffix) = $t =~ m/(\d+)([a-z])?$/; 7091 return $num unless $suffix; 7092 return $suffix eq 's' ? $num # Seconds 7093 : $suffix eq 'm' ? $num * 60 # Minutes 7094 : $suffix eq 'h' ? $num * 3600 # Hours 7095 : $num * 86400; # Days 7096} 7097 7098# distill {{{3 7099sub distill { 7100 my ( $query ) = @_; 7101 return "" unless $query; 7102 my $orig_query = $query; 7103 7104 $query =~ m/\A\s*call\s+(\S+)\(/i && return "CALL $1"; 7105 $query =~ m/\A\s*use\s+/ && return "USE"; 7106 $query =~ m/\A\s*UNLOCK TABLES/i && return "UNLOCK"; 7107 $query =~ m/\A\s*xa\s+(\S+)/i && return "XA_$1"; 7108 7109 # Strip out comments 7110 my $olc_re = qr/(?:--|#)[^'"\r\n]*(?=[\r\n]|\Z)/; # One-line comments 7111 my $mlc_re = qr#/\*[^!].*?\*/#sm; # But not /*!version */ 7112 my $vlc_re = qr#/\*.*?[0-9+].*?\*/#sm; # For SHOW + /*!version */ 7113 my $vlc_rf = qr#^(SHOW).*?/\*![0-9+].*?\*/#sm; # Variation for SHOW 7114 $query =~ s/$olc_re//go; 7115 $query =~ s/$mlc_re//go; 7116 if ( $query =~ m/$vlc_rf/i ) { # contains show + version 7117 $query =~ s/$vlc_re//go; 7118 } 7119 7120 # Handle SHOW queries 7121 if ( $query =~ m/\A\s*SHOW\s+/i ) { 7122 $query = uc $query; 7123 $query =~ s/\s+(?:GLOBAL|SESSION|FULL|STORAGE|ENGINE)\b/ /g; 7124 $query =~ s/\s+COUNT[^)]+\)//g; 7125 $query =~ s/\s+(?:FOR|FROM|LIKE|WHERE|LIMIT|IN)\b.+//ms; 7126 $query =~ s/\A(SHOW(?:\s+\S+){1,2}).*\Z/$1/s; 7127 $query =~ s/\s+/ /g; 7128 } 7129 7130 # Find verbs and tables. 7131 my ($verbs, $table); 7132 7133 # Handle DDL operations (dds) 7134 my $tbl_ident = qr/(?:`[^`]+`|\w+)(?:\.(?:`[^`]+`|\w+))?/; 7135 my $tbl_regex = qr{ 7136 \b(?:FROM|JOIN|(?<!KEY\s)UPDATE|INTO(?!\s+TABLE)|INTO\s+TABLE) # Words that precede table names 7137 \b\s* 7138 \(? # Optional paren around tables 7139 ($tbl_ident 7140 (?: (?:\s+ (?:AS\s+)? \w+)?, \s*$tbl_ident )* 7141 ) 7142 }xio; 7143 my ( $ddl ) = $query =~ m/^\s*((?:CREATE|ALTER|TRUNCATE|DROP|RENAME|CHECK|REPAIR))\b/i; 7144 if ( $ddl ) { 7145 my ( $obj ) = $query =~ m/$ddl.+(DATABASE|EVENT|FUNCTION|INDEX|LOGFILE GROUP|PROCEDURE|SERVER|TABLE|TABLESPACE|TRIGGER|VIEW)\b/i; 7146 if ( $obj ) { 7147 $query =~ s/$obj\s*if\s*(?:not\s*)?exists/$obj/i; 7148 $verbs = uc($ddl . ($obj ? " $obj" : '')); 7149 ($table) = $query =~ m/(?:TABLE|DATABASE)\s+($tbl_ident)(\s+.*)?/i; 7150 } 7151 } 7152 else { 7153 my $verbs_pat = qr{^SHOW|^FLUSH|^COMMIT|^ROLLBACK|^BEGIN|SELECT|INSERT 7154 |UPDATE|DELETE|REPLACE|^SET|UNION|^START|^LOCK|^LOAD}xi; 7155 my @verbs = $query =~ m/\b($verbs_pat)\b/gio; 7156 @verbs = do { 7157 my $last = ''; 7158 grep { my $pass = $_ ne $last; $last = $_; $pass } map { uc } @verbs; 7159 }; 7160 7161 if ( ($verbs[0] || '') eq 'SELECT' && @verbs > 1 ) { 7162 # False-positive verbs after SELECT 7163 my $union = grep { $_ eq 'UNION' } @verbs; 7164 @verbs = $union ? qw(SELECT UNION) : qw(SELECT); 7165 } 7166 7167 my %seen; 7168 $verbs = join(q{ }, grep { !$seen{$_}++ } @verbs); 7169 } 7170 7171 if ( $verbs && $verbs =~ m/^SHOW/ ) { 7172 my %alias_for = qw( 7173 SCHEMA DATABASE 7174 KEYS INDEX 7175 INDEXES INDEX 7176 ); 7177 map { $verbs =~ s/$_/$alias_for{$_}/ } keys %alias_for; 7178 $query = $verbs; 7179 } 7180 else { 7181 my @tables; 7182 $query =~ s/ (?:LOW_PRIORITY|IGNORE|STRAIGHT_JOIN)//ig; 7183 if ( $query =~ /^\s*LOCK\s+TABLES/i ) { 7184 $query =~ s/^(\s*LOCK\s+TABLES\s+)//i; 7185 $query =~ s/\s+(?:READ|WRITE|LOCAL)+\s*//gi; 7186 $query = "FROM $query"; 7187 } 7188 $query =~ s/\\["']//g; # quoted strings 7189 $query =~ s/".*?"/?/sg; # quoted strings 7190 $query =~ s/'.*?'/?/sg; # quoted strings 7191 7192 foreach my $tbls ( $query =~ m/$tbl_regex/gio ) { 7193 next if $tbls =~ m/\ASELECT\b/i; 7194 foreach my $tbl ( split(',', $tbls) ) { 7195 $tbl =~ s/\s*($tbl_ident)(\s+.*)?/$1/gio; 7196 if ( $tbl !~ m/[a-zA-Z]/ ) { 7197 # Skip suspicious table name 7198 next; 7199 } 7200 push @tables, $tbl; 7201 } 7202 } 7203 7204 # If we have a bunch of tables like db1.tbl1 db1.tbl2, convert to 7205 # db1.tbl1 -.tbl2 etc. Also remove repeated tables, and strip `quotes`. 7206 $query = $verbs; 7207 my $prev = ''; 7208 foreach my $t ( @tables, $table ) { 7209 next unless $t; 7210 $t =~ s/`//g; 7211 next if $t eq $prev; 7212 my ($prefix, undef) = split(/\./, $prev); 7213 $prev = $t; 7214 if ( $prefix ) { 7215 $prefix=~s/\(/\\\(/; 7216 $prefix=~s/\)/\\\)/; 7217 $t =~ s/^$prefix\./-./; 7218 } 7219 $query .= " " . $t; 7220 } 7221 } 7222 7223 # die $orig_query if $query eq 'LOCK lock'; 7224 return $query; 7225} 7226 7227# secs_to_time {{{3 7228sub secs_to_time { 7229 my ( $secs, $fmt ) = @_; 7230 $secs ||= 0; 7231 7232 # If the inbound value has a decimal point, then format the seconds with milliseconds. 7233 my $hires = $secs =~ m/\./ ? '%06.3f' : '%02d'; 7234 7235 if ( !$secs ) { 7236 return sprintf("00:$hires", $secs); 7237 } 7238 7239 # Decide what format to use, if not given 7240 $fmt ||= $secs >= 86_400 ? 'd' 7241 : $secs >= 3_600 ? 'h' 7242 : 'm'; 7243 7244 return 7245 $fmt eq 'd' ? sprintf( 7246 "%d+%02d:%02d:$hires", 7247 int($secs / 86_400), 7248 int(($secs % 86_400) / 3_600), 7249 int(($secs % 3_600) / 60), 7250 $secs % 60 + ($secs - int($secs))) 7251 : $fmt eq 'h' ? sprintf( 7252 "%02d:%02d:$hires", 7253 int(($secs % 86_400) / 3_600), 7254 int(($secs % 3_600) / 60), 7255 $secs % 60 + ($secs - int($secs))) 7256 : sprintf( 7257 "%02d:$hires", 7258 int(($secs % 3_600) / 60), 7259 $secs % 60 + ($secs - int($secs))); 7260} 7261 7262# dulint_to_int {{{3 7263# Takes a number that InnoDB formats as two ulint integers, like transaction IDs 7264# and such, and turns it into a single integer 7265sub dulint_to_int { 7266 my $num = shift; 7267 return 0 unless $num; 7268 my ( $high, $low ) = $num =~ m/^(\d+) (\d+)$/; 7269 return $low unless $high; 7270 return $low + ( $high * $MAX_ULONG ); 7271} 7272 7273# create_statusbar {{{3 7274sub create_statusbar { 7275 my $mode = $config{mode}->{val}; 7276 my @cxns = sort { $a cmp $b } get_connections(); 7277 7278 my $modeline = ( $config{readonly}->{val} ? '[RO] ' : '' ) 7279 . $modes{$mode}->{hdr} . " (? for help)"; 7280 my $mode_width = length($modeline); 7281 my $remaining_width = $this_term_size[0] - $mode_width - 1; 7282 my $result; 7283 7284 # The thingie in top-right that says what we're monitoring. 7285 my $cxn = ''; 7286 7287 if ( 1 == @cxns && $dbhs{$cxns[0]} && $dbhs{$cxns[0]}->{dbh} ) { 7288 $cxn = $dbhs{$cxns[0]}->{dbh}->{mysql_serverinfo} || ''; 7289 } 7290 else { 7291 if ( $modes{$mode}->{server_group} ) { 7292 $cxn = "Servers: " . $modes{$mode}->{server_group}; 7293 my $err_count = grep { $dbhs{$_} && $dbhs{$_}->{failed} } @cxns; 7294 if ( $err_count ) { 7295 $cxn .= "(" . ( scalar(@cxns) - $err_count ) . "/" . scalar(@cxns) . ")"; 7296 } 7297 } 7298 else { 7299 $cxn = join(' ', map { ($dbhs{$_}->{failed} ? '!' : '') . $_ } 7300 grep { $dbhs{$_} } @cxns); 7301 } 7302 } 7303 7304 if ( 1 == @cxns ) { 7305 get_driver_status(@cxns); 7306 my $vars = $vars{$cxns[0]}->{$clock}; 7307 my $inc = inc(0, $cxns[0]); 7308 7309 # Format server uptime human-readably, calculate QPS... 7310 my $uptime = fuzzy_time( $vars->{Uptime_hires} ); 7311 my $qps = ($inc->{Queries}||0) / ($inc->{Uptime_hires}||1); 7312 my $ibinfo = ''; 7313 7314 if ( exists $vars->{IB_last_secs} ) { 7315 $ibinfo .= "InnoDB $vars->{IB_last_secs}s "; 7316 if ( $vars->{IB_got_all} ) { 7317 if ( ($mode eq 'T' || $mode eq 'W') 7318 && $vars->{IB_tx_is_truncated} ) { 7319 $ibinfo .= ':^|'; 7320 } 7321 else { 7322 $ibinfo .= ':-)'; 7323 } 7324 } 7325 else { 7326 $ibinfo .= ':-('; 7327 } 7328 } 7329 $result = sprintf( 7330 "%-${mode_width}s %${remaining_width}s", 7331 $modeline, 7332 join(', ', grep { $_ } ( 7333 $cxns[0], 7334 $uptime, 7335 $ibinfo, 7336 shorten($qps) . " QPS", 7337 ($vars->{Threads} || 0) . "/" . ($vars->{Threads_running} || 0) . "/" . ($vars->{Threads_cached} || 0) . " con/run/cac thds", 7338 $cxn))); 7339 } 7340 else { 7341 $result = sprintf( 7342 "%-${mode_width}s %${remaining_width}s", 7343 $modeline, 7344 $cxn); 7345 } 7346 7347 return $config{color}->{val} ? [ $result, 'bold reverse' ] : $result; 7348} 7349 7350# Database connections {{{3 7351sub add_new_dsn { 7352 my ( $name, $dsn, $dl_table, $have_user, $user, $have_pass, $pass, $savepass ) = @_; 7353 7354 if ( defined $name ) { 7355 $name =~ s/[\s:;]//g; 7356 } 7357 7358 if ( !$name ) { 7359 print word_wrap("Choose a name for the connection. It cannot contain " 7360 . "whitespace, colons or semicolons."), "\n\n"; 7361 do { 7362 $name = prompt("Enter a name"); 7363 $name =~ s/[\s:;]//g; 7364 } until ( $name ); 7365 } 7366 7367 if ( !$dsn ) { 7368 do { 7369 $clear_screen_sub->(); 7370 print "Typical DSN strings look like\n DBI:mysql:;host=hostname;port=port\n" 7371 . "The db and port are optional and can usually be omitted.\n" 7372 . "If you specify 'mysql_read_default_group=mysql' many options can be read\n" 7373 . "from your mysql options files (~/.my.cnf, /etc/my.cnf).\n\n"; 7374 $dsn = prompt("Enter a DSN string", undef, "DBI:mysql:;mysql_read_default_group=mysql;host=$name"); 7375 } until ( $dsn ); 7376 } 7377 if ( !$dl_table ) { 7378 $clear_screen_sub->(); 7379 my $dl_table = prompt("Optional: enter a table (must not exist) to use when resetting InnoDB deadlock information", 7380 undef, 'test.innotop_dl'); 7381 } 7382 7383 $connections{$name} = { 7384 dsn => $dsn, 7385 dl_table => $dl_table, 7386 have_user => $have_user, 7387 user => $user, 7388 have_pass => $have_pass, 7389 pass => $pass, 7390 savepass => $savepass 7391 }; 7392} 7393 7394sub add_new_server_group { 7395 my ( $name ) = @_; 7396 7397 if ( defined $name ) { 7398 $name =~ s/[\s:;]//g; 7399 } 7400 7401 if ( !$name ) { 7402 print word_wrap("Choose a name for the group. It cannot contain " 7403 . "whitespace, colons or semicolons."), "\n\n"; 7404 do { 7405 $name = prompt("Enter a name"); 7406 $name =~ s/[\s:;]//g; 7407 } until ( $name ); 7408 } 7409 7410 my @cxns; 7411 do { 7412 $clear_screen_sub->(); 7413 @cxns = select_cxn("Choose servers for $name", keys %connections); 7414 } until ( @cxns ); 7415 7416 $server_groups{$name} = \@cxns; 7417 return $name; 7418} 7419 7420sub get_var_set { 7421 my ( $name ) = @_; 7422 while ( !$name || !exists($var_sets{$config{$name}->{val}}) ) { 7423 $name = choose_var_set($name); 7424 } 7425 return $var_sets{$config{$name}->{val}}->{text}; 7426} 7427 7428sub add_new_var_set { 7429 my ( $name ) = @_; 7430 7431 if ( defined $name ) { 7432 $name =~ s/\W//g; 7433 } 7434 7435 if ( !$name ) { 7436 do { 7437 $name = prompt("Enter a name"); 7438 $name =~ s/\W//g; 7439 } until ( $name ); 7440 } 7441 7442 my $variables; 7443 do { 7444 $clear_screen_sub->(); 7445 $variables = prompt("Enter variables for $name", undef ); 7446 } until ( $variables ); 7447 7448 $var_sets{$name} = { text => $variables, user => 1 }; 7449} 7450 7451sub next_server { 7452 my $mode = $config{mode}->{val}; 7453 my @cxns = sort keys %connections; 7454 my ($cur) = get_connections($mode); 7455 $cur ||= $cxns[0]; 7456 my $pos = grep { $_ lt $cur } @cxns; 7457 my $newpos = ($pos + 1) % @cxns; 7458 $modes{$mode}->{server_group} = ''; 7459 $modes{$mode}->{connections} = [ $cxns[$newpos] ]; 7460 $clear_screen_sub->(); 7461} 7462 7463sub next_server_group { 7464 my $mode = shift || $config{mode}->{val}; 7465 my @grps = sort keys %server_groups; 7466 my $curr = $modes{$mode}->{server_group}; 7467 7468 return unless @grps; 7469 7470 if ( $curr ) { 7471 # Find the current group's position. 7472 my $pos = 0; 7473 while ( $curr ne $grps[$pos] ) { 7474 $pos++; 7475 } 7476 $modes{$mode}->{server_group} = $grps[ ($pos + 1) % @grps ]; 7477 } 7478 else { 7479 $modes{$mode}->{server_group} = $grps[0]; 7480 } 7481} 7482 7483# Get a list of connection names used in this mode. 7484sub get_connections { 7485 if ( $file ) { 7486 return qw(file); 7487 } 7488 my $mode = shift || $config{mode}->{val}; 7489 my @connections = $modes{$mode}->{server_group} 7490 ? @{$server_groups{$modes{$mode}->{server_group}}} 7491 : @{$modes{$mode}->{connections}}; 7492 if ( $modes{$mode}->{one_connection} ) { 7493 @connections = @connections ? $connections[0] : (); 7494 } 7495 # If the connections are the same as a server group, we set the mode's 7496 # group to that group. 7497 if ( ! $modes{$mode}->{server_group} ) { 7498 my $maybe_group = join(',', sort @connections); 7499 foreach my $g ( keys %server_groups ) { 7500 my $group_conns = join(',', sort @{$server_groups{$g}}); 7501 if ( $maybe_group eq $group_conns ) { 7502 $modes{$mode}->{server_group} = $g; 7503 last; 7504 } 7505 } 7506 } 7507 return unique(@connections); 7508} 7509 7510# Get a list of tables used in this mode. If innotop is running non-interactively, just use the first. 7511sub get_visible_tables { 7512 my $mode = shift || $config{mode}->{val}; 7513 my @tbls = @{$modes{$mode}->{visible_tables}}; 7514 if ( $opts{n} ) { 7515 return $tbls[0]; 7516 } 7517 else { 7518 return @tbls; 7519 } 7520} 7521 7522# Choose from among available connections or server groups. 7523# If the mode has a server set in use, prefers that instead. 7524sub choose_connections { 7525 $clear_screen_sub->(); 7526 my $mode = $config{mode}->{val}; 7527 my $meta = { map { $_ => $connections{$_}->{dsn} } keys %connections }; 7528 foreach my $group ( keys %server_groups ) { 7529 $meta->{"#$group"} = join(' ', @{$server_groups{$group}}); 7530 } 7531 7532 my $choices = prompt_list("Choose connections or a group for $mode mode", 7533 undef, sub { return keys %$meta }, $meta); 7534 7535 my @choices = unique(grep { $_ } $choices =~ m/(\S+)/g); 7536 if ( @choices ) { 7537 if ( $choices[0] =~ s/^#// && exists $server_groups{$choices[0]} ) { 7538 $modes{$mode}->{server_group} = $choices[0]; 7539 } 7540 else { 7541 $modes{$mode}->{connections} = [ grep { exists $connections{$_} } @choices ]; 7542 } 7543 } 7544} 7545 7546# Accepts a DB connection name and the name of a prepared query (e.g. status, kill). 7547# Also a list of params for the prepared query. This allows not storing prepared 7548# statements globally. Returns a $sth that's been executed. 7549# ERROR-HANDLING SEMANTICS: if the statement throws an error, propagate, but if the 7550# connection has gone away or can't connect, DO NOT. Just return undef. 7551sub do_stmt { 7552 my ( $cxn, $stmt_name, @args ) = @_; 7553 7554 return undef if $file; 7555 7556 # Test if the cxn should not even be tried 7557 return undef if $dbhs{$cxn} 7558 && $dbhs{$cxn}->{failed} 7559 && ( !$dbhs{$cxn}->{dbh} || !$dbhs{$cxn}->{dbh}->{Active} || $dbhs{$cxn}->{mode} eq $config{mode}->{val} ); 7560 7561 my $sth; 7562 my $retries = 1; 7563 my $success = 0; 7564 TRY: 7565 while ( $retries-- >= 0 && !$success ) { 7566 7567 eval { 7568 my $dbh = connect_to_db($cxn); 7569 7570 # If the prepared query doesn't exist, make it. 7571 if ( !exists $dbhs{$cxn}->{stmts}->{$stmt_name} ) { 7572 $dbhs{$cxn}->{stmts}->{$stmt_name} = $stmt_maker_for{$stmt_name}->($dbh); 7573 } 7574 7575 $sth = $dbhs{$cxn}->{stmts}->{$stmt_name}; 7576 if ( $sth ) { 7577 $sth->execute(@args); 7578 } 7579 $success = 1; 7580 }; 7581 if ( $EVAL_ERROR ) { 7582 if ( $EVAL_ERROR =~ m/$nonfatal_errs/ ) { 7583 handle_cxn_error($cxn, $EVAL_ERROR); 7584 } 7585 else { 7586 die "$cxn $stmt_name: $EVAL_ERROR"; 7587 } 7588 if ( $retries < 0 ) { 7589 $sth = undef; 7590 } 7591 } 7592 } 7593 7594 if ( $sth && $sth->{NUM_OF_FIELDS} ) { 7595 sleep($stmt_sleep_time_for{$stmt_name}) if $stmt_sleep_time_for{$stmt_name}; 7596 return $sth; 7597 } 7598} 7599 7600# Marks a connection as failed. When we sleep between redraws, we try to 7601# reopen. 7602sub handle_cxn_error { 7603 my ( $cxn, $err ) = @_; 7604 my $meta = $dbhs{$cxn}; 7605 $meta->{failed} = 1; 7606 7607 # This is used so errors that have to do with permissions needed by the current 7608 # mode will get displayed as long as we're in this mode, but get ignored if the 7609 # mode changes. 7610 $meta->{mode} = $config{mode}->{val}; 7611 7612 # Strip garbage from the error text if possible. 7613 $err =~ s/\s+/ /g; 7614 if ( $err =~ m/failed: (.*?) at \S*innotop line/ ) { 7615 $err = $1; 7616 } 7617 7618 $meta->{last_err} = $err; 7619 if ( $config{show_cxn_errors}->{val} ) { 7620 print STDERR "DB error: $cxn $err" if $config{debug}->{val}; 7621 } 7622} 7623 7624# Accepts a DB connection name and a (string) query. Returns a $sth that's been 7625# executed. 7626sub do_query { 7627 my ( $cxn, $query ) = @_; 7628 7629 return undef if $file; 7630 7631 # Test if the cxn should not even be tried 7632 return undef if $dbhs{$cxn} 7633 && $dbhs{$cxn}->{failed} 7634 && ( !$dbhs{$cxn}->{dbh} || !$dbhs{$cxn}->{dbh}->{Active} || $dbhs{$cxn}->{mode} eq $config{mode}->{val} ); 7635 7636 my $sth; 7637 my $retries = 1; 7638 my $success = 0; 7639 TRY: 7640 while ( $retries-- >= 0 && !$success ) { 7641 7642 eval { 7643 my $dbh = connect_to_db($cxn); 7644 7645 $sth = $dbh->prepare($query); 7646 $sth->execute(); 7647 $success = 1; 7648 }; 7649 if ( $EVAL_ERROR ) { 7650 if ( $EVAL_ERROR =~ m/$nonfatal_errs/ ) { 7651 handle_cxn_error($cxn, $EVAL_ERROR); 7652 } 7653 else { 7654 die $EVAL_ERROR; 7655 } 7656 if ( $retries < 0 ) { 7657 $sth = undef; 7658 } 7659 } 7660 } 7661 7662 return $sth; 7663} 7664 7665sub get_uptime { 7666 my ( $cxn ) = @_; 7667 $dbhs{$cxn}->{start_time} ||= time(); 7668 # Avoid dividing by zero 7669 return (time() - $dbhs{$cxn}->{start_time}) || .001; 7670} 7671 7672sub connect_to_db { 7673 my ( $cxn ) = @_; 7674 7675 $dbhs{$cxn} ||= { 7676 stmts => {}, # bucket for prepared statements. 7677 start_time => 0, 7678 dbh => undef, 7679 }; 7680 my $href = $dbhs{$cxn}; 7681 7682 if ( !$href->{dbh} || ref($href->{dbh}) !~ m/DBI/ || !$href->{dbh}->ping ) { 7683 my $dbh = get_new_db_connection($cxn); 7684 @{$href}{qw(dbh failed start_time stmts)} = ($dbh, 0, 0, {}); 7685 7686 # Derive and store the server's start time in hi-res 7687 my $uptime = $dbh->selectrow_hashref("show status like 'Uptime'")->{value}; 7688 $href->{start_time} = time() - $uptime; 7689 7690 # Set timeouts so an unused connection stays alive. 7691 # For example, a connection might be used in Q mode but idle in T mode. 7692 if ( version_ge($dbh, '4.0.3')) { 7693 my $timeout = $config{cxn_timeout}->{val}; 7694 $dbh->do("set session wait_timeout=$timeout, interactive_timeout=$timeout"); 7695 } 7696 } 7697 return $href->{dbh}; 7698} 7699 7700# Compares versions like 5.0.27 and 4.1.15-standard-log 7701sub version_ge { 7702 my ( $dbh, $target ) = @_; 7703 my $version = sprintf('%03d%03d%03d', $dbh->{mysql_serverinfo} =~ m/^(\d+).(\d+).(\d+)/g); 7704 return $version ge sprintf('%03d%03d%03d', $target =~ m/(\d+)/g); 7705} 7706 7707# Extracts status values that can be gleaned from the DBD driver without doing a whole query. 7708sub get_driver_status { 7709 my @cxns = @_; 7710 if ( !$info_gotten{driver_status}++ ) { 7711 foreach my $cxn ( @cxns ) { 7712 next unless $dbhs{$cxn} && $dbhs{$cxn}->{dbh} && $dbhs{$cxn}->{dbh}->{Active}; 7713 $vars{$cxn}->{$clock} ||= {}; 7714 my $vars = $vars{$cxn}->{$clock}; 7715 my %res = map { $_ =~ s/ +/_/g; $_ } $dbhs{$cxn}->{dbh}->{mysql_stat} =~ m/(\w[^:]+): ([\d\.]+)/g; 7716 map { $vars->{$_} ||= $res{$_} } keys %res; 7717 $vars->{Uptime_hires} ||= get_uptime($cxn); 7718 $vars->{cxn} = $cxn; 7719 } 7720 } 7721} 7722 7723sub get_new_db_connection { 7724 my ( $connection, $destroy ) = @_; 7725 if ( $file ) { 7726 die "You can't connect to a MySQL server while monitoring a file. This is probably a bug."; 7727 } 7728 7729 my $dsn = $connections{$connection} 7730 or die "No connection named '$connection' is defined in your configuration"; 7731 7732 # don't ask for a username if mysql_read_default_group=client is in the DSN 7733 if ( !defined $dsn->{have_user} and $dsn->{dsn} !~ /mysql_read_default_group=client/ ) { 7734 my $answer = prompt("Do you want to specify a username for $connection?", undef, 'n'); 7735 $dsn->{have_user} = $answer && $answer =~ m/1|y/i; 7736 } 7737 7738 # don't ask for a password if mysql_read_default_group=client is in the DSN 7739 if ( !defined $dsn->{have_pass} and $dsn->{dsn} !~ /mysql_read_default_group=client/ ) { 7740 my $answer = prompt("Do you want to specify a password for $connection?", undef, 'n'); 7741 $dsn->{have_pass} = $answer && $answer =~ m/1|y/i; 7742 } 7743 7744 if ( !$dsn->{user} && $dsn->{have_user} ) { 7745 my $user = $ENV{USERNAME} || $ENV{USER} || getlogin() || getpwuid($REAL_USER_ID) || undef; 7746 $dsn->{user} = prompt("Enter username for $connection", undef, $user); 7747 } 7748 7749 if ( !defined $dsn->{user} ) { 7750 $dsn->{user} = ''; 7751 } 7752 7753 if ( !$dsn->{pass} && !$dsn->{savepass} && $dsn->{have_pass} ) { 7754 $dsn->{pass} = prompt_noecho("Enter password for '$dsn->{user}' on $connection"); 7755 print "\n"; 7756 if ( !defined($dsn->{savepass}) ) { 7757 my $answer = prompt("Save password in plain text in the config file?", undef, 'y'); 7758 $dsn->{savepass} = $answer && $answer =~ m/1|y/i; 7759 } 7760 } 7761 7762 my $defaults = { 7763 AutoCommit => 1, 7764 RaiseError => 1, 7765 PrintError => 0, 7766 }; 7767 $defaults->{mysql_ssl} = $opts{ssl} if $opts{ssl}; 7768 $defaults->{mysql_ssl_ca_file} = $opts{ssl_ca_file} if $opts{ssl_ca_file}; 7769 $defaults->{mysql_ssl_ca_path} = $opts{ssl_ca_path} if $opts{ssl_ca_path}; 7770 $defaults->{mysql_ssl_verify_server_cert} = $opts{ssl_verify_server_cert} if $opts{ssl_verify_server_cert}; 7771 $defaults->{mysql_ssl_client_key} = $opts{ssl_client_key} if $opts{ssl_client_key}; 7772 $defaults->{mysql_ssl_client_cert} = $opts{ssl_client_cert} if $opts{ssl_client_cert}; 7773 $defaults->{mysql_ssl_cipher} = $opts{ssl_cipher} if $opts{ssl_cipher}; 7774 7775 my $dbh = DBI->connect( 7776 $dsn->{dsn}, $dsn->{user}, $dsn->{pass}, 7777 $defaults); 7778 $dbh->{InactiveDestroy} = 1 unless $destroy; # Can't be set in $db_options 7779 $dbh->{FetchHashKeyName} = 'NAME_lc'; # Lowercases all column names for fetchrow_hashref 7780 return $dbh; 7781} 7782 7783sub get_cxn_errors { 7784 my @cxns = @_; 7785 return () unless $config{show_cxn_errors_in_tbl}->{val}; 7786 return 7787 map { [ $_ . ': ' . $dbhs{$_}->{last_err}, 'red' ] } 7788 grep { $dbhs{$_} && $dbhs{$_}->{failed} && $dbhs{$_}->{mode} eq $config{mode}->{val} } 7789 @cxns; 7790} 7791 7792# Setup and tear-down functions {{{2 7793 7794# Takes a string and turns it into a hashref you can apply to %tbl_meta tables. The string 7795# can be in the form 'foo, bar, foo/bar, foo as bar' much like a SQL SELECT statement. 7796sub compile_select_stmt { 7797 my ($str) = @_; 7798 my @exps = $str =~ m/\s*([^,]+(?i:\s+as\s+[^,\s]+)?)\s*(?=,|$)/g; 7799 my %cols; 7800 my @visible; 7801 foreach my $exp ( @exps ) { 7802 my ( $text, $colname ); 7803 if ( $exp =~ m/as\s+(\w+)\s*/ ) { 7804 $colname = $1; 7805 $exp =~ s/as\s+(\w+)\s*//; 7806 $text = $exp; 7807 } 7808 else { 7809 $text = $colname = $exp; 7810 } 7811 my ($func, $err) = compile_expr($text); 7812 $cols{$colname} = { 7813 src => $text, 7814 hdr => $colname, 7815 num => 0, 7816 func => $func, 7817 }; 7818 push @visible, $colname; 7819 } 7820 return (\%cols, \@visible); 7821} 7822 7823# compile_filter {{{3 7824sub compile_filter { 7825 my ( $text ) = @_; 7826 my ( $sub, $err ); 7827 eval "\$sub = sub { my \$set = shift; $text }"; 7828 if ( $EVAL_ERROR ) { 7829 $EVAL_ERROR =~ s/at \(eval.*$//; 7830 $sub = sub { return $EVAL_ERROR }; 7831 $err = $EVAL_ERROR; 7832 } 7833 return ( $sub, $err ); 7834} 7835 7836# compile_expr {{{3 7837sub compile_expr { 7838 my ( $expr ) = @_; 7839 # Leave built-in functions alone so they get called as Perl functions, unless 7840 # they are the only word in $expr, in which case treat them as hash keys. 7841 if ( $expr =~ m/\W/ ) { 7842 $expr =~ s/(?<!\{|\$)\b([A-Za-z]\w{2,})\b/is_func($1) ? $1 : "\$set->{$1}"/eg; 7843 } 7844 else { 7845 $expr = "\$set->{$expr}"; 7846 } 7847 my ( $sub, $err ); 7848 my $quoted = quotemeta($expr); 7849 eval qq{ 7850 \$sub = sub { 7851 my (\$set, \$cur, \$pre) = \@_; 7852 my \$val = eval { $expr }; 7853 if ( \$EVAL_ERROR && \$config{debug}->{val} ) { 7854 \$EVAL_ERROR =~ s/ at \\(eval.*//s; 7855 die "\$EVAL_ERROR in expression $quoted"; 7856 } 7857 return \$val; 7858 } 7859 }; 7860 if ( $EVAL_ERROR ) { 7861 if ( $config{debug}->{val} ) { 7862 die $EVAL_ERROR; 7863 } 7864 $EVAL_ERROR =~ s/ at \(eval.*$//; 7865 $sub = sub { return $EVAL_ERROR }; 7866 $err = $EVAL_ERROR; 7867 } 7868 return ( $sub, $err ); 7869} 7870 7871# finish {{{3 7872# This is a subroutine because it's called from a key to quit the program. 7873sub finish { 7874 save_config(); 7875 foreach my $cxn ( values %dbhs ) { 7876 eval { 7877 foreach my $sth ( values %{$cxn->{stmts}} ) { 7878 $sth->finish; 7879 } 7880 $cxn->{dbh}->disconnect; 7881 }; 7882 # Ignore eval errors, we just don't care 7883 } 7884 ReadMode('normal') unless $opts{n}; 7885 print "\n"; 7886 exit(0); 7887} 7888 7889# core_dump {{{3 7890sub core_dump { 7891 my $msg = shift; 7892 if ($config{debugfile}->{val} && $config{debug}->{val}) { 7893 eval { 7894 open my $file, '>>', $config{debugfile}->{val}; 7895 if ( %vars ) { 7896 print $file "Current variables:\n" . Dumper(\%vars); 7897 } 7898 close $file; 7899 }; 7900 } 7901 print $msg; 7902} 7903 7904# migrate_config {{{3 7905sub migrate_config { 7906 7907 my ($old_filename, $new_filename) = @_; 7908 7909 # don't proceed if old file doesn't exist 7910 if ( ! -f $old_filename ) { 7911 die "Error migrating '$old_filename': file doesn't exist.\n"; 7912 } 7913 # don't migrate files if new file exists 7914 elsif ( -f $new_filename ) { 7915 die "Error migrating '$old_filename' to '$new_filename': new file already exists.\n"; 7916 } 7917 # if migrating from one file to another in the same directory, just rename them 7918 if (dirname($old_filename) eq dirname($new_filename)) { 7919 rename($old_filename, $new_filename) 7920 or die "Can't rename '$old_filename' to '$new_filename': $OS_ERROR"; 7921 } 7922 # otherwise, move the existing conf file to a temp file, make the necessary directory structure, 7923 # and move the temp conf file to its new home 7924 else { 7925 my $tmp = File::Temp->new( TEMPLATE => 'innotopXXXXX', DIR => $homepath, SUFFIX => '.conf'); 7926 my $tmp_filename = $tmp->filename; 7927 my $dirname = dirname($new_filename); 7928 rename($old_filename, $tmp_filename) 7929 or die "Can't rename '$old_filename' to '$tmp_filename': $OS_ERROR"; 7930 mkdir($dirname) or die "Can't create directory '$dirname': $OS_ERROR"; 7931 mkdir("$dirname/plugins") or die "Can't create directory '$dirname/plugins': $OS_ERROR"; 7932 rename($tmp_filename, $new_filename) 7933 or die "Can't rename '$tmp_filename' to '$new_filename': $OS_ERROR"; 7934 } 7935} 7936 7937# load_config {{{3 7938sub load_config { 7939 7940 my ($old_filename, $answer); 7941 7942 if ( $opts{u} or $opts{p} or $opts{h} or $opts{P} or $opts{S} ) { 7943 my @params = $dsn_parser->get_cxn_params(\%opts); # dsn=$params[0] 7944 add_new_dsn($opts{h} || 'localhost', $params[0], 'test.innotop_dl', 7945 $opts{u} ? 1 : 0, $opts{u}, $opts{p} ? 1 : 0, $opts{p}); 7946 } 7947 if ($opts{c}) { 7948 $conf_file = $opts{c}; 7949 } 7950 # If we don't have a new config file but we do have an old one, 7951 # innotop got upgraded and this is an old config file. Convert it, but 7952 # don't overwrite something existing. 7953 elsif ( ! -f $default_home_conf && ( -f "$homepath/.innotop" or -f "$homepath/.innotop/innotop.ini" ) ) { 7954 $conf_file = $default_home_conf; 7955 if ( -f "$homepath/.innotop") { 7956 $old_filename = "$homepath/.innotop"; 7957 } 7958 elsif ( -f "$homepath/.innotop/innotop.ini" ) { 7959 $old_filename = "$homepath/.innotop/innotop.ini"; 7960 } 7961 $answer = pause("Innotop's default config location has moved to '$conf_file'. Move old config file '$old_filename' there now? y/n"); 7962 if ( lc $answer eq 'y' ) { 7963 migrate_config($old_filename, $conf_file); 7964 } 7965 else { 7966 print "\nInnotop will now exit so you can fix the config file.\n"; 7967 exit(0); 7968 } 7969 } 7970 elsif ( -f $default_home_conf ) { 7971 $conf_file = $default_home_conf; 7972 } 7973 elsif ( -f $default_central_conf and not $opts{s} ) { 7974 $conf_file = $default_central_conf; 7975 } 7976 else { 7977 # If no config file was loaded, set readonly to 0 if the user wants to 7978 # write a config 7979 $config{readonly}->{val} = 0 if $opts{w}; 7980 # If no connections have been defined, connect to a MySQL database 7981 # on localhost using mysql_read_default_group=client 7982 if (!%connections) { 7983 add_new_dsn('localhost', 7984 'DBI:mysql:;host=localhost;mysql_read_default_group=client', 7985 'test.innotop_dl'); 7986 } 7987 } 7988 7989 if ( -f "$conf_file" ) { 7990 open my $file, "<", $conf_file or die("Can't open '$conf_file': $OS_ERROR"); 7991 7992 # Check config file version. Just ignore if either innotop or the file has 7993 # garbage in the version number. 7994 if ( defined(my $line = <$file>) && $VERSION =~ m/\d/ ) { 7995 chomp $line; 7996 if ( my ($maj, $min, $rev) = $line =~ m/^version=(\d+)\.(\d+)(?:\.(\d+))?$/ ) { 7997 $rev ||= 0; 7998 my $cfg_ver = sprintf('%03d-%03d-%03d', $maj, $min, $rev); 7999 ( $maj, $min, $rev ) = $VERSION =~ m/^(\d+)\.(\d+)(?:\.(\d+))?$/; 8000 $rev ||= 0; 8001 my $innotop_ver = sprintf('%03d-%03d-%03d', $maj, $min, $rev); 8002 8003 if ( $cfg_ver gt $innotop_ver ) { 8004 pause("The config file is for a newer version of innotop and may not be read correctly."); 8005 } 8006 else { 8007 my @ver_history = @config_versions; 8008 while ( my ($start, $end) = splice(@ver_history, 0, 2) ) { 8009 # If the config file is between the endpoints and innotop is greater than 8010 # the endpoint, innotop has a newer config file format than the file. 8011 if ( $cfg_ver ge $start && $cfg_ver lt $end && $innotop_ver ge $end ) { 8012 my $msg = "innotop's config file format has changed. Overwrite $conf_file? y or n"; 8013 if ( pause($msg) eq 'n' ) { 8014 $config{readonly}->{val} = 1; 8015 print "\ninnotop will not save any configuration changes you make."; 8016 pause(); 8017 print "\n"; 8018 } 8019 close $file; 8020 return; 8021 } 8022 } 8023 } 8024 } 8025 } 8026 8027 while ( my $line = <$file> ) { 8028 chomp $line; 8029 next unless $line =~ m/^\[([a-z_]+)\]$/; 8030 if ( exists $config_file_sections{$1} ) { 8031 $config_file_sections{$1}->{reader}->($file); 8032 } 8033 else { 8034 warn "Unknown config file section '$1'"; 8035 } 8036 } 8037 close $file or die("Can't close $conf_file: $OS_ERROR"); 8038 } 8039 8040} 8041 8042# Do some post-processing on %tbl_meta: compile src properties into func etc. 8043sub post_process_tbl_meta { 8044 foreach my $table ( values %tbl_meta ) { 8045 foreach my $col_name ( keys %{$table->{cols}} ) { 8046 my $col_def = $table->{cols}->{$col_name}; 8047 my ( $sub, $err ) = compile_expr($col_def->{src}); 8048 $col_def->{func} = $sub; 8049 } 8050 } 8051} 8052 8053# load_config_plugins {{{3 8054sub load_config_plugins { 8055 my ( $file ) = @_; 8056 8057 # First, find a list of all plugins that exist on disk, and get information about them. 8058 my $dir = $config{plugin_dir}->{val}; 8059 foreach my $p_file ( <$dir/*.pm> ) { 8060 my ($package, $desc); 8061 eval { 8062 open my $p_in, "<", $p_file or die $OS_ERROR; 8063 while ( my $line = <$p_in> ) { 8064 chomp $line; 8065 if ( $line =~ m/^package\s+(.*?);/ ) { 8066 $package = $1; 8067 } 8068 elsif ( $line =~ m/^# description: (.*)/ ) { 8069 $desc = $1; 8070 } 8071 last if $package && $desc; 8072 } 8073 close $p_in; 8074 }; 8075 if ( $package ) { 8076 $plugins{$package} = { 8077 file => $p_file, 8078 desc => $desc, 8079 class => $package, 8080 active => 0, 8081 }; 8082 if ( $config{debug}->{val} && $EVAL_ERROR ) { 8083 die $EVAL_ERROR; 8084 } 8085 } 8086 } 8087 8088 # Now read which ones the user has activated. Each line simply represents an active plugin. 8089 while ( my $line = <$file> ) { 8090 chomp $line; 8091 next if $line =~ m/^#/; 8092 last if $line =~ m/^\[/; 8093 next unless $line && $plugins{$line}; 8094 8095 my $obj; 8096 eval { 8097 require $plugins{$line}->{file}; 8098 $obj = $line->new(%pluggable_vars); 8099 foreach my $event ( $obj->register_for_events() ) { 8100 my $queue = $event_listener_for{$event}; 8101 if ( $queue ) { 8102 push @$queue, $obj; 8103 } 8104 } 8105 }; 8106 if ( $config{debug}->{val} && $EVAL_ERROR ) { 8107 die $EVAL_ERROR; 8108 } 8109 if ( $obj ) { 8110 $plugins{$line}->{active} = 1; 8111 $plugins{$line}->{object} = $obj; 8112 } 8113 } 8114} 8115 8116# save_config_plugins {{{3 8117sub save_config_plugins { 8118 my $file = shift; 8119 foreach my $class ( sort keys %plugins ) { 8120 next unless $plugins{$class}->{active}; 8121 print $file "$class\n"; 8122 } 8123} 8124 8125# load_config_active_server_groups {{{3 8126sub load_config_active_server_groups { 8127 my ( $file ) = @_; 8128 while ( my $line = <$file> ) { 8129 chomp $line; 8130 next if $line =~ m/^#/; 8131 last if $line =~ m/^\[/; 8132 8133 my ( $mode, $group ) = $line =~ m/^(.*?)=(.*)$/; 8134 next unless $mode && $group 8135 && exists $modes{$mode} && exists $server_groups{$group}; 8136 $modes{$mode}->{server_group} = $group; 8137 } 8138} 8139 8140# save_config_active_server_groups {{{3 8141sub save_config_active_server_groups { 8142 my $file = shift; 8143 foreach my $mode ( sort keys %modes ) { 8144 print $file "$mode=$modes{$mode}->{server_group}\n"; 8145 } 8146} 8147 8148# load_config_server_groups {{{3 8149sub load_config_server_groups { 8150 my ( $file ) = @_; 8151 while ( my $line = <$file> ) { 8152 chomp $line; 8153 next if $line =~ m/^#/; 8154 last if $line =~ m/^\[/; 8155 8156 my ( $name, $rest ) = $line =~ m/^(.*?)=(.*)$/; 8157 next unless $name && $rest; 8158 my @vars = unique(grep { $_ && exists $connections{$_} } split(/\s+/, $rest)); 8159 next unless @vars; 8160 $server_groups{$name} = \@vars; 8161 } 8162} 8163 8164# save_config_server_groups {{{3 8165sub save_config_server_groups { 8166 my $file = shift; 8167 foreach my $set ( sort keys %server_groups ) { 8168 print $file "$set=", join(' ', @{$server_groups{$set}}), "\n"; 8169 } 8170} 8171 8172# load_config_varsets {{{3 8173sub load_config_varsets { 8174 my ( $file ) = @_; 8175 while ( my $line = <$file> ) { 8176 chomp $line; 8177 next if $line =~ m/^#/; 8178 last if $line =~ m/^\[/; 8179 8180 my ( $name, $rest ) = $line =~ m/^(.*?)=(.*)$/; 8181 next unless $name && $rest; 8182 $var_sets{$name} = { 8183 text => $rest, 8184 user => 1, 8185 }; 8186 } 8187} 8188 8189# save_config_varsets {{{3 8190sub save_config_varsets { 8191 my $file = shift; 8192 foreach my $varset ( sort keys %var_sets ) { 8193 next unless $var_sets{$varset}->{user}; 8194 print $file "$varset=$var_sets{$varset}->{text}\n"; 8195 } 8196} 8197 8198# load_config_group_by {{{3 8199sub load_config_group_by { 8200 my ( $file ) = @_; 8201 while ( my $line = <$file> ) { 8202 chomp $line; 8203 next if $line =~ m/^#/; 8204 last if $line =~ m/^\[/; 8205 8206 my ( $tbl , $rest ) = $line =~ m/^(.*?)=(.*)$/; 8207 next unless $tbl && exists $tbl_meta{$tbl}; 8208 my @parts = unique(grep { exists($tbl_meta{$tbl}->{cols}->{$_}) } split(/\s+/, $rest)); 8209 $tbl_meta{$tbl}->{group_by} = [ @parts ]; 8210 $tbl_meta{$tbl}->{cust}->{group_by} = 1; 8211 } 8212} 8213 8214# save_config_group_by {{{3 8215sub save_config_group_by { 8216 my $file = shift; 8217 foreach my $tbl ( sort keys %tbl_meta ) { 8218 next if $tbl_meta{$tbl}->{temp}; 8219 next unless $tbl_meta{$tbl}->{cust}->{group_by}; 8220 my $aref = $tbl_meta{$tbl}->{group_by}; 8221 print $file "$tbl=", join(' ', @$aref), "\n"; 8222 } 8223} 8224 8225# load_config_filters {{{3 8226sub load_config_filters { 8227 my ( $file ) = @_; 8228 while ( my $line = <$file> ) { 8229 chomp $line; 8230 next if $line =~ m/^#/; 8231 last if $line =~ m/^\[/; 8232 8233 my ( $key, $rest ) = $line =~ m/^(.+?)=(.*)$/; 8234 next unless $key && $rest; 8235 8236 my %parts = $rest =~ m/(\w+)='((?:(?!(?<!\\)').)*)'/g; # Properties are single-quoted 8237 next unless $parts{text} && $parts{tbls}; 8238 8239 foreach my $prop ( keys %parts ) { 8240 # Un-escape escaping 8241 $parts{$prop} =~ s/\\\\/\\/g; 8242 $parts{$prop} =~ s/\\'/'/g; 8243 } 8244 8245 my ( $sub, $err ) = compile_filter($parts{text}); 8246 my @tbls = unique(split(/\s+/, $parts{tbls})); 8247 @tbls = grep { exists $tbl_meta{$_} } @tbls; 8248 $filters{$key} = { 8249 func => $sub, 8250 text => $parts{text}, 8251 user => 1, 8252 name => $key, 8253 note => 'User-defined filter', 8254 tbls => \@tbls, 8255 } 8256 } 8257} 8258 8259# save_config_filters {{{3 8260sub save_config_filters { 8261 my $file = shift; 8262 foreach my $key ( sort keys %filters ) { 8263 next if !$filters{$key}->{user} || $filters{$key}->{quick}; 8264 my $text = $filters{$key}->{text}; 8265 $text =~ s/([\\'])/\\$1/g; 8266 my $tbls = join(" ", @{$filters{$key}->{tbls}}); 8267 print $file "$key=text='$text' tbls='$tbls'\n"; 8268 } 8269} 8270 8271# load_config_visible_tables {{{3 8272sub load_config_visible_tables { 8273 my ( $file ) = @_; 8274 while ( my $line = <$file> ) { 8275 chomp $line; 8276 next if $line =~ m/^#/; 8277 last if $line =~ m/^\[/; 8278 8279 my ( $mode, $rest ) = $line =~ m/^(.*?)=(.*)$/; 8280 next unless $mode && exists $modes{$mode}; 8281 $modes{$mode}->{visible_tables} = 8282 [ unique(grep { $_ && exists $tbl_meta{$_} } split(/\s+/, $rest)) ]; 8283 $modes{$mode}->{cust}->{visible_tables} = 1; 8284 } 8285} 8286 8287# save_config_visible_tables {{{3 8288sub save_config_visible_tables { 8289 my $file = shift; 8290 foreach my $mode ( sort keys %modes ) { 8291 next unless $modes{$mode}->{cust}->{visible_tables}; 8292 my $tables = $modes{$mode}->{visible_tables}; 8293 print $file "$mode=", join(' ', @$tables), "\n"; 8294 } 8295} 8296 8297# load_config_sort_cols {{{3 8298sub load_config_sort_cols { 8299 my ( $file ) = @_; 8300 while ( my $line = <$file> ) { 8301 chomp $line; 8302 next if $line =~ m/^#/; 8303 last if $line =~ m/^\[/; 8304 8305 my ( $key , $rest ) = $line =~ m/^(.*?)=(.*)$/; 8306 next unless $key && exists $tbl_meta{$key}; 8307 $tbl_meta{$key}->{sort_cols} = $rest; 8308 $tbl_meta{$key}->{cust}->{sort_cols} = 1; 8309 $tbl_meta{$key}->{sort_func} = make_sort_func($tbl_meta{$key}); 8310 } 8311} 8312 8313# save_config_sort_cols {{{3 8314sub save_config_sort_cols { 8315 my $file = shift; 8316 foreach my $tbl ( sort keys %tbl_meta ) { 8317 next unless $tbl_meta{$tbl}->{cust}->{sort_cols}; 8318 my $col = $tbl_meta{$tbl}->{sort_cols}; 8319 print $file "$tbl=$col\n"; 8320 } 8321} 8322 8323# load_config_active_filters {{{3 8324sub load_config_active_filters { 8325 my ( $file ) = @_; 8326 while ( my $line = <$file> ) { 8327 chomp $line; 8328 next if $line =~ m/^#/; 8329 last if $line =~ m/^\[/; 8330 8331 my ( $tbl , $rest ) = $line =~ m/^(.*?)=(.*)$/; 8332 next unless $tbl && exists $tbl_meta{$tbl}; 8333 my @parts = unique(grep { exists($filters{$_}) } split(/\s+/, $rest)); 8334 @parts = grep { grep { $tbl eq $_ } @{$filters{$_}->{tbls}} } @parts; 8335 $tbl_meta{$tbl}->{filters} = [ @parts ]; 8336 $tbl_meta{$tbl}->{cust}->{filters} = 1; 8337 } 8338} 8339 8340# save_config_active_filters {{{3 8341sub save_config_active_filters { 8342 my $file = shift; 8343 foreach my $tbl ( sort keys %tbl_meta ) { 8344 next if $tbl_meta{$tbl}->{temp}; 8345 next unless $tbl_meta{$tbl}->{cust}->{filters}; 8346 my $aref = $tbl_meta{$tbl}->{filters}; 8347 print $file "$tbl=", join(' ', @$aref), "\n"; 8348 } 8349} 8350 8351# load_config_active_columns {{{3 8352sub load_config_active_columns { 8353 my ( $file ) = @_; 8354 while ( my $line = <$file> ) { 8355 chomp $line; 8356 next if $line =~ m/^#/; 8357 last if $line =~ m/^\[/; 8358 8359 my ( $key , $rest ) = $line =~ m/^(.*?)=(.*)$/; 8360 next unless $key && exists $tbl_meta{$key}; 8361 my @parts = grep { exists($tbl_meta{$key}->{cols}->{$_}) } unique split(/ /, $rest); 8362 $tbl_meta{$key}->{visible} = [ @parts ]; 8363 $tbl_meta{$key}->{cust}->{visible} = 1; 8364 } 8365} 8366 8367# save_config_active_columns {{{3 8368sub save_config_active_columns { 8369 my $file = shift; 8370 foreach my $tbl ( sort keys %tbl_meta ) { 8371 next unless $tbl_meta{$tbl}->{cust}->{visible}; 8372 my $aref = $tbl_meta{$tbl}->{visible}; 8373 print $file "$tbl=", join(' ', @$aref), "\n"; 8374 } 8375} 8376 8377# save_config_tbl_meta {{{3 8378sub save_config_tbl_meta { 8379 my $file = shift; 8380 foreach my $tbl ( sort keys %tbl_meta ) { 8381 foreach my $col ( keys %{$tbl_meta{$tbl}->{cols}} ) { 8382 my $meta = $tbl_meta{$tbl}->{cols}->{$col}; 8383 next unless $meta->{user}; 8384 print $file "$col=", join( 8385 " ", 8386 map { 8387 # Some properties (trans) are arrays, others scalars 8388 my $val = ref($meta->{$_}) ? join(',', @{$meta->{$_}}) : $meta->{$_}; 8389 $val =~ s/([\\'])/\\$1/g; # Escape backslashes and single quotes 8390 "$_='$val'"; # Enclose in single quotes 8391 } 8392 grep { $_ ne 'func' } 8393 keys %$meta 8394 ), "\n"; 8395 } 8396 } 8397} 8398 8399# save_config_config {{{3 8400sub save_config_config { 8401 my $file = shift; 8402 foreach my $key ( sort keys %config ) { 8403 eval { 8404 if ( $key ne 'password' || $config{savepass}->{val} ) { 8405 print $file "# $config{$key}->{note}\n" 8406 or die "Cannot print to file: $OS_ERROR"; 8407 my $val = $config{$key}->{val}; 8408 $val = '' unless defined($val); 8409 if ( ref( $val ) eq 'ARRAY' ) { 8410 print $file "$key=" 8411 . join( " ", @$val ) . "\n" 8412 or die "Cannot print to file: $OS_ERROR"; 8413 } 8414 elsif ( ref( $val ) eq 'HASH' ) { 8415 print $file "$key=" 8416 . join( " ", 8417 map { "$_:$val->{$_}" } keys %$val 8418 ) . "\n"; 8419 } 8420 else { 8421 print $file "$key=$val\n"; 8422 } 8423 } 8424 }; 8425 if ( $EVAL_ERROR ) { print "$EVAL_ERROR in $key"; }; 8426 } 8427 8428} 8429 8430# load_config_config {{{3 8431sub load_config_config { 8432 my ( $file ) = @_; 8433 8434 while ( my $line = <$file> ) { 8435 chomp $line; 8436 next if $line =~ m/^#/; 8437 last if $line =~ m/^\[/; 8438 8439 my ( $name, $val ) = $line =~ m/^(.+?)=(.*)$/; 8440 next unless defined $name && defined $val; 8441 8442 # Validate the incoming values... 8443 if ( $name && exists( $config{$name} ) ) { 8444 if ( !$config{$name}->{pat} || $val =~ m/$config{$name}->{pat}/ ) { 8445 $config{$name}->{val} = $val; 8446 $config{$name}->{read} = 1; 8447 } 8448 } 8449 } 8450} 8451 8452# load_config_tbl_meta {{{3 8453sub load_config_tbl_meta { 8454 my ( $file ) = @_; 8455 8456 while ( my $line = <$file> ) { 8457 chomp $line; 8458 next if $line =~ m/^#/; 8459 last if $line =~ m/^\[/; 8460 8461 # Each tbl_meta section has all the properties defined in %col_props. 8462 my ( $col , $rest ) = $line =~ m/^(.*?)=(.*)$/; 8463 next unless $col; 8464 my %parts = $rest =~ m/(\w+)='((?:(?!(?<!\\)').)*)'/g; # Properties are single-quoted 8465 8466 # Each section read from the config file has one extra property: which table it 8467 # goes in. 8468 my $tbl = $parts{tbl} or die "There's no table for tbl_meta $col"; 8469 my $meta = $tbl_meta{$tbl} or die "There's no table in tbl_meta named $tbl"; 8470 8471 # The section is user-defined by definition (if that makes sense). 8472 $parts{user} = 1; 8473 8474 # The column may already exist in the table, in which case this is just a 8475 # customization. 8476 $meta->{cols}->{$col} ||= {}; 8477 8478 foreach my $prop ( keys %col_props ) { 8479 if ( !defined($parts{$prop}) ) { 8480 # Make it default to whatever's in col_props. 8481 $parts{$prop} = $col_props{$prop}; 8482 } 8483 8484 # Un-escape escaping 8485 $parts{$prop} =~ s/\\\\/\\/g; 8486 $parts{$prop} =~ s/\\'/'/g; 8487 8488 if ( ref $col_props{$prop} ) { 8489 if ( $prop eq 'trans' ) { 8490 $meta->{cols}->{$col}->{trans} 8491 = [ unique(grep { exists $trans_funcs{$_} } split(',', $parts{$prop})) ]; 8492 } 8493 else { 8494 $meta->{cols}->{$col}->{$prop} = [ split(',', $parts{$prop}) ]; 8495 } 8496 } 8497 else { 8498 $meta->{cols}->{$col}->{$prop} = $parts{$prop}; 8499 } 8500 } 8501 8502 } 8503} 8504 8505# save_config {{{3 8506sub save_config { 8507 print "\n"; 8508 return if $config{readonly}->{val}; 8509 # return if no config file was loaded and -w wasn't specified 8510 if (not $conf_file) { 8511 if (not $opts{w}) { 8512 return; 8513 } 8514 else { 8515 # if no config was loaded but -w was specified, 8516 # write to $default_home_conf 8517 $conf_file = $default_home_conf; 8518 } 8519 } 8520 elsif ($conf_file and $opts{w}) { 8521 print "Loaded config file on start-up, so ignoring -w (see --help)\n" 8522 } 8523 8524 my $dirname = dirname($conf_file); 8525 8526 # if directories don't exist, create them. This could cause errors 8527 # or warnings if a central config doesn't have readonly=1, but being 8528 # flexible requires giving the user enough rope to hang themselves with. 8529 if ( ! -d $dirname ) { 8530 mkdir $dirname 8531 or die "Can't create directory '$dirname': $OS_ERROR"; 8532 } 8533 if ( ! -d "$dirname/plugins" ) { 8534 mkdir "$dirname/plugins" 8535 or warn "Can't create directory '$dirname/plugins': $OS_ERROR\n"; 8536 } 8537 8538 # Save to a temp file first, so a crash doesn't destroy the main config file 8539 my $tmpfile = File::Temp->new( TEMPLATE => 'innotopXXXXX', DIR => $dirname, SUFFIX => '.conf.tmp'); 8540 open my $file, "+>", $tmpfile 8541 or die("Can't write to $tmpfile: $OS_ERROR"); 8542 print $file "version=$VERSION\n"; 8543 8544 foreach my $section ( @ordered_config_file_sections ) { 8545 die "No such config file section $section" unless $config_file_sections{$section}; 8546 print $file "\n[$section]\n\n"; 8547 $config_file_sections{$section}->{writer}->($file); 8548 print $file "\n[/$section]\n"; 8549 } 8550 8551 # Now clobber the main config file with the temp. 8552 close $file or die("Can't close $tmpfile: $OS_ERROR"); 8553 rename($tmpfile, $conf_file) or die("Can't rename $tmpfile to $conf_file: $OS_ERROR"); 8554} 8555 8556# load_config_connections {{{3 8557sub load_config_connections { 8558 return if $opts{u} or $opts{p} or $opts{h} or $opts{P}; # don't load connections if DSN or user/pass options used 8559 my ( $file ) = @_; 8560 while ( my $line = <$file> ) { 8561 chomp $line; 8562 next if $line =~ m/^#/; 8563 last if $line =~ m/^\[/; 8564 8565 my ( $key , $rest ) = $line =~ m/^(.*?)=(.*)$/; 8566 next unless $key; 8567 my %parts = $rest =~ m/(\S+?)=(\S*)/g; 8568 my %conn = map { $_ => $parts{$_} || '' } @conn_parts; 8569 $connections{$key} = \%conn; 8570 } 8571} 8572 8573# save_config_connections {{{3 8574sub save_config_connections { 8575 my $file = shift; 8576 foreach my $conn ( sort keys %connections ) { 8577 my $href = $connections{$conn}; 8578 my @keys = $href->{savepass} ? @conn_parts : grep { $_ ne 'pass' } @conn_parts; 8579 print $file "$conn=", join(' ', map { "$_=$href->{$_}" } grep { defined $href->{$_} } @keys), "\n"; 8580 } 8581} 8582 8583sub load_config_colors { 8584 my ( $file ) = @_; 8585 my %rule_set_for; 8586 8587 while ( my $line = <$file> ) { 8588 chomp $line; 8589 next if $line =~ m/^#/; 8590 last if $line =~ m/^\[/; 8591 8592 my ( $tbl, $rule ) = $line =~ m/^(.*?)=(.*)$/; 8593 next unless $tbl && $rule; 8594 next unless exists $tbl_meta{$tbl}; 8595 my %parts = $rule =~ m/(\w+)='((?:(?!(?<!\\)').)*)'/g; # Properties are single-quoted 8596 next unless $parts{col} && exists $tbl_meta{$tbl}->{cols}->{$parts{col}}; 8597 next unless $parts{op} && exists $comp_ops{$parts{op}}; 8598 next unless defined $parts{arg}; 8599 next unless defined $parts{color}; 8600 my @colors = unique(grep { exists $ansicolors{$_} } split(/\W+/, $parts{color})); 8601 next unless @colors; 8602 8603 # Finally! Enough validation... 8604 $rule_set_for{$tbl} ||= []; 8605 push @{$rule_set_for{$tbl}}, \%parts; 8606 } 8607 8608 foreach my $tbl ( keys %rule_set_for ) { 8609 $tbl_meta{$tbl}->{colors} = $rule_set_for{$tbl}; 8610 $tbl_meta{$tbl}->{color_func} = make_color_func($tbl_meta{$tbl}); 8611 $tbl_meta{$tbl}->{cust}->{colors} = 1; 8612 } 8613} 8614 8615# save_config_colors {{{3 8616sub save_config_colors { 8617 my $file = shift; 8618 foreach my $tbl ( sort keys %tbl_meta ) { 8619 my $meta = $tbl_meta{$tbl}; 8620 next unless $meta->{cust}->{colors}; 8621 foreach my $rule ( @{$meta->{colors}} ) { 8622 print $file "$tbl=", join( 8623 ' ', 8624 map { 8625 my $val = $rule->{$_}; 8626 $val =~ s/([\\'])/\\$1/g; # Escape backslashes and single quotes 8627 "$_='$val'"; # Enclose in single quotes 8628 } 8629 qw(col op arg color) 8630 ), "\n"; 8631 } 8632 } 8633} 8634 8635# load_config_active_connections {{{3 8636sub load_config_active_connections { 8637 my ( $file ) = @_; 8638 while ( my $line = <$file> ) { 8639 chomp $line; 8640 next if $line =~ m/^#/; 8641 last if $line =~ m/^\[/; 8642 8643 my ( $key , $rest ) = $line =~ m/^(.*?)=(.*)$/; 8644 next unless $key && exists $modes{$key}; 8645 my @parts = grep { exists $connections{$_} } split(/ /, $rest); 8646 $modes{$key}->{connections} = [ @parts ] if exists $modes{$key}; 8647 } 8648} 8649 8650# save_config_active_connections {{{3 8651sub save_config_active_connections { 8652 my $file = shift; 8653 foreach my $mode ( sort keys %modes ) { 8654 my @connections = get_connections($mode); 8655 print $file "$mode=", join(' ', @connections), "\n"; 8656 } 8657} 8658 8659# load_config_stmt_sleep_times {{{3 8660sub load_config_stmt_sleep_times { 8661 my ( $file ) = @_; 8662 while ( my $line = <$file> ) { 8663 chomp $line; 8664 next if $line =~ m/^#/; 8665 last if $line =~ m/^\[/; 8666 8667 my ( $key , $val ) = split('=', $line); 8668 next unless $key && defined $val && $val =~ m/$num_regex/; 8669 $stmt_sleep_time_for{$key} = $val; 8670 } 8671} 8672 8673# save_config_stmt_sleep_times {{{3 8674sub save_config_stmt_sleep_times { 8675 my $file = shift; 8676 foreach my $key ( sort keys %stmt_sleep_time_for ) { 8677 print $file "$key=$stmt_sleep_time_for{$key}\n"; 8678 } 8679} 8680 8681# load_config_mvs {{{3 8682sub load_config_mvs { 8683 my ( $file ) = @_; 8684 while ( my $line = <$file> ) { 8685 chomp $line; 8686 next if $line =~ m/^#/; 8687 last if $line =~ m/^\[/; 8688 8689 my ( $key , $val ) = split('=', $line); 8690 next unless $key && defined $val && $val =~ m/$num_regex/; 8691 $mvs{$key} = $val; 8692 } 8693} 8694 8695# save_config_mvs {{{3 8696sub save_config_mvs { 8697 my $file = shift; 8698 foreach my $key ( sort keys %mvs ) { 8699 print $file "$key=$mvs{$key}\n"; 8700 } 8701} 8702 8703# edit_configuration {{{3 8704sub edit_configuration { 8705 my $key = ''; 8706 while ( $key ne 'q' ) { 8707 $clear_screen_sub->(); 8708 my @display_lines = ''; 8709 8710 if ( $key && $cfg_editor_action{$key} ) { 8711 $cfg_editor_action{$key}->{func}->(); 8712 } 8713 8714 # Show help 8715 push @display_lines, create_caption('What configuration do you want to edit?', 8716 create_table2( 8717 [ sort keys %cfg_editor_action ], 8718 { map { $_ => $_ } keys %cfg_editor_action }, 8719 { map { $_ => $cfg_editor_action{$_}->{note} } keys %cfg_editor_action }, 8720 { sep => ' ' })); 8721 8722 draw_screen(\@display_lines); 8723 $key = pause(''); 8724 } 8725} 8726 8727# edit_configuration_variables {{{3 8728sub edit_configuration_variables { 8729 $clear_screen_sub->(); 8730 my $mode = $config{mode}->{val}; 8731 8732 my %config_choices 8733 = map { $_ => $config{$_}->{note} || '' } 8734 # Only config values that are marked as applying to this mode. 8735 grep { 8736 my $key = $_; 8737 $config{$key}->{conf} && 8738 ( $config{$key}->{conf} eq 'ALL' 8739 || grep { $mode eq $_ } @{$config{$key}->{conf}} ) 8740 } keys %config; 8741 8742 my $key = prompt_list( 8743 "Enter the name of the variable you wish to configure", 8744 '', 8745 sub{ return keys %config_choices }, 8746 \%config_choices); 8747 8748 if ( exists($config_choices{$key}) ) { 8749 get_config_interactive($key); 8750 } 8751} 8752 8753# edit_color_rules {{{3 8754sub edit_color_rules { 8755 my ( $tbl ) = @_; 8756 $clear_screen_sub->(); 8757 $tbl ||= choose_visible_table(); 8758 if ( $tbl && exists($tbl_meta{$tbl}) ) { 8759 my $meta = $tbl_meta{$tbl}; 8760 my @cols = ('', qw(col op arg color)); 8761 my $info = { map { $_ => { hdr => $_, just => '-', } } @cols }; 8762 $info->{label}->{maxw} = 30; 8763 my $key; 8764 my $selected_rule; 8765 8766 # This loop builds a tabular view of the rules. 8767 do { 8768 8769 # Show help 8770 if ( $key && $key eq '?' ) { 8771 my @display_lines = ''; 8772 push @display_lines, create_caption('Editor key mappings', 8773 create_table2( 8774 [ sort keys %color_editor_action ], 8775 { map { $_ => $_ } keys %color_editor_action }, 8776 { map { $_ => $color_editor_action{$_}->{note} } keys %color_editor_action }, 8777 { sep => ' ' })); 8778 draw_screen(\@display_lines); 8779 pause(); 8780 $key = ''; 8781 } 8782 else { 8783 8784 # Do the action specified 8785 $selected_rule ||= 0; 8786 if ( $key && $color_editor_action{$key} ) { 8787 $selected_rule = $color_editor_action{$key}->{func}->($tbl, $selected_rule); 8788 $selected_rule ||= 0; 8789 } 8790 8791 # Build the table of rules. If the terminal has color, the selected rule 8792 # will be highlighted; otherwise a > at the left will indicate. 8793 my $data = $meta->{colors} || []; 8794 foreach my $i ( 0..@$data - 1 ) { 8795 $data->[$i]->{''} = $i == $selected_rule ? '>' : ''; 8796 } 8797 my @display_lines = create_table(\@cols, $info, $data); 8798 8799 # Highlight selected entry 8800 for my $i ( 0 .. $#display_lines ) { 8801 if ( $display_lines[$i] =~ m/^>/ ) { 8802 $display_lines[$i] = [ $display_lines[$i], 'reverse' ]; 8803 } 8804 } 8805 8806 # Draw the screen and wait for a command. 8807 unshift @display_lines, '', 8808 "Editing color rules for $meta->{capt}. Press ? for help, q to " 8809 . "quit.", ''; 8810 draw_screen(\@display_lines); 8811 print "\n\n", word_wrap('Rules are applied in order from top to ' 8812 . 'bottom. The first matching rule wins and prevents the ' 8813 . 'rest of the rules from being applied.'); 8814 $key = pause(''); 8815 } 8816 } while ( $key ne 'q' ); 8817 $meta->{color_func} = make_color_func($meta); 8818 } 8819} 8820 8821# add_quick_filter {{{3 8822sub add_quick_filter { 8823 my $tbl = choose_visible_table(); 8824 if ( $tbl && exists($tbl_meta{$tbl}) ) { 8825 print "\n"; 8826 my $response = prompt_list( 8827 "Enter column name and filter text", 8828 '', 8829 sub { return keys %{$tbl_meta{$tbl}->{cols}} }, 8830 () 8831 ); 8832 my ( $col, $text ) = split(/\s+/, $response, 2); 8833 8834 # You can't filter on a nonexistent column. But if you filter on a pivoted 8835 # table, the columns are different, so on a pivoted table, allow filtering 8836 # on the 'name' column. 8837 # NOTE: if a table is pivoted and un-pivoted, this will likely cause crashes. 8838 # Currently not an issue since there's no way to toggle pivot/nopivot. 8839 return unless $col && $text && 8840 (exists($tbl_meta{$tbl}->{cols}->{$col}) 8841 || ($tbl_meta{$tbl}->{pivot} && $col eq 'name')); 8842 8843 my ( $sub, $err ) = compile_filter( "defined \$set->{$col} && \$set->{$col} =~ m/$text/" ); 8844 return if !$sub || $err; 8845 my $name = "quick_$tbl.$col"; 8846 $filters{$name} = { 8847 func => $sub, 8848 text => $text, 8849 user => 1, 8850 quick => 1, 8851 name => $name, 8852 note => 'Quick-filter', 8853 tbls => [$tbl], 8854 }; 8855 push @{$tbl_meta{$tbl}->{filters}}, $name; 8856 } 8857} 8858 8859# clear_quick_filters {{{3 8860sub clear_quick_filters { 8861 my $tbl = choose_visible_table( 8862 # Only tables that have quick-filters 8863 sub { 8864 my ( $tbl ) = @_; 8865 return scalar grep { $filters{$_}->{quick} } @{ $tbl_meta{$tbl}->{filters} }; 8866 } 8867 ); 8868 if ( $tbl && exists($tbl_meta{$tbl}) ) { 8869 my @current = @{$tbl_meta{$tbl}->{filters}}; 8870 @current = grep { !$filters{$_}->{quick} } @current; 8871 $tbl_meta{$tbl}->{filters} = \@current; 8872 } 8873} 8874 8875sub edit_plugins { 8876 $clear_screen_sub->(); 8877 8878 my @cols = ('', qw(class desc active)); 8879 my $info = { map { $_ => { hdr => $_, just => '-', } } @cols }; 8880 my @rows = map { $plugins{$_} } sort keys %plugins; 8881 my $key; 8882 my $selected; 8883 8884 # This loop builds a tabular view of the plugins. 8885 do { 8886 8887 # Show help 8888 if ( $key && $key eq '?' ) { 8889 my @display_lines = ''; 8890 push @display_lines, create_caption('Editor key mappings', 8891 create_table2( 8892 [ sort keys %plugin_editor_action ], 8893 { map { $_ => $_ } keys %plugin_editor_action }, 8894 { map { $_ => $plugin_editor_action{$_}->{note} } keys %plugin_editor_action }, 8895 { sep => ' ' })); 8896 draw_screen(\@display_lines); 8897 pause(); 8898 $key = ''; 8899 } 8900 8901 # Do the action specified 8902 else { 8903 $selected ||= 0; 8904 if ( $key && $plugin_editor_action{$key} ) { 8905 $selected = $plugin_editor_action{$key}->{func}->(\@rows, $selected); 8906 $selected ||= 0; 8907 } 8908 8909 # Build the table of plugins. 8910 foreach my $row ( 0.. $#rows ) { 8911 $rows[$row]->{''} = $row eq $selected ? '>' : ' '; 8912 } 8913 my @display_lines = create_table(\@cols, $info, \@rows); 8914 8915 # Highlight selected entry 8916 for my $i ( 0 .. $#display_lines ) { 8917 if ( $display_lines[$i] =~ m/^>/ ) { 8918 $display_lines[$i] = [ $display_lines[$i], 'reverse' ]; 8919 } 8920 } 8921 8922 # Draw the screen and wait for a command. 8923 unshift @display_lines, '', 8924 "Plugin Management. Press ? for help, q to quit.", ''; 8925 draw_screen(\@display_lines); 8926 $key = pause(''); 8927 } 8928 } while ( $key ne 'q' ); 8929} 8930 8931# edit_table {{{3 8932sub edit_table { 8933 $clear_screen_sub->(); 8934 my ( $tbl ) = @_; 8935 $tbl ||= choose_visible_table(); 8936 if ( $tbl && exists($tbl_meta{$tbl}) ) { 8937 my $meta = $tbl_meta{$tbl}; 8938 my @cols = ('', qw(name hdr label src)); 8939 my $info = { map { $_ => { hdr => $_, just => '-', } } @cols }; 8940 $info->{label}->{maxw} = 30; 8941 my $key; 8942 my $selected_column; 8943 8944 # This loop builds a tabular view of the tbl_meta's structure, showing each column 8945 # in the entry as a row. 8946 do { 8947 8948 # Show help 8949 if ( $key && $key eq '?' ) { 8950 my @display_lines = ''; 8951 push @display_lines, create_caption('Editor key mappings', 8952 create_table2( 8953 [ sort keys %tbl_editor_action ], 8954 { map { $_ => $_ } keys %tbl_editor_action }, 8955 { map { $_ => $tbl_editor_action{$_}->{note} } keys %tbl_editor_action }, 8956 { sep => ' ' })); 8957 draw_screen(\@display_lines); 8958 pause(); 8959 $key = ''; 8960 } 8961 else { 8962 8963 # Do the action specified 8964 $selected_column ||= $meta->{visible}->[0]; 8965 if ( $key && $tbl_editor_action{$key} ) { 8966 $selected_column = $tbl_editor_action{$key}->{func}->($tbl, $selected_column); 8967 $selected_column ||= $meta->{visible}->[0]; 8968 } 8969 8970 # Build the pivoted view of the table's meta-data. If the terminal has color, 8971 # The selected row will be highlighted; otherwise a > at the left will indicate. 8972 my $data = []; 8973 foreach my $row ( @{$meta->{visible}} ) { 8974 my %hash; 8975 @hash{ @cols } = @{$meta->{cols}->{$row}}{@cols}; 8976 $hash{src} = '' if ref $hash{src}; 8977 $hash{name} = $row; 8978 $hash{''} = $row eq $selected_column ? '>' : ' '; 8979 push @$data, \%hash; 8980 } 8981 my @display_lines = create_table(\@cols, $info, $data); 8982 8983 # Highlight selected entry 8984 for my $i ( 0 .. $#display_lines ) { 8985 if ( $display_lines[$i] =~ m/^>/ ) { 8986 $display_lines[$i] = [ $display_lines[$i], 'reverse' ]; 8987 } 8988 } 8989 8990 # Draw the screen and wait for a command. 8991 unshift @display_lines, '', 8992 "Editing table definition for $meta->{capt}. Press ? for help, q to quit.", ''; 8993 draw_screen(\@display_lines, { clear => 1 }); 8994 $key = pause(''); 8995 } 8996 } while ( $key ne 'q' ); 8997 } 8998} 8999 9000# choose_mode_tables {{{3 9001# Choose which table(s), and in what order, to display in a given mode. 9002sub choose_mode_tables { 9003 my $mode = $config{mode}->{val}; 9004 my @tbls = @{$modes{$mode}->{visible_tables}}; 9005 my $new = prompt_list( 9006 "Choose tables to display", 9007 join(' ', @tbls), 9008 sub { return @{$modes{$mode}->{tables}} }, 9009 { map { $_ => $tbl_meta{$_}->{capt} } @{$modes{$mode}->{tables}} } 9010 ); 9011 $modes{$mode}->{visible_tables} = 9012 [ unique(grep { $_ && exists $tbl_meta{$_} } split(/\s+/, $new)) ]; 9013 $modes{$mode}->{cust}->{visible_tables} = 1; 9014} 9015 9016# set_visible_table {{{3 9017sub set_visible_table { 9018 my ( $tbl ) = @_; 9019 my $mode = $config{mode}->{val}; 9020 my @tbls = grep { $_ eq $tbl } @{$modes{$mode}->{tables}}; 9021 if ( @tbls == 1 ) { 9022 $modes{$mode}->{visible_tables} = [ $tbl ]; 9023 $modes{$mode}->{cust}->{visible_tables} = 1; 9024 } 9025} 9026 9027# choose_visible_table {{{3 9028sub choose_visible_table { 9029 my ( $grep_cond ) = @_; 9030 my $mode = $config{mode}->{val}; 9031 my @tbls 9032 = grep { $grep_cond ? $grep_cond->($_) : 1 } 9033 @{$modes{$mode}->{visible_tables}}; 9034 my $tbl = $tbls[0]; 9035 if ( @tbls > 1 ) { 9036 $tbl = prompt_list( 9037 "Choose a table", 9038 '', 9039 sub { return @tbls }, 9040 { map { $_ => $tbl_meta{$_}->{capt} } @tbls } 9041 ); 9042 } 9043 return $tbl; 9044} 9045 9046sub toggle_aggregate { 9047 my ( $tbl ) = @_; 9048 $tbl ||= choose_visible_table(); 9049 return unless $tbl && exists $tbl_meta{$tbl}; 9050 my $meta = $tbl_meta{$tbl}; 9051 $meta->{aggregate} ^= 1; 9052} 9053 9054sub choose_filters { 9055 my ( $tbl ) = @_; 9056 $tbl ||= choose_visible_table(); 9057 return unless $tbl && exists $tbl_meta{$tbl}; 9058 my $meta = $tbl_meta{$tbl}; 9059 $clear_screen_sub->(); 9060 9061 print "Choose filters for $meta->{capt}:\n"; 9062 9063 my $ini = join(' ', @{$meta->{filters}}); 9064 my $val = prompt_list( 9065 'Choose filters', 9066 $ini, 9067 sub { return keys %filters }, 9068 { 9069 map { $_ => $filters{$_}->{note} } 9070 grep { grep { $tbl eq $_ } @{$filters{$_}->{tbls}} } 9071 keys %filters 9072 } 9073 ); 9074 9075 my @choices = unique($val =~ m/(\S+)/g); 9076 foreach my $new ( grep { !exists($filters{$_}) } @choices ) { 9077 my $answer = prompt("There is no filter called '$new'. Create it?", undef, 'y'); 9078 if ( $answer eq 'y' ) { 9079 create_new_filter($new, $tbl); 9080 } 9081 } 9082 @choices = grep { exists $filters{$_} } @choices; 9083 @choices = grep { grep { $tbl eq $_ } @{$filters{$_}->{tbls}} } @choices; 9084 $meta->{filters} = [ @choices ]; 9085 $meta->{cust}->{filters} = 1; 9086} 9087 9088sub choose_group_cols { 9089 my ( $tbl ) = @_; 9090 $tbl ||= choose_visible_table(); 9091 return unless $tbl && exists $tbl_meta{$tbl}; 9092 $clear_screen_sub->(); 9093 my $meta = $tbl_meta{$tbl}; 9094 my $curr = join(', ', @{$meta->{group_by}}); 9095 my $val = prompt_list( 9096 'Group-by columns', 9097 $curr, 9098 sub { return keys %{$meta->{cols}} }, 9099 { map { $_ => $meta->{cols}->{$_}->{label} } keys %{$meta->{cols}} }); 9100 if ( $curr ne $val ) { 9101 $meta->{group_by} = [ grep { exists $meta->{cols}->{$_} } $val =~ m/(\w+)/g ]; 9102 $meta->{cust}->{group_by} = 1; 9103 } 9104} 9105 9106sub choose_sort_cols { 9107 my ( $tbl ) = @_; 9108 $tbl ||= choose_visible_table(); 9109 return unless $tbl && exists $tbl_meta{$tbl}; 9110 $clear_screen_sub->(); 9111 my $meta = $tbl_meta{$tbl}; 9112 9113 my ( $cols, $hints ); 9114 if ( $meta->{pivot} ) { 9115 $cols = sub { qw(name set_0) }; 9116 $hints = { name => 'name', set_0 => 'set_0' }; 9117 } 9118 else { 9119 $cols = sub { return keys %{$meta->{cols}} }; 9120 $hints = { map { $_ => $meta->{cols}->{$_}->{label} } keys %{$meta->{cols}} }; 9121 } 9122 9123 my $val = prompt_list( 9124 'Sort columns (reverse sort with -col)', 9125 $meta->{sort_cols}, 9126 $cols, 9127 $hints ); 9128 if ( $meta->{sort_cols} ne $val ) { 9129 $meta->{sort_cols} = $val; 9130 $meta->{cust}->{sort_cols} = 1; 9131 $tbl_meta{$tbl}->{sort_func} = make_sort_func($tbl_meta{$tbl}); 9132 } 9133} 9134 9135# create_new_filter {{{3 9136sub create_new_filter { 9137 my ( $filter, $tbl ) = @_; 9138 $clear_screen_sub->(); 9139 9140 if ( !$filter || $filter =~ m/\W/ ) { 9141 print word_wrap("Choose a name for the filter. This name is not displayed, and is only used " 9142 . "for internal reference. It can only contain lowercase letters, numbers, and underscores."); 9143 print "\n\n"; 9144 do { 9145 $filter = prompt("Enter filter name"); 9146 } while ( !$filter || $filter =~ m/\W/ ); 9147 } 9148 9149 my $completion = sub { keys %{$tbl_meta{$tbl}->{cols}} }; 9150 my ( $err, $sub, $body ); 9151 do { 9152 $clear_screen_sub->(); 9153 print word_wrap("A filter is a Perl subroutine that accepts a hashref of columns " 9154 . "called \$set, and returns a true value if the filter accepts the row. Example:\n" 9155 . " \$set->{active_secs} > 5\n" 9156 . "will only allow rows if their active_secs column is greater than 5."); 9157 print "\n\n"; 9158 if ( $err ) { 9159 print "There's an error in your filter expression: $err\n\n"; 9160 } 9161 $body = prompt("Enter subroutine body", undef, undef, $completion); 9162 ( $sub, $err ) = compile_filter($body); 9163 } while ( $err ); 9164 9165 $filters{$filter} = { 9166 func => $sub, 9167 text => $body, 9168 user => 1, 9169 name => $filter, 9170 note => 'User-defined filter', 9171 tbls => [$tbl], 9172 }; 9173} 9174 9175# get_config_interactive {{{3 9176sub get_config_interactive { 9177 my $key = shift; 9178 $clear_screen_sub->(); 9179 9180 # Print help first. 9181 print "Enter a new value for '$key' ($config{$key}->{note}).\n"; 9182 9183 my $current = ref($config{$key}->{val}) ? join(" ", @{$config{$key}->{val}}) : $config{$key}->{val}; 9184 9185 my $new_value = prompt('Enter a value', $config{$key}->{pat}, $current); 9186 $config{$key}->{val} = $new_value; 9187} 9188 9189sub edit_current_var_set { 9190 my $mode = $config{mode}->{val}; 9191 my $name = $config{"${mode}_set"}->{val}; 9192 my $variables = $var_sets{$name}->{text}; 9193 9194 my $new = $variables; 9195 do { 9196 $clear_screen_sub->(); 9197 $new = prompt("Enter variables for $name", undef, $variables); 9198 } until ( $new ); 9199 9200 if ( $new ne $variables ) { 9201 @{$var_sets{$name}}{qw(text user)} = ( $new, 1); 9202 } 9203} 9204 9205 9206sub choose_var_set { 9207 my ( $key ) = @_; 9208 $clear_screen_sub->(); 9209 9210 my $new_value = prompt_list( 9211 'Choose a set of values to display, or enter the name of a new one', 9212 $config{$key}->{val}, 9213 sub { return keys %var_sets }, 9214 { map { $_ => $var_sets{$_}->{text} } keys %var_sets }); 9215 9216 if ( !exists $var_sets{$new_value} ) { 9217 add_new_var_set($new_value); 9218 } 9219 9220 $config{$key}->{val} = $new_value if exists $var_sets{$new_value}; 9221} 9222 9223sub switch_var_set { 9224 my ( $cfg_var, $dir ) = @_; 9225 my @var_sets = sort keys %var_sets; 9226 my $cur = $config{$cfg_var}->{val}; 9227 my $pos = grep { $_ lt $cur } @var_sets; 9228 my $newpos = ($pos + $dir) % @var_sets; 9229 $config{$cfg_var}->{val} = $var_sets[$newpos]; 9230 $clear_screen_sub->(); 9231} 9232 9233# Online configuration and prompting functions {{{2 9234 9235# edit_stmt_sleep_times {{{3 9236sub edit_stmt_sleep_times { 9237 $clear_screen_sub->(); 9238 my $stmt = prompt_list('Specify a statement', '', sub { return sort keys %stmt_maker_for }); 9239 return unless $stmt && exists $stmt_maker_for{$stmt}; 9240 $clear_screen_sub->(); 9241 my $curr_val = $stmt_sleep_time_for{$stmt} || 0; 9242 my $new_val = prompt('Specify a sleep delay after calling this SQL', $num_regex, $curr_val); 9243 if ( $new_val ) { 9244 $stmt_sleep_time_for{$stmt} = $new_val; 9245 } 9246 else { 9247 delete $stmt_sleep_time_for{$stmt}; 9248 } 9249} 9250 9251# edit_server_groups {{{3 9252# Choose which server connections are in a server group. First choose a group, 9253# then choose which connections are in it. 9254sub edit_server_groups { 9255 $clear_screen_sub->(); 9256 my $mode = $config{mode}->{val}; 9257 my $group = $modes{$mode}->{server_group}; 9258 my %curr = %server_groups; 9259 my $new = choose_or_create_server_group($group, 'to edit'); 9260 $clear_screen_sub->(); 9261 if ( exists $curr{$new} ) { 9262 # Don't do this step if the user just created a new server group, 9263 # because part of that process was to choose connections. 9264 my $cxns = join(' ', @{$server_groups{$new}}); 9265 my @conns = choose_or_create_connection($cxns, 'for this group'); 9266 $server_groups{$new} = \@conns; 9267 } 9268} 9269 9270# choose_server_groups {{{3 9271sub choose_server_groups { 9272 $clear_screen_sub->(); 9273 my $mode = $config{mode}->{val}; 9274 my $group = $modes{$mode}->{server_group}; 9275 my $new = choose_or_create_server_group($group, 'for this mode'); 9276 $modes{$mode}->{server_group} = $new if exists $server_groups{$new}; 9277} 9278 9279sub choose_or_create_server_group { 9280 my ( $group, $prompt ) = @_; 9281 my $new = ''; 9282 9283 my @available = sort keys %server_groups; 9284 9285 if ( @available ) { 9286 print "You can enter the name of a new group to create it.\n"; 9287 9288 $new = prompt_list( 9289 "Choose a server group $prompt", 9290 $group, 9291 sub { return @available }, 9292 { map { $_ => join(' ', @{$server_groups{$_}}) } @available }); 9293 9294 $new =~ s/\s.*//; 9295 9296 if ( !exists $server_groups{$new} ) { 9297 my $answer = prompt("There is no server group called '$new'. Create it?", undef, "y"); 9298 if ( $answer eq 'y' ) { 9299 add_new_server_group($new); 9300 } 9301 } 9302 } 9303 else { 9304 $new = add_new_server_group(); 9305 } 9306 return $new; 9307} 9308 9309sub choose_or_create_connection { 9310 my ( $cxns, $prompt ) = @_; 9311 print "You can enter the name of a new connection to create it.\n"; 9312 9313 my @available = sort keys %connections; 9314 my $new_cxns = prompt_list( 9315 "Choose connections $prompt", 9316 $cxns, 9317 sub { return @available }, 9318 { map { $_ => $connections{$_}->{dsn} } @available }); 9319 9320 my @new = unique(grep { !exists $connections{$_} } $new_cxns =~ m/(\S+)/g); 9321 foreach my $new ( @new ) { 9322 my $answer = prompt("There is no connection called '$new'. Create it?", undef, "y"); 9323 if ( $answer eq 'y' ) { 9324 add_new_dsn($new); 9325 } 9326 } 9327 9328 return unique(grep { exists $connections{$_} } split(/\s+/, $new_cxns)); 9329} 9330 9331# choose_servers {{{3 9332sub choose_servers { 9333 $clear_screen_sub->(); 9334 my $mode = $config{mode}->{val}; 9335 my $cxns = join(' ', get_connections()); 9336 my @chosen = choose_or_create_connection($cxns, 'for this mode'); 9337 $modes{$mode}->{connections} = \@chosen; 9338 $modes{$mode}->{server_group} = ''; # Clear this because it overrides {connections} 9339 get_connections(); # This will set the server group if it matches connections just chosen 9340} 9341 9342# display_license {{{3 9343sub display_license { 9344 $clear_screen_sub->(); 9345 9346 print $innotop_license; 9347 9348 pause(); 9349} 9350 9351# Data-retrieval functions {{{2 9352# get_status_info {{{3 9353# Get SHOW STATUS and SHOW VARIABLES together. 9354sub get_status_info { 9355 my @cxns = @_; 9356 if ( !$info_gotten{status}++ ) { 9357 foreach my $cxn ( @cxns ) { 9358 $vars{$cxn}->{$clock} ||= {}; 9359 my $vars = $vars{$cxn}->{$clock}; 9360 9361 my $sth = do_stmt($cxn, 'SHOW_STATUS') or next; 9362 my $res = $sth->fetchall_arrayref(); 9363 map { $vars->{$_->[0]} = $_->[1] || 0 } @$res; 9364 9365 # Calculate hi-res uptime and add cxn to the hash. This duplicates get_driver_status, 9366 # but it's most important to have consistency. 9367 $vars->{Uptime_hires} ||= get_uptime($cxn); 9368 $vars->{cxn} = $cxn; 9369 9370 # Add SHOW VARIABLES to the hash. If we've gotten this info before, skip and re-use. 9371 if ( $show_variables{$cxn} ) { 9372 $res = $show_variables{$cxn}; 9373 } 9374 else { 9375 $sth = do_stmt($cxn, 'SHOW_VARIABLES') or next; 9376 $res = $sth->fetchall_arrayref(); 9377 $res = {map { $_->[0] => $_->[1] || 0 } @$res}; 9378 $show_variables{$cxn} = $res; 9379 } 9380 @{$vars}{keys %$res} = values %$res; 9381 9382 # Create sparklines for QPS and Threads_running. As a consequence of 9383 # this, we get QPS for free. TODO: remove QPS computation from 9384 # elsewhere. 9385 my $pre = $vars{$cxn}->{$clock - 1}; 9386 if ( $pre && $pre->{Uptime_hires} ) { 9387 my @prev_qps = ($pre->{SPARK_store_qps} || '') =~ m/(\S+)/g; 9388 my @prev_run = ($pre->{SPARK_store_run} || '') =~ m/(\S+)/g; 9389 9390 # Find out the values; throw away if too many; sparkify; store. 9391 my $this_qps = (($vars->{Queries} || 0) - ($pre->{Queries} || 0))/ 9392 ($vars->{Uptime_hires} - $pre->{Uptime_hires}); 9393 push @prev_qps, $this_qps; 9394 shift @prev_qps if @prev_qps > $config{spark}->{val}; 9395 my $qps_spark = sparkify(@prev_qps); 9396 $vars->{SPARK_qps} = $qps_spark; 9397 $vars->{SPARK_store_qps} = join(' ', @prev_qps); 9398 my $this_run = $vars->{Threads_running}; 9399 push @prev_run, $this_run; 9400 shift @prev_run if @prev_run > $config{spark}->{val}; 9401 my $run_spark = sparkify(@prev_run); 9402 $vars->{SPARK_run} = $run_spark; 9403 $vars->{SPARK_store_run} = join(' ', @prev_run); 9404 } 9405 } 9406 } 9407} 9408 9409# Chooses a thread for explaining, killing, etc... 9410# First arg is a func that can be called in grep. 9411sub choose_thread { 9412 my ( $grep_cond, $prompt ) = @_; 9413 9414 my %thread_for = map { 9415 # Eliminate innotop's own threads. 9416 $_ => $dbhs{$_}->{dbh} ? $dbhs{$_}->{dbh}->{mysql_thread_id} : 0 9417 } keys %connections; 9418 9419 my @candidates = grep { 9420 $_->{id} != $thread_for{$_->{cxn}} && $grep_cond->($_) 9421 } @current_queries; 9422 return unless @candidates; 9423 9424 # Find out which server. 9425 my @cxns = unique map { $_->{cxn} } @candidates; 9426 my ( $cxn ) = select_cxn('On which server', @cxns); 9427 return unless $cxn && exists($connections{$cxn}); 9428 9429 # Re-filter the list of candidates to only those on this server 9430 @candidates = grep { $_->{cxn} eq $cxn } @candidates; 9431 9432 # Find out which thread to do. 9433 my $info; 9434 if ( @candidates > 1 ) { 9435 9436 # Sort longest-active first, then longest-idle. 9437 my $sort_func = sub { 9438 my ( $a, $b ) = @_; 9439 return $a->{query} && !$b->{query} ? 1 9440 : $b->{query} && !$a->{query} ? -1 9441 : ($a->{time} || 0) cmp ($b->{time} || 0); 9442 }; 9443 my @threads = map { $_->{id} } reverse sort { $sort_func->($a, $b) } @candidates; 9444 9445 print "\n"; 9446 my $thread = prompt_list($prompt, 9447 $threads[0], 9448 sub { return @threads }); 9449 return unless $thread && $thread =~ m/$int_regex/; 9450 9451 # Find the info hash of that query on that server. 9452 ( $info ) = grep { $thread == $_->{id} } @candidates; 9453 } 9454 else { 9455 $info = $candidates[0]; 9456 } 9457 return $info; 9458} 9459 9460# analyze_query {{{3 9461# Allows the user to show fulltext, explain, show optimized... 9462sub analyze_query { 9463 my ( $action ) = @_; 9464 9465 my $info = choose_thread( 9466 sub { $_[0]->{query} }, 9467 'Select a thread to analyze', 9468 ); 9469 return unless $info; 9470 9471 my %actions = ( 9472 e => \&display_explain, 9473 f => \&show_full_query, 9474 o => \&show_optimized_query, 9475 ); 9476 do { 9477 $actions{$action}->($info); 9478 print "\n"; 9479 $action = pause('Press e to explain, f for full query, o for optimized query'); 9480 } while ( exists($actions{$action}) ); 9481} 9482 9483# inc {{{3 9484# Returns the difference between two sets of variables/status/innodb stuff. 9485sub inc { 9486 my ( $offset, $cxn ) = @_; 9487 my $vars = $vars{$cxn}; 9488 if ( $offset < 0 ) { 9489 return $vars->{$clock}; 9490 } 9491 elsif ( exists $vars{$clock - $offset} && !exists $vars->{$clock - $offset - 1} ) { 9492 return $vars->{$clock - $offset}; 9493 } 9494 my $cur = $vars->{$clock - $offset}; 9495 my $pre = $vars->{$clock - $offset - 1}; 9496 return { 9497 # Numeric variables get subtracted, non-numeric get passed straight through. 9498 map { 9499 $_ => 9500 ( (defined $cur->{$_} && $cur->{$_} =~ m/$num_regex/ && ($pre->{$_} || '') =~ m/$num_regex/ ) 9501 ? $cur->{$_} - ($pre->{$_} || 0) 9502 : $cur->{$_} ) 9503 } keys %{$cur} 9504 }; 9505} 9506 9507# extract_values {{{3 9508# Arguments are a set of values (which may be incremental, derived from 9509# current and previous), current, and previous values. 9510# TODO: there are a few places that don't remember prev set so can't pass it. 9511sub extract_values { 9512 my ( $set, $cur, $pre, $tbl ) = @_; 9513 9514 # Hook in event listeners 9515 foreach my $listener ( @{$event_listener_for{extract_values}} ) { 9516 $listener->extract_values($set, $cur, $pre, $tbl); 9517 } 9518 9519 my $result = {}; 9520 my $meta = $tbl_meta{$tbl}; 9521 my $cols = $meta->{cols}; 9522 foreach my $key ( keys %$cols ) { 9523 my $info = $cols->{$key} 9524 or die "Column '$key' doesn't exist in $tbl"; 9525 die "No func defined for '$key' in $tbl" 9526 unless $info->{func}; 9527 eval { 9528 $result->{$key} = $info->{func}->($set, $cur, $pre) 9529 }; 9530 if ( $EVAL_ERROR ) { 9531 if ( $config{debug}->{val} ) { 9532 die $EVAL_ERROR; 9533 } 9534 $result->{$key} = $info->{num} ? 0 : ''; 9535 } 9536 } 9537 return $result; 9538} 9539 9540# get_processlist_stats {{{3 9541# Inserts special values as though they are SHOW STATUS counters. 9542sub get_processlist_stats { 9543 my @cxns = @_; 9544 @current_queries = (); 9545 if ( !$info_gotten{processlist_stats}++ ) { 9546 foreach my $cxn ( @cxns ) { 9547 my $max_query_time = 0; 9548 my ($user_threads, $slaves, $longest_sql, $slave_sql, $locked); 9549 $vars{$cxn}->{$clock} ||= {}; 9550 my $vars = $vars{$cxn}->{$clock}; 9551 $vars->{cxn} = $cxn; 9552 my $stmt = do_stmt($cxn, 'PROCESSLIST_NO_IS') or next; 9553 my $arr = $stmt->fetchall_arrayref({}); 9554 my $cur = undef; 9555 foreach my $thread ( @$arr ) { 9556 if ( ($thread->{state} || '') =~ m/lock/i ) { 9557 $locked++; 9558 } 9559 # Ignore non-user threads, but remember the SQL in case there is 9560 # no user SQL. Ignore sleeping threads and SHOW PROCESSLIST 9561 # threads. 9562 if ( ($thread->{user} || '') =~ m/system user/ ) { 9563 if ( $thread->{info} && $thread->{time} ) { 9564 $slave_sql = $thread->{info}; 9565 } 9566 next; 9567 } 9568 next unless $thread->{command}; 9569 if ( $thread->{command} eq 'Binlog Dump' ) { 9570 $slaves++; 9571 next; 9572 } 9573 next unless $thread->{command} eq 'Query'; 9574 next unless $thread->{state} && $thread->{info}; 9575 next if $thread->{info} =~ m#/\*innotop#; 9576 $user_threads++; 9577 if ( $thread->{time} > $max_query_time ) { 9578 $max_query_time = $thread->{time}; 9579 $longest_sql = $thread->{info}; 9580 if ( $thread->{state} eq 'Checking table' ) { 9581 $longest_sql = 'CHECK TABLE ' . $thread->{info}; 9582 } 9583 $cur = { 9584 cxn => $cxn, 9585 id => $thread->{id}, 9586 db => $thread->{db}, 9587 query => $thread->{info}, 9588 time => $thread->{time}, 9589 user => $thread->{user}, 9590 host => $thread->{host}, 9591 }; 9592 $thread->{host} =~ s/:.*$//; 9593 } 9594 } 9595 $vars->{Max_query_time} = $max_query_time; 9596 $vars->{User_threads_running} = $user_threads; 9597 $vars->{Slaves} = $slaves || 0; 9598 $vars->{Longest_sql} = $longest_sql || $slave_sql || ''; 9599 $vars->{Locked_count} = $locked || 0; 9600 $vars->{Uptime_hires} ||= get_uptime($cxn); 9601 push @current_queries, $cur if $cur; 9602 } 9603 } 9604} 9605 9606# get_full_processlist {{{3 9607sub get_full_processlist { 9608 my @cxns = @_; 9609 my @result; 9610 foreach my $cxn ( @cxns ) { 9611 my $stmt = do_stmt($cxn, 'PROCESSLIST') or next; 9612 my $arr = $stmt->fetchall_arrayref({}); 9613 push @result, map { $_->{cxn} = $cxn; $_ } @$arr; 9614 } 9615 return @result; 9616} 9617 9618# get_open_tables {{{3 9619sub get_open_tables { 9620 my @cxns = @_; 9621 my @result; 9622 foreach my $cxn ( @cxns ) { 9623 my $stmt = do_stmt($cxn, 'OPEN_TABLES') or next; 9624 my $arr = $stmt->fetchall_arrayref({}); 9625 push @result, map { $_->{cxn} = $cxn; $_ } @$arr; 9626 } 9627 return @result; 9628} 9629 9630# get_index_statistics {{{3 9631sub get_index_statistics { 9632 my @cxns = @_; 9633 my @result; 9634 foreach my $cxn ( @cxns ) { 9635 my $stmt = do_stmt($cxn, 'INDEX_STATISTICS') or next; 9636 my $arr = $stmt->fetchall_arrayref({}); 9637 push @result, map { $_->{cxn} = $cxn; $_ } @$arr; 9638 } 9639 return @result; 9640} 9641 9642# get_index_table_statistics {{{3 9643sub get_index_table_statistics { 9644 my @cxns = @_; 9645 my @result; 9646 foreach my $cxn ( @cxns ) { 9647 my $stmt = do_stmt($cxn, 'INDEX_TABLE_STATISTICS') or next; 9648 my $arr = $stmt->fetchall_arrayref({}); 9649 push @result, map { $_->{cxn} = $cxn; $_ } @$arr; 9650 } 9651 return @result; 9652} 9653 9654# get_table_statistics {{{3 9655sub get_table_statistics { 9656 my @cxns = @_; 9657 my @result; 9658 foreach my $cxn ( @cxns ) { 9659 my $stmt = do_stmt($cxn, 'TABLE_STATISTICS') or next; 9660 my $arr = $stmt->fetchall_arrayref({}); 9661 push @result, map { $_->{cxn} = $cxn; $_ } @$arr; 9662 } 9663 return @result; 9664} 9665 9666# get_innodb_blocked_blocker {{{3 9667sub get_innodb_blocked_blocker { 9668 my @cxns = @_; 9669 my @result; 9670 foreach my $cxn ( @cxns ) { 9671 my $stmt = do_stmt($cxn, 'INNODB_BLOCKED_BLOCKER') or next; 9672 my $arr = $stmt->fetchall_arrayref({}); 9673 push @result, map { $_->{cxn} = $cxn; $_ } @$arr; 9674 } 9675 return @result; 9676} 9677 9678# get_innodb_status {{{3 9679sub get_innodb_status { 9680 my ( $cxns, $addl_sections ) = @_; 9681 if ( !$config{skip_innodb}->{val} && !$info_gotten{innodb_status}++ ) { 9682 9683 # Determine which sections need to be parsed 9684 my %sections_required = 9685 map { $tbl_meta{$_}->{innodb} => 1 } 9686 grep { $_ && $tbl_meta{$_}->{innodb} } 9687 get_visible_tables(); 9688 9689 # Add in any other sections the caller requested. 9690 foreach my $sec ( @$addl_sections ) { 9691 $sections_required{$sec} = 1; 9692 } 9693 9694 foreach my $cxn ( @$cxns ) { 9695 my $innodb_status_text; 9696 9697 if ( $file ) { # Try to fetch status text from the file. 9698 my @stat = stat($file); 9699 9700 # Initialize the file. 9701 if ( !$file_mtime ) { 9702 # Initialize to 130k from the end of the file (because the limit 9703 # on the size of innodb status is 128k even with Google's patches) 9704 # and try to grab the last status from the file. 9705 sysseek($file, (-128 * 1_024), 2); 9706 } 9707 9708 # Read from the file. 9709 my $buffer; 9710 if ( !$file_mtime || $file_mtime != $stat[9] ) { 9711 $file_data = ''; 9712 while ( sysread($file, $buffer, 4096) ) { 9713 $file_data .= $buffer; 9714 } 9715 $file_mtime = $stat[9]; 9716 } 9717 9718 # Delete everything but the last InnoDB status text from the file. 9719 $file_data =~ s/\A.*(?=^=====================================\n...... ........ INNODB MONITOR OUTPUT)//ms; 9720 $innodb_status_text = $file_data; 9721 } 9722 9723 else { 9724 next if ($show_variables{$cxn}->{have_innodb} || 'YES') eq 'NO'; 9725 my $stmt = do_stmt($cxn, 'INNODB_STATUS') or next; 9726 $innodb_status_text = $stmt->fetchrow_hashref()->{status}; 9727 } 9728 9729 next unless $innodb_status_text 9730 && substr($innodb_status_text, 0, 100) =~ m/INNODB MONITOR OUTPUT/; 9731 9732 # Parse and merge into %vars storage 9733 my %innodb_status = ( 9734 $innodb_parser->get_status_hash( 9735 $innodb_status_text, 9736 $config{debug}->{val}, 9737 \%sections_required, 9738 0, # don't parse full lock information 9739 $show_variables{$cxn}->{version} 9740 ) 9741 ); 9742 if ( !$innodb_status{IB_got_all} && $config{auto_wipe_dl}->{val} ) { 9743 clear_deadlock($cxn); 9744 } 9745 9746 # Merge using a hash slice, which is the fastest way 9747 $vars{$cxn}->{$clock} ||= {}; 9748 my $hash = $vars{$cxn}->{$clock}; 9749 @{$hash}{ keys %innodb_status } = values %innodb_status; 9750 $hash->{cxn} = $cxn; 9751 $hash->{Uptime_hires} ||= get_uptime($cxn); 9752 } 9753 } 9754} 9755 9756# clear_deadlock {{{3 9757sub clear_deadlock { 9758 my ( $cxn ) = @_; 9759 return if $clearing_deadlocks++; 9760 my $tbl = $connections{$cxn}->{dl_table}; 9761 return unless $tbl; 9762 9763 eval { 9764 # disable binary logging for the session 9765 do_query($cxn, "set SQL_LOG_BIN=0"); 9766 9767 # Set up the table for creating a deadlock. 9768 my $engine = version_ge($dbhs{$cxn}->{dbh}, '4.1.2') ? 'engine' : 'type'; 9769 return unless do_query($cxn, "drop table if exists $tbl"); 9770 return unless do_query($cxn, "create table $tbl(a int) $engine=innodb"); 9771 return unless do_query($cxn, "delete from $tbl"); 9772 return unless do_query($cxn, "insert into $tbl(a) values(0), (1)"); 9773 return unless do_query($cxn, "commit"); # Or the children will block against the parent 9774 9775 # Fork off two children to deadlock against each other. 9776 my %children; 9777 foreach my $child ( 0..1 ) { 9778 my $pid = fork(); 9779 if ( defined($pid) && $pid == 0 ) { # I am a child 9780 deadlock_thread( $child, $tbl, $cxn ); 9781 } 9782 elsif ( !defined($pid) ) { 9783 die("Unable to fork for clearing deadlocks!\n"); 9784 } 9785 # I already exited if I'm a child, so I'm the parent. 9786 $children{$child} = $pid; 9787 } 9788 9789 # Wait for the children to exit. 9790 foreach my $child ( keys %children ) { 9791 my $pid = waitpid($children{$child}, 0); 9792 } 9793 9794 # Clean up. 9795 do_query($cxn, "drop table $tbl"); 9796 9797 # enable binary logging for the session again 9798 # the session by itself will not be used anymore, but this is clean :) 9799 do_query($cxn, "set SQL_LOG_BIN=1"); 9800 9801 }; 9802 if ( $EVAL_ERROR ) { 9803 print $EVAL_ERROR; 9804 pause(); 9805 } 9806 9807 $clearing_deadlocks = 0; 9808} 9809 9810sub get_master_logs { 9811 my @cxns = @_; 9812 my @result; 9813 if ( !$info_gotten{master_logs}++ ) { 9814 foreach my $cxn ( @cxns ) { 9815 my $stmt = do_stmt($cxn, 'SHOW_MASTER_LOGS') or next; 9816 push @result, @{$stmt->fetchall_arrayref({})}; 9817 } 9818 } 9819 return @result; 9820} 9821 9822# get_master_slave_status {{{3 9823# Inserts special counters as though they are SHOW STATUS counters. 9824sub get_master_slave_status { 9825 my @cxns = @_; 9826 if ( !$info_gotten{replication_status}++ ) { 9827 foreach my $cxn ( @cxns ) { 9828 $vars{$cxn}->{$clock} ||= {}; 9829 my $vars = $vars{$cxn}->{$clock}; 9830 $vars->{cxn} = $cxn; 9831 $vars->{Uptime_hires} ||= get_uptime($cxn); 9832 9833 my $stmt = do_stmt($cxn, 'SHOW_MASTER_STATUS') or next; 9834 my $res = $stmt->fetchall_arrayref({})->[0]; 9835 @{$vars}{ keys %$res } = values %$res; 9836 9837 } 9838 } 9839} 9840 9841# get_slave_status {{{3 9842# Separated handling of slave status to support 5.7 and replication channels 9843sub get_slave_status { 9844 my ($cxn, $channel) = @_; 9845 my $chcxn = $channel . '=' . $cxn; 9846 $vars{$chcxn}->{$clock} ||= {}; 9847 my $vars = $vars{$chcxn}->{$clock}; 9848 $vars->{chcxn} = $chcxn; 9849 $vars->{Uptime_hires} ||= get_uptime($chcxn); 9850 9851 if ( $channel =~ /no_channels/ ) { 9852 my $stmt = do_stmt($cxn, 'SHOW_SLAVE_STATUS') or next; 9853 my $res = $stmt->fetchall_arrayref({}); 9854 if ( $res && @$res ) { 9855 $res = $res->[0]; 9856 @{$vars}{ keys %$res } = values %$res; 9857 $vars->{Slave_ok} = 9858 (($res->{slave_sql_running} || 'Yes') eq 'Yes' 9859 && ($res->{slave_io_running} || 'Yes') eq 'Yes') ? 'Yes' : 'No'; 9860 } 9861 else { 9862 $vars->{Slave_ok} = 'Off'; 9863 } 9864 } else { 9865 my $dbh = connect_to_db($cxn); 9866 my $sql = 'SHOW SLAVE STATUS FOR CHANNEL \'' . $channel . '\''; 9867 my $stmt = $dbh->prepare($sql ) ; 9868 $stmt->execute(); 9869 my $res = $stmt->fetchall_arrayref({}); 9870 if ( $res && @$res ) { 9871 $res = $res->[0]; 9872 @{$vars}{ keys %$res } = values %$res; 9873 $vars->{Slave_ok} = 9874 (($res->{slave_sql_running} || 'Yes') eq 'Yes' 9875 && ($res->{slave_io_running} || 'Yes') eq 'Yes') ? 'Yes' : 'No'; 9876 } 9877 else { 9878 $vars->{Slave_ok} = 'Off'; 9879 } 9880 } 9881 } 9882 9883 9884 9885 9886sub is_func { 9887 my ( $word ) = @_; 9888 return defined(&$word) 9889 || eval "my \$x= sub { $word }; 1" 9890 || $EVAL_ERROR !~ m/^Bareword/; 9891} 9892 9893# Documentation {{{1 9894# ############################################################################ 9895# I put this last as per the Dog book. 9896# ############################################################################ 9897=pod 9898 9899=head1 NAME 9900 9901innotop - MySQL and InnoDB transaction/status monitor. 9902 9903=head1 SYNOPSIS 9904 9905To monitor servers normally: 9906 9907 innotop 9908 9909To monitor InnoDB status information from a file: 9910 9911 innotop /var/log/mysql/mysqld.err 9912 9913To run innotop non-interactively in a pipe-and-filter configuration: 9914 9915 innotop --count 5 -d 1 -n 9916 9917To monitor a database on another system using a particular username and password: 9918 9919 innotop -u <username> -p <password> -h <hostname> 9920 9921=head1 DESCRIPTION 9922 9923innotop monitors MySQL servers. Each of its modes shows you a different aspect 9924of what's happening in the server. For example, there's a mode for monitoring 9925replication, one for queries, and one for transactions. innotop refreshes its 9926data periodically, so you see an updating view. 9927 9928innotop has lots of features for power users, but you can start and run it with 9929virtually no configuration. If you're just getting started, see 9930L<"QUICK-START">. Press '?' at any time while running innotop for 9931context-sensitive help. 9932 9933=head1 QUICK-START 9934 9935To start innotop, open a terminal or command prompt. If you have installed 9936innotop on your system, you should be able to just type "innotop" and press 9937Enter; otherwise, you will need to change to innotop's directory and type "perl 9938innotop". 9939 9940With no options specified, innotop will attempt to connect to a MySQL server on 9941localhost using mysql_read_default_group=client for other connection 9942parameters. If you need to specify a different username and password, use the 9943-u and -p options, respectively. To monitor a MySQL database on another 9944host, use the -h option. 9945 9946After you've connected, innotop should show you something like the following: 9947 9948 [RO] Query List (? for help) localhost, 01:11:19, 449.44 QPS, 14/7/163 con/run 9949 9950 CXN When Load QPS Slow QCacheHit KCacheHit BpsIn BpsOut 9951 localhost Total 0.00 1.07k 697 0.00% 98.17% 476.83k 242.83k 9952 9953 CXN Cmd ID User Host DB Time Query 9954 localhost Query 766446598 test 10.0.0.1 foo 00:02 INSERT INTO table ( 9955 9956 9957(This sample is truncated at the right so it will fit on a terminal when running 9958'man innotop') 9959 9960If your server is busy, you'll see more output. Notice the first line on the 9961screen, which tells you that readonly is set to true ([RO]), what mode you're 9962in and what server you're connected to. You can change to other modes with 9963keystrokes; press 'T' to switch to a list of InnoDB transactions, for example. 9964 9965Press the '?' key to see what keys are active in the current mode. You can 9966press any of these keys and innotop will either take the requested action or 9967prompt you for more input. If your system has Term::ReadLine support, you can 9968use TAB and other keys to auto-complete and edit input. 9969 9970To quit innotop, press the 'q' key. 9971 9972=head1 OPTIONS 9973 9974innotop is mostly configured via its configuration file, but some of the 9975configuration options can come from the command line. You can also specify a 9976file to monitor for InnoDB status output; see L<"MONITORING A FILE"> for more 9977details. 9978 9979You can negate some options by prefixing the option name with --no. For 9980example, --noinc (or --no-inc) negates L<"--inc">. 9981 9982=over 9983 9984=item --color 9985 9986Enable or disable terminal coloring. Corresponds to the L<"color"> config file 9987setting. 9988 9989=item --config 9990 9991Specifies a configuration file to read. This option is non-sticky, that is to 9992say it does not persist to the configuration file itself. 9993 9994=item --count 9995 9996Refresh only the specified number of times (ticks) before exiting. Each refresh 9997is a pause for L<"interval"> seconds, followed by requesting data from MySQL 9998connections and printing it to the terminal. 9999 10000=item --delay 10001 10002Specifies the amount of time to pause between ticks (refreshes). Corresponds to 10003the configuration option L<"interval">. 10004 10005=item --help 10006 10007Print a summary of command-line usage and exit. 10008 10009=item --host 10010 10011Host to connect to. 10012 10013=item --inc 10014 10015Specifies whether innotop should display absolute numbers or relative numbers 10016(offsets from their previous values). Corresponds to the configuration option 10017L<"status_inc">. 10018 10019=item --mode 10020 10021Specifies the mode in which innotop should start. Corresponds to the 10022configuration option L<"mode">. 10023 10024=item --nonint 10025 10026Enable non-interactive operation. See L<"NON-INTERACTIVE OPERATION"> for more. 10027 10028=item --password 10029 10030Password to use for connection. 10031 10032=item --port 10033 10034Port to use for connection. 10035 10036=item --skipcentral 10037 10038Don't read the central configuration file. 10039 10040=item --timestamp 10041 10042In -n mode, write a timestamp either before every screenful of output, or if 10043the option is given twice, at the start of every line. The format is controlled 10044by the timeformat config variable. 10045 10046=item --user 10047 10048User to use for connection. 10049 10050=item --version 10051 10052Output version information and exit. 10053 10054=item --write 10055 10056Sets the configuration option L<"readonly"> to 0, making innotop write the 10057running configuration to ~/.innotop/innotop.conf on exit, if no configuration 10058file was loaded at start-up. 10059 10060=back 10061 10062=head1 HOTKEYS 10063 10064innotop is interactive, and you control it with key-presses. 10065 10066=over 10067 10068=item * 10069 10070Uppercase keys switch between modes. 10071 10072=item * 10073 10074Lowercase keys initiate some action within the current mode. 10075 10076=item * 10077 10078Other keys do something special like change configuration or show the 10079innotop license. 10080 10081=back 10082 10083Press '?' at any time to see the currently active keys and what they do. 10084 10085=head1 MODES 10086 10087Each of innotop's modes retrieves and displays a particular type of data from 10088the servers you're monitoring. You switch between modes with uppercase keys. 10089The following is a brief description of each mode, in alphabetical order. To 10090switch to the mode, press the key listed in front of its heading in the 10091following list: 10092 10093=over 10094 10095=item A: Health Dashboard 10096 10097This mode displays a single table with one row per monitored server. The 10098columns show essential overview information about the server's health, and 10099coloration rules show whether replication is running or if there are any very 10100long-running queries or excessive replication delay. 10101 10102=item B: InnoDB Buffers 10103 10104This mode displays information about the InnoDB buffer pool, page statistics, 10105insert buffer, and adaptive hash index. The data comes from SHOW INNODB STATUS. 10106 10107This mode contains the L<"buffer_pool">, L<"page_statistics">, 10108L<"insert_buffers">, and L<"adaptive_hash_index"> tables by default. 10109 10110=item C: Command Summary 10111 10112This mode is similar to mytop's Command Summary mode. It shows the 10113L<"cmd_summary"> table, which looks something like the following: 10114 10115 Command Summary (? for help) localhost, 25+07:16:43, 2.45 QPS, 3 thd, 5.0.40 10116 _____________________ Command Summary _____________________ 10117 Name Value Pct Last Incr Pct 10118 Select_scan 3244858 69.89% 2 100.00% 10119 Select_range 1354177 29.17% 0 0.00% 10120 Select_full_join 39479 0.85% 0 0.00% 10121 Select_full_range_join 4097 0.09% 0 0.00% 10122 Select_range_check 0 0.00% 0 0.00% 10123 10124The command summary table is built by extracting variables from 10125L<"STATUS_VARIABLES">. The variables must be numeric and must match the prefix 10126given by the L<"cmd_filter"> configuration variable. The variables are then 10127sorted by value descending and compared to the last variable, as shown above. 10128The percentage columns are percentage of the total of all variables in the 10129table, so you can see the relative weight of the variables. 10130 10131The example shows what you see if the prefix is "Select_". The default 10132prefix is "Com_". You can choose a prefix with the 's' key. 10133 10134It's rather like running SHOW VARIABLES LIKE "prefix%" with memory and 10135nice formatting. 10136 10137Values are aggregated across all servers. The Pct columns are not correctly 10138aggregated across multiple servers. This is a known limitation of the grouping 10139algorithm that may be fixed in the future. 10140 10141=item D: InnoDB Deadlocks 10142 10143This mode shows the transactions involved in the last InnoDB deadlock. A second 10144table shows the locks each transaction held and waited for. A deadlock is 10145caused by a cycle in the waits-for graph, so there should be two locks held and 10146one waited for unless the deadlock information is truncated. 10147 10148InnoDB puts deadlock information before some other information in the SHOW 10149INNODB STATUS output. If there are a lot of locks, the deadlock information can 10150grow very large, and there is a limit on the size of the SHOW INNODB 10151STATUS output. A large deadlock can fill the entire output, or even be 10152truncated, and prevent you from seeing other information at all. If you are 10153running innotop in another mode, for example T mode, and suddenly you don't see 10154anything, you might want to check and see if a deadlock has wiped out the data 10155you need. 10156 10157If it has, you can create a small deadlock to replace the large one. Use the 10158'w' key to 'wipe' the large deadlock with a small one. This will not work 10159unless you have defined a deadlock table for the connection (see L<"SERVER 10160CONNECTIONS">). 10161 10162You can also configure innotop to automatically detect when a large deadlock 10163needs to be replaced with a small one (see L<"auto_wipe_dl">). 10164 10165This mode displays the L<"deadlock_transactions"> and L<"deadlock_locks"> tables 10166by default. 10167 10168=item F: InnoDB Foreign Key Errors 10169 10170This mode shows the last InnoDB foreign key error information, such as the 10171table where it happened, when and who and what query caused it, and so on. 10172 10173InnoDB has a huge variety of foreign key error messages, and many of them are 10174just hard to parse. innotop doesn't always do the best job here, but there's 10175so much code devoted to parsing this messy, unparseable output that innotop is 10176likely never to be perfect in this regard. If innotop doesn't show you what 10177you need to see, just look at the status text directly. 10178 10179This mode displays the L<"fk_error"> table by default. 10180 10181=item I: InnoDB I/O Info 10182 10183This mode shows InnoDB's I/O statistics, including the I/O threads, pending I/O, 10184file I/O miscellaneous, and log statistics. It displays the L<"io_threads">, 10185L<"pending_io">, L<"file_io_misc">, and L<"log_statistics"> tables by default. 10186 10187=item K: InnoDB Lock Waits 10188 10189This mode shows information from InnoDB plugin's transaction and locking tables. 10190You can use it to find when a transaction is waiting for another, and kill the 10191blocking transaction. It displays the L<"innodb_blocked_blocker>" table. 10192 10193=item L: Locks 10194 10195This mode shows information about current locks. At the moment only InnoDB 10196locks are supported, and by default you'll only see locks for which transactions 10197are waiting. This information comes from the TRANSACTIONS section of the InnoDB 10198status text. If you have a very busy server, you may have frequent lock waits; 10199it helps to be able to see which tables and indexes are the "hot spot" for 10200locks. If your server is running pretty well, this mode should show nothing. 10201 10202You can configure MySQL and innotop to monitor not only locks for which a 10203transaction is waiting, but those currently held, too. You can do this with the 10204InnoDB Lock Monitor (L<http://dev.mysql.com/doc/en/innodb-monitor.html>). It's 10205not documented in the MySQL manual, but creating the lock monitor with the 10206following statement also affects the output of SHOW INNODB STATUS, which innotop 10207uses: 10208 10209 CREATE TABLE innodb_lock_monitor(a int) ENGINE=INNODB; 10210 10211This causes InnoDB to print its output to the MySQL file every 16 seconds or so, 10212as stated in the manual, but it also makes the normal SHOW INNODB STATUS output 10213include lock information, which innotop can parse and display (that's the 10214undocumented feature). 10215 10216This means you can do what may have seemed impossible: to a limited extent 10217(InnoDB truncates some information in the output), you can see which transaction 10218holds the locks something else is waiting for. You can also enable and disable 10219the InnoDB Lock Monitor with the key mappings in this mode. 10220 10221This mode displays the L<"innodb_locks"> table by default. Here's a sample of 10222the screen when one connection is waiting for locks another connection holds: 10223 10224 _________________________________ InnoDB Locks __________________________ 10225 CXN ID Type Waiting Wait Active Mode DB Table Index 10226 localhost 12 RECORD 1 00:10 00:10 X test t1 PRIMARY 10227 localhost 12 TABLE 0 00:10 00:10 IX test t1 10228 localhost 12 RECORD 1 00:10 00:10 X test t1 PRIMARY 10229 localhost 11 TABLE 0 00:00 00:25 IX test t1 10230 localhost 11 RECORD 0 00:00 00:25 X test t1 PRIMARY 10231 10232You can see the first connection, ID 12, is waiting for a lock on the PRIMARY 10233key on test.t1, and has been waiting for 10 seconds. The second connection 10234isn't waiting, because the Waiting column is 0, but it holds locks on the same 10235index. That tells you connection 11 is blocking connection 12. 10236 10237=item M: Master/Slave Replication Status 10238 10239This mode shows the output of SHOW SLAVE STATUS and SHOW MASTER STATUS in three 10240tables. The first two divide the slave's status into SQL and I/O thread status, 10241and the last shows master status. Filters are applied to eliminate non-slave 10242servers from the slave tables, and non-master servers from the master table. 10243 10244This mode displays the L<"slave_sql_status">, L<"slave_io_status">, and 10245L<"master_status"> tables by default. 10246 10247=item O: Open Tables 10248 10249This section comes from MySQL's SHOW OPEN TABLES command. By default it is 10250filtered to show tables which are in use by one or more queries, so you can 10251get a quick look at which tables are 'hot'. You can use this to guess which 10252tables might be locked implicitly. 10253 10254This mode displays the L<"open_tables"> mode by default. 10255 10256=item U: User Statistics 10257 10258This mode displays data that's available in Percona's enhanced version of MySQL 10259(also known as Percona Server with XtraDB). Specifically, it makes it easy to 10260enable and disable the so-called "user statistics." This feature gathers stats 10261on clients, threads, users, tables, and indexes and makes them available as 10262INFORMATION_SCHEMA tables. These are invaluable for understanding what your 10263server is doing. They are also available in MariaDB. 10264 10265The statistics supported so far are only from the TABLE_STATISTICS and 10266INDEX_STATISTICS tables added by Percona. There are three views: one of table stats, 10267one of index stats (which can be aggregated with the = key), and one of both. 10268 10269The server doesn't gather these stats by default. You have to set the variable 10270userstat_running to turn it on. You can do this easily with innotop from U mode, 10271with the 's' key. 10272 10273=item Q: Query List 10274 10275This mode displays the output from SHOW FULL PROCESSLIST, much like B<mytop>'s 10276query list mode. This mode does B<not> show InnoDB-related information. This 10277is probably one of the most useful modes for general usage. 10278 10279There is an informative header that shows general status information about 10280your server. You can toggle it on and off with the 'h' key. By default, 10281innotop hides inactive processes and its own process. You can toggle these on 10282and off with the 'i' and 'a' keys. 10283 10284You can EXPLAIN a query from this mode with the 'e' key. This displays the 10285query's full text, the results of EXPLAIN, and in newer MySQL versions, even 10286the optimized query resulting from EXPLAIN EXTENDED. innotop also tries to 10287rewrite certain queries to make them EXPLAIN-able. For example, INSERT/SELECT 10288statements are rewritable. 10289 10290This mode displays the L<"q_header"> and L<"processlist"> tables by default. 10291 10292=item R: InnoDB Row Operations and Semaphores 10293 10294This mode shows InnoDB row operations, row operation miscellaneous, semaphores, 10295and information from the wait array. It displays the L<"row_operations">, 10296L<"row_operation_misc">, L<"semaphores">, and L<"wait_array"> tables by default. 10297 10298=item S: Variables & Status 10299 10300This mode calculates statistics, such as queries per second, and prints them out 10301in several different styles. You can show absolute values, or incremental values 10302between ticks. 10303 10304You can switch between the views by pressing a key. The 's' key prints a 10305single line each time the screen updates, in the style of B<vmstat>. The 'g' 10306key changes the view to a graph of the same numbers, sort of like B<tload>. 10307The 'v' key changes the view to a pivoted table of variable names on the left, 10308with successive updates scrolling across the screen from left to right. You can 10309choose how many updates to put on the screen with the L<"num_status_sets"> 10310configuration variable. 10311 10312Headers may be abbreviated to fit on the screen in interactive operation. You 10313choose which variables to display with the 'c' key, which selects from 10314predefined sets, or lets you create your own sets. You can edit the current set 10315with the 'e' key. 10316 10317This mode doesn't really display any tables like other modes. Instead, it uses 10318a table definition to extract and format the data, but it then transforms the 10319result in special ways before outputting it. It uses the L<"var_status"> table 10320definition for this. 10321 10322=item T: InnoDB Transactions 10323 10324This mode shows transactions from the InnoDB monitor's output, in B<top>-like 10325format. This mode is the reason I wrote innotop. 10326 10327You can kill queries or processes with the 'k' and 'x' keys, and EXPLAIN a query 10328with the 'e' or 'f' keys. InnoDB doesn't print the full query in transactions, 10329so explaining may not work right if the query is truncated. 10330 10331The informational header can be toggled on and off with the 'h' key. By 10332default, innotop hides inactive transactions and its own transaction. You can 10333toggle this on and off with the 'i' and 'a' keys. 10334 10335This mode displays the L<"t_header"> and L<"innodb_transactions"> tables by 10336default. 10337 10338=back 10339 10340=head1 INNOTOP STATUS 10341 10342The first line innotop displays is a "status bar" of sorts. What it contains 10343depends on the mode you're in, and what servers you're monitoring. The first 10344few words are always [RO] (if readonly is set to 1), the innotop mode, such as 10345"InnoDB Txns" for T mode, followed by a reminder to press '?' for help at any 10346time. 10347 10348=head2 ONE SERVER 10349 10350The simplest case is when you're monitoring a single server. In this case, the 10351name of the connection is next on the status line. This is the name you gave 10352when you created the connection -- most likely the MySQL server's hostname. 10353This is followed by the server's uptime. 10354 10355If you're in an InnoDB mode, such as T or B, the next word is "InnoDB" followed 10356by some information about the SHOW INNODB STATUS output used to render the 10357screen. The first word is the number of seconds since the last SHOW INNODB 10358STATUS, which InnoDB uses to calculate some per-second statistics. The next is 10359a smiley face indicating whether the InnoDB output is truncated. If the smiley 10360face is a :-), all is well; there is no truncation. A :^| means the transaction 10361list is so long, InnoDB has only printed out some of the transactions. Finally, 10362a frown :-( means the output is incomplete, which is probably due to a deadlock 10363printing too much lock information (see L<"D: InnoDB Deadlocks">). 10364 10365The next two words indicate the server's queries per second (QPS) and how many 10366threads (connections) exist. Finally, the server's version number is the last 10367thing on the line. 10368 10369=head2 MULTIPLE SERVERS 10370 10371If you are monitoring multiple servers (see L<"SERVER CONNECTIONS">), the status 10372line does not show any details about individual servers. Instead, it shows the 10373names of the connections that are active. Again, these are connection names you 10374specified, which are likely to be the server's hostname. A connection that has 10375an error is prefixed with an exclamation point. 10376 10377If you are monitoring a group of servers (see L<"SERVER GROUPS">), the status 10378line shows the name of the group. If any connection in the group has an 10379error, the group's name is followed by the fraction of the connections that 10380don't have errors. 10381 10382See L<"ERROR HANDLING"> for more details about innotop's error handling. 10383 10384=head2 MONITORING A FILE 10385 10386If you give a filename on the command line, innotop will not connect to ANY 10387servers at all. It will watch the specified file for InnoDB status output and 10388use that as its data source. It will always show a single connection called 10389'file'. And since it can't connect to a server, it can't determine how long the 10390server it's monitoring has been up; so it calculates the server's uptime as time 10391since innotop started running. 10392 10393=head1 SERVER ADMINISTRATION 10394 10395While innotop is primarily a monitor that lets you watch and analyze your 10396servers, it can also send commands to servers. The most frequently useful 10397commands are killing queries and stopping or starting slaves. 10398 10399You can kill a connection, or in newer versions of MySQL kill a query but not a 10400connection, from L<"Q: Query List"> and L<"T: InnoDB Transactions"> modes. 10401Press 'k' to issue a KILL command, or 'x' to issue a KILL QUERY command. 10402innotop will prompt you for the server and/or connection ID to kill (innotop 10403does not prompt you if there is only one possible choice for any input). 10404innotop pre-selects the longest-running query, or the oldest connection. 10405Confirm the command with 'y'. 10406 10407In L<"M: Master/Slave Replication Status"> mode, you can start and stop slaves 10408with the 'a' and 'o' keys, respectively. You can send these commands to many 10409slaves at once. innotop fills in a default command of START SLAVE or STOP SLAVE 10410for you, but you can actually edit the command and send anything you wish, such 10411as SET GLOBAL SQL_SLAVE_SKIP_COUNTER=1 to make the slave skip one binlog event 10412when it starts. 10413 10414You can also ask innotop to calculate the earliest binlog in use by any slave 10415and issue a PURGE MASTER LOGS on the master. Use the 'b' key for this. innotop 10416will prompt you for a master to run the command on, then prompt you for the 10417connection names of that master's slaves (there is no way for innotop to 10418determine this reliably itself). innotop will find the minimum binlog in use by 10419these slave connections and suggest it as the argument to PURGE MASTER LOGS. 10420 10421in L<"U: User Statistics"> mode, you can use the 's' key to start and stop 10422the collection of the statistics data for TABLE_STATISTICS and similar. 10423 10424=head1 SERVER CONNECTIONS 10425 10426When you create a server connection using '@', innotop asks you for a series of 10427inputs, as follows: 10428 10429=over 10430 10431=item DSN 10432 10433A DSN is a Data Source Name, which is the initial argument passed to the DBI 10434module for connecting to a server. It is usually of the form 10435 10436 DBI:mysql:;mysql_read_default_group=mysql;host=HOSTNAME 10437 10438Since this DSN is passed to the DBD::mysql driver, you should read the driver's 10439documentation at L<"http://search.cpan.org/dist/DBD-mysql/lib/DBD/mysql.pm"> for 10440the exact details on all the options you can pass the driver in the DSN. You 10441can read more about DBI at L<http://dbi.perl.org/docs/>, and especially at 10442L<http://search.cpan.org/~timb/DBI/DBI.pm>. 10443 10444The mysql_read_default_group=mysql option lets the DBD driver read your MySQL 10445options files, such as ~/.my.cnf on UNIX-ish systems. You can use this to avoid 10446specifying a username or password for the connection. 10447 10448=item InnoDB Deadlock Table 10449 10450This optional item tells innotop a table name it can use to deliberately create 10451a small deadlock (see L<"D: InnoDB Deadlocks">). If you specify this option, 10452you just need to be sure the table doesn't exist, and that innotop can create 10453and drop the table with the InnoDB storage engine. You can safely omit or just 10454accept the default if you don't intend to use this. 10455 10456=item Username 10457 10458innotop will ask you if you want to specify a username. If you say 'y', it will 10459then prompt you for a user name. If you have a MySQL option file that specifies 10460your username, you don't have to specify a username. 10461 10462The username defaults to your login name on the system you're running innotop on. 10463 10464=item Password 10465 10466innotop will ask you if you want to specify a password. Like the username, the 10467password is optional, but there's an additional prompt that asks if you want to 10468save the password in the innotop configuration file. If you don't save it in 10469the configuration file, innotop will prompt you for a password each time it 10470starts. Passwords in the innotop configuration file are saved in plain text, 10471not encrypted in any way. 10472 10473=back 10474 10475Once you finish answering these questions, you should be connected to a server. 10476But innotop isn't limited to monitoring a single server; you can define many 10477server connections and switch between them by pressing the '@' key. See 10478L<"SWITCHING BETWEEN CONNECTIONS">. 10479 10480=head1 SERVER GROUPS 10481 10482If you have multiple MySQL instances, you can put them into named groups, such 10483as 'all', 'masters', and 'slaves', which innotop can monitor all together. 10484 10485You can choose which group to monitor with the '#' key, and you can press the 10486TAB key to switch to the next group. If you're not currently monitoring a 10487group, pressing TAB selects the first group. 10488 10489To create a group, press the '#' key and type the name of your new group, then 10490type the names of the connections you want the group to contain. 10491 10492=head1 SWITCHING BETWEEN CONNECTIONS 10493 10494innotop lets you quickly switch which servers you're monitoring. The most basic 10495way is by pressing the '@' key and typing the name(s) of the connection(s) you 10496want to use. This setting is per-mode, so you can monitor different connections 10497in each mode, and innotop remembers which connections you choose. 10498 10499You can quickly switch to the 'next' connection in alphabetical order with the 10500'n' key. If you're monitoring a server group (see L<"SERVER GROUPS">) this will 10501switch to the first connection. 10502 10503You can also type many connection names, and innotop will fetch and display data 10504from them all. Just separate the connection names with spaces, for example 10505"server1 server2." Again, if you type the name of a connection that doesn't 10506exist, innotop will prompt you for connection information and create the 10507connection. 10508 10509Another way to monitor multiple connections at once is with server groups. You 10510can use the TAB key to switch to the 'next' group in alphabetical order, or if 10511you're not monitoring any groups, TAB will switch to the first group. 10512 10513innotop does not fetch data in parallel from connections, so if you are 10514monitoring a large group or many connections, you may notice increased delay 10515between ticks. 10516 10517When you monitor more than one connection, innotop's status bar changes. See 10518L<"INNOTOP STATUS">. 10519 10520=head1 ERROR HANDLING 10521 10522Error handling is not that important when monitoring a single connection, but is 10523crucial when you have many active connections. A crashed server or lost 10524connection should not crash innotop. As a result, innotop will continue to run 10525even when there is an error; it just won't display any information from the 10526connection that had an error. Because of this, innotop's behavior might confuse 10527you. It's a feature, not a bug! 10528 10529innotop does not continue to query connections that have errors, because they 10530may slow innotop and make it hard to use, especially if the error is a problem 10531connecting and causes a long time-out. Instead, innotop retries the connection 10532occasionally to see if the error still exists. If so, it will wait until some 10533point in the future. The wait time increases in ticks as the Fibonacci series, 10534so it tries less frequently as time passes. 10535 10536Since errors might only happen in certain modes because of the SQL commands 10537issued in those modes, innotop keeps track of which mode caused the error. If 10538you switch to a different mode, innotop will retry the connection instead of 10539waiting. 10540 10541By default innotop will display the problem in red text at the bottom of the 10542first table on the screen. You can disable this behavior with the 10543L<"show_cxn_errors_in_tbl"> configuration option, which is enabled by default. 10544If the L<"debug"> option is enabled, innotop will display the error at the 10545bottom of every table, not just the first. And if L<"show_cxn_errors"> is 10546enabled, innotop will print the error text to STDOUT as well. Error messages 10547might only display in the mode that caused the error, depending on the mode and 10548whether innotop is avoiding querying that connection. 10549 10550=head1 NON-INTERACTIVE OPERATION 10551 10552You can run innotop in non-interactive mode, in which case it is entirely 10553controlled from the configuration file and command-line options. To start 10554innotop in non-interactive mode, give the L"<--nonint"> command-line option. 10555This changes innotop's behavior in the following ways: 10556 10557=over 10558 10559=item * 10560 10561Certain Perl modules are not loaded. Term::Readline is not loaded, since 10562innotop doesn't prompt interactively. Term::ANSIColor and Win32::Console::ANSI 10563modules are not loaded. Term::ReadKey is still used, since innotop may have to 10564prompt for connection passwords when starting up. 10565 10566=item * 10567 10568innotop does not clear the screen after each tick. 10569 10570=item * 10571 10572innotop does not persist any changes to the configuration file. 10573 10574=item * 10575 10576If L<"--count"> is given and innotop is in incremental mode (see L<"status_inc"> 10577and L<"--inc">), innotop actually refreshes one more time than specified so it 10578can print incremental statistics. This suppresses output during the first 10579tick, so innotop may appear to hang. 10580 10581=item * 10582 10583innotop only displays the first table in each mode. This is so the output can 10584be easily processed with other command-line utilities such as awk and sed. To 10585change which tables display in each mode, see L<"TABLES">. Since L<"Q: Query 10586List"> mode is so important, innotop automatically disables the L<"q_header"> 10587table. This ensures you'll see the L<"processlist"> table, even if you have 10588innotop configured to show the q_header table during interactive operation. 10589Similarly, in L<"T: InnoDB Transactions"> mode, the L<"t_header"> table is 10590suppressed so you see only the L<"innodb_transactions"> table. 10591 10592=item * 10593 10594All output is tab-separated instead of being column-aligned with whitespace, and 10595innotop prints the full contents of each table instead of only printing one 10596screenful at a time. 10597 10598=item * 10599 10600innotop only prints column headers once instead of every tick (see 10601L<"hide_hdr">). innotop does not print table captions (see 10602L<"display_table_captions">). innotop ensures there are no empty lines in the 10603output. 10604 10605=item * 10606 10607innotop does not honor the L<"shorten"> transformation, which normally shortens 10608some numbers to human-readable formats. 10609 10610=item * 10611 10612innotop does not print a status line (see L<"INNOTOP STATUS">). 10613 10614=back 10615 10616=head1 CONFIGURING 10617 10618Nearly everything about innotop is configurable. Most things are possible to 10619change with built-in commands, but you can also edit the configuration file. 10620 10621While running innotop, press the '$' key to bring up the configuration editing 10622dialog. Press another key to select the type of data you want to edit: 10623 10624=over 10625 10626=item S: Statement Sleep Times 10627 10628Edits SQL statement sleep delays, which make innotop pause for the specified 10629amount of time after executing a statement. See L<"SQL STATEMENTS"> for a 10630definition of each statement and what it does. By default innotop does not 10631delay after any statements. 10632 10633This feature is included so you can customize the side-effects caused by 10634monitoring your server. You may not see any effects, but some innotop users 10635have noticed that certain MySQL versions under very high load with InnoDB 10636enabled take longer than usual to execute SHOW GLOBAL STATUS. If innotop calls 10637SHOW FULL PROCESSLIST immediately afterward, the processlist contains more 10638queries than the machine actually averages at any given moment. Configuring 10639innotop to pause briefly after calling SHOW GLOBAL STATUS alleviates this 10640effect. 10641 10642Sleep times are stored in the L<"stmt_sleep_times"> section of the configuration 10643file. Fractional-second sleeps are supported, subject to your hardware's 10644limitations. 10645 10646=item c: Edit Columns 10647 10648Starts the table editor on one of the displayed tables. See L<"TABLE EDITOR">. 10649An alternative way to start the table editor without entering the configuration 10650dialog is with the '^' key. 10651 10652=item g: General Configuration 10653 10654Starts the configuration editor to edit global and mode-specific configuration 10655variables (see L<"MODES">). innotop prompts you to choose a variable from among 10656the global and mode-specific ones depending on the current mode. 10657 10658=item k: Row-Coloring Rules 10659 10660Starts the row-coloring rules editor on one of the displayed table(s). See 10661L<"COLORS"> for details. 10662 10663=item p: Manage Plugins 10664 10665Starts the plugin configuration editor. See L<"PLUGINS"> for details. 10666 10667=item s: Server Groups 10668 10669Lets you create and edit server groups. See L<"SERVER GROUPS">. 10670 10671=item t: Choose Displayed Tables 10672 10673Lets you choose which tables to display in this mode. See L<"MODES"> and 10674L<"TABLES">. 10675 10676=back 10677 10678=head1 CONFIGURATION FILE 10679 10680innotop's default configuration file locations are $HOME/.innotop and 10681/usr/local/etc/innotop.conf, and they are looked for in that order. If the first 10682configuration file exists, the second will not be processed. Those can be 10683overridden with the L<"--config"> command-line option. You can edit it by hand 10684safely, however innotop reads the configuration file when it starts, and, if 10685readonly is set to 0, writes it out again when it exits. Thus, if readonly is 10686set to 0, any changes you make by hand while innotop is running will be lost. 10687 10688innotop doesn't store its entire configuration in the configuration file. It 10689has a huge set of default configuration values that it holds only in memory, 10690and the configuration file only overrides these defaults. When you customize a 10691default setting, innotop notices, and then stores the customizations into the 10692file. This keeps the file size down, makes it easier to edit, and makes 10693upgrades easier. 10694 10695A configuration file is read-only be default. You can override that with 10696L<"--write">. See L<"readonly">. 10697 10698The configuration file is arranged into sections like an INI file. Each 10699section begins with [section-name] and ends with [/section-name]. Each 10700section's entries have a different syntax depending on the data they need to 10701store. You can put comments in the file; any line that begins with a # 10702character is a comment. innotop will not read the comments, so it won't write 10703them back out to the file when it exits. Comments in read-only configuration 10704files are still useful, though. 10705 10706The first line in the file is innotop's version number. This lets innotop 10707notice when the file format is not backwards-compatible, and upgrade smoothly 10708without destroying your customized configuration. 10709 10710The following list describes each section of the configuration file and the data 10711it contains: 10712 10713=over 10714 10715=item general 10716 10717The 'general' section contains global configuration variables and variables that 10718may be mode-specific, but don't belong in any other section. The syntax is a 10719simple key=value list. innotop writes a comment above each value to help you 10720edit the file by hand. 10721 10722=over 10723 10724=item S_func 10725 10726Controls S mode presentation (see L<"S: Variables & Status">). If g, values are 10727graphed; if s, values are like vmstat; if p, values are in a pivoted table. 10728 10729=item S_set 10730 10731Specifies which set of variables to display in L<"S: Variables & Status"> mode. 10732See L<"VARIABLE SETS">. 10733 10734=item auto_wipe_dl 10735 10736Instructs innotop to automatically wipe large deadlocks when it notices them. 10737When this happens you may notice a slight delay. At the next tick, you will 10738usually see the information that was being truncated by the large deadlock. 10739 10740=item charset 10741 10742Specifies what kind of characters to allow through the L<"no_ctrl_char"> 10743transformation. This keeps non-printable characters from confusing a 10744terminal when you monitor queries that contain binary data, such as images. 10745 10746The default is 'ascii', which considers anything outside normal ASCII to be a 10747control character. The other allowable values are 'unicode' and 'none'. 'none' 10748considers every character a control character, which can be useful for 10749collapsing ALL text fields in queries. 10750 10751=item cmd_filter 10752 10753This is the prefix that filters variables in L<"C: Command Summary"> mode. 10754 10755=item color 10756 10757Whether terminal coloring is permitted. 10758 10759=item cxn_timeout 10760 10761On MySQL versions 4.0.3 and newer, this variable is used to set the connection's 10762timeout, so MySQL doesn't close the connection if it is not used for a while. 10763This might happen because a connection isn't monitored in a particular mode, for 10764example. 10765 10766=item debug 10767 10768This option enables more verbose errors and makes innotop more strict in some 10769places. It can help in debugging filters and other user-defined code. It also 10770makes innotop write a lot of information to L<"debugfile"> when there is a 10771crash. 10772 10773=item debugfile 10774 10775A file to which innotop will write information when there is a crash. See 10776L<"FILES">. 10777 10778=item display_table_captions 10779 10780innotop displays a table caption above most tables. This variable suppresses or 10781shows captions on all tables globally. Some tables are configured with the 10782hide_caption property, which overrides this. 10783 10784=item global 10785 10786Whether to show GLOBAL variables and status. innotop only tries to do this on 10787servers which support the GLOBAL option to SHOW VARIABLES and SHOW STATUS. In 10788some MySQL versions, you need certain privileges to do this; if you don't have 10789them, innotop will not be able to fetch any variable and status data. This 10790configuration variable lets you run innotop and fetch what data you can even 10791without the elevated privileges. 10792 10793I can no longer find or reproduce the situation where GLOBAL wasn't allowed, but 10794I know there was one. 10795 10796=item graph_char 10797 10798Defines the character to use when drawing graphs in L<"S: Variables & Status"> 10799mode. 10800 10801=item header_highlight 10802 10803Defines how to highlight column headers. This only works if Term::ANSIColor is 10804available. Valid values are 'bold' and 'underline'. 10805 10806=item hide_hdr 10807 10808Hides column headers globally. 10809 10810=item interval 10811 10812The interval at which innotop will refresh its data (ticks). The interval is 10813implemented as a sleep time between ticks, so the true interval will vary 10814depending on how long it takes innotop to fetch and render data. 10815 10816This variable accepts fractions of a second. 10817 10818=item mode 10819 10820The mode in which innotop should start. Allowable arguments are the same as the 10821key presses that select a mode interactively. See L<"MODES">. 10822 10823=item num_digits 10824 10825How many digits to show in fractional numbers and percents. This variable's 10826range is between 0 and 9 and can be set directly from L<"S: Variables & Status"> 10827mode with the '+' and '-' keys. It is used in the L<"set_precision">, 10828L<"shorten">, and L<"percent"> transformations. 10829 10830=item num_status_sets 10831 10832Controls how many sets of status variables to display in pivoted L<"S: Variables 10833& Status"> mode. It also controls the number of old sets of variables innotop 10834keeps in its memory, so the larger this variable is, the more memory innotop 10835uses. 10836 10837=item plugin_dir 10838 10839Specifies where plugins can be found. By default, innotop stores plugins in the 10840'plugins' subdirectory of your innotop configuration directory. 10841 10842=item readonly 10843 10844Whether the configuration file is readonly. This cannot be set interactively. 10845 10846=item show_cxn_errors 10847 10848Makes innotop print connection errors to STDOUT. See L<"ERROR HANDLING">. 10849 10850=item show_cxn_errors_in_tbl 10851 10852Makes innotop display connection errors as rows in the first table on screen. 10853See L<"ERROR HANDLING">. 10854 10855=item show_percent 10856 10857Adds a '%' character after the value returned by the L<"percent"> 10858transformation. 10859 10860=item show_statusbar 10861 10862Controls whether to show the status bar in the display. See L<"INNOTOP 10863STATUS">. 10864 10865=item skip_innodb 10866 10867Disables fetching SHOW INNODB STATUS, in case your server(s) do not have InnoDB 10868enabled and you don't want innotop to try to fetch it. This can also be useful 10869when you don't have the SUPER privilege, required to run SHOW INNODB STATUS. 10870 10871=item spark 10872 10873Specifies how wide a spark chart is. There are two ASCII spark charts in A 10874mode, showing QPS and User_threads_running. 10875 10876=item status_inc 10877 10878Whether to show absolute or incremental values for status variables. 10879Incremental values are calculated as an offset from the last value innotop saw 10880for that variable. This is a global setting, but will probably become 10881mode-specific at some point. Right now it is honored a bit inconsistently; some 10882modes don't pay attention to it. 10883 10884=item timeformat 10885 10886The C-style strftime()-compatible format for the timestamp line to be printed 10887in -n mode when -t is set. 10888 10889=back 10890 10891=item plugins 10892 10893This section holds a list of package names of active plugins. If the plugin 10894exists, innotop will activate it. See L<"PLUGINS"> for more information. 10895 10896=item filters 10897 10898This section holds user-defined filters (see L<"FILTERS">). Each line is in the 10899format filter_name=text='filter text' tbls='table list'. 10900 10901The filter text is the text of the subroutine's code. The table list is a list 10902of tables to which the filter can apply. By default, user-defined filters apply 10903to the table for which they were created, but you can manually override that by 10904editing the definition in the configuration file. 10905 10906=item active_filters 10907 10908This section stores which filters are active on each table. Each line is in the 10909format table_name=filter_list. 10910 10911=item tbl_meta 10912 10913This section stores user-defined or user-customized columns (see L<"COLUMNS">). 10914Each line is in the format col_name=properties, where the properties are a 10915name=quoted-value list. 10916 10917=item connections 10918 10919This section holds the server connections you have defined. Each line is in 10920the format name=properties, where the properties are a name=value list. The 10921properties are self-explanatory, and the only one that is treated specially is 10922'pass' which is only present if 'savepass' is set. This section of the 10923configuration file will be skipped if any DSN, username, or password 10924command-line options are used. See L<"SERVER CONNECTIONS">. 10925 10926=item active_connections 10927 10928This section holds a list of which connections are active in each mode. Each 10929line is in the format mode_name=connection_list. 10930 10931=item server_groups 10932 10933This section holds server groups. Each line is in the format 10934name=connection_list. See L<"SERVER GROUPS">. 10935 10936=item active_server_groups 10937 10938This section holds a list of which server group is active in each mode. Each 10939line is in the format mode_name=server_group. 10940 10941=item max_values_seen 10942 10943This section holds the maximum values seen for variables. This is used to scale 10944the graphs in L<"S: Variables & Status"> mode. Each line is in the format 10945name=value. 10946 10947=item active_columns 10948 10949This section holds table column lists. Each line is in the format 10950tbl_name=column_list. See L<"COLUMNS">. 10951 10952=item sort_cols 10953 10954This section holds the sort definition. Each line is in the format 10955tbl_name=column_list. If a column is prefixed with '-', that column sorts 10956descending. See L<"SORTING">. 10957 10958=item visible_tables 10959 10960This section defines which tables are visible in each mode. Each line is in the 10961format mode_name=table_list. See L<"TABLES">. 10962 10963=item varsets 10964 10965This section defines variable sets for use in L<"S: Status & Variables"> mode. 10966Each line is in the format name=variable_list. See L<"VARIABLE SETS">. 10967 10968=item colors 10969 10970This section defines colorization rules. Each line is in the format 10971tbl_name=property_list. See L<"COLORS">. 10972 10973=item stmt_sleep_times 10974 10975This section contains statement sleep times. Each line is in the format 10976statement_name=sleep_time. See L<"S: Statement Sleep Times">. 10977 10978=item group_by 10979 10980This section contains column lists for table group_by expressions. Each line is 10981in the format tbl_name=column_list. See L<"GROUPING">. 10982 10983=back 10984 10985=head1 CUSTOMIZING 10986 10987You can customize innotop a great deal. For example, you can: 10988 10989=over 10990 10991=item * 10992 10993Choose which tables to display, and in what order. 10994 10995=item * 10996 10997Choose which columns are in those tables, and create new columns. 10998 10999=item * 11000 11001Filter which rows display with built-in filters, user-defined filters, and 11002quick-filters. 11003 11004=item * 11005 11006Sort the rows to put important data first or group together related rows. 11007 11008=item * 11009 11010Highlight rows with color. 11011 11012=item * 11013 11014Customize the alignment, width, and formatting of columns, and apply 11015transformations to columns to extract parts of their values or format the values 11016as you wish (for example, shortening large numbers to familiar units). 11017 11018=item * 11019 11020Design your own expressions to extract and combine data as you need. This gives 11021you unlimited flexibility. 11022 11023=back 11024 11025All these and more are explained in the following sections. 11026 11027=head2 TABLES 11028 11029A table is what you'd expect: a collection of columns. It also has some other 11030properties, such as a caption. Filters, sorting rules, and colorization rules 11031belong to tables and are covered in later sections. 11032 11033Internally, table meta-data is defined in a data structure called %tbl_meta. 11034This hash holds all built-in table definitions, which contain a lot of default 11035instructions to innotop. The meta-data includes the caption, a list of columns 11036the user has customized, a list of columns, a list of visible columns, a list of 11037filters, color rules, a sort-column list, sort direction, and some information 11038about the table's data sources. Most of this is customizable via the table 11039editor (see L<"TABLE EDITOR">). 11040 11041You can choose which tables to show by pressing the '$' key. See L<"MODES"> and 11042L<"TABLES">. 11043 11044The table life-cycle is as follows: 11045 11046=over 11047 11048=item * 11049 11050Each table begins with a data source, which is an array of hashes. See below 11051for details on data sources. 11052 11053=item * 11054 11055Each element of the data source becomes a row in the final table. 11056 11057=item * 11058 11059For each element in the data source, innotop extracts values from the source and 11060creates a row. This row is another hash, which later steps will refer to as 11061$set. The values innotop extracts are determined by the table's columns. Each 11062column has an extraction subroutine, compiled from an expression (see 11063L<"EXPRESSIONS">). The resulting row is a hash whose keys are named the same as 11064the column name. 11065 11066=item * 11067 11068innotop filters the rows, removing those that don't need to be displayed. See 11069L<"FILTERS">. 11070 11071=item * 11072 11073innotop sorts the rows. See L<"SORTING">. 11074 11075=item * 11076 11077innotop groups the rows together, if specified. See L<"GROUPING">. 11078 11079=item * 11080 11081innotop colorizes the rows. See L<"COLORS">. 11082 11083=item * 11084 11085innotop transforms the column values in each row. See L<"TRANSFORMATIONS">. 11086 11087=item * 11088 11089innotop optionally pivots the rows (see L<"PIVOTING">), then filters and sorts 11090them. 11091 11092=item * 11093 11094innotop formats and justifies the rows as a table. During this step, innotop 11095applies further formatting to the column values, including alignment, maximum 11096and minimum widths. innotop also does final error checking to ensure there are 11097no crashes due to undefined values. innotop then adds a caption if specified, 11098and the table is ready to print. 11099 11100=back 11101 11102The lifecycle is slightly different if the table is pivoted, as noted above. To 11103clarify, if the table is pivoted, the process is extract, group, transform, 11104pivot, filter, sort, create. If it's not pivoted, the process is extract, 11105filter, sort, group, color, transform, create. This slightly convoluted process 11106doesn't map all that well to SQL, but pivoting complicates things pretty 11107thoroughly. Roughly speaking, filtering and sorting happen as late as needed to 11108effect the final result as you might expect, but as early as possible for 11109efficiency. 11110 11111Each built-in table is described below: 11112 11113=over 11114 11115=item adaptive_hash_index 11116 11117Displays data about InnoDB's adaptive hash index. Data source: 11118L<"STATUS_VARIABLES">. 11119 11120=item buffer_pool 11121 11122Displays data about InnoDB's buffer pool. Data source: L<"STATUS_VARIABLES">. 11123 11124=item cmd_summary 11125 11126Displays weighted status variables. Data source: L<"STATUS_VARIABLES">. 11127 11128=item deadlock_locks 11129 11130Shows which locks were held and waited for by the last detected deadlock. Data 11131source: L<"DEADLOCK_LOCKS">. 11132 11133=item deadlock_transactions 11134 11135Shows transactions involved in the last detected deadlock. Data source: 11136L<"DEADLOCK_TRANSACTIONS">. 11137 11138=item explain 11139 11140Shows the output of EXPLAIN. Data source: L<"EXPLAIN">. 11141 11142=item file_io_misc 11143 11144Displays data about InnoDB's file and I/O operations. Data source: 11145L<"STATUS_VARIABLES">. 11146 11147=item fk_error 11148 11149Displays various data about InnoDB's last foreign key error. Data source: 11150L<"STATUS_VARIABLES">. 11151 11152=item health_dashboard 11153 11154Displays an overall summary of servers, one server per line, for monitoring. 11155Data source: L<"STATUS_VARIABLES">, L<"MASTER_SLAVE">, L<"PROCESSLIST_STATS">. 11156 11157=item index_statistics 11158 11159Displays data from the INDEX_STATISTICS table in Percona-enhanced servers. 11160 11161=item index_table_statistics 11162 11163Displays data from the INDEX_STATISTICS and TABLE_STATISTICS tables in 11164Percona-enhanced servers. It joins the two together, grouped by the database 11165and table name. It is the default view in L<"U: User Statistics"> mode, 11166and makes it easy to see what tables are hot, how many rows are read from indexes, 11167how many changes are made, and how many changes are made to indexes. 11168 11169=item innodb_blocked_blocker 11170 11171Displays InnoDB locks and lock waits. Data source: L<"INNODB_BLOCKED_BLOCKER">. 11172 11173=item innodb_locks 11174 11175Displays InnoDB locks. Data source: L<"INNODB_LOCKS">. 11176 11177=item innodb_transactions 11178 11179Displays data about InnoDB's current transactions. Data source: 11180L<"INNODB_TRANSACTIONS">. 11181 11182=item insert_buffers 11183 11184Displays data about InnoDB's insert buffer. Data source: L<"STATUS_VARIABLES">. 11185 11186=item io_threads 11187 11188Displays data about InnoDB's I/O threads. Data source: L<"IO_THREADS">. 11189 11190=item log_statistics 11191 11192Displays data about InnoDB's logging system. Data source: L<"STATUS_VARIABLES">. 11193 11194=item master_status 11195 11196Displays replication master status. Data source: L<"STATUS_VARIABLES">. 11197 11198=item open_tables 11199 11200Displays open tables. Data source: L<"OPEN_TABLES">. 11201 11202=item page_statistics 11203 11204Displays InnoDB page statistics. Data source: L<"STATUS_VARIABLES">. 11205 11206=item pending_io 11207 11208Displays InnoDB pending I/O operations. Data source: L<"STATUS_VARIABLES">. 11209 11210=item processlist 11211 11212Displays current MySQL processes (threads/connections). Data source: 11213L<"PROCESSLIST">. 11214 11215=item q_header 11216 11217Displays various status values. Data source: L<"STATUS_VARIABLES">. 11218 11219=item row_operation_misc 11220 11221Displays data about InnoDB's row operations. Data source: 11222L<"STATUS_VARIABLES">. 11223 11224=item row_operations 11225 11226Displays data about InnoDB's row operations. Data source: 11227L<"STATUS_VARIABLES">. 11228 11229=item semaphores 11230 11231Displays data about InnoDB's semaphores and mutexes. Data source: 11232L<"STATUS_VARIABLES">. 11233 11234=item slave_io_status 11235 11236Displays data about the slave I/O thread. Data source: 11237L<"STATUS_VARIABLES">. 11238 11239=item slave_sql_status 11240 11241Displays data about the slave SQL thread. Data source: L<"STATUS_VARIABLES">. 11242 11243=item table_statistics 11244 11245Displays data from the TABLE_STATISTICS table in Percona-enhanced servers. 11246 11247=item t_header 11248 11249Displays various InnoDB status values. Data source: L<"STATUS_VARIABLES">. 11250 11251=item var_status 11252 11253Displays user-configurable data. Data source: L<"STATUS_VARIABLES">. 11254 11255=item wait_array 11256 11257Displays data about InnoDB's OS wait array. Data source: L<"OS_WAIT_ARRAY">. 11258 11259=back 11260 11261=head2 COLUMNS 11262 11263Columns belong to tables. You can choose a table's columns by pressing the '^' 11264key, which starts the L<"TABLE EDITOR"> and lets you choose and edit columns. 11265Pressing 'e' from within the table editor lets you edit the column's properties: 11266 11267=over 11268 11269=item * 11270 11271hdr: a column header. This appears in the first row of the table. 11272 11273=item * 11274 11275just: justification. '-' means left-justified and '' means right-justified, 11276just as with printf formatting codes (not a coincidence). 11277 11278=item * 11279 11280dec: whether to further align the column on the decimal point. 11281 11282=item * 11283 11284num: whether the column is numeric. This affects how values are sorted 11285(lexically or numerically). 11286 11287=item * 11288 11289label: a small note about the column, which appears in dialogs that help the 11290user choose columns. 11291 11292=item * 11293 11294src: an expression that innotop uses to extract the column's data from its 11295source (see L<"DATA SOURCES">). See L<"EXPRESSIONS"> for more on expressions. 11296 11297=item * 11298 11299minw: specifies a minimum display width. This helps stabilize the display, 11300which makes it easier to read if the data is changing frequently. 11301 11302=item * 11303 11304maxw: similar to minw. 11305 11306=item * 11307 11308trans: a list of column transformations. See L<"TRANSFORMATIONS">. 11309 11310=item * 11311 11312agg: an aggregate function. See L<"GROUPING">. The default is L<"first">. 11313 11314=item * 11315 11316aggonly: controls whether the column only shows when grouping is enabled on the 11317table (see L<"GROUPING">). By default, this is disabled. This means columns 11318will always be shown by default, whether grouping is enabled or not. If a 11319column's aggonly is set true, the column will appear when you toggle grouping on 11320the table. Several columns are set this way, such as the count column on 11321L<"processlist"> and L<"innodb_transactions">, so you don't see a count when the 11322grouping isn't enabled, but you do when it is. 11323 11324=item * 11325 11326agghide: the reverse of aggonly. The column is hidden when grouping is enabled. 11327 11328=back 11329 11330=head2 FILTERS 11331 11332Filters remove rows from the display. They behave much like a WHERE clause in 11333SQL. innotop has several built-in filters, which remove irrelevant information 11334like inactive queries, but you can define your own as well. innotop also lets 11335you create quick-filters, which do not get saved to the configuration file, and 11336are just an easy way to quickly view only some rows. 11337 11338You can enable or disable a filter on any table. Press the '%' key (mnemonic: % 11339looks kind of like a line being filtered between two circles) and choose which 11340table you want to filter, if asked. You'll then see a list of possible filters 11341and a list of filters currently enabled for that table. Type the names of 11342filters you want to apply and press Enter. 11343 11344=head3 USER-DEFINED FILTERS 11345 11346If you type a name that doesn't exist, innotop will prompt you to create the 11347filter. Filters are easy to create if you know Perl, and not hard if you don't. 11348What you're doing is creating a subroutine that returns true if the row should 11349be displayed. The row is a hash reference passed to your subroutine as $set. 11350 11351For example, imagine you want to filter the processlist table so you only see 11352queries that have been running more than five minutes. Type a new name for your 11353filter, and when prompted for the subroutine body, press TAB to initiate your 11354terminal's auto-completion. You'll see the names of the columns in the 11355L<"processlist"> table (innotop generally tries to help you with auto-completion 11356lists). You want to filter on the 'time' column. Type the text "$set->{time} > 11357300" to return true when the query is more than five minutes old. That's all 11358you need to do. 11359 11360In other words, the code you're typing is surrounded by an implicit context, 11361which looks like this: 11362 11363 sub filter { 11364 my ( $set ) = @_; 11365 # YOUR CODE HERE 11366 } 11367 11368If your filter doesn't work, or if something else suddenly behaves differently, 11369you might have made an error in your filter, and innotop is silently catching 11370the error. Try enabling L<"debug"> to make innotop throw an error instead. 11371 11372=head3 QUICK-FILTERS 11373 11374innotop's quick-filters are a shortcut to create a temporary filter that doesn't 11375persist when you restart innotop. To create a quick-filter, press the '/' key. 11376innotop will prompt you for the column name and filter text. Again, you can use 11377auto-completion on column names. The filter text can be just the text you want 11378to "search for." For example, to filter the L<"processlist"> table on queries 11379that refer to the products table, type '/' and then 'info product'. Internally, 11380the filter is compiled into a subroutine like this: 11381 11382 sub filter { 11383 my ( $set ) = @_; 11384 $set->{info} =~ m/product/; 11385 } 11386 11387The filter text can actually be any Perl regular expression, but of course a 11388literal string like 'product' works fine as a regular expression. 11389 11390What if you want the filter to discard matching rows, rather than showing 11391matching rows? If you're familiar with Perl regular expressions, you might 11392guess how to do this. You have to use a zero-width negative lookahead 11393assertion. If you don't know what that means, don't worry. Let's filter out 11394all rows where the command is Gandalf. Type the following: 11395 11396 1. / 11397 2. cmd ^(?!Gandalf) 11398 11399Behind the scenes innotop compiles the quick-filter into a specially tagged 11400filter that is otherwise like any other filter. It just isn't saved to the 11401configuration file. 11402 11403To clear quick-filters, press the '\' key and innotop will clear them all at 11404once. 11405 11406=head2 SORTING 11407 11408innotop has sensible built-in defaults to sort the most important rows to the 11409top of the table. Like anything else in innotop, you can customize how any 11410table is sorted. 11411 11412To start the sort dialog, start the L<"TABLE EDITOR"> with the '^' key, choose a 11413table if necessary, and press the 's' key. You'll see a list of columns you can 11414use in the sort expression and the current sort expression, if any. Enter a 11415list of columns by which you want to sort and press Enter. If you want to 11416reverse sort, prefix the column name with a minus sign. For example, if you 11417want to sort by column a ascending, then column b descending, type 'a -b'. You 11418can also explicitly add a + in front of columns you want to sort ascending, but 11419it's not required. 11420 11421Some modes have keys mapped to open this dialog directly, and to quickly reverse 11422sort direction. Press '?' as usual to see which keys are mapped in any mode. 11423 11424=head2 GROUPING 11425 11426innotop can group, or aggregate, rows together (the terms are used 11427interchangeably). This is quite similar to an SQL GROUP BY clause. You can 11428specify to group on certain columns, or if you don't specify any, the entire set 11429of rows is treated as one group. This is quite like SQL so far, but unlike SQL, 11430you can also select un-grouped columns. innotop actually aggregates every 11431column. If you don't explicitly specify a grouping function, the default is 11432'first'. This is basically a convenience so you don't have to specify an 11433aggregate function for every column you want in the result. 11434 11435You can quickly toggle grouping on a table with the '=' key, which toggles its 11436aggregate property. This property doesn't persist to the config file. 11437 11438The columns by which the table is grouped are specified in its group_by 11439property. When you turn grouping on, innotop places the group_by columns at the 11440far left of the table, even if they're not supposed to be visible. The rest of 11441the visible columns appear in order after them. 11442 11443Two tables have default group_by lists and a count column built in: 11444L<"processlist"> and L<"innodb_transactions">. The grouping is by connection 11445and status, so you can quickly see how many queries or transactions are in a 11446given status on each server you're monitoring. The time columns are aggregated 11447as a sum; other columns are left at the default 'first' aggregation. 11448 11449By default, the table shown in L<"S: Variables & Status"> mode also uses 11450grouping so you can monitor variables and status across many servers. The 11451default aggregation function in this mode is 'avg'. 11452 11453Valid grouping functions are defined in the %agg_funcs hash. They include 11454 11455=over 11456 11457=item first 11458 11459Returns the first element in the group. 11460 11461=item count 11462 11463Returns the number of elements in the group, including undefined elements, much 11464like SQL's COUNT(*). 11465 11466=item avg 11467 11468Returns the average of defined elements in the group. 11469 11470=item sum 11471 11472Returns the sum of elements in the group. 11473 11474=back 11475 11476Here's an example of grouping at work. Suppose you have a very busy server with 11477hundreds of open connections, and you want to see how many connections are in 11478what status. Using the built-in grouping rules, you can press 'Q' to enter 11479L<"Q: Query List"> mode. Press '=' to toggle grouping (if necessary, select the 11480L<"processlist"> table when prompted). 11481 11482Your display might now look like the following: 11483 11484 Query List (? for help) localhost, 32:33, 0.11 QPS, 1 thd, 5.0.38-log 11485 11486 CXN Cmd Cnt ID User Host Time Query 11487 localhost Query 49 12933 webusr localhost 19:38 SELECT * FROM 11488 localhost Sending Da 23 2383 webusr localhost 12:43 SELECT col1, 11489 localhost Sleep 120 140 webusr localhost 5:18:12 11490 localhost Statistics 12 19213 webusr localhost 01:19 SELECT * FROM 11491 11492That's actually quite a worrisome picture. You've got a lot of idle connections 11493(Sleep), and some connections executing queries (Query and Sending Data). 11494That's okay, but you also have a lot in Statistics status, collectively spending 11495over a minute. That means the query optimizer is having a really hard time 11496generating execution plans for your statements. Something is wrong; it should 11497normally take milliseconds to plan queries. You might not have seen this pattern if you 11498didn't look at your connections in aggregate. (This is a made-up example, but 11499it can happen in real life). 11500 11501=head2 PIVOTING 11502 11503innotop can pivot a table for more compact display, similar to a Pivot Table in 11504a spreadsheet (also known as a crosstab). Pivoting a table makes columns into 11505rows. Assume you start with this table: 11506 11507 foo bar 11508 === === 11509 1 3 11510 2 4 11511 11512After pivoting, the table will look like this: 11513 11514 name set0 set1 11515 ==== ==== ==== 11516 foo 1 2 11517 bar 3 4 11518 11519To get reasonable results, you might need to group as well as pivoting. 11520innotop currently does this for L<"S: Variables & Status"> mode. 11521 11522=head2 COLORS 11523 11524By default, innotop highlights rows with color so you can see at a glance which 11525rows are more important. You can customize the colorization rules and add your 11526own to any table. Open the table editor with the '^' key, choose a table if 11527needed, and press 'o' to open the color editor dialog. 11528 11529The color editor dialog displays the rules applied to the table, in the order 11530they are evaluated. Each row is evaluated against each rule to see if the rule 11531matches the row; if it does, the row gets the specified color, and no further 11532rules are evaluated. The rules look like the following: 11533 11534 state eq Locked black on_red 11535 cmd eq Sleep white 11536 user eq system user white 11537 cmd eq Connect white 11538 cmd eq Binlog Dump white 11539 time > 600 red 11540 time > 120 yellow 11541 time > 60 green 11542 time > 30 cyan 11543 11544This is the default rule set for the L<"processlist"> table. In order of 11545priority, these rules make locked queries black on a red background, "gray out" 11546connections from replication and sleeping queries, and make queries turn from 11547cyan to red as they run longer. 11548 11549(For some reason, the ANSI color code "white" is actually a light gray. Your 11550terminal's display may vary; experiment to find colors you like). 11551 11552You can use keystrokes to move the rules up and down, which re-orders their 11553priority. You can also delete rules and add new ones. If you add a new rule, 11554innotop prompts you for the column, an operator for the comparison, a value 11555against which to compare the column, and a color to assign if the rule matches. 11556There is auto-completion and prompting at each step. 11557 11558The value in the third step needs to be correctly quoted. innotop does not try 11559to quote the value because it doesn't know whether it should treat the value as 11560a string or a number. If you want to compare the column against a string, as 11561for example in the first rule above, you should enter 'Locked' surrounded by 11562quotes. If you get an error message about a bareword, you probably should have 11563quoted something. 11564 11565=head2 EXPRESSIONS 11566 11567Expressions are at the core of how innotop works, and are what enables you to 11568extend innotop as you wish. Recall the table lifecycle explained in 11569L<"TABLES">. Expressions are used in the earliest step, where it extracts 11570values from a data source to form rows. 11571 11572It does this by calling a subroutine for each column, passing it the source data 11573set, a set of current values, and a set of previous values. These are all 11574needed so the subroutine can calculate things like the difference between this 11575tick and the previous tick. 11576 11577The subroutines that extract the data from the set are compiled from 11578expressions. This gives significantly more power than just naming the values to 11579fill the columns, because it allows the column's value to be calculated from 11580whatever data is necessary, but avoids the need to write complicated and lengthy 11581Perl code. 11582 11583innotop begins with a string of text that can look as simple as a value's name 11584or as complicated as a full-fledged Perl expression. It looks at each 11585'bareword' token in the string and decides whether it's supposed to be a key 11586into the $set hash. A bareword is an unquoted value that isn't already 11587surrounded by code-ish things like dollar signs or curly brackets. If innotop 11588decides that the bareword isn't a function or other valid Perl code, it converts 11589it into a hash access. After the whole string is processed, innotop compiles a 11590subroutine, like this: 11591 11592 sub compute_column_value { 11593 my ( $set, $cur, $pre ) = @_; 11594 my $val = # EXPANDED STRING GOES HERE 11595 return $val; 11596 } 11597 11598Here's a concrete example, taken from the header table L<"q_header"> in L<"Q: 11599Query List"> mode. This expression calculates the qps, or Queries Per Second, 11600column's values, from the values returned by SHOW STATUS: 11601 11602 Queries/Uptime_hires 11603 11604innotop decides both words are barewords, and transforms this expression into 11605the following Perl code: 11606 11607 $set->{Queries}/$set->{Uptime_hires} 11608 11609When surrounded by the rest of the subroutine's code, this is executable Perl 11610that calculates a high-resolution queries-per-second value. 11611 11612The arguments to the subroutine are named $set, $cur, and $pre. In most cases, 11613$set and $cur will be the same values. However, if L<"status_inc"> is set, $cur 11614will not be the same as $set, because $set will already contain values that are 11615the incremental difference between $cur and $pre. 11616 11617Every column in innotop is computed by subroutines compiled in the same fashion. 11618There is no difference between innotop's built-in columns and user-defined 11619columns. This keeps things consistent and predictable. 11620 11621=head2 TRANSFORMATIONS 11622 11623Transformations change how a value is rendered. For example, they can take a 11624number of seconds and display it in H:M:S format. The following transformations 11625are defined: 11626 11627=over 11628 11629=item commify 11630 11631Adds commas to large numbers every three decimal places. 11632 11633=item distill 11634 11635Distills SQL into verb-noun-noun format for quick comprehension. 11636 11637=item dulint_to_int 11638 11639Accepts two unsigned integers and converts them into a single longlong. This is 11640useful for certain operations with InnoDB, which uses two integers as 11641transaction identifiers, for example. 11642 11643=item fuzzy_time 11644 11645Converts a number of seconds into a friendly, readable value like "1h35m". 11646 11647=item no_ctrl_char 11648 11649Removes quoted control characters from the value. This is affected by the 11650L<"charset"> configuration variable. 11651 11652This transformation only operates within quoted strings, for example, values to 11653a SET clause in an UPDATE statement. It will not alter the UPDATE statement, 11654but will collapse the quoted string to [BINARY] or [TEXT], depending on the 11655charset. 11656 11657=item percent 11658 11659Converts a number to a percentage by multiplying it by two, formatting it with 11660L<"num_digits"> digits after the decimal point, and optionally adding a percent 11661sign (see L<"show_percent">). 11662 11663=item secs_to_time 11664 11665Formats a number of seconds as time in days+hours:minutes:seconds format. 11666 11667=item set_precision 11668 11669Formats numbers with L<"num_digits"> number of digits after the decimal point. 11670 11671=item shorten 11672 11673Formats a number as a unit of 1024 (k/M/G/T) and with L<"num_digits"> number of 11674digits after the decimal point. 11675 11676=back 11677 11678=head2 TABLE EDITOR 11679 11680The innotop table editor lets you customize tables with keystrokes. You start 11681the table editor with the '^' key. If there's more than one table on the 11682screen, it will prompt you to choose one of them. Once you do, innotop will 11683show you something like this: 11684 11685 Editing table definition for Buffer Pool. Press ? for help, q to quit. 11686 11687 name hdr label src 11688 cxn CXN Connection from which cxn 11689 buf_pool_size Size Buffer pool size IB_bp_buf_poo 11690 buf_free Free Bufs Buffers free in the b IB_bp_buf_fre 11691 pages_total Pages Pages total IB_bp_pages_t 11692 pages_modified Dirty Pages Pages modified (dirty IB_bp_pages_m 11693 buf_pool_hit_rate Hit Rate Buffer pool hit rate IB_bp_buf_poo 11694 total_mem_alloc Memory Total memory allocate IB_bp_total_m 11695 add_pool_alloc Add'l Pool Additional pool alloc IB_bp_add_poo 11696 11697The first line shows which table you're editing, and reminds you again to press 11698'?' for a list of key mappings. The rest is a tabular representation of the 11699table's columns, because that's likely what you're trying to edit. However, you 11700can edit more than just the table's columns; this screen can start the filter 11701editor, color rule editor, and more. 11702 11703Each row in the display shows a single column in the table you're editing, along 11704with a couple of its properties such as its header and source expression (see 11705L<"EXPRESSIONS">). 11706 11707The key mappings are Vim-style, as in many other places. Pressing 'j' and 'k' 11708moves the highlight up or down. You can then (d)elete or (e)dit the highlighted 11709column. You can also (a)dd a column to the table. This actually just activates 11710one of the columns already defined for the table; it prompts you to choose from 11711among the columns available but not currently displayed. Finally, you can 11712re-order the columns with the '+' and '-' keys. 11713 11714You can do more than just edit the columns with the table editor, you can also 11715edit other properties, such as the table's sort expression and group-by 11716expression. Press '?' to see the full list, of course. 11717 11718If you want to really customize and create your own column, as opposed to just 11719activating a built-in one that's not currently displayed, press the (n)ew key, 11720and innotop will prompt you for the information it needs: 11721 11722=over 11723 11724=item * 11725 11726The column name: this needs to be a word without any funny characters, e.g. just 11727letters, numbers and underscores. 11728 11729=item * 11730 11731The column header: this is the label that appears at the top of the column, in 11732the table header. This can have spaces and funny characters, but be careful not 11733to make it too wide and waste space on-screen. 11734 11735=item * 11736 11737The column's data source: this is an expression that determines what data from 11738the source (see L<"TABLES">) innotop will put into the column. This can just be 11739the name of an item in the source, or it can be a more complex expression, as 11740described in L<"EXPRESSIONS">. 11741 11742=back 11743 11744Once you've entered the required data, your table has a new column. There is no 11745difference between this column and the built-in ones; it can have all the same 11746properties and behaviors. innotop will write the column's definition to the 11747configuration file, so it will persist across sessions. 11748 11749Here's an example: suppose you want to track how many times your slaves have 11750retried transactions. According to the MySQL manual, the 11751Slave_retried_transactions status variable gives you that data: "The total 11752number of times since startup that the replication slave SQL thread has retried 11753transactions. This variable was added in version 5.0.4." This is appropriate to 11754add to the L<"slave_sql_status"> table. 11755 11756To add the column, switch to the replication-monitoring mode with the 'M' key, 11757and press the '^' key to start the table editor. When prompted, choose 11758slave_sql_status as the table, then press 'n' to create the column. Type 11759'retries' as the column name, 'Retries' as the column header, and 11760'Slave_retried_transactions' as the source. Now the column is created, and you 11761see the table editor screen again. Press 'q' to exit the table editor, and 11762you'll see your column at the end of the table. 11763 11764=head1 VARIABLE SETS 11765 11766Variable sets are used in L<"S: Variables & Status"> mode to define more easily 11767what variables you want to monitor. Behind the scenes they are compiled to a 11768list of expressions, and then into a column list so they can be treated just 11769like columns in any other table, in terms of data extraction and 11770transformations. However, you're protected from the tedious details by a syntax 11771that ought to feel very natural to you: a SQL SELECT list. 11772 11773The data source for variable sets, and indeed the entire S mode, is the 11774combination of SHOW STATUS, SHOW VARIABLES, and SHOW INNODB STATUS. Imagine 11775that you had a huge table with one column per variable returned from those 11776statements. That's the data source for variable sets. You can now query this 11777data source just like you'd expect. For example: 11778 11779 Queries, Uptime, Queries/Uptime as QPS 11780 11781Behind the scenes innotop will split that variable set into three expressions, 11782compile them and turn them into a table definition, then extract as usual. This 11783becomes a "variable set," or a "list of variables you want to monitor." 11784 11785innotop lets you name and save your variable sets, and writes them to the 11786configuration file. You can choose which variable set you want to see with the 11787'c' key, or activate the next and previous sets with the '>' and '<' keys. 11788There are many built-in variable sets as well, which should give you a good 11789start for creating your own. Press 'e' to edit the current variable set, or 11790just to see how it's defined. To create a new one, just press 'c' and type its 11791name. 11792 11793You may want to use some of the functions listed in L<"TRANSFORMATIONS"> to help 11794format the results. In particular, L<"set_precision"> is often useful to limit 11795the number of digits you see. Extending the above example, here's how: 11796 11797 Queries, Uptime, set_precision(Queries/Uptime) as QPS 11798 11799Actually, this still needs a little more work. If your L<"interval"> is less 11800than one second, you might be dividing by zero because Uptime is incremental in 11801this mode by default. Instead, use Uptime_hires: 11802 11803 Queries, Uptime, set_precision(Queries/Uptime_hires) as QPS 11804 11805This example is simple, but it shows how easy it is to choose which variables 11806you want to monitor. 11807 11808=head1 PLUGINS 11809 11810innotop has a simple but powerful plugin mechanism by which you can extend 11811or modify its existing functionality, and add new functionality. innotop's 11812plugin functionality is event-based: plugins register themselves to be called 11813when events happen. They then have a chance to influence the event. 11814 11815An innotop plugin is a Perl module (.pm) file placed in innotop's L<"plugin_dir"> 11816directory. On UNIX systems, you can place a symbolic link to the module instead 11817of putting the actual file there. innotop automatically discovers files named C<*.pm>. If 11818there is a corresponding entry in the L<"plugins"> configuration file section, 11819innotop loads and activates the plugin. 11820 11821The module must conform to innotop's plugin interface. Additionally, the source 11822code of the module must be written in such a way that innotop can inspect the 11823file and determine the package name and description. 11824 11825=head2 Package Source Convention 11826 11827innotop inspects the plugin module's source to determine the Perl package name. 11828It looks for a line of the form "package Foo;" and if found, considers the 11829plugin's package name to be Foo. Of course the package name can be a valid Perl 11830package name such as Foo::Bar, with double colons (::) and so on. 11831 11832It also looks for a description in the source code, to make the plugin editor 11833more human-friendly. The description is a comment line of the form "# 11834description: Foo", where "Foo" is the text innotop will consider to be the 11835plugin's description. 11836 11837=head2 Plugin Interface 11838 11839The innotop plugin interface is quite simple: innotop expects the plugin to be 11840an object-oriented module it can call certain methods on. The methods are 11841 11842=over 11843 11844=item new(%variables) 11845 11846This is the plugin's constructor. It is passed a hash of innotop's variables, 11847which it can manipulate (see L<"Plugin Variables">). It must return a reference 11848to the newly created plugin object. 11849 11850At construction time, innotop has only loaded the general configuration and 11851created the default built-in variables with their default contents (which is 11852quite a lot). Therefore, the state of the program is exactly as in the innotop 11853source code, plus the configuration variables from the L<"general"> section in 11854the config file. 11855 11856If your plugin manipulates the variables, it is changing global data, which is 11857shared by innotop and all plugins. Plugins are loaded in the order they're 11858listed in the config file. Your plugin may load before or after another plugin, 11859so there is a potential for conflict or interaction between plugins if they 11860modify data other plugins use or modify. 11861 11862=item register_for_events() 11863 11864This method must return a list of events in which the plugin is interested, if 11865any. See L<"Plugin Events"> for the defined events. If the plugin returns an 11866event that's not defined, the event is ignored. 11867 11868=item event handlers 11869 11870The plugin must implement a method named the same as each event for which it has 11871registered. In other words, if the plugin returns qw(foo bar) from 11872register_for_events(), it must have foo() and bar() methods. These methods are 11873callbacks for the events. See L<"Plugin Events"> for more details about each 11874event. 11875 11876=back 11877 11878=head2 Plugin Variables 11879 11880The plugin's constructor is passed a hash of innotop's variables, which it can 11881manipulate. It is probably a good idea if the plugin object saves a copy of it 11882for later use. The variables are defined in the innotop variable 11883%pluggable_vars, and are as follows: 11884 11885=over 11886 11887=item action_for 11888 11889A hashref of key mappings. These are innotop's global hot-keys. 11890 11891=item agg_funcs 11892 11893A hashref of functions that can be used for grouping. See L<"GROUPING">. 11894 11895=item config 11896 11897The global configuration hash. 11898 11899=item connections 11900 11901A hashref of connection specifications. These are just specifications of how to 11902connect to a server. 11903 11904=item dbhs 11905 11906A hashref of innotop's database connections. These are actual DBI connection 11907objects. 11908 11909=item filters 11910 11911A hashref of filters applied to table rows. See L<"FILTERS"> for more. 11912 11913=item modes 11914 11915A hashref of modes. See L<"MODES"> for more. 11916 11917=item server_groups 11918 11919A hashref of server groups. See L<"SERVER GROUPS">. 11920 11921=item tbl_meta 11922 11923A hashref of innotop's table meta-data, with one entry per table (see 11924L<"TABLES"> for more information). 11925 11926=item trans_funcs 11927 11928A hashref of transformation functions. See L<"TRANSFORMATIONS">. 11929 11930=item var_sets 11931 11932A hashref of variable sets. See L<"VARIABLE SETS">. 11933 11934=back 11935 11936=head2 Plugin Events 11937 11938Each event is defined somewhere in the innotop source code. When innotop runs 11939that code, it executes the callback function for each plugin that expressed its 11940interest in the event. innotop passes some data for each event. The events are 11941defined in the %event_listener_for variable, and are as follows: 11942 11943=over 11944 11945=item extract_values($set, $cur, $pre, $tbl) 11946 11947This event occurs inside the function that extracts values from a data source. 11948The arguments are the set of values, the current values, the previous values, 11949and the table name. 11950 11951=item set_to_tbl 11952 11953Events are defined at many places in this subroutine, which is responsible for 11954turning an arrayref of hashrefs into an arrayref of lines that can be printed to 11955the screen. The events all pass the same data: an arrayref of rows and the name 11956of the table being created. The events are set_to_tbl_pre_filter, 11957set_to_tbl_pre_sort,set_to_tbl_pre_group, set_to_tbl_pre_colorize, 11958set_to_tbl_pre_transform, set_to_tbl_pre_pivot, set_to_tbl_pre_create, 11959set_to_tbl_post_create. 11960 11961=item draw_screen($lines) 11962 11963This event occurs inside the subroutine that prints the lines to the screen. 11964$lines is an arrayref of strings. 11965 11966=back 11967 11968=head2 Simple Plugin Example 11969 11970The easiest way to explain the plugin functionality is probably with a simple 11971example. The following module adds a column to the beginning of every table and 11972sets its value to 1. (If you copy and paste this example code, be sure to remove 11973the first space from each line; lines such as '# description' must not start with 11974whitespace). 11975 11976 use strict; 11977 use warnings FATAL => 'all'; 11978 11979 package Innotop::Plugin::Example; 11980 # description: Adds an 'example' column to every table 11981 11982 sub new { 11983 my ( $class, %vars ) = @_; 11984 # Store reference to innotop's variables in $self 11985 my $self = bless { %vars }, $class; 11986 11987 # Design the example column 11988 my $col = { 11989 hdr => 'Example', 11990 just => '', 11991 dec => 0, 11992 num => 1, 11993 label => 'Example', 11994 src => 'example', # Get data from this column in the data source 11995 tbl => '', 11996 trans => [], 11997 }; 11998 11999 # Add the column to every table. 12000 my $tbl_meta = $vars{tbl_meta}; 12001 foreach my $tbl ( values %$tbl_meta ) { 12002 # Add the column to the list of defined columns 12003 $tbl->{cols}->{example} = $col; 12004 # Add the column to the list of visible columns 12005 unshift @{$tbl->{visible}}, 'example'; 12006 } 12007 12008 # Be sure to return a reference to the object. 12009 return $self; 12010 } 12011 12012 # I'd like to be called when a data set is being rendered into a table, please. 12013 sub register_for_events { 12014 my ( $self ) = @_; 12015 return qw(set_to_tbl_pre_filter); 12016 } 12017 12018 # This method will be called when the event fires. 12019 sub set_to_tbl_pre_filter { 12020 my ( $self, $rows, $tbl ) = @_; 12021 # Set the example column's data source to the value 1. 12022 foreach my $row ( @$rows ) { 12023 $row->{example} = 1; 12024 } 12025 } 12026 12027 1; 12028 12029=head2 Plugin Editor 12030 12031The plugin editor lets you view the plugins innotop discovered and activate or 12032deactivate them. Start the editor by pressing $ to start the configuration 12033editor from any mode. Press the 'p' key to start the plugin editor. You'll see 12034a list of plugins innotop discovered. You can use the 'j' and 'k' keys to move 12035the highlight to the desired one, then press the * key to toggle it active or 12036inactive. Exit the editor and restart innotop for the changes to take effect. 12037 12038=head1 SQL STATEMENTS 12039 12040innotop uses a limited set of SQL statements to retrieve data from MySQL for 12041display. The statements are customized depending on the server version against 12042which they are executed; for example, on MySQL 5 and newer, INNODB_STATUS 12043executes "SHOW ENGINE INNODB STATUS", while on earlier versions it executes 12044"SHOW INNODB STATUS". The statements are as follows: 12045 12046 Statement SQL executed 12047 =================== =============================== 12048 INDEX_STATISTICS SELECT * FROM INFORMATION_SCHEMA.INDEX_STATISTICS 12049 INNODB_STATUS SHOW [ENGINE] INNODB STATUS 12050 KILL_CONNECTION KILL 12051 KILL_QUERY KILL QUERY 12052 OPEN_TABLES SHOW OPEN TABLES 12053 PROCESSLIST SHOW FULL PROCESSLIST 12054 SHOW_MASTER_LOGS SHOW MASTER LOGS 12055 SHOW_MASTER_STATUS SHOW MASTER STATUS 12056 SHOW_SLAVE_STATUS SHOW SLAVE STATUS 12057 SHOW_STATUS SHOW [GLOBAL] STATUS 12058 SHOW_VARIABLES SHOW [GLOBAL] VARIABLES 12059 TABLE_STATISTICS SELECT * FROM INFORMATION_SCHEMA.TABLE_STATISTICS 12060 12061=head1 DATA SOURCES 12062 12063Each time innotop extracts values to create a table (see L<"EXPRESSIONS"> and 12064L<"TABLES">), it does so from a particular data source. Largely because of the 12065complex data extracted from SHOW INNODB STATUS, this is slightly messy. SHOW 12066INNODB STATUS contains a mixture of single values and repeated values that form 12067nested data sets. 12068 12069Whenever innotop fetches data from MySQL, it adds two extra bits to each set: 12070cxn and Uptime_hires. cxn is the name of the connection from which the data 12071came. Uptime_hires is a high-resolution version of the server's Uptime status 12072variable, which is important if your L<"interval"> setting is sub-second. 12073 12074Here are the kinds of data sources from which data is extracted: 12075 12076=over 12077 12078=item STATUS_VARIABLES 12079 12080This is the broadest category, into which the most kinds of data fall. It 12081begins with the combination of SHOW STATUS and SHOW VARIABLES, but other sources 12082may be included as needed, for example, SHOW MASTER STATUS and SHOW SLAVE 12083STATUS, as well as many of the non-repeated values from SHOW INNODB STATUS. 12084 12085=item DEADLOCK_LOCKS 12086 12087This data is extracted from the transaction list in the LATEST DETECTED DEADLOCK 12088section of SHOW INNODB STATUS. It is nested two levels deep: transactions, then 12089locks. 12090 12091=item DEADLOCK_TRANSACTIONS 12092 12093This data is from the transaction list in the LATEST DETECTED DEADLOCK 12094section of SHOW INNODB STATUS. It is nested one level deep. 12095 12096=item EXPLAIN 12097 12098This data is from the result set returned by EXPLAIN. 12099 12100=item INNODB_BLOCKED_BLOCKER 12101 12102This data is from the INFORMATION_SCHEMA tables related to InnoDB locks and 12103the processlist. 12104 12105=item INNODB_TRANSACTIONS 12106 12107This data is from the TRANSACTIONS section of SHOW INNODB STATUS. 12108 12109=item IO_THREADS 12110 12111This data is from the list of threads in the the FILE I/O section of SHOW INNODB 12112STATUS. 12113 12114=item INNODB_LOCKS 12115 12116This data is from the TRANSACTIONS section of SHOW INNODB STATUS and is nested 12117two levels deep. 12118 12119=item MASTER_SLAVE 12120 12121This data is from the combination of SHOW MASTER STATUS and SHOW SLAVE STATUS. 12122 12123=item OPEN_TABLES 12124 12125This data is from SHOW OPEN TABLES. 12126 12127=item PROCESSLIST 12128 12129This data is from SHOW FULL PROCESSLIST. 12130 12131=item PROCESSLIST_STATS 12132 12133This data is from SHOW FULL PROCESSLIST and computes stats such as the maximum time 12134a user query has been running, and how many user queries are running. A "user 12135query" excludes replication threads. 12136 12137=item OS_WAIT_ARRAY 12138 12139This data is from the SEMAPHORES section of SHOW INNODB STATUS and is nested one 12140level deep. It comes from the lines that look like this: 12141 12142 --Thread 1568861104 has waited at btr0cur.c line 424 .... 12143 12144=back 12145 12146=head1 MYSQL PRIVILEGES 12147 12148=over 12149 12150=item * 12151 12152You must connect to MySQL as a user who has the SUPER privilege for many of the 12153functions. 12154 12155=item * 12156 12157If you don't have the SUPER privilege, you can still run some functions, but you 12158won't necessarily see all the same data. 12159 12160=item * 12161 12162You need the PROCESS privilege to see the list of currently running queries in Q 12163mode. 12164 12165=item * 12166 12167You need special privileges to start and stop slave servers. 12168 12169=item * 12170 12171You need appropriate privileges to create and drop the deadlock tables if needed 12172(see L<"SERVER CONNECTIONS">). 12173 12174=back 12175 12176=head1 SYSTEM REQUIREMENTS 12177 12178You need Perl to run innotop, of course. You also need a few Perl modules: DBI, 12179DBD::mysql, Term::ReadKey, and Time::HiRes. These should be included with most 12180Perl distributions, but in case they are not, I recommend using versions 12181distributed with your operating system or Perl distribution, not from CPAN. 12182Term::ReadKey in particular has been known to cause problems if installed from 12183CPAN. 12184 12185If you have Term::ANSIColor, innotop will use it to format headers more readably 12186and compactly. (Under Microsoft Windows, you also need Win32::Console::ANSI for 12187terminal formatting codes to be honored). If you install Term::ReadLine, 12188preferably Term::ReadLine::Gnu, you'll get nice auto-completion support. 12189 12190I run innotop on Gentoo GNU/Linux, Debian and Ubuntu, and I've had feedback from 12191people successfully running it on Red Hat, CentOS, Solaris, and Mac OSX. I 12192don't see any reason why it won't work on other UNIX-ish operating systems, but 12193I don't know for sure. It also runs on Windows under ActivePerl without 12194problem. 12195 12196innotop has been used on MySQL versions 3.23.58, 4.0.27, 4.1.0, 4.1.22, 5.0.26, 121975.1.15, and 5.2.3. If it doesn't run correctly for you, that is a bug that 12198should be reported. 12199 12200=head1 FILES 12201 12202$HOMEDIR/.innotop and/or /usr/local/etc are used to store 12203configuration information. Files include the configuration file innotop.conf, 12204the core_dump file which contains verbose error messages if L<"debug"> is 12205enabled, and the plugins/ subdirectory. 12206 12207=head1 GLOSSARY OF TERMS 12208 12209=over 12210 12211=item tick 12212 12213A tick is a refresh event, when innotop re-fetches data from connections and 12214displays it. 12215 12216=back 12217 12218=head1 ACKNOWLEDGEMENTS 12219 12220The following people and organizations are acknowledged for various reasons. 12221Hopefully no one has been forgotten. 12222 12223Aaron Racine, 12224Allen K. Smith, 12225Aurimas Mikalauskas, 12226Bartosz Fenski, 12227Brian Miezejewski, 12228Christian Hammers, 12229Cyril Scetbon, 12230Dane Miller, 12231David Multer, 12232Dr. Frank Ullrich, 12233Giuseppe Maxia, 12234Google.com Site Reliability Engineers, 12235Google Code, 12236Jan Pieter Kunst, 12237Jari Aalto, 12238Jay Pipes, 12239Jeremy Zawodny, 12240Johan Idren, 12241Kristian Kohntopp, 12242Lenz Grimmer, 12243Maciej Dobrzanski, 12244Michiel Betel, 12245MySQL AB, 12246Paul McCullagh, 12247Sebastien Estienne, 12248Sourceforge.net, 12249Steven Kreuzer, 12250The Gentoo MySQL Team, 12251Trevor Price, 12252Yaar Schnitman, 12253and probably more people that have not been included. 12254 12255(If your name has been misspelled, it's probably out of fear of putting 12256international characters into this documentation; earlier versions of Perl might 12257not be able to compile it then). 12258 12259=head1 COPYRIGHT, LICENSE AND WARRANTY 12260 12261This program is copyright (c) 2006 Baron Schwartz. 12262Feedback and improvements are welcome. 12263 12264THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED 12265WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF 12266MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. 12267 12268This program is free software; you can redistribute it and/or modify it under 12269the terms of the GNU General Public License as published by the Free Software 12270Foundation, version 2; OR the Perl Artistic License. On UNIX and similar 12271systems, you can issue `man perlgpl' or `man perlartistic' to read these 12272licenses. 12273 12274You should have received a copy of the GNU General Public License along with 12275this program; if not, write to the Free Software Foundation, Inc., 59 Temple 12276Place, Suite 330, Boston, MA 02111-1307 USA. 12277 12278Execute innotop and press '!' to see this information at any time. 12279 12280=head1 AUTHOR 12281 12282Originally written by Baron Schwartz; currently maintained by Aaron Racine. 12283 12284=head1 BUGS 12285 12286You can report bugs, ask for improvements, and get other help and support at 12287L<https://github.com/innotop/innotop>. There are mailing lists, a source code 12288browser, a bug tracker, etc. Please use these instead of contacting the 12289maintainer or author directly, as it makes our job easier and benefits others if the 12290discussions are permanent and public. Of course, if you need to contact us in 12291private, please do. 12292 12293=cut 12294