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

Proposed by Brian Fraser
Status: Merged
Merged at revision: 243
Proposed branch: lp:~percona-toolkit-dev/percona-toolkit/fix-923896
Merge into: lp:percona-toolkit/2.0
Diff against target: 68182 lines (+53644/-8418) (has conflicts)
362 files modified
Changelog (+21/-0)
MANIFEST (+2/-0)
Makefile.PL (+1/-1)
bin/pt-align (+1/-1)
bin/pt-archiver (+11/-11)
bin/pt-config-diff (+1/-1)
bin/pt-deadlock-logger (+1/-1)
bin/pt-diskstats (+1/-1)
bin/pt-duplicate-key-checker (+171/-92)
bin/pt-fifo-split (+1/-1)
bin/pt-find (+1/-1)
bin/pt-fingerprint (+2143/-0)
bin/pt-fk-error-logger (+1/-1)
bin/pt-heartbeat (+11/-2)
bin/pt-index-usage (+170/-92)
bin/pt-ioprofile (+1/-1)
bin/pt-kill (+3/-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/Processlist.pm (+2/-0)
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/Processlist.t (+34/-1)
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-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-923896
Reviewer Review Type Date Requested Status
Daniel Nichter Approve
Brian Fraser (community) Approve
Review via email: mp+108041@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Brian Fraser (fraserbn) :
review: Approve
Revision history for this message
Daniel Nichter (daniel-nichter) :
review: Approve

Preview Diff

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

Subscribers

People subscribed via source and target branches