rack-mini-profiler 0.1 → 0.1.5

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.

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