Merge lp:~percona-toolkit-dev/percona-toolkit/pt-query-advisor-docs-2 into lp:percona-toolkit/2.0

Proposed by Daniel Nichter
Status: Merged
Merged at revision: 247
Proposed branch: lp:~percona-toolkit-dev/percona-toolkit/pt-query-advisor-docs-2
Merge into: lp:percona-toolkit/2.0
Diff against target: 61840 lines (+50596/-6720) (has conflicts)
314 files modified
Changelog (+21/-0)
MANIFEST (+2/-0)
Makefile.PL (+1/-1)
bin/pt-align (+1/-1)
bin/pt-archiver (+1/-1)
bin/pt-config-diff (+1/-1)
bin/pt-deadlock-logger (+1/-1)
bin/pt-diskstats (+1/-1)
bin/pt-duplicate-key-checker (+171/-92)
bin/pt-fifo-split (+1/-1)
bin/pt-find (+1/-1)
bin/pt-fingerprint (+2143/-0)
bin/pt-fk-error-logger (+1/-1)
bin/pt-heartbeat (+11/-2)
bin/pt-index-usage (+170/-92)
bin/pt-ioprofile (+1/-1)
bin/pt-kill (+1/-1)
bin/pt-log-player (+1/-1)
bin/pt-mext (+1/-1)
bin/pt-mysql-summary (+2076/-679)
bin/pt-online-schema-change (+4671/-1243)
bin/pt-pmp (+1/-1)
bin/pt-query-advisor (+265/-390)
bin/pt-query-digest (+1/-1)
bin/pt-show-grants (+1/-1)
bin/pt-sift (+1/-1)
bin/pt-slave-delay (+1/-1)
bin/pt-slave-find (+11/-2)
bin/pt-slave-restart (+11/-2)
bin/pt-stalk (+1/-1)
bin/pt-summary (+2041/-793)
bin/pt-table-checksum (+253/-83)
bin/pt-table-sync (+71/-7)
bin/pt-table-usage (+7226/-0)
bin/pt-tcp-model (+1/-1)
bin/pt-trend (+1/-1)
bin/pt-upgrade (+1/-1)
bin/pt-variable-advisor (+1/-1)
bin/pt-visual-explain (+1/-1)
config/deb/changelog (+23/-0)
config/sphinx-build/conf.py (+2/-2)
docs/percona-toolkit.pod (+12/-4)
docs/release_notes.rst (+48/-0)
lib/CleanupTask.pm (+7/-2)
lib/MasterSlave.pm (+10/-0)
lib/MySQLStatusWaiter.pm (+31/-1)
lib/NibbleIterator.pm (+200/-30)
lib/OSCCaptureSync.pm (+0/-142)
lib/OobNibbleIterator.pm (+1/-1)
lib/QueryAdvisorRules.pm (+1/-1)
lib/QueryRewriter.pm (+24/-4)
lib/SQLParser.pm (+103/-8)
lib/TableUsage.pm (+1060/-0)
lib/bash/alt_cmds.sh (+25/-1)
lib/bash/collect_mysql_info.sh (+263/-0)
lib/bash/collect_system_info.sh (+574/-0)
lib/bash/log_warn_die.sh (+6/-0)
lib/bash/report_formatting.sh (+122/-0)
lib/bash/report_mysql_info.sh (+1391/-0)
lib/bash/report_system_info.sh (+1050/-0)
lib/bash/summary_common.sh (+155/-0)
t/lib/CleanupTask.t (+18/-1)
t/lib/MasterSlave.t (+91/-1)
t/lib/MySQLStatusWaiter.t (+41/-0)
t/lib/NibbleIterator.t (+74/-3)
t/lib/OSCCaptureSync.t (+0/-131)
t/lib/OobNibbleIterator.t (+8/-3)
t/lib/QueryRewriter.t (+59/-1)
t/lib/SQLParser.t (+1/-1)
t/lib/TableUsage.t (+816/-0)
t/lib/bash/collect_mysql_info.sh (+197/-0)
t/lib/bash/collect_system_info.sh (+307/-0)
t/lib/bash/report_formatting.sh (+113/-0)
t/lib/bash/report_mysql_info.sh (+734/-0)
t/lib/bash/report_system_info.sh (+1580/-0)
t/lib/bash/summary_common.sh (+79/-0)
t/lib/samples/SchemaIterator/resume-from-ignored-sakila-payment-5.0.txt (+52/-0)
t/lib/samples/SchemaIterator/resume-from-sakila-payment-5.0.txt (+70/-0)
t/lib/samples/ro-checksum-user.sql (+3/-0)
t/pt-deadlock-logger/basics.t (+1/-1)
t/pt-fingerprint/basics.t (+101/-0)
t/pt-fingerprint/samples/query001 (+2/-0)
t/pt-fingerprint/samples/query001.fingerprint (+1/-0)
t/pt-fingerprint/samples/query002 (+2/-0)
t/pt-fingerprint/samples/query002.fingerprint (+1/-0)
t/pt-mysql-summary/find_my_cnf_file.sh (+0/-20)
t/pt-mysql-summary/format_binlog_filters.sh (+0/-12)
t/pt-mysql-summary/format_innodb_status.sh (+0/-147)
t/pt-mysql-summary/format_overall_db_stats.sh (+0/-61)
t/pt-mysql-summary/format_status_variables.sh (+0/-97)
t/pt-mysql-summary/fuzz.sh (+0/-7)
t/pt-mysql-summary/get_mysql_info.sh (+0/-26)
t/pt-mysql-summary/parse_mysqld_instances.sh (+0/-47)
t/pt-mysql-summary/pretty_print_cnf_file.sh (+0/-40)
t/pt-mysql-summary/pt-mysql-summary.t (+54/-2)
t/pt-mysql-summary/samples/expected_output_temp002.txt (+276/-0)
t/pt-mysql-summary/samples/expected_output_temp003.txt (+219/-0)
t/pt-mysql-summary/samples/expected_output_temp004.txt (+218/-0)
t/pt-mysql-summary/samples/expected_output_temp005.txt (+291/-0)
t/pt-mysql-summary/samples/expected_result_report_summary.txt (+257/-0)
t/pt-mysql-summary/samples/mysql-variables-with-semisync.txt (+326/-0)
t/pt-mysql-summary/samples/temp001/mysql-status (+304/-0)
t/pt-mysql-summary/samples/temp001/mysql-variables (+356/-0)
t/pt-mysql-summary/samples/temp002/innodb-status (+118/-0)
t/pt-mysql-summary/samples/temp002/mysql-config-file (+26/-0)
t/pt-mysql-summary/samples/temp002/mysql-databases (+6/-0)
t/pt-mysql-summary/samples/temp002/mysql-plugins (+35/-0)
t/pt-mysql-summary/samples/temp002/mysql-processlist (+12/-0)
t/pt-mysql-summary/samples/temp002/mysql-status (+370/-0)
t/pt-mysql-summary/samples/temp002/mysql-status-defer (+370/-0)
t/pt-mysql-summary/samples/temp002/mysql-users (+1/-0)
t/pt-mysql-summary/samples/temp002/mysql-variables (+372/-0)
t/pt-mysql-summary/samples/temp002/mysqld-instances (+4/-0)
t/pt-mysql-summary/samples/temp002/mysqldump (+396/-0)
t/pt-mysql-summary/samples/temp003/innodb-status (+77/-0)
t/pt-mysql-summary/samples/temp003/mysql-config-file (+26/-0)
t/pt-mysql-summary/samples/temp003/mysql-databases (+2/-0)
t/pt-mysql-summary/samples/temp003/mysql-master-logs (+1/-0)
t/pt-mysql-summary/samples/temp003/mysql-master-status (+1/-0)
t/pt-mysql-summary/samples/temp003/mysql-plugins (+10/-0)
t/pt-mysql-summary/samples/temp003/mysql-processlist (+9/-0)
t/pt-mysql-summary/samples/temp003/mysql-status (+291/-0)
t/pt-mysql-summary/samples/temp003/mysql-status-defer (+291/-0)
t/pt-mysql-summary/samples/temp003/mysql-users (+1/-0)
t/pt-mysql-summary/samples/temp003/mysql-variables (+285/-0)
t/pt-mysql-summary/samples/temp003/mysqld-instances (+2/-0)
t/pt-mysql-summary/samples/temp004/innodb-status (+77/-0)
t/pt-mysql-summary/samples/temp004/mysql-config-file (+26/-0)
t/pt-mysql-summary/samples/temp004/mysql-databases (+3/-0)
t/pt-mysql-summary/samples/temp004/mysql-master-logs (+2/-0)
t/pt-mysql-summary/samples/temp004/mysql-master-status (+1/-0)
t/pt-mysql-summary/samples/temp004/mysql-plugins (+10/-0)
t/pt-mysql-summary/samples/temp004/mysql-processlist (+9/-0)
t/pt-mysql-summary/samples/temp004/mysql-status (+291/-0)
t/pt-mysql-summary/samples/temp004/mysql-status-defer (+291/-0)
t/pt-mysql-summary/samples/temp004/mysql-users (+1/-0)
t/pt-mysql-summary/samples/temp004/mysql-variables (+285/-0)
t/pt-mysql-summary/samples/temp004/mysqld-instances (+2/-0)
t/pt-mysql-summary/samples/temp005/innodb-status (+108/-0)
t/pt-mysql-summary/samples/temp005/mysql-config-file (+26/-0)
t/pt-mysql-summary/samples/temp005/mysql-databases (+3/-0)
t/pt-mysql-summary/samples/temp005/mysql-master-logs (+1/-0)
t/pt-mysql-summary/samples/temp005/mysql-master-status (+1/-0)
t/pt-mysql-summary/samples/temp005/mysql-plugins (+28/-0)
t/pt-mysql-summary/samples/temp005/mysql-processlist (+18/-0)
t/pt-mysql-summary/samples/temp005/mysql-status (+304/-0)
t/pt-mysql-summary/samples/temp005/mysql-status-defer (+304/-0)
t/pt-mysql-summary/samples/temp005/mysql-users (+1/-0)
t/pt-mysql-summary/samples/temp005/mysql-variables (+363/-0)
t/pt-mysql-summary/samples/temp005/mysqld-executables (+1/-0)
t/pt-mysql-summary/samples/temp005/mysqld-instances (+4/-0)
t/pt-mysql-summary/samples/temp005/mysqldump (+1084/-0)
t/pt-mysql-summary/samples/tempdir/innodb-status (+77/-0)
t/pt-mysql-summary/samples/tempdir/mysql-config-file (+26/-0)
t/pt-mysql-summary/samples/tempdir/mysql-databases (+3/-0)
t/pt-mysql-summary/samples/tempdir/mysql-master-logs (+3/-0)
t/pt-mysql-summary/samples/tempdir/mysql-master-status (+1/-0)
t/pt-mysql-summary/samples/tempdir/mysql-plugins (+10/-0)
t/pt-mysql-summary/samples/tempdir/mysql-processlist (+9/-0)
t/pt-mysql-summary/samples/tempdir/mysql-status (+291/-0)
t/pt-mysql-summary/samples/tempdir/mysql-status-defer (+291/-0)
t/pt-mysql-summary/samples/tempdir/mysql-users (+5/-0)
t/pt-mysql-summary/samples/tempdir/mysql-variables (+283/-0)
t/pt-mysql-summary/samples/tempdir/mysqld-instances (+4/-0)
t/pt-mysql-summary/samples/tempdir/mysqldump (+328/-0)
t/pt-mysql-summary/samples/tempdir/tempfile (+130/-0)
t/pt-mysql-summary/summarize_binlogs.sh (+0/-13)
t/pt-mysql-summary/summarize_processlist.sh (+0/-64)
t/pt-online-schema-change/alter_active_table.t (+56/-46)
t/pt-online-schema-change/basics.t (+541/-218)
t/pt-online-schema-change/bugs.t (+73/-0)
t/pt-online-schema-change/check_tables.t (+0/-126)
t/pt-online-schema-change/option_sanity.t (+12/-12)
t/pt-online-schema-change/privs.t (+86/-0)
t/pt-online-schema-change/samples/basic_no_fks.data (+500/-500)
t/pt-online-schema-change/samples/basic_no_fks.sql (+30/-0)
t/pt-online-schema-change/samples/basic_with_fks.sql (+56/-0)
t/pt-online-schema-change/samples/fk_tables_schema.sql (+0/-31)
t/pt-online-schema-change/samples/osc-user.sql (+3/-0)
t/pt-online-schema-change/samples/pk-bug-994002.sql (+29/-0)
t/pt-online-schema-change/samples/query_table.pl (+8/-5)
t/pt-online-schema-change/samples/small_table.sql (+0/-27)
t/pt-online-schema-change/sanity_checks.t (+113/-0)
t/pt-online-schema-change/skip_innodb.t (+60/-0)
t/pt-slave-restart/pt-slave-restart.t (+1/-1)
t/pt-stalk/pt-stalk.t (+1/-1)
t/pt-summary/format_vmstat.sh (+0/-37)
t/pt-summary/parse_arcconf.sh (+0/-176)
t/pt-summary/parse_dmidecode_mem_devices.sh (+0/-104)
t/pt-summary/parse_ethernet_controller_lspci.sh (+0/-11)
t/pt-summary/parse_fdisk.sh (+0/-16)
t/pt-summary/parse_filesystems.sh (+0/-52)
t/pt-summary/parse_free_minus_b.sh (+0/-67)
t/pt-summary/parse_fusionmpt_lsiutil.sh (+0/-50)
t/pt-summary/parse_hpacucli.sh (+0/-26)
t/pt-summary/parse_ip_s_link.sh (+0/-28)
t/pt-summary/parse_lsi_megaraid.sh (+0/-696)
t/pt-summary/parse_netstat.sh (+0/-49)
t/pt-summary/parse_proc_cpuinfo.sh (+0/-74)
t/pt-summary/parse_raid_controller_dmesg.sh (+0/-32)
t/pt-summary/parse_raid_controller_lspci.sh (+0/-39)
t/pt-summary/parse_virtualization_dmesg.sh (+0/-10)
t/pt-summary/pt-summary.t (+12/-2)
t/pt-summary/samples/BSD/freebsd_001/mounted_fs (+7/-0)
t/pt-summary/samples/BSD/freebsd_001/notable_procs (+2/-0)
t/pt-summary/samples/BSD/freebsd_001/processes (+10/-0)
t/pt-summary/samples/BSD/freebsd_001/summary (+10/-0)
t/pt-summary/samples/BSD/freebsd_001/sysctl (+1481/-0)
t/pt-summary/samples/BSD/freebsd_001/uptime (+1/-0)
t/pt-summary/samples/BSD/freebsd_001/vmstat (+7/-0)
t/pt-summary/samples/BSD/netbsd_001/mounted_fs (+5/-0)
t/pt-summary/samples/BSD/netbsd_001/notable_procs (+2/-0)
t/pt-summary/samples/BSD/netbsd_001/proc_cpuinfo_copy (+14/-0)
t/pt-summary/samples/BSD/netbsd_001/processes (+10/-0)
t/pt-summary/samples/BSD/netbsd_001/summary (+10/-0)
t/pt-summary/samples/BSD/netbsd_001/swapctl (+1/-0)
t/pt-summary/samples/BSD/netbsd_001/sysctl (+511/-0)
t/pt-summary/samples/BSD/netbsd_001/uptime (+1/-0)
t/pt-summary/samples/BSD/netbsd_001/vmstat (+7/-0)
t/pt-summary/samples/BSD/openbsd_001/mounted_fs (+4/-0)
t/pt-summary/samples/BSD/openbsd_001/notable_procs (+2/-0)
t/pt-summary/samples/BSD/openbsd_001/processes (+10/-0)
t/pt-summary/samples/BSD/openbsd_001/summary (+10/-0)
t/pt-summary/samples/BSD/openbsd_001/swapctl (+1/-0)
t/pt-summary/samples/BSD/openbsd_001/sysctl (+423/-0)
t/pt-summary/samples/BSD/openbsd_001/uptime (+1/-0)
t/pt-summary/samples/BSD/openbsd_001/vmstat (+7/-0)
t/pt-summary/samples/Linux/001/dmesg_file (+786/-0)
t/pt-summary/samples/Linux/001/dmidecode (+412/-0)
t/pt-summary/samples/Linux/001/ip (+24/-0)
t/pt-summary/samples/Linux/001/lspci_file (+17/-0)
t/pt-summary/samples/Linux/001/lvs (+1/-0)
t/pt-summary/samples/Linux/001/memory (+50/-0)
t/pt-summary/samples/Linux/001/mounted_fs (+12/-0)
t/pt-summary/samples/Linux/001/netstat (+6/-0)
t/pt-summary/samples/Linux/001/notable_procs (+5/-0)
t/pt-summary/samples/Linux/001/partitioning (+30/-0)
t/pt-summary/samples/Linux/001/proc_cpuinfo_copy (+58/-0)
t/pt-summary/samples/Linux/001/proc_cpuinfo_copy.unq (+1/-0)
t/pt-summary/samples/Linux/001/processes (+10/-0)
t/pt-summary/samples/Linux/001/summary (+23/-0)
t/pt-summary/samples/Linux/001/sysctl (+905/-0)
t/pt-summary/samples/Linux/001/uptime (+1/-0)
t/pt-summary/samples/Linux/001/vmstat (+7/-0)
t/pt-summary/samples/Linux/002/dmesg_file (+283/-0)
t/pt-summary/samples/Linux/002/memory (+34/-0)
t/pt-summary/samples/Linux/002/mounted_fs (+3/-0)
t/pt-summary/samples/Linux/002/netstat (+6/-0)
t/pt-summary/samples/Linux/002/notable_procs (+2/-0)
t/pt-summary/samples/Linux/002/partitioning (+9/-0)
t/pt-summary/samples/Linux/002/proc_cpuinfo_copy (+19/-0)
t/pt-summary/samples/Linux/002/processes (+10/-0)
t/pt-summary/samples/Linux/002/summary (+19/-0)
t/pt-summary/samples/Linux/002/uptime (+1/-0)
t/pt-summary/samples/Linux/002/vmstat (+7/-0)
t/pt-summary/samples/Linux/003/dmesg_file (+283/-0)
t/pt-summary/samples/Linux/003/memory (+34/-0)
t/pt-summary/samples/Linux/003/mounted_fs (+3/-0)
t/pt-summary/samples/Linux/003/netstat (+6/-0)
t/pt-summary/samples/Linux/003/notable_procs (+2/-0)
t/pt-summary/samples/Linux/003/partitioning (+1/-0)
t/pt-summary/samples/Linux/003/proc_cpuinfo_copy (+19/-0)
t/pt-summary/samples/Linux/003/processes (+10/-0)
t/pt-summary/samples/Linux/003/summary (+19/-0)
t/pt-summary/samples/Linux/003/uptime (+1/-0)
t/pt-summary/samples/Linux/003/vmstat (+7/-0)
t/pt-summary/samples/Linux/output_002.txt (+82/-0)
t/pt-summary/samples/Linux/output_003.txt (+79/-0)
t/pt-summary/samples/MegaCli64_AdpAllInfo_aALL001.txt (+227/-0)
t/pt-summary/samples/MegaCli64_LdPdInfo_aALL_886223 (+214/-0)
t/pt-summary/samples/arcconf-001.txt (+133/-0)
t/pt-summary/samples/arcconf-003_900285.txt (+228/-0)
t/pt-summary/samples/arcconf-004_917781.txt (+162/-0)
t/pt-summary/samples/dmesg-005.txt (+787/-0)
t/pt-summary/samples/dmesg-007.txt (+136/-0)
t/pt-summary/samples/hpaculi-001.txt (+11/-0)
t/pt-summary/samples/hpaculi-002.txt (+354/-0)
t/pt-summary/samples/hpaculi-003.txt (+11/-0)
t/pt-summary/samples/ip-s-link-003.txt (+24/-0)
t/pt-summary/samples/lspci-005.txt (+38/-0)
t/pt-summary/samples/netstat-002.txt (+1328/-0)
t/pt-summary/samples/proc_cpuinfo001.txt (+57/-0)
t/pt-summary/samples/proc_cpuinfo001.txt.unq (+1/-0)
t/pt-summary/samples/proc_cpuinfo002.txt (+57/-0)
t/pt-summary/samples/proc_cpuinfo002.txt.unq (+1/-0)
t/pt-table-checksum/bugs.t (+143/-0)
t/pt-table-checksum/privs.t (+98/-0)
t/pt-table-checksum/samples/empty-table-bug-987393.sql (+18/-0)
t/pt-table-checksum/samples/not-using-pk-bug.out (+20/-0)
t/pt-table-checksum/samples/not-using-pk-bug.sql (+20/-0)
t/pt-table-checksum/samples/undef-arrayref-bug-995274.sql (+18/-0)
t/pt-table-checksum/skip_innodb.t (+75/-0)
t/pt-table-sync/bugs.t (+156/-0)
t/pt-table-sync/issue_408.t (+1/-1)
t/pt-table-sync/samples/wrong-tbl-struct-bug-1003014.sql (+35/-0)
t/pt-table-usage/basics.t (+138/-0)
t/pt-table-usage/create_table_definitions.t (+41/-0)
t/pt-table-usage/explain_extended.t (+79/-0)
t/pt-table-usage/samples/ee.out (+6/-0)
t/pt-table-usage/samples/ee.sql (+26/-0)
t/pt-table-usage/samples/in/slow001.txt (+24/-0)
t/pt-table-usage/samples/in/slow002.txt (+20/-0)
t/pt-table-usage/samples/in/slow003.txt (+3/-0)
t/pt-table-usage/samples/out/create-table-defs-001.txt (+4/-0)
t/pt-table-usage/samples/out/create001.txt (+5/-0)
t/pt-table-usage/samples/out/drop-table-if-exists.txt (+3/-0)
t/pt-table-usage/samples/out/query001.txt (+6/-0)
t/pt-table-usage/samples/out/query002.txt (+5/-0)
t/pt-table-usage/samples/out/slow001.txt (+31/-0)
t/pt-table-usage/samples/out/slow002.txt (+40/-0)
t/pt-table-usage/samples/out/slow003-001.txt (+6/-0)
t/pt-table-usage/samples/out/slow003-002.txt (+8/-0)
t/pt-table-usage/samples/out/slow003-003.txt (+6/-0)
util/test-bash-functions (+49/-0)
Text conflict in bin/pt-online-schema-change
Text conflict in bin/pt-query-advisor
Text conflict in bin/pt-summary
Text conflict in bin/pt-table-checksum
Text conflict in bin/pt-table-sync
Text conflict in lib/MySQLStatusWaiter.pm
Text conflict in lib/NibbleIterator.pm
Text conflict in t/lib/MySQLStatusWaiter.t
Text conflict in t/lib/NibbleIterator.t
Conflict adding file t/lib/samples/SchemaIterator/resume-from-ignored-sakila-payment-5.0.txt.  Moved existing file to t/lib/samples/SchemaIterator/resume-from-ignored-sakila-payment-5.0.txt.moved.
Conflict adding file t/lib/samples/SchemaIterator/resume-from-sakila-payment-5.0.txt.  Moved existing file to t/lib/samples/SchemaIterator/resume-from-sakila-payment-5.0.txt.moved.
Conflict adding file t/pt-table-checksum/bugs.t.  Moved existing file to t/pt-table-checksum/bugs.t.moved.
Conflict adding file t/pt-table-checksum/samples/empty-table-bug-987393.sql.  Moved existing file to t/pt-table-checksum/samples/empty-table-bug-987393.sql.moved.
Conflict adding file t/pt-table-checksum/samples/not-using-pk-bug.out.  Moved existing file to t/pt-table-checksum/samples/not-using-pk-bug.out.moved.
Conflict adding file t/pt-table-checksum/samples/not-using-pk-bug.sql.  Moved existing file to t/pt-table-checksum/samples/not-using-pk-bug.sql.moved.
Conflict adding file t/pt-table-checksum/samples/undef-arrayref-bug-995274.sql.  Moved existing file to t/pt-table-checksum/samples/undef-arrayref-bug-995274.sql.moved.
Text conflict in t/pt-table-checksum/skip_innodb.t
Conflict adding file t/pt-table-sync/bugs.t.  Moved existing file to t/pt-table-sync/bugs.t.moved.
Conflict adding file t/pt-table-sync/samples/wrong-tbl-struct-bug-1003014.sql.  Moved existing file to t/pt-table-sync/samples/wrong-tbl-struct-bug-1003014.sql.moved.
To merge this branch: bzr merge lp:~percona-toolkit-dev/percona-toolkit/pt-query-advisor-docs-2
Reviewer Review Type Date Requested Status
Daniel Nichter Approve
Review via email: mp+108350@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Daniel Nichter (daniel-nichter) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Changelog'
2--- Changelog 2012-03-07 23:41:54 +0000
3+++ Changelog 2012-06-01 14:38:21 +0000
4@@ -1,5 +1,26 @@
5 Changelog for Percona Toolkit
6
7+v2.1.1 released 2012-04-03
8+
9+ * Completely redesigned pt-online-schema-change
10+ * Completely redesigned pt-mysql-summary
11+ * Completely redesigned pt-summary
12+ * Added new tool: pt-table-usage
13+ * Added new tool: pt-fingerprint
14+ * Fixed bug 955860: pt-stalk doesn't run vmstat, iostat, and mpstat for --run-time
15+ * Fixed bug 960513: SHOW TABLE STATUS is used needlessly
16+ * Fixed bug 969726: pt-online-schema-change loses foreign keys
17+ * Fixed bug 846028: pt-online-schema-change does not show progress until completed
18+ * Fixed bug 898695: pt-online-schema-change add useless ORDER BY
19+ * Fixed bug 952727: pt-diskstats shows incorrect wr_mb_s
20+ * Fixed bug 963225: pt-query-digest fails to set history columns for disk tmp tables and disk filesort
21+ * Fixed bug 967451: Char chunking doesn't quote column name
22+ * Fixed bug 972399: pt-table-checksum docs are not rendered right
23+ * Fixed bug 896553: Various documentation spelling fixes
24+ * Fixed bug 949154: pt-variable-advisor advice for relay-log-space-limit
25+ * Fixed bug 953461: pt-upgrade manual broken 'output' section
26+ * Fixed bug 949653: pt-table-checksum docs don't mention risks posed by inconsistent schemas
27+
28 v2.0.4 released 2012-03-07
29
30 * Added --filter to pt-kill to allow arbitrary --group-by
31
32=== modified file 'MANIFEST'
33--- MANIFEST 2012-02-03 23:25:29 +0000
34+++ MANIFEST 2012-06-01 14:38:21 +0000
35@@ -12,6 +12,7 @@
36 bin/pt-duplicate-key-checker
37 bin/pt-fifo-split
38 bin/pt-find
39+bin/pt-fingerprint
40 bin/pt-fk-error-logger
41 bin/pt-heartbeat
42 bin/pt-index-usage
43@@ -33,6 +34,7 @@
44 bin/pt-summary
45 bin/pt-table-checksum
46 bin/pt-table-sync
47+bin/pt-table-usage
48 bin/pt-tcp-model
49 bin/pt-trend
50 bin/pt-upgrade
51
52=== modified file 'Makefile.PL'
53--- Makefile.PL 2012-03-07 23:41:54 +0000
54+++ Makefile.PL 2012-06-01 14:38:21 +0000
55@@ -2,7 +2,7 @@
56
57 WriteMakefile(
58 NAME => 'percona-toolkit',
59- VERSION => '2.0.4',
60+ VERSION => '2.1.1',
61 EXE_FILES => [ <bin/*> ],
62 MAN1PODS => {
63 'docs/percona-toolkit.pod' => 'blib/man1/percona-toolkit.1',
64
65=== modified file 'bin/pt-align'
66--- bin/pt-align 2012-03-07 23:41:54 +0000
67+++ bin/pt-align 2012-06-01 14:38:21 +0000
68@@ -218,6 +218,6 @@
69
70 =head1 VERSION
71
72-pt-align 2.0.4
73+pt-align 2.1.1
74
75 =cut
76
77=== modified file 'bin/pt-archiver'
78--- bin/pt-archiver 2012-05-30 22:20:01 +0000
79+++ bin/pt-archiver 2012-06-01 14:38:21 +0000
80@@ -5743,6 +5743,6 @@
81
82 =head1 VERSION
83
84-pt-archiver 2.0.4
85+pt-archiver 2.1.1
86
87 =cut
88
89=== modified file 'bin/pt-config-diff'
90--- bin/pt-config-diff 2012-05-24 17:52:01 +0000
91+++ bin/pt-config-diff 2012-06-01 14:38:21 +0000
92@@ -3408,6 +3408,6 @@
93
94 =head1 VERSION
95
96-pt-config-diff 2.0.4
97+pt-config-diff 2.1.1
98
99 =cut
100
101=== modified file 'bin/pt-deadlock-logger'
102--- bin/pt-deadlock-logger 2012-05-30 17:54:33 +0000
103+++ bin/pt-deadlock-logger 2012-06-01 14:38:21 +0000
104@@ -2748,6 +2748,6 @@
105
106 =head1 VERSION
107
108-pt-deadlock-logger 2.0.4
109+pt-deadlock-logger 2.1.1
110
111 =cut
112
113=== modified file 'bin/pt-diskstats'
114--- bin/pt-diskstats 2012-05-11 14:28:14 +0000
115+++ bin/pt-diskstats 2012-06-01 14:38:21 +0000
116@@ -4106,6 +4106,6 @@
117
118 =head1 VERSION
119
120-pt-diskstats 2.0.4
121+pt-diskstats 2.1.1
122
123 =cut
124
125=== modified file 'bin/pt-duplicate-key-checker'
126--- bin/pt-duplicate-key-checker 2012-05-24 17:52:01 +0000
127+++ bin/pt-duplicate-key-checker 2012-06-01 14:38:21 +0000
128@@ -199,19 +199,58 @@
129 return bless $self, $class;
130 }
131
132+sub get_create_table {
133+ my ( $self, $dbh, $db, $tbl ) = @_;
134+ die "I need a dbh parameter" unless $dbh;
135+ die "I need a db parameter" unless $db;
136+ die "I need a tbl parameter" unless $tbl;
137+ my $q = $self->{Quoter};
138+
139+ my $new_sql_mode
140+ = '/*!40101 SET @OLD_SQL_MODE := @@SQL_MODE, '
141+ . q{@@SQL_MODE := REPLACE(REPLACE(@@SQL_MODE, 'ANSI_QUOTES', ''), ',,', ','), }
142+ . '@OLD_QUOTE := @@SQL_QUOTE_SHOW_CREATE, '
143+ . '@@SQL_QUOTE_SHOW_CREATE := 1 */';
144+
145+ my $old_sql_mode = '/*!40101 SET @@SQL_MODE := @OLD_SQL_MODE, '
146+ . '@@SQL_QUOTE_SHOW_CREATE := @OLD_QUOTE */';
147+
148+ PTDEBUG && _d($new_sql_mode);
149+ eval { $dbh->do($new_sql_mode); };
150+ PTDEBUG && $EVAL_ERROR && _d($EVAL_ERROR);
151+
152+ my $use_sql = 'USE ' . $q->quote($db);
153+ PTDEBUG && _d($dbh, $use_sql);
154+ $dbh->do($use_sql);
155+
156+ my $show_sql = "SHOW CREATE TABLE " . $q->quote($db, $tbl);
157+ PTDEBUG && _d($show_sql);
158+ my $href;
159+ eval { $href = $dbh->selectrow_hashref($show_sql); };
160+ if ( $EVAL_ERROR ) {
161+ PTDEBUG && _d($EVAL_ERROR);
162+
163+ PTDEBUG && _d($old_sql_mode);
164+ $dbh->do($old_sql_mode);
165+
166+ return;
167+ }
168+
169+ PTDEBUG && _d($old_sql_mode);
170+ $dbh->do($old_sql_mode);
171+
172+ my ($key) = grep { m/create (?:table|view)/i } keys %$href;
173+ if ( !$key ) {
174+ die "Error: no 'Create Table' or 'Create View' in result set from "
175+ . "$show_sql: " . Dumper($href);
176+ }
177+
178+ return $href->{$key};
179+}
180+
181 sub parse {
182 my ( $self, $ddl, $opts ) = @_;
183 return unless $ddl;
184- if ( ref $ddl eq 'ARRAY' ) {
185- if ( lc $ddl->[0] eq 'table' ) {
186- $ddl = $ddl->[1];
187- }
188- else {
189- return {
190- engine => 'VIEW',
191- };
192- }
193- }
194
195 if ( $ddl !~ m/CREATE (?:TEMPORARY )?TABLE `/ ) {
196 die "Cannot parse table definition; is ANSI quoting "
197@@ -518,41 +557,31 @@
198 return $ddl;
199 }
200
201-sub remove_secondary_indexes {
202- my ( $self, $ddl ) = @_;
203- my $sec_indexes_ddl;
204- my $tbl_struct = $self->parse($ddl);
205-
206- if ( ($tbl_struct->{engine} || '') =~ m/InnoDB/i ) {
207- my $clustered_key = $tbl_struct->{clustered_key};
208- $clustered_key ||= '';
209-
210- my @sec_indexes = map {
211- my $key_def = $_->{ddl};
212- $key_def =~ s/([\(\)])/\\$1/g;
213- $ddl =~ s/\s+$key_def//i;
214-
215- my $key_ddl = "ADD $_->{ddl}";
216- $key_ddl .= ',' unless $key_ddl =~ m/,$/;
217- $key_ddl;
218- }
219- grep { $_->{name} ne $clustered_key }
220- values %{$tbl_struct->{keys}};
221- PTDEBUG && _d('Secondary indexes:', Dumper(\@sec_indexes));
222-
223- if ( @sec_indexes ) {
224- $sec_indexes_ddl = join(' ', @sec_indexes);
225- $sec_indexes_ddl =~ s/,$//;
226- }
227-
228- $ddl =~ s/,(\n\) )/$1/s;
229- }
230- else {
231- PTDEBUG && _d('Not removing secondary indexes from',
232- $tbl_struct->{engine}, 'table');
233- }
234-
235- return $ddl, $sec_indexes_ddl, $tbl_struct;
236+sub get_table_status {
237+ my ( $self, $dbh, $db, $like ) = @_;
238+ my $q = $self->{Quoter};
239+ my $sql = "SHOW TABLE STATUS FROM " . $q->quote($db);
240+ my @params;
241+ if ( $like ) {
242+ $sql .= ' LIKE ?';
243+ push @params, $like;
244+ }
245+ PTDEBUG && _d($sql, @params);
246+ my $sth = $dbh->prepare($sql);
247+ eval { $sth->execute(@params); };
248+ if ($EVAL_ERROR) {
249+ PTDEBUG && _d($EVAL_ERROR);
250+ return;
251+ }
252+ my @tables = @{$sth->fetchall_arrayref({})};
253+ @tables = map {
254+ my %tbl; # Make a copy with lowercased keys
255+ @tbl{ map { lc $_ } keys %$_ } = values %$_;
256+ $tbl{engine} ||= $tbl{type} || $tbl{comment};
257+ delete $tbl{type};
258+ \%tbl;
259+ } @tables;
260+ return @tables;
261 }
262
263 sub _d {
264@@ -3200,7 +3229,7 @@
265
266 sub new {
267 my ( $class, %args ) = @_;
268- my @required_args = qw(OptionParser Quoter);
269+ my @required_args = qw(OptionParser TableParser Quoter);
270 foreach my $arg ( @required_args ) {
271 die "I need a $arg argument" unless $args{$arg};
272 }
273@@ -3209,8 +3238,19 @@
274 die "I need either a dbh or file_itr argument"
275 if (!$dbh && !$file_itr) || ($dbh && $file_itr);
276
277+ my %resume;
278+ if ( my $table = $args{resume} ) {
279+ PTDEBUG && _d('Will resume from or after', $table);
280+ my ($db, $tbl) = $args{Quoter}->split_unquote($table);
281+ die "Resume table must be database-qualified: $table"
282+ unless $db && $tbl;
283+ $resume{db} = $db;
284+ $resume{tbl} = $tbl;
285+ }
286+
287 my $self = {
288 %args,
289+ resume => \%resume,
290 filters => _make_filters(%args),
291 };
292
293@@ -3271,9 +3311,19 @@
294 return \%filters;
295 }
296
297-sub next_schema_object {
298+sub next {
299 my ( $self ) = @_;
300
301+ if ( !$self->{initialized} ) {
302+ $self->{initialized} = 1;
303+ if ( $self->{resume}->{tbl}
304+ && !$self->table_is_allowed(@{$self->{resume}}{qw(db tbl)}) ) {
305+ PTDEBUG && _d('Will resume after',
306+ join('.', @{$self->{resume}}{qw(db tbl)}));
307+ $self->{resume}->{after} = 1;
308+ }
309+ }
310+
311 my $schema_obj;
312 if ( $self->{file_itr} ) {
313 $schema_obj= $self->_iterate_files();
314@@ -3283,19 +3333,13 @@
315 }
316
317 if ( $schema_obj ) {
318- if ( $schema_obj->{ddl} && $self->{TableParser} ) {
319- $schema_obj->{tbl_struct}
320- = $self->{TableParser}->parse($schema_obj->{ddl});
321- }
322-
323- delete $schema_obj->{ddl} unless $self->{keep_ddl};
324-
325 if ( my $schema = $self->{Schema} ) {
326 $schema->add_schema_object($schema_obj);
327 }
328+ PTDEBUG && _d('Next schema object:',
329+ $schema_obj->{db}, $schema_obj->{tbl});
330 }
331
332- PTDEBUG && _d('Next schema object:', $schema_obj->{db}, $schema_obj->{tbl});
333 return $schema_obj;
334 }
335
336@@ -3321,7 +3365,8 @@
337 my $db = $1; # XXX
338 $db =~ s/^`//; # strip leading `
339 $db =~ s/`$//; # and trailing `
340- if ( $self->database_is_allowed($db) ) {
341+ if ( $self->database_is_allowed($db)
342+ && $self->_resume_from_database($db) ) {
343 $self->{db} = $db;
344 }
345 }
346@@ -3334,21 +3379,22 @@
347 my ($tbl) = $chunk =~ m/$tbl_name/;
348 $tbl =~ s/^\s*`//;
349 $tbl =~ s/`\s*$//;
350- if ( $self->table_is_allowed($self->{db}, $tbl) ) {
351+ if ( $self->_resume_from_table($tbl)
352+ && $self->table_is_allowed($self->{db}, $tbl) ) {
353 my ($ddl) = $chunk =~ m/^(?:$open_comment)?(CREATE TABLE.+?;)$/ms;
354 if ( !$ddl ) {
355 warn "Failed to parse CREATE TABLE from\n" . $chunk;
356 next CHUNK;
357 }
358 $ddl =~ s/ \*\/;\Z/;/; # remove end of version comment
359-
360- my ($engine) = $ddl =~ m/\).*?(?:ENGINE|TYPE)=(\w+)/;
361-
362- if ( !$engine || $self->engine_is_allowed($engine) ) {
363+ my $tbl_struct = $self->{TableParser}->parse($ddl);
364+ if ( $self->engine_is_allowed($tbl_struct->{engine}) ) {
365 return {
366- db => $self->{db},
367- tbl => $tbl,
368- ddl => $ddl,
369+ db => $self->{db},
370+ tbl => $tbl,
371+ name => $self->{Quoter}->quote($self->{db}, $tbl),
372+ ddl => $ddl,
373+ tbl_struct => $tbl_struct,
374 };
375 }
376 }
377@@ -3365,6 +3411,7 @@
378 sub _iterate_dbh {
379 my ( $self ) = @_;
380 my $q = $self->{Quoter};
381+ my $tp = $self->{TableParser};
382 my $dbh = $self->{dbh};
383 PTDEBUG && _d('Getting next schema object from dbh', $dbh);
384
385@@ -3378,7 +3425,9 @@
386 }
387
388 if ( !$self->{db} ) {
389- $self->{db} = shift @{$self->{dbs}};
390+ do {
391+ $self->{db} = shift @{$self->{dbs}};
392+ } until $self->_resume_from_database($self->{db});
393 PTDEBUG && _d('Next database:', $self->{db});
394 return unless $self->{db};
395 }
396@@ -3391,8 +3440,9 @@
397 }
398 grep {
399 my ($tbl, $type) = @$_;
400- $self->table_is_allowed($self->{db}, $tbl)
401- && (!$type || ($type ne 'VIEW'));
402+ (!$type || ($type ne 'VIEW'))
403+ && $self->_resume_from_table($tbl)
404+ && $self->table_is_allowed($self->{db}, $tbl);
405 }
406 @{$dbh->selectall_arrayref($sql)};
407 PTDEBUG && _d('Found', scalar @tbls, 'tables in database', $self->{db});
408@@ -3400,27 +3450,15 @@
409 }
410
411 while ( my $tbl = shift @{$self->{tbls}} ) {
412- my $engine;
413- if ( $self->{filters}->{'engines'}
414- || $self->{filters}->{'ignore-engines'} ) {
415- my $sql = "SHOW TABLE STATUS FROM " . $q->quote($self->{db})
416- . " LIKE \'$tbl\'";
417- PTDEBUG && _d($sql);
418- $engine = $dbh->selectrow_hashref($sql)->{engine};
419- PTDEBUG && _d($tbl, 'uses', $engine, 'engine');
420- }
421-
422-
423- if ( !$engine || $self->engine_is_allowed($engine) ) {
424- my $ddl;
425- if ( my $du = $self->{MySQLDump} ) {
426- $ddl = $du->get_create_table($dbh, $q, $self->{db}, $tbl)->[1];
427- }
428-
429+ my $ddl = $tp->get_create_table($dbh, $self->{db}, $tbl);
430+ my $tbl_struct = $tp->parse($ddl);
431+ if ( $self->engine_is_allowed($tbl_struct->{engine}) ) {
432 return {
433- db => $self->{db},
434- tbl => $tbl,
435- ddl => $ddl,
436+ db => $self->{db},
437+ tbl => $tbl,
438+ name => $q->quote($self->{db}, $tbl),
439+ ddl => $ddl,
440+ tbl_struct => $tbl_struct,
441 };
442 }
443 }
444@@ -3481,6 +3519,10 @@
445
446 my $filter = $self->{filters};
447
448+ if ( $db eq 'mysql' && ($tbl eq 'general_log' || $tbl eq 'slow_log') ) {
449+ return 0;
450+ }
451+
452 if ( $filter->{'ignore-tables'}->{$tbl}
453 && ($filter->{'ignore-tables'}->{$tbl} eq '*'
454 || $filter->{'ignore-tables'}->{$tbl} eq $db) ) {
455@@ -3520,7 +3562,11 @@
456
457 sub engine_is_allowed {
458 my ( $self, $engine ) = @_;
459- die "I need an engine argument" unless $engine;
460+
461+ if ( !$engine ) {
462+ PTDEBUG && _d('No engine specified; allowing the table');
463+ return 1;
464+ }
465
466 $engine = lc $engine;
467
468@@ -3540,6 +3586,40 @@
469 return 1;
470 }
471
472+sub _resume_from_database {
473+ my ($self, $db) = @_;
474+
475+ return 1 unless $self->{resume}->{db};
476+
477+ if ( $db eq $self->{resume}->{db} ) {
478+ PTDEBUG && _d('At resume db', $db);
479+ delete $self->{resume}->{db};
480+ return 1;
481+ }
482+
483+ return 0;
484+}
485+
486+sub _resume_from_table {
487+ my ($self, $tbl) = @_;
488+
489+ return 1 unless $self->{resume}->{tbl};
490+
491+ if ( $tbl eq $self->{resume}->{tbl} ) {
492+ if ( !$self->{resume}->{after} ) {
493+ PTDEBUG && _d('Resuming from table', $tbl);
494+ delete $self->{resume}->{tbl};
495+ return 1;
496+ }
497+ else {
498+ PTDEBUG && _d('Resuming after table', $tbl);
499+ delete $self->{resume}->{tbl};
500+ }
501+ }
502+
503+ return 0;
504+}
505+
506 sub _d {
507 my ($package, undef, $line) = caller 0;
508 @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
509@@ -3649,11 +3729,10 @@
510 MySQLDump => $du,
511 TableParser => $tp,
512 Schema => $schema,
513- keep_ddl => 1,
514 );
515 TABLE:
516- while ( my $tbl = $schema_itr->next_schema_object() ) {
517- $tbl->{engine} = $tp->get_engine($tbl->{ddl});
518+ while ( my $tbl = $schema_itr->next() ) {
519+ $tbl->{engine} = $tbl->{tbl_struct}->{engine};
520
521 my ($keys, $clustered_key, $fks);
522 if ( $get_keys ) {
523@@ -4279,6 +4358,6 @@
524
525 =head1 VERSION
526
527-pt-duplicate-key-checker 2.0.4
528+pt-duplicate-key-checker 2.1.1
529
530 =cut
531
532=== modified file 'bin/pt-fifo-split'
533--- bin/pt-fifo-split 2012-03-07 23:41:54 +0000
534+++ bin/pt-fifo-split 2012-06-01 14:38:21 +0000
535@@ -1547,6 +1547,6 @@
536
537 =head1 VERSION
538
539-pt-fifo-split 2.0.4
540+pt-fifo-split 2.1.1
541
542 =cut
543
544=== modified file 'bin/pt-find'
545--- bin/pt-find 2012-05-24 17:52:01 +0000
546+++ bin/pt-find 2012-06-01 14:38:21 +0000
547@@ -3827,6 +3827,6 @@
548
549 =head1 VERSION
550
551-pt-find 2.0.4
552+pt-find 2.1.1
553
554 =cut
555
556=== added file 'bin/pt-fingerprint'
557--- bin/pt-fingerprint 1970-01-01 00:00:00 +0000
558+++ bin/pt-fingerprint 2012-06-01 14:38:21 +0000
559@@ -0,0 +1,2143 @@
560+#!/usr/bin/env perl
561+
562+# This program is part of Percona Toolkit: http://www.percona.com/software/
563+# See "COPYRIGHT, LICENSE, AND WARRANTY" at the end of this file for legal
564+# notices and disclaimers.
565+
566+use strict;
567+use warnings FATAL => 'all';
568+use constant MKDEBUG => $ENV{MKDEBUG} || 0;
569+
570+# ###########################################################################
571+# OptionParser package
572+# This package is a copy without comments from the original. The original
573+# with comments and its test file can be found in the Bazaar repository at,
574+# lib/OptionParser.pm
575+# t/lib/OptionParser.t
576+# See https://launchpad.net/percona-toolkit for more information.
577+# ###########################################################################
578+{
579+package OptionParser;
580+
581+use strict;
582+use warnings FATAL => 'all';
583+use English qw(-no_match_vars);
584+use constant PTDEBUG => $ENV{PTDEBUG} || 0;
585+
586+use List::Util qw(max);
587+use Getopt::Long;
588+
589+my $POD_link_re = '[LC]<"?([^">]+)"?>';
590+
591+sub new {
592+ my ( $class, %args ) = @_;
593+ my @required_args = qw();
594+ foreach my $arg ( @required_args ) {
595+ die "I need a $arg argument" unless $args{$arg};
596+ }
597+
598+ my ($program_name) = $PROGRAM_NAME =~ m/([.A-Za-z-]+)$/;
599+ $program_name ||= $PROGRAM_NAME;
600+ my $home = $ENV{HOME} || $ENV{HOMEPATH} || $ENV{USERPROFILE} || '.';
601+
602+ my %attributes = (
603+ 'type' => 1,
604+ 'short form' => 1,
605+ 'group' => 1,
606+ 'default' => 1,
607+ 'cumulative' => 1,
608+ 'negatable' => 1,
609+ );
610+
611+ my $self = {
612+ head1 => 'OPTIONS', # These args are used internally
613+ skip_rules => 0, # to instantiate another Option-
614+ item => '--(.*)', # Parser obj that parses the
615+ attributes => \%attributes, # DSN OPTIONS section. Tools
616+ parse_attributes => \&_parse_attribs, # don't tinker with these args.
617+
618+ %args,
619+
620+ strict => 1, # disabled by a special rule
621+ program_name => $program_name,
622+ opts => {},
623+ got_opts => 0,
624+ short_opts => {},
625+ defaults => {},
626+ groups => {},
627+ allowed_groups => {},
628+ errors => [],
629+ rules => [], # desc of rules for --help
630+ mutex => [], # rule: opts are mutually exclusive
631+ atleast1 => [], # rule: at least one opt is required
632+ disables => {}, # rule: opt disables other opts
633+ defaults_to => {}, # rule: opt defaults to value of other opt
634+ DSNParser => undef,
635+ default_files => [
636+ "/etc/percona-toolkit/percona-toolkit.conf",
637+ "/etc/percona-toolkit/$program_name.conf",
638+ "$home/.percona-toolkit.conf",
639+ "$home/.$program_name.conf",
640+ ],
641+ types => {
642+ string => 's', # standard Getopt type
643+ int => 'i', # standard Getopt type
644+ float => 'f', # standard Getopt type
645+ Hash => 'H', # hash, formed from a comma-separated list
646+ hash => 'h', # hash as above, but only if a value is given
647+ Array => 'A', # array, similar to Hash
648+ array => 'a', # array, similar to hash
649+ DSN => 'd', # DSN
650+ size => 'z', # size with kMG suffix (powers of 2^10)
651+ time => 'm', # time, with an optional suffix of s/h/m/d
652+ },
653+ };
654+
655+ return bless $self, $class;
656+}
657+
658+sub get_specs {
659+ my ( $self, $file ) = @_;
660+ $file ||= $self->{file} || __FILE__;
661+ my @specs = $self->_pod_to_specs($file);
662+ $self->_parse_specs(@specs);
663+
664+ open my $fh, "<", $file or die "Cannot open $file: $OS_ERROR";
665+ my $contents = do { local $/ = undef; <$fh> };
666+ close $fh;
667+ if ( $contents =~ m/^=head1 DSN OPTIONS/m ) {
668+ PTDEBUG && _d('Parsing DSN OPTIONS');
669+ my $dsn_attribs = {
670+ dsn => 1,
671+ copy => 1,
672+ };
673+ my $parse_dsn_attribs = sub {
674+ my ( $self, $option, $attribs ) = @_;
675+ map {
676+ my $val = $attribs->{$_};
677+ if ( $val ) {
678+ $val = $val eq 'yes' ? 1
679+ : $val eq 'no' ? 0
680+ : $val;
681+ $attribs->{$_} = $val;
682+ }
683+ } keys %$attribs;
684+ return {
685+ key => $option,
686+ %$attribs,
687+ };
688+ };
689+ my $dsn_o = new OptionParser(
690+ description => 'DSN OPTIONS',
691+ head1 => 'DSN OPTIONS',
692+ dsn => 0, # XXX don't infinitely recurse!
693+ item => '\* (.)', # key opts are a single character
694+ skip_rules => 1, # no rules before opts
695+ attributes => $dsn_attribs,
696+ parse_attributes => $parse_dsn_attribs,
697+ );
698+ my @dsn_opts = map {
699+ my $opts = {
700+ key => $_->{spec}->{key},
701+ dsn => $_->{spec}->{dsn},
702+ copy => $_->{spec}->{copy},
703+ desc => $_->{desc},
704+ };
705+ $opts;
706+ } $dsn_o->_pod_to_specs($file);
707+ $self->{DSNParser} = DSNParser->new(opts => \@dsn_opts);
708+ }
709+
710+ if ( $contents =~ m/^=head1 VERSION\n\n^(.+)$/m ) {
711+ $self->{version} = $1;
712+ PTDEBUG && _d($self->{version});
713+ }
714+
715+ return;
716+}
717+
718+sub DSNParser {
719+ my ( $self ) = @_;
720+ return $self->{DSNParser};
721+};
722+
723+sub get_defaults_files {
724+ my ( $self ) = @_;
725+ return @{$self->{default_files}};
726+}
727+
728+sub _pod_to_specs {
729+ my ( $self, $file ) = @_;
730+ $file ||= $self->{file} || __FILE__;
731+ open my $fh, '<', $file or die "Cannot open $file: $OS_ERROR";
732+
733+ my @specs = ();
734+ my @rules = ();
735+ my $para;
736+
737+ local $INPUT_RECORD_SEPARATOR = '';
738+ while ( $para = <$fh> ) {
739+ next unless $para =~ m/^=head1 $self->{head1}/;
740+ last;
741+ }
742+
743+ while ( $para = <$fh> ) {
744+ last if $para =~ m/^=over/;
745+ next if $self->{skip_rules};
746+ chomp $para;
747+ $para =~ s/\s+/ /g;
748+ $para =~ s/$POD_link_re/$1/go;
749+ PTDEBUG && _d('Option rule:', $para);
750+ push @rules, $para;
751+ }
752+
753+ die "POD has no $self->{head1} section" unless $para;
754+
755+ do {
756+ if ( my ($option) = $para =~ m/^=item $self->{item}/ ) {
757+ chomp $para;
758+ PTDEBUG && _d($para);
759+ my %attribs;
760+
761+ $para = <$fh>; # read next paragraph, possibly attributes
762+
763+ if ( $para =~ m/: / ) { # attributes
764+ $para =~ s/\s+\Z//g;
765+ %attribs = map {
766+ my ( $attrib, $val) = split(/: /, $_);
767+ die "Unrecognized attribute for --$option: $attrib"
768+ unless $self->{attributes}->{$attrib};
769+ ($attrib, $val);
770+ } split(/; /, $para);
771+ if ( $attribs{'short form'} ) {
772+ $attribs{'short form'} =~ s/-//;
773+ }
774+ $para = <$fh>; # read next paragraph, probably short help desc
775+ }
776+ else {
777+ PTDEBUG && _d('Option has no attributes');
778+ }
779+
780+ $para =~ s/\s+\Z//g;
781+ $para =~ s/\s+/ /g;
782+ $para =~ s/$POD_link_re/$1/go;
783+
784+ $para =~ s/\.(?:\n.*| [A-Z].*|\Z)//s;
785+ PTDEBUG && _d('Short help:', $para);
786+
787+ die "No description after option spec $option" if $para =~ m/^=item/;
788+
789+ if ( my ($base_option) = $option =~ m/^\[no\](.*)/ ) {
790+ $option = $base_option;
791+ $attribs{'negatable'} = 1;
792+ }
793+
794+ push @specs, {
795+ spec => $self->{parse_attributes}->($self, $option, \%attribs),
796+ desc => $para
797+ . (defined $attribs{default} ? " (default $attribs{default})" : ''),
798+ group => ($attribs{'group'} ? $attribs{'group'} : 'default'),
799+ };
800+ }
801+ while ( $para = <$fh> ) {
802+ last unless $para;
803+ if ( $para =~ m/^=head1/ ) {
804+ $para = undef; # Can't 'last' out of a do {} block.
805+ last;
806+ }
807+ last if $para =~ m/^=item /;
808+ }
809+ } while ( $para );
810+
811+ die "No valid specs in $self->{head1}" unless @specs;
812+
813+ close $fh;
814+ return @specs, @rules;
815+}
816+
817+sub _parse_specs {
818+ my ( $self, @specs ) = @_;
819+ my %disables; # special rule that requires deferred checking
820+
821+ foreach my $opt ( @specs ) {
822+ if ( ref $opt ) { # It's an option spec, not a rule.
823+ PTDEBUG && _d('Parsing opt spec:',
824+ map { ($_, '=>', $opt->{$_}) } keys %$opt);
825+
826+ my ( $long, $short ) = $opt->{spec} =~ m/^([\w-]+)(?:\|([^!+=]*))?/;
827+ if ( !$long ) {
828+ die "Cannot parse long option from spec $opt->{spec}";
829+ }
830+ $opt->{long} = $long;
831+
832+ die "Duplicate long option --$long" if exists $self->{opts}->{$long};
833+ $self->{opts}->{$long} = $opt;
834+
835+ if ( length $long == 1 ) {
836+ PTDEBUG && _d('Long opt', $long, 'looks like short opt');
837+ $self->{short_opts}->{$long} = $long;
838+ }
839+
840+ if ( $short ) {
841+ die "Duplicate short option -$short"
842+ if exists $self->{short_opts}->{$short};
843+ $self->{short_opts}->{$short} = $long;
844+ $opt->{short} = $short;
845+ }
846+ else {
847+ $opt->{short} = undef;
848+ }
849+
850+ $opt->{is_negatable} = $opt->{spec} =~ m/!/ ? 1 : 0;
851+ $opt->{is_cumulative} = $opt->{spec} =~ m/\+/ ? 1 : 0;
852+ $opt->{is_required} = $opt->{desc} =~ m/required/ ? 1 : 0;
853+
854+ $opt->{group} ||= 'default';
855+ $self->{groups}->{ $opt->{group} }->{$long} = 1;
856+
857+ $opt->{value} = undef;
858+ $opt->{got} = 0;
859+
860+ my ( $type ) = $opt->{spec} =~ m/=(.)/;
861+ $opt->{type} = $type;
862+ PTDEBUG && _d($long, 'type:', $type);
863+
864+
865+ $opt->{spec} =~ s/=./=s/ if ( $type && $type =~ m/[HhAadzm]/ );
866+
867+ if ( (my ($def) = $opt->{desc} =~ m/default\b(?: ([^)]+))?/) ) {
868+ $self->{defaults}->{$long} = defined $def ? $def : 1;
869+ PTDEBUG && _d($long, 'default:', $def);
870+ }
871+
872+ if ( $long eq 'config' ) {
873+ $self->{defaults}->{$long} = join(',', $self->get_defaults_files());
874+ }
875+
876+ if ( (my ($dis) = $opt->{desc} =~ m/(disables .*)/) ) {
877+ $disables{$long} = $dis;
878+ PTDEBUG && _d('Deferring check of disables rule for', $opt, $dis);
879+ }
880+
881+ $self->{opts}->{$long} = $opt;
882+ }
883+ else { # It's an option rule, not a spec.
884+ PTDEBUG && _d('Parsing rule:', $opt);
885+ push @{$self->{rules}}, $opt;
886+ my @participants = $self->_get_participants($opt);
887+ my $rule_ok = 0;
888+
889+ if ( $opt =~ m/mutually exclusive|one and only one/ ) {
890+ $rule_ok = 1;
891+ push @{$self->{mutex}}, \@participants;
892+ PTDEBUG && _d(@participants, 'are mutually exclusive');
893+ }
894+ if ( $opt =~ m/at least one|one and only one/ ) {
895+ $rule_ok = 1;
896+ push @{$self->{atleast1}}, \@participants;
897+ PTDEBUG && _d(@participants, 'require at least one');
898+ }
899+ if ( $opt =~ m/default to/ ) {
900+ $rule_ok = 1;
901+ $self->{defaults_to}->{$participants[0]} = $participants[1];
902+ PTDEBUG && _d($participants[0], 'defaults to', $participants[1]);
903+ }
904+ if ( $opt =~ m/restricted to option groups/ ) {
905+ $rule_ok = 1;
906+ my ($groups) = $opt =~ m/groups ([\w\s\,]+)/;
907+ my @groups = split(',', $groups);
908+ %{$self->{allowed_groups}->{$participants[0]}} = map {
909+ s/\s+//;
910+ $_ => 1;
911+ } @groups;
912+ }
913+ if( $opt =~ m/accepts additional command-line arguments/ ) {
914+ $rule_ok = 1;
915+ $self->{strict} = 0;
916+ PTDEBUG && _d("Strict mode disabled by rule");
917+ }
918+
919+ die "Unrecognized option rule: $opt" unless $rule_ok;
920+ }
921+ }
922+
923+ foreach my $long ( keys %disables ) {
924+ my @participants = $self->_get_participants($disables{$long});
925+ $self->{disables}->{$long} = \@participants;
926+ PTDEBUG && _d('Option', $long, 'disables', @participants);
927+ }
928+
929+ return;
930+}
931+
932+sub _get_participants {
933+ my ( $self, $str ) = @_;
934+ my @participants;
935+ foreach my $long ( $str =~ m/--(?:\[no\])?([\w-]+)/g ) {
936+ die "Option --$long does not exist while processing rule $str"
937+ unless exists $self->{opts}->{$long};
938+ push @participants, $long;
939+ }
940+ PTDEBUG && _d('Participants for', $str, ':', @participants);
941+ return @participants;
942+}
943+
944+sub opts {
945+ my ( $self ) = @_;
946+ my %opts = %{$self->{opts}};
947+ return %opts;
948+}
949+
950+sub short_opts {
951+ my ( $self ) = @_;
952+ my %short_opts = %{$self->{short_opts}};
953+ return %short_opts;
954+}
955+
956+sub set_defaults {
957+ my ( $self, %defaults ) = @_;
958+ $self->{defaults} = {};
959+ foreach my $long ( keys %defaults ) {
960+ die "Cannot set default for nonexistent option $long"
961+ unless exists $self->{opts}->{$long};
962+ $self->{defaults}->{$long} = $defaults{$long};
963+ PTDEBUG && _d('Default val for', $long, ':', $defaults{$long});
964+ }
965+ return;
966+}
967+
968+sub get_defaults {
969+ my ( $self ) = @_;
970+ return $self->{defaults};
971+}
972+
973+sub get_groups {
974+ my ( $self ) = @_;
975+ return $self->{groups};
976+}
977+
978+sub _set_option {
979+ my ( $self, $opt, $val ) = @_;
980+ my $long = exists $self->{opts}->{$opt} ? $opt
981+ : exists $self->{short_opts}->{$opt} ? $self->{short_opts}->{$opt}
982+ : die "Getopt::Long gave a nonexistent option: $opt";
983+
984+ $opt = $self->{opts}->{$long};
985+ if ( $opt->{is_cumulative} ) {
986+ $opt->{value}++;
987+ }
988+ else {
989+ $opt->{value} = $val;
990+ }
991+ $opt->{got} = 1;
992+ PTDEBUG && _d('Got option', $long, '=', $val);
993+}
994+
995+sub get_opts {
996+ my ( $self ) = @_;
997+
998+ foreach my $long ( keys %{$self->{opts}} ) {
999+ $self->{opts}->{$long}->{got} = 0;
1000+ $self->{opts}->{$long}->{value}
1001+ = exists $self->{defaults}->{$long} ? $self->{defaults}->{$long}
1002+ : $self->{opts}->{$long}->{is_cumulative} ? 0
1003+ : undef;
1004+ }
1005+ $self->{got_opts} = 0;
1006+
1007+ $self->{errors} = [];
1008+
1009+ if ( @ARGV && $ARGV[0] eq "--config" ) {
1010+ shift @ARGV;
1011+ $self->_set_option('config', shift @ARGV);
1012+ }
1013+ if ( $self->has('config') ) {
1014+ my @extra_args;
1015+ foreach my $filename ( split(',', $self->get('config')) ) {
1016+ eval {
1017+ push @extra_args, $self->_read_config_file($filename);
1018+ };
1019+ if ( $EVAL_ERROR ) {
1020+ if ( $self->got('config') ) {
1021+ die $EVAL_ERROR;
1022+ }
1023+ elsif ( PTDEBUG ) {
1024+ _d($EVAL_ERROR);
1025+ }
1026+ }
1027+ }
1028+ unshift @ARGV, @extra_args;
1029+ }
1030+
1031+ Getopt::Long::Configure('no_ignore_case', 'bundling');
1032+ GetOptions(
1033+ map { $_->{spec} => sub { $self->_set_option(@_); } }
1034+ grep { $_->{long} ne 'config' } # --config is handled specially above.
1035+ values %{$self->{opts}}
1036+ ) or $self->save_error('Error parsing options');
1037+
1038+ if ( exists $self->{opts}->{version} && $self->{opts}->{version}->{got} ) {
1039+ if ( $self->{version} ) {
1040+ print $self->{version}, "\n";
1041+ }
1042+ else {
1043+ print "Error parsing version. See the VERSION section of the tool's documentation.\n";
1044+ }
1045+ exit 0;
1046+ }
1047+
1048+ if ( @ARGV && $self->{strict} ) {
1049+ $self->save_error("Unrecognized command-line options @ARGV");
1050+ }
1051+
1052+ foreach my $mutex ( @{$self->{mutex}} ) {
1053+ my @set = grep { $self->{opts}->{$_}->{got} } @$mutex;
1054+ if ( @set > 1 ) {
1055+ my $err = join(', ', map { "--$self->{opts}->{$_}->{long}" }
1056+ @{$mutex}[ 0 .. scalar(@$mutex) - 2] )
1057+ . ' and --'.$self->{opts}->{$mutex->[-1]}->{long}
1058+ . ' are mutually exclusive.';
1059+ $self->save_error($err);
1060+ }
1061+ }
1062+
1063+ foreach my $required ( @{$self->{atleast1}} ) {
1064+ my @set = grep { $self->{opts}->{$_}->{got} } @$required;
1065+ if ( @set == 0 ) {
1066+ my $err = join(', ', map { "--$self->{opts}->{$_}->{long}" }
1067+ @{$required}[ 0 .. scalar(@$required) - 2] )
1068+ .' or --'.$self->{opts}->{$required->[-1]}->{long};
1069+ $self->save_error("Specify at least one of $err");
1070+ }
1071+ }
1072+
1073+ $self->_check_opts( keys %{$self->{opts}} );
1074+ $self->{got_opts} = 1;
1075+ return;
1076+}
1077+
1078+sub _check_opts {
1079+ my ( $self, @long ) = @_;
1080+ my $long_last = scalar @long;
1081+ while ( @long ) {
1082+ foreach my $i ( 0..$#long ) {
1083+ my $long = $long[$i];
1084+ next unless $long;
1085+ my $opt = $self->{opts}->{$long};
1086+ if ( $opt->{got} ) {
1087+ if ( exists $self->{disables}->{$long} ) {
1088+ my @disable_opts = @{$self->{disables}->{$long}};
1089+ map { $self->{opts}->{$_}->{value} = undef; } @disable_opts;
1090+ PTDEBUG && _d('Unset options', @disable_opts,
1091+ 'because', $long,'disables them');
1092+ }
1093+
1094+ if ( exists $self->{allowed_groups}->{$long} ) {
1095+
1096+ my @restricted_groups = grep {
1097+ !exists $self->{allowed_groups}->{$long}->{$_}
1098+ } keys %{$self->{groups}};
1099+
1100+ my @restricted_opts;
1101+ foreach my $restricted_group ( @restricted_groups ) {
1102+ RESTRICTED_OPT:
1103+ foreach my $restricted_opt (
1104+ keys %{$self->{groups}->{$restricted_group}} )
1105+ {
1106+ next RESTRICTED_OPT if $restricted_opt eq $long;
1107+ push @restricted_opts, $restricted_opt
1108+ if $self->{opts}->{$restricted_opt}->{got};
1109+ }
1110+ }
1111+
1112+ if ( @restricted_opts ) {
1113+ my $err;
1114+ if ( @restricted_opts == 1 ) {
1115+ $err = "--$restricted_opts[0]";
1116+ }
1117+ else {
1118+ $err = join(', ',
1119+ map { "--$self->{opts}->{$_}->{long}" }
1120+ grep { $_ }
1121+ @restricted_opts[0..scalar(@restricted_opts) - 2]
1122+ )
1123+ . ' or --'.$self->{opts}->{$restricted_opts[-1]}->{long};
1124+ }
1125+ $self->save_error("--$long is not allowed with $err");
1126+ }
1127+ }
1128+
1129+ }
1130+ elsif ( $opt->{is_required} ) {
1131+ $self->save_error("Required option --$long must be specified");
1132+ }
1133+
1134+ $self->_validate_type($opt);
1135+ if ( $opt->{parsed} ) {
1136+ delete $long[$i];
1137+ }
1138+ else {
1139+ PTDEBUG && _d('Temporarily failed to parse', $long);
1140+ }
1141+ }
1142+
1143+ die "Failed to parse options, possibly due to circular dependencies"
1144+ if @long == $long_last;
1145+ $long_last = @long;
1146+ }
1147+
1148+ return;
1149+}
1150+
1151+sub _validate_type {
1152+ my ( $self, $opt ) = @_;
1153+ return unless $opt;
1154+
1155+ if ( !$opt->{type} ) {
1156+ $opt->{parsed} = 1;
1157+ return;
1158+ }
1159+
1160+ my $val = $opt->{value};
1161+
1162+ if ( $val && $opt->{type} eq 'm' ) { # type time
1163+ PTDEBUG && _d('Parsing option', $opt->{long}, 'as a time value');
1164+ my ( $prefix, $num, $suffix ) = $val =~ m/([+-]?)(\d+)([a-z])?$/;
1165+ if ( !$suffix ) {
1166+ my ( $s ) = $opt->{desc} =~ m/\(suffix (.)\)/;
1167+ $suffix = $s || 's';
1168+ PTDEBUG && _d('No suffix given; using', $suffix, 'for',
1169+ $opt->{long}, '(value:', $val, ')');
1170+ }
1171+ if ( $suffix =~ m/[smhd]/ ) {
1172+ $val = $suffix eq 's' ? $num # Seconds
1173+ : $suffix eq 'm' ? $num * 60 # Minutes
1174+ : $suffix eq 'h' ? $num * 3600 # Hours
1175+ : $num * 86400; # Days
1176+ $opt->{value} = ($prefix || '') . $val;
1177+ PTDEBUG && _d('Setting option', $opt->{long}, 'to', $val);
1178+ }
1179+ else {
1180+ $self->save_error("Invalid time suffix for --$opt->{long}");
1181+ }
1182+ }
1183+ elsif ( $val && $opt->{type} eq 'd' ) { # type DSN
1184+ PTDEBUG && _d('Parsing option', $opt->{long}, 'as a DSN');
1185+ my $prev = {};
1186+ my $from_key = $self->{defaults_to}->{ $opt->{long} };
1187+ if ( $from_key ) {
1188+ PTDEBUG && _d($opt->{long}, 'DSN copies from', $from_key, 'DSN');
1189+ if ( $self->{opts}->{$from_key}->{parsed} ) {
1190+ $prev = $self->{opts}->{$from_key}->{value};
1191+ }
1192+ else {
1193+ PTDEBUG && _d('Cannot parse', $opt->{long}, 'until',
1194+ $from_key, 'parsed');
1195+ return;
1196+ }
1197+ }
1198+ my $defaults = $self->{DSNParser}->parse_options($self);
1199+ $opt->{value} = $self->{DSNParser}->parse($val, $prev, $defaults);
1200+ }
1201+ elsif ( $val && $opt->{type} eq 'z' ) { # type size
1202+ PTDEBUG && _d('Parsing option', $opt->{long}, 'as a size value');
1203+ $self->_parse_size($opt, $val);
1204+ }
1205+ elsif ( $opt->{type} eq 'H' || (defined $val && $opt->{type} eq 'h') ) {
1206+ $opt->{value} = { map { $_ => 1 } split(/(?<!\\),\s*/, ($val || '')) };
1207+ }
1208+ elsif ( $opt->{type} eq 'A' || (defined $val && $opt->{type} eq 'a') ) {
1209+ $opt->{value} = [ split(/(?<!\\),\s*/, ($val || '')) ];
1210+ }
1211+ else {
1212+ PTDEBUG && _d('Nothing to validate for option',
1213+ $opt->{long}, 'type', $opt->{type}, 'value', $val);
1214+ }
1215+
1216+ $opt->{parsed} = 1;
1217+ return;
1218+}
1219+
1220+sub get {
1221+ my ( $self, $opt ) = @_;
1222+ my $long = (length $opt == 1 ? $self->{short_opts}->{$opt} : $opt);
1223+ die "Option $opt does not exist"
1224+ unless $long && exists $self->{opts}->{$long};
1225+ return $self->{opts}->{$long}->{value};
1226+}
1227+
1228+sub got {
1229+ my ( $self, $opt ) = @_;
1230+ my $long = (length $opt == 1 ? $self->{short_opts}->{$opt} : $opt);
1231+ die "Option $opt does not exist"
1232+ unless $long && exists $self->{opts}->{$long};
1233+ return $self->{opts}->{$long}->{got};
1234+}
1235+
1236+sub has {
1237+ my ( $self, $opt ) = @_;
1238+ my $long = (length $opt == 1 ? $self->{short_opts}->{$opt} : $opt);
1239+ return defined $long ? exists $self->{opts}->{$long} : 0;
1240+}
1241+
1242+sub set {
1243+ my ( $self, $opt, $val ) = @_;
1244+ my $long = (length $opt == 1 ? $self->{short_opts}->{$opt} : $opt);
1245+ die "Option $opt does not exist"
1246+ unless $long && exists $self->{opts}->{$long};
1247+ $self->{opts}->{$long}->{value} = $val;
1248+ return;
1249+}
1250+
1251+sub save_error {
1252+ my ( $self, $error ) = @_;
1253+ push @{$self->{errors}}, $error;
1254+ return;
1255+}
1256+
1257+sub errors {
1258+ my ( $self ) = @_;
1259+ return $self->{errors};
1260+}
1261+
1262+sub usage {
1263+ my ( $self ) = @_;
1264+ warn "No usage string is set" unless $self->{usage}; # XXX
1265+ return "Usage: " . ($self->{usage} || '') . "\n";
1266+}
1267+
1268+sub descr {
1269+ my ( $self ) = @_;
1270+ warn "No description string is set" unless $self->{description}; # XXX
1271+ my $descr = ($self->{description} || $self->{program_name} || '')
1272+ . " For more details, please use the --help option, "
1273+ . "or try 'perldoc $PROGRAM_NAME' "
1274+ . "for complete documentation.";
1275+ $descr = join("\n", $descr =~ m/(.{0,80})(?:\s+|$)/g)
1276+ unless $ENV{DONT_BREAK_LINES};
1277+ $descr =~ s/ +$//mg;
1278+ return $descr;
1279+}
1280+
1281+sub usage_or_errors {
1282+ my ( $self, $file, $return ) = @_;
1283+ $file ||= $self->{file} || __FILE__;
1284+
1285+ if ( !$self->{description} || !$self->{usage} ) {
1286+ PTDEBUG && _d("Getting description and usage from SYNOPSIS in", $file);
1287+ my %synop = $self->_parse_synopsis($file);
1288+ $self->{description} ||= $synop{description};
1289+ $self->{usage} ||= $synop{usage};
1290+ PTDEBUG && _d("Description:", $self->{description},
1291+ "\nUsage:", $self->{usage});
1292+ }
1293+
1294+ if ( $self->{opts}->{help}->{got} ) {
1295+ print $self->print_usage() or die "Cannot print usage: $OS_ERROR";
1296+ exit 0 unless $return;
1297+ }
1298+ elsif ( scalar @{$self->{errors}} ) {
1299+ print $self->print_errors() or die "Cannot print errors: $OS_ERROR";
1300+ exit 0 unless $return;
1301+ }
1302+
1303+ return;
1304+}
1305+
1306+sub print_errors {
1307+ my ( $self ) = @_;
1308+ my $usage = $self->usage() . "\n";
1309+ if ( (my @errors = @{$self->{errors}}) ) {
1310+ $usage .= join("\n * ", 'Errors in command-line arguments:', @errors)
1311+ . "\n";
1312+ }
1313+ return $usage . "\n" . $self->descr();
1314+}
1315+
1316+sub print_usage {
1317+ my ( $self ) = @_;
1318+ die "Run get_opts() before print_usage()" unless $self->{got_opts};
1319+ my @opts = values %{$self->{opts}};
1320+
1321+ my $maxl = max(
1322+ map {
1323+ length($_->{long}) # option long name
1324+ + ($_->{is_negatable} ? 4 : 0) # "[no]" if opt is negatable
1325+ + ($_->{type} ? 2 : 0) # "=x" where x is the opt type
1326+ }
1327+ @opts);
1328+
1329+ my $maxs = max(0,
1330+ map {
1331+ length($_)
1332+ + ($self->{opts}->{$_}->{is_negatable} ? 4 : 0)
1333+ + ($self->{opts}->{$_}->{type} ? 2 : 0)
1334+ }
1335+ values %{$self->{short_opts}});
1336+
1337+ my $lcol = max($maxl, ($maxs + 3));
1338+ my $rcol = 80 - $lcol - 6;
1339+ my $rpad = ' ' x ( 80 - $rcol );
1340+
1341+ $maxs = max($lcol - 3, $maxs);
1342+
1343+ my $usage = $self->descr() . "\n" . $self->usage();
1344+
1345+ my @groups = reverse sort grep { $_ ne 'default'; } keys %{$self->{groups}};
1346+ push @groups, 'default';
1347+
1348+ foreach my $group ( reverse @groups ) {
1349+ $usage .= "\n".($group eq 'default' ? 'Options' : $group).":\n\n";
1350+ foreach my $opt (
1351+ sort { $a->{long} cmp $b->{long} }
1352+ grep { $_->{group} eq $group }
1353+ @opts )
1354+ {
1355+ my $long = $opt->{is_negatable} ? "[no]$opt->{long}" : $opt->{long};
1356+ my $short = $opt->{short};
1357+ my $desc = $opt->{desc};
1358+
1359+ $long .= $opt->{type} ? "=$opt->{type}" : "";
1360+
1361+ if ( $opt->{type} && $opt->{type} eq 'm' ) {
1362+ my ($s) = $desc =~ m/\(suffix (.)\)/;
1363+ $s ||= 's';
1364+ $desc =~ s/\s+\(suffix .\)//;
1365+ $desc .= ". Optional suffix s=seconds, m=minutes, h=hours, "
1366+ . "d=days; if no suffix, $s is used.";
1367+ }
1368+ $desc = join("\n$rpad", grep { $_ } $desc =~ m/(.{0,$rcol})(?:\s+|$)/g);
1369+ $desc =~ s/ +$//mg;
1370+ if ( $short ) {
1371+ $usage .= sprintf(" --%-${maxs}s -%s %s\n", $long, $short, $desc);
1372+ }
1373+ else {
1374+ $usage .= sprintf(" --%-${lcol}s %s\n", $long, $desc);
1375+ }
1376+ }
1377+ }
1378+
1379+ $usage .= "\nOption types: s=string, i=integer, f=float, h/H/a/A=comma-separated list, d=DSN, z=size, m=time\n";
1380+
1381+ if ( (my @rules = @{$self->{rules}}) ) {
1382+ $usage .= "\nRules:\n\n";
1383+ $usage .= join("\n", map { " $_" } @rules) . "\n";
1384+ }
1385+ if ( $self->{DSNParser} ) {
1386+ $usage .= "\n" . $self->{DSNParser}->usage();
1387+ }
1388+ $usage .= "\nOptions and values after processing arguments:\n\n";
1389+ foreach my $opt ( sort { $a->{long} cmp $b->{long} } @opts ) {
1390+ my $val = $opt->{value};
1391+ my $type = $opt->{type} || '';
1392+ my $bool = $opt->{spec} =~ m/^[\w-]+(?:\|[\w-])?!?$/;
1393+ $val = $bool ? ( $val ? 'TRUE' : 'FALSE' )
1394+ : !defined $val ? '(No value)'
1395+ : $type eq 'd' ? $self->{DSNParser}->as_string($val)
1396+ : $type =~ m/H|h/ ? join(',', sort keys %$val)
1397+ : $type =~ m/A|a/ ? join(',', @$val)
1398+ : $val;
1399+ $usage .= sprintf(" --%-${lcol}s %s\n", $opt->{long}, $val);
1400+ }
1401+ return $usage;
1402+}
1403+
1404+sub prompt_noecho {
1405+ shift @_ if ref $_[0] eq __PACKAGE__;
1406+ my ( $prompt ) = @_;
1407+ local $OUTPUT_AUTOFLUSH = 1;
1408+ print $prompt
1409+ or die "Cannot print: $OS_ERROR";
1410+ my $response;
1411+ eval {
1412+ require Term::ReadKey;
1413+ Term::ReadKey::ReadMode('noecho');
1414+ chomp($response = <STDIN>);
1415+ Term::ReadKey::ReadMode('normal');
1416+ print "\n"
1417+ or die "Cannot print: $OS_ERROR";
1418+ };
1419+ if ( $EVAL_ERROR ) {
1420+ die "Cannot read response; is Term::ReadKey installed? $EVAL_ERROR";
1421+ }
1422+ return $response;
1423+}
1424+
1425+sub _read_config_file {
1426+ my ( $self, $filename ) = @_;
1427+ open my $fh, "<", $filename or die "Cannot open $filename: $OS_ERROR\n";
1428+ my @args;
1429+ my $prefix = '--';
1430+ my $parse = 1;
1431+
1432+ LINE:
1433+ while ( my $line = <$fh> ) {
1434+ chomp $line;
1435+ next LINE if $line =~ m/^\s*(?:\#|\;|$)/;
1436+ $line =~ s/\s+#.*$//g;
1437+ $line =~ s/^\s+|\s+$//g;
1438+ if ( $line eq '--' ) {
1439+ $prefix = '';
1440+ $parse = 0;
1441+ next LINE;
1442+ }
1443+ if ( $parse
1444+ && (my($opt, $arg) = $line =~ m/^\s*([^=\s]+?)(?:\s*=\s*(.*?)\s*)?$/)
1445+ ) {
1446+ push @args, grep { defined $_ } ("$prefix$opt", $arg);
1447+ }
1448+ elsif ( $line =~ m/./ ) {
1449+ push @args, $line;
1450+ }
1451+ else {
1452+ die "Syntax error in file $filename at line $INPUT_LINE_NUMBER";
1453+ }
1454+ }
1455+ close $fh;
1456+ return @args;
1457+}
1458+
1459+sub read_para_after {
1460+ my ( $self, $file, $regex ) = @_;
1461+ open my $fh, "<", $file or die "Can't open $file: $OS_ERROR";
1462+ local $INPUT_RECORD_SEPARATOR = '';
1463+ my $para;
1464+ while ( $para = <$fh> ) {
1465+ next unless $para =~ m/^=pod$/m;
1466+ last;
1467+ }
1468+ while ( $para = <$fh> ) {
1469+ next unless $para =~ m/$regex/;
1470+ last;
1471+ }
1472+ $para = <$fh>;
1473+ chomp($para);
1474+ close $fh or die "Can't close $file: $OS_ERROR";
1475+ return $para;
1476+}
1477+
1478+sub clone {
1479+ my ( $self ) = @_;
1480+
1481+ my %clone = map {
1482+ my $hashref = $self->{$_};
1483+ my $val_copy = {};
1484+ foreach my $key ( keys %$hashref ) {
1485+ my $ref = ref $hashref->{$key};
1486+ $val_copy->{$key} = !$ref ? $hashref->{$key}
1487+ : $ref eq 'HASH' ? { %{$hashref->{$key}} }
1488+ : $ref eq 'ARRAY' ? [ @{$hashref->{$key}} ]
1489+ : $hashref->{$key};
1490+ }
1491+ $_ => $val_copy;
1492+ } qw(opts short_opts defaults);
1493+
1494+ foreach my $scalar ( qw(got_opts) ) {
1495+ $clone{$scalar} = $self->{$scalar};
1496+ }
1497+
1498+ return bless \%clone;
1499+}
1500+
1501+sub _parse_size {
1502+ my ( $self, $opt, $val ) = @_;
1503+
1504+ if ( lc($val || '') eq 'null' ) {
1505+ PTDEBUG && _d('NULL size for', $opt->{long});
1506+ $opt->{value} = 'null';
1507+ return;
1508+ }
1509+
1510+ my %factor_for = (k => 1_024, M => 1_048_576, G => 1_073_741_824);
1511+ my ($pre, $num, $factor) = $val =~ m/^([+-])?(\d+)([kMG])?$/;
1512+ if ( defined $num ) {
1513+ if ( $factor ) {
1514+ $num *= $factor_for{$factor};
1515+ PTDEBUG && _d('Setting option', $opt->{y},
1516+ 'to num', $num, '* factor', $factor);
1517+ }
1518+ $opt->{value} = ($pre || '') . $num;
1519+ }
1520+ else {
1521+ $self->save_error("Invalid size for --$opt->{long}: $val");
1522+ }
1523+ return;
1524+}
1525+
1526+sub _parse_attribs {
1527+ my ( $self, $option, $attribs ) = @_;
1528+ my $types = $self->{types};
1529+ return $option
1530+ . ($attribs->{'short form'} ? '|' . $attribs->{'short form'} : '' )
1531+ . ($attribs->{'negatable'} ? '!' : '' )
1532+ . ($attribs->{'cumulative'} ? '+' : '' )
1533+ . ($attribs->{'type'} ? '=' . $types->{$attribs->{type}} : '' );
1534+}
1535+
1536+sub _parse_synopsis {
1537+ my ( $self, $file ) = @_;
1538+ $file ||= $self->{file} || __FILE__;
1539+ PTDEBUG && _d("Parsing SYNOPSIS in", $file);
1540+
1541+ local $INPUT_RECORD_SEPARATOR = ''; # read paragraphs
1542+ open my $fh, "<", $file or die "Cannot open $file: $OS_ERROR";
1543+ my $para;
1544+ 1 while defined($para = <$fh>) && $para !~ m/^=head1 SYNOPSIS/;
1545+ die "$file does not contain a SYNOPSIS section" unless $para;
1546+ my @synop;
1547+ for ( 1..2 ) { # 1 for the usage, 2 for the description
1548+ my $para = <$fh>;
1549+ push @synop, $para;
1550+ }
1551+ close $fh;
1552+ PTDEBUG && _d("Raw SYNOPSIS text:", @synop);
1553+ my ($usage, $desc) = @synop;
1554+ die "The SYNOPSIS section in $file is not formatted properly"
1555+ unless $usage && $desc;
1556+
1557+ $usage =~ s/^\s*Usage:\s+(.+)/$1/;
1558+ chomp $usage;
1559+
1560+ $desc =~ s/\n/ /g;
1561+ $desc =~ s/\s{2,}/ /g;
1562+ $desc =~ s/\. ([A-Z][a-z])/. $1/g;
1563+ $desc =~ s/\s+$//;
1564+
1565+ return (
1566+ description => $desc,
1567+ usage => $usage,
1568+ );
1569+};
1570+
1571+sub _d {
1572+ my ($package, undef, $line) = caller 0;
1573+ @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
1574+ map { defined $_ ? $_ : 'undef' }
1575+ @_;
1576+ print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
1577+}
1578+
1579+if ( PTDEBUG ) {
1580+ print '# ', $^X, ' ', $], "\n";
1581+ if ( my $uname = `uname -a` ) {
1582+ $uname =~ s/\s+/ /g;
1583+ print "# $uname\n";
1584+ }
1585+ print '# Arguments: ',
1586+ join(' ', map { my $a = "_[$_]_"; $a =~ s/\n/\n# /g; $a; } @ARGV), "\n";
1587+}
1588+
1589+1;
1590+}
1591+# ###########################################################################
1592+# End OptionParser package
1593+# ###########################################################################
1594+
1595+# ###########################################################################
1596+# QueryParser package
1597+# This package is a copy without comments from the original. The original
1598+# with comments and its test file can be found in the Bazaar repository at,
1599+# lib/QueryParser.pm
1600+# t/lib/QueryParser.t
1601+# See https://launchpad.net/percona-toolkit for more information.
1602+# ###########################################################################
1603+{
1604+package QueryParser;
1605+
1606+use strict;
1607+use warnings FATAL => 'all';
1608+use English qw(-no_match_vars);
1609+use constant PTDEBUG => $ENV{PTDEBUG} || 0;
1610+
1611+our $tbl_ident = qr/(?:`[^`]+`|\w+)(?:\.(?:`[^`]+`|\w+))?/;
1612+our $tbl_regex = qr{
1613+ \b(?:FROM|JOIN|(?<!KEY\s)UPDATE|INTO) # Words that precede table names
1614+ \b\s*
1615+ \(? # Optional paren around tables
1616+ ($tbl_ident
1617+ (?: (?:\s+ (?:AS\s+)? \w+)?, \s*$tbl_ident )*
1618+ )
1619+ }xio;
1620+our $has_derived = qr{
1621+ \b(?:FROM|JOIN|,)
1622+ \s*\(\s*SELECT
1623+ }xi;
1624+
1625+our $data_def_stmts = qr/(?:CREATE|ALTER|TRUNCATE|DROP|RENAME)/i;
1626+
1627+our $data_manip_stmts = qr/(?:INSERT|UPDATE|DELETE|REPLACE)/i;
1628+
1629+sub new {
1630+ my ( $class ) = @_;
1631+ bless {}, $class;
1632+}
1633+
1634+sub get_tables {
1635+ my ( $self, $query ) = @_;
1636+ return unless $query;
1637+ PTDEBUG && _d('Getting tables for', $query);
1638+
1639+ my ( $ddl_stmt ) = $query =~ m/^\s*($data_def_stmts)\b/i;
1640+ if ( $ddl_stmt ) {
1641+ PTDEBUG && _d('Special table type:', $ddl_stmt);
1642+ $query =~ s/IF\s+(?:NOT\s+)?EXISTS//i;
1643+ if ( $query =~ m/$ddl_stmt DATABASE\b/i ) {
1644+ PTDEBUG && _d('Query alters a database, not a table');
1645+ return ();
1646+ }
1647+ if ( $ddl_stmt =~ m/CREATE/i && $query =~ m/$ddl_stmt\b.+?\bSELECT\b/i ) {
1648+ my ($select) = $query =~ m/\b(SELECT\b.+)/is;
1649+ PTDEBUG && _d('CREATE TABLE ... SELECT:', $select);
1650+ return $self->get_tables($select);
1651+ }
1652+ my ($tbl) = $query =~ m/TABLE\s+($tbl_ident)(\s+.*)?/i;
1653+ PTDEBUG && _d('Matches table:', $tbl);
1654+ return ($tbl);
1655+ }
1656+
1657+ $query =~ s/ (?:LOW_PRIORITY|IGNORE|STRAIGHT_JOIN)//ig;
1658+
1659+ if ( $query =~ /^\s*LOCK TABLES/i ) {
1660+ PTDEBUG && _d('Special table type: LOCK TABLES');
1661+ $query =~ s/^(\s*LOCK TABLES\s+)//;
1662+ $query =~ s/\s+(?:READ|WRITE|LOCAL)+\s*//g;
1663+ PTDEBUG && _d('Locked tables:', $query);
1664+ $query = "FROM $query";
1665+ }
1666+
1667+ $query =~ s/\\["']//g; # quoted strings
1668+ $query =~ s/".*?"/?/sg; # quoted strings
1669+ $query =~ s/'.*?'/?/sg; # quoted strings
1670+
1671+ my @tables;
1672+ foreach my $tbls ( $query =~ m/$tbl_regex/gio ) {
1673+ PTDEBUG && _d('Match tables:', $tbls);
1674+
1675+ next if $tbls =~ m/\ASELECT\b/i;
1676+
1677+ foreach my $tbl ( split(',', $tbls) ) {
1678+ $tbl =~ s/\s*($tbl_ident)(\s+.*)?/$1/gio;
1679+
1680+ if ( $tbl !~ m/[a-zA-Z]/ ) {
1681+ PTDEBUG && _d('Skipping suspicious table name:', $tbl);
1682+ next;
1683+ }
1684+
1685+ push @tables, $tbl;
1686+ }
1687+ }
1688+ return @tables;
1689+}
1690+
1691+sub has_derived_table {
1692+ my ( $self, $query ) = @_;
1693+ my $match = $query =~ m/$has_derived/;
1694+ PTDEBUG && _d($query, 'has ' . ($match ? 'a' : 'no') . ' derived table');
1695+ return $match;
1696+}
1697+
1698+sub get_aliases {
1699+ my ( $self, $query, $list ) = @_;
1700+
1701+ my $result = {
1702+ DATABASE => {},
1703+ TABLE => {},
1704+ };
1705+ return $result unless $query;
1706+
1707+ $query =~ s/ (?:LOW_PRIORITY|IGNORE|STRAIGHT_JOIN)//ig;
1708+
1709+ $query =~ s/ (?:INNER|OUTER|CROSS|LEFT|RIGHT|NATURAL)//ig;
1710+
1711+ my @tbl_refs;
1712+ my ($tbl_refs, $from) = $query =~ m{
1713+ (
1714+ (FROM|INTO|UPDATE)\b\s* # Keyword before table refs
1715+ .+? # Table refs
1716+ )
1717+ (?:\s+|\z) # If the query does not end with the table
1718+ (?:WHERE|ORDER|LIMIT|HAVING|SET|VALUES|\z) # Keyword after table refs
1719+ }ix;
1720+
1721+ if ( $tbl_refs ) {
1722+
1723+ if ( $query =~ m/^(?:INSERT|REPLACE)/i ) {
1724+ $tbl_refs =~ s/\([^\)]+\)\s*//;
1725+ }
1726+
1727+ PTDEBUG && _d('tbl refs:', $tbl_refs);
1728+
1729+ my $before_tbl = qr/(?:,|JOIN|\s|$from)+/i;
1730+
1731+ my $after_tbl = qr/(?:,|JOIN|ON|USING|\z)/i;
1732+
1733+ $tbl_refs =~ s/ = /=/g;
1734+
1735+ while (
1736+ $tbl_refs =~ m{
1737+ $before_tbl\b\s*
1738+ ( ($tbl_ident) (?:\s+ (?:AS\s+)? (\w+))? )
1739+ \s*$after_tbl
1740+ }xgio )
1741+ {
1742+ my ( $tbl_ref, $db_tbl, $alias ) = ($1, $2, $3);
1743+ PTDEBUG && _d('Match table:', $tbl_ref);
1744+ push @tbl_refs, $tbl_ref;
1745+ $alias = $self->trim_identifier($alias);
1746+
1747+ if ( $tbl_ref =~ m/^AS\s+\w+/i ) {
1748+ PTDEBUG && _d('Subquery', $tbl_ref);
1749+ $result->{TABLE}->{$alias} = undef;
1750+ next;
1751+ }
1752+
1753+ my ( $db, $tbl ) = $db_tbl =~ m/^(?:(.*?)\.)?(.*)/;
1754+ $db = $self->trim_identifier($db);
1755+ $tbl = $self->trim_identifier($tbl);
1756+ $result->{TABLE}->{$alias || $tbl} = $tbl;
1757+ $result->{DATABASE}->{$tbl} = $db if $db;
1758+ }
1759+ }
1760+ else {
1761+ PTDEBUG && _d("No tables ref in", $query);
1762+ }
1763+
1764+ if ( $list ) {
1765+ return \@tbl_refs;
1766+ }
1767+ else {
1768+ return $result;
1769+ }
1770+}
1771+
1772+sub split {
1773+ my ( $self, $query ) = @_;
1774+ return unless $query;
1775+ $query = $self->clean_query($query);
1776+ PTDEBUG && _d('Splitting', $query);
1777+
1778+ my $verbs = qr{SELECT|INSERT|UPDATE|DELETE|REPLACE|UNION|CREATE}i;
1779+
1780+ my @split_statements = grep { $_ } split(m/\b($verbs\b(?!(?:\s*\()))/io, $query);
1781+
1782+ my @statements;
1783+ if ( @split_statements == 1 ) {
1784+ push @statements, $query;
1785+ }
1786+ else {
1787+ for ( my $i = 0; $i <= $#split_statements; $i += 2 ) {
1788+ push @statements, $split_statements[$i].$split_statements[$i+1];
1789+
1790+ if ( $statements[-2] && $statements[-2] =~ m/on duplicate key\s+$/i ) {
1791+ $statements[-2] .= pop @statements;
1792+ }
1793+ }
1794+ }
1795+
1796+ PTDEBUG && _d('statements:', map { $_ ? "<$_>" : 'none' } @statements);
1797+ return @statements;
1798+}
1799+
1800+sub clean_query {
1801+ my ( $self, $query ) = @_;
1802+ return unless $query;
1803+ $query =~ s!/\*.*?\*/! !g; # Remove /* comment blocks */
1804+ $query =~ s/^\s+//; # Remove leading spaces
1805+ $query =~ s/\s+$//; # Remove trailing spaces
1806+ $query =~ s/\s{2,}/ /g; # Remove extra spaces
1807+ return $query;
1808+}
1809+
1810+sub split_subquery {
1811+ my ( $self, $query ) = @_;
1812+ return unless $query;
1813+ $query = $self->clean_query($query);
1814+ $query =~ s/;$//;
1815+
1816+ my @subqueries;
1817+ my $sqno = 0; # subquery number
1818+ my $pos = 0;
1819+ while ( $query =~ m/(\S+)(?:\s+|\Z)/g ) {
1820+ $pos = pos($query);
1821+ my $word = $1;
1822+ PTDEBUG && _d($word, $sqno);
1823+ if ( $word =~ m/^\(?SELECT\b/i ) {
1824+ my $start_pos = $pos - length($word) - 1;
1825+ if ( $start_pos ) {
1826+ $sqno++;
1827+ PTDEBUG && _d('Subquery', $sqno, 'starts at', $start_pos);
1828+ $subqueries[$sqno] = {
1829+ start_pos => $start_pos,
1830+ end_pos => 0,
1831+ len => 0,
1832+ words => [$word],
1833+ lp => 1, # left parentheses
1834+ rp => 0, # right parentheses
1835+ done => 0,
1836+ };
1837+ }
1838+ else {
1839+ PTDEBUG && _d('Main SELECT at pos 0');
1840+ }
1841+ }
1842+ else {
1843+ next unless $sqno; # next unless we're in a subquery
1844+ PTDEBUG && _d('In subquery', $sqno);
1845+ my $sq = $subqueries[$sqno];
1846+ if ( $sq->{done} ) {
1847+ PTDEBUG && _d('This subquery is done; SQL is for',
1848+ ($sqno - 1 ? "subquery $sqno" : "the main SELECT"));
1849+ next;
1850+ }
1851+ push @{$sq->{words}}, $word;
1852+ my $lp = ($word =~ tr/\(//) || 0;
1853+ my $rp = ($word =~ tr/\)//) || 0;
1854+ PTDEBUG && _d('parentheses left', $lp, 'right', $rp);
1855+ if ( ($sq->{lp} + $lp) - ($sq->{rp} + $rp) == 0 ) {
1856+ my $end_pos = $pos - 1;
1857+ PTDEBUG && _d('Subquery', $sqno, 'ends at', $end_pos);
1858+ $sq->{end_pos} = $end_pos;
1859+ $sq->{len} = $end_pos - $sq->{start_pos};
1860+ }
1861+ }
1862+ }
1863+
1864+ for my $i ( 1..$#subqueries ) {
1865+ my $sq = $subqueries[$i];
1866+ next unless $sq;
1867+ $sq->{sql} = join(' ', @{$sq->{words}});
1868+ substr $query,
1869+ $sq->{start_pos} + 1, # +1 for (
1870+ $sq->{len} - 1, # -1 for )
1871+ "__subquery_$i";
1872+ }
1873+
1874+ return $query, map { $_->{sql} } grep { defined $_ } @subqueries;
1875+}
1876+
1877+sub query_type {
1878+ my ( $self, $query, $qr ) = @_;
1879+ my ($type, undef) = $qr->distill_verbs($query);
1880+ my $rw;
1881+ if ( $type =~ m/^SELECT\b/ ) {
1882+ $rw = 'read';
1883+ }
1884+ elsif ( $type =~ m/^$data_manip_stmts\b/
1885+ || $type =~ m/^$data_def_stmts\b/ ) {
1886+ $rw = 'write'
1887+ }
1888+
1889+ return {
1890+ type => $type,
1891+ rw => $rw,
1892+ }
1893+}
1894+
1895+sub get_columns {
1896+ my ( $self, $query ) = @_;
1897+ my $cols = [];
1898+ return $cols unless $query;
1899+ my $cols_def;
1900+
1901+ if ( $query =~ m/^SELECT/i ) {
1902+ $query =~ s/
1903+ ^SELECT\s+
1904+ (?:ALL
1905+ |DISTINCT
1906+ |DISTINCTROW
1907+ |HIGH_PRIORITY
1908+ |STRAIGHT_JOIN
1909+ |SQL_SMALL_RESULT
1910+ |SQL_BIG_RESULT
1911+ |SQL_BUFFER_RESULT
1912+ |SQL_CACHE
1913+ |SQL_NO_CACHE
1914+ |SQL_CALC_FOUND_ROWS
1915+ )\s+
1916+ /SELECT /xgi;
1917+ ($cols_def) = $query =~ m/^SELECT\s+(.+?)\s+FROM/i;
1918+ }
1919+ elsif ( $query =~ m/^(?:INSERT|REPLACE)/i ) {
1920+ ($cols_def) = $query =~ m/\(([^\)]+)\)\s*VALUE/i;
1921+ }
1922+
1923+ PTDEBUG && _d('Columns:', $cols_def);
1924+ if ( $cols_def ) {
1925+ @$cols = split(',', $cols_def);
1926+ map {
1927+ my $col = $_;
1928+ $col = s/^\s+//g;
1929+ $col = s/\s+$//g;
1930+ $col;
1931+ } @$cols;
1932+ }
1933+
1934+ return $cols;
1935+}
1936+
1937+sub parse {
1938+ my ( $self, $query ) = @_;
1939+ return unless $query;
1940+ my $parsed = {};
1941+
1942+ $query =~ s/\n/ /g;
1943+ $query = $self->clean_query($query);
1944+
1945+ $parsed->{query} = $query,
1946+ $parsed->{tables} = $self->get_aliases($query, 1);
1947+ $parsed->{columns} = $self->get_columns($query);
1948+
1949+ my ($type) = $query =~ m/^(\w+)/;
1950+ $parsed->{type} = lc $type;
1951+
1952+
1953+ $parsed->{sub_queries} = [];
1954+
1955+ return $parsed;
1956+}
1957+
1958+sub extract_tables {
1959+ my ( $self, %args ) = @_;
1960+ my $query = $args{query};
1961+ my $default_db = $args{default_db};
1962+ my $q = $self->{Quoter} || $args{Quoter};
1963+ return unless $query;
1964+ PTDEBUG && _d('Extracting tables');
1965+ my @tables;
1966+ my %seen;
1967+ foreach my $db_tbl ( $self->get_tables($query) ) {
1968+ next unless $db_tbl;
1969+ next if $seen{$db_tbl}++; # Unique-ify for issue 337.
1970+ my ( $db, $tbl ) = $q->split_unquote($db_tbl);
1971+ push @tables, [ $db || $default_db, $tbl ];
1972+ }
1973+ return @tables;
1974+}
1975+
1976+sub trim_identifier {
1977+ my ($self, $str) = @_;
1978+ return unless defined $str;
1979+ $str =~ s/`//g;
1980+ $str =~ s/^\s+//;
1981+ $str =~ s/\s+$//;
1982+ return $str;
1983+}
1984+
1985+sub _d {
1986+ my ($package, undef, $line) = caller 0;
1987+ @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
1988+ map { defined $_ ? $_ : 'undef' }
1989+ @_;
1990+ print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
1991+}
1992+
1993+1;
1994+}
1995+# ###########################################################################
1996+# End QueryParser package
1997+# ###########################################################################
1998+
1999+# ###########################################################################
2000+# QueryRewriter package
2001+# This package is a copy without comments from the original. The original
2002+# with comments and its test file can be found in the Bazaar repository at,
2003+# lib/QueryRewriter.pm
2004+# t/lib/QueryRewriter.t
2005+# See https://launchpad.net/percona-toolkit for more information.
2006+# ###########################################################################
2007+{
2008+package QueryRewriter;
2009+
2010+use strict;
2011+use warnings FATAL => 'all';
2012+use English qw(-no_match_vars);
2013+use constant PTDEBUG => $ENV{PTDEBUG} || 0;
2014+
2015+our $verbs = qr{^SHOW|^FLUSH|^COMMIT|^ROLLBACK|^BEGIN|SELECT|INSERT
2016+ |UPDATE|DELETE|REPLACE|^SET|UNION|^START|^LOCK}xi;
2017+my $quote_re = qr/"(?:(?!(?<!\\)").)*"|'(?:(?!(?<!\\)').)*'/; # Costly!
2018+my $bal;
2019+$bal = qr/
2020+ \(
2021+ (?:
2022+ (?> [^()]+ ) # Non-parens without backtracking
2023+ |
2024+ (??{ $bal }) # Group with matching parens
2025+ )*
2026+ \)
2027+ /x;
2028+
2029+my $olc_re = qr/(?:--|#)[^'"\r\n]*(?=[\r\n]|\Z)/; # One-line comments
2030+my $mlc_re = qr#/\*[^!].*?\*/#sm; # But not /*!version */
2031+my $vlc_re = qr#/\*.*?[0-9+].*?\*/#sm; # For SHOW + /*!version */
2032+my $vlc_rf = qr#^(SHOW).*?/\*![0-9+].*?\*/#sm; # Variation for SHOW
2033+
2034+
2035+sub new {
2036+ my ( $class, %args ) = @_;
2037+ my $self = { %args };
2038+ return bless $self, $class;
2039+}
2040+
2041+sub strip_comments {
2042+ my ( $self, $query ) = @_;
2043+ return unless $query;
2044+ $query =~ s/$olc_re//go;
2045+ $query =~ s/$mlc_re//go;
2046+ if ( $query =~ m/$vlc_rf/i ) { # contains show + version
2047+ $query =~ s/$vlc_re//go;
2048+ }
2049+ return $query;
2050+}
2051+
2052+sub shorten {
2053+ my ( $self, $query, $length ) = @_;
2054+ $query =~ s{
2055+ \A(
2056+ (?:INSERT|REPLACE)
2057+ (?:\s+LOW_PRIORITY|DELAYED|HIGH_PRIORITY|IGNORE)?
2058+ (?:\s\w+)*\s+\S+\s+VALUES\s*\(.*?\)
2059+ )
2060+ \s*,\s*\(.*?(ON\s+DUPLICATE|\Z)}
2061+ {$1 /*... omitted ...*/$2}xsi;
2062+
2063+ return $query unless $query =~ m/IN\s*\(\s*(?!select)/i;
2064+
2065+ my $last_length = 0;
2066+ my $query_length = length($query);
2067+ while (
2068+ $length > 0
2069+ && $query_length > $length
2070+ && $query_length < ( $last_length || $query_length + 1 )
2071+ ) {
2072+ $last_length = $query_length;
2073+ $query =~ s{
2074+ (\bIN\s*\() # The opening of an IN list
2075+ ([^\)]+) # Contents of the list, assuming no item contains paren
2076+ (?=\)) # Close of the list
2077+ }
2078+ {
2079+ $1 . __shorten($2)
2080+ }gexsi;
2081+ }
2082+
2083+ return $query;
2084+}
2085+
2086+sub __shorten {
2087+ my ( $snippet ) = @_;
2088+ my @vals = split(/,/, $snippet);
2089+ return $snippet unless @vals > 20;
2090+ my @keep = splice(@vals, 0, 20); # Remove and save the first 20 items
2091+ return
2092+ join(',', @keep)
2093+ . "/*... omitted "
2094+ . scalar(@vals)
2095+ . " items ...*/";
2096+}
2097+
2098+sub fingerprint {
2099+ my ( $self, $query ) = @_;
2100+
2101+ $query =~ m#\ASELECT /\*!40001 SQL_NO_CACHE \*/ \* FROM `# # mysqldump query
2102+ && return 'mysqldump';
2103+ $query =~ m#/\*\w+\.\w+:[0-9]/[0-9]\*/# # pt-table-checksum, etc query
2104+ && return 'percona-toolkit';
2105+ $query =~ m/\Aadministrator command: /
2106+ && return $query;
2107+ $query =~ m/\A\s*(call\s+\S+)\(/i
2108+ && return lc($1); # Warning! $1 used, be careful.
2109+ if ( my ($beginning) = $query =~ m/\A((?:INSERT|REPLACE)(?: IGNORE)?\s+INTO.+?VALUES\s*\(.*?\))\s*,\s*\(/is ) {
2110+ $query = $beginning; # Shorten multi-value INSERT statements ASAP
2111+ }
2112+
2113+ $query =~ s/$olc_re//go;
2114+ $query =~ s/$mlc_re//go;
2115+ $query =~ s/\Ause \S+\Z/use ?/i # Abstract the DB in USE
2116+ && return $query;
2117+
2118+ $query =~ s/\\["']//g; # quoted strings
2119+ $query =~ s/".*?"/?/sg; # quoted strings
2120+ $query =~ s/'.*?'/?/sg; # quoted strings
2121+
2122+ if ( $self->{match_md5_checksums} ) {
2123+ $query =~ s/([._-])[a-f0-9]{32}/$1?/g;
2124+ }
2125+
2126+ if ( !$self->{match_embedded_numbers} ) {
2127+ $query =~ s/[0-9+-][0-9a-f.xb+-]*/?/g;
2128+ }
2129+ else {
2130+ $query =~ s/\b[0-9+-][0-9a-f.xb+-]*/?/g;
2131+ }
2132+
2133+ if ( $self->{match_md5_checksums} ) {
2134+ $query =~ s/[xb+-]\?/?/g;
2135+ }
2136+ else {
2137+ $query =~ s/[xb.+-]\?/?/g;
2138+ }
2139+
2140+ $query =~ s/\A\s+//; # Chop off leading whitespace
2141+ chomp $query; # Kill trailing whitespace
2142+ $query =~ tr[ \n\t\r\f][ ]s; # Collapse whitespace
2143+ $query = lc $query;
2144+ $query =~ s/\bnull\b/?/g; # Get rid of NULLs
2145+ $query =~ s{ # Collapse IN and VALUES lists
2146+ \b(in|values?)(?:[\s,]*\([\s?,]*\))+
2147+ }
2148+ {$1(?+)}gx;
2149+ $query =~ s{ # Collapse UNION
2150+ \b(select\s.*?)(?:(\sunion(?:\sall)?)\s\1)+
2151+ }
2152+ {$1 /*repeat$2*/}xg;
2153+ $query =~ s/\blimit \?(?:, ?\?| offset \?)?/limit ?/; # LIMIT
2154+
2155+ if ( $query =~ m/\bORDER BY /gi ) { # Find, anchor on ORDER BY clause
2156+ 1 while $query =~ s/\G(.+?)\s+ASC/$1/gi && pos $query;
2157+ }
2158+
2159+ return $query;
2160+}
2161+
2162+sub distill_verbs {
2163+ my ( $self, $query ) = @_;
2164+
2165+ $query =~ m/\A\s*call\s+(\S+)\(/i && return "CALL $1";
2166+ $query =~ m/\A\s*use\s+/ && return "USE";
2167+ $query =~ m/\A\s*UNLOCK TABLES/i && return "UNLOCK";
2168+ $query =~ m/\A\s*xa\s+(\S+)/i && return "XA_$1";
2169+
2170+ if ( $query =~ m/\Aadministrator command:/ ) {
2171+ $query =~ s/administrator command:/ADMIN/;
2172+ $query = uc $query;
2173+ return $query;
2174+ }
2175+
2176+ $query = $self->strip_comments($query);
2177+
2178+ if ( $query =~ m/\A\s*SHOW\s+/i ) {
2179+ PTDEBUG && _d($query);
2180+
2181+ $query = uc $query;
2182+ $query =~ s/\s+(?:GLOBAL|SESSION|FULL|STORAGE|ENGINE)\b/ /g;
2183+ $query =~ s/\s+COUNT[^)]+\)//g;
2184+
2185+ $query =~ s/\s+(?:FOR|FROM|LIKE|WHERE|LIMIT|IN)\b.+//ms;
2186+
2187+ $query =~ s/\A(SHOW(?:\s+\S+){1,2}).*\Z/$1/s;
2188+ $query =~ s/\s+/ /g;
2189+ PTDEBUG && _d($query);
2190+ return $query;
2191+ }
2192+
2193+ eval $QueryParser::data_def_stmts;
2194+ eval $QueryParser::tbl_ident;
2195+ my ( $dds ) = $query =~ /^\s*($QueryParser::data_def_stmts)\b/i;
2196+ if ( $dds) {
2197+ my ( $obj ) = $query =~ m/$dds.+(DATABASE|TABLE)\b/i;
2198+ $obj = uc $obj if $obj;
2199+ PTDEBUG && _d('Data def statment:', $dds, 'obj:', $obj);
2200+ my ($db_or_tbl)
2201+ = $query =~ m/(?:TABLE|DATABASE)\s+($QueryParser::tbl_ident)(\s+.*)?/i;
2202+ PTDEBUG && _d('Matches db or table:', $db_or_tbl);
2203+ return uc($dds . ($obj ? " $obj" : '')), $db_or_tbl;
2204+ }
2205+
2206+ my @verbs = $query =~ m/\b($verbs)\b/gio;
2207+ @verbs = do {
2208+ my $last = '';
2209+ grep { my $pass = $_ ne $last; $last = $_; $pass } map { uc } @verbs;
2210+ };
2211+
2212+ if ( ($verbs[0] || '') eq 'SELECT' && @verbs > 1 ) {
2213+ PTDEBUG && _d("False-positive verbs after SELECT:", @verbs[1..$#verbs]);
2214+ my $union = grep { $_ eq 'UNION' } @verbs;
2215+ @verbs = $union ? qw(SELECT UNION) : qw(SELECT);
2216+ }
2217+
2218+ my $verb_str = join(q{ }, @verbs);
2219+ return $verb_str;
2220+}
2221+
2222+sub __distill_tables {
2223+ my ( $self, $query, $table, %args ) = @_;
2224+ my $qp = $args{QueryParser} || $self->{QueryParser};
2225+ die "I need a QueryParser argument" unless $qp;
2226+
2227+ my @tables = map {
2228+ $_ =~ s/`//g;
2229+ $_ =~ s/(_?)[0-9]+/$1?/g;
2230+ $_;
2231+ } grep { defined $_ } $qp->get_tables($query);
2232+
2233+ push @tables, $table if $table;
2234+
2235+ @tables = do {
2236+ my $last = '';
2237+ grep { my $pass = $_ ne $last; $last = $_; $pass } @tables;
2238+ };
2239+
2240+ return @tables;
2241+}
2242+
2243+sub distill {
2244+ my ( $self, $query, %args ) = @_;
2245+
2246+ if ( $args{generic} ) {
2247+ my ($cmd, $arg) = $query =~ m/^(\S+)\s+(\S+)/;
2248+ return '' unless $cmd;
2249+ $query = (uc $cmd) . ($arg ? " $arg" : '');
2250+ }
2251+ else {
2252+ my ($verbs, $table) = $self->distill_verbs($query, %args);
2253+
2254+ if ( $verbs && $verbs =~ m/^SHOW/ ) {
2255+ my %alias_for = qw(
2256+ SCHEMA DATABASE
2257+ KEYS INDEX
2258+ INDEXES INDEX
2259+ );
2260+ map { $verbs =~ s/$_/$alias_for{$_}/ } keys %alias_for;
2261+ $query = $verbs;
2262+ }
2263+ else {
2264+ my @tables = $self->__distill_tables($query, $table, %args);
2265+ $query = join(q{ }, $verbs, @tables);
2266+ }
2267+ }
2268+
2269+ if ( $args{trf} ) {
2270+ $query = $args{trf}->($query, %args);
2271+ }
2272+
2273+ return $query;
2274+}
2275+
2276+sub convert_to_select {
2277+ my ( $self, $query ) = @_;
2278+ return unless $query;
2279+
2280+ return if $query =~ m/=\s*\(\s*SELECT /i;
2281+
2282+ $query =~ s{
2283+ \A.*?
2284+ update(?:\s+(?:low_priority|ignore))?\s+(.*?)
2285+ \s+set\b(.*?)
2286+ (?:\s*where\b(.*?))?
2287+ (limit\s*[0-9]+(?:\s*,\s*[0-9]+)?)?
2288+ \Z
2289+ }
2290+ {__update_to_select($1, $2, $3, $4)}exsi
2291+ || $query =~ s{
2292+ \A.*?
2293+ (?:insert(?:\s+ignore)?|replace)\s+
2294+ .*?\binto\b(.*?)\(([^\)]+)\)\s*
2295+ values?\s*(\(.*?\))\s*
2296+ (?:\blimit\b|on\s+duplicate\s+key.*)?\s*
2297+ \Z
2298+ }
2299+ {__insert_to_select($1, $2, $3)}exsi
2300+ || $query =~ s{
2301+ \A.*?
2302+ (?:insert(?:\s+ignore)?|replace)\s+
2303+ (?:.*?\binto)\b(.*?)\s*
2304+ set\s+(.*?)\s*
2305+ (?:\blimit\b|on\s+duplicate\s+key.*)?\s*
2306+ \Z
2307+ }
2308+ {__insert_to_select_with_set($1, $2)}exsi
2309+ || $query =~ s{
2310+ \A.*?
2311+ delete\s+(.*?)
2312+ \bfrom\b(.*)
2313+ \Z
2314+ }
2315+ {__delete_to_select($1, $2)}exsi;
2316+ $query =~ s/\s*on\s+duplicate\s+key\s+update.*\Z//si;
2317+ $query =~ s/\A.*?(?=\bSELECT\s*\b)//ism;
2318+ return $query;
2319+}
2320+
2321+sub convert_select_list {
2322+ my ( $self, $query ) = @_;
2323+ $query =~ s{
2324+ \A\s*select(.*?)\bfrom\b
2325+ }
2326+ {$1 =~ m/\*/ ? "select 1 from" : "select isnull(coalesce($1)) from"}exi;
2327+ return $query;
2328+}
2329+
2330+sub __delete_to_select {
2331+ my ( $delete, $join ) = @_;
2332+ if ( $join =~ m/\bjoin\b/ ) {
2333+ return "select 1 from $join";
2334+ }
2335+ return "select * from $join";
2336+}
2337+
2338+sub __insert_to_select {
2339+ my ( $tbl, $cols, $vals ) = @_;
2340+ PTDEBUG && _d('Args:', @_);
2341+ my @cols = split(/,/, $cols);
2342+ PTDEBUG && _d('Cols:', @cols);
2343+ $vals =~ s/^\(|\)$//g; # Strip leading/trailing parens
2344+ my @vals = $vals =~ m/($quote_re|[^,]*${bal}[^,]*|[^,]+)/g;
2345+ PTDEBUG && _d('Vals:', @vals);
2346+ if ( @cols == @vals ) {
2347+ return "select * from $tbl where "
2348+ . join(' and ', map { "$cols[$_]=$vals[$_]" } (0..$#cols));
2349+ }
2350+ else {
2351+ return "select * from $tbl limit 1";
2352+ }
2353+}
2354+
2355+sub __insert_to_select_with_set {
2356+ my ( $from, $set ) = @_;
2357+ $set =~ s/,/ and /g;
2358+ return "select * from $from where $set ";
2359+}
2360+
2361+sub __update_to_select {
2362+ my ( $from, $set, $where, $limit ) = @_;
2363+ return "select $set from $from "
2364+ . ( $where ? "where $where" : '' )
2365+ . ( $limit ? " $limit " : '' );
2366+}
2367+
2368+sub wrap_in_derived {
2369+ my ( $self, $query ) = @_;
2370+ return unless $query;
2371+ return $query =~ m/\A\s*select/i
2372+ ? "select 1 from ($query) as x limit 1"
2373+ : $query;
2374+}
2375+
2376+sub _d {
2377+ my ($package, undef, $line) = caller 0;
2378+ @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
2379+ map { defined $_ ? $_ : 'undef' }
2380+ @_;
2381+ print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
2382+}
2383+
2384+1;
2385+}
2386+# ###########################################################################
2387+# End QueryRewriter package
2388+# ###########################################################################
2389+
2390+# ###########################################################################
2391+# This is a combination of modules and programs in one -- a runnable module.
2392+# http://www.perl.com/pub/a/2006/07/13/lightning-articles.html?page=last
2393+# Or, look it up in the Camel book on pages 642 and 643 in the 3rd edition.
2394+#
2395+# Check at the end of this package for the call to main() which actually runs
2396+# the program.
2397+# ###########################################################################
2398+package pt_fingerprint;
2399+
2400+use English qw(-no_match_vars);
2401+use Data::Dumper;
2402+$Data::Dumper::Indent = 1;
2403+$OUTPUT_AUTOFLUSH = 1;
2404+
2405+use constant MKDEBUG => $ENV{MKDEBUG} || 0;
2406+
2407+sub main {
2408+ @ARGV = @_; # set global ARGV for this package
2409+
2410+ # ##########################################################################
2411+ # Get configuration information.
2412+ # ##########################################################################
2413+ my $o = new OptionParser();
2414+ $o->get_specs();
2415+ $o->get_opts();
2416+ $o->usage_or_errors();
2417+
2418+ my $qp = new QueryParser();
2419+ my $qr = new QueryRewriter(
2420+ QueryParser => $qp,
2421+ match_md5_checksums => $o->get('match-md5-checksums'),
2422+ match_embedded_numbers => $o->get('match-embedded-numbers'),
2423+ );
2424+
2425+ if ( $o->got('query') ) {
2426+ print $qr->fingerprint($o->get('query')), "\n";
2427+ }
2428+ else {
2429+ local $INPUT_RECORD_SEPARATOR = ";\n";
2430+ while ( <> ) {
2431+ my $query = $_;
2432+ chomp $query;
2433+ $query =~ s/^#.+$//mg;
2434+ $query =~ s/^\s+//;
2435+ next unless $query =~ m/^\w/;
2436+ print $qr->fingerprint($query), "\n";
2437+ }
2438+ }
2439+}
2440+
2441+# ############################################################################
2442+# Run the program.
2443+# ############################################################################
2444+if ( !caller ) { exit main(@ARGV); }
2445+
2446+1; # Because this is a module as well as a script.
2447+
2448+# #############################################################################
2449+# Documentation.
2450+# #############################################################################
2451+
2452+=pod
2453+
2454+=head1 NAME
2455+
2456+pt-fingerprint - Convert queries into fingerprints.
2457+
2458+=head1 SYNOPSIS
2459+
2460+Usage: pt-fingerprint [OPTIONS] [FILES]
2461+
2462+pt-fingerprint converts queries into fingerprints. With the --query
2463+option, converts the option's value into a fingerprint. With no options, treats
2464+command-line arguments as FILEs and reads and converts semicolon-separated
2465+queries from the FILEs. When FILE is -, it read standard input.
2466+
2467+Convert a single query:
2468+
2469+ pt-fingerprint --query "select a, b, c from users where id = 500"
2470+
2471+Convert a file full of queries:
2472+
2473+ pt-fingerprint /path/to/file.txt
2474+
2475+=head1 RISKS
2476+
2477+The following section is included to inform users about the potential risks,
2478+whether known or unknown, of using this tool. The two main categories of risks
2479+are those created by the nature of the tool (e.g. read-only tools vs. read-write
2480+tools) and those created by bugs.
2481+
2482+The pt-fingerprint tool simply reads data and transforms it, so risks are
2483+minimal.
2484+
2485+See also L<"BUGS"> for more information on filing bugs and getting help.
2486+
2487+=head1 DESCRIPTION
2488+
2489+A query fingerprint is the abstracted form of a query, which makes it possible
2490+to group similar queries together. Abstracting a query removes literal values,
2491+normalizes whitespace, and so on. For example, consider these two queries:
2492+
2493+ SELECT name, password FROM user WHERE id='12823';
2494+ select name, password from user
2495+ where id=5;
2496+
2497+Both of those queries will fingerprint to
2498+
2499+ select name, password from user where id=?
2500+
2501+Once the query's fingerprint is known, we can then talk about a query as though
2502+it represents all similar queries.
2503+
2504+Query fingerprinting accommodates a great many special cases, which have proven
2505+necessary in the real world. For example, an IN list with 5 literals is really
2506+equivalent to one with 4 literals, so lists of literals are collapsed to a
2507+single one. If you want to understand more about how and why all of these cases
2508+are handled, please review the test cases in the Subversion repository. If you
2509+find something that is not fingerprinted properly, please submit a bug report
2510+with a reproducible test case. Here is a list of transformations during
2511+fingerprinting, which might not be exhaustive:
2512+
2513+=over
2514+
2515+=item *
2516+
2517+Group all SELECT queries from mysqldump together, even if they are against
2518+different tables. Ditto for all of pt-table-checksum's checksum queries.
2519+
2520+=item *
2521+
2522+Shorten multi-value INSERT statements to a single VALUES() list.
2523+
2524+=item *
2525+
2526+Strip comments.
2527+
2528+=item *
2529+
2530+Abstract the databases in USE statements, so all USE statements are grouped
2531+together.
2532+
2533+=item *
2534+
2535+Replace all literals, such as quoted strings. For efficiency, the code that
2536+replaces literal numbers is somewhat non-selective, and might replace some
2537+things as numbers when they really are not. Hexadecimal literals are also
2538+replaced. NULL is treated as a literal. Numbers embedded in identifiers are
2539+also replaced, so tables named similarly will be fingerprinted to the same
2540+values (e.g. users_2009 and users_2010 will fingerprint identically).
2541+
2542+=item *
2543+
2544+Collapse all whitespace into a single space.
2545+
2546+=item *
2547+
2548+Lowercase the entire query.
2549+
2550+=item *
2551+
2552+Replace all literals inside of IN() and VALUES() lists with a single
2553+placeholder, regardless of cardinality.
2554+
2555+=item *
2556+
2557+Collapse multiple identical UNION queries into a single one.
2558+
2559+=back
2560+
2561+=head1 OPTIONS
2562+
2563+This tool accepts additional command-line arguments. Refer to the
2564+L<"SYNOPSIS"> and usage information for details.
2565+
2566+=over
2567+
2568+=item --config
2569+
2570+type: Array
2571+
2572+Read this comma-separated list of config files; if specified, this must be the
2573+first option on the command line.
2574+
2575+=item --help
2576+
2577+Show help and exit.
2578+
2579+=item --match-embedded-numbers
2580+
2581+Match numbers embedded in words and replace as single values. This option
2582+causes the tool to be more careful about matching numbers so that words
2583+with numbers, like C<catch22> are matched and replaced as a single C<?>
2584+placeholder. Otherwise the default number matching pattern will replace
2585+C<catch22> as C<catch?>.
2586+
2587+This is helpful if database or table names contain numbers.
2588+
2589+=item --match-md5-checksums
2590+
2591+Match MD5 checksums and replace as single values. This option causes
2592+the tool to be more careful about matching numbers so that MD5 checksums
2593+like C<fbc5e685a5d3d45aa1d0347fdb7c4d35> are matched and replaced as a
2594+single C<?> placeholder. Otherwise, the default number matching pattern will
2595+replace C<fbc5e685a5d3d45aa1d0347fdb7c4d35> as C<fbc?>.
2596+
2597+=item --query
2598+
2599+type: string
2600+
2601+The query to convert into a fingerprint.
2602+
2603+=item --version
2604+
2605+Show version and exit.
2606+
2607+=back
2608+
2609+=head1 ENVIRONMENT
2610+
2611+The environment variable C<PTDEBUG> enables verbose debugging output to STDERR.
2612+To enable debugging and capture all output to a file, run the tool like:
2613+
2614+ PTDEBUG=1 pt-fingerprint ... > FILE 2>&1
2615+
2616+Be careful: debugging output is voluminous and can generate several megabytes
2617+of output.
2618+
2619+=head1 SYSTEM REQUIREMENTS
2620+
2621+You need Perl, DBI, DBD::mysql, and some core packages that ought to be
2622+installed in any reasonably new version of Perl.
2623+
2624+=head1 BUGS
2625+
2626+For a list of known bugs, see L<http://www.percona.com/bugs/pt-fingerprint>.
2627+
2628+Please report bugs at L<https://bugs.launchpad.net/percona-toolkit>.
2629+Include the following information in your bug report:
2630+
2631+=over
2632+
2633+=item * Complete command-line used to run the tool
2634+
2635+=item * Tool L<"--version">
2636+
2637+=item * MySQL version of all servers involved
2638+
2639+=item * Output from the tool including STDERR
2640+
2641+=item * Input files (log/dump/config files, etc.)
2642+
2643+=back
2644+
2645+If possible, include debugging output by running the tool with C<PTDEBUG>;
2646+see L<"ENVIRONMENT">.
2647+
2648+=head1 DOWNLOADING
2649+
2650+Visit L<http://www.percona.com/software/percona-toolkit/> to download the
2651+latest release of Percona Toolkit. Or, get the latest release from the
2652+command line:
2653+
2654+ wget percona.com/get/percona-toolkit.tar.gz
2655+
2656+ wget percona.com/get/percona-toolkit.rpm
2657+
2658+ wget percona.com/get/percona-toolkit.deb
2659+
2660+You can also get individual tools from the latest release:
2661+
2662+ wget percona.com/get/TOOL
2663+
2664+Replace C<TOOL> with the name of any tool.
2665+
2666+=head1 AUTHORS
2667+
2668+Baron Schwartz and Daniel Nichter
2669+
2670+=head1 ABOUT PERCONA TOOLKIT
2671+
2672+This tool is part of Percona Toolkit, a collection of advanced command-line
2673+tools developed by Percona for MySQL support and consulting. Percona Toolkit
2674+was forked from two projects in June, 2011: Maatkit and Aspersa. Those
2675+projects were created by Baron Schwartz and developed primarily by him and
2676+Daniel Nichter, both of whom are employed by Percona. Visit
2677+L<http://www.percona.com/software/> for more software developed by Percona.
2678+
2679+=head1 COPYRIGHT, LICENSE, AND WARRANTY
2680+
2681+This program is copyright 2011-2012 Percona Inc.
2682+Feedback and improvements are welcome.
2683+
2684+THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
2685+WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
2686+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
2687+
2688+This program is free software; you can redistribute it and/or modify it under
2689+the terms of the GNU General Public License as published by the Free Software
2690+Foundation, version 2; OR the Perl Artistic License. On UNIX and similar
2691+systems, you can issue `man perlgpl' or `man perlartistic' to read these
2692+licenses.
2693+
2694+You should have received a copy of the GNU General Public License along with
2695+this program; if not, write to the Free Software Foundation, Inc., 59 Temple
2696+Place, Suite 330, Boston, MA 02111-1307 USA.
2697+
2698+=head1 VERSION
2699+
2700+pt-fingerprint 2.1.1
2701+
2702+=cut
2703
2704=== modified file 'bin/pt-fk-error-logger'
2705--- bin/pt-fk-error-logger 2012-05-24 17:52:01 +0000
2706+++ bin/pt-fk-error-logger 2012-06-01 14:38:21 +0000
2707@@ -2465,6 +2465,6 @@
2708
2709 =head1 VERSION
2710
2711-pt-fk-error-logger 2.0.4
2712+pt-fk-error-logger 2.1.1
2713
2714 =cut
2715
2716=== modified file 'bin/pt-heartbeat'
2717--- bin/pt-heartbeat 2012-05-24 17:52:01 +0000
2718+++ bin/pt-heartbeat 2012-06-01 14:38:21 +0000
2719@@ -73,6 +73,9 @@
2720 dsn_table_dsn => $dsn_table_dsn,
2721 );
2722 }
2723+ elsif ( $method =~ m/none/i ) {
2724+ PTDEBUG && _d('Not getting to slaves');
2725+ }
2726 else {
2727 die "Invalid --recursion-method: $method. Valid values are: "
2728 . "dsn=DSN, hosts, or processlist.\n";
2729@@ -87,6 +90,11 @@
2730 my $dp = $args->{dsn_parser};
2731 my $dsn = $args->{dsn};
2732
2733+ if ( lc($args->{method} || '') eq 'none' ) {
2734+ PTDEBUG && _d('Not recursing to slaves');
2735+ return;
2736+ }
2737+
2738 my $dbh;
2739 eval {
2740 $dbh = $args->{dbh} || $dp->get_dbh(
2741@@ -4136,9 +4144,10 @@
2742 Possible methods are:
2743
2744 METHOD USES
2745- =========== ================
2746+ =========== ==================
2747 processlist SHOW PROCESSLIST
2748 hosts SHOW SLAVE HOSTS
2749+ none Do not find slaves
2750
2751 The processlist method is preferred because SHOW SLAVE HOSTS is not reliable.
2752 However, the hosts method is required if the server uses a non-standard
2753@@ -4400,6 +4409,6 @@
2754
2755 =head1 VERSION
2756
2757-pt-heartbeat 2.0.4
2758+pt-heartbeat 2.1.1
2759
2760 =cut
2761
2762=== modified file 'bin/pt-index-usage'
2763--- bin/pt-index-usage 2012-05-24 17:52:01 +0000
2764+++ bin/pt-index-usage 2012-06-01 14:38:21 +0000
2765@@ -2674,19 +2674,58 @@
2766 return bless $self, $class;
2767 }
2768
2769+sub get_create_table {
2770+ my ( $self, $dbh, $db, $tbl ) = @_;
2771+ die "I need a dbh parameter" unless $dbh;
2772+ die "I need a db parameter" unless $db;
2773+ die "I need a tbl parameter" unless $tbl;
2774+ my $q = $self->{Quoter};
2775+
2776+ my $new_sql_mode
2777+ = '/*!40101 SET @OLD_SQL_MODE := @@SQL_MODE, '
2778+ . q{@@SQL_MODE := REPLACE(REPLACE(@@SQL_MODE, 'ANSI_QUOTES', ''), ',,', ','), }
2779+ . '@OLD_QUOTE := @@SQL_QUOTE_SHOW_CREATE, '
2780+ . '@@SQL_QUOTE_SHOW_CREATE := 1 */';
2781+
2782+ my $old_sql_mode = '/*!40101 SET @@SQL_MODE := @OLD_SQL_MODE, '
2783+ . '@@SQL_QUOTE_SHOW_CREATE := @OLD_QUOTE */';
2784+
2785+ PTDEBUG && _d($new_sql_mode);
2786+ eval { $dbh->do($new_sql_mode); };
2787+ PTDEBUG && $EVAL_ERROR && _d($EVAL_ERROR);
2788+
2789+ my $use_sql = 'USE ' . $q->quote($db);
2790+ PTDEBUG && _d($dbh, $use_sql);
2791+ $dbh->do($use_sql);
2792+
2793+ my $show_sql = "SHOW CREATE TABLE " . $q->quote($db, $tbl);
2794+ PTDEBUG && _d($show_sql);
2795+ my $href;
2796+ eval { $href = $dbh->selectrow_hashref($show_sql); };
2797+ if ( $EVAL_ERROR ) {
2798+ PTDEBUG && _d($EVAL_ERROR);
2799+
2800+ PTDEBUG && _d($old_sql_mode);
2801+ $dbh->do($old_sql_mode);
2802+
2803+ return;
2804+ }
2805+
2806+ PTDEBUG && _d($old_sql_mode);
2807+ $dbh->do($old_sql_mode);
2808+
2809+ my ($key) = grep { m/create (?:table|view)/i } keys %$href;
2810+ if ( !$key ) {
2811+ die "Error: no 'Create Table' or 'Create View' in result set from "
2812+ . "$show_sql: " . Dumper($href);
2813+ }
2814+
2815+ return $href->{$key};
2816+}
2817+
2818 sub parse {
2819 my ( $self, $ddl, $opts ) = @_;
2820 return unless $ddl;
2821- if ( ref $ddl eq 'ARRAY' ) {
2822- if ( lc $ddl->[0] eq 'table' ) {
2823- $ddl = $ddl->[1];
2824- }
2825- else {
2826- return {
2827- engine => 'VIEW',
2828- };
2829- }
2830- }
2831
2832 if ( $ddl !~ m/CREATE (?:TEMPORARY )?TABLE `/ ) {
2833 die "Cannot parse table definition; is ANSI quoting "
2834@@ -2993,41 +3032,31 @@
2835 return $ddl;
2836 }
2837
2838-sub remove_secondary_indexes {
2839- my ( $self, $ddl ) = @_;
2840- my $sec_indexes_ddl;
2841- my $tbl_struct = $self->parse($ddl);
2842-
2843- if ( ($tbl_struct->{engine} || '') =~ m/InnoDB/i ) {
2844- my $clustered_key = $tbl_struct->{clustered_key};
2845- $clustered_key ||= '';
2846-
2847- my @sec_indexes = map {
2848- my $key_def = $_->{ddl};
2849- $key_def =~ s/([\(\)])/\\$1/g;
2850- $ddl =~ s/\s+$key_def//i;
2851-
2852- my $key_ddl = "ADD $_->{ddl}";
2853- $key_ddl .= ',' unless $key_ddl =~ m/,$/;
2854- $key_ddl;
2855- }
2856- grep { $_->{name} ne $clustered_key }
2857- values %{$tbl_struct->{keys}};
2858- PTDEBUG && _d('Secondary indexes:', Dumper(\@sec_indexes));
2859-
2860- if ( @sec_indexes ) {
2861- $sec_indexes_ddl = join(' ', @sec_indexes);
2862- $sec_indexes_ddl =~ s/,$//;
2863- }
2864-
2865- $ddl =~ s/,(\n\) )/$1/s;
2866- }
2867- else {
2868- PTDEBUG && _d('Not removing secondary indexes from',
2869- $tbl_struct->{engine}, 'table');
2870- }
2871-
2872- return $ddl, $sec_indexes_ddl, $tbl_struct;
2873+sub get_table_status {
2874+ my ( $self, $dbh, $db, $like ) = @_;
2875+ my $q = $self->{Quoter};
2876+ my $sql = "SHOW TABLE STATUS FROM " . $q->quote($db);
2877+ my @params;
2878+ if ( $like ) {
2879+ $sql .= ' LIKE ?';
2880+ push @params, $like;
2881+ }
2882+ PTDEBUG && _d($sql, @params);
2883+ my $sth = $dbh->prepare($sql);
2884+ eval { $sth->execute(@params); };
2885+ if ($EVAL_ERROR) {
2886+ PTDEBUG && _d($EVAL_ERROR);
2887+ return;
2888+ }
2889+ my @tables = @{$sth->fetchall_arrayref({})};
2890+ @tables = map {
2891+ my %tbl; # Make a copy with lowercased keys
2892+ @tbl{ map { lc $_ } keys %$_ } = values %$_;
2893+ $tbl{engine} ||= $tbl{type} || $tbl{comment};
2894+ delete $tbl{type};
2895+ \%tbl;
2896+ } @tables;
2897+ return @tables;
2898 }
2899
2900 sub _d {
2901@@ -3917,7 +3946,7 @@
2902
2903 sub new {
2904 my ( $class, %args ) = @_;
2905- my @required_args = qw(OptionParser Quoter);
2906+ my @required_args = qw(OptionParser TableParser Quoter);
2907 foreach my $arg ( @required_args ) {
2908 die "I need a $arg argument" unless $args{$arg};
2909 }
2910@@ -3926,8 +3955,19 @@
2911 die "I need either a dbh or file_itr argument"
2912 if (!$dbh && !$file_itr) || ($dbh && $file_itr);
2913
2914+ my %resume;
2915+ if ( my $table = $args{resume} ) {
2916+ PTDEBUG && _d('Will resume from or after', $table);
2917+ my ($db, $tbl) = $args{Quoter}->split_unquote($table);
2918+ die "Resume table must be database-qualified: $table"
2919+ unless $db && $tbl;
2920+ $resume{db} = $db;
2921+ $resume{tbl} = $tbl;
2922+ }
2923+
2924 my $self = {
2925 %args,
2926+ resume => \%resume,
2927 filters => _make_filters(%args),
2928 };
2929
2930@@ -3988,9 +4028,19 @@
2931 return \%filters;
2932 }
2933
2934-sub next_schema_object {
2935+sub next {
2936 my ( $self ) = @_;
2937
2938+ if ( !$self->{initialized} ) {
2939+ $self->{initialized} = 1;
2940+ if ( $self->{resume}->{tbl}
2941+ && !$self->table_is_allowed(@{$self->{resume}}{qw(db tbl)}) ) {
2942+ PTDEBUG && _d('Will resume after',
2943+ join('.', @{$self->{resume}}{qw(db tbl)}));
2944+ $self->{resume}->{after} = 1;
2945+ }
2946+ }
2947+
2948 my $schema_obj;
2949 if ( $self->{file_itr} ) {
2950 $schema_obj= $self->_iterate_files();
2951@@ -4000,19 +4050,13 @@
2952 }
2953
2954 if ( $schema_obj ) {
2955- if ( $schema_obj->{ddl} && $self->{TableParser} ) {
2956- $schema_obj->{tbl_struct}
2957- = $self->{TableParser}->parse($schema_obj->{ddl});
2958- }
2959-
2960- delete $schema_obj->{ddl} unless $self->{keep_ddl};
2961-
2962 if ( my $schema = $self->{Schema} ) {
2963 $schema->add_schema_object($schema_obj);
2964 }
2965+ PTDEBUG && _d('Next schema object:',
2966+ $schema_obj->{db}, $schema_obj->{tbl});
2967 }
2968
2969- PTDEBUG && _d('Next schema object:', $schema_obj->{db}, $schema_obj->{tbl});
2970 return $schema_obj;
2971 }
2972
2973@@ -4038,7 +4082,8 @@
2974 my $db = $1; # XXX
2975 $db =~ s/^`//; # strip leading `
2976 $db =~ s/`$//; # and trailing `
2977- if ( $self->database_is_allowed($db) ) {
2978+ if ( $self->database_is_allowed($db)
2979+ && $self->_resume_from_database($db) ) {
2980 $self->{db} = $db;
2981 }
2982 }
2983@@ -4051,21 +4096,22 @@
2984 my ($tbl) = $chunk =~ m/$tbl_name/;
2985 $tbl =~ s/^\s*`//;
2986 $tbl =~ s/`\s*$//;
2987- if ( $self->table_is_allowed($self->{db}, $tbl) ) {
2988+ if ( $self->_resume_from_table($tbl)
2989+ && $self->table_is_allowed($self->{db}, $tbl) ) {
2990 my ($ddl) = $chunk =~ m/^(?:$open_comment)?(CREATE TABLE.+?;)$/ms;
2991 if ( !$ddl ) {
2992 warn "Failed to parse CREATE TABLE from\n" . $chunk;
2993 next CHUNK;
2994 }
2995 $ddl =~ s/ \*\/;\Z/;/; # remove end of version comment
2996-
2997- my ($engine) = $ddl =~ m/\).*?(?:ENGINE|TYPE)=(\w+)/;
2998-
2999- if ( !$engine || $self->engine_is_allowed($engine) ) {
3000+ my $tbl_struct = $self->{TableParser}->parse($ddl);
3001+ if ( $self->engine_is_allowed($tbl_struct->{engine}) ) {
3002 return {
3003- db => $self->{db},
3004- tbl => $tbl,
3005- ddl => $ddl,
3006+ db => $self->{db},
3007+ tbl => $tbl,
3008+ name => $self->{Quoter}->quote($self->{db}, $tbl),
3009+ ddl => $ddl,
3010+ tbl_struct => $tbl_struct,
3011 };
3012 }
3013 }
3014@@ -4082,6 +4128,7 @@
3015 sub _iterate_dbh {
3016 my ( $self ) = @_;
3017 my $q = $self->{Quoter};
3018+ my $tp = $self->{TableParser};
3019 my $dbh = $self->{dbh};
3020 PTDEBUG && _d('Getting next schema object from dbh', $dbh);
3021
3022@@ -4095,7 +4142,9 @@
3023 }
3024
3025 if ( !$self->{db} ) {
3026- $self->{db} = shift @{$self->{dbs}};
3027+ do {
3028+ $self->{db} = shift @{$self->{dbs}};
3029+ } until $self->_resume_from_database($self->{db});
3030 PTDEBUG && _d('Next database:', $self->{db});
3031 return unless $self->{db};
3032 }
3033@@ -4108,8 +4157,9 @@
3034 }
3035 grep {
3036 my ($tbl, $type) = @$_;
3037- $self->table_is_allowed($self->{db}, $tbl)
3038- && (!$type || ($type ne 'VIEW'));
3039+ (!$type || ($type ne 'VIEW'))
3040+ && $self->_resume_from_table($tbl)
3041+ && $self->table_is_allowed($self->{db}, $tbl);
3042 }
3043 @{$dbh->selectall_arrayref($sql)};
3044 PTDEBUG && _d('Found', scalar @tbls, 'tables in database', $self->{db});
3045@@ -4117,27 +4167,15 @@
3046 }
3047
3048 while ( my $tbl = shift @{$self->{tbls}} ) {
3049- my $engine;
3050- if ( $self->{filters}->{'engines'}
3051- || $self->{filters}->{'ignore-engines'} ) {
3052- my $sql = "SHOW TABLE STATUS FROM " . $q->quote($self->{db})
3053- . " LIKE \'$tbl\'";
3054- PTDEBUG && _d($sql);
3055- $engine = $dbh->selectrow_hashref($sql)->{engine};
3056- PTDEBUG && _d($tbl, 'uses', $engine, 'engine');
3057- }
3058-
3059-
3060- if ( !$engine || $self->engine_is_allowed($engine) ) {
3061- my $ddl;
3062- if ( my $du = $self->{MySQLDump} ) {
3063- $ddl = $du->get_create_table($dbh, $q, $self->{db}, $tbl)->[1];
3064- }
3065-
3066+ my $ddl = $tp->get_create_table($dbh, $self->{db}, $tbl);
3067+ my $tbl_struct = $tp->parse($ddl);
3068+ if ( $self->engine_is_allowed($tbl_struct->{engine}) ) {
3069 return {
3070- db => $self->{db},
3071- tbl => $tbl,
3072- ddl => $ddl,
3073+ db => $self->{db},
3074+ tbl => $tbl,
3075+ name => $q->quote($self->{db}, $tbl),
3076+ ddl => $ddl,
3077+ tbl_struct => $tbl_struct,
3078 };
3079 }
3080 }
3081@@ -4198,6 +4236,10 @@
3082
3083 my $filter = $self->{filters};
3084
3085+ if ( $db eq 'mysql' && ($tbl eq 'general_log' || $tbl eq 'slow_log') ) {
3086+ return 0;
3087+ }
3088+
3089 if ( $filter->{'ignore-tables'}->{$tbl}
3090 && ($filter->{'ignore-tables'}->{$tbl} eq '*'
3091 || $filter->{'ignore-tables'}->{$tbl} eq $db) ) {
3092@@ -4237,7 +4279,11 @@
3093
3094 sub engine_is_allowed {
3095 my ( $self, $engine ) = @_;
3096- die "I need an engine argument" unless $engine;
3097+
3098+ if ( !$engine ) {
3099+ PTDEBUG && _d('No engine specified; allowing the table');
3100+ return 1;
3101+ }
3102
3103 $engine = lc $engine;
3104
3105@@ -4257,6 +4303,40 @@
3106 return 1;
3107 }
3108
3109+sub _resume_from_database {
3110+ my ($self, $db) = @_;
3111+
3112+ return 1 unless $self->{resume}->{db};
3113+
3114+ if ( $db eq $self->{resume}->{db} ) {
3115+ PTDEBUG && _d('At resume db', $db);
3116+ delete $self->{resume}->{db};
3117+ return 1;
3118+ }
3119+
3120+ return 0;
3121+}
3122+
3123+sub _resume_from_table {
3124+ my ($self, $tbl) = @_;
3125+
3126+ return 1 unless $self->{resume}->{tbl};
3127+
3128+ if ( $tbl eq $self->{resume}->{tbl} ) {
3129+ if ( !$self->{resume}->{after} ) {
3130+ PTDEBUG && _d('Resuming from table', $tbl);
3131+ delete $self->{resume}->{tbl};
3132+ return 1;
3133+ }
3134+ else {
3135+ PTDEBUG && _d('Resuming after table', $tbl);
3136+ delete $self->{resume}->{tbl};
3137+ }
3138+ }
3139+
3140+ return 0;
3141+}
3142+
3143 sub _d {
3144 my ($package, undef, $line) = caller 0;
3145 @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
3146@@ -5149,13 +5229,11 @@
3147 dbh => $dbh,
3148 OptionParser => $o,
3149 Quoter => $q,
3150- MySQLDump => $du,
3151 TableParser => $tp,
3152 Schema => $schema,
3153- keep_ddl => 1,
3154 );
3155 TALBE:
3156- while ( my $tbl = $schema_itr->next_schema_object() ) {
3157+ while ( my $tbl = $schema_itr->next() ) {
3158 eval {
3159 my ($indexes) = $tp->get_keys($tbl->{ddl}, {version => $version});
3160 $iu->add_indexes(%$tbl, indexes=>$indexes);
3161@@ -6191,6 +6269,6 @@
3162
3163 =head1 VERSION
3164
3165-pt-index-usage 2.0.4
3166+pt-index-usage 2.1.1
3167
3168 =cut
3169
3170=== modified file 'bin/pt-ioprofile'
3171--- bin/pt-ioprofile 2012-05-16 17:31:10 +0000
3172+++ bin/pt-ioprofile 2012-06-01 14:38:21 +0000
3173@@ -1037,7 +1037,7 @@
3174
3175 =head1 VERSION
3176
3177-pt-ioprofile 2.0.4
3178+pt-ioprofile 2.1.1
3179
3180 =cut
3181
3182
3183=== modified file 'bin/pt-kill'
3184--- bin/pt-kill 2012-05-30 22:10:33 +0000
3185+++ bin/pt-kill 2012-06-01 14:38:21 +0000
3186@@ -4862,6 +4862,6 @@
3187
3188 =head1 VERSION
3189
3190-pt-kill 2.0.4
3191+pt-kill 2.1.1
3192
3193 =cut
3194
3195=== modified file 'bin/pt-log-player'
3196--- bin/pt-log-player 2012-05-24 17:52:01 +0000
3197+++ bin/pt-log-player 2012-06-01 14:38:21 +0000
3198@@ -3609,6 +3609,6 @@
3199
3200 =head1 VERSION
3201
3202-pt-log-player 2.0.4
3203+pt-log-player 2.1.1
3204
3205 =cut
3206
3207=== modified file 'bin/pt-mext'
3208--- bin/pt-mext 2012-05-16 17:31:10 +0000
3209+++ bin/pt-mext 2012-06-01 14:38:21 +0000
3210@@ -279,7 +279,7 @@
3211
3212 =head1 VERSION
3213
3214-pt-mext 2.0.4
3215+pt-mext 2.1.1
3216
3217 =cut
3218
3219
3220=== modified file 'bin/pt-mysql-summary'
3221--- bin/pt-mysql-summary 2012-05-16 17:31:10 +0000
3222+++ bin/pt-mysql-summary 2012-06-01 14:38:21 +0000
3223@@ -4,14 +4,421 @@
3224 # See "COPYRIGHT, LICENSE, AND WARRANTY" at the end of this file for legal
3225 # notices and disclaimers.
3226
3227+set -u
3228+
3229+# ###########################################################################
3230+# log_warn_die package
3231+# This package is a copy without comments from the original. The original
3232+# with comments and its test file can be found in the Bazaar repository at,
3233+# lib/bash/log_warn_die.sh
3234+# t/lib/bash/log_warn_die.sh
3235+# See https://launchpad.net/percona-toolkit for more information.
3236+# ###########################################################################
3237+
3238+
3239+set -u
3240+
3241+PTFUNCNAME=""
3242+PTDEBUG="${PTDEBUG:-""}"
3243+EXIT_STATUS=0
3244+
3245+log() {
3246+ TS=$(date +%F-%T | tr :- _);
3247+ echo "$TS $*"
3248+}
3249+
3250+warn() {
3251+ log "$*" >&2
3252+ EXIT_STATUS=1
3253+}
3254+
3255+die() {
3256+ warn "$*"
3257+ exit 1
3258+}
3259+
3260+_d () {
3261+ [ "$PTDEBUG" ] && echo "# $PTFUNCNAME: $(log "$*")" >&2
3262+}
3263+
3264+# ###########################################################################
3265+# End log_warn_die package
3266+# ###########################################################################
3267+
3268+# ###########################################################################
3269+# parse_options package
3270+# This package is a copy without comments from the original. The original
3271+# with comments and its test file can be found in the Bazaar repository at,
3272+# lib/bash/parse_options.sh
3273+# t/lib/bash/parse_options.sh
3274+# See https://launchpad.net/percona-toolkit for more information.
3275+# ###########################################################################
3276+
3277+
3278+
3279+
3280+
3281+set -u
3282+
3283+ARGV="" # Non-option args (probably input files)
3284+EXT_ARGV="" # Everything after -- (args for an external command)
3285+HAVE_EXT_ARGV="" # Got --, everything else is put into EXT_ARGV
3286+OPT_ERRS=0 # How many command line option errors
3287+OPT_VERSION="" # If --version was specified
3288+OPT_HELP="" # If --help was specified
3289+PO_DIR="" # Directory with program option spec files
3290+
3291 usage() {
3292- if [ "${OPT_ERR}" ]; then
3293- echo "${OPT_ERR}" >&2
3294- fi
3295- echo "Usage: pt-mysql-summary [MYSQL-OPTIONS]" >&2
3296- echo "For more information, 'man pt-mysql-summary' or 'perldoc $0'" >&2
3297- exit 1
3298-}
3299+ local file="$1"
3300+
3301+ local usage=$(grep '^Usage: ' "$file")
3302+ echo $usage
3303+ echo
3304+ echo "For more information, 'man $TOOL' or 'perldoc $file'."
3305+}
3306+
3307+usage_or_errors() {
3308+ local file="$1"
3309+
3310+ if [ "$OPT_VERSION" ]; then
3311+ local version=$(grep '^pt-[^ ]\+ [0-9]' "$file")
3312+ echo "$version"
3313+ return 1
3314+ fi
3315+
3316+ if [ "$OPT_HELP" ]; then
3317+ usage "$file"
3318+ echo
3319+ echo "Command line options:"
3320+ echo
3321+ perl -e '
3322+ use strict;
3323+ use warnings FATAL => qw(all);
3324+ my $lcol = 20; # Allow this much space for option names.
3325+ my $rcol = 80 - $lcol; # The terminal is assumed to be 80 chars wide.
3326+ my $name;
3327+ while ( <> ) {
3328+ my $line = $_;
3329+ chomp $line;
3330+ if ( $line =~ s/^long:/ --/ ) {
3331+ $name = $line;
3332+ }
3333+ elsif ( $line =~ s/^desc:// ) {
3334+ $line =~ s/ +$//mg;
3335+ my @lines = grep { $_ }
3336+ $line =~ m/(.{0,$rcol})(?:\s+|\Z)/g;
3337+ if ( length($name) >= $lcol ) {
3338+ print $name, "\n", (q{ } x $lcol);
3339+ }
3340+ else {
3341+ printf "%-${lcol}s", $name;
3342+ }
3343+ print join("\n" . (q{ } x $lcol), @lines);
3344+ print "\n";
3345+ }
3346+ }
3347+ ' "$PO_DIR"/*
3348+ echo
3349+ echo "Options and values after processing arguments:"
3350+ echo
3351+ for opt in $(ls "$PO_DIR"); do
3352+ local varname="OPT_$(echo "$opt" | tr a-z- A-Z_)"
3353+ local varvalue="${!varname}"
3354+ printf -- " --%-30s %s" "$opt" "${varvalue:-(No value)}"
3355+ echo
3356+ done
3357+ return 1
3358+ fi
3359+
3360+ if [ $OPT_ERRS -gt 0 ]; then
3361+ echo
3362+ usage "$file"
3363+ return 1
3364+ fi
3365+
3366+ return 0
3367+}
3368+
3369+option_error() {
3370+ local err="$1"
3371+ OPT_ERRS=$(($OPT_ERRS + 1))
3372+ echo "$err" >&2
3373+}
3374+
3375+parse_options() {
3376+ local file="$1"
3377+ shift
3378+
3379+ ARGV=""
3380+ EXT_ARGV=""
3381+ HAVE_EXT_ARGV=""
3382+ OPT_ERRS=0
3383+ OPT_VERSION=""
3384+ OPT_HELP=""
3385+ PO_DIR="$TMPDIR/po"
3386+
3387+ if [ ! -d "$PO_DIR" ]; then
3388+ mkdir "$PO_DIR"
3389+ if [ $? -ne 0 ]; then
3390+ echo "Cannot mkdir $PO_DIR" >&2
3391+ exit 1
3392+ fi
3393+ fi
3394+
3395+ rm -rf "$PO_DIR"/*
3396+ if [ $? -ne 0 ]; then
3397+ echo "Cannot rm -rf $PO_DIR/*" >&2
3398+ exit 1
3399+ fi
3400+
3401+ _parse_pod "$file" # Parse POD into program option (po) spec files
3402+ _eval_po # Eval po into existence with default values
3403+
3404+ if [ $# -ge 2 ] && [ "$1" = "--config" ]; then
3405+ shift # --config
3406+ local user_config_files="$1"
3407+ shift # that ^
3408+ local IFS=","
3409+ for user_config_file in $user_config_files; do
3410+ _parse_config_files "$user_config_file"
3411+ done
3412+ else
3413+ _parse_config_files "/etc/percona-toolkit/percona-toolkit.conf" "/etc/percona-toolkit/$TOOL.conf" "$HOME/.percona-toolkit.conf" "$HOME/.$TOOL.conf"
3414+ fi
3415+
3416+ _parse_command_line "$@"
3417+}
3418+
3419+_parse_pod() {
3420+ local file="$1"
3421+
3422+ cat "$file" | PO_DIR="$PO_DIR" perl -ne '
3423+ BEGIN { $/ = ""; }
3424+ next unless $_ =~ m/^=head1 OPTIONS/;
3425+ while ( defined(my $para = <>) ) {
3426+ last if $para =~ m/^=head1/;
3427+ chomp;
3428+ if ( $para =~ m/^=item --(\S+)/ ) {
3429+ my $opt = $1;
3430+ my $file = "$ENV{PO_DIR}/$opt";
3431+ open my $opt_fh, ">", $file or die "Cannot open $file: $!";
3432+ print $opt_fh "long:$opt\n";
3433+ $para = <>;
3434+ chomp;
3435+ if ( $para =~ m/^[a-z ]+:/ ) {
3436+ map {
3437+ chomp;
3438+ my ($attrib, $val) = split(/: /, $_);
3439+ print $opt_fh "$attrib:$val\n";
3440+ } split(/; /, $para);
3441+ $para = <>;
3442+ chomp;
3443+ }
3444+ my ($desc) = $para =~ m/^([^?.]+)/;
3445+ print $opt_fh "desc:$desc.\n";
3446+ close $opt_fh;
3447+ }
3448+ }
3449+ last;
3450+ '
3451+}
3452+
3453+_eval_po() {
3454+ local IFS=":"
3455+ for opt_spec in "$PO_DIR"/*; do
3456+ local opt=""
3457+ local default_val=""
3458+ local neg=0
3459+ local size=0
3460+ while read key val; do
3461+ case "$key" in
3462+ long)
3463+ opt=$(echo $val | sed 's/-/_/g' | tr [:lower:] [:upper:])
3464+ ;;
3465+ default)
3466+ default_val="$val"
3467+ ;;
3468+ "short form")
3469+ ;;
3470+ type)
3471+ [ "$val" = "size" ] && size=1
3472+ ;;
3473+ desc)
3474+ ;;
3475+ negatable)
3476+ if [ "$val" = "yes" ]; then
3477+ neg=1
3478+ fi
3479+ ;;
3480+ *)
3481+ echo "Invalid attribute in $opt_spec: $line" >&2
3482+ exit 1
3483+ esac
3484+ done < "$opt_spec"
3485+
3486+ if [ -z "$opt" ]; then
3487+ echo "No long attribute in option spec $opt_spec" >&2
3488+ exit 1
3489+ fi
3490+
3491+ if [ $neg -eq 1 ]; then
3492+ if [ -z "$default_val" ] || [ "$default_val" != "yes" ]; then
3493+ echo "Option $opt_spec is negatable but not default: yes" >&2
3494+ exit 1
3495+ fi
3496+ fi
3497+
3498+ if [ $size -eq 1 -a -n "$default_val" ]; then
3499+ default_val=$(size_to_bytes $default_val)
3500+ fi
3501+
3502+ eval "OPT_${opt}"="$default_val"
3503+ done
3504+}
3505+
3506+_parse_config_files() {
3507+
3508+ for config_file in "$@"; do
3509+ test -f "$config_file" || continue
3510+
3511+ while read config_opt; do
3512+
3513+ echo "$config_opt" | grep '^[ ]*[^#]' >/dev/null 2>&1 || continue
3514+
3515+ config_opt="$(echo "$config_opt" | sed -e 's/^ *//g' -e 's/ *$//g' -e 's/[ ]*=[ ]*/=/' -e 's/[ ]*#.*$//')"
3516+
3517+ [ "$config_opt" = "" ] && continue
3518+
3519+ if ! [ "$HAVE_EXT_ARGV" ]; then
3520+ config_opt="--$config_opt"
3521+ fi
3522+
3523+ _parse_command_line "$config_opt"
3524+
3525+ done < "$config_file"
3526+
3527+ HAVE_EXT_ARGV="" # reset for each file
3528+
3529+ done
3530+}
3531+
3532+_parse_command_line() {
3533+ local opt=""
3534+ local val=""
3535+ local next_opt_is_val=""
3536+ local opt_is_ok=""
3537+ local opt_is_negated=""
3538+ local real_opt=""
3539+ local required_arg=""
3540+ local spec=""
3541+
3542+ for opt in "$@"; do
3543+ if [ "$opt" = "--" -o "$opt" = "----" ]; then
3544+ HAVE_EXT_ARGV=1
3545+ continue
3546+ fi
3547+ if [ "$HAVE_EXT_ARGV" ]; then
3548+ if [ "$EXT_ARGV" ]; then
3549+ EXT_ARGV="$EXT_ARGV $opt"
3550+ else
3551+ EXT_ARGV="$opt"
3552+ fi
3553+ continue
3554+ fi
3555+
3556+ if [ "$next_opt_is_val" ]; then
3557+ next_opt_is_val=""
3558+ if [ $# -eq 0 ] || [ $(expr "$opt" : "-") -eq 1 ]; then
3559+ option_error "$real_opt requires a $required_arg argument"
3560+ continue
3561+ fi
3562+ val="$opt"
3563+ opt_is_ok=1
3564+ else
3565+ if [ $(expr "$opt" : "-") -eq 0 ]; then
3566+ if [ -z "$ARGV" ]; then
3567+ ARGV="$opt"
3568+ else
3569+ ARGV="$ARGV $opt"
3570+ fi
3571+ continue
3572+ fi
3573+
3574+ real_opt="$opt"
3575+
3576+ if $(echo $opt | grep '^--no-' >/dev/null); then
3577+ opt_is_negated=1
3578+ opt=$(echo $opt | sed 's/^--no-//')
3579+ else
3580+ opt_is_negated=""
3581+ opt=$(echo $opt | sed 's/^-*//')
3582+ fi
3583+
3584+ if $(echo $opt | grep '^[a-z-][a-z-]*=' >/dev/null 2>&1); then
3585+ val="$(echo $opt | awk -F= '{print $2}')"
3586+ opt="$(echo $opt | awk -F= '{print $1}')"
3587+ fi
3588+
3589+ if [ -f "$TMPDIR/po/$opt" ]; then
3590+ spec="$TMPDIR/po/$opt"
3591+ else
3592+ spec=$(grep "^short form:-$opt\$" "$TMPDIR"/po/* | cut -d ':' -f 1)
3593+ if [ -z "$spec" ]; then
3594+ option_error "Unknown option: $real_opt"
3595+ continue
3596+ fi
3597+ fi
3598+
3599+ required_arg=$(cat "$spec" | awk -F: '/^type:/{print $2}')
3600+ if [ "$required_arg" ]; then
3601+ if [ "$val" ]; then
3602+ opt_is_ok=1
3603+ else
3604+ next_opt_is_val=1
3605+ fi
3606+ else
3607+ if [ "$val" ]; then
3608+ option_error "Option $real_opt does not take a value"
3609+ continue
3610+ fi
3611+ if [ "$opt_is_negated" ]; then
3612+ val=""
3613+ else
3614+ val="yes"
3615+ fi
3616+ opt_is_ok=1
3617+ fi
3618+ fi
3619+
3620+ if [ "$opt_is_ok" ]; then
3621+ opt=$(cat "$spec" | grep '^long:' | cut -d':' -f2 | sed 's/-/_/g' | tr [:lower:] [:upper:])
3622+
3623+ if grep "^type:size" "$spec" >/dev/null; then
3624+ val=$(size_to_bytes $val)
3625+ fi
3626+
3627+ eval "OPT_$opt"="'$val'"
3628+
3629+ opt=""
3630+ val=""
3631+ next_opt_is_val=""
3632+ opt_is_ok=""
3633+ opt_is_negated=""
3634+ real_opt=""
3635+ required_arg=""
3636+ spec=""
3637+ fi
3638+ done
3639+}
3640+
3641+size_to_bytes() {
3642+ local size="$1"
3643+ echo $size | perl -ne '%f=(B=>1, K=>1_024, M=>1_048_576, G=>1_073_741_824, T=>1_099_511_627_776); m/^(\d+)([kMGT])?/i; print $1 * $f{uc($2 || "B")};'
3644+}
3645+
3646+# ###########################################################################
3647+# End parse_options package
3648+# ###########################################################################
3649
3650 # ###########################################################################
3651 # tmpdir package
3652@@ -22,6 +429,9 @@
3653 # See https://launchpad.net/percona-toolkit for more information.
3654 # ###########################################################################
3655
3656+
3657+set -u
3658+
3659 TMPDIR=""
3660
3661 mk_tmpdir() {
3662@@ -51,27 +461,68 @@
3663 # End tmpdir package
3664 # ###########################################################################
3665
3666-# ########################################################################
3667-# Some global setup is necessary for cross-platform compatibility, even
3668-# when sourcing this script for testing purposes.
3669-# ########################################################################
3670-AP_AWK="$(which awk)"
3671-which gawk >/dev/null 2>&1 && AP_AWK="$(which gawk)"
3672-AP_SED="$(which sed)"
3673-which gsed >/dev/null 2>&1 && AP_SED="$(which gsed)"
3674-AP_GREP="$(which grep)"
3675-which ggrep >/dev/null 2>&1 && AP_GREP="$(which ggrep)"
3676-
3677-# ########################################################################
3678-# Globals, helper functions
3679-# ########################################################################
3680-
3681-# The awk code for fuzzy rounding. (It's used in a few places, so makes sense
3682-# not to duplicate). It fuzzy-rounds the variable named fuzzy_var. It goes in
3683-# steps of 5, 10, 25, then repeats by a factor of 10 larger (50, 100, 250), and
3684-# so on, until it finds a number that's large enough. The pattern is slightly
3685-# broken between the initial 1 and 50, because rounding to the nearest 2.5
3686-# doesn't seem right to me.
3687+# ###########################################################################
3688+# alt_cmds package
3689+# This package is a copy without comments from the original. The original
3690+# with comments and its test file can be found in the Bazaar repository at,
3691+# lib/bash/alt_cmds.sh
3692+# t/lib/bash/alt_cmds.sh
3693+# See https://launchpad.net/percona-toolkit for more information.
3694+# ###########################################################################
3695+
3696+
3697+set -u
3698+
3699+_seq() {
3700+ local i="$1"
3701+ awk "BEGIN { for(i=1; i<=$i; i++) print i; }"
3702+}
3703+
3704+_pidof() {
3705+ local cmd="$1"
3706+ if ! pidof "$cmd" 2>/dev/null; then
3707+ ps -eo pid,ucomm | awk -v comm="$cmd" '$2 == comm { print $1 }'
3708+ fi
3709+}
3710+
3711+_lsof() {
3712+ local pid="$1"
3713+ if ! lsof -p $pid 2>/dev/null; then
3714+ /bin/ls -l /proc/$pid/fd 2>/dev/null
3715+ fi
3716+}
3717+
3718+
3719+
3720+_which() {
3721+ if [ -x /usr/bin/which ]; then
3722+ /usr/bin/which "$1" 2>/dev/null | awk '{print $1}'
3723+ elif which which 1>/dev/null 2>&1; then
3724+ which "$1" 2>/dev/null | awk '{print $1}'
3725+ else
3726+ echo "$1"
3727+ fi
3728+}
3729+
3730+# ###########################################################################
3731+# End alt_cmds package
3732+# ###########################################################################
3733+
3734+# ###########################################################################
3735+# report_formatting package
3736+# This package is a copy without comments from the original. The original
3737+# with comments and its test file can be found in the Bazaar repository at,
3738+# lib/bash/report_formatting.sh
3739+# t/lib/bash/report_formatting.sh
3740+# See https://launchpad.net/percona-toolkit for more information.
3741+# ###########################################################################
3742+
3743+
3744+set -u
3745+
3746+POSIXLY_CORRECT=1
3747+export POSIXLY_CORRECT
3748+
3749 fuzzy_formula='
3750 rounded = 0;
3751 if (fuzzy_var <= 10 ) {
3752@@ -94,108 +545,458 @@
3753 factor = factor * 10;
3754 }'
3755
3756-# The temp files are for storing working results so we don't call commands many
3757-# times (gives inconsistent results, maybe adds load on things I don't want to
3758-# such as RAID controllers). They must not exist -- if they did, someone would
3759-# symlink them to /etc/passwd and then run this program as root. Call this
3760-# function with "rm" or "touch" as an argument.
3761-temp_files() {
3762- for file in $TMPDIR/percona-toolkit{,-mysql-variables,-mysql-status,-innodb-status} \
3763- $TMPDIR/percona-toolkit{2,-mysql-databases,-mysql-processlist,-noncounters} \
3764- $TMPDIR/percona-toolkit-mysql{dump,-slave};
3765- do
3766- case "$1" in
3767- touch)
3768- if ! touch "${file}"; then
3769- echo "I can't make my temp file ${file}";
3770- exit 1;
3771- fi
3772- ;;
3773- rm)
3774- rm -f "${file}"
3775- ;;
3776- esac
3777- done
3778-}
3779-
3780-# Print a space-padded string into $line. Then translate spaces to hashes, and
3781-# underscores to spaces. End result is a line of hashes with words at the
3782-# start.
3783+fuzz () {
3784+ awk -v fuzzy_var="$1" "BEGIN { ${fuzzy_formula} print fuzzy_var;}"
3785+}
3786+
3787+fuzzy_pct () {
3788+ local pct="$(awk -v one="$1" -v two="$2" 'BEGIN{ if (two > 0) { printf "%d", one/two*100; } else {print 0} }')";
3789+ echo "$(fuzz "${pct}")%"
3790+}
3791+
3792 section () {
3793- line="$(printf '#_%-60s' "$1_")"
3794- line="${line// /#}"
3795- printf "%s\n" "${line//_/ }"
3796-}
3797-
3798-# Print a "name | value" line.
3799-name_val() {
3800- printf "%20s | %s\n" "$1" "$2"
3801-}
3802-
3803-# Converts a value to units of power of 2. Optional precision is $2.
3804+ local str="$1"
3805+ awk -v var="${str} _" 'BEGIN {
3806+ line = sprintf("# %-60s", var);
3807+ i = index(line, "_");
3808+ x = substr(line, i);
3809+ gsub(/[_ \t]/, "#", x);
3810+ printf("%s%s\n", substr(line, 1, i-1), x);
3811+ }'
3812+}
3813+
3814+NAME_VAL_LEN=12
3815+name_val () {
3816+ printf "%+*s | %s\n" "${NAME_VAL_LEN}" "$1" "$2"
3817+}
3818+
3819 shorten() {
3820- unit=k
3821- size=1024
3822- if [ $1 -ge 1099511627776 ] ; then
3823- size=1099511627776
3824- unit=T
3825- elif [ $1 -ge 1073741824 ] ; then
3826- size=1073741824
3827- unit=G
3828- elif [ $1 -ge 1048576 ] ; then
3829- size=1048576
3830- unit=M
3831- fi
3832- result=$(echo "$1 $size ${2:-0}" | $AP_AWK '{printf "%." $3 "f", $1 / $2}')
3833- echo "${result}${unit}"
3834+ local num="$1"
3835+ local prec="${2:-2}"
3836+ local div="${3:-1024}"
3837+
3838+ echo "$num" | awk -v prec="$prec" -v div="$div" '
3839+ {
3840+ num = $1;
3841+ unit = num >= 1125899906842624 ? "P" \
3842+ : num >= 1099511627776 ? "T" \
3843+ : num >= 1073741824 ? "G" \
3844+ : num >= 1048576 ? "M" \
3845+ : num >= 1024 ? "k" \
3846+ : "";
3847+ while ( num >= div ) {
3848+ num /= div;
3849+ }
3850+ printf "%.*f%s", prec, num, unit;
3851+ }
3852+ '
3853 }
3854
3855-# Collapse a file into an aggregated list; file must be created with 'sort |
3856-# uniq -c'. This function is copy-pasted from 'summary' so see there for full
3857-# docs and tests.
3858-# ##############################################################################
3859 group_concat () {
3860- sed -e '{H; $!d}' -e 'x' -e 's/\n[[:space:]]*\([[:digit:]]*\)[[:space:]]*/, \1x/g' -e 's/[[:space:]][[:space:]]*/ /g' -e 's/, //' ${1}
3861-}
3862-
3863-# Accepts a number of seconds, and outputs a d+h:m:s formatted string
3864+ sed -e '{H; $!d;}' -e 'x' -e 's/\n[[:space:]]*\([[:digit:]]*\)[[:space:]]*/, \1x/g' -e 's/[[:space:]][[:space:]]*/ /g' -e 's/, //' "${1}"
3865+}
3866+
3867+# ###########################################################################
3868+# End report_formatting package
3869+# ###########################################################################
3870+
3871+# ###########################################################################
3872+# summary_common package
3873+# This package is a copy without comments from the original. The original
3874+# with comments and its test file can be found in the Bazaar repository at,
3875+# lib/bash/summary_common.sh
3876+# t/lib/bash/summary_common.sh
3877+# See https://launchpad.net/percona-toolkit for more information.
3878+# ###########################################################################
3879+
3880+
3881+set -u
3882+
3883+CMD_FILE="$( _which file 2>/dev/null )"
3884+CMD_NM="$( _which nm 2>/dev/null )"
3885+CMD_OBJDUMP="$( _which objdump 2>/dev/null )"
3886+
3887+get_nice_of_pid () {
3888+ local pid="$1"
3889+ local niceness="$(ps -p $pid -o nice | awk '$1 !~ /[^0-9]/ {print $1; exit}')"
3890+
3891+ if [ -n "${niceness}" ]; then
3892+ echo $niceness
3893+ else
3894+ local tmpfile="$TMPDIR/nice_through_c.tmp.c"
3895+ _d "Getting the niceness from ps failed, somehow. We are about to try this:"
3896+ cat <<EOC > "$tmpfile"
3897+
3898+int main(void) {
3899+ int priority = getpriority(PRIO_PROCESS, $pid);
3900+ if ( priority == -1 && errno == ESRCH ) {
3901+ return 1;
3902+ }
3903+ else {
3904+ printf("%d\\n", priority);
3905+ return 0;
3906+ }
3907+}
3908+
3909+EOC
3910+ local c_comp=$(_which gcc)
3911+ if [ -z "${c_comp}" ]; then
3912+ c_comp=$(_which cc)
3913+ fi
3914+ _d "$tmpfile: $( cat "$tmpfile" )"
3915+ _d "$c_comp -xc \"$tmpfile\" -o \"$tmpfile\" && eval \"$tmpfile\""
3916+ $c_comp -xc "$tmpfile" -o "$tmpfile" 2>/dev/null && eval "$tmpfile" 2>/dev/null
3917+ if [ $? -ne 0 ]; then
3918+ echo "?"
3919+ _d "Failed to get a niceness value for $pid"
3920+ fi
3921+ fi
3922+}
3923+
3924+get_oom_of_pid () {
3925+ local pid="$1"
3926+ local oom_adj=""
3927+
3928+ if [ -n "${pid}" -a -e /proc/cpuinfo ]; then
3929+ if [ -s "/proc/$pid/oom_score_adj" ]; then
3930+ oom_adj=$(cat "/proc/$pid/oom_score_adj" 2>/dev/null)
3931+ _d "For $pid, the oom value is $oom_adj, retreived from oom_score_adj"
3932+ else
3933+ oom_adj=$(cat "/proc/$pid/oom_adj" 2>/dev/null)
3934+ _d "For $pid, the oom value is $oom_adj, retreived from oom_adj"
3935+ fi
3936+ fi
3937+
3938+ if [ -n "${oom_adj}" ]; then
3939+ echo "${oom_adj}"
3940+ else
3941+ echo "?"
3942+ _d "Can't find the oom value for $pid"
3943+ fi
3944+}
3945+
3946+has_symbols () {
3947+ local executable="$(_which "$1")"
3948+ local has_symbols=""
3949+
3950+ if [ "${CMD_FILE}" ] \
3951+ && [ "$($CMD_FILE "${executable}" | grep 'not stripped' )" ]; then
3952+ has_symbols=1
3953+ elif [ "${CMD_NM}" ] \
3954+ || [ "${CMD_OBJDMP}" ]; then
3955+ if [ "${CMD_NM}" ] \
3956+ && [ !"$("${CMD_NM}" -- "${executable}" 2>&1 | grep 'File format not recognized' )" ]; then
3957+ if [ -z "$( $CMD_NM -- "${executable}" 2>&1 | grep ': no symbols' )" ]; then
3958+ has_symbols=1
3959+ fi
3960+ elif [ -z "$("${CMD_OBJDUMP}" -t -- "${executable}" | grep '^no symbols$' )" ]; then
3961+ has_symbols=1
3962+ fi
3963+ fi
3964+
3965+ if [ "${has_symbols}" ]; then
3966+ echo "Yes"
3967+ else
3968+ echo "No"
3969+ fi
3970+}
3971+
3972+setup_data_dir () {
3973+ local existing_dir="$1"
3974+ local data_dir=""
3975+ if [ -z "$existing_dir" ]; then
3976+ mkdir "$TMPDIR/data" || die "Cannot mkdir $TMPDIR/data"
3977+ data_dir="$TMPDIR/data"
3978+ else
3979+ if [ ! -d "$existing_dir" ]; then
3980+ mkdir "$existing_dir" || die "Cannot mkdir $existing_dir"
3981+ elif [ "$( ls -A "$existing_dir" )" ]; then
3982+ die "--save-samples directory isn't empty, halting."
3983+ fi
3984+ touch "$existing_dir/test" || die "Cannot write to $existing_dir"
3985+ rm "$existing_dir/test" || die "Cannot rm $existing_dir/test"
3986+ data_dir="$existing_dir"
3987+ fi
3988+ echo "$data_dir"
3989+}
3990+
3991+get_var () {
3992+ local varname="$1"
3993+ local file="$2"
3994+ awk -v pattern="${varname}" '$1 == pattern { if (length($2)) { len = length($1); print substr($0, len+index(substr($0, len+1), $2)) } }' "${file}"
3995+}
3996+
3997+# ###########################################################################
3998+# End summary_common package
3999+# ###########################################################################
4000+
4001+# ###########################################################################
4002+# collect_mysql_info package
4003+# This package is a copy without comments from the original. The original
4004+# with comments and its test file can be found in the Bazaar repository at,
4005+# lib/bash/collect_mysql_info.sh
4006+# t/lib/bash/collect_mysql_info.sh
4007+# See https://launchpad.net/percona-toolkit for more information.
4008+# ###########################################################################
4009+
4010+
4011+
4012+CMD_MYSQL="${CMD_MYSQL:-""}"
4013+CMD_MYSQLDUMP="${CMD_MYSQLDUMP:-""}"
4014+
4015+collect_mysqld_instances () {
4016+ local variables_file="$1"
4017+
4018+ local pids="$(_pidof mysqld)"
4019+
4020+ if [ -n "$pids" ]; then
4021+
4022+ for pid in $pids; do
4023+ local nice="$( get_nice_of_pid $pid )"
4024+ local oom="$( get_oom_of_pid $pid )"
4025+ echo "internal::nice_of_$pid $nice" >> "$variables_file"
4026+ echo "internal::oom_of_$pid $oom" >> "$variables_file"
4027+ done
4028+
4029+ pids="$(echo $pids | sed -e 's/ /,/g')"
4030+ ps ww -p "$pids" 2>/dev/null
4031+ else
4032+ echo "mysqld doesn't appear to be running"
4033+ fi
4034+
4035+}
4036+
4037+find_my_cnf_file() {
4038+ local file="$1"
4039+ local port="${2:-""}"
4040+
4041+ local cnf_file=""
4042+ if test -n "$port" && grep -- "/mysqld.*--port=$port" "${file}" >/dev/null 2>&1 ; then
4043+ cnf_file="$(grep -- "/mysqld.*--port=$port" "${file}" \
4044+ | awk 'BEGIN{RS=" "; FS="=";} $1 ~ /--defaults-file/ { print $2; }' \
4045+ | head -n1)"
4046+ else
4047+ cnf_file="$(grep '/mysqld' "${file}" \
4048+ | awk 'BEGIN{RS=" "; FS="=";} $1 ~ /--defaults-file/ { print $2; }' \
4049+ | head -n1)"
4050+ fi
4051+
4052+ if [ ! -n "${cnf_file}" ]; then
4053+ cnf_file="/etc/my.cnf";
4054+ if [ ! -e "${cnf_file}" ]; then
4055+ cnf_file="/etc/mysql/my.cnf";
4056+ fi
4057+ if [ ! -e "${cnf_file}" ]; then
4058+ cnf_file="/var/db/mysql/my.cnf";
4059+ fi
4060+ fi
4061+
4062+ echo "$cnf_file"
4063+}
4064+
4065+collect_mysql_variables () {
4066+ $CMD_MYSQL $EXT_ARGV -ss -e 'SHOW /*!40100 GLOBAL*/ VARIABLES'
4067+}
4068+
4069+collect_mysql_status () {
4070+ $CMD_MYSQL $EXT_ARGV -ss -e 'SHOW /*!50000 GLOBAL*/ STATUS'
4071+}
4072+
4073+collect_mysql_databases () {
4074+ $CMD_MYSQL $EXT_ARGV -ss -e 'SHOW DATABASES' 2>/dev/null
4075+}
4076+
4077+collect_mysql_plugins () {
4078+ $CMD_MYSQL $EXT_ARGV -ss -e 'SHOW PLUGINS' 2>/dev/null
4079+}
4080+
4081+collect_mysql_slave_status () {
4082+ $CMD_MYSQL $EXT_ARGV -ssE -e 'SHOW SLAVE STATUS' 2>/dev/null
4083+}
4084+
4085+collect_mysql_innodb_status () {
4086+ $CMD_MYSQL $EXT_ARGV -ssE -e 'SHOW /*!50000 ENGINE*/ INNODB STATUS' 2>/dev/null
4087+}
4088+
4089+collect_mysql_processlist () {
4090+ $CMD_MYSQL $EXT_ARGV -ssE -e 'SHOW FULL PROCESSLIST' 2>/dev/null
4091+}
4092+
4093+collect_mysql_users () {
4094+ $CMD_MYSQL $EXT_ARGV -ss -e 'SELECT COUNT(*), SUM(user=""), SUM(password=""), SUM(password NOT LIKE "*%") FROM mysql.user' 2>/dev/null
4095+}
4096+
4097+collect_master_logs_status () {
4098+ local master_logs_file="$1"
4099+ local master_status_file="$2"
4100+ $CMD_MYSQL $EXT_ARGV -ss -e 'SHOW MASTER LOGS' > "$master_logs_file" 2>/dev/null
4101+ $CMD_MYSQL $EXT_ARGV -ss -e 'SHOW MASTER STATUS' > "$master_status_file" 2>/dev/null
4102+}
4103+
4104+collect_mysql_deferred_status () {
4105+ local status_file="$1"
4106+ collect_mysql_status > "$TMPDIR/defer_gatherer"
4107+ join "$status_file" "$TMPDIR/defer_gatherer"
4108+}
4109+
4110+collect_internal_vars () {
4111+ local mysqld_executables="${1:-""}"
4112+
4113+ local FNV_64=""
4114+ if $CMD_MYSQL $EXT_ARGV -e 'SELECT FNV_64("a")' >/dev/null 2>&1; then
4115+ FNV_64="Enabled";
4116+ else
4117+ FNV_64="Unknown";
4118+ fi
4119+
4120+ local now="$($CMD_MYSQL $EXT_ARGV -ss -e 'SELECT NOW()')"
4121+ local user="$($CMD_MYSQL $EXT_ARGV -ss -e 'SELECT CURRENT_USER()')"
4122+ local trigger_count=$($CMD_MYSQL $EXT_ARGV -ss -e "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TRIGGERS" 2>/dev/null)
4123+
4124+ echo "pt-summary-internal-mysql_executable $CMD_MYSQL"
4125+ echo "pt-summary-internal-now $now"
4126+ echo "pt-summary-internal-user $user"
4127+ echo "pt-summary-internal-FNV_64 $FNV_64"
4128+ echo "pt-summary-internal-trigger_count $trigger_count"
4129+
4130+ if [ -e "$mysqld_executables" ]; then
4131+ local i=1
4132+ while read executable; do
4133+ echo "pt-summary-internal-mysqld_executable_${i} $(has_symbols "$executable")"
4134+ i=$(($i + 1))
4135+ done < "$mysqld_executables"
4136+ fi
4137+}
4138+
4139+get_mysqldump_for () {
4140+ local args="$1"
4141+ local dbtodump="${2:-"--all-databases"}"
4142+
4143+ $CMD_MYSQLDUMP $EXT_ARGV --no-data --skip-comments \
4144+ --skip-add-locks --skip-add-drop-table --compact \
4145+ --skip-lock-all-tables --skip-lock-tables --skip-set-charset \
4146+ ${args} --databases $( local IFS=,; echo ${dbtodump})
4147+}
4148+
4149+get_mysqldump_args () {
4150+ local file="$1"
4151+ local trg_arg=""
4152+
4153+ if $CMD_MYSQLDUMP --help --verbose 2>&1 | grep triggers >/dev/null; then
4154+ trg_arg="--routines"
4155+ fi
4156+
4157+ if [ "${trg_arg}" ]; then
4158+ local triggers="--skip-triggers"
4159+ local trg=$(get_var "pt-summary-internal-trigger_count" "$file" )
4160+ if [ -n "${trg}" ] && [ "${trg}" -gt 0 ]; then
4161+ triggers="--triggers"
4162+ fi
4163+ trg_arg="${trg_arg} ${triggers}";
4164+ fi
4165+ echo "${trg_arg}"
4166+}
4167+
4168+collect_mysqld_executables () {
4169+ local mysqld_instances="$1"
4170+
4171+ for pid in $( grep '/mysqld' "$mysqld_instances" | awk '/^ .*[0-9]/{print $1}' ); do
4172+ ps -o cmd -p $pid | sed -e 's/^\(.*mysqld\) .*/\1/' | grep -v '^CMD$'
4173+ done | sort -u
4174+}
4175+
4176+collect_mysql_info () {
4177+ local dir="$1"
4178+
4179+ collect_mysql_variables > "$dir/mysql-variables"
4180+ collect_mysql_status > "$dir/mysql-status"
4181+ collect_mysql_databases > "$dir/mysql-databases"
4182+ collect_mysql_plugins > "$dir/mysql-plugins"
4183+ collect_mysql_slave_status > "$dir/mysql-slave"
4184+ collect_mysql_innodb_status > "$dir/innodb-status"
4185+ collect_mysql_processlist > "$dir/mysql-processlist"
4186+ collect_mysql_users > "$dir/mysql-users"
4187+
4188+ collect_mysqld_instances "$dir/mysql-variables" > "$dir/mysqld-instances"
4189+ collect_mysqld_executables "$dir/mysqld-instances" > "$dir/mysqld-executables"
4190+
4191+ local binlog="$(get_var log_bin "$dir/mysql-variables")"
4192+ if [ "${binlog}" ]; then
4193+ collect_master_logs_status "$dir/mysql-master-logs" "$dir/mysql-master-status"
4194+ fi
4195+
4196+ local uptime="$(get_var Uptime "$dir/mysql-status")"
4197+ local current_time="$($CMD_MYSQL $EXT_ARGV -ss -e \
4198+ "SELECT LEFT(NOW() - INTERVAL ${uptime} SECOND, 16)")"
4199+
4200+ local port="$(get_var port "$dir/mysql-variables")"
4201+ local cnf_file="$(find_my_cnf_file "$dir/mysqld-instances" ${port})"
4202+
4203+ cat "$cnf_file" > "$dir/mysql-config-file"
4204+
4205+ local pid_file="$(get_var "pid_file" "$dir/mysql-variables")"
4206+ local pid_file_exists=""
4207+ [ -e "${pid_file}" ] && pid_file_exists=1
4208+ echo "pt-summary-internal-pid_file_exists $pid_file_exists" >> "$dir/mysql-variables"
4209+
4210+ echo "pt-summary-internal-current_time $current_time" >> "$dir/mysql-variables"
4211+ echo "pt-summary-internal-Config_File_path $cnf_file" >> "$dir/mysql-variables"
4212+ collect_internal_vars "$dir/mysqld-executables" >> "$dir/mysql-variables"
4213+
4214+ if [ -n "${OPT_DATABASES}" ]; then
4215+ local trg_arg="$( get_mysqldump_args "$dir/mysql-variables" )"
4216+ get_mysqldump_for "${trg_arg}" "${OPT_DATABASES}" > "$dir/mysqldump"
4217+ fi
4218+
4219+ (
4220+ sleep $OPT_SLEEP
4221+ collect_mysql_deferred_status "$dir/mysql-status" > "$dir/mysql-status-defer"
4222+ ) &
4223+ _d "Forked child is $!"
4224+}
4225+
4226+# ###########################################################################
4227+# End collect_mysql_info package
4228+# ###########################################################################
4229+
4230+# ###########################################################################
4231+# report_mysql_info package
4232+# This package is a copy without comments from the original. The original
4233+# with comments and its test file can be found in the Bazaar repository at,
4234+# lib/bash/report_mysql_info.sh
4235+# t/lib/bash/report_mysql_info.sh
4236+# See https://launchpad.net/percona-toolkit for more information.
4237+# ###########################################################################
4238+
4239+
4240+set -u
4241+POSIXLY_CORRECT=1
4242+
4243 secs_to_time () {
4244- echo "$1" | $AP_AWK '{
4245- printf( "%d+%02d:%02d:%02d", $1 / 86400, ($1 % 86400) / 3600, ($1 % 3600) / 60, $1 % 60);
4246+ awk -v sec="$1" 'BEGIN {
4247+ printf( "%d+%02d:%02d:%02d", sec / 86400, (sec % 86400) / 3600, (sec % 3600) / 60, sec % 60);
4248 }'
4249 }
4250
4251-# gets a value from $TMPDIR/percona-toolkit-mysql-variables. Returns zero if it doesn't
4252-# exist.
4253-get_var () {
4254- v="$($AP_AWK "\$1 ~ /^$1$/ { print \$2 }" $TMPDIR/percona-toolkit-mysql-variables)"
4255- echo "${v:-0}"
4256-}
4257-
4258-# Returns true if a variable exists
4259-var_exists () {
4260- $AP_GREP "$1" $TMPDIR/percona-toolkit-mysql-variables >/dev/null 2>&1;
4261-}
4262-
4263-# Returns "Enabled", "Disabled", or "Not Supported" depending on whether the
4264-# variable exists and is ON or enabled. You can pass 2nd and 3rd variables to
4265-# control whether the variable should be 'gt' (numeric greater than) or 'eq'
4266-# (string equal) to some value.
4267 feat_on() {
4268- if var_exists $1 ; then
4269- var="$($AP_AWK "\$1 ~ /^$1$/ { print \$2 }" $TMPDIR/percona-toolkit-mysql-variables)"
4270+ local file="$1"
4271+ local varname="$2"
4272+ [ -e "$file" ] || return
4273+
4274+ if [ "$( get_var "$varname" "${file}" )" ]; then
4275+ local var="$(awk "\$1 ~ /^$2$/ { print \$2 }" $file)"
4276 if [ "${var}" = "ON" ]; then
4277 echo "Enabled"
4278 elif [ "${var}" = "OFF" -o "${var}" = "0" -o -z "${var}" ]; then
4279 echo "Disabled"
4280- elif [ "$2" = "ne" ]; then
4281- if [ "${var}" != "$3" ]; then
4282+ elif [ "$3" = "ne" ]; then
4283+ if [ "${var}" != "$4" ]; then
4284 echo "Enabled"
4285 else
4286 echo "Disabled"
4287 fi
4288- elif [ "$2" = "gt" ]; then
4289- if [ "${var}" -gt "$3" ]; then
4290+ elif [ "$3" = "gt" ]; then
4291+ if [ "${var}" -gt "$4" ]; then
4292 echo "Enabled"
4293 else
4294 echo "Disabled"
4295@@ -210,128 +1011,124 @@
4296 fi
4297 }
4298
4299-# gets a value from $TMPDIR/percona-toolkit-mysql-status. Returns zero if it doesn't
4300-# exist.
4301-get_stat () {
4302- v="$($AP_AWK "\$1 ~ /^$1$/ { print \$2 }" $TMPDIR/percona-toolkit-mysql-status)"
4303- echo "${v:-0}"
4304-}
4305-
4306-# Does fuzzy rounding: rounds to nearest interval, but the interval gets larger
4307-# as the number gets larger. This is to make things easier to diff.
4308-fuzz () {
4309- echo $1 | $AP_AWK "{fuzzy_var=\$1; ${fuzzy_formula} print fuzzy_var;}"
4310-}
4311-
4312-# Fuzzy computes the percent that $1 is of $2
4313-fuzzy_pct () {
4314- pct=$(echo $1 $2 | $AP_AWK '{ if ($2 > 0) { printf "%d", $1/$2*100; } else {print 0} }');
4315- echo "$(fuzz ${pct})%"
4316-}
4317-
4318-# ##############################################################################
4319-# Functions for parsing specific files and getting desired info from them.
4320-# These are called from within main() and are separated so they can be tested
4321-# easily. The calling convention is that the data they need to run is prepared
4322-# first by putting it into $TMPDIR/percona-toolkit. Then code that's testing
4323-# just needs to put sample data into $TMPDIR/percona-toolkit and call it.
4324-# ##############################################################################
4325-
4326-# Parses the output of 'ps -e -o args | $AP_GREP mysqld' or 'ps auxww...'
4327-# which should be in $TMPDIR/percona-toolkit.
4328+get_table_cache () {
4329+ local file="$1"
4330+
4331+ [ -e "$file" ] || return
4332+
4333+ local table_cache=""
4334+ if [ "$( get_var table_open_cache "${file}" )" ]; then
4335+ table_cache="$(get_var table_open_cache "${file}")"
4336+ else
4337+ table_cache="$(get_var table_cache "${file}")"
4338+ fi
4339+ echo ${table_cache:-0}
4340+}
4341+
4342+get_plugin_status () {
4343+ local file="$1"
4344+ local plugin="$2"
4345+
4346+ local status="$(grep -w "$plugin" "$file" | awk '{ print $2 }')"
4347+
4348+ echo ${status:-"Not found"}
4349+}
4350+
4351+
4352+_NO_FALSE_NEGATIVES=""
4353 parse_mysqld_instances () {
4354- local file=$1
4355+ local file="$1"
4356+ local variables_file="$2"
4357+
4358 local socket=${socket:-""}
4359 local port=${port:-""}
4360- local datadir=${datadir:-""}
4361- echo " Port Data Directory Socket"
4362- echo " ===== ========================== ======"
4363- $AP_GREP '/mysqld ' $file | while read line; do
4364+ local datadir="${datadir:-""}"
4365+
4366+ [ -e "$file" ] || return
4367+
4368+ echo " Port Data Directory Nice OOM Socket"
4369+ echo " ===== ========================== ==== === ======"
4370+
4371+ grep '/mysqld ' "$file" | while read line; do
4372+ local pid=$(echo "$line" | awk '{print $1;}')
4373 for word in ${line}; do
4374- # Some grep doesn't have -o, so I have to pull out the words I want by
4375- # looking at each word
4376- if echo "${word}" | $AP_GREP -- "--socket=" > /dev/null; then
4377+ if echo "${word}" | grep -- "--socket=" > /dev/null; then
4378 socket="$(echo "${word}" | cut -d= -f2)"
4379 fi
4380- if echo "${word}" | $AP_GREP -- "--port=" > /dev/null; then
4381+ if echo "${word}" | grep -- "--port=" > /dev/null; then
4382 port="$(echo "${word}" | cut -d= -f2)"
4383 fi
4384- if echo "${word}" | $AP_GREP -- "--datadir=" > /dev/null; then
4385+ if echo "${word}" | grep -- "--datadir=" > /dev/null; then
4386 datadir="$(echo "${word}" | cut -d= -f2)"
4387 fi
4388 done
4389- printf " %5s %-26s %s\n" "${port}" "${datadir}" "${socket}"
4390+ local nice="$(get_var "internal::nice_of_$pid" "$variables_file")"
4391+ local oom="$(get_var "internal::oom_of_$pid" "$variables_file")"
4392+ if [ -n "${_NO_FALSE_NEGATIVES}" ]; then
4393+ nice="?"
4394+ oom="?"
4395+ fi
4396+ printf " %5s %-26s %-4s %-3s %s\n" "${port}" "${datadir}" "${nice:-"?"}" "${oom:-"?"}" "${socket}"
4397 done
4398 }
4399
4400-# Tries to find the my.cnf file by examining 'ps' output, which should be in
4401-# $TMPDIR/percona-toolkit. You have to specify the port for the instance you are
4402-# interested in, in case there are multiple instances.
4403-find_my_cnf_file() {
4404- local file=$1
4405- local port=${2:-""}
4406- if test -n "$port" && $AP_GREP -- "/mysqld.*--port=$port" $file >/dev/null 2>&1 ; then
4407- $AP_GREP -- "/mysqld.*--port=$port" $file \
4408- | $AP_AWK 'BEGIN{RS=" "; FS="=";} $1 ~ /--defaults-file/ { print $2; }' \
4409- | head -n1
4410- else
4411- $AP_GREP '/mysqld' $file \
4412- | $AP_AWK 'BEGIN{RS=" "; FS="=";} $1 ~ /--defaults-file/ { print $2; }' \
4413- | head -n1
4414- fi
4415-}
4416-
4417-# Gets the MySQL system time. Uses input from $TMPDIR/percona-toolkit-mysql-variables.
4418 get_mysql_timezone () {
4419- tz="$(get_var time_zone)"
4420+ local file="$1"
4421+
4422+ [ -e "$file" ] || return
4423+
4424+ local tz="$(get_var time_zone "${file}")"
4425 if [ "${tz}" = "SYSTEM" ]; then
4426- tz="$(get_var system_time_zone)"
4427+ tz="$(get_var system_time_zone "${file}")"
4428 fi
4429 echo "${tz}"
4430 }
4431
4432-# Gets the MySQL system version. Uses input from $TMPDIR/percona-toolkit-mysql-variables.
4433 get_mysql_version () {
4434- name_val Version "$(get_var version) $(get_var version_comment)"
4435- name_val "Built On" "$(get_var version_compile_os) $(get_var version_compile_machine)"
4436+ local file="$1"
4437+
4438+ name_val Version "$(get_var version "${file}") $(get_var version_comment "${file}")"
4439+ name_val "Built On" "$(get_var version_compile_os "${file}") $(get_var version_compile_machine "${file}")"
4440 }
4441
4442-# Gets the system start and uptime in human readable format. Last restart date
4443-# should be in $TMPDIR/percona-toolkit.
4444 get_mysql_uptime () {
4445- local file=$1
4446- restart="$(cat $file)"
4447- uptime="$(get_stat Uptime)"
4448+ local uptime="$1"
4449+ local restart="$2"
4450 uptime="$(secs_to_time ${uptime})"
4451 echo "${restart} (up ${uptime})"
4452 }
4453
4454-# Summarizes the output of SHOW MASTER LOGS, which is in $TMPDIR/percona-toolkit
4455 summarize_binlogs () {
4456- local file=$1
4457- name_val "Binlogs" $(wc -l $file)
4458- name_val "Zero-Sized" $($AP_GREP -c '\<0$' $file)
4459- size=$($AP_AWK '{t += $2} END{printf "%0.f\n", t}' $file)
4460+ local file="$1"
4461+
4462+ [ -e "$file" ] || return
4463+
4464+ local size="$(awk '{t += $2} END{printf "%0.f\n", t}' "$file")"
4465+ name_val "Binlogs" $(wc -l "$file")
4466+ name_val "Zero-Sized" $(grep -c '\<0$' "$file")
4467 name_val "Total Size" $(shorten ${size} 1)
4468 }
4469
4470-# Print out binlog_do_db and binlog_ignore_db
4471+format_users () {
4472+ local file="$1"
4473+ [ -e "$file" ] || return
4474+ awk '{printf "%d users, %d anon, %d w/o pw, %d old pw\n", $1, $2, $3, $4}' "${file}"
4475+}
4476+
4477 format_binlog_filters () {
4478- local file=$1
4479- name_val "binlog_do_db" $(cut -f3 $file)
4480- name_val "binlog_ignore_db" $(cut -f4 $file)
4481+ local file="$1"
4482+ [ -e "$file" ] || return
4483+ name_val "binlog_do_db" "$(cut -f3 "$file")"
4484+ name_val "binlog_ignore_db" "$(cut -f4 "$file")"
4485 }
4486
4487-# Takes as input a file that has two samples of SHOW STATUS, columnized next to
4488-# each other. These should be in $TMPDIR/percona-toolkit. Outputs fuzzy-ed numbers:
4489-# absolute, all-time per second, and per-second over the interval between the
4490-# samples. Omits any rows that are all zeroes.
4491 format_status_variables () {
4492- local file=$1
4493- # First, figure out the intervals.
4494- utime1=$($AP_AWK '/Uptime /{print $2}' $file);
4495- utime2=$($AP_AWK '/Uptime /{print $3}' $file);
4496- ${AP_AWK} "
4497+ local file="$1"
4498+ [ -e "$file" ] || return
4499+
4500+ utime1="$(awk '/Uptime /{print $2}' "$file")";
4501+ utime2="$(awk '/Uptime /{print $3}' "$file")";
4502+ awk "
4503 BEGIN {
4504 utime1 = ${utime1};
4505 utime2 = ${utime2};
4506@@ -367,28 +1164,22 @@
4507 printf(format, \$1, perday, persec, nowsec);
4508 }
4509 }
4510- }" $file
4511+ }" "$file"
4512 }
4513
4514-# Slices the processlist a bunch of different ways. The processlist should be
4515-# created with the \G flag so it's vertical.
4516-# The parsing is a bit awkward because different
4517-# versions of awk have limitations like "too many fields on line xyz". So we
4518-# use 'cut' to shorten the lines. We count all things into temporary variables
4519-# for each process in the processlist, and when we hit the Info: line which
4520-# ought to be the last line in the process, we decide what to do with the temp
4521-# variables. If we're summarizing Command, we count everything; otherwise, only
4522-# non-Sleep processes get counted towards the sum and max of Time.
4523 summarize_processlist () {
4524- local file=$1
4525+ local file="$1"
4526+
4527+ [ -e "$file" ] || return
4528+
4529 for param in Command User Host db State; do
4530 echo
4531 printf ' %-30s %8s %7s %9s %9s\n' \
4532 "${param}" "COUNT(*)" Working "SUM(Time)" "MAX(Time)"
4533 echo " ------------------------------" \
4534 "-------- ------- --------- ---------"
4535- cut -c1-80 $file \
4536- | $AP_AWK "
4537+ cut -c1-80 "$file" \
4538+ | awk "
4539 \$1 == \"${param}:\" {
4540 p = substr(\$0, index(\$0, \":\") + 2);
4541 if ( index(p, \":\") > 0 ) {
4542@@ -428,22 +1219,21 @@
4543 echo
4544 }
4545
4546-# Pretty-prints the my.cnf file, which should be in $TMPDIR/percona-toolkit. It's super
4547-# annoying, but some *modern* versions of awk don't support POSIX character
4548-# sets in regular expressions, like [[:space:]] (looking at you, Debian). So
4549-# the below patterns contain [<space><tab>] and must remain that way.
4550 pretty_print_cnf_file () {
4551- local file=$1
4552- $AP_AWK '
4553+ local file="$1"
4554+
4555+ [ -e "$file" ] || return
4556+
4557+ awk '
4558 BEGIN {
4559 FS="="
4560 }
4561- /^ *[a-zA-Z[]/ {
4562- if ($2) {
4563- gsub(/^[ ]*/, "", $1);
4564- gsub(/^[ ]*/, "", $2);
4565- gsub(/[ ]*$/, "", $1);
4566- gsub(/[ ]*$/, "", $2);
4567+ /^[ \t]*[a-zA-Z[]/ {
4568+ if (length($2)) {
4569+ gsub(/^[ \t]*/, "", $1);
4570+ gsub(/^[ \t]*/, "", $2);
4571+ gsub(/[ \t]*$/, "", $1);
4572+ gsub(/[ \t]*$/, "", $2);
4573 printf("%-35s = %s\n", $1, $2);
4574 }
4575 else if ( $0 ~ /\[/ ) {
4576@@ -453,11 +1243,12 @@
4577 else {
4578 print $1;
4579 }
4580- }' $file
4581+ }' "$file"
4582 }
4583
4584 find_checkpoint_age() {
4585- $AP_AWK '
4586+ local file="$1"
4587+ awk '
4588 /Log sequence number/{
4589 if ( $5 ) {
4590 lsn = $5 + ($4 * 4294967296);
4591@@ -474,11 +1265,15 @@
4592 print lsn - $4;
4593 }
4594 }
4595- ' "$@"
4596+ ' "$file"
4597 }
4598
4599 find_pending_io_reads() {
4600- $AP_AWK '
4601+ local file="$1"
4602+
4603+ [ -e "$file" ] || return
4604+
4605+ awk '
4606 /Pending normal aio reads/ {
4607 normal_aio_reads = substr($5, 1, index($5, ","));
4608 }
4609@@ -495,11 +1290,15 @@
4610 printf "%d buf pool reads, %d normal AIO", reads, normal_aio_reads;
4611 printf ", %d ibuf AIO, %d preads", ibuf_aio_reads, preads;
4612 }
4613- ' "${1}"
4614+ ' "${file}"
4615 }
4616
4617 find_pending_io_writes() {
4618- $AP_AWK '
4619+ local file="$1"
4620+
4621+ [ -e "$file" ] || return
4622+
4623+ awk '
4624 /aio writes/ {
4625 aio_writes = substr($NF, 1, index($NF, ","));
4626 }
4627@@ -522,11 +1321,15 @@
4628 END {
4629 printf "%d buf pool (%d LRU, %d flush list, %d page); %d AIO, %d sync, %d log IO (%d log, %d chkp); %d pwrites", lru + flush_list + single_page, lru, flush_list, single_page, aio_writes, sync_ios, log_ios, log_writes, chkp_writes, pwrites;
4630 }
4631- ' "${1}"
4632+ ' "${file}"
4633 }
4634
4635 find_pending_io_flushes() {
4636- $AP_AWK '
4637+ local file="$1"
4638+
4639+ [ -e "$file" ] || return
4640+
4641+ awk '
4642 /Pending flushes/ {
4643 log_flushes = substr($5, 1, index($5, ";"));
4644 buf_pool = $NF;
4645@@ -534,13 +1337,17 @@
4646 END {
4647 printf "%d buf pool, %d log", buf_pool, log_flushes;
4648 }
4649- ' "${1}"
4650+ ' "${file}"
4651 }
4652
4653 summarize_undo_log_entries() {
4654- $AP_GREP 'undo log entries' "$1" \
4655- | $AP_SED -e 's/^.*undo log entries \([0-9]*\)/\1/' \
4656- | $AP_AWK '
4657+ local file="$1"
4658+
4659+ [ -e "$file" ] || return
4660+
4661+ grep 'undo log entries' "${file}" \
4662+ | sed -e 's/^.*undo log entries \([0-9]*\)/\1/' \
4663+ | awk '
4664 {
4665 count++;
4666 sum += $1;
4667@@ -554,7 +1361,11 @@
4668 }
4669
4670 find_max_trx_time() {
4671- $AP_AWK '
4672+ local file="$1"
4673+
4674+ [ -e "$file" ] || return
4675+
4676+ awk '
4677 BEGIN {
4678 max = 0;
4679 }
4680@@ -570,59 +1381,69 @@
4681 }
4682 END {
4683 print max;
4684- }' "$@"
4685-}
4686-
4687-# Summarizes various things about InnoDB status that are not easy to see by eye.
4688+ }' "${file}"
4689+}
4690+
4691+find_transation_states () {
4692+ local file="$1"
4693+ local tmpfile="$TMPDIR/find_transation_states.tmp"
4694+
4695+ [ -e "$file" ] || return
4696+
4697+ awk -F, '/^---TRANSACTION/{print $2}' "${file}" \
4698+ | sed -e 's/ [0-9]* sec.*//' \
4699+ | sort \
4700+ | uniq -c > "${tmpfile}"
4701+ group_concat "${tmpfile}"
4702+}
4703+
4704 format_innodb_status () {
4705 local file=$1
4706- name_val "Checkpoint Age" $(shorten $(find_checkpoint_age "${file}"))
4707+
4708+ [ -e "$file" ] || return
4709+
4710+ name_val "Checkpoint Age" "$(shorten $(find_checkpoint_age "${file}") 0)"
4711 name_val "InnoDB Queue" "$(awk '/queries inside/{print}' "${file}")"
4712 name_val "Oldest Transaction" "$(find_max_trx_time "${file}") Seconds";
4713- name_val "History List Len" $(awk '/History list length/{print $4}' "${file}")
4714- name_val "Read Views" $(awk '/read views open inside/{print $1}' "${file}")
4715+ name_val "History List Len" "$(awk '/History list length/{print $4}' "${file}")"
4716+ name_val "Read Views" "$(awk '/read views open inside/{print $1}' "${file}")"
4717 name_val "Undo Log Entries" "$(summarize_undo_log_entries "${file}")"
4718 name_val "Pending I/O Reads" "$(find_pending_io_reads "${file}")"
4719 name_val "Pending I/O Writes" "$(find_pending_io_writes "${file}")"
4720 name_val "Pending I/O Flushes" "$(find_pending_io_flushes "${file}")"
4721- $AP_AWK -F, '/^---TRANSACTION/{print $2}' "${file}" \
4722- | $AP_SED -e 's/ [0-9]* sec.*//' | sort | uniq -c > $TMPDIR/percona-toolkit2
4723- name_val "Transaction States" "$(group_concat $TMPDIR/percona-toolkit2)"
4724- if $AP_GREP 'TABLE LOCK table' "${file}" >/dev/null ; then
4725+ name_val "Transaction States" "$(find_transation_states "${file}" )"
4726+ if grep 'TABLE LOCK table' "${file}" >/dev/null ; then
4727 echo "Tables Locked"
4728- $AP_AWK '/^TABLE LOCK table/{print $4}' "${file}" \
4729+ awk '/^TABLE LOCK table/{print $4}' "${file}" \
4730 | sort | uniq -c | sort -rn
4731 fi
4732- if $AP_GREP 'has waited at' "${file}" > /dev/null ; then
4733+ if grep 'has waited at' "${file}" > /dev/null ; then
4734 echo "Semaphore Waits"
4735- $AP_GREP 'has waited at' "${file}" | cut -d' ' -f6-8 \
4736+ grep 'has waited at' "${file}" | cut -d' ' -f6-8 \
4737 | sort | uniq -c | sort -rn
4738 fi
4739- if $AP_GREP 'reserved it in mode' "${file}" > /dev/null; then
4740+ if grep 'reserved it in mode' "${file}" > /dev/null; then
4741 echo "Semaphore Holders"
4742- $AP_AWK '/has reserved it in mode/{
4743+ awk '/has reserved it in mode/{
4744 print substr($0, 1 + index($0, "("), index($0, ")") - index($0, "(") - 1);
4745 }' "${file}" | sort | uniq -c | sort -rn
4746 fi
4747- if $AP_GREP -e 'Mutex at' -e 'lock on' "${file}" >/dev/null 2>&1; then
4748+ if grep -e 'Mutex at' -e 'lock on' "${file}" >/dev/null 2>&1; then
4749 echo "Mutexes/Locks Waited For"
4750- $AP_GREP -e 'Mutex at' -e 'lock on' "${file}" | $AP_SED -e 's/^[XS]-//' -e 's/,.*$//' \
4751+ grep -e 'Mutex at' -e 'lock on' "${file}" | sed -e 's/^[XS]-//' -e 's/,.*$//' \
4752 | sort | uniq -c | sort -rn
4753 fi
4754 }
4755
4756-# Summarizes per-database statistics for a bunch of different things: count of
4757-# tables, views, etc. $1 is the file name. $2 is the database name; if none,
4758-# then there should be multiple databases.
4759 format_overall_db_stats () {
4760- local file=$1
4761+ local file="$1"
4762+ local tmpfile="$TMPDIR/format_overall_db_stats.tmp"
4763+
4764+ [ -e "$file" ] || return
4765+
4766 echo
4767- # We keep counts of everything in an associative array keyed by db name, and
4768- # what it is. The num_dbs counter is to ensure sort order is consistent when
4769- # we run the awk commands following this one.
4770- $AP_AWK '
4771+ awk '
4772 BEGIN {
4773- # In case there is no USE statement in the file.
4774 db = "{chosen}";
4775 num_dbs = 0;
4776 }
4777@@ -634,7 +1455,6 @@
4778 }
4779 }
4780 /^CREATE TABLE/ {
4781- # Handle single-DB dumps, where there is no USE statement.
4782 if (num_dbs == 0) {
4783 num_dbs = 1;
4784 db_seen[db] = 1;
4785@@ -674,15 +1494,13 @@
4786 printf fmt, db, counts[db ",tables"], counts[db ",views"], counts[db ",sps"], counts[db ",trg"], counts[db ",func"], counts[db ",fk"], counts[db ",partn"];
4787 }
4788 }
4789- ' $file > $TMPDIR/percona-toolkit
4790- head -n2 $TMPDIR/percona-toolkit
4791- tail -n +3 $TMPDIR/percona-toolkit | sort
4792+ ' "$file" > "$tmpfile"
4793+ head -n2 "$tmpfile"
4794+ tail -n +3 "$tmpfile" | sort
4795
4796 echo
4797- # Now do the summary of engines per DB
4798- $AP_AWK '
4799+ awk '
4800 BEGIN {
4801- # In case there is no USE statement in the file.
4802 db = "{chosen}";
4803 num_dbs = 0;
4804 num_engines = 0;
4805@@ -695,7 +1513,6 @@
4806 }
4807 }
4808 /^\) ENGINE=/ {
4809- # Handle single-DB dumps, where there is no USE statement.
4810 if (num_dbs == 0) {
4811 num_dbs = 1;
4812 db_seen[db] = 1;
4813@@ -734,16 +1551,13 @@
4814 print "";
4815 }
4816 }
4817- ' $file > $TMPDIR/percona-toolkit
4818- head -n1 $TMPDIR/percona-toolkit
4819- tail -n +2 $TMPDIR/percona-toolkit | sort
4820+ ' "$file" > "$tmpfile"
4821+ head -n1 "$tmpfile"
4822+ tail -n +2 "$tmpfile" | sort
4823
4824 echo
4825- # Now do the summary of index types per DB. Careful -- index is a reserved
4826- # word in awk.
4827- $AP_AWK '
4828+ awk '
4829 BEGIN {
4830- # In case there is no USE statement in the file.
4831 db = "{chosen}";
4832 num_dbs = 0;
4833 num_idxes = 0;
4834@@ -756,7 +1570,6 @@
4835 }
4836 }
4837 /KEY/ {
4838- # Handle single-DB dumps, where there is no USE statement.
4839 if (num_dbs == 0) {
4840 num_dbs = 1;
4841 db_seen[db] = 1;
4842@@ -807,15 +1620,13 @@
4843 print "";
4844 }
4845 }
4846- ' $file > $TMPDIR/percona-toolkit
4847- head -n1 $TMPDIR/percona-toolkit
4848- tail -n +2 $TMPDIR/percona-toolkit | sort
4849+ ' "$file" > "$tmpfile"
4850+ head -n1 "$tmpfile"
4851+ tail -n +2 "$tmpfile" | sort
4852
4853 echo
4854- # Now do the summary of datatypes per DB
4855- $AP_AWK '
4856+ awk '
4857 BEGIN {
4858- # In case there is no USE statement in the file.
4859 db = "{chosen}";
4860 num_dbs = 0;
4861 num_types = 0;
4862@@ -828,7 +1639,6 @@
4863 }
4864 }
4865 /^ `/ {
4866- # Handle single-DB dumps, where there is no USE statement.
4867 if (num_dbs == 0) {
4868 num_dbs = 1;
4869 db_seen[db] = 1;
4870@@ -898,106 +1708,195 @@
4871 print "";
4872 }
4873 }
4874- ' $file > $TMPDIR/percona-toolkit
4875- hdr=$($AP_GREP -n Database $TMPDIR/percona-toolkit | cut -d: -f1);
4876- head -n${hdr} $TMPDIR/percona-toolkit
4877- tail -n +$((${hdr} + 1)) $TMPDIR/percona-toolkit | sort
4878+ ' "$file" > "$tmpfile"
4879+ local hdr=$(grep -n Database "$tmpfile" | cut -d: -f1);
4880+ head -n${hdr} "$tmpfile"
4881+ tail -n +$((${hdr} + 1)) "$tmpfile" | sort
4882 echo
4883 }
4884
4885-# ##############################################################################
4886-# The main() function is called at the end of the script. This makes it
4887-# testable. Major bits of parsing are separated into functions for testability.
4888-# ##############################################################################
4889-main() {
4890-
4891- # Begin by setting the $PATH to include some common locations that are not
4892- # always in the $PATH, including the "sbin" locations. On SunOS systems,
4893- # prefix the path with the location of more sophisticated utilities.
4894- export PATH="${PATH}:/usr/local/bin:/usr/bin:/bin:/usr/libexec"
4895- export PATH="${PATH}:/usr/mysql/bin/:/usr/local/sbin:/usr/sbin:/sbin"
4896- export PATH="/usr/gnu/bin/:/usr/xpg4/bin/:${PATH}"
4897-
4898- # Set up temporary files.
4899- mk_tmpdir
4900- temp_files "rm"
4901- temp_files "touch"
4902-
4903- # ########################################################################
4904- # Header for the whole thing, table of discovered instances
4905- # ########################################################################
4906- section Percona_Toolkit_MySQL_Summary_Report
4907- name_val "System time" "`date -u +'%F %T UTC'` (local TZ: `date +'%Z %z'`)"
4908- section Instances
4909- ps auxww 2>/dev/null | $AP_GREP mysqld > $TMPDIR/percona-toolkit
4910- parse_mysqld_instances $TMPDIR/percona-toolkit
4911-
4912- # ########################################################################
4913- # Fetch some basic info so we can start
4914- # ########################################################################
4915- mysql "$@" -ss -e 'SELECT CURRENT_USER()' > $TMPDIR/percona-toolkit
4916- if [ "$?" != "0" ]; then
4917- echo "Cannot connect to mysql, please specify command-line options."
4918- temp_files "rm"
4919- rm_tmpdir
4920- exit 1
4921- fi
4922- user="$(cat $TMPDIR/percona-toolkit)";
4923- mysql "$@" -ss -e 'SHOW /*!40100 GLOBAL*/ VARIABLES' > $TMPDIR/percona-toolkit-mysql-variables
4924- mysql "$@" -ss -e 'SHOW /*!50000 GLOBAL*/ STATUS' > $TMPDIR/percona-toolkit-mysql-status
4925- mysql "$@" -ss -e 'SHOW DATABASES' > $TMPDIR/percona-toolkit-mysql-databases 2>/dev/null
4926- mysql "$@" -ssE -e 'SHOW SLAVE STATUS' > $TMPDIR/percona-toolkit-mysql-slave 2>/dev/null
4927- mysql "$@" -ssE -e 'SHOW /*!50000 ENGINE*/ INNODB STATUS' > $TMPDIR/percona-toolkit-innodb-status 2>/dev/null
4928- mysql "$@" -ssE -e 'SHOW FULL PROCESSLIST' > $TMPDIR/percona-toolkit-mysql-processlist 2>/dev/null
4929- now="$(mysql "$@" -ss -e 'SELECT NOW()')"
4930- port="$(get_var port)"
4931-
4932- # ########################################################################
4933- # General date, hostname, etc
4934- # ########################################################################
4935- section "Report_On_Port_${port}"
4936- name_val User "${user}"
4937- name_val Time "${now} ($(get_mysql_timezone))"
4938- name_val Hostname "$(get_var hostname)"
4939- get_mysql_version
4940-
4941- uptime="$(get_stat Uptime)"
4942- mysql "$@" -ss -e "SELECT LEFT(NOW() - INTERVAL ${uptime} SECOND, 16)" \
4943- > $TMPDIR/percona-toolkit
4944- name_val Started "$(get_mysql_uptime $TMPDIR/percona-toolkit)"
4945-
4946- name_val Databases "$($AP_GREP -c . $TMPDIR/percona-toolkit-mysql-databases)"
4947- name_val Datadir "$(get_var datadir)"
4948- procs="$(get_stat Threads_connected)"
4949- procr="$(get_stat Threads_running)"
4950- name_val Processes "$(fuzz ${procs}) connected, $(fuzz ${procr}) running"
4951- if [ -s $TMPDIR/percona-toolkit-mysql-slave ]; then slave=""; else slave="not "; fi
4952- slavecount=$($AP_GREP -c 'Binlog Dump' $TMPDIR/percona-toolkit-mysql-processlist)
4953- name_val Replication "Is ${slave}a slave, has ${slavecount} slaves connected"
4954-
4955- # TODO move this into a section with other files: error log, slow log and
4956- # show the sizes
4957- pid_file="$(get_var pid_file)"
4958- [ -e "${pid_file}" ] && PID_EXISTS="(exists)"
4959- name_val Pidfile "${pid_file} ${PID_EXISTS:-(does not exist)}"
4960-
4961- # ########################################################################
4962- # Processlist, sliced several different ways
4963- # ########################################################################
4964- section Processlist
4965- summarize_processlist $TMPDIR/percona-toolkit-mysql-processlist
4966-
4967- # ########################################################################
4968- # Queries and query plans
4969- # ########################################################################
4970- section "Status_Counters_(Wait_10_Seconds)"
4971- sleep 10
4972- # TODO: gather this data in the same format as normal: stats, TS line
4973- mysql "$@" -ss -e 'SHOW /*!50000 GLOBAL*/ STATUS' \
4974- | join $TMPDIR/percona-toolkit-mysql-status - > $TMPDIR/percona-toolkit
4975- # Make a file with a list of things we want to omit because they aren't
4976- # counters, they are gauges (in RRDTool terminology). Gauges are shown
4977- # elsewhere in the output.
4978+section_percona_server_features () {
4979+ local file="$1"
4980+
4981+ [ -e "$file" ] || return
4982+
4983+ name_val "Table & Index Stats" \
4984+ "$(feat_on "$file" userstat_running)"
4985+ name_val "Multiple I/O Threads" \
4986+ "$(feat_on "$file" innodb_read_io_threads gt 1)"
4987+ name_val "Corruption Resilient" \
4988+ "$(feat_on "$file" innodb_pass_corrupt_table)"
4989+ name_val "Durable Replication" \
4990+ "$(feat_on "$file" innodb_overwrite_relay_log_info)"
4991+ name_val "Import InnoDB Tables" \
4992+ "$(feat_on "$file" innodb_expand_import)"
4993+ name_val "Fast Server Restarts" \
4994+ "$(feat_on "$file" innodb_auto_lru_dump)"
4995+ name_val "Enhanced Logging" \
4996+ "$(feat_on "$file" log_slow_verbosity ne microtime)"
4997+ name_val "Replica Perf Logging" \
4998+ "$(feat_on "$file" log_slow_slave_statements)"
4999+ name_val "Response Time Hist." \
5000+ "$(feat_on "$file" enable_query_response_time_stats)"
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches