#!/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>&nbsp;</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