3
eval 'exec /usr/bin/perl -S $0 ${1+"$@"}'
4
if 0; # not running under some shell
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.
13
use warnings FATAL => 'all';
14
use sigtrap qw(handler finish untrapped normal-signals);
18
use English qw(-no_match_vars);
20
use List::Util qw(max min maxstr);
23
# Version, license and warranty information. {{{1
24
# ###########################################################################
25
our $VERSION = '1.4.0';
27
my $innotop_license = <<"LICENSE";
29
This is innotop version $VERSION, a MySQL and InnoDB monitor.
31
This program is copyright (c) 2006 Baron Schwartz, baron at xaprb dot com.
32
Feedback and improvements are welcome.
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.
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
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.
49
# Configuration information and global setup {{{1
50
# ###########################################################################
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.
59
# This defines expected properties and defaults for the column definitions that
60
# eventually end up in tbl_meta.
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
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.
79
# Command-line parameters {{{2
80
# ###########################################################################
82
# Define cmdline args; each is spec, config, desc. Add more hash entries as needed.
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' },
90
# Define the order cmdline opts will appear in help output. Add any extra ones
92
my @opt_keys = qw( c n m h );
94
# This is the container for the command-line options' values to be stored in
95
# after processing. Initial values are defaults.
97
n => !( -t STDIN && -t STDOUT ), # If in/out aren't to terminals, we're interactive
100
Getopt::Long::Configure('no_ignore_case', 'bundling');
101
GetOptions( map { $opt_spec{$_}->{'s'} => \$opts{$_} } @opt_keys );
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'});
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.
120
# Meta-data (table definitions etc) {{{2
121
# ###########################################################################
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
# ###########################################################################
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) },
148
foreach my $key ( keys %builtin_exprs ) {
149
my ( $sub, $err ) = compile_expr($builtin_exprs{$key});
152
text => $builtin_exprs{$key},
154
name => $key, # useful for later
158
# ###########################################################################
159
# Column definitions {{{3
160
# Defines every column in every table. A named column has the following
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
# ###########################################################################
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' },
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} ? '' : '-';
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
411
# These are pre-processed to live in %filters above, by compiling them.
412
my %builtin_filters = (
415
return ( !$set->{info} || $set->{info} ne 'SHOW FULL PROCESSLIST' )
416
&& ( !$set->{query_text} || $set->{query_text} !~ m/INNODB STATUS$/ );
418
note => 'Removes the innotop processes from the list',
419
tbls => [qw(innodb_transactions processlist)],
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/ );
427
note => 'Removes processes which are not doing anything',
428
tbls => [qw(innodb_transactions processlist)],
432
return !$set->{state} || $set->{state} !~ m/^(?:Waiting for master|Has read all relay)/;
434
note => 'Removes slave I/O threads from the list',
435
tbls => [qw(slave_io_status)],
439
return $set->{num_times_open} + $set->{is_name_locked};
441
note => 'Removes tables that are not in use or locked',
442
tbls => [qw(open_tables)],
446
return $set->{master_file} ? 1 : 0;
448
note => 'Removes servers that are not masters',
449
tbls => [qw(master_status)],
453
return $set->{master_host} ? 1 : 0;
455
note => 'Removes servers that are not slaves',
456
tbls => [qw(slave_io_status slave_sql_status)],
458
thd_is_not_waiting => {
460
return $set->{thread_status} !~ m#waiting for i/o request#;
462
note => 'Removes idle I/O threads',
463
tbls => [qw(io_threads)],
466
foreach my $key ( keys %builtin_filters ) {
467
my ( $sub, $err ) = compile_filter($builtin_filters{$key}->{text});
470
text => $builtin_filters{$key}->{text},
472
name => $key, # useful for later
473
note => $builtin_filters{$key}->{note},
474
tbls => $builtin_filters{$key}->{tbls},
479
# Sets (arrayrefs) of variables that are used in V, S, G mode. They are read/written to
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
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) ],
519
# Defines sets of servers between which the user can quickly switch.
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.
527
my @conn_parts = qw(user pass dsn savepass dl_table);
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:
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.
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.
549
# Example 2: biz => { src => \%exprs{bat} } means the expression is
550
# evaluated for the current data set.
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
# ###########################################################################
559
adaptive_hash_index => {
560
hdr => 'Adaptive Hash Index',
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' },
569
visible => [ qw(cxn hash_table_size used_cells bufs_in_node_heap hash_searches_s non_hash_searches_s) ],
576
hdr => 'Buffer Pool',
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' },
601
visible => [ qw(cxn buf_pool_size buf_free pages_total pages_modified buf_pool_hit_rate total_mem_alloc add_pool_alloc)],
608
hdr => 'Deadlock Locks',
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' },
628
visible => [ qw(cxn mysql_thread_id txn_status lock_mode db tbl index special insert_intention)],
630
sort_cols => 'cxn mysql_thread_id',
634
deadlock_transactions => {
635
hdr => 'Deadlock Transactions',
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' },
669
visible => [ qw(cxn mysql_thread_id timestring user hostname victim time undo_log_entries lock_structs query_text)],
671
sort_cols => 'cxn mysql_thread_id',
676
hdr => 'EXPLAIN Results',
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' },
690
visible => [ qw(select_type tbl partitions scan_type possible_keys index key_len index_ref num_rows special)],
697
hdr => 'File I/O Misc',
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' },
709
visible => [ qw(cxn os_file_reads os_file_writes os_fsyncs io_reads_s io_writes_s io_bytes_s)],
716
hdr => 'Foreign Key Error Info',
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' },
729
visible => [ qw(timestring child_db child_table child_index parent_db parent_table parent_col parent_index fk_name attempted_op)],
736
hdr => 'Insert Buffers',
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' },
746
visible => [ qw(cxn inserts merged_recs merges size free_list_len seg_size)],
752
innodb_transactions => {
753
hdr => 'InnoDB Transactions',
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 },
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',
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' },
797
hdr => 'I/O Threads',
799
cxn => { src => 'cxn' },
800
thread => { src => 'thread' },
801
thread_purpose => { src => 'purpose' },
802
event_set => { src => 'event_set' },
803
thread_status => { src => 'state' },
805
visible => [ qw(cxn thread thread_purpose thread_status)],
807
sort_cols => 'cxn thread',
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' },
831
visible => [ qw(cxn mysql_thread_id lock_wait_time time lock_mode db tbl index insert_intention special)],
833
sort_cols => 'cxn -lock_wait_time',
838
hdr => 'Log Statistics',
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' },
849
visible => [ qw(cxn log_seq_no log_flushed_to last_chkp log_ios_done log_ios_s)],
856
hdr => 'Master Status',
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' },
864
visible => [ qw(cxn master_file master_pos)],
865
filters => [ qw(cxn_is_master) ],
871
hdr => 'Pending I/O',
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' },
884
visible => [ qw(cxn p_normal_aio_reads p_aio_writes p_ibuf_aio_reads p_sync_ios p_log_flushes p_log_ios)],
891
hdr => 'Open Tables',
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' },
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',
906
hdr => 'Page Statistics',
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' },
916
visible => [ qw(cxn pages_read pages_written pages_created page_reads_sec page_writes_sec page_creates_sec)],
923
hdr => 'MySQL Process List',
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) ] },
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',
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' },
953
hdr => 'Q-mode Header',
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' },
965
visible => [ qw(cxn when qps slow q_cache_hit key_buffer_hit bps_in bps_out)],
967
sort_cols => 'when cxn',
973
hdr => 'InnoDB Row Operations',
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' },
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)],
992
row_operation_misc => {
993
hdr => 'Row Operation Misc',
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' },
1004
visible => [ qw(cxn queries_in_queue queries_inside read_views_open main_thread_state)],
1011
hdr => 'InnoDB Semaphores',
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' },
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 )],
1033
slave_io_status => {
1034
hdr => 'Slave I/O Status',
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' },
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',
1057
{ col => 'slave_io_running', op => 'ne', arg => 'Yes', color => 'black on_red' },
1062
slave_sql_status => {
1063
hdr => 'Slave SQL Status',
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' },
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',
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' },
1102
hdr => 'InnoDB Wait Array',
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' },
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)],
1127
sort_cols => 'cxn -time',
1133
# TODO: V, G, S mode should have a table in tbl_meta
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};
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} ) {
1145
= defined($columns{$col_name}->{$prop})
1146
? $columns{$col_name}->{$prop}
1147
: $col_props{$prop};
1151
# Compile sort and color subroutines
1152
$table->{sort_func} = make_sort_func($table);
1153
$table->{color_func} = make_color_func($table);
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);
1164
# ###########################################################################
1165
# Valid comparison operators for color rules
1166
# ###########################################################################
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',
1184
# ###########################################################################
1185
# Valid functions for transformations.
1186
# ###########################################################################
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,
1197
# ###########################################################################
1198
# Operating modes {{{3
1199
# ###########################################################################
1202
hdr => 'InnoDB Buf',
1203
note => 'Shows buffer info from InnoDB',
1206
action => sub { toggle_config('status_inc') },
1207
label => 'Toggle overall/incremental status display',
1210
display_sub => \&display_B,
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)],
1218
hdr => 'InnoDB Deadlocks',
1219
note => 'View InnoDB deadlock information',
1222
action => \&create_deadlock,
1223
label => 'Wipe deadlock status info by creating a deadlock',
1226
display_sub => \&display_D,
1229
one_connection => 0,
1230
tables => [qw(deadlock_transactions deadlock_locks)],
1231
visible_tables => [qw(deadlock_transactions deadlock_locks)],
1234
hdr => 'InnoDB FK Err',
1235
note => 'View the latest InnoDB foreign key error',
1237
display_sub => \&display_F,
1240
one_connection => 1,
1241
tables => [qw(fk_error)],
1242
visible_tables => [qw(fk_error)],
1245
hdr => 'Load Graph',
1246
note => 'Shows query load graph',
1250
choose_var_set('G_set');
1253
label => "Choose which set to display",
1256
action => \&edit_current_var_set,
1257
label => 'Edit the current set of variables',
1260
action => sub { $clear_screen_sub->(); toggle_config('status_inc') },
1261
label => 'Toggle overall/incremental status display',
1264
display_sub => \&display_G,
1265
no_clear_screen => 1,
1268
one_connection => 1,
1270
visible_tables => [qw()],
1273
hdr => 'InnoDB I/O Info',
1274
note => 'Shows I/O info (i/o, log...) from InnoDB',
1277
action => sub { toggle_config('status_inc') },
1278
label => 'Toggle overall/incremental status display',
1281
display_sub => \&display_I,
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)],
1289
hdr => 'Replication Status',
1290
note => 'Shows replication (master and slave) status',
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)',
1297
action => sub { toggle_config('status_inc') },
1298
label => 'Toggle overall/incremental status display',
1301
action => sub { send_cmd_to_servers('STOP SLAVE', 1); },
1302
label => 'Stop slave(s)',
1305
display_sub => \&display_M,
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)],
1313
hdr => 'Open Tables',
1314
note => 'Shows open tables in MySQL',
1317
action => sub { get_config_interactive('O_fmt'); },
1318
label => "Choose which columns to display",
1321
action => sub { reverse_sort('open_tables'); },
1322
label => 'Reverse sort order',
1325
action => sub { choose_sort_cols('open_tables'); },
1326
label => "Choose sort column",
1329
display_sub => \&display_O,
1332
one_connection => 0,
1333
tables => [qw(open_tables)],
1334
visible_tables => [qw(open_tables)],
1337
hdr => 'Query List',
1338
note => 'Shows queries from SHOW FULL PROCESSLIST',
1341
action => sub { toggle_filter('processlist', 'hide_self') },
1342
label => 'Toggle hiding the innotop process',
1345
action => sub { choose('Q_fmt'); },
1346
label => "Choose which columns to display",
1349
action => sub { analyze_query('e'); },
1350
label => "Explain a thread's query",
1353
action => sub { analyze_query('f'); },
1354
label => "Show a thread's full query",
1357
action => sub { toggle_config('show_QT_header') },
1358
label => 'Toggle the header on and off',
1361
action => sub { toggle_filter('processlist', 'hide_inactive') },
1362
label => 'Toggle showing or hiding idle (Sleep) processes',
1365
action => sub { kill_query('CONNECTION') },
1366
label => "Kill a query's connection",
1369
action => sub { reverse_sort('processlist'); },
1370
label => 'Reverse sort order',
1373
action => sub { choose_sort_cols('processlist'); },
1374
label => "Change the display's sort column",
1377
action => sub { kill_query('QUERY') },
1378
label => "Kill a query (not the connection; requires 5.0)",
1381
display_sub => \&display_Q,
1384
one_connection => 0,
1385
tables => [qw(q_header processlist)],
1386
visible_tables => [qw(q_header processlist)],
1389
hdr => 'InnoDB Row Ops',
1390
note => 'Shows InnoDB row operation and semaphore info',
1393
action => sub { toggle_config('status_inc') },
1394
label => 'Toggle overall/incremental status display',
1397
display_sub => \&display_R,
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)],
1405
hdr => 'Load Stats',
1406
note => 'Shows query load statistics a la vmstat',
1410
choose_var_set('S_set');
1413
label => "Choose which set to display",
1416
action => \&edit_current_var_set,
1417
label => 'Edit the current set of variables',
1420
action => sub { $clear_screen_sub->(); toggle_config('status_inc') },
1421
label => 'Toggle overall/incremental status display',
1424
action => sub { set_display_precision(-1) },
1425
label => 'Decrease fractional display precision',
1428
action => sub { set_display_precision(1) },
1429
label => 'Increase fractional display precision',
1432
display_sub => \&display_S,
1433
no_clear_screen => 1,
1436
one_connection => 0,
1438
visible_tables => [qw()],
1441
hdr => 'InnoDB Txns',
1442
note => 'Shows InnoDB transactions in top-like format',
1445
action => sub { toggle_filter('innodb_transactions', 'hide_self') },
1446
label => 'Toggle hiding the innotop process',
1449
action => sub { get_config_interactive('T_fmt'); },
1450
label => "Choose which columns to display",
1453
action => sub { toggle_config('show_QT_header') },
1454
label => 'Toggle the header on and off',
1457
action => sub { toggle_filter('innodb_transactions', 'hide_inactive') },
1458
label => 'Toggle showing or hiding inactive transactions',
1461
action => sub { kill_query('CONNECTION') },
1462
label => "Kill a transaction's connection",
1465
action => sub { reverse_sort('innodb_transactions'); },
1466
label => 'Reverse sort order',
1469
action => sub { choose_sort_cols('innodb_transactions'); },
1470
label => "Change the display's sort column",
1473
action => sub { kill_query('QUERY') },
1474
label => "Kill a query, not a connection (requires 5.0)",
1477
display_sub => \&display_T,
1480
one_connection => 0,
1481
tables => [qw(innodb_transactions)],
1482
visible_tables => [qw(innodb_transactions)],
1485
hdr => 'Variables & Status',
1486
note => 'Shows values from SHOW STATUS and SHOW VARIABLES',
1489
action => sub { choose_var_set('V_set') },
1490
label => 'Choose which set to display',
1493
action => \&edit_current_var_set,
1494
label => 'Edit the current set of variables',
1497
action => sub { toggle_config('status_inc') },
1498
label => 'Toggle overall/incremental status display',
1501
action => sub { set_display_precision(-1) },
1502
label => 'Decrease fractional display precision',
1505
action => sub { set_display_precision(1) },
1506
label => 'Increase fractional display precision',
1509
display_sub => \&display_V,
1512
one_connection => 1,
1514
visible_tables => [qw()],
1517
hdr => 'InnoDB Lock Waits',
1518
note => 'Shows transaction lock waits and OS wait array info',
1521
action => sub { get_config_interactive('W_fmt') },
1522
label => 'Choose which columns to show in the lock waits table',
1525
display_sub => \&display_W,
1528
one_connection => 0,
1529
tables => [qw(lock_waits wait_array)],
1530
visible_tables => [qw(lock_waits wait_array)],
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
# ###########################################################################
1542
action => \&edit_configuration,
1543
label => 'Edit configuration settings',
1546
action => \&display_help,
1547
label => 'Show help',
1550
action => \&display_license,
1551
label => 'Show license and warranty information',
1554
action => \&edit_table,
1555
label => "Edit columns, etc in the displayed table(s)",
1558
action => \&choose_server_groups,
1559
label => 'Select/create server groups',
1562
action => \&choose_servers,
1563
label => 'Select/create server connections',
1566
action => \&next_server_group,
1567
label => 'Switch to the next server group',
1571
action => sub { switch_mode('B') },
1572
label => 'Switch to B mode (InnoDB Buffer/Hash Index)',
1575
action => sub { switch_mode('D') },
1576
label => 'Switch to D mode (InnoDB Deadlock Information)',
1579
action => sub { switch_mode('F') },
1580
label => 'Switch to F mode (InnoDB Foreign Key Error)',
1583
action => \&start_G_mode,
1584
label => 'Switch to G mode (Load Graph)',
1587
action => sub { switch_mode('I') },
1588
label => 'Switch to I mode (InnoDB I/O and Log)',
1591
action => sub { switch_mode('M') },
1592
label => 'Switch to M mode (MySQL Replication Status)',
1595
action => sub { switch_mode('O') },
1596
label => 'Switch to O mode (MySQL Open Tables)',
1599
action => sub { switch_mode('Q') },
1600
label => 'Switch to Q mode (Query List, like mytop)',
1603
action => sub { switch_mode('R') },
1604
label => 'Switch to R mode (InnoDB Row Operations)',
1607
action => \&start_S_mode,
1608
label => 'Switch to S mode (Load Statistics)',
1611
action => sub { switch_mode('T') },
1612
label => 'Switch to T mode (InnoDB Transaction)',
1615
action => sub { switch_mode('V') },
1616
label => 'Switch to V mode (Variable & Status)',
1619
action => sub { switch_mode('W') },
1620
label => 'Switch to W mode (InnoDB Lock Waits and OS Wait Info)',
1623
action => sub { get_config_interactive('interval') },
1624
label => 'Change refresh interval',
1626
p => { action => \&pause, label => 'Pause innotop', },
1627
q => { action => \&finish, label => 'Quit innotop', },
1630
# ###########################################################################
1631
# Config editor key mappings {{{3
1632
# ###########################################################################
1633
my %cfg_editor_action = (
1635
note => 'Edit columns, etc in the displayed table(s)',
1636
func => \&edit_table,
1639
note => 'Edit general configuration',
1640
func => \&edit_configuration_variables,
1643
note => 'Edit row-coloring rules',
1644
func => \&edit_color_rules,
1647
note => 'Edit server groups',
1648
func => \&edit_server_groups,
1651
note => 'Choose which table(s) to display in this mode',
1652
func => \&choose_mode_tables,
1656
# ###########################################################################
1657
# Color editor key mappings {{{3
1658
# ###########################################################################
1659
my %color_editor_action = (
1661
note => 'Create a new color rule',
1663
my ( $tbl, $idx ) = @_;
1664
my $meta = $tbl_meta{$tbl};
1666
$clear_screen_sub->();
1670
'Choose the target column for the rule',
1672
sub { return keys %{$meta->{cols}} },
1673
{ map { $_ => $meta->{cols}->{$_}->{label} } keys %{$meta->{cols}} });
1675
( $col ) = grep { $_ } split(/\W+/, $col);
1676
return $idx unless $col && exists $meta->{cols}->{$col};
1678
$clear_screen_sub->();
1682
'Choose the comparison operator for the rule',
1684
sub { return keys %comp_ops },
1685
{ map { $_ => $comp_ops{$_} } keys %comp_ops } );
1688
return $idx unless $op && exists $comp_ops{$op};
1692
$arg = prompt('Specify an argument for the comparison');
1693
} until defined $arg;
1697
$color = prompt_list(
1698
'Choose the color(s) the row should be when the rule matches',
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;
1706
push @{$tbl_meta{$tbl}->{colors}}, {
1717
note => 'Remove the selected rule',
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;
1727
note => 'Move highlight down one',
1729
my ( $tbl, $idx ) = @_;
1730
my $num_rules = scalar @{$tbl_meta{$tbl}->{colors}};
1731
return ($idx + 1) % $num_rules;
1735
note => 'Move highlight up one',
1737
my ( $tbl, $idx ) = @_;
1738
my $num_rules = scalar @{$tbl_meta{$tbl}->{colors}};
1739
return ($idx - 1) % $num_rules;
1743
note => 'Move selected rule up one',
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;
1755
note => 'Move selected rule down one',
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;
1768
# ###########################################################################
1769
# Table editor key mappings {{{3
1770
# ###########################################################################
1771
my %tbl_editor_action = (
1773
note => 'Add a column to the table',
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(
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;
1792
note => 'Create a new column and add it to the table',
1794
my ( $tbl, $col ) = @_;
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.");
1802
$col = prompt("Enter column name");
1803
$col = '' if $col =~ m/[^a-z0-9_]/;
1806
$clear_screen_sub->();
1809
$hdr = prompt("Enter column header");
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.");
1817
my ( $src, $sub, $err );
1820
print "Error: $err\n\n";
1822
$src = prompt("Enter column source");
1824
( $sub, $err ) = compile_expr($src, 1);
1826
} until ( !$src || !$err);
1831
return $col unless $exp && $exprs{$exp};
1834
$tbl_meta{$tbl}->{cols}->{$col} = {
1839
label => 'User-defined',
1842
expr => $exp ? $exp : '',
1846
func => $sub || $exprs{$exp}->{func},
1849
$tbl_meta{$tbl}->{visible} = [ unique(@{$tbl_meta{$tbl}->{visible}}, $col) ];
1854
note => 'Remove selected column',
1856
my ( $tbl, $col ) = @_;
1857
my @visible_cols = @{ $tbl_meta{$tbl}->{visible} };
1859
return $col unless @visible_cols > 1;
1860
while ( $visible_cols[$idx] ne $col ) {
1863
$tbl_meta{$tbl}->{visible} = [ grep { $_ ne $col } @visible_cols ];
1864
return $idx == $#visible_cols ? $visible_cols[$idx - 1] : $visible_cols[$idx + 1];
1868
note => 'Edit selected column',
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);
1877
# Do what the user asked...
1878
if ( $answer && grep { $_ eq $answer } @prop ) {
1879
if ( $answer eq 'expr' ) {
1880
$meta->{expr} = get_expr();
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) ];
1890
@{$meta}{$answer, 'user', 'tbl' } = ( $val, 1, $tbl );
1891
if ( $answer eq 'src' ) {
1895
if ( $meta->{expr} ) {
1896
$meta->{src} = $exprs{$meta->{expr}};
1900
my @display_lines = (
1902
"You are editing column $tbl.$col.\n",
1905
push @display_lines, create_table2(
1907
{ map { $_ => $_ } @prop },
1908
{ map { $_ => ref $meta->{$_} eq 'ARRAY' ? join(' ', @{$meta->{$_}})
1909
: ref $meta->{$_} ? '[expression code]'
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' );
1923
note => 'Move highlight down one',
1925
my ( $tbl, $col ) = @_;
1926
my @visible_cols = @{ $tbl_meta{$tbl}->{visible} };
1928
while ( $visible_cols[$idx] ne $col ) {
1931
return $visible_cols[ ($idx + 1) % @visible_cols ];
1935
note => 'Move highlight up one',
1937
my ( $tbl, $col ) = @_;
1938
my @visible_cols = @{ $tbl_meta{$tbl}->{visible} };
1940
while ( $visible_cols[$idx] ne $col ) {
1943
return $visible_cols[ $idx - 1 ];
1947
note => 'Move selected column up one',
1949
my ( $tbl, $col ) = @_;
1950
my $meta = $tbl_meta{$tbl};
1951
my @visible_cols = @{$meta->{visible}};
1953
while ( $visible_cols[$idx] ne $col ) {
1957
$visible_cols[$idx] = $visible_cols[$idx - 1];
1958
$visible_cols[$idx - 1] = $col;
1959
$meta->{visible} = \@visible_cols;
1962
shift @{$meta->{visible}};
1963
push @{$meta->{visible}}, $col;
1969
note => 'Move selected column down one',
1971
my ( $tbl, $col ) = @_;
1972
my $meta = $tbl_meta{$tbl};
1973
my @visible_cols = @{$meta->{visible}};
1975
while ( $visible_cols[$idx] ne $col ) {
1978
if ( $idx == $#visible_cols ) {
1979
unshift @{$meta->{visible}}, $col;
1980
pop @{$meta->{visible}};
1983
$visible_cols[$idx] = $visible_cols[$idx + 1];
1984
$visible_cols[$idx + 1] = $col;
1985
$meta->{visible} = \@visible_cols;
1991
note => 'Edit table meta-data (sort column, filters...)',
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);
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);
2008
elsif ( $answer eq 'filters' ) {
2009
$clear_screen_sub->();
2010
my $val = prompt_list(
2013
sub { return keys %filters },
2015
map { $_ => $filters{$_}->{note} }
2016
grep { grep { $tbl eq $_ } @{$filters{$_}->{tbls}} }
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);
2028
@choices = grep { exists $filters{$_} } @choices;
2029
@choices = grep { grep { $tbl eq $_ } @{$filters{$_}->{tbls}} } @choices;
2030
$meta->{filters} = [ @choices ];
2034
my @display_lines = "You are editing table $tbl.";
2036
push @display_lines, '', create_caption('Properties', create_table2(
2038
{ map { $_ => $_ } @prop },
2039
{ map { $_ => ref $meta->{$_} eq 'ARRAY' ? join(' ', @{$meta->{$_}})
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' );
2054
# ###########################################################################
2055
# Global variables and environment {{{2
2056
# ###########################################################################
2058
# Set up required stuff for interactive mode...
2060
require Term::ReadKey;
2061
import Term::ReadKey qw(ReadMode ReadKey);
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
2067
my $windows = $OSNAME =~ m/Win/i;
2069
my $MAX_ULONG = 4294967295; # 2^32-1
2070
my $num_regex = qr/^[+-]?(?=\d|\.)\d*(?:\.\d+)?(?:E[+-]?\d+|)$/i;
2074
require Term::ReadLine;
2075
$term = Term::ReadLine->new('innotop');
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.
2082
my %info_gotten = (); # Which things have been retrieved for the current clock tick.
2084
# Stores info on currently displayed queries: cxn, connection ID, query text.
2085
my @current_queries;
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;
2094
# If hi-res time is available, use it.
2096
require Time::HiRes;
2097
import Time::HiRes qw(time sleep);
2101
# Find the home directory; it's different on different OSes.
2102
my $homepath = $ENV{HOME} || $ENV{HOMEPATH} || $ENV{USERPROFILE} || '.';
2104
# If terminal coloring is available, use it. The only function I want from
2105
# the module is the colored() function.
2109
require Win32::Console::ANSI;
2111
require Term::ANSIColor;
2112
import Term::ANSIColor qw(colored);
2116
if ( $EVAL_ERROR ) {
2117
# If there was an error, manufacture my own colored() function that does no
2119
*colored = sub { return shift; };
2123
$clear_screen_sub = sub {};
2125
elsif ( $windows ) {
2126
$clear_screen_sub = sub { $lines_printed = 0; system("cls") };
2129
my $clear = `clear`;
2130
$clear_screen_sub = sub { $lines_printed = 0; print $clear };
2133
# ###########################################################################
2134
# Config storage. {{{2
2135
# ###########################################################################
2137
show_cxn_errors_in_tbl => {
2139
note => 'Whether to display connection errors at the end of every table',
2143
show_cxn_errors => {
2145
note => 'Whether to print connection errors to STDOUT',
2151
note => 'Whether the config file is read-only',
2157
note => 'Whether to show GLOBAL variables and status',
2161
header_highlight => {
2163
note => 'How to highlight table column headers',
2165
pat => qr/^(?:bold|underline)$/,
2167
display_table_captions => {
2169
note => 'Whether to put captions on tables',
2175
note => 'Whether to compact the headers in some modes',
2181
note => 'What type of characters should be displayed in queries (ascii, unicode, none)',
2183
pat => qr/^(?:ascii|unicode|none)$/,
2187
note => 'Whether to auto-wipe InnoDB deadlocks',
2193
note => '[Win32] Max window height',
2199
note => 'Debug mode (more verbose errors, uses more memory)',
2205
note => 'How many digits to show in fractional numbers and percents',
2211
note => 'Whether to show the header in Q and T modes',
2212
conf => [ qw(Q T) ],
2215
val => "$homepath/.innotop_core_dump",
2216
note => 'A debug file in case you are interested in error output',
2221
note => 'Whether to show the status bar in the display',
2226
note => "Which mode to start in",
2231
note => 'Whether to show raw or incremental values for status variables',
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.",
2242
note => 'Which set of variables to display in V (Variables/Status) mode',
2245
num_status_sets => {
2247
pat => qr/^\d*?[1-9]\d*$/,
2248
note => 'How many sets of STATUS and VARIABLES values to show',
2254
note => 'Which set of variables to display in G (Load Graph) mode',
2260
note => 'Which set of variables to display in S (Load Statistics) mode',
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
2270
# ###########################################################################
2271
my %config_file_sections = (
2273
reader => \&load_config_filters,
2274
writer => \&save_config_filters,
2277
reader => \&load_config_active_filters,
2278
writer => \&save_config_active_filters,
2281
reader => \&load_config_visible_tables,
2282
writer => \&save_config_visible_tables,
2285
reader => \&load_config_sort_cols,
2286
writer => \&save_config_sort_cols,
2289
reader => \&load_config_active_columns,
2290
writer => \&save_config_active_columns,
2293
reader => \&load_config_expressions,
2294
writer => \&save_config_expressions,
2297
reader => \&load_config_tbl_meta,
2298
writer => \&save_config_tbl_meta,
2301
reader => \&load_config_config,
2302
writer => \&save_config_config,
2305
reader => \&load_config_connections,
2306
writer => \&save_config_connections,
2308
active_connections => {
2309
reader => \&load_config_active_connections,
2310
writer => \&save_config_active_connections,
2313
reader => \&load_config_server_groups,
2314
writer => \&save_config_server_groups,
2316
active_server_groups => {
2317
reader => \&load_config_active_server_groups,
2318
writer => \&save_config_active_server_groups,
2320
max_values_seen => {
2321
reader => \&load_config_mvs,
2322
writer => \&save_config_mvs,
2325
reader => \&load_config_varsets,
2326
writer => \&save_config_varsets,
2329
reader => \&load_config_colors,
2330
writer => \&save_config_colors,
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);
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 {
2346
my $meta = $dbhs{$cxn};
2347
return ( $meta->{ver_major} >= 5 )
2348
? 'SHOW ENGINE INNODB STATUS'
2349
: 'SHOW INNODB STATUS';
2351
SHOW_VARIABLES => sub {
2353
my $meta = $dbhs{$cxn};
2354
return ( $config{global}->{val} && $meta->{ver_major} >= 4 ) && ( $meta->{ver_rev} >= 3 )
2355
? 'SHOW GLOBAL VARIABLES'
2358
SHOW_STATUS => sub {
2360
my $meta = $dbhs{$cxn};
2361
return ( $config{global}->{val} && $meta->{ver_major} >= 5 ) && ( $meta->{ver_rev} >= 2 )
2362
? 'SHOW GLOBAL STATUS'
2367
my $meta = $dbhs{$cxn};
2368
return ( $meta->{ver_major} >= 5 )
2372
SHOW_MASTER_STATUS => sub {
2374
return 'SHOW MASTER STATUS';
2376
SHOW_SLAVE_STATUS => sub {
2378
return 'SHOW SLAVE STATUS';
2380
KILL_CONNECTION => sub {
2382
return 'KILL CONNECTION ?';
2384
OPEN_TABLES => sub {
2386
return 'SHOW OPEN TABLES';
2388
PROCESSLIST => sub {
2390
return 'SHOW FULL PROCESSLIST';
2394
# ###########################################################################
2395
# Run the program {{{1
2396
# ###########################################################################
2398
# This config variable is only useful for MS Windows because its terminal
2399
# can't tell how tall it is.
2401
delete $config{max_height};
2404
# Try to lower my priority.
2405
eval { setpriority(0, 0, getpriority(0, 0) + 10); };
2407
# Print stuff to the screen immediately, don't wait for a newline.
2408
$OUTPUT_AUTOFLUSH = 1;
2410
# Clear the screen and load the configuration.
2411
$clear_screen_sub->();
2413
post_process_tbl_meta();
2415
# Make sure no changes are written to config file in non-interactive mode.
2417
$config{readonly}->{val} = 1;
2423
my $mode = $config{mode}->{val};
2426
@last_term_size = @this_term_size;
2427
@this_term_size = Term::ReadKey::GetTerminalSize(\*STDOUT);
2429
$this_term_size[0]--;
2431
= min($this_term_size[1], $config{max_height}->{val});
2433
die("Can't read terminal size") unless @this_term_size;
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";
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 ];
2450
choose_connections();
2454
# Term::ReadLine might have re-set $OUTPUT_AUTOFLUSH.
2455
$OUTPUT_AUTOFLUSH = 1;
2458
my $sets = $config{num_status_sets}->{val};
2459
foreach my $store ( values %vars ) {
2460
delete @{$store}{ grep { $_ < $clock - $sets } keys %$store };
2464
# Call the subroutine to display this mode.
2465
$modes{$mode}->{display_sub}->();
2469
sleep($config{interval}->{val});
2473
$char = ReadKey($config{interval}->{val});
2477
# Handle whatever action the key indicates.
2482
if ( $EVAL_ERROR ) {
2483
core_dump( $EVAL_ERROR );
2488
# Mode functions{{{2
2492
$config{mode}->{val} = $mode;
2495
# Prompting functions {{{2
2497
# Prompts the user for a value, given a question, initial value,
2498
# a completion function and a hashref of hints.
2500
die "Can't call in non-interactive mode" if $opts{n};
2501
my ( $question, $init, $completion, $hints ) = @_;
2503
# Figure out how wide the table will be
2504
my $max_name = max(map { length($_) } keys %$hints );
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 },
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]],
2523
map { ref $_ ? colored(@$_) : $_ } create_caption('Choose from', @meta_rows), ''),
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+$//;
2535
# Prints out a prompt and reads from the keyboard, then validates with the
2536
# validation regex until the input is correct.
2538
die "Can't call in non-interactive mode" if $opts{n};
2539
my ( $prompt, $regex, $init, $completion ) = @_;
2543
if ( $completion ) {
2544
$term->Attribs->{completion_function} = $completion;
2546
$response = $term->readline("$prompt: ", $init);
2547
if ( $regex && $response !~ m/$regex/ ) {
2548
print "Invalid response.\n\n";
2553
} while ( !$success );
2554
$OUTPUT_AUTOFLUSH = 1;
2555
$response =~ s/\s+$//;
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.
2563
my ( $prompt ) = @_;
2564
print colored("$prompt: ", 'underline');
2567
$response = <STDIN>;
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.
2579
if ( defined $char ) {
2580
my $mode = $config{mode}->{val};
2582
= defined($modes{$mode}->{action_for}->{$char})
2583
? $modes{$mode}->{action_for}->{$char}->{action}
2584
: defined($action_for{$char})
2585
? $action_for{$char}->{action}
2593
die "Can't call in non-interactive mode" if $opts{n};
2595
print defined($msg) ? "\n$msg" : "\nPress any key to continue";
2597
my $char = ReadKey(0);
2605
$tbl_meta{$tbl}->{sort_dir} *= -1;
2609
# Selects connection(s). If the mode (or argument list) has only one, returns
2610
# it without prompt.
2612
my ( $prompt, @cxns ) = @_;
2614
@cxns = get_connections();
2619
my $choices = prompt_list(
2622
sub{ return @cxns },
2623
{ map { $_ => $connections{$_}->{dsn} } @cxns });
2624
my @result = unique(grep { my $a = $_; grep { $_ eq $a } @cxns } split(/\s+/, $choices));
2629
# Kills a connection, or on new versions, optionally a query but not connection.
2631
my ( $q_or_c ) = @_;
2633
my ( $cxn ) = select_cxn('Kill on which server');
2634
return unless $cxn && exists($connections{$cxn});
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);
2642
if ( $EVAL_ERROR ) {
2643
print "\nError: $EVAL_ERROR";
2650
$config{V_set}->{val} = shift;
2653
# set_display_precision {{{3
2654
sub set_display_precision {
2656
$config{num_digits}->{val} = min(9, max(0, $config{num_digits}->{val} + $dir));
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 ];
2667
push @$filters, $filter;
2671
# toggle_config {{{3
2674
$config{$key}->{val} ^= 1;
2677
# create_deadlock {{{3
2678
sub create_deadlock {
2679
$clear_screen_sub->();
2681
print "This function will deliberately cause a small deadlock, "
2682
. "clearing deadlock information from the InnoDB monitor.\n\n";
2684
my $answer = prompt("Are you sure you want to proceed? Say 'y' if you do");
2685
return 0 unless $answer eq 'y';
2687
my ( $cxn ) = select_cxn('Clear on which server? ');
2688
return unless $cxn && exists($connections{$cxn});
2690
clear_deadlock($cxn);
2693
# deadlock_thread {{{3
2694
sub deadlock_thread {
2695
my ( $id, $tbl, $cxn ) = @_;
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",
2704
my $dbh = get_new_db_connection($cxn, 1);
2705
foreach my $stmt (@stmts[0..2]) {
2709
$dbh->do($stmts[-1]);
2711
if ( $EVAL_ERROR ) {
2712
if ( $EVAL_ERROR !~ m/Deadlock found/ ) {
2719
sub send_cmd_to_servers {
2720
my ( $cmd, $all, $hint ) = @_;
2723
@cxns = get_connections();
2725
@cxns = select_cxn('Which servers?', @cxns);
2727
print "\nHint: $hint\n";
2729
$cmd = prompt('Command to send', undef, $cmd);
2730
foreach my $cxn ( @cxns ) {
2732
my $sth = do_query($cxn, $cmd);
2734
if ( $EVAL_ERROR ) {
2735
print "Error from $cxn: $EVAL_ERROR\n";
2738
print "Success on $cxn\n";
2744
# Display functions {{{2
2748
$clear_screen_sub->();
2754
$clear_screen_sub->();
2761
my @cxns = get_connections();
2762
get_innodb_status(\@cxns);
2765
my @page_statistics;
2767
my @adaptive_hash_index;
2769
buffer_pool => \@buffer_pool,
2770
page_statistics => \@page_statistics,
2771
insert_buffers => \@insert_buffers,
2772
adaptive_hash_index => \@adaptive_hash_index,
2775
my @visible = get_visible_tables();
2776
my %wanted = map { $_ => 1 } @visible;
2778
foreach my $cxn ( @cxns ) {
2779
my $set = $vars{$cxn}->{$clock};
2781
if ( $set->{IB_bp_complete} ) {
2782
if ( $wanted{buffer_pool} ) {
2783
push @buffer_pool, extract_values($set, 'buffer_pool');
2785
if ( $wanted{page_statistics} ) {
2786
push @page_statistics, extract_values($set, 'page_statistics');
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,
2795
if ( $wanted{adaptive_hash_index} ) {
2796
push @adaptive_hash_index, extract_values($set, 'adaptive_hash_index');
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++;;
2807
draw_screen(\@display_lines);
2813
my @cxns = get_connections();
2814
get_innodb_status(\@cxns);
2816
my @deadlock_transactions;
2819
deadlock_transactions => \@deadlock_transactions,
2820
deadlock_locks => \@deadlock_locks,
2823
my @visible = get_visible_tables();
2824
my %wanted = map { $_ => 1 } @visible;
2826
foreach my $cxn ( @cxns ) {
2827
my $innodb_status = $vars{$cxn}->{$clock};
2829
if ( $innodb_status->{IB_dl_timestring} ) {
2831
my $victim = $innodb_status->{IB_dl_rolled_back} || 0;
2834
foreach my $txn_id ( keys %{$innodb_status->{IB_dl_txns}} ) {
2835
my $txn = $innodb_status->{IB_dl_txns}->{$txn_id};
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;
2847
if ( $wanted{deadlock_locks} ) {
2848
foreach my $what (qw(waits_for holds)) {
2849
my $locks = $txn->{$what};
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;
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++;;
2872
draw_screen(\@display_lines);
2878
my ( $cxn ) = get_connections();
2879
get_innodb_status([$cxn]);
2880
my $innodb_status = $vars{$cxn}->{$clock};
2882
if ( $innodb_status->{IB_fk_timestring} ) {
2884
push @display_lines, 'Reason: ' . $innodb_status->{IB_fk_reason};
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,
2891
"User $txn->{user} from $txn->{hostname}, thread $txn->{mysql_thread_id} was executing:",
2892
'', no_ctrl_char($txn->{query_text});
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;
2904
push @display_lines, '', 'No foreign key error data.';
2906
draw_screen(\@display_lines, { raw => 1 } );
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.
2916
if ( !exists $vars{$cxn}->{$clock - 1} ) {
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/;
2926
# Clear the screen if the display width changed.
2927
if ( @last_term_size && $this_term_size[0] != $last_term_size[0] ) {
2929
$clear_screen_sub->();
2933
my $set = inc(0, $cxn);
2934
$set = { map { $_ => ($set->{$_} || 1) / ( $set->{Uptime_hires} || 1) } @$fmt };
2936
# Update max ever seen.
2937
map { $mvs{$_} = max($mvs{$_} || 1, $set->{$_}) } @$fmt;
2939
# Print headers every now and then.
2941
if ( $lines_printed == 0 ) {
2942
print join("\t", @$fmt), "\n";
2943
print join("\t", map { shorten($mvs{$_}) } @$fmt), "\n";
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);
2952
# Scale the values against the max ever seen.
2953
map { $set->{$_} /= $mvs{$_} } @$fmt;
2956
printf($format, map { ( '*' x int( $width * $set->{$_} )) || '.' } @$fmt );
2962
my @cxns = get_connections();
2963
get_innodb_status(\@cxns);
2970
io_threads => \@io_threads,
2971
pending_io => \@pending_io,
2972
file_io_misc => \@file_io_misc,
2973
log_statistics => \@log_statistics,
2976
my @visible = get_visible_tables();
2977
my %wanted = map { $_ => 1 } @visible;
2979
foreach my $cxn ( @cxns ) {
2980
my $set = $vars{$cxn}->{$clock};
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;
2990
if ( $wanted{pending_io} ) {
2991
push @pending_io, extract_values($set, 'pending_io');
2993
if ( $wanted{file_io_misc} ) {
2994
push @file_io_misc, extract_values(
2995
$config{status_inc}->{val} ? inc(0, $cxn) : $set,
2999
if ( $set->{IB_lg_complete} && $wanted{log_statistics} ) {
3000
push @log_statistics, extract_values($set, 'log_statistics');
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++;;
3010
draw_screen(\@display_lines);
3016
my @cxns = get_connections();
3017
get_master_slave_status(@cxns);
3018
get_status_info(@cxns);
3020
my @slave_sql_status;
3021
my @slave_io_status;
3024
slave_sql_status => \@slave_sql_status,
3025
slave_io_status => \@slave_io_status,
3026
master_status => \@master_status,
3029
my @visible = get_visible_tables();
3030
my %wanted = map { $_ => 1 } @visible;
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');
3037
if ( $wanted{slave_io_status} ) {
3038
push @slave_io_status, extract_values($set, 'slave_io_status');
3040
if ( $wanted{master_status} ) {
3041
push @master_status, extract_values($set, 'master_status');
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++;;
3051
draw_screen(\@display_lines);
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);
3071
q_header => \@q_header,
3072
processlist => \@processlist,
3075
my @visible = $opts{n} ? 'processlist' : get_visible_tables();
3076
my %wanted = map { $_ => 1 } @visible;
3078
# Config variable overrides %wanted here. TODO: this is hack-ish.
3079
$wanted{q_header} = $config{show_QT_header}->{val};
3082
my @cxns = get_connections();
3083
my @full_processlist = get_full_processlist(@cxns);
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;
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;
3104
if ( $wanted{processlist} ) {
3105
push @processlist, map { extract_values($_, 'processlist') } @full_processlist;
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++;
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 {
3120
@hash{ qw(cxn id db query) } = @{$_}{ qw(cxn mysql_thread_id db info) };
3122
} @{$rows_for{processlist}};
3124
draw_screen(\@display_lines);
3130
my @cxns = get_connections();
3131
get_innodb_status(\@cxns);
3134
my @row_operation_misc;
3138
row_operations => \@row_operations,
3139
row_operation_misc => \@row_operation_misc,
3140
semaphores => \@semaphores,
3141
wait_array => \@wait_array,
3144
my @visible = get_visible_tables();
3145
my %wanted = map { $_ => 1 } @visible;
3146
my $incvar = $config{status_inc}->{val};
3148
foreach my $cxn ( @cxns ) {
3149
my $set = $vars{$cxn}->{$clock};
3150
my $inc; # Only assigned to if wanted
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');
3157
if ( $wanted{row_operation_misc} ) {
3158
push @row_operation_misc, extract_values($set, 'row_operation_misc'),
3162
if ( $set->{IB_sm_complete} && $wanted{semaphores} ) {
3163
$inc ||= $incvar ? inc(0, $cxn) : $set;
3164
push @semaphores, extract_values($inc, 'semaphores');
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;
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++;
3182
draw_screen(\@display_lines);
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.
3194
# Clear the screen if the display width changed.
3195
if ( @last_term_size && $this_term_size[0] != $last_term_size[0] ) {
3197
$clear_screen_sub->();
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));
3204
# Print headers every now and then. Headers can get really long, so compact them.
3207
if ( $lines_printed == 0 ) {
3208
print join("\t", @hdr), "\n";
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";
3216
# Design a column format for the values.
3219
? join("\t", map { '%s' } @$fmt) . "\n"
3220
: join(' ', map { "%${width}s" } @hdr) . "\n";
3223
my $set = $inc ? inc(0, $cxn) : $vars{$cxn}->{$clock};
3226
exists $set->{$_} ? $set->{$_}
3227
: exists $exprs{$_} ? $exprs{$_}->{func}->($set)
3239
my @cxns = get_connections();
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} );
3244
get_innodb_status( \@cxns, [ $hdr ? qw(bp) : () ] );
3246
foreach my $cxn ( get_connections() ) {
3247
my $set = $vars{$cxn}->{$clock};
3249
next unless $set->{IB_tx_transactions};
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;
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}",
3272
push @display_lines, '', set_to_tbl(\@txns, 'innodb_transactions'), get_cxn_errors(@cxns);
3274
draw_screen(\@display_lines);
3278
# TODO: when entering V mode, remove any non-contiguous stuff from %vars.
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};
3286
get_status_info($cxn);
3287
get_innodb_status([$cxn]); # TODO: might not be needed.
3289
# Figure out how many past sets have actually been kept.
3290
while ( !exists $vars{$cxn}->{$clock - $num} ) {
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 => '' };
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)
3310
$rows[$row]->{"set_$set"} = defined $val ? $val : 0;
3315
foreach my $set ( 0 .. $num ) {
3316
push @cols, "set_$set";
3319
push @display_lines, create_table( \@cols, $meta, \@rows);
3321
$clear_screen_sub->();
3323
draw_screen( \@display_lines );
3329
my @cxns = get_connections();
3330
get_innodb_status(\@cxns);
3335
lock_waits => \@lock_waits,
3336
wait_array => \@wait_array,
3339
my @visible = get_visible_tables();
3340
my %wanted = map { $_ => 1 } @visible;
3342
# Get info on lock waits and OS wait array
3343
foreach my $cxn ( @cxns ) {
3344
my $set = $vars{$cxn}->{$clock} or next;
3346
if ( $wanted{lock_waits} && @{$set->{IB_tx_transactions}} ) {
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');
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;
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++;;
3378
draw_screen(\@display_lines);
3381
# display_explain {{{3
3382
sub display_explain {
3384
my $cxn = $info->{cxn};
3385
my $db = $info->{db};
3386
my $meta = $dbhs{$cxn};
3388
my ( $mods, $query ) = rewrite_for_explain($info->{query});
3395
= ( $meta->{ver_major} >= 5 && $meta->{ver_minor} >= 1 && $meta->{ver_rev} >= 5 )
3398
$query = "EXPLAIN $part\n" . $query;
3402
do_query($cxn, "use $db");
3404
my $sth = do_query($cxn, $query);
3407
while ( $res = $sth->fetchrow_hashref() ) {
3408
map { $res->{$_} ||= '' } ( 'partitions', keys %$res);
3409
my @this_table = create_caption("Sub-Part $res->{id}",
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 });
3418
if ( $EVAL_ERROR ) {
3419
push @display_lines, '', "The query could not be explained: $EVAL_ERROR";
3424
push @display_lines, '', 'The query could not be explained.';
3428
push @display_lines, '', '[This query has been re-written to be explainable]';
3431
unshift @display_lines, no_ctrl_char($query);
3432
draw_screen(\@display_lines, { raw => 1 } );
3435
# rewrite_for_explain {{{3
3436
# Some replace/create/insert...select can be rewritten easily.
3437
sub rewrite_for_explain {
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 );
3450
# show_optimized_query {{{3
3451
sub show_optimized_query {
3453
my $cxn = $info->{cxn};
3454
my $db = $info->{db};
3455
my $meta = $dbhs{$cxn};
3459
my ( $mods, $query ) = rewrite_for_explain($info->{query});
3462
push @display_lines, '[This query has been re-written to be explainable]';
3466
push @display_lines, no_ctrl_char($info->{query});
3470
do_query($cxn, "use $db");
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;
3477
foreach my $result ( @$res ) {
3478
push @display_lines, 'Note:', no_ctrl_char($result->[2]);
3482
push @display_lines, '', 'The query optimization could not be generated.';
3486
if ( $EVAL_ERROR ) {
3487
push @display_lines, '', "The optimization could not be generated: $EVAL_ERROR";
3492
push @display_lines, '', 'The query optimization could not be generated.';
3495
draw_screen(\@display_lines, { raw => 1 } );
3500
my $mode = $config{mode}->{val};
3502
# Get globally mapped keys, then overwrite them with mode-specific ones.
3504
my $key = $action_for{$_}->{key} || $_;
3505
$key => $action_for{$_}->{label}
3507
foreach my $key ( keys %{$modes{$mode}->{action_for}} ) {
3508
$keys{$key} = $modes{$mode}->{action_for}->{$key}->{label};
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 },
3519
push @display_lines, '', 'Any other key refreshes the display.', '';
3520
$clear_screen_sub->();
3521
draw_screen(\@display_lines, { show_all => 1 } );
3525
# show_full_query {{{3
3526
sub show_full_query {
3528
my @display_lines = no_ctrl_char($info->{query});
3529
draw_screen(\@display_lines, { raw => 1 });
3532
# Formatting functions {{{2
3539
# Slice and stack, baby.
3541
while ($i < @vals) {
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);
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
3557
my ( $cols, $labels, $data, $user_prefs ) = @_;
3560
if ( @$cols && %$data ) {
3568
if ( $user_prefs ) {
3569
map { $p->{$_} = $user_prefs->{$_} } keys %$user_prefs;
3573
map { $data->{$_} = '' unless defined $data->{$_} } @$cols;
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});
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
3592
# can stack ccc next to the bbb.
3593
# NOTE: this DOES modify its arguments, even though it returns a new array.
3595
my ( $left, $right, $user_prefs ) = @_;
3602
if ( $user_prefs ) {
3603
map { $p->{$_} = $user_prefs->{$_} } keys %$user_prefs;
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);
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 ) {
3619
if ( $i < scalar(@$left) ) {
3620
# Find the max width of the section of the LHS against which the RHS
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]);
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]);
3631
while ( $i - $offset < @$right ) {
3632
# There is more RHS to push on the end of the array
3634
sprintf("%${max_width}s$pad%${max_r}s", ' ', $right->[$i - $offset]);
3637
push @result, @$left;
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;
3649
# create_caption {{{3
3650
sub create_caption {
3651
my ( $caption, @rows ) = @_;
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));
3659
my $cap_len = length($caption);
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);
3666
("_" x $left) . " $caption " . ("_" x ($width - $left - $cap_len - 2));
3669
# The caption is too wide to add underscores on each side.
3672
# Color is supported, so we can use terminal underlining.
3673
if ( $have_color ) {
3674
my $left = int(($width - $cap_len) / 2);
3676
(" " x $left) . $caption . (" " x ($width - $left - $cap_len)),
3681
# Color is not supported, so we have to add a line underneath to separate the
3682
# caption from whatever it's captioning.
3684
my $left = int(($width - $cap_len) / 2);
3685
unshift @rows, ('-' x $width);
3686
unshift @rows, (" " x $left) . $caption . (" " x ($width - $left - $cap_len));
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 ) {
3694
? [ sprintf('%-' . $cap_len . 's', $_->[0]), $_->[1] ]
3695
: sprintf('%-' . $cap_len . 's', $_);
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.
3713
# Column One Column Two
3714
# ---------- ----------
3718
my ( $cols, $info, $data, $prefs ) = @_;
3720
$prefs->{no_hdr} ||= ($opts{n} && $clock != 1);
3724
if ( @$cols && %$info ) {
3726
# Fix undef values, collapse whitespace.
3727
foreach my $row ( @$data ) {
3728
map { $row->{$_} = collapse_ws($row->{$_}) } @$cols;
3731
my $col_sep = $opts{n} ? "\t" : ' ';
3733
# Find each column's max width.
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} );
3742
if ( $info->{$col_name}->{minw} ) {
3743
$max_width = max( $max_width, $info->{$col_name}->{minw} );
3745
$col_name => $max_width;
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} ];
3757
elsif ( !$opts{n} ) {
3758
push @rows, join( $col_sep, map { "-" x $width_for{$_} } @$cols );
3764
foreach my $item ( @$data ) {
3765
push @rows, join($col_sep, map { $item->{$_} } @$cols );
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} ];
3787
# Unifies all the work of filtering, sorting etc. Alters the input.
3789
my ( $rows, $tbl ) = @_;
3790
my $meta = $tbl_meta{$tbl} or die "No such table $tbl in tbl_meta";
3793
foreach my $filter ( @{$meta->{filters}} ) {
3795
@$rows = grep { $filters{$filter}->{func}->($_) } @$rows;
3800
if ( @$rows && $meta->{sort_func} ) {
3801
if ( $meta->{sort_dir} > 0 ) {
3802
@$rows = $meta->{sort_func}->( @$rows );
3805
@$rows = reverse $meta->{sort_func}->( @$rows );
3809
# Stop altering arguments now.
3812
# Colorize. Adds a _color column to rows.
3813
if ( @rows && $meta->{color_func} ) {
3815
foreach my $row ( @rows ) {
3816
$row->{_color} = $meta->{color_func}->($row);
3819
if ( $EVAL_ERROR ) {
3824
# Apply_transformations.
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;
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)
3846
my $meta = $tbl_meta{$tbl};
3847
my %labels = map { $_ => $meta->{cols}->{$_}->{hdr} } @{$meta->{visible}};
3853
# From perlfaq5: add commas.
3856
$num =~ s/(^[-+]?\d+?(?=(?>(?:\d{3})+)(?!\d))|\G\d{3}(?=\d))/$1,/g;
3860
# set_precision {{{3
3861
# Trim to desired precision.
3863
my ( $num, $precision ) = @_;
3864
sprintf("%.${precision}f", $num);
3868
# Convert to percent
3871
my $digits = $config{num_digits}->{val};
3872
sprintf("%.${digits}f", $num * 100);
3877
my ( $num, $opts ) = @_;
3879
return $num if !defined($num) || $opts{n} || $num =~ m/[^\d\.-]/;
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};
3889
while ( $num >= 1_024 ) {
3894
$num =~ m/\./ || $n || $force
3895
? "%.${num_digits}f%s"
3897
$num, ($pad,'k','M','G', 'T')[$n]);
3901
# Utility functions {{{2
3905
return grep { !$seen{$_}++ } @_;
3908
# make_color_func {{{3
3909
sub make_color_func {
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}) . "/"
3918
"( defined \$set->{$spec->{col}} && \$set->{$spec->{col}} $spec->{op} $val ) { return '$spec->{color}'; }";
3920
return undef unless @criteria;
3921
my $sub = eval 'sub { my ( $set ) = @_; if ' . join(" elsif ", @criteria) . '}';
3926
# make_sort_func {{{3
3927
# Accepts a list of sort columns, like "+cxn -time" and returns a subroutine that will
3929
sub make_sort_func {
3932
foreach my $col ( split(/\s+/, $tbl->{sort_cols} ) ) {
3934
my ( $dir, $name ) = $col =~ m/([+-])?(\w+)$/;
3935
next unless $name && $tbl->{cols}->{$name};
3937
my $op = $tbl->{cols}->{$name}->{num} ? "<=>" : "cmp";
3938
my $df = $tbl->{cols}->{$name}->{num} ? "0" : "''";
3941
? "(\$a->{$name} || $df) $op (\$b->{$name} || $df)"
3942
: "(\$b->{$name} || $df) $op (\$a->{$name} || $df)";
3944
return sub { return @_ } unless @criteria;
3945
my $sub = eval 'sub { sort {' . join("||", @criteria) . '} @_; }';
3951
# Shortens text to specified length.
3953
my ( $text, $len ) = @_;
3954
if ( length($text) <= $len ) {
3957
return substr($text, 0, $len);
3961
# Takes out the middle of text to shorten it.
3963
my ( $text, $len ) = @_;
3964
return $text if length($text) <= $len;
3965
my $max = length($text) - $len;
3968
# Try to remove a single "word" from somewhere in the center
3969
if ( $text =~ s/_[^_]{$min,$max}_/_/ ) {
3973
# Prefer removing the end of a "word"
3974
if ( $text =~ s/([^_]+)[^_]{$max}_/$1_/ ) {
3978
$text = substr($text, 0, int($len/2))
3980
. substr($text, int($len/2) + $max + 1);
3985
# Removes vowels and compacts repeated letters to shorten text.
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;
3996
# Collapses all whitespace to a single space.
3999
return '' unless defined $text;
4004
# Strips out non-printable characters within fields, which freak terminals out.
4007
return '' unless defined $text;
4008
my $charset = $config{charset}->{val};
4009
if ( $charset && $charset eq 'unicode' ) {
4011
("(?:(?!(?<!\\)").)*" # Double-quoted string
4012
|'(?:(?!(?<!\\)').)*') # Or single-quoted string
4013
/$1 =~ m#\p{IsC}# ? "[BINARY]" : $1/egx;
4015
elsif ( $charset && $charset eq 'none' ) {
4017
("(?:(?!(?<!\\)").)*"
4018
|'(?:(?!(?<!\\)').)*')
4021
else { # The default is 'ascii'
4023
("(?:(?!(?<!\\)").)*"
4024
|'(?:(?!(?<!\\)').)*')
4025
/$1 =~ m#[^\040-\176]# ? "[BINARY]" : $1/egx;
4031
# Wraps text at word boundaries so it fits the screen.
4033
my ( $text, $width) = @_;
4034
$width ||= $this_term_size[0];
4035
$text =~ s/(.{0,$width})(?:\s+|$)/$1\n/g;
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().
4046
my ( $display_lines, $prefs ) = @_;
4047
if ( !$opts{n} && $config{show_statusbar}->{val} ) {
4048
unshift @$display_lines, create_statusbar();
4050
$clear_screen_sub->() unless $modes{$config{mode}->{val}}->{no_clear_screen};
4051
if ( $opts{n} || $prefs->{raw} ) {
4055
? colored($_->[0], $_->[1])
4058
grep { !$opts{n} || $_ } # When non-interactive, suppress empty lines
4064
elsif ( $prefs->{show_all} ) {
4068
? colored(substr($_->[0], 0, $this_term_size[0]), $_->[1])
4069
: substr($_, 0, $this_term_size[0]);
4074
my $max_lines = min(scalar(@$display_lines), $this_term_size[1]);
4078
? colored(substr($_->[0], 0, $this_term_size[0]), $_->[1])
4079
: substr($_, 0, $this_term_size[0]);
4080
} @$display_lines[0..$max_lines - 1]);
4086
my ( $secs, $fmt ) = @_;
4088
return '00:00' unless $secs;
4090
# Decide what format to use, if not given
4091
$fmt ||= $secs >= 86_400 ? 'd'
4092
: $secs >= 3_600 ? 'h'
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),
4102
: $fmt eq 'h' ? sprintf(
4104
int(($secs % 86_400) / 3_600),
4105
int(($secs % 3_600) / 60),
4109
int(($secs % 3_600) / 60),
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
4118
return 0 unless $num;
4119
my ( $high, $low ) = $num =~ m/^(\d+) (\d+)$/;
4120
return $low unless $high;
4121
return $low + ( $high * $MAX_ULONG );
4124
# create_statusbar {{{3
4125
sub create_statusbar {
4126
my $mode = $config{mode}->{val};
4127
my @cxns = sort { $a cmp $b } get_connections();
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;
4135
# The thingie in top-right that says what we're monitoring.
4139
$cxn = $dbhs{$cxns[0]}->{mysql_version};
4142
if ( $modes{$mode}->{server_group} ) {
4143
$cxn = "Servers: " . $modes{$mode}->{server_group};
4144
my $err_count = grep { $dbhs{$_}->{err_count} } @cxns;
4146
$cxn .= "(" . ( scalar(@cxns) - $err_count ) . "/" . scalar(@cxns) . ")";
4150
$cxn = join(' ', map { ($dbhs{$_}->{err_count} ? '!' : '') . $_ } @cxns);
4155
get_status_info(@cxns);
4156
my $vars = $vars{$cxns[0]}->{$clock};
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);
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} ) {
4180
"%-${mode_width}s %${remaining_width}s",
4182
$ibinfo . join(', ',
4185
($vars->{Threads_connected} || 0) . " thd",
4191
"%-${mode_width}s %${remaining_width}s",
4196
return [ $result, 'bold reverse' ];
4199
# Database connections {{{3
4203
if ( defined $name ) {
4204
$name =~ s/[\s:;]//g;
4208
print word_wrap("Choose a name for the connection. It cannot contain "
4209
. "whitespace, colons or semicolons."), "\n\n";
4211
$name = prompt("Enter a name");
4212
$name =~ s/[\s:;]//g;
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");
4224
my $user = $ENV{USERNAME} || $ENV{USER} || getlogin() || getpwuid($REAL_USER_ID) || undef;
4226
$clear_screen_sub->();
4227
$user = prompt("Enter a username for $name", undef, $user);
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');
4234
$connections{$name} = {
4237
dl_table => $dl_table,
4241
sub add_new_server_group {
4244
if ( defined $name ) {
4245
$name =~ s/[\s:;]//g;
4249
print word_wrap("Choose a name for the group. It cannot contain "
4250
. "whitespace, colons or semicolons."), "\n\n";
4252
$name = prompt("Enter a name");
4253
$name =~ s/[\s:;]//g;
4259
$clear_screen_sub->();
4260
@cxns = select_cxn("Choose servers for $name", keys %connections);
4263
$server_groups{$name} = \@cxns;
4268
while ( !exists($var_sets{$config{$name}->{val}}) ) {
4269
$name = choose_var_set($name);
4271
return $var_sets{$config{$name}->{val}};
4274
sub add_new_var_set {
4277
if ( defined $name ) {
4283
$name = prompt("Enter a name");
4290
$clear_screen_sub->();
4291
$variables = prompt("Enter variables for $name", undef );
4292
} until ( $variables );
4294
$var_sets{$name} = [ unique(grep { $_ } split(/\s+/, $variables)) ];
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};
4302
return unless @grps;
4305
# Find the current group's position.
4307
while ( $curr ne $grps[$pos] ) {
4310
$modes{$mode}->{server_group} = $grps[ ($pos + 1) % @grps ];
4313
$modes{$mode}->{server_group} = $grps[0];
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] : ();
4326
return unique(@connections);
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}};
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}});
4351
my $choices = prompt_list("Choose connections or a group for $mode mode",
4352
undef, sub { return keys %$meta }, $meta);
4354
my @choices = unique(grep { $_ } split(/\s+/, $choices));
4356
if ( $choices[0] =~ s/^#// && exists $server_groups{$choices[0]} ) {
4357
$modes{$mode}->{server_group} = $choices[0];
4360
$modes{$mode}->{connections} = [ grep { exists $connections{$_} } @choices ];
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.
4371
my ( $cxn, $stmt_name, @args ) = @_;
4373
# Test if the cxn should not even be tried
4375
if $dbhs{$cxn} && $dbhs{$cxn}->{err_count} && $dbhs{$cxn}->{wake_up} > $clock;
4381
while ( $retries-- >= 0 && !$success ) {
4384
my $dbh = connect_to_db($cxn);
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));
4392
$sth = $dbhs{$cxn}->{stmts}->{$stmt_name};
4393
$sth->execute(@args);
4396
if ( $EVAL_ERROR ) {
4397
my $errs = join('|',
4398
'Access denied for user',
4399
'Unknown MySQL server host',
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',
4407
if ( $EVAL_ERROR =~ m/$errs/ ) {
4408
handle_cxn_error($cxn, $EVAL_ERROR);
4413
if ( $retries < 0 ) {
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}++;
4430
# Strip garbage from the error text if possible.
4432
if ( $err =~ m/failed: (.*?) at \S*innotop line/ ) {
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};
4446
# Accepts a DB connection name and a (string) query. Returns a $sth that's been
4449
my ( $cxn, $query ) = @_;
4451
# Test if the cxn should not even be tried
4453
if $dbhs{$cxn} && $dbhs{$cxn}->{err_count} && $dbhs{$cxn}->{wake_up} > $clock;
4459
while ( $retries-- >= 0 && !$success ) {
4462
my $dbh = connect_to_db($cxn);
4464
$sth = $dbh->prepare($query);
4468
if ( $EVAL_ERROR ) {
4469
my $errs = join('|',
4470
'Access denied for user',
4471
'Unknown MySQL server host',
4473
'Can\'t connect to local MySQL server through socket',
4474
'Can\'t connect to MySQL server on',
4475
'MySQL server has gone away',
4477
if ( $EVAL_ERROR =~ m/$errs/ ) {
4478
handle_cxn_error($cxn, $EVAL_ERROR);
4483
if ( $retries < 0 ) {
4496
stmts => {}, # bucket for prepared statements.
4503
my $href = $dbhs{$cxn};
4505
if ( !$href->{dbh} || !$href->{dbh}->ping ) {
4506
my $dbh = get_new_db_connection($cxn);
4507
$href->{dbh} = $dbh;
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+)/;
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;
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");
4524
return $href->{dbh};
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}) ) {
4534
$dsn->{savepass} = prompt("Save password in plain text in the config file? 1 or 0", undef, 1);
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
4544
sub get_cxn_errors {
4546
return () unless $config{show_cxn_errors_in_tbl}->{val};
4548
map { [ $_ . ': ' . $dbhs{$_}->{last_err}, 'red' ] }
4549
grep { $dbhs{$_}->{err_count} }
4553
# Setup and tear-down functions {{{2
4554
# compile_filter {{{3
4555
sub compile_filter {
4558
eval "\$sub = sub { my \$set = shift; $text }";
4559
if ( $EVAL_ERROR ) {
4560
$sub = sub { return $EVAL_ERROR };
4563
return ( $sub, $err );
4567
# TODO: strip off "at (eval..." from error.
4569
my ( $expr, $simple ) = @_;
4571
$expr =~ s/([A-Za-z]\w+)/\$set->{$1}/g;
4574
eval "\$sub = sub { my \$set = shift; $expr }";
4575
if ( $EVAL_ERROR ) {
4576
$sub = sub { return $EVAL_ERROR };
4579
return ( $sub, $err );
4583
# This is a subroutine because it's called from a key to quit the program.
4586
ReadMode('normal') unless $opts{n};
4594
if ($config{debugfile}->{val} && $config{debug}->{val}) {
4596
open my $file, '>>', $config{debugfile}->{val};
4598
print $file "Current variables:\n" . Dumper(\%vars);
4609
my $filename = $opts{c} || "$homepath/.innotop";
4610
if ( -f $filename ) {
4611
open my $file, "<", $filename or die("Can't open $filename: $OS_ERROR");
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/ ) {
4617
if ( my ($maj, $min, $rev) = $line =~ m/^version=(\d+)\.(\d+)(?:\.(\d+))?$/ ) {
4619
my $cfg_ver = sprintf('%03d-%03d-%03d', $maj, $min, $rev);
4620
( $maj, $min, $rev ) = $VERSION =~ m/^(\d+)\.(\d+)(?:\.(\d+))?$/;
4622
my $innotop_ver = sprintf('%03d-%03d-%03d', $maj, $min, $rev);
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.");
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.";
4648
while ( my $line = <$file> ) {
4650
next unless $line =~ m/^\[([a-z_]+)\]$/;
4651
if ( exists $config_file_sections{$1} ) {
4652
$config_file_sections{$1}->{reader}->($file);
4655
warn "Unknown config file section '$1'";
4658
close $file or die("Can't close $filename: $OS_ERROR");
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};
4671
? $col_def->{src}->{func} # Already compiled
4672
: compile_expr($col_def->{src}, 1);
4673
$col_def->{func} = $sub;
4678
# load_config_active_server_groups {{{3
4679
sub load_config_active_server_groups {
4681
while ( my $line = <$file> ) {
4683
next if $line =~ m/^#/;
4684
last if $line =~ m/^\[/;
4686
my ( $mode, $group ) = $line =~ m/^(.*?)=(.*)$/;
4687
next unless $mode && $group;
4688
$modes{$mode}->{server_group} = $group;
4692
# save_config_active_server_groups {{{3
4693
sub save_config_active_server_groups {
4695
foreach my $mode ( keys %modes ) {
4696
print $file "$mode=$modes{$mode}->{server_group}\n";
4700
# load_config_server_groups {{{3
4701
sub load_config_server_groups {
4703
while ( my $line = <$file> ) {
4705
next if $line =~ m/^#/;
4706
last if $line =~ m/^\[/;
4708
my ( $name, $rest ) = $line =~ m/^(.*?)=(.*)$/;
4709
next unless $name && $rest;
4710
my @vars = unique(grep { $_ && exists $connections{$_} } split(/\s+/, $rest));
4712
$server_groups{$name} = \@vars;
4716
# save_config_server_groups {{{3
4717
sub save_config_server_groups {
4719
foreach my $set ( keys %server_groups ) {
4720
print $file "$set=", join(' ', @{$server_groups{$set}}), "\n";
4724
# load_config_varsets {{{3
4725
sub load_config_varsets {
4727
while ( my $line = <$file> ) {
4729
next if $line =~ m/^#/;
4730
last if $line =~ m/^\[/;
4732
my ( $name, $rest ) = $line =~ m/^(.*?)=(.*)$/;
4733
next unless $name && $rest;
4734
my @vars = unique(map { $_ } split(/\s+/, $rest));
4736
$var_sets{$name} = \@vars;
4740
# save_config_varsets {{{3
4741
sub save_config_varsets {
4743
foreach my $varset ( keys %var_sets ) {
4744
print $file "$varset=", join(' ', @{$var_sets{$varset}}), "\n";
4748
# load_config_filters {{{3
4749
sub load_config_filters {
4751
while ( my $line = <$file> ) {
4753
next if $line =~ m/^#/;
4754
last if $line =~ m/^\[/;
4756
my ( $key, $rest ) = $line =~ m/^(.+?)=(.*)$/;
4757
next unless $key && $rest;
4759
my %parts = $rest =~ m/(\w+)='((?:(?!(?<!\\)').)*)'/g; # Properties are single-quoted
4760
next unless $parts{text} && $parts{tbls};
4762
foreach my $prop ( keys %parts ) {
4763
# Un-escape escaping
4764
$parts{$prop} =~ s/\\\\/\\/g;
4765
$parts{$prop} =~ s/\\'/'/g;
4768
my ( $sub, $err ) = compile_filter($parts{text});
4769
my @tbls = unique(split(/\s+/, $parts{tbls}));
4770
@tbls = grep { exists $tbl_meta{$_} } @tbls;
4773
text => $parts{text},
4776
note => 'User-defined filter',
4782
# save_config_filters {{{3
4783
sub save_config_filters {
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";
4794
# load_config_visible_tables {{{3
4795
sub load_config_visible_tables {
4797
while ( my $line = <$file> ) {
4799
next if $line =~ m/^#/;
4800
last if $line =~ m/^\[/;
4802
my ( $mode, $rest ) = $line =~ m/^(.*?)=(.*)$/;
4804
if ( exists $modes{$mode} ) {
4805
$modes{$mode}->{visible_tables} =
4806
[ unique(grep { $_ && exists $tbl_meta{$_} } split(/\s+/, $rest)) ];
4811
# save_config_visible_tables {{{3
4812
sub save_config_visible_tables {
4814
foreach my $mode ( keys %modes ) {
4815
my $tables = $modes{$mode}->{visible_tables};
4816
print $file "$mode=", join(' ', @$tables), "\n";
4820
# load_config_sort_cols {{{3
4821
sub load_config_sort_cols {
4823
while ( my $line = <$file> ) {
4825
next if $line =~ m/^#/;
4826
last if $line =~ m/^\[/;
4828
my ( $key , $rest ) = $line =~ m/^(.*?)=(.*)$/;
4830
$tbl_meta{$key}->{sort_cols} = $rest;
4831
$tbl_meta{$key}->{sort_func} = make_sort_func($tbl_meta{$key});
4835
# save_config_sort_cols {{{3
4836
sub save_config_sort_cols {
4838
foreach my $tbl ( keys %tbl_meta ) {
4839
my $col = $tbl_meta{$tbl}->{sort_cols};
4840
print $file "$tbl=$col\n";
4844
# load_config_active_filters {{{3
4845
sub load_config_active_filters {
4847
while ( my $line = <$file> ) {
4849
next if $line =~ m/^#/;
4850
last if $line =~ m/^\[/;
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 ];
4860
# save_config_active_filters {{{3
4861
sub save_config_active_filters {
4863
foreach my $tbl ( keys %tbl_meta ) {
4864
my $aref = $tbl_meta{$tbl}->{filters};
4865
print $file "$tbl=", join(' ', @$aref), "\n";
4869
# load_config_active_columns {{{3
4870
sub load_config_active_columns {
4872
while ( my $line = <$file> ) {
4874
next if $line =~ m/^#/;
4875
last if $line =~ m/^\[/;
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 ];
4884
# save_config_active_columns {{{3
4885
sub save_config_active_columns {
4887
foreach my $tbl ( keys %tbl_meta ) {
4888
my $aref = $tbl_meta{$tbl}->{visible};
4889
print $file "$tbl=", join(' ', @$aref), "\n";
4893
# load_config_expressions {{{3
4894
sub load_config_expressions {
4896
while ( my $line = <$file> ) {
4898
next if $line =~ m/^#/;
4899
last if $line =~ m/^\[/;
4901
my ( $key, $expr ) = $line =~ m/^(.+?)=(.*)$/;
4902
next unless $key && $expr;
4904
my ( $sub, $err ) = compile_expr($expr);
4914
# save_config_expressions {{{3
4915
sub save_config_expressions {
4917
foreach my $key ( keys %exprs ) {
4918
next unless $exprs{$key}->{user};
4919
print $file "$key=$exprs{$key}->{text}\n";
4923
# save_config_tbl_meta {{{3
4924
sub save_config_tbl_meta {
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(
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
4938
grep { $_ ne 'func' }
4945
# save_config_config {{{3
4946
sub save_config_config {
4948
foreach my $key ( sort keys %config ) {
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' ) {
4957
. join( " ", @$val ) . "\n"
4958
or die "Cannot print to file: $OS_ERROR";
4960
elsif ( ref( $val ) eq 'HASH' ) {
4963
map { "$_:$val->{$_}" } keys %$val
4967
print $file "$key=$val\n";
4971
if ( $EVAL_ERROR ) { print "$EVAL_ERROR in $key"; };
4976
# load_config_config {{{3
4977
sub load_config_config {
4980
# Look in the command-line parameters for things stored in the same slot.
4982
map { $opt_spec{$_}->{config} => $opts{$_} }
4983
grep { exists $opt_spec{$_}->{config} && exists $opts{$_} }
4986
while ( my $line = <$file> ) {
4988
next if $line =~ m/^#/;
4989
last if $line =~ m/^\[/;
4991
my ( $name, $val ) = $line =~ m/^(.+?)=(.*)$/;
4992
next unless defined $name && defined $val;
4994
# Values might already have been set at the command line.
4995
$val = defined($cmdline{$name}) ? $cmdline{$name} : $val;
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;
5007
# load_config_tbl_meta {{{3
5008
sub load_config_tbl_meta {
5011
while ( my $line = <$file> ) {
5013
next if $line =~ m/^#/;
5014
last if $line =~ m/^\[/;
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/^(.*?)=(.*)$/;
5021
my %parts = $rest =~ m/(\w+)='((?:(?!(?<!\\)').)*)'/g; # Properties are single-quoted
5023
# Each section read from the config file has one extra property: which table it
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";
5028
# The section is user-defined by definition (if that makes sense).
5031
# The column may already exist in the table, in which case this is just a
5033
$meta->{cols}->{$col} ||= {};
5035
foreach my $prop ( keys %col_props ) {
5036
if ( !defined($parts{$prop}) ) {
5037
die "Undefined property $prop for column $col in table $tbl";
5040
# Un-escape escaping
5041
$parts{$prop} =~ s/\\\\/\\/g;
5042
$parts{$prop} =~ s/\\'/'/g;
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})) ];
5050
$meta->{cols}->{$col}->{$prop} = [ split(',', $parts{$prop}) ];
5054
$meta->{cols}->{$col}->{$prop} = $parts{$prop};
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";
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";
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";
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");
5087
# load_config_connections {{{3
5088
sub load_config_connections {
5090
while ( my $line = <$file> ) {
5092
next if $line =~ m/^#/;
5093
last if $line =~ m/^\[/;
5095
my ( $key , $rest ) = $line =~ m/^(.*?)=(.*)$/;
5097
my %parts = $rest =~ m/(\S+?)=(\S*)/g;
5098
my %conn = map { $_ => $parts{$_} || '' } @conn_parts;
5099
$connections{$key} = \%conn;
5103
# save_config_connections {{{3
5104
sub save_config_connections {
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";
5113
sub load_config_colors {
5117
while ( my $line = <$file> ) {
5119
next if $line =~ m/^#/;
5120
last if $line =~ m/^\[/;
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;
5133
# Finally! Enough validation...
5134
$rule_set_for{$tbl} ||= [];
5135
push @{$rule_set_for{$tbl}}, \%parts;
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});
5144
# save_config_colors {{{3
5145
sub save_config_colors {
5147
foreach my $tbl ( keys %tbl_meta ) {
5148
my $meta = $tbl_meta{$tbl};
5149
foreach my $rule ( @{$meta->{colors}} ) {
5150
print $file "$tbl=", join(
5153
my $val = $rule->{$_};
5154
$val =~ s/([\\'])/\\$1/g; # Escape backslashes and single quotes
5155
"$_='$val'"; # Enclose in single quotes
5157
qw(col op arg color)
5163
# load_config_active_connections {{{3
5164
sub load_config_active_connections {
5166
while ( my $line = <$file> ) {
5168
next if $line =~ m/^#/;
5169
last if $line =~ m/^\[/;
5171
my ( $key , $rest ) = $line =~ m/^(.*?)=(.*)$/;
5173
my @parts = split(/ /, $rest);
5174
$modes{$key}->{connections} = [ @parts ] if exists $modes{$key};
5178
# save_config_active_connections {{{3
5179
sub save_config_active_connections {
5181
foreach my $mode ( keys %modes ) {
5182
my @connections = get_connections($mode);
5183
print $file "$mode=", join(' ', @connections), "\n";
5187
# load_config_mvs {{{3
5188
sub load_config_mvs {
5190
while ( my $line = <$file> ) {
5192
next if $line =~ m/^#/;
5193
last if $line =~ m/^\[/;
5195
my ( $key , $val ) = $line =~ m/^(.*?)=(.*)$/;
5201
# save_config_mvs {{{3
5202
sub save_config_mvs {
5204
foreach my $key ( keys %mvs ) {
5205
print $file "$key=$mvs{$key}\n";
5209
# edit_configuration {{{3
5210
sub edit_configuration {
5212
while ( $key ne 'q' ) {
5213
$clear_screen_sub->();
5214
my @display_lines = '';
5216
if ( $key && $cfg_editor_action{$key} ) {
5217
$cfg_editor_action{$key}->{func}->();
5221
push @display_lines, create_caption('What configuration do you want to edit?',
5223
[ sort keys %cfg_editor_action ],
5224
{ map { $_ => $_ } keys %cfg_editor_action },
5225
{ map { $_ => $cfg_editor_action{$_}->{note} } keys %cfg_editor_action },
5228
draw_screen(\@display_lines);
5233
# edit_configuration_variables {{{3
5234
sub edit_configuration_variables {
5235
$clear_screen_sub->();
5236
my $mode = $config{mode}->{val};
5239
= map { $_ => $config{$_}->{note} || '' }
5240
# Only config values that are marked as applying to this mode.
5243
$config{$key}->{conf} &&
5244
( $config{$key}->{conf} eq 'ALL'
5245
|| grep { $mode eq $_ } @{$config{$key}->{conf}} )
5248
my $key = prompt_list(
5249
"Enter the name of the variable you wish to configure",
5251
sub{ return keys %config_choices },
5254
if ( exists($config_choices{$key}) ) {
5255
get_config_interactive($key);
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.");
5267
"Enter expression name",
5269
sub { return keys %exprs },
5270
{ map { $_ => trunc(collapse_ws($exprs{$_}->{text}), 30) } keys %exprs },
5273
if ( !exists $exprs{$exp} ) {
5274
my ( $err, $sub, $body );
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.");
5281
print "There's an error in your expression: $err\n\n";
5283
$body = prompt("Enter subroutine body");
5284
( $sub, $err ) = compile_expr($body);
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;
5309
# This loop builds a tabular view of the rules.
5313
if ( $key && $key eq '?' ) {
5314
my @display_lines = '';
5315
push @display_lines, create_caption('Editor key mappings',
5317
[ sort keys %color_editor_action ],
5318
{ map { $_ => $_ } keys %color_editor_action },
5319
{ map { $_ => $color_editor_action{$_}->{note} } keys %color_editor_action },
5321
draw_screen(\@display_lines);
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;
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 ? '>' : '';
5340
my @display_lines = create_table(\@cols, $info, $data);
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' ];
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 "
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.');
5359
} while ( $key ne 'q' );
5360
$meta->{color_func} = make_color_func($meta);
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;
5374
my $selected_column;
5376
# This loop builds a tabular view of the tbl_meta's structure, showing each column
5377
# in the entry as a row.
5381
if ( $key && $key eq '?' ) {
5382
my @display_lines = '';
5383
push @display_lines, create_caption('Editor key mappings',
5385
[ sort keys %tbl_editor_action ],
5386
{ map { $_ => $_ } keys %tbl_editor_action },
5387
{ map { $_ => $tbl_editor_action{$_}->{note} } keys %tbl_editor_action },
5389
draw_screen(\@display_lines);
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];
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.
5405
foreach my $row ( @{$meta->{visible}} ) {
5407
@hash{ @cols } = @{$meta->{cols}->{$row}}{@cols};
5408
$hash{src} = '' if ref $hash{src};
5410
$hash{''} = $row eq $selected_column ? '>' : ' ';
5411
push @$data, \%hash;
5413
my @display_lines = create_table(\@cols, $info, $data);
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' ];
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);
5428
} while ( $key ne 'q' );
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",
5440
sub { return @{$modes{$mode}->{tables}} },
5441
{ map { $_ => $tbl_meta{$_}->{hdr} } @{$modes{$mode}->{tables}} }
5443
$modes{$mode}->{visible_tables} =
5444
[ unique(grep { $_ && exists $tbl_meta{$_} } split(/\s+/, $new)) ];
5447
# choose_visible_table {{{3
5448
sub choose_visible_table {
5449
my $mode = $config{mode}->{val};
5450
my @tbls = @{$modes{$mode}->{visible_tables}};
5456
sub { return @tbls },
5457
{ map { $_ => $tbl_meta{$_}->{hdr} } @tbls }
5463
sub choose_sort_cols {
5464
$clear_screen_sub->();
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)',
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});
5477
# create_new_filter {{{3
5478
sub create_new_filter {
5479
my ( $filter, $tbl ) = @_;
5480
$clear_screen_sub->();
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.");
5487
$filter = prompt("Enter filter name");
5488
} while ( !$filter || $filter =~ m/\W/ );
5491
my ( $err, $sub, $body );
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.");
5500
print "There's an error in your filter expression: $err\n\n";
5502
$body = prompt("Enter subroutine body");
5503
( $sub, $err ) = compile_filter($body);
5506
$filters{$filter} = {
5511
note => 'User-defined filter',
5516
# get_config_interactive {{{3
5517
sub get_config_interactive {
5519
$clear_screen_sub->();
5522
print "Enter a new value for '$key' ($config{$key}->{note}).\n";
5524
my $current = ref($config{$key}->{val}) ? join(" ", @{$config{$key}->{val}}) : $config{$key}->{val};
5526
my $new_value = prompt('Enter a value', $config{$key}->{pat}, $current);
5527
$config{$key}->{val} = $new_value;
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}});
5539
$clear_screen_sub->();
5540
$variables = prompt("Enter variables for $name", undef, $variables );
5541
} until ( $variables );
5543
$var_sets{$name} = [ unique(grep { $_ } split(/\s+/, $variables)) ];
5547
sub choose_var_set {
5549
$clear_screen_sub->();
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 });
5557
if ( !exists $var_sets{$new_value} ) {
5558
add_new_var_set($new_value);
5561
$config{$key}->{val} = $new_value if exists $var_sets{$new_value};
5567
my $filename = shift;
5568
open my $file, "<", "$filename" or die "Can't open $filename: $!";
5569
my $file_contents = do { local $/; <$file>; };
5571
return $file_contents;
5576
( my $filename = shift ) =~ s#^.*[/\\]##;
5580
# Online configuration and prompting functions {{{2
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;
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};
5610
sub choose_or_create_server_group {
5611
my ( $group, $prompt ) = @_;
5614
my @available = sort keys %server_groups;
5617
print "You can enter the name of a new group to create it.\n";
5620
"Choose a server group $prompt",
5622
sub { return @available },
5623
{ map { $_ => join(' ', @{$server_groups{$_}}) } @available });
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);
5635
$new = add_new_server_group();
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";
5644
my @available = sort keys %connections;
5645
my $new_cxns = prompt_list(
5646
"Choose connections $prompt",
5648
sub { return @available },
5649
{ map { $_ => $connections{$_}->{dsn} } @available });
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' ) {
5659
return unique(grep { exists $connections{$_} } split(/\s+/, $new_cxns));
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}
5672
# display_license {{{3
5673
sub display_license {
5674
$clear_screen_sub->();
5676
print $innotop_license;
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 {
5687
if ( !$info_gotten{status}++ ) {
5688
foreach my $cxn ( @cxns ) {
5689
$vars{$cxn}->{$clock} ||= {};
5690
my $vars = $vars{$cxn}->{$clock};
5692
my $sth = do_stmt($cxn, 'SHOW_STATUS') or next;
5693
my $res = $sth->fetchall_arrayref();
5694
map { $vars->{$_->[0]} = $_->[1] || 0 } @$res;
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;
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;
5708
# analyze_query {{{3
5709
# Allows the user to show fulltext, explain, show optimized...
5713
e => \&display_explain,
5714
f => \&show_full_query,
5715
o => \&show_optimized_query,
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});
5723
# Find out which connection.
5724
my @ids = sort map { $_->{id} } grep { $_->{cxn} eq $cxn } @current_queries;
5726
my $id = prompt_list('Specify a connection ID to analyze',
5728
sub { return @ids });
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;
5735
$actions{$action}->($info);
5737
$action = pause('Press e to explain, f for full query, o for optimized query');
5738
} while ( exists($actions{$action}) );
5742
# Returns the difference between two sets of variables/status/innodb stuff.
5744
my ( $offset, $cxn ) = @_;
5745
my $vars = $vars{$cxn};
5746
if ( $offset < 0 ) {
5747
return $vars->{$clock};
5749
elsif ( exists $vars{$clock - $offset} && !exists $vars->{$clock - $offset - 1} ) {
5750
return $vars->{$clock - $offset};
5752
my $cur = $vars->{$clock - $offset};
5753
my $pre = $vars->{$clock - $offset - 1};
5755
# Numeric variables get subtracted, non-numeric get passed straight through.
5758
( (defined $cur->{$_} && $cur->{$_} =~ m/$num_regex/)
5759
? $cur->{$_} - ($pre->{$_} || 0)
5765
# extract_values {{{3
5766
sub extract_values {
5767
my ( $set, $tbl ) = @_;
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};
5777
$result->{$key} = $info->{func}->($set)
5779
if ( $EVAL_ERROR ) {
5780
$result->{$key} = $info->{num} ? 0 : '';
5786
# get_full_processlist {{{3
5787
sub get_full_processlist {
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;
5798
# get_open_tables {{{3
5799
sub get_open_tables {
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;
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;
5816
# Determine which sections need to be parsed
5817
my %sections_required =
5818
map { $tbl_meta{$_}->{innodb} => 1 }
5820
get_visible_tables();
5822
# Add in any other sections the caller requested.
5823
foreach my $sec ( @$addl_sections ) {
5824
$sections_required{$sec} = 1;
5827
foreach my $cxn ( @$cxns ) {
5828
my $stmt = do_stmt($cxn, 'INNODB_STATUS') or next;
5829
my $innodb_status_text = $stmt->fetchrow_hashref()->{Status};
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
5840
if ( !$innodb_status{IB_got_all} && $config{auto_wipe_dl}->{val} ) {
5841
clear_deadlock($cxn);
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};
5854
# clear_deadlock {{{3
5855
sub clear_deadlock {
5857
return if $clearing_deadlocks++;
5858
my $tbl = $connections{$cxn}->{dl_table};
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
5869
# Fork off two children to deadlock against each other.
5871
foreach my $child ( 0..1 ) {
5873
if ( defined($pid) && $pid == 0 ) { # I am a child
5874
deadlock_thread( $child, $tbl, $cxn );
5876
elsif ( !defined($pid) ) {
5877
die("Unable to fork for clearing deadlocks!\n");
5879
# I already exited if I'm a child, so I'm the parent.
5880
$children{$child} = $pid;
5883
# Wait for the children to exit.
5884
foreach my $child ( keys %children ) {
5885
my $pid = waitpid($children{$child}, 0);
5889
do_query($cxn, "drop table $tbl");
5891
if ( $EVAL_ERROR ) {
5895
$clearing_deadlocks = 0;
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 {
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;
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};
5920
# Documentation {{{1
5921
# ############################################################################
5922
# I put this last as per the Dog book.
5923
# ############################################################################
5928
innotop - A MySQL and InnoDB monitor program.
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
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
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.
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.
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
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.
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.
5972
Remember, press '?' to see what commands are available to you at any time.
5974
=head1 CONFIGURATION
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.
5981
You can specify certain options on the command-line. Run `innotop --help' for
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
5992
=item B: InnoDB Buffers
5994
This mode displays the InnoDB buffer pool, page statistics, insert buffer, and
5995
adaptive hash index.
5997
=item D: InnoDB Deadlocks
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).
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
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.
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.
6019
=item F: InnoDB Foreign Key Errors
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.
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.
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.
6036
Headers are abbreviated to fit on the screen if necessary. This only happens in
6037
interactive operation, not while running unattended.
6039
=item I: InnoDB I/O Info
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.
6044
=item M: Master/Slave Replication Status
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.
6051
=item O: Open Tables
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.
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.
6064
You can toggle an informative header that shows general status information about
6065
your server. There are default sorting, filtering, and colorization rules.
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.
6071
=item R: InnoDB Row Operations and Semaphores
6073
This mode shows InnoDB row operations, row operation miscellaneous, semaphores,
6074
and information from the wait array.
6076
=item S: Load Statistics
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.
6085
=item T: InnoDB Transactions
6087
This mode shows every transaction in the InnoDB monitor's output, in `top'
6088
format. This mode is the reason I wrote innotop.
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.
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.
6098
=item V: Variables & Status
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
6105
=item W: InnoDB Lock Waits
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.
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
6120
=head1 SERVER GROUPS
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.
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.
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.
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.
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.
6143
Once you have defined groups, you can press the TAB key to cycle between them.
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
6151
=head1 SYSTEM REQUIREMENTS
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.
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.
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
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.
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.
6180
$HOMEDIR/.innotop is used to store configuration information.
6182
=head1 COPYRIGHT, LICENSE AND WARRANTY
6184
This program is copyright (c) 2006 Baron Schwartz, baron at xaprb dot com.
6185
Feedback and improvements are welcome.
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.
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
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.
6201
Execute innotop and press '!' to see this information at any time.
6205
Baron Schwartz, baron at xaprb dot com.
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