) {
s/\r//g; # turn windows-looking lines into unix-looking lines
$l++;
my $indent = Indentation($_);
if ($l >= $firstline) {
if ($first_indentation < 0 && $indent >= 0) {
$first_indentation = $indent;
last if ($first_indentation == 0);
}
}
if ($l >= $lastline && $indent >= 0) {
if ($indent >= $first_indentation) {
$lastline = $l+1;
} else {
last;
}
}
}
close(FILE);
}
# Assign all samples to the range $firstline,$lastline,
# Hack 4: If an instruction does not occur in the range, its samples
# are moved to the next instruction that occurs in the range.
my $samples1 = {}; # Map from line number to flat count
my $samples2 = {}; # Map from line number to cumulative count
my $running1 = 0; # Unassigned flat counts
my $running2 = 0; # Unassigned cumulative counts
my $total1 = 0; # Total flat counts
my $total2 = 0; # Total cumulative counts
my %disasm = (); # Map from line number to disassembly
my $running_disasm = ""; # Unassigned disassembly
my $skip_marker = "---\n";
if ($html) {
$skip_marker = "";
for (my $l = $firstline; $l <= $lastline; $l++) {
$disasm{$l} = "";
}
}
my $last_dis_filename = '';
my $last_dis_linenum = -1;
my $last_touched_line = -1; # To detect gaps in disassembly for a line
foreach my $e (@instructions) {
# Add up counts for all address that fall inside this instruction
my $c1 = 0;
my $c2 = 0;
for (my $a = $e->[0]; $a lt $e->[4]; $a = AddressInc($a)) {
$c1 += GetEntry($flat, $a);
$c2 += GetEntry($cumulative, $a);
}
if ($html) {
my $dis = sprintf(" %6s %6s \t\t%8s: %s ",
HtmlPrintNumber($c1),
HtmlPrintNumber($c2),
UnparseAddress($offset, $e->[0]),
CleanDisassembly($e->[3]));
# Append the most specific source line associated with this instruction
if (length($dis) < 80) { $dis .= (' ' x (80 - length($dis))) };
$dis = HtmlEscape($dis);
my $f = $e->[5];
my $l = $e->[6];
if ($f ne $last_dis_filename) {
$dis .= sprintf("%s:%d",
HtmlEscape(CleanFileName($f)), $l);
} elsif ($l ne $last_dis_linenum) {
# De-emphasize the unchanged file name portion
$dis .= sprintf("%s" .
":%d",
HtmlEscape(CleanFileName($f)), $l);
} else {
# De-emphasize the entire location
$dis .= sprintf("%s:%d",
HtmlEscape(CleanFileName($f)), $l);
}
$last_dis_filename = $f;
$last_dis_linenum = $l;
$running_disasm .= $dis;
$running_disasm .= "\n";
}
$running1 += $c1;
$running2 += $c2;
$total1 += $c1;
$total2 += $c2;
my $file = $e->[1];
my $line = $e->[2];
if (($file eq $filename) &&
($line >= $firstline) &&
($line <= $lastline)) {
# Assign all accumulated samples to this line
AddEntry($samples1, $line, $running1);
AddEntry($samples2, $line, $running2);
$running1 = 0;
$running2 = 0;
if ($html) {
if ($line != $last_touched_line && $disasm{$line} ne '') {
$disasm{$line} .= "\n";
}
$disasm{$line} .= $running_disasm;
$running_disasm = '';
$last_touched_line = $line;
}
}
}
# Assign any leftover samples to $lastline
AddEntry($samples1, $lastline, $running1);
AddEntry($samples2, $lastline, $running2);
if ($html) {
if ($lastline != $last_touched_line && $disasm{$lastline} ne '') {
$disasm{$lastline} .= "\n";
}
$disasm{$lastline} .= $running_disasm;
}
if ($html) {
printf $output (
"%s
%s\n\n" .
"Total:%6s %6s (flat / cumulative %s)\n",
HtmlEscape(ShortFunctionName($routine)),
HtmlEscape(CleanFileName($filename)),
Unparse($total1),
Unparse($total2),
Units());
} else {
printf $output (
"ROUTINE ====================== %s in %s\n" .
"%6s %6s Total %s (flat / cumulative)\n",
ShortFunctionName($routine),
CleanFileName($filename),
Unparse($total1),
Unparse($total2),
Units());
}
if (!open(FILE, "<$filename")) {
print STDERR "$filename: $!\n";
return 0;
}
my $l = 0;
while () {
s/\r//g; # turn windows-looking lines into unix-looking lines
$l++;
if ($l >= $firstline - 5 &&
(($l <= $oldlastline + 5) || ($l <= $lastline))) {
chop;
my $text = $_;
if ($l == $firstline) { print $output $skip_marker; }
my $n1 = GetEntry($samples1, $l);
my $n2 = GetEntry($samples2, $l);
if ($html) {
# Emit a span that has one of the following classes:
# livesrc -- has samples
# deadsrc -- has disassembly, but with no samples
# nop -- has no matching disasembly
# Also emit an optional span containing disassembly.
my $dis = $disasm{$l};
my $asm = "";
if (defined($dis) && $dis ne '') {
$asm = "" . $dis . "";
}
my $source_class = (($n1 + $n2 > 0)
? "livesrc"
: (($asm ne "") ? "deadsrc" : "nop"));
printf $output (
"%5d " .
"%6s %6s %s%s\n",
$l, $source_class,
HtmlPrintNumber($n1),
HtmlPrintNumber($n2),
HtmlEscape($text),
$asm);
} else {
printf $output(
"%6s %6s %4d: %s\n",
UnparseAlt($n1),
UnparseAlt($n2),
$l,
$text);
}
if ($l == $lastline) { print $output $skip_marker; }
};
}
close(FILE);
if ($html) {
print $output "\n";
}
return 1;
}
# Return the source line for the specified file/linenumber.
# Returns undef if not found.
sub SourceLine {
my $file = shift;
my $line = shift;
# Look in cache
if (!defined($main::source_cache{$file})) {
if (100 < scalar keys(%main::source_cache)) {
# Clear the cache when it gets too big
$main::source_cache = ();
}
# Read all lines from the file
if (!open(FILE, "<$file")) {
print STDERR "$file: $!\n";
$main::source_cache{$file} = []; # Cache the negative result
return undef;
}
my $lines = [];
push(@{$lines}, ""); # So we can use 1-based line numbers as indices
while () {
push(@{$lines}, $_);
}
close(FILE);
# Save the lines in the cache
$main::source_cache{$file} = $lines;
}
my $lines = $main::source_cache{$file};
if (($line < 0) || ($line > $#{$lines})) {
return undef;
} else {
return $lines->[$line];
}
}
# Print disassembly for one routine with interspersed source if available
sub PrintDisassembledFunction {
my $prog = shift;
my $offset = shift;
my $routine = shift;
my $flat = shift;
my $cumulative = shift;
my $start_addr = shift;
my $end_addr = shift;
my $total = shift;
# Disassemble all instructions
my @instructions = Disassemble($prog, $offset, $start_addr, $end_addr);
# Make array of counts per instruction
my @flat_count = ();
my @cum_count = ();
my $flat_total = 0;
my $cum_total = 0;
foreach my $e (@instructions) {
# Add up counts for all address that fall inside this instruction
my $c1 = 0;
my $c2 = 0;
for (my $a = $e->[0]; $a lt $e->[4]; $a = AddressInc($a)) {
$c1 += GetEntry($flat, $a);
$c2 += GetEntry($cumulative, $a);
}
push(@flat_count, $c1);
push(@cum_count, $c2);
$flat_total += $c1;
$cum_total += $c2;
}
# Print header with total counts
printf("ROUTINE ====================== %s\n" .
"%6s %6s %s (flat, cumulative) %.1f%% of total\n",
ShortFunctionName($routine),
Unparse($flat_total),
Unparse($cum_total),
Units(),
($cum_total * 100.0) / $total);
# Process instructions in order
my $current_file = "";
for (my $i = 0; $i <= $#instructions; ) {
my $e = $instructions[$i];
# Print the new file name whenever we switch files
if ($e->[1] ne $current_file) {
$current_file = $e->[1];
my $fname = $current_file;
$fname =~ s|^\./||; # Trim leading "./"
# Shorten long file names
if (length($fname) >= 58) {
$fname = "..." . substr($fname, -55);
}
printf("-------------------- %s\n", $fname);
}
# TODO: Compute range of lines to print together to deal with
# small reorderings.
my $first_line = $e->[2];
my $last_line = $first_line;
my %flat_sum = ();
my %cum_sum = ();
for (my $l = $first_line; $l <= $last_line; $l++) {
$flat_sum{$l} = 0;
$cum_sum{$l} = 0;
}
# Find run of instructions for this range of source lines
my $first_inst = $i;
while (($i <= $#instructions) &&
($instructions[$i]->[2] >= $first_line) &&
($instructions[$i]->[2] <= $last_line)) {
$e = $instructions[$i];
$flat_sum{$e->[2]} += $flat_count[$i];
$cum_sum{$e->[2]} += $cum_count[$i];
$i++;
}
my $last_inst = $i - 1;
# Print source lines
for (my $l = $first_line; $l <= $last_line; $l++) {
my $line = SourceLine($current_file, $l);
if (!defined($line)) {
$line = "?\n";
next;
} else {
$line =~ s/^\s+//;
}
printf("%6s %6s %5d: %s",
UnparseAlt($flat_sum{$l}),
UnparseAlt($cum_sum{$l}),
$l,
$line);
}
# Print disassembly
for (my $x = $first_inst; $x <= $last_inst; $x++) {
my $e = $instructions[$x];
printf("%6s %6s %8s: %6s\n",
UnparseAlt($flat_count[$x]),
UnparseAlt($cum_count[$x]),
UnparseAddress($offset, $e->[0]),
CleanDisassembly($e->[3]));
}
}
}
# Print DOT graph
sub PrintDot {
my $prog = shift;
my $symbols = shift;
my $raw = shift;
my $flat = shift;
my $cumulative = shift;
my $overall_total = shift;
# Get total
my $local_total = TotalProfile($flat);
my $nodelimit = int($main::opt_nodefraction * $local_total);
my $edgelimit = int($main::opt_edgefraction * $local_total);
my $nodecount = $main::opt_nodecount;
# Find nodes to include
my @list = (sort { abs(GetEntry($cumulative, $b)) <=>
abs(GetEntry($cumulative, $a))
|| $a cmp $b }
keys(%{$cumulative}));
my $last = $nodecount - 1;
if ($last > $#list) {
$last = $#list;
}
while (($last >= 0) &&
(abs(GetEntry($cumulative, $list[$last])) <= $nodelimit)) {
$last--;
}
if ($last < 0) {
print STDERR "No nodes to print\n";
return 0;
}
if ($nodelimit > 0 || $edgelimit > 0) {
printf STDERR ("Dropping nodes with <= %s %s; edges with <= %s abs(%s)\n",
Unparse($nodelimit), Units(),
Unparse($edgelimit), Units());
}
# Open DOT output file
my $output;
my $escaped_dot = ShellEscape(@DOT);
my $escaped_ps2pdf = ShellEscape(@PS2PDF);
if ($main::opt_gv) {
my $escaped_outfile = ShellEscape(TempName($main::next_tmpfile, "ps"));
$output = "| $escaped_dot -Tps2 >$escaped_outfile";
} elsif ($main::opt_evince) {
my $escaped_outfile = ShellEscape(TempName($main::next_tmpfile, "pdf"));
$output = "| $escaped_dot -Tps2 | $escaped_ps2pdf - $escaped_outfile";
} elsif ($main::opt_ps) {
$output = "| $escaped_dot -Tps2";
} elsif ($main::opt_pdf) {
$output = "| $escaped_dot -Tps2 | $escaped_ps2pdf - -";
} elsif ($main::opt_web || $main::opt_svg) {
# We need to post-process the SVG, so write to a temporary file always.
my $escaped_outfile = ShellEscape(TempName($main::next_tmpfile, "svg"));
$output = "| $escaped_dot -Tsvg >$escaped_outfile";
} elsif ($main::opt_gif) {
$output = "| $escaped_dot -Tgif";
} else {
$output = ">&STDOUT";
}
open(DOT, $output) || error("$output: $!\n");
# Title
printf DOT ("digraph \"%s; %s %s\" {\n",
$prog,
Unparse($overall_total),
Units());
if ($main::opt_pdf) {
# The output is more printable if we set the page size for dot.
printf DOT ("size=\"8,11\"\n");
}
printf DOT ("node [width=0.375,height=0.25];\n");
# Print legend
printf DOT ("Legend [shape=box,fontsize=24,shape=plaintext," .
"label=\"%s\\l%s\\l%s\\l%s\\l%s\\l\"];\n",
$prog,
sprintf("Total %s: %s", Units(), Unparse($overall_total)),
sprintf("Focusing on: %s", Unparse($local_total)),
sprintf("Dropped nodes with <= %s abs(%s)",
Unparse($nodelimit), Units()),
sprintf("Dropped edges with <= %s %s",
Unparse($edgelimit), Units())
);
# Print nodes
my %node = ();
my $nextnode = 1;
foreach my $a (@list[0..$last]) {
# Pick font size
my $f = GetEntry($flat, $a);
my $c = GetEntry($cumulative, $a);
my $fs = 8;
if ($local_total > 0) {
$fs = 8 + (50.0 * sqrt(abs($f * 1.0 / $local_total)));
}
$node{$a} = $nextnode++;
my $sym = $a;
$sym =~ s/\s+/\\n/g;
$sym =~ s/::/\\n/g;
# Extra cumulative info to print for non-leaves
my $extra = "";
if ($f != $c) {
$extra = sprintf("\\rof %s (%s)",
Unparse($c),
Percent($c, $local_total));
}
my $style = "";
if ($main::opt_heapcheck) {
if ($f > 0) {
# make leak-causing nodes more visible (add a background)
$style = ",style=filled,fillcolor=gray"
} elsif ($f < 0) {
# make anti-leak-causing nodes (which almost never occur)
# stand out as well (triple border)
$style = ",peripheries=3"
}
}
printf DOT ("N%d [label=\"%s\\n%s (%s)%s\\r" .
"\",shape=box,fontsize=%.1f%s];\n",
$node{$a},
$sym,
Unparse($f),
Percent($f, $local_total),
$extra,
$fs,
$style,
);
}
# Get edges and counts per edge
my %edge = ();
my $n;
my $fullname_to_shortname_map = {};
FillFullnameToShortnameMap($symbols, $fullname_to_shortname_map);
foreach my $k (keys(%{$raw})) {
# TODO: omit low %age edges
$n = $raw->{$k};
my @translated = TranslateStack($symbols, $fullname_to_shortname_map, $k);
for (my $i = 1; $i <= $#translated; $i++) {
my $src = $translated[$i];
my $dst = $translated[$i-1];
#next if ($src eq $dst); # Avoid self-edges?
if (exists($node{$src}) && exists($node{$dst})) {
my $edge_label = "$src\001$dst";
if (!exists($edge{$edge_label})) {
$edge{$edge_label} = 0;
}
$edge{$edge_label} += $n;
}
}
}
# Print edges (process in order of decreasing counts)
my %indegree = (); # Number of incoming edges added per node so far
my %outdegree = (); # Number of outgoing edges added per node so far
foreach my $e (sort { $edge{$b} <=> $edge{$a} } keys(%edge)) {
my @x = split(/\001/, $e);
$n = $edge{$e};
# Initialize degree of kept incoming and outgoing edges if necessary
my $src = $x[0];
my $dst = $x[1];
if (!exists($outdegree{$src})) { $outdegree{$src} = 0; }
if (!exists($indegree{$dst})) { $indegree{$dst} = 0; }
my $keep;
if ($indegree{$dst} == 0) {
# Keep edge if needed for reachability
$keep = 1;
} elsif (abs($n) <= $edgelimit) {
# Drop if we are below --edgefraction
$keep = 0;
} elsif ($outdegree{$src} >= $main::opt_maxdegree ||
$indegree{$dst} >= $main::opt_maxdegree) {
# Keep limited number of in/out edges per node
$keep = 0;
} else {
$keep = 1;
}
if ($keep) {
$outdegree{$src}++;
$indegree{$dst}++;
# Compute line width based on edge count
my $fraction = abs($local_total ? (3 * ($n / $local_total)) : 0);
if ($fraction > 1) { $fraction = 1; }
my $w = $fraction * 2;
if ($w < 1 && ($main::opt_web || $main::opt_svg)) {
# SVG output treats line widths < 1 poorly.
$w = 1;
}
# Dot sometimes segfaults if given edge weights that are too large, so
# we cap the weights at a large value
my $edgeweight = abs($n) ** 0.7;
if ($edgeweight > 100000) { $edgeweight = 100000; }
$edgeweight = int($edgeweight);
my $style = sprintf("setlinewidth(%f)", $w);
if ($x[1] =~ m/\(inline\)/) {
$style .= ",dashed";
}
# Use a slightly squashed function of the edge count as the weight
printf DOT ("N%s -> N%s [label=%s, weight=%d, style=\"%s\"];\n",
$node{$x[0]},
$node{$x[1]},
Unparse($n),
$edgeweight,
$style);
}
}
print DOT ("}\n");
close(DOT);
if ($main::opt_web || $main::opt_svg) {
# Rewrite SVG to be more usable inside web browser.
RewriteSvg(TempName($main::next_tmpfile, "svg"));
}
return 1;
}
sub RewriteSvg {
my $svgfile = shift;
open(SVG, $svgfile) || die "open temp svg: $!";
my @svg =