ruby-prof-flamegraph 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +62 -0
- data/Rakefile +2 -0
- data/example.rb +14 -0
- data/example.svg +249 -0
- data/lib/ruby-prof-flamegraph.rb +2 -0
- data/lib/ruby-prof/printers/flame_graph_printer.rb +67 -0
- data/ruby-prof-flamegraph.gemspec +22 -0
- metadata +96 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 8bbc733bda4ede9cd81cec28badf3403953e8eab
|
4
|
+
data.tar.gz: 54665c4c1659e1bb9f0ee39dea833c059d8eeb02
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 29a651045811ed6ed69c2985843945f53fddaca69a008c1c3a0194e26714380e0079426c10a78b313a1ad5d601d179a388c57a3e5fefe4060e393f1a738a241a
|
7
|
+
data.tar.gz: ee3b2bf7cdbaabaa0ed7b526895f74792b1e5c7eca131e23968daf6b1876c307ac787f5b7d42d62c35d5d57a60adb4433c90bf6d87c1788031547834cee5a909
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Thai Pangsakulyanont
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
ruby-prof-flamegraph
|
2
|
+
====================
|
3
|
+
|
4
|
+
A [ruby-prof][] printer that outputs a fold stack file that's compatible with [FlameGraph][].
|
5
|
+
It is created based on `RubyProf::CallStackPrinter`.
|
6
|
+
|
7
|
+
|
8
|
+
Awesomeness
|
9
|
+
-----------
|
10
|
+
|
11
|
+
[FlameGraph][] is a way to visualize stack trace,
|
12
|
+
making it very obvious where in the program takes the longest time.
|
13
|
+
It is a Perl script takes a "fold stack" file and generates a nice, interactive SVG.
|
14
|
+
The fold stack is usually generated from DTrace or Prof data using [stackcollapse.pl][FlameGraph], which is included with FlameGraph.
|
15
|
+
|
16
|
+
I created this gem because I want to find out where the bottleneck is in [SlimWiki][]'s specs,
|
17
|
+
but I don't know DTrace and just want the result quick.
|
18
|
+
|
19
|
+
I did not expect this,
|
20
|
+
but generating a company name from Faker causes 44 YAML files to be parsed,
|
21
|
+
taking 28 seconds.
|
22
|
+
|
23
|
+
(TODO include image)
|
24
|
+
|
25
|
+
|
26
|
+
To learn more about Flame Graphs, check these out:
|
27
|
+
|
28
|
+
- [Official Flame Graphs Website](http://www.brendangregg.com/flamegraphs.html) by [Brendan Gregg](http://www.brendangregg.com/)
|
29
|
+
- [Node.js in Flames](http://techblog.netflix.com/2014/11/nodejs-in-flames.html), which is the article that introduced me to flame graph (via [Node Weekly Issue #62](http://nodeweekly.com/issues/62))
|
30
|
+
- [Blazing Performance with Flame Graphs](https://www.usenix.org/conference/lisa13/technical-sessions/plenary/gregg) talk at USENIX/LISA13 ([slideshare](http://www.slideshare.net/brendangregg/blazing-performance-with-flame-graphs?ref=http://www.brendangregg.com/flamegraphs.html)) ([video](http://www.youtube.com/watch?v=nZfNehCzGdw))
|
31
|
+
|
32
|
+
|
33
|
+
|
34
|
+
[ruby-prof]: https://github.com/ruby-prof/ruby-prof
|
35
|
+
[FlameGraph]: https://github.com/brendangregg/FlameGraph
|
36
|
+
[SlimWiki]: https://slimwiki.com/
|
37
|
+
|
38
|
+
|
39
|
+
## Installation
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
gem 'ruby-prof-flamegraph'
|
43
|
+
```
|
44
|
+
|
45
|
+
|
46
|
+
## Usage
|
47
|
+
|
48
|
+
Just `require 'ruby-prof-flamegraph` and use `RubyProf::FlameGraphPrinter` as your printer for ruby-prof.
|
49
|
+
For vanilla ruby-prof, see [example.rb](example.rb).
|
50
|
+
|
51
|
+
For rspec-prof, `RSpecProf.printer_class = RubyProf::FlameGraphPrinter`
|
52
|
+
|
53
|
+
[rspec-prof]: https://github.com/sinisterchipmunk/rspec-prof
|
54
|
+
|
55
|
+
|
56
|
+
|
57
|
+
## Example
|
58
|
+
|
59
|
+
See the result in [example.svg][]
|
60
|
+
|
61
|
+
[example.svg]: example.svg
|
62
|
+
|
data/Rakefile
ADDED
data/example.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'ruby-prof'
|
2
|
+
require 'ruby-prof-flamegraph'
|
3
|
+
|
4
|
+
rubyprof_dir = Gem::Specification.find_by_name('ruby-prof').gem_dir
|
5
|
+
require "#{rubyprof_dir}/test/prime"
|
6
|
+
|
7
|
+
# Profile the code
|
8
|
+
result = RubyProf.profile do
|
9
|
+
run_primes(200)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Print a graph profile to text
|
13
|
+
printer = RubyProf::FlameGraphPrinter.new(result)
|
14
|
+
printer.print(STDOUT, {})
|
data/example.svg
ADDED
@@ -0,0 +1,249 @@
|
|
1
|
+
<?xml version="1.0" standalone="no"?>
|
2
|
+
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
3
|
+
<svg version="1.1" width="1200" height="210" onload="init(evt)" viewBox="0 0 1200 210" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
4
|
+
<defs >
|
5
|
+
<linearGradient id="background" y1="0" y2="1" x1="0" x2="0" >
|
6
|
+
<stop stop-color="#eeeeee" offset="5%" />
|
7
|
+
<stop stop-color="#eeeeb0" offset="95%" />
|
8
|
+
</linearGradient>
|
9
|
+
</defs>
|
10
|
+
<style type="text/css">
|
11
|
+
.func_g:hover { stroke:black; stroke-width:0.5; cursor:pointer; }
|
12
|
+
</style>
|
13
|
+
<script type="text/ecmascript">
|
14
|
+
<![CDATA[
|
15
|
+
var details, svg;
|
16
|
+
function init(evt) {
|
17
|
+
details = document.getElementById("details").firstChild;
|
18
|
+
svg = document.getElementsByTagName("svg")[0];
|
19
|
+
}
|
20
|
+
function s(info) { details.nodeValue = "Function: " + info; }
|
21
|
+
function c() { details.nodeValue = ' '; }
|
22
|
+
function find_child(parent, name, attr) {
|
23
|
+
var children = parent.childNodes;
|
24
|
+
for (var i=0; i<children.length;i++) {
|
25
|
+
if (children[i].tagName == name)
|
26
|
+
return (attr != undefined) ? children[i].attributes[attr].value : children[i];
|
27
|
+
}
|
28
|
+
return;
|
29
|
+
}
|
30
|
+
function orig_save(e, attr, val) {
|
31
|
+
if (e.attributes["_orig_"+attr] != undefined) return;
|
32
|
+
if (e.attributes[attr] == undefined) return;
|
33
|
+
if (val == undefined) val = e.attributes[attr].value;
|
34
|
+
e.setAttribute("_orig_"+attr, val);
|
35
|
+
}
|
36
|
+
function orig_load(e, attr) {
|
37
|
+
if (e.attributes["_orig_"+attr] == undefined) return;
|
38
|
+
e.attributes[attr].value = e.attributes["_orig_"+attr].value;
|
39
|
+
e.removeAttribute("_orig_"+attr);
|
40
|
+
}
|
41
|
+
function update_text(e) {
|
42
|
+
var r = find_child(e, "rect");
|
43
|
+
var t = find_child(e, "text");
|
44
|
+
var w = parseFloat(r.attributes["width"].value) -3;
|
45
|
+
var txt = find_child(e, "title").textContent.replace(/\([^(]*\)/,"");
|
46
|
+
t.attributes["x"].value = parseFloat(r.attributes["x"].value) +3;
|
47
|
+
|
48
|
+
// Smaller than this size won't fit anything
|
49
|
+
if (w < 2*12*0.59) {
|
50
|
+
t.textContent = "";
|
51
|
+
return;
|
52
|
+
}
|
53
|
+
|
54
|
+
t.textContent = txt;
|
55
|
+
// Fit in full text width
|
56
|
+
if (/^ *$/.test(txt) || t.getSubStringLength(0, txt.length) < w)
|
57
|
+
return;
|
58
|
+
|
59
|
+
for (var x=txt.length-2; x>0; x--) {
|
60
|
+
if (t.getSubStringLength(0, x+2) <= w) {
|
61
|
+
t.textContent = txt.substring(0,x) + "..";
|
62
|
+
return;
|
63
|
+
}
|
64
|
+
}
|
65
|
+
t.textContent = "";
|
66
|
+
}
|
67
|
+
function zoom_reset(e) {
|
68
|
+
if (e.attributes != undefined) {
|
69
|
+
orig_load(e, "x");
|
70
|
+
orig_load(e, "width");
|
71
|
+
}
|
72
|
+
if (e.childNodes == undefined) return;
|
73
|
+
for(var i=0, c=e.childNodes; i<c.length; i++) {
|
74
|
+
zoom_reset(c[i]);
|
75
|
+
}
|
76
|
+
}
|
77
|
+
function zoom_child(e, x, ratio) {
|
78
|
+
if (e.attributes != undefined) {
|
79
|
+
if (e.attributes["x"] != undefined) {
|
80
|
+
orig_save(e, "x");
|
81
|
+
e.attributes["x"].value = (parseFloat(e.attributes["x"].value) - x - 10) * ratio + 10;
|
82
|
+
if(e.tagName == "text") e.attributes["x"].value = find_child(e.parentNode, "rect", "x") + 3;
|
83
|
+
}
|
84
|
+
if (e.attributes["width"] != undefined) {
|
85
|
+
orig_save(e, "width");
|
86
|
+
e.attributes["width"].value = parseFloat(e.attributes["width"].value) * ratio;
|
87
|
+
}
|
88
|
+
}
|
89
|
+
|
90
|
+
if (e.childNodes == undefined) return;
|
91
|
+
for(var i=0, c=e.childNodes; i<c.length; i++) {
|
92
|
+
zoom_child(c[i], x-10, ratio);
|
93
|
+
}
|
94
|
+
}
|
95
|
+
function zoom_parent(e) {
|
96
|
+
if (e.attributes) {
|
97
|
+
if (e.attributes["x"] != undefined) {
|
98
|
+
orig_save(e, "x");
|
99
|
+
e.attributes["x"].value = 10;
|
100
|
+
}
|
101
|
+
if (e.attributes["width"] != undefined) {
|
102
|
+
orig_save(e, "width");
|
103
|
+
e.attributes["width"].value = parseInt(svg.width.baseVal.value) - (10*2);
|
104
|
+
}
|
105
|
+
}
|
106
|
+
if (e.childNodes == undefined) return;
|
107
|
+
for(var i=0, c=e.childNodes; i<c.length; i++) {
|
108
|
+
zoom_parent(c[i]);
|
109
|
+
}
|
110
|
+
}
|
111
|
+
function zoom(node) {
|
112
|
+
var attr = find_child(node, "rect").attributes;
|
113
|
+
var width = parseFloat(attr["width"].value);
|
114
|
+
var xmin = parseFloat(attr["x"].value);
|
115
|
+
var xmax = parseFloat(xmin + width);
|
116
|
+
var ymin = parseFloat(attr["y"].value);
|
117
|
+
var ratio = (svg.width.baseVal.value - 2*10) / width;
|
118
|
+
|
119
|
+
// XXX: Workaround for JavaScript float issues (fix me)
|
120
|
+
var fudge = 0.0001;
|
121
|
+
|
122
|
+
var unzoombtn = document.getElementById("unzoom");
|
123
|
+
unzoombtn.style["opacity"] = "1.0";
|
124
|
+
|
125
|
+
var el = document.getElementsByTagName("g");
|
126
|
+
for(var i=0;i<el.length;i++){
|
127
|
+
var e = el[i];
|
128
|
+
var a = find_child(e, "rect").attributes;
|
129
|
+
var ex = parseFloat(a["x"].value);
|
130
|
+
var ew = parseFloat(a["width"].value);
|
131
|
+
// Is it an ancestor
|
132
|
+
if (0 == 0) {
|
133
|
+
var upstack = parseFloat(a["y"].value) > ymin;
|
134
|
+
} else {
|
135
|
+
var upstack = parseFloat(a["y"].value) < ymin;
|
136
|
+
}
|
137
|
+
if (upstack) {
|
138
|
+
// Direct ancestor
|
139
|
+
if (ex <= xmin && (ex+ew+fudge) >= xmax) {
|
140
|
+
e.style["opacity"] = "0.5";
|
141
|
+
zoom_parent(e);
|
142
|
+
e.onclick = function(e){unzoom(); zoom(this);};
|
143
|
+
update_text(e);
|
144
|
+
}
|
145
|
+
// not in current path
|
146
|
+
else
|
147
|
+
e.style["display"] = "none";
|
148
|
+
}
|
149
|
+
// Children maybe
|
150
|
+
else {
|
151
|
+
// no common path
|
152
|
+
if (ex < xmin || ex + fudge >= xmax) {
|
153
|
+
e.style["display"] = "none";
|
154
|
+
}
|
155
|
+
else {
|
156
|
+
zoom_child(e, xmin, ratio);
|
157
|
+
e.onclick = function(e){zoom(this);};
|
158
|
+
update_text(e);
|
159
|
+
}
|
160
|
+
}
|
161
|
+
}
|
162
|
+
}
|
163
|
+
function unzoom() {
|
164
|
+
var unzoombtn = document.getElementById("unzoom");
|
165
|
+
unzoombtn.style["opacity"] = "0.0";
|
166
|
+
|
167
|
+
var el = document.getElementsByTagName("g");
|
168
|
+
for(i=0;i<el.length;i++) {
|
169
|
+
el[i].style["display"] = "block";
|
170
|
+
el[i].style["opacity"] = "1";
|
171
|
+
zoom_reset(el[i]);
|
172
|
+
update_text(el[i]);
|
173
|
+
}
|
174
|
+
}
|
175
|
+
]]>
|
176
|
+
</script>
|
177
|
+
<rect x="0.0" y="0" width="1200.0" height="210.0" fill="url(#background)" />
|
178
|
+
<text text-anchor="middle" x="600.00" y="24" font-size="17" font-family="Verdana" fill="rgb(0,0,0)" >Flame Graph</text>
|
179
|
+
<text text-anchor="" x="10.00" y="193" font-size="12" font-family="Verdana" fill="rgb(0,0,0)" id="details" > </text>
|
180
|
+
<text text-anchor="" x="10.00" y="24" font-size="12" font-family="Verdana" fill="rgb(0,0,0)" id="unzoom" onclick="unzoom()" style="opacity:0.0;cursor:pointer" >Reset Zoom</text>
|
181
|
+
<g class="func_g" onmouseover="s('Fiber:70303624221020 (27 ms, 100.13%)')" onmouseout="c()" onclick="zoom(this)">
|
182
|
+
<title>Fiber:70303624221020 (27 ms, 100.13%)</title><rect x="10.0" y="129" width="1180.0" height="15.0" fill="rgb(214,125,8)" rx="2" ry="2" />
|
183
|
+
<text text-anchor="" x="13.00" y="139.5" font-size="12" font-family="Verdana" fill="rgb(0,0,0)" >Fiber:70303624221020</text>
|
184
|
+
</g>
|
185
|
+
<g class="func_g" onmouseover="s('Object#run_primes (1) (27 ms, 100.13%)')" onmouseout="c()" onclick="zoom(this)">
|
186
|
+
<title>Object#run_primes (1) (27 ms, 100.13%)</title><rect x="11.4" y="97" width="1178.6" height="15.0" fill="rgb(209,210,36)" rx="2" ry="2" />
|
187
|
+
<text text-anchor="" x="14.40" y="107.5" font-size="12" font-family="Verdana" fill="rgb(0,0,0)" >Object#run_primes (1)</text>
|
188
|
+
</g>
|
189
|
+
<g class="func_g" onmouseover="s('Thread:70303615441860 (27 ms, 100.13%)')" onmouseout="c()" onclick="zoom(this)">
|
190
|
+
<title>Thread:70303615441860 (27 ms, 100.13%)</title><rect x="10.0" y="145" width="1180.0" height="15.0" fill="rgb(248,190,45)" rx="2" ry="2" />
|
191
|
+
<text text-anchor="" x="13.00" y="155.5" font-size="12" font-family="Verdana" fill="rgb(0,0,0)" >Thread:70303615441860</text>
|
192
|
+
</g>
|
193
|
+
<g class="func_g" onmouseover="s('Object#find_largest (1) (0 ms, 0.00%)')" onmouseout="c()" onclick="zoom(this)">
|
194
|
+
<title>Object#find_largest (1) (0 ms, 0.00%)</title><rect x="11.8" y="81" width="3.0" height="15.0" fill="rgb(250,61,52)" rx="2" ry="2" />
|
195
|
+
<text text-anchor="" x="14.84" y="91.5" font-size="12" font-family="Verdana" fill="rgb(0,0,0)" ></text>
|
196
|
+
</g>
|
197
|
+
<g class="func_g" onmouseover="s('Kernel#respond_to_missing? (200) (0 ms, 0.00%)')" onmouseout="c()" onclick="zoom(this)">
|
198
|
+
<title>Kernel#respond_to_missing? (200) (0 ms, 0.00%)</title><rect x="1182.6" y="33" width="7.2" height="15.0" fill="rgb(227,7,25)" rx="2" ry="2" />
|
199
|
+
<text text-anchor="" x="1185.60" y="43.5" font-size="12" font-family="Verdana" fill="rgb(0,0,0)" ></text>
|
200
|
+
</g>
|
201
|
+
<g class="func_g" onmouseover="s('Array#first (1) (0 ms, 0.00%)')" onmouseout="c()" onclick="zoom(this)">
|
202
|
+
<title>Array#first (1) (0 ms, 0.00%)</title><rect x="12.4" y="65" width="0.1" height="15.0" fill="rgb(241,32,35)" rx="2" ry="2" />
|
203
|
+
<text text-anchor="" x="15.36" y="75.5" font-size="12" font-family="Verdana" fill="rgb(0,0,0)" ></text>
|
204
|
+
</g>
|
205
|
+
<g class="func_g" onmouseover="s('Kernel#rand (200) (0 ms, 0.00%)')" onmouseout="c()" onclick="zoom(this)">
|
206
|
+
<title>Kernel#rand (200) (0 ms, 0.00%)</title><rect x="1170.4" y="49" width="19.4" height="15.0" fill="rgb(230,24,35)" rx="2" ry="2" />
|
207
|
+
<text text-anchor="" x="1173.40" y="59.5" font-size="12" font-family="Verdana" fill="rgb(0,0,0)" ></text>
|
208
|
+
</g>
|
209
|
+
<g class="func_g" onmouseover="s('Object#find_primes (1) (26 ms, 96.42%)')" onmouseout="c()" onclick="zoom(this)">
|
210
|
+
<title>Object#find_primes (1) (26 ms, 96.42%)</title><rect x="14.8" y="81" width="1142.5" height="15.0" fill="rgb(251,108,5)" rx="2" ry="2" />
|
211
|
+
<text text-anchor="" x="17.77" y="91.5" font-size="12" font-family="Verdana" fill="rgb(0,0,0)" >Object#find_primes (1)</text>
|
212
|
+
</g>
|
213
|
+
<g class="func_g" onmouseover="s('Array#select (1) (26 ms, 96.42%)')" onmouseout="c()" onclick="zoom(this)">
|
214
|
+
<title>Array#select (1) (26 ms, 96.42%)</title><rect x="15.0" y="65" width="1142.3" height="15.0" fill="rgb(224,170,33)" rx="2" ry="2" />
|
215
|
+
<text text-anchor="" x="18.03" y="75.5" font-size="12" font-family="Verdana" fill="rgb(0,0,0)" >Array#select (1)</text>
|
216
|
+
</g>
|
217
|
+
<g class="func_g" onmouseover="s('Array#each_index (1) (1 ms, 3.71%)')" onmouseout="c()" onclick="zoom(this)">
|
218
|
+
<title>Array#each_index (1) (1 ms, 3.71%)</title><rect x="1157.5" y="65" width="32.3" height="15.0" fill="rgb(223,147,10)" rx="2" ry="2" />
|
219
|
+
<text text-anchor="" x="1160.49" y="75.5" font-size="12" font-family="Verdana" fill="rgb(0,0,0)" >Ar..</text>
|
220
|
+
</g>
|
221
|
+
<g class="func_g" onmouseover="s('Class#new (1) (0 ms, 0.00%)')" onmouseout="c()" onclick="zoom(this)">
|
222
|
+
<title>Class#new (1) (0 ms, 0.00%)</title><rect x="1189.8" y="65" width="0.2" height="15.0" fill="rgb(214,129,37)" rx="2" ry="2" />
|
223
|
+
<text text-anchor="" x="1192.78" y="75.5" font-size="12" font-family="Verdana" fill="rgb(0,0,0)" ></text>
|
224
|
+
</g>
|
225
|
+
<g class="func_g" onmouseover="s('Object#make_random_array (1) (1 ms, 3.71%)')" onmouseout="c()" onclick="zoom(this)">
|
226
|
+
<title>Object#make_random_array (1) (1 ms, 3.71%)</title><rect x="1157.3" y="81" width="32.7" height="15.0" fill="rgb(238,46,41)" rx="2" ry="2" />
|
227
|
+
<text text-anchor="" x="1160.27" y="91.5" font-size="12" font-family="Verdana" fill="rgb(0,0,0)" >Ob..</text>
|
228
|
+
</g>
|
229
|
+
<g class="func_g" onmouseover="s('Integer#upto (200) (25 ms, 92.71%)')" onmouseout="c()" onclick="zoom(this)">
|
230
|
+
<title>Integer#upto (200) (25 ms, 92.71%)</title><rect x="46.9" y="33" width="1110.4" height="15.0" fill="rgb(213,32,29)" rx="2" ry="2" />
|
231
|
+
<text text-anchor="" x="49.89" y="43.5" font-size="12" font-family="Verdana" fill="rgb(0,0,0)" >Integer#upto (200)</text>
|
232
|
+
</g>
|
233
|
+
<g class="func_g" onmouseover="s('Global#[No method] (1) (27 ms, 100.13%)')" onmouseout="c()" onclick="zoom(this)">
|
234
|
+
<title>Global#[No method] (1) (27 ms, 100.13%)</title><rect x="10.0" y="113" width="1180.0" height="15.0" fill="rgb(213,132,43)" rx="2" ry="2" />
|
235
|
+
<text text-anchor="" x="13.00" y="123.5" font-size="12" font-family="Verdana" fill="rgb(0,0,0)" >Global#[No method] (1)</text>
|
236
|
+
</g>
|
237
|
+
<g class="func_g" onmouseover="s('all (27 ms, 100%)')" onmouseout="c()" onclick="zoom(this)">
|
238
|
+
<title>all (27 ms, 100%)</title><rect x="10.0" y="161" width="1180.0" height="15.0" fill="rgb(232,90,54)" rx="2" ry="2" />
|
239
|
+
<text text-anchor="" x="13.00" y="171.5" font-size="12" font-family="Verdana" fill="rgb(0,0,0)" ></text>
|
240
|
+
</g>
|
241
|
+
<g class="func_g" onmouseover="s('Integer#upto (1) (0 ms, 0.00%)')" onmouseout="c()" onclick="zoom(this)">
|
242
|
+
<title>Integer#upto (1) (0 ms, 0.00%)</title><rect x="12.5" y="65" width="2.3" height="15.0" fill="rgb(243,8,16)" rx="2" ry="2" />
|
243
|
+
<text text-anchor="" x="15.49" y="75.5" font-size="12" font-family="Verdana" fill="rgb(0,0,0)" ></text>
|
244
|
+
</g>
|
245
|
+
<g class="func_g" onmouseover="s('Object#is_prime (200) (26 ms, 96.42%)')" onmouseout="c()" onclick="zoom(this)">
|
246
|
+
<title>Object#is_prime (200) (26 ms, 96.42%)</title><rect x="26.2" y="49" width="1131.1" height="15.0" fill="rgb(243,54,53)" rx="2" ry="2" />
|
247
|
+
<text text-anchor="" x="29.24" y="59.5" font-size="12" font-family="Verdana" fill="rgb(0,0,0)" >Object#is_prime (200)</text>
|
248
|
+
</g>
|
249
|
+
</svg>
|
@@ -0,0 +1,67 @@
|
|
1
|
+
|
2
|
+
require 'ruby-prof'
|
3
|
+
|
4
|
+
module RubyProf
|
5
|
+
|
6
|
+
# wow much flame graph many stack wow!!
|
7
|
+
#
|
8
|
+
class FlameGraphPrinter < AbstractPrinter
|
9
|
+
|
10
|
+
VERSION = '0.2.0'
|
11
|
+
|
12
|
+
def print(output = STDOUT, options = {})
|
13
|
+
@output = output
|
14
|
+
setup_options(options)
|
15
|
+
|
16
|
+
@overall_threads_time = @result.threads.reduce(0) { |a, thread| a + thread.total_time }
|
17
|
+
|
18
|
+
@result.threads.each do |thread|
|
19
|
+
@current_thread_id = thread.fiber_id
|
20
|
+
@overall_time = thread.total_time
|
21
|
+
start = []
|
22
|
+
start << "Thread:#{thread.id}"
|
23
|
+
start << "Fiber:#{thread.fiber_id}" unless thread.id == thread.fiber_id
|
24
|
+
thread.methods.each do |m|
|
25
|
+
next unless m.root?
|
26
|
+
m.call_infos.each do |ci|
|
27
|
+
next unless ci.root?
|
28
|
+
print_stack start, ci
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def min_time
|
35
|
+
0
|
36
|
+
end
|
37
|
+
|
38
|
+
def print_stack(prefix, call_info)
|
39
|
+
|
40
|
+
total_time = call_info.total_time
|
41
|
+
percent_total = (total_time/@overall_time)*100
|
42
|
+
return unless percent_total > min_percent
|
43
|
+
return unless total_time >= min_time
|
44
|
+
|
45
|
+
kids = call_info.children
|
46
|
+
current = prefix + [name(call_info)]
|
47
|
+
@output.puts "#{current.join(';')} #{number call_info.self_time * 1e3}"
|
48
|
+
|
49
|
+
kids.each do |child|
|
50
|
+
print_stack current, child
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
def name(call_info)
|
56
|
+
method = call_info.target
|
57
|
+
"#{method.full_name} (#{call_info.called})"
|
58
|
+
end
|
59
|
+
|
60
|
+
def number(x)
|
61
|
+
("%.6f" % x).sub(/\.0*$/, '').sub(/(\..*?)0+$/, '\1')
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'ruby-prof-flamegraph'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "ruby-prof-flamegraph"
|
8
|
+
spec.version = RubyProf::FlameGraphPrinter::VERSION
|
9
|
+
spec.authors = ["Thai Pangsakulyanont"]
|
10
|
+
spec.email = ["org.yi.dttvb@gmail.com"]
|
11
|
+
spec.summary = %q{ruby-prof printer that exports to fold stacks compatible with FlameGraph}
|
12
|
+
spec.homepage = "http://github.com/dtinth/ruby-prof-flamegraph"
|
13
|
+
spec.license = "MIT"
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0")
|
16
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
17
|
+
spec.require_paths = ["lib"]
|
18
|
+
|
19
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
20
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
21
|
+
spec.add_runtime_dependency "ruby-prof", "~> 0.15"
|
22
|
+
end
|
metadata
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ruby-prof-flamegraph
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Thai Pangsakulyanont
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-11-29 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.7'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.7'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: ruby-prof
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.15'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.15'
|
55
|
+
description:
|
56
|
+
email:
|
57
|
+
- org.yi.dttvb@gmail.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- Gemfile
|
64
|
+
- LICENSE.txt
|
65
|
+
- README.md
|
66
|
+
- Rakefile
|
67
|
+
- example.rb
|
68
|
+
- example.svg
|
69
|
+
- lib/ruby-prof-flamegraph.rb
|
70
|
+
- lib/ruby-prof/printers/flame_graph_printer.rb
|
71
|
+
- ruby-prof-flamegraph.gemspec
|
72
|
+
homepage: http://github.com/dtinth/ruby-prof-flamegraph
|
73
|
+
licenses:
|
74
|
+
- MIT
|
75
|
+
metadata: {}
|
76
|
+
post_install_message:
|
77
|
+
rdoc_options: []
|
78
|
+
require_paths:
|
79
|
+
- lib
|
80
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '0'
|
85
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
requirements: []
|
91
|
+
rubyforge_project:
|
92
|
+
rubygems_version: 2.2.2
|
93
|
+
signing_key:
|
94
|
+
specification_version: 4
|
95
|
+
summary: ruby-prof printer that exports to fold stacks compatible with FlameGraph
|
96
|
+
test_files: []
|