rack-mini-profiler 0.1.1 → 0.1.8
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 +32 -0
- data/README.md +40 -15
- data/lib/html/includes.css +75 -1
- data/lib/html/includes.js +26 -2
- data/lib/html/includes.less +1 -1
- data/lib/html/profile_handler.js +9 -9
- 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/page_timer_struct.rb +7 -3
- data/lib/mini_profiler/profiler.rb +251 -135
- data/lib/mini_profiler/profiling_methods.rb +73 -0
- data/lib/mini_profiler/request_timer_struct.rb +40 -9
- data/lib/mini_profiler/sql_timer_struct.rb +16 -5
- data/lib/mini_profiler/storage/file_store.rb +8 -7
- data/lib/mini_profiler/storage/memory_store.rb +0 -4
- data/lib/mini_profiler_rails/railtie.rb +62 -11
- data/lib/patches/sql_patches.rb +152 -39
- data/lib/rack-mini-profiler.rb +3 -3
- data/rack-mini-profiler.gemspec +4 -5
- metadata +9 -8
- data/CHANGELOG.md +0 -7
- data/lib/html/MiniProfilerHandler.cs +0 -419
- data/lib/mini_profiler/body_add_proxy.rb +0 -45
|
@@ -6,72 +6,107 @@ 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'
|
|
11
10
|
require 'mini_profiler/storage/memory_store'
|
|
12
11
|
require 'mini_profiler/storage/redis_store'
|
|
13
12
|
require 'mini_profiler/storage/file_store'
|
|
13
|
+
require 'mini_profiler/config'
|
|
14
|
+
require 'mini_profiler/profiling_methods'
|
|
15
|
+
require 'mini_profiler/context'
|
|
14
16
|
|
|
15
17
|
module Rack
|
|
16
18
|
|
|
17
19
|
class MiniProfiler
|
|
18
20
|
|
|
19
|
-
VERSION = '
|
|
20
|
-
@@instance = nil
|
|
21
|
+
VERSION = '104'.freeze
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
class << self
|
|
24
|
+
|
|
25
|
+
include Rack::MiniProfiler::ProfilingMethods
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
def generate_id
|
|
28
|
+
rand(36**20).to_s(36)
|
|
29
|
+
end
|
|
29
30
|
|
|
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| Rack::Request.new(env).ip }
|
|
42
|
-
}
|
|
43
|
-
end
|
|
31
|
+
def reset_config
|
|
32
|
+
@config = Config.default
|
|
33
|
+
end
|
|
44
34
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
35
|
+
# So we can change the configuration if we want
|
|
36
|
+
def config
|
|
37
|
+
@config ||= Config.default
|
|
38
|
+
end
|
|
48
39
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
40
|
+
def share_template
|
|
41
|
+
return @share_template unless @share_template.nil?
|
|
42
|
+
@share_template = ::File.read(::File.expand_path("../html/share.html", ::File.dirname(__FILE__)))
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def current
|
|
46
|
+
Thread.current[:mini_profiler_private]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def current=(c)
|
|
50
|
+
# we use TLS cause we need access to this from sql blocks and code blocks that have no access to env
|
|
51
|
+
Thread.current[:mini_profiler_private]= c
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# discard existing results, don't track this request
|
|
55
|
+
def discard_results
|
|
56
|
+
self.current.discard = true if current
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# user has the mini profiler cookie, only used when config.authorization_mode == :whitelist
|
|
60
|
+
def has_profiling_cookie?(env)
|
|
61
|
+
env['HTTP_COOKIE'] && env['HTTP_COOKIE'].include?("__profilin=stylin")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# remove the mini profiler cookie, only used when config.authorization_mode == :whitelist
|
|
65
|
+
def remove_profiling_cookie(headers)
|
|
66
|
+
Rack::Utils.delete_cookie_header!(headers, '__profilin')
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def set_profiling_cookie(headers)
|
|
70
|
+
Rack::Utils.set_cookie_header!(headers, '__profilin', 'stylin')
|
|
71
|
+
end
|
|
53
72
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
73
|
+
def create_current(env={}, options={})
|
|
74
|
+
# profiling the request
|
|
75
|
+
self.current = Context.new
|
|
76
|
+
self.current.inject_js = config.auto_inject && (!env['HTTP_X_REQUESTED_WITH'].eql? 'XMLHttpRequest')
|
|
77
|
+
self.current.page_struct = PageTimerStruct.new(env)
|
|
78
|
+
self.current.current_timer = current.page_struct['Root']
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def authorize_request
|
|
82
|
+
Thread.current[:mp_authorized] = true
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def deauthorize_request
|
|
86
|
+
Thread.current[:mp_authorized] = nil
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def request_authorized?
|
|
90
|
+
Thread.current[:mp_authorized]
|
|
91
|
+
end
|
|
57
92
|
end
|
|
58
93
|
|
|
59
94
|
#
|
|
60
95
|
# options:
|
|
61
96
|
# :auto_inject - should script be automatically injected on every html page (not xhr)
|
|
62
|
-
def initialize(app,
|
|
63
|
-
|
|
64
|
-
MiniProfiler.
|
|
65
|
-
@options = MiniProfiler.configuration
|
|
97
|
+
def initialize(app, config = nil)
|
|
98
|
+
MiniProfiler.config.merge!(config)
|
|
99
|
+
@config = MiniProfiler.config
|
|
66
100
|
@app = app
|
|
67
|
-
@
|
|
68
|
-
unless @
|
|
69
|
-
@
|
|
101
|
+
@config.base_url_path << "/" unless @config.base_url_path.end_with? "/"
|
|
102
|
+
unless @config.storage_instance
|
|
103
|
+
@config.storage_instance = @config.storage.new(@config.storage_options)
|
|
70
104
|
end
|
|
105
|
+
@storage = @config.storage_instance
|
|
71
106
|
end
|
|
72
107
|
|
|
73
108
|
def user(env)
|
|
74
|
-
|
|
109
|
+
@config.user_provider.call(env)
|
|
75
110
|
end
|
|
76
111
|
|
|
77
112
|
def serve_results(env)
|
|
@@ -83,7 +118,7 @@ module Rack
|
|
|
83
118
|
return [404, {}, ["Request not found: #{request['id']} - user #{user(env)}"]]
|
|
84
119
|
end
|
|
85
120
|
unless page_struct['HasUserViewed']
|
|
86
|
-
|
|
121
|
+
page_struct['ClientTimings'] = ClientTimerStruct.init_from_form_data(env, page_struct)
|
|
87
122
|
page_struct['HasUserViewed'] = true
|
|
88
123
|
@storage.save(page_struct)
|
|
89
124
|
@storage.set_viewed(user(env), id)
|
|
@@ -97,7 +132,7 @@ module Rack
|
|
|
97
132
|
|
|
98
133
|
# Otherwise give the HTML back
|
|
99
134
|
html = MiniProfiler.share_template.dup
|
|
100
|
-
html.gsub!(/\{path\}/, @
|
|
135
|
+
html.gsub!(/\{path\}/, @config.base_url_path)
|
|
101
136
|
html.gsub!(/\{version\}/, MiniProfiler::VERSION)
|
|
102
137
|
html.gsub!(/\{json\}/, result_json)
|
|
103
138
|
html.gsub!(/\{includes\}/, get_profile_script(env))
|
|
@@ -110,25 +145,26 @@ module Rack
|
|
|
110
145
|
end
|
|
111
146
|
|
|
112
147
|
def serve_html(env)
|
|
113
|
-
file_name = env['PATH_INFO'][(@
|
|
148
|
+
file_name = env['PATH_INFO'][(@config.base_url_path.length)..1000]
|
|
114
149
|
return serve_results(env) if file_name.eql?('results')
|
|
115
150
|
full_path = ::File.expand_path("../html/#{file_name}", ::File.dirname(__FILE__))
|
|
116
151
|
return [404, {}, ["Not found"]] unless ::File.exists? full_path
|
|
117
152
|
f = Rack::File.new nil
|
|
118
153
|
f.path = full_path
|
|
119
|
-
f.cache_control = "max-age:86400"
|
|
120
|
-
f.serving env
|
|
121
|
-
end
|
|
122
154
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
155
|
+
begin
|
|
156
|
+
f.cache_control = "max-age:86400"
|
|
157
|
+
f.serving env
|
|
158
|
+
rescue
|
|
159
|
+
# old versions of rack have a different api
|
|
160
|
+
status, headers, body = f.serving
|
|
161
|
+
headers.merge! 'Cache-Control' => "max-age:86400"
|
|
162
|
+
[status, headers, body]
|
|
163
|
+
end
|
|
126
164
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
end
|
|
131
|
-
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
|
|
132
168
|
def current
|
|
133
169
|
MiniProfiler.current
|
|
134
170
|
end
|
|
@@ -137,54 +173,73 @@ module Rack
|
|
|
137
173
|
MiniProfiler.current=c
|
|
138
174
|
end
|
|
139
175
|
|
|
140
|
-
def options
|
|
141
|
-
@options
|
|
142
|
-
end
|
|
143
176
|
|
|
144
|
-
def
|
|
145
|
-
|
|
146
|
-
self.current = {}
|
|
147
|
-
self.current['inject_js'] = options[:auto_inject] && (!env['HTTP_X_REQUESTED_WITH'].eql? 'XMLHttpRequest')
|
|
148
|
-
self.current['page_struct'] = PageTimerStruct.new(env)
|
|
149
|
-
self.current['current_timer'] = current['page_struct']['Root']
|
|
177
|
+
def config
|
|
178
|
+
@config
|
|
150
179
|
end
|
|
151
180
|
|
|
181
|
+
|
|
152
182
|
def call(env)
|
|
153
|
-
|
|
183
|
+
status = headers = body = nil
|
|
184
|
+
path = env['PATH_INFO']
|
|
154
185
|
|
|
155
|
-
|
|
156
|
-
|
|
186
|
+
skip_it = (@config.pre_authorize_cb && !@config.pre_authorize_cb.call(env)) ||
|
|
187
|
+
(@config.skip_paths && @config.skip_paths.any?{ |p| path[0,p.length] == p}) ||
|
|
188
|
+
env["QUERY_STRING"] =~ /pp=skip/
|
|
189
|
+
|
|
190
|
+
has_profiling_cookie = MiniProfiler.has_profiling_cookie?(env)
|
|
191
|
+
|
|
192
|
+
if skip_it || (@config.authorization_mode == :whitelist && !has_profiling_cookie)
|
|
193
|
+
status,headers,body = @app.call(env)
|
|
194
|
+
if !skip_it && @config.authorization_mode == :whitelist && !has_profiling_cookie && MiniProfiler.request_authorized?
|
|
195
|
+
MiniProfiler.set_profiling_cookie(headers)
|
|
196
|
+
end
|
|
197
|
+
return [status,headers,body]
|
|
198
|
+
end
|
|
157
199
|
|
|
158
|
-
|
|
159
|
-
return serve_html(env) if env['PATH_INFO'].start_with? @
|
|
200
|
+
# handle all /mini-profiler requests here
|
|
201
|
+
return serve_html(env) if env['PATH_INFO'].start_with? @config.base_url_path
|
|
160
202
|
|
|
161
|
-
MiniProfiler.create_current(env, @
|
|
162
|
-
if
|
|
163
|
-
|
|
203
|
+
MiniProfiler.create_current(env, @config)
|
|
204
|
+
MiniProfiler.deauthorize_request if @config.authorization_mode == :whitelist
|
|
205
|
+
if env["QUERY_STRING"] =~ /pp=no-backtrace/
|
|
206
|
+
current.skip_backtrace = true
|
|
207
|
+
elsif env["QUERY_STRING"] =~ /pp=full-backtrace/
|
|
208
|
+
current.full_backtrace = true
|
|
164
209
|
end
|
|
165
210
|
|
|
166
|
-
start = Time.now
|
|
167
|
-
|
|
168
211
|
done_sampling = false
|
|
169
212
|
quit_sampler = false
|
|
170
213
|
backtraces = nil
|
|
214
|
+
missing_stacktrace = false
|
|
171
215
|
if env["QUERY_STRING"] =~ /pp=sample/
|
|
172
216
|
backtraces = []
|
|
173
217
|
t = Thread.current
|
|
174
218
|
Thread.new {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
219
|
+
begin
|
|
220
|
+
require 'stacktrace' rescue nil
|
|
221
|
+
if !t.respond_to? :stacktrace
|
|
222
|
+
missing_stacktrace = true
|
|
223
|
+
quit_sampler = true
|
|
224
|
+
return
|
|
225
|
+
end
|
|
226
|
+
i = 10000 # for sanity never grab more than 10k samples
|
|
227
|
+
while i > 0
|
|
228
|
+
break if done_sampling
|
|
229
|
+
i -= 1
|
|
230
|
+
backtraces << t.stacktrace
|
|
231
|
+
sleep 0.001
|
|
232
|
+
end
|
|
233
|
+
ensure
|
|
234
|
+
quit_sampler = true
|
|
180
235
|
end
|
|
181
|
-
quit_sampler = true
|
|
182
236
|
}
|
|
183
237
|
end
|
|
184
238
|
|
|
185
239
|
status, headers, body = nil
|
|
240
|
+
start = Time.now
|
|
186
241
|
begin
|
|
187
|
-
status,headers,
|
|
242
|
+
status,headers,body = @app.call(env)
|
|
188
243
|
ensure
|
|
189
244
|
if backtraces
|
|
190
245
|
done_sampling = true
|
|
@@ -192,9 +247,35 @@ module Rack
|
|
|
192
247
|
end
|
|
193
248
|
end
|
|
194
249
|
|
|
195
|
-
|
|
250
|
+
skip_it = current.discard
|
|
251
|
+
if (config.authorization_mode == :whitelist && !MiniProfiler.request_authorized?)
|
|
252
|
+
MiniProfiler.remove_profiling_cookie(headers)
|
|
253
|
+
skip_it = true
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
return [status,headers,body] if skip_it
|
|
257
|
+
|
|
258
|
+
# we must do this here, otherwise current[:discard] is not being properly treated
|
|
259
|
+
if env["QUERY_STRING"] =~ /pp=env/
|
|
260
|
+
body.close if body.respond_to? :close
|
|
261
|
+
return dump_env env
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
if env["QUERY_STRING"] =~ /pp=help/
|
|
265
|
+
body.close if body.respond_to? :close
|
|
266
|
+
return help
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
page_struct = current.page_struct
|
|
196
270
|
page_struct['Root'].record_time((Time.now - start) * 1000)
|
|
197
271
|
|
|
272
|
+
if backtraces
|
|
273
|
+
body.close if body.respond_to? :close
|
|
274
|
+
return help(:stacktrace) if missing_stacktrace
|
|
275
|
+
return analyze(backtraces, page_struct)
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
|
|
198
279
|
# no matter what it is, it should be unviewed, otherwise we will miss POST
|
|
199
280
|
@storage.set_unviewed(user(env), page_struct['Id'])
|
|
200
281
|
@storage.save(page_struct)
|
|
@@ -202,32 +283,106 @@ module Rack
|
|
|
202
283
|
# inject headers, script
|
|
203
284
|
if status == 200
|
|
204
285
|
|
|
286
|
+
# mini profiler is meddling with stuff, we can not cache cause we will get incorrect data
|
|
287
|
+
# Rack::ETag has already inserted some nonesense in the chain
|
|
288
|
+
headers.delete('ETag')
|
|
289
|
+
headers.delete('Date')
|
|
290
|
+
headers['Cache-Control'] = 'must-revalidate, private, max-age=0'
|
|
291
|
+
|
|
205
292
|
# inject header
|
|
206
293
|
if headers.is_a? Hash
|
|
207
294
|
headers['X-MiniProfiler-Ids'] = ids_json(env)
|
|
208
295
|
end
|
|
209
296
|
|
|
210
297
|
# inject script
|
|
211
|
-
if current
|
|
298
|
+
if current.inject_js \
|
|
212
299
|
&& headers.has_key?('Content-Type') \
|
|
213
300
|
&& !headers['Content-Type'].match(/text\/html/).nil? then
|
|
214
|
-
|
|
301
|
+
|
|
302
|
+
response = Rack::Response.new([], status, headers)
|
|
303
|
+
script = self.get_profile_script(env)
|
|
304
|
+
if String === body
|
|
305
|
+
response.write inject(body,script)
|
|
306
|
+
else
|
|
307
|
+
body.each { |fragment| response.write inject(fragment, script) }
|
|
308
|
+
end
|
|
309
|
+
body.close if body.respond_to? :close
|
|
310
|
+
return response.finish
|
|
215
311
|
end
|
|
216
312
|
end
|
|
217
313
|
|
|
218
|
-
# mini profiler is meddling with stuff, we can not cache cause we will get incorrect data
|
|
219
|
-
# Rack::ETag has already inserted some nonesense in the chain
|
|
220
|
-
headers.delete('ETag')
|
|
221
|
-
headers.delete('Date')
|
|
222
|
-
headers['Cache-Control'] = 'must-revalidate, private, max-age=0'
|
|
223
314
|
[status, headers, body]
|
|
224
315
|
ensure
|
|
225
316
|
# Make sure this always happens
|
|
226
317
|
current = nil
|
|
227
318
|
end
|
|
228
319
|
|
|
320
|
+
def inject(fragment, script)
|
|
321
|
+
fragment.sub(/<\/body>/i, script + "</body>")
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
def dump_env(env)
|
|
325
|
+
headers = {'Content-Type' => 'text/plain'}
|
|
326
|
+
body = ""
|
|
327
|
+
env.each do |k,v|
|
|
328
|
+
body << "#{k}: #{v}\n"
|
|
329
|
+
end
|
|
330
|
+
[200, headers, [body]]
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def help(category = nil)
|
|
334
|
+
headers = {'Content-Type' => 'text/plain'}
|
|
335
|
+
body = "Append the following to your query string:
|
|
336
|
+
|
|
337
|
+
pp=help : display this screen
|
|
338
|
+
pp=env : display the rack environment
|
|
339
|
+
pp=skip : skip mini profiler for this request
|
|
340
|
+
pp=no-backtrace : don't collect stack traces from all the SQL executed
|
|
341
|
+
pp=full-backtrace : enable full backtrace for SQL executed
|
|
342
|
+
pp=sample : sample stack traces and return a report isolating heavy usage (requires the stacktrace gem)
|
|
343
|
+
"
|
|
344
|
+
if (category == :stacktrace)
|
|
345
|
+
body = "pp=stacktrace requires the stacktrace gem - add gem 'stacktrace' to your Gemfile"
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
[200, headers, [body]]
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
def analyze(traces, page_struct)
|
|
352
|
+
headers = {'Content-Type' => 'text/plain'}
|
|
353
|
+
body = "Collected: #{traces.count} stack traces. Duration(ms): #{page_struct.duration_ms}"
|
|
354
|
+
|
|
355
|
+
seen = {}
|
|
356
|
+
fulldump = ""
|
|
357
|
+
traces.each do |trace|
|
|
358
|
+
fulldump << "\n\n"
|
|
359
|
+
distinct = {}
|
|
360
|
+
trace.each do |frame|
|
|
361
|
+
name = "#{frame.klass} #{frame.method}"
|
|
362
|
+
unless distinct[name]
|
|
363
|
+
distinct[name] = true
|
|
364
|
+
seen[name] ||= 0
|
|
365
|
+
seen[name] += 1
|
|
366
|
+
end
|
|
367
|
+
fulldump << name << "\n"
|
|
368
|
+
end
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
body << "\n\nStack Trace Analysis\n"
|
|
372
|
+
seen.to_a.sort{|x,y| y[1] <=> x[1]}.each do |name, count|
|
|
373
|
+
if count > traces.count / 10
|
|
374
|
+
body << "#{name} x #{count}\n"
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
body << "\n\n\nRaw traces \n"
|
|
379
|
+
body << fulldump
|
|
380
|
+
|
|
381
|
+
[200, headers, [body]]
|
|
382
|
+
end
|
|
383
|
+
|
|
229
384
|
def ids_json(env)
|
|
230
|
-
ids = [current
|
|
385
|
+
ids = [current.page_struct["Id"]] + (@storage.get_unviewed_ids(user(env)) || [])
|
|
231
386
|
::JSON.generate(ids.uniq)
|
|
232
387
|
end
|
|
233
388
|
|
|
@@ -239,16 +394,16 @@ module Rack
|
|
|
239
394
|
# * you do not want script to be automatically appended for the current page. You can also call cancel_auto_inject
|
|
240
395
|
def get_profile_script(env)
|
|
241
396
|
ids = ids_json(env)
|
|
242
|
-
path = @
|
|
397
|
+
path = @config.base_url_path
|
|
243
398
|
version = MiniProfiler::VERSION
|
|
244
|
-
position = @
|
|
399
|
+
position = @config.position
|
|
245
400
|
showTrivial = false
|
|
246
401
|
showChildren = false
|
|
247
402
|
maxTracesToShow = 10
|
|
248
403
|
showControls = false
|
|
249
|
-
currentId = current
|
|
404
|
+
currentId = current.page_struct["Id"]
|
|
250
405
|
authorized = true
|
|
251
|
-
|
|
406
|
+
useExistingjQuery = @config.use_existing_jquery
|
|
252
407
|
# TODO : cache this snippet
|
|
253
408
|
script = IO.read(::File.expand_path('../html/profile_handler.js', ::File.dirname(__FILE__)))
|
|
254
409
|
# replace the variables
|
|
@@ -258,52 +413,13 @@ module Rack
|
|
|
258
413
|
end
|
|
259
414
|
# replace the '{{' and '}}''
|
|
260
415
|
script.gsub!(/\{\{/, '{').gsub!(/\}\}/, '}')
|
|
261
|
-
current
|
|
416
|
+
current.inject_js = false
|
|
262
417
|
script
|
|
263
418
|
end
|
|
264
419
|
|
|
265
420
|
# cancels automatic injection of profile script for the current page
|
|
266
421
|
def cancel_auto_inject(env)
|
|
267
|
-
current
|
|
268
|
-
end
|
|
269
|
-
|
|
270
|
-
# perform a profiling step on given block
|
|
271
|
-
def self.step(name)
|
|
272
|
-
if current
|
|
273
|
-
old_timer = current['current_timer']
|
|
274
|
-
new_step = RequestTimerStruct.new(name, current['page_struct'])
|
|
275
|
-
current['current_timer'] = new_step
|
|
276
|
-
new_step['Name'] = name
|
|
277
|
-
start = Time.now
|
|
278
|
-
result = yield if block_given?
|
|
279
|
-
new_step.record_time((Time.now - start)*1000)
|
|
280
|
-
old_timer.add_child(new_step)
|
|
281
|
-
current['current_timer'] = old_timer
|
|
282
|
-
result
|
|
283
|
-
else
|
|
284
|
-
yield if block_given?
|
|
285
|
-
end
|
|
286
|
-
end
|
|
287
|
-
|
|
288
|
-
def self.profile_method(klass, method, &blk)
|
|
289
|
-
default_name = klass.to_s + " " + method.to_s
|
|
290
|
-
with_profiling = (method.to_s + "_with_mini_profiler").intern
|
|
291
|
-
without_profiling = (method.to_s + "_without_mini_profiler").intern
|
|
292
|
-
|
|
293
|
-
klass.send :alias_method, without_profiling, method
|
|
294
|
-
klass.send :define_method, with_profiling do |*args, &orig|
|
|
295
|
-
name = default_name
|
|
296
|
-
name = blk.bind(self).call(*args) if blk
|
|
297
|
-
::Rack::MiniProfiler.step name do
|
|
298
|
-
self.send without_profiling, *args, &orig
|
|
299
|
-
end
|
|
300
|
-
end
|
|
301
|
-
klass.send :alias_method, method, with_profiling
|
|
302
|
-
end
|
|
303
|
-
|
|
304
|
-
def record_sql(query, elapsed_ms)
|
|
305
|
-
c = current
|
|
306
|
-
c['current_timer'].add_sql(query, elapsed_ms, c['page_struct'], c['skip-backtrace']) if (c && c['current_timer'])
|
|
422
|
+
current.inject_js = false
|
|
307
423
|
end
|
|
308
424
|
|
|
309
425
|
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
module Rack
|
|
2
|
+
class MiniProfiler
|
|
3
|
+
module ProfilingMethods
|
|
4
|
+
|
|
5
|
+
def record_sql(query, elapsed_ms)
|
|
6
|
+
c = current
|
|
7
|
+
return unless c
|
|
8
|
+
c.current_timer.add_sql(query, elapsed_ms, c.page_struct, c.skip_backtrace, c.full_backtrace) if (c && c.current_timer)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# perform a profiling step on given block
|
|
12
|
+
def step(name)
|
|
13
|
+
if current
|
|
14
|
+
parent_timer = current.current_timer
|
|
15
|
+
result = nil
|
|
16
|
+
current.current_timer = current_timer = current.current_timer.add_child(name)
|
|
17
|
+
begin
|
|
18
|
+
result = yield if block_given?
|
|
19
|
+
ensure
|
|
20
|
+
current_timer.record_time
|
|
21
|
+
current.current_timer = parent_timer
|
|
22
|
+
end
|
|
23
|
+
result
|
|
24
|
+
else
|
|
25
|
+
yield if block_given?
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def unprofile_method(klass, method)
|
|
30
|
+
with_profiling = (method.to_s + "_with_mini_profiler").intern
|
|
31
|
+
without_profiling = (method.to_s + "_without_mini_profiler").intern
|
|
32
|
+
|
|
33
|
+
if klass.send :method_defined?, with_profiling
|
|
34
|
+
klass.send :alias_method, method, without_profiling
|
|
35
|
+
klass.send :remove_method, with_profiling
|
|
36
|
+
klass.send :remove_method, without_profiling
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def profile_method(klass, method, &blk)
|
|
41
|
+
default_name = klass.to_s + " " + method.to_s
|
|
42
|
+
with_profiling = (method.to_s + "_with_mini_profiler").intern
|
|
43
|
+
without_profiling = (method.to_s + "_without_mini_profiler").intern
|
|
44
|
+
|
|
45
|
+
if klass.send :method_defined?, with_profiling
|
|
46
|
+
return # dont double profile
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
klass.send :alias_method, without_profiling, method
|
|
50
|
+
klass.send :define_method, with_profiling do |*args, &orig|
|
|
51
|
+
return self.send without_profiling, *args, &orig unless Rack::MiniProfiler.current
|
|
52
|
+
|
|
53
|
+
name = default_name
|
|
54
|
+
name = blk.bind(self).call(*args) if blk
|
|
55
|
+
|
|
56
|
+
parent_timer = Rack::MiniProfiler.current.current_timer
|
|
57
|
+
page_struct = Rack::MiniProfiler.current.page_struct
|
|
58
|
+
result = nil
|
|
59
|
+
|
|
60
|
+
Rack::MiniProfiler.current.current_timer = current_timer = parent_timer.add_child(name)
|
|
61
|
+
begin
|
|
62
|
+
result = self.send without_profiling, *args, &orig
|
|
63
|
+
ensure
|
|
64
|
+
current_timer.record_time
|
|
65
|
+
Rack::MiniProfiler.current.current_timer = parent_timer
|
|
66
|
+
end
|
|
67
|
+
result
|
|
68
|
+
end
|
|
69
|
+
klass.send :alias_method, method, with_profiling
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -6,14 +6,14 @@ module Rack
|
|
|
6
6
|
class RequestTimerStruct < TimerStruct
|
|
7
7
|
|
|
8
8
|
def self.createRoot(name, page)
|
|
9
|
-
rt = RequestTimerStruct.new(name, page)
|
|
9
|
+
rt = RequestTimerStruct.new(name, page, nil)
|
|
10
10
|
rt["IsRoot"]= true
|
|
11
11
|
rt
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
attr_accessor :children_duration
|
|
15
15
|
|
|
16
|
-
def initialize(name, page)
|
|
16
|
+
def initialize(name, page, parent)
|
|
17
17
|
super("Id" => MiniProfiler.generate_id,
|
|
18
18
|
"Name" => name,
|
|
19
19
|
"DurationMilliseconds" => 0,
|
|
@@ -30,34 +30,65 @@ module Rack
|
|
|
30
30
|
"SqlTimingsDurationMilliseconds"=> 0,
|
|
31
31
|
"IsTrivial"=> false,
|
|
32
32
|
"IsRoot"=> false,
|
|
33
|
-
"Depth"=> 0,
|
|
33
|
+
"Depth"=> parent ? parent.depth + 1 : 0,
|
|
34
34
|
"ExecutedReaders"=> 0,
|
|
35
35
|
"ExecutedScalars"=> 0,
|
|
36
36
|
"ExecutedNonQueries"=> 0)
|
|
37
37
|
@children_duration = 0
|
|
38
|
+
@start = Time.now
|
|
39
|
+
@parent = parent
|
|
40
|
+
@page = page
|
|
38
41
|
end
|
|
39
42
|
|
|
40
|
-
def
|
|
43
|
+
def duration_ms
|
|
44
|
+
self['DurationMilliseconds']
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def start_ms
|
|
48
|
+
self['StartMilliseconds']
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def start
|
|
52
|
+
@start
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def depth
|
|
56
|
+
self['Depth']
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def children
|
|
60
|
+
self['Children']
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def add_child(name)
|
|
64
|
+
request_timer = RequestTimerStruct.new(name, @page, self)
|
|
41
65
|
self['Children'].push(request_timer)
|
|
42
66
|
self['HasChildren'] = true
|
|
43
67
|
request_timer['ParentTimingId'] = self['Id']
|
|
44
68
|
request_timer['Depth'] = self['Depth'] + 1
|
|
45
|
-
|
|
69
|
+
request_timer
|
|
46
70
|
end
|
|
47
71
|
|
|
48
|
-
def add_sql(query, elapsed_ms, page, skip_backtrace = false)
|
|
49
|
-
timer = SqlTimerStruct.new(query, elapsed_ms, page, skip_backtrace)
|
|
72
|
+
def add_sql(query, elapsed_ms, page, skip_backtrace = false, full_backtrace = false)
|
|
73
|
+
timer = SqlTimerStruct.new(query, elapsed_ms, page, self , skip_backtrace, full_backtrace)
|
|
50
74
|
timer['ParentTimingId'] = self['Id']
|
|
51
75
|
self['SqlTimings'].push(timer)
|
|
52
76
|
self['HasSqlTimings'] = true
|
|
53
77
|
self['SqlTimingsDurationMilliseconds'] += elapsed_ms
|
|
54
78
|
page['DurationMillisecondsInSql'] += elapsed_ms
|
|
79
|
+
timer
|
|
55
80
|
end
|
|
56
81
|
|
|
57
|
-
def record_time(milliseconds)
|
|
82
|
+
def record_time(milliseconds = nil)
|
|
83
|
+
milliseconds ||= (Time.now - @start) * 1000
|
|
58
84
|
self['DurationMilliseconds'] = milliseconds
|
|
59
85
|
self['IsTrivial'] = true if milliseconds < self["TrivialDurationThresholdMilliseconds"]
|
|
60
86
|
self['DurationWithoutChildrenMilliseconds'] = milliseconds - @children_duration
|
|
87
|
+
|
|
88
|
+
if @parent
|
|
89
|
+
@parent.children_duration += milliseconds
|
|
90
|
+
end
|
|
91
|
+
|
|
61
92
|
end
|
|
62
93
|
end
|
|
63
94
|
end
|