Skip to content
Browse files

Flame Graph proof of concept

  • Loading branch information...
1 parent 8ff6c70 commit 1a4f58182aa9750b2ec98903ba6573d8c37fa990 @SamSaffron committed Mar 17, 2013
View
62 Ruby/Gemfile.lock
@@ -1,52 +1,54 @@
PATH
- remote: /home/dyfrgi/MiniProfiler
+ remote: /home/sam/Source/MiniProfiler
specs:
rack-mini-profiler (0.1.23)
rack (>= 1.1.3)
GEM
remote: http://rubygems.org/
specs:
- ZenTest (4.8.1)
- activemodel (3.2.6)
- activesupport (= 3.2.6)
+ ZenTest (4.9.0)
+ activemodel (3.2.12)
+ activesupport (= 3.2.12)
builder (~> 3.0.0)
- activerecord (3.2.6)
- activemodel (= 3.2.6)
- activesupport (= 3.2.6)
+ activerecord (3.2.12)
+ activemodel (= 3.2.12)
+ activesupport (= 3.2.12)
arel (~> 3.0.2)
tzinfo (~> 0.3.29)
- activesupport (3.2.6)
+ activesupport (3.2.12)
i18n (~> 0.6)
multi_json (~> 1.0)
arel (3.0.2)
autotest (4.4.6)
ZenTest (>= 4.4.1)
- builder (3.0.0)
+ builder (3.0.4)
commonjs (0.2.6)
- dalli (2.1.0)
- diff-lcs (1.1.3)
- i18n (0.6.0)
- less (2.2.1)
+ dalli (2.6.2)
+ diff-lcs (1.2.1)
+ i18n (0.6.4)
+ less (2.3.1)
commonjs (~> 0.2.6)
- libv8 (3.3.10.4)
- multi_json (1.3.6)
- rack (1.4.1)
- rack-test (0.6.1)
+ libv8 (3.11.8.13)
+ multi_json (1.6.1)
+ rack (1.5.2)
+ rack-test (0.6.2)
rack (>= 1.0)
- rake (0.9.2.2)
- redis (3.0.1)
- rspec (2.11.0)
- rspec-core (~> 2.11.0)
- rspec-expectations (~> 2.11.0)
- rspec-mocks (~> 2.11.0)
- rspec-core (2.11.0)
- rspec-expectations (2.11.1)
- diff-lcs (~> 1.1.3)
- rspec-mocks (2.11.1)
- therubyracer (0.10.1)
- libv8 (~> 3.3.10)
- tzinfo (0.3.33)
+ rake (10.0.3)
+ redis (3.0.3)
+ ref (1.0.2)
+ rspec (2.13.0)
+ rspec-core (~> 2.13.0)
+ rspec-expectations (~> 2.13.0)
+ rspec-mocks (~> 2.13.0)
+ rspec-core (2.13.1)
+ rspec-expectations (2.13.0)
+ diff-lcs (>= 1.1.3, < 2.0)
+ rspec-mocks (2.13.0)
+ therubyracer (0.11.4)
+ libv8 (~> 3.11.8.12)
+ ref
+ tzinfo (0.3.37)
PLATFORMS
ruby
View
223 Ruby/lib/html/flamegraph.html
@@ -0,0 +1,223 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
+<script src="http://cdnjs.cloudflare.com/ajax/libs/d3/3.0.8/d3.min.js"></script>
+<!-- <script src="http://cdnjs.cloudflare.com/ajax/libs/sugar/1.3.8/sugar.min.js"></script> -->
+<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;
+$.each(data, function(){
+ maxX = Math.max(maxX, this.x + this.width);
+ maxY = Math.max(maxY, this.y);
+});
+
+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 svg = d3.select(".graph").append("svg")
+ .attr("width", "100%")
+ .attr("height", "100%");
+
+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 = {};
+
+// http://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);
+};
+
+// http://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 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];
+ }
+}
+
+$.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;
+});
+
+
+
+svg.selectAll("rect")
+ .data(data)
+ .enter()
+ .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);
+
+// 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);
+});
+
+
+
+/*
+svg.selectAll("text")
+ .data($.grep(data, function(d){
+ return d.width > 2;
+ }))
+ .enter()
+ .append("text")
+ .text(function(d){ return d.frame; })
+ .attr("x",function(d) { return xScale(d.x - 0.7); })
+ .attr("y",function(d) { return yScale(maxY - d.y + 0.7);})
+ .on("mouseover", mouseover)
+ .on("mouseout", mouseout);
+ */
+ </script>
+</body>
+</html>
+
View
526 Ruby/lib/html/includes.css
@@ -1,75 +1,451 @@
-.profiler-result,.profiler-queries{color:#555;line-height:1;font-size:12px;}.profiler-result pre,.profiler-queries pre,.profiler-result code,.profiler-queries code,.profiler-result label,.profiler-queries label,.profiler-result table,.profiler-queries table,.profiler-result tbody,.profiler-queries tbody,.profiler-result thead,.profiler-queries thead,.profiler-result tfoot,.profiler-queries tfoot,.profiler-result tr,.profiler-queries tr,.profiler-result th,.profiler-queries th,.profiler-result td,.profiler-queries td{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline;background-color:transparent;overflow:visible;max-height:none;}
-.profiler-result table,.profiler-queries table{border-collapse:collapse;border-spacing:0;}
-.profiler-result a,.profiler-queries a,.profiler-result a:hover,.profiler-queries a:hover{cursor:pointer;color:#0077cc;}
-.profiler-result a,.profiler-queries a{text-decoration:none;}.profiler-result a:hover,.profiler-queries a:hover{text-decoration:underline;}
-.profiler-result{font-family:Helvetica,Arial,sans-serif;}.profiler-result .profiler-toggle-duration-with-children{float:right;}
-.profiler-result table.profiler-client-timings{margin-top:10px;}
-.profiler-result .profiler-label{color:#555555;overflow:hidden;text-overflow:ellipsis;}
-.profiler-result .profiler-unit{color:#aaaaaa;}
-.profiler-result .profiler-trivial{display:none;}.profiler-result .profiler-trivial td,.profiler-result .profiler-trivial td *{color:#aaaaaa !important;}
-.profiler-result pre,.profiler-result code,.profiler-result .profiler-number,.profiler-result .profiler-unit{font-family:Consolas,monospace,serif;}
-.profiler-result .profiler-number{color:#111111;}
-.profiler-result .profiler-info{text-align:right;}.profiler-result .profiler-info .profiler-name{float:left;}
-.profiler-result .profiler-info .profiler-server-time{white-space:nowrap;}
-.profiler-result .profiler-timings th{background-color:#fff;color:#aaaaaa;text-align:right;}
-.profiler-result .profiler-timings th,.profiler-result .profiler-timings td{white-space:nowrap;}
-.profiler-result .profiler-timings .profiler-duration-with-children{display:none;}
-.profiler-result .profiler-timings .profiler-duration{font-family:Consolas,monospace,serif;color:#111111;text-align:right;}
-.profiler-result .profiler-timings .profiler-indent{letter-spacing:4px;}
-.profiler-result .profiler-timings .profiler-queries-show .profiler-number,.profiler-result .profiler-timings .profiler-queries-show .profiler-unit{color:#0077cc;}
-.profiler-result .profiler-timings .profiler-queries-duration{padding-left:6px;}
-.profiler-result .profiler-timings .profiler-percent-in-sql{white-space:nowrap;text-align:right;}
-.profiler-result .profiler-timings tfoot td{padding-top:10px;text-align:right;}.profiler-result .profiler-timings tfoot td a{font-size:95%;display:inline-block;margin-left:12px;}.profiler-result .profiler-timings tfoot td a:first-child{float:left;margin-left:0px;}
-.profiler-result .profiler-queries{font-family:Helvetica,Arial,sans-serif;}.profiler-result .profiler-queries .profiler-stack-trace{margin-bottom:15px;}
-.profiler-result .profiler-queries pre{font-family:Consolas,monospace,serif;white-space:pre-wrap;}
-.profiler-result .profiler-queries th{background-color:#fff;border-bottom:1px solid #555;font-weight:bold;padding:15px;white-space:nowrap;}
-.profiler-result .profiler-queries td{padding:15px;text-align:left;background-color:#fff;}.profiler-result .profiler-queries td:last-child{padding-right:25px;}
-.profiler-result .profiler-queries .profiler-odd td{background-color:#e5e5e5;}
-.profiler-result .profiler-queries .profiler-since-start,.profiler-result .profiler-queries .profiler-duration{text-align:right;}
-.profiler-result .profiler-queries .profiler-info div{text-align:right;margin-bottom:5px;}
-.profiler-result .profiler-queries .profiler-gap-info,.profiler-result .profiler-queries .profiler-gap-info td{background-color:#ccc;}
-.profiler-result .profiler-queries .profiler-gap-info .profiler-unit{color:#777;}
-.profiler-result .profiler-queries .profiler-gap-info .profiler-info{text-align:right;}
-.profiler-result .profiler-queries .profiler-gap-info.profiler-trivial-gaps{display:none;}
-.profiler-result .profiler-queries .profiler-trivial-gap-container{text-align:center;}
-.profiler-result .profiler-queries .str{color:#800000;}
-.profiler-result .profiler-queries .kwd{color:#00008b;}
-.profiler-result .profiler-queries .com{color:#808080;}
-.profiler-result .profiler-queries .typ{color:#2b91af;}
-.profiler-result .profiler-queries .lit{color:#800000;}
-.profiler-result .profiler-queries .pun{color:#000000;}
-.profiler-result .profiler-queries .pln{color:#000000;}
-.profiler-result .profiler-queries .tag{color:#800000;}
-.profiler-result .profiler-queries .atn{color:#ff0000;}
-.profiler-result .profiler-queries .atv{color:#0000ff;}
-.profiler-result .profiler-queries .dec{color:#800080;}
-.profiler-result .profiler-warning,.profiler-result .profiler-warning *,.profiler-result .profiler-warning .profiler-queries-show,.profiler-result .profiler-warning .profiler-queries-show .profiler-unit{color:#f00;}.profiler-result .profiler-warning:hover,.profiler-result .profiler-warning *:hover,.profiler-result .profiler-warning .profiler-queries-show:hover,.profiler-result .profiler-warning .profiler-queries-show .profiler-unit:hover{color:#f00;}
-.profiler-result .profiler-nuclear{color:#f00;font-weight:bold;padding-right:2px;}.profiler-result .profiler-nuclear:hover{color:#f00;}
-.profiler-results{z-index:2147483643;position:fixed;top:0px;}.profiler-results.profiler-left{left:0px;}.profiler-results.profiler-left.profiler-no-controls .profiler-result:last-child .profiler-button,.profiler-results.profiler-left .profiler-controls{-webkit-border-bottom-right-radius:10px;-moz-border-radius-bottomright:10px;border-bottom-right-radius:10px;}
-.profiler-results.profiler-left .profiler-button,.profiler-results.profiler-left .profiler-controls{border-right:1px solid #888888;}
-.profiler-results.profiler-right{right:0px;}.profiler-results.profiler-right.profiler-no-controls .profiler-result:last-child .profiler-button,.profiler-results.profiler-right .profiler-controls{-webkit-border-bottom-left-radius:10px;-moz-border-radius-bottomleft:10px;border-bottom-left-radius:10px;}
-.profiler-results.profiler-right .profiler-button,.profiler-results.profiler-right .profiler-controls{border-left:1px solid #888888;}
-.profiler-results .profiler-button,.profiler-results .profiler-controls{display:none;z-index:2147483640;border-bottom:1px solid #888888;background-color:#fff;padding:4px 7px;text-align:right;cursor:pointer;}.profiler-results .profiler-button.profiler-button-active,.profiler-results .profiler-controls.profiler-button-active{background-color:maroon;}.profiler-results .profiler-button.profiler-button-active .profiler-number,.profiler-results .profiler-controls.profiler-button-active .profiler-number,.profiler-results .profiler-button.profiler-button-active .profiler-nuclear,.profiler-results .profiler-controls.profiler-button-active .profiler-nuclear{color:#fff;font-weight:bold;}
-.profiler-results .profiler-button.profiler-button-active .profiler-unit,.profiler-results .profiler-controls.profiler-button-active .profiler-unit{color:#fff;font-weight:normal;}
-.profiler-results .profiler-controls{display:block;font-size:12px;font-family:Consolas,monospace,serif;cursor:default;text-align:center;}.profiler-results .profiler-controls span{border-right:1px solid #aaaaaa;padding-right:5px;margin-right:5px;cursor:pointer;}
-.profiler-results .profiler-controls span:last-child{border-right:none;}
-.profiler-results .profiler-popup{display:none;z-index:2147483641;position:absolute;background-color:#fff;border:1px solid #aaa;padding:5px 10px;text-align:left;line-height:18px;overflow:auto;-moz-box-shadow:0px 1px 15px #555555;-webkit-box-shadow:0px 1px 15px #555555;box-shadow:0px 1px 15px #555555;}.profiler-results .profiler-popup .profiler-info{margin-bottom:3px;padding-bottom:2px;border-bottom:1px solid #ddd;}.profiler-results .profiler-popup .profiler-info .profiler-name{font-size:110%;font-weight:bold;}.profiler-results .profiler-popup .profiler-info .profiler-name .profiler-overall-duration{display:none;}
-.profiler-results .profiler-popup .profiler-info .profiler-server-time{font-size:95%;}
-.profiler-results .profiler-popup .profiler-timings th,.profiler-results .profiler-popup .profiler-timings td{padding-left:6px;padding-right:6px;}
-.profiler-results .profiler-popup .profiler-timings th{font-size:95%;padding-bottom:3px;}
-.profiler-results .profiler-popup .profiler-timings .profiler-label{max-width:275px;}
-.profiler-results .profiler-queries{display:none;z-index:2147483643;position:absolute;overflow-y:auto;overflow-x:auto;background-color:#fff;}.profiler-results .profiler-queries th{font-size:17px;}
-.profiler-results.profiler-min .profiler-result{display:none;}
-.profiler-results.profiler-min .profiler-controls span{display:none;}
-.profiler-results.profiler-min .profiler-controls .profiler-min-max{border-right:none;padding:0px;margin:0px;}
-.profiler-queries-bg{z-index:2147483642;display:none;background:#000;opacity:0.7;position:absolute;top:0px;left:0px;min-width:100%;}
-.profiler-result-full .profiler-result{width:950px;margin:30px auto;}.profiler-result-full .profiler-result .profiler-button{display:none;}
-.profiler-result-full .profiler-result .profiler-popup .profiler-info{font-size:25px;border-bottom:1px solid #aaaaaa;padding-bottom:3px;margin-bottom:25px;}.profiler-result-full .profiler-result .profiler-popup .profiler-info .profiler-overall-duration{padding-right:20px;font-size:80%;color:#888;}
-.profiler-result-full .profiler-result .profiler-popup .profiler-timings td,.profiler-result-full .profiler-result .profiler-popup .profiler-timings th{padding-left:8px;padding-right:8px;}
-.profiler-result-full .profiler-result .profiler-popup .profiler-timings th{padding-bottom:7px;}
-.profiler-result-full .profiler-result .profiler-popup .profiler-timings td{font-size:14px;padding-bottom:4px;}.profiler-result-full .profiler-result .profiler-popup .profiler-timings td:first-child{padding-left:10px;}
-.profiler-result-full .profiler-result .profiler-popup .profiler-timings .profiler-label{max-width:550px;}
-.profiler-result-full .profiler-result .profiler-queries{margin:25px 0;}.profiler-result-full .profiler-result .profiler-queries table{width:100%;}
-.profiler-result-full .profiler-result .profiler-queries th{font-size:16px;color:#555;line-height:20px;}
-.profiler-result-full .profiler-result .profiler-queries td{padding:15px 10px;text-align:left;}
-.profiler-result-full .profiler-result .profiler-queries .profiler-info div{text-align:right;margin-bottom:5px;}
+.profiler-result,
+.profiler-queries {
+ color: #555;
+ line-height: 1;
+ font-size: 12px;
+}
+.profiler-result pre,
+.profiler-queries pre,
+.profiler-result code,
+.profiler-queries code,
+.profiler-result label,
+.profiler-queries label,
+.profiler-result table,
+.profiler-queries table,
+.profiler-result tbody,
+.profiler-queries tbody,
+.profiler-result thead,
+.profiler-queries thead,
+.profiler-result tfoot,
+.profiler-queries tfoot,
+.profiler-result tr,
+.profiler-queries tr,
+.profiler-result th,
+.profiler-queries th,
+.profiler-result td,
+.profiler-queries td {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ font-size: 100%;
+ font: inherit;
+ vertical-align: baseline;
+ background-color: transparent;
+ overflow: visible;
+ max-height: none;
+}
+.profiler-result table,
+.profiler-queries table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+.profiler-result a,
+.profiler-queries a,
+.profiler-result a:hover,
+.profiler-queries a:hover {
+ cursor: pointer;
+ color: #0077cc;
+}
+.profiler-result a,
+.profiler-queries a {
+ text-decoration: none;
+}
+.profiler-result a:hover,
+.profiler-queries a:hover {
+ text-decoration: underline;
+}
+.profiler-result {
+ font-family: Helvetica, Arial, sans-serif;
+}
+.profiler-result .profiler-toggle-duration-with-children {
+ float: right;
+}
+.profiler-result table.profiler-client-timings {
+ margin-top: 10px;
+}
+.profiler-result .profiler-label {
+ color: #555555;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+.profiler-result .profiler-unit {
+ color: #aaaaaa;
+}
+.profiler-result .profiler-trivial {
+ display: none;
+}
+.profiler-result .profiler-trivial td,
+.profiler-result .profiler-trivial td * {
+ color: #aaaaaa !important;
+}
+.profiler-result pre,
+.profiler-result code,
+.profiler-result .profiler-number,
+.profiler-result .profiler-unit {
+ font-family: Consolas, monospace, serif;
+}
+.profiler-result .profiler-number {
+ color: #111111;
+}
+.profiler-result .profiler-info {
+ text-align: right;
+}
+.profiler-result .profiler-info .profiler-name {
+ float: left;
+}
+.profiler-result .profiler-info .profiler-server-time {
+ white-space: nowrap;
+}
+.profiler-result .profiler-timings th {
+ background-color: #fff;
+ color: #aaaaaa;
+ text-align: right;
+}
+.profiler-result .profiler-timings th,
+.profiler-result .profiler-timings td {
+ white-space: nowrap;
+}
+.profiler-result .profiler-timings .profiler-duration-with-children {
+ display: none;
+}
+.profiler-result .profiler-timings .profiler-duration {
+ font-family: Consolas, monospace, serif;
+ color: #111111;
+ text-align: right;
+}
+.profiler-result .profiler-timings .profiler-indent {
+ letter-spacing: 4px;
+}
+.profiler-result .profiler-timings .profiler-queries-show .profiler-number,
+.profiler-result .profiler-timings .profiler-queries-show .profiler-unit {
+ color: #0077cc;
+}
+.profiler-result .profiler-timings .profiler-queries-duration {
+ padding-left: 6px;
+}
+.profiler-result .profiler-timings .profiler-percent-in-sql {
+ white-space: nowrap;
+ text-align: right;
+}
+.profiler-result .profiler-timings tfoot td {
+ padding-top: 10px;
+ text-align: right;
+}
+.profiler-result .profiler-timings tfoot td a {
+ font-size: 95%;
+ display: inline-block;
+ margin-left: 12px;
+}
+.profiler-result .profiler-timings tfoot td a:first-child {
+ float: left;
+ margin-left: 0px;
+}
+.profiler-result .profiler-timings tfoot td a.profiler-custom-link {
+ float: left;
+}
+.profiler-result .profiler-queries {
+ font-family: Helvetica, Arial, sans-serif;
+}
+.profiler-result .profiler-queries .profiler-stack-trace {
+ margin-bottom: 15px;
+}
+.profiler-result .profiler-queries pre {
+ font-family: Consolas, monospace, serif;
+ white-space: pre-wrap;
+}
+.profiler-result .profiler-queries th {
+ background-color: #fff;
+ border-bottom: 1px solid #555;
+ font-weight: bold;
+ padding: 15px;
+ white-space: nowrap;
+}
+.profiler-result .profiler-queries td {
+ padding: 15px;
+ text-align: left;
+ background-color: #fff;
+}
+.profiler-result .profiler-queries td:last-child {
+ padding-right: 25px;
+}
+.profiler-result .profiler-queries .profiler-odd td {
+ background-color: #e5e5e5;
+}
+.profiler-result .profiler-queries .profiler-since-start,
+.profiler-result .profiler-queries .profiler-duration {
+ text-align: right;
+}
+.profiler-result .profiler-queries .profiler-info div {
+ text-align: right;
+ margin-bottom: 5px;
+}
+.profiler-result .profiler-queries .profiler-gap-info,
+.profiler-result .profiler-queries .profiler-gap-info td {
+ background-color: #ccc;
+}
+.profiler-result .profiler-queries .profiler-gap-info .profiler-unit {
+ color: #777;
+}
+.profiler-result .profiler-queries .profiler-gap-info .profiler-info {
+ text-align: right;
+}
+.profiler-result .profiler-queries .profiler-gap-info.profiler-trivial-gaps {
+ display: none;
+}
+.profiler-result .profiler-queries .profiler-trivial-gap-container {
+ text-align: center;
+}
+.profiler-result .profiler-queries .str {
+ color: #800000;
+}
+.profiler-result .profiler-queries .kwd {
+ color: #00008b;
+}
+.profiler-result .profiler-queries .com {
+ color: #808080;
+}
+.profiler-result .profiler-queries .typ {
+ color: #2b91af;
+}
+.profiler-result .profiler-queries .lit {
+ color: #800000;
+}
+.profiler-result .profiler-queries .pun {
+ color: #000000;
+}
+.profiler-result .profiler-queries .pln {
+ color: #000000;
+}
+.profiler-result .profiler-queries .tag {
+ color: #800000;
+}
+.profiler-result .profiler-queries .atn {
+ color: #ff0000;
+}
+.profiler-result .profiler-queries .atv {
+ color: #0000ff;
+}
+.profiler-result .profiler-queries .dec {
+ color: #800080;
+}
+.profiler-result .profiler-warning,
+.profiler-result .profiler-warning *,
+.profiler-result .profiler-warning .profiler-queries-show,
+.profiler-result .profiler-warning .profiler-queries-show .profiler-unit {
+ color: #f00;
+}
+.profiler-result .profiler-warning:hover,
+.profiler-result .profiler-warning *:hover,
+.profiler-result .profiler-warning .profiler-queries-show:hover,
+.profiler-result .profiler-warning .profiler-queries-show .profiler-unit:hover {
+ color: #f00;
+}
+.profiler-result .profiler-nuclear {
+ color: #f00;
+ font-weight: bold;
+ padding-right: 2px;
+}
+.profiler-result .profiler-nuclear:hover {
+ color: #f00;
+}
+.profiler-results {
+ z-index: 2147483643;
+ position: fixed;
+ top: 0px;
+}
+.profiler-results.profiler-left {
+ left: 0px;
+}
+.profiler-results.profiler-left.profiler-no-controls .profiler-result:last-child .profiler-button,
+.profiler-results.profiler-left .profiler-controls {
+ -webkit-border-bottom-right-radius: 10px;
+ -moz-border-radius-bottomright: 10px;
+ border-bottom-right-radius: 10px;
+}
+.profiler-results.profiler-left .profiler-button,
+.profiler-results.profiler-left .profiler-controls {
+ border-right: 1px solid #888888;
+}
+.profiler-results.profiler-right {
+ right: 0px;
+}
+.profiler-results.profiler-right.profiler-no-controls .profiler-result:last-child .profiler-button,
+.profiler-results.profiler-right .profiler-controls {
+ -webkit-border-bottom-left-radius: 10px;
+ -moz-border-radius-bottomleft: 10px;
+ border-bottom-left-radius: 10px;
+}
+.profiler-results.profiler-right .profiler-button,
+.profiler-results.profiler-right .profiler-controls {
+ border-left: 1px solid #888888;
+}
+.profiler-results .profiler-button,
+.profiler-results .profiler-controls {
+ display: none;
+ z-index: 2147483640;
+ border-bottom: 1px solid #888888;
+ background-color: #fff;
+ padding: 4px 7px;
+ text-align: right;
+ cursor: pointer;
+}
+.profiler-results .profiler-button.profiler-button-active,
+.profiler-results .profiler-controls.profiler-button-active {
+ background-color: maroon;
+}
+.profiler-results .profiler-button.profiler-button-active .profiler-number,
+.profiler-results .profiler-controls.profiler-button-active .profiler-number,
+.profiler-results .profiler-button.profiler-button-active .profiler-nuclear,
+.profiler-results .profiler-controls.profiler-button-active .profiler-nuclear {
+ color: #fff;
+ font-weight: bold;
+}
+.profiler-results .profiler-button.profiler-button-active .profiler-unit,
+.profiler-results .profiler-controls.profiler-button-active .profiler-unit {
+ color: #fff;
+ font-weight: normal;
+}
+.profiler-results .profiler-controls {
+ display: block;
+ font-size: 12px;
+ font-family: Consolas, monospace, serif;
+ cursor: default;
+ text-align: center;
+}
+.profiler-results .profiler-controls span {
+ border-right: 1px solid #aaaaaa;
+ padding-right: 5px;
+ margin-right: 5px;
+ cursor: pointer;
+}
+.profiler-results .profiler-controls span:last-child {
+ border-right: none;
+}
+.profiler-results .profiler-popup {
+ display: none;
+ z-index: 2147483641;
+ position: absolute;
+ background-color: #fff;
+ border: 1px solid #aaa;
+ padding: 5px 10px;
+ text-align: left;
+ line-height: 18px;
+ overflow: auto;
+ -moz-box-shadow: 0px 1px 15px #555555;
+ -webkit-box-shadow: 0px 1px 15px #555555;
+ box-shadow: 0px 1px 15px #555555;
+}
+.profiler-results .profiler-popup .profiler-info {
+ margin-bottom: 3px;
+ padding-bottom: 2px;
+ border-bottom: 1px solid #ddd;
+}
+.profiler-results .profiler-popup .profiler-info .profiler-name {
+ font-size: 110%;
+ font-weight: bold;
+}
+.profiler-results .profiler-popup .profiler-info .profiler-name .profiler-overall-duration {
+ display: none;
+}
+.profiler-results .profiler-popup .profiler-info .profiler-server-time {
+ font-size: 95%;
+}
+.profiler-results .profiler-popup .profiler-timings th,
+.profiler-results .profiler-popup .profiler-timings td {
+ padding-left: 6px;
+ padding-right: 6px;
+}
+.profiler-results .profiler-popup .profiler-timings th {
+ font-size: 95%;
+ padding-bottom: 3px;
+}
+.profiler-results .profiler-popup .profiler-timings .profiler-label {
+ max-width: 275px;
+}
+.profiler-results .profiler-queries {
+ display: none;
+ z-index: 2147483643;
+ position: absolute;
+ overflow-y: auto;
+ overflow-x: auto;
+ background-color: #fff;
+}
+.profiler-results .profiler-queries th {
+ font-size: 17px;
+}
+.profiler-results.profiler-min .profiler-result {
+ display: none;
+}
+.profiler-results.profiler-min .profiler-controls span {
+ display: none;
+}
+.profiler-results.profiler-min .profiler-controls .profiler-min-max {
+ border-right: none;
+ padding: 0px;
+ margin: 0px;
+}
+.profiler-queries-bg {
+ z-index: 2147483642;
+ display: none;
+ background: #000;
+ opacity: 0.7;
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ min-width: 100%;
+}
+.profiler-result-full .profiler-result {
+ width: 950px;
+ margin: 30px auto;
+}
+.profiler-result-full .profiler-result .profiler-button {
+ display: none;
+}
+.profiler-result-full .profiler-result .profiler-popup .profiler-info {
+ font-size: 25px;
+ border-bottom: 1px solid #aaaaaa;
+ padding-bottom: 3px;
+ margin-bottom: 25px;
+}
+.profiler-result-full .profiler-result .profiler-popup .profiler-info .profiler-overall-duration {
+ padding-right: 20px;
+ font-size: 80%;
+ color: #888;
+}
+.profiler-result-full .profiler-result .profiler-popup .profiler-timings td,
+.profiler-result-full .profiler-result .profiler-popup .profiler-timings th {
+ padding-left: 8px;
+ padding-right: 8px;
+}
+.profiler-result-full .profiler-result .profiler-popup .profiler-timings th {
+ padding-bottom: 7px;
+}
+.profiler-result-full .profiler-result .profiler-popup .profiler-timings td {
+ font-size: 14px;
+ padding-bottom: 4px;
+}
+.profiler-result-full .profiler-result .profiler-popup .profiler-timings td:first-child {
+ padding-left: 10px;
+}
+.profiler-result-full .profiler-result .profiler-popup .profiler-timings .profiler-label {
+ max-width: 550px;
+}
+.profiler-result-full .profiler-result .profiler-queries {
+ margin: 25px 0;
+}
+.profiler-result-full .profiler-result .profiler-queries table {
+ width: 100%;
+}
+.profiler-result-full .profiler-result .profiler-queries th {
+ font-size: 16px;
+ color: #555;
+ line-height: 20px;
+}
+.profiler-result-full .profiler-result .profiler-queries td {
+ padding: 15px 10px;
+ text-align: left;
+}
+.profiler-result-full .profiler-result .profiler-queries .profiler-info div {
+ text-align: right;
+ margin-bottom: 5px;
+}
View
3 Ruby/lib/mini_profiler/context.rb
@@ -1,7 +1,8 @@
class Rack::MiniProfiler::Context
- attr_accessor :inject_js,:current_timer,:page_struct,:skip_backtrace,:full_backtrace,:discard, :mpt_init
+ attr_accessor :inject_js,:current_timer,:page_struct,:skip_backtrace,:full_backtrace,:discard, :mpt_init, :measure
def initialize(opts = {})
+ opts["measure"] = true unless opts.key? "measure"
opts.each do |k,v|
self.instance_variable_set('@' + k, v)
end
View
54 Ruby/lib/mini_profiler/flame_graph.rb
@@ -0,0 +1,54 @@
+# inspired by https://github.com/brendangregg/FlameGraph
+
+class Rack::MiniProfiler::FlameGraph
+ def initialize(stacks)
+ @stacks = stacks
+ end
+
+ def graph_data
+ height = 0
+
+ table = []
+ prev = []
+
+ # a 2d array makes collapsing easy
+ @stacks.each_with_index do |stack, pos|
+ col = []
+
+ stack.reverse.map{|r| r.to_s}.each_with_index do |frame, i|
+
+ if !prev[i].nil?
+ last_col = prev[i]
+ if last_col[0] == frame
+ last_col[1] += 1
+ col << nil
+ next
+ end
+ end
+
+ prev[i] = [frame, 1]
+ col << prev[i]
+ end
+ prev = prev[0..col.length-1].to_a
+ table << col
+ end
+
+ data = []
+
+ # a 1d array makes rendering easy
+ table.each_with_index do |col, col_num|
+ col.each_with_index do |row, row_num|
+ next unless row && row.length == 2
+ data << {
+ :x => col_num + 1,
+ :y => row_num + 1,
+ :width => row[1],
+ :frame => row[0]
+ }
+ end
+ end
+
+ data
+ end
+
+end
View
88 Ruby/lib/mini_profiler/profiler.rb
@@ -18,6 +18,7 @@
require 'mini_profiler/context'
require 'mini_profiler/client_settings'
require 'mini_profiler/gc_profiler'
+require 'mini_profiler/flame_graph'
module Rack
@@ -79,28 +80,6 @@ def request_authorized?
Thread.current[:mp_authorized]
end
- # Add a custom timing. These are displayed similar to SQL/query time in
- # columns expanding to the right.
- #
- # type - String counter type. Each distinct type gets its own column.
- # duration_ms - Duration of the call in ms. Either this or a block must be
- # given but not both.
- #
- # When a block is given, calculate the duration by yielding to the block
- # and keeping a record of its run time.
- #
- # Returns the result of the block, or nil when no block is given.
- def counter(type, duration_ms=nil)
- result = nil
- if block_given?
- start = Time.now
- result = yield
- duration_ms = (Time.now - start).to_f * 1000
- end
- return result if current.nil? || !request_authorized?
- current.current_timer.add_custom(type, duration_ms, current.page_struct)
- result
- end
end
#
@@ -236,19 +215,13 @@ def call(env)
end
if query_string =~ /pp=profile-gc/
- # begin
- if query_string =~ /pp=profile-gc-time/
- return Rack::MiniProfiler::GCProfiler.new.profile_gc_time(@app, env)
- else
- return Rack::MiniProfiler::GCProfiler.new.profile_gc(@app, env)
- end
- # rescue => e
- # p e
- # e.backtrace.each do |s|
- # puts s
- # end
- # end
+ if query_string =~ /pp=profile-gc-time/
+ return Rack::MiniProfiler::GCProfiler.new.profile_gc_time(@app, env)
+ else
+ return Rack::MiniProfiler::GCProfiler.new.profile_gc(@app, env)
+ end
end
+
MiniProfiler.create_current(env, @config)
MiniProfiler.deauthorize_request if @config.authorization_mode == :whitelist
if query_string =~ /pp=normal-backtrace/
@@ -266,31 +239,25 @@ def call(env)
done_sampling = false
quit_sampler = false
backtraces = nil
- stacktrace_installed = true
- if query_string =~ /pp=sample/
+ if query_string =~ /pp=sample/ || query_string =~ /pp=flamegraph/
+ current.measure = false
skip_frames = 0
backtraces = []
t = Thread.current
- begin
- require 'stacktrace'
- skip_frames = stacktrace.length
- rescue LoadError
- stacktrace_installed = false
- end
-
Thread.new {
+ # new in Ruby 2.0
+ has_backtrace_locations = t.respond_to?(:backtrace_locations)
begin
i = 10000 # for sanity never grab more than 10k samples
while i > 0
break if done_sampling
i -= 1
- if stacktrace_installed
- backtraces << t.stacktrace(0,-(1+skip_frames), StackFrame::Flags::METHOD | StackFrame::Flags::KLASS)
- else
- backtraces << t.backtrace
- end
- sleep 0.001
+ backtraces << (has_backtrace_locations ? t.backtrace_locations : t.backtrace)
+
+ # On my machine using Ruby 2.0 this give me excellent fidelity of stack trace per 1.2ms
+ # with this fidelity analysis becomes very powerful
+ sleep 0.0005
end
ensure
quit_sampler = true
@@ -345,7 +312,11 @@ def call(env)
if backtraces
body.close if body.respond_to? :close
- return analyze(backtraces, page_struct)
+ if query_string =~ /pp=sample/
+ return analyze(backtraces, page_struct)
+ else
+ return flame_graph(backtraces, page_struct)
+ end
end
@@ -447,17 +418,30 @@ def help(client_settings)
pp=no-backtrace #{"(*) " if client_settings.backtrace_none?}: don't collect stack traces from all the SQL executed (sticky, use pp=normal-backtrace to enable)
pp=normal-backtrace #{"(*) " if client_settings.backtrace_default?}: collect stack traces from all the SQL executed and filter normally
pp=full-backtrace #{"(*) " if client_settings.backtrace_full?}: enable full backtraces for SQL executed (use pp=normal-backtrace to disable)
- pp=sample : sample stack traces and return a report isolating heavy usage (experimental works best with the stacktrace gem)
+ pp=sample : sample stack traces and return a report isolating heavy usage (works best on Ruby 2.0)
pp=disable : disable profiling for this session
pp=enable : enable profiling for this session (if previously disabled)
pp=profile-gc: perform gc profiling on this request, analyzes ObjectSpace generated by request (ruby 1.9.3 only)
pp=profile-gc-time: perform built-in gc profiling on this request (ruby 1.9.3 only)
+ pp=flamegraph: works best on Ruby 2.0, a graph representing sampled activity.
"
client_settings.write!(headers)
[200, headers, [body]]
end
+ def flame_graph(traces, page_struct)
+ graph = FlameGraph.new(traces)
+ data = graph.graph_data
+
+ headers = {'Content-Type' => 'text/html'}
+
+ body = IO.read(::File.expand_path('../html/flamegraph.html', ::File.dirname(__FILE__)))
+ body.gsub!("/*DATA*/", ::JSON.generate(data));
+
+ [200, headers, [body]]
+ end
+
def analyze(traces, page_struct)
headers = {'Content-Type' => 'text/plain'}
body = "Collected: #{traces.count} stack traces. Duration(ms): #{page_struct.duration_ms}"
@@ -468,7 +452,7 @@ def analyze(traces, page_struct)
fulldump << "\n\n"
distinct = {}
trace.each do |frame|
- frame = "#{frame.klass}::#{frame.method}" unless String === frame
+ frame = frame.to_s unless String === frame
unless distinct[frame]
distinct[frame] = true
seen[frame] ||= 0
View
23 Ruby/lib/mini_profiler/profiling_methods.rb
@@ -96,6 +96,29 @@ def profile_method(klass, method, &blk)
end
klass.send :alias_method, method, with_profiling
end
+
+ # Add a custom timing. These are displayed similar to SQL/query time in
+ # columns expanding to the right.
+ #
+ # type - String counter type. Each distinct type gets its own column.
+ # duration_ms - Duration of the call in ms. Either this or a block must be
+ # given but not both.
+ #
+ # When a block is given, calculate the duration by yielding to the block
+ # and keeping a record of its run time.
+ #
+ # Returns the result of the block, or nil when no block is given.
+ def counter(type, duration_ms=nil)
+ result = nil
+ if block_given?
+ start = Time.now
+ result = yield
+ duration_ms = (Time.now - start).to_f * 1000
+ end
+ return result if current.nil? || !request_authorized?
+ current.current_timer.add_custom(type, duration_ms, current.page_struct)
+ result
+ end
private
View
4 Ruby/lib/mini_profiler/version.rb
@@ -1,5 +1,5 @@
module Rack
- class MiniProfiler
- VERSION = '33d69ecf833daec8db07a9a0b6cf0bd3'.freeze
+ class MiniProfiler
+ VERSION = '843cab636fb537adf19b58eff230ff75'.freeze
end
end
View
18 Ruby/lib/patches/sql_patches.rb
@@ -42,7 +42,7 @@ class Mysql2::Client
alias_method :query_without_profiling, :query
def query(*args,&blk)
current = ::Rack::MiniProfiler.current
- return query_without_profiling(*args,&blk) unless current
+ return query_without_profiling(*args,&blk) unless current && current.measure
start = Time.now
result = query_without_profiling(*args,&blk)
@@ -105,14 +105,14 @@ def prepare(*args,&blk)
@prepare_map = {} if @prepare_map.length > 1000
current = ::Rack::MiniProfiler.current
- return prepare_without_profiling(*args,&blk) unless current
+ return prepare_without_profiling(*args,&blk) unless current && current.measure
prepare_without_profiling(*args,&blk)
end
def exec(*args,&blk)
current = ::Rack::MiniProfiler.current
- return exec_without_profiling(*args,&blk) unless current
+ return exec_without_profiling(*args,&blk) unless current && current.measure
start = Time.now
result = exec_without_profiling(*args,&blk)
@@ -124,7 +124,7 @@ def exec(*args,&blk)
def exec_prepared(*args,&blk)
current = ::Rack::MiniProfiler.current
- return exec_prepared_without_profiling(*args,&blk) unless current
+ return exec_prepared_without_profiling(*args,&blk) unless current && current.measure
start = Time.now
result = exec_prepared_without_profiling(*args,&blk)
@@ -138,7 +138,7 @@ def exec_prepared(*args,&blk)
def send_query_prepared(*args,&blk)
current = ::Rack::MiniProfiler.current
- return send_query_prepared_without_profiling(*args,&blk) unless current
+ return send_query_prepared_without_profiling(*args,&blk) unless current && current.measure
start = Time.now
result = send_query_prepared_without_profiling(*args,&blk)
@@ -152,7 +152,7 @@ def send_query_prepared(*args,&blk)
def async_exec(*args,&blk)
current = ::Rack::MiniProfiler.current
- return exec_without_profiling(*args,&blk) unless current
+ return exec_without_profiling(*args,&blk) unless current && current.measure
start = Time.now
result = exec_without_profiling(*args,&blk)
@@ -175,7 +175,7 @@ class Moped::Node
alias_method :process_without_profiling, :process
def process(*args,&blk)
current = ::Rack::MiniProfiler.current
- return process_without_profiling(*args,&blk) unless current
+ return process_without_profiling(*args,&blk) unless current && current.measure
start = Time.now
result = process_without_profiling(*args,&blk)
@@ -192,7 +192,7 @@ class RSolr::Connection
alias_method :execute_without_profiling, :execute
def execute_with_profiling(client, request_context)
current = ::Rack::MiniProfiler.current
- return execute_without_profiling(client, request_context) unless current
+ return execute_without_profiling(client, request_context) unless current && current.measure
start = Time.now
result = execute_without_profiling(client, request_context)
@@ -243,7 +243,7 @@ def self.included(instrumented_class)
def log_with_miniprofiler(*args, &block)
current = ::Rack::MiniProfiler.current
- return log_without_miniprofiler(*args, &block) unless current
+ return log_without_miniprofiler(*args, &block) unless current && current.measure
sql, name, binds = args
t0 = Time.now
View
1 Ruby/spec/components/file_store_spec.rb
@@ -1,4 +1,3 @@
-
require 'spec_helper'
require 'rack-mini-profiler'
require 'mini_profiler/page_timer_struct'
View
37 Ruby/spec/components/flame_graph_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper'
+require 'rack-mini-profiler'
+require 'mini_profiler/flame_graph'
+
+describe Rack::MiniProfiler::FlameGraph do
+
+ it "builds a table correctly" do
+ stacks = [["3","2","1"],["4","1"],["4","5"]]
+
+ g = Rack::MiniProfiler::FlameGraph.new(stacks)
+
+ g.graph_data.should == [
+ {:x => 1, :y => 1, :frame => "1", :width => 2},
+ {:x => 1, :y => 2, :frame => "2", :width => 1},
+ {:x => 1, :y => 3, :frame => "3", :width => 1},
+ {:x => 2, :y => 2, :frame => "4", :width => 2},
+ {:x => 3, :y => 1, :frame => "5", :width => 1}
+ ]
+
+ end
+
+
+ it "avoids bridges" do
+ stacks = [["3","2","1"],["1"],["3","2","1"]]
+
+ g = Rack::MiniProfiler::FlameGraph.new(stacks)
+
+ g.graph_data.should == [
+ {:x => 1, :y => 1, :frame => "1", :width => 3},
+ {:x => 1, :y => 2, :frame => "2", :width => 1},
+ {:x => 1, :y => 3, :frame => "3", :width => 1},
+ {:x => 3, :y => 2, :frame => "2", :width => 1},
+ {:x => 3, :y => 3, :frame => "3", :width => 1}
+ ]
+ end
+
+end

0 comments on commit 1a4f581

Please sign in to comment.
Something went wrong with that request. Please try again.