rack-mini-profiler 0.1.31 → 0.9.3

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.

Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +149 -0
  3. data/README.md +285 -0
  4. data/{Ruby/lib → lib}/html/includes.css +15 -4
  5. data/{Ruby/lib → lib}/html/includes.js +95 -59
  6. data/{Ruby/lib → lib}/html/includes.less +21 -5
  7. data/{Ruby/lib → lib}/html/includes.tmpl +50 -50
  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/lib/html/list.tmpl +34 -0
  13. data/lib/html/profile_handler.js +1 -0
  14. data/{Ruby/lib → lib}/html/share.html +2 -2
  15. data/lib/mini_profiler/asset_version.rb +5 -0
  16. data/{Ruby/lib → lib}/mini_profiler/client_settings.rb +3 -3
  17. data/lib/mini_profiler/config.rb +65 -0
  18. data/{Ruby/lib → lib}/mini_profiler/context.rb +0 -0
  19. data/lib/mini_profiler/gc_profiler.rb +181 -0
  20. data/{Ruby/lib → lib}/mini_profiler/profiler.rb +120 -96
  21. data/{Ruby/lib → lib}/mini_profiler/profiling_methods.rb +15 -17
  22. data/{Ruby/lib → lib}/mini_profiler/storage/abstract_store.rb +0 -0
  23. data/{Ruby/lib → lib}/mini_profiler/storage/file_store.rb +30 -8
  24. data/{Ruby/lib → lib}/mini_profiler/storage/memcache_store.rb +5 -7
  25. data/lib/mini_profiler/storage/memory_store.rb +115 -0
  26. data/{Ruby/lib → lib}/mini_profiler/storage/redis_store.rb +19 -11
  27. data/lib/mini_profiler/timer_struct/base.rb +33 -0
  28. data/lib/mini_profiler/timer_struct/client.rb +89 -0
  29. data/lib/mini_profiler/timer_struct/custom.rb +22 -0
  30. data/lib/mini_profiler/timer_struct/page.rb +62 -0
  31. data/lib/mini_profiler/timer_struct/request.rb +126 -0
  32. data/lib/mini_profiler/timer_struct/sql.rb +59 -0
  33. data/lib/mini_profiler/version.rb +5 -0
  34. data/{Ruby/lib → lib}/mini_profiler_rails/railtie.rb +23 -6
  35. data/lib/patches/db/activerecord.rb +42 -0
  36. data/lib/patches/db/moped.rb +12 -0
  37. data/lib/patches/db/mysql2.rb +30 -0
  38. data/lib/patches/db/pg.rb +104 -0
  39. data/lib/patches/db/plucky.rb +47 -0
  40. data/lib/patches/db/rsolr.rb +24 -0
  41. data/lib/patches/db/sequel.rb +10 -0
  42. data/{Ruby/lib → lib}/patches/net_patches.rb +0 -0
  43. data/lib/patches/sql_patches.rb +46 -0
  44. data/lib/rack-mini-profiler.rb +35 -0
  45. data/rack-mini-profiler.gemspec +28 -16
  46. metadata +171 -52
  47. data/Ruby/CHANGELOG +0 -161
  48. data/Ruby/README.md +0 -172
  49. data/Ruby/lib/html/list.tmpl +0 -34
  50. data/Ruby/lib/html/profile_handler.js +0 -1
  51. data/Ruby/lib/mini_profiler/client_timer_struct.rb +0 -78
  52. data/Ruby/lib/mini_profiler/config.rb +0 -58
  53. data/Ruby/lib/mini_profiler/custom_timer_struct.rb +0 -22
  54. data/Ruby/lib/mini_profiler/gc_profiler.rb +0 -107
  55. data/Ruby/lib/mini_profiler/gc_profiler_ruby_head.rb +0 -40
  56. data/Ruby/lib/mini_profiler/page_timer_struct.rb +0 -58
  57. data/Ruby/lib/mini_profiler/request_timer_struct.rb +0 -115
  58. data/Ruby/lib/mini_profiler/sql_timer_struct.rb +0 -58
  59. data/Ruby/lib/mini_profiler/storage/memory_store.rb +0 -65
  60. data/Ruby/lib/mini_profiler/timer_struct.rb +0 -33
  61. data/Ruby/lib/mini_profiler/version.rb +0 -5
  62. data/Ruby/lib/patches/sql_patches.rb +0 -277
  63. data/Ruby/lib/rack-mini-profiler.rb +0 -7
File without changes
File without changes
@@ -1,4 +1,4 @@
1
- tbody tr:nth-child(odd) { background-color:#eee; }
1
+ tbody tr:nth-child(odd) { background-color:#eee; }
2
2
  tbody tr:nth-child(even) { background-color:#fff; }
3
3
  table { border: 0; border-spacing:0;}
4
4
  tr {border: 0;}
@@ -6,4 +6,4 @@ tr {border: 0;}
6
6
  td {padding: 8px;}
7
7
  .time {text-align:center;}
8
8
  thead tr {background-color: #bbb; color: #444; font-size: 12px;}
9
- thead tr th { padding: 5px 15px;}
9
+ thead tr th { padding: 5px 15px;}
@@ -1,4 +1,4 @@
1
- var MiniProfiler = MiniProfiler || {};
1
+ var MiniProfiler = MiniProfiler || {};
2
2
  MiniProfiler.list = {
3
3
  init:
4
4
  function (options) {
@@ -0,0 +1,34 @@
1
+ <script id="tableTemplate" type="text/x-jquery-tmpl">
2
+ <table>
3
+ <thead>
4
+ <tr>
5
+ <th>Name</th>
6
+ <th>Started</th>
7
+ <th>Sql Duration</th>
8
+ <th>Total Duration</th>
9
+ <th>Request Start</th>
10
+ <th>Response Start</th>
11
+ <th>Dom Complete</th>
12
+ </tr>
13
+ </thead>
14
+ <tbody>
15
+
16
+ </tbody>
17
+ </table>
18
+ </script>
19
+ <script id="rowTemplate" type="text/x-jquery-tmpl">
20
+ <tr>
21
+ <td>
22
+ <a href="${MiniProfiler.path}results?id=${id}">${name}</a></td>
23
+ <td class="date">${MiniProfiler.renderDate(started)}</td>
24
+ <td class="time">${duration_milliseconds_in_sql}</td>
25
+ <td class="time">${duration_milliseconds}</td>
26
+ {{if client_timings}}
27
+ <td class="time">${MiniProfiler.getClientTimingByName(client_timings,"Request").start}</td>
28
+ <td class="time">${MiniProfiler.getClientTimingByName(client_timings,"Response").start}</td>
29
+ <td class="time">${MiniProfiler.getClientTimingByName(client_timings,"Dom Complete").start}</td>
30
+ {{else}}
31
+ <td colspan="3"></td>
32
+ {{/if}}
33
+ </tr>
34
+ </script>
@@ -0,0 +1 @@
1
+ <script async type="text/javascript" id="mini-profiler" src="{path}includes.js?v={version}" data-version="{version}" data-path="{path}" data-current-id="{currentId}" data-ids="{ids}" data-position="{position}" data-trivial="{showTrivial}" data-children="{showChildren}" data-max-traces="{maxTracesToShow}" data-controls="{showControls}" data-authorized="{authorized}" data-toggle-shortcut="{toggleShortcut}" data-start-hidden="{startHidden}"></script>
@@ -1,4 +1,4 @@
1
- <html>
1
+ <html>
2
2
  <head>
3
3
  <title>{name} ({duration} ms) - Profiling Results</title>
4
4
  <script type='text/javascript' src='{path}jquery.1.7.1.js?v={version}'></script>
@@ -8,4 +8,4 @@
8
8
  <body>
9
9
  <div class='profiler-result-full'></div>
10
10
  </body>
11
- </html>
11
+ </html>
@@ -0,0 +1,5 @@
1
+ module Rack
2
+ class MiniProfiler
3
+ ASSET_VERSION = '9db1f8db18400644bd5c7449e5295620'.freeze
4
+ end
5
+ end
@@ -5,8 +5,8 @@ module Rack
5
5
  COOKIE_NAME = "__profilin"
6
6
 
7
7
  BACKTRACE_DEFAULT = nil
8
- BACKTRACE_FULL = 1
9
- BACKTRACE_NONE = 2
8
+ BACKTRACE_FULL = 1
9
+ BACKTRACE_NONE = 2
10
10
 
11
11
  attr_accessor :disable_profiling
12
12
  attr_accessor :backtrace_level
@@ -30,7 +30,7 @@ module Rack
30
30
  def write!(headers)
31
31
  if @orig_disable_profiling != @disable_profiling || @orig_backtrace_level != @backtrace_level || @cookie.nil?
32
32
  settings = {"p" => "t" }
33
- settings["dp"] = "t" if @disable_profiling
33
+ settings["dp"] = "t" if @disable_profiling
34
34
  settings["bt"] = @backtrace_level if @backtrace_level
35
35
  settings_string = settings.map{|k,v| "#{k}=#{v}"}.join(",")
36
36
  Rack::Utils.set_cookie_header!(headers, COOKIE_NAME, :value => settings_string, :path => '/')
@@ -0,0 +1,65 @@
1
+ module Rack
2
+ class MiniProfiler
3
+ class Config
4
+
5
+ def self.attr_accessor(*vars)
6
+ @attributes ||= []
7
+ @attributes.concat vars
8
+ super(*vars)
9
+ end
10
+
11
+ def self.attributes
12
+ @attributes
13
+ end
14
+
15
+ attr_accessor :authorization_mode, :auto_inject, :backtrace_ignores, :backtrace_includes, :backtrace_remove,
16
+ :backtrace_threshold_ms, :base_url_path, :disable_caching, :enabled, :flamegraph_sample_rate, :logger, :position,
17
+ :pre_authorize_cb, :skip_paths, :skip_schema_queries, :start_hidden, :storage, :storage_failure,
18
+ :storage_instance, :storage_options, :toggle_shortcut, :user_provider
19
+
20
+ # Deprecated options
21
+ attr_accessor :use_existing_jquery
22
+
23
+ def self.default
24
+ new.instance_eval {
25
+ @auto_inject = true # automatically inject on every html page
26
+ @base_url_path = "/mini-profiler-resources/"
27
+ @disable_caching = true
28
+ # called prior to rack chain, to ensure we are allowed to profile
29
+ @pre_authorize_cb = lambda {|env| true}
30
+
31
+ # called after rack chain, to ensure we are REALLY allowed to profile
32
+ @position = 'left' # Where it is displayed
33
+ @skip_schema_queries = false
34
+ @storage = MiniProfiler::MemoryStore
35
+ @user_provider = Proc.new{|env| Rack::Request.new(env).ip}
36
+ @authorization_mode = :allow_all
37
+ @toggle_shortcut = 'Alt+P'
38
+ @start_hidden = false
39
+ @backtrace_threshold_ms = 0
40
+ @flamegraph_sample_rate = 0.5
41
+ @storage_failure = Proc.new do |exception|
42
+ if @logger
43
+ @logger.warn("MiniProfiler storage failure: #{exception.message}")
44
+ end
45
+ end
46
+ @enabled = true
47
+ self
48
+ }
49
+ end
50
+
51
+ def merge!(config)
52
+ return unless config
53
+ if Hash === config
54
+ config.each{|k,v| instance_variable_set "@#{k}",v}
55
+ else
56
+ self.class.attributes.each{ |k|
57
+ v = config.send k
58
+ instance_variable_set "@#{k}", v if v
59
+ }
60
+ end
61
+ end
62
+
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,181 @@
1
+ class Rack::MiniProfiler::GCProfiler
2
+
3
+ def initialize
4
+ @ignore = []
5
+ @ignore << @ignore.__id__
6
+ end
7
+
8
+ def object_space_stats
9
+ stats = {}
10
+ ids = {}
11
+
12
+ @ignore << stats.__id__
13
+ @ignore << ids.__id__
14
+
15
+ i=0
16
+ ObjectSpace.each_object { |o|
17
+ begin
18
+ i = stats[o.class] || 0
19
+ i += 1
20
+ stats[o.class] = i
21
+ ids[o.__id__] = o if Integer === o.__id__
22
+ rescue NoMethodError
23
+ # protect against BasicObject
24
+ end
25
+ }
26
+
27
+ @ignore.each do |id|
28
+ if ids.delete(id)
29
+ klass = ObjectSpace._id2ref(id).class
30
+ stats[klass] -= 1
31
+ end
32
+ end
33
+
34
+ result = {:stats => stats, :ids => ids}
35
+ @ignore << result.__id__
36
+
37
+ result
38
+ end
39
+
40
+ def diff_object_stats(before, after)
41
+ diff = {}
42
+ after.each do |k,v|
43
+ diff[k] = v - (before[k] || 0)
44
+ end
45
+ before.each do |k,v|
46
+ diff[k] = 0 - v unless after[k]
47
+ end
48
+
49
+ diff
50
+ end
51
+
52
+ def analyze_strings(ids_before, ids_after)
53
+ result = {}
54
+ ids_after.each do |id,_|
55
+ obj = ObjectSpace._id2ref(id)
56
+ if String === obj && !ids_before.include?(obj.object_id)
57
+ result[obj] ||= 0
58
+ result[obj] += 1
59
+ end
60
+ end
61
+ result
62
+ end
63
+
64
+ def analyze_growth(ids_before, ids_after)
65
+ new_objects = 0
66
+ memory_allocated = 0
67
+
68
+ ids_after.each do |id,_|
69
+ if !ids_before.include?(id) && obj=ObjectSpace._id2ref(id)
70
+ # this is going to be version specific (may change in 2.1)
71
+ size = ObjectSpace.memsize_of(obj)
72
+ memory_allocated += size
73
+ new_objects += 1
74
+ end
75
+ end
76
+
77
+ [new_objects, memory_allocated]
78
+ end
79
+
80
+ def analyze_initial_state(ids_before)
81
+ memory_allocated = 0
82
+ objects = 0
83
+
84
+ ids_before.each do |id,_|
85
+ if obj=ObjectSpace._id2ref(id)
86
+ # this is going to be version specific (may change in 2.1)
87
+ memory_allocated += ObjectSpace.memsize_of(obj)
88
+ objects += 1
89
+ end
90
+ end
91
+
92
+ [objects,memory_allocated]
93
+ end
94
+
95
+ def profile_gc_time(app, env)
96
+ body = []
97
+
98
+ begin
99
+ GC::Profiler.clear
100
+ prev_profiler_state = GC::Profiler.enabled?
101
+ prev_gc_state = GC.enable
102
+ GC::Profiler.enable
103
+ b = app.call(env)[2]
104
+ b.close if b.respond_to? :close
105
+ body << "GC Profiler ran during this request, if it fired you will see the cost below:\n\n"
106
+ body << GC::Profiler.result
107
+ ensure
108
+ prev_gc_state ? GC.disable : GC.enable
109
+ GC::Profiler.disable unless prev_profiler_state
110
+ end
111
+
112
+ return [200, {'Content-Type' => 'text/plain'}, body]
113
+ end
114
+
115
+ def profile_gc(app, env)
116
+
117
+ # for memsize_of
118
+ require 'objspace'
119
+
120
+ body = [];
121
+
122
+ stat_before,stat_after,diff,string_analysis,
123
+ new_objects, memory_allocated, stat, memory_before, objects_before = nil
124
+
125
+ # clean up before
126
+ GC.start
127
+ stat = GC.stat
128
+ prev_gc_state = GC.disable
129
+ stat_before = object_space_stats
130
+ b = app.call(env)[2]
131
+ b.close if b.respond_to? :close
132
+ stat_after = object_space_stats
133
+ # so we don't blow out on memory
134
+ prev_gc_state ? GC.disable : GC.enable
135
+
136
+ diff = diff_object_stats(stat_before[:stats],stat_after[:stats])
137
+ string_analysis = analyze_strings(stat_before[:ids], stat_after[:ids])
138
+ new_objects, memory_allocated = analyze_growth(stat_before[:ids], stat_after[:ids])
139
+ objects_before, memory_before = analyze_initial_state(stat_before[:ids])
140
+
141
+
142
+ body << "
143
+ Overview
144
+ ------------------------------------
145
+ Initial state: object count - #{objects_before} , memory allocated outside heap (bytes) #{memory_before}
146
+
147
+ GC Stats: #{stat.map{|k,v| "#{k} : #{v}" }.join(", ")}
148
+
149
+ New bytes allocated outside of Ruby heaps: #{memory_allocated}
150
+ New objects: #{new_objects}
151
+ "
152
+
153
+ body << "
154
+ ObjectSpace delta caused by request:
155
+ --------------------------------------------\n"
156
+ diff.to_a.reject{|k,v| v == 0}.sort{|x,y| y[1] <=> x[1]}.each do |k,v|
157
+ body << "#{k} : #{v}\n" if v != 0
158
+ end
159
+
160
+ body << "\n
161
+ ObjectSpace stats:
162
+ -----------------\n"
163
+
164
+ stat_after[:stats].to_a.sort{|x,y| y[1] <=> x[1]}.each do |k,v|
165
+ body << "#{k} : #{v}\n"
166
+ end
167
+
168
+
169
+ body << "\n
170
+ String stats:
171
+ ------------\n"
172
+
173
+ string_analysis.to_a.sort{|x,y| y[1] <=> x[1] }.take(1000).each do |string,count|
174
+ body << "#{count} : #{string}\n"
175
+ end
176
+
177
+ return [200, {'Content-Type' => 'text/plain'}, body]
178
+ ensure
179
+ prev_gc_state ? GC.disable : GC.enable
180
+ end
181
+ end
@@ -1,30 +1,5 @@
1
- require 'json'
2
- require 'timeout'
3
- require 'thread'
4
-
5
- require 'mini_profiler/version'
6
- require 'mini_profiler/page_timer_struct'
7
- require 'mini_profiler/sql_timer_struct'
8
- require 'mini_profiler/custom_timer_struct'
9
- require 'mini_profiler/client_timer_struct'
10
- require 'mini_profiler/request_timer_struct'
11
- require 'mini_profiler/storage/abstract_store'
12
- require 'mini_profiler/storage/memcache_store'
13
- require 'mini_profiler/storage/memory_store'
14
- require 'mini_profiler/storage/redis_store'
15
- require 'mini_profiler/storage/file_store'
16
- require 'mini_profiler/config'
17
- require 'mini_profiler/profiling_methods'
18
- require 'mini_profiler/context'
19
- require 'mini_profiler/client_settings'
20
- require 'mini_profiler/gc_profiler'
21
- # TODO
22
- # require 'mini_profiler/gc_profiler_ruby_head' if Gem::Version.new('2.1.0') <= Gem::Version.new(RUBY_VERSION)
23
-
24
1
  module Rack
25
-
26
2
  class MiniProfiler
27
-
28
3
  class << self
29
4
 
30
5
  include Rack::MiniProfiler::ProfilingMethods
@@ -43,8 +18,7 @@ module Rack
43
18
  end
44
19
 
45
20
  def share_template
46
- return @share_template unless @share_template.nil?
47
- @share_template = ::File.read(::File.expand_path("../html/share.html", ::File.dirname(__FILE__)))
21
+ @share_template ||= ::File.read(::File.expand_path("../html/share.html", ::File.dirname(__FILE__)))
48
22
  end
49
23
 
50
24
  def current
@@ -53,7 +27,7 @@ module Rack
53
27
 
54
28
  def current=(c)
55
29
  # we use TLS cause we need access to this from sql blocks and code blocks that have no access to env
56
- Thread.current[:mini_profiler_private]= c
30
+ Thread.current[:mini_profiler_private] = c
57
31
  end
58
32
 
59
33
  # discard existing results, don't track this request
@@ -63,10 +37,10 @@ module Rack
63
37
 
64
38
  def create_current(env={}, options={})
65
39
  # profiling the request
66
- self.current = Context.new
67
- self.current.inject_js = config.auto_inject && (!env['HTTP_X_REQUESTED_WITH'].eql? 'XMLHttpRequest')
68
- self.current.page_struct = PageTimerStruct.new(env)
69
- self.current.current_timer = current.page_struct['Root']
40
+ self.current = Context.new
41
+ self.current.inject_js = config.auto_inject && (!env['HTTP_X_REQUESTED_WITH'].eql? 'XMLHttpRequest')
42
+ self.current.page_struct = TimerStruct::Page.new(env)
43
+ self.current.current_timer = current.page_struct[:root]
70
44
  end
71
45
 
72
46
  def authorize_request
@@ -89,7 +63,7 @@ module Rack
89
63
  def initialize(app, config = nil)
90
64
  MiniProfiler.config.merge!(config)
91
65
  @config = MiniProfiler.config
92
- @app = app
66
+ @app = app
93
67
  @config.base_url_path << "/" unless @config.base_url_path.end_with? "/"
94
68
  unless @config.storage_instance
95
69
  @config.storage_instance = @config.storage.new(@config.storage_options)
@@ -102,16 +76,18 @@ module Rack
102
76
  end
103
77
 
104
78
  def serve_results(env)
105
- request = Rack::Request.new(env)
106
- id = request['id']
79
+ request = Rack::Request.new(env)
80
+ id = request[:id]
107
81
  page_struct = @storage.load(id)
108
82
  unless page_struct
109
83
  @storage.set_viewed(user(env), id)
110
- return [404, {}, ["Request not found: #{request['id']} - user #{user(env)}"]]
84
+ id = ERB::Util.html_escape(request['id'])
85
+ user_info = ERB::Util.html_escape(user(env))
86
+ return [404, {}, ["Request not found: #{id} - user #{user_info}"]]
111
87
  end
112
- unless page_struct['HasUserViewed']
113
- page_struct['ClientTimings'] = ClientTimerStruct.init_from_form_data(env, page_struct)
114
- page_struct['HasUserViewed'] = true
88
+ unless page_struct[:has_user_viewed]
89
+ page_struct[:client_timings] = TimerStruct::Client.init_from_form_data(env, page_struct)
90
+ page_struct[:has_user_viewed] = true
115
91
  @storage.save(page_struct)
116
92
  @storage.set_viewed(user(env), id)
117
93
  end
@@ -125,10 +101,10 @@ module Rack
125
101
  # Otherwise give the HTML back
126
102
  html = MiniProfiler.share_template.dup
127
103
  html.gsub!(/\{path\}/, "#{env['SCRIPT_NAME']}#{@config.base_url_path}")
128
- html.gsub!(/\{version\}/, MiniProfiler::VERSION)
104
+ html.gsub!(/\{version\}/, MiniProfiler::ASSET_VERSION)
129
105
  html.gsub!(/\{json\}/, result_json)
130
106
  html.gsub!(/\{includes\}/, get_profile_script(env))
131
- html.gsub!(/\{name\}/, page_struct['Name'])
107
+ html.gsub!(/\{name\}/, page_struct[:name])
132
108
  html.gsub!(/\{duration\}/, "%.1f" % page_struct.duration_ms)
133
109
 
134
110
  [200, {'Content-Type' => 'text/html'}, [html]]
@@ -138,10 +114,12 @@ module Rack
138
114
 
139
115
  def serve_html(env)
140
116
  file_name = env['PATH_INFO'][(@config.base_url_path.length)..1000]
117
+
141
118
  return serve_results(env) if file_name.eql?('results')
119
+
142
120
  full_path = ::File.expand_path("../html/#{file_name}", ::File.dirname(__FILE__))
143
121
  return [404, {}, ["Not found"]] unless ::File.exists? full_path
144
- f = Rack::File.new nil
122
+ f = Rack::File.new nil
145
123
  f.path = full_path
146
124
 
147
125
  begin
@@ -162,7 +140,7 @@ module Rack
162
140
  end
163
141
 
164
142
  def current=(c)
165
- MiniProfiler.current=c
143
+ MiniProfiler.current = c
166
144
  end
167
145
 
168
146
 
@@ -177,7 +155,7 @@ module Rack
177
155
 
178
156
  status = headers = body = nil
179
157
  query_string = env['QUERY_STRING']
180
- path = env['PATH_INFO']
158
+ path = env['PATH_INFO']
181
159
 
182
160
  skip_it = (@config.pre_authorize_cb && !@config.pre_authorize_cb.call(env)) ||
183
161
  (@config.skip_paths && @config.skip_paths.any?{ |p| path[0,p.length] == p}) ||
@@ -202,11 +180,12 @@ module Rack
202
180
  skip_it = true
203
181
  end
204
182
 
205
- if query_string =~ /pp=enable/
183
+ if query_string =~ /pp=enable/ && (@config.authorization_mode != :whitelist || MiniProfiler.request_authorized?)
206
184
  skip_it = false
185
+ config.enabled = true
207
186
  end
208
187
 
209
- if skip_it
188
+ if skip_it || !config.enabled
210
189
  status,headers,body = @app.call(env)
211
190
  client_settings.disable_profiling = true
212
191
  client_settings.write!(headers)
@@ -216,8 +195,18 @@ module Rack
216
195
  end
217
196
 
218
197
  if query_string =~ /pp=profile-gc/
198
+ current.measure = false if current
199
+
219
200
  if query_string =~ /pp=profile-gc-time/
220
201
  return Rack::MiniProfiler::GCProfiler.new.profile_gc_time(@app, env)
202
+ elsif query_string =~ /pp=profile-gc-ruby-head/
203
+ result = StringIO.new
204
+ report = MemoryProfiler.report do
205
+ _,_,body = @app.call(env)
206
+ body.close if body.respond_to? :close
207
+ end
208
+ report.pretty_print(result)
209
+ return text_result(result.string)
221
210
  else
222
211
  return Rack::MiniProfiler::GCProfiler.new.profile_gc(@app, env)
223
212
  end
@@ -247,7 +236,7 @@ module Rack
247
236
 
248
237
  if trace_exceptions
249
238
  exceptions = []
250
- trace = TracePoint.new(:raise) do |tp|
239
+ trace = TracePoint.new(:raise) do |tp|
251
240
  exceptions << tp.raised_exception
252
241
  end
253
242
  trace.enable
@@ -257,8 +246,10 @@ module Rack
257
246
 
258
247
  # Strip all the caching headers so we don't get 304s back
259
248
  # This solves a very annoying bug where rack mini profiler never shows up
260
- env['HTTP_IF_MODIFIED_SINCE'] = ''
261
- env['HTTP_IF_NONE_MATCH'] = ''
249
+ if config.disable_caching
250
+ env['HTTP_IF_MODIFIED_SINCE'] = ''
251
+ env['HTTP_IF_NONE_MATCH'] = ''
252
+ end
262
253
 
263
254
  if query_string =~ /pp=flamegraph/
264
255
  unless defined?(Flamegraph) && Flamegraph.respond_to?(:generate)
@@ -268,9 +259,16 @@ module Rack
268
259
  else
269
260
  # do not sully our profile with mini profiler timings
270
261
  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
262
+ match_data = query_string.match(/flamegraph_sample_rate=([\d\.]+)/)
263
+
264
+ mode = query_string =~ /mode=c/ ? :c : :ruby
265
+
266
+ if match_data && !match_data[1].to_f.zero?
267
+ sample_rate = match_data[1].to_f
268
+ else
269
+ sample_rate = config.flamegraph_sample_rate
270
+ end
271
+ flamegraph = Flamegraph.generate(nil, :fidelity => sample_rate, :embed_resources => query_string =~ /embed/, :mode => mode) do
274
272
  status,headers,body = @app.call(env)
275
273
  end
276
274
  end
@@ -312,8 +310,8 @@ module Rack
312
310
  end
313
311
 
314
312
  page_struct = current.page_struct
315
- page_struct['User'] = user(env)
316
- page_struct['Root'].record_time((Time.now - start) * 1000)
313
+ page_struct[:user] = user(env)
314
+ page_struct[:root].record_time((Time.now - start) * 1000)
317
315
 
318
316
  if flamegraph
319
317
  body.close if body.respond_to? :close
@@ -321,16 +319,21 @@ module Rack
321
319
  end
322
320
 
323
321
 
324
- # no matter what it is, it should be unviewed, otherwise we will miss POST
325
- @storage.set_unviewed(page_struct['User'], page_struct['Id'])
326
- @storage.save(page_struct)
327
-
328
- # inject headers, script
329
- if headers['Content-Type'] && status == 200
330
- client_settings.write!(headers)
322
+ begin
323
+ # no matter what it is, it should be unviewed, otherwise we will miss POST
324
+ @storage.set_unviewed(page_struct[:user], page_struct[:id])
325
+ @storage.save(page_struct)
331
326
 
332
- result = inject_profiler(env,status,headers,body)
333
- return result if result
327
+ # inject headers, script
328
+ if headers['Content-Type'] && status == 200
329
+ client_settings.write!(headers)
330
+ result = inject_profiler(env,status,headers,body)
331
+ return result if result
332
+ end
333
+ rescue Exception => e
334
+ if @config.storage_failure != nil
335
+ @config.storage_failure.call(e)
336
+ end
334
337
  end
335
338
 
336
339
  client_settings.write!(headers)
@@ -338,7 +341,7 @@ module Rack
338
341
 
339
342
  ensure
340
343
  # Make sure this always happens
341
- current = nil
344
+ self.current = nil
342
345
  end
343
346
 
344
347
  def inject_profiler(env,status,headers,body)
@@ -346,9 +349,12 @@ module Rack
346
349
  # Rack::ETag has already inserted some nonesense in the chain
347
350
  content_type = headers['Content-Type']
348
351
 
349
- headers.delete('ETag')
350
- headers.delete('Date')
351
- headers['Cache-Control'] = 'must-revalidate, private, max-age=0'
352
+ if config.disable_caching
353
+ headers.delete('ETag')
354
+ headers.delete('Date')
355
+ end
356
+
357
+ headers['Cache-Control'] = "#{"no-store, " if config.disable_caching}must-revalidate, private, max-age=0"
352
358
 
353
359
  # inject header
354
360
  if headers.is_a? Hash
@@ -357,7 +363,7 @@ module Rack
357
363
 
358
364
  if current.inject_js && content_type =~ /text\/html/
359
365
  response = Rack::Response.new([], status, headers)
360
- script = self.get_profile_script(env)
366
+ script = self.get_profile_script(env)
361
367
 
362
368
  if String === body
363
369
  response.write inject(body,script)
@@ -383,9 +389,9 @@ module Rack
383
389
  regex = /<\/html>/i
384
390
  close_tag = '</html>'
385
391
  else
386
- # implicit </body> and </html>. Just append the script.
392
+ # implicit </body> and </html>. Don't do anything.
387
393
 
388
- return fragment + script
394
+ return fragment
389
395
  end
390
396
 
391
397
  matches = fragment.scan(regex).length
@@ -410,7 +416,7 @@ module Rack
410
416
 
411
417
  def dump_exceptions(exceptions)
412
418
  headers = {'Content-Type' => 'text/plain'}
413
- body = "Exceptions (#{exceptions.length} raised during request)\n\n"
419
+ body = "Exceptions (#{exceptions.length} raised during request)\n\n"
414
420
  exceptions.each do |e|
415
421
  body << "#{e.class} #{e.message}\n#{e.backtrace.join("\n")}\n\n\n\n"
416
422
  end
@@ -419,7 +425,6 @@ module Rack
419
425
  end
420
426
 
421
427
  def dump_env(env)
422
- headers = {'Content-Type' => 'text/plain'}
423
428
  body = "Rack Environment\n---------------\n"
424
429
  env.each do |k,v|
425
430
  body << "#{k}: #{v}\n"
@@ -438,6 +443,11 @@ module Rack
438
443
  body << "User #{user(env)}\n"
439
444
  body << config.storage_instance.diagnostics(user(env)) rescue "no diagnostics implemented for storage"
440
445
 
446
+ text_result(body)
447
+ end
448
+
449
+ def text_result(body)
450
+ headers = {'Content-Type' => 'text/plain'}
441
451
  [200, headers, [body]]
442
452
  end
443
453
 
@@ -455,7 +465,10 @@ module Rack
455
465
  pp=enable : enable profiling for this session (if previously disabled)
456
466
  pp=profile-gc: perform gc profiling on this request, analyzes ObjectSpace generated by request (ruby 1.9.3 only)
457
467
  pp=profile-gc-time: perform built-in gc profiling on this request (ruby 1.9.3 only)
468
+ pp=profile-gc-ruby-head: requires the memory_profiler gem, new location based report
458
469
  pp=flamegraph: works best on Ruby 2.0, a graph representing sampled activity (requires the flamegraph gem).
470
+ pp=flamegraph&flamegraph_sample_rate=1: creates a flamegraph with the specified sample rate (in ms). Overrides value set in config
471
+ 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.
459
472
  pp=trace-exceptions: requires Ruby 2.0, will return all the spots where your application raises execptions
460
473
  "
461
474
 
@@ -468,16 +481,17 @@ module Rack
468
481
  [200, headers, [graph]]
469
482
  end
470
483
 
471
- def ids_json(env)
484
+ def ids(env)
472
485
  # cap at 10 ids, otherwise there is a chance you can blow the header
473
- ids = [current.page_struct["Id"]] + (@storage.get_unviewed_ids(user(env)) || [])[0..8]
474
- ::JSON.generate(ids.uniq)
486
+ ([current.page_struct[:id]] + (@storage.get_unviewed_ids(user(env)) || [])[0..8]).uniq
487
+ end
488
+
489
+ def ids_json(env)
490
+ ::JSON.generate(ids(env))
475
491
  end
476
492
 
477
493
  def ids_comma_separated(env)
478
- # cap at 10 ids, otherwise there is a chance you can blow the header
479
- ids = [current.page_struct["Id"]] + (@storage.get_unviewed_ids(user(env)) || [])[0..8]
480
- ids.join(",")
494
+ ids(env).join(",")
481
495
  end
482
496
 
483
497
  # get_profile_script returns script to be injected inside current html page
@@ -487,26 +501,38 @@ module Rack
487
501
  # * you have disabled auto append behaviour throught :auto_inject => false flag
488
502
  # * you do not want script to be automatically appended for the current page. You can also call cancel_auto_inject
489
503
  def get_profile_script(env)
490
- ids = ids_comma_separated(env)
491
- path = "#{env['SCRIPT_NAME']}#{@config.base_url_path}"
492
- version = MiniProfiler::VERSION
493
- position = @config.position
494
- showTrivial = false
495
- showChildren = false
496
- maxTracesToShow = 10
497
- showControls = false
498
- currentId = current.page_struct["Id"]
499
- authorized = true
500
- toggleShortcut = @config.toggle_shortcut
501
- startHidden = @config.start_hidden
504
+ path = "#{env['SCRIPT_NAME']}#{@config.base_url_path}"
505
+
506
+ settings = {
507
+ :path => path,
508
+ :version => MiniProfiler::ASSET_VERSION,
509
+ :position => @config.position,
510
+ :showTrivial => false,
511
+ :showChildren => false,
512
+ :maxTracesToShow => 10,
513
+ :showControls => false,
514
+ :authorized => true,
515
+ :toggleShortcut => @config.toggle_shortcut,
516
+ :startHidden => @config.start_hidden
517
+ }
518
+
519
+ if current && current.page_struct
520
+ settings[:ids] = ids_comma_separated(env)
521
+ settings[:currentId] = current.page_struct[:id]
522
+ else
523
+ settings[:ids] = []
524
+ settings[:currentId] = ""
525
+ end
526
+
502
527
  # TODO : cache this snippet
503
528
  script = IO.read(::File.expand_path('../html/profile_handler.js', ::File.dirname(__FILE__)))
504
529
  # replace the variables
505
- [:ids, :path, :version, :position, :showTrivial, :showChildren, :maxTracesToShow, :showControls, :currentId, :authorized, :toggleShortcut, :startHidden].each do |v|
506
- regex = Regexp.new("\\{#{v.to_s}\\}")
507
- script.gsub!(regex, eval(v.to_s).to_s)
530
+ settings.each do |k,v|
531
+ regex = Regexp.new("\\{#{k.to_s}\\}")
532
+ script.gsub!(regex, v.to_s)
508
533
  end
509
- current.inject_js = false
534
+
535
+ current.inject_js = false if current
510
536
  script
511
537
  end
512
538
 
@@ -516,6 +542,4 @@ module Rack
516
542
  end
517
543
 
518
544
  end
519
-
520
545
  end
521
-