<!DOCTYPE html> <html> <head>

INCLUDES

<meta charset=utf-8 />

<title>Flame Graph of Page</title> <style>

.info {min-height: 50px; margin: 10px; }
.legend div {
  display: block;
  float: left;
  width: 150px;
  margin: 0 8px 8px;
  padding: 4px;
  height: 50px;
}
.code {
  font-family: Consolas, Menlo, Monaco, "Lucida Console", "Liberation Mono", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Courier New", monospace;
}
#frameinfo table .sample-info {
  width: 200px;
  color: #333;
  font-size: 12px;
}
#frameinfo table {
  border-collapse: collapse;
}
#frameinfo table tr {
  border-bottom: 1px solid #eee;
  margin-top: 5px;
}
#frameinfo td {
  padding: 5px;
}
#frameinfo-wrapper {
  position: fixed;
  z-index: 1;
  opacity: 0.7;
  background-color: black;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
  display: none;
}
#frameinfo {
  position: fixed;
  padding: 10px;
  z-index: 2;
  opacity: 1.0;
  top: 0;
  margin-top: 50px;
  margin-left: 40px;
  width: 80%;
  height: 80%;
  background-color: white;
  border: 2px solid #666;
  display: none;
  overflow: auto;
}

</style> </head> <body>

<div class="graph"></div>
<div class="info code"></div>
<div class="legend"></div>
<div id="frameinfo-wrapper"></div>
<div id="frameinfo">
  <h3>Frame Info</h3>
  <table>
  </table>
</div>
<script>

var data = DATA ; var maxX = 0; var maxY = 0;

debounce = function(func, wait, trickle) {

var timeout;

timeout = null;
return function() {
  var args, context, currentWait, later;
  context = this;
  args = arguments;
  later = function() {
    timeout = null;
    return func.apply(context, args);
  };

  if (timeout && trickle) {
    // already queued, let it through
    return;
  }

  if (typeof wait === "function") {
    currentWait = wait();
  } else {
    currentWait = wait;
  }

  if (timeout) {
    clearTimeout(timeout);
  }

  timeout = setTimeout(later, currentWait);
  return timeout;
};

};

var guessGem = function(frame) {

var split = frame.split('/gems/');
if(split.length == 1) {
  split = frame.split('/app/');
  if(split.length == 1) {
    split = frame.split('/lib/');
  }

  split = split[Math.max(split.length-2,0)].split('/');
  return split[split.length-1].split(':')[0];
}
else
{
  return split[split.length -1].split('/')[0];
}

}

var guessMethod = function(frame) {

var split = frame.split('`');
if(split.length == 2) {
  var fullMethod = split[1].split("'")[0];
  split = fullMethod.split("#");
  if(split.length == 2) {
    return split[1];
  }
  return split[0];
}
return '?';

}

var guessFile = function(frame) {

var split = frame.split(".rb:");
if(split.length == 2) {
  split = split[0].split('/');
  return split[split.length - 1];
}
return "";

}

$.each(data, function(){

maxX = Math.max(maxX, this.x + this.width);
maxY = Math.max(maxY, this.y);
this.shortName = guessMethod(this.frame);

});

var width = $(window).width(); var height = $(window).height() / 1.2;

$('.graph').width(width).height(height);

var xScale = d3.scale.linear()

    .domain([0, maxX])
.range([0, width]);

var yScale = d3.scale.linear()

.domain([0, maxY])
.range([0,height]);

var realHeight = 0; var debouncedHeightCheck = debounce(function(){

if (realHeight > 15) {
  svg.selectAll('text').attr('display','show');
} else {
  svg.selectAll('text').attr('display','none');
}

}, 200);

function zoom() {

svg.attr("transform", "translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")");

realHeight = yScale(1) * d3.event.scale;
debouncedHeightCheck();

}

var svg = d3.select(“.graph”)

.append("svg")
.attr("width", "100%")
.attr("height", "100%")
.attr("pointer-events", "all")
.append('svg:g')
.call(d3.behavior.zoom().on("zoom", zoom))
.append('svg:g');

// so zoom works everywhere svg.append(“rect”)

.attr("x",function(d) { return xScale(0); })
.attr("y",function(d) { return yScale(0);})
.attr("width", function(d){return xScale(maxX);})
.attr("height", yScale(maxY))
.attr("fill", "white");

var color = function() {

var r = parseInt(205 + Math.random() * 50);
var g = parseInt(Math.random() * 230);
var b = parseInt(Math.random() * 55);
return "rgb(" + r + "," + g + "," + b + ")";

} var info = {};

// stackoverflow.com/questions/1960473/unique-values-in-an-array Array.prototype.getUnique = function() {

var o = {}, a = []
for (var i = 0; i < this.length; i++) o[this[i]] = 1
for (var e in o) a.push(e)
return a

}

var samplePercent = function(samples, exclusive){

var info = " (" + samples +
  " sample" + (samples == 1 ? "" : "s") + " - " +
  ((samples / maxX) * 100).toFixed(2) + "%) ";
if (exclusive) {
  info += " (" + exclusive +
    " exclusive - " +
  ((exclusive / maxX) * 100).toFixed(2) + "%) ";
}
return info;

}

var mouseover = function(d) {

var i = info[d.frame];
$('.info').text( d.frame + " " + samplePercent(i.samples.length, d.topFrame ? d.topFrame.exclusiveCount : 0));
d3.selectAll(i.nodes)
   .attr('opacity',0.5);

};

var mouseout = function(d) {

var i = info[d.frame];
$('.info').text("");
d3.selectAll(i.nodes)
   .attr('opacity',1);

};

var backtrace = function(frame){

for(var i=0; i<data.length; i++){
  if(frame === data[i]){ break; }
}

frames = [frame];
var depth = frame.y;

while(i > 0){
  if(depth == -1) break;

  if(data[i].y === depth-1) {
    frames.push(data[i]);
    depth--;
  }

  i--;
}

return frames;

}

$('#frameinfo-wrapper').click(function(d){

$(this).hide();
$('#frameinfo').hide();

});

var click = function(d){

var trace = backtrace(d);

var link = function(path, dest){
  return path.replace(/[^\/]+:\d+/, function(x){ return "<a target='_blank' href='"+ dest +"'>" + x + "</a>"})
};

var linkify = function(path){
  var split = path.split("/")[0].split("-");
  if(["activerecord","actionpack","railties","activesupport", "rails"].indexOf(split[0]) > -1) {
    var github = "https://github.com/rails/rails/blob/";
    var file = path.split(":")[0].split("/");
    if(split[0] === "rails") {
      file.shift();
    } else {
      file[0] = split[0];
    }

    github += (split[1].length < 6 ? "v" : "") + split[1] + "/";
    github += file.join("/");
    github += "#L" + parseInt(path.split(":")[1]);

    return link(path, github);
  }
  return path;
}

var simplify = function(frame){
  var split = frame.split('/gems/');
  if(split.length > 1){
    var path = linkify(split.pop());
    return "<span class='full-location'>" + split.join('/gems/') + "/</span>" + path;
  } else {
    return frame;
  }
}

var table = trace.map(function(f){
      var i = info[f.frame];
      return "<tr><td class='code'>" + simplify(f.frame) + "</td><td class='sample-info'>" +
              samplePercent(i.samples.length, f.topFrame ? f.topFrame.exclusiveCount : 0) + 
              "</td></tr>";
    }).join("\n");

var table = $('#frameinfo table').html(table);
table.find(".full-location").hide().after("<span class='expand'>&hellip; </span>");
table.find(".expand").css({cursor: "pointer"}).click(function(){
    $(this).hide().parent().find(".full-location").show();
});

$('#frameinfo-wrapper').show();
$('#frameinfo').show();

};

// stackoverflow.com/a/7419630 var rainbow = function(numOfSteps, step) {

// This function generates vibrant, "evenly spaced" colours (i.e. no clustering). This is ideal for creating easily distiguishable vibrant markers in Google Maps and other apps.
// Adam Cole, 2011-Sept-14
// HSV to RBG adapted from: http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
var r, g, b;
var h = step / numOfSteps;
var i = ~~(h * 6);
var f = h * 6 - i;
var q = 1 - f;
switch(i % 6){
    case 0: r = 1, g = f, b = 0; break;
    case 1: r = q, g = 1, b = 0; break;
    case 2: r = 0, g = 1, b = f; break;
    case 3: r = 0, g = q, b = 1; break;
    case 4: r = f, g = 0, b = 1; break;
    case 5: r = 1, g = 0, b = q; break;
}
var c = "#" + ("00" + (~ ~(r * 255)).toString(16)).slice(-2) + ("00" + (~ ~(g * 255)).toString(16)).slice(-2) + ("00" + (~ ~(b * 255)).toString(16)).slice(-2);
return (c);

}

// assign some colors, analyze samples per gem var gemStats = {} var topFrames = {} var lastFrame = {frame: 'd52e04d-df28-41ed-a215-b6ec840a8ea5', x: -1}

$.each(data, function(){

var gem = guessGem(this.frame);
var stat = gemStats[gem];

if(!stat) {
  gemStats[gem] = stat = {name: gem, samples: [], frames: []};
}

stat.frames.push(this.frame);
for(var j=0; j < this.width; j++){
  stat.samples.push(this.x + j);
}
// This assumes the traversal is in order
if (lastFrame.x != this.x) {
  var topFrame = topFrames[lastFrame.frame]
  if (!topFrame) {
    topFrames[lastFrame.frame] = topFrame = {exclusiveCount: 0}
  }
  topFrame.exclusiveCount += 1;
  lastFrame.topFrame = topFrame;
}
lastFrame = this;

});

var topFrame = topFrames if (!topFrame) {

topFrames[lastFrame.frame] = topFrame = {exclusiveCount: 0}

} topFrame.exclusiveCount += 1; lastFrame.topFrame = topFrame;

var totalGems = 0; $.each(gemStats, function(k,stat){

totalGems++;
stat.samples = stat.samples.getUnique();

});

var gemsSorted = _(gemStats).pairs()

.sortBy(function(item){
    return -item[1].samples.length;
  })
.map(function(item){return item[1]})
.value();

var currentIndex = 0; _.each(gemsSorted, function(stat){

stat.color = rainbow(totalGems, currentIndex);

for(var x=0; x < stat.frames.length; x++) {
  info[stat.frames[x]] = {nodes: [], samples: [], color: stat.color};
}

currentIndex += 1;

});

// see: bl.ocks.org/mundhradevang/1387786 function fontSize(d,i) {

var size = yScale(1) / 3;
// var words = d.shortName.split(' ');
var word = d.shortName; // words[0];
var width = xScale(d.width+100);
var height = yScale(1);
var length = 0;
d3.select(this).style("font-size", size + "px").text(word);
while((size > 12.1) && ((this.getBBox().width >= width) || (this.getBBox().height >= height)))
 {
  size -= 0.1;
  d3.select(this).style("font-size", size + "px");
 }

 d3.select(this).attr("dy", size);

}

svg.selectAll(“g”)

    .data(data)
    .enter()
.append("g")
.each(function(){
  d3.select(this)
  .append("rect")
  .attr("x",function(d) { return xScale(d.x-1); })
  .attr("y",function(d) { return yScale(maxY - d.y);})
  .attr("width", function(d){return xScale(d.width);})
  .attr("height", yScale(1))
  .attr("fill", function(d){
    var i = info[d.frame];
    if(!i) {
      info[d.frame] = i = {nodes: [], samples: [], color: color()};
    }
    i.nodes.push(this);
    for(var j=0; j < d.width; j++){
      i.samples.push(d.x + j);
    }
    return i.color;
  })
  .on("click", click)
  .on("mouseover", mouseover)
  .on("mouseout", mouseout)
  .attr("cursor", "pointer");

  d3.select(this)
    .append("text")
    .attr("x",function(d) { return xScale(d.x - 0.98); })
    .attr("y",function(d) { return yScale(maxY - d.y);})
    .on("click", click)
    .on("mouseover", mouseover)
    .on("mouseout", mouseout)
    .each(fontSize)
    .attr("cursor", "pointer")
    .attr("display", "none");

});

// Samples may overlap on the same line for (var r in info) {

if (info[r].samples) {
  info[r].samples = info[r].samples.getUnique();
}

};

// render the legend _.each(gemsSorted, function(gem){

var node = $("<div></div>")
  .css("background-color", gem.color)
  .text(gem.name + " " + samplePercent(gem.samples.length)) ;
$('.legend').append(node);

});

</script>

</body> </html>