rack-mini-profiler 0.1.26 → 0.1.31

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

Potentially problematic release.


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

@@ -1,6 +1,6 @@
1
1
  class Rack::MiniProfiler::Context
2
2
  attr_accessor :inject_js,:current_timer,:page_struct,:skip_backtrace,:full_backtrace,:discard, :mpt_init, :measure
3
-
3
+
4
4
  def initialize(opts = {})
5
5
  opts["measure"] = true unless opts.key? "measure"
6
6
  opts.each do |k,v|
@@ -0,0 +1,40 @@
1
+ require 'objspace'
2
+
3
+ class Rack::MiniProfiler::GCProfilerRubyHead
4
+ def profile_rack(app,env)
5
+ end
6
+
7
+ def profile(&block)
8
+ GC.start
9
+ GC.disable
10
+
11
+ items = []
12
+ objs = []
13
+
14
+ ObjectSpace.trace_object_allocations do
15
+ block.call
16
+
17
+ ObjectSpace.each_object do |o|
18
+ objs << o
19
+ end
20
+
21
+ objs.each do |o|
22
+ g = ObjectSpace.allocation_generation(o)
23
+ if g
24
+ l = ObjectSpace.allocation_sourceline(o)
25
+ f = ObjectSpace.allocation_sourcefile(o)
26
+ c = ObjectSpace.allocation_class_path(o)
27
+ m = ObjectSpace.allocation_method_id(o)
28
+ items << "Allocated #{c} in #{m} #{f}:#{l}"
29
+ end
30
+ end
31
+ end
32
+
33
+ items.group_by{|x| x}.sort{|a,b| b[1].length <=> a[1].length}.each do |row, group|
34
+ puts "#{row} x #{group.length}"
35
+ end
36
+
37
+ GC.enable
38
+ profile_allocations(name, &block)
39
+ end
40
+ end
@@ -43,16 +43,16 @@ module Rack
43
43
  def root
44
44
  @attributes['Root']
45
45
  end
46
-
46
+
47
47
  def to_json(*a)
48
48
  attribs = @attributes.merge(
49
- "Started" => '/Date(%d)/' % @attributes['Started'],
49
+ "Started" => '/Date(%d)/' % @attributes['Started'],
50
50
  "DurationMilliseconds" => @attributes['Root']['DurationMilliseconds'],
51
51
  "CustomTimingNames" => @attributes['CustomTimingStats'].keys.sort
52
- )
52
+ )
53
53
  ::JSON.generate(attribs, :max_nesting => 100)
54
54
  end
55
55
  end
56
-
56
+
57
57
  end
58
58
  end
@@ -18,7 +18,8 @@ require 'mini_profiler/profiling_methods'
18
18
  require 'mini_profiler/context'
19
19
  require 'mini_profiler/client_settings'
20
20
  require 'mini_profiler/gc_profiler'
21
- require 'mini_profiler/flame_graph'
21
+ # TODO
22
+ # require 'mini_profiler/gc_profiler_ruby_head' if Gem::Version.new('2.1.0') <= Gem::Version.new(RUBY_VERSION)
22
23
 
23
24
  module Rack
24
25
 
@@ -123,7 +124,7 @@ module Rack
123
124
 
124
125
  # Otherwise give the HTML back
125
126
  html = MiniProfiler.share_template.dup
126
- html.gsub!(/\{path\}/, @config.base_url_path)
127
+ html.gsub!(/\{path\}/, "#{env['SCRIPT_NAME']}#{@config.base_url_path}")
127
128
  html.gsub!(/\{version\}/, MiniProfiler::VERSION)
128
129
  html.gsub!(/\{json\}/, result_json)
129
130
  html.gsub!(/\{includes\}/, get_profile_script(env))
@@ -224,6 +225,7 @@ module Rack
224
225
 
225
226
  MiniProfiler.create_current(env, @config)
226
227
  MiniProfiler.deauthorize_request if @config.authorization_mode == :whitelist
228
+
227
229
  if query_string =~ /pp=normal-backtrace/
228
230
  client_settings.backtrace_level = ClientSettings::BACKTRACE_DEFAULT
229
231
  elsif query_string =~ /pp=no-backtrace/
@@ -236,52 +238,48 @@ module Rack
236
238
  current.skip_backtrace = true
237
239
  end
238
240
 
239
- done_sampling = false
240
- quit_sampler = false
241
- backtraces = nil
242
-
243
- if query_string =~ /pp=sample/ || query_string =~ /pp=flamegraph/
244
- current.measure = false
245
- skip_frames = 0
246
- backtraces = []
247
- t = Thread.current
248
-
249
- Thread.new {
250
- # new in Ruby 2.0
251
- has_backtrace_locations = t.respond_to?(:backtrace_locations)
252
- begin
253
- i = 10000 # for sanity never grab more than 10k samples
254
- while i > 0
255
- break if done_sampling
256
- i -= 1
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
262
- end
263
- ensure
264
- quit_sampler = true
265
- end
266
- }
267
- end
241
+ flamegraph = nil
242
+
243
+ trace_exceptions = query_string =~ /pp=trace-exceptions/ && defined? TracePoint
244
+ status, headers, body, exceptions,trace = nil
268
245
 
269
- status, headers, body = nil
270
246
  start = Time.now
247
+
248
+ if trace_exceptions
249
+ exceptions = []
250
+ trace = TracePoint.new(:raise) do |tp|
251
+ exceptions << tp.raised_exception
252
+ end
253
+ trace.enable
254
+ end
255
+
271
256
  begin
272
257
 
273
258
  # Strip all the caching headers so we don't get 304s back
274
259
  # 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
260
+ env['HTTP_IF_MODIFIED_SINCE'] = ''
261
+ env['HTTP_IF_NONE_MATCH'] = ''
277
262
 
278
- status,headers,body = @app.call(env)
263
+ if query_string =~ /pp=flamegraph/
264
+ unless defined?(Flamegraph) && Flamegraph.respond_to?(:generate)
265
+
266
+ flamegraph = "Please install the flamegraph gem and require it: add gem 'flamegraph' to your Gemfile"
267
+ status,headers,body = @app.call(env)
268
+ else
269
+ # do not sully our profile with mini profiler timings
270
+ current.measure = false
271
+ # first param is the path
272
+ # 0.5 means attempt to collect a sample each 0.5 secs
273
+ flamegraph = Flamegraph.generate(nil, fidelity: 0.5) do
274
+ status,headers,body = @app.call(env)
275
+ end
276
+ end
277
+ else
278
+ status,headers,body = @app.call(env)
279
+ end
279
280
  client_settings.write!(headers)
280
281
  ensure
281
- if backtraces
282
- done_sampling = true
283
- sleep 0.001 until quit_sampler
284
- end
282
+ trace.disable if trace
285
283
  end
286
284
 
287
285
  skip_it = current.discard
@@ -298,6 +296,11 @@ module Rack
298
296
  return [status,headers,body] if skip_it
299
297
 
300
298
  # we must do this here, otherwise current[:discard] is not being properly treated
299
+ if trace_exceptions
300
+ body.close if body.respond_to? :close
301
+ return dump_exceptions exceptions
302
+ end
303
+
301
304
  if query_string =~ /pp=env/
302
305
  body.close if body.respond_to? :close
303
306
  return dump_env env
@@ -312,13 +315,9 @@ module Rack
312
315
  page_struct['User'] = user(env)
313
316
  page_struct['Root'].record_time((Time.now - start) * 1000)
314
317
 
315
- if backtraces
318
+ if flamegraph
316
319
  body.close if body.respond_to? :close
317
- if query_string =~ /pp=sample/
318
- return analyze(backtraces, page_struct)
319
- else
320
- return flame_graph(backtraces, page_struct)
321
- end
320
+ return self.flamegraph(flamegraph)
322
321
  end
323
322
 
324
323
 
@@ -327,46 +326,51 @@ module Rack
327
326
  @storage.save(page_struct)
328
327
 
329
328
  # inject headers, script
330
- if status == 200
331
-
329
+ if headers['Content-Type'] && status == 200
332
330
  client_settings.write!(headers)
333
331
 
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
332
+ result = inject_profiler(env,status,headers,body)
333
+ return result if result
361
334
  end
362
335
 
363
336
  client_settings.write!(headers)
364
337
  [status, headers, body]
338
+
365
339
  ensure
366
340
  # Make sure this always happens
367
341
  current = nil
368
342
  end
369
343
 
344
+ def inject_profiler(env,status,headers,body)
345
+ # mini profiler is meddling with stuff, we can not cache cause we will get incorrect data
346
+ # Rack::ETag has already inserted some nonesense in the chain
347
+ content_type = headers['Content-Type']
348
+
349
+ headers.delete('ETag')
350
+ headers.delete('Date')
351
+ headers['Cache-Control'] = 'must-revalidate, private, max-age=0'
352
+
353
+ # inject header
354
+ if headers.is_a? Hash
355
+ headers['X-MiniProfiler-Ids'] = ids_json(env)
356
+ end
357
+
358
+ if current.inject_js && content_type =~ /text\/html/
359
+ response = Rack::Response.new([], status, headers)
360
+ script = self.get_profile_script(env)
361
+
362
+ if String === body
363
+ response.write inject(body,script)
364
+ else
365
+ body.each { |fragment| response.write inject(fragment, script) }
366
+ end
367
+ body.close if body.respond_to? :close
368
+ response.finish
369
+ else
370
+ nil
371
+ end
372
+ end
373
+
370
374
  def inject(fragment, script)
371
375
  if fragment.match(/<\/body>/i)
372
376
  # explicit </body>
@@ -388,9 +392,9 @@ module Rack
388
392
  index = 1
389
393
  fragment.gsub(regex) do
390
394
  # though malformed there is an edge case where /body exists earlier in the html, work around
391
- if index < matches
395
+ if index < matches
392
396
  index += 1
393
- close_tag
397
+ close_tag
394
398
  else
395
399
 
396
400
  # if for whatever crazy reason we dont get a utf string,
@@ -404,6 +408,16 @@ module Rack
404
408
  end
405
409
  end
406
410
 
411
+ def dump_exceptions(exceptions)
412
+ headers = {'Content-Type' => 'text/plain'}
413
+ body = "Exceptions (#{exceptions.length} raised during request)\n\n"
414
+ exceptions.each do |e|
415
+ body << "#{e.class} #{e.message}\n#{e.backtrace.join("\n")}\n\n\n\n"
416
+ end
417
+
418
+ [200, headers, [body]]
419
+ end
420
+
407
421
  def dump_env(env)
408
422
  headers = {'Content-Type' => 'text/plain'}
409
423
  body = "Rack Environment\n---------------\n"
@@ -416,6 +430,9 @@ module Rack
416
430
  body << "#{k}: #{v}\n"
417
431
  end
418
432
 
433
+ body << "\n\nRuby Version\n---------------\n"
434
+ body << "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL}\n"
435
+
419
436
  body << "\n\nInternals\n---------------\n"
420
437
  body << "Storage Provider #{config.storage_instance}\n"
421
438
  body << "User #{user(env)}\n"
@@ -434,61 +451,21 @@ module Rack
434
451
  pp=no-backtrace #{"(*) " if client_settings.backtrace_none?}: don't collect stack traces from all the SQL executed (sticky, use pp=normal-backtrace to enable)
435
452
  pp=normal-backtrace #{"(*) " if client_settings.backtrace_default?}: collect stack traces from all the SQL executed and filter normally
436
453
  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
454
  pp=disable : disable profiling for this session
439
455
  pp=enable : enable profiling for this session (if previously disabled)
440
456
  pp=profile-gc: perform gc profiling on this request, analyzes ObjectSpace generated by request (ruby 1.9.3 only)
441
457
  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.
458
+ pp=flamegraph: works best on Ruby 2.0, a graph representing sampled activity (requires the flamegraph gem).
459
+ pp=trace-exceptions: requires Ruby 2.0, will return all the spots where your application raises execptions
443
460
  "
444
461
 
445
462
  client_settings.write!(headers)
446
463
  [200, headers, [body]]
447
464
  end
448
465
 
449
- def flame_graph(traces, page_struct)
450
- graph = FlameGraph.new(traces)
451
- data = graph.graph_data
452
-
466
+ def flamegraph(graph)
453
467
  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
-
461
- def analyze(traces, page_struct)
462
- headers = {'Content-Type' => 'text/plain'}
463
- body = "Collected: #{traces.count} stack traces. Duration(ms): #{page_struct.duration_ms}"
464
-
465
- seen = {}
466
- fulldump = ""
467
- traces.each do |trace|
468
- fulldump << "\n\n"
469
- distinct = {}
470
- trace.each do |frame|
471
- frame = frame.to_s unless String === frame
472
- unless distinct[frame]
473
- distinct[frame] = true
474
- seen[frame] ||= 0
475
- seen[frame] += 1
476
- end
477
- fulldump << frame << "\n"
478
- end
479
- end
480
-
481
- body << "\n\nStack Trace Analysis\n"
482
- seen.to_a.sort{|x,y| y[1] <=> x[1]}.each do |name, count|
483
- if count > traces.count / 10
484
- body << "#{name} x #{count}\n"
485
- end
486
- end
487
-
488
- body << "\n\n\nRaw traces \n"
489
- body << fulldump
490
-
491
- [200, headers, [body]]
468
+ [200, headers, [graph]]
492
469
  end
493
470
 
494
471
  def ids_json(env)
@@ -511,7 +488,7 @@ module Rack
511
488
  # * you do not want script to be automatically appended for the current page. You can also call cancel_auto_inject
512
489
  def get_profile_script(env)
513
490
  ids = ids_comma_separated(env)
514
- path = @config.base_url_path
491
+ path = "#{env['SCRIPT_NAME']}#{@config.base_url_path}"
515
492
  version = MiniProfiler::VERSION
516
493
  position = @config.position
517
494
  showTrivial = false
@@ -55,8 +55,16 @@ module Rack
55
55
  end
56
56
  end
57
57
 
58
- def profile_method(klass, method, &blk)
59
- default_name = klass.to_s + " " + method.to_s
58
+ def counter_method(klass, method, &blk)
59
+ self.profile_method(klass, method, :counter, &blk)
60
+ end
61
+
62
+ def uncounter_method(klass, method)
63
+ self.unprofile_method(klass, method)
64
+ end
65
+
66
+ def profile_method(klass, method, type = :profile, &blk)
67
+ default_name = type==:counter ? method.to_s : klass.to_s + " " + method.to_s
60
68
  clean = clean_method_name(method)
61
69
 
62
70
  with_profiling = ("#{clean}_with_mini_profiler").intern
@@ -81,22 +89,34 @@ module Rack
81
89
  end
82
90
  end
83
91
 
84
- parent_timer = Rack::MiniProfiler.current.current_timer
85
- page_struct = Rack::MiniProfiler.current.page_struct
86
92
  result = nil
93
+ parent_timer = Rack::MiniProfiler.current.current_timer
87
94
 
88
- Rack::MiniProfiler.current.current_timer = current_timer = parent_timer.add_child(name)
89
- begin
90
- result = self.send without_profiling, *args, &orig
91
- ensure
92
- current_timer.record_time
93
- Rack::MiniProfiler.current.current_timer = parent_timer
95
+ if type == :counter
96
+ start = Time.now
97
+ begin
98
+ result = self.send without_profiling, *args, &orig
99
+ ensure
100
+ duration_ms = (Time.now - start).to_f * 1000
101
+ parent_timer.add_custom(name, duration_ms, Rack::MiniProfiler.current.page_struct )
102
+ end
103
+ else
104
+ page_struct = Rack::MiniProfiler.current.page_struct
105
+
106
+ Rack::MiniProfiler.current.current_timer = current_timer = parent_timer.add_child(name)
107
+ begin
108
+ result = self.send without_profiling, *args, &orig
109
+ ensure
110
+ current_timer.record_time
111
+ Rack::MiniProfiler.current.current_timer = parent_timer
112
+ end
94
113
  end
114
+
95
115
  result
96
116
  end
97
117
  klass.send :alias_method, method, with_profiling
98
118
  end
99
-
119
+
100
120
  # Add a custom timing. These are displayed similar to SQL/query time in
101
121
  # columns expanding to the right.
102
122
  #