#!perl -w
use strict;

use IO::File;

my ($profdata, $info)= readprofiling(shift);
my ($addrmap, $symmap)= readmapfile(shift);

my $mapoffset= $info->{mleaveproc} - $symmap->{mcountleave};

printf(" %d overflows  %08lx%08lx calls  leave=%08lx [+%08lx] clocks=%08lx ticks=%d\n\n", 
    $info->{overflows}, 
    $info->{entriesH}, $info->{entriesL},
    $info->{mleaveproc}, $mapoffset,
    $info->{clocks}, $info->{ticks});

my %ftotal;

my %calltree;

print "  caller->function :    count     time |     subs\n";
for my $rec (sort { $a->{timeL} <=> $b->{timeL} } (@$profdata)) {

    printf("%08lx->%08lx : %08lx %08lx | %08lx   %s -> %s\n",
        $rec->{caller}, $rec->{function},
        $rec->{count}, $rec->{timeL}, $rec->{subsL},

        findfunction($rec->{caller}-$mapoffset, $addrmap),
        findfunction($rec->{function}-$mapoffset, $addrmap));

    my $basecaller= getBaseFunction($rec->{caller}-$mapoffset, $addrmap)+$mapoffset;
    my $basefunction= getBaseFunction($rec->{function}-$mapoffset, $addrmap)+$mapoffset;

    if (!exists $ftotal{$basefunction})
    {
        my $fstats= {
            function=>$basefunction,
            count=>$rec->{count}, timeL=>$rec->{timeL}, subsL=>$rec->{subsL},
            subtime=>0,
        };
        $ftotal{$basefunction}= $fstats;
    }
    else {
        my $fstats= $ftotal{$basefunction};
        $fstats->{count} += $rec->{count};
        $fstats->{timeL} += $rec->{timeL};
        $fstats->{subsL} += $rec->{subsL};
    }

    if (!exists $ftotal{$basecaller})
    {
        my $fstats= {
            function=>$basecaller,
            count=>0, timeL=>0, subsL=>0,
            subtime=>$rec->{timeL},
        };
        $ftotal{$basecaller}= $fstats;
    }
    else {
        my $fstats= $ftotal{$basecaller};
        $fstats->{subtime} += $rec->{timeL};
    }
}

my $maximum= findmax(\%ftotal);

print "\n\n", "-"x77, "\n\n";

# time = the nr of 3686400 Hz clock ticks.
# the assumption is that : T= A*subs + B*count
print "function :    count     time |     subs   pctm   p.call {   subtm pcthis }\n";
for my $rec (sort { $a->{timeL} <=> $b->{timeL} } (values %ftotal)) {

	# div($rec->{timeL}-2.5*$rec->{subsL}, $rec->{count})
	#     might be a better indicator of time spent, this substracts the time spent
	#     profiling.

    printf("%08lx : %08lx %08lx | %08lx %6.2f %8.2f { %08lx %6.2f } %s\n",
        $rec->{function},

        $rec->{count}, $rec->{timeL}, $rec->{subsL},
        percent($maximum->{timeL}, $rec->{timeL}),
        div($rec->{timeL}, $rec->{count}),

        $rec->{timeL}-$rec->{subtime},
	percent($maximum->{timeL}, $rec->{timeL}-$rec->{subtime}),

        findfunction($rec->{function}-$mapoffset, $addrmap) 
    );
}

exit(0);

sub div {
    my ($a, $b)= @_;
    return $b? $a/$b : -1;
}
sub percent {
    my ($a, $b)= @_;
    return $b/$a*100.0;
}
sub readprofiling {
    my ($filename)= @_;

    my $fh= IO::File->new($filename, "r");
    binmode($fh);

    my @sizes= readlongs($fh, 3);
    my @mcountdata= readlongs($fh, $sizes[0]/4-3);

    my %info= (
        overflows=> $mcountdata[2],
        entriesH=> $mcountdata[4],
        entriesL=> $mcountdata[5],
        mleaveproc=> $mcountdata[6],
        statentries=> $mcountdata[11],
        stackentries=> $mcountdata[12],
        ticks=> $mcountdata[13],
        clocks=> $mcountdata[14],
    );

    my @statrecs;

    for (my $stat_i= 0 ; $stat_i < $info{statentries} ; $stat_i++)
    {
        my $statrec;
        $fh->read($statrec, $sizes[1]);

        my @data= unpack("L*", $statrec);

        next if ($data[2] == 0);

        push(@statrecs, {
                count=>$data[1],
                caller=>$data[2],
                function=>$data[3],
                timeH=>$data[4],
                timeL=>$data[5],
                subsH=>$data[6],
                subsL=>$data[7],
            });
    }

    # followed by a 64K hashtable
    # followed by the stack.
    $fh->close();
    return (\@statrecs, \%info);
}
sub readlongs {
    my ($fh, $n)= @_;

    my $data;
    $fh->read($data, 4*$n);

    return unpack("L*", $data);
}

sub readmapfile {
    my ($filename)= @_;

    my $fh= IO::File->new($filename, "r");

    my $section="";

    my %addr2sym;
    my %sym2addr;

    while(<$fh>)
    {
        chomp;

        next if (/^$/);

        if (/Start\s+Length\s+Name\s+Class/i)
        {
            $section="segdefs";
        }
        elsif (/Address\s+Publics\sby\sValue\s+Rva.Base\s+Lib.Object/i)
        {
            $section="symbols";
        }
        elsif(/Static\ssymbols/i)
        {
            $section="staticsymbols";
        }
        elsif(/entry\spoint\sat\s+\w+/i)
        {
            $section="";
        }
        elsif ($section eq "symbols" && /^\s*(\w+):(\w+)\s+(\S+)\s+(\w+)\s+(\S+)/)
        {
            my ($segment, $offset, $symbol, $rvabase, $object)= (hex($1), hex($2), $3, hex($4), $5);

            $addr2sym{$rvabase}= $symbol;
            $sym2addr{$symbol}= $rvabase;
        }
    }
    $fh->close();
    return (\%addr2sym, \%sym2addr);
}

sub findfunction {
    my ($addr, $map)= @_;

    for (sort {$b <=> $a} keys %$map)
    {
        if ($addr == $_)
        {
            return $map->{$_};
        }
        elsif ($addr > $_)
        {
            return sprintf("%s+%04x", $map->{$_}, $addr-$_);
        }
    }
    return "?";
}
sub getBaseFunction {
    my ($addr, $map)= @_;

    for (sort {$b <=> $a} keys %$map)
    {
        if ($addr >= $_)
        {
            return $_;
        }
    }
    return undef;
}
sub findmax {
    my ($stats)= @_;

    my $max;
    for (keys %$stats) {
        if (!defined $max || $stats->{$_}{timeL} > $max->{timeL}) {
            $max= $stats->{$_};
        }
    }

    return $max;
}


