├── .gitignore ├── flamegraph.pl ├── pyflame ├── readme.md ├── requirements.txt ├── templates ├── list.html └── upload.html ├── uploads └── .gitkeep └── wsgi.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | nohup.out 3 | uploads 4 | -------------------------------------------------------------------------------- /flamegraph.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w 2 | # 3 | # flamegraph.pl flame stack grapher. 4 | # 5 | # This takes stack samples and renders a call graph, allowing hot functions 6 | # and codepaths to be quickly identified. Stack samples can be generated using 7 | # tools such as DTrace, perf, SystemTap, and Instruments. 8 | # 9 | # USAGE: ./flamegraph.pl [options] input.txt > graph.svg 10 | # 11 | # grep funcA input.txt | ./flamegraph.pl [options] > graph.svg 12 | # 13 | # Then open the resulting .svg in a web browser, for interactivity: mouse-over 14 | # frames for info, click to zoom, and ctrl-F to search. 15 | # 16 | # Options are listed in the usage message (--help). 17 | # 18 | # The input is stack frames and sample counts formatted as single lines. Each 19 | # frame in the stack is semicolon separated, with a space and count at the end 20 | # of the line. These can be generated for Linux perf script output using 21 | # stackcollapse-perf.pl, for DTrace using stackcollapse.pl, and for other tools 22 | # using the other stackcollapse programs. Example input: 23 | # 24 | # swapper;start_kernel;rest_init;cpu_idle;default_idle;native_safe_halt 1 25 | # 26 | # An optional extra column of counts can be provided to generate a differential 27 | # flame graph of the counts, colored red for more, and blue for less. This 28 | # can be useful when using flame graphs for non-regression testing. 29 | # See the header comment in the difffolded.pl program for instructions. 30 | # 31 | # The input functions can optionally have annotations at the end of each 32 | # function name, following a precedent by some tools (Linux perf's _[k]): 33 | # _[k] for kernel 34 | # _[i] for inlined 35 | # _[j] for jit 36 | # _[w] for waker 37 | # Some of the stackcollapse programs support adding these annotations, eg, 38 | # stackcollapse-perf.pl --kernel --jit. They are used merely for colors by 39 | # some palettes, eg, flamegraph.pl --color=java. 40 | # 41 | # The output flame graph shows relative presence of functions in stack samples. 42 | # The ordering on the x-axis has no meaning; since the data is samples, time 43 | # order of events is not known. The order used sorts function names 44 | # alphabetically. 45 | # 46 | # While intended to process stack samples, this can also process stack traces. 47 | # For example, tracing stacks for memory allocation, or resource usage. You 48 | # can use --title to set the title to reflect the content, and --countname 49 | # to change "samples" to "bytes" etc. 50 | # 51 | # There are a few different palettes, selectable using --color. By default, 52 | # the colors are selected at random (except for differentials). Functions 53 | # called "-" will be printed gray, which can be used for stack separators (eg, 54 | # between user and kernel stacks). 55 | # 56 | # HISTORY 57 | # 58 | # This was inspired by Neelakanth Nadgir's excellent function_call_graph.rb 59 | # program, which visualized function entry and return trace events. As Neel 60 | # wrote: "The output displayed is inspired by Roch's CallStackAnalyzer which 61 | # was in turn inspired by the work on vftrace by Jan Boerhout". See: 62 | # https://blogs.oracle.com/realneel/entry/visualizing_callstacks_via_dtrace_and 63 | # 64 | # Copyright 2016 Netflix, Inc. 65 | # Copyright 2011 Joyent, Inc. All rights reserved. 66 | # Copyright 2011 Brendan Gregg. All rights reserved. 67 | # 68 | # CDDL HEADER START 69 | # 70 | # The contents of this file are subject to the terms of the 71 | # Common Development and Distribution License (the "License"). 72 | # You may not use this file except in compliance with the License. 73 | # 74 | # You can obtain a copy of the license at docs/cddl1.txt or 75 | # http://opensource.org/licenses/CDDL-1.0. 76 | # See the License for the specific language governing permissions 77 | # and limitations under the License. 78 | # 79 | # When distributing Covered Code, include this CDDL HEADER in each 80 | # file and include the License file at docs/cddl1.txt. 81 | # If applicable, add the following below this CDDL HEADER, with the 82 | # fields enclosed by brackets "[]" replaced with your own identifying 83 | # information: Portions Copyright [yyyy] [name of copyright owner] 84 | # 85 | # CDDL HEADER END 86 | # 87 | # 11-Oct-2014 Adrien Mahieux Added zoom. 88 | # 21-Nov-2013 Shawn Sterling Added consistent palette file option 89 | # 17-Mar-2013 Tim Bunce Added options and more tunables. 90 | # 15-Dec-2011 Dave Pacheco Support for frames with whitespace. 91 | # 10-Sep-2011 Brendan Gregg Created this. 92 | 93 | use strict; 94 | 95 | use Getopt::Long; 96 | 97 | use open qw(:std :utf8); 98 | 99 | # tunables 100 | my $encoding; 101 | my $fonttype = "Verdana"; 102 | my $imagewidth = 1200; # max width, pixels 103 | my $frameheight = 16; # max height is dynamic 104 | my $fontsize = 12; # base text size 105 | my $fontwidth = 0.59; # avg width relative to fontsize 106 | my $minwidth = 0.1; # min function width, pixels 107 | my $nametype = "Function:"; # what are the names in the data? 108 | my $countname = "samples"; # what are the counts in the data? 109 | my $colors = "hot"; # color theme 110 | my $bgcolors = ""; # background color theme 111 | my $nameattrfile; # file holding function attributes 112 | my $timemax; # (override the) sum of the counts 113 | my $factor = 1; # factor to scale counts by 114 | my $hash = 0; # color by function name 115 | my $palette = 0; # if we use consistent palettes (default off) 116 | my %palette_map; # palette map hash 117 | my $pal_file = "palette.map"; # palette map file name 118 | my $stackreverse = 0; # reverse stack order, switching merge end 119 | my $inverted = 0; # icicle graph 120 | my $flamechart = 0; # produce a flame chart (sort by time, do not merge stacks) 121 | my $negate = 0; # switch differential hues 122 | my $titletext = ""; # centered heading 123 | my $titledefault = "Flame Graph"; # overwritten by --title 124 | my $titleinverted = "Icicle Graph"; # " " 125 | my $searchcolor = "rgb(230,0,230)"; # color for search highlighting 126 | my $notestext = ""; # embedded notes in SVG 127 | my $subtitletext = ""; # second level title (optional) 128 | my $help = 0; 129 | 130 | sub usage { 131 | die < outfile.svg\n 133 | --title TEXT # change title text 134 | --subtitle TEXT # second level title (optional) 135 | --width NUM # width of image (default 1200) 136 | --height NUM # height of each frame (default 16) 137 | --minwidth NUM # omit smaller functions (default 0.1 pixels) 138 | --fonttype FONT # font type (default "Verdana") 139 | --fontsize NUM # font size (default 12) 140 | --countname TEXT # count type label (default "samples") 141 | --nametype TEXT # name type label (default "Function:") 142 | --colors PALETTE # set color palette. choices are: hot (default), mem, 143 | # io, wakeup, chain, java, js, perl, red, green, blue, 144 | # aqua, yellow, purple, orange 145 | --bgcolors COLOR # set background colors. gradient choices are yellow 146 | # (default), blue, green, grey; flat colors use "#rrggbb" 147 | --hash # colors are keyed by function name hash 148 | --cp # use consistent palette (palette.map) 149 | --reverse # generate stack-reversed flame graph 150 | --inverted # icicle graph 151 | --flamechart # produce a flame chart (sort by time, do not merge stacks) 152 | --negate # switch differential hues (blue<->red) 153 | --notes TEXT # add notes comment in SVG (for debugging) 154 | --help # this message 155 | 156 | eg, 157 | $0 --title="Flame Graph: malloc()" trace.txt > graph.svg 158 | USAGE_END 159 | } 160 | 161 | GetOptions( 162 | 'fonttype=s' => \$fonttype, 163 | 'width=i' => \$imagewidth, 164 | 'height=i' => \$frameheight, 165 | 'encoding=s' => \$encoding, 166 | 'fontsize=f' => \$fontsize, 167 | 'fontwidth=f' => \$fontwidth, 168 | 'minwidth=f' => \$minwidth, 169 | 'title=s' => \$titletext, 170 | 'subtitle=s' => \$subtitletext, 171 | 'nametype=s' => \$nametype, 172 | 'countname=s' => \$countname, 173 | 'nameattr=s' => \$nameattrfile, 174 | 'total=s' => \$timemax, 175 | 'factor=f' => \$factor, 176 | 'colors=s' => \$colors, 177 | 'bgcolors=s' => \$bgcolors, 178 | 'hash' => \$hash, 179 | 'cp' => \$palette, 180 | 'reverse' => \$stackreverse, 181 | 'inverted' => \$inverted, 182 | 'flamechart' => \$flamechart, 183 | 'negate' => \$negate, 184 | 'notes=s' => \$notestext, 185 | 'help' => \$help, 186 | ) or usage(); 187 | $help && usage(); 188 | 189 | # internals 190 | my $ypad1 = $fontsize * 3; # pad top, include title 191 | my $ypad2 = $fontsize * 2 + 10; # pad bottom, include labels 192 | my $ypad3 = $fontsize * 2; # pad top, include subtitle (optional) 193 | my $xpad = 10; # pad lefm and right 194 | my $framepad = 1; # vertical padding for frames 195 | my $depthmax = 0; 196 | my %Events; 197 | my %nameattr; 198 | 199 | if ($flamechart && $titletext eq "") { 200 | $titletext = "Flame Chart"; 201 | } 202 | 203 | if ($titletext eq "") { 204 | unless ($inverted) { 205 | $titletext = $titledefault; 206 | } else { 207 | $titletext = $titleinverted; 208 | } 209 | } 210 | 211 | if ($nameattrfile) { 212 | # The name-attribute file format is a function name followed by a tab then 213 | # a sequence of tab separated name=value pairs. 214 | open my $attrfh, $nameattrfile or die "Can't read $nameattrfile: $!\n"; 215 | while (<$attrfh>) { 216 | chomp; 217 | my ($funcname, $attrstr) = split /\t/, $_, 2; 218 | die "Invalid format in $nameattrfile" unless defined $attrstr; 219 | $nameattr{$funcname} = { map { split /=/, $_, 2 } split /\t/, $attrstr }; 220 | } 221 | } 222 | 223 | if ($notestext =~ /[<>]/) { 224 | die "Notes string can't contain < or >" 225 | } 226 | 227 | # background colors: 228 | # - yellow gradient: default (hot, java, js, perl) 229 | # - green gradient: mem 230 | # - blue gradient: io, wakeup, chain 231 | # - gray gradient: flat colors (red, green, blue, ...) 232 | if ($bgcolors eq "") { 233 | # choose a default 234 | if ($colors eq "mem") { 235 | $bgcolors = "green"; 236 | } elsif ($colors =~ /^(io|wakeup|chain)$/) { 237 | $bgcolors = "blue"; 238 | } elsif ($colors =~ /^(red|green|blue|aqua|yellow|purple|orange)$/) { 239 | $bgcolors = "grey"; 240 | } else { 241 | $bgcolors = "yellow"; 242 | } 243 | } 244 | my ($bgcolor1, $bgcolor2); 245 | if ($bgcolors eq "yellow") { 246 | $bgcolor1 = "#eeeeee"; # background color gradient start 247 | $bgcolor2 = "#eeeeb0"; # background color gradient stop 248 | } elsif ($bgcolors eq "blue") { 249 | $bgcolor1 = "#eeeeee"; $bgcolor2 = "#e0e0ff"; 250 | } elsif ($bgcolors eq "green") { 251 | $bgcolor1 = "#eef2ee"; $bgcolor2 = "#e0ffe0"; 252 | } elsif ($bgcolors eq "grey") { 253 | $bgcolor1 = "#f8f8f8"; $bgcolor2 = "#e8e8e8"; 254 | } elsif ($bgcolors =~ /^#......$/) { 255 | $bgcolor1 = $bgcolor2 = $bgcolors; 256 | } else { 257 | die "Unrecognized bgcolor option \"$bgcolors\"" 258 | } 259 | 260 | # SVG functions 261 | { package SVG; 262 | sub new { 263 | my $class = shift; 264 | my $self = {}; 265 | bless ($self, $class); 266 | return $self; 267 | } 268 | 269 | sub header { 270 | my ($self, $w, $h) = @_; 271 | my $enc_attr = ''; 272 | if (defined $encoding) { 273 | $enc_attr = qq{ encoding="$encoding"}; 274 | } 275 | $self->{svg} .= < 277 | 278 | 279 | 280 | 281 | SVG 282 | } 283 | 284 | sub include { 285 | my ($self, $content) = @_; 286 | $self->{svg} .= $content; 287 | } 288 | 289 | sub colorAllocate { 290 | my ($self, $r, $g, $b) = @_; 291 | return "rgb($r,$g,$b)"; 292 | } 293 | 294 | sub group_start { 295 | my ($self, $attr) = @_; 296 | 297 | my @g_attr = map { 298 | exists $attr->{$_} ? sprintf(qq/$_="%s"/, $attr->{$_}) : () 299 | } qw(id class); 300 | push @g_attr, $attr->{g_extra} if $attr->{g_extra}; 301 | if ($attr->{href}) { 302 | my @a_attr; 303 | push @a_attr, sprintf qq/xlink:href="%s"/, $attr->{href} if $attr->{href}; 304 | # default target=_top else links will open within SVG 305 | push @a_attr, sprintf qq/target="%s"/, $attr->{target} || "_top"; 306 | push @a_attr, $attr->{a_extra} if $attr->{a_extra}; 307 | $self->{svg} .= sprintf qq/\n/, join(' ', (@a_attr, @g_attr)); 308 | } else { 309 | $self->{svg} .= sprintf qq/\n/, join(' ', @g_attr); 310 | } 311 | 312 | $self->{svg} .= sprintf qq/%s<\/title>/, $attr->{title} 313 | if $attr->{title}; # should be first element within g container 314 | } 315 | 316 | sub group_end { 317 | my ($self, $attr) = @_; 318 | $self->{svg} .= $attr->{href} ? qq/<\/a>\n/ : qq/<\/g>\n/; 319 | } 320 | 321 | sub filledRectangle { 322 | my ($self, $x1, $y1, $x2, $y2, $fill, $extra) = @_; 323 | $x1 = sprintf "%0.1f", $x1; 324 | $x2 = sprintf "%0.1f", $x2; 325 | my $w = sprintf "%0.1f", $x2 - $x1; 326 | my $h = sprintf "%0.1f", $y2 - $y1; 327 | $extra = defined $extra ? $extra : ""; 328 | $self->{svg} .= qq/\n/; 329 | } 330 | 331 | sub stringTTF { 332 | my ($self, $id, $x, $y, $str, $extra) = @_; 333 | $x = sprintf "%0.2f", $x; 334 | $id = defined $id ? qq/id="$id"/ : ""; 335 | $extra ||= ""; 336 | $self->{svg} .= qq/$str<\/text>\n/; 337 | } 338 | 339 | sub svg { 340 | my $self = shift; 341 | return "$self->{svg}\n"; 342 | } 343 | 1; 344 | } 345 | 346 | sub namehash { 347 | # Generate a vector hash for the name string, weighting early over 348 | # later characters. We want to pick the same colors for function 349 | # names across different flame graphs. 350 | my $name = shift; 351 | my $vector = 0; 352 | my $weight = 1; 353 | my $max = 1; 354 | my $mod = 10; 355 | # if module name present, trunc to 1st char 356 | $name =~ s/.(.*?)`//; 357 | foreach my $c (split //, $name) { 358 | my $i = (ord $c) % $mod; 359 | $vector += ($i / ($mod++ - 1)) * $weight; 360 | $max += 1 * $weight; 361 | $weight *= 0.70; 362 | last if $mod > 12; 363 | } 364 | return (1 - $vector / $max) 365 | } 366 | 367 | sub color { 368 | my ($type, $hash, $name) = @_; 369 | my ($v1, $v2, $v3); 370 | 371 | if ($hash) { 372 | $v1 = namehash($name); 373 | $v2 = $v3 = namehash(scalar reverse $name); 374 | } else { 375 | $v1 = rand(1); 376 | $v2 = rand(1); 377 | $v3 = rand(1); 378 | } 379 | 380 | # theme palettes 381 | if (defined $type and $type eq "hot") { 382 | my $r = 205 + int(50 * $v3); 383 | my $g = 0 + int(230 * $v1); 384 | my $b = 0 + int(55 * $v2); 385 | return "rgb($r,$g,$b)"; 386 | } 387 | if (defined $type and $type eq "mem") { 388 | my $r = 0; 389 | my $g = 190 + int(50 * $v2); 390 | my $b = 0 + int(210 * $v1); 391 | return "rgb($r,$g,$b)"; 392 | } 393 | if (defined $type and $type eq "io") { 394 | my $r = 80 + int(60 * $v1); 395 | my $g = $r; 396 | my $b = 190 + int(55 * $v2); 397 | return "rgb($r,$g,$b)"; 398 | } 399 | 400 | # multi palettes 401 | if (defined $type and $type eq "python") { 402 | if ($name =~ m:.*\.py:) { # Python (match .py in path) 403 | $type = "green"; 404 | } elsif ($name =~ m:\<.*?\>.*:){ # Python built-in 405 | $type = "yellow" 406 | } elsif ($name =~ m:0x.*:) { # C++ 407 | $type = "orange"; 408 | } else { # others 409 | $type = "red"; 410 | } 411 | # fall-through to color palettes 412 | } 413 | if (defined $type and $type eq "java") { 414 | # Handle both annotations (_[j], _[i], ...; which are 415 | # accurate), as well as input that lacks any annotations, as 416 | # best as possible. Without annotations, we get a little hacky 417 | # and match on java|org|com, etc. 418 | if ($name =~ m:_\[j\]$:) { # jit annotation 419 | $type = "green"; 420 | } elsif ($name =~ m:_\[i\]$:) { # inline annotation 421 | $type = "aqua"; 422 | } elsif ($name =~ m:^L?(java|javax|jdk|net|org|com|io|sun)/:) { # Java 423 | $type = "green"; 424 | } elsif ($name =~ m:_\[k\]$:) { # kernel annotation 425 | $type = "orange"; 426 | } elsif ($name =~ /::/) { # C++ 427 | $type = "yellow"; 428 | } else { # system 429 | $type = "red"; 430 | } 431 | # fall-through to color palettes 432 | } 433 | if (defined $type and $type eq "perl") { 434 | if ($name =~ /::/) { # C++ 435 | $type = "yellow"; 436 | } elsif ($name =~ m:Perl: or $name =~ m:\.pl:) { # Perl 437 | $type = "green"; 438 | } elsif ($name =~ m:_\[k\]$:) { # kernel 439 | $type = "orange"; 440 | } else { # system 441 | $type = "red"; 442 | } 443 | # fall-through to color palettes 444 | } 445 | if (defined $type and $type eq "js") { 446 | # Handle both annotations (_[j], _[i], ...; which are 447 | # accurate), as well as input that lacks any annotations, as 448 | # best as possible. Without annotations, we get a little hacky, 449 | # and match on a "/" with a ".js", etc. 450 | if ($name =~ m:_\[j\]$:) { # jit annotation 451 | if ($name =~ m:/:) { 452 | $type = "green"; # source 453 | } else { 454 | $type = "aqua"; # builtin 455 | } 456 | } elsif ($name =~ /::/) { # C++ 457 | $type = "yellow"; 458 | } elsif ($name =~ m:/.*\.js:) { # JavaScript (match "/" in path) 459 | $type = "green"; 460 | } elsif ($name =~ m/:/) { # JavaScript (match ":" in builtin) 461 | $type = "aqua"; 462 | } elsif ($name =~ m/^ $/) { # Missing symbol 463 | $type = "green"; 464 | } elsif ($name =~ m:_\[k\]:) { # kernel 465 | $type = "orange"; 466 | } else { # system 467 | $type = "red"; 468 | } 469 | # fall-through to color palettes 470 | } 471 | if (defined $type and $type eq "wakeup") { 472 | $type = "aqua"; 473 | # fall-through to color palettes 474 | } 475 | if (defined $type and $type eq "chain") { 476 | if ($name =~ m:_\[w\]:) { # waker 477 | $type = "aqua" 478 | } else { # off-CPU 479 | $type = "blue"; 480 | } 481 | # fall-through to color palettes 482 | } 483 | 484 | # color palettes 485 | if (defined $type and $type eq "red") { 486 | my $r = 200 + int(55 * $v1); 487 | my $x = 50 + int(80 * $v1); 488 | return "rgb($r,$x,$x)"; 489 | } 490 | if (defined $type and $type eq "green") { 491 | my $g = 200 + int(55 * $v1); 492 | my $x = 50 + int(60 * $v1); 493 | return "rgb($x,$g,$x)"; 494 | } 495 | if (defined $type and $type eq "blue") { 496 | my $b = 205 + int(50 * $v1); 497 | my $x = 80 + int(60 * $v1); 498 | return "rgb($x,$x,$b)"; 499 | } 500 | if (defined $type and $type eq "yellow") { 501 | my $x = 175 + int(55 * $v1); 502 | my $b = 50 + int(20 * $v1); 503 | return "rgb($x,$x,$b)"; 504 | } 505 | if (defined $type and $type eq "purple") { 506 | my $x = 190 + int(65 * $v1); 507 | my $g = 80 + int(60 * $v1); 508 | return "rgb($x,$g,$x)"; 509 | } 510 | if (defined $type and $type eq "aqua") { 511 | my $r = 50 + int(60 * $v1); 512 | my $g = 165 + int(55 * $v1); 513 | my $b = 165 + int(55 * $v1); 514 | return "rgb($r,$g,$b)"; 515 | } 516 | if (defined $type and $type eq "orange") { 517 | my $r = 190 + int(65 * $v1); 518 | my $g = 90 + int(65 * $v1); 519 | return "rgb($r,$g,0)"; 520 | } 521 | 522 | return "rgb(0,0,0)"; 523 | } 524 | 525 | sub color_scale { 526 | my ($value, $max) = @_; 527 | my ($r, $g, $b) = (255, 255, 255); 528 | $value = -$value if $negate; 529 | if ($value > 0) { 530 | $g = $b = int(210 * ($max - $value) / $max); 531 | } elsif ($value < 0) { 532 | $r = $g = int(210 * ($max + $value) / $max); 533 | } 534 | return "rgb($r,$g,$b)"; 535 | } 536 | 537 | sub color_map { 538 | my ($colors, $func) = @_; 539 | if (exists $palette_map{$func}) { 540 | return $palette_map{$func}; 541 | } else { 542 | $palette_map{$func} = color($colors, $hash, $func); 543 | return $palette_map{$func}; 544 | } 545 | } 546 | 547 | sub write_palette { 548 | open(FILE, ">$pal_file"); 549 | foreach my $key (sort keys %palette_map) { 550 | print FILE $key."->".$palette_map{$key}."\n"; 551 | } 552 | close(FILE); 553 | } 554 | 555 | sub read_palette { 556 | if (-e $pal_file) { 557 | open(FILE, $pal_file) or die "can't open file $pal_file: $!"; 558 | while ( my $line = ) { 559 | chomp($line); 560 | (my $key, my $value) = split("->",$line); 561 | $palette_map{$key}=$value; 562 | } 563 | close(FILE) 564 | } 565 | } 566 | 567 | my %Node; # Hash of merged frame data 568 | my %Tmp; 569 | 570 | # flow() merges two stacks, storing the merged frames and value data in %Node. 571 | sub flow { 572 | my ($last, $this, $v, $d) = @_; 573 | 574 | my $len_a = @$last - 1; 575 | my $len_b = @$this - 1; 576 | 577 | my $i = 0; 578 | my $len_same; 579 | for (; $i <= $len_a; $i++) { 580 | last if $i > $len_b; 581 | last if $last->[$i] ne $this->[$i]; 582 | } 583 | $len_same = $i; 584 | 585 | for ($i = $len_a; $i >= $len_same; $i--) { 586 | my $k = "$last->[$i];$i"; 587 | # a unique ID is constructed from "func;depth;etime"; 588 | # func-depth isn't unique, it may be repeated later. 589 | $Node{"$k;$v"}->{stime} = delete $Tmp{$k}->{stime}; 590 | if (defined $Tmp{$k}->{delta}) { 591 | $Node{"$k;$v"}->{delta} = delete $Tmp{$k}->{delta}; 592 | } 593 | delete $Tmp{$k}; 594 | } 595 | 596 | for ($i = $len_same; $i <= $len_b; $i++) { 597 | my $k = "$this->[$i];$i"; 598 | $Tmp{$k}->{stime} = $v; 599 | if (defined $d) { 600 | $Tmp{$k}->{delta} += $i == $len_b ? $d : 0; 601 | } 602 | } 603 | 604 | return $this; 605 | } 606 | 607 | # parse input 608 | my @Data; 609 | my @SortedData; 610 | my $last = []; 611 | my $time = 0; 612 | my $delta = undef; 613 | my $ignored = 0; 614 | my $line; 615 | my $maxdelta = 1; 616 | 617 | # reverse if needed 618 | foreach (<>) { 619 | chomp; 620 | $line = $_; 621 | if ($stackreverse) { 622 | # there may be an extra samples column for differentials 623 | # XXX todo: redo these REs as one. It's repeated below. 624 | my($stack, $samples) = (/^(.*)\s+?(\d+(?:\.\d*)?)$/); 625 | my $samples2 = undef; 626 | if ($stack =~ /^(.*)\s+?(\d+(?:\.\d*)?)$/) { 627 | $samples2 = $samples; 628 | ($stack, $samples) = $stack =~ (/^(.*)\s+?(\d+(?:\.\d*)?)$/); 629 | unshift @Data, join(";", reverse split(";", $stack)) . " $samples $samples2"; 630 | } else { 631 | unshift @Data, join(";", reverse split(";", $stack)) . " $samples"; 632 | } 633 | } else { 634 | unshift @Data, $line; 635 | } 636 | } 637 | 638 | if ($flamechart) { 639 | # In flame chart mode, just reverse the data so time moves from left to right. 640 | @SortedData = reverse @Data; 641 | } else { 642 | @SortedData = sort @Data; 643 | } 644 | 645 | # process and merge frames 646 | foreach (@SortedData) { 647 | chomp; 648 | # process: folded_stack count 649 | # eg: func_a;func_b;func_c 31 650 | my ($stack, $samples) = (/^(.*)\s+?(\d+(?:\.\d*)?)$/); 651 | unless (defined $samples and defined $stack) { 652 | ++$ignored; 653 | next; 654 | } 655 | 656 | # there may be an extra samples column for differentials: 657 | my $samples2 = undef; 658 | if ($stack =~ /^(.*)\s+?(\d+(?:\.\d*)?)$/) { 659 | $samples2 = $samples; 660 | ($stack, $samples) = $stack =~ (/^(.*)\s+?(\d+(?:\.\d*)?)$/); 661 | } 662 | $delta = undef; 663 | if (defined $samples2) { 664 | $delta = $samples2 - $samples; 665 | $maxdelta = abs($delta) if abs($delta) > $maxdelta; 666 | } 667 | 668 | # for chain graphs, annotate waker frames with "_[w]", for later 669 | # coloring. This is a hack, but has a precedent ("_[k]" from perf). 670 | if ($colors eq "chain") { 671 | my @parts = split ";--;", $stack; 672 | my @newparts = (); 673 | $stack = shift @parts; 674 | $stack .= ";--;"; 675 | foreach my $part (@parts) { 676 | $part =~ s/;/_[w];/g; 677 | $part .= "_[w]"; 678 | push @newparts, $part; 679 | } 680 | $stack .= join ";--;", @parts; 681 | } 682 | 683 | # merge frames and populate %Node: 684 | $last = flow($last, [ '', split ";", $stack ], $time, $delta); 685 | 686 | if (defined $samples2) { 687 | $time += $samples2; 688 | } else { 689 | $time += $samples; 690 | } 691 | } 692 | flow($last, [], $time, $delta); 693 | 694 | warn "Ignored $ignored lines with invalid format\n" if $ignored; 695 | unless ($time) { 696 | warn "ERROR: No stack counts found\n"; 697 | my $im = SVG->new(); 698 | # emit an error message SVG, for tools automating flamegraph use 699 | my $imageheight = $fontsize * 5; 700 | $im->header($imagewidth, $imageheight); 701 | $im->stringTTF(undef, int($imagewidth / 2), $fontsize * 2, 702 | "ERROR: No valid input provided to flamegraph.pl."); 703 | print $im->svg; 704 | exit 2; 705 | } 706 | if ($timemax and $timemax < $time) { 707 | warn "Specified --total $timemax is less than actual total $time, so ignored\n" 708 | if $timemax/$time > 0.02; # only warn is significant (e.g., not rounding etc) 709 | undef $timemax; 710 | } 711 | $timemax ||= $time; 712 | 713 | my $widthpertime = ($imagewidth - 2 * $xpad) / $timemax; 714 | my $minwidth_time = $minwidth / $widthpertime; 715 | 716 | # prune blocks that are too narrow and determine max depth 717 | while (my ($id, $node) = each %Node) { 718 | my ($func, $depth, $etime) = split ";", $id; 719 | my $stime = $node->{stime}; 720 | die "missing start for $id" if not defined $stime; 721 | 722 | if (($etime-$stime) < $minwidth_time) { 723 | delete $Node{$id}; 724 | next; 725 | } 726 | $depthmax = $depth if $depth > $depthmax; 727 | } 728 | 729 | # draw canvas, and embed interactive JavaScript program 730 | my $imageheight = (($depthmax + 1) * $frameheight) + $ypad1 + $ypad2; 731 | $imageheight += $ypad3 if $subtitletext ne ""; 732 | my $titlesize = $fontsize + 5; 733 | my $im = SVG->new(); 734 | my ($black, $vdgrey, $dgrey) = ( 735 | $im->colorAllocate(0, 0, 0), 736 | $im->colorAllocate(160, 160, 160), 737 | $im->colorAllocate(200, 200, 200), 738 | ); 739 | $im->header($imagewidth, $imageheight); 740 | my $inc = < 742 | 743 | 744 | 745 | 746 | 747 | 758 | 1072 | INC 1073 | $im->include($inc); 1074 | $im->filledRectangle(0, 0, $imagewidth, $imageheight, 'url(#background)'); 1075 | $im->stringTTF("title", int($imagewidth / 2), $fontsize * 2, $titletext); 1076 | $im->stringTTF("subtitle", int($imagewidth / 2), $fontsize * 4, $subtitletext) if $subtitletext ne ""; 1077 | $im->stringTTF("details", $xpad, $imageheight - ($ypad2 / 2), " "); 1078 | $im->stringTTF("unzoom", $xpad, $fontsize * 2, "Reset Zoom", 'class="hide"'); 1079 | $im->stringTTF("search", $imagewidth - $xpad - 100, $fontsize * 2, "Search"); 1080 | $im->stringTTF("matched", $imagewidth - $xpad - 100, $imageheight - ($ypad2 / 2), " "); 1081 | 1082 | if ($palette) { 1083 | read_palette(); 1084 | } 1085 | 1086 | # draw frames 1087 | $im->group_start({id => "frames"}); 1088 | while (my ($id, $node) = each %Node) { 1089 | my ($func, $depth, $etime) = split ";", $id; 1090 | my $stime = $node->{stime}; 1091 | my $delta = $node->{delta}; 1092 | 1093 | $etime = $timemax if $func eq "" and $depth == 0; 1094 | 1095 | my $x1 = $xpad + $stime * $widthpertime; 1096 | my $x2 = $xpad + $etime * $widthpertime; 1097 | my ($y1, $y2); 1098 | unless ($inverted) { 1099 | $y1 = $imageheight - $ypad2 - ($depth + 1) * $frameheight + $framepad; 1100 | $y2 = $imageheight - $ypad2 - $depth * $frameheight; 1101 | } else { 1102 | $y1 = $ypad1 + $depth * $frameheight; 1103 | $y2 = $ypad1 + ($depth + 1) * $frameheight - $framepad; 1104 | } 1105 | 1106 | my $samples = sprintf "%.0f", ($etime - $stime) * $factor; 1107 | (my $samples_txt = $samples) # add commas per perlfaq5 1108 | =~ s/(^[-+]?\d+?(?=(?>(?:\d{3})+)(?!\d))|\G\d{3}(?=\d))/$1,/g; 1109 | 1110 | my $info; 1111 | if ($func eq "" and $depth == 0) { 1112 | $info = "all ($samples_txt $countname, 100%)"; 1113 | } else { 1114 | my $pct = sprintf "%.2f", ((100 * $samples) / ($timemax * $factor)); 1115 | my $escaped_func = $func; 1116 | # clean up SVG breaking characters: 1117 | $escaped_func =~ s/&/&/g; 1118 | $escaped_func =~ s//>/g; 1120 | $escaped_func =~ s/"/"/g; 1121 | $escaped_func =~ s/_\[[kwij]\]$//; # strip any annotation 1122 | unless (defined $delta) { 1123 | $info = "$escaped_func ($samples_txt $countname, $pct%)"; 1124 | } else { 1125 | my $d = $negate ? -$delta : $delta; 1126 | my $deltapct = sprintf "%.2f", ((100 * $d) / ($timemax * $factor)); 1127 | $deltapct = $d > 0 ? "+$deltapct" : $deltapct; 1128 | $info = "$escaped_func ($samples_txt $countname, $pct%; $deltapct%)"; 1129 | } 1130 | } 1131 | 1132 | my $nameattr = { %{ $nameattr{$func}||{} } }; # shallow clone 1133 | $nameattr->{title} ||= $info; 1134 | $im->group_start($nameattr); 1135 | 1136 | my $color; 1137 | if ($func eq "--") { 1138 | $color = $vdgrey; 1139 | } elsif ($func eq "-") { 1140 | $color = $dgrey; 1141 | } elsif (defined $delta) { 1142 | $color = color_scale($delta, $maxdelta); 1143 | } elsif ($palette) { 1144 | $color = color_map($colors, $func); 1145 | } else { 1146 | $color = color($colors, $hash, $func); 1147 | } 1148 | $im->filledRectangle($x1, $y1, $x2, $y2, $color, 'rx="2" ry="2"'); 1149 | 1150 | my $chars = int( ($x2 - $x1) / ($fontsize * $fontwidth)); 1151 | my $text = ""; 1152 | if ($chars >= 3) { # room for one char plus two dots 1153 | $func =~ s/_\[[kwij]\]$//; # strip any annotation 1154 | $text = substr $func, 0, $chars; 1155 | substr($text, -2, 2) = ".." if $chars < length $func; 1156 | $text =~ s/&/&/g; 1157 | $text =~ s//>/g; 1159 | } 1160 | $im->stringTTF(undef, $x1 + 3, 3 + ($y1 + $y2) / 2, $text); 1161 | 1162 | $im->group_end($nameattr); 1163 | } 1164 | $im->group_end(); 1165 | 1166 | print $im->svg; 1167 | 1168 | if ($palette) { 1169 | write_palette(); 1170 | } 1171 | 1172 | # vim: ts=8 sts=8 sw=8 noexpandtab 1173 | -------------------------------------------------------------------------------- /pyflame: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meteorix/pyflame-server/bd03b900bcae81de68e5d2b396090e0da14063e2/pyflame -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | pyflame-server 2 | ============== 3 | 4 | A webservice to facilate the use of [pyflame](https://github.com/Meteorix/pyflame) 5 | 6 | [Pyflame解析和扩展](https://github.com/Meteorix/meteorix-blog/blob/master/_posts/pyflame.md) 7 | 8 | # Installation 9 | 10 | ```shell 11 | pip install -r requirements.txt 12 | python wsgi.py 13 | ``` 14 | 15 | visit http://localhost:5000/ 16 | 17 | > change `localhost` to your host ip 18 | 19 | # Usage 20 | 21 | 1 get pyflame for py2.6 py2.7 py3.4 py3.5 py3.6 py3.7 22 | ``` shell 23 | curl -o pyflame http://:5000/pyflame ; chmod +x pyflame 24 | ``` 25 | 26 | 2.1 start python to profile 27 | ```shell 28 | ./pyflame -t python test.py > profile.txt 29 | ``` 30 | 31 | 2.2 or attach python pid to profile, -s seconds -p pid, reference 32 | ```shell 33 | sudo ./pyflame -s 5 -p 28947 > profile.txt 34 | ``` 35 | 36 | 2.3 if you need to profile c stack at the same time (when you are curious about **idle**), add ``-c`` option 37 | ```shell 38 | sudo ./pyflame -c -s 5 -p 28947 > profile.txt 39 | ``` 40 | 41 | 3 upload to pyflame-server 42 | ```shell 43 | curl -F "file=@profile.txt" http://:5000/upload 44 | ``` 45 | 46 | 4 visit the url printed by curl, samples here 47 | 48 | # My fork of Pyflame 49 | 50 | Since the author of [uber/pyflame](https://github.com/uber/pyflame) no longer works at uber, the official repo is not well maintained. I forked to [Meteorix/pyflame](https://github.com/Meteorix/pyflame) and did the following things: 51 | 52 | 1. Merge PR of py3.7 support 53 | 1. Merge PR of anaconda fix 54 | 1. Fix py2.7 build script 55 | 1. Add Dockerfile to build pyflame which enables py2.6/py2.7/py3.4/py3.5/py3.6/py3.7 support 56 | 1. Add c/c++ stack profile 57 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | flask 2 | gevent 3 | -------------------------------------------------------------------------------- /templates/list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | pyflame-server 5 | 6 | 7 |

pyflame-server

8 |
9 |
    10 | {% for i in data %} 11 |
  • {{i['mtime']}} {{i['name']}}
  • 12 | {% endfor %} 13 |
14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /templates/upload.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | pyflame-server 5 | 6 | 7 |

pyflame-server

8 |
9 |

1. get pyflame for py2.6 py2.7 py3.4 py3.5 py3.6 py3.7

10 | curl -o pyflame {{url_root}}pyflame ; chmod +x pyflame 11 |

2.1 start python to profile

12 | ./pyflame -t python test.py > profile.txt 13 |

2.2 or attach python pid to profile, -s seconds -p pid, reference

14 | sudo ./pyflame -s 5 -p 28947 > profile.txt 15 |

2.3 if you need to profile c stack at the same time (when you are curious about **idle**), add ``-c`` option

16 | sudo ./pyflame -c -s 5 -p 28947 > profile.txt 17 |

3 upload to pyflame-server

18 | curl -F "file=@profile.txt" {{url_root}}upload 19 |

4 visit the url printed by curl, samples here

20 |
21 |
22 | 23 |
24 | 25 | 26 |
27 |
28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /uploads/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meteorix/pyflame-server/bd03b900bcae81de68e5d2b396090e0da14063e2/uploads/.gitkeep -------------------------------------------------------------------------------- /wsgi.py: -------------------------------------------------------------------------------- 1 | from gevent.monkey import patch_all; patch_all() 2 | from gevent.pywsgi import WSGIServer 3 | from flask import Flask, request, render_template, Response, send_file 4 | from datetime import datetime 5 | import subprocess as sb 6 | import time 7 | import os 8 | import io 9 | 10 | 11 | app = Flask(__name__) 12 | UPLOAD_DIR = "./uploads" 13 | 14 | 15 | @app.route("/") 16 | @app.route("/upload", methods=["GET", "POST"]) 17 | def upload(): 18 | if request.method == "POST": 19 | fs = request.files['file'] 20 | filepath = os.path.join(UPLOAD_DIR, "%s.%s" % (fs.filename, time.time())) 21 | app.logger.warn("saving %s to %s" % (fs, filepath)) 22 | fs.save(filepath) 23 | 24 | svgpath = filepath + ".svg" 25 | out = txt_2_svg(filepath, svgpath) 26 | app.logger.warn("txt_2_svg %s" % out) 27 | 28 | return request.url_root + "svg/" + os.path.basename(svgpath) + "\n" 29 | else: 30 | return render_template("upload.html", url_root=request.url_root) 31 | 32 | 33 | @app.route("/svg") 34 | def svg_list(): 35 | data = [] 36 | fs = os.listdir(UPLOAD_DIR) 37 | for f in fs: 38 | if not f.endswith(".svg"): 39 | continue 40 | name = os.path.basename(f) 41 | url = request.url_root + "svg/" + name 42 | path = os.path.join(UPLOAD_DIR, name) 43 | mtime = os.path.getmtime(path) 44 | mtime = datetime.fromtimestamp(mtime) 45 | data.append({"url": url, "name": name, "mtime": mtime}) 46 | data.sort(key=lambda i:i["mtime"]) 47 | data.reverse() 48 | return render_template("list.html", data=data) 49 | 50 | 51 | @app.route("/svg/") 52 | def svg(filename): 53 | filepath = os.path.join(UPLOAD_DIR, filename) 54 | f = open(filepath) 55 | r = Response(f.read(), mimetype="image/svg+xml") 56 | return r 57 | 58 | 59 | @app.route("/pyflame") 60 | def pyflame(): 61 | return send_file("./pyflame") 62 | 63 | 64 | def txt_2_svg(txt, svg): 65 | cmd = "./flamegraph.pl %s --color=python > %s" % (txt, svg) 66 | out = sb.check_output(cmd, shell=True) 67 | return out 68 | 69 | 70 | 71 | if __name__ == "__main__": 72 | if not os.path.exists(UPLOAD_DIR): 73 | os.makedirs(UPLOAD_DIR) 74 | host, port = "0.0.0.0", 5000 75 | print("gevent server staring on http://%s:%s" % (host, port)) 76 | WSGIServer((host, int(port)), app).serve_forever() 77 | 78 | --------------------------------------------------------------------------------