stackprof 0.2.5 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/bin/stackprof +5 -1
- data/ext/stackprof.c +1 -1
- data/lib/stackprof/flamegraph/flamegraph.js +357 -0
- data/lib/stackprof/flamegraph/viewer.html +85 -0
- data/lib/stackprof/report.rb +8 -5
- data/stackprof.gemspec +1 -1
- data/test/test_stackprof.rb +16 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e783faab7c8c97296d52c602c971eddb3e4c85eb
|
4
|
+
data.tar.gz: 4952c5aec78540e9a2509e37f6e2195f56568712
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3a0f36b8202aefb65836dbe71b359fbcea3f7ecf4252fce52ec127f47db0ea9995f177f808c631d6803f5b40e1697d2e065028a15ceb17387351b2d3fa7af0e7
|
7
|
+
data.tar.gz: 685c58d929c5681693c64a1a63e939382171420ecc83856cabc9efeb2d0f7e65c25802d6f0c5c47e0b6def8addda2f0b78985048c7829f6b23e7ccb636be79ff
|
data/Gemfile.lock
CHANGED
data/bin/stackprof
CHANGED
@@ -20,7 +20,11 @@ parser = OptionParser.new(ARGV) do |o|
|
|
20
20
|
o.on('--callgrind', 'Callgrind output (use with kcachegrind, stackprof-gprof2dot.py)'){ options[:format] = :callgrind }
|
21
21
|
o.on('--graphviz', "Graphviz output (use with dot)"){ options[:format] = :graphviz }
|
22
22
|
o.on('--stackcollapse', 'stackcollapse.pl compatible output (use with stackprof-flamegraph.pl)'){ options[:format] = :stackcollapse }
|
23
|
-
o.on('--flamegraph', "timeline-flamegraph output (js)
|
23
|
+
o.on('--flamegraph', "timeline-flamegraph output (js)"){ options[:format] = :flamegraph }
|
24
|
+
o.on('--flamegraph-viewer [f.js]', String, "open html viewer for flamegraph output\n\n"){ |file|
|
25
|
+
puts("open file://#{File.expand_path('../../lib/stackprof/flamegraph/viewer.html', __FILE__)}?data=#{File.expand_path(file)}")
|
26
|
+
exit
|
27
|
+
}
|
24
28
|
o.on('--dump', 'Print marshaled profile dump (combine multiple profiles)'){ options[:format] = :dump }
|
25
29
|
o.on('--debug', 'Pretty print raw profile data'){ options[:format] = :debug }
|
26
30
|
end
|
data/ext/stackprof.c
CHANGED
@@ -346,7 +346,7 @@ stackprof_record_sample()
|
|
346
346
|
VALUE prev_frame = Qnil;
|
347
347
|
|
348
348
|
_stackprof.overall_samples++;
|
349
|
-
num = rb_profile_frames(0, sizeof(_stackprof.frames_buffer), _stackprof.frames_buffer, _stackprof.lines_buffer);
|
349
|
+
num = rb_profile_frames(0, sizeof(_stackprof.frames_buffer) / sizeof(VALUE), _stackprof.frames_buffer, _stackprof.lines_buffer);
|
350
350
|
|
351
351
|
if (_stackprof.raw) {
|
352
352
|
int found = 0;
|
@@ -0,0 +1,357 @@
|
|
1
|
+
var guessGem = function(frame) {
|
2
|
+
var split = frame.split('/gems/');
|
3
|
+
if(split.length == 1) {
|
4
|
+
split = frame.split('/app/');
|
5
|
+
if(split.length == 1) {
|
6
|
+
split = frame.split('/lib/');
|
7
|
+
} else {
|
8
|
+
return split[split.length-1].split('/')[0]
|
9
|
+
}
|
10
|
+
|
11
|
+
split = split[Math.max(split.length-2,0)].split('/');
|
12
|
+
return split[split.length-1].split(':')[0];
|
13
|
+
}
|
14
|
+
else
|
15
|
+
{
|
16
|
+
return split[split.length -1].split('/')[0].split('-', 2)[0];
|
17
|
+
}
|
18
|
+
}
|
19
|
+
|
20
|
+
var color = function() {
|
21
|
+
var r = parseInt(205 + Math.random() * 50);
|
22
|
+
var g = parseInt(Math.random() * 230);
|
23
|
+
var b = parseInt(Math.random() * 55);
|
24
|
+
return "rgb(" + r + "," + g + "," + b + ")";
|
25
|
+
}
|
26
|
+
|
27
|
+
// http://stackoverflow.com/a/7419630
|
28
|
+
var rainbow = function(numOfSteps, step) {
|
29
|
+
// 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.
|
30
|
+
// Adam Cole, 2011-Sept-14
|
31
|
+
// HSV to RBG adapted from: http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
|
32
|
+
var r, g, b;
|
33
|
+
var h = step / numOfSteps;
|
34
|
+
var i = ~~(h * 6);
|
35
|
+
var f = h * 6 - i;
|
36
|
+
var q = 1 - f;
|
37
|
+
switch(i % 6){
|
38
|
+
case 0: r = 1, g = f, b = 0; break;
|
39
|
+
case 1: r = q, g = 1, b = 0; break;
|
40
|
+
case 2: r = 0, g = 1, b = f; break;
|
41
|
+
case 3: r = 0, g = q, b = 1; break;
|
42
|
+
case 4: r = f, g = 0, b = 1; break;
|
43
|
+
case 5: r = 1, g = 0, b = q; break;
|
44
|
+
}
|
45
|
+
var c = "#" + ("00" + (~ ~(r * 255)).toString(16)).slice(-2) + ("00" + (~ ~(g * 255)).toString(16)).slice(-2) + ("00" + (~ ~(b * 255)).toString(16)).slice(-2);
|
46
|
+
return (c);
|
47
|
+
}
|
48
|
+
|
49
|
+
// http://stackoverflow.com/questions/1960473/unique-values-in-an-array
|
50
|
+
var getUnique = function(orig) {
|
51
|
+
var o = {}, a = []
|
52
|
+
for (var i = 0; i < orig.length; i++) o[orig[i]] = 1
|
53
|
+
for (var e in o) a.push(e)
|
54
|
+
return a
|
55
|
+
}
|
56
|
+
|
57
|
+
function flamegraph(data) {
|
58
|
+
var maxX = 0;
|
59
|
+
var maxY = 0;
|
60
|
+
var minY = 10000;
|
61
|
+
$.each(data, function(){
|
62
|
+
maxX = Math.max(maxX, this.x + this.width);
|
63
|
+
maxY = Math.max(maxY, this.y);
|
64
|
+
minY = Math.min(minY, this.y);
|
65
|
+
});
|
66
|
+
|
67
|
+
// normalize Y
|
68
|
+
if (minY > 0) {
|
69
|
+
$.each(data, function(){
|
70
|
+
this.y -= minY
|
71
|
+
})
|
72
|
+
maxY -= minY
|
73
|
+
minY = 0
|
74
|
+
}
|
75
|
+
|
76
|
+
var margin = {top: 10, right: 10, bottom: 10, left: 10}
|
77
|
+
var width = $(window).width() - 200 - margin.left - margin.right;
|
78
|
+
var height = $(window).height() * 0.70 - margin.top - margin.bottom;
|
79
|
+
var height2 = $(window).height() * 0.30 - 60 - margin.top - margin.bottom;
|
80
|
+
|
81
|
+
$('.flamegraph').width(width + margin.left + margin.right).height(height + margin.top + margin.bottom);
|
82
|
+
$('.zoom').width(width + margin.left + margin.right).height(height2 + margin.top + margin.bottom);
|
83
|
+
|
84
|
+
var xScale = d3.scale.linear()
|
85
|
+
.domain([0, maxX])
|
86
|
+
.range([0, width]);
|
87
|
+
|
88
|
+
var xScale2 = d3.scale.linear()
|
89
|
+
.domain([0, maxX])
|
90
|
+
.range([0, width])
|
91
|
+
|
92
|
+
var yScale = d3.scale.linear()
|
93
|
+
.domain([0, maxY])
|
94
|
+
.range([0,height]);
|
95
|
+
|
96
|
+
var yScale2 = d3.scale.linear()
|
97
|
+
.domain([0, maxY])
|
98
|
+
.range([0,height2]);
|
99
|
+
|
100
|
+
var zoomXRatio = 1
|
101
|
+
var zoomed = function() {
|
102
|
+
svg.attr("transform", "translate(" + d3.event.translate + ")" + " scale(" + (zoomXRatio*d3.event.scale) + "," + d3.event.scale + ")");
|
103
|
+
|
104
|
+
var x = xScale.domain(), y = yScale.domain()
|
105
|
+
brush.extent([ [x[0]/zoomXRatio, y[0]], [x[1]/zoomXRatio, y[1]] ])
|
106
|
+
if (x[1] == maxX && y[1] == maxY)
|
107
|
+
brush.clear()
|
108
|
+
svg2.select('g.brush').call(brush)
|
109
|
+
}
|
110
|
+
|
111
|
+
var zoom = d3.behavior.zoom().x(xScale).y(yScale).scaleExtent([1, 14]).on('zoom', zoomed)
|
112
|
+
|
113
|
+
var svg2 = d3.select('.zoom').append('svg').attr('width', '100%').attr('height', '100%').append('svg:g')
|
114
|
+
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
|
115
|
+
.append('g').attr('class', 'graph')
|
116
|
+
|
117
|
+
var svg = d3.select(".flamegraph")
|
118
|
+
.append("svg")
|
119
|
+
.attr("width", "100%")
|
120
|
+
.attr("height", "100%")
|
121
|
+
.attr("pointer-events", "all")
|
122
|
+
.append('svg:g')
|
123
|
+
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
|
124
|
+
.call(zoom)
|
125
|
+
.append('svg:g').attr('class', 'graph');
|
126
|
+
|
127
|
+
// so zoom works everywhere
|
128
|
+
svg.append("rect")
|
129
|
+
.attr("x",function(d) { return xScale(0); })
|
130
|
+
.attr("y",function(d) { return yScale(0);})
|
131
|
+
.attr("width", function(d){return xScale(maxX);})
|
132
|
+
.attr("height", yScale(maxY))
|
133
|
+
.attr("fill", "white");
|
134
|
+
|
135
|
+
var samplePercentRaw = function(samples, exclusive) {
|
136
|
+
var ret = [samples, ((samples / maxX) * 100).toFixed(2)]
|
137
|
+
if (exclusive)
|
138
|
+
ret = ret.concat([exclusive, ((exclusive / maxX) * 100).toFixed(2)])
|
139
|
+
return ret;
|
140
|
+
}
|
141
|
+
|
142
|
+
var samplePercent = function(samples, exclusive) {
|
143
|
+
var info = samplePercentRaw(samples, exclusive)
|
144
|
+
var samplesPct = info[1], exclusivePct = info[3]
|
145
|
+
var ret = " (" + samples + " sample" + (samples == 1 ? "" : "s") + " - " + samplesPct + "%) ";
|
146
|
+
if (exclusive)
|
147
|
+
ret += " (" + exclusive + " exclusive - " + exclusivePct + "%) ";
|
148
|
+
return ret;
|
149
|
+
}
|
150
|
+
|
151
|
+
var info = {};
|
152
|
+
|
153
|
+
var mouseover = function(d) {
|
154
|
+
var i = info[d.frame_id];
|
155
|
+
var shortFile = d.file.replace(/^.+\/(gems|app|lib|config|jobs)/, '$1')
|
156
|
+
var data = samplePercentRaw(i.samples.length, d.topFrame ? d.topFrame.exclusiveCount : 0)
|
157
|
+
|
158
|
+
$('.info')
|
159
|
+
.css('background-color', i.color)
|
160
|
+
.find('.frame').text(d.frame).end()
|
161
|
+
.find('.file').text(shortFile).end()
|
162
|
+
.find('.samples').text(data[0] + ' samples ('+data[1]+'%)').end()
|
163
|
+
.find('.exclusive').text('')
|
164
|
+
|
165
|
+
if (data[3])
|
166
|
+
$('.info .exclusive').text(data[2] + ' exclusive ('+data[3]+'%)')
|
167
|
+
|
168
|
+
d3.selectAll(i.nodes)
|
169
|
+
.attr('opacity',0.5);
|
170
|
+
};
|
171
|
+
|
172
|
+
var mouseout = function(d) {
|
173
|
+
var i = info[d.frame_id];
|
174
|
+
$('.info').css('background-color', 'none').find('.frame, .file, .samples, .exclusive').text('')
|
175
|
+
|
176
|
+
d3.selectAll(i.nodes)
|
177
|
+
.attr('opacity',1);
|
178
|
+
};
|
179
|
+
|
180
|
+
// assign some colors, analyze samples per gem
|
181
|
+
var gemStats = {}
|
182
|
+
var topFrames = {}
|
183
|
+
var lastFrame = {frame: 'd52e04d-df28-41ed-a215-b6ec840a8ea5', x: -1}
|
184
|
+
|
185
|
+
$.each(data, function(){
|
186
|
+
var gem = guessGem(this.file);
|
187
|
+
var stat = gemStats[gem];
|
188
|
+
this.gemName = gem
|
189
|
+
|
190
|
+
if(!stat) {
|
191
|
+
gemStats[gem] = stat = {name: gem, samples: [], frames: [], nodes:[]};
|
192
|
+
}
|
193
|
+
|
194
|
+
stat.frames.push(this.frame_id);
|
195
|
+
for(var j=0; j < this.width; j++){
|
196
|
+
stat.samples.push(this.x + j);
|
197
|
+
}
|
198
|
+
// This assumes the traversal is in order
|
199
|
+
if (lastFrame.x != this.x) {
|
200
|
+
var topFrame = topFrames[lastFrame.frame_id]
|
201
|
+
if (!topFrame) {
|
202
|
+
topFrames[lastFrame.frame_id] = topFrame = {exclusiveCount: 0}
|
203
|
+
}
|
204
|
+
topFrame.exclusiveCount += 1;
|
205
|
+
lastFrame.topFrame = topFrame;
|
206
|
+
}
|
207
|
+
lastFrame = this;
|
208
|
+
|
209
|
+
});
|
210
|
+
|
211
|
+
var topFrame = topFrames[lastFrame.frame_id]
|
212
|
+
if (!topFrame) {
|
213
|
+
topFrames[lastFrame.frame_id] = topFrame = {exclusiveCount: 0}
|
214
|
+
}
|
215
|
+
topFrame.exclusiveCount += 1;
|
216
|
+
lastFrame.topFrame = topFrame;
|
217
|
+
|
218
|
+
var totalGems = 0;
|
219
|
+
$.each(gemStats, function(k,stat){
|
220
|
+
totalGems++;
|
221
|
+
stat.samples = getUnique(stat.samples);
|
222
|
+
});
|
223
|
+
|
224
|
+
var gemsSorted = $.map(gemStats, function(v, k){ return v })
|
225
|
+
gemsSorted.sort(function(a, b){ return b.samples.length - a.samples.length })
|
226
|
+
|
227
|
+
var currentIndex = 0;
|
228
|
+
$.each(gemsSorted, function(k,stat){
|
229
|
+
stat.color = rainbow(totalGems, currentIndex);
|
230
|
+
currentIndex += 1;
|
231
|
+
|
232
|
+
for(var x=0; x < stat.frames.length; x++) {
|
233
|
+
info[stat.frames[x]] = {nodes: [], samples: [], color: stat.color};
|
234
|
+
}
|
235
|
+
});
|
236
|
+
|
237
|
+
function drawData(svg, data, xScale, yScale, mini) {
|
238
|
+
svg.selectAll("g.flames")
|
239
|
+
.data(data)
|
240
|
+
.enter()
|
241
|
+
.append("g")
|
242
|
+
.attr('class', 'flames')
|
243
|
+
.each(function(d){
|
244
|
+
gemStats[d.gemName].nodes.push(this)
|
245
|
+
|
246
|
+
var r = d3.select(this)
|
247
|
+
.append("rect")
|
248
|
+
.attr("x",function(d) { return xScale(d.x); })
|
249
|
+
.attr("y",function(d) { return yScale(maxY - d.y);})
|
250
|
+
.attr("width", function(d){return xScale(d.width);})
|
251
|
+
.attr("height", yScale(1))
|
252
|
+
.attr("fill", function(d){
|
253
|
+
var i = info[d.frame_id];
|
254
|
+
if(!i) {
|
255
|
+
info[d.frame_id] = i = {nodes: [], samples: [], color: color()};
|
256
|
+
}
|
257
|
+
i.nodes.push(this);
|
258
|
+
if (!mini)
|
259
|
+
for(var j=0; j < d.width; j++){
|
260
|
+
i.samples.push(d.x + j);
|
261
|
+
}
|
262
|
+
return i.color;
|
263
|
+
})
|
264
|
+
|
265
|
+
if (!mini)
|
266
|
+
r
|
267
|
+
.on("mouseover", mouseover)
|
268
|
+
.on("mouseout", mouseout);
|
269
|
+
|
270
|
+
if (!mini)
|
271
|
+
d3.select(this)
|
272
|
+
.append('foreignObject')
|
273
|
+
.classed('label-body', true)
|
274
|
+
.attr("x",function(d) { return xScale(d.x); })
|
275
|
+
.attr("y",function(d) { return yScale(maxY - d.y);})
|
276
|
+
.attr("width", function(d){return xScale(d.width);})
|
277
|
+
.attr("height", yScale(1))
|
278
|
+
.attr("line-height", yScale(1))
|
279
|
+
.attr("font-size", yScale(0.42) + 'px')
|
280
|
+
.attr('pointer-events', 'none')
|
281
|
+
.append('xhtml:span')
|
282
|
+
.style("height", yScale(1))
|
283
|
+
.classed('label', true)
|
284
|
+
.text(function(d){ return d.frame })
|
285
|
+
});
|
286
|
+
}
|
287
|
+
|
288
|
+
drawData(svg, data, xScale, yScale, 0)
|
289
|
+
drawData(svg2, data, xScale2, yScale2, 1)
|
290
|
+
|
291
|
+
var brushed = function(){
|
292
|
+
if (brush.empty()) {
|
293
|
+
svg.attr('transform', '')
|
294
|
+
zoomXRatio = 1
|
295
|
+
zoom.scale(1).translate([0,0])
|
296
|
+
svg.selectAll('.label-body')
|
297
|
+
.attr('transform', 'scale(1,1)')
|
298
|
+
.attr("x",function(d) { return xScale(d.x)*zoomXRatio; })
|
299
|
+
.attr("width", function(d){return xScale(d.width)*zoomXRatio;})
|
300
|
+
} else {
|
301
|
+
var e = brush.extent()
|
302
|
+
var x = [e[0][0],e[1][0]], y = [e[0][1],e[1][1]]
|
303
|
+
|
304
|
+
xScale.domain([0, maxX])
|
305
|
+
yScale.domain([0, maxY])
|
306
|
+
|
307
|
+
var w = width, h = height2
|
308
|
+
var dx = xScale2(1.0*x[1]-x[0]), dy = yScale2(1.0*y[1]-y[0])
|
309
|
+
var sx = w/dx, sy = h/dy
|
310
|
+
var trlx = -xScale(x[0])*sx, trly = -yScale(y[0])*sy
|
311
|
+
var transform = "translate(" + trlx + ',' + trly + ")" + " scale(" + sx + ',' + sy + ")"
|
312
|
+
|
313
|
+
zoomXRatio = sx/sy
|
314
|
+
|
315
|
+
svg.selectAll('.label-body')
|
316
|
+
.attr("x",function(d) { return xScale(d.x)*zoomXRatio; })
|
317
|
+
.attr("width", function(d){return xScale(d.width)*zoomXRatio;})
|
318
|
+
.attr('transform', function(d){
|
319
|
+
var x = xScale(d.x)
|
320
|
+
return "scale("+(1.0/zoomXRatio)+",1)"
|
321
|
+
})
|
322
|
+
|
323
|
+
svg.attr("transform", transform)
|
324
|
+
zoom.translate([trlx, trly]).scale(sy)
|
325
|
+
}
|
326
|
+
}
|
327
|
+
|
328
|
+
var brush = d3.svg.brush()
|
329
|
+
.x(xScale2)
|
330
|
+
.y(yScale2)
|
331
|
+
.on("brush", brushed);
|
332
|
+
|
333
|
+
svg2.append("g")
|
334
|
+
.attr("class", "brush")
|
335
|
+
.call(brush)
|
336
|
+
|
337
|
+
// Samples may overlap on the same line
|
338
|
+
for (var r in info) {
|
339
|
+
if (info[r].samples) {
|
340
|
+
info[r].samples = getUnique(info[r].samples);
|
341
|
+
}
|
342
|
+
};
|
343
|
+
|
344
|
+
// render the legend
|
345
|
+
$.each(gemsSorted, function(k,gem){
|
346
|
+
var data = samplePercentRaw(gem.samples.length)
|
347
|
+
var node = $("<div class='"+gem.name+"'></div>")
|
348
|
+
.css("background-color", gem.color)
|
349
|
+
.html("<span style='float: right'>" + data[0] + 'x<br>' + data[1] + '%' + '</span>' + '<div class="name">'+gem.name+'<br> </div>');
|
350
|
+
|
351
|
+
node.on('mouseenter mouseleave', function(e){
|
352
|
+
d3.selectAll(gemStats[gem.name].nodes).classed('highlighted', e.type == 'mouseenter')
|
353
|
+
})
|
354
|
+
|
355
|
+
$('.legend').append(node);
|
356
|
+
});
|
357
|
+
}
|
@@ -0,0 +1,85 @@
|
|
1
|
+
<html>
|
2
|
+
<head>
|
3
|
+
<title>flamegraph</title>
|
4
|
+
<style>
|
5
|
+
body {
|
6
|
+
margin: 0;
|
7
|
+
padding: 0;
|
8
|
+
font-family: Monaco, "Liberation Mono", Courier, monospace;
|
9
|
+
font-size: 10pt;
|
10
|
+
}
|
11
|
+
.info {
|
12
|
+
display: block;
|
13
|
+
height: 40px;
|
14
|
+
margin: 3px 6px;
|
15
|
+
margin-right: 206px;
|
16
|
+
padding: 3px 6px;
|
17
|
+
line-height: 18px;
|
18
|
+
}
|
19
|
+
.legend {
|
20
|
+
display: block;
|
21
|
+
float: right;
|
22
|
+
width: 195px;
|
23
|
+
max-height: 100%;
|
24
|
+
overflow-y: scroll;
|
25
|
+
}
|
26
|
+
.legend > div {
|
27
|
+
padding: 6px;
|
28
|
+
clear: right;
|
29
|
+
}
|
30
|
+
.legend > div span {
|
31
|
+
opacity: 0.75;
|
32
|
+
display: block;
|
33
|
+
text-align: right;
|
34
|
+
}
|
35
|
+
.legend > div .name {
|
36
|
+
max-width: 70%;
|
37
|
+
word-wrap: break-word;
|
38
|
+
}
|
39
|
+
.legend:hover + .flamegraph .flames:not(.highlighted) {
|
40
|
+
opacity: 0.25;
|
41
|
+
}
|
42
|
+
.legend:hover ~ .zoom .flames:not(.highlighted) {
|
43
|
+
opacity: 0.25;
|
44
|
+
}
|
45
|
+
.brush .extent {
|
46
|
+
stroke: #999;
|
47
|
+
fill-opacity: .125;
|
48
|
+
shape-rendering: crispEdges;
|
49
|
+
}
|
50
|
+
.label {
|
51
|
+
white-space: nowrap;
|
52
|
+
display: inline-flex;
|
53
|
+
align-items: center;
|
54
|
+
vertical-align: middle;
|
55
|
+
padding-left: 1px;
|
56
|
+
}
|
57
|
+
</style>
|
58
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
|
59
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.0.8/d3.min.js"></script>
|
60
|
+
<script src="flamegraph.js"></script>
|
61
|
+
</head>
|
62
|
+
<body>
|
63
|
+
<div class="legend"></div>
|
64
|
+
<div class="flamegraph"></div>
|
65
|
+
<div class="info">
|
66
|
+
<div style="float: right; text-align: right">
|
67
|
+
<div class="samples"></div>
|
68
|
+
<div class="exclusive"></div>
|
69
|
+
</div>
|
70
|
+
<div class="frame"></div>
|
71
|
+
<div class="file"></div>
|
72
|
+
</div>
|
73
|
+
<div class="zoom"></div>
|
74
|
+
<script type="text/javascript">
|
75
|
+
var queryDict = {}
|
76
|
+
location.search.substr(1).split("&").forEach(function(item) {queryDict[item.split("=")[0]] = decodeURIComponent(item.split("=")[1])})
|
77
|
+
|
78
|
+
if (queryDict.data) {
|
79
|
+
s = document.createElement('script')
|
80
|
+
s.setAttribute('src', queryDict.data)
|
81
|
+
document.body.appendChild(s)
|
82
|
+
}
|
83
|
+
</script>
|
84
|
+
</body>
|
85
|
+
</html>
|
data/lib/stackprof/report.rb
CHANGED
@@ -83,12 +83,14 @@ module StackProf
|
|
83
83
|
raise "profile does not include raw samples" unless raw = data[:raw]
|
84
84
|
|
85
85
|
stacks = []
|
86
|
+
max_x = 0
|
86
87
|
max_y = 0
|
87
88
|
while len = raw.shift
|
88
89
|
max_y = len if len > max_y
|
89
|
-
|
90
|
+
stack = raw.slice!(0, len+1)
|
91
|
+
stacks << stack
|
92
|
+
max_x += stack.last
|
90
93
|
end
|
91
|
-
max_x = stacks.inject(0){ |sum, (*stack, weight)| sum + weight }
|
92
94
|
|
93
95
|
f.puts 'flamegraph(['
|
94
96
|
max_y.times do |y|
|
@@ -96,8 +98,9 @@ module StackProf
|
|
96
98
|
row_width = 0
|
97
99
|
x = 0
|
98
100
|
|
99
|
-
stacks.each do
|
100
|
-
|
101
|
+
stacks.each do |stack|
|
102
|
+
weight = stack.last
|
103
|
+
cell = stack[y] unless y == stack.length-1
|
101
104
|
|
102
105
|
if cell.nil?
|
103
106
|
if row_prev
|
@@ -219,7 +222,7 @@ module StackProf
|
|
219
222
|
f.puts "#{line} #{weight.is_a?(Array) ? weight[1] : weight}"
|
220
223
|
end if frame[:lines]
|
221
224
|
frame[:edges].each do |edge, weight|
|
222
|
-
oframe = list[edge
|
225
|
+
oframe = list[edge]
|
223
226
|
f.puts "cfl=#{oframe[:file]}" unless oframe[:file] == frame[:file]
|
224
227
|
f.puts "cfn=#{oframe[:name]}"
|
225
228
|
f.puts "calls=#{weight} #{frame[:line] || 0}\n#{oframe[:line] || 0} #{weight}"
|
data/stackprof.gemspec
CHANGED
data/test/test_stackprof.rb
CHANGED
@@ -61,6 +61,16 @@ class StackProfTest < Test::Unit::TestCase
|
|
61
61
|
assert_equal "block in StackProfTest#math", frame[:name]
|
62
62
|
end
|
63
63
|
|
64
|
+
def test_cputime_bmethod
|
65
|
+
profile = StackProf.run(mode: :cpu, interval: 500) do
|
66
|
+
bmath
|
67
|
+
end
|
68
|
+
|
69
|
+
assert_operator profile[:samples], :>, 1
|
70
|
+
frame = profile[:frames].values.first
|
71
|
+
assert_equal "block in StackProfTest#math", frame[:name]
|
72
|
+
end
|
73
|
+
|
64
74
|
def test_walltime
|
65
75
|
profile = StackProf.run(mode: :wall) do
|
66
76
|
idle
|
@@ -148,4 +158,10 @@ class StackProfTest < Test::Unit::TestCase
|
|
148
158
|
r.close
|
149
159
|
w.close
|
150
160
|
end
|
161
|
+
|
162
|
+
define_method(:bmath) do
|
163
|
+
250_000.times do
|
164
|
+
2 ** 10
|
165
|
+
end
|
166
|
+
end
|
151
167
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: stackprof
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aman Gupta
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-02-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake-compiler
|
@@ -59,6 +59,8 @@ files:
|
|
59
59
|
- bin/stackprof-gprof2dot.py
|
60
60
|
- ext/extconf.rb
|
61
61
|
- ext/stackprof.c
|
62
|
+
- lib/stackprof/flamegraph/flamegraph.js
|
63
|
+
- lib/stackprof/flamegraph/viewer.html
|
62
64
|
- lib/stackprof/middleware.rb
|
63
65
|
- lib/stackprof/report.rb
|
64
66
|
- sample.rb
|