mirror of
https://github.com/percona/percona-toolkit.git
synced 2025-09-21 03:14:58 +00:00
Partially Moified QueryReportFormatter, split event_report and query_report into two different subs; one that gets the values to print, and one that formats things
This commit is contained in:
@@ -29,8 +29,7 @@
|
|||||||
# which is also in mk-query-digest.
|
# which is also in mk-query-digest.
|
||||||
package QueryReportFormatter;
|
package QueryReportFormatter;
|
||||||
|
|
||||||
use strict;
|
use Mo;
|
||||||
use warnings FATAL => 'all';
|
|
||||||
use English qw(-no_match_vars);
|
use English qw(-no_match_vars);
|
||||||
use POSIX qw(floor);
|
use POSIX qw(floor);
|
||||||
|
|
||||||
@@ -43,6 +42,9 @@ use constant PTDEBUG => $ENV{PTDEBUG} || 0;
|
|||||||
use constant LINE_LENGTH => 74;
|
use constant LINE_LENGTH => 74;
|
||||||
use constant MAX_STRING_LENGTH => 10;
|
use constant MAX_STRING_LENGTH => 10;
|
||||||
|
|
||||||
|
{ local $EVAL_ERROR; eval { require Quoter } };
|
||||||
|
{ local $EVAL_ERROR; eval { require ReportFormatter } };
|
||||||
|
|
||||||
# Sub: new
|
# Sub: new
|
||||||
#
|
#
|
||||||
# Parameters:
|
# Parameters:
|
||||||
@@ -56,30 +58,69 @@ use constant MAX_STRING_LENGTH => 10;
|
|||||||
# Optional arguments:
|
# Optional arguments:
|
||||||
# QueryReview - <QueryReview> object used in <query_report()>
|
# QueryReview - <QueryReview> object used in <query_report()>
|
||||||
# dbh - dbh used in <explain_report()>
|
# dbh - dbh used in <explain_report()>
|
||||||
# ExplainAnalyzer - <ExplainAnalyzer> object used in <explain_report()>.
|
|
||||||
#
|
#
|
||||||
# Returns:
|
# Returns:
|
||||||
# QueryReportFormatter object
|
# QueryReportFormatter object
|
||||||
sub new {
|
has Quoter => (
|
||||||
my ( $class, %args ) = @_;
|
is => 'ro',
|
||||||
foreach my $arg ( qw(OptionParser QueryRewriter Quoter) ) {
|
isa => 'Quoter',
|
||||||
die "I need a $arg argument" unless $args{$arg};
|
default => sub { Quoter->new() },
|
||||||
|
);
|
||||||
|
|
||||||
|
has label_width => (
|
||||||
|
is => 'ro',
|
||||||
|
isa => 'Int',
|
||||||
|
);
|
||||||
|
|
||||||
|
has global_headers => (
|
||||||
|
is => 'ro',
|
||||||
|
isa => 'ArrayRef',
|
||||||
|
default => sub { [qw( total min max avg 95% stddev median)] },
|
||||||
|
);
|
||||||
|
|
||||||
|
has event_headers => (
|
||||||
|
is => 'ro',
|
||||||
|
isa => 'ArrayRef',
|
||||||
|
default => sub { [qw(pct total min max avg 95% stddev median)] },
|
||||||
|
);
|
||||||
|
|
||||||
|
has ReportFormatter => (
|
||||||
|
is => 'ro',
|
||||||
|
isa => 'ReportFormatter',
|
||||||
|
builder => '_build_report_formatter',
|
||||||
|
);
|
||||||
|
|
||||||
|
sub _build_report_formatter {
|
||||||
|
return ReportFormatter->new(
|
||||||
|
line_width => LINE_LENGTH,
|
||||||
|
extend_right => 1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub BUILDARGS {
|
||||||
|
my $class = shift;
|
||||||
|
my $args = $class->SUPER::BUILDARGS(@_);
|
||||||
|
|
||||||
|
foreach my $arg ( qw(OptionParser QueryRewriter) ) {
|
||||||
|
die "I need a $arg argument" unless $args->{$arg};
|
||||||
}
|
}
|
||||||
|
|
||||||
# If ever someone wishes for a wider label width.
|
# If ever someone wishes for a wider label width.
|
||||||
my $label_width = $args{label_width} || 12;
|
my $label_width = $args->{label_width} ||= 12;
|
||||||
PTDEBUG && _d('Label width:', $label_width);
|
PTDEBUG && _d('Label width:', $label_width);
|
||||||
|
|
||||||
my $cheat_width = $label_width + 1;
|
my $o = delete $args->{OptionParser};
|
||||||
|
|
||||||
my $self = {
|
my $self = {
|
||||||
%args,
|
%$args,
|
||||||
label_width => $label_width,
|
options => {
|
||||||
|
show_all => $o->get('show-all'),
|
||||||
|
shorten => $o->get('shorten'),
|
||||||
|
report_all => $o->get('report-all'),
|
||||||
|
report_histogram => $o->get('report-histogram'),
|
||||||
|
},
|
||||||
num_format => "# %-${label_width}s %3s %7s %7s %7s %7s %7s %7s %7s",
|
num_format => "# %-${label_width}s %3s %7s %7s %7s %7s %7s %7s %7s",
|
||||||
bool_format => "# %-${label_width}s %3d%% yes, %3d%% no",
|
bool_format => "# %-${label_width}s %3d%% yes, %3d%% no",
|
||||||
string_format => "# %-${label_width}s %s",
|
string_format => "# %-${label_width}s %s",
|
||||||
global_headers => [qw( total min max avg 95% stddev median)],
|
|
||||||
event_headers => [qw(pct total min max avg 95% stddev median)],
|
|
||||||
hidden_attrib => { # Don't sort/print these attribs in the reports.
|
hidden_attrib => { # Don't sort/print these attribs in the reports.
|
||||||
arg => 1, # They're usually handled specially, or not
|
arg => 1, # They're usually handled specially, or not
|
||||||
fingerprint => 1, # printed at all.
|
fingerprint => 1, # printed at all.
|
||||||
@@ -87,30 +128,7 @@ sub new {
|
|||||||
ts => 1,
|
ts => 1,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
return bless $self, $class;
|
return $self;
|
||||||
}
|
|
||||||
|
|
||||||
# Sub: set_report_formatter
|
|
||||||
# Set a report formatter object for a report. By default this package will
|
|
||||||
# instantiate ReportFormatter objects to format columnized reports (e.g.
|
|
||||||
# for profile and prepared reports). Setting a caller-created formatter
|
|
||||||
# object (usually a <ReportFormatter> obj) is used for tests.
|
|
||||||
#
|
|
||||||
# Parameters:
|
|
||||||
# %args - Arguments
|
|
||||||
#
|
|
||||||
# Required Arguments:
|
|
||||||
# report - Report name, e.g. profile, prepared, etc.
|
|
||||||
# formatter - Formatter object, usually a <ReportFormatter> obj
|
|
||||||
sub set_report_formatter {
|
|
||||||
my ( $self, %args ) = @_;
|
|
||||||
my @required_args = qw(report formatter);
|
|
||||||
foreach my $arg ( @required_args ) {
|
|
||||||
die "I need a $arg argument" unless exists $args{$arg};
|
|
||||||
}
|
|
||||||
my ($report, $formatter) = @args{@required_args};
|
|
||||||
$self->{formatter_for}->{$report} = $formatter;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Arguments:
|
# Arguments:
|
||||||
@@ -240,7 +258,7 @@ sub header {
|
|||||||
shorten(scalar keys %{$results->{classes}}, d=>1_000),
|
shorten(scalar keys %{$results->{classes}}, d=>1_000),
|
||||||
shorten($qps || 0, d=>1_000),
|
shorten($qps || 0, d=>1_000),
|
||||||
shorten($conc || 0, d=>1_000));
|
shorten($conc || 0, d=>1_000));
|
||||||
$line .= ('_' x (LINE_LENGTH - length($line) + $self->{label_width} - 12));
|
$line .= ('_' x (LINE_LENGTH - length($line) + $self->label_width() - 12));
|
||||||
push @result, $line;
|
push @result, $line;
|
||||||
|
|
||||||
# Second line: time range
|
# Second line: time range
|
||||||
@@ -305,6 +323,70 @@ sub header {
|
|||||||
return join("\n", map { s/\s+$//; $_ } @result) . "\n";
|
return join("\n", map { s/\s+$//; $_ } @result) . "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub query_report_values {
|
||||||
|
my ($self, %args) = @_;
|
||||||
|
foreach my $arg ( qw(ea worst orderby groupby) ) {
|
||||||
|
die "I need a $arg argument" unless defined $arg;
|
||||||
|
}
|
||||||
|
my $ea = $args{ea};
|
||||||
|
my $groupby = $args{groupby};
|
||||||
|
my $worst = $args{worst};
|
||||||
|
|
||||||
|
my $q = $self->Quoter;
|
||||||
|
my $qv = $self->{QueryReview};
|
||||||
|
my $qr = $self->{QueryRewriter};
|
||||||
|
|
||||||
|
my @values;
|
||||||
|
# Print each worst item: its stats/metrics (sum/min/max/95%/etc.),
|
||||||
|
# Query_time distro chart, tables, EXPLAIN, fingerprint, etc.
|
||||||
|
# Items are usually unique queries/fingerprints--depends on how
|
||||||
|
# the events were grouped.
|
||||||
|
ITEM:
|
||||||
|
foreach my $top_event ( @$worst ) {
|
||||||
|
my $item = $top_event->[0];
|
||||||
|
my $reason = $args{explain_why} ? $top_event->[1] : '';
|
||||||
|
my $rank = $top_event->[2];
|
||||||
|
my $stats = $ea->results->{classes}->{$item};
|
||||||
|
my $sample = $ea->results->{samples}->{$item};
|
||||||
|
my $samp_query = $sample->{arg} || '';
|
||||||
|
|
||||||
|
my %item_vals = (
|
||||||
|
item => $item,
|
||||||
|
samp_query => $samp_query,
|
||||||
|
rank => ($rank || 0),
|
||||||
|
reason => $reason,
|
||||||
|
);
|
||||||
|
|
||||||
|
# ###############################################################
|
||||||
|
# Possibly skip item for --review.
|
||||||
|
# ###############################################################
|
||||||
|
my $review_vals;
|
||||||
|
if ( $qv ) {
|
||||||
|
$review_vals = $qv->get_review_info($item);
|
||||||
|
next ITEM if $review_vals->{reviewed_by} && !$self->{options}->{report_histogram};
|
||||||
|
for my $col ( $qv->review_cols() ) {
|
||||||
|
$item_vals{review_vals}{$col} = $review_vals->{$col};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$item_vals{default_db} = $sample->{db} ? $sample->{db}
|
||||||
|
: $stats->{db}->{unq} ? keys %{$stats->{db}->{unq}}
|
||||||
|
: undef;
|
||||||
|
$item_vals{tables} = [$self->{QueryParser}->extract_tables(
|
||||||
|
query => $samp_query,
|
||||||
|
default_db => $item_vals{default_db},
|
||||||
|
Quoter => $self->Quoter,
|
||||||
|
)];
|
||||||
|
|
||||||
|
if ( $samp_query && ($args{variations} && @{$args{variations}}) ) {
|
||||||
|
$item_vals{crc} = crc32($samp_query);
|
||||||
|
}
|
||||||
|
|
||||||
|
push @values, \%item_vals;
|
||||||
|
}
|
||||||
|
return \@values;
|
||||||
|
}
|
||||||
|
|
||||||
# Arguments:
|
# Arguments:
|
||||||
# * ea obj: EventAggregator
|
# * ea obj: EventAggregator
|
||||||
# * worst arrayref: worst items
|
# * worst arrayref: worst items
|
||||||
@@ -316,16 +398,11 @@ sub header {
|
|||||||
# * print_header bool: "Report grouped by" header
|
# * print_header bool: "Report grouped by" header
|
||||||
sub query_report {
|
sub query_report {
|
||||||
my ( $self, %args ) = @_;
|
my ( $self, %args ) = @_;
|
||||||
foreach my $arg ( qw(ea worst orderby groupby) ) {
|
|
||||||
die "I need a $arg argument" unless defined $arg;
|
|
||||||
}
|
|
||||||
my $ea = $args{ea};
|
my $ea = $args{ea};
|
||||||
my $groupby = $args{groupby};
|
my $groupby = $args{groupby};
|
||||||
my $worst = $args{worst};
|
my $report_values = $self->query_report_values(%args);
|
||||||
|
|
||||||
my $o = $self->{OptionParser};
|
|
||||||
my $q = $self->{Quoter};
|
|
||||||
my $qv = $self->{QueryReview};
|
|
||||||
my $qr = $self->{QueryRewriter};
|
my $qr = $self->{QueryRewriter};
|
||||||
|
|
||||||
my $report = '';
|
my $report = '';
|
||||||
@@ -347,60 +424,36 @@ sub query_report {
|
|||||||
# Items are usually unique queries/fingerprints--depends on how
|
# Items are usually unique queries/fingerprints--depends on how
|
||||||
# the events were grouped.
|
# the events were grouped.
|
||||||
ITEM:
|
ITEM:
|
||||||
foreach my $top_event ( @$worst ) {
|
foreach my $vals ( @$report_values ) {
|
||||||
my $item = $top_event->[0];
|
my $item = $vals->{item};
|
||||||
my $reason = $args{explain_why} ? $top_event->[1] : '';
|
|
||||||
my $rank = $top_event->[2];
|
|
||||||
my $stats = $ea->results->{classes}->{$item};
|
|
||||||
my $sample = $ea->results->{samples}->{$item};
|
|
||||||
my $samp_query = $sample->{arg} || '';
|
|
||||||
|
|
||||||
# ###############################################################
|
|
||||||
# Possibly skip item for --review.
|
|
||||||
# ###############################################################
|
|
||||||
my $review_vals;
|
|
||||||
if ( $qv ) {
|
|
||||||
$review_vals = $qv->get_review_info($item);
|
|
||||||
next ITEM if $review_vals->{reviewed_by} && !$o->get('report-all');
|
|
||||||
}
|
|
||||||
|
|
||||||
my ($default_db) = $sample->{db} ? $sample->{db}
|
|
||||||
: $stats->{db}->{unq} ? keys %{$stats->{db}->{unq}}
|
|
||||||
: undef;
|
|
||||||
my @tables = $self->{QueryParser}->extract_tables(
|
|
||||||
query => $samp_query,
|
|
||||||
default_db => $default_db,
|
|
||||||
Quoter => $self->{Quoter},
|
|
||||||
);
|
|
||||||
|
|
||||||
# ###############################################################
|
# ###############################################################
|
||||||
# Print the standard query analysis report.
|
# Print the standard query analysis report.
|
||||||
# ###############################################################
|
# ###############################################################
|
||||||
$report .= "\n" if $rank > 1; # space between each event report
|
$report .= "\n" if $vals->{rank} > 1; # space between each event report
|
||||||
$report .= $self->event_report(
|
$report .= $self->event_report(
|
||||||
%args,
|
%args,
|
||||||
item => $item,
|
item => $item,
|
||||||
sample => $sample,
|
sample => $ea->results->{samples}->{$item},
|
||||||
rank => $rank,
|
rank => $vals->{rank},
|
||||||
reason => $reason,
|
reason => $vals->{reason},
|
||||||
attribs => $attribs,
|
attribs => $attribs,
|
||||||
db => $default_db,
|
db => $vals->{default_db},
|
||||||
);
|
);
|
||||||
|
|
||||||
if ( $o->get('report-histogram') ) {
|
if ( $self->{options}->{report_histogram} ) {
|
||||||
$report .= $self->chart_distro(
|
$report .= $self->chart_distro(
|
||||||
%args,
|
%args,
|
||||||
attrib => $o->get('report-histogram'),
|
attrib => $self->{options}->{report_histogram},
|
||||||
item => $item,
|
item => $vals->{item},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( $qv && $review_vals ) {
|
if ( $vals->{review_vals} ) {
|
||||||
# Print the review information that is already in the table
|
# Print the review information that is already in the table
|
||||||
# before putting anything new into the table.
|
# before putting anything new into the table.
|
||||||
$report .= "# Review information\n";
|
$report .= "# Review information\n";
|
||||||
foreach my $col ( $qv->review_cols() ) {
|
foreach my $col ( keys %{$vals->{review_vals}} ) {
|
||||||
my $val = $review_vals->{$col};
|
my $val = $vals->{review_vals}->{$col};
|
||||||
if ( !$val || $val ne '0000-00-00 00:00:00' ) { # issue 202
|
if ( !$val || $val ne '0000-00-00 00:00:00' ) { # issue 202
|
||||||
$report .= sprintf "# %13s: %-s\n", $col, ($val ? $val : '');
|
$report .= sprintf "# %13s: %-s\n", $col, ($val ? $val : '');
|
||||||
}
|
}
|
||||||
@@ -409,23 +462,22 @@ sub query_report {
|
|||||||
|
|
||||||
if ( $groupby eq 'fingerprint' ) {
|
if ( $groupby eq 'fingerprint' ) {
|
||||||
# Shorten it if necessary (issue 216 and 292).
|
# Shorten it if necessary (issue 216 and 292).
|
||||||
$samp_query = $qr->shorten($samp_query, $o->get('shorten'))
|
my $samp_query = $qr->shorten($vals->{samp_query}, $self->{options}->{shorten})
|
||||||
if $o->get('shorten');
|
if $self->{options}->{shorten};
|
||||||
|
|
||||||
# Print query fingerprint.
|
# Print query fingerprint.
|
||||||
PTDEBUG && _d("Fingerprint\n# $item\n");
|
PTDEBUG && _d("Fingerprint\n# $vals->{item}\n");
|
||||||
|
|
||||||
# Print tables used by query.
|
# Print tables used by query.
|
||||||
$report .= $self->tables_report(@tables);
|
$report .= $self->tables_report(@{$vals->{tables}});
|
||||||
|
|
||||||
# Print sample (worst) query's CRC % 1_000. We mod 1_000 because
|
# Print sample (worst) query's CRC % 1_000. We mod 1_000 because
|
||||||
# that's actually the value stored in the ea, not the full checksum.
|
# that's actually the value stored in the ea, not the full checksum.
|
||||||
# So the report will print something like,
|
# So the report will print something like,
|
||||||
# # arg crc 685 (2/66%), 159 (1/33%)
|
# # arg crc 685 (2/66%), 159 (1/33%)
|
||||||
# Thus we want our "CRC" line to be 685 and not 18547302820.
|
# Thus we want our "CRC" line to be 685 and not 18547302820.
|
||||||
if ( $samp_query && ($args{variations} && @{$args{variations}}) ) {
|
if ( $vals->{crc} ) {
|
||||||
my $crc = crc32($samp_query);
|
$report.= "# CRC " . ($vals->{crc} % 1_000) . "\n";
|
||||||
$report.= "# CRC " . ($crc ? $crc % 1_000 : "") . "\n";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
my $log_type = $args{log_type} || '';
|
my $log_type = $args{log_type} || '';
|
||||||
@@ -439,7 +491,7 @@ sub query_report {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$report .= "# EXPLAIN /*!50100 PARTITIONS*/\n$samp_query${mark}\n";
|
$report .= "# EXPLAIN /*!50100 PARTITIONS*/\n$samp_query${mark}\n";
|
||||||
$report .= $self->explain_report($samp_query, $default_db);
|
$report .= $self->explain_report($samp_query, $vals->{default_db});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -454,7 +506,7 @@ sub query_report {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if ( $groupby eq 'tables' ) {
|
if ( $groupby eq 'tables' ) {
|
||||||
my ( $db, $tbl ) = $q->split_unquote($item);
|
my ( $db, $tbl ) = $self->Quoter->split_unquote($item);
|
||||||
$report .= $self->tables_report([$db, $tbl]);
|
$report .= $self->tables_report([$db, $tbl]);
|
||||||
}
|
}
|
||||||
$report .= "$item\n";
|
$report .= "$item\n";
|
||||||
@@ -474,21 +526,20 @@ sub query_report {
|
|||||||
# * rank scalar: item rank among the worst
|
# * rank scalar: item rank among the worst
|
||||||
# Print a report about the statistics in the EventAggregator.
|
# Print a report about the statistics in the EventAggregator.
|
||||||
# Called by query_report().
|
# Called by query_report().
|
||||||
sub event_report {
|
sub event_report_values {
|
||||||
my ($self, %args) = @_;
|
my ($self, %args) = @_;
|
||||||
foreach my $arg ( qw(ea item orderby) ) {
|
|
||||||
die "I need a $arg argument" unless defined $args{$arg};
|
|
||||||
}
|
|
||||||
my $ea = $args{ea};
|
my $ea = $args{ea};
|
||||||
my $item = $args{item};
|
my $item = $args{item};
|
||||||
my $orderby = $args{orderby};
|
my $orderby = $args{orderby};
|
||||||
my $results = $ea->results();
|
my $results = $ea->results();
|
||||||
my $o = $self->{OptionParser};
|
|
||||||
my @result;
|
my %vals;
|
||||||
|
|
||||||
# Return unless the item exists in the results (it should).
|
# Return unless the item exists in the results (it should).
|
||||||
my $store = $results->{classes}->{$item};
|
my $store = $results->{classes}->{$item};
|
||||||
return "# No such event $item\n" unless $store;
|
|
||||||
|
return unless $store;
|
||||||
|
|
||||||
# Pick the first attribute to get counts
|
# Pick the first attribute to get counts
|
||||||
my $global_cnt = $results->{globals}->{$orderby}->{cnt};
|
my $global_cnt = $results->{globals}->{$orderby}->{cnt};
|
||||||
@@ -509,51 +560,26 @@ sub event_report {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
# First line like:
|
$vals{groupby} = $ea->{groupby};
|
||||||
# Query 1: 9 QPS, 0x concurrency, ID 0x7F7D57ACDD8A346E at byte 5 ________
|
$vals{qps} = $qps || 0;
|
||||||
my $line = sprintf(
|
$vals{concurrency} = $conc || 0;
|
||||||
'# %s %d: %s QPS, %sx concurrency, ID 0x%s at byte %.f ',
|
$vals{checksum} = make_checksum($item);
|
||||||
($ea->{groupby} eq 'fingerprint' ? 'Query' : 'Item'),
|
$vals{pos_in_log} = $results->{samples}->{$item}->{pos_in_log} || 0;
|
||||||
$args{rank} || 0,
|
$vals{reason} = $args{reason};
|
||||||
shorten($qps || 0, d=>1_000),
|
$vals{variance_to_mean} = do {
|
||||||
shorten($conc || 0, d=>1_000),
|
|
||||||
make_checksum($item),
|
|
||||||
$results->{samples}->{$item}->{pos_in_log} || 0,
|
|
||||||
);
|
|
||||||
$line .= ('_' x (LINE_LENGTH - length($line) + $self->{label_width} - 12));
|
|
||||||
push @result, $line;
|
|
||||||
|
|
||||||
# Second line: reason why this class is being reported.
|
|
||||||
if ( $args{reason} ) {
|
|
||||||
push @result,
|
|
||||||
"# This item is included in the report because it matches "
|
|
||||||
. ($args{reason} eq 'top' ? '--limit.' : '--outliers.');
|
|
||||||
}
|
|
||||||
|
|
||||||
# Third line: Variance-to-mean (V/M) ratio, like:
|
|
||||||
# Scores: V/M = 1.5
|
|
||||||
{
|
|
||||||
my $query_time = $ea->metrics(where => $item, attrib => 'Query_time');
|
my $query_time = $ea->metrics(where => $item, attrib => 'Query_time');
|
||||||
push @result,
|
$query_time->{stddev}**2 / ($query_time->{avg} || 1)
|
||||||
sprintf("# Scores: V/M = %.2f",
|
};
|
||||||
($query_time->{stddev}**2 / ($query_time->{avg} || 1)),
|
|
||||||
);
|
$vals{counts} = {
|
||||||
}
|
class_cnt => $class_cnt,
|
||||||
|
global_cnt => $global_cnt,
|
||||||
|
};
|
||||||
|
|
||||||
# Last line before column headers: time range
|
|
||||||
if ( my $ts = $store->{ts}) {
|
if ( my $ts = $store->{ts}) {
|
||||||
my $time_range = $self->format_time_range($ts) || "unknown";
|
$vals{time_range} = $self->format_time_range($ts) || "unknown";
|
||||||
push @result, "# Time range: $time_range";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Column header line
|
|
||||||
push @result, $self->make_event_header();
|
|
||||||
|
|
||||||
# Count line
|
|
||||||
push @result,
|
|
||||||
sprintf $self->{num_format}, 'Count',
|
|
||||||
percentage_of($class_cnt, $global_cnt), $class_cnt, map { '' } (1..8);
|
|
||||||
|
|
||||||
# Sort the attributes, removing any hidden attributes, if they're not
|
# Sort the attributes, removing any hidden attributes, if they're not
|
||||||
# already given to us. In mk-query-digest, this sub is called from
|
# already given to us. In mk-query-digest, this sub is called from
|
||||||
# query_report(), but in testing it's called directly. query_report()
|
# query_report(), but in testing it's called directly. query_report()
|
||||||
@@ -566,11 +592,10 @@ sub event_report {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$vals{attributes} = { map { $_ => [] } qw(num innodb bool string) };
|
||||||
|
|
||||||
foreach my $type ( qw(num innodb) ) {
|
foreach my $type ( qw(num innodb) ) {
|
||||||
# Add "InnoDB:" sub-header before grouped InnoDB_* attributes.
|
# Add "InnoDB:" sub-header before grouped InnoDB_* attributes.
|
||||||
if ( $type eq 'innodb' && @{$attribs->{$type}} ) {
|
|
||||||
push @result, "# InnoDB:";
|
|
||||||
};
|
|
||||||
|
|
||||||
NUM_ATTRIB:
|
NUM_ATTRIB:
|
||||||
foreach my $attrib ( @{$attribs->{$type}} ) {
|
foreach my $attrib ( @{$attribs->{$type}} ) {
|
||||||
@@ -590,15 +615,12 @@ sub event_report {
|
|||||||
$pct = percentage_of(
|
$pct = percentage_of(
|
||||||
$vals->{sum}, $results->{globals}->{$attrib}->{sum});
|
$vals->{sum}, $results->{globals}->{$attrib}->{sum});
|
||||||
|
|
||||||
push @result,
|
push @{$vals{attributes}{$type}},
|
||||||
sprintf $self->{num_format},
|
[ $attrib, $pct, @values ];
|
||||||
$self->make_label($attrib), $pct, @values;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( @{$attribs->{bool}} ) {
|
if ( @{$attribs->{bool}} ) {
|
||||||
push @result, "# Boolean:";
|
|
||||||
my $printed_bools = 0;
|
|
||||||
BOOL_ATTRIB:
|
BOOL_ATTRIB:
|
||||||
foreach my $attrib ( @{$attribs->{bool}} ) {
|
foreach my $attrib ( @{$attribs->{bool}} ) {
|
||||||
next BOOL_ATTRIB unless exists $store->{$attrib};
|
next BOOL_ATTRIB unless exists $store->{$attrib};
|
||||||
@@ -606,33 +628,125 @@ sub event_report {
|
|||||||
next unless scalar %$vals;
|
next unless scalar %$vals;
|
||||||
|
|
||||||
if ( $vals->{sum} > 0 ) {
|
if ( $vals->{sum} > 0 ) {
|
||||||
push @result,
|
push @{$vals{attributes}{bool}},
|
||||||
sprintf $self->{bool_format},
|
[ $attrib, $self->bool_percents($vals) ];
|
||||||
$self->make_label($attrib), $self->bool_percents($vals);
|
|
||||||
$printed_bools = 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pop @result unless $printed_bools;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( @{$attribs->{string}} ) {
|
if ( @{$attribs->{string}} ) {
|
||||||
push @result, "# String:";
|
|
||||||
my $printed_strings = 0;
|
|
||||||
STRING_ATTRIB:
|
STRING_ATTRIB:
|
||||||
foreach my $attrib ( @{$attribs->{string}} ) {
|
foreach my $attrib ( @{$attribs->{string}} ) {
|
||||||
next STRING_ATTRIB unless exists $store->{$attrib};
|
next STRING_ATTRIB unless exists $store->{$attrib};
|
||||||
my $vals = $store->{$attrib};
|
my $vals = $store->{$attrib};
|
||||||
next unless scalar %$vals;
|
next unless scalar %$vals;
|
||||||
|
|
||||||
|
push @{$vals{attributes}{string}},
|
||||||
|
[ $attrib, $vals ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return \%vals;
|
||||||
|
}
|
||||||
|
|
||||||
|
# TODO I maybe've broken the groupby report
|
||||||
|
|
||||||
|
sub event_report {
|
||||||
|
my ( $self, %args ) = @_;
|
||||||
|
foreach my $arg ( qw(ea item orderby) ) {
|
||||||
|
die "I need a $arg argument" unless defined $args{$arg};
|
||||||
|
}
|
||||||
|
|
||||||
|
my $item = $args{item};
|
||||||
|
my $val = $self->event_report_values(%args);
|
||||||
|
my @result;
|
||||||
|
|
||||||
|
return "# No such event $item\n" unless $val;
|
||||||
|
|
||||||
|
# First line like:
|
||||||
|
# Query 1: 9 QPS, 0x concurrency, ID 0x7F7D57ACDD8A346E at byte 5 ________
|
||||||
|
my $line = sprintf(
|
||||||
|
'# %s %d: %s QPS, %sx concurrency, ID 0x%s at byte %.f ',
|
||||||
|
($val->{groupby} eq 'fingerprint' ? 'Query' : 'Item'),
|
||||||
|
$args{rank} || 0,
|
||||||
|
shorten($val->{qps}, d=>1_000),
|
||||||
|
shorten($val->{concurrency}, d=>1_000),
|
||||||
|
$val->{checksum},
|
||||||
|
$val->{pos_in_log},
|
||||||
|
);
|
||||||
|
$line .= ('_' x (LINE_LENGTH - length($line) + $self->label_width() - 12));
|
||||||
|
push @result, $line;
|
||||||
|
|
||||||
|
# Second line: reason why this class is being reported.
|
||||||
|
if ( $val->{reason} ) {
|
||||||
|
push @result,
|
||||||
|
"# This item is included in the report because it matches "
|
||||||
|
. ($val->{reason} eq 'top' ? '--limit.' : '--outliers.');
|
||||||
|
}
|
||||||
|
|
||||||
|
# Third line: Variance-to-mean (V/M) ratio, like:
|
||||||
|
# Scores: V/M = 1.5
|
||||||
|
push @result,
|
||||||
|
sprintf("# Scores: V/M = %.2f", $val->{variance_to_mean} );
|
||||||
|
|
||||||
|
# Last line before column headers: time range
|
||||||
|
if ( $val->{time_range} ) {
|
||||||
|
push @result, "# Time range: $val->{time_range}";
|
||||||
|
}
|
||||||
|
|
||||||
|
# Column header line
|
||||||
|
push @result, $self->make_event_header();
|
||||||
|
|
||||||
|
# Count line
|
||||||
|
push @result,
|
||||||
|
sprintf $self->{num_format}, 'Count',
|
||||||
|
percentage_of($val->{counts}{class_cnt}, $val->{counts}{global_cnt}),
|
||||||
|
$val->{counts}{class_cnt},
|
||||||
|
map { '' } (1..8);
|
||||||
|
|
||||||
|
|
||||||
|
my $attribs = $val->{attributes};
|
||||||
|
|
||||||
|
foreach my $type ( qw(num innodb) ) {
|
||||||
|
# Add "InnoDB:" sub-header before grouped InnoDB_* attributes.
|
||||||
|
if ( $type eq 'innodb' && @{$attribs->{$type}} ) {
|
||||||
|
push @result, "# InnoDB:";
|
||||||
|
};
|
||||||
|
|
||||||
|
NUM_ATTRIB:
|
||||||
|
foreach my $attrib ( @{$attribs->{$type}} ) {
|
||||||
|
my ($attrib_name, @vals) = @$attrib;
|
||||||
|
push @result,
|
||||||
|
sprintf $self->{num_format},
|
||||||
|
$self->make_label($attrib_name), @vals;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( @{$attribs->{bool}} ) {
|
||||||
|
push @result, "# Boolean:";
|
||||||
|
BOOL_ATTRIB:
|
||||||
|
foreach my $attrib ( @{$attribs->{bool}} ) {
|
||||||
|
my ($attrib_name, @vals) = @$attrib;
|
||||||
|
push @result,
|
||||||
|
sprintf $self->{bool_format},
|
||||||
|
$self->make_label($attrib_name), @vals;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( @{$attribs->{string}} ) {
|
||||||
|
push @result, "# String:";
|
||||||
|
STRING_ATTRIB:
|
||||||
|
foreach my $attrib ( @{$attribs->{string}} ) {
|
||||||
|
my ($attrib_name, $vals) = @$attrib;
|
||||||
push @result,
|
push @result,
|
||||||
sprintf $self->{string_format},
|
sprintf $self->{string_format},
|
||||||
$self->make_label($attrib),
|
$self->make_label($attrib_name),
|
||||||
$self->format_string_list($attrib, $vals, $class_cnt);
|
$self->format_string_list($attrib_name, $vals, $val->{counts}{class_cnt});
|
||||||
$printed_strings = 1;
|
|
||||||
}
|
}
|
||||||
pop @result unless $printed_strings;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return join("\n", map { s/\s+$//; $_ } @result) . "\n";
|
return join("\n", map { s/\s+$//; $_ } @result) . "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -706,7 +820,6 @@ sub chart_distro {
|
|||||||
# Optional arguments:
|
# Optional arguments:
|
||||||
# * other arrayref: other items (that didn't make it into top worst)
|
# * other arrayref: other items (that didn't make it into top worst)
|
||||||
# * distill_args hashref: extra args for distill()
|
# * distill_args hashref: extra args for distill()
|
||||||
# * ReportFormatter obj: passed-in ReportFormatter for testing
|
|
||||||
sub profile {
|
sub profile {
|
||||||
my ( $self, %args ) = @_;
|
my ( $self, %args ) = @_;
|
||||||
foreach my $arg ( qw(ea worst groupby) ) {
|
foreach my $arg ( qw(ea worst groupby) ) {
|
||||||
@@ -718,7 +831,6 @@ sub profile {
|
|||||||
my $groupby = $args{groupby};
|
my $groupby = $args{groupby};
|
||||||
|
|
||||||
my $qr = $self->{QueryRewriter};
|
my $qr = $self->{QueryRewriter};
|
||||||
my $o = $self->{OptionParser};
|
|
||||||
|
|
||||||
# Total response time of all events.
|
# Total response time of all events.
|
||||||
my $results = $ea->results();
|
my $results = $ea->results();
|
||||||
@@ -746,12 +858,8 @@ sub profile {
|
|||||||
push @profiles, \%profile;
|
push @profiles, \%profile;
|
||||||
}
|
}
|
||||||
|
|
||||||
my $report = $self->{formatter_for}->{profile} || new ReportFormatter(
|
my $report = $self->ReportFormatter();
|
||||||
line_width => LINE_LENGTH,
|
$report->title('Profile');
|
||||||
long_last_column => 1,
|
|
||||||
extend_right => 1,
|
|
||||||
);
|
|
||||||
$report->set_title('Profile');
|
|
||||||
my @cols = (
|
my @cols = (
|
||||||
{ name => 'Rank', right_justify => 1, },
|
{ name => 'Rank', right_justify => 1, },
|
||||||
{ name => 'Query ID', },
|
{ name => 'Query ID', },
|
||||||
@@ -817,7 +925,6 @@ sub profile {
|
|||||||
# * groupby scalar: attrib worst items grouped by
|
# * groupby scalar: attrib worst items grouped by
|
||||||
# Optional arguments:
|
# Optional arguments:
|
||||||
# * distill_args hashref: extra args for distill()
|
# * distill_args hashref: extra args for distill()
|
||||||
# * ReportFormatter obj: passed-in ReportFormatter for testing
|
|
||||||
sub prepared {
|
sub prepared {
|
||||||
my ( $self, %args ) = @_;
|
my ( $self, %args ) = @_;
|
||||||
foreach my $arg ( qw(ea worst groupby) ) {
|
foreach my $arg ( qw(ea worst groupby) ) {
|
||||||
@@ -902,12 +1009,8 @@ sub prepared {
|
|||||||
# Return unless there are prepared statements to report.
|
# Return unless there are prepared statements to report.
|
||||||
return unless scalar @prepared;
|
return unless scalar @prepared;
|
||||||
|
|
||||||
my $report = $self->{formatter_for}->{prepared} || new ReportFormatter(
|
my $report = $self->ReportFormatter();
|
||||||
line_width => LINE_LENGTH,
|
$report->title('Prepared statements');
|
||||||
long_last_column => 1,
|
|
||||||
extend_right => 1,
|
|
||||||
);
|
|
||||||
$report->set_title('Prepared statements');
|
|
||||||
$report->set_columns(
|
$report->set_columns(
|
||||||
{ name => 'Rank', right_justify => 1, },
|
{ name => 'Rank', right_justify => 1, },
|
||||||
{ name => 'Query ID', },
|
{ name => 'Query ID', },
|
||||||
@@ -943,7 +1046,7 @@ sub make_global_header {
|
|||||||
# First line:
|
# First line:
|
||||||
# Attribute total min max avg 95% stddev median
|
# Attribute total min max avg 95% stddev median
|
||||||
push @lines,
|
push @lines,
|
||||||
sprintf $self->{num_format}, "Attribute", '', @{$self->{global_headers}};
|
sprintf $self->{num_format}, "Attribute", '', @{$self->global_headers()};
|
||||||
|
|
||||||
# Underline first line:
|
# Underline first line:
|
||||||
# ========= ======= ======= ======= ======= ======= ======= =======
|
# ========= ======= ======= ======= ======= ======= ======= =======
|
||||||
@@ -951,7 +1054,7 @@ sub make_global_header {
|
|||||||
# Hard-coded values aren't ideal but this code rarely changes.
|
# Hard-coded values aren't ideal but this code rarely changes.
|
||||||
push @lines,
|
push @lines,
|
||||||
sprintf $self->{num_format},
|
sprintf $self->{num_format},
|
||||||
(map { "=" x $_ } $self->{label_width}),
|
(map { "=" x $_ } $self->label_width()),
|
||||||
(map { " " x $_ } qw(3)), # no pct column in global header
|
(map { " " x $_ } qw(3)), # no pct column in global header
|
||||||
(map { "=" x $_ } qw(7 7 7 7 7 7 7));
|
(map { "=" x $_ } qw(7 7 7 7 7 7 7));
|
||||||
|
|
||||||
@@ -969,13 +1072,13 @@ sub make_event_header {
|
|||||||
|
|
||||||
my @lines;
|
my @lines;
|
||||||
push @lines,
|
push @lines,
|
||||||
sprintf $self->{num_format}, "Attribute", @{$self->{event_headers}};
|
sprintf $self->{num_format}, "Attribute", @{$self->event_headers()};
|
||||||
|
|
||||||
# The numbers 6, 7, 7, etc. are the field widths from make_header().
|
# The numbers 6, 7, 7, etc. are the field widths from make_header().
|
||||||
# Hard-coded values aren't ideal but this code rarely changes.
|
# Hard-coded values aren't ideal but this code rarely changes.
|
||||||
push @lines,
|
push @lines,
|
||||||
sprintf $self->{num_format},
|
sprintf $self->{num_format},
|
||||||
map { "=" x $_ } ($self->{label_width}, qw(3 7 7 7 7 7 7 7));
|
map { "=" x $_ } ($self->label_width(), qw(3 7 7 7 7 7 7 7));
|
||||||
|
|
||||||
# End result should be like:
|
# End result should be like:
|
||||||
# Attribute pct total min max avg 95% stddev median
|
# Attribute pct total min max avg 95% stddev median
|
||||||
@@ -994,7 +1097,7 @@ sub make_label {
|
|||||||
if ( $val =~ m/^InnoDB/ ) {
|
if ( $val =~ m/^InnoDB/ ) {
|
||||||
$val =~ s/^InnoDB //;
|
$val =~ s/^InnoDB //;
|
||||||
$val = $val eq 'trx id' ? "InnoDB trxID"
|
$val = $val eq 'trx id' ? "InnoDB trxID"
|
||||||
: substr($val, 0, $self->{label_width});
|
: substr($val, 0, $self->label_width());
|
||||||
}
|
}
|
||||||
|
|
||||||
$val = $val eq 'user' ? 'Users'
|
$val = $val eq 'user' ? 'Users'
|
||||||
@@ -1005,7 +1108,7 @@ sub make_label {
|
|||||||
: $val eq 'bytes' ? 'Query size'
|
: $val eq 'bytes' ? 'Query size'
|
||||||
: $val eq 'Tmp disk tables' ? 'Tmp disk tbl'
|
: $val eq 'Tmp disk tables' ? 'Tmp disk tbl'
|
||||||
: $val eq 'Tmp table sizes' ? 'Tmp tbl size'
|
: $val eq 'Tmp table sizes' ? 'Tmp tbl size'
|
||||||
: substr($val, 0, $self->{label_width});
|
: substr($val, 0, $self->label_width);
|
||||||
|
|
||||||
return $val;
|
return $val;
|
||||||
}
|
}
|
||||||
@@ -1023,8 +1126,7 @@ sub bool_percents {
|
|||||||
# Does pretty-printing for lists of strings like users, hosts, db.
|
# Does pretty-printing for lists of strings like users, hosts, db.
|
||||||
sub format_string_list {
|
sub format_string_list {
|
||||||
my ( $self, $attrib, $vals, $class_cnt ) = @_;
|
my ( $self, $attrib, $vals, $class_cnt ) = @_;
|
||||||
my $o = $self->{OptionParser};
|
my $show_all = $self->{options}->{show_all};
|
||||||
my $show_all = $o->get('show-all');
|
|
||||||
|
|
||||||
# Only class result values have unq. So if unq doesn't exist,
|
# Only class result values have unq. So if unq doesn't exist,
|
||||||
# then we've been given global values.
|
# then we've been given global values.
|
||||||
@@ -1164,7 +1266,7 @@ sub pref_sort {
|
|||||||
sub tables_report {
|
sub tables_report {
|
||||||
my ( $self, @tables ) = @_;
|
my ( $self, @tables ) = @_;
|
||||||
return '' unless @tables;
|
return '' unless @tables;
|
||||||
my $q = $self->{Quoter};
|
my $q = $self->Quoter();
|
||||||
my $tables = "";
|
my $tables = "";
|
||||||
foreach my $db_tbl ( @tables ) {
|
foreach my $db_tbl ( @tables ) {
|
||||||
my ( $db, $tbl ) = @$db_tbl;
|
my ( $db, $tbl ) = @$db_tbl;
|
||||||
@@ -1183,7 +1285,7 @@ sub explain_report {
|
|||||||
return '' unless $query;
|
return '' unless $query;
|
||||||
|
|
||||||
my $dbh = $self->{dbh};
|
my $dbh = $self->{dbh};
|
||||||
my $q = $self->{Quoter};
|
my $q = $self->Quoter();
|
||||||
my $qp = $self->{QueryParser};
|
my $qp = $self->{QueryParser};
|
||||||
return '' unless $dbh && $q && $qp;
|
return '' unless $dbh && $q && $qp;
|
||||||
|
|
||||||
|
@@ -43,7 +43,6 @@ my $o = new OptionParser(description=>'qrf');
|
|||||||
my $ex = new ExplainAnalyzer(QueryRewriter => $qr, QueryParser => $qp);
|
my $ex = new ExplainAnalyzer(QueryRewriter => $qr, QueryParser => $qp);
|
||||||
|
|
||||||
$o->get_specs("$trunk/bin/pt-query-digest");
|
$o->get_specs("$trunk/bin/pt-query-digest");
|
||||||
|
|
||||||
my $qrf = new QueryReportFormatter(
|
my $qrf = new QueryReportFormatter(
|
||||||
OptionParser => $o,
|
OptionParser => $o,
|
||||||
QueryRewriter => $qr,
|
QueryRewriter => $qr,
|
||||||
@@ -885,6 +884,13 @@ ok(
|
|||||||
# Test show_all.
|
# Test show_all.
|
||||||
@ARGV = qw(--show-all host);
|
@ARGV = qw(--show-all host);
|
||||||
$o->get_opts();
|
$o->get_opts();
|
||||||
|
$qrf = new QueryReportFormatter(
|
||||||
|
OptionParser => $o,
|
||||||
|
QueryRewriter => $qr,
|
||||||
|
QueryParser => $qp,
|
||||||
|
Quoter => $q,
|
||||||
|
ExplainAnalyzer => $ex,
|
||||||
|
);
|
||||||
$result = $qrf->event_report(
|
$result = $qrf->event_report(
|
||||||
ea => $ea,
|
ea => $ea,
|
||||||
select => [ qw(Query_time host) ],
|
select => [ qw(Query_time host) ],
|
||||||
@@ -971,7 +977,13 @@ $ea->calculate_statistical_metrics(apdex_t=>1);
|
|||||||
# Reset opts in case anything above left something set.
|
# Reset opts in case anything above left something set.
|
||||||
@ARGV = qw();
|
@ARGV = qw();
|
||||||
$o->get_opts();
|
$o->get_opts();
|
||||||
|
$qrf = new QueryReportFormatter(
|
||||||
|
OptionParser => $o,
|
||||||
|
QueryRewriter => $qr,
|
||||||
|
QueryParser => $qp,
|
||||||
|
Quoter => $q,
|
||||||
|
ExplainAnalyzer => $ex,
|
||||||
|
);
|
||||||
# Normally, the report subs will make their own ReportFormatter but
|
# Normally, the report subs will make their own ReportFormatter but
|
||||||
# that package isn't visible to QueryReportFormatter right now so we
|
# that package isn't visible to QueryReportFormatter right now so we
|
||||||
# make ReportFormatters and pass them in. Since ReporFormatters can't
|
# make ReportFormatters and pass them in. Since ReporFormatters can't
|
||||||
@@ -980,7 +992,7 @@ $o->get_opts();
|
|||||||
# profile subreport. And the line width is 82 because that's the new
|
# profile subreport. And the line width is 82 because that's the new
|
||||||
# default to accommodate the EXPLAIN sparkline (issue 1141).
|
# default to accommodate the EXPLAIN sparkline (issue 1141).
|
||||||
my $report = new ReportFormatter(line_width=>82);
|
my $report = new ReportFormatter(line_width=>82);
|
||||||
$qrf->set_report_formatter(report=>'profile', formatter=>$report);
|
$qrf->{formatter} = $report;
|
||||||
ok(
|
ok(
|
||||||
no_diff(
|
no_diff(
|
||||||
sub { $qrf->print_reports(
|
sub { $qrf->print_reports(
|
||||||
@@ -997,8 +1009,6 @@ ok(
|
|||||||
"print_reports(header, query_report, profile)"
|
"print_reports(header, query_report, profile)"
|
||||||
);
|
);
|
||||||
|
|
||||||
$report = new ReportFormatter(line_width=>82);
|
|
||||||
$qrf->set_report_formatter(report=>'profile', formatter=>$report);
|
|
||||||
ok(
|
ok(
|
||||||
no_diff(
|
no_diff(
|
||||||
sub { $qrf->print_reports(
|
sub { $qrf->print_reports(
|
||||||
@@ -1051,11 +1061,6 @@ foreach my $event ( @$events ) {
|
|||||||
$ea->aggregate($event);
|
$ea->aggregate($event);
|
||||||
}
|
}
|
||||||
$ea->calculate_statistical_metrics();
|
$ea->calculate_statistical_metrics();
|
||||||
$report = new ReportFormatter(
|
|
||||||
line_width => 82,
|
|
||||||
extend_right => 1,
|
|
||||||
);
|
|
||||||
$qrf->set_report_formatter(report=>'prepared', formatter=>$report);
|
|
||||||
ok(
|
ok(
|
||||||
no_diff(
|
no_diff(
|
||||||
sub {
|
sub {
|
||||||
@@ -1094,11 +1099,6 @@ foreach my $event ( @$events ) {
|
|||||||
$ea->aggregate($event);
|
$ea->aggregate($event);
|
||||||
}
|
}
|
||||||
$ea->calculate_statistical_metrics();
|
$ea->calculate_statistical_metrics();
|
||||||
$report = new ReportFormatter(
|
|
||||||
line_width => 82,
|
|
||||||
extend_right => 1,
|
|
||||||
);
|
|
||||||
$qrf->set_report_formatter(report=>'profile', formatter=>$report);
|
|
||||||
ok(
|
ok(
|
||||||
no_diff(
|
no_diff(
|
||||||
sub {
|
sub {
|
||||||
@@ -1130,7 +1130,13 @@ SKIP: {
|
|||||||
|
|
||||||
@ARGV = qw(--explain F=/tmp/12345/my.sandbox.cnf);
|
@ARGV = qw(--explain F=/tmp/12345/my.sandbox.cnf);
|
||||||
$o->get_opts();
|
$o->get_opts();
|
||||||
|
$qrf = new QueryReportFormatter(
|
||||||
|
OptionParser => $o,
|
||||||
|
QueryRewriter => $qr,
|
||||||
|
QueryParser => $qp,
|
||||||
|
Quoter => $q,
|
||||||
|
ExplainAnalyzer => $ex,
|
||||||
|
);
|
||||||
my $qrf = new QueryReportFormatter(
|
my $qrf = new QueryReportFormatter(
|
||||||
OptionParser => $o,
|
OptionParser => $o,
|
||||||
QueryRewriter => $qr,
|
QueryRewriter => $qr,
|
||||||
@@ -1151,56 +1157,6 @@ SKIP: {
|
|||||||
"explain_report()"
|
"explain_report()"
|
||||||
);
|
);
|
||||||
|
|
||||||
my $arg = "select t1.i from t as t1 join t as t2 where t1.i < t2.i and t1.v is not null order by t1.i";
|
|
||||||
my $fingerprint = $qr->fingerprint($arg);
|
|
||||||
|
|
||||||
$events = [
|
|
||||||
{
|
|
||||||
Query_time => '0.000286',
|
|
||||||
arg => $arg,
|
|
||||||
fingerprint => $fingerprint,
|
|
||||||
bytes => length $arg,
|
|
||||||
cmd => 'Query',
|
|
||||||
db => 'qrf',
|
|
||||||
pos_in_log => 0,
|
|
||||||
ts => '091208 09:23:49.637394',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
$ea = new EventAggregator(
|
|
||||||
groupby => 'fingerprint',
|
|
||||||
worst => 'Query_time',
|
|
||||||
);
|
|
||||||
foreach my $event ( @$events ) {
|
|
||||||
$ea->aggregate($event);
|
|
||||||
}
|
|
||||||
$ea->calculate_statistical_metrics();
|
|
||||||
|
|
||||||
$dbh->do("USE mysql");
|
|
||||||
$report = new ReportFormatter(
|
|
||||||
line_width => 82,
|
|
||||||
extend_right => 1,
|
|
||||||
);
|
|
||||||
$qrf->set_report_formatter(report=>'profile', formatter=>$report);
|
|
||||||
$dbh->do("USE mysql"); # same reason as above ^; force use db from event
|
|
||||||
ok(
|
|
||||||
no_diff(
|
|
||||||
sub {
|
|
||||||
$qrf->print_reports(
|
|
||||||
reports => ['profile', 'query_report'],
|
|
||||||
ea => $ea,
|
|
||||||
worst => [ [$fingerprint, 'top', 1], ],
|
|
||||||
other => [ [$fingerprint, 'misc', 2], ],
|
|
||||||
orderby => 'Query_time',
|
|
||||||
groupby => 'fingerprint',
|
|
||||||
);
|
|
||||||
},
|
|
||||||
( $sandbox_version eq '5.6' ? "t/lib/samples/QueryReportFormatter/report032.txt"
|
|
||||||
: $sandbox_version ge '5.1' ? "t/lib/samples/QueryReportFormatter/report027.txt"
|
|
||||||
: "t/lib/samples/QueryReportFormatter/report029.txt"),
|
|
||||||
),
|
|
||||||
"EXPLAIN sparkline (issue 1141)"
|
|
||||||
);
|
|
||||||
|
|
||||||
$sb->wipe_clean($dbh);
|
$sb->wipe_clean($dbh);
|
||||||
$dbh->disconnect();
|
$dbh->disconnect();
|
||||||
}
|
}
|
||||||
@@ -1251,7 +1207,6 @@ foreach my $event ( @$events ) {
|
|||||||
$ea->calculate_statistical_metrics();
|
$ea->calculate_statistical_metrics();
|
||||||
@ARGV = qw();
|
@ARGV = qw();
|
||||||
$o->get_opts();
|
$o->get_opts();
|
||||||
$report = new ReportFormatter(line_width=>82);
|
|
||||||
$qrf = new QueryReportFormatter(
|
$qrf = new QueryReportFormatter(
|
||||||
OptionParser => $o,
|
OptionParser => $o,
|
||||||
QueryRewriter => $qr,
|
QueryRewriter => $qr,
|
||||||
@@ -1259,7 +1214,6 @@ $qrf = new QueryReportFormatter(
|
|||||||
Quoter => $q,
|
Quoter => $q,
|
||||||
ExplainAnalyzer => $ex,
|
ExplainAnalyzer => $ex,
|
||||||
);
|
);
|
||||||
$qrf->set_report_formatter(report=>'profile', formatter=>$report);
|
|
||||||
my $output = output(
|
my $output = output(
|
||||||
sub { $qrf->print_reports(
|
sub { $qrf->print_reports(
|
||||||
reports => [qw(rusage date files header query_report profile)],
|
reports => [qw(rusage date files header query_report profile)],
|
||||||
@@ -1323,11 +1277,6 @@ foreach my $event ( @$events ) {
|
|||||||
$ea->aggregate($event);
|
$ea->aggregate($event);
|
||||||
}
|
}
|
||||||
$ea->calculate_statistical_metrics();
|
$ea->calculate_statistical_metrics();
|
||||||
$report = new ReportFormatter(
|
|
||||||
line_width => 82,
|
|
||||||
extend_right => 1,
|
|
||||||
);
|
|
||||||
$qrf->set_report_formatter(report=>'profile', formatter=>$report);
|
|
||||||
ok(
|
ok(
|
||||||
no_diff(
|
no_diff(
|
||||||
sub {
|
sub {
|
||||||
@@ -1376,11 +1325,6 @@ foreach my $event ( @$events ) {
|
|||||||
$ea->aggregate($event);
|
$ea->aggregate($event);
|
||||||
}
|
}
|
||||||
$ea->calculate_statistical_metrics();
|
$ea->calculate_statistical_metrics();
|
||||||
$report = new ReportFormatter(
|
|
||||||
line_width => 82,
|
|
||||||
extend_right => 1,
|
|
||||||
);
|
|
||||||
$qrf->set_report_formatter(report=>'prepared', formatter=>$report);
|
|
||||||
ok(
|
ok(
|
||||||
no_diff(
|
no_diff(
|
||||||
sub {
|
sub {
|
||||||
|
@@ -1,58 +0,0 @@
|
|||||||
|
|
||||||
# Profile
|
|
||||||
# Rank Query ID Response time Calls R/Call Apdx V/M EXPLAIN Item
|
|
||||||
# ==== ================== ============= ===== ====== ==== ===== ======= =========
|
|
||||||
# 1 0x46F81B022F1AD76B 0.0003 100.0% 1 0.0003 NS 0.00 TF>aI SELECT t
|
|
||||||
# MISC 0xMISC 0.0003 100.0% 1 0.0003 NS 0.0 MISC <1 ITEMS>
|
|
||||||
|
|
||||||
# Query 1: 0 QPS, 0x concurrency, ID 0x46F81B022F1AD76B at byte 0 ________
|
|
||||||
# Scores: Apdex = NS [0.0]*, V/M = 0.00
|
|
||||||
# EXPLAIN sparkline: TF>aI
|
|
||||||
# Query_time sparkline: | ^ |
|
|
||||||
# Time range: all events occurred at 2009-12-08 09:23:49.637394
|
|
||||||
# Attribute pct total min max avg 95% stddev median
|
|
||||||
# ============ === ======= ======= ======= ======= ======= ======= =======
|
|
||||||
# Count 100 1
|
|
||||||
# Exec time 100 286us 286us 286us 286us 286us 0 286us
|
|
||||||
# Query size 100 90 90 90 90 90 0 90
|
|
||||||
# String:
|
|
||||||
# cmd Query
|
|
||||||
# Databases qrf
|
|
||||||
# Query_time distribution
|
|
||||||
# 1us
|
|
||||||
# 10us
|
|
||||||
# 100us ################################################################
|
|
||||||
# 1ms
|
|
||||||
# 10ms
|
|
||||||
# 100ms
|
|
||||||
# 1s
|
|
||||||
# 10s+
|
|
||||||
# Tables
|
|
||||||
# SHOW TABLE STATUS FROM `qrf` LIKE 't'\G
|
|
||||||
# SHOW CREATE TABLE `qrf`.`t`\G
|
|
||||||
# EXPLAIN /*!50100 PARTITIONS*/
|
|
||||||
select t1.i from t as t1 join t as t2 where t1.i < t2.i and t1.v is not null order by t1.i\G
|
|
||||||
# *************************** 1. row ***************************
|
|
||||||
# id: 1
|
|
||||||
# select_type: SIMPLE
|
|
||||||
# table: t1
|
|
||||||
# partitions: NULL
|
|
||||||
# type: ALL
|
|
||||||
# possible_keys: PRIMARY
|
|
||||||
# key: NULL
|
|
||||||
# key_len: NULL
|
|
||||||
# ref: NULL
|
|
||||||
# rows: 4
|
|
||||||
# Extra: Using where; Using temporary; Using filesort
|
|
||||||
# *************************** 2. row ***************************
|
|
||||||
# id: 1
|
|
||||||
# select_type: SIMPLE
|
|
||||||
# table: t2
|
|
||||||
# partitions: NULL
|
|
||||||
# type: index
|
|
||||||
# possible_keys: PRIMARY
|
|
||||||
# key: PRIMARY
|
|
||||||
# key_len: 4
|
|
||||||
# ref: NULL
|
|
||||||
# rows: 4
|
|
||||||
# Extra: Using where; Using index; Using join buffer
|
|
@@ -1,56 +0,0 @@
|
|||||||
|
|
||||||
# Profile
|
|
||||||
# Rank Query ID Response time Calls R/Call Apdx V/M EXPLAIN Item
|
|
||||||
# ==== ================== ============= ===== ====== ==== ===== ======= =========
|
|
||||||
# 1 0x46F81B022F1AD76B 0.0003 100.0% 1 0.0003 NS 0.00 TF>aI SELECT t
|
|
||||||
# MISC 0xMISC 0.0003 100.0% 1 0.0003 NS 0.0 MISC <1 ITEMS>
|
|
||||||
|
|
||||||
# Query 1: 0 QPS, 0x concurrency, ID 0x46F81B022F1AD76B at byte 0 ________
|
|
||||||
# Scores: Apdex = NS [0.0]*, V/M = 0.00
|
|
||||||
# EXPLAIN sparkline: TF>aI
|
|
||||||
# Query_time sparkline: | ^ |
|
|
||||||
# Time range: all events occurred at 2009-12-08 09:23:49.637394
|
|
||||||
# Attribute pct total min max avg 95% stddev median
|
|
||||||
# ============ === ======= ======= ======= ======= ======= ======= =======
|
|
||||||
# Count 100 1
|
|
||||||
# Exec time 100 286us 286us 286us 286us 286us 0 286us
|
|
||||||
# Query size 100 90 90 90 90 90 0 90
|
|
||||||
# String:
|
|
||||||
# cmd Query
|
|
||||||
# Databases qrf
|
|
||||||
# Query_time distribution
|
|
||||||
# 1us
|
|
||||||
# 10us
|
|
||||||
# 100us ################################################################
|
|
||||||
# 1ms
|
|
||||||
# 10ms
|
|
||||||
# 100ms
|
|
||||||
# 1s
|
|
||||||
# 10s+
|
|
||||||
# Tables
|
|
||||||
# SHOW TABLE STATUS FROM `qrf` LIKE 't'\G
|
|
||||||
# SHOW CREATE TABLE `qrf`.`t`\G
|
|
||||||
# EXPLAIN /*!50100 PARTITIONS*/
|
|
||||||
select t1.i from t as t1 join t as t2 where t1.i < t2.i and t1.v is not null order by t1.i\G
|
|
||||||
# *************************** 1. row ***************************
|
|
||||||
# id: 1
|
|
||||||
# select_type: SIMPLE
|
|
||||||
# table: t1
|
|
||||||
# type: ALL
|
|
||||||
# possible_keys: PRIMARY
|
|
||||||
# key: NULL
|
|
||||||
# key_len: NULL
|
|
||||||
# ref: NULL
|
|
||||||
# rows: 4
|
|
||||||
# Extra: Using where; Using temporary; Using filesort
|
|
||||||
# *************************** 2. row ***************************
|
|
||||||
# id: 1
|
|
||||||
# select_type: SIMPLE
|
|
||||||
# table: t2
|
|
||||||
# type: index
|
|
||||||
# possible_keys: PRIMARY
|
|
||||||
# key: PRIMARY
|
|
||||||
# key_len: 4
|
|
||||||
# ref: NULL
|
|
||||||
# rows: 4
|
|
||||||
# Extra: Using where; Using index
|
|
@@ -1,56 +0,0 @@
|
|||||||
|
|
||||||
# Profile
|
|
||||||
# Rank Query ID Response time Calls R/Call V/M Item
|
|
||||||
# ==== ================== ============= ===== ====== ===== =========
|
|
||||||
# 1 0x46F81B022F1AD76B 0.0003 100.0% 1 0.0003 0.00 SELECT t
|
|
||||||
# MISC 0xMISC 0.0003 100.0% 1 0.0003 0.0 <1 ITEMS>
|
|
||||||
|
|
||||||
# Query 1: 0 QPS, 0x concurrency, ID 0x46F81B022F1AD76B at byte 0 ________
|
|
||||||
# Scores: V/M = 0.00
|
|
||||||
# Time range: all events occurred at 2009-12-08 09:23:49.637394
|
|
||||||
# Attribute pct total min max avg 95% stddev median
|
|
||||||
# ============ === ======= ======= ======= ======= ======= ======= =======
|
|
||||||
# Count 100 1
|
|
||||||
# Exec time 100 286us 286us 286us 286us 286us 0 286us
|
|
||||||
# Query size 100 90 90 90 90 90 0 90
|
|
||||||
# String:
|
|
||||||
# cmd Query
|
|
||||||
# Databases qrf
|
|
||||||
# Query_time distribution
|
|
||||||
# 1us
|
|
||||||
# 10us
|
|
||||||
# 100us ################################################################
|
|
||||||
# 1ms
|
|
||||||
# 10ms
|
|
||||||
# 100ms
|
|
||||||
# 1s
|
|
||||||
# 10s+
|
|
||||||
# Tables
|
|
||||||
# SHOW TABLE STATUS FROM `qrf` LIKE 't'\G
|
|
||||||
# SHOW CREATE TABLE `qrf`.`t`\G
|
|
||||||
# EXPLAIN /*!50100 PARTITIONS*/
|
|
||||||
select t1.i from t as t1 join t as t2 where t1.i < t2.i and t1.v is not null order by t1.i\G
|
|
||||||
# *************************** 1. row ***************************
|
|
||||||
# id: 1
|
|
||||||
# select_type: SIMPLE
|
|
||||||
# table: t1
|
|
||||||
# partitions: NULL
|
|
||||||
# type: index
|
|
||||||
# possible_keys: PRIMARY
|
|
||||||
# key: PRIMARY
|
|
||||||
# key_len: 4
|
|
||||||
# ref: NULL
|
|
||||||
# rows: 4
|
|
||||||
# Extra: Using where
|
|
||||||
# *************************** 2. row ***************************
|
|
||||||
# id: 1
|
|
||||||
# select_type: SIMPLE
|
|
||||||
# table: t2
|
|
||||||
# partitions: NULL
|
|
||||||
# type: ALL
|
|
||||||
# possible_keys: PRIMARY
|
|
||||||
# key: NULL
|
|
||||||
# key_len: NULL
|
|
||||||
# ref: NULL
|
|
||||||
# rows: 4
|
|
||||||
# Extra: Range checked for each record (index map: 0x1)
|
|
Reference in New Issue
Block a user