rack-mini-profiler 0.1.21 → 0.1.26

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.

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