rack-mini-profiler 0.1.28 → 0.9.0

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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/{Ruby/CHANGELOG → CHANGELOG} +27 -0
  3. data/{Ruby/README.md → README.md} +73 -31
  4. data/{Ruby/lib → lib}/html/includes.css +0 -0
  5. data/{Ruby/lib → lib}/html/includes.js +9 -8
  6. data/{Ruby/lib → lib}/html/includes.less +0 -0
  7. data/{Ruby/lib → lib}/html/includes.tmpl +3 -1
  8. data/{Ruby/lib → lib}/html/jquery.1.7.1.js +0 -0
  9. data/{Ruby/lib → lib}/html/jquery.tmpl.js +0 -0
  10. data/{Ruby/lib → lib}/html/list.css +2 -2
  11. data/{Ruby/lib → lib}/html/list.js +1 -1
  12. data/{Ruby/lib → lib}/html/list.tmpl +2 -2
  13. data/lib/html/profile_handler.js +1 -0
  14. data/{Ruby/lib → lib}/html/share.html +2 -2
  15. data/{Ruby/lib → lib}/mini_profiler/client_settings.rb +11 -11
  16. data/{Ruby/lib → lib}/mini_profiler/client_timer_struct.rb +0 -0
  17. data/{Ruby/lib → lib}/mini_profiler/config.rb +11 -4
  18. data/{Ruby/lib → lib}/mini_profiler/context.rb +1 -1
  19. data/{Ruby/lib → lib}/mini_profiler/custom_timer_struct.rb +0 -0
  20. data/lib/mini_profiler/gc_profiler.rb +181 -0
  21. data/{Ruby/lib → lib}/mini_profiler/page_timer_struct.rb +4 -4
  22. data/{Ruby/lib → lib}/mini_profiler/profiler.rb +165 -142
  23. data/{Ruby/lib → lib}/mini_profiler/profiling_methods.rb +31 -11
  24. data/{Ruby/lib → lib}/mini_profiler/request_timer_struct.rb +5 -5
  25. data/{Ruby/lib → lib}/mini_profiler/sql_timer_struct.rb +0 -0
  26. data/{Ruby/lib → lib}/mini_profiler/storage/abstract_store.rb +0 -0
  27. data/{Ruby/lib → lib}/mini_profiler/storage/file_store.rb +26 -4
  28. data/{Ruby/lib → lib}/mini_profiler/storage/memcache_store.rb +0 -0
  29. data/{Ruby/lib → lib}/mini_profiler/storage/memory_store.rb +25 -4
  30. data/{Ruby/lib → lib}/mini_profiler/storage/redis_store.rb +0 -0
  31. data/{Ruby/lib → lib}/mini_profiler/timer_struct.rb +0 -0
  32. data/lib/mini_profiler/version.rb +5 -0
  33. data/{Ruby/lib → lib}/mini_profiler_rails/railtie.rb +6 -2
  34. data/{Ruby/lib → lib}/patches/net_patches.rb +0 -0
  35. data/{Ruby/lib → lib}/patches/sql_patches.rb +3 -2
  36. data/{Ruby/lib → lib}/rack-mini-profiler.rb +0 -0
  37. data/rack-mini-profiler.gemspec +14 -6
  38. metadata +153 -43
  39. data/Ruby/lib/html/flamegraph.html +0 -351
  40. data/Ruby/lib/html/profile_handler.js +0 -1
  41. data/Ruby/lib/mini_profiler/flame_graph.rb +0 -54
  42. data/Ruby/lib/mini_profiler/gc_profiler.rb +0 -107
  43. data/Ruby/lib/mini_profiler/version.rb +0 -5
@@ -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
 
@@ -137,7 +138,9 @@ module Rack
137
138
 
138
139
  def serve_html(env)
139
140
  file_name = env['PATH_INFO'][(@config.base_url_path.length)..1000]
141
+
140
142
  return serve_results(env) if file_name.eql?('results')
143
+
141
144
  full_path = ::File.expand_path("../html/#{file_name}", ::File.dirname(__FILE__))
142
145
  return [404, {}, ["Not found"]] unless ::File.exists? full_path
143
146
  f = Rack::File.new nil
@@ -201,11 +204,12 @@ module Rack
201
204
  skip_it = true
202
205
  end
203
206
 
204
- if query_string =~ /pp=enable/
207
+ if query_string =~ /pp=enable/ && (@config.authorization_mode != :whitelist || MiniProfiler.request_authorized?)
205
208
  skip_it = false
209
+ config.enabled = true
206
210
  end
207
211
 
208
- if skip_it
212
+ if skip_it || !config.enabled
209
213
  status,headers,body = @app.call(env)
210
214
  client_settings.disable_profiling = true
211
215
  client_settings.write!(headers)
@@ -215,8 +219,18 @@ module Rack
215
219
  end
216
220
 
217
221
  if query_string =~ /pp=profile-gc/
222
+ current.measure = false if current
223
+
218
224
  if query_string =~ /pp=profile-gc-time/
219
225
  return Rack::MiniProfiler::GCProfiler.new.profile_gc_time(@app, env)
226
+ elsif query_string =~ /pp=profile-gc-ruby-head/
227
+ result = StringIO.new
228
+ report = MemoryProfiler.report do
229
+ _,_,body = @app.call(env)
230
+ body.close if body.respond_to? :close
231
+ end
232
+ report.pretty_print(result)
233
+ return text_result(result.string)
220
234
  else
221
235
  return Rack::MiniProfiler::GCProfiler.new.profile_gc(@app, env)
222
236
  end
@@ -237,38 +251,21 @@ module Rack
237
251
  current.skip_backtrace = true
238
252
  end
239
253
 
240
- done_sampling = false
241
- quit_sampler = false
242
- backtraces = nil
243
-
244
- if query_string =~ /pp=sample/ || query_string =~ /pp=flamegraph/
245
- current.measure = false
246
- skip_frames = 0
247
- backtraces = []
248
- t = Thread.current
249
-
250
- Thread.new {
251
- # new in Ruby 2.0
252
- has_backtrace_locations = t.respond_to?(:backtrace_locations)
253
- begin
254
- i = 10000 # for sanity never grab more than 10k samples
255
- while i > 0
256
- break if done_sampling
257
- i -= 1
258
- backtraces << (has_backtrace_locations ? t.backtrace_locations : t.backtrace)
259
-
260
- # On my machine using Ruby 2.0 this give me excellent fidelity of stack trace per 1.2ms
261
- # with this fidelity analysis becomes very powerful
262
- sleep 0.0005
263
- end
264
- ensure
265
- quit_sampler = true
266
- end
267
- }
268
- end
254
+ flamegraph = nil
255
+
256
+ trace_exceptions = query_string =~ /pp=trace-exceptions/ && defined? TracePoint
257
+ status, headers, body, exceptions,trace = nil
269
258
 
270
- status, headers, body = nil
271
259
  start = Time.now
260
+
261
+ if trace_exceptions
262
+ exceptions = []
263
+ trace = TracePoint.new(:raise) do |tp|
264
+ exceptions << tp.raised_exception
265
+ end
266
+ trace.enable
267
+ end
268
+
272
269
  begin
273
270
 
274
271
  # Strip all the caching headers so we don't get 304s back
@@ -276,13 +273,33 @@ module Rack
276
273
  env['HTTP_IF_MODIFIED_SINCE'] = ''
277
274
  env['HTTP_IF_NONE_MATCH'] = ''
278
275
 
279
- status,headers,body = @app.call(env)
276
+ if query_string =~ /pp=flamegraph/
277
+ unless defined?(Flamegraph) && Flamegraph.respond_to?(:generate)
278
+
279
+ flamegraph = "Please install the flamegraph gem and require it: add gem 'flamegraph' to your Gemfile"
280
+ status,headers,body = @app.call(env)
281
+ else
282
+ # do not sully our profile with mini profiler timings
283
+ current.measure = false
284
+ match_data = query_string.match(/flamegraph_sample_rate=(?<rate>[\d\.]+)/)
285
+
286
+ mode = query_string =~ /mode=c/ ? :c : :ruby
287
+
288
+ if match_data && !match_data[:rate].to_f.zero?
289
+ sample_rate = match_data[:rate].to_f
290
+ else
291
+ sample_rate = config.flamegraph_sample_rate
292
+ end
293
+ flamegraph = Flamegraph.generate(nil, fidelity: sample_rate, embed_resources: query_string =~ /embed/, mode: mode) do
294
+ status,headers,body = @app.call(env)
295
+ end
296
+ end
297
+ else
298
+ status,headers,body = @app.call(env)
299
+ end
280
300
  client_settings.write!(headers)
281
301
  ensure
282
- if backtraces
283
- done_sampling = true
284
- sleep 0.001 until quit_sampler
285
- end
302
+ trace.disable if trace
286
303
  end
287
304
 
288
305
  skip_it = current.discard
@@ -299,6 +316,11 @@ module Rack
299
316
  return [status,headers,body] if skip_it
300
317
 
301
318
  # we must do this here, otherwise current[:discard] is not being properly treated
319
+ if trace_exceptions
320
+ body.close if body.respond_to? :close
321
+ return dump_exceptions exceptions
322
+ end
323
+
302
324
  if query_string =~ /pp=env/
303
325
  body.close if body.respond_to? :close
304
326
  return dump_env env
@@ -313,56 +335,65 @@ module Rack
313
335
  page_struct['User'] = user(env)
314
336
  page_struct['Root'].record_time((Time.now - start) * 1000)
315
337
 
316
- if backtraces
338
+ if flamegraph
317
339
  body.close if body.respond_to? :close
318
- if query_string =~ /pp=sample/
319
- return analyze(backtraces, page_struct)
320
- else
321
- return flame_graph(backtraces, page_struct)
322
- end
340
+ return self.flamegraph(flamegraph)
323
341
  end
324
342
 
325
343
 
326
- # no matter what it is, it should be unviewed, otherwise we will miss POST
327
- @storage.set_unviewed(page_struct['User'], page_struct['Id'])
328
- @storage.save(page_struct)
329
-
330
- content_type = headers['Content-Type']
331
- # inject headers, script
332
- if content_type && status == 200
333
-
334
- client_settings.write!(headers)
335
-
336
- # mini profiler is meddling with stuff, we can not cache cause we will get incorrect data
337
- # Rack::ETag has already inserted some nonesense in the chain
338
- headers.delete('ETag')
339
- headers.delete('Date')
340
- headers['Cache-Control'] = 'must-revalidate, private, max-age=0'
344
+ begin
345
+ # no matter what it is, it should be unviewed, otherwise we will miss POST
346
+ @storage.set_unviewed(page_struct['User'], page_struct['Id'])
347
+ @storage.save(page_struct)
341
348
 
342
- # inject header
343
- if headers.is_a? Hash
344
- headers['X-MiniProfiler-Ids'] = ids_json(env)
349
+ # inject headers, script
350
+ if headers['Content-Type'] && status == 200
351
+ client_settings.write!(headers)
352
+ result = inject_profiler(env,status,headers,body)
353
+ return result if result
345
354
  end
346
-
347
- if current.inject_js && content_type =~ /text\/html/
348
- response = Rack::Response.new([], status, headers)
349
- script = self.get_profile_script(env)
350
-
351
- if String === body
352
- response.write inject(body,script)
353
- else
354
- body.each { |fragment| response.write inject(fragment, script) }
355
- end
356
- body.close if body.respond_to? :close
357
- return response.finish
355
+ rescue Exception => e
356
+ if @config.storage_failure != nil
357
+ @config.storage_failure.call(e)
358
358
  end
359
359
  end
360
360
 
361
361
  client_settings.write!(headers)
362
362
  [status, headers, body]
363
+
363
364
  ensure
364
365
  # Make sure this always happens
365
- current = nil
366
+ self.current = nil
367
+ end
368
+
369
+ def inject_profiler(env,status,headers,body)
370
+ # mini profiler is meddling with stuff, we can not cache cause we will get incorrect data
371
+ # Rack::ETag has already inserted some nonesense in the chain
372
+ content_type = headers['Content-Type']
373
+
374
+ headers.delete('ETag')
375
+ headers.delete('Date')
376
+ headers['Cache-Control'] = 'must-revalidate, private, max-age=0'
377
+
378
+ # inject header
379
+ if headers.is_a? Hash
380
+ headers['X-MiniProfiler-Ids'] = ids_json(env)
381
+ end
382
+
383
+ if current.inject_js && content_type =~ /text\/html/
384
+ response = Rack::Response.new([], status, headers)
385
+ script = self.get_profile_script(env)
386
+
387
+ if String === body
388
+ response.write inject(body,script)
389
+ else
390
+ body.each { |fragment| response.write inject(fragment, script) }
391
+ end
392
+ body.close if body.respond_to? :close
393
+ response.finish
394
+ else
395
+ nil
396
+ end
366
397
  end
367
398
 
368
399
  def inject(fragment, script)
@@ -377,9 +408,9 @@ module Rack
377
408
  regex = /<\/html>/i
378
409
  close_tag = '</html>'
379
410
  else
380
- # implicit </body> and </html>. Just append the script.
411
+ # implicit </body> and </html>. Don't do anything.
381
412
 
382
- return fragment + script
413
+ return fragment
383
414
  end
384
415
 
385
416
  matches = fragment.scan(regex).length
@@ -402,8 +433,17 @@ module Rack
402
433
  end
403
434
  end
404
435
 
405
- def dump_env(env)
436
+ def dump_exceptions(exceptions)
406
437
  headers = {'Content-Type' => 'text/plain'}
438
+ body = "Exceptions (#{exceptions.length} raised during request)\n\n"
439
+ exceptions.each do |e|
440
+ body << "#{e.class} #{e.message}\n#{e.backtrace.join("\n")}\n\n\n\n"
441
+ end
442
+
443
+ [200, headers, [body]]
444
+ end
445
+
446
+ def dump_env(env)
407
447
  body = "Rack Environment\n---------------\n"
408
448
  env.each do |k,v|
409
449
  body << "#{k}: #{v}\n"
@@ -414,11 +454,19 @@ module Rack
414
454
  body << "#{k}: #{v}\n"
415
455
  end
416
456
 
457
+ body << "\n\nRuby Version\n---------------\n"
458
+ body << "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL}\n"
459
+
417
460
  body << "\n\nInternals\n---------------\n"
418
461
  body << "Storage Provider #{config.storage_instance}\n"
419
462
  body << "User #{user(env)}\n"
420
463
  body << config.storage_instance.diagnostics(user(env)) rescue "no diagnostics implemented for storage"
421
464
 
465
+ text_result(body)
466
+ end
467
+
468
+ def text_result(body)
469
+ headers = {'Content-Type' => 'text/plain'}
422
470
  [200, headers, [body]]
423
471
  end
424
472
 
@@ -432,73 +480,37 @@ module Rack
432
480
  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)
433
481
  pp=normal-backtrace #{"(*) " if client_settings.backtrace_default?}: collect stack traces from all the SQL executed and filter normally
434
482
  pp=full-backtrace #{"(*) " if client_settings.backtrace_full?}: enable full backtraces for SQL executed (use pp=normal-backtrace to disable)
435
- pp=sample : sample stack traces and return a report isolating heavy usage (works best on Ruby 2.0)
436
483
  pp=disable : disable profiling for this session
437
484
  pp=enable : enable profiling for this session (if previously disabled)
438
485
  pp=profile-gc: perform gc profiling on this request, analyzes ObjectSpace generated by request (ruby 1.9.3 only)
439
486
  pp=profile-gc-time: perform built-in gc profiling on this request (ruby 1.9.3 only)
440
- pp=flamegraph: works best on Ruby 2.0, a graph representing sampled activity.
487
+ pp=profile-gc-ruby-head: requires the memory_profiler gem, new location based report
488
+ pp=flamegraph: works best on Ruby 2.0, a graph representing sampled activity (requires the flamegraph gem).
489
+ pp=flamegraph&flamegraph_sample_rate=1: creates a flamegraph with the specified sample rate (in ms). Overrides value set in config
490
+ pp=flamegraph_embed: works best on Ruby 2.0, a graph representing sampled activity (requires the flamegraph gem), embedded resources for use on an intranet.
491
+ pp=trace-exceptions: requires Ruby 2.0, will return all the spots where your application raises execptions
441
492
  "
442
493
 
443
494
  client_settings.write!(headers)
444
495
  [200, headers, [body]]
445
496
  end
446
497
 
447
- def flame_graph(traces, page_struct)
448
- graph = FlameGraph.new(traces)
449
- data = graph.graph_data
450
-
498
+ def flamegraph(graph)
451
499
  headers = {'Content-Type' => 'text/html'}
452
-
453
- body = IO.read(::File.expand_path('../html/flamegraph.html', ::File.dirname(__FILE__)))
454
- body.gsub!("/*DATA*/", ::JSON.generate(data));
455
-
456
- [200, headers, [body]]
500
+ [200, headers, [graph]]
457
501
  end
458
502
 
459
- def analyze(traces, page_struct)
460
- headers = {'Content-Type' => 'text/plain'}
461
- body = "Collected: #{traces.count} stack traces. Duration(ms): #{page_struct.duration_ms}"
462
-
463
- seen = {}
464
- fulldump = ""
465
- traces.each do |trace|
466
- fulldump << "\n\n"
467
- distinct = {}
468
- trace.each do |frame|
469
- frame = frame.to_s unless String === frame
470
- unless distinct[frame]
471
- distinct[frame] = true
472
- seen[frame] ||= 0
473
- seen[frame] += 1
474
- end
475
- fulldump << frame << "\n"
476
- end
477
- end
478
-
479
- body << "\n\nStack Trace Analysis\n"
480
- seen.to_a.sort{|x,y| y[1] <=> x[1]}.each do |name, count|
481
- if count > traces.count / 10
482
- body << "#{name} x #{count}\n"
483
- end
484
- end
485
-
486
- body << "\n\n\nRaw traces \n"
487
- body << fulldump
488
-
489
- [200, headers, [body]]
503
+ def ids(env)
504
+ # cap at 10 ids, otherwise there is a chance you can blow the header
505
+ ([current.page_struct["Id"]] + (@storage.get_unviewed_ids(user(env)) || [])[0..8]).uniq
490
506
  end
491
507
 
492
508
  def ids_json(env)
493
- # cap at 10 ids, otherwise there is a chance you can blow the header
494
- ids = [current.page_struct["Id"]] + (@storage.get_unviewed_ids(user(env)) || [])[0..8]
495
- ::JSON.generate(ids.uniq)
509
+ ::JSON.generate(ids(env))
496
510
  end
497
511
 
498
512
  def ids_comma_separated(env)
499
- # cap at 10 ids, otherwise there is a chance you can blow the header
500
- ids = [current.page_struct["Id"]] + (@storage.get_unviewed_ids(user(env)) || [])[0..8]
501
- ids.join(",")
513
+ ids(env).join(",")
502
514
  end
503
515
 
504
516
  # get_profile_script returns script to be injected inside current html page
@@ -508,26 +520,37 @@ module Rack
508
520
  # * you have disabled auto append behaviour throught :auto_inject => false flag
509
521
  # * you do not want script to be automatically appended for the current page. You can also call cancel_auto_inject
510
522
  def get_profile_script(env)
511
- ids = ids_comma_separated(env)
512
- path = "#{env['SCRIPT_NAME']}#{@config.base_url_path}"
513
- version = MiniProfiler::VERSION
514
- position = @config.position
515
- showTrivial = false
516
- showChildren = false
517
- maxTracesToShow = 10
518
- showControls = false
519
- currentId = current.page_struct["Id"]
520
- authorized = true
521
- toggleShortcut = @config.toggle_shortcut
522
- startHidden = @config.start_hidden
523
+
524
+ settings = {
525
+ :path => "#{env['SCRIPT_NAME']}#{@config.base_url_path}",
526
+ :version => MiniProfiler::VERSION,
527
+ :position => @config.position,
528
+ :showTrivial => false,
529
+ :showChildren => false,
530
+ :maxTracesToShow => 10,
531
+ :showControls => false,
532
+ :authorized => true,
533
+ :toggleShortcut => @config.toggle_shortcut,
534
+ :startHidden => @config.start_hidden
535
+ }
536
+
537
+ if current && current.page_struct
538
+ settings[:ids] = ids_comma_separated(env)
539
+ settings[:currentId] = current.page_struct["Id"]
540
+ else
541
+ settings[:ids] = []
542
+ settings[:currentId] = ""
543
+ end
544
+
523
545
  # TODO : cache this snippet
524
546
  script = IO.read(::File.expand_path('../html/profile_handler.js', ::File.dirname(__FILE__)))
525
547
  # replace the variables
526
- [:ids, :path, :version, :position, :showTrivial, :showChildren, :maxTracesToShow, :showControls, :currentId, :authorized, :toggleShortcut, :startHidden].each do |v|
527
- regex = Regexp.new("\\{#{v.to_s}\\}")
528
- script.gsub!(regex, eval(v.to_s).to_s)
548
+ settings.each do |k,v|
549
+ regex = Regexp.new("\\{#{k.to_s}\\}")
550
+ script.gsub!(regex, v.to_s)
529
551
  end
530
- current.inject_js = false
552
+
553
+ current.inject_js = false if current
531
554
  script
532
555
  end
533
556