<!DOCTYPE html> <html> <head> <script src=“code.jquery.com/jquery-1.9.1.min.js”>> <script src=“cdnjs.cloudflare.com/ajax/libs/d3/3.0.8/d3.min.js”>> <!– <script src=“cdnjs.cloudflare.com/ajax/libs/sugar/1.3.8/sugar.min.js”>> –> <meta charset=utf-8 /> <title>Flame Graph of Page</title> <style>
.info {height: 40px;} .legend div { display: block; float: left; width: 150px; margin: 0 8px 8px; padding: 4px; height: 50px; }
</style> </head> <body>
<div class="graph"></div> <div class="info"></div> <div class="legend"></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]; } else { return split[split.length -1].split('/')[0]; }
}
var guessMethod = function(frame) {
var split = frame.split('`'); if(split.length == 2) { return split[1].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 = guessGem(this.frame) + " " + guessFile(this.frame) + " " 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){
return "(" + samples + " sample" + (samples == 1 ? "" : "s") + " - " + ((samples / maxX) * 100).toFixed(2) + "%)";
}
var mouseover = function(d) {
var i = info[d.frame]; $('.info').text( d.frame + " " + samplePercent(i.samples.length)); 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);
};
// 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 = {}
$.each(data, function(){
var gem = guessGem(this.frame); var stat = gemStats[gem]; if(!stat) { gemStats[gem] = stat = {samples: [], frames: []}; } stat.frames.push(this.frame); for(var j=0; j < this.width; j++){ stat.samples.push(this.x + j); }
});
var totalGems = 0; $.each(gemStats, function(){totalGems++;});
var currentIndex = 0; $.each(gemStats, function(k,stat){
stat.color = rainbow(totalGems, currentIndex); stat.samples = stat.samples.getUnique(); 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(((this.getBBox().width >= width) || (this.getBBox().height >= height)) && (size > 12)) { 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("mouseover", mouseover) .on("mouseout", mouseout); 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("mouseover", mouseover) .on("mouseout", mouseout) .each(fontSize) .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(gemStats, function(k,v){
var node = $("<div></div>") .css("background-color", v.color) .text(k + " " + samplePercent(v.samples.length)) ; $('.legend').append(node);
});
</script>
</body> </html>