#!/usr/pkg/bin/perl # # cprofalyze.pl # # Analyzes the output files created by the profile command for # Call Profiling. # # Changes: # 14 Aug, 2006 Created (Rogier Meurs) # $UNSIGNED_MAX_DIV_K = 2**32/1000; if ($#ARGV == 0 || process_args(@ARGV)) { print "Usage:\n"; print " cprofalyze.pl [-f] [-aoct] [-i] [-n number] file ...\n\n"; print " clock CPU clock of source machine in MHz (mandatory)\n"; print " -f print totals per function (original order lost)\n"; print " -a sort alphabetically (default)\n"; print " -o no sort (original order)\n"; print " -c sort by number of calls\n"; print " -t sort by time spent\n"; print " -n print maximum of number lines per process\n"; print " -i when -[ao] used: print full paths\n"; exit 1; } sub process_args { $_ = shift; return 1 unless /^(\d+)$/; return 1 if $1 == 0; $MHz = $1; $sort_method = "A"; while (@_[0] =~ /^-/) { $_ = shift; SWITCH: { if (/^-a$/) { $sort_method = "A"; last SWITCH; } if (/^-o$/) { $sort_method = "O"; last SWITCH; } if (/^-c$/) { $sort_method = "C"; last SWITCH; } if (/^-t$/) { $sort_method = "T"; last SWITCH; } if (/^-i$/) { $print_full_paths = 1; last SWITCH; } if (/^-f$/) { $print_totals = 1; last SWITCH; } if (/^-n$/) { $_ = shift; return 1 unless /^(\d+)$/; return 1 unless $1 > 0; $show_paths = $1; last SWITCH; } return 1; } } $print_full_paths == 1 && ($sort_method eq "T" || $sort_method eq "C") && { $print_full_paths = 0 }; @files = @_; return 0; } print <; chomp; if (!/^call$/) { if (/^stat$/) { print "Statistical Profiling output file: "; print "Use sprofalyze.pl instead.\n"; } else { print "Not a profiling output file.\n"; } return 0; } # Second line: header with call path string size. $_ = ; chomp; ($CPATH_MAX_LEN, $PROCNAME_LEN) = split(/ /); $SLOT_SIZE = $CPATH_MAX_LEN + 16; $EXE_HEADER_SIZE = $PROCNAME_LEN + 4; # Read in the data for all the processes and put it in a hash of lists. # A list for each process, which contains lists itself for each call # path. until(eof(FILE)) { read(FILE, $buf, $EXE_HEADER_SIZE) == $EXE_HEADER_SIZE or die ("Short read."); ($exe_name, $slots_used) = unpack("Z${PROCNAME_LEN}i", $buf); @exe = (); for ($i=0; $i<$slots_used; $i++) { read(FILE, $buf, $SLOT_SIZE) == $SLOT_SIZE or die ("Short read."); ($chain, $cpath, $calls, $lo, $hi) = unpack("iA${CPATH_MAX_LEN}iII", $buf); $cycles_div_k = $hi * $UNSIGNED_MAX_DIV_K; $cycles_div_k += $lo / 1000; $ms = $cycles_div_k / $MHz; push @exe, [ ($cpath, $calls, $ms) ]; } $file_res{$exe_name} = [ @exe ]; } return \%file_res; } # Aggregate calls and cycles of paths into totals for each function. sub make_totals { my $ref = shift; my %file_res = %{$ref}; my $exe; my %res, %calls, %time; my @totals; foreach $exe (sort keys %file_res) { @totals = (); %calls = (); %time = (); @ar = @{$file_res{$exe}}; foreach $path (@ar) { $_ = $path->[0]; s/^.* //; # basename of call path $calls{$_} += $path->[1]; $time{$_} += $path->[2]; } foreach $func (keys %calls) { push @totals, [ ($func, $calls{$func}, $time{$func}) ]; } $res{$exe} = [ @totals ]; } return \%res; } sub print_file { my $ref = shift; my %file_res = %{$ref}; my $exe; printf "\n========================================"; printf "========================================\n"; printf("Data file: %s\n", $file); printf "========================================"; printf "========================================\n\n"; # If we have the kernel, print it first. Then the others. print_exe($file_res{"kernel"}, "kernel") if exists($file_res{"kernel"}); foreach $exe (sort keys %file_res) { print_exe($file_res{$exe}, $exe) unless $exe eq "kernel"; } } sub print_exe { my $ref = shift; my $name = shift; my @exe = @{$ref}; my @funcs, @oldfuncs; my $slots_used = @exe; # Print a header. printf "----------------------------------------"; printf "----------------------------------------\n"; $print_totals ? printf "%-8s %60s functions\n", $name, $slots_used : printf "%-8s %59s call paths\n", $name, $slots_used; printf "----------------------------------------"; printf "----------------------------------------\n"; printf("%10s %12s path\n", "calls", "msecs"); printf "----------------------------------------"; printf "----------------------------------------\n"; SWITCH: { if ($sort_method eq "A") { @exe = sort { lc($a->[0]) cmp lc($b->[0]) } @exe; last SWITCH; } if ($sort_method eq "C") { @exe = reverse sort { $a->[1] <=> $b->[1] } @exe; last SWITCH; } if ($sort_method eq "T") { @exe = reverse sort { $a->[2] <=> $b->[2] } @exe; last SWITCH; } last SWITCH; } my $paths; @oldfuncs = (); foreach $path (@exe) { printf("%10u %12.2f ", $path->[1], $path->[2]); if ($print_full_paths == 1 || ($sort_method eq "C" || $sort_method eq "T")) { print $path->[0]; } else { @funcs = split(/ /, $path->[0]); for (my $j=0; $j<=$#funcs; $j++) { if ($j<=$#oldfuncs && $funcs[$j] eq $oldfuncs[$j]) { print " ---"; } else { print " " if ($j > 0); print $funcs[$j]; } } @oldfuncs = @funcs; } print "\n"; last if (++$paths == $show_paths); } print "\n"; }