Merge lp:~percona-toolkit-dev/percona-toolkit/simplify-pqd into lp:percona-toolkit/2.2
- simplify-pqd
- Merge into 2.2
Proposed by
Daniel Nichter
Status: | Merged |
---|---|
Merged at revision: | 514 |
Proposed branch: | lp:~percona-toolkit-dev/percona-toolkit/simplify-pqd |
Merge into: | lp:percona-toolkit/2.2 |
Diff against target: |
11714 lines (+3379/-3929) 155 files modified
bin/pt-query-digest (+1441/-1530) bin/pt-table-usage (+7/-8) lib/EventAggregator.pm (+0/-77) lib/ExplainAnalyzer.pm (+1/-87) lib/JSONReportFormatter.pm (+124/-0) lib/Mo.pm (+11/-0) lib/Pipeline.pm (+7/-8) lib/QueryReportFormatter.pm (+295/-375) lib/QueryReview.pm (+2/-2) lib/ReportFormatter.pm (+141/-72) lib/Transformers.pm (+20/-18) t/lib/EventAggregator.t (+3/-59) t/lib/ExplainAnalyzer.t (+0/-143) t/lib/Pipeline.t (+1/-1) t/lib/QueryReportFormatter.t (+22/-267) t/lib/QueryReview.t (+0/-2) t/lib/ReportFormatter.t (+9/-9) t/lib/samples/QueryReportFormatter/report001.txt (+4/-5) t/lib/samples/QueryReportFormatter/report002.txt (+2/-4) t/lib/samples/QueryReportFormatter/report003.txt (+4/-5) t/lib/samples/QueryReportFormatter/report004.txt (+4/-4) t/lib/samples/QueryReportFormatter/report005.txt (+3/-3) t/lib/samples/QueryReportFormatter/report007.txt (+1/-1) t/lib/samples/QueryReportFormatter/report009.txt (+1/-1) t/lib/samples/QueryReportFormatter/report010.txt (+1/-1) t/lib/samples/QueryReportFormatter/report011.txt (+1/-1) t/lib/samples/QueryReportFormatter/report012.txt (+1/-1) t/lib/samples/QueryReportFormatter/report013.txt (+1/-1) t/lib/samples/QueryReportFormatter/report014.txt (+1/-1) t/lib/samples/QueryReportFormatter/report015.txt (+1/-2) t/lib/samples/QueryReportFormatter/report016.txt (+1/-2) t/lib/samples/QueryReportFormatter/report024.txt (+1/-1) t/lib/samples/QueryReportFormatter/report027.txt (+0/-58) t/lib/samples/QueryReportFormatter/report028.txt (+1/-1) t/lib/samples/QueryReportFormatter/report029.txt (+0/-56) t/lib/samples/QueryReportFormatter/report032.txt (+0/-58) t/pt-query-digest/execute.t (+0/-92) t/pt-query-digest/issue_360.t (+2/-2) t/pt-query-digest/mirror.t (+0/-105) t/pt-query-digest/option_sanity.t (+49/-14) t/pt-query-digest/output.t (+55/-0) t/pt-query-digest/resume.t (+163/-0) t/pt-query-digest/review.t (+24/-21) t/pt-query-digest/samples/binlog001.txt (+14/-20) t/pt-query-digest/samples/binlog002.txt (+8/-11) t/pt-query-digest/samples/cannot-distill-profile.txt (+4/-4) t/pt-query-digest/samples/genlog001.txt (+12/-17) t/pt-query-digest/samples/genlog002.txt (+6/-8) t/pt-query-digest/samples/genlog003.txt (+12/-17) t/pt-query-digest/samples/http_tcpdump002.txt (+10/-20) t/pt-query-digest/samples/issue_1196-output-5.0.txt (+4/-6) t/pt-query-digest/samples/issue_1196-output-5.6.txt (+4/-6) t/pt-query-digest/samples/issue_1196-output.txt (+4/-6) t/pt-query-digest/samples/memc_tcpdump001.txt (+1/-2) t/pt-query-digest/samples/memc_tcpdump002.txt (+1/-2) t/pt-query-digest/samples/memc_tcpdump003.txt (+2/-4) t/pt-query-digest/samples/memc_tcpdump003_report_key_print.txt (+1/-2) t/pt-query-digest/samples/memc_tcpdump004.txt (+2/-4) t/pt-query-digest/samples/memc_tcpdump005.txt (+1/-2) t/pt-query-digest/samples/memc_tcpdump006.txt (+1/-2) t/pt-query-digest/samples/memc_tcpdump007.txt (+1/-2) t/pt-query-digest/samples/memc_tcpdump008.txt (+1/-2) t/pt-query-digest/samples/memc_tcpdump009.txt (+1/-2) t/pt-query-digest/samples/memc_tcpdump010.txt (+1/-2) t/pt-query-digest/samples/output_json_slow002.txt (+203/-0) t/pt-query-digest/samples/output_json_tcpdump021.txt (+359/-0) t/pt-query-digest/samples/pg-sample1 (+23/-23) t/pt-query-digest/samples/pg-syslog-sample1 (+10/-10) t/pt-query-digest/samples/rawlog001.txt (+6/-8) t/pt-query-digest/samples/slow001_distillreport.txt (+2/-4) t/pt-query-digest/samples/slow001_report.txt (+2/-4) t/pt-query-digest/samples/slow001_select_report.txt (+2/-4) t/pt-query-digest/samples/slow001_tablesreport.txt (+1/-2) t/pt-query-digest/samples/slow002-orderbynonexistent.txt (+7/-16) t/pt-query-digest/samples/slow002_iters_2.txt (+5/-7) t/pt-query-digest/samples/slow002_orderbyreport.txt (+2/-5) t/pt-query-digest/samples/slow002_report.txt (+7/-16) t/pt-query-digest/samples/slow002_report_filtered.txt (+1/-2) t/pt-query-digest/samples/slow003_report.txt (+1/-2) t/pt-query-digest/samples/slow004_report.txt (+1/-2) t/pt-query-digest/samples/slow006-order-by-re.txt (+2/-4) t/pt-query-digest/samples/slow006_AR_1.txt (+2/-4) t/pt-query-digest/samples/slow006_AR_2.txt (+1/-2) t/pt-query-digest/samples/slow006_AR_4.txt (+2/-4) t/pt-query-digest/samples/slow006_AR_5.txt (+1/-2) t/pt-query-digest/samples/slow006_report.txt (+2/-4) t/pt-query-digest/samples/slow007_explain_1-51.txt (+1/-3) t/pt-query-digest/samples/slow007_explain_1-55.txt (+1/-3) t/pt-query-digest/samples/slow007_explain_1.txt (+1/-3) t/pt-query-digest/samples/slow007_explain_2-51.txt (+1/-3) t/pt-query-digest/samples/slow007_explain_2.txt (+1/-3) t/pt-query-digest/samples/slow007_explain_3.txt (+4/-5) t/pt-query-digest/samples/slow007_explain_4.txt (+3/-3) t/pt-query-digest/samples/slow008_report.txt (+3/-6) t/pt-query-digest/samples/slow010_reportbyfile.txt (+1/-2) t/pt-query-digest/samples/slow011_report.txt (+2/-4) t/pt-query-digest/samples/slow013_report.txt (+4/-8) t/pt-query-digest/samples/slow013_report_fingerprint_user.txt (+2/-4) t/pt-query-digest/samples/slow013_report_limit.txt (+1/-2) t/pt-query-digest/samples/slow013_report_outliers.txt (+2/-4) t/pt-query-digest/samples/slow013_report_profile.txt (+6/-6) t/pt-query-digest/samples/slow013_report_user.txt (+2/-4) t/pt-query-digest/samples/slow014_report.txt (+1/-2) t/pt-query-digest/samples/slow018_execute_report_1.txt (+0/-41) t/pt-query-digest/samples/slow018_execute_report_2.txt (+0/-30) t/pt-query-digest/samples/slow018_report.txt (+1/-2) t/pt-query-digest/samples/slow019_report.txt (+2/-4) t/pt-query-digest/samples/slow019_report_noza.txt (+2/-4) t/pt-query-digest/samples/slow020_table_access.txt (+0/-3) t/pt-query-digest/samples/slow023.txt (+1/-2) t/pt-query-digest/samples/slow024.txt (+3/-6) t/pt-query-digest/samples/slow028.txt (+1/-2) t/pt-query-digest/samples/slow030_table_access.txt (+0/-2) t/pt-query-digest/samples/slow032.txt (+1/-2) t/pt-query-digest/samples/slow033-precise-since-until.txt (+2/-4) t/pt-query-digest/samples/slow033-rtm-event-1h.txt (+4/-5) t/pt-query-digest/samples/slow033-rtm-event-25h.txt (+4/-5) t/pt-query-digest/samples/slow033-rtm-interval-1d.txt (+18/-23) t/pt-query-digest/samples/slow033-rtm-interval-30m.txt (+20/-25) t/pt-query-digest/samples/slow033-rtm-interval-30s-3iter.txt (+12/-15) t/pt-query-digest/samples/slow033-rtm-interval-30s.txt (+24/-30) t/pt-query-digest/samples/slow033-since-Nd.txt (+2/-4) t/pt-query-digest/samples/slow033-since-yymmdd.txt (+2/-4) t/pt-query-digest/samples/slow033-since-yyyy-mm-dd.txt (+1/-2) t/pt-query-digest/samples/slow033-until-date.txt (+1/-2) t/pt-query-digest/samples/slow034-order-by-Locktime-sum-with-Locktime-distro.txt (+12/-17) t/pt-query-digest/samples/slow034-order-by-Locktime-sum.txt (+12/-17) t/pt-query-digest/samples/slow035.txt (+6/-9) t/pt-query-digest/samples/slow037_report.txt (+4/-5) t/pt-query-digest/samples/slow042-show-all-host.txt (+1/-2) t/pt-query-digest/samples/slow042.txt (+1/-2) t/pt-query-digest/samples/slow048.txt (+4/-5) t/pt-query-digest/samples/slow049.txt (+9/-12) t/pt-query-digest/samples/slow050.txt (+4/-5) t/pt-query-digest/samples/slow051.txt (+4/-5) t/pt-query-digest/samples/slow052-apdex-t-0.1.txt (+0/-66) t/pt-query-digest/samples/slow052.txt (+6/-8) t/pt-query-digest/samples/slow053.txt (+3/-6) t/pt-query-digest/samples/slow054.txt (+1/-2) t/pt-query-digest/samples/slow055.txt (+1/-2) t/pt-query-digest/samples/slow056.txt (+2/-4) t/pt-query-digest/samples/tcpdump001.txt (+1/-2) t/pt-query-digest/samples/tcpdump002_report.txt (+4/-8) t/pt-query-digest/samples/tcpdump003.txt (+1/-2) t/pt-query-digest/samples/tcpdump012.txt (+1/-2) t/pt-query-digest/samples/tcpdump017_report.txt (+4/-5) t/pt-query-digest/samples/tcpdump021.txt (+3/-6) t/pt-query-digest/samples/tcpdump022.txt (+2/-4) t/pt-query-digest/samples/tcpdump023.txt (+2/-4) t/pt-query-digest/samples/tcpdump024.txt (+2/-4) t/pt-query-digest/samples/tcpdump025.txt (+2/-4) t/pt-query-digest/samples/tcpdump033.txt (+8/-11) t/pt-query-digest/samples/tcpdump041.txt (+4/-5) t/pt-query-digest/slowlog_analyses.t (+0/-27) t/pt-query-digest/statistics.t (+0/-32) |
To merge this branch: | bzr merge lp:~percona-toolkit-dev/percona-toolkit/simplify-pqd |
Related bugs: | |
Related blueprints: |
Simplify pt-query-digest
(Essential)
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Daniel Nichter | Pending | ||
Review via email:
|
Commit message
Description of the change
To post a comment you must log in.
- 509. By Daniel Nichter
-
Fix util/update-modules so it doesn't leave tmp files lying around.
- 510. By Daniel Nichter
-
Make t/pt-archiver/
file.t clean up after itself. - 511. By Daniel Nichter
-
Add df -h and w to jenkins-test just in case.
- 512. By Daniel Nichter
-
Remove t/lib/TimeSerie
sTrender. t - 513. By Daniel Nichter
-
Add env to jenkins-test.
- 514. By Brian Fraser
-
Merged simplify-pqd
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'bin/pt-query-digest' |
2 | --- bin/pt-query-digest 2013-01-03 00:54:18 +0000 |
3 | +++ bin/pt-query-digest 2013-01-30 20:58:23 +0000 |
4 | @@ -14,6 +14,7 @@ |
5 | BEGIN { |
6 | $INC{$_} = __FILE__ for map { (my $pkg = "$_.pm") =~ s!::!/!g; $pkg } (qw( |
7 | Percona::Toolkit |
8 | + Mo |
9 | DSNParser |
10 | Quoter |
11 | OptionParser |
12 | @@ -29,6 +30,7 @@ |
13 | EventAggregator |
14 | ReportFormatter |
15 | QueryReportFormatter |
16 | + JSONReportFormatter |
17 | EventTimeline |
18 | QueryParser |
19 | TableParser |
20 | @@ -41,11 +43,9 @@ |
21 | RawLogParser |
22 | ProtocolParser |
23 | HTTPProtocolParser |
24 | - ExecutionThrottler |
25 | MasterSlave |
26 | Progress |
27 | FileIterator |
28 | - ExplainAnalyzer |
29 | Runtime |
30 | Pipeline |
31 | VersionCheck |
32 | @@ -73,6 +73,468 @@ |
33 | # ########################################################################### |
34 | |
35 | # ########################################################################### |
36 | +# Mo package |
37 | +# This package is a copy without comments from the original. The original |
38 | +# with comments and its test file can be found in the Bazaar repository at, |
39 | +# lib/Mo.pm |
40 | +# t/lib/Mo.t |
41 | +# See https://launchpad.net/percona-toolkit for more information. |
42 | +# ########################################################################### |
43 | +{ |
44 | +BEGIN { |
45 | +$INC{"Mo.pm"} = __FILE__; |
46 | +package Mo; |
47 | +our $VERSION = '0.30_Percona'; # Forked from 0.30 of Mo. |
48 | + |
49 | +{ |
50 | + no strict 'refs'; |
51 | + sub _glob_for { |
52 | + return \*{shift()} |
53 | + } |
54 | + |
55 | + sub _stash_for { |
56 | + return \%{ shift() . "::" }; |
57 | + } |
58 | +} |
59 | + |
60 | +use strict; |
61 | +use warnings qw( FATAL all ); |
62 | + |
63 | +use Carp (); |
64 | +use Scalar::Util qw(looks_like_number blessed); |
65 | + |
66 | + |
67 | +our %TYPES = ( |
68 | + Bool => sub { !$_[0] || (defined $_[0] && looks_like_number($_[0]) && $_[0] == 1) }, |
69 | + Num => sub { defined $_[0] && looks_like_number($_[0]) }, |
70 | + Int => sub { defined $_[0] && looks_like_number($_[0]) && $_[0] == int($_[0]) }, |
71 | + Str => sub { defined $_[0] }, |
72 | + Object => sub { defined $_[0] && blessed($_[0]) }, |
73 | + FileHandle => sub { local $@; require IO::Handle; fileno($_[0]) && $_[0]->opened }, |
74 | + |
75 | + map { |
76 | + my $type = /R/ ? $_ : uc $_; |
77 | + $_ . "Ref" => sub { ref $_[0] eq $type } |
78 | + } qw(Array Code Hash Regexp Glob Scalar) |
79 | +); |
80 | + |
81 | +our %metadata_for; |
82 | +{ |
83 | + package Mo::Object; |
84 | + |
85 | + sub new { |
86 | + my $class = shift; |
87 | + my $args = $class->BUILDARGS(@_); |
88 | + |
89 | + my @args_to_delete; |
90 | + while ( my ($attr, $meta) = each %{$metadata_for{$class}} ) { |
91 | + next unless exists $meta->{init_arg}; |
92 | + my $init_arg = $meta->{init_arg}; |
93 | + |
94 | + if ( defined $init_arg ) { |
95 | + $args->{$attr} = delete $args->{$init_arg}; |
96 | + } |
97 | + else { |
98 | + push @args_to_delete, $attr; |
99 | + } |
100 | + } |
101 | + |
102 | + delete $args->{$_} for @args_to_delete; |
103 | + |
104 | + for my $attribute ( keys %$args ) { |
105 | + if ( my $coerce = $metadata_for{$class}{$attribute}{coerce} ) { |
106 | + $args->{$attribute} = $coerce->($args->{$attribute}); |
107 | + } |
108 | + if ( my $I = $metadata_for{$class}{$attribute}{isa} ) { |
109 | + ( (my $I_name), $I ) = @{$I}; |
110 | + Mo::_check_type_constaints($attribute, $I, $I_name, $args->{$attribute}); |
111 | + } |
112 | + } |
113 | + |
114 | + while ( my ($attribute, $meta) = each %{$metadata_for{$class}} ) { |
115 | + next unless $meta->{required}; |
116 | + Carp::confess("Attribute ($attribute) is required for $class") |
117 | + if ! exists $args->{$attribute} |
118 | + } |
119 | + |
120 | + @_ = %$args; |
121 | + my $self = bless $args, $class; |
122 | + |
123 | + my @build_subs; |
124 | + my $linearized_isa = mro::get_linear_isa($class); |
125 | + |
126 | + for my $isa_class ( @$linearized_isa ) { |
127 | + unshift @build_subs, *{ Mo::_glob_for "${isa_class}::BUILD" }{CODE}; |
128 | + } |
129 | + exists &$_ && $_->( $self, @_ ) for grep { defined } @build_subs; |
130 | + return $self; |
131 | + } |
132 | + |
133 | + sub BUILDARGS { |
134 | + shift; |
135 | + my $ref; |
136 | + if ( @_ == 1 && ref($_[0]) ) { |
137 | + Carp::confess("Single parameters to new() must be a HASH ref") |
138 | + unless ref($_[0]) eq ref({}); |
139 | + $ref = {%{$_[0]}} # We want a new reference, always |
140 | + } |
141 | + else { |
142 | + $ref = { @_ }; |
143 | + } |
144 | + return $ref; |
145 | + } |
146 | +} |
147 | + |
148 | +my %export_for; |
149 | +sub Mo::import { |
150 | + warnings->import(qw(FATAL all)); |
151 | + strict->import(); |
152 | + |
153 | + my $caller = scalar caller(); # Caller's package |
154 | + my $caller_pkg = $caller . "::"; # Caller's package with :: at the end |
155 | + my (%exports, %options); |
156 | + |
157 | + my (undef, @features) = @_; |
158 | + my %ignore = ( map { $_ => 1 } qw( is isa init_arg builder buildargs clearer predicate build handles default required ) ); |
159 | + for my $feature (grep { !$ignore{$_} } @features) { |
160 | + { local $@; require "Mo/$feature.pm"; } |
161 | + { |
162 | + no strict 'refs'; |
163 | + &{"Mo::${feature}::e"}( |
164 | + $caller_pkg, |
165 | + \%exports, |
166 | + \%options, |
167 | + \@_ |
168 | + ); |
169 | + } |
170 | + } |
171 | + |
172 | + return if $exports{M}; |
173 | + |
174 | + %exports = ( |
175 | + extends => sub { |
176 | + for my $class ( map { "$_" } @_ ) { |
177 | + $class =~ s{::|'}{/}g; |
178 | + { local $@; eval { require "$class.pm" } } # or warn $@; |
179 | + } |
180 | + _set_package_isa($caller, @_); |
181 | + _set_inherited_metadata($caller); |
182 | + }, |
183 | + override => \&override, |
184 | + has => sub { |
185 | + my $names = shift; |
186 | + for my $attribute ( ref $names ? @$names : $names ) { |
187 | + my %args = @_; |
188 | + my $method = ($args{is} || '') eq 'ro' |
189 | + ? sub { |
190 | + Carp::confess("Cannot assign a value to a read-only accessor at reader ${caller_pkg}${attribute}") |
191 | + if $#_; |
192 | + return $_[0]{$attribute}; |
193 | + } |
194 | + : sub { |
195 | + return $#_ |
196 | + ? $_[0]{$attribute} = $_[1] |
197 | + : $_[0]{$attribute}; |
198 | + }; |
199 | + |
200 | + $metadata_for{$caller}{$attribute} = (); |
201 | + |
202 | + if ( my $I = $args{isa} ) { |
203 | + my $orig_I = $I; |
204 | + my $type; |
205 | + if ( $I =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { |
206 | + $I = _nested_constraints($attribute, $1, $2); |
207 | + } |
208 | + $metadata_for{$caller}{$attribute}{isa} = [$orig_I, $I]; |
209 | + my $orig_method = $method; |
210 | + $method = sub { |
211 | + if ( $#_ ) { |
212 | + Mo::_check_type_constaints($attribute, $I, $orig_I, $_[1]); |
213 | + } |
214 | + goto &$orig_method; |
215 | + }; |
216 | + } |
217 | + |
218 | + if ( my $builder = $args{builder} ) { |
219 | + my $original_method = $method; |
220 | + $method = sub { |
221 | + $#_ |
222 | + ? goto &$original_method |
223 | + : ! exists $_[0]{$attribute} |
224 | + ? $_[0]{$attribute} = $_[0]->$builder |
225 | + : goto &$original_method |
226 | + }; |
227 | + } |
228 | + |
229 | + if ( my $code = $args{default} ) { |
230 | + Carp::confess("${caller}::${attribute}'s default is $code, but should be a coderef") |
231 | + unless ref($code) eq 'CODE'; |
232 | + my $original_method = $method; |
233 | + $method = sub { |
234 | + $#_ |
235 | + ? goto &$original_method |
236 | + : ! exists $_[0]{$attribute} |
237 | + ? $_[0]{$attribute} = $_[0]->$code |
238 | + : goto &$original_method |
239 | + }; |
240 | + } |
241 | + |
242 | + if ( my $role = $args{does} ) { |
243 | + my $original_method = $method; |
244 | + $method = sub { |
245 | + if ( $#_ ) { |
246 | + Carp::confess(qq<Attribute ($attribute) doesn't consume a '$role' role">) |
247 | + unless Scalar::Util::blessed($_[1]) && eval { $_[1]->does($role) } |
248 | + } |
249 | + goto &$original_method |
250 | + }; |
251 | + } |
252 | + |
253 | + if ( my $coercion = $args{coerce} ) { |
254 | + $metadata_for{$caller}{$attribute}{coerce} = $coercion; |
255 | + my $original_method = $method; |
256 | + $method = sub { |
257 | + if ( $#_ ) { |
258 | + return $original_method->($_[0], $coercion->($_[1])) |
259 | + } |
260 | + goto &$original_method; |
261 | + } |
262 | + } |
263 | + |
264 | + $method = $options{$_}->($method, $attribute, @_) |
265 | + for sort keys %options; |
266 | + |
267 | + *{ _glob_for "${caller}::$attribute" } = $method; |
268 | + |
269 | + if ( $args{required} ) { |
270 | + $metadata_for{$caller}{$attribute}{required} = 1; |
271 | + } |
272 | + |
273 | + if ($args{clearer}) { |
274 | + *{ _glob_for "${caller}::$args{clearer}" } |
275 | + = sub { delete shift->{$attribute} } |
276 | + } |
277 | + |
278 | + if ($args{predicate}) { |
279 | + *{ _glob_for "${caller}::$args{predicate}" } |
280 | + = sub { exists shift->{$attribute} } |
281 | + } |
282 | + |
283 | + if ($args{handles}) { |
284 | + _has_handles($caller, $attribute, \%args); |
285 | + } |
286 | + |
287 | + if (exists $args{init_arg}) { |
288 | + $metadata_for{$caller}{$attribute}{init_arg} = $args{init_arg}; |
289 | + } |
290 | + } |
291 | + }, |
292 | + %exports, |
293 | + ); |
294 | + |
295 | + $export_for{$caller} = [ keys %exports ]; |
296 | + |
297 | + for my $keyword ( keys %exports ) { |
298 | + *{ _glob_for "${caller}::$keyword" } = $exports{$keyword} |
299 | + } |
300 | + *{ _glob_for "${caller}::extends" }{CODE}->( "Mo::Object" ) |
301 | + unless @{ *{ _glob_for "${caller}::ISA" }{ARRAY} || [] }; |
302 | +}; |
303 | + |
304 | +sub _check_type_constaints { |
305 | + my ($attribute, $I, $I_name, $val) = @_; |
306 | + ( ref($I) eq 'CODE' |
307 | + ? $I->($val) |
308 | + : (ref $val eq $I |
309 | + || ($val && $val eq $I) |
310 | + || (exists $TYPES{$I} && $TYPES{$I}->($val))) |
311 | + ) |
312 | + || Carp::confess( |
313 | + qq<Attribute ($attribute) does not pass the type constraint because: > |
314 | + . qq<Validation failed for '$I_name' with value > |
315 | + . (defined $val ? Mo::Dumper($val) : 'undef') ) |
316 | +} |
317 | + |
318 | +sub _has_handles { |
319 | + my ($caller, $attribute, $args) = @_; |
320 | + my $handles = $args->{handles}; |
321 | + |
322 | + my $ref = ref $handles; |
323 | + my $kv; |
324 | + if ( $ref eq ref [] ) { |
325 | + $kv = { map { $_,$_ } @{$handles} }; |
326 | + } |
327 | + elsif ( $ref eq ref {} ) { |
328 | + $kv = $handles; |
329 | + } |
330 | + elsif ( $ref eq ref qr// ) { |
331 | + Carp::confess("Cannot delegate methods based on a Regexp without a type constraint (isa)") |
332 | + unless $args->{isa}; |
333 | + my $target_class = $args->{isa}; |
334 | + $kv = { |
335 | + map { $_, $_ } |
336 | + grep { $_ =~ $handles } |
337 | + grep { !exists $Mo::Object::{$_} && $target_class->can($_) } |
338 | + grep { $_ ne 'has' && $_ ne 'extends' } |
339 | + keys %{ _stash_for $target_class } |
340 | + }; |
341 | + } |
342 | + else { |
343 | + Carp::confess("handles for $ref not yet implemented"); |
344 | + } |
345 | + |
346 | + while ( my ($method, $target) = each %{$kv} ) { |
347 | + my $name = _glob_for "${caller}::$method"; |
348 | + Carp::confess("You cannot overwrite a locally defined method ($method) with a delegation") |
349 | + if defined &$name; |
350 | + |
351 | + my ($target, @curried_args) = ref($target) ? @$target : $target; |
352 | + *$name = sub { |
353 | + my $self = shift; |
354 | + my $delegate_to = $self->$attribute(); |
355 | + my $error = "Cannot delegate $method to $target because the value of $attribute"; |
356 | + Carp::confess("$error is not defined") unless $delegate_to; |
357 | + Carp::confess("$error is not an object (got '$delegate_to')") |
358 | + unless Scalar::Util::blessed($delegate_to) || (!ref($delegate_to) && $delegate_to->can($target)); |
359 | + return $delegate_to->$target(@curried_args, @_); |
360 | + } |
361 | + } |
362 | +} |
363 | + |
364 | +sub _nested_constraints { |
365 | + my ($attribute, $aggregate_type, $type) = @_; |
366 | + |
367 | + my $inner_types; |
368 | + if ( $type =~ /\A(ArrayRef|Maybe)\[(.*)\]\z/ ) { |
369 | + $inner_types = _nested_constraints($1, $2); |
370 | + } |
371 | + else { |
372 | + $inner_types = $TYPES{$type}; |
373 | + } |
374 | + |
375 | + if ( $aggregate_type eq 'ArrayRef' ) { |
376 | + return sub { |
377 | + my ($val) = @_; |
378 | + return unless ref($val) eq ref([]); |
379 | + |
380 | + if ($inner_types) { |
381 | + for my $value ( @{$val} ) { |
382 | + return unless $inner_types->($value) |
383 | + } |
384 | + } |
385 | + else { |
386 | + for my $value ( @{$val} ) { |
387 | + return unless $value && ($value eq $type |
388 | + || (Scalar::Util::blessed($value) && $value->isa($type))); |
389 | + } |
390 | + } |
391 | + return 1; |
392 | + }; |
393 | + } |
394 | + elsif ( $aggregate_type eq 'Maybe' ) { |
395 | + return sub { |
396 | + my ($value) = @_; |
397 | + return 1 if ! defined($value); |
398 | + if ($inner_types) { |
399 | + return unless $inner_types->($value) |
400 | + } |
401 | + else { |
402 | + return unless $value eq $type |
403 | + || (Scalar::Util::blessed($value) && $value->isa($type)); |
404 | + } |
405 | + return 1; |
406 | + } |
407 | + } |
408 | + else { |
409 | + Carp::confess("Nested aggregate types are only implemented for ArrayRefs and Maybe"); |
410 | + } |
411 | +} |
412 | + |
413 | +sub _set_package_isa { |
414 | + my ($package, @new_isa) = @_; |
415 | + |
416 | + *{ _glob_for "${package}::ISA" } = [@new_isa]; |
417 | +} |
418 | + |
419 | +sub _set_inherited_metadata { |
420 | + my $class = shift; |
421 | + my $linearized_isa = mro::get_linear_isa($class); |
422 | + my %new_metadata; |
423 | + |
424 | + for my $isa_class (reverse @$linearized_isa) { |
425 | + %new_metadata = ( |
426 | + %new_metadata, |
427 | + %{ $metadata_for{$isa_class} || {} }, |
428 | + ); |
429 | + } |
430 | + $metadata_for{$class} = \%new_metadata; |
431 | +} |
432 | + |
433 | +sub unimport { |
434 | + my $caller = scalar caller(); |
435 | + my $stash = _stash_for( $caller ); |
436 | + |
437 | + delete $stash->{$_} for @{$export_for{$caller}}; |
438 | +} |
439 | + |
440 | +sub Dumper { |
441 | + require Data::Dumper; |
442 | + local $Data::Dumper::Indent = 0; |
443 | + local $Data::Dumper::Sortkeys = 0; |
444 | + local $Data::Dumper::Quotekeys = 0; |
445 | + local $Data::Dumper::Terse = 1; |
446 | + |
447 | + Data::Dumper::Dumper(@_) |
448 | +} |
449 | + |
450 | +BEGIN { |
451 | + if ($] >= 5.010) { |
452 | + { local $@; require mro; } |
453 | + } |
454 | + else { |
455 | + local $@; |
456 | + eval { |
457 | + require MRO::Compat; |
458 | + } or do { |
459 | + *mro::get_linear_isa = *mro::get_linear_isa_dfs = sub { |
460 | + no strict 'refs'; |
461 | + |
462 | + my $classname = shift; |
463 | + |
464 | + my @lin = ($classname); |
465 | + my %stored; |
466 | + foreach my $parent (@{"$classname\::ISA"}) { |
467 | + my $plin = mro::get_linear_isa_dfs($parent); |
468 | + foreach (@$plin) { |
469 | + next if exists $stored{$_}; |
470 | + push(@lin, $_); |
471 | + $stored{$_} = 1; |
472 | + } |
473 | + } |
474 | + return \@lin; |
475 | + }; |
476 | + } |
477 | + } |
478 | +} |
479 | + |
480 | +sub override { |
481 | + my ($methods, $code) = @_; |
482 | + my $caller = scalar caller; |
483 | + |
484 | + for my $method ( ref($methods) ? @$methods : $methods ) { |
485 | + my $full_method = "${caller}::${method}"; |
486 | + *{_glob_for $full_method} = $code; |
487 | + } |
488 | +} |
489 | + |
490 | +} |
491 | +1; |
492 | +} |
493 | +# ########################################################################### |
494 | +# End Mo package |
495 | +# ########################################################################### |
496 | + |
497 | +# ########################################################################### |
498 | # DSNParser package |
499 | # This package is a copy without comments from the original. The original |
500 | # with comments and its test file can be found in the Bazaar repository at, |
501 | @@ -1620,24 +2082,26 @@ |
502 | use Digest::MD5 qw(md5_hex); |
503 | use B qw(); |
504 | |
505 | -require Exporter; |
506 | -our @ISA = qw(Exporter); |
507 | -our %EXPORT_TAGS = (); |
508 | -our @EXPORT = (); |
509 | -our @EXPORT_OK = qw( |
510 | - micro_t |
511 | - percentage_of |
512 | - secs_to_time |
513 | - time_to_secs |
514 | - shorten |
515 | - ts |
516 | - parse_timestamp |
517 | - unix_timestamp |
518 | - any_unix_timestamp |
519 | - make_checksum |
520 | - crc32 |
521 | - encode_json |
522 | -); |
523 | +BEGIN { |
524 | + require Exporter; |
525 | + our @ISA = qw(Exporter); |
526 | + our %EXPORT_TAGS = (); |
527 | + our @EXPORT = (); |
528 | + our @EXPORT_OK = qw( |
529 | + micro_t |
530 | + percentage_of |
531 | + secs_to_time |
532 | + time_to_secs |
533 | + shorten |
534 | + ts |
535 | + parse_timestamp |
536 | + unix_timestamp |
537 | + any_unix_timestamp |
538 | + make_checksum |
539 | + crc32 |
540 | + encode_json |
541 | + ); |
542 | +} |
543 | |
544 | our $mysql_ts = qr/(\d\d)(\d\d)(\d\d) +(\d+):(\d+):(\d+)(\.\d+)?/; |
545 | our $proper_ts = qr/(\d\d\d\d)-(\d\d)-(\d\d)[T ](\d\d):(\d\d):(\d\d)(\.\d+)?/; |
546 | @@ -5448,15 +5912,6 @@ |
547 | $classes->{$class}->{$attrib}->{all}, |
548 | $classes->{$class}->{$attrib} |
549 | ); |
550 | - |
551 | - if ( $args{apdex_t} && $attrib eq 'Query_time' ) { |
552 | - $class_metrics->{$class}->{$attrib}->{apdex_t} = $args{apdex_t}; |
553 | - $class_metrics->{$class}->{$attrib}->{apdex} |
554 | - = $self->calculate_apdex( |
555 | - t => $args{apdex_t}, |
556 | - samples => $classes->{$class}->{$attrib}->{all}, |
557 | - ); |
558 | - } |
559 | } |
560 | } |
561 | } |
562 | @@ -5581,9 +6036,6 @@ |
563 | median => $metrics->{classes}->{$where}->{$attrib}->{median} || 0, |
564 | pct_95 => $metrics->{classes}->{$where}->{$attrib}->{pct_95} || 0, |
565 | stddev => $metrics->{classes}->{$where}->{$attrib}->{stddev} || 0, |
566 | - |
567 | - apdex_t => $metrics->{classes}->{$where}->{$attrib}->{apdex_t}, |
568 | - apdex => $metrics->{classes}->{$where}->{$attrib}->{apdex}, |
569 | }; |
570 | } |
571 | |
572 | @@ -5899,51 +6351,6 @@ |
573 | return $copy; |
574 | } |
575 | |
576 | -sub calculate_apdex { |
577 | - my ( $self, %args ) = @_; |
578 | - my @required_args = qw(t samples); |
579 | - foreach my $arg ( @required_args ) { |
580 | - die "I need a $arg argument" unless $args{$arg}; |
581 | - } |
582 | - my ($t, $samples) = @args{@required_args}; |
583 | - |
584 | - if ( $t <= 0 ) { |
585 | - die "Invalid target threshold (T): $t. T must be greater than zero"; |
586 | - } |
587 | - |
588 | - my $f = 4 * $t; |
589 | - PTDEBUG && _d("Apdex T =", $t, "F =", $f); |
590 | - |
591 | - my $satisfied = 0; |
592 | - my $tolerating = 0; |
593 | - my $frustrated = 0; # just for debug output |
594 | - my $n_samples = 0; |
595 | - BUCKET: |
596 | - for my $bucket ( keys %$samples ) { |
597 | - my $n_responses = $samples->{$bucket}; |
598 | - my $response_time = $buck_vals[$bucket]; |
599 | - |
600 | - if ( $response_time <= $t ) { |
601 | - $satisfied += $n_responses; |
602 | - } |
603 | - elsif ( $response_time <= $f ) { |
604 | - $tolerating += $n_responses; |
605 | - } |
606 | - else { |
607 | - $frustrated += $n_responses; |
608 | - } |
609 | - |
610 | - $n_samples += $n_responses; |
611 | - } |
612 | - |
613 | - my $apdex = sprintf('%.2f', ($satisfied + ($tolerating / 2)) / $n_samples); |
614 | - PTDEBUG && _d($n_samples, "samples,", $satisfied, "satisfied,", |
615 | - $tolerating, "tolerating,", $frustrated, "frustrated, Apdex score:", |
616 | - $apdex); |
617 | - |
618 | - return $apdex; |
619 | -} |
620 | - |
621 | sub _get_value { |
622 | my ( $self, %args ) = @_; |
623 | my ($event, $attrib, $alts) = @args{qw(event attribute alternates)}; |
624 | @@ -5997,8 +6404,7 @@ |
625 | { |
626 | package ReportFormatter; |
627 | |
628 | -use strict; |
629 | -use warnings FATAL => 'all'; |
630 | +use Mo; |
631 | use English qw(-no_match_vars); |
632 | use constant PTDEBUG => $ENV{PTDEBUG} || 0; |
633 | |
634 | @@ -6008,40 +6414,102 @@ |
635 | eval { require Term::ReadKey }; |
636 | my $have_term = $EVAL_ERROR ? 0 : 1; |
637 | |
638 | -sub new { |
639 | - my ( $class, %args ) = @_; |
640 | - my @required_args = qw(); |
641 | - foreach my $arg ( @required_args ) { |
642 | - die "I need a $arg argument" unless $args{$arg}; |
643 | - } |
644 | - my $self = { |
645 | - underline_header => 1, |
646 | - line_prefix => '# ', |
647 | - line_width => 78, |
648 | - column_spacing => ' ', |
649 | - extend_right => 0, |
650 | - truncate_line_mark => '...', |
651 | - column_errors => 'warn', |
652 | - truncate_header_side => 'left', |
653 | - strip_whitespace => 1, |
654 | - %args, # args above can be overriden, args below cannot |
655 | - n_cols => 0, |
656 | - }; |
657 | - |
658 | - if ( ($self->{line_width} || '') eq 'auto' ) { |
659 | + |
660 | +has underline_header => ( |
661 | + is => 'ro', |
662 | + isa => 'Bool', |
663 | + default => sub { 1 }, |
664 | +); |
665 | +has line_prefix => ( |
666 | + is => 'ro', |
667 | + isa => 'Str', |
668 | + default => sub { '# ' }, |
669 | +); |
670 | +has line_width => ( |
671 | + is => 'ro', |
672 | + isa => 'Int', |
673 | + default => sub { 78 }, |
674 | +); |
675 | +has column_spacing => ( |
676 | + is => 'ro', |
677 | + isa => 'Str', |
678 | + default => sub { ' ' }, |
679 | +); |
680 | +has extend_right => ( |
681 | + is => 'ro', |
682 | + isa => 'Bool', |
683 | + default => sub { '' }, |
684 | +); |
685 | +has truncate_line_mark => ( |
686 | + is => 'ro', |
687 | + isa => 'Str', |
688 | + default => sub { '...' }, |
689 | +); |
690 | +has column_errors => ( |
691 | + is => 'ro', |
692 | + isa => 'Str', |
693 | + default => sub { 'warn' }, |
694 | +); |
695 | +has truncate_header_side => ( |
696 | + is => 'ro', |
697 | + isa => 'Str', |
698 | + default => sub { 'left' }, |
699 | +); |
700 | +has strip_whitespace => ( |
701 | + is => 'ro', |
702 | + isa => 'Bool', |
703 | + default => sub { 1 }, |
704 | +); |
705 | +has title => ( |
706 | + is => 'rw', |
707 | + isa => 'Str', |
708 | + predicate => 'has_title', |
709 | +); |
710 | + |
711 | + |
712 | +has n_cols => ( |
713 | + is => 'rw', |
714 | + isa => 'Int', |
715 | + default => sub { 0 }, |
716 | + init_arg => undef, |
717 | +); |
718 | + |
719 | +has cols => ( |
720 | + is => 'ro', |
721 | + isa => 'ArrayRef', |
722 | + init_arg => undef, |
723 | + default => sub { [] }, |
724 | + clearer => 'clear_cols', |
725 | +); |
726 | + |
727 | +has lines => ( |
728 | + is => 'ro', |
729 | + isa => 'ArrayRef', |
730 | + init_arg => undef, |
731 | + default => sub { [] }, |
732 | + clearer => 'clear_lines', |
733 | +); |
734 | + |
735 | +has truncate_headers => ( |
736 | + is => 'rw', |
737 | + isa => 'Bool', |
738 | + default => sub { undef }, |
739 | + init_arg => undef, |
740 | + clearer => 'clear_truncate_headers', |
741 | +); |
742 | + |
743 | +sub BUILDARGS { |
744 | + my $class = shift; |
745 | + my $args = $class->SUPER::BUILDARGS(@_); |
746 | + |
747 | + if ( ($args->{line_width} || '') eq 'auto' ) { |
748 | die "Cannot auto-detect line width because the Term::ReadKey module " |
749 | . "is not installed" unless $have_term; |
750 | - ($self->{line_width}) = GetTerminalSize(); |
751 | + ($args->{line_width}) = GetTerminalSize(); |
752 | + PTDEBUG && _d('Line width:', $args->{line_width}); |
753 | } |
754 | - PTDEBUG && _d('Line width:', $self->{line_width}); |
755 | - |
756 | - return bless $self, $class; |
757 | -} |
758 | - |
759 | -sub set_title { |
760 | - my ( $self, $title ) = @_; |
761 | - $self->{title} = $title; |
762 | - return; |
763 | + |
764 | + return $args; |
765 | } |
766 | |
767 | sub set_columns { |
768 | @@ -6057,7 +6525,7 @@ |
769 | die "Column does not have a name" unless defined $col_name; |
770 | |
771 | if ( $col->{width} ) { |
772 | - $col->{width_pct} = ceil(($col->{width} * 100) / $self->{line_width}); |
773 | + $col->{width_pct} = ceil(($col->{width} * 100) / $self->line_width()); |
774 | PTDEBUG && _d('col:', $col_name, 'width:', $col->{width}, 'chars =', |
775 | $col->{width_pct}, '%'); |
776 | } |
777 | @@ -6084,10 +6552,10 @@ |
778 | |
779 | $col->{right_most} = 1 if $i == $#cols; |
780 | |
781 | - push @{$self->{cols}}, $col; |
782 | + push @{$self->cols}, $col; |
783 | } |
784 | |
785 | - $self->{n_cols} = scalar @cols; |
786 | + $self->n_cols( scalar @cols ); |
787 | |
788 | if ( ($used_width || 0) > 100 ) { |
789 | die "Total width_pct for all columns is >100%"; |
790 | @@ -6097,15 +6565,15 @@ |
791 | my $wid_per_col = int((100 - $used_width) / scalar @auto_width_cols); |
792 | PTDEBUG && _d('Line width left:', (100-$used_width), '%;', |
793 | 'each auto width col:', $wid_per_col, '%'); |
794 | - map { $self->{cols}->[$_]->{width_pct} = $wid_per_col } @auto_width_cols; |
795 | + map { $self->cols->[$_]->{width_pct} = $wid_per_col } @auto_width_cols; |
796 | } |
797 | |
798 | - $min_hdr_wid += ($self->{n_cols} - 1) * length $self->{column_spacing}; |
799 | + $min_hdr_wid += ($self->n_cols() - 1) * length $self->column_spacing(); |
800 | PTDEBUG && _d('min header width:', $min_hdr_wid); |
801 | - if ( $min_hdr_wid > $self->{line_width} ) { |
802 | + if ( $min_hdr_wid > $self->line_width() ) { |
803 | PTDEBUG && _d('Will truncate headers because min header width', |
804 | - $min_hdr_wid, '> line width', $self->{line_width}); |
805 | - $self->{truncate_headers} = 1; |
806 | + $min_hdr_wid, '> line width', $self->line_width()); |
807 | + $self->truncate_headers(1); |
808 | } |
809 | |
810 | return; |
811 | @@ -6114,14 +6582,14 @@ |
812 | sub add_line { |
813 | my ( $self, @vals ) = @_; |
814 | my $n_vals = scalar @vals; |
815 | - if ( $n_vals != $self->{n_cols} ) { |
816 | + if ( $n_vals != $self->n_cols() ) { |
817 | $self->_column_error("Number of values $n_vals does not match " |
818 | - . "number of columns $self->{n_cols}"); |
819 | + . "number of columns " . $self->n_cols()); |
820 | } |
821 | for my $i ( 0..($n_vals-1) ) { |
822 | - my $col = $self->{cols}->[$i]; |
823 | + my $col = $self->cols->[$i]; |
824 | my $val = defined $vals[$i] ? $vals[$i] : $col->{undef_value}; |
825 | - if ( $self->{strip_whitespace} ) { |
826 | + if ( $self->strip_whitespace() ) { |
827 | $val =~ s/^\s+//g; |
828 | $val =~ s/\s+$//; |
829 | $vals[$i] = $val; |
830 | @@ -6130,7 +6598,7 @@ |
831 | $col->{min_val} = min($width, ($col->{min_val} || $width)); |
832 | $col->{max_val} = max($width, ($col->{max_val} || $width)); |
833 | } |
834 | - push @{$self->{lines}}, \@vals; |
835 | + push @{$self->lines}, \@vals; |
836 | return; |
837 | } |
838 | |
839 | @@ -6138,26 +6606,28 @@ |
840 | my ( $self, %args ) = @_; |
841 | |
842 | $self->_calculate_column_widths(); |
843 | - $self->_truncate_headers() if $self->{truncate_headers}; |
844 | + if ( $self->truncate_headers() ) { |
845 | + $self->_truncate_headers(); |
846 | + } |
847 | $self->_truncate_line_values(%args); |
848 | |
849 | my @col_fmts = $self->_make_column_formats(); |
850 | - my $fmt = ($self->{line_prefix} || '') |
851 | - . join($self->{column_spacing}, @col_fmts); |
852 | + my $fmt = $self->line_prefix() |
853 | + . join($self->column_spacing(), @col_fmts); |
854 | PTDEBUG && _d('Format:', $fmt); |
855 | |
856 | (my $hdr_fmt = $fmt) =~ s/%([^-])/%-$1/g; |
857 | |
858 | my @lines; |
859 | - push @lines, sprintf "$self->{line_prefix}$self->{title}" if $self->{title}; |
860 | + push @lines, $self->line_prefix() . $self->title() if $self->has_title(); |
861 | push @lines, $self->_truncate_line( |
862 | - sprintf($hdr_fmt, map { $_->{name} } @{$self->{cols}}), |
863 | + sprintf($hdr_fmt, map { $_->{name} } @{$self->cols}), |
864 | strip => 1, |
865 | mark => '', |
866 | ); |
867 | |
868 | - if ( $self->{underline_header} ) { |
869 | - my @underlines = map { '=' x $_->{print_width} } @{$self->{cols}}; |
870 | + if ( $self->underline_header() ) { |
871 | + my @underlines = map { '=' x $_->{print_width} } @{$self->cols}; |
872 | push @lines, $self->_truncate_line( |
873 | sprintf($fmt, map { $_ || '' } @underlines), |
874 | mark => '', |
875 | @@ -6168,19 +6638,23 @@ |
876 | my $vals = $_; |
877 | my $i = 0; |
878 | my @vals = map { |
879 | - my $val = defined $_ ? $_ : $self->{cols}->[$i++]->{undef_value}; |
880 | + my $val = defined $_ ? $_ : $self->cols->[$i++]->{undef_value}; |
881 | $val = '' if !defined $val; |
882 | $val =~ s/\n/ /g; |
883 | $val; |
884 | } @$vals; |
885 | my $line = sprintf($fmt, @vals); |
886 | - if ( $self->{extend_right} ) { |
887 | + if ( $self->extend_right() ) { |
888 | $line; |
889 | } |
890 | else { |
891 | $self->_truncate_line($line); |
892 | } |
893 | - } @{$self->{lines}}; |
894 | + } @{$self->lines}; |
895 | + |
896 | + $self->clear_cols(); |
897 | + $self->clear_lines(); |
898 | + $self->clear_truncate_headers(); |
899 | |
900 | return join("\n", @lines) . "\n"; |
901 | } |
902 | @@ -6188,7 +6662,7 @@ |
903 | sub truncate_value { |
904 | my ( $self, $col, $val, $width, $side ) = @_; |
905 | return $val if length $val <= $width; |
906 | - return $val if $col->{right_most} && $self->{extend_right}; |
907 | + return $val if $col->{right_most} && $self->extend_right(); |
908 | $side ||= $col->{truncate_side}; |
909 | my $mark = $col->{truncate_mark}; |
910 | if ( $side eq 'right' ) { |
911 | @@ -6208,8 +6682,8 @@ |
912 | my ( $self ) = @_; |
913 | |
914 | my $extra_space = 0; |
915 | - foreach my $col ( @{$self->{cols}} ) { |
916 | - my $print_width = int($self->{line_width} * ($col->{width_pct} / 100)); |
917 | + foreach my $col ( @{$self->cols} ) { |
918 | + my $print_width = int($self->line_width() * ($col->{width_pct} / 100)); |
919 | |
920 | PTDEBUG && _d('col:', $col->{name}, 'width pct:', $col->{width_pct}, |
921 | 'char width:', $print_width, |
922 | @@ -6233,7 +6707,7 @@ |
923 | |
924 | PTDEBUG && _d('Extra space:', $extra_space); |
925 | while ( $extra_space-- ) { |
926 | - foreach my $col ( @{$self->{cols}} ) { |
927 | + foreach my $col ( @{$self->cols} ) { |
928 | if ( $col->{auto_width} |
929 | && ( $col->{print_width} < $col->{max_val} |
930 | || $col->{print_width} < $col->{header_width}) |
931 | @@ -6248,8 +6722,8 @@ |
932 | |
933 | sub _truncate_headers { |
934 | my ( $self, $col ) = @_; |
935 | - my $side = $self->{truncate_header_side}; |
936 | - foreach my $col ( @{$self->{cols}} ) { |
937 | + my $side = $self->truncate_header_side(); |
938 | + foreach my $col ( @{$self->cols} ) { |
939 | my $col_name = $col->{name}; |
940 | my $print_width = $col->{print_width}; |
941 | next if length $col_name <= $print_width; |
942 | @@ -6262,10 +6736,10 @@ |
943 | |
944 | sub _truncate_line_values { |
945 | my ( $self, %args ) = @_; |
946 | - my $n_vals = $self->{n_cols} - 1; |
947 | - foreach my $vals ( @{$self->{lines}} ) { |
948 | + my $n_vals = $self->n_cols() - 1; |
949 | + foreach my $vals ( @{$self->lines} ) { |
950 | for my $i ( 0..$n_vals ) { |
951 | - my $col = $self->{cols}->[$i]; |
952 | + my $col = $self->cols->[$i]; |
953 | my $val = defined $vals->[$i] ? $vals->[$i] : $col->{undef_value}; |
954 | my $width = length $val; |
955 | |
956 | @@ -6291,9 +6765,9 @@ |
957 | sub _make_column_formats { |
958 | my ( $self ) = @_; |
959 | my @col_fmts; |
960 | - my $n_cols = $self->{n_cols} - 1; |
961 | + my $n_cols = $self->n_cols() - 1; |
962 | for my $i ( 0..$n_cols ) { |
963 | - my $col = $self->{cols}->[$i]; |
964 | + my $col = $self->cols->[$i]; |
965 | |
966 | my $width = $col->{right_most} && !$col->{right_justify} ? '' |
967 | : $col->{print_width}; |
968 | @@ -6306,12 +6780,12 @@ |
969 | |
970 | sub _truncate_line { |
971 | my ( $self, $line, %args ) = @_; |
972 | - my $mark = defined $args{mark} ? $args{mark} : $self->{truncate_line_mark}; |
973 | + my $mark = defined $args{mark} ? $args{mark} : $self->truncate_line_mark(); |
974 | if ( $line ) { |
975 | $line =~ s/\s+$// if $args{strip}; |
976 | my $len = length($line); |
977 | - if ( $len > $self->{line_width} ) { |
978 | - $line = substr($line, 0, $self->{line_width} - length $mark); |
979 | + if ( $len > $self->line_width() ) { |
980 | + $line = substr($line, 0, $self->line_width() - length $mark); |
981 | $line .= $mark if $mark; |
982 | } |
983 | } |
984 | @@ -6321,7 +6795,7 @@ |
985 | sub _column_error { |
986 | my ( $self, $err ) = @_; |
987 | my $msg = "Column error: $err"; |
988 | - $self->{column_errors} eq 'die' ? die $msg : warn $msg; |
989 | + $self->column_errors() eq 'die' ? die $msg : warn $msg; |
990 | return; |
991 | } |
992 | |
993 | @@ -6350,8 +6824,7 @@ |
994 | { |
995 | package QueryReportFormatter; |
996 | |
997 | -use strict; |
998 | -use warnings FATAL => 'all'; |
999 | +use Mo; |
1000 | use English qw(-no_match_vars); |
1001 | use POSIX qw(floor); |
1002 | |
1003 | @@ -6364,25 +6837,68 @@ |
1004 | use constant LINE_LENGTH => 74; |
1005 | use constant MAX_STRING_LENGTH => 10; |
1006 | |
1007 | -sub new { |
1008 | - my ( $class, %args ) = @_; |
1009 | - foreach my $arg ( qw(OptionParser QueryRewriter Quoter) ) { |
1010 | - die "I need a $arg argument" unless $args{$arg}; |
1011 | +{ local $EVAL_ERROR; eval { require Quoter } }; |
1012 | +{ local $EVAL_ERROR; eval { require ReportFormatter } }; |
1013 | + |
1014 | +has Quoter => ( |
1015 | + is => 'ro', |
1016 | + isa => 'Quoter', |
1017 | + default => sub { Quoter->new() }, |
1018 | +); |
1019 | + |
1020 | +has label_width => ( |
1021 | + is => 'ro', |
1022 | + isa => 'Int', |
1023 | +); |
1024 | + |
1025 | +has global_headers => ( |
1026 | + is => 'ro', |
1027 | + isa => 'ArrayRef', |
1028 | + default => sub { [qw( total min max avg 95% stddev median)] }, |
1029 | +); |
1030 | + |
1031 | +has event_headers => ( |
1032 | + is => 'ro', |
1033 | + isa => 'ArrayRef', |
1034 | + default => sub { [qw(pct total min max avg 95% stddev median)] }, |
1035 | +); |
1036 | + |
1037 | +has ReportFormatter => ( |
1038 | + is => 'ro', |
1039 | + isa => 'ReportFormatter', |
1040 | + builder => '_build_report_formatter', |
1041 | +); |
1042 | + |
1043 | +sub _build_report_formatter { |
1044 | + return ReportFormatter->new( |
1045 | + line_width => LINE_LENGTH, |
1046 | + extend_right => 1, |
1047 | + ); |
1048 | +} |
1049 | + |
1050 | +sub BUILDARGS { |
1051 | + my $class = shift; |
1052 | + my $args = $class->SUPER::BUILDARGS(@_); |
1053 | + |
1054 | + foreach my $arg ( qw(OptionParser QueryRewriter) ) { |
1055 | + die "I need a $arg argument" unless $args->{$arg}; |
1056 | } |
1057 | |
1058 | - my $label_width = $args{label_width} || 12; |
1059 | + my $label_width = $args->{label_width} ||= 12; |
1060 | PTDEBUG && _d('Label width:', $label_width); |
1061 | |
1062 | - my $cheat_width = $label_width + 1; |
1063 | - |
1064 | + my $o = delete $args->{OptionParser}; |
1065 | my $self = { |
1066 | - %args, |
1067 | - label_width => $label_width, |
1068 | + %$args, |
1069 | + options => { |
1070 | + show_all => $o->get('show-all'), |
1071 | + shorten => $o->get('shorten'), |
1072 | + report_all => $o->get('report-all'), |
1073 | + report_histogram => $o->get('report-histogram'), |
1074 | + }, |
1075 | num_format => "# %-${label_width}s %3s %7s %7s %7s %7s %7s %7s %7s", |
1076 | bool_format => "# %-${label_width}s %3d%% yes, %3d%% no", |
1077 | string_format => "# %-${label_width}s %s", |
1078 | - global_headers => [qw( total min max avg 95% stddev median)], |
1079 | - event_headers => [qw(pct total min max avg 95% stddev median)], |
1080 | hidden_attrib => { # Don't sort/print these attribs in the reports. |
1081 | arg => 1, # They're usually handled specially, or not |
1082 | fingerprint => 1, # printed at all. |
1083 | @@ -6390,18 +6906,7 @@ |
1084 | ts => 1, |
1085 | }, |
1086 | }; |
1087 | - return bless $self, $class; |
1088 | -} |
1089 | - |
1090 | -sub set_report_formatter { |
1091 | - my ( $self, %args ) = @_; |
1092 | - my @required_args = qw(report formatter); |
1093 | - foreach my $arg ( @required_args ) { |
1094 | - die "I need a $arg argument" unless exists $args{$arg}; |
1095 | - } |
1096 | - my ($report, $formatter) = @args{@required_args}; |
1097 | - $self->{formatter_for}->{$report} = $formatter; |
1098 | - return; |
1099 | + return $self; |
1100 | } |
1101 | |
1102 | sub print_reports { |
1103 | @@ -6507,7 +7012,7 @@ |
1104 | shorten(scalar keys %{$results->{classes}}, d=>1_000), |
1105 | shorten($qps || 0, d=>1_000), |
1106 | shorten($conc || 0, d=>1_000)); |
1107 | - $line .= ('_' x (LINE_LENGTH - length($line) + $self->{label_width} - 12)); |
1108 | + $line .= ('_' x (LINE_LENGTH - length($line) + $self->label_width() - 12)); |
1109 | push @result, $line; |
1110 | |
1111 | if ( my $ts = $results->{globals}->{ts} ) { |
1112 | @@ -6568,18 +7073,70 @@ |
1113 | return join("\n", map { s/\s+$//; $_ } @result) . "\n"; |
1114 | } |
1115 | |
1116 | +sub query_report_values { |
1117 | + my ($self, %args) = @_; |
1118 | + foreach my $arg ( qw(ea worst orderby groupby) ) { |
1119 | + die "I need a $arg argument" unless defined $arg; |
1120 | + } |
1121 | + my $ea = $args{ea}; |
1122 | + my $groupby = $args{groupby}; |
1123 | + my $worst = $args{worst}; |
1124 | + |
1125 | + my $q = $self->Quoter; |
1126 | + my $qv = $self->{QueryReview}; |
1127 | + my $qr = $self->{QueryRewriter}; |
1128 | + |
1129 | + my @values; |
1130 | + ITEM: |
1131 | + foreach my $top_event ( @$worst ) { |
1132 | + my $item = $top_event->[0]; |
1133 | + my $reason = $args{explain_why} ? $top_event->[1] : ''; |
1134 | + my $rank = $top_event->[2]; |
1135 | + my $stats = $ea->results->{classes}->{$item}; |
1136 | + my $sample = $ea->results->{samples}->{$item}; |
1137 | + my $samp_query = $sample->{arg} || ''; |
1138 | + |
1139 | + my %item_vals = ( |
1140 | + item => $item, |
1141 | + samp_query => $samp_query, |
1142 | + rank => ($rank || 0), |
1143 | + reason => $reason, |
1144 | + ); |
1145 | + |
1146 | + my $review_vals; |
1147 | + if ( $qv ) { |
1148 | + $review_vals = $qv->get_review_info($item); |
1149 | + next ITEM if $review_vals->{reviewed_by} && !$self->{options}->{report_all}; |
1150 | + for my $col ( $qv->review_cols() ) { |
1151 | + push @{$item_vals{review_vals}}, [$col, $review_vals->{$col}]; |
1152 | + } |
1153 | + } |
1154 | + |
1155 | + $item_vals{default_db} = $sample->{db} ? $sample->{db} |
1156 | + : $stats->{db}->{unq} ? keys %{$stats->{db}->{unq}} |
1157 | + : undef; |
1158 | + $item_vals{tables} = [$self->{QueryParser}->extract_tables( |
1159 | + query => $samp_query, |
1160 | + default_db => $item_vals{default_db}, |
1161 | + Quoter => $self->Quoter, |
1162 | + )]; |
1163 | + |
1164 | + if ( $samp_query && ($args{variations} && @{$args{variations}}) ) { |
1165 | + $item_vals{crc} = crc32($samp_query); |
1166 | + } |
1167 | + |
1168 | + push @values, \%item_vals; |
1169 | + } |
1170 | + return \@values; |
1171 | +} |
1172 | + |
1173 | sub query_report { |
1174 | my ( $self, %args ) = @_; |
1175 | - foreach my $arg ( qw(ea worst orderby groupby) ) { |
1176 | - die "I need a $arg argument" unless defined $arg; |
1177 | - } |
1178 | + |
1179 | my $ea = $args{ea}; |
1180 | my $groupby = $args{groupby}; |
1181 | - my $worst = $args{worst}; |
1182 | + my $report_values = $self->query_report_values(%args); |
1183 | |
1184 | - my $o = $self->{OptionParser}; |
1185 | - my $q = $self->{Quoter}; |
1186 | - my $qv = $self->{QueryReview}; |
1187 | my $qr = $self->{QueryRewriter}; |
1188 | |
1189 | my $report = ''; |
1190 | @@ -6596,55 +7153,31 @@ |
1191 | ); |
1192 | |
1193 | ITEM: |
1194 | - foreach my $top_event ( @$worst ) { |
1195 | - my $item = $top_event->[0]; |
1196 | - my $reason = $args{explain_why} ? $top_event->[1] : ''; |
1197 | - my $rank = $top_event->[2]; |
1198 | - my $stats = $ea->results->{classes}->{$item}; |
1199 | - my $sample = $ea->results->{samples}->{$item}; |
1200 | - my $samp_query = $sample->{arg} || ''; |
1201 | - |
1202 | - my $review_vals; |
1203 | - if ( $qv ) { |
1204 | - $review_vals = $qv->get_review_info($item); |
1205 | - next ITEM if $review_vals->{reviewed_by} && !$o->get('report-all'); |
1206 | - } |
1207 | - |
1208 | - my ($default_db) = $sample->{db} ? $sample->{db} |
1209 | - : $stats->{db}->{unq} ? keys %{$stats->{db}->{unq}} |
1210 | - : undef; |
1211 | - my @tables; |
1212 | - if ( $o->get('for-explain') ) { |
1213 | - @tables = $self->{QueryParser}->extract_tables( |
1214 | - query => $samp_query, |
1215 | - default_db => $default_db, |
1216 | - Quoter => $self->{Quoter}, |
1217 | - ); |
1218 | - } |
1219 | - |
1220 | - $report .= "\n" if $rank > 1; # space between each event report |
1221 | + foreach my $vals ( @$report_values ) { |
1222 | + my $item = $vals->{item}; |
1223 | + $report .= "\n" if $vals->{rank} > 1; # space between each event report |
1224 | $report .= $self->event_report( |
1225 | %args, |
1226 | item => $item, |
1227 | - sample => $sample, |
1228 | - rank => $rank, |
1229 | - reason => $reason, |
1230 | + sample => $ea->results->{samples}->{$item}, |
1231 | + rank => $vals->{rank}, |
1232 | + reason => $vals->{reason}, |
1233 | attribs => $attribs, |
1234 | - db => $default_db, |
1235 | + db => $vals->{default_db}, |
1236 | ); |
1237 | |
1238 | - if ( $o->get('report-histogram') ) { |
1239 | + if ( $self->{options}->{report_histogram} ) { |
1240 | $report .= $self->chart_distro( |
1241 | %args, |
1242 | - attrib => $o->get('report-histogram'), |
1243 | - item => $item, |
1244 | + attrib => $self->{options}->{report_histogram}, |
1245 | + item => $vals->{item}, |
1246 | ); |
1247 | } |
1248 | |
1249 | - if ( $qv && $review_vals ) { |
1250 | + if ( $vals->{review_vals} ) { |
1251 | $report .= "# Review information\n"; |
1252 | - foreach my $col ( $qv->review_cols() ) { |
1253 | - my $val = $review_vals->{$col}; |
1254 | + foreach my $elem ( @{$vals->{review_vals}} ) { |
1255 | + my ($col, $val) = @$elem; |
1256 | if ( !$val || $val ne '0000-00-00 00:00:00' ) { # issue 202 |
1257 | $report .= sprintf "# %13s: %-s\n", $col, ($val ? $val : ''); |
1258 | } |
1259 | @@ -6652,18 +7185,15 @@ |
1260 | } |
1261 | |
1262 | if ( $groupby eq 'fingerprint' ) { |
1263 | - $samp_query = $qr->shorten($samp_query, $o->get('shorten')) |
1264 | - if $o->get('shorten'); |
1265 | - |
1266 | - $report .= "# Fingerprint\n# $item\n" |
1267 | - if $o->get('fingerprints'); |
1268 | - |
1269 | - $report .= $self->tables_report(@tables) |
1270 | - if $o->get('for-explain'); |
1271 | - |
1272 | - if ( $samp_query && ($args{variations} && @{$args{variations}}) ) { |
1273 | - my $crc = crc32($samp_query); |
1274 | - $report.= "# CRC " . ($crc ? $crc % 1_000 : "") . "\n"; |
1275 | + my $samp_query = $qr->shorten($vals->{samp_query}, $self->{options}->{shorten}) |
1276 | + if $self->{options}->{shorten}; |
1277 | + |
1278 | + PTDEBUG && _d("Fingerprint\n# $vals->{item}\n"); |
1279 | + |
1280 | + $report .= $self->tables_report(@{$vals->{tables}}); |
1281 | + |
1282 | + if ( $vals->{crc} ) { |
1283 | + $report.= "# CRC " . ($vals->{crc} % 1_000) . "\n"; |
1284 | } |
1285 | |
1286 | my $log_type = $args{log_type} || ''; |
1287 | @@ -6677,14 +7207,13 @@ |
1288 | } |
1289 | else { |
1290 | $report .= "# EXPLAIN /*!50100 PARTITIONS*/\n$samp_query${mark}\n"; |
1291 | - $report .= $self->explain_report($samp_query, $default_db); |
1292 | + $report .= $self->explain_report($samp_query, $vals->{default_db}); |
1293 | } |
1294 | } |
1295 | else { |
1296 | $report .= "$samp_query${mark}\n"; |
1297 | my $converted = $qr->convert_to_select($samp_query); |
1298 | - if ( $o->get('for-explain') |
1299 | - && $converted |
1300 | + if ( $converted |
1301 | && $converted =~ m/^[\(\s]*select/i ) { |
1302 | $report .= "# Converted for EXPLAIN\n# EXPLAIN /*!50100 PARTITIONS*/\n$converted${mark}\n"; |
1303 | } |
1304 | @@ -6692,7 +7221,7 @@ |
1305 | } |
1306 | else { |
1307 | if ( $groupby eq 'tables' ) { |
1308 | - my ( $db, $tbl ) = $q->split_unquote($item); |
1309 | + my ( $db, $tbl ) = $self->Quoter->split_unquote($item); |
1310 | $report .= $self->tables_report([$db, $tbl]); |
1311 | } |
1312 | $report .= "$item\n"; |
1313 | @@ -6702,20 +7231,19 @@ |
1314 | return $report; |
1315 | } |
1316 | |
1317 | -sub event_report { |
1318 | - my ( $self, %args ) = @_; |
1319 | - foreach my $arg ( qw(ea item orderby) ) { |
1320 | - die "I need a $arg argument" unless defined $args{$arg}; |
1321 | - } |
1322 | - my $ea = $args{ea}; |
1323 | - my $item = $args{item}; |
1324 | +sub event_report_values { |
1325 | + my ($self, %args) = @_; |
1326 | + |
1327 | + my $ea = $args{ea}; |
1328 | + my $item = $args{item}; |
1329 | my $orderby = $args{orderby}; |
1330 | my $results = $ea->results(); |
1331 | - my $o = $self->{OptionParser}; |
1332 | - my @result; |
1333 | + |
1334 | + my %vals; |
1335 | |
1336 | my $store = $results->{classes}->{$item}; |
1337 | - return "# No such event $item\n" unless $store; |
1338 | + |
1339 | + return unless $store; |
1340 | |
1341 | my $global_cnt = $results->{globals}->{$orderby}->{cnt}; |
1342 | my $class_cnt = $store->{$orderby}->{cnt}; |
1343 | @@ -6734,67 +7262,25 @@ |
1344 | }; |
1345 | } |
1346 | |
1347 | - my $line = sprintf( |
1348 | - '# %s %d: %s QPS, %sx concurrency, ID 0x%s at byte %.f ', |
1349 | - ($ea->{groupby} eq 'fingerprint' ? 'Query' : 'Item'), |
1350 | - $args{rank} || 0, |
1351 | - shorten($qps || 0, d=>1_000), |
1352 | - shorten($conc || 0, d=>1_000), |
1353 | - make_checksum($item), |
1354 | - $results->{samples}->{$item}->{pos_in_log} || 0, |
1355 | - ); |
1356 | - $line .= ('_' x (LINE_LENGTH - length($line) + $self->{label_width} - 12)); |
1357 | - push @result, $line; |
1358 | - |
1359 | - if ( $args{reason} ) { |
1360 | - push @result, |
1361 | - "# This item is included in the report because it matches " |
1362 | - . ($args{reason} eq 'top' ? '--limit.' : '--outliers.'); |
1363 | - } |
1364 | - |
1365 | - { |
1366 | + $vals{groupby} = $ea->{groupby}; |
1367 | + $vals{qps} = $qps || 0; |
1368 | + $vals{concurrency} = $conc || 0; |
1369 | + $vals{checksum} = make_checksum($item); |
1370 | + $vals{pos_in_log} = $results->{samples}->{$item}->{pos_in_log} || 0; |
1371 | + $vals{reason} = $args{reason}; |
1372 | + $vals{variance_to_mean} = do { |
1373 | my $query_time = $ea->metrics(where => $item, attrib => 'Query_time'); |
1374 | - push @result, |
1375 | - sprintf("# Scores: Apdex = %s [%3.1f]%s, V/M = %.2f", |
1376 | - (defined $query_time->{apdex} ? "$query_time->{apdex}" : "NS"), |
1377 | - ($query_time->{apdex_t} || 0), |
1378 | - ($query_time->{cnt} < 100 ? "*" : ""), |
1379 | - ($query_time->{stddev}**2 / ($query_time->{avg} || 1)), |
1380 | - ); |
1381 | - } |
1382 | - |
1383 | - if ( $o->get('explain') && $results->{samples}->{$item}->{arg} ) { |
1384 | - eval { |
1385 | - my $sparkline = $self->explain_sparkline( |
1386 | - $results->{samples}->{$item}->{arg}, $args{db}); |
1387 | - push @result, "# EXPLAIN sparkline: $sparkline\n"; |
1388 | - }; |
1389 | - if ( $EVAL_ERROR ) { |
1390 | - PTDEBUG && _d("Failed to get EXPLAIN sparkline:", $EVAL_ERROR); |
1391 | - } |
1392 | - } |
1393 | - |
1394 | - if ( my $attrib = $o->get('report-histogram') ) { |
1395 | - my $sparkline = $self->distro_sparkline( |
1396 | - %args, |
1397 | - attrib => $attrib, |
1398 | - item => $item, |
1399 | - ); |
1400 | - if ( $sparkline ) { |
1401 | - push @result, "# $attrib sparkline: |$sparkline|"; |
1402 | - } |
1403 | - } |
1404 | - |
1405 | - if ( my $ts = $store->{ts} ) { |
1406 | - my $time_range = $self->format_time_range($ts) || "unknown"; |
1407 | - push @result, "# Time range: $time_range"; |
1408 | - } |
1409 | - |
1410 | - push @result, $self->make_event_header(); |
1411 | - |
1412 | - push @result, |
1413 | - sprintf $self->{num_format}, 'Count', |
1414 | - percentage_of($class_cnt, $global_cnt), $class_cnt, map { '' } (1..8); |
1415 | + $query_time->{stddev}**2 / ($query_time->{avg} || 1) |
1416 | + }; |
1417 | + |
1418 | + $vals{counts} = { |
1419 | + class_cnt => $class_cnt, |
1420 | + global_cnt => $global_cnt, |
1421 | + }; |
1422 | + |
1423 | + if ( my $ts = $store->{ts}) { |
1424 | + $vals{time_range} = $self->format_time_range($ts) || "unknown"; |
1425 | + } |
1426 | |
1427 | my $attribs = $args{attribs}; |
1428 | if ( !$attribs ) { |
1429 | @@ -6804,10 +7290,9 @@ |
1430 | ); |
1431 | } |
1432 | |
1433 | + $vals{attributes} = { map { $_ => [] } qw(num innodb bool string) }; |
1434 | + |
1435 | foreach my $type ( qw(num innodb) ) { |
1436 | - if ( $type eq 'innodb' && @{$attribs->{$type}} ) { |
1437 | - push @result, "# InnoDB:"; |
1438 | - }; |
1439 | |
1440 | NUM_ATTRIB: |
1441 | foreach my $attrib ( @{$attribs->{$type}} ) { |
1442 | @@ -6827,15 +7312,12 @@ |
1443 | $pct = percentage_of( |
1444 | $vals->{sum}, $results->{globals}->{$attrib}->{sum}); |
1445 | |
1446 | - push @result, |
1447 | - sprintf $self->{num_format}, |
1448 | - $self->make_label($attrib), $pct, @values; |
1449 | + push @{$vals{attributes}{$type}}, |
1450 | + [ $attrib, $pct, @values ]; |
1451 | } |
1452 | } |
1453 | |
1454 | if ( @{$attribs->{bool}} ) { |
1455 | - push @result, "# Boolean:"; |
1456 | - my $printed_bools = 0; |
1457 | BOOL_ATTRIB: |
1458 | foreach my $attrib ( @{$attribs->{bool}} ) { |
1459 | next BOOL_ATTRIB unless exists $store->{$attrib}; |
1460 | @@ -6843,33 +7325,115 @@ |
1461 | next unless scalar %$vals; |
1462 | |
1463 | if ( $vals->{sum} > 0 ) { |
1464 | - push @result, |
1465 | - sprintf $self->{bool_format}, |
1466 | - $self->make_label($attrib), $self->bool_percents($vals); |
1467 | - $printed_bools = 1; |
1468 | + push @{$vals{attributes}{bool}}, |
1469 | + [ $attrib, $self->bool_percents($vals) ]; |
1470 | } |
1471 | } |
1472 | - pop @result unless $printed_bools; |
1473 | + } |
1474 | + |
1475 | + if ( @{$attribs->{string}} ) { |
1476 | + STRING_ATTRIB: |
1477 | + foreach my $attrib ( @{$attribs->{string}} ) { |
1478 | + next STRING_ATTRIB unless exists $store->{$attrib}; |
1479 | + my $vals = $store->{$attrib}; |
1480 | + next unless scalar %$vals; |
1481 | + |
1482 | + push @{$vals{attributes}{string}}, |
1483 | + [ $attrib, $vals ]; |
1484 | + } |
1485 | + } |
1486 | + |
1487 | + |
1488 | + return \%vals; |
1489 | +} |
1490 | + |
1491 | + |
1492 | +sub event_report { |
1493 | + my ( $self, %args ) = @_; |
1494 | + foreach my $arg ( qw(ea item orderby) ) { |
1495 | + die "I need a $arg argument" unless defined $args{$arg}; |
1496 | + } |
1497 | + |
1498 | + my $item = $args{item}; |
1499 | + my $val = $self->event_report_values(%args); |
1500 | + my @result; |
1501 | + |
1502 | + return "# No such event $item\n" unless $val; |
1503 | + |
1504 | + my $line = sprintf( |
1505 | + '# %s %d: %s QPS, %sx concurrency, ID 0x%s at byte %.f ', |
1506 | + ($val->{groupby} eq 'fingerprint' ? 'Query' : 'Item'), |
1507 | + $args{rank} || 0, |
1508 | + shorten($val->{qps}, d=>1_000), |
1509 | + shorten($val->{concurrency}, d=>1_000), |
1510 | + $val->{checksum}, |
1511 | + $val->{pos_in_log}, |
1512 | + ); |
1513 | + $line .= ('_' x (LINE_LENGTH - length($line) + $self->label_width() - 12)); |
1514 | + push @result, $line; |
1515 | + |
1516 | + if ( $val->{reason} ) { |
1517 | + push @result, |
1518 | + "# This item is included in the report because it matches " |
1519 | + . ($val->{reason} eq 'top' ? '--limit.' : '--outliers.'); |
1520 | + } |
1521 | + |
1522 | + push @result, |
1523 | + sprintf("# Scores: V/M = %.2f", $val->{variance_to_mean} ); |
1524 | + |
1525 | + if ( $val->{time_range} ) { |
1526 | + push @result, "# Time range: $val->{time_range}"; |
1527 | + } |
1528 | + |
1529 | + push @result, $self->make_event_header(); |
1530 | + |
1531 | + push @result, |
1532 | + sprintf $self->{num_format}, 'Count', |
1533 | + percentage_of($val->{counts}{class_cnt}, $val->{counts}{global_cnt}), |
1534 | + $val->{counts}{class_cnt}, |
1535 | + map { '' } (1..8); |
1536 | + |
1537 | + |
1538 | + my $attribs = $val->{attributes}; |
1539 | + |
1540 | + foreach my $type ( qw(num innodb) ) { |
1541 | + if ( $type eq 'innodb' && @{$attribs->{$type}} ) { |
1542 | + push @result, "# InnoDB:"; |
1543 | + }; |
1544 | + |
1545 | + NUM_ATTRIB: |
1546 | + foreach my $attrib ( @{$attribs->{$type}} ) { |
1547 | + my ($attrib_name, @vals) = @$attrib; |
1548 | + push @result, |
1549 | + sprintf $self->{num_format}, |
1550 | + $self->make_label($attrib_name), @vals; |
1551 | + } |
1552 | + } |
1553 | + |
1554 | + if ( @{$attribs->{bool}} ) { |
1555 | + push @result, "# Boolean:"; |
1556 | + BOOL_ATTRIB: |
1557 | + foreach my $attrib ( @{$attribs->{bool}} ) { |
1558 | + my ($attrib_name, @vals) = @$attrib; |
1559 | + push @result, |
1560 | + sprintf $self->{bool_format}, |
1561 | + $self->make_label($attrib_name), @vals; |
1562 | + } |
1563 | } |
1564 | |
1565 | if ( @{$attribs->{string}} ) { |
1566 | push @result, "# String:"; |
1567 | - my $printed_strings = 0; |
1568 | STRING_ATTRIB: |
1569 | foreach my $attrib ( @{$attribs->{string}} ) { |
1570 | - next STRING_ATTRIB unless exists $store->{$attrib}; |
1571 | - my $vals = $store->{$attrib}; |
1572 | - next unless scalar %$vals; |
1573 | - |
1574 | + my ($attrib_name, $vals) = @$attrib; |
1575 | push @result, |
1576 | sprintf $self->{string_format}, |
1577 | - $self->make_label($attrib), |
1578 | - $self->format_string_list($attrib, $vals, $class_cnt); |
1579 | - $printed_strings = 1; |
1580 | + $self->make_label($attrib_name), |
1581 | + $self->format_string_list($attrib_name, $vals, $val->{counts}{class_cnt}); |
1582 | } |
1583 | - pop @result unless $printed_strings; |
1584 | } |
1585 | |
1586 | + |
1587 | return join("\n", map { s/\s+$//; $_ } @result) . "\n"; |
1588 | } |
1589 | |
1590 | @@ -6921,73 +7485,6 @@ |
1591 | return join("\n", @results) . "\n"; |
1592 | } |
1593 | |
1594 | - |
1595 | -sub distro_sparkline { |
1596 | - my ( $self, %args ) = @_; |
1597 | - foreach my $arg ( qw(ea item attrib) ) { |
1598 | - die "I need a $arg argument" unless defined $args{$arg}; |
1599 | - } |
1600 | - my $ea = $args{ea}; |
1601 | - my $item = $args{item}; |
1602 | - my $attrib = $args{attrib}; |
1603 | - |
1604 | - my $results = $ea->results(); |
1605 | - my $store = $results->{classes}->{$item}->{$attrib}; |
1606 | - my $vals = $store->{all}; |
1607 | - |
1608 | - my $all_zeros_sparkline = " " x 8; |
1609 | - |
1610 | - return $all_zeros_sparkline unless defined $vals && scalar %$vals; |
1611 | - |
1612 | - my @buck_tens = $ea->buckets_of(10); |
1613 | - my @distro = map { 0 } (0 .. 7); |
1614 | - my @buckets = map { 0 } (0..999); |
1615 | - map { $buckets[$_] = $vals->{$_} } keys %$vals; |
1616 | - $vals = \@buckets; |
1617 | - map { $distro[$buck_tens[$_]] += $vals->[$_] } (1 .. @$vals - 1); |
1618 | - |
1619 | - my $vals_per_mark; |
1620 | - my $max_val = 0; |
1621 | - my $max_disp_width = 64; |
1622 | - foreach my $n_vals ( @distro ) { |
1623 | - $max_val = $n_vals if $n_vals > $max_val; |
1624 | - } |
1625 | - $vals_per_mark = $max_val / $max_disp_width; |
1626 | - |
1627 | - my ($min, $max); |
1628 | - foreach my $i ( 0 .. $#distro ) { |
1629 | - my $n_vals = $distro[$i]; |
1630 | - my $n_marks = $n_vals / ($vals_per_mark || 1); |
1631 | - $n_marks = 1 if $n_marks < 1 && $n_vals > 0; |
1632 | - |
1633 | - $min = $n_marks if $n_marks && (!$min || $n_marks < $min); |
1634 | - $max = $n_marks if !$max || $n_marks > $max; |
1635 | - } |
1636 | - return $all_zeros_sparkline unless $min && $max; |
1637 | - |
1638 | - |
1639 | - $min = 0 if $min == $max; |
1640 | - my @range_min; |
1641 | - my $d = floor((($max+0.00001)-$min) / 4); |
1642 | - for my $x ( 1..4 ) { |
1643 | - push @range_min, $min + ($d * $x); |
1644 | - } |
1645 | - |
1646 | - my $sparkline = ""; |
1647 | - foreach my $i ( 0 .. $#distro ) { |
1648 | - my $n_vals = $distro[$i]; |
1649 | - my $n_marks = $n_vals / ($vals_per_mark || 1); |
1650 | - $n_marks = 1 if $n_marks < 1 && $n_vals > 0; |
1651 | - $sparkline .= $n_marks <= 0 ? ' ' |
1652 | - : $n_marks <= $range_min[0] ? '_' |
1653 | - : $n_marks <= $range_min[1] ? '.' |
1654 | - : $n_marks <= $range_min[2] ? '-' |
1655 | - : '^'; |
1656 | - } |
1657 | - |
1658 | - return $sparkline; |
1659 | -} |
1660 | - |
1661 | sub profile { |
1662 | my ( $self, %args ) = @_; |
1663 | foreach my $arg ( qw(ea worst groupby) ) { |
1664 | @@ -6999,7 +7496,6 @@ |
1665 | my $groupby = $args{groupby}; |
1666 | |
1667 | my $qr = $self->{QueryRewriter}; |
1668 | - my $o = $self->{OptionParser}; |
1669 | |
1670 | my $results = $ea->results(); |
1671 | my $total_r = $results->{globals}->{Query_time}->{sum} || 0; |
1672 | @@ -7021,40 +7517,20 @@ |
1673 | $qr->distill($samp_query, %{$args{distill_args}}) : $item, |
1674 | id => $groupby eq 'fingerprint' ? make_checksum($item) : '', |
1675 | vmr => ($query_time->{stddev}**2) / ($query_time->{avg} || 1), |
1676 | - apdex => defined $query_time->{apdex} ? $query_time->{apdex} : "NS", |
1677 | ); |
1678 | |
1679 | - if ( $o->get('explain') && $samp_query ) { |
1680 | - my ($default_db) = $sample->{db} ? $sample->{db} |
1681 | - : $stats->{db}->{unq} ? keys %{$stats->{db}->{unq}} |
1682 | - : undef; |
1683 | - eval { |
1684 | - $profile{explain_sparkline} = $self->explain_sparkline( |
1685 | - $samp_query, $default_db); |
1686 | - }; |
1687 | - if ( $EVAL_ERROR ) { |
1688 | - PTDEBUG && _d("Failed to get EXPLAIN sparkline:", $EVAL_ERROR); |
1689 | - } |
1690 | - } |
1691 | - |
1692 | push @profiles, \%profile; |
1693 | } |
1694 | |
1695 | - my $report = $self->{formatter_for}->{profile} || new ReportFormatter( |
1696 | - line_width => LINE_LENGTH, |
1697 | - long_last_column => 1, |
1698 | - extend_right => 1, |
1699 | - ); |
1700 | - $report->set_title('Profile'); |
1701 | + my $report = $self->ReportFormatter(); |
1702 | + $report->title('Profile'); |
1703 | my @cols = ( |
1704 | { name => 'Rank', right_justify => 1, }, |
1705 | { name => 'Query ID', }, |
1706 | { name => 'Response time', right_justify => 1, }, |
1707 | { name => 'Calls', right_justify => 1, }, |
1708 | { name => 'R/Call', right_justify => 1, }, |
1709 | - { name => 'Apdx', right_justify => 1, width => 4, }, |
1710 | { name => 'V/M', right_justify => 1, width => 5, }, |
1711 | - ( $o->get('explain') ? { name => 'EXPLAIN' } : () ), |
1712 | { name => 'Item', }, |
1713 | ); |
1714 | $report->set_columns(@cols); |
1715 | @@ -7070,9 +7546,7 @@ |
1716 | "$rt $rtp", |
1717 | $item->{cnt}, |
1718 | $rc, |
1719 | - $item->{apdex}, |
1720 | $vmr, |
1721 | - ( $o->get('explain') ? $item->{explain_sparkline} || "" : () ), |
1722 | $item->{sample}, |
1723 | ); |
1724 | $report->add_line(@vals); |
1725 | @@ -7098,9 +7572,7 @@ |
1726 | "$rt $rtp", |
1727 | $misc->{cnt}, |
1728 | $rc, |
1729 | - 'NS', # Apdex is not meaningful here |
1730 | '0.0', # variance-to-mean ratio is not meaningful here |
1731 | - ( $o->get('explain') ? "MISC" : () ), |
1732 | "<".scalar @$other." ITEMS>", |
1733 | ); |
1734 | } |
1735 | @@ -7185,12 +7657,8 @@ |
1736 | |
1737 | return unless scalar @prepared; |
1738 | |
1739 | - my $report = $self->{formatter_for}->{prepared} || new ReportFormatter( |
1740 | - line_width => LINE_LENGTH, |
1741 | - long_last_column => 1, |
1742 | - extend_right => 1, |
1743 | - ); |
1744 | - $report->set_title('Prepared statements'); |
1745 | + my $report = $self->ReportFormatter(); |
1746 | + $report->title('Prepared statements'); |
1747 | $report->set_columns( |
1748 | { name => 'Rank', right_justify => 1, }, |
1749 | { name => 'Query ID', }, |
1750 | @@ -7224,11 +7692,11 @@ |
1751 | my @lines; |
1752 | |
1753 | push @lines, |
1754 | - sprintf $self->{num_format}, "Attribute", '', @{$self->{global_headers}}; |
1755 | + sprintf $self->{num_format}, "Attribute", '', @{$self->global_headers()}; |
1756 | |
1757 | push @lines, |
1758 | sprintf $self->{num_format}, |
1759 | - (map { "=" x $_ } $self->{label_width}), |
1760 | + (map { "=" x $_ } $self->label_width()), |
1761 | (map { " " x $_ } qw(3)), # no pct column in global header |
1762 | (map { "=" x $_ } qw(7 7 7 7 7 7 7)); |
1763 | |
1764 | @@ -7242,11 +7710,11 @@ |
1765 | |
1766 | my @lines; |
1767 | push @lines, |
1768 | - sprintf $self->{num_format}, "Attribute", @{$self->{event_headers}}; |
1769 | + sprintf $self->{num_format}, "Attribute", @{$self->event_headers()}; |
1770 | |
1771 | push @lines, |
1772 | sprintf $self->{num_format}, |
1773 | - map { "=" x $_ } ($self->{label_width}, qw(3 7 7 7 7 7 7 7)); |
1774 | + map { "=" x $_ } ($self->label_width(), qw(3 7 7 7 7 7 7 7)); |
1775 | |
1776 | $self->{event_header_lines} = \@lines; |
1777 | return @lines; |
1778 | @@ -7261,7 +7729,7 @@ |
1779 | if ( $val =~ m/^InnoDB/ ) { |
1780 | $val =~ s/^InnoDB //; |
1781 | $val = $val eq 'trx id' ? "InnoDB trxID" |
1782 | - : substr($val, 0, $self->{label_width}); |
1783 | + : substr($val, 0, $self->label_width()); |
1784 | } |
1785 | |
1786 | $val = $val eq 'user' ? 'Users' |
1787 | @@ -7272,7 +7740,7 @@ |
1788 | : $val eq 'bytes' ? 'Query size' |
1789 | : $val eq 'Tmp disk tables' ? 'Tmp disk tbl' |
1790 | : $val eq 'Tmp table sizes' ? 'Tmp tbl size' |
1791 | - : substr($val, 0, $self->{label_width}); |
1792 | + : substr($val, 0, $self->label_width); |
1793 | |
1794 | return $val; |
1795 | } |
1796 | @@ -7286,8 +7754,7 @@ |
1797 | |
1798 | sub format_string_list { |
1799 | my ( $self, $attrib, $vals, $class_cnt ) = @_; |
1800 | - my $o = $self->{OptionParser}; |
1801 | - my $show_all = $o->get('show-all'); |
1802 | + my $show_all = $self->{options}->{show_all}; |
1803 | |
1804 | if ( !exists $vals->{unq} ) { |
1805 | return ($vals->{cnt}); |
1806 | @@ -7417,7 +7884,7 @@ |
1807 | sub tables_report { |
1808 | my ( $self, @tables ) = @_; |
1809 | return '' unless @tables; |
1810 | - my $q = $self->{Quoter}; |
1811 | + my $q = $self->Quoter(); |
1812 | my $tables = ""; |
1813 | foreach my $db_tbl ( @tables ) { |
1814 | my ( $db, $tbl ) = @$db_tbl; |
1815 | @@ -7436,7 +7903,7 @@ |
1816 | return '' unless $query; |
1817 | |
1818 | my $dbh = $self->{dbh}; |
1819 | - my $q = $self->{Quoter}; |
1820 | + my $q = $self->Quoter(); |
1821 | my $qp = $self->{QueryParser}; |
1822 | return '' unless $dbh && $q && $qp; |
1823 | |
1824 | @@ -7485,34 +7952,6 @@ |
1825 | return $min && $max ? "$min to $max" : ''; |
1826 | } |
1827 | |
1828 | -sub explain_sparkline { |
1829 | - my ( $self, $query, $db ) = @_; |
1830 | - return unless $query; |
1831 | - |
1832 | - my $q = $self->{Quoter}; |
1833 | - my $dbh = $self->{dbh}; |
1834 | - my $ex = $self->{ExplainAnalyzer}; |
1835 | - return unless $dbh && $ex; |
1836 | - |
1837 | - if ( $db ) { |
1838 | - PTDEBUG && _d($dbh, "USE", $db); |
1839 | - $dbh->do("USE " . $q->quote($db)); |
1840 | - } |
1841 | - my $res = $ex->normalize( |
1842 | - $ex->explain_query( |
1843 | - dbh => $dbh, |
1844 | - query => $query, |
1845 | - ) |
1846 | - ); |
1847 | - |
1848 | - my $sparkline; |
1849 | - if ( $res ) { |
1850 | - $sparkline = $ex->sparkline(explain => $res); |
1851 | - } |
1852 | - |
1853 | - return $sparkline; |
1854 | -} |
1855 | - |
1856 | sub _d { |
1857 | my ($package, undef, $line) = caller 0; |
1858 | @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; } |
1859 | @@ -7528,6 +7967,139 @@ |
1860 | # ########################################################################### |
1861 | |
1862 | # ########################################################################### |
1863 | +# JSONReportFormatter package |
1864 | +# This package is a copy without comments from the original. The original |
1865 | +# with comments and its test file can be found in the Bazaar repository at, |
1866 | +# lib/JSONReportFormatter.pm |
1867 | +# t/lib/JSONReportFormatter.t |
1868 | +# See https://launchpad.net/percona-toolkit for more information. |
1869 | +# ########################################################################### |
1870 | +{ |
1871 | +package JSONReportFormatter; |
1872 | +use Mo; |
1873 | + |
1874 | +use List::Util qw(sum); |
1875 | +use Transformers qw(make_checksum parse_timestamp); |
1876 | + |
1877 | +use constant PTDEBUG => $ENV{PTDEBUG} || 0; |
1878 | + |
1879 | +my $have_json = eval { require JSON }; |
1880 | + |
1881 | +our $pretty_json = undef; |
1882 | +our $sorted_json = undef; |
1883 | + |
1884 | +extends qw(QueryReportFormatter); |
1885 | + |
1886 | +has _json => ( |
1887 | + is => 'ro', |
1888 | + init_arg => undef, |
1889 | + builder => '_build_json', |
1890 | +); |
1891 | + |
1892 | +sub _build_json { |
1893 | + return unless $have_json; |
1894 | + return JSON->new->utf8 |
1895 | + ->pretty($pretty_json) |
1896 | + ->canonical($sorted_json); |
1897 | +} |
1898 | + |
1899 | +sub encode_json { |
1900 | + my ($self, $encode) = @_; |
1901 | + if ( my $json = $self->_json ) { |
1902 | + return $json->encode($encode); |
1903 | + } |
1904 | + else { |
1905 | + return Transformers::encode_json($encode); |
1906 | + } |
1907 | +} |
1908 | + |
1909 | +override [qw(rusage date hostname files header profile prepared)] => sub { |
1910 | + return; |
1911 | +}; |
1912 | + |
1913 | +override event_report => sub { |
1914 | + my ($self, %args) = @_; |
1915 | + return $self->event_report_values(%args); |
1916 | +}; |
1917 | + |
1918 | +override query_report => sub { |
1919 | + my ($self, %args) = @_; |
1920 | + foreach my $arg ( qw(ea worst orderby groupby) ) { |
1921 | + die "I need a $arg argument" unless defined $arg; |
1922 | + } |
1923 | + |
1924 | + my $ea = $args{ea}; |
1925 | + my $worst = $args{worst}; |
1926 | + |
1927 | + my @attribs = @{$ea->get_attributes()}; |
1928 | + |
1929 | + my @queries; |
1930 | + foreach my $worst_info ( @$worst ) { |
1931 | + my $item = $worst_info->[0]; |
1932 | + my $stats = $ea->results->{classes}->{$item}; |
1933 | + my $sample = $ea->results->{samples}->{$item}; |
1934 | + |
1935 | + my $all_log_pos = $ea->{result_classes}->{$item}->{pos_in_log}->{all}; |
1936 | + my $times_seen = sum values %$all_log_pos; |
1937 | + |
1938 | + my %class = ( |
1939 | + sample => $sample->{arg}, |
1940 | + fingerprint => $item, |
1941 | + checksum => make_checksum($item), |
1942 | + cnt => $times_seen, |
1943 | + ); |
1944 | + |
1945 | + my %metrics; |
1946 | + foreach my $attrib ( @attribs ) { |
1947 | + $metrics{$attrib} = $ea->metrics( |
1948 | + attrib => $attrib, |
1949 | + where => $item, |
1950 | + ); |
1951 | + } |
1952 | + |
1953 | + foreach my $attrib ( keys %metrics ) { |
1954 | + if ( ! grep { $_ } values %{$metrics{$attrib}} ) { |
1955 | + delete $metrics{$attrib}; |
1956 | + next; |
1957 | + } |
1958 | + |
1959 | + if ($attrib eq 'ts') { |
1960 | + my $ts = delete $metrics{ts}; |
1961 | + foreach my $thing ( qw(min max) ) { |
1962 | + next unless defined $ts && defined $ts->{$thing}; |
1963 | + $ts->{$thing} = parse_timestamp($ts->{$thing}); |
1964 | + } |
1965 | + $class{ts_min} = $ts->{min}; |
1966 | + $class{ts_max} = $ts->{max}; |
1967 | + } |
1968 | + elsif ( ($ea->{type_for}->{$attrib} || '') eq 'num' ) { |
1969 | + for my $value ( values %{$metrics{$attrib}} ) { |
1970 | + next unless $value; |
1971 | + $value = sprintf '%.6f', $value; |
1972 | + } |
1973 | + if ( my $pct = $metrics{$attrib}->{pct} ) { |
1974 | + $metrics{$attrib}->{pct} = sprintf('%.2f', $pct); |
1975 | + } |
1976 | + } |
1977 | + } |
1978 | + push @queries, { |
1979 | + class => \%class, |
1980 | + attributes => \%metrics, |
1981 | + }; |
1982 | + } |
1983 | + |
1984 | + my $json = $self->encode_json(\@queries); |
1985 | + $json .= "\n" if $json !~ /\n\Z/; |
1986 | + return $json . "\n"; |
1987 | +}; |
1988 | + |
1989 | +1; |
1990 | +} |
1991 | +# ########################################################################### |
1992 | +# End JSONReportFormatter package |
1993 | +# ########################################################################### |
1994 | + |
1995 | +# ########################################################################### |
1996 | # EventTimeline package |
1997 | # This package is a copy without comments from the original. The original |
1998 | # with comments and its test file can be found in the Bazaar repository at, |
1999 | @@ -8588,7 +9160,7 @@ |
2000 | |
2001 | sub set_history_options { |
2002 | my ( $self, %args ) = @_; |
2003 | - foreach my $arg ( qw(table dbh tbl_struct col_pat) ) { |
2004 | + foreach my $arg ( qw(table tbl_struct col_pat) ) { |
2005 | die "I need a $arg argument" unless $args{$arg}; |
2006 | } |
2007 | |
2008 | @@ -8622,7 +9194,7 @@ |
2009 | } @cols) . ')'; |
2010 | PTDEBUG && _d($sql); |
2011 | |
2012 | - $self->{history_sth} = $args{dbh}->prepare($sql); |
2013 | + $self->{history_sth} = $self->{dbh}->prepare($sql); |
2014 | $self->{history_metrics} = \@metrics; |
2015 | |
2016 | return; |
2017 | @@ -10350,143 +10922,6 @@ |
2018 | # ########################################################################### |
2019 | |
2020 | # ########################################################################### |
2021 | -# ExecutionThrottler package |
2022 | -# This package is a copy without comments from the original. The original |
2023 | -# with comments and its test file can be found in the Bazaar repository at, |
2024 | -# lib/ExecutionThrottler.pm |
2025 | -# t/lib/ExecutionThrottler.t |
2026 | -# See https://launchpad.net/percona-toolkit for more information. |
2027 | -# ########################################################################### |
2028 | -{ |
2029 | -package ExecutionThrottler; |
2030 | - |
2031 | -use strict; |
2032 | -use warnings FATAL => 'all'; |
2033 | -use English qw(-no_match_vars); |
2034 | -use constant PTDEBUG => $ENV{PTDEBUG} || 0; |
2035 | - |
2036 | -use List::Util qw(sum min max); |
2037 | -use Time::HiRes qw(time); |
2038 | -use Data::Dumper; |
2039 | -$Data::Dumper::Indent = 1; |
2040 | -$Data::Dumper::Sortkeys = 1; |
2041 | -$Data::Dumper::Quotekeys = 0; |
2042 | - |
2043 | -sub new { |
2044 | - my ( $class, %args ) = @_; |
2045 | - my @required_args = qw(rate_max get_rate check_int step); |
2046 | - foreach my $arg ( @required_args ) { |
2047 | - die "I need a $arg argument" unless defined $args{$arg}; |
2048 | - } |
2049 | - my $self = { |
2050 | - step => 0.05, # default |
2051 | - %args, |
2052 | - rate_ok => undef, |
2053 | - last_check => undef, |
2054 | - stats => { |
2055 | - rate_avg => 0, |
2056 | - rate_samples => [], |
2057 | - }, |
2058 | - int_rates => [], |
2059 | - skip_prob => 0.0, |
2060 | - }; |
2061 | - |
2062 | - return bless $self, $class; |
2063 | -} |
2064 | - |
2065 | -sub throttle { |
2066 | - my ( $self, %args ) = @_; |
2067 | - my $time = $args{misc}->{time} || time; |
2068 | - if ( $self->_time_to_check($time) ) { |
2069 | - my $rate_avg = (sum(@{$self->{int_rates}}) || 0) |
2070 | - / (scalar @{$self->{int_rates}} || 1); |
2071 | - my $running_avg = $self->_save_rate_avg($rate_avg); |
2072 | - PTDEBUG && _d('Average rate for last interval:', $rate_avg); |
2073 | - |
2074 | - if ( $args{stats} ) { |
2075 | - $args{stats}->{throttle_checked_rate}++; |
2076 | - $args{stats}->{throttle_rate_avg} = sprintf '%.2f', $running_avg; |
2077 | - } |
2078 | - |
2079 | - @{$self->{int_rates}} = (); |
2080 | - |
2081 | - if ( $rate_avg > $self->{rate_max} ) { |
2082 | - $self->{skip_prob} += $self->{step}; |
2083 | - $self->{skip_prob} = 1.0 if $self->{skip_prob} > 1.0; |
2084 | - PTDEBUG && _d('Rate max exceeded'); |
2085 | - $args{stats}->{throttle_rate_max_exceeded}++ if $args{stats}; |
2086 | - } |
2087 | - else { |
2088 | - $self->{skip_prob} -= $self->{step}; |
2089 | - $self->{skip_prob} = 0.0 if $self->{skip_prob} < 0.0; |
2090 | - $args{stats}->{throttle_rate_ok}++ if $args{stats}; |
2091 | - } |
2092 | - |
2093 | - PTDEBUG && _d('Skip probability:', $self->{skip_prob}); |
2094 | - $self->{last_check} = $time; |
2095 | - } |
2096 | - else { |
2097 | - my $current_rate = $self->{get_rate}->(); |
2098 | - push @{$self->{int_rates}}, $current_rate; |
2099 | - if ( $args{stats} ) { |
2100 | - $args{stats}->{throttle_rate_min} = min( |
2101 | - ($args{stats}->{throttle_rate_min} || ()), $current_rate); |
2102 | - $args{stats}->{throttle_rate_max} = max( |
2103 | - ($args{stats}->{throttle_rate_max} || ()), $current_rate); |
2104 | - } |
2105 | - PTDEBUG && _d('Current rate:', $current_rate); |
2106 | - } |
2107 | - |
2108 | - if ( $args{event} ) { |
2109 | - $args{event}->{Skip_exec} = $self->{skip_prob} <= rand() ? 'No' : 'Yes'; |
2110 | - } |
2111 | - |
2112 | - return $args{event}; |
2113 | -} |
2114 | - |
2115 | -sub _time_to_check { |
2116 | - my ( $self, $time ) = @_; |
2117 | - if ( !$self->{last_check} ) { |
2118 | - $self->{last_check} = $time; |
2119 | - return 0; |
2120 | - } |
2121 | - return $time - $self->{last_check} >= $self->{check_int} ? 1 : 0; |
2122 | -} |
2123 | - |
2124 | -sub rate_avg { |
2125 | - my ( $self ) = @_; |
2126 | - return $self->{stats}->{rate_avg} || 0; |
2127 | -} |
2128 | - |
2129 | -sub skip_probability { |
2130 | - my ( $self ) = @_; |
2131 | - return $self->{skip_prob}; |
2132 | -} |
2133 | - |
2134 | -sub _save_rate_avg { |
2135 | - my ( $self, $rate ) = @_; |
2136 | - my $samples = $self->{stats}->{rate_samples}; |
2137 | - push @$samples, $rate; |
2138 | - shift @$samples if @$samples > 1_000; |
2139 | - $self->{stats}->{rate_avg} = sum(@$samples) / (scalar @$samples); |
2140 | - return $self->{stats}->{rate_avg} || 0; |
2141 | -} |
2142 | - |
2143 | -sub _d { |
2144 | - my ($package, undef, $line) = caller 0; |
2145 | - @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; } |
2146 | - map { defined $_ ? $_ : 'undef' } |
2147 | - @_; |
2148 | - print STDERR "# $package:$line $PID ", join(' ', @_), "\n"; |
2149 | -} |
2150 | - |
2151 | -1; |
2152 | -} |
2153 | -# ########################################################################### |
2154 | -# End ExecutionThrottler package |
2155 | -# ########################################################################### |
2156 | - |
2157 | -# ########################################################################### |
2158 | # MasterSlave package |
2159 | # This package is a copy without comments from the original. The original |
2160 | # with comments and its test file can be found in the Bazaar repository at, |
2161 | @@ -11448,226 +11883,6 @@ |
2162 | # ########################################################################### |
2163 | |
2164 | # ########################################################################### |
2165 | -# ExplainAnalyzer package |
2166 | -# This package is a copy without comments from the original. The original |
2167 | -# with comments and its test file can be found in the Bazaar repository at, |
2168 | -# lib/ExplainAnalyzer.pm |
2169 | -# t/lib/ExplainAnalyzer.t |
2170 | -# See https://launchpad.net/percona-toolkit for more information. |
2171 | -# ########################################################################### |
2172 | -{ |
2173 | -package ExplainAnalyzer; |
2174 | - |
2175 | -use strict; |
2176 | -use warnings FATAL => 'all'; |
2177 | -use English qw(-no_match_vars); |
2178 | -use constant PTDEBUG => $ENV{PTDEBUG} || 0; |
2179 | - |
2180 | -use Data::Dumper; |
2181 | -$Data::Dumper::Indent = 1; |
2182 | -$Data::Dumper::Sortkeys = 1; |
2183 | -$Data::Dumper::Quotekeys = 0; |
2184 | - |
2185 | -sub new { |
2186 | - my ( $class, %args ) = @_; |
2187 | - foreach my $arg ( qw(QueryRewriter QueryParser) ) { |
2188 | - die "I need a $arg argument" unless defined $args{$arg}; |
2189 | - } |
2190 | - my $self = { |
2191 | - %args, |
2192 | - }; |
2193 | - return bless $self, $class; |
2194 | -} |
2195 | - |
2196 | -sub explain_query { |
2197 | - my ( $self, %args ) = @_; |
2198 | - foreach my $arg ( qw(dbh query) ) { |
2199 | - die "I need a $arg argument" unless defined $args{$arg}; |
2200 | - } |
2201 | - my ($query, $dbh) = @args{qw(query dbh)}; |
2202 | - $query = $self->{QueryRewriter}->convert_to_select($query); |
2203 | - if ( $query !~ m/^\s*select/i ) { |
2204 | - PTDEBUG && _d("Cannot EXPLAIN non-SELECT query:", |
2205 | - (length $query <= 100 ? $query : substr($query, 0, 100) . "...")); |
2206 | - return; |
2207 | - } |
2208 | - my $sql = "EXPLAIN $query"; |
2209 | - PTDEBUG && _d($dbh, $sql); |
2210 | - my $explain = $dbh->selectall_arrayref($sql, { Slice => {} }); |
2211 | - PTDEBUG && _d("Result of EXPLAIN:", Dumper($explain)); |
2212 | - return $explain; |
2213 | -} |
2214 | - |
2215 | -sub normalize { |
2216 | - my ( $self, $explain ) = @_; |
2217 | - my @result; # Don't modify the input. |
2218 | - |
2219 | - foreach my $row ( @$explain ) { |
2220 | - $row = { %$row }; # Make a copy -- don't modify the input. |
2221 | - |
2222 | - foreach my $col ( qw(key possible_keys key_len ref) ) { |
2223 | - $row->{$col} = [ split(/,/, $row->{$col} || '') ]; |
2224 | - } |
2225 | - |
2226 | - $row->{Extra} = { |
2227 | - map { |
2228 | - my $var = $_; |
2229 | - |
2230 | - if ( my ($key, $vals) = $var =~ m/(Using union)\(([^)]+)\)/ ) { |
2231 | - $key => [ split(/,/, $vals) ]; |
2232 | - } |
2233 | - |
2234 | - else { |
2235 | - $var => 1; |
2236 | - } |
2237 | - } |
2238 | - split(/; /, $row->{Extra} || '') # Split on semicolons. |
2239 | - }; |
2240 | - |
2241 | - push @result, $row; |
2242 | - } |
2243 | - |
2244 | - return \@result; |
2245 | -} |
2246 | - |
2247 | -sub get_alternate_indexes { |
2248 | - my ( $self, $keys, $possible_keys ) = @_; |
2249 | - my %used = map { $_ => 1 } @$keys; |
2250 | - return [ grep { !$used{$_} } @$possible_keys ]; |
2251 | -} |
2252 | - |
2253 | -sub get_index_usage { |
2254 | - my ( $self, %args ) = @_; |
2255 | - foreach my $arg ( qw(query explain) ) { |
2256 | - die "I need a $arg argument" unless defined $args{$arg}; |
2257 | - } |
2258 | - my ($query, $explain) = @args{qw(query explain)}; |
2259 | - my @result; |
2260 | - |
2261 | - my $lookup = $self->{QueryParser}->get_aliases($query); |
2262 | - |
2263 | - foreach my $row ( @$explain ) { |
2264 | - |
2265 | - next if !defined $row->{table} |
2266 | - || $row->{table} =~ m/^<(derived|union)\d/; |
2267 | - |
2268 | - my $table = $lookup->{TABLE}->{$row->{table}} || $row->{table}; |
2269 | - my $db = $lookup->{DATABASE}->{$table} || $args{db}; |
2270 | - push @result, { |
2271 | - db => $db, |
2272 | - tbl => $table, |
2273 | - idx => $row->{key}, |
2274 | - alt => $self->get_alternate_indexes( |
2275 | - $row->{key}, $row->{possible_keys}), |
2276 | - }; |
2277 | - } |
2278 | - |
2279 | - PTDEBUG && _d("Index usage for", |
2280 | - (length $query <= 100 ? $query : substr($query, 0, 100) . "..."), |
2281 | - ":", Dumper(\@result)); |
2282 | - return \@result; |
2283 | -} |
2284 | - |
2285 | -sub get_usage_for { |
2286 | - my ( $self, $checksum, $db ) = @_; |
2287 | - die "I need a checksum and db" unless defined $checksum && defined $db; |
2288 | - my $usage; |
2289 | - if ( exists $self->{usage}->{$db} # Don't auto-vivify |
2290 | - && exists $self->{usage}->{$db}->{$checksum} ) |
2291 | - { |
2292 | - $usage = $self->{usage}->{$db}->{$checksum}; |
2293 | - } |
2294 | - PTDEBUG && _d("Usage for", |
2295 | - (length $checksum <= 100 ? $checksum : substr($checksum, 0, 100) . "..."), |
2296 | - "on", $db, ":", Dumper($usage)); |
2297 | - return $usage; |
2298 | -} |
2299 | - |
2300 | -sub save_usage_for { |
2301 | - my ( $self, $checksum, $db, $usage ) = @_; |
2302 | - die "I need a checksum and db" unless defined $checksum && defined $db; |
2303 | - $self->{usage}->{$db}->{$checksum} = $usage; |
2304 | -} |
2305 | - |
2306 | -sub fingerprint { |
2307 | - my ( $self, %args ) = @_; |
2308 | - my @required_args = qw(explain); |
2309 | - foreach my $arg ( @required_args ) { |
2310 | - die "I need a $arg argument" unless defined $args{$arg}; |
2311 | - } |
2312 | - my ($explain) = @args{@required_args}; |
2313 | -} |
2314 | - |
2315 | -sub sparkline { |
2316 | - my ( $self, %args ) = @_; |
2317 | - my @required_args = qw(explain); |
2318 | - foreach my $arg ( @required_args ) { |
2319 | - die "I need a $arg argument" unless defined $args{$arg}; |
2320 | - } |
2321 | - my ($explain) = @args{@required_args}; |
2322 | - PTDEBUG && _d("Making sparkline for", Dumper($explain)); |
2323 | - |
2324 | - my $access_code = { |
2325 | - 'ALL' => 'a', |
2326 | - 'const' => 'c', |
2327 | - 'eq_ref' => 'e', |
2328 | - 'fulltext' => 'f', |
2329 | - 'index' => 'i', |
2330 | - 'index_merge' => 'm', |
2331 | - 'range' => 'n', |
2332 | - 'ref_or_null' => 'o', |
2333 | - 'ref' => 'r', |
2334 | - 'system' => 's', |
2335 | - 'unique_subquery' => 'u', |
2336 | - }; |
2337 | - |
2338 | - my $sparkline = ''; |
2339 | - my ($T, $F); # Using temporary, Using filesort |
2340 | - |
2341 | - foreach my $tbl ( @$explain ) { |
2342 | - my $code; |
2343 | - if ( defined $tbl->{type} ) { |
2344 | - $code = $access_code->{$tbl->{type}} || "?"; |
2345 | - $code = uc $code if $tbl->{Extra}->{'Using index'}; |
2346 | - } |
2347 | - else { |
2348 | - $code = '-' |
2349 | - }; |
2350 | - $sparkline .= $code; |
2351 | - |
2352 | - $T = 1 if $tbl->{Extra}->{'Using temporary'}; |
2353 | - $F = 1 if $tbl->{Extra}->{'Using filesort'}; |
2354 | - } |
2355 | - |
2356 | - if ( $T || $F ) { |
2357 | - if ( $explain->[-1]->{Extra}->{'Using temporary'} |
2358 | - || $explain->[-1]->{Extra}->{'Using filesort'} ) { |
2359 | - $sparkline .= ">" . ($T ? "T" : "") . ($F ? "F" : ""); |
2360 | - } |
2361 | - else { |
2362 | - $sparkline = ($T ? "T" : "") . ($F ? "F" : "") . ">$sparkline"; |
2363 | - } |
2364 | - } |
2365 | - |
2366 | - PTDEBUG && _d("sparkline:", $sparkline); |
2367 | - return $sparkline; |
2368 | -} |
2369 | - |
2370 | -sub _d { |
2371 | - my ($package, undef, $line) = caller 0; |
2372 | - @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; } |
2373 | - map { defined $_ ? $_ : 'undef' } |
2374 | - @_; |
2375 | - print STDERR "# $package:$line $PID ", join(' ', @_), "\n"; |
2376 | -} |
2377 | - |
2378 | -1; |
2379 | -} |
2380 | -# ########################################################################### |
2381 | -# End ExplainAnalyzer package |
2382 | -# ########################################################################### |
2383 | - |
2384 | -# ########################################################################### |
2385 | # Runtime package |
2386 | # This package is a copy without comments from the original. The original |
2387 | # with comments and its test file can be found in the Bazaar repository at, |
2388 | @@ -11825,7 +12040,7 @@ |
2389 | } |
2390 | |
2391 | my $self = { |
2392 | - instrument => 0, |
2393 | + instrument => PTDEBUG, |
2394 | continue_on_error => 0, |
2395 | |
2396 | %args, |
2397 | @@ -11852,9 +12067,7 @@ |
2398 | |
2399 | push @{$self->{procs}}, $process; |
2400 | push @{$self->{names}}, $name; |
2401 | - if ( my $n = $args{retry_on_error} ) { |
2402 | - $self->{retries}->{$name} = $n; |
2403 | - } |
2404 | + $self->{retries}->{$name} = $args{retry_on_error} || 100; |
2405 | if ( $self->{instrument} ) { |
2406 | $self->{instrumentation}->{$name} = { time => 0, calls => 0 }; |
2407 | } |
2408 | @@ -11923,7 +12136,11 @@ |
2409 | my $msg = "Pipeline process " . ($procno + 1) |
2410 | . " ($name) caused an error: " |
2411 | . $EVAL_ERROR; |
2412 | - if ( defined $self->{retries}->{$name} ) { |
2413 | + if ( !$self->{continue_on_error} ) { |
2414 | + die $msg . "Terminating pipeline because --continue-on-error " |
2415 | + . "is false.\n"; |
2416 | + } |
2417 | + elsif ( defined $self->{retries}->{$name} ) { |
2418 | my $n = $self->{retries}->{$name}; |
2419 | if ( $n ) { |
2420 | warn $msg . "Will retry pipeline process $procno ($name) " |
2421 | @@ -11935,9 +12152,6 @@ |
2422 | . "($name) caused too many errors.\n"; |
2423 | } |
2424 | } |
2425 | - elsif ( !$self->{continue_on_error} ) { |
2426 | - die $msg; |
2427 | - } |
2428 | else { |
2429 | warn $msg; |
2430 | } |
2431 | @@ -13296,33 +13510,57 @@ |
2432 | # ########################################################################### |
2433 | package pt_query_digest; |
2434 | |
2435 | +use strict; |
2436 | +use warnings FATAL => 'all'; |
2437 | use English qw(-no_match_vars); |
2438 | -use Time::Local qw(timelocal); |
2439 | -use Time::HiRes qw(time usleep); |
2440 | -use List::Util qw(max); |
2441 | -use POSIX qw(signal_h); |
2442 | +use constant PTDEBUG => $ENV{PTDEBUG} || 0; |
2443 | + |
2444 | +use Time::Local qw(timelocal); |
2445 | +use Time::HiRes qw(time usleep); |
2446 | +use List::Util qw(max); |
2447 | +use Scalar::Util qw(looks_like_number); |
2448 | +use POSIX qw(signal_h); |
2449 | use Data::Dumper; |
2450 | -$Data::Dumper::Indent = 1; |
2451 | -$OUTPUT_AUTOFLUSH = 1; |
2452 | - |
2453 | -Transformers->import(qw(shorten micro_t percentage_of ts make_checksum |
2454 | - any_unix_timestamp parse_timestamp unix_timestamp crc32)); |
2455 | |
2456 | use Percona::Toolkit; |
2457 | -use constant PTDEBUG => $ENV{PTDEBUG} || 0; |
2458 | + |
2459 | +$Data::Dumper::Indent = 1; |
2460 | +$Data::Dumper::Sortkeys = 1; |
2461 | +$Data::Dumper::Quotekeys = 0; |
2462 | + |
2463 | +$OUTPUT_AUTOFLUSH = 1; |
2464 | + |
2465 | +Transformers->import(qw( |
2466 | + shorten |
2467 | + micro_t |
2468 | + percentage_of |
2469 | + ts |
2470 | + make_checksum |
2471 | + any_unix_timestamp |
2472 | + parse_timestamp |
2473 | + unix_timestamp |
2474 | + crc32 |
2475 | +)); |
2476 | |
2477 | use sigtrap 'handler', \&sig_int, 'normal-signals'; |
2478 | |
2479 | # Global variables. Only really essential variables should be here. |
2480 | my $oktorun = 1; |
2481 | -my $ex_dbh; # For --execute |
2482 | my $ep_dbh; # For --explain |
2483 | my $ps_dbh; # For Processlist |
2484 | my $aux_dbh; # For --aux-dsn (--since/--until "MySQL expression") |
2485 | |
2486 | +my $resume_file; |
2487 | +my $offset; |
2488 | + |
2489 | +(my $tool = __PACKAGE__) =~ tr/_/-/; |
2490 | + |
2491 | sub main { |
2492 | - local @ARGV = @_; # set global ARGV for this package |
2493 | - $oktorun = 1; # reset between tests else pipeline won't run |
2494 | + # Reset global vars, else tests will fail. |
2495 | + local @ARGV = @_; |
2496 | + $oktorun = 1; |
2497 | + $resume_file = undef; |
2498 | + $offset = undef; |
2499 | |
2500 | # ########################################################################## |
2501 | # Get configuration information. |
2502 | @@ -13347,31 +13585,11 @@ |
2503 | } |
2504 | |
2505 | if ( !$o->get('help') ) { |
2506 | - if ( $review_dsn |
2507 | - && (!defined $review_dsn->{D} || !defined $review_dsn->{t}) ) { |
2508 | - $o->save_error('The --review DSN requires a D (database) and t' |
2509 | - . ' (table) part specifying the query review table'); |
2510 | - } |
2511 | - if ( $o->get('mirror') |
2512 | - && (!$o->get('execute') || !$o->get('processlist')) ) { |
2513 | - $o->save_error('--mirror requires --execute and --processlist'); |
2514 | - } |
2515 | if ( $o->get('outliers') |
2516 | && grep { $_ !~ m/^\w+:[0-9.]+(?::[0-9.]+)?$/ } @{$o->get('outliers')} |
2517 | ) { |
2518 | $o->save_error('--outliers requires two or three colon-separated fields'); |
2519 | } |
2520 | - if ( $o->get('execute-throttle') ) { |
2521 | - my ($rate_max, $int, $step) = @{$o->get('execute-throttle')}; |
2522 | - $o->save_error("--execute-throttle max time must be between 1 and 100") |
2523 | - unless $rate_max && $rate_max > 0 && $rate_max <= 100; |
2524 | - $o->save_error("No check interval value for --execute-throttle") |
2525 | - unless $int; |
2526 | - $o->save_error("--execute-throttle check interval must be an integer") |
2527 | - if $int =~ m/[^\d]/; |
2528 | - $o->save_error("--execute-throttle step must be between 1 and 100") |
2529 | - if $step && ($step < 1 || $step > 100); |
2530 | - } |
2531 | if ( $o->get('progress') ) { |
2532 | eval { Progress->validate_spec($o->get('progress')) }; |
2533 | if ( $EVAL_ERROR ) { |
2534 | @@ -13380,9 +13598,20 @@ |
2535 | } |
2536 | } |
2537 | |
2538 | - if ( $o->get('apdex-threshold') <= 0 ) { |
2539 | - $o->save_error("Apdex threshold must be a positive decimal value"); |
2540 | - } |
2541 | + if ( my $review_dsn = $o->get('review') ) { |
2542 | + $o->save_error('--review does not accept a t option. Perhaps you meant ' |
2543 | + . 'to use --review-table or --history-table?') |
2544 | + if defined $review_dsn->{t}; |
2545 | + } |
2546 | + |
2547 | + for my $tables ('review-table', 'history-table') { |
2548 | + my $got = $o->get($tables); |
2549 | + if ( grep !defined, Quoter->split_unquote($got) ) { |
2550 | + $o->save_error("--$tables should be passed a " |
2551 | + . "fully-qualified table name, got $got"); |
2552 | + } |
2553 | + } |
2554 | + |
2555 | if ( my $patterns = $o->get('embedded-attributes') ) { |
2556 | $o->save_error("--embedded-attributes should be passed two " |
2557 | . "comma-separated patterns, got " . scalar(@$patterns) ) |
2558 | @@ -13440,7 +13669,6 @@ |
2559 | # ######################################################################## |
2560 | # Set up for --explain |
2561 | # ######################################################################## |
2562 | - my $exa; |
2563 | if ( my $ep_dsn = $o->get('explain') ) { |
2564 | $ep_dbh = get_cxn( |
2565 | for => '--explain', |
2566 | @@ -13450,19 +13678,13 @@ |
2567 | opts => { AutoCommit => 1 }, |
2568 | ); |
2569 | $ep_dbh->{InactiveDestroy} = 1; # Don't die on fork(). |
2570 | - |
2571 | - $exa = new ExplainAnalyzer( |
2572 | - QueryRewriter => $qr, |
2573 | - QueryParser => $qp, |
2574 | - ); |
2575 | } |
2576 | |
2577 | # ######################################################################## |
2578 | - # Set up for --review and --review-history. |
2579 | + # Set up for --review. |
2580 | # ######################################################################## |
2581 | my $qv; # QueryReview |
2582 | my $qv_dbh; # For QueryReview |
2583 | - my $qv_dbh2; # For QueryReview and --review-history |
2584 | if ( $review_dsn ) { |
2585 | my $tp = new TableParser(Quoter => $q); |
2586 | $qv_dbh = get_cxn( |
2587 | @@ -13473,28 +13695,33 @@ |
2588 | opts => { AutoCommit => 1 }, |
2589 | ); |
2590 | $qv_dbh->{InactiveDestroy} = 1; # Don't die on fork(). |
2591 | - my @db_tbl = @{$review_dsn}{qw(D t)}; |
2592 | - my $db_tbl = $q->quote(@db_tbl); |
2593 | - |
2594 | - # Create the review table if desired |
2595 | - if ( $o->get('create-review-table') ) { |
2596 | - my $sql = $o->read_para_after( |
2597 | - __FILE__, qr/MAGIC_create_review/); |
2598 | - $sql =~ s/query_review/IF NOT EXISTS $db_tbl/; |
2599 | - PTDEBUG && _d($sql); |
2600 | - $qv_dbh->do($sql); |
2601 | - } |
2602 | - |
2603 | - # Check for the existence of the table. |
2604 | - if ( !$tp->check_table( |
2605 | - dbh => $qv_dbh, |
2606 | - db => $db_tbl[0], |
2607 | - tbl => $db_tbl[1]) ) |
2608 | - { |
2609 | - die "The query review table $db_tbl does not exist. " |
2610 | - . "Specify --create-review-table to create it, " |
2611 | - . "and ensure that the MySQL user has privileges to create " |
2612 | - . "and update the table.\n"; |
2613 | + |
2614 | + my @db_tbl = Quoter->split_unquote($o->get('review-table')); |
2615 | + my @hdb_tbl = Quoter->split_unquote($o->get('history-table')); |
2616 | + |
2617 | + my $db_tbl = $q->quote(@db_tbl); |
2618 | + my $hdb_tbl = $q->quote(@hdb_tbl); |
2619 | + |
2620 | + my $create_review_sql = $o->read_para_after( |
2621 | + __FILE__, qr/MAGIC_create_review/); |
2622 | + $create_review_sql =~ s/query_review/IF NOT EXISTS $db_tbl/; |
2623 | + |
2624 | + my $create_history_sql = $o->read_para_after( |
2625 | + __FILE__, qr/MAGIC_create_review_history/); |
2626 | + $create_history_sql =~ s/query_review_history/IF NOT EXISTS $hdb_tbl/; |
2627 | + |
2628 | + for my $create ( |
2629 | + [ $db_tbl, $create_review_sql ], |
2630 | + [ $hdb_tbl, $create_history_sql ], |
2631 | + ) { |
2632 | + my ($tbl_name, $sql) = @$create; |
2633 | + create_review_tables( |
2634 | + dbh => $qv_dbh, |
2635 | + full_table => $tbl_name, |
2636 | + create_table_sql => $sql, |
2637 | + create_table => $o->get('create-review-tables'), |
2638 | + TableParser => $tp, |
2639 | + ); |
2640 | } |
2641 | |
2642 | # Set up the new QueryReview object. |
2643 | @@ -13506,79 +13733,43 @@ |
2644 | quoter => $q, |
2645 | ); |
2646 | |
2647 | - # Set up the review-history table |
2648 | - if ( my $review_history_dsn = $o->get('review-history') ) { |
2649 | - $qv_dbh2 = get_cxn( |
2650 | - for => '--review-history', |
2651 | - dsn => $review_history_dsn, |
2652 | - OptionParser => $o, |
2653 | - DSNParser => $dp, |
2654 | - opts => { AutoCommit => 1 }, |
2655 | - ); |
2656 | - $qv_dbh2->{InactiveDestroy} = 1; # Don't die on fork(). |
2657 | - my @hdb_tbl = @{$o->get('review-history')}{qw(D t)}; |
2658 | - my $hdb_tbl = $q->quote(@hdb_tbl); |
2659 | - |
2660 | - # Create the review-history table if desired |
2661 | - if ( $o->get('create-review-history-table') ) { |
2662 | - my $sql = $o->read_para_after( |
2663 | - __FILE__, qr/MAGIC_create_review_history/); |
2664 | - $sql =~ s/query_review_history/IF NOT EXISTS $hdb_tbl/; |
2665 | - PTDEBUG && _d($sql); |
2666 | - $qv_dbh2->do($sql); |
2667 | - } |
2668 | - |
2669 | - # Check for the existence of the table. |
2670 | - if ( !$tp->check_table( |
2671 | - dbh => $qv_dbh2, |
2672 | - db => $hdb_tbl[0], |
2673 | - tbl => $hdb_tbl[1]) ) |
2674 | - { |
2675 | - die "The query review history table $hdb_tbl does not exist. " |
2676 | - . "Specify --create-review-history-table to create it, " |
2677 | - . "and ensure that the MySQL user has privileges to create " |
2678 | - . "and update the table.\n"; |
2679 | - } |
2680 | - |
2681 | - # Inspect for MAGIC_history_cols. Add them to the --select list |
2682 | - # only if an explicit --select list was given. Otherwise, leave |
2683 | - # --select undef which will cause EventAggregator to aggregate every |
2684 | - # attribute available which will include the history columns. |
2685 | - # If no --select list was given and we make one by adding the history |
2686 | - # columsn to it, then EventAggregator will only aggregate the |
2687 | - # history columns and nothing else--we don't want this. |
2688 | - my $tbl = $tp->parse($tp->get_create_table($qv_dbh2, @hdb_tbl)); |
2689 | - my $pat = $o->read_para_after(__FILE__, qr/MAGIC_history_cols/); |
2690 | - $pat =~ s/\s+//g; |
2691 | - $pat = qr/^(.*?)_($pat)$/; |
2692 | - # Get original --select values. |
2693 | - my %select = map { $_ => 1 } @{$o->get('select')}; |
2694 | - foreach my $col ( @{$tbl->{cols}} ) { |
2695 | - my ( $attr, $metric ) = $col =~ m/$pat/; |
2696 | - next unless $attr && $metric; |
2697 | - $attr = ucfirst $attr if $attr =~ m/_/; # TableParser lowercases |
2698 | - # Add history table values to original select values. |
2699 | - $select{$attr}++; |
2700 | - } |
2701 | - |
2702 | - if ( $o->got('select') ) { |
2703 | - # Re-set --select with its original values plus the history |
2704 | - # table values. |
2705 | - $o->set('select', [keys %select]); |
2706 | - PTDEBUG && _d("--select after parsing --review-history table:", |
2707 | - @{$o->get('select')}); |
2708 | - } |
2709 | - |
2710 | - # And tell the QueryReview that it has more work to do. |
2711 | - $qv->set_history_options( |
2712 | - table => $hdb_tbl, |
2713 | - dbh => $qv_dbh2, |
2714 | - tbl_struct => $tbl, |
2715 | - col_pat => $pat, |
2716 | - ); |
2717 | - } |
2718 | + # Inspect for MAGIC_history_cols. Add them to the --select list |
2719 | + # only if an explicit --select list was given. Otherwise, leave |
2720 | + # --select undef which will cause EventAggregator to aggregate every |
2721 | + # attribute available which will include the history columns. |
2722 | + # If no --select list was given and we make one by adding the history |
2723 | + # columsn to it, then EventAggregator will only aggregate the |
2724 | + # history columns and nothing else--we don't want this. |
2725 | + my $tbl = $tp->parse($tp->get_create_table($qv_dbh, @hdb_tbl)); |
2726 | + my $pat = $o->read_para_after(__FILE__, qr/MAGIC_history_cols/); |
2727 | + $pat =~ s/\s+//g; |
2728 | + $pat = qr/^(.*?)_($pat)$/; |
2729 | + # Get original --select values. |
2730 | + my %select = map { $_ => 1 } @{$o->get('select')}; |
2731 | + foreach my $col ( @{$tbl->{cols}} ) { |
2732 | + my ( $attr, $metric ) = $col =~ $pat; |
2733 | + next unless $attr && $metric; |
2734 | + $attr = ucfirst $attr if $attr =~ m/_/; # TableParser lowercases |
2735 | + # Add history table values to original select values. |
2736 | + $select{$attr}++; |
2737 | + } |
2738 | + |
2739 | + if ( $o->got('select') ) { |
2740 | + # Re-set --select with its original values plus the history |
2741 | + # table values. |
2742 | + $o->set('select', [sort keys %select]); |
2743 | + PTDEBUG && _d("--select after parsing the history table:", |
2744 | + @{$o->get('select')}); |
2745 | + } |
2746 | + |
2747 | + # And tell the QueryReview that it has more work to do. |
2748 | + $qv->set_history_options( |
2749 | + table => $hdb_tbl, |
2750 | + tbl_struct => $tbl, |
2751 | + col_pat => $pat, |
2752 | + ); |
2753 | } |
2754 | - |
2755 | + |
2756 | # ######################################################################## |
2757 | # Create all the pipeline processes that do all the work: get input, |
2758 | # parse events, manage runtime, switch iterations, aggregate, etc. |
2759 | @@ -13599,13 +13790,7 @@ |
2760 | stats => \%stats, |
2761 | }; |
2762 | |
2763 | - # Enable timings to instrument code for either of these two opts. |
2764 | - # Else, don't instrument to avoid cost of measurement. |
2765 | - my $instrument = $o->get('pipeline-profile') || $o->get('execute-throttle'); |
2766 | - PTDEBUG && _d('Instrument:', $instrument); |
2767 | - |
2768 | my $pipeline = new Pipeline( |
2769 | - instrument => $instrument, |
2770 | continue_on_error => $o->get('continue-on-error'), |
2771 | ); |
2772 | |
2773 | @@ -13633,7 +13818,7 @@ |
2774 | } # prep |
2775 | |
2776 | { # input |
2777 | - my $fi = new FileIterator(); |
2778 | + my $fi = FileIterator->new(); |
2779 | my $next_file = $fi->get_file_itr(@ARGV); |
2780 | my $input_fh; # the current input fh |
2781 | my $pr; # Progress obj for ^ |
2782 | @@ -13642,20 +13827,51 @@ |
2783 | name => 'input', |
2784 | process => sub { |
2785 | my ( $args ) = @_; |
2786 | + |
2787 | # Only get the next file when there's no fh or no more events in |
2788 | # the current fh. This allows us to do collect-and-report cycles |
2789 | # (i.e. iterations) on huge files. This doesn't apply to infinite |
2790 | # inputs because they don't set more_events false. |
2791 | if ( !$args->{input_fh} || !$args->{more_events} ) { |
2792 | + |
2793 | + # Close the current file. |
2794 | if ( $args->{input_fh} ) { |
2795 | close $args->{input_fh} |
2796 | or die "Cannot close input fh: $OS_ERROR"; |
2797 | } |
2798 | + |
2799 | + # Open the next file. |
2800 | my ($fh, $filename, $filesize) = $next_file->(); |
2801 | if ( $fh ) { |
2802 | PTDEBUG && _d('Reading', $filename); |
2803 | + PTDEBUG && _d('File size:', $filesize); |
2804 | push @read_files, $filename || "STDIN"; |
2805 | |
2806 | + # Read the file offset for --resume. |
2807 | + if ( ($resume_file = $o->get('resume')) && $filename ) { |
2808 | + if ( -s $resume_file ) { |
2809 | + open my $resume_fh, '<', $resume_file |
2810 | + or die "Error opening $resume_file: $OS_ERROR"; |
2811 | + chomp(my $resume_offset = <$resume_fh>); |
2812 | + close $resume_fh |
2813 | + or die "Error close $resume_file: $OS_ERROR"; |
2814 | + if ( !looks_like_number($resume_offset) ) { |
2815 | + die "Offset $resume_offset in $resume_file " |
2816 | + . "does not look like a number.\n"; |
2817 | + } |
2818 | + PTDEBUG && _d('Resuming at offset', $resume_offset); |
2819 | + seek $fh, $resume_offset, 0 |
2820 | + or die "Error seeking to $resume_offset in " |
2821 | + . "$resume_file: $OS_ERROR"; |
2822 | + warn "Resuming $filename from offset $resume_offset " |
2823 | + . "(file size: $filesize)...\n"; |
2824 | + } |
2825 | + else { |
2826 | + PTDEBUG && _d('Not resuming', $filename, 'because', |
2827 | + $resume_file, 'does not exist'); |
2828 | + } |
2829 | + } |
2830 | + |
2831 | # Create callback to read next event. Some inputs, like |
2832 | # Processlist, may use something else but most next_event. |
2833 | if ( my $read_time = $o->get('read-timeout') ) { |
2834 | @@ -13665,8 +13881,15 @@ |
2835 | else { |
2836 | $args->{next_event} = sub { return <$fh>; }; |
2837 | } |
2838 | + $args->{filename} = $filename; |
2839 | $args->{input_fh} = $fh; |
2840 | - $args->{tell} = sub { return tell $fh; }; |
2841 | + $args->{tell} = sub { |
2842 | + $offset = tell $fh; # update global $offset |
2843 | + if ( $args->{filename} ) { |
2844 | + $args->{pos_for}->{$args->{filename}} = $offset; |
2845 | + } |
2846 | + return $offset; # legacy: return global $offset |
2847 | + }; |
2848 | $args->{more_events} = 1; |
2849 | |
2850 | # Reset in case we read two logs out of order by time. |
2851 | @@ -13725,14 +13948,12 @@ |
2852 | $err = $EVAL_ERROR; |
2853 | if ( $err ) { # Try to reconnect when there's an error. |
2854 | eval { |
2855 | - ($cur_server, $ps_dbh) = find_role( |
2856 | - OptionParser => $o, |
2857 | - DSNParser => $dp, |
2858 | - dbh => $ps_dbh, |
2859 | - current => $cur_server, |
2860 | - read_only => 0, |
2861 | - comment => 'for --processlist' |
2862 | - ); |
2863 | + if ( !$ps_dbh || !$ps_dbh->ping ) { |
2864 | + PTDEBUG && _d('Getting a dbh from', $cur_server); |
2865 | + $ps_dbh = $dp->get_dbh( |
2866 | + $dp->get_cxn_params($o->get($cur_server)), {AutoCommit => 1}); |
2867 | + $ps_dbh->{InactiveDestroy} = 1; # Don't die on fork(). |
2868 | + } |
2869 | $cur_time = time(); |
2870 | $sth = $ps_dbh->prepare('SHOW FULL PROCESSLIST'); |
2871 | $cxn = $ps_dbh->{mysql_thread_id}; |
2872 | @@ -13745,18 +13966,6 @@ |
2873 | } |
2874 | } |
2875 | } until ( $sth && !$err ); |
2876 | - if ( $o->get('mirror') |
2877 | - && time() - $cur_time > $o->get('mirror')) { |
2878 | - ($cur_server, $ps_dbh) = find_role( |
2879 | - OptionParser => $o, |
2880 | - DSNParser => $dp, |
2881 | - dbh => $ps_dbh, |
2882 | - current => $cur_server, |
2883 | - read_only => 0, |
2884 | - comment => 'for --processlist' |
2885 | - ); |
2886 | - $cur_time = time(); |
2887 | - } |
2888 | |
2889 | return [ grep { $_->[0] != $cxn } @{ $sth->fetchall_arrayref(); } ]; |
2890 | }; |
2891 | @@ -13949,7 +14158,7 @@ |
2892 | ); |
2893 | $aux_dbh->{InactiveDestroy} = 1; # Don't die on fork(). |
2894 | } |
2895 | - $aux_dbh ||= $qv_dbh || $qv_dbh2 || $ex_dbh || $ps_dbh || $ep_dbh; |
2896 | + $aux_dbh ||= $qv_dbh || $ps_dbh || $ep_dbh; |
2897 | PTDEBUG && _d('aux dbh:', $aux_dbh); |
2898 | |
2899 | my $time_callback = sub { |
2900 | @@ -14081,7 +14290,6 @@ |
2901 | files => \@read_files, |
2902 | Pipeline => $pipeline, |
2903 | QueryReview => $qv, |
2904 | - ExplainAnalyzer => $exa, |
2905 | %common_modules, |
2906 | ); |
2907 | } |
2908 | @@ -14089,7 +14297,38 @@ |
2909 | print "\n# No events processed.\n"; |
2910 | } |
2911 | |
2912 | - if ( $o->get('statistics') ) { |
2913 | + if ( PTDEBUG ) { |
2914 | + # Print statistics about internal counters. This option is mostly for |
2915 | + # development and debugging. The statistics report is printed for each |
2916 | + # iteration after all other reports, even if no events are processed or |
2917 | + # C<--no-report> is specified. The statistics report looks like: |
2918 | + |
2919 | + # No events processed. |
2920 | + |
2921 | + # Statistic Count %/Events |
2922 | + # ================================================ ====== ======== |
2923 | + # events_read 142030 100.00 |
2924 | + # events_parsed 50430 35.51 |
2925 | + # events_aggregated 0 0.00 |
2926 | + # ignored_midstream_server_response 18111 12.75 |
2927 | + # no_tcp_data 91600 64.49 |
2928 | + # pipeline_restarted_after_MemcachedProtocolParser 142030 100.00 |
2929 | + # pipeline_restarted_after_TcpdumpParser 1 0.00 |
2930 | + # unknown_client_command 1 0.00 |
2931 | + # unknown_client_data 32318 22.75 |
2932 | + |
2933 | + # The first column is the internal counter name; the second column is counter's |
2934 | + # count; and the third column is the count as a percentage of C<events_read>. |
2935 | + |
2936 | + # In this case, it shows why no events were processed/aggregated: 100% of events |
2937 | + # were rejected by the C<MemcachedProtocolParser>. Of those, 35.51% were data |
2938 | + # packets, but of these 12.75% of ignored mid-stream server response, one was |
2939 | + # an unknown client command, and 22.75% were unknown client data. The other |
2940 | + # 64.49% were TCP control packets (probably most ACKs). |
2941 | + |
2942 | + # Since pt-query-digest is complex, you will probably need someone familiar |
2943 | + # with its code to decipher the statistics report. |
2944 | + |
2945 | if ( keys %stats ) { |
2946 | my $report = new ReportFormatter( |
2947 | line_width => 74, |
2948 | @@ -14395,139 +14634,6 @@ |
2949 | } |
2950 | } # sample |
2951 | |
2952 | - my $ex_dsn; |
2953 | - { # execute throttle and execute |
2954 | - my $et; |
2955 | - if ( my $et_args = $o->get('execute-throttle') ) { |
2956 | - # These were check earlier; no need to check them again. |
2957 | - my ($rate_max, $int, $step) = @{$o->get('execute-throttle')}; |
2958 | - $step ||= 5; |
2959 | - $step /= 100; # step specified as percent but $et expect 0.1=10%, etc. |
2960 | - PTDEBUG && _d('Execute throttle:', $rate_max, $int, $step); |
2961 | - |
2962 | - my $get_rate = sub { |
2963 | - my $instrument = $pipeline->instrumentation; |
2964 | - return percentage_of( |
2965 | - $instrument->{execute}->{time} || 0, |
2966 | - $instrument->{Pipeline}->{time} || 0, |
2967 | - ); |
2968 | - }; |
2969 | - |
2970 | - $et = new ExecutionThrottler( |
2971 | - rate_max => $rate_max, |
2972 | - get_rate => $get_rate, |
2973 | - check_int => $int, |
2974 | - step => $step, |
2975 | - ); |
2976 | - |
2977 | - $pipeline->add( |
2978 | - name => 'execute throttle', |
2979 | - process => sub { |
2980 | - my ( $args ) = @_; |
2981 | - $args->{event} = $et->throttle( |
2982 | - event => $args->{event}, |
2983 | - stats => \%stats, |
2984 | - misc => $args->{misc}, |
2985 | - ); |
2986 | - return $args; |
2987 | - }, |
2988 | - ); |
2989 | - } # execute throttle |
2990 | - |
2991 | - if ( $ex_dsn = $o->get('execute') ) { |
2992 | - if ( $o->get('ask-pass') ) { |
2993 | - $ex_dsn->{p} = OptionParser::prompt_noecho("Enter password for " |
2994 | - . "--execute: "); |
2995 | - $o->set('execute', $ex_dsn); |
2996 | - } |
2997 | - |
2998 | - my $cur_server = 'execute'; |
2999 | - ($cur_server, $ex_dbh) = find_role( |
3000 | - OptionParser => $o, |
3001 | - DSNParser => $dp, |
3002 | - dbh => $ex_dbh, |
3003 | - current => $cur_server, |
3004 | - read_only => 1, |
3005 | - comment => 'for --execute' |
3006 | - ); |
3007 | - my $cur_time = time(); |
3008 | - my $curdb; |
3009 | - my $default_db = $o->get('execute')->{D}; |
3010 | - PTDEBUG && _d('Default db:', $default_db); |
3011 | - |
3012 | - $pipeline->add( |
3013 | - name => 'execute', |
3014 | - process => sub { |
3015 | - my ( $args ) = @_; |
3016 | - my $event = $args->{event}; |
3017 | - $event->{Exec_orig_time} = $event->{Query_time}; |
3018 | - if ( ($event->{Skip_exec} || '') eq 'Yes' ) { |
3019 | - PTDEBUG && _d('Not executing event because of ', |
3020 | - '--execute-throttle'); |
3021 | - # Zero Query_time to 'Exec time' will show the real time |
3022 | - # spent executing queries. |
3023 | - $event->{Query_time} = 0; |
3024 | - $stats{execute_skipped}++; |
3025 | - return $args; |
3026 | - } |
3027 | - $stats{execute_executed}++; |
3028 | - my $db = $event->{db} || $default_db; |
3029 | - eval { |
3030 | - if ( $db && (!$curdb || $db ne $curdb) ) { |
3031 | - $ex_dbh->do("USE $db"); |
3032 | - $curdb = $db; |
3033 | - } |
3034 | - my $start = time(); |
3035 | - $ex_dbh->do($event->{arg}); |
3036 | - my $end = time(); |
3037 | - $event->{Query_time} = $end - $start; |
3038 | - $event->{Exec_diff_time} |
3039 | - = $event->{Query_time} - $event->{Exec_orig_time}; |
3040 | - if ($o->get('mirror') && $end-$cur_time > $o->get('mirror')) { |
3041 | - ($cur_server, $ex_dbh) = find_role( |
3042 | - OptionParser => $o, |
3043 | - DSNParser => $dp, |
3044 | - dbh => $ex_dbh, |
3045 | - current => $cur_server, |
3046 | - read_only => 1, |
3047 | - comment => 'for --execute' |
3048 | - ); |
3049 | - $cur_time = $end; |
3050 | - } |
3051 | - }; |
3052 | - if ( $EVAL_ERROR ) { |
3053 | - PTDEBUG && _d($EVAL_ERROR); |
3054 | - $stats{execute_error}++; |
3055 | - # Don't try to re-execute the statement. Just skip it. |
3056 | - if ( $EVAL_ERROR =~ m/server has gone away/ ) { |
3057 | - print STDERR $EVAL_ERROR; |
3058 | - eval { |
3059 | - ($cur_server, $ex_dbh) = find_role( |
3060 | - OptionParser => $o, |
3061 | - DSNParser => $dp, |
3062 | - dbh => $ex_dbh, |
3063 | - current => $cur_server, |
3064 | - read_only => 1, |
3065 | - comment => 'for --execute' |
3066 | - ); |
3067 | - $cur_time = time(); |
3068 | - }; |
3069 | - if ( $EVAL_ERROR ) { |
3070 | - print STDERR $EVAL_ERROR; |
3071 | - sleep 1; |
3072 | - } |
3073 | - return; |
3074 | - } |
3075 | - if ( $EVAL_ERROR =~ m/No database/ ) { |
3076 | - $stats{execute_no_database}++; |
3077 | - } |
3078 | - } |
3079 | - return $args; |
3080 | - }, |
3081 | - ); |
3082 | - } # execute |
3083 | - } # execute throttle and execute |
3084 | - |
3085 | if ( $o->get('print') ) { |
3086 | my $w = new SlowLogWriter(); |
3087 | $pipeline->add( |
3088 | @@ -14636,7 +14742,6 @@ |
3089 | instances => [ |
3090 | ($qv_dbh ? { dbh => $qv_dbh, dsn => $review_dsn } : ()), |
3091 | ($ps_dbh ? { dbh => $ps_dbh, dsn => $ps_dsn } : ()), |
3092 | - ($ex_dbh ? { dbh => $ex_dbh, dsn => $ex_dsn } : ()) |
3093 | ], |
3094 | protocol => $o->get('version-check'), |
3095 | ); |
3096 | @@ -14661,13 +14766,15 @@ |
3097 | } |
3098 | PTDEBUG && _d("Pipeline data:", Dumper($pipeline_data)); |
3099 | |
3100 | + save_resume_offset(); |
3101 | + |
3102 | # Disconnect all open $dbh's |
3103 | map { |
3104 | $dp->disconnect($_); |
3105 | PTDEBUG && _d('Disconnected dbh', $_); |
3106 | } |
3107 | grep { $_ } |
3108 | - ($qv_dbh, $qv_dbh2, $ex_dbh, $ps_dbh, $ep_dbh, $aux_dbh); |
3109 | + ($qv_dbh, $ps_dbh, $ep_dbh, $aux_dbh); |
3110 | |
3111 | return 0; |
3112 | } # End main() |
3113 | @@ -14676,6 +14783,77 @@ |
3114 | # Subroutines. |
3115 | # ############################################################################ |
3116 | |
3117 | +sub create_review_tables { |
3118 | + my ( %args ) = @_; |
3119 | + my @required_args = qw(dbh full_table TableParser); |
3120 | + foreach my $arg ( @required_args ) { |
3121 | + die "I need a $arg argument" unless $args{$arg}; |
3122 | + } |
3123 | + my $create_table_sql = $args{create_table_sql}; |
3124 | + my ($dbh, $full_table, $tp) = @args{@required_args}; |
3125 | + |
3126 | + PTDEBUG && _d('Checking --review table', $full_table); |
3127 | + |
3128 | + # If the repl db doesn't exit, auto-create it, maybe. |
3129 | + my ($db, $tbl) = Quoter->split_unquote($full_table); |
3130 | + my $show_db_sql = qq{SHOW DATABASES LIKE '$db'}; |
3131 | + PTDEBUG && _d($show_db_sql); |
3132 | + my @db_exists = $dbh->selectrow_array($show_db_sql); |
3133 | + if ( !@db_exists && !$args{create_table} ) { |
3134 | + die "--review database $db does not exist and " |
3135 | + . "--no-create-review-tables was specified. You need " |
3136 | + . "to create the database.\n"; |
3137 | + } |
3138 | + else { |
3139 | + # Even if the db already exists, do this in case it does not exist |
3140 | + # on a slave. |
3141 | + my $create_db_sql |
3142 | + = "CREATE DATABASE IF NOT EXISTS " |
3143 | + . Quoter->quote($db) |
3144 | + . " /* $tool */"; |
3145 | + PTDEBUG && _d($create_db_sql); |
3146 | + eval { |
3147 | + $dbh->do($create_db_sql); |
3148 | + }; |
3149 | + if ( $EVAL_ERROR && !@db_exists ) { |
3150 | + warn $EVAL_ERROR; |
3151 | + die "--review database $db does not exist and it cannot be " |
3152 | + . "created automatically. You need to create the database.\n"; |
3153 | + } |
3154 | + } |
3155 | + |
3156 | + # USE the correct db |
3157 | + my $sql = "USE " . Quoter->quote($db); |
3158 | + PTDEBUG && _d($sql); |
3159 | + $dbh->do($sql); |
3160 | + |
3161 | + # Check if the table exists; if not, create it, maybe. |
3162 | + my $tbl_exists = $tp->check_table( |
3163 | + dbh => $dbh, |
3164 | + db => $db, |
3165 | + tbl => $tbl, |
3166 | + ); |
3167 | + |
3168 | + PTDEBUG && _d('Table exists: ', $tbl_exists ? 'yes' : 'no'); |
3169 | + |
3170 | + if ( !$tbl_exists && !$args{create_table} ) { |
3171 | + die "Table $full_table does not exist and " |
3172 | + . "--no-create-review-tables was specified. " |
3173 | + . "You need to create the table.\n"; |
3174 | + } |
3175 | + else { |
3176 | + PTDEBUG && _d($dbh, $create_table_sql); |
3177 | + eval { |
3178 | + $dbh->do($create_table_sql); |
3179 | + }; |
3180 | + if ( $EVAL_ERROR && !$args{create_table} ) { |
3181 | + warn $EVAL_ERROR; |
3182 | + die "--review history table $full_table does not exist and it cannot be " |
3183 | + . "created automatically. You need to create the table.\n" |
3184 | + } |
3185 | + } |
3186 | +} |
3187 | + |
3188 | # TODO: This sub is poorly named since it does more than print reports: |
3189 | # it aggregates, reports, does QueryReview stuff, etc. |
3190 | sub print_reports { |
3191 | @@ -14694,9 +14872,7 @@ |
3192 | |
3193 | for my $i ( 0..$#groupby ) { |
3194 | if ( $o->get('report') || $qv ) { |
3195 | - $eas->[$i]->calculate_statistical_metrics( |
3196 | - apdex_t => $o->get('apdex-threshold'), |
3197 | - ); |
3198 | + $eas->[$i]->calculate_statistical_metrics(); |
3199 | } |
3200 | |
3201 | my ($orderby_attrib, $orderby_func) = split(/:/, $orderby[$i]); |
3202 | @@ -14734,19 +14910,18 @@ |
3203 | $print_header = 1; |
3204 | } |
3205 | |
3206 | - my $qrf = new QueryReportFormatter( |
3207 | - dbh => $ep_dbh, |
3208 | - %args, |
3209 | - ); |
3210 | - # http://code.google.com/p/maatkit/issues/detail?id=1141 |
3211 | - $qrf->set_report_formatter( |
3212 | - report => 'profile', |
3213 | - formatter => new ReportFormatter ( |
3214 | - line_width => $o->get('explain') ? 82 : 74, |
3215 | - long_last_column => 1, |
3216 | - extend_right => 1, |
3217 | - ), |
3218 | - ); |
3219 | + my $report_class = $o->get('output') =~ m/\Ajson\z/i |
3220 | + ? 'JSONReportFormatter' |
3221 | + : 'QueryReportFormatter'; |
3222 | + my $qrf = $report_class->new( |
3223 | + dbh => $ep_dbh, |
3224 | + QueryReview => $args{QueryReview}, |
3225 | + QueryRewriter => $args{QueryRewriter}, |
3226 | + OptionParser => $args{OptionParser}, |
3227 | + QueryParser => $args{QueryParser}, |
3228 | + Quoter => $args{Quoter}, |
3229 | + ); |
3230 | + |
3231 | $qrf->print_reports( |
3232 | reports => \@reports, |
3233 | ea => $eas->[$i], |
3234 | @@ -14777,14 +14952,6 @@ |
3235 | $tls->[$i]->reset_aggregated_data(); |
3236 | } |
3237 | |
3238 | - if ( $o->get('table-access') ) { # --table-access |
3239 | - print_table_access_report( |
3240 | - ea => $eas->[$i], |
3241 | - worst => $worst, |
3242 | - %args, |
3243 | - ); |
3244 | - } |
3245 | - |
3246 | $eas->[$i]->reset_aggregated_data(); # Reset for next iteration. |
3247 | |
3248 | # Print header report only once. So remove it from the |
3249 | @@ -14795,7 +14962,7 @@ |
3250 | |
3251 | } # Each groupby |
3252 | |
3253 | - if ( $o->get('pipeline-profile') ) { |
3254 | + if ( PTDEBUG ) { |
3255 | my $report = new ReportFormatter( |
3256 | line_width => 74, |
3257 | ); |
3258 | @@ -14804,7 +14971,7 @@ |
3259 | { name => 'Time', right_justify => 1 }, |
3260 | { name => 'Count', right_justify => 1 }, |
3261 | ); |
3262 | - $report->set_title('Pipeline profile'); |
3263 | + $report->title('Pipeline profile'); |
3264 | my $instrument = $pipeline->instrumentation; |
3265 | my $total_time = $instrument->{Pipeline}; |
3266 | foreach my $process_name ( $pipeline->processes() ) { |
3267 | @@ -14816,51 +14983,12 @@ |
3268 | # Reset profile for next iteration. |
3269 | $pipeline->reset(); |
3270 | |
3271 | - print "\n" . $report->get_report(); |
3272 | + _d($report->get_report()); |
3273 | } |
3274 | |
3275 | return; |
3276 | } |
3277 | |
3278 | -# Pass in the currently open $dbh (if any), where $current points to ('execute' |
3279 | -# or 'processlist') and whether you want to be connected to the read_only |
3280 | -# server. Get back which server you're looking at, and the $dbh. Assumes that |
3281 | -# one of the servers is ALWAYS read only and the other is ALWAYS not! If |
3282 | -# there's some transition period where this isn't true, maybe both will end up |
3283 | -# pointing to the same place, but that should resolve shortly. |
3284 | -# The magic switching functionality only works if --mirror is given! Otherwise |
3285 | -# it just returns the correct $dbh. $comment is some descriptive text for |
3286 | -# debuggin, like 'for --execute'. |
3287 | -sub find_role { |
3288 | - my ( %args ) = @_; |
3289 | - my $o = $args{OptionParser}; |
3290 | - my $dp = $args{DSNParser}; |
3291 | - my $dbh = $args{dbh}; |
3292 | - my $current = $args{current}; |
3293 | - my $read_only = $args{read_only}; |
3294 | - my $comment = $args{comment}; |
3295 | - |
3296 | - if ( !$dbh || !$dbh->ping ) { |
3297 | - PTDEBUG && _d('Getting a dbh from', $current, $comment); |
3298 | - $dbh = $dp->get_dbh( |
3299 | - $dp->get_cxn_params($o->get($current)), {AutoCommit => 1}); |
3300 | - $dbh->{InactiveDestroy} = 1; # Don't die on fork(). |
3301 | - } |
3302 | - if ( $o->get('mirror') ) { |
3303 | - my ( $is_read_only ) = $dbh->selectrow_array('SELECT @@global.read_only'); |
3304 | - PTDEBUG && _d("read_only on", $current, $comment, ':', |
3305 | - $is_read_only, '(want', $read_only, ')'); |
3306 | - if ( $is_read_only != $read_only ) { |
3307 | - $current = $current eq 'execute' ? 'processlist' : 'execute'; |
3308 | - PTDEBUG && _d("read_only wrong", $comment, "getting a dbh from", $current); |
3309 | - $dbh = $dp->get_dbh( |
3310 | - $dp->get_cxn_params($o->get($current)), {AutoCommit => 1}); |
3311 | - $dbh->{InactiveDestroy} = 1; # Don't die on fork(). |
3312 | - } |
3313 | - } |
3314 | - return ($current, $dbh); |
3315 | -} |
3316 | - |
3317 | # Catches signals so we can exit gracefully. |
3318 | sub sig_int { |
3319 | my ( $signal ) = @_; |
3320 | @@ -14870,6 +14998,7 @@ |
3321 | } |
3322 | else { |
3323 | print STDERR "# Exiting on SIG$signal.\n"; |
3324 | + save_resume_offset(); |
3325 | exit(1); |
3326 | } |
3327 | } |
3328 | @@ -15009,54 +15138,6 @@ |
3329 | return $ea->top_events(%top_spec); |
3330 | } |
3331 | |
3332 | -sub print_table_access_report { |
3333 | - my ( %args ) = @_; |
3334 | - my @required_args = qw(ea worst QueryParser QueryRewriter OptionParser Quoter); |
3335 | - foreach my $arg ( @required_args ) { |
3336 | - die "I need a $arg argument" unless $args{$arg}; |
3337 | - } |
3338 | - my ($ea, $worst, $qp, $qr, $o, $q) = @args{@required_args}; |
3339 | - |
3340 | - my %seen; |
3341 | - PTDEBUG && _d('Doing table access report'); |
3342 | - |
3343 | - foreach my $worst_info ( @$worst ) { |
3344 | - my $item = $worst_info->[0]; |
3345 | - my $stats = $ea->results->{classes}->{$item}; |
3346 | - my $sample = $ea->results->{samples}->{$item}; |
3347 | - my $samp_query = $sample->{arg} || ''; |
3348 | - my ($default_db) = $sample->{db} ? $sample->{db} |
3349 | - : $stats->{db}->{unq} ? keys %{$stats->{db}->{unq}} |
3350 | - : undef; |
3351 | - eval { |
3352 | - QUERY: |
3353 | - foreach my $query ( $qp->split($samp_query) ) { |
3354 | - my $rw = $qp->query_type($query, $qr)->{rw}; |
3355 | - next QUERY unless $rw; |
3356 | - my @tables = $qp->extract_tables( |
3357 | - query => $query, |
3358 | - default_db => $default_db, |
3359 | - Quoter => $args{Quoter}, |
3360 | - ); |
3361 | - next QUERY unless scalar @tables; |
3362 | - DB_TBL: |
3363 | - foreach my $tbl_info ( @tables ) { |
3364 | - my ($db, $tbl) = @$tbl_info; |
3365 | - $db = $db ? "`$db`." : ''; |
3366 | - next DB_TBL if $seen{"$db$tbl"}++; # Unique-ify for issue 337. |
3367 | - print "$rw $db`$tbl`\n"; |
3368 | - } |
3369 | - } |
3370 | - }; |
3371 | - if ( $EVAL_ERROR ) { |
3372 | - PTDEBUG && _d($EVAL_ERROR); |
3373 | - warn "Cannot get table access for query $_"; |
3374 | - } |
3375 | - } |
3376 | - |
3377 | - return; |
3378 | -} |
3379 | - |
3380 | sub update_query_review_tables { |
3381 | my ( %args ) = @_; |
3382 | foreach my $arg ( qw(ea worst QueryReview OptionParser) ) { |
3383 | @@ -15082,17 +15163,15 @@ |
3384 | first_seen => $stats->{ts}->{min}, |
3385 | last_seen => $stats->{ts}->{max} |
3386 | ); |
3387 | - if ( $o->get('review-history') ) { |
3388 | - my %history; |
3389 | - foreach my $attrib ( @$attribs ) { |
3390 | - $history{$attrib} = $ea->metrics( |
3391 | - attrib => $attrib, |
3392 | - where => $item, |
3393 | - ); |
3394 | - } |
3395 | - $qv->set_review_history( |
3396 | - $item, $sample->{arg} || '', %history); |
3397 | + my %history; |
3398 | + foreach my $attrib ( @$attribs ) { |
3399 | + $history{$attrib} = $ea->metrics( |
3400 | + attrib => $attrib, |
3401 | + where => $item, |
3402 | + ); |
3403 | } |
3404 | + $qv->set_review_history( |
3405 | + $item, $sample->{arg} || '', %history); |
3406 | } |
3407 | |
3408 | return; |
3409 | @@ -15163,6 +15242,23 @@ |
3410 | return $boundary; |
3411 | } |
3412 | |
3413 | +sub save_resume_offset { |
3414 | + if ( !$resume_file || !$offset ) { |
3415 | + PTDEBUG && _d('Not saving resume offset because there is no ' |
3416 | + . 'resume file or offset:', $resume_file, $offset); |
3417 | + return; |
3418 | + } |
3419 | + |
3420 | + PTDEBUG && _d('Saving resume at offset', $offset, 'to', $resume_file); |
3421 | + open my $resume_fh, '>', $resume_file |
3422 | + or die "Error opening $resume_file: $OS_ERROR"; |
3423 | + print { $resume_fh } $offset, "\n"; |
3424 | + close $resume_fh |
3425 | + or die "Error close $resume_file: $OS_ERROR"; |
3426 | + warn "\n# Saved resume file offset $offset to $resume_file\n"; |
3427 | + return; |
3428 | +} |
3429 | + |
3430 | sub _d { |
3431 | my ($package, undef, $line) = caller 0; |
3432 | @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; } |
3433 | @@ -15202,13 +15298,8 @@ |
3434 | Review a slow log, saving results to the test.query_review table in a MySQL |
3435 | server running on host1. See L<"--review"> for more on reviewing queries: |
3436 | |
3437 | - pt-query-digest --review h=host1,D=test,t=query_review /path/to/slow.log |
3438 | - |
3439 | -Filter out everything but SELECT queries, replay the queries against another |
3440 | -server, then use the timings from replaying them to analyze their performance: |
3441 | - |
3442 | - pt-query-digest /path/to/slow.log --execute h=another_server \ |
3443 | - --filter '$event->{fingerprint} =~ m/^select/' |
3444 | + pt-query-digest --review h=host1 --review-table test.query_review |
3445 | + --history-table test.query_history /path/to/slow.log |
3446 | |
3447 | Print the structure of events so you can construct a complex L<"--filter">: |
3448 | |
3449 | @@ -15238,8 +15329,7 @@ |
3450 | until you are satisfied that the input you give it does not cause undue load. |
3451 | |
3452 | Various options will cause pt-query-digest to insert data into tables, execute |
3453 | -SQL queries, and so on. These include the L<"--execute"> option and |
3454 | -L<"--review">. |
3455 | +SQL queries, and so on. These include the L<"--review"> option. |
3456 | |
3457 | At the time of this release, we know of no bugs that could cause serious harm |
3458 | to users. |
3459 | @@ -15348,9 +15438,7 @@ |
3460 | Response time The total response time, and percentage of overall total |
3461 | Calls The number of times this query was executed |
3462 | R/Call The mean response time per execution |
3463 | - Apdx The Apdex score; see --apdex-threshold for details |
3464 | V/M The Variance-to-mean ratio of response time |
3465 | - EXPLAIN If --explain was specified, a sparkline; see --explain |
3466 | Item The distilled query |
3467 | |
3468 | A final line whose rank is shown as MISC contains aggregate statistics on the |
3469 | @@ -15464,12 +15552,6 @@ |
3470 | |
3471 | See also L<"--report-format">. |
3472 | |
3473 | -=head2 SPARKLINES |
3474 | - |
3475 | -The output also contains sparklines. Sparklines are "data-intense, |
3476 | -design-simple, word-sized graphics" (L<http://en.wikipedia.org/wiki/Sparkline>).There is a sparkline for L<"--report-histogram"> and for L<"--explain">. |
3477 | -See each of those options for details about interpreting their sparklines. |
3478 | - |
3479 | =head1 QUERY REVIEWS |
3480 | |
3481 | A "query review" is the process of storing all the query fingerprints analyzed. |
3482 | @@ -15534,9 +15616,9 @@ |
3483 | You can see how useful this meta-data is -- as you analyze your queries, you get |
3484 | your comments integrated right into the report. |
3485 | |
3486 | -If you add the L<"--review-history"> option, it will also store information into |
3487 | -a separate database table, so you can keep historical trending information on |
3488 | -classes of queries. |
3489 | +The tool will also store information into a separate database table specified |
3490 | +by the L<"--history-table"> option, so you can keep historical trending information |
3491 | +on classes of queries. |
3492 | |
3493 | =back |
3494 | |
3495 | @@ -15640,27 +15722,11 @@ |
3496 | |
3497 | =head1 OPTIONS |
3498 | |
3499 | -DSN values in L<"--review-history"> default to values in L<"--review"> if COPY |
3500 | -is yes. |
3501 | - |
3502 | This tool accepts additional command-line arguments. Refer to the |
3503 | L<"SYNOPSIS"> and usage information for details. |
3504 | |
3505 | =over |
3506 | |
3507 | -=item --apdex-threshold |
3508 | - |
3509 | -type: float; default: 1.0 |
3510 | - |
3511 | -Set Apdex target threshold (T) for query response time. The Application |
3512 | -Performance Index (Apdex) Technical Specification V1.1 defines T as "a |
3513 | -positive decimal value in seconds, having no more than two significant digits |
3514 | -of granularity." This value only applies to query response time (Query_time). |
3515 | - |
3516 | -Options can be abbreviated so specifying C<--apdex-t> also works. |
3517 | - |
3518 | -See L<http://www.apdex.org/>. |
3519 | - |
3520 | =item --ask-pass |
3521 | |
3522 | Prompt for a password when connecting to MySQL. |
3523 | @@ -15740,21 +15806,19 @@ |
3524 | |
3525 | default: yes |
3526 | |
3527 | -Continue parsing even if there is an error. |
3528 | - |
3529 | -=item --create-review-history-table |
3530 | - |
3531 | -Create the L<"--review-history"> table if it does not exist. |
3532 | - |
3533 | -This option causes the table specified by L<"--review-history"> to be created |
3534 | -with the default structure shown in the documentation for that option. |
3535 | - |
3536 | -=item --create-review-table |
3537 | - |
3538 | -Create the L<"--review"> table if it does not exist. |
3539 | - |
3540 | -This option causes the table specified by L<"--review"> to be created with the |
3541 | -default structure shown in the documentation for that option. |
3542 | +Continue parsing even if there is an error. The tool will not continue |
3543 | +forever: it stops once any process causes 100 errors, in which case there |
3544 | +is probably a bug in the tool or the input is invalid. |
3545 | + |
3546 | +=item --[no]create-review-tables |
3547 | + |
3548 | +default: yes |
3549 | + |
3550 | +Create the L<"--review"> tables if they do not exist. |
3551 | + |
3552 | +This option causes the tables specified by L<"--review-table"> and |
3553 | +L<"--history-table"> to be created with the default structures shown |
3554 | +in the documentation for L<"--review">. |
3555 | |
3556 | =item --daemonize |
3557 | |
3558 | @@ -15800,50 +15864,6 @@ |
3559 | B<NOTE>: All commas in the regex patterns must be escaped with \ otherwise |
3560 | the pattern will break. |
3561 | |
3562 | -=item --execute |
3563 | - |
3564 | -type: DSN |
3565 | - |
3566 | -Execute queries on this DSN. |
3567 | - |
3568 | -Adds a callback into the chain, after filters but before the reports. Events |
3569 | -are executed on this DSN. If they are successful, the time they take to execute |
3570 | -overwrites the event's Query_time attribute and the original Query_time value |
3571 | -(from the log) is saved as the Exec_orig_time attribute. If unsuccessful, |
3572 | -the callback returns false and terminates the chain. |
3573 | - |
3574 | -If the connection fails, pt-query-digest tries to reconnect once per second. |
3575 | - |
3576 | -See also L<"--mirror"> and L<"--execute-throttle">. |
3577 | - |
3578 | -=item --execute-throttle |
3579 | - |
3580 | -type: array |
3581 | - |
3582 | -Throttle values for L<"--execute">. |
3583 | - |
3584 | -By default L<"--execute"> runs without any limitations or concerns for the |
3585 | -amount of time that it takes to execute the events. The L<"--execute-throttle"> |
3586 | -allows you to limit the amount of time spent doing L<"--execute"> relative |
3587 | -to the other processes that handle events. This works by marking some events |
3588 | -with a C<Skip_exec> attribute when L<"--execute"> begins to take too much time. |
3589 | -L<"--execute"> will not execute an event if this attribute is true. This |
3590 | -indirectly decreases the time spent doing L<"--execute">. |
3591 | - |
3592 | -The L<"--execute-throttle"> option takes at least two comma-separated values: |
3593 | -max allowed L<"--execute"> time as a percentage and a check interval time. An |
3594 | -optional third value is a percentage step for increasing and decreasing the |
3595 | -probability that an event will be marked C<Skip_exec> true. 5 (percent) is |
3596 | -the default step. |
3597 | - |
3598 | -For example: L<"--execute-throttle"> C<70,60,10>. This will limit |
3599 | -L<"--execute"> to 70% of total event processing time, checked every minute |
3600 | -(60 seconds) and probability stepped up and down by 10%. When L<"--execute"> |
3601 | -exceeds 70%, the probability that events will be marked C<Skip_exec> true |
3602 | -increases by 10%. L<"--execute"> time is checked again after another minute. |
3603 | -If it's still above 70%, then the probability will increase another 10%. |
3604 | -Or, if it's dropped below 70%, then the probability will decrease by 10%. |
3605 | - |
3606 | =item --expected-range |
3607 | |
3608 | type: array; default: 5,10 |
3609 | @@ -15868,41 +15888,10 @@ |
3610 | |
3611 | select ... from ( select .... ) der; |
3612 | |
3613 | -The EXPLAIN results are printed in three places: a sparkline in the event |
3614 | -header, a full vertical format in the event report, and a sparkline in the |
3615 | -profile. |
3616 | - |
3617 | -The full format appears at the end of each event report in vertical style |
3618 | +The EXPLAIN results are printed as a full vertical format in the event report, |
3619 | +which appears at the end of each event report in vertical style |
3620 | (C<\G>) just like MySQL prints it. |
3621 | |
3622 | -The sparklines (see L<"SPARKLINES">) are compact representations of the |
3623 | -access type for each table and whether or not "Using temporary" or "Using |
3624 | -filesort" appear in EXPLAIN. The sparklines look like: |
3625 | - |
3626 | - nr>TF |
3627 | - |
3628 | -That sparkline means that there are two tables, the first uses a range (n) |
3629 | -access, the second uses a ref access, and both "Using temporary" (T) and |
3630 | -"Using filesort" (F) appear. The greater-than character just separates table |
3631 | -access codes from T and/or F. |
3632 | - |
3633 | -The abbreviated table access codes are: |
3634 | - |
3635 | - a ALL |
3636 | - c const |
3637 | - e eq_ref |
3638 | - f fulltext |
3639 | - i index |
3640 | - m index_merge |
3641 | - n range |
3642 | - o ref_or_null |
3643 | - r ref |
3644 | - s system |
3645 | - u unique_subquery |
3646 | - |
3647 | -A capitalized access code means that "Using index" appears in EXPLAIN for |
3648 | -that table. |
3649 | - |
3650 | =item --filter |
3651 | |
3652 | type: string |
3653 | @@ -15992,22 +15981,6 @@ |
3654 | Since L<"--filter"> allows you to alter C<$event>, you can use it to do other |
3655 | things, like create new attributes. See L<"ATTRIBUTES"> for an example. |
3656 | |
3657 | -=item --fingerprints |
3658 | - |
3659 | -Add query fingerprints to the standard query analysis report. This is mostly |
3660 | -useful for debugging purposes. |
3661 | - |
3662 | -=item --[no]for-explain |
3663 | - |
3664 | -default: yes |
3665 | - |
3666 | -Print extra information to make analysis easy. |
3667 | - |
3668 | -This option adds code snippets to make it easy to run SHOW CREATE TABLE and SHOW |
3669 | -TABLE STATUS for the query's tables. It also rewrites non-SELECT queries into a |
3670 | -SELECT that might be helpful for determining the non-SELECT statement's index |
3671 | -usage. |
3672 | - |
3673 | =item --group-by |
3674 | |
3675 | type: Array; default: fingerprint |
3676 | @@ -16063,6 +16036,12 @@ |
3677 | |
3678 | Show help and exit. |
3679 | |
3680 | +=item --history-table |
3681 | + |
3682 | +type: string; default: percona_schema.query_history |
3683 | + |
3684 | +Where to save the historical data produced by L<"--review">. |
3685 | + |
3686 | =item --host |
3687 | |
3688 | short form: -h; type: string |
3689 | @@ -16092,10 +16071,6 @@ |
3690 | to "foo", but the next event doesn't have the db attribute, then it inherits |
3691 | "foo" for its db attribute. |
3692 | |
3693 | -Inheritance is usually desirable, but in some cases it might confuse things. |
3694 | -If a query inherits a database that it doesn't actually use, then this could |
3695 | -confuse L<"--execute">. |
3696 | - |
3697 | =item --interval |
3698 | |
3699 | type: float; default: .1 |
3700 | @@ -16138,20 +16113,6 @@ |
3701 | |
3702 | Print all output to this file when daemonized. |
3703 | |
3704 | -=item --mirror |
3705 | - |
3706 | -type: float |
3707 | - |
3708 | -How often to check whether connections should be moved, depending on |
3709 | -C<read_only>. Requires L<"--processlist"> and L<"--execute">. |
3710 | - |
3711 | -This option causes pt-query-digest to check every N seconds whether it is reading |
3712 | -from a read-write server and executing against a read-only server, which is a |
3713 | -sensible way to set up two servers if you're doing something like master-master |
3714 | -replication. The L<http://code.google.com/p/mysql-master-master/> master-master |
3715 | -toolkit does this. The aim is to keep the passive server ready for failover, |
3716 | -which is impossible without putting it under a realistic workload. |
3717 | - |
3718 | =item --order-by |
3719 | |
3720 | type: Array; default: Query_time:sum |
3721 | @@ -16211,6 +16172,13 @@ |
3722 | |
3723 | You can specify an --outliers option for each value in L<"--group-by">. |
3724 | |
3725 | + |
3726 | +=item --output |
3727 | + |
3728 | +type: string; default: query |
3729 | + |
3730 | +Type of report to use. Accepted values are C<"query"> and C<"json">. |
3731 | + |
3732 | =item --password |
3733 | |
3734 | short form: -p; type: string |
3735 | @@ -16227,10 +16195,6 @@ |
3736 | PID file when starting; if it exists and the process with the matching PID |
3737 | exists, the program exits. |
3738 | |
3739 | -=item --pipeline-profile |
3740 | - |
3741 | -Print a profile of the pipeline processes. |
3742 | - |
3743 | =item --port |
3744 | |
3745 | short form: -P; type: int |
3746 | @@ -16259,8 +16223,7 @@ |
3747 | |
3748 | Poll this DSN's processlist for queries, with L<"--interval"> sleep between. |
3749 | |
3750 | -If the connection fails, pt-query-digest tries to reopen it once per second. See |
3751 | -also L<"--mirror">. |
3752 | +If the connection fails, pt-query-digest tries to reopen it once per second. |
3753 | |
3754 | =item --progress |
3755 | |
3756 | @@ -16341,36 +16304,35 @@ |
3757 | # 1s ######## |
3758 | # 10s+ |
3759 | |
3760 | -A sparkline (see L<"SPARKLINES">) of the full chart is also printed in the |
3761 | -header for each query event. The sparkline of that full chart is: |
3762 | - |
3763 | - # Query_time sparkline: | .^_ | |
3764 | - |
3765 | -The sparkline itself is the 8 characters between the pipes (C<|>), one character |
3766 | -for each of the 8 buckets (1us, 10us, etc.) Four character codes are used |
3767 | -to represent the approximate relation between each bucket's value: |
3768 | - |
3769 | - _ . - ^ |
3770 | - |
3771 | -The caret C<^> represents peaks (buckets with the most values), and |
3772 | -the underscore C<_> represents lows (buckets with the least or at least |
3773 | -one value). The period C<.> and the hyphen C<-> represent buckets with values |
3774 | -between these two extremes. If a bucket has no values, a space is printed. |
3775 | -So in the example above, the period represents the 10ms bucket, the caret |
3776 | -the 100ms bucket, and the underscore the 1s bucket. |
3777 | - |
3778 | See L<"OUTPUT"> for more information. |
3779 | |
3780 | +=item --resume |
3781 | + |
3782 | +type: string |
3783 | + |
3784 | +If specified, the tool writes the last file offset, if there is one, |
3785 | +to the given filename. When ran again with the same value for this option, |
3786 | +the tool reads the last file offset from the file, seeks to that position |
3787 | +in the log, and resumes parsing events from that point onward. |
3788 | + |
3789 | =item --review |
3790 | |
3791 | type: DSN |
3792 | |
3793 | -Store a sample of each class of query in this DSN. |
3794 | - |
3795 | -The argument specifies a table to store all unique query fingerprints in. The |
3796 | -table must have at least the following columns. You can add more columns for |
3797 | -your own special purposes, but they won't be used by pt-query-digest. The |
3798 | -following CREATE TABLE definition is also used for L<"--create-review-table">. |
3799 | +Store a sample of each class of query in this DSN, plus historical values |
3800 | +for review trend analysis |
3801 | + |
3802 | +The argument specifies a host to store all unique query fingerprints in; the |
3803 | +databases and tables were this data is stored can be specified with the |
3804 | +L<"--review-table"> and L<"--history-table"> options. |
3805 | +By default, if the table doesn't exist the tool mtries creating it; This |
3806 | +behavior can bhe controlled with the L<"--[no]create-review-tables"> option. |
3807 | +If the table was created manually, it must have at least the following columns. |
3808 | +You can add more columns for your own special purposes, but they won't be used |
3809 | +by pt-query-digest. The following CREATE TABLE definition is also used by |
3810 | +L<"--no-create-review-tables">. |
3811 | + |
3812 | +=for comment ignore-pt-internal-value |
3813 | MAGIC_create_review: |
3814 | |
3815 | CREATE TABLE query_review ( |
3816 | @@ -16405,23 +16367,12 @@ |
3817 | fingerprint. This option depends on C<--group-by fingerprint> (which is the |
3818 | default). It will not work otherwise. |
3819 | |
3820 | -=item --review-history |
3821 | - |
3822 | -type: DSN |
3823 | - |
3824 | -The table in which to store historical values for review trend analysis. |
3825 | - |
3826 | -Each time you review queries with L<"--review">, pt-query-digest will save |
3827 | -information into this table so you can see how classes of queries have changed |
3828 | -over time. |
3829 | - |
3830 | -This DSN inherits unspecified values from L<"--review">. It should mention a |
3831 | -table in which to store statistics about each class of queries. pt-query-digest |
3832 | -verifies the existence of the table, and your privileges to insert, delete and |
3833 | -update on that table. |
3834 | - |
3835 | -pt-query-digest then inspects the columns in the table. The table must have at |
3836 | -least the following columns: |
3837 | + |
3838 | +Additionally, pt-query-digest will save historical information into a review table, |
3839 | +so you can see how classes of queries have changed over time. You can |
3840 | +change the destination table with the L<"--history-table"> |
3841 | + |
3842 | +The table must have at least the following columns: |
3843 | |
3844 | CREATE TABLE query_review_history ( |
3845 | checksum BIGINT UNSIGNED NOT NULL, |
3846 | @@ -16430,7 +16381,10 @@ |
3847 | |
3848 | Any columns not mentioned above are inspected to see if they follow a certain |
3849 | naming convention. The column is special if the name ends with an underscore |
3850 | -followed by any of these MAGIC_history_cols values: |
3851 | +followed by any of these values: |
3852 | + |
3853 | +=for comment ignore-pt-internal-value |
3854 | +MAGIC_history_cols |
3855 | |
3856 | pct|avt|cnt|sum|min|max|pct_95|stddev|median|rank |
3857 | |
3858 | @@ -16446,8 +16400,11 @@ |
3859 | you could also just add a ts_min column and make it a DATE type, so you'd get |
3860 | one row per class of queries per day. |
3861 | |
3862 | -The default table structure follows. The following MAGIC_create_review_history |
3863 | -table definition is used for L<"--create-review-history-table">: |
3864 | +The default table structure follows. The following table definition is used |
3865 | +for L<"--[no]create-review-tables">: |
3866 | + |
3867 | +=for comment ignore-pt-internal-value |
3868 | +MAGIC_create_review_history |
3869 | |
3870 | CREATE TABLE query_review_history ( |
3871 | checksum BIGINT UNSIGNED NOT NULL, |
3872 | @@ -16553,6 +16510,12 @@ |
3873 | Note that we store the count (cnt) for the ts attribute only; it will be |
3874 | redundant to store this for other attributes. |
3875 | |
3876 | +=item --review-table |
3877 | + |
3878 | +type: string; default: percona_schema.query_review |
3879 | + |
3880 | +Where to save the samples produced by L<"--review">. |
3881 | + |
3882 | =item --run-time |
3883 | |
3884 | type: time |
3885 | @@ -16655,7 +16618,7 @@ |
3886 | |
3887 | Query_time,Lock_time,Rows_sent,Rows_examined,user,db:Schema,ts |
3888 | |
3889 | -Attributes specified in the L<"--review-history"> table will always be selected |
3890 | +Attributes in the table specified by L<"--history-table"> will always be selected |
3891 | even if you do not specify L<"--select">. |
3892 | |
3893 | See also L<"--ignore-attributes"> and L<"ATTRIBUTES">. |
3894 | @@ -16717,9 +16680,9 @@ |
3895 | CURRENT_DATE - INTERVAL 7 DAY |
3896 | |
3897 | If you give a MySQL time expression, then you must also specify a DSN |
3898 | -so that pt-query-digest can connect to MySQL to evaluate the expression. If you |
3899 | -specify L<"--execute">, L<"--explain">, L<"--processlist">, L<"--review"> |
3900 | -or L<"--review-history">, then one of these DSNs will be used automatically. |
3901 | +so that pt-query-digest can connect to MySQL to evaluate the expression. |
3902 | +If you specify L<"--explain">, L<"--processlist">, L<"--review">, then |
3903 | +one of these DSNs will be used automatically. |
3904 | Otherwise, you must specify an L<"--aux-dsn"> or pt-query-digest will die |
3905 | saying that the value is invalid. |
3906 | |
3907 | @@ -16742,59 +16705,6 @@ |
3908 | |
3909 | Socket file to use for connection. |
3910 | |
3911 | -=item --statistics |
3912 | - |
3913 | -Print statistics about internal counters. This option is mostly for |
3914 | -development and debugging. The statistics report is printed for each |
3915 | -iteration after all other reports, even if no events are processed or |
3916 | -C<--no-report> is specified. The statistics report looks like: |
3917 | - |
3918 | - # No events processed. |
3919 | - |
3920 | - # Statistic Count %/Events |
3921 | - # ================================================ ====== ======== |
3922 | - # events_read 142030 100.00 |
3923 | - # events_parsed 50430 35.51 |
3924 | - # events_aggregated 0 0.00 |
3925 | - # ignored_midstream_server_response 18111 12.75 |
3926 | - # no_tcp_data 91600 64.49 |
3927 | - # pipeline_restarted_after_MemcachedProtocolParser 142030 100.00 |
3928 | - # pipeline_restarted_after_TcpdumpParser 1 0.00 |
3929 | - # unknown_client_command 1 0.00 |
3930 | - # unknown_client_data 32318 22.75 |
3931 | - |
3932 | -The first column is the internal counter name; the second column is counter's |
3933 | -count; and the third column is the count as a percentage of C<events_read>. |
3934 | - |
3935 | -In this case, it shows why no events were processed/aggregated: 100% of events |
3936 | -were rejected by the C<MemcachedProtocolParser>. Of those, 35.51% were data |
3937 | -packets, but of these 12.75% of ignored mid-stream server response, one was |
3938 | -an unknown client command, and 22.75% were unknown client data. The other |
3939 | -64.49% were TCP control packets (probably most ACKs). |
3940 | - |
3941 | -Since pt-query-digest is complex, you will probably need someone familiar |
3942 | -with its code to decipher the statistics report. |
3943 | - |
3944 | -=item --table-access |
3945 | - |
3946 | -Print a table access report. |
3947 | - |
3948 | -The table access report shows which tables are accessed by all the queries |
3949 | -and if the access is a read or write. The report looks like: |
3950 | - |
3951 | - write `baz`.`tbl` |
3952 | - read `baz`.`new_tbl` |
3953 | - write `baz`.`tbl3` |
3954 | - write `db6`.`tbl6` |
3955 | - |
3956 | -If you pipe the output to L<sort>, the read and write tables will be grouped |
3957 | -together and sorted alphabetically: |
3958 | - |
3959 | - read `baz`.`new_tbl` |
3960 | - write `baz`.`tbl` |
3961 | - write `baz`.`tbl3` |
3962 | - write `db6`.`tbl6` |
3963 | - |
3964 | =item --tcpdump-errors |
3965 | |
3966 | type: string |
3967 | @@ -17092,7 +17002,8 @@ |
3968 | |
3969 | dsn: database; copy: yes |
3970 | |
3971 | -Database that contains the query review table. |
3972 | +Default database for the review option. Only useful if there are replication |
3973 | +filters set up. |
3974 | |
3975 | =item * F |
3976 | |
3977 | @@ -17126,7 +17037,7 @@ |
3978 | |
3979 | =item * t |
3980 | |
3981 | -Table to use as the query review table. |
3982 | +Not used. |
3983 | |
3984 | =item * u |
3985 | |
3986 | |
3987 | === modified file 'bin/pt-table-usage' |
3988 | --- bin/pt-table-usage 2013-01-03 00:54:18 +0000 |
3989 | +++ bin/pt-table-usage 2013-01-30 20:58:23 +0000 |
3990 | @@ -5345,7 +5345,7 @@ |
3991 | } |
3992 | |
3993 | my $self = { |
3994 | - instrument => 0, |
3995 | + instrument => PTDEBUG, |
3996 | continue_on_error => 0, |
3997 | |
3998 | %args, |
3999 | @@ -5372,9 +5372,7 @@ |
4000 | |
4001 | push @{$self->{procs}}, $process; |
4002 | push @{$self->{names}}, $name; |
4003 | - if ( my $n = $args{retry_on_error} ) { |
4004 | - $self->{retries}->{$name} = $n; |
4005 | - } |
4006 | + $self->{retries}->{$name} = $args{retry_on_error} || 100; |
4007 | if ( $self->{instrument} ) { |
4008 | $self->{instrumentation}->{$name} = { time => 0, calls => 0 }; |
4009 | } |
4010 | @@ -5443,7 +5441,11 @@ |
4011 | my $msg = "Pipeline process " . ($procno + 1) |
4012 | . " ($name) caused an error: " |
4013 | . $EVAL_ERROR; |
4014 | - if ( defined $self->{retries}->{$name} ) { |
4015 | + if ( !$self->{continue_on_error} ) { |
4016 | + die $msg . "Terminating pipeline because --continue-on-error " |
4017 | + . "is false.\n"; |
4018 | + } |
4019 | + elsif ( defined $self->{retries}->{$name} ) { |
4020 | my $n = $self->{retries}->{$name}; |
4021 | if ( $n ) { |
4022 | warn $msg . "Will retry pipeline process $procno ($name) " |
4023 | @@ -5455,9 +5457,6 @@ |
4024 | . "($name) caused too many errors.\n"; |
4025 | } |
4026 | } |
4027 | - elsif ( !$self->{continue_on_error} ) { |
4028 | - die $msg; |
4029 | - } |
4030 | else { |
4031 | warn $msg; |
4032 | } |
4033 | |
4034 | === modified file 'lib/EventAggregator.pm' |
4035 | --- lib/EventAggregator.pm 2013-01-03 00:19:16 +0000 |
4036 | +++ lib/EventAggregator.pm 2013-01-30 20:58:23 +0000 |
4037 | @@ -619,16 +619,6 @@ |
4038 | $classes->{$class}->{$attrib}->{all}, |
4039 | $classes->{$class}->{$attrib} |
4040 | ); |
4041 | - |
4042 | - # Apdex (http://code.google.com/p/maatkit/issues/detail?id=1054) |
4043 | - if ( $args{apdex_t} && $attrib eq 'Query_time' ) { |
4044 | - $class_metrics->{$class}->{$attrib}->{apdex_t} = $args{apdex_t}; |
4045 | - $class_metrics->{$class}->{$attrib}->{apdex} |
4046 | - = $self->calculate_apdex( |
4047 | - t => $args{apdex_t}, |
4048 | - samples => $classes->{$class}->{$attrib}->{all}, |
4049 | - ); |
4050 | - } |
4051 | } |
4052 | } |
4053 | } |
4054 | @@ -784,9 +774,6 @@ |
4055 | median => $metrics->{classes}->{$where}->{$attrib}->{median} || 0, |
4056 | pct_95 => $metrics->{classes}->{$where}->{$attrib}->{pct_95} || 0, |
4057 | stddev => $metrics->{classes}->{$where}->{$attrib}->{stddev} || 0, |
4058 | - |
4059 | - apdex_t => $metrics->{classes}->{$where}->{$attrib}->{apdex_t}, |
4060 | - apdex => $metrics->{classes}->{$where}->{$attrib}->{apdex}, |
4061 | }; |
4062 | } |
4063 | |
4064 | @@ -1164,70 +1151,6 @@ |
4065 | return $copy; |
4066 | } |
4067 | |
4068 | -# Sub: calculate_apdex |
4069 | -# Calculate the Apdex score for the given T and response times. |
4070 | -# <http://www.apdex.org/documents/ApdexTechnicalSpecificationV11_000.pdf> |
4071 | -# |
4072 | -# Parameters: |
4073 | -# %args - Arguments |
4074 | -# |
4075 | -# Required Arguments: |
4076 | -# t - Target threshold |
4077 | -# samples - Hashref with bucketized response time values, |
4078 | -# i.e. { bucket_number => n_responses, } |
4079 | -# |
4080 | -# Returns: |
4081 | -# Apdex score |
4082 | -sub calculate_apdex { |
4083 | - my ( $self, %args ) = @_; |
4084 | - my @required_args = qw(t samples); |
4085 | - foreach my $arg ( @required_args ) { |
4086 | - die "I need a $arg argument" unless $args{$arg}; |
4087 | - } |
4088 | - my ($t, $samples) = @args{@required_args}; |
4089 | - |
4090 | - if ( $t <= 0 ) { |
4091 | - die "Invalid target threshold (T): $t. T must be greater than zero"; |
4092 | - } |
4093 | - |
4094 | - my $f = 4 * $t; |
4095 | - PTDEBUG && _d("Apdex T =", $t, "F =", $f); |
4096 | - |
4097 | - my $satisfied = 0; |
4098 | - my $tolerating = 0; |
4099 | - my $frustrated = 0; # just for debug output |
4100 | - my $n_samples = 0; |
4101 | - BUCKET: |
4102 | - for my $bucket ( keys %$samples ) { |
4103 | - my $n_responses = $samples->{$bucket}; |
4104 | - my $response_time = $buck_vals[$bucket]; |
4105 | - |
4106 | - # Response time increases from 0 to F. |
4107 | - # 0 --- T --- F |
4108 | - # ^ ^-- tolerating zone |
4109 | - # | |
4110 | - # +-------- satisfied zone |
4111 | - if ( $response_time <= $t ) { |
4112 | - $satisfied += $n_responses; |
4113 | - } |
4114 | - elsif ( $response_time <= $f ) { |
4115 | - $tolerating += $n_responses; |
4116 | - } |
4117 | - else { |
4118 | - $frustrated += $n_responses; |
4119 | - } |
4120 | - |
4121 | - $n_samples += $n_responses; |
4122 | - } |
4123 | - |
4124 | - my $apdex = sprintf('%.2f', ($satisfied + ($tolerating / 2)) / $n_samples); |
4125 | - PTDEBUG && _d($n_samples, "samples,", $satisfied, "satisfied,", |
4126 | - $tolerating, "tolerating,", $frustrated, "frustrated, Apdex score:", |
4127 | - $apdex); |
4128 | - |
4129 | - return $apdex; |
4130 | -} |
4131 | - |
4132 | # Sub: _get_value |
4133 | # Get the value of the attribute (or one of its alternatives) from the event. |
4134 | # Undef is a valid value. If the attrib or none of its alternatives exist |
4135 | |
4136 | === modified file 'lib/ExplainAnalyzer.pm' |
4137 | --- lib/ExplainAnalyzer.pm 2013-01-03 00:19:16 +0000 |
4138 | +++ lib/ExplainAnalyzer.pm 2013-01-30 20:58:23 +0000 |
4139 | @@ -215,7 +215,7 @@ |
4140 | # explain - Hashref of normalized EXPLAIN data |
4141 | # |
4142 | # Returns: |
4143 | -# Fingerprint/sparkline string |
4144 | +# Fingerprint string |
4145 | sub fingerprint { |
4146 | my ( $self, %args ) = @_; |
4147 | my @required_args = qw(explain); |
4148 | @@ -225,92 +225,6 @@ |
4149 | my ($explain) = @args{@required_args}; |
4150 | } |
4151 | |
4152 | -# Sub: sparkline |
4153 | -# Create a sparkline of EXPLAIN data from <normalize()>. A spark line |
4154 | -# is a very compact, terse fingerprint that represents just the following. |
4155 | -# See <issue 1141 at http://code.google.com/p/maatkit/issues/detail?id=1141>. |
4156 | -# |
4157 | -# access (for each table): |
4158 | -# - a: ALL |
4159 | -# - c: const |
4160 | -# - e: eq_ref |
4161 | -# - f: fulltext |
4162 | -# - i: index |
4163 | -# - m: index_merge |
4164 | -# - n: range |
4165 | -# - o: ref_or_null |
4166 | -# - r: ref |
4167 | -# - s: system |
4168 | -# - u: unique_subquery |
4169 | -# |
4170 | -# Extra: |
4171 | -# - uppsercaes access code: Using extra |
4172 | -# - T: Using temprary |
4173 | -# - F: Using filesort |
4174 | -# |
4175 | -# Parameters: |
4176 | -# %args - Arguments |
4177 | -# |
4178 | -# Required Arguments: |
4179 | -# explain - Hashref of normalized EXPLAIN data |
4180 | -# |
4181 | -# Returns: |
4182 | -# Sparkline string like (start code)TF>Ree(end code) |
4183 | -sub sparkline { |
4184 | - my ( $self, %args ) = @_; |
4185 | - my @required_args = qw(explain); |
4186 | - foreach my $arg ( @required_args ) { |
4187 | - die "I need a $arg argument" unless defined $args{$arg}; |
4188 | - } |
4189 | - my ($explain) = @args{@required_args}; |
4190 | - PTDEBUG && _d("Making sparkline for", Dumper($explain)); |
4191 | - |
4192 | - my $access_code = { |
4193 | - 'ALL' => 'a', |
4194 | - 'const' => 'c', |
4195 | - 'eq_ref' => 'e', |
4196 | - 'fulltext' => 'f', |
4197 | - 'index' => 'i', |
4198 | - 'index_merge' => 'm', |
4199 | - 'range' => 'n', |
4200 | - 'ref_or_null' => 'o', |
4201 | - 'ref' => 'r', |
4202 | - 'system' => 's', |
4203 | - 'unique_subquery' => 'u', |
4204 | - }; |
4205 | - |
4206 | - my $sparkline = ''; |
4207 | - my ($T, $F); # Using temporary, Using filesort |
4208 | - |
4209 | - foreach my $tbl ( @$explain ) { |
4210 | - my $code; |
4211 | - if ( defined $tbl->{type} ) { |
4212 | - $code = $access_code->{$tbl->{type}} || "?"; |
4213 | - $code = uc $code if $tbl->{Extra}->{'Using index'}; |
4214 | - } |
4215 | - else { |
4216 | - $code = '-' |
4217 | - }; |
4218 | - $sparkline .= $code; |
4219 | - |
4220 | - $T = 1 if $tbl->{Extra}->{'Using temporary'}; |
4221 | - $F = 1 if $tbl->{Extra}->{'Using filesort'}; |
4222 | - } |
4223 | - |
4224 | - if ( $T || $F ) { |
4225 | - if ( $explain->[-1]->{Extra}->{'Using temporary'} |
4226 | - || $explain->[-1]->{Extra}->{'Using filesort'} ) { |
4227 | - $sparkline .= ">" . ($T ? "T" : "") . ($F ? "F" : ""); |
4228 | - } |
4229 | - else { |
4230 | - $sparkline = ($T ? "T" : "") . ($F ? "F" : "") . ">$sparkline"; |
4231 | - } |
4232 | - } |
4233 | - |
4234 | - PTDEBUG && _d("sparkline:", $sparkline); |
4235 | - return $sparkline; |
4236 | -} |
4237 | - |
4238 | sub _d { |
4239 | my ($package, undef, $line) = caller 0; |
4240 | @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; } |
4241 | |
4242 | === added file 'lib/JSONReportFormatter.pm' |
4243 | --- lib/JSONReportFormatter.pm 1970-01-01 00:00:00 +0000 |
4244 | +++ lib/JSONReportFormatter.pm 2013-01-30 20:58:23 +0000 |
4245 | @@ -0,0 +1,124 @@ |
4246 | +{ |
4247 | +package JSONReportFormatter; |
4248 | +use Mo; |
4249 | + |
4250 | +use List::Util qw(sum); |
4251 | +use Transformers qw(make_checksum parse_timestamp); |
4252 | + |
4253 | +use constant PTDEBUG => $ENV{PTDEBUG} || 0; |
4254 | + |
4255 | +my $have_json = eval { require JSON }; |
4256 | + |
4257 | +our $pretty_json = undef; |
4258 | +our $sorted_json = undef; |
4259 | + |
4260 | +extends qw(QueryReportFormatter); |
4261 | + |
4262 | +has _json => ( |
4263 | + is => 'ro', |
4264 | + init_arg => undef, |
4265 | + builder => '_build_json', |
4266 | +); |
4267 | + |
4268 | +sub _build_json { |
4269 | + return unless $have_json; |
4270 | + return JSON->new->utf8 |
4271 | + ->pretty($pretty_json) |
4272 | + ->canonical($sorted_json); |
4273 | +} |
4274 | + |
4275 | +sub encode_json { |
4276 | + my ($self, $encode) = @_; |
4277 | + if ( my $json = $self->_json ) { |
4278 | + return $json->encode($encode); |
4279 | + } |
4280 | + else { |
4281 | + return Transformers::encode_json($encode); |
4282 | + } |
4283 | +} |
4284 | + |
4285 | +override [qw(rusage date hostname files header profile prepared)] => sub { |
4286 | + return; |
4287 | +}; |
4288 | + |
4289 | +override event_report => sub { |
4290 | + my ($self, %args) = @_; |
4291 | + return $self->event_report_values(%args); |
4292 | +}; |
4293 | + |
4294 | +override query_report => sub { |
4295 | + my ($self, %args) = @_; |
4296 | + foreach my $arg ( qw(ea worst orderby groupby) ) { |
4297 | + die "I need a $arg argument" unless defined $arg; |
4298 | + } |
4299 | + |
4300 | + my $ea = $args{ea}; |
4301 | + my $worst = $args{worst}; |
4302 | + |
4303 | + my @attribs = @{$ea->get_attributes()}; |
4304 | + |
4305 | + my @queries; |
4306 | + foreach my $worst_info ( @$worst ) { |
4307 | + my $item = $worst_info->[0]; |
4308 | + my $stats = $ea->results->{classes}->{$item}; |
4309 | + my $sample = $ea->results->{samples}->{$item}; |
4310 | + |
4311 | + my $all_log_pos = $ea->{result_classes}->{$item}->{pos_in_log}->{all}; |
4312 | + my $times_seen = sum values %$all_log_pos; |
4313 | + |
4314 | + my %class = ( |
4315 | + sample => $sample->{arg}, |
4316 | + fingerprint => $item, |
4317 | + checksum => make_checksum($item), |
4318 | + cnt => $times_seen, |
4319 | + ); |
4320 | + |
4321 | + my %metrics; |
4322 | + foreach my $attrib ( @attribs ) { |
4323 | + $metrics{$attrib} = $ea->metrics( |
4324 | + attrib => $attrib, |
4325 | + where => $item, |
4326 | + ); |
4327 | + } |
4328 | + |
4329 | + foreach my $attrib ( keys %metrics ) { |
4330 | + if ( ! grep { $_ } values %{$metrics{$attrib}} ) { |
4331 | + delete $metrics{$attrib}; |
4332 | + next; |
4333 | + } |
4334 | + |
4335 | + if ($attrib eq 'ts') { |
4336 | + my $ts = delete $metrics{ts}; |
4337 | + foreach my $thing ( qw(min max) ) { |
4338 | + next unless defined $ts && defined $ts->{$thing}; |
4339 | + $ts->{$thing} = parse_timestamp($ts->{$thing}); |
4340 | + } |
4341 | + $class{ts_min} = $ts->{min}; |
4342 | + $class{ts_max} = $ts->{max}; |
4343 | + } |
4344 | + elsif ( ($ea->{type_for}->{$attrib} || '') eq 'num' ) { |
4345 | + # Avoid scientific notation in the metrics by forcing it to use |
4346 | + # six decimal places. |
4347 | + for my $value ( values %{$metrics{$attrib}} ) { |
4348 | + next unless $value; |
4349 | + $value = sprintf '%.6f', $value; |
4350 | + } |
4351 | + # ..except for the percentage, which only needs two |
4352 | + if ( my $pct = $metrics{$attrib}->{pct} ) { |
4353 | + $metrics{$attrib}->{pct} = sprintf('%.2f', $pct); |
4354 | + } |
4355 | + } |
4356 | + } |
4357 | + push @queries, { |
4358 | + class => \%class, |
4359 | + attributes => \%metrics, |
4360 | + }; |
4361 | + } |
4362 | + |
4363 | + my $json = $self->encode_json(\@queries); |
4364 | + $json .= "\n" if $json !~ /\n\Z/; |
4365 | + return $json . "\n"; |
4366 | +}; |
4367 | + |
4368 | +1; |
4369 | +} |
4370 | |
4371 | === modified file 'lib/Mo.pm' |
4372 | --- lib/Mo.pm 2013-01-03 00:19:16 +0000 |
4373 | +++ lib/Mo.pm 2013-01-30 20:58:23 +0000 |
4374 | @@ -177,6 +177,7 @@ |
4375 | _set_package_isa($caller, @_); |
4376 | _set_inherited_metadata($caller); |
4377 | }, |
4378 | + override => \&override, |
4379 | has => sub { |
4380 | my $names = shift; |
4381 | for my $attribute ( ref $names ? @$names : $names ) { |
4382 | @@ -512,6 +513,16 @@ |
4383 | } |
4384 | } |
4385 | |
4386 | +sub override { |
4387 | + my ($methods, $code) = @_; |
4388 | + my $caller = scalar caller; |
4389 | + |
4390 | + for my $method ( ref($methods) ? @$methods : $methods ) { |
4391 | + my $full_method = "${caller}::${method}"; |
4392 | + *{_glob_for $full_method} = $code; |
4393 | + } |
4394 | +} |
4395 | + |
4396 | } |
4397 | 1; |
4398 | # ########################################################################### |
4399 | |
4400 | === modified file 'lib/Pipeline.pm' |
4401 | --- lib/Pipeline.pm 2013-01-03 00:19:16 +0000 |
4402 | +++ lib/Pipeline.pm 2013-01-30 20:58:23 +0000 |
4403 | @@ -42,7 +42,7 @@ |
4404 | |
4405 | my $self = { |
4406 | # default values for optional args |
4407 | - instrument => 0, |
4408 | + instrument => PTDEBUG, |
4409 | continue_on_error => 0, |
4410 | |
4411 | # specified arg values override defaults |
4412 | @@ -71,9 +71,7 @@ |
4413 | |
4414 | push @{$self->{procs}}, $process; |
4415 | push @{$self->{names}}, $name; |
4416 | - if ( my $n = $args{retry_on_error} ) { |
4417 | - $self->{retries}->{$name} = $n; |
4418 | - } |
4419 | + $self->{retries}->{$name} = $args{retry_on_error} || 100; |
4420 | if ( $self->{instrument} ) { |
4421 | $self->{instrumentation}->{$name} = { time => 0, calls => 0 }; |
4422 | } |
4423 | @@ -163,7 +161,11 @@ |
4424 | my $msg = "Pipeline process " . ($procno + 1) |
4425 | . " ($name) caused an error: " |
4426 | . $EVAL_ERROR; |
4427 | - if ( defined $self->{retries}->{$name} ) { |
4428 | + if ( !$self->{continue_on_error} ) { |
4429 | + die $msg . "Terminating pipeline because --continue-on-error " |
4430 | + . "is false.\n"; |
4431 | + } |
4432 | + elsif ( defined $self->{retries}->{$name} ) { |
4433 | my $n = $self->{retries}->{$name}; |
4434 | if ( $n ) { |
4435 | warn $msg . "Will retry pipeline process $procno ($name) " |
4436 | @@ -175,9 +177,6 @@ |
4437 | . "($name) caused too many errors.\n"; |
4438 | } |
4439 | } |
4440 | - elsif ( !$self->{continue_on_error} ) { |
4441 | - die $msg; |
4442 | - } |
4443 | else { |
4444 | warn $msg; |
4445 | } |
4446 | |
4447 | === modified file 'lib/QueryReportFormatter.pm' |
4448 | --- lib/QueryReportFormatter.pm 2013-01-03 00:19:16 +0000 |
4449 | +++ lib/QueryReportFormatter.pm 2013-01-30 20:58:23 +0000 |
4450 | @@ -29,8 +29,7 @@ |
4451 | # which is also in mk-query-digest. |
4452 | package QueryReportFormatter; |
4453 | |
4454 | -use strict; |
4455 | -use warnings FATAL => 'all'; |
4456 | +use Mo; |
4457 | use English qw(-no_match_vars); |
4458 | use POSIX qw(floor); |
4459 | |
4460 | @@ -43,6 +42,9 @@ |
4461 | use constant LINE_LENGTH => 74; |
4462 | use constant MAX_STRING_LENGTH => 10; |
4463 | |
4464 | +{ local $EVAL_ERROR; eval { require Quoter } }; |
4465 | +{ local $EVAL_ERROR; eval { require ReportFormatter } }; |
4466 | + |
4467 | # Sub: new |
4468 | # |
4469 | # Parameters: |
4470 | @@ -56,31 +58,69 @@ |
4471 | # Optional arguments: |
4472 | # QueryReview - <QueryReview> object used in <query_report()> |
4473 | # dbh - dbh used in <explain_report()> |
4474 | -# ExplainAnalyzer - <ExplainAnalyzer> object used in <explain_report()>. |
4475 | -# This causes a sparkline to be printed (issue 1141). |
4476 | # |
4477 | # Returns: |
4478 | # QueryReportFormatter object |
4479 | -sub new { |
4480 | - my ( $class, %args ) = @_; |
4481 | - foreach my $arg ( qw(OptionParser QueryRewriter Quoter) ) { |
4482 | - die "I need a $arg argument" unless $args{$arg}; |
4483 | +has Quoter => ( |
4484 | + is => 'ro', |
4485 | + isa => 'Quoter', |
4486 | + default => sub { Quoter->new() }, |
4487 | +); |
4488 | + |
4489 | +has label_width => ( |
4490 | + is => 'ro', |
4491 | + isa => 'Int', |
4492 | +); |
4493 | + |
4494 | +has global_headers => ( |
4495 | + is => 'ro', |
4496 | + isa => 'ArrayRef', |
4497 | + default => sub { [qw( total min max avg 95% stddev median)] }, |
4498 | +); |
4499 | + |
4500 | +has event_headers => ( |
4501 | + is => 'ro', |
4502 | + isa => 'ArrayRef', |
4503 | + default => sub { [qw(pct total min max avg 95% stddev median)] }, |
4504 | +); |
4505 | + |
4506 | +has ReportFormatter => ( |
4507 | + is => 'ro', |
4508 | + isa => 'ReportFormatter', |
4509 | + builder => '_build_report_formatter', |
4510 | +); |
4511 | + |
4512 | +sub _build_report_formatter { |
4513 | + return ReportFormatter->new( |
4514 | + line_width => LINE_LENGTH, |
4515 | + extend_right => 1, |
4516 | + ); |
4517 | +} |
4518 | + |
4519 | +sub BUILDARGS { |
4520 | + my $class = shift; |
4521 | + my $args = $class->SUPER::BUILDARGS(@_); |
4522 | + |
4523 | + foreach my $arg ( qw(OptionParser QueryRewriter) ) { |
4524 | + die "I need a $arg argument" unless $args->{$arg}; |
4525 | } |
4526 | |
4527 | # If ever someone wishes for a wider label width. |
4528 | - my $label_width = $args{label_width} || 12; |
4529 | + my $label_width = $args->{label_width} ||= 12; |
4530 | PTDEBUG && _d('Label width:', $label_width); |
4531 | |
4532 | - my $cheat_width = $label_width + 1; |
4533 | - |
4534 | + my $o = delete $args->{OptionParser}; |
4535 | my $self = { |
4536 | - %args, |
4537 | - label_width => $label_width, |
4538 | + %$args, |
4539 | + options => { |
4540 | + show_all => $o->get('show-all'), |
4541 | + shorten => $o->get('shorten'), |
4542 | + report_all => $o->get('report-all'), |
4543 | + report_histogram => $o->get('report-histogram'), |
4544 | + }, |
4545 | num_format => "# %-${label_width}s %3s %7s %7s %7s %7s %7s %7s %7s", |
4546 | bool_format => "# %-${label_width}s %3d%% yes, %3d%% no", |
4547 | string_format => "# %-${label_width}s %s", |
4548 | - global_headers => [qw( total min max avg 95% stddev median)], |
4549 | - event_headers => [qw(pct total min max avg 95% stddev median)], |
4550 | hidden_attrib => { # Don't sort/print these attribs in the reports. |
4551 | arg => 1, # They're usually handled specially, or not |
4552 | fingerprint => 1, # printed at all. |
4553 | @@ -88,32 +128,7 @@ |
4554 | ts => 1, |
4555 | }, |
4556 | }; |
4557 | - return bless $self, $class; |
4558 | -} |
4559 | - |
4560 | -# Sub: set_report_formatter |
4561 | -# Set a report formatter object for a report. By default this package will |
4562 | -# instantiate ReportFormatter objects to format columnized reports (e.g. |
4563 | -# for profile and prepared reports). Setting a caller-created formatter |
4564 | -# object (usually a <ReportFormatter> obj) is used for tested and also by |
4565 | -# <mk-query-digest> to extend the profile report line width to 82 for |
4566 | -# the --explain sparkline. |
4567 | -# |
4568 | -# Parameters: |
4569 | -# %args - Arguments |
4570 | -# |
4571 | -# Required Arguments: |
4572 | -# report - Report name, e.g. profile, prepared, etc. |
4573 | -# formatter - Formatter object, usually a <ReportFormatter> obj |
4574 | -sub set_report_formatter { |
4575 | - my ( $self, %args ) = @_; |
4576 | - my @required_args = qw(report formatter); |
4577 | - foreach my $arg ( @required_args ) { |
4578 | - die "I need a $arg argument" unless exists $args{$arg}; |
4579 | - } |
4580 | - my ($report, $formatter) = @args{@required_args}; |
4581 | - $self->{formatter_for}->{$report} = $formatter; |
4582 | - return; |
4583 | + return $self; |
4584 | } |
4585 | |
4586 | # Arguments: |
4587 | @@ -243,7 +258,7 @@ |
4588 | shorten(scalar keys %{$results->{classes}}, d=>1_000), |
4589 | shorten($qps || 0, d=>1_000), |
4590 | shorten($conc || 0, d=>1_000)); |
4591 | - $line .= ('_' x (LINE_LENGTH - length($line) + $self->{label_width} - 12)); |
4592 | + $line .= ('_' x (LINE_LENGTH - length($line) + $self->label_width() - 12)); |
4593 | push @result, $line; |
4594 | |
4595 | # Second line: time range |
4596 | @@ -308,6 +323,70 @@ |
4597 | return join("\n", map { s/\s+$//; $_ } @result) . "\n"; |
4598 | } |
4599 | |
4600 | +sub query_report_values { |
4601 | + my ($self, %args) = @_; |
4602 | + foreach my $arg ( qw(ea worst orderby groupby) ) { |
4603 | + die "I need a $arg argument" unless defined $arg; |
4604 | + } |
4605 | + my $ea = $args{ea}; |
4606 | + my $groupby = $args{groupby}; |
4607 | + my $worst = $args{worst}; |
4608 | + |
4609 | + my $q = $self->Quoter; |
4610 | + my $qv = $self->{QueryReview}; |
4611 | + my $qr = $self->{QueryRewriter}; |
4612 | + |
4613 | + my @values; |
4614 | + # Print each worst item: its stats/metrics (sum/min/max/95%/etc.), |
4615 | + # Query_time distro chart, tables, EXPLAIN, fingerprint, etc. |
4616 | + # Items are usually unique queries/fingerprints--depends on how |
4617 | + # the events were grouped. |
4618 | + ITEM: |
4619 | + foreach my $top_event ( @$worst ) { |
4620 | + my $item = $top_event->[0]; |
4621 | + my $reason = $args{explain_why} ? $top_event->[1] : ''; |
4622 | + my $rank = $top_event->[2]; |
4623 | + my $stats = $ea->results->{classes}->{$item}; |
4624 | + my $sample = $ea->results->{samples}->{$item}; |
4625 | + my $samp_query = $sample->{arg} || ''; |
4626 | + |
4627 | + my %item_vals = ( |
4628 | + item => $item, |
4629 | + samp_query => $samp_query, |
4630 | + rank => ($rank || 0), |
4631 | + reason => $reason, |
4632 | + ); |
4633 | + |
4634 | + # ############################################################### |
4635 | + # Possibly skip item for --review. |
4636 | + # ############################################################### |
4637 | + my $review_vals; |
4638 | + if ( $qv ) { |
4639 | + $review_vals = $qv->get_review_info($item); |
4640 | + next ITEM if $review_vals->{reviewed_by} && !$self->{options}->{report_all}; |
4641 | + for my $col ( $qv->review_cols() ) { |
4642 | + push @{$item_vals{review_vals}}, [$col, $review_vals->{$col}]; |
4643 | + } |
4644 | + } |
4645 | + |
4646 | + $item_vals{default_db} = $sample->{db} ? $sample->{db} |
4647 | + : $stats->{db}->{unq} ? keys %{$stats->{db}->{unq}} |
4648 | + : undef; |
4649 | + $item_vals{tables} = [$self->{QueryParser}->extract_tables( |
4650 | + query => $samp_query, |
4651 | + default_db => $item_vals{default_db}, |
4652 | + Quoter => $self->Quoter, |
4653 | + )]; |
4654 | + |
4655 | + if ( $samp_query && ($args{variations} && @{$args{variations}}) ) { |
4656 | + $item_vals{crc} = crc32($samp_query); |
4657 | + } |
4658 | + |
4659 | + push @values, \%item_vals; |
4660 | + } |
4661 | + return \@values; |
4662 | +} |
4663 | + |
4664 | # Arguments: |
4665 | # * ea obj: EventAggregator |
4666 | # * worst arrayref: worst items |
4667 | @@ -319,16 +398,11 @@ |
4668 | # * print_header bool: "Report grouped by" header |
4669 | sub query_report { |
4670 | my ( $self, %args ) = @_; |
4671 | - foreach my $arg ( qw(ea worst orderby groupby) ) { |
4672 | - die "I need a $arg argument" unless defined $arg; |
4673 | - } |
4674 | + |
4675 | my $ea = $args{ea}; |
4676 | my $groupby = $args{groupby}; |
4677 | - my $worst = $args{worst}; |
4678 | + my $report_values = $self->query_report_values(%args); |
4679 | |
4680 | - my $o = $self->{OptionParser}; |
4681 | - my $q = $self->{Quoter}; |
4682 | - my $qv = $self->{QueryReview}; |
4683 | my $qr = $self->{QueryRewriter}; |
4684 | |
4685 | my $report = ''; |
4686 | @@ -350,66 +424,36 @@ |
4687 | # Items are usually unique queries/fingerprints--depends on how |
4688 | # the events were grouped. |
4689 | ITEM: |
4690 | - foreach my $top_event ( @$worst ) { |
4691 | - my $item = $top_event->[0]; |
4692 | - my $reason = $args{explain_why} ? $top_event->[1] : ''; |
4693 | - my $rank = $top_event->[2]; |
4694 | - my $stats = $ea->results->{classes}->{$item}; |
4695 | - my $sample = $ea->results->{samples}->{$item}; |
4696 | - my $samp_query = $sample->{arg} || ''; |
4697 | - |
4698 | - # ############################################################### |
4699 | - # Possibly skip item for --review. |
4700 | - # ############################################################### |
4701 | - my $review_vals; |
4702 | - if ( $qv ) { |
4703 | - $review_vals = $qv->get_review_info($item); |
4704 | - next ITEM if $review_vals->{reviewed_by} && !$o->get('report-all'); |
4705 | - } |
4706 | - |
4707 | - # ############################################################### |
4708 | - # Get tables for --for-explain. |
4709 | - # ############################################################### |
4710 | - my ($default_db) = $sample->{db} ? $sample->{db} |
4711 | - : $stats->{db}->{unq} ? keys %{$stats->{db}->{unq}} |
4712 | - : undef; |
4713 | - my @tables; |
4714 | - if ( $o->get('for-explain') ) { |
4715 | - @tables = $self->{QueryParser}->extract_tables( |
4716 | - query => $samp_query, |
4717 | - default_db => $default_db, |
4718 | - Quoter => $self->{Quoter}, |
4719 | - ); |
4720 | - } |
4721 | - |
4722 | + foreach my $vals ( @$report_values ) { |
4723 | + my $item = $vals->{item}; |
4724 | # ############################################################### |
4725 | # Print the standard query analysis report. |
4726 | # ############################################################### |
4727 | - $report .= "\n" if $rank > 1; # space between each event report |
4728 | + $report .= "\n" if $vals->{rank} > 1; # space between each event report |
4729 | $report .= $self->event_report( |
4730 | %args, |
4731 | item => $item, |
4732 | - sample => $sample, |
4733 | - rank => $rank, |
4734 | - reason => $reason, |
4735 | + sample => $ea->results->{samples}->{$item}, |
4736 | + rank => $vals->{rank}, |
4737 | + reason => $vals->{reason}, |
4738 | attribs => $attribs, |
4739 | - db => $default_db, |
4740 | + db => $vals->{default_db}, |
4741 | ); |
4742 | |
4743 | - if ( $o->get('report-histogram') ) { |
4744 | + if ( $self->{options}->{report_histogram} ) { |
4745 | $report .= $self->chart_distro( |
4746 | %args, |
4747 | - attrib => $o->get('report-histogram'), |
4748 | - item => $item, |
4749 | + attrib => $self->{options}->{report_histogram}, |
4750 | + item => $vals->{item}, |
4751 | ); |
4752 | } |
4753 | |
4754 | - if ( $qv && $review_vals ) { |
4755 | + if ( $vals->{review_vals} ) { |
4756 | # Print the review information that is already in the table |
4757 | # before putting anything new into the table. |
4758 | $report .= "# Review information\n"; |
4759 | - foreach my $col ( $qv->review_cols() ) { |
4760 | - my $val = $review_vals->{$col}; |
4761 | + foreach my $elem ( @{$vals->{review_vals}} ) { |
4762 | + my ($col, $val) = @$elem; |
4763 | if ( !$val || $val ne '0000-00-00 00:00:00' ) { # issue 202 |
4764 | $report .= sprintf "# %13s: %-s\n", $col, ($val ? $val : ''); |
4765 | } |
4766 | @@ -418,25 +462,22 @@ |
4767 | |
4768 | if ( $groupby eq 'fingerprint' ) { |
4769 | # Shorten it if necessary (issue 216 and 292). |
4770 | - $samp_query = $qr->shorten($samp_query, $o->get('shorten')) |
4771 | - if $o->get('shorten'); |
4772 | + my $samp_query = $qr->shorten($vals->{samp_query}, $self->{options}->{shorten}) |
4773 | + if $self->{options}->{shorten}; |
4774 | |
4775 | # Print query fingerprint. |
4776 | - $report .= "# Fingerprint\n# $item\n" |
4777 | - if $o->get('fingerprints'); |
4778 | + PTDEBUG && _d("Fingerprint\n# $vals->{item}\n"); |
4779 | |
4780 | # Print tables used by query. |
4781 | - $report .= $self->tables_report(@tables) |
4782 | - if $o->get('for-explain'); |
4783 | + $report .= $self->tables_report(@{$vals->{tables}}); |
4784 | |
4785 | # Print sample (worst) query's CRC % 1_000. We mod 1_000 because |
4786 | # that's actually the value stored in the ea, not the full checksum. |
4787 | # So the report will print something like, |
4788 | # # arg crc 685 (2/66%), 159 (1/33%) |
4789 | # Thus we want our "CRC" line to be 685 and not 18547302820. |
4790 | - if ( $samp_query && ($args{variations} && @{$args{variations}}) ) { |
4791 | - my $crc = crc32($samp_query); |
4792 | - $report.= "# CRC " . ($crc ? $crc % 1_000 : "") . "\n"; |
4793 | + if ( $vals->{crc} ) { |
4794 | + $report.= "# CRC " . ($vals->{crc} % 1_000) . "\n"; |
4795 | } |
4796 | |
4797 | my $log_type = $args{log_type} || ''; |
4798 | @@ -450,14 +491,13 @@ |
4799 | } |
4800 | else { |
4801 | $report .= "# EXPLAIN /*!50100 PARTITIONS*/\n$samp_query${mark}\n"; |
4802 | - $report .= $self->explain_report($samp_query, $default_db); |
4803 | + $report .= $self->explain_report($samp_query, $vals->{default_db}); |
4804 | } |
4805 | } |
4806 | else { |
4807 | $report .= "$samp_query${mark}\n"; |
4808 | my $converted = $qr->convert_to_select($samp_query); |
4809 | - if ( $o->get('for-explain') |
4810 | - && $converted |
4811 | + if ( $converted |
4812 | && $converted =~ m/^[\(\s]*select/i ) { |
4813 | # It converted OK to a SELECT |
4814 | $report .= "# Converted for EXPLAIN\n# EXPLAIN /*!50100 PARTITIONS*/\n$converted${mark}\n"; |
4815 | @@ -466,7 +506,7 @@ |
4816 | } |
4817 | else { |
4818 | if ( $groupby eq 'tables' ) { |
4819 | - my ( $db, $tbl ) = $q->split_unquote($item); |
4820 | + my ( $db, $tbl ) = $self->Quoter->split_unquote($item); |
4821 | $report .= $self->tables_report([$db, $tbl]); |
4822 | } |
4823 | $report .= "$item\n"; |
4824 | @@ -486,21 +526,20 @@ |
4825 | # * rank scalar: item rank among the worst |
4826 | # Print a report about the statistics in the EventAggregator. |
4827 | # Called by query_report(). |
4828 | -sub event_report { |
4829 | - my ( $self, %args ) = @_; |
4830 | - foreach my $arg ( qw(ea item orderby) ) { |
4831 | - die "I need a $arg argument" unless defined $args{$arg}; |
4832 | - } |
4833 | - my $ea = $args{ea}; |
4834 | - my $item = $args{item}; |
4835 | +sub event_report_values { |
4836 | + my ($self, %args) = @_; |
4837 | + |
4838 | + my $ea = $args{ea}; |
4839 | + my $item = $args{item}; |
4840 | my $orderby = $args{orderby}; |
4841 | my $results = $ea->results(); |
4842 | - my $o = $self->{OptionParser}; |
4843 | - my @result; |
4844 | + |
4845 | + my %vals; |
4846 | |
4847 | # Return unless the item exists in the results (it should). |
4848 | my $store = $results->{classes}->{$item}; |
4849 | - return "# No such event $item\n" unless $store; |
4850 | + |
4851 | + return unless $store; |
4852 | |
4853 | # Pick the first attribute to get counts |
4854 | my $global_cnt = $results->{globals}->{$orderby}->{cnt}; |
4855 | @@ -521,79 +560,25 @@ |
4856 | }; |
4857 | } |
4858 | |
4859 | - # First line like: |
4860 | - # Query 1: 9 QPS, 0x concurrency, ID 0x7F7D57ACDD8A346E at byte 5 ________ |
4861 | - my $line = sprintf( |
4862 | - '# %s %d: %s QPS, %sx concurrency, ID 0x%s at byte %.f ', |
4863 | - ($ea->{groupby} eq 'fingerprint' ? 'Query' : 'Item'), |
4864 | - $args{rank} || 0, |
4865 | - shorten($qps || 0, d=>1_000), |
4866 | - shorten($conc || 0, d=>1_000), |
4867 | - make_checksum($item), |
4868 | - $results->{samples}->{$item}->{pos_in_log} || 0, |
4869 | - ); |
4870 | - $line .= ('_' x (LINE_LENGTH - length($line) + $self->{label_width} - 12)); |
4871 | - push @result, $line; |
4872 | - |
4873 | - # Second line: reason why this class is being reported. |
4874 | - if ( $args{reason} ) { |
4875 | - push @result, |
4876 | - "# This item is included in the report because it matches " |
4877 | - . ($args{reason} eq 'top' ? '--limit.' : '--outliers.'); |
4878 | - } |
4879 | - |
4880 | - # Third line: Apdex and variance-to-mean (V/M) ratio, like: |
4881 | - # Scores: Apdex = 0.93 [1.0], V/M = 1.5 |
4882 | - { |
4883 | + $vals{groupby} = $ea->{groupby}; |
4884 | + $vals{qps} = $qps || 0; |
4885 | + $vals{concurrency} = $conc || 0; |
4886 | + $vals{checksum} = make_checksum($item); |
4887 | + $vals{pos_in_log} = $results->{samples}->{$item}->{pos_in_log} || 0; |
4888 | + $vals{reason} = $args{reason}; |
4889 | + $vals{variance_to_mean} = do { |
4890 | my $query_time = $ea->metrics(where => $item, attrib => 'Query_time'); |
4891 | - push @result, |
4892 | - sprintf("# Scores: Apdex = %s [%3.1f]%s, V/M = %.2f", |
4893 | - (defined $query_time->{apdex} ? "$query_time->{apdex}" : "NS"), |
4894 | - ($query_time->{apdex_t} || 0), |
4895 | - ($query_time->{cnt} < 100 ? "*" : ""), |
4896 | - ($query_time->{stddev}**2 / ($query_time->{avg} || 1)), |
4897 | - ); |
4898 | - } |
4899 | - |
4900 | - # Fourth line: EXPLAIN sparkline if --explain. |
4901 | - if ( $o->get('explain') && $results->{samples}->{$item}->{arg} ) { |
4902 | - eval { |
4903 | - my $sparkline = $self->explain_sparkline( |
4904 | - $results->{samples}->{$item}->{arg}, $args{db}); |
4905 | - push @result, "# EXPLAIN sparkline: $sparkline\n"; |
4906 | - }; |
4907 | - if ( $EVAL_ERROR ) { |
4908 | - PTDEBUG && _d("Failed to get EXPLAIN sparkline:", $EVAL_ERROR); |
4909 | - } |
4910 | - } |
4911 | - |
4912 | - if ( my $attrib = $o->get('report-histogram') ) { |
4913 | - my $sparkline = $self->distro_sparkline( |
4914 | - %args, |
4915 | - attrib => $attrib, |
4916 | - item => $item, |
4917 | - ); |
4918 | - if ( $sparkline ) { |
4919 | - # I find the | | bookends help make the sparkchart graph more clear. |
4920 | - # Else with just .^- it's difficult to tell where the chart beings |
4921 | - # or ends. |
4922 | - push @result, "# $attrib sparkline: |$sparkline|"; |
4923 | - } |
4924 | - } |
4925 | - |
4926 | - # Last line before column headers: time range |
4927 | - if ( my $ts = $store->{ts} ) { |
4928 | - my $time_range = $self->format_time_range($ts) || "unknown"; |
4929 | - push @result, "# Time range: $time_range"; |
4930 | - } |
4931 | - |
4932 | - # Column header line |
4933 | - push @result, $self->make_event_header(); |
4934 | - |
4935 | - # Count line |
4936 | - push @result, |
4937 | - sprintf $self->{num_format}, 'Count', |
4938 | - percentage_of($class_cnt, $global_cnt), $class_cnt, map { '' } (1..8); |
4939 | + $query_time->{stddev}**2 / ($query_time->{avg} || 1) |
4940 | + }; |
4941 | + |
4942 | + $vals{counts} = { |
4943 | + class_cnt => $class_cnt, |
4944 | + global_cnt => $global_cnt, |
4945 | + }; |
4946 | + |
4947 | + if ( my $ts = $store->{ts}) { |
4948 | + $vals{time_range} = $self->format_time_range($ts) || "unknown"; |
4949 | + } |
4950 | |
4951 | # Sort the attributes, removing any hidden attributes, if they're not |
4952 | # already given to us. In mk-query-digest, this sub is called from |
4953 | @@ -607,11 +592,10 @@ |
4954 | ); |
4955 | } |
4956 | |
4957 | + $vals{attributes} = { map { $_ => [] } qw(num innodb bool string) }; |
4958 | + |
4959 | foreach my $type ( qw(num innodb) ) { |
4960 | # Add "InnoDB:" sub-header before grouped InnoDB_* attributes. |
4961 | - if ( $type eq 'innodb' && @{$attribs->{$type}} ) { |
4962 | - push @result, "# InnoDB:"; |
4963 | - }; |
4964 | |
4965 | NUM_ATTRIB: |
4966 | foreach my $attrib ( @{$attribs->{$type}} ) { |
4967 | @@ -631,15 +615,12 @@ |
4968 | $pct = percentage_of( |
4969 | $vals->{sum}, $results->{globals}->{$attrib}->{sum}); |
4970 | |
4971 | - push @result, |
4972 | - sprintf $self->{num_format}, |
4973 | - $self->make_label($attrib), $pct, @values; |
4974 | + push @{$vals{attributes}{$type}}, |
4975 | + [ $attrib, $pct, @values ]; |
4976 | } |
4977 | } |
4978 | |
4979 | if ( @{$attribs->{bool}} ) { |
4980 | - push @result, "# Boolean:"; |
4981 | - my $printed_bools = 0; |
4982 | BOOL_ATTRIB: |
4983 | foreach my $attrib ( @{$attribs->{bool}} ) { |
4984 | next BOOL_ATTRIB unless exists $store->{$attrib}; |
4985 | @@ -647,33 +628,125 @@ |
4986 | next unless scalar %$vals; |
4987 | |
4988 | if ( $vals->{sum} > 0 ) { |
4989 | - push @result, |
4990 | - sprintf $self->{bool_format}, |
4991 | - $self->make_label($attrib), $self->bool_percents($vals); |
4992 | - $printed_bools = 1; |
4993 | + push @{$vals{attributes}{bool}}, |
4994 | + [ $attrib, $self->bool_percents($vals) ]; |
4995 | } |
4996 | } |
4997 | - pop @result unless $printed_bools; |
4998 | + } |
4999 | + |
5000 | + if ( @{$attribs->{string}} ) { |
The diff has been truncated for viewing.