blob: 31f2af4c168871e16349b24b5c00d52138ea4e37 [file] [log] [blame]
#!/usr/bin/perl -w
my $instructions = "
Usage: $0 <file> to read statistics from a file. Use '-' to read from stdin.
Options:
--depth <n> Show n levels of call graph edges, default = 1
--calledby Show the callers for each entry
";
use strict;
use warnings;
use Math::BigInt;
use Math::BigFloat;
use Getopt::Long;
use File::Basename;
use List::Util qw[min max];
BEGIN {
push @INC, dirname(__FILE__);
}
use arcsymbols;
my $swdepot = dirname(__FILE__)."/../..";
my $muc = undef;
my $csv = undef;
my $help = undef;
my $clockrate = 400000000;
my $objdump = "/usr/local/ARC/gcc/bin/arc-linux-uclibc-objdump";
my $muc_path = "$swdepot/macfw/qtn_ruby";
my $sort_field = "cycles_exc_callees";
my $maxdepth = 1;
my $calledby = undef;
my @all_objs = `echo $swdepot/linux/vmlinux ; find $swdepot/drivers -name \*.ko`;
my %allsyms;
foreach (@all_objs) {
chomp;chomp;
$allsyms{$_} = new arcsymbols($_);
}
sub fixfunc {
my ($name, $offset, $size) = @_;
if ($offset > 0) {
# find symbol, compare sizes; some functions are omitted in kallsyms
foreach my $bin (reverse sort keys %allsyms) {
my $changed = undef;
($name, $offset, $size, $changed) = $allsyms{$bin}->fixfunc($name, $offset, $size);
if ($changed == 1) {
last;
}
}
}
return ($name, $offset, $size);
}
my $result = GetOptions(
"csv" => \$csv,
"help" => \$help,
"clockrate=s" => \$clockrate,
"muc_bin" => \$muc_path,
"sort=s" => \$sort_field,
"depth=s" => \$maxdepth,
"calledby" => \$calledby,
);
$clockrate = int($clockrate);
if ($help || $#ARGV < 0) {
die $instructions;
}
my %funcs = parsedata(readfile($ARGV[0]));
if ($csv) {
$" = ", ";
my @fields = (
"Function",
"Module",
"Cycles w/o subs",
"Cycles w/ subs",
"Call count",
"Cycles w/o subs per iter",
"Offsets",
"Pct% time w/o subs",
"Function size",
"Blockiness (cyc/iter/size)",
"Time w/o subs (".($clockrate / 1000000)." MHz)",
);
print "@fields\n";
foreach my $func (sort { $funcs{$b}->{$sort_field} <=> $funcs{$a}->{$sort_field} } keys %funcs) {
my $f = $funcs{$func};
my @data = (
$func,
$f->{module},
$f->{cycles_exc_callees},
$f->{cycles_inc_callees},
$f->{calls},
$f->{cycles_per_iter_exc_callees},
$f->{offsets},
$f->{percent},
$f->{size},
$f->{blockiness},
$f->{time_exc_callees});
print "@data\n";
}
} else {
my $pattern = "%-9s %14s %16s %8s %7s %4s %10s %8s %8s %s %s\n";
printf($pattern, "Dpth", "Cycles w/o subs", "Cycles w/ subs", "Calls", "cwospi", "Ofs", "Pct%", "FuncSize", "blocky", "Func", "", "");
my @sorted_funcs = sort { $funcs{$b}->{$sort_field} <=> $funcs{$a}->{$sort_field} } keys %funcs;
foreach my $func (@sorted_funcs) {
recursive_print_func($pattern, $func, 0, $maxdepth, \@sorted_funcs);
}
}
sub recursive_print_func {
my ($pattern, $func, $depth, $max_depth, $sorted_funcs_ref) = @_;
return if ($depth >= $max_depth);
print_func_line($pattern, $func, $funcs{$func}, $depth);
foreach my $nextfunc (@{$sorted_funcs_ref}) {
if ($funcs{$nextfunc}->{parents}->{$func}) {
recursive_print_func($pattern, $nextfunc, $depth+1, $max_depth, $sorted_funcs_ref);
}
}
if ($depth == 0 && $calledby) {
# add called by lines...
my @parents_sorted = sort {
$funcs{$func}->{parents}->{$b}->{$sort_field} <=> $funcs{$func}->{parents}->{$a}->{$sort_field}
} keys %{$funcs{$func}->{parents}};
foreach my $parent_name (@parents_sorted) {
print_func_line($pattern, " << $parent_name", $funcs{$func}->{parents}->{$parent_name}, -1);
}
}
}
sub print_func_line {
my ($pattern, $func, $f, $depth) = @_;
my $func_str = "";
my $indent = "";
if ($depth > 0) {
for (my $i = 0; $i < $depth; $i++) {
$func_str .= " ";
$indent .= " ";
}
$func_str .= " |__ ";
$indent .= "|__";
} elsif ($depth == 0) {
$indent = "#_";
} else {
$func_str = " < ";
$indent .= " < ";
}
$func_str .= $f->{name};
my $cwospi = $f->{cycles_per_iter_exc_callees} || 0;
my $percent = $f->{percent} || 0;
my $blockiness = $f->{blockiness} || 0;
my $offsets = $f->{offsets} || 0;
printf $pattern,
$indent,
#sprintf($depth),
sprintf($f->{cycles_exc_callees}),
sprintf($f->{cycles_inc_callees}),
sprintf($f->{calls}),
sprintf($cwospi),
sprintf($offsets),
sprintf("%2.4g", $percent),
sprintf($f->{size}),
sprintf("%2.4g", $blockiness),
$func_str,
$f->{module};
}
sub readfile {
my $file = shift;
my @data;
if ($file eq "-") {
while(<STDIN>) {
push(@data, $_);
}
} else {
open(F, $file) or die "Could not open file '$file': $!\n";
while(<F>) {
push(@data, $_);
}
close(F);
}
return @data;
}
sub load_syms {
my $path = shift;
my %data;
unless ( -e $path ) {
warn "Could not find binary: $path, no function size data available\n";
return %data;
}
foreach (`$objdump -t $path`) {
if (/^([\w\d]+)\s+.*?\bF\b\s+\.\w+\s+([\w\d]+)\s+(\w+)\s*$/) {
my $addr = hex($1);
my $size = hex($2);
my $name = $3;
$data{$name}->{addr} = $addr;
$data{$name}->{size} = $size;
$data{$addr}->{name} = $name;
$data{$addr}->{size} = $size;
}
}
return %data;
}
sub parsedata {
my @data = @_;
my %mucdata;
my %funcs;
my $total_cycles = 0;
foreach my $line (@data) {
$line =~ /^\s*([\w\d_]+)\+?(0x[\w\d]+|)\/?(0x[\w\d]+|)\s+(\[\w+\]\s+|)([\w\d_]+)\+?(0x[\w\d]+|)\/?(0x[\w\d]+|)\s+(\[\w+\]\s+|)(\d+)\s+(0x[\w\d]+)\s+(0x[\w\d]+)\s+(0x[\w\d]+)\s+(0x[\w\d]+)\s*.*?\s*$/;
my $funcname = $1;
my $offset = $2 ? hex($2) : 0;
my $funcsize = $3 ? hex($3) : 0;
my $module = $4 ? $4 : "";
my $calling_funcname = $5;
my $calling_offset = $6 ? hex($6) : 0;
my $calling_funcsize = $7 ? hex($7) : 0;
my $calling_module = $8 ? $8 : "";
my $calls = $9 ? int($9) : 0;
my $time_inc_callees = Math::BigInt->from_hex($10);
$time_inc_callees->blsft(32);
$time_inc_callees->bior(Math::BigInt->from_hex($11));
my $time_exc_callees = Math::BigInt->from_hex($12);
$time_exc_callees->blsft(32);
$time_exc_callees->bior(Math::BigInt->from_hex($13));
($funcname, $offset, $funcsize) = fixfunc($funcname, $offset, $funcsize);
($calling_funcname, $calling_offset, $calling_funcsize) = fixfunc($calling_funcname, $calling_offset, $calling_funcsize);
#print "FFFF $line\nAAAA $funcname $offset $funcsize $module $calls $time_inc_callees $time_exc_callees\n";
if ($funcsize == 0) {
$muc = 1;
%mucdata = &load_syms($muc_path) unless %mucdata;
}
if ($funcname && defined($time_inc_callees)) {
if ($muc && $mucdata{$funcname}->{size}) {
$funcsize = $mucdata{$funcname}->{size};
}
$funcs{$funcname}->{name} = $funcname;
$funcs{$funcname}->{parents}->{$calling_funcname}->{name} = $calling_funcname;
$funcs{$funcname}->{parents}->{$calling_funcname}->{calls} = $calls;
$funcs{$funcname}->{parents}->{$calling_funcname}->{cycles_inc_callees} = $time_inc_callees;
$funcs{$funcname}->{parents}->{$calling_funcname}->{cycles_exc_callees} = $time_exc_callees;
$funcs{$funcname}->{parents}->{$calling_funcname}->{offset} = $calling_offset;
$funcs{$funcname}->{parents}->{$calling_funcname}->{size} = $calling_funcsize;
$funcs{$funcname}->{parents}->{$calling_funcname}->{module} = $calling_module;
$funcs{$funcname}->{offset} = $offset;
$funcs{$funcname}->{size} = $funcsize;
$funcs{$funcname}->{module} = $module;
$funcs{$funcname}->{calls} += $calls;
$funcs{$funcname}->{offsets} += 1;
$funcs{$funcname}->{cycles_inc_callees} += $time_inc_callees;
$funcs{$funcname}->{cycles_exc_callees} += $time_exc_callees;
$total_cycles += $time_exc_callees;
} else {
warn "Error parsing line: $line";
}
}
foreach my $func (keys %funcs) {
my $f = Math::BigFloat->new(sprintf($funcs{$func}->{cycles_exc_callees}));
$f->bdiv(Math::BigFloat->new(sprintf($total_cycles)));
$f->bmul(100);
$funcs{$func}->{percent} = $f;
$f = Math::BigInt->new(sprintf($funcs{$func}->{cycles_exc_callees}));
$f->bdiv($funcs{$func}->{calls});
$funcs{$func}->{cycles_per_iter_exc_callees} = sprintf($f);
if ($funcs{$func}->{size} == 0) {
$funcs{$func}->{blockiness} = 0;
} else {
$funcs{$func}->{blockiness} = $funcs{$func}->{cycles_per_iter_exc_callees} / $funcs{$func}->{size};
}
$f = Math::BigFloat->new(sprintf($funcs{$func}->{cycles_exc_callees}));
$f->bdiv($clockrate);
$funcs{$func}->{time_exc_callees} = sprintf($f);
}
return %funcs;
}