Merge lp:~percona-toolkit-dev/percona-toolkit/fix-ptc-mysql-5.5-bug-919499 into lp:percona-toolkit/2.0

Proposed by Daniel Nichter
Status: Merged
Merged at revision: 234
Proposed branch: lp:~percona-toolkit-dev/percona-toolkit/fix-ptc-mysql-5.5-bug-919499
Merge into: lp:percona-toolkit/2.0
Diff against target: 67674 lines (+53172/-8421) (has conflicts)
358 files modified
Changelog (+21/-0)
MANIFEST (+2/-0)
Makefile.PL (+1/-1)
bin/pt-align (+1/-1)
bin/pt-archiver (+11/-11)
bin/pt-config-diff (+1/-1)
bin/pt-deadlock-logger (+1/-1)
bin/pt-diskstats (+1/-1)
bin/pt-duplicate-key-checker (+171/-92)
bin/pt-fifo-split (+1/-1)
bin/pt-find (+1/-1)
bin/pt-fingerprint (+2143/-0)
bin/pt-fk-error-logger (+1/-1)
bin/pt-heartbeat (+11/-2)
bin/pt-index-usage (+170/-92)
bin/pt-ioprofile (+1/-1)
bin/pt-kill (+1/-1)
bin/pt-log-player (+1/-1)
bin/pt-mext (+1/-1)
bin/pt-mysql-summary (+2076/-679)
bin/pt-online-schema-change (+4671/-1243)
bin/pt-pmp (+1/-1)
bin/pt-query-advisor (+248/-372)
bin/pt-query-digest (+9/-9)
bin/pt-show-grants (+1/-1)
bin/pt-sift (+1/-1)
bin/pt-slave-delay (+1/-1)
bin/pt-slave-find (+11/-2)
bin/pt-slave-restart (+11/-2)
bin/pt-stalk (+1/-1)
bin/pt-summary (+2041/-793)
bin/pt-table-checksum (+271/-117)
bin/pt-table-sync (+564/-406)
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/MasterSlave.pm (+10/-0)
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/MasterSlave.t (+91/-1)
t/lib/MySQLStatusWaiter.t (+38/-4)
t/lib/NibbleIterator.t (+74/-3)
t/lib/OSCCaptureSync.t (+0/-131)
t/lib/OobNibbleIterator.t (+8/-3)
t/lib/QueryRewriter.t (+59/-1)
t/lib/SQLParser.t (+1/-1)
t/lib/SchemaIterator.t (+9/-50)
t/lib/SlowLogParser.t (+63/-63)
t/lib/TableUsage.t (+816/-0)
t/lib/bash/collect_mysql_info.sh (+197/-0)
t/lib/bash/collect_system_info.sh (+307/-0)
t/lib/bash/report_formatting.sh (+113/-0)
t/lib/bash/report_mysql_info.sh (+734/-0)
t/lib/bash/report_system_info.sh (+1580/-0)
t/lib/bash/summary_common.sh (+79/-0)
t/lib/samples/SchemaIterator/all-dbs-tbls-5.0.txt (+421/-0)
t/lib/samples/SchemaIterator/all-dbs-tbls.txt (+486/-0)
t/lib/samples/SchemaIterator/resume-from-ignored-sakila-payment-5.0.txt (+52/-0)
t/lib/samples/SchemaIterator/resume-from-ignored-sakila-payment.txt (+49/-0)
t/lib/samples/SchemaIterator/resume-from-sakila-payment-5.0.txt (+70/-0)
t/lib/samples/SchemaIterator/resume-from-sakila-payment.txt (+66/-0)
t/lib/samples/ro-checksum-user.sql (+3/-0)
t/lib/samples/slowlogs/slow002.txt (+16/-16)
t/lib/samples/slowlogs/slow003.txt (+2/-2)
t/lib/samples/slowlogs/slow005.txt (+2/-2)
t/lib/samples/slowlogs/slow006.txt (+12/-12)
t/lib/samples/slowlogs/slow007.txt (+2/-2)
t/lib/samples/slowlogs/slow022.txt (+12/-12)
t/lib/samples/slowlogs/slow030.txt (+899/-899)
t/lib/samples/slowlogs/slow032-rewritten.txt (+2/-2)
t/lib/samples/slowlogs/slow032.txt (+2/-2)
t/lib/samples/slowlogs/slow034.txt (+18/-18)
t/lib/samples/slowlogs/slow035.txt (+2/-2)
t/pt-archiver/basics.t (+43/-1)
t/pt-deadlock-logger/basics.t (+1/-1)
t/pt-fingerprint/basics.t (+101/-0)
t/pt-fingerprint/samples/query001 (+2/-0)
t/pt-fingerprint/samples/query001.fingerprint (+1/-0)
t/pt-fingerprint/samples/query002 (+2/-0)
t/pt-fingerprint/samples/query002.fingerprint (+1/-0)
t/pt-log-player/samples/log001.txt (+16/-16)
t/pt-mysql-summary/find_my_cnf_file.sh (+0/-20)
t/pt-mysql-summary/format_binlog_filters.sh (+0/-12)
t/pt-mysql-summary/format_innodb_status.sh (+0/-147)
t/pt-mysql-summary/format_overall_db_stats.sh (+0/-61)
t/pt-mysql-summary/format_status_variables.sh (+0/-97)
t/pt-mysql-summary/fuzz.sh (+0/-7)
t/pt-mysql-summary/get_mysql_info.sh (+0/-26)
t/pt-mysql-summary/parse_mysqld_instances.sh (+0/-47)
t/pt-mysql-summary/pretty_print_cnf_file.sh (+0/-40)
t/pt-mysql-summary/pt-mysql-summary.t (+54/-2)
t/pt-mysql-summary/samples/expected_output_temp002.txt (+276/-0)
t/pt-mysql-summary/samples/expected_output_temp003.txt (+219/-0)
t/pt-mysql-summary/samples/expected_output_temp004.txt (+218/-0)
t/pt-mysql-summary/samples/expected_output_temp005.txt (+291/-0)
t/pt-mysql-summary/samples/expected_result_report_summary.txt (+257/-0)
t/pt-mysql-summary/samples/mysql-variables-with-semisync.txt (+326/-0)
t/pt-mysql-summary/samples/temp001/mysql-status (+304/-0)
t/pt-mysql-summary/samples/temp001/mysql-variables (+356/-0)
t/pt-mysql-summary/samples/temp002/innodb-status (+118/-0)
t/pt-mysql-summary/samples/temp002/mysql-config-file (+26/-0)
t/pt-mysql-summary/samples/temp002/mysql-databases (+6/-0)
t/pt-mysql-summary/samples/temp002/mysql-plugins (+35/-0)
t/pt-mysql-summary/samples/temp002/mysql-processlist (+12/-0)
t/pt-mysql-summary/samples/temp002/mysql-status (+370/-0)
t/pt-mysql-summary/samples/temp002/mysql-status-defer (+370/-0)
t/pt-mysql-summary/samples/temp002/mysql-users (+1/-0)
t/pt-mysql-summary/samples/temp002/mysql-variables (+372/-0)
t/pt-mysql-summary/samples/temp002/mysqld-instances (+4/-0)
t/pt-mysql-summary/samples/temp002/mysqldump (+396/-0)
t/pt-mysql-summary/samples/temp003/innodb-status (+77/-0)
t/pt-mysql-summary/samples/temp003/mysql-config-file (+26/-0)
t/pt-mysql-summary/samples/temp003/mysql-databases (+2/-0)
t/pt-mysql-summary/samples/temp003/mysql-master-logs (+1/-0)
t/pt-mysql-summary/samples/temp003/mysql-master-status (+1/-0)
t/pt-mysql-summary/samples/temp003/mysql-plugins (+10/-0)
t/pt-mysql-summary/samples/temp003/mysql-processlist (+9/-0)
t/pt-mysql-summary/samples/temp003/mysql-status (+291/-0)
t/pt-mysql-summary/samples/temp003/mysql-status-defer (+291/-0)
t/pt-mysql-summary/samples/temp003/mysql-users (+1/-0)
t/pt-mysql-summary/samples/temp003/mysql-variables (+285/-0)
t/pt-mysql-summary/samples/temp003/mysqld-instances (+2/-0)
t/pt-mysql-summary/samples/temp004/innodb-status (+77/-0)
t/pt-mysql-summary/samples/temp004/mysql-config-file (+26/-0)
t/pt-mysql-summary/samples/temp004/mysql-databases (+3/-0)
t/pt-mysql-summary/samples/temp004/mysql-master-logs (+2/-0)
t/pt-mysql-summary/samples/temp004/mysql-master-status (+1/-0)
t/pt-mysql-summary/samples/temp004/mysql-plugins (+10/-0)
t/pt-mysql-summary/samples/temp004/mysql-processlist (+9/-0)
t/pt-mysql-summary/samples/temp004/mysql-status (+291/-0)
t/pt-mysql-summary/samples/temp004/mysql-status-defer (+291/-0)
t/pt-mysql-summary/samples/temp004/mysql-users (+1/-0)
t/pt-mysql-summary/samples/temp004/mysql-variables (+285/-0)
t/pt-mysql-summary/samples/temp004/mysqld-instances (+2/-0)
t/pt-mysql-summary/samples/temp005/innodb-status (+108/-0)
t/pt-mysql-summary/samples/temp005/mysql-config-file (+26/-0)
t/pt-mysql-summary/samples/temp005/mysql-databases (+3/-0)
t/pt-mysql-summary/samples/temp005/mysql-master-logs (+1/-0)
t/pt-mysql-summary/samples/temp005/mysql-master-status (+1/-0)
t/pt-mysql-summary/samples/temp005/mysql-plugins (+28/-0)
t/pt-mysql-summary/samples/temp005/mysql-processlist (+18/-0)
t/pt-mysql-summary/samples/temp005/mysql-status (+304/-0)
t/pt-mysql-summary/samples/temp005/mysql-status-defer (+304/-0)
t/pt-mysql-summary/samples/temp005/mysql-users (+1/-0)
t/pt-mysql-summary/samples/temp005/mysql-variables (+363/-0)
t/pt-mysql-summary/samples/temp005/mysqld-executables (+1/-0)
t/pt-mysql-summary/samples/temp005/mysqld-instances (+4/-0)
t/pt-mysql-summary/samples/temp005/mysqldump (+1084/-0)
t/pt-mysql-summary/samples/tempdir/innodb-status (+77/-0)
t/pt-mysql-summary/samples/tempdir/mysql-config-file (+26/-0)
t/pt-mysql-summary/samples/tempdir/mysql-databases (+3/-0)
t/pt-mysql-summary/samples/tempdir/mysql-master-logs (+3/-0)
t/pt-mysql-summary/samples/tempdir/mysql-master-status (+1/-0)
t/pt-mysql-summary/samples/tempdir/mysql-plugins (+10/-0)
t/pt-mysql-summary/samples/tempdir/mysql-processlist (+9/-0)
t/pt-mysql-summary/samples/tempdir/mysql-status (+291/-0)
t/pt-mysql-summary/samples/tempdir/mysql-status-defer (+291/-0)
t/pt-mysql-summary/samples/tempdir/mysql-users (+5/-0)
t/pt-mysql-summary/samples/tempdir/mysql-variables (+283/-0)
t/pt-mysql-summary/samples/tempdir/mysqld-instances (+4/-0)
t/pt-mysql-summary/samples/tempdir/mysqldump (+328/-0)
t/pt-mysql-summary/samples/tempdir/tempfile (+130/-0)
t/pt-mysql-summary/summarize_binlogs.sh (+0/-13)
t/pt-mysql-summary/summarize_processlist.sh (+0/-64)
t/pt-online-schema-change/alter_active_table.t (+56/-46)
t/pt-online-schema-change/basics.t (+541/-218)
t/pt-online-schema-change/bugs.t (+73/-0)
t/pt-online-schema-change/check_tables.t (+0/-126)
t/pt-online-schema-change/option_sanity.t (+12/-12)
t/pt-online-schema-change/privs.t (+86/-0)
t/pt-online-schema-change/samples/basic_no_fks.data (+500/-500)
t/pt-online-schema-change/samples/basic_no_fks.sql (+30/-0)
t/pt-online-schema-change/samples/basic_with_fks.sql (+56/-0)
t/pt-online-schema-change/samples/fk_tables_schema.sql (+0/-31)
t/pt-online-schema-change/samples/osc-user.sql (+3/-0)
t/pt-online-schema-change/samples/pk-bug-994002.sql (+29/-0)
t/pt-online-schema-change/samples/query_table.pl (+8/-5)
t/pt-online-schema-change/samples/small_table.sql (+0/-27)
t/pt-online-schema-change/sanity_checks.t (+113/-0)
t/pt-online-schema-change/skip_innodb.t (+60/-0)
t/pt-query-digest/read_timeout.t (+1/-1)
t/pt-query-digest/review.t (+4/-4)
t/pt-query-digest/samples/save-results/slow002-limit-3.txt (+4/-4)
t/pt-query-digest/samples/save-results/slow002.txt (+2/-2)
t/pt-query-digest/samples/save-results/slow006.txt (+1/-1)
t/pt-query-digest/samples/slow-issue-611.txt (+2/-2)
t/pt-query-digest/samples/slow002-orderbynonexistent.txt (+6/-6)
t/pt-query-digest/samples/slow002_iters_2.txt (+1/-1)
t/pt-query-digest/samples/slow002_orderbyreport.txt (+2/-2)
t/pt-query-digest/samples/slow002_report.txt (+6/-6)
t/pt-query-digest/samples/slow002_report_filtered.txt (+1/-1)
t/pt-query-digest/samples/slow006-first2.txt (+8/-8)
t/pt-query-digest/samples/slow006-order-by-re.txt (+2/-2)
t/pt-query-digest/samples/slow006_AR_1.txt (+2/-2)
t/pt-query-digest/samples/slow006_AR_2.txt (+1/-1)
t/pt-query-digest/samples/slow006_AR_4.txt (+2/-2)
t/pt-query-digest/samples/slow006_AR_5.txt (+1/-1)
t/pt-query-digest/samples/slow006_report.txt (+2/-2)
t/pt-query-digest/samples/slow034-inheritance.txt (+18/-18)
t/pt-query-digest/samples/slow034-no-ts-inheritance.txt (+18/-18)
t/pt-query-digest/samples/slow034-order-by-Locktime-sum-with-Locktime-distro.txt (+8/-8)
t/pt-query-digest/samples/slow034-order-by-Locktime-sum.txt (+8/-8)
t/pt-query-digest/samples/slow035.txt (+1/-1)
t/pt-slave-restart/pt-slave-restart.t (+1/-1)
t/pt-stalk/pt-stalk.t (+1/-1)
t/pt-summary/format_vmstat.sh (+0/-37)
t/pt-summary/parse_arcconf.sh (+0/-176)
t/pt-summary/parse_dmidecode_mem_devices.sh (+0/-104)
t/pt-summary/parse_ethernet_controller_lspci.sh (+0/-11)
t/pt-summary/parse_fdisk.sh (+0/-16)
t/pt-summary/parse_filesystems.sh (+0/-52)
t/pt-summary/parse_free_minus_b.sh (+0/-67)
t/pt-summary/parse_fusionmpt_lsiutil.sh (+0/-50)
t/pt-summary/parse_hpacucli.sh (+0/-26)
t/pt-summary/parse_ip_s_link.sh (+0/-28)
t/pt-summary/parse_lsi_megaraid.sh (+0/-696)
t/pt-summary/parse_netstat.sh (+0/-49)
t/pt-summary/parse_proc_cpuinfo.sh (+0/-74)
t/pt-summary/parse_raid_controller_dmesg.sh (+0/-32)
t/pt-summary/parse_raid_controller_lspci.sh (+0/-39)
t/pt-summary/parse_virtualization_dmesg.sh (+0/-10)
t/pt-summary/pt-summary.t (+12/-2)
t/pt-summary/samples/BSD/freebsd_001/mounted_fs (+7/-0)
t/pt-summary/samples/BSD/freebsd_001/notable_procs (+2/-0)
t/pt-summary/samples/BSD/freebsd_001/processes (+10/-0)
t/pt-summary/samples/BSD/freebsd_001/summary (+10/-0)
t/pt-summary/samples/BSD/freebsd_001/sysctl (+1481/-0)
t/pt-summary/samples/BSD/freebsd_001/uptime (+1/-0)
t/pt-summary/samples/BSD/freebsd_001/vmstat (+7/-0)
t/pt-summary/samples/BSD/netbsd_001/mounted_fs (+5/-0)
t/pt-summary/samples/BSD/netbsd_001/notable_procs (+2/-0)
t/pt-summary/samples/BSD/netbsd_001/proc_cpuinfo_copy (+14/-0)
t/pt-summary/samples/BSD/netbsd_001/processes (+10/-0)
t/pt-summary/samples/BSD/netbsd_001/summary (+10/-0)
t/pt-summary/samples/BSD/netbsd_001/swapctl (+1/-0)
t/pt-summary/samples/BSD/netbsd_001/sysctl (+511/-0)
t/pt-summary/samples/BSD/netbsd_001/uptime (+1/-0)
t/pt-summary/samples/BSD/netbsd_001/vmstat (+7/-0)
t/pt-summary/samples/BSD/openbsd_001/mounted_fs (+4/-0)
t/pt-summary/samples/BSD/openbsd_001/notable_procs (+2/-0)
t/pt-summary/samples/BSD/openbsd_001/processes (+10/-0)
t/pt-summary/samples/BSD/openbsd_001/summary (+10/-0)
t/pt-summary/samples/BSD/openbsd_001/swapctl (+1/-0)
t/pt-summary/samples/BSD/openbsd_001/sysctl (+423/-0)
t/pt-summary/samples/BSD/openbsd_001/uptime (+1/-0)
t/pt-summary/samples/BSD/openbsd_001/vmstat (+7/-0)
t/pt-summary/samples/Linux/001/dmesg_file (+786/-0)
t/pt-summary/samples/Linux/001/dmidecode (+412/-0)
t/pt-summary/samples/Linux/001/ip (+24/-0)
t/pt-summary/samples/Linux/001/lspci_file (+17/-0)
t/pt-summary/samples/Linux/001/lvs (+1/-0)
t/pt-summary/samples/Linux/001/memory (+50/-0)
t/pt-summary/samples/Linux/001/mounted_fs (+12/-0)
t/pt-summary/samples/Linux/001/netstat (+6/-0)
t/pt-summary/samples/Linux/001/notable_procs (+5/-0)
t/pt-summary/samples/Linux/001/partitioning (+30/-0)
t/pt-summary/samples/Linux/001/proc_cpuinfo_copy (+58/-0)
t/pt-summary/samples/Linux/001/proc_cpuinfo_copy.unq (+1/-0)
t/pt-summary/samples/Linux/001/processes (+10/-0)
t/pt-summary/samples/Linux/001/summary (+23/-0)
t/pt-summary/samples/Linux/001/sysctl (+905/-0)
t/pt-summary/samples/Linux/001/uptime (+1/-0)
t/pt-summary/samples/Linux/001/vmstat (+7/-0)
t/pt-summary/samples/Linux/002/dmesg_file (+283/-0)
t/pt-summary/samples/Linux/002/memory (+34/-0)
t/pt-summary/samples/Linux/002/mounted_fs (+3/-0)
t/pt-summary/samples/Linux/002/netstat (+6/-0)
t/pt-summary/samples/Linux/002/notable_procs (+2/-0)
t/pt-summary/samples/Linux/002/partitioning (+9/-0)
t/pt-summary/samples/Linux/002/proc_cpuinfo_copy (+19/-0)
t/pt-summary/samples/Linux/002/processes (+10/-0)
t/pt-summary/samples/Linux/002/summary (+19/-0)
t/pt-summary/samples/Linux/002/uptime (+1/-0)
t/pt-summary/samples/Linux/002/vmstat (+7/-0)
t/pt-summary/samples/Linux/003/dmesg_file (+283/-0)
t/pt-summary/samples/Linux/003/memory (+34/-0)
t/pt-summary/samples/Linux/003/mounted_fs (+3/-0)
t/pt-summary/samples/Linux/003/netstat (+6/-0)
t/pt-summary/samples/Linux/003/notable_procs (+2/-0)
t/pt-summary/samples/Linux/003/partitioning (+1/-0)
t/pt-summary/samples/Linux/003/proc_cpuinfo_copy (+19/-0)
t/pt-summary/samples/Linux/003/processes (+10/-0)
t/pt-summary/samples/Linux/003/summary (+19/-0)
t/pt-summary/samples/Linux/003/uptime (+1/-0)
t/pt-summary/samples/Linux/003/vmstat (+7/-0)
t/pt-summary/samples/Linux/output_002.txt (+82/-0)
t/pt-summary/samples/Linux/output_003.txt (+79/-0)
t/pt-summary/samples/MegaCli64_AdpAllInfo_aALL001.txt (+227/-0)
t/pt-summary/samples/MegaCli64_LdPdInfo_aALL_886223 (+214/-0)
t/pt-summary/samples/arcconf-001.txt (+133/-0)
t/pt-summary/samples/arcconf-003_900285.txt (+228/-0)
t/pt-summary/samples/arcconf-004_917781.txt (+162/-0)
t/pt-summary/samples/dmesg-005.txt (+787/-0)
t/pt-summary/samples/dmesg-007.txt (+136/-0)
t/pt-summary/samples/hpaculi-001.txt (+11/-0)
t/pt-summary/samples/hpaculi-002.txt (+354/-0)
t/pt-summary/samples/hpaculi-003.txt (+11/-0)
t/pt-summary/samples/ip-s-link-003.txt (+24/-0)
t/pt-summary/samples/lspci-005.txt (+38/-0)
t/pt-summary/samples/netstat-002.txt (+1328/-0)
t/pt-summary/samples/proc_cpuinfo001.txt (+57/-0)
t/pt-summary/samples/proc_cpuinfo001.txt.unq (+1/-0)
t/pt-summary/samples/proc_cpuinfo002.txt (+57/-0)
t/pt-summary/samples/proc_cpuinfo002.txt.unq (+1/-0)
t/pt-table-checksum/bugs.t (+143/-0)
t/pt-table-checksum/privs.t (+98/-0)
t/pt-table-checksum/samples/empty-table-bug-987393.sql (+18/-0)
t/pt-table-checksum/samples/not-using-pk-bug.out (+20/-0)
t/pt-table-checksum/samples/not-using-pk-bug.sql (+20/-0)
t/pt-table-checksum/samples/undef-arrayref-bug-995274.sql (+18/-0)
t/pt-table-checksum/skip_innodb.t (+75/-0)
t/pt-table-sync/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-summary
Text conflict in bin/pt-table-checksum
Text conflict in lib/NibbleIterator.pm
Text conflict in t/lib/NibbleIterator.t
Conflict adding file t/pt-table-checksum/bugs.t.  Moved existing file to t/pt-table-checksum/bugs.t.moved.
Conflict adding file t/pt-table-checksum/samples/empty-table-bug-987393.sql.  Moved existing file to t/pt-table-checksum/samples/empty-table-bug-987393.sql.moved.
Conflict adding file t/pt-table-checksum/samples/not-using-pk-bug.out.  Moved existing file to t/pt-table-checksum/samples/not-using-pk-bug.out.moved.
Conflict adding file t/pt-table-checksum/samples/not-using-pk-bug.sql.  Moved existing file to t/pt-table-checksum/samples/not-using-pk-bug.sql.moved.
Conflict adding file t/pt-table-checksum/samples/undef-arrayref-bug-995274.sql.  Moved existing file to t/pt-table-checksum/samples/undef-arrayref-bug-995274.sql.moved.
Text conflict in t/pt-table-checksum/skip_innodb.t
To merge this branch: bzr merge lp:~percona-toolkit-dev/percona-toolkit/fix-ptc-mysql-5.5-bug-919499
Reviewer Review Type Date Requested Status
Daniel Nichter Approve
Review via email: mp+106406@code.launchpad.net
To post a comment you must log in.
262. By Daniel Nichter

Fix 5.5 data file timestamps so they're not in the future.

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
=== modified file 'Changelog'
--- Changelog 2012-03-07 23:41:54 +0000
+++ Changelog 2012-05-18 15:41:24 +0000
@@ -1,5 +1,26 @@
1Changelog for Percona Toolkit1Changelog for Percona Toolkit
22
3v2.1.1 released 2012-04-03
4
5 * Completely redesigned pt-online-schema-change
6 * Completely redesigned pt-mysql-summary
7 * Completely redesigned pt-summary
8 * Added new tool: pt-table-usage
9 * Added new tool: pt-fingerprint
10 * Fixed bug 955860: pt-stalk doesn't run vmstat, iostat, and mpstat for --run-time
11 * Fixed bug 960513: SHOW TABLE STATUS is used needlessly
12 * Fixed bug 969726: pt-online-schema-change loses foreign keys
13 * Fixed bug 846028: pt-online-schema-change does not show progress until completed
14 * Fixed bug 898695: pt-online-schema-change add useless ORDER BY
15 * Fixed bug 952727: pt-diskstats shows incorrect wr_mb_s
16 * Fixed bug 963225: pt-query-digest fails to set history columns for disk tmp tables and disk filesort
17 * Fixed bug 967451: Char chunking doesn't quote column name
18 * Fixed bug 972399: pt-table-checksum docs are not rendered right
19 * Fixed bug 896553: Various documentation spelling fixes
20 * Fixed bug 949154: pt-variable-advisor advice for relay-log-space-limit
21 * Fixed bug 953461: pt-upgrade manual broken 'output' section
22 * Fixed bug 949653: pt-table-checksum docs don't mention risks posed by inconsistent schemas
23
3v2.0.4 released 2012-03-0724v2.0.4 released 2012-03-07
425
5 * Added --filter to pt-kill to allow arbitrary --group-by26 * Added --filter to pt-kill to allow arbitrary --group-by
627
=== modified file 'MANIFEST'
--- MANIFEST 2012-02-03 23:25:29 +0000
+++ MANIFEST 2012-05-18 15:41:24 +0000
@@ -12,6 +12,7 @@
12bin/pt-duplicate-key-checker12bin/pt-duplicate-key-checker
13bin/pt-fifo-split13bin/pt-fifo-split
14bin/pt-find14bin/pt-find
15bin/pt-fingerprint
15bin/pt-fk-error-logger16bin/pt-fk-error-logger
16bin/pt-heartbeat17bin/pt-heartbeat
17bin/pt-index-usage18bin/pt-index-usage
@@ -33,6 +34,7 @@
33bin/pt-summary34bin/pt-summary
34bin/pt-table-checksum35bin/pt-table-checksum
35bin/pt-table-sync36bin/pt-table-sync
37bin/pt-table-usage
36bin/pt-tcp-model38bin/pt-tcp-model
37bin/pt-trend39bin/pt-trend
38bin/pt-upgrade40bin/pt-upgrade
3941
=== modified file 'Makefile.PL'
--- Makefile.PL 2012-03-07 23:41:54 +0000
+++ Makefile.PL 2012-05-18 15:41:24 +0000
@@ -2,7 +2,7 @@
22
3WriteMakefile(3WriteMakefile(
4 NAME => 'percona-toolkit',4 NAME => 'percona-toolkit',
5 VERSION => '2.0.4',5 VERSION => '2.1.1',
6 EXE_FILES => [ <bin/*> ],6 EXE_FILES => [ <bin/*> ],
7 MAN1PODS => {7 MAN1PODS => {
8 'docs/percona-toolkit.pod' => 'blib/man1/percona-toolkit.1',8 'docs/percona-toolkit.pod' => 'blib/man1/percona-toolkit.1',
99
=== modified file 'bin/pt-align'
--- bin/pt-align 2012-03-07 23:41:54 +0000
+++ bin/pt-align 2012-05-18 15:41:24 +0000
@@ -218,6 +218,6 @@
218218
219=head1 VERSION219=head1 VERSION
220220
221pt-align 2.0.4221pt-align 2.1.1
222222
223=cut223=cut
224224
=== modified file 'bin/pt-archiver'
--- bin/pt-archiver 2012-03-07 23:41:54 +0000
+++ bin/pt-archiver 2012-05-18 15:41:24 +0000
@@ -4178,6 +4178,16 @@
4178 commit($o, 1) if $commit_each;4178 commit($o, 1) if $commit_each;
4179 $get_sth = $get_next;4179 $get_sth = $get_next;
41804180
4181 # Sleep between fetching the next chunk of rows.
4182 if( my $sleep_time = $o->get('sleep') ) {
4183 $sleep_time = $last_select_time * $o->get('sleep-coef')
4184 if $o->get('sleep-coef');
4185 PTDEBUG && _d('Sleeping', $sleep_time);
4186 trace('sleep', sub {
4187 sleep($sleep_time);
4188 });
4189 }
4190
4181 PTDEBUG && _d('Fetching rows in next chunk');4191 PTDEBUG && _d('Fetching rows in next chunk');
4182 trace('select', sub {4192 trace('select', sub {
4183 my $select_start = time;4193 my $select_start = time;
@@ -4210,16 +4220,6 @@
4210 $lag = $ms->get_slave_lag($lag_dbh);4220 $lag = $ms->get_slave_lag($lag_dbh);
4211 }4221 }
4212 }4222 }
4213
4214 # Sleep between rows.
4215 if( my $sleep_time = $o->get('sleep') ) {
4216 $sleep_time = $last_select_time * $o->get('sleep-coef')
4217 if $o->get('sleep-coef');
4218 PTDEBUG && _d('Sleeping', $sleep_time);
4219 trace('sleep', sub {
4220 sleep($sleep_time);
4221 });
4222 }
4223 } # ROW 4223 } # ROW
4224 PTDEBUG && _d('Done fetching rows');4224 PTDEBUG && _d('Done fetching rows');
42254225
@@ -5726,6 +5726,6 @@
57265726
5727=head1 VERSION5727=head1 VERSION
57285728
5729pt-archiver 2.0.45729pt-archiver 2.1.1
57305730
5731=cut5731=cut
57325732
=== modified file 'bin/pt-config-diff'
--- bin/pt-config-diff 2012-03-07 23:41:54 +0000
+++ bin/pt-config-diff 2012-05-18 15:41:24 +0000
@@ -3405,6 +3405,6 @@
34053405
3406=head1 VERSION3406=head1 VERSION
34073407
3408pt-config-diff 2.0.43408pt-config-diff 2.1.1
34093409
3410=cut3410=cut
34113411
=== modified file 'bin/pt-deadlock-logger'
--- bin/pt-deadlock-logger 2012-03-07 23:41:54 +0000
+++ bin/pt-deadlock-logger 2012-05-18 15:41:24 +0000
@@ -2740,6 +2740,6 @@
27402740
2741=head1 VERSION2741=head1 VERSION
27422742
2743pt-deadlock-logger 2.0.42743pt-deadlock-logger 2.1.1
27442744
2745=cut2745=cut
27462746
=== modified file 'bin/pt-diskstats'
--- bin/pt-diskstats 2012-05-11 14:28:14 +0000
+++ bin/pt-diskstats 2012-05-18 15:41:24 +0000
@@ -4106,6 +4106,6 @@
41064106
4107=head1 VERSION4107=head1 VERSION
41084108
4109pt-diskstats 2.0.44109pt-diskstats 2.1.1
41104110
4111=cut4111=cut
41124112
=== modified file 'bin/pt-duplicate-key-checker'
--- bin/pt-duplicate-key-checker 2012-05-15 01:14:56 +0000
+++ bin/pt-duplicate-key-checker 2012-05-18 15:41:24 +0000
@@ -199,19 +199,58 @@
199 return bless $self, $class;199 return bless $self, $class;
200}200}
201201
202sub 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
202sub parse {251sub parse {
203 my ( $self, $ddl, $opts ) = @_;252 my ( $self, $ddl, $opts ) = @_;
204 return unless $ddl;253 return unless $ddl;
205 if ( ref $ddl eq 'ARRAY' ) {
206 if ( lc $ddl->[0] eq 'table' ) {
207 $ddl = $ddl->[1];
208 }
209 else {
210 return {
211 engine => 'VIEW',
212 };
213 }
214 }
215254
216 if ( $ddl !~ m/CREATE (?:TEMPORARY )?TABLE `/ ) {255 if ( $ddl !~ m/CREATE (?:TEMPORARY )?TABLE `/ ) {
217 die "Cannot parse table definition; is ANSI quoting "256 die "Cannot parse table definition; is ANSI quoting "
@@ -518,41 +557,31 @@
518 return $ddl;557 return $ddl;
519}558}
520559
521sub remove_secondary_indexes {560sub get_table_status {
522 my ( $self, $ddl ) = @_;561 my ( $self, $dbh, $db, $like ) = @_;
523 my $sec_indexes_ddl;562 my $q = $self->{Quoter};
524 my $tbl_struct = $self->parse($ddl);563 my $sql = "SHOW TABLE STATUS FROM " . $q->quote($db);
525564 my @params;
526 if ( ($tbl_struct->{engine} || '') =~ m/InnoDB/i ) {565 if ( $like ) {
527 my $clustered_key = $tbl_struct->{clustered_key};566 $sql .= ' LIKE ?';
528 $clustered_key ||= '';567 push @params, $like;
529568 }
530 my @sec_indexes = map {569 PTDEBUG && _d($sql, @params);
531 my $key_def = $_->{ddl};570 my $sth = $dbh->prepare($sql);
532 $key_def =~ s/([\(\)])/\\$1/g;571 eval { $sth->execute(@params); };
533 $ddl =~ s/\s+$key_def//i;572 if ($EVAL_ERROR) {
534573 PTDEBUG && _d($EVAL_ERROR);
535 my $key_ddl = "ADD $_->{ddl}";574 return;
536 $key_ddl .= ',' unless $key_ddl =~ m/,$/;575 }
537 $key_ddl;576 my @tables = @{$sth->fetchall_arrayref({})};
538 }577 @tables = map {
539 grep { $_->{name} ne $clustered_key }578 my %tbl; # Make a copy with lowercased keys
540 values %{$tbl_struct->{keys}};579 @tbl{ map { lc $_ } keys %$_ } = values %$_;
541 PTDEBUG && _d('Secondary indexes:', Dumper(\@sec_indexes));580 $tbl{engine} ||= $tbl{type} || $tbl{comment};
542581 delete $tbl{type};
543 if ( @sec_indexes ) {582 \%tbl;
544 $sec_indexes_ddl = join(' ', @sec_indexes);583 } @tables;
545 $sec_indexes_ddl =~ s/,$//;584 return @tables;
546 }
547
548 $ddl =~ s/,(\n\) )/$1/s;
549 }
550 else {
551 PTDEBUG && _d('Not removing secondary indexes from',
552 $tbl_struct->{engine}, 'table');
553 }
554
555 return $ddl, $sec_indexes_ddl, $tbl_struct;
556}585}
557586
558sub _d {587sub _d {
@@ -3195,7 +3224,7 @@
31953224
3196sub new {3225sub new {
3197 my ( $class, %args ) = @_;3226 my ( $class, %args ) = @_;
3198 my @required_args = qw(OptionParser Quoter);3227 my @required_args = qw(OptionParser TableParser Quoter);
3199 foreach my $arg ( @required_args ) {3228 foreach my $arg ( @required_args ) {
3200 die "I need a $arg argument" unless $args{$arg};3229 die "I need a $arg argument" unless $args{$arg};
3201 }3230 }
@@ -3204,8 +3233,19 @@
3204 die "I need either a dbh or file_itr argument"3233 die "I need either a dbh or file_itr argument"
3205 if (!$dbh && !$file_itr) || ($dbh && $file_itr);3234 if (!$dbh && !$file_itr) || ($dbh && $file_itr);
32063235
3236 my %resume;
3237 if ( my $table = $args{resume} ) {
3238 PTDEBUG && _d('Will resume from or after', $table);
3239 my ($db, $tbl) = $args{Quoter}->split_unquote($table);
3240 die "Resume table must be database-qualified: $table"
3241 unless $db && $tbl;
3242 $resume{db} = $db;
3243 $resume{tbl} = $tbl;
3244 }
3245
3207 my $self = {3246 my $self = {
3208 %args,3247 %args,
3248 resume => \%resume,
3209 filters => _make_filters(%args),3249 filters => _make_filters(%args),
3210 };3250 };
32113251
@@ -3266,9 +3306,19 @@
3266 return \%filters;3306 return \%filters;
3267}3307}
32683308
3269sub next_schema_object {3309sub next {
3270 my ( $self ) = @_;3310 my ( $self ) = @_;
32713311
3312 if ( !$self->{initialized} ) {
3313 $self->{initialized} = 1;
3314 if ( $self->{resume}->{tbl}
3315 && !$self->table_is_allowed(@{$self->{resume}}{qw(db tbl)}) ) {
3316 PTDEBUG && _d('Will resume after',
3317 join('.', @{$self->{resume}}{qw(db tbl)}));
3318 $self->{resume}->{after} = 1;
3319 }
3320 }
3321
3272 my $schema_obj;3322 my $schema_obj;
3273 if ( $self->{file_itr} ) {3323 if ( $self->{file_itr} ) {
3274 $schema_obj= $self->_iterate_files();3324 $schema_obj= $self->_iterate_files();
@@ -3278,19 +3328,13 @@
3278 }3328 }
32793329
3280 if ( $schema_obj ) {3330 if ( $schema_obj ) {
3281 if ( $schema_obj->{ddl} && $self->{TableParser} ) {
3282 $schema_obj->{tbl_struct}
3283 = $self->{TableParser}->parse($schema_obj->{ddl});
3284 }
3285
3286 delete $schema_obj->{ddl} unless $self->{keep_ddl};
3287
3288 if ( my $schema = $self->{Schema} ) {3331 if ( my $schema = $self->{Schema} ) {
3289 $schema->add_schema_object($schema_obj);3332 $schema->add_schema_object($schema_obj);
3290 }3333 }
3334 PTDEBUG && _d('Next schema object:',
3335 $schema_obj->{db}, $schema_obj->{tbl});
3291 }3336 }
32923337
3293 PTDEBUG && _d('Next schema object:', $schema_obj->{db}, $schema_obj->{tbl});
3294 return $schema_obj;3338 return $schema_obj;
3295}3339}
32963340
@@ -3316,7 +3360,8 @@
3316 my $db = $1; # XXX3360 my $db = $1; # XXX
3317 $db =~ s/^`//; # strip leading `3361 $db =~ s/^`//; # strip leading `
3318 $db =~ s/`$//; # and trailing `3362 $db =~ s/`$//; # and trailing `
3319 if ( $self->database_is_allowed($db) ) {3363 if ( $self->database_is_allowed($db)
3364 && $self->_resume_from_database($db) ) {
3320 $self->{db} = $db;3365 $self->{db} = $db;
3321 }3366 }
3322 }3367 }
@@ -3329,21 +3374,22 @@
3329 my ($tbl) = $chunk =~ m/$tbl_name/;3374 my ($tbl) = $chunk =~ m/$tbl_name/;
3330 $tbl =~ s/^\s*`//;3375 $tbl =~ s/^\s*`//;
3331 $tbl =~ s/`\s*$//;3376 $tbl =~ s/`\s*$//;
3332 if ( $self->table_is_allowed($self->{db}, $tbl) ) {3377 if ( $self->_resume_from_table($tbl)
3378 && $self->table_is_allowed($self->{db}, $tbl) ) {
3333 my ($ddl) = $chunk =~ m/^(?:$open_comment)?(CREATE TABLE.+?;)$/ms;3379 my ($ddl) = $chunk =~ m/^(?:$open_comment)?(CREATE TABLE.+?;)$/ms;
3334 if ( !$ddl ) {3380 if ( !$ddl ) {
3335 warn "Failed to parse CREATE TABLE from\n" . $chunk;3381 warn "Failed to parse CREATE TABLE from\n" . $chunk;
3336 next CHUNK;3382 next CHUNK;
3337 }3383 }
3338 $ddl =~ s/ \*\/;\Z/;/; # remove end of version comment3384 $ddl =~ s/ \*\/;\Z/;/; # remove end of version comment
33393385 my $tbl_struct = $self->{TableParser}->parse($ddl);
3340 my ($engine) = $ddl =~ m/\).*?(?:ENGINE|TYPE)=(\w+)/; 3386 if ( $self->engine_is_allowed($tbl_struct->{engine}) ) {
3341
3342 if ( !$engine || $self->engine_is_allowed($engine) ) {
3343 return {3387 return {
3344 db => $self->{db},3388 db => $self->{db},
3345 tbl => $tbl,3389 tbl => $tbl,
3346 ddl => $ddl,3390 name => $self->{Quoter}->quote($self->{db}, $tbl),
3391 ddl => $ddl,
3392 tbl_struct => $tbl_struct,
3347 };3393 };
3348 }3394 }
3349 }3395 }
@@ -3360,6 +3406,7 @@
3360sub _iterate_dbh {3406sub _iterate_dbh {
3361 my ( $self ) = @_;3407 my ( $self ) = @_;
3362 my $q = $self->{Quoter};3408 my $q = $self->{Quoter};
3409 my $tp = $self->{TableParser};
3363 my $dbh = $self->{dbh};3410 my $dbh = $self->{dbh};
3364 PTDEBUG && _d('Getting next schema object from dbh', $dbh);3411 PTDEBUG && _d('Getting next schema object from dbh', $dbh);
33653412
@@ -3373,7 +3420,9 @@
3373 }3420 }
33743421
3375 if ( !$self->{db} ) {3422 if ( !$self->{db} ) {
3376 $self->{db} = shift @{$self->{dbs}};3423 do {
3424 $self->{db} = shift @{$self->{dbs}};
3425 } until $self->_resume_from_database($self->{db});
3377 PTDEBUG && _d('Next database:', $self->{db});3426 PTDEBUG && _d('Next database:', $self->{db});
3378 return unless $self->{db};3427 return unless $self->{db};
3379 }3428 }
@@ -3386,8 +3435,9 @@
3386 }3435 }
3387 grep {3436 grep {
3388 my ($tbl, $type) = @$_;3437 my ($tbl, $type) = @$_;
3389 $self->table_is_allowed($self->{db}, $tbl)3438 (!$type || ($type ne 'VIEW'))
3390 && (!$type || ($type ne 'VIEW'));3439 && $self->_resume_from_table($tbl)
3440 && $self->table_is_allowed($self->{db}, $tbl);
3391 }3441 }
3392 @{$dbh->selectall_arrayref($sql)};3442 @{$dbh->selectall_arrayref($sql)};
3393 PTDEBUG && _d('Found', scalar @tbls, 'tables in database', $self->{db});3443 PTDEBUG && _d('Found', scalar @tbls, 'tables in database', $self->{db});
@@ -3395,27 +3445,15 @@
3395 }3445 }
33963446
3397 while ( my $tbl = shift @{$self->{tbls}} ) {3447 while ( my $tbl = shift @{$self->{tbls}} ) {
3398 my $engine;3448 my $ddl = $tp->get_create_table($dbh, $self->{db}, $tbl);
3399 if ( $self->{filters}->{'engines'}3449 my $tbl_struct = $tp->parse($ddl);
3400 || $self->{filters}->{'ignore-engines'} ) {3450 if ( $self->engine_is_allowed($tbl_struct->{engine}) ) {
3401 my $sql = "SHOW TABLE STATUS FROM " . $q->quote($self->{db})
3402 . " LIKE \'$tbl\'";
3403 PTDEBUG && _d($sql);
3404 $engine = $dbh->selectrow_hashref($sql)->{engine};
3405 PTDEBUG && _d($tbl, 'uses', $engine, 'engine');
3406 }
3407
3408
3409 if ( !$engine || $self->engine_is_allowed($engine) ) {
3410 my $ddl;
3411 if ( my $du = $self->{MySQLDump} ) {
3412 $ddl = $du->get_create_table($dbh, $q, $self->{db}, $tbl)->[1];
3413 }
3414
3415 return {3451 return {
3416 db => $self->{db},3452 db => $self->{db},
3417 tbl => $tbl,3453 tbl => $tbl,
3418 ddl => $ddl,3454 name => $q->quote($self->{db}, $tbl),
3455 ddl => $ddl,
3456 tbl_struct => $tbl_struct,
3419 };3457 };
3420 }3458 }
3421 }3459 }
@@ -3476,6 +3514,10 @@
34763514
3477 my $filter = $self->{filters};3515 my $filter = $self->{filters};
34783516
3517 if ( $db eq 'mysql' && ($tbl eq 'general_log' || $tbl eq 'slow_log') ) {
3518 return 0;
3519 }
3520
3479 if ( $filter->{'ignore-tables'}->{$tbl}3521 if ( $filter->{'ignore-tables'}->{$tbl}
3480 && ($filter->{'ignore-tables'}->{$tbl} eq '*'3522 && ($filter->{'ignore-tables'}->{$tbl} eq '*'
3481 || $filter->{'ignore-tables'}->{$tbl} eq $db) ) {3523 || $filter->{'ignore-tables'}->{$tbl} eq $db) ) {
@@ -3515,7 +3557,11 @@
35153557
3516sub engine_is_allowed {3558sub engine_is_allowed {
3517 my ( $self, $engine ) = @_;3559 my ( $self, $engine ) = @_;
3518 die "I need an engine argument" unless $engine;3560
3561 if ( !$engine ) {
3562 PTDEBUG && _d('No engine specified; allowing the table');
3563 return 1;
3564 }
35193565
3520 $engine = lc $engine;3566 $engine = lc $engine;
35213567
@@ -3535,6 +3581,40 @@
3535 return 1;3581 return 1;
3536}3582}
35373583
3584sub _resume_from_database {
3585 my ($self, $db) = @_;
3586
3587 return 1 unless $self->{resume}->{db};
3588
3589 if ( $db eq $self->{resume}->{db} ) {
3590 PTDEBUG && _d('At resume db', $db);
3591 delete $self->{resume}->{db};
3592 return 1;
3593 }
3594
3595 return 0;
3596}
3597
3598sub _resume_from_table {
3599 my ($self, $tbl) = @_;
3600
3601 return 1 unless $self->{resume}->{tbl};
3602
3603 if ( $tbl eq $self->{resume}->{tbl} ) {
3604 if ( !$self->{resume}->{after} ) {
3605 PTDEBUG && _d('Resuming from table', $tbl);
3606 delete $self->{resume}->{tbl};
3607 return 1;
3608 }
3609 else {
3610 PTDEBUG && _d('Resuming after table', $tbl);
3611 delete $self->{resume}->{tbl};
3612 }
3613 }
3614
3615 return 0;
3616}
3617
3538sub _d {3618sub _d {
3539 my ($package, undef, $line) = caller 0;3619 my ($package, undef, $line) = caller 0;
3540 @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }3620 @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
@@ -3644,11 +3724,10 @@
3644 MySQLDump => $du,3724 MySQLDump => $du,
3645 TableParser => $tp,3725 TableParser => $tp,
3646 Schema => $schema,3726 Schema => $schema,
3647 keep_ddl => 1,
3648 );3727 );
3649 TABLE:3728 TABLE:
3650 while ( my $tbl = $schema_itr->next_schema_object() ) {3729 while ( my $tbl = $schema_itr->next() ) {
3651 $tbl->{engine} = $tp->get_engine($tbl->{ddl});3730 $tbl->{engine} = $tbl->{tbl_struct}->{engine};
36523731
3653 my ($keys, $clustered_key, $fks);3732 my ($keys, $clustered_key, $fks);
3654 if ( $get_keys ) {3733 if ( $get_keys ) {
@@ -4274,6 +4353,6 @@
42744353
4275=head1 VERSION4354=head1 VERSION
42764355
4277pt-duplicate-key-checker 2.0.44356pt-duplicate-key-checker 2.1.1
42784357
4279=cut4358=cut
42804359
=== modified file 'bin/pt-fifo-split'
--- bin/pt-fifo-split 2012-03-07 23:41:54 +0000
+++ bin/pt-fifo-split 2012-05-18 15:41:24 +0000
@@ -1547,6 +1547,6 @@
15471547
1548=head1 VERSION1548=head1 VERSION
15491549
1550pt-fifo-split 2.0.41550pt-fifo-split 2.1.1
15511551
1552=cut1552=cut
15531553
=== modified file 'bin/pt-find'
--- bin/pt-find 2012-03-07 23:41:54 +0000
+++ bin/pt-find 2012-05-18 15:41:24 +0000
@@ -3822,6 +3822,6 @@
38223822
3823=head1 VERSION3823=head1 VERSION
38243824
3825pt-find 2.0.43825pt-find 2.1.1
38263826
3827=cut3827=cut
38283828
=== added file 'bin/pt-fingerprint'
--- bin/pt-fingerprint 1970-01-01 00:00:00 +0000
+++ bin/pt-fingerprint 2012-05-18 15:41:24 +0000
@@ -0,0 +1,2143 @@
1#!/usr/bin/env perl
2
3# This program is part of Percona Toolkit: http://www.percona.com/software/
4# See "COPYRIGHT, LICENSE, AND WARRANTY" at the end of this file for legal
5# notices and disclaimers.
6
7use strict;
8use warnings FATAL => 'all';
9use constant MKDEBUG => $ENV{MKDEBUG} || 0;
10
11# ###########################################################################
12# OptionParser package
13# This package is a copy without comments from the original. The original
14# with comments and its test file can be found in the Bazaar repository at,
15# lib/OptionParser.pm
16# t/lib/OptionParser.t
17# See https://launchpad.net/percona-toolkit for more information.
18# ###########################################################################
19{
20package OptionParser;
21
22use strict;
23use warnings FATAL => 'all';
24use English qw(-no_match_vars);
25use constant PTDEBUG => $ENV{PTDEBUG} || 0;
26
27use List::Util qw(max);
28use Getopt::Long;
29
30my $POD_link_re = '[LC]<"?([^">]+)"?>';
31
32sub new {
33 my ( $class, %args ) = @_;
34 my @required_args = qw();
35 foreach my $arg ( @required_args ) {
36 die "I need a $arg argument" unless $args{$arg};
37 }
38
39 my ($program_name) = $PROGRAM_NAME =~ m/([.A-Za-z-]+)$/;
40 $program_name ||= $PROGRAM_NAME;
41 my $home = $ENV{HOME} || $ENV{HOMEPATH} || $ENV{USERPROFILE} || '.';
42
43 my %attributes = (
44 'type' => 1,
45 'short form' => 1,
46 'group' => 1,
47 'default' => 1,
48 'cumulative' => 1,
49 'negatable' => 1,
50 );
51
52 my $self = {
53 head1 => 'OPTIONS', # These args are used internally
54 skip_rules => 0, # to instantiate another Option-
55 item => '--(.*)', # Parser obj that parses the
56 attributes => \%attributes, # DSN OPTIONS section. Tools
57 parse_attributes => \&_parse_attribs, # don't tinker with these args.
58
59 %args,
60
61 strict => 1, # disabled by a special rule
62 program_name => $program_name,
63 opts => {},
64 got_opts => 0,
65 short_opts => {},
66 defaults => {},
67 groups => {},
68 allowed_groups => {},
69 errors => [],
70 rules => [], # desc of rules for --help
71 mutex => [], # rule: opts are mutually exclusive
72 atleast1 => [], # rule: at least one opt is required
73 disables => {}, # rule: opt disables other opts
74 defaults_to => {}, # rule: opt defaults to value of other opt
75 DSNParser => undef,
76 default_files => [
77 "/etc/percona-toolkit/percona-toolkit.conf",
78 "/etc/percona-toolkit/$program_name.conf",
79 "$home/.percona-toolkit.conf",
80 "$home/.$program_name.conf",
81 ],
82 types => {
83 string => 's', # standard Getopt type
84 int => 'i', # standard Getopt type
85 float => 'f', # standard Getopt type
86 Hash => 'H', # hash, formed from a comma-separated list
87 hash => 'h', # hash as above, but only if a value is given
88 Array => 'A', # array, similar to Hash
89 array => 'a', # array, similar to hash
90 DSN => 'd', # DSN
91 size => 'z', # size with kMG suffix (powers of 2^10)
92 time => 'm', # time, with an optional suffix of s/h/m/d
93 },
94 };
95
96 return bless $self, $class;
97}
98
99sub get_specs {
100 my ( $self, $file ) = @_;
101 $file ||= $self->{file} || __FILE__;
102 my @specs = $self->_pod_to_specs($file);
103 $self->_parse_specs(@specs);
104
105 open my $fh, "<", $file or die "Cannot open $file: $OS_ERROR";
106 my $contents = do { local $/ = undef; <$fh> };
107 close $fh;
108 if ( $contents =~ m/^=head1 DSN OPTIONS/m ) {
109 PTDEBUG && _d('Parsing DSN OPTIONS');
110 my $dsn_attribs = {
111 dsn => 1,
112 copy => 1,
113 };
114 my $parse_dsn_attribs = sub {
115 my ( $self, $option, $attribs ) = @_;
116 map {
117 my $val = $attribs->{$_};
118 if ( $val ) {
119 $val = $val eq 'yes' ? 1
120 : $val eq 'no' ? 0
121 : $val;
122 $attribs->{$_} = $val;
123 }
124 } keys %$attribs;
125 return {
126 key => $option,
127 %$attribs,
128 };
129 };
130 my $dsn_o = new OptionParser(
131 description => 'DSN OPTIONS',
132 head1 => 'DSN OPTIONS',
133 dsn => 0, # XXX don't infinitely recurse!
134 item => '\* (.)', # key opts are a single character
135 skip_rules => 1, # no rules before opts
136 attributes => $dsn_attribs,
137 parse_attributes => $parse_dsn_attribs,
138 );
139 my @dsn_opts = map {
140 my $opts = {
141 key => $_->{spec}->{key},
142 dsn => $_->{spec}->{dsn},
143 copy => $_->{spec}->{copy},
144 desc => $_->{desc},
145 };
146 $opts;
147 } $dsn_o->_pod_to_specs($file);
148 $self->{DSNParser} = DSNParser->new(opts => \@dsn_opts);
149 }
150
151 if ( $contents =~ m/^=head1 VERSION\n\n^(.+)$/m ) {
152 $self->{version} = $1;
153 PTDEBUG && _d($self->{version});
154 }
155
156 return;
157}
158
159sub DSNParser {
160 my ( $self ) = @_;
161 return $self->{DSNParser};
162};
163
164sub get_defaults_files {
165 my ( $self ) = @_;
166 return @{$self->{default_files}};
167}
168
169sub _pod_to_specs {
170 my ( $self, $file ) = @_;
171 $file ||= $self->{file} || __FILE__;
172 open my $fh, '<', $file or die "Cannot open $file: $OS_ERROR";
173
174 my @specs = ();
175 my @rules = ();
176 my $para;
177
178 local $INPUT_RECORD_SEPARATOR = '';
179 while ( $para = <$fh> ) {
180 next unless $para =~ m/^=head1 $self->{head1}/;
181 last;
182 }
183
184 while ( $para = <$fh> ) {
185 last if $para =~ m/^=over/;
186 next if $self->{skip_rules};
187 chomp $para;
188 $para =~ s/\s+/ /g;
189 $para =~ s/$POD_link_re/$1/go;
190 PTDEBUG && _d('Option rule:', $para);
191 push @rules, $para;
192 }
193
194 die "POD has no $self->{head1} section" unless $para;
195
196 do {
197 if ( my ($option) = $para =~ m/^=item $self->{item}/ ) {
198 chomp $para;
199 PTDEBUG && _d($para);
200 my %attribs;
201
202 $para = <$fh>; # read next paragraph, possibly attributes
203
204 if ( $para =~ m/: / ) { # attributes
205 $para =~ s/\s+\Z//g;
206 %attribs = map {
207 my ( $attrib, $val) = split(/: /, $_);
208 die "Unrecognized attribute for --$option: $attrib"
209 unless $self->{attributes}->{$attrib};
210 ($attrib, $val);
211 } split(/; /, $para);
212 if ( $attribs{'short form'} ) {
213 $attribs{'short form'} =~ s/-//;
214 }
215 $para = <$fh>; # read next paragraph, probably short help desc
216 }
217 else {
218 PTDEBUG && _d('Option has no attributes');
219 }
220
221 $para =~ s/\s+\Z//g;
222 $para =~ s/\s+/ /g;
223 $para =~ s/$POD_link_re/$1/go;
224
225 $para =~ s/\.(?:\n.*| [A-Z].*|\Z)//s;
226 PTDEBUG && _d('Short help:', $para);
227
228 die "No description after option spec $option" if $para =~ m/^=item/;
229
230 if ( my ($base_option) = $option =~ m/^\[no\](.*)/ ) {
231 $option = $base_option;
232 $attribs{'negatable'} = 1;
233 }
234
235 push @specs, {
236 spec => $self->{parse_attributes}->($self, $option, \%attribs),
237 desc => $para
238 . (defined $attribs{default} ? " (default $attribs{default})" : ''),
239 group => ($attribs{'group'} ? $attribs{'group'} : 'default'),
240 };
241 }
242 while ( $para = <$fh> ) {
243 last unless $para;
244 if ( $para =~ m/^=head1/ ) {
245 $para = undef; # Can't 'last' out of a do {} block.
246 last;
247 }
248 last if $para =~ m/^=item /;
249 }
250 } while ( $para );
251
252 die "No valid specs in $self->{head1}" unless @specs;
253
254 close $fh;
255 return @specs, @rules;
256}
257
258sub _parse_specs {
259 my ( $self, @specs ) = @_;
260 my %disables; # special rule that requires deferred checking
261
262 foreach my $opt ( @specs ) {
263 if ( ref $opt ) { # It's an option spec, not a rule.
264 PTDEBUG && _d('Parsing opt spec:',
265 map { ($_, '=>', $opt->{$_}) } keys %$opt);
266
267 my ( $long, $short ) = $opt->{spec} =~ m/^([\w-]+)(?:\|([^!+=]*))?/;
268 if ( !$long ) {
269 die "Cannot parse long option from spec $opt->{spec}";
270 }
271 $opt->{long} = $long;
272
273 die "Duplicate long option --$long" if exists $self->{opts}->{$long};
274 $self->{opts}->{$long} = $opt;
275
276 if ( length $long == 1 ) {
277 PTDEBUG && _d('Long opt', $long, 'looks like short opt');
278 $self->{short_opts}->{$long} = $long;
279 }
280
281 if ( $short ) {
282 die "Duplicate short option -$short"
283 if exists $self->{short_opts}->{$short};
284 $self->{short_opts}->{$short} = $long;
285 $opt->{short} = $short;
286 }
287 else {
288 $opt->{short} = undef;
289 }
290
291 $opt->{is_negatable} = $opt->{spec} =~ m/!/ ? 1 : 0;
292 $opt->{is_cumulative} = $opt->{spec} =~ m/\+/ ? 1 : 0;
293 $opt->{is_required} = $opt->{desc} =~ m/required/ ? 1 : 0;
294
295 $opt->{group} ||= 'default';
296 $self->{groups}->{ $opt->{group} }->{$long} = 1;
297
298 $opt->{value} = undef;
299 $opt->{got} = 0;
300
301 my ( $type ) = $opt->{spec} =~ m/=(.)/;
302 $opt->{type} = $type;
303 PTDEBUG && _d($long, 'type:', $type);
304
305
306 $opt->{spec} =~ s/=./=s/ if ( $type && $type =~ m/[HhAadzm]/ );
307
308 if ( (my ($def) = $opt->{desc} =~ m/default\b(?: ([^)]+))?/) ) {
309 $self->{defaults}->{$long} = defined $def ? $def : 1;
310 PTDEBUG && _d($long, 'default:', $def);
311 }
312
313 if ( $long eq 'config' ) {
314 $self->{defaults}->{$long} = join(',', $self->get_defaults_files());
315 }
316
317 if ( (my ($dis) = $opt->{desc} =~ m/(disables .*)/) ) {
318 $disables{$long} = $dis;
319 PTDEBUG && _d('Deferring check of disables rule for', $opt, $dis);
320 }
321
322 $self->{opts}->{$long} = $opt;
323 }
324 else { # It's an option rule, not a spec.
325 PTDEBUG && _d('Parsing rule:', $opt);
326 push @{$self->{rules}}, $opt;
327 my @participants = $self->_get_participants($opt);
328 my $rule_ok = 0;
329
330 if ( $opt =~ m/mutually exclusive|one and only one/ ) {
331 $rule_ok = 1;
332 push @{$self->{mutex}}, \@participants;
333 PTDEBUG && _d(@participants, 'are mutually exclusive');
334 }
335 if ( $opt =~ m/at least one|one and only one/ ) {
336 $rule_ok = 1;
337 push @{$self->{atleast1}}, \@participants;
338 PTDEBUG && _d(@participants, 'require at least one');
339 }
340 if ( $opt =~ m/default to/ ) {
341 $rule_ok = 1;
342 $self->{defaults_to}->{$participants[0]} = $participants[1];
343 PTDEBUG && _d($participants[0], 'defaults to', $participants[1]);
344 }
345 if ( $opt =~ m/restricted to option groups/ ) {
346 $rule_ok = 1;
347 my ($groups) = $opt =~ m/groups ([\w\s\,]+)/;
348 my @groups = split(',', $groups);
349 %{$self->{allowed_groups}->{$participants[0]}} = map {
350 s/\s+//;
351 $_ => 1;
352 } @groups;
353 }
354 if( $opt =~ m/accepts additional command-line arguments/ ) {
355 $rule_ok = 1;
356 $self->{strict} = 0;
357 PTDEBUG && _d("Strict mode disabled by rule");
358 }
359
360 die "Unrecognized option rule: $opt" unless $rule_ok;
361 }
362 }
363
364 foreach my $long ( keys %disables ) {
365 my @participants = $self->_get_participants($disables{$long});
366 $self->{disables}->{$long} = \@participants;
367 PTDEBUG && _d('Option', $long, 'disables', @participants);
368 }
369
370 return;
371}
372
373sub _get_participants {
374 my ( $self, $str ) = @_;
375 my @participants;
376 foreach my $long ( $str =~ m/--(?:\[no\])?([\w-]+)/g ) {
377 die "Option --$long does not exist while processing rule $str"
378 unless exists $self->{opts}->{$long};
379 push @participants, $long;
380 }
381 PTDEBUG && _d('Participants for', $str, ':', @participants);
382 return @participants;
383}
384
385sub opts {
386 my ( $self ) = @_;
387 my %opts = %{$self->{opts}};
388 return %opts;
389}
390
391sub short_opts {
392 my ( $self ) = @_;
393 my %short_opts = %{$self->{short_opts}};
394 return %short_opts;
395}
396
397sub set_defaults {
398 my ( $self, %defaults ) = @_;
399 $self->{defaults} = {};
400 foreach my $long ( keys %defaults ) {
401 die "Cannot set default for nonexistent option $long"
402 unless exists $self->{opts}->{$long};
403 $self->{defaults}->{$long} = $defaults{$long};
404 PTDEBUG && _d('Default val for', $long, ':', $defaults{$long});
405 }
406 return;
407}
408
409sub get_defaults {
410 my ( $self ) = @_;
411 return $self->{defaults};
412}
413
414sub get_groups {
415 my ( $self ) = @_;
416 return $self->{groups};
417}
418
419sub _set_option {
420 my ( $self, $opt, $val ) = @_;
421 my $long = exists $self->{opts}->{$opt} ? $opt
422 : exists $self->{short_opts}->{$opt} ? $self->{short_opts}->{$opt}
423 : die "Getopt::Long gave a nonexistent option: $opt";
424
425 $opt = $self->{opts}->{$long};
426 if ( $opt->{is_cumulative} ) {
427 $opt->{value}++;
428 }
429 else {
430 $opt->{value} = $val;
431 }
432 $opt->{got} = 1;
433 PTDEBUG && _d('Got option', $long, '=', $val);
434}
435
436sub get_opts {
437 my ( $self ) = @_;
438
439 foreach my $long ( keys %{$self->{opts}} ) {
440 $self->{opts}->{$long}->{got} = 0;
441 $self->{opts}->{$long}->{value}
442 = exists $self->{defaults}->{$long} ? $self->{defaults}->{$long}
443 : $self->{opts}->{$long}->{is_cumulative} ? 0
444 : undef;
445 }
446 $self->{got_opts} = 0;
447
448 $self->{errors} = [];
449
450 if ( @ARGV && $ARGV[0] eq "--config" ) {
451 shift @ARGV;
452 $self->_set_option('config', shift @ARGV);
453 }
454 if ( $self->has('config') ) {
455 my @extra_args;
456 foreach my $filename ( split(',', $self->get('config')) ) {
457 eval {
458 push @extra_args, $self->_read_config_file($filename);
459 };
460 if ( $EVAL_ERROR ) {
461 if ( $self->got('config') ) {
462 die $EVAL_ERROR;
463 }
464 elsif ( PTDEBUG ) {
465 _d($EVAL_ERROR);
466 }
467 }
468 }
469 unshift @ARGV, @extra_args;
470 }
471
472 Getopt::Long::Configure('no_ignore_case', 'bundling');
473 GetOptions(
474 map { $_->{spec} => sub { $self->_set_option(@_); } }
475 grep { $_->{long} ne 'config' } # --config is handled specially above.
476 values %{$self->{opts}}
477 ) or $self->save_error('Error parsing options');
478
479 if ( exists $self->{opts}->{version} && $self->{opts}->{version}->{got} ) {
480 if ( $self->{version} ) {
481 print $self->{version}, "\n";
482 }
483 else {
484 print "Error parsing version. See the VERSION section of the tool's documentation.\n";
485 }
486 exit 0;
487 }
488
489 if ( @ARGV && $self->{strict} ) {
490 $self->save_error("Unrecognized command-line options @ARGV");
491 }
492
493 foreach my $mutex ( @{$self->{mutex}} ) {
494 my @set = grep { $self->{opts}->{$_}->{got} } @$mutex;
495 if ( @set > 1 ) {
496 my $err = join(', ', map { "--$self->{opts}->{$_}->{long}" }
497 @{$mutex}[ 0 .. scalar(@$mutex) - 2] )
498 . ' and --'.$self->{opts}->{$mutex->[-1]}->{long}
499 . ' are mutually exclusive.';
500 $self->save_error($err);
501 }
502 }
503
504 foreach my $required ( @{$self->{atleast1}} ) {
505 my @set = grep { $self->{opts}->{$_}->{got} } @$required;
506 if ( @set == 0 ) {
507 my $err = join(', ', map { "--$self->{opts}->{$_}->{long}" }
508 @{$required}[ 0 .. scalar(@$required) - 2] )
509 .' or --'.$self->{opts}->{$required->[-1]}->{long};
510 $self->save_error("Specify at least one of $err");
511 }
512 }
513
514 $self->_check_opts( keys %{$self->{opts}} );
515 $self->{got_opts} = 1;
516 return;
517}
518
519sub _check_opts {
520 my ( $self, @long ) = @_;
521 my $long_last = scalar @long;
522 while ( @long ) {
523 foreach my $i ( 0..$#long ) {
524 my $long = $long[$i];
525 next unless $long;
526 my $opt = $self->{opts}->{$long};
527 if ( $opt->{got} ) {
528 if ( exists $self->{disables}->{$long} ) {
529 my @disable_opts = @{$self->{disables}->{$long}};
530 map { $self->{opts}->{$_}->{value} = undef; } @disable_opts;
531 PTDEBUG && _d('Unset options', @disable_opts,
532 'because', $long,'disables them');
533 }
534
535 if ( exists $self->{allowed_groups}->{$long} ) {
536
537 my @restricted_groups = grep {
538 !exists $self->{allowed_groups}->{$long}->{$_}
539 } keys %{$self->{groups}};
540
541 my @restricted_opts;
542 foreach my $restricted_group ( @restricted_groups ) {
543 RESTRICTED_OPT:
544 foreach my $restricted_opt (
545 keys %{$self->{groups}->{$restricted_group}} )
546 {
547 next RESTRICTED_OPT if $restricted_opt eq $long;
548 push @restricted_opts, $restricted_opt
549 if $self->{opts}->{$restricted_opt}->{got};
550 }
551 }
552
553 if ( @restricted_opts ) {
554 my $err;
555 if ( @restricted_opts == 1 ) {
556 $err = "--$restricted_opts[0]";
557 }
558 else {
559 $err = join(', ',
560 map { "--$self->{opts}->{$_}->{long}" }
561 grep { $_ }
562 @restricted_opts[0..scalar(@restricted_opts) - 2]
563 )
564 . ' or --'.$self->{opts}->{$restricted_opts[-1]}->{long};
565 }
566 $self->save_error("--$long is not allowed with $err");
567 }
568 }
569
570 }
571 elsif ( $opt->{is_required} ) {
572 $self->save_error("Required option --$long must be specified");
573 }
574
575 $self->_validate_type($opt);
576 if ( $opt->{parsed} ) {
577 delete $long[$i];
578 }
579 else {
580 PTDEBUG && _d('Temporarily failed to parse', $long);
581 }
582 }
583
584 die "Failed to parse options, possibly due to circular dependencies"
585 if @long == $long_last;
586 $long_last = @long;
587 }
588
589 return;
590}
591
592sub _validate_type {
593 my ( $self, $opt ) = @_;
594 return unless $opt;
595
596 if ( !$opt->{type} ) {
597 $opt->{parsed} = 1;
598 return;
599 }
600
601 my $val = $opt->{value};
602
603 if ( $val && $opt->{type} eq 'm' ) { # type time
604 PTDEBUG && _d('Parsing option', $opt->{long}, 'as a time value');
605 my ( $prefix, $num, $suffix ) = $val =~ m/([+-]?)(\d+)([a-z])?$/;
606 if ( !$suffix ) {
607 my ( $s ) = $opt->{desc} =~ m/\(suffix (.)\)/;
608 $suffix = $s || 's';
609 PTDEBUG && _d('No suffix given; using', $suffix, 'for',
610 $opt->{long}, '(value:', $val, ')');
611 }
612 if ( $suffix =~ m/[smhd]/ ) {
613 $val = $suffix eq 's' ? $num # Seconds
614 : $suffix eq 'm' ? $num * 60 # Minutes
615 : $suffix eq 'h' ? $num * 3600 # Hours
616 : $num * 86400; # Days
617 $opt->{value} = ($prefix || '') . $val;
618 PTDEBUG && _d('Setting option', $opt->{long}, 'to', $val);
619 }
620 else {
621 $self->save_error("Invalid time suffix for --$opt->{long}");
622 }
623 }
624 elsif ( $val && $opt->{type} eq 'd' ) { # type DSN
625 PTDEBUG && _d('Parsing option', $opt->{long}, 'as a DSN');
626 my $prev = {};
627 my $from_key = $self->{defaults_to}->{ $opt->{long} };
628 if ( $from_key ) {
629 PTDEBUG && _d($opt->{long}, 'DSN copies from', $from_key, 'DSN');
630 if ( $self->{opts}->{$from_key}->{parsed} ) {
631 $prev = $self->{opts}->{$from_key}->{value};
632 }
633 else {
634 PTDEBUG && _d('Cannot parse', $opt->{long}, 'until',
635 $from_key, 'parsed');
636 return;
637 }
638 }
639 my $defaults = $self->{DSNParser}->parse_options($self);
640 $opt->{value} = $self->{DSNParser}->parse($val, $prev, $defaults);
641 }
642 elsif ( $val && $opt->{type} eq 'z' ) { # type size
643 PTDEBUG && _d('Parsing option', $opt->{long}, 'as a size value');
644 $self->_parse_size($opt, $val);
645 }
646 elsif ( $opt->{type} eq 'H' || (defined $val && $opt->{type} eq 'h') ) {
647 $opt->{value} = { map { $_ => 1 } split(/(?<!\\),\s*/, ($val || '')) };
648 }
649 elsif ( $opt->{type} eq 'A' || (defined $val && $opt->{type} eq 'a') ) {
650 $opt->{value} = [ split(/(?<!\\),\s*/, ($val || '')) ];
651 }
652 else {
653 PTDEBUG && _d('Nothing to validate for option',
654 $opt->{long}, 'type', $opt->{type}, 'value', $val);
655 }
656
657 $opt->{parsed} = 1;
658 return;
659}
660
661sub get {
662 my ( $self, $opt ) = @_;
663 my $long = (length $opt == 1 ? $self->{short_opts}->{$opt} : $opt);
664 die "Option $opt does not exist"
665 unless $long && exists $self->{opts}->{$long};
666 return $self->{opts}->{$long}->{value};
667}
668
669sub got {
670 my ( $self, $opt ) = @_;
671 my $long = (length $opt == 1 ? $self->{short_opts}->{$opt} : $opt);
672 die "Option $opt does not exist"
673 unless $long && exists $self->{opts}->{$long};
674 return $self->{opts}->{$long}->{got};
675}
676
677sub has {
678 my ( $self, $opt ) = @_;
679 my $long = (length $opt == 1 ? $self->{short_opts}->{$opt} : $opt);
680 return defined $long ? exists $self->{opts}->{$long} : 0;
681}
682
683sub set {
684 my ( $self, $opt, $val ) = @_;
685 my $long = (length $opt == 1 ? $self->{short_opts}->{$opt} : $opt);
686 die "Option $opt does not exist"
687 unless $long && exists $self->{opts}->{$long};
688 $self->{opts}->{$long}->{value} = $val;
689 return;
690}
691
692sub save_error {
693 my ( $self, $error ) = @_;
694 push @{$self->{errors}}, $error;
695 return;
696}
697
698sub errors {
699 my ( $self ) = @_;
700 return $self->{errors};
701}
702
703sub usage {
704 my ( $self ) = @_;
705 warn "No usage string is set" unless $self->{usage}; # XXX
706 return "Usage: " . ($self->{usage} || '') . "\n";
707}
708
709sub descr {
710 my ( $self ) = @_;
711 warn "No description string is set" unless $self->{description}; # XXX
712 my $descr = ($self->{description} || $self->{program_name} || '')
713 . " For more details, please use the --help option, "
714 . "or try 'perldoc $PROGRAM_NAME' "
715 . "for complete documentation.";
716 $descr = join("\n", $descr =~ m/(.{0,80})(?:\s+|$)/g)
717 unless $ENV{DONT_BREAK_LINES};
718 $descr =~ s/ +$//mg;
719 return $descr;
720}
721
722sub usage_or_errors {
723 my ( $self, $file, $return ) = @_;
724 $file ||= $self->{file} || __FILE__;
725
726 if ( !$self->{description} || !$self->{usage} ) {
727 PTDEBUG && _d("Getting description and usage from SYNOPSIS in", $file);
728 my %synop = $self->_parse_synopsis($file);
729 $self->{description} ||= $synop{description};
730 $self->{usage} ||= $synop{usage};
731 PTDEBUG && _d("Description:", $self->{description},
732 "\nUsage:", $self->{usage});
733 }
734
735 if ( $self->{opts}->{help}->{got} ) {
736 print $self->print_usage() or die "Cannot print usage: $OS_ERROR";
737 exit 0 unless $return;
738 }
739 elsif ( scalar @{$self->{errors}} ) {
740 print $self->print_errors() or die "Cannot print errors: $OS_ERROR";
741 exit 0 unless $return;
742 }
743
744 return;
745}
746
747sub print_errors {
748 my ( $self ) = @_;
749 my $usage = $self->usage() . "\n";
750 if ( (my @errors = @{$self->{errors}}) ) {
751 $usage .= join("\n * ", 'Errors in command-line arguments:', @errors)
752 . "\n";
753 }
754 return $usage . "\n" . $self->descr();
755}
756
757sub print_usage {
758 my ( $self ) = @_;
759 die "Run get_opts() before print_usage()" unless $self->{got_opts};
760 my @opts = values %{$self->{opts}};
761
762 my $maxl = max(
763 map {
764 length($_->{long}) # option long name
765 + ($_->{is_negatable} ? 4 : 0) # "[no]" if opt is negatable
766 + ($_->{type} ? 2 : 0) # "=x" where x is the opt type
767 }
768 @opts);
769
770 my $maxs = max(0,
771 map {
772 length($_)
773 + ($self->{opts}->{$_}->{is_negatable} ? 4 : 0)
774 + ($self->{opts}->{$_}->{type} ? 2 : 0)
775 }
776 values %{$self->{short_opts}});
777
778 my $lcol = max($maxl, ($maxs + 3));
779 my $rcol = 80 - $lcol - 6;
780 my $rpad = ' ' x ( 80 - $rcol );
781
782 $maxs = max($lcol - 3, $maxs);
783
784 my $usage = $self->descr() . "\n" . $self->usage();
785
786 my @groups = reverse sort grep { $_ ne 'default'; } keys %{$self->{groups}};
787 push @groups, 'default';
788
789 foreach my $group ( reverse @groups ) {
790 $usage .= "\n".($group eq 'default' ? 'Options' : $group).":\n\n";
791 foreach my $opt (
792 sort { $a->{long} cmp $b->{long} }
793 grep { $_->{group} eq $group }
794 @opts )
795 {
796 my $long = $opt->{is_negatable} ? "[no]$opt->{long}" : $opt->{long};
797 my $short = $opt->{short};
798 my $desc = $opt->{desc};
799
800 $long .= $opt->{type} ? "=$opt->{type}" : "";
801
802 if ( $opt->{type} && $opt->{type} eq 'm' ) {
803 my ($s) = $desc =~ m/\(suffix (.)\)/;
804 $s ||= 's';
805 $desc =~ s/\s+\(suffix .\)//;
806 $desc .= ". Optional suffix s=seconds, m=minutes, h=hours, "
807 . "d=days; if no suffix, $s is used.";
808 }
809 $desc = join("\n$rpad", grep { $_ } $desc =~ m/(.{0,$rcol})(?:\s+|$)/g);
810 $desc =~ s/ +$//mg;
811 if ( $short ) {
812 $usage .= sprintf(" --%-${maxs}s -%s %s\n", $long, $short, $desc);
813 }
814 else {
815 $usage .= sprintf(" --%-${lcol}s %s\n", $long, $desc);
816 }
817 }
818 }
819
820 $usage .= "\nOption types: s=string, i=integer, f=float, h/H/a/A=comma-separated list, d=DSN, z=size, m=time\n";
821
822 if ( (my @rules = @{$self->{rules}}) ) {
823 $usage .= "\nRules:\n\n";
824 $usage .= join("\n", map { " $_" } @rules) . "\n";
825 }
826 if ( $self->{DSNParser} ) {
827 $usage .= "\n" . $self->{DSNParser}->usage();
828 }
829 $usage .= "\nOptions and values after processing arguments:\n\n";
830 foreach my $opt ( sort { $a->{long} cmp $b->{long} } @opts ) {
831 my $val = $opt->{value};
832 my $type = $opt->{type} || '';
833 my $bool = $opt->{spec} =~ m/^[\w-]+(?:\|[\w-])?!?$/;
834 $val = $bool ? ( $val ? 'TRUE' : 'FALSE' )
835 : !defined $val ? '(No value)'
836 : $type eq 'd' ? $self->{DSNParser}->as_string($val)
837 : $type =~ m/H|h/ ? join(',', sort keys %$val)
838 : $type =~ m/A|a/ ? join(',', @$val)
839 : $val;
840 $usage .= sprintf(" --%-${lcol}s %s\n", $opt->{long}, $val);
841 }
842 return $usage;
843}
844
845sub prompt_noecho {
846 shift @_ if ref $_[0] eq __PACKAGE__;
847 my ( $prompt ) = @_;
848 local $OUTPUT_AUTOFLUSH = 1;
849 print $prompt
850 or die "Cannot print: $OS_ERROR";
851 my $response;
852 eval {
853 require Term::ReadKey;
854 Term::ReadKey::ReadMode('noecho');
855 chomp($response = <STDIN>);
856 Term::ReadKey::ReadMode('normal');
857 print "\n"
858 or die "Cannot print: $OS_ERROR";
859 };
860 if ( $EVAL_ERROR ) {
861 die "Cannot read response; is Term::ReadKey installed? $EVAL_ERROR";
862 }
863 return $response;
864}
865
866sub _read_config_file {
867 my ( $self, $filename ) = @_;
868 open my $fh, "<", $filename or die "Cannot open $filename: $OS_ERROR\n";
869 my @args;
870 my $prefix = '--';
871 my $parse = 1;
872
873 LINE:
874 while ( my $line = <$fh> ) {
875 chomp $line;
876 next LINE if $line =~ m/^\s*(?:\#|\;|$)/;
877 $line =~ s/\s+#.*$//g;
878 $line =~ s/^\s+|\s+$//g;
879 if ( $line eq '--' ) {
880 $prefix = '';
881 $parse = 0;
882 next LINE;
883 }
884 if ( $parse
885 && (my($opt, $arg) = $line =~ m/^\s*([^=\s]+?)(?:\s*=\s*(.*?)\s*)?$/)
886 ) {
887 push @args, grep { defined $_ } ("$prefix$opt", $arg);
888 }
889 elsif ( $line =~ m/./ ) {
890 push @args, $line;
891 }
892 else {
893 die "Syntax error in file $filename at line $INPUT_LINE_NUMBER";
894 }
895 }
896 close $fh;
897 return @args;
898}
899
900sub read_para_after {
901 my ( $self, $file, $regex ) = @_;
902 open my $fh, "<", $file or die "Can't open $file: $OS_ERROR";
903 local $INPUT_RECORD_SEPARATOR = '';
904 my $para;
905 while ( $para = <$fh> ) {
906 next unless $para =~ m/^=pod$/m;
907 last;
908 }
909 while ( $para = <$fh> ) {
910 next unless $para =~ m/$regex/;
911 last;
912 }
913 $para = <$fh>;
914 chomp($para);
915 close $fh or die "Can't close $file: $OS_ERROR";
916 return $para;
917}
918
919sub clone {
920 my ( $self ) = @_;
921
922 my %clone = map {
923 my $hashref = $self->{$_};
924 my $val_copy = {};
925 foreach my $key ( keys %$hashref ) {
926 my $ref = ref $hashref->{$key};
927 $val_copy->{$key} = !$ref ? $hashref->{$key}
928 : $ref eq 'HASH' ? { %{$hashref->{$key}} }
929 : $ref eq 'ARRAY' ? [ @{$hashref->{$key}} ]
930 : $hashref->{$key};
931 }
932 $_ => $val_copy;
933 } qw(opts short_opts defaults);
934
935 foreach my $scalar ( qw(got_opts) ) {
936 $clone{$scalar} = $self->{$scalar};
937 }
938
939 return bless \%clone;
940}
941
942sub _parse_size {
943 my ( $self, $opt, $val ) = @_;
944
945 if ( lc($val || '') eq 'null' ) {
946 PTDEBUG && _d('NULL size for', $opt->{long});
947 $opt->{value} = 'null';
948 return;
949 }
950
951 my %factor_for = (k => 1_024, M => 1_048_576, G => 1_073_741_824);
952 my ($pre, $num, $factor) = $val =~ m/^([+-])?(\d+)([kMG])?$/;
953 if ( defined $num ) {
954 if ( $factor ) {
955 $num *= $factor_for{$factor};
956 PTDEBUG && _d('Setting option', $opt->{y},
957 'to num', $num, '* factor', $factor);
958 }
959 $opt->{value} = ($pre || '') . $num;
960 }
961 else {
962 $self->save_error("Invalid size for --$opt->{long}: $val");
963 }
964 return;
965}
966
967sub _parse_attribs {
968 my ( $self, $option, $attribs ) = @_;
969 my $types = $self->{types};
970 return $option
971 . ($attribs->{'short form'} ? '|' . $attribs->{'short form'} : '' )
972 . ($attribs->{'negatable'} ? '!' : '' )
973 . ($attribs->{'cumulative'} ? '+' : '' )
974 . ($attribs->{'type'} ? '=' . $types->{$attribs->{type}} : '' );
975}
976
977sub _parse_synopsis {
978 my ( $self, $file ) = @_;
979 $file ||= $self->{file} || __FILE__;
980 PTDEBUG && _d("Parsing SYNOPSIS in", $file);
981
982 local $INPUT_RECORD_SEPARATOR = ''; # read paragraphs
983 open my $fh, "<", $file or die "Cannot open $file: $OS_ERROR";
984 my $para;
985 1 while defined($para = <$fh>) && $para !~ m/^=head1 SYNOPSIS/;
986 die "$file does not contain a SYNOPSIS section" unless $para;
987 my @synop;
988 for ( 1..2 ) { # 1 for the usage, 2 for the description
989 my $para = <$fh>;
990 push @synop, $para;
991 }
992 close $fh;
993 PTDEBUG && _d("Raw SYNOPSIS text:", @synop);
994 my ($usage, $desc) = @synop;
995 die "The SYNOPSIS section in $file is not formatted properly"
996 unless $usage && $desc;
997
998 $usage =~ s/^\s*Usage:\s+(.+)/$1/;
999 chomp $usage;
1000
1001 $desc =~ s/\n/ /g;
1002 $desc =~ s/\s{2,}/ /g;
1003 $desc =~ s/\. ([A-Z][a-z])/. $1/g;
1004 $desc =~ s/\s+$//;
1005
1006 return (
1007 description => $desc,
1008 usage => $usage,
1009 );
1010};
1011
1012sub _d {
1013 my ($package, undef, $line) = caller 0;
1014 @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
1015 map { defined $_ ? $_ : 'undef' }
1016 @_;
1017 print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
1018}
1019
1020if ( PTDEBUG ) {
1021 print '# ', $^X, ' ', $], "\n";
1022 if ( my $uname = `uname -a` ) {
1023 $uname =~ s/\s+/ /g;
1024 print "# $uname\n";
1025 }
1026 print '# Arguments: ',
1027 join(' ', map { my $a = "_[$_]_"; $a =~ s/\n/\n# /g; $a; } @ARGV), "\n";
1028}
1029
10301;
1031}
1032# ###########################################################################
1033# End OptionParser package
1034# ###########################################################################
1035
1036# ###########################################################################
1037# QueryParser package
1038# This package is a copy without comments from the original. The original
1039# with comments and its test file can be found in the Bazaar repository at,
1040# lib/QueryParser.pm
1041# t/lib/QueryParser.t
1042# See https://launchpad.net/percona-toolkit for more information.
1043# ###########################################################################
1044{
1045package QueryParser;
1046
1047use strict;
1048use warnings FATAL => 'all';
1049use English qw(-no_match_vars);
1050use constant PTDEBUG => $ENV{PTDEBUG} || 0;
1051
1052our $tbl_ident = qr/(?:`[^`]+`|\w+)(?:\.(?:`[^`]+`|\w+))?/;
1053our $tbl_regex = qr{
1054 \b(?:FROM|JOIN|(?<!KEY\s)UPDATE|INTO) # Words that precede table names
1055 \b\s*
1056 \(? # Optional paren around tables
1057 ($tbl_ident
1058 (?: (?:\s+ (?:AS\s+)? \w+)?, \s*$tbl_ident )*
1059 )
1060 }xio;
1061our $has_derived = qr{
1062 \b(?:FROM|JOIN|,)
1063 \s*\(\s*SELECT
1064 }xi;
1065
1066our $data_def_stmts = qr/(?:CREATE|ALTER|TRUNCATE|DROP|RENAME)/i;
1067
1068our $data_manip_stmts = qr/(?:INSERT|UPDATE|DELETE|REPLACE)/i;
1069
1070sub new {
1071 my ( $class ) = @_;
1072 bless {}, $class;
1073}
1074
1075sub get_tables {
1076 my ( $self, $query ) = @_;
1077 return unless $query;
1078 PTDEBUG && _d('Getting tables for', $query);
1079
1080 my ( $ddl_stmt ) = $query =~ m/^\s*($data_def_stmts)\b/i;
1081 if ( $ddl_stmt ) {
1082 PTDEBUG && _d('Special table type:', $ddl_stmt);
1083 $query =~ s/IF\s+(?:NOT\s+)?EXISTS//i;
1084 if ( $query =~ m/$ddl_stmt DATABASE\b/i ) {
1085 PTDEBUG && _d('Query alters a database, not a table');
1086 return ();
1087 }
1088 if ( $ddl_stmt =~ m/CREATE/i && $query =~ m/$ddl_stmt\b.+?\bSELECT\b/i ) {
1089 my ($select) = $query =~ m/\b(SELECT\b.+)/is;
1090 PTDEBUG && _d('CREATE TABLE ... SELECT:', $select);
1091 return $self->get_tables($select);
1092 }
1093 my ($tbl) = $query =~ m/TABLE\s+($tbl_ident)(\s+.*)?/i;
1094 PTDEBUG && _d('Matches table:', $tbl);
1095 return ($tbl);
1096 }
1097
1098 $query =~ s/ (?:LOW_PRIORITY|IGNORE|STRAIGHT_JOIN)//ig;
1099
1100 if ( $query =~ /^\s*LOCK TABLES/i ) {
1101 PTDEBUG && _d('Special table type: LOCK TABLES');
1102 $query =~ s/^(\s*LOCK TABLES\s+)//;
1103 $query =~ s/\s+(?:READ|WRITE|LOCAL)+\s*//g;
1104 PTDEBUG && _d('Locked tables:', $query);
1105 $query = "FROM $query";
1106 }
1107
1108 $query =~ s/\\["']//g; # quoted strings
1109 $query =~ s/".*?"/?/sg; # quoted strings
1110 $query =~ s/'.*?'/?/sg; # quoted strings
1111
1112 my @tables;
1113 foreach my $tbls ( $query =~ m/$tbl_regex/gio ) {
1114 PTDEBUG && _d('Match tables:', $tbls);
1115
1116 next if $tbls =~ m/\ASELECT\b/i;
1117
1118 foreach my $tbl ( split(',', $tbls) ) {
1119 $tbl =~ s/\s*($tbl_ident)(\s+.*)?/$1/gio;
1120
1121 if ( $tbl !~ m/[a-zA-Z]/ ) {
1122 PTDEBUG && _d('Skipping suspicious table name:', $tbl);
1123 next;
1124 }
1125
1126 push @tables, $tbl;
1127 }
1128 }
1129 return @tables;
1130}
1131
1132sub has_derived_table {
1133 my ( $self, $query ) = @_;
1134 my $match = $query =~ m/$has_derived/;
1135 PTDEBUG && _d($query, 'has ' . ($match ? 'a' : 'no') . ' derived table');
1136 return $match;
1137}
1138
1139sub get_aliases {
1140 my ( $self, $query, $list ) = @_;
1141
1142 my $result = {
1143 DATABASE => {},
1144 TABLE => {},
1145 };
1146 return $result unless $query;
1147
1148 $query =~ s/ (?:LOW_PRIORITY|IGNORE|STRAIGHT_JOIN)//ig;
1149
1150 $query =~ s/ (?:INNER|OUTER|CROSS|LEFT|RIGHT|NATURAL)//ig;
1151
1152 my @tbl_refs;
1153 my ($tbl_refs, $from) = $query =~ m{
1154 (
1155 (FROM|INTO|UPDATE)\b\s* # Keyword before table refs
1156 .+? # Table refs
1157 )
1158 (?:\s+|\z) # If the query does not end with the table
1159 (?:WHERE|ORDER|LIMIT|HAVING|SET|VALUES|\z) # Keyword after table refs
1160 }ix;
1161
1162 if ( $tbl_refs ) {
1163
1164 if ( $query =~ m/^(?:INSERT|REPLACE)/i ) {
1165 $tbl_refs =~ s/\([^\)]+\)\s*//;
1166 }
1167
1168 PTDEBUG && _d('tbl refs:', $tbl_refs);
1169
1170 my $before_tbl = qr/(?:,|JOIN|\s|$from)+/i;
1171
1172 my $after_tbl = qr/(?:,|JOIN|ON|USING|\z)/i;
1173
1174 $tbl_refs =~ s/ = /=/g;
1175
1176 while (
1177 $tbl_refs =~ m{
1178 $before_tbl\b\s*
1179 ( ($tbl_ident) (?:\s+ (?:AS\s+)? (\w+))? )
1180 \s*$after_tbl
1181 }xgio )
1182 {
1183 my ( $tbl_ref, $db_tbl, $alias ) = ($1, $2, $3);
1184 PTDEBUG && _d('Match table:', $tbl_ref);
1185 push @tbl_refs, $tbl_ref;
1186 $alias = $self->trim_identifier($alias);
1187
1188 if ( $tbl_ref =~ m/^AS\s+\w+/i ) {
1189 PTDEBUG && _d('Subquery', $tbl_ref);
1190 $result->{TABLE}->{$alias} = undef;
1191 next;
1192 }
1193
1194 my ( $db, $tbl ) = $db_tbl =~ m/^(?:(.*?)\.)?(.*)/;
1195 $db = $self->trim_identifier($db);
1196 $tbl = $self->trim_identifier($tbl);
1197 $result->{TABLE}->{$alias || $tbl} = $tbl;
1198 $result->{DATABASE}->{$tbl} = $db if $db;
1199 }
1200 }
1201 else {
1202 PTDEBUG && _d("No tables ref in", $query);
1203 }
1204
1205 if ( $list ) {
1206 return \@tbl_refs;
1207 }
1208 else {
1209 return $result;
1210 }
1211}
1212
1213sub split {
1214 my ( $self, $query ) = @_;
1215 return unless $query;
1216 $query = $self->clean_query($query);
1217 PTDEBUG && _d('Splitting', $query);
1218
1219 my $verbs = qr{SELECT|INSERT|UPDATE|DELETE|REPLACE|UNION|CREATE}i;
1220
1221 my @split_statements = grep { $_ } split(m/\b($verbs\b(?!(?:\s*\()))/io, $query);
1222
1223 my @statements;
1224 if ( @split_statements == 1 ) {
1225 push @statements, $query;
1226 }
1227 else {
1228 for ( my $i = 0; $i <= $#split_statements; $i += 2 ) {
1229 push @statements, $split_statements[$i].$split_statements[$i+1];
1230
1231 if ( $statements[-2] && $statements[-2] =~ m/on duplicate key\s+$/i ) {
1232 $statements[-2] .= pop @statements;
1233 }
1234 }
1235 }
1236
1237 PTDEBUG && _d('statements:', map { $_ ? "<$_>" : 'none' } @statements);
1238 return @statements;
1239}
1240
1241sub clean_query {
1242 my ( $self, $query ) = @_;
1243 return unless $query;
1244 $query =~ s!/\*.*?\*/! !g; # Remove /* comment blocks */
1245 $query =~ s/^\s+//; # Remove leading spaces
1246 $query =~ s/\s+$//; # Remove trailing spaces
1247 $query =~ s/\s{2,}/ /g; # Remove extra spaces
1248 return $query;
1249}
1250
1251sub split_subquery {
1252 my ( $self, $query ) = @_;
1253 return unless $query;
1254 $query = $self->clean_query($query);
1255 $query =~ s/;$//;
1256
1257 my @subqueries;
1258 my $sqno = 0; # subquery number
1259 my $pos = 0;
1260 while ( $query =~ m/(\S+)(?:\s+|\Z)/g ) {
1261 $pos = pos($query);
1262 my $word = $1;
1263 PTDEBUG && _d($word, $sqno);
1264 if ( $word =~ m/^\(?SELECT\b/i ) {
1265 my $start_pos = $pos - length($word) - 1;
1266 if ( $start_pos ) {
1267 $sqno++;
1268 PTDEBUG && _d('Subquery', $sqno, 'starts at', $start_pos);
1269 $subqueries[$sqno] = {
1270 start_pos => $start_pos,
1271 end_pos => 0,
1272 len => 0,
1273 words => [$word],
1274 lp => 1, # left parentheses
1275 rp => 0, # right parentheses
1276 done => 0,
1277 };
1278 }
1279 else {
1280 PTDEBUG && _d('Main SELECT at pos 0');
1281 }
1282 }
1283 else {
1284 next unless $sqno; # next unless we're in a subquery
1285 PTDEBUG && _d('In subquery', $sqno);
1286 my $sq = $subqueries[$sqno];
1287 if ( $sq->{done} ) {
1288 PTDEBUG && _d('This subquery is done; SQL is for',
1289 ($sqno - 1 ? "subquery $sqno" : "the main SELECT"));
1290 next;
1291 }
1292 push @{$sq->{words}}, $word;
1293 my $lp = ($word =~ tr/\(//) || 0;
1294 my $rp = ($word =~ tr/\)//) || 0;
1295 PTDEBUG && _d('parentheses left', $lp, 'right', $rp);
1296 if ( ($sq->{lp} + $lp) - ($sq->{rp} + $rp) == 0 ) {
1297 my $end_pos = $pos - 1;
1298 PTDEBUG && _d('Subquery', $sqno, 'ends at', $end_pos);
1299 $sq->{end_pos} = $end_pos;
1300 $sq->{len} = $end_pos - $sq->{start_pos};
1301 }
1302 }
1303 }
1304
1305 for my $i ( 1..$#subqueries ) {
1306 my $sq = $subqueries[$i];
1307 next unless $sq;
1308 $sq->{sql} = join(' ', @{$sq->{words}});
1309 substr $query,
1310 $sq->{start_pos} + 1, # +1 for (
1311 $sq->{len} - 1, # -1 for )
1312 "__subquery_$i";
1313 }
1314
1315 return $query, map { $_->{sql} } grep { defined $_ } @subqueries;
1316}
1317
1318sub query_type {
1319 my ( $self, $query, $qr ) = @_;
1320 my ($type, undef) = $qr->distill_verbs($query);
1321 my $rw;
1322 if ( $type =~ m/^SELECT\b/ ) {
1323 $rw = 'read';
1324 }
1325 elsif ( $type =~ m/^$data_manip_stmts\b/
1326 || $type =~ m/^$data_def_stmts\b/ ) {
1327 $rw = 'write'
1328 }
1329
1330 return {
1331 type => $type,
1332 rw => $rw,
1333 }
1334}
1335
1336sub get_columns {
1337 my ( $self, $query ) = @_;
1338 my $cols = [];
1339 return $cols unless $query;
1340 my $cols_def;
1341
1342 if ( $query =~ m/^SELECT/i ) {
1343 $query =~ s/
1344 ^SELECT\s+
1345 (?:ALL
1346 |DISTINCT
1347 |DISTINCTROW
1348 |HIGH_PRIORITY
1349 |STRAIGHT_JOIN
1350 |SQL_SMALL_RESULT
1351 |SQL_BIG_RESULT
1352 |SQL_BUFFER_RESULT
1353 |SQL_CACHE
1354 |SQL_NO_CACHE
1355 |SQL_CALC_FOUND_ROWS
1356 )\s+
1357 /SELECT /xgi;
1358 ($cols_def) = $query =~ m/^SELECT\s+(.+?)\s+FROM/i;
1359 }
1360 elsif ( $query =~ m/^(?:INSERT|REPLACE)/i ) {
1361 ($cols_def) = $query =~ m/\(([^\)]+)\)\s*VALUE/i;
1362 }
1363
1364 PTDEBUG && _d('Columns:', $cols_def);
1365 if ( $cols_def ) {
1366 @$cols = split(',', $cols_def);
1367 map {
1368 my $col = $_;
1369 $col = s/^\s+//g;
1370 $col = s/\s+$//g;
1371 $col;
1372 } @$cols;
1373 }
1374
1375 return $cols;
1376}
1377
1378sub parse {
1379 my ( $self, $query ) = @_;
1380 return unless $query;
1381 my $parsed = {};
1382
1383 $query =~ s/\n/ /g;
1384 $query = $self->clean_query($query);
1385
1386 $parsed->{query} = $query,
1387 $parsed->{tables} = $self->get_aliases($query, 1);
1388 $parsed->{columns} = $self->get_columns($query);
1389
1390 my ($type) = $query =~ m/^(\w+)/;
1391 $parsed->{type} = lc $type;
1392
1393
1394 $parsed->{sub_queries} = [];
1395
1396 return $parsed;
1397}
1398
1399sub extract_tables {
1400 my ( $self, %args ) = @_;
1401 my $query = $args{query};
1402 my $default_db = $args{default_db};
1403 my $q = $self->{Quoter} || $args{Quoter};
1404 return unless $query;
1405 PTDEBUG && _d('Extracting tables');
1406 my @tables;
1407 my %seen;
1408 foreach my $db_tbl ( $self->get_tables($query) ) {
1409 next unless $db_tbl;
1410 next if $seen{$db_tbl}++; # Unique-ify for issue 337.
1411 my ( $db, $tbl ) = $q->split_unquote($db_tbl);
1412 push @tables, [ $db || $default_db, $tbl ];
1413 }
1414 return @tables;
1415}
1416
1417sub trim_identifier {
1418 my ($self, $str) = @_;
1419 return unless defined $str;
1420 $str =~ s/`//g;
1421 $str =~ s/^\s+//;
1422 $str =~ s/\s+$//;
1423 return $str;
1424}
1425
1426sub _d {
1427 my ($package, undef, $line) = caller 0;
1428 @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
1429 map { defined $_ ? $_ : 'undef' }
1430 @_;
1431 print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
1432}
1433
14341;
1435}
1436# ###########################################################################
1437# End QueryParser package
1438# ###########################################################################
1439
1440# ###########################################################################
1441# QueryRewriter package
1442# This package is a copy without comments from the original. The original
1443# with comments and its test file can be found in the Bazaar repository at,
1444# lib/QueryRewriter.pm
1445# t/lib/QueryRewriter.t
1446# See https://launchpad.net/percona-toolkit for more information.
1447# ###########################################################################
1448{
1449package QueryRewriter;
1450
1451use strict;
1452use warnings FATAL => 'all';
1453use English qw(-no_match_vars);
1454use constant PTDEBUG => $ENV{PTDEBUG} || 0;
1455
1456our $verbs = qr{^SHOW|^FLUSH|^COMMIT|^ROLLBACK|^BEGIN|SELECT|INSERT
1457 |UPDATE|DELETE|REPLACE|^SET|UNION|^START|^LOCK}xi;
1458my $quote_re = qr/"(?:(?!(?<!\\)").)*"|'(?:(?!(?<!\\)').)*'/; # Costly!
1459my $bal;
1460$bal = qr/
1461 \(
1462 (?:
1463 (?> [^()]+ ) # Non-parens without backtracking
1464 |
1465 (??{ $bal }) # Group with matching parens
1466 )*
1467 \)
1468 /x;
1469
1470my $olc_re = qr/(?:--|#)[^'"\r\n]*(?=[\r\n]|\Z)/; # One-line comments
1471my $mlc_re = qr#/\*[^!].*?\*/#sm; # But not /*!version */
1472my $vlc_re = qr#/\*.*?[0-9+].*?\*/#sm; # For SHOW + /*!version */
1473my $vlc_rf = qr#^(SHOW).*?/\*![0-9+].*?\*/#sm; # Variation for SHOW
1474
1475
1476sub new {
1477 my ( $class, %args ) = @_;
1478 my $self = { %args };
1479 return bless $self, $class;
1480}
1481
1482sub strip_comments {
1483 my ( $self, $query ) = @_;
1484 return unless $query;
1485 $query =~ s/$olc_re//go;
1486 $query =~ s/$mlc_re//go;
1487 if ( $query =~ m/$vlc_rf/i ) { # contains show + version
1488 $query =~ s/$vlc_re//go;
1489 }
1490 return $query;
1491}
1492
1493sub shorten {
1494 my ( $self, $query, $length ) = @_;
1495 $query =~ s{
1496 \A(
1497 (?:INSERT|REPLACE)
1498 (?:\s+LOW_PRIORITY|DELAYED|HIGH_PRIORITY|IGNORE)?
1499 (?:\s\w+)*\s+\S+\s+VALUES\s*\(.*?\)
1500 )
1501 \s*,\s*\(.*?(ON\s+DUPLICATE|\Z)}
1502 {$1 /*... omitted ...*/$2}xsi;
1503
1504 return $query unless $query =~ m/IN\s*\(\s*(?!select)/i;
1505
1506 my $last_length = 0;
1507 my $query_length = length($query);
1508 while (
1509 $length > 0
1510 && $query_length > $length
1511 && $query_length < ( $last_length || $query_length + 1 )
1512 ) {
1513 $last_length = $query_length;
1514 $query =~ s{
1515 (\bIN\s*\() # The opening of an IN list
1516 ([^\)]+) # Contents of the list, assuming no item contains paren
1517 (?=\)) # Close of the list
1518 }
1519 {
1520 $1 . __shorten($2)
1521 }gexsi;
1522 }
1523
1524 return $query;
1525}
1526
1527sub __shorten {
1528 my ( $snippet ) = @_;
1529 my @vals = split(/,/, $snippet);
1530 return $snippet unless @vals > 20;
1531 my @keep = splice(@vals, 0, 20); # Remove and save the first 20 items
1532 return
1533 join(',', @keep)
1534 . "/*... omitted "
1535 . scalar(@vals)
1536 . " items ...*/";
1537}
1538
1539sub fingerprint {
1540 my ( $self, $query ) = @_;
1541
1542 $query =~ m#\ASELECT /\*!40001 SQL_NO_CACHE \*/ \* FROM `# # mysqldump query
1543 && return 'mysqldump';
1544 $query =~ m#/\*\w+\.\w+:[0-9]/[0-9]\*/# # pt-table-checksum, etc query
1545 && return 'percona-toolkit';
1546 $query =~ m/\Aadministrator command: /
1547 && return $query;
1548 $query =~ m/\A\s*(call\s+\S+)\(/i
1549 && return lc($1); # Warning! $1 used, be careful.
1550 if ( my ($beginning) = $query =~ m/\A((?:INSERT|REPLACE)(?: IGNORE)?\s+INTO.+?VALUES\s*\(.*?\))\s*,\s*\(/is ) {
1551 $query = $beginning; # Shorten multi-value INSERT statements ASAP
1552 }
1553
1554 $query =~ s/$olc_re//go;
1555 $query =~ s/$mlc_re//go;
1556 $query =~ s/\Ause \S+\Z/use ?/i # Abstract the DB in USE
1557 && return $query;
1558
1559 $query =~ s/\\["']//g; # quoted strings
1560 $query =~ s/".*?"/?/sg; # quoted strings
1561 $query =~ s/'.*?'/?/sg; # quoted strings
1562
1563 if ( $self->{match_md5_checksums} ) {
1564 $query =~ s/([._-])[a-f0-9]{32}/$1?/g;
1565 }
1566
1567 if ( !$self->{match_embedded_numbers} ) {
1568 $query =~ s/[0-9+-][0-9a-f.xb+-]*/?/g;
1569 }
1570 else {
1571 $query =~ s/\b[0-9+-][0-9a-f.xb+-]*/?/g;
1572 }
1573
1574 if ( $self->{match_md5_checksums} ) {
1575 $query =~ s/[xb+-]\?/?/g;
1576 }
1577 else {
1578 $query =~ s/[xb.+-]\?/?/g;
1579 }
1580
1581 $query =~ s/\A\s+//; # Chop off leading whitespace
1582 chomp $query; # Kill trailing whitespace
1583 $query =~ tr[ \n\t\r\f][ ]s; # Collapse whitespace
1584 $query = lc $query;
1585 $query =~ s/\bnull\b/?/g; # Get rid of NULLs
1586 $query =~ s{ # Collapse IN and VALUES lists
1587 \b(in|values?)(?:[\s,]*\([\s?,]*\))+
1588 }
1589 {$1(?+)}gx;
1590 $query =~ s{ # Collapse UNION
1591 \b(select\s.*?)(?:(\sunion(?:\sall)?)\s\1)+
1592 }
1593 {$1 /*repeat$2*/}xg;
1594 $query =~ s/\blimit \?(?:, ?\?| offset \?)?/limit ?/; # LIMIT
1595
1596 if ( $query =~ m/\bORDER BY /gi ) { # Find, anchor on ORDER BY clause
1597 1 while $query =~ s/\G(.+?)\s+ASC/$1/gi && pos $query;
1598 }
1599
1600 return $query;
1601}
1602
1603sub distill_verbs {
1604 my ( $self, $query ) = @_;
1605
1606 $query =~ m/\A\s*call\s+(\S+)\(/i && return "CALL $1";
1607 $query =~ m/\A\s*use\s+/ && return "USE";
1608 $query =~ m/\A\s*UNLOCK TABLES/i && return "UNLOCK";
1609 $query =~ m/\A\s*xa\s+(\S+)/i && return "XA_$1";
1610
1611 if ( $query =~ m/\Aadministrator command:/ ) {
1612 $query =~ s/administrator command:/ADMIN/;
1613 $query = uc $query;
1614 return $query;
1615 }
1616
1617 $query = $self->strip_comments($query);
1618
1619 if ( $query =~ m/\A\s*SHOW\s+/i ) {
1620 PTDEBUG && _d($query);
1621
1622 $query = uc $query;
1623 $query =~ s/\s+(?:GLOBAL|SESSION|FULL|STORAGE|ENGINE)\b/ /g;
1624 $query =~ s/\s+COUNT[^)]+\)//g;
1625
1626 $query =~ s/\s+(?:FOR|FROM|LIKE|WHERE|LIMIT|IN)\b.+//ms;
1627
1628 $query =~ s/\A(SHOW(?:\s+\S+){1,2}).*\Z/$1/s;
1629 $query =~ s/\s+/ /g;
1630 PTDEBUG && _d($query);
1631 return $query;
1632 }
1633
1634 eval $QueryParser::data_def_stmts;
1635 eval $QueryParser::tbl_ident;
1636 my ( $dds ) = $query =~ /^\s*($QueryParser::data_def_stmts)\b/i;
1637 if ( $dds) {
1638 my ( $obj ) = $query =~ m/$dds.+(DATABASE|TABLE)\b/i;
1639 $obj = uc $obj if $obj;
1640 PTDEBUG && _d('Data def statment:', $dds, 'obj:', $obj);
1641 my ($db_or_tbl)
1642 = $query =~ m/(?:TABLE|DATABASE)\s+($QueryParser::tbl_ident)(\s+.*)?/i;
1643 PTDEBUG && _d('Matches db or table:', $db_or_tbl);
1644 return uc($dds . ($obj ? " $obj" : '')), $db_or_tbl;
1645 }
1646
1647 my @verbs = $query =~ m/\b($verbs)\b/gio;
1648 @verbs = do {
1649 my $last = '';
1650 grep { my $pass = $_ ne $last; $last = $_; $pass } map { uc } @verbs;
1651 };
1652
1653 if ( ($verbs[0] || '') eq 'SELECT' && @verbs > 1 ) {
1654 PTDEBUG && _d("False-positive verbs after SELECT:", @verbs[1..$#verbs]);
1655 my $union = grep { $_ eq 'UNION' } @verbs;
1656 @verbs = $union ? qw(SELECT UNION) : qw(SELECT);
1657 }
1658
1659 my $verb_str = join(q{ }, @verbs);
1660 return $verb_str;
1661}
1662
1663sub __distill_tables {
1664 my ( $self, $query, $table, %args ) = @_;
1665 my $qp = $args{QueryParser} || $self->{QueryParser};
1666 die "I need a QueryParser argument" unless $qp;
1667
1668 my @tables = map {
1669 $_ =~ s/`//g;
1670 $_ =~ s/(_?)[0-9]+/$1?/g;
1671 $_;
1672 } grep { defined $_ } $qp->get_tables($query);
1673
1674 push @tables, $table if $table;
1675
1676 @tables = do {
1677 my $last = '';
1678 grep { my $pass = $_ ne $last; $last = $_; $pass } @tables;
1679 };
1680
1681 return @tables;
1682}
1683
1684sub distill {
1685 my ( $self, $query, %args ) = @_;
1686
1687 if ( $args{generic} ) {
1688 my ($cmd, $arg) = $query =~ m/^(\S+)\s+(\S+)/;
1689 return '' unless $cmd;
1690 $query = (uc $cmd) . ($arg ? " $arg" : '');
1691 }
1692 else {
1693 my ($verbs, $table) = $self->distill_verbs($query, %args);
1694
1695 if ( $verbs && $verbs =~ m/^SHOW/ ) {
1696 my %alias_for = qw(
1697 SCHEMA DATABASE
1698 KEYS INDEX
1699 INDEXES INDEX
1700 );
1701 map { $verbs =~ s/$_/$alias_for{$_}/ } keys %alias_for;
1702 $query = $verbs;
1703 }
1704 else {
1705 my @tables = $self->__distill_tables($query, $table, %args);
1706 $query = join(q{ }, $verbs, @tables);
1707 }
1708 }
1709
1710 if ( $args{trf} ) {
1711 $query = $args{trf}->($query, %args);
1712 }
1713
1714 return $query;
1715}
1716
1717sub convert_to_select {
1718 my ( $self, $query ) = @_;
1719 return unless $query;
1720
1721 return if $query =~ m/=\s*\(\s*SELECT /i;
1722
1723 $query =~ s{
1724 \A.*?
1725 update(?:\s+(?:low_priority|ignore))?\s+(.*?)
1726 \s+set\b(.*?)
1727 (?:\s*where\b(.*?))?
1728 (limit\s*[0-9]+(?:\s*,\s*[0-9]+)?)?
1729 \Z
1730 }
1731 {__update_to_select($1, $2, $3, $4)}exsi
1732 || $query =~ s{
1733 \A.*?
1734 (?:insert(?:\s+ignore)?|replace)\s+
1735 .*?\binto\b(.*?)\(([^\)]+)\)\s*
1736 values?\s*(\(.*?\))\s*
1737 (?:\blimit\b|on\s+duplicate\s+key.*)?\s*
1738 \Z
1739 }
1740 {__insert_to_select($1, $2, $3)}exsi
1741 || $query =~ s{
1742 \A.*?
1743 (?:insert(?:\s+ignore)?|replace)\s+
1744 (?:.*?\binto)\b(.*?)\s*
1745 set\s+(.*?)\s*
1746 (?:\blimit\b|on\s+duplicate\s+key.*)?\s*
1747 \Z
1748 }
1749 {__insert_to_select_with_set($1, $2)}exsi
1750 || $query =~ s{
1751 \A.*?
1752 delete\s+(.*?)
1753 \bfrom\b(.*)
1754 \Z
1755 }
1756 {__delete_to_select($1, $2)}exsi;
1757 $query =~ s/\s*on\s+duplicate\s+key\s+update.*\Z//si;
1758 $query =~ s/\A.*?(?=\bSELECT\s*\b)//ism;
1759 return $query;
1760}
1761
1762sub convert_select_list {
1763 my ( $self, $query ) = @_;
1764 $query =~ s{
1765 \A\s*select(.*?)\bfrom\b
1766 }
1767 {$1 =~ m/\*/ ? "select 1 from" : "select isnull(coalesce($1)) from"}exi;
1768 return $query;
1769}
1770
1771sub __delete_to_select {
1772 my ( $delete, $join ) = @_;
1773 if ( $join =~ m/\bjoin\b/ ) {
1774 return "select 1 from $join";
1775 }
1776 return "select * from $join";
1777}
1778
1779sub __insert_to_select {
1780 my ( $tbl, $cols, $vals ) = @_;
1781 PTDEBUG && _d('Args:', @_);
1782 my @cols = split(/,/, $cols);
1783 PTDEBUG && _d('Cols:', @cols);
1784 $vals =~ s/^\(|\)$//g; # Strip leading/trailing parens
1785 my @vals = $vals =~ m/($quote_re|[^,]*${bal}[^,]*|[^,]+)/g;
1786 PTDEBUG && _d('Vals:', @vals);
1787 if ( @cols == @vals ) {
1788 return "select * from $tbl where "
1789 . join(' and ', map { "$cols[$_]=$vals[$_]" } (0..$#cols));
1790 }
1791 else {
1792 return "select * from $tbl limit 1";
1793 }
1794}
1795
1796sub __insert_to_select_with_set {
1797 my ( $from, $set ) = @_;
1798 $set =~ s/,/ and /g;
1799 return "select * from $from where $set ";
1800}
1801
1802sub __update_to_select {
1803 my ( $from, $set, $where, $limit ) = @_;
1804 return "select $set from $from "
1805 . ( $where ? "where $where" : '' )
1806 . ( $limit ? " $limit " : '' );
1807}
1808
1809sub wrap_in_derived {
1810 my ( $self, $query ) = @_;
1811 return unless $query;
1812 return $query =~ m/\A\s*select/i
1813 ? "select 1 from ($query) as x limit 1"
1814 : $query;
1815}
1816
1817sub _d {
1818 my ($package, undef, $line) = caller 0;
1819 @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
1820 map { defined $_ ? $_ : 'undef' }
1821 @_;
1822 print STDERR "# $package:$line $PID ", join(' ', @_), "\n";
1823}
1824
18251;
1826}
1827# ###########################################################################
1828# End QueryRewriter package
1829# ###########################################################################
1830
1831# ###########################################################################
1832# This is a combination of modules and programs in one -- a runnable module.
1833# http://www.perl.com/pub/a/2006/07/13/lightning-articles.html?page=last
1834# Or, look it up in the Camel book on pages 642 and 643 in the 3rd edition.
1835#
1836# Check at the end of this package for the call to main() which actually runs
1837# the program.
1838# ###########################################################################
1839package pt_fingerprint;
1840
1841use English qw(-no_match_vars);
1842use Data::Dumper;
1843$Data::Dumper::Indent = 1;
1844$OUTPUT_AUTOFLUSH = 1;
1845
1846use constant MKDEBUG => $ENV{MKDEBUG} || 0;
1847
1848sub main {
1849 @ARGV = @_; # set global ARGV for this package
1850
1851 # ##########################################################################
1852 # Get configuration information.
1853 # ##########################################################################
1854 my $o = new OptionParser();
1855 $o->get_specs();
1856 $o->get_opts();
1857 $o->usage_or_errors();
1858
1859 my $qp = new QueryParser();
1860 my $qr = new QueryRewriter(
1861 QueryParser => $qp,
1862 match_md5_checksums => $o->get('match-md5-checksums'),
1863 match_embedded_numbers => $o->get('match-embedded-numbers'),
1864 );
1865
1866 if ( $o->got('query') ) {
1867 print $qr->fingerprint($o->get('query')), "\n";
1868 }
1869 else {
1870 local $INPUT_RECORD_SEPARATOR = ";\n";
1871 while ( <> ) {
1872 my $query = $_;
1873 chomp $query;
1874 $query =~ s/^#.+$//mg;
1875 $query =~ s/^\s+//;
1876 next unless $query =~ m/^\w/;
1877 print $qr->fingerprint($query), "\n";
1878 }
1879 }
1880}
1881
1882# ############################################################################
1883# Run the program.
1884# ############################################################################
1885if ( !caller ) { exit main(@ARGV); }
1886
18871; # Because this is a module as well as a script.
1888
1889# #############################################################################
1890# Documentation.
1891# #############################################################################
1892
1893=pod
1894
1895=head1 NAME
1896
1897pt-fingerprint - Convert queries into fingerprints.
1898
1899=head1 SYNOPSIS
1900
1901Usage: pt-fingerprint [OPTIONS] [FILES]
1902
1903pt-fingerprint converts queries into fingerprints. With the --query
1904option, converts the option's value into a fingerprint. With no options, treats
1905command-line arguments as FILEs and reads and converts semicolon-separated
1906queries from the FILEs. When FILE is -, it read standard input.
1907
1908Convert a single query:
1909
1910 pt-fingerprint --query "select a, b, c from users where id = 500"
1911
1912Convert a file full of queries:
1913
1914 pt-fingerprint /path/to/file.txt
1915
1916=head1 RISKS
1917
1918The following section is included to inform users about the potential risks,
1919whether known or unknown, of using this tool. The two main categories of risks
1920are those created by the nature of the tool (e.g. read-only tools vs. read-write
1921tools) and those created by bugs.
1922
1923The pt-fingerprint tool simply reads data and transforms it, so risks are
1924minimal.
1925
1926See also L<"BUGS"> for more information on filing bugs and getting help.
1927
1928=head1 DESCRIPTION
1929
1930A query fingerprint is the abstracted form of a query, which makes it possible
1931to group similar queries together. Abstracting a query removes literal values,
1932normalizes whitespace, and so on. For example, consider these two queries:
1933
1934 SELECT name, password FROM user WHERE id='12823';
1935 select name, password from user
1936 where id=5;
1937
1938Both of those queries will fingerprint to
1939
1940 select name, password from user where id=?
1941
1942Once the query's fingerprint is known, we can then talk about a query as though
1943it represents all similar queries.
1944
1945Query fingerprinting accommodates a great many special cases, which have proven
1946necessary in the real world. For example, an IN list with 5 literals is really
1947equivalent to one with 4 literals, so lists of literals are collapsed to a
1948single one. If you want to understand more about how and why all of these cases
1949are handled, please review the test cases in the Subversion repository. If you
1950find something that is not fingerprinted properly, please submit a bug report
1951with a reproducible test case. Here is a list of transformations during
1952fingerprinting, which might not be exhaustive:
1953
1954=over
1955
1956=item *
1957
1958Group all SELECT queries from mysqldump together, even if they are against
1959different tables. Ditto for all of pt-table-checksum's checksum queries.
1960
1961=item *
1962
1963Shorten multi-value INSERT statements to a single VALUES() list.
1964
1965=item *
1966
1967Strip comments.
1968
1969=item *
1970
1971Abstract the databases in USE statements, so all USE statements are grouped
1972together.
1973
1974=item *
1975
1976Replace all literals, such as quoted strings. For efficiency, the code that
1977replaces literal numbers is somewhat non-selective, and might replace some
1978things as numbers when they really are not. Hexadecimal literals are also
1979replaced. NULL is treated as a literal. Numbers embedded in identifiers are
1980also replaced, so tables named similarly will be fingerprinted to the same
1981values (e.g. users_2009 and users_2010 will fingerprint identically).
1982
1983=item *
1984
1985Collapse all whitespace into a single space.
1986
1987=item *
1988
1989Lowercase the entire query.
1990
1991=item *
1992
1993Replace all literals inside of IN() and VALUES() lists with a single
1994placeholder, regardless of cardinality.
1995
1996=item *
1997
1998Collapse multiple identical UNION queries into a single one.
1999
2000=back
2001
2002=head1 OPTIONS
2003
2004This tool accepts additional command-line arguments. Refer to the
2005L<"SYNOPSIS"> and usage information for details.
2006
2007=over
2008
2009=item --config
2010
2011type: Array
2012
2013Read this comma-separated list of config files; if specified, this must be the
2014first option on the command line.
2015
2016=item --help
2017
2018Show help and exit.
2019
2020=item --match-embedded-numbers
2021
2022Match numbers embedded in words and replace as single values. This option
2023causes the tool to be more careful about matching numbers so that words
2024with numbers, like C<catch22> are matched and replaced as a single C<?>
2025placeholder. Otherwise the default number matching pattern will replace
2026C<catch22> as C<catch?>.
2027
2028This is helpful if database or table names contain numbers.
2029
2030=item --match-md5-checksums
2031
2032Match MD5 checksums and replace as single values. This option causes
2033the tool to be more careful about matching numbers so that MD5 checksums
2034like C<fbc5e685a5d3d45aa1d0347fdb7c4d35> are matched and replaced as a
2035single C<?> placeholder. Otherwise, the default number matching pattern will
2036replace C<fbc5e685a5d3d45aa1d0347fdb7c4d35> as C<fbc?>.
2037
2038=item --query
2039
2040type: string
2041
2042The query to convert into a fingerprint.
2043
2044=item --version
2045
2046Show version and exit.
2047
2048=back
2049
2050=head1 ENVIRONMENT
2051
2052The environment variable C<PTDEBUG> enables verbose debugging output to STDERR.
2053To enable debugging and capture all output to a file, run the tool like:
2054
2055 PTDEBUG=1 pt-fingerprint ... > FILE 2>&1
2056
2057Be careful: debugging output is voluminous and can generate several megabytes
2058of output.
2059
2060=head1 SYSTEM REQUIREMENTS
2061
2062You need Perl, DBI, DBD::mysql, and some core packages that ought to be
2063installed in any reasonably new version of Perl.
2064
2065=head1 BUGS
2066
2067For a list of known bugs, see L<http://www.percona.com/bugs/pt-fingerprint>.
2068
2069Please report bugs at L<https://bugs.launchpad.net/percona-toolkit>.
2070Include the following information in your bug report:
2071
2072=over
2073
2074=item * Complete command-line used to run the tool
2075
2076=item * Tool L<"--version">
2077
2078=item * MySQL version of all servers involved
2079
2080=item * Output from the tool including STDERR
2081
2082=item * Input files (log/dump/config files, etc.)
2083
2084=back
2085
2086If possible, include debugging output by running the tool with C<PTDEBUG>;
2087see L<"ENVIRONMENT">.
2088
2089=head1 DOWNLOADING
2090
2091Visit L<http://www.percona.com/software/percona-toolkit/> to download the
2092latest release of Percona Toolkit. Or, get the latest release from the
2093command line:
2094
2095 wget percona.com/get/percona-toolkit.tar.gz
2096
2097 wget percona.com/get/percona-toolkit.rpm
2098
2099 wget percona.com/get/percona-toolkit.deb
2100
2101You can also get individual tools from the latest release:
2102
2103 wget percona.com/get/TOOL
2104
2105Replace C<TOOL> with the name of any tool.
2106
2107=head1 AUTHORS
2108
2109Baron Schwartz and Daniel Nichter
2110
2111=head1 ABOUT PERCONA TOOLKIT
2112
2113This tool is part of Percona Toolkit, a collection of advanced command-line
2114tools developed by Percona for MySQL support and consulting. Percona Toolkit
2115was forked from two projects in June, 2011: Maatkit and Aspersa. Those
2116projects were created by Baron Schwartz and developed primarily by him and
2117Daniel Nichter, both of whom are employed by Percona. Visit
2118L<http://www.percona.com/software/> for more software developed by Percona.
2119
2120=head1 COPYRIGHT, LICENSE, AND WARRANTY
2121
2122This program is copyright 2011-2012 Percona Inc.
2123Feedback and improvements are welcome.
2124
2125THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
2126WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
2127MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
2128
2129This program is free software; you can redistribute it and/or modify it under
2130the terms of the GNU General Public License as published by the Free Software
2131Foundation, version 2; OR the Perl Artistic License. On UNIX and similar
2132systems, you can issue `man perlgpl' or `man perlartistic' to read these
2133licenses.
2134
2135You should have received a copy of the GNU General Public License along with
2136this program; if not, write to the Free Software Foundation, Inc., 59 Temple
2137Place, Suite 330, Boston, MA 02111-1307 USA.
2138
2139=head1 VERSION
2140
2141pt-fingerprint 2.1.1
2142
2143=cut
02144
=== modified file 'bin/pt-fk-error-logger'
--- bin/pt-fk-error-logger 2012-03-07 23:41:54 +0000
+++ bin/pt-fk-error-logger 2012-05-18 15:41:24 +0000
@@ -2460,6 +2460,6 @@
24602460
2461=head1 VERSION2461=head1 VERSION
24622462
2463pt-fk-error-logger 2.0.42463pt-fk-error-logger 2.1.1
24642464
2465=cut2465=cut
24662466
=== modified file 'bin/pt-heartbeat'
--- bin/pt-heartbeat 2012-03-07 23:41:54 +0000
+++ bin/pt-heartbeat 2012-05-18 15:41:24 +0000
@@ -73,6 +73,9 @@
73 dsn_table_dsn => $dsn_table_dsn,73 dsn_table_dsn => $dsn_table_dsn,
74 );74 );
75 }75 }
76 elsif ( $method =~ m/none/i ) {
77 PTDEBUG && _d('Not getting to slaves');
78 }
76 else {79 else {
77 die "Invalid --recursion-method: $method. Valid values are: "80 die "Invalid --recursion-method: $method. Valid values are: "
78 . "dsn=DSN, hosts, or processlist.\n";81 . "dsn=DSN, hosts, or processlist.\n";
@@ -87,6 +90,11 @@
87 my $dp = $args->{dsn_parser};90 my $dp = $args->{dsn_parser};
88 my $dsn = $args->{dsn};91 my $dsn = $args->{dsn};
8992
93 if ( lc($args->{method} || '') eq 'none' ) {
94 PTDEBUG && _d('Not recursing to slaves');
95 return;
96 }
97
90 my $dbh;98 my $dbh;
91 eval {99 eval {
92 $dbh = $args->{dbh} || $dp->get_dbh(100 $dbh = $args->{dbh} || $dp->get_dbh(
@@ -4131,9 +4139,10 @@
4131Possible methods are:4139Possible methods are:
41324140
4133 METHOD USES4141 METHOD USES
4134 =========== ================4142 =========== ==================
4135 processlist SHOW PROCESSLIST4143 processlist SHOW PROCESSLIST
4136 hosts SHOW SLAVE HOSTS4144 hosts SHOW SLAVE HOSTS
4145 none Do not find slaves
41374146
4138The processlist method is preferred because SHOW SLAVE HOSTS is not reliable.4147The processlist method is preferred because SHOW SLAVE HOSTS is not reliable.
4139However, the hosts method is required if the server uses a non-standard4148However, the hosts method is required if the server uses a non-standard
@@ -4395,6 +4404,6 @@
43954404
4396=head1 VERSION4405=head1 VERSION
43974406
4398pt-heartbeat 2.0.44407pt-heartbeat 2.1.1
43994408
4400=cut4409=cut
44014410
=== modified file 'bin/pt-index-usage'
--- bin/pt-index-usage 2012-03-07 23:41:54 +0000
+++ bin/pt-index-usage 2012-05-18 15:41:24 +0000
@@ -2669,19 +2669,58 @@
2669 return bless $self, $class;2669 return bless $self, $class;
2670}2670}
26712671
2672sub get_create_table {
2673 my ( $self, $dbh, $db, $tbl ) = @_;
2674 die "I need a dbh parameter" unless $dbh;
2675 die "I need a db parameter" unless $db;
2676 die "I need a tbl parameter" unless $tbl;
2677 my $q = $self->{Quoter};
2678
2679 my $new_sql_mode
2680 = '/*!40101 SET @OLD_SQL_MODE := @@SQL_MODE, '
2681 . q{@@SQL_MODE := REPLACE(REPLACE(@@SQL_MODE, 'ANSI_QUOTES', ''), ',,', ','), }
2682 . '@OLD_QUOTE := @@SQL_QUOTE_SHOW_CREATE, '
2683 . '@@SQL_QUOTE_SHOW_CREATE := 1 */';
2684
2685 my $old_sql_mode = '/*!40101 SET @@SQL_MODE := @OLD_SQL_MODE, '
2686 . '@@SQL_QUOTE_SHOW_CREATE := @OLD_QUOTE */';
2687
2688 PTDEBUG && _d($new_sql_mode);
2689 eval { $dbh->do($new_sql_mode); };
2690 PTDEBUG && $EVAL_ERROR && _d($EVAL_ERROR);
2691
2692 my $use_sql = 'USE ' . $q->quote($db);
2693 PTDEBUG && _d($dbh, $use_sql);
2694 $dbh->do($use_sql);
2695
2696 my $show_sql = "SHOW CREATE TABLE " . $q->quote($db, $tbl);
2697 PTDEBUG && _d($show_sql);
2698 my $href;
2699 eval { $href = $dbh->selectrow_hashref($show_sql); };
2700 if ( $EVAL_ERROR ) {
2701 PTDEBUG && _d($EVAL_ERROR);
2702
2703 PTDEBUG && _d($old_sql_mode);
2704 $dbh->do($old_sql_mode);
2705
2706 return;
2707 }
2708
2709 PTDEBUG && _d($old_sql_mode);
2710 $dbh->do($old_sql_mode);
2711
2712 my ($key) = grep { m/create (?:table|view)/i } keys %$href;
2713 if ( !$key ) {
2714 die "Error: no 'Create Table' or 'Create View' in result set from "
2715 . "$show_sql: " . Dumper($href);
2716 }
2717
2718 return $href->{$key};
2719}
2720
2672sub parse {2721sub parse {
2673 my ( $self, $ddl, $opts ) = @_;2722 my ( $self, $ddl, $opts ) = @_;
2674 return unless $ddl;2723 return unless $ddl;
2675 if ( ref $ddl eq 'ARRAY' ) {
2676 if ( lc $ddl->[0] eq 'table' ) {
2677 $ddl = $ddl->[1];
2678 }
2679 else {
2680 return {
2681 engine => 'VIEW',
2682 };
2683 }
2684 }
26852724
2686 if ( $ddl !~ m/CREATE (?:TEMPORARY )?TABLE `/ ) {2725 if ( $ddl !~ m/CREATE (?:TEMPORARY )?TABLE `/ ) {
2687 die "Cannot parse table definition; is ANSI quoting "2726 die "Cannot parse table definition; is ANSI quoting "
@@ -2988,41 +3027,31 @@
2988 return $ddl;3027 return $ddl;
2989}3028}
29903029
2991sub remove_secondary_indexes {3030sub get_table_status {
2992 my ( $self, $ddl ) = @_;3031 my ( $self, $dbh, $db, $like ) = @_;
2993 my $sec_indexes_ddl;3032 my $q = $self->{Quoter};
2994 my $tbl_struct = $self->parse($ddl);3033 my $sql = "SHOW TABLE STATUS FROM " . $q->quote($db);
29953034 my @params;
2996 if ( ($tbl_struct->{engine} || '') =~ m/InnoDB/i ) {3035 if ( $like ) {
2997 my $clustered_key = $tbl_struct->{clustered_key};3036 $sql .= ' LIKE ?';
2998 $clustered_key ||= '';3037 push @params, $like;
29993038 }
3000 my @sec_indexes = map {3039 PTDEBUG && _d($sql, @params);
3001 my $key_def = $_->{ddl};3040 my $sth = $dbh->prepare($sql);
3002 $key_def =~ s/([\(\)])/\\$1/g;3041 eval { $sth->execute(@params); };
3003 $ddl =~ s/\s+$key_def//i;3042 if ($EVAL_ERROR) {
30043043 PTDEBUG && _d($EVAL_ERROR);
3005 my $key_ddl = "ADD $_->{ddl}";3044 return;
3006 $key_ddl .= ',' unless $key_ddl =~ m/,$/;3045 }
3007 $key_ddl;3046 my @tables = @{$sth->fetchall_arrayref({})};
3008 }3047 @tables = map {
3009 grep { $_->{name} ne $clustered_key }3048 my %tbl; # Make a copy with lowercased keys
3010 values %{$tbl_struct->{keys}};3049 @tbl{ map { lc $_ } keys %$_ } = values %$_;
3011 PTDEBUG && _d('Secondary indexes:', Dumper(\@sec_indexes));3050 $tbl{engine} ||= $tbl{type} || $tbl{comment};
30123051 delete $tbl{type};
3013 if ( @sec_indexes ) {3052 \%tbl;
3014 $sec_indexes_ddl = join(' ', @sec_indexes);3053 } @tables;
3015 $sec_indexes_ddl =~ s/,$//;3054 return @tables;
3016 }
3017
3018 $ddl =~ s/,(\n\) )/$1/s;
3019 }
3020 else {
3021 PTDEBUG && _d('Not removing secondary indexes from',
3022 $tbl_struct->{engine}, 'table');
3023 }
3024
3025 return $ddl, $sec_indexes_ddl, $tbl_struct;
3026}3055}
30273056
3028sub _d {3057sub _d {
@@ -3912,7 +3941,7 @@
39123941
3913sub new {3942sub new {
3914 my ( $class, %args ) = @_;3943 my ( $class, %args ) = @_;
3915 my @required_args = qw(OptionParser Quoter);3944 my @required_args = qw(OptionParser TableParser Quoter);
3916 foreach my $arg ( @required_args ) {3945 foreach my $arg ( @required_args ) {
3917 die "I need a $arg argument" unless $args{$arg};3946 die "I need a $arg argument" unless $args{$arg};
3918 }3947 }
@@ -3921,8 +3950,19 @@
3921 die "I need either a dbh or file_itr argument"3950 die "I need either a dbh or file_itr argument"
3922 if (!$dbh && !$file_itr) || ($dbh && $file_itr);3951 if (!$dbh && !$file_itr) || ($dbh && $file_itr);
39233952
3953 my %resume;
3954 if ( my $table = $args{resume} ) {
3955 PTDEBUG && _d('Will resume from or after', $table);
3956 my ($db, $tbl) = $args{Quoter}->split_unquote($table);
3957 die "Resume table must be database-qualified: $table"
3958 unless $db && $tbl;
3959 $resume{db} = $db;
3960 $resume{tbl} = $tbl;
3961 }
3962
3924 my $self = {3963 my $self = {
3925 %args,3964 %args,
3965 resume => \%resume,
3926 filters => _make_filters(%args),3966 filters => _make_filters(%args),
3927 };3967 };
39283968
@@ -3983,9 +4023,19 @@
3983 return \%filters;4023 return \%filters;
3984}4024}
39854025
3986sub next_schema_object {4026sub next {
3987 my ( $self ) = @_;4027 my ( $self ) = @_;
39884028
4029 if ( !$self->{initialized} ) {
4030 $self->{initialized} = 1;
4031 if ( $self->{resume}->{tbl}
4032 && !$self->table_is_allowed(@{$self->{resume}}{qw(db tbl)}) ) {
4033 PTDEBUG && _d('Will resume after',
4034 join('.', @{$self->{resume}}{qw(db tbl)}));
4035 $self->{resume}->{after} = 1;
4036 }
4037 }
4038
3989 my $schema_obj;4039 my $schema_obj;
3990 if ( $self->{file_itr} ) {4040 if ( $self->{file_itr} ) {
3991 $schema_obj= $self->_iterate_files();4041 $schema_obj= $self->_iterate_files();
@@ -3995,19 +4045,13 @@
3995 }4045 }
39964046
3997 if ( $schema_obj ) {4047 if ( $schema_obj ) {
3998 if ( $schema_obj->{ddl} && $self->{TableParser} ) {
3999 $schema_obj->{tbl_struct}
4000 = $self->{TableParser}->parse($schema_obj->{ddl});
4001 }
4002
4003 delete $schema_obj->{ddl} unless $self->{keep_ddl};
4004
4005 if ( my $schema = $self->{Schema} ) {4048 if ( my $schema = $self->{Schema} ) {
4006 $schema->add_schema_object($schema_obj);4049 $schema->add_schema_object($schema_obj);
4007 }4050 }
4051 PTDEBUG && _d('Next schema object:',
4052 $schema_obj->{db}, $schema_obj->{tbl});
4008 }4053 }
40094054
4010 PTDEBUG && _d('Next schema object:', $schema_obj->{db}, $schema_obj->{tbl});
4011 return $schema_obj;4055 return $schema_obj;
4012}4056}
40134057
@@ -4033,7 +4077,8 @@
4033 my $db = $1; # XXX4077 my $db = $1; # XXX
4034 $db =~ s/^`//; # strip leading `4078 $db =~ s/^`//; # strip leading `
4035 $db =~ s/`$//; # and trailing `4079 $db =~ s/`$//; # and trailing `
4036 if ( $self->database_is_allowed($db) ) {4080 if ( $self->database_is_allowed($db)
4081 && $self->_resume_from_database($db) ) {
4037 $self->{db} = $db;4082 $self->{db} = $db;
4038 }4083 }
4039 }4084 }
@@ -4046,21 +4091,22 @@
4046 my ($tbl) = $chunk =~ m/$tbl_name/;4091 my ($tbl) = $chunk =~ m/$tbl_name/;
4047 $tbl =~ s/^\s*`//;4092 $tbl =~ s/^\s*`//;
4048 $tbl =~ s/`\s*$//;4093 $tbl =~ s/`\s*$//;
4049 if ( $self->table_is_allowed($self->{db}, $tbl) ) {4094 if ( $self->_resume_from_table($tbl)
4095 && $self->table_is_allowed($self->{db}, $tbl) ) {
4050 my ($ddl) = $chunk =~ m/^(?:$open_comment)?(CREATE TABLE.+?;)$/ms;4096 my ($ddl) = $chunk =~ m/^(?:$open_comment)?(CREATE TABLE.+?;)$/ms;
4051 if ( !$ddl ) {4097 if ( !$ddl ) {
4052 warn "Failed to parse CREATE TABLE from\n" . $chunk;4098 warn "Failed to parse CREATE TABLE from\n" . $chunk;
4053 next CHUNK;4099 next CHUNK;
4054 }4100 }
4055 $ddl =~ s/ \*\/;\Z/;/; # remove end of version comment4101 $ddl =~ s/ \*\/;\Z/;/; # remove end of version comment
40564102 my $tbl_struct = $self->{TableParser}->parse($ddl);
4057 my ($engine) = $ddl =~ m/\).*?(?:ENGINE|TYPE)=(\w+)/; 4103 if ( $self->engine_is_allowed($tbl_struct->{engine}) ) {
4058
4059 if ( !$engine || $self->engine_is_allowed($engine) ) {
4060 return {4104 return {
4061 db => $self->{db},4105 db => $self->{db},
4062 tbl => $tbl,4106 tbl => $tbl,
4063 ddl => $ddl,4107 name => $self->{Quoter}->quote($self->{db}, $tbl),
4108 ddl => $ddl,
4109 tbl_struct => $tbl_struct,
4064 };4110 };
4065 }4111 }
4066 }4112 }
@@ -4077,6 +4123,7 @@
4077sub _iterate_dbh {4123sub _iterate_dbh {
4078 my ( $self ) = @_;4124 my ( $self ) = @_;
4079 my $q = $self->{Quoter};4125 my $q = $self->{Quoter};
4126 my $tp = $self->{TableParser};
4080 my $dbh = $self->{dbh};4127 my $dbh = $self->{dbh};
4081 PTDEBUG && _d('Getting next schema object from dbh', $dbh);4128 PTDEBUG && _d('Getting next schema object from dbh', $dbh);
40824129
@@ -4090,7 +4137,9 @@
4090 }4137 }
40914138
4092 if ( !$self->{db} ) {4139 if ( !$self->{db} ) {
4093 $self->{db} = shift @{$self->{dbs}};4140 do {
4141 $self->{db} = shift @{$self->{dbs}};
4142 } until $self->_resume_from_database($self->{db});
4094 PTDEBUG && _d('Next database:', $self->{db});4143 PTDEBUG && _d('Next database:', $self->{db});
4095 return unless $self->{db};4144 return unless $self->{db};
4096 }4145 }
@@ -4103,8 +4152,9 @@
4103 }4152 }
4104 grep {4153 grep {
4105 my ($tbl, $type) = @$_;4154 my ($tbl, $type) = @$_;
4106 $self->table_is_allowed($self->{db}, $tbl)4155 (!$type || ($type ne 'VIEW'))
4107 && (!$type || ($type ne 'VIEW'));4156 && $self->_resume_from_table($tbl)
4157 && $self->table_is_allowed($self->{db}, $tbl);
4108 }4158 }
4109 @{$dbh->selectall_arrayref($sql)};4159 @{$dbh->selectall_arrayref($sql)};
4110 PTDEBUG && _d('Found', scalar @tbls, 'tables in database', $self->{db});4160 PTDEBUG && _d('Found', scalar @tbls, 'tables in database', $self->{db});
@@ -4112,27 +4162,15 @@
4112 }4162 }
41134163
4114 while ( my $tbl = shift @{$self->{tbls}} ) {4164 while ( my $tbl = shift @{$self->{tbls}} ) {
4115 my $engine;4165 my $ddl = $tp->get_create_table($dbh, $self->{db}, $tbl);
4116 if ( $self->{filters}->{'engines'}4166 my $tbl_struct = $tp->parse($ddl);
4117 || $self->{filters}->{'ignore-engines'} ) {4167 if ( $self->engine_is_allowed($tbl_struct->{engine}) ) {
4118 my $sql = "SHOW TABLE STATUS FROM " . $q->quote($self->{db})
4119 . " LIKE \'$tbl\'";
4120 PTDEBUG && _d($sql);
4121 $engine = $dbh->selectrow_hashref($sql)->{engine};
4122 PTDEBUG && _d($tbl, 'uses', $engine, 'engine');
4123 }
4124
4125
4126 if ( !$engine || $self->engine_is_allowed($engine) ) {
4127 my $ddl;
4128 if ( my $du = $self->{MySQLDump} ) {
4129 $ddl = $du->get_create_table($dbh, $q, $self->{db}, $tbl)->[1];
4130 }
4131
4132 return {4168 return {
4133 db => $self->{db},4169 db => $self->{db},
4134 tbl => $tbl,4170 tbl => $tbl,
4135 ddl => $ddl,4171 name => $q->quote($self->{db}, $tbl),
4172 ddl => $ddl,
4173 tbl_struct => $tbl_struct,
4136 };4174 };
4137 }4175 }
4138 }4176 }
@@ -4193,6 +4231,10 @@
41934231
4194 my $filter = $self->{filters};4232 my $filter = $self->{filters};
41954233
4234 if ( $db eq 'mysql' && ($tbl eq 'general_log' || $tbl eq 'slow_log') ) {
4235 return 0;
4236 }
4237
4196 if ( $filter->{'ignore-tables'}->{$tbl}4238 if ( $filter->{'ignore-tables'}->{$tbl}
4197 && ($filter->{'ignore-tables'}->{$tbl} eq '*'4239 && ($filter->{'ignore-tables'}->{$tbl} eq '*'
4198 || $filter->{'ignore-tables'}->{$tbl} eq $db) ) {4240 || $filter->{'ignore-tables'}->{$tbl} eq $db) ) {
@@ -4232,7 +4274,11 @@
42324274
4233sub engine_is_allowed {4275sub engine_is_allowed {
4234 my ( $self, $engine ) = @_;4276 my ( $self, $engine ) = @_;
4235 die "I need an engine argument" unless $engine;4277
4278 if ( !$engine ) {
4279 PTDEBUG && _d('No engine specified; allowing the table');
4280 return 1;
4281 }
42364282
4237 $engine = lc $engine;4283 $engine = lc $engine;
42384284
@@ -4252,6 +4298,40 @@
4252 return 1;4298 return 1;
4253}4299}
42544300
4301sub _resume_from_database {
4302 my ($self, $db) = @_;
4303
4304 return 1 unless $self->{resume}->{db};
4305
4306 if ( $db eq $self->{resume}->{db} ) {
4307 PTDEBUG && _d('At resume db', $db);
4308 delete $self->{resume}->{db};
4309 return 1;
4310 }
4311
4312 return 0;
4313}
4314
4315sub _resume_from_table {
4316 my ($self, $tbl) = @_;
4317
4318 return 1 unless $self->{resume}->{tbl};
4319
4320 if ( $tbl eq $self->{resume}->{tbl} ) {
4321 if ( !$self->{resume}->{after} ) {
4322 PTDEBUG && _d('Resuming from table', $tbl);
4323 delete $self->{resume}->{tbl};
4324 return 1;
4325 }
4326 else {
4327 PTDEBUG && _d('Resuming after table', $tbl);
4328 delete $self->{resume}->{tbl};
4329 }
4330 }
4331
4332 return 0;
4333}
4334
4255sub _d {4335sub _d {
4256 my ($package, undef, $line) = caller 0;4336 my ($package, undef, $line) = caller 0;
4257 @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }4337 @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; }
@@ -5144,13 +5224,11 @@
5144 dbh => $dbh,5224 dbh => $dbh,
5145 OptionParser => $o,5225 OptionParser => $o,
5146 Quoter => $q,5226 Quoter => $q,
5147 MySQLDump => $du,
5148 TableParser => $tp,5227 TableParser => $tp,
5149 Schema => $schema,5228 Schema => $schema,
5150 keep_ddl => 1,
5151 );5229 );
5152 TALBE:5230 TALBE:
5153 while ( my $tbl = $schema_itr->next_schema_object() ) {5231 while ( my $tbl = $schema_itr->next() ) {
5154 eval {5232 eval {
5155 my ($indexes) = $tp->get_keys($tbl->{ddl}, {version => $version});5233 my ($indexes) = $tp->get_keys($tbl->{ddl}, {version => $version});
5156 $iu->add_indexes(%$tbl, indexes=>$indexes);5234 $iu->add_indexes(%$tbl, indexes=>$indexes);
@@ -6186,6 +6264,6 @@
61866264
6187=head1 VERSION6265=head1 VERSION
61886266
6189pt-index-usage 2.0.46267pt-index-usage 2.1.1
61906268
6191=cut6269=cut
61926270
=== modified file 'bin/pt-ioprofile'
--- bin/pt-ioprofile 2012-05-16 17:31:10 +0000
+++ bin/pt-ioprofile 2012-05-18 15:41:24 +0000
@@ -1037,7 +1037,7 @@
10371037
1038=head1 VERSION1038=head1 VERSION
10391039
1040pt-ioprofile 2.0.41040pt-ioprofile 2.1.1
10411041
1042=cut1042=cut
10431043
10441044
=== modified file 'bin/pt-kill'
--- bin/pt-kill 2012-03-07 23:41:54 +0000
+++ bin/pt-kill 2012-05-18 15:41:24 +0000
@@ -4857,6 +4857,6 @@
48574857
4858=head1 VERSION4858=head1 VERSION
48594859
4860pt-kill 2.0.44860pt-kill 2.1.1
48614861
4862=cut4862=cut
48634863
=== modified file 'bin/pt-log-player'
--- bin/pt-log-player 2012-03-07 23:41:54 +0000
+++ bin/pt-log-player 2012-05-18 15:41:24 +0000
@@ -3604,6 +3604,6 @@
36043604
3605=head1 VERSION3605=head1 VERSION
36063606
3607pt-log-player 2.0.43607pt-log-player 2.1.1
36083608
3609=cut3609=cut
36103610
=== modified file 'bin/pt-mext'
--- bin/pt-mext 2012-05-16 17:31:10 +0000
+++ bin/pt-mext 2012-05-18 15:41:24 +0000
@@ -279,7 +279,7 @@
279279
280=head1 VERSION280=head1 VERSION
281281
282pt-mext 2.0.4282pt-mext 2.1.1
283283
284=cut284=cut
285285
286286
=== modified file 'bin/pt-mysql-summary'
--- bin/pt-mysql-summary 2012-05-16 17:31:10 +0000
+++ bin/pt-mysql-summary 2012-05-18 15:41:24 +0000
@@ -4,14 +4,421 @@
4# See "COPYRIGHT, LICENSE, AND WARRANTY" at the end of this file for legal4# See "COPYRIGHT, LICENSE, AND WARRANTY" at the end of this file for legal
5# notices and disclaimers.5# notices and disclaimers.
66
7set -u
8
9# ###########################################################################
10# log_warn_die package
11# This package is a copy without comments from the original. The original
12# with comments and its test file can be found in the Bazaar repository at,
13# lib/bash/log_warn_die.sh
14# t/lib/bash/log_warn_die.sh
15# See https://launchpad.net/percona-toolkit for more information.
16# ###########################################################################
17
18
19set -u
20
21PTFUNCNAME=""
22PTDEBUG="${PTDEBUG:-""}"
23EXIT_STATUS=0
24
25log() {
26 TS=$(date +%F-%T | tr :- _);
27 echo "$TS $*"
28}
29
30warn() {
31 log "$*" >&2
32 EXIT_STATUS=1
33}
34
35die() {
36 warn "$*"
37 exit 1
38}
39
40_d () {
41 [ "$PTDEBUG" ] && echo "# $PTFUNCNAME: $(log "$*")" >&2
42}
43
44# ###########################################################################
45# End log_warn_die package
46# ###########################################################################
47
48# ###########################################################################
49# parse_options package
50# This package is a copy without comments from the original. The original
51# with comments and its test file can be found in the Bazaar repository at,
52# lib/bash/parse_options.sh
53# t/lib/bash/parse_options.sh
54# See https://launchpad.net/percona-toolkit for more information.
55# ###########################################################################
56
57
58
59
60
61set -u
62
63ARGV="" # Non-option args (probably input files)
64EXT_ARGV="" # Everything after -- (args for an external command)
65HAVE_EXT_ARGV="" # Got --, everything else is put into EXT_ARGV
66OPT_ERRS=0 # How many command line option errors
67OPT_VERSION="" # If --version was specified
68OPT_HELP="" # If --help was specified
69PO_DIR="" # Directory with program option spec files
70
7usage() {71usage() {
8 if [ "${OPT_ERR}" ]; then72 local file="$1"
9 echo "${OPT_ERR}" >&273
10 fi74 local usage=$(grep '^Usage: ' "$file")
11 echo "Usage: pt-mysql-summary [MYSQL-OPTIONS]" >&275 echo $usage
12 echo "For more information, 'man pt-mysql-summary' or 'perldoc $0'" >&276 echo
13 exit 177 echo "For more information, 'man $TOOL' or 'perldoc $file'."
14}78}
79
80usage_or_errors() {
81 local file="$1"
82
83 if [ "$OPT_VERSION" ]; then
84 local version=$(grep '^pt-[^ ]\+ [0-9]' "$file")
85 echo "$version"
86 return 1
87 fi
88
89 if [ "$OPT_HELP" ]; then
90 usage "$file"
91 echo
92 echo "Command line options:"
93 echo
94 perl -e '
95 use strict;
96 use warnings FATAL => qw(all);
97 my $lcol = 20; # Allow this much space for option names.
98 my $rcol = 80 - $lcol; # The terminal is assumed to be 80 chars wide.
99 my $name;
100 while ( <> ) {
101 my $line = $_;
102 chomp $line;
103 if ( $line =~ s/^long:/ --/ ) {
104 $name = $line;
105 }
106 elsif ( $line =~ s/^desc:// ) {
107 $line =~ s/ +$//mg;
108 my @lines = grep { $_ }
109 $line =~ m/(.{0,$rcol})(?:\s+|\Z)/g;
110 if ( length($name) >= $lcol ) {
111 print $name, "\n", (q{ } x $lcol);
112 }
113 else {
114 printf "%-${lcol}s", $name;
115 }
116 print join("\n" . (q{ } x $lcol), @lines);
117 print "\n";
118 }
119 }
120 ' "$PO_DIR"/*
121 echo
122 echo "Options and values after processing arguments:"
123 echo
124 for opt in $(ls "$PO_DIR"); do
125 local varname="OPT_$(echo "$opt" | tr a-z- A-Z_)"
126 local varvalue="${!varname}"
127 printf -- " --%-30s %s" "$opt" "${varvalue:-(No value)}"
128 echo
129 done
130 return 1
131 fi
132
133 if [ $OPT_ERRS -gt 0 ]; then
134 echo
135 usage "$file"
136 return 1
137 fi
138
139 return 0
140}
141
142option_error() {
143 local err="$1"
144 OPT_ERRS=$(($OPT_ERRS + 1))
145 echo "$err" >&2
146}
147
148parse_options() {
149 local file="$1"
150 shift
151
152 ARGV=""
153 EXT_ARGV=""
154 HAVE_EXT_ARGV=""
155 OPT_ERRS=0
156 OPT_VERSION=""
157 OPT_HELP=""
158 PO_DIR="$TMPDIR/po"
159
160 if [ ! -d "$PO_DIR" ]; then
161 mkdir "$PO_DIR"
162 if [ $? -ne 0 ]; then
163 echo "Cannot mkdir $PO_DIR" >&2
164 exit 1
165 fi
166 fi
167
168 rm -rf "$PO_DIR"/*
169 if [ $? -ne 0 ]; then
170 echo "Cannot rm -rf $PO_DIR/*" >&2
171 exit 1
172 fi
173
174 _parse_pod "$file" # Parse POD into program option (po) spec files
175 _eval_po # Eval po into existence with default values
176
177 if [ $# -ge 2 ] && [ "$1" = "--config" ]; then
178 shift # --config
179 local user_config_files="$1"
180 shift # that ^
181 local IFS=","
182 for user_config_file in $user_config_files; do
183 _parse_config_files "$user_config_file"
184 done
185 else
186 _parse_config_files "/etc/percona-toolkit/percona-toolkit.conf" "/etc/percona-toolkit/$TOOL.conf" "$HOME/.percona-toolkit.conf" "$HOME/.$TOOL.conf"
187 fi
188
189 _parse_command_line "$@"
190}
191
192_parse_pod() {
193 local file="$1"
194
195 cat "$file" | PO_DIR="$PO_DIR" perl -ne '
196 BEGIN { $/ = ""; }
197 next unless $_ =~ m/^=head1 OPTIONS/;
198 while ( defined(my $para = <>) ) {
199 last if $para =~ m/^=head1/;
200 chomp;
201 if ( $para =~ m/^=item --(\S+)/ ) {
202 my $opt = $1;
203 my $file = "$ENV{PO_DIR}/$opt";
204 open my $opt_fh, ">", $file or die "Cannot open $file: $!";
205 print $opt_fh "long:$opt\n";
206 $para = <>;
207 chomp;
208 if ( $para =~ m/^[a-z ]+:/ ) {
209 map {
210 chomp;
211 my ($attrib, $val) = split(/: /, $_);
212 print $opt_fh "$attrib:$val\n";
213 } split(/; /, $para);
214 $para = <>;
215 chomp;
216 }
217 my ($desc) = $para =~ m/^([^?.]+)/;
218 print $opt_fh "desc:$desc.\n";
219 close $opt_fh;
220 }
221 }
222 last;
223 '
224}
225
226_eval_po() {
227 local IFS=":"
228 for opt_spec in "$PO_DIR"/*; do
229 local opt=""
230 local default_val=""
231 local neg=0
232 local size=0
233 while read key val; do
234 case "$key" in
235 long)
236 opt=$(echo $val | sed 's/-/_/g' | tr [:lower:] [:upper:])
237 ;;
238 default)
239 default_val="$val"
240 ;;
241 "short form")
242 ;;
243 type)
244 [ "$val" = "size" ] && size=1
245 ;;
246 desc)
247 ;;
248 negatable)
249 if [ "$val" = "yes" ]; then
250 neg=1
251 fi
252 ;;
253 *)
254 echo "Invalid attribute in $opt_spec: $line" >&2
255 exit 1
256 esac
257 done < "$opt_spec"
258
259 if [ -z "$opt" ]; then
260 echo "No long attribute in option spec $opt_spec" >&2
261 exit 1
262 fi
263
264 if [ $neg -eq 1 ]; then
265 if [ -z "$default_val" ] || [ "$default_val" != "yes" ]; then
266 echo "Option $opt_spec is negatable but not default: yes" >&2
267 exit 1
268 fi
269 fi
270
271 if [ $size -eq 1 -a -n "$default_val" ]; then
272 default_val=$(size_to_bytes $default_val)
273 fi
274
275 eval "OPT_${opt}"="$default_val"
276 done
277}
278
279_parse_config_files() {
280
281 for config_file in "$@"; do
282 test -f "$config_file" || continue
283
284 while read config_opt; do
285
286 echo "$config_opt" | grep '^[ ]*[^#]' >/dev/null 2>&1 || continue
287
288 config_opt="$(echo "$config_opt" | sed -e 's/^ *//g' -e 's/ *$//g' -e 's/[ ]*=[ ]*/=/' -e 's/[ ]*#.*$//')"
289
290 [ "$config_opt" = "" ] && continue
291
292 if ! [ "$HAVE_EXT_ARGV" ]; then
293 config_opt="--$config_opt"
294 fi
295
296 _parse_command_line "$config_opt"
297
298 done < "$config_file"
299
300 HAVE_EXT_ARGV="" # reset for each file
301
302 done
303}
304
305_parse_command_line() {
306 local opt=""
307 local val=""
308 local next_opt_is_val=""
309 local opt_is_ok=""
310 local opt_is_negated=""
311 local real_opt=""
312 local required_arg=""
313 local spec=""
314
315 for opt in "$@"; do
316 if [ "$opt" = "--" -o "$opt" = "----" ]; then
317 HAVE_EXT_ARGV=1
318 continue
319 fi
320 if [ "$HAVE_EXT_ARGV" ]; then
321 if [ "$EXT_ARGV" ]; then
322 EXT_ARGV="$EXT_ARGV $opt"
323 else
324 EXT_ARGV="$opt"
325 fi
326 continue
327 fi
328
329 if [ "$next_opt_is_val" ]; then
330 next_opt_is_val=""
331 if [ $# -eq 0 ] || [ $(expr "$opt" : "-") -eq 1 ]; then
332 option_error "$real_opt requires a $required_arg argument"
333 continue
334 fi
335 val="$opt"
336 opt_is_ok=1
337 else
338 if [ $(expr "$opt" : "-") -eq 0 ]; then
339 if [ -z "$ARGV" ]; then
340 ARGV="$opt"
341 else
342 ARGV="$ARGV $opt"
343 fi
344 continue
345 fi
346
347 real_opt="$opt"
348
349 if $(echo $opt | grep '^--no-' >/dev/null); then
350 opt_is_negated=1
351 opt=$(echo $opt | sed 's/^--no-//')
352 else
353 opt_is_negated=""
354 opt=$(echo $opt | sed 's/^-*//')
355 fi
356
357 if $(echo $opt | grep '^[a-z-][a-z-]*=' >/dev/null 2>&1); then
358 val="$(echo $opt | awk -F= '{print $2}')"
359 opt="$(echo $opt | awk -F= '{print $1}')"
360 fi
361
362 if [ -f "$TMPDIR/po/$opt" ]; then
363 spec="$TMPDIR/po/$opt"
364 else
365 spec=$(grep "^short form:-$opt\$" "$TMPDIR"/po/* | cut -d ':' -f 1)
366 if [ -z "$spec" ]; then
367 option_error "Unknown option: $real_opt"
368 continue
369 fi
370 fi
371
372 required_arg=$(cat "$spec" | awk -F: '/^type:/{print $2}')
373 if [ "$required_arg" ]; then
374 if [ "$val" ]; then
375 opt_is_ok=1
376 else
377 next_opt_is_val=1
378 fi
379 else
380 if [ "$val" ]; then
381 option_error "Option $real_opt does not take a value"
382 continue
383 fi
384 if [ "$opt_is_negated" ]; then
385 val=""
386 else
387 val="yes"
388 fi
389 opt_is_ok=1
390 fi
391 fi
392
393 if [ "$opt_is_ok" ]; then
394 opt=$(cat "$spec" | grep '^long:' | cut -d':' -f2 | sed 's/-/_/g' | tr [:lower:] [:upper:])
395
396 if grep "^type:size" "$spec" >/dev/null; then
397 val=$(size_to_bytes $val)
398 fi
399
400 eval "OPT_$opt"="'$val'"
401
402 opt=""
403 val=""
404 next_opt_is_val=""
405 opt_is_ok=""
406 opt_is_negated=""
407 real_opt=""
408 required_arg=""
409 spec=""
410 fi
411 done
412}
413
414size_to_bytes() {
415 local size="$1"
416 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")};'
417}
418
419# ###########################################################################
420# End parse_options package
421# ###########################################################################
15422
16# ###########################################################################423# ###########################################################################
17# tmpdir package424# tmpdir package
@@ -22,6 +429,9 @@
22# See https://launchpad.net/percona-toolkit for more information.429# See https://launchpad.net/percona-toolkit for more information.
23# ###########################################################################430# ###########################################################################
24431
432
433set -u
434
25TMPDIR=""435TMPDIR=""
26436
27mk_tmpdir() {437mk_tmpdir() {
@@ -51,27 +461,68 @@
51# End tmpdir package461# End tmpdir package
52# ###########################################################################462# ###########################################################################
53463
54# ########################################################################464# ###########################################################################
55# Some global setup is necessary for cross-platform compatibility, even465# alt_cmds package
56# when sourcing this script for testing purposes.466# This package is a copy without comments from the original. The original
57# ########################################################################467# with comments and its test file can be found in the Bazaar repository at,
58AP_AWK="$(which awk)"468# lib/bash/alt_cmds.sh
59which gawk >/dev/null 2>&1 && AP_AWK="$(which gawk)"469# t/lib/bash/alt_cmds.sh
60AP_SED="$(which sed)"470# See https://launchpad.net/percona-toolkit for more information.
61which gsed >/dev/null 2>&1 && AP_SED="$(which gsed)"471# ###########################################################################
62AP_GREP="$(which grep)"472
63which ggrep >/dev/null 2>&1 && AP_GREP="$(which ggrep)"473
64474set -u
65# ########################################################################475
66# Globals, helper functions476_seq() {
67# ########################################################################477 local i="$1"
68478 awk "BEGIN { for(i=1; i<=$i; i++) print i; }"
69# The awk code for fuzzy rounding. (It's used in a few places, so makes sense479}
70# not to duplicate). It fuzzy-rounds the variable named fuzzy_var. It goes in480
71# steps of 5, 10, 25, then repeats by a factor of 10 larger (50, 100, 250), and481_pidof() {
72# so on, until it finds a number that's large enough. The pattern is slightly482 local cmd="$1"
73# broken between the initial 1 and 50, because rounding to the nearest 2.5483 if ! pidof "$cmd" 2>/dev/null; then
74# doesn't seem right to me.484 ps -eo pid,ucomm | awk -v comm="$cmd" '$2 == comm { print $1 }'
485 fi
486}
487
488_lsof() {
489 local pid="$1"
490 if ! lsof -p $pid 2>/dev/null; then
491 /bin/ls -l /proc/$pid/fd 2>/dev/null
492 fi
493}
494
495
496
497_which() {
498 if [ -x /usr/bin/which ]; then
499 /usr/bin/which "$1" 2>/dev/null | awk '{print $1}'
500 elif which which 1>/dev/null 2>&1; then
501 which "$1" 2>/dev/null | awk '{print $1}'
502 else
503 echo "$1"
504 fi
505}
506
507# ###########################################################################
508# End alt_cmds package
509# ###########################################################################
510
511# ###########################################################################
512# report_formatting package
513# This package is a copy without comments from the original. The original
514# with comments and its test file can be found in the Bazaar repository at,
515# lib/bash/report_formatting.sh
516# t/lib/bash/report_formatting.sh
517# See https://launchpad.net/percona-toolkit for more information.
518# ###########################################################################
519
520
521set -u
522
523POSIXLY_CORRECT=1
524export POSIXLY_CORRECT
525
75fuzzy_formula='526fuzzy_formula='
76 rounded = 0;527 rounded = 0;
77 if (fuzzy_var <= 10 ) {528 if (fuzzy_var <= 10 ) {
@@ -94,108 +545,458 @@
94 factor = factor * 10;545 factor = factor * 10;
95 }'546 }'
96547
97# The temp files are for storing working results so we don't call commands many548fuzz () {
98# times (gives inconsistent results, maybe adds load on things I don't want to549 awk -v fuzzy_var="$1" "BEGIN { ${fuzzy_formula} print fuzzy_var;}"
99# such as RAID controllers). They must not exist -- if they did, someone would550}
100# symlink them to /etc/passwd and then run this program as root. Call this551
101# function with "rm" or "touch" as an argument.552fuzzy_pct () {
102temp_files() {553 local pct="$(awk -v one="$1" -v two="$2" 'BEGIN{ if (two > 0) { printf "%d", one/two*100; } else {print 0} }')";
103 for file in $TMPDIR/percona-toolkit{,-mysql-variables,-mysql-status,-innodb-status} \554 echo "$(fuzz "${pct}")%"
104 $TMPDIR/percona-toolkit{2,-mysql-databases,-mysql-processlist,-noncounters} \555}
105 $TMPDIR/percona-toolkit-mysql{dump,-slave};556
106 do
107 case "$1" in
108 touch)
109 if ! touch "${file}"; then
110 echo "I can't make my temp file ${file}";
111 exit 1;
112 fi
113 ;;
114 rm)
115 rm -f "${file}"
116 ;;
117 esac
118 done
119}
120
121# Print a space-padded string into $line. Then translate spaces to hashes, and
122# underscores to spaces. End result is a line of hashes with words at the
123# start.
124section () {557section () {
125 line="$(printf '#_%-60s' "$1_")"558 local str="$1"
126 line="${line// /#}"559 awk -v var="${str} _" 'BEGIN {
127 printf "%s\n" "${line//_/ }"560 line = sprintf("# %-60s", var);
128}561 i = index(line, "_");
129562 x = substr(line, i);
130# Print a "name | value" line.563 gsub(/[_ \t]/, "#", x);
131name_val() {564 printf("%s%s\n", substr(line, 1, i-1), x);
132 printf "%20s | %s\n" "$1" "$2"565 }'
133}566}
134567
135# Converts a value to units of power of 2. Optional precision is $2.568NAME_VAL_LEN=12
569name_val () {
570 printf "%+*s | %s\n" "${NAME_VAL_LEN}" "$1" "$2"
571}
572
136shorten() {573shorten() {
137 unit=k574 local num="$1"
138 size=1024575 local prec="${2:-2}"
139 if [ $1 -ge 1099511627776 ] ; then576 local div="${3:-1024}"
140 size=1099511627776577
141 unit=T578 echo "$num" | awk -v prec="$prec" -v div="$div" '
142 elif [ $1 -ge 1073741824 ] ; then579 {
143 size=1073741824580 num = $1;
144 unit=G581 unit = num >= 1125899906842624 ? "P" \
145 elif [ $1 -ge 1048576 ] ; then582 : num >= 1099511627776 ? "T" \
146 size=1048576583 : num >= 1073741824 ? "G" \
147 unit=M584 : num >= 1048576 ? "M" \
148 fi585 : num >= 1024 ? "k" \
149 result=$(echo "$1 $size ${2:-0}" | $AP_AWK '{printf "%." $3 "f", $1 / $2}')586 : "";
150 echo "${result}${unit}"587 while ( num >= div ) {
588 num /= div;
589 }
590 printf "%.*f%s", prec, num, unit;
591 }
592 '
151}593}
152594
153# Collapse a file into an aggregated list; file must be created with 'sort |
154# uniq -c'. This function is copy-pasted from 'summary' so see there for full
155# docs and tests.
156# ##############################################################################
157group_concat () {595group_concat () {
158 sed -e '{H; $!d}' -e 'x' -e 's/\n[[:space:]]*\([[:digit:]]*\)[[:space:]]*/, \1x/g' -e 's/[[:space:]][[:space:]]*/ /g' -e 's/, //' ${1}596 sed -e '{H; $!d;}' -e 'x' -e 's/\n[[:space:]]*\([[:digit:]]*\)[[:space:]]*/, \1x/g' -e 's/[[:space:]][[:space:]]*/ /g' -e 's/, //' "${1}"
159}597}
160598
161# Accepts a number of seconds, and outputs a d+h:m:s formatted string599# ###########################################################################
600# End report_formatting package
601# ###########################################################################
602
603# ###########################################################################
604# summary_common package
605# This package is a copy without comments from the original. The original
606# with comments and its test file can be found in the Bazaar repository at,
607# lib/bash/summary_common.sh
608# t/lib/bash/summary_common.sh
609# See https://launchpad.net/percona-toolkit for more information.
610# ###########################################################################
611
612
613set -u
614
615CMD_FILE="$( _which file 2>/dev/null )"
616CMD_NM="$( _which nm 2>/dev/null )"
617CMD_OBJDUMP="$( _which objdump 2>/dev/null )"
618
619get_nice_of_pid () {
620 local pid="$1"
621 local niceness="$(ps -p $pid -o nice | awk '$1 !~ /[^0-9]/ {print $1; exit}')"
622
623 if [ -n "${niceness}" ]; then
624 echo $niceness
625 else
626 local tmpfile="$TMPDIR/nice_through_c.tmp.c"
627 _d "Getting the niceness from ps failed, somehow. We are about to try this:"
628 cat <<EOC > "$tmpfile"
629
630int main(void) {
631 int priority = getpriority(PRIO_PROCESS, $pid);
632 if ( priority == -1 && errno == ESRCH ) {
633 return 1;
634 }
635 else {
636 printf("%d\\n", priority);
637 return 0;
638 }
639}
640
641EOC
642 local c_comp=$(_which gcc)
643 if [ -z "${c_comp}" ]; then
644 c_comp=$(_which cc)
645 fi
646 _d "$tmpfile: $( cat "$tmpfile" )"
647 _d "$c_comp -xc \"$tmpfile\" -o \"$tmpfile\" && eval \"$tmpfile\""
648 $c_comp -xc "$tmpfile" -o "$tmpfile" 2>/dev/null && eval "$tmpfile" 2>/dev/null
649 if [ $? -ne 0 ]; then
650 echo "?"
651 _d "Failed to get a niceness value for $pid"
652 fi
653 fi
654}
655
656get_oom_of_pid () {
657 local pid="$1"
658 local oom_adj=""
659
660 if [ -n "${pid}" -a -e /proc/cpuinfo ]; then
661 if [ -s "/proc/$pid/oom_score_adj" ]; then
662 oom_adj=$(cat "/proc/$pid/oom_score_adj" 2>/dev/null)
663 _d "For $pid, the oom value is $oom_adj, retreived from oom_score_adj"
664 else
665 oom_adj=$(cat "/proc/$pid/oom_adj" 2>/dev/null)
666 _d "For $pid, the oom value is $oom_adj, retreived from oom_adj"
667 fi
668 fi
669
670 if [ -n "${oom_adj}" ]; then
671 echo "${oom_adj}"
672 else
673 echo "?"
674 _d "Can't find the oom value for $pid"
675 fi
676}
677
678has_symbols () {
679 local executable="$(_which "$1")"
680 local has_symbols=""
681
682 if [ "${CMD_FILE}" ] \
683 && [ "$($CMD_FILE "${executable}" | grep 'not stripped' )" ]; then
684 has_symbols=1
685 elif [ "${CMD_NM}" ] \
686 || [ "${CMD_OBJDMP}" ]; then
687 if [ "${CMD_NM}" ] \
688 && [ !"$("${CMD_NM}" -- "${executable}" 2>&1 | grep 'File format not recognized' )" ]; then
689 if [ -z "$( $CMD_NM -- "${executable}" 2>&1 | grep ': no symbols' )" ]; then
690 has_symbols=1
691 fi
692 elif [ -z "$("${CMD_OBJDUMP}" -t -- "${executable}" | grep '^no symbols$' )" ]; then
693 has_symbols=1
694 fi
695 fi
696
697 if [ "${has_symbols}" ]; then
698 echo "Yes"
699 else
700 echo "No"
701 fi
702}
703
704setup_data_dir () {
705 local existing_dir="$1"
706 local data_dir=""
707 if [ -z "$existing_dir" ]; then
708 mkdir "$TMPDIR/data" || die "Cannot mkdir $TMPDIR/data"
709 data_dir="$TMPDIR/data"
710 else
711 if [ ! -d "$existing_dir" ]; then
712 mkdir "$existing_dir" || die "Cannot mkdir $existing_dir"
713 elif [ "$( ls -A "$existing_dir" )" ]; then
714 die "--save-samples directory isn't empty, halting."
715 fi
716 touch "$existing_dir/test" || die "Cannot write to $existing_dir"
717 rm "$existing_dir/test" || die "Cannot rm $existing_dir/test"
718 data_dir="$existing_dir"
719 fi
720 echo "$data_dir"
721}
722
723get_var () {
724 local varname="$1"
725 local file="$2"
726 awk -v pattern="${varname}" '$1 == pattern { if (length($2)) { len = length($1); print substr($0, len+index(substr($0, len+1), $2)) } }' "${file}"
727}
728
729# ###########################################################################
730# End summary_common package
731# ###########################################################################
732
733# ###########################################################################
734# collect_mysql_info package
735# This package is a copy without comments from the original. The original
736# with comments and its test file can be found in the Bazaar repository at,
737# lib/bash/collect_mysql_info.sh
738# t/lib/bash/collect_mysql_info.sh
739# See https://launchpad.net/percona-toolkit for more information.
740# ###########################################################################
741
742
743
744CMD_MYSQL="${CMD_MYSQL:-""}"
745CMD_MYSQLDUMP="${CMD_MYSQLDUMP:-""}"
746
747collect_mysqld_instances () {
748 local variables_file="$1"
749
750 local pids="$(_pidof mysqld)"
751
752 if [ -n "$pids" ]; then
753
754 for pid in $pids; do
755 local nice="$( get_nice_of_pid $pid )"
756 local oom="$( get_oom_of_pid $pid )"
757 echo "internal::nice_of_$pid $nice" >> "$variables_file"
758 echo "internal::oom_of_$pid $oom" >> "$variables_file"
759 done
760
761 pids="$(echo $pids | sed -e 's/ /,/g')"
762 ps ww -p "$pids" 2>/dev/null
763 else
764 echo "mysqld doesn't appear to be running"
765 fi
766
767}
768
769find_my_cnf_file() {
770 local file="$1"
771 local port="${2:-""}"
772
773 local cnf_file=""
774 if test -n "$port" && grep -- "/mysqld.*--port=$port" "${file}" >/dev/null 2>&1 ; then
775 cnf_file="$(grep -- "/mysqld.*--port=$port" "${file}" \
776 | awk 'BEGIN{RS=" "; FS="=";} $1 ~ /--defaults-file/ { print $2; }' \
777 | head -n1)"
778 else
779 cnf_file="$(grep '/mysqld' "${file}" \
780 | awk 'BEGIN{RS=" "; FS="=";} $1 ~ /--defaults-file/ { print $2; }' \
781 | head -n1)"
782 fi
783
784 if [ ! -n "${cnf_file}" ]; then
785 cnf_file="/etc/my.cnf";
786 if [ ! -e "${cnf_file}" ]; then
787 cnf_file="/etc/mysql/my.cnf";
788 fi
789 if [ ! -e "${cnf_file}" ]; then
790 cnf_file="/var/db/mysql/my.cnf";
791 fi
792 fi
793
794 echo "$cnf_file"
795}
796
797collect_mysql_variables () {
798 $CMD_MYSQL $EXT_ARGV -ss -e 'SHOW /*!40100 GLOBAL*/ VARIABLES'
799}
800
801collect_mysql_status () {
802 $CMD_MYSQL $EXT_ARGV -ss -e 'SHOW /*!50000 GLOBAL*/ STATUS'
803}
804
805collect_mysql_databases () {
806 $CMD_MYSQL $EXT_ARGV -ss -e 'SHOW DATABASES' 2>/dev/null
807}
808
809collect_mysql_plugins () {
810 $CMD_MYSQL $EXT_ARGV -ss -e 'SHOW PLUGINS' 2>/dev/null
811}
812
813collect_mysql_slave_status () {
814 $CMD_MYSQL $EXT_ARGV -ssE -e 'SHOW SLAVE STATUS' 2>/dev/null
815}
816
817collect_mysql_innodb_status () {
818 $CMD_MYSQL $EXT_ARGV -ssE -e 'SHOW /*!50000 ENGINE*/ INNODB STATUS' 2>/dev/null
819}
820
821collect_mysql_processlist () {
822 $CMD_MYSQL $EXT_ARGV -ssE -e 'SHOW FULL PROCESSLIST' 2>/dev/null
823}
824
825collect_mysql_users () {
826 $CMD_MYSQL $EXT_ARGV -ss -e 'SELECT COUNT(*), SUM(user=""), SUM(password=""), SUM(password NOT LIKE "*%") FROM mysql.user' 2>/dev/null
827}
828
829collect_master_logs_status () {
830 local master_logs_file="$1"
831 local master_status_file="$2"
832 $CMD_MYSQL $EXT_ARGV -ss -e 'SHOW MASTER LOGS' > "$master_logs_file" 2>/dev/null
833 $CMD_MYSQL $EXT_ARGV -ss -e 'SHOW MASTER STATUS' > "$master_status_file" 2>/dev/null
834}
835
836collect_mysql_deferred_status () {
837 local status_file="$1"
838 collect_mysql_status > "$TMPDIR/defer_gatherer"
839 join "$status_file" "$TMPDIR/defer_gatherer"
840}
841
842collect_internal_vars () {
843 local mysqld_executables="${1:-""}"
844
845 local FNV_64=""
846 if $CMD_MYSQL $EXT_ARGV -e 'SELECT FNV_64("a")' >/dev/null 2>&1; then
847 FNV_64="Enabled";
848 else
849 FNV_64="Unknown";
850 fi
851
852 local now="$($CMD_MYSQL $EXT_ARGV -ss -e 'SELECT NOW()')"
853 local user="$($CMD_MYSQL $EXT_ARGV -ss -e 'SELECT CURRENT_USER()')"
854 local trigger_count=$($CMD_MYSQL $EXT_ARGV -ss -e "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TRIGGERS" 2>/dev/null)
855
856 echo "pt-summary-internal-mysql_executable $CMD_MYSQL"
857 echo "pt-summary-internal-now $now"
858 echo "pt-summary-internal-user $user"
859 echo "pt-summary-internal-FNV_64 $FNV_64"
860 echo "pt-summary-internal-trigger_count $trigger_count"
861
862 if [ -e "$mysqld_executables" ]; then
863 local i=1
864 while read executable; do
865 echo "pt-summary-internal-mysqld_executable_${i} $(has_symbols "$executable")"
866 i=$(($i + 1))
867 done < "$mysqld_executables"
868 fi
869}
870
871get_mysqldump_for () {
872 local args="$1"
873 local dbtodump="${2:-"--all-databases"}"
874
875 $CMD_MYSQLDUMP $EXT_ARGV --no-data --skip-comments \
876 --skip-add-locks --skip-add-drop-table --compact \
877 --skip-lock-all-tables --skip-lock-tables --skip-set-charset \
878 ${args} --databases $( local IFS=,; echo ${dbtodump})
879}
880
881get_mysqldump_args () {
882 local file="$1"
883 local trg_arg=""
884
885 if $CMD_MYSQLDUMP --help --verbose 2>&1 | grep triggers >/dev/null; then
886 trg_arg="--routines"
887 fi
888
889 if [ "${trg_arg}" ]; then
890 local triggers="--skip-triggers"
891 local trg=$(get_var "pt-summary-internal-trigger_count" "$file" )
892 if [ -n "${trg}" ] && [ "${trg}" -gt 0 ]; then
893 triggers="--triggers"
894 fi
895 trg_arg="${trg_arg} ${triggers}";
896 fi
897 echo "${trg_arg}"
898}
899
900collect_mysqld_executables () {
901 local mysqld_instances="$1"
902
903 for pid in $( grep '/mysqld' "$mysqld_instances" | awk '/^ .*[0-9]/{print $1}' ); do
904 ps -o cmd -p $pid | sed -e 's/^\(.*mysqld\) .*/\1/' | grep -v '^CMD$'
905 done | sort -u
906}
907
908collect_mysql_info () {
909 local dir="$1"
910
911 collect_mysql_variables > "$dir/mysql-variables"
912 collect_mysql_status > "$dir/mysql-status"
913 collect_mysql_databases > "$dir/mysql-databases"
914 collect_mysql_plugins > "$dir/mysql-plugins"
915 collect_mysql_slave_status > "$dir/mysql-slave"
916 collect_mysql_innodb_status > "$dir/innodb-status"
917 collect_mysql_processlist > "$dir/mysql-processlist"
918 collect_mysql_users > "$dir/mysql-users"
919
920 collect_mysqld_instances "$dir/mysql-variables" > "$dir/mysqld-instances"
921 collect_mysqld_executables "$dir/mysqld-instances" > "$dir/mysqld-executables"
922
923 local binlog="$(get_var log_bin "$dir/mysql-variables")"
924 if [ "${binlog}" ]; then
925 collect_master_logs_status "$dir/mysql-master-logs" "$dir/mysql-master-status"
926 fi
927
928 local uptime="$(get_var Uptime "$dir/mysql-status")"
929 local current_time="$($CMD_MYSQL $EXT_ARGV -ss -e \
930 "SELECT LEFT(NOW() - INTERVAL ${uptime} SECOND, 16)")"
931
932 local port="$(get_var port "$dir/mysql-variables")"
933 local cnf_file="$(find_my_cnf_file "$dir/mysqld-instances" ${port})"
934
935 cat "$cnf_file" > "$dir/mysql-config-file"
936
937 local pid_file="$(get_var "pid_file" "$dir/mysql-variables")"
938 local pid_file_exists=""
939 [ -e "${pid_file}" ] && pid_file_exists=1
940 echo "pt-summary-internal-pid_file_exists $pid_file_exists" >> "$dir/mysql-variables"
941
942 echo "pt-summary-internal-current_time $current_time" >> "$dir/mysql-variables"
943 echo "pt-summary-internal-Config_File_path $cnf_file" >> "$dir/mysql-variables"
944 collect_internal_vars "$dir/mysqld-executables" >> "$dir/mysql-variables"
945
946 if [ -n "${OPT_DATABASES}" ]; then
947 local trg_arg="$( get_mysqldump_args "$dir/mysql-variables" )"
948 get_mysqldump_for "${trg_arg}" "${OPT_DATABASES}" > "$dir/mysqldump"
949 fi
950
951 (
952 sleep $OPT_SLEEP
953 collect_mysql_deferred_status "$dir/mysql-status" > "$dir/mysql-status-defer"
954 ) &
955 _d "Forked child is $!"
956}
957
958# ###########################################################################
959# End collect_mysql_info package
960# ###########################################################################
961
962# ###########################################################################
963# report_mysql_info package
964# This package is a copy without comments from the original. The original
965# with comments and its test file can be found in the Bazaar repository at,
966# lib/bash/report_mysql_info.sh
967# t/lib/bash/report_mysql_info.sh
968# See https://launchpad.net/percona-toolkit for more information.
969# ###########################################################################
970
971
972set -u
973POSIXLY_CORRECT=1
974
162secs_to_time () {975secs_to_time () {
163 echo "$1" | $AP_AWK '{976 awk -v sec="$1" 'BEGIN {
164 printf( "%d+%02d:%02d:%02d", $1 / 86400, ($1 % 86400) / 3600, ($1 % 3600) / 60, $1 % 60);977 printf( "%d+%02d:%02d:%02d", sec / 86400, (sec % 86400) / 3600, (sec % 3600) / 60, sec % 60);
165 }'978 }'
166}979}
167980
168# gets a value from $TMPDIR/percona-toolkit-mysql-variables. Returns zero if it doesn't
169# exist.
170get_var () {
171 v="$($AP_AWK "\$1 ~ /^$1$/ { print \$2 }" $TMPDIR/percona-toolkit-mysql-variables)"
172 echo "${v:-0}"
173}
174
175# Returns true if a variable exists
176var_exists () {
177 $AP_GREP "$1" $TMPDIR/percona-toolkit-mysql-variables >/dev/null 2>&1;
178}
179
180# Returns "Enabled", "Disabled", or "Not Supported" depending on whether the
181# variable exists and is ON or enabled. You can pass 2nd and 3rd variables to
182# control whether the variable should be 'gt' (numeric greater than) or 'eq'
183# (string equal) to some value.
184feat_on() {981feat_on() {
185 if var_exists $1 ; then982 local file="$1"
186 var="$($AP_AWK "\$1 ~ /^$1$/ { print \$2 }" $TMPDIR/percona-toolkit-mysql-variables)"983 local varname="$2"
984 [ -e "$file" ] || return
985
986 if [ "$( get_var "$varname" "${file}" )" ]; then
987 local var="$(awk "\$1 ~ /^$2$/ { print \$2 }" $file)"
187 if [ "${var}" = "ON" ]; then988 if [ "${var}" = "ON" ]; then
188 echo "Enabled"989 echo "Enabled"
189 elif [ "${var}" = "OFF" -o "${var}" = "0" -o -z "${var}" ]; then990 elif [ "${var}" = "OFF" -o "${var}" = "0" -o -z "${var}" ]; then
190 echo "Disabled"991 echo "Disabled"
191 elif [ "$2" = "ne" ]; then992 elif [ "$3" = "ne" ]; then
192 if [ "${var}" != "$3" ]; then993 if [ "${var}" != "$4" ]; then
193 echo "Enabled"994 echo "Enabled"
194 else995 else
195 echo "Disabled"996 echo "Disabled"
196 fi997 fi
197 elif [ "$2" = "gt" ]; then998 elif [ "$3" = "gt" ]; then
198 if [ "${var}" -gt "$3" ]; then999 if [ "${var}" -gt "$4" ]; then
199 echo "Enabled"1000 echo "Enabled"
200 else1001 else
201 echo "Disabled"1002 echo "Disabled"
@@ -210,128 +1011,124 @@
210 fi1011 fi
211}1012}
2121013
213# gets a value from $TMPDIR/percona-toolkit-mysql-status. Returns zero if it doesn't1014get_table_cache () {
214# exist.1015 local file="$1"
215get_stat () {1016
216 v="$($AP_AWK "\$1 ~ /^$1$/ { print \$2 }" $TMPDIR/percona-toolkit-mysql-status)"1017 [ -e "$file" ] || return
217 echo "${v:-0}"1018
218}1019 local table_cache=""
2191020 if [ "$( get_var table_open_cache "${file}" )" ]; then
220# Does fuzzy rounding: rounds to nearest interval, but the interval gets larger1021 table_cache="$(get_var table_open_cache "${file}")"
221# as the number gets larger. This is to make things easier to diff.1022 else
222fuzz () {1023 table_cache="$(get_var table_cache "${file}")"
223 echo $1 | $AP_AWK "{fuzzy_var=\$1; ${fuzzy_formula} print fuzzy_var;}"1024 fi
224}1025 echo ${table_cache:-0}
2251026}
226# Fuzzy computes the percent that $1 is of $21027
227fuzzy_pct () {1028get_plugin_status () {
228 pct=$(echo $1 $2 | $AP_AWK '{ if ($2 > 0) { printf "%d", $1/$2*100; } else {print 0} }');1029 local file="$1"
229 echo "$(fuzz ${pct})%"1030 local plugin="$2"
230}1031
2311032 local status="$(grep -w "$plugin" "$file" | awk '{ print $2 }')"
232# ##############################################################################1033
233# Functions for parsing specific files and getting desired info from them.1034 echo ${status:-"Not found"}
234# These are called from within main() and are separated so they can be tested1035}
235# easily. The calling convention is that the data they need to run is prepared1036
236# first by putting it into $TMPDIR/percona-toolkit. Then code that's testing1037
237# just needs to put sample data into $TMPDIR/percona-toolkit and call it.1038_NO_FALSE_NEGATIVES=""
238# ##############################################################################
239
240# Parses the output of 'ps -e -o args | $AP_GREP mysqld' or 'ps auxww...'
241# which should be in $TMPDIR/percona-toolkit.
242parse_mysqld_instances () {1039parse_mysqld_instances () {
243 local file=$11040 local file="$1"
1041 local variables_file="$2"
1042
244 local socket=${socket:-""}1043 local socket=${socket:-""}
245 local port=${port:-""}1044 local port=${port:-""}
246 local datadir=${datadir:-""}1045 local datadir="${datadir:-""}"
247 echo " Port Data Directory Socket"1046
248 echo " ===== ========================== ======"1047 [ -e "$file" ] || return
249 $AP_GREP '/mysqld ' $file | while read line; do1048
1049 echo " Port Data Directory Nice OOM Socket"
1050 echo " ===== ========================== ==== === ======"
1051
1052 grep '/mysqld ' "$file" | while read line; do
1053 local pid=$(echo "$line" | awk '{print $1;}')
250 for word in ${line}; do1054 for word in ${line}; do
251 # Some grep doesn't have -o, so I have to pull out the words I want by1055 if echo "${word}" | grep -- "--socket=" > /dev/null; then
252 # looking at each word
253 if echo "${word}" | $AP_GREP -- "--socket=" > /dev/null; then
254 socket="$(echo "${word}" | cut -d= -f2)"1056 socket="$(echo "${word}" | cut -d= -f2)"
255 fi1057 fi
256 if echo "${word}" | $AP_GREP -- "--port=" > /dev/null; then1058 if echo "${word}" | grep -- "--port=" > /dev/null; then
257 port="$(echo "${word}" | cut -d= -f2)"1059 port="$(echo "${word}" | cut -d= -f2)"
258 fi1060 fi
259 if echo "${word}" | $AP_GREP -- "--datadir=" > /dev/null; then1061 if echo "${word}" | grep -- "--datadir=" > /dev/null; then
260 datadir="$(echo "${word}" | cut -d= -f2)"1062 datadir="$(echo "${word}" | cut -d= -f2)"
261 fi1063 fi
262 done1064 done
263 printf " %5s %-26s %s\n" "${port}" "${datadir}" "${socket}"1065 local nice="$(get_var "internal::nice_of_$pid" "$variables_file")"
1066 local oom="$(get_var "internal::oom_of_$pid" "$variables_file")"
1067 if [ -n "${_NO_FALSE_NEGATIVES}" ]; then
1068 nice="?"
1069 oom="?"
1070 fi
1071 printf " %5s %-26s %-4s %-3s %s\n" "${port}" "${datadir}" "${nice:-"?"}" "${oom:-"?"}" "${socket}"
264 done1072 done
265}1073}
2661074
267# Tries to find the my.cnf file by examining 'ps' output, which should be in
268# $TMPDIR/percona-toolkit. You have to specify the port for the instance you are
269# interested in, in case there are multiple instances.
270find_my_cnf_file() {
271 local file=$1
272 local port=${2:-""}
273 if test -n "$port" && $AP_GREP -- "/mysqld.*--port=$port" $file >/dev/null 2>&1 ; then
274 $AP_GREP -- "/mysqld.*--port=$port" $file \
275 | $AP_AWK 'BEGIN{RS=" "; FS="=";} $1 ~ /--defaults-file/ { print $2; }' \
276 | head -n1
277 else
278 $AP_GREP '/mysqld' $file \
279 | $AP_AWK 'BEGIN{RS=" "; FS="=";} $1 ~ /--defaults-file/ { print $2; }' \
280 | head -n1
281 fi
282}
283
284# Gets the MySQL system time. Uses input from $TMPDIR/percona-toolkit-mysql-variables.
285get_mysql_timezone () {1075get_mysql_timezone () {
286 tz="$(get_var time_zone)"1076 local file="$1"
1077
1078 [ -e "$file" ] || return
1079
1080 local tz="$(get_var time_zone "${file}")"
287 if [ "${tz}" = "SYSTEM" ]; then1081 if [ "${tz}" = "SYSTEM" ]; then
288 tz="$(get_var system_time_zone)"1082 tz="$(get_var system_time_zone "${file}")"
289 fi1083 fi
290 echo "${tz}"1084 echo "${tz}"
291}1085}
2921086
293# Gets the MySQL system version. Uses input from $TMPDIR/percona-toolkit-mysql-variables.
294get_mysql_version () {1087get_mysql_version () {
295 name_val Version "$(get_var version) $(get_var version_comment)"1088 local file="$1"
296 name_val "Built On" "$(get_var version_compile_os) $(get_var version_compile_machine)"1089
1090 name_val Version "$(get_var version "${file}") $(get_var version_comment "${file}")"
1091 name_val "Built On" "$(get_var version_compile_os "${file}") $(get_var version_compile_machine "${file}")"
297}1092}
2981093
299# Gets the system start and uptime in human readable format. Last restart date
300# should be in $TMPDIR/percona-toolkit.
301get_mysql_uptime () {1094get_mysql_uptime () {
302 local file=$11095 local uptime="$1"
303 restart="$(cat $file)"1096 local restart="$2"
304 uptime="$(get_stat Uptime)"
305 uptime="$(secs_to_time ${uptime})"1097 uptime="$(secs_to_time ${uptime})"
306 echo "${restart} (up ${uptime})"1098 echo "${restart} (up ${uptime})"
307}1099}
3081100
309# Summarizes the output of SHOW MASTER LOGS, which is in $TMPDIR/percona-toolkit
310summarize_binlogs () {1101summarize_binlogs () {
311 local file=$11102 local file="$1"
312 name_val "Binlogs" $(wc -l $file)1103
313 name_val "Zero-Sized" $($AP_GREP -c '\<0$' $file)1104 [ -e "$file" ] || return
314 size=$($AP_AWK '{t += $2} END{printf "%0.f\n", t}' $file)1105
1106 local size="$(awk '{t += $2} END{printf "%0.f\n", t}' "$file")"
1107 name_val "Binlogs" $(wc -l "$file")
1108 name_val "Zero-Sized" $(grep -c '\<0$' "$file")
315 name_val "Total Size" $(shorten ${size} 1)1109 name_val "Total Size" $(shorten ${size} 1)
316}1110}
3171111
318# Print out binlog_do_db and binlog_ignore_db1112format_users () {
1113 local file="$1"
1114 [ -e "$file" ] || return
1115 awk '{printf "%d users, %d anon, %d w/o pw, %d old pw\n", $1, $2, $3, $4}' "${file}"
1116}
1117
319format_binlog_filters () {1118format_binlog_filters () {
320 local file=$11119 local file="$1"
321 name_val "binlog_do_db" $(cut -f3 $file)1120 [ -e "$file" ] || return
322 name_val "binlog_ignore_db" $(cut -f4 $file)1121 name_val "binlog_do_db" "$(cut -f3 "$file")"
1122 name_val "binlog_ignore_db" "$(cut -f4 "$file")"
323}1123}
3241124
325# Takes as input a file that has two samples of SHOW STATUS, columnized next to
326# each other. These should be in $TMPDIR/percona-toolkit. Outputs fuzzy-ed numbers:
327# absolute, all-time per second, and per-second over the interval between the
328# samples. Omits any rows that are all zeroes.
329format_status_variables () {1125format_status_variables () {
330 local file=$11126 local file="$1"
331 # First, figure out the intervals.1127 [ -e "$file" ] || return
332 utime1=$($AP_AWK '/Uptime /{print $2}' $file);1128
333 utime2=$($AP_AWK '/Uptime /{print $3}' $file);1129 utime1="$(awk '/Uptime /{print $2}' "$file")";
334 ${AP_AWK} "1130 utime2="$(awk '/Uptime /{print $3}' "$file")";
1131 awk "
335 BEGIN {1132 BEGIN {
336 utime1 = ${utime1};1133 utime1 = ${utime1};
337 utime2 = ${utime2};1134 utime2 = ${utime2};
@@ -367,28 +1164,22 @@
367 printf(format, \$1, perday, persec, nowsec);1164 printf(format, \$1, perday, persec, nowsec);
368 }1165 }
369 }1166 }
370 }" $file1167 }" "$file"
371}1168}
3721169
373# Slices the processlist a bunch of different ways. The processlist should be
374# created with the \G flag so it's vertical.
375# The parsing is a bit awkward because different
376# versions of awk have limitations like "too many fields on line xyz". So we
377# use 'cut' to shorten the lines. We count all things into temporary variables
378# for each process in the processlist, and when we hit the Info: line which
379# ought to be the last line in the process, we decide what to do with the temp
380# variables. If we're summarizing Command, we count everything; otherwise, only
381# non-Sleep processes get counted towards the sum and max of Time.
382summarize_processlist () {1170summarize_processlist () {
383 local file=$11171 local file="$1"
1172
1173 [ -e "$file" ] || return
1174
384 for param in Command User Host db State; do1175 for param in Command User Host db State; do
385 echo1176 echo
386 printf ' %-30s %8s %7s %9s %9s\n' \1177 printf ' %-30s %8s %7s %9s %9s\n' \
387 "${param}" "COUNT(*)" Working "SUM(Time)" "MAX(Time)"1178 "${param}" "COUNT(*)" Working "SUM(Time)" "MAX(Time)"
388 echo " ------------------------------" \1179 echo " ------------------------------" \
389 "-------- ------- --------- ---------"1180 "-------- ------- --------- ---------"
390 cut -c1-80 $file \1181 cut -c1-80 "$file" \
391 | $AP_AWK "1182 | awk "
392 \$1 == \"${param}:\" {1183 \$1 == \"${param}:\" {
393 p = substr(\$0, index(\$0, \":\") + 2);1184 p = substr(\$0, index(\$0, \":\") + 2);
394 if ( index(p, \":\") > 0 ) {1185 if ( index(p, \":\") > 0 ) {
@@ -428,22 +1219,21 @@
428 echo1219 echo
429}1220}
4301221
431# Pretty-prints the my.cnf file, which should be in $TMPDIR/percona-toolkit. It's super
432# annoying, but some *modern* versions of awk don't support POSIX character
433# sets in regular expressions, like [[:space:]] (looking at you, Debian). So
434# the below patterns contain [<space><tab>] and must remain that way.
435pretty_print_cnf_file () {1222pretty_print_cnf_file () {
436 local file=$11223 local file="$1"
437 $AP_AWK '1224
1225 [ -e "$file" ] || return
1226
1227 awk '
438 BEGIN {1228 BEGIN {
439 FS="="1229 FS="="
440 }1230 }
441 /^ *[a-zA-Z[]/ {1231 /^[ \t]*[a-zA-Z[]/ {
442 if ($2) {1232 if (length($2)) {
443 gsub(/^[ ]*/, "", $1);1233 gsub(/^[ \t]*/, "", $1);
444 gsub(/^[ ]*/, "", $2);1234 gsub(/^[ \t]*/, "", $2);
445 gsub(/[ ]*$/, "", $1);1235 gsub(/[ \t]*$/, "", $1);
446 gsub(/[ ]*$/, "", $2);1236 gsub(/[ \t]*$/, "", $2);
447 printf("%-35s = %s\n", $1, $2);1237 printf("%-35s = %s\n", $1, $2);
448 }1238 }
449 else if ( $0 ~ /\[/ ) {1239 else if ( $0 ~ /\[/ ) {
@@ -453,11 +1243,12 @@
453 else {1243 else {
454 print $1;1244 print $1;
455 }1245 }
456 }' $file1246 }' "$file"
457}1247}
4581248
459find_checkpoint_age() {1249find_checkpoint_age() {
460 $AP_AWK '1250 local file="$1"
1251 awk '
461 /Log sequence number/{1252 /Log sequence number/{
462 if ( $5 ) {1253 if ( $5 ) {
463 lsn = $5 + ($4 * 4294967296);1254 lsn = $5 + ($4 * 4294967296);
@@ -474,11 +1265,15 @@
474 print lsn - $4;1265 print lsn - $4;
475 }1266 }
476 }1267 }
477 ' "$@"1268 ' "$file"
478}1269}
4791270
480find_pending_io_reads() {1271find_pending_io_reads() {
481 $AP_AWK '1272 local file="$1"
1273
1274 [ -e "$file" ] || return
1275
1276 awk '
482 /Pending normal aio reads/ {1277 /Pending normal aio reads/ {
483 normal_aio_reads = substr($5, 1, index($5, ","));1278 normal_aio_reads = substr($5, 1, index($5, ","));
484 }1279 }
@@ -495,11 +1290,15 @@
495 printf "%d buf pool reads, %d normal AIO", reads, normal_aio_reads;1290 printf "%d buf pool reads, %d normal AIO", reads, normal_aio_reads;
496 printf ", %d ibuf AIO, %d preads", ibuf_aio_reads, preads;1291 printf ", %d ibuf AIO, %d preads", ibuf_aio_reads, preads;
497 }1292 }
498 ' "${1}"1293 ' "${file}"
499}1294}
5001295
501find_pending_io_writes() {1296find_pending_io_writes() {
502 $AP_AWK '1297 local file="$1"
1298
1299 [ -e "$file" ] || return
1300
1301 awk '
503 /aio writes/ {1302 /aio writes/ {
504 aio_writes = substr($NF, 1, index($NF, ","));1303 aio_writes = substr($NF, 1, index($NF, ","));
505 }1304 }
@@ -522,11 +1321,15 @@
522 END {1321 END {
523 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;1322 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;
524 }1323 }
525 ' "${1}"1324 ' "${file}"
526}1325}
5271326
528find_pending_io_flushes() {1327find_pending_io_flushes() {
529 $AP_AWK '1328 local file="$1"
1329
1330 [ -e "$file" ] || return
1331
1332 awk '
530 /Pending flushes/ {1333 /Pending flushes/ {
531 log_flushes = substr($5, 1, index($5, ";"));1334 log_flushes = substr($5, 1, index($5, ";"));
532 buf_pool = $NF;1335 buf_pool = $NF;
@@ -534,13 +1337,17 @@
534 END {1337 END {
535 printf "%d buf pool, %d log", buf_pool, log_flushes;1338 printf "%d buf pool, %d log", buf_pool, log_flushes;
536 }1339 }
537 ' "${1}"1340 ' "${file}"
538}1341}
5391342
540summarize_undo_log_entries() {1343summarize_undo_log_entries() {
541 $AP_GREP 'undo log entries' "$1" \1344 local file="$1"
542 | $AP_SED -e 's/^.*undo log entries \([0-9]*\)/\1/' \1345
543 | $AP_AWK '1346 [ -e "$file" ] || return
1347
1348 grep 'undo log entries' "${file}" \
1349 | sed -e 's/^.*undo log entries \([0-9]*\)/\1/' \
1350 | awk '
544 {1351 {
545 count++;1352 count++;
546 sum += $1;1353 sum += $1;
@@ -554,7 +1361,11 @@
554}1361}
5551362
556find_max_trx_time() {1363find_max_trx_time() {
557 $AP_AWK '1364 local file="$1"
1365
1366 [ -e "$file" ] || return
1367
1368 awk '
558 BEGIN {1369 BEGIN {
559 max = 0;1370 max = 0;
560 }1371 }
@@ -570,59 +1381,69 @@
570 }1381 }
571 END {1382 END {
572 print max;1383 print max;
573 }' "$@"1384 }' "${file}"
574}1385}
5751386
576# Summarizes various things about InnoDB status that are not easy to see by eye.1387find_transation_states () {
1388 local file="$1"
1389 local tmpfile="$TMPDIR/find_transation_states.tmp"
1390
1391 [ -e "$file" ] || return
1392
1393 awk -F, '/^---TRANSACTION/{print $2}' "${file}" \
1394 | sed -e 's/ [0-9]* sec.*//' \
1395 | sort \
1396 | uniq -c > "${tmpfile}"
1397 group_concat "${tmpfile}"
1398}
1399
577format_innodb_status () {1400format_innodb_status () {
578 local file=$11401 local file=$1
579 name_val "Checkpoint Age" $(shorten $(find_checkpoint_age "${file}"))1402
1403 [ -e "$file" ] || return
1404
1405 name_val "Checkpoint Age" "$(shorten $(find_checkpoint_age "${file}") 0)"
580 name_val "InnoDB Queue" "$(awk '/queries inside/{print}' "${file}")"1406 name_val "InnoDB Queue" "$(awk '/queries inside/{print}' "${file}")"
581 name_val "Oldest Transaction" "$(find_max_trx_time "${file}") Seconds";1407 name_val "Oldest Transaction" "$(find_max_trx_time "${file}") Seconds";
582 name_val "History List Len" $(awk '/History list length/{print $4}' "${file}")1408 name_val "History List Len" "$(awk '/History list length/{print $4}' "${file}")"
583 name_val "Read Views" $(awk '/read views open inside/{print $1}' "${file}")1409 name_val "Read Views" "$(awk '/read views open inside/{print $1}' "${file}")"
584 name_val "Undo Log Entries" "$(summarize_undo_log_entries "${file}")"1410 name_val "Undo Log Entries" "$(summarize_undo_log_entries "${file}")"
585 name_val "Pending I/O Reads" "$(find_pending_io_reads "${file}")"1411 name_val "Pending I/O Reads" "$(find_pending_io_reads "${file}")"
586 name_val "Pending I/O Writes" "$(find_pending_io_writes "${file}")"1412 name_val "Pending I/O Writes" "$(find_pending_io_writes "${file}")"
587 name_val "Pending I/O Flushes" "$(find_pending_io_flushes "${file}")"1413 name_val "Pending I/O Flushes" "$(find_pending_io_flushes "${file}")"
588 $AP_AWK -F, '/^---TRANSACTION/{print $2}' "${file}" \1414 name_val "Transaction States" "$(find_transation_states "${file}" )"
589 | $AP_SED -e 's/ [0-9]* sec.*//' | sort | uniq -c > $TMPDIR/percona-toolkit21415 if grep 'TABLE LOCK table' "${file}" >/dev/null ; then
590 name_val "Transaction States" "$(group_concat $TMPDIR/percona-toolkit2)"
591 if $AP_GREP 'TABLE LOCK table' "${file}" >/dev/null ; then
592 echo "Tables Locked"1416 echo "Tables Locked"
593 $AP_AWK '/^TABLE LOCK table/{print $4}' "${file}" \1417 awk '/^TABLE LOCK table/{print $4}' "${file}" \
594 | sort | uniq -c | sort -rn1418 | sort | uniq -c | sort -rn
595 fi1419 fi
596 if $AP_GREP 'has waited at' "${file}" > /dev/null ; then1420 if grep 'has waited at' "${file}" > /dev/null ; then
597 echo "Semaphore Waits"1421 echo "Semaphore Waits"
598 $AP_GREP 'has waited at' "${file}" | cut -d' ' -f6-8 \1422 grep 'has waited at' "${file}" | cut -d' ' -f6-8 \
599 | sort | uniq -c | sort -rn1423 | sort | uniq -c | sort -rn
600 fi1424 fi
601 if $AP_GREP 'reserved it in mode' "${file}" > /dev/null; then1425 if grep 'reserved it in mode' "${file}" > /dev/null; then
602 echo "Semaphore Holders"1426 echo "Semaphore Holders"
603 $AP_AWK '/has reserved it in mode/{1427 awk '/has reserved it in mode/{
604 print substr($0, 1 + index($0, "("), index($0, ")") - index($0, "(") - 1);1428 print substr($0, 1 + index($0, "("), index($0, ")") - index($0, "(") - 1);
605 }' "${file}" | sort | uniq -c | sort -rn1429 }' "${file}" | sort | uniq -c | sort -rn
606 fi1430 fi
607 if $AP_GREP -e 'Mutex at' -e 'lock on' "${file}" >/dev/null 2>&1; then1431 if grep -e 'Mutex at' -e 'lock on' "${file}" >/dev/null 2>&1; then
608 echo "Mutexes/Locks Waited For"1432 echo "Mutexes/Locks Waited For"
609 $AP_GREP -e 'Mutex at' -e 'lock on' "${file}" | $AP_SED -e 's/^[XS]-//' -e 's/,.*$//' \1433 grep -e 'Mutex at' -e 'lock on' "${file}" | sed -e 's/^[XS]-//' -e 's/,.*$//' \
610 | sort | uniq -c | sort -rn1434 | sort | uniq -c | sort -rn
611 fi1435 fi
612}1436}
6131437
614# Summarizes per-database statistics for a bunch of different things: count of
615# tables, views, etc. $1 is the file name. $2 is the database name; if none,
616# then there should be multiple databases.
617format_overall_db_stats () {1438format_overall_db_stats () {
618 local file=$11439 local file="$1"
1440 local tmpfile="$TMPDIR/format_overall_db_stats.tmp"
1441
1442 [ -e "$file" ] || return
1443
619 echo1444 echo
620 # We keep counts of everything in an associative array keyed by db name, and1445 awk '
621 # what it is. The num_dbs counter is to ensure sort order is consistent when
622 # we run the awk commands following this one.
623 $AP_AWK '
624 BEGIN {1446 BEGIN {
625 # In case there is no USE statement in the file.
626 db = "{chosen}";1447 db = "{chosen}";
627 num_dbs = 0;1448 num_dbs = 0;
628 }1449 }
@@ -634,7 +1455,6 @@
634 }1455 }
635 }1456 }
636 /^CREATE TABLE/ {1457 /^CREATE TABLE/ {
637 # Handle single-DB dumps, where there is no USE statement.
638 if (num_dbs == 0) {1458 if (num_dbs == 0) {
639 num_dbs = 1;1459 num_dbs = 1;
640 db_seen[db] = 1;1460 db_seen[db] = 1;
@@ -674,15 +1494,13 @@
674 printf fmt, db, counts[db ",tables"], counts[db ",views"], counts[db ",sps"], counts[db ",trg"], counts[db ",func"], counts[db ",fk"], counts[db ",partn"];1494 printf fmt, db, counts[db ",tables"], counts[db ",views"], counts[db ",sps"], counts[db ",trg"], counts[db ",func"], counts[db ",fk"], counts[db ",partn"];
675 }1495 }
676 }1496 }
677 ' $file > $TMPDIR/percona-toolkit1497 ' "$file" > "$tmpfile"
678 head -n2 $TMPDIR/percona-toolkit1498 head -n2 "$tmpfile"
679 tail -n +3 $TMPDIR/percona-toolkit | sort1499 tail -n +3 "$tmpfile" | sort
6801500
681 echo1501 echo
682 # Now do the summary of engines per DB1502 awk '
683 $AP_AWK '
684 BEGIN {1503 BEGIN {
685 # In case there is no USE statement in the file.
686 db = "{chosen}";1504 db = "{chosen}";
687 num_dbs = 0;1505 num_dbs = 0;
688 num_engines = 0;1506 num_engines = 0;
@@ -695,7 +1513,6 @@
695 }1513 }
696 }1514 }
697 /^\) ENGINE=/ {1515 /^\) ENGINE=/ {
698 # Handle single-DB dumps, where there is no USE statement.
699 if (num_dbs == 0) {1516 if (num_dbs == 0) {
700 num_dbs = 1;1517 num_dbs = 1;
701 db_seen[db] = 1;1518 db_seen[db] = 1;
@@ -734,16 +1551,13 @@
734 print "";1551 print "";
735 }1552 }
736 }1553 }
737 ' $file > $TMPDIR/percona-toolkit1554 ' "$file" > "$tmpfile"
738 head -n1 $TMPDIR/percona-toolkit1555 head -n1 "$tmpfile"
739 tail -n +2 $TMPDIR/percona-toolkit | sort1556 tail -n +2 "$tmpfile" | sort
7401557
741 echo1558 echo
742 # Now do the summary of index types per DB. Careful -- index is a reserved1559 awk '
743 # word in awk.
744 $AP_AWK '
745 BEGIN {1560 BEGIN {
746 # In case there is no USE statement in the file.
747 db = "{chosen}";1561 db = "{chosen}";
748 num_dbs = 0;1562 num_dbs = 0;
749 num_idxes = 0;1563 num_idxes = 0;
@@ -756,7 +1570,6 @@
756 }1570 }
757 }1571 }
758 /KEY/ {1572 /KEY/ {
759 # Handle single-DB dumps, where there is no USE statement.
760 if (num_dbs == 0) {1573 if (num_dbs == 0) {
761 num_dbs = 1;1574 num_dbs = 1;
762 db_seen[db] = 1;1575 db_seen[db] = 1;
@@ -807,15 +1620,13 @@
807 print "";1620 print "";
808 }1621 }
809 }1622 }
810 ' $file > $TMPDIR/percona-toolkit1623 ' "$file" > "$tmpfile"
811 head -n1 $TMPDIR/percona-toolkit1624 head -n1 "$tmpfile"
812 tail -n +2 $TMPDIR/percona-toolkit | sort1625 tail -n +2 "$tmpfile" | sort
8131626
814 echo1627 echo
815 # Now do the summary of datatypes per DB1628 awk '
816 $AP_AWK '
817 BEGIN {1629 BEGIN {
818 # In case there is no USE statement in the file.
819 db = "{chosen}";1630 db = "{chosen}";
820 num_dbs = 0;1631 num_dbs = 0;
821 num_types = 0;1632 num_types = 0;
@@ -828,7 +1639,6 @@
828 }1639 }
829 }1640 }
830 /^ `/ {1641 /^ `/ {
831 # Handle single-DB dumps, where there is no USE statement.
832 if (num_dbs == 0) {1642 if (num_dbs == 0) {
833 num_dbs = 1;1643 num_dbs = 1;
834 db_seen[db] = 1;1644 db_seen[db] = 1;
@@ -898,106 +1708,195 @@
898 print "";1708 print "";
899 }1709 }
900 }1710 }
901 ' $file > $TMPDIR/percona-toolkit1711 ' "$file" > "$tmpfile"
902 hdr=$($AP_GREP -n Database $TMPDIR/percona-toolkit | cut -d: -f1);1712 local hdr=$(grep -n Database "$tmpfile" | cut -d: -f1);
903 head -n${hdr} $TMPDIR/percona-toolkit1713 head -n${hdr} "$tmpfile"
904 tail -n +$((${hdr} + 1)) $TMPDIR/percona-toolkit | sort1714 tail -n +$((${hdr} + 1)) "$tmpfile" | sort
905 echo1715 echo
906}1716}
9071717
908# ##############################################################################
909# The main() function is called at the end of the script. This makes it
910# testable. Major bits of parsing are separated into functions for testability.
911# ##############################################################################
912main() {
913
914 # Begin by setting the $PATH to include some common locations that are not
915 # always in the $PATH, including the "sbin" locations. On SunOS systems,
916 # prefix the path with the location of more sophisticated utilities.
917 export PATH="${PATH}:/usr/local/bin:/usr/bin:/bin:/usr/libexec"
918 export PATH="${PATH}:/usr/mysql/bin/:/usr/local/sbin:/usr/sbin:/sbin"
919 export PATH="/usr/gnu/bin/:/usr/xpg4/bin/:${PATH}"
920
921 # Set up temporary files.
922 mk_tmpdir
923 temp_files "rm"
924 temp_files "touch"
925
926 # ########################################################################
927 # Header for the whole thing, table of discovered instances
928 # ########################################################################
929 section Percona_Toolkit_MySQL_Summary_Report
930 name_val "System time" "`date -u +'%F %T UTC'` (local TZ: `date +'%Z %z'`)"
931 section Instances
932 ps auxww 2>/dev/null | $AP_GREP mysqld > $TMPDIR/percona-toolkit
933 parse_mysqld_instances $TMPDIR/percona-toolkit
934
935 # ########################################################################
936 # Fetch some basic info so we can start
937 # ########################################################################
938 mysql "$@" -ss -e 'SELECT CURRENT_USER()' > $TMPDIR/percona-toolkit
939 if [ "$?" != "0" ]; then
940 echo "Cannot connect to mysql, please specify command-line options."
941 temp_files "rm"
942 rm_tmpdir
943 exit 1
944 fi
945 user="$(cat $TMPDIR/percona-toolkit)";
946 mysql "$@" -ss -e 'SHOW /*!40100 GLOBAL*/ VARIABLES' > $TMPDIR/percona-toolkit-mysql-variables
947 mysql "$@" -ss -e 'SHOW /*!50000 GLOBAL*/ STATUS' > $TMPDIR/percona-toolkit-mysql-status
948 mysql "$@" -ss -e 'SHOW DATABASES' > $TMPDIR/percona-toolkit-mysql-databases 2>/dev/null
949 mysql "$@" -ssE -e 'SHOW SLAVE STATUS' > $TMPDIR/percona-toolkit-mysql-slave 2>/dev/null
950 mysql "$@" -ssE -e 'SHOW /*!50000 ENGINE*/ INNODB STATUS' > $TMPDIR/percona-toolkit-innodb-status 2>/dev/null
951 mysql "$@" -ssE -e 'SHOW FULL PROCESSLIST' > $TMPDIR/percona-toolkit-mysql-processlist 2>/dev/null
952 now="$(mysql "$@" -ss -e 'SELECT NOW()')"
953 port="$(get_var port)"
954
955 # ########################################################################
956 # General date, hostname, etc
957 # ########################################################################
958 section "Report_On_Port_${port}"
959 name_val User "${user}"
960 name_val Time "${now} ($(get_mysql_timezone))"
961 name_val Hostname "$(get_var hostname)"
962 get_mysql_version
963
964 uptime="$(get_stat Uptime)"
965 mysql "$@" -ss -e "SELECT LEFT(NOW() - INTERVAL ${uptime} SECOND, 16)" \
966 > $TMPDIR/percona-toolkit
967 name_val Started "$(get_mysql_uptime $TMPDIR/percona-toolkit)"
968
969 name_val Databases "$($AP_GREP -c . $TMPDIR/percona-toolkit-mysql-databases)"
970 name_val Datadir "$(get_var datadir)"
971 procs="$(get_stat Threads_connected)"
972 procr="$(get_stat Threads_running)"
973 name_val Processes "$(fuzz ${procs}) connected, $(fuzz ${procr}) running"
974 if [ -s $TMPDIR/percona-toolkit-mysql-slave ]; then slave=""; else slave="not "; fi
975 slavecount=$($AP_GREP -c 'Binlog Dump' $TMPDIR/percona-toolkit-mysql-processlist)
976 name_val Replication "Is ${slave}a slave, has ${slavecount} slaves connected"
977
978 # TODO move this into a section with other files: error log, slow log and
979 # show the sizes
980 pid_file="$(get_var pid_file)"
981 [ -e "${pid_file}" ] && PID_EXISTS="(exists)"
982 name_val Pidfile "${pid_file} ${PID_EXISTS:-(does not exist)}"
983
984 # ########################################################################
985 # Processlist, sliced several different ways
986 # ########################################################################
987 section Processlist
988 summarize_processlist $TMPDIR/percona-toolkit-mysql-processlist
989
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches