flamegraph 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d5843c5d2ae0e8009e59a7b2ae1be5866801f7f2
4
+ data.tar.gz: 2c8783c5494d36e030917705e56469f22bb7ad33
5
+ SHA512:
6
+ metadata.gz: 5eb5c07f7b53389bad117144637dced3282e0db7d8b16fec95b293e3f7d30ea53fe3908fa27cf27fa487099c19745d858ac51fb0a53a60577063cc9ba25e1ccf
7
+ data.tar.gz: 23c5aff03a944b3658dd7ec3beba84c9e5ce3439d1012a5e26c68e796b30800276e38143e07e7bc47e1f0201f8c54602e68f2991858cce45728fba708ff35346
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.swp
19
+ demo/graph.html
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in flamegraph.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Sam Saffron
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.
@@ -0,0 +1,29 @@
1
+ # Flamegraph
2
+
3
+ Flamegraph support for arbitrary Ruby apps
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'flamegraph'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install flamegraph
18
+
19
+ ## Usage
20
+
21
+ Note: Only supported on Ruby 2.0. Gathering stack traces is too slow on earlier versions of Ruby or JRuby
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,21 @@
1
+ $LOAD_PATH.unshift File.expand_path '../lib'
2
+ require File.expand_path('../../lib/flamegraph', __FILE__)
3
+
4
+ def ack(m, n)
5
+ if (m == 0)
6
+ n + 1
7
+ elsif (n == 0)
8
+ ack(m - 1, 1)
9
+ else
10
+ ack(m - 1, ack(m, n - 1))
11
+ end
12
+ end
13
+
14
+ graph = Flamegraph.generate do
15
+ ack(3,7)
16
+ end
17
+
18
+ Flamegraph.generate("graph.html") do
19
+ ack(3,7)
20
+ end
21
+
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'flamegraph/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "flamegraph"
8
+ spec.version = Flamegraph::VERSION
9
+ spec.authors = ["Sam Saffron"]
10
+ spec.email = ["sam.saffron@gmail.com"]
11
+ spec.description = %q{Flamegraph support for arbitrary ruby apps}
12
+ spec.summary = %q{Flamegraph support for arbitrary ruby apps}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ end
@@ -0,0 +1,23 @@
1
+ require "json"
2
+ require "flamegraph/version"
3
+ require "flamegraph/sampler"
4
+ require "flamegraph/renderer"
5
+
6
+ module Flamegraph
7
+ def self.generate(filename=nil)
8
+ sampler = Flamegraph::Sampler.new
9
+ sampler.start
10
+ yield
11
+ results = sampler.finish
12
+
13
+ renderer = Flamegraph::Renderer.new(results)
14
+ result = renderer.graph_html
15
+
16
+ if filename
17
+ File.open(filename,"w") do |f|
18
+ f.write(result)
19
+ end
20
+ end
21
+ result
22
+ end
23
+ end
@@ -0,0 +1,325 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
5
+ <script src="http://cdnjs.cloudflare.com/ajax/libs/d3/3.0.8/d3.min.js"></script>
6
+ <meta charset=utf-8 />
7
+ <title>Flame Graph of Page</title>
8
+ <style>
9
+ .info {height: 40px;}
10
+ .legend div {
11
+ display: block;
12
+ float: left;
13
+ width: 150px;
14
+ margin: 0 8px 8px;
15
+ padding: 4px;
16
+ height: 50px;
17
+ }
18
+ </style>
19
+ </head>
20
+ <body>
21
+ <div class="graph"></div>
22
+ <div class="info"></div>
23
+ <div class="legend"></div>
24
+
25
+ <script>
26
+
27
+ var data = /*DATA*/;
28
+ var maxX = 0;
29
+ var maxY = 0;
30
+
31
+ debounce = function(func, wait, trickle) {
32
+ var timeout;
33
+
34
+ timeout = null;
35
+ return function() {
36
+ var args, context, currentWait, later;
37
+ context = this;
38
+ args = arguments;
39
+ later = function() {
40
+ timeout = null;
41
+ return func.apply(context, args);
42
+ };
43
+
44
+ if (timeout && trickle) {
45
+ // already queued, let it through
46
+ return;
47
+ }
48
+
49
+ if (typeof wait === "function") {
50
+ currentWait = wait();
51
+ } else {
52
+ currentWait = wait;
53
+ }
54
+
55
+ if (timeout) {
56
+ clearTimeout(timeout);
57
+ }
58
+
59
+ timeout = setTimeout(later, currentWait);
60
+ return timeout;
61
+ };
62
+ };
63
+
64
+
65
+ var guessGem = function(frame)
66
+ {
67
+ var split = frame.split('/gems/');
68
+ if(split.length == 1) {
69
+ split = frame.split('/app/');
70
+ if(split.length == 1) {
71
+ split = frame.split('/lib/');
72
+ }
73
+
74
+ split = split[Math.max(split.length-2,0)].split('/');
75
+ return split[split.length-1];
76
+ }
77
+ else
78
+ {
79
+ return split[split.length -1].split('/')[0];
80
+ }
81
+ }
82
+
83
+ var guessMethod = function(frame) {
84
+ var split = frame.split('`');
85
+ if(split.length == 2) {
86
+ return split[1].split("'")[0];
87
+ }
88
+ return '?';
89
+ }
90
+
91
+ var guessFile = function(frame) {
92
+ var split = frame.split(".rb:");
93
+ if(split.length == 2) {
94
+ split = split[0].split('/');
95
+ return split[split.length - 1];
96
+ }
97
+ return "";
98
+ }
99
+
100
+ $.each(data, function(){
101
+ maxX = Math.max(maxX, this.x + this.width);
102
+ maxY = Math.max(maxY, this.y);
103
+ this.shortName = /* guessGem(this.frame) + " " + guessFile(this.frame) + " " */ guessMethod(this.frame);
104
+ });
105
+
106
+ var width = $(window).width();
107
+ var height = $(window).height() / 1.2;
108
+
109
+ $('.graph').width(width).height(height);
110
+
111
+ var xScale = d3.scale.linear()
112
+ .domain([0, maxX])
113
+ .range([0, width]);
114
+
115
+ var yScale = d3.scale.linear()
116
+ .domain([0, maxY])
117
+ .range([0,height]);
118
+
119
+ var realHeight = 0;
120
+ var debouncedHeightCheck = debounce(function(){
121
+ if (realHeight > 15) {
122
+ svg.selectAll('text').attr('display','show');
123
+ } else {
124
+ svg.selectAll('text').attr('display','none');
125
+ }
126
+ }, 200);
127
+
128
+ function zoom()
129
+ {
130
+ svg.attr("transform", "translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")");
131
+
132
+ realHeight = yScale(1) * d3.event.scale;
133
+ debouncedHeightCheck();
134
+ }
135
+
136
+ var svg = d3.select(".graph")
137
+ .append("svg")
138
+ .attr("width", "100%")
139
+ .attr("height", "100%")
140
+ .attr("pointer-events", "all")
141
+ .append('svg:g')
142
+ .call(d3.behavior.zoom().on("zoom", zoom))
143
+ .append('svg:g');
144
+
145
+
146
+ // so zoom works everywhere
147
+ svg.append("rect")
148
+ .attr("x",function(d) { return xScale(0); })
149
+ .attr("y",function(d) { return yScale(0);})
150
+ .attr("width", function(d){return xScale(maxX);})
151
+ .attr("height", yScale(maxY))
152
+ .attr("fill", "white");
153
+
154
+ var color = function() {
155
+ var r = parseInt(205 + Math.random() * 50);
156
+ var g = parseInt(Math.random() * 230);
157
+ var b = parseInt(Math.random() * 55);
158
+ return "rgb(" + r + "," + g + "," + b + ")";
159
+ }
160
+ var info = {};
161
+
162
+ // http://stackoverflow.com/questions/1960473/unique-values-in-an-array
163
+ Array.prototype.getUnique = function() {
164
+ var o = {}, a = []
165
+ for (var i = 0; i < this.length; i++) o[this[i]] = 1
166
+ for (var e in o) a.push(e)
167
+ return a
168
+ }
169
+
170
+ var samplePercent = function(samples){
171
+ return "(" + samples +
172
+ " sample" + (samples == 1 ? "" : "s") + " - " +
173
+ ((samples / maxX) * 100).toFixed(2) + "%)";
174
+ }
175
+
176
+ var mouseover = function(d) {
177
+ var i = info[d.frame];
178
+ $('.info').text( d.frame + " " + samplePercent(i.samples.length));
179
+ d3.selectAll(i.nodes)
180
+ .attr('opacity',0.5);
181
+ };
182
+
183
+ var mouseout = function(d) {
184
+ var i = info[d.frame];
185
+ $('.info').text("");
186
+ d3.selectAll(i.nodes)
187
+ .attr('opacity',1);
188
+ };
189
+
190
+ // http://stackoverflow.com/a/7419630
191
+ var rainbow = function(numOfSteps, step) {
192
+ // 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.
193
+ // Adam Cole, 2011-Sept-14
194
+ // HSV to RBG adapted from: http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
195
+ var r, g, b;
196
+ var h = step / numOfSteps;
197
+ var i = ~~(h * 6);
198
+ var f = h * 6 - i;
199
+ var q = 1 - f;
200
+ switch(i % 6){
201
+ case 0: r = 1, g = f, b = 0; break;
202
+ case 1: r = q, g = 1, b = 0; break;
203
+ case 2: r = 0, g = 1, b = f; break;
204
+ case 3: r = 0, g = q, b = 1; break;
205
+ case 4: r = f, g = 0, b = 1; break;
206
+ case 5: r = 1, g = 0, b = q; break;
207
+ }
208
+ var c = "#" + ("00" + (~ ~(r * 255)).toString(16)).slice(-2) + ("00" + (~ ~(g * 255)).toString(16)).slice(-2) + ("00" + (~ ~(b * 255)).toString(16)).slice(-2);
209
+ return (c);
210
+ }
211
+
212
+ // assign some colors, analyze samples per gem
213
+ var gemStats = {}
214
+
215
+ $.each(data, function(){
216
+
217
+ var gem = guessGem(this.frame);
218
+ var stat = gemStats[gem];
219
+
220
+ if(!stat) {
221
+ gemStats[gem] = stat = {samples: [], frames: []};
222
+ }
223
+
224
+ stat.frames.push(this.frame);
225
+ for(var j=0; j < this.width; j++){
226
+ stat.samples.push(this.x + j);
227
+ }
228
+ });
229
+
230
+ var totalGems = 0;
231
+ $.each(gemStats, function(){totalGems++;});
232
+
233
+
234
+ var currentIndex = 0;
235
+ $.each(gemStats, function(k,stat){
236
+
237
+ stat.color = rainbow(totalGems, currentIndex);
238
+ stat.samples = stat.samples.getUnique();
239
+
240
+ for(var x=0; x < stat.frames.length; x++) {
241
+ info[stat.frames[x]] = {nodes: [], samples: [], color: stat.color};
242
+ }
243
+
244
+ currentIndex += 1;
245
+ });
246
+
247
+
248
+ // see: http://bl.ocks.org/mundhradevang/1387786
249
+ function fontSize(d,i) {
250
+ var size = yScale(1) / 3;
251
+ // var words = d.shortName.split(' ');
252
+ var word = d.shortName; // words[0];
253
+ var width = xScale(d.width+100);
254
+ var height = yScale(1);
255
+ var length = 0;
256
+ d3.select(this).style("font-size", size + "px").text(word);
257
+ while(((this.getBBox().width >= width) || (this.getBBox().height >= height)) && (size > 12))
258
+ {
259
+ size -= 0.1;
260
+ d3.select(this).style("font-size", size + "px");
261
+ }
262
+
263
+ d3.select(this).attr("dy", size);
264
+ }
265
+
266
+ svg.selectAll("g")
267
+ .data(data)
268
+ .enter()
269
+ .append("g")
270
+ .each(function(){
271
+ d3.select(this)
272
+ .append("rect")
273
+ .attr("x",function(d) { return xScale(d.x-1); })
274
+ .attr("y",function(d) { return yScale(maxY - d.y);})
275
+ .attr("width", function(d){return xScale(d.width);})
276
+ .attr("height", yScale(1))
277
+ .attr("fill", function(d){
278
+ var i = info[d.frame];
279
+ if(!i) {
280
+ info[d.frame] = i = {nodes: [], samples: [], color: color()};
281
+ }
282
+ i.nodes.push(this);
283
+ for(var j=0; j < d.width; j++){
284
+ i.samples.push(d.x + j);
285
+ }
286
+ return i.color;
287
+ })
288
+ .on("mouseover", mouseover)
289
+ .on("mouseout", mouseout);
290
+
291
+ d3.select(this)
292
+ .append("text")
293
+ .attr("x",function(d) { return xScale(d.x - 0.98); })
294
+ .attr("y",function(d) { return yScale(maxY - d.y);})
295
+ .on("mouseover", mouseover)
296
+ .on("mouseout", mouseout)
297
+ .each(fontSize)
298
+ .attr("display", "none");
299
+
300
+ });
301
+
302
+
303
+ // Samples may overlap on the same line
304
+ for (var r in info) {
305
+ if (info[r].samples) {
306
+ info[r].samples = info[r].samples.getUnique();
307
+ }
308
+ };
309
+
310
+
311
+ // render the legend
312
+ $.each(gemStats, function(k,v){
313
+ var node = $("<div></div>")
314
+ .css("background-color", v.color)
315
+ .text(k + " " + samplePercent(v.samples.length)) ;
316
+ $('.legend').append(node);
317
+ });
318
+
319
+
320
+
321
+ </script>
322
+ </body>
323
+ </html>
324
+
325
+
@@ -0,0 +1,61 @@
1
+ # inspired by https://github.com/brendangregg/FlameGraph
2
+
3
+ class Flamegraph::Renderer
4
+ def initialize(stacks)
5
+ @stacks = stacks
6
+ end
7
+
8
+ def graph_html
9
+ body = IO.read(::File.expand_path('flamegraph.html', ::File.dirname(__FILE__)))
10
+ body.sub!("/*DATA*/", ::JSON.generate(graph_data));
11
+ body
12
+ end
13
+
14
+ def graph_data
15
+ height = 0
16
+
17
+ table = []
18
+ prev = []
19
+
20
+ # a 2d array makes collapsing easy
21
+ @stacks.each_with_index do |stack, pos|
22
+ col = []
23
+
24
+ stack.reverse.map{|r| r.to_s}.each_with_index do |frame, i|
25
+
26
+ if !prev[i].nil?
27
+ last_col = prev[i]
28
+ if last_col[0] == frame
29
+ last_col[1] += 1
30
+ col << nil
31
+ next
32
+ end
33
+ end
34
+
35
+ prev[i] = [frame, 1]
36
+ col << prev[i]
37
+ end
38
+ prev = prev[0..col.length-1].to_a
39
+ table << col
40
+ end
41
+
42
+ data = []
43
+
44
+ # a 1d array makes rendering easy
45
+ table.each_with_index do |col, col_num|
46
+ col.each_with_index do |row, row_num|
47
+ next unless row && row.length == 2
48
+ data << {
49
+ :x => col_num + 1,
50
+ :y => row_num + 1,
51
+ :width => row[1],
52
+ :frame => row[0]
53
+ }
54
+ end
55
+ end
56
+
57
+ data
58
+ end
59
+
60
+ end
61
+
@@ -0,0 +1,32 @@
1
+ class Flamegraph::Sampler
2
+
3
+ def initialize
4
+ @backtraces = []
5
+ @done_samplint = false
6
+ end
7
+
8
+ def start
9
+ @backtraces = []
10
+ @done_samplint = false
11
+
12
+ t = Thread.current
13
+ @thread = Thread.new do
14
+ begin
15
+ while !@done_sampling
16
+ @backtraces << t.backtrace_locations
17
+
18
+ # On my machine using Ruby 2.0 this give me excellent fidelity of stack trace per 1.2ms
19
+ # with this fidelity analysis becomes very powerful
20
+ sleep 0.0005
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ def finish
27
+ @done_sampling = true
28
+ @thread.join
29
+ @backtraces
30
+ end
31
+
32
+ end
@@ -0,0 +1,3 @@
1
+ module Flamegraph
2
+ VERSION = "0.0.1"
3
+ end
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: flamegraph
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Sam Saffron
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-05-10 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.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Flamegraph support for arbitrary ruby apps
42
+ email:
43
+ - sam.saffron@gmail.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - .gitignore
49
+ - Gemfile
50
+ - LICENSE.txt
51
+ - README.md
52
+ - Rakefile
53
+ - demo/demo.rb
54
+ - flamegraph.gemspec
55
+ - lib/flamegraph.rb
56
+ - lib/flamegraph/flamegraph.html
57
+ - lib/flamegraph/renderer.rb
58
+ - lib/flamegraph/sampler.rb
59
+ - lib/flamegraph/version.rb
60
+ homepage: ''
61
+ licenses:
62
+ - MIT
63
+ metadata: {}
64
+ post_install_message:
65
+ rdoc_options: []
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - '>='
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ requirements: []
79
+ rubyforge_project:
80
+ rubygems_version: 2.0.0
81
+ signing_key:
82
+ specification_version: 4
83
+ summary: Flamegraph support for arbitrary ruby apps
84
+ test_files: []