#!/usr/bin/perl -w
require 5.003;
use strict;
# plots traces labeled with label_results
# see also: label_results, plot_traces
if (`pwd` =~ 'ReportGen') {
use lib '..';
}
use FileHandle;
#use HTML::Entities;
use ReportGen::Opts;
use ReportGen::Globs;
use ReportGen::ObjDbase;
use ReportGen::ObjManip;
use ReportGen::RepFormat;
# Configuration
my %Opts = (
trace_plotter => undef(),
trace_plotter_opts => undef(),
load_balancer => undef(),
);
my %SavedOpts = %Opts;
my @BaseTypes = qw(
hit miss
ims.sc200 ims.sc304
redired_req rep_to_redir
head post put abort
cachable uncachable
fill basic ims reload rep
);
# globals
my @Labels;
&init();
exit(&main());
sub main {
if ($Opts{load_balancer}) {
@BaseTypes = grep { !/^(hit|miss|fill)$/ } @BaseTypes;
}
foreach my $label (@Labels) {
makeReport($label);
}
return 0;
}
sub makeReport {
my $label = &curLabel(shift);
&clearObjects();
my $stats = &ReadDbase(&FullInFName($label, "clt.All.lx"));
&importObjects($stats);
my $facts = &ReadDbase(&FullInFName($label, "facts.lx"), 'may fail');
&importObjects($facts) if $facts;
&checkObjects();
my $fname = &FullOutFName($label, 'index', '.html');
my $hname = label2hname($label);
&openReport($fname, $hname);
&reportAggrStats();
&reportTraces();
#&reportDistrib();
&closeReport();
warn("$0: local report URL is file:$fname\n");
&curLabel(undef());
}
sub reportAggrStats {
&openSection('Executive summary');
&reportExSummary();
&closeSection();
&openSection('Engineer summary');
print($OFile &substNamesInHTML('
<p>[log.count||"Unknown number of"] logs
were used to generate this report.</p>'));
&reportTput();
&reportHits() unless $Opts{load_balancer};
&reportChb();
&reportTimes();
&reportWait();
&reportStreams();
&reportConns();
&reportSizes();
&reportClasses();
&reportErrors();
# print some warnings if needed
my @wobjs = (&getWarnObjs());
if (grep { $_->{warn_txt} } @wobjs) {
my $hasDiff = 0;
print($OFile "<p>Potential <font color=\"#FF0000\">problems</font>:\n<ol>\n");
foreach my $obj (@wobjs) {
next unless $obj->{warn_txt};
printf($OFile "\t<li><a name=\"%s\"></a>%s\n",
$obj->{warn_key}, $obj->{warn_txt});
$hasDiff ||= $obj->{warn_txt} =~ /\bdiffers\b/;
}
print($OFile "</ol>\n");
print($OFile "<ul><li>... where A differs from B by X% means X = 100*(A-B)/B.</p></ul>\n")
if $hasDiff;
}
&closeSection();
}
sub reportExSummary {
my $html = <<HTML;
<p><blockquote>
<table border=0 cellpadding=1 cellspacing=1>
<!-- <tr bgcolor="#BBBBBB"><th>Metric</th><th colspan=2>Value</th></tr> -->
<tr><td align=left>Throughput:</td><td width=33% align=right>[rep.rate]</td><td>rep/sec</td></tr>
<tr><td align=left>Response time:</td><td align=right>[rep.rptm.mean]</td><td>msec</td></tr>
<tr><td align=right>- misses:</td><td align=right>[miss.rptm.mean]</td><td>msec</td></tr>
<tr><td align=right>- hits:</td><td align=right>[hit.rptm.mean]</td><td>msec</td></tr>
<tr><td align=left>Hit Ratio:</td><td align=right>[hit.ratio.obj]</td><td>%</td></tr>
<tr><td align=left>Errors:</td><td align=right>[err_xact.ratio]</td><td>%</td></tr>
<tr><td align=left>Duration:</td><td align=right>[duration/3600]</td><td>hour</td></tr>
</table>
<p>Phases: <small>[name]</small></p>
</blockquote></p>
HTML
if ($Opts{load_balancer}) {
$html =~ s|^.*misses:.*$|<!-- $& -->|m;
$html =~ s|^.*hits:.*$|<!-- $& -->|m;
$html =~ s|^.*Hit Ratio:.*$|<!-- $& -->|m;
}
print($OFile &substNamesInHTML($html));
}
sub reportTput {
my $html = <<TABLE;
<p><blockquote>
<table border=1 cellpadding=1 cellspacing=1>
<tr bgcolor="#BBBBBB">
<th>Load</th>
<th>Count<br>(xact/sec)</th>
<th>Volume<br>(Mbit/sec)</th>
</tr>
<tr><td align=left>Offered:</td>
<td align=right>[req.rate]</td>
<td align=right>[req.bw/$Mbit]</td>
</tr>
<tr><td align=left>Measured:</td>
<td align=right>[rep.rate]</td>
<td align=right>[rep.bw/$Mbit]</td>
</tr>
TABLE
if (&objVal('_have_icp_stats')) {
$html .= <<TABLE;
<tr><td align=left>ICP reqs:</td>
<td align=right>[icp.req.rate]</td>
<td align=right>[icp.req.bw/$Mbit]</td>
</tr>
<tr><td align=left>ICP reps:</td>
<td align=right>[icp.rep.rate]</td>
<td align=right>[icp.rep.bw/$Mbit]</td>
</tr>
TABLE
}
$html .= <<TABLE;
</table>
</blockquote></p>
TABLE
print($OFile &substNamesInHTML($html));
}
sub reportHits {
my $html = <<TABLE;
<p><blockquote>
<table border=1 cellpadding=1 cellspacing=1>
<tr bgcolor="#BBBBBB">
<th>Hit Ratios</th>
<th>DHR<br>(%)</th>
<th>BHR<br>(%)</th>
</tr>
<tr><td align=left>Offered:</td>
<td align=right>[offered.hit.ratio.obj]</td>
<td align=right>[offered.hit.ratio.byte]</td>
</tr>
<tr><td align=left>Measured:</td>
<td align=right>[hit.ratio.obj]</td>
<td align=right>[hit.ratio.byte]</td>
</tr>
TABLE
if (&objVal('_have_icp_stats')) {
$html .= <<TABLE;
<tr><td align=left>ICP:</td>
<td align=right>[icp.hit.ratio.obj]</td>
<td align=right>[icp.hit.ratio.byte]</td>
</tr>
TABLE
}
$html .= <<TABLE;
</table>
</blockquote></p>
TABLE
print($OFile &substNamesInHTML($html));
}
sub reportChb {
my $html = <<TABLE;
<p><blockquote>
<table border=1 cellpadding=1 cellspacing=1>
<tr bgcolor="#BBBBBB">
<th>Cachability Ratios</th>
<th>Count<br>(%)</th>
<th>Volume<br>(%)</th>
</tr>
<tr><td align=left>Measured:</td>
<td align=right>[cachable.ratio.obj]</td>
<td align=right>[cachable.ratio.byte]</td>
</tr>
</table>
</blockquote></p>
TABLE
print($OFile &substNamesInHTML($html));
}
sub reportTimes {
my @types = ( 'byte', @BaseTypes );
my @measures = qw( min median mean max );
push @types, map { "icp.$_" } qw ( hit miss rep ) if
&objVal('_have_icp_stats');
print $OFile <<TABLE;
<p><blockquote>
<table border=1 cellpadding=1 cellspacing=1>
<tr bgcolor="#BBBBBB">
<th rowspan=2>Response Times</th>
<th colspan=5>Response Time (msec)</th>
</tr>
<tr bgcolor="#BBBBBB">
<th>Min</th>
<th>Median</th>
<th>Mean</th>
<th>Max</th>
</tr>
TABLE
my $html = '';
foreach my $type (@types) {
$html .= "<tr><td align=left>$type</td>\n";
foreach my $meas (@measures) {
if ( $type eq 'byte' && $meas ne 'mean' ){
$html .= "\t<td> </td>";
} else {
$html .= "\t<td align=right>[$type.rptm.$meas]</td>\n";
}
}
$html .= "</tr>\n";
}
print($OFile &substNamesInHTML($html));
print $OFile <<TABLE;
</table>
</blockquote></p>
TABLE
}
sub reportWait {
my $html = <<TABLE;
<p><blockquote>
<table border=1 cellpadding=1 cellspacing=1>
<tr bgcolor="#BBBBBB">
<th>Wait Queue</th>
<th>requests</th>
</tr>
<tr><td>Enqueued:</td><td align=right>[wait.started]</td></tr>
<tr><td>Dequeued:</td><td align=right>[wait.finished]</td></tr>
<tr><td>Average length:</td><td align=right>[wait.level.mean]</td></tr>
</table></blockquote></p>
TABLE
print($OFile &substNamesInHTML($html));
}
sub reportStreams {
&reportStreamRates();
&reportStreamTotals();
}
sub reportStreamRates {
my $html = <<TABLE;
<p><blockquote>
<table border=1 cellpadding=1 cellspacing=1>
<tr bgcolor="#BBBBBB">
<th>Stream Rates</th>
<th>Count<br>(rep/sec)</th>
<th>Volume<br>(Mbit/sec)</th>
</tr>
TABLE
my @types = @BaseTypes;
push @types, map { "icp.$_" } qw ( hit miss rep ) if
&objVal('_have_icp_stats');
for my $type (@types) {
$html .= "
<tr><td align=left>$type</td>
<td align=right>[$type.rate]</td>
<td align=right>[$type.bw/$Mbit]</td>
</tr>\n";
}
$html .= '</table></blockquote></p>';
print($OFile &substNamesInHTML($html));
}
sub reportStreamTotals {
my $html = <<TABLE;
<p><blockquote>
<table border=1 cellpadding=1 cellspacing=1>
<tr bgcolor="#BBBBBB">
<th>Stream Totals</th>
<th>Count<br>(rep*10<sup><small>6</small></sup>)</th>
<th>Volume<br>(GByte)</th>
</tr>
TABLE
my @types = @BaseTypes;
push @types, map { "icp.$_" } qw ( hit miss rep ) if
&objVal('_have_icp_stats');
for my $type (@types) {
$html .= "
<tr><td align=left>$type</td>
<td align=right>[$type.size.count/1e6]</td>
<td align=right>[$type.size.sum/$GByte]</td>
</tr>\n";
}
$html .= '</table></blockquote></p>';
print($OFile &substNamesInHTML($html));
}
sub reportConns {
my $html = <<TABLE;
<p><blockquote>
<table border=1 cellpadding=1 cellspacing=1>
<tr bgcolor="#BBBBBB">
<th>Connection Length</th>
<th>Min</th>
<th>Mean</th>
<th>Max</th>
</tr>
<tr>
<td align=left>Use (xact/conn)</td>
<td align=right>[conn.use.min]</td>
<td align=right>[conn.use.mean]</td>
<td align=right>[conn.use.max]</td>
</tr>
<tr>
<td align=left>Life time (msec)</td>
<td align=right>[conn.ttl.min]</td>
<td align=right>[conn.ttl.mean]</td>
<td align=right>[conn.ttl.max]</td>
</tr>
</table>
</blockquote></p>
TABLE
print($OFile &substNamesInHTML($html));
}
sub reportSizes {
my @types = @BaseTypes;
my @measures = qw( min median mean max );
push @types, map { "icp.$_" } qw ( hit miss rep ) if
&objVal('_have_icp_stats');
print $OFile <<TABLE;
<p><blockquote>
<table border=1 cellpadding=1 cellspacing=1>
<tr bgcolor="#BBBBBB">
<th rowspan=2>Object Sizes</th>
<th colspan=5>Size (KB)</th>
</tr>
<tr bgcolor="#BBBBBB">
<th>Min</th>
<th>Median</th>
<th>Mean</th>
<th>Max</th>
</tr>
TABLE
my $html = '';
foreach my $type (@types) {
$html .= "<tr><td align=left>$type</td>\n";
foreach my $meas (@measures) {
$html .= "\t<td align=right>[$type.size.$meas/1024]</td>\n";
}
$html .= "</tr>\n";
}
print($OFile &substNamesInHTML($html));
print $OFile <<TABLE;
</table>
</blockquote></p>
TABLE
}
sub reportClasses {
my @types = @BaseTypes;
my @measures = qw( obj byte );
print $OFile <<TABLE;
<p><blockquote>
<table border=1 cellpadding=1 cellspacing=1>
<tr bgcolor="#BBBBBB">
<th rowspan=2>Object Class</th>
<th colspan=2>Contribution (%)</th>
</tr>
<tr bgcolor="#BBBBBB">
<th>Count</th>
<th>Volume</th>
</tr>
TABLE
my $html = '';
foreach my $type (@types) {
$html .= "<tr><td align=left>$type</td>\n";
foreach my $meas (@measures) {
$html .= "\t<td align=right>[$type.ratio.$meas]</td>\n";
}
$html .= "</tr>\n";
}
print($OFile &substNamesInHTML($html));
print $OFile <<TABLE;
</table>
</blockquote></p>
TABLE
}
sub reportErrors {
if (&objVal('_have_icp_stats')) {
print($OFile &substNamesInHTML(
"<p>ICP timeouts: [icp.timeout.count]".
" ([icp.timeout.ratio]% of all ICP requests)</p>\n"
));
}
# print errors table
my $tbl = &getTable('errors.tbl');
if (defined $tbl) {
my $lines = $tbl->{lines};
if ($lines && @{$lines}) {
print($OFile &substNamesInHTML(
"<p>Errors ([err_xact.ratio]% of all transactions):\n
<blockquote><pre>"));
foreach my $line (@{$lines}) {
print($OFile $line);
}
print($OFile "</pre></blockquote>\n");
}
}
}
sub checkObjects {
my $diffp;
$diffp = 2;
if (&exprDiffer('rep.rate', 'req.rate', \$diffp)) {
&setWarn('rep.rate', 'suspecious reply rate',
"Reply rate ([rep.rate]/sec) differs from the request rate
([req.rate]/sec) by <b>[$diffp]%</b>.");
}
if ($Opts{load_balancer}) {
if (&objVal('_have_cache_stats')) {
&setWarn('rep.rate', 'LB cached data',
'The report was configured for a no-caching
load balancing test, but logs indicate
presence of [hit.size.count] hits
([hit.ratio.obj]% DHR).');
}
} else {
$diffp = 3;
if (&exprDiffer('hit.ratio.obj', 'offered.hit.ratio.obj', \$diffp)) {
&setWarn('hit.ratio.obj', 'suspecious DHR',
"Measured document hit ratio ([hit.ratio.obj]%) differs
from the offered DHR ([offered.hit.ratio.obj]%)
by <b>[$diffp]%</b>.");
}
$diffp = 5;
if (&exprDiffer('hit.ratio.byte', 'offered.hit.ratio.byte', \$diffp)) {
&setWarn('hit.ratio.byte', 'suspecious BHR',
"Measured byte hit ratio ([hit.ratio.byte]%) differs
from the offered BHR ([offered.hit.ratio.byte]%)
by <b>[$diffp]%</b>.");
}
$diffp = 10;
if (&exprDiffer('hit.rptm.mean', 'miss.rptm.mean', \$diffp) && $diffp > 0) {
&setWarn('hit.rptm.mean', 'hit rptm too high',
"Hit response time ([hit.rptm.mean]s msec) is
<b>[$diffp]%</b> higher than miss response time
([miss.rptm.mean] msec).");
}
}
# $diffp = 0.01;
# my $wr = '100*wait.started/xact.started';
# if (&computeExpr("$wr > $diffp")) {
# &setWarn('wait.started', 'too many queued requests',
# "Quite a few ([$wr]% > $diffp%) requests
# got queued waiting for resources.");
# }
$diffp = 0.1;
if (&checkObj('err_xact.ratio') && &objVal('err_xact.ratio') > $diffp) {
&setWarn('err_xact.ratio', 'too many errors',
"Error ratio ([err_xact.ratio]%) exceeds [$diffp]%.");
}
my $minDur = $Opts{load_balancer} ? (1*3600) : (4*3600); # secs
if (checkObj('duration') && &objVal('duration') < $minDur) {
&setWarn('duration', 'too small sample',
"Experiment duration ([duration/60] minutes) is less than
[$minDur/60] minutes and may be too short.");
}
}
sub reportTraces {
&openSection('Traces');
my @rptmTrace = $Opts{load_balancer} ? qw( rep.rptm.mean ) :
qw( miss.rptm.mean rep.rptm.mean hit.rptm.mean );
&traceSmth('rates', [qw( req.rate rep.rate )]);
&traceSmth('rptms', [@rptmTrace]);
&traceSmth('byte_rptms', [qw( byte.rptm.mean )]);
&traceSmth('ratios', [qw( cachable.ratio.obj offered.hit.ratio.obj hit.ratio.obj )])
unless $Opts{load_balancer};
&traceSmth('conn_rates', [qw( conn.open.rate conn.estb.rate )]);
&traceSmth('xact_lvl', [qw( xact.level.mean wait.level.mean )]);
&traceSmth('conn_lvl', [qw( conn.estb.level.mean conn.open.level.mean )]);
&traceSmth('errors',[qw( err_xact.ratio err_xact.count )]);
&traceSmth('many', [qw( req.rate rep.rate rep.rptm.mean conn.open.rate conn.estb.rate)]);
if (&objVal('_have_icp_stats')) {
&traceSmth('icp_rates', [ map { "icp.$_" } qw( req.rate rep.rate )]);
&traceSmth('icp_rptms', [ map { "icp.$_" } qw( miss.rptm.mean rep.rptm.mean hit.rptm.mean )]);
&traceSmth('icp_touts', [ map { "icp.$_" } qw( timeout.count )]);
}
&closeSection();
}
sub traceSmth {
my ($name, $objects, $options) = @_;
$options = $Opts{trace_plotter_opts} unless defined $options;
$options = '' unless defined $options;
$options .= ' --plot_size 1,0.85' unless index('--plot_size ', $options) >= 0;
my $fname = "$name.png";
my $label = &curLabel();
my $fullFName = &FullOutFName($label, $fname);
&System(sprintf("%s %s --out_name %s %s",
$Opts{trace_plotter},
$options,
$fullFName,
join(' ', map { "$label:$_" } @{$objects})));
printf($OFile '<br><br><img src="%s">', $fname);
}
sub init {
die(&usage()) unless &ParseOpts(\%Opts, \@Labels, @ARGV) && @Labels;
$Opts{load_balancer} = defined $Opts{load_balancer} &&
$Opts{load_balancer} =~ /^(yes|1)$/;
}
sub usage {
return "usage: $0 [options] <label> ...\n" . &Opts2Str(\%SavedOpts);
}
syntax highlighted by Code2HTML, v. 0.9.1