~percona-dev/percona-server/release-5.5.11-20.2-fix-bug-764138

« back to all changes in this revision

Viewing changes to build/debian/additions/innotop/innotop

  • Committer: Ignacio Nin
  • Date: 2011-03-13 17:18:23 UTC
  • mfrom: (33.3.17 release-5.5.8-20)
  • Revision ID: ignacio.nin@percona.com-20110313171823-m06xs104nekulywb
Merge changes from release-5.5.8-20 to 5.5.9

Merge changes from the release branch of 5.5.8 to 5.5.9. These include
the HandlerSocket and UDF directories and the building scripts.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/perl
 
2
 
 
3
# vim: tw=160:nowrap:expandtab:tabstop=3:shiftwidth=3:softtabstop=3
 
4
 
 
5
use strict;
 
6
use warnings FATAL => 'all';
 
7
use sigtrap qw(handler finish untrapped normal-signals);
 
8
 
 
9
use Data::Dumper;
 
10
use DBI;
 
11
use English qw(-no_match_vars);
 
12
use File::Basename qw(dirname);
 
13
use Getopt::Long;
 
14
use List::Util qw(max min maxstr sum);
 
15
use InnoDBParser;
 
16
use POSIX qw(ceil);
 
17
use Time::HiRes qw(time sleep);
 
18
use Term::ReadKey qw(ReadMode ReadKey);
 
19
 
 
20
# Version, license and warranty information. {{{1
 
21
# ###########################################################################
 
22
our $VERSION = '1.6.0';
 
23
our $SVN_REV = sprintf("%d", q$Revision: 383 $ =~ m/(\d+)/g);
 
24
our $SVN_URL = sprintf("%s", q$URL: https://innotop.svn.sourceforge.net/svnroot/innotop/trunk/innotop $ =~ m$svnroot/innotop/(\S+)$g);
 
25
 
 
26
my $innotop_license = <<"LICENSE";
 
27
 
 
28
This is innotop version $VERSION, a MySQL and InnoDB monitor.
 
29
 
 
30
This program is copyright (c) 2006 Baron Schwartz.
 
31
Feedback and improvements are welcome.
 
32
 
 
33
THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
 
34
WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 
35
MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 
36
 
 
37
This program is free software; you can redistribute it and/or modify it under
 
38
the terms of the GNU General Public License as published by the Free Software
 
39
Foundation, version 2; OR the Perl Artistic License.  On UNIX and similar
 
40
systems, you can issue `man perlgpl' or `man perlartistic' to read these
 
41
licenses.
 
42
 
 
43
You should have received a copy of the GNU General Public License along with
 
44
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 
45
Place, Suite 330, Boston, MA  02111-1307  USA.
 
46
LICENSE
 
47
 
 
48
# Configuration information and global setup {{{1
 
49
# ###########################################################################
 
50
 
 
51
# Really, really, super-global variables.
 
52
my @config_versions = (
 
53
   "000-000-000", "001-003-000", # config file was one big name-value hash.
 
54
   "001-003-000", "001-004-002", # config file contained non-user-defined stuff.
 
55
);
 
56
 
 
57
my $clear_screen_sub;
 
58
 
 
59
# This defines expected properties and defaults for the column definitions that
 
60
# eventually end up in tbl_meta.
 
61
my %col_props = (
 
62
   hdr     => '',
 
63
   just    => '-',
 
64
   dec     => 0,     # Whether to align the column on the decimal point
 
65
   num     => 0,
 
66
   label   => '',
 
67
   user    => 0,
 
68
   src     => '',
 
69
   tbl     => '',    # Helps when writing/reading custom columns in config files
 
70
   minw    => 0,
 
71
   maxw    => 0,
 
72
   trans   => [],
 
73
   agg     => 'first',  # Aggregate function
 
74
   aggonly => 0,        # Whether to show only when tbl_meta->{aggregate} is true
 
75
);
 
76
 
 
77
# Actual DBI connections to MySQL servers.
 
78
my %dbhs;
 
79
 
 
80
# Command-line parameters {{{2
 
81
# ###########################################################################
 
82
 
 
83
my @opt_spec = (
 
84
   { s => 'help',       d => 'Show this help message' },
 
85
   { s => 'color|C!',   d => 'Use terminal coloring (default)',   c => 'color' },
 
86
   { s => 'config|c=s', d => 'Config file to read' },
 
87
   { s => 'nonint|n',   d => 'Non-interactive, output tab-separated fields' },
 
88
   { s => 'count=i',    d => 'Number of updates before exiting' },
 
89
   { s => 'delay|d=f',  d => 'Delay between updates in seconds',  c => 'interval' },
 
90
   { s => 'mode|m=s',   d => 'Operating mode to start in',        c => 'mode' },
 
91
   { s => 'inc|i!',     d => 'Measure incremental differences',   c => 'status_inc' },
 
92
   { s => 'version',    d => 'Output version information and exit' },
 
93
);
 
94
 
 
95
# This is the container for the command-line options' values to be stored in
 
96
# after processing.  Initial values are defaults.
 
97
my %opts = (
 
98
   n => !( -t STDIN && -t STDOUT ), # If in/out aren't to terminals, we're interactive
 
99
);
 
100
# Post-process...
 
101
my %opt_seen;
 
102
foreach my $spec ( @opt_spec ) {
 
103
   my ( $long, $short ) = $spec->{s} =~ m/^(\w+)(?:\|([^!+=]*))?/;
 
104
   $spec->{k} = $short || $long;
 
105
   $spec->{l} = $long;
 
106
   $spec->{t} = $short;
 
107
   $spec->{n} = $spec->{s} =~ m/!/;
 
108
   $opts{$spec->{k}} = undef unless defined $opts{$spec->{k}};
 
109
   die "Duplicate option $spec->{k}" if $opt_seen{$spec->{k}}++;
 
110
}
 
111
 
 
112
Getopt::Long::Configure('no_ignore_case', 'bundling');
 
113
GetOptions( map { $_->{s} => \$opts{$_->{k}} } @opt_spec) or $opts{help} = 1;
 
114
 
 
115
if ( $opts{version} ) {
 
116
   print "innotop  Ver $VERSION Changeset $SVN_REV from $SVN_URL\n";
 
117
   exit(0);
 
118
}
 
119
 
 
120
if ( $opts{'help'} ) {
 
121
   print "Usage: innotop <options> <innodb-status-file>\n\n";
 
122
   my $maxw = max(map { length($_->{l}) + ($_->{n} ? 4 : 0)} @opt_spec);
 
123
   foreach my $spec ( sort { $a->{l} cmp $b->{l} } @opt_spec ) {
 
124
      my $long  = $spec->{n} ? "[no]$spec->{l}" : $spec->{l};
 
125
      my $short = $spec->{t} ? "-$spec->{t}" : '';
 
126
      printf("  --%-${maxw}s %-4s %s\n", $long, $short, $spec->{d});
 
127
   }
 
128
   print <<USAGE;
 
129
 
 
130
innotop is a MySQL and InnoDB transaction/status monitor, like 'top' for
 
131
MySQL.  It displays queries, InnoDB transactions, lock waits, deadlocks,
 
132
foreign key errors, open tables, replication status, buffer information,
 
133
row operations, logs, I/O operations, load graph, and more.  You can
 
134
monitor many servers at once with innotop. 
 
135
 
 
136
USAGE
 
137
   exit(1);
 
138
}
 
139
 
 
140
# Meta-data (table definitions etc) {{{2
 
141
# ###########################################################################
 
142
 
 
143
# Expressions {{{3
 
144
# Convenience so I can copy/paste these in several places...
 
145
# ###########################################################################
 
146
my %exprs = (
 
147
   Host              => q{my $host = host || hostname || ''; ($host) = $host =~ m/^((?:[\d.]+(?=:))|(?:[a-zA-Z]\w+))/; return $host || ''},
 
148
   Port              => q{my ($p) = host =~ m/:(.*)$/; return $p || 0},
 
149
   OldVersions       => q{dulint_to_int(IB_tx_trx_id_counter) - dulint_to_int(IB_tx_purge_done_for)},
 
150
   MaxTxnTime        => q/max(map{ $_->{active_secs} } @{ IB_tx_transactions }) || 0/,
 
151
   NumTxns           => q{scalar @{ IB_tx_transactions } },
 
152
   DirtyBufs         => q{ $cur->{IB_bp_pages_modified} / ($cur->{IB_bp_buf_pool_size} || 1) },
 
153
   BufPoolFill       => q{ $cur->{IB_bp_pages_total} / ($cur->{IB_bp_buf_pool_size} || 1) },
 
154
   ServerLoad        => q{ $cur->{Threads_connected}/(Questions||1)/Uptime_hires },
 
155
   TxnTimeRemain     => q{ defined undo_log_entries && defined $pre->{undo_log_entries} && undo_log_entries < $pre->{undo_log_entries} ? undo_log_entries / (($pre->{undo_log_entries} - undo_log_entries)/((active_secs-$pre->{active_secs})||1))||1 : 0},
 
156
   SlaveCatchupRate  => ' defined $cur->{seconds_behind_master} && defined $pre->{seconds_behind_master} && $cur->{seconds_behind_master} < $pre->{seconds_behind_master} ? ($pre->{seconds_behind_master}-$cur->{seconds_behind_master})/($cur->{Uptime_hires}-$pre->{Uptime_hires}) : 0',
 
157
   QcacheHitRatio    => q{(Qcache_hits||0)/(((Com_select||0)+(Qcache_hits||0))||1)},
 
158
);
 
159
 
 
160
# ###########################################################################
 
161
# Column definitions {{{3
 
162
# Defines every column in every table. A named column has the following
 
163
# properties:
 
164
#    * hdr    Column header/title
 
165
#    * label  Documentation for humans.
 
166
#    * num    Whether it's numeric (for sorting).
 
167
#    * just   Alignment; generated from num, user-overridable in tbl_meta
 
168
#    * minw, maxw Auto-generated, user-overridable.
 
169
# Values from this hash are just copied to tbl_meta, which is where everything
 
170
# else in the program should read from.
 
171
# ###########################################################################
 
172
 
 
173
my %columns = (
 
174
   active_secs                 => { hdr => 'SecsActive',          num => 1, label => 'Seconds transaction has been active', },
 
175
   add_pool_alloc              => { hdr => 'Add\'l Pool',         num => 1, label => 'Additonal pool allocated' },
 
176
   attempted_op                => { hdr => 'Action',              num => 0, label => 'The action that caused the error' },
 
177
   awe_mem_alloc               => { hdr => 'AWE Memory',          num => 1, label => '[Windows] AWE memory allocated' },
 
178
   binlog_cache_overflow       => { hdr => 'Binlog Cache',        num => 1, label => 'Transactions too big for binlog cache that went to disk' },
 
179
   binlog_do_db                => { hdr => 'Binlog Do DB',        num => 0, label => 'binlog-do-db setting' },
 
180
   binlog_ignore_db            => { hdr => 'Binlog Ignore DB',    num => 0, label => 'binlog-ignore-db setting' },
 
181
   bps_in                      => { hdr => 'BpsIn',               num => 1, label => 'Bytes per second received by the server', },
 
182
   bps_out                     => { hdr => 'BpsOut',              num => 1, label => 'Bytes per second sent by the server', },
 
183
   buf_free                    => { hdr => 'Free Bufs',           num => 1, label => 'Buffers free in the buffer pool' },
 
184
   buf_pool_hit_rate           => { hdr => 'Hit Rate',            num => 0, label => 'Buffer pool hit rate' },
 
185
   buf_pool_hits               => { hdr => 'Hits',                num => 1, label => 'Buffer pool hits' },
 
186
   buf_pool_reads              => { hdr => 'Reads',               num => 1, label => 'Buffer pool reads' },
 
187
   buf_pool_size               => { hdr => 'Size',                num => 1, label => 'Buffer pool size' },
 
188
   bufs_in_node_heap           => { hdr => 'Node Heap Bufs',      num => 1, label => 'Buffers in buffer pool node heap' },
 
189
   bytes_behind_master         => { hdr => 'ByteLag',             num => 1, label => 'Bytes the slave lags the master in binlog' },
 
190
   cell_event_set              => { hdr => 'Ending?',             num => 1, label => 'Whether the cell event is set' },
 
191
   cell_waiting                => { hdr => 'Waiting?',            num => 1, label => 'Whether the cell is waiting' },
 
192
   child_db                    => { hdr => 'Child DB',            num => 0, label => 'The database of the child table' },
 
193
   child_index                 => { hdr => 'Child Index',         num => 0, label => 'The index in the child table' },
 
194
   child_table                 => { hdr => 'Child Table',         num => 0, label => 'The child table' },
 
195
   cmd                         => { hdr => 'Cmd',                 num => 0, label => 'Type of command being executed', },
 
196
   cnt                         => { hdr => 'Cnt',                 num => 0, label => 'Count', agg => 'count', aggonly => 1 },
 
197
   connect_retry               => { hdr => 'Connect Retry',       num => 1, label => 'Slave connect-retry timeout' },
 
198
   cxn                         => { hdr => 'CXN',                 num => 0, label => 'Connection from which the data came', },
 
199
   db                          => { hdr => 'DB',                  num => 0, label => 'Current database', },
 
200
   dict_mem_alloc              => { hdr => 'Dict Mem',            num => 1, label => 'Dictionary memory allocated' },
 
201
   dirty_bufs                  => { hdr => 'Dirty Buf',           num => 1, label => 'Dirty buffer pool pages' },
 
202
   dl_txn_num                  => { hdr => 'Num',                 num => 0, label => 'Deadlocked transaction number', },
 
203
   event_set                   => { hdr => 'Evt Set?',            num => 1, label => '[Win32] if a wait event is set', },
 
204
   exec_master_log_pos         => { hdr => 'Exec Master Log Pos', num => 1, label => 'Exec Master Log Position' },
 
205
   fk_name                     => { hdr => 'Constraint',          num => 0, label => 'The name of the FK constraint' },
 
206
   free_list_len               => { hdr => 'Free List Len',       num => 1, label => 'Length of the free list' },
 
207
   has_read_view               => { hdr => 'Rd View',             num => 1, label => 'Whether the transaction has a read view' },
 
208
   hash_searches_s             => { hdr => 'Hash/Sec',            num => 1, label => 'Number of hash searches/sec' },
 
209
   hash_table_size             => { hdr => 'Size',                num => 1, label => 'Number of non-hash searches/sec' },
 
210
   heap_no                     => { hdr => 'Heap',                num => 1, label => 'Heap number' },
 
211
   heap_size                   => { hdr => 'Heap',                num => 1, label => 'Heap size' },
 
212
   history_list_len            => { hdr => 'History',             num => 1, label => 'History list length' },
 
213
   host_and_domain             => { hdr => 'Host',                num => 0, label => 'Hostname/IP and domain' },
 
214
   host_and_port               => { hdr => 'Host/IP',             num => 0, label => 'Hostname or IP address, and port number', },
 
215
   hostname                    => { hdr => 'Host',                num => 0, label => 'Hostname' },
 
216
   index                       => { hdr => 'Index',               num => 0, label => 'The index involved' },
 
217
   index_ref                   => { hdr => 'Index Ref',           num => 0, label => 'Index referenced' },
 
218
   info                        => { hdr => 'Query',               num => 0, label => 'Info or the current query', },
 
219
   insert_intention            => { hdr => 'Ins Intent',          num => 1, label => 'Whether the thread was trying to insert' },
 
220
   inserts                     => { hdr => 'Inserts',             num => 1, label => 'Inserts' },
 
221
   io_bytes_s                  => { hdr => 'Bytes/Sec',           num => 1, label => 'Average I/O bytes/sec' },
 
222
   io_flush_type               => { hdr => 'Flush Type',          num => 0, label => 'I/O Flush Type' },
 
223
   io_fsyncs_s                 => { hdr => 'fsyncs/sec',          num => 1, label => 'I/O fsyncs/sec' },
 
224
   io_reads_s                  => { hdr => 'Reads/Sec',           num => 1, label => 'Average I/O reads/sec' },
 
225
   io_writes_s                 => { hdr => 'Writes/Sec',          num => 1, label => 'Average I/O writes/sec' },
 
226
   ip                          => { hdr => 'IP',                  num => 0, label => 'IP address' },
 
227
   is_name_locked              => { hdr => 'Locked',              num => 1, label => 'Whether table is name locked', },
 
228
   key_buffer_hit              => { hdr => 'KCacheHit',           num => 1, label => 'Key cache hit ratio', },
 
229
   key_len                     => { hdr => 'Key Length',          num => 1, label => 'Number of bytes used in the key' },
 
230
   last_chkp                   => { hdr => 'Last Checkpoint',     num => 0, label => 'Last log checkpoint' },
 
231
   last_errno                  => { hdr => 'Last Errno',          num => 1, label => 'Last error number' },
 
232
   last_error                  => { hdr => 'Last Error',          num => 0, label => 'Last error' },
 
233
   last_s_file_name            => { hdr => 'S-File',              num => 0, label => 'Filename where last read locked' },
 
234
   last_s_line                 => { hdr => 'S-Line',              num => 1, label => 'Line where last read locked' },
 
235
   last_x_file_name            => { hdr => 'X-File',              num => 0, label => 'Filename where last write locked' },
 
236
   last_x_line                 => { hdr => 'X-Line',              num => 1, label => 'Line where last write locked' },
 
237
   last_pct                    => { hdr => 'Pct',                 num => 1, label => 'Last Percentage' },
 
238
   last_total                  => { hdr => 'Last Total',          num => 1, label => 'Last Total' },
 
239
   last_value                  => { hdr => 'Last Incr',           num => 1, label => 'Last Value' },
 
240
   load                        => { hdr => 'Load',                num => 1, label => 'Server load' },
 
241
   lock_cfile_name             => { hdr => 'Crtd File',           num => 0, label => 'Filename where lock created' },
 
242
   lock_cline                  => { hdr => 'Crtd Line',           num => 1, label => 'Line where lock created' },
 
243
   lock_mem_addr               => { hdr => 'Addr',                num => 0, label => 'The lock memory address' },
 
244
   lock_mode                   => { hdr => 'Mode',                num => 0, label => 'The lock mode' },
 
245
   lock_structs                => { hdr => 'LStrcts',             num => 1, label => 'Number of lock structs' },
 
246
   lock_type                   => { hdr => 'Type',                num => 0, label => 'The lock type' },
 
247
   lock_var                    => { hdr => 'Lck Var',             num => 1, label => 'The lock variable' },
 
248
   lock_wait_time              => { hdr => 'Wait',                num => 1, label => 'How long txn has waited for a lock' },
 
249
   log_flushed_to              => { hdr => 'Flushed To',          num => 0, label => 'Log position flushed to' },
 
250
   log_ios_done                => { hdr => 'IO Done',             num => 1, label => 'Log I/Os done' },
 
251
   log_ios_s                   => { hdr => 'IO/Sec',              num => 1, label => 'Average log I/Os per sec' },
 
252
   log_seq_no                  => { hdr => 'Sequence No.',        num => 0, label => 'Log sequence number' },
 
253
   main_thread_id              => { hdr => 'Main Thread ID',      num => 1, label => 'Main thread ID' },
 
254
   main_thread_proc_no         => { hdr => 'Main Thread Proc',    num => 1, label => 'Main thread process number' },
 
255
   main_thread_state           => { hdr => 'Main Thread State',   num => 0, label => 'Main thread state' },
 
256
   master_file                 => { hdr => 'File',                num => 0, label => 'Master file' },
 
257
   master_host                 => { hdr => 'Master',              num => 0, label => 'Master server hostname' },
 
258
   master_log_file             => { hdr => 'Master Log File',     num => 0, label => 'Master log file' },
 
259
   master_port                 => { hdr => 'Master Port',         num => 1, label => 'Master port' },
 
260
   master_pos                  => { hdr => 'Position',            num => 1, label => 'Master position' },
 
261
   master_ssl_allowed          => { hdr => 'Master SSL Allowed',  num => 0, label => 'Master SSL Allowed' },
 
262
   master_ssl_ca_file          => { hdr => 'Master SSL CA File',  num => 0, label => 'Master SSL Cert Auth File' },
 
263
   master_ssl_ca_path          => { hdr => 'Master SSL CA Path',  num => 0, label => 'Master SSL Cert Auth Path' },
 
264
   master_ssl_cert             => { hdr => 'Master SSL Cert',     num => 0, label => 'Master SSL Cert' },
 
265
   master_ssl_cipher           => { hdr => 'Master SSL Cipher',   num => 0, label => 'Master SSL Cipher' },
 
266
   master_ssl_key              => { hdr => 'Master SSL Key',      num => 0, label => 'Master SSL Key' },
 
267
   master_user                 => { hdr => 'Master User',         num => 0, label => 'Master username' },
 
268
   max_txn                     => { hdr => 'MaxTxnTime',          num => 1, label => 'MaxTxn' },
 
269
   merged_recs                 => { hdr => 'Merged Recs',         num => 1, label => 'Merged records' },
 
270
   merges                      => { hdr => 'Merges',              num => 1, label => 'Merges' },
 
271
   mutex_os_waits              => { hdr => 'Waits',               num => 1, label => 'Mutex OS Waits' },
 
272
   mutex_spin_rounds           => { hdr => 'Rounds',              num => 1, label => 'Mutex Spin Rounds' },
 
273
   mutex_spin_waits            => { hdr => 'Spins',               num => 1, label => 'Mutex Spin Waits' },
 
274
   mysql_thread_id             => { hdr => 'ID',                  num => 1, label => 'MySQL connection (thread) ID', },
 
275
   name                        => { hdr => 'Name',                num => 0, label => 'Variable Name' },
 
276
   n_bits                      => { hdr => '# Bits',              num => 1, label => 'Number of bits' },
 
277
   non_hash_searches_s         => { hdr => 'Non-Hash/Sec',        num => 1, label => 'Non-hash searches/sec' },
 
278
   num_deletes                 => { hdr => 'Del',                 num => 1, label => 'Number of deletes' },
 
279
   num_deletes_sec             => { hdr => 'Del/Sec',             num => 1, label => 'Number of deletes' },
 
280
   num_inserts                 => { hdr => 'Ins',                 num => 1, label => 'Number of inserts' },
 
281
   num_inserts_sec             => { hdr => 'Ins/Sec',             num => 1, label => 'Number of inserts' },
 
282
   num_readers                 => { hdr => 'Readers',             num => 1, label => 'Number of readers' },
 
283
   num_reads                   => { hdr => 'Read',                num => 1, label => 'Number of reads' },
 
284
   num_reads_sec               => { hdr => 'Read/Sec',            num => 1, label => 'Number of reads' },
 
285
   num_res_ext                 => { hdr => 'BTree Extents',       num => 1, label => 'Number of extents reserved for B-Tree' },
 
286
   num_rows                    => { hdr => 'Row Count',           num => 1, label => 'Number of rows estimated to examine' },
 
287
   num_times_open              => { hdr => 'In Use',              num => 1, label => '# times table is opened', },
 
288
   num_txns                    => { hdr => 'Txns',                num => 1, label => 'Number of transactions' },
 
289
   num_updates                 => { hdr => 'Upd',                 num => 1, label => 'Number of updates' },
 
290
   num_updates_sec             => { hdr => 'Upd/Sec',             num => 1, label => 'Number of updates' },
 
291
   os_file_reads               => { hdr => 'OS Reads',            num => 1, label => 'OS file reads' },
 
292
   os_file_writes              => { hdr => 'OS Writes',           num => 1, label => 'OS file writes' },
 
293
   os_fsyncs                   => { hdr => 'OS fsyncs',           num => 1, label => 'OS fsyncs' },
 
294
   os_thread_id                => { hdr => 'OS Thread',           num => 1, label => 'The operating system thread ID' },
 
295
   p_aio_writes                => { hdr => 'Async Wrt',           num => 1, label => 'Pending asynchronous I/O writes' },
 
296
   p_buf_pool_flushes          => { hdr => 'Buffer Pool Flushes', num => 1, label => 'Pending buffer pool flushes' },
 
297
   p_ibuf_aio_reads            => { hdr => 'IBuf Async Rds',      num => 1, label => 'Pending insert buffer asynch I/O reads' },
 
298
   p_log_flushes               => { hdr => 'Log Flushes',         num => 1, label => 'Pending log flushes' },
 
299
   p_log_ios                   => { hdr => 'Log I/Os',            num => 1, label => 'Pending log I/O operations' },
 
300
   p_normal_aio_reads          => { hdr => 'Async Rds',           num => 1, label => 'Pending asynchronous I/O reads' },
 
301
   p_preads                    => { hdr => 'preads',              num => 1, label => 'Pending p-reads' },
 
302
   p_pwrites                   => { hdr => 'pwrites',             num => 1, label => 'Pending p-writes' },
 
303
   p_sync_ios                  => { hdr => 'Sync I/Os',           num => 1, label => 'Pending synchronous I/O operations' },
 
304
   page_creates_sec            => { hdr => 'Creates/Sec',         num => 1, label => 'Page creates/sec' },
 
305
   page_no                     => { hdr => 'Page',                num => 1, label => 'Page number' },
 
306
   page_reads_sec              => { hdr => 'Reads/Sec',           num => 1, label => 'Page reads per second' },
 
307
   page_writes_sec             => { hdr => 'Writes/Sec',          num => 1, label => 'Page writes per second' },
 
308
   pages_created               => { hdr => 'Created',             num => 1, label => 'Pages created' },
 
309
   pages_modified              => { hdr => 'Dirty Pages',         num => 1, label => 'Pages modified (dirty)' },
 
310
   pages_read                  => { hdr => 'Reads',               num => 1, label => 'Pages read' },
 
311
   pages_total                 => { hdr => 'Pages',               num => 1, label => 'Pages total' },
 
312
   pages_written               => { hdr => 'Writes',              num => 1, label => 'Pages written' },
 
313
   parent_col                  => { hdr => 'Parent Column',       num => 0, label => 'The referred column in the parent table', },
 
314
   parent_db                   => { hdr => 'Parent DB',           num => 0, label => 'The database of the parent table' },
 
315
   parent_index                => { hdr => 'Parent Index',        num => 0, label => 'The referred index in the parent table' },
 
316
   parent_table                => { hdr => 'Parent Table',        num => 0, label => 'The parent table' },
 
317
   part_id                     => { hdr => 'Part ID',             num => 1, label => 'Sub-part ID of the query' },
 
318
   partitions                  => { hdr => 'Partitions',          num => 0, label => 'Query partitions used' },
 
319
   pct                         => { hdr => 'Pct',                 num => 1, label => 'Percentage' },
 
320
   pending_chkp_writes         => { hdr => 'Chkpt Writes',        num => 1, label => 'Pending log checkpoint writes' },
 
321
   pending_log_writes          => { hdr => 'Log Writes',          num => 1, label => 'Pending log writes' },
 
322
   port                        => { hdr => 'Port',                num => 1, label => 'Client port number', },
 
323
   possible_keys               => { hdr => 'Poss. Keys',          num => 0, label => 'Possible keys' },
 
324
   proc_no                     => { hdr => 'Proc',                num => 1, label => 'Process number' },
 
325
   q_cache_hit                 => { hdr => 'QCacheHit',           num => 1, label => 'Query cache hit ratio', },
 
326
   qps                         => { hdr => 'QPS',                 num => 1, label => 'How many queries/sec', },
 
327
   queries_in_queue            => { hdr => 'Queries Queued',      num => 1, label => 'Queries in queue' },
 
328
   queries_inside              => { hdr => 'Queries Inside',      num => 1, label => 'Queries inside InnoDB' },
 
329
   query_id                    => { hdr => 'Query ID',            num => 1, label => 'Query ID' },
 
330
   query_status                => { hdr => 'Query Status',        num => 0, label => 'The query status' },
 
331
   query_text                  => { hdr => 'Query Text',          num => 0, label => 'The query text' },
 
332
   questions                   => { hdr => 'Questions',           num => 1, label => 'How many queries the server has gotten', },
 
333
   read_master_log_pos         => { hdr => 'Read Master Pos',     num => 1, label => 'Read master log position' },
 
334
   read_views_open             => { hdr => 'Rd Views',            num => 1, label => 'Number of read views open' },
 
335
   reads_pending               => { hdr => 'Pending Reads',       num => 1, label => 'Reads pending' },
 
336
   relay_log_file              => { hdr => 'Relay File',          num => 0, label => 'Relay log file' },
 
337
   relay_log_pos               => { hdr => 'Relay Pos',           num => 1, label => 'Relay log position' },
 
338
   relay_log_size              => { hdr => 'Relay Size',          num => 1, label => 'Relay log size' },
 
339
   relay_master_log_file       => { hdr => 'Relay Master File',   num => 0, label => 'Relay master log file' },
 
340
   replicate_do_db             => { hdr => 'Do DB',               num => 0, label => 'Replicate-do-db setting' },
 
341
   replicate_do_table          => { hdr => 'Do Table',            num => 0, label => 'Replicate-do-table setting' },
 
342
   replicate_ignore_db         => { hdr => 'Ignore DB',           num => 0, label => 'Replicate-ignore-db setting' },
 
343
   replicate_ignore_table      => { hdr => 'Ignore Table',        num => 0, label => 'Replicate-do-table setting' },
 
344
   replicate_wild_do_table     => { hdr => 'Wild Do Table',       num => 0, label => 'Replicate-wild-do-table setting' },
 
345
   replicate_wild_ignore_table => { hdr => 'Wild Ignore Table',   num => 0, label => 'Replicate-wild-ignore-table setting' },
 
346
   request_type                => { hdr => 'Type',                num => 0, label => 'Type of lock the thread waits for' },
 
347
   reservation_count           => { hdr => 'ResCnt',              num => 1, label => 'Reservation Count' },
 
348
   row_locks                   => { hdr => 'RLocks',              num => 1, label => 'Number of row locks' },
 
349
   rw_excl_os_waits            => { hdr => 'RW Waits',            num => 1, label => 'R/W Excl. OS Waits' },
 
350
   rw_excl_spins               => { hdr => 'RW Spins',            num => 1, label => 'R/W Excl. Spins' },
 
351
   rw_shared_os_waits          => { hdr => 'Sh Waits',            num => 1, label => 'R/W Shared OS Waits' },
 
352
   rw_shared_spins             => { hdr => 'Sh Spins',            num => 1, label => 'R/W Shared Spins' },
 
353
   scan_type                   => { hdr => 'Type',                num => 0, label => 'Scan type in chosen' },
 
354
   seg_size                    => { hdr => 'Seg. Size',           num => 1, label => 'Segment size' },
 
355
   select_type                 => { hdr => 'Select Type',         num => 0, label => 'Type of select used' },
 
356
   signal_count                => { hdr => 'Signals',             num => 1, label => 'Signal Count' },
 
357
   size                        => { hdr => 'Size',                num => 1, label => 'Size of the tablespace' },
 
358
   skip_counter                => { hdr => 'Skip Counter',        num => 1, label => 'Skip counter' },
 
359
   slave_catchup_rate          => { hdr => 'Catchup',             num => 1, label => 'How fast the slave is catching up in the binlog' },
 
360
   slave_io_running            => { hdr => 'Slave-IO',            num => 0, label => 'Whether the slave I/O thread is running' },
 
361
   slave_io_state              => { hdr => 'Slave IO State',      num => 0, label => 'Slave I/O thread state' },
 
362
   slave_open_temp_tables      => { hdr => 'Temp',                num => 1, label => 'Slave open temp tables' },
 
363
   slave_sql_running           => { hdr => 'Slave-SQL',           num => 0, label => 'Whether the slave SQL thread is running' },
 
364
   slow                        => { hdr => 'Slow',                num => 1, label => 'How many slow queries', },
 
365
   space_id                    => { hdr => 'Space',               num => 1, label => 'Tablespace ID' },
 
366
   special                     => { hdr => 'Special',             num => 0, label => 'Special/Other info' },
 
367
   state                       => { hdr => 'State',               num => 0, label => 'Connection state', maxw => 18, },
 
368
   tables_in_use               => { hdr => 'Tbl Used',            num => 1, label => 'Number of tables in use' },
 
369
   tables_locked               => { hdr => 'Tbl Lck',             num => 1, label => 'Number of tables locked' },
 
370
   tbl                         => { hdr => 'Table',               num => 0, label => 'Table', },
 
371
   thread                      => { hdr => 'Thread',              num => 1, label => 'Thread number' },
 
372
   thread_decl_inside          => { hdr => 'Thread Inside',       num => 0, label => 'What the thread is declared inside' },
 
373
   thread_purpose              => { hdr => 'Purpose',             num => 0, label => "The thread's purpose" },
 
374
   thread_status               => { hdr => 'Thread Status',       num => 0, label => 'The thread status' },
 
375
   time                        => { hdr => 'Time',                num => 1, label => 'Time since the last event', },
 
376
   time_behind_master          => { hdr => 'TimeLag',             num => 1, label => 'Time slave lags master' },
 
377
   timestring                  => { hdr => 'Timestring',          num => 0, label => 'Time the event occurred' },
 
378
   total                       => { hdr => 'Total',               num => 1, label => 'Total' },
 
379
   total_mem_alloc             => { hdr => 'Memory',              num => 1, label => 'Total memory allocated' },
 
380
   truncates                   => { hdr => 'Trunc',               num => 0, label => 'Whether the deadlock is truncating InnoDB status' },
 
381
   txn_doesnt_see_ge           => { hdr => "Txn Won't See",       num => 0, label => 'Where txn read view is limited' },
 
382
   txn_id                      => { hdr => 'ID',                  num => 0, label => 'Transaction ID' },
 
383
   txn_sees_lt                 => { hdr => 'Txn Sees',            num => 1, label => 'Where txn read view is limited' },
 
384
   txn_status                  => { hdr => 'Txn Status',          num => 0, label => 'Transaction status' },
 
385
   txn_time_remain             => { hdr => 'Remaining',           num => 1, label => 'Time until txn rollback/commit completes' },
 
386
   undo_log_entries            => { hdr => 'Undo',                num => 1, label => 'Number of undo log entries' },
 
387
   undo_for                    => { hdr => 'Undo',                num => 0, label => 'Undo for' },
 
388
   until_condition             => { hdr => 'Until Condition',     num => 0, label => 'Slave until condition' },
 
389
   until_log_file              => { hdr => 'Until Log File',      num => 0, label => 'Slave until log file' },
 
390
   until_log_pos               => { hdr => 'Until Log Pos',       num => 1, label => 'Slave until log position' },
 
391
   used_cells                  => { hdr => 'Cells Used',          num => 1, label => 'Number of cells used' },
 
392
   used_bufs                   => { hdr => 'Used Bufs',           num => 1, label => 'Number of buffer pool pages used' },
 
393
   user                        => { hdr => 'User',                num => 0, label => 'Database username', },
 
394
   value                       => { hdr => 'Value',               num => 1, label => 'Value' },
 
395
   versions                    => { hdr => 'Versions',            num => 1, label => 'Number of InnoDB MVCC versions unpurged' },
 
396
   victim                      => { hdr => 'Victim',              num => 0, label => 'Whether this txn was the deadlock victim' },
 
397
   wait_array_size             => { hdr => 'Wait Array Size',     num => 1, label => 'Wait Array Size' },
 
398
   wait_status                 => { hdr => 'Lock Status',         num => 0, label => 'Status of txn locks' },
 
399
   waited_at_filename          => { hdr => 'File',                num => 0, label => 'Filename at which thread waits' },
 
400
   waited_at_line              => { hdr => 'Line',                num => 1, label => 'Line at which thread waits' },
 
401
   waiters_flag                => { hdr => 'Waiters',             num => 1, label => 'Waiters Flag' },
 
402
   waiting                     => { hdr => 'Waiting',             num => 1, label => 'Whether lock is being waited for' },
 
403
   when                        => { hdr => 'When',                num => 0, label => 'Time scale' },
 
404
   writer_lock_mode            => { hdr => 'Wrtr Lck Mode',       num => 0, label => 'Writer lock mode' },
 
405
   writer_thread               => { hdr => 'Wrtr Thread',         num => 1, label => 'Writer thread ID' },
 
406
   writes_pending              => { hdr => 'Writes',              num => 1, label => 'Number of writes pending' },
 
407
   writes_pending_flush_list   => { hdr => 'Flush List Writes',   num => 1, label => 'Number of flush list writes pending' },
 
408
   writes_pending_lru          => { hdr => 'LRU Writes',          num => 1, label => 'Number of LRU writes pending' },
 
409
   writes_pending_single_page  => { hdr => '1-Page Writes',       num => 1, label => 'Number of 1-page writes pending' },
 
410
);
 
411
 
 
412
# Apply a default property or three.  By default, columns are not width-constrained,
 
413
# aligned left, and sorted alphabetically, not numerically.
 
414
foreach my $col ( values %columns ) {
 
415
   map { $col->{$_} ||= 0 } qw(num minw maxw);
 
416
   $col->{just} = $col->{num} ? '' : '-';
 
417
}
 
418
 
 
419
# Filters {{{3
 
420
# This hash defines every filter that can be applied to a table.  These
 
421
# become part of tbl_meta as well.  Each filter is just an expression that
 
422
# returns true or false.
 
423
# Properties of each entry:
 
424
#  * func:   the subroutine
 
425
#  * name:   the name, repeated
 
426
#  * user:   whether it's a user-defined filter (saved in config)
 
427
#  * text:   text of the subroutine
 
428
#  * note:   explanation
 
429
my %filters = ();
 
430
 
 
431
# These are pre-processed to live in %filters above, by compiling them.
 
432
my %builtin_filters = (
 
433
   hide_self => {
 
434
      text => <<'      END',
 
435
         return ( !$set->{info} || $set->{info} ne 'SHOW FULL PROCESSLIST' )
 
436
             && ( !$set->{query_text}    || $set->{query_text} !~ m/INNODB STATUS$/ );
 
437
      END
 
438
      note => 'Removes the innotop processes from the list',
 
439
      tbls => [qw(innodb_transactions processlist)],
 
440
   },
 
441
   hide_inactive => {
 
442
      text => <<'      END',
 
443
         return ( !defined($set->{txn_status}) || $set->{txn_status} ne 'not started' )
 
444
             && ( !defined($set->{cmd})        || $set->{cmd} !~ m/Sleep|Binlog Dump/ )
 
445
             && ( !defined($set->{info})       || $set->{info} =~ m/\S/               );
 
446
      END
 
447
      note => 'Removes processes which are not doing anything',
 
448
      tbls => [qw(innodb_transactions processlist)],
 
449
   },
 
450
   hide_slave_io => {
 
451
      text => <<'      END',
 
452
         return !$set->{state} || $set->{state} !~ m/^(?:Waiting for master|Has read all relay)/;
 
453
      END
 
454
      note => 'Removes slave I/O threads from the list',
 
455
      tbls => [qw(processlist slave_io_status)],
 
456
   },
 
457
   table_is_open => {
 
458
      text => <<'      END',
 
459
         return $set->{num_times_open} + $set->{is_name_locked};
 
460
      END
 
461
      note => 'Removes tables that are not in use or locked',
 
462
      tbls => [qw(open_tables)],
 
463
   },
 
464
   cxn_is_master => {
 
465
      text => <<'      END',
 
466
         return $set->{master_file} ? 1 : 0;
 
467
      END
 
468
      note => 'Removes servers that are not masters',
 
469
      tbls => [qw(master_status)],
 
470
   },
 
471
   cxn_is_slave => {
 
472
      text => <<'      END',
 
473
         return $set->{master_host} ? 1 : 0;
 
474
      END
 
475
      note => 'Removes servers that are not slaves',
 
476
      tbls => [qw(slave_io_status slave_sql_status)],
 
477
   },
 
478
   thd_is_not_waiting => {
 
479
      text => <<'      END',
 
480
         return $set->{thread_status} !~ m#waiting for i/o request#;
 
481
      END
 
482
      note => 'Removes idle I/O threads',
 
483
      tbls => [qw(io_threads)],
 
484
   },
 
485
);
 
486
foreach my $key ( keys %builtin_filters ) {
 
487
   my ( $sub, $err ) = compile_filter($builtin_filters{$key}->{text});
 
488
   $filters{$key} = {
 
489
      func => $sub,
 
490
      text => $builtin_filters{$key}->{text},
 
491
      user => 0,
 
492
      name => $key, # useful for later
 
493
      note => $builtin_filters{$key}->{note},
 
494
      tbls => $builtin_filters{$key}->{tbls},
 
495
   }
 
496
}
 
497
 
 
498
# Variable sets {{{3
 
499
# Sets (arrayrefs) of variables that are used in S mode.  They are read/written to
 
500
# the config file.
 
501
my %var_sets = (
 
502
   general => {
 
503
      text => join(
 
504
         ', ',
 
505
         'set_precision(Questions/Uptime_hires) as QPS',
 
506
         'set_precision(Com_commit/Uptime_hires) as Commit_PS',
 
507
         'set_precision((Com_rollback||0)/(Com_commit||1)) as Rollback_Commit',
 
508
         'set_precision(('
 
509
            . join('+', map { "($_||0)" }
 
510
               qw(Com_delete Com_delete_multi Com_insert Com_insert_select Com_replace
 
511
                  Com_replace_select Com_select Com_update Com_update_multi))
 
512
            . ')/(Com_commit||1)) as Write_Commit',
 
513
         'set_precision((Com_select+(Qcache_hits||0))/(('
 
514
            . join('+', map { "($_||0)" }
 
515
               qw(Com_delete Com_delete_multi Com_insert Com_insert_select Com_replace
 
516
                  Com_replace_select Com_select Com_update Com_update_multi))
 
517
            . ')||1)) as R_W_Ratio',
 
518
         'set_precision(Opened_tables/Uptime_hires) as Opens_PS',
 
519
         'percent($cur->{Open_tables}/($cur->{table_cache})) as Table_Cache_Used',
 
520
         'set_precision(Threads_created/Uptime_hires) as Threads_PS',
 
521
         'percent($cur->{Threads_cached}/($cur->{thread_cache_size}||1)) as Thread_Cache_Used',
 
522
         'percent($cur->{Max_used_connections}/($cur->{max_connections}||1)) as CXN_Used_Ever',
 
523
         'percent($cur->{Threads_connected}/($cur->{max_connections}||1)) as CXN_Used_Now',
 
524
      ),
 
525
   },
 
526
   commands => {
 
527
      text => join(
 
528
         ', ',
 
529
         qw(Uptime Questions Com_delete Com_delete_multi Com_insert
 
530
         Com_insert_select Com_replace Com_replace_select Com_select Com_update
 
531
         Com_update_multi)
 
532
      ),
 
533
   },
 
534
   query_status => {
 
535
      text => join(
 
536
         ',',
 
537
         qw( Uptime Select_full_join Select_full_range_join Select_range
 
538
         Select_range_check Select_scan Slow_queries Sort_merge_passes
 
539
         Sort_range Sort_rows Sort_scan)
 
540
      ),
 
541
   },
 
542
   innodb => {
 
543
      text => join(
 
544
         ',',
 
545
         qw( Uptime Innodb_row_lock_current_waits Innodb_row_lock_time
 
546
         Innodb_row_lock_time_avg Innodb_row_lock_time_max Innodb_row_lock_waits
 
547
         Innodb_rows_deleted Innodb_rows_inserted Innodb_rows_read
 
548
         Innodb_rows_updated)
 
549
      ),
 
550
   },
 
551
   txn => {
 
552
      text => join(
 
553
         ',',
 
554
         qw( Uptime Com_begin Com_commit Com_rollback Com_savepoint
 
555
         Com_xa_commit Com_xa_end Com_xa_prepare Com_xa_recover Com_xa_rollback
 
556
         Com_xa_start)
 
557
      ),
 
558
   },
 
559
   key_cache => {
 
560
      text => join(
 
561
         ',',
 
562
         qw( Uptime Key_blocks_not_flushed Key_blocks_unused Key_blocks_used
 
563
         Key_read_requests Key_reads Key_write_requests Key_writes )
 
564
      ),
 
565
   },
 
566
   query_cache => {
 
567
      text => join(
 
568
         ',',
 
569
         "percent($exprs{QcacheHitRatio}) as Hit_Pct",
 
570
         'set_precision((Qcache_hits||0)/(Qcache_inserts||1)) as Hit_Ins',
 
571
         'set_precision((Qcache_lowmem_prunes||0)/Uptime_hires) as Lowmem_Prunes_sec',
 
572
         'percent(1-((Qcache_free_blocks||0)/(Qcache_total_blocks||1))) as Blocks_used',
 
573
         qw( Qcache_free_blocks Qcache_free_memory Qcache_not_cached Qcache_queries_in_cache)
 
574
      ),
 
575
   },
 
576
   handler => {
 
577
      text => join(
 
578
         ',',
 
579
         qw( Uptime Handler_read_key Handler_read_first Handler_read_next
 
580
         Handler_read_prev Handler_read_rnd Handler_read_rnd_next Handler_delete
 
581
         Handler_update Handler_write)
 
582
      ),
 
583
   },
 
584
   cxns_files_threads => {
 
585
      text => join(
 
586
         ',',
 
587
         qw( Uptime Aborted_clients Aborted_connects Bytes_received Bytes_sent
 
588
         Compression Connections Created_tmp_disk_tables Created_tmp_files
 
589
         Created_tmp_tables Max_used_connections Open_files Open_streams
 
590
         Open_tables Opened_tables Table_locks_immediate Table_locks_waited
 
591
         Threads_cached Threads_connected Threads_created Threads_running)
 
592
      ),
 
593
   },
 
594
   prep_stmt => {
 
595
      text => join(
 
596
         ',',
 
597
         qw( Uptime Com_dealloc_sql Com_execute_sql Com_prepare_sql Com_reset
 
598
         Com_stmt_close Com_stmt_execute Com_stmt_fetch Com_stmt_prepare
 
599
         Com_stmt_reset Com_stmt_send_long_data )
 
600
      ),
 
601
   },
 
602
   innodb_health => {
 
603
      text => join(
 
604
         ',',
 
605
         "$exprs{OldVersions} as OldVersions",
 
606
         qw(IB_sm_mutex_spin_waits IB_sm_mutex_spin_rounds IB_sm_mutex_os_waits),
 
607
         "$exprs{NumTxns} as NumTxns",
 
608
         "$exprs{MaxTxnTime} as MaxTxnTime",
 
609
         qw(IB_ro_queries_inside IB_ro_queries_in_queue),
 
610
         "set_precision($exprs{DirtyBufs} * 100) as dirty_bufs",
 
611
         "set_precision($exprs{BufPoolFill} * 100) as buf_fill",
 
612
         qw(IB_bp_pages_total IB_bp_pages_read IB_bp_pages_written IB_bp_pages_created)
 
613
      ),
 
614
   },
 
615
   innodb_health2 => {
 
616
      text => join(
 
617
         ', ',
 
618
         'percent(1-((Innodb_buffer_pool_pages_free||0)/($cur->{Innodb_buffer_pool_pages_total}||1))) as BP_page_cache_usage',
 
619
         'percent(1-((Innodb_buffer_pool_reads||0)/(Innodb_buffer_pool_read_requests||1))) as BP_cache_hit_ratio',
 
620
         'Innodb_buffer_pool_wait_free',
 
621
         'Innodb_log_waits',
 
622
      ),
 
623
   },
 
624
   slow_queries => {
 
625
      text => join(
 
626
         ', ',
 
627
         'set_precision(Slow_queries/Uptime_hires) as Slow_PS',
 
628
         'set_precision(Select_full_join/Uptime_hires) as Full_Join_PS',
 
629
         'percent(Select_full_join/(Com_select||1)) as Full_Join_Ratio',
 
630
      ),
 
631
   },
 
632
);
 
633
 
 
634
# Server sets {{{3
 
635
# Defines sets of servers between which the user can quickly switch.
 
636
my %server_groups;
 
637
 
 
638
# Connections {{{3
 
639
# This hash defines server connections.  Each connection is a string that can be passed to
 
640
# the DBI connection.  These are saved in the connections section in the config file.
 
641
my %connections;
 
642
# Defines the parts of connections.
 
643
my @conn_parts = qw(user have_user pass have_pass dsn savepass dl_table);
 
644
 
 
645
# Graph widths {{{3
 
646
# This hash defines the max values seen for various status/variable values, for graphing.
 
647
# These are stored in their own section in the config file.  These are just initial values:
 
648
my %mvs = (
 
649
   Com_select   => 50,
 
650
   Com_insert   => 50,
 
651
   Com_update   => 50,
 
652
   Com_delete   => 50,
 
653
   Questions    => 100,
 
654
);
 
655
 
 
656
# ###########################################################################
 
657
# Valid Term::ANSIColor color strings.
 
658
# ###########################################################################
 
659
my %ansicolors = map { $_ => 1 }
 
660
   qw( black blink blue bold clear concealed cyan dark green magenta on_black
 
661
       on_blue on_cyan on_green on_magenta on_red on_white on_yellow red reset
 
662
       reverse underline underscore white yellow);
 
663
 
 
664
# ###########################################################################
 
665
# Valid comparison operators for color rules
 
666
# ###########################################################################
 
667
my %comp_ops = (
 
668
   '==' => 'Numeric equality',
 
669
   '>'  => 'Numeric greater-than',
 
670
   '<'  => 'Numeric less-than',
 
671
   '>=' => 'Numeric greater-than/equal',
 
672
   '<=' => 'Numeric less-than/equal',
 
673
   '!=' => 'Numeric not-equal',
 
674
   'eq' => 'String equality',
 
675
   'gt' => 'String greater-than',
 
676
   'lt' => 'String less-than',
 
677
   'ge' => 'String greater-than/equal',
 
678
   'le' => 'String less-than/equal',
 
679
   'ne' => 'String not-equal',
 
680
   '=~' => 'Pattern match',
 
681
   '!~' => 'Negated pattern match',
 
682
);
 
683
 
 
684
# ###########################################################################
 
685
# Valid aggregate functions.
 
686
# ###########################################################################
 
687
my %agg_funcs = (
 
688
   first => sub {
 
689
      return $_[0]
 
690
   },
 
691
   count => sub {
 
692
      return 0 + @_;
 
693
   },
 
694
   avg   => sub {
 
695
      my @args = grep { defined $_ } @_;
 
696
      return (sum(map { m/([\d\.-]+)/g } @args) || 0) / (scalar(@args) || 1);
 
697
   },
 
698
   sum   => \&sum,
 
699
);
 
700
 
 
701
# ###########################################################################
 
702
# Valid functions for transformations.
 
703
# ###########################################################################
 
704
my %trans_funcs = (
 
705
   shorten      => \&shorten,
 
706
   secs_to_time => \&secs_to_time,
 
707
   no_ctrl_char => \&no_ctrl_char,
 
708
   percent      => \&percent,
 
709
   commify      => \&commify,
 
710
   dulint_to_int => \&dulint_to_int,
 
711
   set_precision => \&set_precision,
 
712
);
 
713
 
 
714
# Table definitions {{{3
 
715
# This hash defines every table that can get displayed in every mode.  Each
 
716
# table specifies columns and column data sources.  The column is
 
717
# defined by the %columns hash.
 
718
#
 
719
# Example: foo => { src => 'bar' } means the foo column (look at
 
720
# $columns{foo} for its definition) gets its data from the 'bar' element of
 
721
# the current data set, whatever that is.
 
722
#
 
723
# These columns are post-processed after being defined, because they get stuff
 
724
# from %columns.  After all the config is loaded for columns, there's more
 
725
# post-processing too; the subroutines compiled from src get added to
 
726
# the hash elements for extract_values to use.
 
727
# ###########################################################################
 
728
 
 
729
my %tbl_meta = (
 
730
   adaptive_hash_index => {
 
731
      capt => 'Adaptive Hash Index',
 
732
      cust => {},
 
733
      cols => {
 
734
         cxn                 => { src => 'cxn' },
 
735
         hash_table_size     => { src => 'IB_ib_hash_table_size', trans => [qw(shorten)], },
 
736
         used_cells          => { src => 'IB_ib_used_cells' },
 
737
         bufs_in_node_heap   => { src => 'IB_ib_bufs_in_node_heap' },
 
738
         hash_searches_s     => { src => 'IB_ib_hash_searches_s' },
 
739
         non_hash_searches_s => { src => 'IB_ib_non_hash_searches_s' },
 
740
      },
 
741
      visible => [ qw(cxn hash_table_size used_cells bufs_in_node_heap hash_searches_s non_hash_searches_s) ],
 
742
      filters => [],
 
743
      sort_cols => 'cxn',
 
744
      sort_dir => '1',
 
745
      innodb   => 'ib',
 
746
      group_by => [],
 
747
      aggregate => 0,
 
748
   },
 
749
   buffer_pool => {
 
750
      capt => 'Buffer Pool',
 
751
      cust => {},
 
752
      cols => {
 
753
         cxn                        => { src => 'cxn' },
 
754
         total_mem_alloc            => { src => 'IB_bp_total_mem_alloc', trans => [qw(shorten)], },
 
755
         awe_mem_alloc              => { src => 'IB_bp_awe_mem_alloc', trans => [qw(shorten)], },
 
756
         add_pool_alloc             => { src => 'IB_bp_add_pool_alloc', trans => [qw(shorten)], },
 
757
         buf_pool_size              => { src => 'IB_bp_buf_pool_size', trans => [qw(shorten)], },
 
758
         buf_free                   => { src => 'IB_bp_buf_free' },
 
759
         buf_pool_hit_rate          => { src => 'IB_bp_buf_pool_hit_rate' },
 
760
         buf_pool_reads             => { src => 'IB_bp_buf_pool_reads' },
 
761
         buf_pool_hits              => { src => 'IB_bp_buf_pool_hits' },
 
762
         dict_mem_alloc             => { src => 'IB_bp_dict_mem_alloc' },
 
763
         pages_total                => { src => 'IB_bp_pages_total' },
 
764
         pages_modified             => { src => 'IB_bp_pages_modified' },
 
765
         reads_pending              => { src => 'IB_bp_reads_pending' },
 
766
         writes_pending             => { src => 'IB_bp_writes_pending' },
 
767
         writes_pending_lru         => { src => 'IB_bp_writes_pending_lru' },
 
768
         writes_pending_flush_list  => { src => 'IB_bp_writes_pending_flush_list' },
 
769
         writes_pending_single_page => { src => 'IB_bp_writes_pending_single_page' },
 
770
         page_creates_sec           => { src => 'IB_bp_page_creates_sec' },
 
771
         page_reads_sec             => { src => 'IB_bp_page_reads_sec' },
 
772
         page_writes_sec            => { src => 'IB_bp_page_writes_sec' },
 
773
         pages_created              => { src => 'IB_bp_pages_created' },
 
774
         pages_read                 => { src => 'IB_bp_pages_read' },
 
775
         pages_written              => { src => 'IB_bp_pages_written' },
 
776
      },
 
777
      visible => [ qw(cxn buf_pool_size buf_free pages_total pages_modified buf_pool_hit_rate total_mem_alloc add_pool_alloc)],
 
778
      filters => [],
 
779
      sort_cols => 'cxn',
 
780
      sort_dir => '1',
 
781
      innodb   => 'bp',
 
782
      group_by => [],
 
783
      aggregate => 0,
 
784
   },
 
785
   # TODO: a new step in set_to_tbl: join result to itself, grouped?
 
786
   # TODO: this would also enable pulling Q and T data together.
 
787
   # TODO: using a SQL-ish language would also allow pivots to be easier -- treat the pivoted data as a view and SELECT from it.
 
788
   cmd_summary => {
 
789
      capt => 'Command Summary',
 
790
      cust => {},
 
791
      cols => {
 
792
         name       => { src => 'name' },
 
793
         total      => { src => 'total' },
 
794
         value      => { src => 'value',                     agg   => 'sum'},
 
795
         pct        => { src => 'value/total',               trans => [qw(percent)] },
 
796
         last_total => { src => 'last_total' },
 
797
         last_value => { src => 'last_value',                agg   => 'sum'},
 
798
         last_pct   => { src => 'last_value/last_total',     trans => [qw(percent)] },
 
799
      },
 
800
      visible   => [qw(name value pct last_value last_pct)],
 
801
      filters   => [qw()],
 
802
      sort_cols => '-value',
 
803
      sort_dir  => '1',
 
804
      innodb    => '',
 
805
      group_by  => [qw(name)],
 
806
      aggregate => 1,
 
807
   },
 
808
   deadlock_locks => {
 
809
      capt => 'Deadlock Locks',
 
810
      cust => {},
 
811
      cols => {
 
812
         cxn              => { src => 'cxn' },
 
813
         mysql_thread_id  => { src => 'mysql_thread_id' },
 
814
         dl_txn_num       => { src => 'dl_txn_num' },
 
815
         lock_type        => { src => 'lock_type' },
 
816
         space_id         => { src => 'space_id' },
 
817
         page_no          => { src => 'page_no' },
 
818
         heap_no          => { src => 'heap_no' },
 
819
         n_bits           => { src => 'n_bits' },
 
820
         index            => { src => 'index' },
 
821
         db               => { src => 'db' },
 
822
         tbl              => { src => 'table' },
 
823
         lock_mode        => { src => 'lock_mode' },
 
824
         special          => { src => 'special' },
 
825
         insert_intention => { src => 'insert_intention' },
 
826
         waiting          => { src => 'waiting' },
 
827
      },
 
828
      visible => [ qw(cxn mysql_thread_id waiting lock_mode db tbl index special insert_intention)],
 
829
      filters => [],
 
830
      sort_cols => 'cxn mysql_thread_id',
 
831
      sort_dir => '1',
 
832
      innodb   => 'dl',
 
833
      group_by => [],
 
834
      aggregate => 0,
 
835
   },
 
836
   deadlock_transactions => {
 
837
      capt => 'Deadlock Transactions',
 
838
      cust => {},
 
839
      cols => {
 
840
         cxn                => { src => 'cxn' },
 
841
         active_secs        => { src => 'active_secs' },
 
842
         dl_txn_num         => { src => 'dl_txn_num' },
 
843
         has_read_view      => { src => 'has_read_view' },
 
844
         heap_size          => { src => 'heap_size' },
 
845
         host_and_domain    => { src => 'hostname' },
 
846
         hostname           => { src => $exprs{Host} },
 
847
         ip                 => { src => 'ip' },
 
848
         lock_structs       => { src => 'lock_structs' },
 
849
         lock_wait_time     => { src => 'lock_wait_time', trans => [ qw(secs_to_time) ] },
 
850
         mysql_thread_id    => { src => 'mysql_thread_id' },
 
851
         os_thread_id       => { src => 'os_thread_id' },
 
852
         proc_no            => { src => 'proc_no' },
 
853
         query_id           => { src => 'query_id' },
 
854
         query_status       => { src => 'query_status' },
 
855
         query_text         => { src => 'query_text', trans => [ qw(no_ctrl_char) ] },
 
856
         row_locks          => { src => 'row_locks' },
 
857
         tables_in_use      => { src => 'tables_in_use' },
 
858
         tables_locked      => { src => 'tables_locked' },
 
859
         thread_decl_inside => { src => 'thread_decl_inside' },
 
860
         thread_status      => { src => 'thread_status' },
 
861
         'time'             => { src => 'active_secs', trans => [ qw(secs_to_time) ] },
 
862
         timestring         => { src => 'timestring' },
 
863
         txn_doesnt_see_ge  => { src => 'txn_doesnt_see_ge' },
 
864
         txn_id             => { src => 'txn_id' },
 
865
         txn_sees_lt        => { src => 'txn_sees_lt' },
 
866
         txn_status         => { src => 'txn_status' },
 
867
         truncates          => { src => 'truncates' },
 
868
         undo_log_entries   => { src => 'undo_log_entries' },
 
869
         user               => { src => 'user' },
 
870
         victim             => { src => 'victim' },
 
871
         wait_status        => { src => 'lock_wait_status' },
 
872
      },
 
873
      visible => [ qw(cxn mysql_thread_id timestring user hostname victim time undo_log_entries lock_structs query_text)],
 
874
      filters => [],
 
875
      sort_cols => 'cxn mysql_thread_id',
 
876
      sort_dir => '1',
 
877
      innodb   => 'dl',
 
878
      group_by => [],
 
879
      aggregate => 0,
 
880
   },
 
881
   explain => {
 
882
      capt => 'EXPLAIN Results',
 
883
      cust => {},
 
884
      cols => {
 
885
         part_id       => { src => 'id' },
 
886
         select_type   => { src => 'select_type' },
 
887
         tbl           => { src => 'table' },
 
888
         partitions    => { src => 'partitions' },
 
889
         scan_type     => { src => 'type' },
 
890
         possible_keys => { src => 'possible_keys' },
 
891
         index         => { src => 'key' },
 
892
         key_len       => { src => 'key_len' },
 
893
         index_ref     => { src => 'ref' },
 
894
         num_rows      => { src => 'rows' },
 
895
         special       => { src => 'extra' },
 
896
      },
 
897
      visible => [ qw(select_type tbl partitions scan_type possible_keys index key_len index_ref num_rows special)],
 
898
      filters => [],
 
899
      sort_cols => '',
 
900
      sort_dir => '1',
 
901
      innodb   => '',
 
902
      group_by => [],
 
903
      aggregate => 0,
 
904
   },
 
905
   file_io_misc => {
 
906
      capt => 'File I/O Misc',
 
907
      cust => {},
 
908
      cols => {
 
909
         cxn            => { src => 'cxn' },
 
910
         io_bytes_s     => { src => 'IB_io_avg_bytes_s' },
 
911
         io_flush_type  => { src => 'IB_io_flush_type' },
 
912
         io_fsyncs_s    => { src => 'IB_io_fsyncs_s' },
 
913
         io_reads_s     => { src => 'IB_io_reads_s' },
 
914
         io_writes_s    => { src => 'IB_io_writes_s' },
 
915
         os_file_reads  => { src => 'IB_io_os_file_reads' },
 
916
         os_file_writes => { src => 'IB_io_os_file_writes' },
 
917
         os_fsyncs      => { src => 'IB_io_os_fsyncs' },
 
918
      },
 
919
      visible => [ qw(cxn os_file_reads os_file_writes os_fsyncs io_reads_s io_writes_s io_bytes_s)],
 
920
      filters => [],
 
921
      sort_cols => 'cxn',
 
922
      sort_dir => '1',
 
923
      innodb   => 'io',
 
924
      group_by => [],
 
925
      aggregate => 0,
 
926
   },
 
927
   fk_error => {
 
928
      capt => 'Foreign Key Error Info',
 
929
      cust => {},
 
930
      cols => {
 
931
         timestring   => { src => 'IB_fk_timestring' },
 
932
         child_db     => { src => 'IB_fk_child_db' },
 
933
         child_table  => { src => 'IB_fk_child_table' },
 
934
         child_index  => { src => 'IB_fk_child_index' },
 
935
         fk_name      => { src => 'IB_fk_fk_name' },
 
936
         parent_db    => { src => 'IB_fk_parent_db' },
 
937
         parent_table => { src => 'IB_fk_parent_table' },
 
938
         parent_col   => { src => 'IB_fk_parent_col' },
 
939
         parent_index => { src => 'IB_fk_parent_index' },
 
940
         attempted_op => { src => 'IB_fk_attempted_op' },
 
941
      },
 
942
      visible => [ qw(timestring child_db child_table child_index parent_db parent_table parent_col parent_index fk_name attempted_op)],
 
943
      filters => [],
 
944
      sort_cols => '',
 
945
      sort_dir => '1',
 
946
      innodb   => 'fk',
 
947
      group_by => [],
 
948
      aggregate => 0,
 
949
   },
 
950
   insert_buffers => {
 
951
      capt => 'Insert Buffers',
 
952
      cust => {},
 
953
      cols => {
 
954
         cxn           => { src => 'cxn' },
 
955
         inserts       => { src => 'IB_ib_inserts' },
 
956
         merged_recs   => { src => 'IB_ib_merged_recs' },
 
957
         merges        => { src => 'IB_ib_merges' },
 
958
         size          => { src => 'IB_ib_size' },
 
959
         free_list_len => { src => 'IB_ib_free_list_len' },
 
960
         seg_size      => { src => 'IB_ib_seg_size' },
 
961
      },
 
962
      visible => [ qw(cxn inserts merged_recs merges size free_list_len seg_size)],
 
963
      filters => [],
 
964
      sort_cols => 'cxn',
 
965
      sort_dir => '1',
 
966
      innodb   => 'ib',
 
967
      group_by => [],
 
968
      aggregate => 0,
 
969
   },
 
970
   innodb_locks  => {
 
971
      capt => 'InnoDB Locks',
 
972
      cust => {},
 
973
      cols => {
 
974
         cxn              => { src => 'cxn' },
 
975
         db               => { src => 'db' },
 
976
         index            => { src => 'index' },
 
977
         insert_intention => { src => 'insert_intention' },
 
978
         lock_mode        => { src => 'lock_mode' },
 
979
         lock_type        => { src => 'lock_type' },
 
980
         lock_wait_time   => { src => 'lock_wait_time', trans => [ qw(secs_to_time) ] },
 
981
         mysql_thread_id  => { src => 'mysql_thread_id' },
 
982
         n_bits           => { src => 'n_bits' },
 
983
         page_no          => { src => 'page_no' },
 
984
         space_id         => { src => 'space_id' },
 
985
         special          => { src => 'special' },
 
986
         tbl              => { src => 'table' },
 
987
         'time'           => { src => 'active_secs', hdr => 'Active', trans => [ qw(secs_to_time) ] },
 
988
         txn_id           => { src => 'txn_id' },
 
989
         waiting          => { src => 'waiting' },
 
990
      },
 
991
      visible => [ qw(cxn mysql_thread_id lock_type waiting lock_wait_time time lock_mode db tbl index insert_intention special)],
 
992
      filters => [],
 
993
      sort_cols => 'cxn -lock_wait_time',
 
994
      sort_dir => '1',
 
995
      innodb   => 'tx',
 
996
      colors   => [
 
997
         { col => 'lock_wait_time', op => '>',  arg => 60, color => 'red' },
 
998
         { col => 'lock_wait_time', op => '>',  arg => 30, color => 'yellow' },
 
999
         { col => 'lock_wait_time', op => '>',  arg => 10, color => 'green' },
 
1000
      ],
 
1001
      group_by => [],
 
1002
      aggregate => 0,
 
1003
   },
 
1004
   innodb_transactions => {
 
1005
      capt => 'InnoDB Transactions',
 
1006
      cust => {},
 
1007
      cols => {
 
1008
         cxn                => { src => 'cxn' },
 
1009
         active_secs        => { src => 'active_secs' },
 
1010
         has_read_view      => { src => 'has_read_view' },
 
1011
         heap_size          => { src => 'heap_size' },
 
1012
         hostname           => { src => $exprs{Host} },
 
1013
         ip                 => { src => 'ip' },
 
1014
         wait_status        => { src => 'lock_wait_status' },
 
1015
         lock_wait_time     => { src => 'lock_wait_time',      trans => [ qw(secs_to_time) ] },
 
1016
         lock_structs       => { src => 'lock_structs' },
 
1017
         mysql_thread_id    => { src => 'mysql_thread_id' },
 
1018
         os_thread_id       => { src => 'os_thread_id' },
 
1019
         proc_no            => { src => 'proc_no' },
 
1020
         query_id           => { src => 'query_id' },
 
1021
         query_status       => { src => 'query_status' },
 
1022
         query_text         => { src => 'query_text',          trans => [ qw(no_ctrl_char) ] },
 
1023
         txn_time_remain    => { src => $exprs{TxnTimeRemain}, trans => [ qw(secs_to_time) ] },
 
1024
         row_locks          => { src => 'row_locks' },
 
1025
         tables_in_use      => { src => 'tables_in_use' },
 
1026
         tables_locked      => { src => 'tables_locked' },
 
1027
         thread_decl_inside => { src => 'thread_decl_inside' },
 
1028
         thread_status      => { src => 'thread_status' },
 
1029
         'time'             => { src => 'active_secs',         trans => [ qw(secs_to_time) ], agg => 'sum' },
 
1030
         txn_doesnt_see_ge  => { src => 'txn_doesnt_see_ge' },
 
1031
         txn_id             => { src => 'txn_id' },
 
1032
         txn_sees_lt        => { src => 'txn_sees_lt' },
 
1033
         txn_status         => { src => 'txn_status',          minw => 10, maxw => 10 },
 
1034
         undo_log_entries   => { src => 'undo_log_entries' },
 
1035
         user               => { src => 'user',                maxw => 10 },
 
1036
         cnt                => { src => 'mysql_thread_id',     minw => 0 },
 
1037
      },
 
1038
      visible => [ qw(cxn cnt mysql_thread_id user hostname txn_status time undo_log_entries query_text)],
 
1039
      filters => [ qw( hide_self hide_inactive ) ],
 
1040
      sort_cols => '-active_secs txn_status cxn mysql_thread_id',
 
1041
      sort_dir => '1',
 
1042
      innodb   => 'tx',
 
1043
      hide_caption => 1,
 
1044
      colors   => [
 
1045
         { col => 'wait_status', op => 'eq', arg => 'LOCK WAIT',   color => 'black on_red' },
 
1046
         { col => 'time',        op => '>',  arg => 600,           color => 'red' },
 
1047
         { col => 'time',        op => '>',  arg => 300,           color => 'yellow' },
 
1048
         { col => 'time',        op => '>',  arg => 60,            color => 'green' },
 
1049
         { col => 'time',        op => '>',  arg => 30,            color => 'cyan' },
 
1050
         { col => 'txn_status',  op => 'eq', arg => 'not started', color => 'white' },
 
1051
      ],
 
1052
      group_by => [ qw(cxn txn_status) ],
 
1053
      aggregate => 0,
 
1054
   },
 
1055
   io_threads => {
 
1056
      capt => 'I/O Threads',
 
1057
      cust => {},
 
1058
      cols => {
 
1059
         cxn            => { src => 'cxn' },
 
1060
         thread         => { src => 'thread' },
 
1061
         thread_purpose => { src => 'purpose' },
 
1062
         event_set      => { src => 'event_set' },
 
1063
         thread_status  => { src => 'state' },
 
1064
      },
 
1065
      visible => [ qw(cxn thread thread_purpose thread_status)],
 
1066
      filters => [ qw() ],
 
1067
      sort_cols => 'cxn thread',
 
1068
      sort_dir => '1',
 
1069
      innodb   => 'io',
 
1070
      group_by => [],
 
1071
      aggregate => 0,
 
1072
   },
 
1073
   log_statistics => {
 
1074
      capt => 'Log Statistics',
 
1075
      cust => {},
 
1076
      cols => {
 
1077
         cxn                 => { src => 'cxn' },
 
1078
         last_chkp           => { src => 'IB_lg_last_chkp' },
 
1079
         log_flushed_to      => { src => 'IB_lg_log_flushed_to' },
 
1080
         log_ios_done        => { src => 'IB_lg_log_ios_done' },
 
1081
         log_ios_s           => { src => 'IB_lg_log_ios_s' },
 
1082
         log_seq_no          => { src => 'IB_lg_log_seq_no' },
 
1083
         pending_chkp_writes => { src => 'IB_lg_pending_chkp_writes' },
 
1084
         pending_log_writes  => { src => 'IB_lg_pending_log_writes' },
 
1085
      },
 
1086
      visible => [ qw(cxn log_seq_no log_flushed_to last_chkp log_ios_done log_ios_s)],
 
1087
      filters => [],
 
1088
      sort_cols => 'cxn',
 
1089
      sort_dir => '1',
 
1090
      innodb   => 'lg',
 
1091
      group_by => [],
 
1092
      aggregate => 0,
 
1093
   },
 
1094
   master_status => {
 
1095
      capt => 'Master Status',
 
1096
      cust => {},
 
1097
      cols => {
 
1098
         cxn                         => { src => 'cxn' },
 
1099
         binlog_do_db                => { src => 'binlog_do_db' },
 
1100
         binlog_ignore_db            => { src => 'binlog_ignore_db' },
 
1101
         master_file                 => { src => 'file' },
 
1102
         master_pos                  => { src => 'position' },
 
1103
         binlog_cache_overflow       => { src => '(Binlog_cache_disk_use||0)/(Binlog_cache_use||1)', trans => [ qw(percent) ] },
 
1104
      },
 
1105
      visible => [ qw(cxn master_file master_pos binlog_cache_overflow)],
 
1106
      filters => [ qw(cxn_is_master) ],
 
1107
      sort_cols => 'cxn',
 
1108
      sort_dir => '1',
 
1109
      innodb   => '',
 
1110
      group_by => [],
 
1111
      aggregate => 0,
 
1112
   },
 
1113
   pending_io => {
 
1114
      capt => 'Pending I/O',
 
1115
      cust => {},
 
1116
      cols => {
 
1117
         cxn                => { src => 'cxn' },
 
1118
         p_normal_aio_reads => { src => 'IB_io_pending_normal_aio_reads' },
 
1119
         p_aio_writes       => { src => 'IB_io_pending_aio_writes' },
 
1120
         p_ibuf_aio_reads   => { src => 'IB_io_pending_ibuf_aio_reads' },
 
1121
         p_sync_ios         => { src => 'IB_io_pending_sync_ios' },
 
1122
         p_buf_pool_flushes => { src => 'IB_io_pending_buffer_pool_flushes' },
 
1123
         p_log_flushes      => { src => 'IB_io_pending_log_flushes' },
 
1124
         p_log_ios          => { src => 'IB_io_pending_log_ios' },
 
1125
         p_preads           => { src => 'IB_io_pending_preads' },
 
1126
         p_pwrites          => { src => 'IB_io_pending_pwrites' },
 
1127
      },
 
1128
      visible => [ qw(cxn p_normal_aio_reads p_aio_writes p_ibuf_aio_reads p_sync_ios p_log_flushes p_log_ios)],
 
1129
      filters => [],
 
1130
      sort_cols => 'cxn',
 
1131
      sort_dir => '1',
 
1132
      innodb   => 'io',
 
1133
      group_by => [],
 
1134
      aggregate => 0,
 
1135
   },
 
1136
   open_tables => {
 
1137
      capt => 'Open Tables',
 
1138
      cust => {},
 
1139
      cols => {
 
1140
         cxn            => { src => 'cxn' },
 
1141
         db             => { src => 'database' },
 
1142
         tbl            => { src => 'table' },
 
1143
         num_times_open => { src => 'in_use' },
 
1144
         is_name_locked => { src => 'name_locked' },
 
1145
      },
 
1146
      visible => [ qw(cxn db tbl num_times_open is_name_locked)],
 
1147
      filters => [ qw(table_is_open) ],
 
1148
      sort_cols => '-num_times_open cxn db tbl',
 
1149
      sort_dir => '1',
 
1150
      innodb   => '',
 
1151
      group_by => [],
 
1152
      aggregate => 0,
 
1153
   },
 
1154
   page_statistics => {
 
1155
      capt => 'Page Statistics',
 
1156
      cust => {},
 
1157
      cols => {
 
1158
         cxn              => { src => 'cxn' },
 
1159
         pages_read       => { src => 'IB_bp_pages_read' },
 
1160
         pages_written    => { src => 'IB_bp_pages_written' },
 
1161
         pages_created    => { src => 'IB_bp_pages_created' },
 
1162
         page_reads_sec   => { src => 'IB_bp_page_reads_sec' },
 
1163
         page_writes_sec  => { src => 'IB_bp_page_writes_sec' },
 
1164
         page_creates_sec => { src => 'IB_bp_page_creates_sec' },
 
1165
      },
 
1166
      visible => [ qw(cxn pages_read pages_written pages_created page_reads_sec page_writes_sec page_creates_sec)],
 
1167
      filters => [],
 
1168
      sort_cols => 'cxn',
 
1169
      sort_dir => '1',
 
1170
      innodb   => 'bp',
 
1171
      group_by => [],
 
1172
      aggregate => 0,
 
1173
   },
 
1174
   processlist => {
 
1175
      capt => 'MySQL Process List',
 
1176
      cust => {},
 
1177
      cols => {
 
1178
         cxn             => { src => 'cxn',        minw => 6,  maxw => 10 },
 
1179
         mysql_thread_id => { src => 'id',         minw => 6,  maxw => 0 },
 
1180
         user            => { src => 'user',       minw => 5,  maxw => 8 },
 
1181
         hostname        => { src => $exprs{Host}, minw => 13, maxw => 8, },
 
1182
         port            => { src => $exprs{Port}, minw => 0,  maxw => 0, },
 
1183
         host_and_port   => { src => 'host',       minw => 0,  maxw => 0 },
 
1184
         db              => { src => 'db',         minw => 6,  maxw => 12 },
 
1185
         cmd             => { src => 'command',    minw => 5,  maxw => 0 },
 
1186
         time            => { src => 'time',       minw => 5,  maxw => 0, trans => [ qw(secs_to_time) ], agg => 'sum' },
 
1187
         state           => { src => 'state',      minw => 0,  maxw => 0 },
 
1188
         info            => { src => 'info',       minw => 0,  maxw => 0, trans => [ qw(no_ctrl_char) ] },
 
1189
         cnt             => { src => 'id',         minw => 0,  maxw => 0 },
 
1190
      },
 
1191
      visible => [ qw(cxn cmd cnt mysql_thread_id user hostname db time info)],
 
1192
      filters => [ qw(hide_self hide_inactive hide_slave_io) ],
 
1193
      sort_cols => '-time cxn hostname mysql_thread_id',
 
1194
      sort_dir => '1',
 
1195
      innodb   => '',
 
1196
      hide_caption => 1,
 
1197
      colors   => [
 
1198
         { col => 'state',       op => 'eq', arg => 'Locked',      color => 'black on_red' },
 
1199
         { col => 'cmd',         op => 'eq', arg => 'Sleep',       color => 'white' },
 
1200
         { col => 'user',        op => 'eq', arg => 'system user', color => 'white' },
 
1201
         { col => 'cmd',         op => 'eq', arg => 'Connect',     color => 'white' },
 
1202
         { col => 'cmd',         op => 'eq', arg => 'Binlog Dump', color => 'white' },
 
1203
         { col => 'time',        op => '>',  arg => 600,           color => 'red' },
 
1204
         { col => 'time',        op => '>',  arg => 120,           color => 'yellow' },
 
1205
         { col => 'time',        op => '>',  arg => 60,            color => 'green' },
 
1206
         { col => 'time',        op => '>',  arg => 30,            color => 'cyan' },
 
1207
      ],
 
1208
      group_by => [qw(cxn cmd)],
 
1209
      aggregate => 0,
 
1210
   },
 
1211
 
 
1212
   # TODO: some more columns:
 
1213
   # kb_used=hdr='BufUsed' minw='0' num='0' src='percent(1 - ((Key_blocks_unused * key_cache_block_size) / (key_buffer_size||1)))' dec='0' trans='' tbl='q_header' just='-' user='1' maxw='0' label='User-defined'
 
1214
   # retries=hdr='Retries' minw='0' num='0' src='Slave_retried_transactions' dec='0' trans='' tbl='slave_sql_status' just='-' user='1' maxw='0' label='User-defined'
 
1215
   # thd=hdr='Thd' minw='0' num='0' src='Threads_connected' dec='0' trans='' tbl='slave_sql_status' just='-' user='1' maxw='0' label='User-defined'
 
1216
 
 
1217
   q_header => {
 
1218
      capt => 'Q-mode Header',
 
1219
      cust => {},
 
1220
      cols => {
 
1221
         cxn            => { src => 'cxn' },
 
1222
         questions      => { src => 'Questions' },
 
1223
         qps            => { src => 'Questions/Uptime_hires',               dec => 1, trans => [qw(shorten)] },
 
1224
         load           => { src => $exprs{ServerLoad},                     dec => 1, trans => [qw(shorten)] },
 
1225
         slow           => { src => 'Slow_queries',                         dec => 1, trans => [qw(shorten)] },
 
1226
         q_cache_hit    => { src => $exprs{QcacheHitRatio},                 dec => 1, trans => [qw(percent)] },
 
1227
         key_buffer_hit => { src => '1-(Key_reads/(Key_read_requests||1))', dec => 1, trans => [qw(percent)] },
 
1228
         bps_in         => { src => 'Bytes_received/Uptime_hires',          dec => 1, trans => [qw(shorten)] },
 
1229
         bps_out        => { src => 'Bytes_sent/Uptime_hires',              dec => 1, trans => [qw(shorten)] },
 
1230
         when           => { src => 'when' },
 
1231
      },
 
1232
      visible => [ qw(cxn when load qps slow q_cache_hit key_buffer_hit bps_in bps_out)],
 
1233
      filters => [],
 
1234
      sort_cols => 'when cxn',
 
1235
      sort_dir => '1',
 
1236
      innodb   => '',
 
1237
      hide_caption => 1,
 
1238
      group_by => [],
 
1239
      aggregate => 0,
 
1240
   },
 
1241
   row_operations => {
 
1242
      capt => 'InnoDB Row Operations',
 
1243
      cust => {},
 
1244
      cols => {
 
1245
         cxn         => { src => 'cxn' },
 
1246
         num_inserts => { src => 'IB_ro_num_rows_ins' },
 
1247
         num_updates => { src => 'IB_ro_num_rows_upd' },
 
1248
         num_reads   => { src => 'IB_ro_num_rows_read' },
 
1249
         num_deletes => { src => 'IB_ro_num_rows_del' },
 
1250
         num_inserts_sec => { src => 'IB_ro_ins_sec' },
 
1251
         num_updates_sec => { src => 'IB_ro_upd_sec' },
 
1252
         num_reads_sec   => { src => 'IB_ro_read_sec' },
 
1253
         num_deletes_sec => { src => 'IB_ro_del_sec' },
 
1254
      },
 
1255
      visible => [ qw(cxn num_inserts num_updates num_reads num_deletes num_inserts_sec
 
1256
                       num_updates_sec num_reads_sec num_deletes_sec)],
 
1257
      filters => [],
 
1258
      sort_cols => 'cxn',
 
1259
      sort_dir => '1',
 
1260
      innodb   => 'ro',
 
1261
      group_by => [],
 
1262
      aggregate => 0,
 
1263
   },
 
1264
   row_operation_misc => {
 
1265
      capt => 'Row Operation Misc',
 
1266
      cust => {},
 
1267
      cols => {
 
1268
         cxn                 => { src => 'cxn' },
 
1269
         queries_in_queue    => { src => 'IB_ro_queries_in_queue' },
 
1270
         queries_inside      => { src => 'IB_ro_queries_inside' },
 
1271
         read_views_open     => { src => 'IB_ro_read_views_open' },
 
1272
         main_thread_id      => { src => 'IB_ro_main_thread_id' },
 
1273
         main_thread_proc_no => { src => 'IB_ro_main_thread_proc_no' },
 
1274
         main_thread_state   => { src => 'IB_ro_main_thread_state' },
 
1275
         num_res_ext         => { src => 'IB_ro_n_reserved_extents' },
 
1276
      },
 
1277
      visible => [ qw(cxn queries_in_queue queries_inside read_views_open main_thread_state)],
 
1278
      filters => [],
 
1279
      sort_cols => 'cxn',
 
1280
      sort_dir => '1',
 
1281
      innodb   => 'ro',
 
1282
      group_by => [],
 
1283
      aggregate => 0,
 
1284
   },
 
1285
   semaphores => {
 
1286
      capt => 'InnoDB Semaphores',
 
1287
      cust => {},
 
1288
      cols => {
 
1289
         cxn                => { src => 'cxn' },
 
1290
         mutex_os_waits     => { src => 'IB_sm_mutex_os_waits' },
 
1291
         mutex_spin_rounds  => { src => 'IB_sm_mutex_spin_rounds' },
 
1292
         mutex_spin_waits   => { src => 'IB_sm_mutex_spin_waits' },
 
1293
         reservation_count  => { src => 'IB_sm_reservation_count' },
 
1294
         rw_excl_os_waits   => { src => 'IB_sm_rw_excl_os_waits' },
 
1295
         rw_excl_spins      => { src => 'IB_sm_rw_excl_spins' },
 
1296
         rw_shared_os_waits => { src => 'IB_sm_rw_shared_os_waits' },
 
1297
         rw_shared_spins    => { src => 'IB_sm_rw_shared_spins' },
 
1298
         signal_count       => { src => 'IB_sm_signal_count' },
 
1299
         wait_array_size    => { src => 'IB_sm_wait_array_size' },
 
1300
      },
 
1301
      visible => [ qw(cxn mutex_os_waits mutex_spin_waits mutex_spin_rounds
 
1302
         rw_excl_os_waits rw_excl_spins rw_shared_os_waits rw_shared_spins
 
1303
         signal_count reservation_count )],
 
1304
      filters => [],
 
1305
      sort_cols => 'cxn',
 
1306
      sort_dir => '1',
 
1307
      innodb   => 'sm',
 
1308
      group_by => [],
 
1309
      aggregate => 0,
 
1310
   },
 
1311
   slave_io_status => {
 
1312
      capt => 'Slave I/O Status',
 
1313
      cust => {},
 
1314
      cols => {
 
1315
         cxn                         => { src => 'cxn' },
 
1316
         connect_retry               => { src => 'connect_retry' },
 
1317
         master_host                 => { src => 'master_host', hdr => 'Master'},
 
1318
         master_log_file             => { src => 'master_log_file', hdr => 'File' },
 
1319
         master_port                 => { src => 'master_port' },
 
1320
         master_ssl_allowed          => { src => 'master_ssl_allowed' },
 
1321
         master_ssl_ca_file          => { src => 'master_ssl_ca_file' },
 
1322
         master_ssl_ca_path          => { src => 'master_ssl_ca_path' },
 
1323
         master_ssl_cert             => { src => 'master_ssl_cert' },
 
1324
         master_ssl_cipher           => { src => 'master_ssl_cipher' },
 
1325
         master_ssl_key              => { src => 'master_ssl_key' },
 
1326
         master_user                 => { src => 'master_user' },
 
1327
         read_master_log_pos         => { src => 'read_master_log_pos', hdr => 'Pos' },
 
1328
         relay_log_size              => { src => 'relay_log_space', trans => [qw(shorten)] },
 
1329
         slave_io_running            => { src => 'slave_io_running', hdr => 'On?' },
 
1330
         slave_io_state              => { src => 'slave_io_state', hdr => 'State' },
 
1331
      },
 
1332
      visible => [ qw(cxn master_host slave_io_running master_log_file relay_log_size read_master_log_pos slave_io_state)],
 
1333
      filters => [ qw( cxn_is_slave ) ],
 
1334
      sort_cols => 'slave_io_running cxn',
 
1335
      colors   => [
 
1336
         { col => 'slave_io_running',  op => 'ne', arg => 'Yes', color => 'black on_red' },
 
1337
      ],
 
1338
      sort_dir => '1',
 
1339
      innodb   => '',
 
1340
      group_by => [],
 
1341
      aggregate => 0,
 
1342
   },
 
1343
   slave_sql_status => {
 
1344
      capt => 'Slave SQL Status',
 
1345
      cust => {},
 
1346
      cols => {
 
1347
         cxn                         => { src => 'cxn' },
 
1348
         exec_master_log_pos         => { src => 'exec_master_log_pos', hdr => 'Master Pos' },
 
1349
         last_errno                  => { src => 'last_errno' },
 
1350
         last_error                  => { src => 'last_error' },
 
1351
         master_host                 => { src => 'master_host', hdr => 'Master' },
 
1352
         relay_log_file              => { src => 'relay_log_file' },
 
1353
         relay_log_pos               => { src => 'relay_log_pos' },
 
1354
         relay_log_size              => { src => 'relay_log_space', trans => [qw(shorten)] },
 
1355
         relay_master_log_file       => { src => 'relay_master_log_file', hdr => 'Master File' },
 
1356
         replicate_do_db             => { src => 'replicate_do_db' },
 
1357
         replicate_do_table          => { src => 'replicate_do_table' },
 
1358
         replicate_ignore_db         => { src => 'replicate_ignore_db' },
 
1359
         replicate_ignore_table      => { src => 'replicate_ignore_table' },
 
1360
         replicate_wild_do_table     => { src => 'replicate_wild_do_table' },
 
1361
         replicate_wild_ignore_table => { src => 'replicate_wild_ignore_table' },
 
1362
         skip_counter                => { src => 'skip_counter' },
 
1363
         slave_sql_running           => { src => 'slave_sql_running', hdr => 'On?' },
 
1364
         until_condition             => { src => 'until_condition' },
 
1365
         until_log_file              => { src => 'until_log_file' },
 
1366
         until_log_pos               => { src => 'until_log_pos' },
 
1367
         time_behind_master          => { src => 'seconds_behind_master', trans => [ qw(secs_to_time) ] },
 
1368
         bytes_behind_master         => { src => 'master_log_file && master_log_file eq relay_master_log_file ? read_master_log_pos - exec_master_log_pos : 0', trans => [qw(shorten)] },
 
1369
         slave_catchup_rate          => { src => $exprs{SlaveCatchupRate}, trans => [ qw(set_precision) ] },
 
1370
         slave_open_temp_tables      => { src => 'Slave_open_temp_tables' },
 
1371
      },
 
1372
      visible => [ qw(cxn master_host slave_sql_running time_behind_master slave_catchup_rate slave_open_temp_tables relay_log_pos last_error)],
 
1373
      filters => [ qw( cxn_is_slave ) ],
 
1374
      sort_cols => 'slave_sql_running cxn',
 
1375
      sort_dir => '1',
 
1376
      innodb   => '',
 
1377
      colors   => [
 
1378
         { col => 'slave_sql_running',  op => 'ne', arg => 'Yes', color => 'black on_red' },
 
1379
         { col => 'time_behind_master', op => '>',  arg => 600,   color => 'red' },
 
1380
         { col => 'time_behind_master', op => '>',  arg => 60,    color => 'yellow' },
 
1381
         { col => 'time_behind_master', op => '==', arg => 0,     color => 'white' },
 
1382
      ],
 
1383
      group_by => [],
 
1384
      aggregate => 0,
 
1385
   },
 
1386
   t_header => {
 
1387
      capt => 'T-Mode Header',
 
1388
      cust => {},
 
1389
      cols => {
 
1390
         cxn                         => { src => 'cxn' },
 
1391
         dirty_bufs                  => { src => $exprs{DirtyBufs},           trans => [qw(percent)] },
 
1392
         history_list_len            => { src => 'IB_tx_history_list_len' },
 
1393
         lock_structs                => { src => 'IB_tx_num_lock_structs' },
 
1394
         num_txns                    => { src => $exprs{NumTxns} },
 
1395
         max_txn                     => { src => $exprs{MaxTxnTime},          trans => [qw(secs_to_time)] },
 
1396
         undo_for                    => { src => 'IB_tx_purge_undo_for' },
 
1397
         used_bufs                   => { src => $exprs{BufPoolFill},         trans => [qw(percent)]},
 
1398
         versions                    => { src => $exprs{OldVersions} },
 
1399
      },
 
1400
      visible => [ qw(cxn history_list_len versions undo_for dirty_bufs used_bufs num_txns max_txn lock_structs)],
 
1401
      filters => [ ],
 
1402
      sort_cols => 'cxn',
 
1403
      sort_dir => '1',
 
1404
      innodb   => '',
 
1405
      colors   => [],
 
1406
      hide_caption => 1,
 
1407
      group_by => [],
 
1408
      aggregate => 0,
 
1409
   },
 
1410
   var_status => {
 
1411
      capt      => 'Variables & Status',
 
1412
      cust      => {},
 
1413
      cols      => {}, # Generated from current varset
 
1414
      visible   => [], # Generated from current varset
 
1415
      filters   => [],
 
1416
      sort_cols => '',
 
1417
      sort_dir  => 1,
 
1418
      innodb    => '',
 
1419
      temp      => 1, # Do not persist to config file.
 
1420
      hide_caption  => 1,
 
1421
      pivot     => 0,
 
1422
      group_by => [],
 
1423
      aggregate => 0,
 
1424
   },
 
1425
   wait_array => {
 
1426
      capt => 'InnoDB Wait Array',
 
1427
      cust => {},
 
1428
      cols => {
 
1429
         cxn                => { src => 'cxn' },
 
1430
         thread             => { src => 'thread' },
 
1431
         waited_at_filename => { src => 'waited_at_filename' },
 
1432
         waited_at_line     => { src => 'waited_at_line' },
 
1433
         'time'             => { src => 'waited_secs', trans => [ qw(secs_to_time) ] },
 
1434
         request_type       => { src => 'request_type' },
 
1435
         lock_mem_addr      => { src => 'lock_mem_addr' },
 
1436
         lock_cfile_name    => { src => 'lock_cfile_name' },
 
1437
         lock_cline         => { src => 'lock_cline' },
 
1438
         writer_thread      => { src => 'writer_thread' },
 
1439
         writer_lock_mode   => { src => 'writer_lock_mode' },
 
1440
         num_readers        => { src => 'num_readers' },
 
1441
         lock_var           => { src => 'lock_var' },
 
1442
         waiters_flag       => { src => 'waiters_flag' },
 
1443
         last_s_file_name   => { src => 'last_s_file_name' },
 
1444
         last_s_line        => { src => 'last_s_line' },
 
1445
         last_x_file_name   => { src => 'last_x_file_name' },
 
1446
         last_x_line        => { src => 'last_x_line' },
 
1447
         cell_waiting       => { src => 'cell_waiting' },
 
1448
         cell_event_set     => { src => 'cell_event_set' },
 
1449
      },
 
1450
      visible => [ qw(cxn thread time waited_at_filename waited_at_line request_type num_readers lock_var waiters_flag cell_waiting cell_event_set)],
 
1451
      filters => [],
 
1452
      sort_cols => 'cxn -time',
 
1453
      sort_dir => '1',
 
1454
      innodb   => 'sm',
 
1455
      group_by => [],
 
1456
      aggregate => 0,
 
1457
   },
 
1458
);
 
1459
 
 
1460
# Initialize %tbl_meta from %columns and do some checks.
 
1461
foreach my $table_name ( keys %tbl_meta ) {
 
1462
   my $table = $tbl_meta{$table_name};
 
1463
   my $cols  = $table->{cols};
 
1464
 
 
1465
   foreach my $col_name ( keys %$cols ) {
 
1466
      my $col_def = $table->{cols}->{$col_name};
 
1467
      die "I can't find a column named '$col_name' for '$table_name'" unless $columns{$col_name};
 
1468
      $columns{$col_name}->{referenced} = 1;
 
1469
 
 
1470
      foreach my $prop ( keys %col_props ) {
 
1471
         # Each column gets non-existing values set from %columns or defaults from %col_props.
 
1472
         if ( !$col_def->{$prop} ) {
 
1473
            $col_def->{$prop}
 
1474
               = defined($columns{$col_name}->{$prop})
 
1475
               ? $columns{$col_name}->{$prop}
 
1476
               : $col_props{$prop};
 
1477
         }
 
1478
      }
 
1479
 
 
1480
      # Ensure transformations and aggregate functions are valid
 
1481
      die "Unknown aggregate function '$col_def->{agg}' "
 
1482
         . "for column '$col_name' in table '$table_name'"
 
1483
         unless exists $agg_funcs{$col_def->{agg}};
 
1484
      foreach my $trans ( @{$col_def->{trans}} ) {
 
1485
         die "Unknown transformation '$trans' "
 
1486
            . "for column '$col_name' in table '$table_name'"
 
1487
            unless exists $trans_funcs{$trans};
 
1488
      }
 
1489
   }
 
1490
 
 
1491
   # Ensure each column in visible and group_by exists in cols
 
1492
   foreach my $place ( qw(visible group_by) ) {
 
1493
      foreach my $col_name ( @{$table->{$place}} ) {
 
1494
         if ( !exists $cols->{$col_name} ) {
 
1495
            die "Column '$col_name' is listed in '$place' for '$table_name', but doesn't exist";
 
1496
         }
 
1497
      }
 
1498
   }
 
1499
 
 
1500
   # Compile sort and color subroutines
 
1501
   $table->{sort_func}  = make_sort_func($table);
 
1502
   $table->{color_func} = make_color_func($table);
 
1503
}
 
1504
 
 
1505
# This is for code cleanup:
 
1506
{
 
1507
   my @unused_cols = grep { !$columns{$_}->{referenced} } sort keys %columns;
 
1508
   if ( @unused_cols ) {
 
1509
      die "The following columns are not used: "
 
1510
         . join(' ', @unused_cols);
 
1511
   }
 
1512
}
 
1513
 
 
1514
# ###########################################################################
 
1515
# Operating modes {{{3
 
1516
# ###########################################################################
 
1517
my %modes = (
 
1518
   B => {
 
1519
      hdr               => 'InnoDB Buffers',
 
1520
      cust              => {},
 
1521
      note              => 'Shows buffer info from InnoDB',
 
1522
      action_for        => {
 
1523
         i => {
 
1524
            action => sub { toggle_config('status_inc') },
 
1525
            label  => 'Toggle incremental status display',
 
1526
         },
 
1527
      },
 
1528
      display_sub       => \&display_B,
 
1529
      connections       => [],
 
1530
      server_group      => '',
 
1531
      one_connection    => 0,
 
1532
      tables            => [qw(buffer_pool page_statistics insert_buffers adaptive_hash_index)],
 
1533
      visible_tables    => [qw(buffer_pool page_statistics insert_buffers adaptive_hash_index)],
 
1534
   },
 
1535
   C => {
 
1536
      hdr               => 'Command Summary',
 
1537
      cust              => {},
 
1538
      note              => 'Shows relative magnitude of variables',
 
1539
      action_for        => {
 
1540
         s => {
 
1541
            action => sub { get_config_interactive('cmd_filter') },
 
1542
            label  => 'Choose variable prefix',
 
1543
         },
 
1544
      },
 
1545
      display_sub       => \&display_C,
 
1546
      connections       => [],
 
1547
      server_group      => '',
 
1548
      one_connection    => 0,
 
1549
      tables            => [qw(cmd_summary)],
 
1550
      visible_tables    => [qw(cmd_summary)],
 
1551
   },
 
1552
   D => {
 
1553
      hdr               => 'InnoDB Deadlocks',
 
1554
      cust              => {},
 
1555
      note              => 'View InnoDB deadlock information',
 
1556
      action_for        => {
 
1557
         c => {
 
1558
            action => sub { edit_table('deadlock_transactions') },
 
1559
            label  => 'Choose visible columns',
 
1560
         },
 
1561
         w => {
 
1562
            action => \&create_deadlock,
 
1563
            label  => 'Wipe deadlock status info by creating a deadlock',
 
1564
         },
 
1565
      },
 
1566
      display_sub       => \&display_D,
 
1567
      connections       => [],
 
1568
      server_group      => '',
 
1569
      one_connection    => 0,
 
1570
      tables            => [qw(deadlock_transactions deadlock_locks)],
 
1571
      visible_tables    => [qw(deadlock_transactions deadlock_locks)],
 
1572
   },
 
1573
   F => {
 
1574
      hdr               => 'InnoDB FK Err',
 
1575
      cust              => {},
 
1576
      note              => 'View the latest InnoDB foreign key error',
 
1577
      action_for        => {},
 
1578
      display_sub       => \&display_F,
 
1579
      connections       => [],
 
1580
      server_group      => '',
 
1581
      one_connection    => 1,
 
1582
      tables            => [qw(fk_error)],
 
1583
      visible_tables    => [qw(fk_error)],
 
1584
   },
 
1585
   I => {
 
1586
      hdr               => 'InnoDB I/O Info',
 
1587
      cust              => {},
 
1588
      note              => 'Shows I/O info (i/o, log...) from InnoDB',
 
1589
      action_for        => {
 
1590
         i => {
 
1591
            action => sub { toggle_config('status_inc') },
 
1592
            label  => 'Toggle incremental status display',
 
1593
         },
 
1594
      },
 
1595
      display_sub       => \&display_I,
 
1596
      connections       => [],
 
1597
      server_group      => '',
 
1598
      one_connection    => 0,
 
1599
      tables            => [qw(io_threads pending_io file_io_misc log_statistics)],
 
1600
      visible_tables    => [qw(io_threads pending_io file_io_misc log_statistics)],
 
1601
   },
 
1602
   L => {
 
1603
      hdr             => 'Locks',
 
1604
      cust            => {},
 
1605
      note            => 'Shows transaction locks',
 
1606
      action_for      => {
 
1607
         a => {
 
1608
            action => sub { send_cmd_to_servers('CREATE TABLE IF NOT EXISTS test.innodb_lock_monitor(a int) ENGINE=InnoDB', 0, '', []); },
 
1609
            label  => 'Start the InnoDB Lock Monitor',
 
1610
         },
 
1611
         o => {
 
1612
            action => sub { send_cmd_to_servers('DROP TABLE IF EXISTS test.innodb_lock_monitor', 0, '', []); },
 
1613
            label  => 'Stop the InnoDB Lock Monitor',
 
1614
         },
 
1615
      },
 
1616
      display_sub     => \&display_L,
 
1617
      connections     => [],
 
1618
      server_group    => '',
 
1619
      one_connection  => 0,
 
1620
      tables            => [qw(innodb_locks)],
 
1621
      visible_tables    => [qw(innodb_locks)],
 
1622
   },
 
1623
   M => {
 
1624
      hdr               => 'Replication Status',
 
1625
      cust              => {},
 
1626
      note              => 'Shows replication (master and slave) status',
 
1627
      action_for        => {
 
1628
         a => {
 
1629
            action => sub { send_cmd_to_servers('START SLAVE', 0, 'START SLAVE SQL_THREAD UNTIL MASTER_LOG_FILE = ?, MASTER_LOG_POS = ?', []); },
 
1630
            label  => 'Start slave(s)',
 
1631
         },
 
1632
         i => {
 
1633
            action => sub { toggle_config('status_inc') },
 
1634
            label  => 'Toggle incremental status display',
 
1635
         },
 
1636
         o => {
 
1637
            action => sub { send_cmd_to_servers('STOP SLAVE', 0, '', []); },
 
1638
            label  => 'Stop slave(s)',
 
1639
         },
 
1640
         b => {
 
1641
            action => sub { purge_master_logs() },
 
1642
            label  => 'Purge unused master logs',
 
1643
         },
 
1644
      },
 
1645
      display_sub       => \&display_M,
 
1646
      connections       => [],
 
1647
      server_group      => '',
 
1648
      one_connection    => 0,
 
1649
      tables            => [qw(slave_sql_status slave_io_status master_status)],
 
1650
      visible_tables    => [qw(slave_sql_status slave_io_status master_status)],
 
1651
   },
 
1652
   O => {
 
1653
      hdr               => 'Open Tables',
 
1654
      cust              => {},
 
1655
      note              => 'Shows open tables in MySQL',
 
1656
      action_for        => {
 
1657
         r => {
 
1658
            action => sub { reverse_sort('open_tables'); },
 
1659
            label  => 'Reverse sort order',
 
1660
         },
 
1661
         s => {
 
1662
            action => sub { choose_sort_cols('open_tables'); },
 
1663
            label => "Choose sort column",
 
1664
         },
 
1665
      },
 
1666
      display_sub       => \&display_O,
 
1667
      connections       => [],
 
1668
      server_group      => '',
 
1669
      one_connection    => 0,
 
1670
      tables            => [qw(open_tables)],
 
1671
      visible_tables    => [qw(open_tables)],
 
1672
   },
 
1673
   Q => {
 
1674
      hdr        => 'Query List',
 
1675
      cust       => {},
 
1676
      note       => 'Shows queries from SHOW FULL PROCESSLIST',
 
1677
      action_for => {
 
1678
         a => {
 
1679
            action => sub { toggle_filter('processlist', 'hide_self') },
 
1680
            label  => 'Toggle the innotop process',
 
1681
         },
 
1682
         c => {
 
1683
            action => sub { edit_table('processlist') },
 
1684
            label  => 'Choose visible columns',
 
1685
         },
 
1686
         e => {
 
1687
            action => sub { analyze_query('e'); },
 
1688
            label  => "Explain a thread's query",
 
1689
         },
 
1690
         f => {
 
1691
            action => sub { analyze_query('f'); },
 
1692
            label  => "Show a thread's full query",
 
1693
         },
 
1694
         h => {
 
1695
            action => sub { toggle_visible_table('Q', 'q_header') },
 
1696
            label  => 'Toggle the header on and off',
 
1697
         },
 
1698
         i => {
 
1699
            action => sub { toggle_filter('processlist', 'hide_inactive') },
 
1700
            label  => 'Toggle idle processes',
 
1701
         },
 
1702
         k => {
 
1703
            action => sub { kill_query('CONNECTION') },
 
1704
            label => "Kill a query's connection",
 
1705
         },
 
1706
         r => {
 
1707
            action => sub { reverse_sort('processlist'); },
 
1708
            label  => 'Reverse sort order',
 
1709
         },
 
1710
         s => {
 
1711
            action => sub { choose_sort_cols('processlist'); },
 
1712
            label => "Change the display's sort column",
 
1713
         },
 
1714
         x => {
 
1715
            action => sub { kill_query('QUERY') },
 
1716
            label => "Kill a query",
 
1717
         },
 
1718
      },
 
1719
      display_sub       => \&display_Q,
 
1720
      connections       => [],
 
1721
      server_group      => '',
 
1722
      one_connection    => 0,
 
1723
      tables            => [qw(q_header processlist)],
 
1724
      visible_tables    => [qw(q_header processlist)],
 
1725
   },
 
1726
   R => {
 
1727
      hdr               => 'InnoDB Row Ops',
 
1728
      cust              => {},
 
1729
      note              => 'Shows InnoDB row operation and semaphore info',
 
1730
      action_for        => {
 
1731
         i => {
 
1732
            action => sub { toggle_config('status_inc') },
 
1733
            label  => 'Toggle incremental status display',
 
1734
         },
 
1735
      },
 
1736
      display_sub       => \&display_R,
 
1737
      connections       => [],
 
1738
      server_group      => '',
 
1739
      one_connection    => 0,
 
1740
      tables            => [qw(row_operations row_operation_misc semaphores wait_array)],
 
1741
      visible_tables    => [qw(row_operations row_operation_misc semaphores wait_array)],
 
1742
   },
 
1743
   S => {
 
1744
      hdr               => 'Variables & Status',
 
1745
      cust              => {},
 
1746
      note              => 'Shows query load statistics a la vmstat',
 
1747
      action_for        => {
 
1748
         '>' => {
 
1749
            action => sub { switch_var_set('S_set', 1) },
 
1750
            label  => 'Switch to next variable set',
 
1751
         },
 
1752
         '<' => {
 
1753
            action => sub { switch_var_set('S_set', -1) },
 
1754
            label  => 'Switch to prev variable set',
 
1755
         },
 
1756
         c => {
 
1757
            action => sub {
 
1758
               choose_var_set('S_set');
 
1759
               start_S_mode();
 
1760
            },
 
1761
            label => "Choose which set to display",
 
1762
         },
 
1763
         e => {
 
1764
            action => \&edit_current_var_set,
 
1765
            label  => 'Edit the current set of variables',
 
1766
         },
 
1767
         i => {
 
1768
            action => sub { $clear_screen_sub->(); toggle_config('status_inc') },
 
1769
            label  => 'Toggle incremental status display',
 
1770
         },
 
1771
         '-' => {
 
1772
            action => sub { set_display_precision(-1) },
 
1773
            label  => 'Decrease fractional display precision',
 
1774
         },
 
1775
         '+' => {
 
1776
            action => sub { set_display_precision(1) },
 
1777
            label  => 'Increase fractional display precision',
 
1778
         },
 
1779
         g => {
 
1780
            action => sub { set_s_mode('g') },
 
1781
            label  => 'Switch to graph (tload) view',
 
1782
         },
 
1783
         s => {
 
1784
            action => sub { set_s_mode('s') },
 
1785
            label  => 'Switch to standard (vmstat) view',
 
1786
         },
 
1787
         v => {
 
1788
            action => sub { set_s_mode('v') },
 
1789
            label  => 'Switch to pivoted view',
 
1790
         },
 
1791
      },
 
1792
      display_sub       => \&display_S,
 
1793
      no_clear_screen   => 1,
 
1794
      connections       => [],
 
1795
      server_group      => '',
 
1796
      one_connection    => 0,
 
1797
      tables            => [qw(var_status)],
 
1798
      visible_tables    => [qw(var_status)],
 
1799
   },
 
1800
   T => {
 
1801
      hdr        => 'InnoDB Txns',
 
1802
      cust       => {},
 
1803
      note       => 'Shows InnoDB transactions in top-like format',
 
1804
      action_for => {
 
1805
         a => {
 
1806
            action => sub { toggle_filter('innodb_transactions', 'hide_self') },
 
1807
            label  => 'Toggle the innotop process',
 
1808
         },
 
1809
         c => {
 
1810
            action => sub { edit_table('innodb_transactions') },
 
1811
            label  => 'Choose visible columns',
 
1812
         },
 
1813
         e => {
 
1814
            action => sub { analyze_query('e'); },
 
1815
            label  => "Explain a thread's query",
 
1816
         },
 
1817
         f => {
 
1818
            action => sub { analyze_query('f'); },
 
1819
            label  => "Show a thread's full query",
 
1820
         },
 
1821
         h => {
 
1822
            action => sub { toggle_visible_table('T', 't_header') },
 
1823
            label  => 'Toggle the header on and off',
 
1824
         },
 
1825
         i => {
 
1826
            action => sub { toggle_filter('innodb_transactions', 'hide_inactive') },
 
1827
            label  => 'Toggle inactive transactions',
 
1828
         },
 
1829
         k => {
 
1830
            action => sub { kill_query('CONNECTION') },
 
1831
            label  => "Kill a transaction's connection",
 
1832
         },
 
1833
         r => {
 
1834
            action => sub { reverse_sort('innodb_transactions'); },
 
1835
            label  => 'Reverse sort order',
 
1836
         },
 
1837
         s => {
 
1838
            action => sub { choose_sort_cols('innodb_transactions'); },
 
1839
            label  => "Change the display's sort column",
 
1840
         },
 
1841
         x => {
 
1842
            action => sub { kill_query('QUERY') },
 
1843
            label  => "Kill a query",
 
1844
         },
 
1845
      },
 
1846
      display_sub       => \&display_T,
 
1847
      connections       => [],
 
1848
      server_group      => '',
 
1849
      one_connection    => 0,
 
1850
      tables            => [qw(t_header innodb_transactions)],
 
1851
      visible_tables    => [qw(t_header innodb_transactions)],
 
1852
   },
 
1853
);
 
1854
 
 
1855
# ###########################################################################
 
1856
# Global key mappings {{{3
 
1857
# Keyed on a single character, which is read from the keyboard.  Uppercase
 
1858
# letters switch modes.  Lowercase letters access commands when in a mode.
 
1859
# These can be overridden by action_for in %modes.
 
1860
# ###########################################################################
 
1861
my %action_for = (
 
1862
   '$' => {
 
1863
      action => \&edit_configuration,
 
1864
      label  => 'Edit configuration settings',
 
1865
   },
 
1866
   '?' => {
 
1867
      action => \&display_help,
 
1868
      label  => 'Show help',
 
1869
   },
 
1870
   '!' => {
 
1871
      action => \&display_license,
 
1872
      label  => 'Show license and warranty',
 
1873
   },
 
1874
   '^' => {
 
1875
      action => \&edit_table,
 
1876
      label  => "Edit the displayed table(s)",
 
1877
   },
 
1878
   '#' => {
 
1879
      action => \&choose_server_groups,
 
1880
      label  => 'Select/create server groups',
 
1881
   },
 
1882
   '@' => {
 
1883
      action => \&choose_servers,
 
1884
      label  => 'Select/create server connections',
 
1885
   },
 
1886
   '/' => {
 
1887
      action => \&add_quick_filter,
 
1888
      label  => 'Quickly filter what you see',
 
1889
   },
 
1890
   '\\' => {
 
1891
      action => \&clear_quick_filters,
 
1892
      label  => 'Clear quick-filters',
 
1893
   },
 
1894
   '%' => {
 
1895
      action => \&choose_filters,
 
1896
      label  => 'Choose and edit table filters',
 
1897
   },
 
1898
   "\t" => {
 
1899
      action => \&next_server_group,
 
1900
      label  => 'Switch to the next server group',
 
1901
      key    => 'TAB',
 
1902
   },
 
1903
   '=' => {
 
1904
      action => \&toggle_aggregate,
 
1905
      label  => 'Toggle aggregation',
 
1906
   },
 
1907
   # TODO: can these be auto-generated from %modes?
 
1908
   B => {
 
1909
      action => sub { switch_mode('B') },
 
1910
      label  => '',
 
1911
   },
 
1912
   C => {
 
1913
      action => sub { switch_mode('C') },
 
1914
      label  => '',
 
1915
   },
 
1916
   D => {
 
1917
      action => sub { switch_mode('D') },
 
1918
      label  => '',
 
1919
   },
 
1920
   F => {
 
1921
      action => sub { switch_mode('F') },
 
1922
      label  => '',
 
1923
   },
 
1924
   I => {
 
1925
      action => sub { switch_mode('I') },
 
1926
      label  => '',
 
1927
   },
 
1928
   L => {
 
1929
      action => sub { switch_mode('L') },
 
1930
      label  => '',
 
1931
   },
 
1932
   M => {
 
1933
      action => sub { switch_mode('M') },
 
1934
      label  => '',
 
1935
   },
 
1936
   O => {
 
1937
      action => sub { switch_mode('O') },
 
1938
      label  => '',
 
1939
   },
 
1940
   Q => {
 
1941
      action => sub { switch_mode('Q') },
 
1942
      label  => '',
 
1943
   },
 
1944
   R => {
 
1945
      action => sub { switch_mode('R') },
 
1946
      label  => '',
 
1947
   },
 
1948
   S => {
 
1949
      action => \&start_S_mode,
 
1950
      label  => '',
 
1951
   },
 
1952
   T => {
 
1953
      action => sub { switch_mode('T') },
 
1954
      label  => '',
 
1955
   },
 
1956
   d => {
 
1957
      action => sub { get_config_interactive('interval') },
 
1958
      label  => 'Change refresh interval',
 
1959
   },
 
1960
   n => { action => \&next_server,       label => 'Switch to the next connection' },
 
1961
   p => { action => \&pause,             label => 'Pause innotop', },
 
1962
   q => { action => \&finish,            label => 'Quit innotop', },
 
1963
);
 
1964
 
 
1965
# ###########################################################################
 
1966
# Sleep times after certain statements {{{3
 
1967
# ###########################################################################
 
1968
my %stmt_sleep_time_for = ();
 
1969
 
 
1970
# ###########################################################################
 
1971
# Config editor key mappings {{{3
 
1972
# ###########################################################################
 
1973
my %cfg_editor_action = (
 
1974
   c => {
 
1975
      note => 'Edit columns, etc in the displayed table(s)',
 
1976
      func => \&edit_table,
 
1977
   },
 
1978
   g => {
 
1979
      note => 'Edit general configuration',
 
1980
      func => \&edit_configuration_variables,
 
1981
   },
 
1982
   k => {
 
1983
      note => 'Edit row-coloring rules',
 
1984
      func => \&edit_color_rules,
 
1985
   },
 
1986
   p => {
 
1987
      note => 'Manage plugins',
 
1988
      func => \&edit_plugins,
 
1989
   },
 
1990
   s => {
 
1991
      note => 'Edit server groups',
 
1992
      func => \&edit_server_groups,
 
1993
   },
 
1994
   S => {
 
1995
      note => 'Edit SQL statement sleep delays',
 
1996
      func => \&edit_stmt_sleep_times,
 
1997
   },
 
1998
   t => {
 
1999
      note => 'Choose which table(s) to display in this mode',
 
2000
      func => \&choose_mode_tables,
 
2001
   },
 
2002
);
 
2003
 
 
2004
# ###########################################################################
 
2005
# Color editor key mappings {{{3
 
2006
# ###########################################################################
 
2007
my %color_editor_action = (
 
2008
   n => {
 
2009
      note => 'Create a new color rule',
 
2010
      func => sub {
 
2011
         my ( $tbl, $idx ) = @_;
 
2012
         my $meta = $tbl_meta{$tbl};
 
2013
 
 
2014
         $clear_screen_sub->();
 
2015
         my $col;
 
2016
         do {
 
2017
            $col = prompt_list(
 
2018
               'Choose the target column for the rule',
 
2019
               '',
 
2020
               sub { return keys %{$meta->{cols}} },
 
2021
               { map { $_ => $meta->{cols}->{$_}->{label} } keys %{$meta->{cols}} });
 
2022
         } while ( !$col );
 
2023
         ( $col ) = grep { $_ } split(/\W+/, $col);
 
2024
         return $idx unless $col && exists $meta->{cols}->{$col};
 
2025
 
 
2026
         $clear_screen_sub->();
 
2027
         my $op;
 
2028
         do {
 
2029
            $op = prompt_list(
 
2030
               'Choose the comparison operator for the rule',
 
2031
               '',
 
2032
               sub { return keys %comp_ops },
 
2033
               { map { $_ => $comp_ops{$_} } keys %comp_ops } );
 
2034
         } until ( $op );
 
2035
         $op =~ s/\s+//g;
 
2036
         return $idx unless $op && exists $comp_ops{$op};
 
2037
 
 
2038
         my $arg;
 
2039
         do {
 
2040
            $arg = prompt('Specify an argument for the comparison');
 
2041
         } until defined $arg;
 
2042
 
 
2043
         my $color;
 
2044
         do {
 
2045
            $color = prompt_list(
 
2046
               'Choose the color(s) the row should be when the rule matches',
 
2047
               '',
 
2048
               sub { return keys %ansicolors },
 
2049
               { map { $_ => $_ } keys %ansicolors } );
 
2050
         } until defined $color;
 
2051
         $color = join(' ', unique(grep { exists $ansicolors{$_} } split(/\W+/, $color)));
 
2052
         return $idx unless $color;
 
2053
 
 
2054
         push @{$tbl_meta{$tbl}->{colors}}, {
 
2055
            col   => $col,
 
2056
            op    => $op,
 
2057
            arg   => $arg,
 
2058
            color => $color
 
2059
         };
 
2060
         $tbl_meta{$tbl}->{cust}->{colors} = 1;
 
2061
 
 
2062
         return $idx;
 
2063
      },
 
2064
   },
 
2065
   d => {
 
2066
      note => 'Remove the selected rule',
 
2067
      func => sub {
 
2068
         my ( $tbl, $idx ) = @_;
 
2069
         my @rules = @{ $tbl_meta{$tbl}->{colors} };
 
2070
         return 0 unless @rules > 0 && $idx < @rules && $idx >= 0;
 
2071
         splice(@{$tbl_meta{$tbl}->{colors}}, $idx, 1);
 
2072
         $tbl_meta{$tbl}->{cust}->{colors} = 1;
 
2073
         return $idx == @rules ? $#rules : $idx;
 
2074
      },
 
2075
   },
 
2076
   j => {
 
2077
      note => 'Move highlight down one',
 
2078
      func => sub {
 
2079
         my ( $tbl, $idx ) = @_;
 
2080
         my $num_rules = scalar @{$tbl_meta{$tbl}->{colors}};
 
2081
         return ($idx + 1) % $num_rules;
 
2082
      },
 
2083
   },
 
2084
   k => {
 
2085
      note => 'Move highlight up one',
 
2086
      func => sub {
 
2087
         my ( $tbl, $idx ) = @_;
 
2088
         my $num_rules = scalar @{$tbl_meta{$tbl}->{colors}};
 
2089
         return ($idx - 1) % $num_rules;
 
2090
      },
 
2091
   },
 
2092
   '+' => {
 
2093
      note => 'Move selected rule up one',
 
2094
      func => sub {
 
2095
         my ( $tbl, $idx ) = @_;
 
2096
         my $meta = $tbl_meta{$tbl};
 
2097
         my $dest = $idx == 0 ? scalar(@{$meta->{colors}} - 1) : $idx - 1;
 
2098
         my $temp = $meta->{colors}->[$idx];
 
2099
         $meta->{colors}->[$idx]  = $meta->{colors}->[$dest];
 
2100
         $meta->{colors}->[$dest] = $temp;
 
2101
         $meta->{cust}->{colors} = 1;
 
2102
         return $dest;
 
2103
      },
 
2104
   },
 
2105
   '-' => {
 
2106
      note => 'Move selected rule down one',
 
2107
      func => sub {
 
2108
         my ( $tbl, $idx ) = @_;
 
2109
         my $meta = $tbl_meta{$tbl};
 
2110
         my $dest = $idx == scalar(@{$meta->{colors}} - 1) ? 0 : $idx + 1;
 
2111
         my $temp = $meta->{colors}->[$idx];
 
2112
         $meta->{colors}->[$idx]  = $meta->{colors}->[$dest];
 
2113
         $meta->{colors}->[$dest] = $temp;
 
2114
         $meta->{cust}->{colors} = 1;
 
2115
         return $dest;
 
2116
      },
 
2117
   },
 
2118
);
 
2119
 
 
2120
# ###########################################################################
 
2121
# Plugin editor key mappings {{{3
 
2122
# ###########################################################################
 
2123
my %plugin_editor_action = (
 
2124
   '*' => {
 
2125
      note => 'Toggle selected plugin active/inactive',
 
2126
      func => sub {
 
2127
         my ( $plugins, $idx ) = @_;
 
2128
         my $plugin = $plugins->[$idx];
 
2129
         $plugin->{active} = $plugin->{active} ? 0 : 1;
 
2130
         return $idx;
 
2131
      },
 
2132
   },
 
2133
   j => {
 
2134
      note => 'Move highlight down one',
 
2135
      func => sub {
 
2136
         my ( $plugins, $idx ) = @_;
 
2137
         return ($idx + 1) % scalar(@$plugins);
 
2138
      },
 
2139
   },
 
2140
   k => {
 
2141
      note => 'Move highlight up one',
 
2142
      func => sub {
 
2143
         my ( $plugins, $idx ) = @_;
 
2144
         return $idx == 0 ? @$plugins - 1 : $idx - 1;
 
2145
      },
 
2146
   },
 
2147
);
 
2148
 
 
2149
# ###########################################################################
 
2150
# Table editor key mappings {{{3
 
2151
# ###########################################################################
 
2152
my %tbl_editor_action = (
 
2153
   a => {
 
2154
      note => 'Add a column to the table',
 
2155
      func => sub {
 
2156
         my ( $tbl, $col ) = @_;
 
2157
         my @visible_cols = @{ $tbl_meta{$tbl}->{visible} };
 
2158
         my %all_cols     = %{ $tbl_meta{$tbl}->{cols} };
 
2159
         delete @all_cols{@visible_cols};
 
2160
         my $choice = prompt_list(
 
2161
            'Choose a column',
 
2162
            '',
 
2163
            sub { return keys %all_cols; },
 
2164
            { map { $_ => $all_cols{$_}->{label} || $all_cols{$_}->{hdr} } keys %all_cols });
 
2165
         if ( $all_cols{$choice} ) {
 
2166
            push @{$tbl_meta{$tbl}->{visible}}, $choice;
 
2167
            $tbl_meta{$tbl}->{cust}->{visible} = 1;
 
2168
            return $choice;
 
2169
         }
 
2170
         return $col;
 
2171
      },
 
2172
   },
 
2173
   n => {
 
2174
      note => 'Create a new column and add it to the table',
 
2175
      func => sub {
 
2176
         my ( $tbl, $col ) = @_;
 
2177
 
 
2178
         $clear_screen_sub->();
 
2179
         print word_wrap("Choose a name for the column.  This name is not displayed, and is used only "
 
2180
               . "for internal reference.  It can contain only lowercase letters, numbers, "
 
2181
               . "and underscores.");
 
2182
         print "\n\n";
 
2183
         do {
 
2184
            $col = prompt("Enter column name");
 
2185
            $col = '' if $col =~ m/[^a-z0-9_]/;
 
2186
         } while ( !$col );
 
2187
 
 
2188
         $clear_screen_sub->();
 
2189
         my $hdr;
 
2190
         do {
 
2191
            $hdr = prompt("Enter column header");
 
2192
         } while ( !$hdr );
 
2193
 
 
2194
         $clear_screen_sub->();
 
2195
         print "Choose a source for the column's data\n\n";
 
2196
         my ( $src, $sub, $err );
 
2197
         do {
 
2198
            if ( $err ) {
 
2199
               print "Error: $err\n\n";
 
2200
            }
 
2201
            $src = prompt("Enter column source");
 
2202
            if ( $src ) {
 
2203
               ( $sub, $err ) = compile_expr($src);
 
2204
            }
 
2205
         } until ( !$err);
 
2206
 
 
2207
         # TODO: this duplicates %col_props.
 
2208
         $tbl_meta{$tbl}->{cols}->{$col} = {
 
2209
            hdr   => $hdr,
 
2210
            src   => $src,
 
2211
            just  => '-',
 
2212
            num   => 0,
 
2213
            label => 'User-defined',
 
2214
            user  => 1,
 
2215
            tbl   => $tbl,
 
2216
            minw  => 0,
 
2217
            maxw  => 0,
 
2218
            trans => [],
 
2219
            func  => $sub,
 
2220
            dec   => 0,
 
2221
            agg   => 0,
 
2222
            aggonly => 0,
 
2223
         };
 
2224
 
 
2225
         $tbl_meta{$tbl}->{visible} = [ unique(@{$tbl_meta{$tbl}->{visible}}, $col) ];
 
2226
         $tbl_meta{$tbl}->{cust}->{visible} = 1;
 
2227
         return $col;
 
2228
      },
 
2229
   },
 
2230
   d => {
 
2231
      note => 'Remove selected column',
 
2232
      func => sub {
 
2233
         my ( $tbl, $col ) = @_;
 
2234
         my @visible_cols = @{ $tbl_meta{$tbl}->{visible} };
 
2235
         my $idx          = 0;
 
2236
         return $col unless @visible_cols > 1;
 
2237
         while ( $visible_cols[$idx] ne $col ) {
 
2238
            $idx++;
 
2239
         }
 
2240
         $tbl_meta{$tbl}->{visible} = [ grep { $_ ne $col } @visible_cols ];
 
2241
         $tbl_meta{$tbl}->{cust}->{visible} = 1;
 
2242
         return $idx == $#visible_cols ? $visible_cols[$idx - 1] : $visible_cols[$idx + 1];
 
2243
      },
 
2244
   },
 
2245
   e => {
 
2246
      note => 'Edit selected column',
 
2247
      func => sub {
 
2248
         # TODO: make this editor hotkey-driven and give readline support.
 
2249
         my ( $tbl, $col ) = @_;
 
2250
         $clear_screen_sub->();
 
2251
         my $meta = $tbl_meta{$tbl}->{cols}->{$col};
 
2252
         my @prop = qw(hdr label src just num minw maxw trans agg); # TODO redundant
 
2253
 
 
2254
         my $answer;
 
2255
         do {
 
2256
            # Do what the user asked...
 
2257
            if ( $answer && grep { $_ eq $answer } @prop ) {
 
2258
               # Some properties are arrays, others scalars.
 
2259
               my $ini = ref $col_props{$answer} ? join(' ', @{$meta->{$answer}}) : $meta->{$answer};
 
2260
               my $val = prompt("New value for $answer", undef, $ini);
 
2261
               $val = [ split(' ', $val) ] if ref($col_props{$answer});
 
2262
               if ( $answer eq 'trans' ) {
 
2263
                  $val = [ unique(grep{ exists $trans_funcs{$_} } @$val) ];
 
2264
               }
 
2265
               @{$meta}{$answer, 'user', 'tbl' } = ( $val, 1, $tbl );
 
2266
            }
 
2267
 
 
2268
            my @display_lines = (
 
2269
               '',
 
2270
               "You are editing column $tbl.$col.\n",
 
2271
            );
 
2272
 
 
2273
            push @display_lines, create_table2(
 
2274
               \@prop,
 
2275
               { map { $_ => $_ } @prop },
 
2276
               { map { $_ => ref $meta->{$_} eq 'ARRAY' ? join(' ', @{$meta->{$_}})
 
2277
                           : ref $meta->{$_}            ? '[expression code]'
 
2278
                           :                              $meta->{$_}
 
2279
                     } @prop
 
2280
               },
 
2281
               { sep => '  ' });
 
2282
            draw_screen(\@display_lines, { raw => 1 });
 
2283
            print "\n\n"; # One to add space, one to clear readline artifacts
 
2284
            $answer = prompt('Edit what? (q to quit)');
 
2285
         } while ( $answer ne 'q' );
 
2286
 
 
2287
         return $col;
 
2288
      },
 
2289
   },
 
2290
   j => {
 
2291
      note => 'Move highlight down one',
 
2292
      func => sub {
 
2293
         my ( $tbl, $col ) = @_;
 
2294
         my @visible_cols = @{ $tbl_meta{$tbl}->{visible} };
 
2295
         my $idx          = 0;
 
2296
         while ( $visible_cols[$idx] ne $col ) {
 
2297
            $idx++;
 
2298
         }
 
2299
         return $visible_cols[ ($idx + 1) % @visible_cols ];
 
2300
      },
 
2301
   },
 
2302
   k => {
 
2303
      note => 'Move highlight up one',
 
2304
      func => sub {
 
2305
         my ( $tbl, $col ) = @_;
 
2306
         my @visible_cols = @{ $tbl_meta{$tbl}->{visible} };
 
2307
         my $idx          = 0;
 
2308
         while ( $visible_cols[$idx] ne $col ) {
 
2309
            $idx++;
 
2310
         }
 
2311
         return $visible_cols[ $idx - 1 ];
 
2312
      },
 
2313
   },
 
2314
   '+' => {
 
2315
      note => 'Move selected column up one',
 
2316
      func => sub {
 
2317
         my ( $tbl, $col ) = @_;
 
2318
         my $meta         = $tbl_meta{$tbl};
 
2319
         my @visible_cols = @{$meta->{visible}};
 
2320
         my $idx          = 0;
 
2321
         while ( $visible_cols[$idx] ne $col ) {
 
2322
            $idx++;
 
2323
         }
 
2324
         if ( $idx ) {
 
2325
            $visible_cols[$idx]     = $visible_cols[$idx - 1];
 
2326
            $visible_cols[$idx - 1] = $col;
 
2327
            $meta->{visible}        = \@visible_cols;
 
2328
         }
 
2329
         else {
 
2330
            shift @{$meta->{visible}};
 
2331
            push @{$meta->{visible}}, $col;
 
2332
         }
 
2333
         $meta->{cust}->{visible} = 1;
 
2334
         return $col;
 
2335
      },
 
2336
   },
 
2337
   '-' => {
 
2338
      note => 'Move selected column down one',
 
2339
      func => sub {
 
2340
         my ( $tbl, $col ) = @_;
 
2341
         my $meta         = $tbl_meta{$tbl};
 
2342
         my @visible_cols = @{$meta->{visible}};
 
2343
         my $idx          = 0;
 
2344
         while ( $visible_cols[$idx] ne $col ) {
 
2345
            $idx++;
 
2346
         }
 
2347
         if ( $idx == $#visible_cols ) {
 
2348
            unshift @{$meta->{visible}}, $col;
 
2349
            pop @{$meta->{visible}};
 
2350
         }
 
2351
         else {
 
2352
            $visible_cols[$idx]     = $visible_cols[$idx + 1];
 
2353
            $visible_cols[$idx + 1] = $col;
 
2354
            $meta->{visible}        = \@visible_cols;
 
2355
         }
 
2356
         $meta->{cust}->{visible} = 1;
 
2357
         return $col;
 
2358
      },
 
2359
   },
 
2360
   f => {
 
2361
      note => 'Choose filters',
 
2362
      func => sub {
 
2363
         my ( $tbl, $col ) = @_;
 
2364
         choose_filters($tbl);
 
2365
         return $col;
 
2366
      },
 
2367
   },
 
2368
   o => {
 
2369
      note => 'Edit color rules',
 
2370
      func => sub {
 
2371
         my ( $tbl, $col ) = @_;
 
2372
         edit_color_rules($tbl);
 
2373
         return $col;
 
2374
      },
 
2375
   },
 
2376
   s => {
 
2377
      note => 'Choose sort columns',
 
2378
      func => sub {
 
2379
         my ( $tbl, $col ) = @_;
 
2380
         choose_sort_cols($tbl);
 
2381
         return $col;
 
2382
      },
 
2383
   },
 
2384
   g => {
 
2385
      note => 'Choose group-by (aggregate) columns',
 
2386
      func => sub {
 
2387
         my ( $tbl, $col ) = @_;
 
2388
         choose_group_cols($tbl);
 
2389
         return $col;
 
2390
      },
 
2391
   },
 
2392
);
 
2393
 
 
2394
# ###########################################################################
 
2395
# Global variables and environment {{{2
 
2396
# ###########################################################################
 
2397
 
 
2398
my @this_term_size; # w_chars, h_chars, w_pix, h_pix
 
2399
my @last_term_size; # w_chars, h_chars, w_pix, h_pix
 
2400
my $char;
 
2401
my $windows       = $OSNAME =~ m/MSWin/;
 
2402
my $have_color    = 0;
 
2403
my $MAX_ULONG     = 4294967295; # 2^32-1
 
2404
my $num_regex     = qr/^[+-]?(?=\d|\.)\d*(?:\.\d+)?(?:E[+-]?\d+|)$/i;
 
2405
my $int_regex     = qr/^\d+$/;
 
2406
my $bool_regex    = qr/^[01]$/;
 
2407
my $term          = undef;
 
2408
my $file          = undef; # File to watch for InnoDB monitor output
 
2409
my $file_mtime    = undef; # Status of watched file
 
2410
my $file_data     = undef; # Last chunk of text read from file
 
2411
my $innodb_parser = InnoDBParser->new;
 
2412
 
 
2413
my $nonfatal_errs = join('|',
 
2414
   'Access denied for user',
 
2415
   'Unknown MySQL server host',
 
2416
   'Unknown database',
 
2417
   'Can\'t connect to local MySQL server through socket',
 
2418
   'Can\'t connect to MySQL server on',
 
2419
   'MySQL server has gone away',
 
2420
   'Cannot call SHOW INNODB STATUS',
 
2421
   'Access denied',
 
2422
   'AutoCommit',
 
2423
);
 
2424
 
 
2425
if ( !$opts{n} ) {
 
2426
   require Term::ReadLine;
 
2427
   $term = Term::ReadLine->new('innotop');
 
2428
}
 
2429
 
 
2430
# Stores status, variables, innodb status, master/slave status etc.
 
2431
# Keyed on connection name.  Each entry is a hashref of current and past data sets,
 
2432
# keyed on clock tick.
 
2433
my %vars;
 
2434
my %info_gotten = (); # Which things have been retrieved for the current clock tick.
 
2435
 
 
2436
# Stores info on currently displayed queries: cxn, connection ID, query text.
 
2437
my @current_queries;
 
2438
 
 
2439
my $lines_printed       = 0;
 
2440
my $clock               = 0;   # Incremented with every wake-sleep cycle
 
2441
my $clearing_deadlocks  = 0;
 
2442
 
 
2443
# Find the home directory; it's different on different OSes.
 
2444
my $homepath = $ENV{HOME} || $ENV{HOMEPATH} || $ENV{USERPROFILE} || '.';
 
2445
 
 
2446
# If terminal coloring is available, use it.  The only function I want from
 
2447
# the module is the colored() function.
 
2448
eval {
 
2449
   if ( !$opts{n} ) {
 
2450
      if ( $windows ) {
 
2451
         require Win32::Console::ANSI;
 
2452
      }
 
2453
      require Term::ANSIColor;
 
2454
      import Term::ANSIColor qw(colored);
 
2455
      $have_color = 1;
 
2456
   }
 
2457
};
 
2458
if ( $EVAL_ERROR || $opts{n} ) {
 
2459
   # If there was an error, manufacture my own colored() function that does no
 
2460
   # coloring.
 
2461
   *colored = sub { pop @_; @_; };
 
2462
}
 
2463
 
 
2464
if ( $opts{n} ) {
 
2465
   $clear_screen_sub = sub {};
 
2466
}
 
2467
elsif ( $windows ) {
 
2468
   $clear_screen_sub = sub { $lines_printed = 0; system("cls") };
 
2469
}
 
2470
else {
 
2471
   my $clear = `clear`;
 
2472
   $clear_screen_sub = sub { $lines_printed = 0; print $clear };
 
2473
}
 
2474
 
 
2475
# ###########################################################################
 
2476
# Config storage. {{{2
 
2477
# ###########################################################################
 
2478
my %config = (
 
2479
   color => {
 
2480
      val  => $have_color,
 
2481
      note => 'Whether to use terminal coloring',
 
2482
      conf => 'ALL',
 
2483
      pat  => $bool_regex,
 
2484
   },
 
2485
   cmd_filter => {
 
2486
      val  => 'Com_',
 
2487
      note => 'Prefix for values in C mode',
 
2488
      conf => [qw(C)],
 
2489
   },
 
2490
   plugin_dir => {
 
2491
      val  => "$homepath/.innotop/plugins",
 
2492
      note => 'Directory where plugins can be found',
 
2493
      conf => 'ALL',
 
2494
   },
 
2495
   show_percent => {
 
2496
      val  => 1,
 
2497
      note => 'Show the % symbol after percentages',
 
2498
      conf => 'ALL',
 
2499
      pat  => $bool_regex,
 
2500
   },
 
2501
   skip_innodb => {
 
2502
      val  => 0,
 
2503
      note => 'Disable SHOW INNODB STATUS',
 
2504
      conf => 'ALL',
 
2505
      pat  => $bool_regex,
 
2506
   },
 
2507
   S_func => {
 
2508
      val  => 's',
 
2509
      note => 'What to display in S mode: graph, status, pivoted status',
 
2510
      conf => [qw(S)],
 
2511
      pat  => qr/^[gsv]$/,
 
2512
   },
 
2513
   cxn_timeout => {
 
2514
      val  => 28800,
 
2515
      note => 'Connection timeout for keeping unused connections alive',
 
2516
      conf => 'ALL',
 
2517
      pat  => $int_regex,
 
2518
   },
 
2519
   graph_char => {
 
2520
      val  => '*',
 
2521
      note => 'Character for drawing graphs',
 
2522
      conf => [ qw(S) ],
 
2523
      pat  => qr/^.$/,
 
2524
   },
 
2525
   show_cxn_errors_in_tbl => {
 
2526
      val  => 1,
 
2527
      note => 'Whether to display connection errors as rows in the table',
 
2528
      conf => 'ALL',
 
2529
      pat  => $bool_regex,
 
2530
   },
 
2531
   hide_hdr => {
 
2532
      val  => 0,
 
2533
      note => 'Whether to show column headers',
 
2534
      conf => 'ALL',
 
2535
      pat  => $bool_regex,
 
2536
   },
 
2537
   show_cxn_errors => {
 
2538
      val  => 1,
 
2539
      note => 'Whether to print connection errors to STDOUT',
 
2540
      conf => 'ALL',
 
2541
      pat  => $bool_regex,
 
2542
   },
 
2543
   readonly => {
 
2544
      val  => 0,
 
2545
      note => 'Whether the config file is read-only',
 
2546
      conf => [ qw() ],
 
2547
      pat  => $bool_regex,
 
2548
   },
 
2549
   global => {
 
2550
      val  => 1,
 
2551
      note => 'Whether to show GLOBAL variables and status',
 
2552
      conf => 'ALL',
 
2553
      pat  => $bool_regex,
 
2554
   },
 
2555
   header_highlight => {
 
2556
      val  => 'bold',
 
2557
      note => 'How to highlight table column headers',
 
2558
      conf => 'ALL',
 
2559
      pat  => qr/^(?:bold|underline)$/,
 
2560
   },
 
2561
   display_table_captions => {
 
2562
      val  => 1,
 
2563
      note => 'Whether to put captions on tables',
 
2564
      conf => 'ALL',
 
2565
      pat  => $bool_regex,
 
2566
   },
 
2567
   charset => {
 
2568
      val  => 'ascii',
 
2569
      note => 'What type of characters should be displayed in queries (ascii, unicode, none)',
 
2570
      conf => 'ALL',
 
2571
      pat  => qr/^(?:ascii|unicode|none)$/,
 
2572
   },
 
2573
   auto_wipe_dl => {
 
2574
      val  => 0,
 
2575
      note => 'Whether to auto-wipe InnoDB deadlocks',
 
2576
      conf => 'ALL',
 
2577
      pat  => $bool_regex,
 
2578
   },
 
2579
   max_height => {
 
2580
      val  => 30,
 
2581
      note => '[Win32] Max window height',
 
2582
      conf => 'ALL',
 
2583
   },
 
2584
   debug => {
 
2585
      val  => 0,
 
2586
      pat  => $bool_regex,
 
2587
      note => 'Debug mode (more verbose errors, uses more memory)',
 
2588
      conf => 'ALL',
 
2589
   },
 
2590
   num_digits => {
 
2591
      val  => 2,
 
2592
      pat  => $int_regex,
 
2593
      note => 'How many digits to show in fractional numbers and percents',
 
2594
      conf => 'ALL',
 
2595
   },
 
2596
   debugfile => {
 
2597
      val  => "$homepath/.innotop/core_dump",
 
2598
      note => 'A debug file in case you are interested in error output',
 
2599
   },
 
2600
   show_statusbar => {
 
2601
      val  => 1,
 
2602
      pat  => $bool_regex,
 
2603
      note => 'Whether to show the status bar in the display',
 
2604
      conf => 'ALL',
 
2605
   },
 
2606
   mode => {
 
2607
      val  => "T",
 
2608
      note => "Which mode to start in",
 
2609
      cmdline => 1,
 
2610
   },
 
2611
   status_inc => {
 
2612
      val  => 0,
 
2613
      note => 'Whether to show raw or incremental values for status variables',
 
2614
      pat  => $bool_regex,
 
2615
   },
 
2616
   interval => {
 
2617
      val  => 10,
 
2618
      pat  => qr/^(?:(?:\d*?[1-9]\d*(?:\.\d*)?)|(?:\d*\.\d*?[1-9]\d*))$/,
 
2619
      note => "The interval at which the display will be refreshed.  Fractional values allowed.",
 
2620
   },
 
2621
   num_status_sets => {
 
2622
      val  => 9,
 
2623
      pat  => $int_regex,
 
2624
      note => 'How many sets of STATUS and VARIABLES values to show',
 
2625
      conf => [ qw(S) ],
 
2626
   },
 
2627
   S_set => {
 
2628
      val  => 'general',
 
2629
      pat  => qr/^\w+$/,
 
2630
      note => 'Which set of variables to display in S (Variables & Status) mode',
 
2631
      conf => [ qw(S) ],
 
2632
   },
 
2633
);
 
2634
 
 
2635
# ###########################################################################
 
2636
# Config file sections {{{2
 
2637
# The configuration file is broken up into sections like a .ini file.  This
 
2638
# variable defines those sections and the subroutines responsible for reading
 
2639
# and writing them.
 
2640
# ###########################################################################
 
2641
my %config_file_sections = (
 
2642
   plugins => {
 
2643
      reader => \&load_config_plugins,
 
2644
      writer => \&save_config_plugins,
 
2645
   },
 
2646
   group_by => {
 
2647
      reader => \&load_config_group_by,
 
2648
      writer => \&save_config_group_by,
 
2649
   },
 
2650
   filters => {
 
2651
      reader => \&load_config_filters,
 
2652
      writer => \&save_config_filters,
 
2653
   },
 
2654
   active_filters => {
 
2655
      reader => \&load_config_active_filters,
 
2656
      writer => \&save_config_active_filters,
 
2657
   },
 
2658
   visible_tables => {
 
2659
      reader => \&load_config_visible_tables,
 
2660
      writer => \&save_config_visible_tables,
 
2661
   },
 
2662
   sort_cols => {
 
2663
      reader => \&load_config_sort_cols,
 
2664
      writer => \&save_config_sort_cols,
 
2665
   },
 
2666
   active_columns => {
 
2667
      reader => \&load_config_active_columns,
 
2668
      writer => \&save_config_active_columns,
 
2669
   },
 
2670
   tbl_meta => {
 
2671
      reader => \&load_config_tbl_meta,
 
2672
      writer => \&save_config_tbl_meta,
 
2673
   },
 
2674
   general => {
 
2675
      reader => \&load_config_config,
 
2676
      writer => \&save_config_config,
 
2677
   },
 
2678
   connections => {
 
2679
      reader => \&load_config_connections,
 
2680
      writer => \&save_config_connections,
 
2681
   },
 
2682
   active_connections => {
 
2683
      reader => \&load_config_active_connections,
 
2684
      writer => \&save_config_active_connections,
 
2685
   },
 
2686
   server_groups => {
 
2687
      reader => \&load_config_server_groups,
 
2688
      writer => \&save_config_server_groups,
 
2689
   },
 
2690
   active_server_groups => {
 
2691
      reader => \&load_config_active_server_groups,
 
2692
      writer => \&save_config_active_server_groups,
 
2693
   },
 
2694
   max_values_seen => {
 
2695
      reader => \&load_config_mvs,
 
2696
      writer => \&save_config_mvs,
 
2697
   },
 
2698
   varsets => {
 
2699
      reader => \&load_config_varsets,
 
2700
      writer => \&save_config_varsets,
 
2701
   },
 
2702
   colors => {
 
2703
      reader => \&load_config_colors,
 
2704
      writer => \&save_config_colors,
 
2705
   },
 
2706
   stmt_sleep_times => {
 
2707
      reader => \&load_config_stmt_sleep_times,
 
2708
      writer => \&save_config_stmt_sleep_times,
 
2709
   },
 
2710
);
 
2711
 
 
2712
# Config file sections have some dependencies, so they have to be read/written in order.
 
2713
my @ordered_config_file_sections = qw(general plugins filters active_filters tbl_meta
 
2714
   connections active_connections server_groups active_server_groups max_values_seen
 
2715
   active_columns sort_cols visible_tables varsets colors stmt_sleep_times
 
2716
   group_by);
 
2717
 
 
2718
# All events for which plugins may register themselves.  Entries are arrayrefs.
 
2719
my %event_listener_for = map { $_ => [] }
 
2720
   qw(
 
2721
      extract_values
 
2722
      set_to_tbl_pre_filter set_to_tbl_pre_sort set_to_tbl_pre_group
 
2723
      set_to_tbl_pre_colorize set_to_tbl_pre_transform set_to_tbl_pre_pivot
 
2724
      set_to_tbl_pre_create set_to_tbl_post_create
 
2725
      draw_screen
 
2726
   );
 
2727
 
 
2728
# All variables to which plugins have access.
 
2729
my %pluggable_vars = (
 
2730
   action_for    => \%action_for,
 
2731
   agg_funcs     => \%agg_funcs,
 
2732
   config        => \%config,
 
2733
   connections   => \%connections,
 
2734
   dbhs          => \%dbhs,
 
2735
   filters       => \%filters,
 
2736
   modes         => \%modes,
 
2737
   server_groups => \%server_groups,
 
2738
   tbl_meta      => \%tbl_meta,
 
2739
   trans_funcs   => \%trans_funcs,
 
2740
   var_sets      => \%var_sets,
 
2741
);
 
2742
 
 
2743
# ###########################################################################
 
2744
# Contains logic to generate prepared statements for a given function for a
 
2745
# given DB connection.  Returns a $sth.
 
2746
# ###########################################################################
 
2747
my %stmt_maker_for = (
 
2748
   INNODB_STATUS => sub {
 
2749
      my ( $dbh ) = @_;
 
2750
      return $dbh->prepare(version_ge( $dbh, '5.0.0' )
 
2751
             ? 'SHOW ENGINE INNODB STATUS'
 
2752
             : 'SHOW INNODB STATUS');
 
2753
   },
 
2754
   SHOW_VARIABLES => sub {
 
2755
      my ( $dbh ) = @_;
 
2756
      return $dbh->prepare($config{global}->{val} && version_ge( $dbh, '4.0.3' )
 
2757
             ? 'SHOW GLOBAL VARIABLES'
 
2758
             : 'SHOW VARIABLES');
 
2759
   },
 
2760
   SHOW_STATUS => sub {
 
2761
      my ( $dbh ) = @_;
 
2762
      return $dbh->prepare($config{global}->{val} && version_ge( $dbh, '5.0.2' )
 
2763
             ? 'SHOW GLOBAL STATUS'
 
2764
             : 'SHOW STATUS');
 
2765
   },
 
2766
   KILL_QUERY => sub {
 
2767
      my ( $dbh ) = @_;
 
2768
      return $dbh->prepare(version_ge( $dbh, '5.0.0' )
 
2769
             ? 'KILL QUERY ?'
 
2770
             : 'KILL ?');
 
2771
   },
 
2772
   SHOW_MASTER_LOGS => sub {
 
2773
      my ( $dbh ) = @_;
 
2774
      return $dbh->prepare('SHOW MASTER LOGS');
 
2775
   },
 
2776
   SHOW_MASTER_STATUS => sub {
 
2777
      my ( $dbh ) = @_;
 
2778
      return $dbh->prepare('SHOW MASTER STATUS');
 
2779
   },
 
2780
   SHOW_SLAVE_STATUS => sub {
 
2781
      my ( $dbh ) = @_;
 
2782
      return $dbh->prepare('SHOW SLAVE STATUS');
 
2783
   },
 
2784
   KILL_CONNECTION => sub {
 
2785
      my ( $dbh ) = @_;
 
2786
      return $dbh->prepare(version_ge( $dbh, '5.0.0' )
 
2787
             ? 'KILL CONNECTION ?'
 
2788
             : 'KILL ?');
 
2789
   },
 
2790
   OPEN_TABLES => sub {
 
2791
      my ( $dbh ) = @_;
 
2792
      return version_ge($dbh, '4.0.0')
 
2793
         ? $dbh->prepare('SHOW OPEN TABLES')
 
2794
         : undef;
 
2795
   },
 
2796
   PROCESSLIST => sub {
 
2797
      my ( $dbh ) = @_;
 
2798
      return $dbh->prepare('SHOW FULL PROCESSLIST');
 
2799
   },
 
2800
);
 
2801
 
 
2802
# Plugins!
 
2803
my %plugins = (
 
2804
);
 
2805
 
 
2806
# ###########################################################################
 
2807
# Run the program {{{1
 
2808
# ###########################################################################
 
2809
 
 
2810
# This config variable is only useful for MS Windows because its terminal
 
2811
# can't tell how tall it is.
 
2812
if ( !$windows ) {
 
2813
   delete $config{max_height};
 
2814
}
 
2815
 
 
2816
# Try to lower my priority.
 
2817
eval { setpriority(0, 0, getpriority(0, 0) + 10); };
 
2818
 
 
2819
# Print stuff to the screen immediately, don't wait for a newline.
 
2820
$OUTPUT_AUTOFLUSH = 1;
 
2821
 
 
2822
# Clear the screen and load the configuration.
 
2823
$clear_screen_sub->();
 
2824
load_config();
 
2825
post_process_tbl_meta();
 
2826
 
 
2827
# Make sure no changes are written to config file in non-interactive mode.
 
2828
if ( $opts{n} ) {
 
2829
   $config{readonly}->{val} = 1;
 
2830
}
 
2831
 
 
2832
eval {
 
2833
 
 
2834
   # Open the file for InnoDB status
 
2835
   if ( @ARGV ) {
 
2836
      my $filename = shift @ARGV;
 
2837
      open $file, "<", $filename
 
2838
         or die "Cannot open '$filename': $OS_ERROR";
 
2839
   }
 
2840
 
 
2841
   # In certain modes we might have to collect data for two cycles
 
2842
   # before printing anything out, so we need to bump up the count one.
 
2843
   if ( $opts{n} && $opts{count} && $config{status_inc}->{val}
 
2844
      && $config{mode}->{val} =~ m/[S]/ )
 
2845
   {
 
2846
      $opts{count}++;
 
2847
   }
 
2848
 
 
2849
   while (++$clock) {
 
2850
 
 
2851
      my $mode = $config{mode}->{val} || 'T';
 
2852
      if ( !$modes{$mode} ) {
 
2853
         die "Mode '$mode' doesn't exist; try one of these:\n"
 
2854
            . join("\n", map { "  $_ $modes{$_}->{hdr}" }  sort keys %modes)
 
2855
            . "\n";
 
2856
      }
 
2857
 
 
2858
      if ( !$opts{n} ) {
 
2859
         @last_term_size = @this_term_size;
 
2860
         @this_term_size = Term::ReadKey::GetTerminalSize(\*STDOUT);
 
2861
         if ( $windows ) {
 
2862
            $this_term_size[0]--;
 
2863
            $this_term_size[1]
 
2864
               = min($this_term_size[1], $config{max_height}->{val});
 
2865
         }
 
2866
         die("Can't read terminal size") unless @this_term_size;
 
2867
      }
 
2868
 
 
2869
      # If there's no connection to a database server, we need to fix that...
 
2870
      if ( !%connections ) {
 
2871
         print "You have not defined any database connections.\n\n";
 
2872
         add_new_dsn();
 
2873
      }
 
2874
 
 
2875
      # See whether there are any connections defined for this mode.  If there's only one
 
2876
      # connection total, assume the user wants to just use innotop for a single server
 
2877
      # and don't ask which server to connect to.  Also, if we're monitoring from a file,
 
2878
      # we just use the first connection.
 
2879
      if ( !get_connections() ) {
 
2880
         if ( $file || 1 == scalar keys %connections ) {
 
2881
            $modes{$config{mode}->{val}}->{connections} = [ keys %connections ];
 
2882
         }
 
2883
         else {
 
2884
            choose_connections();
 
2885
         }
 
2886
      }
 
2887
 
 
2888
      # Term::ReadLine might have re-set $OUTPUT_AUTOFLUSH.
 
2889
      $OUTPUT_AUTOFLUSH = 1;
 
2890
 
 
2891
      # Prune old data
 
2892
      my $sets = $config{num_status_sets}->{val};
 
2893
      foreach my $store ( values %vars ) {
 
2894
         delete @{$store}{ grep { $_ < $clock - $sets } keys %$store };
 
2895
      }
 
2896
      %info_gotten = ();
 
2897
 
 
2898
      # Call the subroutine to display this mode.
 
2899
      $modes{$mode}->{display_sub}->();
 
2900
 
 
2901
      # It may be time to quit now.
 
2902
      if ( $opts{count} && $clock >= $opts{count} ) {
 
2903
         finish();
 
2904
      }
 
2905
 
 
2906
      # Wait for a bit.
 
2907
      if ( $opts{n} ) {
 
2908
         sleep($config{interval}->{val});
 
2909
      }
 
2910
      else {
 
2911
         ReadMode('cbreak');
 
2912
         $char = ReadKey($config{interval}->{val});
 
2913
         ReadMode('normal');
 
2914
      }
 
2915
 
 
2916
      # Handle whatever action the key indicates.
 
2917
      do_key_action();
 
2918
 
 
2919
   }
 
2920
};
 
2921
if ( $EVAL_ERROR ) {
 
2922
   core_dump( $EVAL_ERROR );
 
2923
}
 
2924
finish();
 
2925
 
 
2926
# Subroutines {{{1
 
2927
# Mode functions{{{2
 
2928
# switch_mode {{{3
 
2929
sub switch_mode {
 
2930
   my $mode = shift;
 
2931
   $config{mode}->{val} = $mode;
 
2932
}
 
2933
 
 
2934
# Prompting functions {{{2
 
2935
# prompt_list {{{3
 
2936
# Prompts the user for a value, given a question, initial value,
 
2937
# a completion function and a hashref of hints.
 
2938
sub prompt_list {
 
2939
   die "Can't call in non-interactive mode" if $opts{n};
 
2940
   my ( $question, $init, $completion, $hints ) = @_;
 
2941
   if ( $hints ) {
 
2942
      # Figure out how wide the table will be
 
2943
      my $max_name = max(map { length($_) } keys %$hints );
 
2944
      $max_name ||= 0;
 
2945
      $max_name +=  3;
 
2946
      my @meta_rows = create_table2(
 
2947
               [ sort keys %$hints ],
 
2948
               { map { $_ => $_ } keys %$hints },
 
2949
               { map { $_ => trunc($hints->{$_}, $this_term_size[0] - $max_name) } keys %$hints },
 
2950
               { sep => '  ' });
 
2951
      if (@meta_rows > 10) {
 
2952
         # Try to split and stack the meta rows next to each other
 
2953
         my $split = int(@meta_rows / 2);
 
2954
         @meta_rows = stack_next(
 
2955
            [@meta_rows[0..$split - 1]],
 
2956
            [@meta_rows[$split..$#meta_rows]],
 
2957
            { pad => ' | '},
 
2958
         );
 
2959
      }
 
2960
      print join( "\n",
 
2961
         '',
 
2962
         map { ref $_ ? colored(@$_) : $_ } create_caption('Choose from', @meta_rows), ''),
 
2963
         "\n";
 
2964
   }
 
2965
   $term->Attribs->{completion_function} = $completion;
 
2966
   my $answer = $term->readline("$question: ", $init);
 
2967
   $OUTPUT_AUTOFLUSH = 1;
 
2968
   $answer = '' if !defined($answer);
 
2969
   $answer =~ s/\s+$//;
 
2970
   return $answer;
 
2971
}
 
2972
 
 
2973
# prompt {{{3
 
2974
# Prints out a prompt and reads from the keyboard, then validates with the
 
2975
# validation regex until the input is correct.
 
2976
sub prompt {
 
2977
   die "Can't call in non-interactive mode" if $opts{n};
 
2978
   my ( $prompt, $regex, $init, $completion ) = @_;
 
2979
   my $response;
 
2980
   my $success = 0;
 
2981
   do {
 
2982
      if ( $completion ) {
 
2983
         $term->Attribs->{completion_function} = $completion;
 
2984
      }
 
2985
      $response = $term->readline("$prompt: ", $init);
 
2986
      if ( $regex && $response !~ m/$regex/ ) {
 
2987
         print "Invalid response.\n\n";
 
2988
      }
 
2989
      else {
 
2990
         $success = 1;
 
2991
      }
 
2992
   } while ( !$success );
 
2993
   $OUTPUT_AUTOFLUSH = 1;
 
2994
   $response =~ s/\s+$//;
 
2995
   return $response;
 
2996
}
 
2997
 
 
2998
# prompt_noecho {{{3
 
2999
# Unfortunately, suppressing echo with Term::ReadLine isn't reliable; the user might not
 
3000
# have that library, or it might not support that feature.
 
3001
sub prompt_noecho {
 
3002
   my ( $prompt ) = @_;
 
3003
   print colored("$prompt: ", 'underline');
 
3004
   my $response;
 
3005
   ReadMode('noecho');
 
3006
   $response = <STDIN>;
 
3007
   chomp($response);
 
3008
   ReadMode('normal');
 
3009
   return $response;
 
3010
}
 
3011
 
 
3012
# do_key_action {{{3
 
3013
# Depending on whether a key was read, do something.  Keys have certain
 
3014
# actions defined in lookup tables.  Each mode may have its own lookup table,
 
3015
# which trumps the global table -- so keys can be context-sensitive.  The key
 
3016
# may be read and written in a subroutine, so it's a global.
 
3017
sub do_key_action {
 
3018
   if ( defined $char ) {
 
3019
      my $mode = $config{mode}->{val};
 
3020
      my $action
 
3021
         = defined($modes{$mode}->{action_for}->{$char})
 
3022
         ? $modes{$mode}->{action_for}->{$char}->{action}
 
3023
         : defined($action_for{$char})
 
3024
         ? $action_for{$char}->{action}
 
3025
         : sub{};
 
3026
      $action->();
 
3027
   }
 
3028
}
 
3029
 
 
3030
# pause {{{3
 
3031
sub pause {
 
3032
   die "Can't call in non-interactive mode" if $opts{n};
 
3033
   my $msg = shift;
 
3034
   print defined($msg) ? "\n$msg" : "\nPress any key to continue";
 
3035
   ReadMode('cbreak');
 
3036
   my $char = ReadKey(0);
 
3037
   ReadMode('normal');
 
3038
   return $char;
 
3039
}
 
3040
 
 
3041
# reverse_sort {{{3
 
3042
sub reverse_sort {
 
3043
   my $tbl = shift;
 
3044
   $tbl_meta{$tbl}->{sort_dir} *= -1;
 
3045
}
 
3046
 
 
3047
# select_cxn {{{3
 
3048
# Selects connection(s).  If the mode (or argument list) has only one, returns
 
3049
# it without prompt.
 
3050
sub select_cxn {
 
3051
   my ( $prompt, @cxns ) = @_;
 
3052
   if ( !@cxns ) {
 
3053
      @cxns = get_connections();
 
3054
   }
 
3055
   if ( @cxns == 1 ) {
 
3056
      return $cxns[0];
 
3057
   }
 
3058
   my $choices = prompt_list(
 
3059
         $prompt,
 
3060
         $cxns[0],
 
3061
         sub{ return @cxns },
 
3062
         { map { $_ => $connections{$_}->{dsn} } @cxns });
 
3063
   my @result = unique(grep { my $a = $_; grep { $_ eq $a } @cxns } split(/\s+/, $choices));
 
3064
   return @result;
 
3065
}
 
3066
 
 
3067
# kill_query {{{3
 
3068
# Kills a connection, or on new versions, optionally a query but not connection.
 
3069
sub kill_query {
 
3070
   my ( $q_or_c ) = @_;
 
3071
 
 
3072
   my $info = choose_thread(
 
3073
      sub { 1 },
 
3074
      'Select a thread to kill the ' . $q_or_c,
 
3075
   );
 
3076
   return unless $info;
 
3077
   return unless pause("Kill $info->{id}?") =~ m/y/i;
 
3078
 
 
3079
   eval {
 
3080
      do_stmt($info->{cxn}, $q_or_c eq 'QUERY' ? 'KILL_QUERY' : 'KILL_CONNECTION', $info->{id} );
 
3081
   };
 
3082
 
 
3083
   if ( $EVAL_ERROR ) {
 
3084
      print "\nError: $EVAL_ERROR";
 
3085
      pause();
 
3086
   }
 
3087
}
 
3088
 
 
3089
# set_display_precision {{{3
 
3090
sub set_display_precision {
 
3091
   my $dir = shift;
 
3092
   $config{num_digits}->{val} = min(9, max(0, $config{num_digits}->{val} + $dir));
 
3093
}
 
3094
 
 
3095
sub toggle_visible_table {
 
3096
   my ( $mode, $table ) = @_;
 
3097
   my $visible = $modes{$mode}->{visible_tables};
 
3098
   if ( grep { $_ eq $table } @$visible ) {
 
3099
      $modes{$mode}->{visible_tables} = [ grep { $_ ne $table } @$visible ];
 
3100
   }
 
3101
   else {
 
3102
      unshift @$visible, $table;
 
3103
   }
 
3104
   $modes{$mode}->{cust}->{visible_tables} = 1;
 
3105
}
 
3106
 
 
3107
# toggle_filter{{{3
 
3108
sub toggle_filter {
 
3109
   my ( $tbl, $filter ) = @_;
 
3110
   my $filters = $tbl_meta{$tbl}->{filters};
 
3111
   if ( grep { $_ eq $filter } @$filters ) {
 
3112
      $tbl_meta{$tbl}->{filters} = [ grep { $_ ne $filter } @$filters ];
 
3113
   }
 
3114
   else {
 
3115
      push @$filters, $filter;
 
3116
   }
 
3117
   $tbl_meta{$tbl}->{cust}->{filters} = 1;
 
3118
}
 
3119
 
 
3120
# toggle_config {{{3
 
3121
sub toggle_config {
 
3122
   my ( $key ) = @_;
 
3123
   $config{$key}->{val} ^= 1;
 
3124
}
 
3125
 
 
3126
# create_deadlock {{{3
 
3127
sub create_deadlock {
 
3128
   $clear_screen_sub->();
 
3129
 
 
3130
   print "This function will deliberately cause a small deadlock, "
 
3131
      . "clearing deadlock information from the InnoDB monitor.\n\n";
 
3132
 
 
3133
   my $answer = prompt("Are you sure you want to proceed?  Say 'y' if you do");
 
3134
   return 0 unless $answer eq 'y';
 
3135
 
 
3136
   my ( $cxn ) = select_cxn('Clear on which server? ');
 
3137
   return unless $cxn && exists($connections{$cxn});
 
3138
 
 
3139
   clear_deadlock($cxn);
 
3140
}
 
3141
 
 
3142
# deadlock_thread {{{3
 
3143
sub deadlock_thread {
 
3144
   my ( $id, $tbl, $cxn ) = @_;
 
3145
 
 
3146
   eval {
 
3147
      my $dbh = get_new_db_connection($cxn, 1);
 
3148
      my @stmts = (
 
3149
         "set transaction isolation level serializable",
 
3150
         (version_ge($dbh, '4.0.11') ? "start transaction" : 'begin'),
 
3151
         "select * from $tbl where a = $id",
 
3152
         "update $tbl set a = $id where a <> $id",
 
3153
      );
 
3154
 
 
3155
      foreach my $stmt (@stmts[0..2]) {
 
3156
         $dbh->do($stmt);
 
3157
      }
 
3158
      sleep(1 + $id);
 
3159
      $dbh->do($stmts[-1]);
 
3160
   };
 
3161
   if ( $EVAL_ERROR ) {
 
3162
      if ( $EVAL_ERROR !~ m/Deadlock found/ ) {
 
3163
         die $EVAL_ERROR;
 
3164
      }
 
3165
   }
 
3166
   exit(0);
 
3167
}
 
3168
 
 
3169
# Purges unused binlogs on the master, up to but not including the latest log.
 
3170
# TODO: guess which connections are slaves of a given master.
 
3171
sub purge_master_logs {
 
3172
   my @cxns = get_connections();
 
3173
 
 
3174
   get_master_slave_status(@cxns);
 
3175
 
 
3176
   # Toss out the rows that don't have master/slave status...
 
3177
   my @vars =
 
3178
      grep { $_ && ($_->{file} || $_->{master_host}) }
 
3179
      map  { $vars{$_}->{$clock} } @cxns;
 
3180
   @cxns = map { $_->{cxn} } @vars;
 
3181
 
 
3182
   # Figure out which master to purge ons.
 
3183
   my @masters = map { $_->{cxn} } grep { $_->{file} } @vars;
 
3184
   my ( $master ) = select_cxn('Which master?', @masters );
 
3185
   return unless $master;
 
3186
   my ($master_status) = grep { $_->{cxn} eq $master } @vars;
 
3187
 
 
3188
   # Figure out the result order (not lexical order) of master logs.
 
3189
   my @master_logs = get_master_logs($master);
 
3190
   my $i = 0;
 
3191
   my %master_logs = map { $_->{log_name} => $i++ } @master_logs;
 
3192
 
 
3193
   # Ask which slave(s) are reading from this master.
 
3194
   my @slave_status = grep { $_->{master_host} } @vars;
 
3195
   my @slaves = map { $_->{cxn} } @slave_status;
 
3196
   @slaves = select_cxn("Which slaves are reading from $master?", @slaves);
 
3197
   @slave_status = grep { my $item = $_; grep { $item->{cxn} eq $_ } @slaves } @slave_status;
 
3198
   return unless @slave_status;
 
3199
 
 
3200
   # Find the minimum binary log in use.
 
3201
   my $min_log = min(map { $master_logs{$_->{master_log_file}} } @slave_status);
 
3202
   my $log_name = $master_logs[$min_log]->{log_name};
 
3203
 
 
3204
   my $stmt = "PURGE MASTER LOGS TO '$log_name'";
 
3205
   send_cmd_to_servers($stmt, 0, 'PURGE {MASTER | BINARY} LOGS {TO "log_name" | BEFORE "date"}', [$master]);
 
3206
}
 
3207
 
 
3208
sub send_cmd_to_servers {
 
3209
   my ( $cmd, $all, $hint, $cxns ) = @_;
 
3210
   if ( $all ) {
 
3211
      @$cxns = get_connections();
 
3212
   }
 
3213
   elsif ( !@$cxns ) {
 
3214
      @$cxns = select_cxn('Which servers?', @$cxns);
 
3215
   }
 
3216
   if ( $hint ) {
 
3217
      print "\nHint: $hint\n";
 
3218
   }
 
3219
   $cmd = prompt('Command to send', undef, $cmd);
 
3220
   foreach my $cxn ( @$cxns ) {
 
3221
      eval {
 
3222
         my $sth = do_query($cxn, $cmd);
 
3223
      };
 
3224
      if ( $EVAL_ERROR ) {
 
3225
         print "Error from $cxn: $EVAL_ERROR\n";
 
3226
      }
 
3227
      else {
 
3228
         print "Success on $cxn\n";
 
3229
      }
 
3230
   }
 
3231
   pause();
 
3232
}
 
3233
 
 
3234
# Display functions {{{2
 
3235
 
 
3236
sub set_s_mode {
 
3237
   my ( $func ) = @_;
 
3238
   $clear_screen_sub->();
 
3239
   $config{S_func}->{val} = $func;
 
3240
}
 
3241
 
 
3242
# start_S_mode {{{3
 
3243
sub start_S_mode {
 
3244
   $clear_screen_sub->();
 
3245
   switch_mode('S');
 
3246
}
 
3247
 
 
3248
# display_B {{{3
 
3249
sub display_B {
 
3250
   my @display_lines;
 
3251
   my @cxns = get_connections();
 
3252
   get_innodb_status(\@cxns);
 
3253
 
 
3254
   my @buffer_pool;
 
3255
   my @page_statistics;
 
3256
   my @insert_buffers;
 
3257
   my @adaptive_hash_index;
 
3258
   my %rows_for = (
 
3259
      buffer_pool         => \@buffer_pool,
 
3260
      page_statistics     => \@page_statistics,
 
3261
      insert_buffers      => \@insert_buffers,
 
3262
      adaptive_hash_index => \@adaptive_hash_index,
 
3263
   );
 
3264
 
 
3265
   my @visible = get_visible_tables();
 
3266
   my %wanted  = map { $_ => 1 } @visible;
 
3267
 
 
3268
   foreach my $cxn ( @cxns ) {
 
3269
      my $set = $vars{$cxn}->{$clock};
 
3270
      my $pre = $vars{$cxn}->{$clock-1} || $set;
 
3271
 
 
3272
      if ( $set->{IB_bp_complete} ) {
 
3273
         if ( $wanted{buffer_pool} ) {
 
3274
            push @buffer_pool, extract_values($set, $set, $pre, 'buffer_pool');
 
3275
         }
 
3276
         if ( $wanted{page_statistics} ) {
 
3277
            push @page_statistics, extract_values($set, $set, $pre, 'page_statistics');
 
3278
         }
 
3279
      }
 
3280
      if ( $set->{IB_ib_complete} ) {
 
3281
         if ( $wanted{insert_buffers} ) {
 
3282
            push @insert_buffers, extract_values(
 
3283
               $config{status_inc}->{val} ? inc(0, $cxn) : $set, $set, $pre,
 
3284
               'insert_buffers');
 
3285
         }
 
3286
         if ( $wanted{adaptive_hash_index} ) {
 
3287
            push @adaptive_hash_index, extract_values($set, $set, $pre, 'adaptive_hash_index');
 
3288
         }
 
3289
      }
 
3290
   }
 
3291
 
 
3292
   my $first_table = 0;
 
3293
   foreach my $tbl ( @visible ) {
 
3294
      push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl);
 
3295
      push @display_lines, get_cxn_errors(@cxns)
 
3296
         if ( $config{debug}->{val} || !$first_table++ );
 
3297
   }
 
3298
 
 
3299
   draw_screen(\@display_lines);
 
3300
}
 
3301
 
 
3302
# display_C {{{3
 
3303
sub display_C {
 
3304
   my @display_lines;
 
3305
   my @cxns = get_connections();
 
3306
   get_status_info(@cxns);
 
3307
 
 
3308
   my @cmd_summary;
 
3309
   my %rows_for = (
 
3310
      cmd_summary => \@cmd_summary,
 
3311
   );
 
3312
 
 
3313
   my @visible = get_visible_tables();
 
3314
   my %wanted  = map { $_ => 1 } @visible;
 
3315
 
 
3316
   # For now, I'm manually pulling these variables out and pivoting.  Eventually a SQL-ish
 
3317
   # dialect should let me join a table to a grouped and pivoted table and do this more easily.
 
3318
   # TODO: make it so.
 
3319
   my $prefix = qr/^$config{cmd_filter}->{val}/; # TODO: this is a total hack
 
3320
   my @values;
 
3321
   my ($total, $last_total) = (0, 0);
 
3322
   foreach my $cxn ( @cxns ) {
 
3323
      my $set = $vars{$cxn}->{$clock};
 
3324
      my $pre = $vars{$cxn}->{$clock-1} || $set;
 
3325
      foreach my $key ( keys %$set ) {
 
3326
         next unless $key =~ m/$prefix/i;
 
3327
         my $val = $set->{$key};
 
3328
         next unless defined $val && $val =~ m/^\d+$/;
 
3329
         my $last_val = $val - ($pre->{$key} || 0);
 
3330
         $total      += $val;
 
3331
         $last_total += $last_val;
 
3332
         push @values, {
 
3333
            name       => $key,
 
3334
            value      => $val,
 
3335
            last_value => $last_val,
 
3336
         };
 
3337
      }
 
3338
   }
 
3339
 
 
3340
   # Add aggregation and turn into a real set TODO: total hack
 
3341
   if ( $wanted{cmd_summary} ) {
 
3342
      foreach my $value ( @values ) {
 
3343
         @{$value}{qw(total last_total)} = ($total, $last_total);
 
3344
         push @cmd_summary, extract_values($value, $value, $value, 'cmd_summary');
 
3345
      }
 
3346
   }
 
3347
 
 
3348
   my $first_table = 0;
 
3349
   foreach my $tbl ( @visible ) {
 
3350
      push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl);
 
3351
      push @display_lines, get_cxn_errors(@cxns)
 
3352
         if ( $config{debug}->{val} || !$first_table++ );
 
3353
   }
 
3354
 
 
3355
   draw_screen(\@display_lines);
 
3356
}
 
3357
 
 
3358
# display_D {{{3
 
3359
sub display_D {
 
3360
   my @display_lines;
 
3361
   my @cxns = get_connections();
 
3362
   get_innodb_status(\@cxns);
 
3363
 
 
3364
   my @deadlock_transactions;
 
3365
   my @deadlock_locks;
 
3366
   my %rows_for = (
 
3367
      deadlock_transactions => \@deadlock_transactions,
 
3368
      deadlock_locks        => \@deadlock_locks,
 
3369
   );
 
3370
 
 
3371
   my @visible = get_visible_tables();
 
3372
   my %wanted  = map { $_ => 1 } @visible;
 
3373
 
 
3374
   foreach my $cxn ( @cxns ) {
 
3375
      my $innodb_status = $vars{$cxn}->{$clock};
 
3376
      my $prev_status   = $vars{$cxn}->{$clock-1} || $innodb_status;
 
3377
 
 
3378
      if ( $innodb_status->{IB_dl_timestring} ) {
 
3379
 
 
3380
         my $victim = $innodb_status->{IB_dl_rolled_back} || 0;
 
3381
 
 
3382
         if ( %wanted ) {
 
3383
            foreach my $txn_id ( keys %{$innodb_status->{IB_dl_txns}} ) {
 
3384
               my $txn = $innodb_status->{IB_dl_txns}->{$txn_id};
 
3385
               my $pre = $prev_status->{IB_dl_txns}->{$txn_id} || $txn;
 
3386
 
 
3387
               if ( $wanted{deadlock_transactions} ) {
 
3388
                  my $hash = extract_values($txn->{tx}, $txn->{tx}, $pre->{tx}, 'deadlock_transactions');
 
3389
                  $hash->{cxn}        = $cxn;
 
3390
                  $hash->{dl_txn_num} = $txn_id;
 
3391
                  $hash->{victim}     = $txn_id == $victim ? 'Yes' : 'No';
 
3392
                  $hash->{timestring} = $innodb_status->{IB_dl_timestring};
 
3393
                  $hash->{truncates}  = $innodb_status->{IB_dl_complete} ? 'No' : 'Yes';
 
3394
                  push @deadlock_transactions, $hash;
 
3395
               }
 
3396
 
 
3397
               if ( $wanted{deadlock_locks} ) {
 
3398
                  foreach my $lock ( @{$txn->{locks}} ) {
 
3399
                     my $hash = extract_values($lock, $lock, $lock, 'deadlock_locks');
 
3400
                     $hash->{dl_txn_num}      = $txn_id;
 
3401
                     $hash->{cxn}             = $cxn;
 
3402
                     $hash->{mysql_thread_id} = $txn->{tx}->{mysql_thread_id};
 
3403
                     push @deadlock_locks, $hash;
 
3404
                  }
 
3405
               }
 
3406
 
 
3407
            }
 
3408
         }
 
3409
      }
 
3410
   }
 
3411
 
 
3412
   my $first_table = 0;
 
3413
   foreach my $tbl ( @visible ) {
 
3414
      push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl);
 
3415
      push @display_lines, get_cxn_errors(@cxns)
 
3416
         if ( $config{debug}->{val} || !$first_table++ );
 
3417
   }
 
3418
 
 
3419
   draw_screen(\@display_lines);
 
3420
}
 
3421
 
 
3422
# display_F {{{3
 
3423
sub display_F {
 
3424
   my @display_lines;
 
3425
   my ( $cxn ) = get_connections();
 
3426
   get_innodb_status([$cxn]);
 
3427
   my $innodb_status = $vars{$cxn}->{$clock};
 
3428
 
 
3429
   if ( $innodb_status->{IB_fk_timestring} ) {
 
3430
 
 
3431
      push @display_lines, 'Reason: ' . $innodb_status->{IB_fk_reason};
 
3432
 
 
3433
      # Display FK errors caused by invalid DML.
 
3434
      if ( $innodb_status->{IB_fk_txn} ) {
 
3435
         my $txn = $innodb_status->{IB_fk_txn};
 
3436
         push @display_lines,
 
3437
            '',
 
3438
            "User $txn->{user} from $txn->{hostname}, thread $txn->{mysql_thread_id} was executing:",
 
3439
            '', no_ctrl_char($txn->{query_text});
 
3440
      }
 
3441
 
 
3442
      my @fk_table = create_table2(
 
3443
         $tbl_meta{fk_error}->{visible},
 
3444
         meta_to_hdr('fk_error'),
 
3445
         extract_values($innodb_status, $innodb_status, $innodb_status, 'fk_error'),
 
3446
         { just => '-', sep => '  '});
 
3447
      push @display_lines, '', @fk_table;
 
3448
 
 
3449
   }
 
3450
   else {
 
3451
      push @display_lines, '', 'No foreign key error data.';
 
3452
   }
 
3453
   draw_screen(\@display_lines, { raw => 1 } );
 
3454
}
 
3455
 
 
3456
# display_I {{{3
 
3457
sub display_I {
 
3458
   my @display_lines;
 
3459
   my @cxns = get_connections();
 
3460
   get_innodb_status(\@cxns);
 
3461
 
 
3462
   my @io_threads;
 
3463
   my @pending_io;
 
3464
   my @file_io_misc;
 
3465
   my @log_statistics;
 
3466
   my %rows_for = (
 
3467
      io_threads     => \@io_threads,
 
3468
      pending_io     => \@pending_io,
 
3469
      file_io_misc   => \@file_io_misc,
 
3470
      log_statistics => \@log_statistics,
 
3471
   );
 
3472
 
 
3473
   my @visible = get_visible_tables();
 
3474
   my %wanted  = map { $_ => 1 } @visible;
 
3475
 
 
3476
   foreach my $cxn ( @cxns ) {
 
3477
      my $set = $vars{$cxn}->{$clock};
 
3478
      my $pre = $vars{$cxn}->{$clock-1} || $set;
 
3479
 
 
3480
      if ( $set->{IB_io_complete} ) {
 
3481
         if ( $wanted{io_threads} ) {
 
3482
            my $cur_threads = $set->{IB_io_threads};
 
3483
            my $pre_threads = $pre->{IB_io_threads} || $cur_threads;
 
3484
            foreach my $key ( sort keys %$cur_threads ) {
 
3485
               my $cur_thd = $cur_threads->{$key};
 
3486
               my $pre_thd = $pre_threads->{$key} || $cur_thd;
 
3487
               my $hash = extract_values($cur_thd, $cur_thd, $pre_thd, 'io_threads');
 
3488
               $hash->{cxn} = $cxn;
 
3489
               push @io_threads, $hash;
 
3490
            }
 
3491
         }
 
3492
         if ( $wanted{pending_io} ) {
 
3493
            push @pending_io, extract_values($set, $set, $pre, 'pending_io');
 
3494
         }
 
3495
         if ( $wanted{file_io_misc} ) {
 
3496
            push @file_io_misc, extract_values(
 
3497
               $config{status_inc}->{val} ? inc(0, $cxn) : $set,
 
3498
               $set, $pre, 'file_io_misc');
 
3499
         }
 
3500
      }
 
3501
      if ( $set->{IB_lg_complete} && $wanted{log_statistics} ) {
 
3502
         push @log_statistics, extract_values($set, $set, $pre, 'log_statistics');
 
3503
      }
 
3504
   }
 
3505
 
 
3506
   my $first_table = 0;
 
3507
   foreach my $tbl ( @visible ) {
 
3508
      push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl);
 
3509
      push @display_lines, get_cxn_errors(@cxns)
 
3510
         if ( $config{debug}->{val} || !$first_table++ );
 
3511
   }
 
3512
 
 
3513
   draw_screen(\@display_lines);
 
3514
}
 
3515
 
 
3516
# display_L {{{3
 
3517
sub display_L {
 
3518
   my @display_lines;
 
3519
   my @cxns = get_connections();
 
3520
   get_innodb_status(\@cxns);
 
3521
 
 
3522
   my @innodb_locks;
 
3523
   my %rows_for = (
 
3524
      innodb_locks => \@innodb_locks,
 
3525
   );
 
3526
 
 
3527
   my @visible = get_visible_tables();
 
3528
   my %wanted  = map { $_ => 1 } @visible;
 
3529
 
 
3530
   # Get info on locks
 
3531
   foreach my $cxn ( @cxns ) {
 
3532
      my $set = $vars{$cxn}->{$clock} or next;
 
3533
      my $pre = $vars{$cxn}->{$clock-1} || $set;
 
3534
 
 
3535
      if ( $wanted{innodb_locks} && defined $set->{IB_tx_transactions} && @{$set->{IB_tx_transactions}} ) {
 
3536
 
 
3537
         my $cur_txns = $set->{IB_tx_transactions};
 
3538
         my $pre_txns = $pre->{IB_tx_transactions} || $cur_txns;
 
3539
         my %cur_txns = map { $_->{mysql_thread_id} => $_ } @$cur_txns;
 
3540
         my %pre_txns = map { $_->{mysql_thread_id} => $_ } @$pre_txns;
 
3541
         foreach my $txn ( @$cur_txns ) {
 
3542
            foreach my $lock ( @{$txn->{locks}} ) {
 
3543
               my %hash = map { $_ => $txn->{$_} } qw(txn_id mysql_thread_id lock_wait_time active_secs);
 
3544
               map { $hash{$_} = $lock->{$_} } qw(lock_type space_id page_no n_bits index db table txn_id lock_mode special insert_intention waiting);
 
3545
               $hash{cxn} = $cxn;
 
3546
               push @innodb_locks, extract_values(\%hash, \%hash, \%hash, 'innodb_locks');
 
3547
            }
 
3548
         }
 
3549
      }
 
3550
   }
 
3551
 
 
3552
   my $first_table = 0;
 
3553
   foreach my $tbl ( @visible ) {
 
3554
      push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl);
 
3555
      push @display_lines, get_cxn_errors(@cxns)
 
3556
         if ( $config{debug}->{val} || !$first_table++ );
 
3557
   }
 
3558
 
 
3559
   draw_screen(\@display_lines);
 
3560
}
 
3561
 
 
3562
# display_M {{{3
 
3563
sub display_M {
 
3564
   my @display_lines;
 
3565
   my @cxns = get_connections();
 
3566
   get_master_slave_status(@cxns);
 
3567
   get_status_info(@cxns);
 
3568
 
 
3569
   my @slave_sql_status;
 
3570
   my @slave_io_status;
 
3571
   my @master_status;
 
3572
   my %rows_for = (
 
3573
      slave_sql_status => \@slave_sql_status,
 
3574
      slave_io_status  => \@slave_io_status,
 
3575
      master_status    => \@master_status,
 
3576
   );
 
3577
 
 
3578
   my @visible = get_visible_tables();
 
3579
   my %wanted  = map { $_ => 1 } @visible;
 
3580
 
 
3581
   foreach my $cxn ( @cxns ) {
 
3582
      my $set  = $config{status_inc}->{val} ? inc(0, $cxn) : $vars{$cxn}->{$clock};
 
3583
      my $pre  = $vars{$cxn}->{$clock - 1} || $set;
 
3584
      if ( $wanted{slave_sql_status} ) {
 
3585
         push @slave_sql_status, extract_values($set, $set, $pre, 'slave_sql_status');
 
3586
      }
 
3587
      if ( $wanted{slave_io_status} ) {
 
3588
         push @slave_io_status, extract_values($set, $set, $pre, 'slave_io_status');
 
3589
      }
 
3590
      if ( $wanted{master_status} ) {
 
3591
         push @master_status, extract_values($set, $set, $pre, 'master_status');
 
3592
      }
 
3593
   }
 
3594
 
 
3595
   my $first_table = 0;
 
3596
   foreach my $tbl ( @visible ) {
 
3597
      push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl);
 
3598
      push @display_lines, get_cxn_errors(@cxns)
 
3599
         if ( $config{debug}->{val} || !$first_table++ );
 
3600
   }
 
3601
 
 
3602
   draw_screen(\@display_lines);
 
3603
}
 
3604
 
 
3605
# display_O {{{3
 
3606
sub display_O {
 
3607
   my @display_lines = ('');
 
3608
   my @cxns          = get_connections();
 
3609
   my @open_tables   = get_open_tables(@cxns);
 
3610
   my @tables = map { extract_values($_, $_, $_, 'open_tables') } @open_tables;
 
3611
   push @display_lines, set_to_tbl(\@tables, 'open_tables'), get_cxn_errors(@cxns);
 
3612
   draw_screen(\@display_lines);
 
3613
}
 
3614
 
 
3615
# display_Q {{{3
 
3616
sub display_Q {
 
3617
   my @display_lines;
 
3618
 
 
3619
   my @q_header;
 
3620
   my @processlist;
 
3621
   my %rows_for = (
 
3622
      q_header    => \@q_header,
 
3623
      processlist => \@processlist,
 
3624
   );
 
3625
 
 
3626
   my @visible = $opts{n} ? 'processlist' : get_visible_tables();
 
3627
   my %wanted  = map { $_ => 1 } @visible;
 
3628
 
 
3629
   # Get the data
 
3630
   my @cxns             = get_connections();
 
3631
   my @full_processlist = get_full_processlist(@cxns);
 
3632
 
 
3633
   # Create header
 
3634
   if ( $wanted{q_header} ) {
 
3635
      get_status_info(@cxns);
 
3636
      foreach my $cxn ( @cxns ) {
 
3637
         my $set = $vars{$cxn}->{$clock};
 
3638
         my $pre = $vars{$cxn}->{$clock-1} || $set;
 
3639
         my $hash = extract_values($set, $set, $pre, 'q_header');
 
3640
         $hash->{cxn} = $cxn;
 
3641
         $hash->{when} = 'Total';
 
3642
         push @q_header, $hash;
 
3643
 
 
3644
         if ( exists $vars{$cxn}->{$clock - 1} ) {
 
3645
            my $inc = inc(0, $cxn);
 
3646
            my $hash = extract_values($inc, $set, $pre, 'q_header');
 
3647
            $hash->{cxn} = $cxn;
 
3648
            $hash->{when} = 'Now';
 
3649
            push @q_header, $hash;
 
3650
         }
 
3651
      }
 
3652
   }
 
3653
 
 
3654
   if ( $wanted{processlist} ) {
 
3655
      # TODO: save prev values
 
3656
      push @processlist, map { extract_values($_, $_, $_, 'processlist') } @full_processlist;
 
3657
   }
 
3658
 
 
3659
   my $first_table = 0;
 
3660
   foreach my $tbl ( @visible ) {
 
3661
      next unless $wanted{$tbl};
 
3662
      push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl);
 
3663
      push @display_lines, get_cxn_errors(@cxns)
 
3664
         if ( $config{debug}->{val} || !$first_table++ );
 
3665
   }
 
3666
 
 
3667
   # Save queries in global variable for analysis.  The rows in %rows_for have been
 
3668
   # filtered, etc as a side effect of set_to_tbl(), so they are the same as the rows
 
3669
   # that get pushed to the screen.
 
3670
   @current_queries = map {
 
3671
      my %hash;
 
3672
      @hash{ qw(cxn id db query secs) } = @{$_}{ qw(cxn mysql_thread_id db info secs) };
 
3673
      \%hash;
 
3674
   } @{$rows_for{processlist}};
 
3675
 
 
3676
   draw_screen(\@display_lines);
 
3677
}
 
3678
 
 
3679
# display_R {{{3
 
3680
sub display_R {
 
3681
   my @display_lines;
 
3682
   my @cxns = get_connections();
 
3683
   get_innodb_status(\@cxns);
 
3684
 
 
3685
   my @row_operations;
 
3686
   my @row_operation_misc;
 
3687
   my @semaphores;
 
3688
   my @wait_array;
 
3689
   my %rows_for = (
 
3690
      row_operations     => \@row_operations,
 
3691
      row_operation_misc => \@row_operation_misc,
 
3692
      semaphores         => \@semaphores,
 
3693
      wait_array         => \@wait_array,
 
3694
   );
 
3695
 
 
3696
   my @visible = get_visible_tables();
 
3697
   my %wanted  = map { $_ => 1 } @visible;
 
3698
   my $incvar  = $config{status_inc}->{val};
 
3699
 
 
3700
   foreach my $cxn ( @cxns ) {
 
3701
      my $set = $vars{$cxn}->{$clock};
 
3702
      my $pre = $vars{$cxn}->{$clock-1} || $set;
 
3703
      my $inc; # Only assigned to if wanted
 
3704
 
 
3705
      if ( $set->{IB_ro_complete} ) {
 
3706
         if ( $wanted{row_operations} ) {
 
3707
            $inc ||= $incvar ? inc(0, $cxn) : $set;
 
3708
            push @row_operations, extract_values($inc, $set, $pre, 'row_operations');
 
3709
         }
 
3710
         if ( $wanted{row_operation_misc} ) {
 
3711
            push @row_operation_misc, extract_values($set, $set, $pre, 'row_operation_misc'),
 
3712
         }
 
3713
      }
 
3714
 
 
3715
      if ( $set->{IB_sm_complete} && $wanted{semaphores} ) {
 
3716
         $inc ||= $incvar ? inc(0, $cxn) : $set;
 
3717
         push @semaphores, extract_values($inc, $set, $pre, 'semaphores');
 
3718
      }
 
3719
 
 
3720
      if ( $set->{IB_sm_wait_array_size} && $wanted{wait_array} ) {
 
3721
         foreach my $wait ( @{$set->{IB_sm_waits}} ) {
 
3722
            my $hash = extract_values($wait, $wait, $wait, 'wait_array');
 
3723
            $hash->{cxn} = $cxn;
 
3724
            push @wait_array, $hash;
 
3725
         }
 
3726
      }
 
3727
   }
 
3728
 
 
3729
   my $first_table = 0;
 
3730
   foreach my $tbl ( @visible ) {
 
3731
      push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl);
 
3732
      push @display_lines, get_cxn_errors(@cxns)
 
3733
         if ( $config{debug}->{val} || !$first_table++ );
 
3734
   }
 
3735
 
 
3736
   draw_screen(\@display_lines);
 
3737
}
 
3738
 
 
3739
# display_T {{{3
 
3740
sub display_T {
 
3741
   my @display_lines;
 
3742
 
 
3743
   my @t_header;
 
3744
   my @innodb_transactions;
 
3745
   my %rows_for = (
 
3746
      t_header            => \@t_header,
 
3747
      innodb_transactions => \@innodb_transactions,
 
3748
   );
 
3749
 
 
3750
   my @visible = $opts{n} ? 'innodb_transactions' : get_visible_tables();
 
3751
   my %wanted  = map { $_ => 1 } @visible;
 
3752
 
 
3753
   my @cxns = get_connections();
 
3754
 
 
3755
   # If the header is to be shown, buffer pool data is required.
 
3756
   get_innodb_status( \@cxns, [ $wanted{t_header} ? qw(bp) : () ] );
 
3757
 
 
3758
   foreach my $cxn ( get_connections() ) {
 
3759
      my $set = $vars{$cxn}->{$clock};
 
3760
      my $pre = $vars{$cxn}->{$clock-1} || $set;
 
3761
 
 
3762
      next unless $set->{IB_tx_transactions};
 
3763
 
 
3764
      if ( $wanted{t_header} ) {
 
3765
         my $hash = extract_values($set, $set, $pre, 't_header');
 
3766
         push @t_header, $hash;
 
3767
      }
 
3768
 
 
3769
      if ( $wanted{innodb_transactions} ) {
 
3770
         my $cur_txns = $set->{IB_tx_transactions};
 
3771
         my $pre_txns = $pre->{IB_tx_transactions} || $cur_txns;
 
3772
         my %cur_txns = map { $_->{mysql_thread_id} => $_ } @$cur_txns;
 
3773
         my %pre_txns = map { $_->{mysql_thread_id} => $_ } @$pre_txns;
 
3774
         foreach my $thd_id ( sort keys %cur_txns ) {
 
3775
            my $cur_txn = $cur_txns{$thd_id};
 
3776
            my $pre_txn = $pre_txns{$thd_id} || $cur_txn;
 
3777
            my $hash    = extract_values($cur_txn, $cur_txn, $pre_txn, 'innodb_transactions');
 
3778
            $hash->{cxn} = $cxn;
 
3779
            push @innodb_transactions, $hash;
 
3780
         }
 
3781
      }
 
3782
 
 
3783
   }
 
3784
 
 
3785
   my $first_table = 0;
 
3786
   foreach my $tbl ( @visible ) {
 
3787
      push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl);
 
3788
      push @display_lines, get_cxn_errors(@cxns)
 
3789
         if ( $config{debug}->{val} || !$first_table++ );
 
3790
   }
 
3791
 
 
3792
   # Save queries in global variable for analysis.  The rows in %rows_for have been
 
3793
   # filtered, etc as a side effect of set_to_tbl(), so they are the same as the rows
 
3794
   # that get pushed to the screen.
 
3795
   @current_queries = map {
 
3796
      my %hash;
 
3797
      @hash{ qw(cxn id db query secs) } = @{$_}{ qw(cxn mysql_thread_id db query_text active_secs) };
 
3798
      \%hash;
 
3799
   } @{$rows_for{innodb_transactions}};
 
3800
 
 
3801
   draw_screen(\@display_lines);
 
3802
}
 
3803
 
 
3804
# display_S {{{3
 
3805
sub display_S {
 
3806
   my $fmt  = get_var_set('S_set');
 
3807
   my $func = $config{S_func}->{val};
 
3808
   my $inc  = $func eq 'g' || $config{status_inc}->{val};
 
3809
 
 
3810
   # The table's meta-data is generated from the compiled var_set.
 
3811
   my ( $cols, $visible );
 
3812
   if ( $tbl_meta{var_status}->{fmt} && $fmt eq $tbl_meta{var_status}->{fmt} ) {
 
3813
      ( $cols, $visible ) = @{$tbl_meta{var_status}}{qw(cols visible)};
 
3814
   }
 
3815
   else {
 
3816
      ( $cols, $visible ) = compile_select_stmt($fmt);
 
3817
 
 
3818
      # Apply missing values to columns.  Always apply averages across all connections.
 
3819
      map {
 
3820
         $_->{agg}   = 'avg';
 
3821
         $_->{label} = $_->{hdr};
 
3822
      } values %$cols;
 
3823
 
 
3824
      $tbl_meta{var_status}->{cols}    = $cols;
 
3825
      $tbl_meta{var_status}->{visible} = $visible;
 
3826
      $tbl_meta{var_status}->{fmt}     = $fmt;
 
3827
      map { $tbl_meta{var_status}->{cols}->{$_}->{just} = ''} @$visible;
 
3828
   }
 
3829
 
 
3830
   my @var_status;
 
3831
   my %rows_for = (
 
3832
      var_status => \@var_status,
 
3833
   );
 
3834
 
 
3835
   my @visible = get_visible_tables();
 
3836
   my %wanted  = map { $_ => 1 } @visible;
 
3837
   my @cxns    = get_connections();
 
3838
 
 
3839
   get_status_info(@cxns);
 
3840
   get_innodb_status(\@cxns);
 
3841
 
 
3842
   # Set up whether to pivot and how many sets to extract.
 
3843
   $tbl_meta{var_status}->{pivot} = $func eq 'v';
 
3844
 
 
3845
   my $num_sets
 
3846
      = $func eq 'v'
 
3847
      ? $config{num_status_sets}->{val}
 
3848
      : 0;
 
3849
   foreach my $set ( 0 .. $num_sets ) {
 
3850
      my @rows;
 
3851
      foreach my $cxn ( @cxns ) {
 
3852
         my $vars = $inc ? inc($set, $cxn) : $vars{$cxn}->{$clock - $set};
 
3853
         my $cur  = $vars{$cxn}->{$clock-$set};
 
3854
         my $pre  = $vars{$cxn}->{$clock-$set-1} || $cur;
 
3855
         next unless $vars && %$vars;
 
3856
         my $hash = extract_values($vars, $cur, $pre, 'var_status');
 
3857
         push @rows, $hash;
 
3858
      }
 
3859
      @rows = apply_group_by('var_status', [], @rows);
 
3860
      push @var_status, @rows;
 
3861
   }
 
3862
 
 
3863
   # Recompile the sort func. TODO: avoid recompiling at every refresh.
 
3864
   # Figure out whether the data is all numeric and decide on a sort type.
 
3865
   # my $cmp
 
3866
   #   = scalar(
 
3867
   #      grep { !defined $_ || $_ !~ m/^\d+$/ }
 
3868
   #      map  { my $col = $_; map { $_->{$col} } @var_status }
 
3869
   #           $tbl_meta{var_status}->{sort_cols} =~ m/(\w+)/g)
 
3870
   #   ? 'cmp'
 
3871
   #   : '<=>';
 
3872
   $tbl_meta{var_status}->{sort_func} = make_sort_func($tbl_meta{var_status});
 
3873
 
 
3874
   # ################################################################
 
3875
   # Now there is specific display code based on $config{S_func}
 
3876
   # ################################################################
 
3877
   if ( $func =~ m/s|g/ ) {
 
3878
      my $min_width = 4;
 
3879
 
 
3880
      # Clear the screen if the display width changed.
 
3881
      if ( @last_term_size && $this_term_size[0] != $last_term_size[0] ) {
 
3882
         $lines_printed = 0;
 
3883
         $clear_screen_sub->();
 
3884
      }
 
3885
 
 
3886
      if ( $func eq 's' ) {
 
3887
         # Decide how wide columns should be.
 
3888
         my $num_cols = scalar(@$visible);
 
3889
         my $width    = $opts{n} ? 0 : max($min_width, int(($this_term_size[0] - $num_cols + 1) / $num_cols));
 
3890
         my $g_format = $opts{n} ? ( "%s\t" x $num_cols ) : ( "%-${width}s " x $num_cols );
 
3891
 
 
3892
         # Print headers every now and then.  Headers can get really long, so compact them.
 
3893
         my @hdr = @$visible;
 
3894
         if ( $opts{n} ) {
 
3895
            if ( $lines_printed == 0 ) {
 
3896
               print join("\t", @hdr), "\n";
 
3897
               $lines_printed++;
 
3898
            }
 
3899
         }
 
3900
         elsif ( $lines_printed == 0 || $lines_printed > $this_term_size[1] - 2 ) {
 
3901
            @hdr = map { donut(crunch($_, $width), $width) } @hdr;
 
3902
            print join(' ', map { sprintf( "%${width}s", donut($_, $width)) } @hdr) . "\n";
 
3903
            $lines_printed = 1;
 
3904
         }
 
3905
 
 
3906
         # Design a column format for the values.
 
3907
         my $format
 
3908
            = $opts{n}
 
3909
            ? join("\t", map { '%s' } @$visible) . "\n"
 
3910
            : join(' ',  map { "%${width}s" } @hdr) . "\n";
 
3911
 
 
3912
         foreach my $row ( @var_status ) {
 
3913
            printf($format, map { defined $_ ? $_ : '' } @{$row}{ @$visible });
 
3914
            $lines_printed++;
 
3915
         }
 
3916
      }
 
3917
      else { # 'g' mode
 
3918
         # Design a column format for the values.
 
3919
         my $num_cols = scalar(@$visible);
 
3920
         my $width    = $opts{n} ? 0 : int(($this_term_size[0] - $num_cols + 1) / $num_cols);
 
3921
         my $format   = $opts{n} ? ( "%s\t" x $num_cols ) : ( "%-${width}s " x $num_cols );
 
3922
         $format      =~ s/\s$/\n/;
 
3923
 
 
3924
         # Print headers every now and then.
 
3925
         if ( $opts{n} ) {
 
3926
            if ( $lines_printed == 0 ) {
 
3927
               print join("\t", @$visible), "\n";
 
3928
               print join("\t", map { shorten($mvs{$_}) } @$visible), "\n";
 
3929
            }
 
3930
         }
 
3931
         elsif ( $lines_printed == 0 || $lines_printed > $this_term_size[1] - 2 ) {
 
3932
            printf($format, map { donut(crunch($_, $width), $width) } @$visible);
 
3933
            printf($format, map { shorten($mvs{$_} || 0) } @$visible);
 
3934
            $lines_printed = 2;
 
3935
         }
 
3936
 
 
3937
         # Update the max ever seen, and scale by the max ever seen.
 
3938
         my $set = $var_status[0];
 
3939
         foreach my $col ( @$visible ) {
 
3940
            $set->{$col}  = 1 unless defined $set->{$col} && $set->{$col} =~ m/$num_regex/;
 
3941
            $set->{$col}  = ($set->{$col} || 1) / ($set->{Uptime_hires} || 1);
 
3942
            $mvs{$col}    = max($mvs{$col} || 1, $set->{$col});
 
3943
            $set->{$col} /= $mvs{$col};
 
3944
         }
 
3945
         printf($format, map { ( $config{graph_char}->{val} x int( $width * $set->{$_} )) || '.' } @$visible );
 
3946
         $lines_printed++;
 
3947
 
 
3948
      }
 
3949
   }
 
3950
   else { # 'v'
 
3951
      my $first_table = 0;
 
3952
      my @display_lines;
 
3953
      foreach my $tbl ( @visible ) {
 
3954
         push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl);
 
3955
         push @display_lines, get_cxn_errors(@cxns)
 
3956
            if ( $config{debug}->{val} || !$first_table++ );
 
3957
      }
 
3958
      $clear_screen_sub->();
 
3959
      draw_screen( \@display_lines );
 
3960
   }
 
3961
}
 
3962
 
 
3963
# display_explain {{{3
 
3964
sub display_explain {
 
3965
   my $info = shift;
 
3966
   my $cxn   = $info->{cxn};
 
3967
   my $db    = $info->{db};
 
3968
 
 
3969
   my ( $mods, $query ) = rewrite_for_explain($info->{query});
 
3970
 
 
3971
   my @display_lines;
 
3972
 
 
3973
   if ( $query ) {
 
3974
 
 
3975
      my $part = version_ge($dbhs{$cxn}->{dbh}, '5.1.5') ? 'PARTITIONS' : '';
 
3976
      $query = "EXPLAIN $part\n" . $query;
 
3977
 
 
3978
      eval {
 
3979
         if ( $db ) {
 
3980
            do_query($cxn, "use $db");
 
3981
         }
 
3982
         my $sth = do_query($cxn, $query);
 
3983
 
 
3984
         my $res;
 
3985
         while ( $res = $sth->fetchrow_hashref() ) {
 
3986
            map { $res->{$_} ||= '' } ( 'partitions', keys %$res);
 
3987
            my @this_table = create_caption("Sub-Part $res->{id}",
 
3988
               create_table2(
 
3989
                  $tbl_meta{explain}->{visible},
 
3990
                  meta_to_hdr('explain'),
 
3991
                  extract_values($res, $res, $res, 'explain')));
 
3992
            @display_lines = stack_next(\@display_lines, \@this_table, { pad => '  ', vsep => 2 });
 
3993
         }
 
3994
      };
 
3995
 
 
3996
      if ( $EVAL_ERROR ) {
 
3997
         push @display_lines,
 
3998
            '',
 
3999
            "The query could not be explained.  Only SELECT queries can be "
 
4000
            . "explained; innotop tries to rewrite certain REPLACE and INSERT queries "
 
4001
            . "into SELECT, but this doesn't always succeed.";
 
4002
      }
 
4003
 
 
4004
   }
 
4005
   else {
 
4006
      push @display_lines, '', 'The query could not be explained.';
 
4007
   }
 
4008
 
 
4009
   if ( $mods ) {
 
4010
      push @display_lines, '', '[This query has been re-written to be explainable]';
 
4011
   }
 
4012
 
 
4013
   unshift @display_lines, no_ctrl_char($query);
 
4014
   draw_screen(\@display_lines, { raw => 1 } );
 
4015
}
 
4016
 
 
4017
# rewrite_for_explain {{{3
 
4018
sub rewrite_for_explain {
 
4019
   my $query = shift;
 
4020
 
 
4021
   my $mods = 0;
 
4022
   my $orig = $query;
 
4023
   $mods += $query =~ s/^\s*(?:replace|insert).*?select/select/is;
 
4024
   $mods += $query =~ s/^
 
4025
      \s*create\s+(?:temporary\s+)?table
 
4026
      \s+(?:\S+\s+)as\s+select/select/xis;
 
4027
   $mods += $query =~ s/\s+on\s+duplicate\s+key\s+update.*$//is;
 
4028
   return ( $mods, $query );
 
4029
}
 
4030
 
 
4031
# show_optimized_query {{{3
 
4032
sub show_optimized_query {
 
4033
   my $info = shift;
 
4034
   my $cxn   = $info->{cxn};
 
4035
   my $db    = $info->{db};
 
4036
   my $meta  = $dbhs{$cxn};
 
4037
 
 
4038
   my @display_lines;
 
4039
 
 
4040
   my ( $mods, $query ) = rewrite_for_explain($info->{query});
 
4041
 
 
4042
   if ( $mods ) {
 
4043
      push @display_lines, '[This query has been re-written to be explainable]';
 
4044
   }
 
4045
 
 
4046
   if ( $query ) {
 
4047
      push @display_lines, no_ctrl_char($info->{query});
 
4048
 
 
4049
      eval {
 
4050
         if ( $db ) {
 
4051
            do_query($cxn, "use $db");
 
4052
         }
 
4053
         do_query( $cxn, 'EXPLAIN EXTENDED ' . $query ) or die "Can't explain query";
 
4054
         my $sth = do_query($cxn, 'SHOW WARNINGS');
 
4055
         my $res = $sth->fetchall_arrayref({});
 
4056
 
 
4057
         if ( $res ) {
 
4058
            foreach my $result ( @$res ) {
 
4059
               push @display_lines, 'Note:', no_ctrl_char($result->{message});
 
4060
            }
 
4061
         }
 
4062
         else {
 
4063
            push @display_lines, '', 'The query optimization could not be generated.';
 
4064
         }
 
4065
      };
 
4066
 
 
4067
      if ( $EVAL_ERROR ) {
 
4068
         push @display_lines, '', "The optimization could not be generated: $EVAL_ERROR";
 
4069
      }
 
4070
 
 
4071
   }
 
4072
   else {
 
4073
      push @display_lines, '', 'The query optimization could not be generated.';
 
4074
   }
 
4075
 
 
4076
   draw_screen(\@display_lines, { raw => 1 } );
 
4077
}
 
4078
 
 
4079
# display_help {{{3
 
4080
sub display_help {
 
4081
   my $mode = $config{mode}->{val};
 
4082
 
 
4083
   # Get globally mapped keys, then overwrite them with mode-specific ones.
 
4084
   my %keys = map {
 
4085
         $_ => $action_for{$_}->{label}
 
4086
      } keys %action_for;
 
4087
   foreach my $key ( keys %{$modes{$mode}->{action_for}} ) {
 
4088
      $keys{$key} = $modes{$mode}->{action_for}->{$key}->{label};
 
4089
   }
 
4090
   delete $keys{'?'};
 
4091
 
 
4092
   # Split them into three kinds of keys: MODE keys, action keys, and
 
4093
   # magic (special character) keys.
 
4094
   my @modes   = sort grep { m/[A-Z]/   } keys %keys;
 
4095
   my @actions = sort grep { m/[a-z]/   } keys %keys;
 
4096
   my @magic   = sort grep { m/[^A-Z]/i } keys %keys;
 
4097
 
 
4098
   my @display_lines = ( '', 'Switch to a different mode:' );
 
4099
 
 
4100
   # Mode keys
 
4101
   my @all_modes = map { "$_  $modes{$_}->{hdr}" } @modes;
 
4102
   my @col1 = splice(@all_modes, 0, ceil(@all_modes/3));
 
4103
   my @col2 = splice(@all_modes, 0, ceil(@all_modes/2));
 
4104
   my $max1 = max(map {length($_)} @col1);
 
4105
   my $max2 = max(map {length($_)} @col2);
 
4106
   while ( @col1 ) {
 
4107
      push @display_lines, sprintf("   %-${max1}s  %-${max2}s  %s",
 
4108
         (shift @col1      || ''),
 
4109
         (shift @col2      || ''),
 
4110
         (shift @all_modes || ''));
 
4111
   }
 
4112
 
 
4113
   # Action keys
 
4114
   my @all_actions = map { "$_  $keys{$_}" } @actions;
 
4115
   @col1 = splice(@all_actions, 0, ceil(@all_actions/2));
 
4116
   $max1 = max(map {length($_)} @col1);
 
4117
   push @display_lines, '', 'Actions:';
 
4118
   while ( @col1 ) {
 
4119
      push @display_lines, sprintf("   %-${max1}s  %s",
 
4120
         (shift @col1        || ''),
 
4121
         (shift @all_actions || ''));
 
4122
   }
 
4123
 
 
4124
   # Magic keys
 
4125
   my @all_magic = map { sprintf('%4s', $action_for{$_}->{key} || $_) . "  $keys{$_}" } @magic;
 
4126
   @col1 = splice(@all_magic, 0, ceil(@all_magic/2));
 
4127
   $max1 = max(map {length($_)} @col1);
 
4128
   push @display_lines, '', 'Other:';
 
4129
   while ( @col1 ) {
 
4130
      push @display_lines, sprintf("%-${max1}s%s",
 
4131
         (shift @col1      || ''),
 
4132
         (shift @all_magic || ''));
 
4133
   }
 
4134
 
 
4135
   $clear_screen_sub->();
 
4136
   draw_screen(\@display_lines, { show_all => 1 } );
 
4137
   pause();
 
4138
   $clear_screen_sub->();
 
4139
}
 
4140
 
 
4141
# show_full_query {{{3
 
4142
sub show_full_query {
 
4143
   my $info = shift;
 
4144
   my @display_lines = no_ctrl_char($info->{query});
 
4145
   draw_screen(\@display_lines, { raw => 1 });
 
4146
}
 
4147
 
 
4148
# Formatting functions {{{2
 
4149
 
 
4150
# create_table2 {{{3
 
4151
# Makes a two-column table, labels on left, data on right.
 
4152
# Takes refs of @cols, %labels and %data, %user_prefs
 
4153
sub create_table2 {
 
4154
   my ( $cols, $labels, $data, $user_prefs ) = @_;
 
4155
   my @rows;
 
4156
 
 
4157
   if ( @$cols && %$data ) {
 
4158
 
 
4159
      # Override defaults
 
4160
      my $p = {
 
4161
         just  => '',
 
4162
         sep   => ':',
 
4163
         just1 => '-',
 
4164
      };
 
4165
      if ( $user_prefs ) {
 
4166
         map { $p->{$_} = $user_prefs->{$_} } keys %$user_prefs;
 
4167
      }
 
4168
 
 
4169
      # Fix undef values
 
4170
      map { $data->{$_} = '' unless defined $data->{$_} } @$cols;
 
4171
 
 
4172
      # Format the table
 
4173
      my $max_l = max(map{ length($labels->{$_}) } @$cols);
 
4174
      my $max_v = max(map{ length($data->{$_}) } @$cols);
 
4175
      my $format    = "%$p->{just}${max_l}s$p->{sep} %$p->{just1}${max_v}s";
 
4176
      foreach my $col ( @$cols ) {
 
4177
         push @rows, sprintf($format, $labels->{$col}, $data->{$col});
 
4178
      }
 
4179
   }
 
4180
   return @rows;
 
4181
}
 
4182
 
 
4183
# stack_next {{{3
 
4184
# Stacks one display section next to the other.  Accepts left-hand arrayref,
 
4185
# right-hand arrayref, and options hashref.  Tries to stack as high as
 
4186
# possible, so
 
4187
# aaaaaa
 
4188
# bbb
 
4189
# can stack ccc next to the bbb.
 
4190
# NOTE: this DOES modify its arguments, even though it returns a new array.
 
4191
sub stack_next {
 
4192
   my ( $left, $right, $user_prefs ) = @_;
 
4193
   my @result;
 
4194
 
 
4195
   my $p = {
 
4196
      pad   => ' ',
 
4197
      vsep  => 0,
 
4198
   };
 
4199
   if ( $user_prefs ) {
 
4200
      map { $p->{$_} = $user_prefs->{$_} } keys %$user_prefs;
 
4201
   }
 
4202
 
 
4203
   # Find out how wide the LHS can be and still let the RHS fit next to it.
 
4204
   my $pad   = $p->{pad};
 
4205
   my $max_r = max( map { length($_) } @$right) || 0;
 
4206
   my $max_l = $this_term_size[0] - $max_r - length($pad);
 
4207
 
 
4208
   # Find the minimum row on the LHS that the RHS will fit next to.
 
4209
   my $i = scalar(@$left) - 1;
 
4210
   while ( $i >= 0 && length($left->[$i]) <= $max_l ) {
 
4211
      $i--;
 
4212
   }
 
4213
   $i++;
 
4214
   my $offset = $i;
 
4215
 
 
4216
   if ( $i < scalar(@$left) ) {
 
4217
      # Find the max width of the section of the LHS against which the RHS
 
4218
      # will sit.
 
4219
      my $max_i_in_common = min($i + scalar(@$right) - 1, scalar(@$left) - 1);
 
4220
      my $max_width = max( map { length($_) } @{$left}[$i..$max_i_in_common]);
 
4221
 
 
4222
      # Append the RHS onto the LHS until one runs out.
 
4223
      while ( $i < @$left && $i - $offset < @$right ) {
 
4224
         my $format = "%-${max_width}s$pad%${max_r}s";
 
4225
         $left->[$i] = sprintf($format, $left->[$i], $right->[$i - $offset]);
 
4226
         $i++;
 
4227
      }
 
4228
      while ( $i - $offset < @$right ) {
 
4229
         # There is more RHS to push on the end of the array
 
4230
         push @$left,
 
4231
            sprintf("%${max_width}s$pad%${max_r}s", ' ', $right->[$i - $offset]);
 
4232
         $i++;
 
4233
      }
 
4234
      push @result, @$left;
 
4235
   }
 
4236
   else {
 
4237
      # There is no room to put them side by side.  Add them below, with
 
4238
      # a blank line above them if specified.
 
4239
      push @result, @$left;
 
4240
      push @result, (' ' x $this_term_size[0]) if $p->{vsep} && @$left;
 
4241
      push @result, @$right;
 
4242
   }
 
4243
   return @result;
 
4244
}
 
4245
 
 
4246
# create_caption {{{3
 
4247
sub create_caption {
 
4248
   my ( $caption, @rows ) = @_;
 
4249
   if ( @rows ) {
 
4250
 
 
4251
      # Calculate the width of what will be displayed, so it can be centered
 
4252
      # in that space.  When the thing is wider than the display, center the
 
4253
      # caption in the display.
 
4254
      my $width = min($this_term_size[0], max(map { length(ref($_) ? $_->[0] : $_) } @rows));
 
4255
 
 
4256
      my $cap_len = length($caption);
 
4257
 
 
4258
      # It may be narrow enough to pad the sides with underscores and save a
 
4259
      # line on the screen.
 
4260
      if ( $cap_len <= $width - 6 ) {
 
4261
         my $left = int(($width - 2 - $cap_len) / 2);
 
4262
         unshift @rows,
 
4263
            ("_" x $left) . " $caption " . ("_" x ($width - $left - $cap_len - 2));
 
4264
      }
 
4265
 
 
4266
      # The caption is too wide to add underscores on each side.
 
4267
      else {
 
4268
 
 
4269
         # Color is supported, so we can use terminal underlining.
 
4270
         if ( $config{color}->{val} ) {
 
4271
            my $left = int(($width - $cap_len) / 2);
 
4272
            unshift @rows, [
 
4273
               (" " x $left) . $caption . (" " x ($width - $left - $cap_len)),
 
4274
               'underline',
 
4275
            ];
 
4276
         }
 
4277
 
 
4278
         # Color is not supported, so we have to add a line underneath to separate the
 
4279
         # caption from whatever it's captioning.
 
4280
         else {
 
4281
            my $left = int(($width - $cap_len) / 2);
 
4282
            unshift @rows, ('-' x $width);
 
4283
            unshift @rows, (" " x $left) . $caption . (" " x ($width - $left - $cap_len));
 
4284
         }
 
4285
 
 
4286
         # The caption is wider than the thing it labels, so we have to pad the
 
4287
         # thing it labels to a consistent width.
 
4288
         if ( $cap_len > $width ) {
 
4289
            @rows = map {
 
4290
               ref($_)
 
4291
                  ? [ sprintf('%-' . $cap_len . 's', $_->[0]), $_->[1] ]
 
4292
                  : sprintf('%-' . $cap_len . 's', $_);
 
4293
            } @rows;
 
4294
         }
 
4295
 
 
4296
      }
 
4297
   }
 
4298
   return @rows;
 
4299
}
 
4300
 
 
4301
# create_table {{{3
 
4302
# Input: an arrayref of columns, hashref of col info, and an arrayref of hashes
 
4303
# Example: [ 'a', 'b' ]
 
4304
#          { a => spec, b => spec }
 
4305
#          [ { a => 1, b => 2}, { a => 3, b => 4 } ]
 
4306
# The 'spec' is a hashref of hdr => label, just => ('-' or '').  It also supports min and max-widths
 
4307
# vi the minw and maxw params.
 
4308
# Output: an array of strings, one per row.
 
4309
# Example:
 
4310
# Column One Column Two
 
4311
# ---------- ----------
 
4312
# 1          2
 
4313
# 3          4
 
4314
sub create_table {
 
4315
   my ( $cols, $info, $data, $prefs ) = @_;
 
4316
   $prefs ||= {};
 
4317
   $prefs->{no_hdr} ||= ($opts{n} && $clock != 1);
 
4318
 
 
4319
   # Truncate rows that will surely be off screen even if this is the only table.
 
4320
   if ( !$opts{n} && !$prefs->{raw} && !$prefs->{show_all} && $this_term_size[1] < @$data-1 ) {
 
4321
      $data = [ @$data[0..$this_term_size[1] - 1] ];
 
4322
   }
 
4323
 
 
4324
   my @rows = ();
 
4325
 
 
4326
   if ( @$cols && %$info ) {
 
4327
 
 
4328
      # Fix undef values, collapse whitespace.
 
4329
      foreach my $row ( @$data ) {
 
4330
         map { $row->{$_} = collapse_ws($row->{$_}) } @$cols;
 
4331
      }
 
4332
 
 
4333
      my $col_sep = $opts{n} ? "\t" : '  ';
 
4334
 
 
4335
      # Find each column's max width.
 
4336
      my %width_for;
 
4337
      if ( !$opts{n} ) {
 
4338
         %width_for = map {
 
4339
            my $col_name  = $_;
 
4340
            if ( $info->{$_}->{dec} ) {
 
4341
               # Align along the decimal point
 
4342
               my $max_rodp = max(0, map { $_->{$col_name} =~ m/([^\s\d-].*)$/ ? length($1) : 0 } @$data);
 
4343
               foreach my $row ( @$data ) {
 
4344
                  my $col = $row->{$col_name};
 
4345
                  my ( $l, $r ) = $col =~ m/^([\s\d]*)(.*)$/;
 
4346
                  $row->{$col_name} = sprintf("%s%-${max_rodp}s", $l, $r);
 
4347
               }
 
4348
            }
 
4349
            my $max_width = max( length($info->{$_}->{hdr}), map { length($_->{$col_name}) } @$data);
 
4350
            if ( $info->{$col_name}->{maxw} ) {
 
4351
               $max_width = min( $max_width, $info->{$col_name}->{maxw} );
 
4352
            }
 
4353
            if ( $info->{$col_name}->{minw} ) {
 
4354
               $max_width = max( $max_width, $info->{$col_name}->{minw} );
 
4355
            }
 
4356
            $col_name => $max_width;
 
4357
         } @$cols;
 
4358
      }
 
4359
 
 
4360
      # The table header.
 
4361
      if ( !$config{hide_hdr}->{val} && !$prefs->{no_hdr} ) {
 
4362
         push @rows, $opts{n}
 
4363
            ? join( $col_sep, @$cols )
 
4364
            : join( $col_sep, map { sprintf( "%-$width_for{$_}s", trunc($info->{$_}->{hdr}, $width_for{$_}) ) } @$cols );
 
4365
         if ( $config{color}->{val} && $config{header_highlight}->{val} ) {
 
4366
            push @rows, [ pop @rows, $config{header_highlight}->{val} ];
 
4367
         }
 
4368
         elsif ( !$opts{n} ) {
 
4369
            push @rows, join( $col_sep, map { "-" x $width_for{$_} } @$cols );
 
4370
         }
 
4371
      }
 
4372
 
 
4373
      # The table data.
 
4374
      if ( $opts{n} ) {
 
4375
         foreach my $item ( @$data ) {
 
4376
            push @rows, join($col_sep, map { $item->{$_} } @$cols );
 
4377
         }
 
4378
      }
 
4379
      else {
 
4380
         my $format = join( $col_sep,
 
4381
            map { "%$info->{$_}->{just}$width_for{$_}s" } @$cols );
 
4382
         foreach my $item ( @$data ) {
 
4383
            my $row = sprintf($format, map { trunc($item->{$_}, $width_for{$_}) } @$cols );
 
4384
            if ( $config{color}->{val} && $item->{_color} ) {
 
4385
               push @rows, [ $row, $item->{_color} ];
 
4386
            }
 
4387
            else {
 
4388
               push @rows, $row;
 
4389
            }
 
4390
         }
 
4391
      }
 
4392
   }
 
4393
 
 
4394
   return @rows;
 
4395
}
 
4396
 
 
4397
# Aggregates a table.  If $group_by is an arrayref of columns, the grouping key
 
4398
# is the specified columns; otherwise it's just the empty string (e.g.
 
4399
# everything is grouped as one group).
 
4400
sub apply_group_by {
 
4401
   my ( $tbl, $group_by, @rows ) = @_;
 
4402
   my $meta = $tbl_meta{$tbl};
 
4403
   my %is_group = map { $_ => 1 } @$group_by;
 
4404
   my @non_grp  = grep { !$is_group{$_} } keys %{$meta->{cols}};
 
4405
 
 
4406
   my %temp_table;
 
4407
   foreach my $row ( @rows ) {
 
4408
      my $group_key
 
4409
         = @$group_by
 
4410
         ? '{' . join('}{', map { defined $_ ? $_ : '' } @{$row}{@$group_by}) . '}'
 
4411
         : '';
 
4412
      $temp_table{$group_key} ||= [];
 
4413
      push @{$temp_table{$group_key}}, $row;
 
4414
   }
 
4415
 
 
4416
   # Crush the rows together...
 
4417
   my @new_rows;
 
4418
   foreach my $key ( sort keys %temp_table ) {
 
4419
      my $group = $temp_table{$key};
 
4420
      my %new_row;
 
4421
      @new_row{@$group_by} = @{$group->[0]}{@$group_by};
 
4422
      foreach my $col ( @non_grp ) {
 
4423
         my $agg = $meta->{cols}->{$col}->{agg} || 'first';
 
4424
         $new_row{$col} = $agg_funcs{$agg}->( map { $_->{$col} } @$group );
 
4425
      }
 
4426
      push @new_rows, \%new_row;
 
4427
   }
 
4428
   return @new_rows;
 
4429
}
 
4430
 
 
4431
# set_to_tbl {{{3
 
4432
# Unifies all the work of filtering, sorting etc.  Alters the input.
 
4433
# TODO: pull all the little pieces out into subroutines and stick events in each of them.
 
4434
sub set_to_tbl {
 
4435
   my ( $rows, $tbl ) = @_;
 
4436
   my $meta = $tbl_meta{$tbl} or die "No such table $tbl in tbl_meta";
 
4437
 
 
4438
   if ( !$meta->{pivot} ) {
 
4439
 
 
4440
      # Hook in event listeners
 
4441
      foreach my $listener ( @{$event_listener_for{set_to_tbl_pre_filter}} ) {
 
4442
         $listener->set_to_tbl_pre_filter($rows, $tbl);
 
4443
      }
 
4444
 
 
4445
      # Apply filters.  Note that if the table is pivoted, filtering and sorting
 
4446
      # are applied later.
 
4447
      foreach my $filter ( @{$meta->{filters}} ) {
 
4448
         eval {
 
4449
            @$rows = grep { $filters{$filter}->{func}->($_) } @$rows;
 
4450
         };
 
4451
         if ( $EVAL_ERROR && $config{debug}->{val} ) {
 
4452
            die $EVAL_ERROR;
 
4453
         }
 
4454
      }
 
4455
 
 
4456
      foreach my $listener ( @{$event_listener_for{set_to_tbl_pre_sort}} ) {
 
4457
         $listener->set_to_tbl_pre_sort($rows, $tbl);
 
4458
      }
 
4459
 
 
4460
      # Sort.  Note that if the table is pivoted, sorting might have the wrong
 
4461
      # columns and it could crash.  This will only be an issue if it's possible
 
4462
      # to toggle pivoting on and off, which it's not at the moment.
 
4463
      if ( @$rows && $meta->{sort_func} && !$meta->{aggregate} ) {
 
4464
         if ( $meta->{sort_dir} > 0 ) {
 
4465
            @$rows = $meta->{sort_func}->( @$rows );
 
4466
         }
 
4467
         else {
 
4468
            @$rows = reverse $meta->{sort_func}->( @$rows );
 
4469
         }
 
4470
      }
 
4471
 
 
4472
   }
 
4473
 
 
4474
   # Stop altering arguments now.
 
4475
   my @rows = @$rows;
 
4476
 
 
4477
   foreach my $listener ( @{$event_listener_for{set_to_tbl_pre_group}} ) {
 
4478
      $listener->set_to_tbl_pre_group(\@rows, $tbl);
 
4479
   }
 
4480
 
 
4481
   # Apply group-by.
 
4482
   if ( $meta->{aggregate} ) {
 
4483
      @rows = apply_group_by($tbl, $meta->{group_by}, @rows);
 
4484
 
 
4485
      # Sort.  Note that if the table is pivoted, sorting might have the wrong
 
4486
      # columns and it could crash.  This will only be an issue if it's possible
 
4487
      # to toggle pivoting on and off, which it's not at the moment.
 
4488
      if ( @rows && $meta->{sort_func} ) {
 
4489
         if ( $meta->{sort_dir} > 0 ) {
 
4490
            @rows = $meta->{sort_func}->( @rows );
 
4491
         }
 
4492
         else {
 
4493
            @rows = reverse $meta->{sort_func}->( @rows );
 
4494
         }
 
4495
      }
 
4496
 
 
4497
   }
 
4498
 
 
4499
   foreach my $listener ( @{$event_listener_for{set_to_tbl_pre_colorize}} ) {
 
4500
      $listener->set_to_tbl_pre_colorize(\@rows, $tbl);
 
4501
   }
 
4502
 
 
4503
   if ( !$meta->{pivot} ) {
 
4504
      # Colorize.  Adds a _color column to rows.
 
4505
      if ( @rows && $meta->{color_func} ) {
 
4506
         eval {
 
4507
            foreach my $row ( @rows ) {
 
4508
               $row->{_color} = $meta->{color_func}->($row);
 
4509
            }
 
4510
         };
 
4511
         if ( $EVAL_ERROR ) {
 
4512
            pause($EVAL_ERROR);
 
4513
         }
 
4514
      }
 
4515
   }
 
4516
 
 
4517
   foreach my $listener ( @{$event_listener_for{set_to_tbl_pre_transform}} ) {
 
4518
      $listener->set_to_tbl_pre_transform(\@rows, $tbl);
 
4519
   }
 
4520
 
 
4521
   # Apply_transformations.
 
4522
   if ( @rows ) {
 
4523
      my $cols = $meta->{cols};
 
4524
      foreach my $col ( keys %{$rows->[0]} ) {
 
4525
         # Don't auto-vivify $tbl_meta{tbl}-{cols}->{_color}->{trans}
 
4526
         next if $col eq '_color';
 
4527
         foreach my $trans ( @{$cols->{$col}->{trans}} ) {
 
4528
            map { $_->{$col} = $trans_funcs{$trans}->($_->{$col}) } @rows;
 
4529
         }
 
4530
      }
 
4531
   }
 
4532
 
 
4533
   my ($fmt_cols, $fmt_meta);
 
4534
 
 
4535
   # Pivot.
 
4536
   if ( $meta->{pivot} ) {
 
4537
 
 
4538
      foreach my $listener ( @{$event_listener_for{set_to_tbl_pre_pivot}} ) {
 
4539
         $listener->set_to_tbl_pre_pivot(\@rows, $tbl);
 
4540
      }
 
4541
 
 
4542
      my @vars = @{$meta->{visible}};
 
4543
      my @tmp  = map { { name => $_ } } @vars;
 
4544
      my @cols = 'name';
 
4545
      foreach my $i ( 0..@$rows-1 ) {
 
4546
         my $col = "set_$i";
 
4547
         push @cols, $col;
 
4548
         foreach my $j ( 0..@vars-1 ) {
 
4549
            $tmp[$j]->{$col} = $rows[$i]->{$vars[$j]};
 
4550
         }
 
4551
      }
 
4552
      $fmt_meta = { map { $_ => { hdr => $_, just => '-' } } @cols };
 
4553
      $fmt_cols = \@cols;
 
4554
      @rows = @tmp;
 
4555
 
 
4556
      # Hook in event listeners
 
4557
      foreach my $listener ( @{$event_listener_for{set_to_tbl_pre_filter}} ) {
 
4558
         $listener->set_to_tbl_pre_filter($rows, $tbl);
 
4559
      }
 
4560
 
 
4561
      # Apply filters.
 
4562
      foreach my $filter ( @{$meta->{filters}} ) {
 
4563
         eval {
 
4564
            @rows = grep { $filters{$filter}->{func}->($_) } @rows;
 
4565
         };
 
4566
         if ( $EVAL_ERROR && $config{debug}->{val} ) {
 
4567
            die $EVAL_ERROR;
 
4568
         }
 
4569
      }
 
4570
 
 
4571
      foreach my $listener ( @{$event_listener_for{set_to_tbl_pre_sort}} ) {
 
4572
         $listener->set_to_tbl_pre_sort($rows, $tbl);
 
4573
      }
 
4574
 
 
4575
      # Sort.
 
4576
      if ( @rows && $meta->{sort_func} ) {
 
4577
         if ( $meta->{sort_dir} > 0 ) {
 
4578
            @rows = $meta->{sort_func}->( @rows );
 
4579
         }
 
4580
         else {
 
4581
            @rows = reverse $meta->{sort_func}->( @rows );
 
4582
         }
 
4583
      }
 
4584
 
 
4585
   }
 
4586
   else {
 
4587
      # If the table isn't pivoted, just show all columns that are supposed to
 
4588
      # be shown; but eliminate aggonly columns if the table isn't aggregated.
 
4589
      my $aggregated = $meta->{aggregate};
 
4590
      $fmt_cols = [ grep { $aggregated || !$meta->{cols}->{$_}->{aggonly} } @{$meta->{visible}} ];
 
4591
      $fmt_meta = { map  { $_ => $meta->{cols}->{$_}                      } @$fmt_cols };
 
4592
 
 
4593
      # If the table is aggregated, re-order the group_by columns to the left of
 
4594
      # the display.
 
4595
      if ( $aggregated ) {
 
4596
         my %is_group = map { $_ => 1 } @{$meta->{group_by}};
 
4597
         $fmt_cols = [ @{$meta->{group_by}}, grep { !$is_group{$_} } @$fmt_cols ];
 
4598
      }
 
4599
   }
 
4600
 
 
4601
   foreach my $listener ( @{$event_listener_for{set_to_tbl_pre_create}} ) {
 
4602
      $listener->set_to_tbl_pre_create(\@rows, $tbl);
 
4603
   }
 
4604
 
 
4605
   @rows = create_table( $fmt_cols, $fmt_meta, \@rows);
 
4606
   if ( !$meta->{hide_caption} && !$opts{n} && $config{display_table_captions}->{val} ) {
 
4607
      @rows = create_caption($meta->{capt}, @rows)
 
4608
   }
 
4609
 
 
4610
   foreach my $listener ( @{$event_listener_for{set_to_tbl_post_create}} ) {
 
4611
      $listener->set_to_tbl_post_create(\@rows, $tbl);
 
4612
   }
 
4613
 
 
4614
   return @rows;
 
4615
}
 
4616
 
 
4617
# meta_to_hdr {{{3
 
4618
sub meta_to_hdr {
 
4619
   my $tbl = shift;
 
4620
   my $meta = $tbl_meta{$tbl};
 
4621
   my %labels = map { $_ => $meta->{cols}->{$_}->{hdr} } @{$meta->{visible}};
 
4622
   return \%labels;
 
4623
}
 
4624
 
 
4625
# commify {{{3
 
4626
# From perlfaq5: add commas.
 
4627
sub commify {
 
4628
   my ( $num ) = @_;
 
4629
   $num = 0 unless defined $num;
 
4630
   $num =~ s/(^[-+]?\d+?(?=(?>(?:\d{3})+)(?!\d))|\G\d{3}(?=\d))/$1,/g;
 
4631
   return $num;
 
4632
}
 
4633
 
 
4634
# set_precision {{{3
 
4635
# Trim to desired precision.
 
4636
sub set_precision {
 
4637
   my ( $num, $precision ) = @_;
 
4638
   $precision = $config{num_digits}->{val} if !defined $precision;
 
4639
   sprintf("%.${precision}f", $num);
 
4640
}
 
4641
 
 
4642
# percent {{{3
 
4643
# Convert to percent
 
4644
sub percent {
 
4645
   my ( $num ) = @_;
 
4646
   $num = 0 unless defined $num;
 
4647
   my $digits = $config{num_digits}->{val};
 
4648
   return sprintf("%.${digits}f", $num * 100)
 
4649
      . ($config{show_percent}->{val} ? '%' : '');
 
4650
}
 
4651
 
 
4652
# shorten {{{3
 
4653
sub shorten {
 
4654
   my ( $num, $opts ) = @_;
 
4655
 
 
4656
   return $num if !defined($num) || $opts{n} || $num !~ m/$num_regex/;
 
4657
 
 
4658
   $opts ||= {};
 
4659
   my $pad = defined $opts->{pad} ? $opts->{pad} : '';
 
4660
   my $num_digits = defined $opts->{num_digits}
 
4661
      ? $opts->{num_digits}
 
4662
      : $config{num_digits}->{val};
 
4663
   my $force = defined $opts->{force};
 
4664
 
 
4665
   my $n = 0;
 
4666
   while ( $num >= 1_024 ) {
 
4667
      $num /= 1_024;
 
4668
      ++$n;
 
4669
   }
 
4670
   return sprintf(
 
4671
      $num =~ m/\./ || $n || $force
 
4672
         ? "%.${num_digits}f%s"
 
4673
         : '%d',
 
4674
      $num, ($pad,'k','M','G', 'T')[$n]);
 
4675
 
 
4676
}
 
4677
 
 
4678
# Utility functions {{{2
 
4679
# unique {{{3
 
4680
sub unique {
 
4681
   my %seen;
 
4682
   return grep { !$seen{$_}++ } @_;
 
4683
}
 
4684
 
 
4685
# make_color_func {{{3
 
4686
sub make_color_func {
 
4687
   my ( $tbl ) = @_;
 
4688
   my @criteria;
 
4689
   foreach my $spec ( @{$tbl->{colors}} ) {
 
4690
      next unless exists $comp_ops{$spec->{op}};
 
4691
      my $val = $spec->{op} =~ m/^(?:eq|ne|le|ge|lt|gt)$/ ? "'$spec->{arg}'"
 
4692
              : $spec->{op} =~ m/^(?:=~|!~)$/             ? "m/" . quotemeta($spec->{arg}) . "/"
 
4693
              :                                             $spec->{arg};
 
4694
      push @criteria,
 
4695
         "( defined \$set->{$spec->{col}} && \$set->{$spec->{col}} $spec->{op} $val ) { return '$spec->{color}'; }";
 
4696
   }
 
4697
   return undef unless @criteria;
 
4698
   my $sub = eval 'sub { my ( $set ) = @_; if ' . join(" elsif ", @criteria) . '}';
 
4699
   die if $EVAL_ERROR;
 
4700
   return $sub;
 
4701
}
 
4702
 
 
4703
# make_sort_func {{{3
 
4704
# Gets a list of sort columns from the table, like "+cxn -time" and returns a
 
4705
# subroutine that will sort that way.
 
4706
sub make_sort_func {
 
4707
   my ( $tbl ) = @_;
 
4708
   my @criteria;
 
4709
 
 
4710
   # Pivoted tables can be sorted by 'name' and set_x columns; others must be
 
4711
   # sorted by existing columns.  TODO: this will crash if you toggle between
 
4712
   # pivoted and nonpivoted.  I have several other 'crash' notes about this if
 
4713
   # this ever becomes possible.
 
4714
 
 
4715
   if ( $tbl->{pivot} ) {
 
4716
      # Sort type is not really possible on pivoted columns, because a 'column'
 
4717
      # contains data from an entire non-pivoted row, so there could be a mix of
 
4718
      # numeric and non-numeric data.  Thus everything has to be 'cmp' type.
 
4719
      foreach my $col ( split(/\s+/, $tbl->{sort_cols} ) ) {
 
4720
         next unless $col;
 
4721
         my ( $dir, $name ) = $col =~ m/([+-])?(\w+)$/;
 
4722
         next unless $name && $name =~ m/^(?:name|set_\d+)$/;
 
4723
         $dir ||= '+';
 
4724
         my $op = 'cmp';
 
4725
         my $df = "''";
 
4726
         push @criteria,
 
4727
            $dir eq '+'
 
4728
            ? "(\$a->{$name} || $df) $op (\$b->{$name} || $df)"
 
4729
            : "(\$b->{$name} || $df) $op (\$a->{$name} || $df)";
 
4730
      }
 
4731
   }
 
4732
   else {
 
4733
      foreach my $col ( split(/\s+/, $tbl->{sort_cols} ) ) {
 
4734
         next unless $col;
 
4735
         my ( $dir, $name ) = $col =~ m/([+-])?(\w+)$/;
 
4736
         next unless $name && $tbl->{cols}->{$name};
 
4737
         $dir ||= '+';
 
4738
         my $op = $tbl->{cols}->{$name}->{num} ? "<=>" : "cmp";
 
4739
         my $df = $tbl->{cols}->{$name}->{num} ? "0"   : "''";
 
4740
         push @criteria,
 
4741
            $dir eq '+'
 
4742
            ? "(\$a->{$name} || $df) $op (\$b->{$name} || $df)"
 
4743
            : "(\$b->{$name} || $df) $op (\$a->{$name} || $df)";
 
4744
      }
 
4745
   }
 
4746
   return sub { return @_ } unless @criteria;
 
4747
   my $sub = eval 'sub { sort {' . join("||", @criteria) . '} @_; }';
 
4748
   die if $EVAL_ERROR;
 
4749
   return $sub;
 
4750
}
 
4751
 
 
4752
# trunc {{{3
 
4753
# Shortens text to specified length.
 
4754
sub trunc {
 
4755
   my ( $text, $len ) = @_;
 
4756
   if ( length($text) <= $len ) {
 
4757
      return $text;
 
4758
   }
 
4759
   return substr($text, 0, $len);
 
4760
}
 
4761
 
 
4762
# donut {{{3
 
4763
# Takes out the middle of text to shorten it.
 
4764
sub donut {
 
4765
   my ( $text, $len ) = @_;
 
4766
   return $text if length($text) <= $len;
 
4767
   my $max = length($text) - $len;
 
4768
   my $min = $max - 1;
 
4769
 
 
4770
   # Try to remove a single "word" from somewhere in the center
 
4771
   if ( $text =~ s/_[^_]{$min,$max}_/_/ ) {
 
4772
      return $text;
 
4773
   }
 
4774
 
 
4775
   # Prefer removing the end of a "word"
 
4776
   if ( $text =~ s/([^_]+)[^_]{$max}_/$1_/ ) {
 
4777
      return $text;
 
4778
   }
 
4779
 
 
4780
   $text = substr($text, 0, int($len/2))
 
4781
         . "_"
 
4782
         . substr($text, int($len/2) + $max + 1);
 
4783
   return $text;
 
4784
}
 
4785
 
 
4786
# crunch {{{3
 
4787
# Removes vowels and compacts repeated letters to shorten text.
 
4788
sub crunch {
 
4789
   my ( $text, $len ) = @_;
 
4790
   return $text if $len && length($text) <= $len;
 
4791
   $text =~ s/^IB_\w\w_//;
 
4792
   $text =~ s/(?<![_ ])[aeiou]//g;
 
4793
   $text =~ s/(.)\1+/$1/g;
 
4794
   return $text;
 
4795
}
 
4796
 
 
4797
# collapse_ws {{{3
 
4798
# Collapses all whitespace to a single space.
 
4799
sub collapse_ws {
 
4800
   my ( $text ) = @_;
 
4801
   return '' unless defined $text;
 
4802
   $text =~ s/\s+/ /g;
 
4803
   return $text;
 
4804
}
 
4805
 
 
4806
# Strips out non-printable characters within fields, which freak terminals out.
 
4807
sub no_ctrl_char {
 
4808
   my ( $text ) = @_;
 
4809
   return '' unless defined $text;
 
4810
   my $charset = $config{charset}->{val};
 
4811
   if ( $charset && $charset eq 'unicode' ) {
 
4812
      $text =~ s/
 
4813
         ("(?:(?!(?<!\\)").)*"  # Double-quoted string
 
4814
         |'(?:(?!(?<!\\)').)*') # Or single-quoted string
 
4815
         /$1 =~ m#\p{IsC}# ? "[BINARY]" : $1/egx;
 
4816
   }
 
4817
   elsif ( $charset && $charset eq 'none' ) {
 
4818
      $text =~ s/
 
4819
         ("(?:(?!(?<!\\)").)*"
 
4820
         |'(?:(?!(?<!\\)').)*')
 
4821
         /[TEXT]/gx;
 
4822
   }
 
4823
   else { # The default is 'ascii'
 
4824
      $text =~ s/
 
4825
         ("(?:(?!(?<!\\)").)*"
 
4826
         |'(?:(?!(?<!\\)').)*')
 
4827
         /$1 =~ m#[^\040-\176]# ? "[BINARY]" : $1/egx;
 
4828
   }
 
4829
   return $text;
 
4830
}
 
4831
 
 
4832
# word_wrap {{{3
 
4833
# Wraps text at word boundaries so it fits the screen.
 
4834
sub word_wrap {
 
4835
   my ( $text, $width) = @_;
 
4836
   $width ||= $this_term_size[0];
 
4837
   $text =~ s/(.{0,$width})(?:\s+|$)/$1\n/g;
 
4838
   $text =~ s/ +$//mg;
 
4839
   return $text;
 
4840
}
 
4841
 
 
4842
# draw_screen {{{3
 
4843
# Prints lines to the screen.  The first argument is an arrayref.  Each
 
4844
# element of the array is either a string or an arrayref.  If it's a string it
 
4845
# just gets printed.  If it's an arrayref, the first element is the string to
 
4846
# print, and the second is args to colored().
 
4847
sub draw_screen {
 
4848
   my ( $display_lines, $prefs ) = @_;
 
4849
   if ( !$opts{n} && $config{show_statusbar}->{val} ) {
 
4850
      unshift @$display_lines, create_statusbar();
 
4851
   }
 
4852
 
 
4853
   foreach my $listener ( @{$event_listener_for{draw_screen}} ) {
 
4854
      $listener->draw_screen($display_lines);
 
4855
   }
 
4856
 
 
4857
   $clear_screen_sub->()
 
4858
      if $prefs->{clear} || !$modes{$config{mode}->{val}}->{no_clear_screen};
 
4859
   if ( $opts{n} || $prefs->{raw} ) {
 
4860
      my $num_lines = 0;
 
4861
      print join("\n",
 
4862
         map {
 
4863
            $num_lines++;
 
4864
            ref $_
 
4865
               ? colored($_->[0], $_->[1])
 
4866
               : $_;
 
4867
         }
 
4868
         grep { !$opts{n} || $_ } # Suppress empty lines
 
4869
         @$display_lines);
 
4870
      if ( $opts{n} && $num_lines ) {
 
4871
         print "\n";
 
4872
      }
 
4873
   }
 
4874
   else {
 
4875
      my $max_lines = $prefs->{show_all}
 
4876
         ? scalar(@$display_lines)- 1
 
4877
         : min(scalar(@$display_lines), $this_term_size[1]);
 
4878
      print join("\n",
 
4879
         map {
 
4880
            ref $_
 
4881
               ? colored(substr($_->[0], 0, $this_term_size[0]), $_->[1])
 
4882
               : substr($_, 0, $this_term_size[0]);
 
4883
         } @$display_lines[0..$max_lines - 1]);
 
4884
   }
 
4885
}
 
4886
 
 
4887
# secs_to_time {{{3
 
4888
sub secs_to_time {
 
4889
   my ( $secs, $fmt ) = @_;
 
4890
   $secs ||= 0;
 
4891
   return '00:00' unless $secs;
 
4892
 
 
4893
   # Decide what format to use, if not given
 
4894
   $fmt ||= $secs >= 86_400 ? 'd'
 
4895
          : $secs >= 3_600  ? 'h'
 
4896
          :                   'm';
 
4897
 
 
4898
   return
 
4899
      $fmt eq 'd' ? sprintf(
 
4900
         "%d+%02d:%02d:%02d",
 
4901
         int($secs / 86_400),
 
4902
         int(($secs % 86_400) / 3_600),
 
4903
         int(($secs % 3_600) / 60),
 
4904
         $secs % 60)
 
4905
      : $fmt eq 'h' ? sprintf(
 
4906
         "%02d:%02d:%02d",
 
4907
         int(($secs % 86_400) / 3_600),
 
4908
         int(($secs % 3_600) / 60),
 
4909
         $secs % 60)
 
4910
      : sprintf(
 
4911
         "%02d:%02d",
 
4912
         int(($secs % 3_600) / 60),
 
4913
         $secs % 60);
 
4914
}
 
4915
 
 
4916
# dulint_to_int {{{3
 
4917
# Takes a number that InnoDB formats as two ulint integers, like transaction IDs
 
4918
# and such, and turns it into a single integer
 
4919
sub dulint_to_int {
 
4920
   my $num = shift;
 
4921
   return 0 unless $num;
 
4922
   my ( $high, $low ) = $num =~ m/^(\d+) (\d+)$/;
 
4923
   return $low unless $high;
 
4924
   return $low + ( $high * $MAX_ULONG );
 
4925
}
 
4926
 
 
4927
# create_statusbar {{{3
 
4928
sub create_statusbar {
 
4929
   my $mode = $config{mode}->{val};
 
4930
   my @cxns = sort { $a cmp $b } get_connections();
 
4931
 
 
4932
   my $modeline        = ( $config{readonly}->{val} ? '[RO] ' : '' )
 
4933
                         . $modes{$mode}->{hdr} . " (? for help)";
 
4934
   my $mode_width      = length($modeline);
 
4935
   my $remaining_width = $this_term_size[0] - $mode_width - 1;
 
4936
   my $result;
 
4937
 
 
4938
   # The thingie in top-right that says what we're monitoring.
 
4939
   my $cxn = '';
 
4940
 
 
4941
   if ( 1 == @cxns && $dbhs{$cxns[0]} && $dbhs{$cxns[0]}->{dbh} ) {
 
4942
      $cxn = $dbhs{$cxns[0]}->{dbh}->{mysql_serverinfo} || '';
 
4943
   }
 
4944
   else {
 
4945
      if ( $modes{$mode}->{server_group} ) {
 
4946
         $cxn = "Servers: " . $modes{$mode}->{server_group};
 
4947
         my $err_count = grep { $dbhs{$_} && $dbhs{$_}->{err_count} } @cxns;
 
4948
         if ( $err_count ) {
 
4949
            $cxn .= "(" . ( scalar(@cxns) - $err_count ) . "/" . scalar(@cxns) . ")";
 
4950
         }
 
4951
      }
 
4952
      else {
 
4953
         $cxn = join(' ', map { ($dbhs{$_}->{err_count} ? '!' : '') . $_ }
 
4954
            grep { $dbhs{$_} } @cxns);
 
4955
      }
 
4956
   }
 
4957
 
 
4958
   if ( 1 == @cxns ) {
 
4959
      get_driver_status(@cxns);
 
4960
      my $vars = $vars{$cxns[0]}->{$clock};
 
4961
      my $inc  = inc(0, $cxns[0]);
 
4962
 
 
4963
      # Format server uptime human-readably, calculate QPS...
 
4964
      my $uptime = secs_to_time( $vars->{Uptime_hires} );
 
4965
      my $qps    = ($inc->{Questions}||0) / ($inc->{Uptime_hires}||1);
 
4966
      my $ibinfo = '';
 
4967
 
 
4968
      if ( exists $vars->{IB_last_secs} ) {
 
4969
         $ibinfo .= "InnoDB $vars->{IB_last_secs}s ";
 
4970
         if ( $vars->{IB_got_all} ) {
 
4971
            if ( ($mode eq 'T' || $mode eq 'W')
 
4972
                  && $vars->{IB_tx_is_truncated} ) {
 
4973
               $ibinfo .= ':^|';
 
4974
            }
 
4975
            else {
 
4976
               $ibinfo .= ':-)';
 
4977
            }
 
4978
         }
 
4979
         else {
 
4980
            $ibinfo .= ':-(';
 
4981
         }
 
4982
      }
 
4983
      $result = sprintf(
 
4984
         "%-${mode_width}s %${remaining_width}s",
 
4985
         $modeline,
 
4986
         join(', ', grep { $_ } (
 
4987
            $cxns[0],
 
4988
            $uptime,
 
4989
            $ibinfo,
 
4990
            shorten($qps) . " QPS",
 
4991
            ($vars->{Threads} || 0) . " thd",
 
4992
            $cxn)));
 
4993
   }
 
4994
   else {
 
4995
      $result = sprintf(
 
4996
         "%-${mode_width}s %${remaining_width}s",
 
4997
         $modeline,
 
4998
         $cxn);
 
4999
   }
 
5000
 
 
5001
   return $config{color}->{val} ? [ $result, 'bold reverse' ] : $result;
 
5002
}
 
5003
 
 
5004
# Database connections {{{3
 
5005
sub add_new_dsn {
 
5006
   my ( $name ) = @_;
 
5007
 
 
5008
   if ( defined $name ) {
 
5009
      $name =~ s/[\s:;]//g;
 
5010
   }
 
5011
 
 
5012
   if ( !$name ) {
 
5013
      print word_wrap("Choose a name for the connection.  It cannot contain "
 
5014
         . "whitespace, colons or semicolons."), "\n\n";
 
5015
      do {
 
5016
         $name = prompt("Enter a name");
 
5017
         $name =~ s/[\s:;]//g;
 
5018
      } until ( $name );
 
5019
   }
 
5020
 
 
5021
   my $dsn;
 
5022
   do {
 
5023
      $clear_screen_sub->();
 
5024
      print "Typical DSN strings look like\n   DBI:mysql:;host=hostname;port=port\n"
 
5025
         . "The db and port are optional and can usually be omitted.\n"
 
5026
         . "If you specify 'mysql_read_default_group=mysql' many options can be read\n"
 
5027
         . "from your mysql options files (~/.my.cnf, /etc/my.cnf).\n\n";
 
5028
      $dsn = prompt("Enter a DSN string", undef, "DBI:mysql:;mysql_read_default_group=mysql;host=$name");
 
5029
   } until ( $dsn );
 
5030
 
 
5031
   $clear_screen_sub->();
 
5032
   my $dl_table = prompt("Optional: enter a table (must not exist) to use when resetting InnoDB deadlock information",
 
5033
      undef, 'test.innotop_dl');
 
5034
 
 
5035
   $connections{$name} = {
 
5036
      dsn      => $dsn,
 
5037
      dl_table => $dl_table,
 
5038
   };
 
5039
}
 
5040
 
 
5041
sub add_new_server_group {
 
5042
   my ( $name ) = @_;
 
5043
 
 
5044
   if ( defined $name ) {
 
5045
      $name =~ s/[\s:;]//g;
 
5046
   }
 
5047
 
 
5048
   if ( !$name ) {
 
5049
      print word_wrap("Choose a name for the group.  It cannot contain "
 
5050
         . "whitespace, colons or semicolons."), "\n\n";
 
5051
      do {
 
5052
         $name = prompt("Enter a name");
 
5053
         $name =~ s/[\s:;]//g;
 
5054
      } until ( $name );
 
5055
   }
 
5056
 
 
5057
   my @cxns;
 
5058
   do {
 
5059
      $clear_screen_sub->();
 
5060
      @cxns = select_cxn("Choose servers for $name", keys %connections);
 
5061
   } until ( @cxns );
 
5062
 
 
5063
   $server_groups{$name} = \@cxns;
 
5064
   return $name;
 
5065
}
 
5066
 
 
5067
sub get_var_set {
 
5068
   my ( $name ) = @_;
 
5069
   while ( !$name || !exists($var_sets{$config{$name}->{val}}) ) {
 
5070
      $name = choose_var_set($name);
 
5071
   }
 
5072
   return $var_sets{$config{$name}->{val}}->{text};
 
5073
}
 
5074
 
 
5075
sub add_new_var_set {
 
5076
   my ( $name ) = @_;
 
5077
 
 
5078
   if ( defined $name ) {
 
5079
      $name =~ s/\W//g;
 
5080
   }
 
5081
 
 
5082
   if ( !$name ) {
 
5083
      do {
 
5084
         $name = prompt("Enter a name");
 
5085
         $name =~ s/\W//g;
 
5086
      } until ( $name );
 
5087
   }
 
5088
 
 
5089
   my $variables;
 
5090
   do {
 
5091
      $clear_screen_sub->();
 
5092
      $variables = prompt("Enter variables for $name", undef );
 
5093
   } until ( $variables );
 
5094
 
 
5095
   $var_sets{$name} = { text => $variables, user => 1 };
 
5096
}
 
5097
 
 
5098
sub next_server {
 
5099
   my $mode     = $config{mode}->{val};
 
5100
   my @cxns     = sort keys %connections;
 
5101
   my ($cur)    = get_connections($mode);
 
5102
   $cur         ||= $cxns[0];
 
5103
   my $pos      = grep { $_ lt $cur } @cxns;
 
5104
   my $newpos   = ($pos + 1) % @cxns;
 
5105
   $modes{$mode}->{server_group} = '';
 
5106
   $modes{$mode}->{connections} = [ $cxns[$newpos] ];
 
5107
   $clear_screen_sub->();
 
5108
}
 
5109
 
 
5110
sub next_server_group {
 
5111
   my $mode = shift || $config{mode}->{val};
 
5112
   my @grps = sort keys %server_groups;
 
5113
   my $curr = $modes{$mode}->{server_group};
 
5114
 
 
5115
   return unless @grps;
 
5116
 
 
5117
   if ( $curr ) {
 
5118
      # Find the current group's position.
 
5119
      my $pos = 0;
 
5120
      while ( $curr ne $grps[$pos] ) {
 
5121
         $pos++;
 
5122
      }
 
5123
      $modes{$mode}->{server_group} = $grps[ ($pos + 1) % @grps ];
 
5124
   }
 
5125
   else {
 
5126
      $modes{$mode}->{server_group} = $grps[0];
 
5127
   }
 
5128
}
 
5129
 
 
5130
# Get a list of connection names used in this mode.
 
5131
sub get_connections {
 
5132
   if ( $file ) {
 
5133
      return qw(file);
 
5134
   }
 
5135
   my $mode = shift || $config{mode}->{val};
 
5136
   my @connections = $modes{$mode}->{server_group}
 
5137
      ? @{$server_groups{$modes{$mode}->{server_group}}}
 
5138
      : @{$modes{$mode}->{connections}};
 
5139
   if ( $modes{$mode}->{one_connection} ) {
 
5140
      @connections = @connections ? $connections[0] : ();
 
5141
   }
 
5142
   return unique(@connections);
 
5143
}
 
5144
 
 
5145
# Get a list of tables used in this mode.  If innotop is running non-interactively, just use the first.
 
5146
sub get_visible_tables {
 
5147
   my $mode = shift || $config{mode}->{val};
 
5148
   my @tbls = @{$modes{$mode}->{visible_tables}};
 
5149
   if ( $opts{n} ) {
 
5150
      return $tbls[0];
 
5151
   }
 
5152
   else {
 
5153
      return @tbls;
 
5154
   }
 
5155
}
 
5156
 
 
5157
# Choose from among available connections or server groups.
 
5158
# If the mode has a server set in use, prefers that instead.
 
5159
sub choose_connections {
 
5160
   $clear_screen_sub->();
 
5161
   my $mode    = $config{mode}->{val};
 
5162
   my $meta    =  { map { $_ => $connections{$_}->{dsn} } keys %connections };
 
5163
   foreach my $group ( keys %server_groups ) {
 
5164
      $meta->{"#$group"} = join(' ', @{$server_groups{$group}});
 
5165
   }
 
5166
 
 
5167
   my $choices = prompt_list("Choose connections or a group for $mode mode",
 
5168
      undef, sub { return keys %$meta }, $meta);
 
5169
 
 
5170
   my @choices = unique(grep { $_ } split(/\s+/, $choices));
 
5171
   if ( @choices ) {
 
5172
      if ( $choices[0] =~ s/^#// && exists $server_groups{$choices[0]} ) {
 
5173
         $modes{$mode}->{server_group} = $choices[0];
 
5174
      }
 
5175
      else {
 
5176
         $modes{$mode}->{connections} = [ grep { exists $connections{$_} } @choices ];
 
5177
      }
 
5178
   }
 
5179
}
 
5180
 
 
5181
# Accepts a DB connection name and the name of a prepared query (e.g. status, kill).
 
5182
# Also a list of params for the prepared query.  This allows not storing prepared
 
5183
# statements globally.  Returns a $sth that's been executed.
 
5184
# ERROR-HANDLING SEMANTICS: if the statement throws an error, propagate, but if the
 
5185
# connection has gone away or can't connect, DO NOT.  Just return undef.
 
5186
sub do_stmt {
 
5187
   my ( $cxn, $stmt_name, @args ) = @_;
 
5188
 
 
5189
   return undef if $file;
 
5190
 
 
5191
   # Test if the cxn should not even be tried
 
5192
   return undef if $dbhs{$cxn}
 
5193
      && $dbhs{$cxn}->{err_count} 
 
5194
      && ( !$dbhs{$cxn}->{dbh} || !$dbhs{$cxn}->{dbh}->{Active} || $dbhs{$cxn}->{mode} eq $config{mode}->{val} )
 
5195
      && $dbhs{$cxn}->{wake_up} > $clock;
 
5196
 
 
5197
   my $sth;
 
5198
   my $retries = 1;
 
5199
   my $success = 0;
 
5200
   TRY:
 
5201
   while ( $retries-- >= 0 && !$success ) {
 
5202
 
 
5203
      eval {
 
5204
         my $dbh = connect_to_db($cxn);
 
5205
 
 
5206
         # If the prepared query doesn't exist, make it.
 
5207
         if ( !exists $dbhs{$cxn}->{stmts}->{$stmt_name} ) {
 
5208
            $dbhs{$cxn}->{stmts}->{$stmt_name} = $stmt_maker_for{$stmt_name}->($dbh);
 
5209
         }
 
5210
 
 
5211
         $sth = $dbhs{$cxn}->{stmts}->{$stmt_name};
 
5212
         if ( $sth ) {
 
5213
            $sth->execute(@args);
 
5214
         }
 
5215
         $success = 1;
 
5216
      };
 
5217
      if ( $EVAL_ERROR ) {
 
5218
         if ( $EVAL_ERROR =~ m/$nonfatal_errs/ ) {
 
5219
            handle_cxn_error($cxn, $EVAL_ERROR);
 
5220
         }
 
5221
         else {
 
5222
            die "$cxn $stmt_name: $EVAL_ERROR";
 
5223
         }
 
5224
         if ( $retries < 0 ) {
 
5225
            $sth = undef;
 
5226
         }
 
5227
      }
 
5228
   }
 
5229
 
 
5230
   if ( $sth && $sth->{NUM_OF_FIELDS} ) {
 
5231
      sleep($stmt_sleep_time_for{$stmt_name}) if $stmt_sleep_time_for{$stmt_name};
 
5232
      return $sth;
 
5233
   }
 
5234
}
 
5235
 
 
5236
# Keeps track of error count, sleep times till retries, etc etc.
 
5237
# When there's an error we retry the connection every so often, increasing in
 
5238
# Fibonacci series to prevent too much banging on the server.
 
5239
sub handle_cxn_error {
 
5240
   my ( $cxn, $err ) = @_;
 
5241
   my $meta = $dbhs{$cxn};
 
5242
   $meta->{err_count}++;
 
5243
 
 
5244
   # This is used so errors that have to do with permissions needed by the current
 
5245
   # mode will get displayed as long as we're in this mode, but get ignored if the
 
5246
   # mode changes.
 
5247
   $meta->{mode} = $config{mode}->{val};
 
5248
 
 
5249
   # Strip garbage from the error text if possible.
 
5250
   $err =~ s/\s+/ /g;
 
5251
   if ( $err =~ m/failed: (.*?) at \S*innotop line/ ) {
 
5252
      $err = $1;
 
5253
   }
 
5254
 
 
5255
   $meta->{last_err}   = $err;
 
5256
   my $sleep_time      = $meta->{this_sleep} + $meta->{prev_sleep};
 
5257
   $meta->{prev_sleep} = $meta->{this_sleep};
 
5258
   $meta->{this_sleep} = $sleep_time;
 
5259
   $meta->{wake_up}    = $clock + $sleep_time;
 
5260
   if ( $config{show_cxn_errors}->{val} ) {
 
5261
      print STDERR "Error at tick $clock $cxn $err" if $config{debug}->{val};
 
5262
   }
 
5263
}
 
5264
 
 
5265
# Accepts a DB connection name and a (string) query.  Returns a $sth that's been
 
5266
# executed.
 
5267
sub do_query {
 
5268
   my ( $cxn, $query ) = @_;
 
5269
 
 
5270
   return undef if $file;
 
5271
 
 
5272
   # Test if the cxn should not even be tried
 
5273
   return undef if $dbhs{$cxn}
 
5274
      && $dbhs{$cxn}->{err_count} 
 
5275
      && ( !$dbhs{$cxn}->{dbh} || !$dbhs{$cxn}->{dbh}->{Active} || $dbhs{$cxn}->{mode} eq $config{mode}->{val} )
 
5276
      && $dbhs{$cxn}->{wake_up} > $clock;
 
5277
 
 
5278
   my $sth;
 
5279
   my $retries = 1;
 
5280
   my $success = 0;
 
5281
   TRY:
 
5282
   while ( $retries-- >= 0 && !$success ) {
 
5283
 
 
5284
      eval {
 
5285
         my $dbh = connect_to_db($cxn);
 
5286
 
 
5287
         $sth = $dbh->prepare($query);
 
5288
         $sth->execute();
 
5289
         $success = 1;
 
5290
      };
 
5291
      if ( $EVAL_ERROR ) {
 
5292
         if ( $EVAL_ERROR =~ m/$nonfatal_errs/ ) {
 
5293
            handle_cxn_error($cxn, $EVAL_ERROR);
 
5294
         }
 
5295
         else {
 
5296
            die $EVAL_ERROR;
 
5297
         }
 
5298
         if ( $retries < 0 ) {
 
5299
            $sth = undef;
 
5300
         }
 
5301
      }
 
5302
   }
 
5303
 
 
5304
   return $sth;
 
5305
}
 
5306
 
 
5307
sub get_uptime {
 
5308
   my ( $cxn ) = @_;
 
5309
   $dbhs{$cxn}->{start_time} ||= time();
 
5310
   # Avoid dividing by zero
 
5311
   return (time() - $dbhs{$cxn}->{start_time}) || .001;
 
5312
}
 
5313
 
 
5314
sub connect_to_db {
 
5315
   my ( $cxn ) = @_;
 
5316
 
 
5317
   $dbhs{$cxn} ||= {
 
5318
      stmts      => {},  # bucket for prepared statements.
 
5319
      prev_sleep => 0,
 
5320
      this_sleep => 1,
 
5321
      wake_up    => 0,
 
5322
      start_time => 0,
 
5323
      dbh        => undef,
 
5324
   };
 
5325
   my $href = $dbhs{$cxn};
 
5326
 
 
5327
   if ( !$href->{dbh} || ref($href->{dbh}) !~ m/DBI/ || !$href->{dbh}->ping ) {
 
5328
      my $dbh = get_new_db_connection($cxn);
 
5329
      @{$href}{qw(dbh err_count wake_up this_sleep start_time prev_sleep)}
 
5330
               = ($dbh, 0, 0, 1, 0, 0);
 
5331
 
 
5332
      # Derive and store the server's start time in hi-res
 
5333
      my $uptime = $dbh->selectrow_hashref("show status like 'Uptime'")->{value};
 
5334
      $href->{start_time} = time() - $uptime;
 
5335
 
 
5336
      # Set timeouts so an unused connection stays alive.
 
5337
      # For example, a connection might be used in Q mode but idle in T mode.
 
5338
      if ( version_ge($dbh, '4.0.3')) {
 
5339
         my $timeout = $config{cxn_timeout}->{val};
 
5340
         $dbh->do("set session wait_timeout=$timeout, interactive_timeout=$timeout");
 
5341
      }
 
5342
   }
 
5343
   return $href->{dbh};
 
5344
}
 
5345
 
 
5346
# Compares versions like 5.0.27 and 4.1.15-standard-log
 
5347
sub version_ge {
 
5348
   my ( $dbh, $target ) = @_;
 
5349
   my $version = sprintf('%03d%03d%03d', $dbh->{mysql_serverinfo} =~ m/(\d+)/g);
 
5350
   return $version ge sprintf('%03d%03d%03d', $target =~ m/(\d+)/g);
 
5351
}
 
5352
 
 
5353
# Extracts status values that can be gleaned from the DBD driver without doing a whole query.
 
5354
sub get_driver_status {
 
5355
   my @cxns = @_;
 
5356
   if ( !$info_gotten{driver_status}++ ) {
 
5357
      foreach my $cxn ( @cxns ) {
 
5358
         next unless $dbhs{$cxn} && $dbhs{$cxn}->{dbh} && $dbhs{$cxn}->{dbh}->{Active};
 
5359
         $vars{$cxn}->{$clock} ||= {};
 
5360
         my $vars = $vars{$cxn}->{$clock};
 
5361
         my %res = map {  $_ =~ s/ +/_/g; $_ } $dbhs{$cxn}->{dbh}->{mysql_stat} =~ m/(\w[^:]+): ([\d\.]+)/g;
 
5362
         map { $vars->{$_} ||= $res{$_} } keys %res;
 
5363
         $vars->{Uptime_hires} ||= get_uptime($cxn);
 
5364
         $vars->{cxn} = $cxn;
 
5365
      }
 
5366
   }
 
5367
}
 
5368
 
 
5369
sub get_new_db_connection {
 
5370
   my ( $connection, $destroy ) = @_;
 
5371
   if ( $file ) {
 
5372
      die "You can't connect to a MySQL server while monitoring a file.  This is probably a bug.";
 
5373
   }
 
5374
 
 
5375
   my $dsn = $connections{$connection}
 
5376
      or die "No connection named '$connection' is defined in your configuration";
 
5377
 
 
5378
   if ( !defined $dsn->{have_user} ) {
 
5379
      my $answer = prompt("Do you want to specify a username for $connection?", undef, 'n');
 
5380
      $dsn->{have_user} = $answer && $answer =~ m/1|y/i;
 
5381
   }
 
5382
 
 
5383
   if ( !defined $dsn->{have_pass} ) {
 
5384
      my $answer = prompt("Do you want to specify a password for $connection?", undef, 'n');
 
5385
      $dsn->{have_pass} = $answer && $answer =~ m/1|y/i;
 
5386
   }
 
5387
 
 
5388
   if ( !$dsn->{user} && $dsn->{have_user} ) {
 
5389
      my $user = $ENV{USERNAME} || $ENV{USER} || getlogin() || getpwuid($REAL_USER_ID) || undef;
 
5390
      $dsn->{user} = prompt("Enter username for $connection", undef, $user);
 
5391
   }
 
5392
 
 
5393
   if ( !defined $dsn->{user} ) {
 
5394
      $dsn->{user} = '';
 
5395
   }
 
5396
 
 
5397
   if ( !$dsn->{pass} && !$dsn->{savepass} && $dsn->{have_pass} ) {
 
5398
      $dsn->{pass} = prompt_noecho("Enter password for '$dsn->{user}' on $connection");
 
5399
      print "\n";
 
5400
      if ( !defined($dsn->{savepass}) ) {
 
5401
         my $answer = prompt("Save password in plain text in the config file?", undef, 'y');
 
5402
         $dsn->{savepass} = $answer && $answer =~ m/1|y/i;
 
5403
      }
 
5404
   }
 
5405
 
 
5406
   my $dbh = DBI->connect(
 
5407
      $dsn->{dsn}, $dsn->{user}, $dsn->{pass},
 
5408
      { RaiseError => 1, PrintError => 0, AutoCommit => 1 });
 
5409
   $dbh->{InactiveDestroy} = 1 unless $destroy; # Can't be set in $db_options
 
5410
   $dbh->{FetchHashKeyName} = 'NAME_lc'; # Lowercases all column names for fetchrow_hashref
 
5411
   return $dbh;
 
5412
}
 
5413
 
 
5414
sub get_cxn_errors {
 
5415
   my @cxns = @_;
 
5416
   return () unless $config{show_cxn_errors_in_tbl}->{val};
 
5417
   return
 
5418
      map  { [ $_ . ': ' . $dbhs{$_}->{last_err}, 'red' ] }
 
5419
      grep { $dbhs{$_} && $dbhs{$_}->{err_count} && $dbhs{$_}->{mode} eq $config{mode}->{val} }
 
5420
      @cxns;
 
5421
}
 
5422
 
 
5423
# Setup and tear-down functions {{{2
 
5424
 
 
5425
# Takes a string and turns it into a hashref you can apply to %tbl_meta tables.  The string
 
5426
# can be in the form 'foo, bar, foo/bar, foo as bar' much like a SQL SELECT statement.
 
5427
sub compile_select_stmt {
 
5428
   my ($str) = @_;
 
5429
   my @exps = $str =~ m/\s*([^,]+(?i:\s+as\s+[^,\s]+)?)\s*(?=,|$)/g;
 
5430
   my %cols;
 
5431
   my @visible;
 
5432
   foreach my $exp ( @exps ) {
 
5433
      my ( $text, $colname );
 
5434
      if ( $exp =~ m/as\s+(\w+)\s*/ ) {
 
5435
         $colname = $1;
 
5436
         $exp =~ s/as\s+(\w+)\s*//;
 
5437
         $text    = $exp;
 
5438
      }
 
5439
      else {
 
5440
         $text = $colname = $exp;
 
5441
      }
 
5442
      my ($func, $err) = compile_expr($text);
 
5443
      $cols{$colname} = {
 
5444
         src  => $text,
 
5445
         hdr  => $colname,
 
5446
         num  => 0,
 
5447
         func => $func,
 
5448
      };
 
5449
      push @visible, $colname;
 
5450
   }
 
5451
   return (\%cols, \@visible);
 
5452
}
 
5453
 
 
5454
# compile_filter {{{3
 
5455
sub compile_filter {
 
5456
   my ( $text ) = @_;
 
5457
   my ( $sub, $err );
 
5458
   eval "\$sub = sub { my \$set = shift; $text }";
 
5459
   if ( $EVAL_ERROR ) {
 
5460
      $EVAL_ERROR =~ s/at \(eval.*$//;
 
5461
      $sub = sub { return $EVAL_ERROR };
 
5462
      $err = $EVAL_ERROR;
 
5463
   }
 
5464
   return ( $sub, $err );
 
5465
}
 
5466
 
 
5467
# compile_expr {{{3
 
5468
sub compile_expr {
 
5469
   my ( $expr ) = @_;
 
5470
   # Leave built-in functions alone so they get called as Perl functions, unless
 
5471
   # they are the only word in $expr, in which case treat them as hash keys.
 
5472
   if ( $expr =~ m/\W/ ) {
 
5473
      $expr =~ s/(?<!\{|\$)\b([A-Za-z]\w{2,})\b/is_func($1) ? $1 : "\$set->{$1}"/eg;
 
5474
   }
 
5475
   else {
 
5476
      $expr = "\$set->{$expr}";
 
5477
   }
 
5478
   my ( $sub, $err );
 
5479
   my $quoted = quotemeta($expr);
 
5480
   eval qq{
 
5481
      \$sub = sub {
 
5482
         my (\$set, \$cur, \$pre) = \@_;
 
5483
         my \$val = eval { $expr };
 
5484
         if ( \$EVAL_ERROR && \$config{debug}->{val} ) {
 
5485
            \$EVAL_ERROR =~ s/ at \\(eval.*//s;
 
5486
            die "\$EVAL_ERROR in expression $quoted";
 
5487
         }
 
5488
         return \$val;
 
5489
      }
 
5490
   };
 
5491
   if ( $EVAL_ERROR ) {
 
5492
      if ( $config{debug}->{val} ) {
 
5493
         die $EVAL_ERROR;
 
5494
      }
 
5495
      $EVAL_ERROR =~ s/ at \(eval.*$//;
 
5496
      $sub = sub { return $EVAL_ERROR };
 
5497
      $err = $EVAL_ERROR;
 
5498
   }
 
5499
   return ( $sub, $err );
 
5500
}
 
5501
 
 
5502
# finish {{{3
 
5503
# This is a subroutine because it's called from a key to quit the program.
 
5504
sub finish {
 
5505
   save_config();
 
5506
   ReadMode('normal') unless $opts{n};
 
5507
   print "\n";
 
5508
   exit(0);
 
5509
}
 
5510
 
 
5511
# core_dump {{{3
 
5512
sub core_dump {
 
5513
   my $msg = shift;
 
5514
   if ($config{debugfile}->{val} && $config{debug}->{val}) {
 
5515
      eval {
 
5516
         open my $file, '>>', $config{debugfile}->{val};
 
5517
         if ( %vars ) {
 
5518
            print $file "Current variables:\n" . Dumper(\%vars);
 
5519
         }
 
5520
         close $file;
 
5521
      };
 
5522
   }
 
5523
   print $msg;
 
5524
}
 
5525
 
 
5526
# load_config {{{3
 
5527
sub load_config {
 
5528
 
 
5529
   my $filename = $opts{c} || "$homepath/.innotop/innotop.ini";
 
5530
   my $dirname  = dirname($filename);
 
5531
   if ( -f $dirname && !$opts{c} ) {
 
5532
      # innotop got upgraded and this is the old config file.
 
5533
      my $answer = pause("Innotop's default config location has moved to $filename.  Move old config file $dirname there now? y/n");
 
5534
      if ( lc $answer eq 'y' ) {
 
5535
         rename($dirname, "$homepath/innotop.ini")
 
5536
            or die "Can't rename '$dirname': $OS_ERROR";
 
5537
         mkdir($dirname) or die "Can't create directory '$dirname': $OS_ERROR";
 
5538
         mkdir("$dirname/plugins") or die "Can't create directory '$dirname/plugins': $OS_ERROR";
 
5539
         rename("$homepath/innotop.ini", $filename)
 
5540
            or die "Can't rename '$homepath/innotop.ini' to '$filename': $OS_ERROR";
 
5541
      }
 
5542
      else {
 
5543
         print "\nInnotop will now exit so you can fix the config file.\n";
 
5544
         exit(0);
 
5545
      }
 
5546
   }
 
5547
 
 
5548
   if ( ! -d $dirname ) {
 
5549
      mkdir $dirname
 
5550
         or die "Can't create directory '$dirname': $OS_ERROR";
 
5551
   }
 
5552
   if ( ! -d "$dirname/plugins" ) {
 
5553
      mkdir "$dirname/plugins"
 
5554
         or die "Can't create directory '$dirname/plugins': $OS_ERROR";
 
5555
   }
 
5556
 
 
5557
   if ( -f $filename ) {
 
5558
      open my $file, "<", $filename or die("Can't open '$filename': $OS_ERROR");
 
5559
 
 
5560
      # Check config file version.  Just ignore if either innotop or the file has
 
5561
      # garbage in the version number.
 
5562
      if ( defined(my $line = <$file>) && $VERSION =~ m/\d/ ) {
 
5563
         chomp $line;
 
5564
         if ( my ($maj, $min, $rev) = $line =~ m/^version=(\d+)\.(\d+)(?:\.(\d+))?$/ ) {
 
5565
            $rev ||= 0;
 
5566
            my $cfg_ver          = sprintf('%03d-%03d-%03d', $maj, $min, $rev);
 
5567
            ( $maj, $min, $rev ) = $VERSION =~ m/^(\d+)\.(\d+)(?:\.(\d+))?$/;
 
5568
            $rev ||= 0;
 
5569
            my $innotop_ver      = sprintf('%03d-%03d-%03d', $maj, $min, $rev);
 
5570
 
 
5571
            if ( $cfg_ver gt $innotop_ver ) {
 
5572
               pause("The config file is for a newer version of innotop and may not be read correctly.");
 
5573
            }
 
5574
            else {
 
5575
               my @ver_history = @config_versions;
 
5576
               while ( my ($start, $end) = splice(@ver_history, 0, 2) ) {
 
5577
                  # If the config file is between the endpoints and innotop is greater than
 
5578
                  # the endpoint, innotop has a newer config file format than the file.
 
5579
                  if ( $cfg_ver ge $start && $cfg_ver lt $end && $innotop_ver ge $end ) {
 
5580
                     my $msg = "innotop's config file format has changed.  Overwrite $filename?  y or n";
 
5581
                     if ( pause($msg) eq 'n' ) {
 
5582
                        $config{readonly}->{val} = 1;
 
5583
                        print "\ninnotop will not save any configuration changes you make.";
 
5584
                        pause();
 
5585
                        print "\n";
 
5586
                     }
 
5587
                     close $file;
 
5588
                     return;
 
5589
                  }
 
5590
               }
 
5591
            }
 
5592
         }
 
5593
      }
 
5594
 
 
5595
      while ( my $line = <$file> ) {
 
5596
         chomp $line;
 
5597
         next unless $line =~ m/^\[([a-z_]+)\]$/;
 
5598
         if ( exists $config_file_sections{$1} ) {
 
5599
            $config_file_sections{$1}->{reader}->($file);
 
5600
         }
 
5601
         else {
 
5602
            warn "Unknown config file section '$1'";
 
5603
         }
 
5604
      }
 
5605
      close $file or die("Can't close $filename: $OS_ERROR");
 
5606
   }
 
5607
 
 
5608
}
 
5609
 
 
5610
# Do some post-processing on %tbl_meta: compile src properties into func etc.
 
5611
sub post_process_tbl_meta {
 
5612
   foreach my $table ( values %tbl_meta ) {
 
5613
      foreach my $col_name ( keys %{$table->{cols}} ) {
 
5614
         my $col_def = $table->{cols}->{$col_name};
 
5615
         my ( $sub, $err ) = compile_expr($col_def->{src});
 
5616
         $col_def->{func} = $sub;
 
5617
      }
 
5618
   }
 
5619
}
 
5620
 
 
5621
# load_config_plugins {{{3
 
5622
sub load_config_plugins {
 
5623
   my ( $file ) = @_;
 
5624
 
 
5625
   # First, find a list of all plugins that exist on disk, and get information about them.
 
5626
   my $dir = $config{plugin_dir}->{val};
 
5627
   foreach my $p_file ( <$dir/*.pm> ) {
 
5628
      my ($package, $desc);
 
5629
      eval {
 
5630
         open my $p_in, "<", $p_file or die $OS_ERROR;
 
5631
         while ( my $line = <$p_in> ) {
 
5632
            chomp $line;
 
5633
            if ( $line =~ m/^package\s+(.*?);/ ) {
 
5634
               $package = $1;
 
5635
            }
 
5636
            elsif ( $line =~ m/^# description: (.*)/ ) {
 
5637
               $desc = $1;
 
5638
            }
 
5639
            last if $package && $desc;
 
5640
         }
 
5641
         close $p_in;
 
5642
      };
 
5643
      if ( $package ) {
 
5644
         $plugins{$package} = {
 
5645
            file   => $p_file,
 
5646
            desc   => $desc,
 
5647
            class  => $package,
 
5648
            active => 0,
 
5649
         };
 
5650
         if ( $config{debug}->{val} && $EVAL_ERROR ) {
 
5651
            die $EVAL_ERROR;
 
5652
         }
 
5653
      }
 
5654
   }
 
5655
 
 
5656
   # Now read which ones the user has activated.  Each line simply represents an active plugin.
 
5657
   while ( my $line = <$file> ) {
 
5658
      chomp $line;
 
5659
      next if $line =~ m/^#/;
 
5660
      last if $line =~ m/^\[/;
 
5661
      next unless $line && $plugins{$line};
 
5662
 
 
5663
      my $obj;
 
5664
      eval {
 
5665
         require $plugins{$line}->{file};
 
5666
         $obj = $line->new(%pluggable_vars);
 
5667
         foreach my $event ( $obj->register_for_events() ) {
 
5668
            my $queue = $event_listener_for{$event};
 
5669
            if ( $queue ) {
 
5670
               push @$queue, $obj;
 
5671
            }
 
5672
         }
 
5673
      };
 
5674
      if ( $config{debug}->{val} && $EVAL_ERROR ) {
 
5675
         die $EVAL_ERROR;
 
5676
      }
 
5677
      if ( $obj ) {
 
5678
         $plugins{$line}->{active} = 1;
 
5679
         $plugins{$line}->{object} = $obj;
 
5680
      }
 
5681
   }
 
5682
}
 
5683
 
 
5684
# save_config_plugins {{{3
 
5685
sub save_config_plugins {
 
5686
   my $file = shift;
 
5687
   foreach my $class ( sort keys %plugins ) {
 
5688
      next unless $plugins{$class}->{active};
 
5689
      print $file "$class\n";
 
5690
   }
 
5691
}
 
5692
 
 
5693
# load_config_active_server_groups {{{3
 
5694
sub load_config_active_server_groups {
 
5695
   my ( $file ) = @_;
 
5696
   while ( my $line = <$file> ) {
 
5697
      chomp $line;
 
5698
      next if $line =~ m/^#/;
 
5699
      last if $line =~ m/^\[/;
 
5700
 
 
5701
      my ( $mode, $group ) = $line =~ m/^(.*?)=(.*)$/;
 
5702
      next unless $mode && $group
 
5703
         && exists $modes{$mode} && exists $server_groups{$group};
 
5704
      $modes{$mode}->{server_group} = $group;
 
5705
   }
 
5706
}
 
5707
 
 
5708
# save_config_active_server_groups {{{3
 
5709
sub save_config_active_server_groups {
 
5710
   my $file = shift;
 
5711
   foreach my $mode ( sort keys %modes ) {
 
5712
      print $file "$mode=$modes{$mode}->{server_group}\n";
 
5713
   }
 
5714
}
 
5715
 
 
5716
# load_config_server_groups {{{3
 
5717
sub load_config_server_groups {
 
5718
   my ( $file ) = @_;
 
5719
   while ( my $line = <$file> ) {
 
5720
      chomp $line;
 
5721
      next if $line =~ m/^#/;
 
5722
      last if $line =~ m/^\[/;
 
5723
 
 
5724
      my ( $name, $rest ) = $line =~ m/^(.*?)=(.*)$/;
 
5725
      next unless $name && $rest;
 
5726
      my @vars = unique(grep { $_ && exists $connections{$_} } split(/\s+/, $rest));
 
5727
      next unless @vars;
 
5728
      $server_groups{$name} = \@vars;
 
5729
   }
 
5730
}
 
5731
 
 
5732
# save_config_server_groups {{{3
 
5733
sub save_config_server_groups {
 
5734
   my $file = shift;
 
5735
   foreach my $set ( sort keys %server_groups ) {
 
5736
      print $file "$set=", join(' ', @{$server_groups{$set}}), "\n";
 
5737
   }
 
5738
}
 
5739
 
 
5740
# load_config_varsets {{{3
 
5741
sub load_config_varsets {
 
5742
   my ( $file ) = @_;
 
5743
   while ( my $line = <$file> ) {
 
5744
      chomp $line;
 
5745
      next if $line =~ m/^#/;
 
5746
      last if $line =~ m/^\[/;
 
5747
 
 
5748
      my ( $name, $rest ) = $line =~ m/^(.*?)=(.*)$/;
 
5749
      next unless $name && $rest;
 
5750
      $var_sets{$name} = {
 
5751
         text => $rest,
 
5752
         user => 1,
 
5753
      };
 
5754
   }
 
5755
}
 
5756
 
 
5757
# save_config_varsets {{{3
 
5758
sub save_config_varsets {
 
5759
   my $file = shift;
 
5760
   foreach my $varset ( sort keys %var_sets ) {
 
5761
      next unless $var_sets{$varset}->{user};
 
5762
      print $file "$varset=$var_sets{$varset}->{text}\n";
 
5763
   }
 
5764
}
 
5765
 
 
5766
# load_config_group_by {{{3
 
5767
sub load_config_group_by {
 
5768
   my ( $file ) = @_;
 
5769
   while ( my $line = <$file> ) {
 
5770
      chomp $line;
 
5771
      next if $line =~ m/^#/;
 
5772
      last if $line =~ m/^\[/;
 
5773
 
 
5774
      my ( $tbl , $rest ) = $line =~ m/^(.*?)=(.*)$/;
 
5775
      next unless $tbl && exists $tbl_meta{$tbl};
 
5776
      my @parts = unique(grep { exists($tbl_meta{$tbl}->{cols}->{$_}) } split(/\s+/, $rest));
 
5777
      $tbl_meta{$tbl}->{group_by} = [ @parts ];
 
5778
      $tbl_meta{$tbl}->{cust}->{group_by} = 1;
 
5779
   }
 
5780
}
 
5781
 
 
5782
# save_config_group_by {{{3
 
5783
sub save_config_group_by {
 
5784
   my $file = shift;
 
5785
   foreach my $tbl ( sort keys %tbl_meta ) {
 
5786
      next if $tbl_meta{$tbl}->{temp};
 
5787
      next unless $tbl_meta{$tbl}->{cust}->{group_by};
 
5788
      my $aref = $tbl_meta{$tbl}->{group_by};
 
5789
      print $file "$tbl=", join(' ', @$aref), "\n";
 
5790
   }
 
5791
}
 
5792
 
 
5793
# load_config_filters {{{3
 
5794
sub load_config_filters {
 
5795
   my ( $file ) = @_;
 
5796
   while ( my $line = <$file> ) {
 
5797
      chomp $line;
 
5798
      next if $line =~ m/^#/;
 
5799
      last if $line =~ m/^\[/;
 
5800
 
 
5801
      my ( $key, $rest ) = $line =~ m/^(.+?)=(.*)$/;
 
5802
      next unless $key && $rest;
 
5803
 
 
5804
      my %parts = $rest =~ m/(\w+)='((?:(?!(?<!\\)').)*)'/g; # Properties are single-quoted
 
5805
      next unless $parts{text} && $parts{tbls};
 
5806
 
 
5807
      foreach my $prop ( keys %parts ) {
 
5808
         # Un-escape escaping
 
5809
         $parts{$prop} =~ s/\\\\/\\/g;
 
5810
         $parts{$prop} =~ s/\\'/'/g;
 
5811
      }
 
5812
 
 
5813
      my ( $sub, $err ) = compile_filter($parts{text});
 
5814
      my @tbls = unique(split(/\s+/, $parts{tbls}));
 
5815
      @tbls = grep { exists $tbl_meta{$_} } @tbls;
 
5816
      $filters{$key} = {
 
5817
         func => $sub,
 
5818
         text => $parts{text},
 
5819
         user => 1,
 
5820
         name => $key,
 
5821
         note => 'User-defined filter',
 
5822
         tbls => \@tbls,
 
5823
      }
 
5824
   }
 
5825
}
 
5826
 
 
5827
# save_config_filters {{{3
 
5828
sub save_config_filters {
 
5829
   my $file = shift;
 
5830
   foreach my $key ( sort keys %filters ) {
 
5831
      next if !$filters{$key}->{user} || $filters{$key}->{quick};
 
5832
      my $text = $filters{$key}->{text};
 
5833
      $text =~ s/([\\'])/\\$1/g;
 
5834
      my $tbls = join(" ", @{$filters{$key}->{tbls}});
 
5835
      print $file "$key=text='$text' tbls='$tbls'\n";
 
5836
   }
 
5837
}
 
5838
 
 
5839
# load_config_visible_tables {{{3
 
5840
sub load_config_visible_tables {
 
5841
   my ( $file ) = @_;
 
5842
   while ( my $line = <$file> ) {
 
5843
      chomp $line;
 
5844
      next if $line =~ m/^#/;
 
5845
      last if $line =~ m/^\[/;
 
5846
 
 
5847
      my ( $mode, $rest ) = $line =~ m/^(.*?)=(.*)$/;
 
5848
      next unless $mode && exists $modes{$mode};
 
5849
      $modes{$mode}->{visible_tables} =
 
5850
         [ unique(grep { $_ && exists $tbl_meta{$_} } split(/\s+/, $rest)) ];
 
5851
      $modes{$mode}->{cust}->{visible_tables} = 1;
 
5852
   }
 
5853
}
 
5854
 
 
5855
# save_config_visible_tables {{{3
 
5856
sub save_config_visible_tables {
 
5857
   my $file = shift;
 
5858
   foreach my $mode ( sort keys %modes ) {
 
5859
      next unless $modes{$mode}->{cust}->{visible_tables};
 
5860
      my $tables = $modes{$mode}->{visible_tables};
 
5861
      print $file "$mode=", join(' ', @$tables), "\n";
 
5862
   }
 
5863
}
 
5864
 
 
5865
# load_config_sort_cols {{{3
 
5866
sub load_config_sort_cols {
 
5867
   my ( $file ) = @_;
 
5868
   while ( my $line = <$file> ) {
 
5869
      chomp $line;
 
5870
      next if $line =~ m/^#/;
 
5871
      last if $line =~ m/^\[/;
 
5872
 
 
5873
      my ( $key , $rest ) = $line =~ m/^(.*?)=(.*)$/;
 
5874
      next unless $key && exists $tbl_meta{$key};
 
5875
      $tbl_meta{$key}->{sort_cols} = $rest;
 
5876
      $tbl_meta{$key}->{cust}->{sort_cols} = 1;
 
5877
      $tbl_meta{$key}->{sort_func} = make_sort_func($tbl_meta{$key});
 
5878
   }
 
5879
}
 
5880
 
 
5881
# save_config_sort_cols {{{3
 
5882
sub save_config_sort_cols {
 
5883
   my $file = shift;
 
5884
   foreach my $tbl ( sort keys %tbl_meta ) {
 
5885
      next unless $tbl_meta{$tbl}->{cust}->{sort_cols};
 
5886
      my $col = $tbl_meta{$tbl}->{sort_cols};
 
5887
      print $file "$tbl=$col\n";
 
5888
   }
 
5889
}
 
5890
 
 
5891
# load_config_active_filters {{{3
 
5892
sub load_config_active_filters {
 
5893
   my ( $file ) = @_;
 
5894
   while ( my $line = <$file> ) {
 
5895
      chomp $line;
 
5896
      next if $line =~ m/^#/;
 
5897
      last if $line =~ m/^\[/;
 
5898
 
 
5899
      my ( $tbl , $rest ) = $line =~ m/^(.*?)=(.*)$/;
 
5900
      next unless $tbl && exists $tbl_meta{$tbl};
 
5901
      my @parts = unique(grep { exists($filters{$_}) } split(/\s+/, $rest));
 
5902
      @parts = grep { grep { $tbl eq $_ } @{$filters{$_}->{tbls}} } @parts;
 
5903
      $tbl_meta{$tbl}->{filters} = [ @parts ];
 
5904
      $tbl_meta{$tbl}->{cust}->{filters} = 1;
 
5905
   }
 
5906
}
 
5907
 
 
5908
# save_config_active_filters {{{3
 
5909
sub save_config_active_filters {
 
5910
   my $file = shift;
 
5911
   foreach my $tbl ( sort keys %tbl_meta ) {
 
5912
      next if $tbl_meta{$tbl}->{temp};
 
5913
      next unless $tbl_meta{$tbl}->{cust}->{filters};
 
5914
      my $aref = $tbl_meta{$tbl}->{filters};
 
5915
      print $file "$tbl=", join(' ', @$aref), "\n";
 
5916
   }
 
5917
}
 
5918
 
 
5919
# load_config_active_columns {{{3
 
5920
sub load_config_active_columns {
 
5921
   my ( $file ) = @_;
 
5922
   while ( my $line = <$file> ) {
 
5923
      chomp $line;
 
5924
      next if $line =~ m/^#/;
 
5925
      last if $line =~ m/^\[/;
 
5926
 
 
5927
      my ( $key , $rest ) = $line =~ m/^(.*?)=(.*)$/;
 
5928
      next unless $key && exists $tbl_meta{$key};
 
5929
      my @parts = grep { exists($tbl_meta{$key}->{cols}->{$_}) } unique split(/ /, $rest);
 
5930
      $tbl_meta{$key}->{visible} = [ @parts ];
 
5931
      $tbl_meta{$key}->{cust}->{visible} = 1;
 
5932
   }
 
5933
}
 
5934
 
 
5935
# save_config_active_columns {{{3
 
5936
sub save_config_active_columns {
 
5937
   my $file = shift;
 
5938
   foreach my $tbl ( sort keys %tbl_meta ) {
 
5939
      next unless $tbl_meta{$tbl}->{cust}->{visible};
 
5940
      my $aref = $tbl_meta{$tbl}->{visible};
 
5941
      print $file "$tbl=", join(' ', @$aref), "\n";
 
5942
   }
 
5943
}
 
5944
 
 
5945
# save_config_tbl_meta {{{3
 
5946
sub save_config_tbl_meta {
 
5947
   my $file = shift;
 
5948
   foreach my $tbl ( sort keys %tbl_meta ) {
 
5949
      foreach my $col ( keys %{$tbl_meta{$tbl}->{cols}} ) {
 
5950
         my $meta = $tbl_meta{$tbl}->{cols}->{$col};
 
5951
         next unless $meta->{user};
 
5952
         print $file "$col=", join(
 
5953
            " ",
 
5954
            map {
 
5955
               # Some properties (trans) are arrays, others scalars
 
5956
               my $val = ref($meta->{$_}) ? join(',', @{$meta->{$_}}) : $meta->{$_};
 
5957
               $val =~ s/([\\'])/\\$1/g;  # Escape backslashes and single quotes
 
5958
               "$_='$val'";               # Enclose in single quotes
 
5959
            }
 
5960
            grep { $_ ne 'func' }
 
5961
            keys %$meta
 
5962
         ), "\n";
 
5963
      }
 
5964
   }
 
5965
}
 
5966
 
 
5967
# save_config_config {{{3
 
5968
sub save_config_config {
 
5969
   my $file = shift;
 
5970
   foreach my $key ( sort keys %config ) {
 
5971
      eval {
 
5972
      if ( $key ne 'password' || $config{savepass}->{val} ) {
 
5973
         print $file "# $config{$key}->{note}\n"
 
5974
            or die "Cannot print to file: $OS_ERROR";
 
5975
         my $val = $config{$key}->{val};
 
5976
         $val = '' unless defined($val);
 
5977
         if ( ref( $val ) eq 'ARRAY' ) {
 
5978
            print $file "$key="
 
5979
               . join( " ", @$val ) . "\n"
 
5980
               or die "Cannot print to file: $OS_ERROR";
 
5981
         }
 
5982
         elsif ( ref( $val ) eq 'HASH' ) {
 
5983
            print $file "$key="
 
5984
               . join( " ",
 
5985
                  map { "$_:$val->{$_}" } keys %$val
 
5986
               ) . "\n";
 
5987
         }
 
5988
         else {
 
5989
            print $file "$key=$val\n";
 
5990
         }
 
5991
      }
 
5992
      };
 
5993
      if ( $EVAL_ERROR ) { print "$EVAL_ERROR in $key"; };
 
5994
   }
 
5995
 
 
5996
}
 
5997
 
 
5998
# load_config_config {{{3
 
5999
sub load_config_config {
 
6000
   my ( $file ) = @_;
 
6001
 
 
6002
   # Look in the command-line parameters for things stored in the same slot.
 
6003
   my %cmdline =
 
6004
      map  { $_->{c} => $opts{$_->{k}} }
 
6005
      grep { exists $_->{c} && exists $opts{$_->{k}} }
 
6006
      @opt_spec;
 
6007
 
 
6008
   while ( my $line = <$file> ) {
 
6009
      chomp $line;
 
6010
      next if $line =~ m/^#/;
 
6011
      last if $line =~ m/^\[/;
 
6012
 
 
6013
      my ( $name, $val ) = $line =~ m/^(.+?)=(.*)$/;
 
6014
      next unless defined $name && defined $val;
 
6015
 
 
6016
      # Values might already have been set at the command line.
 
6017
      $val = defined($cmdline{$name}) ? $cmdline{$name} : $val;
 
6018
 
 
6019
      # Validate the incoming values...
 
6020
      if ( $name && exists( $config{$name} ) ) {
 
6021
         if ( !$config{$name}->{pat} || $val =~ m/$config{$name}->{pat}/ ) {
 
6022
            $config{$name}->{val} = $val;
 
6023
            $config{$name}->{read} = 1;
 
6024
         }
 
6025
      }
 
6026
   }
 
6027
}
 
6028
 
 
6029
# load_config_tbl_meta {{{3
 
6030
sub load_config_tbl_meta {
 
6031
   my ( $file ) = @_;
 
6032
 
 
6033
   while ( my $line = <$file> ) {
 
6034
      chomp $line;
 
6035
      next if $line =~ m/^#/;
 
6036
      last if $line =~ m/^\[/;
 
6037
 
 
6038
      # Each tbl_meta section has all the properties defined in %col_props.
 
6039
      my ( $col , $rest ) = $line =~ m/^(.*?)=(.*)$/;
 
6040
      next unless $col;
 
6041
      my %parts = $rest =~ m/(\w+)='((?:(?!(?<!\\)').)*)'/g; # Properties are single-quoted
 
6042
 
 
6043
      # Each section read from the config file has one extra property: which table it
 
6044
      # goes in.
 
6045
      my $tbl  = $parts{tbl}     or die "There's no table for tbl_meta $col";
 
6046
      my $meta = $tbl_meta{$tbl} or die "There's no table in tbl_meta named $tbl";
 
6047
 
 
6048
      # The section is user-defined by definition (if that makes sense).
 
6049
      $parts{user} = 1;
 
6050
 
 
6051
      # The column may already exist in the table, in which case this is just a
 
6052
      # customization.
 
6053
      $meta->{cols}->{$col} ||= {};
 
6054
 
 
6055
      foreach my $prop ( keys %col_props ) {
 
6056
         if ( !defined($parts{$prop}) ) {
 
6057
            die "Undefined property $prop for column $col in table $tbl";
 
6058
         }
 
6059
 
 
6060
         # Un-escape escaping
 
6061
         $parts{$prop} =~ s/\\\\/\\/g;
 
6062
         $parts{$prop} =~ s/\\'/'/g;
 
6063
 
 
6064
         if ( ref $col_props{$prop} ) {
 
6065
            if ( $prop eq 'trans' ) {
 
6066
               $meta->{cols}->{$col}->{trans}
 
6067
                  = [ unique(grep { exists $trans_funcs{$_} } split(',', $parts{$prop})) ];
 
6068
            }
 
6069
            else {
 
6070
               $meta->{cols}->{$col}->{$prop} = [ split(',', $parts{$prop}) ];
 
6071
            }
 
6072
         }
 
6073
         else {
 
6074
            $meta->{cols}->{$col}->{$prop} = $parts{$prop};
 
6075
         }
 
6076
      }
 
6077
 
 
6078
   }
 
6079
}
 
6080
 
 
6081
# save_config {{{3
 
6082
sub save_config {
 
6083
   return if $config{readonly}->{val};
 
6084
   # Save to a temp file first, so a crash doesn't destroy the main config file
 
6085
   my $newname  = $opts{c} || "$homepath/.innotop/innotop.ini";
 
6086
   my $filename = $newname . '_tmp';
 
6087
   open my $file, "+>", $filename
 
6088
      or die("Can't write to $filename: $OS_ERROR");
 
6089
   print $file "version=$VERSION\n";
 
6090
 
 
6091
   foreach my $section ( @ordered_config_file_sections ) {
 
6092
      die "No such config file section $section" unless $config_file_sections{$section};
 
6093
      print $file "\n[$section]\n\n";
 
6094
      $config_file_sections{$section}->{writer}->($file);
 
6095
      print $file "\n[/$section]\n";
 
6096
   }
 
6097
 
 
6098
   # Now clobber the main config file with the temp.
 
6099
   close $file or die("Can't close $filename: $OS_ERROR");
 
6100
   rename($filename, $newname) or die("Can't rename $filename to $newname: $OS_ERROR");
 
6101
}
 
6102
 
 
6103
# load_config_connections {{{3
 
6104
sub load_config_connections {
 
6105
   my ( $file ) = @_;
 
6106
   while ( my $line = <$file> ) {
 
6107
      chomp $line;
 
6108
      next if $line =~ m/^#/;
 
6109
      last if $line =~ m/^\[/;
 
6110
 
 
6111
      my ( $key , $rest ) = $line =~ m/^(.*?)=(.*)$/;
 
6112
      next unless $key;
 
6113
      my %parts = $rest =~ m/(\S+?)=(\S*)/g;
 
6114
      my %conn  = map { $_ => $parts{$_} || '' } @conn_parts;
 
6115
      $connections{$key} = \%conn;
 
6116
   }
 
6117
}
 
6118
 
 
6119
# save_config_connections {{{3
 
6120
sub save_config_connections {
 
6121
   my $file = shift;
 
6122
   foreach my $conn ( sort keys %connections ) {
 
6123
      my $href = $connections{$conn};
 
6124
      my @keys = $href->{savepass} ? @conn_parts : grep { $_ ne 'pass' } @conn_parts;
 
6125
      print $file "$conn=", join(' ', map { "$_=$href->{$_}" } grep { defined $href->{$_} } @keys), "\n";
 
6126
   }
 
6127
}
 
6128
 
 
6129
sub load_config_colors {
 
6130
   my ( $file ) = @_;
 
6131
   my %rule_set_for;
 
6132
 
 
6133
   while ( my $line = <$file> ) {
 
6134
      chomp $line;
 
6135
      next if $line =~ m/^#/;
 
6136
      last if $line =~ m/^\[/;
 
6137
 
 
6138
      my ( $tbl, $rule ) = $line =~ m/^(.*?)=(.*)$/;
 
6139
      next unless $tbl && $rule;
 
6140
      next unless exists $tbl_meta{$tbl};
 
6141
      my %parts = $rule =~ m/(\w+)='((?:(?!(?<!\\)').)*)'/g; # Properties are single-quoted
 
6142
      next unless $parts{col} && exists $tbl_meta{$tbl}->{cols}->{$parts{col}};
 
6143
      next unless $parts{op}  && exists $comp_ops{$parts{op}};
 
6144
      next unless defined $parts{arg};
 
6145
      next unless defined $parts{color};
 
6146
      my @colors = unique(grep { exists $ansicolors{$_} } split(/\W+/, $parts{color}));
 
6147
      next unless @colors;
 
6148
 
 
6149
      # Finally!  Enough validation...
 
6150
      $rule_set_for{$tbl} ||= [];
 
6151
      push @{$rule_set_for{$tbl}}, \%parts;
 
6152
   }
 
6153
 
 
6154
   foreach my $tbl ( keys %rule_set_for ) {
 
6155
      $tbl_meta{$tbl}->{colors} = $rule_set_for{$tbl};
 
6156
      $tbl_meta{$tbl}->{color_func} = make_color_func($tbl_meta{$tbl});
 
6157
      $tbl_meta{$tbl}->{cust}->{colors} = 1;
 
6158
   }
 
6159
}
 
6160
 
 
6161
# save_config_colors {{{3
 
6162
sub save_config_colors {
 
6163
   my $file = shift;
 
6164
   foreach my $tbl ( sort keys %tbl_meta ) {
 
6165
      my $meta = $tbl_meta{$tbl};
 
6166
      next unless $meta->{cust}->{colors};
 
6167
      foreach my $rule ( @{$meta->{colors}} ) {
 
6168
         print $file "$tbl=", join(
 
6169
            ' ',
 
6170
            map {
 
6171
               my $val = $rule->{$_};
 
6172
               $val =~ s/([\\'])/\\$1/g;  # Escape backslashes and single quotes
 
6173
               "$_='$val'";               # Enclose in single quotes
 
6174
            }
 
6175
            qw(col op arg color)
 
6176
         ), "\n";
 
6177
      }
 
6178
   }
 
6179
}
 
6180
 
 
6181
# load_config_active_connections {{{3
 
6182
sub load_config_active_connections {
 
6183
   my ( $file ) = @_;
 
6184
   while ( my $line = <$file> ) {
 
6185
      chomp $line;
 
6186
      next if $line =~ m/^#/;
 
6187
      last if $line =~ m/^\[/;
 
6188
 
 
6189
      my ( $key , $rest ) = $line =~ m/^(.*?)=(.*)$/;
 
6190
      next unless $key && exists $modes{$key};
 
6191
      my @parts = grep { exists $connections{$_} } split(/ /, $rest);
 
6192
      $modes{$key}->{connections} = [ @parts ] if exists $modes{$key};
 
6193
   }
 
6194
}
 
6195
 
 
6196
# save_config_active_connections {{{3
 
6197
sub save_config_active_connections {
 
6198
   my $file = shift;
 
6199
   foreach my $mode ( sort keys %modes ) {
 
6200
      my @connections = get_connections($mode);
 
6201
      print $file "$mode=", join(' ', @connections), "\n";
 
6202
   }
 
6203
}
 
6204
 
 
6205
# load_config_stmt_sleep_times {{{3
 
6206
sub load_config_stmt_sleep_times {
 
6207
   my ( $file ) = @_;
 
6208
   while ( my $line = <$file> ) {
 
6209
      chomp $line;
 
6210
      next if $line =~ m/^#/;
 
6211
      last if $line =~ m/^\[/;
 
6212
 
 
6213
      my ( $key , $val ) = split('=', $line);
 
6214
      next unless $key && defined $val && $val =~ m/$num_regex/;
 
6215
      $stmt_sleep_time_for{$key} = $val;
 
6216
   }
 
6217
}
 
6218
 
 
6219
# save_config_stmt_sleep_times {{{3
 
6220
sub save_config_stmt_sleep_times {
 
6221
   my $file = shift;
 
6222
   foreach my $key ( sort keys %stmt_sleep_time_for ) {
 
6223
      print $file "$key=$stmt_sleep_time_for{$key}\n";
 
6224
   }
 
6225
}
 
6226
 
 
6227
# load_config_mvs {{{3
 
6228
sub load_config_mvs {
 
6229
   my ( $file ) = @_;
 
6230
   while ( my $line = <$file> ) {
 
6231
      chomp $line;
 
6232
      next if $line =~ m/^#/;
 
6233
      last if $line =~ m/^\[/;
 
6234
 
 
6235
      my ( $key , $val ) = split('=', $line);
 
6236
      next unless $key && defined $val && $val =~ m/$num_regex/;
 
6237
      $mvs{$key} = $val;
 
6238
   }
 
6239
}
 
6240
 
 
6241
# save_config_mvs {{{3
 
6242
sub save_config_mvs {
 
6243
   my $file = shift;
 
6244
   foreach my $key ( sort keys %mvs ) {
 
6245
      print $file "$key=$mvs{$key}\n";
 
6246
   }
 
6247
}
 
6248
 
 
6249
# edit_configuration {{{3
 
6250
sub edit_configuration {
 
6251
   my $key = '';
 
6252
   while ( $key ne 'q' ) {
 
6253
      $clear_screen_sub->();
 
6254
      my @display_lines = '';
 
6255
 
 
6256
      if ( $key && $cfg_editor_action{$key} ) {
 
6257
         $cfg_editor_action{$key}->{func}->();
 
6258
      }
 
6259
 
 
6260
      # Show help
 
6261
      push @display_lines, create_caption('What configuration do you want to edit?',
 
6262
      create_table2(
 
6263
         [ sort keys %cfg_editor_action ],
 
6264
         { map { $_ => $_ } keys %cfg_editor_action },
 
6265
         { map { $_ => $cfg_editor_action{$_}->{note} } keys %cfg_editor_action },
 
6266
         { sep => '  ' }));
 
6267
 
 
6268
      draw_screen(\@display_lines);
 
6269
      $key = pause('');
 
6270
   }
 
6271
}
 
6272
 
 
6273
# edit_configuration_variables {{{3
 
6274
sub edit_configuration_variables {
 
6275
   $clear_screen_sub->();
 
6276
   my $mode = $config{mode}->{val};
 
6277
 
 
6278
   my %config_choices
 
6279
      = map  { $_ => $config{$_}->{note} || '' }
 
6280
        # Only config values that are marked as applying to this mode.
 
6281
        grep {
 
6282
           my $key = $_;
 
6283
           $config{$key}->{conf} &&
 
6284
              ( $config{$key}->{conf} eq 'ALL'
 
6285
              || grep { $mode eq $_ } @{$config{$key}->{conf}} )
 
6286
        } keys %config;
 
6287
 
 
6288
   my $key = prompt_list(
 
6289
      "Enter the name of the variable you wish to configure",
 
6290
      '',
 
6291
      sub{ return keys %config_choices },
 
6292
      \%config_choices);
 
6293
 
 
6294
   if ( exists($config_choices{$key}) ) {
 
6295
      get_config_interactive($key);
 
6296
   }
 
6297
}
 
6298
 
 
6299
# edit_color_rules {{{3
 
6300
sub edit_color_rules {
 
6301
   my ( $tbl ) = @_;
 
6302
   $clear_screen_sub->();
 
6303
   $tbl ||= choose_visible_table();
 
6304
   if ( $tbl && exists($tbl_meta{$tbl}) ) {
 
6305
      my $meta = $tbl_meta{$tbl};
 
6306
      my @cols = ('', qw(col op arg color));
 
6307
      my $info = { map { $_ => { hdr => $_, just => '-', } }  @cols };
 
6308
      $info->{label}->{maxw} = 30;
 
6309
      my $key;
 
6310
      my $selected_rule;
 
6311
 
 
6312
      # This loop builds a tabular view of the rules.
 
6313
      do {
 
6314
 
 
6315
         # Show help
 
6316
         if ( $key && $key eq '?' ) {
 
6317
            my @display_lines = '';
 
6318
            push @display_lines, create_caption('Editor key mappings',
 
6319
            create_table2(
 
6320
               [ sort keys %color_editor_action ],
 
6321
               { map { $_ => $_ } keys %color_editor_action },
 
6322
               { map { $_ => $color_editor_action{$_}->{note} } keys %color_editor_action },
 
6323
               { sep => '  ' }));
 
6324
            draw_screen(\@display_lines);
 
6325
            pause();
 
6326
            $key = '';
 
6327
         }
 
6328
         else {
 
6329
 
 
6330
            # Do the action specified
 
6331
            $selected_rule ||= 0;
 
6332
            if ( $key && $color_editor_action{$key} ) {
 
6333
               $selected_rule = $color_editor_action{$key}->{func}->($tbl, $selected_rule);
 
6334
               $selected_rule ||= 0;
 
6335
            }
 
6336
 
 
6337
            # Build the table of rules.  If the terminal has color, the selected rule
 
6338
            # will be highlighted; otherwise a > at the left will indicate.
 
6339
            my $data = $meta->{colors} || [];
 
6340
            foreach my $i ( 0..@$data - 1  ) {
 
6341
               $data->[$i]->{''} = $i == $selected_rule ? '>' : '';
 
6342
            }
 
6343
            my @display_lines = create_table(\@cols, $info, $data);
 
6344
 
 
6345
            # Highlight selected entry
 
6346
            for my $i ( 0 .. $#display_lines ) {
 
6347
               if ( $display_lines[$i] =~ m/^>/ ) {
 
6348
                  $display_lines[$i] = [ $display_lines[$i], 'reverse' ];
 
6349
               }
 
6350
            }
 
6351
 
 
6352
            # Draw the screen and wait for a command.
 
6353
            unshift @display_lines, '',
 
6354
               "Editing color rules for $meta->{capt}.  Press ? for help, q to "
 
6355
               . "quit.", '';
 
6356
            draw_screen(\@display_lines);
 
6357
            print "\n\n", word_wrap('Rules are applied in order from top to '
 
6358
               . 'bottom.  The first matching rule wins and prevents the '
 
6359
               . 'rest of the rules from being applied.');
 
6360
            $key = pause('');
 
6361
         }
 
6362
      } while ( $key ne 'q' );
 
6363
      $meta->{color_func} = make_color_func($meta);
 
6364
   }
 
6365
}
 
6366
 
 
6367
# add_quick_filter {{{3
 
6368
sub add_quick_filter {
 
6369
   my $tbl = choose_visible_table();
 
6370
   if ( $tbl && exists($tbl_meta{$tbl}) ) {
 
6371
      print "\n";
 
6372
      my $response = prompt_list(
 
6373
         "Enter column name and filter text",
 
6374
         '',
 
6375
         sub { return keys %{$tbl_meta{$tbl}->{cols}} },
 
6376
         ()
 
6377
      );
 
6378
      my ( $col, $text ) = split(/\s+/, $response, 2);
 
6379
 
 
6380
      # You can't filter on a nonexistent column.  But if you filter on a pivoted
 
6381
      # table, the columns are different, so on a pivoted table, allow filtering
 
6382
      # on the 'name' column.
 
6383
      # NOTE: if a table is pivoted and un-pivoted, this will likely cause crashes.
 
6384
      # Currently not an issue since there's no way to toggle pivot/nopivot.
 
6385
      return unless $col && $text &&
 
6386
         (exists($tbl_meta{$tbl}->{cols}->{$col})
 
6387
            || ($tbl_meta{$tbl}->{pivot} && $col eq 'name'));
 
6388
 
 
6389
      my ( $sub, $err ) = compile_filter( "defined \$set->{$col} && \$set->{$col} =~ m/$text/" );
 
6390
      return if !$sub || $err;
 
6391
      my $name = "quick_$tbl.$col";
 
6392
      $filters{$name} = {
 
6393
         func  => $sub,
 
6394
         text  => $text,
 
6395
         user  => 1,
 
6396
         quick => 1,
 
6397
         name  => $name,
 
6398
         note  => 'Quick-filter',
 
6399
         tbls  => [$tbl],
 
6400
      };
 
6401
      push @{$tbl_meta{$tbl}->{filters}}, $name;
 
6402
   }
 
6403
}
 
6404
 
 
6405
# clear_quick_filters {{{3
 
6406
sub clear_quick_filters {
 
6407
   my $tbl = choose_visible_table(
 
6408
      # Only tables that have quick-filters
 
6409
      sub {
 
6410
         my ( $tbl ) = @_;
 
6411
         return scalar grep { $filters{$_}->{quick} } @{ $tbl_meta{$tbl}->{filters} };
 
6412
      }
 
6413
   );
 
6414
   if ( $tbl && exists($tbl_meta{$tbl}) ) {
 
6415
      my @current = @{$tbl_meta{$tbl}->{filters}};
 
6416
      @current = grep { !$filters{$_}->{quick} } @current;
 
6417
      $tbl_meta{$tbl}->{filters} = \@current;
 
6418
   }
 
6419
}
 
6420
 
 
6421
sub edit_plugins {
 
6422
   $clear_screen_sub->();
 
6423
 
 
6424
   my @cols = ('', qw(class desc active));
 
6425
   my $info = { map { $_ => { hdr => $_, just => '-', } }  @cols };
 
6426
   my @rows = map { $plugins{$_} } sort keys %plugins;
 
6427
   my $key;
 
6428
   my $selected;
 
6429
 
 
6430
   # This loop builds a tabular view of the plugins.
 
6431
   do {
 
6432
 
 
6433
      # Show help
 
6434
      if ( $key && $key eq '?' ) {
 
6435
         my @display_lines = '';
 
6436
         push @display_lines, create_caption('Editor key mappings',
 
6437
         create_table2(
 
6438
            [ sort keys %plugin_editor_action ],
 
6439
            { map { $_ => $_ } keys %plugin_editor_action },
 
6440
            { map { $_ => $plugin_editor_action{$_}->{note} } keys %plugin_editor_action },
 
6441
            { sep => '  ' }));
 
6442
         draw_screen(\@display_lines);
 
6443
         pause();
 
6444
         $key = '';
 
6445
      }
 
6446
 
 
6447
      # Do the action specified
 
6448
      else {
 
6449
         $selected ||= 0;
 
6450
         if ( $key && $plugin_editor_action{$key} ) {
 
6451
            $selected = $plugin_editor_action{$key}->{func}->(\@rows, $selected);
 
6452
            $selected ||= 0;
 
6453
         }
 
6454
 
 
6455
         # Build the table of plugins.
 
6456
         foreach my $row ( 0.. $#rows ) {
 
6457
            $rows[$row]->{''} = $row eq $selected ? '>' : ' ';
 
6458
         }
 
6459
         my @display_lines = create_table(\@cols, $info, \@rows);
 
6460
 
 
6461
         # Highlight selected entry
 
6462
         for my $i ( 0 .. $#display_lines ) {
 
6463
            if ( $display_lines[$i] =~ m/^>/ ) {
 
6464
               $display_lines[$i] = [ $display_lines[$i], 'reverse' ];
 
6465
            }
 
6466
         }
 
6467
 
 
6468
         # Draw the screen and wait for a command.
 
6469
         unshift @display_lines, '',
 
6470
            "Plugin Management.  Press ? for help, q to quit.", '';
 
6471
         draw_screen(\@display_lines);
 
6472
         $key = pause('');
 
6473
      }
 
6474
   } while ( $key ne 'q' );
 
6475
}
 
6476
 
 
6477
# edit_table {{{3
 
6478
sub edit_table {
 
6479
   $clear_screen_sub->();
 
6480
   my ( $tbl ) = @_;
 
6481
   $tbl ||= choose_visible_table();
 
6482
   if ( $tbl && exists($tbl_meta{$tbl}) ) {
 
6483
      my $meta = $tbl_meta{$tbl};
 
6484
      my @cols = ('', qw(name hdr label src));
 
6485
      my $info = { map { $_ => { hdr => $_, just => '-', } }  @cols };
 
6486
      $info->{label}->{maxw} = 30;
 
6487
      my $key;
 
6488
      my $selected_column;
 
6489
 
 
6490
      # This loop builds a tabular view of the tbl_meta's structure, showing each column
 
6491
      # in the entry as a row.
 
6492
      do {
 
6493
 
 
6494
         # Show help
 
6495
         if ( $key && $key eq '?' ) {
 
6496
            my @display_lines = '';
 
6497
            push @display_lines, create_caption('Editor key mappings',
 
6498
            create_table2(
 
6499
               [ sort keys %tbl_editor_action ],
 
6500
               { map { $_ => $_ } keys %tbl_editor_action },
 
6501
               { map { $_ => $tbl_editor_action{$_}->{note} } keys %tbl_editor_action },
 
6502
               { sep => '  ' }));
 
6503
            draw_screen(\@display_lines);
 
6504
            pause();
 
6505
            $key = '';
 
6506
         }
 
6507
         else {
 
6508
 
 
6509
            # Do the action specified
 
6510
            $selected_column ||= $meta->{visible}->[0];
 
6511
            if ( $key && $tbl_editor_action{$key} ) {
 
6512
               $selected_column = $tbl_editor_action{$key}->{func}->($tbl, $selected_column);
 
6513
               $selected_column ||= $meta->{visible}->[0];
 
6514
            }
 
6515
 
 
6516
            # Build the pivoted view of the table's meta-data.  If the terminal has color,
 
6517
            # The selected row will be highlighted; otherwise a > at the left will indicate.
 
6518
            my $data = [];
 
6519
            foreach my $row ( @{$meta->{visible}} ) {
 
6520
               my %hash;
 
6521
               @hash{ @cols } = @{$meta->{cols}->{$row}}{@cols};
 
6522
               $hash{src}  = '' if ref $hash{src};
 
6523
               $hash{name} = $row;
 
6524
               $hash{''}   = $row eq $selected_column ? '>' : ' ';
 
6525
               push @$data, \%hash;
 
6526
            }
 
6527
            my @display_lines = create_table(\@cols, $info, $data);
 
6528
 
 
6529
            # Highlight selected entry
 
6530
            for my $i ( 0 .. $#display_lines ) {
 
6531
               if ( $display_lines[$i] =~ m/^>/ ) {
 
6532
                  $display_lines[$i] = [ $display_lines[$i], 'reverse' ];
 
6533
               }
 
6534
            }
 
6535
 
 
6536
            # Draw the screen and wait for a command.
 
6537
            unshift @display_lines, '',
 
6538
               "Editing table definition for $meta->{capt}.  Press ? for help, q to quit.", '';
 
6539
            draw_screen(\@display_lines, { clear => 1 });
 
6540
            $key = pause('');
 
6541
         }
 
6542
      } while ( $key ne 'q' );
 
6543
   }
 
6544
}
 
6545
 
 
6546
# choose_mode_tables {{{3
 
6547
# Choose which table(s), and in what order, to display in a given mode.
 
6548
sub choose_mode_tables {
 
6549
   my $mode = $config{mode}->{val};
 
6550
   my @tbls = @{$modes{$mode}->{visible_tables}};
 
6551
   my $new  = prompt_list(
 
6552
      "Choose tables to display",
 
6553
      join(' ', @tbls),
 
6554
      sub { return @{$modes{$mode}->{tables}} },
 
6555
      { map { $_ => $tbl_meta{$_}->{capt} } @{$modes{$mode}->{tables}} }
 
6556
   );
 
6557
   $modes{$mode}->{visible_tables} =
 
6558
      [ unique(grep { $_ && exists $tbl_meta{$_} } split(/\s+/, $new)) ];
 
6559
   $modes{$mode}->{cust}->{visible_tables} = 1;
 
6560
}
 
6561
 
 
6562
# choose_visible_table {{{3
 
6563
sub choose_visible_table {
 
6564
   my ( $grep_cond ) = @_;
 
6565
   my $mode = $config{mode}->{val};
 
6566
   my @tbls
 
6567
      = grep { $grep_cond ? $grep_cond->($_) : 1 }
 
6568
        @{$modes{$mode}->{visible_tables}};
 
6569
   my $tbl = $tbls[0];
 
6570
   if ( @tbls > 1 ) {
 
6571
      $tbl = prompt_list(
 
6572
         "Choose a table",
 
6573
         '',
 
6574
         sub { return @tbls },
 
6575
         { map { $_ => $tbl_meta{$_}->{capt} } @tbls }
 
6576
      );
 
6577
   }
 
6578
   return $tbl;
 
6579
}
 
6580
 
 
6581
sub toggle_aggregate {
 
6582
   my ( $tbl ) = @_;
 
6583
   $tbl ||= choose_visible_table();
 
6584
   return unless $tbl && exists $tbl_meta{$tbl};
 
6585
   my $meta = $tbl_meta{$tbl};
 
6586
   $meta->{aggregate} ^= 1;
 
6587
}
 
6588
 
 
6589
sub choose_filters {
 
6590
   my ( $tbl ) = @_;
 
6591
   $tbl ||= choose_visible_table();
 
6592
   return unless $tbl && exists $tbl_meta{$tbl};
 
6593
   my $meta = $tbl_meta{$tbl};
 
6594
   $clear_screen_sub->();
 
6595
 
 
6596
   print "Choose filters for $meta->{capt}:\n";
 
6597
 
 
6598
   my $ini = join(' ', @{$meta->{filters}});
 
6599
   my $val = prompt_list(
 
6600
      'Choose filters',
 
6601
      $ini,
 
6602
      sub { return keys %filters },
 
6603
      {
 
6604
         map  { $_ => $filters{$_}->{note} }
 
6605
         grep { grep { $tbl eq $_ } @{$filters{$_}->{tbls}} }
 
6606
         keys %filters
 
6607
      }
 
6608
   );
 
6609
 
 
6610
   my @choices = unique(split(/\s+/, $val));
 
6611
   foreach my $new ( grep { !exists($filters{$_}) } @choices ) {
 
6612
      my $answer = prompt("There is no filter called '$new'.  Create it?", undef, 'y');
 
6613
      if ( $answer eq 'y' ) {
 
6614
         create_new_filter($new, $tbl);
 
6615
      }
 
6616
   }
 
6617
   @choices = grep { exists $filters{$_} } @choices;
 
6618
   @choices = grep { grep { $tbl eq $_ } @{$filters{$_}->{tbls}} } @choices;
 
6619
   $meta->{filters} = [ @choices ];
 
6620
   $meta->{cust}->{filters} = 1;
 
6621
}
 
6622
 
 
6623
sub choose_group_cols {
 
6624
   my ( $tbl ) = @_;
 
6625
   $tbl ||= choose_visible_table();
 
6626
   return unless $tbl && exists $tbl_meta{$tbl};
 
6627
   $clear_screen_sub->();
 
6628
   my $meta = $tbl_meta{$tbl};
 
6629
   my $curr = join(', ', @{$meta->{group_by}});
 
6630
   my $val = prompt_list(
 
6631
      'Group-by columns',
 
6632
      $curr,
 
6633
      sub { return keys %{$meta->{cols}} },
 
6634
      { map { $_ => $meta->{cols}->{$_}->{label} } keys %{$meta->{cols}} });
 
6635
   if ( $curr ne $val ) {
 
6636
      $meta->{group_by} = [ grep { exists $meta->{cols}->{$_} } $val =~ m/(\w+)/g ];
 
6637
      $meta->{cust}->{group_by} = 1;
 
6638
   }
 
6639
}
 
6640
 
 
6641
sub choose_sort_cols {
 
6642
   my ( $tbl ) = @_;
 
6643
   $tbl ||= choose_visible_table();
 
6644
   return unless $tbl && exists $tbl_meta{$tbl};
 
6645
   $clear_screen_sub->();
 
6646
   my $meta = $tbl_meta{$tbl};
 
6647
 
 
6648
   my ( $cols, $hints );
 
6649
   if ( $meta->{pivot} ) {
 
6650
      $cols  = sub { qw(name set_0) };
 
6651
      $hints = { name => 'name', set_0 => 'set_0' };
 
6652
   }
 
6653
   else {
 
6654
      $cols  = sub { return keys %{$meta->{cols}} };
 
6655
      $hints = { map { $_ => $meta->{cols}->{$_}->{label} } keys %{$meta->{cols}} };
 
6656
   }
 
6657
 
 
6658
   my $val = prompt_list(
 
6659
      'Sort columns (reverse sort with -col)',
 
6660
      $meta->{sort_cols},
 
6661
      $cols,
 
6662
      $hints );
 
6663
   if ( $meta->{sort_cols} ne $val ) {
 
6664
      $meta->{sort_cols} = $val;
 
6665
      $meta->{cust}->{sort_cols} = 1;
 
6666
      $tbl_meta{$tbl}->{sort_func} = make_sort_func($tbl_meta{$tbl});
 
6667
   }
 
6668
}
 
6669
 
 
6670
# create_new_filter {{{3
 
6671
sub create_new_filter {
 
6672
   my ( $filter, $tbl ) = @_;
 
6673
   $clear_screen_sub->();
 
6674
 
 
6675
   if ( !$filter || $filter =~ m/\W/ ) {
 
6676
      print word_wrap("Choose a name for the filter.  This name is not displayed, and is only used "
 
6677
            . "for internal reference.  It can only contain lowercase letters, numbers, and underscores.");
 
6678
      print "\n\n";
 
6679
      do {
 
6680
         $filter = prompt("Enter filter name");
 
6681
      } while ( !$filter || $filter =~ m/\W/ );
 
6682
   }
 
6683
 
 
6684
   my $completion = sub { keys %{$tbl_meta{$tbl}->{cols}} };
 
6685
   my ( $err, $sub, $body );
 
6686
   do {
 
6687
      $clear_screen_sub->();
 
6688
      print word_wrap("A filter is a Perl subroutine that accepts a hashref of columns "
 
6689
         . "called \$set, and returns a true value if the filter accepts the row.  Example:\n"
 
6690
         . "   \$set->{active_secs} > 5\n"
 
6691
         . "will only allow rows if their active_secs column is greater than 5.");
 
6692
      print "\n\n";
 
6693
      if ( $err ) {
 
6694
         print "There's an error in your filter expression: $err\n\n";
 
6695
      }
 
6696
      $body = prompt("Enter subroutine body", undef, undef, $completion);
 
6697
      ( $sub, $err ) = compile_filter($body);
 
6698
   } while ( $err );
 
6699
 
 
6700
   $filters{$filter} = {
 
6701
      func => $sub,
 
6702
      text => $body,
 
6703
      user => 1,
 
6704
      name => $filter,
 
6705
      note => 'User-defined filter',
 
6706
      tbls => [$tbl],
 
6707
   };
 
6708
}
 
6709
 
 
6710
# get_config_interactive {{{3
 
6711
sub get_config_interactive {
 
6712
   my $key = shift;
 
6713
   $clear_screen_sub->();
 
6714
 
 
6715
   # Print help first.
 
6716
   print "Enter a new value for '$key' ($config{$key}->{note}).\n";
 
6717
 
 
6718
   my $current = ref($config{$key}->{val}) ? join(" ", @{$config{$key}->{val}}) : $config{$key}->{val};
 
6719
 
 
6720
   my $new_value = prompt('Enter a value', $config{$key}->{pat}, $current);
 
6721
   $config{$key}->{val} = $new_value;
 
6722
}
 
6723
 
 
6724
sub edit_current_var_set {
 
6725
   my $mode = $config{mode}->{val};
 
6726
   my $name = $config{"${mode}_set"}->{val};
 
6727
   my $variables = $var_sets{$name}->{text};
 
6728
 
 
6729
   my $new = $variables;
 
6730
   do {
 
6731
      $clear_screen_sub->();
 
6732
      $new = prompt("Enter variables for $name", undef, $variables);
 
6733
   } until ( $new );
 
6734
 
 
6735
   if ( $new ne $variables ) {
 
6736
      @{$var_sets{$name}}{qw(text user)} = ( $new, 1);
 
6737
   }
 
6738
}
 
6739
 
 
6740
 
 
6741
sub choose_var_set {
 
6742
   my ( $key ) = @_;
 
6743
   $clear_screen_sub->();
 
6744
 
 
6745
   my $new_value = prompt_list(
 
6746
      'Choose a set of values to display, or enter the name of a new one',
 
6747
      $config{$key}->{val},
 
6748
      sub { return keys %var_sets },
 
6749
      { map { $_ => $var_sets{$_}->{text} } keys %var_sets });
 
6750
 
 
6751
   if ( !exists $var_sets{$new_value} ) {
 
6752
      add_new_var_set($new_value);
 
6753
   }
 
6754
 
 
6755
   $config{$key}->{val} = $new_value if exists $var_sets{$new_value};
 
6756
}
 
6757
 
 
6758
sub switch_var_set {
 
6759
   my ( $cfg_var, $dir ) = @_;
 
6760
   my @var_sets = sort keys %var_sets;
 
6761
   my $cur      = $config{$cfg_var}->{val};
 
6762
   my $pos      = grep { $_ lt $cur } @var_sets;
 
6763
   my $newpos   = ($pos + $dir) % @var_sets;
 
6764
   $config{$cfg_var}->{val} = $var_sets[$newpos];
 
6765
   $clear_screen_sub->();
 
6766
}
 
6767
 
 
6768
# Online configuration and prompting functions {{{2
 
6769
 
 
6770
# edit_stmt_sleep_times {{{3
 
6771
sub edit_stmt_sleep_times {
 
6772
   $clear_screen_sub->();
 
6773
   my $stmt = prompt_list('Specify a statement', '', sub { return sort keys %stmt_maker_for });
 
6774
   return unless $stmt && exists $stmt_maker_for{$stmt};
 
6775
   $clear_screen_sub->();
 
6776
   my $curr_val = $stmt_sleep_time_for{$stmt} || 0;
 
6777
   my $new_val  = prompt('Specify a sleep delay after calling this SQL', $num_regex, $curr_val);
 
6778
   if ( $new_val ) {
 
6779
      $stmt_sleep_time_for{$stmt} = $new_val;
 
6780
   }
 
6781
   else {
 
6782
      delete $stmt_sleep_time_for{$stmt};
 
6783
   }
 
6784
}
 
6785
 
 
6786
# edit_server_groups {{{3
 
6787
# Choose which server connections are in a server group.  First choose a group,
 
6788
# then choose which connections are in it.
 
6789
sub edit_server_groups {
 
6790
   $clear_screen_sub->();
 
6791
   my $mode  = $config{mode}->{val};
 
6792
   my $group = $modes{$mode}->{server_group};
 
6793
   my %curr  = %server_groups;
 
6794
   my $new   = choose_or_create_server_group($group, 'to edit');
 
6795
   $clear_screen_sub->();
 
6796
   if ( exists $curr{$new} ) {
 
6797
      # Don't do this step if the user just created a new server group,
 
6798
      # because part of that process was to choose connections.
 
6799
      my $cxns  = join(' ', @{$server_groups{$new}});
 
6800
      my @conns = choose_or_create_connection($cxns, 'for this group');
 
6801
      $server_groups{$new} = \@conns;
 
6802
   }
 
6803
}
 
6804
 
 
6805
# choose_server_groups {{{3
 
6806
sub choose_server_groups {
 
6807
   $clear_screen_sub->();
 
6808
   my $mode  = $config{mode}->{val};
 
6809
   my $group = $modes{$mode}->{server_group};
 
6810
   my $new   = choose_or_create_server_group($group, 'for this mode');
 
6811
   $modes{$mode}->{server_group} = $new if exists $server_groups{$new};
 
6812
}
 
6813
 
 
6814
sub choose_or_create_server_group {
 
6815
   my ( $group, $prompt ) = @_;
 
6816
   my $new   = '';
 
6817
 
 
6818
   my @available = sort keys %server_groups;
 
6819
 
 
6820
   if ( @available ) {
 
6821
      print "You can enter the name of a new group to create it.\n";
 
6822
 
 
6823
      $new = prompt_list(
 
6824
         "Choose a server group $prompt",
 
6825
         $group,
 
6826
         sub { return @available },
 
6827
         { map { $_ => join(' ', @{$server_groups{$_}}) } @available });
 
6828
 
 
6829
      $new =~ s/\s.*//;
 
6830
 
 
6831
      if ( !exists $server_groups{$new} ) {
 
6832
         my $answer = prompt("There is no server group called '$new'.  Create it?", undef, "y");
 
6833
         if ( $answer eq 'y' ) {
 
6834
            add_new_server_group($new);
 
6835
         }
 
6836
      }
 
6837
   }
 
6838
   else {
 
6839
      $new = add_new_server_group();
 
6840
   }
 
6841
   return $new;
 
6842
}
 
6843
 
 
6844
sub choose_or_create_connection {
 
6845
   my ( $cxns, $prompt ) = @_;
 
6846
   print "You can enter the name of a new connection to create it.\n";
 
6847
 
 
6848
   my @available = sort keys %connections;
 
6849
   my $new_cxns = prompt_list(
 
6850
      "Choose connections $prompt",
 
6851
      $cxns,
 
6852
      sub { return @available },
 
6853
      { map { $_ => $connections{$_}->{dsn} } @available });
 
6854
 
 
6855
   my @new = unique(grep { !exists $connections{$_} } split(/\s+/, $new_cxns));
 
6856
   foreach my $new ( @new ) {
 
6857
      my $answer = prompt("There is no connection called '$new'.  Create it?", undef, "y");
 
6858
      if ( $answer eq 'y' ) {
 
6859
         add_new_dsn($new);
 
6860
      }
 
6861
   }
 
6862
 
 
6863
   return unique(grep { exists $connections{$_} } split(/\s+/, $new_cxns));
 
6864
}
 
6865
 
 
6866
# choose_servers {{{3
 
6867
sub choose_servers {
 
6868
   $clear_screen_sub->();
 
6869
   my $mode = $config{mode}->{val};
 
6870
   my $cxns = join(' ', get_connections());
 
6871
   my @chosen = choose_or_create_connection($cxns, 'for this mode');
 
6872
   $modes{$mode}->{connections} = \@chosen;
 
6873
   $modes{$mode}->{server_group} = ''; # Clear this because it overrides {connections}
 
6874
}
 
6875
 
 
6876
# display_license {{{3
 
6877
sub display_license {
 
6878
   $clear_screen_sub->();
 
6879
 
 
6880
   print $innotop_license;
 
6881
 
 
6882
   pause();
 
6883
}
 
6884
 
 
6885
# Data-retrieval functions {{{2
 
6886
# get_status_info {{{3
 
6887
# Get SHOW STATUS and SHOW VARIABLES together.
 
6888
sub get_status_info {
 
6889
   my @cxns = @_;
 
6890
   if ( !$info_gotten{status}++ ) {
 
6891
      foreach my $cxn ( @cxns ) {
 
6892
         $vars{$cxn}->{$clock} ||= {};
 
6893
         my $vars = $vars{$cxn}->{$clock};
 
6894
 
 
6895
         my $sth = do_stmt($cxn, 'SHOW_STATUS') or next;
 
6896
         my $res = $sth->fetchall_arrayref();
 
6897
         map { $vars->{$_->[0]} = $_->[1] || 0 } @$res;
 
6898
 
 
6899
         # Calculate hi-res uptime and add cxn to the hash.  This duplicates get_driver_status,
 
6900
         # but it's most important to have consistency.
 
6901
         $vars->{Uptime_hires} ||= get_uptime($cxn);
 
6902
         $vars->{cxn} = $cxn;
 
6903
 
 
6904
         # Add SHOW VARIABLES to the hash
 
6905
         $sth = do_stmt($cxn, 'SHOW_VARIABLES') or next;
 
6906
         $res = $sth->fetchall_arrayref();
 
6907
         map { $vars->{$_->[0]} = $_->[1] || 0 } @$res;
 
6908
      }
 
6909
   }
 
6910
}
 
6911
 
 
6912
# Chooses a thread for explaining, killing, etc...
 
6913
# First arg is a func that can be called in grep.
 
6914
sub choose_thread {
 
6915
   my ( $grep_cond, $prompt ) = @_;
 
6916
 
 
6917
   # Narrow the list to queries that can be explained.
 
6918
   my %thread_for = map {
 
6919
      # Eliminate innotop's own threads.
 
6920
      $_ => $dbhs{$_}->{dbh} ? $dbhs{$_}->{dbh}->{mysql_thread_id} : 0
 
6921
   } keys %connections;
 
6922
 
 
6923
   my @candidates = grep {
 
6924
      $_->{id} != $thread_for{$_->{cxn}} && $grep_cond->($_)
 
6925
   } @current_queries;
 
6926
   return unless @candidates;
 
6927
 
 
6928
   # Find out which server.
 
6929
   my @cxns = unique map { $_->{cxn} } @candidates;
 
6930
   my ( $cxn ) = select_cxn('On which server', @cxns);
 
6931
   return unless $cxn && exists($connections{$cxn});
 
6932
 
 
6933
   # Re-filter the list of candidates to only those on this server
 
6934
   @candidates = grep { $_->{cxn} eq $cxn } @candidates;
 
6935
 
 
6936
   # Find out which thread to do.
 
6937
   my $info;
 
6938
   if ( @candidates > 1 ) {
 
6939
 
 
6940
      # Sort longest-active first, then longest-idle.
 
6941
      my $sort_func = sub {
 
6942
         my ( $a, $b ) = @_;
 
6943
         return  $a->{query} && !$b->{query} ? 1
 
6944
               : $b->{query} && !$a->{query} ? -1
 
6945
               : ($a->{time} || 0) <=> ($b->{time} || 0);
 
6946
      };
 
6947
      my @threads = map { $_->{id} } reverse sort { $sort_func->($a, $b) } @candidates;
 
6948
 
 
6949
      print "\n";
 
6950
      my $thread = prompt_list($prompt,
 
6951
         $threads[0],
 
6952
         sub { return @threads });
 
6953
      return unless $thread && $thread =~ m/$int_regex/;
 
6954
 
 
6955
      # Find the info hash of that query on that server.
 
6956
      ( $info ) = grep { $thread == $_->{id} } @candidates;
 
6957
   }
 
6958
   else {
 
6959
      $info = $candidates[0];
 
6960
   }
 
6961
   return $info;
 
6962
}
 
6963
 
 
6964
# analyze_query {{{3
 
6965
# Allows the user to show fulltext, explain, show optimized...
 
6966
sub analyze_query {
 
6967
   my ( $action ) = @_;
 
6968
 
 
6969
   my $info = choose_thread(
 
6970
      sub { $_[0]->{query} },
 
6971
      'Select a thread to analyze',
 
6972
   );
 
6973
   return unless $info;
 
6974
 
 
6975
   my %actions = (
 
6976
      e => \&display_explain,
 
6977
      f => \&show_full_query,
 
6978
      o => \&show_optimized_query,
 
6979
   );
 
6980
   do {
 
6981
      $actions{$action}->($info);
 
6982
      print "\n";
 
6983
      $action = pause('Press e to explain, f for full query, o for optimized query');
 
6984
   } while ( exists($actions{$action}) );
 
6985
}
 
6986
 
 
6987
# inc {{{3
 
6988
# Returns the difference between two sets of variables/status/innodb stuff.
 
6989
sub inc {
 
6990
   my ( $offset, $cxn ) = @_;
 
6991
   my $vars = $vars{$cxn};
 
6992
   if ( $offset < 0 ) {
 
6993
      return $vars->{$clock};
 
6994
   }
 
6995
   elsif ( exists $vars{$clock - $offset} && !exists $vars->{$clock - $offset - 1} ) {
 
6996
      return $vars->{$clock - $offset};
 
6997
   }
 
6998
   my $cur = $vars->{$clock - $offset};
 
6999
   my $pre = $vars->{$clock - $offset - 1};
 
7000
   return {
 
7001
      # Numeric variables get subtracted, non-numeric get passed straight through.
 
7002
      map  {
 
7003
         $_ =>
 
7004
            ( (defined $cur->{$_} && $cur->{$_} =~ m/$num_regex/)
 
7005
            ?  $cur->{$_} - ($pre->{$_} || 0)
 
7006
            :  $cur->{$_} )
 
7007
      } keys %{$cur}
 
7008
   };
 
7009
}
 
7010
 
 
7011
# extract_values {{{3
 
7012
# Arguments are a set of values (which may be incremental, derived from
 
7013
# current and previous), current, and previous values.
 
7014
# TODO: there are a few places that don't remember prev set so can't pass it.
 
7015
sub extract_values {
 
7016
   my ( $set, $cur, $pre, $tbl ) = @_;
 
7017
 
 
7018
   # Hook in event listeners
 
7019
   foreach my $listener ( @{$event_listener_for{extract_values}} ) {
 
7020
      $listener->extract_values($set, $cur, $pre, $tbl);
 
7021
   }
 
7022
 
 
7023
   my $result = {};
 
7024
   my $meta   = $tbl_meta{$tbl};
 
7025
   my $cols   = $meta->{cols};
 
7026
   foreach my $key ( keys %$cols ) {
 
7027
      my $info = $cols->{$key}
 
7028
         or die "Column '$key' doesn't exist in $tbl";
 
7029
      die "No func defined for '$key' in $tbl"
 
7030
         unless $info->{func};
 
7031
      eval {
 
7032
         $result->{$key} = $info->{func}->($set, $cur, $pre)
 
7033
      };
 
7034
      if ( $EVAL_ERROR ) {
 
7035
         if ( $config{debug}->{val} ) {
 
7036
            die $EVAL_ERROR;
 
7037
         }
 
7038
         $result->{$key} = $info->{num} ? 0 : '';
 
7039
      }
 
7040
   }
 
7041
   return $result;
 
7042
}
 
7043
 
 
7044
# get_full_processlist {{{3
 
7045
sub get_full_processlist {
 
7046
   my @cxns = @_;
 
7047
   my @result;
 
7048
   foreach my $cxn ( @cxns ) {
 
7049
      my $stmt = do_stmt($cxn, 'PROCESSLIST') or next;
 
7050
      my $arr  = $stmt->fetchall_arrayref({});
 
7051
      push @result, map { $_->{cxn} = $cxn; $_ } @$arr;
 
7052
   }
 
7053
   return @result;
 
7054
}
 
7055
 
 
7056
# get_open_tables {{{3
 
7057
sub get_open_tables {
 
7058
   my @cxns = @_;
 
7059
   my @result;
 
7060
   foreach my $cxn ( @cxns ) {
 
7061
      my $stmt = do_stmt($cxn, 'OPEN_TABLES') or next;
 
7062
      my $arr  = $stmt->fetchall_arrayref({});
 
7063
      push @result, map { $_->{cxn} = $cxn; $_ } @$arr;
 
7064
   }
 
7065
   return @result;
 
7066
}
 
7067
 
 
7068
# get_innodb_status {{{3
 
7069
sub get_innodb_status {
 
7070
   my ( $cxns, $addl_sections ) = @_;
 
7071
   if ( !$config{skip_innodb}->{val} && !$info_gotten{innodb_status}++ ) {
 
7072
 
 
7073
      # Determine which sections need to be parsed
 
7074
      my %sections_required =
 
7075
         map  { $tbl_meta{$_}->{innodb} => 1 }
 
7076
         grep { $_ && $tbl_meta{$_}->{innodb} }
 
7077
         get_visible_tables();
 
7078
 
 
7079
      # Add in any other sections the caller requested.
 
7080
      foreach my $sec ( @$addl_sections ) {
 
7081
         $sections_required{$sec} = 1;
 
7082
      }
 
7083
 
 
7084
      foreach my $cxn ( @$cxns ) {
 
7085
         my $innodb_status_text;
 
7086
 
 
7087
         if ( $file ) { # Try to fetch status text from the file.
 
7088
            my @stat = stat($file);
 
7089
 
 
7090
            # Initialize the file.
 
7091
            if ( !$file_mtime ) {
 
7092
               # Initialize to 130k from the end of the file (because the limit
 
7093
               # on the size of innodb status is 128k even with Google's patches)
 
7094
               # and try to grab the last status from the file.
 
7095
               sysseek($file, (-128 * 1_024), 2);
 
7096
            }
 
7097
 
 
7098
            # Read from the file.
 
7099
            my $buffer;
 
7100
            if ( !$file_mtime || $file_mtime != $stat[9] ) {
 
7101
               $file_data = '';
 
7102
               while ( sysread($file, $buffer, 4096) ) {
 
7103
                  $file_data .= $buffer;
 
7104
               }
 
7105
               $file_mtime = $stat[9];
 
7106
            }
 
7107
 
 
7108
            # Delete everything but the last InnoDB status text from the file.
 
7109
            $file_data =~ s/\A.*(?=^=====================================\n...... ........ INNODB MONITOR OUTPUT)//ms;
 
7110
            $innodb_status_text = $file_data;
 
7111
         }
 
7112
 
 
7113
         else {
 
7114
            my $stmt = do_stmt($cxn, 'INNODB_STATUS') or next;
 
7115
            $innodb_status_text = $stmt->fetchrow_hashref()->{status};
 
7116
         }
 
7117
 
 
7118
         next unless $innodb_status_text
 
7119
            && substr($innodb_status_text, 0, 100) =~ m/INNODB MONITOR OUTPUT/;
 
7120
 
 
7121
         # Parse and merge into %vars storage
 
7122
         my %innodb_status = (
 
7123
            $innodb_parser->get_status_hash(
 
7124
               $innodb_status_text,
 
7125
               $config{debug}->{val},
 
7126
               \%sections_required,
 
7127
               0, # don't parse full lock information
 
7128
            )
 
7129
         );
 
7130
         if ( !$innodb_status{IB_got_all} && $config{auto_wipe_dl}->{val} ) {
 
7131
            clear_deadlock($cxn);
 
7132
         }
 
7133
 
 
7134
         # Merge using a hash slice, which is the fastest way
 
7135
         $vars{$cxn}->{$clock} ||= {};
 
7136
         my $hash = $vars{$cxn}->{$clock};
 
7137
         @{$hash}{ keys %innodb_status } = values %innodb_status;
 
7138
         $hash->{cxn} = $cxn;
 
7139
         $hash->{Uptime_hires} ||= get_uptime($cxn);
 
7140
      }
 
7141
   }
 
7142
}
 
7143
 
 
7144
# clear_deadlock {{{3
 
7145
sub clear_deadlock {
 
7146
   my ( $cxn ) = @_;
 
7147
   return if $clearing_deadlocks++;
 
7148
   my $tbl = $connections{$cxn}->{dl_table};
 
7149
   return unless $tbl;
 
7150
 
 
7151
   eval {
 
7152
      # Set up the table for creating a deadlock.
 
7153
      my $engine = version_ge($dbhs{$cxn}->{dbh}, '4.1.2') ? 'engine' : 'type';
 
7154
      return unless do_query($cxn, "drop table if exists $tbl");
 
7155
      return unless do_query($cxn, "create table $tbl(a int) $engine=innodb");
 
7156
      return unless do_query($cxn, "delete from $tbl");
 
7157
      return unless do_query($cxn, "insert into $tbl(a) values(0), (1)");
 
7158
      return unless do_query($cxn, "commit"); # Or the children will block against the parent
 
7159
 
 
7160
      # Fork off two children to deadlock against each other.
 
7161
      my %children;
 
7162
      foreach my $child ( 0..1 ) {
 
7163
         my $pid = fork();
 
7164
         if ( defined($pid) && $pid == 0 ) { # I am a child
 
7165
            deadlock_thread( $child, $tbl, $cxn );
 
7166
         }
 
7167
         elsif ( !defined($pid) ) {
 
7168
            die("Unable to fork for clearing deadlocks!\n");
 
7169
         }
 
7170
         # I already exited if I'm a child, so I'm the parent.
 
7171
         $children{$child} = $pid;
 
7172
      }
 
7173
 
 
7174
      # Wait for the children to exit.
 
7175
      foreach my $child ( keys %children ) {
 
7176
         my $pid = waitpid($children{$child}, 0);
 
7177
      }
 
7178
 
 
7179
      # Clean up.
 
7180
      do_query($cxn, "drop table $tbl");
 
7181
   };
 
7182
   if ( $EVAL_ERROR ) {
 
7183
      print $EVAL_ERROR;
 
7184
      pause();
 
7185
   }
 
7186
 
 
7187
   $clearing_deadlocks = 0;
 
7188
}
 
7189
 
 
7190
sub get_master_logs {
 
7191
   my @cxns = @_;
 
7192
   my @result;
 
7193
   if ( !$info_gotten{master_logs}++ ) {
 
7194
      foreach my $cxn ( @cxns ) {
 
7195
         my $stmt = do_stmt($cxn, 'SHOW_MASTER_LOGS') or next;
 
7196
         push @result, @{$stmt->fetchall_arrayref({})};
 
7197
      }
 
7198
   }
 
7199
   return @result;
 
7200
}
 
7201
 
 
7202
# get_master_slave_status {{{3
 
7203
sub get_master_slave_status {
 
7204
   my @cxns = @_;
 
7205
   if ( !$info_gotten{replication_status}++ ) {
 
7206
      foreach my $cxn ( @cxns ) {
 
7207
         $vars{$cxn}->{$clock} ||= {};
 
7208
         my $vars = $vars{$cxn}->{$clock};
 
7209
         $vars->{cxn} = $cxn;
 
7210
 
 
7211
         my $stmt = do_stmt($cxn, 'SHOW_MASTER_STATUS') or next;
 
7212
         my $res = $stmt->fetchall_arrayref({})->[0];
 
7213
         @{$vars}{ keys %$res } = values %$res;
 
7214
         $stmt = do_stmt($cxn, 'SHOW_SLAVE_STATUS') or next;
 
7215
         $res = $stmt->fetchall_arrayref({})->[0];
 
7216
         @{$vars}{ keys %$res } = values %$res;
 
7217
         $vars->{Uptime_hires} ||= get_uptime($cxn);
 
7218
      }
 
7219
   }
 
7220
}
 
7221
 
 
7222
sub is_func {
 
7223
   my ( $word ) = @_;
 
7224
   return defined(&$word)
 
7225
      || eval "my \$x= sub { $word  }; 1"
 
7226
      || $EVAL_ERROR !~ m/^Bareword/;
 
7227
}
 
7228
 
 
7229
# Documentation {{{1
 
7230
# ############################################################################
 
7231
# I put this last as per the Dog book.
 
7232
# ############################################################################
 
7233
=pod
 
7234
 
 
7235
=head1 NAME
 
7236
 
 
7237
innotop - MySQL and InnoDB transaction/status monitor.
 
7238
 
 
7239
=head1 SYNOPSIS
 
7240
 
 
7241
To monitor servers normally:
 
7242
 
 
7243
 innotop
 
7244
 
 
7245
To monitor InnoDB status information from a file:
 
7246
 
 
7247
 innotop /var/log/mysql/mysqld.err
 
7248
 
 
7249
To run innotop non-interactively in a pipe-and-filter configuration:
 
7250
 
 
7251
 innotop --count 5 -d 1 -n
 
7252
 
 
7253
=head1 DESCRIPTION
 
7254
 
 
7255
innotop monitors MySQL servers.  Each of its modes shows you a different aspect
 
7256
of what's happening in the server.  For example, there's a mode for monitoring
 
7257
replication, one for queries, and one for transactions.  innotop refreshes its
 
7258
data periodically, so you see an updating view.
 
7259
 
 
7260
innotop has lots of features for power users, but you can start and run it with
 
7261
virtually no configuration.  If you're just getting started, see
 
7262
L<"QUICK-START">.  Press '?' at any time while running innotop for
 
7263
context-sensitive help.
 
7264
 
 
7265
=head1 QUICK-START
 
7266
 
 
7267
To start innotop, open a terminal or command prompt.  If you have installed
 
7268
innotop on your system, you should be able to just type "innotop" and press
 
7269
Enter; otherwise, you will need to change to innotop's directory and type "perl
 
7270
innotop".
 
7271
 
 
7272
The first thing innotop needs to know is how to connect to a MySQL server.  You
 
7273
can just enter the hostname of the server, for example "localhost" or
 
7274
"127.0.0.1" if the server is on the same machine as innotop.  After this innotop
 
7275
will prompt you for a DSN (data source name).  You should be able to just accept
 
7276
the defaults by pressing Enter.
 
7277
 
 
7278
When innotop asks you about a table to use when resetting InnoDB deadlock
 
7279
information, just accept the default for now.  This is an advanced feature you
 
7280
can configure later (see L<"D: InnoDB Deadlocks"> for more).
 
7281
 
 
7282
If you have a .my.cnf file with your MySQL connection defaults, innotop can read
 
7283
it, and you won't need to specify a username and password if it's in that file.
 
7284
Otherwise, you should answer 'y' to the next couple of prompts.
 
7285
 
 
7286
After this, you should be connected, and innotop should show you something like
 
7287
the following:
 
7288
 
 
7289
 InnoDB Txns (? for help) localhost, 01:11:19, InnoDB 10s :-), 50 QPS,
 
7290
 
 
7291
 CXN        History  Versions  Undo  Dirty Buf  Used Bufs  Txns  MaxTxn
 
7292
 localhost        7      2035  0 0       0.00%     92.19%     1   07:34
 
7293
 
 
7294
 CXN        ID     User   Host       Txn Status  Time   Undo  Query Tex
 
7295
 localhost  98379  user1  webserver  ACTIVE      07:34     0  SELECT `c
 
7296
 localhost  98450  user1  webserver  ACTIVE      01:06     0  INSERT IN
 
7297
 localhost  97750  user1  webserver  not starte  00:00     0      
 
7298
 localhost  98375  user1  appserver  not starte  00:00     0      
 
7299
 
 
7300
(This sample is truncated at the right so it will fit on a terminal when running
 
7301
'man innotop')
 
7302
 
 
7303
This sample comes from a quiet server with few transactions active.  If your
 
7304
server is busy, you'll see more output.  Notice the first line on the screen,
 
7305
which tells you what mode you're in and what server you're connected to.  You
 
7306
can change to other modes with keystrokes; press 'Q' to switch to a list of
 
7307
currently running queries.
 
7308
 
 
7309
Press the '?' key to see what keys are active in the current mode.  You can
 
7310
press any of these keys and innotop will either take the requested action or
 
7311
prompt you for more input.  If your system has Term::ReadLine support, you can
 
7312
use TAB and other keys to auto-complete and edit input.
 
7313
 
 
7314
To quit innotop, press the 'q' key.
 
7315
 
 
7316
=head1 OPTIONS
 
7317
 
 
7318
innotop is mostly configured via its configuration file, but some of the
 
7319
configuration options can come from the command line.  You can also specify a
 
7320
file to monitor for InnoDB status output; see L<"MONITORING A FILE"> for more
 
7321
details.
 
7322
 
 
7323
You can negate some options by prefixing the option name with --no.  For
 
7324
example, --noinc (or --no-inc) negates L<"--inc">.
 
7325
 
 
7326
=over
 
7327
 
 
7328
=item --help
 
7329
 
 
7330
Print a summary of command-line usage and exit.
 
7331
 
 
7332
=item --color
 
7333
 
 
7334
Enable or disable terminal coloring.  Corresponds to the L<"color"> config file
 
7335
setting.
 
7336
 
 
7337
=item --config
 
7338
 
 
7339
Specifies a configuration file to read.  This option is non-sticky, that is to
 
7340
say it does not persist to the configuration file itself.
 
7341
 
 
7342
=item --nonint
 
7343
 
 
7344
Enable non-interactive operation.  See L<"NON-INTERACTIVE OPERATION"> for more.
 
7345
 
 
7346
=item --count
 
7347
 
 
7348
Refresh only the specified number of times (ticks) before exiting.  Each refresh
 
7349
is a pause for L<"interval"> seconds, followed by requesting data from MySQL
 
7350
connections and printing it to the terminal.
 
7351
 
 
7352
=item --delay
 
7353
 
 
7354
Specifies the amount of time to pause between ticks (refreshes).  Corresponds to
 
7355
the configuration option L<"interval">.
 
7356
 
 
7357
=item --mode
 
7358
 
 
7359
Specifies the mode in which innotop should start.  Corresponds to the
 
7360
configuration option L<"mode">.
 
7361
 
 
7362
=item --inc
 
7363
 
 
7364
Specifies whether innotop should display absolute numbers or relative numbers
 
7365
(offsets from their previous values).  Corresponds to the configuration option
 
7366
L<"status_inc">.
 
7367
 
 
7368
=item --version
 
7369
 
 
7370
Output version information and exit.
 
7371
 
 
7372
=back
 
7373
 
 
7374
=head1 HOTKEYS
 
7375
 
 
7376
innotop is interactive, and you control it with key-presses.
 
7377
 
 
7378
=over
 
7379
 
 
7380
=item *
 
7381
 
 
7382
Uppercase keys switch between modes.
 
7383
 
 
7384
=item *
 
7385
 
 
7386
Lowercase keys initiate some action within the current mode.
 
7387
 
 
7388
=item *
 
7389
 
 
7390
Other keys do something special like change configuration or show the
 
7391
innotop license.
 
7392
 
 
7393
=back
 
7394
 
 
7395
Press '?' at any time to see the currently active keys and what they do.
 
7396
 
 
7397
=head1 MODES
 
7398
 
 
7399
Each of innotop's modes retrieves and displays a particular type of data from
 
7400
the servers you're monitoring.  You switch between modes with uppercase keys.
 
7401
The following is a brief description of each mode, in alphabetical order.  To
 
7402
switch to the mode, press the key listed in front of its heading in the
 
7403
following list:
 
7404
 
 
7405
=over
 
7406
 
 
7407
=item B: InnoDB Buffers
 
7408
 
 
7409
This mode displays information about the InnoDB buffer pool, page statistics,
 
7410
insert buffer, and adaptive hash index.  The data comes from SHOW INNODB STATUS.
 
7411
 
 
7412
This mode contains the L<"buffer_pool">, L<"page_statistics">,
 
7413
L<"insert_buffers">, and L<"adaptive_hash_index"> tables by default.
 
7414
 
 
7415
=item C: Command Summary
 
7416
 
 
7417
This mode is similar to mytop's Command Summary mode.  It shows the
 
7418
L<"cmd_summary"> table, which looks something like the following:
 
7419
 
 
7420
 Command Summary (? for help) localhost, 25+07:16:43, 2.45 QPS, 3 thd, 5.0.40
 
7421
 _____________________ Command Summary _____________________
 
7422
 Name                    Value    Pct     Last Incr  Pct    
 
7423
 Select_scan             3244858  69.89%          2  100.00%
 
7424
 Select_range            1354177  29.17%          0    0.00%
 
7425
 Select_full_join          39479   0.85%          0    0.00%
 
7426
 Select_full_range_join     4097   0.09%          0    0.00%
 
7427
 Select_range_check            0   0.00%          0    0.00%
 
7428
 
 
7429
The command summary table is built by extracting variables from
 
7430
L<"STATUS_VARIABLES">.  The variables must be numeric and must match the prefix
 
7431
given by the L<"cmd_filter"> configuration variable.  The variables are then
 
7432
sorted by value descending and compared to the last variable, as shown above.
 
7433
The percentage columns are percentage of the total of all variables in the
 
7434
table, so you can see the relative weight of the variables.
 
7435
 
 
7436
The example shows what you see if the prefix is "Select_".  The default
 
7437
prefix is "Com_".  You can choose a prefix with the 's' key.
 
7438
 
 
7439
It's rather like running SHOW VARIABLES LIKE "prefix%" with memory and
 
7440
nice formatting.
 
7441
 
 
7442
Values are aggregated across all servers.  The Pct columns are not correctly
 
7443
aggregated across multiple servers.  This is a known limitation of the grouping
 
7444
algorithm that may be fixed in the future.
 
7445
 
 
7446
=item D: InnoDB Deadlocks
 
7447
 
 
7448
This mode shows the transactions involved in the last InnoDB deadlock.  A second
 
7449
table shows the locks each transaction held and waited for.  A deadlock is
 
7450
caused by a cycle in the waits-for graph, so there should be two locks held and
 
7451
one waited for unless the deadlock information is truncated.
 
7452
 
 
7453
InnoDB puts deadlock information before some other information in the SHOW
 
7454
INNODB STATUS output.  If there are a lot of locks, the deadlock information can
 
7455
grow very large, and there is a limit on the size of the SHOW INNODB
 
7456
STATUS output.  A large deadlock can fill the entire output, or even be
 
7457
truncated, and prevent you from seeing other information at all.  If you are
 
7458
running innotop in another mode, for example T mode, and suddenly you don't see
 
7459
anything, you might want to check and see if a deadlock has wiped out the data
 
7460
you need.
 
7461
 
 
7462
If it has, you can create a small deadlock to replace the large one.  Use the
 
7463
'w' key to 'wipe' the large deadlock with a small one.  This will not work
 
7464
unless you have defined a deadlock table for the connection (see L<"SERVER
 
7465
CONNECTIONS">).
 
7466
 
 
7467
You can also configure innotop to automatically detect when a large deadlock
 
7468
needs to be replaced with a small one (see L<"auto_wipe_dl">).
 
7469
 
 
7470
This mode displays the L<"deadlock_transactions"> and L<"deadlock_locks"> tables
 
7471
by default.
 
7472
 
 
7473
=item F: InnoDB Foreign Key Errors
 
7474
 
 
7475
This mode shows the last InnoDB foreign key error information, such as the
 
7476
table where it happened, when and who and what query caused it, and so on.
 
7477
 
 
7478
InnoDB has a huge variety of foreign key error messages, and many of them are
 
7479
just hard to parse.  innotop doesn't always do the best job here, but there's
 
7480
so much code devoted to parsing this messy, unparseable output that innotop is
 
7481
likely never to be perfect in this regard.  If innotop doesn't show you what
 
7482
you need to see, just look at the status text directly.
 
7483
 
 
7484
This mode displays the L<"fk_error"> table by default.
 
7485
 
 
7486
=item I: InnoDB I/O Info
 
7487
 
 
7488
This mode shows InnoDB's I/O statistics, including the I/O threads, pending I/O,
 
7489
file I/O miscellaneous, and log statistics.  It displays the L<"io_threads">,
 
7490
L<"pending_io">, L<"file_io_misc">, and L<"log_statistics"> tables by default.
 
7491
 
 
7492
=item L: Locks
 
7493
 
 
7494
This mode shows information about current locks.  At the moment only InnoDB
 
7495
locks are supported, and by default you'll only see locks for which transactions
 
7496
are waiting.  This information comes from the TRANSACTIONS section of the InnoDB
 
7497
status text.  If you have a very busy server, you may have frequent lock waits;
 
7498
it helps to be able to see which tables and indexes are the "hot spot" for
 
7499
locks.  If your server is running pretty well, this mode should show nothing.
 
7500
 
 
7501
You can configure MySQL and innotop to monitor not only locks for which a
 
7502
transaction is waiting, but those currently held, too.  You can do this with the
 
7503
InnoDB Lock Monitor (L<http://dev.mysql.com/doc/en/innodb-monitor.html>).  It's
 
7504
not documented in the MySQL manual, but creating the lock monitor with the
 
7505
following statement also affects the output of SHOW INNODB STATUS, which innotop
 
7506
uses:
 
7507
 
 
7508
  CREATE TABLE innodb_lock_monitor(a int) ENGINE=INNODB;
 
7509
 
 
7510
This causes InnoDB to print its output to the MySQL file every 16 seconds or so,
 
7511
as stated in the manual, but it also makes the normal SHOW INNODB STATUS output
 
7512
include lock information, which innotop can parse and display (that's the
 
7513
undocumented feature).
 
7514
 
 
7515
This means you can do what may have seemed impossible: to a limited extent
 
7516
(InnoDB truncates some information in the output), you can see which transaction
 
7517
holds the locks something else is waiting for.  You can also enable and disable
 
7518
the InnoDB Lock Monitor with the key mappings in this mode.
 
7519
 
 
7520
This mode displays the L<"innodb_locks"> table by default.  Here's a sample of
 
7521
the screen when one connection is waiting for locks another connection holds:
 
7522
 
 
7523
 _________________________________ InnoDB Locks __________________________
 
7524
 CXN        ID  Type    Waiting  Wait   Active  Mode  DB    Table  Index
 
7525
 localhost  12  RECORD        1  00:10   00:10  X     test  t1     PRIMARY
 
7526
 localhost  12  TABLE         0  00:10   00:10  IX    test  t1
 
7527
 localhost  12  RECORD        1  00:10   00:10  X     test  t1     PRIMARY
 
7528
 localhost  11  TABLE         0  00:00   00:25  IX    test  t1
 
7529
 localhost  11  RECORD        0  00:00   00:25  X     test  t1     PRIMARY
 
7530
 
 
7531
You can see the first connection, ID 12, is waiting for a lock on the PRIMARY
 
7532
key on test.t1, and has been waiting for 10 seconds.  The second connection
 
7533
isn't waiting, because the Waiting column is 0, but it holds locks on the same
 
7534
index.  That tells you connection 11 is blocking connection 12.
 
7535
 
 
7536
=item M: Master/Slave Replication Status
 
7537
 
 
7538
This mode shows the output of SHOW SLAVE STATUS and SHOW MASTER STATUS in three
 
7539
tables.  The first two divide the slave's status into SQL and I/O thread status,
 
7540
and the last shows master status.  Filters are applied to eliminate non-slave
 
7541
servers from the slave tables, and non-master servers from the master table.
 
7542
 
 
7543
This mode displays the L<"slave_sql_status">, L<"slave_io_status">, and
 
7544
L<"master_status"> tables by default.
 
7545
 
 
7546
=item O: Open Tables
 
7547
 
 
7548
This section comes from MySQL's SHOW OPEN TABLES command.  By default it is
 
7549
filtered to show tables which are in use by one or more queries, so you can
 
7550
get a quick look at which tables are 'hot'.  You can use this to guess which
 
7551
tables might be locked implicitly.
 
7552
 
 
7553
This mode displays the L<"open_tables"> mode by default.
 
7554
 
 
7555
=item Q: Query List
 
7556
 
 
7557
This mode displays the output from SHOW FULL PROCESSLIST, much like B<mytop>'s
 
7558
query list mode.  This mode does B<not> show InnoDB-related information.  This
 
7559
is probably one of the most useful modes for general usage.
 
7560
 
 
7561
There is an informative header that shows general status information about
 
7562
your server.  You can toggle it on and off with the 'h' key.  By default,
 
7563
innotop hides inactive processes and its own process.  You can toggle these on
 
7564
and off with the 'i' and 'a' keys.
 
7565
 
 
7566
You can EXPLAIN a query from this mode with the 'e' key.  This displays the
 
7567
query's full text, the results of EXPLAIN, and in newer MySQL versions, even
 
7568
the optimized query resulting from EXPLAIN EXTENDED.  innotop also tries to
 
7569
rewrite certain queries to make them EXPLAIN-able.  For example, INSERT/SELECT
 
7570
statements are rewritable.
 
7571
 
 
7572
This mode displays the L<"q_header"> and L<"processlist"> tables by default.
 
7573
 
 
7574
=item R: InnoDB Row Operations and Semaphores
 
7575
 
 
7576
This mode shows InnoDB row operations, row operation miscellaneous, semaphores,
 
7577
and information from the wait array.  It displays the L<"row_operations">,
 
7578
L<"row_operation_misc">, L<"semaphores">, and L<"wait_array"> tables by default.
 
7579
 
 
7580
=item S: Variables & Status
 
7581
 
 
7582
This mode calculates statistics, such as queries per second, and prints them out
 
7583
in several different styles.  You can show absolute values, or incremental values
 
7584
between ticks.
 
7585
 
 
7586
You can switch between the views by pressing a key.  The 's' key prints a
 
7587
single line each time the screen updates, in the style of B<vmstat>.  The 'g'
 
7588
key changes the view to a graph of the same numbers, sort of like B<tload>.
 
7589
The 'v' key changes the view to a pivoted table of variable names on the left,
 
7590
with successive updates scrolling across the screen from left to right.  You can
 
7591
choose how many updates to put on the screen with the L<"num_status_sets">
 
7592
configuration variable.
 
7593
 
 
7594
Headers may be abbreviated to fit on the screen in interactive operation.  You
 
7595
choose which variables to display with the 'c' key, which selects from
 
7596
predefined sets, or lets you create your own sets.  You can edit the current set
 
7597
with the 'e' key.
 
7598
 
 
7599
This mode doesn't really display any tables like other modes.  Instead, it uses
 
7600
a table definition to extract and format the data, but it then transforms the
 
7601
result in special ways before outputting it.  It uses the L<"var_status"> table
 
7602
definition for this.
 
7603
 
 
7604
=item T: InnoDB Transactions
 
7605
 
 
7606
This mode shows transactions from the InnoDB monitor's output, in B<top>-like
 
7607
format.  This mode is the reason I wrote innotop.
 
7608
 
 
7609
You can kill queries or processes with the 'k' and 'x' keys, and EXPLAIN a query
 
7610
with the 'e' or 'f' keys.  InnoDB doesn't print the full query in transactions,
 
7611
so explaining may not work right if the query is truncated.
 
7612
 
 
7613
The informational header can be toggled on and off with the 'h' key.  By
 
7614
default, innotop hides inactive transactions and its own transaction.  You can
 
7615
toggle this on and off with the 'i' and 'a' keys.
 
7616
 
 
7617
This mode displays the L<"t_header"> and L<"innodb_transactions"> tables by
 
7618
default.
 
7619
 
 
7620
=back
 
7621
 
 
7622
=head1 INNOTOP STATUS
 
7623
 
 
7624
The first line innotop displays is a "status bar" of sorts.  What it contains
 
7625
depends on the mode you're in, and what servers you're monitoring.  The first
 
7626
few words are always the innotop mode, such as "InnoDB Txns" for T mode,
 
7627
followed by a reminder to press '?' for help at any time.
 
7628
 
 
7629
=head2 ONE SERVER
 
7630
 
 
7631
The simplest case is when you're monitoring a single server.  In this case, the
 
7632
name of the connection is next on the status line.  This is the name you gave
 
7633
when you created the connection -- most likely the MySQL server's hostname.
 
7634
This is followed by the server's uptime.
 
7635
 
 
7636
If you're in an InnoDB mode, such as T or B, the next word is "InnoDB" followed
 
7637
by some information about the SHOW INNODB STATUS output used to render the
 
7638
screen.  The first word is the number of seconds since the last SHOW INNODB
 
7639
STATUS, which InnoDB uses to calculate some per-second statistics.  The next is
 
7640
a smiley face indicating whether the InnoDB output is truncated.  If the smiley
 
7641
face is a :-), all is well; there is no truncation.  A :^| means the transaction
 
7642
list is so long, InnoDB has only printed out some of the transactions.  Finally,
 
7643
a frown :-( means the output is incomplete, which is probably due to a deadlock
 
7644
printing too much lock information (see L<"D: InnoDB Deadlocks">).
 
7645
 
 
7646
The next two words indicate the server's queries per second (QPS) and how many
 
7647
threads (connections) exist.  Finally, the server's version number is the last
 
7648
thing on the line.
 
7649
 
 
7650
=head2 MULTIPLE SERVERS
 
7651
 
 
7652
If you are monitoring multiple servers (see L<"SERVER CONNECTIONS">), the status
 
7653
line does not show any details about individual servers.  Instead, it shows the
 
7654
names of the connections that are active.  Again, these are connection names you
 
7655
specified, which are likely to be the server's hostname.  A connection that has
 
7656
an error is prefixed with an exclamation point.
 
7657
 
 
7658
If you are monitoring a group of servers (see L<"SERVER GROUPS">), the status
 
7659
line shows the name of the group.  If any connection in the group has an
 
7660
error, the group's name is followed by the fraction of the connections that
 
7661
don't have errors.
 
7662
 
 
7663
See L<"ERROR HANDLING"> for more details about innotop's error handling.
 
7664
 
 
7665
=head2 MONITORING A FILE
 
7666
 
 
7667
If you give a filename on the command line, innotop will not connect to ANY
 
7668
servers at all.  It will watch the specified file for InnoDB status output and
 
7669
use that as its data source.  It will always show a single connection called
 
7670
'file'.  And since it can't connect to a server, it can't determine how long the
 
7671
server it's monitoring has been up; so it calculates the server's uptime as time
 
7672
since innotop started running.
 
7673
 
 
7674
=head1 SERVER ADMINISTRATION
 
7675
 
 
7676
While innotop is primarily a monitor that lets you watch and analyze your
 
7677
servers, it can also send commands to servers.  The most frequently useful
 
7678
commands are killing queries and stopping or starting slaves.
 
7679
 
 
7680
You can kill a connection, or in newer versions of MySQL kill a query but not a
 
7681
connection, from L<"Q: Query List"> and L<"T: InnoDB Transactions"> modes.
 
7682
Press 'k' to issue a KILL command, or 'x' to issue a KILL QUERY command.
 
7683
innotop will prompt you for the server and/or connection ID to kill (innotop
 
7684
does not prompt you if there is only one possible choice for any input).
 
7685
innotop pre-selects the longest-running query, or the oldest connection.
 
7686
Confirm the command with 'y'.
 
7687
 
 
7688
In L<"M: Master/Slave Replication Status"> mode, you can start and stop slaves
 
7689
with the 'a' and 'o' keys, respectively.  You can send these commands to many
 
7690
slaves at once.  innotop fills in a default command of START SLAVE or STOP SLAVE
 
7691
for you, but you can actually edit the command and send anything you wish, such
 
7692
as SET GLOBAL SQL_SLAVE_SKIP_COUNTER=1 to make the slave skip one binlog event
 
7693
when it starts.
 
7694
 
 
7695
You can also ask innotop to calculate the earliest binlog in use by any slave
 
7696
and issue a PURGE MASTER LOGS on the master.  Use the 'b' key for this.  innotop
 
7697
will prompt you for a master to run the command on, then prompt you for the
 
7698
connection names of that master's slaves (there is no way for innotop to
 
7699
determine this reliably itself).  innotop will find the minimum binlog in use by
 
7700
these slave connections and suggest it as the argument to PURGE MASTER LOGS.
 
7701
 
 
7702
=head1 SERVER CONNECTIONS
 
7703
 
 
7704
When you create a server connection, innotop asks you for a series of inputs, as
 
7705
follows:
 
7706
 
 
7707
=over
 
7708
 
 
7709
=item DSN
 
7710
 
 
7711
A DSN is a Data Source Name, which is the initial argument passed to the DBI
 
7712
module for connecting to a server.  It is usually of the form
 
7713
 
 
7714
 DBI:mysql:;mysql_read_default_group=mysql;host=HOSTNAME
 
7715
 
 
7716
Since this DSN is passed to the DBD::mysql driver, you should read the driver's
 
7717
documentation at L<"http://search.cpan.org/dist/DBD-mysql/lib/DBD/mysql.pm"> for
 
7718
the exact details on all the options you can pass the driver in the DSN.  You
 
7719
can read more about DBI at L<http://dbi.perl.org/docs/>, and especially at
 
7720
L<http://search.cpan.org/~timb/DBI/DBI.pm>.
 
7721
 
 
7722
The mysql_read_default_group=mysql option lets the DBD driver read your MySQL
 
7723
options files, such as ~/.my.cnf on UNIX-ish systems.  You can use this to avoid
 
7724
specifying a username or password for the connection.
 
7725
 
 
7726
=item InnoDB Deadlock Table
 
7727
 
 
7728
This optional item tells innotop a table name it can use to deliberately create
 
7729
a small deadlock (see L<"D: InnoDB Deadlocks">).  If you specify this option,
 
7730
you just need to be sure the table doesn't exist, and that innotop can create
 
7731
and drop the table with the InnoDB storage engine.  You can safely omit or just
 
7732
accept the default if you don't intend to use this.
 
7733
 
 
7734
=item Username
 
7735
 
 
7736
innotop will ask you if you want to specify a username.  If you say 'y', it will
 
7737
then prompt you for a user name.  If you have a MySQL option file that specifies
 
7738
your username, you don't have to specify a username.
 
7739
 
 
7740
The username defaults to your login name on the system you're running innotop on.
 
7741
 
 
7742
=item Password
 
7743
 
 
7744
innotop will ask you if you want to specify a password.  Like the username, the
 
7745
password is optional, but there's an additional prompt that asks if you want to
 
7746
save the password in the innotop configuration file.  If you don't save it in
 
7747
the configuration file, innotop will prompt you for a password each time it
 
7748
starts.  Passwords in the innotop configuration file are saved in plain text,
 
7749
not encrypted in any way.
 
7750
 
 
7751
=back
 
7752
 
 
7753
Once you finish answering these questions, you should be connected to a server.
 
7754
But innotop isn't limited to monitoring a single server; you can define many
 
7755
server connections and switch between them by pressing the '@' key.  See
 
7756
L<"SWITCHING BETWEEN CONNECTIONS">.
 
7757
 
 
7758
To create a new connection, press the '@' key and type the name of the new
 
7759
connection, then follow the steps given above.
 
7760
 
 
7761
=head1 SERVER GROUPS
 
7762
 
 
7763
If you have multiple MySQL instances, you can put them into named groups, such
 
7764
as 'all', 'masters', and 'slaves', which innotop can monitor all together.
 
7765
 
 
7766
You can choose which group to monitor with the '#' key, and you can press the
 
7767
TAB key to switch to the next group.  If you're not currently monitoring a
 
7768
group, pressing TAB selects the first group.
 
7769
 
 
7770
To create a group, press the '#' key and type the name of your new group, then
 
7771
type the names of the connections you want the group to contain.
 
7772
 
 
7773
=head1 SWITCHING BETWEEN CONNECTIONS
 
7774
 
 
7775
innotop lets you quickly switch which servers you're monitoring.  The most basic
 
7776
way is by pressing the '@' key and typing the name(s) of the connection(s) you
 
7777
want to use.  This setting is per-mode, so you can monitor different connections
 
7778
in each mode, and innotop remembers which connections you choose.
 
7779
 
 
7780
You can quickly switch to the 'next' connection in alphabetical order with the
 
7781
'n' key.  If you're monitoring a server group (see L<"SERVER GROUPS">) this will
 
7782
switch to the first connection.
 
7783
 
 
7784
You can also type many connection names, and innotop will fetch and display data
 
7785
from them all.  Just separate the connection names with spaces, for example
 
7786
"server1 server2."  Again, if you type the name of a connection that doesn't
 
7787
exist, innotop will prompt you for connection information and create the
 
7788
connection.
 
7789
 
 
7790
Another way to monitor multiple connections at once is with server groups.  You
 
7791
can use the TAB key to switch to the 'next' group in alphabetical order, or if
 
7792
you're not monitoring any groups, TAB will switch to the first group.
 
7793
 
 
7794
innotop does not fetch data in parallel from connections, so if you are
 
7795
monitoring a large group or many connections, you may notice increased delay
 
7796
between ticks.
 
7797
 
 
7798
When you monitor more than one connection, innotop's status bar changes.  See
 
7799
L<"INNOTOP STATUS">.
 
7800
 
 
7801
=head1 ERROR HANDLING
 
7802
 
 
7803
Error handling is not that important when monitoring a single connection, but is
 
7804
crucial when you have many active connections.  A crashed server or lost
 
7805
connection should not crash innotop.  As a result, innotop will continue to run
 
7806
even when there is an error; it just won't display any information from the
 
7807
connection that had an error.  Because of this, innotop's behavior might confuse
 
7808
you.  It's a feature, not a bug!
 
7809
 
 
7810
innotop does not continue to query connections that have errors, because they
 
7811
may slow innotop and make it hard to use, especially if the error is a problem
 
7812
connecting and causes a long time-out.  Instead, innotop retries the connection
 
7813
occasionally to see if the error still exists.  If so, it will wait until some
 
7814
point in the future.  The wait time increases in ticks as the Fibonacci series,
 
7815
so it tries less frequently as time passes.
 
7816
 
 
7817
Since errors might only happen in certain modes because of the SQL commands
 
7818
issued in those modes, innotop keeps track of which mode caused the error.  If
 
7819
you switch to a different mode, innotop will retry the connection instead of
 
7820
waiting.
 
7821
 
 
7822
By default innotop will display the problem in red text at the bottom of the
 
7823
first table on the screen.  You can disable this behavior with the
 
7824
L<"show_cxn_errors_in_tbl"> configuration option, which is enabled by default.
 
7825
If the L<"debug"> option is enabled, innotop will display the error at the
 
7826
bottom of every table, not just the first.  And if L<"show_cxn_errors"> is
 
7827
enabled, innotop will print the error text to STDOUT as well.  Error messages
 
7828
might only display in the mode that caused the error, depending on the mode and
 
7829
whether innotop is avoiding querying that connection.
 
7830
 
 
7831
=head1 NON-INTERACTIVE OPERATION
 
7832
 
 
7833
You can run innotop in non-interactive mode, in which case it is entirely
 
7834
controlled from the configuration file and command-line options.  To start
 
7835
innotop in non-interactive mode, give the L"<--nonint"> command-line option.
 
7836
This changes innotop's behavior in the following ways:
 
7837
 
 
7838
=over
 
7839
 
 
7840
=item *
 
7841
 
 
7842
Certain Perl modules are not loaded.  Term::Readline is not loaded, since
 
7843
innotop doesn't prompt interactively.  Term::ANSIColor and Win32::Console::ANSI
 
7844
modules are not loaded.  Term::ReadKey is still used, since innotop may have to
 
7845
prompt for connection passwords when starting up.
 
7846
 
 
7847
=item *
 
7848
 
 
7849
innotop does not clear the screen after each tick.
 
7850
 
 
7851
=item *
 
7852
 
 
7853
innotop does not persist any changes to the configuration file.
 
7854
 
 
7855
=item *
 
7856
 
 
7857
If L<"--count"> is given and innotop is in incremental mode (see L<"status_inc">
 
7858
and L<"--inc">), innotop actually refreshes one more time than specified so it
 
7859
can print incremental statistics.  This suppresses output during the first
 
7860
tick, so innotop may appear to hang.
 
7861
 
 
7862
=item *
 
7863
 
 
7864
innotop only displays the first table in each mode.  This is so the output can
 
7865
be easily processed with other command-line utilities such as awk and sed.  To
 
7866
change which tables display in each mode, see L<"TABLES">.  Since L<"Q: Query
 
7867
List"> mode is so important, innotop automatically disables the L<"q_header">
 
7868
table.  This ensures you'll see the L<"processlist"> table, even if you have
 
7869
innotop configured to show the q_header table during interactive operation.
 
7870
Similarly, in L<"T: InnoDB Transactions"> mode, the L<"t_header"> table is
 
7871
suppressed so you see only the L<"innodb_transactions"> table.
 
7872
 
 
7873
=item *
 
7874
 
 
7875
All output is tab-separated instead of being column-aligned with whitespace, and
 
7876
innotop prints the full contents of each table instead of only printing one
 
7877
screenful at a time.
 
7878
 
 
7879
=item *
 
7880
 
 
7881
innotop only prints column headers once instead of every tick (see
 
7882
L<"hide_hdr">).  innotop does not print table captions (see
 
7883
L<"display_table_captions">).  innotop ensures there are no empty lines in the
 
7884
output.
 
7885
 
 
7886
=item *
 
7887
 
 
7888
innotop does not honor the L<"shorten"> transformation, which normally shortens
 
7889
some numbers to human-readable formats.
 
7890
 
 
7891
=item *
 
7892
 
 
7893
innotop does not print a status line (see L<"INNOTOP STATUS">).
 
7894
 
 
7895
=back
 
7896
 
 
7897
=head1 CONFIGURING
 
7898
 
 
7899
Nearly everything about innotop is configurable.  Most things are possible to
 
7900
change with built-in commands, but you can also edit the configuration file.
 
7901
 
 
7902
While running innotop, press the '$' key to bring up the configuration editing
 
7903
dialog.  Press another key to select the type of data you want to edit:
 
7904
 
 
7905
=over
 
7906
 
 
7907
=item S: Statement Sleep Times
 
7908
 
 
7909
Edits SQL statement sleep delays, which make innotop pause for the specified
 
7910
amount of time after executing a statement.  See L<"SQL STATEMENTS"> for a
 
7911
definition of each statement and what it does.  By default innotop does not
 
7912
delay after any statements.
 
7913
 
 
7914
This feature is included so you can customize the side-effects caused by
 
7915
monitoring your server.  You may not see any effects, but some innotop users
 
7916
have noticed that certain MySQL versions under very high load with InnoDB
 
7917
enabled take longer than usual to execute SHOW GLOBAL STATUS.  If innotop calls
 
7918
SHOW FULL PROCESSLIST immediately afterward, the processlist contains more
 
7919
queries than the machine actually averages at any given moment.  Configuring
 
7920
innotop to pause briefly after calling SHOW GLOBAL STATUS alleviates this
 
7921
effect.
 
7922
 
 
7923
Sleep times are stored in the L<"stmt_sleep_times"> section of the configuration
 
7924
file.  Fractional-second sleeps are supported, subject to your hardware's
 
7925
limitations.
 
7926
 
 
7927
=item c: Edit Columns
 
7928
 
 
7929
Starts the table editor on one of the displayed tables.  See L<"TABLE EDITOR">.
 
7930
An alternative way to start the table editor without entering the configuration
 
7931
dialog is with the '^' key.
 
7932
 
 
7933
=item g: General Configuration
 
7934
 
 
7935
Starts the configuration editor to edit global and mode-specific configuration
 
7936
variables (see L<"MODES">).  innotop prompts you to choose a variable from among
 
7937
the global and mode-specific ones depending on the current mode.
 
7938
 
 
7939
=item k: Row-Coloring Rules
 
7940
 
 
7941
Starts the row-coloring rules editor on one of the displayed table(s).  See
 
7942
L<"COLORS"> for details.
 
7943
 
 
7944
=item p: Manage Plugins
 
7945
 
 
7946
Starts the plugin configuration editor.  See L<"PLUGINS"> for details.
 
7947
 
 
7948
=item s: Server Groups
 
7949
 
 
7950
Lets you create and edit server groups.  See L<"SERVER GROUPS">.
 
7951
 
 
7952
=item t: Choose Displayed Tables
 
7953
 
 
7954
Lets you choose which tables to display in this mode.  See L<"MODES"> and
 
7955
L<"TABLES">.
 
7956
 
 
7957
=back
 
7958
 
 
7959
=head1 CONFIGURATION FILE
 
7960
 
 
7961
innotop's default configuration file location is in $HOME/.innotop, but can be
 
7962
overridden with the L<"--config"> command-line option.  You can edit it by hand
 
7963
safely.  innotop reads the configuration file when it starts, and writes it out
 
7964
again when it exits, so any changes you make while innotop is running will be
 
7965
lost.
 
7966
 
 
7967
innotop doesn't store its entire configuration in the configuration file.  It
 
7968
has a huge set of default configuration that it holds only in memory, and the
 
7969
configuration file only overrides these defaults.  When you customize a default
 
7970
setting, innotop notices, and then stores the customizations into the file.
 
7971
This keeps the file size down, makes it easier to edit, and makes upgrades
 
7972
easier.
 
7973
 
 
7974
A configuration file can be made read-only.  See L<"readonly">.
 
7975
 
 
7976
The configuration file is arranged into sections like an INI file.  Each
 
7977
section begins with [section-name] and ends with [/section-name].  Each
 
7978
section's entries have a different syntax depending on the data they need to
 
7979
store.  You can put comments in the file; any line that begins with a #
 
7980
character is a comment.  innotop will not read the comments, so it won't write
 
7981
them back out to the file when it exits.  Comments in read-only configuration
 
7982
files are still useful, though.
 
7983
 
 
7984
The first line in the file is innotop's version number.  This lets innotop
 
7985
notice when the file format is not backwards-compatible, and upgrade smoothly
 
7986
without destroying your customized configuration.
 
7987
 
 
7988
The following list describes each section of the configuration file and the data
 
7989
it contains:
 
7990
 
 
7991
=over
 
7992
 
 
7993
=item general
 
7994
 
 
7995
The 'general' section contains global configuration variables and variables that
 
7996
may be mode-specific, but don't belong in any other section.  The syntax is a
 
7997
simple key=value list.  innotop writes a comment above each value to help you
 
7998
edit the file by hand.
 
7999
 
 
8000
=over
 
8001
 
 
8002
=item S_func
 
8003
 
 
8004
Controls S mode presentation (see L<"S: Variables & Status">).  If g, values are
 
8005
graphed; if s, values are like vmstat; if p, values are in a pivoted table.
 
8006
 
 
8007
=item S_set
 
8008
 
 
8009
Specifies which set of variables to display in L<"S: Variables & Status"> mode.
 
8010
See L<"VARIABLE SETS">.
 
8011
 
 
8012
=item auto_wipe_dl
 
8013
 
 
8014
Instructs innotop to automatically wipe large deadlocks when it notices them.
 
8015
When this happens you may notice a slight delay.  At the next tick, you will
 
8016
usually see the information that was being truncated by the large deadlock.
 
8017
 
 
8018
=item charset
 
8019
 
 
8020
Specifies what kind of characters to allow through the L<"no_ctrl_char">
 
8021
transformation.  This keeps non-printable characters from confusing a
 
8022
terminal when you monitor queries that contain binary data, such as images.
 
8023
 
 
8024
The default is 'ascii', which considers anything outside normal ASCII to be a
 
8025
control character.  The other allowable values are 'unicode' and 'none'.  'none'
 
8026
considers every character a control character, which can be useful for
 
8027
collapsing ALL text fields in queries.
 
8028
 
 
8029
=item cmd_filter
 
8030
 
 
8031
This is the prefix that filters variables in L<"C: Command Summary"> mode.
 
8032
 
 
8033
=item color
 
8034
 
 
8035
Whether terminal coloring is permitted.
 
8036
 
 
8037
=item cxn_timeout
 
8038
 
 
8039
On MySQL versions 4.0.3 and newer, this variable is used to set the connection's
 
8040
timeout, so MySQL doesn't close the connection if it is not used for a while.
 
8041
This might happen because a connection isn't monitored in a particular mode, for
 
8042
example.
 
8043
 
 
8044
=item debug
 
8045
 
 
8046
This option enables more verbose errors and makes innotop more strict in some
 
8047
places.  It can help in debugging filters and other user-defined code.  It also
 
8048
makes innotop write a lot of information to L<"debugfile"> when there is a
 
8049
crash.
 
8050
 
 
8051
=item debugfile
 
8052
 
 
8053
A file to which innotop will write information when there is a crash.  See
 
8054
L<"FILES">.
 
8055
 
 
8056
=item display_table_captions
 
8057
 
 
8058
innotop displays a table caption above most tables.  This variable suppresses or
 
8059
shows captions on all tables globally.  Some tables are configured with the
 
8060
hide_caption property, which overrides this.
 
8061
 
 
8062
=item global
 
8063
 
 
8064
Whether to show GLOBAL variables and status.  innotop only tries to do this on
 
8065
servers which support the GLOBAL option to SHOW VARIABLES and SHOW STATUS.  In
 
8066
some MySQL versions, you need certain privileges to do this; if you don't have
 
8067
them, innotop will not be able to fetch any variable and status data.  This
 
8068
configuration variable lets you run innotop and fetch what data you can even
 
8069
without the elevated privileges.
 
8070
 
 
8071
I can no longer find or reproduce the situation where GLOBAL wasn't allowed, but
 
8072
I know there was one.
 
8073
 
 
8074
=item graph_char
 
8075
 
 
8076
Defines the character to use when drawing graphs in L<"S: Variables & Status">
 
8077
mode.
 
8078
 
 
8079
=item header_highlight
 
8080
 
 
8081
Defines how to highlight column headers.  This only works if Term::ANSIColor is
 
8082
available.  Valid values are 'bold' and 'underline'.
 
8083
 
 
8084
=item hide_hdr
 
8085
 
 
8086
Hides column headers globally.
 
8087
 
 
8088
=item interval
 
8089
 
 
8090
The interval at which innotop will refresh its data (ticks).  The interval is
 
8091
implemented as a sleep time between ticks, so the true interval will vary
 
8092
depending on how long it takes innotop to fetch and render data.
 
8093
 
 
8094
This variable accepts fractions of a second.
 
8095
 
 
8096
=item mode
 
8097
 
 
8098
The mode in which innotop should start.  Allowable arguments are the same as the
 
8099
key presses that select a mode interactively.  See L<"MODES">.
 
8100
 
 
8101
=item num_digits
 
8102
 
 
8103
How many digits to show in fractional numbers and percents.  This variable's
 
8104
range is between 0 and 9 and can be set directly from L<"S: Variables & Status">
 
8105
mode with the '+' and '-' keys.  It is used in the L<"set_precision">,
 
8106
L<"shorten">, and L<"percent"> transformations.
 
8107
 
 
8108
=item num_status_sets
 
8109
 
 
8110
Controls how many sets of status variables to display in pivoted L<"S: Variables
 
8111
& Status"> mode.  It also controls the number of old sets of variables innotop
 
8112
keeps in its memory, so the larger this variable is, the more memory innotop
 
8113
uses.
 
8114
 
 
8115
=item plugin_dir
 
8116
 
 
8117
Specifies where plugins can be found.  By default, innotop stores plugins in the
 
8118
'plugins' subdirectory of your innotop configuration directory.
 
8119
 
 
8120
=item readonly
 
8121
 
 
8122
Whether the configuration file is readonly.  This cannot be set interactively,
 
8123
because it would prevent itself from being written to the configuration file.
 
8124
 
 
8125
=item show_cxn_errors
 
8126
 
 
8127
Makes innotop print connection errors to STDOUT.  See L<"ERROR HANDLING">.
 
8128
 
 
8129
=item show_cxn_errors_in_tbl
 
8130
 
 
8131
Makes innotop display connection errors as rows in the first table on screen.
 
8132
See L<"ERROR HANDLING">.
 
8133
 
 
8134
=item show_percent
 
8135
 
 
8136
Adds a '%' character after the value returned by the L<"percent">
 
8137
transformation.
 
8138
 
 
8139
=item show_statusbar
 
8140
 
 
8141
Controls whether to show the status bar in the display.  See L<"INNOTOP
 
8142
STATUS">.
 
8143
 
 
8144
=item skip_innodb
 
8145
 
 
8146
Disables fetching SHOW INNODB STATUS, in case your server(s) do not have InnoDB
 
8147
enabled and you don't want innotop to try to fetch it.  This can also be useful
 
8148
when you don't have the SUPER privilege, required to run SHOW INNODB STATUS.
 
8149
 
 
8150
=item status_inc
 
8151
 
 
8152
Whether to show absolute or incremental values for status variables.
 
8153
Incremental values are calculated as an offset from the last value innotop saw
 
8154
for that variable.  This is a global setting, but will probably become
 
8155
mode-specific at some point.  Right now it is honored a bit inconsistently; some
 
8156
modes don't pay attention to it.
 
8157
 
 
8158
=back
 
8159
 
 
8160
=item plugins
 
8161
 
 
8162
This section holds a list of package names of active plugins.  If the plugin
 
8163
exists, innotop will activate it.  See L<"PLUGINS"> for more information.
 
8164
 
 
8165
=item filters
 
8166
 
 
8167
This section holds user-defined filters (see L<"FILTERS">).  Each line is in the
 
8168
format filter_name=text='filter text' tbls='table list'.
 
8169
 
 
8170
The filter text is the text of the subroutine's code.  The table list is a list
 
8171
of tables to which the filter can apply.  By default, user-defined filters apply
 
8172
to the table for which they were created, but you can manually override that by
 
8173
editing the definition in the configuration file.
 
8174
 
 
8175
=item active_filters
 
8176
 
 
8177
This section stores which filters are active on each table.  Each line is in the
 
8178
format table_name=filter_list.
 
8179
 
 
8180
=item tbl_meta
 
8181
 
 
8182
This section stores user-defined or user-customized columns (see L<"COLUMNS">).
 
8183
Each line is in the format col_name=properties, where the properties are a
 
8184
name=quoted-value list.
 
8185
 
 
8186
=item connections
 
8187
 
 
8188
This section holds the server connections you have defined.  Each line is in the
 
8189
format name=properties, where the properties are a name=value list.  The
 
8190
properties are self-explanatory, and the only one that is treated specially is
 
8191
'pass' which is only present if 'savepass' is set.  See L<"SERVER CONNECTIONS">.
 
8192
 
 
8193
=item active_connections
 
8194
 
 
8195
This section holds a list of which connections are active in each mode.  Each
 
8196
line is in the format mode_name=connection_list.
 
8197
 
 
8198
=item server_groups
 
8199
 
 
8200
This section holds server groups.  Each line is in the format
 
8201
name=connection_list.  See L<"SERVER GROUPS">.
 
8202
 
 
8203
=item active_server_groups
 
8204
 
 
8205
This section holds a list of which server group is active in each mode.  Each
 
8206
line is in the format mode_name=server_group.
 
8207
 
 
8208
=item max_values_seen
 
8209
 
 
8210
This section holds the maximum values seen for variables.  This is used to scale
 
8211
the graphs in L<"S: Variables & Status"> mode.  Each line is in the format
 
8212
name=value.
 
8213
 
 
8214
=item active_columns
 
8215
 
 
8216
This section holds table column lists.  Each line is in the format
 
8217
tbl_name=column_list.  See L<"COLUMNS">.
 
8218
 
 
8219
=item sort_cols
 
8220
 
 
8221
This section holds the sort definition.  Each line is in the format
 
8222
tbl_name=column_list.  If a column is prefixed with '-', that column sorts
 
8223
descending.  See L<"SORTING">.
 
8224
 
 
8225
=item visible_tables
 
8226
 
 
8227
This section defines which tables are visible in each mode.  Each line is in the
 
8228
format mode_name=table_list.  See L<"TABLES">.
 
8229
 
 
8230
=item varsets
 
8231
 
 
8232
This section defines variable sets for use in L<"S: Status & Variables"> mode.
 
8233
Each line is in the format name=variable_list.  See L<"VARIABLE SETS">.
 
8234
 
 
8235
=item colors
 
8236
 
 
8237
This section defines colorization rules.  Each line is in the format
 
8238
tbl_name=property_list.  See L<"COLORS">.
 
8239
 
 
8240
=item stmt_sleep_times
 
8241
 
 
8242
This section contains statement sleep times.  Each line is in the format
 
8243
statement_name=sleep_time.  See L<"S: Statement Sleep Times">.
 
8244
 
 
8245
=item group_by
 
8246
 
 
8247
This section contains column lists for table group_by expressions.  Each line is
 
8248
in the format tbl_name=column_list.  See L<"GROUPING">.
 
8249
 
 
8250
=back
 
8251
 
 
8252
=head1 CUSTOMIZING
 
8253
 
 
8254
You can customize innotop a great deal.  For example, you can:
 
8255
 
 
8256
=over
 
8257
 
 
8258
=item *
 
8259
 
 
8260
Choose which tables to display, and in what order.
 
8261
 
 
8262
=item *
 
8263
 
 
8264
Choose which columns are in those tables, and create new columns.
 
8265
 
 
8266
=item *
 
8267
 
 
8268
Filter which rows display with built-in filters, user-defined filters, and
 
8269
quick-filters.
 
8270
 
 
8271
=item *
 
8272
 
 
8273
Sort the rows to put important data first or group together related rows.
 
8274
 
 
8275
=item *
 
8276
 
 
8277
Highlight rows with color.
 
8278
 
 
8279
=item *
 
8280
 
 
8281
Customize the alignment, width, and formatting of columns, and apply
 
8282
transformations to columns to extract parts of their values or format the values
 
8283
as you wish (for example, shortening large numbers to familiar units).
 
8284
 
 
8285
=item *
 
8286
 
 
8287
Design your own expressions to extract and combine data as you need.  This gives
 
8288
you unlimited flexibility.
 
8289
 
 
8290
=back
 
8291
 
 
8292
All these and more are explained in the following sections.
 
8293
 
 
8294
=head2 TABLES
 
8295
 
 
8296
A table is what you'd expect: a collection of columns.  It also has some other
 
8297
properties, such as a caption.  Filters, sorting rules, and colorization rules
 
8298
belong to tables and are covered in later sections.
 
8299
 
 
8300
Internally, table meta-data is defined in a data structure called %tbl_meta.
 
8301
This hash holds all built-in table definitions, which contain a lot of default
 
8302
instructions to innotop.  The meta-data includes the caption, a list of columns
 
8303
the user has customized, a list of columns, a list of visible columns, a list of
 
8304
filters, color rules, a sort-column list, sort direction, and some information
 
8305
about the table's data sources.  Most of this is customizable via the table
 
8306
editor (see L<"TABLE EDITOR">).
 
8307
 
 
8308
You can choose which tables to show by pressing the '$' key.  See L<"MODES"> and
 
8309
L<"TABLES">.
 
8310
 
 
8311
The table life-cycle is as follows:
 
8312
 
 
8313
=over
 
8314
 
 
8315
=item *
 
8316
 
 
8317
Each table begins with a data source, which is an array of hashes.  See below
 
8318
for details on data sources.
 
8319
 
 
8320
=item *
 
8321
 
 
8322
Each element of the data source becomes a row in the final table.
 
8323
 
 
8324
=item *
 
8325
 
 
8326
For each element in the data source, innotop extracts values from the source and
 
8327
creates a row.  This row is another hash, which later steps will refer to as
 
8328
$set.  The values innotop extracts are determined by the table's columns.  Each
 
8329
column has an extraction subroutine, compiled from an expression (see
 
8330
L<"EXPRESSIONS">).  The resulting row is a hash whose keys are named the same as
 
8331
the column name.
 
8332
 
 
8333
=item *
 
8334
 
 
8335
innotop filters the rows, removing those that don't need to be displayed.  See
 
8336
L<"FILTERS">.
 
8337
 
 
8338
=item *
 
8339
 
 
8340
innotop sorts the rows.  See L<"SORTING">.
 
8341
 
 
8342
=item *
 
8343
 
 
8344
innotop groups the rows together, if specified.  See L<"GROUPING">.
 
8345
 
 
8346
=item *
 
8347
 
 
8348
innotop colorizes the rows.  See L<"COLORS">.
 
8349
 
 
8350
=item *
 
8351
 
 
8352
innotop transforms the column values in each row.  See L<"TRANSFORMATIONS">.
 
8353
 
 
8354
=item *
 
8355
 
 
8356
innotop optionally pivots the rows (see L<"PIVOTING">), then filters and sorts
 
8357
them.
 
8358
 
 
8359
=item *
 
8360
 
 
8361
innotop formats and justifies the rows as a table.  During this step, innotop
 
8362
applies further formatting to the column values, including alignment, maximum
 
8363
and minimum widths.  innotop also does final error checking to ensure there are
 
8364
no crashes due to undefined values.  innotop then adds a caption if specified,
 
8365
and the table is ready to print.
 
8366
 
 
8367
=back
 
8368
 
 
8369
The lifecycle is slightly different if the table is pivoted, as noted above.  To
 
8370
clarify, if the table is pivoted, the process is extract, group, transform,
 
8371
pivot, filter, sort, create.  If it's not pivoted, the process is extract,
 
8372
filter, sort, group, color, transform, create.  This slightly convoluted process
 
8373
doesn't map all that well to SQL, but pivoting complicates things pretty
 
8374
thoroughly.  Roughly speaking, filtering and sorting happen as late as needed to
 
8375
effect the final result as you might expect, but as early as possible for
 
8376
efficiency.
 
8377
 
 
8378
Each built-in table is described below:
 
8379
 
 
8380
=over
 
8381
 
 
8382
=item adaptive_hash_index
 
8383
 
 
8384
Displays data about InnoDB's adaptive hash index.  Data source:
 
8385
L<"STATUS_VARIABLES">.
 
8386
 
 
8387
=item buffer_pool
 
8388
 
 
8389
Displays data about InnoDB's buffer pool.  Data source: L<"STATUS_VARIABLES">.
 
8390
 
 
8391
=item cmd_summary
 
8392
 
 
8393
Displays weighted status variables.  Data source: L<"STATUS_VARIABLES">.
 
8394
 
 
8395
=item deadlock_locks
 
8396
 
 
8397
Shows which locks were held and waited for by the last detected deadlock.  Data
 
8398
source: L<"DEADLOCK_LOCKS">.
 
8399
 
 
8400
=item deadlock_transactions
 
8401
 
 
8402
Shows transactions involved in the last detected deadlock.  Data source:
 
8403
L<"DEADLOCK_TRANSACTIONS">.
 
8404
 
 
8405
=item explain
 
8406
 
 
8407
Shows the output of EXPLAIN.  Data source: L<"EXPLAIN">.
 
8408
 
 
8409
=item file_io_misc
 
8410
 
 
8411
Displays data about InnoDB's file and I/O operations.  Data source:
 
8412
L<"STATUS_VARIABLES">.
 
8413
 
 
8414
=item fk_error
 
8415
 
 
8416
Displays various data about InnoDB's last foreign key error.  Data source:
 
8417
L<"STATUS_VARIABLES">.
 
8418
 
 
8419
=item innodb_locks
 
8420
 
 
8421
Displays InnoDB locks.  Data source: L<"INNODB_LOCKS">.
 
8422
 
 
8423
=item innodb_transactions
 
8424
 
 
8425
Displays data about InnoDB's current transactions.  Data source:
 
8426
L<"INNODB_TRANSACTIONS">.
 
8427
 
 
8428
=item insert_buffers
 
8429
 
 
8430
Displays data about InnoDB's insert buffer.  Data source: L<"STATUS_VARIABLES">.
 
8431
 
 
8432
=item io_threads
 
8433
 
 
8434
Displays data about InnoDB's I/O threads.  Data source: L<"IO_THREADS">.
 
8435
 
 
8436
=item log_statistics
 
8437
 
 
8438
Displays data about InnoDB's logging system.  Data source: L<"STATUS_VARIABLES">.
 
8439
 
 
8440
=item master_status
 
8441
 
 
8442
Displays replication master status.  Data source: L<"STATUS_VARIABLES">.
 
8443
 
 
8444
=item open_tables
 
8445
 
 
8446
Displays open tables.  Data source: L<"OPEN_TABLES">.
 
8447
 
 
8448
=item page_statistics
 
8449
 
 
8450
Displays InnoDB page statistics.  Data source: L<"STATUS_VARIABLES">.
 
8451
 
 
8452
=item pending_io
 
8453
 
 
8454
Displays InnoDB pending I/O operations.  Data source: L<"STATUS_VARIABLES">.
 
8455
 
 
8456
=item processlist
 
8457
 
 
8458
Displays current MySQL processes (threads/connections).  Data source:
 
8459
L<"PROCESSLIST">.
 
8460
 
 
8461
=item q_header
 
8462
 
 
8463
Displays various status values.  Data source: L<"STATUS_VARIABLES">.
 
8464
 
 
8465
=item row_operation_misc
 
8466
 
 
8467
Displays data about InnoDB's row operations.  Data source:
 
8468
L<"STATUS_VARIABLES">.
 
8469
 
 
8470
=item row_operations
 
8471
 
 
8472
Displays data about InnoDB's row operations.  Data source:
 
8473
L<"STATUS_VARIABLES">.
 
8474
 
 
8475
=item semaphores
 
8476
 
 
8477
Displays data about InnoDB's semaphores and mutexes.  Data source:
 
8478
L<"STATUS_VARIABLES">.
 
8479
 
 
8480
=item slave_io_status
 
8481
 
 
8482
Displays data about the slave I/O thread.  Data source: 
 
8483
L<"STATUS_VARIABLES">.
 
8484
 
 
8485
=item slave_sql_status
 
8486
 
 
8487
Displays data about the slave SQL thread.  Data source: L<"STATUS_VARIABLES">.
 
8488
 
 
8489
=item t_header
 
8490
 
 
8491
Displays various InnoDB status values.  Data source: L<"STATUS_VARIABLES">.
 
8492
 
 
8493
=item var_status
 
8494
 
 
8495
Displays user-configurable data.  Data source: L<"STATUS_VARIABLES">.
 
8496
 
 
8497
=item wait_array
 
8498
 
 
8499
Displays data about InnoDB's OS wait array.  Data source: L<"OS_WAIT_ARRAY">.
 
8500
 
 
8501
=back
 
8502
 
 
8503
=head2 COLUMNS
 
8504
 
 
8505
Columns belong to tables.  You can choose a table's columns by pressing the '^'
 
8506
key, which starts the L<"TABLE EDITOR"> and lets you choose and edit columns.
 
8507
Pressing 'e' from within the table editor lets you edit the column's properties:
 
8508
 
 
8509
=over
 
8510
 
 
8511
=item *
 
8512
 
 
8513
hdr: a column header.  This appears in the first row of the table.
 
8514
 
 
8515
=item *
 
8516
 
 
8517
just: justification.  '-' means left-justified and '' means right-justified,
 
8518
just as with printf formatting codes (not a coincidence).
 
8519
 
 
8520
=item *
 
8521
 
 
8522
dec: whether to further align the column on the decimal point.
 
8523
 
 
8524
=item *
 
8525
 
 
8526
num: whether the column is numeric.  This affects how values are sorted
 
8527
(lexically or numerically).
 
8528
 
 
8529
=item *
 
8530
 
 
8531
label: a small note about the column, which appears in dialogs that help the
 
8532
user choose columns.
 
8533
 
 
8534
=item *
 
8535
 
 
8536
src: an expression that innotop uses to extract the column's data from its
 
8537
source (see L<"DATA SOURCES">).  See L<"EXPRESSIONS"> for more on expressions.
 
8538
 
 
8539
=item *
 
8540
 
 
8541
minw: specifies a minimum display width.  This helps stabilize the display,
 
8542
which makes it easier to read if the data is changing frequently.
 
8543
 
 
8544
=item *
 
8545
 
 
8546
maxw: similar to minw.
 
8547
 
 
8548
=item *
 
8549
 
 
8550
trans: a list of column transformations.  See L<"TRANSFORMATIONS">.
 
8551
 
 
8552
=item *
 
8553
 
 
8554
agg: an aggregate function.  See L<"GROUPING">.  The default is L<"first">.
 
8555
 
 
8556
=item *
 
8557
 
 
8558
aggonly: controls whether the column only shows when grouping is enabled on the
 
8559
table (see L<"GROUPING">).  By default, this is disabled.  This means columns
 
8560
will always be shown by default, whether grouping is enabled or not.  If a
 
8561
column's aggonly is set true, the column will appear when you toggle grouping on
 
8562
the table.  Several columns are set this way, such as the count column on
 
8563
L<"processlist"> and L<"innodb_transactions">, so you don't see a count when the
 
8564
grouping isn't enabled, but you do when it is.
 
8565
 
 
8566
=back
 
8567
 
 
8568
=head2 FILTERS
 
8569
 
 
8570
Filters remove rows from the display.  They behave much like a WHERE clause in
 
8571
SQL.  innotop has several built-in filters, which remove irrelevant information
 
8572
like inactive queries, but you can define your own as well.  innotop also lets
 
8573
you create quick-filters, which do not get saved to the configuration file, and
 
8574
are just an easy way to quickly view only some rows.
 
8575
 
 
8576
You can enable or disable a filter on any table.  Press the '%' key (mnemonic: %
 
8577
looks kind of like a line being filtered between two circles) and choose which
 
8578
table you want to filter, if asked.  You'll then see a list of possible filters
 
8579
and a list of filters currently enabled for that table.  Type the names of
 
8580
filters you want to apply and press Enter.
 
8581
 
 
8582
=head3 USER-DEFINED FILTERS
 
8583
 
 
8584
If you type a name that doesn't exist, innotop will prompt you to create the
 
8585
filter.  Filters are easy to create if you know Perl, and not hard if you don't.
 
8586
What you're doing is creating a subroutine that returns true if the row should
 
8587
be displayed.  The row is a hash reference passed to your subroutine as $set.
 
8588
 
 
8589
For example, imagine you want to filter the processlist table so you only see
 
8590
queries that have been running more than five minutes.  Type a new name for your
 
8591
filter, and when prompted for the subroutine body, press TAB to initiate your
 
8592
terminal's auto-completion.  You'll see the names of the columns in the
 
8593
L<"processlist"> table (innotop generally tries to help you with auto-completion
 
8594
lists).  You want to filter on the 'time' column.  Type the text "$set->{time} >
 
8595
300" to return true when the query is more than five minutes old.  That's all
 
8596
you need to do.
 
8597
 
 
8598
In other words, the code you're typing is surrounded by an implicit context,
 
8599
which looks like this:
 
8600
 
 
8601
 sub filter {
 
8602
    my ( $set ) = @_;
 
8603
    # YOUR CODE HERE
 
8604
 }
 
8605
 
 
8606
If your filter doesn't work, or if something else suddenly behaves differently,
 
8607
you might have made an error in your filter, and innotop is silently catching
 
8608
the error.  Try enabling L<"debug"> to make innotop throw an error instead.
 
8609
 
 
8610
=head3 QUICK-FILTERS
 
8611
 
 
8612
innotop's quick-filters are a shortcut to create a temporary filter that doesn't
 
8613
persist when you restart innotop.  To create a quick-filter, press the '/' key.
 
8614
innotop will prompt you for the column name and filter text.  Again, you can use
 
8615
auto-completion on column names.  The filter text can be just the text you want
 
8616
to "search for."  For example, to filter the L<"processlist"> table on queries
 
8617
that refer to the products table, type '/' and then 'info product'.
 
8618
 
 
8619
The filter text can actually be any Perl regular expression, but of course a
 
8620
literal string like 'product' works fine as a regular expression.
 
8621
 
 
8622
Behind the scenes innotop compiles the quick-filter into a specially tagged
 
8623
filter that is otherwise like any other filter.  It just isn't saved to the
 
8624
configuration file.
 
8625
 
 
8626
To clear quick-filters, press the '\' key and innotop will clear them all at
 
8627
once.
 
8628
 
 
8629
=head2 SORTING
 
8630
 
 
8631
innotop has sensible built-in defaults to sort the most important rows to the
 
8632
top of the table.  Like anything else in innotop, you can customize how any
 
8633
table is sorted.
 
8634
 
 
8635
To start the sort dialog, start the L<"TABLE EDITOR"> with the '^' key, choose a
 
8636
table if necessary, and press the 's' key.  You'll see a list of columns you can
 
8637
use in the sort expression and the current sort expression, if any.  Enter a
 
8638
list of columns by which you want to sort and press Enter.  If you want to
 
8639
reverse sort, prefix the column name with a minus sign.  For example, if you
 
8640
want to sort by column a ascending, then column b descending, type 'a -b'.  You
 
8641
can also explicitly add a + in front of columns you want to sort ascending, but
 
8642
it's not required.
 
8643
 
 
8644
Some modes have keys mapped to open this dialog directly, and to quickly reverse
 
8645
sort direction.  Press '?' as usual to see which keys are mapped in any mode.
 
8646
 
 
8647
=head2 GROUPING
 
8648
 
 
8649
innotop can group, or aggregate, rows together (I use the terms
 
8650
interchangeably).  This is quite similar to an SQL GROUP BY clause.  You can
 
8651
specify to group on certain columns, or if you don't specify any, the entire set
 
8652
of rows is treated as one group.  This is quite like SQL so far, but unlike SQL,
 
8653
you can also select un-grouped columns.  innotop actually aggregates every
 
8654
column.  If you don't explicitly specify a grouping function, the default is
 
8655
'first'.  This is basically a convenience so you don't have to specify an
 
8656
aggregate function for every column you want in the result.
 
8657
 
 
8658
You can quickly toggle grouping on a table with the '=' key, which toggles its
 
8659
aggregate property.  This property doesn't persist to the config file.
 
8660
 
 
8661
The columns by which the table is grouped are specified in its group_by
 
8662
property.  When you turn grouping on, innotop places the group_by columns at the
 
8663
far left of the table, even if they're not supposed to be visible.  The rest of
 
8664
the visible columns appear in order after them.
 
8665
 
 
8666
Two tables have default group_by lists and a count column built in:
 
8667
L<"processlist"> and L<"innodb_transactions">.  The grouping is by connection
 
8668
and status, so you can quickly see how many queries or transactions are in a
 
8669
given status on each server you're monitoring.  The time columns are aggregated
 
8670
as a sum; other columns are left at the default 'first' aggregation.
 
8671
 
 
8672
By default, the table shown in L<"S: Variables & Status"> mode also uses
 
8673
grouping so you can monitor variables and status across many servers.  The
 
8674
default aggregation function in this mode is 'avg'.
 
8675
 
 
8676
Valid grouping functions are defined in the %agg_funcs hash.  They include
 
8677
 
 
8678
=over
 
8679
 
 
8680
=item first
 
8681
 
 
8682
Returns the first element in the group.
 
8683
 
 
8684
=item count
 
8685
 
 
8686
Returns the number of elements in the group, including undefined elements, much
 
8687
like SQL's COUNT(*).
 
8688
 
 
8689
=item avg
 
8690
 
 
8691
Returns the average of defined elements in the group.
 
8692
 
 
8693
=item sum
 
8694
 
 
8695
Returns the sum of elements in the group.
 
8696
 
 
8697
=back
 
8698
 
 
8699
Here's an example of grouping at work.  Suppose you have a very busy server with
 
8700
hundreds of open connections, and you want to see how many connections are in
 
8701
what status.  Using the built-in grouping rules, you can press 'Q' to enter
 
8702
L<"Q: Query List"> mode.  Press '=' to toggle grouping (if necessary, select the
 
8703
L<"processlist"> table when prompted).
 
8704
 
 
8705
Your display might now look like the following:
 
8706
 
 
8707
 Query List (? for help) localhost, 32:33, 0.11 QPS, 1 thd, 5.0.38-log
 
8708
 
 
8709
 CXN        Cmd        Cnt  ID      User   Host           Time   Query       
 
8710
 localhost  Query      49    12933  webusr localhost      19:38  SELECT * FROM
 
8711
 localhost  Sending Da 23     2383  webusr localhost      12:43  SELECT col1,
 
8712
 localhost  Sleep      120     140  webusr localhost    5:18:12
 
8713
 localhost  Statistics 12    19213  webusr localhost      01:19  SELECT * FROM
 
8714
 
 
8715
That's actually quite a worrisome picture.  You've got a lot of idle connections
 
8716
(Sleep), and some connections executing queries (Query and Sending Data).
 
8717
That's okay, but you also have a lot in Statistics status, collectively spending
 
8718
over a minute.  That means the query optimizer is having a really hard time
 
8719
optimizing your statements.  Something is wrong; it should normally take
 
8720
milliseconds to optimize queries.  You might not have seen this pattern if you
 
8721
didn't look at your connections in aggregate.  (This is a made-up example, but
 
8722
it can happen in real life).
 
8723
 
 
8724
=head2 PIVOTING
 
8725
 
 
8726
innotop can pivot a table for more compact display, similar to a Pivot Table in
 
8727
a spreadsheet (also known as a crosstab).  Pivoting a table makes columns into
 
8728
rows.  Assume you start with this table:
 
8729
 
 
8730
 foo bar
 
8731
 === ===
 
8732
 1   3
 
8733
 2   4
 
8734
 
 
8735
After pivoting, the table will look like this:
 
8736
 
 
8737
 name set0 set1
 
8738
 ==== ==== ====
 
8739
 foo  1    2
 
8740
 bar  3    4
 
8741
 
 
8742
To get reasonable results, you might need to group as well as pivoting.
 
8743
innotop currently does this for L<"S: Variables & Status"> mode.
 
8744
 
 
8745
=head2 COLORS
 
8746
 
 
8747
By default, innotop highlights rows with color so you can see at a glance which
 
8748
rows are more important.  You can customize the colorization rules and add your
 
8749
own to any table.  Open the table editor with the '^' key, choose a table if
 
8750
needed, and press 'o' to open the color editor dialog.
 
8751
 
 
8752
The color editor dialog displays the rules applied to the table, in the order
 
8753
they are evaluated.  Each row is evaluated against each rule to see if the rule
 
8754
matches the row; if it does, the row gets the specified color, and no further
 
8755
rules are evaluated.  The rules look like the following:
 
8756
 
 
8757
 state  eq  Locked       black on_red
 
8758
 cmd    eq  Sleep        white       
 
8759
 user   eq  system user  white       
 
8760
 cmd    eq  Connect      white       
 
8761
 cmd    eq  Binlog Dump  white       
 
8762
 time   >   600          red         
 
8763
 time   >   120          yellow      
 
8764
 time   >   60           green       
 
8765
 time   >   30           cyan        
 
8766
 
 
8767
This is the default rule set for the L<"processlist"> table.  In order of
 
8768
priority, these rules make locked queries black on a red background, "gray out"
 
8769
connections from replication and sleeping queries, and make queries turn from
 
8770
cyan to red as they run longer.
 
8771
 
 
8772
(For some reason, the ANSI color code "white" is actually a light gray.  Your
 
8773
terminal's display may vary; experiment to find colors you like).
 
8774
 
 
8775
You can use keystrokes to move the rules up and down, which re-orders their
 
8776
priority.  You can also delete rules and add new ones.  If you add a new rule,
 
8777
innotop prompts you for the column, an operator for the comparison, a value
 
8778
against which to compare the column, and a color to assign if the rule matches.
 
8779
There is auto-completion and prompting at each step.
 
8780
 
 
8781
The value in the third step needs to be correctly quoted.  innotop does not try
 
8782
to quote the value because it doesn't know whether it should treat the value as
 
8783
a string or a number.  If you want to compare the column against a string, as
 
8784
for example in the first rule above, you should enter 'Locked' surrounded by
 
8785
quotes.  If you get an error message about a bareword, you probably should have
 
8786
quoted something.
 
8787
 
 
8788
=head2 EXPRESSIONS
 
8789
 
 
8790
Expressions are at the core of how innotop works, and are what enables you to
 
8791
extend innotop as you wish.  Recall the table lifecycle explained in
 
8792
L<"TABLES">.  Expressions are used in the earliest step, where it extracts
 
8793
values from a data source to form rows.
 
8794
 
 
8795
It does this by calling a subroutine for each column, passing it the source data
 
8796
set, a set of current values, and a set of previous values.  These are all
 
8797
needed so the subroutine can calculate things like the difference between this
 
8798
tick and the previous tick.
 
8799
 
 
8800
The subroutines that extract the data from the set are compiled from
 
8801
expressions.  This gives significantly more power than just naming the values to
 
8802
fill the columns, because it allows the column's value to be calculated from
 
8803
whatever data is necessary, but avoids the need to write complicated and lengthy
 
8804
Perl code.
 
8805
 
 
8806
innotop begins with a string of text that can look as simple as a value's name
 
8807
or as complicated as a full-fledged Perl expression.  It looks at each
 
8808
'bareword' token in the string and decides whether it's supposed to be a key
 
8809
into the $set hash.  A bareword is an unquoted value that isn't already
 
8810
surrounded by code-ish things like dollar signs or curly brackets.  If innotop
 
8811
decides that the bareword isn't a function or other valid Perl code, it converts
 
8812
it into a hash access.  After the whole string is processed, innotop compiles a
 
8813
subroutine, like this:
 
8814
 
 
8815
 sub compute_column_value {
 
8816
    my ( $set, $cur, $pre ) = @_;
 
8817
    my $val = # EXPANDED STRING GOES HERE
 
8818
    return $val;
 
8819
 }
 
8820
 
 
8821
Here's a concrete example, taken from the header table L<"q_header"> in L<"Q:
 
8822
Query List"> mode.  This expression calculates the qps, or Queries Per Second,
 
8823
column's values, from the values returned by SHOW STATUS:
 
8824
 
 
8825
 Questions/Uptime_hires
 
8826
 
 
8827
innotop decides both words are barewords, and transforms this expression into
 
8828
the following Perl code:
 
8829
 
 
8830
 $set->{Questions}/$set->{Uptime_hires}
 
8831
 
 
8832
When surrounded by the rest of the subroutine's code, this is executable Perl
 
8833
that calculates a high-resolution queries-per-second value.
 
8834
 
 
8835
The arguments to the subroutine are named $set, $cur, and $pre.  In most cases,
 
8836
$set and $cur will be the same values.  However, if L<"status_inc"> is set, $cur
 
8837
will not be the same as $set, because $set will already contain values that are
 
8838
the incremental difference between $cur and $pre.
 
8839
 
 
8840
Every column in innotop is computed by subroutines compiled in the same fashion.
 
8841
There is no difference between innotop's built-in columns and user-defined
 
8842
columns.  This keeps things consistent and predictable.
 
8843
 
 
8844
=head2 TRANSFORMATIONS
 
8845
 
 
8846
Transformations change how a value is rendered.  For example, they can take a
 
8847
number of seconds and display it in H:M:S format.  The following transformations
 
8848
are defined:
 
8849
 
 
8850
=over
 
8851
 
 
8852
=item commify
 
8853
 
 
8854
Adds commas to large numbers every three decimal places.
 
8855
 
 
8856
=item dulint_to_int
 
8857
 
 
8858
Accepts two unsigned integers and converts them into a single longlong.  This is
 
8859
useful for certain operations with InnoDB, which uses two integers as
 
8860
transaction identifiers, for example.
 
8861
 
 
8862
=item no_ctrl_char
 
8863
 
 
8864
Removes quoted control characters from the value.  This is affected by the
 
8865
L<"charset"> configuration variable.
 
8866
 
 
8867
This transformation only operates within quoted strings, for example, values to
 
8868
a SET clause in an UPDATE statement.  It will not alter the UPDATE statement,
 
8869
but will collapse the quoted string to [BINARY] or [TEXT], depending on the
 
8870
charset.
 
8871
 
 
8872
=item percent
 
8873
 
 
8874
Converts a number to a percentage by multiplying it by two, formatting it with
 
8875
L<"num_digits"> digits after the decimal point, and optionally adding a percent
 
8876
sign (see L<"show_percent">).
 
8877
 
 
8878
=item secs_to_time
 
8879
 
 
8880
Formats a number of seconds as time in days+hours:minutes:seconds format.
 
8881
 
 
8882
=item set_precision
 
8883
 
 
8884
Formats numbers with L<"num_digits"> number of digits after the decimal point.
 
8885
 
 
8886
=item shorten
 
8887
 
 
8888
Formats a number as a unit of 1024 (k/M/G/T) and with L<"num_digits"> number of
 
8889
digits after the decimal point.
 
8890
 
 
8891
=back
 
8892
 
 
8893
=head2 TABLE EDITOR
 
8894
 
 
8895
The innotop table editor lets you customize tables with keystrokes.  You start
 
8896
the table editor with the '^' key.  If there's more than one table on the
 
8897
screen, it will prompt you to choose one of them.  Once you do, innotop will
 
8898
show you something like this:
 
8899
 
 
8900
 Editing table definition for Buffer Pool.  Press ? for help, q to quit.
 
8901
 
 
8902
 name               hdr          label                  src          
 
8903
 cxn                CXN          Connection from which  cxn          
 
8904
 buf_pool_size      Size         Buffer pool size       IB_bp_buf_poo
 
8905
 buf_free           Free Bufs    Buffers free in the b  IB_bp_buf_fre
 
8906
 pages_total        Pages        Pages total            IB_bp_pages_t
 
8907
 pages_modified     Dirty Pages  Pages modified (dirty  IB_bp_pages_m
 
8908
 buf_pool_hit_rate  Hit Rate     Buffer pool hit rate   IB_bp_buf_poo
 
8909
 total_mem_alloc    Memory       Total memory allocate  IB_bp_total_m
 
8910
 add_pool_alloc     Add'l Pool   Additonal pool alloca  IB_bp_add_poo
 
8911
 
 
8912
The first line shows which table you're editing, and reminds you again to press
 
8913
'?' for a list of key mappings.  The rest is a tabular representation of the
 
8914
table's columns, because that's likely what you're trying to edit.  However, you
 
8915
can edit more than just the table's columns; this screen can start the filter
 
8916
editor, color rule editor, and more.
 
8917
 
 
8918
Each row in the display shows a single column in the table you're editing, along
 
8919
with a couple of its properties such as its header and source expression (see
 
8920
L<"EXPRESSIONS">).
 
8921
 
 
8922
The key mappings are Vim-style, as in many other places.  Pressing 'j' and 'k'
 
8923
moves the highlight up or down.  You can then (d)elete or (e)dit the highlighted
 
8924
column.  You can also (a)dd a column to the table.  This actually just activates
 
8925
one of the columns already defined for the table; it prompts you to choose from
 
8926
among the columns available but not currently displayed.  Finally, you can
 
8927
re-order the columns with the '+' and '-' keys.
 
8928
 
 
8929
You can do more than just edit the columns with the table editor, you can also
 
8930
edit other properties, such as the table's sort expression and group-by
 
8931
expression.  Press '?' to see the full list, of course.
 
8932
 
 
8933
If you want to really customize and create your own column, as opposed to just
 
8934
activating a built-in one that's not currently displayed, press the (n)ew key,
 
8935
and innotop will prompt you for the information it needs:
 
8936
 
 
8937
=over
 
8938
 
 
8939
=item *
 
8940
 
 
8941
The column name: this needs to be a word without any funny characters, e.g. just
 
8942
letters, numbers and underscores.
 
8943
 
 
8944
=item *
 
8945
 
 
8946
The column header: this is the label that appears at the top of the column, in
 
8947
the table header.  This can have spaces and funny characters, but be careful not
 
8948
to make it too wide and waste space on-screen.
 
8949
 
 
8950
=item *
 
8951
 
 
8952
The column's data source: this is an expression that determines what data from
 
8953
the source (see L<"TABLES">) innotop will put into the column.  This can just be
 
8954
the name of an item in the source, or it can be a more complex expression, as
 
8955
described in L<"EXPRESSIONS">.
 
8956
 
 
8957
=back
 
8958
 
 
8959
Once you've entered the required data, your table has a new column.  There is no
 
8960
difference between this column and the built-in ones; it can have all the same
 
8961
properties and behaviors.  innotop will write the column's definition to the
 
8962
configuration file, so it will persist across sessions.
 
8963
 
 
8964
Here's an example: suppose you want to track how many times your slaves have
 
8965
retried transactions.  According to the MySQL manual, the
 
8966
Slave_retried_transactions status variable gives you that data: "The total
 
8967
number of times since startup that the replication slave SQL thread has retried
 
8968
transactions. This variable was added in version 5.0.4."  This is appropriate to
 
8969
add to the L<"slave_sql_status"> table.
 
8970
 
 
8971
To add the column, switch to the replication-monitoring mode with the 'M' key,
 
8972
and press the '^' key to start the table editor.  When prompted, choose
 
8973
slave_sql_status as the table, then press 'n' to create the column.  Type
 
8974
'retries' as the column name, 'Retries' as the column header, and
 
8975
'Slave_retried_transactions' as the source.  Now the column is created, and you
 
8976
see the table editor screen again.  Press 'q' to exit the table editor, and
 
8977
you'll see your column at the end of the table.
 
8978
 
 
8979
=head1 VARIABLE SETS
 
8980
 
 
8981
Variable sets are used in L<"S: Variables & Status"> mode to define more easily
 
8982
what variables you want to monitor.  Behind the scenes they are compiled to a
 
8983
list of expressions, and then into a column list so they can be treated just
 
8984
like columns in any other table, in terms of data extraction and
 
8985
transformations.  However, you're protected from the tedious details by a syntax
 
8986
that ought to feel very natural to you: a SQL SELECT list.
 
8987
 
 
8988
The data source for variable sets, and indeed the entire S mode, is the
 
8989
combination of SHOW STATUS, SHOW VARIABLES, and SHOW INNODB STATUS.  Imagine
 
8990
that you had a huge table with one column per variable returned from those
 
8991
statements.  That's the data source for variable sets.  You can now query this
 
8992
data source just like you'd expect.  For example:
 
8993
 
 
8994
 Questions, Uptime, Questions/Uptime as QPS
 
8995
 
 
8996
Behind the scenes innotop will split that variable set into three expressions,
 
8997
compile them and turn them into a table definition, then extract as usual.  This
 
8998
becomes a "variable set," or a "list of variables you want to monitor."
 
8999
 
 
9000
innotop lets you name and save your variable sets, and writes them to the
 
9001
configuration file.  You can choose which variable set you want to see with the
 
9002
'c' key, or activate the next and previous sets with the '>' and '<' keys.
 
9003
There are many built-in variable sets as well, which should give you a good
 
9004
start for creating your own.  Press 'e' to edit the current variable set, or
 
9005
just to see how it's defined.  To create a new one, just press 'c' and type its
 
9006
name.
 
9007
 
 
9008
You may want to use some of the functions listed in L<"TRANSFORMATIONS"> to help
 
9009
format the results.  In particular, L<"set_precision"> is often useful to limit
 
9010
the number of digits you see.  Extending the above example, here's how:
 
9011
 
 
9012
 Questions, Uptime, set_precision(Questions/Uptime) as QPS
 
9013
 
 
9014
Actually, this still needs a little more work.  If your L<"interval"> is less
 
9015
than one second, you might be dividing by zero because Uptime is incremental in
 
9016
this mode by default.  Instead, use Uptime_hires:
 
9017
 
 
9018
 Questions, Uptime, set_precision(Questions/Uptime_hires) as QPS
 
9019
 
 
9020
This example is simple, but it shows how easy it is to choose which variables
 
9021
you want to monitor.
 
9022
 
 
9023
=head1 PLUGINS
 
9024
 
 
9025
innotop has a simple but powerful plugin mechanism by which you can extend
 
9026
or modify its existing functionality, and add new functionality.  innotop's
 
9027
plugin functionality is event-based: plugins register themselves to be called
 
9028
when events happen.  They then have a chance to influence the event.
 
9029
 
 
9030
An innotop plugin is a Perl module placed in innotop's L<"plugin_dir">
 
9031
directory.  On UNIX systems, you can place a symbolic link to the module instead
 
9032
of putting the actual file there.  innotop automatically discovers the file.  If
 
9033
there is a corresponding entry in the L<"plugins"> configuration file section,
 
9034
innotop loads and activates the plugin.
 
9035
 
 
9036
The module must conform to innotop's plugin interface.  Additionally, the source
 
9037
code of the module must be written in such a way that innotop can inspect the
 
9038
file and determine the package name and description.
 
9039
 
 
9040
=head2 Package Source Convention
 
9041
 
 
9042
innotop inspects the plugin module's source to determine the Perl package name.
 
9043
It looks for a line of the form "package Foo;" and if found, considers the
 
9044
plugin's package name to be Foo.  Of course the package name can be a valid Perl
 
9045
package name, with double semicolons and so on.
 
9046
 
 
9047
It also looks for a description in the source code, to make the plugin editor
 
9048
more human-friendly.  The description is a comment line of the form "#
 
9049
description: Foo", where "Foo" is the text innotop will consider to be the
 
9050
plugin's description.
 
9051
 
 
9052
=head2 Plugin Interface
 
9053
 
 
9054
The innotop plugin interface is quite simple: innotop expects the plugin to be
 
9055
an object-oriented module it can call certain methods on.  The methods are
 
9056
 
 
9057
=over
 
9058
 
 
9059
=item new(%variables)
 
9060
 
 
9061
This is the plugin's constructor.  It is passed a hash of innotop's variables,
 
9062
which it can manipulate (see L<"Plugin Variables">).  It must return a reference
 
9063
to the newly created plugin object.
 
9064
 
 
9065
At construction time, innotop has only loaded the general configuration and
 
9066
created the default built-in variables with their default contents (which is
 
9067
quite a lot).  Therefore, the state of the program is exactly as in the innotop
 
9068
source code, plus the configuration variables from the L<"general"> section in
 
9069
the config file.
 
9070
 
 
9071
If your plugin manipulates the variables, it is changing global data, which is
 
9072
shared by innotop and all plugins.  Plugins are loaded in the order they're
 
9073
listed in the config file.  Your plugin may load before or after another plugin,
 
9074
so there is a potential for conflict or interaction between plugins if they
 
9075
modify data other plugins use or modify.
 
9076
 
 
9077
=item register_for_events()
 
9078
 
 
9079
This method must return a list of events in which the plugin is interested, if
 
9080
any.  See L<"Plugin Events"> for the defined events.  If the plugin returns an
 
9081
event that's not defined, the event is ignored.
 
9082
 
 
9083
=item event handlers
 
9084
 
 
9085
The plugin must implement a method named the same as each event for which it has
 
9086
registered.  In other words, if the plugin returns qw(foo bar) from
 
9087
register_for_events(), it must have foo() and bar() methods.  These methods are
 
9088
callbacks for the events.  See L<"Plugin Events"> for more details about each
 
9089
event.
 
9090
 
 
9091
=back
 
9092
 
 
9093
=head2 Plugin Variables
 
9094
 
 
9095
The plugin's constructor is passed a hash of innotop's variables, which it can
 
9096
manipulate.  It is probably a good idea if the plugin object saves a copy of it
 
9097
for later use.  The variables are defined in the innotop variable
 
9098
%pluggable_vars, and are as follows:
 
9099
 
 
9100
=over
 
9101
 
 
9102
=item action_for
 
9103
 
 
9104
A hashref of key mappings.  These are innotop's global hot-keys.
 
9105
 
 
9106
=item agg_funcs
 
9107
 
 
9108
A hashref of functions that can be used for grouping.  See L<"GROUPING">.
 
9109
 
 
9110
=item config
 
9111
 
 
9112
The global configuration hash.
 
9113
 
 
9114
=item connections
 
9115
 
 
9116
A hashref of connection specifications.  These are just specifications of how to
 
9117
connect to a server.
 
9118
 
 
9119
=item dbhs
 
9120
 
 
9121
A hashref of innotop's database connections.  These are actual DBI connection
 
9122
objects.
 
9123
 
 
9124
=item filters
 
9125
 
 
9126
A hashref of filters applied to table rows.  See L<"FILTERS"> for more.
 
9127
 
 
9128
=item modes
 
9129
 
 
9130
A hashref of modes.  See L<"MODES"> for more.
 
9131
 
 
9132
=item server_groups
 
9133
 
 
9134
A hashref of server groups.  See L<"SERVER GROUPS">.
 
9135
 
 
9136
=item tbl_meta
 
9137
 
 
9138
A hashref of innotop's table meta-data, with one entry per table (see
 
9139
L<"TABLES"> for more information).
 
9140
 
 
9141
=item trans_funcs
 
9142
 
 
9143
A hashref of transformation functions.  See L<"TRANSFORMATIONS">.
 
9144
 
 
9145
=item var_sets
 
9146
 
 
9147
A hashref of variable sets.  See L<"VARIABLE SETS">.
 
9148
 
 
9149
=back
 
9150
 
 
9151
=head2 Plugin Events
 
9152
 
 
9153
Each event is defined somewhere in the innotop source code.  When innotop runs
 
9154
that code, it executes the callback function for each plugin that expressed its
 
9155
interest in the event.  innotop passes some data for each event.  The events are
 
9156
defined in the %event_listener_for variable, and are as follows:
 
9157
 
 
9158
=over
 
9159
 
 
9160
=item extract_values($set, $cur, $pre, $tbl)
 
9161
 
 
9162
This event occurs inside the function that extracts values from a data source.
 
9163
The arguments are the set of values, the current values, the previous values,
 
9164
and the table name.
 
9165
 
 
9166
=item set_to_tbl
 
9167
 
 
9168
Events are defined at many places in this subroutine, which is responsible for
 
9169
turning an arrayref of hashrefs into an arrayref of lines that can be printed to
 
9170
the screen.  The events all pass the same data: an arrayref of rows and the name
 
9171
of the table being created.  The events are set_to_tbl_pre_filter,
 
9172
set_to_tbl_pre_sort,set_to_tbl_pre_group, set_to_tbl_pre_colorize,
 
9173
set_to_tbl_pre_transform, set_to_tbl_pre_pivot, set_to_tbl_pre_create,
 
9174
set_to_tbl_post_create.
 
9175
 
 
9176
=item draw_screen($lines)
 
9177
 
 
9178
This event occurs inside the subroutine that prints the lines to the screen.
 
9179
$lines is an arrayref of strings.
 
9180
 
 
9181
=back
 
9182
 
 
9183
=head2 Simple Plugin Example
 
9184
 
 
9185
The easiest way to explain the plugin functionality is probably with a simple
 
9186
example.  The following module adds a column to the beginning of every table and
 
9187
sets its value to 1.
 
9188
 
 
9189
 use strict;
 
9190
 use warnings FATAL => 'all';
 
9191
 
 
9192
 package Innotop::Plugin::Example;
 
9193
 # description: Adds an 'example' column to every table
 
9194
 
 
9195
 sub new {
 
9196
    my ( $class, %vars ) = @_;
 
9197
    # Store reference to innotop's variables in $self
 
9198
    my $self = bless { %vars }, $class;
 
9199
 
 
9200
    # Design the example column
 
9201
    my $col = {
 
9202
       hdr   => 'Example',
 
9203
       just  => '',
 
9204
       dec   => 0,
 
9205
       num   => 1,
 
9206
       label => 'Example',
 
9207
       src   => 'example', # Get data from this column in the data source
 
9208
       tbl   => '',
 
9209
       trans => [],
 
9210
    };
 
9211
 
 
9212
    # Add the column to every table.
 
9213
    my $tbl_meta = $vars{tbl_meta};
 
9214
    foreach my $tbl ( values %$tbl_meta ) {
 
9215
       # Add the column to the list of defined columns
 
9216
       $tbl->{cols}->{example} = $col;
 
9217
       # Add the column to the list of visible columns
 
9218
       unshift @{$tbl->{visible}}, 'example';
 
9219
    }
 
9220
 
 
9221
    # Be sure to return a reference to the object.
 
9222
    return $self;
 
9223
 }
 
9224
 
 
9225
 # I'd like to be called when a data set is being rendered into a table, please.
 
9226
 sub register_for_events {
 
9227
    my ( $self ) = @_;
 
9228
    return qw(set_to_tbl_pre_filter);
 
9229
 }
 
9230
 
 
9231
 # This method will be called when the event fires.
 
9232
 sub set_to_tbl_pre_filter {
 
9233
    my ( $self, $rows, $tbl ) = @_;
 
9234
    # Set the example column's data source to the value 1.
 
9235
    foreach my $row ( @$rows ) {
 
9236
       $row->{example} = 1;
 
9237
    }
 
9238
 }
 
9239
 
 
9240
 1;
 
9241
 
 
9242
=head2 Plugin Editor
 
9243
 
 
9244
The plugin editor lets you view the plugins innotop discovered and activate or
 
9245
deactivate them.  Start the editor by pressing $ to start the configuration
 
9246
editor from any mode.  Press the 'p' key to start the plugin editor.  You'll see
 
9247
a list of plugins innotop discovered.  You can use the 'j' and 'k' keys to move
 
9248
the highlight to the desired one, then press the * key to toggle it active or
 
9249
inactive.  Exit the editor and restart innotop for the changes to take effect.
 
9250
 
 
9251
=head1 SQL STATEMENTS
 
9252
 
 
9253
innotop uses a limited set of SQL statements to retrieve data from MySQL for
 
9254
display.  The statements are customized depending on the server version against
 
9255
which they are executed; for example, on MySQL 5 and newer, INNODB_STATUS
 
9256
executes "SHOW ENGINE INNODB STATUS", while on earlier versions it executes
 
9257
"SHOW INNODB STATUS".  The statements are as follows:
 
9258
 
 
9259
 Statement           SQL executed
 
9260
 =================== ===============================
 
9261
 INNODB_STATUS       SHOW [ENGINE] INNODB STATUS
 
9262
 KILL_CONNECTION     KILL
 
9263
 KILL_QUERY          KILL QUERY
 
9264
 OPEN_TABLES         SHOW OPEN TABLES
 
9265
 PROCESSLIST         SHOW FULL PROCESSLIST
 
9266
 SHOW_MASTER_LOGS    SHOW MASTER LOGS
 
9267
 SHOW_MASTER_STATUS  SHOW MASTER STATUS
 
9268
 SHOW_SLAVE_STATUS   SHOW SLAVE STATUS
 
9269
 SHOW_STATUS         SHOW [GLOBAL] STATUS
 
9270
 SHOW_VARIABLES      SHOW [GLOBAL] VARIABLES
 
9271
 
 
9272
=head1 DATA SOURCES
 
9273
 
 
9274
Each time innotop extracts values to create a table (see L<"EXPRESSIONS"> and
 
9275
L<"TABLES">), it does so from a particular data source.  Largely because of the
 
9276
complex data extracted from SHOW INNODB STATUS, this is slightly messy.  SHOW
 
9277
INNODB STATUS contains a mixture of single values and repeated values that form
 
9278
nested data sets.
 
9279
 
 
9280
Whenever innotop fetches data from MySQL, it adds two extra bits to each set:
 
9281
cxn and Uptime_hires.  cxn is the name of the connection from which the data
 
9282
came.  Uptime_hires is a high-resolution version of the server's Uptime status
 
9283
variable, which is important if your L<"interval"> setting is sub-second.
 
9284
 
 
9285
Here are the kinds of data sources from which data is extracted:
 
9286
 
 
9287
=over
 
9288
 
 
9289
=item STATUS_VARIABLES
 
9290
 
 
9291
This is the broadest category, into which the most kinds of data fall.  It
 
9292
begins with the combination of SHOW STATUS and SHOW VARIABLES, but other sources
 
9293
may be included as needed, for example, SHOW MASTER STATUS and SHOW SLAVE
 
9294
STATUS, as well as many of the non-repeated values from SHOW INNODB STATUS.
 
9295
 
 
9296
=item DEADLOCK_LOCKS
 
9297
 
 
9298
This data is extracted from the transaction list in the LATEST DETECTED DEADLOCK
 
9299
section of SHOW INNODB STATUS.  It is nested two levels deep: transactions, then
 
9300
locks.
 
9301
 
 
9302
=item DEADLOCK_TRANSACTIONS
 
9303
 
 
9304
This data is from the transaction list in the LATEST DETECTED DEADLOCK
 
9305
section of SHOW INNODB STATUS.  It is nested one level deep.
 
9306
 
 
9307
=item EXPLAIN
 
9308
 
 
9309
This data is from the result set returned by EXPLAIN.
 
9310
 
 
9311
=item INNODB_TRANSACTIONS
 
9312
 
 
9313
This data is from the TRANSACTIONS section of SHOW INNODB STATUS.
 
9314
 
 
9315
=item IO_THREADS
 
9316
 
 
9317
This data is from the list of threads in the the FILE I/O section of SHOW INNODB
 
9318
STATUS.
 
9319
 
 
9320
=item INNODB_LOCKS
 
9321
 
 
9322
This data is from the TRANSACTIONS section of SHOW INNODB STATUS and is nested
 
9323
two levels deep.
 
9324
 
 
9325
=item OPEN_TABLES
 
9326
 
 
9327
This data is from SHOW OPEN TABLES.
 
9328
 
 
9329
=item PROCESSLIST
 
9330
 
 
9331
This data is from SHOW FULL PROCESSLIST.
 
9332
 
 
9333
=item OS_WAIT_ARRAY
 
9334
 
 
9335
This data is from the SEMAPHORES section of SHOW INNODB STATUS and is nested one
 
9336
level deep.  It comes from the lines that look like this:
 
9337
 
 
9338
 --Thread 1568861104 has waited at btr0cur.c line 424 ....
 
9339
 
 
9340
=back
 
9341
 
 
9342
=head1 MYSQL PRIVILEGES
 
9343
 
 
9344
=over
 
9345
 
 
9346
=item *
 
9347
 
 
9348
You must connect to MySQL as a user who has the SUPER privilege for many of the
 
9349
functions.
 
9350
 
 
9351
=item *
 
9352
 
 
9353
If you don't have the SUPER privilege, you can still run some functions, but you
 
9354
won't necessarily see all the same data.
 
9355
 
 
9356
=item *
 
9357
 
 
9358
You need the PROCESS privilege to see the list of currently running queries in Q
 
9359
mode.
 
9360
 
 
9361
=item *
 
9362
 
 
9363
You need special privileges to start and stop slave servers.
 
9364
 
 
9365
=item *
 
9366
 
 
9367
You need appropriate privileges to create and drop the deadlock tables if needed
 
9368
(see L<"SERVER CONNECTIONS">).
 
9369
 
 
9370
=back
 
9371
 
 
9372
=head1 SYSTEM REQUIREMENTS
 
9373
 
 
9374
You need Perl to run innotop, of course.  You also need a few Perl modules: DBI,
 
9375
DBD::mysql,  Term::ReadKey, and Time::HiRes.  These should be included with most
 
9376
Perl distributions, but in case they are not, I recommend using versions
 
9377
distributed with your operating system or Perl distribution, not from CPAN.
 
9378
Term::ReadKey in particular has been known to cause problems if installed from
 
9379
CPAN.
 
9380
 
 
9381
If you have Term::ANSIColor, innotop will use it to format headers more readably
 
9382
and compactly.  (Under Microsoft Windows, you also need Win32::Console::ANSI for
 
9383
terminal formatting codes to be honored).  If you install Term::ReadLine,
 
9384
preferably Term::ReadLine::Gnu, you'll get nice auto-completion support.
 
9385
 
 
9386
I run innotop on Gentoo GNU/Linux, Debian and Ubuntu, and I've had feedback from
 
9387
people successfully running it on Red Hat, CentOS, Solaris, and Mac OSX.  I
 
9388
don't see any reason why it won't work on other UNIX-ish operating systems, but
 
9389
I don't know for sure.  It also runs on Windows under ActivePerl without
 
9390
problem.
 
9391
 
 
9392
I use innotop on MySQL versions 3.23.58, 4.0.27, 4.1.0, 4.1.22, 5.0.26, 5.1.15,
 
9393
and 5.2.3.  If it doesn't run correctly for you, that is a bug and I hope you
 
9394
report it.
 
9395
 
 
9396
=head1 FILES
 
9397
 
 
9398
$HOMEDIR/.innotop is used to store configuration information.  Files include the
 
9399
configuration file innotop.ini, the core_dump file which contains verbose error
 
9400
messages if L<"debug"> is enabled, and the plugins/ subdirectory.
 
9401
 
 
9402
=head1 GLOSSARY OF TERMS
 
9403
 
 
9404
=over
 
9405
 
 
9406
=item tick
 
9407
 
 
9408
A tick is a refresh event, when innotop re-fetches data from connections and
 
9409
displays it.
 
9410
 
 
9411
=back
 
9412
 
 
9413
=head1 ACKNOWLEDGEMENTS
 
9414
 
 
9415
I'm grateful to the following people for various reasons, and hope I haven't
 
9416
forgotten to include anyone:
 
9417
 
 
9418
Allen K. Smith,
 
9419
Aurimas Mikalauskas,
 
9420
Bartosz Fenski,
 
9421
Brian Miezejewski,
 
9422
Christian Hammers, 
 
9423
Cyril Scetbon,
 
9424
Dane Miller,
 
9425
David Multer,
 
9426
Dr. Frank Ullrich,
 
9427
Giuseppe Maxia,
 
9428
Google.com Site Reliability Engineers,
 
9429
Jan Pieter Kunst,
 
9430
Jari Aalto,
 
9431
Jay Pipes,
 
9432
Jeremy Zawodny,
 
9433
Johan Idren,
 
9434
Kristian Kohntopp,
 
9435
Lenz Grimmer,
 
9436
Maciej Dobrzanski,
 
9437
Michiel Betel,
 
9438
MySQL AB,
 
9439
Paul McCullagh,
 
9440
Sebastien Estienne,
 
9441
Sourceforge.net,
 
9442
Steven Kreuzer,
 
9443
The Gentoo MySQL Team,
 
9444
Trevor Price,
 
9445
Yaar Schnitman,
 
9446
and probably more people I've neglected to include.
 
9447
 
 
9448
(If I misspelled your name, it's probably because I'm afraid of putting
 
9449
international characters into this documentation; earlier versions of Perl might
 
9450
not be able to compile it then).
 
9451
 
 
9452
=head1 COPYRIGHT, LICENSE AND WARRANTY
 
9453
 
 
9454
This program is copyright (c) 2006 Baron Schwartz.
 
9455
Feedback and improvements are welcome.
 
9456
 
 
9457
THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
 
9458
WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 
9459
MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 
9460
 
 
9461
This program is free software; you can redistribute it and/or modify it under
 
9462
the terms of the GNU General Public License as published by the Free Software
 
9463
Foundation, version 2; OR the Perl Artistic License.  On UNIX and similar
 
9464
systems, you can issue `man perlgpl' or `man perlartistic' to read these
 
9465
licenses.
 
9466
 
 
9467
You should have received a copy of the GNU General Public License along with
 
9468
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 
9469
Place, Suite 330, Boston, MA  02111-1307  USA.
 
9470
 
 
9471
Execute innotop and press '!' to see this information at any time.
 
9472
 
 
9473
=head1 AUTHOR
 
9474
 
 
9475
Baron Schwartz.
 
9476
 
 
9477
=head1 BUGS
 
9478
 
 
9479
You can report bugs, ask for improvements, and get other help and support at
 
9480
L<http://sourceforge.net/projects/innotop>.  There are mailing lists, forums,
 
9481
a bug tracker, etc.  Please use these instead of contacting me directly, as it
 
9482
makes my job easier and benefits others if the discussions are permanent and
 
9483
public.  Of course, if you need to contact me in private, please do.
 
9484
 
 
9485
=cut