~ubuntu-branches/ubuntu/gutsy/mysql-dfsg-5.0/gutsy-updates

« back to all changes in this revision

Viewing changes to debian/additions/innotop/innotop

  • Committer: Bazaar Package Importer
  • Author(s): Martin Pitt
  • Date: 2007-04-27 16:03:02 UTC
  • Revision ID: james.westby@ubuntu.com-20070427160302-gp3teonw93l6ja75
Tags: 5.0.38-3build1
Fake sync from Debian. All Ubuntu changes are in Debian, but we have a
md5sum mismatch on the orig.tar.gz.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/perl
 
2
 
 
3
eval 'exec /usr/bin/perl  -S $0 ${1+"$@"}'
 
4
    if 0; # not running under some shell
 
5
 
 
6
# vim: foldmethod=marker:tw=160:nowrap:expandtab:tabstop=3:shiftwidth=3:softtabstop=3
 
7
# vim users, set modeline to enable auto-folding and compatibility with my preferred
 
8
# formatting.  I use a very wide textwidth because there's tons of configuration
 
9
# data that's much easier to manage when it's laid out in a (wide) tabular
 
10
# format.  I try to keep most real code to a textwidth of 80 or so.
 
11
 
 
12
use strict;
 
13
use warnings FATAL => 'all';
 
14
use sigtrap qw(handler finish untrapped normal-signals);
 
15
 
 
16
use Data::Dumper;
 
17
use DBI;
 
18
use English qw(-no_match_vars);
 
19
use Getopt::Long;
 
20
use List::Util qw(max min maxstr);
 
21
use InnoDBParser;
 
22
 
 
23
# Version, license and warranty information. {{{1
 
24
# ###########################################################################
 
25
our $VERSION = '1.4.0';
 
26
 
 
27
my $innotop_license = <<"LICENSE";
 
28
 
 
29
This is innotop version $VERSION, a MySQL and InnoDB monitor.
 
30
 
 
31
This program is copyright (c) 2006 Baron Schwartz, baron at xaprb dot com.
 
32
Feedback and improvements are welcome.
 
33
 
 
34
THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
 
35
WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 
36
MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 
37
 
 
38
This program is free software; you can redistribute it and/or modify it under
 
39
the terms of the GNU General Public License as published by the Free Software
 
40
Foundation, version 2; OR the Perl Artistic License.  On UNIX and similar
 
41
systems, you can issue `man perlgpl' or `man perlartistic' to read these
 
42
licenses.
 
43
 
 
44
You should have received a copy of the GNU General Public License along with
 
45
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 
46
Place, Suite 330, Boston, MA  02111-1307  USA.
 
47
LICENSE
 
48
 
 
49
# Configuration information and global setup {{{1
 
50
# ###########################################################################
 
51
 
 
52
# Really, really, super-global variables.
 
53
my @config_versions = (
 
54
   "000-000-000", "001-003-000", # config file was one big name-value hash.
 
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
   num   => 0,
 
65
   label => '',
 
66
   user  => 0,
 
67
   src   => '',
 
68
   tbl   => '', # Helps when writing/reading custom columns in config files
 
69
   expr  => '', # In case column's src is an expression, the name of the expr
 
70
   minw  => 0,
 
71
   maxw  => 0,
 
72
   trans => [],
 
73
);
 
74
 
 
75
# I use my own caching because I need to explicitly know when I actually get a new
 
76
# connection.  It's just easier to code it myself.
 
77
my %dbhs;
 
78
 
 
79
# Command-line parameters {{{2
 
80
# ###########################################################################
 
81
 
 
82
# Define cmdline args; each is spec, config, desc.  Add more hash entries as needed.
 
83
my %opt_spec = (
 
84
   h => { s => 'help|h',                       d => 'Show this help message' },
 
85
   c => { s => 'config|c=s',                   d => 'Config file to read' },
 
86
   n => { s => 'nonint|n',                     d => 'Non-interactive, output tab-separated fields' },
 
87
   m => { s => 'mode|m=s',   config => 'mode', d => 'Operating mode to start in' },
 
88
);
 
89
 
 
90
# Define the order cmdline opts will appear in help output.  Add any extra ones
 
91
# defined above.
 
92
my @opt_keys = qw( c n m h );
 
93
 
 
94
# This is the container for the command-line options' values to be stored in
 
95
# after processing.  Initial values are defaults.
 
96
my %opts = (
 
97
   n => !( -t STDIN && -t STDOUT ), # If in/out aren't to terminals, we're interactive
 
98
);
 
99
 
 
100
Getopt::Long::Configure('no_ignore_case', 'bundling');
 
101
GetOptions( map { $opt_spec{$_}->{'s'} => \$opts{$_} }  @opt_keys );
 
102
 
 
103
if ( $opts{'h'} ) {
 
104
   print "Usage: innotop <options>\n\nOptions:\n\n";
 
105
   foreach my $key ( @opt_keys ) {
 
106
      my ( $long, $short ) = $opt_spec{$key}->{'s'} =~ m/^(\w+)(?:\|([^=]*))?/;
 
107
      $long  = "--$long" . ( $short ? ',' : '' );
 
108
      $short = $short ? " -$short" : '';
 
109
      printf("  %-13s %-4s %s\n", $long, $short, $opt_spec{$key}->{'d'});
 
110
   }
 
111
   print <<USAGE;
 
112
 
 
113
innotop connects to a MySQL database and displays information from it so you can
 
114
monitor its status, such as what queries are running.
 
115
 
 
116
USAGE
 
117
   exit(1);
 
118
}
 
119
 
 
120
# Meta-data (table definitions etc) {{{2
 
121
# ###########################################################################
 
122
 
 
123
# Expressions {{{3
 
124
# Each expression looks like this when fully hydrated:
 
125
# Name => { func => sub{ return 1 }, text => 'return 1', user => 1 }
 
126
#   * The text is the plain text of the expression
 
127
#   * The func is that text, compiled into a subroutine
 
128
#   * The user is whether it's user-defined, and hence needs writing to config
 
129
# So the loading is exactly the same whether user-defined or built-in, the
 
130
# expressions aren't initially stored here; they are initially stored in a
 
131
# hash that looks just like what comes out of the config file.  Those are
 
132
# hydrated with the compile_expr() function.
 
133
# ###########################################################################
 
134
my %exprs         = ();
 
135
my %builtin_exprs = (
 
136
   # TODO remove more of these.
 
137
   Host              => q{my $host = $set->{Host} || $set->{hostname} || ''; ($host) = $host =~ m/^((?:[\d.]+(?=:))|(?:[a-zA-Z]\w+))/; return $host || ''},
 
138
   HostAndDomain     => q{my $host = $set->{Host} || $set->{hostname} || ''; ($host) = $host =~ m/^([^:]+)/; return $host || ''},
 
139
   Port              => q{my ( $port ) = $set->{Host} =~ m/:(.*)$/; return $port || 0},
 
140
   QPS               => q{$set->{Uptime_hires} ? $set->{Questions} / ($set->{Uptime_hires} || 1) : 0},
 
141
   ReplByteLag       => q{defined $set->{Master_Log_File} && $set->{Master_Log_File} eq $set->{Relay_Master_Log_File} ? $set->{Read_Master_Log_Pos} - $set->{Exec_Master_Log_Pos} : 0},
 
142
   OldVersions       => q{dulint_to_int($set->{IB_tx_trx_id_counter}) - dulint_to_int($set->{IB_tx_purge_done_for})},
 
143
   MaxTxnTime        => q{max(map{ $_->{active_secs} } @{$set->{IB_tx_transactions}}) || 0},
 
144
   NumTxns           => q{scalar @{$set->{IB_tx_transactions}} },
 
145
   DirtyBufs         => q{ $set->{IB_bp_pages_modified} / ($set->{IB_bp_buf_pool_size} || 1) },
 
146
   BufPoolFill       => q{ $set->{IB_bp_pages_total} / ($set->{IB_bp_buf_pool_size} || 1) },
 
147
);
 
148
foreach my $key ( keys %builtin_exprs ) {
 
149
   my ( $sub, $err ) = compile_expr($builtin_exprs{$key});
 
150
   $exprs{$key} = {
 
151
      func => $sub,
 
152
      text => $builtin_exprs{$key},
 
153
      user => 0,
 
154
      name => $key, # useful for later
 
155
   }
 
156
}
 
157
 
 
158
# ###########################################################################
 
159
# Column definitions {{{3
 
160
# Defines every column in every table. A named column has the following
 
161
# properties:
 
162
#    * hdr    Column header/title
 
163
#    * label  Documentation for humans.
 
164
#    * num    Whether it's numeric (for sorting).
 
165
#    * just   Alignment; generated from num, user-overridable in tbl_meta
 
166
#    * minw, maxw Auto-generated, user-overridable.
 
167
# Values from this hash are just copied to tbl_meta, which is where everything
 
168
# else in the program should read from.
 
169
# ###########################################################################
 
170
 
 
171
my %columns = (
 
172
   active_secs                 => { hdr => 'SecsActive',          num => 1, label => 'Seconds transaction has been active', },
 
173
   add_pool_alloc              => { hdr => 'Add\'l Pool',         num => 1, label => 'Additonal pool allocated' },
 
174
   attempted_op                => { hdr => 'Action',              num => 0, label => 'The action that caused the error' },
 
175
   awe_mem_alloc               => { hdr => 'AWE Memory',          num => 1, label => '[Windows] AWE memory allocated' },
 
176
   binlog_do_db                => { hdr => 'Binlog Do DB',        num => 0, label => 'binlog-do-db setting' },
 
177
   binlog_ignore_db            => { hdr => 'Binlog Ignore DB',    num => 0, label => 'binlog-ignore-db setting' },
 
178
   bps_in                      => { hdr => 'BpsIn',               num => 1, label => 'Bytes per second received by the server', },
 
179
   bps_out                     => { hdr => 'BpsOut',              num => 1, label => 'Bytes per second sent by the server', },
 
180
   buf_free                    => { hdr => 'Free Bufs',           num => 1, label => 'Buffers free in the buffer pool' },
 
181
   buf_pool_hit_rate           => { hdr => 'Hit Rate',            num => 0, label => 'Buffer pool hit rate' },
 
182
   buf_pool_hits               => { hdr => 'Hits',                num => 1, label => 'Buffer pool hits' },
 
183
   buf_pool_reads              => { hdr => 'Reads',               num => 1, label => 'Buffer pool reads' },
 
184
   buf_pool_size               => { hdr => 'Size',                num => 1, label => 'Buffer pool size' },
 
185
   bufs_in_node_heap           => { hdr => 'Node Heap Bufs',      num => 1, label => 'Buffers in buffer pool node heap' },
 
186
   bytes_behind_master         => { hdr => 'ByteLag',             num => 1, label => 'Bytes the slave lags the master in binlog' },
 
187
   cell_event_set              => { hdr => 'Ending?',             num => 1, label => 'Whether the cell event is set' },
 
188
   cell_waiting                => { hdr => 'Waiting?',            num => 1, label => 'Whether the cell is waiting' },
 
189
   child_db                    => { hdr => 'Child DB',            num => 0, label => 'The database of the child table' },
 
190
   child_index                 => { hdr => 'Child Index',         num => 0, label => 'The index in the child table' },
 
191
   child_table                 => { hdr => 'Child Table',         num => 0, label => 'The child table' },
 
192
   cmd                         => { hdr => 'Cmd',                 num => 0, label => 'Type of command being executed', },
 
193
   connect_retry               => { hdr => 'Connect Retry',       num => 1, label => 'Slave connect-retry timeout' },
 
194
   cxn                         => { hdr => 'CXN',                 num => 0, label => 'Connection from which the data came', },
 
195
   db                          => { hdr => 'DB',                  num => 0, label => 'Current database', },
 
196
   dl_txn_num                  => { hdr => 'Num',                 num => 0, label => 'Deadlocked transaction number', },
 
197
   event_set                   => { hdr => 'Evt Set?',            num => 1, label => '[Win32] if a wait event is set', },
 
198
   exec_master_log_pos         => { hdr => 'Exec Master Log Pos', num => 1, label => 'Exec Master Log Position' },
 
199
   fk_name                     => { hdr => 'Constraint',          num => 0, label => 'The name of the FK constraint' },
 
200
   free_list_len               => { hdr => 'Free List Len',       num => 1, label => 'Length of the free list' },
 
201
   has_read_view               => { hdr => 'Rd View',             num => 1, label => 'Whether the transaction has a read view' },
 
202
   hash_searches_s             => { hdr => 'Hash/Sec',            num => 1, label => 'Number of hash searches/sec' },
 
203
   hash_table_size             => { hdr => 'Size',                num => 1, label => 'Number of non-hash searches/sec' },
 
204
   heap_no                     => { hdr => 'Heap',                num => 1, label => 'Heap number' },
 
205
   heap_size                   => { hdr => 'Heap',                num => 1, label => 'Heap size' },
 
206
   host_and_domain             => { hdr => 'Host',                num => 0, label => 'Hostname/IP and domain' },
 
207
   host_and_port               => { hdr => 'Host/IP',             num => 0, label => 'Hostname or IP address, and port number', },
 
208
   host_or_ip                  => { hdr => 'Host',                num => 0, label => 'Hostname or IP address', },
 
209
   hostname                    => { hdr => 'Host',                num => 0, label => 'Hostname' },
 
210
   index                       => { hdr => 'Index',               num => 0, label => 'The index involved' },
 
211
   index_ref                   => { hdr => 'Index Ref',           num => 0, label => 'Index referenced' },
 
212
   info                        => { hdr => 'Query',               num => 0, label => 'Info or the current query', },
 
213
   insert_intention            => { hdr => 'Ins Intent',          num => 1, label => 'Whether the thread was trying to insert' },
 
214
   inserts                     => { hdr => 'Inserts',             num => 1, label => 'Inserts' },
 
215
   io_bytes_s                  => { hdr => 'Bytes/Sec',           num => 1, label => 'Average I/O bytes/sec' },
 
216
   io_flush_type               => { hdr => 'Flush Type',          num => 0, label => 'I/O Flush Type' },
 
217
   io_fsyncs_s                 => { hdr => 'fsyncs/sec',          num => 1, label => 'I/O fsyncs/sec' },
 
218
   io_reads_s                  => { hdr => 'Reads/Sec',           num => 1, label => 'Average I/O reads/sec' },
 
219
   io_writes_s                 => { hdr => 'Writes/Sec',          num => 1, label => 'Average I/O writes/sec' },
 
220
   ip                          => { hdr => 'IP',                  num => 0, label => 'IP address' },
 
221
   is_name_locked              => { hdr => 'Locked',              num => 1, label => 'Whether table is name locked', },
 
222
   key_buffer_hit              => { hdr => 'KCacheHit',           num => 1, label => 'Key cache hit ratio', },
 
223
   key_len                     => { hdr => 'Key Length',          num => 1, label => 'Number of bytes used in the key' },
 
224
   last_chkp                   => { hdr => 'Last Checkpoint',     num => 0, label => 'Last log checkpoint' },
 
225
   last_errno                  => { hdr => 'Last Errno',          num => 1, label => 'Last error number' },
 
226
   last_error                  => { hdr => 'Last Error',          num => 0, label => 'Last error' },
 
227
   last_s_file_name            => { hdr => 'S-File',              num => 0, label => 'Filename where last read locked' },
 
228
   last_s_line                 => { hdr => 'S-Line',              num => 1, label => 'Line where last read locked' },
 
229
   last_x_file_name            => { hdr => 'X-File',              num => 0, label => 'Filename where last write locked' },
 
230
   last_x_line                 => { hdr => 'X-Line',              num => 1, label => 'Line where last write locked' },
 
231
   lock_cfile_name             => { hdr => 'Crtd File',           num => 0, label => 'Filename where lock created' },
 
232
   lock_cline                  => { hdr => 'Crtd Line',           num => 1, label => 'Line where lock created' },
 
233
   lock_mem_addr               => { hdr => 'Addr',                num => 0, label => 'The lock memory address' },
 
234
   lock_mode                   => { hdr => 'Mode',                num => 0, label => 'The lock mode' },
 
235
   lock_structs                => { hdr => 'LStrcts',             num => 1, label => 'Number of lock structs' },
 
236
   lock_type                   => { hdr => 'Type',                num => 0, label => 'The lock type' },
 
237
   lock_var                    => { hdr => 'Lck Var',             num => 1, label => 'The lock variable' },
 
238
   lock_wait_time              => { hdr => 'Wait',                num => 1, label => 'How long txn has waited for a lock' },
 
239
   log_flushed_to              => { hdr => 'Flushed To',          num => 0, label => 'Log position flushed to' },
 
240
   log_ios_done                => { hdr => 'IO Done',             num => 1, label => 'Log I/Os done' },
 
241
   log_ios_s                   => { hdr => 'IO/Sec',              num => 1, label => 'Average log I/Os per sec' },
 
242
   log_seq_no                  => { hdr => 'Sequence No.',        num => 0, label => 'Log sequence number' },
 
243
   main_thread_id              => { hdr => 'Main Thread ID',      num => 1, label => 'Main thread ID' },
 
244
   main_thread_proc_no         => { hdr => 'Main Thread Proc',    num => 1, label => 'Main thread process number' },
 
245
   main_thread_state           => { hdr => 'Main Thread State',   num => 0, label => 'Main thread state' },
 
246
   master_file                 => { hdr => 'File',                num => 0, label => 'Master file' },
 
247
   master_host                 => { hdr => 'Master',              num => 0, label => 'Master server hostname' },
 
248
   master_log_file             => { hdr => 'Master Log File',     num => 0, label => 'Master log file' },
 
249
   master_port                 => { hdr => 'Master Port',         num => 1, label => 'Master port' },
 
250
   master_pos                  => { hdr => 'Position',            num => 1, label => 'Master position' },
 
251
   master_ssl_allowed          => { hdr => 'Master SSL Allowed',  num => 0, label => 'Master SSL Allowed' },
 
252
   master_ssl_ca_file          => { hdr => 'Master SSL CA File',  num => 0, label => 'Master SSL Cert Auth File' },
 
253
   master_ssl_ca_path          => { hdr => 'Master SSL CA Path',  num => 0, label => 'Master SSL Cert Auth Path' },
 
254
   master_ssl_cert             => { hdr => 'Master SSL Cert',     num => 0, label => 'Master SSL Cert' },
 
255
   master_ssl_cipher           => { hdr => 'Master SSL Cipher',   num => 0, label => 'Master SSL Cipher' },
 
256
   master_ssl_key              => { hdr => 'Master SSL Key',      num => 0, label => 'Master SSL Key' },
 
257
   master_user                 => { hdr => 'Master User',         num => 0, label => 'Master username' },
 
258
   merged_recs                 => { hdr => 'Merged Recs',         num => 1, label => 'Merged records' },
 
259
   merges                      => { hdr => 'Merges',              num => 1, label => 'Merges' },
 
260
   mutex_os_waits              => { hdr => 'Waits',               num => 1, label => 'Mutex OS Waits' },
 
261
   mutex_spin_rounds           => { hdr => 'Rounds',              num => 1, label => 'Mutex Spin Rounds' },
 
262
   mutex_spin_waits            => { hdr => 'Spins',               num => 1, label => 'Mutex Spin Waits' },
 
263
   mysql_thread_id             => { hdr => 'ID',                  num => 1, label => 'MySQL connection (thread) ID', },
 
264
   n_bits                      => { hdr => '# Bits',              num => 1, label => 'Number of bits' },
 
265
   non_hash_searches_s         => { hdr => 'Non-Hash/Sec',        num => 1, label => 'Non-hash searches/sec' },
 
266
   num_deletes                 => { hdr => 'Del',                 num => 1, label => 'Number of deletes' },
 
267
   num_deletes_sec             => { hdr => 'Del/Sec',             num => 1, label => 'Number of deletes' },
 
268
   num_inserts                 => { hdr => 'Ins',                 num => 1, label => 'Number of inserts' },
 
269
   num_inserts_sec             => { hdr => 'Ins/Sec',             num => 1, label => 'Number of inserts' },
 
270
   num_locks                   => { hdr => 'Num Lcks',            num => 1, label => 'Number of locks' },
 
271
   num_readers                 => { hdr => 'Readers',             num => 1, label => 'Number of readers' },
 
272
   num_reads                   => { hdr => 'Read',                num => 1, label => 'Number of reads' },
 
273
   num_reads_sec               => { hdr => 'Read/Sec',            num => 1, label => 'Number of reads' },
 
274
   num_res_ext                 => { hdr => 'BTree Extents',       num => 1, label => 'Number of extents reserved for B-Tree' },
 
275
   num_rows                    => { hdr => 'Row Count',           num => 1, label => 'Number of rows estimated to examine' },
 
276
   num_times_open              => { hdr => 'In Use',              num => 1, label => '# times table is opened', },
 
277
   num_updates                 => { hdr => 'Upd',                 num => 1, label => 'Number of updates' },
 
278
   num_updates_sec             => { hdr => 'Upd/Sec',             num => 1, label => 'Number of updates' },
 
279
   os_file_reads               => { hdr => 'OS Reads',            num => 1, label => 'OS file reads' },
 
280
   os_file_writes              => { hdr => 'OS Writes',           num => 1, label => 'OS file writes' },
 
281
   os_fsyncs                   => { hdr => 'OS fsyncs',           num => 1, label => 'OS fsyncs' },
 
282
   os_thread_id                => { hdr => 'OS Thread',           num => 1, label => 'The operating system thread ID' },
 
283
   p_aio_writes                => { hdr => 'Async Wrt',           num => 1, label => 'Pending asynchronous I/O writes' },
 
284
   p_buf_pool_flushes          => { hdr => 'Buffer Pool Flushes', num => 1, label => 'Pending buffer pool flushes' },
 
285
   p_ibuf_aio_reads            => { hdr => 'IBuf Async Rds',      num => 1, label => 'Pending insert buffer asynch I/O reads' },
 
286
   p_log_flushes               => { hdr => 'Log Flushes',         num => 1, label => 'Pending log flushes' },
 
287
   p_log_ios                   => { hdr => 'Log I/Os',            num => 1, label => 'Pending log I/O operations' },
 
288
   p_normal_aio_reads          => { hdr => 'Async Rds',           num => 1, label => 'Pending asynchronous I/O reads' },
 
289
   p_preads                    => { hdr => 'preads',              num => 1, label => 'Pending p-reads' },
 
290
   p_pwrites                   => { hdr => 'pwrites',             num => 1, label => 'Pending p-writes' },
 
291
   p_sync_ios                  => { hdr => 'Sync I/Os',           num => 1, label => 'Pending synchronous I/O operations' },
 
292
   page_creates_sec            => { hdr => 'Creates/Sec',         num => 1, label => 'Page creates/sec' },
 
293
   page_no                     => { hdr => 'Page',                num => 1, label => 'Page number' },
 
294
   page_reads_sec              => { hdr => 'Reads/Sec',           num => 1, label => 'Page reads per second' },
 
295
   page_writes_sec             => { hdr => 'Writes/Sec',          num => 1, label => 'Page writes per second' },
 
296
   pages_created               => { hdr => 'Created',             num => 1, label => 'Pages created' },
 
297
   pages_modified              => { hdr => 'Dirty Pages',         num => 1, label => 'Pages modified (dirty)' },
 
298
   pages_read                  => { hdr => 'Reads',               num => 1, label => 'Pages read' },
 
299
   pages_total                 => { hdr => 'Pages',               num => 1, label => 'Pages total' },
 
300
   pages_written               => { hdr => 'Writes',              num => 1, label => 'Pages written' },
 
301
   parent_col                  => { hdr => 'Parent Column',       num => 0, label => 'The referred column in the parent table', },
 
302
   parent_db                   => { hdr => 'Parent DB',           num => 0, label => 'The database of the parent table' },
 
303
   parent_index                => { hdr => 'Parent Index',        num => 0, label => 'The referred index in the parent table' },
 
304
   parent_table                => { hdr => 'Parent Table',        num => 0, label => 'The parent table' },
 
305
   part_id                     => { hdr => 'Part ID',             num => 1, label => 'Sub-part ID of the query' },
 
306
   partitions                  => { hdr => 'Partitions',          num => 0, label => 'Query partitions used' },
 
307
   pending_chkp_writes         => { hdr => 'Chkpt Writes',        num => 1, label => 'Pending log checkpoint writes' },
 
308
   pending_log_writes          => { hdr => 'Log Writes',          num => 1, label => 'Pending log writes' },
 
309
   port                        => { hdr => 'Port',                num => 1, label => 'Client port number', },
 
310
   possible_keys               => { hdr => 'Poss. Keys',          num => 0, label => 'Possible keys' },
 
311
   proc_no                     => { hdr => 'Proc',                num => 1, label => 'Process number' },
 
312
   q_cache_hit                 => { hdr => 'QCacheHit',           num => 1, label => 'Query cache hit ratio', },
 
313
   qps                         => { hdr => 'QPS',                 num => 1, label => 'How many queries/sec', },
 
314
   queries_in_queue            => { hdr => 'Queries Queued',      num => 1, label => 'Queries in queue' },
 
315
   queries_inside              => { hdr => 'Queries Inside',      num => 1, label => 'Queries inside InnoDB' },
 
316
   query_id                    => { hdr => 'Query ID',            num => 1, label => 'Query ID' },
 
317
   query_status                => { hdr => 'Query Status',        num => 0, label => 'The query status' },
 
318
   query_text                  => { hdr => 'Query Text',          num => 0, label => 'The query text' },
 
319
   questions                   => { hdr => 'Questions',           num => 1, label => 'How many queries the server has gotten', },
 
320
   read_master_log_pos         => { hdr => 'Read Master Pos',     num => 1, label => 'Read master log position' },
 
321
   read_views_open             => { hdr => 'Rd Views',            num => 1, label => 'Number of read views open' },
 
322
   reads_pending               => { hdr => 'Pending Reads',       num => 1, label => 'Reads pending' },
 
323
   relay_log_file              => { hdr => 'Relay File',          num => 0, label => 'Relay log file' },
 
324
   relay_log_pos               => { hdr => 'Relay Pos',           num => 1, label => 'Relay log position' },
 
325
   relay_log_size              => { hdr => 'Relay Size',          num => 1, label => 'Relay log size' },
 
326
   relay_master_log_file       => { hdr => 'Relay Master File',   num => 0, label => 'Relay master log file' },
 
327
   replicate_do_db             => { hdr => 'Do DB',               num => 0, label => 'Replicate-do-db setting' },
 
328
   replicate_do_table          => { hdr => 'Do Table',            num => 0, label => 'Replicate-do-table setting' },
 
329
   replicate_ignore_db         => { hdr => 'Ignore DB',           num => 0, label => 'Replicate-ignore-db setting' },
 
330
   replicate_ignore_table      => { hdr => 'Ignore Table',        num => 0, label => 'Replicate-do-table setting' },
 
331
   replicate_wild_do_table     => { hdr => 'Wild Do Table',       num => 0, label => 'Replicate-wild-do-table setting' },
 
332
   replicate_wild_ignore_table => { hdr => 'Wild Ignore Table',   num => 0, label => 'Replicate-wild-ignore-table setting' },
 
333
   request_type                => { hdr => 'Type',                num => 0, label => 'Type of lock the thread waits for' },
 
334
   reservation_count           => { hdr => 'ResCnt',              num => 1, label => 'Reservation Count' },
 
335
   row_header                  => { hdr => 'What',                num => 0, label => 'Row header' },
 
336
   rw_excl_os_waits            => { hdr => 'RW Waits',            num => 1, label => 'R/W Excl. OS Waits' },
 
337
   rw_excl_spins               => { hdr => 'RW Spins',            num => 1, label => 'R/W Excl. Spins' },
 
338
   rw_shared_os_waits          => { hdr => 'Sh Waits',            num => 1, label => 'R/W Shared OS Waits' },
 
339
   rw_shared_spins             => { hdr => 'Sh Spins',            num => 1, label => 'R/W Shared Spins' },
 
340
   scan_type                   => { hdr => 'Type',                num => 0, label => 'Scan type in chosen' },
 
341
   seg_size                    => { hdr => 'Seg. Size',           num => 1, label => 'Segment size' },
 
342
   select_type                 => { hdr => 'Select Type',         num => 0, label => 'Type of select used' },
 
343
   signal_count                => { hdr => 'Signals',             num => 1, label => 'Signal Count' },
 
344
   size                        => { hdr => 'Size',                num => 1, label => 'Size of the tablespace' },
 
345
   skip_counter                => { hdr => 'Skip Counter',        num => 1, label => 'Skip counter' },
 
346
   slave_io_running            => { hdr => 'Slave-IO',            num => 0, label => 'Whether the slave I/O thread is running' },
 
347
   slave_io_state              => { hdr => 'Slave IO State',      num => 0, label => 'Slave I/O thread state' },
 
348
   slave_open_temp_tables      => { hdr => 'Temp',                num => 1, label => 'Slave open temp tables' },
 
349
   slave_sql_running           => { hdr => 'Slave-SQL',           num => 0, label => 'Whether the slave SQL thread is running' },
 
350
   slow                        => { hdr => 'Slow',                num => 1, label => 'How many slow queries', },
 
351
   space_id                    => { hdr => 'Space',               num => 1, label => 'Tablespace ID' },
 
352
   special                     => { hdr => 'Special',             num => 0, label => 'Special/Other info' },
 
353
   state                       => { hdr => 'State',               num => 0, label => 'Connection state', },
 
354
   tables_in_use               => { hdr => 'Tbl Used',            num => 1, label => 'Number of tables in use' },
 
355
   tables_locked               => { hdr => 'Tbl Lck',             num => 1, label => 'Number of tables locked' },
 
356
   tbl                         => { hdr => 'Table',               num => 0, label => 'Table', },
 
357
   thread                      => { hdr => 'Thread',              num => 1, label => 'Thread number' },
 
358
   thread_decl_inside          => { hdr => 'Thread Inside',       num => 0, label => 'What the thread is declared inside' },
 
359
   thread_purpose              => { hdr => 'Purpose',             num => 0, label => "The thread's purpose" },
 
360
   thread_status               => { hdr => 'Thread Status',       num => 0, label => 'The thread status' },
 
361
   time                        => { hdr => 'Time',                num => 1, label => 'Time since the last event', },
 
362
   time_behind_master          => { hdr => 'TimeLag',             num => 1, label => 'Time slave lags master' },
 
363
   timestring                  => { hdr => 'Timestring',          num => 0, label => 'Time the event occurred' },
 
364
   total_mem_alloc             => { hdr => 'Memory',              num => 1, label => 'Total memory allocated' },
 
365
   truncates                   => { hdr => 'Trunc',               num => 0, label => 'Whether the deadlock is truncating InnoDB status' },
 
366
   txn_doesnt_see_ge           => { hdr => "Txn Won't See",       num => 0, label => 'Where txn read view is limited' },
 
367
   txn_id                      => { hdr => 'ID',                  num => 0, label => 'Transaction ID' },
 
368
   txn_sees_lt                 => { hdr => 'Txn Sees',            num => 1, label => 'Where txn read view is limited' },
 
369
   txn_status                  => { hdr => 'Txn Status',          num => 0, label => 'Transaction status' },
 
370
   undo_log_entries            => { hdr => 'Undo',                num => 1, label => 'Number of undo log entries' },
 
371
   until_condition             => { hdr => 'Until Condition',     num => 0, label => 'Slave until condition' },
 
372
   until_log_file              => { hdr => 'Until Log File',      num => 0, label => 'Slave until log file' },
 
373
   until_log_pos               => { hdr => 'Until Log Pos',       num => 1, label => 'Slave until log position' },
 
374
   used_cells                  => { hdr => 'Cells Used',          num => 1, label => 'Number of cells used' },
 
375
   user                        => { hdr => 'User',                num => 0, label => 'Database username', },
 
376
   victim                      => { hdr => 'Victim',              num => 0, label => 'Whether this txn was the deadlock victim' },
 
377
   wait_array_size             => { hdr => 'Wait Array Size',     num => 1, label => 'Wait Array Size' },
 
378
   wait_status                 => { hdr => 'Lock Wait?',          num => 0, label => 'Whether txn is waiting for a lock' },
 
379
   waited_at_filename          => { hdr => 'File',                num => 0, label => 'Filename at which thread waits' },
 
380
   waited_at_line              => { hdr => 'Line',                num => 1, label => 'Line at which thread waits' },
 
381
   waiters_flag                => { hdr => 'Waiters',             num => 1, label => 'Waiters Flag' },
 
382
   waiting                     => { hdr => 'Wait',                num => 1, label => 'Whether txn is waiting for a lock' },
 
383
   when                        => { hdr => 'When',                num => 0, label => 'Time scale' },
 
384
   writer_lock_mode            => { hdr => 'Wrtr Lck Mode',       num => 0, label => 'Writer lock mode' },
 
385
   writer_thread               => { hdr => 'Wrtr Thread',         num => 1, label => 'Writer thread ID' },
 
386
   writes_pending              => { hdr => 'Writes',              num => 1, label => 'Number of writes pending' },
 
387
   writes_pending_flush_list   => { hdr => 'Flush List Writes',   num => 1, label => 'Number of flush list writes pending' },
 
388
   writes_pending_lru          => { hdr => 'LRU Writes',          num => 1, label => 'Number of LRU writes pending' },
 
389
   writes_pending_single_page  => { hdr => '1-Page Writes',       num => 1, label => 'Number of 1-page writes pending' },
 
390
);
 
391
 
 
392
# Apply a default property or three.  By default, columns are not width-constrained,
 
393
# aligned left, and sorted alphabetically, not numerically.
 
394
foreach my $col ( values %columns ) {
 
395
   map { $col->{$_} ||= 0 } qw(num minw maxw);
 
396
   $col->{just} = $col->{num} ? '' : '-';
 
397
}
 
398
 
 
399
# Filters {{{3
 
400
# This hash defines every filter that can be applied to a table.  These
 
401
# become part of tbl_meta as well.  Each filter is just an expression that
 
402
# returns true or false, just like values in %exprs.
 
403
# Properties of each entry:
 
404
#  * func:   the subroutine
 
405
#  * name:   the name, repeated
 
406
#  * user:   whether it's a user-defined filter (saved in config)
 
407
#  * text:   text of the subroutine
 
408
#  * note:   explanation
 
409
my %filters = ();
 
410
 
 
411
# These are pre-processed to live in %filters above, by compiling them.
 
412
my %builtin_filters = (
 
413
   hide_self => {
 
414
      text => <<'      END',
 
415
         return ( !$set->{info} || $set->{info} ne 'SHOW FULL PROCESSLIST' )
 
416
             && ( !$set->{query_text}    || $set->{query_text} !~ m/INNODB STATUS$/ );
 
417
      END
 
418
      note => 'Removes the innotop processes from the list',
 
419
      tbls => [qw(innodb_transactions processlist)],
 
420
   },
 
421
   hide_inactive => {
 
422
      text => <<'      END',
 
423
         return ( !defined($set->{txn_status}) || $set->{txn_status} ne 'not started' )
 
424
             && ( !defined($set->{cmd})        || $set->{cmd} !~ m/Sleep|Binlog Dump/ )
 
425
             && ( !defined($set->{info})       || $set->{info} =~ m/\S/               );
 
426
      END
 
427
      note => 'Removes processes which are not doing anything',
 
428
      tbls => [qw(innodb_transactions processlist)],
 
429
   },
 
430
   hide_slave_io => {
 
431
      text => <<'      END',
 
432
         return !$set->{state} || $set->{state} !~ m/^(?:Waiting for master|Has read all relay)/;
 
433
      END
 
434
      note => 'Removes slave I/O threads from the list',
 
435
      tbls => [qw(slave_io_status)],
 
436
   },
 
437
   table_is_open => {
 
438
      text => <<'      END',
 
439
         return $set->{num_times_open} + $set->{is_name_locked};
 
440
      END
 
441
      note => 'Removes tables that are not in use or locked',
 
442
      tbls => [qw(open_tables)],
 
443
   },
 
444
   cxn_is_master => {
 
445
      text => <<'      END',
 
446
         return $set->{master_file} ? 1 : 0;
 
447
      END
 
448
      note => 'Removes servers that are not masters',
 
449
      tbls => [qw(master_status)],
 
450
   },
 
451
   cxn_is_slave => {
 
452
      text => <<'      END',
 
453
         return $set->{master_host} ? 1 : 0;
 
454
      END
 
455
      note => 'Removes servers that are not slaves',
 
456
      tbls => [qw(slave_io_status slave_sql_status)],
 
457
   },
 
458
   thd_is_not_waiting => {
 
459
      text => <<'      END',
 
460
         return $set->{thread_status} !~ m#waiting for i/o request#;
 
461
      END
 
462
      note => 'Removes idle I/O threads',
 
463
      tbls => [qw(io_threads)],
 
464
   },
 
465
);
 
466
foreach my $key ( keys %builtin_filters ) {
 
467
   my ( $sub, $err ) = compile_filter($builtin_filters{$key}->{text});
 
468
   $filters{$key} = {
 
469
      func => $sub,
 
470
      text => $builtin_filters{$key}->{text},
 
471
      user => 0,
 
472
      name => $key, # useful for later
 
473
      note => $builtin_filters{$key}->{note},
 
474
      tbls => $builtin_filters{$key}->{tbls},
 
475
   }
 
476
}
 
477
 
 
478
# Variable sets {{{3
 
479
# Sets (arrayrefs) of variables that are used in V, S, G mode.  They are read/written to
 
480
# the config file.
 
481
my %var_sets = (
 
482
   general => [ qw( Uptime Questions Com_delete Com_delete_multi Com_insert
 
483
            Com_insert_select Com_replace Com_replace_select Com_select
 
484
            Com_update Com_update_multi ) ],
 
485
   query_status => [ qw( Uptime Select_full_join Select_full_range_join Select_range Select_range_check
 
486
            Select_scan Slow_queries Sort_merge_passes Sort_range Sort_rows Sort_scan) ],
 
487
   innodb => [ qw( Uptime Innodb_row_lock_current_waits Innodb_row_lock_time
 
488
            Innodb_row_lock_time_avg Innodb_row_lock_time_max
 
489
            Innodb_row_lock_waits Innodb_rows_deleted Innodb_rows_inserted
 
490
            Innodb_rows_read Innodb_rows_updated) ],
 
491
   txn => [ qw( Uptime Com_begin Com_commit Com_rollback Com_savepoint
 
492
            Com_xa_commit Com_xa_end Com_xa_prepare Com_xa_recover
 
493
            Com_xa_rollback Com_xa_start) ],
 
494
   key_cache => [ qw( Uptime Key_blocks_not_flushed Key_blocks_unused
 
495
            Key_blocks_used Key_read_requests Key_reads Key_write_requests
 
496
            Key_writes ) ],
 
497
   query_cache => [ qw( Uptime Qcache_free_blocks Qcache_free_memory Qcache_hits
 
498
            Qcache_inserts Qcache_lowmem_prunes Qcache_not_cached
 
499
            Qcache_queries_in_cache Qcache_total_blocks ) ],
 
500
   handler => [ qw( Uptime Handler_read_key Handler_read_first Handler_read_next
 
501
            Handler_read_prev Handler_read_rnd Handler_read_rnd_next
 
502
            Handler_delete Handler_update Handler_write) ],
 
503
   cxns_files_threads => [ qw( Uptime Aborted_clients Aborted_connects Bytes_received
 
504
            Bytes_sent Compression Connections Created_tmp_disk_tables
 
505
            Created_tmp_files Created_tmp_tables Max_used_connections
 
506
            Open_files Open_streams Open_tables Opened_tables
 
507
            Table_locks_immediate Table_locks_waited Threads_cached
 
508
            Threads_connected Threads_created Threads_running) ],
 
509
   prep_stmt => [ qw( Uptime Com_dealloc_sql Com_execute_sql Com_prepare_sql
 
510
            Com_reset Com_stmt_close Com_stmt_execute Com_stmt_fetch
 
511
            Com_stmt_prepare Com_stmt_reset Com_stmt_send_long_data ) ],
 
512
   innodb_health => [ qw(OldVersions IB_sm_mutex_spin_waits IB_sm_mutex_spin_rounds
 
513
            IB_sm_mutex_os_waits NumTxns MaxTxnTime IB_ro_queries_inside IB_ro_queries_in_queue
 
514
            DirtyBufs BufPoolFill IB_bp_pages_total IB_bp_pages_read IB_bp_pages_written
 
515
            IB_bp_pages_created) ],
 
516
);
 
517
 
 
518
# Server sets {{{3
 
519
# Defines sets of servers between which the user can quickly switch.
 
520
my %server_groups;
 
521
 
 
522
# Connections {{{3
 
523
# This hash defines server connections.  Each connection is a string that can be passed to
 
524
# the DBI connection.  These are saved in the connections section in the config file.
 
525
# Each has dsn, user, pass, savepass properties.
 
526
my %connections;
 
527
my @conn_parts = qw(user pass dsn savepass dl_table);
 
528
 
 
529
# Graph widths {{{3
 
530
# This hash defines the max values seen for various status/variable values, for graphing.
 
531
# These are stored in their own section in the config file.  These are just initial values:
 
532
my %mvs = (
 
533
   Com_select   => 50,
 
534
   Com_insert   => 50,
 
535
   Com_update   => 50,
 
536
   Com_delete   => 50,
 
537
   Questions    => 100,
 
538
);
 
539
 
 
540
# Table definitions {{{3
 
541
# This hash defines every table that can get displayed in every mode.  Each
 
542
# table specifies columns and column data sources.  The column is
 
543
# defined by the %columns hash.
 
544
#
 
545
# Example: foo => { src => 'bar' } means the foo column (look at
 
546
# $columns{foo} for its definition) gets its data from the 'bar' element of
 
547
# the current data set, whatever that is.
 
548
#
 
549
# Example 2: biz => { src => \%exprs{bat} } means the expression is
 
550
# evaluated for the current data set.
 
551
#
 
552
# These columns are post-processed after being defined, because they get stuff
 
553
# from %columns.  After all the config is loaded for columns, there's more
 
554
# post-processing too; the subroutines compiled from src and expr get added to
 
555
# the hash elements for extract_values to use.
 
556
# ###########################################################################
 
557
 
 
558
my %tbl_meta = (
 
559
   adaptive_hash_index => {
 
560
      hdr  => 'Adaptive Hash Index',
 
561
      cols => {
 
562
         cxn                 => { src => 'cxn' },
 
563
         hash_table_size     => { src => 'IB_ib_hash_table_size', trans => [qw(shorten)], },
 
564
         used_cells          => { src => 'IB_ib_used_cells' },
 
565
         bufs_in_node_heap   => { src => 'IB_ib_bufs_in_node_heap' },
 
566
         hash_searches_s     => { src => 'IB_ib_hash_searches_s' },
 
567
         non_hash_searches_s => { src => 'IB_ib_non_hash_searches_s' },
 
568
      },
 
569
      visible => [ qw(cxn hash_table_size used_cells bufs_in_node_heap hash_searches_s non_hash_searches_s) ],
 
570
      filters => [],
 
571
      sort_cols => 'cxn',
 
572
      sort_dir => '1',
 
573
      innodb   => 'ib',
 
574
   },
 
575
   buffer_pool => {
 
576
      hdr  => 'Buffer Pool',
 
577
      cols => {
 
578
         cxn                        => { src => 'cxn' },
 
579
         total_mem_alloc            => { src => 'IB_bp_total_mem_alloc', trans => [qw(shorten)], },
 
580
         awe_mem_alloc              => { src => 'IB_bp_awe_mem_alloc', trans => [qw(shorten)], },
 
581
         add_pool_alloc             => { src => 'IB_bp_add_pool_alloc', trans => [qw(shorten)], },
 
582
         buf_pool_size              => { src => 'IB_bp_buf_pool_size', trans => [qw(shorten)], },
 
583
         buf_free                   => { src => 'IB_bp_buf_free' },
 
584
         buf_pool_hit_rate          => { src => 'IB_bp_buf_pool_hit_rate' },
 
585
         buf_pool_reads             => { src => 'IB_bp_buf_pool_reads' },
 
586
         buf_pool_hits              => { src => 'IB_bp_buf_pool_hits' },
 
587
         pages_total                => { src => 'IB_bp_pages_total' },
 
588
         pages_modified             => { src => 'IB_bp_pages_modified' },
 
589
         reads_pending              => { src => 'IB_bp_reads_pending' },
 
590
         writes_pending             => { src => 'IB_bp_writes_pending' },
 
591
         writes_pending_lru         => { src => 'IB_bp_writes_pending_lru' },
 
592
         writes_pending_flush_list  => { src => 'IB_bp_writes_pending_flush_list' },
 
593
         writes_pending_single_page => { src => 'IB_bp_writes_pending_single_page' },
 
594
         page_creates_sec           => { src => 'IB_bp_page_creates_sec' },
 
595
         page_reads_sec             => { src => 'IB_bp_page_reads_sec' },
 
596
         page_writes_sec            => { src => 'IB_bp_page_writes_sec' },
 
597
         pages_created              => { src => 'IB_bp_pages_created' },
 
598
         pages_read                 => { src => 'IB_bp_pages_read' },
 
599
         pages_written              => { src => 'IB_bp_pages_written' },
 
600
      },
 
601
      visible => [ qw(cxn buf_pool_size buf_free pages_total pages_modified buf_pool_hit_rate total_mem_alloc add_pool_alloc)],
 
602
      filters => [],
 
603
      sort_cols => 'cxn',
 
604
      sort_dir => '1',
 
605
      innodb   => 'bp',
 
606
   },
 
607
   deadlock_locks => {
 
608
      hdr  => 'Deadlock Locks',
 
609
      cols => {
 
610
         cxn              => { src => 'cxn' },
 
611
         mysql_thread_id  => { src => 'mysql_thread_id' },
 
612
         dl_txn_num       => { src => 'dl_txn_num' },
 
613
         txn_status       => { src => 'txn_status' },
 
614
         lock_type        => { src => 'lock_type' },
 
615
         space_id         => { src => 'space_id' },
 
616
         page_no          => { src => 'page_no' },
 
617
         heap_no          => { src => 'heap_no' },
 
618
         n_bits           => { src => 'n_bits' },
 
619
         index            => { src => 'index' },
 
620
         db               => { src => 'db' },
 
621
         tbl              => { src => 'table' },
 
622
         lock_mode        => { src => 'lock_mode' },
 
623
         special          => { src => 'special' },
 
624
         insert_intention => { src => 'insert_intention' },
 
625
         waiting          => { src => 'waiting' },
 
626
         num_locks        => { src => 'num_locks' },
 
627
      },
 
628
      visible => [ qw(cxn mysql_thread_id txn_status lock_mode db tbl index special insert_intention)],
 
629
      filters => [],
 
630
      sort_cols => 'cxn mysql_thread_id',
 
631
      sort_dir => '1',
 
632
      innodb   => 'dl',
 
633
   },
 
634
   deadlock_transactions => {
 
635
      hdr  => 'Deadlock Transactions',
 
636
      cols => {
 
637
         cxn                => { src => 'cxn' },
 
638
         active_secs        => { src => 'active_secs' },
 
639
         dl_txn_num         => { src => 'dl_txn_num' },
 
640
         has_read_view      => { src => 'has_read_view' },
 
641
         heap_size          => { src => 'heap_size' },
 
642
         host_and_domain    => { src => 'hostname' },
 
643
         hostname           => { src => $exprs{Host}, expr => 'Host' },
 
644
         ip                 => { src => 'ip' },
 
645
         lock_structs       => { src => 'lock_structs' },
 
646
         lock_wait_time     => { src => 'lock_wait_time', trans => [ qw(secs_to_time) ] },
 
647
         mysql_thread_id    => { src => 'mysql_thread_id' },
 
648
         os_thread_id       => { src => 'os_thread_id' },
 
649
         proc_no            => { src => 'proc_no' },
 
650
         query_id           => { src => 'query_id' },
 
651
         query_status       => { src => 'query_status' },
 
652
         query_text         => { src => 'query_text', trans => [ qw(no_ctrl_char) ] },
 
653
         tables_in_use      => { src => 'tables_in_use' },
 
654
         tables_locked      => { src => 'tables_locked' },
 
655
         thread_decl_inside => { src => 'thread_decl_inside' },
 
656
         thread_status      => { src => 'thread_status' },
 
657
         'time'             => { src => 'active_secs', trans => [ qw(secs_to_time) ] },
 
658
         timestring         => { src => 'timestring' },
 
659
         txn_doesnt_see_ge  => { src => 'txn_doesnt_see_ge' },
 
660
         txn_id             => { src => 'txn_id' },
 
661
         txn_sees_lt        => { src => 'txn_sees_lt' },
 
662
         txn_status         => { src => 'txn_status' },
 
663
         truncates          => { src => 'truncates' },
 
664
         undo_log_entries   => { src => 'undo_log_entries' },
 
665
         user               => { src => 'user' },
 
666
         victim             => { src => 'victim' },
 
667
         wait_status        => { src => 'wait_status' },
 
668
      },
 
669
      visible => [ qw(cxn mysql_thread_id timestring user hostname victim time undo_log_entries lock_structs query_text)],
 
670
      filters => [],
 
671
      sort_cols => 'cxn mysql_thread_id',
 
672
      sort_dir => '1',
 
673
      innodb   => 'dl',
 
674
   },
 
675
   explain => {
 
676
      hdr  => 'EXPLAIN Results',
 
677
      cols => {
 
678
         part_id       => { src => 'id' },
 
679
         select_type   => { src => 'select_type' },
 
680
         tbl           => { src => 'table' },
 
681
         partitions    => { src => 'partitions' },
 
682
         scan_type     => { src => 'type' },
 
683
         possible_keys => { src => 'possible_keys' },
 
684
         index         => { src => 'key' },
 
685
         key_len       => { src => 'key_len' },
 
686
         index_ref     => { src => 'ref' },
 
687
         num_rows      => { src => 'rows' },
 
688
         special       => { src => 'Extra' },
 
689
      },
 
690
      visible => [ qw(select_type tbl partitions scan_type possible_keys index key_len index_ref num_rows special)],
 
691
      filters => [],
 
692
      sort_cols => '',
 
693
      sort_dir => '1',
 
694
      innodb   => '',
 
695
   },
 
696
   file_io_misc => {
 
697
      hdr  => 'File I/O Misc',
 
698
      cols => {
 
699
         cxn            => { src => 'cxn' },
 
700
         io_bytes_s     => { src => 'IB_io_avg_bytes_s' },
 
701
         io_flush_type  => { src => 'IB_io_flush_type' },
 
702
         io_fsyncs_s    => { src => 'IB_io_fsyncs_s' },
 
703
         io_reads_s     => { src => 'IB_io_reads_s' },
 
704
         io_writes_s    => { src => 'IB_io_writes_s' },
 
705
         os_file_reads  => { src => 'IB_io_os_file_reads' },
 
706
         os_file_writes => { src => 'IB_io_os_file_writes' },
 
707
         os_fsyncs      => { src => 'IB_io_os_fsyncs' },
 
708
      },
 
709
      visible => [ qw(cxn os_file_reads os_file_writes os_fsyncs io_reads_s io_writes_s io_bytes_s)],
 
710
      filters => [],
 
711
      sort_cols => 'cxn',
 
712
      sort_dir => '1',
 
713
      innodb   => 'io',
 
714
   },
 
715
   fk_error => {
 
716
      hdr  => 'Foreign Key Error Info',
 
717
      cols => {
 
718
         timestring   => { src => 'IB_fk_timestring' },
 
719
         child_db     => { src => 'IB_fk_child_db' },
 
720
         child_table  => { src => 'IB_fk_child_table' },
 
721
         child_index  => { src => 'IB_fk_child_index' },
 
722
         fk_name      => { src => 'IB_fk_fk_name' },
 
723
         parent_db    => { src => 'IB_fk_parent_db' },
 
724
         parent_table => { src => 'IB_fk_parent_table' },
 
725
         parent_col   => { src => 'IB_fk_parent_col' },
 
726
         parent_index => { src => 'IB_fk_parent_index' },
 
727
         attempted_op => { src => 'IB_fk_attempted_op' },
 
728
      },
 
729
      visible => [ qw(timestring child_db child_table child_index parent_db parent_table parent_col parent_index fk_name attempted_op)],
 
730
      filters => [],
 
731
      sort_cols => '',
 
732
      sort_dir => '1',
 
733
      innodb   => 'fk',
 
734
   },
 
735
   insert_buffers => {
 
736
      hdr  => 'Insert Buffers',
 
737
      cols => {
 
738
         cxn           => { src => 'cxn' },
 
739
         inserts       => { src => 'IB_ib_inserts' },
 
740
         merged_recs   => { src => 'IB_ib_merged_recs' },
 
741
         merges        => { src => 'IB_ib_merges' },
 
742
         size          => { src => 'IB_ib_size' },
 
743
         free_list_len => { src => 'IB_ib_free_list_len' },
 
744
         seg_size      => { src => 'IB_ib_seg_size' },
 
745
      },
 
746
      visible => [ qw(cxn inserts merged_recs merges size free_list_len seg_size)],
 
747
      filters => [],
 
748
      sort_cols => 'cxn',
 
749
      sort_dir => '1',
 
750
      innodb   => 'ib',
 
751
   },
 
752
   innodb_transactions => {
 
753
      hdr  => 'InnoDB Transactions',
 
754
      cols => {
 
755
         cxn                => { src => 'cxn' },
 
756
         active_secs        => { src => 'active_secs' },
 
757
         has_read_view      => { src => 'has_read_view' },
 
758
         heap_size          => { src => 'heap_size' },
 
759
         hostname           => { src => $exprs{Host}, expr => 'Host' },
 
760
         ip                 => { src => 'ip' },
 
761
         wait_status        => { src => 'wait_status' },
 
762
         lock_wait_time     => { src => 'lock_wait_time', trans => [ qw(secs_to_time) ] },
 
763
         lock_structs       => { src => 'lock_structs' },
 
764
         mysql_thread_id    => { src => 'mysql_thread_id' },
 
765
         os_thread_id       => { src => 'os_thread_id' },
 
766
         proc_no            => { src => 'proc_no' },
 
767
         query_id           => { src => 'query_id' },
 
768
         query_status       => { src => 'query_status' },
 
769
         query_text         => { src => 'query_text', trans => [ qw(no_ctrl_char) ]  },
 
770
         tables_in_use      => { src => 'tables_in_use' },
 
771
         tables_locked      => { src => 'tables_locked' },
 
772
         thread_decl_inside => { src => 'thread_decl_inside' },
 
773
         thread_status      => { src => 'thread_status' },
 
774
         'time'             => { src => 'active_secs', trans => [ qw(secs_to_time) ] },
 
775
         txn_doesnt_see_ge  => { src => 'txn_doesnt_see_ge' },
 
776
         txn_id             => { src => 'txn_id' },
 
777
         txn_sees_lt        => { src => 'txn_sees_lt' },
 
778
         txn_status         => { src => 'txn_status', minw => 10, maxw => 10 },
 
779
         undo_log_entries   => { src => 'undo_log_entries' },
 
780
         user               => { src => 'user', maxw => 10 },
 
781
      },
 
782
      visible => [ qw(cxn mysql_thread_id user hostname txn_status time undo_log_entries query_text)],
 
783
      filters => [ qw( hide_self hide_inactive ) ],
 
784
      sort_cols => '-active_secs txn_status cxn mysql_thread_id',
 
785
      sort_dir => '1',
 
786
      innodb   => 'tx',
 
787
      hide_hdr => 1,
 
788
      colors   => [
 
789
         { col => 'wait_status', op => '>',  arg => 0,             color => 'black on_red' },
 
790
         { col => 'time',        op => '>',  arg => 600,           color => 'red' },
 
791
         { col => 'time',        op => '>',  arg => 300,           color => 'yellow' },
 
792
         { col => 'time',        op => '>',  arg => 30,            color => 'green' },
 
793
         { col => 'txn_status',  op => 'eq', arg => 'not started', color => 'white' },
 
794
      ],
 
795
   },
 
796
   io_threads => {
 
797
      hdr  => 'I/O Threads',
 
798
      cols => {
 
799
         cxn            => { src => 'cxn' },
 
800
         thread         => { src => 'thread' },
 
801
         thread_purpose => { src => 'purpose' },
 
802
         event_set      => { src => 'event_set' },
 
803
         thread_status  => { src => 'state' },
 
804
      },
 
805
      visible => [ qw(cxn thread thread_purpose thread_status)],
 
806
      filters => [ qw() ],
 
807
      sort_cols => 'cxn thread',
 
808
      sort_dir => '1',
 
809
      innodb   => 'io',
 
810
   },
 
811
   lock_waits => {
 
812
      hdr  => 'Lock Waits',
 
813
      cols => {
 
814
         cxn              => { src => 'cxn' },
 
815
         db               => { src => 'db' },
 
816
         index            => { src => 'index' },
 
817
         insert_intention => { src => 'insert_intention' },
 
818
         lock_mode        => { src => 'lock_mode' },
 
819
         lock_type        => { src => 'lock_type' },
 
820
         lock_wait_time   => { src => 'lock_wait_time', trans => [ qw(secs_to_time) ] },
 
821
         mysql_thread_id  => { src => 'mysql_thread_id' },
 
822
         n_bits           => { src => 'n_bits' },
 
823
         num_locks        => { src => 'num_locks' },
 
824
         page_no          => { src => 'page_no' },
 
825
         space_id         => { src => 'space_id' },
 
826
         special          => { src => 'special' },
 
827
         tbl              => { src => 'table' },
 
828
         'time'           => { src => 'active_secs', hdr => 'Active', trans => [ qw(secs_to_time) ] },
 
829
         txn_id           => { src => 'txn_id' },
 
830
      },
 
831
      visible => [ qw(cxn mysql_thread_id lock_wait_time time lock_mode db tbl index insert_intention special)],
 
832
      filters => [],
 
833
      sort_cols => 'cxn -lock_wait_time',
 
834
      sort_dir => '1',
 
835
      innodb   => 'tx',
 
836
   },
 
837
   log_statistics => {
 
838
      hdr  => 'Log Statistics',
 
839
      cols => {
 
840
         cxn                 => { src => 'cxn' },
 
841
         last_chkp           => { src => 'IB_lg_last_chkp' },
 
842
         log_flushed_to      => { src => 'IB_lg_log_flushed_to' },
 
843
         log_ios_done        => { src => 'IB_lg_log_ios_done' },
 
844
         log_ios_s           => { src => 'IB_lg_log_ios_s' },
 
845
         log_seq_no          => { src => 'IB_lg_log_seq_no' },
 
846
         pending_chkp_writes => { src => 'IB_lg_pending_chkp_writes' },
 
847
         pending_log_writes  => { src => 'IB_lg_pending_log_writes' },
 
848
      },
 
849
      visible => [ qw(cxn log_seq_no log_flushed_to last_chkp log_ios_done log_ios_s)],
 
850
      filters => [],
 
851
      sort_cols => 'cxn',
 
852
      sort_dir => '1',
 
853
      innodb   => 'lg',
 
854
   },
 
855
   master_status => {
 
856
      hdr  => 'Master Status',
 
857
      cols => {
 
858
         cxn                         => { src => 'cxn' },
 
859
         binlog_do_db                => { src => 'Binlog_Do_DB' },
 
860
         binlog_ignore_db            => { src => 'Binlog_Ignore_DB' },
 
861
         master_file                 => { src => 'File' },
 
862
         master_pos                  => { src => 'Position' },
 
863
      },
 
864
      visible => [ qw(cxn master_file master_pos)],
 
865
      filters => [ qw(cxn_is_master) ],
 
866
      sort_cols => 'cxn',
 
867
      sort_dir => '1',
 
868
      innodb   => '',
 
869
   },
 
870
   pending_io => {
 
871
      hdr  => 'Pending I/O',
 
872
      cols => {
 
873
         cxn                => { src => 'cxn' },
 
874
         p_normal_aio_reads => { src => 'IB_io_pending_normal_aio_reads' },
 
875
         p_aio_writes       => { src => 'IB_io_pending_aio_writes' },
 
876
         p_ibuf_aio_reads   => { src => 'IB_io_pending_ibuf_aio_reads' },
 
877
         p_sync_ios         => { src => 'IB_io_pending_sync_ios' },
 
878
         p_buf_pool_flushes => { src => 'IB_io_pending_buffer_pool_flushes' },
 
879
         p_log_flushes      => { src => 'IB_io_pending_log_flushes' },
 
880
         p_log_ios          => { src => 'IB_io_pending_log_ios' },
 
881
         p_preads           => { src => 'IB_io_pending_preads' },
 
882
         p_pwrites          => { src => 'IB_io_pending_pwrites' },
 
883
      },
 
884
      visible => [ qw(cxn p_normal_aio_reads p_aio_writes p_ibuf_aio_reads p_sync_ios p_log_flushes p_log_ios)],
 
885
      filters => [],
 
886
      sort_cols => 'cxn',
 
887
      sort_dir => '1',
 
888
      innodb   => 'io',
 
889
   },
 
890
   open_tables => {
 
891
      hdr  => 'Open Tables',
 
892
      cols => {
 
893
         cxn            => { src => 'cxn' },
 
894
         db             => { src => 'Database' },
 
895
         tbl            => { src => 'Table' },
 
896
         num_times_open => { src => 'In_use' },
 
897
         is_name_locked => { src => 'Name_locked' },
 
898
      },
 
899
      visible => [ qw(cxn db tbl num_times_open is_name_locked)],
 
900
      filters => [ qw(table_is_open) ],
 
901
      sort_cols => '-num_times_open cxn db tbl',
 
902
      sort_dir => '1',
 
903
      innodb   => '',
 
904
   },
 
905
   page_statistics => {
 
906
      hdr  => 'Page Statistics',
 
907
      cols => {
 
908
         cxn              => { src => 'cxn' },
 
909
         pages_read       => { src => 'IB_bp_pages_read' },
 
910
         pages_written    => { src => 'IB_bp_pages_written' },
 
911
         pages_created    => { src => 'IB_bp_pages_created' },
 
912
         page_reads_sec   => { src => 'IB_bp_page_reads_sec' },
 
913
         page_writes_sec  => { src => 'IB_bp_page_writes_sec' },
 
914
         page_creates_sec => { src => 'IB_bp_page_creates_sec' },
 
915
      },
 
916
      visible => [ qw(cxn pages_read pages_written pages_created page_reads_sec page_writes_sec page_creates_sec)],
 
917
      filters => [],
 
918
      sort_cols => 'cxn',
 
919
      sort_dir => '1',
 
920
      innodb   => 'bp',
 
921
   },
 
922
   processlist => {
 
923
      hdr  => 'MySQL Process List',
 
924
      cols => {
 
925
         cxn             => { src => 'cxn',        minw => 6,  maxw => 10 },
 
926
         mysql_thread_id => { src => 'Id',         minw => 6,  maxw => 0 },
 
927
         user            => { src => 'User',       minw => 5,  maxw => 8 },
 
928
         hostname        => { src => $exprs{Host}, minw => 13, maxw => 8, expr => 'Host' },
 
929
         port            => { src => $exprs{Port}, minw => 0,  maxw => 0, expr => 'Port' },
 
930
         host_and_port   => { src => 'Host',       minw => 0,  maxw => 0 },
 
931
         db              => { src => 'db',         minw => 6,  maxw => 12 },
 
932
         cmd             => { src => 'Command',    minw => 5,  maxw => 0 },
 
933
         time            => { src => 'Time',       minw => 5,  maxw => 0, trans => [ qw(secs_to_time) ] },
 
934
         state           => { src => 'State',      minw => 0,  maxw => 0 },
 
935
         info            => { src => 'Info',       minw => 0,  maxw => 0, trans => [ qw(no_ctrl_char) ] },
 
936
      },
 
937
      visible => [ qw(cxn mysql_thread_id user hostname db time info)],
 
938
      filters => [ qw(hide_self hide_inactive hide_slave_io) ],
 
939
      sort_cols => '-time cxn hostname mysql_thread_id',
 
940
      sort_dir => '1',
 
941
      innodb   => '',
 
942
      hide_hdr => 1,
 
943
      colors   => [
 
944
         { col => 'cmd',         op => 'eq', arg => 'Locked',      color => 'red' },
 
945
         { col => 'cmd',         op => 'eq', arg => 'Query',       color => 'yellow' },
 
946
         { col => 'cmd',         op => 'eq', arg => 'Sleep',       color => 'white' },
 
947
         { col => 'user',        op => 'eq', arg => 'system user', color => 'white' },
 
948
         { col => 'cmd',         op => 'eq', arg => 'Connect',     color => 'green' },
 
949
         { col => 'cmd',         op => 'eq', arg => 'Binlog Dump', color => 'white' },
 
950
      ],
 
951
   },
 
952
   q_header => {
 
953
      hdr  => 'Q-mode Header',
 
954
      cols => {
 
955
         cxn            => { src => 'cxn' },
 
956
         questions      => { src => 'Questions' },
 
957
         qps            => { src => 'Questions/Uptime_hires',                       trans => [qw(shorten)] },
 
958
         slow           => { src => 'Slow_queries',                                 trans => [qw(shorten)] },
 
959
         q_cache_hit    => { src => 'Qcache_hits/Com_select',                       trans => [qw(percent)] },
 
960
         key_buffer_hit => { src => '1-(Key_reads/Key_read_requests)',              trans => [qw(percent)] },
 
961
         bps_in         => { src => 'Bytes_received/Uptime_hires',                  trans => [qw(shorten)] },
 
962
         bps_out        => { src => 'Bytes_sent/Uptime_hires',                      trans => [qw(shorten)] },
 
963
         when           => { src => 'when' },
 
964
      },
 
965
      visible => [ qw(cxn when qps slow q_cache_hit key_buffer_hit bps_in bps_out)],
 
966
      filters => [],
 
967
      sort_cols => 'when cxn',
 
968
      sort_dir => '1',
 
969
      innodb   => '',
 
970
      hide_hdr => 1,
 
971
   },
 
972
   row_operations => {
 
973
      hdr  => 'InnoDB Row Operations',
 
974
      cols => {
 
975
         cxn         => { src => 'cxn' },
 
976
         num_inserts => { src => 'IB_ro_num_rows_ins' },
 
977
         num_updates => { src => 'IB_ro_num_rows_upd' },
 
978
         num_reads   => { src => 'IB_ro_num_rows_read' },
 
979
         num_deletes => { src => 'IB_ro_num_rows_del' },
 
980
         num_inserts_sec => { src => 'IB_ro_ins_sec' },
 
981
         num_updates_sec => { src => 'IB_ro_upd_sec' },
 
982
         num_reads_sec   => { src => 'IB_ro_read_sec' },
 
983
         num_deletes_sec => { src => 'IB_ro_del_sec' },
 
984
      },
 
985
      visible => [ qw(cxn num_inserts num_updates num_reads num_deletes num_inserts_sec
 
986
                       num_updates_sec num_reads_sec num_deletes_sec)],
 
987
      filters => [],
 
988
      sort_cols => 'cxn',
 
989
      sort_dir => '1',
 
990
      innodb   => 'ro',
 
991
   },
 
992
   row_operation_misc => {
 
993
      hdr  => 'Row Operation Misc',
 
994
      cols => {
 
995
         cxn                 => { src => 'cxn' },
 
996
         queries_in_queue    => { src => 'IB_ro_queries_in_queue' },
 
997
         queries_inside      => { src => 'IB_ro_queries_inside' },
 
998
         read_views_open     => { src => 'IB_ro_read_views_open' },
 
999
         main_thread_id      => { src => 'IB_ro_main_thread_id' },
 
1000
         main_thread_proc_no => { src => 'IB_ro_main_thread_proc_no' },
 
1001
         main_thread_state   => { src => 'IB_ro_main_thread_state' },
 
1002
         num_res_ext         => { src => 'IB_ro_n_reserved_extents' },
 
1003
      },
 
1004
      visible => [ qw(cxn queries_in_queue queries_inside read_views_open main_thread_state)],
 
1005
      filters => [],
 
1006
      sort_cols => 'cxn',
 
1007
      sort_dir => '1',
 
1008
      innodb   => 'ro',
 
1009
   },
 
1010
   semaphores => {
 
1011
      hdr  => 'InnoDB Semaphores',
 
1012
      cols => {
 
1013
         cxn                => { src => 'cxn' },
 
1014
         mutex_os_waits     => { src => 'IB_sm_mutex_os_waits' },
 
1015
         mutex_spin_rounds  => { src => 'IB_sm_mutex_spin_rounds' },
 
1016
         mutex_spin_waits   => { src => 'IB_sm_mutex_spin_waits' },
 
1017
         reservation_count  => { src => 'IB_sm_reservation_count' },
 
1018
         rw_excl_os_waits   => { src => 'IB_sm_rw_excl_os_waits' },
 
1019
         rw_excl_spins      => { src => 'IB_sm_rw_excl_spins' },
 
1020
         rw_shared_os_waits => { src => 'IB_sm_rw_shared_os_waits' },
 
1021
         rw_shared_spins    => { src => 'IB_sm_rw_shared_spins' },
 
1022
         signal_count       => { src => 'IB_sm_signal_count' },
 
1023
         wait_array_size    => { src => 'IB_sm_wait_array_size' },
 
1024
      },
 
1025
      visible => [ qw(cxn mutex_os_waits mutex_spin_waits mutex_spin_rounds
 
1026
         rw_excl_os_waits rw_excl_spins rw_shared_os_waits rw_shared_spins
 
1027
         signal_count reservation_count )],
 
1028
      filters => [],
 
1029
      sort_cols => 'cxn',
 
1030
      sort_dir => '1',
 
1031
      innodb   => 'sm',
 
1032
   },
 
1033
   slave_io_status => {
 
1034
      hdr  => 'Slave I/O Status',
 
1035
      cols => {
 
1036
         cxn                         => { src => 'cxn' },
 
1037
         connect_retry               => { src => 'Connect_Retry' },
 
1038
         master_host                 => { src => 'Master_Host', hdr => 'Master'},
 
1039
         master_log_file             => { src => 'Master_Log_File', hdr => 'File' },
 
1040
         master_port                 => { src => 'Master_Port' },
 
1041
         master_ssl_allowed          => { src => 'Master_SSL_Allowed' },
 
1042
         master_ssl_ca_file          => { src => 'Master_SSL_CA_File' },
 
1043
         master_ssl_ca_path          => { src => 'Master_SSL_CA_Path' },
 
1044
         master_ssl_cert             => { src => 'Master_SSL_Cert' },
 
1045
         master_ssl_cipher           => { src => 'Master_SSL_Cipher' },
 
1046
         master_ssl_key              => { src => 'Master_SSL_Key' },
 
1047
         master_user                 => { src => 'Master_User' },
 
1048
         read_master_log_pos         => { src => 'Read_Master_Log_Pos', hdr => 'Pos' },
 
1049
         relay_log_size              => { src => 'Relay_Log_Space', trans => [qw(shorten)] },
 
1050
         slave_io_running            => { src => 'Slave_IO_Running', hdr => 'On?' },
 
1051
         slave_io_state              => { src => 'Slave_IO_State', hdr => 'State' },
 
1052
      },
 
1053
      visible => [ qw(cxn master_host slave_io_running master_log_file relay_log_size read_master_log_pos slave_io_state)],
 
1054
      filters => [ qw( cxn_is_slave ) ],
 
1055
      sort_cols => 'slave_io_running cxn',
 
1056
      colors   => [
 
1057
         { col => 'slave_io_running',  op => 'ne', arg => 'Yes', color => 'black on_red' },
 
1058
      ],
 
1059
      sort_dir => '1',
 
1060
      innodb   => '',
 
1061
   },
 
1062
   slave_sql_status => {
 
1063
      hdr  => 'Slave SQL Status',
 
1064
      cols => {
 
1065
         cxn                         => { src => 'cxn' },
 
1066
         exec_master_log_pos         => { src => 'Exec_Master_Log_Pos', hdr => 'Master Pos' },
 
1067
         last_errno                  => { src => 'Last_Errno' },
 
1068
         last_error                  => { src => 'Last_Error' },
 
1069
         master_host                 => { src => 'Master_Host', hdr => 'Master' },
 
1070
         relay_log_file              => { src => 'Relay_Log_File' },
 
1071
         relay_log_pos               => { src => 'Relay_Log_Pos' },
 
1072
         relay_log_size              => { src => 'Relay_Log_Space', trans => [qw(shorten)] },
 
1073
         relay_master_log_file       => { src => 'Relay_Master_Log_File', hdr => 'Master File' },
 
1074
         replicate_do_db             => { src => 'Replicate_Do_DB' },
 
1075
         replicate_do_table          => { src => 'Replicate_Do_Table' },
 
1076
         replicate_ignore_db         => { src => 'Replicate_Ignore_DB' },
 
1077
         replicate_ignore_table      => { src => 'Replicate_Ignore_Table' },
 
1078
         replicate_wild_do_table     => { src => 'Replicate_Wild_Do_Table' },
 
1079
         replicate_wild_ignore_table => { src => 'Replicate_Wild_Ignore_Table' },
 
1080
         skip_counter                => { src => 'Skip_Counter' },
 
1081
         slave_sql_running           => { src => 'Slave_SQL_Running', hdr => 'On?' },
 
1082
         until_condition             => { src => 'Until_Condition' },
 
1083
         until_log_file              => { src => 'Until_Log_File' },
 
1084
         until_log_pos               => { src => 'Until_Log_Pos' },
 
1085
         time_behind_master          => { src => 'Seconds_Behind_Master', trans => [ qw(secs_to_time) ] },
 
1086
         bytes_behind_master         => { src => $exprs{ReplByteLag}, trans => [qw(shorten)], expr => 'ReplByteLag' },
 
1087
         slave_open_temp_tables      => { src => 'Slave_open_temp_tables' },
 
1088
      },
 
1089
      visible => [ qw(cxn master_host slave_sql_running time_behind_master slave_open_temp_tables relay_log_pos last_error)],
 
1090
      filters => [ qw( cxn_is_slave ) ],
 
1091
      sort_cols => 'slave_sql_running cxn',
 
1092
      sort_dir => '1',
 
1093
      innodb   => '',
 
1094
      colors   => [
 
1095
         { col => 'slave_sql_running',  op => 'ne', arg => 'Yes', color => 'black on_red' },
 
1096
         { col => 'time_behind_master', op => '>',  arg => 600,   color => 'red' },
 
1097
         { col => 'time_behind_master', op => '>',  arg => 60,    color => 'yellow' },
 
1098
         { col => 'time_behind_master', op => '==', arg => 0,     color => 'white' },
 
1099
      ],
 
1100
   },
 
1101
   wait_array => {
 
1102
      hdr  => 'InnoDB Wait Array',
 
1103
      cols => {
 
1104
         cxn                => { src => 'cxn' },
 
1105
         thread             => { src => 'thread' },
 
1106
         waited_at_filename => { src => 'waited_at_filename' },
 
1107
         waited_at_line     => { src => 'waited_at_line' },
 
1108
         'time'             => { src => 'waited_secs', trans => [ qw(secs_to_time) ] },
 
1109
         request_type       => { src => 'request_type' },
 
1110
         lock_mem_addr      => { src => 'lock_mem_addr' },
 
1111
         lock_cfile_name    => { src => 'lock_cfile_name' },
 
1112
         lock_cline         => { src => 'lock_cline' },
 
1113
         writer_thread      => { src => 'writer_thread' },
 
1114
         writer_lock_mode   => { src => 'writer_lock_mode' },
 
1115
         num_readers        => { src => 'num_readers' },
 
1116
         lock_var           => { src => 'lock_var' },
 
1117
         waiters_flag       => { src => 'waiters_flag' },
 
1118
         last_s_file_name   => { src => 'last_s_file_name' },
 
1119
         last_s_line        => { src => 'last_s_line' },
 
1120
         last_x_file_name   => { src => 'last_x_file_name' },
 
1121
         last_x_line        => { src => 'last_x_line' },
 
1122
         cell_waiting       => { src => 'cell_waiting' },
 
1123
         cell_event_set     => { src => 'cell_event_set' },
 
1124
      },
 
1125
      visible => [ qw(cxn thread time waited_at_filename waited_at_line request_type num_readers lock_var waiters_flag cell_waiting cell_event_set)],
 
1126
      filters => [],
 
1127
      sort_cols => 'cxn -time',
 
1128
      sort_dir => '1',
 
1129
      innodb   => 'sm',
 
1130
   },
 
1131
);
 
1132
 
 
1133
# TODO: V, G, S mode should have a table in tbl_meta
 
1134
 
 
1135
# Initialize %tbl_meta from %columns
 
1136
foreach my $table ( values %tbl_meta ) {
 
1137
   foreach my $col_name ( keys %{$table->{cols}} ) {
 
1138
      my $col_def = $table->{cols}->{$col_name};
 
1139
      die "I can't find a column named '$col_name'" unless $columns{$col_name};
 
1140
 
 
1141
      foreach my $prop ( keys %col_props ) {
 
1142
         # Each column gets non-existing values set from %columns or defaults from %col_props.
 
1143
         if ( !$col_def->{$prop} ) {
 
1144
            $col_def->{$prop}
 
1145
               = defined($columns{$col_name}->{$prop})
 
1146
               ? $columns{$col_name}->{$prop}
 
1147
               : $col_props{$prop};
 
1148
         }
 
1149
      }
 
1150
   }
 
1151
   # Compile sort and color subroutines
 
1152
   $table->{sort_func}  = make_sort_func($table);
 
1153
   $table->{color_func} = make_color_func($table);
 
1154
}
 
1155
 
 
1156
# ###########################################################################
 
1157
# Valid Term::ANSIColor color strings.
 
1158
# ###########################################################################
 
1159
my %ansicolors = map { $_ => 1 }
 
1160
   qw( black blink blue bold clear concealed cyan dark green magenta on_black
 
1161
       on_blue on_cyan on_green on_magenta on_red on_white on_yellow red reset
 
1162
       reverse underline underscore white yellow);
 
1163
 
 
1164
# ###########################################################################
 
1165
# Valid comparison operators for color rules
 
1166
# ###########################################################################
 
1167
my %comp_ops = (
 
1168
   '==' => 'Numeric equality',
 
1169
   '>'  => 'Numeric greater-than',
 
1170
   '<'  => 'Numeric less-than',
 
1171
   '>=' => 'Numeric greater-than/equal',
 
1172
   '<=' => 'Numeric less-than/equal',
 
1173
   '!=' => 'Numeric not-equal',
 
1174
   'eq' => 'String equality',
 
1175
   'gt' => 'String greater-than',
 
1176
   'lt' => 'String less-than',
 
1177
   'ge' => 'String greater-than/equal',
 
1178
   'le' => 'String less-than/equal',
 
1179
   'ne' => 'String not-equal',
 
1180
   '=~' => 'Pattern match',
 
1181
   '!~' => 'Negated pattern match',
 
1182
);
 
1183
 
 
1184
# ###########################################################################
 
1185
# Valid functions for transformations.
 
1186
# ###########################################################################
 
1187
my %trans_funcs = (
 
1188
   shorten      => \&shorten,
 
1189
   secs_to_time => \&secs_to_time,
 
1190
   no_ctrl_char => \&no_ctrl_char,
 
1191
   percent      => \&percent,
 
1192
   commify      => \&commify,
 
1193
   collapse_ws  => \&collapse_ws,
 
1194
   dulint_to_int => \&dulint_to_int,
 
1195
);
 
1196
 
 
1197
# ###########################################################################
 
1198
# Operating modes {{{3
 
1199
# ###########################################################################
 
1200
my %modes = (
 
1201
   B => {
 
1202
      hdr               => 'InnoDB Buf',
 
1203
      note              => 'Shows buffer info from InnoDB',
 
1204
      action_for        => {
 
1205
         i => {
 
1206
            action => sub { toggle_config('status_inc') },
 
1207
            label  => 'Toggle overall/incremental status display',
 
1208
         },
 
1209
      },
 
1210
      display_sub       => \&display_B,
 
1211
      connections       => [],
 
1212
      server_group      => '',
 
1213
      one_connection    => 0,
 
1214
      tables            => [qw(buffer_pool page_statistics insert_buffers adaptive_hash_index)],
 
1215
      visible_tables    => [qw(buffer_pool page_statistics insert_buffers adaptive_hash_index)],
 
1216
   },
 
1217
   D => {
 
1218
      hdr               => 'InnoDB Deadlocks',
 
1219
      note              => 'View InnoDB deadlock information',
 
1220
      action_for        => {
 
1221
         w => {
 
1222
            action => \&create_deadlock,
 
1223
            label  => 'Wipe deadlock status info by creating a deadlock',
 
1224
         },
 
1225
      },
 
1226
      display_sub       => \&display_D,
 
1227
      connections       => [],
 
1228
      server_group      => '',
 
1229
      one_connection    => 0,
 
1230
      tables            => [qw(deadlock_transactions deadlock_locks)],
 
1231
      visible_tables    => [qw(deadlock_transactions deadlock_locks)],
 
1232
   },
 
1233
   F => {
 
1234
      hdr               => 'InnoDB FK Err',
 
1235
      note              => 'View the latest InnoDB foreign key error',
 
1236
      action_for        => {},
 
1237
      display_sub       => \&display_F,
 
1238
      connections       => [],
 
1239
      server_group      => '',
 
1240
      one_connection    => 1,
 
1241
      tables            => [qw(fk_error)],
 
1242
      visible_tables    => [qw(fk_error)],
 
1243
   },
 
1244
   G => {
 
1245
      hdr               => 'Load Graph',
 
1246
      note              => 'Shows query load graph',
 
1247
      action_for        => {
 
1248
         c => {
 
1249
            action => sub {
 
1250
               choose_var_set('G_set');
 
1251
               start_G_mode();
 
1252
            },
 
1253
            label => "Choose which set to display",
 
1254
         },
 
1255
         e => {
 
1256
            action => \&edit_current_var_set,
 
1257
            label  => 'Edit the current set of variables',
 
1258
         },
 
1259
         i => {
 
1260
            action => sub { $clear_screen_sub->(); toggle_config('status_inc') },
 
1261
            label  => 'Toggle overall/incremental status display',
 
1262
         },
 
1263
      },
 
1264
      display_sub       => \&display_G,
 
1265
      no_clear_screen   => 1,
 
1266
      connections       => [],
 
1267
      server_group      => '',
 
1268
      one_connection    => 1,
 
1269
      tables            => [qw()],
 
1270
      visible_tables    => [qw()],
 
1271
   },
 
1272
   I => {
 
1273
      hdr               => 'InnoDB I/O Info',
 
1274
      note              => 'Shows I/O info (i/o, log...) from InnoDB',
 
1275
      action_for        => {
 
1276
         i => {
 
1277
            action => sub { toggle_config('status_inc') },
 
1278
            label  => 'Toggle overall/incremental status display',
 
1279
         },
 
1280
      },
 
1281
      display_sub       => \&display_I,
 
1282
      connections       => [],
 
1283
      server_group      => '',
 
1284
      one_connection    => 0,
 
1285
      tables            => [qw(io_threads pending_io file_io_misc log_statistics)],
 
1286
      visible_tables    => [qw(io_threads pending_io file_io_misc log_statistics)],
 
1287
   },
 
1288
   M => {
 
1289
      hdr               => 'Replication Status',
 
1290
      note              => 'Shows replication (master and slave) status',
 
1291
      action_for        => {
 
1292
         a => {
 
1293
            action => sub { send_cmd_to_servers('START SLAVE', 1, 'START SLAVE SQL_THREAD UNTIL MASTER_LOG_FILE = ?, MASTER_LOG_POS = ?'); },
 
1294
            label  => 'Start slave(s)',
 
1295
         },
 
1296
         i => {
 
1297
            action => sub { toggle_config('status_inc') },
 
1298
            label  => 'Toggle overall/incremental status display',
 
1299
         },
 
1300
         o => {
 
1301
            action => sub { send_cmd_to_servers('STOP SLAVE', 1); },
 
1302
            label  => 'Stop slave(s)',
 
1303
         },
 
1304
      },
 
1305
      display_sub       => \&display_M,
 
1306
      connections       => [],
 
1307
      server_group      => '',
 
1308
      one_connection    => 0,
 
1309
      tables            => [qw(slave_sql_status slave_io_status master_status)],
 
1310
      visible_tables    => [qw(slave_sql_status slave_io_status master_status)],
 
1311
   },
 
1312
   O => {
 
1313
      hdr               => 'Open Tables',
 
1314
      note              => 'Shows open tables in MySQL',
 
1315
      action_for        => {
 
1316
         c => {
 
1317
            action => sub { get_config_interactive('O_fmt'); },
 
1318
            label => "Choose which columns to display",
 
1319
         },
 
1320
         r => {
 
1321
            action => sub { reverse_sort('open_tables'); },
 
1322
            label  => 'Reverse sort order',
 
1323
         },
 
1324
         s => {
 
1325
            action => sub { choose_sort_cols('open_tables'); },
 
1326
            label => "Choose sort column",
 
1327
         },
 
1328
      },
 
1329
      display_sub       => \&display_O,
 
1330
      connections       => [],
 
1331
      server_group      => '',
 
1332
      one_connection    => 0,
 
1333
      tables            => [qw(open_tables)],
 
1334
      visible_tables    => [qw(open_tables)],
 
1335
   },
 
1336
   Q => {
 
1337
      hdr        => 'Query List',
 
1338
      note       => 'Shows queries from SHOW FULL PROCESSLIST',
 
1339
      action_for => {
 
1340
         a => {
 
1341
            action => sub { toggle_filter('processlist', 'hide_self') },
 
1342
            label  => 'Toggle hiding the innotop process',
 
1343
         },
 
1344
         c => {
 
1345
            action => sub { choose('Q_fmt'); },
 
1346
            label => "Choose which columns to display",
 
1347
         },
 
1348
         e => {
 
1349
            action => sub { analyze_query('e'); },
 
1350
            label  => "Explain a thread's query",
 
1351
         },
 
1352
         f => {
 
1353
            action => sub { analyze_query('f'); },
 
1354
            label  => "Show a thread's full query",
 
1355
         },
 
1356
         h => {
 
1357
            action => sub { toggle_config('show_QT_header') },
 
1358
            label  => 'Toggle the header on and off',
 
1359
         },
 
1360
         i => {
 
1361
            action => sub { toggle_filter('processlist', 'hide_inactive') },
 
1362
            label  => 'Toggle showing or hiding idle (Sleep) processes',
 
1363
         },
 
1364
         k => {
 
1365
            action => sub { kill_query('CONNECTION') },
 
1366
            label => "Kill a query's connection",
 
1367
         },
 
1368
         r => {
 
1369
            action => sub { reverse_sort('processlist'); },
 
1370
            label  => 'Reverse sort order',
 
1371
         },
 
1372
         s => {
 
1373
            action => sub { choose_sort_cols('processlist'); },
 
1374
            label => "Change the display's sort column",
 
1375
         },
 
1376
         x => {
 
1377
            action => sub { kill_query('QUERY') },
 
1378
            label => "Kill a query (not the connection; requires 5.0)",
 
1379
         },
 
1380
      },
 
1381
      display_sub       => \&display_Q,
 
1382
      connections       => [],
 
1383
      server_group      => '',
 
1384
      one_connection    => 0,
 
1385
      tables            => [qw(q_header processlist)],
 
1386
      visible_tables    => [qw(q_header processlist)],
 
1387
   },
 
1388
   R => {
 
1389
      hdr               => 'InnoDB Row Ops',
 
1390
      note              => 'Shows InnoDB row operation and semaphore info',
 
1391
      action_for        => {
 
1392
         i => {
 
1393
            action => sub { toggle_config('status_inc') },
 
1394
            label  => 'Toggle overall/incremental status display',
 
1395
         },
 
1396
      },
 
1397
      display_sub       => \&display_R,
 
1398
      connections       => [],
 
1399
      server_group      => '',
 
1400
      one_connection    => 0,
 
1401
      tables            => [qw(row_operations row_operation_misc semaphores wait_array)],
 
1402
      visible_tables    => [qw(row_operations row_operation_misc semaphores wait_array)],
 
1403
   },
 
1404
   S => {
 
1405
      hdr               => 'Load Stats',
 
1406
      note              => 'Shows query load statistics a la vmstat',
 
1407
      action_for        => {
 
1408
         c => {
 
1409
            action => sub {
 
1410
               choose_var_set('S_set');
 
1411
               start_S_mode();
 
1412
            },
 
1413
            label => "Choose which set to display",
 
1414
         },
 
1415
         e => {
 
1416
            action => \&edit_current_var_set,
 
1417
            label  => 'Edit the current set of variables',
 
1418
         },
 
1419
         i => {
 
1420
            action => sub { $clear_screen_sub->(); toggle_config('status_inc') },
 
1421
            label  => 'Toggle overall/incremental status display',
 
1422
         },
 
1423
         '-' => {
 
1424
            action => sub { set_display_precision(-1) },
 
1425
            label  => 'Decrease fractional display precision',
 
1426
         },
 
1427
         '+' => {
 
1428
            action => sub { set_display_precision(1) },
 
1429
            label  => 'Increase fractional display precision',
 
1430
         },
 
1431
      },
 
1432
      display_sub       => \&display_S,
 
1433
      no_clear_screen   => 1,
 
1434
      connections       => [],
 
1435
      server_group      => '',
 
1436
      one_connection    => 0,
 
1437
      tables            => [qw()],
 
1438
      visible_tables    => [qw()],
 
1439
   },
 
1440
   T => {
 
1441
      hdr        => 'InnoDB Txns',
 
1442
      note       => 'Shows InnoDB transactions in top-like format',
 
1443
      action_for => {
 
1444
         a => {
 
1445
            action => sub { toggle_filter('innodb_transactions', 'hide_self') },
 
1446
            label  => 'Toggle hiding the innotop process',
 
1447
         },
 
1448
         c => {
 
1449
            action => sub { get_config_interactive('T_fmt'); },
 
1450
            label => "Choose which columns to display",
 
1451
         },
 
1452
         h => {
 
1453
            action => sub { toggle_config('show_QT_header') },
 
1454
            label  => 'Toggle the header on and off',
 
1455
         },
 
1456
         i => {
 
1457
            action => sub { toggle_filter('innodb_transactions', 'hide_inactive') },
 
1458
            label  => 'Toggle showing or hiding inactive transactions',
 
1459
         },
 
1460
         k => {
 
1461
            action => sub { kill_query('CONNECTION') },
 
1462
            label  => "Kill a transaction's connection",
 
1463
         },
 
1464
         r => {
 
1465
            action => sub { reverse_sort('innodb_transactions'); },
 
1466
            label  => 'Reverse sort order',
 
1467
         },
 
1468
         s => {
 
1469
            action => sub { choose_sort_cols('innodb_transactions'); },
 
1470
            label  => "Change the display's sort column",
 
1471
         },
 
1472
         x => {
 
1473
            action => sub { kill_query('QUERY') },
 
1474
            label  => "Kill a query, not a connection (requires 5.0)",
 
1475
         },
 
1476
      },
 
1477
      display_sub       => \&display_T,
 
1478
      connections       => [],
 
1479
      server_group      => '',
 
1480
      one_connection    => 0,
 
1481
      tables            => [qw(innodb_transactions)],
 
1482
      visible_tables    => [qw(innodb_transactions)],
 
1483
   },
 
1484
   V => {
 
1485
      hdr               => 'Variables & Status',
 
1486
      note              => 'Shows values from SHOW STATUS and SHOW VARIABLES',
 
1487
      action_for        => {
 
1488
         c => {
 
1489
            action => sub { choose_var_set('V_set') },
 
1490
            label  => 'Choose which set to display',
 
1491
         },
 
1492
         e => {
 
1493
            action => \&edit_current_var_set,
 
1494
            label  => 'Edit the current set of variables',
 
1495
         },
 
1496
         i => {
 
1497
            action => sub { toggle_config('status_inc') },
 
1498
            label  => 'Toggle overall/incremental status display',
 
1499
         },
 
1500
         '-' => {
 
1501
            action => sub { set_display_precision(-1) },
 
1502
            label  => 'Decrease fractional display precision',
 
1503
         },
 
1504
         '+' => {
 
1505
            action => sub { set_display_precision(1) },
 
1506
            label  => 'Increase fractional display precision',
 
1507
         },
 
1508
      },
 
1509
      display_sub    => \&display_V,
 
1510
      connections    => [],
 
1511
      server_group   => '',
 
1512
      one_connection => 1,
 
1513
      tables            => [qw()],
 
1514
      visible_tables    => [qw()],
 
1515
   },
 
1516
   W => {
 
1517
      hdr             => 'InnoDB Lock Waits',
 
1518
      note            => 'Shows transaction lock waits and OS wait array info',
 
1519
      action_for      => {
 
1520
         c => {
 
1521
            action => sub { get_config_interactive('W_fmt') },
 
1522
            label  => 'Choose which columns to show in the lock waits table',
 
1523
         },
 
1524
      },
 
1525
      display_sub     => \&display_W,
 
1526
      connections     => [],
 
1527
      server_group    => '',
 
1528
      one_connection  => 0,
 
1529
      tables            => [qw(lock_waits wait_array)],
 
1530
      visible_tables    => [qw(lock_waits wait_array)],
 
1531
   },
 
1532
);
 
1533
 
 
1534
# ###########################################################################
 
1535
# Global key mappings {{{3
 
1536
# Keyed on a single character, which is read from the keyboard.  Uppercase
 
1537
# letters switch modes.  Lowercase letters access commands when in a mode.
 
1538
# These can be overridden by action_for in %modes.
 
1539
# ###########################################################################
 
1540
my %action_for = (
 
1541
   '$' => {
 
1542
      action => \&edit_configuration,
 
1543
      label  => 'Edit configuration settings',
 
1544
   },
 
1545
   '?' => {
 
1546
      action => \&display_help,
 
1547
      label  => 'Show help',
 
1548
   },
 
1549
   '!' => {
 
1550
      action => \&display_license,
 
1551
      label  => 'Show license and warranty information',
 
1552
   },
 
1553
   '^' => {
 
1554
      action => \&edit_table,
 
1555
      label  => "Edit columns, etc in the displayed table(s)",
 
1556
   },
 
1557
   '#' => {
 
1558
      action => \&choose_server_groups,
 
1559
      label  => 'Select/create server groups',
 
1560
   },
 
1561
   '@' => {
 
1562
      action => \&choose_servers,
 
1563
      label  => 'Select/create server connections',
 
1564
   },
 
1565
   "\t" => {
 
1566
      action => \&next_server_group,
 
1567
      label  => 'Switch to the next server group',
 
1568
      key    => 'TAB',
 
1569
   },
 
1570
   B => {
 
1571
      action => sub { switch_mode('B') },
 
1572
      label  => 'Switch to B mode (InnoDB Buffer/Hash Index)',
 
1573
   },
 
1574
   D => {
 
1575
      action => sub { switch_mode('D') },
 
1576
      label  => 'Switch to D mode (InnoDB Deadlock Information)',
 
1577
   },
 
1578
   F => {
 
1579
      action => sub { switch_mode('F') },
 
1580
      label  => 'Switch to F mode (InnoDB Foreign Key Error)',
 
1581
   },
 
1582
   G => {
 
1583
      action => \&start_G_mode,
 
1584
      label  => 'Switch to G mode (Load Graph)',
 
1585
   },
 
1586
   I => {
 
1587
      action => sub { switch_mode('I') },
 
1588
      label  => 'Switch to I mode (InnoDB I/O and Log)',
 
1589
   },
 
1590
   M => {
 
1591
      action => sub { switch_mode('M') },
 
1592
      label  => 'Switch to M mode (MySQL Replication Status)',
 
1593
   },
 
1594
   O => {
 
1595
      action => sub { switch_mode('O') },
 
1596
      label  => 'Switch to O mode (MySQL Open Tables)',
 
1597
   },
 
1598
   Q => {
 
1599
      action => sub { switch_mode('Q') },
 
1600
      label  => 'Switch to Q mode (Query List, like mytop)',
 
1601
   },
 
1602
   R => {
 
1603
      action => sub { switch_mode('R') },
 
1604
      label  => 'Switch to R mode (InnoDB Row Operations)',
 
1605
   },
 
1606
   S => {
 
1607
      action => \&start_S_mode,
 
1608
      label  => 'Switch to S mode (Load Statistics)',
 
1609
   },
 
1610
   T => {
 
1611
      action => sub { switch_mode('T') },
 
1612
      label  => 'Switch to T mode (InnoDB Transaction)',
 
1613
   },
 
1614
   V => {
 
1615
      action => sub { switch_mode('V') },
 
1616
      label  => 'Switch to V mode (Variable & Status)',
 
1617
   },
 
1618
   W => {
 
1619
      action => sub { switch_mode('W') },
 
1620
      label  => 'Switch to W mode (InnoDB Lock Waits and OS Wait Info)',
 
1621
   },
 
1622
   d => {
 
1623
      action => sub { get_config_interactive('interval') },
 
1624
      label  => 'Change refresh interval',
 
1625
   },
 
1626
   p => { action => \&pause,             label => 'Pause innotop', },
 
1627
   q => { action => \&finish,            label => 'Quit innotop', },
 
1628
);
 
1629
 
 
1630
# ###########################################################################
 
1631
# Config editor key mappings {{{3
 
1632
# ###########################################################################
 
1633
my %cfg_editor_action = (
 
1634
   c => {
 
1635
      note => 'Edit columns, etc in the displayed table(s)',
 
1636
      func => \&edit_table,
 
1637
   },
 
1638
   g => {
 
1639
      note => 'Edit general configuration',
 
1640
      func => \&edit_configuration_variables,
 
1641
   },
 
1642
   k => {
 
1643
      note => 'Edit row-coloring rules',
 
1644
      func => \&edit_color_rules,
 
1645
   },
 
1646
   s => {
 
1647
      note => 'Edit server groups',
 
1648
      func => \&edit_server_groups,
 
1649
   },
 
1650
   t => {
 
1651
      note => 'Choose which table(s) to display in this mode',
 
1652
      func => \&choose_mode_tables,
 
1653
   },
 
1654
);
 
1655
 
 
1656
# ###########################################################################
 
1657
# Color editor key mappings {{{3
 
1658
# ###########################################################################
 
1659
my %color_editor_action = (
 
1660
   n => {
 
1661
      note => 'Create a new color rule',
 
1662
      func => sub {
 
1663
         my ( $tbl, $idx ) = @_;
 
1664
         my $meta = $tbl_meta{$tbl};
 
1665
 
 
1666
         $clear_screen_sub->();
 
1667
         my $col;
 
1668
         do {
 
1669
            $col = prompt_list(
 
1670
               'Choose the target column for the rule',
 
1671
               '',
 
1672
               sub { return keys %{$meta->{cols}} },
 
1673
               { map { $_ => $meta->{cols}->{$_}->{label} } keys %{$meta->{cols}} });
 
1674
         } while ( !$col );
 
1675
         ( $col ) = grep { $_ } split(/\W+/, $col);
 
1676
         return $idx unless $col && exists $meta->{cols}->{$col};
 
1677
 
 
1678
         $clear_screen_sub->();
 
1679
         my $op;
 
1680
         do {
 
1681
            $op = prompt_list(
 
1682
               'Choose the comparison operator for the rule',
 
1683
               '',
 
1684
               sub { return keys %comp_ops },
 
1685
               { map { $_ => $comp_ops{$_} } keys %comp_ops } );
 
1686
         } until ( $op );
 
1687
         $op =~ s/\s+//g;
 
1688
         return $idx unless $op && exists $comp_ops{$op};
 
1689
 
 
1690
         my $arg;
 
1691
         do {
 
1692
            $arg = prompt('Specify an argument for the comparison');
 
1693
         } until defined $arg;
 
1694
 
 
1695
         my $color;
 
1696
         do {
 
1697
            $color = prompt_list(
 
1698
               'Choose the color(s) the row should be when the rule matches',
 
1699
               '',
 
1700
               sub { return keys %ansicolors },
 
1701
               { map { $_ => $_ } keys %ansicolors } );
 
1702
         } until defined $color;
 
1703
         $color = join(' ', unique(grep { exists $ansicolors{$_} } split(/\W+/, $color)));
 
1704
         return $idx unless $color;
 
1705
 
 
1706
         push @{$tbl_meta{$tbl}->{colors}}, {
 
1707
            col   => $col,
 
1708
            op    => $op,
 
1709
            arg   => $arg,
 
1710
            color => $color
 
1711
         };
 
1712
 
 
1713
         return $idx;
 
1714
      },
 
1715
   },
 
1716
   d => {
 
1717
      note => 'Remove the selected rule',
 
1718
      func => sub {
 
1719
         my ( $tbl, $idx ) = @_;
 
1720
         my @rules = @{ $tbl_meta{$tbl}->{colors} };
 
1721
         return 0 unless @rules > 0 && $idx < @rules && $idx >= 0;
 
1722
         splice(@{$tbl_meta{$tbl}->{colors}}, $idx, 1);
 
1723
         return $idx == @rules ? $#rules : $idx;
 
1724
      },
 
1725
   },
 
1726
   j => {
 
1727
      note => 'Move highlight down one',
 
1728
      func => sub {
 
1729
         my ( $tbl, $idx ) = @_;
 
1730
         my $num_rules = scalar @{$tbl_meta{$tbl}->{colors}};
 
1731
         return ($idx + 1) % $num_rules;
 
1732
      },
 
1733
   },
 
1734
   k => {
 
1735
      note => 'Move highlight up one',
 
1736
      func => sub {
 
1737
         my ( $tbl, $idx ) = @_;
 
1738
         my $num_rules = scalar @{$tbl_meta{$tbl}->{colors}};
 
1739
         return ($idx - 1) % $num_rules;
 
1740
      },
 
1741
   },
 
1742
   '+' => {
 
1743
      note => 'Move selected rule up one',
 
1744
      func => sub {
 
1745
         my ( $tbl, $idx ) = @_;
 
1746
         my $meta = $tbl_meta{$tbl};
 
1747
         my $dest = $idx == 0 ? scalar(@{$meta->{colors}} - 1) : $idx - 1;
 
1748
         my $temp = $meta->{colors}->[$idx];
 
1749
         $meta->{colors}->[$idx]  = $meta->{colors}->[$dest];
 
1750
         $meta->{colors}->[$dest] = $temp;
 
1751
         return $dest;
 
1752
      },
 
1753
   },
 
1754
   '-' => {
 
1755
      note => 'Move selected rule down one',
 
1756
      func => sub {
 
1757
         my ( $tbl, $idx ) = @_;
 
1758
         my $meta = $tbl_meta{$tbl};
 
1759
         my $dest = $idx == scalar(@{$meta->{colors}} - 1) ? 0 : $idx + 1;
 
1760
         my $temp = $meta->{colors}->[$idx];
 
1761
         $meta->{colors}->[$idx]  = $meta->{colors}->[$dest];
 
1762
         $meta->{colors}->[$dest] = $temp;
 
1763
         return $dest;
 
1764
      },
 
1765
   },
 
1766
);
 
1767
 
 
1768
# ###########################################################################
 
1769
# Table editor key mappings {{{3
 
1770
# ###########################################################################
 
1771
my %tbl_editor_action = (
 
1772
   a => {
 
1773
      note => 'Add a column to the table',
 
1774
      func => sub {
 
1775
         my ( $tbl, $col ) = @_;
 
1776
         my @visible_cols = @{ $tbl_meta{$tbl}->{visible} };
 
1777
         my %all_cols     = %{ $tbl_meta{$tbl}->{cols} };
 
1778
         delete @all_cols{@visible_cols};
 
1779
         my $choice = prompt_list(
 
1780
            'Choose a column',
 
1781
            '',
 
1782
            sub { return keys %all_cols; },
 
1783
            { map { $_ => $all_cols{$_}->{label} || $all_cols{$_}->{hdr} } keys %all_cols });
 
1784
         if ( $all_cols{$choice} ) {
 
1785
            push @{$tbl_meta{$tbl}->{visible}}, $choice;
 
1786
            return $choice;
 
1787
         }
 
1788
         return $col;
 
1789
      },
 
1790
   },
 
1791
   n => {
 
1792
      note => 'Create a new column and add it to the table',
 
1793
      func => sub {
 
1794
         my ( $tbl, $col ) = @_;
 
1795
 
 
1796
         $clear_screen_sub->();
 
1797
         print word_wrap("Choose a name for the column.  This name is not displayed, and is only used "
 
1798
               . "for internal reference.  It can only contain lowercase letters, numbers, "
 
1799
               . "and underscores.");
 
1800
         print "\n\n";
 
1801
         do {
 
1802
            $col = prompt("Enter column name");
 
1803
            $col = '' if $col =~ m/[^a-z0-9_]/;
 
1804
         } while ( !$col );
 
1805
 
 
1806
         $clear_screen_sub->();
 
1807
         my $hdr;
 
1808
         do {
 
1809
            $hdr = prompt("Enter column header");
 
1810
         } while ( !$hdr );
 
1811
 
 
1812
         $clear_screen_sub->();
 
1813
         print word_wrap("Choose a source for the column's data.  You can either enter the name of an entry "
 
1814
               . "in the data available to the table (varies by context) or if you want to enter "
 
1815
               . "the name of an expression, specify nothing here.");
 
1816
         print "\n\n";
 
1817
         my ( $src, $sub, $err );
 
1818
         do {
 
1819
            if ( $err ) {
 
1820
               print "Error: $err\n\n";
 
1821
            }
 
1822
            $src = prompt("Enter column source");
 
1823
            if ( $src ) {
 
1824
               ( $sub, $err ) = compile_expr($src, 1);
 
1825
            }
 
1826
         } until ( !$src || !$err);
 
1827
 
 
1828
         my $exp;
 
1829
         if ( !$src ) {
 
1830
            $exp = get_expr();
 
1831
            return $col unless $exp && $exprs{$exp};
 
1832
         }
 
1833
 
 
1834
         $tbl_meta{$tbl}->{cols}->{$col} = {
 
1835
            hdr   => $hdr,
 
1836
            src   => $src,
 
1837
            just  => '-',
 
1838
            num   => 0,
 
1839
            label => 'User-defined',
 
1840
            user  => 1,
 
1841
            tbl   => $tbl,
 
1842
            expr  => $exp ? $exp : '',
 
1843
            minw  => 0,
 
1844
            maxw  => 0,
 
1845
            trans => [],
 
1846
            func  => $sub || $exprs{$exp}->{func},
 
1847
         };
 
1848
 
 
1849
         $tbl_meta{$tbl}->{visible} = [ unique(@{$tbl_meta{$tbl}->{visible}}, $col) ];
 
1850
         return $col;
 
1851
      },
 
1852
   },
 
1853
   d => {
 
1854
      note => 'Remove selected column',
 
1855
      func => sub {
 
1856
         my ( $tbl, $col ) = @_;
 
1857
         my @visible_cols = @{ $tbl_meta{$tbl}->{visible} };
 
1858
         my $idx          = 0;
 
1859
         return $col unless @visible_cols > 1;
 
1860
         while ( $visible_cols[$idx] ne $col ) {
 
1861
            $idx++;
 
1862
         }
 
1863
         $tbl_meta{$tbl}->{visible} = [ grep { $_ ne $col } @visible_cols ];
 
1864
         return $idx == $#visible_cols ? $visible_cols[$idx - 1] : $visible_cols[$idx + 1];
 
1865
      },
 
1866
   },
 
1867
   e => {
 
1868
      note => 'Edit selected column',
 
1869
      func => sub {
 
1870
         my ( $tbl, $col ) = @_;
 
1871
         $clear_screen_sub->();
 
1872
         my $meta = $tbl_meta{$tbl}->{cols}->{$col};
 
1873
         my @prop = qw(hdr label src expr just num minw maxw trans);
 
1874
 
 
1875
         my $answer;
 
1876
         do {
 
1877
            # Do what the user asked...
 
1878
            if ( $answer && grep { $_ eq $answer } @prop ) {
 
1879
               if ( $answer eq 'expr' ) {
 
1880
                  $meta->{expr} = get_expr();
 
1881
               }
 
1882
               else {
 
1883
                  # Some properties are arrays, others scalars.
 
1884
                  my $ini = ref $col_props{$answer} ? join(' ', @{$meta->{$answer}}) : $meta->{$answer};
 
1885
                  my $val = prompt("New value for $answer", undef, $ini);
 
1886
                  $val = [ split(' ', $val) ] if ref($col_props{$answer});
 
1887
                  if ( $answer eq 'trans' ) {
 
1888
                     $val = [ unique(grep{ exists $trans_funcs{$_} } @$val) ];
 
1889
                  }
 
1890
                  @{$meta}{$answer, 'user', 'tbl' } = ( $val, 1, $tbl );
 
1891
                  if ( $answer eq 'src' ) {
 
1892
                     $meta->{expr} = '';
 
1893
                  }
 
1894
               }
 
1895
               if ( $meta->{expr} ) {
 
1896
                  $meta->{src}  = $exprs{$meta->{expr}};
 
1897
               }
 
1898
            }
 
1899
 
 
1900
            my @display_lines = (
 
1901
               '',
 
1902
               "You are editing column $tbl.$col.\n",
 
1903
            );
 
1904
 
 
1905
            push @display_lines, create_table2(
 
1906
               \@prop,
 
1907
               { map { $_ => $_ } @prop },
 
1908
               { map { $_ => ref $meta->{$_} eq 'ARRAY' ? join(' ', @{$meta->{$_}})
 
1909
                           : ref $meta->{$_}            ? '[expression code]'
 
1910
                           :                              $meta->{$_}
 
1911
                     } @prop
 
1912
               },
 
1913
               { sep => '  ' });
 
1914
            draw_screen(\@display_lines, { raw => 1 });
 
1915
            print "\n\n"; # One to add space, one to clear readline artifacts
 
1916
            $answer = prompt('Edit what? (q to quit)');
 
1917
         } while ( $answer ne 'q' );
 
1918
 
 
1919
         return $col;
 
1920
      },
 
1921
   },
 
1922
   j => {
 
1923
      note => 'Move highlight down one',
 
1924
      func => sub {
 
1925
         my ( $tbl, $col ) = @_;
 
1926
         my @visible_cols = @{ $tbl_meta{$tbl}->{visible} };
 
1927
         my $idx          = 0;
 
1928
         while ( $visible_cols[$idx] ne $col ) {
 
1929
            $idx++;
 
1930
         }
 
1931
         return $visible_cols[ ($idx + 1) % @visible_cols ];
 
1932
      },
 
1933
   },
 
1934
   k => {
 
1935
      note => 'Move highlight up one',
 
1936
      func => sub {
 
1937
         my ( $tbl, $col ) = @_;
 
1938
         my @visible_cols = @{ $tbl_meta{$tbl}->{visible} };
 
1939
         my $idx          = 0;
 
1940
         while ( $visible_cols[$idx] ne $col ) {
 
1941
            $idx++;
 
1942
         }
 
1943
         return $visible_cols[ $idx - 1 ];
 
1944
      },
 
1945
   },
 
1946
   '+' => {
 
1947
      note => 'Move selected column up one',
 
1948
      func => sub {
 
1949
         my ( $tbl, $col ) = @_;
 
1950
         my $meta         = $tbl_meta{$tbl};
 
1951
         my @visible_cols = @{$meta->{visible}};
 
1952
         my $idx          = 0;
 
1953
         while ( $visible_cols[$idx] ne $col ) {
 
1954
            $idx++;
 
1955
         }
 
1956
         if ( $idx ) {
 
1957
            $visible_cols[$idx]     = $visible_cols[$idx - 1];
 
1958
            $visible_cols[$idx - 1] = $col;
 
1959
            $meta->{visible}        = \@visible_cols;
 
1960
         }
 
1961
         else {
 
1962
            shift @{$meta->{visible}};
 
1963
            push @{$meta->{visible}}, $col;
 
1964
         }
 
1965
         return $col;
 
1966
      },
 
1967
   },
 
1968
   '-' => {
 
1969
      note => 'Move selected column down one',
 
1970
      func => sub {
 
1971
         my ( $tbl, $col ) = @_;
 
1972
         my $meta         = $tbl_meta{$tbl};
 
1973
         my @visible_cols = @{$meta->{visible}};
 
1974
         my $idx          = 0;
 
1975
         while ( $visible_cols[$idx] ne $col ) {
 
1976
            $idx++;
 
1977
         }
 
1978
         if ( $idx == $#visible_cols ) {
 
1979
            unshift @{$meta->{visible}}, $col;
 
1980
            pop @{$meta->{visible}};
 
1981
         }
 
1982
         else {
 
1983
            $visible_cols[$idx]     = $visible_cols[$idx + 1];
 
1984
            $visible_cols[$idx + 1] = $col;
 
1985
            $meta->{visible}        = \@visible_cols;
 
1986
         }
 
1987
         return $col;
 
1988
      },
 
1989
   },
 
1990
   o => {
 
1991
      note => 'Edit table meta-data (sort column, filters...)',
 
1992
      func => sub {
 
1993
         my ( $tbl, $col ) = @_;
 
1994
         $clear_screen_sub->();
 
1995
         my $meta         = $tbl_meta{$tbl};
 
1996
         my $sort_cols    = $meta->{sort_cols};
 
1997
         my $filters      = $meta->{filters};
 
1998
         my @prop         = qw(filters sort_cols);
 
1999
 
 
2000
         my $answer;
 
2001
         do {
 
2002
            # Do whatever the user asked
 
2003
            if ( $answer && grep { $_ eq $answer } @prop ) {
 
2004
               my $ini = ref $meta->{$answer} ? join(' ', @{$meta->{$answer}}) : $meta->{$answer};
 
2005
               if ( $answer eq 'sort_cols' ) {
 
2006
                  choose_sort_cols($tbl);
 
2007
               }
 
2008
               elsif ( $answer eq 'filters' ) {
 
2009
                  $clear_screen_sub->();
 
2010
                  my $val = prompt_list(
 
2011
                     'Choose filters',
 
2012
                     $ini,
 
2013
                     sub { return keys %filters },
 
2014
                     {
 
2015
                        map  { $_ => $filters{$_}->{note} }
 
2016
                        grep { grep { $tbl eq $_ } @{$filters{$_}->{tbls}} }
 
2017
                        keys %filters
 
2018
                     }
 
2019
                  );
 
2020
 
 
2021
                  my @choices = unique(split(/\s+/, $val));
 
2022
                  foreach my $new ( grep { !exists($filters{$_}) } @choices ) {
 
2023
                     my $answer = prompt("There is no filter called '$new'.  Create it?", undef, 'y');
 
2024
                     if ( $answer eq 'y' ) {
 
2025
                        create_new_filter($new, $tbl);
 
2026
                     }
 
2027
                  }
 
2028
                  @choices = grep { exists $filters{$_} } @choices;
 
2029
                  @choices = grep { grep { $tbl eq $_ } @{$filters{$_}->{tbls}} } @choices;
 
2030
                  $meta->{filters} = [ @choices ];
 
2031
               }
 
2032
            }
 
2033
 
 
2034
            my @display_lines = "You are editing table $tbl.";
 
2035
 
 
2036
            push @display_lines, '', create_caption('Properties', create_table2(
 
2037
               \@prop,
 
2038
               { map { $_ => $_ } @prop },
 
2039
               { map { $_ => ref $meta->{$_} eq 'ARRAY' ? join(' ', @{$meta->{$_}})
 
2040
                           :                              $meta->{$_}
 
2041
                     } @prop
 
2042
               },
 
2043
               { sep => '  ' }));
 
2044
            draw_screen(\@display_lines, { raw => 1 });
 
2045
            print "\n\n"; # One to add space, one to clear readline artifacts
 
2046
            $answer = prompt('Edit what? (q to quit)', undef, undef, sub { return @prop });
 
2047
         } while ( $answer ne 'q' );
 
2048
 
 
2049
         return $col;
 
2050
      },
 
2051
   },
 
2052
);
 
2053
 
 
2054
# ###########################################################################
 
2055
# Global variables and environment {{{2
 
2056
# ###########################################################################
 
2057
 
 
2058
# Set up required stuff for interactive mode...
 
2059
if ( !$opts{n} ) {
 
2060
   require Term::ReadKey;
 
2061
   import Term::ReadKey qw(ReadMode ReadKey);
 
2062
}
 
2063
 
 
2064
my @this_term_size; # w_chars, h_chars, w_pix, h_pix
 
2065
my @last_term_size; # w_chars, h_chars, w_pix, h_pix
 
2066
my $char;
 
2067
my $windows       = $OSNAME =~ m/Win/i;
 
2068
my $have_color    = 0;
 
2069
my $MAX_ULONG     = 4294967295; # 2^32-1
 
2070
my $num_regex     = qr/^[+-]?(?=\d|\.)\d*(?:\.\d+)?(?:E[+-]?\d+|)$/i;
 
2071
my $term          = undef;
 
2072
 
 
2073
if ( !$opts{n} ) {
 
2074
   require Term::ReadLine;
 
2075
   $term = Term::ReadLine->new('innotop');
 
2076
}
 
2077
 
 
2078
# Stores status, variables, innodb status, master/slave status etc.
 
2079
# Keyed on connection name.  Each entry is a hashref of current and past data sets,
 
2080
# keyed on clock tick.
 
2081
my %vars;
 
2082
my %info_gotten = (); # Which things have been retrieved for the current clock tick.
 
2083
 
 
2084
# Stores info on currently displayed queries: cxn, connection ID, query text.
 
2085
my @current_queries;
 
2086
 
 
2087
my $hi_res              = 0;
 
2088
my $lines_printed       = 0;
 
2089
my @innodb_files        = ();
 
2090
my $innodb_file_counter = -1;
 
2091
my $clock               = 0;   # Incremented with every wake-sleep cycle
 
2092
my $clearing_deadlocks  = 0;
 
2093
 
 
2094
# If hi-res time is available, use it.
 
2095
eval {
 
2096
   require Time::HiRes;
 
2097
   import Time::HiRes qw(time sleep);
 
2098
   $hi_res   = 1;
 
2099
};
 
2100
 
 
2101
# Find the home directory; it's different on different OSes.
 
2102
my $homepath = $ENV{HOME} || $ENV{HOMEPATH} || $ENV{USERPROFILE} || '.';
 
2103
 
 
2104
# If terminal coloring is available, use it.  The only function I want from
 
2105
# the module is the colored() function.
 
2106
eval {
 
2107
   if ( !$opts{n} ) {
 
2108
      if ( $windows ) {
 
2109
         require Win32::Console::ANSI;
 
2110
      }
 
2111
      require Term::ANSIColor;
 
2112
      import Term::ANSIColor qw(colored);
 
2113
      $have_color = 1;
 
2114
   }
 
2115
};
 
2116
if ( $EVAL_ERROR ) {
 
2117
   # If there was an error, manufacture my own colored() function that does no
 
2118
   # coloring.
 
2119
   *colored = sub { return shift; };
 
2120
}
 
2121
 
 
2122
if ( $opts{n} ) {
 
2123
   $clear_screen_sub = sub {};
 
2124
}
 
2125
elsif ( $windows ) {
 
2126
   $clear_screen_sub = sub { $lines_printed = 0; system("cls") };
 
2127
}
 
2128
else {
 
2129
   my $clear = `clear`;
 
2130
   $clear_screen_sub = sub { $lines_printed = 0; print $clear };
 
2131
}
 
2132
 
 
2133
# ###########################################################################
 
2134
# Config storage. {{{2
 
2135
# ###########################################################################
 
2136
my %config = (
 
2137
   show_cxn_errors_in_tbl => {
 
2138
      val  => 1,
 
2139
      note => 'Whether to display connection errors at the end of every table',
 
2140
      conf => [ 'ALL' ],
 
2141
      pat  => qr/^[01]$/,
 
2142
   },
 
2143
   show_cxn_errors => {
 
2144
      val  => 1,
 
2145
      note => 'Whether to print connection errors to STDOUT',
 
2146
      conf => [ 'ALL' ],
 
2147
      pat  => qr/^[01]$/,
 
2148
   },
 
2149
   readonly => {
 
2150
      val  => 0,
 
2151
      note => 'Whether the config file is read-only',
 
2152
      conf => [ qw() ],
 
2153
      pat  => qr/^[01]$/,
 
2154
   },
 
2155
   global => {
 
2156
      val  => 1,
 
2157
      note => 'Whether to show GLOBAL variables and status',
 
2158
      conf => 'ALL',
 
2159
      pat  => qr/^[01]$/,
 
2160
   },
 
2161
   header_highlight => {
 
2162
      val  => 'bold',
 
2163
      note => 'How to highlight table column headers',
 
2164
      conf => 'ALL',
 
2165
      pat  => qr/^(?:bold|underline)$/,
 
2166
   },
 
2167
   display_table_captions => {
 
2168
      val  => 1,
 
2169
      note => 'Whether to put captions on tables',
 
2170
      conf => 'ALL',
 
2171
      pat  => qr/^[01]$/,
 
2172
   },
 
2173
   compact_hdr => {
 
2174
      val  => 1,
 
2175
      note => 'Whether to compact the headers in some modes',
 
2176
      conf => 'ALL',
 
2177
      pat  => qr/^[01]$/,
 
2178
   },
 
2179
   charset => {
 
2180
      val  => 'ascii',
 
2181
      note => 'What type of characters should be displayed in queries (ascii, unicode, none)',
 
2182
      conf => 'ALL',
 
2183
      pat  => qr/^(?:ascii|unicode|none)$/,
 
2184
   },
 
2185
   auto_wipe_dl => {
 
2186
      val  => 0,
 
2187
      note => 'Whether to auto-wipe InnoDB deadlocks',
 
2188
      conf => 'ALL',
 
2189
      pat  => qr/^[01]$/,
 
2190
   },
 
2191
   max_height => {
 
2192
      val  => 30,
 
2193
      note => '[Win32] Max window height',
 
2194
      conf => 'ALL',
 
2195
   },
 
2196
   debug => {
 
2197
      val  => 0,
 
2198
      pat  => qr/^[01]$/,
 
2199
      note => 'Debug mode (more verbose errors, uses more memory)',
 
2200
      conf => [ qw(D) ],
 
2201
   },
 
2202
   num_digits => {
 
2203
      val  => 2,
 
2204
      pat  => qr/^\d$/,
 
2205
      note => 'How many digits to show in fractional numbers and percents',
 
2206
      conf => 'ALL',
 
2207
   },
 
2208
   show_QT_header => {
 
2209
      val  => 1,
 
2210
      pat  => qr/^[01]$/,
 
2211
      note => 'Whether to show the header in Q and T modes',
 
2212
      conf => [ qw(Q T) ],
 
2213
   },
 
2214
   debugfile => {
 
2215
      val  => "$homepath/.innotop_core_dump",
 
2216
      note => 'A debug file in case you are interested in error output',
 
2217
   },
 
2218
   show_statusbar => {
 
2219
      val  => 1,
 
2220
      pat  => qr/^[01]$/,
 
2221
      note => 'Whether to show the status bar in the display',
 
2222
      conf => 'ALL',
 
2223
   },
 
2224
   mode => {
 
2225
      val  => "T",
 
2226
      note => "Which mode to start in",
 
2227
      cmdline => 1,
 
2228
   },
 
2229
   status_inc => {
 
2230
      val  => 0,
 
2231
      note => 'Whether to show raw or incremental values for status variables',
 
2232
      pat  => qr/^[01]$/,
 
2233
   },
 
2234
   interval => {
 
2235
      val  => 10,
 
2236
      pat  => qr/^(?:(?:\d*?[1-9]\d*(?:\.\d*)?)|(?:\d*\.\d*?[1-9]\d*))$/,
 
2237
      note => "The interval at which the display will be refreshed.  Fractional values allowed.",
 
2238
   },
 
2239
   V_set => {
 
2240
      val  => 'general',
 
2241
      pat  => qr/^\w+$/,
 
2242
      note => 'Which set of variables to display in V (Variables/Status) mode',
 
2243
      conf => [ qw(V) ],
 
2244
   },
 
2245
   num_status_sets => {
 
2246
      val  => 9,
 
2247
      pat  => qr/^\d*?[1-9]\d*$/,
 
2248
      note => 'How many sets of STATUS and VARIABLES values to show',
 
2249
      conf => [ qw(V) ],
 
2250
   },
 
2251
   G_set => {
 
2252
      val  => 'general',
 
2253
      pat  => qr/^\w+$/,
 
2254
      note => 'Which set of variables to display in G (Load Graph) mode',
 
2255
      conf => [ qw(G) ],
 
2256
   },
 
2257
   S_set => {
 
2258
      val  => 'general',
 
2259
      pat  => qr/^\w+$/,
 
2260
      note => 'Which set of variables to display in S (Load Statistics) mode',
 
2261
      conf => [ qw(S) ],
 
2262
   },
 
2263
);
 
2264
 
 
2265
# ###########################################################################
 
2266
# Config file sections {{{2
 
2267
# The configuration file is broken up into sections like a .ini file.  This
 
2268
# variable defines those sections and the subroutines responsible for reading
 
2269
# and writing them.
 
2270
# ###########################################################################
 
2271
my %config_file_sections = (
 
2272
   filters => {
 
2273
      reader => \&load_config_filters,
 
2274
      writer => \&save_config_filters,
 
2275
   },
 
2276
   active_filters => {
 
2277
      reader => \&load_config_active_filters,
 
2278
      writer => \&save_config_active_filters,
 
2279
   },
 
2280
   visible_tables => {
 
2281
      reader => \&load_config_visible_tables,
 
2282
      writer => \&save_config_visible_tables,
 
2283
   },
 
2284
   sort_cols => {
 
2285
      reader => \&load_config_sort_cols,
 
2286
      writer => \&save_config_sort_cols,
 
2287
   },
 
2288
   active_columns => {
 
2289
      reader => \&load_config_active_columns,
 
2290
      writer => \&save_config_active_columns,
 
2291
   },
 
2292
   expressions => {
 
2293
      reader => \&load_config_expressions,
 
2294
      writer => \&save_config_expressions,
 
2295
   },
 
2296
   tbl_meta => {
 
2297
      reader => \&load_config_tbl_meta,
 
2298
      writer => \&save_config_tbl_meta,
 
2299
   },
 
2300
   general => {
 
2301
      reader => \&load_config_config,
 
2302
      writer => \&save_config_config,
 
2303
   },
 
2304
   connections => {
 
2305
      reader => \&load_config_connections,
 
2306
      writer => \&save_config_connections,
 
2307
   },
 
2308
   active_connections => {
 
2309
      reader => \&load_config_active_connections,
 
2310
      writer => \&save_config_active_connections,
 
2311
   },
 
2312
   server_groups => {
 
2313
      reader => \&load_config_server_groups,
 
2314
      writer => \&save_config_server_groups,
 
2315
   },
 
2316
   active_server_groups => {
 
2317
      reader => \&load_config_active_server_groups,
 
2318
      writer => \&save_config_active_server_groups,
 
2319
   },
 
2320
   max_values_seen => {
 
2321
      reader => \&load_config_mvs,
 
2322
      writer => \&save_config_mvs,
 
2323
   },
 
2324
   varsets => {
 
2325
      reader => \&load_config_varsets,
 
2326
      writer => \&save_config_varsets,
 
2327
   },
 
2328
   colors => {
 
2329
      reader => \&load_config_colors,
 
2330
      writer => \&save_config_colors,
 
2331
   },
 
2332
);
 
2333
 
 
2334
# Config file sections have some dependencies, so they have to be read/written in order.
 
2335
my @ordered_config_file_sections = qw(filters active_filters expressions tbl_meta
 
2336
   general connections active_connections server_groups active_server_groups max_values_seen
 
2337
   active_columns sort_cols visible_tables varsets colors);
 
2338
 
 
2339
# ###########################################################################
 
2340
# Contains logic to generate prepared statements for a given function for a
 
2341
# given DB connection.  $cxn is a key in %dbhs.  Returns a SQL string.
 
2342
# ###########################################################################
 
2343
my %stmt_maker_for = (
 
2344
   INNODB_STATUS => sub {
 
2345
      my ( $cxn ) = @_;
 
2346
      my $meta = $dbhs{$cxn};
 
2347
      return ( $meta->{ver_major} >= 5 )
 
2348
             ? 'SHOW ENGINE INNODB STATUS'
 
2349
             : 'SHOW INNODB STATUS';
 
2350
   },
 
2351
   SHOW_VARIABLES => sub {
 
2352
      my ( $cxn ) = @_;
 
2353
      my $meta = $dbhs{$cxn};
 
2354
      return ( $config{global}->{val} && $meta->{ver_major} >= 4 ) && ( $meta->{ver_rev} >= 3 )
 
2355
             ? 'SHOW GLOBAL VARIABLES'
 
2356
             : 'SHOW VARIABLES';
 
2357
   },
 
2358
   SHOW_STATUS => sub {
 
2359
      my ( $cxn ) = @_;
 
2360
      my $meta = $dbhs{$cxn};
 
2361
      return ( $config{global}->{val} && $meta->{ver_major} >= 5 ) && ( $meta->{ver_rev} >= 2 )
 
2362
             ? 'SHOW GLOBAL STATUS'
 
2363
             : 'SHOW STATUS';
 
2364
   },
 
2365
   KILL_QUERY => sub {
 
2366
      my ( $cxn ) = @_;
 
2367
      my $meta = $dbhs{$cxn};
 
2368
      return ( $meta->{ver_major} >= 5 )
 
2369
             ? 'KILL QUERY ?'
 
2370
             : 'KILL ?';
 
2371
   },
 
2372
   SHOW_MASTER_STATUS => sub {
 
2373
      my ( $cxn ) = @_;
 
2374
      return 'SHOW MASTER STATUS';
 
2375
   },
 
2376
   SHOW_SLAVE_STATUS => sub {
 
2377
      my ( $cxn ) = @_;
 
2378
      return 'SHOW SLAVE STATUS';
 
2379
   },
 
2380
   KILL_CONNECTION => sub {
 
2381
      my ( $cxn ) = @_;
 
2382
      return 'KILL CONNECTION ?';
 
2383
   },
 
2384
   OPEN_TABLES => sub {
 
2385
      my ( $cxn ) = @_;
 
2386
      return 'SHOW OPEN TABLES';
 
2387
   },
 
2388
   PROCESSLIST => sub {
 
2389
      my ( $cxn ) = @_;
 
2390
      return 'SHOW FULL PROCESSLIST';
 
2391
   },
 
2392
);
 
2393
 
 
2394
# ###########################################################################
 
2395
# Run the program {{{1
 
2396
# ###########################################################################
 
2397
 
 
2398
# This config variable is only useful for MS Windows because its terminal
 
2399
# can't tell how tall it is.
 
2400
if ( !$windows ) {
 
2401
   delete $config{max_height};
 
2402
}
 
2403
 
 
2404
# Try to lower my priority.
 
2405
eval { setpriority(0, 0, getpriority(0, 0) + 10); };
 
2406
 
 
2407
# Print stuff to the screen immediately, don't wait for a newline.
 
2408
$OUTPUT_AUTOFLUSH = 1;
 
2409
 
 
2410
# Clear the screen and load the configuration.
 
2411
$clear_screen_sub->();
 
2412
load_config();
 
2413
post_process_tbl_meta();
 
2414
 
 
2415
# Make sure no changes are written to config file in non-interactive mode.
 
2416
if ( $opts{n} ) {
 
2417
   $config{readonly}->{val} = 1;
 
2418
}
 
2419
 
 
2420
eval {
 
2421
   while (++$clock) {
 
2422
 
 
2423
      my $mode = $config{mode}->{val};
 
2424
 
 
2425
      if ( !$opts{n} ) {
 
2426
         @last_term_size = @this_term_size;
 
2427
         @this_term_size = Term::ReadKey::GetTerminalSize(\*STDOUT);
 
2428
         if ( $windows ) {
 
2429
            $this_term_size[0]--;
 
2430
            $this_term_size[1]
 
2431
               = min($this_term_size[1], $config{max_height}->{val});
 
2432
         }
 
2433
         die("Can't read terminal size") unless @this_term_size;
 
2434
      }
 
2435
 
 
2436
      # If there's no connection to a database server, we need to fix that...
 
2437
      if ( !%connections ) {
 
2438
         print "You have not defined any database connections.\n\n";
 
2439
         add_new_dsn();
 
2440
      }
 
2441
 
 
2442
      # See whether there are any connections defined for this mode.  If there's only one
 
2443
      # connection total, assume the user wants to just use innotop for a single server
 
2444
      # and don't ask which server to connect to.
 
2445
      if ( !get_connections() ) {
 
2446
         if ( 1 == scalar keys %connections ) {
 
2447
            $modes{$config{mode}->{val}}->{connections} = [ keys %connections ];
 
2448
         }
 
2449
         else {
 
2450
            choose_connections();
 
2451
         }
 
2452
      }
 
2453
 
 
2454
      # Term::ReadLine might have re-set $OUTPUT_AUTOFLUSH.
 
2455
      $OUTPUT_AUTOFLUSH = 1;
 
2456
 
 
2457
      # Prune old data
 
2458
      my $sets = $config{num_status_sets}->{val};
 
2459
      foreach my $store ( values %vars ) {
 
2460
         delete @{$store}{ grep { $_ < $clock - $sets } keys %$store };
 
2461
      }
 
2462
      %info_gotten = ();
 
2463
 
 
2464
      # Call the subroutine to display this mode.
 
2465
      $modes{$mode}->{display_sub}->();
 
2466
 
 
2467
      # Wait for a bit.
 
2468
      if ( $opts{n} ) {
 
2469
         sleep($config{interval}->{val});
 
2470
      }
 
2471
      else {
 
2472
         ReadMode('cbreak');
 
2473
         $char = ReadKey($config{interval}->{val});
 
2474
         ReadMode('normal');
 
2475
      }
 
2476
 
 
2477
      # Handle whatever action the key indicates.
 
2478
      do_key_action();
 
2479
 
 
2480
   }
 
2481
};
 
2482
if ( $EVAL_ERROR ) {
 
2483
   core_dump( $EVAL_ERROR );
 
2484
}
 
2485
finish();
 
2486
 
 
2487
# Subroutines {{{1
 
2488
# Mode functions{{{2
 
2489
# switch_mode {{{3
 
2490
sub switch_mode {
 
2491
   my $mode = shift;
 
2492
   $config{mode}->{val} = $mode;
 
2493
}
 
2494
 
 
2495
# Prompting functions {{{2
 
2496
# prompt_list {{{3
 
2497
# Prompts the user for a value, given a question, initial value,
 
2498
# a completion function and a hashref of hints.
 
2499
sub prompt_list {
 
2500
   die "Can't call in non-interactive mode" if $opts{n};
 
2501
   my ( $question, $init, $completion, $hints ) = @_;
 
2502
   if ( $hints ) {
 
2503
      # Figure out how wide the table will be
 
2504
      my $max_name = max(map { length($_) } keys %$hints );
 
2505
      $max_name ||= 0;
 
2506
      $max_name +=  3;
 
2507
      my @meta_rows = create_table2(
 
2508
               [ sort keys %$hints ],
 
2509
               { map { $_ => $_ } keys %$hints },
 
2510
               { map { $_ => trunc($hints->{$_}, $this_term_size[0] - $max_name) } keys %$hints },
 
2511
               { sep => '  ' });
 
2512
      if (@meta_rows > 10) {
 
2513
         # Try to split and stack the meta rows next to each other
 
2514
         my $split = int(@meta_rows / 2);
 
2515
         @meta_rows = stack_next(
 
2516
            [@meta_rows[0..$split - 1]],
 
2517
            [@meta_rows[$split..$#meta_rows]],
 
2518
            { pad => ' | '},
 
2519
         );
 
2520
      }
 
2521
      print join( "\n",
 
2522
         '',
 
2523
         map { ref $_ ? colored(@$_) : $_ } create_caption('Choose from', @meta_rows), ''),
 
2524
         "\n";
 
2525
   }
 
2526
   $term->Attribs->{completion_function} = $completion;
 
2527
   my $answer = $term->readline("$question: ", $init);
 
2528
   $OUTPUT_AUTOFLUSH = 1;
 
2529
   $answer = '' if !defined($answer);
 
2530
   $answer =~ s/\s+$//;
 
2531
   return $answer;
 
2532
}
 
2533
 
 
2534
# prompt {{{3
 
2535
# Prints out a prompt and reads from the keyboard, then validates with the
 
2536
# validation regex until the input is correct.
 
2537
sub prompt {
 
2538
   die "Can't call in non-interactive mode" if $opts{n};
 
2539
   my ( $prompt, $regex, $init, $completion ) = @_;
 
2540
   my $response;
 
2541
   my $success = 0;
 
2542
   do {
 
2543
      if ( $completion ) {
 
2544
         $term->Attribs->{completion_function} = $completion;
 
2545
      }
 
2546
      $response = $term->readline("$prompt: ", $init);
 
2547
      if ( $regex && $response !~ m/$regex/ ) {
 
2548
         print "Invalid response.\n\n";
 
2549
      }
 
2550
      else {
 
2551
         $success = 1;
 
2552
      }
 
2553
   } while ( !$success );
 
2554
   $OUTPUT_AUTOFLUSH = 1;
 
2555
   $response =~ s/\s+$//;
 
2556
   return $response;
 
2557
}
 
2558
 
 
2559
# prompt_noecho {{{3
 
2560
# Unfortunately, suppressing echo with Term::ReadLine isn't reliable; the user might not
 
2561
# have that library, or it might not support that feature.
 
2562
sub prompt_noecho {
 
2563
   my ( $prompt ) = @_;
 
2564
   print colored("$prompt: ", 'underline');
 
2565
   my $response;
 
2566
   ReadMode('noecho');
 
2567
   $response = <STDIN>;
 
2568
   chomp($response);
 
2569
   ReadMode('normal');
 
2570
   return $response;
 
2571
}
 
2572
 
 
2573
# do_key_action {{{3
 
2574
# Depending on whether a key was read, do something.  Keys have certain
 
2575
# actions defined in lookup tables.  Each mode may have its own lookup table,
 
2576
# which trumps the global table -- so keys can be context-sensitive.  The key
 
2577
# may be read and written in a subroutine, so it's a global.
 
2578
sub do_key_action {
 
2579
   if ( defined $char ) {
 
2580
      my $mode = $config{mode}->{val};
 
2581
      my $action
 
2582
         = defined($modes{$mode}->{action_for}->{$char})
 
2583
         ? $modes{$mode}->{action_for}->{$char}->{action}
 
2584
         : defined($action_for{$char})
 
2585
         ? $action_for{$char}->{action}
 
2586
         : sub{};
 
2587
      $action->();
 
2588
   }
 
2589
}
 
2590
 
 
2591
# pause {{{3
 
2592
sub pause {
 
2593
   die "Can't call in non-interactive mode" if $opts{n};
 
2594
   my $msg = shift;
 
2595
   print defined($msg) ? "\n$msg" : "\nPress any key to continue";
 
2596
   ReadMode('cbreak');
 
2597
   my $char = ReadKey(0);
 
2598
   ReadMode('normal');
 
2599
   return $char;
 
2600
}
 
2601
 
 
2602
# reverse_sort {{{3
 
2603
sub reverse_sort {
 
2604
   my $tbl = shift;
 
2605
   $tbl_meta{$tbl}->{sort_dir} *= -1;
 
2606
}
 
2607
 
 
2608
# select_cxn {{{3
 
2609
# Selects connection(s).  If the mode (or argument list) has only one, returns
 
2610
# it without prompt.
 
2611
sub select_cxn {
 
2612
   my ( $prompt, @cxns ) = @_;
 
2613
   if ( !@cxns ) {
 
2614
      @cxns = get_connections();
 
2615
   }
 
2616
   if ( @cxns == 1 ) {
 
2617
      return $cxns[0];
 
2618
   }
 
2619
   my $choices = prompt_list(
 
2620
         $prompt,
 
2621
         $cxns[0],
 
2622
         sub{ return @cxns },
 
2623
         { map { $_ => $connections{$_}->{dsn} } @cxns });
 
2624
   my @result = unique(grep { my $a = $_; grep { $_ eq $a } @cxns } split(/\s+/, $choices));
 
2625
   return @result;
 
2626
}
 
2627
 
 
2628
# kill_query {{{3
 
2629
# Kills a connection, or on new versions, optionally a query but not connection.
 
2630
sub kill_query {
 
2631
   my ( $q_or_c ) = @_;
 
2632
 
 
2633
   my ( $cxn ) = select_cxn('Kill on which server');
 
2634
   return unless $cxn && exists($connections{$cxn});
 
2635
 
 
2636
   eval {
 
2637
      my $thread = prompt("Choose which $q_or_c to kill");
 
2638
      return unless $thread && $thread =~ m/^\d+$/;
 
2639
      do_stmt($cxn, $q_or_c eq 'QUERY' ? 'KILL_QUERY' : 'KILL_CONNECTION', $thread);
 
2640
   };
 
2641
 
 
2642
   if ( $EVAL_ERROR ) {
 
2643
      print "\nError: $EVAL_ERROR";
 
2644
      pause();
 
2645
   }
 
2646
}
 
2647
 
 
2648
# set_V_set {{{3
 
2649
sub set_V_set {
 
2650
   $config{V_set}->{val} = shift;
 
2651
}
 
2652
 
 
2653
# set_display_precision {{{3
 
2654
sub set_display_precision {
 
2655
   my $dir = shift;
 
2656
   $config{num_digits}->{val} = min(9, max(0, $config{num_digits}->{val} + $dir));
 
2657
}
 
2658
 
 
2659
# toggle_filter{{{3
 
2660
sub toggle_filter {
 
2661
   my ( $tbl, $filter ) = @_;
 
2662
   my $filters = $tbl_meta{$tbl}->{filters};
 
2663
   if ( grep { $_ eq $filter } @$filters ) {
 
2664
      $tbl_meta{$tbl}->{filters} = [ grep { $_ ne $filter } @$filters ];
 
2665
   }
 
2666
   else {
 
2667
      push @$filters, $filter;
 
2668
   }
 
2669
}
 
2670
 
 
2671
# toggle_config {{{3
 
2672
sub toggle_config {
 
2673
   my ( $key ) = @_;
 
2674
   $config{$key}->{val} ^= 1;
 
2675
}
 
2676
 
 
2677
# create_deadlock {{{3
 
2678
sub create_deadlock {
 
2679
   $clear_screen_sub->();
 
2680
 
 
2681
   print "This function will deliberately cause a small deadlock, "
 
2682
      . "clearing deadlock information from the InnoDB monitor.\n\n";
 
2683
 
 
2684
   my $answer = prompt("Are you sure you want to proceed?  Say 'y' if you do");
 
2685
   return 0 unless $answer eq 'y';
 
2686
 
 
2687
   my ( $cxn ) = select_cxn('Clear on which server? ');
 
2688
   return unless $cxn && exists($connections{$cxn});
 
2689
 
 
2690
   clear_deadlock($cxn);
 
2691
}
 
2692
 
 
2693
# deadlock_thread {{{3
 
2694
sub deadlock_thread {
 
2695
   my ( $id, $tbl, $cxn ) = @_;
 
2696
   my @stmts = (
 
2697
      "set transaction isolation level serializable",
 
2698
      "start transaction",
 
2699
      "select * from $tbl where a = $id",
 
2700
      "update $tbl set a = $id where a <> $id",
 
2701
   );
 
2702
 
 
2703
   eval {
 
2704
      my $dbh = get_new_db_connection($cxn, 1);
 
2705
      foreach my $stmt (@stmts[0..2]) {
 
2706
         $dbh->do($stmt);
 
2707
      }
 
2708
      sleep(1 + $id);
 
2709
      $dbh->do($stmts[-1]);
 
2710
   };
 
2711
   if ( $EVAL_ERROR ) {
 
2712
      if ( $EVAL_ERROR !~ m/Deadlock found/ ) {
 
2713
         die $EVAL_ERROR;
 
2714
      }
 
2715
   }
 
2716
   exit(0);
 
2717
}
 
2718
 
 
2719
sub send_cmd_to_servers {
 
2720
   my ( $cmd, $all, $hint ) = @_;
 
2721
   my @cxns;
 
2722
   if ( $all ) {
 
2723
      @cxns = get_connections();
 
2724
   }
 
2725
   @cxns = select_cxn('Which servers?', @cxns);
 
2726
   if ( $hint ) {
 
2727
      print "\nHint: $hint\n";
 
2728
   }
 
2729
   $cmd = prompt('Command to send', undef, $cmd);
 
2730
   foreach my $cxn ( @cxns ) {
 
2731
      eval {
 
2732
         my $sth = do_query($cxn, $cmd);
 
2733
      };
 
2734
      if ( $EVAL_ERROR ) {
 
2735
         print "Error from $cxn: $EVAL_ERROR\n";
 
2736
      }
 
2737
      else {
 
2738
         print "Success on $cxn\n";
 
2739
      }
 
2740
   }
 
2741
   pause();
 
2742
}
 
2743
 
 
2744
# Display functions {{{2
 
2745
 
 
2746
# start_G_mode {{{3
 
2747
sub start_G_mode {
 
2748
   $clear_screen_sub->();
 
2749
   switch_mode('G');
 
2750
}
 
2751
 
 
2752
# start_S_mode {{{3
 
2753
sub start_S_mode {
 
2754
   $clear_screen_sub->();
 
2755
   switch_mode('S');
 
2756
}
 
2757
 
 
2758
# display_B {{{3
 
2759
sub display_B {
 
2760
   my @display_lines;
 
2761
   my @cxns = get_connections();
 
2762
   get_innodb_status(\@cxns);
 
2763
 
 
2764
   my @buffer_pool;
 
2765
   my @page_statistics;
 
2766
   my @insert_buffers;
 
2767
   my @adaptive_hash_index;
 
2768
   my %rows_for = (
 
2769
      buffer_pool         => \@buffer_pool,
 
2770
      page_statistics     => \@page_statistics,
 
2771
      insert_buffers      => \@insert_buffers,
 
2772
      adaptive_hash_index => \@adaptive_hash_index,
 
2773
   );
 
2774
 
 
2775
   my @visible = get_visible_tables();
 
2776
   my %wanted  = map { $_ => 1 } @visible;
 
2777
 
 
2778
   foreach my $cxn ( @cxns ) {
 
2779
      my $set = $vars{$cxn}->{$clock};
 
2780
 
 
2781
      if ( $set->{IB_bp_complete} ) {
 
2782
         if ( $wanted{buffer_pool} ) {
 
2783
            push @buffer_pool, extract_values($set, 'buffer_pool');
 
2784
         }
 
2785
         if ( $wanted{page_statistics} ) {
 
2786
            push @page_statistics, extract_values($set, 'page_statistics');
 
2787
         }
 
2788
      }
 
2789
      if ( $set->{IB_ib_complete} ) {
 
2790
         if ( $wanted{insert_buffers} ) {
 
2791
            push @insert_buffers, extract_values(
 
2792
               $config{status_inc}->{val} ? inc(0, $cxn) : $set,
 
2793
               'insert_buffers');
 
2794
         }
 
2795
         if ( $wanted{adaptive_hash_index} ) {
 
2796
            push @adaptive_hash_index, extract_values($set, 'adaptive_hash_index');
 
2797
         }
 
2798
      }
 
2799
   }
 
2800
 
 
2801
   my $first_table = 0;
 
2802
   foreach my $tbl ( @visible ) {
 
2803
      push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl);
 
2804
      push @display_lines, get_cxn_errors(@cxns) unless $config{debug}->{val} || $first_table++;;
 
2805
   }
 
2806
 
 
2807
   draw_screen(\@display_lines);
 
2808
}
 
2809
 
 
2810
# display_D {{{3
 
2811
sub display_D {
 
2812
   my @display_lines;
 
2813
   my @cxns = get_connections();
 
2814
   get_innodb_status(\@cxns);
 
2815
 
 
2816
   my @deadlock_transactions;
 
2817
   my @deadlock_locks;
 
2818
   my %rows_for = (
 
2819
      deadlock_transactions => \@deadlock_transactions,
 
2820
      deadlock_locks        => \@deadlock_locks,
 
2821
   );
 
2822
 
 
2823
   my @visible = get_visible_tables();
 
2824
   my %wanted  = map { $_ => 1 } @visible;
 
2825
 
 
2826
   foreach my $cxn ( @cxns ) {
 
2827
      my $innodb_status = $vars{$cxn}->{$clock};
 
2828
 
 
2829
      if ( $innodb_status->{IB_dl_timestring} ) {
 
2830
 
 
2831
         my $victim = $innodb_status->{IB_dl_rolled_back} || 0;
 
2832
 
 
2833
         if ( %wanted ) {
 
2834
            foreach my $txn_id ( keys %{$innodb_status->{IB_dl_txns}} ) {
 
2835
               my $txn = $innodb_status->{IB_dl_txns}->{$txn_id};
 
2836
 
 
2837
               if ( $wanted{deadlock_transactions} ) {
 
2838
                  my $hash = extract_values($txn->{tx}, 'deadlock_transactions');
 
2839
                  $hash->{cxn}        = $cxn;
 
2840
                  $hash->{dl_txn_num} = $txn_id;
 
2841
                  $hash->{victim}     = $txn_id == $victim ? 'Yes' : 'No';
 
2842
                  $hash->{timestring} = $innodb_status->{IB_dl_timestring};
 
2843
                  $hash->{truncates}  = $innodb_status->{IB_dl_complete} ? 'No' : 'Yes';
 
2844
                  push @deadlock_transactions, $hash;
 
2845
               }
 
2846
 
 
2847
               if ( $wanted{deadlock_locks} ) {
 
2848
                  foreach my $what (qw(waits_for holds)) {
 
2849
                     my $locks = $txn->{$what};
 
2850
                     if ( $locks ) {
 
2851
                        my $hash = extract_values($locks, 'deadlock_locks');
 
2852
                        $hash->{dl_txn_num}      = $txn_id;
 
2853
                        $hash->{txn_status}      = $what;
 
2854
                        $hash->{cxn}             = $cxn;
 
2855
                        $hash->{mysql_thread_id} = $txn->{tx}->{mysql_thread_id};
 
2856
                        push @deadlock_locks, $hash;
 
2857
                     }
 
2858
                  }
 
2859
               }
 
2860
 
 
2861
            }
 
2862
         }
 
2863
      }
 
2864
   }
 
2865
 
 
2866
   my $first_table = 0;
 
2867
   foreach my $tbl ( @visible ) {
 
2868
      push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl);
 
2869
      push @display_lines, get_cxn_errors(@cxns) unless $config{debug}->{val} || $first_table++;;
 
2870
   }
 
2871
 
 
2872
   draw_screen(\@display_lines);
 
2873
}
 
2874
 
 
2875
# display_F {{{3
 
2876
sub display_F {
 
2877
   my @display_lines;
 
2878
   my ( $cxn ) = get_connections();
 
2879
   get_innodb_status([$cxn]);
 
2880
   my $innodb_status = $vars{$cxn}->{$clock};
 
2881
 
 
2882
   if ( $innodb_status->{IB_fk_timestring} ) {
 
2883
 
 
2884
      push @display_lines, 'Reason: ' . $innodb_status->{IB_fk_reason};
 
2885
 
 
2886
      # Display FK errors caused by invalid DML.
 
2887
      if ( $innodb_status->{IB_fk_txn} ) {
 
2888
         my $txn = $innodb_status->{IB_fk_txn};
 
2889
         push @display_lines,
 
2890
            '',
 
2891
            "User $txn->{user} from $txn->{hostname}, thread $txn->{mysql_thread_id} was executing:",
 
2892
            '', no_ctrl_char($txn->{query_text});
 
2893
      }
 
2894
 
 
2895
      my @fk_table = create_table2(
 
2896
         $tbl_meta{fk_error}->{visible},
 
2897
         meta_to_hdr('fk_error'),
 
2898
         extract_values($innodb_status, 'fk_error'),
 
2899
         { just => '-', sep => '  '});
 
2900
      push @display_lines, '', @fk_table;
 
2901
 
 
2902
   }
 
2903
   else {
 
2904
      push @display_lines, '', 'No foreign key error data.';
 
2905
   }
 
2906
   draw_screen(\@display_lines, { raw => 1 } );
 
2907
}
 
2908
 
 
2909
# display_G {{{3
 
2910
sub display_G {
 
2911
   my ( $cxn ) = get_connections();
 
2912
   my $fmt     = get_var_set('G_set');
 
2913
   get_status_info($cxn);
 
2914
   get_innodb_status([$cxn]); # TODO: might not be needed.
 
2915
 
 
2916
   if ( !exists $vars{$cxn}->{$clock - 1} ) {
 
2917
      return;
 
2918
   }
 
2919
 
 
2920
   # Design a column format for the values.
 
2921
   my $num_cols = scalar(@$fmt);
 
2922
   my $width    = $opts{n} ? 0 : int(($this_term_size[0] - $num_cols + 1) / $num_cols);
 
2923
   my $format   = $opts{n} ? ( "%s\t" x $num_cols ) : ( "%-${width}s " x $num_cols );
 
2924
   $format      =~ s/\s$/\n/;
 
2925
 
 
2926
   # Clear the screen if the display width changed.
 
2927
   if ( @last_term_size && $this_term_size[0] != $last_term_size[0] ) {
 
2928
      $lines_printed = 0;
 
2929
      $clear_screen_sub->();
 
2930
   }
 
2931
 
 
2932
   # Get the values.
 
2933
   my $set = inc(0, $cxn);
 
2934
   $set = { map { $_ => ($set->{$_} || 1) / ( $set->{Uptime_hires} || 1) } @$fmt };
 
2935
 
 
2936
   # Update max ever seen.
 
2937
   map { $mvs{$_} = max($mvs{$_} || 1, $set->{$_}) } @$fmt;
 
2938
 
 
2939
   # Print headers every now and then.
 
2940
   if ( $opts{n} ) {
 
2941
      if ( $lines_printed == 0 ) {
 
2942
         print join("\t", @$fmt), "\n";
 
2943
         print join("\t", map { shorten($mvs{$_}) } @$fmt), "\n";
 
2944
      }
 
2945
   }
 
2946
   elsif ( $lines_printed % int( $this_term_size[1] - 2 ) == 0 ) {
 
2947
      printf($format, map { donut(crunch($_, $width), $width) } @$fmt);
 
2948
      printf($format, map { shorten($mvs{$_}) } @$fmt);
 
2949
   }
 
2950
   $lines_printed++;
 
2951
 
 
2952
   # Scale the values against the max ever seen.
 
2953
   map { $set->{$_} /= $mvs{$_} } @$fmt;
 
2954
 
 
2955
   # Print the values.
 
2956
   printf($format, map { ( '*' x int( $width * $set->{$_} )) || '.' } @$fmt );
 
2957
}
 
2958
 
 
2959
# display_I {{{3
 
2960
sub display_I {
 
2961
   my @display_lines;
 
2962
   my @cxns = get_connections();
 
2963
   get_innodb_status(\@cxns);
 
2964
 
 
2965
   my @io_threads;
 
2966
   my @pending_io;
 
2967
   my @file_io_misc;
 
2968
   my @log_statistics;
 
2969
   my %rows_for = (
 
2970
      io_threads     => \@io_threads,
 
2971
      pending_io     => \@pending_io,
 
2972
      file_io_misc   => \@file_io_misc,
 
2973
      log_statistics => \@log_statistics,
 
2974
   );
 
2975
 
 
2976
   my @visible = get_visible_tables();
 
2977
   my %wanted  = map { $_ => 1 } @visible;
 
2978
 
 
2979
   foreach my $cxn ( @cxns ) {
 
2980
      my $set = $vars{$cxn}->{$clock};
 
2981
 
 
2982
      if ( $set->{IB_io_complete} ) {
 
2983
         if ( $wanted{io_threads} ) {
 
2984
            foreach my $thd ( values %{$set->{IB_io_threads}} ) {
 
2985
               my $hash = extract_values($thd, 'io_threads');
 
2986
               $hash->{cxn} = $cxn;
 
2987
               push @io_threads, $hash;
 
2988
            }
 
2989
         }
 
2990
         if ( $wanted{pending_io} ) {
 
2991
            push @pending_io, extract_values($set, 'pending_io');
 
2992
         }
 
2993
         if ( $wanted{file_io_misc} ) {
 
2994
            push @file_io_misc, extract_values(
 
2995
               $config{status_inc}->{val} ? inc(0, $cxn) : $set,
 
2996
               'file_io_misc');
 
2997
         }
 
2998
      }
 
2999
      if ( $set->{IB_lg_complete} && $wanted{log_statistics} ) {
 
3000
         push @log_statistics, extract_values($set, 'log_statistics');
 
3001
      }
 
3002
   }
 
3003
 
 
3004
   my $first_table = 0;
 
3005
   foreach my $tbl ( @visible ) {
 
3006
      push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl);
 
3007
      push @display_lines, get_cxn_errors(@cxns) unless $config{debug}->{val} || $first_table++;;
 
3008
   }
 
3009
 
 
3010
   draw_screen(\@display_lines);
 
3011
}
 
3012
 
 
3013
# display_M {{{3
 
3014
sub display_M {
 
3015
   my @display_lines;
 
3016
   my @cxns = get_connections();
 
3017
   get_master_slave_status(@cxns);
 
3018
   get_status_info(@cxns);
 
3019
 
 
3020
   my @slave_sql_status;
 
3021
   my @slave_io_status;
 
3022
   my @master_status;
 
3023
   my %rows_for = (
 
3024
      slave_sql_status => \@slave_sql_status,
 
3025
      slave_io_status  => \@slave_io_status,
 
3026
      master_status    => \@master_status,
 
3027
   );
 
3028
 
 
3029
   my @visible = get_visible_tables();
 
3030
   my %wanted  = map { $_ => 1 } @visible;
 
3031
 
 
3032
   foreach my $cxn ( @cxns ) {
 
3033
      my $set  = $config{status_inc}->{val} ? inc(0, $cxn) : $vars{$cxn}->{$clock};
 
3034
      if ( $wanted{slave_sql_status} ) {
 
3035
         push @slave_sql_status, extract_values($set, 'slave_sql_status');
 
3036
      }
 
3037
      if ( $wanted{slave_io_status} ) {
 
3038
         push @slave_io_status, extract_values($set, 'slave_io_status');
 
3039
      }
 
3040
      if ( $wanted{master_status} ) {
 
3041
         push @master_status, extract_values($set, 'master_status');
 
3042
      }
 
3043
   }
 
3044
 
 
3045
   my $first_table = 0;
 
3046
   foreach my $tbl ( @visible ) {
 
3047
      push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl);
 
3048
      push @display_lines, get_cxn_errors(@cxns) unless $config{debug}->{val} || $first_table++;;
 
3049
   }
 
3050
 
 
3051
   draw_screen(\@display_lines);
 
3052
}
 
3053
 
 
3054
# display_O {{{3
 
3055
sub display_O {
 
3056
   my @display_lines = ('');
 
3057
   my @cxns          = get_connections();
 
3058
   my @open_tables   = get_open_tables(@cxns);
 
3059
   my @tables = map { extract_values($_, 'open_tables') } @open_tables;
 
3060
   push @display_lines, set_to_tbl(\@tables, 'open_tables'), get_cxn_errors(@cxns);
 
3061
   draw_screen(\@display_lines);
 
3062
}
 
3063
 
 
3064
# display_Q {{{3
 
3065
sub display_Q {
 
3066
   my @display_lines;
 
3067
 
 
3068
   my @q_header;
 
3069
   my @processlist;
 
3070
   my %rows_for = (
 
3071
      q_header    => \@q_header,
 
3072
      processlist => \@processlist,
 
3073
   );
 
3074
 
 
3075
   my @visible = $opts{n} ? 'processlist' : get_visible_tables();
 
3076
   my %wanted  = map { $_ => 1 } @visible;
 
3077
 
 
3078
   # Config variable overrides %wanted here. TODO: this is hack-ish.
 
3079
   $wanted{q_header} = $config{show_QT_header}->{val};
 
3080
 
 
3081
   # Get the data
 
3082
   my @cxns             = get_connections();
 
3083
   my @full_processlist = get_full_processlist(@cxns);
 
3084
 
 
3085
   # Create header
 
3086
   if ( $wanted{q_header} ) {
 
3087
      get_status_info(@cxns);
 
3088
      foreach my $cxn ( @cxns ) {
 
3089
         my $hash = extract_values($vars{$cxn}->{$clock}, 'q_header');
 
3090
         $hash->{cxn} = $cxn;
 
3091
         $hash->{when} = 'Total';
 
3092
         push @q_header, $hash;
 
3093
 
 
3094
         if ( exists $vars{$cxn}->{$clock - 1} ) {
 
3095
            my $inc = inc(0, $cxn);
 
3096
            my $hash = extract_values($inc, 'q_header');
 
3097
            $hash->{cxn} = $cxn;
 
3098
            $hash->{when} = 'Now';
 
3099
            push @q_header, $hash;
 
3100
         }
 
3101
      }
 
3102
   }
 
3103
 
 
3104
   if ( $wanted{processlist} ) {
 
3105
      push @processlist, map { extract_values($_, 'processlist') } @full_processlist;
 
3106
   }
 
3107
 
 
3108
   my $first_table = 0;
 
3109
   foreach my $tbl ( @visible ) {
 
3110
      next unless $wanted{$tbl};
 
3111
      push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl);
 
3112
      push @display_lines, get_cxn_errors(@cxns) unless $config{debug}->{val} || $first_table++;
 
3113
   }
 
3114
 
 
3115
   # Save queries in global variable for analysis.  The rows in %rows_for have been
 
3116
   # filtered, etc as a side effect of set_to_tbl(), so they are the same as the rows
 
3117
   # that get pushed to the screen.
 
3118
   @current_queries = map {
 
3119
      my %hash;
 
3120
      @hash{ qw(cxn id db query) } = @{$_}{ qw(cxn mysql_thread_id db info) };
 
3121
      \%hash;
 
3122
   } @{$rows_for{processlist}};
 
3123
 
 
3124
   draw_screen(\@display_lines);
 
3125
}
 
3126
 
 
3127
# display_R {{{3
 
3128
sub display_R {
 
3129
   my @display_lines;
 
3130
   my @cxns = get_connections();
 
3131
   get_innodb_status(\@cxns);
 
3132
 
 
3133
   my @row_operations;
 
3134
   my @row_operation_misc;
 
3135
   my @semaphores;
 
3136
   my @wait_array;
 
3137
   my %rows_for = (
 
3138
      row_operations     => \@row_operations,
 
3139
      row_operation_misc => \@row_operation_misc,
 
3140
      semaphores         => \@semaphores,
 
3141
      wait_array         => \@wait_array,
 
3142
   );
 
3143
 
 
3144
   my @visible = get_visible_tables();
 
3145
   my %wanted  = map { $_ => 1 } @visible;
 
3146
   my $incvar  = $config{status_inc}->{val};
 
3147
 
 
3148
   foreach my $cxn ( @cxns ) {
 
3149
      my $set = $vars{$cxn}->{$clock};
 
3150
      my $inc; # Only assigned to if wanted
 
3151
 
 
3152
      if ( $set->{IB_ro_complete} ) {
 
3153
         if ( $wanted{row_operations} ) {
 
3154
            $inc ||= $incvar ? inc(0, $cxn) : $set;
 
3155
            push @row_operations, extract_values($inc, 'row_operations');
 
3156
         }
 
3157
         if ( $wanted{row_operation_misc} ) {
 
3158
            push @row_operation_misc, extract_values($set, 'row_operation_misc'),
 
3159
         }
 
3160
      }
 
3161
 
 
3162
      if ( $set->{IB_sm_complete} && $wanted{semaphores} ) {
 
3163
         $inc ||= $incvar ? inc(0, $cxn) : $set;
 
3164
         push @semaphores, extract_values($inc, 'semaphores');
 
3165
      }
 
3166
 
 
3167
      if ( $set->{IB_sm_wait_array_size} && $wanted{wait_array} ) {
 
3168
         foreach my $wait ( @{$set->{IB_sm_waits}} ) {
 
3169
            my $hash = extract_values($wait, 'wait_array');
 
3170
            $hash->{cxn} = $cxn;
 
3171
            push @wait_array, $hash;
 
3172
         }
 
3173
      }
 
3174
   }
 
3175
 
 
3176
   my $first_table = 0;
 
3177
   foreach my $tbl ( @visible ) {
 
3178
      push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl);
 
3179
      push @display_lines, get_cxn_errors(@cxns) unless $config{debug}->{val} || $first_table++;
 
3180
   }
 
3181
 
 
3182
   draw_screen(\@display_lines);
 
3183
}
 
3184
 
 
3185
# display_S {{{3
 
3186
sub display_S {
 
3187
   my $min_width = 4;
 
3188
   my $inc       = $config{status_inc}->{val};
 
3189
   my ( $cxn )   = get_connections();
 
3190
   my $fmt       = get_var_set('S_set');
 
3191
   get_status_info( $cxn );
 
3192
   get_innodb_status([$cxn]); # TODO: might not be needed.
 
3193
 
 
3194
   # Clear the screen if the display width changed.
 
3195
   if ( @last_term_size && $this_term_size[0] != $last_term_size[0] ) {
 
3196
      $lines_printed = 0;
 
3197
      $clear_screen_sub->();
 
3198
   }
 
3199
 
 
3200
   # Decide how wide columns should be.
 
3201
   my $num_cols = scalar(@$fmt);
 
3202
   my $width    = $opts{n} ? 0 : max($min_width, int(($this_term_size[0] - $num_cols + 1) / $num_cols));
 
3203
 
 
3204
   # Print headers every now and then.  Headers can get really long, so compact them.
 
3205
   my @hdr = @$fmt;
 
3206
   if ( $opts{n} ) {
 
3207
      if ( $lines_printed == 0 ) {
 
3208
         print join("\t", @hdr), "\n";
 
3209
      }
 
3210
   }
 
3211
   elsif ( $lines_printed % int( $this_term_size[1] - 2 ) == 0 ) {
 
3212
      @hdr = map { donut(crunch($_, $width), $width) } @hdr;
 
3213
      print join(' ', map { sprintf( "%${width}s", donut($_, $width)) } @hdr) . "\n";
 
3214
   }
 
3215
 
 
3216
   # Design a column format for the values.
 
3217
   my $format
 
3218
      = $opts{n}
 
3219
      ? join("\t", map { '%s' } @$fmt) . "\n"
 
3220
      : join(' ',  map { "%${width}s" } @hdr) . "\n";
 
3221
 
 
3222
   # Print the values.
 
3223
   my $set = $inc ? inc(0, $cxn) : $vars{$cxn}->{$clock};
 
3224
   printf($format,
 
3225
      map {
 
3226
            exists $set->{$_} ? $set->{$_}
 
3227
          : exists $exprs{$_} ? $exprs{$_}->{func}->($set)
 
3228
          :                     0
 
3229
      } @$fmt
 
3230
   );
 
3231
   $lines_printed++;
 
3232
}
 
3233
 
 
3234
# display_T {{{3
 
3235
sub display_T {
 
3236
   my @display_lines;
 
3237
 
 
3238
   my @txns;
 
3239
   my @cxns = get_connections();
 
3240
 
 
3241
   # If the header is to be shown, buffer pool data is required.
 
3242
   my $hdr = ( !$opts{n} && 1 == scalar @cxns && $config{show_QT_header}->{val} );
 
3243
 
 
3244
   get_innodb_status( \@cxns, [ $hdr ? qw(bp) : () ] );
 
3245
 
 
3246
   foreach my $cxn ( get_connections() ) {
 
3247
      my $set = $vars{$cxn}->{$clock};
 
3248
 
 
3249
      next unless $set->{IB_tx_transactions};
 
3250
 
 
3251
      if ( $set->{IB_tx_transactions} ) {
 
3252
         foreach my $txn ( @{$set->{IB_tx_transactions}} ) {
 
3253
            my $hash = extract_values($txn, 'innodb_transactions');
 
3254
            $hash->{cxn} = $cxn;
 
3255
            push @txns, $hash;
 
3256
         }
 
3257
      }
 
3258
 
 
3259
      if ( $hdr ) {
 
3260
         push @display_lines, '', join(", ",
 
3261
            "History: $set->{IB_tx_history_list_len}",
 
3262
            "Versions: " . $exprs{OldVersions}->{func}->( $set ),
 
3263
            "Undo: $set->{IB_tx_purge_undo_for}",
 
3264
            "Dirty Bufs: " . percent($exprs{DirtyBufs}->{func}->( $set )) . '%',
 
3265
            "Used Bufs: " . percent($exprs{BufPoolFill}->{func}->( $set )) . '%',
 
3266
            "Max time: " . secs_to_time($exprs{MaxTxnTime}->{func}->( $set )),
 
3267
            "Lock structs: $set->{IB_tx_num_lock_structs}",
 
3268
         );
 
3269
      }
 
3270
   }
 
3271
 
 
3272
   push @display_lines, '', set_to_tbl(\@txns, 'innodb_transactions'), get_cxn_errors(@cxns);
 
3273
 
 
3274
   draw_screen(\@display_lines);
 
3275
}
 
3276
 
 
3277
# display_V {{{3
 
3278
# TODO: when entering V mode, remove any non-contiguous stuff from %vars.
 
3279
sub display_V {
 
3280
   my @display_lines;
 
3281
   my ( $cxn ) = get_connections();
 
3282
   my $fmt     = get_var_set('V_set');
 
3283
   my $inc     = $config{status_inc}->{val};
 
3284
   my $num     = $config{num_status_sets}->{val};
 
3285
 
 
3286
   get_status_info($cxn);
 
3287
   get_innodb_status([$cxn]); # TODO: might not be needed.
 
3288
 
 
3289
   # Figure out how many past sets have actually been kept.
 
3290
   while ( !exists $vars{$cxn}->{$clock - $num} ) {
 
3291
      $num--;
 
3292
   }
 
3293
 
 
3294
   # Build a meta dataset that can be used for a type-1 table
 
3295
   my $meta = { name => { hdr => 'Name', just => '-' } };
 
3296
   foreach my $set ( 0 .. $num ) {
 
3297
      $meta->{"set_$set"} = { hdr => "Set $set", just => '' };
 
3298
   }
 
3299
 
 
3300
   # Loop through them and do a 'pivot table' transformation on them.  Instead of
 
3301
   # sets becoming rows, sets must become columns, and variables become rows.
 
3302
   my @rows = map { { name => $_ } } @$fmt;
 
3303
   foreach my $set ( 0 .. $num ) {
 
3304
      my $vars = $inc ? inc($set, $cxn) : $vars{$cxn}->{$clock - $set};
 
3305
      foreach my $row ( 0.. @$fmt - 1 ) {
 
3306
         my $name = $fmt->[$row];
 
3307
         my $val = exists($vars->{$name}) ? $vars->{$name}
 
3308
                 : exists($exprs{$name})  ? $exprs{$name}->{func}->($vars)
 
3309
                 :                          0;
 
3310
         $rows[$row]->{"set_$set"} = defined $val ? $val : 0;
 
3311
      }
 
3312
   }
 
3313
 
 
3314
   my @cols = 'name';
 
3315
   foreach my $set ( 0 .. $num ) {
 
3316
      push @cols, "set_$set";
 
3317
   }
 
3318
 
 
3319
   push @display_lines, create_table( \@cols, $meta, \@rows);
 
3320
 
 
3321
   $clear_screen_sub->();
 
3322
 
 
3323
   draw_screen( \@display_lines );
 
3324
}
 
3325
 
 
3326
# display_W {{{3
 
3327
sub display_W {
 
3328
   my @display_lines;
 
3329
   my @cxns = get_connections();
 
3330
   get_innodb_status(\@cxns);
 
3331
 
 
3332
   my @lock_waits;
 
3333
   my @wait_array;
 
3334
   my %rows_for = (
 
3335
      lock_waits => \@lock_waits,
 
3336
      wait_array => \@wait_array,
 
3337
   );
 
3338
 
 
3339
   my @visible = get_visible_tables();
 
3340
   my %wanted  = map { $_ => 1 } @visible;
 
3341
 
 
3342
   # Get info on lock waits and OS wait array
 
3343
   foreach my $cxn ( @cxns ) {
 
3344
      my $set = $vars{$cxn}->{$clock} or next;
 
3345
 
 
3346
      if ( $wanted{lock_waits} && @{$set->{IB_tx_transactions}} ) {
 
3347
 
 
3348
         my @txns = @{$set->{IB_tx_transactions}};
 
3349
         foreach my $txn ( grep { $_->{lock_wait_status} } @txns ) {
 
3350
            my %lock_wait = map { $_ => $txn->{$_} }
 
3351
               qw(txn_id mysql_thread_id lock_wait_time active_secs);
 
3352
            my $wait_locks = $txn->{wait_locks};
 
3353
            map { $lock_wait{$_} = $wait_locks->{$_} }
 
3354
               qw(lock_type space_id page_no n_bits index db table txn_id
 
3355
                     lock_mode special insert_intention waiting num_locks);
 
3356
            $lock_wait{cxn} = $cxn;
 
3357
            push @lock_waits, extract_values(\%lock_wait, 'lock_waits');
 
3358
         }
 
3359
      }
 
3360
 
 
3361
      if ( $wanted{wait_array} && $set->{IB_sm_complete} ) {
 
3362
         if ( $set->{IB_sm_wait_array_size} ) {
 
3363
            foreach my $wait ( @{$set->{IB_sm_waits}} ) {
 
3364
               my $hash = extract_values($wait, 'wait_array');
 
3365
               $hash->{cxn} = $cxn;
 
3366
               push @wait_array, $hash;
 
3367
            }
 
3368
         }
 
3369
      }
 
3370
   }
 
3371
 
 
3372
   my $first_table = 0;
 
3373
   foreach my $tbl ( @visible ) {
 
3374
      push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl);
 
3375
      push @display_lines, get_cxn_errors(@cxns) unless $config{debug}->{val} || $first_table++;;
 
3376
   }
 
3377
 
 
3378
   draw_screen(\@display_lines);
 
3379
}
 
3380
 
 
3381
# display_explain {{{3
 
3382
sub display_explain {
 
3383
   my $info = shift;
 
3384
   my $cxn   = $info->{cxn};
 
3385
   my $db    = $info->{db};
 
3386
   my $meta  = $dbhs{$cxn};
 
3387
 
 
3388
   my ( $mods, $query ) = rewrite_for_explain($info->{query});
 
3389
 
 
3390
   my @display_lines;
 
3391
 
 
3392
   if ( $query ) {
 
3393
 
 
3394
      my $part
 
3395
         = ( $meta->{ver_major} >= 5 && $meta->{ver_minor} >= 1 && $meta->{ver_rev} >= 5 )
 
3396
         ? 'PARTITIONS'
 
3397
         : '';
 
3398
      $query = "EXPLAIN $part\n" . $query;
 
3399
 
 
3400
      eval {
 
3401
         if ( $db ) {
 
3402
            do_query($cxn, "use $db");
 
3403
         }
 
3404
         my $sth = do_query($cxn, $query);
 
3405
 
 
3406
         my $res;
 
3407
         while ( $res = $sth->fetchrow_hashref() ) {
 
3408
            map { $res->{$_} ||= '' } ( 'partitions', keys %$res);
 
3409
            my @this_table = create_caption("Sub-Part $res->{id}",
 
3410
               create_table2(
 
3411
                  $tbl_meta{explain}->{visible},
 
3412
                  meta_to_hdr('explain'),
 
3413
                  extract_values($res, 'explain')));
 
3414
            @display_lines = stack_next(\@display_lines, \@this_table, { pad => '  ', vsep => 2 });
 
3415
         }
 
3416
      };
 
3417
 
 
3418
      if ( $EVAL_ERROR ) {
 
3419
         push @display_lines, '', "The query could not be explained: $EVAL_ERROR";
 
3420
      }
 
3421
 
 
3422
   }
 
3423
   else {
 
3424
      push @display_lines, '', 'The query could not be explained.';
 
3425
   }
 
3426
 
 
3427
   if ( $mods ) {
 
3428
      push @display_lines, '', '[This query has been re-written to be explainable]';
 
3429
   }
 
3430
 
 
3431
   unshift @display_lines, no_ctrl_char($query);
 
3432
   draw_screen(\@display_lines, { raw => 1 } );
 
3433
}
 
3434
 
 
3435
# rewrite_for_explain {{{3
 
3436
# Some replace/create/insert...select can be rewritten easily.
 
3437
sub rewrite_for_explain {
 
3438
   my $query = shift;
 
3439
 
 
3440
   my $mods = 0;
 
3441
   my $orig = $query;
 
3442
   $mods += $query =~ s/^\s*(?:replace|insert).*?select/select/is;
 
3443
   $mods += $query =~ s/^
 
3444
      \s*create\s+(?:temporary\s+)?table
 
3445
      \s+(?:\S+\s+)as\s+select/select/xis;
 
3446
   $mods += $query =~ s/\s+on\s+duplicate\s+key\s+update.*$//is;
 
3447
   return ( $mods, $query );
 
3448
}
 
3449
 
 
3450
# show_optimized_query {{{3
 
3451
sub show_optimized_query {
 
3452
   my $info = shift;
 
3453
   my $cxn   = $info->{cxn};
 
3454
   my $db    = $info->{db};
 
3455
   my $meta  = $dbhs{$cxn};
 
3456
 
 
3457
   my @display_lines;
 
3458
 
 
3459
   my ( $mods, $query ) = rewrite_for_explain($info->{query});
 
3460
 
 
3461
   if ( $mods ) {
 
3462
      push @display_lines, '[This query has been re-written to be explainable]';
 
3463
   }
 
3464
 
 
3465
   if ( $query ) {
 
3466
      push @display_lines, no_ctrl_char($info->{query});
 
3467
 
 
3468
      eval {
 
3469
         if ( $db ) {
 
3470
            do_query($cxn, "use $db");
 
3471
         }
 
3472
         do_query( $cxn, 'EXPLAIN EXTENDED ' . $query ) or die "Can't explain query";
 
3473
         my $sth = do_query($cxn, 'SHOW WARNINGS');
 
3474
         my $res = $sth->fetchall_arrayref;
 
3475
 
 
3476
         if ( $res ) {
 
3477
            foreach my $result ( @$res ) {
 
3478
               push @display_lines, 'Note:', no_ctrl_char($result->[2]);
 
3479
            }
 
3480
         }
 
3481
         else {
 
3482
            push @display_lines, '', 'The query optimization could not be generated.';
 
3483
         }
 
3484
      };
 
3485
 
 
3486
      if ( $EVAL_ERROR ) {
 
3487
         push @display_lines, '', "The optimization could not be generated: $EVAL_ERROR";
 
3488
      }
 
3489
 
 
3490
   }
 
3491
   else {
 
3492
      push @display_lines, '', 'The query optimization could not be generated.';
 
3493
   }
 
3494
 
 
3495
   draw_screen(\@display_lines, { raw => 1 } );
 
3496
}
 
3497
 
 
3498
# display_help {{{3
 
3499
sub display_help {
 
3500
   my $mode = $config{mode}->{val};
 
3501
 
 
3502
   # Get globally mapped keys, then overwrite them with mode-specific ones.
 
3503
   my %keys = map {
 
3504
         my $key = $action_for{$_}->{key} || $_;
 
3505
         $key => $action_for{$_}->{label}
 
3506
      } keys %action_for;
 
3507
   foreach my $key ( keys %{$modes{$mode}->{action_for}} ) {
 
3508
      $keys{$key} = $modes{$mode}->{action_for}->{$key}->{label};
 
3509
   }
 
3510
   delete $keys{'?'};
 
3511
 
 
3512
   my @display_lines = ( '', 'The following keys are mapped in this mode:', '', );
 
3513
   push @display_lines,  create_table2(
 
3514
      [ sort keys %keys ],
 
3515
      { map { $_ => $_ } keys %keys },
 
3516
      \%keys,
 
3517
      { sep => '    ' }
 
3518
   );
 
3519
   push @display_lines, '', 'Any other key refreshes the display.', '';
 
3520
   $clear_screen_sub->();
 
3521
   draw_screen(\@display_lines, { show_all => 1 } );
 
3522
   pause();
 
3523
}
 
3524
 
 
3525
# show_full_query {{{3
 
3526
sub show_full_query {
 
3527
   my $info = shift;
 
3528
   my @display_lines = no_ctrl_char($info->{query});
 
3529
   draw_screen(\@display_lines, { raw => 1 });
 
3530
}
 
3531
 
 
3532
# Formatting functions {{{2
 
3533
 
 
3534
# create_grid {{{3
 
3535
sub create_grid {
 
3536
   my @vals = @_;
 
3537
   my @result;
 
3538
 
 
3539
   # Slice and stack, baby.
 
3540
   my $i = 0;
 
3541
   while ($i < @vals) {
 
3542
      # Do 5 at a time
 
3543
      my $max_index = min( scalar(@vals), $i + 5 );
 
3544
      my @slice = @vals[$i..$max_index - 1];
 
3545
      my $max_width = max( map{ length($_) } @slice );
 
3546
      @slice  = map { sprintf("%-${max_width}s", $_) } @slice;
 
3547
      @result = stack_next(\@result, \@slice);
 
3548
      $i += 5;
 
3549
   }
 
3550
   return @result;
 
3551
}
 
3552
 
 
3553
# create_table2 {{{3
 
3554
# Makes a two-column table, labels on left, data on right.
 
3555
# Takes refs of @cols, %labels and %data, %user_prefs
 
3556
sub create_table2 {
 
3557
   my ( $cols, $labels, $data, $user_prefs ) = @_;
 
3558
   my @rows;
 
3559
 
 
3560
   if ( @$cols && %$data ) {
 
3561
 
 
3562
      # Override defaults
 
3563
      my $p = {
 
3564
         just  => '',
 
3565
         sep   => ':',
 
3566
         just1 => '-',
 
3567
      };
 
3568
      if ( $user_prefs ) {
 
3569
         map { $p->{$_} = $user_prefs->{$_} } keys %$user_prefs;
 
3570
      }
 
3571
 
 
3572
      # Fix undef values
 
3573
      map { $data->{$_} = '' unless defined $data->{$_} } @$cols;
 
3574
 
 
3575
      # Format the table
 
3576
      my $max_l = max(map{ length($labels->{$_}) } @$cols);
 
3577
      my $max_v = max(map{ length($data->{$_}) } @$cols);
 
3578
      my $format    = "%$p->{just}${max_l}s$p->{sep} %$p->{just1}${max_v}s";
 
3579
      foreach my $col ( @$cols ) {
 
3580
         push @rows, sprintf($format, $labels->{$col}, $data->{$col});
 
3581
      }
 
3582
   }
 
3583
   return @rows;
 
3584
}
 
3585
 
 
3586
# stack_next {{{3
 
3587
# Stacks one display section next to the other.  Accepts left-hand arrayref,
 
3588
# right-hand arrayref, and options hashref.  Tries to stack as high as
 
3589
# possible, so
 
3590
# aaaaaa
 
3591
# bbb
 
3592
# can stack ccc next to the bbb.
 
3593
# NOTE: this DOES modify its arguments, even though it returns a new array.
 
3594
sub stack_next {
 
3595
   my ( $left, $right, $user_prefs ) = @_;
 
3596
   my @result;
 
3597
 
 
3598
   my $p = {
 
3599
      pad   => ' ',
 
3600
      vsep  => 0,
 
3601
   };
 
3602
   if ( $user_prefs ) {
 
3603
      map { $p->{$_} = $user_prefs->{$_} } keys %$user_prefs;
 
3604
   }
 
3605
 
 
3606
   # Find out how wide the LHS can be and still let the RHS fit next to it.
 
3607
   my $pad   = $p->{pad};
 
3608
   my $max_r = max( map { length($_) } @$right) || 0;
 
3609
   my $max_l = $this_term_size[0] - $max_r - length($pad);
 
3610
 
 
3611
   # Find the minimum row on the LHS that the RHS will fit next to.
 
3612
   my $i = scalar(@$left) - 1;
 
3613
   while ( $i >= 0 && length($left->[$i]) <= $max_l ) {
 
3614
      $i--;
 
3615
   }
 
3616
   $i++;
 
3617
   my $offset = $i;
 
3618
 
 
3619
   if ( $i < scalar(@$left) ) {
 
3620
      # Find the max width of the section of the LHS against which the RHS
 
3621
      # will sit.
 
3622
      my $max_i_in_common = min($i + scalar(@$right) - 1, scalar(@$left) - 1);
 
3623
      my $max_width = max( map { length($_) } @{$left}[$i..$max_i_in_common]);
 
3624
 
 
3625
      # Append the RHS onto the LHS until one runs out.
 
3626
      while ( $i < @$left && $i - $offset < @$right ) {
 
3627
         my $format = "%-${max_width}s$pad%${max_r}s";
 
3628
         $left->[$i] = sprintf($format, $left->[$i], $right->[$i - $offset]);
 
3629
         $i++;
 
3630
      }
 
3631
      while ( $i - $offset < @$right ) {
 
3632
         # There is more RHS to push on the end of the array
 
3633
         push @$left,
 
3634
            sprintf("%${max_width}s$pad%${max_r}s", ' ', $right->[$i - $offset]);
 
3635
         $i++;
 
3636
      }
 
3637
      push @result, @$left;
 
3638
   }
 
3639
   else {
 
3640
      # There is no room to put them side by side.  Add them below, with
 
3641
      # a blank line above them if specified.
 
3642
      push @result, @$left;
 
3643
      push @result, (' ' x $this_term_size[0]) if $p->{vsep} && @$left;
 
3644
      push @result, @$right;
 
3645
   }
 
3646
   return @result;
 
3647
}
 
3648
 
 
3649
# create_caption {{{3
 
3650
sub create_caption {
 
3651
   my ( $caption, @rows ) = @_;
 
3652
   if ( @rows ) {
 
3653
 
 
3654
      # Calculate the width of what will be displayed, so it can be centered
 
3655
      # in that space.  When the thing is wider than the display, center the
 
3656
      # caption in the display.
 
3657
      my $width = min($this_term_size[0], max(map { length(ref($_) ? $_->[0] : $_) } @rows));
 
3658
 
 
3659
      my $cap_len = length($caption);
 
3660
 
 
3661
      # It may be narrow enough to pad the sides with underscores and save a
 
3662
      # line on the screen.
 
3663
      if ( $cap_len <= $width - 6 ) {
 
3664
         my $left = int(($width - 2 - $cap_len) / 2);
 
3665
         unshift @rows,
 
3666
            ("_" x $left) . " $caption " . ("_" x ($width - $left - $cap_len - 2));
 
3667
      }
 
3668
 
 
3669
      # The caption is too wide to add underscores on each side.
 
3670
      else {
 
3671
 
 
3672
         # Color is supported, so we can use terminal underlining.
 
3673
         if ( $have_color ) {
 
3674
            my $left = int(($width - $cap_len) / 2);
 
3675
            unshift @rows, [
 
3676
               (" " x $left) . $caption . (" " x ($width - $left - $cap_len)),
 
3677
               'underline',
 
3678
            ];
 
3679
         }
 
3680
 
 
3681
         # Color is not supported, so we have to add a line underneath to separate the
 
3682
         # caption from whatever it's captioning.
 
3683
         else {
 
3684
            my $left = int(($width - $cap_len) / 2);
 
3685
            unshift @rows, ('-' x $width);
 
3686
            unshift @rows, (" " x $left) . $caption . (" " x ($width - $left - $cap_len));
 
3687
         }
 
3688
 
 
3689
         # The caption is wider than the thing it labels, so we have to pad the
 
3690
         # thing it labels to a consistent width.
 
3691
         if ( $cap_len > $width ) {
 
3692
            @rows = map {
 
3693
               ref($_)
 
3694
                  ? [ sprintf('%-' . $cap_len . 's', $_->[0]), $_->[1] ]
 
3695
                  : sprintf('%-' . $cap_len . 's', $_);
 
3696
            } @rows;
 
3697
         }
 
3698
 
 
3699
      }
 
3700
   }
 
3701
   return @rows;
 
3702
}
 
3703
 
 
3704
# create_table {{{3
 
3705
# Input: an arrayref of columns, hashref of col info, and an arrayref of hashes
 
3706
# Example: [ 'a', 'b' ]
 
3707
#          { a => spec, b => spec }
 
3708
#          [ { a => 1, b => 2}, { a => 3, b => 4 } ]
 
3709
# The 'spec' is a hashref of hdr => label, just => ('-' or '').  It also supports min and max-widths
 
3710
# vi the minw and maxw params.
 
3711
# Output: an array of strings, one per row.
 
3712
# Example:
 
3713
# Column One Column Two
 
3714
# ---------- ----------
 
3715
# 1          2
 
3716
# 3          4
 
3717
sub create_table {
 
3718
   my ( $cols, $info, $data, $prefs ) = @_;
 
3719
   $prefs ||= {};
 
3720
   $prefs->{no_hdr} ||= ($opts{n} && $clock != 1);
 
3721
 
 
3722
   my @rows = ();
 
3723
 
 
3724
   if ( @$cols && %$info ) {
 
3725
 
 
3726
      # Fix undef values, collapse whitespace.
 
3727
      foreach my $row ( @$data ) {
 
3728
         map { $row->{$_} = collapse_ws($row->{$_}) } @$cols;
 
3729
      }
 
3730
 
 
3731
      my $col_sep = $opts{n} ? "\t" : '  ';
 
3732
 
 
3733
      # Find each column's max width.
 
3734
      my %width_for;
 
3735
      if ( !$opts{n} ) {
 
3736
         %width_for = map {
 
3737
            my $col_name  = $_;
 
3738
            my $max_width = max( length($info->{$_}->{hdr}), map { length($_->{$col_name}) } @$data);
 
3739
            if ( $info->{$col_name}->{maxw} ) {
 
3740
               $max_width = min( $max_width, $info->{$col_name}->{maxw} );
 
3741
            }
 
3742
            if ( $info->{$col_name}->{minw} ) {
 
3743
               $max_width = max( $max_width, $info->{$col_name}->{minw} );
 
3744
            }
 
3745
            $col_name => $max_width;
 
3746
         } @$cols;
 
3747
      }
 
3748
 
 
3749
      # The table header.
 
3750
      if ( !$prefs->{no_hdr} ) {
 
3751
         push @rows, $opts{n}
 
3752
            ? join( $col_sep, @$cols )
 
3753
            : join( $col_sep, map { sprintf( "%-$width_for{$_}s", trunc($info->{$_}->{hdr}, $width_for{$_}) ) } @$cols );
 
3754
         if ( $have_color && $config{header_highlight}->{val} ) {
 
3755
            push @rows, [ pop @rows, $config{header_highlight}->{val} ];
 
3756
         }
 
3757
         elsif ( !$opts{n} ) {
 
3758
            push @rows, join( $col_sep, map { "-" x $width_for{$_} } @$cols );
 
3759
         }
 
3760
      }
 
3761
 
 
3762
      # The table data.
 
3763
      if ( $opts{n} ) {
 
3764
         foreach my $item ( @$data ) {
 
3765
            push @rows, join($col_sep, map { $item->{$_} } @$cols );
 
3766
         }
 
3767
      }
 
3768
      else {
 
3769
         my $format = join( $col_sep,
 
3770
            map { "%$info->{$_}->{just}$width_for{$_}s" } @$cols );
 
3771
         foreach my $item ( @$data ) {
 
3772
            my $row = sprintf($format, map { trunc($item->{$_}, $width_for{$_}) } @$cols );
 
3773
            if ( $have_color && $item->{_color} ) {
 
3774
               push @rows, [ $row, $item->{_color} ];
 
3775
            }
 
3776
            else {
 
3777
               push @rows, $row;
 
3778
            }
 
3779
         }
 
3780
      }
 
3781
   }
 
3782
 
 
3783
   return @rows;
 
3784
}
 
3785
 
 
3786
# set_to_tbl {{{3
 
3787
# Unifies all the work of filtering, sorting etc.  Alters the input.
 
3788
sub set_to_tbl {
 
3789
   my ( $rows, $tbl ) = @_;
 
3790
   my $meta = $tbl_meta{$tbl} or die "No such table $tbl in tbl_meta";
 
3791
 
 
3792
   # Apply filters.
 
3793
   foreach my $filter ( @{$meta->{filters}} ) {
 
3794
      eval {
 
3795
         @$rows = grep { $filters{$filter}->{func}->($_) } @$rows;
 
3796
      };
 
3797
   }
 
3798
 
 
3799
   # Sort.
 
3800
   if ( @$rows && $meta->{sort_func} ) {
 
3801
      if ( $meta->{sort_dir} > 0 ) {
 
3802
         @$rows = $meta->{sort_func}->( @$rows );
 
3803
      }
 
3804
      else {
 
3805
         @$rows = reverse $meta->{sort_func}->( @$rows );
 
3806
      }
 
3807
   }
 
3808
 
 
3809
   # Stop altering arguments now.
 
3810
   my @rows = @$rows;
 
3811
 
 
3812
   # Colorize.  Adds a _color column to rows.
 
3813
   if ( @rows && $meta->{color_func} ) {
 
3814
      eval {
 
3815
         foreach my $row ( @rows ) {
 
3816
            $row->{_color} = $meta->{color_func}->($row);
 
3817
         }
 
3818
      };
 
3819
      if ( $EVAL_ERROR ) {
 
3820
         pause($EVAL_ERROR);
 
3821
      }
 
3822
   }
 
3823
 
 
3824
   # Apply_transformations.
 
3825
   if ( @rows ) {
 
3826
      my $cols = $meta->{cols};
 
3827
      foreach my $col ( keys %{$rows->[0]} ) {
 
3828
         # Don't auto-vivify $tbl_meta{tbl}-{cols}->{_color}->{trans}
 
3829
         next if $col eq '_color';
 
3830
         foreach my $trans ( @{$cols->{$col}->{trans}} ) {
 
3831
            map { $_->{$col} = $trans_funcs{$trans}->($_->{$col}) } @rows;
 
3832
         }
 
3833
      }
 
3834
   }
 
3835
 
 
3836
   @rows = create_table( $meta->{visible}, $meta->{cols}, \@rows);
 
3837
   if ( !$meta->{hide_hdr} && !$opts{n} && $config{display_table_captions}->{val} ) {
 
3838
      @rows = create_caption($meta->{hdr}, @rows)
 
3839
   }
 
3840
   return @rows;
 
3841
}
 
3842
 
 
3843
# meta_to_hdr {{{3
 
3844
sub meta_to_hdr {
 
3845
   my $tbl = shift;
 
3846
   my $meta = $tbl_meta{$tbl};
 
3847
   my %labels = map { $_ => $meta->{cols}->{$_}->{hdr} } @{$meta->{visible}};
 
3848
   return \%labels;
 
3849
}
 
3850
 
 
3851
 
 
3852
# commify {{{3
 
3853
# From perlfaq5: add commas.
 
3854
sub commify {
 
3855
   my $num = shift;
 
3856
   $num =~ s/(^[-+]?\d+?(?=(?>(?:\d{3})+)(?!\d))|\G\d{3}(?=\d))/$1,/g;
 
3857
   return $num;
 
3858
}
 
3859
 
 
3860
# set_precision {{{3
 
3861
# Trim to desired precision.
 
3862
sub set_precision {
 
3863
   my ( $num, $precision ) = @_;
 
3864
   sprintf("%.${precision}f", $num);
 
3865
}
 
3866
 
 
3867
# percent {{{3
 
3868
# Convert to percent
 
3869
sub percent {
 
3870
   my ( $num ) = @_;
 
3871
   my $digits = $config{num_digits}->{val};
 
3872
   sprintf("%.${digits}f", $num * 100);
 
3873
}
 
3874
 
 
3875
# shorten {{{3
 
3876
sub shorten {
 
3877
   my ( $num, $opts ) = @_;
 
3878
 
 
3879
   return $num if !defined($num) || $opts{n} || $num =~ m/[^\d\.-]/;
 
3880
 
 
3881
   $opts ||= {};
 
3882
   my $pad = defined $opts->{pad} ? $opts->{pad} : '';
 
3883
   my $num_digits = defined $opts->{num_digits}
 
3884
      ? $opts->{num_digits}
 
3885
      : $config{num_digits}->{val};
 
3886
   my $force = defined $opts->{force};
 
3887
 
 
3888
   my $n = 0;
 
3889
   while ( $num >= 1_024 ) {
 
3890
      $num /= 1_024;
 
3891
      ++$n;
 
3892
   }
 
3893
   return sprintf(
 
3894
      $num =~ m/\./ || $n || $force
 
3895
         ? "%.${num_digits}f%s"
 
3896
         : '%d',
 
3897
      $num, ($pad,'k','M','G', 'T')[$n]);
 
3898
 
 
3899
}
 
3900
 
 
3901
# Utility functions {{{2
 
3902
# unique {{{3
 
3903
sub unique {
 
3904
   my %seen;
 
3905
   return grep { !$seen{$_}++ } @_;
 
3906
}
 
3907
 
 
3908
# make_color_func {{{3
 
3909
sub make_color_func {
 
3910
   my ( $tbl ) = @_;
 
3911
   my @criteria;
 
3912
   foreach my $spec ( @{$tbl->{colors}} ) {
 
3913
      next unless exists $comp_ops{$spec->{op}};
 
3914
      my $val = $spec->{op} =~ m/^(?:eq|ne|le|ge|lt|gt)$/ ? "'$spec->{arg}'"
 
3915
              : $spec->{op} =~ m/^(?:=~|!~)$/             ? "m/" . quotemeta($spec->{arg}) . "/"
 
3916
              :                                             $spec->{arg};
 
3917
      push @criteria,
 
3918
         "( defined \$set->{$spec->{col}} && \$set->{$spec->{col}} $spec->{op} $val ) { return '$spec->{color}'; }";
 
3919
   }
 
3920
   return undef unless @criteria;
 
3921
   my $sub = eval 'sub { my ( $set ) = @_; if ' . join(" elsif ", @criteria) . '}';
 
3922
   die if $EVAL_ERROR;
 
3923
   return $sub;
 
3924
}
 
3925
 
 
3926
# make_sort_func {{{3
 
3927
# Accepts a list of sort columns, like "+cxn -time" and returns a subroutine that will
 
3928
# sort that way.
 
3929
sub make_sort_func {
 
3930
   my ( $tbl ) = @_;
 
3931
   my @criteria;
 
3932
   foreach my $col ( split(/\s+/, $tbl->{sort_cols} ) ) {
 
3933
      next unless $col;
 
3934
      my ( $dir, $name ) = $col =~ m/([+-])?(\w+)$/;
 
3935
      next unless $name && $tbl->{cols}->{$name};
 
3936
      $dir ||= '+';
 
3937
      my $op = $tbl->{cols}->{$name}->{num} ? "<=>" : "cmp";
 
3938
      my $df = $tbl->{cols}->{$name}->{num} ? "0"   : "''";
 
3939
      push @criteria,
 
3940
         $dir eq '+'
 
3941
         ? "(\$a->{$name} || $df) $op (\$b->{$name} || $df)"
 
3942
         : "(\$b->{$name} || $df) $op (\$a->{$name} || $df)";
 
3943
   }
 
3944
   return sub { return @_ } unless @criteria;
 
3945
   my $sub = eval 'sub { sort {' . join("||", @criteria) . '} @_; }';
 
3946
   die if $EVAL_ERROR;
 
3947
   return $sub;
 
3948
}
 
3949
 
 
3950
# trunc {{{3
 
3951
# Shortens text to specified length.
 
3952
sub trunc {
 
3953
   my ( $text, $len ) = @_;
 
3954
   if ( length($text) <= $len ) {
 
3955
      return $text;
 
3956
   }
 
3957
   return substr($text, 0, $len);
 
3958
}
 
3959
 
 
3960
# donut {{{3
 
3961
# Takes out the middle of text to shorten it.
 
3962
sub donut {
 
3963
   my ( $text, $len ) = @_;
 
3964
   return $text if length($text) <= $len;
 
3965
   my $max = length($text) - $len;
 
3966
   my $min = $max - 1;
 
3967
 
 
3968
   # Try to remove a single "word" from somewhere in the center
 
3969
   if ( $text =~ s/_[^_]{$min,$max}_/_/ ) {
 
3970
      return $text;
 
3971
   }
 
3972
 
 
3973
   # Prefer removing the end of a "word"
 
3974
   if ( $text =~ s/([^_]+)[^_]{$max}_/$1_/ ) {
 
3975
      return $text;
 
3976
   }
 
3977
 
 
3978
   $text = substr($text, 0, int($len/2))
 
3979
         . "_"
 
3980
         . substr($text, int($len/2) + $max + 1);
 
3981
   return $text;
 
3982
}
 
3983
 
 
3984
# crunch {{{3
 
3985
# Removes vowels and compacts repeated letters to shorten text.
 
3986
sub crunch {
 
3987
   my ( $text, $len ) = @_;
 
3988
   return $text if $len && length($text) <= $len;
 
3989
   $text =~ s/^IB_\w\w_//;
 
3990
   $text =~ s/(?<![_ ])[aeiou]//g;
 
3991
   $text =~ s/(.)\1+/$1/g;
 
3992
   return $text;
 
3993
}
 
3994
 
 
3995
# collapse_ws {{{3
 
3996
# Collapses all whitespace to a single space.
 
3997
sub collapse_ws {
 
3998
   my ( $text ) = @_;
 
3999
   return '' unless defined $text;
 
4000
   $text =~ s/\s+/ /g;
 
4001
   return $text;
 
4002
}
 
4003
 
 
4004
# Strips out non-printable characters within fields, which freak terminals out.
 
4005
sub no_ctrl_char {
 
4006
   my ( $text ) = @_;
 
4007
   return '' unless defined $text;
 
4008
   my $charset = $config{charset}->{val};
 
4009
   if ( $charset && $charset eq 'unicode' ) {
 
4010
      $text =~ s/
 
4011
         ("(?:(?!(?<!\\)").)*"  # Double-quoted string
 
4012
         |'(?:(?!(?<!\\)').)*') # Or single-quoted string
 
4013
         /$1 =~ m#\p{IsC}# ? "[BINARY]" : $1/egx;
 
4014
   }
 
4015
   elsif ( $charset && $charset eq 'none' ) {
 
4016
      $text =~ s/
 
4017
         ("(?:(?!(?<!\\)").)*"
 
4018
         |'(?:(?!(?<!\\)').)*')
 
4019
         /[TEXT]/gx;
 
4020
   }
 
4021
   else { # The default is 'ascii'
 
4022
      $text =~ s/
 
4023
         ("(?:(?!(?<!\\)").)*"
 
4024
         |'(?:(?!(?<!\\)').)*')
 
4025
         /$1 =~ m#[^\040-\176]# ? "[BINARY]" : $1/egx;
 
4026
   }
 
4027
   return $text;
 
4028
}
 
4029
 
 
4030
# word_wrap {{{3
 
4031
# Wraps text at word boundaries so it fits the screen.
 
4032
sub word_wrap {
 
4033
   my ( $text, $width) = @_;
 
4034
   $width ||= $this_term_size[0];
 
4035
   $text =~ s/(.{0,$width})(?:\s+|$)/$1\n/g;
 
4036
   $text =~ s/\s+$//;
 
4037
   return $text;
 
4038
}
 
4039
 
 
4040
# draw_screen {{{3
 
4041
# Prints lines to the screen.  The first argument is an arrayref.  Each
 
4042
# element of the array is either a string or an arrayref.  If it's a string it
 
4043
# just gets printed.  If it's an arrayref, the first element is the string to
 
4044
# print, and the second is args to colored().
 
4045
sub draw_screen {
 
4046
   my ( $display_lines, $prefs ) = @_;
 
4047
   if ( !$opts{n} && $config{show_statusbar}->{val} ) {
 
4048
      unshift @$display_lines, create_statusbar();
 
4049
   }
 
4050
   $clear_screen_sub->() unless $modes{$config{mode}->{val}}->{no_clear_screen};
 
4051
   if ( $opts{n} || $prefs->{raw} ) {
 
4052
      print join("\n",
 
4053
         map {
 
4054
            ref $_
 
4055
               ? colored($_->[0], $_->[1])
 
4056
               : $_;
 
4057
         }
 
4058
         grep { !$opts{n} || $_ } # When non-interactive, suppress empty lines
 
4059
         @$display_lines);
 
4060
      if ( $opts{n} ) {
 
4061
         print "\n";
 
4062
      }
 
4063
   }
 
4064
   elsif ( $prefs->{show_all} ) {
 
4065
      print join("\n",
 
4066
            map {
 
4067
               ref $_
 
4068
                  ? colored(substr($_->[0], 0, $this_term_size[0]), $_->[1])
 
4069
                  : substr($_, 0, $this_term_size[0]);
 
4070
            }
 
4071
         @$display_lines);
 
4072
   }
 
4073
   else {
 
4074
      my $max_lines = min(scalar(@$display_lines), $this_term_size[1]);
 
4075
      print join("\n",
 
4076
         map {
 
4077
            ref $_
 
4078
               ? colored(substr($_->[0], 0, $this_term_size[0]), $_->[1])
 
4079
               : substr($_, 0, $this_term_size[0]);
 
4080
         } @$display_lines[0..$max_lines - 1]);
 
4081
   }
 
4082
}
 
4083
 
 
4084
# secs_to_time {{{3
 
4085
sub secs_to_time {
 
4086
   my ( $secs, $fmt ) = @_;
 
4087
   $secs ||= 0;
 
4088
   return '00:00' unless $secs;
 
4089
 
 
4090
   # Decide what format to use, if not given
 
4091
   $fmt ||= $secs >= 86_400 ? 'd'
 
4092
          : $secs >= 3_600  ? 'h'
 
4093
          :                   'm';
 
4094
 
 
4095
   return
 
4096
      $fmt eq 'd' ? sprintf(
 
4097
         "%d+%02d:%02d:%02d",
 
4098
         int($secs / 86_400),
 
4099
         int(($secs % 86_400) / 3_600),
 
4100
         int(($secs % 3_600) / 60),
 
4101
         $secs % 60)
 
4102
      : $fmt eq 'h' ? sprintf(
 
4103
         "%02d:%02d:%02d",
 
4104
         int(($secs % 86_400) / 3_600),
 
4105
         int(($secs % 3_600) / 60),
 
4106
         $secs % 60)
 
4107
      : sprintf(
 
4108
         "%02d:%02d",
 
4109
         int(($secs % 3_600) / 60),
 
4110
         $secs % 60);
 
4111
}
 
4112
 
 
4113
# dulint_to_int {{{3
 
4114
# Takes a number that InnoDB formats as two ulint integers, like transaction IDs
 
4115
# and such, and turns it into a single integer
 
4116
sub dulint_to_int {
 
4117
   my $num = shift;
 
4118
   return 0 unless $num;
 
4119
   my ( $high, $low ) = $num =~ m/^(\d+) (\d+)$/;
 
4120
   return $low unless $high;
 
4121
   return $low + ( $high * $MAX_ULONG );
 
4122
}
 
4123
 
 
4124
# create_statusbar {{{3
 
4125
sub create_statusbar {
 
4126
   my $mode = $config{mode}->{val};
 
4127
   my @cxns = sort { $a cmp $b } get_connections();
 
4128
 
 
4129
   my $modeline        = ( $config{readonly}->{val} ? '[RO] ' : '' )
 
4130
                         . $modes{$mode}->{hdr} . " (? for help)";
 
4131
   my $mode_width      = length($modeline);
 
4132
   my $remaining_width = $this_term_size[0] - $mode_width - 1;
 
4133
   my $result;
 
4134
 
 
4135
   # The thingie in top-right that says what we're monitoring.
 
4136
   my $cxn = '';
 
4137
 
 
4138
   if ( 1 == @cxns ) {
 
4139
      $cxn = $dbhs{$cxns[0]}->{mysql_version};
 
4140
   }
 
4141
   else {
 
4142
      if ( $modes{$mode}->{server_group} ) {
 
4143
         $cxn = "Servers: " . $modes{$mode}->{server_group};
 
4144
         my $err_count = grep { $dbhs{$_}->{err_count} } @cxns;
 
4145
         if ( $err_count ) {
 
4146
            $cxn .= "(" . ( scalar(@cxns) - $err_count ) . "/" . scalar(@cxns) . ")";
 
4147
         }
 
4148
      }
 
4149
      else {
 
4150
         $cxn = join(' ', map { ($dbhs{$_}->{err_count} ? '!' : '') . $_ } @cxns);
 
4151
      }
 
4152
   }
 
4153
 
 
4154
   if ( 1 == @cxns ) {
 
4155
      get_status_info(@cxns);
 
4156
      my $vars = $vars{$cxns[0]}->{$clock};
 
4157
 
 
4158
      # Format server uptime human-readably.
 
4159
      my $uptime = secs_to_time( $vars->{Uptime} );
 
4160
      my $inc    = inc(0, $cxns[0]);
 
4161
      my $qps    = set_precision($exprs{QPS}->{func}->($inc), 2);
 
4162
      my $ibinfo = '';
 
4163
 
 
4164
      if ( exists $vars->{IB_last_secs} ) {
 
4165
         $ibinfo .= "InnoDB $vars->{IB_last_secs} sec ";
 
4166
         if ( $vars->{IB_got_all} ) {
 
4167
            if ( ($mode eq 'T' || $mode eq 'W')
 
4168
                  && $vars->{IB_tx_is_truncated} ) {
 
4169
               $ibinfo .= ':^|, ';
 
4170
            }
 
4171
            else {
 
4172
               $ibinfo .= ':-), ';
 
4173
            }
 
4174
         }
 
4175
         else {
 
4176
            $ibinfo .= ':-(, ';
 
4177
         }
 
4178
      }
 
4179
      $result = sprintf(
 
4180
         "%-${mode_width}s %${remaining_width}s",
 
4181
         $modeline,
 
4182
         $ibinfo . join(', ',
 
4183
            "$qps QPS",
 
4184
            $cxns[0],
 
4185
            ($vars->{Threads_connected} || 0) . " thd",
 
4186
            $uptime,
 
4187
            $cxn));
 
4188
   }
 
4189
   else {
 
4190
      $result = sprintf(
 
4191
         "%-${mode_width}s %${remaining_width}s",
 
4192
         $modeline,
 
4193
         $cxn);
 
4194
   }
 
4195
 
 
4196
   return [ $result, 'bold reverse' ];
 
4197
}
 
4198
 
 
4199
# Database connections {{{3
 
4200
sub add_new_dsn {
 
4201
   my ( $name ) = @_;
 
4202
 
 
4203
   if ( defined $name ) {
 
4204
      $name =~ s/[\s:;]//g;
 
4205
   }
 
4206
 
 
4207
   if ( !$name ) {
 
4208
      print word_wrap("Choose a name for the connection.  It cannot contain "
 
4209
         . "whitespace, colons or semicolons."), "\n\n";
 
4210
      do {
 
4211
         $name = prompt("Enter a name");
 
4212
         $name =~ s/[\s:;]//g;
 
4213
      } until ( $name );
 
4214
   }
 
4215
 
 
4216
   my $dsn;
 
4217
   do {
 
4218
      $clear_screen_sub->();
 
4219
      print "Typical DSN strings look like\n   DBI:mysql:db;host=hostname;port=port\n"
 
4220
         . "The db and port are optional and can typically be omitted.\n\n";
 
4221
      $dsn = prompt("Enter a DSN string", undef, "DBI:mysql:;host=$name");
 
4222
   } until ( $dsn );
 
4223
 
 
4224
   my $user = $ENV{USERNAME} || $ENV{USER} || getlogin() || getpwuid($REAL_USER_ID) || undef;
 
4225
   do {
 
4226
      $clear_screen_sub->();
 
4227
      $user = prompt("Enter a username for $name", undef, $user);
 
4228
   } until ( $user );
 
4229
 
 
4230
   $clear_screen_sub->();
 
4231
   my $dl_table = prompt("Optional: enter a table (must not exist) to use when resetting InnoDB deadlock information",
 
4232
      undef, 'test.innodb_deadlock_maker');
 
4233
 
 
4234
   $connections{$name} = {
 
4235
      dsn      => $dsn,
 
4236
      user     => $user,
 
4237
      dl_table => $dl_table,
 
4238
   };
 
4239
}
 
4240
 
 
4241
sub add_new_server_group {
 
4242
   my ( $name ) = @_;
 
4243
 
 
4244
   if ( defined $name ) {
 
4245
      $name =~ s/[\s:;]//g;
 
4246
   }
 
4247
 
 
4248
   if ( !$name ) {
 
4249
      print word_wrap("Choose a name for the group.  It cannot contain "
 
4250
         . "whitespace, colons or semicolons."), "\n\n";
 
4251
      do {
 
4252
         $name = prompt("Enter a name");
 
4253
         $name =~ s/[\s:;]//g;
 
4254
      } until ( $name );
 
4255
   }
 
4256
 
 
4257
   my @cxns;
 
4258
   do {
 
4259
      $clear_screen_sub->();
 
4260
      @cxns = select_cxn("Choose servers for $name", keys %connections);
 
4261
   } until ( @cxns );
 
4262
 
 
4263
   $server_groups{$name} = \@cxns;
 
4264
}
 
4265
 
 
4266
sub get_var_set {
 
4267
   my ( $name ) = @_;
 
4268
   while ( !exists($var_sets{$config{$name}->{val}}) ) {
 
4269
      $name = choose_var_set($name);
 
4270
   }
 
4271
   return $var_sets{$config{$name}->{val}};
 
4272
}
 
4273
 
 
4274
sub add_new_var_set {
 
4275
   my ( $name ) = @_;
 
4276
 
 
4277
   if ( defined $name ) {
 
4278
      $name =~ s/\W//g;
 
4279
   }
 
4280
 
 
4281
   if ( !$name ) {
 
4282
      do {
 
4283
         $name = prompt("Enter a name");
 
4284
         $name =~ s/\W//g;
 
4285
      } until ( $name );
 
4286
   }
 
4287
 
 
4288
   my $variables;
 
4289
   do {
 
4290
      $clear_screen_sub->();
 
4291
      $variables = prompt("Enter variables for $name", undef );
 
4292
   } until ( $variables );
 
4293
 
 
4294
   $var_sets{$name} = [ unique(grep { $_ } split(/\s+/, $variables)) ];
 
4295
}
 
4296
 
 
4297
sub next_server_group {
 
4298
   my $mode = shift || $config{mode}->{val};
 
4299
   my @grps = sort keys %server_groups;
 
4300
   my $curr = $modes{$mode}->{server_group};
 
4301
 
 
4302
   return unless @grps;
 
4303
 
 
4304
   if ( $curr ) {
 
4305
      # Find the current group's position.
 
4306
      my $pos = 0;
 
4307
      while ( $curr ne $grps[$pos] ) {
 
4308
         $pos++;
 
4309
      }
 
4310
      $modes{$mode}->{server_group} = $grps[ ($pos + 1) % @grps ];
 
4311
   }
 
4312
   else {
 
4313
      $modes{$mode}->{server_group} = $grps[0];
 
4314
   }
 
4315
}
 
4316
 
 
4317
# Get a list of connection names used in this mode.
 
4318
sub get_connections {
 
4319
   my $mode = shift || $config{mode}->{val};
 
4320
   my @connections = $modes{$mode}->{server_group}
 
4321
      ? @{$server_groups{$modes{$mode}->{server_group}}}
 
4322
      : @{$modes{$mode}->{connections}};
 
4323
   if ( $modes{$mode}->{one_connection} ) {
 
4324
      @connections = @connections ? $connections[0] : ();
 
4325
   }
 
4326
   return unique(@connections);
 
4327
}
 
4328
 
 
4329
# Get a list of tables used in this mode.  If innotop is running non-interactively, just use the first.
 
4330
sub get_visible_tables {
 
4331
   my $mode = shift || $config{mode}->{val};
 
4332
   my @tbls = @{$modes{$mode}->{visible_tables}};
 
4333
   if ( $opts{n} ) {
 
4334
      return $tbls[0];
 
4335
   }
 
4336
   else {
 
4337
      return @tbls;
 
4338
   }
 
4339
}
 
4340
 
 
4341
# Choose from among available connections or server groups.
 
4342
# If the mode has a server set in use, prefers that instead.
 
4343
sub choose_connections {
 
4344
   $clear_screen_sub->();
 
4345
   my $mode    = $config{mode}->{val};
 
4346
   my $meta    =  { map { $_ => $connections{$_}->{dsn} } keys %connections };
 
4347
   foreach my $group ( keys %server_groups ) {
 
4348
      $meta->{"#$group"} = join(' ', @{$server_groups{$group}});
 
4349
   }
 
4350
 
 
4351
   my $choices = prompt_list("Choose connections or a group for $mode mode",
 
4352
      undef, sub { return keys %$meta }, $meta);
 
4353
 
 
4354
   my @choices = unique(grep { $_ } split(/\s+/, $choices));
 
4355
   if ( @choices ) {
 
4356
      if ( $choices[0] =~ s/^#// && exists $server_groups{$choices[0]} ) {
 
4357
         $modes{$mode}->{server_group} = $choices[0];
 
4358
      }
 
4359
      else {
 
4360
         $modes{$mode}->{connections} = [ grep { exists $connections{$_} } @choices ];
 
4361
      }
 
4362
   }
 
4363
}
 
4364
 
 
4365
# Accepts a DB connection name and the name of a prepared query (e.g. status, kill).
 
4366
# Also a list of params for the prepared query.  This allows not storing prepared
 
4367
# statements globally.  Returns a $sth that's been executed.
 
4368
# ERROR-HANDLING SEMANTICS: if the statement throws an error, propagate, but if the
 
4369
# connection has gone away or can't connect, DO NOT.  Just return undef.
 
4370
sub do_stmt {
 
4371
   my ( $cxn, $stmt_name, @args ) = @_;
 
4372
 
 
4373
   # Test if the cxn should not even be tried
 
4374
   return undef
 
4375
      if $dbhs{$cxn} && $dbhs{$cxn}->{err_count} && $dbhs{$cxn}->{wake_up} > $clock;
 
4376
 
 
4377
   my $sth;
 
4378
   my $retries = 1;
 
4379
   my $success = 0;
 
4380
   TRY:
 
4381
   while ( $retries-- >= 0 && !$success ) {
 
4382
 
 
4383
      eval {
 
4384
         my $dbh = connect_to_db($cxn);
 
4385
 
 
4386
         # If the prepared query doesn't exist, make it.
 
4387
         if ( !exists $dbhs{$cxn}->{stmts}->{$stmt_name} ) {
 
4388
            $dbhs{$cxn}->{stmts}->{$stmt_name}
 
4389
               = $dbh->prepare($stmt_maker_for{$stmt_name}->($cxn));
 
4390
         }
 
4391
 
 
4392
         $sth = $dbhs{$cxn}->{stmts}->{$stmt_name};
 
4393
         $sth->execute(@args);
 
4394
         $success = 1;
 
4395
      };
 
4396
      if ( $EVAL_ERROR ) {
 
4397
         my $errs = join('|',
 
4398
            'Access denied for user',
 
4399
            'Unknown MySQL server host',
 
4400
            'Unknown database',
 
4401
            'Can\'t connect to local MySQL server through socket',
 
4402
            'Can\'t connect to MySQL server on',
 
4403
            'MySQL server has gone away',
 
4404
            'Cannot call SHOW INNODB STATUS',
 
4405
            'Access denied',
 
4406
         );
 
4407
         if ( $EVAL_ERROR =~ m/$errs/ ) {
 
4408
            handle_cxn_error($cxn, $EVAL_ERROR);
 
4409
         }
 
4410
         else {
 
4411
            die $EVAL_ERROR;
 
4412
         }
 
4413
         if ( $retries < 0 ) {
 
4414
            $sth = undef;
 
4415
         }
 
4416
      }
 
4417
   }
 
4418
 
 
4419
   return $sth;
 
4420
}
 
4421
 
 
4422
# Keeps track of error count, sleep times till retries, etc etc.
 
4423
# When there's an error we retry the connection every so often, increasing in
 
4424
# Fibonacci series to prevent too much banging on the server.
 
4425
sub handle_cxn_error {
 
4426
   my ( $cxn, $err ) = @_;
 
4427
   my $meta = $dbhs{$cxn};
 
4428
   $meta->{err_count}++;
 
4429
 
 
4430
   # Strip garbage from the error text if possible.
 
4431
   $err =~ s/\s+/ /g;
 
4432
   if ( $err =~ m/failed: (.*?) at \S*innotop line/ ) {
 
4433
      $err = $1;
 
4434
   }
 
4435
 
 
4436
   $meta->{last_err}   = $err;
 
4437
   my $sleep_time      = $meta->{this_sleep} + $meta->{prev_sleep};
 
4438
   $meta->{prev_sleep} = $meta->{this_sleep};
 
4439
   $meta->{this_sleep} = $sleep_time;
 
4440
   $meta->{wake_up}    = $clock + $sleep_time;
 
4441
   if ( $config{show_cxn_errors}->{val} ) {
 
4442
      print STDERR "Error at tick $clock $cxn $err" if $config{debug}->{val};
 
4443
   }
 
4444
}
 
4445
 
 
4446
# Accepts a DB connection name and a (string) query.  Returns a $sth that's been
 
4447
# executed.
 
4448
sub do_query {
 
4449
   my ( $cxn, $query ) = @_;
 
4450
 
 
4451
   # Test if the cxn should not even be tried
 
4452
   return undef
 
4453
      if $dbhs{$cxn} && $dbhs{$cxn}->{err_count} && $dbhs{$cxn}->{wake_up} > $clock;
 
4454
 
 
4455
   my $sth;
 
4456
   my $retries = 1;
 
4457
   my $success = 0;
 
4458
   TRY:
 
4459
   while ( $retries-- >= 0 && !$success ) {
 
4460
 
 
4461
      eval {
 
4462
         my $dbh = connect_to_db($cxn);
 
4463
 
 
4464
         $sth = $dbh->prepare($query);
 
4465
         $sth->execute();
 
4466
         $success = 1;
 
4467
      };
 
4468
      if ( $EVAL_ERROR ) {
 
4469
         my $errs = join('|',
 
4470
            'Access denied for user',
 
4471
            'Unknown MySQL server host',
 
4472
            'Unknown database',
 
4473
            'Can\'t connect to local MySQL server through socket',
 
4474
            'Can\'t connect to MySQL server on',
 
4475
            'MySQL server has gone away',
 
4476
         );
 
4477
         if ( $EVAL_ERROR =~ m/$errs/ ) {
 
4478
            handle_cxn_error($cxn, $EVAL_ERROR);
 
4479
         }
 
4480
         else {
 
4481
            die $EVAL_ERROR;
 
4482
         }
 
4483
         if ( $retries < 0 ) {
 
4484
            $sth = undef;
 
4485
         }
 
4486
      }
 
4487
   }
 
4488
 
 
4489
   return $sth;
 
4490
}
 
4491
 
 
4492
sub connect_to_db {
 
4493
   my ( $cxn ) = @_;
 
4494
 
 
4495
   $dbhs{$cxn} ||= {
 
4496
      stmts      => {},  # bucket for prepared statements.
 
4497
      prev_sleep => 0,
 
4498
      this_sleep => 1,
 
4499
      wake_up    => 0,
 
4500
      start_time => 0,
 
4501
      dbh        => undef,
 
4502
   };
 
4503
   my $href = $dbhs{$cxn};
 
4504
 
 
4505
   if ( !$href->{dbh} || !$href->{dbh}->ping ) {
 
4506
      my $dbh = get_new_db_connection($cxn);
 
4507
      $href->{dbh} = $dbh;
 
4508
 
 
4509
      # Get version and connection ID.  This is necessary to do repeatedly
 
4510
      # because we may disconnect and connect again.
 
4511
      my ($version, $connection_id)
 
4512
         = $dbh->selectrow_array("SELECT VERSION(), CONNECTION_ID()");
 
4513
      @{$href}{qw(mysql_version connection_id)} = ($version, $connection_id);
 
4514
      @{$href}{qw(ver_major ver_minor ver_rev)} = $version =~ m/^(\d+)\.(\d+)\.(\d+)/;
 
4515
 
 
4516
      # Derive and store the server's start time in hi-res
 
4517
      my $uptime = $dbh->selectrow_hashref("show status like 'Uptime'")->{Value};
 
4518
      $href->{start_time} = time() - $uptime;
 
4519
 
 
4520
      # Set timeouts to 8 hours so an unused connection stays alive
 
4521
      # (for example, a connection might be used in Q mode but idle in T mode).
 
4522
      $dbh->do("set session wait_timeout=28800, interactive_timeout=28800");
 
4523
   }
 
4524
   return $href->{dbh};
 
4525
}
 
4526
 
 
4527
sub get_new_db_connection {
 
4528
   my ( $connection, $destroy ) = @_;
 
4529
   my $dsn = $connections{$connection};
 
4530
   if ( !$dsn->{pass} && !$dsn->{savepass} ) {
 
4531
      $dsn->{pass} = prompt_noecho("Enter password for $dsn->{user} on $connection");
 
4532
      if ( !defined($dsn->{savepass}) ) {
 
4533
         print "\n";
 
4534
         $dsn->{savepass} = prompt("Save password in plain text in the config file? 1 or 0", undef, 1);
 
4535
      }
 
4536
   }
 
4537
   my $dbh = DBI->connect(
 
4538
      $dsn->{dsn}, $dsn->{user}, $dsn->{pass},
 
4539
      { RaiseError => 1, PrintError => 0, AutoCommit => 1 });
 
4540
   $dbh->{InactiveDestroy} = 1 unless $destroy; # Can't be set in $db_options
 
4541
   return $dbh;
 
4542
}
 
4543
 
 
4544
sub get_cxn_errors {
 
4545
   my @cxns = @_;
 
4546
   return () unless $config{show_cxn_errors_in_tbl}->{val};
 
4547
   return
 
4548
      map  { [ $_ . ': ' . $dbhs{$_}->{last_err}, 'red' ] }
 
4549
      grep { $dbhs{$_}->{err_count} }
 
4550
      @cxns;
 
4551
}
 
4552
 
 
4553
# Setup and tear-down functions {{{2
 
4554
# compile_filter {{{3
 
4555
sub compile_filter {
 
4556
   my ( $text ) = @_;
 
4557
   my ( $sub, $err );
 
4558
   eval "\$sub = sub { my \$set = shift; $text }";
 
4559
   if ( $EVAL_ERROR ) {
 
4560
      $sub = sub { return $EVAL_ERROR };
 
4561
      $err = $EVAL_ERROR;
 
4562
   }
 
4563
   return ( $sub, $err );
 
4564
}
 
4565
 
 
4566
# compile_expr {{{3
 
4567
# TODO: strip off "at (eval..." from error.
 
4568
sub compile_expr {
 
4569
   my ( $expr, $simple ) = @_;
 
4570
   if ( $simple ) {
 
4571
      $expr =~ s/([A-Za-z]\w+)/\$set->{$1}/g;
 
4572
   }
 
4573
   my ( $sub, $err );
 
4574
   eval "\$sub = sub { my \$set = shift; $expr }";
 
4575
   if ( $EVAL_ERROR ) {
 
4576
      $sub = sub { return $EVAL_ERROR };
 
4577
      $err = $EVAL_ERROR;
 
4578
   }
 
4579
   return ( $sub, $err );
 
4580
}
 
4581
 
 
4582
# finish {{{3
 
4583
# This is a subroutine because it's called from a key to quit the program.
 
4584
sub finish {
 
4585
   save_config();
 
4586
   ReadMode('normal') unless $opts{n};
 
4587
   print "\n";
 
4588
   exit(0);
 
4589
}
 
4590
 
 
4591
# core_dump {{{3
 
4592
sub core_dump {
 
4593
   my $msg = shift;
 
4594
   if ($config{debugfile}->{val} && $config{debug}->{val}) {
 
4595
      eval {
 
4596
         open my $file, '>>', $config{debugfile}->{val};
 
4597
         if ( %vars ) {
 
4598
            print $file "Current variables:\n" . Dumper(\%vars);
 
4599
         }
 
4600
         close $file;
 
4601
      };
 
4602
   }
 
4603
   print $msg;
 
4604
}
 
4605
 
 
4606
# load_config {{{3
 
4607
sub load_config {
 
4608
 
 
4609
   my $filename = $opts{c} || "$homepath/.innotop";
 
4610
   if ( -f $filename ) {
 
4611
      open my $file, "<", $filename or die("Can't open $filename: $OS_ERROR");
 
4612
 
 
4613
      # Check config file version.  Just ignore if either innotop or the file has
 
4614
      # garbage in the version number.
 
4615
      if ( defined(my $line = <$file>) && $VERSION =~ m/\d/ ) {
 
4616
         chomp $line;
 
4617
         if ( my ($maj, $min, $rev) = $line =~ m/^version=(\d+)\.(\d+)(?:\.(\d+))?$/ ) {
 
4618
            $rev ||= 0;
 
4619
            my $cfg_ver          = sprintf('%03d-%03d-%03d', $maj, $min, $rev);
 
4620
            ( $maj, $min, $rev ) = $VERSION =~ m/^(\d+)\.(\d+)(?:\.(\d+))?$/;
 
4621
            $rev ||= 0;
 
4622
            my $innotop_ver      = sprintf('%03d-%03d-%03d', $maj, $min, $rev);
 
4623
 
 
4624
            if ( $cfg_ver gt $innotop_ver ) {
 
4625
               pause("The config file is for a newer version of innotop and may not be read correctly.");
 
4626
            }
 
4627
            else {
 
4628
               my @ver_history = @config_versions;
 
4629
               while ( my ($start, $end) = splice(@ver_history, 0, 2) ) {
 
4630
                  # If the config file is between the endpoints and innotop is greater than
 
4631
                  # the endpoint, innotop has a newer config file format than the file.
 
4632
                  if ( $cfg_ver ge $start && $cfg_ver lt $end && $innotop_ver ge $end ) {
 
4633
                     my $msg = "Innotop's config file format has changed.  Overwrite $filename?  y or n";
 
4634
                     if ( pause($msg) eq 'n' ) {
 
4635
                        $config{readonly}->{val} = 1;
 
4636
                        print "\nInnotop will not save any configuration changes you make.";
 
4637
                        pause();
 
4638
                        print "\n";
 
4639
                     }
 
4640
                     close $file;
 
4641
                     return;
 
4642
                  }
 
4643
               }
 
4644
            }
 
4645
         }
 
4646
      }
 
4647
 
 
4648
      while ( my $line = <$file> ) {
 
4649
         chomp $line;
 
4650
         next unless $line =~ m/^\[([a-z_]+)\]$/;
 
4651
         if ( exists $config_file_sections{$1} ) {
 
4652
            $config_file_sections{$1}->{reader}->($file);
 
4653
         }
 
4654
         else {
 
4655
            warn "Unknown config file section '$1'";
 
4656
         }
 
4657
      }
 
4658
      close $file or die("Can't close $filename: $OS_ERROR");
 
4659
   }
 
4660
 
 
4661
}
 
4662
 
 
4663
# Do some post-processing on %tbl_meta: compile src properties into func etc.
 
4664
# TODO: allow 'foo/bar as bif' syntax.
 
4665
sub post_process_tbl_meta {
 
4666
   foreach my $table ( values %tbl_meta ) {
 
4667
      foreach my $col_name ( keys %{$table->{cols}} ) {
 
4668
         my $col_def = $table->{cols}->{$col_name};
 
4669
         my ( $sub, $err )
 
4670
            = $col_def->{expr}
 
4671
            ? $col_def->{src}->{func} # Already compiled
 
4672
            : compile_expr($col_def->{src}, 1);
 
4673
         $col_def->{func} = $sub;
 
4674
      }
 
4675
   }
 
4676
}
 
4677
 
 
4678
# load_config_active_server_groups {{{3
 
4679
sub load_config_active_server_groups {
 
4680
   my ( $file ) = @_;
 
4681
   while ( my $line = <$file> ) {
 
4682
      chomp $line;
 
4683
      next if $line =~ m/^#/;
 
4684
      last if $line =~ m/^\[/;
 
4685
 
 
4686
      my ( $mode, $group ) = $line =~ m/^(.*?)=(.*)$/;
 
4687
      next unless $mode && $group;
 
4688
      $modes{$mode}->{server_group} = $group;
 
4689
   }
 
4690
}
 
4691
 
 
4692
# save_config_active_server_groups {{{3
 
4693
sub save_config_active_server_groups {
 
4694
   my $file = shift;
 
4695
   foreach my $mode ( keys %modes ) {
 
4696
      print $file "$mode=$modes{$mode}->{server_group}\n";
 
4697
   }
 
4698
}
 
4699
 
 
4700
# load_config_server_groups {{{3
 
4701
sub load_config_server_groups {
 
4702
   my ( $file ) = @_;
 
4703
   while ( my $line = <$file> ) {
 
4704
      chomp $line;
 
4705
      next if $line =~ m/^#/;
 
4706
      last if $line =~ m/^\[/;
 
4707
 
 
4708
      my ( $name, $rest ) = $line =~ m/^(.*?)=(.*)$/;
 
4709
      next unless $name && $rest;
 
4710
      my @vars = unique(grep { $_ && exists $connections{$_} } split(/\s+/, $rest));
 
4711
      next unless @vars;
 
4712
      $server_groups{$name} = \@vars;
 
4713
   }
 
4714
}
 
4715
 
 
4716
# save_config_server_groups {{{3
 
4717
sub save_config_server_groups {
 
4718
   my $file = shift;
 
4719
   foreach my $set ( keys %server_groups ) {
 
4720
      print $file "$set=", join(' ', @{$server_groups{$set}}), "\n";
 
4721
   }
 
4722
}
 
4723
 
 
4724
# load_config_varsets {{{3
 
4725
sub load_config_varsets {
 
4726
   my ( $file ) = @_;
 
4727
   while ( my $line = <$file> ) {
 
4728
      chomp $line;
 
4729
      next if $line =~ m/^#/;
 
4730
      last if $line =~ m/^\[/;
 
4731
 
 
4732
      my ( $name, $rest ) = $line =~ m/^(.*?)=(.*)$/;
 
4733
      next unless $name && $rest;
 
4734
      my @vars = unique(map { $_ } split(/\s+/, $rest));
 
4735
      next unless @vars;
 
4736
      $var_sets{$name} = \@vars;
 
4737
   }
 
4738
}
 
4739
 
 
4740
# save_config_varsets {{{3
 
4741
sub save_config_varsets {
 
4742
   my $file = shift;
 
4743
   foreach my $varset ( keys %var_sets ) {
 
4744
      print $file "$varset=", join(' ', @{$var_sets{$varset}}), "\n";
 
4745
   }
 
4746
}
 
4747
 
 
4748
# load_config_filters {{{3
 
4749
sub load_config_filters {
 
4750
   my ( $file ) = @_;
 
4751
   while ( my $line = <$file> ) {
 
4752
      chomp $line;
 
4753
      next if $line =~ m/^#/;
 
4754
      last if $line =~ m/^\[/;
 
4755
 
 
4756
      my ( $key, $rest ) = $line =~ m/^(.+?)=(.*)$/;
 
4757
      next unless $key && $rest;
 
4758
 
 
4759
      my %parts = $rest =~ m/(\w+)='((?:(?!(?<!\\)').)*)'/g; # Properties are single-quoted
 
4760
      next unless $parts{text} && $parts{tbls};
 
4761
 
 
4762
      foreach my $prop ( keys %parts ) {
 
4763
         # Un-escape escaping
 
4764
         $parts{$prop} =~ s/\\\\/\\/g;
 
4765
         $parts{$prop} =~ s/\\'/'/g;
 
4766
      }
 
4767
 
 
4768
      my ( $sub, $err ) = compile_filter($parts{text});
 
4769
      my @tbls = unique(split(/\s+/, $parts{tbls}));
 
4770
      @tbls = grep { exists $tbl_meta{$_} } @tbls;
 
4771
      $filters{$key} = {
 
4772
         func => $sub,
 
4773
         text => $parts{text},
 
4774
         user => 1,
 
4775
         name => $key,
 
4776
         note => 'User-defined filter',
 
4777
         tbls => \@tbls,
 
4778
      }
 
4779
   }
 
4780
}
 
4781
 
 
4782
# save_config_filters {{{3
 
4783
sub save_config_filters {
 
4784
   my $file = shift;
 
4785
   foreach my $key ( keys %filters ) {
 
4786
      next unless $filters{$key}->{user};
 
4787
      my $text = $filters{$key}->{text};
 
4788
      $text =~ s/([\\'])/\\$1/g;
 
4789
      my $tbls = join(" ", @{$filters{$key}->{tbls}});
 
4790
      print $file "$key=text='$text' tbls='$tbls'\n";
 
4791
   }
 
4792
}
 
4793
 
 
4794
# load_config_visible_tables {{{3
 
4795
sub load_config_visible_tables {
 
4796
   my ( $file ) = @_;
 
4797
   while ( my $line = <$file> ) {
 
4798
      chomp $line;
 
4799
      next if $line =~ m/^#/;
 
4800
      last if $line =~ m/^\[/;
 
4801
 
 
4802
      my ( $mode, $rest ) = $line =~ m/^(.*?)=(.*)$/;
 
4803
      next unless $mode;
 
4804
      if ( exists $modes{$mode} ) {
 
4805
         $modes{$mode}->{visible_tables} =
 
4806
            [ unique(grep { $_ && exists $tbl_meta{$_} } split(/\s+/, $rest)) ];
 
4807
      }
 
4808
   }
 
4809
}
 
4810
 
 
4811
# save_config_visible_tables {{{3
 
4812
sub save_config_visible_tables {
 
4813
   my $file = shift;
 
4814
   foreach my $mode ( keys %modes ) {
 
4815
      my $tables = $modes{$mode}->{visible_tables};
 
4816
      print $file "$mode=", join(' ', @$tables), "\n";
 
4817
   }
 
4818
}
 
4819
 
 
4820
# load_config_sort_cols {{{3
 
4821
sub load_config_sort_cols {
 
4822
   my ( $file ) = @_;
 
4823
   while ( my $line = <$file> ) {
 
4824
      chomp $line;
 
4825
      next if $line =~ m/^#/;
 
4826
      last if $line =~ m/^\[/;
 
4827
 
 
4828
      my ( $key , $rest ) = $line =~ m/^(.*?)=(.*)$/;
 
4829
      next unless $key;
 
4830
      $tbl_meta{$key}->{sort_cols} = $rest;
 
4831
      $tbl_meta{$key}->{sort_func} = make_sort_func($tbl_meta{$key});
 
4832
   }
 
4833
}
 
4834
 
 
4835
# save_config_sort_cols {{{3
 
4836
sub save_config_sort_cols {
 
4837
   my $file = shift;
 
4838
   foreach my $tbl ( keys %tbl_meta ) {
 
4839
      my $col = $tbl_meta{$tbl}->{sort_cols};
 
4840
      print $file "$tbl=$col\n";
 
4841
   }
 
4842
}
 
4843
 
 
4844
# load_config_active_filters {{{3
 
4845
sub load_config_active_filters {
 
4846
   my ( $file ) = @_;
 
4847
   while ( my $line = <$file> ) {
 
4848
      chomp $line;
 
4849
      next if $line =~ m/^#/;
 
4850
      last if $line =~ m/^\[/;
 
4851
 
 
4852
      my ( $tbl , $rest ) = $line =~ m/^(.*?)=(.*)$/;
 
4853
      next unless $tbl && exists $tbl_meta{$tbl};
 
4854
      my @parts = unique(grep { exists($filters{$_}) } split(/\s+/, $rest));
 
4855
      @parts = grep { grep { $tbl eq $_ } @{$filters{$_}->{tbls}} } @parts;
 
4856
      $tbl_meta{$tbl}->{filters} = [ @parts ];
 
4857
   }
 
4858
}
 
4859
 
 
4860
# save_config_active_filters {{{3
 
4861
sub save_config_active_filters {
 
4862
   my $file = shift;
 
4863
   foreach my $tbl ( keys %tbl_meta ) {
 
4864
      my $aref = $tbl_meta{$tbl}->{filters};
 
4865
      print $file "$tbl=", join(' ', @$aref), "\n";
 
4866
   }
 
4867
}
 
4868
 
 
4869
# load_config_active_columns {{{3
 
4870
sub load_config_active_columns {
 
4871
   my ( $file ) = @_;
 
4872
   while ( my $line = <$file> ) {
 
4873
      chomp $line;
 
4874
      next if $line =~ m/^#/;
 
4875
      last if $line =~ m/^\[/;
 
4876
 
 
4877
      my ( $key , $rest ) = $line =~ m/^(.*?)=(.*)$/;
 
4878
      next unless $key && exists $tbl_meta{$key};
 
4879
      my @parts = grep { exists($tbl_meta{$key}->{cols}->{$_}) } unique split(/ /, $rest);
 
4880
      $tbl_meta{$key}->{visible} = [ @parts ];
 
4881
   }
 
4882
}
 
4883
 
 
4884
# save_config_active_columns {{{3
 
4885
sub save_config_active_columns {
 
4886
   my $file = shift;
 
4887
   foreach my $tbl ( keys %tbl_meta ) {
 
4888
      my $aref = $tbl_meta{$tbl}->{visible};
 
4889
      print $file "$tbl=", join(' ', @$aref), "\n";
 
4890
   }
 
4891
}
 
4892
 
 
4893
# load_config_expressions {{{3
 
4894
sub load_config_expressions {
 
4895
   my ( $file ) = @_;
 
4896
   while ( my $line = <$file> ) {
 
4897
      chomp $line;
 
4898
      next if $line =~ m/^#/;
 
4899
      last if $line =~ m/^\[/;
 
4900
 
 
4901
      my ( $key, $expr ) = $line =~ m/^(.+?)=(.*)$/;
 
4902
      next unless $key && $expr;
 
4903
 
 
4904
      my ( $sub, $err ) = compile_expr($expr);
 
4905
      $exprs{$key} = {
 
4906
         func => $sub,
 
4907
         text => $expr,
 
4908
         user => 1,
 
4909
         name => $key,
 
4910
      }
 
4911
   }
 
4912
}
 
4913
 
 
4914
# save_config_expressions {{{3
 
4915
sub save_config_expressions {
 
4916
   my $file = shift;
 
4917
   foreach my $key ( keys %exprs ) {
 
4918
      next unless $exprs{$key}->{user};
 
4919
      print $file "$key=$exprs{$key}->{text}\n";
 
4920
   }
 
4921
}
 
4922
 
 
4923
# save_config_tbl_meta {{{3
 
4924
sub save_config_tbl_meta {
 
4925
   my $file = shift;
 
4926
   foreach my $tbl ( keys %tbl_meta ) {
 
4927
      foreach my $col ( keys %{$tbl_meta{$tbl}->{cols}} ) {
 
4928
         my $meta = $tbl_meta{$tbl}->{cols}->{$col};
 
4929
         next unless $meta->{user};
 
4930
         print $file "$col=", join(
 
4931
            " ",
 
4932
            map {
 
4933
               # Some properties (trans) are arrays, others scalars
 
4934
               my $val = ref($meta->{$_}) ? join(',', @{$meta->{$_}}) : $meta->{$_};
 
4935
               $val =~ s/([\\'])/\\$1/g;  # Escape backslashes and single quotes
 
4936
               "$_='$val'";               # Enclose in single quotes
 
4937
            }
 
4938
            grep { $_ ne 'func' }
 
4939
            keys %$meta
 
4940
         ), "\n";
 
4941
      }
 
4942
   }
 
4943
}
 
4944
 
 
4945
# save_config_config {{{3
 
4946
sub save_config_config {
 
4947
   my $file = shift;
 
4948
   foreach my $key ( sort keys %config ) {
 
4949
      eval {
 
4950
      if ( $key ne 'password' || $config{savepass}->{val} ) {
 
4951
         print $file "# $config{$key}->{note}\n"
 
4952
            or die "Cannot print to file: $OS_ERROR";
 
4953
         my $val = $config{$key}->{val};
 
4954
         $val = '' unless defined($val);
 
4955
         if ( ref( $val ) eq 'ARRAY' ) {
 
4956
            print $file "$key="
 
4957
               . join( " ", @$val ) . "\n"
 
4958
               or die "Cannot print to file: $OS_ERROR";
 
4959
         }
 
4960
         elsif ( ref( $val ) eq 'HASH' ) {
 
4961
            print $file "$key="
 
4962
               . join( " ",
 
4963
                  map { "$_:$val->{$_}" } keys %$val
 
4964
               ) . "\n";
 
4965
         }
 
4966
         else {
 
4967
            print $file "$key=$val\n";
 
4968
         }
 
4969
      }
 
4970
      };
 
4971
      if ( $EVAL_ERROR ) { print "$EVAL_ERROR in $key"; };
 
4972
   }
 
4973
 
 
4974
}
 
4975
 
 
4976
# load_config_config {{{3
 
4977
sub load_config_config {
 
4978
   my ( $file ) = @_;
 
4979
 
 
4980
   # Look in the command-line parameters for things stored in the same slot.
 
4981
   my %cmdline =
 
4982
      map  { $opt_spec{$_}->{config} => $opts{$_} }
 
4983
      grep { exists $opt_spec{$_}->{config} && exists $opts{$_} }
 
4984
      keys %opt_spec;
 
4985
 
 
4986
   while ( my $line = <$file> ) {
 
4987
      chomp $line;
 
4988
      next if $line =~ m/^#/;
 
4989
      last if $line =~ m/^\[/;
 
4990
 
 
4991
      my ( $name, $val ) = $line =~ m/^(.+?)=(.*)$/;
 
4992
      next unless defined $name && defined $val;
 
4993
 
 
4994
      # Values might already have been set at the command line.
 
4995
      $val = defined($cmdline{$name}) ? $cmdline{$name} : $val;
 
4996
 
 
4997
      # Validate the incoming values...
 
4998
      if ( $name && exists( $config{$name} ) ) {
 
4999
         if ( !$config{$name}->{pat} || $val =~ m/$config{$name}->{pat}/ ) {
 
5000
            $config{$name}->{val} = $val;
 
5001
            $config{$name}->{read} = 1;
 
5002
         }
 
5003
      }
 
5004
   }
 
5005
}
 
5006
 
 
5007
# load_config_tbl_meta {{{3
 
5008
sub load_config_tbl_meta {
 
5009
   my ( $file ) = @_;
 
5010
 
 
5011
   while ( my $line = <$file> ) {
 
5012
      chomp $line;
 
5013
      next if $line =~ m/^#/;
 
5014
      last if $line =~ m/^\[/;
 
5015
 
 
5016
      # Each tbl_meta section has all the properties defined in %col_props.  If expr
 
5017
      # is set, it gets looked up by name.  That's why load_config_expressions() has
 
5018
      # to be called before this.
 
5019
      my ( $col , $rest ) = $line =~ m/^(.*?)=(.*)$/;
 
5020
      next unless $col;
 
5021
      my %parts = $rest =~ m/(\w+)='((?:(?!(?<!\\)').)*)'/g; # Properties are single-quoted
 
5022
 
 
5023
      # Each section read from the config file has one extra property: which table it
 
5024
      # goes in.
 
5025
      my $tbl  = $parts{tbl}     or die "There's no table for tbl_meta $col";
 
5026
      my $meta = $tbl_meta{$tbl} or die "There's no table in tbl_meta named $tbl";
 
5027
 
 
5028
      # The section is user-defined by definition (if that makes sense).
 
5029
      $parts{user} = 1;
 
5030
 
 
5031
      # The column may already exist in the table, in which case this is just a
 
5032
      # customization.
 
5033
      $meta->{cols}->{$col} ||= {};
 
5034
 
 
5035
      foreach my $prop ( keys %col_props ) {
 
5036
         if ( !defined($parts{$prop}) ) {
 
5037
            die "Undefined property $prop for column $col in table $tbl";
 
5038
         }
 
5039
 
 
5040
         # Un-escape escaping
 
5041
         $parts{$prop} =~ s/\\\\/\\/g;
 
5042
         $parts{$prop} =~ s/\\'/'/g;
 
5043
 
 
5044
         if ( ref $col_props{$prop} ) {
 
5045
            if ( $prop eq 'trans' ) {
 
5046
               $meta->{cols}->{$col}->{trans}
 
5047
                  = [ unique(grep { exists $trans_funcs{$_} } split(',', $parts{$prop})) ];
 
5048
            }
 
5049
            else {
 
5050
               $meta->{cols}->{$col}->{$prop} = [ split(',', $parts{$prop}) ];
 
5051
            }
 
5052
         }
 
5053
         else {
 
5054
            $meta->{cols}->{$col}->{$prop} = $parts{$prop};
 
5055
         }
 
5056
      }
 
5057
      if ( $meta->{cols}->{$col}->{expr} ) {
 
5058
         $meta->{cols}->{$col}->{src} = $exprs{$parts{expr}}
 
5059
            or die "There's no expression named $parts{expr} for column $col in table $tbl";
 
5060
      }
 
5061
 
 
5062
   }
 
5063
}
 
5064
 
 
5065
# save_config {{{3
 
5066
sub save_config {
 
5067
   return if $config{readonly}->{val};
 
5068
   # Save to a temp file first, so a crash doesn't destroy the main config file
 
5069
   my $newname  = $opts{c} || "$homepath/.innotop";
 
5070
   my $filename = $newname . '_tmp';
 
5071
   open my $file, "+>", $filename
 
5072
      or die("Can't write to $filename: $OS_ERROR");
 
5073
   print $file "version=$VERSION\n";
 
5074
 
 
5075
   foreach my $section ( @ordered_config_file_sections ) {
 
5076
      die "No such config file section $section" unless $config_file_sections{$section};
 
5077
      print $file "\n[$section]\n\n";
 
5078
      $config_file_sections{$section}->{writer}->($file);
 
5079
      print $file "\n[/$section]\n";
 
5080
   }
 
5081
 
 
5082
   # Now clobber the main config file with the temp.
 
5083
   close $file or die("Can't close $filename: $OS_ERROR");
 
5084
   rename($filename, $newname) or die("Can't rename $filename to $newname: $OS_ERROR");
 
5085
}
 
5086
 
 
5087
# load_config_connections {{{3
 
5088
sub load_config_connections {
 
5089
   my ( $file ) = @_;
 
5090
   while ( my $line = <$file> ) {
 
5091
      chomp $line;
 
5092
      next if $line =~ m/^#/;
 
5093
      last if $line =~ m/^\[/;
 
5094
 
 
5095
      my ( $key , $rest ) = $line =~ m/^(.*?)=(.*)$/;
 
5096
      next unless $key;
 
5097
      my %parts = $rest =~ m/(\S+?)=(\S*)/g;
 
5098
      my %conn  = map { $_ => $parts{$_} || '' } @conn_parts;
 
5099
      $connections{$key} = \%conn;
 
5100
   }
 
5101
}
 
5102
 
 
5103
# save_config_connections {{{3
 
5104
sub save_config_connections {
 
5105
   my $file = shift;
 
5106
   foreach my $conn ( keys %connections ) {
 
5107
      my $href = $connections{$conn};
 
5108
      my @keys = $href->{savepass} ? @conn_parts : grep { $_ ne 'pass' } @conn_parts;
 
5109
      print $file "$conn=", join(' ', map { "$_=$href->{$_}" } @keys), "\n";
 
5110
   }
 
5111
}
 
5112
 
 
5113
sub load_config_colors {
 
5114
   my ( $file ) = @_;
 
5115
   my %rule_set_for;
 
5116
 
 
5117
   while ( my $line = <$file> ) {
 
5118
      chomp $line;
 
5119
      next if $line =~ m/^#/;
 
5120
      last if $line =~ m/^\[/;
 
5121
 
 
5122
      my ( $tbl, $rule ) = $line =~ m/^(.*?)=(.*)$/;
 
5123
      next unless $tbl && $rule;
 
5124
      next unless exists $tbl_meta{$tbl};
 
5125
      my %parts = $rule =~ m/(\w+)='((?:(?!(?<!\\)').)*)'/g; # Properties are single-quoted
 
5126
      next unless $parts{col} && exists $tbl_meta{$tbl}->{cols}->{$parts{col}};
 
5127
      next unless $parts{op}  && exists $comp_ops{$parts{op}};
 
5128
      next unless defined $parts{arg};
 
5129
      next unless defined $parts{color};
 
5130
      my @colors = unique(grep { exists $ansicolors{$_} } split(/\W+/, $parts{color}));
 
5131
      next unless @colors;
 
5132
 
 
5133
      # Finally!  Enough validation...
 
5134
      $rule_set_for{$tbl} ||= [];
 
5135
      push @{$rule_set_for{$tbl}}, \%parts;
 
5136
   }
 
5137
 
 
5138
   foreach my $tbl ( keys %rule_set_for ) {
 
5139
      $tbl_meta{$tbl}->{colors} = $rule_set_for{$tbl};
 
5140
      $tbl_meta{$tbl}->{color_func} = make_color_func($tbl_meta{$tbl});
 
5141
   }
 
5142
}
 
5143
 
 
5144
# save_config_colors {{{3
 
5145
sub save_config_colors {
 
5146
   my $file = shift;
 
5147
   foreach my $tbl ( keys %tbl_meta ) {
 
5148
      my $meta = $tbl_meta{$tbl};
 
5149
      foreach my $rule ( @{$meta->{colors}} ) {
 
5150
         print $file "$tbl=", join(
 
5151
            ' ',
 
5152
            map {
 
5153
               my $val = $rule->{$_};
 
5154
               $val =~ s/([\\'])/\\$1/g;  # Escape backslashes and single quotes
 
5155
               "$_='$val'";               # Enclose in single quotes
 
5156
            }
 
5157
            qw(col op arg color)
 
5158
         ), "\n";
 
5159
      }
 
5160
   }
 
5161
}
 
5162
 
 
5163
# load_config_active_connections {{{3
 
5164
sub load_config_active_connections {
 
5165
   my ( $file ) = @_;
 
5166
   while ( my $line = <$file> ) {
 
5167
      chomp $line;
 
5168
      next if $line =~ m/^#/;
 
5169
      last if $line =~ m/^\[/;
 
5170
 
 
5171
      my ( $key , $rest ) = $line =~ m/^(.*?)=(.*)$/;
 
5172
      next unless $key;
 
5173
      my @parts = split(/ /, $rest);
 
5174
      $modes{$key}->{connections} = [ @parts ] if exists $modes{$key};
 
5175
   }
 
5176
}
 
5177
 
 
5178
# save_config_active_connections {{{3
 
5179
sub save_config_active_connections {
 
5180
   my $file = shift;
 
5181
   foreach my $mode ( keys %modes ) {
 
5182
      my @connections = get_connections($mode);
 
5183
      print $file "$mode=", join(' ', @connections), "\n";
 
5184
   }
 
5185
}
 
5186
 
 
5187
# load_config_mvs {{{3
 
5188
sub load_config_mvs {
 
5189
   my ( $file ) = @_;
 
5190
   while ( my $line = <$file> ) {
 
5191
      chomp $line;
 
5192
      next if $line =~ m/^#/;
 
5193
      last if $line =~ m/^\[/;
 
5194
 
 
5195
      my ( $key , $val ) = $line =~ m/^(.*?)=(.*)$/;
 
5196
      next unless $key;
 
5197
      $mvs{$key} = $val;
 
5198
   }
 
5199
}
 
5200
 
 
5201
# save_config_mvs {{{3
 
5202
sub save_config_mvs {
 
5203
   my $file = shift;
 
5204
   foreach my $key ( keys %mvs ) {
 
5205
      print $file "$key=$mvs{$key}\n";
 
5206
   }
 
5207
}
 
5208
 
 
5209
# edit_configuration {{{3
 
5210
sub edit_configuration {
 
5211
   my $key = '';
 
5212
   while ( $key ne 'q' ) {
 
5213
      $clear_screen_sub->();
 
5214
      my @display_lines = '';
 
5215
 
 
5216
      if ( $key && $cfg_editor_action{$key} ) {
 
5217
         $cfg_editor_action{$key}->{func}->();
 
5218
      }
 
5219
 
 
5220
      # Show help
 
5221
      push @display_lines, create_caption('What configuration do you want to edit?',
 
5222
      create_table2(
 
5223
         [ sort keys %cfg_editor_action ],
 
5224
         { map { $_ => $_ } keys %cfg_editor_action },
 
5225
         { map { $_ => $cfg_editor_action{$_}->{note} } keys %cfg_editor_action },
 
5226
         { sep => '  ' }));
 
5227
 
 
5228
      draw_screen(\@display_lines);
 
5229
      $key = pause('');
 
5230
   }
 
5231
}
 
5232
 
 
5233
# edit_configuration_variables {{{3
 
5234
sub edit_configuration_variables {
 
5235
   $clear_screen_sub->();
 
5236
   my $mode = $config{mode}->{val};
 
5237
 
 
5238
   my %config_choices
 
5239
      = map  { $_ => $config{$_}->{note} || '' }
 
5240
        # Only config values that are marked as applying to this mode.
 
5241
        grep {
 
5242
           my $key = $_;
 
5243
           $config{$key}->{conf} &&
 
5244
              ( $config{$key}->{conf} eq 'ALL'
 
5245
              || grep { $mode eq $_ } @{$config{$key}->{conf}} )
 
5246
        } keys %config;
 
5247
 
 
5248
   my $key = prompt_list(
 
5249
      "Enter the name of the variable you wish to configure",
 
5250
      '',
 
5251
      sub{ return keys %config_choices },
 
5252
      \%config_choices);
 
5253
 
 
5254
   if ( exists($config_choices{$key}) ) {
 
5255
      get_config_interactive($key);
 
5256
   }
 
5257
}
 
5258
 
 
5259
# get_expr {{{3
 
5260
sub get_expr {
 
5261
   my $exp;
 
5262
   $clear_screen_sub->();
 
5263
   print word_wrap("Choose the name of an expression to be used to calculate the column's contents.  "
 
5264
      . "You can choose an existing expression, or type a new name to create a new one.");
 
5265
   do {
 
5266
      $exp = prompt_list(
 
5267
         "Enter expression name",
 
5268
         '',
 
5269
         sub { return keys %exprs },
 
5270
         { map { $_ => trunc(collapse_ws($exprs{$_}->{text}), 30) } keys %exprs },
 
5271
         { sep => ' ' });
 
5272
   } while ( !$exp );
 
5273
   if ( !exists $exprs{$exp} ) {
 
5274
      my ( $err, $sub, $body );
 
5275
      do {
 
5276
         $clear_screen_sub->();
 
5277
         print word_wrap("The expression you named doesn't exist yet.  Specify a Perl expression for the body of "
 
5278
               . "a subroutine that accepts a hashref called \$set and returns your desired value.");
 
5279
         print "\n\n";
 
5280
         if ( $err ) {
 
5281
            print "There's an error in your expression: $err\n\n";
 
5282
         }
 
5283
         $body = prompt("Enter subroutine body");
 
5284
         ( $sub, $err )  = compile_expr($body);
 
5285
      } while ( $err );
 
5286
 
 
5287
      $exprs{$exp} = {
 
5288
         func => $sub,
 
5289
         text => $body,
 
5290
         user => 1,
 
5291
         name => $exp,
 
5292
      };
 
5293
   }
 
5294
   return $exp;
 
5295
}
 
5296
 
 
5297
# edit_color_rules {{{3
 
5298
sub edit_color_rules {
 
5299
   $clear_screen_sub->();
 
5300
   my $tbl = choose_visible_table();
 
5301
   if ( exists($tbl_meta{$tbl}) ) {
 
5302
      my $meta = $tbl_meta{$tbl};
 
5303
      my @cols = ('', qw(col op arg color));
 
5304
      my $info = { map { $_ => { hdr => $_, just => '-', } }  @cols };
 
5305
      $info->{label}->{maxw} = 30;
 
5306
      my $key;
 
5307
      my $selected_rule;
 
5308
 
 
5309
      # This loop builds a tabular view of the rules.
 
5310
      do {
 
5311
 
 
5312
         # Show help
 
5313
         if ( $key && $key eq '?' ) {
 
5314
            my @display_lines = '';
 
5315
            push @display_lines, create_caption('Editor key mappings',
 
5316
            create_table2(
 
5317
               [ sort keys %color_editor_action ],
 
5318
               { map { $_ => $_ } keys %color_editor_action },
 
5319
               { map { $_ => $color_editor_action{$_}->{note} } keys %color_editor_action },
 
5320
               { sep => '  ' }));
 
5321
            draw_screen(\@display_lines);
 
5322
            pause();
 
5323
            $key = '';
 
5324
         }
 
5325
         else {
 
5326
 
 
5327
            # Do the action specified
 
5328
            $selected_rule ||= 0;
 
5329
            if ( $key && $color_editor_action{$key} ) {
 
5330
               $selected_rule = $color_editor_action{$key}->{func}->($tbl, $selected_rule);
 
5331
               $selected_rule ||= 0;
 
5332
            }
 
5333
 
 
5334
            # Build the table of rules.  If the terminal has color, the selected rule
 
5335
            # will be highlighted; otherwise a > at the left will indicate.
 
5336
            my $data = $meta->{colors} || [];
 
5337
            foreach my $i ( 0..@$data - 1  ) {
 
5338
               $data->[$i]->{''} = $i == $selected_rule ? '>' : '';
 
5339
            }
 
5340
            my @display_lines = create_table(\@cols, $info, $data);
 
5341
 
 
5342
            # Highlight selected entry
 
5343
            for my $i ( 0 .. $#display_lines ) {
 
5344
               if ( $display_lines[$i] =~ m/^>/ ) {
 
5345
                  $display_lines[$i] = [ $display_lines[$i], 'reverse' ];
 
5346
               }
 
5347
            }
 
5348
 
 
5349
            # Draw the screen and wait for a command.
 
5350
            unshift @display_lines, '',
 
5351
               "Editing color rules for $meta->{hdr}.  Press ? for help, q to "
 
5352
               . "quit.", '';
 
5353
            draw_screen(\@display_lines);
 
5354
            print "\n\n", word_wrap('Rules are applied in order from top to '
 
5355
               . 'bottom.  The first matching rule wins and prevents the '
 
5356
               . 'rest of the rules from being applied.');
 
5357
            $key = pause('');
 
5358
         }
 
5359
      } while ( $key ne 'q' );
 
5360
      $meta->{color_func} = make_color_func($meta);
 
5361
   }
 
5362
}
 
5363
 
 
5364
# edit_table {{{3
 
5365
sub edit_table {
 
5366
   $clear_screen_sub->();
 
5367
   my $tbl = choose_visible_table();
 
5368
   if ( exists($tbl_meta{$tbl}) ) {
 
5369
      my $meta = $tbl_meta{$tbl};
 
5370
      my @cols = ('', qw(name hdr label src expr));
 
5371
      my $info = { map { $_ => { hdr => $_, just => '-', } }  @cols };
 
5372
      $info->{label}->{maxw} = 30;
 
5373
      my $key;
 
5374
      my $selected_column;
 
5375
 
 
5376
      # This loop builds a tabular view of the tbl_meta's structure, showing each column
 
5377
      # in the entry as a row.
 
5378
      do {
 
5379
 
 
5380
         # Show help
 
5381
         if ( $key && $key eq '?' ) {
 
5382
            my @display_lines = '';
 
5383
            push @display_lines, create_caption('Editor key mappings',
 
5384
            create_table2(
 
5385
               [ sort keys %tbl_editor_action ],
 
5386
               { map { $_ => $_ } keys %tbl_editor_action },
 
5387
               { map { $_ => $tbl_editor_action{$_}->{note} } keys %tbl_editor_action },
 
5388
               { sep => '  ' }));
 
5389
            draw_screen(\@display_lines);
 
5390
            pause();
 
5391
            $key = '';
 
5392
         }
 
5393
         else {
 
5394
 
 
5395
            # Do the action specified
 
5396
            $selected_column ||= $meta->{visible}->[0];
 
5397
            if ( $key && $tbl_editor_action{$key} ) {
 
5398
               $selected_column = $tbl_editor_action{$key}->{func}->($tbl, $selected_column);
 
5399
               $selected_column ||= $meta->{visible}->[0];
 
5400
            }
 
5401
 
 
5402
            # Build the pivoted view of the table's meta-data.  If the terminal has color,
 
5403
            # The selected row will be highlighted; otherwise a > at the left will indicate.
 
5404
            my $data = [];
 
5405
            foreach my $row ( @{$meta->{visible}} ) {
 
5406
               my %hash;
 
5407
               @hash{ @cols } = @{$meta->{cols}->{$row}}{@cols};
 
5408
               $hash{src}  = '' if ref $hash{src};
 
5409
               $hash{name} = $row;
 
5410
               $hash{''}   = $row eq $selected_column ? '>' : ' ';
 
5411
               push @$data, \%hash;
 
5412
            }
 
5413
            my @display_lines = create_table(\@cols, $info, $data);
 
5414
 
 
5415
            # Highlight selected entry
 
5416
            for my $i ( 0 .. $#display_lines ) {
 
5417
               if ( $display_lines[$i] =~ m/^>/ ) {
 
5418
                  $display_lines[$i] = [ $display_lines[$i], 'reverse' ];
 
5419
               }
 
5420
            }
 
5421
 
 
5422
            # Draw the screen and wait for a command.
 
5423
            unshift @display_lines, '',
 
5424
               "Editing table definition for $meta->{hdr}.  Press ? for help, q to quit.", '';
 
5425
            draw_screen(\@display_lines);
 
5426
            $key = pause('');
 
5427
         }
 
5428
      } while ( $key ne 'q' );
 
5429
   }
 
5430
}
 
5431
 
 
5432
# choose_mode_tables {{{3
 
5433
# Choose which table(s), and in what order, to display in a given mode.
 
5434
sub choose_mode_tables {
 
5435
   my $mode = $config{mode}->{val};
 
5436
   my @tbls = @{$modes{$mode}->{visible_tables}};
 
5437
   my $new  = prompt_list(
 
5438
      "Choose tables to display",
 
5439
      join(' ', @tbls),
 
5440
      sub { return @{$modes{$mode}->{tables}} },
 
5441
      { map { $_ => $tbl_meta{$_}->{hdr} } @{$modes{$mode}->{tables}} }
 
5442
   );
 
5443
   $modes{$mode}->{visible_tables} =
 
5444
      [ unique(grep { $_ && exists $tbl_meta{$_} } split(/\s+/, $new)) ];
 
5445
}
 
5446
 
 
5447
# choose_visible_table {{{3
 
5448
sub choose_visible_table {
 
5449
   my $mode = $config{mode}->{val};
 
5450
   my @tbls = @{$modes{$mode}->{visible_tables}};
 
5451
   my $tbl = $tbls[0];
 
5452
   if ( @tbls > 1 ) {
 
5453
      $tbl = prompt_list(
 
5454
         "Choose a table",
 
5455
         '',
 
5456
         sub { return @tbls },
 
5457
         { map { $_ => $tbl_meta{$_}->{hdr} } @tbls }
 
5458
      );
 
5459
   }
 
5460
   return $tbl;
 
5461
}
 
5462
 
 
5463
sub choose_sort_cols {
 
5464
   $clear_screen_sub->();
 
5465
   my $tbl = shift;
 
5466
   return unless $tbl && exists $tbl_meta{$tbl};
 
5467
   my $meta = $tbl_meta{$tbl};
 
5468
   my $val = prompt_list(
 
5469
      'Choose sort columns (prefix a column with - to reverse sort)',
 
5470
      $meta->{sort_cols},
 
5471
      sub { return keys %{$meta->{cols}} },
 
5472
      { map { $_ => $meta->{cols}->{$_}->{label} } keys %{$meta->{cols}} });
 
5473
   $meta->{sort_cols} = $val;
 
5474
   $tbl_meta{$tbl}->{sort_func} = make_sort_func($tbl_meta{$tbl});
 
5475
}
 
5476
 
 
5477
# create_new_filter {{{3
 
5478
sub create_new_filter {
 
5479
   my ( $filter, $tbl ) = @_;
 
5480
   $clear_screen_sub->();
 
5481
 
 
5482
   if ( !$filter || $filter =~ m/\W/ ) {
 
5483
      print word_wrap("Choose a name for the filter.  This name is not displayed, and is only used "
 
5484
            . "for internal reference.  It can only contain lowercase letters, numbers, and underscores.");
 
5485
      print "\n\n";
 
5486
      do {
 
5487
         $filter = prompt("Enter filter name");
 
5488
      } while ( !$filter || $filter =~ m/\W/ );
 
5489
   }
 
5490
 
 
5491
   my ( $err, $sub, $body );
 
5492
   do {
 
5493
      $clear_screen_sub->();
 
5494
      print word_wrap("A filter is a Perl subroutine that accepts a hashref of columns "
 
5495
         . "called \$set, and returns a true value if the filter accepts the row.  Example:\n"
 
5496
         . "   \$set->{active_secs} > 5\n"
 
5497
         . "will only allow rows if their active_secs column is greater than 5.");
 
5498
      print "\n\n";
 
5499
      if ( $err ) {
 
5500
         print "There's an error in your filter expression: $err\n\n";
 
5501
      }
 
5502
      $body = prompt("Enter subroutine body");
 
5503
      ( $sub, $err ) = compile_filter($body);
 
5504
   } while ( $err );
 
5505
 
 
5506
   $filters{$filter} = {
 
5507
      func => $sub,
 
5508
      text => $body,
 
5509
      user => 1,
 
5510
      name => $filter,
 
5511
      note => 'User-defined filter',
 
5512
      tbls => [$tbl],
 
5513
   };
 
5514
}
 
5515
 
 
5516
# get_config_interactive {{{3
 
5517
sub get_config_interactive {
 
5518
   my $key = shift;
 
5519
   $clear_screen_sub->();
 
5520
 
 
5521
   # Print help first.
 
5522
   print "Enter a new value for '$key' ($config{$key}->{note}).\n";
 
5523
 
 
5524
   my $current = ref($config{$key}->{val}) ? join(" ", @{$config{$key}->{val}}) : $config{$key}->{val};
 
5525
 
 
5526
   my $new_value = prompt('Enter a value', $config{$key}->{pat}, $current);
 
5527
   $config{$key}->{val} = $new_value;
 
5528
}
 
5529
 
 
5530
# TODO: make a list of all variables and what they come from.  Use that for
 
5531
# auto-completion here and in add_new_var_set, and for figuring out what data is needed.
 
5532
# Cache the list of what data is needed.
 
5533
sub edit_current_var_set {
 
5534
   my $mode = $config{mode}->{val};
 
5535
   my $name = $config{"${mode}_set"}->{val};
 
5536
   my $variables = join(' ', @{$var_sets{$name}});
 
5537
 
 
5538
   do {
 
5539
      $clear_screen_sub->();
 
5540
      $variables = prompt("Enter variables for $name", undef, $variables );
 
5541
   } until ( $variables );
 
5542
 
 
5543
   $var_sets{$name} = [ unique(grep { $_ } split(/\s+/, $variables)) ];
 
5544
}
 
5545
 
 
5546
 
 
5547
sub choose_var_set {
 
5548
   my $key = shift;
 
5549
   $clear_screen_sub->();
 
5550
 
 
5551
   my $new_value = prompt_list(
 
5552
      'Choose a set of values to display, or enter the name of a new one',
 
5553
      $config{$key}->{val},
 
5554
      sub { return keys %var_sets },
 
5555
      { map { $_ => join(' ', @{$var_sets{$_}}) } sort keys %var_sets });
 
5556
 
 
5557
   if ( !exists $var_sets{$new_value} ) {
 
5558
      add_new_var_set($new_value);
 
5559
   }
 
5560
 
 
5561
   $config{$key}->{val} = $new_value if exists $var_sets{$new_value};
 
5562
}
 
5563
 
 
5564
 
 
5565
# get_file {{{3
 
5566
sub get_file {
 
5567
   my $filename = shift;
 
5568
   open my $file, "<", "$filename" or die "Can't open $filename: $!";
 
5569
   my $file_contents = do { local $/; <$file>; };
 
5570
   close $file;
 
5571
   return $file_contents;
 
5572
}
 
5573
 
 
5574
# filename {{{3
 
5575
sub filename {
 
5576
   ( my $filename = shift ) =~ s#^.*[/\\]##;
 
5577
   return $filename;
 
5578
}
 
5579
 
 
5580
# Online configuration and prompting functions {{{2
 
5581
 
 
5582
# edit_server_groups {{{3
 
5583
# Choose which server connections are in a server group.  First choose a group,
 
5584
# then choose which connections are in it.
 
5585
sub edit_server_groups {
 
5586
   $clear_screen_sub->();
 
5587
   my $mode  = $config{mode}->{val};
 
5588
   my $group = $modes{$mode}->{server_group};
 
5589
   my %curr  = %server_groups;
 
5590
   my $new   = choose_or_create_server_group($group, 'to edit');
 
5591
   my $cxns  = join(' ', @{$server_groups{$new}});
 
5592
   $clear_screen_sub->();
 
5593
   if ( exists $curr{$new} ) {
 
5594
      # Don't do this step if the user just created a new server group,
 
5595
      # because part of that process was to choose connections.
 
5596
      my @conns = choose_or_create_connection($cxns, 'for this group');
 
5597
      $server_groups{$new} = \@conns;
 
5598
   }
 
5599
}
 
5600
 
 
5601
# choose_server_groups {{{3
 
5602
sub choose_server_groups {
 
5603
   $clear_screen_sub->();
 
5604
   my $mode  = $config{mode}->{val};
 
5605
   my $group = $modes{$mode}->{server_group};
 
5606
   my $new   = choose_or_create_server_group($group, 'for this mode');
 
5607
   $modes{$mode}->{server_group} = $new if exists $server_groups{$new};
 
5608
}
 
5609
 
 
5610
sub choose_or_create_server_group {
 
5611
   my ( $group, $prompt ) = @_;
 
5612
   my $new   = '';
 
5613
 
 
5614
   my @available = sort keys %server_groups;
 
5615
 
 
5616
   if ( @available ) {
 
5617
      print "You can enter the name of a new group to create it.\n";
 
5618
 
 
5619
      $new = prompt_list(
 
5620
         "Choose a server group $prompt",
 
5621
         $group,
 
5622
         sub { return @available },
 
5623
         { map { $_ => join(' ', @{$server_groups{$_}}) } @available });
 
5624
 
 
5625
      $new =~ s/\s.*//;
 
5626
 
 
5627
      if ( !exists $server_groups{$new} ) {
 
5628
         my $answer = prompt("There is no server group called '$new'.  Create it?", undef, "y");
 
5629
         if ( $answer eq 'y' ) {
 
5630
            add_new_server_group($new);
 
5631
         }
 
5632
      }
 
5633
   }
 
5634
   else {
 
5635
      $new = add_new_server_group();
 
5636
   }
 
5637
   return $new;
 
5638
}
 
5639
 
 
5640
sub choose_or_create_connection {
 
5641
   my ( $cxns, $prompt ) = @_;
 
5642
   print "You can enter the name of a new connection to create it.\n";
 
5643
 
 
5644
   my @available = sort keys %connections;
 
5645
   my $new_cxns = prompt_list(
 
5646
      "Choose connections $prompt",
 
5647
      $cxns,
 
5648
      sub { return @available },
 
5649
      { map { $_ => $connections{$_}->{dsn} } @available });
 
5650
 
 
5651
   my @new = unique(grep { !exists $connections{$_} } split(/\s+/, $new_cxns));
 
5652
   foreach my $new ( @new ) {
 
5653
      my $answer = prompt("There is no connection called '$new'.  Create it?", undef, "y");
 
5654
      if ( $answer eq 'y' ) {
 
5655
         add_new_dsn($new);
 
5656
      }
 
5657
   }
 
5658
 
 
5659
   return unique(grep { exists $connections{$_} } split(/\s+/, $new_cxns));
 
5660
}
 
5661
 
 
5662
# choose_servers {{{3
 
5663
sub choose_servers {
 
5664
   $clear_screen_sub->();
 
5665
   my $mode = $config{mode}->{val};
 
5666
   my $cxns = join(' ', get_connections());
 
5667
   my @chosen = choose_or_create_connection($cxns, 'for this mode');
 
5668
   $modes{$mode}->{connections} = \@chosen;
 
5669
   $modes{$mode}->{server_group} = ''; # Clear this because it overrides {connections}
 
5670
}
 
5671
 
 
5672
# display_license {{{3
 
5673
sub display_license {
 
5674
   $clear_screen_sub->();
 
5675
 
 
5676
   print $innotop_license;
 
5677
 
 
5678
   pause();
 
5679
}
 
5680
 
 
5681
# Data-retrieval functions {{{2
 
5682
# get_status_info {{{3
 
5683
# Get SHOW STATUS and SHOW VARIABLES together.
 
5684
# TODO: figure out how to only get the needed parts.  Maybe split status/vars into two subs.
 
5685
sub get_status_info {
 
5686
   my @cxns = @_;
 
5687
   if ( !$info_gotten{status}++ ) {
 
5688
      foreach my $cxn ( @cxns ) {
 
5689
         $vars{$cxn}->{$clock} ||= {};
 
5690
         my $vars = $vars{$cxn}->{$clock};
 
5691
 
 
5692
         my $sth = do_stmt($cxn, 'SHOW_STATUS') or next;
 
5693
         my $res = $sth->fetchall_arrayref();
 
5694
         map { $vars->{$_->[0]} = $_->[1] || 0 } @$res;
 
5695
 
 
5696
         # Calculate hi-res uptime and add cxn to the hash
 
5697
         $vars->{Uptime_hires} ||= $hi_res ? time() - $dbhs{$cxn}->{start_time} : $vars->{Uptime};
 
5698
         $vars->{cxn} = $cxn;
 
5699
 
 
5700
         # Add SHOW VARIABLES to the hash
 
5701
         $sth = do_stmt($cxn, 'SHOW_VARIABLES') or next;
 
5702
         $res = $sth->fetchall_arrayref();
 
5703
         map { $vars->{$_->[0]} = $_->[1] || 0 } @$res;
 
5704
      }
 
5705
   }
 
5706
}
 
5707
 
 
5708
# analyze_query {{{3
 
5709
# Allows the user to show fulltext, explain, show optimized...
 
5710
sub analyze_query {
 
5711
   my $action = shift;
 
5712
   my %actions = (
 
5713
      e => \&display_explain,
 
5714
      f => \&show_full_query,
 
5715
      o => \&show_optimized_query,
 
5716
   );
 
5717
 
 
5718
   # Find out which server.
 
5719
   my @cxns = unique map { $_->{cxn} } @current_queries;
 
5720
   my ( $cxn ) = select_cxn('On which server', @cxns);
 
5721
   return unless $cxn && exists($connections{$cxn});
 
5722
 
 
5723
   # Find out which connection.
 
5724
   my @ids = sort map { $_->{id} } grep { $_->{cxn} eq $cxn } @current_queries;
 
5725
   return unless @ids;
 
5726
   my $id = prompt_list('Specify a connection ID to analyze',
 
5727
      $ids[0],
 
5728
      sub { return @ids });
 
5729
 
 
5730
   # Find the info hash of that query on that server.
 
5731
   my ( $info ) = grep { $cxn eq $_->{cxn} && $id == $_->{id} } @current_queries;
 
5732
   return unless $info;
 
5733
 
 
5734
   do {
 
5735
      $actions{$action}->($info);
 
5736
      print "\n";
 
5737
      $action = pause('Press e to explain, f for full query, o for optimized query');
 
5738
   } while ( exists($actions{$action}) );
 
5739
}
 
5740
 
 
5741
# inc {{{3
 
5742
# Returns the difference between two sets of variables/status/innodb stuff.
 
5743
sub inc {
 
5744
   my ( $offset, $cxn ) = @_;
 
5745
   my $vars = $vars{$cxn};
 
5746
   if ( $offset < 0 ) {
 
5747
      return $vars->{$clock};
 
5748
   }
 
5749
   elsif ( exists $vars{$clock - $offset} && !exists $vars->{$clock - $offset - 1} ) {
 
5750
      return $vars->{$clock - $offset};
 
5751
   }
 
5752
   my $cur = $vars->{$clock - $offset};
 
5753
   my $pre = $vars->{$clock - $offset - 1};
 
5754
   return {
 
5755
      # Numeric variables get subtracted, non-numeric get passed straight through.
 
5756
      map  {
 
5757
         $_ =>
 
5758
            ( (defined $cur->{$_} && $cur->{$_} =~ m/$num_regex/)
 
5759
            ?  $cur->{$_} - ($pre->{$_} || 0)
 
5760
            :  $cur->{$_} )
 
5761
      } keys %{$cur}
 
5762
   };
 
5763
}
 
5764
 
 
5765
# extract_values {{{3
 
5766
sub extract_values {
 
5767
   my ( $set, $tbl ) = @_;
 
5768
   my $result = {};
 
5769
   my $meta   = $tbl_meta{$tbl};
 
5770
   my $cols   = $meta->{cols};
 
5771
   foreach my $key ( keys %$cols ) {
 
5772
      my $info = $cols->{$key}
 
5773
         or die "Column '$key' doesn't exist in $tbl";
 
5774
      die "No func defined for '$key' in $tbl"
 
5775
         unless $info->{func};
 
5776
      eval {
 
5777
         $result->{$key} = $info->{func}->($set)
 
5778
      };
 
5779
      if ( $EVAL_ERROR ) {
 
5780
         $result->{$key} = $info->{num} ? 0 : '';
 
5781
      }
 
5782
   }
 
5783
   return $result;
 
5784
}
 
5785
 
 
5786
# get_full_processlist {{{3
 
5787
sub get_full_processlist {
 
5788
   my @cxns = @_;
 
5789
   my @result;
 
5790
   foreach my $cxn ( @cxns ) {
 
5791
      my $stmt = do_stmt($cxn, 'PROCESSLIST') or next;
 
5792
      my $arr  = $stmt->fetchall_arrayref({});
 
5793
      push @result, map { $_->{cxn} = $cxn; $_ } @$arr;
 
5794
   }
 
5795
   return @result;
 
5796
}
 
5797
 
 
5798
# get_open_tables {{{3
 
5799
sub get_open_tables {
 
5800
   my @cxns = @_;
 
5801
   my @result;
 
5802
   foreach my $cxn ( @cxns ) {
 
5803
      my $stmt = do_stmt($cxn, 'OPEN_TABLES') or next;
 
5804
      my $arr  = $stmt->fetchall_arrayref({});
 
5805
      push @result, map { $_->{cxn} = $cxn; $_ } @$arr;
 
5806
   }
 
5807
   return @result;
 
5808
}
 
5809
 
 
5810
# get_innodb_status {{{3
 
5811
sub get_innodb_status {
 
5812
   my ( $cxns, $addl_sections ) = @_;
 
5813
   if ( !$info_gotten{innodb_status}++ ) {
 
5814
      my $parser = InnoDBParser->new;
 
5815
 
 
5816
      # Determine which sections need to be parsed
 
5817
      my %sections_required =
 
5818
         map  { $tbl_meta{$_}->{innodb} => 1 }
 
5819
         grep { $_ }
 
5820
         get_visible_tables();
 
5821
 
 
5822
      # Add in any other sections the caller requested.
 
5823
      foreach my $sec ( @$addl_sections ) {
 
5824
         $sections_required{$sec} = 1;
 
5825
      }
 
5826
 
 
5827
      foreach my $cxn ( @$cxns ) {
 
5828
         my $stmt = do_stmt($cxn, 'INNODB_STATUS') or next;
 
5829
         my $innodb_status_text = $stmt->fetchrow_hashref()->{Status};
 
5830
 
 
5831
         # Parse and merge into %vars storage
 
5832
         my %innodb_status = (
 
5833
            $parser->get_status_hash(
 
5834
               $innodb_status_text,
 
5835
               $config{debug}->{val},
 
5836
               \%sections_required,
 
5837
               0, # don't parse full lock information
 
5838
            )
 
5839
         );
 
5840
         if ( !$innodb_status{IB_got_all} && $config{auto_wipe_dl}->{val} ) {
 
5841
            clear_deadlock($cxn);
 
5842
         }
 
5843
 
 
5844
         # Merge using a hash slice, which is the fastest way
 
5845
         $vars{$cxn}->{$clock} ||= {};
 
5846
         my $hash = $vars{$cxn}->{$clock};
 
5847
         @{$hash}{ keys %innodb_status } = values %innodb_status;
 
5848
         $hash->{cxn} = $cxn;
 
5849
         $hash->{Uptime_hires} ||= $hi_res ? time() - $dbhs{$cxn}->{start_time} : $hash->{Uptime};
 
5850
      }
 
5851
   }
 
5852
}
 
5853
 
 
5854
# clear_deadlock {{{3
 
5855
sub clear_deadlock {
 
5856
   my ( $cxn ) = @_;
 
5857
   return if $clearing_deadlocks++;
 
5858
   my $tbl = $connections{$cxn}->{dl_table};
 
5859
   return unless $tbl;
 
5860
 
 
5861
   eval {
 
5862
      # Set up the table for creating a deadlock.
 
5863
      return unless do_query($cxn, "drop table if exists $tbl");
 
5864
      return unless do_query($cxn, "create table $tbl(a int) engine=innodb");
 
5865
      return unless do_query($cxn, "delete from $tbl");
 
5866
      return unless do_query($cxn, "insert into $tbl(a) values(0), (1)");
 
5867
      return unless do_query($cxn, "commit"); # Or the children will block against the parent
 
5868
 
 
5869
      # Fork off two children to deadlock against each other.
 
5870
      my %children;
 
5871
      foreach my $child ( 0..1 ) {
 
5872
         my $pid = fork();
 
5873
         if ( defined($pid) && $pid == 0 ) { # I am a child
 
5874
            deadlock_thread( $child, $tbl, $cxn );
 
5875
         }
 
5876
         elsif ( !defined($pid) ) {
 
5877
            die("Unable to fork for clearing deadlocks!\n");
 
5878
         }
 
5879
         # I already exited if I'm a child, so I'm the parent.
 
5880
         $children{$child} = $pid;
 
5881
      }
 
5882
 
 
5883
      # Wait for the children to exit.
 
5884
      foreach my $child ( keys %children ) {
 
5885
         my $pid = waitpid($children{$child}, 0);
 
5886
      }
 
5887
 
 
5888
      # Clean up.
 
5889
      do_query($cxn, "drop table $tbl");
 
5890
   };
 
5891
   if ( $EVAL_ERROR ) {
 
5892
      print $EVAL_ERROR;
 
5893
   }
 
5894
 
 
5895
   $clearing_deadlocks = 0;
 
5896
}
 
5897
 
 
5898
# get_master_slave_status {{{3
 
5899
# TODO: apparently in version 4 the column names are different?
 
5900
# TODO: split into master/slave status...
 
5901
sub get_master_slave_status {
 
5902
   my @cxns = @_;
 
5903
   if ( !$info_gotten{replication_status}++ ) {
 
5904
      foreach my $cxn ( @cxns ) {
 
5905
         $vars{$cxn}->{$clock} ||= {};
 
5906
         my $vars = $vars{$cxn}->{$clock};
 
5907
         $vars->{cxn} = $cxn;
 
5908
 
 
5909
         my $stmt = do_stmt($cxn, 'SHOW_MASTER_STATUS') or next;
 
5910
         my $res = $stmt->fetchall_arrayref({})->[0];
 
5911
         @{$vars}{ keys %$res } = values %$res;
 
5912
         $stmt = do_stmt($cxn, 'SHOW_SLAVE_STATUS') or next;
 
5913
         $res = $stmt->fetchall_arrayref({})->[0];
 
5914
         @{$vars}{ keys %$res } = values %$res;
 
5915
         $vars->{Uptime_hires} ||= $hi_res ? time() - $dbhs{$cxn}->{start_time} : $vars->{Uptime};
 
5916
      }
 
5917
   }
 
5918
}
 
5919
 
 
5920
# Documentation {{{1
 
5921
# ############################################################################
 
5922
# I put this last as per the Dog book.
 
5923
# ############################################################################
 
5924
=pod
 
5925
 
 
5926
=head1 NAME
 
5927
 
 
5928
innotop - A MySQL and InnoDB monitor program.
 
5929
 
 
5930
=head1 DESCRIPTION
 
5931
 
 
5932
innotop connects to one or many MySQL database servers and retrieves data, then
 
5933
displays it.  It can run interactively as a monitor, or serve as a source for
 
5934
UNIX pipe-and-filter style programming.  innotop uses the data from SHOW
 
5935
VARIABLES, SHOW GLOBAL STATUS, SHOW FULL PROCESSLIST, and SHOW ENGINE INNODB
 
5936
STATUS, among other things.  It refreshes the data at regular intervals, so you
 
5937
get a sense of what's happening inside your MySQL servers.  You can control how
 
5938
fast it refreshes.
 
5939
 
 
5940
I originally wrote innotop to parse SHOW INNODB STATUS and show a list of
 
5941
current transactions in `top' style, hence the name.  It now has much more
 
5942
functionality.
 
5943
 
 
5944
When innotop is running interactively, you control it with key presses.  You can
 
5945
find a complete list of all available keys at any time by pressing '?' for help.
 
5946
Keys change innotop from one mode to another, let you change configuration, and
 
5947
send commands to MySQL servers.
 
5948
 
 
5949
=head1 OVERVIEW
 
5950
 
 
5951
Within each of innotop's modes, innotop displays 'tables' of the current data.
 
5952
For example, in T (InnoDB Transactions) mode, it shows the transactions in a
 
5953
table.  In some modes there are many tables on screen at once.
 
5954
 
 
5955
You can choose which tables to display, in what order, which columns and in what
 
5956
order, how to sort the rows, colorize and filter the rows, and more.  Think of
 
5957
the tables as spreadsheets; you have quite a bit of control over what goes into
 
5958
the cells.  You can even define your own formulas and apply formatting.  For
 
5959
example, you can choose whether a cell should be right or left justified,
 
5960
specify minimum and maximum widths, shorten large numbers to familiar units like
 
5961
MB and GB, and turn an integer number of seconds into hours:minutes:seconds
 
5962
display.
 
5963
 
 
5964
Some modes allow you to see the incremental changes since last refresh.  This
 
5965
can be useful to see how many new queries have been issued during that time, for
 
5966
example.  You can toggle this on and off.
 
5967
 
 
5968
You can define many connections to servers, group the servers together, and
 
5969
switch between them easily to manage many MySQL instances conveniently.  See
 
5970
SERVER GROUPS for more.
 
5971
 
 
5972
Remember, press '?' to see what commands are available to you at any time.
 
5973
 
 
5974
=head1 CONFIGURATION
 
5975
 
 
5976
innotop is completely configurable.  The default configuration is built into the
 
5977
program, but everything is written out to a configuration file when you exit
 
5978
innotop.  You can edit this file by hand as you wish, or just use the built-in
 
5979
configuration commands while innotop is running.
 
5980
 
 
5981
You can specify certain options on the command-line.  Run `innotop --help' for
 
5982
details.
 
5983
 
 
5984
=head1 MODES
 
5985
 
 
5986
innotop has many modes.  The following is a brief description of each in
 
5987
alphabetical order.  Remember, you can always get the authoritative help by
 
5988
pressing '?'.
 
5989
 
 
5990
=over 8
 
5991
 
 
5992
=item B: InnoDB Buffers
 
5993
 
 
5994
This mode displays the InnoDB buffer pool, page statistics, insert buffer, and
 
5995
adaptive hash index.
 
5996
 
 
5997
=item D: InnoDB Deadlocks
 
5998
 
 
5999
This mode shows the transactions involved in the last InnoDB deadlock.  A second
 
6000
table shows the locks each transaction held and waited for (recall that a
 
6001
deadlock is caused by a cycle in the waits-for graph).
 
6002
 
 
6003
InnoDB puts deadlock information before some other information in the SHOW
 
6004
INNODB STATUS output.  If there are a lot of locks, the deadlock information can
 
6005
grow very large indeed, and there is a limit on the size of the SHOW INNODB
 
6006
STATUS output.  A large deadlock can fill the entire output, or even be
 
6007
truncated, and prevent you from seeing other information at all.  If you are
 
6008
running innotop in another mode, for example T mode, and suddenly you don't see
 
6009
anything, you might want to check and see if a deadlock has wiped out the data
 
6010
you need.
 
6011
 
 
6012
If it has, you can create a small deadlock to replace it.  Use the 'w' key to
 
6013
'wipe' the large deadlock with a small one.  This will not work unless you have
 
6014
defined a deadlock table for the connection -- look in your configuration file.
 
6015
 
 
6016
You can also set innotop to automatically detect when a large deadlock needs to
 
6017
be replaced with a small one.  This feature is turned off by default.
 
6018
 
 
6019
=item F: InnoDB Foreign Key Errors
 
6020
 
 
6021
This mode shows the last InnoDB foreign key error information, such as the
 
6022
table where it happened, when and who and what query caused it, and so on.
 
6023
 
 
6024
InnoDB has a huge variety of foreign key error messages, and many of them are
 
6025
just hard to parse.  innotop doesn't always do the best job here, but there's
 
6026
so much code devoted to parsing this messy, unparseable output that innotop is
 
6027
likely never to be perfect in this regard.  If innotop doesn't show you what
 
6028
you need to see, just look at the status text directly.
 
6029
 
 
6030
=item G: Load Graph
 
6031
 
 
6032
This mode calculates per-second statistics, such as queries per second, scales
 
6033
them against a maximum, and prints them out as a "bar graph."  It's similar to
 
6034
the Load Statistics mode, except it's a graph instead of numbers.
 
6035
 
 
6036
Headers are abbreviated to fit on the screen if necessary.  This only happens in
 
6037
interactive operation, not while running unattended.
 
6038
 
 
6039
=item I: InnoDB I/O Info
 
6040
 
 
6041
This mode shows InnoDB's I/O statistics, including the I/O threads, pending
 
6042
I/O, file I/O miscellaneous, and log statistics.
 
6043
 
 
6044
=item M: Master/Slave Replication Status
 
6045
 
 
6046
This mode shows the output of SHOW SLAVE STATUS and SHOW MASTER STATUS in three
 
6047
tables.  The first two divide the slave's status into SQL and I/O thread status,
 
6048
and the last shows master status.  Filters are applied to eliminate non-slave
 
6049
servers from the slave tables and vice versa.
 
6050
 
 
6051
=item O: Open Tables
 
6052
 
 
6053
This section comes from MySQL's SHOW OPEN TABLES command.  By default it is
 
6054
filtered to show tables which are in use by one or more queries, so you can
 
6055
get a quick look at which tables are 'hot'.  You can use this to guess which
 
6056
tables might be locked implicitly.
 
6057
 
 
6058
=item Q: Query List
 
6059
 
 
6060
This mode displays the output from SHOW FULL PROCESSLIST, much like B<mytop>'s
 
6061
query list mode.  This mode does B<not> show InnoDB-related information.  This
 
6062
is probably one of the most useful modes for general usage.
 
6063
 
 
6064
You can toggle an informative header that shows general status information about
 
6065
your server.  There are default sorting, filtering, and colorization rules.
 
6066
 
 
6067
You can EXPLAIN a query from this mode.  This will allow you to see the query's
 
6068
full text, the results of EXPLAIN, and in newer MySQL versions, even see the
 
6069
optimized query resulting from EXPLAIN EXTENDED.
 
6070
 
 
6071
=item R: InnoDB Row Operations and Semaphores
 
6072
 
 
6073
This mode shows InnoDB row operations, row operation miscellaneous, semaphores,
 
6074
and information from the wait array.
 
6075
 
 
6076
=item S: Load Statistics
 
6077
 
 
6078
This mode calculates statistics, such as queries per second, and prints them out
 
6079
in the style of <vmstat>.  It's similar to the Load Graph mode, except it's a
 
6080
numbers instead of a graph.  You can show absolute values or incremental values
 
6081
since the last refresh.  Like G mode, headers may be abbreviated to fit on the
 
6082
screen in interactive operation.  You choose which variables to display with the
 
6083
'c' key, which selects from predefined sets.  You can choose your own sets.
 
6084
 
 
6085
=item T: InnoDB Transactions
 
6086
 
 
6087
This mode shows every transaction in the InnoDB monitor's output, in `top'
 
6088
format.  This mode is the reason I wrote innotop.
 
6089
 
 
6090
By default, two filters are applied to the table to hide inactive transactions
 
6091
and hide innotop's own transaction.  You can toggle this on and off.  There are
 
6092
also default sort and colorization rules in this view.  You can customize these.
 
6093
 
 
6094
If you are only viewing one server's transactions, innotop can display an
 
6095
informational header.  This will show you things like how many entries there are
 
6096
in the InnoDB history list, how much of the buffer pool is used, and so forth.
 
6097
 
 
6098
=item V: Variables & Status
 
6099
 
 
6100
This mode displays any variables you please from SHOW GLOBAL STATUS and SHOW
 
6101
VARIABLES, as well as the values parsed from SHOW INNODB STATUS.  It displays
 
6102
not only the current values, but previous values too; you choose how many sets
 
6103
to keep on screen.
 
6104
 
 
6105
=item W: InnoDB Lock Waits
 
6106
 
 
6107
This mode shows information about current InnoDB lock waits.  This information
 
6108
comes from the TRANSACTIONS section of the InnoDB status text.  If you have a
 
6109
very busy server, you may have frequent lock waits; it helps to be able to see
 
6110
which tables and indexes are the "hot spot" for locks.  If your server is
 
6111
running pretty well, this mode should show nothing.
 
6112
 
 
6113
A second table shows any waits in the OS wait array.  This comes from a separate
 
6114
section of the status text.  If you see frequent waits, your server is probably
 
6115
running under a high concurrency workload.  This is the same table displayed in
 
6116
R mode.
 
6117
 
 
6118
=back
 
6119
 
 
6120
=head1 SERVER GROUPS
 
6121
 
 
6122
If you have a lot of MySQL instances, or even if you only have a few, you will
 
6123
probably find this functionality helpful.
 
6124
 
 
6125
To begin with, when you start innotop it will prompt you to define a connection
 
6126
to a server.  After that is done, you can tell it to monitor another server with
 
6127
the @ key.  This key actually brings up a list of connections you've defined.
 
6128
If you name one that doesn't exist, innotop will guide you through the process
 
6129
of defining it as a new connection, and it will be available from then on.
 
6130
 
 
6131
You can name multiple connections in any mode.  For example, suppose you are in
 
6132
T mode, monitoring transactions on server1; if you press @, you can type
 
6133
'server1 server2' and see data from both.
 
6134
 
 
6135
This becomes unwieldy after a bit though.  To address this, you can press the
 
6136
'#' key to create and select server groups.  Groups work just the same as
 
6137
connections: if you name one that doesn't exist, you can create it.
 
6138
 
 
6139
As an example, you might have groups named 'all', 'masters', 'slaves', 'oltp'
 
6140
and 'olap'.  Many of the servers could belong to several of these groups.  It's
 
6141
just a quick way to toggle between various servers.
 
6142
 
 
6143
Once you have defined groups, you can press the TAB key to cycle between them.
 
6144
 
 
6145
As of this writing innotop does NOT fetch data in parallel from different
 
6146
servers, so if your groups get large you may notice increased delay time when
 
6147
innotop refreshes.  You can address this by creating more small groups.  At some
 
6148
point I plan to make the data-fetching multi-threaded and this problem will not
 
6149
be so severe.
 
6150
 
 
6151
=head1 SYSTEM REQUIREMENTS
 
6152
 
 
6153
You must connect to the DB server as a user who has the SUPER privilege for
 
6154
many of the functions.  If you don't have the SUPER privilege, you may still
 
6155
be able to run some functions.
 
6156
 
 
6157
I think everything you need to run innotop is distributed either with Perl, or
 
6158
with innotop itself.  You need DBI and DBD::mysql.  You also need the
 
6159
InnoDBParser module, and Term::ReadKey.  If you have Time::HiRes, innotop will
 
6160
use it.  If you have Term::ANSIColor, innotop will use it to format headers more
 
6161
readably and compactly.  (Under Microsoft Windows, you also need
 
6162
Win32::Console::ANSI for terminal formatting codes to be honored).  If you
 
6163
install Term::ReadLine, preferably Term::ReadLine::Gnu, you'll get nice
 
6164
auto-completion support.
 
6165
 
 
6166
I run innotop on Gentoo GNU/Linux, Debian and Ubuntu, and I've had feedback from
 
6167
people successfully running it on Red Hat, CentOS, Solaris, and Mac OSX.  I
 
6168
don't see any reason why it won't work on other UNIX-ish operating systems, but
 
6169
I don't know for sure.  It also runs on Windows under ActivePerl without
 
6170
problem.
 
6171
 
 
6172
I have perl v5.8.8 installed, and I've had reports of it working on 5.8.5 but
 
6173
I don't know about other versions.
 
6174
 
 
6175
I use innotop on MySQL version 4.1 and 5.0, and have heard of others using it
 
6176
on these same versions and 5.1.
 
6177
 
 
6178
=head1 FILES
 
6179
 
 
6180
$HOMEDIR/.innotop is used to store configuration information.
 
6181
 
 
6182
=head1 COPYRIGHT, LICENSE AND WARRANTY
 
6183
 
 
6184
This program is copyright (c) 2006 Baron Schwartz, baron at xaprb dot com.
 
6185
Feedback and improvements are welcome.
 
6186
 
 
6187
THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
 
6188
WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 
6189
MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 
6190
 
 
6191
This program is free software; you can redistribute it and/or modify it under
 
6192
the terms of the GNU General Public License as published by the Free Software
 
6193
Foundation, version 2; OR the Perl Artistic License.  On UNIX and similar
 
6194
systems, you can issue `man perlgpl' or `man perlartistic' to read these
 
6195
licenses.
 
6196
 
 
6197
You should have received a copy of the GNU General Public License along with
 
6198
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 
6199
Place, Suite 330, Boston, MA  02111-1307  USA.
 
6200
 
 
6201
Execute innotop and press '!' to see this information at any time.
 
6202
 
 
6203
=head1 AUTHOR
 
6204
 
 
6205
Baron Schwartz, baron at xaprb dot com.
 
6206
 
 
6207
=head1 BUGS
 
6208
 
 
6209
If you find any problems with innotop, please contact me.  Specifically, if
 
6210
you find any problems with parsing the InnoDB monitor output, I would greatly
 
6211
appreciate you sending me the full text of the monitor output that caused the
 
6212
problem.
 
6213
 
 
6214
=cut