flamegraph 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +6 -0
- data/Gemfile +4 -0
- data/Guardfile +1 -1
- data/lib/flamegraph.rb +27 -4
- data/lib/flamegraph/flamegraph.html +146 -7
- data/lib/flamegraph/renderer.rb +4 -3
- data/lib/flamegraph/stackprof_sampler.rb +42 -0
- data/lib/flamegraph/version.rb +1 -1
- data/test/test_stackprof_sampler.rb +36 -0
- metadata +21 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f613760265ad137dae994ad575e5d90397506e1f
|
4
|
+
data.tar.gz: b0b4968018697e4308746c72a7f0b9ac88fce97a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 046ab66a7a510a2f9bd69da94e0b6c610d00e53a73ee3d98e38298075507fc73511fe92d4736c50253e957c8e453da5f9ea174d2577a21bb324a2466db030d03
|
7
|
+
data.tar.gz: b8346b54a13bf842a3a584a1f6149a0467b904b1a53d2c72720031c63c42b40268d1b38c4e2c9746c47276fcd5045c98e013c93eae3a27d977f60a7a19155bc4
|
data/CHANGELOG
CHANGED
data/Gemfile
CHANGED
data/Guardfile
CHANGED
data/lib/flamegraph.rb
CHANGED
@@ -1,5 +1,21 @@
|
|
1
1
|
require "json"
|
2
|
-
|
2
|
+
|
3
|
+
if RUBY_VERSION >= "2.1.0".freeze
|
4
|
+
begin
|
5
|
+
require "stackprof"
|
6
|
+
require "flamegraph/stackprof_sampler"
|
7
|
+
rescue
|
8
|
+
STDERR.puts "Please require the stackprof gem falling back to fast_stack"
|
9
|
+
require "fast_stack"
|
10
|
+
end
|
11
|
+
else
|
12
|
+
begin
|
13
|
+
require "fast_stack"
|
14
|
+
rescue
|
15
|
+
STDERR.puts "Please require the fast_stack gem, note flamegraph is only supported on Ruby 2.0 and above"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
3
19
|
require "flamegraph/version"
|
4
20
|
require "flamegraph/renderer"
|
5
21
|
|
@@ -7,9 +23,16 @@ module Flamegraph
|
|
7
23
|
def self.generate(filename=nil, opts = {})
|
8
24
|
fidelity = opts[:fidelity] || 0.5
|
9
25
|
|
10
|
-
backtraces =
|
11
|
-
|
12
|
-
|
26
|
+
backtraces =
|
27
|
+
if defined? StackProf
|
28
|
+
StackProfSampler.collect(fidelity) do
|
29
|
+
yield
|
30
|
+
end
|
31
|
+
else
|
32
|
+
FastStack.profile(fidelity) do # , opts[:mode] || :ruby) do
|
33
|
+
yield
|
34
|
+
end
|
35
|
+
end
|
13
36
|
|
14
37
|
embed_resources = (filename && !opts.key?(:embed_resources)) || opts[:embed_resources]
|
15
38
|
|
@@ -7,7 +7,7 @@
|
|
7
7
|
|
8
8
|
<title>Flame Graph of Page</title>
|
9
9
|
<style>
|
10
|
-
.info {height:
|
10
|
+
.info {min-height: 50px; margin: 10px; }
|
11
11
|
.legend div {
|
12
12
|
display: block;
|
13
13
|
float: left;
|
@@ -16,13 +16,62 @@
|
|
16
16
|
padding: 4px;
|
17
17
|
height: 50px;
|
18
18
|
}
|
19
|
+
.code {
|
20
|
+
font-family: Consolas, Menlo, Monaco, "Lucida Console", "Liberation Mono", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Courier New", monospace;
|
21
|
+
}
|
22
|
+
#frameinfo table .sample-info {
|
23
|
+
width: 200px;
|
24
|
+
color: #333;
|
25
|
+
font-size: 12px;
|
26
|
+
}
|
27
|
+
#frameinfo table {
|
28
|
+
border-collapse: collapse;
|
29
|
+
}
|
30
|
+
#frameinfo table tr {
|
31
|
+
border-bottom: 1px solid #eee;
|
32
|
+
margin-top: 5px;
|
33
|
+
}
|
34
|
+
#frameinfo td {
|
35
|
+
padding: 5px;
|
36
|
+
}
|
37
|
+
#frameinfo-wrapper {
|
38
|
+
position: fixed;
|
39
|
+
z-index: 1;
|
40
|
+
opacity: 0.7;
|
41
|
+
background-color: black;
|
42
|
+
width: 100%;
|
43
|
+
height: 100%;
|
44
|
+
top: 0;
|
45
|
+
left: 0;
|
46
|
+
display: none;
|
47
|
+
}
|
48
|
+
#frameinfo {
|
49
|
+
position: fixed;
|
50
|
+
padding: 10px;
|
51
|
+
z-index: 2;
|
52
|
+
opacity: 1.0;
|
53
|
+
top: 0;
|
54
|
+
margin-top: 50px;
|
55
|
+
margin-left: 40px;
|
56
|
+
width: 80%;
|
57
|
+
height: 80%;
|
58
|
+
background-color: white;
|
59
|
+
border: 2px solid #666;
|
60
|
+
display: none;
|
61
|
+
overflow: auto;
|
62
|
+
}
|
19
63
|
</style>
|
20
64
|
</head>
|
21
65
|
<body>
|
22
66
|
<div class="graph"></div>
|
23
|
-
<div class="info"></div>
|
67
|
+
<div class="info code"></div>
|
24
68
|
<div class="legend"></div>
|
25
|
-
|
69
|
+
<div id="frameinfo-wrapper"></div>
|
70
|
+
<div id="frameinfo">
|
71
|
+
<h3>Frame Info</h3>
|
72
|
+
<table>
|
73
|
+
</table>
|
74
|
+
</div>
|
26
75
|
<script>
|
27
76
|
|
28
77
|
var data = /**DATA**/;
|
@@ -84,7 +133,12 @@ var guessGem = function(frame)
|
|
84
133
|
var guessMethod = function(frame) {
|
85
134
|
var split = frame.split('`');
|
86
135
|
if(split.length == 2) {
|
87
|
-
|
136
|
+
var fullMethod = split[1].split("'")[0];
|
137
|
+
split = fullMethod.split("#");
|
138
|
+
if(split.length == 2) {
|
139
|
+
return split[1];
|
140
|
+
}
|
141
|
+
return split[0];
|
88
142
|
}
|
89
143
|
return '?';
|
90
144
|
}
|
@@ -101,7 +155,7 @@ var guessFile = function(frame) {
|
|
101
155
|
$.each(data, function(){
|
102
156
|
maxX = Math.max(maxX, this.x + this.width);
|
103
157
|
maxY = Math.max(maxY, this.y);
|
104
|
-
this.shortName =
|
158
|
+
this.shortName = guessMethod(this.frame);
|
105
159
|
});
|
106
160
|
|
107
161
|
var width = $(window).width();
|
@@ -194,6 +248,87 @@ var mouseout = function(d) {
|
|
194
248
|
.attr('opacity',1);
|
195
249
|
};
|
196
250
|
|
251
|
+
var backtrace = function(frame){
|
252
|
+
for(var i=0; i<data.length; i++){
|
253
|
+
if(frame === data[i]){ break; }
|
254
|
+
}
|
255
|
+
|
256
|
+
frames = [frame];
|
257
|
+
var depth = frame.y;
|
258
|
+
|
259
|
+
while(i > 0){
|
260
|
+
if(depth == -1) break;
|
261
|
+
|
262
|
+
if(data[i].y === depth-1) {
|
263
|
+
frames.push(data[i]);
|
264
|
+
depth--;
|
265
|
+
}
|
266
|
+
|
267
|
+
i--;
|
268
|
+
}
|
269
|
+
|
270
|
+
return frames;
|
271
|
+
}
|
272
|
+
|
273
|
+
$('#frameinfo-wrapper').click(function(d){
|
274
|
+
$(this).hide();
|
275
|
+
$('#frameinfo').hide();
|
276
|
+
});
|
277
|
+
|
278
|
+
var click = function(d){
|
279
|
+
var trace = backtrace(d);
|
280
|
+
|
281
|
+
var link = function(path, dest){
|
282
|
+
return path.replace(/[^\/]+:\d+/, function(x){ return "<a target='_blank' href='"+ dest +"'>" + x + "</a>"})
|
283
|
+
};
|
284
|
+
|
285
|
+
var linkify = function(path){
|
286
|
+
var split = path.split("/")[0].split("-");
|
287
|
+
if(["activerecord","actionpack","railties","activesupport", "rails"].indexOf(split[0]) > -1) {
|
288
|
+
var github = "https://github.com/rails/rails/blob/";
|
289
|
+
var file = path.split(":")[0].split("/");
|
290
|
+
if(split[0] === "rails") {
|
291
|
+
file.shift();
|
292
|
+
} else {
|
293
|
+
file[0] = split[0];
|
294
|
+
}
|
295
|
+
|
296
|
+
github += (split[1].length < 6 ? "v" : "") + split[1] + "/";
|
297
|
+
github += file.join("/");
|
298
|
+
github += "#L" + parseInt(path.split(":")[1]);
|
299
|
+
|
300
|
+
return link(path, github);
|
301
|
+
}
|
302
|
+
return path;
|
303
|
+
}
|
304
|
+
|
305
|
+
var simplify = function(frame){
|
306
|
+
var split = frame.split('/gems/');
|
307
|
+
if(split.length > 1){
|
308
|
+
var path = linkify(split.pop());
|
309
|
+
return "<span class='full-location'>" + split.join('/gems/') + "/</span>" + path;
|
310
|
+
} else {
|
311
|
+
return frame;
|
312
|
+
}
|
313
|
+
}
|
314
|
+
|
315
|
+
var table = trace.map(function(f){
|
316
|
+
var i = info[f.frame];
|
317
|
+
return "<tr><td class='code'>" + simplify(f.frame) + "</td><td class='sample-info'>" +
|
318
|
+
samplePercent(i.samples.length, f.topFrame ? f.topFrame.exclusiveCount : 0) +
|
319
|
+
"</td></tr>";
|
320
|
+
}).join("\n");
|
321
|
+
|
322
|
+
var table = $('#frameinfo table').html(table);
|
323
|
+
table.find(".full-location").hide().after("<span class='expand'>… </span>");
|
324
|
+
table.find(".expand").css({cursor: "pointer"}).click(function(){
|
325
|
+
$(this).hide().parent().find(".full-location").show();
|
326
|
+
});
|
327
|
+
|
328
|
+
$('#frameinfo-wrapper').show();
|
329
|
+
$('#frameinfo').show();
|
330
|
+
};
|
331
|
+
|
197
332
|
// http://stackoverflow.com/a/7419630
|
198
333
|
var rainbow = function(numOfSteps, step) {
|
199
334
|
// 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.
|
@@ -291,7 +426,7 @@ function fontSize(d,i) {
|
|
291
426
|
var height = yScale(1);
|
292
427
|
var length = 0;
|
293
428
|
d3.select(this).style("font-size", size + "px").text(word);
|
294
|
-
while(((this.getBBox().width >= width) || (this.getBBox().height >= height))
|
429
|
+
while((size > 12.1) && ((this.getBBox().width >= width) || (this.getBBox().height >= height)))
|
295
430
|
{
|
296
431
|
size -= 0.1;
|
297
432
|
d3.select(this).style("font-size", size + "px");
|
@@ -323,7 +458,9 @@ svg.selectAll("g")
|
|
323
458
|
return i.color;
|
324
459
|
})
|
325
460
|
.on("mouseover", mouseover)
|
326
|
-
.on("mouseout", mouseout)
|
461
|
+
.on("mouseout", mouseout)
|
462
|
+
.on("click", click)
|
463
|
+
.attr("cursor", "pointer");
|
327
464
|
|
328
465
|
d3.select(this)
|
329
466
|
.append("text")
|
@@ -331,7 +468,9 @@ svg.selectAll("g")
|
|
331
468
|
.attr("y",function(d) { return yScale(maxY - d.y);})
|
332
469
|
.on("mouseover", mouseover)
|
333
470
|
.on("mouseout", mouseout)
|
471
|
+
.on("click", click)
|
334
472
|
.each(fontSize)
|
473
|
+
.attr("cursor", "pointer")
|
335
474
|
.attr("display", "none");
|
336
475
|
|
337
476
|
});
|
data/lib/flamegraph/renderer.rb
CHANGED
@@ -22,13 +22,14 @@ class Flamegraph::Renderer
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def graph_data
|
25
|
-
height = 0
|
26
|
-
|
27
25
|
table = []
|
28
26
|
prev = []
|
29
27
|
|
30
28
|
# a 2d array makes collapsing easy
|
31
29
|
@stacks.each_with_index do |stack, pos|
|
30
|
+
|
31
|
+
next unless stack
|
32
|
+
|
32
33
|
col = []
|
33
34
|
|
34
35
|
stack.reverse.map{|r| r.to_s}.each_with_index do |frame, i|
|
@@ -79,7 +80,7 @@ class Flamegraph::Renderer
|
|
79
80
|
end
|
80
81
|
|
81
82
|
def read(file)
|
82
|
-
|
83
|
+
IO.read(::File.expand_path(file, ::File.dirname(__FILE__)))
|
83
84
|
end
|
84
85
|
|
85
86
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class Flamegraph::StackProfSampler
|
2
|
+
def self.collect(fidelity=0.5)
|
3
|
+
|
4
|
+
result = StackProf.run(mode: :wall,
|
5
|
+
raw: true,
|
6
|
+
aggregate: false,
|
7
|
+
interval: (fidelity * 1000).to_i) do
|
8
|
+
yield
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
stacks = []
|
13
|
+
stack = []
|
14
|
+
|
15
|
+
return [] unless result[:raw]
|
16
|
+
|
17
|
+
length = nil
|
18
|
+
result[:raw].each do |i|
|
19
|
+
if length.nil?
|
20
|
+
length = i
|
21
|
+
next
|
22
|
+
end
|
23
|
+
|
24
|
+
if length > 0
|
25
|
+
frame = result[:frames][i]
|
26
|
+
frame = "#{frame[:file]}:#{frame[:line]}:in `#{frame[:name]}'"
|
27
|
+
stack << frame.to_s
|
28
|
+
length -= 1
|
29
|
+
next
|
30
|
+
end
|
31
|
+
|
32
|
+
i.times do
|
33
|
+
stacks << stack.reverse
|
34
|
+
end
|
35
|
+
|
36
|
+
stack = []
|
37
|
+
length = nil
|
38
|
+
end
|
39
|
+
|
40
|
+
stacks
|
41
|
+
end
|
42
|
+
end
|
data/lib/flamegraph/version.rb
CHANGED
@@ -0,0 +1,36 @@
|
|
1
|
+
if RUBY_VERSION >= "2.1.0"
|
2
|
+
|
3
|
+
require 'stackprof'
|
4
|
+
require 'test_helper'
|
5
|
+
require 'flamegraph/stackprof_sampler'
|
6
|
+
|
7
|
+
class TestStackprofSampler < Minitest::Test
|
8
|
+
|
9
|
+
def idle(duration)
|
10
|
+
r, w = IO.pipe
|
11
|
+
IO.select([r], nil, nil, duration)
|
12
|
+
ensure
|
13
|
+
r.close
|
14
|
+
w.close
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_sample_collection
|
18
|
+
|
19
|
+
samples = Flamegraph::StackProfSampler.collect do
|
20
|
+
idle 0.005
|
21
|
+
end
|
22
|
+
|
23
|
+
assert(samples.count > 3, "Should get more than 3 samples in 5 millisecs")
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_fidelity
|
27
|
+
samples = Flamegraph::StackProfSampler.collect(10) do
|
28
|
+
idle 0.005
|
29
|
+
end
|
30
|
+
|
31
|
+
assert(samples.count <= 1, "Should get a max of 1 sample got #{samples.count}")
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
metadata
CHANGED
@@ -1,97 +1,97 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: flamegraph
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sam Saffron
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2014-11-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: fast_stack
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- -
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: bundler
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - ~>
|
31
|
+
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '1.3'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - ~>
|
38
|
+
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '1.3'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rake
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- -
|
45
|
+
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: '0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- -
|
52
|
+
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: minitest
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- -
|
59
|
+
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '0'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- -
|
66
|
+
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: guard
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- -
|
73
|
+
- - ">="
|
74
74
|
- !ruby/object:Gem::Version
|
75
75
|
version: '0'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
|
-
- -
|
80
|
+
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: guard-minitest
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
|
-
- -
|
87
|
+
- - ">="
|
88
88
|
- !ruby/object:Gem::Version
|
89
89
|
version: '0'
|
90
90
|
type: :development
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
|
-
- -
|
94
|
+
- - ">="
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '0'
|
97
97
|
description: Flamegraph support for arbitrary ruby apps
|
@@ -101,7 +101,7 @@ executables: []
|
|
101
101
|
extensions: []
|
102
102
|
extra_rdoc_files: []
|
103
103
|
files:
|
104
|
-
- .gitignore
|
104
|
+
- ".gitignore"
|
105
105
|
- CHANGELOG
|
106
106
|
- Gemfile
|
107
107
|
- Guardfile
|
@@ -119,11 +119,13 @@ files:
|
|
119
119
|
- lib/flamegraph/lodash.min.js
|
120
120
|
- lib/flamegraph/renderer.rb
|
121
121
|
- lib/flamegraph/sampler.rb
|
122
|
+
- lib/flamegraph/stackprof_sampler.rb
|
122
123
|
- lib/flamegraph/version.rb
|
123
124
|
- test/test_fast_stack_sampler.rb
|
124
125
|
- test/test_helper.rb
|
125
126
|
- test/test_renderer.rb
|
126
127
|
- test/test_sampler.rb
|
128
|
+
- test/test_stackprof_sampler.rb
|
127
129
|
homepage: ''
|
128
130
|
licenses:
|
129
131
|
- MIT
|
@@ -134,17 +136,17 @@ require_paths:
|
|
134
136
|
- lib
|
135
137
|
required_ruby_version: !ruby/object:Gem::Requirement
|
136
138
|
requirements:
|
137
|
-
- -
|
139
|
+
- - ">="
|
138
140
|
- !ruby/object:Gem::Version
|
139
141
|
version: '0'
|
140
142
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
141
143
|
requirements:
|
142
|
-
- -
|
144
|
+
- - ">="
|
143
145
|
- !ruby/object:Gem::Version
|
144
146
|
version: '0'
|
145
147
|
requirements: []
|
146
148
|
rubyforge_project:
|
147
|
-
rubygems_version: 2.
|
149
|
+
rubygems_version: 2.2.2
|
148
150
|
signing_key:
|
149
151
|
specification_version: 4
|
150
152
|
summary: Flamegraph support for arbitrary ruby apps
|
@@ -153,3 +155,4 @@ test_files:
|
|
153
155
|
- test/test_helper.rb
|
154
156
|
- test/test_renderer.rb
|
155
157
|
- test/test_sampler.rb
|
158
|
+
- test/test_stackprof_sampler.rb
|