#!/usr/bin/perl -w require 5.003; use strict; # compares results labeled with label_results # see also: label_results, plot_tables 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 = ( table_plotter => undef(), table_plotter_opts => undef(), ); my %SavedOpts = %Opts; my @RawMeas = qw( product.price rep.rate hit.rptm.mean rep.rptm.mean miss.rptm.mean hit.ratio.obj rep.rptm.mean.save.ratio hit.rate_price.ratio rep.rate_price.ratio miss.time_till_first hit.time_till_first cache.age.max ); my @RawMeasAll = qw( product.price rep.rate hit.rate hit.rptm.mean rep.rptm.median rep.rptm.mean miss.rptm.mean hit.ratio.obj hit.ratio.byte rep.rptm.mean.save.ratio hit.rate_price.ratio rep.rate_price.ratio miss.time_till_first hit.time_till_first cache.age.max err_xact.ratio cache.rack.space hit.rate_rack.space.ratio rep.rate_rack.space.ratio ); my %MetaGraphs = ( 'rep.rate' => 'Peak Throughput', 'hit.rate' => 'Peak Hit Rate', 'product.price' => 'Total Price', 'rep.rptm.mean' => 'Mean Response Time', 'rep.rptm.median' => 'Median Response Time', 'hit.rptm.mean' => 'Hit Response Time', 'miss.rptm.mean' => 'Miss Response Time', 'hit.ratio.obj' => 'Document Hit Ratio', 'hit.ratio.byte' => 'Byte Hit Ratio', 'rep.rptm.mean.save.ratio' => 'Mean Response Time Improvement', 'rep.rate_price.ratio' => 'Throughput / Price', 'hit.rate_price.ratio' => 'Hit rate / Price', 'hit.time_till_first' => 'Time till first hit', 'miss.time_till_first' => 'Time till first miss', 'cache.age.max' => 'Cache Age', 'rep.rate_rack.space.ratio' => 'Throughput / Rack Space', 'hit.rate_rack.space.ratio' => 'Hit rate / Rack Space', ); my @CfgMeas = qw( human_name cache.price cache.availability cache.units cache.cpus cache.ram cache.disks cache.nics cache.rack.space cache.size.bytes cache.software ); # globals my @Labels = (); my $MetaDir; my $CmpName = 'all'; my $TmpFName; my $ConfSep = ' · '; &init(); exit(&main()); sub main { foreach my $label (@Labels) { &curLabel($label); my $stats = &ReadDbase(&FullInFName($label, "clt.All.lx")); &importObjects($stats); my $facts = &ReadDbase(&FullInFName($label, "facts.lx")); &importObjects($facts); &addMoreObjects(); &curLabel(undef()); } # order runs based on request rate # @Labels = sort { &objVal("$a\::rep.rate") <=> &objVal("$b\::rep.rate") } @Labels; # alphabetical order @Labels = sort cmpLabelsAlph @Labels; if (1) { $TmpFName = &FullOutFName($CmpName, "tmp-$$", '.dat'); my $fname = &FullOutFName($CmpName, 'index', '.html'); &openReport($fname, $CmpName); &buildExecSumTable(); &buildRawSumTable(); &plotGraphs(); &buildCfgSumTable(); &closeReport(); unlink($TmpFName); warn("$0: comparison: file:$fname\n"); } &buildComments(); &buildMetaFiles(); my $metaFName = &FullOutFName($MetaDir, 'index', '.html'); warn("$0: meta-dir: file:$metaFName\n"); return 0; } sub buildExecSumTable { my $pfx = "$MetaDir/all"; # "polyrep/$CmpName"; my $table = < Product Total
Price

(US\$) Peak
Tput

(req/sec) Response Time
(sec) Savings
(%) \$1,000
can buy Minutes
Till First Cache
Age

(hour) Hit All Miss Doc Time hit/sec req/sec Miss Hit HTML foreach my $label (sort cmpLabelsAlph @Labels) { $table .= sprintf('%s', &benchRef($label, $MetaDir)); foreach my $meas (@RawMeas) { my $val = &objVal("$label\::$meas"); if (!defined $val) { if (my $special = &objVal("$label\::${meas}_special_")) { $val = $special; } else { $val = 'n/a'; } } elsif ($meas =~ /\.price/) { $val =~ s/(\d)(\d\d\d)$/$1,$2/; } elsif ($meas =~ /hit\.ratio/) { $val = sprintf('%.1f', $val); } elsif ($meas =~ /\bsave\b/) { $val = sprintf('%.1f', $val); } elsif ($meas =~ /rptm/) { $val = sprintf('%.2f', $val/1000); } elsif ($meas =~ /time_till_first/) { $val = sprintf('%.1f', $val/(60*1000)); } elsif ($meas =~ /cache.age/) { $val = sprintf('%.1f', $val/(3600*1000)); } elsif ($meas =~ /\.rate/) { $val = sprintf('%.0f', &roundVal($val)); } else { $val = sprintf('%.0f', $val); # default case } $table .= ''; $table .= $val; $table .= "\n"; } } $table .= ''; # &openSection('Summary'); my $localTable = $table; $localTable =~ s|$MetaDir|..|g; print($OFile "$localTable"); # &closeSection(); # stand-alone "include" file my $IncFName = &FullOutFName($CmpName, 'execsum', '.inc'); my $IncFile = new FileHandle(">$IncFName"); print($IncFile $table); undef $IncFile; } sub buildCfgSumTable { my $pfx = "."; # "polyrep/$CmpName"; my $table = < Label Full product name Price
(US\$) Avail
able Cache units CPUs
(n${ConfSep}MHz) RAM
(MB) Cache disks
(n${ConfSep}GB) NICs
(n${ConfSep}Mbps) Rack
Space
(RU) Cache
(GB) Software HTML foreach my $label (sort cmpLabelsAlph @Labels) { $table .= sprintf( '%s', &benchRef($label, $MetaDir, 'conf.html')); foreach my $meas (@CfgMeas) { my $val = &objVal("$label\::$meas"); if (!defined $val) { if (my $special = &objVal("$label\::${meas}_special_")) { $val = $special; } else { $val = $meas =~ /\.space/ ? 'n/m' : 'n/a'; } } elsif ($meas =~ /\.price/) { $val =~ s/(\d)(\d\d\d)$/$1,$2/; } elsif ($meas =~ /\.ram/) { $val = sprintf('%d', $val/1024/1024); } elsif ($meas =~ /\.bytes/) { $val = sprintf('%d', $val/1024/1024/1024); } elsif ($val =~ /\bx\b.*\@/) { my @kinds = &splitKindVals($val); $val = join('
', map { sprintf("%d$ConfSep%02d", ${$_}[0], ${$_}[2]) } @kinds); } elsif ($val =~ /^"(.*)"$/) { $val = $1; $val = "$val" if $meas =~ /human_name|software/; } else { $val = sprintf('%.0f', $val); # default case } $table .= $val =~ /^\d/ ? '' : ''; $table .= $val; $table .= "\n"; } } $table .= ''; # &openSection('Configuration'); my $localTable = $table; $localTable =~ s|$MetaDir|..|g; print($OFile "$localTable"); # &closeSection(); # stand-alone "include" file my $IncFName = &FullOutFName($CmpName, 'config.all', '.inc'); my $IncFile = new FileHandle(">$IncFName"); print($IncFile $table); undef $IncFile; } sub buildComments { my $fname = &FullOutFName($CmpName, 'comments.all', '.inc'); my $f = new FileHandle(">$fname"); foreach my $label (sort cmpLabelsAlph @Labels) { &curLabel($label); my $vname = &substNamesInHTML('[vendor.comments.label||human_label]'); $vname =~ s/^"|"$//g; my $url = &objVal('vendor.comments.url') || ''; $url =~ s/^"|"$//g; my $urlImage = $url; my $h = $url; $h =~ s|^[^/]+//||; $urlImage =~ $h unless $h =~ m|/.|; $urlImage =~ s|/$|| unless $h =~ m|/.|; my $comments = &objBody('vendor.comments.text'); next unless defined $comments; $comments =~ s/^"|"$//g; $comments =~ s|^\s*$|

|mg; # $comments = sprintf('TBA (%d)', length($comments)); $f->print("\n\n\n"); $f->printf('

%s
%s
', $vname, $url, $urlImage); $f->printf('

%s

', $comments); &curLabel(undef()); } $f->close(); } sub buildRawSumTable { warn("$0: building tmp exchange file: $TmpFName\n"); my $TmpFile = new FileHandle(">$TmpFName"); # create header for temporary data file print($TmpFile '#label'); foreach my $meas (@RawMeasAll) { print($TmpFile " $meas"); } print($TmpFile " human_name\n"); # dump table rows foreach my $label (@Labels) { # skip unless rep.rate is known (skip failed entries) next unless defined &objVal("$label\::rep.rate"); print($TmpFile $label); foreach my $meas (@RawMeasAll) { my $val = &objVal("$label\::$meas"); if (defined $val) { $val /= 60*1000 if $meas =~ /time_till_first/; $val /= 3600*1000 if $meas =~ /^cache\.age/; printf($TmpFile "\t %.2f", $val); } else { printf($TmpFile "\t ?"); } } my $hlabel = &label2hlabel($label); printf($TmpFile "\t \"%s\"", $hlabel); print($TmpFile "\n"); } undef $TmpFile; } sub buildMetaFiles { my $mf = &openMetaFile('index.html', 'Tested Products'); my $fmd = &FullDir($MetaDir); my $fad = &FullDir($CmpName); &System("cp -pr $fad $fmd/") == 0 or die($!); $mf->printf('

Auto-generated graphs and tables comparing all tested'. ' products side-by-side are available.'. ' Below are links to individual product pages.

'); $mf->print("
\n"); foreach my $label (@Labels) { my $bdir = &benchMetaDir($label); my $fbd = &FullDir($label); &System("rm -rf $fmd/$bdir"); &System("cp -pr $fbd $fmd/$bdir"); &buildBenchMetas($label); $mf->printf("\n", &benchRef($label), &cardRef($label), &sheetRef($label), &confRef($label)); } $mf->print("
%s%s%s%s
\n"); &closeMetaFile($mf); &buildGraphPairMetas(); } sub buildGraphPairMetas { while (my ($name, $title) = each %MetaGraphs) { my $mf = &openMetaFile("$CmpName/$name.html", $title); my $fmt = '
%s
'; $mf->printf($fmt, $name, $title); $mf->printf($fmt, "$name.by-meas", $title); &closeMetaFile($mf); } } sub buildBenchMetas { my $label = shift; &buildBenchIndex($label); &buildBenchConf($label); } sub buildBenchIndex { my $label = shift; my $hlabel = label2hlabel($label); my $fmd = &FullDir($MetaDir); my $bdir = &benchMetaDir($label); my $idx = &openMetaFile("$bdir/index.html", $hlabel); $idx->print("

The following information is available about $hlabel:"); $idx->printf("

\n", '2-page auto-generated performance card', 'detailed auto-generated performance sheet', 'entry configuration details'); &closeMetaFile($idx); } sub buildBenchConf { my $label = shift; &curLabel($label); my $hlabel = label2hlabel($label); my $fmd = &FullDir($MetaDir); my $bdir = &benchMetaDir($label); my $conf = &openMetaFile("$bdir/conf.html", $hlabel); my $html = ''; $html .= "

$hlabel

\n"; $html .= "\n"; $html .= "\n"; my ($val, $n, $kind, $major, $minor); # cache config $html .= ""; # netgear $html .= sprintf("", &freeText2Html('netgear.comments')); $html .= "\n"; # total $html .= "\n"; $html .= "
ComponentPrice (US\$)

[human_name]:

    \n"; $html .= "
  • [cache.units#%d] cache unit(s)\n"; $val = &objVal("cache.cpus"); foreach my $k (&splitKindVals($val)) { ($n, $kind, $major) = @{$k}; $html .= sprintf("
  • (%d) %s %dMHz CPU\n", $n, $kind, $major); } $val = &objVal("cache.nics"); foreach my $k (&splitKindVals($val)) { ($n, $kind, $major, $minor) = @{$k}; $html .= sprintf("
  • (%d) %s %dMbps NIC\n", $n, $kind, $major); } $val = &objVal("cache.disks"); foreach my $k (&splitKindVals($val)) { ($n, $kind, $major, $minor) = @{$k}; $html .= sprintf("
  • (%d) %s %dRPM %.1fGB disk\n", $n, $kind, $minor, $major); } $html .= "
  • [int(cache.ram/1024/1024)#%d]MB RAM\n"; $html .= "
  • Software: [cache.software]\n"; $html .= &freeText2Html('cache.comments'); $html .= "
[cache.price || cache.price_special_]

Network gear:

    %s
[netgear.price]
Total:[product.price || product.price_special_]

\n"; # config changes if (my $changes = &freeText2Html('product.changes')) { $html .= '

According to the participant, the following changes were applied to the configuration above:'; $html .= "

\n"; } else { $html .= '

According to the participant, no changes were applied to the configuration above.

'; } if (my $pic = &objVal('cache.image.fname')) { $pic =~ s/^"|"$//g; my $dir = '../../pics'; $html .= sprintf( '

a picture taken during the tests

', $dir, $pic, $dir, $pic); } $conf->print(&substNamesInHTML($html)); &closeMetaFile($conf); &curLabel(undef()); } sub openMetaFile { my ($fname, $title) = @_; $fname = &FullOutFName($MetaDir, $fname); my $mf = new FileHandle(">$fname") or die("cannot open `$fname': $!; stopped"); $mf->printf("%s\n", $title); $mf->print("\n"); warn("meta-file: file:$fname\n"); return $mf; } sub closeMetaFile { my $mf = shift; $mf->print("\n"); $mf->close(); } sub benchHref { my ($label, $level, $fname) = @_; die("$level, $label, $fname") unless defined $label; $level = '.' unless defined $level; $fname = 'index.html' unless defined $fname; return sprintf('%s/%s/%s', $level, &benchMetaDir($label), $fname); } sub benchRef { my ($label, $level, $fname) = @_; die("$level, $label, $fname") unless defined $label; $level = '.' unless defined $level; $fname = 'index.html' unless defined $fname; return sprintf('%s', benchHref(@_), &label2hlabel($label)); } sub cardRef { my ($label, $level, $text) = @_; die("$level, $label, $text") unless defined $label; $level = '.' unless defined $level; my $res = ''; $res .= '[ ' unless defined $text; $res .= sprintf('%s', $level, &benchMetaDir($label), defined $text ? $text : 'card'); $res .= ' ]' unless defined $text; return $res; } sub sheetRef { my ($label, $level, $text) = @_; die("$level, $label, $text") unless defined $label; $level = '.' unless defined $level; my $res = ''; $res .= '[ ' unless defined $text; $res .= sprintf('%s', $level, &benchMetaDir($label), defined $text ? $text : 'sheet'); $res .= ' ]' unless defined $text; return $res; } sub confRef { my ($label, $level, $text) = @_; die("$level, $label, $text") unless defined $label; $level = '.' unless defined $level; my $res = ''; $res .= '[ ' unless defined $text; $res .= sprintf('%s', $level, &benchMetaDir($label), 'conf.html', defined $text ? $text : 'conf'); $res .= ' ]' unless defined $text; return $res; } sub benchMetaDir { my $label = shift; my ($bdir) = ($label =~ /^(b\d+)/); return $bdir if defined $bdir; my ($dir) = ($label =~ m|([^/]+)$|); return $dir if defined $dir; return $label; } sub plotGraphs { while (my ($name, $title) = each %MetaGraphs) { &plotGraph($name, $title); } } sub plotGraph { my ($meas, $title) = @_; my $options = $Opts{table_plotter_opts}; $options = '' unless defined $options; # &openSection($title); my $fname = &callPlotter($meas, $title); my $sortedFName = &callPlotter($meas, $title, $meas); printf($OFile '

', $fname, $sortedFName); # &closeSection(); } sub callPlotter { my ($meas, $title, $order) = @_; my $options = $Opts{table_plotter_opts}; $options = '' unless defined $options; my $fname = "$meas"; $fname .= '.by-meas' if defined $order; $fname .= '.png'; my $sortOpt = defined $order ? "--sort_by $order" : ''; my $fullFName = &FullOutFName($CmpName, $fname); &System(sprintf('%s %s --out_name %s --plot_title "%s" %s %s %s', $Opts{table_plotter}, $options, $fullFName, $title, $sortOpt, $meas, $TmpFName)); return $fname; } sub addMoreObjects() { &defineObj('product.price', 'cache.price + netgear.price'); &defineObj('nopxy.rptm.mean', '2800'); &defineObj('rep.rptm.mean.save', 'nopxy.rptm.mean - rep.rptm.mean'); &defineObj('rep.rptm.mean.save.ratio', '100*rep.rptm.mean.save/nopxy.rptm.mean'); &defineObj('rep.rate_price.ratio', '1000*rep.rate/product.price'); &defineObj('hit.rate_price.ratio', '1000*hit.rate/product.price'); &defineObj('cache.age.max', 'cache.size.bytes/fill.bw*1000'); &defineObj('rep.rate_rack.space.ratio', 'rep.rate/cache.rack.space'); &defineObj('hit.rate_rack.space.ratio', 'hit.rate/cache.rack.space'); } sub cmpLabelsAlph($$) { my $l1 = uc(&label2hlabel($a)); my $l2 = uc(&label2hlabel($b)); # make sure that all numbers are long enough to compare alphabetically $l1 =~ s/(\d+)/sprintf('%010d', $1)/eg; $l2 =~ s/(\d+)/sprintf('%010d', $1)/eg; return $l1 cmp $l2; # my @l1 = split(/-/, uc(&label2hlabel($a))); # my @l2 = split(/-/, uc(&label2hlabel($b))); # while (@l1 && @l2) { # my $l1 = shift @l1; # my $l2 = shift @l2; # if ($l1 =~ /^\d/) # } # return uc(&label2hlabel($a)) cmp uc(&label2hlabel($b)); } sub splitKindVals { my $val = shift; return () unless defined $val; my @res = (); my @kinds = split(/\s*,+\s*/, $val); foreach my $kind (@kinds) { my ($n, $k, $major, $minor) = ($kind =~ /^"?(\d+)\s*x\s*(.*?)\s*\@\s*(\d+(?:\.\d*)?)\s*(?:\:\s*(\d+(?:\.\d*)?)\s*)?"?$/); die("cannot parse `$val' as `n x kind @ major : minor, ...' value, stopped") unless defined $major; push @res, [$n, $k, $major, $minor]; } return (@res); } sub roundVal { my $val = shift; my %fudges = ( 1 => -1, 2 => -2, 4 => +1, 6 => -1, 8 => +2, 9 => +1, ); return $val if $val < 100; $val = int($val); my $ldigit = $val - 10 * int($val / 10); return $val unless defined $fudges{$ldigit}; return $val + $fudges{$ldigit}; } sub init { die(&usage()) unless &ParseOpts(\%Opts, \@Labels, @ARGV) && @Labels > 2; $MetaDir = shift @Labels; $CmpName = shift @Labels; } sub usage { return "usage: $0 [options]