Merge lp:~percona-toolkit-dev/percona-toolkit/fix-bug-903379 into lp:percona-toolkit/2.0

Proposed by Brian Fraser
Status: Merged
Merged at revision: 241
Proposed branch: lp:~percona-toolkit-dev/percona-toolkit/fix-bug-903379
Merge into: lp:percona-toolkit/2.0
Diff against target: 68195 lines (+53670/-8428) (has conflicts)
361 files modified
Changelog (+21/-0)
MANIFEST (+2/-0)
Makefile.PL (+1/-1)
bin/pt-align (+1/-1)
bin/pt-archiver (+33/-21)
bin/pt-config-diff (+1/-1)
bin/pt-deadlock-logger (+1/-1)
bin/pt-diskstats (+1/-1)
bin/pt-duplicate-key-checker (+171/-92)
bin/pt-fifo-split (+1/-1)
bin/pt-find (+1/-1)
bin/pt-fingerprint (+2143/-0)
bin/pt-fk-error-logger (+1/-1)
bin/pt-heartbeat (+11/-2)
bin/pt-index-usage (+170/-92)
bin/pt-ioprofile (+1/-1)
bin/pt-kill (+1/-1)
bin/pt-log-player (+1/-1)
bin/pt-mext (+1/-1)
bin/pt-mysql-summary (+2076/-679)
bin/pt-online-schema-change (+4761/-1242)
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 (+11/-2)
bin/pt-slave-restart (+11/-2)
bin/pt-stalk (+1/-1)
bin/pt-summary (+2041/-793)
bin/pt-table-checksum (+232/-82)
bin/pt-table-sync (+634/-450)
bin/pt-table-usage (+7226/-0)
bin/pt-tcp-model (+1/-1)
bin/pt-trend (+1/-1)
bin/pt-upgrade (+1/-1)
bin/pt-variable-advisor (+1/-1)
bin/pt-visual-explain (+1/-1)
config/deb/changelog (+23/-0)
config/sphinx-build/conf.py (+2/-2)
docs/percona-toolkit.pod (+12/-4)
docs/release_notes.rst (+48/-0)
lib/CleanupTask.pm (+7/-2)
lib/Cxn.pm (+1/-1)
lib/MasterSlave.pm (+10/-0)
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/TableChecksum.pm (+1/-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/MasterSlave.t (+91/-1)
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/ro-checksum-user.sql (+3/-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-archiver/file.t (+42/-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 (+587/-218)
t/pt-online-schema-change/bugs.t (+73/-0)
t/pt-online-schema-change/check_tables.t (+0/-126)
t/pt-online-schema-change/option_sanity.t (+34/-13)
t/pt-online-schema-change/privs.t (+86/-0)
t/pt-online-schema-change/samples/basic_no_fks.data (+500/-500)
t/pt-online-schema-change/samples/basic_no_fks.sql (+30/-0)
t/pt-online-schema-change/samples/basic_with_fks.sql (+56/-0)
t/pt-online-schema-change/samples/fk_tables_schema.sql (+0/-31)
t/pt-online-schema-change/samples/osc-user.sql (+3/-0)
t/pt-online-schema-change/samples/pk-bug-994002.sql (+29/-0)
t/pt-online-schema-change/samples/query_table.pl (+8/-5)
t/pt-online-schema-change/samples/small_table.sql (+0/-27)
t/pt-online-schema-change/sanity_checks.t (+113/-0)
t/pt-online-schema-change/skip_innodb.t (+60/-0)
t/pt-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-slave-restart/pt-slave-restart.t (+1/-1)
t/pt-stalk/pt-stalk.t (+1/-1)
t/pt-summary/format_vmstat.sh (+0/-37)
t/pt-summary/parse_arcconf.sh (+0/-176)
t/pt-summary/parse_dmidecode_mem_devices.sh (+0/-104)
t/pt-summary/parse_ethernet_controller_lspci.sh (+0/-11)
t/pt-summary/parse_fdisk.sh (+0/-16)
t/pt-summary/parse_filesystems.sh (+0/-52)
t/pt-summary/parse_free_minus_b.sh (+0/-67)
t/pt-summary/parse_fusionmpt_lsiutil.sh (+0/-50)
t/pt-summary/parse_hpacucli.sh (+0/-26)
t/pt-summary/parse_ip_s_link.sh (+0/-28)
t/pt-summary/parse_lsi_megaraid.sh (+0/-696)
t/pt-summary/parse_netstat.sh (+0/-49)
t/pt-summary/parse_proc_cpuinfo.sh (+0/-74)
t/pt-summary/parse_raid_controller_dmesg.sh (+0/-32)
t/pt-summary/parse_raid_controller_lspci.sh (+0/-39)
t/pt-summary/parse_virtualization_dmesg.sh (+0/-10)
t/pt-summary/pt-summary.t (+12/-2)
t/pt-summary/samples/BSD/freebsd_001/mounted_fs (+7/-0)
t/pt-summary/samples/BSD/freebsd_001/notable_procs (+2/-0)
t/pt-summary/samples/BSD/freebsd_001/processes (+10/-0)
t/pt-summary/samples/BSD/freebsd_001/summary (+10/-0)
t/pt-summary/samples/BSD/freebsd_001/sysctl (+1481/-0)
t/pt-summary/samples/BSD/freebsd_001/uptime (+1/-0)
t/pt-summary/samples/BSD/freebsd_001/vmstat (+7/-0)
t/pt-summary/samples/BSD/netbsd_001/mounted_fs (+5/-0)
t/pt-summary/samples/BSD/netbsd_001/notable_procs (+2/-0)
t/pt-summary/samples/BSD/netbsd_001/proc_cpuinfo_copy (+14/-0)
t/pt-summary/samples/BSD/netbsd_001/processes (+10/-0)
t/pt-summary/samples/BSD/netbsd_001/summary (+10/-0)
t/pt-summary/samples/BSD/netbsd_001/swapctl (+1/-0)
t/pt-summary/samples/BSD/netbsd_001/sysctl (+511/-0)
t/pt-summary/samples/BSD/netbsd_001/uptime (+1/-0)
t/pt-summary/samples/BSD/netbsd_001/vmstat (+7/-0)
t/pt-summary/samples/BSD/openbsd_001/mounted_fs (+4/-0)
t/pt-summary/samples/BSD/openbsd_001/notable_procs (+2/-0)
t/pt-summary/samples/BSD/openbsd_001/processes (+10/-0)
t/pt-summary/samples/BSD/openbsd_001/summary (+10/-0)
t/pt-summary/samples/BSD/openbsd_001/swapctl (+1/-0)
t/pt-summary/samples/BSD/openbsd_001/sysctl (+423/-0)
t/pt-summary/samples/BSD/openbsd_001/uptime (+1/-0)
t/pt-summary/samples/BSD/openbsd_001/vmstat (+7/-0)
t/pt-summary/samples/Linux/001/dmesg_file (+786/-0)
t/pt-summary/samples/Linux/001/dmidecode (+412/-0)
t/pt-summary/samples/Linux/001/ip (+24/-0)
t/pt-summary/samples/Linux/001/lspci_file (+17/-0)
t/pt-summary/samples/Linux/001/lvs (+1/-0)
t/pt-summary/samples/Linux/001/memory (+50/-0)
t/pt-summary/samples/Linux/001/mounted_fs (+12/-0)
t/pt-summary/samples/Linux/001/netstat (+6/-0)
t/pt-summary/samples/Linux/001/notable_procs (+5/-0)
t/pt-summary/samples/Linux/001/partitioning (+30/-0)
t/pt-summary/samples/Linux/001/proc_cpuinfo_copy (+58/-0)
t/pt-summary/samples/Linux/001/proc_cpuinfo_copy.unq (+1/-0)
t/pt-summary/samples/Linux/001/processes (+10/-0)
t/pt-summary/samples/Linux/001/summary (+23/-0)
t/pt-summary/samples/Linux/001/sysctl (+905/-0)
t/pt-summary/samples/Linux/001/uptime (+1/-0)
t/pt-summary/samples/Linux/001/vmstat (+7/-0)
t/pt-summary/samples/Linux/002/dmesg_file (+283/-0)
t/pt-summary/samples/Linux/002/memory (+34/-0)
t/pt-summary/samples/Linux/002/mounted_fs (+3/-0)
t/pt-summary/samples/Linux/002/netstat (+6/-0)
t/pt-summary/samples/Linux/002/notable_procs (+2/-0)
t/pt-summary/samples/Linux/002/partitioning (+9/-0)
t/pt-summary/samples/Linux/002/proc_cpuinfo_copy (+19/-0)
t/pt-summary/samples/Linux/002/processes (+10/-0)
t/pt-summary/samples/Linux/002/summary (+19/-0)
t/pt-summary/samples/Linux/002/uptime (+1/-0)
t/pt-summary/samples/Linux/002/vmstat (+7/-0)
t/pt-summary/samples/Linux/003/dmesg_file (+283/-0)
t/pt-summary/samples/Linux/003/memory (+34/-0)
t/pt-summary/samples/Linux/003/mounted_fs (+3/-0)
t/pt-summary/samples/Linux/003/netstat (+6/-0)
t/pt-summary/samples/Linux/003/notable_procs (+2/-0)
t/pt-summary/samples/Linux/003/partitioning (+1/-0)
t/pt-summary/samples/Linux/003/proc_cpuinfo_copy (+19/-0)
t/pt-summary/samples/Linux/003/processes (+10/-0)
t/pt-summary/samples/Linux/003/summary (+19/-0)
t/pt-summary/samples/Linux/003/uptime (+1/-0)
t/pt-summary/samples/Linux/003/vmstat (+7/-0)
t/pt-summary/samples/Linux/output_002.txt (+82/-0)
t/pt-summary/samples/Linux/output_003.txt (+79/-0)
t/pt-summary/samples/MegaCli64_AdpAllInfo_aALL001.txt (+227/-0)
t/pt-summary/samples/MegaCli64_LdPdInfo_aALL_886223 (+214/-0)
t/pt-summary/samples/arcconf-001.txt (+133/-0)
t/pt-summary/samples/arcconf-003_900285.txt (+228/-0)
t/pt-summary/samples/arcconf-004_917781.txt (+162/-0)
t/pt-summary/samples/dmesg-005.txt (+787/-0)
t/pt-summary/samples/dmesg-007.txt (+136/-0)
t/pt-summary/samples/hpaculi-001.txt (+11/-0)
t/pt-summary/samples/hpaculi-002.txt (+354/-0)
t/pt-summary/samples/hpaculi-003.txt (+11/-0)
t/pt-summary/samples/ip-s-link-003.txt (+24/-0)
t/pt-summary/samples/lspci-005.txt (+38/-0)
t/pt-summary/samples/netstat-002.txt (+1328/-0)
t/pt-summary/samples/proc_cpuinfo001.txt (+57/-0)
t/pt-summary/samples/proc_cpuinfo001.txt.unq (+1/-0)
t/pt-summary/samples/proc_cpuinfo002.txt (+57/-0)
t/pt-summary/samples/proc_cpuinfo002.txt.unq (+1/-0)
t/pt-table-checksum/bugs.t (+143/-0)
t/pt-table-checksum/privs.t (+98/-0)
t/pt-table-checksum/samples/empty-table-bug-987393.sql (+18/-0)
t/pt-table-checksum/samples/not-using-pk-bug.out (+20/-0)
t/pt-table-checksum/samples/not-using-pk-bug.sql (+20/-0)
t/pt-table-checksum/samples/undef-arrayref-bug-995274.sql (+18/-0)
t/pt-table-checksum/skip_innodb.t (+75/-0)
t/pt-table-sync/bugs.t (+156/-0)
t/pt-table-sync/filters.t (+74/-1)
t/pt-table-sync/issue_408.t (+1/-1)
t/pt-table-sync/samples/wrong-tbl-struct-bug-1003014.sql (+35/-0)
t/pt-table-usage/basics.t (+186/-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-summary
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.
Text conflict in t/pt-table-checksum/skip_innodb.t
Conflict adding file t/pt-table-sync/bugs.t.  Moved existing file to t/pt-table-sync/bugs.t.moved.
Conflict adding file t/pt-table-sync/samples/wrong-tbl-struct-bug-1003014.sql.  Moved existing file to t/pt-table-sync/samples/wrong-tbl-struct-bug-1003014.sql.moved.
To merge this branch: bzr merge lp:~percona-toolkit-dev/percona-toolkit/fix-bug-903379
Reviewer Review Type Date Requested Status
Brian Fraser (community) Approve
Daniel Nichter Approve
Review via email: mp+96358@code.launchpad.net

Description of the change

The fix itself is very simple, so I've probably overcomplicated things. Still, I think this approach has its advantages, since it doesn't punish users unlucky enough not to have Encode, but thorough enough to be using standardized charset names.

To post a comment you must log in.
209. By Daniel Nichter

Fix samples for MySQL 5.0.

210. By Daniel Nichter

Fix trailing blank lines in samples.

211. By Daniel Nichter

Set TZ to make tests work on other boxes.

212. By Daniel Nichter

Update Daemon in all tools (bug 944420).

213. By Daniel Nichter

Update Changelog for 2.0.4.

214. By Daniel Nichter

Fix quoting in util/build-packages.

215. By Daniel Nichter

Fix more quoting in util/build-packages.

216. By Daniel Nichter

Fix 'Bad substitution' error in util/build-packages.

217. By Daniel Nichter

Build percona-toolkit-2.0.4

218. By Daniel Nichter

Merge lp:~percona-toolkit-dev/percona-toolkit/doc-spelling-fixes-2.1-bug-896553.

219. By Daniel Nichter

Merge lp:~percona-toolkit-dev/percona-toolkit/update-relay-log-space-limit-docs-fix-bug-949154.

220. By Daniel Nichter

Merge fix-risks-docs-bug-949653.

221. By Daniel Nichter

Merge fix-quoting-bug-967451.

222. By Daniel Nichter

Merge fix-pt-upgrade-docs-bug-953461.

223. By Daniel Nichter

Merge fix-no-stalk-bug-955860.

224. By Daniel Nichter

Add pt-fingerprint.

225. By Daniel Nichter

Add pt-table-usage and update SQLParser.pm from Maatkit.

226. By Daniel Nichter

Merge pt-table-usage-docs.

227. By Daniel Nichter

Merge fix-table-status-bug-960513.

228. By Daniel Nichter

Merge pt-osc-2.1. Fix NibbleItertor.pm to work with OobNibbleIterator when resuming at oob boundaries (t/pt-table-checksum/resume.t was failing).

229. By Daniel Nichter

Merge fix-doc-bug-972399.

230. By Daniel Nichter

Merge fix-log-parser-writer-bug-963225.

231. By Daniel Nichter

Merge summary-tools-2.1.

232. By Daniel Nichter

Remove a bzr backup file.

233. By Daniel Nichter

Convert TableUsage.t from Maatkit to Percona Toolkit.

234. By Daniel Nichter

Add func to col ident struct to fix QueryAdvisorRules.t.

235. By Daniel Nichter

Fix SchemaIterator.t by updating and adding 5.0 samples.

236. By Daniel Nichter

Update modules in pt-query-advisor, replacing MySQLDump with TableParser.

237. By Daniel Nichter

Make pt-stalk.t a little more reliable.

238. By Daniel Nichter

Remove unused developer test file.

239. By Daniel Nichter

Remove useless var in SchemaIterator::_iterate_files() and update module in tools.

240. By Daniel Nichter

Update Changelog for 2.1.1.

241. By Daniel Nichter

Build percona-toolkit-2.1.1

242. By Daniel Nichter

Merged fix-sleep-bug-979092.

243. By Daniel Nichter

Merge fix-debug-bug-989227.

244. By Daniel Nichter

Merge ../fix-ptc-innodb-bug-994010.

245. By Daniel Nichter

Merge ../dis-fk-chk-bug-976108.

246. By Daniel Nichter

Merge ../doc-osc-limitation-bug-976109.

247. By Daniel Nichter

Merge ../fix-summay-size-bug-993436.

248. By Daniel Nichter

Merge ../fix-ptc-pk-bug-978432.

249. By Daniel Nichter

Merge fix-undef-arrayref-bug-995274.

250. By Daniel Nichter

Merge change-diskstats-group-by-default-fix-bug-952727.

251. By Daniel Nichter

Merge fix-empty-table-bug-987393.

252. By Daniel Nichter

Merge fix-diskstats-hdr-bug-994176.

253. By Daniel Nichter

Merge fix-dupe-key-bug-894140.

254. By Daniel Nichter

Merge fix-stalk-cycles-bug-994947.

255. By Daniel Nichter

Merge fix-osc-index-bug-994002.

256. By Daniel Nichter

Merge fix-mktemp-bug-986151.

257. By Daniel Nichter

Merge fix-osc-innodb-bug-996110.

258. By Daniel Nichter

Merge recursion-method-none-bug-987694.

259. By Daniel Nichter

Merge fix-ptc-mysql-5.5-bug-919499.

260. By Daniel Nichter

Merge fix-sync-ignore-bug-1002365.

261. By Daniel Nichter

Merge fix-sync-index-bug-1003014.

262. By Daniel Nichter

Merge fix-pqd-infinite-loop-bug-888114.

263. By Daniel Nichter

Merge fix-password-comma-bug-886077.

264. By Daniel Nichter

Merge fix-pva-mysql-5.5-bug-898138.

265. By Daniel Nichter

Merge handle-deadlocks-pt-osc.

266. By Daniel Nichter

Merge fix-osc-no-swap-tables-bug-1004551.

267. By Daniel Nichter

Merge test-table-usage-with-fingerprints.

268. By Daniel Nichter

Merge validate-load-options-bug-996915.

269. By Brian Fraser <email address hidden>

Fix for 903379

Revision history for this message
Daniel Nichter (daniel-nichter) wrote :

Whenever the 2.1 merge is done, you can do the magic to backport to 2.0.

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

Subscribers

People subscribed via source and target branches