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