#!/usr/bin/perl

# **********************************************************
# Copyright (c) 2003-2010 VMware, Inc.  All rights reserved.
# **********************************************************

# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
#   this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
#   this list of conditions and the following disclaimer in the documentation
#   and/or other materials provided with the distribution.
#
# * Neither the name of VMware, Inc. nor the names of its contributors may be
#   used to endorse or promote products derived from this software without
#   specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
# DAMAGE.

### bmtable.pl
### author: Derek Bruening   April 2001
###
### Produces a table summarizing times from one or more benchmark runs
### For times, the default is to use the median,
###   can also calculate min w/ -min parameter
### For memory, the default is to use the max,
###   can also calculate average or use first run w/ -memave or -mem1

$usage = "Usage: $0 [-v] [-k] [-c] [-min] [-mem1] [-memave] <logfile> [logfile2 ...]\n";

# constants
$debug = 0;

# parameters
$verbose = 0;
$ignore_errors = 0;
$ignore_cpu_reqt = 0;
$min = 0;
$files = 0;

# memory: use max of all runs, or:
$mem_first = 0;
$mem_ave = 0;

while ($#ARGV >= 0) {
    if ($ARGV[0] eq "-v") {
        $verbose = 1;
    } elsif ($ARGV[0] eq "-k") {
        $ignore_errors = 1;
    } elsif ($ARGV[0] eq "-c") {
        $ignore_cpu_reqt = 1;
    } elsif ($ARGV[0] eq "-mem1") {
        $mem_first = 1;
    } elsif ($ARGV[0] eq "-memave") {
        $mem_ave = 1;
    } elsif ($ARGV[0] eq "-min") {
        $min = 1;
    } else {
        $logfile[$files] = $ARGV[0];
        $files++;
    }
    shift;
}
if (!defined($logfile[0])) {
    print $usage;
    exit;
}

if ($mem_ave) {
    print "RSS and VSz are average of runs for those with multiple runs\n";
} elsif ($mem_first) {
    print "RSS and VSz are from first run for those with multiple runs\n";
} else {
    print "RSS and VSz are max of runs for those with multiple runs\n";
}

$cwd = `pwd`;
chop $cwd;
foreach $file (@logfile) {
    if (!($file =~ m+^/+ || $file =~ m+^\\+)) {
        # make it absolute
        $file = "$cwd/$file";
    }
    print "Processing $file\n";
    &process_file($file);
}

if ($verbose) {
    print "Key: - = invalid, + = valid, ** = " . ($min ? "min" : "median") . "\n";
}
print "----------------------------------------------------------------------\n";

# only single type of run!
# (this script adapted from wbmtable.pl, which has multiple types)
$run{'all'} = 'all';
foreach $r (sort (keys %run)) {
    if (defined($run{$r})) {
        &print_cur_stats($r);
    }
}

sub process_file($) {
    my ($logfile) = @_;
    open(LOG, "< $logfile") || die "Error opening $logfile\n";
    while (<LOG>) {
        chop;
        # support old logfile formats that don't print name w/ Verify
        if ($_ =~ m|^%%%% /.+/([^/]+)| ||
            $_ =~ /Verify for (\S+):/ ||
            # support for specjvm
            $_ =~ /======= (\S+) Starting/) {
            print "Name is $_\n" if ($debug);
            $bmark = $1;
            $bmark =~ s/\.exe$//;
            if (!defined($name{$bmark})) {
                $name{$bmark} = $bmark;
                $iters{$bmark} = 0;
                print "New bmark $bmark\n" if ($debug);
            }
            # have to allow two names in a row if 1st is runsuite comment,
            # and second is the Verify for the 1st iter
            if ($_ =~ m|^%%%% /.+/([^/]+)|) {
                $runsuite_name = 1;
            }
            $iter = $iters{$bmark};
            print "  New iter $iter for $bmark\n" if ($debug);
            # two differently-triggered names in a row: count as one iter
            if ($runsuite_name && $_ =~ /Verify for (\S+):/) {
                $iter--;
                print "\tDouble-dip: iter back to $iter for $bmark\n" if ($debug);
                $runsuite_name = 0;
            }
            if (!defined($time{$bmark,$iter})) {
                $iters{$bmark}++;
                $time{$bmark,$iter} = 0;
                $rss{$bmark,$iter} = 0;
                $vsz{$bmark,$iter} = 0;
                $cpu{$bmark,$iter} = 0;
                $status{$bmark,$iter} = " ?  ";
            }
        }
        if ($_ =~ /make:/ || $_ =~ /[Ee]rror:/ || $_ =~ /FAILED/) {
            $status{$bmark,$iter} = "FAIL";
        } elsif ($_ =~ /Verify.* correct/ ||
                 # support for specjvm
                 $_ =~ /======= (\S+) Finished/) {
            $status{$bmark,$iter} = " ok ";
        } elsif ($_ =~ /Elapsed: (\d+):(\d+):([\d\.]+)/) {
            # texec format
            $elapsed = $1*60 + $2 + $3/60.; # minutes
            $time{$bmark,$iter} += $elapsed;
        } elsif ($_ =~ /real\t([0-9]+)m([0-9\.]+)s/) {
            # /usr/bin/time simple format
            # FIXME: what do hours look like?
            $elapsed = $1 + $2/60.; # minutes
            $time{$bmark,$iter} += $elapsed;
            printf "\t$bmark, $iter: elapsed %5.2f @$cpu%%cpu\n", $elapsed
                if ($debug);
        } elsif ($_ =~ /(\d+):(\d+):(\d+)elapsed (\d+)%CPU/) {
            # /usr/bin/time and runstats compressed format
            $elapsed = $1*60 + $2 + $3/60.; # minutes
            $time{$bmark,$iter} += $elapsed;
            $cpu{$bmark,$iter} = $4 if ($4 > 0);
            printf "\t$bmark, $iter: elapsed %5.2f @$cpu%%cpu\n", $elapsed
                if ($debug);
        } elsif ($_ =~ /(\d+):(\d+).(\d+)elapsed (\d+)%CPU/) {
            # /usr/bin/time and runstats compressed format, with hours
            $elapsed = $1 + $2/60. + $3/6000.; # minutes
            $time{$bmark,$iter} += $elapsed;
            $try_cpu = $4;
            $cpu{$bmark,$iter} = $4 if ($4 > 0);
            printf "\t$bmark, $iter: elapsed %5.2f @$cpu%%cpu\n", $elapsed
                if ($debug);
        } elsif ($_ =~ /(\d+) tot, (\d+) RSS/) {
            # runstats memory usage
            # use max of all runs by default
            if ($mem_first) {
                $vsz{$bmark,$iter} = $1 if ($vsz{$bmark,$iter} == 0);
                $rss{$bmark,$iter} = $2 if ($rss{$bmark,$iter} == 0);
            } elsif ($mem_ave) {
                $vsz{$bmark,$iter} = ($1 + $vsz{$bmark,$iter}*$memruns{$bmark,$iter}) /
                    ($memruns{$bmark,$iter}+1);
                $rss{$bmark,$iter} = ($1 + $rss{$bmark,$iter}*$memruns{$bmark,$iter}) /
                    ($memruns{$bmark,$iter}+1);
                $memruns{$bmark,$iter}++;
            } else {
                $vsz{$bmark,$iter} = $1 if ($1 > $vsz{$bmark,$iter});
                $rss{$bmark,$iter} = $2 if ($2 > $rss{$bmark,$iter});
            }
        }
    }
    close(LOG);
}

sub print_cur_stats($) {
# run name is holdover from wbmtable.pl, so disabling this
#    my ($runname) = @_;
#    print "\nRun $runname\n";
    printf("%14s      #    %-6s  %-4s  %-9s   %-7s   %-7s\n",
           "Benchmark", "Status", "%CPU", "Time(min)", "RSS(KB)", "VSz(KB)");
    $format       = "  %2d/%-2d   %4s    %4s   %6.2f   %7d   %7d\n";
    $empty_format = "  %2d/%-2d   %4s    %4s   %6s   %7s   %7s\n";
    foreach $bm (sort (keys %name)) {
        # ignore FORTRAN90
        next if ($bm =~ /facerec/ ||
                 $bm =~ /fma3d/ ||
                 $bm =~ /galgel/ ||
                 $bm =~ /lucas/);
        # find median time, assume memory doesn't change much, just
        # use rss and vsz from median time run
        $num_ok = 0;
        $#ints = -1; # clear from last time
        for ($i=0; $i<$iters{$bm}; $i++) {
            if ($ignore_errors || &valid($status{$bm,$i}, $cpu{$bm,$i})) {
                $ints[$num_ok] = $i;
                $num_ok++;
            }
        }
        if ($num_ok == 0) {
            $median = -1;
        } else {
            @sorted = sort({$time{$bm,$a} <=> $time{$bm,$b}} @ints);
            if ($min) {
                $median = $sorted[0];
            } else {
                $median = $sorted[$num_ok/2]; # if even number, take smaller!
            }
        }
        if ($verbose) {
            for ($i=0; $i<$iters{$bm}; $i++) {
                if ($i == $median) {
                    $str = "%14s**";
                } elsif (&valid($status{$bm,$i}, $cpu{$bm,$i})) {
                    $str = "%14s+ ";
                } else {
                    $str = "%14s- ";
                }
                printf($str.$format,
                       $bm, $i+1, $iters{$bm},
                       $status{$bm,$i}, &cpustr($cpu{$bm,$i}),
                       $time{$bm,$i}, $rss{$bm,$i}, $vsz{$bm,$i});
            }
        } else {
            if ($median == -1) {
                printf("%14s  ".$empty_format,
                       $bm, 0, $iters{$bm}, $status{$bm,0}, " -- ",
                       "----", "----", "----");
            } else {
                if ($num_ok == $iters{$bm}) {
                    $stat = $status{$bm,$median};
                } else {
                    $stat = "<ok>";
                }
                printf("%14s  ".$format,
                       $bm, $num_ok, $iters{$bm},
                       $stat, &cpustr($cpu{$bm,$median}),
                       $time{$bm,$median}, $rss{$bm,$median},
                       $vsz{$bm,$median});
            }
        }
    }
}


sub cpustr($) {
    my ($cpu) = @_;
    if ($cpu == 0) {
        return " -- ";
    } elsif ($cpu < 99) {
        return sprintf "*%02d*", $cpu;
    } else {
        return sprintf "%3d ", $cpu;
    }
}

sub valid($, $) {
    my ($status, $cpu) = @_;
    return ($status eq " ok " &&
            # passed as param -> defined to 0, so check for 0
            ($ignore_cpu_reqt || !defined($cpu) || $cpu == 0 || $cpu >= 99));
}