package ReportGen::ObjManip;
use strict;
require Exporter;
#use Carp qw(cluck carp);
@ReportGen::ObjManip::ISA = qw ( Exporter );
# XXX: delete addObjects from below?!
@ReportGen::ObjManip::EXPORT = qw (
&curLabel &clearObjects &importObjects &addObjects &objExpr
&interpretTable &tableMedian &checkObj &getTable
&exprDiffer &expr2str &newObj &defineObj
&computeExpr &obj2StrByName &objVal &objBody
&substNamesInHTML &freeText2Html
&setWarn &getWarnObjs &label2hname &label2hlabel );
# &raw2expr &formatVal &formatWarnVal &num2str &isNum);
use ReportGen::Globs;
my $Label; # current default global label
my %Objs = (); # all known objects
my %Verbs = (); # all known verbatim objects
my %Tables = (); # all known tables
my %Undefs = (); # complain about indefiend symblos only once
sub curLabel {
$Label = shift if @_;
return $Label;
}
sub clearObjects {
%Objs = ();
%Tables = ();
}
sub importObjects {
my $stats = shift;
$Label = shift if @_;
die("importObjects called with undefined label, stopped") unless defined $Label;
while (my ($name, $obj) = each %{$stats}) {
#warn("$Label\::$name");
die($name) unless defined $obj;
if ($obj->{atom}) {
$Objs{"$Label\::$name"} = &newObj($name, &raw2expr($obj->{body}));
} elsif ($obj->{verb}) {
$Verbs{"$Label\::$name"} = $obj;
} else {
&interpretTable($obj);
$Tables{"$Label\::$name"} = $obj;
my $med = &tableMedian($obj);
if (defined $med) {
$name =~ s/\.hist$//;
&defineObj("$name.median", $med);
}
}
}
addObjects();
}
sub addObjects {
# internal objects
&defineObj('_warn_count', 0);
&defineObj('_have_icp_stats', &haveIcpStats());
&defineObj('_have_cache_stats', &haveCacheStats());
# cannot do this check: rep.rate is not defined here for some reason?
#return unless defined &objVal('rep.rate'); # skip failed entries
my @protos = ('');
my @extras = ();
push @protos, 'icp.';
if (&objVal('_have_icp_stats')) {
push @protos, 'icp.';
push @extras, ('icp.rep', 'icp.hit', 'icp.miss');
&defineObj('icp.req.size.count', 'icp.timeout.count + icp.rep.size.count');
&defineObj('icp.timeout.ratio', '100*icp.timeout.count/icp.req.size.count');
}
for my $proto (@protos) {
&defineObj("${proto}rep.ratio.obj", 100);
&defineObj("${proto}rep.ratio.byte", 100);
&defineObj("${proto}req.bw", "${proto}req.rate*${proto}rep.size.mean");
&defineObj("${proto}rep.bw", "${proto}rep.rate*${proto}rep.size.mean");
&defineObj("${proto}miss.ratio.obj", "100-${proto}hit.ratio.obj");
&defineObj("${proto}miss.ratio.byte", "100-${proto}hit.ratio.byte");
&defineObj("${proto}byte.rptm.mean", "${proto}rep.rptm.mean/${proto}rep.size.mean*1024");
}
&defineObj('uncachable.ratio.obj', "100-cachable.ratio.obj");
&defineObj('uncachable.ratio.byte', "100-cachable.ratio.byte");
&defineObj("conn.open.rate", "conn.open.started/duration");
&defineObj("conn.estb.rate", "conn.estb.started/duration");
my @obj_types = qw( fill ims.sc200 ims.sc304 basic reload
ims head put post abort redired_req rep_to_redir
hit miss cachable uncachable
);
# some of the objects are already defined; defineObj() should skip those
for my $type (@obj_types) {
&defineObj("$type.ratio.obj", "100*$type.size.count/rep.size.count");
&defineObj("$type.ratio.byte", "100*$type.size.sum/rep.size.sum");
}
# for my $type (qw( hit miss ims.sc200 ims.sc304 cachable uncachable fill basic reload ims rep), @extras) {
for my $type (@obj_types) {
&defineObj("$type.rate", "rep.rate*$type.ratio.obj/100");
&defineObj("$type.bw", "rep.bw*$type.ratio.byte/100");
for my $meas (qw( min median mean max )) {
&defineObj("$type.size.$meas");
&defineObj("$type.rptm.$meas");
}
}
}
sub haveCacheStats {
return
&objVal('hit.rptm.count?') ||
&objVal('hit.size.count?');
}
sub haveIcpStats {
return
&objVal('icp.hit.rptm.count?') ||
&objVal('icp.miss.rptm.count?') ||
&objVal('icp.timeout.count?');
}
sub interpretTable {
my $obj = shift;
my $lines = $obj->{lines};
return unless $lines && @{$lines};
my @lines = @{$lines};
# guess field names using last comment line only
my @fieldNames;
while (@lines && $lines[0] =~ /^\#/) {
my $fieldNames = shift @lines;
$fieldNames =~ s/^\#\s*//;
@fieldNames = split(/\s+/, $fieldNames);
}
return unless @fieldNames;
$obj->{columns} = {};
for(my $c = 0; $c <= $#fieldNames; ++$c) {
my $name = $fieldNames[$c];
my @column;
foreach my $line (@lines) {
$line =~ s/^\s+//;
push @column, (split /\s+/, $line)[$c];
}
$obj->{columns}->{$name} = [ @column ];
}
}
sub tableMedian {
my $obj = shift;
return undef() unless $obj;
foreach my $name (qw( count min max )) {
return undef() unless $obj->{columns}->{$name};
}
my $counts = $obj->{columns}->{count};
# find total count
my $tot = 0;
foreach (@{$counts}) {
$tot += $_;
}
# find median
my $sum = 0;
for(my $c = 0; $c <= $#{$counts}; ++$c) {
$sum += $counts->[$c];
my $middle = ($obj->{columns}->{min}->[$c] +
$obj->{columns}->{max}->[$c])/2;
return $middle if $sum >= $tot/2;
}
return undef();
}
sub checkObj {
my @objs = @_;
foreach my $obj (@objs) {
return undef() unless $obj;
return 0 unless defined &objVal($obj);
}
return 1;
}
sub exprDiffer {
my ($exp1, $exp2, $maxDiffpPtr) = @_;
die() unless defined $maxDiffpPtr;
my $val1 = &computeExpr($exp1);
my $val2 = &computeExpr($exp2);
return undef() unless defined $val1 && defined $val2;
my $diffp = Percent($val1 - $val2, $val2);
my $maxDiffp = ${$maxDiffpPtr};
${$maxDiffpPtr} = $diffp;
return abs($diffp) > $maxDiffp;
}
sub expr2str {
my $expr = shift;
die() unless defined $expr;
my $res;
{
local $SIG{'__DIE__'};
local $SIG{__WARN__} = sub { die $_[0] };
$res = eval $expr;
}
#warn("$expr = $res");
return undef() if $@;
$res = "\"$res\"" if defined $res && !&isNum($res) && length($res);
return $res;
}
sub newObj {
my ($name, $expr) = @_;
die($name) unless defined $name && defined $expr;
$name = "$Label\::$name" unless $name =~ /::/;
my $obj = {
name => $name,
expr => $expr,
warn_txt => undef(),
warn_digest => undef(),
warn_id => 0,
warn_key => undef(),
locked => undef(),
};
return $obj;
}
sub defineObj {
my ($name, $expr) = @_;
$name = "$Label\::$name" unless $name =~ /::/;
return $Objs{$name} if exists $Objs{$name};
$expr = 'undef()' unless defined $expr;
my $obj = &newObj($name, $expr);
$Objs{$name} = $obj;
#warn("defined: $name as $expr");
return $obj;
}
sub computeExpr {
my ($expr, $objects) = @_;
return undef() unless defined $expr;
#warn("computeExpr: $expr");
# split expression into a sequence of tokens
my @tokens = ();
my $e = $expr;
while (length($e)) {
if ($e =~ /^\"[^\"]*\"/) { # quoted string
$e = $';
push @tokens, $&;
}
elsif ($e =~ /^[A-z][\w\.]*\b(?!\()/) { # object name
$e = $';
push @tokens, obj2StrByName($&, $expr, $objects);
}
elsif ($e =~ /^[A-z]\w*/) { # perl function call?
$e = $';
push @tokens, $&;
}
elsif ($e =~ /^\$\w+/) { # perl variable
$e = $';
push @tokens, $&;
}
elsif ($e =~ /^\d[\w\.]*/) { # a number
$e = $';
push @tokens, $&;
} else { # other
push @tokens, substr($e, 0, 1);
$e = substr($e, 1);
}
}
#warn("computeExpr> ". join('', @tokens));
return &expr2str(join('', @tokens));
}
sub obj2StrByName {
my ($name, $expr, $objects) = @_;
if ($name !~ /::/) {
die($name) unless defined $Label;
$name = "$Label\::$name";
}
my $obj = $Objs{$name};
$obj = $Tables{$name} unless defined $obj;
if (!$obj) {
warn("warning: labeled logs have no object named `$name'\n");
return 'undef()';
}
push @{$objects}, $obj if $objects;
die("no expression defined for `$name' in `$expr'\n")
unless defined $obj->{expr};
die("recursion on variable `$name' in `$expr'\n")
if $obj->{locked};
my $val = &objVal($obj, $objects);
#warn("obj2StrByName: $name -> $val");
return defined $val ? $val : 'undef()';
}
sub objBody {
my ($label) = @_;
$label = "$Label\::$label" unless $label =~ /::/;
return undef() unless defined $Verbs{$label};
return $Verbs{$label}->{body};
}
sub objVal {
my ($objORname, $objects) = @_;
my $obj;
my $quiet;
if (ref($objORname)) {
$obj = $objORname;
} else {
$quiet = $objORname =~ s/\?$//;
$objORname = "$Label\::$objORname" unless $objORname =~ /::/;
$obj = $Objs{$objORname};
}
if (!defined $obj) {
warn("Object `$objORname' is not defined\n") unless
$objORname =~ /\_$/ || $Undefs{$objORname} || $quiet;
$Undefs{$objORname} = 1;
return undef();
}
my $oldLabel = &curLabel();
if ($obj->{'name'} =~ /(.*?)::/) {
&curLabel($1);
}
if (!exists $obj->{val}) {
$obj->{locked} = 1;
$obj->{val} = &computeExpr($obj->{expr}, $objects);
$obj->{locked} = undef();
}
&curLabel($oldLabel);
return $obj->{val};
}
sub objExpr {
my ($label) = @_;
$label = "$Label\::$label" unless $label =~ /::/;
return undef() unless defined $Objs{$label};
return $Objs{$label}->{expr};
}
sub getTable {
my $name = shift;
$name = "$Label\::$name" unless $name =~ /::/;
return $Tables{$name};
}
sub substNamesInHTML {
my $html = shift;
my $e = $html;
while ($e =~ m|(?<!\\)\[([^\]]*)(?<!\\)\]|) {
my ($left, $right, $expr) = ($`, $', $1);
my @objs = ();
my $fmt;
if ($expr =~ /(?<!\\)\#(\%\S+)$/) {
$expr = $`;
$fmt = $1;
}
my $val = &computeExpr($expr, \@objs);
$val = &formatVal($val, \@objs, $fmt);
$e = $left . $val . $right;
}
$e =~ s/^\s+|\s+$//g;
return $e;
}
sub raw2expr {
my $raw = shift;
return 'undef()' unless defined $raw;
if (isNum($raw)) {
return int($raw) == -1 ? 'undef()' : $raw;
}
return "\"$raw\"";
}
sub setWarn {
my ($name, $digest, $text) = @_;
$name = "$Label\::$name" unless $name =~ /::/;
my $warnCount = ++$Objs{"$Label\::_warn_count"}->{'expr'};
my $obj = $Objs{$name};
die("$0: unknown object `$name' used in setWarn\n") unless $obj;
die($name) unless $digest && $text;
$obj->{warn_digest} = $digest;
$obj->{warn_id} = $warnCount;
$obj->{warn_key} = "warn:$warnCount";
$obj->{warn_txt} = $text;
$obj->{warn_txt} = &substNamesInHTML($text); # after all warn_* are set!
}
sub getWarnObjs {
return (
sort { $a->{warn_id} <=> $b->{warn_id} }
grep { defined $_->{warn_id} }
values %Objs );
}
sub label2hname($) {
my $label = shift;
my $l = "$label\::human_name";
my $hname = $Objs{$l} ? &objVal($l) : undef();
$hname = $label unless defined $hname;
$hname =~ s/^"|"$//g;
return $hname;
}
sub label2hlabel($) {
my $label = shift;
my $l = "$label\::human_label";
my $hlabel = $Objs{$l} ? &objVal($l) : undef();
$hlabel = $label unless defined $hlabel;
$hlabel =~ s/^"|"$//g;
return $hlabel;
}
sub formatVal {
my ($val, $objects, $fmt) = @_;
return ' ' unless defined $val;
if (defined $fmt) {
$val = sprintf($fmt, $val);
} elsif (isNum($val)) {
$val = sprintf('%.2f', $val);
} else {
$val =~ s/^\"|\"$//g;
}
if ($objects && @{$objects} == 1) {
my $obj = ${$objects}[0];
$val = &formatWarnVal($val, $obj) if $obj->{warn_txt};
}
return $val;
}
sub formatWarnVal {
my ($val, $obj) = @_;
my $res = <<HTML;
<font color="#FF0000"><b>
<a style="color:#FF0000;text-decoration:none"
onMouseOver="window.status='$obj->{warn_digest}'"
href="#$obj->{warn_key}">$val</a>
</b></font>
HTML
$res =~ s/^\s+|\s*\n\s*|\s+$//g;
return $res;
}
sub freeText2Html {
my ($label, $li) = @_;
$li = '<li>' unless defined $li;
$label = "$Label\::$label" unless $label =~ /::/;
my @lines = ();
if (my $table = $Tables{$label}) {
@lines = @{$table->{lines}};
}
elsif (my $atomVal = &objVal($label)) {
$atomVal =~ s/^"(.*)"$/$1/;
push @lines, $atomVal;
} else {
return '';
}
return '' unless @lines;
return join ('', map { "$li$_" } @lines);
}
sub num2str {
my $num = shift;
return sprintf('%.2f', $num);
}
sub isNum {
my $n = shift;
return $n =~ /^[+-]?\d+(\.\d+)?(e[+-]\d+)?$/;
}
1;
syntax highlighted by Code2HTML, v. 0.9.1