lib/stackprof/report.rb in stackprof-0.2.12 vs lib/stackprof/report.rb in stackprof-0.2.13

- old
+ new

@@ -66,10 +66,15 @@ def print_dump(f=STDOUT) f.puts Marshal.dump(@data.reject{|k,v| k == :files }) end + def print_json(f=STDOUT) + require "json" + f.puts JSON.generate(@data, max_nesting: false) + end + def print_stackcollapse raise "profile does not include raw samples (add `raw: true` to collecting StackProf.run)" unless raw = data[:raw] while len = raw.shift frames = raw.slice!(0, len) @@ -78,11 +83,19 @@ print frames.map{ |a| data[:frames][a][:name] }.join(';') puts " #{weight}" end end - def print_flamegraph(f=STDOUT, skip_common=true) + def print_timeline_flamegraph(f=STDOUT, skip_common=true) + print_flamegraph(f, skip_common, false) + end + + def print_alphabetical_flamegraph(f=STDOUT, skip_common=true) + print_flamegraph(f, skip_common, true) + end + + def print_flamegraph(f, skip_common, alphabetical=false) raise "profile does not include raw samples (add `raw: true` to collecting StackProf.run)" unless raw = data[:raw] stacks = [] max_x = 0 max_y = 0 @@ -91,10 +104,12 @@ stack = raw.slice!(0, len+1) stacks << stack max_x += stack.last end + stacks.sort! if alphabetical + f.puts 'flamegraph([' max_y.times do |y| row_prev = nil row_width = 0 x = 0 @@ -144,9 +159,219 @@ def flamegraph_row(f, x, y, weight, addr) frame = frames[addr] f.print ',' if @rows_started @rows_started = true f.puts %{{"x":#{x},"y":#{y},"width":#{weight},"frame_id":#{addr},"frame":#{frame[:name].dump},"file":#{frame[:file].dump}}} + end + + def convert_to_d3_flame_graph_format(name, stacks, depth) + weight = 0 + children = [] + stacks.chunk do |stack| + if depth == stack.length - 1 + :leaf + else + stack[depth] + end + end.each do |val, child_stacks| + if val == :leaf + child_stacks.each do |stack| + weight += stack.last + end + else + frame = frames[val] + child_name = "#{ frame[:name] } : #{ frame[:file] }" + child_data = convert_to_d3_flame_graph_format(child_name, child_stacks, depth + 1) + weight += child_data["value"] + children << child_data + end + end + + { + "name" => name, + "value" => weight, + "children" => children, + } + end + + def print_d3_flamegraph(f=STDOUT, skip_common=true) + raise "profile does not include raw samples (add `raw: true` to collecting StackProf.run)" unless raw = data[:raw] + + stacks = [] + max_x = 0 + max_y = 0 + while len = raw.shift + max_y = len if len > max_y + stack = raw.slice!(0, len+1) + stacks << stack + max_x += stack.last + end + + # d3-flame-grpah supports only alphabetical flamegraph + stacks.sort! + + require "json" + json = JSON.generate(convert_to_d3_flame_graph_format("<root>", stacks, 0), max_nesting: false) + + # This html code is almost copied from d3-flame-graph sample code. + # (Apache License 2.0) + # https://github.com/spiermar/d3-flame-graph/blob/gh-pages/index.html + + f.print <<-END +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8"> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + + <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> + <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/gh/spiermar/d3-flame-graph@2.0.3/dist/d3-flamegraph.css"> + + <style> + + /* Space out content a bit */ + body { + padding-top: 20px; + padding-bottom: 20px; + } + + /* Custom page header */ + .header { + padding-bottom: 20px; + padding-right: 15px; + padding-left: 15px; + border-bottom: 1px solid #e5e5e5; + } + + /* Make the masthead heading the same height as the navigation */ + .header h3 { + margin-top: 0; + margin-bottom: 0; + line-height: 40px; + } + + /* Customize container */ + .container { + max-width: 990px; + } + + address { + text-align: right; + } + </style> + + <title>stackprof (mode: #{ data[:mode] })</title> + + <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries --> + <!--[if lt IE 9]> + <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script> + <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script> + <![endif]--> + </head> + <body> + <div class="container"> + <div class="header clearfix"> + <nav> + <div class="pull-right"> + <form class="form-inline" id="form"> + <a class="btn" href="javascript: resetZoom();">Reset zoom</a> + <a class="btn" href="javascript: clear();">Clear</a> + <div class="form-group"> + <input type="text" class="form-control" id="term"> + </div> + <a class="btn btn-primary" href="javascript: search();">Search</a> + </form> + </div> + </nav> + <h3 class="text-muted">stackprof (mode: #{ data[:mode] })</h3> + </div> + <div id="chart"> + </div> + <address> + powered by <a href="https://github.com/spiermar/d3-flame-graph">d3-flame-graph</a> + </address> + <hr> + <div id="details"> + </div> + </div> + + <!-- D3.js --> + <script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script> + + <!-- d3-tip --> + <script type="text/javascript" src=https://cdnjs.cloudflare.com/ajax/libs/d3-tip/0.9.1/d3-tip.min.js></script> + + <!-- d3-flamegraph --> + <script type="text/javascript" src="https://cdn.jsdelivr.net/gh/spiermar/d3-flame-graph@2.0.3/dist/d3-flamegraph.min.js"></script> + + <script type="text/javascript"> + var flameGraph = d3.flamegraph() + .width(960) + .cellHeight(18) + .transitionDuration(750) + .minFrameSize(5) + .transitionEase(d3.easeCubic) + .sort(true) + //Example to sort in reverse order + //.sort(function(a,b){ return d3.descending(a.name, b.name);}) + .title("") + .onClick(onClick) + .differential(false) + .selfValue(false); + + + // Example on how to use custom tooltips using d3-tip. + // var tip = d3.tip() + // .direction("s") + // .offset([8, 0]) + // .attr('class', 'd3-flame-graph-tip') + // .html(function(d) { return "name: " + d.data.name + ", value: " + d.data.value; }); + + // flameGraph.tooltip(tip); + + var details = document.getElementById("details"); + flameGraph.setDetailsElement(details); + + // Example on how to use custom labels + // var label = function(d) { + // return "name: " + d.name + ", value: " + d.value; + // } + // flameGraph.label(label); + + // Example of how to set fixed chart height + // flameGraph.height(540); + + d3.select("#chart") + .datum(#{ json }) + .call(flameGraph); + + document.getElementById("form").addEventListener("submit", function(event){ + event.preventDefault(); + search(); + }); + + function search() { + var term = document.getElementById("term").value; + flameGraph.search(term); + } + + function clear() { + document.getElementById('term').value = ''; + flameGraph.clear(); + } + + function resetZoom() { + flameGraph.resetZoom(); + } + + function onClick(d) { + console.info("Clicked on " + d.data.name); + } + </script> + </body> +</html> + END end def print_graphviz(options = {}, f = STDOUT) if filter = options[:filter] mark_stack = []