rack-mini-profiler 0.1.1 → 0.1.2

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.

@@ -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