a665ae3de1
- contributed by Bjorn Swift - adds process accounting, for example counting the number of messages sent, how often the process was preemted and how much time it spent in the run queue. These statistics, along with the current cpu load, are sent back to the user-space scheduler in the Out Of Quantum message. - the user-space scheduler may choose to make use of these statistics when making scheduling decisions. For isntance the cpu load becomes especially useful when scheduling on multiple cores.
374 lines
8.9 KiB
Perl
Executable file
374 lines
8.9 KiB
Perl
Executable file
#!/usr/pkg/bin/perl
|
|
#
|
|
# sprofalyze.pl
|
|
#
|
|
# Analyzes the output files created by the profile command for
|
|
# Statistical Profiling.
|
|
#
|
|
# Changes:
|
|
# 14 Aug, 2006 Created (Rogier Meurs)
|
|
#
|
|
|
|
# Configuration options:
|
|
|
|
# Location and parameters of nm program to extract symbol tables
|
|
$nm = "/usr/bin/acknm -dn";
|
|
|
|
# Location of src (including trailing /)
|
|
$src_root = qw(
|
|
/usr/src/
|
|
);
|
|
|
|
# Location of system executables within src. Add new servers/drivers here.
|
|
# This should be replaced with something less maintenance-prone some day.
|
|
@exes = qw(
|
|
kernel/kernel
|
|
|
|
servers/ds/ds
|
|
servers/hgfs/hgfs
|
|
servers/inet/inet
|
|
servers/ipc/ipc
|
|
servers/is/is
|
|
servers/iso9660fs/isofs
|
|
servers/mfs/mfs
|
|
servers/pfs/pfs
|
|
servers/pm/pm
|
|
servers/procfs/procfs
|
|
servers/rs/rs
|
|
servers/sched/sched
|
|
servers/vfs/vfs
|
|
servers/vm/vm
|
|
servers/sched/sched
|
|
commands/service/service
|
|
|
|
drivers/ahci/ahci
|
|
drivers/acpi/acpi
|
|
drivers/amddev/amddev
|
|
drivers/at_wini/at_wini
|
|
drivers/atl2/atl2
|
|
drivers/audio/es1370/es1370
|
|
drivers/audio/es1371/es1371
|
|
drivers/bios_wini/bios_wini
|
|
drivers/dec21140A/dec21140A
|
|
drivers/dp8390/dp8390
|
|
drivers/dpeth/dpeth
|
|
drivers/e1000/e1000
|
|
drivers/filter/filter
|
|
drivers/floppy/floppy
|
|
drivers/fxp/fxp
|
|
drivers/lance/lance
|
|
drivers/log/log
|
|
drivers/memory/memory
|
|
drivers/orinoco/orinoco
|
|
drivers/pci/pci
|
|
drivers/printer/printer
|
|
drivers/random/random
|
|
drivers/rtl8139/rtl8139
|
|
drivers/rtl8169/rtl8169
|
|
drivers/sb16/sb16_dsp
|
|
drivers/sb16/sb16_mixer
|
|
drivers/ti1225/ti1225
|
|
drivers/tty/tty
|
|
);
|
|
|
|
# 8< ----------- no user configurable parameters below this line ----------- >8
|
|
|
|
$SAMPLE_SIZE = 12;
|
|
$MINIMUM_PERC = 1.0;
|
|
|
|
|
|
if ($#ARGV < 0 || process_args(@ARGV)) {
|
|
print "Usage:\n";
|
|
print " sprofalyze.pl [-p percentage] file ...\n\n";
|
|
print " percentage print only processes/functions >= percentage\n";
|
|
exit 1;
|
|
}
|
|
|
|
sub process_args {
|
|
$_ = shift;
|
|
|
|
if (/^-p$/) {
|
|
$_ = shift;
|
|
return 1 unless /^(\d{1,2})(.\d+)?$/;
|
|
$MINIMUM_PERC = $1 + $2;
|
|
} else {
|
|
unshift @_, $_;
|
|
}
|
|
|
|
@files = @_;
|
|
|
|
return 1 unless @files > 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
if (read_symbols()) { exit 1; }
|
|
|
|
print "Showing processes and functions using at least $MINIMUM_PERC% time.\n";
|
|
|
|
foreach $file (@files) {
|
|
if (process_datafile($file)) { exit 1; }
|
|
}
|
|
|
|
exit 0;
|
|
|
|
|
|
sub short_name
|
|
{
|
|
my $shortname = shift;
|
|
$shortname =~ s/^.*\///;
|
|
return substr($shortname, 0, 7);
|
|
}
|
|
|
|
|
|
sub read_symbols
|
|
{
|
|
print "Building indexes from symbol tables:";
|
|
|
|
for ($i=0; $i<= $#exes; $i++) {
|
|
my $exe = @exes[$i];
|
|
$shortname = $exe;
|
|
$shortname =~ s/^.*\///;
|
|
print " " if $i <= $#exes;
|
|
print $shortname;
|
|
$shortname = short_name($exe);
|
|
|
|
$fullname = $src_root . $exe;
|
|
|
|
if ((! -x $fullname) || (! -r $fullname)) {
|
|
print "\nERROR: $fullname does not exist or not readable.\n";
|
|
print "Did you do a make?\n";
|
|
return 1;
|
|
}
|
|
|
|
# Create a hash entry for each symbol table (text) entry.
|
|
foreach $_ (`$nm $fullname`) {
|
|
if (/^0{0,7}(\d{0,8})\s[tT]\s(\w{1,8})\n$/) {
|
|
${$shortname."_hash"}{$1} = $2;
|
|
}
|
|
}
|
|
|
|
# Create hash entries for every possible pc value. This will pay off.
|
|
@lines = sort { $a <=> $b } keys %{$shortname."_hash"};
|
|
|
|
for ($y = 0; $y <= $#lines; $y++) {
|
|
for ($z = @lines->[$y] + 1; $z < @lines->[$y + 1]; $z++) {
|
|
${$shortname."_hash"}{$z} =
|
|
${$shortname."_hash"}{@lines->[$y]}
|
|
}
|
|
}
|
|
}
|
|
|
|
# Clock and system are in kernel image but are seperate processes.
|
|
push(@exes, "clock");
|
|
push(@exes, "system");
|
|
%clock_hash = %kernel_hash;
|
|
%system_hash = %kernel_hash;
|
|
|
|
print ".\n\n";
|
|
return 0;
|
|
}
|
|
|
|
|
|
sub process_datafile
|
|
{
|
|
my %res = ();
|
|
my %merged = ();
|
|
my $file = shift;
|
|
my $buf, $pc, $exe, $total_system_perc;
|
|
|
|
unless (open(FILE, $file)) {
|
|
print "\nERROR: Unable to open $file: $!\n";
|
|
return 0;
|
|
}
|
|
|
|
# First line: check file type.
|
|
$_ = <FILE>; chomp;
|
|
if (!/^stat$/) {
|
|
if (/^call$/) {
|
|
print "Call Profiling output file: ";
|
|
print "Use cprofalyze.pl instead.\n";
|
|
} else {
|
|
print "Not a profiling output file.\n";
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
$file =~ s/^.*\///;
|
|
|
|
printf "\n========================================";
|
|
printf "========================================\n";
|
|
printf("Data file: %s\n", $file);
|
|
printf "========================================";
|
|
printf "========================================\n\n";
|
|
|
|
# Read header with total, idle, system and user hits.
|
|
$_ = <FILE>;
|
|
chomp;
|
|
|
|
($total_hits, $idle_hits, $system_hits, $user_hits) = split (/ /);
|
|
|
|
my $system_perc = sprintf("%3.f", $system_hits / $total_hits * 100);
|
|
my $user_perc = sprintf("%3.f", $user_hits / $total_hits * 100);
|
|
my $idle_perc = 100 - $system_perc - $user_perc;
|
|
|
|
printf(" System process ticks: %10d (%3d%%)\n", $system_hits, $system_perc);
|
|
printf(" User process ticks: %10d (%3d%%)", $user_hits, $user_perc);
|
|
printf(" Details of system process\n");
|
|
printf(" Idle time ticks: %10d (%3d%%)", $idle_hits, $idle_perc);
|
|
printf(" samples, aggregated and\n");
|
|
printf(" ---------- ----");
|
|
printf(" per process, are below.\n");
|
|
printf(" Total ticks: %10d (100%)\n\n", $total_hits);
|
|
|
|
# Read sample records from file and increase relevant counters.
|
|
until (eof(FILE)) {
|
|
read(FILE, $buf, $SAMPLE_SIZE) == $SAMPLE_SIZE or die ("Short read.");
|
|
($exe, $pc) = unpack("Z8i", $buf);
|
|
|
|
# We can access the hash by pc because they are all in there.
|
|
if (!defined(${$exe."_hash"}{$pc})) {
|
|
print "ERROR: Undefined in symbol table indexes: ";
|
|
print "executable $exe address $pc\n";
|
|
print "Did you include this executable in the configuration?\n";
|
|
return 1;
|
|
}
|
|
$res{$exe}{${$exe."_hash"}{$pc}} ++;
|
|
}
|
|
|
|
# We only need to continue with executables that had any hits.
|
|
my @actives = ();
|
|
foreach my $exe (@exes) {
|
|
$exe = short_name($exe);
|
|
next if (!exists($res{$exe}));
|
|
push(@actives, $exe);
|
|
}
|
|
|
|
# Calculate number of samples for each executable and create aggregated hash.
|
|
%exe_hits = ();
|
|
foreach $exe (@actives) {
|
|
foreach my $hits (values %{$res{$exe}}) {
|
|
$exe_hits{$exe} += $hits;
|
|
}
|
|
|
|
foreach my $key (keys %{$res{$exe}}) {
|
|
$merged{sprintf("%8s %8s", $exe, $key)} = $res{$exe}{$key};
|
|
}
|
|
}
|
|
|
|
$total_system_perc = 0;
|
|
# Print the aggregated results.
|
|
process_hash("", \%merged);
|
|
|
|
# Print results for each executable in decreasing order.
|
|
foreach my $exe
|
|
(reverse sort { $exe_hits{$a} <=> $exe_hits{$b} } keys %exe_hits)
|
|
{
|
|
process_hash($exe, \%{$res{$exe}}) if
|
|
$exe_hits{$exe} >= $system_hits / 100 * $MINIMUM_PERC;
|
|
}
|
|
|
|
# Print total of processes <threshold.
|
|
printf "----------------------------------------";
|
|
printf "----------------------------------------\n";
|
|
printf("%-47s %5.1f%% of system process samples\n",
|
|
"processes <$MINIMUM_PERC% (not showing functions)",
|
|
100 - $total_system_perc);
|
|
printf "----------------------------------------";
|
|
printf "----------------------------------------\n";
|
|
printf("%-47s 100.0%%\n\n", "total");
|
|
|
|
close(FILE);
|
|
return 0;
|
|
}
|
|
|
|
|
|
sub process_hash
|
|
{
|
|
my $exe = shift;
|
|
my $ref = shift;
|
|
%hash = %{$ref};
|
|
my $aggr = $exe eq "";
|
|
|
|
# Print a header.
|
|
printf "----------------------------------------";
|
|
printf "----------------------------------------\n";
|
|
if ($aggr) {
|
|
$astr_max = 55;
|
|
$perc_hits = $system_hits / 100;
|
|
printf("Total system process time %46d samples\n", $system_hits);
|
|
} else {
|
|
$astr_max = 64;
|
|
$perc_hits = $exe_hits{$exe} / 100;
|
|
$total_system_perc += $exe_perc =
|
|
sprintf("%5.1f", $exe_hits{$exe} / $system_hits * 100);
|
|
printf("%-47s %5.1f%% of system process samples\n", $exe, $exe_perc);
|
|
}
|
|
printf "----------------------------------------";
|
|
printf "----------------------------------------\n";
|
|
|
|
|
|
# Delete functions <threshold. Get percentage for all functions >threshold.
|
|
my $below_thres_hits;
|
|
my $total_exe_perc;
|
|
foreach my $func (keys %hash)
|
|
{
|
|
if ($hash{$func} < $perc_hits * $MINIMUM_PERC) {
|
|
$below_thres_hits += $hash{$func};
|
|
delete($hash{$func});
|
|
} else {
|
|
$total_exe_perc += sprintf("%3.1f", $hash{$func} / $perc_hits);
|
|
}
|
|
}
|
|
|
|
# Now print the hash entries in decreasing order.
|
|
@sorted = reverse sort { $hash{$a} <=> $hash{$b} } keys %hash;
|
|
|
|
$astr_hits = ($hash{@sorted->[0]} > $below_thres_hits ?
|
|
$hash{@sorted->[0]} : $below_thres_hits) / $astr_max;
|
|
|
|
foreach $func (@sorted) {
|
|
process_line($func, $hash{$func});
|
|
}
|
|
|
|
# Print <threshold hits.
|
|
my $below_thres;
|
|
if ($aggr) { $below_thres = " "; }
|
|
$below_thres .= sprintf("%8s", "<" . $MINIMUM_PERC . "%");
|
|
process_line($below_thres, $below_thres_hits, 100 - $total_exe_perc);
|
|
|
|
# Print footer.
|
|
printf "----------------------------------------";
|
|
printf "----------------------------------------\n";
|
|
printf("%-73s 100.0%%\n\n", $aggr ? "total" : $exe);
|
|
}
|
|
|
|
|
|
sub process_line
|
|
{
|
|
my $func = shift;
|
|
my $hits = shift;
|
|
|
|
my $perc = $hits / $perc_hits;
|
|
my $astr = $hits / $astr_hits;
|
|
|
|
if ($aggr) {
|
|
print "$func ";
|
|
} else {
|
|
printf("%8s ", $func);
|
|
}
|
|
for ($i = 0; $i < $astr_max; $i++) {
|
|
if ($i <= $astr) {
|
|
print "*";
|
|
} else {
|
|
print " ";
|
|
}
|
|
}
|
|
print " ";
|
|
if (my $rest = shift) {
|
|
printf("%5.1f%%\n", $rest);
|
|
} else {
|
|
printf("%5.1f%%\n", $perc);
|
|
}
|
|
}
|
|
|