hotcoldgraph.pl 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. #!/usr/bin/perl -w
  2. #
  3. # hotcoldgraph.pl flame/cold stack grapher.
  4. #
  5. # EXPERIMENTAL: This is a work in progress, and may not work properly.
  6. #
  7. # This takes on and off-cpu stack timings (see hcstackcollapse.pl) and
  8. # renders a call graph, allowing latency in codepaths to be quickly identified.
  9. #
  10. # USAGE: ./hotcoldgraph.pl input.txt > graph.svg
  11. #
  12. # grep funcA input.txt | ./hotcoldgraph.pl > graph.svg
  13. #
  14. # The input is stack frames and sample counts formatted as single lines. Each
  15. # frame in the stack is comma separated, with a space and count at the end of
  16. # the line. These can be generated using DTrace with stackcollapse.pl.
  17. #
  18. # The output graph shows relative presense of functions in stack samples. The
  19. # ordering on the x-axis has no meaning; since the data is samples, time order
  20. # of events is not known. The order used sorts function names alphabeticly.
  21. #
  22. # HISTORY
  23. #
  24. # This was inspired by Neelakanth Nadgir's excellent function_call_graph.rb
  25. # program, which visualized function entry and return trace events. As Neel
  26. # wrote: "The output displayed is inspired by Roch's CallStackAnalyzer which
  27. # was in turn inspired by the work on vftrace by Jan Boerhout". See:
  28. # http://blogs.sun.com/realneel/entry/visualizing_callstacks_via_dtrace_and
  29. #
  30. # For the on-CPU graph only, see flamegraph.pl.
  31. #
  32. # Copyright 2013 Joyent, Inc. All rights reserved.
  33. # Copyright 2013 Brendan Gregg. All rights reserved.
  34. #
  35. # CDDL HEADER START
  36. #
  37. # The contents of this file are subject to the terms of the
  38. # Common Development and Distribution License (the "License").
  39. # You may not use this file except in compliance with the License.
  40. #
  41. # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
  42. # or http://opensource.org/licenses/CDDL-1.0.
  43. # See the License for the specific language governing permissions
  44. # and limitations under the License.
  45. #
  46. # When distributing Covered Code, include this CDDL HEADER in each
  47. # file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  48. # If applicable, add the following below this CDDL HEADER, with the
  49. # fields enclosed by brackets "[]" replaced with your own identifying
  50. # information: Portions Copyright [yyyy] [name of copyright owner]
  51. #
  52. # CDDL HEADER END
  53. #
  54. # 10-Sep-2011 Brendan Gregg Created this.
  55. use strict;
  56. # tunables
  57. my $fonttype = "Verdana";
  58. my $imagewidth = 1200; # max width, pixels
  59. my $frameheight = 16; # max height is dynamic
  60. my $fontsize = 12; # base text size
  61. my $minwidth = 0.1; # min function width, pixels
  62. # internals
  63. my $ypad1 = $fontsize * 4; # pad top, include title
  64. my $ypad2 = $fontsize * 2 + 10; # pad bottom, include labels
  65. my $xpad = 10; # pad lefm and right
  66. my $timemax = 0;
  67. my $depthmax = 0;
  68. my %Events;
  69. # SVG functions
  70. { package SVG;
  71. sub new {
  72. my $class = shift;
  73. my $self = {};
  74. bless ($self, $class);
  75. return $self;
  76. }
  77. sub header {
  78. my ($self, $w, $h) = @_;
  79. $self->{svg} .= <<SVG;
  80. <?xml version="1.0" standalone="no"?>
  81. <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
  82. <svg version="1.1" width="$w" height="$h" onload="init(evt)" viewBox="0 0 $w $h" xmlns="http://www.w3.org/2000/svg" >
  83. SVG
  84. }
  85. sub include {
  86. my ($self, $content) = @_;
  87. $self->{svg} .= $content;
  88. }
  89. sub colorAllocate {
  90. my ($self, $r, $g, $b) = @_;
  91. return "rgb($r,$g,$b)";
  92. }
  93. sub filledRectangle {
  94. my ($self, $x1, $y1, $x2, $y2, $fill, $extra) = @_;
  95. $x1 = sprintf "%0.1f", $x1;
  96. $x2 = sprintf "%0.1f", $x2;
  97. my $w = sprintf "%0.1f", $x2 - $x1;
  98. my $h = sprintf "%0.1f", $y2 - $y1;
  99. $extra = defined $extra ? $extra : "";
  100. $self->{svg} .= qq/<rect x="$x1" y="$y1" width="$w" height="$h" fill="$fill" $extra \/>\n/;
  101. }
  102. sub stringTTF {
  103. my ($self, $color, $font, $size, $angle, $x, $y, $str, $loc, $extra) = @_;
  104. $loc = defined $loc ? $loc : "left";
  105. $extra = defined $extra ? $extra : "";
  106. $self->{svg} .= qq/<text text-anchor="$loc" x="$x" y="$y" font-size="$size" font-family="$font" fill="$color" $extra >$str<\/text>\n/;
  107. }
  108. sub svg {
  109. my $self = shift;
  110. return "$self->{svg}</svg>\n";
  111. }
  112. 1;
  113. }
  114. sub color {
  115. my $type = shift;
  116. if (defined $type and $type eq "hot") {
  117. my $r = 205 + int(rand(50));
  118. my $g = 0 + int(rand(230));
  119. my $b = 0 + int(rand(55));
  120. return "rgb($r,$g,$b)";
  121. }
  122. if (defined $type and $type eq "cold") {
  123. my $r = 0 + int(rand(40));
  124. my $b = 205 + int(rand(50));
  125. my $g = 0 + int(rand(150));
  126. return "rgb($r,$g,$b)";
  127. }
  128. return "rgb(0,0,0)";
  129. }
  130. my %Node;
  131. my %Tmp;
  132. sub flow {
  133. my ($a, $b, $ca, $cb, $v) = @_;
  134. my @A = split ",", $a;
  135. my @B = split ",", $b;
  136. my $len_a = $#A;
  137. my $len_b = $#B;
  138. $depthmax = $len_b if $len_b > $depthmax;
  139. my $i = 0;
  140. my $len_same = 0;
  141. for (; $i <= $len_a; $i++) {
  142. last if $i > $len_b;
  143. last if $A[$i] ne $B[$i];
  144. }
  145. $len_same = $i;
  146. $len_same = 0 if $ca != $cb;
  147. for ($i = $len_a; $i >= $len_same; $i--) {
  148. my $k = "$A[$i]-$i";
  149. # a unique ID is constructed from func-depth-etime;
  150. # func-depth isn't unique, it may be repeated later.
  151. $Node{"$k-$v-$ca"}->{stime} = $Tmp{$k}->{stime};
  152. delete $Tmp{$k}->{stime};
  153. delete $Tmp{$k};
  154. }
  155. for ($i = $len_same; $i <= $len_b; $i++) {
  156. my $k = "$B[$i]-$i";
  157. $Tmp{$k}->{stime} = $v;
  158. }
  159. }
  160. # Parse input
  161. my @Data = <>;
  162. my $laststack = "";
  163. my $lastcpu = 0;
  164. my $time = 0;
  165. foreach (sort @Data) {
  166. chomp;
  167. my ($stack, $cpu, $samples) = split ' ';
  168. $stack = ",$stack";
  169. next unless defined $samples;
  170. flow($laststack, $stack, $lastcpu, $cpu, $time);
  171. $time += $samples;
  172. $laststack = $stack;
  173. $lastcpu = $cpu;
  174. }
  175. flow($laststack, "", $lastcpu, 0, $time);
  176. $timemax = $time or die "ERROR: No stack counts found\n";
  177. # Draw canvas
  178. my $widthpertime = ($imagewidth - 2 * $xpad) / $timemax;
  179. my $imageheight = ($depthmax * $frameheight) + $ypad1 + $ypad2;
  180. my $im = SVG->new();
  181. $im->header($imagewidth, $imageheight);
  182. my $inc = <<INC;
  183. <defs >
  184. <linearGradient id="background" y1="0" y2="1" x1="0" x2="0" >
  185. <stop stop-color="#eeeeee" offset="5%" />
  186. <stop stop-color="#eeeeb0" offset="95%" />
  187. </linearGradient>
  188. </defs>
  189. <style type="text/css">
  190. rect[rx]:hover { stroke:black; stroke-width:1; }
  191. text:hover { stroke:black; stroke-width:1; stroke-opacity:0.35; }
  192. </style>
  193. <script type="text/ecmascript">
  194. <![CDATA[
  195. var details;
  196. function init(evt) { details = document.getElementById("details").firstChild; }
  197. function s(info) { details.nodeValue = info; }
  198. function c() { details.nodeValue = ' '; }
  199. ]]>
  200. </script>
  201. INC
  202. $im->include($inc);
  203. $im->filledRectangle(0, 0, $imagewidth, $imageheight, 'url(#background)');
  204. my ($white, $black, $vvdgrey, $vdgrey) = (
  205. $im->colorAllocate(255, 255, 255),
  206. $im->colorAllocate(0, 0, 0),
  207. $im->colorAllocate(40, 40, 40),
  208. $im->colorAllocate(160, 160, 160),
  209. );
  210. $im->stringTTF($black, $fonttype, $fontsize + 5, 0.0, int($imagewidth / 2), $fontsize * 2, "Flame Graph", "middle");
  211. $im->stringTTF($black, $fonttype, $fontsize, 0.0, $xpad, $imageheight - ($ypad2 / 2), 'Function:');
  212. $im->stringTTF($black, $fonttype, $fontsize, 0.0, $xpad + 60, $imageheight - ($ypad2 / 2), " ", "", 'id="details"');
  213. # Draw frames
  214. foreach my $id (keys %Node) {
  215. my ($func, $depth, $etime, $cpu) = split "-", $id;
  216. die "missing start for $id" if !defined $Node{$id}->{stime};
  217. my $stime = $Node{$id}->{stime};
  218. my $samples = $etime - $stime;
  219. my $x1 = $xpad + $stime * $widthpertime;
  220. my $x2 = $xpad + $etime * $widthpertime;
  221. my $width = $x2 - $x1;
  222. next if $width < $minwidth;
  223. my $y1 = $imageheight - $ypad2 - ($depth + 1) * $frameheight + 1;
  224. my $y2 = $imageheight - $ypad2 - $depth * $frameheight;
  225. my $info;
  226. if ($func eq "" and $depth == 0) {
  227. $info = "all ($samples ms, 100%)";
  228. } else {
  229. my $pct = sprintf "%.2f", ((100 * $samples) / $timemax);
  230. $info = "$func ($samples ms, $pct%)";
  231. }
  232. my $color = $cpu ? "hot" : "cold";
  233. $im->filledRectangle($x1, $y1, $x2, $y2, color($color), 'rx="2" ry="2" onmouseover="s(' . "'$info'" . ')" onmouseout="c()"');
  234. if ($width > 50) {
  235. my $chars = int($width / (0.7 * $fontsize));
  236. my $text = substr $func, 0, $chars;
  237. $text .= ".." if $chars < length $func;
  238. $im->stringTTF($black, $fonttype, $fontsize, 0.0, $x1 + 3, 3 + ($y1 + $y2) / 2, $text, "",
  239. 'onmouseover="s(' . "'$info'" . ')" onmouseout="c()"');
  240. }
  241. }
  242. print $im->svg;