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