Merge lp:~percona-dev/percona-patches/5.0.87-with-configs into lp:percona-patches

Proposed by Aleksandr Kuzminsky
Status: Merged
Approved by: Vadim Tkachenko
Approved revision: not available
Merged at revision: not available
Proposed branch: lp:~percona-dev/percona-patches/5.0.87-with-configs
Merge into: lp:percona-patches
Diff against target: 36490 lines (+35913/-0)
114 files modified
build/debian/README.Maintainer (+122/-0)
build/debian/additions/Docs__Images__Makefile.in (+6/-0)
build/debian/additions/Docs__Makefile.in (+6/-0)
build/debian/additions/debian-start (+31/-0)
build/debian/additions/debian-start.inc.sh (+72/-0)
build/debian/additions/echo_stderr (+2/-0)
build/debian/additions/innotop/InnoDBParser.pm (+1089/-0)
build/debian/additions/innotop/changelog.innotop (+318/-0)
build/debian/additions/innotop/innotop (+9485/-0)
build/debian/additions/innotop/innotop.1 (+2086/-0)
build/debian/additions/manpages/mysqlbug.1 (+14/-0)
build/debian/additions/manpages/mysqldumpslow.1 (+50/-0)
build/debian/additions/my.cnf (+143/-0)
build/debian/additions/mysql-server.lintian-overrides (+2/-0)
build/debian/additions/mysqlreport/mysqlreport (+1151/-0)
build/debian/additions/mysqlreport/mysqlreport.1 (+180/-0)
build/debian/additions/ndb_mgmd.cnf (+35/-0)
build/debian/changelog (+3964/-0)
build/debian/changelog.orig (+3935/-0)
build/debian/compat (+1/-0)
build/debian/control (+113/-0)
build/debian/copyright (+169/-0)
build/debian/copyright.more (+60/-0)
build/debian/libmysqlclient15-dev.README.Maintainer (+4/-0)
build/debian/libmysqlclient15-dev.dirs (+2/-0)
build/debian/libmysqlclient15-dev.docs (+1/-0)
build/debian/libmysqlclient15-dev.examples (+1/-0)
build/debian/libmysqlclient15-dev.files (+7/-0)
build/debian/libmysqlclient15-dev.links (+2/-0)
build/debian/libmysqlclient15-dev.lintian-overrides (+2/-0)
build/debian/libmysqlclient15off.NEWS (+14/-0)
build/debian/libmysqlclient15off.README.Debian (+30/-0)
build/debian/libmysqlclient15off.dirs (+1/-0)
build/debian/libmysqlclient15off.docs (+1/-0)
build/debian/libmysqlclient15off.files (+1/-0)
build/debian/libmysqlclient15off.postinst (+12/-0)
build/debian/mysql-client-5.0.NEWS (+6/-0)
build/debian/mysql-client-5.0.README.Debian (+4/-0)
build/debian/mysql-client-5.0.dirs (+3/-0)
build/debian/mysql-client-5.0.docs (+3/-0)
build/debian/mysql-client-5.0.files (+46/-0)
build/debian/mysql-client-5.0.links (+3/-0)
build/debian/mysql-client-5.0.lintian-overrides (+3/-0)
build/debian/mysql-common.dirs (+1/-0)
build/debian/mysql-common.files (+2/-0)
build/debian/mysql-common.postrm (+7/-0)
build/debian/mysql-common.preinst (+215/-0)
build/debian/mysql-server-5.0.NEWS (+17/-0)
build/debian/mysql-server-5.0.README.Debian (+131/-0)
build/debian/mysql-server-5.0.config (+70/-0)
build/debian/mysql-server-5.0.dirs (+9/-0)
build/debian/mysql-server-5.0.docs (+2/-0)
build/debian/mysql-server-5.0.files (+85/-0)
build/debian/mysql-server-5.0.links (+2/-0)
build/debian/mysql-server-5.0.lintian-overrides (+4/-0)
build/debian/mysql-server-5.0.logcheck.ignore.paranoid (+11/-0)
build/debian/mysql-server-5.0.logcheck.ignore.server (+32/-0)
build/debian/mysql-server-5.0.logcheck.ignore.workstation (+32/-0)
build/debian/mysql-server-5.0.mysql-ndb-mgm.init (+86/-0)
build/debian/mysql-server-5.0.mysql-ndb.init (+103/-0)
build/debian/mysql-server-5.0.mysql-server.logrotate (+27/-0)
build/debian/mysql-server-5.0.mysql.init (+190/-0)
build/debian/mysql-server-5.0.postinst (+277/-0)
build/debian/mysql-server-5.0.postrm (+97/-0)
build/debian/mysql-server-5.0.preinst (+174/-0)
build/debian/mysql-server-5.0.prerm (+8/-0)
build/debian/mysql-server-5.0.templates (+102/-0)
build/debian/mysql-server.preinst (+198/-0)
build/debian/patches/00list (+11/-0)
build/debian/patches/01_MAKEFILES__Docs_Images_Makefile.in.dpatch (+776/-0)
build/debian/patches/01_MAKEFILES__Docs_Makefile.in.dpatch (+776/-0)
build/debian/patches/10_mysql_secure_installation.dpatch (+18/-0)
build/debian/patches/25_mysys__default.c.dpatch (+19/-0)
build/debian/patches/33_scripts__mysql_create_system_tables__no_test.dpatch (+18/-0)
build/debian/patches/37_scripts__mysqld_safe.sh__syslog.dpatch (+124/-0)
build/debian/patches/38_scripts__mysqld_safe.sh__signals.dpatch (+45/-0)
build/debian/patches/41_scripts__mysql_install_db.sh__no_test.dpatch (+19/-0)
build/debian/patches/44_scripts__mysql_config__libs.dpatch (+24/-0)
build/debian/patches/45_warn-CLI-passwords.dpatch (+203/-0)
build/debian/patches/86_PATH_MAX.dpatch (+139/-0)
build/debian/patches/89_ndb__staticlib.dpatch (+43/-0)
build/debian/patches/91_SECURITY_CVE-2007-5925.dpatch (+123/-0)
build/debian/patches/92_SECURITY_CVE-2008-4098.dpatch (+634/-0)
build/debian/patches/93_SECURITY_CVE-2008-3963.dpatch (+129/-0)
build/debian/patches/94_SECURITY_CVE-2008-4456.dpatch (+37/-0)
build/debian/patches/99_percona_support.dpatch (+20/-0)
build/debian/po/POTFILES.in (+1/-0)
build/debian/po/ar.po (+223/-0)
build/debian/po/ca.po (+344/-0)
build/debian/po/cs.po (+311/-0)
build/debian/po/da.po (+354/-0)
build/debian/po/de.po (+235/-0)
build/debian/po/es.po (+361/-0)
build/debian/po/eu.po (+219/-0)
build/debian/po/fi.po (+209/-0)
build/debian/po/fr.po (+229/-0)
build/debian/po/gl.po (+216/-0)
build/debian/po/it.po (+219/-0)
build/debian/po/ja.po (+225/-0)
build/debian/po/nb.po (+288/-0)
build/debian/po/nl.po (+220/-0)
build/debian/po/pt.po (+276/-0)
build/debian/po/pt_BR.po (+415/-0)
build/debian/po/ro.po (+281/-0)
build/debian/po/ru.po (+221/-0)
build/debian/po/sk.po (+164/-0)
build/debian/po/sv.po (+224/-0)
build/debian/po/templates.pot (+189/-0)
build/debian/po/tr.po (+297/-0)
build/debian/po/vi.po (+190/-0)
build/debian/rules (+349/-0)
build/debian/source.lintian-overrides (+4/-0)
build/debian/watch (+3/-0)
build/mysql-percona.spec (+1426/-0)
To merge this branch: bzr merge lp:~percona-dev/percona-patches/5.0.87-with-configs
Reviewer Review Type Date Requested Status
Vadim Tkachenko Approve
Review via email: mp+17977@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Aleksandr Kuzminsky (akuzminsky) wrote :

The branch contains the patches as well as build options

Revision history for this message
Vadim Tkachenko (vadim-tk) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory 'build'
2=== added directory 'build/debian'
3=== added file 'build/debian/README.Maintainer'
4--- build/debian/README.Maintainer 1970-01-01 00:00:00 +0000
5+++ build/debian/README.Maintainer 2010-01-24 19:18:15 +0000
6@@ -0,0 +1,122 @@
7+###########################################################################
8+# Here are some information that are only of interest for the current and #
9+# following Debian maintainers of MySQL. #
10+###########################################################################
11+
12+The debian/ directory is under SVN control at
13+ svn+ssh://svn.debian.org/svn/pkg-mysql/branches/sid-5.0/debian
14+
15+#
16+# Preparing a new version
17+#
18+The new orig.tar.gz (without non-free documentation) is created in /tmp/ when
19+running this command:
20+
21+debian/rules get-orig-source
22+
23+#
24+# mysqlreport
25+#
26+The authors e-mail address is <public@codenode.com>.
27+
28+#
29+# SphinxSE
30+#
31+The authors email address is Andrew Aksyonoff <shodan@shodan.ru>.
32+
33+The Sphinx Search engine itself is in the process of being packaged for Debian.
34+
35+#
36+# Remarks to dependencies
37+#
38+libwrap0-dev (>= 7.6-8.3)
39+ According to bug report 114582 where where build problems on
40+ IA-64/sid with at least two prior versions.
41+psmisc
42+ /usr/bin/killall in the initscript
43+
44+zlib1g in libmysqlclient-dev:
45+ "mysql_config --libs" ads "-lz"
46+
47+Build-Dep:
48+
49+debhelper (>=4.1.16):
50+ See po-debconf(7).
51+
52+autoconf (>= 2.13-20), automake1.7
53+ Try to get rid of them.
54+
55+doxygen, tetex-bin, tetex-extra, gs
56+ for ndb/docs/*tex
57+
58+mysql-server-5.0: Pre-Depends: mysql-common
59+ This was necessary as mysql-server-5.0.preinst checks for unmodified
60+ conffiles from mysql-server-4.1 and copies 5.0 ones over them to avoid
61+ unnecessary dpkg questions. As mysql-server-5.0 is not unpacked at its
62+ pre-inst stage, it had to copy those files from a package that is
63+ definetly already unpacked which does not have to be the case with Depends.
64+
65+#
66+# Remarks to the start scripts
67+#
68+
69+## initscripts rely on mysqladmin from a different package
70+We have the problem that "/etc/init.d/mysql stop" relies on mysqladmin which
71+is in another package (mysql-client) and a passwordless access that's maybe
72+only available if the user configured his /root/.my.cnf. Can this be a problem?
73+* normal mode: not because the user is required to have it. Else:
74+* purge/remove: not, same as normal mode
75+* upgrade: not, same as normal mode
76+* first install: not, it depends on mysql-client which at least is unpacked
77+ so mysqladmin is there (to ping). It is not yet configured
78+ passwordles but if there's a server running then there's a
79+ /root/.my.cnf. Anyways, we simply kill anything that's mysqld.
80+
81+## Passwordless access for the maintainer scripts
82+Another issue is that the scripts needs passwordless access. To ensure this
83+a debian-sys-maint user is configured which has process and shutdown privs.
84+The file with the randomly (that's important!) generated password must be
85+present as long as the databases remain installed because else a new install
86+would have no access. This file should be used like:
87+ mysqladmin --defaults-file=/etc/mysql/debian.cnf restart
88+to avoid providing the password in plaintext on a commandline where it would
89+be visible to any user via the "ps" command.
90+
91+## When to start the daemon?
92+We aim to give the admin full control on when MySQL is running.
93+Issues to be faced here:
94+OLD:
95+ 1. Debconf asks whether MySQL should be started on boot so update-rc.d is
96+ only run if the answer has been yes. The admin is likely to forget
97+ this decision but update-rc.d checks for an existing line in
98+ /etc/runlevel.conf and leaves it intact.
99+ 2. On initial install, if the answer is yes, the daemon has to be started.
100+ 3. On upgrades it should only be started if it was already running, everything
101+ else is confusing. Especiall relying on an debconf decision made month ago
102+ is considered suboptimal. See bug #274264
103+ Implementation so far:
104+ prerm (called on upgrade before stopping the server):
105+ check for a running server and set flag if necessary
106+ preinst (called on initial install and before unpacking when upgrading):
107+ check for the debconf variable and set flag if necessary
108+ postinst (called on initial install and after each upgrade after unpacking):
109+ call update-rc.d if debconf says yes
110+ call invoce-rc.d if the flag has been set
111+ Problems remaining:
112+ dpkg-reconfigure and setting mysql start on boot to yes did not start mysql
113+ (ok "start on boot" literally does not mean "start now" so that might have been ok)
114+NEW:
115+ 1. --- no debconf anymore for the sake of simplicity. We have runlevel.conf,
116+ the admin should use it
117+ 2. On initial install the server is started.
118+ 3. On upgrades the server is started exactly if it was running before so the
119+ runlevel configuration is irrelevant. It will be preserved by the mean of
120+ update-rc.d's builtin check.
121+ Implementation:
122+ prerm (called on upgrade before stopping the server):
123+ check for a running server and set flag if necessary
124+ preinst (called on initial install and before unpacking when upgrading):
125+ check for $1 beeing (initial) "install" and set flag
126+ postinst (called on initial install and after each upgrade after unpacking):
127+ call update-rc.d
128+ call invoce-rc.d if the flag has been set
129
130=== added directory 'build/debian/additions'
131=== added file 'build/debian/additions/Docs__Images__Makefile.in'
132--- build/debian/additions/Docs__Images__Makefile.in 1970-01-01 00:00:00 +0000
133+++ build/debian/additions/Docs__Images__Makefile.in 2010-01-24 19:18:15 +0000
134@@ -0,0 +1,6 @@
135+all:
136+
137+distclean:
138+ -rm -f Makefile
139+
140+.PHONY: all distclean clean install check
141
142=== added file 'build/debian/additions/Docs__Makefile.in'
143--- build/debian/additions/Docs__Makefile.in 1970-01-01 00:00:00 +0000
144+++ build/debian/additions/Docs__Makefile.in 2010-01-24 19:18:15 +0000
145@@ -0,0 +1,6 @@
146+all:
147+
148+distclean:
149+ -rm -f Makefile
150+
151+.PHONY: all distclean clean install check
152
153=== added file 'build/debian/additions/debian-start'
154--- build/debian/additions/debian-start 1970-01-01 00:00:00 +0000
155+++ build/debian/additions/debian-start 2010-01-24 19:18:15 +0000
156@@ -0,0 +1,31 @@
157+#!/bin/bash
158+#
159+# This script is executed by "/etc/init.d/mysql" on every (re)start.
160+#
161+# Changes to this file will be preserved when updating the Debian package.
162+#
163+
164+source /usr/share/mysql/debian-start.inc.sh
165+
166+MYSQL="/usr/bin/mysql --defaults-file=/etc/mysql/debian.cnf"
167+MYADMIN="/usr/bin/mysqladmin --defaults-file=/etc/mysql/debian.cnf"
168+MYUPGRADE="/usr/bin/mysql_upgrade --defaults-extra-file=/etc/mysql/debian.cnf"
169+MYCHECK="/usr/bin/mysqlcheck --defaults-file=/etc/mysql/debian.cnf"
170+MYCHECK_SUBJECT="WARNING: mysqlcheck has found corrupt tables"
171+MYCHECK_PARAMS="--all-databases --fast --silent"
172+MYCHECK_RCPT="root"
173+
174+# The following commands should be run when the server is up but in background
175+# where they do not block the server start and in one shell instance so that
176+# they run sequentially. They are supposed not to echo anything to stdout.
177+# If you want to disable the check for crashed tables comment
178+# "check_for_crashed_tables" out.
179+# (There may be no output to stdout inside the background process!)
180+echo "Checking for corrupt, not cleanly closed and upgrade needing tables."
181+(
182+ upgrade_system_tables_if_necessary;
183+ check_root_accounts;
184+ check_for_crashed_tables;
185+) >&2 &
186+
187+exit 0
188
189=== added file 'build/debian/additions/debian-start.inc.sh'
190--- build/debian/additions/debian-start.inc.sh 1970-01-01 00:00:00 +0000
191+++ build/debian/additions/debian-start.inc.sh 2010-01-24 19:18:15 +0000
192@@ -0,0 +1,72 @@
193+#!/bin/bash
194+#
195+# This file is included by /etc/mysql/debian-start
196+#
197+
198+## Check all unclosed tables.
199+# - Requires the server to be up.
200+# - Is supposed to run silently in background.
201+function check_for_crashed_tables() {
202+ set -e
203+ set -u
204+
205+ # But do it in the background to not stall the boot process.
206+ logger -p daemon.info -i -t$0 "Triggering myisam-recover for all MyISAM tables"
207+
208+ # Checking for $? is unreliable so the size of the output is checked.
209+ # Some table handlers like HEAP do not support CHECK TABLE.
210+ tempfile=`tempfile`
211+ # We have to use xargs in this case, because a for loop barfs on the
212+ # spaces in the thing to be looped over.
213+ LC_ALL=C $MYSQL --skip-column-names --batch -e "
214+ SELECT CONCAT('SELECT COUNT(*) INTO @discard FROM \`',
215+ TABLE_SCHEMA, '\`.\`', TABLE_NAME, '\`' )
216+ FROM information_schema.TABLES WHERE ENGINE='MyISAM'" | \
217+ xargs -i $MYSQL --skip-column-names --silent --batch \
218+ --force -e "{}" >$tempfile
219+ if [ -s $tempfile ]; then
220+ (
221+ /bin/echo -e "\n" \
222+ "Improperly closed tables are also reported if clients are accessing\n" \
223+ "the tables *now*. A list of current connections is below.\n";
224+ $MYADMIN processlist status
225+ ) >> $tempfile
226+ # Check for presence as a dependency on mailx would require an MTA.
227+ if [ -x /usr/bin/mailx ]; then
228+ mailx -e -s"$MYCHECK_SUBJECT" $MYCHECK_RCPT < $tempfile
229+ fi
230+ (echo "$MYCHECK_SUBJECT"; cat $tempfile) | logger -p daemon.warn -i -t$0
231+ fi
232+ rm $tempfile
233+}
234+
235+## Check for tables needing an upgrade.
236+# - Requires the server to be up.
237+# - Is supposed to run silently in background.
238+function upgrade_system_tables_if_necessary() {
239+ set -e
240+ set -u
241+
242+ logger -p daemon.info -i -t$0 "Upgrading MySQL tables if necessary."
243+
244+ # Filter all "duplicate column", "duplicate key" and "unknown column"
245+ # errors as the script is designed to be idempotent.
246+ LC_ALL=C $MYUPGRADE \
247+ 2>&1 \
248+ | egrep -v '^(1|@had|ERROR (1054|1060|1061))' \
249+ | logger -p daemon.warn -i -t$0
250+}
251+
252+## Check for the presence of both, root accounts with and without password.
253+# This might have been caused by a bug related to mysql_install_db (#418672).
254+function check_root_accounts() {
255+ set -e
256+ set -u
257+
258+ logger -p daemon.info -i -t$0 "Checking for insecure root accounts."
259+
260+ ret=$( echo "SELECT count(*) FROM mysql.user WHERE user='root' and password='';" | $MYSQL --skip-column-names )
261+ if [ "$ret" -ne "0" ]; then
262+ logger -p daemon.warn -i -t$0 "WARNING: mysql.user contains $ret root accounts without password!"
263+ fi
264+}
265
266=== added file 'build/debian/additions/echo_stderr'
267--- build/debian/additions/echo_stderr 1970-01-01 00:00:00 +0000
268+++ build/debian/additions/echo_stderr 2010-01-24 19:18:15 +0000
269@@ -0,0 +1,2 @@
270+#!/bin/bash
271+echo "$*" 1>&2
272
273=== added directory 'build/debian/additions/innotop'
274=== added file 'build/debian/additions/innotop/InnoDBParser.pm'
275--- build/debian/additions/innotop/InnoDBParser.pm 1970-01-01 00:00:00 +0000
276+++ build/debian/additions/innotop/InnoDBParser.pm 2010-01-24 19:18:15 +0000
277@@ -0,0 +1,1089 @@
278+use strict;
279+use warnings FATAL => 'all';
280+
281+package InnoDBParser;
282+
283+# This program is copyright (c) 2006 Baron Schwartz, baron at xaprb dot com.
284+# Feedback and improvements are gratefully received.
285+#
286+# THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
287+# WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
288+# MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
289+#
290+# This program is free software; you can redistribute it and/or modify it under
291+# the terms of the GNU General Public License as published by the Free Software
292+# Foundation, version 2; OR the Perl Artistic License. On UNIX and similar
293+# systems, you can issue `man perlgpl' or `man perlartistic' to read these
294+
295+# You should have received a copy of the GNU General Public License along with
296+# this program; if not, write to the Free Software Foundation, Inc., 59 Temple
297+# Place, Suite 330, Boston, MA 02111-1307 USA
298+
299+our $VERSION = '1.6.0';
300+
301+use Data::Dumper;
302+$Data::Dumper::Sortkeys = 1;
303+use English qw(-no_match_vars);
304+use List::Util qw(max);
305+
306+# Some common patterns
307+my $d = qr/(\d+)/; # Digit
308+my $f = qr/(\d+\.\d+)/; # Float
309+my $t = qr/(\d+ \d+)/; # Transaction ID
310+my $i = qr/((?:\d{1,3}\.){3}\d+)/; # IP address
311+my $n = qr/([^`\s]+)/; # MySQL object name
312+my $w = qr/(\w+)/; # Words
313+my $fl = qr/([\w\.\/]+) line $d/; # Filename and line number
314+my $h = qr/((?:0x)?[0-9a-f]*)/; # Hex
315+my $s = qr/(\d{6} .\d:\d\d:\d\d)/; # InnoDB timestamp
316+
317+# If you update this variable, also update the SYNOPSIS in the pod.
318+my %innodb_section_headers = (
319+ "TRANSACTIONS" => "tx",
320+ "BUFFER POOL AND MEMORY" => "bp",
321+ "SEMAPHORES" => "sm",
322+ "LOG" => "lg",
323+ "ROW OPERATIONS" => "ro",
324+ "INSERT BUFFER AND ADAPTIVE HASH INDEX" => "ib",
325+ "FILE I/O" => "io",
326+ "LATEST DETECTED DEADLOCK" => "dl",
327+ "LATEST FOREIGN KEY ERROR" => "fk",
328+);
329+
330+my %parser_for = (
331+ tx => \&parse_tx_section,
332+ bp => \&parse_bp_section,
333+ sm => \&parse_sm_section,
334+ lg => \&parse_lg_section,
335+ ro => \&parse_ro_section,
336+ ib => \&parse_ib_section,
337+ io => \&parse_io_section,
338+ dl => \&parse_dl_section,
339+ fk => \&parse_fk_section,
340+);
341+
342+my %fk_parser_for = (
343+ Transaction => \&parse_fk_transaction_error,
344+ Error => \&parse_fk_bad_constraint_error,
345+ Cannot => \&parse_fk_cant_drop_parent_error,
346+);
347+
348+# A thread's proc_info can be at least 98 different things I've found in the
349+# source. Fortunately, most of them begin with a gerunded verb. These are
350+# the ones that don't.
351+my %is_proc_info = (
352+ 'After create' => 1,
353+ 'Execution of init_command' => 1,
354+ 'FULLTEXT initialization' => 1,
355+ 'Reopen tables' => 1,
356+ 'Repair done' => 1,
357+ 'Repair with keycache' => 1,
358+ 'System lock' => 1,
359+ 'Table lock' => 1,
360+ 'Thread initialized' => 1,
361+ 'User lock' => 1,
362+ 'copy to tmp table' => 1,
363+ 'discard_or_import_tablespace' => 1,
364+ 'end' => 1,
365+ 'got handler lock' => 1,
366+ 'got old table' => 1,
367+ 'init' => 1,
368+ 'key cache' => 1,
369+ 'locks' => 1,
370+ 'malloc' => 1,
371+ 'query end' => 1,
372+ 'rename result table' => 1,
373+ 'rename' => 1,
374+ 'setup' => 1,
375+ 'statistics' => 1,
376+ 'status' => 1,
377+ 'table cache' => 1,
378+ 'update' => 1,
379+);
380+
381+sub new {
382+ bless {}, shift;
383+}
384+
385+# Parse the status and return it.
386+# See srv_printf_innodb_monitor in innobase/srv/srv0srv.c
387+# Pass in the text to parse, whether to be in debugging mode, which sections
388+# to parse (hashref; if empty, parse all), and whether to parse full info from
389+# locks and such (probably shouldn't unless you need to).
390+sub parse_status_text {
391+ my ( $self, $fulltext, $debug, $sections, $full ) = @_;
392+
393+ die "I can't parse undef" unless defined $fulltext;
394+ $fulltext =~ s/[\r\n]+/\n/g;
395+
396+ $sections ||= {};
397+ die '$sections must be a hashref' unless ref($sections) eq 'HASH';
398+
399+ my %innodb_data = (
400+ got_all => 0, # Whether I was able to get the whole thing
401+ ts => '', # Timestamp the server put on it
402+ last_secs => 0, # Num seconds the averages are over
403+ sections => {}, # Parsed values from each section
404+ );
405+
406+ if ( $debug ) {
407+ $innodb_data{'fulltext'} = $fulltext;
408+ }
409+
410+ # Get the most basic info about the status: beginning and end, and whether
411+ # I got the whole thing (if there has been a big deadlock and there are
412+ # too many locks to print, the output might be truncated)
413+ my ( $time_text ) = $fulltext =~ m/^$s INNODB MONITOR OUTPUT$/m;
414+ $innodb_data{'ts'} = [ parse_innodb_timestamp( $time_text ) ];
415+ $innodb_data{'timestring'} = ts_to_string($innodb_data{'ts'});
416+ ( $innodb_data{'last_secs'} ) = $fulltext
417+ =~ m/Per second averages calculated from the last $d seconds/;
418+
419+ ( my $got_all ) = $fulltext =~ m/END OF INNODB MONITOR OUTPUT/;
420+ $innodb_data{'got_all'} = $got_all || 0;
421+
422+ # Split it into sections. Each section begins with
423+ # -----
424+ # LABEL
425+ # -----
426+ my %innodb_sections;
427+ my @matches = $fulltext
428+ =~ m#\n(---+)\n([A-Z /]+)\n\1\n(.*?)(?=\n(---+)\n[A-Z /]+\n\4\n|$)#gs;
429+ while ( my ( $start, $name, $text, $end ) = splice(@matches, 0, 4) ) {
430+ $innodb_sections{$name} = [ $text, $end ? 1 : 0 ];
431+ }
432+ # The Row Operations section is a special case, because instead of ending
433+ # with the beginning of another section, it ends with the end of the file.
434+ # So this section is complete if the entire file is complete.
435+ $innodb_sections{'ROW OPERATIONS'}->[1] ||= $innodb_data{'got_all'};
436+
437+ # Just for sanity's sake, make sure I understand what to do with each
438+ # section
439+ eval {
440+ foreach my $section ( keys %innodb_sections ) {
441+ my $header = $innodb_section_headers{$section};
442+ die "Unknown section $section in $fulltext\n"
443+ unless $header;
444+ $innodb_data{'sections'}->{ $header }
445+ ->{'fulltext'} = $innodb_sections{$section}->[0];
446+ $innodb_data{'sections'}->{ $header }
447+ ->{'complete'} = $innodb_sections{$section}->[1];
448+ }
449+ };
450+ if ( $EVAL_ERROR ) {
451+ _debug( $debug, $EVAL_ERROR);
452+ }
453+
454+ # ################################################################
455+ # Parse the detailed data out of the sections.
456+ # ################################################################
457+ eval {
458+ foreach my $section ( keys %parser_for ) {
459+ if ( defined $innodb_data{'sections'}->{$section}
460+ && (!%$sections || (defined($sections->{$section} && $sections->{$section})) )) {
461+ $parser_for{$section}->(
462+ $innodb_data{'sections'}->{$section},
463+ $innodb_data{'sections'}->{$section}->{'complete'},
464+ $debug,
465+ $full )
466+ or delete $innodb_data{'sections'}->{$section};
467+ }
468+ else {
469+ delete $innodb_data{'sections'}->{$section};
470+ }
471+ }
472+ };
473+ if ( $EVAL_ERROR ) {
474+ _debug( $debug, $EVAL_ERROR);
475+ }
476+
477+ return \%innodb_data;
478+}
479+
480+# Parses the status text and returns it flattened out as a single hash.
481+sub get_status_hash {
482+ my ( $self, $fulltext, $debug, $sections, $full ) = @_;
483+
484+ # Parse the status text...
485+ my $innodb_status
486+ = $self->parse_status_text($fulltext, $debug, $sections, $full );
487+
488+ # Flatten the hierarchical structure into a single list by grabbing desired
489+ # sections from it.
490+ return
491+ (map { 'IB_' . $_ => $innodb_status->{$_} } qw(timestring last_secs got_all)),
492+ (map { 'IB_bp_' . $_ => $innodb_status->{'sections'}->{'bp'}->{$_} }
493+ qw( writes_pending buf_pool_hit_rate total_mem_alloc buf_pool_reads
494+ awe_mem_alloc pages_modified writes_pending_lru page_creates_sec
495+ reads_pending pages_total buf_pool_hits writes_pending_single_page
496+ page_writes_sec pages_read pages_written page_reads_sec
497+ writes_pending_flush_list buf_pool_size add_pool_alloc
498+ dict_mem_alloc pages_created buf_free complete )),
499+ (map { 'IB_tx_' . $_ => $innodb_status->{'sections'}->{'tx'}->{$_} }
500+ qw( num_lock_structs history_list_len purge_done_for transactions
501+ purge_undo_for is_truncated trx_id_counter complete )),
502+ (map { 'IB_ib_' . $_ => $innodb_status->{'sections'}->{'ib'}->{$_} }
503+ qw( hash_table_size hash_searches_s non_hash_searches_s
504+ bufs_in_node_heap used_cells size free_list_len seg_size inserts
505+ merged_recs merges complete )),
506+ (map { 'IB_lg_' . $_ => $innodb_status->{'sections'}->{'lg'}->{$_} }
507+ qw( log_ios_done pending_chkp_writes last_chkp log_ios_s
508+ log_flushed_to log_seq_no pending_log_writes complete )),
509+ (map { 'IB_sm_' . $_ => $innodb_status->{'sections'}->{'sm'}->{$_} }
510+ qw( wait_array_size rw_shared_spins rw_excl_os_waits mutex_os_waits
511+ mutex_spin_rounds mutex_spin_waits rw_excl_spins rw_shared_os_waits
512+ waits signal_count reservation_count complete )),
513+ (map { 'IB_ro_' . $_ => $innodb_status->{'sections'}->{'ro'}->{$_} }
514+ qw( queries_in_queue n_reserved_extents main_thread_state
515+ main_thread_proc_no main_thread_id read_sec del_sec upd_sec ins_sec
516+ read_views_open num_rows_upd num_rows_ins num_rows_read
517+ queries_inside num_rows_del complete )),
518+ (map { 'IB_fk_' . $_ => $innodb_status->{'sections'}->{'fk'}->{$_} }
519+ qw( trigger parent_table child_index parent_index attempted_op
520+ child_db timestring fk_name records col_name reason txn parent_db
521+ type child_table parent_col complete )),
522+ (map { 'IB_io_' . $_ => $innodb_status->{'sections'}->{'io'}->{$_} }
523+ qw( pending_buffer_pool_flushes pending_pwrites pending_preads
524+ pending_normal_aio_reads fsyncs_s os_file_writes pending_sync_ios
525+ reads_s flush_type avg_bytes_s pending_ibuf_aio_reads writes_s
526+ threads os_file_reads pending_aio_writes pending_log_ios os_fsyncs
527+ pending_log_flushes complete )),
528+ (map { 'IB_dl_' . $_ => $innodb_status->{'sections'}->{'dl'}->{$_} }
529+ qw( timestring rolled_back txns complete ));
530+
531+}
532+
533+sub ts_to_string {
534+ my $parts = shift;
535+ return sprintf('%02d-%02d-%02d %02d:%02d:%02d', @$parts);
536+}
537+
538+sub parse_innodb_timestamp {
539+ my $text = shift;
540+ my ( $y, $m, $d, $h, $i, $s )
541+ = $text =~ m/^(\d\d)(\d\d)(\d\d) +(\d+):(\d+):(\d+)$/;
542+ die("Can't get timestamp from $text\n") unless $y;
543+ $y += 2000;
544+ return ( $y, $m, $d, $h, $i, $s );
545+}
546+
547+sub parse_fk_section {
548+ my ( $section, $complete, $debug, $full ) = @_;
549+ my $fulltext = $section->{'fulltext'};
550+
551+ return 0 unless $fulltext;
552+
553+ my ( $ts, $type ) = $fulltext =~ m/^$s\s+(\w+)/m;
554+ $section->{'ts'} = [ parse_innodb_timestamp( $ts ) ];
555+ $section->{'timestring'} = ts_to_string($section->{'ts'});
556+ $section->{'type'} = $type;
557+
558+ # Decide which type of FK error happened, and dispatch to the right parser.
559+ if ( $type && $fk_parser_for{$type} ) {
560+ $fk_parser_for{$type}->( $section, $complete, $debug, $fulltext, $full );
561+ }
562+
563+ delete $section->{'fulltext'} unless $debug;
564+
565+ return 1;
566+}
567+
568+sub parse_fk_cant_drop_parent_error {
569+ my ( $section, $complete, $debug, $fulltext, $full ) = @_;
570+
571+ # Parse the parent/child table info out
572+ @{$section}{ qw(attempted_op parent_db parent_table) } = $fulltext
573+ =~ m{Cannot $w table `(.*)/(.*)`}m;
574+ @{$section}{ qw(child_db child_table) } = $fulltext
575+ =~ m{because it is referenced by `(.*)/(.*)`}m;
576+
577+ ( $section->{'reason'} ) = $fulltext =~ m/(Cannot .*)/s;
578+ $section->{'reason'} =~ s/\n(?:InnoDB: )?/ /gm
579+ if $section->{'reason'};
580+
581+ # Certain data may not be present. Make them '' if not present.
582+ map { $section->{$_} ||= "" }
583+ qw(child_index fk_name col_name parent_col);
584+}
585+
586+# See dict/dict0dict.c, function dict_foreign_error_report
587+# I don't care much about these. There are lots of different messages, and
588+# they come from someone trying to create a foreign key, or similar
589+# statements. They aren't indicative of some transaction trying to insert,
590+# delete or update data. Sometimes it is possible to parse out a lot of
591+# information about the tables and indexes involved, but often the message
592+# contains the DDL string the user entered, which is way too much for this
593+# module to try to handle.
594+sub parse_fk_bad_constraint_error {
595+ my ( $section, $complete, $debug, $fulltext, $full ) = @_;
596+
597+ # Parse the parent/child table and index info out
598+ @{$section}{ qw(child_db child_table) } = $fulltext
599+ =~ m{Error in foreign key constraint of table (.*)/(.*):$}m;
600+ $section->{'attempted_op'} = 'DDL';
601+
602+ # FK name, parent info... if possible.
603+ @{$section}{ qw(fk_name col_name parent_db parent_table parent_col) }
604+ = $fulltext
605+ =~ m/CONSTRAINT `?$n`? FOREIGN KEY \(`?$n`?\) REFERENCES (?:`?$n`?\.)?`?$n`? \(`?$n`?\)/;
606+
607+ if ( !defined($section->{'fk_name'}) ) {
608+ # Try to parse SQL a user might have typed in a CREATE statement or such
609+ @{$section}{ qw(col_name parent_db parent_table parent_col) }
610+ = $fulltext
611+ =~ m/FOREIGN\s+KEY\s*\(`?$n`?\)\s+REFERENCES\s+(?:`?$n`?\.)?`?$n`?\s*\(`?$n`?\)/i;
612+ }
613+ $section->{'parent_db'} ||= $section->{'child_db'};
614+
615+ # Name of the child index (index in the same table where the FK is, see
616+ # definition of dict_foreign_struct in include/dict0mem.h, where it is
617+ # called foreign_index, as opposed to referenced_index which is in the
618+ # parent table. This may not be possible to find.
619+ @{$section}{ qw(child_index) } = $fulltext
620+ =~ m/^The index in the foreign key in table is $n$/m;
621+
622+ @{$section}{ qw(reason) } = $fulltext =~ m/:\s*([^:]+)(?= Constraint:|$)/ms;
623+ $section->{'reason'} =~ s/\s+/ /g
624+ if $section->{'reason'};
625+
626+ # Certain data may not be present. Make them '' if not present.
627+ map { $section->{$_} ||= "" }
628+ qw(child_index fk_name col_name parent_table parent_col);
629+}
630+
631+# see source file row/row0ins.c
632+sub parse_fk_transaction_error {
633+ my ( $section, $complete, $debug, $fulltext, $full ) = @_;
634+
635+ # Parse the txn info out
636+ my ( $txn ) = $fulltext
637+ =~ m/Transaction:\n(TRANSACTION.*)\nForeign key constraint fails/s;
638+ if ( $txn ) {
639+ $section->{'txn'} = parse_tx_text( $txn, $complete, $debug, $full );
640+ }
641+
642+ # Parse the parent/child table and index info out. There are two types: an
643+ # update or a delete of a parent record leaves a child orphaned
644+ # (row_ins_foreign_report_err), and an insert or update of a child record has
645+ # no matching parent record (row_ins_foreign_report_add_err).
646+
647+ @{$section}{ qw(reason child_db child_table) }
648+ = $fulltext =~ m{^(Foreign key constraint fails for table `(.*)/(.*)`:)$}m;
649+
650+ @{$section}{ qw(fk_name col_name parent_db parent_table parent_col) }
651+ = $fulltext
652+ =~ m/CONSTRAINT `$n` FOREIGN KEY \(`$n`\) REFERENCES (?:`$n`\.)?`$n` \(`$n`\)/;
653+ $section->{'parent_db'} ||= $section->{'child_db'};
654+
655+ # Special case, which I don't know how to trigger, but see
656+ # innobase/row/row0ins.c row_ins_check_foreign_constraint
657+ if ( $fulltext =~ m/ibd file does not currently exist!/ ) {
658+ my ( $attempted_op, $index, $records )
659+ = $fulltext =~ m/^Trying to (add to index) `$n` tuple:\n(.*))?/sm;
660+ $section->{'child_index'} = $index;
661+ $section->{'attempted_op'} = $attempted_op || '';
662+ if ( $records && $full ) {
663+ ( $section->{'records'} )
664+ = parse_innodb_record_dump( $records, $complete, $debug );
665+ }
666+ @{$section}{qw(parent_db parent_table)}
667+ =~ m/^But the parent table `$n`\.`$n`$/m;
668+ }
669+ else {
670+ my ( $attempted_op, $which, $index )
671+ = $fulltext =~ m/^Trying to ([\w ]*) in (child|parent) table, in index `$n` tuple:$/m;
672+ if ( $which ) {
673+ $section->{$which . '_index'} = $index;
674+ $section->{'attempted_op'} = $attempted_op || '';
675+
676+ # Parse out the related records in the other table.
677+ my ( $search_index, $records );
678+ if ( $which eq 'child' ) {
679+ ( $search_index, $records ) = $fulltext
680+ =~ m/^But in parent table [^,]*, in index `$n`,\nthe closest match we can find is record:\n(.*)/ms;
681+ $section->{'parent_index'} = $search_index;
682+ }
683+ else {
684+ ( $search_index, $records ) = $fulltext
685+ =~ m/^But in child table [^,]*, in index `$n`, (?:the record is not available|there is a record:\n(.*))?/ms;
686+ $section->{'child_index'} = $search_index;
687+ }
688+ if ( $records && $full ) {
689+ $section->{'records'}
690+ = parse_innodb_record_dump( $records, $complete, $debug );
691+ }
692+ else {
693+ $section->{'records'} = '';
694+ }
695+ }
696+ }
697+
698+ # Parse out the tuple trying to be updated, deleted or inserted.
699+ my ( $trigger ) = $fulltext =~ m/^(DATA TUPLE: \d+ fields;\n.*)$/m;
700+ if ( $trigger ) {
701+ $section->{'trigger'} = parse_innodb_record_dump( $trigger, $complete, $debug );
702+ }
703+
704+ # Certain data may not be present. Make them '' if not present.
705+ map { $section->{$_} ||= "" }
706+ qw(child_index fk_name col_name parent_table parent_col);
707+}
708+
709+# There are new-style and old-style record formats. See rem/rem0rec.c
710+# TODO: write some tests for this
711+sub parse_innodb_record_dump {
712+ my ( $dump, $complete, $debug ) = @_;
713+ return undef unless $dump;
714+
715+ my $result = {};
716+
717+ if ( $dump =~ m/PHYSICAL RECORD/ ) {
718+ my $style = $dump =~ m/compact format/ ? 'new' : 'old';
719+ $result->{'style'} = $style;
720+
721+ # This is a new-style record.
722+ if ( $style eq 'new' ) {
723+ @{$result}{qw( heap_no type num_fields info_bits )}
724+ = $dump
725+ =~ m/^(?:Record lock, heap no $d )?([A-Z ]+): n_fields $d; compact format; info bits $d$/m;
726+ }
727+
728+ # OK, it's old-style. Unfortunately there are variations here too.
729+ elsif ( $dump =~ m/-byte offs / ) {
730+ # Older-old style.
731+ @{$result}{qw( heap_no type num_fields byte_offset info_bits )}
732+ = $dump
733+ =~ m/^(?:Record lock, heap no $d )?([A-Z ]+): n_fields $d; $d-byte offs [A-Z]+; info bits $d$/m;
734+ if ( $dump !~ m/-byte offs TRUE/ ) {
735+ $result->{'byte_offset'} = 0;
736+ }
737+ }
738+ else {
739+ # Newer-old style.
740+ @{$result}{qw( heap_no type num_fields byte_offset info_bits )}
741+ = $dump
742+ =~ m/^(?:Record lock, heap no $d )?([A-Z ]+): n_fields $d; $d-byte offsets; info bits $d$/m;
743+ }
744+
745+ }
746+ else {
747+ $result->{'style'} = 'tuple';
748+ @{$result}{qw( type num_fields )}
749+ = $dump =~ m/^(DATA TUPLE): $d fields;$/m;
750+ }
751+
752+ # Fill in default values for things that couldn't be parsed.
753+ map { $result->{$_} ||= 0 }
754+ qw(heap_no num_fields byte_offset info_bits);
755+ map { $result->{$_} ||= '' }
756+ qw(style type );
757+
758+ my @fields = $dump =~ m/ (\d+:.*?;?);(?=$| \d+:)/gm;
759+ $result->{'fields'} = [ map { parse_field($_, $complete, $debug ) } @fields ];
760+
761+ return $result;
762+}
763+
764+# New/old-style applies here. See rem/rem0rec.c
765+# $text should not include the leading space or the second trailing semicolon.
766+sub parse_field {
767+ my ( $text, $complete, $debug ) = @_;
768+
769+ # Sample fields:
770+ # '4: SQL NULL, size 4 '
771+ # '1: len 6; hex 000000005601; asc V ;'
772+ # '6: SQL NULL'
773+ # '5: len 30; hex 687474703a2f2f7777772e737765657477617465722e636f6d2f73746f72; asc http://www.sweetwater.com/stor;...(truncated)'
774+ my ( $id, $nullsize, $len, $hex, $asc, $truncated );
775+ ( $id, $nullsize ) = $text =~ m/^$d: SQL NULL, size $d $/;
776+ if ( !defined($id) ) {
777+ ( $id ) = $text =~ m/^$d: SQL NULL$/;
778+ }
779+ if ( !defined($id) ) {
780+ ( $id, $len, $hex, $asc, $truncated )
781+ = $text =~ m/^$d: len $d; hex $h; asc (.*);(\.\.\.\(truncated\))?$/;
782+ }
783+
784+ die "Could not parse this field: '$text'" unless defined $id;
785+ return {
786+ id => $id,
787+ len => defined($len) ? $len : defined($nullsize) ? $nullsize : 0,
788+ 'hex' => defined($hex) ? $hex : '',
789+ asc => defined($asc) ? $asc : '',
790+ trunc => $truncated ? 1 : 0,
791+ };
792+
793+}
794+
795+sub parse_dl_section {
796+ my ( $dl, $complete, $debug, $full ) = @_;
797+ return unless $dl;
798+ my $fulltext = $dl->{'fulltext'};
799+ return 0 unless $fulltext;
800+
801+ my ( $ts ) = $fulltext =~ m/^$s$/m;
802+ return 0 unless $ts;
803+
804+ $dl->{'ts'} = [ parse_innodb_timestamp( $ts ) ];
805+ $dl->{'timestring'} = ts_to_string($dl->{'ts'});
806+ $dl->{'txns'} = {};
807+
808+ my @sections
809+ = $fulltext
810+ =~ m{
811+ ^\*{3}\s([^\n]*) # *** (1) WAITING FOR THIS...
812+ (.*?) # Followed by anything, non-greedy
813+ (?=(?:^\*{3})|\z) # Followed by another three stars or EOF
814+ }gmsx;
815+
816+
817+ # Loop through each section. There are no assumptions about how many
818+ # there are, who holds and wants what locks, and who gets rolled back.
819+ while ( my ($header, $body) = splice(@sections, 0, 2) ) {
820+ my ( $txn_id, $what ) = $header =~ m/^\($d\) (.*):$/;
821+ next unless $txn_id;
822+ $dl->{'txns'}->{$txn_id} ||= {};
823+ my $txn = $dl->{'txns'}->{$txn_id};
824+
825+ if ( $what eq 'TRANSACTION' ) {
826+ $txn->{'tx'} = parse_tx_text( $body, $complete, $debug, $full );
827+ }
828+ else {
829+ push @{$txn->{'locks'}}, parse_innodb_record_locks( $body, $complete, $debug, $full );
830+ }
831+ }
832+
833+ @{ $dl }{ qw(rolled_back) }
834+ = $fulltext =~ m/^\*\*\* WE ROLL BACK TRANSACTION \($d\)$/m;
835+
836+ # Make sure certain values aren't undef
837+ map { $dl->{$_} ||= '' } qw(rolled_back);
838+
839+ delete $dl->{'fulltext'} unless $debug;
840+ return 1;
841+}
842+
843+sub parse_innodb_record_locks {
844+ my ( $text, $complete, $debug, $full ) = @_;
845+ my @result;
846+
847+ foreach my $lock ( $text =~ m/(^(?:RECORD|TABLE) LOCKS?.*$)/gm ) {
848+ my $hash = {};
849+ @{$hash}{ qw(lock_type space_id page_no n_bits index db table txn_id lock_mode) }
850+ = $lock
851+ =~ m{^(RECORD|TABLE) LOCKS? (?:space id $d page no $d n bits $d index `?$n`? of )?table `$n(?:/|`\.`)$n` trx id $t lock.mode (\S+)}m;
852+ ( $hash->{'special'} )
853+ = $lock =~ m/^(?:RECORD|TABLE) .*? locks (rec but not gap|gap before rec)/m;
854+ $hash->{'insert_intention'}
855+ = $lock =~ m/^(?:RECORD|TABLE) .*? insert intention/m ? 1 : 0;
856+ $hash->{'waiting'}
857+ = $lock =~ m/^(?:RECORD|TABLE) .*? waiting/m ? 1 : 0;
858+
859+ # Some things may not be in the text, so make sure they are not
860+ # undef.
861+ map { $hash->{$_} ||= 0 } qw(n_bits page_no space_id);
862+ map { $hash->{$_} ||= "" } qw(index special);
863+ push @result, $hash;
864+ }
865+
866+ return @result;
867+}
868+
869+sub parse_tx_text {
870+ my ( $txn, $complete, $debug, $full ) = @_;
871+
872+ my ( $txn_id, $txn_status, $active_secs, $proc_no, $os_thread_id )
873+ = $txn
874+ =~ m/^(?:---)?TRANSACTION $t, (\D*?)(?: $d sec)?, (?:process no $d, )?OS thread id $d/m;
875+ my ( $thread_status, $thread_decl_inside )
876+ = $txn
877+ =~ m/OS thread id \d+(?: ([^,]+?))?(?:, thread declared inside InnoDB $d)?$/m;
878+
879+ # Parsing the line that begins 'MySQL thread id' is complicated. The only
880+ # thing always in the line is the thread and query id. See function
881+ # innobase_mysql_print_thd in InnoDB source file sql/ha_innodb.cc.
882+ my ( $thread_line ) = $txn =~ m/^(MySQL thread id .*)$/m;
883+ my ( $mysql_thread_id, $query_id, $hostname, $ip, $user, $query_status );
884+
885+ if ( $thread_line ) {
886+ # These parts can always be gotten.
887+ ( $mysql_thread_id, $query_id ) = $thread_line =~ m/^MySQL thread id $d, query id $d/m;
888+
889+ # If it's a master/slave thread, "Has (read|sent) all" may be the thread's
890+ # proc_info. In these cases, there won't be any host/ip/user info
891+ ( $query_status ) = $thread_line =~ m/(Has (?:read|sent) all .*$)/m;
892+ if ( defined($query_status) ) {
893+ $user = 'system user';
894+ }
895+
896+ # It may be the case that the query id is the last thing in the line.
897+ elsif ( $thread_line =~ m/query id \d+ / ) {
898+ # The IP address is the only non-word thing left, so it's the most
899+ # useful marker for where I have to start guessing.
900+ ( $hostname, $ip ) = $thread_line =~ m/query id \d+(?: ([A-Za-z]\S+))? $i/m;
901+ if ( defined $ip ) {
902+ ( $user, $query_status ) = $thread_line =~ m/$ip $w(?: (.*))?$/;
903+ }
904+ else { # OK, there wasn't an IP address.
905+ # There might not be ANYTHING except the query status.
906+ ( $query_status ) = $thread_line =~ m/query id \d+ (.*)$/;
907+ if ( $query_status !~ m/^\w+ing/ && !exists($is_proc_info{$query_status}) ) {
908+ # The remaining tokens are, in order: hostname, user, query_status.
909+ # It's basically impossible to know which is which.
910+ ( $hostname, $user, $query_status ) = $thread_line
911+ =~ m/query id \d+(?: ([A-Za-z]\S+))?(?: $w(?: (.*))?)?$/m;
912+ }
913+ else {
914+ $user = 'system user';
915+ }
916+ }
917+ }
918+ }
919+
920+ my ( $lock_wait_status, $lock_structs, $heap_size, $row_locks, $undo_log_entries )
921+ = $txn
922+ =~ m/^(?:(\D*) )?$d lock struct\(s\), heap size $d(?:, $d row lock\(s\))?(?:, undo log entries $d)?$/m;
923+ my ( $lock_wait_time )
924+ = $txn
925+ =~ m/^------- TRX HAS BEEN WAITING $d SEC/m;
926+
927+ my $locks;
928+ # If the transaction has locks, grab the locks.
929+ if ( $txn =~ m/^TABLE LOCK|RECORD LOCKS/ ) {
930+ $locks = [parse_innodb_record_locks($txn, $complete, $debug, $full)];
931+ }
932+
933+ my ( $tables_in_use, $tables_locked )
934+ = $txn
935+ =~ m/^mysql tables in use $d, locked $d$/m;
936+ my ( $txn_doesnt_see_ge, $txn_sees_lt )
937+ = $txn
938+ =~ m/^Trx read view will not see trx with id >= $t, sees < $t$/m;
939+ my $has_read_view = defined($txn_doesnt_see_ge);
940+ # Only a certain number of bytes of the query text are included here, at least
941+ # under some circumstances. Some versions include 300, some 600.
942+ my ( $query_text )
943+ = $txn
944+ =~ m{
945+ ^MySQL\sthread\sid\s[^\n]+\n # This comes before the query text
946+ (.*?) # The query text
947+ (?= # Followed by any of...
948+ ^Trx\sread\sview
949+ |^-------\sTRX\sHAS\sBEEN\sWAITING
950+ |^TABLE\sLOCK
951+ |^RECORD\sLOCKS\sspace\sid
952+ |^(?:---)?TRANSACTION
953+ |^\*\*\*\s\(\d\)
954+ |\Z
955+ )
956+ }xms;
957+ if ( $query_text ) {
958+ $query_text =~ s/\s+$//;
959+ }
960+ else {
961+ $query_text = '';
962+ }
963+
964+ my %stuff = (
965+ active_secs => $active_secs,
966+ has_read_view => $has_read_view,
967+ heap_size => $heap_size,
968+ hostname => $hostname,
969+ ip => $ip,
970+ lock_structs => $lock_structs,
971+ lock_wait_status => $lock_wait_status,
972+ lock_wait_time => $lock_wait_time,
973+ mysql_thread_id => $mysql_thread_id,
974+ os_thread_id => $os_thread_id,
975+ proc_no => $proc_no,
976+ query_id => $query_id,
977+ query_status => $query_status,
978+ query_text => $query_text,
979+ row_locks => $row_locks,
980+ tables_in_use => $tables_in_use,
981+ tables_locked => $tables_locked,
982+ thread_decl_inside => $thread_decl_inside,
983+ thread_status => $thread_status,
984+ txn_doesnt_see_ge => $txn_doesnt_see_ge,
985+ txn_id => $txn_id,
986+ txn_sees_lt => $txn_sees_lt,
987+ txn_status => $txn_status,
988+ undo_log_entries => $undo_log_entries,
989+ user => $user,
990+ );
991+ $stuff{'fulltext'} = $txn if $debug;
992+ $stuff{'locks'} = $locks if $locks;
993+
994+ # Some things may not be in the txn text, so make sure they are not
995+ # undef.
996+ map { $stuff{$_} ||= 0 } qw(active_secs heap_size lock_structs
997+ tables_in_use undo_log_entries tables_locked has_read_view
998+ thread_decl_inside lock_wait_time proc_no row_locks);
999+ map { $stuff{$_} ||= "" } qw(thread_status txn_doesnt_see_ge
1000+ txn_sees_lt query_status ip query_text lock_wait_status user);
1001+ $stuff{'hostname'} ||= $stuff{'ip'};
1002+
1003+ return \%stuff;
1004+}
1005+
1006+sub parse_tx_section {
1007+ my ( $section, $complete, $debug, $full ) = @_;
1008+ return unless $section && $section->{'fulltext'};
1009+ my $fulltext = $section->{'fulltext'};
1010+ $section->{'transactions'} = [];
1011+
1012+ # Handle the individual transactions
1013+ my @transactions = $fulltext =~ m/(---TRANSACTION \d.*?)(?=\n---TRANSACTION|$)/gs;
1014+ foreach my $txn ( @transactions ) {
1015+ my $stuff = parse_tx_text( $txn, $complete, $debug, $full );
1016+ delete $stuff->{'fulltext'} unless $debug;
1017+ push @{$section->{'transactions'}}, $stuff;
1018+ }
1019+
1020+ # Handle the general info
1021+ @{$section}{ 'trx_id_counter' }
1022+ = $fulltext =~ m/^Trx id counter $t$/m;
1023+ @{$section}{ 'purge_done_for', 'purge_undo_for' }
1024+ = $fulltext =~ m/^Purge done for trx's n:o < $t undo n:o < $t$/m;
1025+ @{$section}{ 'history_list_len' } # This isn't present in some 4.x versions
1026+ = $fulltext =~ m/^History list length $d$/m;
1027+ @{$section}{ 'num_lock_structs' }
1028+ = $fulltext =~ m/^Total number of lock structs in row lock hash table $d$/m;
1029+ @{$section}{ 'is_truncated' }
1030+ = $fulltext =~ m/^\.\.\. truncated\.\.\.$/m ? 1 : 0;
1031+
1032+ # Fill in things that might not be present
1033+ foreach ( qw(history_list_len) ) {
1034+ $section->{$_} ||= 0;
1035+ }
1036+
1037+ delete $section->{'fulltext'} unless $debug;
1038+ return 1;
1039+}
1040+
1041+# I've read the source for this section.
1042+sub parse_ro_section {
1043+ my ( $section, $complete, $debug, $full ) = @_;
1044+ return unless $section && $section->{'fulltext'};
1045+ my $fulltext = $section->{'fulltext'};
1046+
1047+ # Grab the info
1048+ @{$section}{ 'queries_inside', 'queries_in_queue' }
1049+ = $fulltext =~ m/^$d queries inside InnoDB, $d queries in queue$/m;
1050+ ( $section->{ 'read_views_open' } )
1051+ = $fulltext =~ m/^$d read views open inside InnoDB$/m;
1052+ ( $section->{ 'n_reserved_extents' } )
1053+ = $fulltext =~ m/^$d tablespace extents now reserved for B-tree/m;
1054+ @{$section}{ 'main_thread_proc_no', 'main_thread_id', 'main_thread_state' }
1055+ = $fulltext =~ m/^Main thread (?:process no. $d, )?id $d, state: (.*)$/m;
1056+ @{$section}{ 'num_rows_ins', 'num_rows_upd', 'num_rows_del', 'num_rows_read' }
1057+ = $fulltext =~ m/^Number of rows inserted $d, updated $d, deleted $d, read $d$/m;
1058+ @{$section}{ 'ins_sec', 'upd_sec', 'del_sec', 'read_sec' }
1059+ = $fulltext =~ m#^$f inserts/s, $f updates/s, $f deletes/s, $f reads/s$#m;
1060+ $section->{'main_thread_proc_no'} ||= 0;
1061+
1062+ map { $section->{$_} ||= 0 } qw(read_views_open n_reserved_extents);
1063+ delete $section->{'fulltext'} unless $debug;
1064+ return 1;
1065+}
1066+
1067+sub parse_lg_section {
1068+ my ( $section, $complete, $debug, $full ) = @_;
1069+ return unless $section;
1070+ my $fulltext = $section->{'fulltext'};
1071+
1072+ # Grab the info
1073+ ( $section->{ 'log_seq_no' } )
1074+ = $fulltext =~ m/Log sequence number \s*(\d.*)$/m;
1075+ ( $section->{ 'log_flushed_to' } )
1076+ = $fulltext =~ m/Log flushed up to \s*(\d.*)$/m;
1077+ ( $section->{ 'last_chkp' } )
1078+ = $fulltext =~ m/Last checkpoint at \s*(\d.*)$/m;
1079+ @{$section}{ 'pending_log_writes', 'pending_chkp_writes' }
1080+ = $fulltext =~ m/$d pending log writes, $d pending chkp writes/;
1081+ @{$section}{ 'log_ios_done', 'log_ios_s' }
1082+ = $fulltext =~ m#$d log i/o's done, $f log i/o's/second#;
1083+
1084+ delete $section->{'fulltext'} unless $debug;
1085+ return 1;
1086+}
1087+
1088+sub parse_ib_section {
1089+ my ( $section, $complete, $debug, $full ) = @_;
1090+ return unless $section && $section->{'fulltext'};
1091+ my $fulltext = $section->{'fulltext'};
1092+
1093+ # Some servers will output ibuf information for tablespace 0, as though there
1094+ # might be many tablespaces with insert buffers. (In practice I believe
1095+ # the source code shows there will only ever be one). I have to parse both
1096+ # cases here, but I assume there will only be one.
1097+ @{$section}{ 'size', 'free_list_len', 'seg_size' }
1098+ = $fulltext =~ m/^Ibuf(?: for space 0)?: size $d, free list len $d, seg size $d,$/m;
1099+ @{$section}{ 'inserts', 'merged_recs', 'merges' }
1100+ = $fulltext =~ m/^$d inserts, $d merged recs, $d merges$/m;
1101+
1102+ @{$section}{ 'hash_table_size', 'used_cells', 'bufs_in_node_heap' }
1103+ = $fulltext =~ m/^Hash table size $d, used cells $d, node heap has $d buffer\(s\)$/m;
1104+ @{$section}{ 'hash_searches_s', 'non_hash_searches_s' }
1105+ = $fulltext =~ m{^$f hash searches/s, $f non-hash searches/s$}m;
1106+
1107+ delete $section->{'fulltext'} unless $debug;
1108+ return 1;
1109+}
1110+
1111+sub parse_wait_array {
1112+ my ( $text, $complete, $debug, $full ) = @_;
1113+ my %result;
1114+
1115+ @result{ qw(thread waited_at_filename waited_at_line waited_secs) }
1116+ = $text =~ m/^--Thread $d has waited at $fl for $f seconds/m;
1117+
1118+ # Depending on whether it's a SYNC_MUTEX,RW_LOCK_EX,RW_LOCK_SHARED,
1119+ # there will be different text output
1120+ if ( $text =~ m/^Mutex at/m ) {
1121+ $result{'request_type'} = 'M';
1122+ @result{ qw( lock_mem_addr lock_cfile_name lock_cline lock_var) }
1123+ = $text =~ m/^Mutex at $h created file $fl, lock var $d$/m;
1124+ @result{ qw( waiters_flag )}
1125+ = $text =~ m/^waiters flag $d$/m;
1126+ }
1127+ else {
1128+ @result{ qw( request_type lock_mem_addr lock_cfile_name lock_cline) }
1129+ = $text =~ m/^(.)-lock on RW-latch at $h created in file $fl$/m;
1130+ @result{ qw( writer_thread writer_lock_mode ) }
1131+ = $text =~ m/^a writer \(thread id $d\) has reserved it in mode (.*)$/m;
1132+ @result{ qw( num_readers waiters_flag )}
1133+ = $text =~ m/^number of readers $d, waiters flag $d$/m;
1134+ @result{ qw(last_s_file_name last_s_line ) }
1135+ = $text =~ m/Last time read locked in file $fl$/m;
1136+ @result{ qw(last_x_file_name last_x_line ) }
1137+ = $text =~ m/Last time write locked in file $fl$/m;
1138+ }
1139+
1140+ $result{'cell_waiting'} = $text =~ m/^wait has ended$/m ? 0 : 1;
1141+ $result{'cell_event_set'} = $text =~ m/^wait is ending$/m ? 1 : 0;
1142+
1143+ # Because there are two code paths, some things won't get set.
1144+ map { $result{$_} ||= '' }
1145+ qw(last_s_file_name last_x_file_name writer_lock_mode);
1146+ map { $result{$_} ||= 0 }
1147+ qw(num_readers lock_var last_s_line last_x_line writer_thread);
1148+
1149+ return \%result;
1150+}
1151+
1152+sub parse_sm_section {
1153+ my ( $section, $complete, $debug, $full ) = @_;
1154+ return 0 unless $section && $section->{'fulltext'};
1155+ my $fulltext = $section->{'fulltext'};
1156+
1157+ # Grab the info
1158+ @{$section}{ 'reservation_count', 'signal_count' }
1159+ = $fulltext =~ m/^OS WAIT ARRAY INFO: reservation count $d, signal count $d$/m;
1160+ @{$section}{ 'mutex_spin_waits', 'mutex_spin_rounds', 'mutex_os_waits' }
1161+ = $fulltext =~ m/^Mutex spin waits $d, rounds $d, OS waits $d$/m;
1162+ @{$section}{ 'rw_shared_spins', 'rw_shared_os_waits', 'rw_excl_spins', 'rw_excl_os_waits' }
1163+ = $fulltext =~ m/^RW-shared spins $d, OS waits $d; RW-excl spins $d, OS waits $d$/m;
1164+
1165+ # Look for info on waits.
1166+ my @waits = $fulltext =~ m/^(--Thread.*?)^(?=Mutex spin|--Thread)/gms;
1167+ $section->{'waits'} = [ map { parse_wait_array($_, $complete, $debug) } @waits ];
1168+ $section->{'wait_array_size'} = scalar(@waits);
1169+
1170+ delete $section->{'fulltext'} unless $debug;
1171+ return 1;
1172+}
1173+
1174+# I've read the source for this section.
1175+sub parse_bp_section {
1176+ my ( $section, $complete, $debug, $full ) = @_;
1177+ return unless $section && $section->{'fulltext'};
1178+ my $fulltext = $section->{'fulltext'};
1179+
1180+ # Grab the info
1181+ @{$section}{ 'total_mem_alloc', 'add_pool_alloc' }
1182+ = $fulltext =~ m/^Total memory allocated $d; in additional pool allocated $d$/m;
1183+ @{$section}{'dict_mem_alloc'} = $fulltext =~ m/Dictionary memory allocated $d/;
1184+ @{$section}{'awe_mem_alloc'} = $fulltext =~ m/$d MB of AWE memory/;
1185+ @{$section}{'buf_pool_size'} = $fulltext =~ m/^Buffer pool size\s*$d$/m;
1186+ @{$section}{'buf_free'} = $fulltext =~ m/^Free buffers\s*$d$/m;
1187+ @{$section}{'pages_total'} = $fulltext =~ m/^Database pages\s*$d$/m;
1188+ @{$section}{'pages_modified'} = $fulltext =~ m/^Modified db pages\s*$d$/m;
1189+ @{$section}{'pages_read', 'pages_created', 'pages_written'}
1190+ = $fulltext =~ m/^Pages read $d, created $d, written $d$/m;
1191+ @{$section}{'page_reads_sec', 'page_creates_sec', 'page_writes_sec'}
1192+ = $fulltext =~ m{^$f reads/s, $f creates/s, $f writes/s$}m;
1193+ @{$section}{'buf_pool_hits', 'buf_pool_reads'}
1194+ = $fulltext =~ m{Buffer pool hit rate $d / $d$}m;
1195+ if ($fulltext =~ m/^No buffer pool page gets since the last printout$/m) {
1196+ @{$section}{'buf_pool_hits', 'buf_pool_reads'} = (0, 0);
1197+ @{$section}{'buf_pool_hit_rate'} = '--';
1198+ }
1199+ else {
1200+ @{$section}{'buf_pool_hit_rate'}
1201+ = $fulltext =~ m{Buffer pool hit rate (\d+ / \d+)$}m;
1202+ }
1203+ @{$section}{'reads_pending'} = $fulltext =~ m/^Pending reads $d/m;
1204+ @{$section}{'writes_pending_lru', 'writes_pending_flush_list', 'writes_pending_single_page' }
1205+ = $fulltext =~ m/^Pending writes: LRU $d, flush list $d, single page $d$/m;
1206+
1207+ map { $section->{$_} ||= 0 }
1208+ qw(writes_pending_lru writes_pending_flush_list writes_pending_single_page
1209+ awe_mem_alloc dict_mem_alloc);
1210+ @{$section}{'writes_pending'} = List::Util::sum(
1211+ @{$section}{ qw(writes_pending_lru writes_pending_flush_list writes_pending_single_page) });
1212+
1213+ delete $section->{'fulltext'} unless $debug;
1214+ return 1;
1215+}
1216+
1217+# I've read the source for this.
1218+sub parse_io_section {
1219+ my ( $section, $complete, $debug, $full ) = @_;
1220+ return unless $section && $section->{'fulltext'};
1221+ my $fulltext = $section->{'fulltext'};
1222+ $section->{'threads'} = {};
1223+
1224+ # Grab the I/O thread info
1225+ my @threads = $fulltext =~ m<^(I/O thread \d+ .*)$>gm;
1226+ foreach my $thread (@threads) {
1227+ my ( $tid, $state, $purpose, $event_set )
1228+ = $thread =~ m{I/O thread $d state: (.+?) \((.*)\)(?: ev set)?$}m;
1229+ if ( defined $tid ) {
1230+ $section->{'threads'}->{$tid} = {
1231+ thread => $tid,
1232+ state => $state,
1233+ purpose => $purpose,
1234+ event_set => $event_set ? 1 : 0,
1235+ };
1236+ }
1237+ }
1238+
1239+ # Grab the reads/writes/flushes info
1240+ @{$section}{ 'pending_normal_aio_reads', 'pending_aio_writes' }
1241+ = $fulltext =~ m/^Pending normal aio reads: $d, aio writes: $d,$/m;
1242+ @{$section}{ 'pending_ibuf_aio_reads', 'pending_log_ios', 'pending_sync_ios' }
1243+ = $fulltext =~ m{^ ibuf aio reads: $d, log i/o's: $d, sync i/o's: $d$}m;
1244+ @{$section}{ 'flush_type', 'pending_log_flushes', 'pending_buffer_pool_flushes' }
1245+ = $fulltext =~ m/^Pending flushes \($w\) log: $d; buffer pool: $d$/m;
1246+ @{$section}{ 'os_file_reads', 'os_file_writes', 'os_fsyncs' }
1247+ = $fulltext =~ m/^$d OS file reads, $d OS file writes, $d OS fsyncs$/m;
1248+ @{$section}{ 'reads_s', 'avg_bytes_s', 'writes_s', 'fsyncs_s' }
1249+ = $fulltext =~ m{^$f reads/s, $d avg bytes/read, $f writes/s, $f fsyncs/s$}m;
1250+ @{$section}{ 'pending_preads', 'pending_pwrites' }
1251+ = $fulltext =~ m/$d pending preads, $d pending pwrites$/m;
1252+ @{$section}{ 'pending_preads', 'pending_pwrites' } = (0, 0)
1253+ unless defined($section->{'pending_preads'});
1254+
1255+ delete $section->{'fulltext'} unless $debug;
1256+ return 1;
1257+}
1258+
1259+sub _debug {
1260+ my ( $debug, $msg ) = @_;
1261+ if ( $debug ) {
1262+ die $msg;
1263+ }
1264+ else {
1265+ warn $msg;
1266+ }
1267+ return 1;
1268+}
1269+
1270+1;
1271+
1272+# end_of_package
1273+# ############################################################################
1274+# Perldoc section. I put this last as per the Dog book.
1275+# ############################################################################
1276+=pod
1277+
1278+=head1 NAME
1279+
1280+InnoDBParser - Parse InnoDB monitor text.
1281+
1282+=head1 DESCRIPTION
1283+
1284+InnoDBParser tries to parse the output of the InnoDB monitor. One way to get
1285+this output is to connect to a MySQL server and issue the command SHOW ENGINE
1286+INNODB STATUS (omit 'ENGINE' on earlier versions of MySQL). The goal is to
1287+turn text into data that something else (e.g. innotop) can use.
1288+
1289+The output comes from all over, but the place to start in the source is
1290+innobase/srv/srv0srv.c.
1291+
1292+=head1 SYNOPSIS
1293+
1294+ use InnoDBParser;
1295+ use DBI;
1296+
1297+ # Get the status text.
1298+ my $dbh = DBI->connect(
1299+ "DBI::mysql:test;host=localhost",
1300+ 'user',
1301+ 'password'
1302+ );
1303+ my $query = 'SHOW /*!5 ENGINE */ INNODB STATUS';
1304+ my $text = $dbh->selectcol_arrayref($query)->[0];
1305+
1306+ # 1 or 0
1307+ my $debug = 1;
1308+
1309+ # Choose sections of the monitor text you want. Possible values:
1310+ # TRANSACTIONS => tx
1311+ # BUFFER POOL AND MEMORY => bp
1312+ # SEMAPHORES => sm
1313+ # LOG => lg
1314+ # ROW OPERATIONS => ro
1315+ # INSERT BUFFER AND ADAPTIVE HASH INDEX => ib
1316+ # FILE I/O => io
1317+ # LATEST DETECTED DEADLOCK => dl
1318+ # LATEST FOREIGN KEY ERROR => fk
1319+
1320+ my $required_sections = {
1321+ tx => 1,
1322+ };
1323+
1324+ # Parse the status text.
1325+ my $parser = InnoDBParser->new;
1326+ $innodb_status = $parser->parse_status_text(
1327+ $text,
1328+ $debug,
1329+ # Omit the following parameter to get all sections.
1330+ $required_sections,
1331+ );
1332+
1333+=head1 COPYRIGHT, LICENSE AND WARRANTY
1334+
1335+This package is copyright (c) 2006 Baron Schwartz, baron at xaprb dot com.
1336+Feedback and improvements are gratefully received.
1337+
1338+THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
1339+WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
1340+MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
1341+
1342+This program is free software; you can redistribute it and/or modify it under
1343+the terms of the GNU General Public License as published by the Free Software
1344+Foundation, version 2; OR the Perl Artistic License. On UNIX and similar
1345+systems, you can issue `man perlgpl' or `man perlartistic' to read these
1346+licenses.
1347+
1348+You should have received a copy of the GNU General Public License along with
1349+this program; if not, write to the Free Software Foundation, Inc., 59 Temple
1350+Place, Suite 330, Boston, MA 02111-1307 USA
1351+
1352+=head1 AUTHOR
1353+
1354+Baron Schwartz, baron at xaprb dot com.
1355+
1356+=head1 BUGS
1357+
1358+None known, but I bet there are some. The InnoDB monitor text wasn't really
1359+designed to be parsable.
1360+
1361+=head1 SEE ALSO
1362+
1363+innotop - a program that can format the parsed status information for humans
1364+to read and enjoy.
1365+
1366+=cut
1367
1368=== added file 'build/debian/additions/innotop/changelog.innotop'
1369--- build/debian/additions/innotop/changelog.innotop 1970-01-01 00:00:00 +0000
1370+++ build/debian/additions/innotop/changelog.innotop 2010-01-24 19:18:15 +0000
1371@@ -0,0 +1,318 @@
1372+Changelog for innotop and InnoDBParser:
1373+
1374+2007-11-09: version 1.6.0
1375+
1376+ * S mode crashed on non-numeric values.
1377+ * New user-defined columns crashed upon restart.
1378+ * Added --color option to control terminal coloring.
1379+
1380+2007-09-18: version 1.5.2
1381+
1382+ * Added the ability to monitor InnoDB status from a file.
1383+ * Changed W mode to L mode; it monitors all locks, not just lock waits.
1384+
1385+2007-09-16: version 1.5.1
1386+
1387+ * Added C (Command Summary) mode.
1388+ * Fixed a bug in the 'avg' aggregate function.
1389+
1390+2007-09-10: version 1.5.0
1391+
1392+ Changes:
1393+ * Added plugin functionality.
1394+ * Added group-by functionality.
1395+ * Moved the configuration file to a directory.
1396+ * Enhanced filtering and sorting on pivoted tables.
1397+ * Many small bug fixes.
1398+
1399+2007-07-16: version 1.4.3
1400+
1401+ Changes:
1402+ * Added standard --version command-line option
1403+ * Changed colors to cyan instead of blue; more visible on dark terminals.
1404+ * Added information to the filter-choosing dialog.
1405+ * Added column auto-completion when entering a filter expression.
1406+ * Changed Term::ReadKey from optional to mandatory.
1407+ * Clarified username in password prompting.
1408+ * Ten thousand words of documentation!
1409+
1410+ Bugs fixed:
1411+ * innotop crashed in W mode when InnoDB status data was truncated.
1412+ * innotop didn't display errors in tables if debug was enabled.
1413+ * The colored() subroutine wasn't being created in non-interactive mode.
1414+ * Don't prompt to save password except the first time.
1415+
1416+2007-05-03: version 1.4.2
1417+
1418+ This version contains all changes to the trunk until revision 239; some
1419+ changes in revisions 240:250 are included.
1420+
1421+ MAJOR CHANGES:
1422+
1423+ * Quick-filters to easily filter any column in any display
1424+ * Compatibility with MySQL 3.23 through 6.0
1425+ * Improved error handling when a server is down, permissions denied, etc
1426+ * Use additional SHOW INNODB STATUS information in 5.1.x
1427+ * Make all modes use tables consistently, so they can all be edited,
1428+ filtered, colored and sorted consistently
1429+ * Combine V, G and S modes into S mode, with v, g, and s hot-keys
1430+ * Let DBD driver read MySQL option files; permit connections without
1431+ user/pass/etc
1432+ * Compile SQL-like expressions into Perl subroutines; eliminate need to
1433+ know Perl
1434+ * Do not save all config data to config file, only save user's customizations
1435+ * Rewritten and improved command-line option handling
1436+ * Added --count, --delay, and other command-line options to support
1437+ run-and-exit operation
1438+ * Improve built-in variable sets
1439+ * Improve help screen with three-part balanced-column layout
1440+ * Simplify table-editor and improve hotkey support
1441+ * Require Perl to have high-resolution time support (Time::HiRes)
1442+ * Help the user choose a query to analyze or kill
1443+ * Enable EXPLAIN, show-full-query in T mode just like Q mode
1444+ * Let data-extraction access current, previous and incremental data sets
1445+ all at once
1446+
1447+ MINOR CHANGES:
1448+
1449+ * Column stabilizing for Q mode
1450+ * New color rules for T, Q, W modes
1451+ * Apply slave I/O filter to Q mode
1452+ * Improve detection of server version and other meta-data
1453+ * Make connection timeout a config variable
1454+ * Improve cross-version-compatible SQL syntax
1455+ * Get some information from the DBD driver instead of asking MySQL for it
1456+ * Improved error messages
1457+ * Improve server group creation/editing
1458+ * Improve connection/thread killing
1459+ * Fix broken key bindings and restore previously mapped hot-keys for
1460+ choosing columns
1461+ * Some documentation updates (but not nearly enough)
1462+ * Allow the user to specify graphing char in S mode (formerly G mode)
1463+ * Allow easy switching between variable sets in S mode
1464+ * Bind 'n' key globally to choose the 'next' server connection
1465+ * Bind '%' key globally to filter displayed tables
1466+ * Allow aligning columns on the decimal place for easy readability
1467+ * Add hide_hdr config variable to hide column headers in tables
1468+ * Add a feature to smartly run PURGE MASTER LOGS in Replication mode
1469+ * Enable debug mode as a globally configurable variable
1470+ * Improve error messages when an expression or filter doesn't compile or has
1471+ a run-time error; die on error when debug is enabled
1472+ * Allow user-configurable delays after executing SQL (to let the server
1473+ settle down before taking another measurement)
1474+ * Add an expression to show how long until a transaction is finished
1475+ * Add skip_innodb as a global config variable
1476+ * Add '%' after percentages to help disambiguate (user-configurable)
1477+ * Add column to M mode to help see how fast slave is catching up to master
1478+
1479+ BUG FIXES:
1480+
1481+ * T and W modes had wrong value for wait_status column
1482+ * Error tracking on connections didn't reset when the connection recovered
1483+ * wait_timeout on connections couldn't be set before MySQL 4.0.3
1484+ * There was a crash on 3.23 when wiping deadlocks
1485+ * Lettercase changes in some result sets (SHOW MASTER/SLAVE STATUS) between
1486+ MySQL versions crashed innotop
1487+ * Inactive connections crashed innotop upon access to DBD driver
1488+ * set_precision did not respect user defaults for number of digits
1489+ * --inc command-line option could not be negated
1490+ * InnoDB status parsing was not always parsing all needed information
1491+ * S mode (formerly G mode) could crash trying to divide non-numeric data
1492+ * M table didn't show Slave_open_temp_tables variable; incorrect lettercase
1493+ * DBD drivers with broken AutoCommit would crash innotop
1494+ * Some key bindings had incorrect labels
1495+ * Some config-file loading routines could load data for things that didn't
1496+ exist
1497+ * Headers printed too often in S mode
1498+ * High-resolution time was not used even when the user had it
1499+ * Non-interactive mode printed blank lines sometimes
1500+ * Q-mode header and statusbar showed different QPS numbers
1501+ * Formulas for key-cache and query-cache hit ratios were wrong
1502+ * Mac OS "Darwin" machines were mis-identified as Microsoft Windows
1503+ * Some multiplications crashed when given undefined input
1504+ * The commify transformation did not check its input and could crash
1505+ * Specifying an invalid mode on the command line or config file could crash
1506+ innotop
1507+
1508+2007-03-29: version 1.4.1
1509+
1510+ * More tweaks to display of connection errors.
1511+ * Fixed a problem with skip-innodb in MySQL 5.1.
1512+ * Fix a bug with dead connections in single-connection mode.
1513+ * Fix a regex to allow parsing more data from truncated deadlocks.
1514+ * Don't load active cxns from the config file if the cxn isn't defined.
1515+
1516+2007-03-03: version 1.4.0
1517+
1518+ * Further tweak error handling and display of connection errors
1519+ * More centralization of querying
1520+ * Fix forking so it doesn't kill all database connections
1521+ * Allow user to run innotop without permissions for GLOBAL variables and status
1522+
1523+2007-02-11: version 1.3.6
1524+
1525+ * Handle some connection failures so innotop doesn't crash because of one server.
1526+ * Enable incremental display in more modes.
1527+ * Tweaks to colorizing, color editor, and default color rules.
1528+ * Tweaks to default sorting rules.
1529+ * Use prepared statements for efficiency.
1530+ * Bug fixes and code cleanups.
1531+ * Data storage is keyed on clock ticks now.
1532+
1533+2007-02-03: version 1.3.5
1534+
1535+ * Bug fixes.
1536+ * More tools for editing configuration from within innotop.
1537+ * Filters and transformations are constrained to valid values.
1538+ * Support for colorizing rows.
1539+ * Sorting by multiple columns.
1540+ * Compress headers when display is very wide.
1541+ * Stabilize and limit column widths.
1542+ * Check config file formats when upgrading so upgrades go smoothly.
1543+ * Make D mode handle many connections at once.
1544+ * Extract simple expressions from data sets in column src property.
1545+ This makes innotop more awk-ish.
1546+
1547+2007-01-16: version 1.3
1548+
1549+ * Readline support.
1550+ * Can be used unattended, or in a pipe-and-filter mode
1551+ where it outputs tab-separated data to standard output.
1552+ * You can specify a config file on the command line.
1553+ Config files can be marked read-only.
1554+ * Monitor multiple servers simultaneously.
1555+ * Server groups to help manage many servers conveniently.
1556+ * Monitor master/slave status, and control slaves.
1557+ * Columns can have user-defined expressions as their data sources.
1558+ * Better configuration tools.
1559+ * InnoDB status information is merged into SHOW VARIABLES and
1560+ SHOW STATUS information, so you can access it all together.
1561+ * High-precision time support in more places.
1562+ * Lots of tweaks to make things display more readably and compactly.
1563+ * Column transformations and filters.
1564+
1565+2007-01-16: version 1.0.1
1566+ * NOTE: innotop is now hosted at Sourceforge, in Subversion not CVS.
1567+ The new project homepage is http://sourceforge.net/projects/innotop/
1568+ * Tweak default T/Q mode sort columns to match what people expect.
1569+ * Fix broken InnoDBParser.pm documentation (and hence man page).
1570+
1571+2007-01-06: version 1.0
1572+ * NOTE: innotop is now hosted at Sourceforge, in Subversion not CVS.
1573+ The new project homepage is http://sourceforge.net/projects/innotop/
1574+ * Prevent control characters from freaking terminal out.
1575+ * Set timeout to keep busy servers from closing connection.
1576+ * There is only one InnoDB insert buffer.
1577+ * Make licenses clear and consistent.
1578+
1579+2006-11-14: innotop 0.1.160, InnoDBParser version 1.69
1580+ * Support for ANSI color on Microsoft Windows (more readable, compact
1581+ display; thanks Gisbert W. Selke).
1582+ * Better handling of $ENV{HOME} on Windows.
1583+ * Added a LICENSE file to the package as per Gentoo bug:
1584+ http://bugs.gentoo.org/show_bug.cgi?id=147600
1585+
1586+2006-11-11: innotop 0.1.157, InnoDBParser version 1.69
1587+ * Add Microsoft Windows support.
1588+
1589+2006-10-19: innotop 0.1.154, InnoDBParser version 1.69
1590+ * Add O (Open Tables) mode
1591+ * Add some more checks to handle incomplete InnoDB status information
1592+
1593+2006-09-30: innotop 0.1.152, InnoDBParser version 1.69
1594+ * Figured out what was wrong with package $VERSION variable: it wasn't
1595+ after the package declaration!
1596+
1597+2006-09-28: innotop 0.1.152, InnoDBParser version 1.67
1598+ * Make more efforts towards crash-resistance and tolerance of completely
1599+ messed-up inputs. If innotop itself is broken, it is now much harder to
1600+ tell, because it just keeps on running without complaining.
1601+ * Fix a small bug parsing out some information and displaying it.
1602+
1603+2006-09-05: innotop 0.1.149, InnoDBParser version 1.64
1604+ * Try to find and eliminate any parsing code that assumes pattern matches
1605+ will succeed.
1606+
1607+2006-09-05: innotop 0.1.149, InnoDBParser version 1.62
1608+ * Make innotop crash-resistant, so I can declare it STABLE finally.
1609+ * Instead of using SQL conditional comments, detect MySQL version.
1610+
1611+2006-08-22: innotop 0.1.147, InnoDBParser version 1.60
1612+ * Fix some innotop bugs with undefined values, bad formatting etc.
1613+
1614+2006-08-19: innotop 0.1.146, InnoDBParser version 1.60
1615+ * Make innotop handle some unexpected NULL values in Q mode.
1616+ * Add OS wait information to W mode, so it is now "everything that waits."
1617+ * Center section captions better.
1618+ * Make R mode more readable and compact.
1619+ * Make InnoDBParser parse lock waits even when they've been waiting 0 secs.
1620+
1621+2006-08-12: innotop 0.1.139, InnoDBParser version 1.59
1622+ * Add more documentation
1623+ * Tweak V mode to show more info in less space.
1624+ * Fix a bug in G mode.
1625+
1626+2006-08-10: innotop 0.1.132, InnoDBParser version 1.58
1627+ * Handle yet more types of FK error... it will never end!
1628+ * Handle some special cases when DEADLOCK info truncated
1629+ * Add a bit more FK info to F mode in innotop
1630+ * More tests added to the test suite
1631+
1632+2006-08-07: innotop 0.1.131, InnoDBParser version 1.55
1633+ * Fix another issue with configuration
1634+ * Handle another type of FK error
1635+
1636+2006-08-03: innotop 0.1.130, InnoDBParser version 1.54
1637+ * Fix an issue loading config file
1638+ * Add heap_no to 'D' (InnoDB Deadlock) mode to ease deadlock debugging.
1639+
1640+2006-08-02: innotop 0.1.128, InnoDBParser version 1.54
1641+ * Parse lock wait information from the TRANSACTION section.
1642+ * Even more OS-specific parsing... pain in the butt...
1643+ * Add 'W' (InnoDB Lock Wait) mode.
1644+ * Fix some minor display issues with statusbar.
1645+
1646+2006-08-02: innotop 0.1.125, InnoDBParser version 1.50
1647+ * Don't try to get references to Perl built-in functions like time()
1648+ * Handle more OS-specific variations of InnoDB status text
1649+ * Add some more information to various places in innotop
1650+
1651+2006-08-01: innotop 0.1.123, InnoDBParser version 1.47
1652+
1653+ * Enhance S and G modes: clear screen and re-print headers
1654+ * Don't crash when deadlock data is truncated
1655+ * Make Analyze mode say how to get back to whatever you came from
1656+ * Display 'nothing to display' when there is nothing
1657+ * Add ability to read InnoDB status text from a file (mostly helps test)
1658+ * Add table of Wait Array Information in Row Op/Semaphore mode
1659+ * Add table of lock information in InnoDB deadlock mode
1660+ * Ensure new features in upgrades don't get masked by existing config files
1661+ * Tweak default column choices for T mode
1662+ * Enhance foreign key parsing
1663+ * Enhance physical record and data tuple parsing
1664+ * Enhance lock parsing (handle old-style and new-style formats)
1665+
1666+2006-07-24: innotop 0.1.112, InnoDBParser version 1.36
1667+
1668+ * InnoDBParser enhancements for FK error messages.
1669+ * A fix to innotop to prevent it from crashing while trying to display a FK
1670+ error message.
1671+ * Some minor cosmetic changes to number formatting in innotop.
1672+
1673+2006-07-22: innotop 0.1.106, InnoDBParser version 1.35
1674+
1675+ * InnoDBParser is much more complete and accurate.
1676+ * Tons of bug fixes.
1677+ * Add partitions to EXPLAIN mode.
1678+ * Enhance Q mode header, add T mode header.
1679+ * Share some configuration variables across modes.
1680+ * Add formatted time columns to Q, T modes.
1681+ * Add command-line argument parsing.
1682+ * Turn off echo when asking for password.
1683+ * Add option to specify port when connecting.
1684+ * Let display-optimized-query display multiple notes.
1685+ * Lots of small improvements, such as showing more info in statusbar.
1686+
1687+2006-07-02: innotop 0.1.74, InnoDBParser version 1.24
1688+
1689+ * Initial release for public consumption.
1690
1691=== added file 'build/debian/additions/innotop/innotop'
1692--- build/debian/additions/innotop/innotop 1970-01-01 00:00:00 +0000
1693+++ build/debian/additions/innotop/innotop 2010-01-24 19:18:15 +0000
1694@@ -0,0 +1,9485 @@
1695+#!/usr/bin/perl
1696+
1697+# vim: tw=160:nowrap:expandtab:tabstop=3:shiftwidth=3:softtabstop=3
1698+
1699+use strict;
1700+use warnings FATAL => 'all';
1701+use sigtrap qw(handler finish untrapped normal-signals);
1702+
1703+use Data::Dumper;
1704+use DBI;
1705+use English qw(-no_match_vars);
1706+use File::Basename qw(dirname);
1707+use Getopt::Long;
1708+use List::Util qw(max min maxstr sum);
1709+use InnoDBParser;
1710+use POSIX qw(ceil);
1711+use Time::HiRes qw(time sleep);
1712+use Term::ReadKey qw(ReadMode ReadKey);
1713+
1714+# Version, license and warranty information. {{{1
1715+# ###########################################################################
1716+our $VERSION = '1.6.0';
1717+our $SVN_REV = sprintf("%d", q$Revision: 383 $ =~ m/(\d+)/g);
1718+our $SVN_URL = sprintf("%s", q$URL: https://innotop.svn.sourceforge.net/svnroot/innotop/trunk/innotop $ =~ m$svnroot/innotop/(\S+)$g);
1719+
1720+my $innotop_license = <<"LICENSE";
1721+
1722+This is innotop version $VERSION, a MySQL and InnoDB monitor.
1723+
1724+This program is copyright (c) 2006 Baron Schwartz.
1725+Feedback and improvements are welcome.
1726+
1727+THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
1728+WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
1729+MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
1730+
1731+This program is free software; you can redistribute it and/or modify it under
1732+the terms of the GNU General Public License as published by the Free Software
1733+Foundation, version 2; OR the Perl Artistic License. On UNIX and similar
1734+systems, you can issue `man perlgpl' or `man perlartistic' to read these
1735+licenses.
1736+
1737+You should have received a copy of the GNU General Public License along with
1738+this program; if not, write to the Free Software Foundation, Inc., 59 Temple
1739+Place, Suite 330, Boston, MA 02111-1307 USA.
1740+LICENSE
1741+
1742+# Configuration information and global setup {{{1
1743+# ###########################################################################
1744+
1745+# Really, really, super-global variables.
1746+my @config_versions = (
1747+ "000-000-000", "001-003-000", # config file was one big name-value hash.
1748+ "001-003-000", "001-004-002", # config file contained non-user-defined stuff.
1749+);
1750+
1751+my $clear_screen_sub;
1752+
1753+# This defines expected properties and defaults for the column definitions that
1754+# eventually end up in tbl_meta.
1755+my %col_props = (
1756+ hdr => '',
1757+ just => '-',
1758+ dec => 0, # Whether to align the column on the decimal point
1759+ num => 0,
1760+ label => '',
1761+ user => 0,
1762+ src => '',
1763+ tbl => '', # Helps when writing/reading custom columns in config files
1764+ minw => 0,
1765+ maxw => 0,
1766+ trans => [],
1767+ agg => 'first', # Aggregate function
1768+ aggonly => 0, # Whether to show only when tbl_meta->{aggregate} is true
1769+);
1770+
1771+# Actual DBI connections to MySQL servers.
1772+my %dbhs;
1773+
1774+# Command-line parameters {{{2
1775+# ###########################################################################
1776+
1777+my @opt_spec = (
1778+ { s => 'help', d => 'Show this help message' },
1779+ { s => 'color|C!', d => 'Use terminal coloring (default)', c => 'color' },
1780+ { s => 'config|c=s', d => 'Config file to read' },
1781+ { s => 'nonint|n', d => 'Non-interactive, output tab-separated fields' },
1782+ { s => 'count=i', d => 'Number of updates before exiting' },
1783+ { s => 'delay|d=f', d => 'Delay between updates in seconds', c => 'interval' },
1784+ { s => 'mode|m=s', d => 'Operating mode to start in', c => 'mode' },
1785+ { s => 'inc|i!', d => 'Measure incremental differences', c => 'status_inc' },
1786+ { s => 'version', d => 'Output version information and exit' },
1787+);
1788+
1789+# This is the container for the command-line options' values to be stored in
1790+# after processing. Initial values are defaults.
1791+my %opts = (
1792+ n => !( -t STDIN && -t STDOUT ), # If in/out aren't to terminals, we're interactive
1793+);
1794+# Post-process...
1795+my %opt_seen;
1796+foreach my $spec ( @opt_spec ) {
1797+ my ( $long, $short ) = $spec->{s} =~ m/^(\w+)(?:\|([^!+=]*))?/;
1798+ $spec->{k} = $short || $long;
1799+ $spec->{l} = $long;
1800+ $spec->{t} = $short;
1801+ $spec->{n} = $spec->{s} =~ m/!/;
1802+ $opts{$spec->{k}} = undef unless defined $opts{$spec->{k}};
1803+ die "Duplicate option $spec->{k}" if $opt_seen{$spec->{k}}++;
1804+}
1805+
1806+Getopt::Long::Configure('no_ignore_case', 'bundling');
1807+GetOptions( map { $_->{s} => \$opts{$_->{k}} } @opt_spec) or $opts{help} = 1;
1808+
1809+if ( $opts{version} ) {
1810+ print "innotop Ver $VERSION Changeset $SVN_REV from $SVN_URL\n";
1811+ exit(0);
1812+}
1813+
1814+if ( $opts{'help'} ) {
1815+ print "Usage: innotop <options> <innodb-status-file>\n\n";
1816+ my $maxw = max(map { length($_->{l}) + ($_->{n} ? 4 : 0)} @opt_spec);
1817+ foreach my $spec ( sort { $a->{l} cmp $b->{l} } @opt_spec ) {
1818+ my $long = $spec->{n} ? "[no]$spec->{l}" : $spec->{l};
1819+ my $short = $spec->{t} ? "-$spec->{t}" : '';
1820+ printf(" --%-${maxw}s %-4s %s\n", $long, $short, $spec->{d});
1821+ }
1822+ print <<USAGE;
1823+
1824+innotop is a MySQL and InnoDB transaction/status monitor, like 'top' for
1825+MySQL. It displays queries, InnoDB transactions, lock waits, deadlocks,
1826+foreign key errors, open tables, replication status, buffer information,
1827+row operations, logs, I/O operations, load graph, and more. You can
1828+monitor many servers at once with innotop.
1829+
1830+USAGE
1831+ exit(1);
1832+}
1833+
1834+# Meta-data (table definitions etc) {{{2
1835+# ###########################################################################
1836+
1837+# Expressions {{{3
1838+# Convenience so I can copy/paste these in several places...
1839+# ###########################################################################
1840+my %exprs = (
1841+ Host => q{my $host = host || hostname || ''; ($host) = $host =~ m/^((?:[\d.]+(?=:))|(?:[a-zA-Z]\w+))/; return $host || ''},
1842+ Port => q{my ($p) = host =~ m/:(.*)$/; return $p || 0},
1843+ OldVersions => q{dulint_to_int(IB_tx_trx_id_counter) - dulint_to_int(IB_tx_purge_done_for)},
1844+ MaxTxnTime => q/max(map{ $_->{active_secs} } @{ IB_tx_transactions }) || 0/,
1845+ NumTxns => q{scalar @{ IB_tx_transactions } },
1846+ DirtyBufs => q{ $cur->{IB_bp_pages_modified} / ($cur->{IB_bp_buf_pool_size} || 1) },
1847+ BufPoolFill => q{ $cur->{IB_bp_pages_total} / ($cur->{IB_bp_buf_pool_size} || 1) },
1848+ ServerLoad => q{ $cur->{Threads_connected}/(Questions||1)/Uptime_hires },
1849+ 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},
1850+ 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',
1851+ QcacheHitRatio => q{(Qcache_hits||0)/(((Com_select||0)+(Qcache_hits||0))||1)},
1852+);
1853+
1854+# ###########################################################################
1855+# Column definitions {{{3
1856+# Defines every column in every table. A named column has the following
1857+# properties:
1858+# * hdr Column header/title
1859+# * label Documentation for humans.
1860+# * num Whether it's numeric (for sorting).
1861+# * just Alignment; generated from num, user-overridable in tbl_meta
1862+# * minw, maxw Auto-generated, user-overridable.
1863+# Values from this hash are just copied to tbl_meta, which is where everything
1864+# else in the program should read from.
1865+# ###########################################################################
1866+
1867+my %columns = (
1868+ active_secs => { hdr => 'SecsActive', num => 1, label => 'Seconds transaction has been active', },
1869+ add_pool_alloc => { hdr => 'Add\'l Pool', num => 1, label => 'Additonal pool allocated' },
1870+ attempted_op => { hdr => 'Action', num => 0, label => 'The action that caused the error' },
1871+ awe_mem_alloc => { hdr => 'AWE Memory', num => 1, label => '[Windows] AWE memory allocated' },
1872+ binlog_cache_overflow => { hdr => 'Binlog Cache', num => 1, label => 'Transactions too big for binlog cache that went to disk' },
1873+ binlog_do_db => { hdr => 'Binlog Do DB', num => 0, label => 'binlog-do-db setting' },
1874+ binlog_ignore_db => { hdr => 'Binlog Ignore DB', num => 0, label => 'binlog-ignore-db setting' },
1875+ bps_in => { hdr => 'BpsIn', num => 1, label => 'Bytes per second received by the server', },
1876+ bps_out => { hdr => 'BpsOut', num => 1, label => 'Bytes per second sent by the server', },
1877+ buf_free => { hdr => 'Free Bufs', num => 1, label => 'Buffers free in the buffer pool' },
1878+ buf_pool_hit_rate => { hdr => 'Hit Rate', num => 0, label => 'Buffer pool hit rate' },
1879+ buf_pool_hits => { hdr => 'Hits', num => 1, label => 'Buffer pool hits' },
1880+ buf_pool_reads => { hdr => 'Reads', num => 1, label => 'Buffer pool reads' },
1881+ buf_pool_size => { hdr => 'Size', num => 1, label => 'Buffer pool size' },
1882+ bufs_in_node_heap => { hdr => 'Node Heap Bufs', num => 1, label => 'Buffers in buffer pool node heap' },
1883+ bytes_behind_master => { hdr => 'ByteLag', num => 1, label => 'Bytes the slave lags the master in binlog' },
1884+ cell_event_set => { hdr => 'Ending?', num => 1, label => 'Whether the cell event is set' },
1885+ cell_waiting => { hdr => 'Waiting?', num => 1, label => 'Whether the cell is waiting' },
1886+ child_db => { hdr => 'Child DB', num => 0, label => 'The database of the child table' },
1887+ child_index => { hdr => 'Child Index', num => 0, label => 'The index in the child table' },
1888+ child_table => { hdr => 'Child Table', num => 0, label => 'The child table' },
1889+ cmd => { hdr => 'Cmd', num => 0, label => 'Type of command being executed', },
1890+ cnt => { hdr => 'Cnt', num => 0, label => 'Count', agg => 'count', aggonly => 1 },
1891+ connect_retry => { hdr => 'Connect Retry', num => 1, label => 'Slave connect-retry timeout' },
1892+ cxn => { hdr => 'CXN', num => 0, label => 'Connection from which the data came', },
1893+ db => { hdr => 'DB', num => 0, label => 'Current database', },
1894+ dict_mem_alloc => { hdr => 'Dict Mem', num => 1, label => 'Dictionary memory allocated' },
1895+ dirty_bufs => { hdr => 'Dirty Buf', num => 1, label => 'Dirty buffer pool pages' },
1896+ dl_txn_num => { hdr => 'Num', num => 0, label => 'Deadlocked transaction number', },
1897+ event_set => { hdr => 'Evt Set?', num => 1, label => '[Win32] if a wait event is set', },
1898+ exec_master_log_pos => { hdr => 'Exec Master Log Pos', num => 1, label => 'Exec Master Log Position' },
1899+ fk_name => { hdr => 'Constraint', num => 0, label => 'The name of the FK constraint' },
1900+ free_list_len => { hdr => 'Free List Len', num => 1, label => 'Length of the free list' },
1901+ has_read_view => { hdr => 'Rd View', num => 1, label => 'Whether the transaction has a read view' },
1902+ hash_searches_s => { hdr => 'Hash/Sec', num => 1, label => 'Number of hash searches/sec' },
1903+ hash_table_size => { hdr => 'Size', num => 1, label => 'Number of non-hash searches/sec' },
1904+ heap_no => { hdr => 'Heap', num => 1, label => 'Heap number' },
1905+ heap_size => { hdr => 'Heap', num => 1, label => 'Heap size' },
1906+ history_list_len => { hdr => 'History', num => 1, label => 'History list length' },
1907+ host_and_domain => { hdr => 'Host', num => 0, label => 'Hostname/IP and domain' },
1908+ host_and_port => { hdr => 'Host/IP', num => 0, label => 'Hostname or IP address, and port number', },
1909+ hostname => { hdr => 'Host', num => 0, label => 'Hostname' },
1910+ index => { hdr => 'Index', num => 0, label => 'The index involved' },
1911+ index_ref => { hdr => 'Index Ref', num => 0, label => 'Index referenced' },
1912+ info => { hdr => 'Query', num => 0, label => 'Info or the current query', },
1913+ insert_intention => { hdr => 'Ins Intent', num => 1, label => 'Whether the thread was trying to insert' },
1914+ inserts => { hdr => 'Inserts', num => 1, label => 'Inserts' },
1915+ io_bytes_s => { hdr => 'Bytes/Sec', num => 1, label => 'Average I/O bytes/sec' },
1916+ io_flush_type => { hdr => 'Flush Type', num => 0, label => 'I/O Flush Type' },
1917+ io_fsyncs_s => { hdr => 'fsyncs/sec', num => 1, label => 'I/O fsyncs/sec' },
1918+ io_reads_s => { hdr => 'Reads/Sec', num => 1, label => 'Average I/O reads/sec' },
1919+ io_writes_s => { hdr => 'Writes/Sec', num => 1, label => 'Average I/O writes/sec' },
1920+ ip => { hdr => 'IP', num => 0, label => 'IP address' },
1921+ is_name_locked => { hdr => 'Locked', num => 1, label => 'Whether table is name locked', },
1922+ key_buffer_hit => { hdr => 'KCacheHit', num => 1, label => 'Key cache hit ratio', },
1923+ key_len => { hdr => 'Key Length', num => 1, label => 'Number of bytes used in the key' },
1924+ last_chkp => { hdr => 'Last Checkpoint', num => 0, label => 'Last log checkpoint' },
1925+ last_errno => { hdr => 'Last Errno', num => 1, label => 'Last error number' },
1926+ last_error => { hdr => 'Last Error', num => 0, label => 'Last error' },
1927+ last_s_file_name => { hdr => 'S-File', num => 0, label => 'Filename where last read locked' },
1928+ last_s_line => { hdr => 'S-Line', num => 1, label => 'Line where last read locked' },
1929+ last_x_file_name => { hdr => 'X-File', num => 0, label => 'Filename where last write locked' },
1930+ last_x_line => { hdr => 'X-Line', num => 1, label => 'Line where last write locked' },
1931+ last_pct => { hdr => 'Pct', num => 1, label => 'Last Percentage' },
1932+ last_total => { hdr => 'Last Total', num => 1, label => 'Last Total' },
1933+ last_value => { hdr => 'Last Incr', num => 1, label => 'Last Value' },
1934+ load => { hdr => 'Load', num => 1, label => 'Server load' },
1935+ lock_cfile_name => { hdr => 'Crtd File', num => 0, label => 'Filename where lock created' },
1936+ lock_cline => { hdr => 'Crtd Line', num => 1, label => 'Line where lock created' },
1937+ lock_mem_addr => { hdr => 'Addr', num => 0, label => 'The lock memory address' },
1938+ lock_mode => { hdr => 'Mode', num => 0, label => 'The lock mode' },
1939+ lock_structs => { hdr => 'LStrcts', num => 1, label => 'Number of lock structs' },
1940+ lock_type => { hdr => 'Type', num => 0, label => 'The lock type' },
1941+ lock_var => { hdr => 'Lck Var', num => 1, label => 'The lock variable' },
1942+ lock_wait_time => { hdr => 'Wait', num => 1, label => 'How long txn has waited for a lock' },
1943+ log_flushed_to => { hdr => 'Flushed To', num => 0, label => 'Log position flushed to' },
1944+ log_ios_done => { hdr => 'IO Done', num => 1, label => 'Log I/Os done' },
1945+ log_ios_s => { hdr => 'IO/Sec', num => 1, label => 'Average log I/Os per sec' },
1946+ log_seq_no => { hdr => 'Sequence No.', num => 0, label => 'Log sequence number' },
1947+ main_thread_id => { hdr => 'Main Thread ID', num => 1, label => 'Main thread ID' },
1948+ main_thread_proc_no => { hdr => 'Main Thread Proc', num => 1, label => 'Main thread process number' },
1949+ main_thread_state => { hdr => 'Main Thread State', num => 0, label => 'Main thread state' },
1950+ master_file => { hdr => 'File', num => 0, label => 'Master file' },
1951+ master_host => { hdr => 'Master', num => 0, label => 'Master server hostname' },
1952+ master_log_file => { hdr => 'Master Log File', num => 0, label => 'Master log file' },
1953+ master_port => { hdr => 'Master Port', num => 1, label => 'Master port' },
1954+ master_pos => { hdr => 'Position', num => 1, label => 'Master position' },
1955+ master_ssl_allowed => { hdr => 'Master SSL Allowed', num => 0, label => 'Master SSL Allowed' },
1956+ master_ssl_ca_file => { hdr => 'Master SSL CA File', num => 0, label => 'Master SSL Cert Auth File' },
1957+ master_ssl_ca_path => { hdr => 'Master SSL CA Path', num => 0, label => 'Master SSL Cert Auth Path' },
1958+ master_ssl_cert => { hdr => 'Master SSL Cert', num => 0, label => 'Master SSL Cert' },
1959+ master_ssl_cipher => { hdr => 'Master SSL Cipher', num => 0, label => 'Master SSL Cipher' },
1960+ master_ssl_key => { hdr => 'Master SSL Key', num => 0, label => 'Master SSL Key' },
1961+ master_user => { hdr => 'Master User', num => 0, label => 'Master username' },
1962+ max_txn => { hdr => 'MaxTxnTime', num => 1, label => 'MaxTxn' },
1963+ merged_recs => { hdr => 'Merged Recs', num => 1, label => 'Merged records' },
1964+ merges => { hdr => 'Merges', num => 1, label => 'Merges' },
1965+ mutex_os_waits => { hdr => 'Waits', num => 1, label => 'Mutex OS Waits' },
1966+ mutex_spin_rounds => { hdr => 'Rounds', num => 1, label => 'Mutex Spin Rounds' },
1967+ mutex_spin_waits => { hdr => 'Spins', num => 1, label => 'Mutex Spin Waits' },
1968+ mysql_thread_id => { hdr => 'ID', num => 1, label => 'MySQL connection (thread) ID', },
1969+ name => { hdr => 'Name', num => 0, label => 'Variable Name' },
1970+ n_bits => { hdr => '# Bits', num => 1, label => 'Number of bits' },
1971+ non_hash_searches_s => { hdr => 'Non-Hash/Sec', num => 1, label => 'Non-hash searches/sec' },
1972+ num_deletes => { hdr => 'Del', num => 1, label => 'Number of deletes' },
1973+ num_deletes_sec => { hdr => 'Del/Sec', num => 1, label => 'Number of deletes' },
1974+ num_inserts => { hdr => 'Ins', num => 1, label => 'Number of inserts' },
1975+ num_inserts_sec => { hdr => 'Ins/Sec', num => 1, label => 'Number of inserts' },
1976+ num_readers => { hdr => 'Readers', num => 1, label => 'Number of readers' },
1977+ num_reads => { hdr => 'Read', num => 1, label => 'Number of reads' },
1978+ num_reads_sec => { hdr => 'Read/Sec', num => 1, label => 'Number of reads' },
1979+ num_res_ext => { hdr => 'BTree Extents', num => 1, label => 'Number of extents reserved for B-Tree' },
1980+ num_rows => { hdr => 'Row Count', num => 1, label => 'Number of rows estimated to examine' },
1981+ num_times_open => { hdr => 'In Use', num => 1, label => '# times table is opened', },
1982+ num_txns => { hdr => 'Txns', num => 1, label => 'Number of transactions' },
1983+ num_updates => { hdr => 'Upd', num => 1, label => 'Number of updates' },
1984+ num_updates_sec => { hdr => 'Upd/Sec', num => 1, label => 'Number of updates' },
1985+ os_file_reads => { hdr => 'OS Reads', num => 1, label => 'OS file reads' },
1986+ os_file_writes => { hdr => 'OS Writes', num => 1, label => 'OS file writes' },
1987+ os_fsyncs => { hdr => 'OS fsyncs', num => 1, label => 'OS fsyncs' },
1988+ os_thread_id => { hdr => 'OS Thread', num => 1, label => 'The operating system thread ID' },
1989+ p_aio_writes => { hdr => 'Async Wrt', num => 1, label => 'Pending asynchronous I/O writes' },
1990+ p_buf_pool_flushes => { hdr => 'Buffer Pool Flushes', num => 1, label => 'Pending buffer pool flushes' },
1991+ p_ibuf_aio_reads => { hdr => 'IBuf Async Rds', num => 1, label => 'Pending insert buffer asynch I/O reads' },
1992+ p_log_flushes => { hdr => 'Log Flushes', num => 1, label => 'Pending log flushes' },
1993+ p_log_ios => { hdr => 'Log I/Os', num => 1, label => 'Pending log I/O operations' },
1994+ p_normal_aio_reads => { hdr => 'Async Rds', num => 1, label => 'Pending asynchronous I/O reads' },
1995+ p_preads => { hdr => 'preads', num => 1, label => 'Pending p-reads' },
1996+ p_pwrites => { hdr => 'pwrites', num => 1, label => 'Pending p-writes' },
1997+ p_sync_ios => { hdr => 'Sync I/Os', num => 1, label => 'Pending synchronous I/O operations' },
1998+ page_creates_sec => { hdr => 'Creates/Sec', num => 1, label => 'Page creates/sec' },
1999+ page_no => { hdr => 'Page', num => 1, label => 'Page number' },
2000+ page_reads_sec => { hdr => 'Reads/Sec', num => 1, label => 'Page reads per second' },
2001+ page_writes_sec => { hdr => 'Writes/Sec', num => 1, label => 'Page writes per second' },
2002+ pages_created => { hdr => 'Created', num => 1, label => 'Pages created' },
2003+ pages_modified => { hdr => 'Dirty Pages', num => 1, label => 'Pages modified (dirty)' },
2004+ pages_read => { hdr => 'Reads', num => 1, label => 'Pages read' },
2005+ pages_total => { hdr => 'Pages', num => 1, label => 'Pages total' },
2006+ pages_written => { hdr => 'Writes', num => 1, label => 'Pages written' },
2007+ parent_col => { hdr => 'Parent Column', num => 0, label => 'The referred column in the parent table', },
2008+ parent_db => { hdr => 'Parent DB', num => 0, label => 'The database of the parent table' },
2009+ parent_index => { hdr => 'Parent Index', num => 0, label => 'The referred index in the parent table' },
2010+ parent_table => { hdr => 'Parent Table', num => 0, label => 'The parent table' },
2011+ part_id => { hdr => 'Part ID', num => 1, label => 'Sub-part ID of the query' },
2012+ partitions => { hdr => 'Partitions', num => 0, label => 'Query partitions used' },
2013+ pct => { hdr => 'Pct', num => 1, label => 'Percentage' },
2014+ pending_chkp_writes => { hdr => 'Chkpt Writes', num => 1, label => 'Pending log checkpoint writes' },
2015+ pending_log_writes => { hdr => 'Log Writes', num => 1, label => 'Pending log writes' },
2016+ port => { hdr => 'Port', num => 1, label => 'Client port number', },
2017+ possible_keys => { hdr => 'Poss. Keys', num => 0, label => 'Possible keys' },
2018+ proc_no => { hdr => 'Proc', num => 1, label => 'Process number' },
2019+ q_cache_hit => { hdr => 'QCacheHit', num => 1, label => 'Query cache hit ratio', },
2020+ qps => { hdr => 'QPS', num => 1, label => 'How many queries/sec', },
2021+ queries_in_queue => { hdr => 'Queries Queued', num => 1, label => 'Queries in queue' },
2022+ queries_inside => { hdr => 'Queries Inside', num => 1, label => 'Queries inside InnoDB' },
2023+ query_id => { hdr => 'Query ID', num => 1, label => 'Query ID' },
2024+ query_status => { hdr => 'Query Status', num => 0, label => 'The query status' },
2025+ query_text => { hdr => 'Query Text', num => 0, label => 'The query text' },
2026+ questions => { hdr => 'Questions', num => 1, label => 'How many queries the server has gotten', },
2027+ read_master_log_pos => { hdr => 'Read Master Pos', num => 1, label => 'Read master log position' },
2028+ read_views_open => { hdr => 'Rd Views', num => 1, label => 'Number of read views open' },
2029+ reads_pending => { hdr => 'Pending Reads', num => 1, label => 'Reads pending' },
2030+ relay_log_file => { hdr => 'Relay File', num => 0, label => 'Relay log file' },
2031+ relay_log_pos => { hdr => 'Relay Pos', num => 1, label => 'Relay log position' },
2032+ relay_log_size => { hdr => 'Relay Size', num => 1, label => 'Relay log size' },
2033+ relay_master_log_file => { hdr => 'Relay Master File', num => 0, label => 'Relay master log file' },
2034+ replicate_do_db => { hdr => 'Do DB', num => 0, label => 'Replicate-do-db setting' },
2035+ replicate_do_table => { hdr => 'Do Table', num => 0, label => 'Replicate-do-table setting' },
2036+ replicate_ignore_db => { hdr => 'Ignore DB', num => 0, label => 'Replicate-ignore-db setting' },
2037+ replicate_ignore_table => { hdr => 'Ignore Table', num => 0, label => 'Replicate-do-table setting' },
2038+ replicate_wild_do_table => { hdr => 'Wild Do Table', num => 0, label => 'Replicate-wild-do-table setting' },
2039+ replicate_wild_ignore_table => { hdr => 'Wild Ignore Table', num => 0, label => 'Replicate-wild-ignore-table setting' },
2040+ request_type => { hdr => 'Type', num => 0, label => 'Type of lock the thread waits for' },
2041+ reservation_count => { hdr => 'ResCnt', num => 1, label => 'Reservation Count' },
2042+ row_locks => { hdr => 'RLocks', num => 1, label => 'Number of row locks' },
2043+ rw_excl_os_waits => { hdr => 'RW Waits', num => 1, label => 'R/W Excl. OS Waits' },
2044+ rw_excl_spins => { hdr => 'RW Spins', num => 1, label => 'R/W Excl. Spins' },
2045+ rw_shared_os_waits => { hdr => 'Sh Waits', num => 1, label => 'R/W Shared OS Waits' },
2046+ rw_shared_spins => { hdr => 'Sh Spins', num => 1, label => 'R/W Shared Spins' },
2047+ scan_type => { hdr => 'Type', num => 0, label => 'Scan type in chosen' },
2048+ seg_size => { hdr => 'Seg. Size', num => 1, label => 'Segment size' },
2049+ select_type => { hdr => 'Select Type', num => 0, label => 'Type of select used' },
2050+ signal_count => { hdr => 'Signals', num => 1, label => 'Signal Count' },
2051+ size => { hdr => 'Size', num => 1, label => 'Size of the tablespace' },
2052+ skip_counter => { hdr => 'Skip Counter', num => 1, label => 'Skip counter' },
2053+ slave_catchup_rate => { hdr => 'Catchup', num => 1, label => 'How fast the slave is catching up in the binlog' },
2054+ slave_io_running => { hdr => 'Slave-IO', num => 0, label => 'Whether the slave I/O thread is running' },
2055+ slave_io_state => { hdr => 'Slave IO State', num => 0, label => 'Slave I/O thread state' },
2056+ slave_open_temp_tables => { hdr => 'Temp', num => 1, label => 'Slave open temp tables' },
2057+ slave_sql_running => { hdr => 'Slave-SQL', num => 0, label => 'Whether the slave SQL thread is running' },
2058+ slow => { hdr => 'Slow', num => 1, label => 'How many slow queries', },
2059+ space_id => { hdr => 'Space', num => 1, label => 'Tablespace ID' },
2060+ special => { hdr => 'Special', num => 0, label => 'Special/Other info' },
2061+ state => { hdr => 'State', num => 0, label => 'Connection state', maxw => 18, },
2062+ tables_in_use => { hdr => 'Tbl Used', num => 1, label => 'Number of tables in use' },
2063+ tables_locked => { hdr => 'Tbl Lck', num => 1, label => 'Number of tables locked' },
2064+ tbl => { hdr => 'Table', num => 0, label => 'Table', },
2065+ thread => { hdr => 'Thread', num => 1, label => 'Thread number' },
2066+ thread_decl_inside => { hdr => 'Thread Inside', num => 0, label => 'What the thread is declared inside' },
2067+ thread_purpose => { hdr => 'Purpose', num => 0, label => "The thread's purpose" },
2068+ thread_status => { hdr => 'Thread Status', num => 0, label => 'The thread status' },
2069+ time => { hdr => 'Time', num => 1, label => 'Time since the last event', },
2070+ time_behind_master => { hdr => 'TimeLag', num => 1, label => 'Time slave lags master' },
2071+ timestring => { hdr => 'Timestring', num => 0, label => 'Time the event occurred' },
2072+ total => { hdr => 'Total', num => 1, label => 'Total' },
2073+ total_mem_alloc => { hdr => 'Memory', num => 1, label => 'Total memory allocated' },
2074+ truncates => { hdr => 'Trunc', num => 0, label => 'Whether the deadlock is truncating InnoDB status' },
2075+ txn_doesnt_see_ge => { hdr => "Txn Won't See", num => 0, label => 'Where txn read view is limited' },
2076+ txn_id => { hdr => 'ID', num => 0, label => 'Transaction ID' },
2077+ txn_sees_lt => { hdr => 'Txn Sees', num => 1, label => 'Where txn read view is limited' },
2078+ txn_status => { hdr => 'Txn Status', num => 0, label => 'Transaction status' },
2079+ txn_time_remain => { hdr => 'Remaining', num => 1, label => 'Time until txn rollback/commit completes' },
2080+ undo_log_entries => { hdr => 'Undo', num => 1, label => 'Number of undo log entries' },
2081+ undo_for => { hdr => 'Undo', num => 0, label => 'Undo for' },
2082+ until_condition => { hdr => 'Until Condition', num => 0, label => 'Slave until condition' },
2083+ until_log_file => { hdr => 'Until Log File', num => 0, label => 'Slave until log file' },
2084+ until_log_pos => { hdr => 'Until Log Pos', num => 1, label => 'Slave until log position' },
2085+ used_cells => { hdr => 'Cells Used', num => 1, label => 'Number of cells used' },
2086+ used_bufs => { hdr => 'Used Bufs', num => 1, label => 'Number of buffer pool pages used' },
2087+ user => { hdr => 'User', num => 0, label => 'Database username', },
2088+ value => { hdr => 'Value', num => 1, label => 'Value' },
2089+ versions => { hdr => 'Versions', num => 1, label => 'Number of InnoDB MVCC versions unpurged' },
2090+ victim => { hdr => 'Victim', num => 0, label => 'Whether this txn was the deadlock victim' },
2091+ wait_array_size => { hdr => 'Wait Array Size', num => 1, label => 'Wait Array Size' },
2092+ wait_status => { hdr => 'Lock Status', num => 0, label => 'Status of txn locks' },
2093+ waited_at_filename => { hdr => 'File', num => 0, label => 'Filename at which thread waits' },
2094+ waited_at_line => { hdr => 'Line', num => 1, label => 'Line at which thread waits' },
2095+ waiters_flag => { hdr => 'Waiters', num => 1, label => 'Waiters Flag' },
2096+ waiting => { hdr => 'Waiting', num => 1, label => 'Whether lock is being waited for' },
2097+ when => { hdr => 'When', num => 0, label => 'Time scale' },
2098+ writer_lock_mode => { hdr => 'Wrtr Lck Mode', num => 0, label => 'Writer lock mode' },
2099+ writer_thread => { hdr => 'Wrtr Thread', num => 1, label => 'Writer thread ID' },
2100+ writes_pending => { hdr => 'Writes', num => 1, label => 'Number of writes pending' },
2101+ writes_pending_flush_list => { hdr => 'Flush List Writes', num => 1, label => 'Number of flush list writes pending' },
2102+ writes_pending_lru => { hdr => 'LRU Writes', num => 1, label => 'Number of LRU writes pending' },
2103+ writes_pending_single_page => { hdr => '1-Page Writes', num => 1, label => 'Number of 1-page writes pending' },
2104+);
2105+
2106+# Apply a default property or three. By default, columns are not width-constrained,
2107+# aligned left, and sorted alphabetically, not numerically.
2108+foreach my $col ( values %columns ) {
2109+ map { $col->{$_} ||= 0 } qw(num minw maxw);
2110+ $col->{just} = $col->{num} ? '' : '-';
2111+}
2112+
2113+# Filters {{{3
2114+# This hash defines every filter that can be applied to a table. These
2115+# become part of tbl_meta as well. Each filter is just an expression that
2116+# returns true or false.
2117+# Properties of each entry:
2118+# * func: the subroutine
2119+# * name: the name, repeated
2120+# * user: whether it's a user-defined filter (saved in config)
2121+# * text: text of the subroutine
2122+# * note: explanation
2123+my %filters = ();
2124+
2125+# These are pre-processed to live in %filters above, by compiling them.
2126+my %builtin_filters = (
2127+ hide_self => {
2128+ text => <<' END',
2129+ return ( !$set->{info} || $set->{info} ne 'SHOW FULL PROCESSLIST' )
2130+ && ( !$set->{query_text} || $set->{query_text} !~ m/INNODB STATUS$/ );
2131+ END
2132+ note => 'Removes the innotop processes from the list',
2133+ tbls => [qw(innodb_transactions processlist)],
2134+ },
2135+ hide_inactive => {
2136+ text => <<' END',
2137+ return ( !defined($set->{txn_status}) || $set->{txn_status} ne 'not started' )
2138+ && ( !defined($set->{cmd}) || $set->{cmd} !~ m/Sleep|Binlog Dump/ )
2139+ && ( !defined($set->{info}) || $set->{info} =~ m/\S/ );
2140+ END
2141+ note => 'Removes processes which are not doing anything',
2142+ tbls => [qw(innodb_transactions processlist)],
2143+ },
2144+ hide_slave_io => {
2145+ text => <<' END',
2146+ return !$set->{state} || $set->{state} !~ m/^(?:Waiting for master|Has read all relay)/;
2147+ END
2148+ note => 'Removes slave I/O threads from the list',
2149+ tbls => [qw(processlist slave_io_status)],
2150+ },
2151+ table_is_open => {
2152+ text => <<' END',
2153+ return $set->{num_times_open} + $set->{is_name_locked};
2154+ END
2155+ note => 'Removes tables that are not in use or locked',
2156+ tbls => [qw(open_tables)],
2157+ },
2158+ cxn_is_master => {
2159+ text => <<' END',
2160+ return $set->{master_file} ? 1 : 0;
2161+ END
2162+ note => 'Removes servers that are not masters',
2163+ tbls => [qw(master_status)],
2164+ },
2165+ cxn_is_slave => {
2166+ text => <<' END',
2167+ return $set->{master_host} ? 1 : 0;
2168+ END
2169+ note => 'Removes servers that are not slaves',
2170+ tbls => [qw(slave_io_status slave_sql_status)],
2171+ },
2172+ thd_is_not_waiting => {
2173+ text => <<' END',
2174+ return $set->{thread_status} !~ m#waiting for i/o request#;
2175+ END
2176+ note => 'Removes idle I/O threads',
2177+ tbls => [qw(io_threads)],
2178+ },
2179+);
2180+foreach my $key ( keys %builtin_filters ) {
2181+ my ( $sub, $err ) = compile_filter($builtin_filters{$key}->{text});
2182+ $filters{$key} = {
2183+ func => $sub,
2184+ text => $builtin_filters{$key}->{text},
2185+ user => 0,
2186+ name => $key, # useful for later
2187+ note => $builtin_filters{$key}->{note},
2188+ tbls => $builtin_filters{$key}->{tbls},
2189+ }
2190+}
2191+
2192+# Variable sets {{{3
2193+# Sets (arrayrefs) of variables that are used in S mode. They are read/written to
2194+# the config file.
2195+my %var_sets = (
2196+ general => {
2197+ text => join(
2198+ ', ',
2199+ 'set_precision(Questions/Uptime_hires) as QPS',
2200+ 'set_precision(Com_commit/Uptime_hires) as Commit_PS',
2201+ 'set_precision((Com_rollback||0)/(Com_commit||1)) as Rollback_Commit',
2202+ 'set_precision(('
2203+ . join('+', map { "($_||0)" }
2204+ qw(Com_delete Com_delete_multi Com_insert Com_insert_select Com_replace
2205+ Com_replace_select Com_select Com_update Com_update_multi))
2206+ . ')/(Com_commit||1)) as Write_Commit',
2207+ 'set_precision((Com_select+(Qcache_hits||0))/(('
2208+ . join('+', map { "($_||0)" }
2209+ qw(Com_delete Com_delete_multi Com_insert Com_insert_select Com_replace
2210+ Com_replace_select Com_select Com_update Com_update_multi))
2211+ . ')||1)) as R_W_Ratio',
2212+ 'set_precision(Opened_tables/Uptime_hires) as Opens_PS',
2213+ 'percent($cur->{Open_tables}/($cur->{table_cache})) as Table_Cache_Used',
2214+ 'set_precision(Threads_created/Uptime_hires) as Threads_PS',
2215+ 'percent($cur->{Threads_cached}/($cur->{thread_cache_size}||1)) as Thread_Cache_Used',
2216+ 'percent($cur->{Max_used_connections}/($cur->{max_connections}||1)) as CXN_Used_Ever',
2217+ 'percent($cur->{Threads_connected}/($cur->{max_connections}||1)) as CXN_Used_Now',
2218+ ),
2219+ },
2220+ commands => {
2221+ text => join(
2222+ ', ',
2223+ qw(Uptime Questions Com_delete Com_delete_multi Com_insert
2224+ Com_insert_select Com_replace Com_replace_select Com_select Com_update
2225+ Com_update_multi)
2226+ ),
2227+ },
2228+ query_status => {
2229+ text => join(
2230+ ',',
2231+ qw( Uptime Select_full_join Select_full_range_join Select_range
2232+ Select_range_check Select_scan Slow_queries Sort_merge_passes
2233+ Sort_range Sort_rows Sort_scan)
2234+ ),
2235+ },
2236+ innodb => {
2237+ text => join(
2238+ ',',
2239+ qw( Uptime Innodb_row_lock_current_waits Innodb_row_lock_time
2240+ Innodb_row_lock_time_avg Innodb_row_lock_time_max Innodb_row_lock_waits
2241+ Innodb_rows_deleted Innodb_rows_inserted Innodb_rows_read
2242+ Innodb_rows_updated)
2243+ ),
2244+ },
2245+ txn => {
2246+ text => join(
2247+ ',',
2248+ qw( Uptime Com_begin Com_commit Com_rollback Com_savepoint
2249+ Com_xa_commit Com_xa_end Com_xa_prepare Com_xa_recover Com_xa_rollback
2250+ Com_xa_start)
2251+ ),
2252+ },
2253+ key_cache => {
2254+ text => join(
2255+ ',',
2256+ qw( Uptime Key_blocks_not_flushed Key_blocks_unused Key_blocks_used
2257+ Key_read_requests Key_reads Key_write_requests Key_writes )
2258+ ),
2259+ },
2260+ query_cache => {
2261+ text => join(
2262+ ',',
2263+ "percent($exprs{QcacheHitRatio}) as Hit_Pct",
2264+ 'set_precision((Qcache_hits||0)/(Qcache_inserts||1)) as Hit_Ins',
2265+ 'set_precision((Qcache_lowmem_prunes||0)/Uptime_hires) as Lowmem_Prunes_sec',
2266+ 'percent(1-((Qcache_free_blocks||0)/(Qcache_total_blocks||1))) as Blocks_used',
2267+ qw( Qcache_free_blocks Qcache_free_memory Qcache_not_cached Qcache_queries_in_cache)
2268+ ),
2269+ },
2270+ handler => {
2271+ text => join(
2272+ ',',
2273+ qw( Uptime Handler_read_key Handler_read_first Handler_read_next
2274+ Handler_read_prev Handler_read_rnd Handler_read_rnd_next Handler_delete
2275+ Handler_update Handler_write)
2276+ ),
2277+ },
2278+ cxns_files_threads => {
2279+ text => join(
2280+ ',',
2281+ qw( Uptime Aborted_clients Aborted_connects Bytes_received Bytes_sent
2282+ Compression Connections Created_tmp_disk_tables Created_tmp_files
2283+ Created_tmp_tables Max_used_connections Open_files Open_streams
2284+ Open_tables Opened_tables Table_locks_immediate Table_locks_waited
2285+ Threads_cached Threads_connected Threads_created Threads_running)
2286+ ),
2287+ },
2288+ prep_stmt => {
2289+ text => join(
2290+ ',',
2291+ qw( Uptime Com_dealloc_sql Com_execute_sql Com_prepare_sql Com_reset
2292+ Com_stmt_close Com_stmt_execute Com_stmt_fetch Com_stmt_prepare
2293+ Com_stmt_reset Com_stmt_send_long_data )
2294+ ),
2295+ },
2296+ innodb_health => {
2297+ text => join(
2298+ ',',
2299+ "$exprs{OldVersions} as OldVersions",
2300+ qw(IB_sm_mutex_spin_waits IB_sm_mutex_spin_rounds IB_sm_mutex_os_waits),
2301+ "$exprs{NumTxns} as NumTxns",
2302+ "$exprs{MaxTxnTime} as MaxTxnTime",
2303+ qw(IB_ro_queries_inside IB_ro_queries_in_queue),
2304+ "set_precision($exprs{DirtyBufs} * 100) as dirty_bufs",
2305+ "set_precision($exprs{BufPoolFill} * 100) as buf_fill",
2306+ qw(IB_bp_pages_total IB_bp_pages_read IB_bp_pages_written IB_bp_pages_created)
2307+ ),
2308+ },
2309+ innodb_health2 => {
2310+ text => join(
2311+ ', ',
2312+ 'percent(1-((Innodb_buffer_pool_pages_free||0)/($cur->{Innodb_buffer_pool_pages_total}||1))) as BP_page_cache_usage',
2313+ 'percent(1-((Innodb_buffer_pool_reads||0)/(Innodb_buffer_pool_read_requests||1))) as BP_cache_hit_ratio',
2314+ 'Innodb_buffer_pool_wait_free',
2315+ 'Innodb_log_waits',
2316+ ),
2317+ },
2318+ slow_queries => {
2319+ text => join(
2320+ ', ',
2321+ 'set_precision(Slow_queries/Uptime_hires) as Slow_PS',
2322+ 'set_precision(Select_full_join/Uptime_hires) as Full_Join_PS',
2323+ 'percent(Select_full_join/(Com_select||1)) as Full_Join_Ratio',
2324+ ),
2325+ },
2326+);
2327+
2328+# Server sets {{{3
2329+# Defines sets of servers between which the user can quickly switch.
2330+my %server_groups;
2331+
2332+# Connections {{{3
2333+# This hash defines server connections. Each connection is a string that can be passed to
2334+# the DBI connection. These are saved in the connections section in the config file.
2335+my %connections;
2336+# Defines the parts of connections.
2337+my @conn_parts = qw(user have_user pass have_pass dsn savepass dl_table);
2338+
2339+# Graph widths {{{3
2340+# This hash defines the max values seen for various status/variable values, for graphing.
2341+# These are stored in their own section in the config file. These are just initial values:
2342+my %mvs = (
2343+ Com_select => 50,
2344+ Com_insert => 50,
2345+ Com_update => 50,
2346+ Com_delete => 50,
2347+ Questions => 100,
2348+);
2349+
2350+# ###########################################################################
2351+# Valid Term::ANSIColor color strings.
2352+# ###########################################################################
2353+my %ansicolors = map { $_ => 1 }
2354+ qw( black blink blue bold clear concealed cyan dark green magenta on_black
2355+ on_blue on_cyan on_green on_magenta on_red on_white on_yellow red reset
2356+ reverse underline underscore white yellow);
2357+
2358+# ###########################################################################
2359+# Valid comparison operators for color rules
2360+# ###########################################################################
2361+my %comp_ops = (
2362+ '==' => 'Numeric equality',
2363+ '>' => 'Numeric greater-than',
2364+ '<' => 'Numeric less-than',
2365+ '>=' => 'Numeric greater-than/equal',
2366+ '<=' => 'Numeric less-than/equal',
2367+ '!=' => 'Numeric not-equal',
2368+ 'eq' => 'String equality',
2369+ 'gt' => 'String greater-than',
2370+ 'lt' => 'String less-than',
2371+ 'ge' => 'String greater-than/equal',
2372+ 'le' => 'String less-than/equal',
2373+ 'ne' => 'String not-equal',
2374+ '=~' => 'Pattern match',
2375+ '!~' => 'Negated pattern match',
2376+);
2377+
2378+# ###########################################################################
2379+# Valid aggregate functions.
2380+# ###########################################################################
2381+my %agg_funcs = (
2382+ first => sub {
2383+ return $_[0]
2384+ },
2385+ count => sub {
2386+ return 0 + @_;
2387+ },
2388+ avg => sub {
2389+ my @args = grep { defined $_ } @_;
2390+ return (sum(map { m/([\d\.-]+)/g } @args) || 0) / (scalar(@args) || 1);
2391+ },
2392+ sum => \&sum,
2393+);
2394+
2395+# ###########################################################################
2396+# Valid functions for transformations.
2397+# ###########################################################################
2398+my %trans_funcs = (
2399+ shorten => \&shorten,
2400+ secs_to_time => \&secs_to_time,
2401+ no_ctrl_char => \&no_ctrl_char,
2402+ percent => \&percent,
2403+ commify => \&commify,
2404+ dulint_to_int => \&dulint_to_int,
2405+ set_precision => \&set_precision,
2406+);
2407+
2408+# Table definitions {{{3
2409+# This hash defines every table that can get displayed in every mode. Each
2410+# table specifies columns and column data sources. The column is
2411+# defined by the %columns hash.
2412+#
2413+# Example: foo => { src => 'bar' } means the foo column (look at
2414+# $columns{foo} for its definition) gets its data from the 'bar' element of
2415+# the current data set, whatever that is.
2416+#
2417+# These columns are post-processed after being defined, because they get stuff
2418+# from %columns. After all the config is loaded for columns, there's more
2419+# post-processing too; the subroutines compiled from src get added to
2420+# the hash elements for extract_values to use.
2421+# ###########################################################################
2422+
2423+my %tbl_meta = (
2424+ adaptive_hash_index => {
2425+ capt => 'Adaptive Hash Index',
2426+ cust => {},
2427+ cols => {
2428+ cxn => { src => 'cxn' },
2429+ hash_table_size => { src => 'IB_ib_hash_table_size', trans => [qw(shorten)], },
2430+ used_cells => { src => 'IB_ib_used_cells' },
2431+ bufs_in_node_heap => { src => 'IB_ib_bufs_in_node_heap' },
2432+ hash_searches_s => { src => 'IB_ib_hash_searches_s' },
2433+ non_hash_searches_s => { src => 'IB_ib_non_hash_searches_s' },
2434+ },
2435+ visible => [ qw(cxn hash_table_size used_cells bufs_in_node_heap hash_searches_s non_hash_searches_s) ],
2436+ filters => [],
2437+ sort_cols => 'cxn',
2438+ sort_dir => '1',
2439+ innodb => 'ib',
2440+ group_by => [],
2441+ aggregate => 0,
2442+ },
2443+ buffer_pool => {
2444+ capt => 'Buffer Pool',
2445+ cust => {},
2446+ cols => {
2447+ cxn => { src => 'cxn' },
2448+ total_mem_alloc => { src => 'IB_bp_total_mem_alloc', trans => [qw(shorten)], },
2449+ awe_mem_alloc => { src => 'IB_bp_awe_mem_alloc', trans => [qw(shorten)], },
2450+ add_pool_alloc => { src => 'IB_bp_add_pool_alloc', trans => [qw(shorten)], },
2451+ buf_pool_size => { src => 'IB_bp_buf_pool_size', trans => [qw(shorten)], },
2452+ buf_free => { src => 'IB_bp_buf_free' },
2453+ buf_pool_hit_rate => { src => 'IB_bp_buf_pool_hit_rate' },
2454+ buf_pool_reads => { src => 'IB_bp_buf_pool_reads' },
2455+ buf_pool_hits => { src => 'IB_bp_buf_pool_hits' },
2456+ dict_mem_alloc => { src => 'IB_bp_dict_mem_alloc' },
2457+ pages_total => { src => 'IB_bp_pages_total' },
2458+ pages_modified => { src => 'IB_bp_pages_modified' },
2459+ reads_pending => { src => 'IB_bp_reads_pending' },
2460+ writes_pending => { src => 'IB_bp_writes_pending' },
2461+ writes_pending_lru => { src => 'IB_bp_writes_pending_lru' },
2462+ writes_pending_flush_list => { src => 'IB_bp_writes_pending_flush_list' },
2463+ writes_pending_single_page => { src => 'IB_bp_writes_pending_single_page' },
2464+ page_creates_sec => { src => 'IB_bp_page_creates_sec' },
2465+ page_reads_sec => { src => 'IB_bp_page_reads_sec' },
2466+ page_writes_sec => { src => 'IB_bp_page_writes_sec' },
2467+ pages_created => { src => 'IB_bp_pages_created' },
2468+ pages_read => { src => 'IB_bp_pages_read' },
2469+ pages_written => { src => 'IB_bp_pages_written' },
2470+ },
2471+ visible => [ qw(cxn buf_pool_size buf_free pages_total pages_modified buf_pool_hit_rate total_mem_alloc add_pool_alloc)],
2472+ filters => [],
2473+ sort_cols => 'cxn',
2474+ sort_dir => '1',
2475+ innodb => 'bp',
2476+ group_by => [],
2477+ aggregate => 0,
2478+ },
2479+ # TODO: a new step in set_to_tbl: join result to itself, grouped?
2480+ # TODO: this would also enable pulling Q and T data together.
2481+ # TODO: using a SQL-ish language would also allow pivots to be easier -- treat the pivoted data as a view and SELECT from it.
2482+ cmd_summary => {
2483+ capt => 'Command Summary',
2484+ cust => {},
2485+ cols => {
2486+ name => { src => 'name' },
2487+ total => { src => 'total' },
2488+ value => { src => 'value', agg => 'sum'},
2489+ pct => { src => 'value/total', trans => [qw(percent)] },
2490+ last_total => { src => 'last_total' },
2491+ last_value => { src => 'last_value', agg => 'sum'},
2492+ last_pct => { src => 'last_value/last_total', trans => [qw(percent)] },
2493+ },
2494+ visible => [qw(name value pct last_value last_pct)],
2495+ filters => [qw()],
2496+ sort_cols => '-value',
2497+ sort_dir => '1',
2498+ innodb => '',
2499+ group_by => [qw(name)],
2500+ aggregate => 1,
2501+ },
2502+ deadlock_locks => {
2503+ capt => 'Deadlock Locks',
2504+ cust => {},
2505+ cols => {
2506+ cxn => { src => 'cxn' },
2507+ mysql_thread_id => { src => 'mysql_thread_id' },
2508+ dl_txn_num => { src => 'dl_txn_num' },
2509+ lock_type => { src => 'lock_type' },
2510+ space_id => { src => 'space_id' },
2511+ page_no => { src => 'page_no' },
2512+ heap_no => { src => 'heap_no' },
2513+ n_bits => { src => 'n_bits' },
2514+ index => { src => 'index' },
2515+ db => { src => 'db' },
2516+ tbl => { src => 'table' },
2517+ lock_mode => { src => 'lock_mode' },
2518+ special => { src => 'special' },
2519+ insert_intention => { src => 'insert_intention' },
2520+ waiting => { src => 'waiting' },
2521+ },
2522+ visible => [ qw(cxn mysql_thread_id waiting lock_mode db tbl index special insert_intention)],
2523+ filters => [],
2524+ sort_cols => 'cxn mysql_thread_id',
2525+ sort_dir => '1',
2526+ innodb => 'dl',
2527+ group_by => [],
2528+ aggregate => 0,
2529+ },
2530+ deadlock_transactions => {
2531+ capt => 'Deadlock Transactions',
2532+ cust => {},
2533+ cols => {
2534+ cxn => { src => 'cxn' },
2535+ active_secs => { src => 'active_secs' },
2536+ dl_txn_num => { src => 'dl_txn_num' },
2537+ has_read_view => { src => 'has_read_view' },
2538+ heap_size => { src => 'heap_size' },
2539+ host_and_domain => { src => 'hostname' },
2540+ hostname => { src => $exprs{Host} },
2541+ ip => { src => 'ip' },
2542+ lock_structs => { src => 'lock_structs' },
2543+ lock_wait_time => { src => 'lock_wait_time', trans => [ qw(secs_to_time) ] },
2544+ mysql_thread_id => { src => 'mysql_thread_id' },
2545+ os_thread_id => { src => 'os_thread_id' },
2546+ proc_no => { src => 'proc_no' },
2547+ query_id => { src => 'query_id' },
2548+ query_status => { src => 'query_status' },
2549+ query_text => { src => 'query_text', trans => [ qw(no_ctrl_char) ] },
2550+ row_locks => { src => 'row_locks' },
2551+ tables_in_use => { src => 'tables_in_use' },
2552+ tables_locked => { src => 'tables_locked' },
2553+ thread_decl_inside => { src => 'thread_decl_inside' },
2554+ thread_status => { src => 'thread_status' },
2555+ 'time' => { src => 'active_secs', trans => [ qw(secs_to_time) ] },
2556+ timestring => { src => 'timestring' },
2557+ txn_doesnt_see_ge => { src => 'txn_doesnt_see_ge' },
2558+ txn_id => { src => 'txn_id' },
2559+ txn_sees_lt => { src => 'txn_sees_lt' },
2560+ txn_status => { src => 'txn_status' },
2561+ truncates => { src => 'truncates' },
2562+ undo_log_entries => { src => 'undo_log_entries' },
2563+ user => { src => 'user' },
2564+ victim => { src => 'victim' },
2565+ wait_status => { src => 'lock_wait_status' },
2566+ },
2567+ visible => [ qw(cxn mysql_thread_id timestring user hostname victim time undo_log_entries lock_structs query_text)],
2568+ filters => [],
2569+ sort_cols => 'cxn mysql_thread_id',
2570+ sort_dir => '1',
2571+ innodb => 'dl',
2572+ group_by => [],
2573+ aggregate => 0,
2574+ },
2575+ explain => {
2576+ capt => 'EXPLAIN Results',
2577+ cust => {},
2578+ cols => {
2579+ part_id => { src => 'id' },
2580+ select_type => { src => 'select_type' },
2581+ tbl => { src => 'table' },
2582+ partitions => { src => 'partitions' },
2583+ scan_type => { src => 'type' },
2584+ possible_keys => { src => 'possible_keys' },
2585+ index => { src => 'key' },
2586+ key_len => { src => 'key_len' },
2587+ index_ref => { src => 'ref' },
2588+ num_rows => { src => 'rows' },
2589+ special => { src => 'extra' },
2590+ },
2591+ visible => [ qw(select_type tbl partitions scan_type possible_keys index key_len index_ref num_rows special)],
2592+ filters => [],
2593+ sort_cols => '',
2594+ sort_dir => '1',
2595+ innodb => '',
2596+ group_by => [],
2597+ aggregate => 0,
2598+ },
2599+ file_io_misc => {
2600+ capt => 'File I/O Misc',
2601+ cust => {},
2602+ cols => {
2603+ cxn => { src => 'cxn' },
2604+ io_bytes_s => { src => 'IB_io_avg_bytes_s' },
2605+ io_flush_type => { src => 'IB_io_flush_type' },
2606+ io_fsyncs_s => { src => 'IB_io_fsyncs_s' },
2607+ io_reads_s => { src => 'IB_io_reads_s' },
2608+ io_writes_s => { src => 'IB_io_writes_s' },
2609+ os_file_reads => { src => 'IB_io_os_file_reads' },
2610+ os_file_writes => { src => 'IB_io_os_file_writes' },
2611+ os_fsyncs => { src => 'IB_io_os_fsyncs' },
2612+ },
2613+ visible => [ qw(cxn os_file_reads os_file_writes os_fsyncs io_reads_s io_writes_s io_bytes_s)],
2614+ filters => [],
2615+ sort_cols => 'cxn',
2616+ sort_dir => '1',
2617+ innodb => 'io',
2618+ group_by => [],
2619+ aggregate => 0,
2620+ },
2621+ fk_error => {
2622+ capt => 'Foreign Key Error Info',
2623+ cust => {},
2624+ cols => {
2625+ timestring => { src => 'IB_fk_timestring' },
2626+ child_db => { src => 'IB_fk_child_db' },
2627+ child_table => { src => 'IB_fk_child_table' },
2628+ child_index => { src => 'IB_fk_child_index' },
2629+ fk_name => { src => 'IB_fk_fk_name' },
2630+ parent_db => { src => 'IB_fk_parent_db' },
2631+ parent_table => { src => 'IB_fk_parent_table' },
2632+ parent_col => { src => 'IB_fk_parent_col' },
2633+ parent_index => { src => 'IB_fk_parent_index' },
2634+ attempted_op => { src => 'IB_fk_attempted_op' },
2635+ },
2636+ visible => [ qw(timestring child_db child_table child_index parent_db parent_table parent_col parent_index fk_name attempted_op)],
2637+ filters => [],
2638+ sort_cols => '',
2639+ sort_dir => '1',
2640+ innodb => 'fk',
2641+ group_by => [],
2642+ aggregate => 0,
2643+ },
2644+ insert_buffers => {
2645+ capt => 'Insert Buffers',
2646+ cust => {},
2647+ cols => {
2648+ cxn => { src => 'cxn' },
2649+ inserts => { src => 'IB_ib_inserts' },
2650+ merged_recs => { src => 'IB_ib_merged_recs' },
2651+ merges => { src => 'IB_ib_merges' },
2652+ size => { src => 'IB_ib_size' },
2653+ free_list_len => { src => 'IB_ib_free_list_len' },
2654+ seg_size => { src => 'IB_ib_seg_size' },
2655+ },
2656+ visible => [ qw(cxn inserts merged_recs merges size free_list_len seg_size)],
2657+ filters => [],
2658+ sort_cols => 'cxn',
2659+ sort_dir => '1',
2660+ innodb => 'ib',
2661+ group_by => [],
2662+ aggregate => 0,
2663+ },
2664+ innodb_locks => {
2665+ capt => 'InnoDB Locks',
2666+ cust => {},
2667+ cols => {
2668+ cxn => { src => 'cxn' },
2669+ db => { src => 'db' },
2670+ index => { src => 'index' },
2671+ insert_intention => { src => 'insert_intention' },
2672+ lock_mode => { src => 'lock_mode' },
2673+ lock_type => { src => 'lock_type' },
2674+ lock_wait_time => { src => 'lock_wait_time', trans => [ qw(secs_to_time) ] },
2675+ mysql_thread_id => { src => 'mysql_thread_id' },
2676+ n_bits => { src => 'n_bits' },
2677+ page_no => { src => 'page_no' },
2678+ space_id => { src => 'space_id' },
2679+ special => { src => 'special' },
2680+ tbl => { src => 'table' },
2681+ 'time' => { src => 'active_secs', hdr => 'Active', trans => [ qw(secs_to_time) ] },
2682+ txn_id => { src => 'txn_id' },
2683+ waiting => { src => 'waiting' },
2684+ },
2685+ visible => [ qw(cxn mysql_thread_id lock_type waiting lock_wait_time time lock_mode db tbl index insert_intention special)],
2686+ filters => [],
2687+ sort_cols => 'cxn -lock_wait_time',
2688+ sort_dir => '1',
2689+ innodb => 'tx',
2690+ colors => [
2691+ { col => 'lock_wait_time', op => '>', arg => 60, color => 'red' },
2692+ { col => 'lock_wait_time', op => '>', arg => 30, color => 'yellow' },
2693+ { col => 'lock_wait_time', op => '>', arg => 10, color => 'green' },
2694+ ],
2695+ group_by => [],
2696+ aggregate => 0,
2697+ },
2698+ innodb_transactions => {
2699+ capt => 'InnoDB Transactions',
2700+ cust => {},
2701+ cols => {
2702+ cxn => { src => 'cxn' },
2703+ active_secs => { src => 'active_secs' },
2704+ has_read_view => { src => 'has_read_view' },
2705+ heap_size => { src => 'heap_size' },
2706+ hostname => { src => $exprs{Host} },
2707+ ip => { src => 'ip' },
2708+ wait_status => { src => 'lock_wait_status' },
2709+ lock_wait_time => { src => 'lock_wait_time', trans => [ qw(secs_to_time) ] },
2710+ lock_structs => { src => 'lock_structs' },
2711+ mysql_thread_id => { src => 'mysql_thread_id' },
2712+ os_thread_id => { src => 'os_thread_id' },
2713+ proc_no => { src => 'proc_no' },
2714+ query_id => { src => 'query_id' },
2715+ query_status => { src => 'query_status' },
2716+ query_text => { src => 'query_text', trans => [ qw(no_ctrl_char) ] },
2717+ txn_time_remain => { src => $exprs{TxnTimeRemain}, trans => [ qw(secs_to_time) ] },
2718+ row_locks => { src => 'row_locks' },
2719+ tables_in_use => { src => 'tables_in_use' },
2720+ tables_locked => { src => 'tables_locked' },
2721+ thread_decl_inside => { src => 'thread_decl_inside' },
2722+ thread_status => { src => 'thread_status' },
2723+ 'time' => { src => 'active_secs', trans => [ qw(secs_to_time) ], agg => 'sum' },
2724+ txn_doesnt_see_ge => { src => 'txn_doesnt_see_ge' },
2725+ txn_id => { src => 'txn_id' },
2726+ txn_sees_lt => { src => 'txn_sees_lt' },
2727+ txn_status => { src => 'txn_status', minw => 10, maxw => 10 },
2728+ undo_log_entries => { src => 'undo_log_entries' },
2729+ user => { src => 'user', maxw => 10 },
2730+ cnt => { src => 'mysql_thread_id', minw => 0 },
2731+ },
2732+ visible => [ qw(cxn cnt mysql_thread_id user hostname txn_status time undo_log_entries query_text)],
2733+ filters => [ qw( hide_self hide_inactive ) ],
2734+ sort_cols => '-active_secs txn_status cxn mysql_thread_id',
2735+ sort_dir => '1',
2736+ innodb => 'tx',
2737+ hide_caption => 1,
2738+ colors => [
2739+ { col => 'wait_status', op => 'eq', arg => 'LOCK WAIT', color => 'black on_red' },
2740+ { col => 'time', op => '>', arg => 600, color => 'red' },
2741+ { col => 'time', op => '>', arg => 300, color => 'yellow' },
2742+ { col => 'time', op => '>', arg => 60, color => 'green' },
2743+ { col => 'time', op => '>', arg => 30, color => 'cyan' },
2744+ { col => 'txn_status', op => 'eq', arg => 'not started', color => 'white' },
2745+ ],
2746+ group_by => [ qw(cxn txn_status) ],
2747+ aggregate => 0,
2748+ },
2749+ io_threads => {
2750+ capt => 'I/O Threads',
2751+ cust => {},
2752+ cols => {
2753+ cxn => { src => 'cxn' },
2754+ thread => { src => 'thread' },
2755+ thread_purpose => { src => 'purpose' },
2756+ event_set => { src => 'event_set' },
2757+ thread_status => { src => 'state' },
2758+ },
2759+ visible => [ qw(cxn thread thread_purpose thread_status)],
2760+ filters => [ qw() ],
2761+ sort_cols => 'cxn thread',
2762+ sort_dir => '1',
2763+ innodb => 'io',
2764+ group_by => [],
2765+ aggregate => 0,
2766+ },
2767+ log_statistics => {
2768+ capt => 'Log Statistics',
2769+ cust => {},
2770+ cols => {
2771+ cxn => { src => 'cxn' },
2772+ last_chkp => { src => 'IB_lg_last_chkp' },
2773+ log_flushed_to => { src => 'IB_lg_log_flushed_to' },
2774+ log_ios_done => { src => 'IB_lg_log_ios_done' },
2775+ log_ios_s => { src => 'IB_lg_log_ios_s' },
2776+ log_seq_no => { src => 'IB_lg_log_seq_no' },
2777+ pending_chkp_writes => { src => 'IB_lg_pending_chkp_writes' },
2778+ pending_log_writes => { src => 'IB_lg_pending_log_writes' },
2779+ },
2780+ visible => [ qw(cxn log_seq_no log_flushed_to last_chkp log_ios_done log_ios_s)],
2781+ filters => [],
2782+ sort_cols => 'cxn',
2783+ sort_dir => '1',
2784+ innodb => 'lg',
2785+ group_by => [],
2786+ aggregate => 0,
2787+ },
2788+ master_status => {
2789+ capt => 'Master Status',
2790+ cust => {},
2791+ cols => {
2792+ cxn => { src => 'cxn' },
2793+ binlog_do_db => { src => 'binlog_do_db' },
2794+ binlog_ignore_db => { src => 'binlog_ignore_db' },
2795+ master_file => { src => 'file' },
2796+ master_pos => { src => 'position' },
2797+ binlog_cache_overflow => { src => '(Binlog_cache_disk_use||0)/(Binlog_cache_use||1)', trans => [ qw(percent) ] },
2798+ },
2799+ visible => [ qw(cxn master_file master_pos binlog_cache_overflow)],
2800+ filters => [ qw(cxn_is_master) ],
2801+ sort_cols => 'cxn',
2802+ sort_dir => '1',
2803+ innodb => '',
2804+ group_by => [],
2805+ aggregate => 0,
2806+ },
2807+ pending_io => {
2808+ capt => 'Pending I/O',
2809+ cust => {},
2810+ cols => {
2811+ cxn => { src => 'cxn' },
2812+ p_normal_aio_reads => { src => 'IB_io_pending_normal_aio_reads' },
2813+ p_aio_writes => { src => 'IB_io_pending_aio_writes' },
2814+ p_ibuf_aio_reads => { src => 'IB_io_pending_ibuf_aio_reads' },
2815+ p_sync_ios => { src => 'IB_io_pending_sync_ios' },
2816+ p_buf_pool_flushes => { src => 'IB_io_pending_buffer_pool_flushes' },
2817+ p_log_flushes => { src => 'IB_io_pending_log_flushes' },
2818+ p_log_ios => { src => 'IB_io_pending_log_ios' },
2819+ p_preads => { src => 'IB_io_pending_preads' },
2820+ p_pwrites => { src => 'IB_io_pending_pwrites' },
2821+ },
2822+ visible => [ qw(cxn p_normal_aio_reads p_aio_writes p_ibuf_aio_reads p_sync_ios p_log_flushes p_log_ios)],
2823+ filters => [],
2824+ sort_cols => 'cxn',
2825+ sort_dir => '1',
2826+ innodb => 'io',
2827+ group_by => [],
2828+ aggregate => 0,
2829+ },
2830+ open_tables => {
2831+ capt => 'Open Tables',
2832+ cust => {},
2833+ cols => {
2834+ cxn => { src => 'cxn' },
2835+ db => { src => 'database' },
2836+ tbl => { src => 'table' },
2837+ num_times_open => { src => 'in_use' },
2838+ is_name_locked => { src => 'name_locked' },
2839+ },
2840+ visible => [ qw(cxn db tbl num_times_open is_name_locked)],
2841+ filters => [ qw(table_is_open) ],
2842+ sort_cols => '-num_times_open cxn db tbl',
2843+ sort_dir => '1',
2844+ innodb => '',
2845+ group_by => [],
2846+ aggregate => 0,
2847+ },
2848+ page_statistics => {
2849+ capt => 'Page Statistics',
2850+ cust => {},
2851+ cols => {
2852+ cxn => { src => 'cxn' },
2853+ pages_read => { src => 'IB_bp_pages_read' },
2854+ pages_written => { src => 'IB_bp_pages_written' },
2855+ pages_created => { src => 'IB_bp_pages_created' },
2856+ page_reads_sec => { src => 'IB_bp_page_reads_sec' },
2857+ page_writes_sec => { src => 'IB_bp_page_writes_sec' },
2858+ page_creates_sec => { src => 'IB_bp_page_creates_sec' },
2859+ },
2860+ visible => [ qw(cxn pages_read pages_written pages_created page_reads_sec page_writes_sec page_creates_sec)],
2861+ filters => [],
2862+ sort_cols => 'cxn',
2863+ sort_dir => '1',
2864+ innodb => 'bp',
2865+ group_by => [],
2866+ aggregate => 0,
2867+ },
2868+ processlist => {
2869+ capt => 'MySQL Process List',
2870+ cust => {},
2871+ cols => {
2872+ cxn => { src => 'cxn', minw => 6, maxw => 10 },
2873+ mysql_thread_id => { src => 'id', minw => 6, maxw => 0 },
2874+ user => { src => 'user', minw => 5, maxw => 8 },
2875+ hostname => { src => $exprs{Host}, minw => 13, maxw => 8, },
2876+ port => { src => $exprs{Port}, minw => 0, maxw => 0, },
2877+ host_and_port => { src => 'host', minw => 0, maxw => 0 },
2878+ db => { src => 'db', minw => 6, maxw => 12 },
2879+ cmd => { src => 'command', minw => 5, maxw => 0 },
2880+ time => { src => 'time', minw => 5, maxw => 0, trans => [ qw(secs_to_time) ], agg => 'sum' },
2881+ state => { src => 'state', minw => 0, maxw => 0 },
2882+ info => { src => 'info', minw => 0, maxw => 0, trans => [ qw(no_ctrl_char) ] },
2883+ cnt => { src => 'id', minw => 0, maxw => 0 },
2884+ },
2885+ visible => [ qw(cxn cmd cnt mysql_thread_id user hostname db time info)],
2886+ filters => [ qw(hide_self hide_inactive hide_slave_io) ],
2887+ sort_cols => '-time cxn hostname mysql_thread_id',
2888+ sort_dir => '1',
2889+ innodb => '',
2890+ hide_caption => 1,
2891+ colors => [
2892+ { col => 'state', op => 'eq', arg => 'Locked', color => 'black on_red' },
2893+ { col => 'cmd', op => 'eq', arg => 'Sleep', color => 'white' },
2894+ { col => 'user', op => 'eq', arg => 'system user', color => 'white' },
2895+ { col => 'cmd', op => 'eq', arg => 'Connect', color => 'white' },
2896+ { col => 'cmd', op => 'eq', arg => 'Binlog Dump', color => 'white' },
2897+ { col => 'time', op => '>', arg => 600, color => 'red' },
2898+ { col => 'time', op => '>', arg => 120, color => 'yellow' },
2899+ { col => 'time', op => '>', arg => 60, color => 'green' },
2900+ { col => 'time', op => '>', arg => 30, color => 'cyan' },
2901+ ],
2902+ group_by => [qw(cxn cmd)],
2903+ aggregate => 0,
2904+ },
2905+
2906+ # TODO: some more columns:
2907+ # kb_used=hdr='BufUsed' minw='0' num='0' src='percent(1 - ((Key_blocks_unused * key_cache_block_size) / (key_buffer_size||1)))' dec='0' trans='' tbl='q_header' just='-' user='1' maxw='0' label='User-defined'
2908+ # retries=hdr='Retries' minw='0' num='0' src='Slave_retried_transactions' dec='0' trans='' tbl='slave_sql_status' just='-' user='1' maxw='0' label='User-defined'
2909+ # thd=hdr='Thd' minw='0' num='0' src='Threads_connected' dec='0' trans='' tbl='slave_sql_status' just='-' user='1' maxw='0' label='User-defined'
2910+
2911+ q_header => {
2912+ capt => 'Q-mode Header',
2913+ cust => {},
2914+ cols => {
2915+ cxn => { src => 'cxn' },
2916+ questions => { src => 'Questions' },
2917+ qps => { src => 'Questions/Uptime_hires', dec => 1, trans => [qw(shorten)] },
2918+ load => { src => $exprs{ServerLoad}, dec => 1, trans => [qw(shorten)] },
2919+ slow => { src => 'Slow_queries', dec => 1, trans => [qw(shorten)] },
2920+ q_cache_hit => { src => $exprs{QcacheHitRatio}, dec => 1, trans => [qw(percent)] },
2921+ key_buffer_hit => { src => '1-(Key_reads/(Key_read_requests||1))', dec => 1, trans => [qw(percent)] },
2922+ bps_in => { src => 'Bytes_received/Uptime_hires', dec => 1, trans => [qw(shorten)] },
2923+ bps_out => { src => 'Bytes_sent/Uptime_hires', dec => 1, trans => [qw(shorten)] },
2924+ when => { src => 'when' },
2925+ },
2926+ visible => [ qw(cxn when load qps slow q_cache_hit key_buffer_hit bps_in bps_out)],
2927+ filters => [],
2928+ sort_cols => 'when cxn',
2929+ sort_dir => '1',
2930+ innodb => '',
2931+ hide_caption => 1,
2932+ group_by => [],
2933+ aggregate => 0,
2934+ },
2935+ row_operations => {
2936+ capt => 'InnoDB Row Operations',
2937+ cust => {},
2938+ cols => {
2939+ cxn => { src => 'cxn' },
2940+ num_inserts => { src => 'IB_ro_num_rows_ins' },
2941+ num_updates => { src => 'IB_ro_num_rows_upd' },
2942+ num_reads => { src => 'IB_ro_num_rows_read' },
2943+ num_deletes => { src => 'IB_ro_num_rows_del' },
2944+ num_inserts_sec => { src => 'IB_ro_ins_sec' },
2945+ num_updates_sec => { src => 'IB_ro_upd_sec' },
2946+ num_reads_sec => { src => 'IB_ro_read_sec' },
2947+ num_deletes_sec => { src => 'IB_ro_del_sec' },
2948+ },
2949+ visible => [ qw(cxn num_inserts num_updates num_reads num_deletes num_inserts_sec
2950+ num_updates_sec num_reads_sec num_deletes_sec)],
2951+ filters => [],
2952+ sort_cols => 'cxn',
2953+ sort_dir => '1',
2954+ innodb => 'ro',
2955+ group_by => [],
2956+ aggregate => 0,
2957+ },
2958+ row_operation_misc => {
2959+ capt => 'Row Operation Misc',
2960+ cust => {},
2961+ cols => {
2962+ cxn => { src => 'cxn' },
2963+ queries_in_queue => { src => 'IB_ro_queries_in_queue' },
2964+ queries_inside => { src => 'IB_ro_queries_inside' },
2965+ read_views_open => { src => 'IB_ro_read_views_open' },
2966+ main_thread_id => { src => 'IB_ro_main_thread_id' },
2967+ main_thread_proc_no => { src => 'IB_ro_main_thread_proc_no' },
2968+ main_thread_state => { src => 'IB_ro_main_thread_state' },
2969+ num_res_ext => { src => 'IB_ro_n_reserved_extents' },
2970+ },
2971+ visible => [ qw(cxn queries_in_queue queries_inside read_views_open main_thread_state)],
2972+ filters => [],
2973+ sort_cols => 'cxn',
2974+ sort_dir => '1',
2975+ innodb => 'ro',
2976+ group_by => [],
2977+ aggregate => 0,
2978+ },
2979+ semaphores => {
2980+ capt => 'InnoDB Semaphores',
2981+ cust => {},
2982+ cols => {
2983+ cxn => { src => 'cxn' },
2984+ mutex_os_waits => { src => 'IB_sm_mutex_os_waits' },
2985+ mutex_spin_rounds => { src => 'IB_sm_mutex_spin_rounds' },
2986+ mutex_spin_waits => { src => 'IB_sm_mutex_spin_waits' },
2987+ reservation_count => { src => 'IB_sm_reservation_count' },
2988+ rw_excl_os_waits => { src => 'IB_sm_rw_excl_os_waits' },
2989+ rw_excl_spins => { src => 'IB_sm_rw_excl_spins' },
2990+ rw_shared_os_waits => { src => 'IB_sm_rw_shared_os_waits' },
2991+ rw_shared_spins => { src => 'IB_sm_rw_shared_spins' },
2992+ signal_count => { src => 'IB_sm_signal_count' },
2993+ wait_array_size => { src => 'IB_sm_wait_array_size' },
2994+ },
2995+ visible => [ qw(cxn mutex_os_waits mutex_spin_waits mutex_spin_rounds
2996+ rw_excl_os_waits rw_excl_spins rw_shared_os_waits rw_shared_spins
2997+ signal_count reservation_count )],
2998+ filters => [],
2999+ sort_cols => 'cxn',
3000+ sort_dir => '1',
3001+ innodb => 'sm',
3002+ group_by => [],
3003+ aggregate => 0,
3004+ },
3005+ slave_io_status => {
3006+ capt => 'Slave I/O Status',
3007+ cust => {},
3008+ cols => {
3009+ cxn => { src => 'cxn' },
3010+ connect_retry => { src => 'connect_retry' },
3011+ master_host => { src => 'master_host', hdr => 'Master'},
3012+ master_log_file => { src => 'master_log_file', hdr => 'File' },
3013+ master_port => { src => 'master_port' },
3014+ master_ssl_allowed => { src => 'master_ssl_allowed' },
3015+ master_ssl_ca_file => { src => 'master_ssl_ca_file' },
3016+ master_ssl_ca_path => { src => 'master_ssl_ca_path' },
3017+ master_ssl_cert => { src => 'master_ssl_cert' },
3018+ master_ssl_cipher => { src => 'master_ssl_cipher' },
3019+ master_ssl_key => { src => 'master_ssl_key' },
3020+ master_user => { src => 'master_user' },
3021+ read_master_log_pos => { src => 'read_master_log_pos', hdr => 'Pos' },
3022+ relay_log_size => { src => 'relay_log_space', trans => [qw(shorten)] },
3023+ slave_io_running => { src => 'slave_io_running', hdr => 'On?' },
3024+ slave_io_state => { src => 'slave_io_state', hdr => 'State' },
3025+ },
3026+ visible => [ qw(cxn master_host slave_io_running master_log_file relay_log_size read_master_log_pos slave_io_state)],
3027+ filters => [ qw( cxn_is_slave ) ],
3028+ sort_cols => 'slave_io_running cxn',
3029+ colors => [
3030+ { col => 'slave_io_running', op => 'ne', arg => 'Yes', color => 'black on_red' },
3031+ ],
3032+ sort_dir => '1',
3033+ innodb => '',
3034+ group_by => [],
3035+ aggregate => 0,
3036+ },
3037+ slave_sql_status => {
3038+ capt => 'Slave SQL Status',
3039+ cust => {},
3040+ cols => {
3041+ cxn => { src => 'cxn' },
3042+ exec_master_log_pos => { src => 'exec_master_log_pos', hdr => 'Master Pos' },
3043+ last_errno => { src => 'last_errno' },
3044+ last_error => { src => 'last_error' },
3045+ master_host => { src => 'master_host', hdr => 'Master' },
3046+ relay_log_file => { src => 'relay_log_file' },
3047+ relay_log_pos => { src => 'relay_log_pos' },
3048+ relay_log_size => { src => 'relay_log_space', trans => [qw(shorten)] },
3049+ relay_master_log_file => { src => 'relay_master_log_file', hdr => 'Master File' },
3050+ replicate_do_db => { src => 'replicate_do_db' },
3051+ replicate_do_table => { src => 'replicate_do_table' },
3052+ replicate_ignore_db => { src => 'replicate_ignore_db' },
3053+ replicate_ignore_table => { src => 'replicate_ignore_table' },
3054+ replicate_wild_do_table => { src => 'replicate_wild_do_table' },
3055+ replicate_wild_ignore_table => { src => 'replicate_wild_ignore_table' },
3056+ skip_counter => { src => 'skip_counter' },
3057+ slave_sql_running => { src => 'slave_sql_running', hdr => 'On?' },
3058+ until_condition => { src => 'until_condition' },
3059+ until_log_file => { src => 'until_log_file' },
3060+ until_log_pos => { src => 'until_log_pos' },
3061+ time_behind_master => { src => 'seconds_behind_master', trans => [ qw(secs_to_time) ] },
3062+ 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)] },
3063+ slave_catchup_rate => { src => $exprs{SlaveCatchupRate}, trans => [ qw(set_precision) ] },
3064+ slave_open_temp_tables => { src => 'Slave_open_temp_tables' },
3065+ },
3066+ visible => [ qw(cxn master_host slave_sql_running time_behind_master slave_catchup_rate slave_open_temp_tables relay_log_pos last_error)],
3067+ filters => [ qw( cxn_is_slave ) ],
3068+ sort_cols => 'slave_sql_running cxn',
3069+ sort_dir => '1',
3070+ innodb => '',
3071+ colors => [
3072+ { col => 'slave_sql_running', op => 'ne', arg => 'Yes', color => 'black on_red' },
3073+ { col => 'time_behind_master', op => '>', arg => 600, color => 'red' },
3074+ { col => 'time_behind_master', op => '>', arg => 60, color => 'yellow' },
3075+ { col => 'time_behind_master', op => '==', arg => 0, color => 'white' },
3076+ ],
3077+ group_by => [],
3078+ aggregate => 0,
3079+ },
3080+ t_header => {
3081+ capt => 'T-Mode Header',
3082+ cust => {},
3083+ cols => {
3084+ cxn => { src => 'cxn' },
3085+ dirty_bufs => { src => $exprs{DirtyBufs}, trans => [qw(percent)] },
3086+ history_list_len => { src => 'IB_tx_history_list_len' },
3087+ lock_structs => { src => 'IB_tx_num_lock_structs' },
3088+ num_txns => { src => $exprs{NumTxns} },
3089+ max_txn => { src => $exprs{MaxTxnTime}, trans => [qw(secs_to_time)] },
3090+ undo_for => { src => 'IB_tx_purge_undo_for' },
3091+ used_bufs => { src => $exprs{BufPoolFill}, trans => [qw(percent)]},
3092+ versions => { src => $exprs{OldVersions} },
3093+ },
3094+ visible => [ qw(cxn history_list_len versions undo_for dirty_bufs used_bufs num_txns max_txn lock_structs)],
3095+ filters => [ ],
3096+ sort_cols => 'cxn',
3097+ sort_dir => '1',
3098+ innodb => '',
3099+ colors => [],
3100+ hide_caption => 1,
3101+ group_by => [],
3102+ aggregate => 0,
3103+ },
3104+ var_status => {
3105+ capt => 'Variables & Status',
3106+ cust => {},
3107+ cols => {}, # Generated from current varset
3108+ visible => [], # Generated from current varset
3109+ filters => [],
3110+ sort_cols => '',
3111+ sort_dir => 1,
3112+ innodb => '',
3113+ temp => 1, # Do not persist to config file.
3114+ hide_caption => 1,
3115+ pivot => 0,
3116+ group_by => [],
3117+ aggregate => 0,
3118+ },
3119+ wait_array => {
3120+ capt => 'InnoDB Wait Array',
3121+ cust => {},
3122+ cols => {
3123+ cxn => { src => 'cxn' },
3124+ thread => { src => 'thread' },
3125+ waited_at_filename => { src => 'waited_at_filename' },
3126+ waited_at_line => { src => 'waited_at_line' },
3127+ 'time' => { src => 'waited_secs', trans => [ qw(secs_to_time) ] },
3128+ request_type => { src => 'request_type' },
3129+ lock_mem_addr => { src => 'lock_mem_addr' },
3130+ lock_cfile_name => { src => 'lock_cfile_name' },
3131+ lock_cline => { src => 'lock_cline' },
3132+ writer_thread => { src => 'writer_thread' },
3133+ writer_lock_mode => { src => 'writer_lock_mode' },
3134+ num_readers => { src => 'num_readers' },
3135+ lock_var => { src => 'lock_var' },
3136+ waiters_flag => { src => 'waiters_flag' },
3137+ last_s_file_name => { src => 'last_s_file_name' },
3138+ last_s_line => { src => 'last_s_line' },
3139+ last_x_file_name => { src => 'last_x_file_name' },
3140+ last_x_line => { src => 'last_x_line' },
3141+ cell_waiting => { src => 'cell_waiting' },
3142+ cell_event_set => { src => 'cell_event_set' },
3143+ },
3144+ visible => [ qw(cxn thread time waited_at_filename waited_at_line request_type num_readers lock_var waiters_flag cell_waiting cell_event_set)],
3145+ filters => [],
3146+ sort_cols => 'cxn -time',
3147+ sort_dir => '1',
3148+ innodb => 'sm',
3149+ group_by => [],
3150+ aggregate => 0,
3151+ },
3152+);
3153+
3154+# Initialize %tbl_meta from %columns and do some checks.
3155+foreach my $table_name ( keys %tbl_meta ) {
3156+ my $table = $tbl_meta{$table_name};
3157+ my $cols = $table->{cols};
3158+
3159+ foreach my $col_name ( keys %$cols ) {
3160+ my $col_def = $table->{cols}->{$col_name};
3161+ die "I can't find a column named '$col_name' for '$table_name'" unless $columns{$col_name};
3162+ $columns{$col_name}->{referenced} = 1;
3163+
3164+ foreach my $prop ( keys %col_props ) {
3165+ # Each column gets non-existing values set from %columns or defaults from %col_props.
3166+ if ( !$col_def->{$prop} ) {
3167+ $col_def->{$prop}
3168+ = defined($columns{$col_name}->{$prop})
3169+ ? $columns{$col_name}->{$prop}
3170+ : $col_props{$prop};
3171+ }
3172+ }
3173+
3174+ # Ensure transformations and aggregate functions are valid
3175+ die "Unknown aggregate function '$col_def->{agg}' "
3176+ . "for column '$col_name' in table '$table_name'"
3177+ unless exists $agg_funcs{$col_def->{agg}};
3178+ foreach my $trans ( @{$col_def->{trans}} ) {
3179+ die "Unknown transformation '$trans' "
3180+ . "for column '$col_name' in table '$table_name'"
3181+ unless exists $trans_funcs{$trans};
3182+ }
3183+ }
3184+
3185+ # Ensure each column in visible and group_by exists in cols
3186+ foreach my $place ( qw(visible group_by) ) {
3187+ foreach my $col_name ( @{$table->{$place}} ) {
3188+ if ( !exists $cols->{$col_name} ) {
3189+ die "Column '$col_name' is listed in '$place' for '$table_name', but doesn't exist";
3190+ }
3191+ }
3192+ }
3193+
3194+ # Compile sort and color subroutines
3195+ $table->{sort_func} = make_sort_func($table);
3196+ $table->{color_func} = make_color_func($table);
3197+}
3198+
3199+# This is for code cleanup:
3200+{
3201+ my @unused_cols = grep { !$columns{$_}->{referenced} } sort keys %columns;
3202+ if ( @unused_cols ) {
3203+ die "The following columns are not used: "
3204+ . join(' ', @unused_cols);
3205+ }
3206+}
3207+
3208+# ###########################################################################
3209+# Operating modes {{{3
3210+# ###########################################################################
3211+my %modes = (
3212+ B => {
3213+ hdr => 'InnoDB Buffers',
3214+ cust => {},
3215+ note => 'Shows buffer info from InnoDB',
3216+ action_for => {
3217+ i => {
3218+ action => sub { toggle_config('status_inc') },
3219+ label => 'Toggle incremental status display',
3220+ },
3221+ },
3222+ display_sub => \&display_B,
3223+ connections => [],
3224+ server_group => '',
3225+ one_connection => 0,
3226+ tables => [qw(buffer_pool page_statistics insert_buffers adaptive_hash_index)],
3227+ visible_tables => [qw(buffer_pool page_statistics insert_buffers adaptive_hash_index)],
3228+ },
3229+ C => {
3230+ hdr => 'Command Summary',
3231+ cust => {},
3232+ note => 'Shows relative magnitude of variables',
3233+ action_for => {
3234+ s => {
3235+ action => sub { get_config_interactive('cmd_filter') },
3236+ label => 'Choose variable prefix',
3237+ },
3238+ },
3239+ display_sub => \&display_C,
3240+ connections => [],
3241+ server_group => '',
3242+ one_connection => 0,
3243+ tables => [qw(cmd_summary)],
3244+ visible_tables => [qw(cmd_summary)],
3245+ },
3246+ D => {
3247+ hdr => 'InnoDB Deadlocks',
3248+ cust => {},
3249+ note => 'View InnoDB deadlock information',
3250+ action_for => {
3251+ c => {
3252+ action => sub { edit_table('deadlock_transactions') },
3253+ label => 'Choose visible columns',
3254+ },
3255+ w => {
3256+ action => \&create_deadlock,
3257+ label => 'Wipe deadlock status info by creating a deadlock',
3258+ },
3259+ },
3260+ display_sub => \&display_D,
3261+ connections => [],
3262+ server_group => '',
3263+ one_connection => 0,
3264+ tables => [qw(deadlock_transactions deadlock_locks)],
3265+ visible_tables => [qw(deadlock_transactions deadlock_locks)],
3266+ },
3267+ F => {
3268+ hdr => 'InnoDB FK Err',
3269+ cust => {},
3270+ note => 'View the latest InnoDB foreign key error',
3271+ action_for => {},
3272+ display_sub => \&display_F,
3273+ connections => [],
3274+ server_group => '',
3275+ one_connection => 1,
3276+ tables => [qw(fk_error)],
3277+ visible_tables => [qw(fk_error)],
3278+ },
3279+ I => {
3280+ hdr => 'InnoDB I/O Info',
3281+ cust => {},
3282+ note => 'Shows I/O info (i/o, log...) from InnoDB',
3283+ action_for => {
3284+ i => {
3285+ action => sub { toggle_config('status_inc') },
3286+ label => 'Toggle incremental status display',
3287+ },
3288+ },
3289+ display_sub => \&display_I,
3290+ connections => [],
3291+ server_group => '',
3292+ one_connection => 0,
3293+ tables => [qw(io_threads pending_io file_io_misc log_statistics)],
3294+ visible_tables => [qw(io_threads pending_io file_io_misc log_statistics)],
3295+ },
3296+ L => {
3297+ hdr => 'Locks',
3298+ cust => {},
3299+ note => 'Shows transaction locks',
3300+ action_for => {
3301+ a => {
3302+ action => sub { send_cmd_to_servers('CREATE TABLE IF NOT EXISTS test.innodb_lock_monitor(a int) ENGINE=InnoDB', 0, '', []); },
3303+ label => 'Start the InnoDB Lock Monitor',
3304+ },
3305+ o => {
3306+ action => sub { send_cmd_to_servers('DROP TABLE IF EXISTS test.innodb_lock_monitor', 0, '', []); },
3307+ label => 'Stop the InnoDB Lock Monitor',
3308+ },
3309+ },
3310+ display_sub => \&display_L,
3311+ connections => [],
3312+ server_group => '',
3313+ one_connection => 0,
3314+ tables => [qw(innodb_locks)],
3315+ visible_tables => [qw(innodb_locks)],
3316+ },
3317+ M => {
3318+ hdr => 'Replication Status',
3319+ cust => {},
3320+ note => 'Shows replication (master and slave) status',
3321+ action_for => {
3322+ a => {
3323+ action => sub { send_cmd_to_servers('START SLAVE', 0, 'START SLAVE SQL_THREAD UNTIL MASTER_LOG_FILE = ?, MASTER_LOG_POS = ?', []); },
3324+ label => 'Start slave(s)',
3325+ },
3326+ i => {
3327+ action => sub { toggle_config('status_inc') },
3328+ label => 'Toggle incremental status display',
3329+ },
3330+ o => {
3331+ action => sub { send_cmd_to_servers('STOP SLAVE', 0, '', []); },
3332+ label => 'Stop slave(s)',
3333+ },
3334+ b => {
3335+ action => sub { purge_master_logs() },
3336+ label => 'Purge unused master logs',
3337+ },
3338+ },
3339+ display_sub => \&display_M,
3340+ connections => [],
3341+ server_group => '',
3342+ one_connection => 0,
3343+ tables => [qw(slave_sql_status slave_io_status master_status)],
3344+ visible_tables => [qw(slave_sql_status slave_io_status master_status)],
3345+ },
3346+ O => {
3347+ hdr => 'Open Tables',
3348+ cust => {},
3349+ note => 'Shows open tables in MySQL',
3350+ action_for => {
3351+ r => {
3352+ action => sub { reverse_sort('open_tables'); },
3353+ label => 'Reverse sort order',
3354+ },
3355+ s => {
3356+ action => sub { choose_sort_cols('open_tables'); },
3357+ label => "Choose sort column",
3358+ },
3359+ },
3360+ display_sub => \&display_O,
3361+ connections => [],
3362+ server_group => '',
3363+ one_connection => 0,
3364+ tables => [qw(open_tables)],
3365+ visible_tables => [qw(open_tables)],
3366+ },
3367+ Q => {
3368+ hdr => 'Query List',
3369+ cust => {},
3370+ note => 'Shows queries from SHOW FULL PROCESSLIST',
3371+ action_for => {
3372+ a => {
3373+ action => sub { toggle_filter('processlist', 'hide_self') },
3374+ label => 'Toggle the innotop process',
3375+ },
3376+ c => {
3377+ action => sub { edit_table('processlist') },
3378+ label => 'Choose visible columns',
3379+ },
3380+ e => {
3381+ action => sub { analyze_query('e'); },
3382+ label => "Explain a thread's query",
3383+ },
3384+ f => {
3385+ action => sub { analyze_query('f'); },
3386+ label => "Show a thread's full query",
3387+ },
3388+ h => {
3389+ action => sub { toggle_visible_table('Q', 'q_header') },
3390+ label => 'Toggle the header on and off',
3391+ },
3392+ i => {
3393+ action => sub { toggle_filter('processlist', 'hide_inactive') },
3394+ label => 'Toggle idle processes',
3395+ },
3396+ k => {
3397+ action => sub { kill_query('CONNECTION') },
3398+ label => "Kill a query's connection",
3399+ },
3400+ r => {
3401+ action => sub { reverse_sort('processlist'); },
3402+ label => 'Reverse sort order',
3403+ },
3404+ s => {
3405+ action => sub { choose_sort_cols('processlist'); },
3406+ label => "Change the display's sort column",
3407+ },
3408+ x => {
3409+ action => sub { kill_query('QUERY') },
3410+ label => "Kill a query",
3411+ },
3412+ },
3413+ display_sub => \&display_Q,
3414+ connections => [],
3415+ server_group => '',
3416+ one_connection => 0,
3417+ tables => [qw(q_header processlist)],
3418+ visible_tables => [qw(q_header processlist)],
3419+ },
3420+ R => {
3421+ hdr => 'InnoDB Row Ops',
3422+ cust => {},
3423+ note => 'Shows InnoDB row operation and semaphore info',
3424+ action_for => {
3425+ i => {
3426+ action => sub { toggle_config('status_inc') },
3427+ label => 'Toggle incremental status display',
3428+ },
3429+ },
3430+ display_sub => \&display_R,
3431+ connections => [],
3432+ server_group => '',
3433+ one_connection => 0,
3434+ tables => [qw(row_operations row_operation_misc semaphores wait_array)],
3435+ visible_tables => [qw(row_operations row_operation_misc semaphores wait_array)],
3436+ },
3437+ S => {
3438+ hdr => 'Variables & Status',
3439+ cust => {},
3440+ note => 'Shows query load statistics a la vmstat',
3441+ action_for => {
3442+ '>' => {
3443+ action => sub { switch_var_set('S_set', 1) },
3444+ label => 'Switch to next variable set',
3445+ },
3446+ '<' => {
3447+ action => sub { switch_var_set('S_set', -1) },
3448+ label => 'Switch to prev variable set',
3449+ },
3450+ c => {
3451+ action => sub {
3452+ choose_var_set('S_set');
3453+ start_S_mode();
3454+ },
3455+ label => "Choose which set to display",
3456+ },
3457+ e => {
3458+ action => \&edit_current_var_set,
3459+ label => 'Edit the current set of variables',
3460+ },
3461+ i => {
3462+ action => sub { $clear_screen_sub->(); toggle_config('status_inc') },
3463+ label => 'Toggle incremental status display',
3464+ },
3465+ '-' => {
3466+ action => sub { set_display_precision(-1) },
3467+ label => 'Decrease fractional display precision',
3468+ },
3469+ '+' => {
3470+ action => sub { set_display_precision(1) },
3471+ label => 'Increase fractional display precision',
3472+ },
3473+ g => {
3474+ action => sub { set_s_mode('g') },
3475+ label => 'Switch to graph (tload) view',
3476+ },
3477+ s => {
3478+ action => sub { set_s_mode('s') },
3479+ label => 'Switch to standard (vmstat) view',
3480+ },
3481+ v => {
3482+ action => sub { set_s_mode('v') },
3483+ label => 'Switch to pivoted view',
3484+ },
3485+ },
3486+ display_sub => \&display_S,
3487+ no_clear_screen => 1,
3488+ connections => [],
3489+ server_group => '',
3490+ one_connection => 0,
3491+ tables => [qw(var_status)],
3492+ visible_tables => [qw(var_status)],
3493+ },
3494+ T => {
3495+ hdr => 'InnoDB Txns',
3496+ cust => {},
3497+ note => 'Shows InnoDB transactions in top-like format',
3498+ action_for => {
3499+ a => {
3500+ action => sub { toggle_filter('innodb_transactions', 'hide_self') },
3501+ label => 'Toggle the innotop process',
3502+ },
3503+ c => {
3504+ action => sub { edit_table('innodb_transactions') },
3505+ label => 'Choose visible columns',
3506+ },
3507+ e => {
3508+ action => sub { analyze_query('e'); },
3509+ label => "Explain a thread's query",
3510+ },
3511+ f => {
3512+ action => sub { analyze_query('f'); },
3513+ label => "Show a thread's full query",
3514+ },
3515+ h => {
3516+ action => sub { toggle_visible_table('T', 't_header') },
3517+ label => 'Toggle the header on and off',
3518+ },
3519+ i => {
3520+ action => sub { toggle_filter('innodb_transactions', 'hide_inactive') },
3521+ label => 'Toggle inactive transactions',
3522+ },
3523+ k => {
3524+ action => sub { kill_query('CONNECTION') },
3525+ label => "Kill a transaction's connection",
3526+ },
3527+ r => {
3528+ action => sub { reverse_sort('innodb_transactions'); },
3529+ label => 'Reverse sort order',
3530+ },
3531+ s => {
3532+ action => sub { choose_sort_cols('innodb_transactions'); },
3533+ label => "Change the display's sort column",
3534+ },
3535+ x => {
3536+ action => sub { kill_query('QUERY') },
3537+ label => "Kill a query",
3538+ },
3539+ },
3540+ display_sub => \&display_T,
3541+ connections => [],
3542+ server_group => '',
3543+ one_connection => 0,
3544+ tables => [qw(t_header innodb_transactions)],
3545+ visible_tables => [qw(t_header innodb_transactions)],
3546+ },
3547+);
3548+
3549+# ###########################################################################
3550+# Global key mappings {{{3
3551+# Keyed on a single character, which is read from the keyboard. Uppercase
3552+# letters switch modes. Lowercase letters access commands when in a mode.
3553+# These can be overridden by action_for in %modes.
3554+# ###########################################################################
3555+my %action_for = (
3556+ '$' => {
3557+ action => \&edit_configuration,
3558+ label => 'Edit configuration settings',
3559+ },
3560+ '?' => {
3561+ action => \&display_help,
3562+ label => 'Show help',
3563+ },
3564+ '!' => {
3565+ action => \&display_license,
3566+ label => 'Show license and warranty',
3567+ },
3568+ '^' => {
3569+ action => \&edit_table,
3570+ label => "Edit the displayed table(s)",
3571+ },
3572+ '#' => {
3573+ action => \&choose_server_groups,
3574+ label => 'Select/create server groups',
3575+ },
3576+ '@' => {
3577+ action => \&choose_servers,
3578+ label => 'Select/create server connections',
3579+ },
3580+ '/' => {
3581+ action => \&add_quick_filter,
3582+ label => 'Quickly filter what you see',
3583+ },
3584+ '\\' => {
3585+ action => \&clear_quick_filters,
3586+ label => 'Clear quick-filters',
3587+ },
3588+ '%' => {
3589+ action => \&choose_filters,
3590+ label => 'Choose and edit table filters',
3591+ },
3592+ "\t" => {
3593+ action => \&next_server_group,
3594+ label => 'Switch to the next server group',
3595+ key => 'TAB',
3596+ },
3597+ '=' => {
3598+ action => \&toggle_aggregate,
3599+ label => 'Toggle aggregation',
3600+ },
3601+ # TODO: can these be auto-generated from %modes?
3602+ B => {
3603+ action => sub { switch_mode('B') },
3604+ label => '',
3605+ },
3606+ C => {
3607+ action => sub { switch_mode('C') },
3608+ label => '',
3609+ },
3610+ D => {
3611+ action => sub { switch_mode('D') },
3612+ label => '',
3613+ },
3614+ F => {
3615+ action => sub { switch_mode('F') },
3616+ label => '',
3617+ },
3618+ I => {
3619+ action => sub { switch_mode('I') },
3620+ label => '',
3621+ },
3622+ L => {
3623+ action => sub { switch_mode('L') },
3624+ label => '',
3625+ },
3626+ M => {
3627+ action => sub { switch_mode('M') },
3628+ label => '',
3629+ },
3630+ O => {
3631+ action => sub { switch_mode('O') },
3632+ label => '',
3633+ },
3634+ Q => {
3635+ action => sub { switch_mode('Q') },
3636+ label => '',
3637+ },
3638+ R => {
3639+ action => sub { switch_mode('R') },
3640+ label => '',
3641+ },
3642+ S => {
3643+ action => \&start_S_mode,
3644+ label => '',
3645+ },
3646+ T => {
3647+ action => sub { switch_mode('T') },
3648+ label => '',
3649+ },
3650+ d => {
3651+ action => sub { get_config_interactive('interval') },
3652+ label => 'Change refresh interval',
3653+ },
3654+ n => { action => \&next_server, label => 'Switch to the next connection' },
3655+ p => { action => \&pause, label => 'Pause innotop', },
3656+ q => { action => \&finish, label => 'Quit innotop', },
3657+);
3658+
3659+# ###########################################################################
3660+# Sleep times after certain statements {{{3
3661+# ###########################################################################
3662+my %stmt_sleep_time_for = ();
3663+
3664+# ###########################################################################
3665+# Config editor key mappings {{{3
3666+# ###########################################################################
3667+my %cfg_editor_action = (
3668+ c => {
3669+ note => 'Edit columns, etc in the displayed table(s)',
3670+ func => \&edit_table,
3671+ },
3672+ g => {
3673+ note => 'Edit general configuration',
3674+ func => \&edit_configuration_variables,
3675+ },
3676+ k => {
3677+ note => 'Edit row-coloring rules',
3678+ func => \&edit_color_rules,
3679+ },
3680+ p => {
3681+ note => 'Manage plugins',
3682+ func => \&edit_plugins,
3683+ },
3684+ s => {
3685+ note => 'Edit server groups',
3686+ func => \&edit_server_groups,
3687+ },
3688+ S => {
3689+ note => 'Edit SQL statement sleep delays',
3690+ func => \&edit_stmt_sleep_times,
3691+ },
3692+ t => {
3693+ note => 'Choose which table(s) to display in this mode',
3694+ func => \&choose_mode_tables,
3695+ },
3696+);
3697+
3698+# ###########################################################################
3699+# Color editor key mappings {{{3
3700+# ###########################################################################
3701+my %color_editor_action = (
3702+ n => {
3703+ note => 'Create a new color rule',
3704+ func => sub {
3705+ my ( $tbl, $idx ) = @_;
3706+ my $meta = $tbl_meta{$tbl};
3707+
3708+ $clear_screen_sub->();
3709+ my $col;
3710+ do {
3711+ $col = prompt_list(
3712+ 'Choose the target column for the rule',
3713+ '',
3714+ sub { return keys %{$meta->{cols}} },
3715+ { map { $_ => $meta->{cols}->{$_}->{label} } keys %{$meta->{cols}} });
3716+ } while ( !$col );
3717+ ( $col ) = grep { $_ } split(/\W+/, $col);
3718+ return $idx unless $col && exists $meta->{cols}->{$col};
3719+
3720+ $clear_screen_sub->();
3721+ my $op;
3722+ do {
3723+ $op = prompt_list(
3724+ 'Choose the comparison operator for the rule',
3725+ '',
3726+ sub { return keys %comp_ops },
3727+ { map { $_ => $comp_ops{$_} } keys %comp_ops } );
3728+ } until ( $op );
3729+ $op =~ s/\s+//g;
3730+ return $idx unless $op && exists $comp_ops{$op};
3731+
3732+ my $arg;
3733+ do {
3734+ $arg = prompt('Specify an argument for the comparison');
3735+ } until defined $arg;
3736+
3737+ my $color;
3738+ do {
3739+ $color = prompt_list(
3740+ 'Choose the color(s) the row should be when the rule matches',
3741+ '',
3742+ sub { return keys %ansicolors },
3743+ { map { $_ => $_ } keys %ansicolors } );
3744+ } until defined $color;
3745+ $color = join(' ', unique(grep { exists $ansicolors{$_} } split(/\W+/, $color)));
3746+ return $idx unless $color;
3747+
3748+ push @{$tbl_meta{$tbl}->{colors}}, {
3749+ col => $col,
3750+ op => $op,
3751+ arg => $arg,
3752+ color => $color
3753+ };
3754+ $tbl_meta{$tbl}->{cust}->{colors} = 1;
3755+
3756+ return $idx;
3757+ },
3758+ },
3759+ d => {
3760+ note => 'Remove the selected rule',
3761+ func => sub {
3762+ my ( $tbl, $idx ) = @_;
3763+ my @rules = @{ $tbl_meta{$tbl}->{colors} };
3764+ return 0 unless @rules > 0 && $idx < @rules && $idx >= 0;
3765+ splice(@{$tbl_meta{$tbl}->{colors}}, $idx, 1);
3766+ $tbl_meta{$tbl}->{cust}->{colors} = 1;
3767+ return $idx == @rules ? $#rules : $idx;
3768+ },
3769+ },
3770+ j => {
3771+ note => 'Move highlight down one',
3772+ func => sub {
3773+ my ( $tbl, $idx ) = @_;
3774+ my $num_rules = scalar @{$tbl_meta{$tbl}->{colors}};
3775+ return ($idx + 1) % $num_rules;
3776+ },
3777+ },
3778+ k => {
3779+ note => 'Move highlight up one',
3780+ func => sub {
3781+ my ( $tbl, $idx ) = @_;
3782+ my $num_rules = scalar @{$tbl_meta{$tbl}->{colors}};
3783+ return ($idx - 1) % $num_rules;
3784+ },
3785+ },
3786+ '+' => {
3787+ note => 'Move selected rule up one',
3788+ func => sub {
3789+ my ( $tbl, $idx ) = @_;
3790+ my $meta = $tbl_meta{$tbl};
3791+ my $dest = $idx == 0 ? scalar(@{$meta->{colors}} - 1) : $idx - 1;
3792+ my $temp = $meta->{colors}->[$idx];
3793+ $meta->{colors}->[$idx] = $meta->{colors}->[$dest];
3794+ $meta->{colors}->[$dest] = $temp;
3795+ $meta->{cust}->{colors} = 1;
3796+ return $dest;
3797+ },
3798+ },
3799+ '-' => {
3800+ note => 'Move selected rule down one',
3801+ func => sub {
3802+ my ( $tbl, $idx ) = @_;
3803+ my $meta = $tbl_meta{$tbl};
3804+ my $dest = $idx == scalar(@{$meta->{colors}} - 1) ? 0 : $idx + 1;
3805+ my $temp = $meta->{colors}->[$idx];
3806+ $meta->{colors}->[$idx] = $meta->{colors}->[$dest];
3807+ $meta->{colors}->[$dest] = $temp;
3808+ $meta->{cust}->{colors} = 1;
3809+ return $dest;
3810+ },
3811+ },
3812+);
3813+
3814+# ###########################################################################
3815+# Plugin editor key mappings {{{3
3816+# ###########################################################################
3817+my %plugin_editor_action = (
3818+ '*' => {
3819+ note => 'Toggle selected plugin active/inactive',
3820+ func => sub {
3821+ my ( $plugins, $idx ) = @_;
3822+ my $plugin = $plugins->[$idx];
3823+ $plugin->{active} = $plugin->{active} ? 0 : 1;
3824+ return $idx;
3825+ },
3826+ },
3827+ j => {
3828+ note => 'Move highlight down one',
3829+ func => sub {
3830+ my ( $plugins, $idx ) = @_;
3831+ return ($idx + 1) % scalar(@$plugins);
3832+ },
3833+ },
3834+ k => {
3835+ note => 'Move highlight up one',
3836+ func => sub {
3837+ my ( $plugins, $idx ) = @_;
3838+ return $idx == 0 ? @$plugins - 1 : $idx - 1;
3839+ },
3840+ },
3841+);
3842+
3843+# ###########################################################################
3844+# Table editor key mappings {{{3
3845+# ###########################################################################
3846+my %tbl_editor_action = (
3847+ a => {
3848+ note => 'Add a column to the table',
3849+ func => sub {
3850+ my ( $tbl, $col ) = @_;
3851+ my @visible_cols = @{ $tbl_meta{$tbl}->{visible} };
3852+ my %all_cols = %{ $tbl_meta{$tbl}->{cols} };
3853+ delete @all_cols{@visible_cols};
3854+ my $choice = prompt_list(
3855+ 'Choose a column',
3856+ '',
3857+ sub { return keys %all_cols; },
3858+ { map { $_ => $all_cols{$_}->{label} || $all_cols{$_}->{hdr} } keys %all_cols });
3859+ if ( $all_cols{$choice} ) {
3860+ push @{$tbl_meta{$tbl}->{visible}}, $choice;
3861+ $tbl_meta{$tbl}->{cust}->{visible} = 1;
3862+ return $choice;
3863+ }
3864+ return $col;
3865+ },
3866+ },
3867+ n => {
3868+ note => 'Create a new column and add it to the table',
3869+ func => sub {
3870+ my ( $tbl, $col ) = @_;
3871+
3872+ $clear_screen_sub->();
3873+ print word_wrap("Choose a name for the column. This name is not displayed, and is used only "
3874+ . "for internal reference. It can contain only lowercase letters, numbers, "
3875+ . "and underscores.");
3876+ print "\n\n";
3877+ do {
3878+ $col = prompt("Enter column name");
3879+ $col = '' if $col =~ m/[^a-z0-9_]/;
3880+ } while ( !$col );
3881+
3882+ $clear_screen_sub->();
3883+ my $hdr;
3884+ do {
3885+ $hdr = prompt("Enter column header");
3886+ } while ( !$hdr );
3887+
3888+ $clear_screen_sub->();
3889+ print "Choose a source for the column's data\n\n";
3890+ my ( $src, $sub, $err );
3891+ do {
3892+ if ( $err ) {
3893+ print "Error: $err\n\n";
3894+ }
3895+ $src = prompt("Enter column source");
3896+ if ( $src ) {
3897+ ( $sub, $err ) = compile_expr($src);
3898+ }
3899+ } until ( !$err);
3900+
3901+ # TODO: this duplicates %col_props.
3902+ $tbl_meta{$tbl}->{cols}->{$col} = {
3903+ hdr => $hdr,
3904+ src => $src,
3905+ just => '-',
3906+ num => 0,
3907+ label => 'User-defined',
3908+ user => 1,
3909+ tbl => $tbl,
3910+ minw => 0,
3911+ maxw => 0,
3912+ trans => [],
3913+ func => $sub,
3914+ dec => 0,
3915+ agg => 0,
3916+ aggonly => 0,
3917+ };
3918+
3919+ $tbl_meta{$tbl}->{visible} = [ unique(@{$tbl_meta{$tbl}->{visible}}, $col) ];
3920+ $tbl_meta{$tbl}->{cust}->{visible} = 1;
3921+ return $col;
3922+ },
3923+ },
3924+ d => {
3925+ note => 'Remove selected column',
3926+ func => sub {
3927+ my ( $tbl, $col ) = @_;
3928+ my @visible_cols = @{ $tbl_meta{$tbl}->{visible} };
3929+ my $idx = 0;
3930+ return $col unless @visible_cols > 1;
3931+ while ( $visible_cols[$idx] ne $col ) {
3932+ $idx++;
3933+ }
3934+ $tbl_meta{$tbl}->{visible} = [ grep { $_ ne $col } @visible_cols ];
3935+ $tbl_meta{$tbl}->{cust}->{visible} = 1;
3936+ return $idx == $#visible_cols ? $visible_cols[$idx - 1] : $visible_cols[$idx + 1];
3937+ },
3938+ },
3939+ e => {
3940+ note => 'Edit selected column',
3941+ func => sub {
3942+ # TODO: make this editor hotkey-driven and give readline support.
3943+ my ( $tbl, $col ) = @_;
3944+ $clear_screen_sub->();
3945+ my $meta = $tbl_meta{$tbl}->{cols}->{$col};
3946+ my @prop = qw(hdr label src just num minw maxw trans agg); # TODO redundant
3947+
3948+ my $answer;
3949+ do {
3950+ # Do what the user asked...
3951+ if ( $answer && grep { $_ eq $answer } @prop ) {
3952+ # Some properties are arrays, others scalars.
3953+ my $ini = ref $col_props{$answer} ? join(' ', @{$meta->{$answer}}) : $meta->{$answer};
3954+ my $val = prompt("New value for $answer", undef, $ini);
3955+ $val = [ split(' ', $val) ] if ref($col_props{$answer});
3956+ if ( $answer eq 'trans' ) {
3957+ $val = [ unique(grep{ exists $trans_funcs{$_} } @$val) ];
3958+ }
3959+ @{$meta}{$answer, 'user', 'tbl' } = ( $val, 1, $tbl );
3960+ }
3961+
3962+ my @display_lines = (
3963+ '',
3964+ "You are editing column $tbl.$col.\n",
3965+ );
3966+
3967+ push @display_lines, create_table2(
3968+ \@prop,
3969+ { map { $_ => $_ } @prop },
3970+ { map { $_ => ref $meta->{$_} eq 'ARRAY' ? join(' ', @{$meta->{$_}})
3971+ : ref $meta->{$_} ? '[expression code]'
3972+ : $meta->{$_}
3973+ } @prop
3974+ },
3975+ { sep => ' ' });
3976+ draw_screen(\@display_lines, { raw => 1 });
3977+ print "\n\n"; # One to add space, one to clear readline artifacts
3978+ $answer = prompt('Edit what? (q to quit)');
3979+ } while ( $answer ne 'q' );
3980+
3981+ return $col;
3982+ },
3983+ },
3984+ j => {
3985+ note => 'Move highlight down one',
3986+ func => sub {
3987+ my ( $tbl, $col ) = @_;
3988+ my @visible_cols = @{ $tbl_meta{$tbl}->{visible} };
3989+ my $idx = 0;
3990+ while ( $visible_cols[$idx] ne $col ) {
3991+ $idx++;
3992+ }
3993+ return $visible_cols[ ($idx + 1) % @visible_cols ];
3994+ },
3995+ },
3996+ k => {
3997+ note => 'Move highlight up one',
3998+ func => sub {
3999+ my ( $tbl, $col ) = @_;
4000+ my @visible_cols = @{ $tbl_meta{$tbl}->{visible} };
4001+ my $idx = 0;
4002+ while ( $visible_cols[$idx] ne $col ) {
4003+ $idx++;
4004+ }
4005+ return $visible_cols[ $idx - 1 ];
4006+ },
4007+ },
4008+ '+' => {
4009+ note => 'Move selected column up one',
4010+ func => sub {
4011+ my ( $tbl, $col ) = @_;
4012+ my $meta = $tbl_meta{$tbl};
4013+ my @visible_cols = @{$meta->{visible}};
4014+ my $idx = 0;
4015+ while ( $visible_cols[$idx] ne $col ) {
4016+ $idx++;
4017+ }
4018+ if ( $idx ) {
4019+ $visible_cols[$idx] = $visible_cols[$idx - 1];
4020+ $visible_cols[$idx - 1] = $col;
4021+ $meta->{visible} = \@visible_cols;
4022+ }
4023+ else {
4024+ shift @{$meta->{visible}};
4025+ push @{$meta->{visible}}, $col;
4026+ }
4027+ $meta->{cust}->{visible} = 1;
4028+ return $col;
4029+ },
4030+ },
4031+ '-' => {
4032+ note => 'Move selected column down one',
4033+ func => sub {
4034+ my ( $tbl, $col ) = @_;
4035+ my $meta = $tbl_meta{$tbl};
4036+ my @visible_cols = @{$meta->{visible}};
4037+ my $idx = 0;
4038+ while ( $visible_cols[$idx] ne $col ) {
4039+ $idx++;
4040+ }
4041+ if ( $idx == $#visible_cols ) {
4042+ unshift @{$meta->{visible}}, $col;
4043+ pop @{$meta->{visible}};
4044+ }
4045+ else {
4046+ $visible_cols[$idx] = $visible_cols[$idx + 1];
4047+ $visible_cols[$idx + 1] = $col;
4048+ $meta->{visible} = \@visible_cols;
4049+ }
4050+ $meta->{cust}->{visible} = 1;
4051+ return $col;
4052+ },
4053+ },
4054+ f => {
4055+ note => 'Choose filters',
4056+ func => sub {
4057+ my ( $tbl, $col ) = @_;
4058+ choose_filters($tbl);
4059+ return $col;
4060+ },
4061+ },
4062+ o => {
4063+ note => 'Edit color rules',
4064+ func => sub {
4065+ my ( $tbl, $col ) = @_;
4066+ edit_color_rules($tbl);
4067+ return $col;
4068+ },
4069+ },
4070+ s => {
4071+ note => 'Choose sort columns',
4072+ func => sub {
4073+ my ( $tbl, $col ) = @_;
4074+ choose_sort_cols($tbl);
4075+ return $col;
4076+ },
4077+ },
4078+ g => {
4079+ note => 'Choose group-by (aggregate) columns',
4080+ func => sub {
4081+ my ( $tbl, $col ) = @_;
4082+ choose_group_cols($tbl);
4083+ return $col;
4084+ },
4085+ },
4086+);
4087+
4088+# ###########################################################################
4089+# Global variables and environment {{{2
4090+# ###########################################################################
4091+
4092+my @this_term_size; # w_chars, h_chars, w_pix, h_pix
4093+my @last_term_size; # w_chars, h_chars, w_pix, h_pix
4094+my $char;
4095+my $windows = $OSNAME =~ m/MSWin/;
4096+my $have_color = 0;
4097+my $MAX_ULONG = 4294967295; # 2^32-1
4098+my $num_regex = qr/^[+-]?(?=\d|\.)\d*(?:\.\d+)?(?:E[+-]?\d+|)$/i;
4099+my $int_regex = qr/^\d+$/;
4100+my $bool_regex = qr/^[01]$/;
4101+my $term = undef;
4102+my $file = undef; # File to watch for InnoDB monitor output
4103+my $file_mtime = undef; # Status of watched file
4104+my $file_data = undef; # Last chunk of text read from file
4105+my $innodb_parser = InnoDBParser->new;
4106+
4107+my $nonfatal_errs = join('|',
4108+ 'Access denied for user',
4109+ 'Unknown MySQL server host',
4110+ 'Unknown database',
4111+ 'Can\'t connect to local MySQL server through socket',
4112+ 'Can\'t connect to MySQL server on',
4113+ 'MySQL server has gone away',
4114+ 'Cannot call SHOW INNODB STATUS',
4115+ 'Access denied',
4116+ 'AutoCommit',
4117+);
4118+
4119+if ( !$opts{n} ) {
4120+ require Term::ReadLine;
4121+ $term = Term::ReadLine->new('innotop');
4122+}
4123+
4124+# Stores status, variables, innodb status, master/slave status etc.
4125+# Keyed on connection name. Each entry is a hashref of current and past data sets,
4126+# keyed on clock tick.
4127+my %vars;
4128+my %info_gotten = (); # Which things have been retrieved for the current clock tick.
4129+
4130+# Stores info on currently displayed queries: cxn, connection ID, query text.
4131+my @current_queries;
4132+
4133+my $lines_printed = 0;
4134+my $clock = 0; # Incremented with every wake-sleep cycle
4135+my $clearing_deadlocks = 0;
4136+
4137+# Find the home directory; it's different on different OSes.
4138+my $homepath = $ENV{HOME} || $ENV{HOMEPATH} || $ENV{USERPROFILE} || '.';
4139+
4140+# If terminal coloring is available, use it. The only function I want from
4141+# the module is the colored() function.
4142+eval {
4143+ if ( !$opts{n} ) {
4144+ if ( $windows ) {
4145+ require Win32::Console::ANSI;
4146+ }
4147+ require Term::ANSIColor;
4148+ import Term::ANSIColor qw(colored);
4149+ $have_color = 1;
4150+ }
4151+};
4152+if ( $EVAL_ERROR || $opts{n} ) {
4153+ # If there was an error, manufacture my own colored() function that does no
4154+ # coloring.
4155+ *colored = sub { pop @_; @_; };
4156+}
4157+
4158+if ( $opts{n} ) {
4159+ $clear_screen_sub = sub {};
4160+}
4161+elsif ( $windows ) {
4162+ $clear_screen_sub = sub { $lines_printed = 0; system("cls") };
4163+}
4164+else {
4165+ my $clear = `clear`;
4166+ $clear_screen_sub = sub { $lines_printed = 0; print $clear };
4167+}
4168+
4169+# ###########################################################################
4170+# Config storage. {{{2
4171+# ###########################################################################
4172+my %config = (
4173+ color => {
4174+ val => $have_color,
4175+ note => 'Whether to use terminal coloring',
4176+ conf => 'ALL',
4177+ pat => $bool_regex,
4178+ },
4179+ cmd_filter => {
4180+ val => 'Com_',
4181+ note => 'Prefix for values in C mode',
4182+ conf => [qw(C)],
4183+ },
4184+ plugin_dir => {
4185+ val => "$homepath/.innotop/plugins",
4186+ note => 'Directory where plugins can be found',
4187+ conf => 'ALL',
4188+ },
4189+ show_percent => {
4190+ val => 1,
4191+ note => 'Show the % symbol after percentages',
4192+ conf => 'ALL',
4193+ pat => $bool_regex,
4194+ },
4195+ skip_innodb => {
4196+ val => 0,
4197+ note => 'Disable SHOW INNODB STATUS',
4198+ conf => 'ALL',
4199+ pat => $bool_regex,
4200+ },
4201+ S_func => {
4202+ val => 's',
4203+ note => 'What to display in S mode: graph, status, pivoted status',
4204+ conf => [qw(S)],
4205+ pat => qr/^[gsv]$/,
4206+ },
4207+ cxn_timeout => {
4208+ val => 28800,
4209+ note => 'Connection timeout for keeping unused connections alive',
4210+ conf => 'ALL',
4211+ pat => $int_regex,
4212+ },
4213+ graph_char => {
4214+ val => '*',
4215+ note => 'Character for drawing graphs',
4216+ conf => [ qw(S) ],
4217+ pat => qr/^.$/,
4218+ },
4219+ show_cxn_errors_in_tbl => {
4220+ val => 1,
4221+ note => 'Whether to display connection errors as rows in the table',
4222+ conf => 'ALL',
4223+ pat => $bool_regex,
4224+ },
4225+ hide_hdr => {
4226+ val => 0,
4227+ note => 'Whether to show column headers',
4228+ conf => 'ALL',
4229+ pat => $bool_regex,
4230+ },
4231+ show_cxn_errors => {
4232+ val => 1,
4233+ note => 'Whether to print connection errors to STDOUT',
4234+ conf => 'ALL',
4235+ pat => $bool_regex,
4236+ },
4237+ readonly => {
4238+ val => 0,
4239+ note => 'Whether the config file is read-only',
4240+ conf => [ qw() ],
4241+ pat => $bool_regex,
4242+ },
4243+ global => {
4244+ val => 1,
4245+ note => 'Whether to show GLOBAL variables and status',
4246+ conf => 'ALL',
4247+ pat => $bool_regex,
4248+ },
4249+ header_highlight => {
4250+ val => 'bold',
4251+ note => 'How to highlight table column headers',
4252+ conf => 'ALL',
4253+ pat => qr/^(?:bold|underline)$/,
4254+ },
4255+ display_table_captions => {
4256+ val => 1,
4257+ note => 'Whether to put captions on tables',
4258+ conf => 'ALL',
4259+ pat => $bool_regex,
4260+ },
4261+ charset => {
4262+ val => 'ascii',
4263+ note => 'What type of characters should be displayed in queries (ascii, unicode, none)',
4264+ conf => 'ALL',
4265+ pat => qr/^(?:ascii|unicode|none)$/,
4266+ },
4267+ auto_wipe_dl => {
4268+ val => 0,
4269+ note => 'Whether to auto-wipe InnoDB deadlocks',
4270+ conf => 'ALL',
4271+ pat => $bool_regex,
4272+ },
4273+ max_height => {
4274+ val => 30,
4275+ note => '[Win32] Max window height',
4276+ conf => 'ALL',
4277+ },
4278+ debug => {
4279+ val => 0,
4280+ pat => $bool_regex,
4281+ note => 'Debug mode (more verbose errors, uses more memory)',
4282+ conf => 'ALL',
4283+ },
4284+ num_digits => {
4285+ val => 2,
4286+ pat => $int_regex,
4287+ note => 'How many digits to show in fractional numbers and percents',
4288+ conf => 'ALL',
4289+ },
4290+ debugfile => {
4291+ val => "$homepath/.innotop/core_dump",
4292+ note => 'A debug file in case you are interested in error output',
4293+ },
4294+ show_statusbar => {
4295+ val => 1,
4296+ pat => $bool_regex,
4297+ note => 'Whether to show the status bar in the display',
4298+ conf => 'ALL',
4299+ },
4300+ mode => {
4301+ val => "T",
4302+ note => "Which mode to start in",
4303+ cmdline => 1,
4304+ },
4305+ status_inc => {
4306+ val => 0,
4307+ note => 'Whether to show raw or incremental values for status variables',
4308+ pat => $bool_regex,
4309+ },
4310+ interval => {
4311+ val => 10,
4312+ pat => qr/^(?:(?:\d*?[1-9]\d*(?:\.\d*)?)|(?:\d*\.\d*?[1-9]\d*))$/,
4313+ note => "The interval at which the display will be refreshed. Fractional values allowed.",
4314+ },
4315+ num_status_sets => {
4316+ val => 9,
4317+ pat => $int_regex,
4318+ note => 'How many sets of STATUS and VARIABLES values to show',
4319+ conf => [ qw(S) ],
4320+ },
4321+ S_set => {
4322+ val => 'general',
4323+ pat => qr/^\w+$/,
4324+ note => 'Which set of variables to display in S (Variables & Status) mode',
4325+ conf => [ qw(S) ],
4326+ },
4327+);
4328+
4329+# ###########################################################################
4330+# Config file sections {{{2
4331+# The configuration file is broken up into sections like a .ini file. This
4332+# variable defines those sections and the subroutines responsible for reading
4333+# and writing them.
4334+# ###########################################################################
4335+my %config_file_sections = (
4336+ plugins => {
4337+ reader => \&load_config_plugins,
4338+ writer => \&save_config_plugins,
4339+ },
4340+ group_by => {
4341+ reader => \&load_config_group_by,
4342+ writer => \&save_config_group_by,
4343+ },
4344+ filters => {
4345+ reader => \&load_config_filters,
4346+ writer => \&save_config_filters,
4347+ },
4348+ active_filters => {
4349+ reader => \&load_config_active_filters,
4350+ writer => \&save_config_active_filters,
4351+ },
4352+ visible_tables => {
4353+ reader => \&load_config_visible_tables,
4354+ writer => \&save_config_visible_tables,
4355+ },
4356+ sort_cols => {
4357+ reader => \&load_config_sort_cols,
4358+ writer => \&save_config_sort_cols,
4359+ },
4360+ active_columns => {
4361+ reader => \&load_config_active_columns,
4362+ writer => \&save_config_active_columns,
4363+ },
4364+ tbl_meta => {
4365+ reader => \&load_config_tbl_meta,
4366+ writer => \&save_config_tbl_meta,
4367+ },
4368+ general => {
4369+ reader => \&load_config_config,
4370+ writer => \&save_config_config,
4371+ },
4372+ connections => {
4373+ reader => \&load_config_connections,
4374+ writer => \&save_config_connections,
4375+ },
4376+ active_connections => {
4377+ reader => \&load_config_active_connections,
4378+ writer => \&save_config_active_connections,
4379+ },
4380+ server_groups => {
4381+ reader => \&load_config_server_groups,
4382+ writer => \&save_config_server_groups,
4383+ },
4384+ active_server_groups => {
4385+ reader => \&load_config_active_server_groups,
4386+ writer => \&save_config_active_server_groups,
4387+ },
4388+ max_values_seen => {
4389+ reader => \&load_config_mvs,
4390+ writer => \&save_config_mvs,
4391+ },
4392+ varsets => {
4393+ reader => \&load_config_varsets,
4394+ writer => \&save_config_varsets,
4395+ },
4396+ colors => {
4397+ reader => \&load_config_colors,
4398+ writer => \&save_config_colors,
4399+ },
4400+ stmt_sleep_times => {
4401+ reader => \&load_config_stmt_sleep_times,
4402+ writer => \&save_config_stmt_sleep_times,
4403+ },
4404+);
4405+
4406+# Config file sections have some dependencies, so they have to be read/written in order.
4407+my @ordered_config_file_sections = qw(general plugins filters active_filters tbl_meta
4408+ connections active_connections server_groups active_server_groups max_values_seen
4409+ active_columns sort_cols visible_tables varsets colors stmt_sleep_times
4410+ group_by);
4411+
4412+# All events for which plugins may register themselves. Entries are arrayrefs.
4413+my %event_listener_for = map { $_ => [] }
4414+ qw(
4415+ extract_values
4416+ set_to_tbl_pre_filter set_to_tbl_pre_sort set_to_tbl_pre_group
4417+ set_to_tbl_pre_colorize set_to_tbl_pre_transform set_to_tbl_pre_pivot
4418+ set_to_tbl_pre_create set_to_tbl_post_create
4419+ draw_screen
4420+ );
4421+
4422+# All variables to which plugins have access.
4423+my %pluggable_vars = (
4424+ action_for => \%action_for,
4425+ agg_funcs => \%agg_funcs,
4426+ config => \%config,
4427+ connections => \%connections,
4428+ dbhs => \%dbhs,
4429+ filters => \%filters,
4430+ modes => \%modes,
4431+ server_groups => \%server_groups,
4432+ tbl_meta => \%tbl_meta,
4433+ trans_funcs => \%trans_funcs,
4434+ var_sets => \%var_sets,
4435+);
4436+
4437+# ###########################################################################
4438+# Contains logic to generate prepared statements for a given function for a
4439+# given DB connection. Returns a $sth.
4440+# ###########################################################################
4441+my %stmt_maker_for = (
4442+ INNODB_STATUS => sub {
4443+ my ( $dbh ) = @_;
4444+ return $dbh->prepare(version_ge( $dbh, '5.0.0' )
4445+ ? 'SHOW ENGINE INNODB STATUS'
4446+ : 'SHOW INNODB STATUS');
4447+ },
4448+ SHOW_VARIABLES => sub {
4449+ my ( $dbh ) = @_;
4450+ return $dbh->prepare($config{global}->{val} && version_ge( $dbh, '4.0.3' )
4451+ ? 'SHOW GLOBAL VARIABLES'
4452+ : 'SHOW VARIABLES');
4453+ },
4454+ SHOW_STATUS => sub {
4455+ my ( $dbh ) = @_;
4456+ return $dbh->prepare($config{global}->{val} && version_ge( $dbh, '5.0.2' )
4457+ ? 'SHOW GLOBAL STATUS'
4458+ : 'SHOW STATUS');
4459+ },
4460+ KILL_QUERY => sub {
4461+ my ( $dbh ) = @_;
4462+ return $dbh->prepare(version_ge( $dbh, '5.0.0' )
4463+ ? 'KILL QUERY ?'
4464+ : 'KILL ?');
4465+ },
4466+ SHOW_MASTER_LOGS => sub {
4467+ my ( $dbh ) = @_;
4468+ return $dbh->prepare('SHOW MASTER LOGS');
4469+ },
4470+ SHOW_MASTER_STATUS => sub {
4471+ my ( $dbh ) = @_;
4472+ return $dbh->prepare('SHOW MASTER STATUS');
4473+ },
4474+ SHOW_SLAVE_STATUS => sub {
4475+ my ( $dbh ) = @_;
4476+ return $dbh->prepare('SHOW SLAVE STATUS');
4477+ },
4478+ KILL_CONNECTION => sub {
4479+ my ( $dbh ) = @_;
4480+ return $dbh->prepare(version_ge( $dbh, '5.0.0' )
4481+ ? 'KILL CONNECTION ?'
4482+ : 'KILL ?');
4483+ },
4484+ OPEN_TABLES => sub {
4485+ my ( $dbh ) = @_;
4486+ return version_ge($dbh, '4.0.0')
4487+ ? $dbh->prepare('SHOW OPEN TABLES')
4488+ : undef;
4489+ },
4490+ PROCESSLIST => sub {
4491+ my ( $dbh ) = @_;
4492+ return $dbh->prepare('SHOW FULL PROCESSLIST');
4493+ },
4494+);
4495+
4496+# Plugins!
4497+my %plugins = (
4498+);
4499+
4500+# ###########################################################################
4501+# Run the program {{{1
4502+# ###########################################################################
4503+
4504+# This config variable is only useful for MS Windows because its terminal
4505+# can't tell how tall it is.
4506+if ( !$windows ) {
4507+ delete $config{max_height};
4508+}
4509+
4510+# Try to lower my priority.
4511+eval { setpriority(0, 0, getpriority(0, 0) + 10); };
4512+
4513+# Print stuff to the screen immediately, don't wait for a newline.
4514+$OUTPUT_AUTOFLUSH = 1;
4515+
4516+# Clear the screen and load the configuration.
4517+$clear_screen_sub->();
4518+load_config();
4519+post_process_tbl_meta();
4520+
4521+# Make sure no changes are written to config file in non-interactive mode.
4522+if ( $opts{n} ) {
4523+ $config{readonly}->{val} = 1;
4524+}
4525+
4526+eval {
4527+
4528+ # Open the file for InnoDB status
4529+ if ( @ARGV ) {
4530+ my $filename = shift @ARGV;
4531+ open $file, "<", $filename
4532+ or die "Cannot open '$filename': $OS_ERROR";
4533+ }
4534+
4535+ # In certain modes we might have to collect data for two cycles
4536+ # before printing anything out, so we need to bump up the count one.
4537+ if ( $opts{n} && $opts{count} && $config{status_inc}->{val}
4538+ && $config{mode}->{val} =~ m/[S]/ )
4539+ {
4540+ $opts{count}++;
4541+ }
4542+
4543+ while (++$clock) {
4544+
4545+ my $mode = $config{mode}->{val} || 'T';
4546+ if ( !$modes{$mode} ) {
4547+ die "Mode '$mode' doesn't exist; try one of these:\n"
4548+ . join("\n", map { " $_ $modes{$_}->{hdr}" } sort keys %modes)
4549+ . "\n";
4550+ }
4551+
4552+ if ( !$opts{n} ) {
4553+ @last_term_size = @this_term_size;
4554+ @this_term_size = Term::ReadKey::GetTerminalSize(\*STDOUT);
4555+ if ( $windows ) {
4556+ $this_term_size[0]--;
4557+ $this_term_size[1]
4558+ = min($this_term_size[1], $config{max_height}->{val});
4559+ }
4560+ die("Can't read terminal size") unless @this_term_size;
4561+ }
4562+
4563+ # If there's no connection to a database server, we need to fix that...
4564+ if ( !%connections ) {
4565+ print "You have not defined any database connections.\n\n";
4566+ add_new_dsn();
4567+ }
4568+
4569+ # See whether there are any connections defined for this mode. If there's only one
4570+ # connection total, assume the user wants to just use innotop for a single server
4571+ # and don't ask which server to connect to. Also, if we're monitoring from a file,
4572+ # we just use the first connection.
4573+ if ( !get_connections() ) {
4574+ if ( $file || 1 == scalar keys %connections ) {
4575+ $modes{$config{mode}->{val}}->{connections} = [ keys %connections ];
4576+ }
4577+ else {
4578+ choose_connections();
4579+ }
4580+ }
4581+
4582+ # Term::ReadLine might have re-set $OUTPUT_AUTOFLUSH.
4583+ $OUTPUT_AUTOFLUSH = 1;
4584+
4585+ # Prune old data
4586+ my $sets = $config{num_status_sets}->{val};
4587+ foreach my $store ( values %vars ) {
4588+ delete @{$store}{ grep { $_ < $clock - $sets } keys %$store };
4589+ }
4590+ %info_gotten = ();
4591+
4592+ # Call the subroutine to display this mode.
4593+ $modes{$mode}->{display_sub}->();
4594+
4595+ # It may be time to quit now.
4596+ if ( $opts{count} && $clock >= $opts{count} ) {
4597+ finish();
4598+ }
4599+
4600+ # Wait for a bit.
4601+ if ( $opts{n} ) {
4602+ sleep($config{interval}->{val});
4603+ }
4604+ else {
4605+ ReadMode('cbreak');
4606+ $char = ReadKey($config{interval}->{val});
4607+ ReadMode('normal');
4608+ }
4609+
4610+ # Handle whatever action the key indicates.
4611+ do_key_action();
4612+
4613+ }
4614+};
4615+if ( $EVAL_ERROR ) {
4616+ core_dump( $EVAL_ERROR );
4617+}
4618+finish();
4619+
4620+# Subroutines {{{1
4621+# Mode functions{{{2
4622+# switch_mode {{{3
4623+sub switch_mode {
4624+ my $mode = shift;
4625+ $config{mode}->{val} = $mode;
4626+}
4627+
4628+# Prompting functions {{{2
4629+# prompt_list {{{3
4630+# Prompts the user for a value, given a question, initial value,
4631+# a completion function and a hashref of hints.
4632+sub prompt_list {
4633+ die "Can't call in non-interactive mode" if $opts{n};
4634+ my ( $question, $init, $completion, $hints ) = @_;
4635+ if ( $hints ) {
4636+ # Figure out how wide the table will be
4637+ my $max_name = max(map { length($_) } keys %$hints );
4638+ $max_name ||= 0;
4639+ $max_name += 3;
4640+ my @meta_rows = create_table2(
4641+ [ sort keys %$hints ],
4642+ { map { $_ => $_ } keys %$hints },
4643+ { map { $_ => trunc($hints->{$_}, $this_term_size[0] - $max_name) } keys %$hints },
4644+ { sep => ' ' });
4645+ if (@meta_rows > 10) {
4646+ # Try to split and stack the meta rows next to each other
4647+ my $split = int(@meta_rows / 2);
4648+ @meta_rows = stack_next(
4649+ [@meta_rows[0..$split - 1]],
4650+ [@meta_rows[$split..$#meta_rows]],
4651+ { pad => ' | '},
4652+ );
4653+ }
4654+ print join( "\n",
4655+ '',
4656+ map { ref $_ ? colored(@$_) : $_ } create_caption('Choose from', @meta_rows), ''),
4657+ "\n";
4658+ }
4659+ $term->Attribs->{completion_function} = $completion;
4660+ my $answer = $term->readline("$question: ", $init);
4661+ $OUTPUT_AUTOFLUSH = 1;
4662+ $answer = '' if !defined($answer);
4663+ $answer =~ s/\s+$//;
4664+ return $answer;
4665+}
4666+
4667+# prompt {{{3
4668+# Prints out a prompt and reads from the keyboard, then validates with the
4669+# validation regex until the input is correct.
4670+sub prompt {
4671+ die "Can't call in non-interactive mode" if $opts{n};
4672+ my ( $prompt, $regex, $init, $completion ) = @_;
4673+ my $response;
4674+ my $success = 0;
4675+ do {
4676+ if ( $completion ) {
4677+ $term->Attribs->{completion_function} = $completion;
4678+ }
4679+ $response = $term->readline("$prompt: ", $init);
4680+ if ( $regex && $response !~ m/$regex/ ) {
4681+ print "Invalid response.\n\n";
4682+ }
4683+ else {
4684+ $success = 1;
4685+ }
4686+ } while ( !$success );
4687+ $OUTPUT_AUTOFLUSH = 1;
4688+ $response =~ s/\s+$//;
4689+ return $response;
4690+}
4691+
4692+# prompt_noecho {{{3
4693+# Unfortunately, suppressing echo with Term::ReadLine isn't reliable; the user might not
4694+# have that library, or it might not support that feature.
4695+sub prompt_noecho {
4696+ my ( $prompt ) = @_;
4697+ print colored("$prompt: ", 'underline');
4698+ my $response;
4699+ ReadMode('noecho');
4700+ $response = <STDIN>;
4701+ chomp($response);
4702+ ReadMode('normal');
4703+ return $response;
4704+}
4705+
4706+# do_key_action {{{3
4707+# Depending on whether a key was read, do something. Keys have certain
4708+# actions defined in lookup tables. Each mode may have its own lookup table,
4709+# which trumps the global table -- so keys can be context-sensitive. The key
4710+# may be read and written in a subroutine, so it's a global.
4711+sub do_key_action {
4712+ if ( defined $char ) {
4713+ my $mode = $config{mode}->{val};
4714+ my $action
4715+ = defined($modes{$mode}->{action_for}->{$char})
4716+ ? $modes{$mode}->{action_for}->{$char}->{action}
4717+ : defined($action_for{$char})
4718+ ? $action_for{$char}->{action}
4719+ : sub{};
4720+ $action->();
4721+ }
4722+}
4723+
4724+# pause {{{3
4725+sub pause {
4726+ die "Can't call in non-interactive mode" if $opts{n};
4727+ my $msg = shift;
4728+ print defined($msg) ? "\n$msg" : "\nPress any key to continue";
4729+ ReadMode('cbreak');
4730+ my $char = ReadKey(0);
4731+ ReadMode('normal');
4732+ return $char;
4733+}
4734+
4735+# reverse_sort {{{3
4736+sub reverse_sort {
4737+ my $tbl = shift;
4738+ $tbl_meta{$tbl}->{sort_dir} *= -1;
4739+}
4740+
4741+# select_cxn {{{3
4742+# Selects connection(s). If the mode (or argument list) has only one, returns
4743+# it without prompt.
4744+sub select_cxn {
4745+ my ( $prompt, @cxns ) = @_;
4746+ if ( !@cxns ) {
4747+ @cxns = get_connections();
4748+ }
4749+ if ( @cxns == 1 ) {
4750+ return $cxns[0];
4751+ }
4752+ my $choices = prompt_list(
4753+ $prompt,
4754+ $cxns[0],
4755+ sub{ return @cxns },
4756+ { map { $_ => $connections{$_}->{dsn} } @cxns });
4757+ my @result = unique(grep { my $a = $_; grep { $_ eq $a } @cxns } split(/\s+/, $choices));
4758+ return @result;
4759+}
4760+
4761+# kill_query {{{3
4762+# Kills a connection, or on new versions, optionally a query but not connection.
4763+sub kill_query {
4764+ my ( $q_or_c ) = @_;
4765+
4766+ my $info = choose_thread(
4767+ sub { 1 },
4768+ 'Select a thread to kill the ' . $q_or_c,
4769+ );
4770+ return unless $info;
4771+ return unless pause("Kill $info->{id}?") =~ m/y/i;
4772+
4773+ eval {
4774+ do_stmt($info->{cxn}, $q_or_c eq 'QUERY' ? 'KILL_QUERY' : 'KILL_CONNECTION', $info->{id} );
4775+ };
4776+
4777+ if ( $EVAL_ERROR ) {
4778+ print "\nError: $EVAL_ERROR";
4779+ pause();
4780+ }
4781+}
4782+
4783+# set_display_precision {{{3
4784+sub set_display_precision {
4785+ my $dir = shift;
4786+ $config{num_digits}->{val} = min(9, max(0, $config{num_digits}->{val} + $dir));
4787+}
4788+
4789+sub toggle_visible_table {
4790+ my ( $mode, $table ) = @_;
4791+ my $visible = $modes{$mode}->{visible_tables};
4792+ if ( grep { $_ eq $table } @$visible ) {
4793+ $modes{$mode}->{visible_tables} = [ grep { $_ ne $table } @$visible ];
4794+ }
4795+ else {
4796+ unshift @$visible, $table;
4797+ }
4798+ $modes{$mode}->{cust}->{visible_tables} = 1;
4799+}
4800+
4801+# toggle_filter{{{3
4802+sub toggle_filter {
4803+ my ( $tbl, $filter ) = @_;
4804+ my $filters = $tbl_meta{$tbl}->{filters};
4805+ if ( grep { $_ eq $filter } @$filters ) {
4806+ $tbl_meta{$tbl}->{filters} = [ grep { $_ ne $filter } @$filters ];
4807+ }
4808+ else {
4809+ push @$filters, $filter;
4810+ }
4811+ $tbl_meta{$tbl}->{cust}->{filters} = 1;
4812+}
4813+
4814+# toggle_config {{{3
4815+sub toggle_config {
4816+ my ( $key ) = @_;
4817+ $config{$key}->{val} ^= 1;
4818+}
4819+
4820+# create_deadlock {{{3
4821+sub create_deadlock {
4822+ $clear_screen_sub->();
4823+
4824+ print "This function will deliberately cause a small deadlock, "
4825+ . "clearing deadlock information from the InnoDB monitor.\n\n";
4826+
4827+ my $answer = prompt("Are you sure you want to proceed? Say 'y' if you do");
4828+ return 0 unless $answer eq 'y';
4829+
4830+ my ( $cxn ) = select_cxn('Clear on which server? ');
4831+ return unless $cxn && exists($connections{$cxn});
4832+
4833+ clear_deadlock($cxn);
4834+}
4835+
4836+# deadlock_thread {{{3
4837+sub deadlock_thread {
4838+ my ( $id, $tbl, $cxn ) = @_;
4839+
4840+ eval {
4841+ my $dbh = get_new_db_connection($cxn, 1);
4842+ my @stmts = (
4843+ "set transaction isolation level serializable",
4844+ (version_ge($dbh, '4.0.11') ? "start transaction" : 'begin'),
4845+ "select * from $tbl where a = $id",
4846+ "update $tbl set a = $id where a <> $id",
4847+ );
4848+
4849+ foreach my $stmt (@stmts[0..2]) {
4850+ $dbh->do($stmt);
4851+ }
4852+ sleep(1 + $id);
4853+ $dbh->do($stmts[-1]);
4854+ };
4855+ if ( $EVAL_ERROR ) {
4856+ if ( $EVAL_ERROR !~ m/Deadlock found/ ) {
4857+ die $EVAL_ERROR;
4858+ }
4859+ }
4860+ exit(0);
4861+}
4862+
4863+# Purges unused binlogs on the master, up to but not including the latest log.
4864+# TODO: guess which connections are slaves of a given master.
4865+sub purge_master_logs {
4866+ my @cxns = get_connections();
4867+
4868+ get_master_slave_status(@cxns);
4869+
4870+ # Toss out the rows that don't have master/slave status...
4871+ my @vars =
4872+ grep { $_ && ($_->{file} || $_->{master_host}) }
4873+ map { $vars{$_}->{$clock} } @cxns;
4874+ @cxns = map { $_->{cxn} } @vars;
4875+
4876+ # Figure out which master to purge ons.
4877+ my @masters = map { $_->{cxn} } grep { $_->{file} } @vars;
4878+ my ( $master ) = select_cxn('Which master?', @masters );
4879+ return unless $master;
4880+ my ($master_status) = grep { $_->{cxn} eq $master } @vars;
4881+
4882+ # Figure out the result order (not lexical order) of master logs.
4883+ my @master_logs = get_master_logs($master);
4884+ my $i = 0;
4885+ my %master_logs = map { $_->{log_name} => $i++ } @master_logs;
4886+
4887+ # Ask which slave(s) are reading from this master.
4888+ my @slave_status = grep { $_->{master_host} } @vars;
4889+ my @slaves = map { $_->{cxn} } @slave_status;
4890+ @slaves = select_cxn("Which slaves are reading from $master?", @slaves);
4891+ @slave_status = grep { my $item = $_; grep { $item->{cxn} eq $_ } @slaves } @slave_status;
4892+ return unless @slave_status;
4893+
4894+ # Find the minimum binary log in use.
4895+ my $min_log = min(map { $master_logs{$_->{master_log_file}} } @slave_status);
4896+ my $log_name = $master_logs[$min_log]->{log_name};
4897+
4898+ my $stmt = "PURGE MASTER LOGS TO '$log_name'";
4899+ send_cmd_to_servers($stmt, 0, 'PURGE {MASTER | BINARY} LOGS {TO "log_name" | BEFORE "date"}', [$master]);
4900+}
4901+
4902+sub send_cmd_to_servers {
4903+ my ( $cmd, $all, $hint, $cxns ) = @_;
4904+ if ( $all ) {
4905+ @$cxns = get_connections();
4906+ }
4907+ elsif ( !@$cxns ) {
4908+ @$cxns = select_cxn('Which servers?', @$cxns);
4909+ }
4910+ if ( $hint ) {
4911+ print "\nHint: $hint\n";
4912+ }
4913+ $cmd = prompt('Command to send', undef, $cmd);
4914+ foreach my $cxn ( @$cxns ) {
4915+ eval {
4916+ my $sth = do_query($cxn, $cmd);
4917+ };
4918+ if ( $EVAL_ERROR ) {
4919+ print "Error from $cxn: $EVAL_ERROR\n";
4920+ }
4921+ else {
4922+ print "Success on $cxn\n";
4923+ }
4924+ }
4925+ pause();
4926+}
4927+
4928+# Display functions {{{2
4929+
4930+sub set_s_mode {
4931+ my ( $func ) = @_;
4932+ $clear_screen_sub->();
4933+ $config{S_func}->{val} = $func;
4934+}
4935+
4936+# start_S_mode {{{3
4937+sub start_S_mode {
4938+ $clear_screen_sub->();
4939+ switch_mode('S');
4940+}
4941+
4942+# display_B {{{3
4943+sub display_B {
4944+ my @display_lines;
4945+ my @cxns = get_connections();
4946+ get_innodb_status(\@cxns);
4947+
4948+ my @buffer_pool;
4949+ my @page_statistics;
4950+ my @insert_buffers;
4951+ my @adaptive_hash_index;
4952+ my %rows_for = (
4953+ buffer_pool => \@buffer_pool,
4954+ page_statistics => \@page_statistics,
4955+ insert_buffers => \@insert_buffers,
4956+ adaptive_hash_index => \@adaptive_hash_index,
4957+ );
4958+
4959+ my @visible = get_visible_tables();
4960+ my %wanted = map { $_ => 1 } @visible;
4961+
4962+ foreach my $cxn ( @cxns ) {
4963+ my $set = $vars{$cxn}->{$clock};
4964+ my $pre = $vars{$cxn}->{$clock-1} || $set;
4965+
4966+ if ( $set->{IB_bp_complete} ) {
4967+ if ( $wanted{buffer_pool} ) {
4968+ push @buffer_pool, extract_values($set, $set, $pre, 'buffer_pool');
4969+ }
4970+ if ( $wanted{page_statistics} ) {
4971+ push @page_statistics, extract_values($set, $set, $pre, 'page_statistics');
4972+ }
4973+ }
4974+ if ( $set->{IB_ib_complete} ) {
4975+ if ( $wanted{insert_buffers} ) {
4976+ push @insert_buffers, extract_values(
4977+ $config{status_inc}->{val} ? inc(0, $cxn) : $set, $set, $pre,
4978+ 'insert_buffers');
4979+ }
4980+ if ( $wanted{adaptive_hash_index} ) {
4981+ push @adaptive_hash_index, extract_values($set, $set, $pre, 'adaptive_hash_index');
4982+ }
4983+ }
4984+ }
4985+
4986+ my $first_table = 0;
4987+ foreach my $tbl ( @visible ) {
4988+ push @display_lines, '', set_to_tbl($rows_for{$tbl}, $tbl);
4989+ push @display_lines, get_cxn_errors(@cxns)
4990+ if ( $config{debug}->{val} || !$first_table++ );
4991+ }
4992+
4993+ draw_screen(\@display_lines);
4994+}
4995+
4996+# display_C {{{3
4997+sub display_C {
4998+ my @display_lines;
4999+ my @cxns = get_connections();
5000+ get_status_info(@cxns);
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches

to all changes: