rack-mini-profiler 0.1.3 → 0.1.4
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.
- data/CHANGELOG +9 -0
- data/lib/mini_profiler/client_timer_struct.rb +33 -0
- data/lib/mini_profiler/context.rb +10 -0
- data/lib/mini_profiler/page_timer_struct.rb +6 -2
- data/lib/mini_profiler/profiler.rb +125 -131
- data/lib/mini_profiler/profiling_methods.rb +73 -0
- data/lib/mini_profiler/request_timer_struct.rb +39 -8
- data/lib/mini_profiler/sql_timer_struct.rb +13 -2
- data/lib/mini_profiler_rails/railtie.rb +37 -0
- data/lib/patches/sql_patches.rb +124 -11
- data/rack-mini-profiler.gemspec +1 -1
- metadata +5 -3
data/CHANGELOG
CHANGED
@@ -15,3 +15,12 @@
|
|
15
15
|
* Added option to display full backtraces pp=full-backtrace
|
16
16
|
* Cleaned up railties, got rid of the post authorize callback
|
17
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
|
+
|
@@ -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
|
@@ -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,
|
48
|
+
::JSON.generate(attribs, :max_nesting => 100)
|
45
49
|
end
|
46
50
|
end
|
47
51
|
|
48
52
|
end
|
49
|
-
end
|
53
|
+
end
|
@@ -12,41 +12,90 @@ require 'mini_profiler/storage/memory_store'
|
|
12
12
|
require 'mini_profiler/storage/redis_store'
|
13
13
|
require 'mini_profiler/storage/file_store'
|
14
14
|
require 'mini_profiler/config'
|
15
|
+
require 'mini_profiler/profiling_methods'
|
16
|
+
require 'mini_profiler/context'
|
15
17
|
|
16
18
|
module Rack
|
17
19
|
|
18
20
|
class MiniProfiler
|
19
21
|
|
20
|
-
VERSION = 'rZlycOOTnzxZvxTmFuOEV0dSmu4P5m5bLrCtwJHVXPA='.freeze
|
21
|
-
@@instance = nil
|
22
|
+
VERSION = 'rZlycOOTnzxZvxTmFuOEV0dSmu4P5m5bLrCtwJHVXPA=A'.freeze
|
22
23
|
|
23
|
-
|
24
|
-
|
25
|
-
|
24
|
+
class << self
|
25
|
+
|
26
|
+
include Rack::MiniProfiler::ProfilingMethods
|
26
27
|
|
27
|
-
|
28
|
-
|
29
|
-
|
28
|
+
def generate_id
|
29
|
+
rand(36**20).to_s(36)
|
30
|
+
end
|
30
31
|
|
31
|
-
|
32
|
-
|
33
|
-
|
32
|
+
def reset_config
|
33
|
+
@config = Config.default
|
34
|
+
end
|
34
35
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
36
|
+
# So we can change the configuration if we want
|
37
|
+
def config
|
38
|
+
@config ||= Config.default
|
39
|
+
end
|
40
|
+
|
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
|
69
|
+
|
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
|
39
85
|
|
40
|
-
|
41
|
-
|
42
|
-
|
86
|
+
def deauthorize_request
|
87
|
+
Thread.current[:mp_authorized] = nil
|
88
|
+
end
|
89
|
+
|
90
|
+
def request_authorized?
|
91
|
+
Thread.current[:mp_authorized]
|
92
|
+
end
|
43
93
|
end
|
44
94
|
|
45
95
|
#
|
46
96
|
# options:
|
47
97
|
# :auto_inject - should script be automatically injected on every html page (not xhr)
|
48
98
|
def initialize(app, config = nil)
|
49
|
-
@@instance = self
|
50
99
|
MiniProfiler.config.merge!(config)
|
51
100
|
@config = MiniProfiler.config
|
52
101
|
@app = app
|
@@ -106,14 +155,6 @@ module Rack
|
|
106
155
|
f.serving env
|
107
156
|
end
|
108
157
|
|
109
|
-
def self.current
|
110
|
-
Thread.current['profiler.mini.private']
|
111
|
-
end
|
112
|
-
|
113
|
-
def self.current=(c)
|
114
|
-
# we use TLS cause we need access to this from sql blocks and code blocks that have no access to env
|
115
|
-
Thread.current['profiler.mini.private'] = c
|
116
|
-
end
|
117
158
|
|
118
159
|
def current
|
119
160
|
MiniProfiler.current
|
@@ -123,48 +164,11 @@ module Rack
|
|
123
164
|
MiniProfiler.current=c
|
124
165
|
end
|
125
166
|
|
126
|
-
# discard existing results, don't track this request
|
127
|
-
def self.discard_results
|
128
|
-
current[:discard] = true if current
|
129
|
-
end
|
130
|
-
|
131
|
-
# user has the mini profiler cookie, only used when config.authorization_mode == :whitelist
|
132
|
-
def self.has_profiling_cookie?(env)
|
133
|
-
env['HTTP_COOKIE'] && env['HTTP_COOKIE'].include?("__profilin=stylin")
|
134
|
-
end
|
135
|
-
|
136
|
-
# remove the mini profiler cookie, only used when config.authorization_mode == :whitelist
|
137
|
-
def self.remove_profiling_cookie(headers)
|
138
|
-
Rack::Utils.delete_cookie_header!(headers, '__profilin')
|
139
|
-
end
|
140
|
-
|
141
|
-
def self.set_profiling_cookie(headers)
|
142
|
-
Rack::Utils.set_cookie_header!(headers, '__profilin', 'stylin')
|
143
|
-
end
|
144
167
|
|
145
168
|
def config
|
146
169
|
@config
|
147
170
|
end
|
148
171
|
|
149
|
-
def self.create_current(env={}, options={})
|
150
|
-
# profiling the request
|
151
|
-
self.current = {}
|
152
|
-
self.current['inject_js'] = config.auto_inject && (!env['HTTP_X_REQUESTED_WITH'].eql? 'XMLHttpRequest')
|
153
|
-
self.current['page_struct'] = PageTimerStruct.new(env)
|
154
|
-
self.current['current_timer'] = current['page_struct']['Root']
|
155
|
-
end
|
156
|
-
|
157
|
-
def self.authorize_request
|
158
|
-
Thread.current[:mp_authorized] = true
|
159
|
-
end
|
160
|
-
|
161
|
-
def self.deauthorize_request
|
162
|
-
Thread.current[:mp_authorized] = nil
|
163
|
-
end
|
164
|
-
|
165
|
-
def self.request_authorized?
|
166
|
-
Thread.current[:mp_authorized]
|
167
|
-
end
|
168
172
|
|
169
173
|
def call(env)
|
170
174
|
status = headers = body = nil
|
@@ -174,12 +178,12 @@ module Rack
|
|
174
178
|
(@config.skip_paths && @config.skip_paths.any?{ |p| path[0,p.length] == p}) ||
|
175
179
|
env["QUERY_STRING"] =~ /pp=skip/
|
176
180
|
|
177
|
-
has_profiling_cookie =
|
181
|
+
has_profiling_cookie = MiniProfiler.has_profiling_cookie?(env)
|
178
182
|
|
179
183
|
if skip_it || (@config.authorization_mode == :whitelist && !has_profiling_cookie)
|
180
184
|
status,headers,body = @app.call(env)
|
181
185
|
if !skip_it && @config.authorization_mode == :whitelist && !has_profiling_cookie && MiniProfiler.request_authorized?
|
182
|
-
|
186
|
+
MiniProfiler.set_profiling_cookie(headers)
|
183
187
|
end
|
184
188
|
return [status,headers,body]
|
185
189
|
end
|
@@ -188,34 +192,38 @@ module Rack
|
|
188
192
|
return serve_html(env) if env['PATH_INFO'].start_with? @config.base_url_path
|
189
193
|
|
190
194
|
MiniProfiler.create_current(env, @config)
|
191
|
-
|
192
195
|
MiniProfiler.deauthorize_request if @config.authorization_mode == :whitelist
|
193
196
|
if env["QUERY_STRING"] =~ /pp=no-backtrace/
|
194
|
-
current
|
197
|
+
current.skip_backtrace = true
|
195
198
|
elsif env["QUERY_STRING"] =~ /pp=full-backtrace/
|
196
|
-
current
|
199
|
+
current.full_backtrace = true
|
197
200
|
end
|
198
201
|
|
199
202
|
done_sampling = false
|
200
203
|
quit_sampler = false
|
201
204
|
backtraces = nil
|
205
|
+
missing_stacktrace = false
|
202
206
|
if env["QUERY_STRING"] =~ /pp=sample/
|
203
207
|
backtraces = []
|
204
208
|
t = Thread.current
|
205
209
|
Thread.new {
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
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
|
210
226
|
end
|
211
|
-
i = 10000 # for sanity never grab more than 10k samples
|
212
|
-
while i > 0
|
213
|
-
break if done_sampling
|
214
|
-
i -= 1
|
215
|
-
backtraces << t.stacktrace
|
216
|
-
sleep 0.001
|
217
|
-
end
|
218
|
-
quit_sampler = true
|
219
227
|
}
|
220
228
|
end
|
221
229
|
|
@@ -230,7 +238,7 @@ module Rack
|
|
230
238
|
end
|
231
239
|
end
|
232
240
|
|
233
|
-
skip_it = current
|
241
|
+
skip_it = current.discard
|
234
242
|
if (config.authorization_mode == :whitelist && !MiniProfiler.request_authorized?)
|
235
243
|
MiniProfiler.remove_profiling_cookie(headers)
|
236
244
|
skip_it = true
|
@@ -249,11 +257,12 @@ module Rack
|
|
249
257
|
return help
|
250
258
|
end
|
251
259
|
|
252
|
-
page_struct = current
|
260
|
+
page_struct = current.page_struct
|
253
261
|
page_struct['Root'].record_time((Time.now - start) * 1000)
|
254
262
|
|
255
263
|
if backtraces
|
256
264
|
body.close if body.respond_to? :close
|
265
|
+
return help(:stacktrace) if missing_stacktrace
|
257
266
|
return analyze(backtraces, page_struct)
|
258
267
|
end
|
259
268
|
|
@@ -271,7 +280,7 @@ module Rack
|
|
271
280
|
end
|
272
281
|
|
273
282
|
# inject script
|
274
|
-
if current
|
283
|
+
if current.inject_js \
|
275
284
|
&& headers.has_key?('Content-Type') \
|
276
285
|
&& !headers['Content-Type'].match(/text\/html/).nil? then
|
277
286
|
body = MiniProfiler::BodyAddProxy.new(body, self.get_profile_script(env))
|
@@ -298,7 +307,7 @@ module Rack
|
|
298
307
|
[200, headers, [body]]
|
299
308
|
end
|
300
309
|
|
301
|
-
def help
|
310
|
+
def help(category = nil)
|
302
311
|
headers = {'Content-Type' => 'text/plain'}
|
303
312
|
body = "Append the following to your query string:
|
304
313
|
|
@@ -309,24 +318,48 @@ module Rack
|
|
309
318
|
pp=full-backtrace : enable full backtrace for SQL executed
|
310
319
|
pp=sample : sample stack traces and return a report isolating heavy usage (requires the stacktrace gem)
|
311
320
|
"
|
312
|
-
|
321
|
+
if (category == :stacktrace)
|
322
|
+
body = "pp=stacktrace requires the stacktrace gem - add gem 'stacktrace' to your Gemfile"
|
323
|
+
end
|
324
|
+
|
313
325
|
[200, headers, [body]]
|
314
326
|
end
|
315
327
|
|
316
328
|
def analyze(traces, page_struct)
|
317
329
|
headers = {'Content-Type' => 'text/plain'}
|
318
330
|
body = "Collected: #{traces.count} stack traces. Duration(ms): #{page_struct.duration_ms}"
|
331
|
+
|
332
|
+
seen = {}
|
333
|
+
fulldump = ""
|
319
334
|
traces.each do |trace|
|
320
|
-
|
335
|
+
fulldump << "\n\n"
|
336
|
+
distinct = {}
|
321
337
|
trace.each do |frame|
|
322
|
-
|
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"
|
323
345
|
end
|
324
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
|
+
|
325
358
|
[200, headers, [body]]
|
326
359
|
end
|
327
360
|
|
328
361
|
def ids_json(env)
|
329
|
-
ids = [current
|
362
|
+
ids = [current.page_struct["Id"]] + (@storage.get_unviewed_ids(user(env)) || [])
|
330
363
|
::JSON.generate(ids.uniq)
|
331
364
|
end
|
332
365
|
|
@@ -345,7 +378,7 @@ module Rack
|
|
345
378
|
showChildren = false
|
346
379
|
maxTracesToShow = 10
|
347
380
|
showControls = false
|
348
|
-
currentId = current
|
381
|
+
currentId = current.page_struct["Id"]
|
349
382
|
authorized = true
|
350
383
|
useExistingjQuery = false
|
351
384
|
# TODO : cache this snippet
|
@@ -357,52 +390,13 @@ module Rack
|
|
357
390
|
end
|
358
391
|
# replace the '{{' and '}}''
|
359
392
|
script.gsub!(/\{\{/, '{').gsub!(/\}\}/, '}')
|
360
|
-
current
|
393
|
+
current.inject_js = false
|
361
394
|
script
|
362
395
|
end
|
363
396
|
|
364
397
|
# cancels automatic injection of profile script for the current page
|
365
398
|
def cancel_auto_inject(env)
|
366
|
-
current
|
367
|
-
end
|
368
|
-
|
369
|
-
# perform a profiling step on given block
|
370
|
-
def self.step(name)
|
371
|
-
if current
|
372
|
-
old_timer = current['current_timer']
|
373
|
-
new_step = RequestTimerStruct.new(name, current['page_struct'])
|
374
|
-
current['current_timer'] = new_step
|
375
|
-
new_step['Name'] = name
|
376
|
-
start = Time.now
|
377
|
-
result = yield if block_given?
|
378
|
-
new_step.record_time((Time.now - start)*1000)
|
379
|
-
old_timer.add_child(new_step)
|
380
|
-
current['current_timer'] = old_timer
|
381
|
-
result
|
382
|
-
else
|
383
|
-
yield if block_given?
|
384
|
-
end
|
385
|
-
end
|
386
|
-
|
387
|
-
def self.profile_method(klass, method, &blk)
|
388
|
-
default_name = klass.to_s + " " + method.to_s
|
389
|
-
with_profiling = (method.to_s + "_with_mini_profiler").intern
|
390
|
-
without_profiling = (method.to_s + "_without_mini_profiler").intern
|
391
|
-
|
392
|
-
klass.send :alias_method, without_profiling, method
|
393
|
-
klass.send :define_method, with_profiling do |*args, &orig|
|
394
|
-
name = default_name
|
395
|
-
name = blk.bind(self).call(*args) if blk
|
396
|
-
::Rack::MiniProfiler.step name do
|
397
|
-
self.send without_profiling, *args, &orig
|
398
|
-
end
|
399
|
-
end
|
400
|
-
klass.send :alias_method, method, with_profiling
|
401
|
-
end
|
402
|
-
|
403
|
-
def record_sql(query, elapsed_ms)
|
404
|
-
c = current
|
405
|
-
c['current_timer'].add_sql(query, elapsed_ms, c['page_struct'], c['skip-backtrace'], c['full-backtrace']) if (c && c['current_timer'])
|
399
|
+
current.inject_js = false
|
406
400
|
end
|
407
401
|
|
408
402
|
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
72
|
def add_sql(query, elapsed_ms, page, skip_backtrace = false, full_backtrace = false)
|
49
|
-
timer = SqlTimerStruct.new(query, elapsed_ms, page, skip_backtrace, full_backtrace)
|
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
|
@@ -5,7 +5,7 @@ module Rack
|
|
5
5
|
|
6
6
|
# Timing system for a SQL query
|
7
7
|
class SqlTimerStruct < TimerStruct
|
8
|
-
def initialize(query, duration_ms, page, skip_backtrace = false, full_backtrace = false)
|
8
|
+
def initialize(query, duration_ms, page, parent, skip_backtrace = false, full_backtrace = false)
|
9
9
|
|
10
10
|
stack_trace = nil
|
11
11
|
unless skip_backtrace
|
@@ -20,17 +20,28 @@ module Rack
|
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
+
@parent = parent
|
24
|
+
@page = page
|
25
|
+
|
23
26
|
super("ExecuteType" => 3, # TODO
|
24
27
|
"FormattedCommandString" => query,
|
25
28
|
"StackTraceSnippet" => stack_trace,
|
26
29
|
"StartMilliseconds" => ((Time.now.to_f * 1000).to_i - page['Started']) - duration_ms,
|
27
30
|
"DurationMilliseconds" => duration_ms,
|
28
|
-
"FirstFetchDurationMilliseconds" =>
|
31
|
+
"FirstFetchDurationMilliseconds" => duration_ms,
|
29
32
|
"Parameters" => nil,
|
30
33
|
"ParentTimingId" => nil,
|
31
34
|
"IsDuplicate" => false)
|
32
35
|
end
|
33
36
|
|
37
|
+
def report_reader_duration(elapsed_ms)
|
38
|
+
return if @reported
|
39
|
+
@reported = true
|
40
|
+
self["DurationMilliseconds"] += elapsed_ms
|
41
|
+
@parent["SqlTimingsDurationMilliseconds"] += elapsed_ms
|
42
|
+
@page["DurationMillisecondsInSql"] += elapsed_ms
|
43
|
+
end
|
44
|
+
|
34
45
|
end
|
35
46
|
|
36
47
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
module MiniProfilerRails
|
2
|
+
|
2
3
|
class Railtie < ::Rails::Railtie
|
3
4
|
|
4
5
|
initializer "rack_mini_profiler.configure_rails_initialization" do |app|
|
@@ -38,6 +39,42 @@ module MiniProfilerRails
|
|
38
39
|
::Rack::MiniProfiler.profile_method(ActionController::Base, :process) {|action| "Executing action: #{action}"}
|
39
40
|
::Rack::MiniProfiler.profile_method(ActionView::Template, :render) {|x,y| "Rendering: #{@virtual_path}"}
|
40
41
|
|
42
|
+
end
|
43
|
+
|
44
|
+
# TODO: Implement something better here
|
45
|
+
# config.after_initialize do
|
46
|
+
#
|
47
|
+
# class ::ActionView::Helpers::AssetTagHelper::JavascriptIncludeTag
|
48
|
+
# alias_method :asset_tag_orig, :asset_tag
|
49
|
+
# def asset_tag(source,options)
|
50
|
+
# current = Rack::MiniProfiler.current
|
51
|
+
# return asset_tag_orig(source,options) unless current
|
52
|
+
# wrapped = ""
|
53
|
+
# unless current.mpt_init
|
54
|
+
# current.mpt_init = true
|
55
|
+
# wrapped << Rack::MiniProfiler::ClientTimerStruct.init_instrumentation
|
56
|
+
# end
|
57
|
+
# name = source.split('/')[-1]
|
58
|
+
# wrapped << Rack::MiniProfiler::ClientTimerStruct.instrument(name, asset_tag_orig(source,options)).html_safe
|
59
|
+
# wrapped
|
60
|
+
# end
|
61
|
+
# end
|
62
|
+
|
63
|
+
# class ::ActionView::Helpers::AssetTagHelper::StylesheetIncludeTag
|
64
|
+
# alias_method :asset_tag_orig, :asset_tag
|
65
|
+
# def asset_tag(source,options)
|
66
|
+
# current = Rack::MiniProfiler.current
|
67
|
+
# return asset_tag_orig(source,options) unless current
|
68
|
+
# wrapped = ""
|
69
|
+
# unless current.mpt_init
|
70
|
+
# current.mpt_init = true
|
71
|
+
# wrapped << Rack::MiniProfiler::ClientTimerStruct.init_instrumentation
|
72
|
+
# end
|
73
|
+
# name = source.split('/')[-1]
|
74
|
+
# wrapped << Rack::MiniProfiler::ClientTimerStruct.instrument(name, asset_tag_orig(source,options)).html_safe
|
75
|
+
# wrapped
|
76
|
+
# end
|
77
|
+
# end
|
41
78
|
|
42
79
|
end
|
43
80
|
|
data/lib/patches/sql_patches.rb
CHANGED
@@ -1,4 +1,13 @@
|
|
1
|
-
|
1
|
+
class SqlPatches
|
2
|
+
|
3
|
+
def self.patched?
|
4
|
+
@patched
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.patched=(val)
|
8
|
+
@patched = val
|
9
|
+
end
|
10
|
+
|
2
11
|
def self.class_exists?(name)
|
3
12
|
eval(name + ".class").to_s.eql?('Class')
|
4
13
|
rescue NameError
|
@@ -6,12 +15,116 @@ module SqlPatches
|
|
6
15
|
end
|
7
16
|
end
|
8
17
|
|
9
|
-
|
18
|
+
# The best kind of instrumentation is in the actual db provider, however we don't want to double instrument
|
19
|
+
if SqlPatches.class_exists? "Mysql2::Client"
|
20
|
+
|
21
|
+
class Mysql2::Result
|
22
|
+
alias_method :each_without_profiling, :each
|
23
|
+
def each(*args, &blk)
|
24
|
+
return each_without_profiling(*args, &blk) unless @miniprofiler_sql_id
|
25
|
+
|
26
|
+
start = Time.now
|
27
|
+
result = each_without_profiling(*args,&blk)
|
28
|
+
elapsed_time = ((Time.now - start).to_f * 1000).round(1)
|
29
|
+
|
30
|
+
@miniprofiler_sql_id.report_reader_duration(elapsed_time)
|
31
|
+
result
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class Mysql2::Client
|
36
|
+
alias_method :query_without_profiling, :query
|
37
|
+
def query(*args,&blk)
|
38
|
+
current = ::Rack::MiniProfiler.current
|
39
|
+
return query_without_profiling(*args,&blk) unless current
|
40
|
+
|
41
|
+
start = Time.now
|
42
|
+
result = query_without_profiling(*args,&blk)
|
43
|
+
elapsed_time = ((Time.now - start).to_f * 1000).round(1)
|
44
|
+
result.instance_variable_set("@miniprofiler_sql_id", ::Rack::MiniProfiler.record_sql(args[0], elapsed_time))
|
45
|
+
|
46
|
+
result
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
SqlPatches.patched = true
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
# PG patches, keep in mind exec and async_exec have a exec{|r| } semantics that is yet to be implemented
|
56
|
+
if SqlPatches.class_exists? "PG::Result"
|
57
|
+
|
58
|
+
class PG::Result
|
59
|
+
alias_method :each_without_profiling, :each
|
60
|
+
alias_method :values_without_profiling, :values
|
61
|
+
|
62
|
+
def values(*args, &blk)
|
63
|
+
return values_without_profiling(*args, &blk) unless @miniprofiler_sql_id
|
64
|
+
|
65
|
+
start = Time.now
|
66
|
+
result = values_without_profiling(*args,&blk)
|
67
|
+
elapsed_time = ((Time.now - start).to_f * 1000).round(1)
|
68
|
+
|
69
|
+
@miniprofiler_sql_id.report_reader_duration(elapsed_time)
|
70
|
+
result
|
71
|
+
end
|
72
|
+
|
73
|
+
def each(*args, &blk)
|
74
|
+
return each_without_profiling(*args, &blk) unless @miniprofiler_sql_id
|
75
|
+
|
76
|
+
start = Time.now
|
77
|
+
result = each_without_profiling(*args,&blk)
|
78
|
+
elapsed_time = ((Time.now - start).to_f * 1000).round(1)
|
79
|
+
|
80
|
+
@miniprofiler_sql_id.report_reader_duration(elapsed_time)
|
81
|
+
result
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class PG::Connection
|
86
|
+
alias_method :exec_without_profiling, :exec
|
87
|
+
alias_method :async_exec_without_profiling, :async_exec
|
88
|
+
|
89
|
+
def exec(*args,&blk)
|
90
|
+
current = ::Rack::MiniProfiler.current
|
91
|
+
return exec_without_profiling(*args,&blk) unless current
|
92
|
+
|
93
|
+
start = Time.now
|
94
|
+
result = exec_without_profiling(*args,&blk)
|
95
|
+
elapsed_time = ((Time.now - start).to_f * 1000).round(1)
|
96
|
+
result.instance_variable_set("@miniprofiler_sql_id", ::Rack::MiniProfiler.record_sql(args[0], elapsed_time))
|
97
|
+
|
98
|
+
result
|
99
|
+
end
|
100
|
+
|
101
|
+
def async_exec(*args,&blk)
|
102
|
+
current = ::Rack::MiniProfiler.current
|
103
|
+
return exec_without_profiling(*args,&blk) unless current
|
104
|
+
|
105
|
+
start = Time.now
|
106
|
+
result = exec_without_profiling(*args,&blk)
|
107
|
+
elapsed_time = ((Time.now - start).to_f * 1000).round(1)
|
108
|
+
result.instance_variable_set("@miniprofiler_sql_id", ::Rack::MiniProfiler.record_sql(args[0], elapsed_time))
|
109
|
+
|
110
|
+
result
|
111
|
+
end
|
112
|
+
|
113
|
+
alias_method :query, :exec
|
114
|
+
end
|
115
|
+
|
116
|
+
SqlPatches.patched = true
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
|
121
|
+
# Fallback for sequel
|
122
|
+
if SqlPatches.class_exists?("Sequel::Database") && !SqlPatches.patched?
|
10
123
|
module Sequel
|
11
124
|
class Database
|
12
125
|
alias_method :log_duration_original, :log_duration
|
13
126
|
def log_duration(duration, message)
|
14
|
-
::Rack::MiniProfiler.
|
127
|
+
::Rack::MiniProfiler.record_sql(message, duration)
|
15
128
|
log_duration_original(duration, message)
|
16
129
|
end
|
17
130
|
end
|
@@ -20,6 +133,7 @@ end
|
|
20
133
|
|
21
134
|
|
22
135
|
## based off https://github.com/newrelic/rpm/blob/master/lib/new_relic/agent/instrumentation/active_record.rb
|
136
|
+
## fallback for alls sorts of weird dbs
|
23
137
|
module Rack
|
24
138
|
class MiniProfiler
|
25
139
|
module ActiveRecordInstrumentation
|
@@ -34,19 +148,18 @@ module Rack
|
|
34
148
|
end
|
35
149
|
|
36
150
|
def log_with_miniprofiler(*args, &block)
|
151
|
+
current = ::Rack::MiniProfiler.current
|
152
|
+
return log_without_miniprofiler(*args, &block) unless current
|
153
|
+
|
37
154
|
sql, name, binds = args
|
38
155
|
t0 = Time.now
|
39
156
|
rval = log_without_miniprofiler(*args, &block)
|
40
|
-
|
41
|
-
# Get our MP Instance
|
42
|
-
instance = ::Rack::MiniProfiler.instance
|
43
|
-
return rval unless instance
|
44
|
-
|
157
|
+
|
45
158
|
# Don't log schema queries if the option is set
|
46
|
-
return rval if
|
159
|
+
return rval if Rack::MiniProfiler.config.skip_schema_queries and name =~ /SCHEMA/
|
47
160
|
|
48
161
|
elapsed_time = ((Time.now - t0).to_f * 1000).round(1)
|
49
|
-
|
162
|
+
Rack::MiniProfiler.record_sql(sql, elapsed_time)
|
50
163
|
rval
|
51
164
|
end
|
52
165
|
end
|
@@ -58,7 +171,7 @@ module Rack
|
|
58
171
|
end
|
59
172
|
end
|
60
173
|
|
61
|
-
if defined?(::Rails)
|
174
|
+
if defined?(::Rails) && !SqlPatches.patched?
|
62
175
|
insert_instrumentation
|
63
176
|
end
|
64
177
|
end
|
data/rack-mini-profiler.gemspec
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-mini-profiler
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -91,8 +91,10 @@ files:
|
|
91
91
|
- lib/mini_profiler/request_timer_struct.rb
|
92
92
|
- lib/mini_profiler/timer_struct.rb
|
93
93
|
- lib/mini_profiler/page_timer_struct.rb
|
94
|
+
- lib/mini_profiler/context.rb
|
94
95
|
- lib/mini_profiler/config.rb
|
95
96
|
- lib/mini_profiler/body_add_proxy.rb
|
97
|
+
- lib/mini_profiler/profiling_methods.rb
|
96
98
|
- lib/mini_profiler/client_timer_struct.rb
|
97
99
|
- lib/mini_profiler/profiler.rb
|
98
100
|
- lib/mini_profiler/storage/memory_store.rb
|
@@ -130,7 +132,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
130
132
|
version: '0'
|
131
133
|
segments:
|
132
134
|
- 0
|
133
|
-
hash:
|
135
|
+
hash: 887503633
|
134
136
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
135
137
|
none: false
|
136
138
|
requirements:
|
@@ -139,7 +141,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
139
141
|
version: '0'
|
140
142
|
segments:
|
141
143
|
- 0
|
142
|
-
hash:
|
144
|
+
hash: 887503633
|
143
145
|
requirements: []
|
144
146
|
rubyforge_project:
|
145
147
|
rubygems_version: 1.8.24
|