rack-mini-profiler 0.1 → 0.1.5

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.

@@ -0,0 +1,26 @@
1
+ 28-June-2012 - Sam
2
+
3
+ * Started change log
4
+ * Corrected profiler so it properly captures POST requests (was supressing non 200s)
5
+ * Amended Rack.MiniProfiler.config[:user_provider] to use ip addres for identity
6
+ * Fixed bug where unviewed missing ids never got cleared
7
+ * Supress all '/assets/' in the rails tie (makes debugging easier)
8
+ * record_sql was mega buggy
9
+
10
+ 9-July-2012 - Sam
11
+
12
+ * Cleaned up mechanism for profiling in production, all you need to do now
13
+ is call Rack::MiniProfiler.authorize_request to get profiling working in
14
+ production
15
+ * Added option to display full backtraces pp=full-backtrace
16
+ * Cleaned up railties, got rid of the post authorize callback
17
+ * Version 0.1.3
18
+
19
+ 12-July-2012 - Sam
20
+
21
+ * Fixed incorrect profiling steps (was not indenting or measuring start time right
22
+ * Implemented native PG and MySql2 interceptors, this gives way more accurate times
23
+ * Refactored context so its a proper class and not a hash
24
+ * Added some more client probing built in to rails
25
+ * More tests
26
+
data/README.md CHANGED
@@ -1,27 +1,31 @@
1
1
  # rack-mini-profiler
2
2
 
3
- Middleware that displays speed badge for every html page.
3
+ Middleware that displays speed badge for every html page. Designed to work both in production and in development.
4
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
11
-
12
- ## Using mini-profiler in your app
5
+ ## Using rack-mini-profiler in your app
13
6
 
14
7
  Install/add to Gemfile
15
8
 
16
9
  ```ruby
17
10
  gem 'rack-mini-profiler'
18
11
  ```
12
+ Using Rails:
19
13
 
20
- Add it to your middleware stack:
14
+ All you have to do is include the Gem and you're good to go in development.
21
15
 
22
- Using Rails:
16
+ rack-mini-profiler is designed with production profiling in mind. To enable that just run `Rack::MiniProfiler.authorize_request` once you know a request is allowed to profile.
17
+
18
+ For example:
19
+
20
+ ```ruby
21
+ # A hook in your ApplicationController
22
+ def authorize
23
+ if current_user.is_admin?
24
+ Rack::MiniProfiler.authorize_request
25
+ end
26
+ end
27
+ ````
23
28
 
24
- All you have to do is include the Gem and you're good to go.
25
29
 
26
30
  Using Builder:
27
31
 
@@ -58,25 +62,18 @@ You can set configuration options using the configuration accessor on Rack::Mini
58
62
 
59
63
  ```
60
64
  # Have Mini Profiler show up on the right
61
- Rack::MiniProfiler.configuration[:position] = 'right'
65
+ Rack::MiniProfiler.config.position = 'right'
62
66
  ```
63
67
 
64
68
  In a Rails app, this can be done conveniently in an initializer such as config/initializers/mini_profiler.rb.
65
69
 
66
70
  ## Available Options
67
71
 
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.
72
+ * 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.
69
73
  * 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'
71
-
74
+ * 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.
72
75
 
73
- ## TODO: prior to release - pull requests welcome
76
+ ## Special query strings
74
77
 
75
- - Stack Traces for SQL called (added but mental, needs to be filtered to something usable)
76
- - Decide if we hook up SQL at the driver level (eg mysql gem) or library level (eg active record) - my personal perference is to do driver level hooks (Sam)
77
- - Add automatic instrumentation for Rails (Controller times, Action times, Partial times, Layout times)
78
- - Grab / display the parameters of SQL executed for parameterized SQL
79
- - Beef up the documentation
80
- - Auto-wire-up rails middleware
81
- - Review our API and ensure it is trivial
78
+ If you include the query string `pp=help` at the end of your request you will see the various option you have. You can use these options to extend or contract the amount of diagnostics rack-mini-profiler gathers.
82
79
 
@@ -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
  };
@@ -6,6 +6,19 @@ module Rack
6
6
  # This class holds the client timings
7
7
  class ClientTimerStruct < TimerStruct
8
8
 
9
+ def self.init_instrumentation
10
+ "<script type=\"text/javascript\">mPt=function(){var t=[];return{t:t,probe:function(n){t.push({d:new Date(),n:n})}}}()</script>"
11
+ end
12
+
13
+ def self.instrument(name,orig)
14
+ probe = "<script>mPt.probe('#{name}')</script>"
15
+ wrapped = probe
16
+ wrapped << orig
17
+ wrapped << probe
18
+ wrapped
19
+ end
20
+
21
+
9
22
  def initialize(env={})
10
23
  super
11
24
  end
@@ -21,6 +34,26 @@ module Rack
21
34
  baseTime = clientTimes['navigationStart'].to_i if clientTimes
22
35
  return unless clientTimes && baseTime
23
36
 
37
+ probes = form['clientProbes']
38
+ translated = {}
39
+ if probes
40
+ probes.each do |id, val|
41
+ name = val["n"]
42
+ translated[name] ||= {}
43
+ if translated[name][:start]
44
+ translated[name][:finish] = val["d"]
45
+ else
46
+ translated[name][:start] = val["d"]
47
+ end
48
+ end
49
+ end
50
+
51
+ translated.each do |name, data|
52
+ h = {"Name" => name, "Start" => data[:start].to_i - baseTime}
53
+ h["Duration"] = data[:finish].to_i - data[:start].to_i if data[:finish]
54
+ timings.push(h)
55
+ end
56
+
24
57
  clientTimes.keys.find_all{|k| k =~ /Start$/ }.each do |k|
25
58
  start = clientTimes[k].to_i - baseTime
26
59
  finish = clientTimes[k.sub(/Start$/, "End")].to_i - baseTime
@@ -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, :position,
16
+ :backtrace_remove, :backtrace_filter, :skip_schema_queries,
17
+ :storage, :user_provider, :storage_instance, :storage_options, :skip_paths, :authorization_mode
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
+ @position = 'left' # Where it is displayed
29
+ @skip_schema_queries = false
30
+ @storage = MiniProfiler::MemoryStore
31
+ @user_provider = Proc.new{|env| Rack::Request.new(env).ip}
32
+ @authorization_mode = :allow_all
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
@@ -0,0 +1,10 @@
1
+ class Rack::MiniProfiler::Context
2
+ attr_accessor :inject_js,:current_timer,:page_struct,:skip_backtrace,:full_backtrace,:discard, :mpt_init
3
+
4
+ def initialize(opts = {})
5
+ opts.each do |k,v|
6
+ self.instance_variable_set('@' + k, v)
7
+ end
8
+ end
9
+
10
+ end
@@ -35,15 +35,19 @@ module Rack
35
35
  def duration_ms
36
36
  @attributes['Root']['DurationMilliseconds']
37
37
  end
38
+
39
+ def root
40
+ @attributes['Root']
41
+ end
38
42
 
39
43
  def to_json(*a)
40
44
  attribs = @attributes.merge(
41
45
  "Started" => '/Date(%d)/' % @attributes['Started'],
42
46
  "DurationMilliseconds" => @attributes['Root']['DurationMilliseconds']
43
47
  )
44
- ::JSON.generate(attribs, a[0])
48
+ ::JSON.generate(attribs, :max_nesting => 100)
45
49
  end
46
50
  end
47
51
 
48
52
  end
49
- end
53
+ end
@@ -11,81 +11,117 @@ 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'
15
+ require 'mini_profiler/profiling_methods'
16
+ require 'mini_profiler/context'
14
17
 
15
18
  module Rack
16
19
 
17
20
  class MiniProfiler
18
21
 
19
- VERSION = 'rZlycOOTnzxZvxTmFuOEV0dSmu4P5m5bLrCtwJHVXPA='.freeze
20
- @@instance = nil
22
+ VERSION = 'rZlycOOTnzxZvxTmFuOEV0dSmu4P5m5bLrCtwJHVXPA=A'.freeze
21
23
 
22
- def self.instance
23
- @@instance
24
- end
24
+ class << self
25
+
26
+ include Rack::MiniProfiler::ProfilingMethods
25
27
 
26
- def self.generate_id
27
- rand(36**20).to_s(36)
28
- end
28
+ def generate_id
29
+ rand(36**20).to_s(36)
30
+ end
29
31
 
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| "TODO" }
42
- }
43
- end
32
+ def reset_config
33
+ @config = Config.default
34
+ end
44
35
 
45
- def self.reset_configuration
46
- @configuration = configuration_defaults
47
- end
36
+ # So we can change the configuration if we want
37
+ def config
38
+ @config ||= Config.default
39
+ end
48
40
 
49
- # So we can change the configuration if we want
50
- def self.configuration
51
- @configuration ||= configuration_defaults.dup
52
- end
41
+ def share_template
42
+ return @share_template unless @share_template.nil?
43
+ @share_template = ::File.read(::File.expand_path("../html/share.html", ::File.dirname(__FILE__)))
44
+ end
45
+
46
+ def current
47
+ Thread.current[:mini_profiler_private]
48
+ end
49
+
50
+ def current=(c)
51
+ # we use TLS cause we need access to this from sql blocks and code blocks that have no access to env
52
+ Thread.current[:mini_profiler_private]= c
53
+ end
54
+
55
+ # discard existing results, don't track this request
56
+ def discard_results
57
+ self.current.discard = true if current
58
+ end
59
+
60
+ # user has the mini profiler cookie, only used when config.authorization_mode == :whitelist
61
+ def has_profiling_cookie?(env)
62
+ env['HTTP_COOKIE'] && env['HTTP_COOKIE'].include?("__profilin=stylin")
63
+ end
64
+
65
+ # remove the mini profiler cookie, only used when config.authorization_mode == :whitelist
66
+ def remove_profiling_cookie(headers)
67
+ Rack::Utils.delete_cookie_header!(headers, '__profilin')
68
+ end
53
69
 
54
- def self.share_template
55
- return @share_template unless @share_template.nil?
56
- @share_template = ::File.read(::File.expand_path("../html/share.html", ::File.dirname(__FILE__)))
70
+ def set_profiling_cookie(headers)
71
+ Rack::Utils.set_cookie_header!(headers, '__profilin', 'stylin')
72
+ end
73
+
74
+ def create_current(env={}, options={})
75
+ # profiling the request
76
+ self.current = Context.new
77
+ self.current.inject_js = config.auto_inject && (!env['HTTP_X_REQUESTED_WITH'].eql? 'XMLHttpRequest')
78
+ self.current.page_struct = PageTimerStruct.new(env)
79
+ self.current.current_timer = current.page_struct['Root']
80
+ end
81
+
82
+ def authorize_request
83
+ Thread.current[:mp_authorized] = true
84
+ end
85
+
86
+ def deauthorize_request
87
+ Thread.current[:mp_authorized] = nil
88
+ end
89
+
90
+ def request_authorized?
91
+ Thread.current[:mp_authorized]
92
+ end
57
93
  end
58
94
 
59
95
  #
60
96
  # options:
61
97
  # :auto_inject - should script be automatically injected on every html page (not xhr)
62
- def initialize(app, opts={})
63
- @@instance = self
64
- MiniProfiler.configuration.merge!(opts)
65
- @options = MiniProfiler.configuration
98
+ def initialize(app, config = nil)
99
+ MiniProfiler.config.merge!(config)
100
+ @config = MiniProfiler.config
66
101
  @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])
102
+ @config.base_url_path << "/" unless @config.base_url_path.end_with? "/"
103
+ unless @config.storage_instance
104
+ @storage = @config.storage_instance = @config.storage.new(@config.storage_options)
70
105
  end
71
106
  end
72
107
 
73
108
  def user(env)
74
- options[:user_provider].call(env)
109
+ @config.user_provider.call(env)
75
110
  end
76
111
 
77
112
  def serve_results(env)
78
113
  request = Rack::Request.new(env)
79
- page_struct = @storage.load(request['id'])
114
+ id = request['id']
115
+ page_struct = @storage.load(id)
80
116
  unless page_struct
81
- @storage.set_viewed(user(env), request['Id'])
82
- return [404, {}, ["No such result #{request['id']}"]]
117
+ @storage.set_viewed(user(env), id)
118
+ return [404, {}, ["Request not found: #{request['id']} - user #{user(env)}"]]
83
119
  end
84
120
  unless page_struct['HasUserViewed']
85
121
  page_struct['ClientTimings'].init_from_form_data(env, page_struct)
86
122
  page_struct['HasUserViewed'] = true
87
123
  @storage.save(page_struct)
88
- @storage.set_viewed(user(env), page_struct['Id'])
124
+ @storage.set_viewed(user(env), id)
89
125
  end
90
126
 
91
127
  result_json = page_struct.to_json
@@ -96,7 +132,7 @@ module Rack
96
132
 
97
133
  # Otherwise give the HTML back
98
134
  html = MiniProfiler.share_template.dup
99
- html.gsub!(/\{path\}/, @options[:base_url_path])
135
+ html.gsub!(/\{path\}/, @config.base_url_path)
100
136
  html.gsub!(/\{version\}/, MiniProfiler::VERSION)
101
137
  html.gsub!(/\{json\}/, result_json)
102
138
  html.gsub!(/\{includes\}/, get_profile_script(env))
@@ -109,7 +145,7 @@ module Rack
109
145
  end
110
146
 
111
147
  def serve_html(env)
112
- file_name = env['PATH_INFO'][(@options[:base_url_path].length)..1000]
148
+ file_name = env['PATH_INFO'][(@config.base_url_path.length)..1000]
113
149
  return serve_results(env) if file_name.eql?('results')
114
150
  full_path = ::File.expand_path("../html/#{file_name}", ::File.dirname(__FILE__))
115
151
  return [404, {}, ["Not found"]] unless ::File.exists? full_path
@@ -119,15 +155,7 @@ module Rack
119
155
  f.serving env
120
156
  end
121
157
 
122
- def self.current
123
- Thread.current['profiler.mini.private']
124
- end
125
-
126
- def self.current=(c)
127
- # we use TLS cause we need access to this from sql blocks and code blocks that have no access to env
128
- Thread.current['profiler.mini.private'] = c
129
- end
130
-
158
+
131
159
  def current
132
160
  MiniProfiler.current
133
161
  end
@@ -136,54 +164,73 @@ module Rack
136
164
  MiniProfiler.current=c
137
165
  end
138
166
 
139
- def options
140
- @options
141
- end
142
167
 
143
- def self.create_current(env={}, options={})
144
- # profiling the request
145
- self.current = {}
146
- self.current['inject_js'] = options[:auto_inject] && (!env['HTTP_X_REQUESTED_WITH'].eql? 'XMLHttpRequest')
147
- self.current['page_struct'] = PageTimerStruct.new(env)
148
- self.current['current_timer'] = current['page_struct']['Root']
168
+ def config
169
+ @config
149
170
  end
150
171
 
172
+
151
173
  def call(env)
152
- status = headers = body = nil
174
+ status = headers = body = nil
175
+ path = env['PATH_INFO']
153
176
 
154
- # only profile if authorized
155
- return @app.call(env) unless @options[:authorize_cb].call(env)
177
+ skip_it = (@config.pre_authorize_cb && !@config.pre_authorize_cb.call(env)) ||
178
+ (@config.skip_paths && @config.skip_paths.any?{ |p| path[0,p.length] == p}) ||
179
+ env["QUERY_STRING"] =~ /pp=skip/
180
+
181
+ has_profiling_cookie = MiniProfiler.has_profiling_cookie?(env)
182
+
183
+ if skip_it || (@config.authorization_mode == :whitelist && !has_profiling_cookie)
184
+ status,headers,body = @app.call(env)
185
+ if !skip_it && @config.authorization_mode == :whitelist && !has_profiling_cookie && MiniProfiler.request_authorized?
186
+ MiniProfiler.set_profiling_cookie(headers)
187
+ end
188
+ return [status,headers,body]
189
+ end
156
190
 
157
- # handle all /mini-profiler requests here
158
- return serve_html(env) if env['PATH_INFO'].start_with? @options[:base_url_path]
191
+ # handle all /mini-profiler requests here
192
+ return serve_html(env) if env['PATH_INFO'].start_with? @config.base_url_path
159
193
 
160
- MiniProfiler.create_current(env, @options)
161
- if env["QUERY_STRING"] =~ /pp=skip-backtrace/
162
- current['skip-backtrace'] = true
194
+ MiniProfiler.create_current(env, @config)
195
+ MiniProfiler.deauthorize_request if @config.authorization_mode == :whitelist
196
+ if env["QUERY_STRING"] =~ /pp=no-backtrace/
197
+ current.skip_backtrace = true
198
+ elsif env["QUERY_STRING"] =~ /pp=full-backtrace/
199
+ current.full_backtrace = true
163
200
  end
164
201
 
165
- start = Time.now
166
-
167
202
  done_sampling = false
168
203
  quit_sampler = false
169
204
  backtraces = nil
205
+ missing_stacktrace = false
170
206
  if env["QUERY_STRING"] =~ /pp=sample/
171
207
  backtraces = []
172
208
  t = Thread.current
173
209
  Thread.new {
174
- i = 10000 # for sanity never grab more than 10k samples
175
- unless done_sampling || i < 0
176
- i -= 1
177
- backtraces << t.backtrace
178
- sleep 0.001
210
+ begin
211
+ require 'stacktrace' rescue nil
212
+ if !t.respond_to? :stacktrace
213
+ missing_stacktrace = true
214
+ quit_sampler = true
215
+ return
216
+ end
217
+ i = 10000 # for sanity never grab more than 10k samples
218
+ while i > 0
219
+ break if done_sampling
220
+ i -= 1
221
+ backtraces << t.stacktrace
222
+ sleep 0.001
223
+ end
224
+ ensure
225
+ quit_sampler = true
179
226
  end
180
- quit_sampler = true
181
227
  }
182
228
  end
183
229
 
184
230
  status, headers, body = nil
231
+ start = Time.now
185
232
  begin
186
- status,headers, body = @app.call(env)
233
+ status,headers,body = @app.call(env)
187
234
  ensure
188
235
  if backtraces
189
236
  done_sampling = true
@@ -191,13 +238,41 @@ module Rack
191
238
  end
192
239
  end
193
240
 
194
- page_struct = current['page_struct']
241
+ skip_it = current.discard
242
+ if (config.authorization_mode == :whitelist && !MiniProfiler.request_authorized?)
243
+ MiniProfiler.remove_profiling_cookie(headers)
244
+ skip_it = true
245
+ end
246
+
247
+ return [status,headers,body] if skip_it
248
+
249
+ # we must do this here, otherwise current[:discard] is not being properly treated
250
+ if env["QUERY_STRING"] =~ /pp=env/
251
+ body.close if body.respond_to? :close
252
+ return dump_env env
253
+ end
254
+
255
+ if env["QUERY_STRING"] =~ /pp=help/
256
+ body.close if body.respond_to? :close
257
+ return help
258
+ end
259
+
260
+ page_struct = current.page_struct
195
261
  page_struct['Root'].record_time((Time.now - start) * 1000)
196
262
 
197
- # inject headers, script
263
+ if backtraces
264
+ body.close if body.respond_to? :close
265
+ return help(:stacktrace) if missing_stacktrace
266
+ return analyze(backtraces, page_struct)
267
+ end
268
+
269
+
270
+ # no matter what it is, it should be unviewed, otherwise we will miss POST
271
+ @storage.set_unviewed(user(env), page_struct['Id'])
272
+ @storage.save(page_struct)
273
+
274
+ # inject headers, script
198
275
  if status == 200
199
- @storage.save(page_struct)
200
- @storage.set_unviewed(user(env), page_struct['Id'])
201
276
 
202
277
  # inject header
203
278
  if headers.is_a? Hash
@@ -205,7 +280,7 @@ module Rack
205
280
  end
206
281
 
207
282
  # inject script
208
- if current['inject_js'] \
283
+ if current.inject_js \
209
284
  && headers.has_key?('Content-Type') \
210
285
  && !headers['Content-Type'].match(/text\/html/).nil? then
211
286
  body = MiniProfiler::BodyAddProxy.new(body, self.get_profile_script(env))
@@ -223,8 +298,68 @@ module Rack
223
298
  current = nil
224
299
  end
225
300
 
301
+ def dump_env(env)
302
+ headers = {'Content-Type' => 'text/plain'}
303
+ body = ""
304
+ env.each do |k,v|
305
+ body << "#{k}: #{v}\n"
306
+ end
307
+ [200, headers, [body]]
308
+ end
309
+
310
+ def help(category = nil)
311
+ headers = {'Content-Type' => 'text/plain'}
312
+ body = "Append the following to your query string:
313
+
314
+ pp=help : display this screen
315
+ pp=env : display the rack environment
316
+ pp=skip : skip mini profiler for this request
317
+ pp=no-backtrace : don't collect stack traces from all the SQL executed
318
+ pp=full-backtrace : enable full backtrace for SQL executed
319
+ pp=sample : sample stack traces and return a report isolating heavy usage (requires the stacktrace gem)
320
+ "
321
+ if (category == :stacktrace)
322
+ body = "pp=stacktrace requires the stacktrace gem - add gem 'stacktrace' to your Gemfile"
323
+ end
324
+
325
+ [200, headers, [body]]
326
+ end
327
+
328
+ def analyze(traces, page_struct)
329
+ headers = {'Content-Type' => 'text/plain'}
330
+ body = "Collected: #{traces.count} stack traces. Duration(ms): #{page_struct.duration_ms}"
331
+
332
+ seen = {}
333
+ fulldump = ""
334
+ traces.each do |trace|
335
+ fulldump << "\n\n"
336
+ distinct = {}
337
+ trace.each do |frame|
338
+ name = "#{frame.klass} #{frame.method}"
339
+ unless distinct[name]
340
+ distinct[name] = true
341
+ seen[name] ||= 0
342
+ seen[name] += 1
343
+ end
344
+ fulldump << name << "\n"
345
+ end
346
+ end
347
+
348
+ body << "\n\nStack Trace Analysis\n"
349
+ seen.to_a.sort{|x,y| y[1] <=> x[1]}.each do |name, count|
350
+ if count > traces.count / 10
351
+ body << "#{name} x #{count}\n"
352
+ end
353
+ end
354
+
355
+ body << "\n\n\nRaw traces \n"
356
+ body << fulldump
357
+
358
+ [200, headers, [body]]
359
+ end
360
+
226
361
  def ids_json(env)
227
- ids = [current['page_struct']["Id"]] + (@storage.get_unviewed_ids(user(env)) || [])
362
+ ids = [current.page_struct["Id"]] + (@storage.get_unviewed_ids(user(env)) || [])
228
363
  ::JSON.generate(ids.uniq)
229
364
  end
230
365
 
@@ -236,14 +371,14 @@ module Rack
236
371
  # * you do not want script to be automatically appended for the current page. You can also call cancel_auto_inject
237
372
  def get_profile_script(env)
238
373
  ids = ids_json(env)
239
- path = @options[:base_url_path]
374
+ path = @config.base_url_path
240
375
  version = MiniProfiler::VERSION
241
- position = @options[:position]
376
+ position = @config.position
242
377
  showTrivial = false
243
378
  showChildren = false
244
379
  maxTracesToShow = 10
245
380
  showControls = false
246
- currentId = current['page_struct']["Id"]
381
+ currentId = current.page_struct["Id"]
247
382
  authorized = true
248
383
  useExistingjQuery = false
249
384
  # TODO : cache this snippet
@@ -255,52 +390,13 @@ module Rack
255
390
  end
256
391
  # replace the '{{' and '}}''
257
392
  script.gsub!(/\{\{/, '{').gsub!(/\}\}/, '}')
258
- current['inject_js'] = false
393
+ current.inject_js = false
259
394
  script
260
395
  end
261
396
 
262
397
  # cancels automatic injection of profile script for the current page
263
398
  def cancel_auto_inject(env)
264
- current['inject_js'] = false
265
- end
266
-
267
- # perform a profiling step on given block
268
- def self.step(name)
269
- if current
270
- old_timer = current['current_timer']
271
- new_step = RequestTimerStruct.new(name, current['page_struct'])
272
- current['current_timer'] = new_step
273
- new_step['Name'] = name
274
- start = Time.now
275
- result = yield if block_given?
276
- new_step.record_time((Time.now - start)*1000)
277
- old_timer.add_child(new_step)
278
- current['current_timer'] = old_timer
279
- result
280
- else
281
- yield if block_given?
282
- end
283
- end
284
-
285
- def self.profile_method(klass, method, &blk)
286
- default_name = klass.to_s + " " + method.to_s
287
- with_profiling = (method.to_s + "_with_mini_profiler").intern
288
- without_profiling = (method.to_s + "_without_mini_profiler").intern
289
-
290
- klass.send :alias_method, without_profiling, method
291
- klass.send :define_method, with_profiling do |*args, &orig|
292
- name = default_name
293
- name = blk.bind(self).call(*args) if blk
294
- ::Rack::MiniProfiler.step name do
295
- self.send without_profiling, *args, &orig
296
- end
297
- end
298
- klass.send :alias_method, method, with_profiling
299
- end
300
-
301
- def record_sql(query, elapsed_ms)
302
- c = current
303
- c['current_timer'].add_sql(query, elapsed_ms, c['page_struct'], c['skip-backtrace']) if (c && c['current_timer'])
399
+ current.inject_js = false
304
400
  end
305
401
 
306
402
  end