Merge lp:~percona-toolkit-dev/percona-toolkit/fix-stalk-cycles-bug-994947 into lp:percona-toolkit/2.0

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

Subscribers

People subscribed via source and target branches