rack-mini-profiler 0.1.30 → 0.1.31

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rack-mini-profiler might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 88d14211a1bd6e204377a0405f364fa9069029f0
4
- data.tar.gz: 0c96e6d8d1df58cdd879319063e203efa846bd9f
3
+ metadata.gz: 50a96f45043bb94f118971de9fe539a5af648d9c
4
+ data.tar.gz: 2deba7160023f7ae64ec39bdc456bcaf2c77fb45
5
5
  SHA512:
6
- metadata.gz: f681b1fe0dedcbd7ec6dc43f31cc0eb6d7dd78c0c69ae0b6bd040df166450a833a7f987c7d0167f2e54b30c37323bc99f48440078ad59052b852567a4d6ff442
7
- data.tar.gz: dccb1eec80365ab26fd439d280ccc03fe3f0c9c72d193df117c1392b8f338abb4fbb72f676217c5ae5cd47bfb66a8249b4f7a6e244dcf1df7cd322bbff36c2d1
6
+ metadata.gz: 4444d3a819342722b3e27d8de3d9cbf9687fa3d54f9d9f39214ccb405b05973cc68950687227f3ea7caa4d994f0d6f757b4f34122c24a95689f9563b97e844a2
7
+ data.tar.gz: 2a9bdaf1850ca648c2569994f680874ce79bd5351553ce4d1c802119923ac375cddf4ba9d67812a2d4db1e06562af5672a9318aff678a84eef5896a3897b3907
@@ -154,3 +154,8 @@
154
154
  * Feature: Added Rack::MiniProfiler.counter_method(klass,name) for injecting counters
155
155
  * Bug: Counters were not shifting the table correctly
156
156
 
157
+ 3-September-2013
158
+
159
+ * Ripped out flamegraph so it can be isolated into a gem
160
+ * Flamegraph now has much increased fidelity
161
+ * Ripped out pp=sample it just was never really used
@@ -1,6 +1,6 @@
1
1
  class Rack::MiniProfiler::Context
2
2
  attr_accessor :inject_js,:current_timer,:page_struct,:skip_backtrace,:full_backtrace,:discard, :mpt_init, :measure
3
-
3
+
4
4
  def initialize(opts = {})
5
5
  opts["measure"] = true unless opts.key? "measure"
6
6
  opts.each do |k,v|
@@ -0,0 +1,40 @@
1
+ require 'objspace'
2
+
3
+ class Rack::MiniProfiler::GCProfilerRubyHead
4
+ def profile_rack(app,env)
5
+ end
6
+
7
+ def profile(&block)
8
+ GC.start
9
+ GC.disable
10
+
11
+ items = []
12
+ objs = []
13
+
14
+ ObjectSpace.trace_object_allocations do
15
+ block.call
16
+
17
+ ObjectSpace.each_object do |o|
18
+ objs << o
19
+ end
20
+
21
+ objs.each do |o|
22
+ g = ObjectSpace.allocation_generation(o)
23
+ if g
24
+ l = ObjectSpace.allocation_sourceline(o)
25
+ f = ObjectSpace.allocation_sourcefile(o)
26
+ c = ObjectSpace.allocation_class_path(o)
27
+ m = ObjectSpace.allocation_method_id(o)
28
+ items << "Allocated #{c} in #{m} #{f}:#{l}"
29
+ end
30
+ end
31
+ end
32
+
33
+ items.group_by{|x| x}.sort{|a,b| b[1].length <=> a[1].length}.each do |row, group|
34
+ puts "#{row} x #{group.length}"
35
+ end
36
+
37
+ GC.enable
38
+ profile_allocations(name, &block)
39
+ end
40
+ end
@@ -18,7 +18,8 @@ require 'mini_profiler/profiling_methods'
18
18
  require 'mini_profiler/context'
19
19
  require 'mini_profiler/client_settings'
20
20
  require 'mini_profiler/gc_profiler'
21
- require 'mini_profiler/flame_graph'
21
+ # TODO
22
+ # require 'mini_profiler/gc_profiler_ruby_head' if Gem::Version.new('2.1.0') <= Gem::Version.new(RUBY_VERSION)
22
23
 
23
24
  module Rack
24
25
 
@@ -237,35 +238,7 @@ module Rack
237
238
  current.skip_backtrace = true
238
239
  end
239
240
 
240
- done_sampling = false
241
- quit_sampler = false
242
- backtraces = nil
243
-
244
- if query_string =~ /pp=sample/ || query_string =~ /pp=flamegraph/
245
- current.measure = false
246
- skip_frames = 0
247
- backtraces = []
248
- t = Thread.current
249
-
250
- Thread.new {
251
- # new in Ruby 2.0
252
- has_backtrace_locations = t.respond_to?(:backtrace_locations)
253
- begin
254
- i = 10000 # for sanity never grab more than 10k samples
255
- while i > 0
256
- break if done_sampling
257
- i -= 1
258
- backtraces << (has_backtrace_locations ? t.backtrace_locations : t.backtrace)
259
-
260
- # On my machine using Ruby 2.0 this give me excellent fidelity of stack trace per 1.2ms
261
- # with this fidelity analysis becomes very powerful
262
- sleep 0.0005
263
- end
264
- ensure
265
- quit_sampler = true
266
- end
267
- }
268
- end
241
+ flamegraph = nil
269
242
 
270
243
  trace_exceptions = query_string =~ /pp=trace-exceptions/ && defined? TracePoint
271
244
  status, headers, body, exceptions,trace = nil
@@ -287,15 +260,26 @@ module Rack
287
260
  env['HTTP_IF_MODIFIED_SINCE'] = ''
288
261
  env['HTTP_IF_NONE_MATCH'] = ''
289
262
 
290
- status,headers,body = @app.call(env)
263
+ if query_string =~ /pp=flamegraph/
264
+ unless defined?(Flamegraph) && Flamegraph.respond_to?(:generate)
265
+
266
+ flamegraph = "Please install the flamegraph gem and require it: add gem 'flamegraph' to your Gemfile"
267
+ status,headers,body = @app.call(env)
268
+ else
269
+ # do not sully our profile with mini profiler timings
270
+ current.measure = false
271
+ # first param is the path
272
+ # 0.5 means attempt to collect a sample each 0.5 secs
273
+ flamegraph = Flamegraph.generate(nil, fidelity: 0.5) do
274
+ status,headers,body = @app.call(env)
275
+ end
276
+ end
277
+ else
278
+ status,headers,body = @app.call(env)
279
+ end
291
280
  client_settings.write!(headers)
292
281
  ensure
293
282
  trace.disable if trace
294
-
295
- if backtraces
296
- done_sampling = true
297
- sleep 0.001 until quit_sampler
298
- end
299
283
  end
300
284
 
301
285
  skip_it = current.discard
@@ -331,13 +315,9 @@ module Rack
331
315
  page_struct['User'] = user(env)
332
316
  page_struct['Root'].record_time((Time.now - start) * 1000)
333
317
 
334
- if backtraces
318
+ if flamegraph
335
319
  body.close if body.respond_to? :close
336
- if query_string =~ /pp=sample/
337
- return analyze(backtraces, page_struct)
338
- else
339
- return flame_graph(backtraces, page_struct)
340
- end
320
+ return self.flamegraph(flamegraph)
341
321
  end
342
322
 
343
323
 
@@ -471,12 +451,11 @@ module Rack
471
451
  pp=no-backtrace #{"(*) " if client_settings.backtrace_none?}: don't collect stack traces from all the SQL executed (sticky, use pp=normal-backtrace to enable)
472
452
  pp=normal-backtrace #{"(*) " if client_settings.backtrace_default?}: collect stack traces from all the SQL executed and filter normally
473
453
  pp=full-backtrace #{"(*) " if client_settings.backtrace_full?}: enable full backtraces for SQL executed (use pp=normal-backtrace to disable)
474
- pp=sample : sample stack traces and return a report isolating heavy usage (works best on Ruby 2.0)
475
454
  pp=disable : disable profiling for this session
476
455
  pp=enable : enable profiling for this session (if previously disabled)
477
456
  pp=profile-gc: perform gc profiling on this request, analyzes ObjectSpace generated by request (ruby 1.9.3 only)
478
457
  pp=profile-gc-time: perform built-in gc profiling on this request (ruby 1.9.3 only)
479
- pp=flamegraph: works best on Ruby 2.0, a graph representing sampled activity.
458
+ pp=flamegraph: works best on Ruby 2.0, a graph representing sampled activity (requires the flamegraph gem).
480
459
  pp=trace-exceptions: requires Ruby 2.0, will return all the spots where your application raises execptions
481
460
  "
482
461
 
@@ -484,49 +463,9 @@ module Rack
484
463
  [200, headers, [body]]
485
464
  end
486
465
 
487
- def flame_graph(traces, page_struct)
488
- graph = FlameGraph.new(traces)
489
- data = graph.graph_data
490
-
466
+ def flamegraph(graph)
491
467
  headers = {'Content-Type' => 'text/html'}
492
-
493
- body = IO.read(::File.expand_path('../html/flamegraph.html', ::File.dirname(__FILE__)))
494
- body.gsub!("/*DATA*/", ::JSON.generate(data));
495
-
496
- [200, headers, [body]]
497
- end
498
-
499
- def analyze(traces, page_struct)
500
- headers = {'Content-Type' => 'text/plain'}
501
- body = "Collected: #{traces.count} stack traces. Duration(ms): #{page_struct.duration_ms}"
502
-
503
- seen = {}
504
- fulldump = ""
505
- traces.each do |trace|
506
- fulldump << "\n\n"
507
- distinct = {}
508
- trace.each do |frame|
509
- frame = frame.to_s unless String === frame
510
- unless distinct[frame]
511
- distinct[frame] = true
512
- seen[frame] ||= 0
513
- seen[frame] += 1
514
- end
515
- fulldump << frame << "\n"
516
- end
517
- end
518
-
519
- body << "\n\nStack Trace Analysis\n"
520
- seen.to_a.sort{|x,y| y[1] <=> x[1]}.each do |name, count|
521
- if count > traces.count / 10
522
- body << "#{name} x #{count}\n"
523
- end
524
- end
525
-
526
- body << "\n\n\nRaw traces \n"
527
- body << fulldump
528
-
529
- [200, headers, [body]]
468
+ [200, headers, [graph]]
530
469
  end
531
470
 
532
471
  def ids_json(env)
@@ -1,5 +1,5 @@
1
1
  module Rack
2
2
  class MiniProfiler
3
- VERSION = '94950211d89e89a9694bc493497ab05f'.freeze
3
+ VERSION = '55905d0d15f4dc1e14b29260abce19b2'.freeze
4
4
  end
5
5
  end
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "rack-mini-profiler"
3
- s.version = "0.1.30"
3
+ s.version = "0.1.31"
4
4
  s.summary = "Profiles loading speed for rack applications."
5
5
  s.authors = ["Sam Saffron", "Robin Ward","Aleks Totic"]
6
6
  s.description = "Profiling toolkit for Rack applications with Rails integration. Client Side profiling, DB profiling and Server profiling."
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-mini-profiler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.30
4
+ version: 0.1.31
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Saffron
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2013-08-30 00:00:00.000000000 Z
13
+ date: 2013-09-03 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rack
@@ -89,7 +89,6 @@ files:
89
89
  - Ruby/lib/html/includes.js
90
90
  - Ruby/lib/html/list.js
91
91
  - Ruby/lib/html/jquery.1.7.1.js
92
- - Ruby/lib/html/flamegraph.html
93
92
  - Ruby/lib/html/includes.css
94
93
  - Ruby/lib/mini_profiler/context.rb
95
94
  - Ruby/lib/mini_profiler/page_timer_struct.rb
@@ -103,13 +102,13 @@ files:
103
102
  - Ruby/lib/mini_profiler/gc_profiler.rb
104
103
  - Ruby/lib/mini_profiler/client_timer_struct.rb
105
104
  - Ruby/lib/mini_profiler/sql_timer_struct.rb
105
+ - Ruby/lib/mini_profiler/gc_profiler_ruby_head.rb
106
106
  - Ruby/lib/mini_profiler/config.rb
107
107
  - Ruby/lib/mini_profiler/custom_timer_struct.rb
108
108
  - Ruby/lib/mini_profiler/version.rb
109
109
  - Ruby/lib/mini_profiler/timer_struct.rb
110
110
  - Ruby/lib/mini_profiler/profiler.rb
111
111
  - Ruby/lib/mini_profiler/request_timer_struct.rb
112
- - Ruby/lib/mini_profiler/flame_graph.rb
113
112
  - Ruby/lib/patches/net_patches.rb
114
113
  - Ruby/lib/patches/sql_patches.rb
115
114
  - Ruby/lib/rack-mini-profiler.rb
@@ -1,351 +0,0 @@
1
- <!DOCTYPE html>
2
- <html>
3
- <head>
4
- <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
5
- <script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.0.8/d3.min.js"></script>
6
- <!-- <script src="http://cdnjs.cloudflare.com/ajax/libs/sugar/1.3.8/sugar.min.js"></script> -->
7
- <meta charset=utf-8 />
8
- <title>Flame Graph of Page</title>
9
- <style>
10
- .info {height: 40px;}
11
- .legend div {
12
- display: block;
13
- float: left;
14
- width: 150px;
15
- margin: 0 8px 8px;
16
- padding: 4px;
17
- height: 50px;
18
- }
19
- </style>
20
- </head>
21
- <body>
22
- <div class="graph"></div>
23
- <div class="info"></div>
24
- <div class="legend"></div>
25
-
26
- <script>
27
-
28
- var data = /*DATA*/;
29
- var maxX = 0;
30
- var maxY = 0;
31
-
32
- debounce = function(func, wait, trickle) {
33
- var timeout;
34
-
35
- timeout = null;
36
- return function() {
37
- var args, context, currentWait, later;
38
- context = this;
39
- args = arguments;
40
- later = function() {
41
- timeout = null;
42
- return func.apply(context, args);
43
- };
44
-
45
- if (timeout && trickle) {
46
- // already queued, let it through
47
- return;
48
- }
49
-
50
- if (typeof wait === "function") {
51
- currentWait = wait();
52
- } else {
53
- currentWait = wait;
54
- }
55
-
56
- if (timeout) {
57
- clearTimeout(timeout);
58
- }
59
-
60
- timeout = setTimeout(later, currentWait);
61
- return timeout;
62
- };
63
- };
64
-
65
-
66
- var guessGem = function(frame)
67
- {
68
- var split = frame.split('/gems/');
69
- if(split.length == 1) {
70
- split = frame.split('/app/');
71
- if(split.length == 1) {
72
- split = frame.split('/lib/');
73
- }
74
-
75
- split = split[Math.max(split.length-2,0)].split('/');
76
- return split[split.length-1];
77
- }
78
- else
79
- {
80
- return split[split.length -1].split('/')[0];
81
- }
82
- }
83
-
84
- var guessMethod = function(frame) {
85
- var split = frame.split('`');
86
- if(split.length == 2) {
87
- return split[1].split("'")[0];
88
- }
89
- return '?';
90
- }
91
-
92
- var guessFile = function(frame) {
93
- var split = frame.split(".rb:");
94
- if(split.length == 2) {
95
- split = split[0].split('/');
96
- return split[split.length - 1];
97
- }
98
- return "";
99
- }
100
-
101
- $.each(data, function(){
102
- maxX = Math.max(maxX, this.x + this.width);
103
- maxY = Math.max(maxY, this.y);
104
- this.shortName = /* guessGem(this.frame) + " " + guessFile(this.frame) + " " */ guessMethod(this.frame);
105
- });
106
-
107
- var width = $(window).width();
108
- var height = $(window).height() / 1.2;
109
-
110
- $('.graph').width(width).height(height);
111
-
112
- var xScale = d3.scale.linear()
113
- .domain([0, maxX])
114
- .range([0, width]);
115
-
116
- var yScale = d3.scale.linear()
117
- .domain([0, maxY])
118
- .range([0,height]);
119
-
120
- var realHeight = 0;
121
- var debouncedHeightCheck = debounce(function(){
122
- if (realHeight > 15) {
123
- svg.selectAll('text').attr('display','show');
124
- } else {
125
- svg.selectAll('text').attr('display','none');
126
- }
127
- }, 200);
128
-
129
- function zoom()
130
- {
131
- svg.attr("transform", "translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")");
132
-
133
- realHeight = yScale(1) * d3.event.scale;
134
- debouncedHeightCheck();
135
- }
136
-
137
- var svg = d3.select(".graph")
138
- .append("svg")
139
- .attr("width", "100%")
140
- .attr("height", "100%")
141
- .attr("pointer-events", "all")
142
- .append('svg:g')
143
- .call(d3.behavior.zoom().on("zoom", zoom))
144
- .append('svg:g');
145
-
146
-
147
- // so zoom works everywhere
148
- svg.append("rect")
149
- .attr("x",function(d) { return xScale(0); })
150
- .attr("y",function(d) { return yScale(0);})
151
- .attr("width", function(d){return xScale(maxX);})
152
- .attr("height", yScale(maxY))
153
- .attr("fill", "white");
154
-
155
- var color = function() {
156
- var r = parseInt(205 + Math.random() * 50);
157
- var g = parseInt(Math.random() * 230);
158
- var b = parseInt(Math.random() * 55);
159
- return "rgb(" + r + "," + g + "," + b + ")";
160
- }
161
- var info = {};
162
-
163
- // http://stackoverflow.com/questions/1960473/unique-values-in-an-array
164
- Array.prototype.getUnique = function() {
165
- var o = {}, a = []
166
- for (var i = 0; i < this.length; i++) o[this[i]] = 1
167
- for (var e in o) a.push(e)
168
- return a
169
- }
170
-
171
- var samplePercent = function(samples, exclusive){
172
- var info = " (" + samples +
173
- " sample" + (samples == 1 ? "" : "s") + " - " +
174
- ((samples / maxX) * 100).toFixed(2) + "%) ";
175
- if (exclusive) {
176
- info += " (" + exclusive +
177
- " exclusive - " +
178
- ((exclusive / maxX) * 100).toFixed(2) + "%) ";
179
- }
180
- return info;
181
- }
182
-
183
- var mouseover = function(d) {
184
- var i = info[d.frame];
185
- $('.info').text( d.frame + " " + samplePercent(i.samples.length, d.topFrame ? d.topFrame.exclusiveCount : 0));
186
- d3.selectAll(i.nodes)
187
- .attr('opacity',0.5);
188
- };
189
-
190
- var mouseout = function(d) {
191
- var i = info[d.frame];
192
- $('.info').text("");
193
- d3.selectAll(i.nodes)
194
- .attr('opacity',1);
195
- };
196
-
197
- // http://stackoverflow.com/a/7419630
198
- var rainbow = function(numOfSteps, step) {
199
- // 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.
200
- // Adam Cole, 2011-Sept-14
201
- // HSV to RBG adapted from: http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
202
- var r, g, b;
203
- var h = step / numOfSteps;
204
- var i = ~~(h * 6);
205
- var f = h * 6 - i;
206
- var q = 1 - f;
207
- switch(i % 6){
208
- case 0: r = 1, g = f, b = 0; break;
209
- case 1: r = q, g = 1, b = 0; break;
210
- case 2: r = 0, g = 1, b = f; break;
211
- case 3: r = 0, g = q, b = 1; break;
212
- case 4: r = f, g = 0, b = 1; break;
213
- case 5: r = 1, g = 0, b = q; break;
214
- }
215
- var c = "#" + ("00" + (~ ~(r * 255)).toString(16)).slice(-2) + ("00" + (~ ~(g * 255)).toString(16)).slice(-2) + ("00" + (~ ~(b * 255)).toString(16)).slice(-2);
216
- return (c);
217
- }
218
-
219
- // assign some colors, analyze samples per gem
220
- var gemStats = {}
221
- var topFrames = {}
222
- var lastFrame = {frame: 'd52e04d-df28-41ed-a215-b6ec840a8ea5', x: -1}
223
-
224
- $.each(data, function(){
225
-
226
- var gem = guessGem(this.frame);
227
- var stat = gemStats[gem];
228
-
229
- if(!stat) {
230
- gemStats[gem] = stat = {samples: [], frames: []};
231
- }
232
-
233
- stat.frames.push(this.frame);
234
- for(var j=0; j < this.width; j++){
235
- stat.samples.push(this.x + j);
236
- }
237
- // This assumes the traversal is in order
238
- if (lastFrame.x != this.x) {
239
- var topFrame = topFrames[lastFrame.frame]
240
- if (!topFrame) {
241
- topFrames[lastFrame.frame] = topFrame = {exclusiveCount: 0}
242
- }
243
- topFrame.exclusiveCount += 1;
244
- lastFrame.topFrame = topFrame;
245
- }
246
- lastFrame = this;
247
-
248
- });
249
-
250
- var topFrame = topFrames[lastFrame.frame]
251
- if (!topFrame) {
252
- topFrames[lastFrame.frame] = topFrame = {exclusiveCount: 0}
253
- }
254
- topFrame.exclusiveCount += 1;
255
- lastFrame.topFrame = topFrame;
256
-
257
- var totalGems = 0;
258
- $.each(gemStats, function(){totalGems++;});
259
-
260
-
261
- var currentIndex = 0;
262
- $.each(gemStats, function(k,stat){
263
-
264
- stat.color = rainbow(totalGems, currentIndex);
265
- stat.samples = stat.samples.getUnique();
266
-
267
- for(var x=0; x < stat.frames.length; x++) {
268
- info[stat.frames[x]] = {nodes: [], samples: [], color: stat.color};
269
- }
270
-
271
- currentIndex += 1;
272
- });
273
-
274
-
275
- // see: http://bl.ocks.org/mundhradevang/1387786
276
- function fontSize(d,i) {
277
- var size = yScale(1) / 3;
278
- // var words = d.shortName.split(' ');
279
- var word = d.shortName; // words[0];
280
- var width = xScale(d.width+100);
281
- var height = yScale(1);
282
- var length = 0;
283
- d3.select(this).style("font-size", size + "px").text(word);
284
- while(((this.getBBox().width >= width) || (this.getBBox().height >= height)) && (size > 12))
285
- {
286
- size -= 0.1;
287
- d3.select(this).style("font-size", size + "px");
288
- }
289
-
290
- d3.select(this).attr("dy", size);
291
- }
292
-
293
- svg.selectAll("g")
294
- .data(data)
295
- .enter()
296
- .append("g")
297
- .each(function(){
298
- d3.select(this)
299
- .append("rect")
300
- .attr("x",function(d) { return xScale(d.x-1); })
301
- .attr("y",function(d) { return yScale(maxY - d.y);})
302
- .attr("width", function(d){return xScale(d.width);})
303
- .attr("height", yScale(1))
304
- .attr("fill", function(d){
305
- var i = info[d.frame];
306
- if(!i) {
307
- info[d.frame] = i = {nodes: [], samples: [], color: color()};
308
- }
309
- i.nodes.push(this);
310
- for(var j=0; j < d.width; j++){
311
- i.samples.push(d.x + j);
312
- }
313
- return i.color;
314
- })
315
- .on("mouseover", mouseover)
316
- .on("mouseout", mouseout);
317
-
318
- d3.select(this)
319
- .append("text")
320
- .attr("x",function(d) { return xScale(d.x - 0.98); })
321
- .attr("y",function(d) { return yScale(maxY - d.y);})
322
- .on("mouseover", mouseover)
323
- .on("mouseout", mouseout)
324
- .each(fontSize)
325
- .attr("display", "none");
326
-
327
- });
328
-
329
-
330
- // Samples may overlap on the same line
331
- for (var r in info) {
332
- if (info[r].samples) {
333
- info[r].samples = info[r].samples.getUnique();
334
- }
335
- };
336
-
337
-
338
- // render the legend
339
- $.each(gemStats, function(k,v){
340
- var node = $("<div></div>")
341
- .css("background-color", v.color)
342
- .text(k + " " + samplePercent(v.samples.length)) ;
343
- $('.legend').append(node);
344
- });
345
-
346
-
347
-
348
- </script>
349
- </body>
350
- </html>
351
-
@@ -1,54 +0,0 @@
1
- # inspired by https://github.com/brendangregg/FlameGraph
2
-
3
- class Rack::MiniProfiler::FlameGraph
4
- def initialize(stacks)
5
- @stacks = stacks
6
- end
7
-
8
- def graph_data
9
- height = 0
10
-
11
- table = []
12
- prev = []
13
-
14
- # a 2d array makes collapsing easy
15
- @stacks.each_with_index do |stack, pos|
16
- col = []
17
-
18
- stack.reverse.map{|r| r.to_s}.each_with_index do |frame, i|
19
-
20
- if !prev[i].nil?
21
- last_col = prev[i]
22
- if last_col[0] == frame
23
- last_col[1] += 1
24
- col << nil
25
- next
26
- end
27
- end
28
-
29
- prev[i] = [frame, 1]
30
- col << prev[i]
31
- end
32
- prev = prev[0..col.length-1].to_a
33
- table << col
34
- end
35
-
36
- data = []
37
-
38
- # a 1d array makes rendering easy
39
- table.each_with_index do |col, col_num|
40
- col.each_with_index do |row, row_num|
41
- next unless row && row.length == 2
42
- data << {
43
- :x => col_num + 1,
44
- :y => row_num + 1,
45
- :width => row[1],
46
- :frame => row[0]
47
- }
48
- end
49
- end
50
-
51
- data
52
- end
53
-
54
- end