ruby-prof-flamegraph 0.2.0
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 +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: []
|