Merge lp:~maria-captains/ourdelta/ourdelta-mariadb-5.2-montyprogram into lp:ourdelta

Proposed by Kristian Nielsen
Status: Needs review
Proposed branch: lp:~maria-captains/ourdelta/ourdelta-mariadb-5.2-montyprogram
Merge into: lp:ourdelta
Diff against target: 27515 lines (+26842/-1)
131 files modified
bakery/autobake51-centos.sh (+1/-1)
bakery/debian-5.2/README.Maintainer (+114/-0)
bakery/debian-5.2/additions/Docs__Images__Makefile.in (+6/-0)
bakery/debian-5.2/additions/Docs__Makefile.in (+6/-0)
bakery/debian-5.2/additions/debian-start (+35/-0)
bakery/debian-5.2/additions/debian-start.inc.sh (+72/-0)
bakery/debian-5.2/additions/echo_stderr (+2/-0)
bakery/debian-5.2/additions/innotop/changelog.innotop (+357/-0)
bakery/debian-5.2/additions/innotop/innotop (+10946/-0)
bakery/debian-5.2/additions/innotop/innotop.1 (+2084/-0)
bakery/debian-5.2/additions/msql2mysql.1 (+16/-0)
bakery/debian-5.2/additions/my.cnf (+193/-0)
bakery/debian-5.2/additions/my_print_defaults.1 (+16/-0)
bakery/debian-5.2/additions/myisam_ftdump.1 (+16/-0)
bakery/debian-5.2/additions/myisamchk.1 (+17/-0)
bakery/debian-5.2/additions/myisamlog.1 (+16/-0)
bakery/debian-5.2/additions/myisampack.1 (+19/-0)
bakery/debian-5.2/additions/mysql-server.lintian-overrides (+2/-0)
bakery/debian-5.2/additions/mysql_config.1 (+17/-0)
bakery/debian-5.2/additions/mysql_convert_table_format.1 (+17/-0)
bakery/debian-5.2/additions/mysql_find_rows.1 (+18/-0)
bakery/debian-5.2/additions/mysql_fix_extensions.1 (+18/-0)
bakery/debian-5.2/additions/mysql_install_db.1 (+16/-0)
bakery/debian-5.2/additions/mysql_secure_installation.1 (+17/-0)
bakery/debian-5.2/additions/mysql_setpermission.1 (+23/-0)
bakery/debian-5.2/additions/mysql_tableinfo.1 (+322/-0)
bakery/debian-5.2/additions/mysql_waitpid.1 (+20/-0)
bakery/debian-5.2/additions/mysqlbinlog.1 (+17/-0)
bakery/debian-5.2/additions/mysqlbug.1 (+14/-0)
bakery/debian-5.2/additions/mysqlcheck.1 (+28/-0)
bakery/debian-5.2/additions/mysqld_safe_syslog.cnf (+2/-0)
bakery/debian-5.2/additions/mysqldumpslow.1 (+50/-0)
bakery/debian-5.2/additions/mysqlimport.1 (+20/-0)
bakery/debian-5.2/additions/mysqlreport (+1298/-0)
bakery/debian-5.2/additions/mysqlreport.1 (+180/-0)
bakery/debian-5.2/additions/mysqltest.1 (+16/-0)
bakery/debian-5.2/additions/pack_isam.1 (+19/-0)
bakery/debian-5.2/additions/resolve_stack_dump.1 (+16/-0)
bakery/debian-5.2/additions/resolveip.1 (+16/-0)
bakery/debian-5.2/changelog (+11/-0)
bakery/debian-5.2/compat (+1/-0)
bakery/debian-5.2/copyright (+164/-0)
bakery/debian-5.2/dist/Debian/control (+169/-0)
bakery/debian-5.2/dist/Debian/mariadb-server-5.2.README.Debian (+109/-0)
bakery/debian-5.2/dist/Debian/mariadb-server-5.2.dirs (+10/-0)
bakery/debian-5.2/dist/Debian/mariadb-server-5.2.files (+90/-0)
bakery/debian-5.2/dist/Debian/mariadb-server-5.2.postinst (+267/-0)
bakery/debian-5.2/dist/Debian/mariadb-server-5.2.postrm (+83/-0)
bakery/debian-5.2/dist/Debian/rules (+319/-0)
bakery/debian-5.2/dist/Ubuntu/apparmor-profile (+36/-0)
bakery/debian-5.2/dist/Ubuntu/control (+183/-0)
bakery/debian-5.2/dist/Ubuntu/mariadb-server-5.2.README.Debian (+119/-0)
bakery/debian-5.2/dist/Ubuntu/mariadb-server-5.2.dirs (+11/-0)
bakery/debian-5.2/dist/Ubuntu/mariadb-server-5.2.files (+66/-0)
bakery/debian-5.2/dist/Ubuntu/mariadb-server-5.2.postinst (+274/-0)
bakery/debian-5.2/dist/Ubuntu/mariadb-server-5.2.postrm (+86/-0)
bakery/debian-5.2/dist/Ubuntu/mariadb-server-5.2.py (+53/-0)
bakery/debian-5.2/dist/Ubuntu/mariadb-server-core-5.2.files (+26/-0)
bakery/debian-5.2/dist/Ubuntu/rules (+325/-0)
bakery/debian-5.2/libmariadbclient-dev.README.Maintainer (+4/-0)
bakery/debian-5.2/libmariadbclient-dev.dirs (+2/-0)
bakery/debian-5.2/libmariadbclient-dev.docs (+1/-0)
bakery/debian-5.2/libmariadbclient-dev.examples (+1/-0)
bakery/debian-5.2/libmariadbclient-dev.files (+9/-0)
bakery/debian-5.2/libmariadbclient-dev.links (+2/-0)
bakery/debian-5.2/libmariadbclient16.dirs (+1/-0)
bakery/debian-5.2/libmariadbclient16.docs (+1/-0)
bakery/debian-5.2/libmariadbclient16.files (+1/-0)
bakery/debian-5.2/libmariadbclient16.postinst (+12/-0)
bakery/debian-5.2/libmariadbd-dev.files (+2/-0)
bakery/debian-5.2/mariadb-client-5.2.README.Debian (+4/-0)
bakery/debian-5.2/mariadb-client-5.2.dirs (+3/-0)
bakery/debian-5.2/mariadb-client-5.2.docs (+3/-0)
bakery/debian-5.2/mariadb-client-5.2.files (+36/-0)
bakery/debian-5.2/mariadb-client-5.2.links (+3/-0)
bakery/debian-5.2/mariadb-client-5.2.lintian-overrides (+3/-0)
bakery/debian-5.2/mariadb-client-5.2.menu (+3/-0)
bakery/debian-5.2/mariadb-server-5.2.NEWS (+34/-0)
bakery/debian-5.2/mariadb-server-5.2.config (+46/-0)
bakery/debian-5.2/mariadb-server-5.2.docs (+1/-0)
bakery/debian-5.2/mariadb-server-5.2.lintian-overrides (+5/-0)
bakery/debian-5.2/mariadb-server-5.2.logcheck.ignore.paranoid (+9/-0)
bakery/debian-5.2/mariadb-server-5.2.logcheck.ignore.server (+32/-0)
bakery/debian-5.2/mariadb-server-5.2.logcheck.ignore.workstation (+32/-0)
bakery/debian-5.2/mariadb-server-5.2.mysql-server.logrotate (+27/-0)
bakery/debian-5.2/mariadb-server-5.2.mysql.init (+187/-0)
bakery/debian-5.2/mariadb-server-5.2.preinst (+182/-0)
bakery/debian-5.2/mariadb-server-5.2.prerm (+8/-0)
bakery/debian-5.2/mariadb-server-5.2.templates (+89/-0)
bakery/debian-5.2/mariadb-test-5.2.dirs (+94/-0)
bakery/debian-5.2/mariadb-test-5.2.files (+9/-0)
bakery/debian-5.2/mariadb-test-5.2.links (+2/-0)
bakery/debian-5.2/mysql-common.dirs (+1/-0)
bakery/debian-5.2/mysql-common.files (+3/-0)
bakery/debian-5.2/mysql-common.lintian-overrides (+2/-0)
bakery/debian-5.2/mysql-common.postrm (+7/-0)
bakery/debian-5.2/patches/00list (+10/-0)
bakery/debian-5.2/patches/01_MAKEFILES__Docs_Images_Makefile.in.dpatch (+776/-0)
bakery/debian-5.2/patches/01_MAKEFILES__Docs_Makefile.in.dpatch (+776/-0)
bakery/debian-5.2/patches/02_no_builtin_ndbcluster_plugin.dpatch (+18/-0)
bakery/debian-5.2/patches/21_init__openquery_configtest.dpatch (+33/-0)
bakery/debian-5.2/patches/33_scripts__mysql_create_system_tables__no_test.dpatch (+29/-0)
bakery/debian-5.2/patches/38_scripts__mysqld_safe.sh__signals.dpatch (+43/-0)
bakery/debian-5.2/patches/41_scripts__mysql_install_db.sh__no_test.dpatch (+20/-0)
bakery/debian-5.2/patches/44_scripts__mysql_config__libs.dpatch (+24/-0)
bakery/debian-5.2/patches/50_mysql-test__db_test.dpatch (+23/-0)
bakery/debian-5.2/patches/60_zlib_innodb_workaround.dpatch (+31/-0)
bakery/debian-5.2/po/POTFILES.in (+1/-0)
bakery/debian-5.2/po/ar.po (+236/-0)
bakery/debian-5.2/po/ca.po (+341/-0)
bakery/debian-5.2/po/cs.po (+330/-0)
bakery/debian-5.2/po/da.po (+366/-0)
bakery/debian-5.2/po/de.po (+229/-0)
bakery/debian-5.2/po/es.po (+374/-0)
bakery/debian-5.2/po/eu.po (+236/-0)
bakery/debian-5.2/po/fr.po (+241/-0)
bakery/debian-5.2/po/gl.po (+233/-0)
bakery/debian-5.2/po/it.po (+213/-0)
bakery/debian-5.2/po/ja.po (+218/-0)
bakery/debian-5.2/po/nb.po (+296/-0)
bakery/debian-5.2/po/nl.po (+301/-0)
bakery/debian-5.2/po/pt.po (+291/-0)
bakery/debian-5.2/po/pt_BR.po (+427/-0)
bakery/debian-5.2/po/ro.po (+318/-0)
bakery/debian-5.2/po/ru.po (+229/-0)
bakery/debian-5.2/po/sv.po (+230/-0)
bakery/debian-5.2/po/templates.pot (+186/-0)
bakery/debian-5.2/po/tr.po (+341/-0)
bakery/debian-5.2/source.lintian-overrides (+2/-0)
bakery/debian-5.2/watch (+3/-0)
bakery/tarbake52.sh (+92/-0)
To merge this branch: bzr merge lp:~maria-captains/ourdelta/ourdelta-mariadb-5.2-montyprogram
Reviewer Review Type Date Requested Status
OurDelta-core Pending
Review via email: mp+21697@code.launchpad.net

Description of the change

I went through all the stuff in bakery/debian-5.2 and updated as seemed appropriate.
I also checked the centos .spec for .rpm.

Things now look ok on all platforms in Buildbot, so should be ready for merge.

To post a comment you must log in.

Unmerged revisions

75. By Kristian Nielsen

Add new vcol testsuite.

74. By Kristian Nielsen

Don't include -alpha suffix from version like mariadb-5.2.0-alpha, rpmbuild doesn't like it.

73. By Kristian Nielsen

MWL#88: Update packaging scripts for MariaDB 5.2.

Update all ourdelta bakery files, renaming 5.1->5.2 where appropriate.
Attempt to add necessary extra dependencies.

72. By Arjen Lentz

Adjusted build scripts for 5.2 (some symlinked same as 5.1) - work in progress

71. By Arjen Lentz

Cloned bakery/debian-5.1 to -5.2 (before other changes)

Preview Diff

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

Subscribers

People subscribed via source and target branches

to all changes: