[Pkg-mysql-commits] r975 - in branches/sid-5.0/debian: . additions/innotop
Norbert Tretkowski
nobse at alioth.debian.org
Thu Nov 8 19:04:39 UTC 2007
Author: nobse
Date: 2007-11-08 19:04:38 +0000 (Thu, 08 Nov 2007)
New Revision: 975
Modified:
branches/sid-5.0/debian/additions/innotop/InnoDBParser.pm
branches/sid-5.0/debian/additions/innotop/changelog.innotop
branches/sid-5.0/debian/additions/innotop/innotop
branches/sid-5.0/debian/changelog
Log:
Updated innotop to 1.4.3 release.
Modified: branches/sid-5.0/debian/additions/innotop/InnoDBParser.pm
===================================================================
--- branches/sid-5.0/debian/additions/innotop/InnoDBParser.pm 2007-11-08 18:39:40 UTC (rev 974)
+++ branches/sid-5.0/debian/additions/innotop/InnoDBParser.pm 2007-11-08 19:04:38 UTC (rev 975)
@@ -19,10 +19,8 @@
# this program; if not, write to the Free Software Foundation, Inc., 59 Temple
# Place, Suite 330, Boston, MA 02111-1307 USA
-our $VERSION = '1.4.0';
+our $VERSION = '1.4.3';
-# TODO: make some options you can pass, such as noparselocks...
-
use Data::Dumper;
$Data::Dumper::Sortkeys = 1;
use English qw(-no_match_vars);
@@ -530,8 +528,15 @@
$dl->{'timestring'} = ts_to_string($dl->{'ts'});
$dl->{'txns'} = {};
- my @sections = $fulltext =~ m/^\*{3} (.*)\n(?s:(.*?))(?=^\*)/gm;
+ my @sections
+ = $fulltext
+ =~ m{
+ ^\*{3}\s([^\n]*) # *** (1) WAITING FOR THIS...
+ (.*?) # Followed by anything, non-greedy
+ (?=(?:^\*{3})|\z) # Followed by another three stars or EOF
+ }gmsx;
+
# Loop through each section. There are no assumptions about how many
# there are, who holds and wants what locks, and who gets rolled back.
while ( my ($header, $body) = splice(@sections, 0, 2) ) {
Modified: branches/sid-5.0/debian/additions/innotop/changelog.innotop
===================================================================
--- branches/sid-5.0/debian/additions/innotop/changelog.innotop 2007-11-08 18:39:40 UTC (rev 974)
+++ branches/sid-5.0/debian/additions/innotop/changelog.innotop 2007-11-08 19:04:38 UTC (rev 975)
@@ -1,8 +1,122 @@
-This package contains innotop and InnoDBParser. Please view the perldoc
-for more details on them.
+Changelog for innotop and InnoDBParser:
-Changelog:
+2007-07-16: version 1.4.3
+ Changes:
+ * Added standard --version command-line option
+ * Changed colors to cyan instead of blue; more visible on dark terminals.
+ * Added information to the filter-choosing dialog.
+ * Added column auto-completion when entering a filter expression.
+ * Changed Term::ReadKey from optional to mandatory.
+ * Clarified username in password prompting.
+ * Ten thousand words of documentation!
+
+ Bugs fixed:
+ * innotop crashed in W mode when InnoDB status data was truncated.
+ * innotop didn't display errors in tables if debug was enabled.
+ * The colored() subroutine wasn't being created in non-interactive mode.
+ * Don't prompt to save password except the first time.
+
+2007-05-03: version 1.4.2
+
+ This version contains all changes to the trunk until revision 239; some
+ changes in revisions 240:250 are included.
+
+ MAJOR CHANGES:
+
+ * Quick-filters to easily filter any column in any display
+ * Compatibility with MySQL 3.23 through 6.0
+ * Improved error handling when a server is down, permissions denied, etc
+ * Use additional SHOW INNODB STATUS information in 5.1.x
+ * Make all modes use tables consistently, so they can all be edited,
+ filtered, colored and sorted consistently
+ * Combine V, G and S modes into S mode, with v, g, and s hot-keys
+ * Let DBD driver read MySQL option files; permit connections without
+ user/pass/etc
+ * Compile SQL-like expressions into Perl subroutines; eliminate need to
+ know Perl
+ * Do not save all config data to config file, only save user's customizations
+ * Rewritten and improved command-line option handling
+ * Added --count, --delay, and other command-line options to support
+ run-and-exit operation
+ * Improve built-in variable sets
+ * Improve help screen with three-part balanced-column layout
+ * Simplify table-editor and improve hotkey support
+ * Require Perl to have high-resolution time support (Time::HiRes)
+ * Help the user choose a query to analyze or kill
+ * Enable EXPLAIN, show-full-query in T mode just like Q mode
+ * Let data-extraction access current, previous and incremental data sets
+ all at once
+
+ MINOR CHANGES:
+
+ * Column stabilizing for Q mode
+ * New color rules for T, Q, W modes
+ * Apply slave I/O filter to Q mode
+ * Improve detection of server version and other meta-data
+ * Make connection timeout a config variable
+ * Improve cross-version-compatible SQL syntax
+ * Get some information from the DBD driver instead of asking MySQL for it
+ * Improved error messages
+ * Improve server group creation/editing
+ * Improve connection/thread killing
+ * Fix broken key bindings and restore previously mapped hot-keys for
+ choosing columns
+ * Some documentation updates (but not nearly enough)
+ * Allow the user to specify graphing char in S mode (formerly G mode)
+ * Allow easy switching between variable sets in S mode
+ * Bind 'n' key globally to choose the 'next' server connection
+ * Bind '%' key globally to filter displayed tables
+ * Allow aligning columns on the decimal place for easy readability
+ * Add hide_hdr config variable to hide column headers in tables
+ * Add a feature to smartly run PURGE MASTER LOGS in Replication mode
+ * Enable debug mode as a globally configurable variable
+ * Improve error messages when an expression or filter doesn't compile or has
+ a run-time error; die on error when debug is enabled
+ * Allow user-configurable delays after executing SQL (to let the server
+ settle down before taking another measurement)
+ * Add an expression to show how long until a transaction is finished
+ * Add skip_innodb as a global config variable
+ * Add '%' after percentages to help disambiguate (user-configurable)
+ * Add column to M mode to help see how fast slave is catching up to master
+
+ BUG FIXES:
+
+ * T and W modes had wrong value for wait_status column
+ * Error tracking on connections didn't reset when the connection recovered
+ * wait_timeout on connections couldn't be set before MySQL 4.0.3
+ * There was a crash on 3.23 when wiping deadlocks
+ * Lettercase changes in some result sets (SHOW MASTER/SLAVE STATUS) between
+ MySQL versions crashed innotop
+ * Inactive connections crashed innotop upon access to DBD driver
+ * set_precision did not respect user defaults for number of digits
+ * --inc command-line option could not be negated
+ * InnoDB status parsing was not always parsing all needed information
+ * S mode (formerly G mode) could crash trying to divide non-numeric data
+ * M table didn't show Slave_open_temp_tables variable; incorrect lettercase
+ * DBD drivers with broken AutoCommit would crash innotop
+ * Some key bindings had incorrect labels
+ * Some config-file loading routines could load data for things that didn't
+ exist
+ * Headers printed too often in S mode
+ * High-resolution time was not used even when the user had it
+ * Non-interactive mode printed blank lines sometimes
+ * Q-mode header and statusbar showed different QPS numbers
+ * Formulas for key-cache and query-cache hit ratios were wrong
+ * Mac OS "Darwin" machines were mis-identified as Microsoft Windows
+ * Some multiplications crashed when given undefined input
+ * The commify transformation did not check its input and could crash
+ * Specifying an invalid mode on the command line or config file could crash
+ innotop
+
+2007-03-29: version 1.4.1
+
+ * More tweaks to display of connection errors.
+ * Fixed a problem with skip-innodb in MySQL 5.1.
+ * Fix a bug with dead connections in single-connection mode.
+ * Fix a regex to allow parsing more data from truncated deadlocks.
+ * Don't load active cxns from the config file if the cxn isn't defined.
+
2007-03-03: version 1.4.0
* Further tweak error handling and display of connection errors
Modified: branches/sid-5.0/debian/additions/innotop/innotop
===================================================================
--- branches/sid-5.0/debian/additions/innotop/innotop 2007-11-08 18:39:40 UTC (rev 974)
+++ branches/sid-5.0/debian/additions/innotop/innotop 2007-11-08 19:04:38 UTC (rev 975)
@@ -1,8 +1,5 @@
#!/usr/bin/perl
-eval 'exec /usr/bin/perl -S $0 ${1+"$@"}'
- if 0; # not running under some shell
-
# vim: foldmethod=marker:tw=160:nowrap:expandtab:tabstop=3:shiftwidth=3:softtabstop=3
# vim users, set modeline to enable auto-folding and compatibility with my preferred
# formatting. I use a very wide textwidth because there's tons of configuration
@@ -19,16 +16,21 @@
use Getopt::Long;
use List::Util qw(max min maxstr);
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.4.0';
+our $VERSION = '1.4.3';
+our $SVN_REV = sprintf("%d", q$Revision: 291 $ =~ m/(\d+)/g);
+our $SVN_URL = sprintf("%s", q$URL: https://innotop.svn.sourceforge.net/svnroot/innotop/branches/1.4/innotop $ =~ m/URL: (\S+)/g);
my $innotop_license = <<"LICENSE";
This is innotop version $VERSION, a MySQL and InnoDB monitor.
-This program is copyright (c) 2006 Baron Schwartz, baron at xaprb dot com.
+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
@@ -52,6 +54,7 @@
# 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;
@@ -61,12 +64,12 @@
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
- expr => '', # In case column's src is an expression, the name of the expr
minw => 0,
maxw => 0,
trans => [],
@@ -79,39 +82,57 @@
# Command-line parameters {{{2
# ###########################################################################
-# Define cmdline args; each is spec, config, desc. Add more hash entries as needed.
-my %opt_spec = (
- h => { s => 'help|h', d => 'Show this help message' },
- c => { s => 'config|c=s', d => 'Config file to read' },
- n => { s => 'nonint|n', d => 'Non-interactive, output tab-separated fields' },
- m => { s => 'mode|m=s', config => 'mode', d => 'Operating mode to start in' },
+my @opt_spec = (
+ { s => 'help', d => 'Show this help message' },
+ { 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' },
);
-# Define the order cmdline opts will appear in help output. Add any extra ones
-# defined above.
-my @opt_keys = qw( c n m h );
-
# 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 { $opt_spec{$_}->{'s'} => \$opts{$_} } @opt_keys );
+GetOptions( map { $_->{s} => \$opts{$_->{k}} } @opt_spec) or $opts{help} = 1;
-if ( $opts{'h'} ) {
- print "Usage: innotop <options>\n\nOptions:\n\n";
- foreach my $key ( @opt_keys ) {
- my ( $long, $short ) = $opt_spec{$key}->{'s'} =~ m/^(\w+)(?:\|([^=]*))?/;
- $long = "--$long" . ( $short ? ',' : '' );
- $short = $short ? " -$short" : '';
- printf(" %-13s %-4s %s\n", $long, $short, $opt_spec{$key}->{'d'});
+if ( $opts{version} ) {
+ print "innotop Ver $VERSION Changeset $SVN_REV from $SVN_URL\n";
+ exit(0);
+}
+
+if ( $opts{'help'} ) {
+ print "Usage: innotop <options>\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 connects to a MySQL database and displays information from it so you can
-monitor its status, such as what queries are running.
+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);
@@ -121,39 +142,21 @@
# ###########################################################################
# Expressions {{{3
-# Each expression looks like this when fully hydrated:
-# Name => { func => sub{ return 1 }, text => 'return 1', user => 1 }
-# * The text is the plain text of the expression
-# * The func is that text, compiled into a subroutine
-# * The user is whether it's user-defined, and hence needs writing to config
-# So the loading is exactly the same whether user-defined or built-in, the
-# expressions aren't initially stored here; they are initially stored in a
-# hash that looks just like what comes out of the config file. Those are
-# hydrated with the compile_expr() function.
+# Convenience so I can copy/paste these in several places...
# ###########################################################################
-my %exprs = ();
-my %builtin_exprs = (
- # TODO remove more of these.
- Host => q{my $host = $set->{Host} || $set->{hostname} || ''; ($host) = $host =~ m/^((?:[\d.]+(?=:))|(?:[a-zA-Z]\w+))/; return $host || ''},
- HostAndDomain => q{my $host = $set->{Host} || $set->{hostname} || ''; ($host) = $host =~ m/^([^:]+)/; return $host || ''},
- Port => q{my ( $port ) = $set->{Host} =~ m/:(.*)$/; return $port || 0},
- QPS => q{$set->{Uptime_hires} ? $set->{Questions} / ($set->{Uptime_hires} || 1) : 0},
- ReplByteLag => q{defined $set->{Master_Log_File} && $set->{Master_Log_File} eq $set->{Relay_Master_Log_File} ? $set->{Read_Master_Log_Pos} - $set->{Exec_Master_Log_Pos} : 0},
- OldVersions => q{dulint_to_int($set->{IB_tx_trx_id_counter}) - dulint_to_int($set->{IB_tx_purge_done_for})},
- MaxTxnTime => q{max(map{ $_->{active_secs} } @{$set->{IB_tx_transactions}}) || 0},
- NumTxns => q{scalar @{$set->{IB_tx_transactions}} },
- DirtyBufs => q{ $set->{IB_bp_pages_modified} / ($set->{IB_bp_buf_pool_size} || 1) },
- BufPoolFill => q{ $set->{IB_bp_pages_total} / ($set->{IB_bp_buf_pool_size} || 1) },
+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)},
);
-foreach my $key ( keys %builtin_exprs ) {
- my ( $sub, $err ) = compile_expr($builtin_exprs{$key});
- $exprs{$key} = {
- func => $sub,
- text => $builtin_exprs{$key},
- user => 0,
- name => $key, # useful for later
- }
-}
# ###########################################################################
# Column definitions {{{3
@@ -173,6 +176,7 @@
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', },
@@ -193,6 +197,8 @@
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' },
@@ -203,9 +209,9 @@
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', },
- host_or_ip => { hdr => 'Host', num => 0, label => 'Hostname or IP address', },
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' },
@@ -228,6 +234,7 @@
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' },
+ 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' },
@@ -255,6 +262,7 @@
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' },
@@ -267,13 +275,14 @@
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_locks => { hdr => 'Num Lcks', num => 1, label => 'Number of locks' },
+ num_locks => { hdr => 'Row Locks', num => 1, label => 'Number of locks' },
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' },
@@ -332,7 +341,7 @@
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_header => { hdr => 'What', num => 0, label => 'Row header' },
+ 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' },
@@ -343,6 +352,7 @@
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' },
@@ -350,7 +360,7 @@
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', },
+ 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', },
@@ -367,15 +377,19 @@
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', },
+ 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 Wait?', num => 0, label => 'Whether txn is waiting for a lock' },
+ 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' },
@@ -399,7 +413,7 @@
# 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, just like values in %exprs.
+# returns true or false.
# Properties of each entry:
# * func: the subroutine
# * name: the name, repeated
@@ -432,7 +446,7 @@
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(slave_io_status)],
+ tbls => [qw(processlist slave_io_status)],
},
table_is_open => {
text => <<' END',
@@ -476,43 +490,139 @@
}
# Variable sets {{{3
-# Sets (arrayrefs) of variables that are used in V, S, G mode. They are read/written to
+# Sets (arrayrefs) of variables that are used in S mode. They are read/written to
# the config file.
my %var_sets = (
- general => [ 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 => [ 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 => [ 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 => [ 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 => [ qw( Uptime Key_blocks_not_flushed Key_blocks_unused
- Key_blocks_used Key_read_requests Key_reads Key_write_requests
- Key_writes ) ],
- query_cache => [ qw( Uptime Qcache_free_blocks Qcache_free_memory Qcache_hits
- Qcache_inserts Qcache_lowmem_prunes Qcache_not_cached
- Qcache_queries_in_cache Qcache_total_blocks ) ],
- handler => [ 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 => [ 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 => [ 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 => [ qw(OldVersions IB_sm_mutex_spin_waits IB_sm_mutex_spin_rounds
- IB_sm_mutex_os_waits NumTxns MaxTxnTime IB_ro_queries_inside IB_ro_queries_in_queue
- DirtyBufs BufPoolFill IB_bp_pages_total IB_bp_pages_read IB_bp_pages_written
- IB_bp_pages_created) ],
+ 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
@@ -522,9 +632,9 @@
# 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.
-# Each has dsn, user, pass, savepass properties.
my %connections;
-my @conn_parts = qw(user pass dsn savepass dl_table);
+# 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.
@@ -537,6 +647,34 @@
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',
+);
+
# 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
@@ -546,18 +684,16 @@
# $columns{foo} for its definition) gets its data from the 'bar' element of
# the current data set, whatever that is.
#
-# Example 2: biz => { src => \%exprs{bat} } means the expression is
-# evaluated for the current data set.
-#
# 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 and expr get added to
+# 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 => {
- hdr => 'Adaptive Hash Index',
+ capt => 'Adaptive Hash Index',
+ cust => {},
cols => {
cxn => { src => 'cxn' },
hash_table_size => { src => 'IB_ib_hash_table_size', trans => [qw(shorten)], },
@@ -573,7 +709,8 @@
innodb => 'ib',
},
buffer_pool => {
- hdr => 'Buffer Pool',
+ capt => 'Buffer Pool',
+ cust => {},
cols => {
cxn => { src => 'cxn' },
total_mem_alloc => { src => 'IB_bp_total_mem_alloc', trans => [qw(shorten)], },
@@ -584,6 +721,7 @@
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' },
@@ -605,7 +743,8 @@
innodb => 'bp',
},
deadlock_locks => {
- hdr => 'Deadlock Locks',
+ capt => 'Deadlock Locks',
+ cust => {},
cols => {
cxn => { src => 'cxn' },
mysql_thread_id => { src => 'mysql_thread_id' },
@@ -632,7 +771,8 @@
innodb => 'dl',
},
deadlock_transactions => {
- hdr => 'Deadlock Transactions',
+ capt => 'Deadlock Transactions',
+ cust => {},
cols => {
cxn => { src => 'cxn' },
active_secs => { src => 'active_secs' },
@@ -640,7 +780,7 @@
has_read_view => { src => 'has_read_view' },
heap_size => { src => 'heap_size' },
host_and_domain => { src => 'hostname' },
- hostname => { src => $exprs{Host}, expr => 'Host' },
+ hostname => { src => $exprs{Host} },
ip => { src => 'ip' },
lock_structs => { src => 'lock_structs' },
lock_wait_time => { src => 'lock_wait_time', trans => [ qw(secs_to_time) ] },
@@ -650,6 +790,7 @@
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' },
@@ -664,7 +805,7 @@
undo_log_entries => { src => 'undo_log_entries' },
user => { src => 'user' },
victim => { src => 'victim' },
- wait_status => { src => 'wait_status' },
+ 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 => [],
@@ -673,7 +814,8 @@
innodb => 'dl',
},
explain => {
- hdr => 'EXPLAIN Results',
+ capt => 'EXPLAIN Results',
+ cust => {},
cols => {
part_id => { src => 'id' },
select_type => { src => 'select_type' },
@@ -685,7 +827,7 @@
key_len => { src => 'key_len' },
index_ref => { src => 'ref' },
num_rows => { src => 'rows' },
- special => { src => 'Extra' },
+ special => { src => 'extra' },
},
visible => [ qw(select_type tbl partitions scan_type possible_keys index key_len index_ref num_rows special)],
filters => [],
@@ -694,7 +836,8 @@
innodb => '',
},
file_io_misc => {
- hdr => 'File I/O Misc',
+ capt => 'File I/O Misc',
+ cust => {},
cols => {
cxn => { src => 'cxn' },
io_bytes_s => { src => 'IB_io_avg_bytes_s' },
@@ -713,7 +856,8 @@
innodb => 'io',
},
fk_error => {
- hdr => 'Foreign Key Error Info',
+ capt => 'Foreign Key Error Info',
+ cust => {},
cols => {
timestring => { src => 'IB_fk_timestring' },
child_db => { src => 'IB_fk_child_db' },
@@ -733,7 +877,8 @@
innodb => 'fk',
},
insert_buffers => {
- hdr => 'Insert Buffers',
+ capt => 'Insert Buffers',
+ cust => {},
cols => {
cxn => { src => 'cxn' },
inserts => { src => 'IB_ib_inserts' },
@@ -750,15 +895,16 @@
innodb => 'ib',
},
innodb_transactions => {
- hdr => '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}, expr => 'Host' },
+ hostname => { src => $exprs{Host} },
ip => { src => 'ip' },
- wait_status => { src => 'wait_status' },
+ 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' },
@@ -767,6 +913,8 @@
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' },
@@ -784,17 +932,19 @@
sort_cols => '-active_secs txn_status cxn mysql_thread_id',
sort_dir => '1',
innodb => 'tx',
- hide_hdr => 1,
+ hide_caption => 1,
colors => [
- { col => 'wait_status', op => '>', arg => 0, color => 'black on_red' },
+ { 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 => 30, color => 'green' },
+ { col => 'time', op => '>', arg => 60, color => 'green' },
+ { col => 'time', op => '>', arg => 30, color => 'cyan' },
{ col => 'txn_status', op => 'eq', arg => 'not started', color => 'white' },
],
},
io_threads => {
- hdr => 'I/O Threads',
+ capt => 'I/O Threads',
+ cust => {},
cols => {
cxn => { src => 'cxn' },
thread => { src => 'thread' },
@@ -809,7 +959,8 @@
innodb => 'io',
},
lock_waits => {
- hdr => 'Lock Waits',
+ capt => 'Lock Waits',
+ cust => {},
cols => {
cxn => { src => 'cxn' },
db => { src => 'db' },
@@ -833,9 +984,15 @@
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' },
+ ],
},
log_statistics => {
- hdr => 'Log Statistics',
+ capt => 'Log Statistics',
+ cust => {},
cols => {
cxn => { src => 'cxn' },
last_chkp => { src => 'IB_lg_last_chkp' },
@@ -853,22 +1010,25 @@
innodb => 'lg',
},
master_status => {
- hdr => '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_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)],
+ visible => [ qw(cxn master_file master_pos binlog_cache_overflow)],
filters => [ qw(cxn_is_master) ],
sort_cols => 'cxn',
sort_dir => '1',
innodb => '',
},
pending_io => {
- hdr => 'Pending I/O',
+ capt => 'Pending I/O',
+ cust => {},
cols => {
cxn => { src => 'cxn' },
p_normal_aio_reads => { src => 'IB_io_pending_normal_aio_reads' },
@@ -888,13 +1048,14 @@
innodb => 'io',
},
open_tables => {
- hdr => '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' },
+ 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) ],
@@ -903,7 +1064,8 @@
innodb => '',
},
page_statistics => {
- hdr => 'Page Statistics',
+ capt => 'Page Statistics',
+ cust => {},
cols => {
cxn => { src => 'cxn' },
pages_read => { src => 'IB_bp_pages_read' },
@@ -920,57 +1082,64 @@
innodb => 'bp',
},
processlist => {
- hdr => 'MySQL Process List',
+ 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, expr => 'Host' },
- port => { src => $exprs{Port}, minw => 0, maxw => 0, expr => 'Port' },
- host_and_port => { src => 'Host', minw => 0, maxw => 0 },
+ 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) ] },
- state => { src => 'State', minw => 0, maxw => 0 },
- info => { src => 'Info', minw => 0, maxw => 0, trans => [ qw(no_ctrl_char) ] },
+ cmd => { src => 'command', minw => 5, maxw => 0 },
+ time => { src => 'time', minw => 5, maxw => 0, trans => [ qw(secs_to_time) ] },
+ state => { src => 'state', minw => 0, maxw => 0 },
+ info => { src => 'info', minw => 0, maxw => 0, trans => [ qw(no_ctrl_char) ] },
},
visible => [ qw(cxn 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_hdr => 1,
+ hide_caption => 1,
colors => [
- { col => 'cmd', op => 'eq', arg => 'Locked', color => 'red' },
- { col => 'cmd', op => 'eq', arg => 'Query', color => 'yellow' },
+ { 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 => 'green' },
+ { 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' },
],
},
q_header => {
- hdr => 'Q-mode Header',
+ capt => 'Q-mode Header',
+ cust => {},
cols => {
cxn => { src => 'cxn' },
questions => { src => 'Questions' },
- qps => { src => 'Questions/Uptime_hires', trans => [qw(shorten)] },
- slow => { src => 'Slow_queries', trans => [qw(shorten)] },
- q_cache_hit => { src => 'Qcache_hits/Com_select', trans => [qw(percent)] },
- key_buffer_hit => { src => '1-(Key_reads/Key_read_requests)', trans => [qw(percent)] },
- bps_in => { src => 'Bytes_received/Uptime_hires', trans => [qw(shorten)] },
- bps_out => { src => 'Bytes_sent/Uptime_hires', trans => [qw(shorten)] },
+ 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 qps slow q_cache_hit key_buffer_hit bps_in bps_out)],
+ 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_hdr => 1,
+ hide_caption => 1,
},
row_operations => {
- hdr => 'InnoDB Row Operations',
+ capt => 'InnoDB Row Operations',
+ cust => {},
cols => {
cxn => { src => 'cxn' },
num_inserts => { src => 'IB_ro_num_rows_ins' },
@@ -990,7 +1159,8 @@
innodb => 'ro',
},
row_operation_misc => {
- hdr => 'Row Operation Misc',
+ capt => 'Row Operation Misc',
+ cust => {},
cols => {
cxn => { src => 'cxn' },
queries_in_queue => { src => 'IB_ro_queries_in_queue' },
@@ -1008,7 +1178,8 @@
innodb => 'ro',
},
semaphores => {
- hdr => 'InnoDB Semaphores',
+ capt => 'InnoDB Semaphores',
+ cust => {},
cols => {
cxn => { src => 'cxn' },
mutex_os_waits => { src => 'IB_sm_mutex_os_waits' },
@@ -1031,24 +1202,25 @@
innodb => 'sm',
},
slave_io_status => {
- hdr => 'Slave I/O 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' },
+ 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 ) ],
@@ -1060,33 +1232,35 @@
innodb => '',
},
slave_sql_status => {
- hdr => '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 => $exprs{ReplByteLag}, trans => [qw(shorten)], expr => 'ReplByteLag' },
+ 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_open_temp_tables relay_log_pos last_error)],
+ 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',
@@ -1098,8 +1272,44 @@
{ col => 'time_behind_master', op => '==', arg => 0, color => 'white' },
],
},
+ 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,
+ },
+ 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,
+ },
wait_array => {
- hdr => 'InnoDB Wait Array',
+ capt => 'InnoDB Wait Array',
+ cust => {},
cols => {
cxn => { src => 'cxn' },
thread => { src => 'thread' },
@@ -1130,13 +1340,15 @@
},
);
-# TODO: V, G, S mode should have a table in tbl_meta
+# 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};
-# Initialize %tbl_meta from %columns
-foreach my $table ( values %tbl_meta ) {
- foreach my $col_name ( keys %{$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'" unless $columns{$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.
@@ -1148,40 +1360,29 @@
}
}
}
+
+ # Ensure each column in visible exists in cols
+ foreach my $col_name ( @{$table->{visible}} ) {
+ if ( !exists $cols->{$col_name} ) {
+ die "Column '$col_name' is listed in visible 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);
}
-# ###########################################################################
-# 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);
+# 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);
+ }
+}
# ###########################################################################
-# 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 functions for transformations.
# ###########################################################################
my %trans_funcs = (
@@ -1190,8 +1391,8 @@
no_ctrl_char => \&no_ctrl_char,
percent => \&percent,
commify => \&commify,
- collapse_ws => \&collapse_ws,
dulint_to_int => \&dulint_to_int,
+ set_precision => \&set_precision,
);
# ###########################################################################
@@ -1199,12 +1400,13 @@
# ###########################################################################
my %modes = (
B => {
- hdr => 'InnoDB Buf',
+ hdr => 'InnoDB Buffers',
+ cust => {},
note => 'Shows buffer info from InnoDB',
action_for => {
i => {
action => sub { toggle_config('status_inc') },
- label => 'Toggle overall/incremental status display',
+ label => 'Toggle incremental status display',
},
},
display_sub => \&display_B,
@@ -1216,8 +1418,13 @@
},
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',
@@ -1232,6 +1439,7 @@
},
F => {
hdr => 'InnoDB FK Err',
+ cust => {},
note => 'View the latest InnoDB foreign key error',
action_for => {},
display_sub => \&display_F,
@@ -1241,41 +1449,14 @@
tables => [qw(fk_error)],
visible_tables => [qw(fk_error)],
},
- G => {
- hdr => 'Load Graph',
- note => 'Shows query load graph',
- action_for => {
- c => {
- action => sub {
- choose_var_set('G_set');
- start_G_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 overall/incremental status display',
- },
- },
- display_sub => \&display_G,
- no_clear_screen => 1,
- connections => [],
- server_group => '',
- one_connection => 1,
- tables => [qw()],
- visible_tables => [qw()],
- },
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 overall/incremental status display',
+ label => 'Toggle incremental status display',
},
},
display_sub => \&display_I,
@@ -1287,20 +1468,25 @@
},
M => {
hdr => 'Replication Status',
+ cust => {},
note => 'Shows replication (master and slave) status',
action_for => {
a => {
- action => sub { send_cmd_to_servers('START SLAVE', 1, 'START SLAVE SQL_THREAD UNTIL MASTER_LOG_FILE = ?, MASTER_LOG_POS = ?'); },
+ 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 overall/incremental status display',
+ label => 'Toggle incremental status display',
},
o => {
- action => sub { send_cmd_to_servers('STOP SLAVE', 1); },
+ 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 => [],
@@ -1311,12 +1497,9 @@
},
O => {
hdr => 'Open Tables',
+ cust => {},
note => 'Shows open tables in MySQL',
action_for => {
- c => {
- action => sub { get_config_interactive('O_fmt'); },
- label => "Choose which columns to display",
- },
r => {
action => sub { reverse_sort('open_tables'); },
label => 'Reverse sort order',
@@ -1335,15 +1518,16 @@
},
Q => {
hdr => 'Query List',
+ cust => {},
note => 'Shows queries from SHOW FULL PROCESSLIST',
action_for => {
a => {
action => sub { toggle_filter('processlist', 'hide_self') },
- label => 'Toggle hiding the innotop process',
+ label => 'Toggle the innotop process',
},
c => {
- action => sub { choose('Q_fmt'); },
- label => "Choose which columns to display",
+ action => sub { edit_table('processlist') },
+ label => 'Choose visible columns',
},
e => {
action => sub { analyze_query('e'); },
@@ -1354,12 +1538,12 @@
label => "Show a thread's full query",
},
h => {
- action => sub { toggle_config('show_QT_header') },
+ 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 showing or hiding idle (Sleep) processes',
+ label => 'Toggle idle processes',
},
k => {
action => sub { kill_query('CONNECTION') },
@@ -1375,7 +1559,7 @@
},
x => {
action => sub { kill_query('QUERY') },
- label => "Kill a query (not the connection; requires 5.0)",
+ label => "Kill a query",
},
},
display_sub => \&display_Q,
@@ -1387,11 +1571,12 @@
},
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 overall/incremental status display',
+ label => 'Toggle incremental status display',
},
},
display_sub => \&display_R,
@@ -1402,9 +1587,18 @@
visible_tables => [qw(row_operations row_operation_misc semaphores wait_array)],
},
S => {
- hdr => 'Load Stats',
+ 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');
@@ -1418,7 +1612,7 @@
},
i => {
action => sub { $clear_screen_sub->(); toggle_config('status_inc') },
- label => 'Toggle overall/incremental status display',
+ label => 'Toggle incremental status display',
},
'-' => {
action => sub { set_display_precision(-1) },
@@ -1428,34 +1622,55 @@
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()],
- visible_tables => [qw()],
+ one_connection => 1,
+ 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 hiding the innotop process',
+ label => 'Toggle the innotop process',
},
c => {
- action => sub { get_config_interactive('T_fmt'); },
- label => "Choose which columns to display",
+ 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_config('show_QT_header') },
+ 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 showing or hiding inactive transactions',
+ label => 'Toggle inactive transactions',
},
k => {
action => sub { kill_query('CONNECTION') },
@@ -1471,57 +1686,21 @@
},
x => {
action => sub { kill_query('QUERY') },
- label => "Kill a query, not a connection (requires 5.0)",
+ label => "Kill a query",
},
},
display_sub => \&display_T,
connections => [],
server_group => '',
one_connection => 0,
- tables => [qw(innodb_transactions)],
- visible_tables => [qw(innodb_transactions)],
+ tables => [qw(t_header innodb_transactions)],
+ visible_tables => [qw(t_header innodb_transactions)],
},
- V => {
- hdr => 'Variables & Status',
- note => 'Shows values from SHOW STATUS and SHOW VARIABLES',
- action_for => {
- c => {
- action => sub { choose_var_set('V_set') },
- label => 'Choose which set to display',
- },
- e => {
- action => \&edit_current_var_set,
- label => 'Edit the current set of variables',
- },
- i => {
- action => sub { toggle_config('status_inc') },
- label => 'Toggle overall/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',
- },
- },
- display_sub => \&display_V,
- connections => [],
- server_group => '',
- one_connection => 1,
- tables => [qw()],
- visible_tables => [qw()],
- },
W => {
hdr => 'InnoDB Lock Waits',
+ cust => {},
note => 'Shows transaction lock waits and OS wait array info',
- action_for => {
- c => {
- action => sub { get_config_interactive('W_fmt') },
- label => 'Choose which columns to show in the lock waits table',
- },
- },
+ action_for => { },
display_sub => \&display_W,
connections => [],
server_group => '',
@@ -1548,11 +1727,11 @@
},
'!' => {
action => \&display_license,
- label => 'Show license and warranty information',
+ label => 'Show license and warranty',
},
'^' => {
action => \&edit_table,
- label => "Edit columns, etc in the displayed table(s)",
+ label => "Edit the displayed table(s)",
},
'#' => {
action => \&choose_server_groups,
@@ -1562,6 +1741,18 @@
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',
@@ -1569,65 +1760,63 @@
},
B => {
action => sub { switch_mode('B') },
- label => 'Switch to B mode (InnoDB Buffer/Hash Index)',
+ label => '',
},
D => {
action => sub { switch_mode('D') },
- label => 'Switch to D mode (InnoDB Deadlock Information)',
+ label => '',
},
F => {
action => sub { switch_mode('F') },
- label => 'Switch to F mode (InnoDB Foreign Key Error)',
+ label => '',
},
- G => {
- action => \&start_G_mode,
- label => 'Switch to G mode (Load Graph)',
- },
I => {
action => sub { switch_mode('I') },
- label => 'Switch to I mode (InnoDB I/O and Log)',
+ label => '',
},
M => {
action => sub { switch_mode('M') },
- label => 'Switch to M mode (MySQL Replication Status)',
+ label => '',
},
O => {
action => sub { switch_mode('O') },
- label => 'Switch to O mode (MySQL Open Tables)',
+ label => '',
},
Q => {
action => sub { switch_mode('Q') },
- label => 'Switch to Q mode (Query List, like mytop)',
+ label => '',
},
R => {
action => sub { switch_mode('R') },
- label => 'Switch to R mode (InnoDB Row Operations)',
+ label => '',
},
S => {
action => \&start_S_mode,
- label => 'Switch to S mode (Load Statistics)',
+ label => '',
},
T => {
action => sub { switch_mode('T') },
- label => 'Switch to T mode (InnoDB Transaction)',
+ label => '',
},
- V => {
- action => sub { switch_mode('V') },
- label => 'Switch to V mode (Variable & Status)',
- },
W => {
action => sub { switch_mode('W') },
- label => 'Switch to W mode (InnoDB Lock Waits and OS Wait Info)',
+ 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 = (
@@ -1647,6 +1836,10 @@
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,
@@ -1709,6 +1902,7 @@
arg => $arg,
color => $color
};
+ $tbl_meta{$tbl}->{cust}->{colors} = 1;
return $idx;
},
@@ -1720,6 +1914,7 @@
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;
},
},
@@ -1748,6 +1943,7 @@
my $temp = $meta->{colors}->[$idx];
$meta->{colors}->[$idx] = $meta->{colors}->[$dest];
$meta->{colors}->[$dest] = $temp;
+ $meta->{cust}->{colors} = 1;
return $dest;
},
},
@@ -1760,6 +1956,7 @@
my $temp = $meta->{colors}->[$idx];
$meta->{colors}->[$idx] = $meta->{colors}->[$dest];
$meta->{colors}->[$dest] = $temp;
+ $meta->{cust}->{colors} = 1;
return $dest;
},
},
@@ -1783,6 +1980,7 @@
{ 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;
@@ -1810,10 +2008,7 @@
} while ( !$hdr );
$clear_screen_sub->();
- print word_wrap("Choose a source for the column's data. You can either enter the name of an entry "
- . "in the data available to the table (varies by context) or if you want to enter "
- . "the name of an expression, specify nothing here.");
- print "\n\n";
+ print "Choose a source for the column's data\n\n";
my ( $src, $sub, $err );
do {
if ( $err ) {
@@ -1821,16 +2016,10 @@
}
$src = prompt("Enter column source");
if ( $src ) {
- ( $sub, $err ) = compile_expr($src, 1);
+ ( $sub, $err ) = compile_expr($src);
}
- } until ( !$src || !$err);
+ } until ( !$err);
- my $exp;
- if ( !$src ) {
- $exp = get_expr();
- return $col unless $exp && $exprs{$exp};
- }
-
$tbl_meta{$tbl}->{cols}->{$col} = {
hdr => $hdr,
src => $src,
@@ -1839,14 +2028,15 @@
label => 'User-defined',
user => 1,
tbl => $tbl,
- expr => $exp ? $exp : '',
minw => 0,
maxw => 0,
trans => [],
- func => $sub || $exprs{$exp}->{func},
+ func => $sub,
+ dec => 0,
};
$tbl_meta{$tbl}->{visible} = [ unique(@{$tbl_meta{$tbl}->{visible}}, $col) ];
+ $tbl_meta{$tbl}->{cust}->{visible} = 1;
return $col;
},
},
@@ -1861,40 +2051,31 @@
$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 expr just num minw maxw trans);
+ my @prop = qw(hdr label src just num minw maxw trans); # TODO redundant
my $answer;
do {
# Do what the user asked...
if ( $answer && grep { $_ eq $answer } @prop ) {
- if ( $answer eq 'expr' ) {
- $meta->{expr} = get_expr();
+ # 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) ];
}
- else {
- # 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 );
- if ( $answer eq 'src' ) {
- $meta->{expr} = '';
- }
- }
- if ( $meta->{expr} ) {
- $meta->{src} = $exprs{$meta->{expr}};
- }
+ @{$meta}{$answer, 'user', 'tbl' } = ( $val, 1, $tbl );
}
my @display_lines = (
@@ -1962,6 +2143,7 @@
shift @{$meta->{visible}};
push @{$meta->{visible}}, $col;
}
+ $meta->{cust}->{visible} = 1;
return $col;
},
},
@@ -1984,92 +2166,64 @@
$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 table meta-data (sort column, filters...)',
+ note => 'Edit color rules',
func => sub {
my ( $tbl, $col ) = @_;
- $clear_screen_sub->();
- my $meta = $tbl_meta{$tbl};
- my $sort_cols = $meta->{sort_cols};
- my $filters = $meta->{filters};
- my @prop = qw(filters sort_cols);
-
- my $answer;
- do {
- # Do whatever the user asked
- if ( $answer && grep { $_ eq $answer } @prop ) {
- my $ini = ref $meta->{$answer} ? join(' ', @{$meta->{$answer}}) : $meta->{$answer};
- if ( $answer eq 'sort_cols' ) {
- choose_sort_cols($tbl);
- }
- elsif ( $answer eq 'filters' ) {
- $clear_screen_sub->();
- 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 ];
- }
- }
-
- my @display_lines = "You are editing table $tbl.";
-
- push @display_lines, '', create_caption('Properties', create_table2(
- \@prop,
- { map { $_ => $_ } @prop },
- { map { $_ => ref $meta->{$_} eq 'ARRAY' ? join(' ', @{$meta->{$_}})
- : $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)', undef, undef, sub { return @prop });
- } while ( $answer ne 'q' );
-
+ edit_color_rules($tbl);
return $col;
},
},
+ s => {
+ note => 'Choose sort columns',
+ func => sub {
+ my ( $tbl, $col ) = @_;
+ choose_sort_cols($tbl);
+ return $col;
+ },
+ },
);
# ###########################################################################
# Global variables and environment {{{2
# ###########################################################################
-# Set up required stuff for interactive mode...
-if ( !$opts{n} ) {
- require Term::ReadKey;
- import Term::ReadKey qw(ReadMode ReadKey);
-}
-
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/Win/i;
+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 $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');
@@ -2084,20 +2238,12 @@
# Stores info on currently displayed queries: cxn, connection ID, query text.
my @current_queries;
-my $hi_res = 0;
my $lines_printed = 0;
my @innodb_files = ();
my $innodb_file_counter = -1;
my $clock = 0; # Incremented with every wake-sleep cycle
my $clearing_deadlocks = 0;
-# If hi-res time is available, use it.
-eval {
- require Time::HiRes;
- import Time::HiRes qw(time sleep);
- $hi_res = 1;
-};
-
# Find the home directory; it's different on different OSes.
my $homepath = $ENV{HOME} || $ENV{HOMEPATH} || $ENV{USERPROFILE} || '.';
@@ -2113,10 +2259,10 @@
$have_color = 1;
}
};
-if ( $EVAL_ERROR ) {
+if ( $EVAL_ERROR || $opts{n} ) {
# If there was an error, manufacture my own colored() function that does no
# coloring.
- *colored = sub { return shift; };
+ *colored = sub { pop @_; @_; };
}
if ( $opts{n} ) {
@@ -2134,29 +2280,65 @@
# Config storage. {{{2
# ###########################################################################
my %config = (
+ 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 at the end of every table',
- conf => [ 'ALL' ],
- pat => qr/^[01]$/,
+ 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 => qr/^[01]$/,
+ conf => 'ALL',
+ pat => $bool_regex,
},
readonly => {
val => 0,
note => 'Whether the config file is read-only',
conf => [ qw() ],
- pat => qr/^[01]$/,
+ pat => $bool_regex,
},
global => {
val => 1,
note => 'Whether to show GLOBAL variables and status',
conf => 'ALL',
- pat => qr/^[01]$/,
+ pat => $bool_regex,
},
header_highlight => {
val => 'bold',
@@ -2168,14 +2350,8 @@
val => 1,
note => 'Whether to put captions on tables',
conf => 'ALL',
- pat => qr/^[01]$/,
+ pat => $bool_regex,
},
- compact_hdr => {
- val => 1,
- note => 'Whether to compact the headers in some modes',
- conf => 'ALL',
- pat => qr/^[01]$/,
- },
charset => {
val => 'ascii',
note => 'What type of characters should be displayed in queries (ascii, unicode, none)',
@@ -2186,7 +2362,7 @@
val => 0,
note => 'Whether to auto-wipe InnoDB deadlocks',
conf => 'ALL',
- pat => qr/^[01]$/,
+ pat => $bool_regex,
},
max_height => {
val => 30,
@@ -2195,29 +2371,23 @@
},
debug => {
val => 0,
- pat => qr/^[01]$/,
+ pat => $bool_regex,
note => 'Debug mode (more verbose errors, uses more memory)',
- conf => [ qw(D) ],
+ conf => 'ALL',
},
num_digits => {
val => 2,
- pat => qr/^\d$/,
+ pat => $int_regex,
note => 'How many digits to show in fractional numbers and percents',
conf => 'ALL',
},
- show_QT_header => {
- val => 1,
- pat => qr/^[01]$/,
- note => 'Whether to show the header in Q and T modes',
- conf => [ qw(Q T) ],
- },
debugfile => {
val => "$homepath/.innotop_core_dump",
note => 'A debug file in case you are interested in error output',
},
show_statusbar => {
val => 1,
- pat => qr/^[01]$/,
+ pat => $bool_regex,
note => 'Whether to show the status bar in the display',
conf => 'ALL',
},
@@ -2229,35 +2399,23 @@
status_inc => {
val => 0,
note => 'Whether to show raw or incremental values for status variables',
- pat => qr/^[01]$/,
+ 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.",
},
- V_set => {
- val => 'general',
- pat => qr/^\w+$/,
- note => 'Which set of variables to display in V (Variables/Status) mode',
- conf => [ qw(V) ],
- },
num_status_sets => {
val => 9,
- pat => qr/^\d*?[1-9]\d*$/,
+ pat => $int_regex,
note => 'How many sets of STATUS and VARIABLES values to show',
- conf => [ qw(V) ],
+ conf => [ qw(S) ],
},
- G_set => {
- val => 'general',
- pat => qr/^\w+$/,
- note => 'Which set of variables to display in G (Load Graph) mode',
- conf => [ qw(G) ],
- },
S_set => {
val => 'general',
pat => qr/^\w+$/,
- note => 'Which set of variables to display in S (Load Statistics) mode',
+ note => 'Which set of variables to display in S (Variables & Status) mode',
conf => [ qw(S) ],
},
);
@@ -2289,10 +2447,6 @@
reader => \&load_config_active_columns,
writer => \&save_config_active_columns,
},
- expressions => {
- reader => \&load_config_expressions,
- writer => \&save_config_expressions,
- },
tbl_meta => {
reader => \&load_config_tbl_meta,
writer => \&save_config_tbl_meta,
@@ -2329,65 +2483,73 @@
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(filters active_filters expressions tbl_meta
- general connections active_connections server_groups active_server_groups max_values_seen
- active_columns sort_cols visible_tables varsets colors);
+my @ordered_config_file_sections = qw(general 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);
# ###########################################################################
# Contains logic to generate prepared statements for a given function for a
-# given DB connection. $cxn is a key in %dbhs. Returns a SQL string.
+# given DB connection. Returns a $sth.
# ###########################################################################
my %stmt_maker_for = (
INNODB_STATUS => sub {
- my ( $cxn ) = @_;
- my $meta = $dbhs{$cxn};
- return ( $meta->{ver_major} >= 5 )
+ my ( $dbh ) = @_;
+ return $dbh->prepare(version_ge( $dbh, '5.0.0' )
? 'SHOW ENGINE INNODB STATUS'
- : 'SHOW INNODB STATUS';
+ : 'SHOW INNODB STATUS');
},
SHOW_VARIABLES => sub {
- my ( $cxn ) = @_;
- my $meta = $dbhs{$cxn};
- return ( $config{global}->{val} && $meta->{ver_major} >= 4 ) && ( $meta->{ver_rev} >= 3 )
+ my ( $dbh ) = @_;
+ return $dbh->prepare($config{global}->{val} && version_ge( $dbh, '4.0.3' )
? 'SHOW GLOBAL VARIABLES'
- : 'SHOW VARIABLES';
+ : 'SHOW VARIABLES');
},
SHOW_STATUS => sub {
- my ( $cxn ) = @_;
- my $meta = $dbhs{$cxn};
- return ( $config{global}->{val} && $meta->{ver_major} >= 5 ) && ( $meta->{ver_rev} >= 2 )
+ my ( $dbh ) = @_;
+ return $dbh->prepare($config{global}->{val} && version_ge( $dbh, '5.0.2' )
? 'SHOW GLOBAL STATUS'
- : 'SHOW STATUS';
+ : 'SHOW STATUS');
},
KILL_QUERY => sub {
- my ( $cxn ) = @_;
- my $meta = $dbhs{$cxn};
- return ( $meta->{ver_major} >= 5 )
+ my ( $dbh ) = @_;
+ return $dbh->prepare(version_ge( $dbh, '5.0.0' )
? 'KILL QUERY ?'
- : 'KILL ?';
+ : 'KILL ?');
},
+ SHOW_MASTER_LOGS => sub {
+ my ( $dbh ) = @_;
+ return $dbh->prepare('SHOW MASTER LOGS');
+ },
SHOW_MASTER_STATUS => sub {
- my ( $cxn ) = @_;
- return 'SHOW MASTER STATUS';
+ my ( $dbh ) = @_;
+ return $dbh->prepare('SHOW MASTER STATUS');
},
SHOW_SLAVE_STATUS => sub {
- my ( $cxn ) = @_;
- return 'SHOW SLAVE STATUS';
+ my ( $dbh ) = @_;
+ return $dbh->prepare('SHOW SLAVE STATUS');
},
KILL_CONNECTION => sub {
- my ( $cxn ) = @_;
- return 'KILL CONNECTION ?';
+ my ( $dbh ) = @_;
+ return $dbh->prepare(version_ge( $dbh, '5.0.0' )
+ ? 'KILL CONNECTION ?'
+ : 'KILL ?');
},
OPEN_TABLES => sub {
- my ( $cxn ) = @_;
- return 'SHOW OPEN TABLES';
+ my ( $dbh ) = @_;
+ return version_ge($dbh, '4.0.0')
+ ? $dbh->prepare('SHOW OPEN TABLES')
+ : undef;
},
PROCESSLIST => sub {
- my ( $cxn ) = @_;
- return 'SHOW FULL PROCESSLIST';
+ my ( $dbh ) = @_;
+ return $dbh->prepare('SHOW FULL PROCESSLIST');
},
);
@@ -2418,9 +2580,23 @@
}
eval {
+
+ # 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};
+ 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;
@@ -2464,6 +2640,11 @@
# 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});
@@ -2630,13 +2811,15 @@
sub kill_query {
my ( $q_or_c ) = @_;
- my ( $cxn ) = select_cxn('Kill on which server');
- return unless $cxn && exists($connections{$cxn});
+ 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 {
- my $thread = prompt("Choose which $q_or_c to kill");
- return unless $thread && $thread =~ m/^\d+$/;
- do_stmt($cxn, $q_or_c eq 'QUERY' ? 'KILL_QUERY' : 'KILL_CONNECTION', $thread);
+ do_stmt($info->{cxn}, $q_or_c eq 'QUERY' ? 'KILL_QUERY' : 'KILL_CONNECTION', $info->{id} );
};
if ( $EVAL_ERROR ) {
@@ -2645,17 +2828,24 @@
}
}
-# set_V_set {{{3
-sub set_V_set {
- $config{V_set}->{val} = shift;
-}
-
# 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 ) = @_;
@@ -2666,6 +2856,7 @@
else {
push @$filters, $filter;
}
+ $tbl_meta{$tbl}->{cust}->{filters} = 1;
}
# toggle_config {{{3
@@ -2693,15 +2884,16 @@
# deadlock_thread {{{3
sub deadlock_thread {
my ( $id, $tbl, $cxn ) = @_;
- my @stmts = (
- "set transaction isolation level serializable",
- "start transaction",
- "select * from $tbl where a = $id",
- "update $tbl set a = $id where a <> $id",
- );
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);
}
@@ -2716,18 +2908,58 @@
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 ) = @_;
- my @cxns;
+ my ( $cmd, $all, $hint, $cxns ) = @_;
if ( $all ) {
- @cxns = get_connections();
+ @$cxns = get_connections();
}
- @cxns = select_cxn('Which servers?', @cxns);
+ 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 ) {
+ foreach my $cxn ( @$cxns ) {
eval {
my $sth = do_query($cxn, $cmd);
};
@@ -2743,10 +2975,10 @@
# Display functions {{{2
-# start_G_mode {{{3
-sub start_G_mode {
+sub set_s_mode {
+ my ( $func ) = @_;
$clear_screen_sub->();
- switch_mode('G');
+ $config{S_func}->{val} = $func;
}
# start_S_mode {{{3
@@ -2777,23 +3009,24 @@
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, 'buffer_pool');
+ push @buffer_pool, extract_values($set, $set, $pre, 'buffer_pool');
}
if ( $wanted{page_statistics} ) {
- push @page_statistics, extract_values($set, '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,
+ $config{status_inc}->{val} ? inc(0, $cxn) : $set, $set, $pre,
'insert_buffers');
}
if ( $wanted{adaptive_hash_index} ) {
- push @adaptive_hash_index, extract_values($set, 'adaptive_hash_index');
+ push @adaptive_hash_index, extract_values($set, $set, $pre, 'adaptive_hash_index');
}
}
}
@@ -2801,7 +3034,8 @@
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) unless $config{debug}->{val} || $first_table++;;
+ push @display_lines, get_cxn_errors(@cxns)
+ if ( $config{debug}->{val} || !$first_table++ );
}
draw_screen(\@display_lines);
@@ -2825,6 +3059,7 @@
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} ) {
@@ -2833,9 +3068,10 @@
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}, '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';
@@ -2847,8 +3083,9 @@
if ( $wanted{deadlock_locks} ) {
foreach my $what (qw(waits_for holds)) {
my $locks = $txn->{$what};
+ my $pre_l = $pre->{$what};
if ( $locks ) {
- my $hash = extract_values($locks, 'deadlock_locks');
+ my $hash = extract_values($locks, $locks, $pre_l, 'deadlock_locks');
$hash->{dl_txn_num} = $txn_id;
$hash->{txn_status} = $what;
$hash->{cxn} = $cxn;
@@ -2866,7 +3103,8 @@
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) unless $config{debug}->{val} || $first_table++;;
+ push @display_lines, get_cxn_errors(@cxns)
+ if ( $config{debug}->{val} || !$first_table++ );
}
draw_screen(\@display_lines);
@@ -2895,7 +3133,7 @@
my @fk_table = create_table2(
$tbl_meta{fk_error}->{visible},
meta_to_hdr('fk_error'),
- extract_values($innodb_status, 'fk_error'),
+ extract_values($innodb_status, $innodb_status, $innodb_status, 'fk_error'),
{ just => '-', sep => ' '});
push @display_lines, '', @fk_table;
@@ -2906,56 +3144,6 @@
draw_screen(\@display_lines, { raw => 1 } );
}
-# display_G {{{3
-sub display_G {
- my ( $cxn ) = get_connections();
- my $fmt = get_var_set('G_set');
- get_status_info($cxn);
- get_innodb_status([$cxn]); # TODO: might not be needed.
-
- if ( !exists $vars{$cxn}->{$clock - 1} ) {
- return;
- }
-
- # Design a column format for the values.
- my $num_cols = scalar(@$fmt);
- 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/;
-
- # 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->();
- }
-
- # Get the values.
- my $set = inc(0, $cxn);
- $set = { map { $_ => ($set->{$_} || 1) / ( $set->{Uptime_hires} || 1) } @$fmt };
-
- # Update max ever seen.
- map { $mvs{$_} = max($mvs{$_} || 1, $set->{$_}) } @$fmt;
-
- # Print headers every now and then.
- if ( $opts{n} ) {
- if ( $lines_printed == 0 ) {
- print join("\t", @$fmt), "\n";
- print join("\t", map { shorten($mvs{$_}) } @$fmt), "\n";
- }
- }
- elsif ( $lines_printed % int( $this_term_size[1] - 2 ) == 0 ) {
- printf($format, map { donut(crunch($_, $width), $width) } @$fmt);
- printf($format, map { shorten($mvs{$_}) } @$fmt);
- }
- $lines_printed++;
-
- # Scale the values against the max ever seen.
- map { $set->{$_} /= $mvs{$_} } @$fmt;
-
- # Print the values.
- printf($format, map { ( '*' x int( $width * $set->{$_} )) || '.' } @$fmt );
-}
-
# display_I {{{3
sub display_I {
my @display_lines;
@@ -2978,33 +3166,39 @@
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} ) {
- foreach my $thd ( values %{$set->{IB_io_threads}} ) {
- my $hash = extract_values($thd, '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, '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,
- 'file_io_misc');
+ $set, $pre, 'file_io_misc');
}
}
if ( $set->{IB_lg_complete} && $wanted{log_statistics} ) {
- push @log_statistics, extract_values($set, '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) unless $config{debug}->{val} || $first_table++;;
+ push @display_lines, get_cxn_errors(@cxns)
+ if ( $config{debug}->{val} || !$first_table++ );
}
draw_screen(\@display_lines);
@@ -3031,21 +3225,23 @@
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, '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, '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, '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) unless $config{debug}->{val} || $first_table++;;
+ push @display_lines, get_cxn_errors(@cxns)
+ if ( $config{debug}->{val} || !$first_table++ );
}
draw_screen(\@display_lines);
@@ -3056,7 +3252,7 @@
my @display_lines = ('');
my @cxns = get_connections();
my @open_tables = get_open_tables(@cxns);
- my @tables = map { extract_values($_, 'open_tables') } @open_tables;
+ 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);
}
@@ -3075,9 +3271,6 @@
my @visible = $opts{n} ? 'processlist' : get_visible_tables();
my %wanted = map { $_ => 1 } @visible;
- # Config variable overrides %wanted here. TODO: this is hack-ish.
- $wanted{q_header} = $config{show_QT_header}->{val};
-
# Get the data
my @cxns = get_connections();
my @full_processlist = get_full_processlist(@cxns);
@@ -3086,14 +3279,16 @@
if ( $wanted{q_header} ) {
get_status_info(@cxns);
foreach my $cxn ( @cxns ) {
- my $hash = extract_values($vars{$cxn}->{$clock}, 'q_header');
+ 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, 'q_header');
+ my $hash = extract_values($inc, $set, $pre, 'q_header');
$hash->{cxn} = $cxn;
$hash->{when} = 'Now';
push @q_header, $hash;
@@ -3102,14 +3297,16 @@
}
if ( $wanted{processlist} ) {
- push @processlist, map { extract_values($_, 'processlist') } @full_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) unless $config{debug}->{val} || $first_table++;
+ 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
@@ -3147,26 +3344,27 @@
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, 'row_operations');
+ push @row_operations, extract_values($inc, $set, $pre, 'row_operations');
}
if ( $wanted{row_operation_misc} ) {
- push @row_operation_misc, extract_values($set, '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, 'semaphores');
+ 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_array');
+ my $hash = extract_values($wait, $wait, $wait, 'wait_array');
$hash->{cxn} = $cxn;
push @wait_array, $hash;
}
@@ -3176,151 +3374,211 @@
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) unless $config{debug}->{val} || $first_table++;
+ push @display_lines, get_cxn_errors(@cxns)
+ if ( $config{debug}->{val} || !$first_table++ );
}
draw_screen(\@display_lines);
}
-# display_S {{{3
-sub display_S {
- my $min_width = 4;
- my $inc = $config{status_inc}->{val};
- my ( $cxn ) = get_connections();
- my $fmt = get_var_set('S_set');
- get_status_info( $cxn );
- get_innodb_status([$cxn]); # TODO: might not be needed.
-
- # 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->();
- }
-
- # Decide how wide columns should be.
- my $num_cols = scalar(@$fmt);
- my $width = $opts{n} ? 0 : max($min_width, int(($this_term_size[0] - $num_cols + 1) / $num_cols));
-
- # Print headers every now and then. Headers can get really long, so compact them.
- my @hdr = @$fmt;
- if ( $opts{n} ) {
- if ( $lines_printed == 0 ) {
- print join("\t", @hdr), "\n";
- }
- }
- elsif ( $lines_printed % int( $this_term_size[1] - 2 ) == 0 ) {
- @hdr = map { donut(crunch($_, $width), $width) } @hdr;
- print join(' ', map { sprintf( "%${width}s", donut($_, $width)) } @hdr) . "\n";
- }
-
- # Design a column format for the values.
- my $format
- = $opts{n}
- ? join("\t", map { '%s' } @$fmt) . "\n"
- : join(' ', map { "%${width}s" } @hdr) . "\n";
-
- # Print the values.
- my $set = $inc ? inc(0, $cxn) : $vars{$cxn}->{$clock};
- printf($format,
- map {
- exists $set->{$_} ? $set->{$_}
- : exists $exprs{$_} ? $exprs{$_}->{func}->($set)
- : 0
- } @$fmt
- );
- $lines_printed++;
-}
-
# display_T {{{3
sub display_T {
my @display_lines;
- my @txns;
+ 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.
- my $hdr = ( !$opts{n} && 1 == scalar @cxns && $config{show_QT_header}->{val} );
+ get_innodb_status( \@cxns, [ $wanted{t_header} ? qw(bp) : () ] );
- get_innodb_status( \@cxns, [ $hdr ? 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 ( $set->{IB_tx_transactions} ) {
- foreach my $txn ( @{$set->{IB_tx_transactions}} ) {
- my $hash = extract_values($txn, 'innodb_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 @txns, $hash;
+ push @innodb_transactions, $hash;
}
}
- if ( $hdr ) {
- push @display_lines, '', join(", ",
- "History: $set->{IB_tx_history_list_len}",
- "Versions: " . $exprs{OldVersions}->{func}->( $set ),
- "Undo: $set->{IB_tx_purge_undo_for}",
- "Dirty Bufs: " . percent($exprs{DirtyBufs}->{func}->( $set )) . '%',
- "Used Bufs: " . percent($exprs{BufPoolFill}->{func}->( $set )) . '%',
- "Max time: " . secs_to_time($exprs{MaxTxnTime}->{func}->( $set )),
- "Lock structs: $set->{IB_tx_num_lock_structs}",
- );
- }
}
- push @display_lines, '', set_to_tbl(\@txns, 'innodb_transactions'), get_cxn_errors(@cxns);
+ 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) } = @{$_}{ qw(cxn mysql_thread_id db query_text) };
+ \%hash;
+ } @{$rows_for{innodb_transactions}};
+
draw_screen(\@display_lines);
}
-# display_V {{{3
-# TODO: when entering V mode, remove any non-contiguous stuff from %vars.
-sub display_V {
- my @display_lines;
- my ( $cxn ) = get_connections();
- my $fmt = get_var_set('V_set');
- my $inc = $config{status_inc}->{val};
- my $num = $config{num_status_sets}->{val};
+# 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};
- get_status_info($cxn);
- get_innodb_status([$cxn]); # TODO: might not be needed.
-
- # Figure out how many past sets have actually been kept.
- while ( !exists $vars{$cxn}->{$clock - $num} ) {
- $num--;
+ # 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)};
}
-
- # Build a meta dataset that can be used for a type-1 table
- my $meta = { name => { hdr => 'Name', just => '-' } };
- foreach my $set ( 0 .. $num ) {
- $meta->{"set_$set"} = { hdr => "Set $set", just => '' };
+ else {
+ ( $cols, $visible ) = compile_select_stmt($fmt);
+ $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;
}
- # Loop through them and do a 'pivot table' transformation on them. Instead of
- # sets becoming rows, sets must become columns, and variables become rows.
- my @rows = map { { name => $_ } } @$fmt;
- foreach my $set ( 0 .. $num ) {
- my $vars = $inc ? inc($set, $cxn) : $vars{$cxn}->{$clock - $set};
- foreach my $row ( 0.. @$fmt - 1 ) {
- my $name = $fmt->[$row];
- my $val = exists($vars->{$name}) ? $vars->{$name}
- : exists($exprs{$name}) ? $exprs{$name}->{func}->($vars)
- : 0;
- $rows[$row]->{"set_$set"} = defined $val ? $val : 0;
+ 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 $cxn ( @cxns ) {
+ foreach my $set ( 0 .. $num_sets ) {
+ 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 @var_status, $hash;
}
}
- my @cols = 'name';
- foreach my $set ( 0 .. $num ) {
- push @cols, "set_$set";
- }
+ # ################################################################
+ # Now there is specific display code based on $config{S_func}
+ # ################################################################
+ if ( $func =~ m/s|g/ ) {
+ my $min_width = 4;
- push @display_lines, create_table( \@cols, $meta, \@rows);
+ # 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->();
+ }
- $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 );
- draw_screen( \@display_lines );
+ # 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_W {{{3
@@ -3342,26 +3600,37 @@
# Get info on lock waits and OS wait array
foreach my $cxn ( @cxns ) {
my $set = $vars{$cxn}->{$clock} or next;
+ my $pre = $vars{$cxn}->{$clock-1} || $set;
- if ( $wanted{lock_waits} && @{$set->{IB_tx_transactions}} ) {
+ if ( $wanted{lock_waits} && defined $set->{IB_tx_transactions} && @{$set->{IB_tx_transactions}} ) {
- my @txns = @{$set->{IB_tx_transactions}};
- foreach my $txn ( grep { $_->{lock_wait_status} } @txns ) {
- my %lock_wait = map { $_ => $txn->{$_} }
- qw(txn_id mysql_thread_id lock_wait_time active_secs);
- my $wait_locks = $txn->{wait_locks};
- map { $lock_wait{$_} = $wait_locks->{$_} }
- qw(lock_type space_id page_no n_bits index db table txn_id
- lock_mode special insert_intention waiting num_locks);
- $lock_wait{cxn} = $cxn;
- push @lock_waits, extract_values(\%lock_wait, 'lock_waits');
+ 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 ( map { $_->{mysql_thread_id} } grep { $_->{lock_wait_status} } @$cur_txns ) {
+ my $cur_txn = $cur_txns{$thd_id};
+ my $pre_txn = $pre_txns{$thd_id} || $cur_txn;
+
+ my %cur_lock_wait = map { $_ => $cur_txn->{$_} } qw(txn_id mysql_thread_id lock_wait_time active_secs);
+ my $cur_wait_locks = $cur_txn->{wait_locks};
+ map { $cur_lock_wait{$_} = $cur_wait_locks->{$_} } qw(lock_type space_id page_no n_bits index db table txn_id lock_mode special insert_intention waiting num_locks);
+ $cur_lock_wait{cxn} = $cxn;
+
+ my %pre_lock_wait = map { $_ => $pre_txn->{$_} } qw(txn_id mysql_thread_id lock_wait_time active_secs);
+ my $pre_wait_locks = $pre_txn->{wait_locks};
+ map { $pre_lock_wait{$_} = $pre_wait_locks->{$_} } qw(lock_type space_id page_no n_bits index db table txn_id lock_mode special insert_intention waiting num_locks);
+ $pre_lock_wait{cxn} = $cxn;
+
+ push @lock_waits, extract_values(\%cur_lock_wait, \%cur_lock_wait, \%pre_lock_wait, 'lock_waits');
}
}
if ( $wanted{wait_array} && $set->{IB_sm_complete} ) {
if ( $set->{IB_sm_wait_array_size} ) {
foreach my $wait ( @{$set->{IB_sm_waits}} ) {
- my $hash = extract_values($wait, 'wait_array');
+ # TODO: try to get prev values...
+ my $hash = extract_values($wait, $wait, $wait, 'wait_array');
$hash->{cxn} = $cxn;
push @wait_array, $hash;
}
@@ -3372,7 +3641,8 @@
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) unless $config{debug}->{val} || $first_table++;;
+ push @display_lines, get_cxn_errors(@cxns)
+ if ( $config{debug}->{val} || !$first_table++ );
}
draw_screen(\@display_lines);
@@ -3383,7 +3653,6 @@
my $info = shift;
my $cxn = $info->{cxn};
my $db = $info->{db};
- my $meta = $dbhs{$cxn};
my ( $mods, $query ) = rewrite_for_explain($info->{query});
@@ -3391,10 +3660,7 @@
if ( $query ) {
- my $part
- = ( $meta->{ver_major} >= 5 && $meta->{ver_minor} >= 1 && $meta->{ver_rev} >= 5 )
- ? 'PARTITIONS'
- : '';
+ my $part = version_ge($dbhs{$cxn}->{dbh}, '5.1.5') ? 'PARTITIONS' : '';
$query = "EXPLAIN $part\n" . $query;
eval {
@@ -3410,7 +3676,7 @@
create_table2(
$tbl_meta{explain}->{visible},
meta_to_hdr('explain'),
- extract_values($res, 'explain')));
+ extract_values($res, $res, $res, 'explain')));
@display_lines = stack_next(\@display_lines, \@this_table, { pad => ' ', vsep => 2 });
}
};
@@ -3433,7 +3699,8 @@
}
# rewrite_for_explain {{{3
-# Some replace/create/insert...select can be rewritten easily.
+# TODO: Some replace/create/insert...select can be rewritten easily.
+# TODO: update foo inner join bar can also be rewritten.
sub rewrite_for_explain {
my $query = shift;
@@ -3471,11 +3738,11 @@
}
do_query( $cxn, 'EXPLAIN EXTENDED ' . $query ) or die "Can't explain query";
my $sth = do_query($cxn, 'SHOW WARNINGS');
- my $res = $sth->fetchall_arrayref;
+ my $res = $sth->fetchall_arrayref({});
if ( $res ) {
foreach my $result ( @$res ) {
- push @display_lines, 'Note:', no_ctrl_char($result->[2]);
+ push @display_lines, 'Note:', no_ctrl_char($result->{message});
}
}
else {
@@ -3501,25 +3768,60 @@
# Get globally mapped keys, then overwrite them with mode-specific ones.
my %keys = map {
- my $key = $action_for{$_}->{key} || $_;
- $key => $action_for{$_}->{label}
+ $_ => $action_for{$_}->{label}
} keys %action_for;
foreach my $key ( keys %{$modes{$mode}->{action_for}} ) {
$keys{$key} = $modes{$mode}->{action_for}->{$key}->{label};
}
delete $keys{'?'};
- my @display_lines = ( '', 'The following keys are mapped in this mode:', '', );
- push @display_lines, create_table2(
- [ sort keys %keys ],
- { map { $_ => $_ } keys %keys },
- \%keys,
- { sep => ' ' }
- );
- push @display_lines, '', 'Any other key refreshes the display.', '';
+ # 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
@@ -3719,6 +4021,11 @@
$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 ) {
@@ -3735,6 +4042,15 @@
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} );
@@ -3747,7 +4063,7 @@
}
# The table header.
- if ( !$prefs->{no_hdr} ) {
+ 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 );
@@ -3794,30 +4110,37 @@
eval {
@$rows = grep { $filters{$filter}->{func}->($_) } @$rows;
};
+ if ( $EVAL_ERROR && $config{debug}->{val} ) {
+ die $EVAL_ERROR;
+ }
}
- # Sort.
- if ( @$rows && $meta->{sort_func} ) {
- if ( $meta->{sort_dir} > 0 ) {
- @$rows = $meta->{sort_func}->( @$rows );
+ if ( !$meta->{pivot} ) {
+ # Sort.
+ if ( @$rows && $meta->{sort_func} ) {
+ if ( $meta->{sort_dir} > 0 ) {
+ @$rows = $meta->{sort_func}->( @$rows );
+ }
+ else {
+ @$rows = reverse $meta->{sort_func}->( @$rows );
+ }
}
- else {
- @$rows = reverse $meta->{sort_func}->( @$rows );
- }
}
# Stop altering arguments now.
my @rows = @$rows;
- # Colorize. Adds a _color column to rows.
- if ( @rows && $meta->{color_func} ) {
- eval {
- foreach my $row ( @rows ) {
- $row->{_color} = $meta->{color_func}->($row);
+ 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);
}
- };
- if ( $EVAL_ERROR ) {
- pause($EVAL_ERROR);
}
}
@@ -3833,10 +4156,33 @@
}
}
- @rows = create_table( $meta->{visible}, $meta->{cols}, \@rows);
- if ( !$meta->{hide_hdr} && !$opts{n} && $config{display_table_captions}->{val} ) {
- @rows = create_caption($meta->{hdr}, @rows)
+ my ($fmt_cols, $fmt_meta);
+
+ # Pivot.
+ if ( $meta->{pivot} ) {
+ 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.. at vars-1 ) {
+ $tmp[$j]->{$col} = $rows[$i]->{$vars[$j]};
+ }
+ }
+ $fmt_meta = { map { $_ => { hdr => $_, just => '-'} } @cols };
+ $fmt_cols = \@cols;
+ @rows = @tmp;
}
+ else {
+ $fmt_meta = $meta->{cols};
+ $fmt_cols = $meta->{visible};
+ }
+
+ @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)
+ }
return @rows;
}
@@ -3852,7 +4198,8 @@
# commify {{{3
# From perlfaq5: add commas.
sub commify {
- my $num = shift;
+ my ( $num ) = @_;
+ $num = 0 unless defined $num;
$num =~ s/(^[-+]?\d+?(?=(?>(?:\d{3})+)(?!\d))|\G\d{3}(?=\d))/$1,/g;
return $num;
}
@@ -3861,6 +4208,7 @@
# Trim to desired precision.
sub set_precision {
my ( $num, $precision ) = @_;
+ $precision = $config{num_digits}->{val} if !defined $precision;
sprintf("%.${precision}f", $num);
}
@@ -3868,15 +4216,17 @@
# Convert to percent
sub percent {
my ( $num ) = @_;
+ $num = 0 unless defined $num;
my $digits = $config{num_digits}->{val};
- sprintf("%.${digits}f", $num * 100);
+ 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/[^\d\.-]/;
+ return $num if !defined($num) || $opts{n} || $num !~ m/$num_regex/;
$opts ||= {};
my $pad = defined $opts->{pad} ? $opts->{pad} : '';
@@ -4049,29 +4399,24 @@
}
$clear_screen_sub->() unless $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} || $_ } # When non-interactive, suppress empty lines
+ grep { !$opts{n} || $_ } # Suppress empty lines
@$display_lines);
- if ( $opts{n} ) {
+ if ( $opts{n} && $num_lines ) {
print "\n";
}
}
- elsif ( $prefs->{show_all} ) {
- print join("\n",
- map {
- ref $_
- ? colored(substr($_->[0], 0, $this_term_size[0]), $_->[1])
- : substr($_, 0, $this_term_size[0]);
- }
- @$display_lines);
- }
else {
- my $max_lines = min(scalar(@$display_lines), $this_term_size[1]);
+ my $max_lines = $prefs->{show_all}
+ ? scalar(@$display_lines)- 1
+ : min(scalar(@$display_lines), $this_term_size[1]);
print join("\n",
map {
ref $_
@@ -4135,56 +4480,58 @@
# The thingie in top-right that says what we're monitoring.
my $cxn = '';
- if ( 1 == @cxns ) {
- $cxn = $dbhs{$cxns[0]}->{mysql_version};
+ 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{$_}->{err_count} } @cxns;
+ 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} ? '!' : '') . $_ } @cxns);
+ $cxn = join(' ', map { ($dbhs{$_}->{err_count} ? '!' : '') . $_ }
+ grep { $dbhs{$_} } @cxns);
}
}
if ( 1 == @cxns ) {
- get_status_info(@cxns);
+ get_driver_status(@cxns);
my $vars = $vars{$cxns[0]}->{$clock};
+ my $inc = inc(0, $cxns[0]);
- # Format server uptime human-readably.
- my $uptime = secs_to_time( $vars->{Uptime} );
- my $inc = inc(0, $cxns[0]);
- my $qps = set_precision($exprs{QPS}->{func}->($inc), 2);
+ # 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} sec ";
+ $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 .= ':^|, ';
+ $ibinfo .= ':^|';
}
else {
- $ibinfo .= ':-), ';
+ $ibinfo .= ':-)';
}
}
else {
- $ibinfo .= ':-(, ';
+ $ibinfo .= ':-(';
}
}
$result = sprintf(
"%-${mode_width}s %${remaining_width}s",
$modeline,
- $ibinfo . join(', ',
- "$qps QPS",
+ join(', ', grep { $_ } (
$cxns[0],
- ($vars->{Threads_connected} || 0) . " thd",
$uptime,
- $cxn));
+ $ibinfo,
+ shorten($qps) . " QPS",
+ ($vars->{Threads} || 0) . " thd",
+ $cxn)));
}
else {
$result = sprintf(
@@ -4216,24 +4563,19 @@
my $dsn;
do {
$clear_screen_sub->();
- print "Typical DSN strings look like\n DBI:mysql:db;host=hostname;port=port\n"
- . "The db and port are optional and can typically be omitted.\n\n";
- $dsn = prompt("Enter a DSN string", undef, "DBI:mysql:;host=$name");
+ 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 );
- my $user = $ENV{USERNAME} || $ENV{USER} || getlogin() || getpwuid($REAL_USER_ID) || undef;
- do {
- $clear_screen_sub->();
- $user = prompt("Enter a username for $name", undef, $user);
- } until ( $user );
-
$clear_screen_sub->();
my $dl_table = prompt("Optional: enter a table (must not exist) to use when resetting InnoDB deadlock information",
- undef, 'test.innodb_deadlock_maker');
+ undef, 'test.innotop_dl');
$connections{$name} = {
dsn => $dsn,
- user => $user,
dl_table => $dl_table,
};
}
@@ -4261,14 +4603,15 @@
} until ( @cxns );
$server_groups{$name} = \@cxns;
+ return $name;
}
sub get_var_set {
my ( $name ) = @_;
- while ( !exists($var_sets{$config{$name}->{val}}) ) {
+ while ( !$name || !exists($var_sets{$config{$name}->{val}}) ) {
$name = choose_var_set($name);
}
- return $var_sets{$config{$name}->{val}};
+ return $var_sets{$config{$name}->{val}}->{text};
}
sub add_new_var_set {
@@ -4291,9 +4634,21 @@
$variables = prompt("Enter variables for $name", undef );
} until ( $variables );
- $var_sets{$name} = [ unique(grep { $_ } split(/\s+/, $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;
@@ -4371,8 +4726,10 @@
my ( $cxn, $stmt_name, @args ) = @_;
# Test if the cxn should not even be tried
- return undef
- if $dbhs{$cxn} && $dbhs{$cxn}->{err_count} && $dbhs{$cxn}->{wake_up} > $clock;
+ 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;
@@ -4385,30 +4742,21 @@
# If the prepared query doesn't exist, make it.
if ( !exists $dbhs{$cxn}->{stmts}->{$stmt_name} ) {
- $dbhs{$cxn}->{stmts}->{$stmt_name}
- = $dbh->prepare($stmt_maker_for{$stmt_name}->($cxn));
+ $dbhs{$cxn}->{stmts}->{$stmt_name} = $stmt_maker_for{$stmt_name}->($dbh);
}
$sth = $dbhs{$cxn}->{stmts}->{$stmt_name};
- $sth->execute(@args);
+ if ( $sth ) {
+ $sth->execute(@args);
+ }
$success = 1;
};
if ( $EVAL_ERROR ) {
- my $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',
- );
- if ( $EVAL_ERROR =~ m/$errs/ ) {
+ if ( $EVAL_ERROR =~ m/$nonfatal_errs/ ) {
handle_cxn_error($cxn, $EVAL_ERROR);
}
else {
- die $EVAL_ERROR;
+ die "$cxn $stmt_name: $EVAL_ERROR";
}
if ( $retries < 0 ) {
$sth = undef;
@@ -4416,7 +4764,10 @@
}
}
- return $sth;
+ 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.
@@ -4427,6 +4778,11 @@
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/ ) {
@@ -4449,8 +4805,10 @@
my ( $cxn, $query ) = @_;
# Test if the cxn should not even be tried
- return undef
- if $dbhs{$cxn} && $dbhs{$cxn}->{err_count} && $dbhs{$cxn}->{wake_up} > $clock;
+ 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;
@@ -4466,15 +4824,7 @@
$success = 1;
};
if ( $EVAL_ERROR ) {
- my $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',
- );
- if ( $EVAL_ERROR =~ m/$errs/ ) {
+ if ( $EVAL_ERROR =~ m/$nonfatal_errs/ ) {
handle_cxn_error($cxn, $EVAL_ERROR);
}
else {
@@ -4502,42 +4852,86 @@
};
my $href = $dbhs{$cxn};
- if ( !$href->{dbh} || !$href->{dbh}->ping ) {
+ if ( !$href->{dbh} || ref($href->{dbh}) !~ m/DBI/ || !$href->{dbh}->ping ) {
my $dbh = get_new_db_connection($cxn);
- $href->{dbh} = $dbh;
+ @{$href}{qw(dbh err_count wake_up this_sleep start_time prev_sleep)}
+ = ($dbh, 0, 0, 1, 0, 0);
- # Get version and connection ID. This is necessary to do repeatedly
- # because we may disconnect and connect again.
- my ($version, $connection_id)
- = $dbh->selectrow_array("SELECT VERSION(), CONNECTION_ID()");
- @{$href}{qw(mysql_version connection_id)} = ($version, $connection_id);
- @{$href}{qw(ver_major ver_minor ver_rev)} = $version =~ m/^(\d+)\.(\d+)\.(\d+)/;
-
# Derive and store the server's start time in hi-res
- my $uptime = $dbh->selectrow_hashref("show status like 'Uptime'")->{Value};
+ my $uptime = $dbh->selectrow_hashref("show status like 'Uptime'")->{value};
$href->{start_time} = time() - $uptime;
- # Set timeouts to 8 hours so an unused connection stays alive
- # (for example, a connection might be used in Q mode but idle in T mode).
- $dbh->do("set session wait_timeout=28800, interactive_timeout=28800");
+ # 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} ||= time() - $dbhs{$cxn}->{start_time};
+ $vars->{cxn} = $cxn;
+ }
+ }
+}
+
sub get_new_db_connection {
my ( $connection, $destroy ) = @_;
- my $dsn = $connections{$connection};
- if ( !$dsn->{pass} && !$dsn->{savepass} ) {
- $dsn->{pass} = prompt_noecho("Enter password for $dsn->{user} on $connection");
+ 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}) ) {
- print "\n";
- $dsn->{savepass} = prompt("Save password in plain text in the config file? 1 or 0", undef, 1);
+ 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;
}
@@ -4546,17 +4940,48 @@
return () unless $config{show_cxn_errors_in_tbl}->{val};
return
map { [ $_ . ': ' . $dbhs{$_}->{last_err}, 'red' ] }
- grep { $dbhs{$_}->{err_count} }
+ 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;
}
@@ -4564,15 +4989,34 @@
}
# compile_expr {{{3
-# TODO: strip off "at (eval..." from error.
sub compile_expr {
- my ( $expr, $simple ) = @_;
- if ( $simple ) {
- $expr =~ s/([A-Za-z]\w+)/\$set->{$1}/g;
+ 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 );
- eval "\$sub = sub { my \$set = shift; $expr }";
+ 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;
}
@@ -4630,10 +5074,10 @@
# 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";
+ 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.";
+ print "\ninnotop will not save any configuration changes you make.";
pause();
print "\n";
}
@@ -4661,15 +5105,11 @@
}
# Do some post-processing on %tbl_meta: compile src properties into func etc.
-# TODO: allow 'foo/bar as bif' syntax.
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 )
- = $col_def->{expr}
- ? $col_def->{src}->{func} # Already compiled
- : compile_expr($col_def->{src}, 1);
+ my ( $sub, $err ) = compile_expr($col_def->{src});
$col_def->{func} = $sub;
}
}
@@ -4684,7 +5124,8 @@
last if $line =~ m/^\[/;
my ( $mode, $group ) = $line =~ m/^(.*?)=(.*)$/;
- next unless $mode && $group;
+ next unless $mode && $group
+ && exists $modes{$mode} && exists $server_groups{$group};
$modes{$mode}->{server_group} = $group;
}
}
@@ -4692,7 +5133,7 @@
# save_config_active_server_groups {{{3
sub save_config_active_server_groups {
my $file = shift;
- foreach my $mode ( keys %modes ) {
+ foreach my $mode ( sort keys %modes ) {
print $file "$mode=$modes{$mode}->{server_group}\n";
}
}
@@ -4716,7 +5157,7 @@
# save_config_server_groups {{{3
sub save_config_server_groups {
my $file = shift;
- foreach my $set ( keys %server_groups ) {
+ foreach my $set ( sort keys %server_groups ) {
print $file "$set=", join(' ', @{$server_groups{$set}}), "\n";
}
}
@@ -4731,17 +5172,19 @@
my ( $name, $rest ) = $line =~ m/^(.*?)=(.*)$/;
next unless $name && $rest;
- my @vars = unique(map { $_ } split(/\s+/, $rest));
- next unless @vars;
- $var_sets{$name} = \@vars;
+ $var_sets{$name} = {
+ text => $rest,
+ user => 1,
+ };
}
}
# save_config_varsets {{{3
sub save_config_varsets {
my $file = shift;
- foreach my $varset ( keys %var_sets ) {
- print $file "$varset=", join(' ', @{$var_sets{$varset}}), "\n";
+ foreach my $varset ( sort keys %var_sets ) {
+ next unless $var_sets{$varset}->{user};
+ print $file "$varset=$var_sets{$varset}->{text}\n";
}
}
@@ -4782,8 +5225,8 @@
# save_config_filters {{{3
sub save_config_filters {
my $file = shift;
- foreach my $key ( keys %filters ) {
- next unless $filters{$key}->{user};
+ 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}});
@@ -4800,18 +5243,18 @@
last if $line =~ m/^\[/;
my ( $mode, $rest ) = $line =~ m/^(.*?)=(.*)$/;
- next unless $mode;
- if ( exists $modes{$mode} ) {
- $modes{$mode}->{visible_tables} =
- [ unique(grep { $_ && exists $tbl_meta{$_} } split(/\s+/, $rest)) ];
- }
+ 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 ( keys %modes ) {
+ 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";
}
@@ -4826,8 +5269,9 @@
last if $line =~ m/^\[/;
my ( $key , $rest ) = $line =~ m/^(.*?)=(.*)$/;
- next unless $key;
+ 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});
}
}
@@ -4835,7 +5279,8 @@
# save_config_sort_cols {{{3
sub save_config_sort_cols {
my $file = shift;
- foreach my $tbl ( keys %tbl_meta ) {
+ 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";
}
@@ -4854,13 +5299,16 @@
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 ( keys %tbl_meta ) {
+ 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";
}
@@ -4878,52 +5326,24 @@
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 ( keys %tbl_meta ) {
+ 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";
}
}
-# load_config_expressions {{{3
-sub load_config_expressions {
- my ( $file ) = @_;
- while ( my $line = <$file> ) {
- chomp $line;
- next if $line =~ m/^#/;
- last if $line =~ m/^\[/;
-
- my ( $key, $expr ) = $line =~ m/^(.+?)=(.*)$/;
- next unless $key && $expr;
-
- my ( $sub, $err ) = compile_expr($expr);
- $exprs{$key} = {
- func => $sub,
- text => $expr,
- user => 1,
- name => $key,
- }
- }
-}
-
-# save_config_expressions {{{3
-sub save_config_expressions {
- my $file = shift;
- foreach my $key ( keys %exprs ) {
- next unless $exprs{$key}->{user};
- print $file "$key=$exprs{$key}->{text}\n";
- }
-}
-
# save_config_tbl_meta {{{3
sub save_config_tbl_meta {
my $file = shift;
- foreach my $tbl ( keys %tbl_meta ) {
+ 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};
@@ -4979,9 +5399,9 @@
# Look in the command-line parameters for things stored in the same slot.
my %cmdline =
- map { $opt_spec{$_}->{config} => $opts{$_} }
- grep { exists $opt_spec{$_}->{config} && exists $opts{$_} }
- keys %opt_spec;
+ map { $_->{c} => $opts{$_->{k}} }
+ grep { exists $_->{c} && exists $opts{$_->{k}} }
+ @opt_spec;
while ( my $line = <$file> ) {
chomp $line;
@@ -5013,9 +5433,7 @@
next if $line =~ m/^#/;
last if $line =~ m/^\[/;
- # Each tbl_meta section has all the properties defined in %col_props. If expr
- # is set, it gets looked up by name. That's why load_config_expressions() has
- # to be called before this.
+ # 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
@@ -5054,10 +5472,6 @@
$meta->{cols}->{$col}->{$prop} = $parts{$prop};
}
}
- if ( $meta->{cols}->{$col}->{expr} ) {
- $meta->{cols}->{$col}->{src} = $exprs{$parts{expr}}
- or die "There's no expression named $parts{expr} for column $col in table $tbl";
- }
}
}
@@ -5103,10 +5517,10 @@
# save_config_connections {{{3
sub save_config_connections {
my $file = shift;
- foreach my $conn ( keys %connections ) {
+ 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->{$_}" } @keys), "\n";
+ print $file "$conn=", join(' ', map { "$_=$href->{$_}" } grep { defined $href->{$_} } @keys), "\n";
}
}
@@ -5138,14 +5552,16 @@
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 ( keys %tbl_meta ) {
+ 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(
' ',
@@ -5169,8 +5585,8 @@
last if $line =~ m/^\[/;
my ( $key , $rest ) = $line =~ m/^(.*?)=(.*)$/;
- next unless $key;
- my @parts = split(/ /, $rest);
+ next unless $key && exists $modes{$key};
+ my @parts = grep { exists $connections{$_} } split(/ /, $rest);
$modes{$key}->{connections} = [ @parts ] if exists $modes{$key};
}
}
@@ -5178,12 +5594,34 @@
# save_config_active_connections {{{3
sub save_config_active_connections {
my $file = shift;
- foreach my $mode ( keys %modes ) {
+ 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 ) = @_;
@@ -5192,8 +5630,8 @@
next if $line =~ m/^#/;
last if $line =~ m/^\[/;
- my ( $key , $val ) = $line =~ m/^(.*?)=(.*)$/;
- next unless $key;
+ my ( $key , $val ) = split('=', $line);
+ next unless $key && defined $val && $val =~ m/$num_regex/;
$mvs{$key} = $val;
}
}
@@ -5201,7 +5639,7 @@
# save_config_mvs {{{3
sub save_config_mvs {
my $file = shift;
- foreach my $key ( keys %mvs ) {
+ foreach my $key ( sort keys %mvs ) {
print $file "$key=$mvs{$key}\n";
}
}
@@ -5256,49 +5694,12 @@
}
}
-# get_expr {{{3
-sub get_expr {
- my $exp;
- $clear_screen_sub->();
- print word_wrap("Choose the name of an expression to be used to calculate the column's contents. "
- . "You can choose an existing expression, or type a new name to create a new one.");
- do {
- $exp = prompt_list(
- "Enter expression name",
- '',
- sub { return keys %exprs },
- { map { $_ => trunc(collapse_ws($exprs{$_}->{text}), 30) } keys %exprs },
- { sep => ' ' });
- } while ( !$exp );
- if ( !exists $exprs{$exp} ) {
- my ( $err, $sub, $body );
- do {
- $clear_screen_sub->();
- print word_wrap("The expression you named doesn't exist yet. Specify a Perl expression for the body of "
- . "a subroutine that accepts a hashref called \$set and returns your desired value.");
- print "\n\n";
- if ( $err ) {
- print "There's an error in your expression: $err\n\n";
- }
- $body = prompt("Enter subroutine body");
- ( $sub, $err ) = compile_expr($body);
- } while ( $err );
-
- $exprs{$exp} = {
- func => $sub,
- text => $body,
- user => 1,
- name => $exp,
- };
- }
- return $exp;
-}
-
# edit_color_rules {{{3
sub edit_color_rules {
+ my ( $tbl ) = @_;
$clear_screen_sub->();
- my $tbl = choose_visible_table();
- if ( exists($tbl_meta{$tbl}) ) {
+ $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 };
@@ -5348,7 +5749,7 @@
# Draw the screen and wait for a command.
unshift @display_lines, '',
- "Editing color rules for $meta->{hdr}. Press ? for help, q to "
+ "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 '
@@ -5361,13 +5762,59 @@
}
}
+# 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);
+ return unless $col && $text && exists($tbl_meta{$tbl}->{cols}->{$col});
+ 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;
+ }
+}
+
# edit_table {{{3
sub edit_table {
$clear_screen_sub->();
- my $tbl = choose_visible_table();
- if ( exists($tbl_meta{$tbl}) ) {
+ my ( $tbl ) = @_;
+ $tbl ||= choose_visible_table();
+ if ( $tbl && exists($tbl_meta{$tbl}) ) {
my $meta = $tbl_meta{$tbl};
- my @cols = ('', qw(name hdr label src expr));
+ my @cols = ('', qw(name hdr label src));
my $info = { map { $_ => { hdr => $_, just => '-', } } @cols };
$info->{label}->{maxw} = 30;
my $key;
@@ -5421,7 +5868,7 @@
# Draw the screen and wait for a command.
unshift @display_lines, '',
- "Editing table definition for $meta->{hdr}. Press ? for help, q to quit.", '';
+ "Editing table definition for $meta->{capt}. Press ? for help, q to quit.", '';
draw_screen(\@display_lines);
$key = pause('');
}
@@ -5438,40 +5885,82 @@
"Choose tables to display",
join(' ', @tbls),
sub { return @{$modes{$mode}->{tables}} },
- { map { $_ => $tbl_meta{$_}->{hdr} } @{$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 = @{$modes{$mode}->{visible_tables}};
+ 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{$_}->{hdr} } @tbls }
+ { map { $_ => $tbl_meta{$_}->{capt} } @tbls }
);
}
return $tbl;
}
+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_sort_cols {
+ my ( $tbl ) = @_;
+ $tbl ||= choose_visible_table();
+ return unless $tbl && exists $tbl_meta{$tbl};
$clear_screen_sub->();
- my $tbl = shift;
- return unless $tbl && exists $tbl_meta{$tbl};
my $meta = $tbl_meta{$tbl};
my $val = prompt_list(
- 'Choose sort columns (prefix a column with - to reverse sort)',
+ 'Sort columns (reverse sort with -col)',
$meta->{sort_cols},
sub { return keys %{$meta->{cols}} },
{ map { $_ => $meta->{cols}->{$_}->{label} } keys %{$meta->{cols}} });
- $meta->{sort_cols} = $val;
- $tbl_meta{$tbl}->{sort_func} = make_sort_func($tbl_meta{$tbl});
+ 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
@@ -5488,6 +5977,7 @@
} while ( !$filter || $filter =~ m/\W/ );
}
+ my $completion = sub { keys %{$tbl_meta{$tbl}->{cols}} };
my ( $err, $sub, $body );
do {
$clear_screen_sub->();
@@ -5499,7 +5989,7 @@
if ( $err ) {
print "There's an error in your filter expression: $err\n\n";
}
- $body = prompt("Enter subroutine body");
+ $body = prompt("Enter subroutine body", undef, undef, $completion);
( $sub, $err ) = compile_filter($body);
} while ( $err );
@@ -5527,32 +6017,32 @@
$config{$key}->{val} = $new_value;
}
-# TODO: make a list of all variables and what they come from. Use that for
-# auto-completion here and in add_new_var_set, and for figuring out what data is needed.
-# Cache the list of what data is needed.
sub edit_current_var_set {
my $mode = $config{mode}->{val};
my $name = $config{"${mode}_set"}->{val};
- my $variables = join(' ', @{$var_sets{$name}});
+ my $variables = $var_sets{$name}->{text};
+ my $new = $variables;
do {
$clear_screen_sub->();
- $variables = prompt("Enter variables for $name", undef, $variables );
- } until ( $variables );
+ $new = prompt("Enter variables for $name", undef, $variables);
+ } until ( $new );
- $var_sets{$name} = [ unique(grep { $_ } split(/\s+/, $variables)) ];
+ if ( $new ne $variables ) {
+ @{$var_sets{$name}}{qw(text user)} = ( $new, 1);
+ }
}
sub choose_var_set {
- my $key = shift;
+ 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 { $_ => join(' ', @{$var_sets{$_}}) } sort keys %var_sets });
+ { map { $_ => $var_sets{$_}->{text} } keys %var_sets });
if ( !exists $var_sets{$new_value} ) {
add_new_var_set($new_value);
@@ -5561,6 +6051,15 @@
$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->();
+}
# get_file {{{3
sub get_file {
@@ -5579,6 +6078,22 @@
# 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.
@@ -5588,11 +6103,11 @@
my $group = $modes{$mode}->{server_group};
my %curr = %server_groups;
my $new = choose_or_create_server_group($group, 'to edit');
- my $cxns = join(' ', @{$server_groups{$new}});
$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;
}
@@ -5681,7 +6196,6 @@
# Data-retrieval functions {{{2
# get_status_info {{{3
# Get SHOW STATUS and SHOW VARIABLES together.
-# TODO: figure out how to only get the needed parts. Maybe split status/vars into two subs.
sub get_status_info {
my @cxns = @_;
if ( !$info_gotten{status}++ ) {
@@ -5693,8 +6207,9 @@
my $res = $sth->fetchall_arrayref();
map { $vars->{$_->[0]} = $_->[1] || 0 } @$res;
- # Calculate hi-res uptime and add cxn to the hash
- $vars->{Uptime_hires} ||= $hi_res ? time() - $dbhs{$cxn}->{start_time} : $vars->{Uptime};
+ # 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} ||= time() - $dbhs{$cxn}->{start_time};
$vars->{cxn} = $cxn;
# Add SHOW VARIABLES to the hash
@@ -5705,32 +6220,64 @@
}
}
+# 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 ) {
+ my @threads = sort map { $_->{id} } @candidates;
+ 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 = shift;
+ 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,
);
-
- # Find out which server.
- my @cxns = unique map { $_->{cxn} } @current_queries;
- my ( $cxn ) = select_cxn('On which server', @cxns);
- return unless $cxn && exists($connections{$cxn});
-
- # Find out which connection.
- my @ids = sort map { $_->{id} } grep { $_->{cxn} eq $cxn } @current_queries;
- return unless @ids;
- my $id = prompt_list('Specify a connection ID to analyze',
- $ids[0],
- sub { return @ids });
-
- # Find the info hash of that query on that server.
- my ( $info ) = grep { $cxn eq $_->{cxn} && $id == $_->{id} } @current_queries;
- return unless $info;
-
do {
$actions{$action}->($info);
print "\n";
@@ -5763,8 +6310,11 @@
}
# 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, $tbl ) = @_;
+ my ( $set, $cur, $pre, $tbl ) = @_;
my $result = {};
my $meta = $tbl_meta{$tbl};
my $cols = $meta->{cols};
@@ -5774,9 +6324,12 @@
die "No func defined for '$key' in $tbl"
unless $info->{func};
eval {
- $result->{$key} = $info->{func}->($set)
+ $result->{$key} = $info->{func}->($set, $cur, $pre)
};
if ( $EVAL_ERROR ) {
+ if ( $config{debug}->{val} ) {
+ die $EVAL_ERROR;
+ }
$result->{$key} = $info->{num} ? 0 : '';
}
}
@@ -5810,13 +6363,12 @@
# get_innodb_status {{{3
sub get_innodb_status {
my ( $cxns, $addl_sections ) = @_;
- if ( !$info_gotten{innodb_status}++ ) {
- my $parser = InnoDBParser->new;
+ 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 { $_ }
+ grep { $_ && $tbl_meta{$_}->{innodb} }
get_visible_tables();
# Add in any other sections the caller requested.
@@ -5826,11 +6378,13 @@
foreach my $cxn ( @$cxns ) {
my $stmt = do_stmt($cxn, 'INNODB_STATUS') or next;
- my $innodb_status_text = $stmt->fetchrow_hashref()->{Status};
+ my $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 = (
- $parser->get_status_hash(
+ $innodb_parser->get_status_hash(
$innodb_status_text,
$config{debug}->{val},
\%sections_required,
@@ -5846,7 +6400,7 @@
my $hash = $vars{$cxn}->{$clock};
@{$hash}{ keys %innodb_status } = values %innodb_status;
$hash->{cxn} = $cxn;
- $hash->{Uptime_hires} ||= $hi_res ? time() - $dbhs{$cxn}->{start_time} : $hash->{Uptime};
+ $hash->{Uptime_hires} ||= time() - $dbhs{$cxn}->{start_time};
}
}
}
@@ -5860,8 +6414,9 @@
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, "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
@@ -5890,14 +6445,25 @@
};
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
-# TODO: apparently in version 4 the column names are different?
-# TODO: split into master/slave status...
sub get_master_slave_status {
my @cxns = @_;
if ( !$info_gotten{replication_status}++ ) {
@@ -5912,11 +6478,18 @@
$stmt = do_stmt($cxn, 'SHOW_SLAVE_STATUS') or next;
$res = $stmt->fetchall_arrayref({})->[0];
@{$vars}{ keys %$res } = values %$res;
- $vars->{Uptime_hires} ||= $hi_res ? time() - $dbhs{$cxn}->{start_time} : $vars->{Uptime};
+ $vars->{Uptime_hires} ||= time() - $dbhs{$cxn}->{start_time};
}
}
}
+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.
@@ -5925,97 +6498,183 @@
=head1 NAME
-innotop - A MySQL and InnoDB monitor program.
+innotop - MySQL and InnoDB transaction/status monitor.
=head1 DESCRIPTION
-innotop connects to one or many MySQL database servers and retrieves data, then
-displays it. It can run interactively as a monitor, or serve as a source for
-UNIX pipe-and-filter style programming. innotop uses the data from SHOW
-VARIABLES, SHOW GLOBAL STATUS, SHOW FULL PROCESSLIST, and SHOW ENGINE INNODB
-STATUS, among other things. It refreshes the data at regular intervals, so you
-get a sense of what's happening inside your MySQL servers. You can control how
-fast it refreshes.
+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.
-I originally wrote innotop to parse SHOW INNODB STATUS and show a list of
-current transactions in `top' style, hence the name. It now has much more
-functionality.
+innotop has many 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.
-When innotop is running interactively, you control it with key presses. You can
-find a complete list of all available keys at any time by pressing '?' for help.
-Keys change innotop from one mode to another, let you change configuration, and
-send commands to MySQL servers.
+=head1 QUICK-START
-=head1 OVERVIEW
+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".
-Within each of innotop's modes, innotop displays 'tables' of the current data.
-For example, in T (InnoDB Transactions) mode, it shows the transactions in a
-table. In some modes there are many tables on screen at once.
+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.
-You can choose which tables to display, in what order, which columns and in what
-order, how to sort the rows, colorize and filter the rows, and more. Think of
-the tables as spreadsheets; you have quite a bit of control over what goes into
-the cells. You can even define your own formulas and apply formatting. For
-example, you can choose whether a cell should be right or left justified,
-specify minimum and maximum widths, shorten large numbers to familiar units like
-MB and GB, and turn an integer number of seconds into hours:minutes:seconds
-display.
+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.
-Some modes allow you to see the incremental changes since last refresh. This
-can be useful to see how many new queries have been issued during that time, for
-example. You can toggle this on and off.
+After this, you should be connected, and innotop should show you something like
+the following:
-You can define many connections to servers, group the servers together, and
-switch between them easily to manage many MySQL instances conveniently. See
-SERVER GROUPS for more.
+ 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
-Remember, press '?' to see what commands are available to you at any time.
+(This sample is truncated at the right so it will fit on a terminal when running
+'man innotop')
-=head1 CONFIGURATION
+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.
-innotop is completely configurable. The default configuration is built into the
-program, but everything is written out to a configuration file when you exit
-innotop. You can edit this file by hand as you wish, or just use the built-in
-configuration commands while innotop is running.
+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.
-You can specify certain options on the command-line. Run `innotop --help' for
-details.
+To quit innotop, press the 'q' key.
+=head1 OPTIONS
+
+You can negate some options by prefixing the option name with --no. For
+example, --noinc negates L<"--inc">.
+
+=over
+
+=item --help
+
+Print a summary of command-line usage and exit.
+
+=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
-innotop has many modes. The following is a brief description of each in
-alphabetical order. Remember, you can always get the authoritative help by
-pressing '?'.
+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 8
+=over
=item B: InnoDB Buffers
-This mode displays the InnoDB buffer pool, page statistics, insert buffer, and
-adaptive hash index.
+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 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 (recall that a
-deadlock is caused by a cycle in the waits-for graph).
+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 indeed, and there is a limit on the size of the SHOW INNODB
+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 it. 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 -- look in your configuration file.
+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 set innotop to automatically detect when a large deadlock needs to
-be replaced with a small one. This feature is turned off by default.
+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
@@ -6027,27 +6686,24 @@
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.
-=item G: Load Graph
+This mode displays the L<"fk_error"> table by default.
-This mode calculates per-second statistics, such as queries per second, scales
-them against a maximum, and prints them out as a "bar graph." It's similar to
-the Load Statistics mode, except it's a graph instead of numbers.
-
-Headers are abbreviated to fit on the screen if necessary. This only happens in
-interactive operation, not while running unattended.
-
=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.
+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 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 vice versa.
+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
@@ -6055,53 +6711,72 @@
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.
-You can toggle an informative header that shows general status information about
-your server. There are default sorting, filtering, and colorization rules.
+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. This will allow you to see the query's
-full text, the results of EXPLAIN, and in newer MySQL versions, even see the
-optimized query resulting from EXPLAIN EXTENDED.
+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.
+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: Load Statistics
+=item S: Variables & Status
This mode calculates statistics, such as queries per second, and prints them out
-in the style of <vmstat>. It's similar to the Load Graph mode, except it's a
-numbers instead of a graph. You can show absolute values or incremental values
-since the last refresh. Like G mode, 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. You can choose your own sets.
+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 every transaction in the InnoDB monitor's output, in `top'
+This mode shows transactions from the InnoDB monitor's output, in B<top>-like
format. This mode is the reason I wrote innotop.
-By default, two filters are applied to the table to hide inactive transactions
-and hide innotop's own transaction. You can toggle this on and off. There are
-also default sort and colorization rules in this view. You can customize these.
+You can kill queries or processes with the 'k' and 'x' keys, and EXPLAIN a query
+with the 'e' or 'f' keys.
-If you are only viewing one server's transactions, innotop can display an
-informational header. This will show you things like how many entries there are
-in the InnoDB history list, how much of the buffer pool is used, and so forth.
+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.
-=item V: Variables & Status
+This mode displays the L<"t_header"> and L<"innodb_transactions"> tables by
+default.
-This mode displays any variables you please from SHOW GLOBAL STATUS and SHOW
-VARIABLES, as well as the values parsed from SHOW INNODB STATUS. It displays
-not only the current values, but previous values too; you choose how many sets
-to keep on screen.
-
=item W: InnoDB Lock Waits
This mode shows information about current InnoDB lock waits. This information
@@ -6110,58 +6785,1391 @@
which tables and indexes are the "hot spot" for locks. If your server is
running pretty well, this mode should show nothing.
-A second table shows any waits in the OS wait array. This comes from a separate
-section of the status text. If you see frequent waits, your server is probably
-running under a high concurrency workload. This is the same table displayed in
-R mode.
+A second table shows any waits in the OS wait array. This comes from a
+different section of the status text. If you see frequent waits, your server is
+probably running under a high concurrency workload. This is the same table
+displayed in R mode.
+This mode displays the L<"lock_waits"> and L<"wait_array"> 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.
+
+=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).
+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 a lot of MySQL instances, or even if you only have a few, you will
-probably find this functionality helpful.
+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.
-To begin with, when you start innotop it will prompt you to define a connection
-to a server. After that is done, you can tell it to monitor another server with
-the @ key. This key actually brings up a list of connections you've defined.
-If you name one that doesn't exist, innotop will guide you through the process
-of defining it as a new connection, and it will be available from then on.
+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.
-You can name multiple connections in any mode. For example, suppose you are in
-T mode, monitoring transactions on server1; if you press @, you can type
-'server1 server2' and see data from both.
+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.
-This becomes unwieldy after a bit though. To address this, you can press the
-'#' key to create and select server groups. Groups work just the same as
-connections: if you name one that doesn't exist, you can create it.
+=head1 SWITCHING BETWEEN CONNECTIONS
-As an example, you might have groups named 'all', 'masters', 'slaves', 'oltp'
-and 'olap'. Many of the servers could belong to several of these groups. It's
-just a quick way to toggle between various servers.
+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.
-Once you have defined groups, you can press the TAB key to cycle between them.
+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.
-As of this writing innotop does NOT fetch data in parallel from different
-servers, so if your groups get large you may notice increased delay time when
-innotop refreshes. You can address this by creating more small groups. At some
-point I plan to make the data-fetching multi-threaded and this problem will not
-be so severe.
+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 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 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 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 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">.
+
+=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, unless they are to be pivoted. See L<"SORTING">.
+
+=item *
+
+innotop colorizes the rows, again skipping this step if they are to be
+pivoted. See L<"COLORS">.
+
+=item *
+
+innotop transforms the column values in each row. See L<"TRANSFORMATIONS">.
+
+=item *
+
+innotop optionally pivots the rows (see L<"PIVOTING">).
+
+=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
+
+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 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_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 lock_waits
+
+Displays InnoDB lock waits, i.e. transactions which are waiting for a lock to be
+granted. Data source: L<"LOCK_WAITS">.
+
+=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">.
+
+=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 PIVOTING
+
+innotop can pivot a table for more compact display, just like a Pivot Table in a
+spreadsheet (also known as a crosstab). This is used in L<"S: Variables &
+Status"> mode, for example.
+
+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
+
+This functionality is currently limited because it becomes tricky to pivot when
+innotop is displaying data from multiple connections, each with a 'foo' and
+'bar' value. In the future innotop will support some kind of aggregation,
+similar to SQL GROUP BY, to help solve this problem.
+
+=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.
+
+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.
+
+=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 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 LOCK_WAITS
+
+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 must connect to the DB server as a user who has the SUPER privilege for
-many of the functions. If you don't have the SUPER privilege, you may still
-be able to run some functions.
+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.
-I think everything you need to run innotop is distributed either with Perl, or
-with innotop itself. You need DBI and DBD::mysql. You also need the
-InnoDBParser module, and Term::ReadKey. If you have Time::HiRes, innotop will
-use it. 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.
+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
@@ -6169,19 +8177,69 @@
I don't know for sure. It also runs on Windows under ActivePerl without
problem.
-I have perl v5.8.8 installed, and I've had reports of it working on 5.8.5 but
-I don't know about other versions.
+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.
-I use innotop on MySQL version 4.1 and 5.0, and have heard of others using it
-on these same versions and 5.1.
-
=head1 FILES
$HOMEDIR/.innotop is used to store configuration information.
+$HOMEDIR/.innotop_core_dump contains verbose error messages if L<"debug"> is
+enabled.
+=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, baron at xaprb dot com.
+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
@@ -6202,13 +8260,14 @@
=head1 AUTHOR
-Baron Schwartz, baron at xaprb dot com.
+Baron Schwartz.
=head1 BUGS
-If you find any problems with innotop, please contact me. Specifically, if
-you find any problems with parsing the InnoDB monitor output, I would greatly
-appreciate you sending me the full text of the monitor output that caused the
-problem.
+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
Modified: branches/sid-5.0/debian/changelog
===================================================================
--- branches/sid-5.0/debian/changelog 2007-11-08 18:39:40 UTC (rev 974)
+++ branches/sid-5.0/debian/changelog 2007-11-08 19:04:38 UTC (rev 975)
@@ -15,8 +15,9 @@
* Suggest usage of an update statement on the user table to change the mysql
root user password instead using mysqladmin, to catch all root users from
all hosts. (closes: #435744)
+ * Updated innotop to 1.4.3 release.
- -- Norbert Tretkowski <nobse at debian.org> Thu, 08 Nov 2007 19:29:38 +0100
+ -- Norbert Tretkowski <nobse at debian.org> Thu, 08 Nov 2007 20:03:58 +0100
mysql-dfsg-5.0 (5.0.45-1) unstable; urgency=low
More information about the Pkg-mysql-commits
mailing list