rack-mini-profiler 0.1 → 0.1.21
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.
- data/CHANGELOG +99 -0
- data/README.md +72 -24
- data/lib/html/includes.css +75 -1
- data/lib/html/includes.js +42 -11
- data/lib/html/includes.less +2 -2
- data/lib/html/includes.tmpl +2 -2
- data/lib/html/profile_handler.js +9 -9
- data/lib/mini_profiler/client_settings.rb +65 -0
- data/lib/mini_profiler/client_timer_struct.rb +38 -3
- data/lib/mini_profiler/config.rb +52 -0
- data/lib/mini_profiler/context.rb +10 -0
- data/lib/mini_profiler/gc_profiler.rb +84 -0
- data/lib/mini_profiler/page_timer_struct.rb +7 -3
- data/lib/mini_profiler/profiler.rb +312 -142
- data/lib/mini_profiler/profiling_methods.rb +108 -0
- data/lib/mini_profiler/request_timer_struct.rb +40 -9
- data/lib/mini_profiler/sql_timer_struct.rb +26 -5
- data/lib/mini_profiler/storage/abstract_store.rb +0 -0
- data/lib/mini_profiler/storage/file_store.rb +8 -7
- data/lib/mini_profiler/storage/memcache_store.rb +51 -0
- data/lib/mini_profiler/storage/memory_store.rb +1 -6
- data/lib/mini_profiler/storage/redis_store.rb +2 -1
- data/lib/mini_profiler/timer_struct.rb +3 -1
- data/lib/mini_profiler_rails/railtie.rb +66 -11
- data/lib/patches/sql_patches.rb +198 -39
- data/lib/rack-mini-profiler.rb +4 -4
- data/rack-mini-profiler.gemspec +8 -8
- metadata +16 -10
- data/lib/html/MiniProfilerHandler.cs +0 -419
- data/lib/mini_profiler/body_add_proxy.rb +0 -45
|
@@ -6,86 +6,111 @@ require 'mini_profiler/page_timer_struct'
|
|
|
6
6
|
require 'mini_profiler/sql_timer_struct'
|
|
7
7
|
require 'mini_profiler/client_timer_struct'
|
|
8
8
|
require 'mini_profiler/request_timer_struct'
|
|
9
|
-
require 'mini_profiler/body_add_proxy'
|
|
10
9
|
require 'mini_profiler/storage/abstract_store'
|
|
10
|
+
require 'mini_profiler/storage/memcache_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'
|
|
17
|
+
require 'mini_profiler/client_settings'
|
|
18
|
+
require 'mini_profiler/gc_profiler'
|
|
14
19
|
|
|
15
20
|
module Rack
|
|
16
21
|
|
|
17
22
|
class MiniProfiler
|
|
18
23
|
|
|
19
|
-
VERSION = '
|
|
20
|
-
@@instance = nil
|
|
24
|
+
VERSION = '107'.freeze
|
|
21
25
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
class << self
|
|
27
|
+
|
|
28
|
+
include Rack::MiniProfiler::ProfilingMethods
|
|
25
29
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
def generate_id
|
|
31
|
+
rand(36**20).to_s(36)
|
|
32
|
+
end
|
|
29
33
|
|
|
30
|
-
|
|
31
|
-
|
|
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
|
|
34
|
+
def reset_config
|
|
35
|
+
@config = Config.default
|
|
36
|
+
end
|
|
44
37
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
38
|
+
# So we can change the configuration if we want
|
|
39
|
+
def config
|
|
40
|
+
@config ||= Config.default
|
|
41
|
+
end
|
|
48
42
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
43
|
+
def share_template
|
|
44
|
+
return @share_template unless @share_template.nil?
|
|
45
|
+
@share_template = ::File.read(::File.expand_path("../html/share.html", ::File.dirname(__FILE__)))
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def current
|
|
49
|
+
Thread.current[:mini_profiler_private]
|
|
50
|
+
end
|
|
53
51
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
def current=(c)
|
|
53
|
+
# we use TLS cause we need access to this from sql blocks and code blocks that have no access to env
|
|
54
|
+
Thread.current[:mini_profiler_private]= c
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# discard existing results, don't track this request
|
|
58
|
+
def discard_results
|
|
59
|
+
self.current.discard = true if current
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def create_current(env={}, options={})
|
|
63
|
+
# profiling the request
|
|
64
|
+
self.current = Context.new
|
|
65
|
+
self.current.inject_js = config.auto_inject && (!env['HTTP_X_REQUESTED_WITH'].eql? 'XMLHttpRequest')
|
|
66
|
+
self.current.page_struct = PageTimerStruct.new(env)
|
|
67
|
+
self.current.current_timer = current.page_struct['Root']
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def authorize_request
|
|
71
|
+
Thread.current[:mp_authorized] = true
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def deauthorize_request
|
|
75
|
+
Thread.current[:mp_authorized] = nil
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def request_authorized?
|
|
79
|
+
Thread.current[:mp_authorized]
|
|
80
|
+
end
|
|
57
81
|
end
|
|
58
82
|
|
|
59
83
|
#
|
|
60
84
|
# options:
|
|
61
85
|
# :auto_inject - should script be automatically injected on every html page (not xhr)
|
|
62
|
-
def initialize(app,
|
|
63
|
-
|
|
64
|
-
MiniProfiler.
|
|
65
|
-
@options = MiniProfiler.configuration
|
|
86
|
+
def initialize(app, config = nil)
|
|
87
|
+
MiniProfiler.config.merge!(config)
|
|
88
|
+
@config = MiniProfiler.config
|
|
66
89
|
@app = app
|
|
67
|
-
@
|
|
68
|
-
unless @
|
|
69
|
-
@
|
|
90
|
+
@config.base_url_path << "/" unless @config.base_url_path.end_with? "/"
|
|
91
|
+
unless @config.storage_instance
|
|
92
|
+
@config.storage_instance = @config.storage.new(@config.storage_options)
|
|
70
93
|
end
|
|
94
|
+
@storage = @config.storage_instance
|
|
71
95
|
end
|
|
72
96
|
|
|
73
97
|
def user(env)
|
|
74
|
-
|
|
98
|
+
@config.user_provider.call(env)
|
|
75
99
|
end
|
|
76
100
|
|
|
77
101
|
def serve_results(env)
|
|
78
102
|
request = Rack::Request.new(env)
|
|
79
|
-
|
|
103
|
+
id = request['id']
|
|
104
|
+
page_struct = @storage.load(id)
|
|
80
105
|
unless page_struct
|
|
81
|
-
@storage.set_viewed(user(env),
|
|
82
|
-
|
|
106
|
+
@storage.set_viewed(user(env), id)
|
|
107
|
+
return [404, {}, ["Request not found: #{request['id']} - user #{user(env)}"]]
|
|
83
108
|
end
|
|
84
109
|
unless page_struct['HasUserViewed']
|
|
85
|
-
|
|
110
|
+
page_struct['ClientTimings'] = ClientTimerStruct.init_from_form_data(env, page_struct)
|
|
86
111
|
page_struct['HasUserViewed'] = true
|
|
87
112
|
@storage.save(page_struct)
|
|
88
|
-
@storage.set_viewed(user(env),
|
|
113
|
+
@storage.set_viewed(user(env), id)
|
|
89
114
|
end
|
|
90
115
|
|
|
91
116
|
result_json = page_struct.to_json
|
|
@@ -96,7 +121,7 @@ module Rack
|
|
|
96
121
|
|
|
97
122
|
# Otherwise give the HTML back
|
|
98
123
|
html = MiniProfiler.share_template.dup
|
|
99
|
-
html.gsub!(/\{path\}/, @
|
|
124
|
+
html.gsub!(/\{path\}/, @config.base_url_path)
|
|
100
125
|
html.gsub!(/\{version\}/, MiniProfiler::VERSION)
|
|
101
126
|
html.gsub!(/\{json\}/, result_json)
|
|
102
127
|
html.gsub!(/\{includes\}/, get_profile_script(env))
|
|
@@ -109,25 +134,26 @@ module Rack
|
|
|
109
134
|
end
|
|
110
135
|
|
|
111
136
|
def serve_html(env)
|
|
112
|
-
file_name = env['PATH_INFO'][(@
|
|
137
|
+
file_name = env['PATH_INFO'][(@config.base_url_path.length)..1000]
|
|
113
138
|
return serve_results(env) if file_name.eql?('results')
|
|
114
139
|
full_path = ::File.expand_path("../html/#{file_name}", ::File.dirname(__FILE__))
|
|
115
140
|
return [404, {}, ["Not found"]] unless ::File.exists? full_path
|
|
116
141
|
f = Rack::File.new nil
|
|
117
142
|
f.path = full_path
|
|
118
|
-
f.cache_control = "max-age:86400"
|
|
119
|
-
f.serving env
|
|
120
|
-
end
|
|
121
143
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
144
|
+
begin
|
|
145
|
+
f.cache_control = "max-age:86400"
|
|
146
|
+
f.serving env
|
|
147
|
+
rescue
|
|
148
|
+
# old versions of rack have a different api
|
|
149
|
+
status, headers, body = f.serving
|
|
150
|
+
headers.merge! 'Cache-Control' => "max-age:86400"
|
|
151
|
+
[status, headers, body]
|
|
152
|
+
end
|
|
125
153
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
end
|
|
130
|
-
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
|
|
131
157
|
def current
|
|
132
158
|
MiniProfiler.current
|
|
133
159
|
end
|
|
@@ -136,54 +162,118 @@ module Rack
|
|
|
136
162
|
MiniProfiler.current=c
|
|
137
163
|
end
|
|
138
164
|
|
|
139
|
-
def options
|
|
140
|
-
@options
|
|
141
|
-
end
|
|
142
165
|
|
|
143
|
-
def
|
|
144
|
-
|
|
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']
|
|
166
|
+
def config
|
|
167
|
+
@config
|
|
149
168
|
end
|
|
150
169
|
|
|
170
|
+
|
|
151
171
|
def call(env)
|
|
152
|
-
status = headers = body = nil
|
|
153
172
|
|
|
154
|
-
|
|
155
|
-
return @app.call(env) unless @options[:authorize_cb].call(env)
|
|
173
|
+
client_settings = ClientSettings.new(env)
|
|
156
174
|
|
|
157
|
-
|
|
158
|
-
|
|
175
|
+
status = headers = body = nil
|
|
176
|
+
query_string = env['QUERY_STRING']
|
|
177
|
+
path = env['PATH_INFO']
|
|
159
178
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
179
|
+
skip_it = (@config.pre_authorize_cb && !@config.pre_authorize_cb.call(env)) ||
|
|
180
|
+
(@config.skip_paths && @config.skip_paths.any?{ |p| path[0,p.length] == p}) ||
|
|
181
|
+
query_string =~ /pp=skip/
|
|
182
|
+
|
|
183
|
+
has_profiling_cookie = client_settings.has_cookie?
|
|
184
|
+
|
|
185
|
+
if skip_it || (@config.authorization_mode == :whitelist && !has_profiling_cookie)
|
|
186
|
+
status,headers,body = @app.call(env)
|
|
187
|
+
if !skip_it && @config.authorization_mode == :whitelist && !has_profiling_cookie && MiniProfiler.request_authorized?
|
|
188
|
+
client_settings.write!(headers)
|
|
189
|
+
end
|
|
190
|
+
return [status,headers,body]
|
|
163
191
|
end
|
|
164
192
|
|
|
165
|
-
|
|
193
|
+
# handle all /mini-profiler requests here
|
|
194
|
+
return serve_html(env) if path.start_with? @config.base_url_path
|
|
195
|
+
|
|
196
|
+
has_disable_cookie = client_settings.disable_profiling?
|
|
197
|
+
# manual session disable / enable
|
|
198
|
+
if query_string =~ /pp=disable/ || has_disable_cookie
|
|
199
|
+
skip_it = true
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
if query_string =~ /pp=enable/
|
|
203
|
+
skip_it = false
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
if skip_it
|
|
207
|
+
status,headers,body = @app.call(env)
|
|
208
|
+
client_settings.disable_profiling = true
|
|
209
|
+
client_settings.write!(headers)
|
|
210
|
+
return [status,headers,body]
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
if query_string =~ /pp=profile-gc/
|
|
214
|
+
return Rack::MiniProfiler::GCProfiler.new.profile_gc(@app, env)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
MiniProfiler.create_current(env, @config)
|
|
218
|
+
MiniProfiler.deauthorize_request if @config.authorization_mode == :whitelist
|
|
219
|
+
if query_string =~ /pp=normal-backtrace/
|
|
220
|
+
client_settings.backtrace_level = ClientSettings::BACKTRACE_DEFAULT
|
|
221
|
+
elsif query_string =~ /pp=no-backtrace/
|
|
222
|
+
current.skip_backtrace = true
|
|
223
|
+
client_settings.backtrace_level = ClientSettings::BACKTRACE_NONE
|
|
224
|
+
elsif query_string =~ /pp=full-backtrace/ || client_settings.backtrace_full?
|
|
225
|
+
current.full_backtrace = true
|
|
226
|
+
client_settings.backtrace_level = ClientSettings::BACKTRACE_FULL
|
|
227
|
+
elsif client_settings.backtrace_none?
|
|
228
|
+
current.skip_backtrace = true
|
|
229
|
+
end
|
|
166
230
|
|
|
167
231
|
done_sampling = false
|
|
168
232
|
quit_sampler = false
|
|
169
233
|
backtraces = nil
|
|
170
|
-
|
|
234
|
+
stacktrace_installed = true
|
|
235
|
+
if query_string =~ /pp=sample/
|
|
236
|
+
skip_frames = 0
|
|
171
237
|
backtraces = []
|
|
172
238
|
t = Thread.current
|
|
239
|
+
|
|
240
|
+
begin
|
|
241
|
+
require 'stacktrace'
|
|
242
|
+
skip_frames = stacktrace.length
|
|
243
|
+
rescue LoadError
|
|
244
|
+
stacktrace_installed = false
|
|
245
|
+
end
|
|
246
|
+
|
|
173
247
|
Thread.new {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
i
|
|
177
|
-
|
|
178
|
-
|
|
248
|
+
begin
|
|
249
|
+
i = 10000 # for sanity never grab more than 10k samples
|
|
250
|
+
while i > 0
|
|
251
|
+
break if done_sampling
|
|
252
|
+
i -= 1
|
|
253
|
+
if stacktrace_installed
|
|
254
|
+
backtraces << t.stacktrace(0,-(1+skip_frames), StackFrame::Flags::METHOD | StackFrame::Flags::KLASS)
|
|
255
|
+
else
|
|
256
|
+
backtraces << t.backtrace
|
|
257
|
+
end
|
|
258
|
+
sleep 0.001
|
|
259
|
+
end
|
|
260
|
+
ensure
|
|
261
|
+
quit_sampler = true
|
|
179
262
|
end
|
|
180
|
-
quit_sampler = true
|
|
181
263
|
}
|
|
182
264
|
end
|
|
183
265
|
|
|
184
266
|
status, headers, body = nil
|
|
267
|
+
start = Time.now
|
|
185
268
|
begin
|
|
186
|
-
|
|
269
|
+
|
|
270
|
+
# Strip all the caching headers so we don't get 304s back
|
|
271
|
+
# This solves a very annoying bug where rack mini profiler never shows up
|
|
272
|
+
env['HTTP_IF_MODIFIED_SINCE'] = nil
|
|
273
|
+
env['HTTP_IF_NONE_MATCH'] = nil
|
|
274
|
+
|
|
275
|
+
status,headers,body = @app.call(env)
|
|
276
|
+
client_settings.write!(headers)
|
|
187
277
|
ensure
|
|
188
278
|
if backtraces
|
|
189
279
|
done_sampling = true
|
|
@@ -191,40 +281,159 @@ module Rack
|
|
|
191
281
|
end
|
|
192
282
|
end
|
|
193
283
|
|
|
194
|
-
|
|
284
|
+
skip_it = current.discard
|
|
285
|
+
if (config.authorization_mode == :whitelist && !MiniProfiler.request_authorized?)
|
|
286
|
+
# this is non-obvious, don't kill the profiling cookie on errors or short requests
|
|
287
|
+
# this ensures that stuff that never reaches the rails stack does not kill profiling
|
|
288
|
+
if status == 200 && ((Time.now - start) > 0.1)
|
|
289
|
+
client_settings.discard_cookie!(headers)
|
|
290
|
+
end
|
|
291
|
+
skip_it = true
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
return [status,headers,body] if skip_it
|
|
295
|
+
|
|
296
|
+
# we must do this here, otherwise current[:discard] is not being properly treated
|
|
297
|
+
if query_string =~ /pp=env/
|
|
298
|
+
body.close if body.respond_to? :close
|
|
299
|
+
return dump_env env
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
if query_string =~ /pp=help/
|
|
303
|
+
body.close if body.respond_to? :close
|
|
304
|
+
return help(client_settings)
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
page_struct = current.page_struct
|
|
195
308
|
page_struct['Root'].record_time((Time.now - start) * 1000)
|
|
196
309
|
|
|
197
|
-
|
|
310
|
+
if backtraces
|
|
311
|
+
body.close if body.respond_to? :close
|
|
312
|
+
return analyze(backtraces, page_struct)
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
# no matter what it is, it should be unviewed, otherwise we will miss POST
|
|
317
|
+
@storage.set_unviewed(user(env), page_struct['Id'])
|
|
318
|
+
@storage.save(page_struct)
|
|
319
|
+
|
|
320
|
+
# inject headers, script
|
|
198
321
|
if status == 200
|
|
199
|
-
|
|
200
|
-
|
|
322
|
+
|
|
323
|
+
client_settings.write!(headers)
|
|
201
324
|
|
|
325
|
+
# mini profiler is meddling with stuff, we can not cache cause we will get incorrect data
|
|
326
|
+
# Rack::ETag has already inserted some nonesense in the chain
|
|
327
|
+
headers.delete('ETag')
|
|
328
|
+
headers.delete('Date')
|
|
329
|
+
headers['Cache-Control'] = 'must-revalidate, private, max-age=0'
|
|
330
|
+
|
|
202
331
|
# inject header
|
|
203
332
|
if headers.is_a? Hash
|
|
204
333
|
headers['X-MiniProfiler-Ids'] = ids_json(env)
|
|
205
334
|
end
|
|
206
335
|
|
|
207
336
|
# inject script
|
|
208
|
-
if current
|
|
337
|
+
if current.inject_js \
|
|
209
338
|
&& headers.has_key?('Content-Type') \
|
|
210
339
|
&& !headers['Content-Type'].match(/text\/html/).nil? then
|
|
211
|
-
|
|
340
|
+
|
|
341
|
+
response = Rack::Response.new([], status, headers)
|
|
342
|
+
script = self.get_profile_script(env)
|
|
343
|
+
if String === body
|
|
344
|
+
response.write inject(body,script)
|
|
345
|
+
else
|
|
346
|
+
body.each { |fragment| response.write inject(fragment, script) }
|
|
347
|
+
end
|
|
348
|
+
body.close if body.respond_to? :close
|
|
349
|
+
return response.finish
|
|
212
350
|
end
|
|
213
351
|
end
|
|
214
352
|
|
|
215
|
-
|
|
216
|
-
# Rack::ETag has already inserted some nonesense in the chain
|
|
217
|
-
headers.delete('ETag')
|
|
218
|
-
headers.delete('Date')
|
|
219
|
-
headers['Cache-Control'] = 'must-revalidate, private, max-age=0'
|
|
353
|
+
client_settings.write!(headers)
|
|
220
354
|
[status, headers, body]
|
|
221
355
|
ensure
|
|
222
356
|
# Make sure this always happens
|
|
223
357
|
current = nil
|
|
224
358
|
end
|
|
225
359
|
|
|
360
|
+
def inject(fragment, script)
|
|
361
|
+
fragment.sub(/<\/body>/i) do
|
|
362
|
+
# if for whatever crazy reason we dont get a utf string,
|
|
363
|
+
# just force the encoding, no utf in the mp scripts anyway
|
|
364
|
+
if script.respond_to?(:encoding) && script.respond_to?(:force_encoding)
|
|
365
|
+
(script + "</body>").force_encoding(fragment.encoding)
|
|
366
|
+
else
|
|
367
|
+
script + "</body>"
|
|
368
|
+
end
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
def dump_env(env)
|
|
373
|
+
headers = {'Content-Type' => 'text/plain'}
|
|
374
|
+
body = ""
|
|
375
|
+
env.each do |k,v|
|
|
376
|
+
body << "#{k}: #{v}\n"
|
|
377
|
+
end
|
|
378
|
+
[200, headers, [body]]
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
def help(client_settings)
|
|
382
|
+
headers = {'Content-Type' => 'text/plain'}
|
|
383
|
+
body = "Append the following to your query string:
|
|
384
|
+
|
|
385
|
+
pp=help : display this screen
|
|
386
|
+
pp=env : display the rack environment
|
|
387
|
+
pp=skip : skip mini profiler for this request
|
|
388
|
+
pp=no-backtrace #{"(*) " if client_settings.backtrace_none?}: don't collect stack traces from all the SQL executed (sticky, use pp=normal-backtrace to enable)
|
|
389
|
+
pp=normal-backtrace #{"(*) " if client_settings.backtrace_default?}: collect stack traces from all the SQL executed and filter normally
|
|
390
|
+
pp=full-backtrace #{"(*) " if client_settings.backtrace_full?}: enable full backtraces for SQL executed (use pp=normal-backtrace to disable)
|
|
391
|
+
pp=sample : sample stack traces and return a report isolating heavy usage (experimental works best with the stacktrace gem)
|
|
392
|
+
pp=disable : disable profiling for this session
|
|
393
|
+
pp=enable : enable profiling for this session (if previously disabled)
|
|
394
|
+
pp=profile-gc: perform gc profiling on this request (ruby 1.9.3 only)
|
|
395
|
+
"
|
|
396
|
+
|
|
397
|
+
client_settings.write!(headers)
|
|
398
|
+
[200, headers, [body]]
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
def analyze(traces, page_struct)
|
|
402
|
+
headers = {'Content-Type' => 'text/plain'}
|
|
403
|
+
body = "Collected: #{traces.count} stack traces. Duration(ms): #{page_struct.duration_ms}"
|
|
404
|
+
|
|
405
|
+
seen = {}
|
|
406
|
+
fulldump = ""
|
|
407
|
+
traces.each do |trace|
|
|
408
|
+
fulldump << "\n\n"
|
|
409
|
+
distinct = {}
|
|
410
|
+
trace.each do |frame|
|
|
411
|
+
frame = "#{frame.klass}::#{frame.method}" unless String === frame
|
|
412
|
+
unless distinct[frame]
|
|
413
|
+
distinct[frame] = true
|
|
414
|
+
seen[frame] ||= 0
|
|
415
|
+
seen[frame] += 1
|
|
416
|
+
end
|
|
417
|
+
fulldump << frame << "\n"
|
|
418
|
+
end
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
body << "\n\nStack Trace Analysis\n"
|
|
422
|
+
seen.to_a.sort{|x,y| y[1] <=> x[1]}.each do |name, count|
|
|
423
|
+
if count > traces.count / 10
|
|
424
|
+
body << "#{name} x #{count}\n"
|
|
425
|
+
end
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
body << "\n\n\nRaw traces \n"
|
|
429
|
+
body << fulldump
|
|
430
|
+
|
|
431
|
+
[200, headers, [body]]
|
|
432
|
+
end
|
|
433
|
+
|
|
226
434
|
def ids_json(env)
|
|
227
|
-
ids
|
|
435
|
+
# cap at 10 ids, otherwise there is a chance you can blow the header
|
|
436
|
+
ids = [current.page_struct["Id"]] + (@storage.get_unviewed_ids(user(env)) || [])[0..8]
|
|
228
437
|
::JSON.generate(ids.uniq)
|
|
229
438
|
end
|
|
230
439
|
|
|
@@ -236,16 +445,16 @@ module Rack
|
|
|
236
445
|
# * you do not want script to be automatically appended for the current page. You can also call cancel_auto_inject
|
|
237
446
|
def get_profile_script(env)
|
|
238
447
|
ids = ids_json(env)
|
|
239
|
-
path = @
|
|
448
|
+
path = @config.base_url_path
|
|
240
449
|
version = MiniProfiler::VERSION
|
|
241
|
-
position = @
|
|
450
|
+
position = @config.position
|
|
242
451
|
showTrivial = false
|
|
243
452
|
showChildren = false
|
|
244
453
|
maxTracesToShow = 10
|
|
245
454
|
showControls = false
|
|
246
|
-
currentId = current
|
|
455
|
+
currentId = current.page_struct["Id"]
|
|
247
456
|
authorized = true
|
|
248
|
-
|
|
457
|
+
useExistingjQuery = @config.use_existing_jquery
|
|
249
458
|
# TODO : cache this snippet
|
|
250
459
|
script = IO.read(::File.expand_path('../html/profile_handler.js', ::File.dirname(__FILE__)))
|
|
251
460
|
# replace the variables
|
|
@@ -255,52 +464,13 @@ module Rack
|
|
|
255
464
|
end
|
|
256
465
|
# replace the '{{' and '}}''
|
|
257
466
|
script.gsub!(/\{\{/, '{').gsub!(/\}\}/, '}')
|
|
258
|
-
current
|
|
467
|
+
current.inject_js = false
|
|
259
468
|
script
|
|
260
469
|
end
|
|
261
470
|
|
|
262
471
|
# cancels automatic injection of profile script for the current page
|
|
263
472
|
def cancel_auto_inject(env)
|
|
264
|
-
current
|
|
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'])
|
|
473
|
+
current.inject_js = false
|
|
304
474
|
end
|
|
305
475
|
|
|
306
476
|
end
|