Merge lp:~percona-toolkit-dev/percona-toolkit/fix-diskstats-hdr-bug-994176 into lp:percona-toolkit/2.0

Proposed by Daniel Nichter
Status: Merged
Merged at revision: 230
Proposed branch: lp:~percona-toolkit-dev/percona-toolkit/fix-diskstats-hdr-bug-994176
Merge into: lp:percona-toolkit/2.0
Diff against target: 70079 lines (+52542/-8884) (has conflicts)
355 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 (+7/-11)
bin/pt-duplicate-key-checker (+171/-92)
bin/pt-fifo-split (+1/-1)
bin/pt-find (+1/-1)
bin/pt-fingerprint (+2143/-0)
bin/pt-fk-error-logger (+1/-1)
bin/pt-heartbeat (+1/-1)
bin/pt-index-usage (+170/-92)
bin/pt-ioprofile (+1/-1)
bin/pt-kill (+1/-1)
bin/pt-log-player (+1/-1)
bin/pt-mext (+1/-1)
bin/pt-mysql-summary (+2080/-683)
bin/pt-online-schema-change (+4640/-1243)
bin/pt-pmp (+1/-1)
bin/pt-query-advisor (+248/-372)
bin/pt-query-digest (+9/-9)
bin/pt-show-grants (+1/-1)
bin/pt-sift (+1/-1)
bin/pt-slave-delay (+1/-1)
bin/pt-slave-find (+1/-1)
bin/pt-slave-restart (+1/-1)
bin/pt-stalk (+1/-1)
bin/pt-summary (+2022/-842)
bin/pt-table-checksum (+234/-95)
bin/pt-table-sync (+554/-405)
bin/pt-table-usage (+7223/-0)
bin/pt-tcp-model (+1/-1)
bin/pt-trend (+1/-1)
bin/pt-upgrade (+1/-1)
bin/pt-variable-advisor (+1/-1)
bin/pt-visual-explain (+1/-1)
config/deb/changelog (+23/-0)
config/sphinx-build/conf.py (+2/-2)
docs/percona-toolkit.pod (+12/-4)
docs/release_notes.rst (+48/-0)
lib/CleanupTask.pm (+7/-2)
lib/Cxn.pm (+1/-1)
lib/Diskstats.pm (+1/-3)
lib/DiskstatsGroupByAll.pm (+5/-7)
lib/MySQLStatusWaiter.pm (+34/-12)
lib/NibbleIterator.pm (+200/-30)
lib/OSCCaptureSync.pm (+0/-142)
lib/OobNibbleIterator.pm (+1/-1)
lib/QueryAdvisorRules.pm (+2/-2)
lib/QueryRewriter.pm (+24/-4)
lib/SQLParser.pm (+103/-8)
lib/Schema.pm (+5/-5)
lib/SchemaIterator.pm (+26/-44)
lib/SlowLogWriter.pm (+2/-2)
lib/TableUsage.pm (+1060/-0)
lib/bash/alt_cmds.sh (+25/-1)
lib/bash/collect_mysql_info.sh (+263/-0)
lib/bash/collect_system_info.sh (+574/-0)
lib/bash/log_warn_die.sh (+6/-0)
lib/bash/report_formatting.sh (+122/-0)
lib/bash/report_mysql_info.sh (+1391/-0)
lib/bash/report_system_info.sh (+1050/-0)
lib/bash/summary_common.sh (+155/-0)
t/lib/CleanupTask.t (+18/-1)
t/lib/Diskstats.t (+32/-29)
t/lib/MySQLStatusWaiter.t (+38/-4)
t/lib/NibbleIterator.t (+74/-3)
t/lib/OSCCaptureSync.t (+0/-131)
t/lib/OobNibbleIterator.t (+8/-3)
t/lib/QueryRewriter.t (+59/-1)
t/lib/SQLParser.t (+1/-1)
t/lib/SchemaIterator.t (+9/-50)
t/lib/SlowLogParser.t (+63/-63)
t/lib/TableUsage.t (+816/-0)
t/lib/bash/collect_mysql_info.sh (+197/-0)
t/lib/bash/collect_system_info.sh (+307/-0)
t/lib/bash/report_formatting.sh (+113/-0)
t/lib/bash/report_mysql_info.sh (+734/-0)
t/lib/bash/report_system_info.sh (+1580/-0)
t/lib/bash/summary_common.sh (+79/-0)
t/lib/samples/SchemaIterator/all-dbs-tbls-5.0.txt (+421/-0)
t/lib/samples/SchemaIterator/all-dbs-tbls.txt (+486/-0)
t/lib/samples/SchemaIterator/resume-from-ignored-sakila-payment-5.0.txt (+52/-0)
t/lib/samples/SchemaIterator/resume-from-ignored-sakila-payment.txt (+49/-0)
t/lib/samples/SchemaIterator/resume-from-sakila-payment-5.0.txt (+70/-0)
t/lib/samples/SchemaIterator/resume-from-sakila-payment.txt (+66/-0)
t/lib/samples/slowlogs/slow002.txt (+16/-16)
t/lib/samples/slowlogs/slow003.txt (+2/-2)
t/lib/samples/slowlogs/slow005.txt (+2/-2)
t/lib/samples/slowlogs/slow006.txt (+12/-12)
t/lib/samples/slowlogs/slow007.txt (+2/-2)
t/lib/samples/slowlogs/slow022.txt (+12/-12)
t/lib/samples/slowlogs/slow030.txt (+899/-899)
t/lib/samples/slowlogs/slow032-rewritten.txt (+2/-2)
t/lib/samples/slowlogs/slow032.txt (+2/-2)
t/lib/samples/slowlogs/slow034.txt (+18/-18)
t/lib/samples/slowlogs/slow035.txt (+2/-2)
t/pt-archiver/basics.t (+43/-1)
t/pt-deadlock-logger/basics.t (+1/-1)
t/pt-diskstats/expected/all_diskstats-001.txt (+0/-3)
t/pt-diskstats/expected/all_diskstats-002.txt (+0/-100)
t/pt-diskstats/expected/all_diskstats-003.txt (+0/-278)
t/pt-diskstats/expected/all_diskstats-004.txt (+0/-4)
t/pt-diskstats/expected/all_diskstats-005.txt (+0/-4)
t/pt-fingerprint/basics.t (+101/-0)
t/pt-fingerprint/samples/query001 (+2/-0)
t/pt-fingerprint/samples/query001.fingerprint (+1/-0)
t/pt-fingerprint/samples/query002 (+2/-0)
t/pt-fingerprint/samples/query002.fingerprint (+1/-0)
t/pt-log-player/samples/log001.txt (+16/-16)
t/pt-mysql-summary/find_my_cnf_file.sh (+0/-20)
t/pt-mysql-summary/format_binlog_filters.sh (+0/-12)
t/pt-mysql-summary/format_innodb_status.sh (+0/-147)
t/pt-mysql-summary/format_overall_db_stats.sh (+0/-61)
t/pt-mysql-summary/format_status_variables.sh (+0/-97)
t/pt-mysql-summary/fuzz.sh (+0/-7)
t/pt-mysql-summary/get_mysql_info.sh (+0/-26)
t/pt-mysql-summary/parse_mysqld_instances.sh (+0/-47)
t/pt-mysql-summary/pretty_print_cnf_file.sh (+0/-40)
t/pt-mysql-summary/pt-mysql-summary.t (+54/-2)
t/pt-mysql-summary/samples/expected_output_temp002.txt (+276/-0)
t/pt-mysql-summary/samples/expected_output_temp003.txt (+219/-0)
t/pt-mysql-summary/samples/expected_output_temp004.txt (+218/-0)
t/pt-mysql-summary/samples/expected_output_temp005.txt (+291/-0)
t/pt-mysql-summary/samples/expected_result_report_summary.txt (+257/-0)
t/pt-mysql-summary/samples/mysql-variables-with-semisync.txt (+326/-0)
t/pt-mysql-summary/samples/temp001/mysql-status (+304/-0)
t/pt-mysql-summary/samples/temp001/mysql-variables (+356/-0)
t/pt-mysql-summary/samples/temp002/innodb-status (+118/-0)
t/pt-mysql-summary/samples/temp002/mysql-config-file (+26/-0)
t/pt-mysql-summary/samples/temp002/mysql-databases (+6/-0)
t/pt-mysql-summary/samples/temp002/mysql-plugins (+35/-0)
t/pt-mysql-summary/samples/temp002/mysql-processlist (+12/-0)
t/pt-mysql-summary/samples/temp002/mysql-status (+370/-0)
t/pt-mysql-summary/samples/temp002/mysql-status-defer (+370/-0)
t/pt-mysql-summary/samples/temp002/mysql-users (+1/-0)
t/pt-mysql-summary/samples/temp002/mysql-variables (+372/-0)
t/pt-mysql-summary/samples/temp002/mysqld-instances (+4/-0)
t/pt-mysql-summary/samples/temp002/mysqldump (+396/-0)
t/pt-mysql-summary/samples/temp003/innodb-status (+77/-0)
t/pt-mysql-summary/samples/temp003/mysql-config-file (+26/-0)
t/pt-mysql-summary/samples/temp003/mysql-databases (+2/-0)
t/pt-mysql-summary/samples/temp003/mysql-master-logs (+1/-0)
t/pt-mysql-summary/samples/temp003/mysql-master-status (+1/-0)
t/pt-mysql-summary/samples/temp003/mysql-plugins (+10/-0)
t/pt-mysql-summary/samples/temp003/mysql-processlist (+9/-0)
t/pt-mysql-summary/samples/temp003/mysql-status (+291/-0)
t/pt-mysql-summary/samples/temp003/mysql-status-defer (+291/-0)
t/pt-mysql-summary/samples/temp003/mysql-users (+1/-0)
t/pt-mysql-summary/samples/temp003/mysql-variables (+285/-0)
t/pt-mysql-summary/samples/temp003/mysqld-instances (+2/-0)
t/pt-mysql-summary/samples/temp004/innodb-status (+77/-0)
t/pt-mysql-summary/samples/temp004/mysql-config-file (+26/-0)
t/pt-mysql-summary/samples/temp004/mysql-databases (+3/-0)
t/pt-mysql-summary/samples/temp004/mysql-master-logs (+2/-0)
t/pt-mysql-summary/samples/temp004/mysql-master-status (+1/-0)
t/pt-mysql-summary/samples/temp004/mysql-plugins (+10/-0)
t/pt-mysql-summary/samples/temp004/mysql-processlist (+9/-0)
t/pt-mysql-summary/samples/temp004/mysql-status (+291/-0)
t/pt-mysql-summary/samples/temp004/mysql-status-defer (+291/-0)
t/pt-mysql-summary/samples/temp004/mysql-users (+1/-0)
t/pt-mysql-summary/samples/temp004/mysql-variables (+285/-0)
t/pt-mysql-summary/samples/temp004/mysqld-instances (+2/-0)
t/pt-mysql-summary/samples/temp005/innodb-status (+108/-0)
t/pt-mysql-summary/samples/temp005/mysql-config-file (+26/-0)
t/pt-mysql-summary/samples/temp005/mysql-databases (+3/-0)
t/pt-mysql-summary/samples/temp005/mysql-master-logs (+1/-0)
t/pt-mysql-summary/samples/temp005/mysql-master-status (+1/-0)
t/pt-mysql-summary/samples/temp005/mysql-plugins (+28/-0)
t/pt-mysql-summary/samples/temp005/mysql-processlist (+18/-0)
t/pt-mysql-summary/samples/temp005/mysql-status (+304/-0)
t/pt-mysql-summary/samples/temp005/mysql-status-defer (+304/-0)
t/pt-mysql-summary/samples/temp005/mysql-users (+1/-0)
t/pt-mysql-summary/samples/temp005/mysql-variables (+363/-0)
t/pt-mysql-summary/samples/temp005/mysqld-executables (+1/-0)
t/pt-mysql-summary/samples/temp005/mysqld-instances (+4/-0)
t/pt-mysql-summary/samples/temp005/mysqldump (+1084/-0)
t/pt-mysql-summary/samples/tempdir/innodb-status (+77/-0)
t/pt-mysql-summary/samples/tempdir/mysql-config-file (+26/-0)
t/pt-mysql-summary/samples/tempdir/mysql-databases (+3/-0)
t/pt-mysql-summary/samples/tempdir/mysql-master-logs (+3/-0)
t/pt-mysql-summary/samples/tempdir/mysql-master-status (+1/-0)
t/pt-mysql-summary/samples/tempdir/mysql-plugins (+10/-0)
t/pt-mysql-summary/samples/tempdir/mysql-processlist (+9/-0)
t/pt-mysql-summary/samples/tempdir/mysql-status (+291/-0)
t/pt-mysql-summary/samples/tempdir/mysql-status-defer (+291/-0)
t/pt-mysql-summary/samples/tempdir/mysql-users (+5/-0)
t/pt-mysql-summary/samples/tempdir/mysql-variables (+283/-0)
t/pt-mysql-summary/samples/tempdir/mysqld-instances (+4/-0)
t/pt-mysql-summary/samples/tempdir/mysqldump (+328/-0)
t/pt-mysql-summary/samples/tempdir/tempfile (+130/-0)
t/pt-mysql-summary/summarize_binlogs.sh (+0/-13)
t/pt-mysql-summary/summarize_processlist.sh (+0/-64)
t/pt-online-schema-change/alter_active_table.t (+56/-46)
t/pt-online-schema-change/basics.t (+541/-218)
t/pt-online-schema-change/check_tables.t (+0/-126)
t/pt-online-schema-change/option_sanity.t (+12/-12)
t/pt-online-schema-change/samples/basic_no_fks.data (+500/-500)
t/pt-online-schema-change/samples/basic_no_fks.sql (+30/-0)
t/pt-online-schema-change/samples/basic_with_fks.sql (+56/-0)
t/pt-online-schema-change/samples/fk_tables_schema.sql (+0/-31)
t/pt-online-schema-change/samples/query_table.pl (+8/-5)
t/pt-online-schema-change/samples/small_table.sql (+0/-27)
t/pt-online-schema-change/sanity_checks.t (+113/-0)
t/pt-query-digest/read_timeout.t (+1/-1)
t/pt-query-digest/review.t (+4/-4)
t/pt-query-digest/samples/save-results/slow002-limit-3.txt (+4/-4)
t/pt-query-digest/samples/save-results/slow002.txt (+2/-2)
t/pt-query-digest/samples/save-results/slow006.txt (+1/-1)
t/pt-query-digest/samples/slow-issue-611.txt (+2/-2)
t/pt-query-digest/samples/slow002-orderbynonexistent.txt (+6/-6)
t/pt-query-digest/samples/slow002_iters_2.txt (+1/-1)
t/pt-query-digest/samples/slow002_orderbyreport.txt (+2/-2)
t/pt-query-digest/samples/slow002_report.txt (+6/-6)
t/pt-query-digest/samples/slow002_report_filtered.txt (+1/-1)
t/pt-query-digest/samples/slow006-first2.txt (+8/-8)
t/pt-query-digest/samples/slow006-order-by-re.txt (+2/-2)
t/pt-query-digest/samples/slow006_AR_1.txt (+2/-2)
t/pt-query-digest/samples/slow006_AR_2.txt (+1/-1)
t/pt-query-digest/samples/slow006_AR_4.txt (+2/-2)
t/pt-query-digest/samples/slow006_AR_5.txt (+1/-1)
t/pt-query-digest/samples/slow006_report.txt (+2/-2)
t/pt-query-digest/samples/slow034-inheritance.txt (+18/-18)
t/pt-query-digest/samples/slow034-no-ts-inheritance.txt (+18/-18)
t/pt-query-digest/samples/slow034-order-by-Locktime-sum-with-Locktime-distro.txt (+8/-8)
t/pt-query-digest/samples/slow034-order-by-Locktime-sum.txt (+8/-8)
t/pt-query-digest/samples/slow035.txt (+1/-1)
t/pt-stalk/pt-stalk.t (+1/-1)
t/pt-summary/format_vmstat.sh (+0/-37)
t/pt-summary/parse_arcconf.sh (+0/-176)
t/pt-summary/parse_dmidecode_mem_devices.sh (+0/-104)
t/pt-summary/parse_ethernet_controller_lspci.sh (+0/-11)
t/pt-summary/parse_fdisk.sh (+0/-16)
t/pt-summary/parse_filesystems.sh (+0/-52)
t/pt-summary/parse_free_minus_b.sh (+0/-67)
t/pt-summary/parse_fusionmpt_lsiutil.sh (+0/-50)
t/pt-summary/parse_hpacucli.sh (+0/-26)
t/pt-summary/parse_ip_s_link.sh (+0/-28)
t/pt-summary/parse_lsi_megaraid.sh (+0/-696)
t/pt-summary/parse_netstat.sh (+0/-49)
t/pt-summary/parse_proc_cpuinfo.sh (+0/-74)
t/pt-summary/parse_raid_controller_dmesg.sh (+0/-32)
t/pt-summary/parse_raid_controller_lspci.sh (+0/-39)
t/pt-summary/parse_virtualization_dmesg.sh (+0/-10)
t/pt-summary/pt-summary.t (+12/-2)
t/pt-summary/samples/BSD/freebsd_001/mounted_fs (+7/-0)
t/pt-summary/samples/BSD/freebsd_001/notable_procs (+2/-0)
t/pt-summary/samples/BSD/freebsd_001/processes (+10/-0)
t/pt-summary/samples/BSD/freebsd_001/summary (+10/-0)
t/pt-summary/samples/BSD/freebsd_001/sysctl (+1481/-0)
t/pt-summary/samples/BSD/freebsd_001/uptime (+1/-0)
t/pt-summary/samples/BSD/freebsd_001/vmstat (+7/-0)
t/pt-summary/samples/BSD/netbsd_001/mounted_fs (+5/-0)
t/pt-summary/samples/BSD/netbsd_001/notable_procs (+2/-0)
t/pt-summary/samples/BSD/netbsd_001/proc_cpuinfo_copy (+14/-0)
t/pt-summary/samples/BSD/netbsd_001/processes (+10/-0)
t/pt-summary/samples/BSD/netbsd_001/summary (+10/-0)
t/pt-summary/samples/BSD/netbsd_001/swapctl (+1/-0)
t/pt-summary/samples/BSD/netbsd_001/sysctl (+511/-0)
t/pt-summary/samples/BSD/netbsd_001/uptime (+1/-0)
t/pt-summary/samples/BSD/netbsd_001/vmstat (+7/-0)
t/pt-summary/samples/BSD/openbsd_001/mounted_fs (+4/-0)
t/pt-summary/samples/BSD/openbsd_001/notable_procs (+2/-0)
t/pt-summary/samples/BSD/openbsd_001/processes (+10/-0)
t/pt-summary/samples/BSD/openbsd_001/summary (+10/-0)
t/pt-summary/samples/BSD/openbsd_001/swapctl (+1/-0)
t/pt-summary/samples/BSD/openbsd_001/sysctl (+423/-0)
t/pt-summary/samples/BSD/openbsd_001/uptime (+1/-0)
t/pt-summary/samples/BSD/openbsd_001/vmstat (+7/-0)
t/pt-summary/samples/Linux/001/dmesg_file (+786/-0)
t/pt-summary/samples/Linux/001/dmidecode (+412/-0)
t/pt-summary/samples/Linux/001/ip (+24/-0)
t/pt-summary/samples/Linux/001/lspci_file (+17/-0)
t/pt-summary/samples/Linux/001/lvs (+1/-0)
t/pt-summary/samples/Linux/001/memory (+50/-0)
t/pt-summary/samples/Linux/001/mounted_fs (+12/-0)
t/pt-summary/samples/Linux/001/netstat (+6/-0)
t/pt-summary/samples/Linux/001/notable_procs (+5/-0)
t/pt-summary/samples/Linux/001/partitioning (+30/-0)
t/pt-summary/samples/Linux/001/proc_cpuinfo_copy (+58/-0)
t/pt-summary/samples/Linux/001/proc_cpuinfo_copy.unq (+1/-0)
t/pt-summary/samples/Linux/001/processes (+10/-0)
t/pt-summary/samples/Linux/001/summary (+23/-0)
t/pt-summary/samples/Linux/001/sysctl (+905/-0)
t/pt-summary/samples/Linux/001/uptime (+1/-0)
t/pt-summary/samples/Linux/001/vmstat (+7/-0)
t/pt-summary/samples/Linux/002/dmesg_file (+283/-0)
t/pt-summary/samples/Linux/002/memory (+34/-0)
t/pt-summary/samples/Linux/002/mounted_fs (+3/-0)
t/pt-summary/samples/Linux/002/netstat (+6/-0)
t/pt-summary/samples/Linux/002/notable_procs (+2/-0)
t/pt-summary/samples/Linux/002/partitioning (+9/-0)
t/pt-summary/samples/Linux/002/proc_cpuinfo_copy (+19/-0)
t/pt-summary/samples/Linux/002/processes (+10/-0)
t/pt-summary/samples/Linux/002/summary (+19/-0)
t/pt-summary/samples/Linux/002/uptime (+1/-0)
t/pt-summary/samples/Linux/002/vmstat (+7/-0)
t/pt-summary/samples/Linux/003/dmesg_file (+283/-0)
t/pt-summary/samples/Linux/003/memory (+34/-0)
t/pt-summary/samples/Linux/003/mounted_fs (+3/-0)
t/pt-summary/samples/Linux/003/netstat (+6/-0)
t/pt-summary/samples/Linux/003/notable_procs (+2/-0)
t/pt-summary/samples/Linux/003/partitioning (+1/-0)
t/pt-summary/samples/Linux/003/proc_cpuinfo_copy (+19/-0)
t/pt-summary/samples/Linux/003/processes (+10/-0)
t/pt-summary/samples/Linux/003/summary (+19/-0)
t/pt-summary/samples/Linux/003/uptime (+1/-0)
t/pt-summary/samples/Linux/003/vmstat (+7/-0)
t/pt-summary/samples/Linux/output_002.txt (+82/-0)
t/pt-summary/samples/Linux/output_003.txt (+79/-0)
t/pt-summary/samples/MegaCli64_AdpAllInfo_aALL001.txt (+227/-0)
t/pt-summary/samples/MegaCli64_LdPdInfo_aALL_886223 (+214/-0)
t/pt-summary/samples/arcconf-001.txt (+133/-0)
t/pt-summary/samples/arcconf-003_900285.txt (+228/-0)
t/pt-summary/samples/arcconf-004_917781.txt (+162/-0)
t/pt-summary/samples/dmesg-005.txt (+787/-0)
t/pt-summary/samples/dmesg-007.txt (+136/-0)
t/pt-summary/samples/hpaculi-001.txt (+11/-0)
t/pt-summary/samples/hpaculi-002.txt (+354/-0)
t/pt-summary/samples/hpaculi-003.txt (+11/-0)
t/pt-summary/samples/ip-s-link-003.txt (+24/-0)
t/pt-summary/samples/lspci-005.txt (+38/-0)
t/pt-summary/samples/netstat-002.txt (+1328/-0)
t/pt-summary/samples/proc_cpuinfo001.txt (+57/-0)
t/pt-summary/samples/proc_cpuinfo001.txt.unq (+1/-0)
t/pt-summary/samples/proc_cpuinfo002.txt (+57/-0)
t/pt-summary/samples/proc_cpuinfo002.txt.unq (+1/-0)
t/pt-table-checksum/bugs.t (+121/-0)
t/pt-table-checksum/samples/empty-table-bug-987393.sql (+18/-0)
t/pt-table-checksum/samples/not-using-pk-bug.out (+20/-0)
t/pt-table-checksum/samples/not-using-pk-bug.sql (+20/-0)
t/pt-table-checksum/samples/undef-arrayref-bug-995274.sql (+18/-0)
t/pt-table-sync/issue_408.t (+1/-1)
t/pt-table-usage/basics.t (+138/-0)
t/pt-table-usage/create_table_definitions.t (+41/-0)
t/pt-table-usage/explain_extended.t (+79/-0)
t/pt-table-usage/samples/ee.out (+6/-0)
t/pt-table-usage/samples/ee.sql (+26/-0)
t/pt-table-usage/samples/in/slow001.txt (+24/-0)
t/pt-table-usage/samples/in/slow002.txt (+20/-0)
t/pt-table-usage/samples/in/slow003.txt (+3/-0)
t/pt-table-usage/samples/out/create-table-defs-001.txt (+4/-0)
t/pt-table-usage/samples/out/create001.txt (+5/-0)
t/pt-table-usage/samples/out/drop-table-if-exists.txt (+3/-0)
t/pt-table-usage/samples/out/query001.txt (+6/-0)
t/pt-table-usage/samples/out/query002.txt (+5/-0)
t/pt-table-usage/samples/out/slow001.txt (+31/-0)
t/pt-table-usage/samples/out/slow002.txt (+40/-0)
t/pt-table-usage/samples/out/slow003-001.txt (+6/-0)
t/pt-table-usage/samples/out/slow003-002.txt (+8/-0)
t/pt-table-usage/samples/out/slow003-003.txt (+6/-0)
util/test-bash-functions (+49/-0)
Text conflict in bin/pt-online-schema-change
Text conflict in bin/pt-table-checksum
Text conflict in lib/NibbleIterator.pm
Text conflict in t/lib/NibbleIterator.t
Conflict adding file t/pt-table-checksum/bugs.t.  Moved existing file to t/pt-table-checksum/bugs.t.moved.
Conflict adding file t/pt-table-checksum/samples/empty-table-bug-987393.sql.  Moved existing file to t/pt-table-checksum/samples/empty-table-bug-987393.sql.moved.
Conflict adding file t/pt-table-checksum/samples/not-using-pk-bug.out.  Moved existing file to t/pt-table-checksum/samples/not-using-pk-bug.out.moved.
Conflict adding file t/pt-table-checksum/samples/not-using-pk-bug.sql.  Moved existing file to t/pt-table-checksum/samples/not-using-pk-bug.sql.moved.
Conflict adding file t/pt-table-checksum/samples/undef-arrayref-bug-995274.sql.  Moved existing file to t/pt-table-checksum/samples/undef-arrayref-bug-995274.sql.moved.
To merge this branch: bzr merge lp:~percona-toolkit-dev/percona-toolkit/fix-diskstats-hdr-bug-994176
Reviewer Review Type Date Requested Status
Daniel Nichter Approve
Review via email: mp+105492@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Daniel Nichter (daniel-nichter) :
review: Approve

Preview Diff

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

Subscribers

People subscribed via source and target branches