flamegraph 0.0.5 → 0.0.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/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
|