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

Proposed by Daniel Nichter
Status: Merged
Merged at revision: 241
Proposed branch: lp:~percona-toolkit-dev/percona-toolkit/fix-sleep-bug-979092
Merge into: lp:percona-toolkit/2.0
Diff against target: 66121 lines (+52092/-8394) (has conflicts)
341 files modified
Changelog (+21/-0)
MANIFEST (+2/-0)
Makefile.PL (+1/-1)
bin/pt-align (+1/-1)
bin/pt-archiver (+1/-1)
bin/pt-config-diff (+1/-1)
bin/pt-deadlock-logger (+1/-1)
bin/pt-diskstats (+1/-1)
bin/pt-duplicate-key-checker (+171/-92)
bin/pt-fifo-split (+1/-1)
bin/pt-find (+1/-1)
bin/pt-fingerprint (+2143/-0)
bin/pt-fk-error-logger (+1/-1)
bin/pt-heartbeat (+1/-1)
bin/pt-index-usage (+170/-92)
bin/pt-ioprofile (+1/-1)
bin/pt-kill (+1/-1)
bin/pt-log-player (+1/-1)
bin/pt-mext (+1/-1)
bin/pt-mysql-summary (+2079/-679)
bin/pt-online-schema-change (+4617/-1243)
bin/pt-pmp (+1/-1)
bin/pt-query-advisor (+248/-372)
bin/pt-query-digest (+9/-9)
bin/pt-show-grants (+1/-1)
bin/pt-sift (+1/-1)
bin/pt-slave-delay (+1/-1)
bin/pt-slave-find (+1/-1)
bin/pt-slave-restart (+1/-1)
bin/pt-stalk (+1/-1)
bin/pt-summary (+2019/-836)
bin/pt-table-checksum (+191/-81)
bin/pt-table-sync (+554/-405)
bin/pt-table-usage (+7223/-0)
bin/pt-tcp-model (+1/-1)
bin/pt-trend (+1/-1)
bin/pt-upgrade (+1/-1)
bin/pt-variable-advisor (+1/-1)
bin/pt-visual-explain (+1/-1)
config/deb/changelog (+23/-0)
config/sphinx-build/conf.py (+2/-2)
docs/percona-toolkit.pod (+12/-4)
docs/release_notes.rst (+48/-0)
lib/CleanupTask.pm (+7/-2)
lib/Cxn.pm (+1/-1)
lib/MySQLStatusWaiter.pm (+31/-1)
lib/NibbleIterator.pm (+166/-28)
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 (+117/-0)
lib/bash/report_mysql_info.sh (+1391/-0)
lib/bash/report_system_info.sh (+1050/-0)
lib/bash/summary_common.sh (+155/-0)
t/lib/CleanupTask.t (+18/-1)
t/lib/MySQLStatusWaiter.t (+41/-0)
t/lib/NibbleIterator.t (+44/-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 (+109/-0)
t/lib/bash/report_mysql_info.sh (+734/-0)
t/lib/bash/report_system_info.sh (+1568/-0)
t/lib/bash/summary_common.sh (+79/-0)
t/lib/samples/SchemaIterator/all-dbs-tbls-5.0.txt (+421/-0)
t/lib/samples/SchemaIterator/all-dbs-tbls.txt (+486/-0)
t/lib/samples/SchemaIterator/resume-from-ignored-sakila-payment-5.0.txt (+52/-0)
t/lib/samples/SchemaIterator/resume-from-ignored-sakila-payment.txt (+49/-0)
t/lib/samples/SchemaIterator/resume-from-sakila-payment-5.0.txt (+70/-0)
t/lib/samples/SchemaIterator/resume-from-sakila-payment.txt (+66/-0)
t/lib/samples/slowlogs/slow002.txt (+16/-16)
t/lib/samples/slowlogs/slow003.txt (+2/-2)
t/lib/samples/slowlogs/slow005.txt (+2/-2)
t/lib/samples/slowlogs/slow006.txt (+12/-12)
t/lib/samples/slowlogs/slow007.txt (+2/-2)
t/lib/samples/slowlogs/slow022.txt (+12/-12)
t/lib/samples/slowlogs/slow030.txt (+899/-899)
t/lib/samples/slowlogs/slow032-rewritten.txt (+2/-2)
t/lib/samples/slowlogs/slow032.txt (+2/-2)
t/lib/samples/slowlogs/slow034.txt (+18/-18)
t/lib/samples/slowlogs/slow035.txt (+2/-2)
t/pt-deadlock-logger/basics.t (+1/-1)
t/pt-fingerprint/basics.t (+101/-0)
t/pt-fingerprint/samples/query001 (+2/-0)
t/pt-fingerprint/samples/query001.fingerprint (+1/-0)
t/pt-fingerprint/samples/query002 (+2/-0)
t/pt-fingerprint/samples/query002.fingerprint (+1/-0)
t/pt-log-player/samples/log001.txt (+16/-16)
t/pt-mysql-summary/find_my_cnf_file.sh (+0/-20)
t/pt-mysql-summary/format_binlog_filters.sh (+0/-12)
t/pt-mysql-summary/format_innodb_status.sh (+0/-147)
t/pt-mysql-summary/format_overall_db_stats.sh (+0/-61)
t/pt-mysql-summary/format_status_variables.sh (+0/-97)
t/pt-mysql-summary/fuzz.sh (+0/-7)
t/pt-mysql-summary/get_mysql_info.sh (+0/-26)
t/pt-mysql-summary/parse_mysqld_instances.sh (+0/-47)
t/pt-mysql-summary/pretty_print_cnf_file.sh (+0/-40)
t/pt-mysql-summary/pt-mysql-summary.t (+54/-2)
t/pt-mysql-summary/samples/expected_output_temp002.txt (+276/-0)
t/pt-mysql-summary/samples/expected_output_temp003.txt (+219/-0)
t/pt-mysql-summary/samples/expected_output_temp004.txt (+218/-0)
t/pt-mysql-summary/samples/expected_output_temp005.txt (+291/-0)
t/pt-mysql-summary/samples/expected_result_report_summary.txt (+257/-0)
t/pt-mysql-summary/samples/mysql-variables-with-semisync.txt (+326/-0)
t/pt-mysql-summary/samples/temp001/mysql-status (+304/-0)
t/pt-mysql-summary/samples/temp001/mysql-variables (+356/-0)
t/pt-mysql-summary/samples/temp002/innodb-status (+118/-0)
t/pt-mysql-summary/samples/temp002/mysql-config-file (+26/-0)
t/pt-mysql-summary/samples/temp002/mysql-databases (+6/-0)
t/pt-mysql-summary/samples/temp002/mysql-plugins (+35/-0)
t/pt-mysql-summary/samples/temp002/mysql-processlist (+12/-0)
t/pt-mysql-summary/samples/temp002/mysql-status (+370/-0)
t/pt-mysql-summary/samples/temp002/mysql-status-defer (+370/-0)
t/pt-mysql-summary/samples/temp002/mysql-users (+1/-0)
t/pt-mysql-summary/samples/temp002/mysql-variables (+372/-0)
t/pt-mysql-summary/samples/temp002/mysqld-instances (+4/-0)
t/pt-mysql-summary/samples/temp002/mysqldump (+396/-0)
t/pt-mysql-summary/samples/temp003/innodb-status (+77/-0)
t/pt-mysql-summary/samples/temp003/mysql-config-file (+26/-0)
t/pt-mysql-summary/samples/temp003/mysql-databases (+2/-0)
t/pt-mysql-summary/samples/temp003/mysql-master-logs (+1/-0)
t/pt-mysql-summary/samples/temp003/mysql-master-status (+1/-0)
t/pt-mysql-summary/samples/temp003/mysql-plugins (+10/-0)
t/pt-mysql-summary/samples/temp003/mysql-processlist (+9/-0)
t/pt-mysql-summary/samples/temp003/mysql-status (+291/-0)
t/pt-mysql-summary/samples/temp003/mysql-status-defer (+291/-0)
t/pt-mysql-summary/samples/temp003/mysql-users (+1/-0)
t/pt-mysql-summary/samples/temp003/mysql-variables (+285/-0)
t/pt-mysql-summary/samples/temp003/mysqld-instances (+2/-0)
t/pt-mysql-summary/samples/temp004/innodb-status (+77/-0)
t/pt-mysql-summary/samples/temp004/mysql-config-file (+26/-0)
t/pt-mysql-summary/samples/temp004/mysql-databases (+3/-0)
t/pt-mysql-summary/samples/temp004/mysql-master-logs (+2/-0)
t/pt-mysql-summary/samples/temp004/mysql-master-status (+1/-0)
t/pt-mysql-summary/samples/temp004/mysql-plugins (+10/-0)
t/pt-mysql-summary/samples/temp004/mysql-processlist (+9/-0)
t/pt-mysql-summary/samples/temp004/mysql-status (+291/-0)
t/pt-mysql-summary/samples/temp004/mysql-status-defer (+291/-0)
t/pt-mysql-summary/samples/temp004/mysql-users (+1/-0)
t/pt-mysql-summary/samples/temp004/mysql-variables (+285/-0)
t/pt-mysql-summary/samples/temp004/mysqld-instances (+2/-0)
t/pt-mysql-summary/samples/temp005/innodb-status (+108/-0)
t/pt-mysql-summary/samples/temp005/mysql-config-file (+26/-0)
t/pt-mysql-summary/samples/temp005/mysql-databases (+3/-0)
t/pt-mysql-summary/samples/temp005/mysql-master-logs (+1/-0)
t/pt-mysql-summary/samples/temp005/mysql-master-status (+1/-0)
t/pt-mysql-summary/samples/temp005/mysql-plugins (+28/-0)
t/pt-mysql-summary/samples/temp005/mysql-processlist (+18/-0)
t/pt-mysql-summary/samples/temp005/mysql-status (+304/-0)
t/pt-mysql-summary/samples/temp005/mysql-status-defer (+304/-0)
t/pt-mysql-summary/samples/temp005/mysql-users (+1/-0)
t/pt-mysql-summary/samples/temp005/mysql-variables (+363/-0)
t/pt-mysql-summary/samples/temp005/mysqld-executables (+1/-0)
t/pt-mysql-summary/samples/temp005/mysqld-instances (+4/-0)
t/pt-mysql-summary/samples/temp005/mysqldump (+1084/-0)
t/pt-mysql-summary/samples/tempdir/innodb-status (+77/-0)
t/pt-mysql-summary/samples/tempdir/mysql-config-file (+26/-0)
t/pt-mysql-summary/samples/tempdir/mysql-databases (+3/-0)
t/pt-mysql-summary/samples/tempdir/mysql-master-logs (+3/-0)
t/pt-mysql-summary/samples/tempdir/mysql-master-status (+1/-0)
t/pt-mysql-summary/samples/tempdir/mysql-plugins (+10/-0)
t/pt-mysql-summary/samples/tempdir/mysql-processlist (+9/-0)
t/pt-mysql-summary/samples/tempdir/mysql-status (+291/-0)
t/pt-mysql-summary/samples/tempdir/mysql-status-defer (+291/-0)
t/pt-mysql-summary/samples/tempdir/mysql-users (+5/-0)
t/pt-mysql-summary/samples/tempdir/mysql-variables (+283/-0)
t/pt-mysql-summary/samples/tempdir/mysqld-instances (+4/-0)
t/pt-mysql-summary/samples/tempdir/mysqldump (+328/-0)
t/pt-mysql-summary/samples/tempdir/tempfile (+130/-0)
t/pt-mysql-summary/summarize_binlogs.sh (+0/-13)
t/pt-mysql-summary/summarize_processlist.sh (+0/-64)
t/pt-online-schema-change/alter_active_table.t (+56/-46)
t/pt-online-schema-change/basics.t (+541/-218)
t/pt-online-schema-change/check_tables.t (+0/-126)
t/pt-online-schema-change/option_sanity.t (+12/-12)
t/pt-online-schema-change/samples/basic_no_fks.data (+500/-500)
t/pt-online-schema-change/samples/basic_no_fks.sql (+30/-0)
t/pt-online-schema-change/samples/basic_with_fks.sql (+56/-0)
t/pt-online-schema-change/samples/fk_tables_schema.sql (+0/-31)
t/pt-online-schema-change/samples/query_table.pl (+8/-5)
t/pt-online-schema-change/samples/small_table.sql (+0/-27)
t/pt-online-schema-change/sanity_checks.t (+113/-0)
t/pt-query-digest/read_timeout.t (+1/-1)
t/pt-query-digest/review.t (+4/-4)
t/pt-query-digest/samples/save-results/slow002-limit-3.txt (+4/-4)
t/pt-query-digest/samples/save-results/slow002.txt (+2/-2)
t/pt-query-digest/samples/save-results/slow006.txt (+1/-1)
t/pt-query-digest/samples/slow-issue-611.txt (+2/-2)
t/pt-query-digest/samples/slow002-orderbynonexistent.txt (+6/-6)
t/pt-query-digest/samples/slow002_iters_2.txt (+1/-1)
t/pt-query-digest/samples/slow002_orderbyreport.txt (+2/-2)
t/pt-query-digest/samples/slow002_report.txt (+6/-6)
t/pt-query-digest/samples/slow002_report_filtered.txt (+1/-1)
t/pt-query-digest/samples/slow006-first2.txt (+8/-8)
t/pt-query-digest/samples/slow006-order-by-re.txt (+2/-2)
t/pt-query-digest/samples/slow006_AR_1.txt (+2/-2)
t/pt-query-digest/samples/slow006_AR_2.txt (+1/-1)
t/pt-query-digest/samples/slow006_AR_4.txt (+2/-2)
t/pt-query-digest/samples/slow006_AR_5.txt (+1/-1)
t/pt-query-digest/samples/slow006_report.txt (+2/-2)
t/pt-query-digest/samples/slow034-inheritance.txt (+18/-18)
t/pt-query-digest/samples/slow034-no-ts-inheritance.txt (+18/-18)
t/pt-query-digest/samples/slow034-order-by-Locktime-sum-with-Locktime-distro.txt (+8/-8)
t/pt-query-digest/samples/slow034-order-by-Locktime-sum.txt (+8/-8)
t/pt-query-digest/samples/slow035.txt (+1/-1)
t/pt-stalk/pt-stalk.t (+1/-1)
t/pt-summary/format_vmstat.sh (+0/-37)
t/pt-summary/parse_arcconf.sh (+0/-176)
t/pt-summary/parse_dmidecode_mem_devices.sh (+0/-104)
t/pt-summary/parse_ethernet_controller_lspci.sh (+0/-11)
t/pt-summary/parse_fdisk.sh (+0/-16)
t/pt-summary/parse_filesystems.sh (+0/-52)
t/pt-summary/parse_free_minus_b.sh (+0/-67)
t/pt-summary/parse_fusionmpt_lsiutil.sh (+0/-50)
t/pt-summary/parse_hpacucli.sh (+0/-26)
t/pt-summary/parse_ip_s_link.sh (+0/-28)
t/pt-summary/parse_lsi_megaraid.sh (+0/-696)
t/pt-summary/parse_netstat.sh (+0/-49)
t/pt-summary/parse_proc_cpuinfo.sh (+0/-74)
t/pt-summary/parse_raid_controller_dmesg.sh (+0/-32)
t/pt-summary/parse_raid_controller_lspci.sh (+0/-39)
t/pt-summary/parse_virtualization_dmesg.sh (+0/-10)
t/pt-summary/pt-summary.t (+12/-2)
t/pt-summary/samples/BSD/freebsd_001/mounted_fs (+7/-0)
t/pt-summary/samples/BSD/freebsd_001/notable_procs (+2/-0)
t/pt-summary/samples/BSD/freebsd_001/processes (+10/-0)
t/pt-summary/samples/BSD/freebsd_001/summary (+10/-0)
t/pt-summary/samples/BSD/freebsd_001/sysctl (+1481/-0)
t/pt-summary/samples/BSD/freebsd_001/uptime (+1/-0)
t/pt-summary/samples/BSD/freebsd_001/vmstat (+7/-0)
t/pt-summary/samples/BSD/netbsd_001/mounted_fs (+5/-0)
t/pt-summary/samples/BSD/netbsd_001/notable_procs (+2/-0)
t/pt-summary/samples/BSD/netbsd_001/proc_cpuinfo_copy (+14/-0)
t/pt-summary/samples/BSD/netbsd_001/processes (+10/-0)
t/pt-summary/samples/BSD/netbsd_001/summary (+10/-0)
t/pt-summary/samples/BSD/netbsd_001/swapctl (+1/-0)
t/pt-summary/samples/BSD/netbsd_001/sysctl (+511/-0)
t/pt-summary/samples/BSD/netbsd_001/uptime (+1/-0)
t/pt-summary/samples/BSD/netbsd_001/vmstat (+7/-0)
t/pt-summary/samples/BSD/openbsd_001/mounted_fs (+4/-0)
t/pt-summary/samples/BSD/openbsd_001/notable_procs (+2/-0)
t/pt-summary/samples/BSD/openbsd_001/processes (+10/-0)
t/pt-summary/samples/BSD/openbsd_001/summary (+10/-0)
t/pt-summary/samples/BSD/openbsd_001/swapctl (+1/-0)
t/pt-summary/samples/BSD/openbsd_001/sysctl (+423/-0)
t/pt-summary/samples/BSD/openbsd_001/uptime (+1/-0)
t/pt-summary/samples/BSD/openbsd_001/vmstat (+7/-0)
t/pt-summary/samples/Linux/001/dmesg_file (+786/-0)
t/pt-summary/samples/Linux/001/dmidecode (+412/-0)
t/pt-summary/samples/Linux/001/ip (+24/-0)
t/pt-summary/samples/Linux/001/lspci_file (+17/-0)
t/pt-summary/samples/Linux/001/lvs (+1/-0)
t/pt-summary/samples/Linux/001/memory (+50/-0)
t/pt-summary/samples/Linux/001/mounted_fs (+12/-0)
t/pt-summary/samples/Linux/001/netstat (+6/-0)
t/pt-summary/samples/Linux/001/notable_procs (+5/-0)
t/pt-summary/samples/Linux/001/partitioning (+30/-0)
t/pt-summary/samples/Linux/001/proc_cpuinfo_copy (+58/-0)
t/pt-summary/samples/Linux/001/proc_cpuinfo_copy.unq (+1/-0)
t/pt-summary/samples/Linux/001/processes (+10/-0)
t/pt-summary/samples/Linux/001/summary (+23/-0)
t/pt-summary/samples/Linux/001/sysctl (+905/-0)
t/pt-summary/samples/Linux/001/uptime (+1/-0)
t/pt-summary/samples/Linux/001/vmstat (+7/-0)
t/pt-summary/samples/Linux/002/dmesg_file (+283/-0)
t/pt-summary/samples/Linux/002/memory (+34/-0)
t/pt-summary/samples/Linux/002/mounted_fs (+3/-0)
t/pt-summary/samples/Linux/002/netstat (+6/-0)
t/pt-summary/samples/Linux/002/notable_procs (+2/-0)
t/pt-summary/samples/Linux/002/partitioning (+9/-0)
t/pt-summary/samples/Linux/002/proc_cpuinfo_copy (+19/-0)
t/pt-summary/samples/Linux/002/processes (+10/-0)
t/pt-summary/samples/Linux/002/summary (+19/-0)
t/pt-summary/samples/Linux/002/uptime (+1/-0)
t/pt-summary/samples/Linux/002/vmstat (+7/-0)
t/pt-summary/samples/Linux/003/dmesg_file (+283/-0)
t/pt-summary/samples/Linux/003/memory (+34/-0)
t/pt-summary/samples/Linux/003/mounted_fs (+3/-0)
t/pt-summary/samples/Linux/003/netstat (+6/-0)
t/pt-summary/samples/Linux/003/notable_procs (+2/-0)
t/pt-summary/samples/Linux/003/partitioning (+1/-0)
t/pt-summary/samples/Linux/003/proc_cpuinfo_copy (+19/-0)
t/pt-summary/samples/Linux/003/processes (+10/-0)
t/pt-summary/samples/Linux/003/summary (+19/-0)
t/pt-summary/samples/Linux/003/uptime (+1/-0)
t/pt-summary/samples/Linux/003/vmstat (+7/-0)
t/pt-summary/samples/Linux/output_002.txt (+82/-0)
t/pt-summary/samples/Linux/output_003.txt (+79/-0)
t/pt-summary/samples/MegaCli64_AdpAllInfo_aALL001.txt (+227/-0)
t/pt-summary/samples/MegaCli64_LdPdInfo_aALL_886223 (+214/-0)
t/pt-summary/samples/arcconf-001.txt (+133/-0)
t/pt-summary/samples/arcconf-003_900285.txt (+228/-0)
t/pt-summary/samples/arcconf-004_917781.txt (+162/-0)
t/pt-summary/samples/dmesg-005.txt (+787/-0)
t/pt-summary/samples/dmesg-007.txt (+136/-0)
t/pt-summary/samples/hpaculi-001.txt (+11/-0)
t/pt-summary/samples/hpaculi-002.txt (+354/-0)
t/pt-summary/samples/hpaculi-003.txt (+11/-0)
t/pt-summary/samples/ip-s-link-003.txt (+24/-0)
t/pt-summary/samples/lspci-005.txt (+38/-0)
t/pt-summary/samples/netstat-002.txt (+1328/-0)
t/pt-summary/samples/proc_cpuinfo001.txt (+57/-0)
t/pt-summary/samples/proc_cpuinfo001.txt.unq (+1/-0)
t/pt-summary/samples/proc_cpuinfo002.txt (+57/-0)
t/pt-summary/samples/proc_cpuinfo002.txt.unq (+1/-0)
t/pt-table-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 (+48/-0)
Text conflict in bin/pt-online-schema-change
Text conflict in bin/pt-table-checksum
Text conflict in lib/MySQLStatusWaiter.pm
Text conflict in lib/NibbleIterator.pm
Text conflict in t/lib/MySQLStatusWaiter.t
Text conflict in t/lib/NibbleIterator.t
To merge this branch: bzr merge lp:~percona-toolkit-dev/percona-toolkit/fix-sleep-bug-979092
Reviewer Review Type Date Requested Status
Daniel Nichter Approve
Review via email: mp+108057@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Daniel Nichter (daniel-nichter) :
review: Approve

Preview Diff

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

Subscribers

People subscribed via source and target branches