Merge lp:~percona-toolkit-dev/percona-toolkit/simplify-pqd into lp:percona-toolkit/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
Reviewer Review Type Date Requested Status
Daniel Nichter Pending
Review via email: mp+143538@code.launchpad.net
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/TimeSeriesTrender.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.

Subscribers

People subscribed via source and target branches