rack-mini-profiler 0.1.24 → 0.1.29

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.

@@ -7,15 +7,15 @@ module Rack
7
7
  @attributes.concat vars
8
8
  super(*vars)
9
9
  end
10
-
10
+
11
11
  def self.attributes
12
12
  @attributes
13
13
  end
14
14
 
15
15
  attr_accessor :auto_inject, :base_url_path, :pre_authorize_cb, :position,
16
- :backtrace_remove, :backtrace_includes, :backtrace_ignores, :skip_schema_queries,
16
+ :backtrace_remove, :backtrace_includes, :backtrace_ignores, :skip_schema_queries,
17
17
  :storage, :user_provider, :storage_instance, :storage_options, :skip_paths, :authorization_mode,
18
- :toggle_shortcut, :start_hidden
18
+ :toggle_shortcut, :start_hidden, :backtrace_threshold_ms
19
19
 
20
20
  # Deprecated options
21
21
  attr_accessor :use_existing_jquery
@@ -24,10 +24,10 @@ module Rack
24
24
  new.instance_eval {
25
25
  @auto_inject = true # automatically inject on every html page
26
26
  @base_url_path = "/mini-profiler-resources/"
27
-
27
+
28
28
  # called prior to rack chain, to ensure we are allowed to profile
29
- @pre_authorize_cb = lambda {|env| true}
30
-
29
+ @pre_authorize_cb = lambda {|env| true}
30
+
31
31
  # called after rack chain, to ensure we are REALLY allowed to profile
32
32
  @position = 'left' # Where it is displayed
33
33
  @skip_schema_queries = false
@@ -36,16 +36,17 @@ module Rack
36
36
  @authorization_mode = :allow_all
37
37
  @toggle_shortcut = 'Alt+P'
38
38
  @start_hidden = false
39
+ @backtrace_threshold_ms = 0
39
40
  self
40
41
  }
41
42
  end
42
43
 
43
44
  def merge!(config)
44
45
  return unless config
45
- if Hash === config
46
+ if Hash === config
46
47
  config.each{|k,v| instance_variable_set "@#{k}",v}
47
- else
48
- self.class.attributes.each{ |k|
48
+ else
49
+ self.class.attributes.each{ |k|
49
50
  v = config.send k
50
51
  instance_variable_set "@#{k}", v if v
51
52
  }
@@ -6,9 +6,9 @@ class Rack::MiniProfiler::FlameGraph
6
6
  end
7
7
 
8
8
  def graph_data
9
- height = 0
9
+ height = 0
10
10
 
11
- table = []
11
+ table = []
12
12
  prev = []
13
13
 
14
14
  # a 2d array makes collapsing easy
@@ -17,7 +17,7 @@ class Rack::MiniProfiler::FlameGraph
17
17
 
18
18
  stack.reverse.map{|r| r.to_s}.each_with_index do |frame, i|
19
19
 
20
- if !prev[i].nil?
20
+ if !prev[i].nil?
21
21
  last_col = prev[i]
22
22
  if last_col[0] == frame
23
23
  last_col[1] += 1
@@ -26,7 +26,7 @@ class Rack::MiniProfiler::FlameGraph
26
26
  end
27
27
  end
28
28
 
29
- prev[i] = [frame, 1]
29
+ prev[i] = [frame, 1]
30
30
  col << prev[i]
31
31
  end
32
32
  prev = prev[0..col.length-1].to_a
@@ -123,7 +123,7 @@ module Rack
123
123
 
124
124
  # Otherwise give the HTML back
125
125
  html = MiniProfiler.share_template.dup
126
- html.gsub!(/\{path\}/, @config.base_url_path)
126
+ html.gsub!(/\{path\}/, "#{env['SCRIPT_NAME']}#{@config.base_url_path}")
127
127
  html.gsub!(/\{version\}/, MiniProfiler::VERSION)
128
128
  html.gsub!(/\{json\}/, result_json)
129
129
  html.gsub!(/\{includes\}/, get_profile_script(env))
@@ -224,6 +224,7 @@ module Rack
224
224
 
225
225
  MiniProfiler.create_current(env, @config)
226
226
  MiniProfiler.deauthorize_request if @config.authorization_mode == :whitelist
227
+
227
228
  if query_string =~ /pp=normal-backtrace/
228
229
  client_settings.backtrace_level = ClientSettings::BACKTRACE_DEFAULT
229
230
  elsif query_string =~ /pp=no-backtrace/
@@ -239,7 +240,7 @@ module Rack
239
240
  done_sampling = false
240
241
  quit_sampler = false
241
242
  backtraces = nil
242
-
243
+
243
244
  if query_string =~ /pp=sample/ || query_string =~ /pp=flamegraph/
244
245
  current.measure = false
245
246
  skip_frames = 0
@@ -255,7 +256,7 @@ module Rack
255
256
  break if done_sampling
256
257
  i -= 1
257
258
  backtraces << (has_backtrace_locations ? t.backtrace_locations : t.backtrace)
258
-
259
+
259
260
  # On my machine using Ruby 2.0 this give me excellent fidelity of stack trace per 1.2ms
260
261
  # with this fidelity analysis becomes very powerful
261
262
  sleep 0.0005
@@ -266,18 +267,31 @@ module Rack
266
267
  }
267
268
  end
268
269
 
269
- status, headers, body = nil
270
+ trace_exceptions = query_string =~ /pp=trace-exceptions/ && defined? TracePoint
271
+ status, headers, body, exceptions,trace = nil
272
+
270
273
  start = Time.now
274
+
275
+ if trace_exceptions
276
+ exceptions = []
277
+ trace = TracePoint.new(:raise) do |tp|
278
+ exceptions << tp.raised_exception
279
+ end
280
+ trace.enable
281
+ end
282
+
271
283
  begin
272
284
 
273
285
  # Strip all the caching headers so we don't get 304s back
274
286
  # This solves a very annoying bug where rack mini profiler never shows up
275
- env['HTTP_IF_MODIFIED_SINCE'] = nil
276
- env['HTTP_IF_NONE_MATCH'] = nil
287
+ env['HTTP_IF_MODIFIED_SINCE'] = ''
288
+ env['HTTP_IF_NONE_MATCH'] = ''
277
289
 
278
290
  status,headers,body = @app.call(env)
279
291
  client_settings.write!(headers)
280
292
  ensure
293
+ trace.disable if trace
294
+
281
295
  if backtraces
282
296
  done_sampling = true
283
297
  sleep 0.001 until quit_sampler
@@ -298,6 +312,11 @@ module Rack
298
312
  return [status,headers,body] if skip_it
299
313
 
300
314
  # we must do this here, otherwise current[:discard] is not being properly treated
315
+ if trace_exceptions
316
+ body.close if body.respond_to? :close
317
+ return dump_exceptions exceptions
318
+ end
319
+
301
320
  if query_string =~ /pp=env/
302
321
  body.close if body.respond_to? :close
303
322
  return dump_env env
@@ -316,7 +335,7 @@ module Rack
316
335
  body.close if body.respond_to? :close
317
336
  if query_string =~ /pp=sample/
318
337
  return analyze(backtraces, page_struct)
319
- else
338
+ else
320
339
  return flame_graph(backtraces, page_struct)
321
340
  end
322
341
  end
@@ -327,46 +346,51 @@ module Rack
327
346
  @storage.save(page_struct)
328
347
 
329
348
  # inject headers, script
330
- if status == 200
331
-
349
+ if headers['Content-Type'] && status == 200
332
350
  client_settings.write!(headers)
333
351
 
334
- # mini profiler is meddling with stuff, we can not cache cause we will get incorrect data
335
- # Rack::ETag has already inserted some nonesense in the chain
336
- headers.delete('ETag')
337
- headers.delete('Date')
338
- headers['Cache-Control'] = 'must-revalidate, private, max-age=0'
339
-
340
- # inject header
341
- if headers.is_a? Hash
342
- headers['X-MiniProfiler-Ids'] = ids_json(env)
343
- end
344
-
345
- # inject script
346
- if current.inject_js \
347
- && headers.has_key?('Content-Type') \
348
- && !headers['Content-Type'].match(/text\/html/).nil? then
349
-
350
- response = Rack::Response.new([], status, headers)
351
- script = self.get_profile_script(env)
352
-
353
- if String === body
354
- response.write inject(body,script)
355
- else
356
- body.each { |fragment| response.write inject(fragment, script) }
357
- end
358
- body.close if body.respond_to? :close
359
- return response.finish
360
- end
352
+ result = inject_profiler(env,status,headers,body)
353
+ return result if result
361
354
  end
362
355
 
363
356
  client_settings.write!(headers)
364
357
  [status, headers, body]
358
+
365
359
  ensure
366
360
  # Make sure this always happens
367
361
  current = nil
368
362
  end
369
363
 
364
+ def inject_profiler(env,status,headers,body)
365
+ # mini profiler is meddling with stuff, we can not cache cause we will get incorrect data
366
+ # Rack::ETag has already inserted some nonesense in the chain
367
+ content_type = headers['Content-Type']
368
+
369
+ headers.delete('ETag')
370
+ headers.delete('Date')
371
+ headers['Cache-Control'] = 'must-revalidate, private, max-age=0'
372
+
373
+ # inject header
374
+ if headers.is_a? Hash
375
+ headers['X-MiniProfiler-Ids'] = ids_json(env)
376
+ end
377
+
378
+ if current.inject_js && content_type =~ /text\/html/
379
+ response = Rack::Response.new([], status, headers)
380
+ script = self.get_profile_script(env)
381
+
382
+ if String === body
383
+ response.write inject(body,script)
384
+ else
385
+ body.each { |fragment| response.write inject(fragment, script) }
386
+ end
387
+ body.close if body.respond_to? :close
388
+ response.finish
389
+ else
390
+ nil
391
+ end
392
+ end
393
+
370
394
  def inject(fragment, script)
371
395
  if fragment.match(/<\/body>/i)
372
396
  # explicit </body>
@@ -388,9 +412,9 @@ module Rack
388
412
  index = 1
389
413
  fragment.gsub(regex) do
390
414
  # though malformed there is an edge case where /body exists earlier in the html, work around
391
- if index < matches
415
+ if index < matches
392
416
  index += 1
393
- close_tag
417
+ close_tag
394
418
  else
395
419
 
396
420
  # if for whatever crazy reason we dont get a utf string,
@@ -404,6 +428,16 @@ module Rack
404
428
  end
405
429
  end
406
430
 
431
+ def dump_exceptions(exceptions)
432
+ headers = {'Content-Type' => 'text/plain'}
433
+ body = "Exceptions (#{exceptions.length} raised during request)\n\n"
434
+ exceptions.each do |e|
435
+ body << "#{e.class} #{e.message}\n#{e.backtrace.join("\n")}\n\n\n\n"
436
+ end
437
+
438
+ [200, headers, [body]]
439
+ end
440
+
407
441
  def dump_env(env)
408
442
  headers = {'Content-Type' => 'text/plain'}
409
443
  body = "Rack Environment\n---------------\n"
@@ -440,6 +474,7 @@ module Rack
440
474
  pp=profile-gc: perform gc profiling on this request, analyzes ObjectSpace generated by request (ruby 1.9.3 only)
441
475
  pp=profile-gc-time: perform built-in gc profiling on this request (ruby 1.9.3 only)
442
476
  pp=flamegraph: works best on Ruby 2.0, a graph representing sampled activity.
477
+ pp=trace-exceptions: requires Ruby 2.0, will return all the spots where your application raises execptions
443
478
  "
444
479
 
445
480
  client_settings.write!(headers)
@@ -451,7 +486,7 @@ module Rack
451
486
  data = graph.graph_data
452
487
 
453
488
  headers = {'Content-Type' => 'text/html'}
454
-
489
+
455
490
  body = IO.read(::File.expand_path('../html/flamegraph.html', ::File.dirname(__FILE__)))
456
491
  body.gsub!("/*DATA*/", ::JSON.generate(data));
457
492
 
@@ -511,7 +546,7 @@ module Rack
511
546
  # * you do not want script to be automatically appended for the current page. You can also call cancel_auto_inject
512
547
  def get_profile_script(env)
513
548
  ids = ids_comma_separated(env)
514
- path = @config.base_url_path
549
+ path = "#{env['SCRIPT_NAME']}#{@config.base_url_path}"
515
550
  version = MiniProfiler::VERSION
516
551
  position = @config.position
517
552
  showTrivial = false
@@ -8,7 +8,7 @@ module Rack
8
8
  def initialize(query, duration_ms, page, parent, skip_backtrace = false, full_backtrace = false)
9
9
 
10
10
  stack_trace = nil
11
- unless skip_backtrace
11
+ unless skip_backtrace || duration_ms < Rack::MiniProfiler.config.backtrace_threshold_ms
12
12
  # Allow us to filter the stack trace
13
13
  stack_trace = ""
14
14
  # Clean up the stack trace if there are options to do so
@@ -1,7 +1,7 @@
1
1
  module Rack
2
2
  class MiniProfiler
3
3
  class AbstractStore
4
-
4
+
5
5
  def save(page_struct)
6
6
  raise NotImplementedError.new("save is not implemented")
7
7
  end
@@ -23,9 +23,10 @@ module Rack
23
23
  end
24
24
 
25
25
  def diagnostics(user)
26
- raise NotImplementedError.new("diagnostics not implemented")
26
+ # this is opt in, no need to explode if not implemented
27
+ ""
27
28
  end
28
-
29
+
29
30
  end
30
31
  end
31
32
  end
@@ -3,7 +3,7 @@ module Rack
3
3
  class RedisStore < AbstractStore
4
4
 
5
5
  EXPIRES_IN_SECONDS = 60 * 60 * 24
6
-
6
+
7
7
  def initialize(args = nil)
8
8
  @args = args || {}
9
9
  @prefix = @args.delete(:prefix) || 'MPRedisStore'
@@ -12,7 +12,7 @@ module Rack
12
12
  end
13
13
 
14
14
  def save(page_struct)
15
- redis.setex "#{@prefix}#{page_struct['Id']}", @expires_in_seconds, Marshal::dump(page_struct)
15
+ redis.setex "#{@prefix}#{page_struct['Id']}", @expires_in_seconds, Marshal::dump(page_struct)
16
16
  end
17
17
 
18
18
  def load(id)
@@ -41,7 +41,7 @@ unviewed_ids: #{get_unviewed_ids(user)}
41
41
  "
42
42
  end
43
43
 
44
- private
44
+ private
45
45
 
46
46
  def redis
47
47
  return @redis_connection if @redis_connection
@@ -1,5 +1,5 @@
1
1
  module Rack
2
2
  class MiniProfiler
3
- VERSION = '843cab636fb537adf19b58eff230ff75'.freeze
3
+ VERSION = '8ceae04dd2abbc08ffe85a258d408292'.freeze
4
4
  end
5
5
  end
@@ -1,61 +1,65 @@
1
1
  require 'fileutils'
2
2
 
3
- module MiniProfilerRails
3
+ module Rack::MiniProfilerRails
4
4
 
5
- class Railtie < ::Rails::Railtie
5
+ # call direct if needed to do a defer init
6
+ def self.initialize!(app)
7
+ c = Rack::MiniProfiler.config
6
8
 
7
- initializer "rack_mini_profiler.configure_rails_initialization" do |app|
8
- c = Rack::MiniProfiler.config
9
+ # By default, only show the MiniProfiler in development mode, in production allow profiling if post_authorize_cb is set
10
+ c.pre_authorize_cb = lambda { |env|
11
+ !Rails.env.test?
12
+ }
9
13
 
10
- # By default, only show the MiniProfiler in development mode, in production allow profiling if post_authorize_cb is set
11
- c.pre_authorize_cb = lambda { |env|
12
- !Rails.env.test?
13
- }
14
+ c.skip_paths ||= []
14
15
 
15
- c.skip_paths ||= []
16
+ if Rails.env.development?
17
+ c.skip_paths << "/assets/"
18
+ c.skip_schema_queries = true
19
+ end
16
20
 
17
- if Rails.env.development?
18
- c.skip_paths << "/assets/"
19
- c.skip_schema_queries = true
20
- end
21
+ if Rails.env.production?
22
+ c.authorization_mode = :whitelist
23
+ end
21
24
 
22
- if Rails.env.production?
23
- c.authorization_mode = :whitelist
24
- end
25
+ # The file store is just so much less flaky
26
+ tmp = Rails.root.to_s + "/tmp/miniprofiler"
27
+ FileUtils.mkdir_p(tmp) unless File.exists?(tmp)
25
28
 
26
- # The file store is just so much less flaky
27
- tmp = Rails.root.to_s + "/tmp/miniprofiler"
28
- FileUtils.mkdir_p(tmp) unless File.exists?(tmp)
29
+ c.storage_options = {:path => tmp}
30
+ c.storage = Rack::MiniProfiler::FileStore
29
31
 
30
- c.storage_options = {:path => tmp}
31
- c.storage = Rack::MiniProfiler::FileStore
32
+ # Quiet the SQL stack traces
33
+ c.backtrace_remove = Rails.root.to_s + "/"
34
+ c.backtrace_includes = [/^\/?(app|config|lib|test)/]
35
+ c.skip_schema_queries = Rails.env != 'production'
32
36
 
33
- # Quiet the SQL stack traces
34
- c.backtrace_remove = Rails.root.to_s + "/"
35
- c.backtrace_includes = [/^\/?(app|config|lib|test)/]
36
- c.skip_schema_queries = Rails.env != 'production'
37
+ # Install the Middleware
38
+ app.middleware.insert(0, Rack::MiniProfiler)
37
39
 
38
- # Install the Middleware
39
- app.middleware.insert(0, Rack::MiniProfiler)
40
+ # Attach to various Rails methods
41
+ ::Rack::MiniProfiler.profile_method(ActionController::Base, :process) {|action| "Executing action: #{action}"}
42
+ ::Rack::MiniProfiler.profile_method(ActionView::Template, :render) {|x,y| "Rendering: #{@virtual_path}"}
43
+ end
40
44
 
41
- # Attach to various Rails methods
42
- ::Rack::MiniProfiler.profile_method(ActionController::Base, :process) {|action| "Executing action: #{action}"}
43
- ::Rack::MiniProfiler.profile_method(ActionView::Template, :render) {|x,y| "Rendering: #{@virtual_path}"}
45
+ class Railtie < ::Rails::Railtie
44
46
 
47
+ initializer "rack_mini_profiler.configure_rails_initialization" do |app|
48
+ Rack::MiniProfilerRails.initialize!(app)
45
49
  end
46
50
 
47
51
  # TODO: Implement something better here
48
- # config.after_initialize do
49
- #
50
- # class ::ActionView::Helpers::AssetTagHelper::JavascriptIncludeTag
52
+ # config.after_initialize do
53
+ #
54
+ # class ::ActionView::Helpers::AssetTagHelper::JavascriptIncludeTag
51
55
  # alias_method :asset_tag_orig, :asset_tag
52
56
  # def asset_tag(source,options)
53
- # current = Rack::MiniProfiler.current
54
- # return asset_tag_orig(source,options) unless current
57
+ # current = Rack::MiniProfiler.current
58
+ # return asset_tag_orig(source,options) unless current
55
59
  # wrapped = ""
56
60
  # unless current.mpt_init
57
61
  # current.mpt_init = true
58
- # wrapped << Rack::MiniProfiler::ClientTimerStruct.init_instrumentation
62
+ # wrapped << Rack::MiniProfiler::ClientTimerStruct.init_instrumentation
59
63
  # end
60
64
  # name = source.split('/')[-1]
61
65
  # wrapped << Rack::MiniProfiler::ClientTimerStruct.instrument(name, asset_tag_orig(source,options)).html_safe
@@ -63,15 +67,15 @@ module MiniProfilerRails
63
67
  # end
64
68
  # end
65
69
 
66
- # class ::ActionView::Helpers::AssetTagHelper::StylesheetIncludeTag
70
+ # class ::ActionView::Helpers::AssetTagHelper::StylesheetIncludeTag
67
71
  # alias_method :asset_tag_orig, :asset_tag
68
72
  # def asset_tag(source,options)
69
- # current = Rack::MiniProfiler.current
70
- # return asset_tag_orig(source,options) unless current
73
+ # current = Rack::MiniProfiler.current
74
+ # return asset_tag_orig(source,options) unless current
71
75
  # wrapped = ""
72
76
  # unless current.mpt_init
73
77
  # current.mpt_init = true
74
- # wrapped << Rack::MiniProfiler::ClientTimerStruct.init_instrumentation
78
+ # wrapped << Rack::MiniProfiler::ClientTimerStruct.init_instrumentation
75
79
  # end
76
80
  # name = source.split('/')[-1]
77
81
  # wrapped << Rack::MiniProfiler::ClientTimerStruct.instrument(name, asset_tag_orig(source,options)).html_safe