rack-mini-profiler 0.1.20 → 0.1.25

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