rack-mini-profiler 0.1.1 → 0.1.2

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.

@@ -5,3 +5,4 @@
5
5
  * Amended Rack.MiniProfiler.config[:user_provider] to use ip addres for identity
6
6
  * Fixed bug where unviewed missing ids never got cleared
7
7
  * Supress all '/assets/' in the rails tie (makes debugging easier)
8
+ * record_sql was mega buggy
data/README.md CHANGED
@@ -1,13 +1,6 @@
1
1
  # rack-mini-profiler
2
2
 
3
- Middleware that displays speed badge for every html page.
4
-
5
- ## What does it do
6
-
7
- MiniProfiler keeps you aware of your site's performance as you are developing it.
8
- It does this by....
9
-
10
- `env['profiler.mini']` is the profiler
3
+ Middleware that displays speed badge for every html page. Designed to work both in production and in development.
11
4
 
12
5
  ## Using mini-profiler in your app
13
6
 
@@ -16,12 +9,9 @@ Install/add to Gemfile
16
9
  ```ruby
17
10
  gem 'rack-mini-profiler'
18
11
  ```
19
-
20
- Add it to your middleware stack:
21
-
22
12
  Using Rails:
23
13
 
24
- All you have to do is include the Gem and you're good to go.
14
+ All you have to do is include the Gem and you're good to go in development.
25
15
 
26
16
  Using Builder:
27
17
 
@@ -58,15 +48,17 @@ You can set configuration options using the configuration accessor on Rack::Mini
58
48
 
59
49
  ```
60
50
  # Have Mini Profiler show up on the right
61
- Rack::MiniProfiler.configuration[:position] = 'right'
51
+ Rack::MiniProfiler.config.position = 'right'
62
52
  ```
63
53
 
64
54
  In a Rails app, this can be done conveniently in an initializer such as config/initializers/mini_profiler.rb.
65
55
 
66
56
  ## Available Options
67
57
 
68
- * authorize_cb - A lambda callback you can set to determine whether or not mini_profiler should be visible on a given request. Default in a Rails environment is only on in development mode. If in a Rack app, the default is always on.
58
+ * pre_authorize_cb - A lambda callback you can set to determine whether or not mini_profiler should be visible on a given request. Default in a Rails environment is only on in development mode. If in a Rack app, the default is always on.
59
+ * post_authorize_cb - A lambda that is called after your request executed to ensure you really have access to the results.
69
60
  * position - Can either be 'right' or 'left'. Default is 'left'.
70
- * skip_schema_queries - Whether or not you want to log the queries about the schema of your tables. Default is 'true'
61
+ * skip_schema_queries - Whether or not you want to log the queries about the schema of your tables. Default is 'false', 'true' in rails development.
62
+
71
63
 
72
64
 
@@ -475,6 +475,18 @@ var MiniProfiler = (function ($) {
475
475
  })();
476
476
  }
477
477
 
478
+ // also fetch results after ExtJS requests, in case it is being used
479
+ if (typeof (Ext) != 'undefined' && typeof (Ext.Ajax) != 'undefined' && typeof (Ext.Ajax.on) != 'undefined') {
480
+ // Ext.Ajax is a singleton, so we just have to attach to its 'requestcomplete' event
481
+ Ext.Ajax.on('requestcomplete', function(e, xhr, settings) {
482
+ var stringIds = xhr.getResponseHeader('X-MiniProfiler-Ids');
483
+ if (stringIds) {
484
+ var ids = typeof JSON != 'undefined' ? JSON.parse(stringIds) : eval(stringIds);
485
+ fetchResults(ids);
486
+ }
487
+ });
488
+ }
489
+
478
490
  // some elements want to be hidden on certain doc events
479
491
  bindDocumentEvents();
480
492
  };
@@ -0,0 +1,51 @@
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 :auto_inject, :base_url_path, :pre_authorize_cb, :post_authorize_cb, :position,
16
+ :backtrace_remove, :backtrace_filter, :skip_schema_queries,
17
+ :storage, :user_provider, :storage_instance, :storage_options, :skip_paths
18
+
19
+ def self.default
20
+ new.instance_eval {
21
+ @auto_inject = true # automatically inject on every html page
22
+ @base_url_path = "/mini-profiler-resources/"
23
+
24
+ # called prior to rack chain, to ensure we are allowed to profile
25
+ @pre_authorize_cb = lambda {|env| true}
26
+
27
+ # called after rack chain, to ensure we are REALLY allowed to profile
28
+ @post_authorize_cb = nil
29
+ @position = 'left' # Where it is displayed
30
+ @skip_schema_queries = false
31
+ @storage = MiniProfiler::MemoryStore
32
+ @user_provider = Proc.new{|env| Rack::Request.new(env).ip}
33
+ self
34
+ }
35
+ end
36
+
37
+ def merge!(config)
38
+ return unless config
39
+ if Hash === config
40
+ config.each{|k,v| instance_variable_set "@#{k}",v}
41
+ else
42
+ self.class.attributes.each{ |k|
43
+ v = config.send k
44
+ instance_variable_set "@#{k}", v if v
45
+ }
46
+ end
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -11,6 +11,7 @@ require 'mini_profiler/storage/abstract_store'
11
11
  require 'mini_profiler/storage/memory_store'
12
12
  require 'mini_profiler/storage/redis_store'
13
13
  require 'mini_profiler/storage/file_store'
14
+ require 'mini_profiler/config'
14
15
 
15
16
  module Rack
16
17
 
@@ -27,28 +28,13 @@ module Rack
27
28
  rand(36**20).to_s(36)
28
29
  end
29
30
 
30
- # Defaults for MiniProfiler's configuration
31
- def self.configuration_defaults
32
- {
33
- :auto_inject => true, # automatically inject on every html page
34
- :base_url_path => "/mini-profiler-resources/",
35
- :authorize_cb => lambda {|env| true}, # callback returns true if this request is authorized to profile
36
- :position => 'left', # Where it is displayed
37
- :backtrace_remove => nil,
38
- :backtrace_filter => nil,
39
- :skip_schema_queries => true,
40
- :storage => MiniProfiler::MemoryStore,
41
- :user_provider => Proc.new{|env| Rack::Request.new(env).ip }
42
- }
43
- end
44
-
45
- def self.reset_configuration
46
- @configuration = configuration_defaults
31
+ def self.reset_config
32
+ @config = Config.default
47
33
  end
48
34
 
49
35
  # So we can change the configuration if we want
50
- def self.configuration
51
- @configuration ||= configuration_defaults.dup
36
+ def self.config
37
+ @config ||= Config.default
52
38
  end
53
39
 
54
40
  def self.share_template
@@ -59,19 +45,19 @@ module Rack
59
45
  #
60
46
  # options:
61
47
  # :auto_inject - should script be automatically injected on every html page (not xhr)
62
- def initialize(app, opts={})
48
+ def initialize(app, config = nil)
63
49
  @@instance = self
64
- MiniProfiler.configuration.merge!(opts)
65
- @options = MiniProfiler.configuration
50
+ MiniProfiler.config.merge!(config)
51
+ @config = MiniProfiler.config
66
52
  @app = app
67
- @options[:base_url_path] << "/" unless @options[:base_url_path].end_with? "/"
68
- unless @options[:storage_instance]
69
- @storage = @options[:storage_instance] = @options[:storage].new(@options[:storage_options])
53
+ @config.base_url_path << "/" unless @config.base_url_path.end_with? "/"
54
+ unless @config.storage_instance
55
+ @storage = @config.storage_instance = @config.storage.new(@config.storage_options)
70
56
  end
71
57
  end
72
58
 
73
59
  def user(env)
74
- options[:user_provider].call(env)
60
+ @config.user_provider.call(env)
75
61
  end
76
62
 
77
63
  def serve_results(env)
@@ -97,7 +83,7 @@ module Rack
97
83
 
98
84
  # Otherwise give the HTML back
99
85
  html = MiniProfiler.share_template.dup
100
- html.gsub!(/\{path\}/, @options[:base_url_path])
86
+ html.gsub!(/\{path\}/, @config.base_url_path)
101
87
  html.gsub!(/\{version\}/, MiniProfiler::VERSION)
102
88
  html.gsub!(/\{json\}/, result_json)
103
89
  html.gsub!(/\{includes\}/, get_profile_script(env))
@@ -110,7 +96,7 @@ module Rack
110
96
  end
111
97
 
112
98
  def serve_html(env)
113
- file_name = env['PATH_INFO'][(@options[:base_url_path].length)..1000]
99
+ file_name = env['PATH_INFO'][(@config.base_url_path.length)..1000]
114
100
  return serve_results(env) if file_name.eql?('results')
115
101
  full_path = ::File.expand_path("../html/#{file_name}", ::File.dirname(__FILE__))
116
102
  return [404, {}, ["Not found"]] unless ::File.exists? full_path
@@ -128,7 +114,23 @@ module Rack
128
114
  # we use TLS cause we need access to this from sql blocks and code blocks that have no access to env
129
115
  Thread.current['profiler.mini.private'] = c
130
116
  end
131
-
117
+
118
+ def self.discard_results
119
+ current[:discard] = true if current
120
+ end
121
+
122
+ def self.has_profiling_cookie?(env)
123
+ env['HTTP_COOKIE'] && env['HTTP_COOKIE'].include?("__profilin=stylin")
124
+ end
125
+
126
+ def self.remove_profiling_cookie(headers)
127
+ Rack::Utils.delete_cookie_header!(headers, '__profilin')
128
+ end
129
+
130
+ def self.set_profiling_cookie(headers)
131
+ Rack::Utils.set_cookie_header!(headers, '__profilin', 'stylin')
132
+ end
133
+
132
134
  def current
133
135
  MiniProfiler.current
134
136
  end
@@ -137,34 +139,45 @@ module Rack
137
139
  MiniProfiler.current=c
138
140
  end
139
141
 
140
- def options
141
- @options
142
+ def config
143
+ @config
142
144
  end
143
145
 
144
146
  def self.create_current(env={}, options={})
145
147
  # profiling the request
146
148
  self.current = {}
147
- self.current['inject_js'] = options[:auto_inject] && (!env['HTTP_X_REQUESTED_WITH'].eql? 'XMLHttpRequest')
149
+ self.current['inject_js'] = config.auto_inject && (!env['HTTP_X_REQUESTED_WITH'].eql? 'XMLHttpRequest')
148
150
  self.current['page_struct'] = PageTimerStruct.new(env)
149
151
  self.current['current_timer'] = current['page_struct']['Root']
150
152
  end
151
153
 
154
+
152
155
  def call(env)
153
156
  status = headers = body = nil
154
157
 
158
+ path = env['PATH_INFO']
155
159
  # only profile if authorized
156
- return @app.call(env) unless @options[:authorize_cb].call(env)
160
+ if !@config.pre_authorize_cb.call(env) ||
161
+ (@config.skip_paths && @config.skip_paths.any?{ |p| path[0,p.length] == p}) ||
162
+ env["QUERY_STRING"] =~ /pp=skip/
163
+
164
+ status,headers,body = @app.call(env)
165
+ if @config.post_authorize_cb
166
+ if @config.post_authorize_cb.call(env)
167
+ self.class.set_profiling_cookie(headers)
168
+ end
169
+ end
170
+ return [status,headers,body]
171
+ end
157
172
 
158
- # handle all /mini-profiler requests here
159
- return serve_html(env) if env['PATH_INFO'].start_with? @options[:base_url_path]
173
+ # handle all /mini-profiler requests here
174
+ return serve_html(env) if env['PATH_INFO'].start_with? @config.base_url_path
160
175
 
161
- MiniProfiler.create_current(env, @options)
162
- if env["QUERY_STRING"] =~ /pp=skip-backtrace/
176
+ MiniProfiler.create_current(env, @config)
177
+ if env["QUERY_STRING"] =~ /pp=no-backtrace/
163
178
  current['skip-backtrace'] = true
164
179
  end
165
-
166
- start = Time.now
167
-
180
+
168
181
  done_sampling = false
169
182
  quit_sampler = false
170
183
  backtraces = nil
@@ -172,10 +185,16 @@ module Rack
172
185
  backtraces = []
173
186
  t = Thread.current
174
187
  Thread.new {
188
+ require 'stacktrace'
189
+ if !t.respond_to? :stacktrace
190
+ quit_sampler = true
191
+ return
192
+ end
175
193
  i = 10000 # for sanity never grab more than 10k samples
176
- unless done_sampling || i < 0
194
+ while i > 0
195
+ break if done_sampling
177
196
  i -= 1
178
- backtraces << t.backtrace
197
+ backtraces << t.stacktrace
179
198
  sleep 0.001
180
199
  end
181
200
  quit_sampler = true
@@ -183,8 +202,9 @@ module Rack
183
202
  end
184
203
 
185
204
  status, headers, body = nil
205
+ start = Time.now
186
206
  begin
187
- status,headers, body = @app.call(env)
207
+ status,headers,body = @app.call(env)
188
208
  ensure
189
209
  if backtraces
190
210
  done_sampling = true
@@ -192,9 +212,34 @@ module Rack
192
212
  end
193
213
  end
194
214
 
215
+ skip_it = current['discard']
216
+ if @config.post_authorize_cb && !@config.post_authorize_cb.call(env)
217
+ self.class.remove_profiling_cookie(headers)
218
+ skip_it = true
219
+ end
220
+
221
+ return [status,headers,body] if skip_it
222
+
223
+ # we must do this here, otherwise current['discard'] is not being properly treated
224
+ if env["QUERY_STRING"] =~ /pp=env/
225
+ body.close if body.respond_to? :close
226
+ return dump_env env
227
+ end
228
+
229
+ if env["QUERY_STRING"] =~ /pp=help/
230
+ body.close if body.respond_to? :close
231
+ return help
232
+ end
233
+
195
234
  page_struct = current['page_struct']
196
235
  page_struct['Root'].record_time((Time.now - start) * 1000)
197
236
 
237
+ if backtraces
238
+ body.close if body.respond_to? :close
239
+ return analyze(backtraces, page_struct)
240
+ end
241
+
242
+
198
243
  # no matter what it is, it should be unviewed, otherwise we will miss POST
199
244
  @storage.set_unviewed(user(env), page_struct['Id'])
200
245
  @storage.save(page_struct)
@@ -226,6 +271,41 @@ module Rack
226
271
  current = nil
227
272
  end
228
273
 
274
+ def dump_env(env)
275
+ headers = {'Content-Type' => 'text/plain'}
276
+ body = ""
277
+ env.each do |k,v|
278
+ body << "#{k}: #{v}\n"
279
+ end
280
+ [200, headers, [body]]
281
+ end
282
+
283
+ def help
284
+ headers = {'Content-Type' => 'text/plain'}
285
+ body = "Append the following to your query string:
286
+
287
+ pp=help : display this screen
288
+ pp=env : display the rack environment
289
+ pp=skip : skip mini profiler for this request
290
+ pp=no-backtrace : don't collect stack traces from all the SQL calls
291
+ pp=sample : sample stack traces and return a report isolating heavy usage (requires the stacktrace gem)
292
+ "
293
+ #headers['Content-Length'] = body.length
294
+ [200, headers, [body]]
295
+ end
296
+
297
+ def analyze(traces, page_struct)
298
+ headers = {'Content-Type' => 'text/plain'}
299
+ body = "Collected: #{traces.count} stack traces. Duration(ms): #{page_struct.duration_ms}"
300
+ traces.each do |trace|
301
+ body << "\n\n"
302
+ trace.each do |frame|
303
+ body << "#{frame.klass} #{frame.method}\n"
304
+ end
305
+ end
306
+ [200, headers, [body]]
307
+ end
308
+
229
309
  def ids_json(env)
230
310
  ids = [current['page_struct']["Id"]] + (@storage.get_unviewed_ids(user(env)) || [])
231
311
  ::JSON.generate(ids.uniq)
@@ -239,9 +319,9 @@ module Rack
239
319
  # * you do not want script to be automatically appended for the current page. You can also call cancel_auto_inject
240
320
  def get_profile_script(env)
241
321
  ids = ids_json(env)
242
- path = @options[:base_url_path]
322
+ path = @config.base_url_path
243
323
  version = MiniProfiler::VERSION
244
- position = @options[:position]
324
+ position = @config.position
245
325
  showTrivial = false
246
326
  showChildren = false
247
327
  maxTracesToShow = 10
@@ -13,8 +13,8 @@ module Rack
13
13
  stack_trace = ""
14
14
  # Clean up the stack trace if there are options to do so
15
15
  Kernel.caller.each do |ln|
16
- ln.gsub!(Rack::MiniProfiler.configuration[:backtrace_remove], '') if Rack::MiniProfiler.configuration[:backtrace_remove]
17
- if Rack::MiniProfiler.configuration[:backtrace_filter].nil? or ln =~ Rack::MiniProfiler.configuration[:backtrace_filter]
16
+ ln.gsub!(Rack::MiniProfiler.config.backtrace_remove, '') if Rack::MiniProfiler.config.backtrace_remove
17
+ if Rack::MiniProfiler.config.backtrace_filter.nil? or ln =~ Rack::MiniProfiler.config.backtrace_filter
18
18
  stack_trace << ln << "\n"
19
19
  end
20
20
  end
@@ -23,7 +23,7 @@ module Rack
23
23
  super("ExecuteType" => 3, # TODO
24
24
  "FormattedCommandString" => query,
25
25
  "StackTraceSnippet" => stack_trace,
26
- "StartMilliseconds" => (Time.now.to_f * 1000).to_i - page['Started'],
26
+ "StartMilliseconds" => ((Time.now.to_f * 1000).to_i - page['Started']) - duration_ms,
27
27
  "DurationMilliseconds" => duration_ms,
28
28
  "FirstFetchDurationMilliseconds" => 0,
29
29
  "Parameters" => nil,
@@ -2,22 +2,31 @@ module MiniProfilerRails
2
2
  class Railtie < ::Rails::Railtie
3
3
 
4
4
  initializer "rack_mini_profiler.configure_rails_initialization" do |app|
5
+ c = Rack::MiniProfiler.config
5
6
 
6
- # By default, only show the MiniProfiler in development mode
7
- Rack::MiniProfiler.configuration[:authorize_cb] = lambda { |env|
8
- Rails.env.development? && !(env['PATH_INFO'] =~ /^\/assets\//)
7
+ # By default, only show the MiniProfiler in development mode, in production allow profiling if post_authorize_cb is set
8
+ c.pre_authorize_cb = lambda { |env|
9
+ Rails.env.development? ||
10
+ (Rack::MiniProfiler.config.post_authorize_cb && Rack::MiniProfiler.has_profiling_cookie?(env))
9
11
  }
10
12
 
13
+ if Rails.env.development?
14
+ c.skip_paths ||= []
15
+ c.skip_paths << "/assets/"
16
+ c.skip_schema_queries = true
17
+ end
18
+
11
19
  # The file store is just so much less flaky
12
20
  tmp = Rails.root.to_s + "/tmp/miniprofiler"
13
21
  Dir::mkdir(tmp) unless File.exists?(tmp)
14
-
15
- Rack::MiniProfiler.configuration[:storage_options] = {:path => tmp}
16
- Rack::MiniProfiler.configuration[:storage] = Rack::MiniProfiler::FileStore
22
+
23
+ c.storage_options = {:path => tmp}
24
+ c.storage = Rack::MiniProfiler::FileStore
17
25
 
18
26
  # Quiet the SQL stack traces
19
- Rack::MiniProfiler.configuration[:backtrace_remove] = Rails.root.to_s + "/"
20
- Rack::MiniProfiler.configuration[:backtrace_filter] = /^\/?(app|config|lib|test)/
27
+ c.backtrace_remove = Rails.root.to_s + "/"
28
+ c.backtrace_filter = /^\/?(app|config|lib|test)/
29
+ c.skip_schema_queries = Rails.env != 'production'
21
30
 
22
31
  # Install the Middleware
23
32
  app.middleware.insert_before 'Rack::Lock', 'Rack::MiniProfiler'
@@ -43,7 +43,7 @@ module Rack
43
43
  return rval unless instance
44
44
 
45
45
  # Don't log schema queries if the option is set
46
- return rval if instance.options[:skip_schema_queries] and name =~ /SCHEMA/
46
+ return rval if instance.config.skip_schema_queries and name =~ /SCHEMA/
47
47
 
48
48
  elapsed_time = ((Time.now - t0).to_f * 1000).round(1)
49
49
  instance.record_sql(sql, elapsed_time)
@@ -59,14 +59,7 @@ module Rack
59
59
  end
60
60
 
61
61
  if defined?(::Rails)
62
- if ::Rails::VERSION::MAJOR.to_i == 3
63
- # in theory this is the right thing to do for rails 3 ... but it seems to work anyway
64
- #Rails.configuration.after_initialize do
65
- insert_instrumentation
66
- #end
67
- else
68
- insert_instrumentation
69
- end
62
+ insert_instrumentation
70
63
  end
71
64
  end
72
65
 
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "rack-mini-profiler"
3
- s.version = "0.1.1"
3
+ s.version = "0.1.2"
4
4
  s.summary = "Profiles loading speed for rack applications."
5
5
  s.authors = ["Aleks Totic","Sam Saffron", "Robin Ward"]
6
6
  s.date = "2012-04-02"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-mini-profiler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -91,6 +91,7 @@ files:
91
91
  - lib/mini_profiler/request_timer_struct.rb
92
92
  - lib/mini_profiler/timer_struct.rb
93
93
  - lib/mini_profiler/page_timer_struct.rb
94
+ - lib/mini_profiler/config.rb
94
95
  - lib/mini_profiler/body_add_proxy.rb
95
96
  - lib/mini_profiler/client_timer_struct.rb
96
97
  - lib/mini_profiler/profiler.rb
@@ -129,7 +130,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
129
130
  version: '0'
130
131
  segments:
131
132
  - 0
132
- hash: -779964803
133
+ hash: 876564877
133
134
  required_rubygems_version: !ruby/object:Gem::Requirement
134
135
  none: false
135
136
  requirements:
@@ -138,7 +139,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
138
139
  version: '0'
139
140
  segments:
140
141
  - 0
141
- hash: -779964803
142
+ hash: 876564877
142
143
  requirements: []
143
144
  rubyforge_project:
144
145
  rubygems_version: 1.8.24