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