diff options
Diffstat (limited to 'storage/xtradb/build/debian/additions/innotop/innotop')
-rw-r--r-- | storage/xtradb/build/debian/additions/innotop/innotop | 9485 |
1 files changed, 0 insertions, 9485 deletions
diff --git a/storage/xtradb/build/debian/additions/innotop/innotop b/storage/xtradb/build/debian/additions/innotop/innotop deleted file mode 100644 index e2bfc1bd965..00000000000 --- a/storage/xtradb/build/debian/additions/innotop/innotop +++ /dev/null @@ -1,9485 +0,0 @@ -#!/usr/bin/perl - -# vim: tw=160:nowrap:expandtab:tabstop=3:shiftwidth=3:softtabstop=3 - -use strict; -use warnings FATAL => 'all'; -use sigtrap qw(handler finish untrapped normal-signals); - -use Data::Dumper; -use DBI; -use English qw(-no_match_vars); -use File::Basename qw(dirname); -use Getopt::Long; -use List::Util qw(max min maxstr sum); -use InnoDBParser; -use POSIX qw(ceil); -use Time::HiRes qw(time sleep); -use Term::ReadKey qw(ReadMode ReadKey); - -# Version, license and warranty information. {{{1 -# ########################################################################### -our $VERSION = '1.6.0'; -our $SVN_REV = sprintf("%d", q$Revision: 383 $ =~ m/(\d+)/g); -our $SVN_URL = sprintf("%s", q$URL: https://innotop.svn.sourceforge.net/svnroot/innotop/trunk/innotop $ =~ m$svnroot/innotop/(\S+)$g); - -my $innotop_license = <<"LICENSE"; - -This is innotop version $VERSION, a MySQL and InnoDB monitor. - -This program is copyright (c) 2006 Baron Schwartz. -Feedback and improvements are welcome. - -THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED -WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF -MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. - -This program is free software; you can redistribute it and/or modify it under -the terms of the GNU General Public License as published by the Free Software -Foundation, version 2; OR the Perl Artistic License. On UNIX and similar -systems, you can issue `man perlgpl' or `man perlartistic' to read these -licenses. - -You should have received a copy of the GNU General Public License along with -this program; if not, write to the Free Software Foundation, Inc., 59 Temple -Place, Suite 330, Boston, MA 02111-1307 USA. -LICENSE - -# Configuration information and global setup {{{1 -# ########################################################################### - -# Really, really, super-global variables. -my @config_versions = ( - "000-000-000", "001-003-000", # config file was one big name-value hash. - "001-003-000", "001-004-002", # config file contained non-user-defined stuff. -); - -my $clear_screen_sub; - -# This defines expected properties and defaults for the column definitions that -# eventually end up in tbl_meta. -my %col_props = ( - hdr => '', - just => '-', - dec => 0, # Whether to align the column on the decimal point - num => 0, - label => '', - user => 0, - src => '', - tbl => '', # Helps when writing/reading custom columns in config files - minw => 0, - maxw => 0, - trans => [], - agg => 'first', # Aggregate function - aggonly => 0, # Whether to show only when tbl_meta->{aggregate} is true -); - -# Actual DBI connections to MySQL servers. -my %dbhs; - -# Command-line parameters {{{2 -# ########################################################################### - -my @opt_spec = ( - { s => 'help', d => 'Show this help message' }, - { s => 'color|C!', d => 'Use terminal coloring (default)', c => 'color' }, - { s => 'config|c=s', d => 'Config file to read' }, - { s => 'nonint|n', d => 'Non-interactive, output tab-separated fields' }, - { s => 'count=i', d => 'Number of updates before exiting' }, - { s => 'delay|d=f', d => 'Delay between updates in seconds', c => 'interval' }, - { s => 'mode|m=s', d => 'Operating mode to start in', c => 'mode' }, - { s => 'inc|i!', d => 'Measure incremental differences', c => 'status_inc' }, - { s => 'version', d => 'Output version information and exit' }, -); - -# This is the container for the command-line options' values to be stored in -# after processing. Initial values are defaults. -my %opts = ( - n => !( -t STDIN && -t STDOUT ), # If in/out aren't to terminals, we're interactive -); -# Post-process... -my %opt_seen; -foreach my $spec ( @opt_spec ) { - my ( $long, $short ) = $spec->{s} =~ m/^(\w+)(?:\|([^!+=]*))?/; - $spec->{k} = $short || $long; - $spec->{l} = $long; - $spec->{t} = $short; - $spec->{n} = $spec->{s} =~ m/!/; - $opts{$spec->{k}} = undef unless defined $opts{$spec->{k}}; - die "Duplicate option $spec->{k}" if $opt_seen{$spec->{k}}++; -} - -Getopt::Long::Configure('no_ignore_case', 'bundling'); -GetOptions( map { $_->{s} => \$opts{$_->{k}} } @opt_spec) or $opts{help} = 1; - -if ( $opts{version} ) { - print "innotop Ver $VERSION Changeset $SVN_REV from $SVN_URL\n"; - exit(0); -} - -if ( $opts{'help'} ) { - print "Usage: innotop <options> <innodb-status-file>\n\n"; - my $maxw = max(map { length($_->{l}) + ($_->{n} ? 4 : 0)} @opt_spec); - foreach my $spec ( sort { $a->{l} cmp $b->{l} } @opt_spec ) { - my $long = $spec->{n} ? "[no]$spec->{l}" : $spec->{l}; - my $short = $spec->{t} ? "-$spec->{t}" : ''; - printf(" --%-${maxw}s %-4s %s\n", $long, $short, $spec->{d}); - } - print <<USAGE; - -innotop is a MySQL and InnoDB transaction/status monitor, like 'top' for -MySQL. It displays queries, InnoDB transactions, lock waits, deadlocks, -foreign key errors, open tables, replication status, buffer information, -row operations, logs, I/O operations, load graph, and more. You can -monitor many servers at once with innotop. - -USAGE - exit(1); -} - -# Meta-data (table definitions etc) {{{2 -# ########################################################################### - -# Expressions {{{3 -# Convenience so I can copy/paste these in several places... -# ########################################################################### -my %exprs = ( - Host => q{my $host = host || hostname || ''; ($host) = $host =~ m/^((?:[\d.]+(?=:))|(?:[a-zA-Z]\w+))/; return $host || ''}, - Port => q{my ($p) = host =~ m/:(.*)$/; return $p || 0}, - OldVersions => q{dulint_to_int(IB_tx_trx_id_counter) - dulint_to_int(IB_tx_purge_done_for)}, - MaxTxnTime => q/max(map{ $_->{active_secs} } @{ IB_tx_transactions }) || 0/, - NumTxns => q{scalar @{ IB_tx_transactions } }, - DirtyBufs => q{ $cur->{IB_bp_pages_modified} / ($cur->{IB_bp_buf_pool_size} || 1) }, - BufPoolFill => q{ $cur->{IB_bp_pages_total} / ($cur->{IB_bp_buf_pool_size} || 1) }, - ServerLoad => q{ $cur->{Threads_connected}/(Questions||1)/Uptime_hires }, - 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}, - 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', - QcacheHitRatio => q{(Qcache_hits||0)/(((Com_select||0)+(Qcache_hits||0))||1)}, -); - -# ########################################################################### -# Column definitions {{{3 -# Defines every column in every table. A named column has the following -# properties: -# * hdr Column header/title -# * label Documentation for humans. -# * num Whether it's numeric (for sorting). -# * just Alignment; generated from num, user-overridable in tbl_meta -# * minw, maxw Auto-generated, user-overridable. -# Values from this hash are just copied to tbl_meta, which is where everything -# else in the program should read from. -# ########################################################################### - -my %columns = ( - active_secs => { hdr => 'SecsActive', num => 1, label => 'Seconds transaction has been active', }, - add_pool_alloc => { hdr => 'Add\'l Pool', num => 1, label => 'Additonal pool allocated' }, - attempted_op => { hdr => 'Action', num => 0, label => 'The action that caused the error' }, - awe_mem_alloc => { hdr => 'AWE Memory', num => 1, label => '[Windows] AWE memory allocated' }, - binlog_cache_overflow => { hdr => 'Binlog Cache', num => 1, label => 'Transactions too big for binlog cache that went to disk' }, - binlog_do_db => { hdr => 'Binlog Do DB', num => 0, label => 'binlog-do-db setting' }, - binlog_ignore_db => { hdr => 'Binlog Ignore DB', num => 0, label => 'binlog-ignore-db setting' }, - bps_in => { hdr => 'BpsIn', num => 1, label => 'Bytes per second received by the server', }, - bps_out => { hdr => 'BpsOut', num => 1, label => 'Bytes per second sent by the server', }, - buf_free => { hdr => 'Free Bufs', num => 1, label => 'Buffers free in the buffer pool' }, - buf_pool_hit_rate => { hdr => 'Hit Rate', num => 0, label => 'Buffer pool hit rate' }, - buf_pool_hits => { hdr => 'Hits', num => 1, label => 'Buffer pool hits' }, - buf_pool_reads => { hdr => 'Reads', num => 1, label => 'Buffer pool reads' }, - buf_pool_size => { hdr => 'Size', num => 1, label => 'Buffer pool size' }, - bufs_in_node_heap => { hdr => 'Node Heap Bufs', num => 1, label => 'Buffers in buffer pool node heap' }, - bytes_behind_master => { hdr => 'ByteLag', num => 1, label => 'Bytes the slave lags the master in binlog' }, - cell_event_set => { hdr => 'Ending?', num => 1, label => 'Whether the cell event is set' }, - cell_waiting => { hdr => 'Waiting?', num => 1, label => 'Whether the cell is waiting' }, - child_db => { hdr => 'Child DB', num => 0, label => 'The database of the child table' }, - child_index => { hdr => 'Child Index', num => 0, label => 'The index in the child table' }, - child_table => { hdr => 'Child Table', num => 0, label => 'The child table' }, - cmd => { hdr => 'Cmd', num => 0, label => 'Type of command being executed', }, - cnt => { hdr => 'Cnt', num => 0, label => 'Count', agg => 'count', aggonly => 1 }, - connect_retry => { hdr => 'Connect Retry', num => 1, label => 'Slave connect-retry timeout' }, - cxn => { hdr => 'CXN', num => 0, label => 'Connection from which the data came', }, - db => { hdr => 'DB', num => 0, label => 'Current database', }, - dict_mem_alloc => { hdr => 'Dict Mem', num => 1, label => 'Dictionary memory allocated' }, - dirty_bufs => { hdr => 'Dirty Buf', num => 1, label => 'Dirty buffer pool pages' }, - dl_txn_num => { hdr => 'Num', num => 0, label => 'Deadlocked transaction number', }, - event_set => { hdr => 'Evt Set?', num => 1, label => '[Win32] if a wait event is set', }, - exec_master_log_pos => { hdr => 'Exec Master Log Pos', num => 1, label => 'Exec Master Log Position' }, - fk_name => { hdr => 'Constraint', num => 0, label => 'The name of the FK constraint' }, - free_list_len => { hdr => 'Free List Len', num => 1, label => 'Length of the free list' }, - has_read_view => { hdr => 'Rd View', num => 1, label => 'Whether the transaction has a read view' }, - hash_searches_s => { hdr => 'Hash/Sec', num => 1, label => 'Number of hash searches/sec' }, - hash_table_size => { hdr => 'Size', num => 1, label => 'Number of non-hash searches/sec' }, - heap_no => { hdr => 'Heap', num => 1, label => 'Heap number' }, - heap_size => { hdr => 'Heap', num => 1, label => 'Heap size' }, - history_list_len => { hdr => 'History', num => 1, label => 'History list length' }, - host_and_domain => { hdr => 'Host', num => 0, label => 'Hostname/IP and domain' }, - host_and_port => { hdr => 'Host/IP', num => 0, label => 'Hostname or IP address, and port number', }, - hostname => { hdr => 'Host', num => 0, label => 'Hostname' }, - index => { hdr => 'Index', num => 0, label => 'The index involved' }, - index_ref => { hdr => 'Index Ref', num => 0, label => 'Index referenced' }, - info => { hdr => 'Query', num => 0, label => 'Info or the current query', }, - insert_intention => { hdr => 'Ins Intent', num => 1, label => 'Whether the thread was trying to insert' }, - inserts => { hdr => 'Inserts', num => 1, label => 'Inserts' }, - io_bytes_s => { hdr => 'Bytes/Sec', num => 1, label => 'Average I/O bytes/sec' }, - io_flush_type => { hdr => 'Flush Type', num => 0, label => 'I/O Flush Type' }, - io_fsyncs_s => { hdr => 'fsyncs/sec', num => 1, label => 'I/O fsyncs/sec' }, - io_reads_s => { hdr => 'Reads/Sec', num => 1, label => 'Average I/O reads/sec' }, - io_writes_s => { hdr => 'Writes/Sec', num => 1, label => 'Average I/O writes/sec' }, - ip => { hdr => 'IP', num => 0, label => 'IP address' }, - is_name_locked => { hdr => 'Locked', num => 1, label => 'Whether table is name locked', }, - key_buffer_hit => { hdr => 'KCacheHit', num => 1, label => 'Key cache hit ratio', }, - key_len => { hdr => 'Key Length', num => 1, label => 'Number of bytes used in the key' }, - last_chkp => { hdr => 'Last Checkpoint', num => 0, label => 'Last log checkpoint' }, - last_errno => { hdr => 'Last Errno', num => 1, label => 'Last error number' }, - last_error => { hdr => 'Last Error', num => 0, label => 'Last error' }, - last_s_file_name => { hdr => 'S-File', num => 0, label => 'Filename where last read locked' }, - last_s_line => { hdr => 'S-Line', num => 1, label => 'Line where last read locked' }, - last_x_file_name => { hdr => 'X-File', num => 0, label => 'Filename where last write locked' }, - last_x_line => { hdr => 'X-Line', num => 1, label => 'Line where last write locked' }, - last_pct => { hdr => 'Pct', num => 1, label => 'Last Percentage' }, - last_total => { hdr => 'Last Total', num => 1, label => 'Last Total' }, - last_value => { hdr => 'Last Incr', num => 1, label => 'Last Value' }, - load => { hdr => 'Load', num => 1, label => 'Server load' }, - lock_cfile_name => { hdr => 'Crtd File', num => 0, label => 'Filename where lock created' }, - lock_cline => { hdr => 'Crtd Line', num => 1, label => 'Line where lock created' }, - lock_mem_addr => { hdr => 'Addr', num => 0, label => 'The lock memory address' }, - lock_mode => { hdr => 'Mode', num => 0, label => 'The lock mode' }, - lock_structs => { hdr => 'LStrcts', num => 1, label => 'Number of lock structs' }, - lock_type => { hdr => 'Type', num => 0, label => 'The lock type' }, - lock_var => { hdr => 'Lck Var', num => 1, label => 'The lock variable' }, - lock_wait_time => { hdr => 'Wait', num => 1, label => 'How long txn has waited for a lock' }, - log_flushed_to => { hdr => 'Flushed To', num => 0, label => 'Log position flushed to' }, - log_ios_done => { hdr => 'IO Done', num => 1, label => 'Log I/Os done' }, - log_ios_s => { hdr => 'IO/Sec', num => 1, label => 'Average log I/Os per sec' }, - log_seq_no => { hdr => 'Sequence No.', num => 0, label => 'Log sequence number' }, - main_thread_id => { hdr => 'Main Thread ID', num => 1, label => 'Main thread ID' }, - main_thread_proc_no => { hdr => 'Main Thread Proc', num => 1, label => 'Main thread process number' }, - main_thread_state => { hdr => 'Main Thread State', num => 0, label => 'Main thread state' }, - master_file => { hdr => 'File', num => 0, label => 'Master file' }, - master_host => { hdr => 'Master', num => 0, label => 'Master server hostname' }, - master_log_file => { hdr => 'Master Log File', num => 0, label => 'Master log file' }, - master_port => { hdr => 'Master Port', num => 1, label => 'Master port' }, - master_pos => { hdr => 'Position', num => 1, label => 'Master position' }, - master_ssl_allowed => { hdr => 'Master SSL Allowed', num => 0, label => 'Master SSL Allowed' }, - master_ssl_ca_file => { hdr => 'Master SSL CA File', num => 0, label => 'Master SSL Cert Auth File' }, - master_ssl_ca_path => { hdr => 'Master SSL CA Path', num => 0, label => 'Master SSL Cert Auth Path' }, - master_ssl_cert => { hdr => 'Master SSL Cert', num => 0, label => 'Master SSL Cert' }, - master_ssl_cipher => { hdr => 'Master SSL Cipher', num => 0, label => 'Master SSL Cipher' }, - master_ssl_key => { hdr => 'Master SSL Key', num => 0, label => 'Master SSL Key' }, - master_user => { hdr => 'Master User', num => 0, label => 'Master username' }, - max_txn => { hdr => 'MaxTxnTime', num => 1, label => 'MaxTxn' }, - merged_recs => { hdr => 'Merged Recs', num => 1, label => 'Merged records' }, - merges => { hdr => 'Merges', num => 1, label => 'Merges' }, - mutex_os_waits => { hdr => 'Waits', num => 1, label => 'Mutex OS Waits' }, - mutex_spin_rounds => { hdr => 'Rounds', num => 1, label => 'Mutex Spin Rounds' }, - mutex_spin_waits => { hdr => 'Spins', num => 1, label => 'Mutex Spin Waits' }, - mysql_thread_id => { hdr => 'ID', num => 1, label => 'MySQL connection (thread) ID', }, - name => { hdr => 'Name', num => 0, label => 'Variable Name' }, - n_bits => { hdr => '# Bits', num => 1, label => 'Number of bits' }, - non_hash_searches_s => { hdr => 'Non-Hash/Sec', num => 1, label => 'Non-hash searches/sec' }, - num_deletes => { hdr => 'Del', num => 1, label => 'Number of deletes' }, - num_deletes_sec => { hdr => 'Del/Sec', num => 1, label => 'Number of deletes' }, - num_inserts => { hdr => 'Ins', num => 1, label => 'Number of inserts' }, - num_inserts_sec => { hdr => 'Ins/Sec', num => 1, label => 'Number of inserts' }, - num_readers => { hdr => 'Readers', num => 1, label => 'Number of readers' }, - num_reads => { hdr => 'Read', num => 1, label => 'Number of reads' }, - num_reads_sec => { hdr => 'Read/Sec', num => 1, label => 'Number of reads' }, - num_res_ext => { hdr => 'BTree Extents', num => 1, label => 'Number of extents reserved for B-Tree' }, - num_rows => { hdr => 'Row Count', num => 1, label => 'Number of rows estimated to examine' }, - num_times_open => { hdr => 'In Use', num => 1, label => '# times table is opened', }, - num_txns => { hdr => 'Txns', num => 1, label => 'Number of transactions' }, - num_updates => { hdr => 'Upd', num => 1, label => 'Number of updates' }, - num_updates_sec => { hdr => 'Upd/Sec', num => 1, label => 'Number of updates' }, - os_file_reads => { hdr => 'OS Reads', num => 1, label => 'OS file reads' }, - os_file_writes => { hdr => 'OS Writes', num => 1, label => 'OS file writes' }, - os_fsyncs => { hdr => 'OS fsyncs', num => 1, label => 'OS fsyncs' }, - os_thread_id => { hdr => 'OS Thread', num => 1, label => 'The operating system thread ID' }, - p_aio_writes => { hdr => 'Async Wrt', num => 1, label => 'Pending asynchronous I/O writes' }, - p_buf_pool_flushes => { hdr => 'Buffer Pool Flushes', num => 1, label => 'Pending buffer pool flushes' }, - p_ibuf_aio_reads => { hdr => 'IBuf Async Rds', num => 1, label => 'Pending insert buffer asynch I/O reads' }, - p_log_flushes => { hdr => 'Log Flushes', num => 1, label => 'Pending log flushes' }, - p_log_ios => { hdr => 'Log I/Os', num => 1, label => 'Pending log I/O operations' }, - p_normal_aio_reads => { hdr => 'Async Rds', num => 1, label => 'Pending asynchronous I/O reads' }, - p_preads => { hdr => 'preads', num => 1, label => 'Pending p-reads' }, - p_pwrites => { hdr => 'pwrites', num => 1, label => 'Pending p-writes' }, - p_sync_ios => { hdr => 'Sync I/Os', num => 1, label => 'Pending synchronous I/O operations' }, - page_creates_sec => { hdr => 'Creates/Sec', num => 1, label => 'Page creates/sec' }, - page_no => { hdr => 'Page', num => 1, label => 'Page number' }, - page_reads_sec => { hdr => 'Reads/Sec', num => 1, label => 'Page reads per second' }, - page_writes_sec => { hdr => 'Writes/Sec', num => 1, label => 'Page writes per second' }, - pages_created => { hdr => 'Created', num => 1, label => 'Pages created' }, - pages_modified => { hdr => 'Dirty Pages', num => 1, label => 'Pages modified (dirty)' }, - pages_read => { hdr => 'Reads', num => 1, label => 'Pages read' }, - pages_total => { hdr => 'Pages', num => 1, label => 'Pages total' }, - pages_written => { hdr => 'Writes', num => 1, label => 'Pages written' }, - parent_col => { hdr => 'Parent Column', num => 0, label => 'The referred column in the parent table', }, - parent_db => { hdr => 'Parent DB', num => 0, label => 'The database of the parent table' }, - parent_index => { hdr => 'Parent Index', num => 0, label => 'The referred index in the parent table' }, - parent_table => { hdr => 'Parent Table', num => 0, label => 'The parent table' }, - part_id => { hdr => 'Part ID', num => 1, label => 'Sub-part ID of the query' }, - partitions => { hdr => 'Partitions', num => 0, label => 'Query partitions used' }, - pct => { hdr => 'Pct', num => 1, label => 'Percentage' }, - pending_chkp_writes => { hdr => 'Chkpt Writes', num => 1, label => 'Pending log checkpoint writes' }, - pending_log_writes => { hdr => 'Log Writes', num => 1, label => 'Pending log writes' }, - port => { hdr => 'Port', num => 1, label => 'Client port number', }, - possible_keys => { hdr => 'Poss. Keys', num => 0, label => 'Possible keys' }, - proc_no => { hdr => 'Proc', num => 1, label => 'Process number' }, - q_cache_hit => { hdr => 'QCacheHit', num => 1, label => 'Query cache hit ratio', }, - qps => { hdr => 'QPS', num => 1, label => 'How many queries/sec', }, - queries_in_queue => { hdr => 'Queries Queued', num => 1, label => 'Queries in queue' }, - queries_inside => { hdr => 'Queries Inside', num => 1, label => 'Queries inside InnoDB' }, - query_id => { hdr => 'Query ID', num => 1, label => 'Query ID' }, - query_status => { hdr => 'Query Status', num => 0, label => 'The query status' }, - query_text => { hdr => 'Query Text', num => 0, label => 'The query text' }, - questions => { hdr => 'Questions', num => 1, label => 'How many queries the server has gotten', }, - read_master_log_pos => { hdr => 'Read Master Pos', num => 1, label => 'Read master log position' }, - read_views_open => { hdr => 'Rd Views', num => 1, label => 'Number of read views open' }, - reads_pending => { hdr => 'Pending Reads', num => 1, label => 'Reads pending' }, - relay_log_file => { hdr => 'Relay File', num => 0, label => 'Relay log file' }, - relay_log_pos => { hdr => 'Relay Pos', num => 1, label => 'Relay log position' }, - relay_log_size => { hdr => 'Relay Size', num => 1, label => 'Relay log size' }, - relay_master_log_file => { hdr => 'Relay Master File', num => 0, label => 'Relay master log file' }, - replicate_do_db => { hdr => 'Do DB', num => 0, label => 'Replicate-do-db setting' }, - replicate_do_table => { hdr => 'Do Table', num => 0, label => 'Replicate-do-table setting' }, - replicate_ignore_db => { hdr => 'Ignore DB', num => 0, label => 'Replicate-ignore-db setting' }, - replicate_ignore_table => { hdr => 'Ignore Table', num => 0, label => 'Replicate-do-table setting' }, - replicate_wild_do_table => { hdr => 'Wild Do Table', num => 0, label => 'Replicate-wild-do-table setting' }, - replicate_wild_ignore_table => { hdr => 'Wild Ignore Table', num => 0, label => 'Replicate-wild-ignore-table setting' }, - request_type => { hdr => 'Type', num => 0, label => 'Type of lock the thread waits for' }, - reservation_count => { hdr => 'ResCnt', num => 1, label => 'Reservation Count' }, - row_locks => { hdr => 'RLocks', num => 1, label => 'Number of row locks' }, - rw_excl_os_waits => { hdr => 'RW Waits', num => 1, label => 'R/W Excl. OS Waits' }, - rw_excl_spins => { hdr => 'RW Spins', num => 1, label => 'R/W Excl. Spins' }, - rw_shared_os_waits => { hdr => 'Sh Waits', num => 1, label => 'R/W Shared OS Waits' }, - rw_shared_spins => { hdr => 'Sh Spins', num => 1, label => 'R/W Shared Spins' }, - scan_type => { hdr => 'Type', num => 0, label => 'Scan type in chosen' }, - seg_size => { hdr => 'Seg. Size', num => 1, label => 'Segment size' }, - select_type => { hdr => 'Select Type', num => 0, label => 'Type of select used' }, - signal_count => { hdr => 'Signals', num => 1, label => 'Signal Count' }, - size => { hdr => 'Size', num => 1, label => 'Size of the tablespace' }, - skip_counter => { hdr => 'Skip Counter', num => 1, label => 'Skip counter' }, - slave_catchup_rate => { hdr => 'Catchup', num => 1, label => 'How fast the slave is catching up in the binlog' }, - slave_io_running => { hdr => 'Slave-IO', num => 0, label => 'Whether the slave I/O thread is running' }, - slave_io_state => { hdr => 'Slave IO State', num => 0, label => 'Slave I/O thread state' }, - slave_open_temp_tables => { hdr => 'Temp', num => 1, label => 'Slave open temp tables' }, - slave_sql_running => { hdr => 'Slave-SQL', num => 0, label => 'Whether the slave SQL thread is running' }, - slow => { hdr => 'Slow', num => 1, label => 'How many slow queries', }, - space_id => { hdr => 'Space', num => 1, label => 'Tablespace ID' }, - special => { hdr => 'Special', num => 0, label => 'Special/Other info' }, - state => { hdr => 'State', num => 0, label => 'Connection state', maxw => 18, }, - tables_in_use => { hdr => 'Tbl Used', num => 1, label => 'Number of tables in use' }, - tables_locked => { hdr => 'Tbl Lck', num => 1, label => 'Number of tables locked' }, - tbl => { hdr => 'Table', num => 0, label => 'Table', }, - thread => { hdr => 'Thread', num => 1, label => 'Thread number' }, - thread_decl_inside => { hdr => 'Thread Inside', num => 0, label => 'What the thread is declared inside' }, - thread_purpose => { hdr => 'Purpose', num => 0, label => "The thread's purpose" }, - thread_status => { hdr => 'Thread Status', num => 0, label => 'The thread status' }, - time => { hdr => 'Time', num => 1, label => 'Time since the last event', }, - time_behind_master => { hdr => 'TimeLag', num => 1, label => 'Time slave lags master' }, - timestring => { hdr => 'Timestring', num => 0, label => 'Time the event occurred' }, - total => { hdr => 'Total', num => 1, label => 'Total' }, - total_mem_alloc => { hdr => 'Memory', num => 1, label => 'Total memory allocated' }, - truncates => { hdr => 'Trunc', num => 0, label => 'Whether the deadlock is truncating InnoDB status' }, - txn_doesnt_see_ge => { hdr => "Txn Won't See", num => 0, label => 'Where txn read view is limited' }, - txn_id => { hdr => 'ID', num => 0, label => 'Transaction ID' }, - txn_sees_lt => { hdr => 'Txn Sees', num => 1, label => 'Where txn read view is limited' }, - txn_status => { hdr => 'Txn Status', num => 0, label => 'Transaction status' }, - txn_time_remain => { hdr => 'Remaining', num => 1, label => 'Time until txn rollback/commit completes' }, - undo_log_entries => { hdr => 'Undo', num => 1, label => 'Number of undo log entries' }, - undo_for => { hdr => 'Undo', num => 0, label => 'Undo for' }, - until_condition => { hdr => 'Until Condition', num => 0, label => 'Slave until condition' }, - until_log_file => { hdr => 'Until Log File', num => 0, label => 'Slave until log file' }, - until_log_pos => { hdr => 'Until Log Pos', num => 1, label => 'Slave until log position' }, - used_cells => { hdr => 'Cells Used', num => 1, label => 'Number of cells used' }, - used_bufs => { hdr => 'Used Bufs', num => 1, label => 'Number of buffer pool pages used' }, - user => { hdr => 'User', num => 0, label => 'Database username', }, - value => { hdr => 'Value', num => 1, label => 'Value' }, - versions => { hdr => 'Versions', num => 1, label => 'Number of InnoDB MVCC versions unpurged' }, - victim => { hdr => 'Victim', num => 0, label => 'Whether this txn was the deadlock victim' }, - wait_array_size => { hdr => 'Wait Array Size', num => 1, label => 'Wait Array Size' }, - wait_status => { hdr => 'Lock Status', num => 0, label => 'Status of txn locks' }, - waited_at_filename => { hdr => 'File', num => 0, label => 'Filename at which thread waits' }, - waited_at_line => { hdr => 'Line', num => 1, label => 'Line at which thread waits' }, - waiters_flag => { hdr => 'Waiters', num => 1, label => 'Waiters Flag' }, - waiting => { hdr => 'Waiting', num => 1, label => 'Whether lock is being waited for' }, - when => { hdr => 'When', num => 0, label => 'Time scale' }, - writer_lock_mode => { hdr => 'Wrtr Lck Mode', num => 0, label => 'Writer lock mode' }, - writer_thread => { hdr => 'Wrtr Thread', num => 1, label => 'Writer thread ID' }, - writes_pending => { hdr => 'Writes', num => 1, label => 'Number of writes pending' }, - writes_pending_flush_list => { hdr => 'Flush List Writes', num => 1, label => 'Number of flush list writes pending' }, - writes_pending_lru => { hdr => 'LRU Writes', num => 1, label => 'Number of LRU writes pending' }, - writes_pending_single_page => { hdr => '1-Page Writes', num => 1, label => 'Number of 1-page writes pending' }, -); - -# Apply a default property or three. By default, columns are not width-constrained, -# aligned left, and sorted alphabetically, not numerically. -foreach my $col ( values %columns ) { - map { $col->{$_} ||= 0 } qw(num minw maxw); - $col->{just} = $col->{num} ? '' : '-'; -} - -# Filters {{{3 -# This hash defines every filter that can be applied to a table. These -# become part of tbl_meta as well. Each filter is just an expression that -# returns true or false. -# Properties of each entry: -# * func: the subroutine -# * name: the name, repeated -# * user: whether it's a user-defined filter (saved in config) -# * text: text of the subroutine -# * note: explanation -my %filters = (); - -# These are pre-processed to live in %filters above, by compiling them. -my %builtin_filters = ( - hide_self => { - text => <<' END', - return ( !$set->{info} || $set->{info} ne 'SHOW FULL PROCESSLIST' ) - && ( !$set->{query_text} || $set->{query_text} !~ m/INNODB STATUS$/ ); - END - note => 'Removes the innotop processes from the list', - tbls => [qw(innodb_transactions processlist)], - }, - hide_inactive => { - text => <<' END', - return ( !defined($set->{txn_status}) || $set->{txn_status} ne 'not started' ) - && ( !defined($set->{cmd}) || $set->{cmd} !~ m/Sleep|Binlog Dump/ ) - && ( !defined($set->{info}) || $set->{info} =~ m/\S/ ); - END - note => 'Removes processes which are not doing anything', - tbls => [qw(innodb_transactions processlist)], - }, - hide_slave_io => { - text => <<' END', - return !$set->{state} || $set->{state} !~ m/^(?:Waiting for master|Has read all relay)/; - END - note => 'Removes slave I/O threads from the list', - tbls => [qw(processlist slave_io_status)], - }, - table_is_open => { - text => <<' END', - return $set->{num_times_open} + $set->{is_name_locked}; - END - note => 'Removes tables that are not in use or locked', - tbls => [qw(open_tables)], - }, - cxn_is_master => { - text => <<' END', - return $set->{master_file} ? 1 : 0; - END - note => 'Removes servers that are not masters', - tbls => [qw(master_status)], - }, - cxn_is_slave => { - text => <<' END', - return $set->{master_host} ? 1 : 0; - END - note => 'Removes servers that are not slaves', - tbls => [qw(slave_io_status slave_sql_status)], - }, - thd_is_not_waiting => { - text => <<' END', - return $set->{thread_status} !~ m#waiting for i/o request#; - END - note => 'Removes idle I/O threads', - tbls => [qw(io_threads)], - }, -); -foreach my $key ( keys %builtin_filters ) { - my ( $sub, $err ) = compile_filter($builtin_filters{$key}->{text}); - $filters{$key} = { - func => $sub, - text => $builtin_filters{$key}->{text}, - user => 0, - name => $key, # useful for later - note => $builtin_filters{$key}->{note}, - tbls => $builtin_filters{$key}->{tbls}, - } -} - -# Variable sets {{{3 -# Sets (arrayrefs) of variables that are used in S mode. They are read/written to -# the config file. -my %var_sets = ( - general => { - text => join( - ', ', - 'set_precision(Questions/Uptime_hires) as QPS', - 'set_precision(Com_commit/Uptime_hires) as Commit_PS', - 'set_precision((Com_rollback||0)/(Com_commit||1)) as Rollback_Commit', - 'set_precision((' - . join('+', map { "($_||0)" } - qw(Com_delete Com_delete_multi Com_insert Com_insert_select Com_replace - Com_replace_select Com_select Com_update Com_update_multi)) - . ')/(Com_commit||1)) as Write_Commit', - 'set_precision((Com_select+(Qcache_hits||0))/((' - . join('+', map { "($_||0)" } - qw(Com_delete Com_delete_multi Com_insert Com_insert_select Com_replace - Com_replace_select Com_select Com_update Com_update_multi)) - . ')||1)) as R_W_Ratio', - 'set_precision(Opened_tables/Uptime_hires) as Opens_PS', - 'percent($cur->{Open_tables}/($cur->{table_cache})) as Table_Cache_Used', - 'set_precision(Threads_created/Uptime_hires) as Threads_PS', - 'percent($cur->{Threads_cached}/($cur->{thread_cache_size}||1)) as Thread_Cache_Used', - 'percent($cur->{Max_used_connections}/($cur->{max_connections}||1)) as CXN_Used_Ever', - 'percent($cur->{Threads_connected}/($cur->{max_connections}||1)) as CXN_Used_Now', - ), - }, - commands => { - text => join( - ', ', - qw(Uptime Questions Com_delete Com_delete_multi Com_insert - Com_insert_select Com_replace Com_replace_select Com_select Com_update - Com_update_multi) - ), - }, - query_status => { - text => join( - ',', - qw( Uptime Select_full_join Select_full_range_join Select_range - Select_range_check Select_scan Slow_queries Sort_merge_passes - Sort_range Sort_rows Sort_scan) - ), - }, - innodb => { - text => join( - ',', - qw( Uptime Innodb_row_lock_current_waits Innodb_row_lock_time - Innodb_row_lock_time_avg Innodb_row_lock_time_max Innodb_row_lock_waits - Innodb_rows_deleted Innodb_rows_inserted Innodb_rows_read - Innodb_rows_updated) - ), - }, - txn => { - text => join( - ',', - qw( Uptime Com_begin Com_commit Com_rollback Com_savepoint - Com_xa_commit Com_xa_end Com_xa_prepare Com_xa_recover Com_xa_rollback - Com_xa_start) - ), - }, - key_cache => { - text => join( - ',', - qw( Uptime Key_blocks_not_flushed Key_blocks_unused Key_blocks_used - Key_read_requests Key_reads Key_write_requests Key_writes ) - ), - }, - query_cache => { - text => join( - ',', - "percent($exprs{QcacheHitRatio}) as Hit_Pct", - 'set_precision((Qcache_hits||0)/(Qcache_inserts||1)) as Hit_Ins', - 'set_precision((Qcache_lowmem_prunes||0)/Uptime_hires) as Lowmem_Prunes_sec', - 'percent(1-((Qcache_free_blocks||0)/(Qcache_total_blocks||1))) as Blocks_used', - qw( Qcache_free_blocks Qcache_free_memory Qcache_not_cached Qcache_queries_in_cache) - ), - }, - handler => { - text => join( - ',', - qw( Uptime Handler_read_key Handler_read_first Handler_read_next - Handler_read_prev Handler_read_rnd Handler_read_rnd_next Handler_delete - Handler_update Handler_write) - ), - }, - cxns_files_threads => { - text => join( - ',', - qw( Uptime Aborted_clients Aborted_connects Bytes_received Bytes_sent - Compression Connections Created_tmp_disk_tables Created_tmp_files - Created_tmp_tables Max_used_connections Open_files Open_streams - Open_tables Opened_tables Table_locks_immediate Table_locks_waited - Threads_cached Threads_connected Threads_created Threads_running) - ), - }, - prep_stmt => { - text => join( - ',', - qw( Uptime Com_dealloc_sql Com_execute_sql Com_prepare_sql Com_reset - Com_stmt_close Com_stmt_execute Com_stmt_fetch Com_stmt_prepare - Com_stmt_reset Com_stmt_send_long_data ) - ), - }, - innodb_health => { - text => join( - ',', - "$exprs{OldVersions} as OldVersions", - qw(IB_sm_mutex_spin_waits IB_sm_mutex_spin_rounds IB_sm_mutex_os_waits), - "$exprs{NumTxns} as NumTxns", - "$exprs{MaxTxnTime} as MaxTxnTime", - qw(IB_ro_queries_inside IB_ro_queries_in_queue), - "set_precision($exprs{DirtyBufs} * 100) as dirty_bufs", - "set_precision($exprs{BufPoolFill} * 100) as buf_fill", - qw(IB_bp_pages_total IB_bp_pages_read IB_bp_pages_written IB_bp_pages_created) - ), - }, - innodb_health2 => { - text => join( - ', ', - 'percent(1-((Innodb_buffer_pool_pages_free||0)/($cur->{Innodb_buffer_pool_pages_total}||1))) as BP_page_cache_usage', - 'percent(1-((Innodb_buffer_pool_reads||0)/(Innodb_buffer_pool_read_requests||1))) as BP_cache_hit_ratio', - 'Innodb_buffer_pool_wait_free', - 'Innodb_log_waits', - ), - }, - slow_queries => { - text => join( - ', ', - 'set_precision(Slow_queries/Uptime_hires) as Slow_PS', - 'set_precision(Select_full_join/Uptime_hires) as Full_Join_PS', - 'percent(Select_full_join/(Com_select||1)) as Full_Join_Ratio', - ), - }, -); - -# Server sets {{{3 -# Defines sets of servers between which the user can quickly switch. -my %server_groups; - -# Connections {{{3 -# This hash defines server connections. Each connection is a string that can be passed to -# the DBI connection. These are saved in the connections section in the config file. -my %connections; -# Defines the parts of connections. -my @conn_parts = qw(user have_user pass have_pass dsn savepass dl_table); - -# Graph widths {{{3 -# This hash defines the max values seen for various status/variable values, for graphing. -# These are stored in their own section in the config file. These are just initial values: -my %mvs = ( - Com_select => 50, - Com_insert => 50, - Com_update => 50, - Com_delete => 50, - Questions => 100, -); - -# ########################################################################### -# Valid Term::ANSIColor color strings. -# ########################################################################### -my %ansicolors = map { $_ => 1 } - qw( black blink blue bold clear concealed cyan dark green magenta on_black - on_blue on_cyan on_green on_magenta on_red on_white on_yellow red reset - reverse underline underscore white yellow); - -# ########################################################################### -# Valid comparison operators for color rules -# ########################################################################### -my %comp_ops = ( - '==' => 'Numeric equality', - '>' => 'Numeric greater-than', - '<' => 'Numeric less-than', - '>=' => 'Numeric greater-than/equal', - '<=' => 'Numeric less-than/equal', - '!=' => 'Numeric not-equal', - 'eq' => 'String equality', - 'gt' => 'String greater-than', - 'lt' => 'String less-than', - 'ge' => 'String greater-than/equal', - 'le' => 'String less-than/equal', - 'ne' => 'String not-equal', - '=~' => 'Pattern match', - '!~' => 'Negated pattern match', -); - -# ########################################################################### -# Valid aggregate functions. -# ########################################################################### -my %agg_funcs = ( - first => sub { - return $_[0] - }, - count => sub { - return 0 + @_; - }, - avg => sub { - my @args = grep { defined $_ } @_; - return (sum(map { m/([\d\.-]+)/g } @args) || 0) / (scalar(@args) || 1); - }, - sum => \&sum, -); - -# ########################################################################### -# Valid functions for transformations. -# ########################################################################### -my %trans_funcs = ( - shorten => \&shorten, - secs_to_time => \&secs_to_time, - no_ctrl_char => \&no_ctrl_char, - percent => \&percent, - commify => \&commify, - dulint_to_int => \&dulint_to_int, - set_precision => \&set_precision, -); - -# Table definitions {{{3 -# This hash defines every table that can get displayed in every mode. Each -# table specifies columns and column data sources. The column is -# defined by the %columns hash. -# -# Example: foo => { src => 'bar' } means the foo column (look at -# $columns{foo} for its definition) gets its data from the 'bar' element of -# the current data set, whatever that is. -# -# These columns are post-processed after being defined, because they get stuff -# from %columns. After all the config is loaded for columns, there's more -# post-processing too; the subroutines compiled from src get added to -# the hash elements for extract_values to use. -# ########################################################################### - -my %tbl_meta = ( - adaptive_hash_index => { - capt => 'Adaptive Hash Index', - cust => {}, - cols => { - cxn => { src => 'cxn' }, - hash_table_size => { src => 'IB_ib_hash_table_size', trans => [qw(shorten)], }, - used_cells => { src => 'IB_ib_used_cells' }, - bufs_in_node_heap => { src => 'IB_ib_bufs_in_node_heap' }, - hash_searches_s => { src => 'IB_ib_hash_searches_s' }, - non_hash_searches_s => { src => 'IB_ib_non_hash_searches_s' }, - }, - visible => [ qw(cxn hash_table_size used_cells bufs_in_node_heap hash_searches_s non_hash_searches_s) ], - filters => [], - sort_cols => 'cxn', - sort_dir => '1', - innodb => 'ib', - group_by => [], - aggregate => 0, - }, - buffer_pool => { - capt => 'Buffer Pool', - cust => {}, - cols => { - cxn => { src => 'cxn' }, - total_mem_alloc => { src => 'IB_bp_total_mem_alloc', trans => [qw(shorten)], }, - awe_mem_alloc => { src => 'IB_bp_awe_mem_alloc', trans => [qw(shorten)], }, - add_pool_alloc => { src => 'IB_bp_add_pool_alloc', trans => [qw(shorten)], }, - buf_pool_size => { src => 'IB_bp_buf_pool_size', trans => [qw(shorten)], }, - buf_free => { src => 'IB_bp_buf_free' }, - buf_pool_hit_rate => { src => 'IB_bp_buf_pool_hit_rate' }, - buf_pool_reads => { src => 'IB_bp_buf_pool_reads' }, - buf_pool_hits => { src => 'IB_bp_buf_pool_hits' }, - dict_mem_alloc => { src => 'IB_bp_dict_mem_alloc' }, - pages_total => { src => 'IB_bp_pages_total' }, - pages_modified => { src => 'IB_bp_pages_modified' }, - reads_pending => { src => 'IB_bp_reads_pending' }, - writes_pending => { src => 'IB_bp_writes_pending' }, - writes_pending_lru => { src => 'IB_bp_writes_pending_lru' }, - writes_pending_flush_list => { src => 'IB_bp_writes_pending_flush_list' }, - writes_pending_single_page => { src => 'IB_bp_writes_pending_single_page' }, - page_creates_sec => { src => 'IB_bp_page_creates_sec' }, - page_reads_sec => { src => 'IB_bp_page_reads_sec' }, - page_writes_sec => { src => 'IB_bp_page_writes_sec' }, - pages_created => { src => 'IB_bp_pages_created' }, - pages_read => { src => 'IB_bp_pages_read' }, - pages_written => { src => 'IB_bp_pages_written' }, - }, - visible => [ qw(cxn buf_pool_size buf_free pages_total pages_modified buf_pool_hit_rate total_mem_alloc add_pool_alloc)], - filters => [], - sort_cols => 'cxn', - sort_dir => '1', - innodb => 'bp', - group_by => [], - aggregate => 0, - }, - # TODO: a new step in set_to_tbl: join result to itself, grouped? - # TODO: this would also enable pulling Q and T data together. - # TODO: using a SQL-ish language would also allow pivots to be easier -- treat the pivoted data as a view and SELECT from it. - cmd_summary => { - capt => 'Command Summary', - cust => {}, - cols => { - name => { src => 'name' }, - total => { src => 'total' }, - value => { src => 'value', agg => 'sum'}, - pct => { src => 'value/total', trans => [qw(percent)] }, - last_total => { src => 'last_total' }, - last_value => { src => 'last_value', agg => 'sum'}, - last_pct => { src => 'last_value/last_total', trans => [qw(percent)] }, - }, - visible => [qw(name value pct last_value last_pct)], - filters => [qw()], - sort_cols => '-value', - sort_dir => '1', - innodb => '', - group_by => [qw(name)], - aggregate => 1, - }, - deadlock_locks => { - capt => 'Deadlock Locks', - cust => {}, - cols => { - cxn => { src => 'cxn' }, - mysql_thread_id => { src => 'mysql_thread_id' }, - dl_txn_num => { src => 'dl_txn_num' }, - lock_type => { src => 'lock_type' }, - space_id => { src => 'space_id' }, - page_no => { src => 'page_no' }, - heap_no => { src => 'heap_no' }, - n_bits => { src => 'n_bits' }, - index => { src => 'index' }, - db => { src => 'db' }, - tbl => { src => 'table' }, - lock_mode => { src => 'lock_mode' }, - special => { src => 'special' }, - insert_intention => { src => 'insert_intention' }, - waiting => { src => 'waiting' }, - }, - visible => [ qw(cxn mysql_thread_id waiting lock_mode db tbl index special insert_intention)], - filters => [], - sort_cols => 'cxn mysql_thread_id', - sort_dir => '1', - innodb => 'dl', - group_by => [], - aggregate => 0, - }, - deadlock_transactions => { - capt => 'Deadlock Transactions', - cust => {}, - cols => { - cxn => { src => 'cxn' }, - active_secs => { src => 'active_secs' }, - dl_txn_num => { src => 'dl_txn_num' }, - has_read_view => { src => 'has_read_view' }, - heap_size => { src => 'heap_size' }, - host_and_domain => { src => 'hostname' }, - hostname => { src => $exprs{Host} }, - ip => { src => 'ip' }, - lock_structs => { src => 'lock_structs' }, - lock_wait_time => { src => 'lock_wait_time', trans => [ qw(secs_to_time) ] }, - mysql_thread_id => { src => 'mysql_thread_id' }, - os_thread_id => { src => 'os_thread_id' }, - proc_no => { src => 'proc_no' }, - query_id => { src => 'query_id' }, - query_status => { src => 'query_status' }, - query_text => { src => 'query_text', trans => [ qw(no_ctrl_char) ] }, - row_locks => { src => 'row_locks' }, - tables_in_use => { src => 'tables_in_use' }, - tables_locked => { src => 'tables_locked' }, - thread_decl_inside => { src => 'thread_decl_inside' }, - thread_status => { src => 'thread_status' }, - 'time' => { src => 'active_secs', trans => [ qw(secs_to_time) ] }, - timestring => { src => 'timestring' }, - txn_doesnt_see_ge => { src => 'txn_doesnt_see_ge' }, - txn_id => { src => 'txn_id' }, - txn_sees_lt => { src => 'txn_sees_lt' }, - txn_status => { src => 'txn_status' }, - truncates => { src => 'truncates' }, - undo_log_entries => { src => 'undo_log_entries' }, - user => { src => 'user' }, - victim => { src => 'victim' }, - wait_status => { src => 'lock_wait_status' }, - }, - visible => [ qw(cxn mysql_thread_id timestring user hostname victim time undo_log_entries lock_structs query_text)], - filters => [], - sort_cols => 'cxn mysql_thread_id', - sort_dir => '1', - innodb => 'dl', - group_by => [], - aggregate => 0, - }, - explain => { - capt => 'EXPLAIN Results', - cust => {}, - cols => { - part_id => { src => 'id' }, - select_type => { src => 'select_type' }, - tbl => { src => 'table' }, - partitions => { src => 'partitions' }, - scan_type => { src => 'type' }, - possible_keys => { src => 'possible_keys' }, - index => { src => 'key' }, - key_len => { src => 'key_len' }, - index_ref => { src => 'ref' }, - num_rows => { src => 'rows' }, - special => { src => 'extra' }, - }, - visible => [ qw(select_type tbl partitions scan_type possible_keys index key_len index_ref num_rows special)], - filters => [], - sort_cols => '', - sort_dir => '1', - innodb => '', - group_by => [], - aggregate => 0, - }, - file_io_misc => { - capt => 'File I/O Misc', - cust => {}, - cols => { - cxn => { src => 'cxn' }, - io_bytes_s => { src => 'IB_io_avg_bytes_s' }, - io_flush_type => { src => 'IB_io_flush_type' }, - io_fsyncs_s => { src => 'IB_io_fsyncs_s' }, - io_reads_s => { src => 'IB_io_reads_s' }, - io_writes_s => { src => 'IB_io_writes_s' }, - os_file_reads => { src => 'IB_io_os_file_reads' }, - os_file_writes => { src => 'IB_io_os_file_writes' }, - os_fsyncs => { src => 'IB_io_os_fsyncs' }, - }, - visible => [ qw(cxn os_file_reads os_file_writes os_fsyncs io_reads_s io_writes_s io_bytes_s)], - filters => [], - sort_cols => 'cxn', - sort_dir => '1', - innodb => 'io', - group_by => [], - aggregate => 0, - }, - fk_error => { - capt => 'Foreign Key Error Info', - cust => {}, - cols => { - timestring => { src => 'IB_fk_timestring' }, - child_db => { src => 'IB_fk_child_db' }, - child_table => { src => 'IB_fk_child_table' }, - child_index => { src => 'IB_fk_child_index' }, - fk_name => { src => 'IB_fk_fk_name' }, - parent_db => { src => 'IB_fk_parent_db' }, - parent_table => { src => 'IB_fk_parent_table' }, - parent_col => { src => 'IB_fk_parent_col' }, - parent_index => { src => 'IB_fk_parent_index' }, - attempted_op => { src => 'IB_fk_attempted_op' }, - }, - visible => [ qw(timestring child_db child_table child_index parent_db parent_table parent_col parent_index fk_name attempted_op)], - filters => [], - sort_cols => '', - sort_dir => '1', - innodb => 'fk', - group_by => [], - aggregate => 0, - }, - insert_buffers => { - capt => 'Insert Buffers', - cust => {}, - cols => { - cxn => { src => 'cxn' }, - inserts => { src => 'IB_ib_inserts' }, - merged_recs => { src => 'IB_ib_merged_recs' }, - merges => { src => 'IB_ib_merges' }, - size => { src => 'IB_ib_size' }, - free_list_len => { src => 'IB_ib_free_list_len' }, - seg_size => { src => 'IB_ib_seg_size' }, - }, - visible => [ qw(cxn inserts merged_recs merges size free_list_len seg_size)], - filters => [], - sort_cols => 'cxn', - sort_dir => '1', - innodb => 'ib', - group_by => [], - aggregate => 0, - }, - innodb_locks => { - capt => 'InnoDB Locks', - cust => {}, - cols => { - cxn => { src => 'cxn' }, - db => { src => 'db' }, - index => { src => 'index' }, - insert_intention => { src => 'insert_intention' }, - lock_mode => { src => 'lock_mode' }, - lock_type => { src => 'lock_type' }, - lock_wait_time => { src => 'lock_wait_time', trans => [ qw(secs_to_time) ] }, - mysql_thread_id => { src => 'mysql_thread_id' }, - n_bits => { src => 'n_bits' }, - page_no => { src => 'page_no' }, - space_id => { src => 'space_id' }, - special => { src => 'special' }, - tbl => { src => 'table' }, - 'time' => { src => 'active_secs', hdr => 'Active', trans => [ qw(secs_to_time) ] }, - txn_id => { src => 'txn_id' }, - waiting => { src => 'waiting' }, - }, - visible => [ qw(cxn mysql_thread_id lock_type waiting lock_wait_time time lock_mode db tbl index insert_intention special)], - filters => [], - sort_cols => 'cxn -lock_wait_time', - sort_dir => '1', - innodb => 'tx', - colors => [ - { col => 'lock_wait_time', op => '>', arg => 60, color => 'red' }, - { col => 'lock_wait_time', op => '>', arg => 30, color => 'yellow' }, - { col => 'lock_wait_time', op => '>', arg => 10, color => 'green' }, - ], - group_by => [], - aggregate => 0, - }, - innodb_transactions => { - capt => 'InnoDB Transactions', - cust => {}, - cols => { - cxn => { src => 'cxn' }, - active_secs => { src => 'active_secs' }, - has_read_view => { src => 'has_read_view' }, - heap_size => { src => 'heap_size' }, - hostname => { src => $exprs{Host} }, - ip => { src => 'ip' }, - wait_status => { src => 'lock_wait_status' }, - lock_wait_time => { src => 'lock_wait_time', trans => [ qw(secs_to_time) ] }, - lock_structs => { src => 'lock_structs' }, - mysql_thread_id => { src => 'mysql_thread_id' }, - os_thread_id => { src => 'os_thread_id' }, - proc_no => { src => 'proc_no' }, - query_id => { src => 'query_id' }, - query_status => { src => 'query_status' }, - query_text => { src => 'query_text', trans => [ qw(no_ctrl_char) ] }, - txn_time_remain => { src => $exprs{TxnTimeRemain}, trans => [ qw(secs_to_time) ] }, - row_locks => { src => 'row_locks' }, - tables_in_use => { src => 'tables_in_use' }, - tables_locked => { src => 'tables_locked' }, - thread_decl_inside => { src => 'thread_decl_inside' }, - thread_status => { src => 'thread_status' }, - 'time' => { src => 'active_secs', trans => [ qw(secs_to_time) ], agg => 'sum' }, - txn_doesnt_see_ge => { src => 'txn_doesnt_see_ge' }, - txn_id => { src => 'txn_id' }, - txn_sees_lt => { src => 'txn_sees_lt' }, - txn_status => { src => 'txn_status', minw => 10, maxw => 10 }, - undo_log_entries => { src => 'undo_log_entries' }, - user => { src => 'user', maxw => 10 }, - cnt => { src => 'mysql_thread_id', minw => 0 }, - }, - visible => [ qw(cxn cnt mysql_thread_id user hostname txn_status time undo_log_entries query_text)], - filters => [ qw( hide_self hide_inactive ) ], - sort_cols => '-active_secs txn_status cxn mysql_thread_id', - sort_dir => '1', - innodb => 'tx', - hide_caption => 1, - colors => [ - { col => 'wait_status', op => 'eq', arg => 'LOCK WAIT', color => 'black on_red' }, - { col => 'time', op => '>', arg => 600, color => 'red' }, - { col => 'time', op => '>', arg => 300, color => 'yellow' }, - { col => 'time', op => '>', arg => 60, color => 'green' }, - { col => 'time', op => '>', arg => 30, color => 'cyan' }, - { col => 'txn_status', op => 'eq', arg => 'not started', color => 'white' }, - ], - group_by => [ qw(cxn txn_status) ], - aggregate => 0, - }, - io_threads => { - capt => 'I/O Threads', - cust => {}, - cols => { - cxn => { src => 'cxn' }, - thread => { src => 'thread' }, - thread_purpose => { src => 'purpose' }, - event_set => { src => 'event_set' }, - thread_status => { src => 'state' }, - }, - visible => [ qw(cxn thread thread_purpose thread_status)], - filters => [ qw() ], - sort_cols => 'cxn thread', - sort_dir => '1', - innodb => 'io', - group_by => [], - aggregate => 0, - }, - log_statistics => { - capt => 'Log Statistics', - cust => {}, - cols => { - cxn => { src => 'cxn' }, - last_chkp => { src => 'IB_lg_last_chkp' }, - log_flushed_to => { src => 'IB_lg_log_flushed_to' }, - log_ios_done => { src => 'IB_lg_log_ios_done' }, - log_ios_s => { src => 'IB_lg_log_ios_s' }, - log_seq_no => { src => 'IB_lg_log_seq_no' }, - pending_chkp_writes => { src => 'IB_lg_pending_chkp_writes' }, - pending_log_writes => { src => 'IB_lg_pending_log_writes' }, - }, - visible => [ qw(cxn log_seq_no log_flushed_to last_chkp log_ios_done log_ios_s)], - filters => [], - sort_cols => 'cxn', - sort_dir => '1', - innodb => 'lg', - group_by => [], - aggregate => 0, - }, - master_status => { - capt => 'Master Status', - cust => {}, - cols => { - cxn => { src => 'cxn' }, - binlog_do_db => { src => 'binlog_do_db' }, - binlog_ignore_db => { src => 'binlog_ignore_db' }, - master_file => { src => 'file' }, - master_pos => { src => 'position' }, - binlog_cache_overflow => { src => '(Binlog_cache_disk_use||0)/(Binlog_cache_use||1)', trans => [ qw(percent) ] }, - }, - visible => [ qw(cxn master_file master_pos binlog_cache_overflow)], - filters => [ qw(cxn_is_master) ], - sort_cols => 'cxn', - sort_dir => '1', - innodb => '', - group_by => [], - aggregate => 0, - }, - pending_io => { - capt => 'Pending I/O', - cust => {}, - cols => { - cxn => { src => 'cxn' }, - p_normal_aio_reads => { src => 'IB_io_pending_normal_aio_reads' }, - p_aio_writes => { src => 'IB_io_pending_aio_writes' }, - p_ibuf_aio_reads => { src => 'IB_io_pending_ibuf_aio_reads' }, - p_sync_ios => { src => 'IB_io_pending_sync_ios' }, - p_buf_pool_flushes => { src => 'IB_io_pending_buffer_pool_flushes' }, - p_log_flushes => { src => 'IB_io_pending_log_flushes' }, - p_log_ios => { src => 'IB_io_pending_log_ios' }, - p_preads => { src => 'IB_io_pending_preads' }, - p_pwrites => { src => 'IB_io_pending_pwrites' }, - }, - visible => [ qw(cxn p_normal_aio_reads p_aio_writes p_ibuf_aio_reads p_sync_ios p_log_flushes p_log_ios)], - filters => [], - sort_cols => 'cxn', - sort_dir => '1', - innodb => 'io', - group_by => [], - aggregate => 0, - }, - open_tables => { - capt => 'Open Tables', - cust => {}, - cols => { - cxn => { src => 'cxn' }, - db => { src => 'database' }, - tbl => { src => 'table' }, - num_times_open => { src => 'in_use' }, - is_name_locked => { src => 'name_locked' }, - }, - visible => [ qw(cxn db tbl num_times_open is_name_locked)], - filters => [ qw(table_is_open) ], - sort_cols => '-num_times_open cxn db tbl', - sort_dir => '1', - innodb => '', - group_by => [], - aggregate => 0, - }, - page_statistics => { - capt => 'Page Statistics', - cust => {}, - cols => { - cxn => { src => 'cxn' }, - pages_read => { src => 'IB_bp_pages_read' }, - pages_written => { src => 'IB_bp_pages_written' }, - pages_created => { src => 'IB_bp_pages_created' }, - page_reads_sec => { src => 'IB_bp_page_reads_sec' }, - page_writes_sec => { src => 'IB_bp_page_writes_sec' }, - page_creates_sec => { src => 'IB_bp_page_creates_sec' }, - }, - visible => [ qw(cxn pages_read pages_written pages_created page_reads_sec page_writes_sec page_creates_sec)], - filters => [], - sort_cols => 'cxn', - sort_dir => '1', - innodb => 'bp', - group_by => [], - aggregate => 0, - }, - processlist => { - capt => 'MySQL Process List', - cust => {}, - cols => { - cxn => { src => 'cxn', minw => 6, maxw => 10 }, - mysql_thread_id => { src => 'id', minw => 6, maxw => 0 }, - user => { src => 'user', minw => 5, maxw => 8 }, - hostname => { src => $exprs{Host}, minw => 13, maxw => 8, }, - port => { src => $exprs{Port}, minw => 0, maxw => 0, }, - host_and_port => { src => 'host', minw => 0, maxw => 0 }, - db => { src => 'db', minw => 6, maxw => 12 }, - cmd => { src => 'command', minw => 5, maxw => 0 }, - time => { src => 'time', minw => 5, maxw => 0, trans => [ qw(secs_to_time) ], agg => 'sum' }, - state => { src => 'state', minw => 0, maxw => 0 }, - info => { src => 'info', minw => 0, maxw => 0, trans => [ qw(no_ctrl_char) ] }, - cnt => { src => 'id', minw => 0, maxw => 0 }, - }, - visible => [ qw(cxn cmd cnt mysql_thread_id user hostname db time info)], - filters => [ qw(hide_self hide_inactive hide_slave_io) ], - sort_cols => '-time cxn hostname mysql_thread_id', - sort_dir => '1', - innodb => '', - hide_caption => 1, - colors => [ - { col => 'state', op => 'eq', arg => 'Locked', color => 'black on_red' }, - { col => 'cmd', op => 'eq', arg => 'Sleep', color => 'white' }, - { col => 'user', op => 'eq', arg => 'system user', color => 'white' }, - { col => 'cmd', op => 'eq', arg => 'Connect', color => 'white' }, - { col => 'cmd', op => 'eq', arg => 'Binlog Dump', color => 'white' }, - { col => 'time', op => '>', arg => 600, color => 'red' }, - { col => 'time', op => '>', arg => 120, color => 'yellow' }, - { col => 'time', op => '>', arg => 60, color => 'green' }, - { col => 'time', op => '>', arg => 30, color => 'cyan' }, - ], - group_by => [qw(cxn cmd)], - aggregate => 0, - }, - - # TODO: some more columns: - # 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' - # 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' - # 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' - - q_header => { - capt => 'Q-mode Header', - cust => {}, - cols => { - cxn => { src => 'cxn' }, - questions => { src => 'Questions' }, - qps => { src => 'Questions/Uptime_hires', dec => 1, trans => [qw(shorten)] }, - load => { src => $exprs{ServerLoad}, dec => 1, trans => [qw(shorten)] }, - slow => { src => 'Slow_queries', dec => 1, trans => [qw(shorten)] }, - q_cache_hit => { src => $exprs{QcacheHitRatio}, dec => 1, trans => [qw(percent)] }, - key_buffer_hit => { src => '1-(Key_reads/(Key_read_requests||1))', dec => 1, trans => [qw(percent)] }, - bps_in => { src => 'Bytes_received/Uptime_hires', dec => 1, trans => [qw(shorten)] }, - bps_out => { src => 'Bytes_sent/Uptime_hires', dec => 1, trans => [qw(shorten)] }, - when => { src => 'when' }, - }, - visible => [ qw(cxn when load qps slow q_cache_hit key_buffer_hit bps_in bps_out)], - filters => [], - sort_cols => 'when cxn', - sort_dir => '1', - innodb => '', - hide_caption => 1, - group_by => [], - aggregate => 0, - }, - row_operations => { - capt => 'InnoDB Row Operations', - cust => {}, - cols => { - cxn => { src => 'cxn' }, - num_inserts => { src => 'IB_ro_num_rows_ins' }, - num_updates => { src => 'IB_ro_num_rows_upd' }, - num_reads => { src => 'IB_ro_num_rows_read' }, - num_deletes => { src => 'IB_ro_num_rows_del' }, - num_inserts_sec => { src => 'IB_ro_ins_sec' }, - num_updates_sec => { src => 'IB_ro_upd_sec' }, - num_reads_sec => { src => 'IB_ro_read_sec' }, - num_deletes_sec => { src => 'IB_ro_del_sec' }, - }, - visible => [ qw(cxn num_inserts num_updates num_reads num_deletes num_inserts_sec - num_updates_sec num_reads_sec num_deletes_sec)], - filters => [], - sort_cols => 'cxn', - sort_dir => '1', - innodb => 'ro', - group_by => [], - aggregate => 0, - }, - row_operation_misc => { - capt => 'Row Operation Misc', - cust => {}, - cols => { - cxn => { src => 'cxn' }, - queries_in_queue => { src => 'IB_ro_queries_in_queue' }, - queries_inside => { src => 'IB_ro_queries_inside' }, - read_views_open => { src => 'IB_ro_read_views_open' }, - main_thread_id => { src => 'IB_ro_main_thread_id' }, - main_thread_proc_no => { src => 'IB_ro_main_thread_proc_no' }, - main_thread_state => { src => 'IB_ro_main_thread_state' }, - num_res_ext => { src => 'IB_ro_n_reserved_extents' }, - }, - visible => [ qw(cxn queries_in_queue queries_inside read_views_open main_thread_state)], - filters => [], - sort_cols => 'cxn', - sort_dir => '1', - innodb => 'ro', - group_by => [], - aggregate => 0, - }, - semaphores => { - capt => 'InnoDB Semaphores', - cust => {}, - cols => { - cxn => { src => 'cxn' }, - mutex_os_waits => { src => 'IB_sm_mutex_os_waits' }, - mutex_spin_rounds => { src => 'IB_sm_mutex_spin_rounds' }, - mutex_spin_waits => { src => 'IB_sm_mutex_spin_waits' }, - reservation_count => { src => 'IB_sm_reservation_count' }, - rw_excl_os_waits => { src => 'IB_sm_rw_excl_os_waits' }, - rw_excl_spins => { src => 'IB_sm_rw_excl_spins' }, - rw_shared_os_waits => { src => 'IB_sm_rw_shared_os_waits' }, - rw_shared_spins => { src => 'IB_sm_rw_shared_spins' }, - signal_count => { src => 'IB_sm_signal_count' }, - wait_array_size => { src => 'IB_sm_wait_array_size' }, - }, - visible => [ qw(cxn mutex_os_waits mutex_spin_waits mutex_spin_rounds - rw_excl_os_waits rw_excl_spins rw_shared_os_waits rw_shared_spins - signal_count reservation_count )], - filters => [], - sort_cols => 'cxn', - sort_dir => '1', - innodb => 'sm', - group_by => [], - aggregate => 0, - }, - slave_io_status => { - capt => 'Slave I/O Status', - cust => {}, - cols => { - cxn => { src => 'cxn' }, - connect_retry => { src => 'connect_retry' }, - master_host => { src => 'master_host', hdr => 'Master'}, - master_log_file => { src => 'master_log_file', hdr => 'File' }, - master_port => { src => 'master_port' }, - master_ssl_allowed => { src => 'master_ssl_allowed' }, - master_ssl_ca_file => { src => 'master_ssl_ca_file' }, - master_ssl_ca_path => { src => 'master_ssl_ca_path' }, - master_ssl_cert => { src => 'master_ssl_cert' }, - master_ssl_cipher => { src => 'master_ssl_cipher' }, - master_ssl_key => { src => 'master_ssl_key' }, - master_user => { src => 'master_user' }, - read_master_log_pos => { src => 'read_master_log_pos', hdr => 'Pos' }, - relay_log_size => { src => 'relay_log_space', trans => [qw(shorten)] }, - slave_io_running => { src => 'slave_io_running', hdr => 'On?' }, - slave_io_state => { src => 'slave_io_state', hdr => 'State' }, - }, - visible => [ qw(cxn master_host slave_io_running master_log_file relay_log_size read_master_log_pos slave_io_state)], - filters => [ qw( cxn_is_slave ) ], - sort_cols => 'slave_io_running cxn', - colors => [ - { col => 'slave_io_running', op => 'ne', arg => 'Yes', color => 'black on_red' }, - ], - sort_dir => '1', - innodb => '', - group_by => [], - aggregate => 0, - }, - slave_sql_status => { - capt => 'Slave SQL Status', - cust => {}, - cols => { - cxn => { src => 'cxn' }, - exec_master_log_pos => { src => 'exec_master_log_pos', hdr => 'Master Pos' }, - last_errno => { src => 'last_errno' }, - last_error => { src => 'last_error' }, - master_host => { src => 'master_host', hdr => 'Master' }, - relay_log_file => { src => 'relay_log_file' }, - relay_log_pos => { src => 'relay_log_pos' }, - relay_log_size => { src => 'relay_log_space', trans => [qw(shorten)] }, - relay_master_log_file => { src => 'relay_master_log_file', hdr => 'Master File' }, - replicate_do_db => { src => 'replicate_do_db' }, - replicate_do_table => { src => 'replicate_do_table' }, - replicate_ignore_db => { src => 'replicate_ignore_db' }, - replicate_ignore_table => { src => 'replicate_ignore_table' }, - replicate_wild_do_table => { src => 'replicate_wild_do_table' }, - replicate_wild_ignore_table => { src => 'replicate_wild_ignore_table' }, - skip_counter => { src => 'skip_counter' }, - slave_sql_running => { src => 'slave_sql_running', hdr => 'On?' }, - until_condition => { src => 'until_condition' }, - until_log_file => { src => 'until_log_file' }, - until_log_pos => { src => 'until_log_pos' }, - time_behind_master => { src => 'seconds_behind_master', trans => [ qw(secs_to_time) ] }, - 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)] }, - slave_catchup_rate => { src => $exprs{SlaveCatchupRate}, trans => [ qw(set_precision) ] }, - slave_open_temp_tables => { src => 'Slave_open_temp_tables' }, - }, - visible => [ qw(cxn master_host slave_sql_running time_behind_master slave_catchup_rate slave_open_temp_tables relay_log_pos last_error)], - filters => [ qw( cxn_is_slave ) ], - sort_cols => 'slave_sql_running cxn', - sort_dir => '1', - innodb => '', - colors => [ - { col => 'slave_sql_running', op => 'ne', arg => 'Yes', color => 'black on_red' }, - { col => 'time_behind_master', op => '>', arg => 600, color => 'red' }, - { col => 'time_behind_master', op => '>', arg => 60, color => 'yellow' }, - { col => 'time_behind_master', op => '==', arg => 0, color => 'white' }, - ], - group_by => [], - aggregate => 0, - }, - t_header => { - capt => 'T-Mode Header', - cust => {}, - cols => { - cxn => { src => 'cxn' }, - dirty_bufs => { src => $exprs{DirtyBufs}, trans => [qw(percent)] }, - history_list_len => { src => 'IB_tx_history_list_len' }, - lock_structs => { src => 'IB_tx_num_lock_structs' }, - num_txns => { src => $exprs{NumTxns} }, - max_txn => { src => $exprs{MaxTxnTime}, trans => [qw(secs_to_time)] }, - undo_for => { src => 'IB_tx_purge_undo_for' }, - used_bufs => { src => $exprs{BufPoolFill}, trans => [qw(percent)]}, - versions => { src => $exprs{OldVersions} }, - }, - visible => [ qw(cxn history_list_len versions undo_for dirty_bufs used_bufs num_txns max_txn lock_structs)], - filters => [ ], - sort_cols => 'cxn', - sort_dir => '1', - innodb => '', - colors => [], - hide_caption => 1, - group_by => [], - aggregate => 0, - }, - var_status => { - capt => 'Variables & Status', - cust => {}, - cols => {}, # Generated from current varset - visible => [], # Generated from current varset - filters => [], - sort_cols => '', - sort_dir => 1, - innodb => '', - temp => 1, # Do not persist to config file. - hide_caption => 1, - pivot => 0, - group_by => [], - aggregate => 0, - }, - wait_array => { - capt => 'InnoDB Wait Array', - cust => {}, - cols => { - cxn => { src => 'cxn' }, - thread => { src => 'thread' }, - waited_at_filename => { src => 'waited_at_filename' }, - waited_at_line => { src => 'waited_at_line' }, - 'time' => { src => 'waited_secs', trans => [ qw(secs_to_time) ] }, - request_type => { src => 'request_type' }, - lock_mem_addr => { src => 'lock_mem_addr' }, - lock_cfile_name => { src => 'lock_cfile_name' }, - lock_cline => { src => 'lock_cline' }, - writer_thread => { src => 'writer_thread' }, - writer_lock_mode => { src => 'writer_lock_mode' }, - num_readers => { src => 'num_readers' }, - lock_var => { src => 'lock_var' }, - waiters_flag => { src => 'waiters_flag' }, - last_s_file_name => { src => 'last_s_file_name' }, - last_s_line => { src => 'last_s_line' }, - last_x_file_name => { src => 'last_x_file_name' }, - last_x_line => { src => 'last_x_line' }, - cell_waiting => { src => 'cell_waiting' }, - cell_event_set => { src => 'cell_event_set' }, - }, - visible => [ qw(cxn thread time waited_at_filename waited_at_line request_type num_readers lock_var waiters_flag cell_waiting cell_event_set)], - filters => [], - sort_cols => 'cxn -time', - sort_dir => '1', - innodb => 'sm', - group_by => [], - aggregate => 0, - }, -); - -# Initialize %tbl_meta from %columns and do some checks. -foreach my $table_name ( keys %tbl_meta ) { - my $table = $tbl_meta{$table_name}; - my $cols = $table->{cols}; - - foreach my $col_name ( keys %$cols ) { - my $col_def = $table->{cols}->{$col_name}; - die "I can't find a column named '$col_name' for '$table_name'" unless $columns{$col_name}; - $columns{$col_name}->{referenced} = 1; - - foreach my $prop ( keys %col_props ) { - # Each column gets non-existing values set from %columns or defaults from %col_props. - if ( !$col_def->{$prop} ) { - $col_def->{$prop} - = defined($columns{$col_name}->{$prop}) - ? $columns{$col_name}->{$prop} - : $col_props{$prop}; - } - } - - # Ensure transformations and aggregate functions are valid - die "Unknown aggregate function '$col_def->{agg}' " - . "for column '$col_name' in table '$table_name'" - unless exists $agg_funcs{$col_def->{agg}}; - foreach my $trans ( @{$col_def->{trans}} ) { - die "Unknown transformation '$trans' " - . "for column '$col_name' in table '$table_name'" - unless exists $trans_funcs{$trans}; - } - } - - # Ensure each column in visible and group_by exists in cols - foreach my $place ( qw(visible group_by) ) { - foreach my $col_name ( @{$table->{$place}} ) { - if ( !exists $cols->{$col_name} ) { - die "Column '$col_name' is listed in '$place' for '$table_name', but doesn't exist"; - } - } - } - - # Compile sort and color subroutines - $table->{sort_func} = make_sort_func($table); - $table->{color_func} = make_color_func($table); -} - -# This is for code cleanup: -{ - my @unused_cols = grep { !$columns{$_}->{referenced} } sort keys %columns; - if ( @unused_cols ) { - die "The following columns are not used: " - . join(' ', @unused_cols); - } -} - -# ########################################################################### -# Operating modes {{{3 -# ########################################################################### -my %modes = ( - B => { - hdr => 'InnoDB Buffers', - cust => {}, - note => 'Shows buffer info from InnoDB', - action_for => { - i => { - action => sub { toggle_config('status_inc') }, - label => 'Toggle incremental status display', - }, - }, - display_sub => \&display_B, - connections => [], - server_group => '', - one_connection => 0, - tables => [qw(buffer_pool page_statistics insert_buffers adaptive_hash_index)], - visible_tables => [qw(buffer_pool page_statistics insert_buffers adaptive_hash_index)], - }, - C => { - hdr => 'Command Summary', - cust => {}, - note => 'Shows relative magnitude of variables', - action_for => { - s => { - action => sub { get_config_interactive('cmd_filter') }, - label => 'Choose variable prefix', - }, - }, - display_sub => \&display_C, - connections => [], - server_group => '', - one_connection => 0, - tables => [qw(cmd_summary)], - visible_tables => [qw(cmd_summary)], - }, - D => { - hdr => 'InnoDB Deadlocks', - cust => {}, - note => 'View InnoDB deadlock information', - action_for => { - c => { - action => sub { edit_table('deadlock_transactions') }, - label => 'Choose visible columns', - }, - w => { - action => \&create_deadlock, - label => 'Wipe deadlock status info by creating a deadlock', - }, - }, - display_sub => \&display_D, - connections => [], - server_group => '', - one_connection => 0, - tables => [qw(deadlock_transactions deadlock_locks)], - visible_tables => [qw(deadlock_transactions deadlock_locks)], - }, - F => { - hdr => 'InnoDB FK Err', - cust => {}, - note => 'View the latest InnoDB foreign key error', - action_for => {}, - display_sub => \&display_F, - connections => [], - server_group => '', - one_connection => 1, - tables => [qw(fk_error)], - visible_tables => [qw(fk_error)], - }, - I => { - hdr => 'InnoDB I/O Info', - cust => {}, - note => 'Shows I/O info (i/o, log...) from InnoDB', - action_for => { - i => { - action => sub { toggle_config('status_inc') }, - label => 'Toggle incremental status display', - }, - }, - display_sub => \&display_I, - connections => [], - server_group => '', - one_connection => 0, - tables => [qw(io_threads pending_io file_io_misc log_statistics)], - visible_tables => [qw(io_threads pending_io file_io_misc log_statistics)], - }, - L => { - hdr => 'Locks', - cust => {}, - note => 'Shows transaction locks', - action_for => { - a => { - action => sub { send_cmd_to_servers('CREATE TABLE IF NOT EXISTS test.innodb_lock_monitor(a int) ENGINE=InnoDB', 0, '', []); }, - label => 'Start the InnoDB Lock Monitor', - }, - o => { - action => sub { send_cmd_to_servers('DROP TABLE IF EXISTS test.innodb_lock_monitor', 0, '', []); }, - label => 'Stop the InnoDB Lock Monitor', - }, - }, - display_sub => \&display_L, - connections => [], - server_group => '', - one_connection => 0, - tables => [qw(innodb_locks)], - visible_tables => [qw(innodb_locks)], - }, - M => { - hdr => 'Replication Status', - cust => {}, - note => 'Shows replication (master and slave) status', - action_for => { - a => { - action => sub { send_cmd_to_servers('START SLAVE', 0, 'START SLAVE SQL_THREAD UNTIL MASTER_LOG_FILE = ?, MASTER_LOG_POS = ?', []); }, - label => 'Start slave(s)', - }, - i => { - action => sub { toggle_config('status_inc') }, - label => 'Toggle incremental status display', - }, - o => { - action => sub { send_cmd_to_servers('STOP SLAVE', 0, '', []); }, - label => 'Stop slave(s)', - }, - b => { - action => sub { purge_master_logs() }, - label => 'Purge unused master logs', - }, - }, - display_sub => \&display_M, - connections => [], - server_group => '', - one_connection => 0, - tables => [qw(slave_sql_status slave_io_status master_status)], - visible_tables => [qw(slave_sql_status slave_io_status master_status)], - }, - O => { - hdr => 'Open Tables', - cust => {}, - note => 'Shows open tables in MySQL', - action_for => { - r => { - action => sub { reverse_sort('open_tables'); }, - label => 'Reverse sort order', - }, - s => { - action => sub { choose_sort_cols('open_tables'); }, - label => "Choose sort column", - }, - }, - display_sub => \&display_O, - connections => [], - server_group => '', - one_connection => 0, - tables => [qw(open_tables)], - visible_tables => [qw(open_tables)], - }, - Q => { - hdr => 'Query List', - cust => {}, - note => 'Shows queries from SHOW FULL PROCESSLIST', - action_for => { - a => { - action => sub { toggle_filter('processlist', 'hide_self') }, - label => 'Toggle the innotop process', - }, - c => { - action => sub { edit_table('processlist') }, - label => 'Choose visible columns', - }, - e => { - action => sub { analyze_query('e'); }, - label => "Explain a thread's query", - }, - f => { - action => sub { analyze_query('f'); }, - label => "Show a thread's full query", - }, - h => { - action => sub { toggle_visible_table('Q', 'q_header') }, - label => 'Toggle the header on and off', - }, - i => { - action => sub { toggle_filter('processlist', 'hide_inactive') }, - label => 'Toggle idle processes', - }, - k => { - action => sub { kill_query('CONNECTION') }, - label => "Kill a query's connection", - }, - r => { - action => sub { reverse_sort('processlist'); }, - label => 'Reverse sort order', - }, - s => { - action => sub { choose_sort_cols('processlist'); }, - label => "Change the display's sort column", - }, - x => { - action => sub { kill_query('QUERY') }, - label => "Kill a query", - }, - }, - display_sub => \&display_Q, - connections => [], - server_group => '', - one_connection => 0, - tables => [qw(q_header processlist)], - visible_tables => [qw(q_header processlist)], - }, - R => { - hdr => 'InnoDB Row Ops', - cust => {}, - note => 'Shows InnoDB row operation and semaphore info', - action_for => { - i => { - action => sub { toggle_config('status_inc') }, - label => 'Toggle incremental status display', - }, - }, - display_sub => \&display_R, - connections => [], - server_group => '', - one_connection => 0, - tables => [qw(row_operations row_operation_misc semaphores wait_array)], - visible_tables => [qw(row_operations row_operation_misc semaphores wait_array)], - }, - S => { - hdr => 'Variables & Status', - cust => {}, - note => 'Shows query load statistics a la vmstat', - action_for => { - '>' => { - action => sub { switch_var_set('S_set', 1) }, - label => 'Switch to next variable set', - }, - '<' => { - action => sub { switch_var_set('S_set', -1) }, - label => 'Switch to prev variable set', - }, - c => { - action => sub { - choose_var_set('S_set'); - start_S_mode(); - }, - label => "Choose which set to display", - }, - e => { - action => \&edit_current_var_set, - label => 'Edit the current set of variables', - }, - i => { - action => sub { $clear_screen_sub->(); toggle_config('status_inc') }, - label => 'Toggle incremental status display', - }, - '-' => { - action => sub { set_display_precision(-1) }, - label => 'Decrease fractional display precision', - }, - '+' => { - action => sub { set_display_precision(1) }, - label => 'Increase fractional display precision', - }, - g => { - action => sub { set_s_mode('g') }, - label => 'Switch to graph (tload) view', - }, - s => { - action => sub { set_s_mode('s') }, - label => 'Switch to standard (vmstat) view', - }, - v => { - action => sub { set_s_mode('v') }, - label => 'Switch to pivoted view', - }, - }, - display_sub => \&display_S, - no_clear_screen => 1, - connections => [], - server_group => '', - one_connection => 0, - tables => [qw(var_status)], - visible_tables => [qw(var_status)], - }, - T => { - hdr => 'InnoDB Txns', - cust => {}, - note => 'Shows InnoDB transactions in top-like format', - action_for => { - a => { - action => sub { toggle_filter('innodb_transactions', 'hide_self') }, - label => 'Toggle the innotop process', - }, - c => { - action => sub { edit_table('innodb_transactions') }, - label => 'Choose visible columns', - }, - e => { - action => sub { analyze_query('e'); }, - label => "Explain a thread's query", - }, - f => { - action => sub { analyze_query('f'); }, - label => "Show a thread's full query", - }, - h => { - action => sub { toggle_visible_table('T', 't_header') }, - label => 'Toggle the header on and off', - }, - i => { - action => sub { toggle_filter('innodb_transactions', 'hide_inactive') }, - label => 'Toggle inactive transactions', - }, - k => { - action => sub { kill_query('CONNECTION') }, - label => "Kill a transaction's connection", - }, - r => { - action => sub { reverse_sort('innodb_transactions'); }, - label => 'Reverse sort order', - }, - s => { - action => sub { choose_sort_cols('innodb_transactions'); }, - label => "Change the display's sort column", - }, - x => { - action => sub { kill_query('QUERY') }, - label => "Kill a query", - }, - }, - display_sub => \&display_T, - connections => [], - server_group => '', - one_connection => 0, - tables => [qw(t_header innodb_transactions)], - visible_tables => [qw(t_header innodb_transactions)], - }, -); - -# ########################################################################### -# Global key mappings {{{3 -# Keyed on a single character, which is read from the keyboard. Uppercase -# letters switch modes. Lowercase letters access commands when in a mode. -# These can be overridden by action_for in %modes. -# ########################################################################### -my %action_for = ( - '$' => { - action => \&edit_configuration, - label => 'Edit configuration settings', - }, - '?' => { - action => \&display_help, - label => 'Show help', - }, - '!' => { - action => \&display_license, - label => 'Show license and warranty', - }, - '^' => { - action => \&edit_table, - label => "Edit the displayed table(s)", - }, - '#' => { - action => \&choose_server_groups, - label => 'Select/create server groups', - }, - '@' => { - action => \&choose_servers, - label => 'Select/create server connections', - }, - '/' => { - action => \&add_quick_filter, - label => 'Quickly filter what you see', - }, - '\\' => { - action => \&clear_quick_filters, - label => 'Clear quick-filters', - }, - '%' => { - action => \&choose_filters, - label => 'Choose and edit table filters', - }, - "\t" => { - action => \&next_server_group, - label => 'Switch to the next server group', - key => 'TAB', - }, - '=' => { - action => \&toggle_aggregate, - label => 'Toggle aggregation', - }, - # TODO: can these be auto-generated from %modes? - B => { - action => sub { switch_mode('B') }, - label => '', - }, - C => { - action => sub { switch_mode('C') }, - label => '', - }, - D => { - action => sub { switch_mode('D') }, - label => '', - }, - F => { - action => sub { switch_mode('F') }, - label => '', - }, - I => { - action => sub { switch_mode('I') }, - label => '', - }, - L => { - action => sub { switch_mode('L') }, - label => '', - }, - M => { - action => sub { switch_mode('M') }, - label => '', - }, - O => { - action => sub { switch_mode('O') }, - label => '', - }, - Q => { - action => sub { switch_mode('Q') }, - label => '', - }, - R => { - action => sub { switch_mode('R') }, - label => '', - }, - S => { - action => \&start_S_mode, - label => '', - }, - T => { - action => sub { switch_mode('T') }, - label => '', - }, - d => { - action => sub { get_config_interactive('interval') }, - label => 'Change refresh interval', - }, - n => { action => \&next_server, label => 'Switch to the next connection' }, - p => { action => \&pause, label => 'Pause innotop', }, - q => { action => \&finish, label => 'Quit innotop', }, -); - -# ########################################################################### -# Sleep times after certain statements {{{3 -# ########################################################################### -my %stmt_sleep_time_for = (); - -# ########################################################################### -# Config editor key mappings {{{3 -# ########################################################################### -my %cfg_editor_action = ( - c => { - note => 'Edit columns, etc in the displayed table(s)', - func => \&edit_table, - }, - g => { - note => 'Edit general configuration', - func => \&edit_configuration_variables, - }, - k => { - note => 'Edit row-coloring rules', - func => \&edit_color_rules, - }, - p => { - note => 'Manage plugins', - func => \&edit_plugins, - }, - s => { - note => 'Edit server groups', - func => \&edit_server_groups, - }, - S => { - note => 'Edit SQL statement sleep delays', - func => \&edit_stmt_sleep_times, - }, - t => { - note => 'Choose which table(s) to display in this mode', - func => \&choose_mode_tables, - }, -); - -# ########################################################################### -# Color editor key mappings {{{3 -# ########################################################################### -my %color_editor_action = ( - n => { - note => 'Create a new color rule', - func => sub { - my ( $tbl, $idx ) = @_; - my $meta = $tbl_meta{$tbl}; - - $clear_screen_sub->(); - my $col; - do { - $col = prompt_list( - 'Choose the target column for the rule', - '', - sub { return keys %{$meta->{cols}} }, - { map { $_ => $meta->{cols}->{$_}->{label} } keys %{$meta->{cols}} }); - } while ( !$col ); - ( $col ) = grep { $_ } split(/\W+/, $col); - return $idx unless $col && exists $meta->{cols}->{$col}; - - $clear_screen_sub->(); - my $op; - do { - $op = prompt_list( - 'Choose the comparison operator for the rule', - '', - sub { return keys %comp_ops }, - { map { $_ => $comp_ops{$_} } keys %comp_ops } ); - } until ( $op ); - $op =~ s/\s+//g; - return $idx unless $op && exists $comp_ops{$op}; - - my $arg; - do { - $arg = prompt('Specify an argument for the comparison'); - } until defined $arg; - - my $color; - do { - $color = prompt_list( - 'Choose the color(s) the row should be when the rule matches', - '', - sub { return keys %ansicolors }, - { map { $_ => $_ } keys %ansicolors } ); - } until defined $color; - $color = join(' ', unique(grep { exists $ansicolors{$_} } split(/\W+/, $color))); - return $idx unless $color; - - push @{$tbl_meta{$tbl}->{colors}}, { - col => $col, - op => $op, - arg => $arg, - color => $color - }; - $tbl_meta{$tbl}->{cust}->{colors} = 1; - - return $idx; - }, - }, - d => { - note => 'Remove the selected rule', - func => sub { - my ( $tbl, $idx ) = @_; - my @rules = @{ $tbl_meta{$tbl}->{colors} }; - return 0 unless @rules > 0 && $idx < @rules && $idx >= 0; - splice(@{$tbl_meta{$tbl}->{colors}}, $idx, 1); - $tbl_meta{$tbl}->{cust}->{colors} = 1; - return $idx == @rules ? $#rules : $idx; - }, - }, - j => { - note => 'Move highlight down one', - func => sub { - my ( $tbl, $idx ) = @_; - my $num_rules = scalar @{$tbl_meta{$tbl}->{colors}}; - return ($idx + 1) % $num_rules; - }, - }, - k => { - note => 'Move highlight up one', - func => sub { - my ( $tbl, $idx ) = @_; - my $num_rules = scalar @{$tbl_meta{$tbl}->{colors}}; - return ($idx - 1) % $num_rules; - }, - }, - '+' => { - note => 'Move selected rule up one', - func => sub { - my ( $tbl, $idx ) = @_; - my $meta = $tbl_meta{$tbl}; - my $dest = $idx == 0 ? scalar(@{$meta->{colors}} - 1) : $idx - 1; - my $temp = $meta->{colors}->[$idx]; - $meta->{colors}->[$idx] = $meta->{colors}->[$dest]; - $meta->{colors}->[$dest] = $temp; - $meta->{cust}->{colors} = 1; - return $dest; - }, - }, - '-' => { - note => 'Move selected rule down one', - func => sub { - my ( $tbl, $idx ) = @_; - my $meta = $tbl_meta{$tbl}; - my $dest = $idx == scalar(@{$meta->{colors}} - 1) ? 0 : $idx + 1; - my $temp = $meta->{colors}->[$idx]; - $meta->{colors}->[$idx] = $meta->{colors}->[$dest]; - $meta->{colors}->[$dest] = $temp; - $meta->{cust}->{colors} = 1; - return $dest; - }, - }, -); - -# ########################################################################### -# Plugin editor key mappings {{{3 -# ########################################################################### -my %plugin_editor_action = ( - '*' => { - note => 'Toggle selected plugin active/inactive', - func => sub { - my ( $plugins, $idx ) = @_; - my $plugin = $plugins->[$idx]; - $plugin->{active} = $plugin->{active} ? 0 : 1; - return $idx; - }, - }, - j => { - note => 'Move highlight down one', - func => sub { - my ( $plugins, $idx ) = @_; - return ($idx + 1) % scalar(@$plugins); - }, - }, - k => { - note => 'Move highlight up one', - func => sub { - my ( $plugins, $idx ) = @_; - return $idx == 0 ? @$plugins - 1 : $idx - 1; - }, - }, -); - -# ########################################################################### -# Table editor key mappings {{{3 -# ########################################################################### -my %tbl_editor_action = ( - a => { - note => 'Add a column to the table', - func => sub { - my ( $tbl, $col ) = @_; - my @visible_cols = @{ $tbl_meta{$tbl}->{visible} }; - my %all_cols = %{ $tbl_meta{$tbl}->{cols} }; - delete @all_cols{@visible_cols}; - my $choice = prompt_list( - 'Choose a column', - '', - sub { return keys %all_cols; }, - { map { $_ => $all_cols{$_}->{label} || $all_cols{$_}->{hdr} } keys %all_cols }); - if ( $all_cols{$choice} ) { - push @{$tbl_meta{$tbl}->{visible}}, $choice; - $tbl_meta{$tbl}->{cust}->{visible} = 1; - return $choice; - } - return $col; - }, - }, - n => { - note => 'Create a new column and add it to the table', - func => sub { - my ( $tbl, $col ) = @_; - - $clear_screen_sub->(); - print word_wrap("Choose a name for the column. This name is not displayed, and is used only " - . "for internal reference. It can contain only lowercase letters, numbers, " - . "and underscores."); - print "\n\n"; - do { - $col = prompt("Enter column name"); - $col = '' if $col =~ m/[^a-z0-9_]/; - } while ( !$col ); - - $clear_screen_sub->(); - my $hdr; - do { - $hdr = prompt("Enter column header"); - } while ( !$hdr ); - - $clear_screen_sub->(); - print "Choose a source for the column's data\n\n"; - my ( $src, $sub, $err ); - do { - if ( $err ) { - print "Error: $err\n\n"; - } - $src = prompt("Enter column source"); - if ( $src ) { - ( $sub, $err ) = compile_expr($src); - } - } until ( !$err); - - # TODO: this duplicates %col_props. - $tbl_meta{$tbl}->{cols}->{$col} = { - hdr => $hdr, - src => $src, - just => '-', - num => 0, - label => 'User-defined', - user => 1, - tbl => $tbl, - minw => 0, - maxw => 0, - trans => [], - func => $sub, - dec => 0, - agg => 0, - aggonly => 0, - }; - - $tbl_meta{$tbl}->{visible} = [ unique(@{$tbl_meta{$tbl}->{visible}}, $col) ]; - $tbl_meta{$tbl}->{cust}->{visible} = 1; - return $col; - }, - }, - d => { - note => 'Remove selected column', - func => sub { - my ( $tbl, $col ) = @_; - my @visible_cols = @{ $tbl_meta{$tbl}->{visible} }; - my $idx = 0; - return $col unless @visible_cols > 1; - while ( $visible_cols[$idx] ne $col ) { - $idx++; - } - $tbl_meta{$tbl}->{visible} = [ grep { $_ ne $col } @visible_cols ]; - $tbl_meta{$tbl}->{cust}->{visible} = 1; - return $idx == $#visible_cols ? $visible_cols[$idx - 1] : $visible_cols[$idx + 1]; - }, - }, - e => { - note => 'Edit selected column', - func => sub { - # TODO: make this editor hotkey-driven and give readline support. - my ( $tbl, $col ) = @_; - $clear_screen_sub->(); - my $meta = $tbl_meta{$tbl}->{cols}->{$col}; - my @prop = qw(hdr label src just num minw maxw trans agg); # TODO redundant - - my $answer; - do { - # Do what the user asked... - if ( $answer && grep { $_ eq $answer } @prop ) { - # Some properties are arrays, others scalars. - my $ini = ref $col_props{$answer} ? join(' ', @{$meta->{$answer}}) : $meta->{$answer}; - my $val = prompt("New value for $answer", undef, $ini); - $val = [ split(' ', $val) ] if ref($col_props{$answer}); - if ( $answer eq 'trans' ) { - $val = [ unique(grep{ exists $trans_funcs{$_} } @$val) ]; - } - @{$meta}{$answer, 'user', 'tbl' } = ( $val, 1, $tbl ); - } - - my @display_lines = ( - '', - "You are editing column $tbl.$col.\n", - ); - - push @display_lines, create_table2( - \@prop, - { map { $_ => $_ } @prop }, - { map { $_ => ref $meta->{$_} eq 'ARRAY' ? join(' ', @{$meta->{$_}}) - : ref $meta->{$_} ? '[expression code]' - : $meta->{$_} - } @prop - }, - { sep => ' ' }); - draw_screen(\@display_lines, { raw => 1 }); - print "\n\n"; # One to add space, one to clear readline artifacts - $answer = prompt('Edit what? (q to quit)'); - } while ( $answer ne 'q' ); - - return $col; - }, - }, - j => { - note => 'Move highlight down one', - func => sub { - my ( $tbl, $col ) = @_; - my @visible_cols = @{ $tbl_meta{$tbl}->{visible} }; - my $idx = 0; - while ( $visible_cols[$idx] ne $col ) { - $idx++; - } - return $visible_cols[ ($idx + 1) % @visible_cols ]; - }, - }, - k => { - note => 'Move highlight up one', - func => sub { - my ( $tbl, $col ) = @_; - my @visible_cols = @{ $tbl_meta{$tbl}->{visible} }; - my $idx = 0; - while ( $visible_cols[$idx] ne $col ) { - $idx++; - } - return $visible_cols[ $idx - 1 ]; - }, - }, - '+' => { - note => 'Move selected column up one', - func => sub { - my ( $tbl, $col ) = @_; - my $meta = $tbl_meta{$tbl}; - my @visible_cols = @{$meta->{visible}}; - my $idx = 0; - while ( $visible_cols[$idx] ne $col ) { - $idx++; - } - if ( $idx ) { - $visible_cols[$idx] = $visible_cols[$idx - 1]; - $visible_cols[$idx - 1] = $col; - $meta->{visible} = \@visible_cols; - } - else { - shift @{$meta->{visible}}; - push @{$meta->{visible}}, $col; - } - $meta->{cust}->{visible} = 1; - return $col; - }, - }, - '-' => { - note => 'Move selected column down one', - func => sub { - my ( $tbl, $col ) = @_; - my $meta = $tbl_meta{$tbl}; - my @visible_cols = @{$meta->{visible}}; - my $idx = 0; - while ( $visible_cols[$idx] ne $col ) { - $idx++; - } - if ( $idx == $#visible_cols ) { - unshift @{$meta->{visible}}, $col; - pop @{$meta->{visible}}; - } - else { - $visible_cols[$idx] = $visible_cols[$idx + 1]; - $visible_cols[$idx + 1] = $col; - $meta->{visible} = \@visible_cols; - } - $meta->{cust}->{visible} = 1; - return $col; - }, - }, - f => { - note => 'Choose filters', - func => sub { - my ( $tbl, $col ) = @_; - choose_filters($tbl); - return $col; - }, - }, - o => { - note => 'Edit color rules', - func => sub { - my ( $tbl, $col ) = @_; - edit_color_rules($tbl); - return $col; - }, - }, - s => { - note => 'Choose sort columns', - func => sub { - my ( $tbl, $col ) = @_; - choose_sort_cols($tbl); - return $col; - }, - }, - g => { - note => 'Choose group-by (aggregate) columns', - func => sub { - my ( $tbl, $col ) = @_; - choose_group_cols($tbl); - return $col; - }, - }, -); - -# ########################################################################### -# Global variables and environment {{{2 -# ########################################################################### - -my @this_term_size; # w_chars, h_chars, w_pix, h_pix -my @last_term_size; # w_chars, h_chars, w_pix, h_pix -my $char; -my $windows = $OSNAME =~ m/MSWin/; -my $have_color = 0; -my $MAX_ULONG = 4294967295; # 2^32-1 -my $num_regex = qr/^[+-]?(?=\d|\.)\d*(?:\.\d+)?(?:E[+-]?\d+|)$/i; -my $int_regex = qr/^\d+$/; -my $bool_regex = qr/^[01]$/; -my $term = undef; -my $file = undef; # File to watch for InnoDB monitor output -my $file_mtime = undef; # Status of watched file -my $file_data = undef; # Last chunk of text read from file -my $innodb_parser = InnoDBParser->new; - -my $nonfatal_errs = join('|', - 'Access denied for user', - 'Unknown MySQL server host', - 'Unknown database', - 'Can\'t connect to local MySQL server through socket', - 'Can\'t connect to MySQL server on', - 'MySQL server has gone away', - 'Cannot call SHOW INNODB STATUS', - 'Access denied', - 'AutoCommit', -); - -if ( !$opts{n} ) { - require Term::ReadLine; - $term = Term::ReadLine->new('innotop'); -} - -# Stores status, variables, innodb status, master/slave status etc. -# Keyed on connection name. Each entry is a hashref of current and past data sets, -# keyed on clock tick. -my %vars; -my %info_gotten = (); # Which things have been retrieved for the current clock tick. - -# Stores info on currently displayed queries: cxn, connection ID, query text. -my @current_queries; - -my $lines_printed = 0; -my $clock = 0; # Incremented with every wake-sleep cycle -my $clearing_deadlocks = 0; - -# Find the home directory; it's different on different OSes. -my $homepath = $ENV{HOME} || $ENV{HOMEPATH} || $ENV{USERPROFILE} || '.'; - -# If terminal coloring is available, use it. The only function I want from -# the module is the colored() function. -eval { - if ( !$opts{n} ) { - if ( $windows ) { - require Win32::Console::ANSI; - } - require Term::ANSIColor; - import Term::ANSIColor qw(colored); - $have_color = 1; - } -}; -if ( $EVAL_ERROR || $opts{n} ) { - # If there was an error, manufacture my own colored() function that does no - # coloring. - *colored = sub { pop @_; @_; }; -} - -if ( $opts{n} ) { - $clear_screen_sub = sub {}; -} -elsif ( $windows ) { - $clear_screen_sub = sub { $lines_printed = 0; system("cls") }; -} -else { - my $clear = `clear`; - $clear_screen_sub = sub { $lines_printed = 0; print $clear }; -} - -# ########################################################################### -# Config storage. {{{2 -# ########################################################################### -my %config = ( - color => { - val => $have_color, - note => 'Whether to use terminal coloring', - conf => 'ALL', - pat => $bool_regex, - }, - cmd_filter => { - val => 'Com_', - note => 'Prefix for values in C mode', - conf => [qw(C)], - }, - plugin_dir => { - val => "$homepath/.innotop/plugins", - note => 'Directory where plugins can be found', - conf => 'ALL', - }, - show_percent => { - val => 1, - note => 'Show the % symbol after percentages', - conf => 'ALL', - pat => $bool_regex, - }, - skip_innodb => { - val => 0, - note => 'Disable SHOW INNODB STATUS', - conf => 'ALL', - pat => $bool_regex, - }, - S_func => { - val => 's', - note => 'What to display in S mode: graph, status, pivoted status', - conf => [qw(S)], - pat => qr/^[gsv]$/, - }, - cxn_timeout => { - val => 28800, - note => 'Connection timeout for keeping unused connections alive', - conf => 'ALL', - pat => $int_regex, - }, - graph_char => { - val => '*', - note => 'Character for drawing graphs', - conf => [ qw(S) ], - pat => qr/^.$/, - }, - show_cxn_errors_in_tbl => { - val => 1, - note => 'Whether to display connection errors as rows in the table', - conf => 'ALL', - pat => $bool_regex, - }, - hide_hdr => { - val => 0, - note => 'Whether to show column headers', - conf => 'ALL', - pat => $bool_regex, - }, - show_cxn_errors => { - val => 1, - note => 'Whether to print connection errors to STDOUT', - conf => 'ALL', - pat => $bool_regex, - }, - readonly => { - val => 0, - note => 'Whether the config file is read-only', - conf => [ qw() ], - pat => $bool_regex, - }, - global => { - val => 1, - note => 'Whether to show GLOBAL variables and status', - conf => 'ALL', - pat => $bool_regex, - }, - header_highlight => { - val => 'bold', - note => 'How to highlight table column headers', - conf => 'ALL', - pat => qr/^(?:bold|underline)$/, - }, - display_table_captions => { - val => 1, - note => 'Whether to put captions on tables', - conf => 'ALL', - pat => $bool_regex, - }, - charset => { - val => 'ascii', - note => 'What type of characters should be displayed in queries (ascii, unicode, none)', - conf => 'ALL', - pat => qr/^(?:ascii|unicode|none)$/, - }, - auto_wipe_dl => { - val => 0, - note => 'Whether to auto-wipe InnoDB deadlocks', - conf => 'ALL', - pat => $bool_regex, - }, - max_height => { - val => 30, - note => '[Win32] Max window height', - conf => 'ALL', - }, - debug => { - val => 0, - pat => $bool_regex, - note => 'Debug mode (more verbose errors, uses more memory)', - conf => 'ALL', - }, - num_digits => { - val => 2, - pat => $int_regex, - note => 'How many digits to show in fractional numbers and percents', - conf => 'ALL', - }, - debugfile => { - val => "$homepath/.innotop/core_dump", - note => 'A debug file in case you are interested in error output', - }, - show_statusbar => { - val => 1, - pat => $bool_regex, - note => 'Whether to show the status bar in the display', - conf => 'ALL', - }, - mode => { - val => "T", - note => "Which mode to start in", - cmdline => 1, - }, - status_inc => { - val => 0, - note => 'Whether to show raw or incremental values for status variables', - pat => $bool_regex, - }, - interval => { - val => 10, - pat => qr/^(?:(?:\d*?[1-9]\d*(?:\.\d*)?)|(?:\d*\.\d*?[1-9]\d*))$/, - note => "The interval at which the display will be refreshed. Fractional values allowed.", - }, - num_status_sets => { - val => 9, - pat => $int_regex, - note => 'How many sets of STATUS and VARIABLES values to show', - conf => [ qw(S) ], - }, - S_set => { - val => 'general', - pat => qr/^\w+$/, - note => 'Which set of variables to display in S (Variables & Status) mode', - conf => [ qw(S) ], - }, -); - -# ########################################################################### -# Config file sections {{{2 -# The configuration file is broken up into sections like a .ini file. This -# variable defines those sections and the subroutines responsible for reading -# and writing them. -# ########################################################################### -my %config_file_sections = ( - plugins => { - reader => \&load_config_plugins, - writer => \&save_config_plugins, - }, - group_by => { - reader => \&load_config_group_by, - writer => \&save_config_group_by, - }, - filters => { - reader => \&load_config_filters, - writer => \&save_config_filters, - }, - active_filters => { - reader => \&load_config_active_filters, - writer => \&save_config_active_filters, - }, - visible_tables => { - reader => \&load_config_visible_tables, - writer => \&save_config_visible_tables, - }, - sort_cols => { - reader => \&load_config_sort_cols, - writer => \&save_config_sort_cols, - }, - active_columns => { - reader => \&load_config_active_columns, - writer => \&save_config_active_columns, - }, - tbl_meta => { - reader => \&load_config_tbl_meta, - writer => \&save_config_tbl_meta, - }, - general => { - reader => \&load_config_config, - writer => \&save_config_config, - }, - connections => { - reader => \&load_config_connections, - writer => \&save_config_connections, - }, - active_connections => { - reader => \&load_config_active_connections, - writer => \&save_config_active_connections, - }, - server_groups => { - reader => \&load_config_server_groups, - writer => \&save_config_server_groups, - }, - active_server_groups => { - reader => \&load_config_active_server_groups, - writer => \&save_config_active_server_groups, - }, - max_values_seen => { - reader => \&load_config_mvs, - writer => \&save_config_mvs, - }, - varsets => { - reader => \&load_config_varsets, - writer => \&save_config_varsets, - }, - colors => { - reader => \&load_config_colors, - writer => \&save_config_colors, - }, - stmt_sleep_times => { - reader => \&load_config_stmt_sleep_times, - writer => \&save_config_stmt_sleep_times, - }, -); - -# Config file sections have some dependencies, so they have to be read/written in order. -my @ordered_config_file_sections = qw(general plugins filters active_filters tbl_meta - connections active_connections server_groups active_server_groups max_values_seen - active_columns sort_cols visible_tables varsets colors stmt_sleep_times - group_by); - -# All events for which plugins may register themselves. Entries are arrayrefs. -my %event_listener_for = map { $_ => [] } - qw( - extract_values - set_to_tbl_pre_filter set_to_tbl_pre_sort set_to_tbl_pre_group - set_to_tbl_pre_colorize set_to_tbl_pre_transform set_to_tbl_pre_pivot - set_to_tbl_pre_create set_to_tbl_post_create - draw_screen - ); - -# All variables to which plugins have access. -my %pluggable_vars = ( - action_for => \%action_for, - agg_funcs => \%agg_funcs, - config => \%config, - connections => \%connections, - dbhs => \%dbhs, - filters => \%filters, - modes => \%modes, - server_groups => \%server_groups, - tbl_meta => \%tbl_meta, - trans_funcs => \%trans_funcs, - var_sets => \%var_sets, -); - -# ########################################################################### -# Contains logic to generate prepared statements for a given function for a -# given DB connection. Returns a $sth. -# ########################################################################### -my %stmt_maker_for = ( - INNODB_STATUS => sub { - my ( $dbh ) = @_; - return $dbh->prepare(version_ge( $dbh, '5.0.0' ) - ? 'SHOW ENGINE INNODB STATUS' - : 'SHOW INNODB STATUS'); - }, - SHOW_VARIABLES => sub { - my ( $dbh ) = @_; - return $dbh->prepare($config{global}->{val} && version_ge( $dbh, '4.0.3' ) - ? 'SHOW GLOBAL VARIABLES' - : 'SHOW VARIABLES'); - }, - SHOW_STATUS => sub { - my ( $dbh ) = @_; - return $dbh->prepare($config{global}->{val} && version_ge( $dbh, '5.0.2' ) - ? 'SHOW GLOBAL STATUS' - : 'SHOW STATUS'); - }, - KILL_QUERY => sub { - my ( $dbh ) = @_; - return $dbh->prepare(version_ge( $dbh, '5.0.0' ) - ? 'KILL QUERY ?' - : 'KILL ?'); - }, - SHOW_MASTER_LOGS => sub { - my ( $dbh ) = @_; - return $dbh->prepare('SHOW MASTER LOGS'); - }, - SHOW_MASTER_STATUS => sub { - my ( $dbh ) = @_; - return $dbh->prepare('SHOW MASTER STATUS'); - }, - SHOW_SLAVE_STATUS => sub { - my ( $dbh ) = @_; - return $dbh->prepare('SHOW SLAVE STATUS'); - }, - KILL_CONNECTION => sub { - my ( $dbh ) = @_; - return $dbh->prepare(version_ge( $dbh, '5.0.0' ) - ? 'KILL CONNECTION ?' - : 'KILL ?'); - }, - OPEN_TABLES => sub { - my ( $dbh ) = @_; - return version_ge($dbh, '4.0.0') - ? $dbh->prepare('SHOW OPEN TABLES') - : undef; - }, - PROCESSLIST => sub { - my ( $dbh ) = @_; - return $dbh->prepare('SHOW FULL PROCESSLIST'); - }, -); - -# Plugins! -my %plugins = ( -); - -# ########################################################################### -# Run the program {{{1 -# ########################################################################### - -# This config variable is only useful for MS Windows because its terminal -# can't tell how tall it is. -if ( !$windows ) { - delete $config{max_height}; -} - -# Try to lower my priority. -eval { setpriority(0, 0, getpriority(0, 0) + 10); }; - -# Print stuff to the screen immediately, don't wait for a newline. -$OUTPUT_AUTOFLUSH = 1; - -# Clear the screen and load the configuration. -$clear_screen_sub->(); -load_config(); -post_process_tbl_meta(); - -# Make sure no changes are written to config file in non-interactive mode. -if ( $opts{n} ) { - $config{readonly}->{val} = 1; -} - -eval { - - # Open the file for InnoDB status - if ( @ARGV ) { - my $filename = shift @ARGV; - open $file, "<", $filename - or die "Cannot open '$filename': $OS_ERROR"; - } - - # In certain modes we might have to collect data for two cycles - # before printing anything out, so we need to bump up the count one. - if ( $opts{n} && $opts{count} && $config{status_inc}->{val} - && $config{mode}->{val} =~ m/[S]/ ) - { - $opts{count}++; - } - - while (++$clock) { - - my $mode = $config{mode}->{val} || 'T'; - if ( !$modes{$mode} ) { - die "Mode '$mode' doesn't exist; try one of these:\n" - . join("\n", map { " $_ $modes{$_}->{hdr}" } sort keys %modes) - . "\n"; - } - - if ( !$opts{n} ) { - @last_term_size = @this_term_size; - @this_term_size = Term::ReadKey::GetTerminalSize(\*STDOUT); - if ( $windows ) { - $this_term_size[0]--; - $this_term_size[1] - = min($this_term_size[1], $config{max_height}->{val}); - } - die("Can't read terminal size") unless @this_term_size; - } - - # If there's no connection to a database server, we need to fix that... - if ( !%connections ) { - print "You have not defined any database connections.\n\n"; - add_new_dsn(); - } - - # See whether there are any connections defined for this mode. If there's only one - # connection total, assume the user wants to just use innotop for a single server - # and don't ask which server to connect to. Also, if we're monitoring from a file, - # we just use the first connection. - if ( !get_connections() ) { - if ( $file || 1 == scalar keys %connections ) { - $modes{$config{mode}->{val}}->{connections} = [ keys %connections ]; - } - else { - choose_connections(); - } - } - - # Term::ReadLine might have re-set $OUTPUT_AUTOFLUSH. - $OUTPUT_AUTOFLUSH = 1; - - # Prune old data - my $sets = $config{num_status_sets}->{val}; - foreach my $store ( values %vars ) { - delete @{$store}{ grep { $_ < $clock - $sets } keys %$store }; - } - %info_gotten = (); - - # Call the subroutine to display this mode. - $modes{$mode}->{display_sub}->(); - - # It may be time to quit now. - if ( $opts{count} && $clock >= $opts{count} ) { - finish(); - } - - # Wait for a bit. - if ( $opts{n} ) { - sleep($config{interval}->{val}); - } - else { - ReadMode('cbreak'); - $char = ReadKey($config{interval}->{val}); - ReadMode('normal'); - } - - # Handle whatever action the key indicates. - do_key_action(); - - } -}; -if ( $EVAL_ERROR ) { - core_dump( $EVAL_ERROR ); -} -finish(); - -# Subroutines {{{1 -# Mode functions{{{2 -# switch_mode {{{3 -sub switch_mode { - my $mode = shift; - $config{mode}->{val} = $mode; -} - -# Prompting functions {{{2 -# prompt_list {{{3 -# Prompts the user for a value, given a question, initial value, -# a completion function and a hashref of hints. -sub prompt_list { - die "Can't call in non-interactive mode" if $opts{n}; - my ( $question, $init, $completion, $hints ) = @_; - if ( $hints ) { - # Figure out how wide the table will be - my $max_name = max(map { length($_) } keys %$hints ); - $max_name ||= 0; - $max_name += 3; - my @meta_rows = create_table2( - [ sort keys %$hints ], - { map { $_ => $_ } keys %$hints }, - { map { $_ => trunc($hints->{$_}, $this_term_size[0] - $max_name) } keys %$hints }, - { sep => ' ' }); - if (@meta_rows > 10) { - # Try to split and stack the meta rows next to each other - my $split = int(@meta_rows / 2); - @meta_rows = stack_next( - [@meta_rows[0..$split - 1]], - [@meta_rows[$split..$#meta_rows]], - { pad => ' | '}, - ); - } - print join( "\n", - '', - map { ref $_ ? colored(@$_) : $_ } create_caption('Choose from', @meta_rows), ''), - "\n"; - } - $term->Attribs->{completion_function} = $completion; - my $answer = $term->readline("$question: ", $init); - $OUTPUT_AUTOFLUSH = 1; - $answer = '' if !defined($answer); - $answer =~ s/\s+$//; - return $answer; -} - -# prompt {{{3 -# Prints out a prompt and reads from the keyboard, then validates with the -# validation regex until the input is correct. -sub prompt { - die "Can't call in non-interactive mode" if $opts{n}; - my ( $prompt, $regex, $init, $completion ) = @_; - my $response; - my $success = 0; - do { - if ( $completion ) { - $term->Attribs->{completion_function} = $completion; - } - $response = $term->readline("$prompt: ", $init); - if ( $regex && $response !~ m/$regex/ ) { - print "Invalid response.\n\n"; - } - else { - $success = 1; - } - } while ( !$success ); - $OUTPUT_AUTOFLUSH = 1; - $response =~ s/\s+$//; - return $response; -} - -# prompt_noecho {{{3 -# Unfortunately, suppressing echo with Term::ReadLine isn't reliable; the user might not -# have that library, or it might not support that feature. -sub prompt_noecho { - my ( $prompt ) = @_; - print colored("$prompt: ", 'underline'); - my $response; - ReadMode('noecho'); - $response = <STDIN>; - chomp($response); - ReadMode('normal'); - return $response; -} - -# do_key_action {{{3 -# Depending on whether a key was read, do something. Keys have certain -# actions defined in lookup tables. Each mode may have its own lookup table, -# which trumps the global table -- so keys can be context-sensitive. The key -# may be read and written in a subroutine, so it's a global. -sub do_key_action { - if ( defined $char ) { - my $mode = $config{mode}->{val}; - my $action - = defined($modes{$mode}->{action_for}->{$char}) - ? $modes{$mode}->{action_for}->{$char}->{action} - : defined($action_for{$char}) - ? $action_for{$char}->{action} - : sub{}; - $action->(); - } -} - -# pause {{{3 -sub pause { - die "Can't call in non-interactive mode" if $opts{n}; - my $msg = shift; - print defined($msg) ? "\n$msg" : "\nPress any key to continue"; - ReadMode('cbreak'); - my $char = ReadKey(0); - ReadMode('normal'); - return $char; -} - -# reverse_sort {{{3 -sub reverse_sort { - my $tbl = shift; - $tbl_meta{$tbl}->{sort_dir} *= -1; -} - -# select_cxn {{{3 -# Selects connection(s). If the mode (or argument list) has only one, returns -# it without prompt. -sub select_cxn { - my ( $prompt, @cxns ) = @_; - if ( !@cxns ) { - @cxns = get_connections(); - } - if ( @cxns == 1 ) { - return $cxns[0]; - } - my $choices = prompt_list( - $prompt, - $cxns[0], - sub{ return @cxns }, - { map { $_ => $connections{$_}->{dsn} } @cxns }); - my @result = unique(grep { my $a = $_; grep { $_ eq $a } @cxns } split(/\s+/, $choices)); - return @result; -} - -# kill_query {{{3 -# Kills a connection, or on new versions, optionally a query but not connection. -sub kill_query { - my ( $q_or_c ) = @_; - - my $info = choose_thread( - sub { 1 }, - 'Select a thread to kill the ' . $q_or_c, - ); - return unless $info; - return unless pause("Kill $info->{id}?") =~ m/y/i; - - eval { - do_stmt($info->{cxn}, $q_or_c eq 'QUERY' ? 'KILL_QUERY' : 'KILL_CONNECTION', $info->{id} ); - }; - - if ( $EVAL_ERROR ) { - print "\nError: $EVAL_ERROR"; - pause(); - } -} - -# set_display_precision {{{3 -sub set_display_precision { - my $dir = shift; - $config{num_digits}->{val} = min(9, max(0, $config{num_digits}->{val} + $dir)); -} - -sub toggle_visible_table { - my ( $mode, $table ) = @_; - my $visible = $modes{$mode}->{visible_tables}; - if ( grep { $_ eq $table } @$visible ) { - $modes{$mode}->{visible_tables} = [ grep { $_ ne $table } @$visible ]; - } - else { - unshift @$visible, $table; - } - $modes{$mode}->{cust}->{visible_tables} = 1; -} - -# toggle_filter{{{3 -sub toggle_filter { - my ( $tbl, $filter ) = @_; - my $filters = $tbl_meta{$tbl}->{filters}; - if ( grep { $_ eq $filter } @$filters ) { - $tbl_meta{$tbl}->{filters} = [ grep { $_ ne $filter } @$filters ]; - } - else { - push @$filters, $filter; - } - $tbl_meta{$tbl}->{cust}->{filters} = 1; -} - -# toggle_config {{{3 -sub toggle_config { - my ( $key ) = @_; - $config{$key}->{val} ^= 1; -} - -# create_deadlock {{{3 -sub create_deadlock { - $clear_screen_sub->(); - - print "This function will deliberately cause a small deadlock, " - . "clearing deadlock information from the InnoDB monitor.\n\n"; - - my $answer = prompt("Are you sure you want to proceed? Say 'y' if you do"); - return 0 unless $answer eq 'y'; - - my ( $cxn ) = select_cxn('Clear on which server? '); - return unless $cxn && exists($connections{$cxn}); - - clear_deadlock($cxn); -} - -# deadlock_thread {{{3 -sub deadlock_thread { - my ( $id, $tbl, $cxn ) = @_; - - eval { - my $dbh = get_new_db_connection($cxn, 1); - my @stmts = ( - "set transaction isolation level serializable", - (version_ge($dbh, '4.0.11') ? "start transaction" : 'begin'), - "select * from $tbl where a = $id", - "update $tbl set a = $id where a <> $id", - ); - - foreach my $stmt (@stmts[0..2]) { - $dbh->do($stmt); - } - sleep(1 + $id); - $dbh->do($stmts[-1]); - }; - if ( $EVAL_ERROR ) { - if ( $EVAL_ERROR !~ m/Deadlock found/ ) { - die $EVAL_ERROR; - } - } - exit(0); -} - -# Purges unused binlogs on the master, up to but not including the latest log. -# TODO: guess which connections are slaves of a given master. -sub purge_master_logs { - my @cxns = get_connections(); - - get_master_slave_status(@cxns); - - # Toss out the rows that don't have master/slave status... - my @vars = - grep { $_ && ($_->{file} || $_->{master_host}) } - map { $vars{$_}->{$clock} } @cxns; - @cxns = map { $_->{cxn} } @vars; - - # Figure out which master to purge ons. - my @masters = map { $_->{cxn} } grep { $_->{file} } @vars; - my ( $master ) = select_cxn('Which master?', @masters ); - return unless $master; - my ($master_status) = grep { $_->{cxn} eq $master } @vars; - - # Figure out the result order (not lexical order) of master logs. - my @master_logs = get_master_logs($master); - my $i = 0; - my %master_logs = map { $_->{log_name} => $i++ } @master_logs; - - # Ask which slave(s) are reading from this master. - my @slave_status = grep { $_->{master_host} } @vars; - my @slaves = map { $_->{cxn} } @slave_status; - @slaves = select_cxn("Which slaves are reading from $master?", @slaves); - @slave_status = grep { my $item = $_; grep { $item->{cxn} eq $_ } @slaves } @slave_status; - return unless @slave_status; - - # Find the minimum binary log in use. - my $min_log = min(map { $master_logs{$_->{master_log_file}} } @slave_status); - my $log_name = $master_logs[$min_log]->{log_name}; - - my $stmt = "PURGE MASTER LOGS TO '$log_name'"; - send_cmd_to_servers($stmt, 0, 'PURGE {MASTER | BINARY} LOGS {TO "log_name" | BEFORE "date"}', [$master]); -} - -sub send_cmd_to_servers { - my ( $cmd, $all, $hint, $cxns ) = @_; - if ( $all ) { - @$cxns = get_connections(); - } - elsif ( !@$cxns ) { - @$cxns = select_cxn('Which servers?', @$cxns); - } - if ( $hint ) { - print "\nHint: $hint\n"; - } - $cmd = prompt('Command to send', undef, $cmd); - foreach my $cxn ( @$cxns ) { - eval { - my $sth = do_query($cxn, $cmd); - }; - if ( $EVAL_ERROR ) { - print "Error from $cxn: $EVAL_ERROR\n"; - } - else { - print "Success on $cxn\n"; - } - } - pause(); -} - -# Display functions {{{2 - -sub set_s_mode { - my ( $func ) = @_; - $clear_screen_sub->(); - $config{S_func}->{val} = $func; -} - -# start_S_mode {{{3 -sub start_S_mode { - $clear_screen_sub->(); - switch_mode('S'); -} - -# display_B {{{3 -sub display_B { - my @display_lines; - my @cxns = get_connections(); - get_innodb_status(\@cxns); - - my @buffer_pool; - my @page_statistics; - my @insert_buffers; - my @adaptive_hash_index; - my %rows_for = ( - buffer_pool => \@buffer_pool, - page_statistics => \@page_statistics, - insert_buffers => \@insert_buffers, - adaptive_hash_index => \@adaptive_hash_index, - ); - - my @visible = get_visible_tables(); - my %wanted = map { $_ => 1 } @visible; - - foreach my $cxn ( @cxns ) { - my $set = $vars{$cxn}->{$clock}; - my $pre = $vars{$cxn}->{$clock-1} || $set; - - if ( $set->{IB_bp_complete} ) { - if ( $wanted{buffer_pool} ) { - push @buffer_pool, extract_values($set, $set, $pre, 'buffer_pool'); - } - if ( $wanted{page_statistics} ) { - push @page_statistics, extract_values($set, $set, $pre, 'page_statistics'); - } - } - if ( $set->{IB_ib_complete} ) { - if ( $wanted{insert_buffers} ) { - push @insert_buffers, extract_values( - $config{status_inc}->{val} ? inc(0, $cxn) : $set, $set, $pre, - 'insert_buffers'); - } - if ( $wanted{adaptive_hash_index} ) { - push @adaptive_hash_index, extract_values($set, $set, $pre, 'adaptive_hash_index'); - } - } - } - - my $first_table = 0; - foreach my $tbl ( @visible ) { - push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl); - push @display_lines, get_cxn_errors(@cxns) - if ( $config{debug}->{val} || !$first_table++ ); - } - - draw_screen(\@display_lines); -} - -# display_C {{{3 -sub display_C { - my @display_lines; - my @cxns = get_connections(); - get_status_info(@cxns); - - my @cmd_summary; - my %rows_for = ( - cmd_summary => \@cmd_summary, - ); - - my @visible = get_visible_tables(); - my %wanted = map { $_ => 1 } @visible; - - # For now, I'm manually pulling these variables out and pivoting. Eventually a SQL-ish - # dialect should let me join a table to a grouped and pivoted table and do this more easily. - # TODO: make it so. - my $prefix = qr/^$config{cmd_filter}->{val}/; # TODO: this is a total hack - my @values; - my ($total, $last_total) = (0, 0); - foreach my $cxn ( @cxns ) { - my $set = $vars{$cxn}->{$clock}; - my $pre = $vars{$cxn}->{$clock-1} || $set; - foreach my $key ( keys %$set ) { - next unless $key =~ m/$prefix/i; - my $val = $set->{$key}; - next unless defined $val && $val =~ m/^\d+$/; - my $last_val = $val - ($pre->{$key} || 0); - $total += $val; - $last_total += $last_val; - push @values, { - name => $key, - value => $val, - last_value => $last_val, - }; - } - } - - # Add aggregation and turn into a real set TODO: total hack - if ( $wanted{cmd_summary} ) { - foreach my $value ( @values ) { - @{$value}{qw(total last_total)} = ($total, $last_total); - push @cmd_summary, extract_values($value, $value, $value, 'cmd_summary'); - } - } - - my $first_table = 0; - foreach my $tbl ( @visible ) { - push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl); - push @display_lines, get_cxn_errors(@cxns) - if ( $config{debug}->{val} || !$first_table++ ); - } - - draw_screen(\@display_lines); -} - -# display_D {{{3 -sub display_D { - my @display_lines; - my @cxns = get_connections(); - get_innodb_status(\@cxns); - - my @deadlock_transactions; - my @deadlock_locks; - my %rows_for = ( - deadlock_transactions => \@deadlock_transactions, - deadlock_locks => \@deadlock_locks, - ); - - my @visible = get_visible_tables(); - my %wanted = map { $_ => 1 } @visible; - - foreach my $cxn ( @cxns ) { - my $innodb_status = $vars{$cxn}->{$clock}; - my $prev_status = $vars{$cxn}->{$clock-1} || $innodb_status; - - if ( $innodb_status->{IB_dl_timestring} ) { - - my $victim = $innodb_status->{IB_dl_rolled_back} || 0; - - if ( %wanted ) { - foreach my $txn_id ( keys %{$innodb_status->{IB_dl_txns}} ) { - my $txn = $innodb_status->{IB_dl_txns}->{$txn_id}; - my $pre = $prev_status->{IB_dl_txns}->{$txn_id} || $txn; - - if ( $wanted{deadlock_transactions} ) { - my $hash = extract_values($txn->{tx}, $txn->{tx}, $pre->{tx}, 'deadlock_transactions'); - $hash->{cxn} = $cxn; - $hash->{dl_txn_num} = $txn_id; - $hash->{victim} = $txn_id == $victim ? 'Yes' : 'No'; - $hash->{timestring} = $innodb_status->{IB_dl_timestring}; - $hash->{truncates} = $innodb_status->{IB_dl_complete} ? 'No' : 'Yes'; - push @deadlock_transactions, $hash; - } - - if ( $wanted{deadlock_locks} ) { - foreach my $lock ( @{$txn->{locks}} ) { - my $hash = extract_values($lock, $lock, $lock, 'deadlock_locks'); - $hash->{dl_txn_num} = $txn_id; - $hash->{cxn} = $cxn; - $hash->{mysql_thread_id} = $txn->{tx}->{mysql_thread_id}; - push @deadlock_locks, $hash; - } - } - - } - } - } - } - - my $first_table = 0; - foreach my $tbl ( @visible ) { - push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl); - push @display_lines, get_cxn_errors(@cxns) - if ( $config{debug}->{val} || !$first_table++ ); - } - - draw_screen(\@display_lines); -} - -# display_F {{{3 -sub display_F { - my @display_lines; - my ( $cxn ) = get_connections(); - get_innodb_status([$cxn]); - my $innodb_status = $vars{$cxn}->{$clock}; - - if ( $innodb_status->{IB_fk_timestring} ) { - - push @display_lines, 'Reason: ' . $innodb_status->{IB_fk_reason}; - - # Display FK errors caused by invalid DML. - if ( $innodb_status->{IB_fk_txn} ) { - my $txn = $innodb_status->{IB_fk_txn}; - push @display_lines, - '', - "User $txn->{user} from $txn->{hostname}, thread $txn->{mysql_thread_id} was executing:", - '', no_ctrl_char($txn->{query_text}); - } - - my @fk_table = create_table2( - $tbl_meta{fk_error}->{visible}, - meta_to_hdr('fk_error'), - extract_values($innodb_status, $innodb_status, $innodb_status, 'fk_error'), - { just => '-', sep => ' '}); - push @display_lines, '', @fk_table; - - } - else { - push @display_lines, '', 'No foreign key error data.'; - } - draw_screen(\@display_lines, { raw => 1 } ); -} - -# display_I {{{3 -sub display_I { - my @display_lines; - my @cxns = get_connections(); - get_innodb_status(\@cxns); - - my @io_threads; - my @pending_io; - my @file_io_misc; - my @log_statistics; - my %rows_for = ( - io_threads => \@io_threads, - pending_io => \@pending_io, - file_io_misc => \@file_io_misc, - log_statistics => \@log_statistics, - ); - - my @visible = get_visible_tables(); - my %wanted = map { $_ => 1 } @visible; - - foreach my $cxn ( @cxns ) { - my $set = $vars{$cxn}->{$clock}; - my $pre = $vars{$cxn}->{$clock-1} || $set; - - if ( $set->{IB_io_complete} ) { - if ( $wanted{io_threads} ) { - my $cur_threads = $set->{IB_io_threads}; - my $pre_threads = $pre->{IB_io_threads} || $cur_threads; - foreach my $key ( sort keys %$cur_threads ) { - my $cur_thd = $cur_threads->{$key}; - my $pre_thd = $pre_threads->{$key} || $cur_thd; - my $hash = extract_values($cur_thd, $cur_thd, $pre_thd, 'io_threads'); - $hash->{cxn} = $cxn; - push @io_threads, $hash; - } - } - if ( $wanted{pending_io} ) { - push @pending_io, extract_values($set, $set, $pre, 'pending_io'); - } - if ( $wanted{file_io_misc} ) { - push @file_io_misc, extract_values( - $config{status_inc}->{val} ? inc(0, $cxn) : $set, - $set, $pre, 'file_io_misc'); - } - } - if ( $set->{IB_lg_complete} && $wanted{log_statistics} ) { - push @log_statistics, extract_values($set, $set, $pre, 'log_statistics'); - } - } - - my $first_table = 0; - foreach my $tbl ( @visible ) { - push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl); - push @display_lines, get_cxn_errors(@cxns) - if ( $config{debug}->{val} || !$first_table++ ); - } - - draw_screen(\@display_lines); -} - -# display_L {{{3 -sub display_L { - my @display_lines; - my @cxns = get_connections(); - get_innodb_status(\@cxns); - - my @innodb_locks; - my %rows_for = ( - innodb_locks => \@innodb_locks, - ); - - my @visible = get_visible_tables(); - my %wanted = map { $_ => 1 } @visible; - - # Get info on locks - foreach my $cxn ( @cxns ) { - my $set = $vars{$cxn}->{$clock} or next; - my $pre = $vars{$cxn}->{$clock-1} || $set; - - if ( $wanted{innodb_locks} && defined $set->{IB_tx_transactions} && @{$set->{IB_tx_transactions}} ) { - - my $cur_txns = $set->{IB_tx_transactions}; - my $pre_txns = $pre->{IB_tx_transactions} || $cur_txns; - my %cur_txns = map { $_->{mysql_thread_id} => $_ } @$cur_txns; - my %pre_txns = map { $_->{mysql_thread_id} => $_ } @$pre_txns; - foreach my $txn ( @$cur_txns ) { - foreach my $lock ( @{$txn->{locks}} ) { - my %hash = map { $_ => $txn->{$_} } qw(txn_id mysql_thread_id lock_wait_time active_secs); - map { $hash{$_} = $lock->{$_} } qw(lock_type space_id page_no n_bits index db table txn_id lock_mode special insert_intention waiting); - $hash{cxn} = $cxn; - push @innodb_locks, extract_values(\%hash, \%hash, \%hash, 'innodb_locks'); - } - } - } - } - - my $first_table = 0; - foreach my $tbl ( @visible ) { - push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl); - push @display_lines, get_cxn_errors(@cxns) - if ( $config{debug}->{val} || !$first_table++ ); - } - - draw_screen(\@display_lines); -} - -# display_M {{{3 -sub display_M { - my @display_lines; - my @cxns = get_connections(); - get_master_slave_status(@cxns); - get_status_info(@cxns); - - my @slave_sql_status; - my @slave_io_status; - my @master_status; - my %rows_for = ( - slave_sql_status => \@slave_sql_status, - slave_io_status => \@slave_io_status, - master_status => \@master_status, - ); - - my @visible = get_visible_tables(); - my %wanted = map { $_ => 1 } @visible; - - foreach my $cxn ( @cxns ) { - my $set = $config{status_inc}->{val} ? inc(0, $cxn) : $vars{$cxn}->{$clock}; - my $pre = $vars{$cxn}->{$clock - 1} || $set; - if ( $wanted{slave_sql_status} ) { - push @slave_sql_status, extract_values($set, $set, $pre, 'slave_sql_status'); - } - if ( $wanted{slave_io_status} ) { - push @slave_io_status, extract_values($set, $set, $pre, 'slave_io_status'); - } - if ( $wanted{master_status} ) { - push @master_status, extract_values($set, $set, $pre, 'master_status'); - } - } - - my $first_table = 0; - foreach my $tbl ( @visible ) { - push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl); - push @display_lines, get_cxn_errors(@cxns) - if ( $config{debug}->{val} || !$first_table++ ); - } - - draw_screen(\@display_lines); -} - -# display_O {{{3 -sub display_O { - my @display_lines = (''); - my @cxns = get_connections(); - my @open_tables = get_open_tables(@cxns); - my @tables = map { extract_values($_, $_, $_, 'open_tables') } @open_tables; - push @display_lines, set_to_tbl(\@tables, 'open_tables'), get_cxn_errors(@cxns); - draw_screen(\@display_lines); -} - -# display_Q {{{3 -sub display_Q { - my @display_lines; - - my @q_header; - my @processlist; - my %rows_for = ( - q_header => \@q_header, - processlist => \@processlist, - ); - - my @visible = $opts{n} ? 'processlist' : get_visible_tables(); - my %wanted = map { $_ => 1 } @visible; - - # Get the data - my @cxns = get_connections(); - my @full_processlist = get_full_processlist(@cxns); - - # Create header - if ( $wanted{q_header} ) { - get_status_info(@cxns); - foreach my $cxn ( @cxns ) { - my $set = $vars{$cxn}->{$clock}; - my $pre = $vars{$cxn}->{$clock-1} || $set; - my $hash = extract_values($set, $set, $pre, 'q_header'); - $hash->{cxn} = $cxn; - $hash->{when} = 'Total'; - push @q_header, $hash; - - if ( exists $vars{$cxn}->{$clock - 1} ) { - my $inc = inc(0, $cxn); - my $hash = extract_values($inc, $set, $pre, 'q_header'); - $hash->{cxn} = $cxn; - $hash->{when} = 'Now'; - push @q_header, $hash; - } - } - } - - if ( $wanted{processlist} ) { - # TODO: save prev values - push @processlist, map { extract_values($_, $_, $_, 'processlist') } @full_processlist; - } - - my $first_table = 0; - foreach my $tbl ( @visible ) { - next unless $wanted{$tbl}; - push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl); - push @display_lines, get_cxn_errors(@cxns) - if ( $config{debug}->{val} || !$first_table++ ); - } - - # Save queries in global variable for analysis. The rows in %rows_for have been - # filtered, etc as a side effect of set_to_tbl(), so they are the same as the rows - # that get pushed to the screen. - @current_queries = map { - my %hash; - @hash{ qw(cxn id db query secs) } = @{$_}{ qw(cxn mysql_thread_id db info secs) }; - \%hash; - } @{$rows_for{processlist}}; - - draw_screen(\@display_lines); -} - -# display_R {{{3 -sub display_R { - my @display_lines; - my @cxns = get_connections(); - get_innodb_status(\@cxns); - - my @row_operations; - my @row_operation_misc; - my @semaphores; - my @wait_array; - my %rows_for = ( - row_operations => \@row_operations, - row_operation_misc => \@row_operation_misc, - semaphores => \@semaphores, - wait_array => \@wait_array, - ); - - my @visible = get_visible_tables(); - my %wanted = map { $_ => 1 } @visible; - my $incvar = $config{status_inc}->{val}; - - foreach my $cxn ( @cxns ) { - my $set = $vars{$cxn}->{$clock}; - my $pre = $vars{$cxn}->{$clock-1} || $set; - my $inc; # Only assigned to if wanted - - if ( $set->{IB_ro_complete} ) { - if ( $wanted{row_operations} ) { - $inc ||= $incvar ? inc(0, $cxn) : $set; - push @row_operations, extract_values($inc, $set, $pre, 'row_operations'); - } - if ( $wanted{row_operation_misc} ) { - push @row_operation_misc, extract_values($set, $set, $pre, 'row_operation_misc'), - } - } - - if ( $set->{IB_sm_complete} && $wanted{semaphores} ) { - $inc ||= $incvar ? inc(0, $cxn) : $set; - push @semaphores, extract_values($inc, $set, $pre, 'semaphores'); - } - - if ( $set->{IB_sm_wait_array_size} && $wanted{wait_array} ) { - foreach my $wait ( @{$set->{IB_sm_waits}} ) { - my $hash = extract_values($wait, $wait, $wait, 'wait_array'); - $hash->{cxn} = $cxn; - push @wait_array, $hash; - } - } - } - - my $first_table = 0; - foreach my $tbl ( @visible ) { - push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl); - push @display_lines, get_cxn_errors(@cxns) - if ( $config{debug}->{val} || !$first_table++ ); - } - - draw_screen(\@display_lines); -} - -# display_T {{{3 -sub display_T { - my @display_lines; - - my @t_header; - my @innodb_transactions; - my %rows_for = ( - t_header => \@t_header, - innodb_transactions => \@innodb_transactions, - ); - - my @visible = $opts{n} ? 'innodb_transactions' : get_visible_tables(); - my %wanted = map { $_ => 1 } @visible; - - my @cxns = get_connections(); - - # If the header is to be shown, buffer pool data is required. - get_innodb_status( \@cxns, [ $wanted{t_header} ? qw(bp) : () ] ); - - foreach my $cxn ( get_connections() ) { - my $set = $vars{$cxn}->{$clock}; - my $pre = $vars{$cxn}->{$clock-1} || $set; - - next unless $set->{IB_tx_transactions}; - - if ( $wanted{t_header} ) { - my $hash = extract_values($set, $set, $pre, 't_header'); - push @t_header, $hash; - } - - if ( $wanted{innodb_transactions} ) { - my $cur_txns = $set->{IB_tx_transactions}; - my $pre_txns = $pre->{IB_tx_transactions} || $cur_txns; - my %cur_txns = map { $_->{mysql_thread_id} => $_ } @$cur_txns; - my %pre_txns = map { $_->{mysql_thread_id} => $_ } @$pre_txns; - foreach my $thd_id ( sort keys %cur_txns ) { - my $cur_txn = $cur_txns{$thd_id}; - my $pre_txn = $pre_txns{$thd_id} || $cur_txn; - my $hash = extract_values($cur_txn, $cur_txn, $pre_txn, 'innodb_transactions'); - $hash->{cxn} = $cxn; - push @innodb_transactions, $hash; - } - } - - } - - my $first_table = 0; - foreach my $tbl ( @visible ) { - push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl); - push @display_lines, get_cxn_errors(@cxns) - if ( $config{debug}->{val} || !$first_table++ ); - } - - # Save queries in global variable for analysis. The rows in %rows_for have been - # filtered, etc as a side effect of set_to_tbl(), so they are the same as the rows - # that get pushed to the screen. - @current_queries = map { - my %hash; - @hash{ qw(cxn id db query secs) } = @{$_}{ qw(cxn mysql_thread_id db query_text active_secs) }; - \%hash; - } @{$rows_for{innodb_transactions}}; - - draw_screen(\@display_lines); -} - -# display_S {{{3 -sub display_S { - my $fmt = get_var_set('S_set'); - my $func = $config{S_func}->{val}; - my $inc = $func eq 'g' || $config{status_inc}->{val}; - - # The table's meta-data is generated from the compiled var_set. - my ( $cols, $visible ); - if ( $tbl_meta{var_status}->{fmt} && $fmt eq $tbl_meta{var_status}->{fmt} ) { - ( $cols, $visible ) = @{$tbl_meta{var_status}}{qw(cols visible)}; - } - else { - ( $cols, $visible ) = compile_select_stmt($fmt); - - # Apply missing values to columns. Always apply averages across all connections. - map { - $_->{agg} = 'avg'; - $_->{label} = $_->{hdr}; - } values %$cols; - - $tbl_meta{var_status}->{cols} = $cols; - $tbl_meta{var_status}->{visible} = $visible; - $tbl_meta{var_status}->{fmt} = $fmt; - map { $tbl_meta{var_status}->{cols}->{$_}->{just} = ''} @$visible; - } - - my @var_status; - my %rows_for = ( - var_status => \@var_status, - ); - - my @visible = get_visible_tables(); - my %wanted = map { $_ => 1 } @visible; - my @cxns = get_connections(); - - get_status_info(@cxns); - get_innodb_status(\@cxns); - - # Set up whether to pivot and how many sets to extract. - $tbl_meta{var_status}->{pivot} = $func eq 'v'; - - my $num_sets - = $func eq 'v' - ? $config{num_status_sets}->{val} - : 0; - foreach my $set ( 0 .. $num_sets ) { - my @rows; - foreach my $cxn ( @cxns ) { - my $vars = $inc ? inc($set, $cxn) : $vars{$cxn}->{$clock - $set}; - my $cur = $vars{$cxn}->{$clock-$set}; - my $pre = $vars{$cxn}->{$clock-$set-1} || $cur; - next unless $vars && %$vars; - my $hash = extract_values($vars, $cur, $pre, 'var_status'); - push @rows, $hash; - } - @rows = apply_group_by('var_status', [], @rows); - push @var_status, @rows; - } - - # Recompile the sort func. TODO: avoid recompiling at every refresh. - # Figure out whether the data is all numeric and decide on a sort type. - # my $cmp - # = scalar( - # grep { !defined $_ || $_ !~ m/^\d+$/ } - # map { my $col = $_; map { $_->{$col} } @var_status } - # $tbl_meta{var_status}->{sort_cols} =~ m/(\w+)/g) - # ? 'cmp' - # : '<=>'; - $tbl_meta{var_status}->{sort_func} = make_sort_func($tbl_meta{var_status}); - - # ################################################################ - # Now there is specific display code based on $config{S_func} - # ################################################################ - if ( $func =~ m/s|g/ ) { - my $min_width = 4; - - # Clear the screen if the display width changed. - if ( @last_term_size && $this_term_size[0] != $last_term_size[0] ) { - $lines_printed = 0; - $clear_screen_sub->(); - } - - if ( $func eq 's' ) { - # Decide how wide columns should be. - my $num_cols = scalar(@$visible); - my $width = $opts{n} ? 0 : max($min_width, int(($this_term_size[0] - $num_cols + 1) / $num_cols)); - my $g_format = $opts{n} ? ( "%s\t" x $num_cols ) : ( "%-${width}s " x $num_cols ); - - # Print headers every now and then. Headers can get really long, so compact them. - my @hdr = @$visible; - if ( $opts{n} ) { - if ( $lines_printed == 0 ) { - print join("\t", @hdr), "\n"; - $lines_printed++; - } - } - elsif ( $lines_printed == 0 || $lines_printed > $this_term_size[1] - 2 ) { - @hdr = map { donut(crunch($_, $width), $width) } @hdr; - print join(' ', map { sprintf( "%${width}s", donut($_, $width)) } @hdr) . "\n"; - $lines_printed = 1; - } - - # Design a column format for the values. - my $format - = $opts{n} - ? join("\t", map { '%s' } @$visible) . "\n" - : join(' ', map { "%${width}s" } @hdr) . "\n"; - - foreach my $row ( @var_status ) { - printf($format, map { defined $_ ? $_ : '' } @{$row}{ @$visible }); - $lines_printed++; - } - } - else { # 'g' mode - # Design a column format for the values. - my $num_cols = scalar(@$visible); - my $width = $opts{n} ? 0 : int(($this_term_size[0] - $num_cols + 1) / $num_cols); - my $format = $opts{n} ? ( "%s\t" x $num_cols ) : ( "%-${width}s " x $num_cols ); - $format =~ s/\s$/\n/; - - # Print headers every now and then. - if ( $opts{n} ) { - if ( $lines_printed == 0 ) { - print join("\t", @$visible), "\n"; - print join("\t", map { shorten($mvs{$_}) } @$visible), "\n"; - } - } - elsif ( $lines_printed == 0 || $lines_printed > $this_term_size[1] - 2 ) { - printf($format, map { donut(crunch($_, $width), $width) } @$visible); - printf($format, map { shorten($mvs{$_} || 0) } @$visible); - $lines_printed = 2; - } - - # Update the max ever seen, and scale by the max ever seen. - my $set = $var_status[0]; - foreach my $col ( @$visible ) { - $set->{$col} = 1 unless defined $set->{$col} && $set->{$col} =~ m/$num_regex/; - $set->{$col} = ($set->{$col} || 1) / ($set->{Uptime_hires} || 1); - $mvs{$col} = max($mvs{$col} || 1, $set->{$col}); - $set->{$col} /= $mvs{$col}; - } - printf($format, map { ( $config{graph_char}->{val} x int( $width * $set->{$_} )) || '.' } @$visible ); - $lines_printed++; - - } - } - else { # 'v' - my $first_table = 0; - my @display_lines; - foreach my $tbl ( @visible ) { - push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl); - push @display_lines, get_cxn_errors(@cxns) - if ( $config{debug}->{val} || !$first_table++ ); - } - $clear_screen_sub->(); - draw_screen( \@display_lines ); - } -} - -# display_explain {{{3 -sub display_explain { - my $info = shift; - my $cxn = $info->{cxn}; - my $db = $info->{db}; - - my ( $mods, $query ) = rewrite_for_explain($info->{query}); - - my @display_lines; - - if ( $query ) { - - my $part = version_ge($dbhs{$cxn}->{dbh}, '5.1.5') ? 'PARTITIONS' : ''; - $query = "EXPLAIN $part\n" . $query; - - eval { - if ( $db ) { - do_query($cxn, "use $db"); - } - my $sth = do_query($cxn, $query); - - my $res; - while ( $res = $sth->fetchrow_hashref() ) { - map { $res->{$_} ||= '' } ( 'partitions', keys %$res); - my @this_table = create_caption("Sub-Part $res->{id}", - create_table2( - $tbl_meta{explain}->{visible}, - meta_to_hdr('explain'), - extract_values($res, $res, $res, 'explain'))); - @display_lines = stack_next(\@display_lines, \@this_table, { pad => ' ', vsep => 2 }); - } - }; - - if ( $EVAL_ERROR ) { - push @display_lines, - '', - "The query could not be explained. Only SELECT queries can be " - . "explained; innotop tries to rewrite certain REPLACE and INSERT queries " - . "into SELECT, but this doesn't always succeed."; - } - - } - else { - push @display_lines, '', 'The query could not be explained.'; - } - - if ( $mods ) { - push @display_lines, '', '[This query has been re-written to be explainable]'; - } - - unshift @display_lines, no_ctrl_char($query); - draw_screen(\@display_lines, { raw => 1 } ); -} - -# rewrite_for_explain {{{3 -sub rewrite_for_explain { - my $query = shift; - - my $mods = 0; - my $orig = $query; - $mods += $query =~ s/^\s*(?:replace|insert).*?select/select/is; - $mods += $query =~ s/^ - \s*create\s+(?:temporary\s+)?table - \s+(?:\S+\s+)as\s+select/select/xis; - $mods += $query =~ s/\s+on\s+duplicate\s+key\s+update.*$//is; - return ( $mods, $query ); -} - -# show_optimized_query {{{3 -sub show_optimized_query { - my $info = shift; - my $cxn = $info->{cxn}; - my $db = $info->{db}; - my $meta = $dbhs{$cxn}; - - my @display_lines; - - my ( $mods, $query ) = rewrite_for_explain($info->{query}); - - if ( $mods ) { - push @display_lines, '[This query has been re-written to be explainable]'; - } - - if ( $query ) { - push @display_lines, no_ctrl_char($info->{query}); - - eval { - if ( $db ) { - do_query($cxn, "use $db"); - } - do_query( $cxn, 'EXPLAIN EXTENDED ' . $query ) or die "Can't explain query"; - my $sth = do_query($cxn, 'SHOW WARNINGS'); - my $res = $sth->fetchall_arrayref({}); - - if ( $res ) { - foreach my $result ( @$res ) { - push @display_lines, 'Note:', no_ctrl_char($result->{message}); - } - } - else { - push @display_lines, '', 'The query optimization could not be generated.'; - } - }; - - if ( $EVAL_ERROR ) { - push @display_lines, '', "The optimization could not be generated: $EVAL_ERROR"; - } - - } - else { - push @display_lines, '', 'The query optimization could not be generated.'; - } - - draw_screen(\@display_lines, { raw => 1 } ); -} - -# display_help {{{3 -sub display_help { - my $mode = $config{mode}->{val}; - - # Get globally mapped keys, then overwrite them with mode-specific ones. - my %keys = map { - $_ => $action_for{$_}->{label} - } keys %action_for; - foreach my $key ( keys %{$modes{$mode}->{action_for}} ) { - $keys{$key} = $modes{$mode}->{action_for}->{$key}->{label}; - } - delete $keys{'?'}; - - # Split them into three kinds of keys: MODE keys, action keys, and - # magic (special character) keys. - my @modes = sort grep { m/[A-Z]/ } keys %keys; - my @actions = sort grep { m/[a-z]/ } keys %keys; - my @magic = sort grep { m/[^A-Z]/i } keys %keys; - - my @display_lines = ( '', 'Switch to a different mode:' ); - - # Mode keys - my @all_modes = map { "$_ $modes{$_}->{hdr}" } @modes; - my @col1 = splice(@all_modes, 0, ceil(@all_modes/3)); - my @col2 = splice(@all_modes, 0, ceil(@all_modes/2)); - my $max1 = max(map {length($_)} @col1); - my $max2 = max(map {length($_)} @col2); - while ( @col1 ) { - push @display_lines, sprintf(" %-${max1}s %-${max2}s %s", - (shift @col1 || ''), - (shift @col2 || ''), - (shift @all_modes || '')); - } - - # Action keys - my @all_actions = map { "$_ $keys{$_}" } @actions; - @col1 = splice(@all_actions, 0, ceil(@all_actions/2)); - $max1 = max(map {length($_)} @col1); - push @display_lines, '', 'Actions:'; - while ( @col1 ) { - push @display_lines, sprintf(" %-${max1}s %s", - (shift @col1 || ''), - (shift @all_actions || '')); - } - - # Magic keys - my @all_magic = map { sprintf('%4s', $action_for{$_}->{key} || $_) . " $keys{$_}" } @magic; - @col1 = splice(@all_magic, 0, ceil(@all_magic/2)); - $max1 = max(map {length($_)} @col1); - push @display_lines, '', 'Other:'; - while ( @col1 ) { - push @display_lines, sprintf("%-${max1}s%s", - (shift @col1 || ''), - (shift @all_magic || '')); - } - - $clear_screen_sub->(); - draw_screen(\@display_lines, { show_all => 1 } ); - pause(); - $clear_screen_sub->(); -} - -# show_full_query {{{3 -sub show_full_query { - my $info = shift; - my @display_lines = no_ctrl_char($info->{query}); - draw_screen(\@display_lines, { raw => 1 }); -} - -# Formatting functions {{{2 - -# create_table2 {{{3 -# Makes a two-column table, labels on left, data on right. -# Takes refs of @cols, %labels and %data, %user_prefs -sub create_table2 { - my ( $cols, $labels, $data, $user_prefs ) = @_; - my @rows; - - if ( @$cols && %$data ) { - - # Override defaults - my $p = { - just => '', - sep => ':', - just1 => '-', - }; - if ( $user_prefs ) { - map { $p->{$_} = $user_prefs->{$_} } keys %$user_prefs; - } - - # Fix undef values - map { $data->{$_} = '' unless defined $data->{$_} } @$cols; - - # Format the table - my $max_l = max(map{ length($labels->{$_}) } @$cols); - my $max_v = max(map{ length($data->{$_}) } @$cols); - my $format = "%$p->{just}${max_l}s$p->{sep} %$p->{just1}${max_v}s"; - foreach my $col ( @$cols ) { - push @rows, sprintf($format, $labels->{$col}, $data->{$col}); - } - } - return @rows; -} - -# stack_next {{{3 -# Stacks one display section next to the other. Accepts left-hand arrayref, -# right-hand arrayref, and options hashref. Tries to stack as high as -# possible, so -# aaaaaa -# bbb -# can stack ccc next to the bbb. -# NOTE: this DOES modify its arguments, even though it returns a new array. -sub stack_next { - my ( $left, $right, $user_prefs ) = @_; - my @result; - - my $p = { - pad => ' ', - vsep => 0, - }; - if ( $user_prefs ) { - map { $p->{$_} = $user_prefs->{$_} } keys %$user_prefs; - } - - # Find out how wide the LHS can be and still let the RHS fit next to it. - my $pad = $p->{pad}; - my $max_r = max( map { length($_) } @$right) || 0; - my $max_l = $this_term_size[0] - $max_r - length($pad); - - # Find the minimum row on the LHS that the RHS will fit next to. - my $i = scalar(@$left) - 1; - while ( $i >= 0 && length($left->[$i]) <= $max_l ) { - $i--; - } - $i++; - my $offset = $i; - - if ( $i < scalar(@$left) ) { - # Find the max width of the section of the LHS against which the RHS - # will sit. - my $max_i_in_common = min($i + scalar(@$right) - 1, scalar(@$left) - 1); - my $max_width = max( map { length($_) } @{$left}[$i..$max_i_in_common]); - - # Append the RHS onto the LHS until one runs out. - while ( $i < @$left && $i - $offset < @$right ) { - my $format = "%-${max_width}s$pad%${max_r}s"; - $left->[$i] = sprintf($format, $left->[$i], $right->[$i - $offset]); - $i++; - } - while ( $i - $offset < @$right ) { - # There is more RHS to push on the end of the array - push @$left, - sprintf("%${max_width}s$pad%${max_r}s", ' ', $right->[$i - $offset]); - $i++; - } - push @result, @$left; - } - else { - # There is no room to put them side by side. Add them below, with - # a blank line above them if specified. - push @result, @$left; - push @result, (' ' x $this_term_size[0]) if $p->{vsep} && @$left; - push @result, @$right; - } - return @result; -} - -# create_caption {{{3 -sub create_caption { - my ( $caption, @rows ) = @_; - if ( @rows ) { - - # Calculate the width of what will be displayed, so it can be centered - # in that space. When the thing is wider than the display, center the - # caption in the display. - my $width = min($this_term_size[0], max(map { length(ref($_) ? $_->[0] : $_) } @rows)); - - my $cap_len = length($caption); - - # It may be narrow enough to pad the sides with underscores and save a - # line on the screen. - if ( $cap_len <= $width - 6 ) { - my $left = int(($width - 2 - $cap_len) / 2); - unshift @rows, - ("_" x $left) . " $caption " . ("_" x ($width - $left - $cap_len - 2)); - } - - # The caption is too wide to add underscores on each side. - else { - - # Color is supported, so we can use terminal underlining. - if ( $config{color}->{val} ) { - my $left = int(($width - $cap_len) / 2); - unshift @rows, [ - (" " x $left) . $caption . (" " x ($width - $left - $cap_len)), - 'underline', - ]; - } - - # Color is not supported, so we have to add a line underneath to separate the - # caption from whatever it's captioning. - else { - my $left = int(($width - $cap_len) / 2); - unshift @rows, ('-' x $width); - unshift @rows, (" " x $left) . $caption . (" " x ($width - $left - $cap_len)); - } - - # The caption is wider than the thing it labels, so we have to pad the - # thing it labels to a consistent width. - if ( $cap_len > $width ) { - @rows = map { - ref($_) - ? [ sprintf('%-' . $cap_len . 's', $_->[0]), $_->[1] ] - : sprintf('%-' . $cap_len . 's', $_); - } @rows; - } - - } - } - return @rows; -} - -# create_table {{{3 -# Input: an arrayref of columns, hashref of col info, and an arrayref of hashes -# Example: [ 'a', 'b' ] -# { a => spec, b => spec } -# [ { a => 1, b => 2}, { a => 3, b => 4 } ] -# The 'spec' is a hashref of hdr => label, just => ('-' or ''). It also supports min and max-widths -# vi the minw and maxw params. -# Output: an array of strings, one per row. -# Example: -# Column One Column Two -# ---------- ---------- -# 1 2 -# 3 4 -sub create_table { - my ( $cols, $info, $data, $prefs ) = @_; - $prefs ||= {}; - $prefs->{no_hdr} ||= ($opts{n} && $clock != 1); - - # Truncate rows that will surely be off screen even if this is the only table. - if ( !$opts{n} && !$prefs->{raw} && !$prefs->{show_all} && $this_term_size[1] < @$data-1 ) { - $data = [ @$data[0..$this_term_size[1] - 1] ]; - } - - my @rows = (); - - if ( @$cols && %$info ) { - - # Fix undef values, collapse whitespace. - foreach my $row ( @$data ) { - map { $row->{$_} = collapse_ws($row->{$_}) } @$cols; - } - - my $col_sep = $opts{n} ? "\t" : ' '; - - # Find each column's max width. - my %width_for; - if ( !$opts{n} ) { - %width_for = map { - my $col_name = $_; - if ( $info->{$_}->{dec} ) { - # Align along the decimal point - my $max_rodp = max(0, map { $_->{$col_name} =~ m/([^\s\d-].*)$/ ? length($1) : 0 } @$data); - foreach my $row ( @$data ) { - my $col = $row->{$col_name}; - my ( $l, $r ) = $col =~ m/^([\s\d]*)(.*)$/; - $row->{$col_name} = sprintf("%s%-${max_rodp}s", $l, $r); - } - } - my $max_width = max( length($info->{$_}->{hdr}), map { length($_->{$col_name}) } @$data); - if ( $info->{$col_name}->{maxw} ) { - $max_width = min( $max_width, $info->{$col_name}->{maxw} ); - } - if ( $info->{$col_name}->{minw} ) { - $max_width = max( $max_width, $info->{$col_name}->{minw} ); - } - $col_name => $max_width; - } @$cols; - } - - # The table header. - if ( !$config{hide_hdr}->{val} && !$prefs->{no_hdr} ) { - push @rows, $opts{n} - ? join( $col_sep, @$cols ) - : join( $col_sep, map { sprintf( "%-$width_for{$_}s", trunc($info->{$_}->{hdr}, $width_for{$_}) ) } @$cols ); - if ( $config{color}->{val} && $config{header_highlight}->{val} ) { - push @rows, [ pop @rows, $config{header_highlight}->{val} ]; - } - elsif ( !$opts{n} ) { - push @rows, join( $col_sep, map { "-" x $width_for{$_} } @$cols ); - } - } - - # The table data. - if ( $opts{n} ) { - foreach my $item ( @$data ) { - push @rows, join($col_sep, map { $item->{$_} } @$cols ); - } - } - else { - my $format = join( $col_sep, - map { "%$info->{$_}->{just}$width_for{$_}s" } @$cols ); - foreach my $item ( @$data ) { - my $row = sprintf($format, map { trunc($item->{$_}, $width_for{$_}) } @$cols ); - if ( $config{color}->{val} && $item->{_color} ) { - push @rows, [ $row, $item->{_color} ]; - } - else { - push @rows, $row; - } - } - } - } - - return @rows; -} - -# Aggregates a table. If $group_by is an arrayref of columns, the grouping key -# is the specified columns; otherwise it's just the empty string (e.g. -# everything is grouped as one group). -sub apply_group_by { - my ( $tbl, $group_by, @rows ) = @_; - my $meta = $tbl_meta{$tbl}; - my %is_group = map { $_ => 1 } @$group_by; - my @non_grp = grep { !$is_group{$_} } keys %{$meta->{cols}}; - - my %temp_table; - foreach my $row ( @rows ) { - my $group_key - = @$group_by - ? '{' . join('}{', map { defined $_ ? $_ : '' } @{$row}{@$group_by}) . '}' - : ''; - $temp_table{$group_key} ||= []; - push @{$temp_table{$group_key}}, $row; - } - - # Crush the rows together... - my @new_rows; - foreach my $key ( sort keys %temp_table ) { - my $group = $temp_table{$key}; - my %new_row; - @new_row{@$group_by} = @{$group->[0]}{@$group_by}; - foreach my $col ( @non_grp ) { - my $agg = $meta->{cols}->{$col}->{agg} || 'first'; - $new_row{$col} = $agg_funcs{$agg}->( map { $_->{$col} } @$group ); - } - push @new_rows, \%new_row; - } - return @new_rows; -} - -# set_to_tbl {{{3 -# Unifies all the work of filtering, sorting etc. Alters the input. -# TODO: pull all the little pieces out into subroutines and stick events in each of them. -sub set_to_tbl { - my ( $rows, $tbl ) = @_; - my $meta = $tbl_meta{$tbl} or die "No such table $tbl in tbl_meta"; - - if ( !$meta->{pivot} ) { - - # Hook in event listeners - foreach my $listener ( @{$event_listener_for{set_to_tbl_pre_filter}} ) { - $listener->set_to_tbl_pre_filter($rows, $tbl); - } - - # Apply filters. Note that if the table is pivoted, filtering and sorting - # are applied later. - foreach my $filter ( @{$meta->{filters}} ) { - eval { - @$rows = grep { $filters{$filter}->{func}->($_) } @$rows; - }; - if ( $EVAL_ERROR && $config{debug}->{val} ) { - die $EVAL_ERROR; - } - } - - foreach my $listener ( @{$event_listener_for{set_to_tbl_pre_sort}} ) { - $listener->set_to_tbl_pre_sort($rows, $tbl); - } - - # Sort. Note that if the table is pivoted, sorting might have the wrong - # columns and it could crash. This will only be an issue if it's possible - # to toggle pivoting on and off, which it's not at the moment. - if ( @$rows && $meta->{sort_func} && !$meta->{aggregate} ) { - if ( $meta->{sort_dir} > 0 ) { - @$rows = $meta->{sort_func}->( @$rows ); - } - else { - @$rows = reverse $meta->{sort_func}->( @$rows ); - } - } - - } - - # Stop altering arguments now. - my @rows = @$rows; - - foreach my $listener ( @{$event_listener_for{set_to_tbl_pre_group}} ) { - $listener->set_to_tbl_pre_group(\@rows, $tbl); - } - - # Apply group-by. - if ( $meta->{aggregate} ) { - @rows = apply_group_by($tbl, $meta->{group_by}, @rows); - - # Sort. Note that if the table is pivoted, sorting might have the wrong - # columns and it could crash. This will only be an issue if it's possible - # to toggle pivoting on and off, which it's not at the moment. - if ( @rows && $meta->{sort_func} ) { - if ( $meta->{sort_dir} > 0 ) { - @rows = $meta->{sort_func}->( @rows ); - } - else { - @rows = reverse $meta->{sort_func}->( @rows ); - } - } - - } - - foreach my $listener ( @{$event_listener_for{set_to_tbl_pre_colorize}} ) { - $listener->set_to_tbl_pre_colorize(\@rows, $tbl); - } - - if ( !$meta->{pivot} ) { - # Colorize. Adds a _color column to rows. - if ( @rows && $meta->{color_func} ) { - eval { - foreach my $row ( @rows ) { - $row->{_color} = $meta->{color_func}->($row); - } - }; - if ( $EVAL_ERROR ) { - pause($EVAL_ERROR); - } - } - } - - foreach my $listener ( @{$event_listener_for{set_to_tbl_pre_transform}} ) { - $listener->set_to_tbl_pre_transform(\@rows, $tbl); - } - - # Apply_transformations. - if ( @rows ) { - my $cols = $meta->{cols}; - foreach my $col ( keys %{$rows->[0]} ) { - # Don't auto-vivify $tbl_meta{tbl}-{cols}->{_color}->{trans} - next if $col eq '_color'; - foreach my $trans ( @{$cols->{$col}->{trans}} ) { - map { $_->{$col} = $trans_funcs{$trans}->($_->{$col}) } @rows; - } - } - } - - my ($fmt_cols, $fmt_meta); - - # Pivot. - if ( $meta->{pivot} ) { - - foreach my $listener ( @{$event_listener_for{set_to_tbl_pre_pivot}} ) { - $listener->set_to_tbl_pre_pivot(\@rows, $tbl); - } - - my @vars = @{$meta->{visible}}; - my @tmp = map { { name => $_ } } @vars; - my @cols = 'name'; - foreach my $i ( 0..@$rows-1 ) { - my $col = "set_$i"; - push @cols, $col; - foreach my $j ( 0..@vars-1 ) { - $tmp[$j]->{$col} = $rows[$i]->{$vars[$j]}; - } - } - $fmt_meta = { map { $_ => { hdr => $_, just => '-' } } @cols }; - $fmt_cols = \@cols; - @rows = @tmp; - - # Hook in event listeners - foreach my $listener ( @{$event_listener_for{set_to_tbl_pre_filter}} ) { - $listener->set_to_tbl_pre_filter($rows, $tbl); - } - - # Apply filters. - foreach my $filter ( @{$meta->{filters}} ) { - eval { - @rows = grep { $filters{$filter}->{func}->($_) } @rows; - }; - if ( $EVAL_ERROR && $config{debug}->{val} ) { - die $EVAL_ERROR; - } - } - - foreach my $listener ( @{$event_listener_for{set_to_tbl_pre_sort}} ) { - $listener->set_to_tbl_pre_sort($rows, $tbl); - } - - # Sort. - if ( @rows && $meta->{sort_func} ) { - if ( $meta->{sort_dir} > 0 ) { - @rows = $meta->{sort_func}->( @rows ); - } - else { - @rows = reverse $meta->{sort_func}->( @rows ); - } - } - - } - else { - # If the table isn't pivoted, just show all columns that are supposed to - # be shown; but eliminate aggonly columns if the table isn't aggregated. - my $aggregated = $meta->{aggregate}; - $fmt_cols = [ grep { $aggregated || !$meta->{cols}->{$_}->{aggonly} } @{$meta->{visible}} ]; - $fmt_meta = { map { $_ => $meta->{cols}->{$_} } @$fmt_cols }; - - # If the table is aggregated, re-order the group_by columns to the left of - # the display. - if ( $aggregated ) { - my %is_group = map { $_ => 1 } @{$meta->{group_by}}; - $fmt_cols = [ @{$meta->{group_by}}, grep { !$is_group{$_} } @$fmt_cols ]; - } - } - - foreach my $listener ( @{$event_listener_for{set_to_tbl_pre_create}} ) { - $listener->set_to_tbl_pre_create(\@rows, $tbl); - } - - @rows = create_table( $fmt_cols, $fmt_meta, \@rows); - if ( !$meta->{hide_caption} && !$opts{n} && $config{display_table_captions}->{val} ) { - @rows = create_caption($meta->{capt}, @rows) - } - - foreach my $listener ( @{$event_listener_for{set_to_tbl_post_create}} ) { - $listener->set_to_tbl_post_create(\@rows, $tbl); - } - - return @rows; -} - -# meta_to_hdr {{{3 -sub meta_to_hdr { - my $tbl = shift; - my $meta = $tbl_meta{$tbl}; - my %labels = map { $_ => $meta->{cols}->{$_}->{hdr} } @{$meta->{visible}}; - return \%labels; -} - -# commify {{{3 -# From perlfaq5: add commas. -sub commify { - my ( $num ) = @_; - $num = 0 unless defined $num; - $num =~ s/(^[-+]?\d+?(?=(?>(?:\d{3})+)(?!\d))|\G\d{3}(?=\d))/$1,/g; - return $num; -} - -# set_precision {{{3 -# Trim to desired precision. -sub set_precision { - my ( $num, $precision ) = @_; - $precision = $config{num_digits}->{val} if !defined $precision; - sprintf("%.${precision}f", $num); -} - -# percent {{{3 -# Convert to percent -sub percent { - my ( $num ) = @_; - $num = 0 unless defined $num; - my $digits = $config{num_digits}->{val}; - return sprintf("%.${digits}f", $num * 100) - . ($config{show_percent}->{val} ? '%' : ''); -} - -# shorten {{{3 -sub shorten { - my ( $num, $opts ) = @_; - - return $num if !defined($num) || $opts{n} || $num !~ m/$num_regex/; - - $opts ||= {}; - my $pad = defined $opts->{pad} ? $opts->{pad} : ''; - my $num_digits = defined $opts->{num_digits} - ? $opts->{num_digits} - : $config{num_digits}->{val}; - my $force = defined $opts->{force}; - - my $n = 0; - while ( $num >= 1_024 ) { - $num /= 1_024; - ++$n; - } - return sprintf( - $num =~ m/\./ || $n || $force - ? "%.${num_digits}f%s" - : '%d', - $num, ($pad,'k','M','G', 'T')[$n]); - -} - -# Utility functions {{{2 -# unique {{{3 -sub unique { - my %seen; - return grep { !$seen{$_}++ } @_; -} - -# make_color_func {{{3 -sub make_color_func { - my ( $tbl ) = @_; - my @criteria; - foreach my $spec ( @{$tbl->{colors}} ) { - next unless exists $comp_ops{$spec->{op}}; - my $val = $spec->{op} =~ m/^(?:eq|ne|le|ge|lt|gt)$/ ? "'$spec->{arg}'" - : $spec->{op} =~ m/^(?:=~|!~)$/ ? "m/" . quotemeta($spec->{arg}) . "/" - : $spec->{arg}; - push @criteria, - "( defined \$set->{$spec->{col}} && \$set->{$spec->{col}} $spec->{op} $val ) { return '$spec->{color}'; }"; - } - return undef unless @criteria; - my $sub = eval 'sub { my ( $set ) = @_; if ' . join(" elsif ", @criteria) . '}'; - die if $EVAL_ERROR; - return $sub; -} - -# make_sort_func {{{3 -# Gets a list of sort columns from the table, like "+cxn -time" and returns a -# subroutine that will sort that way. -sub make_sort_func { - my ( $tbl ) = @_; - my @criteria; - - # Pivoted tables can be sorted by 'name' and set_x columns; others must be - # sorted by existing columns. TODO: this will crash if you toggle between - # pivoted and nonpivoted. I have several other 'crash' notes about this if - # this ever becomes possible. - - if ( $tbl->{pivot} ) { - # Sort type is not really possible on pivoted columns, because a 'column' - # contains data from an entire non-pivoted row, so there could be a mix of - # numeric and non-numeric data. Thus everything has to be 'cmp' type. - foreach my $col ( split(/\s+/, $tbl->{sort_cols} ) ) { - next unless $col; - my ( $dir, $name ) = $col =~ m/([+-])?(\w+)$/; - next unless $name && $name =~ m/^(?:name|set_\d+)$/; - $dir ||= '+'; - my $op = 'cmp'; - my $df = "''"; - push @criteria, - $dir eq '+' - ? "(\$a->{$name} || $df) $op (\$b->{$name} || $df)" - : "(\$b->{$name} || $df) $op (\$a->{$name} || $df)"; - } - } - else { - foreach my $col ( split(/\s+/, $tbl->{sort_cols} ) ) { - next unless $col; - my ( $dir, $name ) = $col =~ m/([+-])?(\w+)$/; - next unless $name && $tbl->{cols}->{$name}; - $dir ||= '+'; - my $op = $tbl->{cols}->{$name}->{num} ? "<=>" : "cmp"; - my $df = $tbl->{cols}->{$name}->{num} ? "0" : "''"; - push @criteria, - $dir eq '+' - ? "(\$a->{$name} || $df) $op (\$b->{$name} || $df)" - : "(\$b->{$name} || $df) $op (\$a->{$name} || $df)"; - } - } - return sub { return @_ } unless @criteria; - my $sub = eval 'sub { sort {' . join("||", @criteria) . '} @_; }'; - die if $EVAL_ERROR; - return $sub; -} - -# trunc {{{3 -# Shortens text to specified length. -sub trunc { - my ( $text, $len ) = @_; - if ( length($text) <= $len ) { - return $text; - } - return substr($text, 0, $len); -} - -# donut {{{3 -# Takes out the middle of text to shorten it. -sub donut { - my ( $text, $len ) = @_; - return $text if length($text) <= $len; - my $max = length($text) - $len; - my $min = $max - 1; - - # Try to remove a single "word" from somewhere in the center - if ( $text =~ s/_[^_]{$min,$max}_/_/ ) { - return $text; - } - - # Prefer removing the end of a "word" - if ( $text =~ s/([^_]+)[^_]{$max}_/$1_/ ) { - return $text; - } - - $text = substr($text, 0, int($len/2)) - . "_" - . substr($text, int($len/2) + $max + 1); - return $text; -} - -# crunch {{{3 -# Removes vowels and compacts repeated letters to shorten text. -sub crunch { - my ( $text, $len ) = @_; - return $text if $len && length($text) <= $len; - $text =~ s/^IB_\w\w_//; - $text =~ s/(?<![_ ])[aeiou]//g; - $text =~ s/(.)\1+/$1/g; - return $text; -} - -# collapse_ws {{{3 -# Collapses all whitespace to a single space. -sub collapse_ws { - my ( $text ) = @_; - return '' unless defined $text; - $text =~ s/\s+/ /g; - return $text; -} - -# Strips out non-printable characters within fields, which freak terminals out. -sub no_ctrl_char { - my ( $text ) = @_; - return '' unless defined $text; - my $charset = $config{charset}->{val}; - if ( $charset && $charset eq 'unicode' ) { - $text =~ s/ - ("(?:(?!(?<!\\)").)*" # Double-quoted string - |'(?:(?!(?<!\\)').)*') # Or single-quoted string - /$1 =~ m#\p{IsC}# ? "[BINARY]" : $1/egx; - } - elsif ( $charset && $charset eq 'none' ) { - $text =~ s/ - ("(?:(?!(?<!\\)").)*" - |'(?:(?!(?<!\\)').)*') - /[TEXT]/gx; - } - else { # The default is 'ascii' - $text =~ s/ - ("(?:(?!(?<!\\)").)*" - |'(?:(?!(?<!\\)').)*') - /$1 =~ m#[^\040-\176]# ? "[BINARY]" : $1/egx; - } - return $text; -} - -# word_wrap {{{3 -# Wraps text at word boundaries so it fits the screen. -sub word_wrap { - my ( $text, $width) = @_; - $width ||= $this_term_size[0]; - $text =~ s/(.{0,$width})(?:\s+|$)/$1\n/g; - $text =~ s/ +$//mg; - return $text; -} - -# draw_screen {{{3 -# Prints lines to the screen. The first argument is an arrayref. Each -# element of the array is either a string or an arrayref. If it's a string it -# just gets printed. If it's an arrayref, the first element is the string to -# print, and the second is args to colored(). -sub draw_screen { - my ( $display_lines, $prefs ) = @_; - if ( !$opts{n} && $config{show_statusbar}->{val} ) { - unshift @$display_lines, create_statusbar(); - } - - foreach my $listener ( @{$event_listener_for{draw_screen}} ) { - $listener->draw_screen($display_lines); - } - - $clear_screen_sub->() - if $prefs->{clear} || !$modes{$config{mode}->{val}}->{no_clear_screen}; - if ( $opts{n} || $prefs->{raw} ) { - my $num_lines = 0; - print join("\n", - map { - $num_lines++; - ref $_ - ? colored($_->[0], $_->[1]) - : $_; - } - grep { !$opts{n} || $_ } # Suppress empty lines - @$display_lines); - if ( $opts{n} && $num_lines ) { - print "\n"; - } - } - else { - my $max_lines = $prefs->{show_all} - ? scalar(@$display_lines)- 1 - : min(scalar(@$display_lines), $this_term_size[1]); - print join("\n", - map { - ref $_ - ? colored(substr($_->[0], 0, $this_term_size[0]), $_->[1]) - : substr($_, 0, $this_term_size[0]); - } @$display_lines[0..$max_lines - 1]); - } -} - -# secs_to_time {{{3 -sub secs_to_time { - my ( $secs, $fmt ) = @_; - $secs ||= 0; - return '00:00' unless $secs; - - # Decide what format to use, if not given - $fmt ||= $secs >= 86_400 ? 'd' - : $secs >= 3_600 ? 'h' - : 'm'; - - return - $fmt eq 'd' ? sprintf( - "%d+%02d:%02d:%02d", - int($secs / 86_400), - int(($secs % 86_400) / 3_600), - int(($secs % 3_600) / 60), - $secs % 60) - : $fmt eq 'h' ? sprintf( - "%02d:%02d:%02d", - int(($secs % 86_400) / 3_600), - int(($secs % 3_600) / 60), - $secs % 60) - : sprintf( - "%02d:%02d", - int(($secs % 3_600) / 60), - $secs % 60); -} - -# dulint_to_int {{{3 -# Takes a number that InnoDB formats as two ulint integers, like transaction IDs -# and such, and turns it into a single integer -sub dulint_to_int { - my $num = shift; - return 0 unless $num; - my ( $high, $low ) = $num =~ m/^(\d+) (\d+)$/; - return $low unless $high; - return $low + ( $high * $MAX_ULONG ); -} - -# create_statusbar {{{3 -sub create_statusbar { - my $mode = $config{mode}->{val}; - my @cxns = sort { $a cmp $b } get_connections(); - - my $modeline = ( $config{readonly}->{val} ? '[RO] ' : '' ) - . $modes{$mode}->{hdr} . " (? for help)"; - my $mode_width = length($modeline); - my $remaining_width = $this_term_size[0] - $mode_width - 1; - my $result; - - # The thingie in top-right that says what we're monitoring. - my $cxn = ''; - - if ( 1 == @cxns && $dbhs{$cxns[0]} && $dbhs{$cxns[0]}->{dbh} ) { - $cxn = $dbhs{$cxns[0]}->{dbh}->{mysql_serverinfo} || ''; - } - else { - if ( $modes{$mode}->{server_group} ) { - $cxn = "Servers: " . $modes{$mode}->{server_group}; - my $err_count = grep { $dbhs{$_} && $dbhs{$_}->{err_count} } @cxns; - if ( $err_count ) { - $cxn .= "(" . ( scalar(@cxns) - $err_count ) . "/" . scalar(@cxns) . ")"; - } - } - else { - $cxn = join(' ', map { ($dbhs{$_}->{err_count} ? '!' : '') . $_ } - grep { $dbhs{$_} } @cxns); - } - } - - if ( 1 == @cxns ) { - get_driver_status(@cxns); - my $vars = $vars{$cxns[0]}->{$clock}; - my $inc = inc(0, $cxns[0]); - - # Format server uptime human-readably, calculate QPS... - my $uptime = secs_to_time( $vars->{Uptime_hires} ); - my $qps = ($inc->{Questions}||0) / ($inc->{Uptime_hires}||1); - my $ibinfo = ''; - - if ( exists $vars->{IB_last_secs} ) { - $ibinfo .= "InnoDB $vars->{IB_last_secs}s "; - if ( $vars->{IB_got_all} ) { - if ( ($mode eq 'T' || $mode eq 'W') - && $vars->{IB_tx_is_truncated} ) { - $ibinfo .= ':^|'; - } - else { - $ibinfo .= ':-)'; - } - } - else { - $ibinfo .= ':-('; - } - } - $result = sprintf( - "%-${mode_width}s %${remaining_width}s", - $modeline, - join(', ', grep { $_ } ( - $cxns[0], - $uptime, - $ibinfo, - shorten($qps) . " QPS", - ($vars->{Threads} || 0) . " thd", - $cxn))); - } - else { - $result = sprintf( - "%-${mode_width}s %${remaining_width}s", - $modeline, - $cxn); - } - - return $config{color}->{val} ? [ $result, 'bold reverse' ] : $result; -} - -# Database connections {{{3 -sub add_new_dsn { - my ( $name ) = @_; - - if ( defined $name ) { - $name =~ s/[\s:;]//g; - } - - if ( !$name ) { - print word_wrap("Choose a name for the connection. It cannot contain " - . "whitespace, colons or semicolons."), "\n\n"; - do { - $name = prompt("Enter a name"); - $name =~ s/[\s:;]//g; - } until ( $name ); - } - - my $dsn; - do { - $clear_screen_sub->(); - print "Typical DSN strings look like\n DBI:mysql:;host=hostname;port=port\n" - . "The db and port are optional and can usually be omitted.\n" - . "If you specify 'mysql_read_default_group=mysql' many options can be read\n" - . "from your mysql options files (~/.my.cnf, /etc/my.cnf).\n\n"; - $dsn = prompt("Enter a DSN string", undef, "DBI:mysql:;mysql_read_default_group=mysql;host=$name"); - } until ( $dsn ); - - $clear_screen_sub->(); - my $dl_table = prompt("Optional: enter a table (must not exist) to use when resetting InnoDB deadlock information", - undef, 'test.innotop_dl'); - - $connections{$name} = { - dsn => $dsn, - dl_table => $dl_table, - }; -} - -sub add_new_server_group { - my ( $name ) = @_; - - if ( defined $name ) { - $name =~ s/[\s:;]//g; - } - - if ( !$name ) { - print word_wrap("Choose a name for the group. It cannot contain " - . "whitespace, colons or semicolons."), "\n\n"; - do { - $name = prompt("Enter a name"); - $name =~ s/[\s:;]//g; - } until ( $name ); - } - - my @cxns; - do { - $clear_screen_sub->(); - @cxns = select_cxn("Choose servers for $name", keys %connections); - } until ( @cxns ); - - $server_groups{$name} = \@cxns; - return $name; -} - -sub get_var_set { - my ( $name ) = @_; - while ( !$name || !exists($var_sets{$config{$name}->{val}}) ) { - $name = choose_var_set($name); - } - return $var_sets{$config{$name}->{val}}->{text}; -} - -sub add_new_var_set { - my ( $name ) = @_; - - if ( defined $name ) { - $name =~ s/\W//g; - } - - if ( !$name ) { - do { - $name = prompt("Enter a name"); - $name =~ s/\W//g; - } until ( $name ); - } - - my $variables; - do { - $clear_screen_sub->(); - $variables = prompt("Enter variables for $name", undef ); - } until ( $variables ); - - $var_sets{$name} = { text => $variables, user => 1 }; -} - -sub next_server { - my $mode = $config{mode}->{val}; - my @cxns = sort keys %connections; - my ($cur) = get_connections($mode); - $cur ||= $cxns[0]; - my $pos = grep { $_ lt $cur } @cxns; - my $newpos = ($pos + 1) % @cxns; - $modes{$mode}->{server_group} = ''; - $modes{$mode}->{connections} = [ $cxns[$newpos] ]; - $clear_screen_sub->(); -} - -sub next_server_group { - my $mode = shift || $config{mode}->{val}; - my @grps = sort keys %server_groups; - my $curr = $modes{$mode}->{server_group}; - - return unless @grps; - - if ( $curr ) { - # Find the current group's position. - my $pos = 0; - while ( $curr ne $grps[$pos] ) { - $pos++; - } - $modes{$mode}->{server_group} = $grps[ ($pos + 1) % @grps ]; - } - else { - $modes{$mode}->{server_group} = $grps[0]; - } -} - -# Get a list of connection names used in this mode. -sub get_connections { - if ( $file ) { - return qw(file); - } - my $mode = shift || $config{mode}->{val}; - my @connections = $modes{$mode}->{server_group} - ? @{$server_groups{$modes{$mode}->{server_group}}} - : @{$modes{$mode}->{connections}}; - if ( $modes{$mode}->{one_connection} ) { - @connections = @connections ? $connections[0] : (); - } - return unique(@connections); -} - -# Get a list of tables used in this mode. If innotop is running non-interactively, just use the first. -sub get_visible_tables { - my $mode = shift || $config{mode}->{val}; - my @tbls = @{$modes{$mode}->{visible_tables}}; - if ( $opts{n} ) { - return $tbls[0]; - } - else { - return @tbls; - } -} - -# Choose from among available connections or server groups. -# If the mode has a server set in use, prefers that instead. -sub choose_connections { - $clear_screen_sub->(); - my $mode = $config{mode}->{val}; - my $meta = { map { $_ => $connections{$_}->{dsn} } keys %connections }; - foreach my $group ( keys %server_groups ) { - $meta->{"#$group"} = join(' ', @{$server_groups{$group}}); - } - - my $choices = prompt_list("Choose connections or a group for $mode mode", - undef, sub { return keys %$meta }, $meta); - - my @choices = unique(grep { $_ } split(/\s+/, $choices)); - if ( @choices ) { - if ( $choices[0] =~ s/^#// && exists $server_groups{$choices[0]} ) { - $modes{$mode}->{server_group} = $choices[0]; - } - else { - $modes{$mode}->{connections} = [ grep { exists $connections{$_} } @choices ]; - } - } -} - -# Accepts a DB connection name and the name of a prepared query (e.g. status, kill). -# Also a list of params for the prepared query. This allows not storing prepared -# statements globally. Returns a $sth that's been executed. -# ERROR-HANDLING SEMANTICS: if the statement throws an error, propagate, but if the -# connection has gone away or can't connect, DO NOT. Just return undef. -sub do_stmt { - my ( $cxn, $stmt_name, @args ) = @_; - - return undef if $file; - - # Test if the cxn should not even be tried - return undef if $dbhs{$cxn} - && $dbhs{$cxn}->{err_count} - && ( !$dbhs{$cxn}->{dbh} || !$dbhs{$cxn}->{dbh}->{Active} || $dbhs{$cxn}->{mode} eq $config{mode}->{val} ) - && $dbhs{$cxn}->{wake_up} > $clock; - - my $sth; - my $retries = 1; - my $success = 0; - TRY: - while ( $retries-- >= 0 && !$success ) { - - eval { - my $dbh = connect_to_db($cxn); - - # If the prepared query doesn't exist, make it. - if ( !exists $dbhs{$cxn}->{stmts}->{$stmt_name} ) { - $dbhs{$cxn}->{stmts}->{$stmt_name} = $stmt_maker_for{$stmt_name}->($dbh); - } - - $sth = $dbhs{$cxn}->{stmts}->{$stmt_name}; - if ( $sth ) { - $sth->execute(@args); - } - $success = 1; - }; - if ( $EVAL_ERROR ) { - if ( $EVAL_ERROR =~ m/$nonfatal_errs/ ) { - handle_cxn_error($cxn, $EVAL_ERROR); - } - else { - die "$cxn $stmt_name: $EVAL_ERROR"; - } - if ( $retries < 0 ) { - $sth = undef; - } - } - } - - if ( $sth && $sth->{NUM_OF_FIELDS} ) { - sleep($stmt_sleep_time_for{$stmt_name}) if $stmt_sleep_time_for{$stmt_name}; - return $sth; - } -} - -# Keeps track of error count, sleep times till retries, etc etc. -# When there's an error we retry the connection every so often, increasing in -# Fibonacci series to prevent too much banging on the server. -sub handle_cxn_error { - my ( $cxn, $err ) = @_; - my $meta = $dbhs{$cxn}; - $meta->{err_count}++; - - # This is used so errors that have to do with permissions needed by the current - # mode will get displayed as long as we're in this mode, but get ignored if the - # mode changes. - $meta->{mode} = $config{mode}->{val}; - - # Strip garbage from the error text if possible. - $err =~ s/\s+/ /g; - if ( $err =~ m/failed: (.*?) at \S*innotop line/ ) { - $err = $1; - } - - $meta->{last_err} = $err; - my $sleep_time = $meta->{this_sleep} + $meta->{prev_sleep}; - $meta->{prev_sleep} = $meta->{this_sleep}; - $meta->{this_sleep} = $sleep_time; - $meta->{wake_up} = $clock + $sleep_time; - if ( $config{show_cxn_errors}->{val} ) { - print STDERR "Error at tick $clock $cxn $err" if $config{debug}->{val}; - } -} - -# Accepts a DB connection name and a (string) query. Returns a $sth that's been -# executed. -sub do_query { - my ( $cxn, $query ) = @_; - - return undef if $file; - - # Test if the cxn should not even be tried - return undef if $dbhs{$cxn} - && $dbhs{$cxn}->{err_count} - && ( !$dbhs{$cxn}->{dbh} || !$dbhs{$cxn}->{dbh}->{Active} || $dbhs{$cxn}->{mode} eq $config{mode}->{val} ) - && $dbhs{$cxn}->{wake_up} > $clock; - - my $sth; - my $retries = 1; - my $success = 0; - TRY: - while ( $retries-- >= 0 && !$success ) { - - eval { - my $dbh = connect_to_db($cxn); - - $sth = $dbh->prepare($query); - $sth->execute(); - $success = 1; - }; - if ( $EVAL_ERROR ) { - if ( $EVAL_ERROR =~ m/$nonfatal_errs/ ) { - handle_cxn_error($cxn, $EVAL_ERROR); - } - else { - die $EVAL_ERROR; - } - if ( $retries < 0 ) { - $sth = undef; - } - } - } - - return $sth; -} - -sub get_uptime { - my ( $cxn ) = @_; - $dbhs{$cxn}->{start_time} ||= time(); - # Avoid dividing by zero - return (time() - $dbhs{$cxn}->{start_time}) || .001; -} - -sub connect_to_db { - my ( $cxn ) = @_; - - $dbhs{$cxn} ||= { - stmts => {}, # bucket for prepared statements. - prev_sleep => 0, - this_sleep => 1, - wake_up => 0, - start_time => 0, - dbh => undef, - }; - my $href = $dbhs{$cxn}; - - if ( !$href->{dbh} || ref($href->{dbh}) !~ m/DBI/ || !$href->{dbh}->ping ) { - my $dbh = get_new_db_connection($cxn); - @{$href}{qw(dbh err_count wake_up this_sleep start_time prev_sleep)} - = ($dbh, 0, 0, 1, 0, 0); - - # Derive and store the server's start time in hi-res - my $uptime = $dbh->selectrow_hashref("show status like 'Uptime'")->{value}; - $href->{start_time} = time() - $uptime; - - # Set timeouts so an unused connection stays alive. - # For example, a connection might be used in Q mode but idle in T mode. - if ( version_ge($dbh, '4.0.3')) { - my $timeout = $config{cxn_timeout}->{val}; - $dbh->do("set session wait_timeout=$timeout, interactive_timeout=$timeout"); - } - } - return $href->{dbh}; -} - -# Compares versions like 5.0.27 and 4.1.15-standard-log -sub version_ge { - my ( $dbh, $target ) = @_; - my $version = sprintf('%03d%03d%03d', $dbh->{mysql_serverinfo} =~ m/(\d+)/g); - return $version ge sprintf('%03d%03d%03d', $target =~ m/(\d+)/g); -} - -# Extracts status values that can be gleaned from the DBD driver without doing a whole query. -sub get_driver_status { - my @cxns = @_; - if ( !$info_gotten{driver_status}++ ) { - foreach my $cxn ( @cxns ) { - next unless $dbhs{$cxn} && $dbhs{$cxn}->{dbh} && $dbhs{$cxn}->{dbh}->{Active}; - $vars{$cxn}->{$clock} ||= {}; - my $vars = $vars{$cxn}->{$clock}; - my %res = map { $_ =~ s/ +/_/g; $_ } $dbhs{$cxn}->{dbh}->{mysql_stat} =~ m/(\w[^:]+): ([\d\.]+)/g; - map { $vars->{$_} ||= $res{$_} } keys %res; - $vars->{Uptime_hires} ||= get_uptime($cxn); - $vars->{cxn} = $cxn; - } - } -} - -sub get_new_db_connection { - my ( $connection, $destroy ) = @_; - if ( $file ) { - die "You can't connect to a MySQL server while monitoring a file. This is probably a bug."; - } - - my $dsn = $connections{$connection} - or die "No connection named '$connection' is defined in your configuration"; - - if ( !defined $dsn->{have_user} ) { - my $answer = prompt("Do you want to specify a username for $connection?", undef, 'n'); - $dsn->{have_user} = $answer && $answer =~ m/1|y/i; - } - - if ( !defined $dsn->{have_pass} ) { - my $answer = prompt("Do you want to specify a password for $connection?", undef, 'n'); - $dsn->{have_pass} = $answer && $answer =~ m/1|y/i; - } - - if ( !$dsn->{user} && $dsn->{have_user} ) { - my $user = $ENV{USERNAME} || $ENV{USER} || getlogin() || getpwuid($REAL_USER_ID) || undef; - $dsn->{user} = prompt("Enter username for $connection", undef, $user); - } - - if ( !defined $dsn->{user} ) { - $dsn->{user} = ''; - } - - if ( !$dsn->{pass} && !$dsn->{savepass} && $dsn->{have_pass} ) { - $dsn->{pass} = prompt_noecho("Enter password for '$dsn->{user}' on $connection"); - print "\n"; - if ( !defined($dsn->{savepass}) ) { - my $answer = prompt("Save password in plain text in the config file?", undef, 'y'); - $dsn->{savepass} = $answer && $answer =~ m/1|y/i; - } - } - - my $dbh = DBI->connect( - $dsn->{dsn}, $dsn->{user}, $dsn->{pass}, - { RaiseError => 1, PrintError => 0, AutoCommit => 1 }); - $dbh->{InactiveDestroy} = 1 unless $destroy; # Can't be set in $db_options - $dbh->{FetchHashKeyName} = 'NAME_lc'; # Lowercases all column names for fetchrow_hashref - return $dbh; -} - -sub get_cxn_errors { - my @cxns = @_; - return () unless $config{show_cxn_errors_in_tbl}->{val}; - return - map { [ $_ . ': ' . $dbhs{$_}->{last_err}, 'red' ] } - grep { $dbhs{$_} && $dbhs{$_}->{err_count} && $dbhs{$_}->{mode} eq $config{mode}->{val} } - @cxns; -} - -# Setup and tear-down functions {{{2 - -# Takes a string and turns it into a hashref you can apply to %tbl_meta tables. The string -# can be in the form 'foo, bar, foo/bar, foo as bar' much like a SQL SELECT statement. -sub compile_select_stmt { - my ($str) = @_; - my @exps = $str =~ m/\s*([^,]+(?i:\s+as\s+[^,\s]+)?)\s*(?=,|$)/g; - my %cols; - my @visible; - foreach my $exp ( @exps ) { - my ( $text, $colname ); - if ( $exp =~ m/as\s+(\w+)\s*/ ) { - $colname = $1; - $exp =~ s/as\s+(\w+)\s*//; - $text = $exp; - } - else { - $text = $colname = $exp; - } - my ($func, $err) = compile_expr($text); - $cols{$colname} = { - src => $text, - hdr => $colname, - num => 0, - func => $func, - }; - push @visible, $colname; - } - return (\%cols, \@visible); -} - -# compile_filter {{{3 -sub compile_filter { - my ( $text ) = @_; - my ( $sub, $err ); - eval "\$sub = sub { my \$set = shift; $text }"; - if ( $EVAL_ERROR ) { - $EVAL_ERROR =~ s/at \(eval.*$//; - $sub = sub { return $EVAL_ERROR }; - $err = $EVAL_ERROR; - } - return ( $sub, $err ); -} - -# compile_expr {{{3 -sub compile_expr { - my ( $expr ) = @_; - # Leave built-in functions alone so they get called as Perl functions, unless - # they are the only word in $expr, in which case treat them as hash keys. - if ( $expr =~ m/\W/ ) { - $expr =~ s/(?<!\{|\$)\b([A-Za-z]\w{2,})\b/is_func($1) ? $1 : "\$set->{$1}"/eg; - } - else { - $expr = "\$set->{$expr}"; - } - my ( $sub, $err ); - my $quoted = quotemeta($expr); - eval qq{ - \$sub = sub { - my (\$set, \$cur, \$pre) = \@_; - my \$val = eval { $expr }; - if ( \$EVAL_ERROR && \$config{debug}->{val} ) { - \$EVAL_ERROR =~ s/ at \\(eval.*//s; - die "\$EVAL_ERROR in expression $quoted"; - } - return \$val; - } - }; - if ( $EVAL_ERROR ) { - if ( $config{debug}->{val} ) { - die $EVAL_ERROR; - } - $EVAL_ERROR =~ s/ at \(eval.*$//; - $sub = sub { return $EVAL_ERROR }; - $err = $EVAL_ERROR; - } - return ( $sub, $err ); -} - -# finish {{{3 -# This is a subroutine because it's called from a key to quit the program. -sub finish { - save_config(); - ReadMode('normal') unless $opts{n}; - print "\n"; - exit(0); -} - -# core_dump {{{3 -sub core_dump { - my $msg = shift; - if ($config{debugfile}->{val} && $config{debug}->{val}) { - eval { - open my $file, '>>', $config{debugfile}->{val}; - if ( %vars ) { - print $file "Current variables:\n" . Dumper(\%vars); - } - close $file; - }; - } - print $msg; -} - -# load_config {{{3 -sub load_config { - - my $filename = $opts{c} || "$homepath/.innotop/innotop.ini"; - my $dirname = dirname($filename); - if ( -f $dirname && !$opts{c} ) { - # innotop got upgraded and this is the old config file. - my $answer = pause("Innotop's default config location has moved to $filename. Move old config file $dirname there now? y/n"); - if ( lc $answer eq 'y' ) { - rename($dirname, "$homepath/innotop.ini") - or die "Can't rename '$dirname': $OS_ERROR"; - mkdir($dirname) or die "Can't create directory '$dirname': $OS_ERROR"; - mkdir("$dirname/plugins") or die "Can't create directory '$dirname/plugins': $OS_ERROR"; - rename("$homepath/innotop.ini", $filename) - or die "Can't rename '$homepath/innotop.ini' to '$filename': $OS_ERROR"; - } - else { - print "\nInnotop will now exit so you can fix the config file.\n"; - exit(0); - } - } - - if ( ! -d $dirname ) { - mkdir $dirname - or die "Can't create directory '$dirname': $OS_ERROR"; - } - if ( ! -d "$dirname/plugins" ) { - mkdir "$dirname/plugins" - or die "Can't create directory '$dirname/plugins': $OS_ERROR"; - } - - if ( -f $filename ) { - open my $file, "<", $filename or die("Can't open '$filename': $OS_ERROR"); - - # Check config file version. Just ignore if either innotop or the file has - # garbage in the version number. - if ( defined(my $line = <$file>) && $VERSION =~ m/\d/ ) { - chomp $line; - if ( my ($maj, $min, $rev) = $line =~ m/^version=(\d+)\.(\d+)(?:\.(\d+))?$/ ) { - $rev ||= 0; - my $cfg_ver = sprintf('%03d-%03d-%03d', $maj, $min, $rev); - ( $maj, $min, $rev ) = $VERSION =~ m/^(\d+)\.(\d+)(?:\.(\d+))?$/; - $rev ||= 0; - my $innotop_ver = sprintf('%03d-%03d-%03d', $maj, $min, $rev); - - if ( $cfg_ver gt $innotop_ver ) { - pause("The config file is for a newer version of innotop and may not be read correctly."); - } - else { - my @ver_history = @config_versions; - while ( my ($start, $end) = splice(@ver_history, 0, 2) ) { - # If the config file is between the endpoints and innotop is greater than - # the endpoint, innotop has a newer config file format than the file. - if ( $cfg_ver ge $start && $cfg_ver lt $end && $innotop_ver ge $end ) { - my $msg = "innotop's config file format has changed. Overwrite $filename? y or n"; - if ( pause($msg) eq 'n' ) { - $config{readonly}->{val} = 1; - print "\ninnotop will not save any configuration changes you make."; - pause(); - print "\n"; - } - close $file; - return; - } - } - } - } - } - - while ( my $line = <$file> ) { - chomp $line; - next unless $line =~ m/^\[([a-z_]+)\]$/; - if ( exists $config_file_sections{$1} ) { - $config_file_sections{$1}->{reader}->($file); - } - else { - warn "Unknown config file section '$1'"; - } - } - close $file or die("Can't close $filename: $OS_ERROR"); - } - -} - -# Do some post-processing on %tbl_meta: compile src properties into func etc. -sub post_process_tbl_meta { - foreach my $table ( values %tbl_meta ) { - foreach my $col_name ( keys %{$table->{cols}} ) { - my $col_def = $table->{cols}->{$col_name}; - my ( $sub, $err ) = compile_expr($col_def->{src}); - $col_def->{func} = $sub; - } - } -} - -# load_config_plugins {{{3 -sub load_config_plugins { - my ( $file ) = @_; - - # First, find a list of all plugins that exist on disk, and get information about them. - my $dir = $config{plugin_dir}->{val}; - foreach my $p_file ( <$dir/*.pm> ) { - my ($package, $desc); - eval { - open my $p_in, "<", $p_file or die $OS_ERROR; - while ( my $line = <$p_in> ) { - chomp $line; - if ( $line =~ m/^package\s+(.*?);/ ) { - $package = $1; - } - elsif ( $line =~ m/^# description: (.*)/ ) { - $desc = $1; - } - last if $package && $desc; - } - close $p_in; - }; - if ( $package ) { - $plugins{$package} = { - file => $p_file, - desc => $desc, - class => $package, - active => 0, - }; - if ( $config{debug}->{val} && $EVAL_ERROR ) { - die $EVAL_ERROR; - } - } - } - - # Now read which ones the user has activated. Each line simply represents an active plugin. - while ( my $line = <$file> ) { - chomp $line; - next if $line =~ m/^#/; - last if $line =~ m/^\[/; - next unless $line && $plugins{$line}; - - my $obj; - eval { - require $plugins{$line}->{file}; - $obj = $line->new(%pluggable_vars); - foreach my $event ( $obj->register_for_events() ) { - my $queue = $event_listener_for{$event}; - if ( $queue ) { - push @$queue, $obj; - } - } - }; - if ( $config{debug}->{val} && $EVAL_ERROR ) { - die $EVAL_ERROR; - } - if ( $obj ) { - $plugins{$line}->{active} = 1; - $plugins{$line}->{object} = $obj; - } - } -} - -# save_config_plugins {{{3 -sub save_config_plugins { - my $file = shift; - foreach my $class ( sort keys %plugins ) { - next unless $plugins{$class}->{active}; - print $file "$class\n"; - } -} - -# load_config_active_server_groups {{{3 -sub load_config_active_server_groups { - my ( $file ) = @_; - while ( my $line = <$file> ) { - chomp $line; - next if $line =~ m/^#/; - last if $line =~ m/^\[/; - - my ( $mode, $group ) = $line =~ m/^(.*?)=(.*)$/; - next unless $mode && $group - && exists $modes{$mode} && exists $server_groups{$group}; - $modes{$mode}->{server_group} = $group; - } -} - -# save_config_active_server_groups {{{3 -sub save_config_active_server_groups { - my $file = shift; - foreach my $mode ( sort keys %modes ) { - print $file "$mode=$modes{$mode}->{server_group}\n"; - } -} - -# load_config_server_groups {{{3 -sub load_config_server_groups { - my ( $file ) = @_; - while ( my $line = <$file> ) { - chomp $line; - next if $line =~ m/^#/; - last if $line =~ m/^\[/; - - my ( $name, $rest ) = $line =~ m/^(.*?)=(.*)$/; - next unless $name && $rest; - my @vars = unique(grep { $_ && exists $connections{$_} } split(/\s+/, $rest)); - next unless @vars; - $server_groups{$name} = \@vars; - } -} - -# save_config_server_groups {{{3 -sub save_config_server_groups { - my $file = shift; - foreach my $set ( sort keys %server_groups ) { - print $file "$set=", join(' ', @{$server_groups{$set}}), "\n"; - } -} - -# load_config_varsets {{{3 -sub load_config_varsets { - my ( $file ) = @_; - while ( my $line = <$file> ) { - chomp $line; - next if $line =~ m/^#/; - last if $line =~ m/^\[/; - - my ( $name, $rest ) = $line =~ m/^(.*?)=(.*)$/; - next unless $name && $rest; - $var_sets{$name} = { - text => $rest, - user => 1, - }; - } -} - -# save_config_varsets {{{3 -sub save_config_varsets { - my $file = shift; - foreach my $varset ( sort keys %var_sets ) { - next unless $var_sets{$varset}->{user}; - print $file "$varset=$var_sets{$varset}->{text}\n"; - } -} - -# load_config_group_by {{{3 -sub load_config_group_by { - my ( $file ) = @_; - while ( my $line = <$file> ) { - chomp $line; - next if $line =~ m/^#/; - last if $line =~ m/^\[/; - - my ( $tbl , $rest ) = $line =~ m/^(.*?)=(.*)$/; - next unless $tbl && exists $tbl_meta{$tbl}; - my @parts = unique(grep { exists($tbl_meta{$tbl}->{cols}->{$_}) } split(/\s+/, $rest)); - $tbl_meta{$tbl}->{group_by} = [ @parts ]; - $tbl_meta{$tbl}->{cust}->{group_by} = 1; - } -} - -# save_config_group_by {{{3 -sub save_config_group_by { - my $file = shift; - foreach my $tbl ( sort keys %tbl_meta ) { - next if $tbl_meta{$tbl}->{temp}; - next unless $tbl_meta{$tbl}->{cust}->{group_by}; - my $aref = $tbl_meta{$tbl}->{group_by}; - print $file "$tbl=", join(' ', @$aref), "\n"; - } -} - -# load_config_filters {{{3 -sub load_config_filters { - my ( $file ) = @_; - while ( my $line = <$file> ) { - chomp $line; - next if $line =~ m/^#/; - last if $line =~ m/^\[/; - - my ( $key, $rest ) = $line =~ m/^(.+?)=(.*)$/; - next unless $key && $rest; - - my %parts = $rest =~ m/(\w+)='((?:(?!(?<!\\)').)*)'/g; # Properties are single-quoted - next unless $parts{text} && $parts{tbls}; - - foreach my $prop ( keys %parts ) { - # Un-escape escaping - $parts{$prop} =~ s/\\\\/\\/g; - $parts{$prop} =~ s/\\'/'/g; - } - - my ( $sub, $err ) = compile_filter($parts{text}); - my @tbls = unique(split(/\s+/, $parts{tbls})); - @tbls = grep { exists $tbl_meta{$_} } @tbls; - $filters{$key} = { - func => $sub, - text => $parts{text}, - user => 1, - name => $key, - note => 'User-defined filter', - tbls => \@tbls, - } - } -} - -# save_config_filters {{{3 -sub save_config_filters { - my $file = shift; - foreach my $key ( sort keys %filters ) { - next if !$filters{$key}->{user} || $filters{$key}->{quick}; - my $text = $filters{$key}->{text}; - $text =~ s/([\\'])/\\$1/g; - my $tbls = join(" ", @{$filters{$key}->{tbls}}); - print $file "$key=text='$text' tbls='$tbls'\n"; - } -} - -# load_config_visible_tables {{{3 -sub load_config_visible_tables { - my ( $file ) = @_; - while ( my $line = <$file> ) { - chomp $line; - next if $line =~ m/^#/; - last if $line =~ m/^\[/; - - my ( $mode, $rest ) = $line =~ m/^(.*?)=(.*)$/; - next unless $mode && exists $modes{$mode}; - $modes{$mode}->{visible_tables} = - [ unique(grep { $_ && exists $tbl_meta{$_} } split(/\s+/, $rest)) ]; - $modes{$mode}->{cust}->{visible_tables} = 1; - } -} - -# save_config_visible_tables {{{3 -sub save_config_visible_tables { - my $file = shift; - foreach my $mode ( sort keys %modes ) { - next unless $modes{$mode}->{cust}->{visible_tables}; - my $tables = $modes{$mode}->{visible_tables}; - print $file "$mode=", join(' ', @$tables), "\n"; - } -} - -# load_config_sort_cols {{{3 -sub load_config_sort_cols { - my ( $file ) = @_; - while ( my $line = <$file> ) { - chomp $line; - next if $line =~ m/^#/; - last if $line =~ m/^\[/; - - my ( $key , $rest ) = $line =~ m/^(.*?)=(.*)$/; - next unless $key && exists $tbl_meta{$key}; - $tbl_meta{$key}->{sort_cols} = $rest; - $tbl_meta{$key}->{cust}->{sort_cols} = 1; - $tbl_meta{$key}->{sort_func} = make_sort_func($tbl_meta{$key}); - } -} - -# save_config_sort_cols {{{3 -sub save_config_sort_cols { - my $file = shift; - foreach my $tbl ( sort keys %tbl_meta ) { - next unless $tbl_meta{$tbl}->{cust}->{sort_cols}; - my $col = $tbl_meta{$tbl}->{sort_cols}; - print $file "$tbl=$col\n"; - } -} - -# load_config_active_filters {{{3 -sub load_config_active_filters { - my ( $file ) = @_; - while ( my $line = <$file> ) { - chomp $line; - next if $line =~ m/^#/; - last if $line =~ m/^\[/; - - my ( $tbl , $rest ) = $line =~ m/^(.*?)=(.*)$/; - next unless $tbl && exists $tbl_meta{$tbl}; - my @parts = unique(grep { exists($filters{$_}) } split(/\s+/, $rest)); - @parts = grep { grep { $tbl eq $_ } @{$filters{$_}->{tbls}} } @parts; - $tbl_meta{$tbl}->{filters} = [ @parts ]; - $tbl_meta{$tbl}->{cust}->{filters} = 1; - } -} - -# save_config_active_filters {{{3 -sub save_config_active_filters { - my $file = shift; - foreach my $tbl ( sort keys %tbl_meta ) { - next if $tbl_meta{$tbl}->{temp}; - next unless $tbl_meta{$tbl}->{cust}->{filters}; - my $aref = $tbl_meta{$tbl}->{filters}; - print $file "$tbl=", join(' ', @$aref), "\n"; - } -} - -# load_config_active_columns {{{3 -sub load_config_active_columns { - my ( $file ) = @_; - while ( my $line = <$file> ) { - chomp $line; - next if $line =~ m/^#/; - last if $line =~ m/^\[/; - - my ( $key , $rest ) = $line =~ m/^(.*?)=(.*)$/; - next unless $key && exists $tbl_meta{$key}; - my @parts = grep { exists($tbl_meta{$key}->{cols}->{$_}) } unique split(/ /, $rest); - $tbl_meta{$key}->{visible} = [ @parts ]; - $tbl_meta{$key}->{cust}->{visible} = 1; - } -} - -# save_config_active_columns {{{3 -sub save_config_active_columns { - my $file = shift; - foreach my $tbl ( sort keys %tbl_meta ) { - next unless $tbl_meta{$tbl}->{cust}->{visible}; - my $aref = $tbl_meta{$tbl}->{visible}; - print $file "$tbl=", join(' ', @$aref), "\n"; - } -} - -# save_config_tbl_meta {{{3 -sub save_config_tbl_meta { - my $file = shift; - foreach my $tbl ( sort keys %tbl_meta ) { - foreach my $col ( keys %{$tbl_meta{$tbl}->{cols}} ) { - my $meta = $tbl_meta{$tbl}->{cols}->{$col}; - next unless $meta->{user}; - print $file "$col=", join( - " ", - map { - # Some properties (trans) are arrays, others scalars - my $val = ref($meta->{$_}) ? join(',', @{$meta->{$_}}) : $meta->{$_}; - $val =~ s/([\\'])/\\$1/g; # Escape backslashes and single quotes - "$_='$val'"; # Enclose in single quotes - } - grep { $_ ne 'func' } - keys %$meta - ), "\n"; - } - } -} - -# save_config_config {{{3 -sub save_config_config { - my $file = shift; - foreach my $key ( sort keys %config ) { - eval { - if ( $key ne 'password' || $config{savepass}->{val} ) { - print $file "# $config{$key}->{note}\n" - or die "Cannot print to file: $OS_ERROR"; - my $val = $config{$key}->{val}; - $val = '' unless defined($val); - if ( ref( $val ) eq 'ARRAY' ) { - print $file "$key=" - . join( " ", @$val ) . "\n" - or die "Cannot print to file: $OS_ERROR"; - } - elsif ( ref( $val ) eq 'HASH' ) { - print $file "$key=" - . join( " ", - map { "$_:$val->{$_}" } keys %$val - ) . "\n"; - } - else { - print $file "$key=$val\n"; - } - } - }; - if ( $EVAL_ERROR ) { print "$EVAL_ERROR in $key"; }; - } - -} - -# load_config_config {{{3 -sub load_config_config { - my ( $file ) = @_; - - # Look in the command-line parameters for things stored in the same slot. - my %cmdline = - map { $_->{c} => $opts{$_->{k}} } - grep { exists $_->{c} && exists $opts{$_->{k}} } - @opt_spec; - - while ( my $line = <$file> ) { - chomp $line; - next if $line =~ m/^#/; - last if $line =~ m/^\[/; - - my ( $name, $val ) = $line =~ m/^(.+?)=(.*)$/; - next unless defined $name && defined $val; - - # Values might already have been set at the command line. - $val = defined($cmdline{$name}) ? $cmdline{$name} : $val; - - # Validate the incoming values... - if ( $name && exists( $config{$name} ) ) { - if ( !$config{$name}->{pat} || $val =~ m/$config{$name}->{pat}/ ) { - $config{$name}->{val} = $val; - $config{$name}->{read} = 1; - } - } - } -} - -# load_config_tbl_meta {{{3 -sub load_config_tbl_meta { - my ( $file ) = @_; - - while ( my $line = <$file> ) { - chomp $line; - next if $line =~ m/^#/; - last if $line =~ m/^\[/; - - # Each tbl_meta section has all the properties defined in %col_props. - my ( $col , $rest ) = $line =~ m/^(.*?)=(.*)$/; - next unless $col; - my %parts = $rest =~ m/(\w+)='((?:(?!(?<!\\)').)*)'/g; # Properties are single-quoted - - # Each section read from the config file has one extra property: which table it - # goes in. - my $tbl = $parts{tbl} or die "There's no table for tbl_meta $col"; - my $meta = $tbl_meta{$tbl} or die "There's no table in tbl_meta named $tbl"; - - # The section is user-defined by definition (if that makes sense). - $parts{user} = 1; - - # The column may already exist in the table, in which case this is just a - # customization. - $meta->{cols}->{$col} ||= {}; - - foreach my $prop ( keys %col_props ) { - if ( !defined($parts{$prop}) ) { - die "Undefined property $prop for column $col in table $tbl"; - } - - # Un-escape escaping - $parts{$prop} =~ s/\\\\/\\/g; - $parts{$prop} =~ s/\\'/'/g; - - if ( ref $col_props{$prop} ) { - if ( $prop eq 'trans' ) { - $meta->{cols}->{$col}->{trans} - = [ unique(grep { exists $trans_funcs{$_} } split(',', $parts{$prop})) ]; - } - else { - $meta->{cols}->{$col}->{$prop} = [ split(',', $parts{$prop}) ]; - } - } - else { - $meta->{cols}->{$col}->{$prop} = $parts{$prop}; - } - } - - } -} - -# save_config {{{3 -sub save_config { - return if $config{readonly}->{val}; - # Save to a temp file first, so a crash doesn't destroy the main config file - my $newname = $opts{c} || "$homepath/.innotop/innotop.ini"; - my $filename = $newname . '_tmp'; - open my $file, "+>", $filename - or die("Can't write to $filename: $OS_ERROR"); - print $file "version=$VERSION\n"; - - foreach my $section ( @ordered_config_file_sections ) { - die "No such config file section $section" unless $config_file_sections{$section}; - print $file "\n[$section]\n\n"; - $config_file_sections{$section}->{writer}->($file); - print $file "\n[/$section]\n"; - } - - # Now clobber the main config file with the temp. - close $file or die("Can't close $filename: $OS_ERROR"); - rename($filename, $newname) or die("Can't rename $filename to $newname: $OS_ERROR"); -} - -# load_config_connections {{{3 -sub load_config_connections { - my ( $file ) = @_; - while ( my $line = <$file> ) { - chomp $line; - next if $line =~ m/^#/; - last if $line =~ m/^\[/; - - my ( $key , $rest ) = $line =~ m/^(.*?)=(.*)$/; - next unless $key; - my %parts = $rest =~ m/(\S+?)=(\S*)/g; - my %conn = map { $_ => $parts{$_} || '' } @conn_parts; - $connections{$key} = \%conn; - } -} - -# save_config_connections {{{3 -sub save_config_connections { - my $file = shift; - foreach my $conn ( sort keys %connections ) { - my $href = $connections{$conn}; - my @keys = $href->{savepass} ? @conn_parts : grep { $_ ne 'pass' } @conn_parts; - print $file "$conn=", join(' ', map { "$_=$href->{$_}" } grep { defined $href->{$_} } @keys), "\n"; - } -} - -sub load_config_colors { - my ( $file ) = @_; - my %rule_set_for; - - while ( my $line = <$file> ) { - chomp $line; - next if $line =~ m/^#/; - last if $line =~ m/^\[/; - - my ( $tbl, $rule ) = $line =~ m/^(.*?)=(.*)$/; - next unless $tbl && $rule; - next unless exists $tbl_meta{$tbl}; - my %parts = $rule =~ m/(\w+)='((?:(?!(?<!\\)').)*)'/g; # Properties are single-quoted - next unless $parts{col} && exists $tbl_meta{$tbl}->{cols}->{$parts{col}}; - next unless $parts{op} && exists $comp_ops{$parts{op}}; - next unless defined $parts{arg}; - next unless defined $parts{color}; - my @colors = unique(grep { exists $ansicolors{$_} } split(/\W+/, $parts{color})); - next unless @colors; - - # Finally! Enough validation... - $rule_set_for{$tbl} ||= []; - push @{$rule_set_for{$tbl}}, \%parts; - } - - foreach my $tbl ( keys %rule_set_for ) { - $tbl_meta{$tbl}->{colors} = $rule_set_for{$tbl}; - $tbl_meta{$tbl}->{color_func} = make_color_func($tbl_meta{$tbl}); - $tbl_meta{$tbl}->{cust}->{colors} = 1; - } -} - -# save_config_colors {{{3 -sub save_config_colors { - my $file = shift; - foreach my $tbl ( sort keys %tbl_meta ) { - my $meta = $tbl_meta{$tbl}; - next unless $meta->{cust}->{colors}; - foreach my $rule ( @{$meta->{colors}} ) { - print $file "$tbl=", join( - ' ', - map { - my $val = $rule->{$_}; - $val =~ s/([\\'])/\\$1/g; # Escape backslashes and single quotes - "$_='$val'"; # Enclose in single quotes - } - qw(col op arg color) - ), "\n"; - } - } -} - -# load_config_active_connections {{{3 -sub load_config_active_connections { - my ( $file ) = @_; - while ( my $line = <$file> ) { - chomp $line; - next if $line =~ m/^#/; - last if $line =~ m/^\[/; - - my ( $key , $rest ) = $line =~ m/^(.*?)=(.*)$/; - next unless $key && exists $modes{$key}; - my @parts = grep { exists $connections{$_} } split(/ /, $rest); - $modes{$key}->{connections} = [ @parts ] if exists $modes{$key}; - } -} - -# save_config_active_connections {{{3 -sub save_config_active_connections { - my $file = shift; - foreach my $mode ( sort keys %modes ) { - my @connections = get_connections($mode); - print $file "$mode=", join(' ', @connections), "\n"; - } -} - -# load_config_stmt_sleep_times {{{3 -sub load_config_stmt_sleep_times { - my ( $file ) = @_; - while ( my $line = <$file> ) { - chomp $line; - next if $line =~ m/^#/; - last if $line =~ m/^\[/; - - my ( $key , $val ) = split('=', $line); - next unless $key && defined $val && $val =~ m/$num_regex/; - $stmt_sleep_time_for{$key} = $val; - } -} - -# save_config_stmt_sleep_times {{{3 -sub save_config_stmt_sleep_times { - my $file = shift; - foreach my $key ( sort keys %stmt_sleep_time_for ) { - print $file "$key=$stmt_sleep_time_for{$key}\n"; - } -} - -# load_config_mvs {{{3 -sub load_config_mvs { - my ( $file ) = @_; - while ( my $line = <$file> ) { - chomp $line; - next if $line =~ m/^#/; - last if $line =~ m/^\[/; - - my ( $key , $val ) = split('=', $line); - next unless $key && defined $val && $val =~ m/$num_regex/; - $mvs{$key} = $val; - } -} - -# save_config_mvs {{{3 -sub save_config_mvs { - my $file = shift; - foreach my $key ( sort keys %mvs ) { - print $file "$key=$mvs{$key}\n"; - } -} - -# edit_configuration {{{3 -sub edit_configuration { - my $key = ''; - while ( $key ne 'q' ) { - $clear_screen_sub->(); - my @display_lines = ''; - - if ( $key && $cfg_editor_action{$key} ) { - $cfg_editor_action{$key}->{func}->(); - } - - # Show help - push @display_lines, create_caption('What configuration do you want to edit?', - create_table2( - [ sort keys %cfg_editor_action ], - { map { $_ => $_ } keys %cfg_editor_action }, - { map { $_ => $cfg_editor_action{$_}->{note} } keys %cfg_editor_action }, - { sep => ' ' })); - - draw_screen(\@display_lines); - $key = pause(''); - } -} - -# edit_configuration_variables {{{3 -sub edit_configuration_variables { - $clear_screen_sub->(); - my $mode = $config{mode}->{val}; - - my %config_choices - = map { $_ => $config{$_}->{note} || '' } - # Only config values that are marked as applying to this mode. - grep { - my $key = $_; - $config{$key}->{conf} && - ( $config{$key}->{conf} eq 'ALL' - || grep { $mode eq $_ } @{$config{$key}->{conf}} ) - } keys %config; - - my $key = prompt_list( - "Enter the name of the variable you wish to configure", - '', - sub{ return keys %config_choices }, - \%config_choices); - - if ( exists($config_choices{$key}) ) { - get_config_interactive($key); - } -} - -# edit_color_rules {{{3 -sub edit_color_rules { - my ( $tbl ) = @_; - $clear_screen_sub->(); - $tbl ||= choose_visible_table(); - if ( $tbl && exists($tbl_meta{$tbl}) ) { - my $meta = $tbl_meta{$tbl}; - my @cols = ('', qw(col op arg color)); - my $info = { map { $_ => { hdr => $_, just => '-', } } @cols }; - $info->{label}->{maxw} = 30; - my $key; - my $selected_rule; - - # This loop builds a tabular view of the rules. - do { - - # Show help - if ( $key && $key eq '?' ) { - my @display_lines = ''; - push @display_lines, create_caption('Editor key mappings', - create_table2( - [ sort keys %color_editor_action ], - { map { $_ => $_ } keys %color_editor_action }, - { map { $_ => $color_editor_action{$_}->{note} } keys %color_editor_action }, - { sep => ' ' })); - draw_screen(\@display_lines); - pause(); - $key = ''; - } - else { - - # Do the action specified - $selected_rule ||= 0; - if ( $key && $color_editor_action{$key} ) { - $selected_rule = $color_editor_action{$key}->{func}->($tbl, $selected_rule); - $selected_rule ||= 0; - } - - # Build the table of rules. If the terminal has color, the selected rule - # will be highlighted; otherwise a > at the left will indicate. - my $data = $meta->{colors} || []; - foreach my $i ( 0..@$data - 1 ) { - $data->[$i]->{''} = $i == $selected_rule ? '>' : ''; - } - my @display_lines = create_table(\@cols, $info, $data); - - # Highlight selected entry - for my $i ( 0 .. $#display_lines ) { - if ( $display_lines[$i] =~ m/^>/ ) { - $display_lines[$i] = [ $display_lines[$i], 'reverse' ]; - } - } - - # Draw the screen and wait for a command. - unshift @display_lines, '', - "Editing color rules for $meta->{capt}. Press ? for help, q to " - . "quit.", ''; - draw_screen(\@display_lines); - print "\n\n", word_wrap('Rules are applied in order from top to ' - . 'bottom. The first matching rule wins and prevents the ' - . 'rest of the rules from being applied.'); - $key = pause(''); - } - } while ( $key ne 'q' ); - $meta->{color_func} = make_color_func($meta); - } -} - -# add_quick_filter {{{3 -sub add_quick_filter { - my $tbl = choose_visible_table(); - if ( $tbl && exists($tbl_meta{$tbl}) ) { - print "\n"; - my $response = prompt_list( - "Enter column name and filter text", - '', - sub { return keys %{$tbl_meta{$tbl}->{cols}} }, - () - ); - my ( $col, $text ) = split(/\s+/, $response, 2); - - # You can't filter on a nonexistent column. But if you filter on a pivoted - # table, the columns are different, so on a pivoted table, allow filtering - # on the 'name' column. - # NOTE: if a table is pivoted and un-pivoted, this will likely cause crashes. - # Currently not an issue since there's no way to toggle pivot/nopivot. - return unless $col && $text && - (exists($tbl_meta{$tbl}->{cols}->{$col}) - || ($tbl_meta{$tbl}->{pivot} && $col eq 'name')); - - my ( $sub, $err ) = compile_filter( "defined \$set->{$col} && \$set->{$col} =~ m/$text/" ); - return if !$sub || $err; - my $name = "quick_$tbl.$col"; - $filters{$name} = { - func => $sub, - text => $text, - user => 1, - quick => 1, - name => $name, - note => 'Quick-filter', - tbls => [$tbl], - }; - push @{$tbl_meta{$tbl}->{filters}}, $name; - } -} - -# clear_quick_filters {{{3 -sub clear_quick_filters { - my $tbl = choose_visible_table( - # Only tables that have quick-filters - sub { - my ( $tbl ) = @_; - return scalar grep { $filters{$_}->{quick} } @{ $tbl_meta{$tbl}->{filters} }; - } - ); - if ( $tbl && exists($tbl_meta{$tbl}) ) { - my @current = @{$tbl_meta{$tbl}->{filters}}; - @current = grep { !$filters{$_}->{quick} } @current; - $tbl_meta{$tbl}->{filters} = \@current; - } -} - -sub edit_plugins { - $clear_screen_sub->(); - - my @cols = ('', qw(class desc active)); - my $info = { map { $_ => { hdr => $_, just => '-', } } @cols }; - my @rows = map { $plugins{$_} } sort keys %plugins; - my $key; - my $selected; - - # This loop builds a tabular view of the plugins. - do { - - # Show help - if ( $key && $key eq '?' ) { - my @display_lines = ''; - push @display_lines, create_caption('Editor key mappings', - create_table2( - [ sort keys %plugin_editor_action ], - { map { $_ => $_ } keys %plugin_editor_action }, - { map { $_ => $plugin_editor_action{$_}->{note} } keys %plugin_editor_action }, - { sep => ' ' })); - draw_screen(\@display_lines); - pause(); - $key = ''; - } - - # Do the action specified - else { - $selected ||= 0; - if ( $key && $plugin_editor_action{$key} ) { - $selected = $plugin_editor_action{$key}->{func}->(\@rows, $selected); - $selected ||= 0; - } - - # Build the table of plugins. - foreach my $row ( 0.. $#rows ) { - $rows[$row]->{''} = $row eq $selected ? '>' : ' '; - } - my @display_lines = create_table(\@cols, $info, \@rows); - - # Highlight selected entry - for my $i ( 0 .. $#display_lines ) { - if ( $display_lines[$i] =~ m/^>/ ) { - $display_lines[$i] = [ $display_lines[$i], 'reverse' ]; - } - } - - # Draw the screen and wait for a command. - unshift @display_lines, '', - "Plugin Management. Press ? for help, q to quit.", ''; - draw_screen(\@display_lines); - $key = pause(''); - } - } while ( $key ne 'q' ); -} - -# edit_table {{{3 -sub edit_table { - $clear_screen_sub->(); - my ( $tbl ) = @_; - $tbl ||= choose_visible_table(); - if ( $tbl && exists($tbl_meta{$tbl}) ) { - my $meta = $tbl_meta{$tbl}; - my @cols = ('', qw(name hdr label src)); - my $info = { map { $_ => { hdr => $_, just => '-', } } @cols }; - $info->{label}->{maxw} = 30; - my $key; - my $selected_column; - - # This loop builds a tabular view of the tbl_meta's structure, showing each column - # in the entry as a row. - do { - - # Show help - if ( $key && $key eq '?' ) { - my @display_lines = ''; - push @display_lines, create_caption('Editor key mappings', - create_table2( - [ sort keys %tbl_editor_action ], - { map { $_ => $_ } keys %tbl_editor_action }, - { map { $_ => $tbl_editor_action{$_}->{note} } keys %tbl_editor_action }, - { sep => ' ' })); - draw_screen(\@display_lines); - pause(); - $key = ''; - } - else { - - # Do the action specified - $selected_column ||= $meta->{visible}->[0]; - if ( $key && $tbl_editor_action{$key} ) { - $selected_column = $tbl_editor_action{$key}->{func}->($tbl, $selected_column); - $selected_column ||= $meta->{visible}->[0]; - } - - # Build the pivoted view of the table's meta-data. If the terminal has color, - # The selected row will be highlighted; otherwise a > at the left will indicate. - my $data = []; - foreach my $row ( @{$meta->{visible}} ) { - my %hash; - @hash{ @cols } = @{$meta->{cols}->{$row}}{@cols}; - $hash{src} = '' if ref $hash{src}; - $hash{name} = $row; - $hash{''} = $row eq $selected_column ? '>' : ' '; - push @$data, \%hash; - } - my @display_lines = create_table(\@cols, $info, $data); - - # Highlight selected entry - for my $i ( 0 .. $#display_lines ) { - if ( $display_lines[$i] =~ m/^>/ ) { - $display_lines[$i] = [ $display_lines[$i], 'reverse' ]; - } - } - - # Draw the screen and wait for a command. - unshift @display_lines, '', - "Editing table definition for $meta->{capt}. Press ? for help, q to quit.", ''; - draw_screen(\@display_lines, { clear => 1 }); - $key = pause(''); - } - } while ( $key ne 'q' ); - } -} - -# choose_mode_tables {{{3 -# Choose which table(s), and in what order, to display in a given mode. -sub choose_mode_tables { - my $mode = $config{mode}->{val}; - my @tbls = @{$modes{$mode}->{visible_tables}}; - my $new = prompt_list( - "Choose tables to display", - join(' ', @tbls), - sub { return @{$modes{$mode}->{tables}} }, - { map { $_ => $tbl_meta{$_}->{capt} } @{$modes{$mode}->{tables}} } - ); - $modes{$mode}->{visible_tables} = - [ unique(grep { $_ && exists $tbl_meta{$_} } split(/\s+/, $new)) ]; - $modes{$mode}->{cust}->{visible_tables} = 1; -} - -# choose_visible_table {{{3 -sub choose_visible_table { - my ( $grep_cond ) = @_; - my $mode = $config{mode}->{val}; - my @tbls - = grep { $grep_cond ? $grep_cond->($_) : 1 } - @{$modes{$mode}->{visible_tables}}; - my $tbl = $tbls[0]; - if ( @tbls > 1 ) { - $tbl = prompt_list( - "Choose a table", - '', - sub { return @tbls }, - { map { $_ => $tbl_meta{$_}->{capt} } @tbls } - ); - } - return $tbl; -} - -sub toggle_aggregate { - my ( $tbl ) = @_; - $tbl ||= choose_visible_table(); - return unless $tbl && exists $tbl_meta{$tbl}; - my $meta = $tbl_meta{$tbl}; - $meta->{aggregate} ^= 1; -} - -sub choose_filters { - my ( $tbl ) = @_; - $tbl ||= choose_visible_table(); - return unless $tbl && exists $tbl_meta{$tbl}; - my $meta = $tbl_meta{$tbl}; - $clear_screen_sub->(); - - print "Choose filters for $meta->{capt}:\n"; - - my $ini = join(' ', @{$meta->{filters}}); - my $val = prompt_list( - 'Choose filters', - $ini, - sub { return keys %filters }, - { - map { $_ => $filters{$_}->{note} } - grep { grep { $tbl eq $_ } @{$filters{$_}->{tbls}} } - keys %filters - } - ); - - my @choices = unique(split(/\s+/, $val)); - foreach my $new ( grep { !exists($filters{$_}) } @choices ) { - my $answer = prompt("There is no filter called '$new'. Create it?", undef, 'y'); - if ( $answer eq 'y' ) { - create_new_filter($new, $tbl); - } - } - @choices = grep { exists $filters{$_} } @choices; - @choices = grep { grep { $tbl eq $_ } @{$filters{$_}->{tbls}} } @choices; - $meta->{filters} = [ @choices ]; - $meta->{cust}->{filters} = 1; -} - -sub choose_group_cols { - my ( $tbl ) = @_; - $tbl ||= choose_visible_table(); - return unless $tbl && exists $tbl_meta{$tbl}; - $clear_screen_sub->(); - my $meta = $tbl_meta{$tbl}; - my $curr = join(', ', @{$meta->{group_by}}); - my $val = prompt_list( - 'Group-by columns', - $curr, - sub { return keys %{$meta->{cols}} }, - { map { $_ => $meta->{cols}->{$_}->{label} } keys %{$meta->{cols}} }); - if ( $curr ne $val ) { - $meta->{group_by} = [ grep { exists $meta->{cols}->{$_} } $val =~ m/(\w+)/g ]; - $meta->{cust}->{group_by} = 1; - } -} - -sub choose_sort_cols { - my ( $tbl ) = @_; - $tbl ||= choose_visible_table(); - return unless $tbl && exists $tbl_meta{$tbl}; - $clear_screen_sub->(); - my $meta = $tbl_meta{$tbl}; - - my ( $cols, $hints ); - if ( $meta->{pivot} ) { - $cols = sub { qw(name set_0) }; - $hints = { name => 'name', set_0 => 'set_0' }; - } - else { - $cols = sub { return keys %{$meta->{cols}} }; - $hints = { map { $_ => $meta->{cols}->{$_}->{label} } keys %{$meta->{cols}} }; - } - - my $val = prompt_list( - 'Sort columns (reverse sort with -col)', - $meta->{sort_cols}, - $cols, - $hints ); - if ( $meta->{sort_cols} ne $val ) { - $meta->{sort_cols} = $val; - $meta->{cust}->{sort_cols} = 1; - $tbl_meta{$tbl}->{sort_func} = make_sort_func($tbl_meta{$tbl}); - } -} - -# create_new_filter {{{3 -sub create_new_filter { - my ( $filter, $tbl ) = @_; - $clear_screen_sub->(); - - if ( !$filter || $filter =~ m/\W/ ) { - print word_wrap("Choose a name for the filter. This name is not displayed, and is only used " - . "for internal reference. It can only contain lowercase letters, numbers, and underscores."); - print "\n\n"; - do { - $filter = prompt("Enter filter name"); - } while ( !$filter || $filter =~ m/\W/ ); - } - - my $completion = sub { keys %{$tbl_meta{$tbl}->{cols}} }; - my ( $err, $sub, $body ); - do { - $clear_screen_sub->(); - print word_wrap("A filter is a Perl subroutine that accepts a hashref of columns " - . "called \$set, and returns a true value if the filter accepts the row. Example:\n" - . " \$set->{active_secs} > 5\n" - . "will only allow rows if their active_secs column is greater than 5."); - print "\n\n"; - if ( $err ) { - print "There's an error in your filter expression: $err\n\n"; - } - $body = prompt("Enter subroutine body", undef, undef, $completion); - ( $sub, $err ) = compile_filter($body); - } while ( $err ); - - $filters{$filter} = { - func => $sub, - text => $body, - user => 1, - name => $filter, - note => 'User-defined filter', - tbls => [$tbl], - }; -} - -# get_config_interactive {{{3 -sub get_config_interactive { - my $key = shift; - $clear_screen_sub->(); - - # Print help first. - print "Enter a new value for '$key' ($config{$key}->{note}).\n"; - - my $current = ref($config{$key}->{val}) ? join(" ", @{$config{$key}->{val}}) : $config{$key}->{val}; - - my $new_value = prompt('Enter a value', $config{$key}->{pat}, $current); - $config{$key}->{val} = $new_value; -} - -sub edit_current_var_set { - my $mode = $config{mode}->{val}; - my $name = $config{"${mode}_set"}->{val}; - my $variables = $var_sets{$name}->{text}; - - my $new = $variables; - do { - $clear_screen_sub->(); - $new = prompt("Enter variables for $name", undef, $variables); - } until ( $new ); - - if ( $new ne $variables ) { - @{$var_sets{$name}}{qw(text user)} = ( $new, 1); - } -} - - -sub choose_var_set { - my ( $key ) = @_; - $clear_screen_sub->(); - - my $new_value = prompt_list( - 'Choose a set of values to display, or enter the name of a new one', - $config{$key}->{val}, - sub { return keys %var_sets }, - { map { $_ => $var_sets{$_}->{text} } keys %var_sets }); - - if ( !exists $var_sets{$new_value} ) { - add_new_var_set($new_value); - } - - $config{$key}->{val} = $new_value if exists $var_sets{$new_value}; -} - -sub switch_var_set { - my ( $cfg_var, $dir ) = @_; - my @var_sets = sort keys %var_sets; - my $cur = $config{$cfg_var}->{val}; - my $pos = grep { $_ lt $cur } @var_sets; - my $newpos = ($pos + $dir) % @var_sets; - $config{$cfg_var}->{val} = $var_sets[$newpos]; - $clear_screen_sub->(); -} - -# Online configuration and prompting functions {{{2 - -# edit_stmt_sleep_times {{{3 -sub edit_stmt_sleep_times { - $clear_screen_sub->(); - my $stmt = prompt_list('Specify a statement', '', sub { return sort keys %stmt_maker_for }); - return unless $stmt && exists $stmt_maker_for{$stmt}; - $clear_screen_sub->(); - my $curr_val = $stmt_sleep_time_for{$stmt} || 0; - my $new_val = prompt('Specify a sleep delay after calling this SQL', $num_regex, $curr_val); - if ( $new_val ) { - $stmt_sleep_time_for{$stmt} = $new_val; - } - else { - delete $stmt_sleep_time_for{$stmt}; - } -} - -# edit_server_groups {{{3 -# Choose which server connections are in a server group. First choose a group, -# then choose which connections are in it. -sub edit_server_groups { - $clear_screen_sub->(); - my $mode = $config{mode}->{val}; - my $group = $modes{$mode}->{server_group}; - my %curr = %server_groups; - my $new = choose_or_create_server_group($group, 'to edit'); - $clear_screen_sub->(); - if ( exists $curr{$new} ) { - # Don't do this step if the user just created a new server group, - # because part of that process was to choose connections. - my $cxns = join(' ', @{$server_groups{$new}}); - my @conns = choose_or_create_connection($cxns, 'for this group'); - $server_groups{$new} = \@conns; - } -} - -# choose_server_groups {{{3 -sub choose_server_groups { - $clear_screen_sub->(); - my $mode = $config{mode}->{val}; - my $group = $modes{$mode}->{server_group}; - my $new = choose_or_create_server_group($group, 'for this mode'); - $modes{$mode}->{server_group} = $new if exists $server_groups{$new}; -} - -sub choose_or_create_server_group { - my ( $group, $prompt ) = @_; - my $new = ''; - - my @available = sort keys %server_groups; - - if ( @available ) { - print "You can enter the name of a new group to create it.\n"; - - $new = prompt_list( - "Choose a server group $prompt", - $group, - sub { return @available }, - { map { $_ => join(' ', @{$server_groups{$_}}) } @available }); - - $new =~ s/\s.*//; - - if ( !exists $server_groups{$new} ) { - my $answer = prompt("There is no server group called '$new'. Create it?", undef, "y"); - if ( $answer eq 'y' ) { - add_new_server_group($new); - } - } - } - else { - $new = add_new_server_group(); - } - return $new; -} - -sub choose_or_create_connection { - my ( $cxns, $prompt ) = @_; - print "You can enter the name of a new connection to create it.\n"; - - my @available = sort keys %connections; - my $new_cxns = prompt_list( - "Choose connections $prompt", - $cxns, - sub { return @available }, - { map { $_ => $connections{$_}->{dsn} } @available }); - - my @new = unique(grep { !exists $connections{$_} } split(/\s+/, $new_cxns)); - foreach my $new ( @new ) { - my $answer = prompt("There is no connection called '$new'. Create it?", undef, "y"); - if ( $answer eq 'y' ) { - add_new_dsn($new); - } - } - - return unique(grep { exists $connections{$_} } split(/\s+/, $new_cxns)); -} - -# choose_servers {{{3 -sub choose_servers { - $clear_screen_sub->(); - my $mode = $config{mode}->{val}; - my $cxns = join(' ', get_connections()); - my @chosen = choose_or_create_connection($cxns, 'for this mode'); - $modes{$mode}->{connections} = \@chosen; - $modes{$mode}->{server_group} = ''; # Clear this because it overrides {connections} -} - -# display_license {{{3 -sub display_license { - $clear_screen_sub->(); - - print $innotop_license; - - pause(); -} - -# Data-retrieval functions {{{2 -# get_status_info {{{3 -# Get SHOW STATUS and SHOW VARIABLES together. -sub get_status_info { - my @cxns = @_; - if ( !$info_gotten{status}++ ) { - foreach my $cxn ( @cxns ) { - $vars{$cxn}->{$clock} ||= {}; - my $vars = $vars{$cxn}->{$clock}; - - my $sth = do_stmt($cxn, 'SHOW_STATUS') or next; - my $res = $sth->fetchall_arrayref(); - map { $vars->{$_->[0]} = $_->[1] || 0 } @$res; - - # Calculate hi-res uptime and add cxn to the hash. This duplicates get_driver_status, - # but it's most important to have consistency. - $vars->{Uptime_hires} ||= get_uptime($cxn); - $vars->{cxn} = $cxn; - - # Add SHOW VARIABLES to the hash - $sth = do_stmt($cxn, 'SHOW_VARIABLES') or next; - $res = $sth->fetchall_arrayref(); - map { $vars->{$_->[0]} = $_->[1] || 0 } @$res; - } - } -} - -# Chooses a thread for explaining, killing, etc... -# First arg is a func that can be called in grep. -sub choose_thread { - my ( $grep_cond, $prompt ) = @_; - - # Narrow the list to queries that can be explained. - my %thread_for = map { - # Eliminate innotop's own threads. - $_ => $dbhs{$_}->{dbh} ? $dbhs{$_}->{dbh}->{mysql_thread_id} : 0 - } keys %connections; - - my @candidates = grep { - $_->{id} != $thread_for{$_->{cxn}} && $grep_cond->($_) - } @current_queries; - return unless @candidates; - - # Find out which server. - my @cxns = unique map { $_->{cxn} } @candidates; - my ( $cxn ) = select_cxn('On which server', @cxns); - return unless $cxn && exists($connections{$cxn}); - - # Re-filter the list of candidates to only those on this server - @candidates = grep { $_->{cxn} eq $cxn } @candidates; - - # Find out which thread to do. - my $info; - if ( @candidates > 1 ) { - - # Sort longest-active first, then longest-idle. - my $sort_func = sub { - my ( $a, $b ) = @_; - return $a->{query} && !$b->{query} ? 1 - : $b->{query} && !$a->{query} ? -1 - : ($a->{time} || 0) <=> ($b->{time} || 0); - }; - my @threads = map { $_->{id} } reverse sort { $sort_func->($a, $b) } @candidates; - - print "\n"; - my $thread = prompt_list($prompt, - $threads[0], - sub { return @threads }); - return unless $thread && $thread =~ m/$int_regex/; - - # Find the info hash of that query on that server. - ( $info ) = grep { $thread == $_->{id} } @candidates; - } - else { - $info = $candidates[0]; - } - return $info; -} - -# analyze_query {{{3 -# Allows the user to show fulltext, explain, show optimized... -sub analyze_query { - my ( $action ) = @_; - - my $info = choose_thread( - sub { $_[0]->{query} }, - 'Select a thread to analyze', - ); - return unless $info; - - my %actions = ( - e => \&display_explain, - f => \&show_full_query, - o => \&show_optimized_query, - ); - do { - $actions{$action}->($info); - print "\n"; - $action = pause('Press e to explain, f for full query, o for optimized query'); - } while ( exists($actions{$action}) ); -} - -# inc {{{3 -# Returns the difference between two sets of variables/status/innodb stuff. -sub inc { - my ( $offset, $cxn ) = @_; - my $vars = $vars{$cxn}; - if ( $offset < 0 ) { - return $vars->{$clock}; - } - elsif ( exists $vars{$clock - $offset} && !exists $vars->{$clock - $offset - 1} ) { - return $vars->{$clock - $offset}; - } - my $cur = $vars->{$clock - $offset}; - my $pre = $vars->{$clock - $offset - 1}; - return { - # Numeric variables get subtracted, non-numeric get passed straight through. - map { - $_ => - ( (defined $cur->{$_} && $cur->{$_} =~ m/$num_regex/) - ? $cur->{$_} - ($pre->{$_} || 0) - : $cur->{$_} ) - } keys %{$cur} - }; -} - -# extract_values {{{3 -# Arguments are a set of values (which may be incremental, derived from -# current and previous), current, and previous values. -# TODO: there are a few places that don't remember prev set so can't pass it. -sub extract_values { - my ( $set, $cur, $pre, $tbl ) = @_; - - # Hook in event listeners - foreach my $listener ( @{$event_listener_for{extract_values}} ) { - $listener->extract_values($set, $cur, $pre, $tbl); - } - - my $result = {}; - my $meta = $tbl_meta{$tbl}; - my $cols = $meta->{cols}; - foreach my $key ( keys %$cols ) { - my $info = $cols->{$key} - or die "Column '$key' doesn't exist in $tbl"; - die "No func defined for '$key' in $tbl" - unless $info->{func}; - eval { - $result->{$key} = $info->{func}->($set, $cur, $pre) - }; - if ( $EVAL_ERROR ) { - if ( $config{debug}->{val} ) { - die $EVAL_ERROR; - } - $result->{$key} = $info->{num} ? 0 : ''; - } - } - return $result; -} - -# get_full_processlist {{{3 -sub get_full_processlist { - my @cxns = @_; - my @result; - foreach my $cxn ( @cxns ) { - my $stmt = do_stmt($cxn, 'PROCESSLIST') or next; - my $arr = $stmt->fetchall_arrayref({}); - push @result, map { $_->{cxn} = $cxn; $_ } @$arr; - } - return @result; -} - -# get_open_tables {{{3 -sub get_open_tables { - my @cxns = @_; - my @result; - foreach my $cxn ( @cxns ) { - my $stmt = do_stmt($cxn, 'OPEN_TABLES') or next; - my $arr = $stmt->fetchall_arrayref({}); - push @result, map { $_->{cxn} = $cxn; $_ } @$arr; - } - return @result; -} - -# get_innodb_status {{{3 -sub get_innodb_status { - my ( $cxns, $addl_sections ) = @_; - if ( !$config{skip_innodb}->{val} && !$info_gotten{innodb_status}++ ) { - - # Determine which sections need to be parsed - my %sections_required = - map { $tbl_meta{$_}->{innodb} => 1 } - grep { $_ && $tbl_meta{$_}->{innodb} } - get_visible_tables(); - - # Add in any other sections the caller requested. - foreach my $sec ( @$addl_sections ) { - $sections_required{$sec} = 1; - } - - foreach my $cxn ( @$cxns ) { - my $innodb_status_text; - - if ( $file ) { # Try to fetch status text from the file. - my @stat = stat($file); - - # Initialize the file. - if ( !$file_mtime ) { - # Initialize to 130k from the end of the file (because the limit - # on the size of innodb status is 128k even with Google's patches) - # and try to grab the last status from the file. - sysseek($file, (-128 * 1_024), 2); - } - - # Read from the file. - my $buffer; - if ( !$file_mtime || $file_mtime != $stat[9] ) { - $file_data = ''; - while ( sysread($file, $buffer, 4096) ) { - $file_data .= $buffer; - } - $file_mtime = $stat[9]; - } - - # Delete everything but the last InnoDB status text from the file. - $file_data =~ s/\A.*(?=^=====================================\n...... ........ INNODB MONITOR OUTPUT)//ms; - $innodb_status_text = $file_data; - } - - else { - my $stmt = do_stmt($cxn, 'INNODB_STATUS') or next; - $innodb_status_text = $stmt->fetchrow_hashref()->{status}; - } - - next unless $innodb_status_text - && substr($innodb_status_text, 0, 100) =~ m/INNODB MONITOR OUTPUT/; - - # Parse and merge into %vars storage - my %innodb_status = ( - $innodb_parser->get_status_hash( - $innodb_status_text, - $config{debug}->{val}, - \%sections_required, - 0, # don't parse full lock information - ) - ); - if ( !$innodb_status{IB_got_all} && $config{auto_wipe_dl}->{val} ) { - clear_deadlock($cxn); - } - - # Merge using a hash slice, which is the fastest way - $vars{$cxn}->{$clock} ||= {}; - my $hash = $vars{$cxn}->{$clock}; - @{$hash}{ keys %innodb_status } = values %innodb_status; - $hash->{cxn} = $cxn; - $hash->{Uptime_hires} ||= get_uptime($cxn); - } - } -} - -# clear_deadlock {{{3 -sub clear_deadlock { - my ( $cxn ) = @_; - return if $clearing_deadlocks++; - my $tbl = $connections{$cxn}->{dl_table}; - return unless $tbl; - - eval { - # Set up the table for creating a deadlock. - my $engine = version_ge($dbhs{$cxn}->{dbh}, '4.1.2') ? 'engine' : 'type'; - return unless do_query($cxn, "drop table if exists $tbl"); - return unless do_query($cxn, "create table $tbl(a int) $engine=innodb"); - return unless do_query($cxn, "delete from $tbl"); - return unless do_query($cxn, "insert into $tbl(a) values(0), (1)"); - return unless do_query($cxn, "commit"); # Or the children will block against the parent - - # Fork off two children to deadlock against each other. - my %children; - foreach my $child ( 0..1 ) { - my $pid = fork(); - if ( defined($pid) && $pid == 0 ) { # I am a child - deadlock_thread( $child, $tbl, $cxn ); - } - elsif ( !defined($pid) ) { - die("Unable to fork for clearing deadlocks!\n"); - } - # I already exited if I'm a child, so I'm the parent. - $children{$child} = $pid; - } - - # Wait for the children to exit. - foreach my $child ( keys %children ) { - my $pid = waitpid($children{$child}, 0); - } - - # Clean up. - do_query($cxn, "drop table $tbl"); - }; - if ( $EVAL_ERROR ) { - print $EVAL_ERROR; - pause(); - } - - $clearing_deadlocks = 0; -} - -sub get_master_logs { - my @cxns = @_; - my @result; - if ( !$info_gotten{master_logs}++ ) { - foreach my $cxn ( @cxns ) { - my $stmt = do_stmt($cxn, 'SHOW_MASTER_LOGS') or next; - push @result, @{$stmt->fetchall_arrayref({})}; - } - } - return @result; -} - -# get_master_slave_status {{{3 -sub get_master_slave_status { - my @cxns = @_; - if ( !$info_gotten{replication_status}++ ) { - foreach my $cxn ( @cxns ) { - $vars{$cxn}->{$clock} ||= {}; - my $vars = $vars{$cxn}->{$clock}; - $vars->{cxn} = $cxn; - - my $stmt = do_stmt($cxn, 'SHOW_MASTER_STATUS') or next; - my $res = $stmt->fetchall_arrayref({})->[0]; - @{$vars}{ keys %$res } = values %$res; - $stmt = do_stmt($cxn, 'SHOW_SLAVE_STATUS') or next; - $res = $stmt->fetchall_arrayref({})->[0]; - @{$vars}{ keys %$res } = values %$res; - $vars->{Uptime_hires} ||= get_uptime($cxn); - } - } -} - -sub is_func { - my ( $word ) = @_; - return defined(&$word) - || eval "my \$x= sub { $word }; 1" - || $EVAL_ERROR !~ m/^Bareword/; -} - -# Documentation {{{1 -# ############################################################################ -# I put this last as per the Dog book. -# ############################################################################ -=pod - -=head1 NAME - -innotop - MySQL and InnoDB transaction/status monitor. - -=head1 SYNOPSIS - -To monitor servers normally: - - innotop - -To monitor InnoDB status information from a file: - - innotop /var/log/mysql/mysqld.err - -To run innotop non-interactively in a pipe-and-filter configuration: - - innotop --count 5 -d 1 -n - -=head1 DESCRIPTION - -innotop monitors MySQL servers. Each of its modes shows you a different aspect -of what's happening in the server. For example, there's a mode for monitoring -replication, one for queries, and one for transactions. innotop refreshes its -data periodically, so you see an updating view. - -innotop has lots of features for power users, but you can start and run it with -virtually no configuration. If you're just getting started, see -L<"QUICK-START">. Press '?' at any time while running innotop for -context-sensitive help. - -=head1 QUICK-START - -To start innotop, open a terminal or command prompt. If you have installed -innotop on your system, you should be able to just type "innotop" and press -Enter; otherwise, you will need to change to innotop's directory and type "perl -innotop". - -The first thing innotop needs to know is how to connect to a MySQL server. You -can just enter the hostname of the server, for example "localhost" or -"127.0.0.1" if the server is on the same machine as innotop. After this innotop -will prompt you for a DSN (data source name). You should be able to just accept -the defaults by pressing Enter. - -When innotop asks you about a table to use when resetting InnoDB deadlock -information, just accept the default for now. This is an advanced feature you -can configure later (see L<"D: InnoDB Deadlocks"> for more). - -If you have a .my.cnf file with your MySQL connection defaults, innotop can read -it, and you won't need to specify a username and password if it's in that file. -Otherwise, you should answer 'y' to the next couple of prompts. - -After this, you should be connected, and innotop should show you something like -the following: - - InnoDB Txns (? for help) localhost, 01:11:19, InnoDB 10s :-), 50 QPS, - - CXN History Versions Undo Dirty Buf Used Bufs Txns MaxTxn - localhost 7 2035 0 0 0.00% 92.19% 1 07:34 - - CXN ID User Host Txn Status Time Undo Query Tex - localhost 98379 user1 webserver ACTIVE 07:34 0 SELECT `c - localhost 98450 user1 webserver ACTIVE 01:06 0 INSERT IN - localhost 97750 user1 webserver not starte 00:00 0 - localhost 98375 user1 appserver not starte 00:00 0 - -(This sample is truncated at the right so it will fit on a terminal when running -'man innotop') - -This sample comes from a quiet server with few transactions active. If your -server is busy, you'll see more output. Notice the first line on the screen, -which tells you what mode you're in and what server you're connected to. You -can change to other modes with keystrokes; press 'Q' to switch to a list of -currently running queries. - -Press the '?' key to see what keys are active in the current mode. You can -press any of these keys and innotop will either take the requested action or -prompt you for more input. If your system has Term::ReadLine support, you can -use TAB and other keys to auto-complete and edit input. - -To quit innotop, press the 'q' key. - -=head1 OPTIONS - -innotop is mostly configured via its configuration file, but some of the -configuration options can come from the command line. You can also specify a -file to monitor for InnoDB status output; see L<"MONITORING A FILE"> for more -details. - -You can negate some options by prefixing the option name with --no. For -example, --noinc (or --no-inc) negates L<"--inc">. - -=over - -=item --help - -Print a summary of command-line usage and exit. - -=item --color - -Enable or disable terminal coloring. Corresponds to the L<"color"> config file -setting. - -=item --config - -Specifies a configuration file to read. This option is non-sticky, that is to -say it does not persist to the configuration file itself. - -=item --nonint - -Enable non-interactive operation. See L<"NON-INTERACTIVE OPERATION"> for more. - -=item --count - -Refresh only the specified number of times (ticks) before exiting. Each refresh -is a pause for L<"interval"> seconds, followed by requesting data from MySQL -connections and printing it to the terminal. - -=item --delay - -Specifies the amount of time to pause between ticks (refreshes). Corresponds to -the configuration option L<"interval">. - -=item --mode - -Specifies the mode in which innotop should start. Corresponds to the -configuration option L<"mode">. - -=item --inc - -Specifies whether innotop should display absolute numbers or relative numbers -(offsets from their previous values). Corresponds to the configuration option -L<"status_inc">. - -=item --version - -Output version information and exit. - -=back - -=head1 HOTKEYS - -innotop is interactive, and you control it with key-presses. - -=over - -=item * - -Uppercase keys switch between modes. - -=item * - -Lowercase keys initiate some action within the current mode. - -=item * - -Other keys do something special like change configuration or show the -innotop license. - -=back - -Press '?' at any time to see the currently active keys and what they do. - -=head1 MODES - -Each of innotop's modes retrieves and displays a particular type of data from -the servers you're monitoring. You switch between modes with uppercase keys. -The following is a brief description of each mode, in alphabetical order. To -switch to the mode, press the key listed in front of its heading in the -following list: - -=over - -=item B: InnoDB Buffers - -This mode displays information about the InnoDB buffer pool, page statistics, -insert buffer, and adaptive hash index. The data comes from SHOW INNODB STATUS. - -This mode contains the L<"buffer_pool">, L<"page_statistics">, -L<"insert_buffers">, and L<"adaptive_hash_index"> tables by default. - -=item C: Command Summary - -This mode is similar to mytop's Command Summary mode. It shows the -L<"cmd_summary"> table, which looks something like the following: - - Command Summary (? for help) localhost, 25+07:16:43, 2.45 QPS, 3 thd, 5.0.40 - _____________________ Command Summary _____________________ - Name Value Pct Last Incr Pct - Select_scan 3244858 69.89% 2 100.00% - Select_range 1354177 29.17% 0 0.00% - Select_full_join 39479 0.85% 0 0.00% - Select_full_range_join 4097 0.09% 0 0.00% - Select_range_check 0 0.00% 0 0.00% - -The command summary table is built by extracting variables from -L<"STATUS_VARIABLES">. The variables must be numeric and must match the prefix -given by the L<"cmd_filter"> configuration variable. The variables are then -sorted by value descending and compared to the last variable, as shown above. -The percentage columns are percentage of the total of all variables in the -table, so you can see the relative weight of the variables. - -The example shows what you see if the prefix is "Select_". The default -prefix is "Com_". You can choose a prefix with the 's' key. - -It's rather like running SHOW VARIABLES LIKE "prefix%" with memory and -nice formatting. - -Values are aggregated across all servers. The Pct columns are not correctly -aggregated across multiple servers. This is a known limitation of the grouping -algorithm that may be fixed in the future. - -=item D: InnoDB Deadlocks - -This mode shows the transactions involved in the last InnoDB deadlock. A second -table shows the locks each transaction held and waited for. A deadlock is -caused by a cycle in the waits-for graph, so there should be two locks held and -one waited for unless the deadlock information is truncated. - -InnoDB puts deadlock information before some other information in the SHOW -INNODB STATUS output. If there are a lot of locks, the deadlock information can -grow very large, and there is a limit on the size of the SHOW INNODB -STATUS output. A large deadlock can fill the entire output, or even be -truncated, and prevent you from seeing other information at all. If you are -running innotop in another mode, for example T mode, and suddenly you don't see -anything, you might want to check and see if a deadlock has wiped out the data -you need. - -If it has, you can create a small deadlock to replace the large one. Use the -'w' key to 'wipe' the large deadlock with a small one. This will not work -unless you have defined a deadlock table for the connection (see L<"SERVER -CONNECTIONS">). - -You can also configure innotop to automatically detect when a large deadlock -needs to be replaced with a small one (see L<"auto_wipe_dl">). - -This mode displays the L<"deadlock_transactions"> and L<"deadlock_locks"> tables -by default. - -=item F: InnoDB Foreign Key Errors - -This mode shows the last InnoDB foreign key error information, such as the -table where it happened, when and who and what query caused it, and so on. - -InnoDB has a huge variety of foreign key error messages, and many of them are -just hard to parse. innotop doesn't always do the best job here, but there's -so much code devoted to parsing this messy, unparseable output that innotop is -likely never to be perfect in this regard. If innotop doesn't show you what -you need to see, just look at the status text directly. - -This mode displays the L<"fk_error"> table by default. - -=item I: InnoDB I/O Info - -This mode shows InnoDB's I/O statistics, including the I/O threads, pending I/O, -file I/O miscellaneous, and log statistics. It displays the L<"io_threads">, -L<"pending_io">, L<"file_io_misc">, and L<"log_statistics"> tables by default. - -=item L: Locks - -This mode shows information about current locks. At the moment only InnoDB -locks are supported, and by default you'll only see locks for which transactions -are waiting. This information comes from the TRANSACTIONS section of the InnoDB -status text. If you have a very busy server, you may have frequent lock waits; -it helps to be able to see which tables and indexes are the "hot spot" for -locks. If your server is running pretty well, this mode should show nothing. - -You can configure MySQL and innotop to monitor not only locks for which a -transaction is waiting, but those currently held, too. You can do this with the -InnoDB Lock Monitor (L<http://dev.mysql.com/doc/en/innodb-monitor.html>). It's -not documented in the MySQL manual, but creating the lock monitor with the -following statement also affects the output of SHOW INNODB STATUS, which innotop -uses: - - CREATE TABLE innodb_lock_monitor(a int) ENGINE=INNODB; - -This causes InnoDB to print its output to the MySQL file every 16 seconds or so, -as stated in the manual, but it also makes the normal SHOW INNODB STATUS output -include lock information, which innotop can parse and display (that's the -undocumented feature). - -This means you can do what may have seemed impossible: to a limited extent -(InnoDB truncates some information in the output), you can see which transaction -holds the locks something else is waiting for. You can also enable and disable -the InnoDB Lock Monitor with the key mappings in this mode. - -This mode displays the L<"innodb_locks"> table by default. Here's a sample of -the screen when one connection is waiting for locks another connection holds: - - _________________________________ InnoDB Locks __________________________ - CXN ID Type Waiting Wait Active Mode DB Table Index - localhost 12 RECORD 1 00:10 00:10 X test t1 PRIMARY - localhost 12 TABLE 0 00:10 00:10 IX test t1 - localhost 12 RECORD 1 00:10 00:10 X test t1 PRIMARY - localhost 11 TABLE 0 00:00 00:25 IX test t1 - localhost 11 RECORD 0 00:00 00:25 X test t1 PRIMARY - -You can see the first connection, ID 12, is waiting for a lock on the PRIMARY -key on test.t1, and has been waiting for 10 seconds. The second connection -isn't waiting, because the Waiting column is 0, but it holds locks on the same -index. That tells you connection 11 is blocking connection 12. - -=item M: Master/Slave Replication Status - -This mode shows the output of SHOW SLAVE STATUS and SHOW MASTER STATUS in three -tables. The first two divide the slave's status into SQL and I/O thread status, -and the last shows master status. Filters are applied to eliminate non-slave -servers from the slave tables, and non-master servers from the master table. - -This mode displays the L<"slave_sql_status">, L<"slave_io_status">, and -L<"master_status"> tables by default. - -=item O: Open Tables - -This section comes from MySQL's SHOW OPEN TABLES command. By default it is -filtered to show tables which are in use by one or more queries, so you can -get a quick look at which tables are 'hot'. You can use this to guess which -tables might be locked implicitly. - -This mode displays the L<"open_tables"> mode by default. - -=item Q: Query List - -This mode displays the output from SHOW FULL PROCESSLIST, much like B<mytop>'s -query list mode. This mode does B<not> show InnoDB-related information. This -is probably one of the most useful modes for general usage. - -There is an informative header that shows general status information about -your server. You can toggle it on and off with the 'h' key. By default, -innotop hides inactive processes and its own process. You can toggle these on -and off with the 'i' and 'a' keys. - -You can EXPLAIN a query from this mode with the 'e' key. This displays the -query's full text, the results of EXPLAIN, and in newer MySQL versions, even -the optimized query resulting from EXPLAIN EXTENDED. innotop also tries to -rewrite certain queries to make them EXPLAIN-able. For example, INSERT/SELECT -statements are rewritable. - -This mode displays the L<"q_header"> and L<"processlist"> tables by default. - -=item R: InnoDB Row Operations and Semaphores - -This mode shows InnoDB row operations, row operation miscellaneous, semaphores, -and information from the wait array. It displays the L<"row_operations">, -L<"row_operation_misc">, L<"semaphores">, and L<"wait_array"> tables by default. - -=item S: Variables & Status - -This mode calculates statistics, such as queries per second, and prints them out -in several different styles. You can show absolute values, or incremental values -between ticks. - -You can switch between the views by pressing a key. The 's' key prints a -single line each time the screen updates, in the style of B<vmstat>. The 'g' -key changes the view to a graph of the same numbers, sort of like B<tload>. -The 'v' key changes the view to a pivoted table of variable names on the left, -with successive updates scrolling across the screen from left to right. You can -choose how many updates to put on the screen with the L<"num_status_sets"> -configuration variable. - -Headers may be abbreviated to fit on the screen in interactive operation. You -choose which variables to display with the 'c' key, which selects from -predefined sets, or lets you create your own sets. You can edit the current set -with the 'e' key. - -This mode doesn't really display any tables like other modes. Instead, it uses -a table definition to extract and format the data, but it then transforms the -result in special ways before outputting it. It uses the L<"var_status"> table -definition for this. - -=item T: InnoDB Transactions - -This mode shows transactions from the InnoDB monitor's output, in B<top>-like -format. This mode is the reason I wrote innotop. - -You can kill queries or processes with the 'k' and 'x' keys, and EXPLAIN a query -with the 'e' or 'f' keys. InnoDB doesn't print the full query in transactions, -so explaining may not work right if the query is truncated. - -The informational header can be toggled on and off with the 'h' key. By -default, innotop hides inactive transactions and its own transaction. You can -toggle this on and off with the 'i' and 'a' keys. - -This mode displays the L<"t_header"> and L<"innodb_transactions"> tables by -default. - -=back - -=head1 INNOTOP STATUS - -The first line innotop displays is a "status bar" of sorts. What it contains -depends on the mode you're in, and what servers you're monitoring. The first -few words are always the innotop mode, such as "InnoDB Txns" for T mode, -followed by a reminder to press '?' for help at any time. - -=head2 ONE SERVER - -The simplest case is when you're monitoring a single server. In this case, the -name of the connection is next on the status line. This is the name you gave -when you created the connection -- most likely the MySQL server's hostname. -This is followed by the server's uptime. - -If you're in an InnoDB mode, such as T or B, the next word is "InnoDB" followed -by some information about the SHOW INNODB STATUS output used to render the -screen. The first word is the number of seconds since the last SHOW INNODB -STATUS, which InnoDB uses to calculate some per-second statistics. The next is -a smiley face indicating whether the InnoDB output is truncated. If the smiley -face is a :-), all is well; there is no truncation. A :^| means the transaction -list is so long, InnoDB has only printed out some of the transactions. Finally, -a frown :-( means the output is incomplete, which is probably due to a deadlock -printing too much lock information (see L<"D: InnoDB Deadlocks">). - -The next two words indicate the server's queries per second (QPS) and how many -threads (connections) exist. Finally, the server's version number is the last -thing on the line. - -=head2 MULTIPLE SERVERS - -If you are monitoring multiple servers (see L<"SERVER CONNECTIONS">), the status -line does not show any details about individual servers. Instead, it shows the -names of the connections that are active. Again, these are connection names you -specified, which are likely to be the server's hostname. A connection that has -an error is prefixed with an exclamation point. - -If you are monitoring a group of servers (see L<"SERVER GROUPS">), the status -line shows the name of the group. If any connection in the group has an -error, the group's name is followed by the fraction of the connections that -don't have errors. - -See L<"ERROR HANDLING"> for more details about innotop's error handling. - -=head2 MONITORING A FILE - -If you give a filename on the command line, innotop will not connect to ANY -servers at all. It will watch the specified file for InnoDB status output and -use that as its data source. It will always show a single connection called -'file'. And since it can't connect to a server, it can't determine how long the -server it's monitoring has been up; so it calculates the server's uptime as time -since innotop started running. - -=head1 SERVER ADMINISTRATION - -While innotop is primarily a monitor that lets you watch and analyze your -servers, it can also send commands to servers. The most frequently useful -commands are killing queries and stopping or starting slaves. - -You can kill a connection, or in newer versions of MySQL kill a query but not a -connection, from L<"Q: Query List"> and L<"T: InnoDB Transactions"> modes. -Press 'k' to issue a KILL command, or 'x' to issue a KILL QUERY command. -innotop will prompt you for the server and/or connection ID to kill (innotop -does not prompt you if there is only one possible choice for any input). -innotop pre-selects the longest-running query, or the oldest connection. -Confirm the command with 'y'. - -In L<"M: Master/Slave Replication Status"> mode, you can start and stop slaves -with the 'a' and 'o' keys, respectively. You can send these commands to many -slaves at once. innotop fills in a default command of START SLAVE or STOP SLAVE -for you, but you can actually edit the command and send anything you wish, such -as SET GLOBAL SQL_SLAVE_SKIP_COUNTER=1 to make the slave skip one binlog event -when it starts. - -You can also ask innotop to calculate the earliest binlog in use by any slave -and issue a PURGE MASTER LOGS on the master. Use the 'b' key for this. innotop -will prompt you for a master to run the command on, then prompt you for the -connection names of that master's slaves (there is no way for innotop to -determine this reliably itself). innotop will find the minimum binlog in use by -these slave connections and suggest it as the argument to PURGE MASTER LOGS. - -=head1 SERVER CONNECTIONS - -When you create a server connection, innotop asks you for a series of inputs, as -follows: - -=over - -=item DSN - -A DSN is a Data Source Name, which is the initial argument passed to the DBI -module for connecting to a server. It is usually of the form - - DBI:mysql:;mysql_read_default_group=mysql;host=HOSTNAME - -Since this DSN is passed to the DBD::mysql driver, you should read the driver's -documentation at L<"http://search.cpan.org/dist/DBD-mysql/lib/DBD/mysql.pm"> for -the exact details on all the options you can pass the driver in the DSN. You -can read more about DBI at L<http://dbi.perl.org/docs/>, and especially at -L<http://search.cpan.org/~timb/DBI/DBI.pm>. - -The mysql_read_default_group=mysql option lets the DBD driver read your MySQL -options files, such as ~/.my.cnf on UNIX-ish systems. You can use this to avoid -specifying a username or password for the connection. - -=item InnoDB Deadlock Table - -This optional item tells innotop a table name it can use to deliberately create -a small deadlock (see L<"D: InnoDB Deadlocks">). If you specify this option, -you just need to be sure the table doesn't exist, and that innotop can create -and drop the table with the InnoDB storage engine. You can safely omit or just -accept the default if you don't intend to use this. - -=item Username - -innotop will ask you if you want to specify a username. If you say 'y', it will -then prompt you for a user name. If you have a MySQL option file that specifies -your username, you don't have to specify a username. - -The username defaults to your login name on the system you're running innotop on. - -=item Password - -innotop will ask you if you want to specify a password. Like the username, the -password is optional, but there's an additional prompt that asks if you want to -save the password in the innotop configuration file. If you don't save it in -the configuration file, innotop will prompt you for a password each time it -starts. Passwords in the innotop configuration file are saved in plain text, -not encrypted in any way. - -=back - -Once you finish answering these questions, you should be connected to a server. -But innotop isn't limited to monitoring a single server; you can define many -server connections and switch between them by pressing the '@' key. See -L<"SWITCHING BETWEEN CONNECTIONS">. - -To create a new connection, press the '@' key and type the name of the new -connection, then follow the steps given above. - -=head1 SERVER GROUPS - -If you have multiple MySQL instances, you can put them into named groups, such -as 'all', 'masters', and 'slaves', which innotop can monitor all together. - -You can choose which group to monitor with the '#' key, and you can press the -TAB key to switch to the next group. If you're not currently monitoring a -group, pressing TAB selects the first group. - -To create a group, press the '#' key and type the name of your new group, then -type the names of the connections you want the group to contain. - -=head1 SWITCHING BETWEEN CONNECTIONS - -innotop lets you quickly switch which servers you're monitoring. The most basic -way is by pressing the '@' key and typing the name(s) of the connection(s) you -want to use. This setting is per-mode, so you can monitor different connections -in each mode, and innotop remembers which connections you choose. - -You can quickly switch to the 'next' connection in alphabetical order with the -'n' key. If you're monitoring a server group (see L<"SERVER GROUPS">) this will -switch to the first connection. - -You can also type many connection names, and innotop will fetch and display data -from them all. Just separate the connection names with spaces, for example -"server1 server2." Again, if you type the name of a connection that doesn't -exist, innotop will prompt you for connection information and create the -connection. - -Another way to monitor multiple connections at once is with server groups. You -can use the TAB key to switch to the 'next' group in alphabetical order, or if -you're not monitoring any groups, TAB will switch to the first group. - -innotop does not fetch data in parallel from connections, so if you are -monitoring a large group or many connections, you may notice increased delay -between ticks. - -When you monitor more than one connection, innotop's status bar changes. See -L<"INNOTOP STATUS">. - -=head1 ERROR HANDLING - -Error handling is not that important when monitoring a single connection, but is -crucial when you have many active connections. A crashed server or lost -connection should not crash innotop. As a result, innotop will continue to run -even when there is an error; it just won't display any information from the -connection that had an error. Because of this, innotop's behavior might confuse -you. It's a feature, not a bug! - -innotop does not continue to query connections that have errors, because they -may slow innotop and make it hard to use, especially if the error is a problem -connecting and causes a long time-out. Instead, innotop retries the connection -occasionally to see if the error still exists. If so, it will wait until some -point in the future. The wait time increases in ticks as the Fibonacci series, -so it tries less frequently as time passes. - -Since errors might only happen in certain modes because of the SQL commands -issued in those modes, innotop keeps track of which mode caused the error. If -you switch to a different mode, innotop will retry the connection instead of -waiting. - -By default innotop will display the problem in red text at the bottom of the -first table on the screen. You can disable this behavior with the -L<"show_cxn_errors_in_tbl"> configuration option, which is enabled by default. -If the L<"debug"> option is enabled, innotop will display the error at the -bottom of every table, not just the first. And if L<"show_cxn_errors"> is -enabled, innotop will print the error text to STDOUT as well. Error messages -might only display in the mode that caused the error, depending on the mode and -whether innotop is avoiding querying that connection. - -=head1 NON-INTERACTIVE OPERATION - -You can run innotop in non-interactive mode, in which case it is entirely -controlled from the configuration file and command-line options. To start -innotop in non-interactive mode, give the L"<--nonint"> command-line option. -This changes innotop's behavior in the following ways: - -=over - -=item * - -Certain Perl modules are not loaded. Term::Readline is not loaded, since -innotop doesn't prompt interactively. Term::ANSIColor and Win32::Console::ANSI -modules are not loaded. Term::ReadKey is still used, since innotop may have to -prompt for connection passwords when starting up. - -=item * - -innotop does not clear the screen after each tick. - -=item * - -innotop does not persist any changes to the configuration file. - -=item * - -If L<"--count"> is given and innotop is in incremental mode (see L<"status_inc"> -and L<"--inc">), innotop actually refreshes one more time than specified so it -can print incremental statistics. This suppresses output during the first -tick, so innotop may appear to hang. - -=item * - -innotop only displays the first table in each mode. This is so the output can -be easily processed with other command-line utilities such as awk and sed. To -change which tables display in each mode, see L<"TABLES">. Since L<"Q: Query -List"> mode is so important, innotop automatically disables the L<"q_header"> -table. This ensures you'll see the L<"processlist"> table, even if you have -innotop configured to show the q_header table during interactive operation. -Similarly, in L<"T: InnoDB Transactions"> mode, the L<"t_header"> table is -suppressed so you see only the L<"innodb_transactions"> table. - -=item * - -All output is tab-separated instead of being column-aligned with whitespace, and -innotop prints the full contents of each table instead of only printing one -screenful at a time. - -=item * - -innotop only prints column headers once instead of every tick (see -L<"hide_hdr">). innotop does not print table captions (see -L<"display_table_captions">). innotop ensures there are no empty lines in the -output. - -=item * - -innotop does not honor the L<"shorten"> transformation, which normally shortens -some numbers to human-readable formats. - -=item * - -innotop does not print a status line (see L<"INNOTOP STATUS">). - -=back - -=head1 CONFIGURING - -Nearly everything about innotop is configurable. Most things are possible to -change with built-in commands, but you can also edit the configuration file. - -While running innotop, press the '$' key to bring up the configuration editing -dialog. Press another key to select the type of data you want to edit: - -=over - -=item S: Statement Sleep Times - -Edits SQL statement sleep delays, which make innotop pause for the specified -amount of time after executing a statement. See L<"SQL STATEMENTS"> for a -definition of each statement and what it does. By default innotop does not -delay after any statements. - -This feature is included so you can customize the side-effects caused by -monitoring your server. You may not see any effects, but some innotop users -have noticed that certain MySQL versions under very high load with InnoDB -enabled take longer than usual to execute SHOW GLOBAL STATUS. If innotop calls -SHOW FULL PROCESSLIST immediately afterward, the processlist contains more -queries than the machine actually averages at any given moment. Configuring -innotop to pause briefly after calling SHOW GLOBAL STATUS alleviates this -effect. - -Sleep times are stored in the L<"stmt_sleep_times"> section of the configuration -file. Fractional-second sleeps are supported, subject to your hardware's -limitations. - -=item c: Edit Columns - -Starts the table editor on one of the displayed tables. See L<"TABLE EDITOR">. -An alternative way to start the table editor without entering the configuration -dialog is with the '^' key. - -=item g: General Configuration - -Starts the configuration editor to edit global and mode-specific configuration -variables (see L<"MODES">). innotop prompts you to choose a variable from among -the global and mode-specific ones depending on the current mode. - -=item k: Row-Coloring Rules - -Starts the row-coloring rules editor on one of the displayed table(s). See -L<"COLORS"> for details. - -=item p: Manage Plugins - -Starts the plugin configuration editor. See L<"PLUGINS"> for details. - -=item s: Server Groups - -Lets you create and edit server groups. See L<"SERVER GROUPS">. - -=item t: Choose Displayed Tables - -Lets you choose which tables to display in this mode. See L<"MODES"> and -L<"TABLES">. - -=back - -=head1 CONFIGURATION FILE - -innotop's default configuration file location is in $HOME/.innotop, but can be -overridden with the L<"--config"> command-line option. You can edit it by hand -safely. innotop reads the configuration file when it starts, and writes it out -again when it exits, so any changes you make while innotop is running will be -lost. - -innotop doesn't store its entire configuration in the configuration file. It -has a huge set of default configuration that it holds only in memory, and the -configuration file only overrides these defaults. When you customize a default -setting, innotop notices, and then stores the customizations into the file. -This keeps the file size down, makes it easier to edit, and makes upgrades -easier. - -A configuration file can be made read-only. See L<"readonly">. - -The configuration file is arranged into sections like an INI file. Each -section begins with [section-name] and ends with [/section-name]. Each -section's entries have a different syntax depending on the data they need to -store. You can put comments in the file; any line that begins with a # -character is a comment. innotop will not read the comments, so it won't write -them back out to the file when it exits. Comments in read-only configuration -files are still useful, though. - -The first line in the file is innotop's version number. This lets innotop -notice when the file format is not backwards-compatible, and upgrade smoothly -without destroying your customized configuration. - -The following list describes each section of the configuration file and the data -it contains: - -=over - -=item general - -The 'general' section contains global configuration variables and variables that -may be mode-specific, but don't belong in any other section. The syntax is a -simple key=value list. innotop writes a comment above each value to help you -edit the file by hand. - -=over - -=item S_func - -Controls S mode presentation (see L<"S: Variables & Status">). If g, values are -graphed; if s, values are like vmstat; if p, values are in a pivoted table. - -=item S_set - -Specifies which set of variables to display in L<"S: Variables & Status"> mode. -See L<"VARIABLE SETS">. - -=item auto_wipe_dl - -Instructs innotop to automatically wipe large deadlocks when it notices them. -When this happens you may notice a slight delay. At the next tick, you will -usually see the information that was being truncated by the large deadlock. - -=item charset - -Specifies what kind of characters to allow through the L<"no_ctrl_char"> -transformation. This keeps non-printable characters from confusing a -terminal when you monitor queries that contain binary data, such as images. - -The default is 'ascii', which considers anything outside normal ASCII to be a -control character. The other allowable values are 'unicode' and 'none'. 'none' -considers every character a control character, which can be useful for -collapsing ALL text fields in queries. - -=item cmd_filter - -This is the prefix that filters variables in L<"C: Command Summary"> mode. - -=item color - -Whether terminal coloring is permitted. - -=item cxn_timeout - -On MySQL versions 4.0.3 and newer, this variable is used to set the connection's -timeout, so MySQL doesn't close the connection if it is not used for a while. -This might happen because a connection isn't monitored in a particular mode, for -example. - -=item debug - -This option enables more verbose errors and makes innotop more strict in some -places. It can help in debugging filters and other user-defined code. It also -makes innotop write a lot of information to L<"debugfile"> when there is a -crash. - -=item debugfile - -A file to which innotop will write information when there is a crash. See -L<"FILES">. - -=item display_table_captions - -innotop displays a table caption above most tables. This variable suppresses or -shows captions on all tables globally. Some tables are configured with the -hide_caption property, which overrides this. - -=item global - -Whether to show GLOBAL variables and status. innotop only tries to do this on -servers which support the GLOBAL option to SHOW VARIABLES and SHOW STATUS. In -some MySQL versions, you need certain privileges to do this; if you don't have -them, innotop will not be able to fetch any variable and status data. This -configuration variable lets you run innotop and fetch what data you can even -without the elevated privileges. - -I can no longer find or reproduce the situation where GLOBAL wasn't allowed, but -I know there was one. - -=item graph_char - -Defines the character to use when drawing graphs in L<"S: Variables & Status"> -mode. - -=item header_highlight - -Defines how to highlight column headers. This only works if Term::ANSIColor is -available. Valid values are 'bold' and 'underline'. - -=item hide_hdr - -Hides column headers globally. - -=item interval - -The interval at which innotop will refresh its data (ticks). The interval is -implemented as a sleep time between ticks, so the true interval will vary -depending on how long it takes innotop to fetch and render data. - -This variable accepts fractions of a second. - -=item mode - -The mode in which innotop should start. Allowable arguments are the same as the -key presses that select a mode interactively. See L<"MODES">. - -=item num_digits - -How many digits to show in fractional numbers and percents. This variable's -range is between 0 and 9 and can be set directly from L<"S: Variables & Status"> -mode with the '+' and '-' keys. It is used in the L<"set_precision">, -L<"shorten">, and L<"percent"> transformations. - -=item num_status_sets - -Controls how many sets of status variables to display in pivoted L<"S: Variables -& Status"> mode. It also controls the number of old sets of variables innotop -keeps in its memory, so the larger this variable is, the more memory innotop -uses. - -=item plugin_dir - -Specifies where plugins can be found. By default, innotop stores plugins in the -'plugins' subdirectory of your innotop configuration directory. - -=item readonly - -Whether the configuration file is readonly. This cannot be set interactively, -because it would prevent itself from being written to the configuration file. - -=item show_cxn_errors - -Makes innotop print connection errors to STDOUT. See L<"ERROR HANDLING">. - -=item show_cxn_errors_in_tbl - -Makes innotop display connection errors as rows in the first table on screen. -See L<"ERROR HANDLING">. - -=item show_percent - -Adds a '%' character after the value returned by the L<"percent"> -transformation. - -=item show_statusbar - -Controls whether to show the status bar in the display. See L<"INNOTOP -STATUS">. - -=item skip_innodb - -Disables fetching SHOW INNODB STATUS, in case your server(s) do not have InnoDB -enabled and you don't want innotop to try to fetch it. This can also be useful -when you don't have the SUPER privilege, required to run SHOW INNODB STATUS. - -=item status_inc - -Whether to show absolute or incremental values for status variables. -Incremental values are calculated as an offset from the last value innotop saw -for that variable. This is a global setting, but will probably become -mode-specific at some point. Right now it is honored a bit inconsistently; some -modes don't pay attention to it. - -=back - -=item plugins - -This section holds a list of package names of active plugins. If the plugin -exists, innotop will activate it. See L<"PLUGINS"> for more information. - -=item filters - -This section holds user-defined filters (see L<"FILTERS">). Each line is in the -format filter_name=text='filter text' tbls='table list'. - -The filter text is the text of the subroutine's code. The table list is a list -of tables to which the filter can apply. By default, user-defined filters apply -to the table for which they were created, but you can manually override that by -editing the definition in the configuration file. - -=item active_filters - -This section stores which filters are active on each table. Each line is in the -format table_name=filter_list. - -=item tbl_meta - -This section stores user-defined or user-customized columns (see L<"COLUMNS">). -Each line is in the format col_name=properties, where the properties are a -name=quoted-value list. - -=item connections - -This section holds the server connections you have defined. Each line is in the -format name=properties, where the properties are a name=value list. The -properties are self-explanatory, and the only one that is treated specially is -'pass' which is only present if 'savepass' is set. See L<"SERVER CONNECTIONS">. - -=item active_connections - -This section holds a list of which connections are active in each mode. Each -line is in the format mode_name=connection_list. - -=item server_groups - -This section holds server groups. Each line is in the format -name=connection_list. See L<"SERVER GROUPS">. - -=item active_server_groups - -This section holds a list of which server group is active in each mode. Each -line is in the format mode_name=server_group. - -=item max_values_seen - -This section holds the maximum values seen for variables. This is used to scale -the graphs in L<"S: Variables & Status"> mode. Each line is in the format -name=value. - -=item active_columns - -This section holds table column lists. Each line is in the format -tbl_name=column_list. See L<"COLUMNS">. - -=item sort_cols - -This section holds the sort definition. Each line is in the format -tbl_name=column_list. If a column is prefixed with '-', that column sorts -descending. See L<"SORTING">. - -=item visible_tables - -This section defines which tables are visible in each mode. Each line is in the -format mode_name=table_list. See L<"TABLES">. - -=item varsets - -This section defines variable sets for use in L<"S: Status & Variables"> mode. -Each line is in the format name=variable_list. See L<"VARIABLE SETS">. - -=item colors - -This section defines colorization rules. Each line is in the format -tbl_name=property_list. See L<"COLORS">. - -=item stmt_sleep_times - -This section contains statement sleep times. Each line is in the format -statement_name=sleep_time. See L<"S: Statement Sleep Times">. - -=item group_by - -This section contains column lists for table group_by expressions. Each line is -in the format tbl_name=column_list. See L<"GROUPING">. - -=back - -=head1 CUSTOMIZING - -You can customize innotop a great deal. For example, you can: - -=over - -=item * - -Choose which tables to display, and in what order. - -=item * - -Choose which columns are in those tables, and create new columns. - -=item * - -Filter which rows display with built-in filters, user-defined filters, and -quick-filters. - -=item * - -Sort the rows to put important data first or group together related rows. - -=item * - -Highlight rows with color. - -=item * - -Customize the alignment, width, and formatting of columns, and apply -transformations to columns to extract parts of their values or format the values -as you wish (for example, shortening large numbers to familiar units). - -=item * - -Design your own expressions to extract and combine data as you need. This gives -you unlimited flexibility. - -=back - -All these and more are explained in the following sections. - -=head2 TABLES - -A table is what you'd expect: a collection of columns. It also has some other -properties, such as a caption. Filters, sorting rules, and colorization rules -belong to tables and are covered in later sections. - -Internally, table meta-data is defined in a data structure called %tbl_meta. -This hash holds all built-in table definitions, which contain a lot of default -instructions to innotop. The meta-data includes the caption, a list of columns -the user has customized, a list of columns, a list of visible columns, a list of -filters, color rules, a sort-column list, sort direction, and some information -about the table's data sources. Most of this is customizable via the table -editor (see L<"TABLE EDITOR">). - -You can choose which tables to show by pressing the '$' key. See L<"MODES"> and -L<"TABLES">. - -The table life-cycle is as follows: - -=over - -=item * - -Each table begins with a data source, which is an array of hashes. See below -for details on data sources. - -=item * - -Each element of the data source becomes a row in the final table. - -=item * - -For each element in the data source, innotop extracts values from the source and -creates a row. This row is another hash, which later steps will refer to as -$set. The values innotop extracts are determined by the table's columns. Each -column has an extraction subroutine, compiled from an expression (see -L<"EXPRESSIONS">). The resulting row is a hash whose keys are named the same as -the column name. - -=item * - -innotop filters the rows, removing those that don't need to be displayed. See -L<"FILTERS">. - -=item * - -innotop sorts the rows. See L<"SORTING">. - -=item * - -innotop groups the rows together, if specified. See L<"GROUPING">. - -=item * - -innotop colorizes the rows. See L<"COLORS">. - -=item * - -innotop transforms the column values in each row. See L<"TRANSFORMATIONS">. - -=item * - -innotop optionally pivots the rows (see L<"PIVOTING">), then filters and sorts -them. - -=item * - -innotop formats and justifies the rows as a table. During this step, innotop -applies further formatting to the column values, including alignment, maximum -and minimum widths. innotop also does final error checking to ensure there are -no crashes due to undefined values. innotop then adds a caption if specified, -and the table is ready to print. - -=back - -The lifecycle is slightly different if the table is pivoted, as noted above. To -clarify, if the table is pivoted, the process is extract, group, transform, -pivot, filter, sort, create. If it's not pivoted, the process is extract, -filter, sort, group, color, transform, create. This slightly convoluted process -doesn't map all that well to SQL, but pivoting complicates things pretty -thoroughly. Roughly speaking, filtering and sorting happen as late as needed to -effect the final result as you might expect, but as early as possible for -efficiency. - -Each built-in table is described below: - -=over - -=item adaptive_hash_index - -Displays data about InnoDB's adaptive hash index. Data source: -L<"STATUS_VARIABLES">. - -=item buffer_pool - -Displays data about InnoDB's buffer pool. Data source: L<"STATUS_VARIABLES">. - -=item cmd_summary - -Displays weighted status variables. Data source: L<"STATUS_VARIABLES">. - -=item deadlock_locks - -Shows which locks were held and waited for by the last detected deadlock. Data -source: L<"DEADLOCK_LOCKS">. - -=item deadlock_transactions - -Shows transactions involved in the last detected deadlock. Data source: -L<"DEADLOCK_TRANSACTIONS">. - -=item explain - -Shows the output of EXPLAIN. Data source: L<"EXPLAIN">. - -=item file_io_misc - -Displays data about InnoDB's file and I/O operations. Data source: -L<"STATUS_VARIABLES">. - -=item fk_error - -Displays various data about InnoDB's last foreign key error. Data source: -L<"STATUS_VARIABLES">. - -=item innodb_locks - -Displays InnoDB locks. Data source: L<"INNODB_LOCKS">. - -=item innodb_transactions - -Displays data about InnoDB's current transactions. Data source: -L<"INNODB_TRANSACTIONS">. - -=item insert_buffers - -Displays data about InnoDB's insert buffer. Data source: L<"STATUS_VARIABLES">. - -=item io_threads - -Displays data about InnoDB's I/O threads. Data source: L<"IO_THREADS">. - -=item log_statistics - -Displays data about InnoDB's logging system. Data source: L<"STATUS_VARIABLES">. - -=item master_status - -Displays replication master status. Data source: L<"STATUS_VARIABLES">. - -=item open_tables - -Displays open tables. Data source: L<"OPEN_TABLES">. - -=item page_statistics - -Displays InnoDB page statistics. Data source: L<"STATUS_VARIABLES">. - -=item pending_io - -Displays InnoDB pending I/O operations. Data source: L<"STATUS_VARIABLES">. - -=item processlist - -Displays current MySQL processes (threads/connections). Data source: -L<"PROCESSLIST">. - -=item q_header - -Displays various status values. Data source: L<"STATUS_VARIABLES">. - -=item row_operation_misc - -Displays data about InnoDB's row operations. Data source: -L<"STATUS_VARIABLES">. - -=item row_operations - -Displays data about InnoDB's row operations. Data source: -L<"STATUS_VARIABLES">. - -=item semaphores - -Displays data about InnoDB's semaphores and mutexes. Data source: -L<"STATUS_VARIABLES">. - -=item slave_io_status - -Displays data about the slave I/O thread. Data source: -L<"STATUS_VARIABLES">. - -=item slave_sql_status - -Displays data about the slave SQL thread. Data source: L<"STATUS_VARIABLES">. - -=item t_header - -Displays various InnoDB status values. Data source: L<"STATUS_VARIABLES">. - -=item var_status - -Displays user-configurable data. Data source: L<"STATUS_VARIABLES">. - -=item wait_array - -Displays data about InnoDB's OS wait array. Data source: L<"OS_WAIT_ARRAY">. - -=back - -=head2 COLUMNS - -Columns belong to tables. You can choose a table's columns by pressing the '^' -key, which starts the L<"TABLE EDITOR"> and lets you choose and edit columns. -Pressing 'e' from within the table editor lets you edit the column's properties: - -=over - -=item * - -hdr: a column header. This appears in the first row of the table. - -=item * - -just: justification. '-' means left-justified and '' means right-justified, -just as with printf formatting codes (not a coincidence). - -=item * - -dec: whether to further align the column on the decimal point. - -=item * - -num: whether the column is numeric. This affects how values are sorted -(lexically or numerically). - -=item * - -label: a small note about the column, which appears in dialogs that help the -user choose columns. - -=item * - -src: an expression that innotop uses to extract the column's data from its -source (see L<"DATA SOURCES">). See L<"EXPRESSIONS"> for more on expressions. - -=item * - -minw: specifies a minimum display width. This helps stabilize the display, -which makes it easier to read if the data is changing frequently. - -=item * - -maxw: similar to minw. - -=item * - -trans: a list of column transformations. See L<"TRANSFORMATIONS">. - -=item * - -agg: an aggregate function. See L<"GROUPING">. The default is L<"first">. - -=item * - -aggonly: controls whether the column only shows when grouping is enabled on the -table (see L<"GROUPING">). By default, this is disabled. This means columns -will always be shown by default, whether grouping is enabled or not. If a -column's aggonly is set true, the column will appear when you toggle grouping on -the table. Several columns are set this way, such as the count column on -L<"processlist"> and L<"innodb_transactions">, so you don't see a count when the -grouping isn't enabled, but you do when it is. - -=back - -=head2 FILTERS - -Filters remove rows from the display. They behave much like a WHERE clause in -SQL. innotop has several built-in filters, which remove irrelevant information -like inactive queries, but you can define your own as well. innotop also lets -you create quick-filters, which do not get saved to the configuration file, and -are just an easy way to quickly view only some rows. - -You can enable or disable a filter on any table. Press the '%' key (mnemonic: % -looks kind of like a line being filtered between two circles) and choose which -table you want to filter, if asked. You'll then see a list of possible filters -and a list of filters currently enabled for that table. Type the names of -filters you want to apply and press Enter. - -=head3 USER-DEFINED FILTERS - -If you type a name that doesn't exist, innotop will prompt you to create the -filter. Filters are easy to create if you know Perl, and not hard if you don't. -What you're doing is creating a subroutine that returns true if the row should -be displayed. The row is a hash reference passed to your subroutine as $set. - -For example, imagine you want to filter the processlist table so you only see -queries that have been running more than five minutes. Type a new name for your -filter, and when prompted for the subroutine body, press TAB to initiate your -terminal's auto-completion. You'll see the names of the columns in the -L<"processlist"> table (innotop generally tries to help you with auto-completion -lists). You want to filter on the 'time' column. Type the text "$set->{time} > -300" to return true when the query is more than five minutes old. That's all -you need to do. - -In other words, the code you're typing is surrounded by an implicit context, -which looks like this: - - sub filter { - my ( $set ) = @_; - # YOUR CODE HERE - } - -If your filter doesn't work, or if something else suddenly behaves differently, -you might have made an error in your filter, and innotop is silently catching -the error. Try enabling L<"debug"> to make innotop throw an error instead. - -=head3 QUICK-FILTERS - -innotop's quick-filters are a shortcut to create a temporary filter that doesn't -persist when you restart innotop. To create a quick-filter, press the '/' key. -innotop will prompt you for the column name and filter text. Again, you can use -auto-completion on column names. The filter text can be just the text you want -to "search for." For example, to filter the L<"processlist"> table on queries -that refer to the products table, type '/' and then 'info product'. - -The filter text can actually be any Perl regular expression, but of course a -literal string like 'product' works fine as a regular expression. - -Behind the scenes innotop compiles the quick-filter into a specially tagged -filter that is otherwise like any other filter. It just isn't saved to the -configuration file. - -To clear quick-filters, press the '\' key and innotop will clear them all at -once. - -=head2 SORTING - -innotop has sensible built-in defaults to sort the most important rows to the -top of the table. Like anything else in innotop, you can customize how any -table is sorted. - -To start the sort dialog, start the L<"TABLE EDITOR"> with the '^' key, choose a -table if necessary, and press the 's' key. You'll see a list of columns you can -use in the sort expression and the current sort expression, if any. Enter a -list of columns by which you want to sort and press Enter. If you want to -reverse sort, prefix the column name with a minus sign. For example, if you -want to sort by column a ascending, then column b descending, type 'a -b'. You -can also explicitly add a + in front of columns you want to sort ascending, but -it's not required. - -Some modes have keys mapped to open this dialog directly, and to quickly reverse -sort direction. Press '?' as usual to see which keys are mapped in any mode. - -=head2 GROUPING - -innotop can group, or aggregate, rows together (I use the terms -interchangeably). This is quite similar to an SQL GROUP BY clause. You can -specify to group on certain columns, or if you don't specify any, the entire set -of rows is treated as one group. This is quite like SQL so far, but unlike SQL, -you can also select un-grouped columns. innotop actually aggregates every -column. If you don't explicitly specify a grouping function, the default is -'first'. This is basically a convenience so you don't have to specify an -aggregate function for every column you want in the result. - -You can quickly toggle grouping on a table with the '=' key, which toggles its -aggregate property. This property doesn't persist to the config file. - -The columns by which the table is grouped are specified in its group_by -property. When you turn grouping on, innotop places the group_by columns at the -far left of the table, even if they're not supposed to be visible. The rest of -the visible columns appear in order after them. - -Two tables have default group_by lists and a count column built in: -L<"processlist"> and L<"innodb_transactions">. The grouping is by connection -and status, so you can quickly see how many queries or transactions are in a -given status on each server you're monitoring. The time columns are aggregated -as a sum; other columns are left at the default 'first' aggregation. - -By default, the table shown in L<"S: Variables & Status"> mode also uses -grouping so you can monitor variables and status across many servers. The -default aggregation function in this mode is 'avg'. - -Valid grouping functions are defined in the %agg_funcs hash. They include - -=over - -=item first - -Returns the first element in the group. - -=item count - -Returns the number of elements in the group, including undefined elements, much -like SQL's COUNT(*). - -=item avg - -Returns the average of defined elements in the group. - -=item sum - -Returns the sum of elements in the group. - -=back - -Here's an example of grouping at work. Suppose you have a very busy server with -hundreds of open connections, and you want to see how many connections are in -what status. Using the built-in grouping rules, you can press 'Q' to enter -L<"Q: Query List"> mode. Press '=' to toggle grouping (if necessary, select the -L<"processlist"> table when prompted). - -Your display might now look like the following: - - Query List (? for help) localhost, 32:33, 0.11 QPS, 1 thd, 5.0.38-log - - CXN Cmd Cnt ID User Host Time Query - localhost Query 49 12933 webusr localhost 19:38 SELECT * FROM - localhost Sending Da 23 2383 webusr localhost 12:43 SELECT col1, - localhost Sleep 120 140 webusr localhost 5:18:12 - localhost Statistics 12 19213 webusr localhost 01:19 SELECT * FROM - -That's actually quite a worrisome picture. You've got a lot of idle connections -(Sleep), and some connections executing queries (Query and Sending Data). -That's okay, but you also have a lot in Statistics status, collectively spending -over a minute. That means the query optimizer is having a really hard time -optimizing your statements. Something is wrong; it should normally take -milliseconds to optimize queries. You might not have seen this pattern if you -didn't look at your connections in aggregate. (This is a made-up example, but -it can happen in real life). - -=head2 PIVOTING - -innotop can pivot a table for more compact display, similar to a Pivot Table in -a spreadsheet (also known as a crosstab). Pivoting a table makes columns into -rows. Assume you start with this table: - - foo bar - === === - 1 3 - 2 4 - -After pivoting, the table will look like this: - - name set0 set1 - ==== ==== ==== - foo 1 2 - bar 3 4 - -To get reasonable results, you might need to group as well as pivoting. -innotop currently does this for L<"S: Variables & Status"> mode. - -=head2 COLORS - -By default, innotop highlights rows with color so you can see at a glance which -rows are more important. You can customize the colorization rules and add your -own to any table. Open the table editor with the '^' key, choose a table if -needed, and press 'o' to open the color editor dialog. - -The color editor dialog displays the rules applied to the table, in the order -they are evaluated. Each row is evaluated against each rule to see if the rule -matches the row; if it does, the row gets the specified color, and no further -rules are evaluated. The rules look like the following: - - state eq Locked black on_red - cmd eq Sleep white - user eq system user white - cmd eq Connect white - cmd eq Binlog Dump white - time > 600 red - time > 120 yellow - time > 60 green - time > 30 cyan - -This is the default rule set for the L<"processlist"> table. In order of -priority, these rules make locked queries black on a red background, "gray out" -connections from replication and sleeping queries, and make queries turn from -cyan to red as they run longer. - -(For some reason, the ANSI color code "white" is actually a light gray. Your -terminal's display may vary; experiment to find colors you like). - -You can use keystrokes to move the rules up and down, which re-orders their -priority. You can also delete rules and add new ones. If you add a new rule, -innotop prompts you for the column, an operator for the comparison, a value -against which to compare the column, and a color to assign if the rule matches. -There is auto-completion and prompting at each step. - -The value in the third step needs to be correctly quoted. innotop does not try -to quote the value because it doesn't know whether it should treat the value as -a string or a number. If you want to compare the column against a string, as -for example in the first rule above, you should enter 'Locked' surrounded by -quotes. If you get an error message about a bareword, you probably should have -quoted something. - -=head2 EXPRESSIONS - -Expressions are at the core of how innotop works, and are what enables you to -extend innotop as you wish. Recall the table lifecycle explained in -L<"TABLES">. Expressions are used in the earliest step, where it extracts -values from a data source to form rows. - -It does this by calling a subroutine for each column, passing it the source data -set, a set of current values, and a set of previous values. These are all -needed so the subroutine can calculate things like the difference between this -tick and the previous tick. - -The subroutines that extract the data from the set are compiled from -expressions. This gives significantly more power than just naming the values to -fill the columns, because it allows the column's value to be calculated from -whatever data is necessary, but avoids the need to write complicated and lengthy -Perl code. - -innotop begins with a string of text that can look as simple as a value's name -or as complicated as a full-fledged Perl expression. It looks at each -'bareword' token in the string and decides whether it's supposed to be a key -into the $set hash. A bareword is an unquoted value that isn't already -surrounded by code-ish things like dollar signs or curly brackets. If innotop -decides that the bareword isn't a function or other valid Perl code, it converts -it into a hash access. After the whole string is processed, innotop compiles a -subroutine, like this: - - sub compute_column_value { - my ( $set, $cur, $pre ) = @_; - my $val = # EXPANDED STRING GOES HERE - return $val; - } - -Here's a concrete example, taken from the header table L<"q_header"> in L<"Q: -Query List"> mode. This expression calculates the qps, or Queries Per Second, -column's values, from the values returned by SHOW STATUS: - - Questions/Uptime_hires - -innotop decides both words are barewords, and transforms this expression into -the following Perl code: - - $set->{Questions}/$set->{Uptime_hires} - -When surrounded by the rest of the subroutine's code, this is executable Perl -that calculates a high-resolution queries-per-second value. - -The arguments to the subroutine are named $set, $cur, and $pre. In most cases, -$set and $cur will be the same values. However, if L<"status_inc"> is set, $cur -will not be the same as $set, because $set will already contain values that are -the incremental difference between $cur and $pre. - -Every column in innotop is computed by subroutines compiled in the same fashion. -There is no difference between innotop's built-in columns and user-defined -columns. This keeps things consistent and predictable. - -=head2 TRANSFORMATIONS - -Transformations change how a value is rendered. For example, they can take a -number of seconds and display it in H:M:S format. The following transformations -are defined: - -=over - -=item commify - -Adds commas to large numbers every three decimal places. - -=item dulint_to_int - -Accepts two unsigned integers and converts them into a single longlong. This is -useful for certain operations with InnoDB, which uses two integers as -transaction identifiers, for example. - -=item no_ctrl_char - -Removes quoted control characters from the value. This is affected by the -L<"charset"> configuration variable. - -This transformation only operates within quoted strings, for example, values to -a SET clause in an UPDATE statement. It will not alter the UPDATE statement, -but will collapse the quoted string to [BINARY] or [TEXT], depending on the -charset. - -=item percent - -Converts a number to a percentage by multiplying it by two, formatting it with -L<"num_digits"> digits after the decimal point, and optionally adding a percent -sign (see L<"show_percent">). - -=item secs_to_time - -Formats a number of seconds as time in days+hours:minutes:seconds format. - -=item set_precision - -Formats numbers with L<"num_digits"> number of digits after the decimal point. - -=item shorten - -Formats a number as a unit of 1024 (k/M/G/T) and with L<"num_digits"> number of -digits after the decimal point. - -=back - -=head2 TABLE EDITOR - -The innotop table editor lets you customize tables with keystrokes. You start -the table editor with the '^' key. If there's more than one table on the -screen, it will prompt you to choose one of them. Once you do, innotop will -show you something like this: - - Editing table definition for Buffer Pool. Press ? for help, q to quit. - - name hdr label src - cxn CXN Connection from which cxn - buf_pool_size Size Buffer pool size IB_bp_buf_poo - buf_free Free Bufs Buffers free in the b IB_bp_buf_fre - pages_total Pages Pages total IB_bp_pages_t - pages_modified Dirty Pages Pages modified (dirty IB_bp_pages_m - buf_pool_hit_rate Hit Rate Buffer pool hit rate IB_bp_buf_poo - total_mem_alloc Memory Total memory allocate IB_bp_total_m - add_pool_alloc Add'l Pool Additonal pool alloca IB_bp_add_poo - -The first line shows which table you're editing, and reminds you again to press -'?' for a list of key mappings. The rest is a tabular representation of the -table's columns, because that's likely what you're trying to edit. However, you -can edit more than just the table's columns; this screen can start the filter -editor, color rule editor, and more. - -Each row in the display shows a single column in the table you're editing, along -with a couple of its properties such as its header and source expression (see -L<"EXPRESSIONS">). - -The key mappings are Vim-style, as in many other places. Pressing 'j' and 'k' -moves the highlight up or down. You can then (d)elete or (e)dit the highlighted -column. You can also (a)dd a column to the table. This actually just activates -one of the columns already defined for the table; it prompts you to choose from -among the columns available but not currently displayed. Finally, you can -re-order the columns with the '+' and '-' keys. - -You can do more than just edit the columns with the table editor, you can also -edit other properties, such as the table's sort expression and group-by -expression. Press '?' to see the full list, of course. - -If you want to really customize and create your own column, as opposed to just -activating a built-in one that's not currently displayed, press the (n)ew key, -and innotop will prompt you for the information it needs: - -=over - -=item * - -The column name: this needs to be a word without any funny characters, e.g. just -letters, numbers and underscores. - -=item * - -The column header: this is the label that appears at the top of the column, in -the table header. This can have spaces and funny characters, but be careful not -to make it too wide and waste space on-screen. - -=item * - -The column's data source: this is an expression that determines what data from -the source (see L<"TABLES">) innotop will put into the column. This can just be -the name of an item in the source, or it can be a more complex expression, as -described in L<"EXPRESSIONS">. - -=back - -Once you've entered the required data, your table has a new column. There is no -difference between this column and the built-in ones; it can have all the same -properties and behaviors. innotop will write the column's definition to the -configuration file, so it will persist across sessions. - -Here's an example: suppose you want to track how many times your slaves have -retried transactions. According to the MySQL manual, the -Slave_retried_transactions status variable gives you that data: "The total -number of times since startup that the replication slave SQL thread has retried -transactions. This variable was added in version 5.0.4." This is appropriate to -add to the L<"slave_sql_status"> table. - -To add the column, switch to the replication-monitoring mode with the 'M' key, -and press the '^' key to start the table editor. When prompted, choose -slave_sql_status as the table, then press 'n' to create the column. Type -'retries' as the column name, 'Retries' as the column header, and -'Slave_retried_transactions' as the source. Now the column is created, and you -see the table editor screen again. Press 'q' to exit the table editor, and -you'll see your column at the end of the table. - -=head1 VARIABLE SETS - -Variable sets are used in L<"S: Variables & Status"> mode to define more easily -what variables you want to monitor. Behind the scenes they are compiled to a -list of expressions, and then into a column list so they can be treated just -like columns in any other table, in terms of data extraction and -transformations. However, you're protected from the tedious details by a syntax -that ought to feel very natural to you: a SQL SELECT list. - -The data source for variable sets, and indeed the entire S mode, is the -combination of SHOW STATUS, SHOW VARIABLES, and SHOW INNODB STATUS. Imagine -that you had a huge table with one column per variable returned from those -statements. That's the data source for variable sets. You can now query this -data source just like you'd expect. For example: - - Questions, Uptime, Questions/Uptime as QPS - -Behind the scenes innotop will split that variable set into three expressions, -compile them and turn them into a table definition, then extract as usual. This -becomes a "variable set," or a "list of variables you want to monitor." - -innotop lets you name and save your variable sets, and writes them to the -configuration file. You can choose which variable set you want to see with the -'c' key, or activate the next and previous sets with the '>' and '<' keys. -There are many built-in variable sets as well, which should give you a good -start for creating your own. Press 'e' to edit the current variable set, or -just to see how it's defined. To create a new one, just press 'c' and type its -name. - -You may want to use some of the functions listed in L<"TRANSFORMATIONS"> to help -format the results. In particular, L<"set_precision"> is often useful to limit -the number of digits you see. Extending the above example, here's how: - - Questions, Uptime, set_precision(Questions/Uptime) as QPS - -Actually, this still needs a little more work. If your L<"interval"> is less -than one second, you might be dividing by zero because Uptime is incremental in -this mode by default. Instead, use Uptime_hires: - - Questions, Uptime, set_precision(Questions/Uptime_hires) as QPS - -This example is simple, but it shows how easy it is to choose which variables -you want to monitor. - -=head1 PLUGINS - -innotop has a simple but powerful plugin mechanism by which you can extend -or modify its existing functionality, and add new functionality. innotop's -plugin functionality is event-based: plugins register themselves to be called -when events happen. They then have a chance to influence the event. - -An innotop plugin is a Perl module placed in innotop's L<"plugin_dir"> -directory. On UNIX systems, you can place a symbolic link to the module instead -of putting the actual file there. innotop automatically discovers the file. If -there is a corresponding entry in the L<"plugins"> configuration file section, -innotop loads and activates the plugin. - -The module must conform to innotop's plugin interface. Additionally, the source -code of the module must be written in such a way that innotop can inspect the -file and determine the package name and description. - -=head2 Package Source Convention - -innotop inspects the plugin module's source to determine the Perl package name. -It looks for a line of the form "package Foo;" and if found, considers the -plugin's package name to be Foo. Of course the package name can be a valid Perl -package name, with double semicolons and so on. - -It also looks for a description in the source code, to make the plugin editor -more human-friendly. The description is a comment line of the form "# -description: Foo", where "Foo" is the text innotop will consider to be the -plugin's description. - -=head2 Plugin Interface - -The innotop plugin interface is quite simple: innotop expects the plugin to be -an object-oriented module it can call certain methods on. The methods are - -=over - -=item new(%variables) - -This is the plugin's constructor. It is passed a hash of innotop's variables, -which it can manipulate (see L<"Plugin Variables">). It must return a reference -to the newly created plugin object. - -At construction time, innotop has only loaded the general configuration and -created the default built-in variables with their default contents (which is -quite a lot). Therefore, the state of the program is exactly as in the innotop -source code, plus the configuration variables from the L<"general"> section in -the config file. - -If your plugin manipulates the variables, it is changing global data, which is -shared by innotop and all plugins. Plugins are loaded in the order they're -listed in the config file. Your plugin may load before or after another plugin, -so there is a potential for conflict or interaction between plugins if they -modify data other plugins use or modify. - -=item register_for_events() - -This method must return a list of events in which the plugin is interested, if -any. See L<"Plugin Events"> for the defined events. If the plugin returns an -event that's not defined, the event is ignored. - -=item event handlers - -The plugin must implement a method named the same as each event for which it has -registered. In other words, if the plugin returns qw(foo bar) from -register_for_events(), it must have foo() and bar() methods. These methods are -callbacks for the events. See L<"Plugin Events"> for more details about each -event. - -=back - -=head2 Plugin Variables - -The plugin's constructor is passed a hash of innotop's variables, which it can -manipulate. It is probably a good idea if the plugin object saves a copy of it -for later use. The variables are defined in the innotop variable -%pluggable_vars, and are as follows: - -=over - -=item action_for - -A hashref of key mappings. These are innotop's global hot-keys. - -=item agg_funcs - -A hashref of functions that can be used for grouping. See L<"GROUPING">. - -=item config - -The global configuration hash. - -=item connections - -A hashref of connection specifications. These are just specifications of how to -connect to a server. - -=item dbhs - -A hashref of innotop's database connections. These are actual DBI connection -objects. - -=item filters - -A hashref of filters applied to table rows. See L<"FILTERS"> for more. - -=item modes - -A hashref of modes. See L<"MODES"> for more. - -=item server_groups - -A hashref of server groups. See L<"SERVER GROUPS">. - -=item tbl_meta - -A hashref of innotop's table meta-data, with one entry per table (see -L<"TABLES"> for more information). - -=item trans_funcs - -A hashref of transformation functions. See L<"TRANSFORMATIONS">. - -=item var_sets - -A hashref of variable sets. See L<"VARIABLE SETS">. - -=back - -=head2 Plugin Events - -Each event is defined somewhere in the innotop source code. When innotop runs -that code, it executes the callback function for each plugin that expressed its -interest in the event. innotop passes some data for each event. The events are -defined in the %event_listener_for variable, and are as follows: - -=over - -=item extract_values($set, $cur, $pre, $tbl) - -This event occurs inside the function that extracts values from a data source. -The arguments are the set of values, the current values, the previous values, -and the table name. - -=item set_to_tbl - -Events are defined at many places in this subroutine, which is responsible for -turning an arrayref of hashrefs into an arrayref of lines that can be printed to -the screen. The events all pass the same data: an arrayref of rows and the name -of the table being created. The events are set_to_tbl_pre_filter, -set_to_tbl_pre_sort,set_to_tbl_pre_group, set_to_tbl_pre_colorize, -set_to_tbl_pre_transform, set_to_tbl_pre_pivot, set_to_tbl_pre_create, -set_to_tbl_post_create. - -=item draw_screen($lines) - -This event occurs inside the subroutine that prints the lines to the screen. -$lines is an arrayref of strings. - -=back - -=head2 Simple Plugin Example - -The easiest way to explain the plugin functionality is probably with a simple -example. The following module adds a column to the beginning of every table and -sets its value to 1. - - use strict; - use warnings FATAL => 'all'; - - package Innotop::Plugin::Example; - # description: Adds an 'example' column to every table - - sub new { - my ( $class, %vars ) = @_; - # Store reference to innotop's variables in $self - my $self = bless { %vars }, $class; - - # Design the example column - my $col = { - hdr => 'Example', - just => '', - dec => 0, - num => 1, - label => 'Example', - src => 'example', # Get data from this column in the data source - tbl => '', - trans => [], - }; - - # Add the column to every table. - my $tbl_meta = $vars{tbl_meta}; - foreach my $tbl ( values %$tbl_meta ) { - # Add the column to the list of defined columns - $tbl->{cols}->{example} = $col; - # Add the column to the list of visible columns - unshift @{$tbl->{visible}}, 'example'; - } - - # Be sure to return a reference to the object. - return $self; - } - - # I'd like to be called when a data set is being rendered into a table, please. - sub register_for_events { - my ( $self ) = @_; - return qw(set_to_tbl_pre_filter); - } - - # This method will be called when the event fires. - sub set_to_tbl_pre_filter { - my ( $self, $rows, $tbl ) = @_; - # Set the example column's data source to the value 1. - foreach my $row ( @$rows ) { - $row->{example} = 1; - } - } - - 1; - -=head2 Plugin Editor - -The plugin editor lets you view the plugins innotop discovered and activate or -deactivate them. Start the editor by pressing $ to start the configuration -editor from any mode. Press the 'p' key to start the plugin editor. You'll see -a list of plugins innotop discovered. You can use the 'j' and 'k' keys to move -the highlight to the desired one, then press the * key to toggle it active or -inactive. Exit the editor and restart innotop for the changes to take effect. - -=head1 SQL STATEMENTS - -innotop uses a limited set of SQL statements to retrieve data from MySQL for -display. The statements are customized depending on the server version against -which they are executed; for example, on MySQL 5 and newer, INNODB_STATUS -executes "SHOW ENGINE INNODB STATUS", while on earlier versions it executes -"SHOW INNODB STATUS". The statements are as follows: - - Statement SQL executed - =================== =============================== - INNODB_STATUS SHOW [ENGINE] INNODB STATUS - KILL_CONNECTION KILL - KILL_QUERY KILL QUERY - OPEN_TABLES SHOW OPEN TABLES - PROCESSLIST SHOW FULL PROCESSLIST - SHOW_MASTER_LOGS SHOW MASTER LOGS - SHOW_MASTER_STATUS SHOW MASTER STATUS - SHOW_SLAVE_STATUS SHOW SLAVE STATUS - SHOW_STATUS SHOW [GLOBAL] STATUS - SHOW_VARIABLES SHOW [GLOBAL] VARIABLES - -=head1 DATA SOURCES - -Each time innotop extracts values to create a table (see L<"EXPRESSIONS"> and -L<"TABLES">), it does so from a particular data source. Largely because of the -complex data extracted from SHOW INNODB STATUS, this is slightly messy. SHOW -INNODB STATUS contains a mixture of single values and repeated values that form -nested data sets. - -Whenever innotop fetches data from MySQL, it adds two extra bits to each set: -cxn and Uptime_hires. cxn is the name of the connection from which the data -came. Uptime_hires is a high-resolution version of the server's Uptime status -variable, which is important if your L<"interval"> setting is sub-second. - -Here are the kinds of data sources from which data is extracted: - -=over - -=item STATUS_VARIABLES - -This is the broadest category, into which the most kinds of data fall. It -begins with the combination of SHOW STATUS and SHOW VARIABLES, but other sources -may be included as needed, for example, SHOW MASTER STATUS and SHOW SLAVE -STATUS, as well as many of the non-repeated values from SHOW INNODB STATUS. - -=item DEADLOCK_LOCKS - -This data is extracted from the transaction list in the LATEST DETECTED DEADLOCK -section of SHOW INNODB STATUS. It is nested two levels deep: transactions, then -locks. - -=item DEADLOCK_TRANSACTIONS - -This data is from the transaction list in the LATEST DETECTED DEADLOCK -section of SHOW INNODB STATUS. It is nested one level deep. - -=item EXPLAIN - -This data is from the result set returned by EXPLAIN. - -=item INNODB_TRANSACTIONS - -This data is from the TRANSACTIONS section of SHOW INNODB STATUS. - -=item IO_THREADS - -This data is from the list of threads in the the FILE I/O section of SHOW INNODB -STATUS. - -=item INNODB_LOCKS - -This data is from the TRANSACTIONS section of SHOW INNODB STATUS and is nested -two levels deep. - -=item OPEN_TABLES - -This data is from SHOW OPEN TABLES. - -=item PROCESSLIST - -This data is from SHOW FULL PROCESSLIST. - -=item OS_WAIT_ARRAY - -This data is from the SEMAPHORES section of SHOW INNODB STATUS and is nested one -level deep. It comes from the lines that look like this: - - --Thread 1568861104 has waited at btr0cur.c line 424 .... - -=back - -=head1 MYSQL PRIVILEGES - -=over - -=item * - -You must connect to MySQL as a user who has the SUPER privilege for many of the -functions. - -=item * - -If you don't have the SUPER privilege, you can still run some functions, but you -won't necessarily see all the same data. - -=item * - -You need the PROCESS privilege to see the list of currently running queries in Q -mode. - -=item * - -You need special privileges to start and stop slave servers. - -=item * - -You need appropriate privileges to create and drop the deadlock tables if needed -(see L<"SERVER CONNECTIONS">). - -=back - -=head1 SYSTEM REQUIREMENTS - -You need Perl to run innotop, of course. You also need a few Perl modules: DBI, -DBD::mysql, Term::ReadKey, and Time::HiRes. These should be included with most -Perl distributions, but in case they are not, I recommend using versions -distributed with your operating system or Perl distribution, not from CPAN. -Term::ReadKey in particular has been known to cause problems if installed from -CPAN. - -If you have Term::ANSIColor, innotop will use it to format headers more readably -and compactly. (Under Microsoft Windows, you also need Win32::Console::ANSI for -terminal formatting codes to be honored). If you install Term::ReadLine, -preferably Term::ReadLine::Gnu, you'll get nice auto-completion support. - -I run innotop on Gentoo GNU/Linux, Debian and Ubuntu, and I've had feedback from -people successfully running it on Red Hat, CentOS, Solaris, and Mac OSX. I -don't see any reason why it won't work on other UNIX-ish operating systems, but -I don't know for sure. It also runs on Windows under ActivePerl without -problem. - -I use innotop on MySQL versions 3.23.58, 4.0.27, 4.1.0, 4.1.22, 5.0.26, 5.1.15, -and 5.2.3. If it doesn't run correctly for you, that is a bug and I hope you -report it. - -=head1 FILES - -$HOMEDIR/.innotop is used to store configuration information. Files include the -configuration file innotop.ini, the core_dump file which contains verbose error -messages if L<"debug"> is enabled, and the plugins/ subdirectory. - -=head1 GLOSSARY OF TERMS - -=over - -=item tick - -A tick is a refresh event, when innotop re-fetches data from connections and -displays it. - -=back - -=head1 ACKNOWLEDGEMENTS - -I'm grateful to the following people for various reasons, and hope I haven't -forgotten to include anyone: - -Allen K. Smith, -Aurimas Mikalauskas, -Bartosz Fenski, -Brian Miezejewski, -Christian Hammers, -Cyril Scetbon, -Dane Miller, -David Multer, -Dr. Frank Ullrich, -Giuseppe Maxia, -Google.com Site Reliability Engineers, -Jan Pieter Kunst, -Jari Aalto, -Jay Pipes, -Jeremy Zawodny, -Johan Idren, -Kristian Kohntopp, -Lenz Grimmer, -Maciej Dobrzanski, -Michiel Betel, -MySQL AB, -Paul McCullagh, -Sebastien Estienne, -Sourceforge.net, -Steven Kreuzer, -The Gentoo MySQL Team, -Trevor Price, -Yaar Schnitman, -and probably more people I've neglected to include. - -(If I misspelled your name, it's probably because I'm afraid of putting -international characters into this documentation; earlier versions of Perl might -not be able to compile it then). - -=head1 COPYRIGHT, LICENSE AND WARRANTY - -This program is copyright (c) 2006 Baron Schwartz. -Feedback and improvements are welcome. - -THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED -WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF -MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. - -This program is free software; you can redistribute it and/or modify it under -the terms of the GNU General Public License as published by the Free Software -Foundation, version 2; OR the Perl Artistic License. On UNIX and similar -systems, you can issue `man perlgpl' or `man perlartistic' to read these -licenses. - -You should have received a copy of the GNU General Public License along with -this program; if not, write to the Free Software Foundation, Inc., 59 Temple -Place, Suite 330, Boston, MA 02111-1307 USA. - -Execute innotop and press '!' to see this information at any time. - -=head1 AUTHOR - -Baron Schwartz. - -=head1 BUGS - -You can report bugs, ask for improvements, and get other help and support at -L<http://sourceforge.net/projects/innotop>. There are mailing lists, forums, -a bug tracker, etc. Please use these instead of contacting me directly, as it -makes my job easier and benefits others if the discussions are permanent and -public. Of course, if you need to contact me in private, please do. - -=cut |