3
# vim: tw=160:nowrap:expandtab:tabstop=3:shiftwidth=3:softtabstop=3
6
use warnings FATAL => 'all';
7
use sigtrap qw(handler finish untrapped normal-signals);
11
use English qw(-no_match_vars);
12
use File::Basename qw(dirname);
14
use List::Util qw(max min maxstr sum);
17
use Time::HiRes qw(time sleep);
18
use Term::ReadKey qw(ReadMode ReadKey);
20
# Version, license and warranty information. {{{1
21
# ###########################################################################
22
our $VERSION = '1.6.0';
23
our $SVN_REV = sprintf("%d", q$Revision: 383 $ =~ m/(\d+)/g);
24
our $SVN_URL = sprintf("%s", q$URL: https://innotop.svn.sourceforge.net/svnroot/innotop/trunk/innotop $ =~ m$svnroot/innotop/(\S+)$g);
26
my $innotop_license = <<"LICENSE";
28
This is innotop version $VERSION, a MySQL and InnoDB monitor.
30
This program is copyright (c) 2006 Baron Schwartz.
31
Feedback and improvements are welcome.
33
THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
34
WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
35
MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
37
This program is free software; you can redistribute it and/or modify it under
38
the terms of the GNU General Public License as published by the Free Software
39
Foundation, version 2; OR the Perl Artistic License. On UNIX and similar
40
systems, you can issue `man perlgpl' or `man perlartistic' to read these
43
You should have received a copy of the GNU General Public License along with
44
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
45
Place, Suite 330, Boston, MA 02111-1307 USA.
48
# Configuration information and global setup {{{1
49
# ###########################################################################
51
# Really, really, super-global variables.
52
my @config_versions = (
53
"000-000-000", "001-003-000", # config file was one big name-value hash.
54
"001-003-000", "001-004-002", # config file contained non-user-defined stuff.
59
# This defines expected properties and defaults for the column definitions that
60
# eventually end up in tbl_meta.
64
dec => 0, # Whether to align the column on the decimal point
69
tbl => '', # Helps when writing/reading custom columns in config files
73
agg => 'first', # Aggregate function
74
aggonly => 0, # Whether to show only when tbl_meta->{aggregate} is true
77
# Actual DBI connections to MySQL servers.
80
# Command-line parameters {{{2
81
# ###########################################################################
84
{ s => 'help', d => 'Show this help message' },
85
{ s => 'color|C!', d => 'Use terminal coloring (default)', c => 'color' },
86
{ s => 'config|c=s', d => 'Config file to read' },
87
{ s => 'nonint|n', d => 'Non-interactive, output tab-separated fields' },
88
{ s => 'count=i', d => 'Number of updates before exiting' },
89
{ s => 'delay|d=f', d => 'Delay between updates in seconds', c => 'interval' },
90
{ s => 'mode|m=s', d => 'Operating mode to start in', c => 'mode' },
91
{ s => 'inc|i!', d => 'Measure incremental differences', c => 'status_inc' },
92
{ s => 'version', d => 'Output version information and exit' },
95
# This is the container for the command-line options' values to be stored in
96
# after processing. Initial values are defaults.
98
n => !( -t STDIN && -t STDOUT ), # If in/out aren't to terminals, we're interactive
102
foreach my $spec ( @opt_spec ) {
103
my ( $long, $short ) = $spec->{s} =~ m/^(\w+)(?:\|([^!+=]*))?/;
104
$spec->{k} = $short || $long;
107
$spec->{n} = $spec->{s} =~ m/!/;
108
$opts{$spec->{k}} = undef unless defined $opts{$spec->{k}};
109
die "Duplicate option $spec->{k}" if $opt_seen{$spec->{k}}++;
112
Getopt::Long::Configure('no_ignore_case', 'bundling');
113
GetOptions( map { $_->{s} => \$opts{$_->{k}} } @opt_spec) or $opts{help} = 1;
115
if ( $opts{version} ) {
116
print "innotop Ver $VERSION Changeset $SVN_REV from $SVN_URL\n";
120
if ( $opts{'help'} ) {
121
print "Usage: innotop <options> <innodb-status-file>\n\n";
122
my $maxw = max(map { length($_->{l}) + ($_->{n} ? 4 : 0)} @opt_spec);
123
foreach my $spec ( sort { $a->{l} cmp $b->{l} } @opt_spec ) {
124
my $long = $spec->{n} ? "[no]$spec->{l}" : $spec->{l};
125
my $short = $spec->{t} ? "-$spec->{t}" : '';
126
printf(" --%-${maxw}s %-4s %s\n", $long, $short, $spec->{d});
130
innotop is a MySQL and InnoDB transaction/status monitor, like 'top' for
131
MySQL. It displays queries, InnoDB transactions, lock waits, deadlocks,
132
foreign key errors, open tables, replication status, buffer information,
133
row operations, logs, I/O operations, load graph, and more. You can
134
monitor many servers at once with innotop.
140
# Meta-data (table definitions etc) {{{2
141
# ###########################################################################
144
# Convenience so I can copy/paste these in several places...
145
# ###########################################################################
147
Host => q{my $host = host || hostname || ''; ($host) = $host =~ m/^((?:[\d.]+(?=:))|(?:[a-zA-Z]\w+))/; return $host || ''},
148
Port => q{my ($p) = host =~ m/:(.*)$/; return $p || 0},
149
OldVersions => q{dulint_to_int(IB_tx_trx_id_counter) - dulint_to_int(IB_tx_purge_done_for)},
150
MaxTxnTime => q/max(map{ $_->{active_secs} } @{ IB_tx_transactions }) || 0/,
151
NumTxns => q{scalar @{ IB_tx_transactions } },
152
DirtyBufs => q{ $cur->{IB_bp_pages_modified} / ($cur->{IB_bp_buf_pool_size} || 1) },
153
BufPoolFill => q{ $cur->{IB_bp_pages_total} / ($cur->{IB_bp_buf_pool_size} || 1) },
154
ServerLoad => q{ $cur->{Threads_connected}/(Questions||1)/Uptime_hires },
155
TxnTimeRemain => q{ defined undo_log_entries && defined $pre->{undo_log_entries} && undo_log_entries < $pre->{undo_log_entries} ? undo_log_entries / (($pre->{undo_log_entries} - undo_log_entries)/((active_secs-$pre->{active_secs})||1))||1 : 0},
156
SlaveCatchupRate => ' defined $cur->{seconds_behind_master} && defined $pre->{seconds_behind_master} && $cur->{seconds_behind_master} < $pre->{seconds_behind_master} ? ($pre->{seconds_behind_master}-$cur->{seconds_behind_master})/($cur->{Uptime_hires}-$pre->{Uptime_hires}) : 0',
157
QcacheHitRatio => q{(Qcache_hits||0)/(((Com_select||0)+(Qcache_hits||0))||1)},
160
# ###########################################################################
161
# Column definitions {{{3
162
# Defines every column in every table. A named column has the following
164
# * hdr Column header/title
165
# * label Documentation for humans.
166
# * num Whether it's numeric (for sorting).
167
# * just Alignment; generated from num, user-overridable in tbl_meta
168
# * minw, maxw Auto-generated, user-overridable.
169
# Values from this hash are just copied to tbl_meta, which is where everything
170
# else in the program should read from.
171
# ###########################################################################
174
active_secs => { hdr => 'SecsActive', num => 1, label => 'Seconds transaction has been active', },
175
add_pool_alloc => { hdr => 'Add\'l Pool', num => 1, label => 'Additonal pool allocated' },
176
attempted_op => { hdr => 'Action', num => 0, label => 'The action that caused the error' },
177
awe_mem_alloc => { hdr => 'AWE Memory', num => 1, label => '[Windows] AWE memory allocated' },
178
binlog_cache_overflow => { hdr => 'Binlog Cache', num => 1, label => 'Transactions too big for binlog cache that went to disk' },
179
binlog_do_db => { hdr => 'Binlog Do DB', num => 0, label => 'binlog-do-db setting' },
180
binlog_ignore_db => { hdr => 'Binlog Ignore DB', num => 0, label => 'binlog-ignore-db setting' },
181
bps_in => { hdr => 'BpsIn', num => 1, label => 'Bytes per second received by the server', },
182
bps_out => { hdr => 'BpsOut', num => 1, label => 'Bytes per second sent by the server', },
183
buf_free => { hdr => 'Free Bufs', num => 1, label => 'Buffers free in the buffer pool' },
184
buf_pool_hit_rate => { hdr => 'Hit Rate', num => 0, label => 'Buffer pool hit rate' },
185
buf_pool_hits => { hdr => 'Hits', num => 1, label => 'Buffer pool hits' },
186
buf_pool_reads => { hdr => 'Reads', num => 1, label => 'Buffer pool reads' },
187
buf_pool_size => { hdr => 'Size', num => 1, label => 'Buffer pool size' },
188
bufs_in_node_heap => { hdr => 'Node Heap Bufs', num => 1, label => 'Buffers in buffer pool node heap' },
189
bytes_behind_master => { hdr => 'ByteLag', num => 1, label => 'Bytes the slave lags the master in binlog' },
190
cell_event_set => { hdr => 'Ending?', num => 1, label => 'Whether the cell event is set' },
191
cell_waiting => { hdr => 'Waiting?', num => 1, label => 'Whether the cell is waiting' },
192
child_db => { hdr => 'Child DB', num => 0, label => 'The database of the child table' },
193
child_index => { hdr => 'Child Index', num => 0, label => 'The index in the child table' },
194
child_table => { hdr => 'Child Table', num => 0, label => 'The child table' },
195
cmd => { hdr => 'Cmd', num => 0, label => 'Type of command being executed', },
196
cnt => { hdr => 'Cnt', num => 0, label => 'Count', agg => 'count', aggonly => 1 },
197
connect_retry => { hdr => 'Connect Retry', num => 1, label => 'Slave connect-retry timeout' },
198
cxn => { hdr => 'CXN', num => 0, label => 'Connection from which the data came', },
199
db => { hdr => 'DB', num => 0, label => 'Current database', },
200
dict_mem_alloc => { hdr => 'Dict Mem', num => 1, label => 'Dictionary memory allocated' },
201
dirty_bufs => { hdr => 'Dirty Buf', num => 1, label => 'Dirty buffer pool pages' },
202
dl_txn_num => { hdr => 'Num', num => 0, label => 'Deadlocked transaction number', },
203
event_set => { hdr => 'Evt Set?', num => 1, label => '[Win32] if a wait event is set', },
204
exec_master_log_pos => { hdr => 'Exec Master Log Pos', num => 1, label => 'Exec Master Log Position' },
205
fk_name => { hdr => 'Constraint', num => 0, label => 'The name of the FK constraint' },
206
free_list_len => { hdr => 'Free List Len', num => 1, label => 'Length of the free list' },
207
has_read_view => { hdr => 'Rd View', num => 1, label => 'Whether the transaction has a read view' },
208
hash_searches_s => { hdr => 'Hash/Sec', num => 1, label => 'Number of hash searches/sec' },
209
hash_table_size => { hdr => 'Size', num => 1, label => 'Number of non-hash searches/sec' },
210
heap_no => { hdr => 'Heap', num => 1, label => 'Heap number' },
211
heap_size => { hdr => 'Heap', num => 1, label => 'Heap size' },
212
history_list_len => { hdr => 'History', num => 1, label => 'History list length' },
213
host_and_domain => { hdr => 'Host', num => 0, label => 'Hostname/IP and domain' },
214
host_and_port => { hdr => 'Host/IP', num => 0, label => 'Hostname or IP address, and port number', },
215
hostname => { hdr => 'Host', num => 0, label => 'Hostname' },
216
index => { hdr => 'Index', num => 0, label => 'The index involved' },
217
index_ref => { hdr => 'Index Ref', num => 0, label => 'Index referenced' },
218
info => { hdr => 'Query', num => 0, label => 'Info or the current query', },
219
insert_intention => { hdr => 'Ins Intent', num => 1, label => 'Whether the thread was trying to insert' },
220
inserts => { hdr => 'Inserts', num => 1, label => 'Inserts' },
221
io_bytes_s => { hdr => 'Bytes/Sec', num => 1, label => 'Average I/O bytes/sec' },
222
io_flush_type => { hdr => 'Flush Type', num => 0, label => 'I/O Flush Type' },
223
io_fsyncs_s => { hdr => 'fsyncs/sec', num => 1, label => 'I/O fsyncs/sec' },
224
io_reads_s => { hdr => 'Reads/Sec', num => 1, label => 'Average I/O reads/sec' },
225
io_writes_s => { hdr => 'Writes/Sec', num => 1, label => 'Average I/O writes/sec' },
226
ip => { hdr => 'IP', num => 0, label => 'IP address' },
227
is_name_locked => { hdr => 'Locked', num => 1, label => 'Whether table is name locked', },
228
key_buffer_hit => { hdr => 'KCacheHit', num => 1, label => 'Key cache hit ratio', },
229
key_len => { hdr => 'Key Length', num => 1, label => 'Number of bytes used in the key' },
230
last_chkp => { hdr => 'Last Checkpoint', num => 0, label => 'Last log checkpoint' },
231
last_errno => { hdr => 'Last Errno', num => 1, label => 'Last error number' },
232
last_error => { hdr => 'Last Error', num => 0, label => 'Last error' },
233
last_s_file_name => { hdr => 'S-File', num => 0, label => 'Filename where last read locked' },
234
last_s_line => { hdr => 'S-Line', num => 1, label => 'Line where last read locked' },
235
last_x_file_name => { hdr => 'X-File', num => 0, label => 'Filename where last write locked' },
236
last_x_line => { hdr => 'X-Line', num => 1, label => 'Line where last write locked' },
237
last_pct => { hdr => 'Pct', num => 1, label => 'Last Percentage' },
238
last_total => { hdr => 'Last Total', num => 1, label => 'Last Total' },
239
last_value => { hdr => 'Last Incr', num => 1, label => 'Last Value' },
240
load => { hdr => 'Load', num => 1, label => 'Server load' },
241
lock_cfile_name => { hdr => 'Crtd File', num => 0, label => 'Filename where lock created' },
242
lock_cline => { hdr => 'Crtd Line', num => 1, label => 'Line where lock created' },
243
lock_mem_addr => { hdr => 'Addr', num => 0, label => 'The lock memory address' },
244
lock_mode => { hdr => 'Mode', num => 0, label => 'The lock mode' },
245
lock_structs => { hdr => 'LStrcts', num => 1, label => 'Number of lock structs' },
246
lock_type => { hdr => 'Type', num => 0, label => 'The lock type' },
247
lock_var => { hdr => 'Lck Var', num => 1, label => 'The lock variable' },
248
lock_wait_time => { hdr => 'Wait', num => 1, label => 'How long txn has waited for a lock' },
249
log_flushed_to => { hdr => 'Flushed To', num => 0, label => 'Log position flushed to' },
250
log_ios_done => { hdr => 'IO Done', num => 1, label => 'Log I/Os done' },
251
log_ios_s => { hdr => 'IO/Sec', num => 1, label => 'Average log I/Os per sec' },
252
log_seq_no => { hdr => 'Sequence No.', num => 0, label => 'Log sequence number' },
253
main_thread_id => { hdr => 'Main Thread ID', num => 1, label => 'Main thread ID' },
254
main_thread_proc_no => { hdr => 'Main Thread Proc', num => 1, label => 'Main thread process number' },
255
main_thread_state => { hdr => 'Main Thread State', num => 0, label => 'Main thread state' },
256
master_file => { hdr => 'File', num => 0, label => 'Master file' },
257
master_host => { hdr => 'Master', num => 0, label => 'Master server hostname' },
258
master_log_file => { hdr => 'Master Log File', num => 0, label => 'Master log file' },
259
master_port => { hdr => 'Master Port', num => 1, label => 'Master port' },
260
master_pos => { hdr => 'Position', num => 1, label => 'Master position' },
261
master_ssl_allowed => { hdr => 'Master SSL Allowed', num => 0, label => 'Master SSL Allowed' },
262
master_ssl_ca_file => { hdr => 'Master SSL CA File', num => 0, label => 'Master SSL Cert Auth File' },
263
master_ssl_ca_path => { hdr => 'Master SSL CA Path', num => 0, label => 'Master SSL Cert Auth Path' },
264
master_ssl_cert => { hdr => 'Master SSL Cert', num => 0, label => 'Master SSL Cert' },
265
master_ssl_cipher => { hdr => 'Master SSL Cipher', num => 0, label => 'Master SSL Cipher' },
266
master_ssl_key => { hdr => 'Master SSL Key', num => 0, label => 'Master SSL Key' },
267
master_user => { hdr => 'Master User', num => 0, label => 'Master username' },
268
max_txn => { hdr => 'MaxTxnTime', num => 1, label => 'MaxTxn' },
269
merged_recs => { hdr => 'Merged Recs', num => 1, label => 'Merged records' },
270
merges => { hdr => 'Merges', num => 1, label => 'Merges' },
271
mutex_os_waits => { hdr => 'Waits', num => 1, label => 'Mutex OS Waits' },
272
mutex_spin_rounds => { hdr => 'Rounds', num => 1, label => 'Mutex Spin Rounds' },
273
mutex_spin_waits => { hdr => 'Spins', num => 1, label => 'Mutex Spin Waits' },
274
mysql_thread_id => { hdr => 'ID', num => 1, label => 'MySQL connection (thread) ID', },
275
name => { hdr => 'Name', num => 0, label => 'Variable Name' },
276
n_bits => { hdr => '# Bits', num => 1, label => 'Number of bits' },
277
non_hash_searches_s => { hdr => 'Non-Hash/Sec', num => 1, label => 'Non-hash searches/sec' },
278
num_deletes => { hdr => 'Del', num => 1, label => 'Number of deletes' },
279
num_deletes_sec => { hdr => 'Del/Sec', num => 1, label => 'Number of deletes' },
280
num_inserts => { hdr => 'Ins', num => 1, label => 'Number of inserts' },
281
num_inserts_sec => { hdr => 'Ins/Sec', num => 1, label => 'Number of inserts' },
282
num_readers => { hdr => 'Readers', num => 1, label => 'Number of readers' },
283
num_reads => { hdr => 'Read', num => 1, label => 'Number of reads' },
284
num_reads_sec => { hdr => 'Read/Sec', num => 1, label => 'Number of reads' },
285
num_res_ext => { hdr => 'BTree Extents', num => 1, label => 'Number of extents reserved for B-Tree' },
286
num_rows => { hdr => 'Row Count', num => 1, label => 'Number of rows estimated to examine' },
287
num_times_open => { hdr => 'In Use', num => 1, label => '# times table is opened', },
288
num_txns => { hdr => 'Txns', num => 1, label => 'Number of transactions' },
289
num_updates => { hdr => 'Upd', num => 1, label => 'Number of updates' },
290
num_updates_sec => { hdr => 'Upd/Sec', num => 1, label => 'Number of updates' },
291
os_file_reads => { hdr => 'OS Reads', num => 1, label => 'OS file reads' },
292
os_file_writes => { hdr => 'OS Writes', num => 1, label => 'OS file writes' },
293
os_fsyncs => { hdr => 'OS fsyncs', num => 1, label => 'OS fsyncs' },
294
os_thread_id => { hdr => 'OS Thread', num => 1, label => 'The operating system thread ID' },
295
p_aio_writes => { hdr => 'Async Wrt', num => 1, label => 'Pending asynchronous I/O writes' },
296
p_buf_pool_flushes => { hdr => 'Buffer Pool Flushes', num => 1, label => 'Pending buffer pool flushes' },
297
p_ibuf_aio_reads => { hdr => 'IBuf Async Rds', num => 1, label => 'Pending insert buffer asynch I/O reads' },
298
p_log_flushes => { hdr => 'Log Flushes', num => 1, label => 'Pending log flushes' },
299
p_log_ios => { hdr => 'Log I/Os', num => 1, label => 'Pending log I/O operations' },
300
p_normal_aio_reads => { hdr => 'Async Rds', num => 1, label => 'Pending asynchronous I/O reads' },
301
p_preads => { hdr => 'preads', num => 1, label => 'Pending p-reads' },
302
p_pwrites => { hdr => 'pwrites', num => 1, label => 'Pending p-writes' },
303
p_sync_ios => { hdr => 'Sync I/Os', num => 1, label => 'Pending synchronous I/O operations' },
304
page_creates_sec => { hdr => 'Creates/Sec', num => 1, label => 'Page creates/sec' },
305
page_no => { hdr => 'Page', num => 1, label => 'Page number' },
306
page_reads_sec => { hdr => 'Reads/Sec', num => 1, label => 'Page reads per second' },
307
page_writes_sec => { hdr => 'Writes/Sec', num => 1, label => 'Page writes per second' },
308
pages_created => { hdr => 'Created', num => 1, label => 'Pages created' },
309
pages_modified => { hdr => 'Dirty Pages', num => 1, label => 'Pages modified (dirty)' },
310
pages_read => { hdr => 'Reads', num => 1, label => 'Pages read' },
311
pages_total => { hdr => 'Pages', num => 1, label => 'Pages total' },
312
pages_written => { hdr => 'Writes', num => 1, label => 'Pages written' },
313
parent_col => { hdr => 'Parent Column', num => 0, label => 'The referred column in the parent table', },
314
parent_db => { hdr => 'Parent DB', num => 0, label => 'The database of the parent table' },
315
parent_index => { hdr => 'Parent Index', num => 0, label => 'The referred index in the parent table' },
316
parent_table => { hdr => 'Parent Table', num => 0, label => 'The parent table' },
317
part_id => { hdr => 'Part ID', num => 1, label => 'Sub-part ID of the query' },
318
partitions => { hdr => 'Partitions', num => 0, label => 'Query partitions used' },
319
pct => { hdr => 'Pct', num => 1, label => 'Percentage' },
320
pending_chkp_writes => { hdr => 'Chkpt Writes', num => 1, label => 'Pending log checkpoint writes' },
321
pending_log_writes => { hdr => 'Log Writes', num => 1, label => 'Pending log writes' },
322
port => { hdr => 'Port', num => 1, label => 'Client port number', },
323
possible_keys => { hdr => 'Poss. Keys', num => 0, label => 'Possible keys' },
324
proc_no => { hdr => 'Proc', num => 1, label => 'Process number' },
325
q_cache_hit => { hdr => 'QCacheHit', num => 1, label => 'Query cache hit ratio', },
326
qps => { hdr => 'QPS', num => 1, label => 'How many queries/sec', },
327
queries_in_queue => { hdr => 'Queries Queued', num => 1, label => 'Queries in queue' },
328
queries_inside => { hdr => 'Queries Inside', num => 1, label => 'Queries inside InnoDB' },
329
query_id => { hdr => 'Query ID', num => 1, label => 'Query ID' },
330
query_status => { hdr => 'Query Status', num => 0, label => 'The query status' },
331
query_text => { hdr => 'Query Text', num => 0, label => 'The query text' },
332
questions => { hdr => 'Questions', num => 1, label => 'How many queries the server has gotten', },
333
read_master_log_pos => { hdr => 'Read Master Pos', num => 1, label => 'Read master log position' },
334
read_views_open => { hdr => 'Rd Views', num => 1, label => 'Number of read views open' },
335
reads_pending => { hdr => 'Pending Reads', num => 1, label => 'Reads pending' },
336
relay_log_file => { hdr => 'Relay File', num => 0, label => 'Relay log file' },
337
relay_log_pos => { hdr => 'Relay Pos', num => 1, label => 'Relay log position' },
338
relay_log_size => { hdr => 'Relay Size', num => 1, label => 'Relay log size' },
339
relay_master_log_file => { hdr => 'Relay Master File', num => 0, label => 'Relay master log file' },
340
replicate_do_db => { hdr => 'Do DB', num => 0, label => 'Replicate-do-db setting' },
341
replicate_do_table => { hdr => 'Do Table', num => 0, label => 'Replicate-do-table setting' },
342
replicate_ignore_db => { hdr => 'Ignore DB', num => 0, label => 'Replicate-ignore-db setting' },
343
replicate_ignore_table => { hdr => 'Ignore Table', num => 0, label => 'Replicate-do-table setting' },
344
replicate_wild_do_table => { hdr => 'Wild Do Table', num => 0, label => 'Replicate-wild-do-table setting' },
345
replicate_wild_ignore_table => { hdr => 'Wild Ignore Table', num => 0, label => 'Replicate-wild-ignore-table setting' },
346
request_type => { hdr => 'Type', num => 0, label => 'Type of lock the thread waits for' },
347
reservation_count => { hdr => 'ResCnt', num => 1, label => 'Reservation Count' },
348
row_locks => { hdr => 'RLocks', num => 1, label => 'Number of row locks' },
349
rw_excl_os_waits => { hdr => 'RW Waits', num => 1, label => 'R/W Excl. OS Waits' },
350
rw_excl_spins => { hdr => 'RW Spins', num => 1, label => 'R/W Excl. Spins' },
351
rw_shared_os_waits => { hdr => 'Sh Waits', num => 1, label => 'R/W Shared OS Waits' },
352
rw_shared_spins => { hdr => 'Sh Spins', num => 1, label => 'R/W Shared Spins' },
353
scan_type => { hdr => 'Type', num => 0, label => 'Scan type in chosen' },
354
seg_size => { hdr => 'Seg. Size', num => 1, label => 'Segment size' },
355
select_type => { hdr => 'Select Type', num => 0, label => 'Type of select used' },
356
signal_count => { hdr => 'Signals', num => 1, label => 'Signal Count' },
357
size => { hdr => 'Size', num => 1, label => 'Size of the tablespace' },
358
skip_counter => { hdr => 'Skip Counter', num => 1, label => 'Skip counter' },
359
slave_catchup_rate => { hdr => 'Catchup', num => 1, label => 'How fast the slave is catching up in the binlog' },
360
slave_io_running => { hdr => 'Slave-IO', num => 0, label => 'Whether the slave I/O thread is running' },
361
slave_io_state => { hdr => 'Slave IO State', num => 0, label => 'Slave I/O thread state' },
362
slave_open_temp_tables => { hdr => 'Temp', num => 1, label => 'Slave open temp tables' },
363
slave_sql_running => { hdr => 'Slave-SQL', num => 0, label => 'Whether the slave SQL thread is running' },
364
slow => { hdr => 'Slow', num => 1, label => 'How many slow queries', },
365
space_id => { hdr => 'Space', num => 1, label => 'Tablespace ID' },
366
special => { hdr => 'Special', num => 0, label => 'Special/Other info' },
367
state => { hdr => 'State', num => 0, label => 'Connection state', maxw => 18, },
368
tables_in_use => { hdr => 'Tbl Used', num => 1, label => 'Number of tables in use' },
369
tables_locked => { hdr => 'Tbl Lck', num => 1, label => 'Number of tables locked' },
370
tbl => { hdr => 'Table', num => 0, label => 'Table', },
371
thread => { hdr => 'Thread', num => 1, label => 'Thread number' },
372
thread_decl_inside => { hdr => 'Thread Inside', num => 0, label => 'What the thread is declared inside' },
373
thread_purpose => { hdr => 'Purpose', num => 0, label => "The thread's purpose" },
374
thread_status => { hdr => 'Thread Status', num => 0, label => 'The thread status' },
375
time => { hdr => 'Time', num => 1, label => 'Time since the last event', },
376
time_behind_master => { hdr => 'TimeLag', num => 1, label => 'Time slave lags master' },
377
timestring => { hdr => 'Timestring', num => 0, label => 'Time the event occurred' },
378
total => { hdr => 'Total', num => 1, label => 'Total' },
379
total_mem_alloc => { hdr => 'Memory', num => 1, label => 'Total memory allocated' },
380
truncates => { hdr => 'Trunc', num => 0, label => 'Whether the deadlock is truncating InnoDB status' },
381
txn_doesnt_see_ge => { hdr => "Txn Won't See", num => 0, label => 'Where txn read view is limited' },
382
txn_id => { hdr => 'ID', num => 0, label => 'Transaction ID' },
383
txn_sees_lt => { hdr => 'Txn Sees', num => 1, label => 'Where txn read view is limited' },
384
txn_status => { hdr => 'Txn Status', num => 0, label => 'Transaction status' },
385
txn_time_remain => { hdr => 'Remaining', num => 1, label => 'Time until txn rollback/commit completes' },
386
undo_log_entries => { hdr => 'Undo', num => 1, label => 'Number of undo log entries' },
387
undo_for => { hdr => 'Undo', num => 0, label => 'Undo for' },
388
until_condition => { hdr => 'Until Condition', num => 0, label => 'Slave until condition' },
389
until_log_file => { hdr => 'Until Log File', num => 0, label => 'Slave until log file' },
390
until_log_pos => { hdr => 'Until Log Pos', num => 1, label => 'Slave until log position' },
391
used_cells => { hdr => 'Cells Used', num => 1, label => 'Number of cells used' },
392
used_bufs => { hdr => 'Used Bufs', num => 1, label => 'Number of buffer pool pages used' },
393
user => { hdr => 'User', num => 0, label => 'Database username', },
394
value => { hdr => 'Value', num => 1, label => 'Value' },
395
versions => { hdr => 'Versions', num => 1, label => 'Number of InnoDB MVCC versions unpurged' },
396
victim => { hdr => 'Victim', num => 0, label => 'Whether this txn was the deadlock victim' },
397
wait_array_size => { hdr => 'Wait Array Size', num => 1, label => 'Wait Array Size' },
398
wait_status => { hdr => 'Lock Status', num => 0, label => 'Status of txn locks' },
399
waited_at_filename => { hdr => 'File', num => 0, label => 'Filename at which thread waits' },
400
waited_at_line => { hdr => 'Line', num => 1, label => 'Line at which thread waits' },
401
waiters_flag => { hdr => 'Waiters', num => 1, label => 'Waiters Flag' },
402
waiting => { hdr => 'Waiting', num => 1, label => 'Whether lock is being waited for' },
403
when => { hdr => 'When', num => 0, label => 'Time scale' },
404
writer_lock_mode => { hdr => 'Wrtr Lck Mode', num => 0, label => 'Writer lock mode' },
405
writer_thread => { hdr => 'Wrtr Thread', num => 1, label => 'Writer thread ID' },
406
writes_pending => { hdr => 'Writes', num => 1, label => 'Number of writes pending' },
407
writes_pending_flush_list => { hdr => 'Flush List Writes', num => 1, label => 'Number of flush list writes pending' },
408
writes_pending_lru => { hdr => 'LRU Writes', num => 1, label => 'Number of LRU writes pending' },
409
writes_pending_single_page => { hdr => '1-Page Writes', num => 1, label => 'Number of 1-page writes pending' },
412
# Apply a default property or three. By default, columns are not width-constrained,
413
# aligned left, and sorted alphabetically, not numerically.
414
foreach my $col ( values %columns ) {
415
map { $col->{$_} ||= 0 } qw(num minw maxw);
416
$col->{just} = $col->{num} ? '' : '-';
420
# This hash defines every filter that can be applied to a table. These
421
# become part of tbl_meta as well. Each filter is just an expression that
422
# returns true or false.
423
# Properties of each entry:
424
# * func: the subroutine
425
# * name: the name, repeated
426
# * user: whether it's a user-defined filter (saved in config)
427
# * text: text of the subroutine
428
# * note: explanation
431
# These are pre-processed to live in %filters above, by compiling them.
432
my %builtin_filters = (
435
return ( !$set->{info} || $set->{info} ne 'SHOW FULL PROCESSLIST' )
436
&& ( !$set->{query_text} || $set->{query_text} !~ m/INNODB STATUS$/ );
438
note => 'Removes the innotop processes from the list',
439
tbls => [qw(innodb_transactions processlist)],
443
return ( !defined($set->{txn_status}) || $set->{txn_status} ne 'not started' )
444
&& ( !defined($set->{cmd}) || $set->{cmd} !~ m/Sleep|Binlog Dump/ )
445
&& ( !defined($set->{info}) || $set->{info} =~ m/\S/ );
447
note => 'Removes processes which are not doing anything',
448
tbls => [qw(innodb_transactions processlist)],
452
return !$set->{state} || $set->{state} !~ m/^(?:Waiting for master|Has read all relay)/;
454
note => 'Removes slave I/O threads from the list',
455
tbls => [qw(processlist slave_io_status)],
459
return $set->{num_times_open} + $set->{is_name_locked};
461
note => 'Removes tables that are not in use or locked',
462
tbls => [qw(open_tables)],
466
return $set->{master_file} ? 1 : 0;
468
note => 'Removes servers that are not masters',
469
tbls => [qw(master_status)],
473
return $set->{master_host} ? 1 : 0;
475
note => 'Removes servers that are not slaves',
476
tbls => [qw(slave_io_status slave_sql_status)],
478
thd_is_not_waiting => {
480
return $set->{thread_status} !~ m#waiting for i/o request#;
482
note => 'Removes idle I/O threads',
483
tbls => [qw(io_threads)],
486
foreach my $key ( keys %builtin_filters ) {
487
my ( $sub, $err ) = compile_filter($builtin_filters{$key}->{text});
490
text => $builtin_filters{$key}->{text},
492
name => $key, # useful for later
493
note => $builtin_filters{$key}->{note},
494
tbls => $builtin_filters{$key}->{tbls},
499
# Sets (arrayrefs) of variables that are used in S mode. They are read/written to
505
'set_precision(Questions/Uptime_hires) as QPS',
506
'set_precision(Com_commit/Uptime_hires) as Commit_PS',
507
'set_precision((Com_rollback||0)/(Com_commit||1)) as Rollback_Commit',
509
. join('+', map { "($_||0)" }
510
qw(Com_delete Com_delete_multi Com_insert Com_insert_select Com_replace
511
Com_replace_select Com_select Com_update Com_update_multi))
512
. ')/(Com_commit||1)) as Write_Commit',
513
'set_precision((Com_select+(Qcache_hits||0))/(('
514
. join('+', map { "($_||0)" }
515
qw(Com_delete Com_delete_multi Com_insert Com_insert_select Com_replace
516
Com_replace_select Com_select Com_update Com_update_multi))
517
. ')||1)) as R_W_Ratio',
518
'set_precision(Opened_tables/Uptime_hires) as Opens_PS',
519
'percent($cur->{Open_tables}/($cur->{table_cache})) as Table_Cache_Used',
520
'set_precision(Threads_created/Uptime_hires) as Threads_PS',
521
'percent($cur->{Threads_cached}/($cur->{thread_cache_size}||1)) as Thread_Cache_Used',
522
'percent($cur->{Max_used_connections}/($cur->{max_connections}||1)) as CXN_Used_Ever',
523
'percent($cur->{Threads_connected}/($cur->{max_connections}||1)) as CXN_Used_Now',
529
qw(Uptime Questions Com_delete Com_delete_multi Com_insert
530
Com_insert_select Com_replace Com_replace_select Com_select Com_update
537
qw( Uptime Select_full_join Select_full_range_join Select_range
538
Select_range_check Select_scan Slow_queries Sort_merge_passes
539
Sort_range Sort_rows Sort_scan)
545
qw( Uptime Innodb_row_lock_current_waits Innodb_row_lock_time
546
Innodb_row_lock_time_avg Innodb_row_lock_time_max Innodb_row_lock_waits
547
Innodb_rows_deleted Innodb_rows_inserted Innodb_rows_read
554
qw( Uptime Com_begin Com_commit Com_rollback Com_savepoint
555
Com_xa_commit Com_xa_end Com_xa_prepare Com_xa_recover Com_xa_rollback
562
qw( Uptime Key_blocks_not_flushed Key_blocks_unused Key_blocks_used
563
Key_read_requests Key_reads Key_write_requests Key_writes )
569
"percent($exprs{QcacheHitRatio}) as Hit_Pct",
570
'set_precision((Qcache_hits||0)/(Qcache_inserts||1)) as Hit_Ins',
571
'set_precision((Qcache_lowmem_prunes||0)/Uptime_hires) as Lowmem_Prunes_sec',
572
'percent(1-((Qcache_free_blocks||0)/(Qcache_total_blocks||1))) as Blocks_used',
573
qw( Qcache_free_blocks Qcache_free_memory Qcache_not_cached Qcache_queries_in_cache)
579
qw( Uptime Handler_read_key Handler_read_first Handler_read_next
580
Handler_read_prev Handler_read_rnd Handler_read_rnd_next Handler_delete
581
Handler_update Handler_write)
584
cxns_files_threads => {
587
qw( Uptime Aborted_clients Aborted_connects Bytes_received Bytes_sent
588
Compression Connections Created_tmp_disk_tables Created_tmp_files
589
Created_tmp_tables Max_used_connections Open_files Open_streams
590
Open_tables Opened_tables Table_locks_immediate Table_locks_waited
591
Threads_cached Threads_connected Threads_created Threads_running)
597
qw( Uptime Com_dealloc_sql Com_execute_sql Com_prepare_sql Com_reset
598
Com_stmt_close Com_stmt_execute Com_stmt_fetch Com_stmt_prepare
599
Com_stmt_reset Com_stmt_send_long_data )
605
"$exprs{OldVersions} as OldVersions",
606
qw(IB_sm_mutex_spin_waits IB_sm_mutex_spin_rounds IB_sm_mutex_os_waits),
607
"$exprs{NumTxns} as NumTxns",
608
"$exprs{MaxTxnTime} as MaxTxnTime",
609
qw(IB_ro_queries_inside IB_ro_queries_in_queue),
610
"set_precision($exprs{DirtyBufs} * 100) as dirty_bufs",
611
"set_precision($exprs{BufPoolFill} * 100) as buf_fill",
612
qw(IB_bp_pages_total IB_bp_pages_read IB_bp_pages_written IB_bp_pages_created)
618
'percent(1-((Innodb_buffer_pool_pages_free||0)/($cur->{Innodb_buffer_pool_pages_total}||1))) as BP_page_cache_usage',
619
'percent(1-((Innodb_buffer_pool_reads||0)/(Innodb_buffer_pool_read_requests||1))) as BP_cache_hit_ratio',
620
'Innodb_buffer_pool_wait_free',
627
'set_precision(Slow_queries/Uptime_hires) as Slow_PS',
628
'set_precision(Select_full_join/Uptime_hires) as Full_Join_PS',
629
'percent(Select_full_join/(Com_select||1)) as Full_Join_Ratio',
635
# Defines sets of servers between which the user can quickly switch.
639
# This hash defines server connections. Each connection is a string that can be passed to
640
# the DBI connection. These are saved in the connections section in the config file.
642
# Defines the parts of connections.
643
my @conn_parts = qw(user have_user pass have_pass dsn savepass dl_table);
646
# This hash defines the max values seen for various status/variable values, for graphing.
647
# These are stored in their own section in the config file. These are just initial values:
656
# ###########################################################################
657
# Valid Term::ANSIColor color strings.
658
# ###########################################################################
659
my %ansicolors = map { $_ => 1 }
660
qw( black blink blue bold clear concealed cyan dark green magenta on_black
661
on_blue on_cyan on_green on_magenta on_red on_white on_yellow red reset
662
reverse underline underscore white yellow);
664
# ###########################################################################
665
# Valid comparison operators for color rules
666
# ###########################################################################
668
'==' => 'Numeric equality',
669
'>' => 'Numeric greater-than',
670
'<' => 'Numeric less-than',
671
'>=' => 'Numeric greater-than/equal',
672
'<=' => 'Numeric less-than/equal',
673
'!=' => 'Numeric not-equal',
674
'eq' => 'String equality',
675
'gt' => 'String greater-than',
676
'lt' => 'String less-than',
677
'ge' => 'String greater-than/equal',
678
'le' => 'String less-than/equal',
679
'ne' => 'String not-equal',
680
'=~' => 'Pattern match',
681
'!~' => 'Negated pattern match',
684
# ###########################################################################
685
# Valid aggregate functions.
686
# ###########################################################################
695
my @args = grep { defined $_ } @_;
696
return (sum(map { m/([\d\.-]+)/g } @args) || 0) / (scalar(@args) || 1);
701
# ###########################################################################
702
# Valid functions for transformations.
703
# ###########################################################################
705
shorten => \&shorten,
706
secs_to_time => \&secs_to_time,
707
no_ctrl_char => \&no_ctrl_char,
708
percent => \&percent,
709
commify => \&commify,
710
dulint_to_int => \&dulint_to_int,
711
set_precision => \&set_precision,
714
# Table definitions {{{3
715
# This hash defines every table that can get displayed in every mode. Each
716
# table specifies columns and column data sources. The column is
717
# defined by the %columns hash.
719
# Example: foo => { src => 'bar' } means the foo column (look at
720
# $columns{foo} for its definition) gets its data from the 'bar' element of
721
# the current data set, whatever that is.
723
# These columns are post-processed after being defined, because they get stuff
724
# from %columns. After all the config is loaded for columns, there's more
725
# post-processing too; the subroutines compiled from src get added to
726
# the hash elements for extract_values to use.
727
# ###########################################################################
730
adaptive_hash_index => {
731
capt => 'Adaptive Hash Index',
734
cxn => { src => 'cxn' },
735
hash_table_size => { src => 'IB_ib_hash_table_size', trans => [qw(shorten)], },
736
used_cells => { src => 'IB_ib_used_cells' },
737
bufs_in_node_heap => { src => 'IB_ib_bufs_in_node_heap' },
738
hash_searches_s => { src => 'IB_ib_hash_searches_s' },
739
non_hash_searches_s => { src => 'IB_ib_non_hash_searches_s' },
741
visible => [ qw(cxn hash_table_size used_cells bufs_in_node_heap hash_searches_s non_hash_searches_s) ],
750
capt => 'Buffer Pool',
753
cxn => { src => 'cxn' },
754
total_mem_alloc => { src => 'IB_bp_total_mem_alloc', trans => [qw(shorten)], },
755
awe_mem_alloc => { src => 'IB_bp_awe_mem_alloc', trans => [qw(shorten)], },
756
add_pool_alloc => { src => 'IB_bp_add_pool_alloc', trans => [qw(shorten)], },
757
buf_pool_size => { src => 'IB_bp_buf_pool_size', trans => [qw(shorten)], },
758
buf_free => { src => 'IB_bp_buf_free' },
759
buf_pool_hit_rate => { src => 'IB_bp_buf_pool_hit_rate' },
760
buf_pool_reads => { src => 'IB_bp_buf_pool_reads' },
761
buf_pool_hits => { src => 'IB_bp_buf_pool_hits' },
762
dict_mem_alloc => { src => 'IB_bp_dict_mem_alloc' },
763
pages_total => { src => 'IB_bp_pages_total' },
764
pages_modified => { src => 'IB_bp_pages_modified' },
765
reads_pending => { src => 'IB_bp_reads_pending' },
766
writes_pending => { src => 'IB_bp_writes_pending' },
767
writes_pending_lru => { src => 'IB_bp_writes_pending_lru' },
768
writes_pending_flush_list => { src => 'IB_bp_writes_pending_flush_list' },
769
writes_pending_single_page => { src => 'IB_bp_writes_pending_single_page' },
770
page_creates_sec => { src => 'IB_bp_page_creates_sec' },
771
page_reads_sec => { src => 'IB_bp_page_reads_sec' },
772
page_writes_sec => { src => 'IB_bp_page_writes_sec' },
773
pages_created => { src => 'IB_bp_pages_created' },
774
pages_read => { src => 'IB_bp_pages_read' },
775
pages_written => { src => 'IB_bp_pages_written' },
777
visible => [ qw(cxn buf_pool_size buf_free pages_total pages_modified buf_pool_hit_rate total_mem_alloc add_pool_alloc)],
785
# TODO: a new step in set_to_tbl: join result to itself, grouped?
786
# TODO: this would also enable pulling Q and T data together.
787
# TODO: using a SQL-ish language would also allow pivots to be easier -- treat the pivoted data as a view and SELECT from it.
789
capt => 'Command Summary',
792
name => { src => 'name' },
793
total => { src => 'total' },
794
value => { src => 'value', agg => 'sum'},
795
pct => { src => 'value/total', trans => [qw(percent)] },
796
last_total => { src => 'last_total' },
797
last_value => { src => 'last_value', agg => 'sum'},
798
last_pct => { src => 'last_value/last_total', trans => [qw(percent)] },
800
visible => [qw(name value pct last_value last_pct)],
802
sort_cols => '-value',
805
group_by => [qw(name)],
809
capt => 'Deadlock Locks',
812
cxn => { src => 'cxn' },
813
mysql_thread_id => { src => 'mysql_thread_id' },
814
dl_txn_num => { src => 'dl_txn_num' },
815
lock_type => { src => 'lock_type' },
816
space_id => { src => 'space_id' },
817
page_no => { src => 'page_no' },
818
heap_no => { src => 'heap_no' },
819
n_bits => { src => 'n_bits' },
820
index => { src => 'index' },
821
db => { src => 'db' },
822
tbl => { src => 'table' },
823
lock_mode => { src => 'lock_mode' },
824
special => { src => 'special' },
825
insert_intention => { src => 'insert_intention' },
826
waiting => { src => 'waiting' },
828
visible => [ qw(cxn mysql_thread_id waiting lock_mode db tbl index special insert_intention)],
830
sort_cols => 'cxn mysql_thread_id',
836
deadlock_transactions => {
837
capt => 'Deadlock Transactions',
840
cxn => { src => 'cxn' },
841
active_secs => { src => 'active_secs' },
842
dl_txn_num => { src => 'dl_txn_num' },
843
has_read_view => { src => 'has_read_view' },
844
heap_size => { src => 'heap_size' },
845
host_and_domain => { src => 'hostname' },
846
hostname => { src => $exprs{Host} },
847
ip => { src => 'ip' },
848
lock_structs => { src => 'lock_structs' },
849
lock_wait_time => { src => 'lock_wait_time', trans => [ qw(secs_to_time) ] },
850
mysql_thread_id => { src => 'mysql_thread_id' },
851
os_thread_id => { src => 'os_thread_id' },
852
proc_no => { src => 'proc_no' },
853
query_id => { src => 'query_id' },
854
query_status => { src => 'query_status' },
855
query_text => { src => 'query_text', trans => [ qw(no_ctrl_char) ] },
856
row_locks => { src => 'row_locks' },
857
tables_in_use => { src => 'tables_in_use' },
858
tables_locked => { src => 'tables_locked' },
859
thread_decl_inside => { src => 'thread_decl_inside' },
860
thread_status => { src => 'thread_status' },
861
'time' => { src => 'active_secs', trans => [ qw(secs_to_time) ] },
862
timestring => { src => 'timestring' },
863
txn_doesnt_see_ge => { src => 'txn_doesnt_see_ge' },
864
txn_id => { src => 'txn_id' },
865
txn_sees_lt => { src => 'txn_sees_lt' },
866
txn_status => { src => 'txn_status' },
867
truncates => { src => 'truncates' },
868
undo_log_entries => { src => 'undo_log_entries' },
869
user => { src => 'user' },
870
victim => { src => 'victim' },
871
wait_status => { src => 'lock_wait_status' },
873
visible => [ qw(cxn mysql_thread_id timestring user hostname victim time undo_log_entries lock_structs query_text)],
875
sort_cols => 'cxn mysql_thread_id',
882
capt => 'EXPLAIN Results',
885
part_id => { src => 'id' },
886
select_type => { src => 'select_type' },
887
tbl => { src => 'table' },
888
partitions => { src => 'partitions' },
889
scan_type => { src => 'type' },
890
possible_keys => { src => 'possible_keys' },
891
index => { src => 'key' },
892
key_len => { src => 'key_len' },
893
index_ref => { src => 'ref' },
894
num_rows => { src => 'rows' },
895
special => { src => 'extra' },
897
visible => [ qw(select_type tbl partitions scan_type possible_keys index key_len index_ref num_rows special)],
906
capt => 'File I/O Misc',
909
cxn => { src => 'cxn' },
910
io_bytes_s => { src => 'IB_io_avg_bytes_s' },
911
io_flush_type => { src => 'IB_io_flush_type' },
912
io_fsyncs_s => { src => 'IB_io_fsyncs_s' },
913
io_reads_s => { src => 'IB_io_reads_s' },
914
io_writes_s => { src => 'IB_io_writes_s' },
915
os_file_reads => { src => 'IB_io_os_file_reads' },
916
os_file_writes => { src => 'IB_io_os_file_writes' },
917
os_fsyncs => { src => 'IB_io_os_fsyncs' },
919
visible => [ qw(cxn os_file_reads os_file_writes os_fsyncs io_reads_s io_writes_s io_bytes_s)],
928
capt => 'Foreign Key Error Info',
931
timestring => { src => 'IB_fk_timestring' },
932
child_db => { src => 'IB_fk_child_db' },
933
child_table => { src => 'IB_fk_child_table' },
934
child_index => { src => 'IB_fk_child_index' },
935
fk_name => { src => 'IB_fk_fk_name' },
936
parent_db => { src => 'IB_fk_parent_db' },
937
parent_table => { src => 'IB_fk_parent_table' },
938
parent_col => { src => 'IB_fk_parent_col' },
939
parent_index => { src => 'IB_fk_parent_index' },
940
attempted_op => { src => 'IB_fk_attempted_op' },
942
visible => [ qw(timestring child_db child_table child_index parent_db parent_table parent_col parent_index fk_name attempted_op)],
951
capt => 'Insert Buffers',
954
cxn => { src => 'cxn' },
955
inserts => { src => 'IB_ib_inserts' },
956
merged_recs => { src => 'IB_ib_merged_recs' },
957
merges => { src => 'IB_ib_merges' },
958
size => { src => 'IB_ib_size' },
959
free_list_len => { src => 'IB_ib_free_list_len' },
960
seg_size => { src => 'IB_ib_seg_size' },
962
visible => [ qw(cxn inserts merged_recs merges size free_list_len seg_size)],
971
capt => 'InnoDB Locks',
974
cxn => { src => 'cxn' },
975
db => { src => 'db' },
976
index => { src => 'index' },
977
insert_intention => { src => 'insert_intention' },
978
lock_mode => { src => 'lock_mode' },
979
lock_type => { src => 'lock_type' },
980
lock_wait_time => { src => 'lock_wait_time', trans => [ qw(secs_to_time) ] },
981
mysql_thread_id => { src => 'mysql_thread_id' },
982
n_bits => { src => 'n_bits' },
983
page_no => { src => 'page_no' },
984
space_id => { src => 'space_id' },
985
special => { src => 'special' },
986
tbl => { src => 'table' },
987
'time' => { src => 'active_secs', hdr => 'Active', trans => [ qw(secs_to_time) ] },
988
txn_id => { src => 'txn_id' },
989
waiting => { src => 'waiting' },
991
visible => [ qw(cxn mysql_thread_id lock_type waiting lock_wait_time time lock_mode db tbl index insert_intention special)],
993
sort_cols => 'cxn -lock_wait_time',
997
{ col => 'lock_wait_time', op => '>', arg => 60, color => 'red' },
998
{ col => 'lock_wait_time', op => '>', arg => 30, color => 'yellow' },
999
{ col => 'lock_wait_time', op => '>', arg => 10, color => 'green' },
1004
innodb_transactions => {
1005
capt => 'InnoDB Transactions',
1008
cxn => { src => 'cxn' },
1009
active_secs => { src => 'active_secs' },
1010
has_read_view => { src => 'has_read_view' },
1011
heap_size => { src => 'heap_size' },
1012
hostname => { src => $exprs{Host} },
1013
ip => { src => 'ip' },
1014
wait_status => { src => 'lock_wait_status' },
1015
lock_wait_time => { src => 'lock_wait_time', trans => [ qw(secs_to_time) ] },
1016
lock_structs => { src => 'lock_structs' },
1017
mysql_thread_id => { src => 'mysql_thread_id' },
1018
os_thread_id => { src => 'os_thread_id' },
1019
proc_no => { src => 'proc_no' },
1020
query_id => { src => 'query_id' },
1021
query_status => { src => 'query_status' },
1022
query_text => { src => 'query_text', trans => [ qw(no_ctrl_char) ] },
1023
txn_time_remain => { src => $exprs{TxnTimeRemain}, trans => [ qw(secs_to_time) ] },
1024
row_locks => { src => 'row_locks' },
1025
tables_in_use => { src => 'tables_in_use' },
1026
tables_locked => { src => 'tables_locked' },
1027
thread_decl_inside => { src => 'thread_decl_inside' },
1028
thread_status => { src => 'thread_status' },
1029
'time' => { src => 'active_secs', trans => [ qw(secs_to_time) ], agg => 'sum' },
1030
txn_doesnt_see_ge => { src => 'txn_doesnt_see_ge' },
1031
txn_id => { src => 'txn_id' },
1032
txn_sees_lt => { src => 'txn_sees_lt' },
1033
txn_status => { src => 'txn_status', minw => 10, maxw => 10 },
1034
undo_log_entries => { src => 'undo_log_entries' },
1035
user => { src => 'user', maxw => 10 },
1036
cnt => { src => 'mysql_thread_id', minw => 0 },
1038
visible => [ qw(cxn cnt mysql_thread_id user hostname txn_status time undo_log_entries query_text)],
1039
filters => [ qw( hide_self hide_inactive ) ],
1040
sort_cols => '-active_secs txn_status cxn mysql_thread_id',
1045
{ col => 'wait_status', op => 'eq', arg => 'LOCK WAIT', color => 'black on_red' },
1046
{ col => 'time', op => '>', arg => 600, color => 'red' },
1047
{ col => 'time', op => '>', arg => 300, color => 'yellow' },
1048
{ col => 'time', op => '>', arg => 60, color => 'green' },
1049
{ col => 'time', op => '>', arg => 30, color => 'cyan' },
1050
{ col => 'txn_status', op => 'eq', arg => 'not started', color => 'white' },
1052
group_by => [ qw(cxn txn_status) ],
1056
capt => 'I/O Threads',
1059
cxn => { src => 'cxn' },
1060
thread => { src => 'thread' },
1061
thread_purpose => { src => 'purpose' },
1062
event_set => { src => 'event_set' },
1063
thread_status => { src => 'state' },
1065
visible => [ qw(cxn thread thread_purpose thread_status)],
1066
filters => [ qw() ],
1067
sort_cols => 'cxn thread',
1074
capt => 'Log Statistics',
1077
cxn => { src => 'cxn' },
1078
last_chkp => { src => 'IB_lg_last_chkp' },
1079
log_flushed_to => { src => 'IB_lg_log_flushed_to' },
1080
log_ios_done => { src => 'IB_lg_log_ios_done' },
1081
log_ios_s => { src => 'IB_lg_log_ios_s' },
1082
log_seq_no => { src => 'IB_lg_log_seq_no' },
1083
pending_chkp_writes => { src => 'IB_lg_pending_chkp_writes' },
1084
pending_log_writes => { src => 'IB_lg_pending_log_writes' },
1086
visible => [ qw(cxn log_seq_no log_flushed_to last_chkp log_ios_done log_ios_s)],
1095
capt => 'Master Status',
1098
cxn => { src => 'cxn' },
1099
binlog_do_db => { src => 'binlog_do_db' },
1100
binlog_ignore_db => { src => 'binlog_ignore_db' },
1101
master_file => { src => 'file' },
1102
master_pos => { src => 'position' },
1103
binlog_cache_overflow => { src => '(Binlog_cache_disk_use||0)/(Binlog_cache_use||1)', trans => [ qw(percent) ] },
1105
visible => [ qw(cxn master_file master_pos binlog_cache_overflow)],
1106
filters => [ qw(cxn_is_master) ],
1114
capt => 'Pending I/O',
1117
cxn => { src => 'cxn' },
1118
p_normal_aio_reads => { src => 'IB_io_pending_normal_aio_reads' },
1119
p_aio_writes => { src => 'IB_io_pending_aio_writes' },
1120
p_ibuf_aio_reads => { src => 'IB_io_pending_ibuf_aio_reads' },
1121
p_sync_ios => { src => 'IB_io_pending_sync_ios' },
1122
p_buf_pool_flushes => { src => 'IB_io_pending_buffer_pool_flushes' },
1123
p_log_flushes => { src => 'IB_io_pending_log_flushes' },
1124
p_log_ios => { src => 'IB_io_pending_log_ios' },
1125
p_preads => { src => 'IB_io_pending_preads' },
1126
p_pwrites => { src => 'IB_io_pending_pwrites' },
1128
visible => [ qw(cxn p_normal_aio_reads p_aio_writes p_ibuf_aio_reads p_sync_ios p_log_flushes p_log_ios)],
1137
capt => 'Open Tables',
1140
cxn => { src => 'cxn' },
1141
db => { src => 'database' },
1142
tbl => { src => 'table' },
1143
num_times_open => { src => 'in_use' },
1144
is_name_locked => { src => 'name_locked' },
1146
visible => [ qw(cxn db tbl num_times_open is_name_locked)],
1147
filters => [ qw(table_is_open) ],
1148
sort_cols => '-num_times_open cxn db tbl',
1154
page_statistics => {
1155
capt => 'Page Statistics',
1158
cxn => { src => 'cxn' },
1159
pages_read => { src => 'IB_bp_pages_read' },
1160
pages_written => { src => 'IB_bp_pages_written' },
1161
pages_created => { src => 'IB_bp_pages_created' },
1162
page_reads_sec => { src => 'IB_bp_page_reads_sec' },
1163
page_writes_sec => { src => 'IB_bp_page_writes_sec' },
1164
page_creates_sec => { src => 'IB_bp_page_creates_sec' },
1166
visible => [ qw(cxn pages_read pages_written pages_created page_reads_sec page_writes_sec page_creates_sec)],
1175
capt => 'MySQL Process List',
1178
cxn => { src => 'cxn', minw => 6, maxw => 10 },
1179
mysql_thread_id => { src => 'id', minw => 6, maxw => 0 },
1180
user => { src => 'user', minw => 5, maxw => 8 },
1181
hostname => { src => $exprs{Host}, minw => 13, maxw => 8, },
1182
port => { src => $exprs{Port}, minw => 0, maxw => 0, },
1183
host_and_port => { src => 'host', minw => 0, maxw => 0 },
1184
db => { src => 'db', minw => 6, maxw => 12 },
1185
cmd => { src => 'command', minw => 5, maxw => 0 },
1186
time => { src => 'time', minw => 5, maxw => 0, trans => [ qw(secs_to_time) ], agg => 'sum' },
1187
state => { src => 'state', minw => 0, maxw => 0 },
1188
info => { src => 'info', minw => 0, maxw => 0, trans => [ qw(no_ctrl_char) ] },
1189
cnt => { src => 'id', minw => 0, maxw => 0 },
1191
visible => [ qw(cxn cmd cnt mysql_thread_id user hostname db time info)],
1192
filters => [ qw(hide_self hide_inactive hide_slave_io) ],
1193
sort_cols => '-time cxn hostname mysql_thread_id',
1198
{ col => 'state', op => 'eq', arg => 'Locked', color => 'black on_red' },
1199
{ col => 'cmd', op => 'eq', arg => 'Sleep', color => 'white' },
1200
{ col => 'user', op => 'eq', arg => 'system user', color => 'white' },
1201
{ col => 'cmd', op => 'eq', arg => 'Connect', color => 'white' },
1202
{ col => 'cmd', op => 'eq', arg => 'Binlog Dump', color => 'white' },
1203
{ col => 'time', op => '>', arg => 600, color => 'red' },
1204
{ col => 'time', op => '>', arg => 120, color => 'yellow' },
1205
{ col => 'time', op => '>', arg => 60, color => 'green' },
1206
{ col => 'time', op => '>', arg => 30, color => 'cyan' },
1208
group_by => [qw(cxn cmd)],
1212
# TODO: some more columns:
1213
# kb_used=hdr='BufUsed' minw='0' num='0' src='percent(1 - ((Key_blocks_unused * key_cache_block_size) / (key_buffer_size||1)))' dec='0' trans='' tbl='q_header' just='-' user='1' maxw='0' label='User-defined'
1214
# retries=hdr='Retries' minw='0' num='0' src='Slave_retried_transactions' dec='0' trans='' tbl='slave_sql_status' just='-' user='1' maxw='0' label='User-defined'
1215
# thd=hdr='Thd' minw='0' num='0' src='Threads_connected' dec='0' trans='' tbl='slave_sql_status' just='-' user='1' maxw='0' label='User-defined'
1218
capt => 'Q-mode Header',
1221
cxn => { src => 'cxn' },
1222
questions => { src => 'Questions' },
1223
qps => { src => 'Questions/Uptime_hires', dec => 1, trans => [qw(shorten)] },
1224
load => { src => $exprs{ServerLoad}, dec => 1, trans => [qw(shorten)] },
1225
slow => { src => 'Slow_queries', dec => 1, trans => [qw(shorten)] },
1226
q_cache_hit => { src => $exprs{QcacheHitRatio}, dec => 1, trans => [qw(percent)] },
1227
key_buffer_hit => { src => '1-(Key_reads/(Key_read_requests||1))', dec => 1, trans => [qw(percent)] },
1228
bps_in => { src => 'Bytes_received/Uptime_hires', dec => 1, trans => [qw(shorten)] },
1229
bps_out => { src => 'Bytes_sent/Uptime_hires', dec => 1, trans => [qw(shorten)] },
1230
when => { src => 'when' },
1232
visible => [ qw(cxn when load qps slow q_cache_hit key_buffer_hit bps_in bps_out)],
1234
sort_cols => 'when cxn',
1242
capt => 'InnoDB Row Operations',
1245
cxn => { src => 'cxn' },
1246
num_inserts => { src => 'IB_ro_num_rows_ins' },
1247
num_updates => { src => 'IB_ro_num_rows_upd' },
1248
num_reads => { src => 'IB_ro_num_rows_read' },
1249
num_deletes => { src => 'IB_ro_num_rows_del' },
1250
num_inserts_sec => { src => 'IB_ro_ins_sec' },
1251
num_updates_sec => { src => 'IB_ro_upd_sec' },
1252
num_reads_sec => { src => 'IB_ro_read_sec' },
1253
num_deletes_sec => { src => 'IB_ro_del_sec' },
1255
visible => [ qw(cxn num_inserts num_updates num_reads num_deletes num_inserts_sec
1256
num_updates_sec num_reads_sec num_deletes_sec)],
1264
row_operation_misc => {
1265
capt => 'Row Operation Misc',
1268
cxn => { src => 'cxn' },
1269
queries_in_queue => { src => 'IB_ro_queries_in_queue' },
1270
queries_inside => { src => 'IB_ro_queries_inside' },
1271
read_views_open => { src => 'IB_ro_read_views_open' },
1272
main_thread_id => { src => 'IB_ro_main_thread_id' },
1273
main_thread_proc_no => { src => 'IB_ro_main_thread_proc_no' },
1274
main_thread_state => { src => 'IB_ro_main_thread_state' },
1275
num_res_ext => { src => 'IB_ro_n_reserved_extents' },
1277
visible => [ qw(cxn queries_in_queue queries_inside read_views_open main_thread_state)],
1286
capt => 'InnoDB Semaphores',
1289
cxn => { src => 'cxn' },
1290
mutex_os_waits => { src => 'IB_sm_mutex_os_waits' },
1291
mutex_spin_rounds => { src => 'IB_sm_mutex_spin_rounds' },
1292
mutex_spin_waits => { src => 'IB_sm_mutex_spin_waits' },
1293
reservation_count => { src => 'IB_sm_reservation_count' },
1294
rw_excl_os_waits => { src => 'IB_sm_rw_excl_os_waits' },
1295
rw_excl_spins => { src => 'IB_sm_rw_excl_spins' },
1296
rw_shared_os_waits => { src => 'IB_sm_rw_shared_os_waits' },
1297
rw_shared_spins => { src => 'IB_sm_rw_shared_spins' },
1298
signal_count => { src => 'IB_sm_signal_count' },
1299
wait_array_size => { src => 'IB_sm_wait_array_size' },
1301
visible => [ qw(cxn mutex_os_waits mutex_spin_waits mutex_spin_rounds
1302
rw_excl_os_waits rw_excl_spins rw_shared_os_waits rw_shared_spins
1303
signal_count reservation_count )],
1311
slave_io_status => {
1312
capt => 'Slave I/O Status',
1315
cxn => { src => 'cxn' },
1316
connect_retry => { src => 'connect_retry' },
1317
master_host => { src => 'master_host', hdr => 'Master'},
1318
master_log_file => { src => 'master_log_file', hdr => 'File' },
1319
master_port => { src => 'master_port' },
1320
master_ssl_allowed => { src => 'master_ssl_allowed' },
1321
master_ssl_ca_file => { src => 'master_ssl_ca_file' },
1322
master_ssl_ca_path => { src => 'master_ssl_ca_path' },
1323
master_ssl_cert => { src => 'master_ssl_cert' },
1324
master_ssl_cipher => { src => 'master_ssl_cipher' },
1325
master_ssl_key => { src => 'master_ssl_key' },
1326
master_user => { src => 'master_user' },
1327
read_master_log_pos => { src => 'read_master_log_pos', hdr => 'Pos' },
1328
relay_log_size => { src => 'relay_log_space', trans => [qw(shorten)] },
1329
slave_io_running => { src => 'slave_io_running', hdr => 'On?' },
1330
slave_io_state => { src => 'slave_io_state', hdr => 'State' },
1332
visible => [ qw(cxn master_host slave_io_running master_log_file relay_log_size read_master_log_pos slave_io_state)],
1333
filters => [ qw( cxn_is_slave ) ],
1334
sort_cols => 'slave_io_running cxn',
1336
{ col => 'slave_io_running', op => 'ne', arg => 'Yes', color => 'black on_red' },
1343
slave_sql_status => {
1344
capt => 'Slave SQL Status',
1347
cxn => { src => 'cxn' },
1348
exec_master_log_pos => { src => 'exec_master_log_pos', hdr => 'Master Pos' },
1349
last_errno => { src => 'last_errno' },
1350
last_error => { src => 'last_error' },
1351
master_host => { src => 'master_host', hdr => 'Master' },
1352
relay_log_file => { src => 'relay_log_file' },
1353
relay_log_pos => { src => 'relay_log_pos' },
1354
relay_log_size => { src => 'relay_log_space', trans => [qw(shorten)] },
1355
relay_master_log_file => { src => 'relay_master_log_file', hdr => 'Master File' },
1356
replicate_do_db => { src => 'replicate_do_db' },
1357
replicate_do_table => { src => 'replicate_do_table' },
1358
replicate_ignore_db => { src => 'replicate_ignore_db' },
1359
replicate_ignore_table => { src => 'replicate_ignore_table' },
1360
replicate_wild_do_table => { src => 'replicate_wild_do_table' },
1361
replicate_wild_ignore_table => { src => 'replicate_wild_ignore_table' },
1362
skip_counter => { src => 'skip_counter' },
1363
slave_sql_running => { src => 'slave_sql_running', hdr => 'On?' },
1364
until_condition => { src => 'until_condition' },
1365
until_log_file => { src => 'until_log_file' },
1366
until_log_pos => { src => 'until_log_pos' },
1367
time_behind_master => { src => 'seconds_behind_master', trans => [ qw(secs_to_time) ] },
1368
bytes_behind_master => { src => 'master_log_file && master_log_file eq relay_master_log_file ? read_master_log_pos - exec_master_log_pos : 0', trans => [qw(shorten)] },
1369
slave_catchup_rate => { src => $exprs{SlaveCatchupRate}, trans => [ qw(set_precision) ] },
1370
slave_open_temp_tables => { src => 'Slave_open_temp_tables' },
1372
visible => [ qw(cxn master_host slave_sql_running time_behind_master slave_catchup_rate slave_open_temp_tables relay_log_pos last_error)],
1373
filters => [ qw( cxn_is_slave ) ],
1374
sort_cols => 'slave_sql_running cxn',
1378
{ col => 'slave_sql_running', op => 'ne', arg => 'Yes', color => 'black on_red' },
1379
{ col => 'time_behind_master', op => '>', arg => 600, color => 'red' },
1380
{ col => 'time_behind_master', op => '>', arg => 60, color => 'yellow' },
1381
{ col => 'time_behind_master', op => '==', arg => 0, color => 'white' },
1387
capt => 'T-Mode Header',
1390
cxn => { src => 'cxn' },
1391
dirty_bufs => { src => $exprs{DirtyBufs}, trans => [qw(percent)] },
1392
history_list_len => { src => 'IB_tx_history_list_len' },
1393
lock_structs => { src => 'IB_tx_num_lock_structs' },
1394
num_txns => { src => $exprs{NumTxns} },
1395
max_txn => { src => $exprs{MaxTxnTime}, trans => [qw(secs_to_time)] },
1396
undo_for => { src => 'IB_tx_purge_undo_for' },
1397
used_bufs => { src => $exprs{BufPoolFill}, trans => [qw(percent)]},
1398
versions => { src => $exprs{OldVersions} },
1400
visible => [ qw(cxn history_list_len versions undo_for dirty_bufs used_bufs num_txns max_txn lock_structs)],
1411
capt => 'Variables & Status',
1413
cols => {}, # Generated from current varset
1414
visible => [], # Generated from current varset
1419
temp => 1, # Do not persist to config file.
1426
capt => 'InnoDB Wait Array',
1429
cxn => { src => 'cxn' },
1430
thread => { src => 'thread' },
1431
waited_at_filename => { src => 'waited_at_filename' },
1432
waited_at_line => { src => 'waited_at_line' },
1433
'time' => { src => 'waited_secs', trans => [ qw(secs_to_time) ] },
1434
request_type => { src => 'request_type' },
1435
lock_mem_addr => { src => 'lock_mem_addr' },
1436
lock_cfile_name => { src => 'lock_cfile_name' },
1437
lock_cline => { src => 'lock_cline' },
1438
writer_thread => { src => 'writer_thread' },
1439
writer_lock_mode => { src => 'writer_lock_mode' },
1440
num_readers => { src => 'num_readers' },
1441
lock_var => { src => 'lock_var' },
1442
waiters_flag => { src => 'waiters_flag' },
1443
last_s_file_name => { src => 'last_s_file_name' },
1444
last_s_line => { src => 'last_s_line' },
1445
last_x_file_name => { src => 'last_x_file_name' },
1446
last_x_line => { src => 'last_x_line' },
1447
cell_waiting => { src => 'cell_waiting' },
1448
cell_event_set => { src => 'cell_event_set' },
1450
visible => [ qw(cxn thread time waited_at_filename waited_at_line request_type num_readers lock_var waiters_flag cell_waiting cell_event_set)],
1452
sort_cols => 'cxn -time',
1460
# Initialize %tbl_meta from %columns and do some checks.
1461
foreach my $table_name ( keys %tbl_meta ) {
1462
my $table = $tbl_meta{$table_name};
1463
my $cols = $table->{cols};
1465
foreach my $col_name ( keys %$cols ) {
1466
my $col_def = $table->{cols}->{$col_name};
1467
die "I can't find a column named '$col_name' for '$table_name'" unless $columns{$col_name};
1468
$columns{$col_name}->{referenced} = 1;
1470
foreach my $prop ( keys %col_props ) {
1471
# Each column gets non-existing values set from %columns or defaults from %col_props.
1472
if ( !$col_def->{$prop} ) {
1474
= defined($columns{$col_name}->{$prop})
1475
? $columns{$col_name}->{$prop}
1476
: $col_props{$prop};
1480
# Ensure transformations and aggregate functions are valid
1481
die "Unknown aggregate function '$col_def->{agg}' "
1482
. "for column '$col_name' in table '$table_name'"
1483
unless exists $agg_funcs{$col_def->{agg}};
1484
foreach my $trans ( @{$col_def->{trans}} ) {
1485
die "Unknown transformation '$trans' "
1486
. "for column '$col_name' in table '$table_name'"
1487
unless exists $trans_funcs{$trans};
1491
# Ensure each column in visible and group_by exists in cols
1492
foreach my $place ( qw(visible group_by) ) {
1493
foreach my $col_name ( @{$table->{$place}} ) {
1494
if ( !exists $cols->{$col_name} ) {
1495
die "Column '$col_name' is listed in '$place' for '$table_name', but doesn't exist";
1500
# Compile sort and color subroutines
1501
$table->{sort_func} = make_sort_func($table);
1502
$table->{color_func} = make_color_func($table);
1505
# This is for code cleanup:
1507
my @unused_cols = grep { !$columns{$_}->{referenced} } sort keys %columns;
1508
if ( @unused_cols ) {
1509
die "The following columns are not used: "
1510
. join(' ', @unused_cols);
1514
# ###########################################################################
1515
# Operating modes {{{3
1516
# ###########################################################################
1519
hdr => 'InnoDB Buffers',
1521
note => 'Shows buffer info from InnoDB',
1524
action => sub { toggle_config('status_inc') },
1525
label => 'Toggle incremental status display',
1528
display_sub => \&display_B,
1531
one_connection => 0,
1532
tables => [qw(buffer_pool page_statistics insert_buffers adaptive_hash_index)],
1533
visible_tables => [qw(buffer_pool page_statistics insert_buffers adaptive_hash_index)],
1536
hdr => 'Command Summary',
1538
note => 'Shows relative magnitude of variables',
1541
action => sub { get_config_interactive('cmd_filter') },
1542
label => 'Choose variable prefix',
1545
display_sub => \&display_C,
1548
one_connection => 0,
1549
tables => [qw(cmd_summary)],
1550
visible_tables => [qw(cmd_summary)],
1553
hdr => 'InnoDB Deadlocks',
1555
note => 'View InnoDB deadlock information',
1558
action => sub { edit_table('deadlock_transactions') },
1559
label => 'Choose visible columns',
1562
action => \&create_deadlock,
1563
label => 'Wipe deadlock status info by creating a deadlock',
1566
display_sub => \&display_D,
1569
one_connection => 0,
1570
tables => [qw(deadlock_transactions deadlock_locks)],
1571
visible_tables => [qw(deadlock_transactions deadlock_locks)],
1574
hdr => 'InnoDB FK Err',
1576
note => 'View the latest InnoDB foreign key error',
1578
display_sub => \&display_F,
1581
one_connection => 1,
1582
tables => [qw(fk_error)],
1583
visible_tables => [qw(fk_error)],
1586
hdr => 'InnoDB I/O Info',
1588
note => 'Shows I/O info (i/o, log...) from InnoDB',
1591
action => sub { toggle_config('status_inc') },
1592
label => 'Toggle incremental status display',
1595
display_sub => \&display_I,
1598
one_connection => 0,
1599
tables => [qw(io_threads pending_io file_io_misc log_statistics)],
1600
visible_tables => [qw(io_threads pending_io file_io_misc log_statistics)],
1605
note => 'Shows transaction locks',
1608
action => sub { send_cmd_to_servers('CREATE TABLE IF NOT EXISTS test.innodb_lock_monitor(a int) ENGINE=InnoDB', 0, '', []); },
1609
label => 'Start the InnoDB Lock Monitor',
1612
action => sub { send_cmd_to_servers('DROP TABLE IF EXISTS test.innodb_lock_monitor', 0, '', []); },
1613
label => 'Stop the InnoDB Lock Monitor',
1616
display_sub => \&display_L,
1619
one_connection => 0,
1620
tables => [qw(innodb_locks)],
1621
visible_tables => [qw(innodb_locks)],
1624
hdr => 'Replication Status',
1626
note => 'Shows replication (master and slave) status',
1629
action => sub { send_cmd_to_servers('START SLAVE', 0, 'START SLAVE SQL_THREAD UNTIL MASTER_LOG_FILE = ?, MASTER_LOG_POS = ?', []); },
1630
label => 'Start slave(s)',
1633
action => sub { toggle_config('status_inc') },
1634
label => 'Toggle incremental status display',
1637
action => sub { send_cmd_to_servers('STOP SLAVE', 0, '', []); },
1638
label => 'Stop slave(s)',
1641
action => sub { purge_master_logs() },
1642
label => 'Purge unused master logs',
1645
display_sub => \&display_M,
1648
one_connection => 0,
1649
tables => [qw(slave_sql_status slave_io_status master_status)],
1650
visible_tables => [qw(slave_sql_status slave_io_status master_status)],
1653
hdr => 'Open Tables',
1655
note => 'Shows open tables in MySQL',
1658
action => sub { reverse_sort('open_tables'); },
1659
label => 'Reverse sort order',
1662
action => sub { choose_sort_cols('open_tables'); },
1663
label => "Choose sort column",
1666
display_sub => \&display_O,
1669
one_connection => 0,
1670
tables => [qw(open_tables)],
1671
visible_tables => [qw(open_tables)],
1674
hdr => 'Query List',
1676
note => 'Shows queries from SHOW FULL PROCESSLIST',
1679
action => sub { toggle_filter('processlist', 'hide_self') },
1680
label => 'Toggle the innotop process',
1683
action => sub { edit_table('processlist') },
1684
label => 'Choose visible columns',
1687
action => sub { analyze_query('e'); },
1688
label => "Explain a thread's query",
1691
action => sub { analyze_query('f'); },
1692
label => "Show a thread's full query",
1695
action => sub { toggle_visible_table('Q', 'q_header') },
1696
label => 'Toggle the header on and off',
1699
action => sub { toggle_filter('processlist', 'hide_inactive') },
1700
label => 'Toggle idle processes',
1703
action => sub { kill_query('CONNECTION') },
1704
label => "Kill a query's connection",
1707
action => sub { reverse_sort('processlist'); },
1708
label => 'Reverse sort order',
1711
action => sub { choose_sort_cols('processlist'); },
1712
label => "Change the display's sort column",
1715
action => sub { kill_query('QUERY') },
1716
label => "Kill a query",
1719
display_sub => \&display_Q,
1722
one_connection => 0,
1723
tables => [qw(q_header processlist)],
1724
visible_tables => [qw(q_header processlist)],
1727
hdr => 'InnoDB Row Ops',
1729
note => 'Shows InnoDB row operation and semaphore info',
1732
action => sub { toggle_config('status_inc') },
1733
label => 'Toggle incremental status display',
1736
display_sub => \&display_R,
1739
one_connection => 0,
1740
tables => [qw(row_operations row_operation_misc semaphores wait_array)],
1741
visible_tables => [qw(row_operations row_operation_misc semaphores wait_array)],
1744
hdr => 'Variables & Status',
1746
note => 'Shows query load statistics a la vmstat',
1749
action => sub { switch_var_set('S_set', 1) },
1750
label => 'Switch to next variable set',
1753
action => sub { switch_var_set('S_set', -1) },
1754
label => 'Switch to prev variable set',
1758
choose_var_set('S_set');
1761
label => "Choose which set to display",
1764
action => \&edit_current_var_set,
1765
label => 'Edit the current set of variables',
1768
action => sub { $clear_screen_sub->(); toggle_config('status_inc') },
1769
label => 'Toggle incremental status display',
1772
action => sub { set_display_precision(-1) },
1773
label => 'Decrease fractional display precision',
1776
action => sub { set_display_precision(1) },
1777
label => 'Increase fractional display precision',
1780
action => sub { set_s_mode('g') },
1781
label => 'Switch to graph (tload) view',
1784
action => sub { set_s_mode('s') },
1785
label => 'Switch to standard (vmstat) view',
1788
action => sub { set_s_mode('v') },
1789
label => 'Switch to pivoted view',
1792
display_sub => \&display_S,
1793
no_clear_screen => 1,
1796
one_connection => 0,
1797
tables => [qw(var_status)],
1798
visible_tables => [qw(var_status)],
1801
hdr => 'InnoDB Txns',
1803
note => 'Shows InnoDB transactions in top-like format',
1806
action => sub { toggle_filter('innodb_transactions', 'hide_self') },
1807
label => 'Toggle the innotop process',
1810
action => sub { edit_table('innodb_transactions') },
1811
label => 'Choose visible columns',
1814
action => sub { analyze_query('e'); },
1815
label => "Explain a thread's query",
1818
action => sub { analyze_query('f'); },
1819
label => "Show a thread's full query",
1822
action => sub { toggle_visible_table('T', 't_header') },
1823
label => 'Toggle the header on and off',
1826
action => sub { toggle_filter('innodb_transactions', 'hide_inactive') },
1827
label => 'Toggle inactive transactions',
1830
action => sub { kill_query('CONNECTION') },
1831
label => "Kill a transaction's connection",
1834
action => sub { reverse_sort('innodb_transactions'); },
1835
label => 'Reverse sort order',
1838
action => sub { choose_sort_cols('innodb_transactions'); },
1839
label => "Change the display's sort column",
1842
action => sub { kill_query('QUERY') },
1843
label => "Kill a query",
1846
display_sub => \&display_T,
1849
one_connection => 0,
1850
tables => [qw(t_header innodb_transactions)],
1851
visible_tables => [qw(t_header innodb_transactions)],
1855
# ###########################################################################
1856
# Global key mappings {{{3
1857
# Keyed on a single character, which is read from the keyboard. Uppercase
1858
# letters switch modes. Lowercase letters access commands when in a mode.
1859
# These can be overridden by action_for in %modes.
1860
# ###########################################################################
1863
action => \&edit_configuration,
1864
label => 'Edit configuration settings',
1867
action => \&display_help,
1868
label => 'Show help',
1871
action => \&display_license,
1872
label => 'Show license and warranty',
1875
action => \&edit_table,
1876
label => "Edit the displayed table(s)",
1879
action => \&choose_server_groups,
1880
label => 'Select/create server groups',
1883
action => \&choose_servers,
1884
label => 'Select/create server connections',
1887
action => \&add_quick_filter,
1888
label => 'Quickly filter what you see',
1891
action => \&clear_quick_filters,
1892
label => 'Clear quick-filters',
1895
action => \&choose_filters,
1896
label => 'Choose and edit table filters',
1899
action => \&next_server_group,
1900
label => 'Switch to the next server group',
1904
action => \&toggle_aggregate,
1905
label => 'Toggle aggregation',
1907
# TODO: can these be auto-generated from %modes?
1909
action => sub { switch_mode('B') },
1913
action => sub { switch_mode('C') },
1917
action => sub { switch_mode('D') },
1921
action => sub { switch_mode('F') },
1925
action => sub { switch_mode('I') },
1929
action => sub { switch_mode('L') },
1933
action => sub { switch_mode('M') },
1937
action => sub { switch_mode('O') },
1941
action => sub { switch_mode('Q') },
1945
action => sub { switch_mode('R') },
1949
action => \&start_S_mode,
1953
action => sub { switch_mode('T') },
1957
action => sub { get_config_interactive('interval') },
1958
label => 'Change refresh interval',
1960
n => { action => \&next_server, label => 'Switch to the next connection' },
1961
p => { action => \&pause, label => 'Pause innotop', },
1962
q => { action => \&finish, label => 'Quit innotop', },
1965
# ###########################################################################
1966
# Sleep times after certain statements {{{3
1967
# ###########################################################################
1968
my %stmt_sleep_time_for = ();
1970
# ###########################################################################
1971
# Config editor key mappings {{{3
1972
# ###########################################################################
1973
my %cfg_editor_action = (
1975
note => 'Edit columns, etc in the displayed table(s)',
1976
func => \&edit_table,
1979
note => 'Edit general configuration',
1980
func => \&edit_configuration_variables,
1983
note => 'Edit row-coloring rules',
1984
func => \&edit_color_rules,
1987
note => 'Manage plugins',
1988
func => \&edit_plugins,
1991
note => 'Edit server groups',
1992
func => \&edit_server_groups,
1995
note => 'Edit SQL statement sleep delays',
1996
func => \&edit_stmt_sleep_times,
1999
note => 'Choose which table(s) to display in this mode',
2000
func => \&choose_mode_tables,
2004
# ###########################################################################
2005
# Color editor key mappings {{{3
2006
# ###########################################################################
2007
my %color_editor_action = (
2009
note => 'Create a new color rule',
2011
my ( $tbl, $idx ) = @_;
2012
my $meta = $tbl_meta{$tbl};
2014
$clear_screen_sub->();
2018
'Choose the target column for the rule',
2020
sub { return keys %{$meta->{cols}} },
2021
{ map { $_ => $meta->{cols}->{$_}->{label} } keys %{$meta->{cols}} });
2023
( $col ) = grep { $_ } split(/\W+/, $col);
2024
return $idx unless $col && exists $meta->{cols}->{$col};
2026
$clear_screen_sub->();
2030
'Choose the comparison operator for the rule',
2032
sub { return keys %comp_ops },
2033
{ map { $_ => $comp_ops{$_} } keys %comp_ops } );
2036
return $idx unless $op && exists $comp_ops{$op};
2040
$arg = prompt('Specify an argument for the comparison');
2041
} until defined $arg;
2045
$color = prompt_list(
2046
'Choose the color(s) the row should be when the rule matches',
2048
sub { return keys %ansicolors },
2049
{ map { $_ => $_ } keys %ansicolors } );
2050
} until defined $color;
2051
$color = join(' ', unique(grep { exists $ansicolors{$_} } split(/\W+/, $color)));
2052
return $idx unless $color;
2054
push @{$tbl_meta{$tbl}->{colors}}, {
2060
$tbl_meta{$tbl}->{cust}->{colors} = 1;
2066
note => 'Remove the selected rule',
2068
my ( $tbl, $idx ) = @_;
2069
my @rules = @{ $tbl_meta{$tbl}->{colors} };
2070
return 0 unless @rules > 0 && $idx < @rules && $idx >= 0;
2071
splice(@{$tbl_meta{$tbl}->{colors}}, $idx, 1);
2072
$tbl_meta{$tbl}->{cust}->{colors} = 1;
2073
return $idx == @rules ? $#rules : $idx;
2077
note => 'Move highlight down one',
2079
my ( $tbl, $idx ) = @_;
2080
my $num_rules = scalar @{$tbl_meta{$tbl}->{colors}};
2081
return ($idx + 1) % $num_rules;
2085
note => 'Move highlight up one',
2087
my ( $tbl, $idx ) = @_;
2088
my $num_rules = scalar @{$tbl_meta{$tbl}->{colors}};
2089
return ($idx - 1) % $num_rules;
2093
note => 'Move selected rule up one',
2095
my ( $tbl, $idx ) = @_;
2096
my $meta = $tbl_meta{$tbl};
2097
my $dest = $idx == 0 ? scalar(@{$meta->{colors}} - 1) : $idx - 1;
2098
my $temp = $meta->{colors}->[$idx];
2099
$meta->{colors}->[$idx] = $meta->{colors}->[$dest];
2100
$meta->{colors}->[$dest] = $temp;
2101
$meta->{cust}->{colors} = 1;
2106
note => 'Move selected rule down one',
2108
my ( $tbl, $idx ) = @_;
2109
my $meta = $tbl_meta{$tbl};
2110
my $dest = $idx == scalar(@{$meta->{colors}} - 1) ? 0 : $idx + 1;
2111
my $temp = $meta->{colors}->[$idx];
2112
$meta->{colors}->[$idx] = $meta->{colors}->[$dest];
2113
$meta->{colors}->[$dest] = $temp;
2114
$meta->{cust}->{colors} = 1;
2120
# ###########################################################################
2121
# Plugin editor key mappings {{{3
2122
# ###########################################################################
2123
my %plugin_editor_action = (
2125
note => 'Toggle selected plugin active/inactive',
2127
my ( $plugins, $idx ) = @_;
2128
my $plugin = $plugins->[$idx];
2129
$plugin->{active} = $plugin->{active} ? 0 : 1;
2134
note => 'Move highlight down one',
2136
my ( $plugins, $idx ) = @_;
2137
return ($idx + 1) % scalar(@$plugins);
2141
note => 'Move highlight up one',
2143
my ( $plugins, $idx ) = @_;
2144
return $idx == 0 ? @$plugins - 1 : $idx - 1;
2149
# ###########################################################################
2150
# Table editor key mappings {{{3
2151
# ###########################################################################
2152
my %tbl_editor_action = (
2154
note => 'Add a column to the table',
2156
my ( $tbl, $col ) = @_;
2157
my @visible_cols = @{ $tbl_meta{$tbl}->{visible} };
2158
my %all_cols = %{ $tbl_meta{$tbl}->{cols} };
2159
delete @all_cols{@visible_cols};
2160
my $choice = prompt_list(
2163
sub { return keys %all_cols; },
2164
{ map { $_ => $all_cols{$_}->{label} || $all_cols{$_}->{hdr} } keys %all_cols });
2165
if ( $all_cols{$choice} ) {
2166
push @{$tbl_meta{$tbl}->{visible}}, $choice;
2167
$tbl_meta{$tbl}->{cust}->{visible} = 1;
2174
note => 'Create a new column and add it to the table',
2176
my ( $tbl, $col ) = @_;
2178
$clear_screen_sub->();
2179
print word_wrap("Choose a name for the column. This name is not displayed, and is used only "
2180
. "for internal reference. It can contain only lowercase letters, numbers, "
2181
. "and underscores.");
2184
$col = prompt("Enter column name");
2185
$col = '' if $col =~ m/[^a-z0-9_]/;
2188
$clear_screen_sub->();
2191
$hdr = prompt("Enter column header");
2194
$clear_screen_sub->();
2195
print "Choose a source for the column's data\n\n";
2196
my ( $src, $sub, $err );
2199
print "Error: $err\n\n";
2201
$src = prompt("Enter column source");
2203
( $sub, $err ) = compile_expr($src);
2207
# TODO: this duplicates %col_props.
2208
$tbl_meta{$tbl}->{cols}->{$col} = {
2213
label => 'User-defined',
2225
$tbl_meta{$tbl}->{visible} = [ unique(@{$tbl_meta{$tbl}->{visible}}, $col) ];
2226
$tbl_meta{$tbl}->{cust}->{visible} = 1;
2231
note => 'Remove selected column',
2233
my ( $tbl, $col ) = @_;
2234
my @visible_cols = @{ $tbl_meta{$tbl}->{visible} };
2236
return $col unless @visible_cols > 1;
2237
while ( $visible_cols[$idx] ne $col ) {
2240
$tbl_meta{$tbl}->{visible} = [ grep { $_ ne $col } @visible_cols ];
2241
$tbl_meta{$tbl}->{cust}->{visible} = 1;
2242
return $idx == $#visible_cols ? $visible_cols[$idx - 1] : $visible_cols[$idx + 1];
2246
note => 'Edit selected column',
2248
# TODO: make this editor hotkey-driven and give readline support.
2249
my ( $tbl, $col ) = @_;
2250
$clear_screen_sub->();
2251
my $meta = $tbl_meta{$tbl}->{cols}->{$col};
2252
my @prop = qw(hdr label src just num minw maxw trans agg); # TODO redundant
2256
# Do what the user asked...
2257
if ( $answer && grep { $_ eq $answer } @prop ) {
2258
# Some properties are arrays, others scalars.
2259
my $ini = ref $col_props{$answer} ? join(' ', @{$meta->{$answer}}) : $meta->{$answer};
2260
my $val = prompt("New value for $answer", undef, $ini);
2261
$val = [ split(' ', $val) ] if ref($col_props{$answer});
2262
if ( $answer eq 'trans' ) {
2263
$val = [ unique(grep{ exists $trans_funcs{$_} } @$val) ];
2265
@{$meta}{$answer, 'user', 'tbl' } = ( $val, 1, $tbl );
2268
my @display_lines = (
2270
"You are editing column $tbl.$col.\n",
2273
push @display_lines, create_table2(
2275
{ map { $_ => $_ } @prop },
2276
{ map { $_ => ref $meta->{$_} eq 'ARRAY' ? join(' ', @{$meta->{$_}})
2277
: ref $meta->{$_} ? '[expression code]'
2282
draw_screen(\@display_lines, { raw => 1 });
2283
print "\n\n"; # One to add space, one to clear readline artifacts
2284
$answer = prompt('Edit what? (q to quit)');
2285
} while ( $answer ne 'q' );
2291
note => 'Move highlight down one',
2293
my ( $tbl, $col ) = @_;
2294
my @visible_cols = @{ $tbl_meta{$tbl}->{visible} };
2296
while ( $visible_cols[$idx] ne $col ) {
2299
return $visible_cols[ ($idx + 1) % @visible_cols ];
2303
note => 'Move highlight up one',
2305
my ( $tbl, $col ) = @_;
2306
my @visible_cols = @{ $tbl_meta{$tbl}->{visible} };
2308
while ( $visible_cols[$idx] ne $col ) {
2311
return $visible_cols[ $idx - 1 ];
2315
note => 'Move selected column up one',
2317
my ( $tbl, $col ) = @_;
2318
my $meta = $tbl_meta{$tbl};
2319
my @visible_cols = @{$meta->{visible}};
2321
while ( $visible_cols[$idx] ne $col ) {
2325
$visible_cols[$idx] = $visible_cols[$idx - 1];
2326
$visible_cols[$idx - 1] = $col;
2327
$meta->{visible} = \@visible_cols;
2330
shift @{$meta->{visible}};
2331
push @{$meta->{visible}}, $col;
2333
$meta->{cust}->{visible} = 1;
2338
note => 'Move selected column down one',
2340
my ( $tbl, $col ) = @_;
2341
my $meta = $tbl_meta{$tbl};
2342
my @visible_cols = @{$meta->{visible}};
2344
while ( $visible_cols[$idx] ne $col ) {
2347
if ( $idx == $#visible_cols ) {
2348
unshift @{$meta->{visible}}, $col;
2349
pop @{$meta->{visible}};
2352
$visible_cols[$idx] = $visible_cols[$idx + 1];
2353
$visible_cols[$idx + 1] = $col;
2354
$meta->{visible} = \@visible_cols;
2356
$meta->{cust}->{visible} = 1;
2361
note => 'Choose filters',
2363
my ( $tbl, $col ) = @_;
2364
choose_filters($tbl);
2369
note => 'Edit color rules',
2371
my ( $tbl, $col ) = @_;
2372
edit_color_rules($tbl);
2377
note => 'Choose sort columns',
2379
my ( $tbl, $col ) = @_;
2380
choose_sort_cols($tbl);
2385
note => 'Choose group-by (aggregate) columns',
2387
my ( $tbl, $col ) = @_;
2388
choose_group_cols($tbl);
2394
# ###########################################################################
2395
# Global variables and environment {{{2
2396
# ###########################################################################
2398
my @this_term_size; # w_chars, h_chars, w_pix, h_pix
2399
my @last_term_size; # w_chars, h_chars, w_pix, h_pix
2401
my $windows = $OSNAME =~ m/MSWin/;
2403
my $MAX_ULONG = 4294967295; # 2^32-1
2404
my $num_regex = qr/^[+-]?(?=\d|\.)\d*(?:\.\d+)?(?:E[+-]?\d+|)$/i;
2405
my $int_regex = qr/^\d+$/;
2406
my $bool_regex = qr/^[01]$/;
2408
my $file = undef; # File to watch for InnoDB monitor output
2409
my $file_mtime = undef; # Status of watched file
2410
my $file_data = undef; # Last chunk of text read from file
2411
my $innodb_parser = InnoDBParser->new;
2413
my $nonfatal_errs = join('|',
2414
'Access denied for user',
2415
'Unknown MySQL server host',
2417
'Can\'t connect to local MySQL server through socket',
2418
'Can\'t connect to MySQL server on',
2419
'MySQL server has gone away',
2420
'Cannot call SHOW INNODB STATUS',
2426
require Term::ReadLine;
2427
$term = Term::ReadLine->new('innotop');
2430
# Stores status, variables, innodb status, master/slave status etc.
2431
# Keyed on connection name. Each entry is a hashref of current and past data sets,
2432
# keyed on clock tick.
2434
my %info_gotten = (); # Which things have been retrieved for the current clock tick.
2436
# Stores info on currently displayed queries: cxn, connection ID, query text.
2437
my @current_queries;
2439
my $lines_printed = 0;
2440
my $clock = 0; # Incremented with every wake-sleep cycle
2441
my $clearing_deadlocks = 0;
2443
# Find the home directory; it's different on different OSes.
2444
my $homepath = $ENV{HOME} || $ENV{HOMEPATH} || $ENV{USERPROFILE} || '.';
2446
# If terminal coloring is available, use it. The only function I want from
2447
# the module is the colored() function.
2451
require Win32::Console::ANSI;
2453
require Term::ANSIColor;
2454
import Term::ANSIColor qw(colored);
2458
if ( $EVAL_ERROR || $opts{n} ) {
2459
# If there was an error, manufacture my own colored() function that does no
2461
*colored = sub { pop @_; @_; };
2465
$clear_screen_sub = sub {};
2467
elsif ( $windows ) {
2468
$clear_screen_sub = sub { $lines_printed = 0; system("cls") };
2471
my $clear = `clear`;
2472
$clear_screen_sub = sub { $lines_printed = 0; print $clear };
2475
# ###########################################################################
2476
# Config storage. {{{2
2477
# ###########################################################################
2481
note => 'Whether to use terminal coloring',
2487
note => 'Prefix for values in C mode',
2491
val => "$homepath/.innotop/plugins",
2492
note => 'Directory where plugins can be found',
2497
note => 'Show the % symbol after percentages',
2503
note => 'Disable SHOW INNODB STATUS',
2509
note => 'What to display in S mode: graph, status, pivoted status',
2515
note => 'Connection timeout for keeping unused connections alive',
2521
note => 'Character for drawing graphs',
2525
show_cxn_errors_in_tbl => {
2527
note => 'Whether to display connection errors as rows in the table',
2533
note => 'Whether to show column headers',
2537
show_cxn_errors => {
2539
note => 'Whether to print connection errors to STDOUT',
2545
note => 'Whether the config file is read-only',
2551
note => 'Whether to show GLOBAL variables and status',
2555
header_highlight => {
2557
note => 'How to highlight table column headers',
2559
pat => qr/^(?:bold|underline)$/,
2561
display_table_captions => {
2563
note => 'Whether to put captions on tables',
2569
note => 'What type of characters should be displayed in queries (ascii, unicode, none)',
2571
pat => qr/^(?:ascii|unicode|none)$/,
2575
note => 'Whether to auto-wipe InnoDB deadlocks',
2581
note => '[Win32] Max window height',
2587
note => 'Debug mode (more verbose errors, uses more memory)',
2593
note => 'How many digits to show in fractional numbers and percents',
2597
val => "$homepath/.innotop/core_dump",
2598
note => 'A debug file in case you are interested in error output',
2603
note => 'Whether to show the status bar in the display',
2608
note => "Which mode to start in",
2613
note => 'Whether to show raw or incremental values for status variables',
2618
pat => qr/^(?:(?:\d*?[1-9]\d*(?:\.\d*)?)|(?:\d*\.\d*?[1-9]\d*))$/,
2619
note => "The interval at which the display will be refreshed. Fractional values allowed.",
2621
num_status_sets => {
2624
note => 'How many sets of STATUS and VARIABLES values to show',
2630
note => 'Which set of variables to display in S (Variables & Status) mode',
2635
# ###########################################################################
2636
# Config file sections {{{2
2637
# The configuration file is broken up into sections like a .ini file. This
2638
# variable defines those sections and the subroutines responsible for reading
2640
# ###########################################################################
2641
my %config_file_sections = (
2643
reader => \&load_config_plugins,
2644
writer => \&save_config_plugins,
2647
reader => \&load_config_group_by,
2648
writer => \&save_config_group_by,
2651
reader => \&load_config_filters,
2652
writer => \&save_config_filters,
2655
reader => \&load_config_active_filters,
2656
writer => \&save_config_active_filters,
2659
reader => \&load_config_visible_tables,
2660
writer => \&save_config_visible_tables,
2663
reader => \&load_config_sort_cols,
2664
writer => \&save_config_sort_cols,
2667
reader => \&load_config_active_columns,
2668
writer => \&save_config_active_columns,
2671
reader => \&load_config_tbl_meta,
2672
writer => \&save_config_tbl_meta,
2675
reader => \&load_config_config,
2676
writer => \&save_config_config,
2679
reader => \&load_config_connections,
2680
writer => \&save_config_connections,
2682
active_connections => {
2683
reader => \&load_config_active_connections,
2684
writer => \&save_config_active_connections,
2687
reader => \&load_config_server_groups,
2688
writer => \&save_config_server_groups,
2690
active_server_groups => {
2691
reader => \&load_config_active_server_groups,
2692
writer => \&save_config_active_server_groups,
2694
max_values_seen => {
2695
reader => \&load_config_mvs,
2696
writer => \&save_config_mvs,
2699
reader => \&load_config_varsets,
2700
writer => \&save_config_varsets,
2703
reader => \&load_config_colors,
2704
writer => \&save_config_colors,
2706
stmt_sleep_times => {
2707
reader => \&load_config_stmt_sleep_times,
2708
writer => \&save_config_stmt_sleep_times,
2712
# Config file sections have some dependencies, so they have to be read/written in order.
2713
my @ordered_config_file_sections = qw(general plugins filters active_filters tbl_meta
2714
connections active_connections server_groups active_server_groups max_values_seen
2715
active_columns sort_cols visible_tables varsets colors stmt_sleep_times
2718
# All events for which plugins may register themselves. Entries are arrayrefs.
2719
my %event_listener_for = map { $_ => [] }
2722
set_to_tbl_pre_filter set_to_tbl_pre_sort set_to_tbl_pre_group
2723
set_to_tbl_pre_colorize set_to_tbl_pre_transform set_to_tbl_pre_pivot
2724
set_to_tbl_pre_create set_to_tbl_post_create
2728
# All variables to which plugins have access.
2729
my %pluggable_vars = (
2730
action_for => \%action_for,
2731
agg_funcs => \%agg_funcs,
2733
connections => \%connections,
2735
filters => \%filters,
2737
server_groups => \%server_groups,
2738
tbl_meta => \%tbl_meta,
2739
trans_funcs => \%trans_funcs,
2740
var_sets => \%var_sets,
2743
# ###########################################################################
2744
# Contains logic to generate prepared statements for a given function for a
2745
# given DB connection. Returns a $sth.
2746
# ###########################################################################
2747
my %stmt_maker_for = (
2748
INNODB_STATUS => sub {
2750
return $dbh->prepare(version_ge( $dbh, '5.0.0' )
2751
? 'SHOW ENGINE INNODB STATUS'
2752
: 'SHOW INNODB STATUS');
2754
SHOW_VARIABLES => sub {
2756
return $dbh->prepare($config{global}->{val} && version_ge( $dbh, '4.0.3' )
2757
? 'SHOW GLOBAL VARIABLES'
2758
: 'SHOW VARIABLES');
2760
SHOW_STATUS => sub {
2762
return $dbh->prepare($config{global}->{val} && version_ge( $dbh, '5.0.2' )
2763
? 'SHOW GLOBAL STATUS'
2768
return $dbh->prepare(version_ge( $dbh, '5.0.0' )
2772
SHOW_MASTER_LOGS => sub {
2774
return $dbh->prepare('SHOW MASTER LOGS');
2776
SHOW_MASTER_STATUS => sub {
2778
return $dbh->prepare('SHOW MASTER STATUS');
2780
SHOW_SLAVE_STATUS => sub {
2782
return $dbh->prepare('SHOW SLAVE STATUS');
2784
KILL_CONNECTION => sub {
2786
return $dbh->prepare(version_ge( $dbh, '5.0.0' )
2787
? 'KILL CONNECTION ?'
2790
OPEN_TABLES => sub {
2792
return version_ge($dbh, '4.0.0')
2793
? $dbh->prepare('SHOW OPEN TABLES')
2796
PROCESSLIST => sub {
2798
return $dbh->prepare('SHOW FULL PROCESSLIST');
2806
# ###########################################################################
2807
# Run the program {{{1
2808
# ###########################################################################
2810
# This config variable is only useful for MS Windows because its terminal
2811
# can't tell how tall it is.
2813
delete $config{max_height};
2816
# Try to lower my priority.
2817
eval { setpriority(0, 0, getpriority(0, 0) + 10); };
2819
# Print stuff to the screen immediately, don't wait for a newline.
2820
$OUTPUT_AUTOFLUSH = 1;
2822
# Clear the screen and load the configuration.
2823
$clear_screen_sub->();
2825
post_process_tbl_meta();
2827
# Make sure no changes are written to config file in non-interactive mode.
2829
$config{readonly}->{val} = 1;
2834
# Open the file for InnoDB status
2836
my $filename = shift @ARGV;
2837
open $file, "<", $filename
2838
or die "Cannot open '$filename': $OS_ERROR";
2841
# In certain modes we might have to collect data for two cycles
2842
# before printing anything out, so we need to bump up the count one.
2843
if ( $opts{n} && $opts{count} && $config{status_inc}->{val}
2844
&& $config{mode}->{val} =~ m/[S]/ )
2851
my $mode = $config{mode}->{val} || 'T';
2852
if ( !$modes{$mode} ) {
2853
die "Mode '$mode' doesn't exist; try one of these:\n"
2854
. join("\n", map { " $_ $modes{$_}->{hdr}" } sort keys %modes)
2859
@last_term_size = @this_term_size;
2860
@this_term_size = Term::ReadKey::GetTerminalSize(\*STDOUT);
2862
$this_term_size[0]--;
2864
= min($this_term_size[1], $config{max_height}->{val});
2866
die("Can't read terminal size") unless @this_term_size;
2869
# If there's no connection to a database server, we need to fix that...
2870
if ( !%connections ) {
2871
print "You have not defined any database connections.\n\n";
2875
# See whether there are any connections defined for this mode. If there's only one
2876
# connection total, assume the user wants to just use innotop for a single server
2877
# and don't ask which server to connect to. Also, if we're monitoring from a file,
2878
# we just use the first connection.
2879
if ( !get_connections() ) {
2880
if ( $file || 1 == scalar keys %connections ) {
2881
$modes{$config{mode}->{val}}->{connections} = [ keys %connections ];
2884
choose_connections();
2888
# Term::ReadLine might have re-set $OUTPUT_AUTOFLUSH.
2889
$OUTPUT_AUTOFLUSH = 1;
2892
my $sets = $config{num_status_sets}->{val};
2893
foreach my $store ( values %vars ) {
2894
delete @{$store}{ grep { $_ < $clock - $sets } keys %$store };
2898
# Call the subroutine to display this mode.
2899
$modes{$mode}->{display_sub}->();
2901
# It may be time to quit now.
2902
if ( $opts{count} && $clock >= $opts{count} ) {
2908
sleep($config{interval}->{val});
2912
$char = ReadKey($config{interval}->{val});
2916
# Handle whatever action the key indicates.
2921
if ( $EVAL_ERROR ) {
2922
core_dump( $EVAL_ERROR );
2927
# Mode functions{{{2
2931
$config{mode}->{val} = $mode;
2934
# Prompting functions {{{2
2936
# Prompts the user for a value, given a question, initial value,
2937
# a completion function and a hashref of hints.
2939
die "Can't call in non-interactive mode" if $opts{n};
2940
my ( $question, $init, $completion, $hints ) = @_;
2942
# Figure out how wide the table will be
2943
my $max_name = max(map { length($_) } keys %$hints );
2946
my @meta_rows = create_table2(
2947
[ sort keys %$hints ],
2948
{ map { $_ => $_ } keys %$hints },
2949
{ map { $_ => trunc($hints->{$_}, $this_term_size[0] - $max_name) } keys %$hints },
2951
if (@meta_rows > 10) {
2952
# Try to split and stack the meta rows next to each other
2953
my $split = int(@meta_rows / 2);
2954
@meta_rows = stack_next(
2955
[@meta_rows[0..$split - 1]],
2956
[@meta_rows[$split..$#meta_rows]],
2962
map { ref $_ ? colored(@$_) : $_ } create_caption('Choose from', @meta_rows), ''),
2965
$term->Attribs->{completion_function} = $completion;
2966
my $answer = $term->readline("$question: ", $init);
2967
$OUTPUT_AUTOFLUSH = 1;
2968
$answer = '' if !defined($answer);
2969
$answer =~ s/\s+$//;
2974
# Prints out a prompt and reads from the keyboard, then validates with the
2975
# validation regex until the input is correct.
2977
die "Can't call in non-interactive mode" if $opts{n};
2978
my ( $prompt, $regex, $init, $completion ) = @_;
2982
if ( $completion ) {
2983
$term->Attribs->{completion_function} = $completion;
2985
$response = $term->readline("$prompt: ", $init);
2986
if ( $regex && $response !~ m/$regex/ ) {
2987
print "Invalid response.\n\n";
2992
} while ( !$success );
2993
$OUTPUT_AUTOFLUSH = 1;
2994
$response =~ s/\s+$//;
2998
# prompt_noecho {{{3
2999
# Unfortunately, suppressing echo with Term::ReadLine isn't reliable; the user might not
3000
# have that library, or it might not support that feature.
3002
my ( $prompt ) = @_;
3003
print colored("$prompt: ", 'underline');
3006
$response = <STDIN>;
3012
# do_key_action {{{3
3013
# Depending on whether a key was read, do something. Keys have certain
3014
# actions defined in lookup tables. Each mode may have its own lookup table,
3015
# which trumps the global table -- so keys can be context-sensitive. The key
3016
# may be read and written in a subroutine, so it's a global.
3018
if ( defined $char ) {
3019
my $mode = $config{mode}->{val};
3021
= defined($modes{$mode}->{action_for}->{$char})
3022
? $modes{$mode}->{action_for}->{$char}->{action}
3023
: defined($action_for{$char})
3024
? $action_for{$char}->{action}
3032
die "Can't call in non-interactive mode" if $opts{n};
3034
print defined($msg) ? "\n$msg" : "\nPress any key to continue";
3036
my $char = ReadKey(0);
3044
$tbl_meta{$tbl}->{sort_dir} *= -1;
3048
# Selects connection(s). If the mode (or argument list) has only one, returns
3049
# it without prompt.
3051
my ( $prompt, @cxns ) = @_;
3053
@cxns = get_connections();
3058
my $choices = prompt_list(
3061
sub{ return @cxns },
3062
{ map { $_ => $connections{$_}->{dsn} } @cxns });
3063
my @result = unique(grep { my $a = $_; grep { $_ eq $a } @cxns } split(/\s+/, $choices));
3068
# Kills a connection, or on new versions, optionally a query but not connection.
3070
my ( $q_or_c ) = @_;
3072
my $info = choose_thread(
3074
'Select a thread to kill the ' . $q_or_c,
3076
return unless $info;
3077
return unless pause("Kill $info->{id}?") =~ m/y/i;
3080
do_stmt($info->{cxn}, $q_or_c eq 'QUERY' ? 'KILL_QUERY' : 'KILL_CONNECTION', $info->{id} );
3083
if ( $EVAL_ERROR ) {
3084
print "\nError: $EVAL_ERROR";
3089
# set_display_precision {{{3
3090
sub set_display_precision {
3092
$config{num_digits}->{val} = min(9, max(0, $config{num_digits}->{val} + $dir));
3095
sub toggle_visible_table {
3096
my ( $mode, $table ) = @_;
3097
my $visible = $modes{$mode}->{visible_tables};
3098
if ( grep { $_ eq $table } @$visible ) {
3099
$modes{$mode}->{visible_tables} = [ grep { $_ ne $table } @$visible ];
3102
unshift @$visible, $table;
3104
$modes{$mode}->{cust}->{visible_tables} = 1;
3109
my ( $tbl, $filter ) = @_;
3110
my $filters = $tbl_meta{$tbl}->{filters};
3111
if ( grep { $_ eq $filter } @$filters ) {
3112
$tbl_meta{$tbl}->{filters} = [ grep { $_ ne $filter } @$filters ];
3115
push @$filters, $filter;
3117
$tbl_meta{$tbl}->{cust}->{filters} = 1;
3120
# toggle_config {{{3
3123
$config{$key}->{val} ^= 1;
3126
# create_deadlock {{{3
3127
sub create_deadlock {
3128
$clear_screen_sub->();
3130
print "This function will deliberately cause a small deadlock, "
3131
. "clearing deadlock information from the InnoDB monitor.\n\n";
3133
my $answer = prompt("Are you sure you want to proceed? Say 'y' if you do");
3134
return 0 unless $answer eq 'y';
3136
my ( $cxn ) = select_cxn('Clear on which server? ');
3137
return unless $cxn && exists($connections{$cxn});
3139
clear_deadlock($cxn);
3142
# deadlock_thread {{{3
3143
sub deadlock_thread {
3144
my ( $id, $tbl, $cxn ) = @_;
3147
my $dbh = get_new_db_connection($cxn, 1);
3149
"set transaction isolation level serializable",
3150
(version_ge($dbh, '4.0.11') ? "start transaction" : 'begin'),
3151
"select * from $tbl where a = $id",
3152
"update $tbl set a = $id where a <> $id",
3155
foreach my $stmt (@stmts[0..2]) {
3159
$dbh->do($stmts[-1]);
3161
if ( $EVAL_ERROR ) {
3162
if ( $EVAL_ERROR !~ m/Deadlock found/ ) {
3169
# Purges unused binlogs on the master, up to but not including the latest log.
3170
# TODO: guess which connections are slaves of a given master.
3171
sub purge_master_logs {
3172
my @cxns = get_connections();
3174
get_master_slave_status(@cxns);
3176
# Toss out the rows that don't have master/slave status...
3178
grep { $_ && ($_->{file} || $_->{master_host}) }
3179
map { $vars{$_}->{$clock} } @cxns;
3180
@cxns = map { $_->{cxn} } @vars;
3182
# Figure out which master to purge ons.
3183
my @masters = map { $_->{cxn} } grep { $_->{file} } @vars;
3184
my ( $master ) = select_cxn('Which master?', @masters );
3185
return unless $master;
3186
my ($master_status) = grep { $_->{cxn} eq $master } @vars;
3188
# Figure out the result order (not lexical order) of master logs.
3189
my @master_logs = get_master_logs($master);
3191
my %master_logs = map { $_->{log_name} => $i++ } @master_logs;
3193
# Ask which slave(s) are reading from this master.
3194
my @slave_status = grep { $_->{master_host} } @vars;
3195
my @slaves = map { $_->{cxn} } @slave_status;
3196
@slaves = select_cxn("Which slaves are reading from $master?", @slaves);
3197
@slave_status = grep { my $item = $_; grep { $item->{cxn} eq $_ } @slaves } @slave_status;
3198
return unless @slave_status;
3200
# Find the minimum binary log in use.
3201
my $min_log = min(map { $master_logs{$_->{master_log_file}} } @slave_status);
3202
my $log_name = $master_logs[$min_log]->{log_name};
3204
my $stmt = "PURGE MASTER LOGS TO '$log_name'";
3205
send_cmd_to_servers($stmt, 0, 'PURGE {MASTER | BINARY} LOGS {TO "log_name" | BEFORE "date"}', [$master]);
3208
sub send_cmd_to_servers {
3209
my ( $cmd, $all, $hint, $cxns ) = @_;
3211
@$cxns = get_connections();
3214
@$cxns = select_cxn('Which servers?', @$cxns);
3217
print "\nHint: $hint\n";
3219
$cmd = prompt('Command to send', undef, $cmd);
3220
foreach my $cxn ( @$cxns ) {
3222
my $sth = do_query($cxn, $cmd);
3224
if ( $EVAL_ERROR ) {
3225
print "Error from $cxn: $EVAL_ERROR\n";
3228
print "Success on $cxn\n";
3234
# Display functions {{{2
3238
$clear_screen_sub->();
3239
$config{S_func}->{val} = $func;
3244
$clear_screen_sub->();
3251
my @cxns = get_connections();
3252
get_innodb_status(\@cxns);
3255
my @page_statistics;
3257
my @adaptive_hash_index;
3259
buffer_pool => \@buffer_pool,
3260
page_statistics => \@page_statistics,
3261
insert_buffers => \@insert_buffers,
3262
adaptive_hash_index => \@adaptive_hash_index,
3265
my @visible = get_visible_tables();
3266
my %wanted = map { $_ => 1 } @visible;
3268
foreach my $cxn ( @cxns ) {
3269
my $set = $vars{$cxn}->{$clock};
3270
my $pre = $vars{$cxn}->{$clock-1} || $set;
3272
if ( $set->{IB_bp_complete} ) {
3273
if ( $wanted{buffer_pool} ) {
3274
push @buffer_pool, extract_values($set, $set, $pre, 'buffer_pool');
3276
if ( $wanted{page_statistics} ) {
3277
push @page_statistics, extract_values($set, $set, $pre, 'page_statistics');
3280
if ( $set->{IB_ib_complete} ) {
3281
if ( $wanted{insert_buffers} ) {
3282
push @insert_buffers, extract_values(
3283
$config{status_inc}->{val} ? inc(0, $cxn) : $set, $set, $pre,
3286
if ( $wanted{adaptive_hash_index} ) {
3287
push @adaptive_hash_index, extract_values($set, $set, $pre, 'adaptive_hash_index');
3292
my $first_table = 0;
3293
foreach my $tbl ( @visible ) {
3294
push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl);
3295
push @display_lines, get_cxn_errors(@cxns)
3296
if ( $config{debug}->{val} || !$first_table++ );
3299
draw_screen(\@display_lines);
3305
my @cxns = get_connections();
3306
get_status_info(@cxns);
3310
cmd_summary => \@cmd_summary,
3313
my @visible = get_visible_tables();
3314
my %wanted = map { $_ => 1 } @visible;
3316
# For now, I'm manually pulling these variables out and pivoting. Eventually a SQL-ish
3317
# dialect should let me join a table to a grouped and pivoted table and do this more easily.
3319
my $prefix = qr/^$config{cmd_filter}->{val}/; # TODO: this is a total hack
3321
my ($total, $last_total) = (0, 0);
3322
foreach my $cxn ( @cxns ) {
3323
my $set = $vars{$cxn}->{$clock};
3324
my $pre = $vars{$cxn}->{$clock-1} || $set;
3325
foreach my $key ( keys %$set ) {
3326
next unless $key =~ m/$prefix/i;
3327
my $val = $set->{$key};
3328
next unless defined $val && $val =~ m/^\d+$/;
3329
my $last_val = $val - ($pre->{$key} || 0);
3331
$last_total += $last_val;
3335
last_value => $last_val,
3340
# Add aggregation and turn into a real set TODO: total hack
3341
if ( $wanted{cmd_summary} ) {
3342
foreach my $value ( @values ) {
3343
@{$value}{qw(total last_total)} = ($total, $last_total);
3344
push @cmd_summary, extract_values($value, $value, $value, 'cmd_summary');
3348
my $first_table = 0;
3349
foreach my $tbl ( @visible ) {
3350
push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl);
3351
push @display_lines, get_cxn_errors(@cxns)
3352
if ( $config{debug}->{val} || !$first_table++ );
3355
draw_screen(\@display_lines);
3361
my @cxns = get_connections();
3362
get_innodb_status(\@cxns);
3364
my @deadlock_transactions;
3367
deadlock_transactions => \@deadlock_transactions,
3368
deadlock_locks => \@deadlock_locks,
3371
my @visible = get_visible_tables();
3372
my %wanted = map { $_ => 1 } @visible;
3374
foreach my $cxn ( @cxns ) {
3375
my $innodb_status = $vars{$cxn}->{$clock};
3376
my $prev_status = $vars{$cxn}->{$clock-1} || $innodb_status;
3378
if ( $innodb_status->{IB_dl_timestring} ) {
3380
my $victim = $innodb_status->{IB_dl_rolled_back} || 0;
3383
foreach my $txn_id ( keys %{$innodb_status->{IB_dl_txns}} ) {
3384
my $txn = $innodb_status->{IB_dl_txns}->{$txn_id};
3385
my $pre = $prev_status->{IB_dl_txns}->{$txn_id} || $txn;
3387
if ( $wanted{deadlock_transactions} ) {
3388
my $hash = extract_values($txn->{tx}, $txn->{tx}, $pre->{tx}, 'deadlock_transactions');
3389
$hash->{cxn} = $cxn;
3390
$hash->{dl_txn_num} = $txn_id;
3391
$hash->{victim} = $txn_id == $victim ? 'Yes' : 'No';
3392
$hash->{timestring} = $innodb_status->{IB_dl_timestring};
3393
$hash->{truncates} = $innodb_status->{IB_dl_complete} ? 'No' : 'Yes';
3394
push @deadlock_transactions, $hash;
3397
if ( $wanted{deadlock_locks} ) {
3398
foreach my $lock ( @{$txn->{locks}} ) {
3399
my $hash = extract_values($lock, $lock, $lock, 'deadlock_locks');
3400
$hash->{dl_txn_num} = $txn_id;
3401
$hash->{cxn} = $cxn;
3402
$hash->{mysql_thread_id} = $txn->{tx}->{mysql_thread_id};
3403
push @deadlock_locks, $hash;
3412
my $first_table = 0;
3413
foreach my $tbl ( @visible ) {
3414
push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl);
3415
push @display_lines, get_cxn_errors(@cxns)
3416
if ( $config{debug}->{val} || !$first_table++ );
3419
draw_screen(\@display_lines);
3425
my ( $cxn ) = get_connections();
3426
get_innodb_status([$cxn]);
3427
my $innodb_status = $vars{$cxn}->{$clock};
3429
if ( $innodb_status->{IB_fk_timestring} ) {
3431
push @display_lines, 'Reason: ' . $innodb_status->{IB_fk_reason};
3433
# Display FK errors caused by invalid DML.
3434
if ( $innodb_status->{IB_fk_txn} ) {
3435
my $txn = $innodb_status->{IB_fk_txn};
3436
push @display_lines,
3438
"User $txn->{user} from $txn->{hostname}, thread $txn->{mysql_thread_id} was executing:",
3439
'', no_ctrl_char($txn->{query_text});
3442
my @fk_table = create_table2(
3443
$tbl_meta{fk_error}->{visible},
3444
meta_to_hdr('fk_error'),
3445
extract_values($innodb_status, $innodb_status, $innodb_status, 'fk_error'),
3446
{ just => '-', sep => ' '});
3447
push @display_lines, '', @fk_table;
3451
push @display_lines, '', 'No foreign key error data.';
3453
draw_screen(\@display_lines, { raw => 1 } );
3459
my @cxns = get_connections();
3460
get_innodb_status(\@cxns);
3467
io_threads => \@io_threads,
3468
pending_io => \@pending_io,
3469
file_io_misc => \@file_io_misc,
3470
log_statistics => \@log_statistics,
3473
my @visible = get_visible_tables();
3474
my %wanted = map { $_ => 1 } @visible;
3476
foreach my $cxn ( @cxns ) {
3477
my $set = $vars{$cxn}->{$clock};
3478
my $pre = $vars{$cxn}->{$clock-1} || $set;
3480
if ( $set->{IB_io_complete} ) {
3481
if ( $wanted{io_threads} ) {
3482
my $cur_threads = $set->{IB_io_threads};
3483
my $pre_threads = $pre->{IB_io_threads} || $cur_threads;
3484
foreach my $key ( sort keys %$cur_threads ) {
3485
my $cur_thd = $cur_threads->{$key};
3486
my $pre_thd = $pre_threads->{$key} || $cur_thd;
3487
my $hash = extract_values($cur_thd, $cur_thd, $pre_thd, 'io_threads');
3488
$hash->{cxn} = $cxn;
3489
push @io_threads, $hash;
3492
if ( $wanted{pending_io} ) {
3493
push @pending_io, extract_values($set, $set, $pre, 'pending_io');
3495
if ( $wanted{file_io_misc} ) {
3496
push @file_io_misc, extract_values(
3497
$config{status_inc}->{val} ? inc(0, $cxn) : $set,
3498
$set, $pre, 'file_io_misc');
3501
if ( $set->{IB_lg_complete} && $wanted{log_statistics} ) {
3502
push @log_statistics, extract_values($set, $set, $pre, 'log_statistics');
3506
my $first_table = 0;
3507
foreach my $tbl ( @visible ) {
3508
push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl);
3509
push @display_lines, get_cxn_errors(@cxns)
3510
if ( $config{debug}->{val} || !$first_table++ );
3513
draw_screen(\@display_lines);
3519
my @cxns = get_connections();
3520
get_innodb_status(\@cxns);
3524
innodb_locks => \@innodb_locks,
3527
my @visible = get_visible_tables();
3528
my %wanted = map { $_ => 1 } @visible;
3531
foreach my $cxn ( @cxns ) {
3532
my $set = $vars{$cxn}->{$clock} or next;
3533
my $pre = $vars{$cxn}->{$clock-1} || $set;
3535
if ( $wanted{innodb_locks} && defined $set->{IB_tx_transactions} && @{$set->{IB_tx_transactions}} ) {
3537
my $cur_txns = $set->{IB_tx_transactions};
3538
my $pre_txns = $pre->{IB_tx_transactions} || $cur_txns;
3539
my %cur_txns = map { $_->{mysql_thread_id} => $_ } @$cur_txns;
3540
my %pre_txns = map { $_->{mysql_thread_id} => $_ } @$pre_txns;
3541
foreach my $txn ( @$cur_txns ) {
3542
foreach my $lock ( @{$txn->{locks}} ) {
3543
my %hash = map { $_ => $txn->{$_} } qw(txn_id mysql_thread_id lock_wait_time active_secs);
3544
map { $hash{$_} = $lock->{$_} } qw(lock_type space_id page_no n_bits index db table txn_id lock_mode special insert_intention waiting);
3546
push @innodb_locks, extract_values(\%hash, \%hash, \%hash, 'innodb_locks');
3552
my $first_table = 0;
3553
foreach my $tbl ( @visible ) {
3554
push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl);
3555
push @display_lines, get_cxn_errors(@cxns)
3556
if ( $config{debug}->{val} || !$first_table++ );
3559
draw_screen(\@display_lines);
3565
my @cxns = get_connections();
3566
get_master_slave_status(@cxns);
3567
get_status_info(@cxns);
3569
my @slave_sql_status;
3570
my @slave_io_status;
3573
slave_sql_status => \@slave_sql_status,
3574
slave_io_status => \@slave_io_status,
3575
master_status => \@master_status,
3578
my @visible = get_visible_tables();
3579
my %wanted = map { $_ => 1 } @visible;
3581
foreach my $cxn ( @cxns ) {
3582
my $set = $config{status_inc}->{val} ? inc(0, $cxn) : $vars{$cxn}->{$clock};
3583
my $pre = $vars{$cxn}->{$clock - 1} || $set;
3584
if ( $wanted{slave_sql_status} ) {
3585
push @slave_sql_status, extract_values($set, $set, $pre, 'slave_sql_status');
3587
if ( $wanted{slave_io_status} ) {
3588
push @slave_io_status, extract_values($set, $set, $pre, 'slave_io_status');
3590
if ( $wanted{master_status} ) {
3591
push @master_status, extract_values($set, $set, $pre, 'master_status');
3595
my $first_table = 0;
3596
foreach my $tbl ( @visible ) {
3597
push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl);
3598
push @display_lines, get_cxn_errors(@cxns)
3599
if ( $config{debug}->{val} || !$first_table++ );
3602
draw_screen(\@display_lines);
3607
my @display_lines = ('');
3608
my @cxns = get_connections();
3609
my @open_tables = get_open_tables(@cxns);
3610
my @tables = map { extract_values($_, $_, $_, 'open_tables') } @open_tables;
3611
push @display_lines, set_to_tbl(\@tables, 'open_tables'), get_cxn_errors(@cxns);
3612
draw_screen(\@display_lines);
3622
q_header => \@q_header,
3623
processlist => \@processlist,
3626
my @visible = $opts{n} ? 'processlist' : get_visible_tables();
3627
my %wanted = map { $_ => 1 } @visible;
3630
my @cxns = get_connections();
3631
my @full_processlist = get_full_processlist(@cxns);
3634
if ( $wanted{q_header} ) {
3635
get_status_info(@cxns);
3636
foreach my $cxn ( @cxns ) {
3637
my $set = $vars{$cxn}->{$clock};
3638
my $pre = $vars{$cxn}->{$clock-1} || $set;
3639
my $hash = extract_values($set, $set, $pre, 'q_header');
3640
$hash->{cxn} = $cxn;
3641
$hash->{when} = 'Total';
3642
push @q_header, $hash;
3644
if ( exists $vars{$cxn}->{$clock - 1} ) {
3645
my $inc = inc(0, $cxn);
3646
my $hash = extract_values($inc, $set, $pre, 'q_header');
3647
$hash->{cxn} = $cxn;
3648
$hash->{when} = 'Now';
3649
push @q_header, $hash;
3654
if ( $wanted{processlist} ) {
3655
# TODO: save prev values
3656
push @processlist, map { extract_values($_, $_, $_, 'processlist') } @full_processlist;
3659
my $first_table = 0;
3660
foreach my $tbl ( @visible ) {
3661
next unless $wanted{$tbl};
3662
push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl);
3663
push @display_lines, get_cxn_errors(@cxns)
3664
if ( $config{debug}->{val} || !$first_table++ );
3667
# Save queries in global variable for analysis. The rows in %rows_for have been
3668
# filtered, etc as a side effect of set_to_tbl(), so they are the same as the rows
3669
# that get pushed to the screen.
3670
@current_queries = map {
3672
@hash{ qw(cxn id db query secs) } = @{$_}{ qw(cxn mysql_thread_id db info secs) };
3674
} @{$rows_for{processlist}};
3676
draw_screen(\@display_lines);
3682
my @cxns = get_connections();
3683
get_innodb_status(\@cxns);
3686
my @row_operation_misc;
3690
row_operations => \@row_operations,
3691
row_operation_misc => \@row_operation_misc,
3692
semaphores => \@semaphores,
3693
wait_array => \@wait_array,
3696
my @visible = get_visible_tables();
3697
my %wanted = map { $_ => 1 } @visible;
3698
my $incvar = $config{status_inc}->{val};
3700
foreach my $cxn ( @cxns ) {
3701
my $set = $vars{$cxn}->{$clock};
3702
my $pre = $vars{$cxn}->{$clock-1} || $set;
3703
my $inc; # Only assigned to if wanted
3705
if ( $set->{IB_ro_complete} ) {
3706
if ( $wanted{row_operations} ) {
3707
$inc ||= $incvar ? inc(0, $cxn) : $set;
3708
push @row_operations, extract_values($inc, $set, $pre, 'row_operations');
3710
if ( $wanted{row_operation_misc} ) {
3711
push @row_operation_misc, extract_values($set, $set, $pre, 'row_operation_misc'),
3715
if ( $set->{IB_sm_complete} && $wanted{semaphores} ) {
3716
$inc ||= $incvar ? inc(0, $cxn) : $set;
3717
push @semaphores, extract_values($inc, $set, $pre, 'semaphores');
3720
if ( $set->{IB_sm_wait_array_size} && $wanted{wait_array} ) {
3721
foreach my $wait ( @{$set->{IB_sm_waits}} ) {
3722
my $hash = extract_values($wait, $wait, $wait, 'wait_array');
3723
$hash->{cxn} = $cxn;
3724
push @wait_array, $hash;
3729
my $first_table = 0;
3730
foreach my $tbl ( @visible ) {
3731
push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl);
3732
push @display_lines, get_cxn_errors(@cxns)
3733
if ( $config{debug}->{val} || !$first_table++ );
3736
draw_screen(\@display_lines);
3744
my @innodb_transactions;
3746
t_header => \@t_header,
3747
innodb_transactions => \@innodb_transactions,
3750
my @visible = $opts{n} ? 'innodb_transactions' : get_visible_tables();
3751
my %wanted = map { $_ => 1 } @visible;
3753
my @cxns = get_connections();
3755
# If the header is to be shown, buffer pool data is required.
3756
get_innodb_status( \@cxns, [ $wanted{t_header} ? qw(bp) : () ] );
3758
foreach my $cxn ( get_connections() ) {
3759
my $set = $vars{$cxn}->{$clock};
3760
my $pre = $vars{$cxn}->{$clock-1} || $set;
3762
next unless $set->{IB_tx_transactions};
3764
if ( $wanted{t_header} ) {
3765
my $hash = extract_values($set, $set, $pre, 't_header');
3766
push @t_header, $hash;
3769
if ( $wanted{innodb_transactions} ) {
3770
my $cur_txns = $set->{IB_tx_transactions};
3771
my $pre_txns = $pre->{IB_tx_transactions} || $cur_txns;
3772
my %cur_txns = map { $_->{mysql_thread_id} => $_ } @$cur_txns;
3773
my %pre_txns = map { $_->{mysql_thread_id} => $_ } @$pre_txns;
3774
foreach my $thd_id ( sort keys %cur_txns ) {
3775
my $cur_txn = $cur_txns{$thd_id};
3776
my $pre_txn = $pre_txns{$thd_id} || $cur_txn;
3777
my $hash = extract_values($cur_txn, $cur_txn, $pre_txn, 'innodb_transactions');
3778
$hash->{cxn} = $cxn;
3779
push @innodb_transactions, $hash;
3785
my $first_table = 0;
3786
foreach my $tbl ( @visible ) {
3787
push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl);
3788
push @display_lines, get_cxn_errors(@cxns)
3789
if ( $config{debug}->{val} || !$first_table++ );
3792
# Save queries in global variable for analysis. The rows in %rows_for have been
3793
# filtered, etc as a side effect of set_to_tbl(), so they are the same as the rows
3794
# that get pushed to the screen.
3795
@current_queries = map {
3797
@hash{ qw(cxn id db query secs) } = @{$_}{ qw(cxn mysql_thread_id db query_text active_secs) };
3799
} @{$rows_for{innodb_transactions}};
3801
draw_screen(\@display_lines);
3806
my $fmt = get_var_set('S_set');
3807
my $func = $config{S_func}->{val};
3808
my $inc = $func eq 'g' || $config{status_inc}->{val};
3810
# The table's meta-data is generated from the compiled var_set.
3811
my ( $cols, $visible );
3812
if ( $tbl_meta{var_status}->{fmt} && $fmt eq $tbl_meta{var_status}->{fmt} ) {
3813
( $cols, $visible ) = @{$tbl_meta{var_status}}{qw(cols visible)};
3816
( $cols, $visible ) = compile_select_stmt($fmt);
3818
# Apply missing values to columns. Always apply averages across all connections.
3821
$_->{label} = $_->{hdr};
3824
$tbl_meta{var_status}->{cols} = $cols;
3825
$tbl_meta{var_status}->{visible} = $visible;
3826
$tbl_meta{var_status}->{fmt} = $fmt;
3827
map { $tbl_meta{var_status}->{cols}->{$_}->{just} = ''} @$visible;
3832
var_status => \@var_status,
3835
my @visible = get_visible_tables();
3836
my %wanted = map { $_ => 1 } @visible;
3837
my @cxns = get_connections();
3839
get_status_info(@cxns);
3840
get_innodb_status(\@cxns);
3842
# Set up whether to pivot and how many sets to extract.
3843
$tbl_meta{var_status}->{pivot} = $func eq 'v';
3847
? $config{num_status_sets}->{val}
3849
foreach my $set ( 0 .. $num_sets ) {
3851
foreach my $cxn ( @cxns ) {
3852
my $vars = $inc ? inc($set, $cxn) : $vars{$cxn}->{$clock - $set};
3853
my $cur = $vars{$cxn}->{$clock-$set};
3854
my $pre = $vars{$cxn}->{$clock-$set-1} || $cur;
3855
next unless $vars && %$vars;
3856
my $hash = extract_values($vars, $cur, $pre, 'var_status');
3859
@rows = apply_group_by('var_status', [], @rows);
3860
push @var_status, @rows;
3863
# Recompile the sort func. TODO: avoid recompiling at every refresh.
3864
# Figure out whether the data is all numeric and decide on a sort type.
3867
# grep { !defined $_ || $_ !~ m/^\d+$/ }
3868
# map { my $col = $_; map { $_->{$col} } @var_status }
3869
# $tbl_meta{var_status}->{sort_cols} =~ m/(\w+)/g)
3872
$tbl_meta{var_status}->{sort_func} = make_sort_func($tbl_meta{var_status});
3874
# ################################################################
3875
# Now there is specific display code based on $config{S_func}
3876
# ################################################################
3877
if ( $func =~ m/s|g/ ) {
3880
# Clear the screen if the display width changed.
3881
if ( @last_term_size && $this_term_size[0] != $last_term_size[0] ) {
3883
$clear_screen_sub->();
3886
if ( $func eq 's' ) {
3887
# Decide how wide columns should be.
3888
my $num_cols = scalar(@$visible);
3889
my $width = $opts{n} ? 0 : max($min_width, int(($this_term_size[0] - $num_cols + 1) / $num_cols));
3890
my $g_format = $opts{n} ? ( "%s\t" x $num_cols ) : ( "%-${width}s " x $num_cols );
3892
# Print headers every now and then. Headers can get really long, so compact them.
3893
my @hdr = @$visible;
3895
if ( $lines_printed == 0 ) {
3896
print join("\t", @hdr), "\n";
3900
elsif ( $lines_printed == 0 || $lines_printed > $this_term_size[1] - 2 ) {
3901
@hdr = map { donut(crunch($_, $width), $width) } @hdr;
3902
print join(' ', map { sprintf( "%${width}s", donut($_, $width)) } @hdr) . "\n";
3906
# Design a column format for the values.
3909
? join("\t", map { '%s' } @$visible) . "\n"
3910
: join(' ', map { "%${width}s" } @hdr) . "\n";
3912
foreach my $row ( @var_status ) {
3913
printf($format, map { defined $_ ? $_ : '' } @{$row}{ @$visible });
3918
# Design a column format for the values.
3919
my $num_cols = scalar(@$visible);
3920
my $width = $opts{n} ? 0 : int(($this_term_size[0] - $num_cols + 1) / $num_cols);
3921
my $format = $opts{n} ? ( "%s\t" x $num_cols ) : ( "%-${width}s " x $num_cols );
3922
$format =~ s/\s$/\n/;
3924
# Print headers every now and then.
3926
if ( $lines_printed == 0 ) {
3927
print join("\t", @$visible), "\n";
3928
print join("\t", map { shorten($mvs{$_}) } @$visible), "\n";
3931
elsif ( $lines_printed == 0 || $lines_printed > $this_term_size[1] - 2 ) {
3932
printf($format, map { donut(crunch($_, $width), $width) } @$visible);
3933
printf($format, map { shorten($mvs{$_} || 0) } @$visible);
3937
# Update the max ever seen, and scale by the max ever seen.
3938
my $set = $var_status[0];
3939
foreach my $col ( @$visible ) {
3940
$set->{$col} = 1 unless defined $set->{$col} && $set->{$col} =~ m/$num_regex/;
3941
$set->{$col} = ($set->{$col} || 1) / ($set->{Uptime_hires} || 1);
3942
$mvs{$col} = max($mvs{$col} || 1, $set->{$col});
3943
$set->{$col} /= $mvs{$col};
3945
printf($format, map { ( $config{graph_char}->{val} x int( $width * $set->{$_} )) || '.' } @$visible );
3951
my $first_table = 0;
3953
foreach my $tbl ( @visible ) {
3954
push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl);
3955
push @display_lines, get_cxn_errors(@cxns)
3956
if ( $config{debug}->{val} || !$first_table++ );
3958
$clear_screen_sub->();
3959
draw_screen( \@display_lines );
3963
# display_explain {{{3
3964
sub display_explain {
3966
my $cxn = $info->{cxn};
3967
my $db = $info->{db};
3969
my ( $mods, $query ) = rewrite_for_explain($info->{query});
3975
my $part = version_ge($dbhs{$cxn}->{dbh}, '5.1.5') ? 'PARTITIONS' : '';
3976
$query = "EXPLAIN $part\n" . $query;
3980
do_query($cxn, "use $db");
3982
my $sth = do_query($cxn, $query);
3985
while ( $res = $sth->fetchrow_hashref() ) {
3986
map { $res->{$_} ||= '' } ( 'partitions', keys %$res);
3987
my @this_table = create_caption("Sub-Part $res->{id}",
3989
$tbl_meta{explain}->{visible},
3990
meta_to_hdr('explain'),
3991
extract_values($res, $res, $res, 'explain')));
3992
@display_lines = stack_next(\@display_lines, \@this_table, { pad => ' ', vsep => 2 });
3996
if ( $EVAL_ERROR ) {
3997
push @display_lines,
3999
"The query could not be explained. Only SELECT queries can be "
4000
. "explained; innotop tries to rewrite certain REPLACE and INSERT queries "
4001
. "into SELECT, but this doesn't always succeed.";
4006
push @display_lines, '', 'The query could not be explained.';
4010
push @display_lines, '', '[This query has been re-written to be explainable]';
4013
unshift @display_lines, no_ctrl_char($query);
4014
draw_screen(\@display_lines, { raw => 1 } );
4017
# rewrite_for_explain {{{3
4018
sub rewrite_for_explain {
4023
$mods += $query =~ s/^\s*(?:replace|insert).*?select/select/is;
4024
$mods += $query =~ s/^
4025
\s*create\s+(?:temporary\s+)?table
4026
\s+(?:\S+\s+)as\s+select/select/xis;
4027
$mods += $query =~ s/\s+on\s+duplicate\s+key\s+update.*$//is;
4028
return ( $mods, $query );
4031
# show_optimized_query {{{3
4032
sub show_optimized_query {
4034
my $cxn = $info->{cxn};
4035
my $db = $info->{db};
4036
my $meta = $dbhs{$cxn};
4040
my ( $mods, $query ) = rewrite_for_explain($info->{query});
4043
push @display_lines, '[This query has been re-written to be explainable]';
4047
push @display_lines, no_ctrl_char($info->{query});
4051
do_query($cxn, "use $db");
4053
do_query( $cxn, 'EXPLAIN EXTENDED ' . $query ) or die "Can't explain query";
4054
my $sth = do_query($cxn, 'SHOW WARNINGS');
4055
my $res = $sth->fetchall_arrayref({});
4058
foreach my $result ( @$res ) {
4059
push @display_lines, 'Note:', no_ctrl_char($result->{message});
4063
push @display_lines, '', 'The query optimization could not be generated.';
4067
if ( $EVAL_ERROR ) {
4068
push @display_lines, '', "The optimization could not be generated: $EVAL_ERROR";
4073
push @display_lines, '', 'The query optimization could not be generated.';
4076
draw_screen(\@display_lines, { raw => 1 } );
4081
my $mode = $config{mode}->{val};
4083
# Get globally mapped keys, then overwrite them with mode-specific ones.
4085
$_ => $action_for{$_}->{label}
4087
foreach my $key ( keys %{$modes{$mode}->{action_for}} ) {
4088
$keys{$key} = $modes{$mode}->{action_for}->{$key}->{label};
4092
# Split them into three kinds of keys: MODE keys, action keys, and
4093
# magic (special character) keys.
4094
my @modes = sort grep { m/[A-Z]/ } keys %keys;
4095
my @actions = sort grep { m/[a-z]/ } keys %keys;
4096
my @magic = sort grep { m/[^A-Z]/i } keys %keys;
4098
my @display_lines = ( '', 'Switch to a different mode:' );
4101
my @all_modes = map { "$_ $modes{$_}->{hdr}" } @modes;
4102
my @col1 = splice(@all_modes, 0, ceil(@all_modes/3));
4103
my @col2 = splice(@all_modes, 0, ceil(@all_modes/2));
4104
my $max1 = max(map {length($_)} @col1);
4105
my $max2 = max(map {length($_)} @col2);
4107
push @display_lines, sprintf(" %-${max1}s %-${max2}s %s",
4108
(shift @col1 || ''),
4109
(shift @col2 || ''),
4110
(shift @all_modes || ''));
4114
my @all_actions = map { "$_ $keys{$_}" } @actions;
4115
@col1 = splice(@all_actions, 0, ceil(@all_actions/2));
4116
$max1 = max(map {length($_)} @col1);
4117
push @display_lines, '', 'Actions:';
4119
push @display_lines, sprintf(" %-${max1}s %s",
4120
(shift @col1 || ''),
4121
(shift @all_actions || ''));
4125
my @all_magic = map { sprintf('%4s', $action_for{$_}->{key} || $_) . " $keys{$_}" } @magic;
4126
@col1 = splice(@all_magic, 0, ceil(@all_magic/2));
4127
$max1 = max(map {length($_)} @col1);
4128
push @display_lines, '', 'Other:';
4130
push @display_lines, sprintf("%-${max1}s%s",
4131
(shift @col1 || ''),
4132
(shift @all_magic || ''));
4135
$clear_screen_sub->();
4136
draw_screen(\@display_lines, { show_all => 1 } );
4138
$clear_screen_sub->();
4141
# show_full_query {{{3
4142
sub show_full_query {
4144
my @display_lines = no_ctrl_char($info->{query});
4145
draw_screen(\@display_lines, { raw => 1 });
4148
# Formatting functions {{{2
4150
# create_table2 {{{3
4151
# Makes a two-column table, labels on left, data on right.
4152
# Takes refs of @cols, %labels and %data, %user_prefs
4154
my ( $cols, $labels, $data, $user_prefs ) = @_;
4157
if ( @$cols && %$data ) {
4165
if ( $user_prefs ) {
4166
map { $p->{$_} = $user_prefs->{$_} } keys %$user_prefs;
4170
map { $data->{$_} = '' unless defined $data->{$_} } @$cols;
4173
my $max_l = max(map{ length($labels->{$_}) } @$cols);
4174
my $max_v = max(map{ length($data->{$_}) } @$cols);
4175
my $format = "%$p->{just}${max_l}s$p->{sep} %$p->{just1}${max_v}s";
4176
foreach my $col ( @$cols ) {
4177
push @rows, sprintf($format, $labels->{$col}, $data->{$col});
4184
# Stacks one display section next to the other. Accepts left-hand arrayref,
4185
# right-hand arrayref, and options hashref. Tries to stack as high as
4189
# can stack ccc next to the bbb.
4190
# NOTE: this DOES modify its arguments, even though it returns a new array.
4192
my ( $left, $right, $user_prefs ) = @_;
4199
if ( $user_prefs ) {
4200
map { $p->{$_} = $user_prefs->{$_} } keys %$user_prefs;
4203
# Find out how wide the LHS can be and still let the RHS fit next to it.
4204
my $pad = $p->{pad};
4205
my $max_r = max( map { length($_) } @$right) || 0;
4206
my $max_l = $this_term_size[0] - $max_r - length($pad);
4208
# Find the minimum row on the LHS that the RHS will fit next to.
4209
my $i = scalar(@$left) - 1;
4210
while ( $i >= 0 && length($left->[$i]) <= $max_l ) {
4216
if ( $i < scalar(@$left) ) {
4217
# Find the max width of the section of the LHS against which the RHS
4219
my $max_i_in_common = min($i + scalar(@$right) - 1, scalar(@$left) - 1);
4220
my $max_width = max( map { length($_) } @{$left}[$i..$max_i_in_common]);
4222
# Append the RHS onto the LHS until one runs out.
4223
while ( $i < @$left && $i - $offset < @$right ) {
4224
my $format = "%-${max_width}s$pad%${max_r}s";
4225
$left->[$i] = sprintf($format, $left->[$i], $right->[$i - $offset]);
4228
while ( $i - $offset < @$right ) {
4229
# There is more RHS to push on the end of the array
4231
sprintf("%${max_width}s$pad%${max_r}s", ' ', $right->[$i - $offset]);
4234
push @result, @$left;
4237
# There is no room to put them side by side. Add them below, with
4238
# a blank line above them if specified.
4239
push @result, @$left;
4240
push @result, (' ' x $this_term_size[0]) if $p->{vsep} && @$left;
4241
push @result, @$right;
4246
# create_caption {{{3
4247
sub create_caption {
4248
my ( $caption, @rows ) = @_;
4251
# Calculate the width of what will be displayed, so it can be centered
4252
# in that space. When the thing is wider than the display, center the
4253
# caption in the display.
4254
my $width = min($this_term_size[0], max(map { length(ref($_) ? $_->[0] : $_) } @rows));
4256
my $cap_len = length($caption);
4258
# It may be narrow enough to pad the sides with underscores and save a
4259
# line on the screen.
4260
if ( $cap_len <= $width - 6 ) {
4261
my $left = int(($width - 2 - $cap_len) / 2);
4263
("_" x $left) . " $caption " . ("_" x ($width - $left - $cap_len - 2));
4266
# The caption is too wide to add underscores on each side.
4269
# Color is supported, so we can use terminal underlining.
4270
if ( $config{color}->{val} ) {
4271
my $left = int(($width - $cap_len) / 2);
4273
(" " x $left) . $caption . (" " x ($width - $left - $cap_len)),
4278
# Color is not supported, so we have to add a line underneath to separate the
4279
# caption from whatever it's captioning.
4281
my $left = int(($width - $cap_len) / 2);
4282
unshift @rows, ('-' x $width);
4283
unshift @rows, (" " x $left) . $caption . (" " x ($width - $left - $cap_len));
4286
# The caption is wider than the thing it labels, so we have to pad the
4287
# thing it labels to a consistent width.
4288
if ( $cap_len > $width ) {
4291
? [ sprintf('%-' . $cap_len . 's', $_->[0]), $_->[1] ]
4292
: sprintf('%-' . $cap_len . 's', $_);
4302
# Input: an arrayref of columns, hashref of col info, and an arrayref of hashes
4303
# Example: [ 'a', 'b' ]
4304
# { a => spec, b => spec }
4305
# [ { a => 1, b => 2}, { a => 3, b => 4 } ]
4306
# The 'spec' is a hashref of hdr => label, just => ('-' or ''). It also supports min and max-widths
4307
# vi the minw and maxw params.
4308
# Output: an array of strings, one per row.
4310
# Column One Column Two
4311
# ---------- ----------
4315
my ( $cols, $info, $data, $prefs ) = @_;
4317
$prefs->{no_hdr} ||= ($opts{n} && $clock != 1);
4319
# Truncate rows that will surely be off screen even if this is the only table.
4320
if ( !$opts{n} && !$prefs->{raw} && !$prefs->{show_all} && $this_term_size[1] < @$data-1 ) {
4321
$data = [ @$data[0..$this_term_size[1] - 1] ];
4326
if ( @$cols && %$info ) {
4328
# Fix undef values, collapse whitespace.
4329
foreach my $row ( @$data ) {
4330
map { $row->{$_} = collapse_ws($row->{$_}) } @$cols;
4333
my $col_sep = $opts{n} ? "\t" : ' ';
4335
# Find each column's max width.
4340
if ( $info->{$_}->{dec} ) {
4341
# Align along the decimal point
4342
my $max_rodp = max(0, map { $_->{$col_name} =~ m/([^\s\d-].*)$/ ? length($1) : 0 } @$data);
4343
foreach my $row ( @$data ) {
4344
my $col = $row->{$col_name};
4345
my ( $l, $r ) = $col =~ m/^([\s\d]*)(.*)$/;
4346
$row->{$col_name} = sprintf("%s%-${max_rodp}s", $l, $r);
4349
my $max_width = max( length($info->{$_}->{hdr}), map { length($_->{$col_name}) } @$data);
4350
if ( $info->{$col_name}->{maxw} ) {
4351
$max_width = min( $max_width, $info->{$col_name}->{maxw} );
4353
if ( $info->{$col_name}->{minw} ) {
4354
$max_width = max( $max_width, $info->{$col_name}->{minw} );
4356
$col_name => $max_width;
4361
if ( !$config{hide_hdr}->{val} && !$prefs->{no_hdr} ) {
4362
push @rows, $opts{n}
4363
? join( $col_sep, @$cols )
4364
: join( $col_sep, map { sprintf( "%-$width_for{$_}s", trunc($info->{$_}->{hdr}, $width_for{$_}) ) } @$cols );
4365
if ( $config{color}->{val} && $config{header_highlight}->{val} ) {
4366
push @rows, [ pop @rows, $config{header_highlight}->{val} ];
4368
elsif ( !$opts{n} ) {
4369
push @rows, join( $col_sep, map { "-" x $width_for{$_} } @$cols );
4375
foreach my $item ( @$data ) {
4376
push @rows, join($col_sep, map { $item->{$_} } @$cols );
4380
my $format = join( $col_sep,
4381
map { "%$info->{$_}->{just}$width_for{$_}s" } @$cols );
4382
foreach my $item ( @$data ) {
4383
my $row = sprintf($format, map { trunc($item->{$_}, $width_for{$_}) } @$cols );
4384
if ( $config{color}->{val} && $item->{_color} ) {
4385
push @rows, [ $row, $item->{_color} ];
4397
# Aggregates a table. If $group_by is an arrayref of columns, the grouping key
4398
# is the specified columns; otherwise it's just the empty string (e.g.
4399
# everything is grouped as one group).
4400
sub apply_group_by {
4401
my ( $tbl, $group_by, @rows ) = @_;
4402
my $meta = $tbl_meta{$tbl};
4403
my %is_group = map { $_ => 1 } @$group_by;
4404
my @non_grp = grep { !$is_group{$_} } keys %{$meta->{cols}};
4407
foreach my $row ( @rows ) {
4410
? '{' . join('}{', map { defined $_ ? $_ : '' } @{$row}{@$group_by}) . '}'
4412
$temp_table{$group_key} ||= [];
4413
push @{$temp_table{$group_key}}, $row;
4416
# Crush the rows together...
4418
foreach my $key ( sort keys %temp_table ) {
4419
my $group = $temp_table{$key};
4421
@new_row{@$group_by} = @{$group->[0]}{@$group_by};
4422
foreach my $col ( @non_grp ) {
4423
my $agg = $meta->{cols}->{$col}->{agg} || 'first';
4424
$new_row{$col} = $agg_funcs{$agg}->( map { $_->{$col} } @$group );
4426
push @new_rows, \%new_row;
4432
# Unifies all the work of filtering, sorting etc. Alters the input.
4433
# TODO: pull all the little pieces out into subroutines and stick events in each of them.
4435
my ( $rows, $tbl ) = @_;
4436
my $meta = $tbl_meta{$tbl} or die "No such table $tbl in tbl_meta";
4438
if ( !$meta->{pivot} ) {
4440
# Hook in event listeners
4441
foreach my $listener ( @{$event_listener_for{set_to_tbl_pre_filter}} ) {
4442
$listener->set_to_tbl_pre_filter($rows, $tbl);
4445
# Apply filters. Note that if the table is pivoted, filtering and sorting
4446
# are applied later.
4447
foreach my $filter ( @{$meta->{filters}} ) {
4449
@$rows = grep { $filters{$filter}->{func}->($_) } @$rows;
4451
if ( $EVAL_ERROR && $config{debug}->{val} ) {
4456
foreach my $listener ( @{$event_listener_for{set_to_tbl_pre_sort}} ) {
4457
$listener->set_to_tbl_pre_sort($rows, $tbl);
4460
# Sort. Note that if the table is pivoted, sorting might have the wrong
4461
# columns and it could crash. This will only be an issue if it's possible
4462
# to toggle pivoting on and off, which it's not at the moment.
4463
if ( @$rows && $meta->{sort_func} && !$meta->{aggregate} ) {
4464
if ( $meta->{sort_dir} > 0 ) {
4465
@$rows = $meta->{sort_func}->( @$rows );
4468
@$rows = reverse $meta->{sort_func}->( @$rows );
4474
# Stop altering arguments now.
4477
foreach my $listener ( @{$event_listener_for{set_to_tbl_pre_group}} ) {
4478
$listener->set_to_tbl_pre_group(\@rows, $tbl);
4482
if ( $meta->{aggregate} ) {
4483
@rows = apply_group_by($tbl, $meta->{group_by}, @rows);
4485
# Sort. Note that if the table is pivoted, sorting might have the wrong
4486
# columns and it could crash. This will only be an issue if it's possible
4487
# to toggle pivoting on and off, which it's not at the moment.
4488
if ( @rows && $meta->{sort_func} ) {
4489
if ( $meta->{sort_dir} > 0 ) {
4490
@rows = $meta->{sort_func}->( @rows );
4493
@rows = reverse $meta->{sort_func}->( @rows );
4499
foreach my $listener ( @{$event_listener_for{set_to_tbl_pre_colorize}} ) {
4500
$listener->set_to_tbl_pre_colorize(\@rows, $tbl);
4503
if ( !$meta->{pivot} ) {
4504
# Colorize. Adds a _color column to rows.
4505
if ( @rows && $meta->{color_func} ) {
4507
foreach my $row ( @rows ) {
4508
$row->{_color} = $meta->{color_func}->($row);
4511
if ( $EVAL_ERROR ) {
4517
foreach my $listener ( @{$event_listener_for{set_to_tbl_pre_transform}} ) {
4518
$listener->set_to_tbl_pre_transform(\@rows, $tbl);
4521
# Apply_transformations.
4523
my $cols = $meta->{cols};
4524
foreach my $col ( keys %{$rows->[0]} ) {
4525
# Don't auto-vivify $tbl_meta{tbl}-{cols}->{_color}->{trans}
4526
next if $col eq '_color';
4527
foreach my $trans ( @{$cols->{$col}->{trans}} ) {
4528
map { $_->{$col} = $trans_funcs{$trans}->($_->{$col}) } @rows;
4533
my ($fmt_cols, $fmt_meta);
4536
if ( $meta->{pivot} ) {
4538
foreach my $listener ( @{$event_listener_for{set_to_tbl_pre_pivot}} ) {
4539
$listener->set_to_tbl_pre_pivot(\@rows, $tbl);
4542
my @vars = @{$meta->{visible}};
4543
my @tmp = map { { name => $_ } } @vars;
4545
foreach my $i ( 0..@$rows-1 ) {
4548
foreach my $j ( 0..@vars-1 ) {
4549
$tmp[$j]->{$col} = $rows[$i]->{$vars[$j]};
4552
$fmt_meta = { map { $_ => { hdr => $_, just => '-' } } @cols };
4556
# Hook in event listeners
4557
foreach my $listener ( @{$event_listener_for{set_to_tbl_pre_filter}} ) {
4558
$listener->set_to_tbl_pre_filter($rows, $tbl);
4562
foreach my $filter ( @{$meta->{filters}} ) {
4564
@rows = grep { $filters{$filter}->{func}->($_) } @rows;
4566
if ( $EVAL_ERROR && $config{debug}->{val} ) {
4571
foreach my $listener ( @{$event_listener_for{set_to_tbl_pre_sort}} ) {
4572
$listener->set_to_tbl_pre_sort($rows, $tbl);
4576
if ( @rows && $meta->{sort_func} ) {
4577
if ( $meta->{sort_dir} > 0 ) {
4578
@rows = $meta->{sort_func}->( @rows );
4581
@rows = reverse $meta->{sort_func}->( @rows );
4587
# If the table isn't pivoted, just show all columns that are supposed to
4588
# be shown; but eliminate aggonly columns if the table isn't aggregated.
4589
my $aggregated = $meta->{aggregate};
4590
$fmt_cols = [ grep { $aggregated || !$meta->{cols}->{$_}->{aggonly} } @{$meta->{visible}} ];
4591
$fmt_meta = { map { $_ => $meta->{cols}->{$_} } @$fmt_cols };
4593
# If the table is aggregated, re-order the group_by columns to the left of
4595
if ( $aggregated ) {
4596
my %is_group = map { $_ => 1 } @{$meta->{group_by}};
4597
$fmt_cols = [ @{$meta->{group_by}}, grep { !$is_group{$_} } @$fmt_cols ];
4601
foreach my $listener ( @{$event_listener_for{set_to_tbl_pre_create}} ) {
4602
$listener->set_to_tbl_pre_create(\@rows, $tbl);
4605
@rows = create_table( $fmt_cols, $fmt_meta, \@rows);
4606
if ( !$meta->{hide_caption} && !$opts{n} && $config{display_table_captions}->{val} ) {
4607
@rows = create_caption($meta->{capt}, @rows)
4610
foreach my $listener ( @{$event_listener_for{set_to_tbl_post_create}} ) {
4611
$listener->set_to_tbl_post_create(\@rows, $tbl);
4620
my $meta = $tbl_meta{$tbl};
4621
my %labels = map { $_ => $meta->{cols}->{$_}->{hdr} } @{$meta->{visible}};
4626
# From perlfaq5: add commas.
4629
$num = 0 unless defined $num;
4630
$num =~ s/(^[-+]?\d+?(?=(?>(?:\d{3})+)(?!\d))|\G\d{3}(?=\d))/$1,/g;
4634
# set_precision {{{3
4635
# Trim to desired precision.
4637
my ( $num, $precision ) = @_;
4638
$precision = $config{num_digits}->{val} if !defined $precision;
4639
sprintf("%.${precision}f", $num);
4643
# Convert to percent
4646
$num = 0 unless defined $num;
4647
my $digits = $config{num_digits}->{val};
4648
return sprintf("%.${digits}f", $num * 100)
4649
. ($config{show_percent}->{val} ? '%' : '');
4654
my ( $num, $opts ) = @_;
4656
return $num if !defined($num) || $opts{n} || $num !~ m/$num_regex/;
4659
my $pad = defined $opts->{pad} ? $opts->{pad} : '';
4660
my $num_digits = defined $opts->{num_digits}
4661
? $opts->{num_digits}
4662
: $config{num_digits}->{val};
4663
my $force = defined $opts->{force};
4666
while ( $num >= 1_024 ) {
4671
$num =~ m/\./ || $n || $force
4672
? "%.${num_digits}f%s"
4674
$num, ($pad,'k','M','G', 'T')[$n]);
4678
# Utility functions {{{2
4682
return grep { !$seen{$_}++ } @_;
4685
# make_color_func {{{3
4686
sub make_color_func {
4689
foreach my $spec ( @{$tbl->{colors}} ) {
4690
next unless exists $comp_ops{$spec->{op}};
4691
my $val = $spec->{op} =~ m/^(?:eq|ne|le|ge|lt|gt)$/ ? "'$spec->{arg}'"
4692
: $spec->{op} =~ m/^(?:=~|!~)$/ ? "m/" . quotemeta($spec->{arg}) . "/"
4695
"( defined \$set->{$spec->{col}} && \$set->{$spec->{col}} $spec->{op} $val ) { return '$spec->{color}'; }";
4697
return undef unless @criteria;
4698
my $sub = eval 'sub { my ( $set ) = @_; if ' . join(" elsif ", @criteria) . '}';
4703
# make_sort_func {{{3
4704
# Gets a list of sort columns from the table, like "+cxn -time" and returns a
4705
# subroutine that will sort that way.
4706
sub make_sort_func {
4710
# Pivoted tables can be sorted by 'name' and set_x columns; others must be
4711
# sorted by existing columns. TODO: this will crash if you toggle between
4712
# pivoted and nonpivoted. I have several other 'crash' notes about this if
4713
# this ever becomes possible.
4715
if ( $tbl->{pivot} ) {
4716
# Sort type is not really possible on pivoted columns, because a 'column'
4717
# contains data from an entire non-pivoted row, so there could be a mix of
4718
# numeric and non-numeric data. Thus everything has to be 'cmp' type.
4719
foreach my $col ( split(/\s+/, $tbl->{sort_cols} ) ) {
4721
my ( $dir, $name ) = $col =~ m/([+-])?(\w+)$/;
4722
next unless $name && $name =~ m/^(?:name|set_\d+)$/;
4728
? "(\$a->{$name} || $df) $op (\$b->{$name} || $df)"
4729
: "(\$b->{$name} || $df) $op (\$a->{$name} || $df)";
4733
foreach my $col ( split(/\s+/, $tbl->{sort_cols} ) ) {
4735
my ( $dir, $name ) = $col =~ m/([+-])?(\w+)$/;
4736
next unless $name && $tbl->{cols}->{$name};
4738
my $op = $tbl->{cols}->{$name}->{num} ? "<=>" : "cmp";
4739
my $df = $tbl->{cols}->{$name}->{num} ? "0" : "''";
4742
? "(\$a->{$name} || $df) $op (\$b->{$name} || $df)"
4743
: "(\$b->{$name} || $df) $op (\$a->{$name} || $df)";
4746
return sub { return @_ } unless @criteria;
4747
my $sub = eval 'sub { sort {' . join("||", @criteria) . '} @_; }';
4753
# Shortens text to specified length.
4755
my ( $text, $len ) = @_;
4756
if ( length($text) <= $len ) {
4759
return substr($text, 0, $len);
4763
# Takes out the middle of text to shorten it.
4765
my ( $text, $len ) = @_;
4766
return $text if length($text) <= $len;
4767
my $max = length($text) - $len;
4770
# Try to remove a single "word" from somewhere in the center
4771
if ( $text =~ s/_[^_]{$min,$max}_/_/ ) {
4775
# Prefer removing the end of a "word"
4776
if ( $text =~ s/([^_]+)[^_]{$max}_/$1_/ ) {
4780
$text = substr($text, 0, int($len/2))
4782
. substr($text, int($len/2) + $max + 1);
4787
# Removes vowels and compacts repeated letters to shorten text.
4789
my ( $text, $len ) = @_;
4790
return $text if $len && length($text) <= $len;
4791
$text =~ s/^IB_\w\w_//;
4792
$text =~ s/(?<![_ ])[aeiou]//g;
4793
$text =~ s/(.)\1+/$1/g;
4798
# Collapses all whitespace to a single space.
4801
return '' unless defined $text;
4806
# Strips out non-printable characters within fields, which freak terminals out.
4809
return '' unless defined $text;
4810
my $charset = $config{charset}->{val};
4811
if ( $charset && $charset eq 'unicode' ) {
4813
("(?:(?!(?<!\\)").)*" # Double-quoted string
4814
|'(?:(?!(?<!\\)').)*') # Or single-quoted string
4815
/$1 =~ m#\p{IsC}# ? "[BINARY]" : $1/egx;
4817
elsif ( $charset && $charset eq 'none' ) {
4819
("(?:(?!(?<!\\)").)*"
4820
|'(?:(?!(?<!\\)').)*')
4823
else { # The default is 'ascii'
4825
("(?:(?!(?<!\\)").)*"
4826
|'(?:(?!(?<!\\)').)*')
4827
/$1 =~ m#[^\040-\176]# ? "[BINARY]" : $1/egx;
4833
# Wraps text at word boundaries so it fits the screen.
4835
my ( $text, $width) = @_;
4836
$width ||= $this_term_size[0];
4837
$text =~ s/(.{0,$width})(?:\s+|$)/$1\n/g;
4843
# Prints lines to the screen. The first argument is an arrayref. Each
4844
# element of the array is either a string or an arrayref. If it's a string it
4845
# just gets printed. If it's an arrayref, the first element is the string to
4846
# print, and the second is args to colored().
4848
my ( $display_lines, $prefs ) = @_;
4849
if ( !$opts{n} && $config{show_statusbar}->{val} ) {
4850
unshift @$display_lines, create_statusbar();
4853
foreach my $listener ( @{$event_listener_for{draw_screen}} ) {
4854
$listener->draw_screen($display_lines);
4857
$clear_screen_sub->()
4858
if $prefs->{clear} || !$modes{$config{mode}->{val}}->{no_clear_screen};
4859
if ( $opts{n} || $prefs->{raw} ) {
4865
? colored($_->[0], $_->[1])
4868
grep { !$opts{n} || $_ } # Suppress empty lines
4870
if ( $opts{n} && $num_lines ) {
4875
my $max_lines = $prefs->{show_all}
4876
? scalar(@$display_lines)- 1
4877
: min(scalar(@$display_lines), $this_term_size[1]);
4881
? colored(substr($_->[0], 0, $this_term_size[0]), $_->[1])
4882
: substr($_, 0, $this_term_size[0]);
4883
} @$display_lines[0..$max_lines - 1]);
4889
my ( $secs, $fmt ) = @_;
4891
return '00:00' unless $secs;
4893
# Decide what format to use, if not given
4894
$fmt ||= $secs >= 86_400 ? 'd'
4895
: $secs >= 3_600 ? 'h'
4899
$fmt eq 'd' ? sprintf(
4900
"%d+%02d:%02d:%02d",
4901
int($secs / 86_400),
4902
int(($secs % 86_400) / 3_600),
4903
int(($secs % 3_600) / 60),
4905
: $fmt eq 'h' ? sprintf(
4907
int(($secs % 86_400) / 3_600),
4908
int(($secs % 3_600) / 60),
4912
int(($secs % 3_600) / 60),
4916
# dulint_to_int {{{3
4917
# Takes a number that InnoDB formats as two ulint integers, like transaction IDs
4918
# and such, and turns it into a single integer
4921
return 0 unless $num;
4922
my ( $high, $low ) = $num =~ m/^(\d+) (\d+)$/;
4923
return $low unless $high;
4924
return $low + ( $high * $MAX_ULONG );
4927
# create_statusbar {{{3
4928
sub create_statusbar {
4929
my $mode = $config{mode}->{val};
4930
my @cxns = sort { $a cmp $b } get_connections();
4932
my $modeline = ( $config{readonly}->{val} ? '[RO] ' : '' )
4933
. $modes{$mode}->{hdr} . " (? for help)";
4934
my $mode_width = length($modeline);
4935
my $remaining_width = $this_term_size[0] - $mode_width - 1;
4938
# The thingie in top-right that says what we're monitoring.
4941
if ( 1 == @cxns && $dbhs{$cxns[0]} && $dbhs{$cxns[0]}->{dbh} ) {
4942
$cxn = $dbhs{$cxns[0]}->{dbh}->{mysql_serverinfo} || '';
4945
if ( $modes{$mode}->{server_group} ) {
4946
$cxn = "Servers: " . $modes{$mode}->{server_group};
4947
my $err_count = grep { $dbhs{$_} && $dbhs{$_}->{err_count} } @cxns;
4949
$cxn .= "(" . ( scalar(@cxns) - $err_count ) . "/" . scalar(@cxns) . ")";
4953
$cxn = join(' ', map { ($dbhs{$_}->{err_count} ? '!' : '') . $_ }
4954
grep { $dbhs{$_} } @cxns);
4959
get_driver_status(@cxns);
4960
my $vars = $vars{$cxns[0]}->{$clock};
4961
my $inc = inc(0, $cxns[0]);
4963
# Format server uptime human-readably, calculate QPS...
4964
my $uptime = secs_to_time( $vars->{Uptime_hires} );
4965
my $qps = ($inc->{Questions}||0) / ($inc->{Uptime_hires}||1);
4968
if ( exists $vars->{IB_last_secs} ) {
4969
$ibinfo .= "InnoDB $vars->{IB_last_secs}s ";
4970
if ( $vars->{IB_got_all} ) {
4971
if ( ($mode eq 'T' || $mode eq 'W')
4972
&& $vars->{IB_tx_is_truncated} ) {
4984
"%-${mode_width}s %${remaining_width}s",
4986
join(', ', grep { $_ } (
4990
shorten($qps) . " QPS",
4991
($vars->{Threads} || 0) . " thd",
4996
"%-${mode_width}s %${remaining_width}s",
5001
return $config{color}->{val} ? [ $result, 'bold reverse' ] : $result;
5004
# Database connections {{{3
5008
if ( defined $name ) {
5009
$name =~ s/[\s:;]//g;
5013
print word_wrap("Choose a name for the connection. It cannot contain "
5014
. "whitespace, colons or semicolons."), "\n\n";
5016
$name = prompt("Enter a name");
5017
$name =~ s/[\s:;]//g;
5023
$clear_screen_sub->();
5024
print "Typical DSN strings look like\n DBI:mysql:;host=hostname;port=port\n"
5025
. "The db and port are optional and can usually be omitted.\n"
5026
. "If you specify 'mysql_read_default_group=mysql' many options can be read\n"
5027
. "from your mysql options files (~/.my.cnf, /etc/my.cnf).\n\n";
5028
$dsn = prompt("Enter a DSN string", undef, "DBI:mysql:;mysql_read_default_group=mysql;host=$name");
5031
$clear_screen_sub->();
5032
my $dl_table = prompt("Optional: enter a table (must not exist) to use when resetting InnoDB deadlock information",
5033
undef, 'test.innotop_dl');
5035
$connections{$name} = {
5037
dl_table => $dl_table,
5041
sub add_new_server_group {
5044
if ( defined $name ) {
5045
$name =~ s/[\s:;]//g;
5049
print word_wrap("Choose a name for the group. It cannot contain "
5050
. "whitespace, colons or semicolons."), "\n\n";
5052
$name = prompt("Enter a name");
5053
$name =~ s/[\s:;]//g;
5059
$clear_screen_sub->();
5060
@cxns = select_cxn("Choose servers for $name", keys %connections);
5063
$server_groups{$name} = \@cxns;
5069
while ( !$name || !exists($var_sets{$config{$name}->{val}}) ) {
5070
$name = choose_var_set($name);
5072
return $var_sets{$config{$name}->{val}}->{text};
5075
sub add_new_var_set {
5078
if ( defined $name ) {
5084
$name = prompt("Enter a name");
5091
$clear_screen_sub->();
5092
$variables = prompt("Enter variables for $name", undef );
5093
} until ( $variables );
5095
$var_sets{$name} = { text => $variables, user => 1 };
5099
my $mode = $config{mode}->{val};
5100
my @cxns = sort keys %connections;
5101
my ($cur) = get_connections($mode);
5103
my $pos = grep { $_ lt $cur } @cxns;
5104
my $newpos = ($pos + 1) % @cxns;
5105
$modes{$mode}->{server_group} = '';
5106
$modes{$mode}->{connections} = [ $cxns[$newpos] ];
5107
$clear_screen_sub->();
5110
sub next_server_group {
5111
my $mode = shift || $config{mode}->{val};
5112
my @grps = sort keys %server_groups;
5113
my $curr = $modes{$mode}->{server_group};
5115
return unless @grps;
5118
# Find the current group's position.
5120
while ( $curr ne $grps[$pos] ) {
5123
$modes{$mode}->{server_group} = $grps[ ($pos + 1) % @grps ];
5126
$modes{$mode}->{server_group} = $grps[0];
5130
# Get a list of connection names used in this mode.
5131
sub get_connections {
5135
my $mode = shift || $config{mode}->{val};
5136
my @connections = $modes{$mode}->{server_group}
5137
? @{$server_groups{$modes{$mode}->{server_group}}}
5138
: @{$modes{$mode}->{connections}};
5139
if ( $modes{$mode}->{one_connection} ) {
5140
@connections = @connections ? $connections[0] : ();
5142
return unique(@connections);
5145
# Get a list of tables used in this mode. If innotop is running non-interactively, just use the first.
5146
sub get_visible_tables {
5147
my $mode = shift || $config{mode}->{val};
5148
my @tbls = @{$modes{$mode}->{visible_tables}};
5157
# Choose from among available connections or server groups.
5158
# If the mode has a server set in use, prefers that instead.
5159
sub choose_connections {
5160
$clear_screen_sub->();
5161
my $mode = $config{mode}->{val};
5162
my $meta = { map { $_ => $connections{$_}->{dsn} } keys %connections };
5163
foreach my $group ( keys %server_groups ) {
5164
$meta->{"#$group"} = join(' ', @{$server_groups{$group}});
5167
my $choices = prompt_list("Choose connections or a group for $mode mode",
5168
undef, sub { return keys %$meta }, $meta);
5170
my @choices = unique(grep { $_ } split(/\s+/, $choices));
5172
if ( $choices[0] =~ s/^#// && exists $server_groups{$choices[0]} ) {
5173
$modes{$mode}->{server_group} = $choices[0];
5176
$modes{$mode}->{connections} = [ grep { exists $connections{$_} } @choices ];
5181
# Accepts a DB connection name and the name of a prepared query (e.g. status, kill).
5182
# Also a list of params for the prepared query. This allows not storing prepared
5183
# statements globally. Returns a $sth that's been executed.
5184
# ERROR-HANDLING SEMANTICS: if the statement throws an error, propagate, but if the
5185
# connection has gone away or can't connect, DO NOT. Just return undef.
5187
my ( $cxn, $stmt_name, @args ) = @_;
5189
return undef if $file;
5191
# Test if the cxn should not even be tried
5192
return undef if $dbhs{$cxn}
5193
&& $dbhs{$cxn}->{err_count}
5194
&& ( !$dbhs{$cxn}->{dbh} || !$dbhs{$cxn}->{dbh}->{Active} || $dbhs{$cxn}->{mode} eq $config{mode}->{val} )
5195
&& $dbhs{$cxn}->{wake_up} > $clock;
5201
while ( $retries-- >= 0 && !$success ) {
5204
my $dbh = connect_to_db($cxn);
5206
# If the prepared query doesn't exist, make it.
5207
if ( !exists $dbhs{$cxn}->{stmts}->{$stmt_name} ) {
5208
$dbhs{$cxn}->{stmts}->{$stmt_name} = $stmt_maker_for{$stmt_name}->($dbh);
5211
$sth = $dbhs{$cxn}->{stmts}->{$stmt_name};
5213
$sth->execute(@args);
5217
if ( $EVAL_ERROR ) {
5218
if ( $EVAL_ERROR =~ m/$nonfatal_errs/ ) {
5219
handle_cxn_error($cxn, $EVAL_ERROR);
5222
die "$cxn $stmt_name: $EVAL_ERROR";
5224
if ( $retries < 0 ) {
5230
if ( $sth && $sth->{NUM_OF_FIELDS} ) {
5231
sleep($stmt_sleep_time_for{$stmt_name}) if $stmt_sleep_time_for{$stmt_name};
5236
# Keeps track of error count, sleep times till retries, etc etc.
5237
# When there's an error we retry the connection every so often, increasing in
5238
# Fibonacci series to prevent too much banging on the server.
5239
sub handle_cxn_error {
5240
my ( $cxn, $err ) = @_;
5241
my $meta = $dbhs{$cxn};
5242
$meta->{err_count}++;
5244
# This is used so errors that have to do with permissions needed by the current
5245
# mode will get displayed as long as we're in this mode, but get ignored if the
5247
$meta->{mode} = $config{mode}->{val};
5249
# Strip garbage from the error text if possible.
5251
if ( $err =~ m/failed: (.*?) at \S*innotop line/ ) {
5255
$meta->{last_err} = $err;
5256
my $sleep_time = $meta->{this_sleep} + $meta->{prev_sleep};
5257
$meta->{prev_sleep} = $meta->{this_sleep};
5258
$meta->{this_sleep} = $sleep_time;
5259
$meta->{wake_up} = $clock + $sleep_time;
5260
if ( $config{show_cxn_errors}->{val} ) {
5261
print STDERR "Error at tick $clock $cxn $err" if $config{debug}->{val};
5265
# Accepts a DB connection name and a (string) query. Returns a $sth that's been
5268
my ( $cxn, $query ) = @_;
5270
return undef if $file;
5272
# Test if the cxn should not even be tried
5273
return undef if $dbhs{$cxn}
5274
&& $dbhs{$cxn}->{err_count}
5275
&& ( !$dbhs{$cxn}->{dbh} || !$dbhs{$cxn}->{dbh}->{Active} || $dbhs{$cxn}->{mode} eq $config{mode}->{val} )
5276
&& $dbhs{$cxn}->{wake_up} > $clock;
5282
while ( $retries-- >= 0 && !$success ) {
5285
my $dbh = connect_to_db($cxn);
5287
$sth = $dbh->prepare($query);
5291
if ( $EVAL_ERROR ) {
5292
if ( $EVAL_ERROR =~ m/$nonfatal_errs/ ) {
5293
handle_cxn_error($cxn, $EVAL_ERROR);
5298
if ( $retries < 0 ) {
5309
$dbhs{$cxn}->{start_time} ||= time();
5310
# Avoid dividing by zero
5311
return (time() - $dbhs{$cxn}->{start_time}) || .001;
5318
stmts => {}, # bucket for prepared statements.
5325
my $href = $dbhs{$cxn};
5327
if ( !$href->{dbh} || ref($href->{dbh}) !~ m/DBI/ || !$href->{dbh}->ping ) {
5328
my $dbh = get_new_db_connection($cxn);
5329
@{$href}{qw(dbh err_count wake_up this_sleep start_time prev_sleep)}
5330
= ($dbh, 0, 0, 1, 0, 0);
5332
# Derive and store the server's start time in hi-res
5333
my $uptime = $dbh->selectrow_hashref("show status like 'Uptime'")->{value};
5334
$href->{start_time} = time() - $uptime;
5336
# Set timeouts so an unused connection stays alive.
5337
# For example, a connection might be used in Q mode but idle in T mode.
5338
if ( version_ge($dbh, '4.0.3')) {
5339
my $timeout = $config{cxn_timeout}->{val};
5340
$dbh->do("set session wait_timeout=$timeout, interactive_timeout=$timeout");
5343
return $href->{dbh};
5346
# Compares versions like 5.0.27 and 4.1.15-standard-log
5348
my ( $dbh, $target ) = @_;
5349
my $version = sprintf('%03d%03d%03d', $dbh->{mysql_serverinfo} =~ m/(\d+)/g);
5350
return $version ge sprintf('%03d%03d%03d', $target =~ m/(\d+)/g);
5353
# Extracts status values that can be gleaned from the DBD driver without doing a whole query.
5354
sub get_driver_status {
5356
if ( !$info_gotten{driver_status}++ ) {
5357
foreach my $cxn ( @cxns ) {
5358
next unless $dbhs{$cxn} && $dbhs{$cxn}->{dbh} && $dbhs{$cxn}->{dbh}->{Active};
5359
$vars{$cxn}->{$clock} ||= {};
5360
my $vars = $vars{$cxn}->{$clock};
5361
my %res = map { $_ =~ s/ +/_/g; $_ } $dbhs{$cxn}->{dbh}->{mysql_stat} =~ m/(\w[^:]+): ([\d\.]+)/g;
5362
map { $vars->{$_} ||= $res{$_} } keys %res;
5363
$vars->{Uptime_hires} ||= get_uptime($cxn);
5364
$vars->{cxn} = $cxn;
5369
sub get_new_db_connection {
5370
my ( $connection, $destroy ) = @_;
5372
die "You can't connect to a MySQL server while monitoring a file. This is probably a bug.";
5375
my $dsn = $connections{$connection}
5376
or die "No connection named '$connection' is defined in your configuration";
5378
if ( !defined $dsn->{have_user} ) {
5379
my $answer = prompt("Do you want to specify a username for $connection?", undef, 'n');
5380
$dsn->{have_user} = $answer && $answer =~ m/1|y/i;
5383
if ( !defined $dsn->{have_pass} ) {
5384
my $answer = prompt("Do you want to specify a password for $connection?", undef, 'n');
5385
$dsn->{have_pass} = $answer && $answer =~ m/1|y/i;
5388
if ( !$dsn->{user} && $dsn->{have_user} ) {
5389
my $user = $ENV{USERNAME} || $ENV{USER} || getlogin() || getpwuid($REAL_USER_ID) || undef;
5390
$dsn->{user} = prompt("Enter username for $connection", undef, $user);
5393
if ( !defined $dsn->{user} ) {
5397
if ( !$dsn->{pass} && !$dsn->{savepass} && $dsn->{have_pass} ) {
5398
$dsn->{pass} = prompt_noecho("Enter password for '$dsn->{user}' on $connection");
5400
if ( !defined($dsn->{savepass}) ) {
5401
my $answer = prompt("Save password in plain text in the config file?", undef, 'y');
5402
$dsn->{savepass} = $answer && $answer =~ m/1|y/i;
5406
my $dbh = DBI->connect(
5407
$dsn->{dsn}, $dsn->{user}, $dsn->{pass},
5408
{ RaiseError => 1, PrintError => 0, AutoCommit => 1 });
5409
$dbh->{InactiveDestroy} = 1 unless $destroy; # Can't be set in $db_options
5410
$dbh->{FetchHashKeyName} = 'NAME_lc'; # Lowercases all column names for fetchrow_hashref
5414
sub get_cxn_errors {
5416
return () unless $config{show_cxn_errors_in_tbl}->{val};
5418
map { [ $_ . ': ' . $dbhs{$_}->{last_err}, 'red' ] }
5419
grep { $dbhs{$_} && $dbhs{$_}->{err_count} && $dbhs{$_}->{mode} eq $config{mode}->{val} }
5423
# Setup and tear-down functions {{{2
5425
# Takes a string and turns it into a hashref you can apply to %tbl_meta tables. The string
5426
# can be in the form 'foo, bar, foo/bar, foo as bar' much like a SQL SELECT statement.
5427
sub compile_select_stmt {
5429
my @exps = $str =~ m/\s*([^,]+(?i:\s+as\s+[^,\s]+)?)\s*(?=,|$)/g;
5432
foreach my $exp ( @exps ) {
5433
my ( $text, $colname );
5434
if ( $exp =~ m/as\s+(\w+)\s*/ ) {
5436
$exp =~ s/as\s+(\w+)\s*//;
5440
$text = $colname = $exp;
5442
my ($func, $err) = compile_expr($text);
5449
push @visible, $colname;
5451
return (\%cols, \@visible);
5454
# compile_filter {{{3
5455
sub compile_filter {
5458
eval "\$sub = sub { my \$set = shift; $text }";
5459
if ( $EVAL_ERROR ) {
5460
$EVAL_ERROR =~ s/at \(eval.*$//;
5461
$sub = sub { return $EVAL_ERROR };
5464
return ( $sub, $err );
5470
# Leave built-in functions alone so they get called as Perl functions, unless
5471
# they are the only word in $expr, in which case treat them as hash keys.
5472
if ( $expr =~ m/\W/ ) {
5473
$expr =~ s/(?<!\{|\$)\b([A-Za-z]\w{2,})\b/is_func($1) ? $1 : "\$set->{$1}"/eg;
5476
$expr = "\$set->{$expr}";
5479
my $quoted = quotemeta($expr);
5482
my (\$set, \$cur, \$pre) = \@_;
5483
my \$val = eval { $expr };
5484
if ( \$EVAL_ERROR && \$config{debug}->{val} ) {
5485
\$EVAL_ERROR =~ s/ at \\(eval.*//s;
5486
die "\$EVAL_ERROR in expression $quoted";
5491
if ( $EVAL_ERROR ) {
5492
if ( $config{debug}->{val} ) {
5495
$EVAL_ERROR =~ s/ at \(eval.*$//;
5496
$sub = sub { return $EVAL_ERROR };
5499
return ( $sub, $err );
5503
# This is a subroutine because it's called from a key to quit the program.
5506
ReadMode('normal') unless $opts{n};
5514
if ($config{debugfile}->{val} && $config{debug}->{val}) {
5516
open my $file, '>>', $config{debugfile}->{val};
5518
print $file "Current variables:\n" . Dumper(\%vars);
5529
my $filename = $opts{c} || "$homepath/.innotop/innotop.ini";
5530
my $dirname = dirname($filename);
5531
if ( -f $dirname && !$opts{c} ) {
5532
# innotop got upgraded and this is the old config file.
5533
my $answer = pause("Innotop's default config location has moved to $filename. Move old config file $dirname there now? y/n");
5534
if ( lc $answer eq 'y' ) {
5535
rename($dirname, "$homepath/innotop.ini")
5536
or die "Can't rename '$dirname': $OS_ERROR";
5537
mkdir($dirname) or die "Can't create directory '$dirname': $OS_ERROR";
5538
mkdir("$dirname/plugins") or die "Can't create directory '$dirname/plugins': $OS_ERROR";
5539
rename("$homepath/innotop.ini", $filename)
5540
or die "Can't rename '$homepath/innotop.ini' to '$filename': $OS_ERROR";
5543
print "\nInnotop will now exit so you can fix the config file.\n";
5548
if ( ! -d $dirname ) {
5550
or die "Can't create directory '$dirname': $OS_ERROR";
5552
if ( ! -d "$dirname/plugins" ) {
5553
mkdir "$dirname/plugins"
5554
or die "Can't create directory '$dirname/plugins': $OS_ERROR";
5557
if ( -f $filename ) {
5558
open my $file, "<", $filename or die("Can't open '$filename': $OS_ERROR");
5560
# Check config file version. Just ignore if either innotop or the file has
5561
# garbage in the version number.
5562
if ( defined(my $line = <$file>) && $VERSION =~ m/\d/ ) {
5564
if ( my ($maj, $min, $rev) = $line =~ m/^version=(\d+)\.(\d+)(?:\.(\d+))?$/ ) {
5566
my $cfg_ver = sprintf('%03d-%03d-%03d', $maj, $min, $rev);
5567
( $maj, $min, $rev ) = $VERSION =~ m/^(\d+)\.(\d+)(?:\.(\d+))?$/;
5569
my $innotop_ver = sprintf('%03d-%03d-%03d', $maj, $min, $rev);
5571
if ( $cfg_ver gt $innotop_ver ) {
5572
pause("The config file is for a newer version of innotop and may not be read correctly.");
5575
my @ver_history = @config_versions;
5576
while ( my ($start, $end) = splice(@ver_history, 0, 2) ) {
5577
# If the config file is between the endpoints and innotop is greater than
5578
# the endpoint, innotop has a newer config file format than the file.
5579
if ( $cfg_ver ge $start && $cfg_ver lt $end && $innotop_ver ge $end ) {
5580
my $msg = "innotop's config file format has changed. Overwrite $filename? y or n";
5581
if ( pause($msg) eq 'n' ) {
5582
$config{readonly}->{val} = 1;
5583
print "\ninnotop will not save any configuration changes you make.";
5595
while ( my $line = <$file> ) {
5597
next unless $line =~ m/^\[([a-z_]+)\]$/;
5598
if ( exists $config_file_sections{$1} ) {
5599
$config_file_sections{$1}->{reader}->($file);
5602
warn "Unknown config file section '$1'";
5605
close $file or die("Can't close $filename: $OS_ERROR");
5610
# Do some post-processing on %tbl_meta: compile src properties into func etc.
5611
sub post_process_tbl_meta {
5612
foreach my $table ( values %tbl_meta ) {
5613
foreach my $col_name ( keys %{$table->{cols}} ) {
5614
my $col_def = $table->{cols}->{$col_name};
5615
my ( $sub, $err ) = compile_expr($col_def->{src});
5616
$col_def->{func} = $sub;
5621
# load_config_plugins {{{3
5622
sub load_config_plugins {
5625
# First, find a list of all plugins that exist on disk, and get information about them.
5626
my $dir = $config{plugin_dir}->{val};
5627
foreach my $p_file ( <$dir/*.pm> ) {
5628
my ($package, $desc);
5630
open my $p_in, "<", $p_file or die $OS_ERROR;
5631
while ( my $line = <$p_in> ) {
5633
if ( $line =~ m/^package\s+(.*?);/ ) {
5636
elsif ( $line =~ m/^# description: (.*)/ ) {
5639
last if $package && $desc;
5644
$plugins{$package} = {
5650
if ( $config{debug}->{val} && $EVAL_ERROR ) {
5656
# Now read which ones the user has activated. Each line simply represents an active plugin.
5657
while ( my $line = <$file> ) {
5659
next if $line =~ m/^#/;
5660
last if $line =~ m/^\[/;
5661
next unless $line && $plugins{$line};
5665
require $plugins{$line}->{file};
5666
$obj = $line->new(%pluggable_vars);
5667
foreach my $event ( $obj->register_for_events() ) {
5668
my $queue = $event_listener_for{$event};
5674
if ( $config{debug}->{val} && $EVAL_ERROR ) {
5678
$plugins{$line}->{active} = 1;
5679
$plugins{$line}->{object} = $obj;
5684
# save_config_plugins {{{3
5685
sub save_config_plugins {
5687
foreach my $class ( sort keys %plugins ) {
5688
next unless $plugins{$class}->{active};
5689
print $file "$class\n";
5693
# load_config_active_server_groups {{{3
5694
sub load_config_active_server_groups {
5696
while ( my $line = <$file> ) {
5698
next if $line =~ m/^#/;
5699
last if $line =~ m/^\[/;
5701
my ( $mode, $group ) = $line =~ m/^(.*?)=(.*)$/;
5702
next unless $mode && $group
5703
&& exists $modes{$mode} && exists $server_groups{$group};
5704
$modes{$mode}->{server_group} = $group;
5708
# save_config_active_server_groups {{{3
5709
sub save_config_active_server_groups {
5711
foreach my $mode ( sort keys %modes ) {
5712
print $file "$mode=$modes{$mode}->{server_group}\n";
5716
# load_config_server_groups {{{3
5717
sub load_config_server_groups {
5719
while ( my $line = <$file> ) {
5721
next if $line =~ m/^#/;
5722
last if $line =~ m/^\[/;
5724
my ( $name, $rest ) = $line =~ m/^(.*?)=(.*)$/;
5725
next unless $name && $rest;
5726
my @vars = unique(grep { $_ && exists $connections{$_} } split(/\s+/, $rest));
5728
$server_groups{$name} = \@vars;
5732
# save_config_server_groups {{{3
5733
sub save_config_server_groups {
5735
foreach my $set ( sort keys %server_groups ) {
5736
print $file "$set=", join(' ', @{$server_groups{$set}}), "\n";
5740
# load_config_varsets {{{3
5741
sub load_config_varsets {
5743
while ( my $line = <$file> ) {
5745
next if $line =~ m/^#/;
5746
last if $line =~ m/^\[/;
5748
my ( $name, $rest ) = $line =~ m/^(.*?)=(.*)$/;
5749
next unless $name && $rest;
5750
$var_sets{$name} = {
5757
# save_config_varsets {{{3
5758
sub save_config_varsets {
5760
foreach my $varset ( sort keys %var_sets ) {
5761
next unless $var_sets{$varset}->{user};
5762
print $file "$varset=$var_sets{$varset}->{text}\n";
5766
# load_config_group_by {{{3
5767
sub load_config_group_by {
5769
while ( my $line = <$file> ) {
5771
next if $line =~ m/^#/;
5772
last if $line =~ m/^\[/;
5774
my ( $tbl , $rest ) = $line =~ m/^(.*?)=(.*)$/;
5775
next unless $tbl && exists $tbl_meta{$tbl};
5776
my @parts = unique(grep { exists($tbl_meta{$tbl}->{cols}->{$_}) } split(/\s+/, $rest));
5777
$tbl_meta{$tbl}->{group_by} = [ @parts ];
5778
$tbl_meta{$tbl}->{cust}->{group_by} = 1;
5782
# save_config_group_by {{{3
5783
sub save_config_group_by {
5785
foreach my $tbl ( sort keys %tbl_meta ) {
5786
next if $tbl_meta{$tbl}->{temp};
5787
next unless $tbl_meta{$tbl}->{cust}->{group_by};
5788
my $aref = $tbl_meta{$tbl}->{group_by};
5789
print $file "$tbl=", join(' ', @$aref), "\n";
5793
# load_config_filters {{{3
5794
sub load_config_filters {
5796
while ( my $line = <$file> ) {
5798
next if $line =~ m/^#/;
5799
last if $line =~ m/^\[/;
5801
my ( $key, $rest ) = $line =~ m/^(.+?)=(.*)$/;
5802
next unless $key && $rest;
5804
my %parts = $rest =~ m/(\w+)='((?:(?!(?<!\\)').)*)'/g; # Properties are single-quoted
5805
next unless $parts{text} && $parts{tbls};
5807
foreach my $prop ( keys %parts ) {
5808
# Un-escape escaping
5809
$parts{$prop} =~ s/\\\\/\\/g;
5810
$parts{$prop} =~ s/\\'/'/g;
5813
my ( $sub, $err ) = compile_filter($parts{text});
5814
my @tbls = unique(split(/\s+/, $parts{tbls}));
5815
@tbls = grep { exists $tbl_meta{$_} } @tbls;
5818
text => $parts{text},
5821
note => 'User-defined filter',
5827
# save_config_filters {{{3
5828
sub save_config_filters {
5830
foreach my $key ( sort keys %filters ) {
5831
next if !$filters{$key}->{user} || $filters{$key}->{quick};
5832
my $text = $filters{$key}->{text};
5833
$text =~ s/([\\'])/\\$1/g;
5834
my $tbls = join(" ", @{$filters{$key}->{tbls}});
5835
print $file "$key=text='$text' tbls='$tbls'\n";
5839
# load_config_visible_tables {{{3
5840
sub load_config_visible_tables {
5842
while ( my $line = <$file> ) {
5844
next if $line =~ m/^#/;
5845
last if $line =~ m/^\[/;
5847
my ( $mode, $rest ) = $line =~ m/^(.*?)=(.*)$/;
5848
next unless $mode && exists $modes{$mode};
5849
$modes{$mode}->{visible_tables} =
5850
[ unique(grep { $_ && exists $tbl_meta{$_} } split(/\s+/, $rest)) ];
5851
$modes{$mode}->{cust}->{visible_tables} = 1;
5855
# save_config_visible_tables {{{3
5856
sub save_config_visible_tables {
5858
foreach my $mode ( sort keys %modes ) {
5859
next unless $modes{$mode}->{cust}->{visible_tables};
5860
my $tables = $modes{$mode}->{visible_tables};
5861
print $file "$mode=", join(' ', @$tables), "\n";
5865
# load_config_sort_cols {{{3
5866
sub load_config_sort_cols {
5868
while ( my $line = <$file> ) {
5870
next if $line =~ m/^#/;
5871
last if $line =~ m/^\[/;
5873
my ( $key , $rest ) = $line =~ m/^(.*?)=(.*)$/;
5874
next unless $key && exists $tbl_meta{$key};
5875
$tbl_meta{$key}->{sort_cols} = $rest;
5876
$tbl_meta{$key}->{cust}->{sort_cols} = 1;
5877
$tbl_meta{$key}->{sort_func} = make_sort_func($tbl_meta{$key});
5881
# save_config_sort_cols {{{3
5882
sub save_config_sort_cols {
5884
foreach my $tbl ( sort keys %tbl_meta ) {
5885
next unless $tbl_meta{$tbl}->{cust}->{sort_cols};
5886
my $col = $tbl_meta{$tbl}->{sort_cols};
5887
print $file "$tbl=$col\n";
5891
# load_config_active_filters {{{3
5892
sub load_config_active_filters {
5894
while ( my $line = <$file> ) {
5896
next if $line =~ m/^#/;
5897
last if $line =~ m/^\[/;
5899
my ( $tbl , $rest ) = $line =~ m/^(.*?)=(.*)$/;
5900
next unless $tbl && exists $tbl_meta{$tbl};
5901
my @parts = unique(grep { exists($filters{$_}) } split(/\s+/, $rest));
5902
@parts = grep { grep { $tbl eq $_ } @{$filters{$_}->{tbls}} } @parts;
5903
$tbl_meta{$tbl}->{filters} = [ @parts ];
5904
$tbl_meta{$tbl}->{cust}->{filters} = 1;
5908
# save_config_active_filters {{{3
5909
sub save_config_active_filters {
5911
foreach my $tbl ( sort keys %tbl_meta ) {
5912
next if $tbl_meta{$tbl}->{temp};
5913
next unless $tbl_meta{$tbl}->{cust}->{filters};
5914
my $aref = $tbl_meta{$tbl}->{filters};
5915
print $file "$tbl=", join(' ', @$aref), "\n";
5919
# load_config_active_columns {{{3
5920
sub load_config_active_columns {
5922
while ( my $line = <$file> ) {
5924
next if $line =~ m/^#/;
5925
last if $line =~ m/^\[/;
5927
my ( $key , $rest ) = $line =~ m/^(.*?)=(.*)$/;
5928
next unless $key && exists $tbl_meta{$key};
5929
my @parts = grep { exists($tbl_meta{$key}->{cols}->{$_}) } unique split(/ /, $rest);
5930
$tbl_meta{$key}->{visible} = [ @parts ];
5931
$tbl_meta{$key}->{cust}->{visible} = 1;
5935
# save_config_active_columns {{{3
5936
sub save_config_active_columns {
5938
foreach my $tbl ( sort keys %tbl_meta ) {
5939
next unless $tbl_meta{$tbl}->{cust}->{visible};
5940
my $aref = $tbl_meta{$tbl}->{visible};
5941
print $file "$tbl=", join(' ', @$aref), "\n";
5945
# save_config_tbl_meta {{{3
5946
sub save_config_tbl_meta {
5948
foreach my $tbl ( sort keys %tbl_meta ) {
5949
foreach my $col ( keys %{$tbl_meta{$tbl}->{cols}} ) {
5950
my $meta = $tbl_meta{$tbl}->{cols}->{$col};
5951
next unless $meta->{user};
5952
print $file "$col=", join(
5955
# Some properties (trans) are arrays, others scalars
5956
my $val = ref($meta->{$_}) ? join(',', @{$meta->{$_}}) : $meta->{$_};
5957
$val =~ s/([\\'])/\\$1/g; # Escape backslashes and single quotes
5958
"$_='$val'"; # Enclose in single quotes
5960
grep { $_ ne 'func' }
5967
# save_config_config {{{3
5968
sub save_config_config {
5970
foreach my $key ( sort keys %config ) {
5972
if ( $key ne 'password' || $config{savepass}->{val} ) {
5973
print $file "# $config{$key}->{note}\n"
5974
or die "Cannot print to file: $OS_ERROR";
5975
my $val = $config{$key}->{val};
5976
$val = '' unless defined($val);
5977
if ( ref( $val ) eq 'ARRAY' ) {
5979
. join( " ", @$val ) . "\n"
5980
or die "Cannot print to file: $OS_ERROR";
5982
elsif ( ref( $val ) eq 'HASH' ) {
5985
map { "$_:$val->{$_}" } keys %$val
5989
print $file "$key=$val\n";
5993
if ( $EVAL_ERROR ) { print "$EVAL_ERROR in $key"; };
5998
# load_config_config {{{3
5999
sub load_config_config {
6002
# Look in the command-line parameters for things stored in the same slot.
6004
map { $_->{c} => $opts{$_->{k}} }
6005
grep { exists $_->{c} && exists $opts{$_->{k}} }
6008
while ( my $line = <$file> ) {
6010
next if $line =~ m/^#/;
6011
last if $line =~ m/^\[/;
6013
my ( $name, $val ) = $line =~ m/^(.+?)=(.*)$/;
6014
next unless defined $name && defined $val;
6016
# Values might already have been set at the command line.
6017
$val = defined($cmdline{$name}) ? $cmdline{$name} : $val;
6019
# Validate the incoming values...
6020
if ( $name && exists( $config{$name} ) ) {
6021
if ( !$config{$name}->{pat} || $val =~ m/$config{$name}->{pat}/ ) {
6022
$config{$name}->{val} = $val;
6023
$config{$name}->{read} = 1;
6029
# load_config_tbl_meta {{{3
6030
sub load_config_tbl_meta {
6033
while ( my $line = <$file> ) {
6035
next if $line =~ m/^#/;
6036
last if $line =~ m/^\[/;
6038
# Each tbl_meta section has all the properties defined in %col_props.
6039
my ( $col , $rest ) = $line =~ m/^(.*?)=(.*)$/;
6041
my %parts = $rest =~ m/(\w+)='((?:(?!(?<!\\)').)*)'/g; # Properties are single-quoted
6043
# Each section read from the config file has one extra property: which table it
6045
my $tbl = $parts{tbl} or die "There's no table for tbl_meta $col";
6046
my $meta = $tbl_meta{$tbl} or die "There's no table in tbl_meta named $tbl";
6048
# The section is user-defined by definition (if that makes sense).
6051
# The column may already exist in the table, in which case this is just a
6053
$meta->{cols}->{$col} ||= {};
6055
foreach my $prop ( keys %col_props ) {
6056
if ( !defined($parts{$prop}) ) {
6057
die "Undefined property $prop for column $col in table $tbl";
6060
# Un-escape escaping
6061
$parts{$prop} =~ s/\\\\/\\/g;
6062
$parts{$prop} =~ s/\\'/'/g;
6064
if ( ref $col_props{$prop} ) {
6065
if ( $prop eq 'trans' ) {
6066
$meta->{cols}->{$col}->{trans}
6067
= [ unique(grep { exists $trans_funcs{$_} } split(',', $parts{$prop})) ];
6070
$meta->{cols}->{$col}->{$prop} = [ split(',', $parts{$prop}) ];
6074
$meta->{cols}->{$col}->{$prop} = $parts{$prop};
6083
return if $config{readonly}->{val};
6084
# Save to a temp file first, so a crash doesn't destroy the main config file
6085
my $newname = $opts{c} || "$homepath/.innotop/innotop.ini";
6086
my $filename = $newname . '_tmp';
6087
open my $file, "+>", $filename
6088
or die("Can't write to $filename: $OS_ERROR");
6089
print $file "version=$VERSION\n";
6091
foreach my $section ( @ordered_config_file_sections ) {
6092
die "No such config file section $section" unless $config_file_sections{$section};
6093
print $file "\n[$section]\n\n";
6094
$config_file_sections{$section}->{writer}->($file);
6095
print $file "\n[/$section]\n";
6098
# Now clobber the main config file with the temp.
6099
close $file or die("Can't close $filename: $OS_ERROR");
6100
rename($filename, $newname) or die("Can't rename $filename to $newname: $OS_ERROR");
6103
# load_config_connections {{{3
6104
sub load_config_connections {
6106
while ( my $line = <$file> ) {
6108
next if $line =~ m/^#/;
6109
last if $line =~ m/^\[/;
6111
my ( $key , $rest ) = $line =~ m/^(.*?)=(.*)$/;
6113
my %parts = $rest =~ m/(\S+?)=(\S*)/g;
6114
my %conn = map { $_ => $parts{$_} || '' } @conn_parts;
6115
$connections{$key} = \%conn;
6119
# save_config_connections {{{3
6120
sub save_config_connections {
6122
foreach my $conn ( sort keys %connections ) {
6123
my $href = $connections{$conn};
6124
my @keys = $href->{savepass} ? @conn_parts : grep { $_ ne 'pass' } @conn_parts;
6125
print $file "$conn=", join(' ', map { "$_=$href->{$_}" } grep { defined $href->{$_} } @keys), "\n";
6129
sub load_config_colors {
6133
while ( my $line = <$file> ) {
6135
next if $line =~ m/^#/;
6136
last if $line =~ m/^\[/;
6138
my ( $tbl, $rule ) = $line =~ m/^(.*?)=(.*)$/;
6139
next unless $tbl && $rule;
6140
next unless exists $tbl_meta{$tbl};
6141
my %parts = $rule =~ m/(\w+)='((?:(?!(?<!\\)').)*)'/g; # Properties are single-quoted
6142
next unless $parts{col} && exists $tbl_meta{$tbl}->{cols}->{$parts{col}};
6143
next unless $parts{op} && exists $comp_ops{$parts{op}};
6144
next unless defined $parts{arg};
6145
next unless defined $parts{color};
6146
my @colors = unique(grep { exists $ansicolors{$_} } split(/\W+/, $parts{color}));
6147
next unless @colors;
6149
# Finally! Enough validation...
6150
$rule_set_for{$tbl} ||= [];
6151
push @{$rule_set_for{$tbl}}, \%parts;
6154
foreach my $tbl ( keys %rule_set_for ) {
6155
$tbl_meta{$tbl}->{colors} = $rule_set_for{$tbl};
6156
$tbl_meta{$tbl}->{color_func} = make_color_func($tbl_meta{$tbl});
6157
$tbl_meta{$tbl}->{cust}->{colors} = 1;
6161
# save_config_colors {{{3
6162
sub save_config_colors {
6164
foreach my $tbl ( sort keys %tbl_meta ) {
6165
my $meta = $tbl_meta{$tbl};
6166
next unless $meta->{cust}->{colors};
6167
foreach my $rule ( @{$meta->{colors}} ) {
6168
print $file "$tbl=", join(
6171
my $val = $rule->{$_};
6172
$val =~ s/([\\'])/\\$1/g; # Escape backslashes and single quotes
6173
"$_='$val'"; # Enclose in single quotes
6175
qw(col op arg color)
6181
# load_config_active_connections {{{3
6182
sub load_config_active_connections {
6184
while ( my $line = <$file> ) {
6186
next if $line =~ m/^#/;
6187
last if $line =~ m/^\[/;
6189
my ( $key , $rest ) = $line =~ m/^(.*?)=(.*)$/;
6190
next unless $key && exists $modes{$key};
6191
my @parts = grep { exists $connections{$_} } split(/ /, $rest);
6192
$modes{$key}->{connections} = [ @parts ] if exists $modes{$key};
6196
# save_config_active_connections {{{3
6197
sub save_config_active_connections {
6199
foreach my $mode ( sort keys %modes ) {
6200
my @connections = get_connections($mode);
6201
print $file "$mode=", join(' ', @connections), "\n";
6205
# load_config_stmt_sleep_times {{{3
6206
sub load_config_stmt_sleep_times {
6208
while ( my $line = <$file> ) {
6210
next if $line =~ m/^#/;
6211
last if $line =~ m/^\[/;
6213
my ( $key , $val ) = split('=', $line);
6214
next unless $key && defined $val && $val =~ m/$num_regex/;
6215
$stmt_sleep_time_for{$key} = $val;
6219
# save_config_stmt_sleep_times {{{3
6220
sub save_config_stmt_sleep_times {
6222
foreach my $key ( sort keys %stmt_sleep_time_for ) {
6223
print $file "$key=$stmt_sleep_time_for{$key}\n";
6227
# load_config_mvs {{{3
6228
sub load_config_mvs {
6230
while ( my $line = <$file> ) {
6232
next if $line =~ m/^#/;
6233
last if $line =~ m/^\[/;
6235
my ( $key , $val ) = split('=', $line);
6236
next unless $key && defined $val && $val =~ m/$num_regex/;
6241
# save_config_mvs {{{3
6242
sub save_config_mvs {
6244
foreach my $key ( sort keys %mvs ) {
6245
print $file "$key=$mvs{$key}\n";
6249
# edit_configuration {{{3
6250
sub edit_configuration {
6252
while ( $key ne 'q' ) {
6253
$clear_screen_sub->();
6254
my @display_lines = '';
6256
if ( $key && $cfg_editor_action{$key} ) {
6257
$cfg_editor_action{$key}->{func}->();
6261
push @display_lines, create_caption('What configuration do you want to edit?',
6263
[ sort keys %cfg_editor_action ],
6264
{ map { $_ => $_ } keys %cfg_editor_action },
6265
{ map { $_ => $cfg_editor_action{$_}->{note} } keys %cfg_editor_action },
6268
draw_screen(\@display_lines);
6273
# edit_configuration_variables {{{3
6274
sub edit_configuration_variables {
6275
$clear_screen_sub->();
6276
my $mode = $config{mode}->{val};
6279
= map { $_ => $config{$_}->{note} || '' }
6280
# Only config values that are marked as applying to this mode.
6283
$config{$key}->{conf} &&
6284
( $config{$key}->{conf} eq 'ALL'
6285
|| grep { $mode eq $_ } @{$config{$key}->{conf}} )
6288
my $key = prompt_list(
6289
"Enter the name of the variable you wish to configure",
6291
sub{ return keys %config_choices },
6294
if ( exists($config_choices{$key}) ) {
6295
get_config_interactive($key);
6299
# edit_color_rules {{{3
6300
sub edit_color_rules {
6302
$clear_screen_sub->();
6303
$tbl ||= choose_visible_table();
6304
if ( $tbl && exists($tbl_meta{$tbl}) ) {
6305
my $meta = $tbl_meta{$tbl};
6306
my @cols = ('', qw(col op arg color));
6307
my $info = { map { $_ => { hdr => $_, just => '-', } } @cols };
6308
$info->{label}->{maxw} = 30;
6312
# This loop builds a tabular view of the rules.
6316
if ( $key && $key eq '?' ) {
6317
my @display_lines = '';
6318
push @display_lines, create_caption('Editor key mappings',
6320
[ sort keys %color_editor_action ],
6321
{ map { $_ => $_ } keys %color_editor_action },
6322
{ map { $_ => $color_editor_action{$_}->{note} } keys %color_editor_action },
6324
draw_screen(\@display_lines);
6330
# Do the action specified
6331
$selected_rule ||= 0;
6332
if ( $key && $color_editor_action{$key} ) {
6333
$selected_rule = $color_editor_action{$key}->{func}->($tbl, $selected_rule);
6334
$selected_rule ||= 0;
6337
# Build the table of rules. If the terminal has color, the selected rule
6338
# will be highlighted; otherwise a > at the left will indicate.
6339
my $data = $meta->{colors} || [];
6340
foreach my $i ( 0..@$data - 1 ) {
6341
$data->[$i]->{''} = $i == $selected_rule ? '>' : '';
6343
my @display_lines = create_table(\@cols, $info, $data);
6345
# Highlight selected entry
6346
for my $i ( 0 .. $#display_lines ) {
6347
if ( $display_lines[$i] =~ m/^>/ ) {
6348
$display_lines[$i] = [ $display_lines[$i], 'reverse' ];
6352
# Draw the screen and wait for a command.
6353
unshift @display_lines, '',
6354
"Editing color rules for $meta->{capt}. Press ? for help, q to "
6356
draw_screen(\@display_lines);
6357
print "\n\n", word_wrap('Rules are applied in order from top to '
6358
. 'bottom. The first matching rule wins and prevents the '
6359
. 'rest of the rules from being applied.');
6362
} while ( $key ne 'q' );
6363
$meta->{color_func} = make_color_func($meta);
6367
# add_quick_filter {{{3
6368
sub add_quick_filter {
6369
my $tbl = choose_visible_table();
6370
if ( $tbl && exists($tbl_meta{$tbl}) ) {
6372
my $response = prompt_list(
6373
"Enter column name and filter text",
6375
sub { return keys %{$tbl_meta{$tbl}->{cols}} },
6378
my ( $col, $text ) = split(/\s+/, $response, 2);
6380
# You can't filter on a nonexistent column. But if you filter on a pivoted
6381
# table, the columns are different, so on a pivoted table, allow filtering
6382
# on the 'name' column.
6383
# NOTE: if a table is pivoted and un-pivoted, this will likely cause crashes.
6384
# Currently not an issue since there's no way to toggle pivot/nopivot.
6385
return unless $col && $text &&
6386
(exists($tbl_meta{$tbl}->{cols}->{$col})
6387
|| ($tbl_meta{$tbl}->{pivot} && $col eq 'name'));
6389
my ( $sub, $err ) = compile_filter( "defined \$set->{$col} && \$set->{$col} =~ m/$text/" );
6390
return if !$sub || $err;
6391
my $name = "quick_$tbl.$col";
6398
note => 'Quick-filter',
6401
push @{$tbl_meta{$tbl}->{filters}}, $name;
6405
# clear_quick_filters {{{3
6406
sub clear_quick_filters {
6407
my $tbl = choose_visible_table(
6408
# Only tables that have quick-filters
6411
return scalar grep { $filters{$_}->{quick} } @{ $tbl_meta{$tbl}->{filters} };
6414
if ( $tbl && exists($tbl_meta{$tbl}) ) {
6415
my @current = @{$tbl_meta{$tbl}->{filters}};
6416
@current = grep { !$filters{$_}->{quick} } @current;
6417
$tbl_meta{$tbl}->{filters} = \@current;
6422
$clear_screen_sub->();
6424
my @cols = ('', qw(class desc active));
6425
my $info = { map { $_ => { hdr => $_, just => '-', } } @cols };
6426
my @rows = map { $plugins{$_} } sort keys %plugins;
6430
# This loop builds a tabular view of the plugins.
6434
if ( $key && $key eq '?' ) {
6435
my @display_lines = '';
6436
push @display_lines, create_caption('Editor key mappings',
6438
[ sort keys %plugin_editor_action ],
6439
{ map { $_ => $_ } keys %plugin_editor_action },
6440
{ map { $_ => $plugin_editor_action{$_}->{note} } keys %plugin_editor_action },
6442
draw_screen(\@display_lines);
6447
# Do the action specified
6450
if ( $key && $plugin_editor_action{$key} ) {
6451
$selected = $plugin_editor_action{$key}->{func}->(\@rows, $selected);
6455
# Build the table of plugins.
6456
foreach my $row ( 0.. $#rows ) {
6457
$rows[$row]->{''} = $row eq $selected ? '>' : ' ';
6459
my @display_lines = create_table(\@cols, $info, \@rows);
6461
# Highlight selected entry
6462
for my $i ( 0 .. $#display_lines ) {
6463
if ( $display_lines[$i] =~ m/^>/ ) {
6464
$display_lines[$i] = [ $display_lines[$i], 'reverse' ];
6468
# Draw the screen and wait for a command.
6469
unshift @display_lines, '',
6470
"Plugin Management. Press ? for help, q to quit.", '';
6471
draw_screen(\@display_lines);
6474
} while ( $key ne 'q' );
6479
$clear_screen_sub->();
6481
$tbl ||= choose_visible_table();
6482
if ( $tbl && exists($tbl_meta{$tbl}) ) {
6483
my $meta = $tbl_meta{$tbl};
6484
my @cols = ('', qw(name hdr label src));
6485
my $info = { map { $_ => { hdr => $_, just => '-', } } @cols };
6486
$info->{label}->{maxw} = 30;
6488
my $selected_column;
6490
# This loop builds a tabular view of the tbl_meta's structure, showing each column
6491
# in the entry as a row.
6495
if ( $key && $key eq '?' ) {
6496
my @display_lines = '';
6497
push @display_lines, create_caption('Editor key mappings',
6499
[ sort keys %tbl_editor_action ],
6500
{ map { $_ => $_ } keys %tbl_editor_action },
6501
{ map { $_ => $tbl_editor_action{$_}->{note} } keys %tbl_editor_action },
6503
draw_screen(\@display_lines);
6509
# Do the action specified
6510
$selected_column ||= $meta->{visible}->[0];
6511
if ( $key && $tbl_editor_action{$key} ) {
6512
$selected_column = $tbl_editor_action{$key}->{func}->($tbl, $selected_column);
6513
$selected_column ||= $meta->{visible}->[0];
6516
# Build the pivoted view of the table's meta-data. If the terminal has color,
6517
# The selected row will be highlighted; otherwise a > at the left will indicate.
6519
foreach my $row ( @{$meta->{visible}} ) {
6521
@hash{ @cols } = @{$meta->{cols}->{$row}}{@cols};
6522
$hash{src} = '' if ref $hash{src};
6524
$hash{''} = $row eq $selected_column ? '>' : ' ';
6525
push @$data, \%hash;
6527
my @display_lines = create_table(\@cols, $info, $data);
6529
# Highlight selected entry
6530
for my $i ( 0 .. $#display_lines ) {
6531
if ( $display_lines[$i] =~ m/^>/ ) {
6532
$display_lines[$i] = [ $display_lines[$i], 'reverse' ];
6536
# Draw the screen and wait for a command.
6537
unshift @display_lines, '',
6538
"Editing table definition for $meta->{capt}. Press ? for help, q to quit.", '';
6539
draw_screen(\@display_lines, { clear => 1 });
6542
} while ( $key ne 'q' );
6546
# choose_mode_tables {{{3
6547
# Choose which table(s), and in what order, to display in a given mode.
6548
sub choose_mode_tables {
6549
my $mode = $config{mode}->{val};
6550
my @tbls = @{$modes{$mode}->{visible_tables}};
6551
my $new = prompt_list(
6552
"Choose tables to display",
6554
sub { return @{$modes{$mode}->{tables}} },
6555
{ map { $_ => $tbl_meta{$_}->{capt} } @{$modes{$mode}->{tables}} }
6557
$modes{$mode}->{visible_tables} =
6558
[ unique(grep { $_ && exists $tbl_meta{$_} } split(/\s+/, $new)) ];
6559
$modes{$mode}->{cust}->{visible_tables} = 1;
6562
# choose_visible_table {{{3
6563
sub choose_visible_table {
6564
my ( $grep_cond ) = @_;
6565
my $mode = $config{mode}->{val};
6567
= grep { $grep_cond ? $grep_cond->($_) : 1 }
6568
@{$modes{$mode}->{visible_tables}};
6574
sub { return @tbls },
6575
{ map { $_ => $tbl_meta{$_}->{capt} } @tbls }
6581
sub toggle_aggregate {
6583
$tbl ||= choose_visible_table();
6584
return unless $tbl && exists $tbl_meta{$tbl};
6585
my $meta = $tbl_meta{$tbl};
6586
$meta->{aggregate} ^= 1;
6589
sub choose_filters {
6591
$tbl ||= choose_visible_table();
6592
return unless $tbl && exists $tbl_meta{$tbl};
6593
my $meta = $tbl_meta{$tbl};
6594
$clear_screen_sub->();
6596
print "Choose filters for $meta->{capt}:\n";
6598
my $ini = join(' ', @{$meta->{filters}});
6599
my $val = prompt_list(
6602
sub { return keys %filters },
6604
map { $_ => $filters{$_}->{note} }
6605
grep { grep { $tbl eq $_ } @{$filters{$_}->{tbls}} }
6610
my @choices = unique(split(/\s+/, $val));
6611
foreach my $new ( grep { !exists($filters{$_}) } @choices ) {
6612
my $answer = prompt("There is no filter called '$new'. Create it?", undef, 'y');
6613
if ( $answer eq 'y' ) {
6614
create_new_filter($new, $tbl);
6617
@choices = grep { exists $filters{$_} } @choices;
6618
@choices = grep { grep { $tbl eq $_ } @{$filters{$_}->{tbls}} } @choices;
6619
$meta->{filters} = [ @choices ];
6620
$meta->{cust}->{filters} = 1;
6623
sub choose_group_cols {
6625
$tbl ||= choose_visible_table();
6626
return unless $tbl && exists $tbl_meta{$tbl};
6627
$clear_screen_sub->();
6628
my $meta = $tbl_meta{$tbl};
6629
my $curr = join(', ', @{$meta->{group_by}});
6630
my $val = prompt_list(
6633
sub { return keys %{$meta->{cols}} },
6634
{ map { $_ => $meta->{cols}->{$_}->{label} } keys %{$meta->{cols}} });
6635
if ( $curr ne $val ) {
6636
$meta->{group_by} = [ grep { exists $meta->{cols}->{$_} } $val =~ m/(\w+)/g ];
6637
$meta->{cust}->{group_by} = 1;
6641
sub choose_sort_cols {
6643
$tbl ||= choose_visible_table();
6644
return unless $tbl && exists $tbl_meta{$tbl};
6645
$clear_screen_sub->();
6646
my $meta = $tbl_meta{$tbl};
6648
my ( $cols, $hints );
6649
if ( $meta->{pivot} ) {
6650
$cols = sub { qw(name set_0) };
6651
$hints = { name => 'name', set_0 => 'set_0' };
6654
$cols = sub { return keys %{$meta->{cols}} };
6655
$hints = { map { $_ => $meta->{cols}->{$_}->{label} } keys %{$meta->{cols}} };
6658
my $val = prompt_list(
6659
'Sort columns (reverse sort with -col)',
6663
if ( $meta->{sort_cols} ne $val ) {
6664
$meta->{sort_cols} = $val;
6665
$meta->{cust}->{sort_cols} = 1;
6666
$tbl_meta{$tbl}->{sort_func} = make_sort_func($tbl_meta{$tbl});
6670
# create_new_filter {{{3
6671
sub create_new_filter {
6672
my ( $filter, $tbl ) = @_;
6673
$clear_screen_sub->();
6675
if ( !$filter || $filter =~ m/\W/ ) {
6676
print word_wrap("Choose a name for the filter. This name is not displayed, and is only used "
6677
. "for internal reference. It can only contain lowercase letters, numbers, and underscores.");
6680
$filter = prompt("Enter filter name");
6681
} while ( !$filter || $filter =~ m/\W/ );
6684
my $completion = sub { keys %{$tbl_meta{$tbl}->{cols}} };
6685
my ( $err, $sub, $body );
6687
$clear_screen_sub->();
6688
print word_wrap("A filter is a Perl subroutine that accepts a hashref of columns "
6689
. "called \$set, and returns a true value if the filter accepts the row. Example:\n"
6690
. " \$set->{active_secs} > 5\n"
6691
. "will only allow rows if their active_secs column is greater than 5.");
6694
print "There's an error in your filter expression: $err\n\n";
6696
$body = prompt("Enter subroutine body", undef, undef, $completion);
6697
( $sub, $err ) = compile_filter($body);
6700
$filters{$filter} = {
6705
note => 'User-defined filter',
6710
# get_config_interactive {{{3
6711
sub get_config_interactive {
6713
$clear_screen_sub->();
6716
print "Enter a new value for '$key' ($config{$key}->{note}).\n";
6718
my $current = ref($config{$key}->{val}) ? join(" ", @{$config{$key}->{val}}) : $config{$key}->{val};
6720
my $new_value = prompt('Enter a value', $config{$key}->{pat}, $current);
6721
$config{$key}->{val} = $new_value;
6724
sub edit_current_var_set {
6725
my $mode = $config{mode}->{val};
6726
my $name = $config{"${mode}_set"}->{val};
6727
my $variables = $var_sets{$name}->{text};
6729
my $new = $variables;
6731
$clear_screen_sub->();
6732
$new = prompt("Enter variables for $name", undef, $variables);
6735
if ( $new ne $variables ) {
6736
@{$var_sets{$name}}{qw(text user)} = ( $new, 1);
6741
sub choose_var_set {
6743
$clear_screen_sub->();
6745
my $new_value = prompt_list(
6746
'Choose a set of values to display, or enter the name of a new one',
6747
$config{$key}->{val},
6748
sub { return keys %var_sets },
6749
{ map { $_ => $var_sets{$_}->{text} } keys %var_sets });
6751
if ( !exists $var_sets{$new_value} ) {
6752
add_new_var_set($new_value);
6755
$config{$key}->{val} = $new_value if exists $var_sets{$new_value};
6758
sub switch_var_set {
6759
my ( $cfg_var, $dir ) = @_;
6760
my @var_sets = sort keys %var_sets;
6761
my $cur = $config{$cfg_var}->{val};
6762
my $pos = grep { $_ lt $cur } @var_sets;
6763
my $newpos = ($pos + $dir) % @var_sets;
6764
$config{$cfg_var}->{val} = $var_sets[$newpos];
6765
$clear_screen_sub->();
6768
# Online configuration and prompting functions {{{2
6770
# edit_stmt_sleep_times {{{3
6771
sub edit_stmt_sleep_times {
6772
$clear_screen_sub->();
6773
my $stmt = prompt_list('Specify a statement', '', sub { return sort keys %stmt_maker_for });
6774
return unless $stmt && exists $stmt_maker_for{$stmt};
6775
$clear_screen_sub->();
6776
my $curr_val = $stmt_sleep_time_for{$stmt} || 0;
6777
my $new_val = prompt('Specify a sleep delay after calling this SQL', $num_regex, $curr_val);
6779
$stmt_sleep_time_for{$stmt} = $new_val;
6782
delete $stmt_sleep_time_for{$stmt};
6786
# edit_server_groups {{{3
6787
# Choose which server connections are in a server group. First choose a group,
6788
# then choose which connections are in it.
6789
sub edit_server_groups {
6790
$clear_screen_sub->();
6791
my $mode = $config{mode}->{val};
6792
my $group = $modes{$mode}->{server_group};
6793
my %curr = %server_groups;
6794
my $new = choose_or_create_server_group($group, 'to edit');
6795
$clear_screen_sub->();
6796
if ( exists $curr{$new} ) {
6797
# Don't do this step if the user just created a new server group,
6798
# because part of that process was to choose connections.
6799
my $cxns = join(' ', @{$server_groups{$new}});
6800
my @conns = choose_or_create_connection($cxns, 'for this group');
6801
$server_groups{$new} = \@conns;
6805
# choose_server_groups {{{3
6806
sub choose_server_groups {
6807
$clear_screen_sub->();
6808
my $mode = $config{mode}->{val};
6809
my $group = $modes{$mode}->{server_group};
6810
my $new = choose_or_create_server_group($group, 'for this mode');
6811
$modes{$mode}->{server_group} = $new if exists $server_groups{$new};
6814
sub choose_or_create_server_group {
6815
my ( $group, $prompt ) = @_;
6818
my @available = sort keys %server_groups;
6821
print "You can enter the name of a new group to create it.\n";
6824
"Choose a server group $prompt",
6826
sub { return @available },
6827
{ map { $_ => join(' ', @{$server_groups{$_}}) } @available });
6831
if ( !exists $server_groups{$new} ) {
6832
my $answer = prompt("There is no server group called '$new'. Create it?", undef, "y");
6833
if ( $answer eq 'y' ) {
6834
add_new_server_group($new);
6839
$new = add_new_server_group();
6844
sub choose_or_create_connection {
6845
my ( $cxns, $prompt ) = @_;
6846
print "You can enter the name of a new connection to create it.\n";
6848
my @available = sort keys %connections;
6849
my $new_cxns = prompt_list(
6850
"Choose connections $prompt",
6852
sub { return @available },
6853
{ map { $_ => $connections{$_}->{dsn} } @available });
6855
my @new = unique(grep { !exists $connections{$_} } split(/\s+/, $new_cxns));
6856
foreach my $new ( @new ) {
6857
my $answer = prompt("There is no connection called '$new'. Create it?", undef, "y");
6858
if ( $answer eq 'y' ) {
6863
return unique(grep { exists $connections{$_} } split(/\s+/, $new_cxns));
6866
# choose_servers {{{3
6867
sub choose_servers {
6868
$clear_screen_sub->();
6869
my $mode = $config{mode}->{val};
6870
my $cxns = join(' ', get_connections());
6871
my @chosen = choose_or_create_connection($cxns, 'for this mode');
6872
$modes{$mode}->{connections} = \@chosen;
6873
$modes{$mode}->{server_group} = ''; # Clear this because it overrides {connections}
6876
# display_license {{{3
6877
sub display_license {
6878
$clear_screen_sub->();
6880
print $innotop_license;
6885
# Data-retrieval functions {{{2
6886
# get_status_info {{{3
6887
# Get SHOW STATUS and SHOW VARIABLES together.
6888
sub get_status_info {
6890
if ( !$info_gotten{status}++ ) {
6891
foreach my $cxn ( @cxns ) {
6892
$vars{$cxn}->{$clock} ||= {};
6893
my $vars = $vars{$cxn}->{$clock};
6895
my $sth = do_stmt($cxn, 'SHOW_STATUS') or next;
6896
my $res = $sth->fetchall_arrayref();
6897
map { $vars->{$_->[0]} = $_->[1] || 0 } @$res;
6899
# Calculate hi-res uptime and add cxn to the hash. This duplicates get_driver_status,
6900
# but it's most important to have consistency.
6901
$vars->{Uptime_hires} ||= get_uptime($cxn);
6902
$vars->{cxn} = $cxn;
6904
# Add SHOW VARIABLES to the hash
6905
$sth = do_stmt($cxn, 'SHOW_VARIABLES') or next;
6906
$res = $sth->fetchall_arrayref();
6907
map { $vars->{$_->[0]} = $_->[1] || 0 } @$res;
6912
# Chooses a thread for explaining, killing, etc...
6913
# First arg is a func that can be called in grep.
6915
my ( $grep_cond, $prompt ) = @_;
6917
# Narrow the list to queries that can be explained.
6918
my %thread_for = map {
6919
# Eliminate innotop's own threads.
6920
$_ => $dbhs{$_}->{dbh} ? $dbhs{$_}->{dbh}->{mysql_thread_id} : 0
6921
} keys %connections;
6923
my @candidates = grep {
6924
$_->{id} != $thread_for{$_->{cxn}} && $grep_cond->($_)
6926
return unless @candidates;
6928
# Find out which server.
6929
my @cxns = unique map { $_->{cxn} } @candidates;
6930
my ( $cxn ) = select_cxn('On which server', @cxns);
6931
return unless $cxn && exists($connections{$cxn});
6933
# Re-filter the list of candidates to only those on this server
6934
@candidates = grep { $_->{cxn} eq $cxn } @candidates;
6936
# Find out which thread to do.
6938
if ( @candidates > 1 ) {
6940
# Sort longest-active first, then longest-idle.
6941
my $sort_func = sub {
6943
return $a->{query} && !$b->{query} ? 1
6944
: $b->{query} && !$a->{query} ? -1
6945
: ($a->{time} || 0) <=> ($b->{time} || 0);
6947
my @threads = map { $_->{id} } reverse sort { $sort_func->($a, $b) } @candidates;
6950
my $thread = prompt_list($prompt,
6952
sub { return @threads });
6953
return unless $thread && $thread =~ m/$int_regex/;
6955
# Find the info hash of that query on that server.
6956
( $info ) = grep { $thread == $_->{id} } @candidates;
6959
$info = $candidates[0];
6964
# analyze_query {{{3
6965
# Allows the user to show fulltext, explain, show optimized...
6967
my ( $action ) = @_;
6969
my $info = choose_thread(
6970
sub { $_[0]->{query} },
6971
'Select a thread to analyze',
6973
return unless $info;
6976
e => \&display_explain,
6977
f => \&show_full_query,
6978
o => \&show_optimized_query,
6981
$actions{$action}->($info);
6983
$action = pause('Press e to explain, f for full query, o for optimized query');
6984
} while ( exists($actions{$action}) );
6988
# Returns the difference between two sets of variables/status/innodb stuff.
6990
my ( $offset, $cxn ) = @_;
6991
my $vars = $vars{$cxn};
6992
if ( $offset < 0 ) {
6993
return $vars->{$clock};
6995
elsif ( exists $vars{$clock - $offset} && !exists $vars->{$clock - $offset - 1} ) {
6996
return $vars->{$clock - $offset};
6998
my $cur = $vars->{$clock - $offset};
6999
my $pre = $vars->{$clock - $offset - 1};
7001
# Numeric variables get subtracted, non-numeric get passed straight through.
7004
( (defined $cur->{$_} && $cur->{$_} =~ m/$num_regex/)
7005
? $cur->{$_} - ($pre->{$_} || 0)
7011
# extract_values {{{3
7012
# Arguments are a set of values (which may be incremental, derived from
7013
# current and previous), current, and previous values.
7014
# TODO: there are a few places that don't remember prev set so can't pass it.
7015
sub extract_values {
7016
my ( $set, $cur, $pre, $tbl ) = @_;
7018
# Hook in event listeners
7019
foreach my $listener ( @{$event_listener_for{extract_values}} ) {
7020
$listener->extract_values($set, $cur, $pre, $tbl);
7024
my $meta = $tbl_meta{$tbl};
7025
my $cols = $meta->{cols};
7026
foreach my $key ( keys %$cols ) {
7027
my $info = $cols->{$key}
7028
or die "Column '$key' doesn't exist in $tbl";
7029
die "No func defined for '$key' in $tbl"
7030
unless $info->{func};
7032
$result->{$key} = $info->{func}->($set, $cur, $pre)
7034
if ( $EVAL_ERROR ) {
7035
if ( $config{debug}->{val} ) {
7038
$result->{$key} = $info->{num} ? 0 : '';
7044
# get_full_processlist {{{3
7045
sub get_full_processlist {
7048
foreach my $cxn ( @cxns ) {
7049
my $stmt = do_stmt($cxn, 'PROCESSLIST') or next;
7050
my $arr = $stmt->fetchall_arrayref({});
7051
push @result, map { $_->{cxn} = $cxn; $_ } @$arr;
7056
# get_open_tables {{{3
7057
sub get_open_tables {
7060
foreach my $cxn ( @cxns ) {
7061
my $stmt = do_stmt($cxn, 'OPEN_TABLES') or next;
7062
my $arr = $stmt->fetchall_arrayref({});
7063
push @result, map { $_->{cxn} = $cxn; $_ } @$arr;
7068
# get_innodb_status {{{3
7069
sub get_innodb_status {
7070
my ( $cxns, $addl_sections ) = @_;
7071
if ( !$config{skip_innodb}->{val} && !$info_gotten{innodb_status}++ ) {
7073
# Determine which sections need to be parsed
7074
my %sections_required =
7075
map { $tbl_meta{$_}->{innodb} => 1 }
7076
grep { $_ && $tbl_meta{$_}->{innodb} }
7077
get_visible_tables();
7079
# Add in any other sections the caller requested.
7080
foreach my $sec ( @$addl_sections ) {
7081
$sections_required{$sec} = 1;
7084
foreach my $cxn ( @$cxns ) {
7085
my $innodb_status_text;
7087
if ( $file ) { # Try to fetch status text from the file.
7088
my @stat = stat($file);
7090
# Initialize the file.
7091
if ( !$file_mtime ) {
7092
# Initialize to 130k from the end of the file (because the limit
7093
# on the size of innodb status is 128k even with Google's patches)
7094
# and try to grab the last status from the file.
7095
sysseek($file, (-128 * 1_024), 2);
7098
# Read from the file.
7100
if ( !$file_mtime || $file_mtime != $stat[9] ) {
7102
while ( sysread($file, $buffer, 4096) ) {
7103
$file_data .= $buffer;
7105
$file_mtime = $stat[9];
7108
# Delete everything but the last InnoDB status text from the file.
7109
$file_data =~ s/\A.*(?=^=====================================\n...... ........ INNODB MONITOR OUTPUT)//ms;
7110
$innodb_status_text = $file_data;
7114
my $stmt = do_stmt($cxn, 'INNODB_STATUS') or next;
7115
$innodb_status_text = $stmt->fetchrow_hashref()->{status};
7118
next unless $innodb_status_text
7119
&& substr($innodb_status_text, 0, 100) =~ m/INNODB MONITOR OUTPUT/;
7121
# Parse and merge into %vars storage
7122
my %innodb_status = (
7123
$innodb_parser->get_status_hash(
7124
$innodb_status_text,
7125
$config{debug}->{val},
7126
\%sections_required,
7127
0, # don't parse full lock information
7130
if ( !$innodb_status{IB_got_all} && $config{auto_wipe_dl}->{val} ) {
7131
clear_deadlock($cxn);
7134
# Merge using a hash slice, which is the fastest way
7135
$vars{$cxn}->{$clock} ||= {};
7136
my $hash = $vars{$cxn}->{$clock};
7137
@{$hash}{ keys %innodb_status } = values %innodb_status;
7138
$hash->{cxn} = $cxn;
7139
$hash->{Uptime_hires} ||= get_uptime($cxn);
7144
# clear_deadlock {{{3
7145
sub clear_deadlock {
7147
return if $clearing_deadlocks++;
7148
my $tbl = $connections{$cxn}->{dl_table};
7152
# Set up the table for creating a deadlock.
7153
my $engine = version_ge($dbhs{$cxn}->{dbh}, '4.1.2') ? 'engine' : 'type';
7154
return unless do_query($cxn, "drop table if exists $tbl");
7155
return unless do_query($cxn, "create table $tbl(a int) $engine=innodb");
7156
return unless do_query($cxn, "delete from $tbl");
7157
return unless do_query($cxn, "insert into $tbl(a) values(0), (1)");
7158
return unless do_query($cxn, "commit"); # Or the children will block against the parent
7160
# Fork off two children to deadlock against each other.
7162
foreach my $child ( 0..1 ) {
7164
if ( defined($pid) && $pid == 0 ) { # I am a child
7165
deadlock_thread( $child, $tbl, $cxn );
7167
elsif ( !defined($pid) ) {
7168
die("Unable to fork for clearing deadlocks!\n");
7170
# I already exited if I'm a child, so I'm the parent.
7171
$children{$child} = $pid;
7174
# Wait for the children to exit.
7175
foreach my $child ( keys %children ) {
7176
my $pid = waitpid($children{$child}, 0);
7180
do_query($cxn, "drop table $tbl");
7182
if ( $EVAL_ERROR ) {
7187
$clearing_deadlocks = 0;
7190
sub get_master_logs {
7193
if ( !$info_gotten{master_logs}++ ) {
7194
foreach my $cxn ( @cxns ) {
7195
my $stmt = do_stmt($cxn, 'SHOW_MASTER_LOGS') or next;
7196
push @result, @{$stmt->fetchall_arrayref({})};
7202
# get_master_slave_status {{{3
7203
sub get_master_slave_status {
7205
if ( !$info_gotten{replication_status}++ ) {
7206
foreach my $cxn ( @cxns ) {
7207
$vars{$cxn}->{$clock} ||= {};
7208
my $vars = $vars{$cxn}->{$clock};
7209
$vars->{cxn} = $cxn;
7211
my $stmt = do_stmt($cxn, 'SHOW_MASTER_STATUS') or next;
7212
my $res = $stmt->fetchall_arrayref({})->[0];
7213
@{$vars}{ keys %$res } = values %$res;
7214
$stmt = do_stmt($cxn, 'SHOW_SLAVE_STATUS') or next;
7215
$res = $stmt->fetchall_arrayref({})->[0];
7216
@{$vars}{ keys %$res } = values %$res;
7217
$vars->{Uptime_hires} ||= get_uptime($cxn);
7224
return defined(&$word)
7225
|| eval "my \$x= sub { $word }; 1"
7226
|| $EVAL_ERROR !~ m/^Bareword/;
7229
# Documentation {{{1
7230
# ############################################################################
7231
# I put this last as per the Dog book.
7232
# ############################################################################
7237
innotop - MySQL and InnoDB transaction/status monitor.
7241
To monitor servers normally:
7245
To monitor InnoDB status information from a file:
7247
innotop /var/log/mysql/mysqld.err
7249
To run innotop non-interactively in a pipe-and-filter configuration:
7251
innotop --count 5 -d 1 -n
7255
innotop monitors MySQL servers. Each of its modes shows you a different aspect
7256
of what's happening in the server. For example, there's a mode for monitoring
7257
replication, one for queries, and one for transactions. innotop refreshes its
7258
data periodically, so you see an updating view.
7260
innotop has lots of features for power users, but you can start and run it with
7261
virtually no configuration. If you're just getting started, see
7262
L<"QUICK-START">. Press '?' at any time while running innotop for
7263
context-sensitive help.
7267
To start innotop, open a terminal or command prompt. If you have installed
7268
innotop on your system, you should be able to just type "innotop" and press
7269
Enter; otherwise, you will need to change to innotop's directory and type "perl
7272
The first thing innotop needs to know is how to connect to a MySQL server. You
7273
can just enter the hostname of the server, for example "localhost" or
7274
"127.0.0.1" if the server is on the same machine as innotop. After this innotop
7275
will prompt you for a DSN (data source name). You should be able to just accept
7276
the defaults by pressing Enter.
7278
When innotop asks you about a table to use when resetting InnoDB deadlock
7279
information, just accept the default for now. This is an advanced feature you
7280
can configure later (see L<"D: InnoDB Deadlocks"> for more).
7282
If you have a .my.cnf file with your MySQL connection defaults, innotop can read
7283
it, and you won't need to specify a username and password if it's in that file.
7284
Otherwise, you should answer 'y' to the next couple of prompts.
7286
After this, you should be connected, and innotop should show you something like
7289
InnoDB Txns (? for help) localhost, 01:11:19, InnoDB 10s :-), 50 QPS,
7291
CXN History Versions Undo Dirty Buf Used Bufs Txns MaxTxn
7292
localhost 7 2035 0 0 0.00% 92.19% 1 07:34
7294
CXN ID User Host Txn Status Time Undo Query Tex
7295
localhost 98379 user1 webserver ACTIVE 07:34 0 SELECT `c
7296
localhost 98450 user1 webserver ACTIVE 01:06 0 INSERT IN
7297
localhost 97750 user1 webserver not starte 00:00 0
7298
localhost 98375 user1 appserver not starte 00:00 0
7300
(This sample is truncated at the right so it will fit on a terminal when running
7303
This sample comes from a quiet server with few transactions active. If your
7304
server is busy, you'll see more output. Notice the first line on the screen,
7305
which tells you what mode you're in and what server you're connected to. You
7306
can change to other modes with keystrokes; press 'Q' to switch to a list of
7307
currently running queries.
7309
Press the '?' key to see what keys are active in the current mode. You can
7310
press any of these keys and innotop will either take the requested action or
7311
prompt you for more input. If your system has Term::ReadLine support, you can
7312
use TAB and other keys to auto-complete and edit input.
7314
To quit innotop, press the 'q' key.
7318
innotop is mostly configured via its configuration file, but some of the
7319
configuration options can come from the command line. You can also specify a
7320
file to monitor for InnoDB status output; see L<"MONITORING A FILE"> for more
7323
You can negate some options by prefixing the option name with --no. For
7324
example, --noinc (or --no-inc) negates L<"--inc">.
7330
Print a summary of command-line usage and exit.
7334
Enable or disable terminal coloring. Corresponds to the L<"color"> config file
7339
Specifies a configuration file to read. This option is non-sticky, that is to
7340
say it does not persist to the configuration file itself.
7344
Enable non-interactive operation. See L<"NON-INTERACTIVE OPERATION"> for more.
7348
Refresh only the specified number of times (ticks) before exiting. Each refresh
7349
is a pause for L<"interval"> seconds, followed by requesting data from MySQL
7350
connections and printing it to the terminal.
7354
Specifies the amount of time to pause between ticks (refreshes). Corresponds to
7355
the configuration option L<"interval">.
7359
Specifies the mode in which innotop should start. Corresponds to the
7360
configuration option L<"mode">.
7364
Specifies whether innotop should display absolute numbers or relative numbers
7365
(offsets from their previous values). Corresponds to the configuration option
7370
Output version information and exit.
7376
innotop is interactive, and you control it with key-presses.
7382
Uppercase keys switch between modes.
7386
Lowercase keys initiate some action within the current mode.
7390
Other keys do something special like change configuration or show the
7395
Press '?' at any time to see the currently active keys and what they do.
7399
Each of innotop's modes retrieves and displays a particular type of data from
7400
the servers you're monitoring. You switch between modes with uppercase keys.
7401
The following is a brief description of each mode, in alphabetical order. To
7402
switch to the mode, press the key listed in front of its heading in the
7407
=item B: InnoDB Buffers
7409
This mode displays information about the InnoDB buffer pool, page statistics,
7410
insert buffer, and adaptive hash index. The data comes from SHOW INNODB STATUS.
7412
This mode contains the L<"buffer_pool">, L<"page_statistics">,
7413
L<"insert_buffers">, and L<"adaptive_hash_index"> tables by default.
7415
=item C: Command Summary
7417
This mode is similar to mytop's Command Summary mode. It shows the
7418
L<"cmd_summary"> table, which looks something like the following:
7420
Command Summary (? for help) localhost, 25+07:16:43, 2.45 QPS, 3 thd, 5.0.40
7421
_____________________ Command Summary _____________________
7422
Name Value Pct Last Incr Pct
7423
Select_scan 3244858 69.89% 2 100.00%
7424
Select_range 1354177 29.17% 0 0.00%
7425
Select_full_join 39479 0.85% 0 0.00%
7426
Select_full_range_join 4097 0.09% 0 0.00%
7427
Select_range_check 0 0.00% 0 0.00%
7429
The command summary table is built by extracting variables from
7430
L<"STATUS_VARIABLES">. The variables must be numeric and must match the prefix
7431
given by the L<"cmd_filter"> configuration variable. The variables are then
7432
sorted by value descending and compared to the last variable, as shown above.
7433
The percentage columns are percentage of the total of all variables in the
7434
table, so you can see the relative weight of the variables.
7436
The example shows what you see if the prefix is "Select_". The default
7437
prefix is "Com_". You can choose a prefix with the 's' key.
7439
It's rather like running SHOW VARIABLES LIKE "prefix%" with memory and
7442
Values are aggregated across all servers. The Pct columns are not correctly
7443
aggregated across multiple servers. This is a known limitation of the grouping
7444
algorithm that may be fixed in the future.
7446
=item D: InnoDB Deadlocks
7448
This mode shows the transactions involved in the last InnoDB deadlock. A second
7449
table shows the locks each transaction held and waited for. A deadlock is
7450
caused by a cycle in the waits-for graph, so there should be two locks held and
7451
one waited for unless the deadlock information is truncated.
7453
InnoDB puts deadlock information before some other information in the SHOW
7454
INNODB STATUS output. If there are a lot of locks, the deadlock information can
7455
grow very large, and there is a limit on the size of the SHOW INNODB
7456
STATUS output. A large deadlock can fill the entire output, or even be
7457
truncated, and prevent you from seeing other information at all. If you are
7458
running innotop in another mode, for example T mode, and suddenly you don't see
7459
anything, you might want to check and see if a deadlock has wiped out the data
7462
If it has, you can create a small deadlock to replace the large one. Use the
7463
'w' key to 'wipe' the large deadlock with a small one. This will not work
7464
unless you have defined a deadlock table for the connection (see L<"SERVER
7467
You can also configure innotop to automatically detect when a large deadlock
7468
needs to be replaced with a small one (see L<"auto_wipe_dl">).
7470
This mode displays the L<"deadlock_transactions"> and L<"deadlock_locks"> tables
7473
=item F: InnoDB Foreign Key Errors
7475
This mode shows the last InnoDB foreign key error information, such as the
7476
table where it happened, when and who and what query caused it, and so on.
7478
InnoDB has a huge variety of foreign key error messages, and many of them are
7479
just hard to parse. innotop doesn't always do the best job here, but there's
7480
so much code devoted to parsing this messy, unparseable output that innotop is
7481
likely never to be perfect in this regard. If innotop doesn't show you what
7482
you need to see, just look at the status text directly.
7484
This mode displays the L<"fk_error"> table by default.
7486
=item I: InnoDB I/O Info
7488
This mode shows InnoDB's I/O statistics, including the I/O threads, pending I/O,
7489
file I/O miscellaneous, and log statistics. It displays the L<"io_threads">,
7490
L<"pending_io">, L<"file_io_misc">, and L<"log_statistics"> tables by default.
7494
This mode shows information about current locks. At the moment only InnoDB
7495
locks are supported, and by default you'll only see locks for which transactions
7496
are waiting. This information comes from the TRANSACTIONS section of the InnoDB
7497
status text. If you have a very busy server, you may have frequent lock waits;
7498
it helps to be able to see which tables and indexes are the "hot spot" for
7499
locks. If your server is running pretty well, this mode should show nothing.
7501
You can configure MySQL and innotop to monitor not only locks for which a
7502
transaction is waiting, but those currently held, too. You can do this with the
7503
InnoDB Lock Monitor (L<http://dev.mysql.com/doc/en/innodb-monitor.html>). It's
7504
not documented in the MySQL manual, but creating the lock monitor with the
7505
following statement also affects the output of SHOW INNODB STATUS, which innotop
7508
CREATE TABLE innodb_lock_monitor(a int) ENGINE=INNODB;
7510
This causes InnoDB to print its output to the MySQL file every 16 seconds or so,
7511
as stated in the manual, but it also makes the normal SHOW INNODB STATUS output
7512
include lock information, which innotop can parse and display (that's the
7513
undocumented feature).
7515
This means you can do what may have seemed impossible: to a limited extent
7516
(InnoDB truncates some information in the output), you can see which transaction
7517
holds the locks something else is waiting for. You can also enable and disable
7518
the InnoDB Lock Monitor with the key mappings in this mode.
7520
This mode displays the L<"innodb_locks"> table by default. Here's a sample of
7521
the screen when one connection is waiting for locks another connection holds:
7523
_________________________________ InnoDB Locks __________________________
7524
CXN ID Type Waiting Wait Active Mode DB Table Index
7525
localhost 12 RECORD 1 00:10 00:10 X test t1 PRIMARY
7526
localhost 12 TABLE 0 00:10 00:10 IX test t1
7527
localhost 12 RECORD 1 00:10 00:10 X test t1 PRIMARY
7528
localhost 11 TABLE 0 00:00 00:25 IX test t1
7529
localhost 11 RECORD 0 00:00 00:25 X test t1 PRIMARY
7531
You can see the first connection, ID 12, is waiting for a lock on the PRIMARY
7532
key on test.t1, and has been waiting for 10 seconds. The second connection
7533
isn't waiting, because the Waiting column is 0, but it holds locks on the same
7534
index. That tells you connection 11 is blocking connection 12.
7536
=item M: Master/Slave Replication Status
7538
This mode shows the output of SHOW SLAVE STATUS and SHOW MASTER STATUS in three
7539
tables. The first two divide the slave's status into SQL and I/O thread status,
7540
and the last shows master status. Filters are applied to eliminate non-slave
7541
servers from the slave tables, and non-master servers from the master table.
7543
This mode displays the L<"slave_sql_status">, L<"slave_io_status">, and
7544
L<"master_status"> tables by default.
7546
=item O: Open Tables
7548
This section comes from MySQL's SHOW OPEN TABLES command. By default it is
7549
filtered to show tables which are in use by one or more queries, so you can
7550
get a quick look at which tables are 'hot'. You can use this to guess which
7551
tables might be locked implicitly.
7553
This mode displays the L<"open_tables"> mode by default.
7557
This mode displays the output from SHOW FULL PROCESSLIST, much like B<mytop>'s
7558
query list mode. This mode does B<not> show InnoDB-related information. This
7559
is probably one of the most useful modes for general usage.
7561
There is an informative header that shows general status information about
7562
your server. You can toggle it on and off with the 'h' key. By default,
7563
innotop hides inactive processes and its own process. You can toggle these on
7564
and off with the 'i' and 'a' keys.
7566
You can EXPLAIN a query from this mode with the 'e' key. This displays the
7567
query's full text, the results of EXPLAIN, and in newer MySQL versions, even
7568
the optimized query resulting from EXPLAIN EXTENDED. innotop also tries to
7569
rewrite certain queries to make them EXPLAIN-able. For example, INSERT/SELECT
7570
statements are rewritable.
7572
This mode displays the L<"q_header"> and L<"processlist"> tables by default.
7574
=item R: InnoDB Row Operations and Semaphores
7576
This mode shows InnoDB row operations, row operation miscellaneous, semaphores,
7577
and information from the wait array. It displays the L<"row_operations">,
7578
L<"row_operation_misc">, L<"semaphores">, and L<"wait_array"> tables by default.
7580
=item S: Variables & Status
7582
This mode calculates statistics, such as queries per second, and prints them out
7583
in several different styles. You can show absolute values, or incremental values
7586
You can switch between the views by pressing a key. The 's' key prints a
7587
single line each time the screen updates, in the style of B<vmstat>. The 'g'
7588
key changes the view to a graph of the same numbers, sort of like B<tload>.
7589
The 'v' key changes the view to a pivoted table of variable names on the left,
7590
with successive updates scrolling across the screen from left to right. You can
7591
choose how many updates to put on the screen with the L<"num_status_sets">
7592
configuration variable.
7594
Headers may be abbreviated to fit on the screen in interactive operation. You
7595
choose which variables to display with the 'c' key, which selects from
7596
predefined sets, or lets you create your own sets. You can edit the current set
7599
This mode doesn't really display any tables like other modes. Instead, it uses
7600
a table definition to extract and format the data, but it then transforms the
7601
result in special ways before outputting it. It uses the L<"var_status"> table
7602
definition for this.
7604
=item T: InnoDB Transactions
7606
This mode shows transactions from the InnoDB monitor's output, in B<top>-like
7607
format. This mode is the reason I wrote innotop.
7609
You can kill queries or processes with the 'k' and 'x' keys, and EXPLAIN a query
7610
with the 'e' or 'f' keys. InnoDB doesn't print the full query in transactions,
7611
so explaining may not work right if the query is truncated.
7613
The informational header can be toggled on and off with the 'h' key. By
7614
default, innotop hides inactive transactions and its own transaction. You can
7615
toggle this on and off with the 'i' and 'a' keys.
7617
This mode displays the L<"t_header"> and L<"innodb_transactions"> tables by
7622
=head1 INNOTOP STATUS
7624
The first line innotop displays is a "status bar" of sorts. What it contains
7625
depends on the mode you're in, and what servers you're monitoring. The first
7626
few words are always the innotop mode, such as "InnoDB Txns" for T mode,
7627
followed by a reminder to press '?' for help at any time.
7631
The simplest case is when you're monitoring a single server. In this case, the
7632
name of the connection is next on the status line. This is the name you gave
7633
when you created the connection -- most likely the MySQL server's hostname.
7634
This is followed by the server's uptime.
7636
If you're in an InnoDB mode, such as T or B, the next word is "InnoDB" followed
7637
by some information about the SHOW INNODB STATUS output used to render the
7638
screen. The first word is the number of seconds since the last SHOW INNODB
7639
STATUS, which InnoDB uses to calculate some per-second statistics. The next is
7640
a smiley face indicating whether the InnoDB output is truncated. If the smiley
7641
face is a :-), all is well; there is no truncation. A :^| means the transaction
7642
list is so long, InnoDB has only printed out some of the transactions. Finally,
7643
a frown :-( means the output is incomplete, which is probably due to a deadlock
7644
printing too much lock information (see L<"D: InnoDB Deadlocks">).
7646
The next two words indicate the server's queries per second (QPS) and how many
7647
threads (connections) exist. Finally, the server's version number is the last
7650
=head2 MULTIPLE SERVERS
7652
If you are monitoring multiple servers (see L<"SERVER CONNECTIONS">), the status
7653
line does not show any details about individual servers. Instead, it shows the
7654
names of the connections that are active. Again, these are connection names you
7655
specified, which are likely to be the server's hostname. A connection that has
7656
an error is prefixed with an exclamation point.
7658
If you are monitoring a group of servers (see L<"SERVER GROUPS">), the status
7659
line shows the name of the group. If any connection in the group has an
7660
error, the group's name is followed by the fraction of the connections that
7663
See L<"ERROR HANDLING"> for more details about innotop's error handling.
7665
=head2 MONITORING A FILE
7667
If you give a filename on the command line, innotop will not connect to ANY
7668
servers at all. It will watch the specified file for InnoDB status output and
7669
use that as its data source. It will always show a single connection called
7670
'file'. And since it can't connect to a server, it can't determine how long the
7671
server it's monitoring has been up; so it calculates the server's uptime as time
7672
since innotop started running.
7674
=head1 SERVER ADMINISTRATION
7676
While innotop is primarily a monitor that lets you watch and analyze your
7677
servers, it can also send commands to servers. The most frequently useful
7678
commands are killing queries and stopping or starting slaves.
7680
You can kill a connection, or in newer versions of MySQL kill a query but not a
7681
connection, from L<"Q: Query List"> and L<"T: InnoDB Transactions"> modes.
7682
Press 'k' to issue a KILL command, or 'x' to issue a KILL QUERY command.
7683
innotop will prompt you for the server and/or connection ID to kill (innotop
7684
does not prompt you if there is only one possible choice for any input).
7685
innotop pre-selects the longest-running query, or the oldest connection.
7686
Confirm the command with 'y'.
7688
In L<"M: Master/Slave Replication Status"> mode, you can start and stop slaves
7689
with the 'a' and 'o' keys, respectively. You can send these commands to many
7690
slaves at once. innotop fills in a default command of START SLAVE or STOP SLAVE
7691
for you, but you can actually edit the command and send anything you wish, such
7692
as SET GLOBAL SQL_SLAVE_SKIP_COUNTER=1 to make the slave skip one binlog event
7695
You can also ask innotop to calculate the earliest binlog in use by any slave
7696
and issue a PURGE MASTER LOGS on the master. Use the 'b' key for this. innotop
7697
will prompt you for a master to run the command on, then prompt you for the
7698
connection names of that master's slaves (there is no way for innotop to
7699
determine this reliably itself). innotop will find the minimum binlog in use by
7700
these slave connections and suggest it as the argument to PURGE MASTER LOGS.
7702
=head1 SERVER CONNECTIONS
7704
When you create a server connection, innotop asks you for a series of inputs, as
7711
A DSN is a Data Source Name, which is the initial argument passed to the DBI
7712
module for connecting to a server. It is usually of the form
7714
DBI:mysql:;mysql_read_default_group=mysql;host=HOSTNAME
7716
Since this DSN is passed to the DBD::mysql driver, you should read the driver's
7717
documentation at L<"http://search.cpan.org/dist/DBD-mysql/lib/DBD/mysql.pm"> for
7718
the exact details on all the options you can pass the driver in the DSN. You
7719
can read more about DBI at L<http://dbi.perl.org/docs/>, and especially at
7720
L<http://search.cpan.org/~timb/DBI/DBI.pm>.
7722
The mysql_read_default_group=mysql option lets the DBD driver read your MySQL
7723
options files, such as ~/.my.cnf on UNIX-ish systems. You can use this to avoid
7724
specifying a username or password for the connection.
7726
=item InnoDB Deadlock Table
7728
This optional item tells innotop a table name it can use to deliberately create
7729
a small deadlock (see L<"D: InnoDB Deadlocks">). If you specify this option,
7730
you just need to be sure the table doesn't exist, and that innotop can create
7731
and drop the table with the InnoDB storage engine. You can safely omit or just
7732
accept the default if you don't intend to use this.
7736
innotop will ask you if you want to specify a username. If you say 'y', it will
7737
then prompt you for a user name. If you have a MySQL option file that specifies
7738
your username, you don't have to specify a username.
7740
The username defaults to your login name on the system you're running innotop on.
7744
innotop will ask you if you want to specify a password. Like the username, the
7745
password is optional, but there's an additional prompt that asks if you want to
7746
save the password in the innotop configuration file. If you don't save it in
7747
the configuration file, innotop will prompt you for a password each time it
7748
starts. Passwords in the innotop configuration file are saved in plain text,
7749
not encrypted in any way.
7753
Once you finish answering these questions, you should be connected to a server.
7754
But innotop isn't limited to monitoring a single server; you can define many
7755
server connections and switch between them by pressing the '@' key. See
7756
L<"SWITCHING BETWEEN CONNECTIONS">.
7758
To create a new connection, press the '@' key and type the name of the new
7759
connection, then follow the steps given above.
7761
=head1 SERVER GROUPS
7763
If you have multiple MySQL instances, you can put them into named groups, such
7764
as 'all', 'masters', and 'slaves', which innotop can monitor all together.
7766
You can choose which group to monitor with the '#' key, and you can press the
7767
TAB key to switch to the next group. If you're not currently monitoring a
7768
group, pressing TAB selects the first group.
7770
To create a group, press the '#' key and type the name of your new group, then
7771
type the names of the connections you want the group to contain.
7773
=head1 SWITCHING BETWEEN CONNECTIONS
7775
innotop lets you quickly switch which servers you're monitoring. The most basic
7776
way is by pressing the '@' key and typing the name(s) of the connection(s) you
7777
want to use. This setting is per-mode, so you can monitor different connections
7778
in each mode, and innotop remembers which connections you choose.
7780
You can quickly switch to the 'next' connection in alphabetical order with the
7781
'n' key. If you're monitoring a server group (see L<"SERVER GROUPS">) this will
7782
switch to the first connection.
7784
You can also type many connection names, and innotop will fetch and display data
7785
from them all. Just separate the connection names with spaces, for example
7786
"server1 server2." Again, if you type the name of a connection that doesn't
7787
exist, innotop will prompt you for connection information and create the
7790
Another way to monitor multiple connections at once is with server groups. You
7791
can use the TAB key to switch to the 'next' group in alphabetical order, or if
7792
you're not monitoring any groups, TAB will switch to the first group.
7794
innotop does not fetch data in parallel from connections, so if you are
7795
monitoring a large group or many connections, you may notice increased delay
7798
When you monitor more than one connection, innotop's status bar changes. See
7799
L<"INNOTOP STATUS">.
7801
=head1 ERROR HANDLING
7803
Error handling is not that important when monitoring a single connection, but is
7804
crucial when you have many active connections. A crashed server or lost
7805
connection should not crash innotop. As a result, innotop will continue to run
7806
even when there is an error; it just won't display any information from the
7807
connection that had an error. Because of this, innotop's behavior might confuse
7808
you. It's a feature, not a bug!
7810
innotop does not continue to query connections that have errors, because they
7811
may slow innotop and make it hard to use, especially if the error is a problem
7812
connecting and causes a long time-out. Instead, innotop retries the connection
7813
occasionally to see if the error still exists. If so, it will wait until some
7814
point in the future. The wait time increases in ticks as the Fibonacci series,
7815
so it tries less frequently as time passes.
7817
Since errors might only happen in certain modes because of the SQL commands
7818
issued in those modes, innotop keeps track of which mode caused the error. If
7819
you switch to a different mode, innotop will retry the connection instead of
7822
By default innotop will display the problem in red text at the bottom of the
7823
first table on the screen. You can disable this behavior with the
7824
L<"show_cxn_errors_in_tbl"> configuration option, which is enabled by default.
7825
If the L<"debug"> option is enabled, innotop will display the error at the
7826
bottom of every table, not just the first. And if L<"show_cxn_errors"> is
7827
enabled, innotop will print the error text to STDOUT as well. Error messages
7828
might only display in the mode that caused the error, depending on the mode and
7829
whether innotop is avoiding querying that connection.
7831
=head1 NON-INTERACTIVE OPERATION
7833
You can run innotop in non-interactive mode, in which case it is entirely
7834
controlled from the configuration file and command-line options. To start
7835
innotop in non-interactive mode, give the L"<--nonint"> command-line option.
7836
This changes innotop's behavior in the following ways:
7842
Certain Perl modules are not loaded. Term::Readline is not loaded, since
7843
innotop doesn't prompt interactively. Term::ANSIColor and Win32::Console::ANSI
7844
modules are not loaded. Term::ReadKey is still used, since innotop may have to
7845
prompt for connection passwords when starting up.
7849
innotop does not clear the screen after each tick.
7853
innotop does not persist any changes to the configuration file.
7857
If L<"--count"> is given and innotop is in incremental mode (see L<"status_inc">
7858
and L<"--inc">), innotop actually refreshes one more time than specified so it
7859
can print incremental statistics. This suppresses output during the first
7860
tick, so innotop may appear to hang.
7864
innotop only displays the first table in each mode. This is so the output can
7865
be easily processed with other command-line utilities such as awk and sed. To
7866
change which tables display in each mode, see L<"TABLES">. Since L<"Q: Query
7867
List"> mode is so important, innotop automatically disables the L<"q_header">
7868
table. This ensures you'll see the L<"processlist"> table, even if you have
7869
innotop configured to show the q_header table during interactive operation.
7870
Similarly, in L<"T: InnoDB Transactions"> mode, the L<"t_header"> table is
7871
suppressed so you see only the L<"innodb_transactions"> table.
7875
All output is tab-separated instead of being column-aligned with whitespace, and
7876
innotop prints the full contents of each table instead of only printing one
7877
screenful at a time.
7881
innotop only prints column headers once instead of every tick (see
7882
L<"hide_hdr">). innotop does not print table captions (see
7883
L<"display_table_captions">). innotop ensures there are no empty lines in the
7888
innotop does not honor the L<"shorten"> transformation, which normally shortens
7889
some numbers to human-readable formats.
7893
innotop does not print a status line (see L<"INNOTOP STATUS">).
7899
Nearly everything about innotop is configurable. Most things are possible to
7900
change with built-in commands, but you can also edit the configuration file.
7902
While running innotop, press the '$' key to bring up the configuration editing
7903
dialog. Press another key to select the type of data you want to edit:
7907
=item S: Statement Sleep Times
7909
Edits SQL statement sleep delays, which make innotop pause for the specified
7910
amount of time after executing a statement. See L<"SQL STATEMENTS"> for a
7911
definition of each statement and what it does. By default innotop does not
7912
delay after any statements.
7914
This feature is included so you can customize the side-effects caused by
7915
monitoring your server. You may not see any effects, but some innotop users
7916
have noticed that certain MySQL versions under very high load with InnoDB
7917
enabled take longer than usual to execute SHOW GLOBAL STATUS. If innotop calls
7918
SHOW FULL PROCESSLIST immediately afterward, the processlist contains more
7919
queries than the machine actually averages at any given moment. Configuring
7920
innotop to pause briefly after calling SHOW GLOBAL STATUS alleviates this
7923
Sleep times are stored in the L<"stmt_sleep_times"> section of the configuration
7924
file. Fractional-second sleeps are supported, subject to your hardware's
7927
=item c: Edit Columns
7929
Starts the table editor on one of the displayed tables. See L<"TABLE EDITOR">.
7930
An alternative way to start the table editor without entering the configuration
7931
dialog is with the '^' key.
7933
=item g: General Configuration
7935
Starts the configuration editor to edit global and mode-specific configuration
7936
variables (see L<"MODES">). innotop prompts you to choose a variable from among
7937
the global and mode-specific ones depending on the current mode.
7939
=item k: Row-Coloring Rules
7941
Starts the row-coloring rules editor on one of the displayed table(s). See
7942
L<"COLORS"> for details.
7944
=item p: Manage Plugins
7946
Starts the plugin configuration editor. See L<"PLUGINS"> for details.
7948
=item s: Server Groups
7950
Lets you create and edit server groups. See L<"SERVER GROUPS">.
7952
=item t: Choose Displayed Tables
7954
Lets you choose which tables to display in this mode. See L<"MODES"> and
7959
=head1 CONFIGURATION FILE
7961
innotop's default configuration file location is in $HOME/.innotop, but can be
7962
overridden with the L<"--config"> command-line option. You can edit it by hand
7963
safely. innotop reads the configuration file when it starts, and writes it out
7964
again when it exits, so any changes you make while innotop is running will be
7967
innotop doesn't store its entire configuration in the configuration file. It
7968
has a huge set of default configuration that it holds only in memory, and the
7969
configuration file only overrides these defaults. When you customize a default
7970
setting, innotop notices, and then stores the customizations into the file.
7971
This keeps the file size down, makes it easier to edit, and makes upgrades
7974
A configuration file can be made read-only. See L<"readonly">.
7976
The configuration file is arranged into sections like an INI file. Each
7977
section begins with [section-name] and ends with [/section-name]. Each
7978
section's entries have a different syntax depending on the data they need to
7979
store. You can put comments in the file; any line that begins with a #
7980
character is a comment. innotop will not read the comments, so it won't write
7981
them back out to the file when it exits. Comments in read-only configuration
7982
files are still useful, though.
7984
The first line in the file is innotop's version number. This lets innotop
7985
notice when the file format is not backwards-compatible, and upgrade smoothly
7986
without destroying your customized configuration.
7988
The following list describes each section of the configuration file and the data
7995
The 'general' section contains global configuration variables and variables that
7996
may be mode-specific, but don't belong in any other section. The syntax is a
7997
simple key=value list. innotop writes a comment above each value to help you
7998
edit the file by hand.
8004
Controls S mode presentation (see L<"S: Variables & Status">). If g, values are
8005
graphed; if s, values are like vmstat; if p, values are in a pivoted table.
8009
Specifies which set of variables to display in L<"S: Variables & Status"> mode.
8010
See L<"VARIABLE SETS">.
8014
Instructs innotop to automatically wipe large deadlocks when it notices them.
8015
When this happens you may notice a slight delay. At the next tick, you will
8016
usually see the information that was being truncated by the large deadlock.
8020
Specifies what kind of characters to allow through the L<"no_ctrl_char">
8021
transformation. This keeps non-printable characters from confusing a
8022
terminal when you monitor queries that contain binary data, such as images.
8024
The default is 'ascii', which considers anything outside normal ASCII to be a
8025
control character. The other allowable values are 'unicode' and 'none'. 'none'
8026
considers every character a control character, which can be useful for
8027
collapsing ALL text fields in queries.
8031
This is the prefix that filters variables in L<"C: Command Summary"> mode.
8035
Whether terminal coloring is permitted.
8039
On MySQL versions 4.0.3 and newer, this variable is used to set the connection's
8040
timeout, so MySQL doesn't close the connection if it is not used for a while.
8041
This might happen because a connection isn't monitored in a particular mode, for
8046
This option enables more verbose errors and makes innotop more strict in some
8047
places. It can help in debugging filters and other user-defined code. It also
8048
makes innotop write a lot of information to L<"debugfile"> when there is a
8053
A file to which innotop will write information when there is a crash. See
8056
=item display_table_captions
8058
innotop displays a table caption above most tables. This variable suppresses or
8059
shows captions on all tables globally. Some tables are configured with the
8060
hide_caption property, which overrides this.
8064
Whether to show GLOBAL variables and status. innotop only tries to do this on
8065
servers which support the GLOBAL option to SHOW VARIABLES and SHOW STATUS. In
8066
some MySQL versions, you need certain privileges to do this; if you don't have
8067
them, innotop will not be able to fetch any variable and status data. This
8068
configuration variable lets you run innotop and fetch what data you can even
8069
without the elevated privileges.
8071
I can no longer find or reproduce the situation where GLOBAL wasn't allowed, but
8072
I know there was one.
8076
Defines the character to use when drawing graphs in L<"S: Variables & Status">
8079
=item header_highlight
8081
Defines how to highlight column headers. This only works if Term::ANSIColor is
8082
available. Valid values are 'bold' and 'underline'.
8086
Hides column headers globally.
8090
The interval at which innotop will refresh its data (ticks). The interval is
8091
implemented as a sleep time between ticks, so the true interval will vary
8092
depending on how long it takes innotop to fetch and render data.
8094
This variable accepts fractions of a second.
8098
The mode in which innotop should start. Allowable arguments are the same as the
8099
key presses that select a mode interactively. See L<"MODES">.
8103
How many digits to show in fractional numbers and percents. This variable's
8104
range is between 0 and 9 and can be set directly from L<"S: Variables & Status">
8105
mode with the '+' and '-' keys. It is used in the L<"set_precision">,
8106
L<"shorten">, and L<"percent"> transformations.
8108
=item num_status_sets
8110
Controls how many sets of status variables to display in pivoted L<"S: Variables
8111
& Status"> mode. It also controls the number of old sets of variables innotop
8112
keeps in its memory, so the larger this variable is, the more memory innotop
8117
Specifies where plugins can be found. By default, innotop stores plugins in the
8118
'plugins' subdirectory of your innotop configuration directory.
8122
Whether the configuration file is readonly. This cannot be set interactively,
8123
because it would prevent itself from being written to the configuration file.
8125
=item show_cxn_errors
8127
Makes innotop print connection errors to STDOUT. See L<"ERROR HANDLING">.
8129
=item show_cxn_errors_in_tbl
8131
Makes innotop display connection errors as rows in the first table on screen.
8132
See L<"ERROR HANDLING">.
8136
Adds a '%' character after the value returned by the L<"percent">
8139
=item show_statusbar
8141
Controls whether to show the status bar in the display. See L<"INNOTOP
8146
Disables fetching SHOW INNODB STATUS, in case your server(s) do not have InnoDB
8147
enabled and you don't want innotop to try to fetch it. This can also be useful
8148
when you don't have the SUPER privilege, required to run SHOW INNODB STATUS.
8152
Whether to show absolute or incremental values for status variables.
8153
Incremental values are calculated as an offset from the last value innotop saw
8154
for that variable. This is a global setting, but will probably become
8155
mode-specific at some point. Right now it is honored a bit inconsistently; some
8156
modes don't pay attention to it.
8162
This section holds a list of package names of active plugins. If the plugin
8163
exists, innotop will activate it. See L<"PLUGINS"> for more information.
8167
This section holds user-defined filters (see L<"FILTERS">). Each line is in the
8168
format filter_name=text='filter text' tbls='table list'.
8170
The filter text is the text of the subroutine's code. The table list is a list
8171
of tables to which the filter can apply. By default, user-defined filters apply
8172
to the table for which they were created, but you can manually override that by
8173
editing the definition in the configuration file.
8175
=item active_filters
8177
This section stores which filters are active on each table. Each line is in the
8178
format table_name=filter_list.
8182
This section stores user-defined or user-customized columns (see L<"COLUMNS">).
8183
Each line is in the format col_name=properties, where the properties are a
8184
name=quoted-value list.
8188
This section holds the server connections you have defined. Each line is in the
8189
format name=properties, where the properties are a name=value list. The
8190
properties are self-explanatory, and the only one that is treated specially is
8191
'pass' which is only present if 'savepass' is set. See L<"SERVER CONNECTIONS">.
8193
=item active_connections
8195
This section holds a list of which connections are active in each mode. Each
8196
line is in the format mode_name=connection_list.
8200
This section holds server groups. Each line is in the format
8201
name=connection_list. See L<"SERVER GROUPS">.
8203
=item active_server_groups
8205
This section holds a list of which server group is active in each mode. Each
8206
line is in the format mode_name=server_group.
8208
=item max_values_seen
8210
This section holds the maximum values seen for variables. This is used to scale
8211
the graphs in L<"S: Variables & Status"> mode. Each line is in the format
8214
=item active_columns
8216
This section holds table column lists. Each line is in the format
8217
tbl_name=column_list. See L<"COLUMNS">.
8221
This section holds the sort definition. Each line is in the format
8222
tbl_name=column_list. If a column is prefixed with '-', that column sorts
8223
descending. See L<"SORTING">.
8225
=item visible_tables
8227
This section defines which tables are visible in each mode. Each line is in the
8228
format mode_name=table_list. See L<"TABLES">.
8232
This section defines variable sets for use in L<"S: Status & Variables"> mode.
8233
Each line is in the format name=variable_list. See L<"VARIABLE SETS">.
8237
This section defines colorization rules. Each line is in the format
8238
tbl_name=property_list. See L<"COLORS">.
8240
=item stmt_sleep_times
8242
This section contains statement sleep times. Each line is in the format
8243
statement_name=sleep_time. See L<"S: Statement Sleep Times">.
8247
This section contains column lists for table group_by expressions. Each line is
8248
in the format tbl_name=column_list. See L<"GROUPING">.
8254
You can customize innotop a great deal. For example, you can:
8260
Choose which tables to display, and in what order.
8264
Choose which columns are in those tables, and create new columns.
8268
Filter which rows display with built-in filters, user-defined filters, and
8273
Sort the rows to put important data first or group together related rows.
8277
Highlight rows with color.
8281
Customize the alignment, width, and formatting of columns, and apply
8282
transformations to columns to extract parts of their values or format the values
8283
as you wish (for example, shortening large numbers to familiar units).
8287
Design your own expressions to extract and combine data as you need. This gives
8288
you unlimited flexibility.
8292
All these and more are explained in the following sections.
8296
A table is what you'd expect: a collection of columns. It also has some other
8297
properties, such as a caption. Filters, sorting rules, and colorization rules
8298
belong to tables and are covered in later sections.
8300
Internally, table meta-data is defined in a data structure called %tbl_meta.
8301
This hash holds all built-in table definitions, which contain a lot of default
8302
instructions to innotop. The meta-data includes the caption, a list of columns
8303
the user has customized, a list of columns, a list of visible columns, a list of
8304
filters, color rules, a sort-column list, sort direction, and some information
8305
about the table's data sources. Most of this is customizable via the table
8306
editor (see L<"TABLE EDITOR">).
8308
You can choose which tables to show by pressing the '$' key. See L<"MODES"> and
8311
The table life-cycle is as follows:
8317
Each table begins with a data source, which is an array of hashes. See below
8318
for details on data sources.
8322
Each element of the data source becomes a row in the final table.
8326
For each element in the data source, innotop extracts values from the source and
8327
creates a row. This row is another hash, which later steps will refer to as
8328
$set. The values innotop extracts are determined by the table's columns. Each
8329
column has an extraction subroutine, compiled from an expression (see
8330
L<"EXPRESSIONS">). The resulting row is a hash whose keys are named the same as
8335
innotop filters the rows, removing those that don't need to be displayed. See
8340
innotop sorts the rows. See L<"SORTING">.
8344
innotop groups the rows together, if specified. See L<"GROUPING">.
8348
innotop colorizes the rows. See L<"COLORS">.
8352
innotop transforms the column values in each row. See L<"TRANSFORMATIONS">.
8356
innotop optionally pivots the rows (see L<"PIVOTING">), then filters and sorts
8361
innotop formats and justifies the rows as a table. During this step, innotop
8362
applies further formatting to the column values, including alignment, maximum
8363
and minimum widths. innotop also does final error checking to ensure there are
8364
no crashes due to undefined values. innotop then adds a caption if specified,
8365
and the table is ready to print.
8369
The lifecycle is slightly different if the table is pivoted, as noted above. To
8370
clarify, if the table is pivoted, the process is extract, group, transform,
8371
pivot, filter, sort, create. If it's not pivoted, the process is extract,
8372
filter, sort, group, color, transform, create. This slightly convoluted process
8373
doesn't map all that well to SQL, but pivoting complicates things pretty
8374
thoroughly. Roughly speaking, filtering and sorting happen as late as needed to
8375
effect the final result as you might expect, but as early as possible for
8378
Each built-in table is described below:
8382
=item adaptive_hash_index
8384
Displays data about InnoDB's adaptive hash index. Data source:
8385
L<"STATUS_VARIABLES">.
8389
Displays data about InnoDB's buffer pool. Data source: L<"STATUS_VARIABLES">.
8393
Displays weighted status variables. Data source: L<"STATUS_VARIABLES">.
8395
=item deadlock_locks
8397
Shows which locks were held and waited for by the last detected deadlock. Data
8398
source: L<"DEADLOCK_LOCKS">.
8400
=item deadlock_transactions
8402
Shows transactions involved in the last detected deadlock. Data source:
8403
L<"DEADLOCK_TRANSACTIONS">.
8407
Shows the output of EXPLAIN. Data source: L<"EXPLAIN">.
8411
Displays data about InnoDB's file and I/O operations. Data source:
8412
L<"STATUS_VARIABLES">.
8416
Displays various data about InnoDB's last foreign key error. Data source:
8417
L<"STATUS_VARIABLES">.
8421
Displays InnoDB locks. Data source: L<"INNODB_LOCKS">.
8423
=item innodb_transactions
8425
Displays data about InnoDB's current transactions. Data source:
8426
L<"INNODB_TRANSACTIONS">.
8428
=item insert_buffers
8430
Displays data about InnoDB's insert buffer. Data source: L<"STATUS_VARIABLES">.
8434
Displays data about InnoDB's I/O threads. Data source: L<"IO_THREADS">.
8436
=item log_statistics
8438
Displays data about InnoDB's logging system. Data source: L<"STATUS_VARIABLES">.
8442
Displays replication master status. Data source: L<"STATUS_VARIABLES">.
8446
Displays open tables. Data source: L<"OPEN_TABLES">.
8448
=item page_statistics
8450
Displays InnoDB page statistics. Data source: L<"STATUS_VARIABLES">.
8454
Displays InnoDB pending I/O operations. Data source: L<"STATUS_VARIABLES">.
8458
Displays current MySQL processes (threads/connections). Data source:
8463
Displays various status values. Data source: L<"STATUS_VARIABLES">.
8465
=item row_operation_misc
8467
Displays data about InnoDB's row operations. Data source:
8468
L<"STATUS_VARIABLES">.
8470
=item row_operations
8472
Displays data about InnoDB's row operations. Data source:
8473
L<"STATUS_VARIABLES">.
8477
Displays data about InnoDB's semaphores and mutexes. Data source:
8478
L<"STATUS_VARIABLES">.
8480
=item slave_io_status
8482
Displays data about the slave I/O thread. Data source:
8483
L<"STATUS_VARIABLES">.
8485
=item slave_sql_status
8487
Displays data about the slave SQL thread. Data source: L<"STATUS_VARIABLES">.
8491
Displays various InnoDB status values. Data source: L<"STATUS_VARIABLES">.
8495
Displays user-configurable data. Data source: L<"STATUS_VARIABLES">.
8499
Displays data about InnoDB's OS wait array. Data source: L<"OS_WAIT_ARRAY">.
8505
Columns belong to tables. You can choose a table's columns by pressing the '^'
8506
key, which starts the L<"TABLE EDITOR"> and lets you choose and edit columns.
8507
Pressing 'e' from within the table editor lets you edit the column's properties:
8513
hdr: a column header. This appears in the first row of the table.
8517
just: justification. '-' means left-justified and '' means right-justified,
8518
just as with printf formatting codes (not a coincidence).
8522
dec: whether to further align the column on the decimal point.
8526
num: whether the column is numeric. This affects how values are sorted
8527
(lexically or numerically).
8531
label: a small note about the column, which appears in dialogs that help the
8532
user choose columns.
8536
src: an expression that innotop uses to extract the column's data from its
8537
source (see L<"DATA SOURCES">). See L<"EXPRESSIONS"> for more on expressions.
8541
minw: specifies a minimum display width. This helps stabilize the display,
8542
which makes it easier to read if the data is changing frequently.
8546
maxw: similar to minw.
8550
trans: a list of column transformations. See L<"TRANSFORMATIONS">.
8554
agg: an aggregate function. See L<"GROUPING">. The default is L<"first">.
8558
aggonly: controls whether the column only shows when grouping is enabled on the
8559
table (see L<"GROUPING">). By default, this is disabled. This means columns
8560
will always be shown by default, whether grouping is enabled or not. If a
8561
column's aggonly is set true, the column will appear when you toggle grouping on
8562
the table. Several columns are set this way, such as the count column on
8563
L<"processlist"> and L<"innodb_transactions">, so you don't see a count when the
8564
grouping isn't enabled, but you do when it is.
8570
Filters remove rows from the display. They behave much like a WHERE clause in
8571
SQL. innotop has several built-in filters, which remove irrelevant information
8572
like inactive queries, but you can define your own as well. innotop also lets
8573
you create quick-filters, which do not get saved to the configuration file, and
8574
are just an easy way to quickly view only some rows.
8576
You can enable or disable a filter on any table. Press the '%' key (mnemonic: %
8577
looks kind of like a line being filtered between two circles) and choose which
8578
table you want to filter, if asked. You'll then see a list of possible filters
8579
and a list of filters currently enabled for that table. Type the names of
8580
filters you want to apply and press Enter.
8582
=head3 USER-DEFINED FILTERS
8584
If you type a name that doesn't exist, innotop will prompt you to create the
8585
filter. Filters are easy to create if you know Perl, and not hard if you don't.
8586
What you're doing is creating a subroutine that returns true if the row should
8587
be displayed. The row is a hash reference passed to your subroutine as $set.
8589
For example, imagine you want to filter the processlist table so you only see
8590
queries that have been running more than five minutes. Type a new name for your
8591
filter, and when prompted for the subroutine body, press TAB to initiate your
8592
terminal's auto-completion. You'll see the names of the columns in the
8593
L<"processlist"> table (innotop generally tries to help you with auto-completion
8594
lists). You want to filter on the 'time' column. Type the text "$set->{time} >
8595
300" to return true when the query is more than five minutes old. That's all
8598
In other words, the code you're typing is surrounded by an implicit context,
8599
which looks like this:
8606
If your filter doesn't work, or if something else suddenly behaves differently,
8607
you might have made an error in your filter, and innotop is silently catching
8608
the error. Try enabling L<"debug"> to make innotop throw an error instead.
8610
=head3 QUICK-FILTERS
8612
innotop's quick-filters are a shortcut to create a temporary filter that doesn't
8613
persist when you restart innotop. To create a quick-filter, press the '/' key.
8614
innotop will prompt you for the column name and filter text. Again, you can use
8615
auto-completion on column names. The filter text can be just the text you want
8616
to "search for." For example, to filter the L<"processlist"> table on queries
8617
that refer to the products table, type '/' and then 'info product'.
8619
The filter text can actually be any Perl regular expression, but of course a
8620
literal string like 'product' works fine as a regular expression.
8622
Behind the scenes innotop compiles the quick-filter into a specially tagged
8623
filter that is otherwise like any other filter. It just isn't saved to the
8626
To clear quick-filters, press the '\' key and innotop will clear them all at
8631
innotop has sensible built-in defaults to sort the most important rows to the
8632
top of the table. Like anything else in innotop, you can customize how any
8635
To start the sort dialog, start the L<"TABLE EDITOR"> with the '^' key, choose a
8636
table if necessary, and press the 's' key. You'll see a list of columns you can
8637
use in the sort expression and the current sort expression, if any. Enter a
8638
list of columns by which you want to sort and press Enter. If you want to
8639
reverse sort, prefix the column name with a minus sign. For example, if you
8640
want to sort by column a ascending, then column b descending, type 'a -b'. You
8641
can also explicitly add a + in front of columns you want to sort ascending, but
8644
Some modes have keys mapped to open this dialog directly, and to quickly reverse
8645
sort direction. Press '?' as usual to see which keys are mapped in any mode.
8649
innotop can group, or aggregate, rows together (I use the terms
8650
interchangeably). This is quite similar to an SQL GROUP BY clause. You can
8651
specify to group on certain columns, or if you don't specify any, the entire set
8652
of rows is treated as one group. This is quite like SQL so far, but unlike SQL,
8653
you can also select un-grouped columns. innotop actually aggregates every
8654
column. If you don't explicitly specify a grouping function, the default is
8655
'first'. This is basically a convenience so you don't have to specify an
8656
aggregate function for every column you want in the result.
8658
You can quickly toggle grouping on a table with the '=' key, which toggles its
8659
aggregate property. This property doesn't persist to the config file.
8661
The columns by which the table is grouped are specified in its group_by
8662
property. When you turn grouping on, innotop places the group_by columns at the
8663
far left of the table, even if they're not supposed to be visible. The rest of
8664
the visible columns appear in order after them.
8666
Two tables have default group_by lists and a count column built in:
8667
L<"processlist"> and L<"innodb_transactions">. The grouping is by connection
8668
and status, so you can quickly see how many queries or transactions are in a
8669
given status on each server you're monitoring. The time columns are aggregated
8670
as a sum; other columns are left at the default 'first' aggregation.
8672
By default, the table shown in L<"S: Variables & Status"> mode also uses
8673
grouping so you can monitor variables and status across many servers. The
8674
default aggregation function in this mode is 'avg'.
8676
Valid grouping functions are defined in the %agg_funcs hash. They include
8682
Returns the first element in the group.
8686
Returns the number of elements in the group, including undefined elements, much
8687
like SQL's COUNT(*).
8691
Returns the average of defined elements in the group.
8695
Returns the sum of elements in the group.
8699
Here's an example of grouping at work. Suppose you have a very busy server with
8700
hundreds of open connections, and you want to see how many connections are in
8701
what status. Using the built-in grouping rules, you can press 'Q' to enter
8702
L<"Q: Query List"> mode. Press '=' to toggle grouping (if necessary, select the
8703
L<"processlist"> table when prompted).
8705
Your display might now look like the following:
8707
Query List (? for help) localhost, 32:33, 0.11 QPS, 1 thd, 5.0.38-log
8709
CXN Cmd Cnt ID User Host Time Query
8710
localhost Query 49 12933 webusr localhost 19:38 SELECT * FROM
8711
localhost Sending Da 23 2383 webusr localhost 12:43 SELECT col1,
8712
localhost Sleep 120 140 webusr localhost 5:18:12
8713
localhost Statistics 12 19213 webusr localhost 01:19 SELECT * FROM
8715
That's actually quite a worrisome picture. You've got a lot of idle connections
8716
(Sleep), and some connections executing queries (Query and Sending Data).
8717
That's okay, but you also have a lot in Statistics status, collectively spending
8718
over a minute. That means the query optimizer is having a really hard time
8719
optimizing your statements. Something is wrong; it should normally take
8720
milliseconds to optimize queries. You might not have seen this pattern if you
8721
didn't look at your connections in aggregate. (This is a made-up example, but
8722
it can happen in real life).
8726
innotop can pivot a table for more compact display, similar to a Pivot Table in
8727
a spreadsheet (also known as a crosstab). Pivoting a table makes columns into
8728
rows. Assume you start with this table:
8735
After pivoting, the table will look like this:
8742
To get reasonable results, you might need to group as well as pivoting.
8743
innotop currently does this for L<"S: Variables & Status"> mode.
8747
By default, innotop highlights rows with color so you can see at a glance which
8748
rows are more important. You can customize the colorization rules and add your
8749
own to any table. Open the table editor with the '^' key, choose a table if
8750
needed, and press 'o' to open the color editor dialog.
8752
The color editor dialog displays the rules applied to the table, in the order
8753
they are evaluated. Each row is evaluated against each rule to see if the rule
8754
matches the row; if it does, the row gets the specified color, and no further
8755
rules are evaluated. The rules look like the following:
8757
state eq Locked black on_red
8759
user eq system user white
8760
cmd eq Connect white
8761
cmd eq Binlog Dump white
8767
This is the default rule set for the L<"processlist"> table. In order of
8768
priority, these rules make locked queries black on a red background, "gray out"
8769
connections from replication and sleeping queries, and make queries turn from
8770
cyan to red as they run longer.
8772
(For some reason, the ANSI color code "white" is actually a light gray. Your
8773
terminal's display may vary; experiment to find colors you like).
8775
You can use keystrokes to move the rules up and down, which re-orders their
8776
priority. You can also delete rules and add new ones. If you add a new rule,
8777
innotop prompts you for the column, an operator for the comparison, a value
8778
against which to compare the column, and a color to assign if the rule matches.
8779
There is auto-completion and prompting at each step.
8781
The value in the third step needs to be correctly quoted. innotop does not try
8782
to quote the value because it doesn't know whether it should treat the value as
8783
a string or a number. If you want to compare the column against a string, as
8784
for example in the first rule above, you should enter 'Locked' surrounded by
8785
quotes. If you get an error message about a bareword, you probably should have
8790
Expressions are at the core of how innotop works, and are what enables you to
8791
extend innotop as you wish. Recall the table lifecycle explained in
8792
L<"TABLES">. Expressions are used in the earliest step, where it extracts
8793
values from a data source to form rows.
8795
It does this by calling a subroutine for each column, passing it the source data
8796
set, a set of current values, and a set of previous values. These are all
8797
needed so the subroutine can calculate things like the difference between this
8798
tick and the previous tick.
8800
The subroutines that extract the data from the set are compiled from
8801
expressions. This gives significantly more power than just naming the values to
8802
fill the columns, because it allows the column's value to be calculated from
8803
whatever data is necessary, but avoids the need to write complicated and lengthy
8806
innotop begins with a string of text that can look as simple as a value's name
8807
or as complicated as a full-fledged Perl expression. It looks at each
8808
'bareword' token in the string and decides whether it's supposed to be a key
8809
into the $set hash. A bareword is an unquoted value that isn't already
8810
surrounded by code-ish things like dollar signs or curly brackets. If innotop
8811
decides that the bareword isn't a function or other valid Perl code, it converts
8812
it into a hash access. After the whole string is processed, innotop compiles a
8813
subroutine, like this:
8815
sub compute_column_value {
8816
my ( $set, $cur, $pre ) = @_;
8817
my $val = # EXPANDED STRING GOES HERE
8821
Here's a concrete example, taken from the header table L<"q_header"> in L<"Q:
8822
Query List"> mode. This expression calculates the qps, or Queries Per Second,
8823
column's values, from the values returned by SHOW STATUS:
8825
Questions/Uptime_hires
8827
innotop decides both words are barewords, and transforms this expression into
8828
the following Perl code:
8830
$set->{Questions}/$set->{Uptime_hires}
8832
When surrounded by the rest of the subroutine's code, this is executable Perl
8833
that calculates a high-resolution queries-per-second value.
8835
The arguments to the subroutine are named $set, $cur, and $pre. In most cases,
8836
$set and $cur will be the same values. However, if L<"status_inc"> is set, $cur
8837
will not be the same as $set, because $set will already contain values that are
8838
the incremental difference between $cur and $pre.
8840
Every column in innotop is computed by subroutines compiled in the same fashion.
8841
There is no difference between innotop's built-in columns and user-defined
8842
columns. This keeps things consistent and predictable.
8844
=head2 TRANSFORMATIONS
8846
Transformations change how a value is rendered. For example, they can take a
8847
number of seconds and display it in H:M:S format. The following transformations
8854
Adds commas to large numbers every three decimal places.
8858
Accepts two unsigned integers and converts them into a single longlong. This is
8859
useful for certain operations with InnoDB, which uses two integers as
8860
transaction identifiers, for example.
8864
Removes quoted control characters from the value. This is affected by the
8865
L<"charset"> configuration variable.
8867
This transformation only operates within quoted strings, for example, values to
8868
a SET clause in an UPDATE statement. It will not alter the UPDATE statement,
8869
but will collapse the quoted string to [BINARY] or [TEXT], depending on the
8874
Converts a number to a percentage by multiplying it by two, formatting it with
8875
L<"num_digits"> digits after the decimal point, and optionally adding a percent
8876
sign (see L<"show_percent">).
8880
Formats a number of seconds as time in days+hours:minutes:seconds format.
8884
Formats numbers with L<"num_digits"> number of digits after the decimal point.
8888
Formats a number as a unit of 1024 (k/M/G/T) and with L<"num_digits"> number of
8889
digits after the decimal point.
8895
The innotop table editor lets you customize tables with keystrokes. You start
8896
the table editor with the '^' key. If there's more than one table on the
8897
screen, it will prompt you to choose one of them. Once you do, innotop will
8898
show you something like this:
8900
Editing table definition for Buffer Pool. Press ? for help, q to quit.
8903
cxn CXN Connection from which cxn
8904
buf_pool_size Size Buffer pool size IB_bp_buf_poo
8905
buf_free Free Bufs Buffers free in the b IB_bp_buf_fre
8906
pages_total Pages Pages total IB_bp_pages_t
8907
pages_modified Dirty Pages Pages modified (dirty IB_bp_pages_m
8908
buf_pool_hit_rate Hit Rate Buffer pool hit rate IB_bp_buf_poo
8909
total_mem_alloc Memory Total memory allocate IB_bp_total_m
8910
add_pool_alloc Add'l Pool Additonal pool alloca IB_bp_add_poo
8912
The first line shows which table you're editing, and reminds you again to press
8913
'?' for a list of key mappings. The rest is a tabular representation of the
8914
table's columns, because that's likely what you're trying to edit. However, you
8915
can edit more than just the table's columns; this screen can start the filter
8916
editor, color rule editor, and more.
8918
Each row in the display shows a single column in the table you're editing, along
8919
with a couple of its properties such as its header and source expression (see
8922
The key mappings are Vim-style, as in many other places. Pressing 'j' and 'k'
8923
moves the highlight up or down. You can then (d)elete or (e)dit the highlighted
8924
column. You can also (a)dd a column to the table. This actually just activates
8925
one of the columns already defined for the table; it prompts you to choose from
8926
among the columns available but not currently displayed. Finally, you can
8927
re-order the columns with the '+' and '-' keys.
8929
You can do more than just edit the columns with the table editor, you can also
8930
edit other properties, such as the table's sort expression and group-by
8931
expression. Press '?' to see the full list, of course.
8933
If you want to really customize and create your own column, as opposed to just
8934
activating a built-in one that's not currently displayed, press the (n)ew key,
8935
and innotop will prompt you for the information it needs:
8941
The column name: this needs to be a word without any funny characters, e.g. just
8942
letters, numbers and underscores.
8946
The column header: this is the label that appears at the top of the column, in
8947
the table header. This can have spaces and funny characters, but be careful not
8948
to make it too wide and waste space on-screen.
8952
The column's data source: this is an expression that determines what data from
8953
the source (see L<"TABLES">) innotop will put into the column. This can just be
8954
the name of an item in the source, or it can be a more complex expression, as
8955
described in L<"EXPRESSIONS">.
8959
Once you've entered the required data, your table has a new column. There is no
8960
difference between this column and the built-in ones; it can have all the same
8961
properties and behaviors. innotop will write the column's definition to the
8962
configuration file, so it will persist across sessions.
8964
Here's an example: suppose you want to track how many times your slaves have
8965
retried transactions. According to the MySQL manual, the
8966
Slave_retried_transactions status variable gives you that data: "The total
8967
number of times since startup that the replication slave SQL thread has retried
8968
transactions. This variable was added in version 5.0.4." This is appropriate to
8969
add to the L<"slave_sql_status"> table.
8971
To add the column, switch to the replication-monitoring mode with the 'M' key,
8972
and press the '^' key to start the table editor. When prompted, choose
8973
slave_sql_status as the table, then press 'n' to create the column. Type
8974
'retries' as the column name, 'Retries' as the column header, and
8975
'Slave_retried_transactions' as the source. Now the column is created, and you
8976
see the table editor screen again. Press 'q' to exit the table editor, and
8977
you'll see your column at the end of the table.
8979
=head1 VARIABLE SETS
8981
Variable sets are used in L<"S: Variables & Status"> mode to define more easily
8982
what variables you want to monitor. Behind the scenes they are compiled to a
8983
list of expressions, and then into a column list so they can be treated just
8984
like columns in any other table, in terms of data extraction and
8985
transformations. However, you're protected from the tedious details by a syntax
8986
that ought to feel very natural to you: a SQL SELECT list.
8988
The data source for variable sets, and indeed the entire S mode, is the
8989
combination of SHOW STATUS, SHOW VARIABLES, and SHOW INNODB STATUS. Imagine
8990
that you had a huge table with one column per variable returned from those
8991
statements. That's the data source for variable sets. You can now query this
8992
data source just like you'd expect. For example:
8994
Questions, Uptime, Questions/Uptime as QPS
8996
Behind the scenes innotop will split that variable set into three expressions,
8997
compile them and turn them into a table definition, then extract as usual. This
8998
becomes a "variable set," or a "list of variables you want to monitor."
9000
innotop lets you name and save your variable sets, and writes them to the
9001
configuration file. You can choose which variable set you want to see with the
9002
'c' key, or activate the next and previous sets with the '>' and '<' keys.
9003
There are many built-in variable sets as well, which should give you a good
9004
start for creating your own. Press 'e' to edit the current variable set, or
9005
just to see how it's defined. To create a new one, just press 'c' and type its
9008
You may want to use some of the functions listed in L<"TRANSFORMATIONS"> to help
9009
format the results. In particular, L<"set_precision"> is often useful to limit
9010
the number of digits you see. Extending the above example, here's how:
9012
Questions, Uptime, set_precision(Questions/Uptime) as QPS
9014
Actually, this still needs a little more work. If your L<"interval"> is less
9015
than one second, you might be dividing by zero because Uptime is incremental in
9016
this mode by default. Instead, use Uptime_hires:
9018
Questions, Uptime, set_precision(Questions/Uptime_hires) as QPS
9020
This example is simple, but it shows how easy it is to choose which variables
9021
you want to monitor.
9025
innotop has a simple but powerful plugin mechanism by which you can extend
9026
or modify its existing functionality, and add new functionality. innotop's
9027
plugin functionality is event-based: plugins register themselves to be called
9028
when events happen. They then have a chance to influence the event.
9030
An innotop plugin is a Perl module placed in innotop's L<"plugin_dir">
9031
directory. On UNIX systems, you can place a symbolic link to the module instead
9032
of putting the actual file there. innotop automatically discovers the file. If
9033
there is a corresponding entry in the L<"plugins"> configuration file section,
9034
innotop loads and activates the plugin.
9036
The module must conform to innotop's plugin interface. Additionally, the source
9037
code of the module must be written in such a way that innotop can inspect the
9038
file and determine the package name and description.
9040
=head2 Package Source Convention
9042
innotop inspects the plugin module's source to determine the Perl package name.
9043
It looks for a line of the form "package Foo;" and if found, considers the
9044
plugin's package name to be Foo. Of course the package name can be a valid Perl
9045
package name, with double semicolons and so on.
9047
It also looks for a description in the source code, to make the plugin editor
9048
more human-friendly. The description is a comment line of the form "#
9049
description: Foo", where "Foo" is the text innotop will consider to be the
9050
plugin's description.
9052
=head2 Plugin Interface
9054
The innotop plugin interface is quite simple: innotop expects the plugin to be
9055
an object-oriented module it can call certain methods on. The methods are
9059
=item new(%variables)
9061
This is the plugin's constructor. It is passed a hash of innotop's variables,
9062
which it can manipulate (see L<"Plugin Variables">). It must return a reference
9063
to the newly created plugin object.
9065
At construction time, innotop has only loaded the general configuration and
9066
created the default built-in variables with their default contents (which is
9067
quite a lot). Therefore, the state of the program is exactly as in the innotop
9068
source code, plus the configuration variables from the L<"general"> section in
9071
If your plugin manipulates the variables, it is changing global data, which is
9072
shared by innotop and all plugins. Plugins are loaded in the order they're
9073
listed in the config file. Your plugin may load before or after another plugin,
9074
so there is a potential for conflict or interaction between plugins if they
9075
modify data other plugins use or modify.
9077
=item register_for_events()
9079
This method must return a list of events in which the plugin is interested, if
9080
any. See L<"Plugin Events"> for the defined events. If the plugin returns an
9081
event that's not defined, the event is ignored.
9083
=item event handlers
9085
The plugin must implement a method named the same as each event for which it has
9086
registered. In other words, if the plugin returns qw(foo bar) from
9087
register_for_events(), it must have foo() and bar() methods. These methods are
9088
callbacks for the events. See L<"Plugin Events"> for more details about each
9093
=head2 Plugin Variables
9095
The plugin's constructor is passed a hash of innotop's variables, which it can
9096
manipulate. It is probably a good idea if the plugin object saves a copy of it
9097
for later use. The variables are defined in the innotop variable
9098
%pluggable_vars, and are as follows:
9104
A hashref of key mappings. These are innotop's global hot-keys.
9108
A hashref of functions that can be used for grouping. See L<"GROUPING">.
9112
The global configuration hash.
9116
A hashref of connection specifications. These are just specifications of how to
9117
connect to a server.
9121
A hashref of innotop's database connections. These are actual DBI connection
9126
A hashref of filters applied to table rows. See L<"FILTERS"> for more.
9130
A hashref of modes. See L<"MODES"> for more.
9134
A hashref of server groups. See L<"SERVER GROUPS">.
9138
A hashref of innotop's table meta-data, with one entry per table (see
9139
L<"TABLES"> for more information).
9143
A hashref of transformation functions. See L<"TRANSFORMATIONS">.
9147
A hashref of variable sets. See L<"VARIABLE SETS">.
9151
=head2 Plugin Events
9153
Each event is defined somewhere in the innotop source code. When innotop runs
9154
that code, it executes the callback function for each plugin that expressed its
9155
interest in the event. innotop passes some data for each event. The events are
9156
defined in the %event_listener_for variable, and are as follows:
9160
=item extract_values($set, $cur, $pre, $tbl)
9162
This event occurs inside the function that extracts values from a data source.
9163
The arguments are the set of values, the current values, the previous values,
9168
Events are defined at many places in this subroutine, which is responsible for
9169
turning an arrayref of hashrefs into an arrayref of lines that can be printed to
9170
the screen. The events all pass the same data: an arrayref of rows and the name
9171
of the table being created. The events are set_to_tbl_pre_filter,
9172
set_to_tbl_pre_sort,set_to_tbl_pre_group, set_to_tbl_pre_colorize,
9173
set_to_tbl_pre_transform, set_to_tbl_pre_pivot, set_to_tbl_pre_create,
9174
set_to_tbl_post_create.
9176
=item draw_screen($lines)
9178
This event occurs inside the subroutine that prints the lines to the screen.
9179
$lines is an arrayref of strings.
9183
=head2 Simple Plugin Example
9185
The easiest way to explain the plugin functionality is probably with a simple
9186
example. The following module adds a column to the beginning of every table and
9187
sets its value to 1.
9190
use warnings FATAL => 'all';
9192
package Innotop::Plugin::Example;
9193
# description: Adds an 'example' column to every table
9196
my ( $class, %vars ) = @_;
9197
# Store reference to innotop's variables in $self
9198
my $self = bless { %vars }, $class;
9200
# Design the example column
9207
src => 'example', # Get data from this column in the data source
9212
# Add the column to every table.
9213
my $tbl_meta = $vars{tbl_meta};
9214
foreach my $tbl ( values %$tbl_meta ) {
9215
# Add the column to the list of defined columns
9216
$tbl->{cols}->{example} = $col;
9217
# Add the column to the list of visible columns
9218
unshift @{$tbl->{visible}}, 'example';
9221
# Be sure to return a reference to the object.
9225
# I'd like to be called when a data set is being rendered into a table, please.
9226
sub register_for_events {
9228
return qw(set_to_tbl_pre_filter);
9231
# This method will be called when the event fires.
9232
sub set_to_tbl_pre_filter {
9233
my ( $self, $rows, $tbl ) = @_;
9234
# Set the example column's data source to the value 1.
9235
foreach my $row ( @$rows ) {
9236
$row->{example} = 1;
9242
=head2 Plugin Editor
9244
The plugin editor lets you view the plugins innotop discovered and activate or
9245
deactivate them. Start the editor by pressing $ to start the configuration
9246
editor from any mode. Press the 'p' key to start the plugin editor. You'll see
9247
a list of plugins innotop discovered. You can use the 'j' and 'k' keys to move
9248
the highlight to the desired one, then press the * key to toggle it active or
9249
inactive. Exit the editor and restart innotop for the changes to take effect.
9251
=head1 SQL STATEMENTS
9253
innotop uses a limited set of SQL statements to retrieve data from MySQL for
9254
display. The statements are customized depending on the server version against
9255
which they are executed; for example, on MySQL 5 and newer, INNODB_STATUS
9256
executes "SHOW ENGINE INNODB STATUS", while on earlier versions it executes
9257
"SHOW INNODB STATUS". The statements are as follows:
9259
Statement SQL executed
9260
=================== ===============================
9261
INNODB_STATUS SHOW [ENGINE] INNODB STATUS
9262
KILL_CONNECTION KILL
9263
KILL_QUERY KILL QUERY
9264
OPEN_TABLES SHOW OPEN TABLES
9265
PROCESSLIST SHOW FULL PROCESSLIST
9266
SHOW_MASTER_LOGS SHOW MASTER LOGS
9267
SHOW_MASTER_STATUS SHOW MASTER STATUS
9268
SHOW_SLAVE_STATUS SHOW SLAVE STATUS
9269
SHOW_STATUS SHOW [GLOBAL] STATUS
9270
SHOW_VARIABLES SHOW [GLOBAL] VARIABLES
9274
Each time innotop extracts values to create a table (see L<"EXPRESSIONS"> and
9275
L<"TABLES">), it does so from a particular data source. Largely because of the
9276
complex data extracted from SHOW INNODB STATUS, this is slightly messy. SHOW
9277
INNODB STATUS contains a mixture of single values and repeated values that form
9280
Whenever innotop fetches data from MySQL, it adds two extra bits to each set:
9281
cxn and Uptime_hires. cxn is the name of the connection from which the data
9282
came. Uptime_hires is a high-resolution version of the server's Uptime status
9283
variable, which is important if your L<"interval"> setting is sub-second.
9285
Here are the kinds of data sources from which data is extracted:
9289
=item STATUS_VARIABLES
9291
This is the broadest category, into which the most kinds of data fall. It
9292
begins with the combination of SHOW STATUS and SHOW VARIABLES, but other sources
9293
may be included as needed, for example, SHOW MASTER STATUS and SHOW SLAVE
9294
STATUS, as well as many of the non-repeated values from SHOW INNODB STATUS.
9296
=item DEADLOCK_LOCKS
9298
This data is extracted from the transaction list in the LATEST DETECTED DEADLOCK
9299
section of SHOW INNODB STATUS. It is nested two levels deep: transactions, then
9302
=item DEADLOCK_TRANSACTIONS
9304
This data is from the transaction list in the LATEST DETECTED DEADLOCK
9305
section of SHOW INNODB STATUS. It is nested one level deep.
9309
This data is from the result set returned by EXPLAIN.
9311
=item INNODB_TRANSACTIONS
9313
This data is from the TRANSACTIONS section of SHOW INNODB STATUS.
9317
This data is from the list of threads in the the FILE I/O section of SHOW INNODB
9322
This data is from the TRANSACTIONS section of SHOW INNODB STATUS and is nested
9327
This data is from SHOW OPEN TABLES.
9331
This data is from SHOW FULL PROCESSLIST.
9335
This data is from the SEMAPHORES section of SHOW INNODB STATUS and is nested one
9336
level deep. It comes from the lines that look like this:
9338
--Thread 1568861104 has waited at btr0cur.c line 424 ....
9342
=head1 MYSQL PRIVILEGES
9348
You must connect to MySQL as a user who has the SUPER privilege for many of the
9353
If you don't have the SUPER privilege, you can still run some functions, but you
9354
won't necessarily see all the same data.
9358
You need the PROCESS privilege to see the list of currently running queries in Q
9363
You need special privileges to start and stop slave servers.
9367
You need appropriate privileges to create and drop the deadlock tables if needed
9368
(see L<"SERVER CONNECTIONS">).
9372
=head1 SYSTEM REQUIREMENTS
9374
You need Perl to run innotop, of course. You also need a few Perl modules: DBI,
9375
DBD::mysql, Term::ReadKey, and Time::HiRes. These should be included with most
9376
Perl distributions, but in case they are not, I recommend using versions
9377
distributed with your operating system or Perl distribution, not from CPAN.
9378
Term::ReadKey in particular has been known to cause problems if installed from
9381
If you have Term::ANSIColor, innotop will use it to format headers more readably
9382
and compactly. (Under Microsoft Windows, you also need Win32::Console::ANSI for
9383
terminal formatting codes to be honored). If you install Term::ReadLine,
9384
preferably Term::ReadLine::Gnu, you'll get nice auto-completion support.
9386
I run innotop on Gentoo GNU/Linux, Debian and Ubuntu, and I've had feedback from
9387
people successfully running it on Red Hat, CentOS, Solaris, and Mac OSX. I
9388
don't see any reason why it won't work on other UNIX-ish operating systems, but
9389
I don't know for sure. It also runs on Windows under ActivePerl without
9392
I use innotop on MySQL versions 3.23.58, 4.0.27, 4.1.0, 4.1.22, 5.0.26, 5.1.15,
9393
and 5.2.3. If it doesn't run correctly for you, that is a bug and I hope you
9398
$HOMEDIR/.innotop is used to store configuration information. Files include the
9399
configuration file innotop.ini, the core_dump file which contains verbose error
9400
messages if L<"debug"> is enabled, and the plugins/ subdirectory.
9402
=head1 GLOSSARY OF TERMS
9408
A tick is a refresh event, when innotop re-fetches data from connections and
9413
=head1 ACKNOWLEDGEMENTS
9415
I'm grateful to the following people for various reasons, and hope I haven't
9416
forgotten to include anyone:
9419
Aurimas Mikalauskas,
9428
Google.com Site Reliability Engineers,
9443
The Gentoo MySQL Team,
9446
and probably more people I've neglected to include.
9448
(If I misspelled your name, it's probably because I'm afraid of putting
9449
international characters into this documentation; earlier versions of Perl might
9450
not be able to compile it then).
9452
=head1 COPYRIGHT, LICENSE AND WARRANTY
9454
This program is copyright (c) 2006 Baron Schwartz.
9455
Feedback and improvements are welcome.
9457
THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
9458
WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
9459
MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
9461
This program is free software; you can redistribute it and/or modify it under
9462
the terms of the GNU General Public License as published by the Free Software
9463
Foundation, version 2; OR the Perl Artistic License. On UNIX and similar
9464
systems, you can issue `man perlgpl' or `man perlartistic' to read these
9467
You should have received a copy of the GNU General Public License along with
9468
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
9469
Place, Suite 330, Boston, MA 02111-1307 USA.
9471
Execute innotop and press '!' to see this information at any time.
9479
You can report bugs, ask for improvements, and get other help and support at
9480
L<http://sourceforge.net/projects/innotop>. There are mailing lists, forums,
9481
a bug tracker, etc. Please use these instead of contacting me directly, as it
9482
makes my job easier and benefits others if the discussions are permanent and
9483
public. Of course, if you need to contact me in private, please do.