rack-mini-profiler 0.1.21 → 0.1.26

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.

Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/Ruby/CHANGELOG +135 -0
  3. data/{README.md → Ruby/README.md} +40 -9
  4. data/Ruby/lib/html/flamegraph.html +325 -0
  5. data/Ruby/lib/html/includes.css +451 -0
  6. data/{lib → Ruby/lib}/html/includes.js +135 -24
  7. data/{lib → Ruby/lib}/html/includes.less +38 -35
  8. data/{lib → Ruby/lib}/html/includes.tmpl +40 -15
  9. data/{lib → Ruby/lib}/html/jquery.1.7.1.js +1 -1
  10. data/{lib → Ruby/lib}/html/jquery.tmpl.js +1 -1
  11. data/{lib → Ruby/lib}/html/list.css +0 -0
  12. data/{lib → Ruby/lib}/html/list.js +7 -6
  13. data/{lib → Ruby/lib}/html/list.tmpl +0 -0
  14. data/Ruby/lib/html/profile_handler.js +1 -0
  15. data/{lib → Ruby/lib}/html/share.html +0 -0
  16. data/{lib → Ruby/lib}/mini_profiler/client_settings.rb +0 -0
  17. data/{lib → Ruby/lib}/mini_profiler/client_timer_struct.rb +1 -1
  18. data/{lib → Ruby/lib}/mini_profiler/config.rb +57 -52
  19. data/{lib → Ruby/lib}/mini_profiler/context.rb +11 -10
  20. data/Ruby/lib/mini_profiler/custom_timer_struct.rb +22 -0
  21. data/Ruby/lib/mini_profiler/flame_graph.rb +54 -0
  22. data/{lib → Ruby/lib}/mini_profiler/gc_profiler.rb +35 -12
  23. data/{lib → Ruby/lib}/mini_profiler/page_timer_struct.rb +7 -2
  24. data/{lib → Ruby/lib}/mini_profiler/profiler.rb +209 -144
  25. data/{lib → Ruby/lib}/mini_profiler/profiling_methods.rb +131 -108
  26. data/{lib → Ruby/lib}/mini_profiler/request_timer_struct.rb +20 -1
  27. data/{lib → Ruby/lib}/mini_profiler/sql_timer_struct.rb +0 -0
  28. data/{lib → Ruby/lib}/mini_profiler/storage/abstract_store.rb +31 -27
  29. data/{lib → Ruby/lib}/mini_profiler/storage/file_store.rb +111 -109
  30. data/{lib → Ruby/lib}/mini_profiler/storage/memcache_store.rb +11 -9
  31. data/{lib → Ruby/lib}/mini_profiler/storage/memory_store.rb +65 -63
  32. data/Ruby/lib/mini_profiler/storage/redis_store.rb +54 -0
  33. data/{lib → Ruby/lib}/mini_profiler/timer_struct.rb +0 -0
  34. data/Ruby/lib/mini_profiler/version.rb +5 -0
  35. data/Ruby/lib/mini_profiler_rails/railtie.rb +89 -0
  36. data/Ruby/lib/patches/net_patches.rb +14 -0
  37. data/{lib → Ruby/lib}/patches/sql_patches.rb +89 -48
  38. data/{lib → Ruby/lib}/rack-mini-profiler.rb +1 -0
  39. data/rack-mini-profiler.gemspec +6 -4
  40. metadata +53 -64
  41. data/CHANGELOG +0 -99
  42. data/lib/html/includes.css +0 -75
  43. data/lib/html/profile_handler.js +0 -62
  44. data/lib/mini_profiler/storage/redis_store.rb +0 -44
  45. data/lib/mini_profiler_rails/railtie.rb +0 -85
@@ -3,10 +3,16 @@ class Rack::MiniProfiler::GCProfiler
3
3
  def object_space_stats
4
4
  stats = {}
5
5
  ids = Set.new
6
+ i=0
6
7
  ObjectSpace.each_object { |o|
7
- stats[o.class] ||= 1
8
- stats[o.class] += 1
9
- ids << o.object_id
8
+ begin
9
+ i = stats[o.class] || 0
10
+ i += 1
11
+ stats[o.class] = i
12
+ ids << o.object_id if Integer === o.object_id
13
+ rescue NoMethodError
14
+ # Redis::Future undefines .class and .object_id super weird
15
+ end
10
16
  }
11
17
  {:stats => stats, :ids => ids}
12
18
  end
@@ -35,24 +41,42 @@ class Rack::MiniProfiler::GCProfiler
35
41
  result
36
42
  end
37
43
 
38
- def profile_gc(app,env)
39
-
40
- body = [];
44
+ def profile_gc_time(app,env)
45
+ body = []
41
46
 
42
- stat_after = nil
43
- stat_before = object_space_stats
44
47
  begin
45
48
  GC::Profiler.clear
46
49
  GC::Profiler.enable
47
50
  b = app.call(env)[2]
48
51
  b.close if b.respond_to? :close
49
- stat_after = object_space_stats
52
+ body << "GC Profiler ran during this request, if it fired you will see the cost below:\n\n"
50
53
  body << GC::Profiler.result
51
54
  ensure
55
+ GC.enable
52
56
  GC::Profiler.disable
53
57
  end
54
58
 
55
- diff = diff_object_stats(stat_before[:stats],stat_after[:stats])
59
+ return [200, {'Content-Type' => 'text/plain'}, body]
60
+ end
61
+
62
+ def profile_gc(app,env)
63
+
64
+ body = [];
65
+
66
+ stat_before,stat_after,diff,string_analysis = nil
67
+ begin
68
+ GC.disable
69
+ stat_before = object_space_stats
70
+ b = app.call(env)[2]
71
+ b.close if b.respond_to? :close
72
+ stat_after = object_space_stats
73
+
74
+ diff = diff_object_stats(stat_before[:stats],stat_after[:stats])
75
+ string_analysis = analyze_strings(stat_before[:ids], stat_after[:ids])
76
+ ensure
77
+ GC.enable
78
+ end
79
+
56
80
 
57
81
  body << "
58
82
  ObjectSpace delta caused by request:
@@ -69,13 +93,12 @@ ObjectSpace stats:
69
93
  body << "#{k} : #{v}\n"
70
94
  end
71
95
 
72
- r = analyze_strings(stat_before[:ids], stat_after[:ids])
73
96
 
74
97
  body << "\n
75
98
  String stats:
76
99
  ------------\n"
77
100
 
78
- r.to_a.sort{|x,y| y[1] <=> x[1] }.take(1000).each do |string,count|
101
+ string_analysis.to_a.sort{|x,y| y[1] <=> x[1] }.take(1000).each do |string,count|
79
102
  body << "#{count} : #{string}\n"
80
103
  end
81
104
 
@@ -7,6 +7,7 @@ module Rack
7
7
  # Root: RequestTimer
8
8
  # :has_many RequestTimer children
9
9
  # :has_many SqlTimer children
10
+ # :has_many CustomTimer children
10
11
  class PageTimerStruct < TimerStruct
11
12
  def initialize(env)
12
13
  super("Id" => MiniProfiler.generate_id,
@@ -27,7 +28,10 @@ module Rack
27
28
  "HasDuplicateSqlTimings" => false,
28
29
  "ExecutedReaders" => 0,
29
30
  "ExecutedScalars" => 0,
30
- "ExecutedNonQueries" => 0)
31
+ "ExecutedNonQueries" => 0,
32
+ "CustomTimingNames" => [],
33
+ "CustomTimingStats" => {}
34
+ )
31
35
  name = "#{env['REQUEST_METHOD']} http://#{env['SERVER_NAME']}:#{env['SERVER_PORT']}#{env['SCRIPT_NAME']}#{env['PATH_INFO']}"
32
36
  self['Root'] = RequestTimerStruct.createRoot(name, self)
33
37
  end
@@ -43,7 +47,8 @@ module Rack
43
47
  def to_json(*a)
44
48
  attribs = @attributes.merge(
45
49
  "Started" => '/Date(%d)/' % @attributes['Started'],
46
- "DurationMilliseconds" => @attributes['Root']['DurationMilliseconds']
50
+ "DurationMilliseconds" => @attributes['Root']['DurationMilliseconds'],
51
+ "CustomTimingNames" => @attributes['CustomTimingStats'].keys.sort
47
52
  )
48
53
  ::JSON.generate(attribs, :max_nesting => 100)
49
54
  end
@@ -2,8 +2,10 @@ require 'json'
2
2
  require 'timeout'
3
3
  require 'thread'
4
4
 
5
+ require 'mini_profiler/version'
5
6
  require 'mini_profiler/page_timer_struct'
6
7
  require 'mini_profiler/sql_timer_struct'
8
+ require 'mini_profiler/custom_timer_struct'
7
9
  require 'mini_profiler/client_timer_struct'
8
10
  require 'mini_profiler/request_timer_struct'
9
11
  require 'mini_profiler/storage/abstract_store'
@@ -16,15 +18,14 @@ require 'mini_profiler/profiling_methods'
16
18
  require 'mini_profiler/context'
17
19
  require 'mini_profiler/client_settings'
18
20
  require 'mini_profiler/gc_profiler'
21
+ require 'mini_profiler/flame_graph'
19
22
 
20
23
  module Rack
21
24
 
22
- class MiniProfiler
25
+ class MiniProfiler
23
26
 
24
- VERSION = '107'.freeze
27
+ class << self
25
28
 
26
- class << self
27
-
28
29
  include Rack::MiniProfiler::ProfilingMethods
29
30
 
30
31
  def generate_id
@@ -44,7 +45,7 @@ module Rack
44
45
  return @share_template unless @share_template.nil?
45
46
  @share_template = ::File.read(::File.expand_path("../html/share.html", ::File.dirname(__FILE__)))
46
47
  end
47
-
48
+
48
49
  def current
49
50
  Thread.current[:mini_profiler_private]
50
51
  end
@@ -78,82 +79,83 @@ module Rack
78
79
  def request_authorized?
79
80
  Thread.current[:mp_authorized]
80
81
  end
82
+
81
83
  end
82
84
 
83
- #
84
- # options:
85
- # :auto_inject - should script be automatically injected on every html page (not xhr)
86
- def initialize(app, config = nil)
85
+ #
86
+ # options:
87
+ # :auto_inject - should script be automatically injected on every html page (not xhr)
88
+ def initialize(app, config = nil)
87
89
  MiniProfiler.config.merge!(config)
88
- @config = MiniProfiler.config
89
- @app = app
90
- @config.base_url_path << "/" unless @config.base_url_path.end_with? "/"
90
+ @config = MiniProfiler.config
91
+ @app = app
92
+ @config.base_url_path << "/" unless @config.base_url_path.end_with? "/"
91
93
  unless @config.storage_instance
92
94
  @config.storage_instance = @config.storage.new(@config.storage_options)
93
95
  end
94
- @storage = @config.storage_instance
95
- end
96
-
96
+ @storage = @config.storage_instance
97
+ end
98
+
97
99
  def user(env)
98
100
  @config.user_provider.call(env)
99
101
  end
100
102
 
101
- def serve_results(env)
102
- request = Rack::Request.new(env)
103
+ def serve_results(env)
104
+ request = Rack::Request.new(env)
103
105
  id = request['id']
104
- page_struct = @storage.load(id)
106
+ page_struct = @storage.load(id)
105
107
  unless page_struct
106
- @storage.set_viewed(user(env), id)
107
- return [404, {}, ["Request not found: #{request['id']} - user #{user(env)}"]]
108
+ @storage.set_viewed(user(env), id)
109
+ return [404, {}, ["Request not found: #{request['id']} - user #{user(env)}"]]
108
110
  end
109
- unless page_struct['HasUserViewed']
111
+ unless page_struct['HasUserViewed']
110
112
  page_struct['ClientTimings'] = ClientTimerStruct.init_from_form_data(env, page_struct)
111
- page_struct['HasUserViewed'] = true
112
- @storage.save(page_struct)
113
- @storage.set_viewed(user(env), id)
114
- end
113
+ page_struct['HasUserViewed'] = true
114
+ @storage.save(page_struct)
115
+ @storage.set_viewed(user(env), id)
116
+ end
115
117
 
116
118
  result_json = page_struct.to_json
117
119
  # If we're an XMLHttpRequest, serve up the contents as JSON
118
120
  if request.xhr?
119
- [200, { 'Content-Type' => 'application/json'}, [result_json]]
121
+ [200, { 'Content-Type' => 'application/json'}, [result_json]]
120
122
  else
121
123
 
122
124
  # Otherwise give the HTML back
123
- html = MiniProfiler.share_template.dup
124
- html.gsub!(/\{path\}/, @config.base_url_path)
125
- html.gsub!(/\{version\}/, MiniProfiler::VERSION)
125
+ html = MiniProfiler.share_template.dup
126
+ html.gsub!(/\{path\}/, @config.base_url_path)
127
+ html.gsub!(/\{version\}/, MiniProfiler::VERSION)
126
128
  html.gsub!(/\{json\}/, result_json)
127
129
  html.gsub!(/\{includes\}/, get_profile_script(env))
128
130
  html.gsub!(/\{name\}/, page_struct['Name'])
129
- html.gsub!(/\{duration\}/, page_struct.duration_ms.round(1).to_s)
130
-
131
+ html.gsub!(/\{duration\}/, "%.1f" % page_struct.duration_ms)
132
+
131
133
  [200, {'Content-Type' => 'text/html'}, [html]]
132
134
  end
133
135
 
134
- end
136
+ end
135
137
 
136
- def serve_html(env)
137
- file_name = env['PATH_INFO'][(@config.base_url_path.length)..1000]
138
- return serve_results(env) if file_name.eql?('results')
139
- full_path = ::File.expand_path("../html/#{file_name}", ::File.dirname(__FILE__))
140
- return [404, {}, ["Not found"]] unless ::File.exists? full_path
141
- f = Rack::File.new nil
142
- f.path = full_path
138
+ def serve_html(env)
139
+ file_name = env['PATH_INFO'][(@config.base_url_path.length)..1000]
140
+ return serve_results(env) if file_name.eql?('results')
141
+ full_path = ::File.expand_path("../html/#{file_name}", ::File.dirname(__FILE__))
142
+ return [404, {}, ["Not found"]] unless ::File.exists? full_path
143
+ f = Rack::File.new nil
144
+ f.path = full_path
143
145
 
144
- begin
146
+ begin
145
147
  f.cache_control = "max-age:86400"
146
148
  f.serving env
147
149
  rescue
148
- # old versions of rack have a different api
150
+ # old versions of rack have a different api
149
151
  status, headers, body = f.serving
150
152
  headers.merge! 'Cache-Control' => "max-age:86400"
151
153
  [status, headers, body]
152
154
  end
153
155
 
154
- end
156
+ end
157
+
155
158
 
156
-
157
159
  def current
158
160
  MiniProfiler.current
159
161
  end
@@ -168,7 +170,7 @@ module Rack
168
170
  end
169
171
 
170
172
 
171
- def call(env)
173
+ def call(env)
172
174
 
173
175
  client_settings = ClientSettings.new(env)
174
176
 
@@ -178,20 +180,20 @@ module Rack
178
180
 
179
181
  skip_it = (@config.pre_authorize_cb && !@config.pre_authorize_cb.call(env)) ||
180
182
  (@config.skip_paths && @config.skip_paths.any?{ |p| path[0,p.length] == p}) ||
181
- query_string =~ /pp=skip/
182
-
183
+ query_string =~ /pp=skip/
184
+
183
185
  has_profiling_cookie = client_settings.has_cookie?
184
-
186
+
185
187
  if skip_it || (@config.authorization_mode == :whitelist && !has_profiling_cookie)
186
188
  status,headers,body = @app.call(env)
187
- if !skip_it && @config.authorization_mode == :whitelist && !has_profiling_cookie && MiniProfiler.request_authorized?
188
- client_settings.write!(headers)
189
+ if !skip_it && @config.authorization_mode == :whitelist && !has_profiling_cookie && MiniProfiler.request_authorized?
190
+ client_settings.write!(headers)
189
191
  end
190
192
  return [status,headers,body]
191
193
  end
192
194
 
193
195
  # handle all /mini-profiler requests here
194
- return serve_html(env) if path.start_with? @config.base_url_path
196
+ return serve_html(env) if path.start_with? @config.base_url_path
195
197
 
196
198
  has_disable_cookie = client_settings.disable_profiling?
197
199
  # manual session disable / enable
@@ -208,10 +210,16 @@ module Rack
208
210
  client_settings.disable_profiling = true
209
211
  client_settings.write!(headers)
210
212
  return [status,headers,body]
213
+ else
214
+ client_settings.disable_profiling = false
211
215
  end
212
216
 
213
217
  if query_string =~ /pp=profile-gc/
214
- return Rack::MiniProfiler::GCProfiler.new.profile_gc(@app, env)
218
+ if query_string =~ /pp=profile-gc-time/
219
+ return Rack::MiniProfiler::GCProfiler.new.profile_gc_time(@app, env)
220
+ else
221
+ return Rack::MiniProfiler::GCProfiler.new.profile_gc(@app, env)
222
+ end
215
223
  end
216
224
 
217
225
  MiniProfiler.create_current(env, @config)
@@ -231,31 +239,26 @@ module Rack
231
239
  done_sampling = false
232
240
  quit_sampler = false
233
241
  backtraces = nil
234
- stacktrace_installed = true
235
- if query_string =~ /pp=sample/
242
+
243
+ if query_string =~ /pp=sample/ || query_string =~ /pp=flamegraph/
244
+ current.measure = false
236
245
  skip_frames = 0
237
246
  backtraces = []
238
247
  t = Thread.current
239
-
240
- begin
241
- require 'stacktrace'
242
- skip_frames = stacktrace.length
243
- rescue LoadError
244
- stacktrace_installed = false
245
- end
246
248
 
247
249
  Thread.new {
250
+ # new in Ruby 2.0
251
+ has_backtrace_locations = t.respond_to?(:backtrace_locations)
248
252
  begin
249
- i = 10000 # for sanity never grab more than 10k samples
253
+ i = 10000 # for sanity never grab more than 10k samples
250
254
  while i > 0
251
255
  break if done_sampling
252
256
  i -= 1
253
- if stacktrace_installed
254
- backtraces << t.stacktrace(0,-(1+skip_frames), StackFrame::Flags::METHOD | StackFrame::Flags::KLASS)
255
- else
256
- backtraces << t.backtrace
257
- end
258
- sleep 0.001
257
+ backtraces << (has_backtrace_locations ? t.backtrace_locations : t.backtrace)
258
+
259
+ # On my machine using Ruby 2.0 this give me excellent fidelity of stack trace per 1.2ms
260
+ # with this fidelity analysis becomes very powerful
261
+ sleep 0.0005
259
262
  end
260
263
  ensure
261
264
  quit_sampler = true
@@ -263,11 +266,11 @@ module Rack
263
266
  }
264
267
  end
265
268
 
266
- status, headers, body = nil
267
- start = Time.now
268
- begin
269
+ status, headers, body = nil
270
+ start = Time.now
271
+ begin
269
272
 
270
- # Strip all the caching headers so we don't get 304s back
273
+ # Strip all the caching headers so we don't get 304s back
271
274
  # This solves a very annoying bug where rack mini profiler never shows up
272
275
  env['HTTP_IF_MODIFIED_SINCE'] = nil
273
276
  env['HTTP_IF_NONE_MATCH'] = nil
@@ -275,17 +278,18 @@ module Rack
275
278
  status,headers,body = @app.call(env)
276
279
  client_settings.write!(headers)
277
280
  ensure
278
- if backtraces
281
+ if backtraces
279
282
  done_sampling = true
280
283
  sleep 0.001 until quit_sampler
281
284
  end
282
285
  end
283
286
 
284
287
  skip_it = current.discard
288
+
285
289
  if (config.authorization_mode == :whitelist && !MiniProfiler.request_authorized?)
286
290
  # this is non-obvious, don't kill the profiling cookie on errors or short requests
287
291
  # this ensures that stuff that never reaches the rails stack does not kill profiling
288
- if status == 200 && ((Time.now - start) > 0.1)
292
+ if status == 200 && ((Time.now - start) > 0.1)
289
293
  client_settings.discard_cookie!(headers)
290
294
  end
291
295
  skip_it = true
@@ -303,43 +307,49 @@ module Rack
303
307
  body.close if body.respond_to? :close
304
308
  return help(client_settings)
305
309
  end
306
-
310
+
307
311
  page_struct = current.page_struct
308
- page_struct['Root'].record_time((Time.now - start) * 1000)
312
+ page_struct['User'] = user(env)
313
+ page_struct['Root'].record_time((Time.now - start) * 1000)
309
314
 
310
315
  if backtraces
311
316
  body.close if body.respond_to? :close
312
- return analyze(backtraces, page_struct)
317
+ if query_string =~ /pp=sample/
318
+ return analyze(backtraces, page_struct)
319
+ else
320
+ return flame_graph(backtraces, page_struct)
321
+ end
313
322
  end
314
-
323
+
315
324
 
316
325
  # no matter what it is, it should be unviewed, otherwise we will miss POST
317
- @storage.set_unviewed(user(env), page_struct['Id'])
318
- @storage.save(page_struct)
319
-
326
+ @storage.set_unviewed(page_struct['User'], page_struct['Id'])
327
+ @storage.save(page_struct)
328
+
320
329
  # inject headers, script
321
- if status == 200
330
+ if status == 200
322
331
 
323
332
  client_settings.write!(headers)
324
-
333
+
325
334
  # mini profiler is meddling with stuff, we can not cache cause we will get incorrect data
326
335
  # Rack::ETag has already inserted some nonesense in the chain
327
336
  headers.delete('ETag')
328
337
  headers.delete('Date')
329
338
  headers['Cache-Control'] = 'must-revalidate, private, max-age=0'
330
339
 
331
- # inject header
340
+ # inject header
332
341
  if headers.is_a? Hash
333
342
  headers['X-MiniProfiler-Ids'] = ids_json(env)
334
343
  end
335
344
 
336
- # inject script
337
- if current.inject_js \
338
- && headers.has_key?('Content-Type') \
339
- && !headers['Content-Type'].match(/text\/html/).nil? then
340
-
345
+ # inject script
346
+ if current.inject_js \
347
+ && headers.has_key?('Content-Type') \
348
+ && !headers['Content-Type'].match(/text\/html/).nil? then
349
+
341
350
  response = Rack::Response.new([], status, headers)
342
351
  script = self.get_profile_script(env)
352
+
343
353
  if String === body
344
354
  response.write inject(body,script)
345
355
  else
@@ -347,34 +357,70 @@ module Rack
347
357
  end
348
358
  body.close if body.respond_to? :close
349
359
  return response.finish
350
- end
351
- end
360
+ end
361
+ end
352
362
 
353
363
  client_settings.write!(headers)
354
- [status, headers, body]
364
+ [status, headers, body]
355
365
  ensure
356
366
  # Make sure this always happens
357
367
  current = nil
358
- end
368
+ end
359
369
 
360
370
  def inject(fragment, script)
361
- fragment.sub(/<\/body>/i) do
362
- # if for whatever crazy reason we dont get a utf string,
363
- # just force the encoding, no utf in the mp scripts anyway
364
- if script.respond_to?(:encoding) && script.respond_to?(:force_encoding)
365
- (script + "</body>").force_encoding(fragment.encoding)
371
+ if fragment.match(/<\/body>/i)
372
+ # explicit </body>
373
+
374
+ regex = /<\/body>/i
375
+ close_tag = '</body>'
376
+ elsif fragment.match(/<\/html>/i)
377
+ # implicit </body>
378
+
379
+ regex = /<\/html>/i
380
+ close_tag = '</html>'
381
+ else
382
+ # implicit </body> and </html>. Just append the script.
383
+
384
+ return fragment + script
385
+ end
386
+
387
+ matches = fragment.scan(regex).length
388
+ index = 1
389
+ fragment.gsub(regex) do
390
+ # though malformed there is an edge case where /body exists earlier in the html, work around
391
+ if index < matches
392
+ index += 1
393
+ close_tag
366
394
  else
367
- script + "</body>"
395
+
396
+ # if for whatever crazy reason we dont get a utf string,
397
+ # just force the encoding, no utf in the mp scripts anyway
398
+ if script.respond_to?(:encoding) && script.respond_to?(:force_encoding)
399
+ (script + close_tag).force_encoding(fragment.encoding)
400
+ else
401
+ script + close_tag
402
+ end
368
403
  end
369
404
  end
370
405
  end
371
406
 
372
407
  def dump_env(env)
373
408
  headers = {'Content-Type' => 'text/plain'}
374
- body = ""
409
+ body = "Rack Environment\n---------------\n"
375
410
  env.each do |k,v|
376
411
  body << "#{k}: #{v}\n"
377
412
  end
413
+
414
+ body << "\n\nEnvironment\n---------------\n"
415
+ ENV.each do |k,v|
416
+ body << "#{k}: #{v}\n"
417
+ end
418
+
419
+ body << "\n\nInternals\n---------------\n"
420
+ body << "Storage Provider #{config.storage_instance}\n"
421
+ body << "User #{user(env)}\n"
422
+ body << config.storage_instance.diagnostics(user(env)) rescue "no diagnostics implemented for storage"
423
+
378
424
  [200, headers, [body]]
379
425
  end
380
426
 
@@ -387,28 +433,42 @@ module Rack
387
433
  pp=skip : skip mini profiler for this request
388
434
  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)
389
435
  pp=normal-backtrace #{"(*) " if client_settings.backtrace_default?}: collect stack traces from all the SQL executed and filter normally
390
- pp=full-backtrace #{"(*) " if client_settings.backtrace_full?}: enable full backtraces for SQL executed (use pp=normal-backtrace to disable)
391
- pp=sample : sample stack traces and return a report isolating heavy usage (experimental works best with the stacktrace gem)
392
- pp=disable : disable profiling for this session
436
+ pp=full-backtrace #{"(*) " if client_settings.backtrace_full?}: enable full backtraces for SQL executed (use pp=normal-backtrace to disable)
437
+ pp=sample : sample stack traces and return a report isolating heavy usage (works best on Ruby 2.0)
438
+ pp=disable : disable profiling for this session
393
439
  pp=enable : enable profiling for this session (if previously disabled)
394
- pp=profile-gc: perform gc profiling on this request (ruby 1.9.3 only)
440
+ pp=profile-gc: perform gc profiling on this request, analyzes ObjectSpace generated by request (ruby 1.9.3 only)
441
+ pp=profile-gc-time: perform built-in gc profiling on this request (ruby 1.9.3 only)
442
+ pp=flamegraph: works best on Ruby 2.0, a graph representing sampled activity.
395
443
  "
396
-
444
+
397
445
  client_settings.write!(headers)
398
446
  [200, headers, [body]]
399
447
  end
400
448
 
449
+ def flame_graph(traces, page_struct)
450
+ graph = FlameGraph.new(traces)
451
+ data = graph.graph_data
452
+
453
+ headers = {'Content-Type' => 'text/html'}
454
+
455
+ body = IO.read(::File.expand_path('../html/flamegraph.html', ::File.dirname(__FILE__)))
456
+ body.gsub!("/*DATA*/", ::JSON.generate(data));
457
+
458
+ [200, headers, [body]]
459
+ end
460
+
401
461
  def analyze(traces, page_struct)
402
462
  headers = {'Content-Type' => 'text/plain'}
403
463
  body = "Collected: #{traces.count} stack traces. Duration(ms): #{page_struct.duration_ms}"
404
464
 
405
465
  seen = {}
406
466
  fulldump = ""
407
- traces.each do |trace|
467
+ traces.each do |trace|
408
468
  fulldump << "\n\n"
409
469
  distinct = {}
410
470
  trace.each do |frame|
411
- frame = "#{frame.klass}::#{frame.method}" unless String === frame
471
+ frame = frame.to_s unless String === frame
412
472
  unless distinct[frame]
413
473
  distinct[frame] = true
414
474
  seen[frame] ||= 0
@@ -424,7 +484,7 @@ module Rack
424
484
  body << "#{name} x #{count}\n"
425
485
  end
426
486
  end
427
-
487
+
428
488
  body << "\n\n\nRaw traces \n"
429
489
  body << fulldump
430
490
 
@@ -437,43 +497,48 @@ module Rack
437
497
  ::JSON.generate(ids.uniq)
438
498
  end
439
499
 
440
- # get_profile_script returns script to be injected inside current html page
441
- # By default, profile_script is appended to the end of all html requests automatically.
442
- # Calling get_profile_script cancels automatic append for the current page
443
- # Use it when:
444
- # * you have disabled auto append behaviour throught :auto_inject => false flag
445
- # * you do not want script to be automatically appended for the current page. You can also call cancel_auto_inject
446
- def get_profile_script(env)
447
- ids = ids_json(env)
448
- path = @config.base_url_path
449
- version = MiniProfiler::VERSION
450
- position = @config.position
451
- showTrivial = false
452
- showChildren = false
453
- maxTracesToShow = 10
454
- showControls = false
455
- currentId = current.page_struct["Id"]
456
- authorized = true
457
- useExistingjQuery = @config.use_existing_jquery
458
- # TODO : cache this snippet
459
- script = IO.read(::File.expand_path('../html/profile_handler.js', ::File.dirname(__FILE__)))
460
- # replace the variables
461
- [:ids, :path, :version, :position, :showTrivial, :showChildren, :maxTracesToShow, :showControls, :currentId, :authorized, :useExistingjQuery].each do |v|
462
- regex = Regexp.new("\\{#{v.to_s}\\}")
463
- script.gsub!(regex, eval(v.to_s).to_s)
464
- end
465
- # replace the '{{' and '}}''
466
- script.gsub!(/\{\{/, '{').gsub!(/\}\}/, '}')
467
- current.inject_js = false
468
- script
469
- end
470
-
471
- # cancels automatic injection of profile script for the current page
472
- def cancel_auto_inject(env)
473
- current.inject_js = false
474
- end
475
-
476
- end
500
+ def ids_comma_separated(env)
501
+ # cap at 10 ids, otherwise there is a chance you can blow the header
502
+ ids = [current.page_struct["Id"]] + (@storage.get_unviewed_ids(user(env)) || [])[0..8]
503
+ ids.join(",")
504
+ end
505
+
506
+ # get_profile_script returns script to be injected inside current html page
507
+ # By default, profile_script is appended to the end of all html requests automatically.
508
+ # Calling get_profile_script cancels automatic append for the current page
509
+ # Use it when:
510
+ # * you have disabled auto append behaviour throught :auto_inject => false flag
511
+ # * you do not want script to be automatically appended for the current page. You can also call cancel_auto_inject
512
+ def get_profile_script(env)
513
+ ids = ids_comma_separated(env)
514
+ path = @config.base_url_path
515
+ version = MiniProfiler::VERSION
516
+ position = @config.position
517
+ showTrivial = false
518
+ showChildren = false
519
+ maxTracesToShow = 10
520
+ showControls = false
521
+ currentId = current.page_struct["Id"]
522
+ authorized = true
523
+ toggleShortcut = @config.toggle_shortcut
524
+ startHidden = @config.start_hidden
525
+ # TODO : cache this snippet
526
+ script = IO.read(::File.expand_path('../html/profile_handler.js', ::File.dirname(__FILE__)))
527
+ # replace the variables
528
+ [:ids, :path, :version, :position, :showTrivial, :showChildren, :maxTracesToShow, :showControls, :currentId, :authorized, :toggleShortcut, :startHidden].each do |v|
529
+ regex = Regexp.new("\\{#{v.to_s}\\}")
530
+ script.gsub!(regex, eval(v.to_s).to_s)
531
+ end
532
+ current.inject_js = false
533
+ script
534
+ end
535
+
536
+ # cancels automatic injection of profile script for the current page
537
+ def cancel_auto_inject(env)
538
+ current.inject_js = false
539
+ end
540
+
541
+ end
477
542
 
478
543
  end
479
544