miniprofiler 0.1.7.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/CHANGELOG +32 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +64 -0
- data/README.md +110 -0
- data/Rakefile +40 -0
- data/autotest/discover.rb +2 -0
- data/lib/mini_profiler/body_add_proxy.rb +45 -0
- data/lib/mini_profiler/client_timer_struct.rb +76 -0
- data/lib/mini_profiler/config.rb +52 -0
- data/lib/mini_profiler/context.rb +10 -0
- data/lib/mini_profiler/page_timer_struct.rb +53 -0
- data/lib/mini_profiler/profiler.rb +405 -0
- data/lib/mini_profiler/profiling_methods.rb +73 -0
- data/lib/mini_profiler/request_timer_struct.rb +96 -0
- data/lib/mini_profiler/sql_timer_struct.rb +48 -0
- data/lib/mini_profiler/storage/abstract_store.rb +27 -0
- data/lib/mini_profiler/storage/file_store.rb +108 -0
- data/lib/mini_profiler/storage/memory_store.rb +68 -0
- data/lib/mini_profiler/storage/redis_store.rb +44 -0
- data/lib/mini_profiler/timer_struct.rb +31 -0
- data/lib/mini_profiler_rails/railtie.rb +84 -0
- data/lib/patches/sql_patches.rb +185 -0
- data/lib/rack-mini-profiler.rb +6 -0
- data/rack-mini-profiler.gemspec +23 -0
- data/spec/components/body_add_proxy_spec.rb +90 -0
- data/spec/components/client_timer_struct_spec.rb +165 -0
- data/spec/components/file_store_spec.rb +47 -0
- data/spec/components/memory_store_spec.rb +40 -0
- data/spec/components/page_timer_struct_spec.rb +33 -0
- data/spec/components/profiler_spec.rb +126 -0
- data/spec/components/redis_store_spec.rb +44 -0
- data/spec/components/request_timer_struct_spec.rb +148 -0
- data/spec/components/sql_timer_struct_spec.rb +63 -0
- data/spec/components/timer_struct_spec.rb +47 -0
- data/spec/fixtures/simple_client_request.yml +17 -0
- data/spec/fixtures/weird_client_request.yml +13 -0
- data/spec/integration/mini_profiler_spec.rb +142 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/support/expand_each_to_matcher.rb +8 -0
- data/test_old/config.ru +41 -0
- metadata +155 -0
@@ -0,0 +1,405 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'timeout'
|
3
|
+
require 'thread'
|
4
|
+
|
5
|
+
require 'mini_profiler/page_timer_struct'
|
6
|
+
require 'mini_profiler/sql_timer_struct'
|
7
|
+
require 'mini_profiler/client_timer_struct'
|
8
|
+
require 'mini_profiler/request_timer_struct'
|
9
|
+
require 'mini_profiler/body_add_proxy'
|
10
|
+
require 'mini_profiler/storage/abstract_store'
|
11
|
+
require 'mini_profiler/storage/memory_store'
|
12
|
+
require 'mini_profiler/storage/redis_store'
|
13
|
+
require 'mini_profiler/storage/file_store'
|
14
|
+
require 'mini_profiler/config'
|
15
|
+
require 'mini_profiler/profiling_methods'
|
16
|
+
require 'mini_profiler/context'
|
17
|
+
|
18
|
+
module Rack
|
19
|
+
|
20
|
+
class MiniProfiler
|
21
|
+
|
22
|
+
VERSION = '104'.freeze
|
23
|
+
|
24
|
+
class << self
|
25
|
+
|
26
|
+
include Rack::MiniProfiler::ProfilingMethods
|
27
|
+
|
28
|
+
def generate_id
|
29
|
+
rand(36**20).to_s(36)
|
30
|
+
end
|
31
|
+
|
32
|
+
def reset_config
|
33
|
+
@config = Config.default
|
34
|
+
end
|
35
|
+
|
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
|
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
|
93
|
+
end
|
94
|
+
|
95
|
+
#
|
96
|
+
# options:
|
97
|
+
# :auto_inject - should script be automatically injected on every html page (not xhr)
|
98
|
+
def initialize(app, config = nil)
|
99
|
+
MiniProfiler.config.merge!(config)
|
100
|
+
@config = MiniProfiler.config
|
101
|
+
@app = app
|
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)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def user(env)
|
109
|
+
@config.user_provider.call(env)
|
110
|
+
end
|
111
|
+
|
112
|
+
def serve_results(env)
|
113
|
+
request = Rack::Request.new(env)
|
114
|
+
id = request['id']
|
115
|
+
page_struct = @storage.load(id)
|
116
|
+
unless page_struct
|
117
|
+
@storage.set_viewed(user(env), id)
|
118
|
+
return [404, {}, ["Request not found: #{request['id']} - user #{user(env)}"]]
|
119
|
+
end
|
120
|
+
unless page_struct['HasUserViewed']
|
121
|
+
page_struct['ClientTimings'].init_from_form_data(env, page_struct)
|
122
|
+
page_struct['HasUserViewed'] = true
|
123
|
+
@storage.save(page_struct)
|
124
|
+
@storage.set_viewed(user(env), id)
|
125
|
+
end
|
126
|
+
|
127
|
+
result_json = page_struct.to_json
|
128
|
+
# If we're an XMLHttpRequest, serve up the contents as JSON
|
129
|
+
if request.xhr?
|
130
|
+
[200, { 'Content-Type' => 'application/json'}, [result_json]]
|
131
|
+
else
|
132
|
+
|
133
|
+
# Otherwise give the HTML back
|
134
|
+
html = MiniProfiler.share_template.dup
|
135
|
+
html.gsub!(/\{path\}/, @config.base_url_path)
|
136
|
+
html.gsub!(/\{version\}/, MiniProfiler::VERSION)
|
137
|
+
html.gsub!(/\{json\}/, result_json)
|
138
|
+
html.gsub!(/\{includes\}/, get_profile_script(env))
|
139
|
+
html.gsub!(/\{name\}/, page_struct['Name'])
|
140
|
+
html.gsub!(/\{duration\}/, page_struct.duration_ms.round(1).to_s)
|
141
|
+
|
142
|
+
[200, {'Content-Type' => 'text/html'}, [html]]
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
|
147
|
+
def serve_html(env)
|
148
|
+
file_name = env['PATH_INFO'][(@config.base_url_path.length)..1000]
|
149
|
+
return serve_results(env) if file_name.eql?('results')
|
150
|
+
full_path = ::File.expand_path("../html/#{file_name}", ::File.dirname(__FILE__))
|
151
|
+
return [404, {}, ["Not found"]] unless ::File.exists? full_path
|
152
|
+
f = Rack::File.new nil
|
153
|
+
f.path = full_path
|
154
|
+
f.cache_control = "max-age:86400"
|
155
|
+
f.serving env
|
156
|
+
end
|
157
|
+
|
158
|
+
|
159
|
+
def current
|
160
|
+
MiniProfiler.current
|
161
|
+
end
|
162
|
+
|
163
|
+
def current=(c)
|
164
|
+
MiniProfiler.current=c
|
165
|
+
end
|
166
|
+
|
167
|
+
|
168
|
+
def config
|
169
|
+
@config
|
170
|
+
end
|
171
|
+
|
172
|
+
|
173
|
+
def call(env)
|
174
|
+
status = headers = body = nil
|
175
|
+
path = env['PATH_INFO']
|
176
|
+
|
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
|
190
|
+
|
191
|
+
# handle all /mini-profiler requests here
|
192
|
+
return serve_html(env) if env['PATH_INFO'].start_with? @config.base_url_path
|
193
|
+
|
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
|
200
|
+
end
|
201
|
+
|
202
|
+
done_sampling = false
|
203
|
+
quit_sampler = false
|
204
|
+
backtraces = nil
|
205
|
+
missing_stacktrace = false
|
206
|
+
if env["QUERY_STRING"] =~ /pp=sample/
|
207
|
+
backtraces = []
|
208
|
+
t = Thread.current
|
209
|
+
Thread.new {
|
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
|
226
|
+
end
|
227
|
+
}
|
228
|
+
end
|
229
|
+
|
230
|
+
status, headers, body = nil
|
231
|
+
start = Time.now
|
232
|
+
begin
|
233
|
+
status,headers,body = @app.call(env)
|
234
|
+
ensure
|
235
|
+
if backtraces
|
236
|
+
done_sampling = true
|
237
|
+
sleep 0.001 until quit_sampler
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
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
|
261
|
+
page_struct['Root'].record_time((Time.now - start) * 1000)
|
262
|
+
|
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
|
275
|
+
if status == 200
|
276
|
+
|
277
|
+
# inject header
|
278
|
+
if headers.is_a? Hash
|
279
|
+
headers['X-MiniProfiler-Ids'] = ids_json(env)
|
280
|
+
end
|
281
|
+
|
282
|
+
# inject script
|
283
|
+
if current.inject_js \
|
284
|
+
&& headers.has_key?('Content-Type') \
|
285
|
+
&& !headers['Content-Type'].match(/text\/html/).nil? then
|
286
|
+
body = MiniProfiler::BodyAddProxy.new(body, self.get_profile_script(env))
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
# mini profiler is meddling with stuff, we can not cache cause we will get incorrect data
|
291
|
+
# Rack::ETag has already inserted some nonesense in the chain
|
292
|
+
headers.delete('ETag')
|
293
|
+
headers.delete('Date')
|
294
|
+
headers['Cache-Control'] = 'must-revalidate, private, max-age=0'
|
295
|
+
[status, headers, body]
|
296
|
+
ensure
|
297
|
+
# Make sure this always happens
|
298
|
+
current = nil
|
299
|
+
end
|
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
|
+
|
361
|
+
def ids_json(env)
|
362
|
+
ids = [current.page_struct["Id"]] + (@storage.get_unviewed_ids(user(env)) || [])
|
363
|
+
::JSON.generate(ids.uniq)
|
364
|
+
end
|
365
|
+
|
366
|
+
# get_profile_script returns script to be injected inside current html page
|
367
|
+
# By default, profile_script is appended to the end of all html requests automatically.
|
368
|
+
# Calling get_profile_script cancels automatic append for the current page
|
369
|
+
# Use it when:
|
370
|
+
# * you have disabled auto append behaviour throught :auto_inject => false flag
|
371
|
+
# * you do not want script to be automatically appended for the current page. You can also call cancel_auto_inject
|
372
|
+
def get_profile_script(env)
|
373
|
+
ids = ids_json(env)
|
374
|
+
path = @config.base_url_path
|
375
|
+
version = MiniProfiler::VERSION
|
376
|
+
position = @config.position
|
377
|
+
showTrivial = false
|
378
|
+
showChildren = false
|
379
|
+
maxTracesToShow = 10
|
380
|
+
showControls = false
|
381
|
+
currentId = current.page_struct["Id"]
|
382
|
+
authorized = true
|
383
|
+
useExistingjQuery = @config.use_existing_jquery
|
384
|
+
# TODO : cache this snippet
|
385
|
+
script = IO.read(::File.expand_path('../html/profile_handler.js', ::File.dirname(__FILE__)))
|
386
|
+
# replace the variables
|
387
|
+
[:ids, :path, :version, :position, :showTrivial, :showChildren, :maxTracesToShow, :showControls, :currentId, :authorized, :useExistingjQuery].each do |v|
|
388
|
+
regex = Regexp.new("\\{#{v.to_s}\\}")
|
389
|
+
script.gsub!(regex, eval(v.to_s).to_s)
|
390
|
+
end
|
391
|
+
# replace the '{{' and '}}''
|
392
|
+
script.gsub!(/\{\{/, '{').gsub!(/\}\}/, '}')
|
393
|
+
current.inject_js = false
|
394
|
+
script
|
395
|
+
end
|
396
|
+
|
397
|
+
# cancels automatic injection of profile script for the current page
|
398
|
+
def cancel_auto_inject(env)
|
399
|
+
current.inject_js = false
|
400
|
+
end
|
401
|
+
|
402
|
+
end
|
403
|
+
|
404
|
+
end
|
405
|
+
|
@@ -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
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'mini_profiler/timer_struct'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
class MiniProfiler
|
5
|
+
|
6
|
+
class RequestTimerStruct < TimerStruct
|
7
|
+
|
8
|
+
def self.createRoot(name, page)
|
9
|
+
rt = RequestTimerStruct.new(name, page, nil)
|
10
|
+
rt["IsRoot"]= true
|
11
|
+
rt
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_accessor :children_duration
|
15
|
+
|
16
|
+
def initialize(name, page, parent)
|
17
|
+
super("Id" => MiniProfiler.generate_id,
|
18
|
+
"Name" => name,
|
19
|
+
"DurationMilliseconds" => 0,
|
20
|
+
"DurationWithoutChildrenMilliseconds"=> 0,
|
21
|
+
"StartMilliseconds" => (Time.now.to_f * 1000).to_i - page['Started'],
|
22
|
+
"ParentTimingId" => nil,
|
23
|
+
"Children" => [],
|
24
|
+
"HasChildren"=> false,
|
25
|
+
"KeyValues" => nil,
|
26
|
+
"HasSqlTimings"=> false,
|
27
|
+
"HasDuplicateSqlTimings"=> false,
|
28
|
+
"TrivialDurationThresholdMilliseconds" => 2,
|
29
|
+
"SqlTimings" => [],
|
30
|
+
"SqlTimingsDurationMilliseconds"=> 0,
|
31
|
+
"IsTrivial"=> false,
|
32
|
+
"IsRoot"=> false,
|
33
|
+
"Depth"=> parent ? parent.depth + 1 : 0,
|
34
|
+
"ExecutedReaders"=> 0,
|
35
|
+
"ExecutedScalars"=> 0,
|
36
|
+
"ExecutedNonQueries"=> 0)
|
37
|
+
@children_duration = 0
|
38
|
+
@start = Time.now
|
39
|
+
@parent = parent
|
40
|
+
@page = page
|
41
|
+
end
|
42
|
+
|
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)
|
65
|
+
self['Children'].push(request_timer)
|
66
|
+
self['HasChildren'] = true
|
67
|
+
request_timer['ParentTimingId'] = self['Id']
|
68
|
+
request_timer['Depth'] = self['Depth'] + 1
|
69
|
+
request_timer
|
70
|
+
end
|
71
|
+
|
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)
|
74
|
+
timer['ParentTimingId'] = self['Id']
|
75
|
+
self['SqlTimings'].push(timer)
|
76
|
+
self['HasSqlTimings'] = true
|
77
|
+
self['SqlTimingsDurationMilliseconds'] += elapsed_ms
|
78
|
+
page['DurationMillisecondsInSql'] += elapsed_ms
|
79
|
+
timer
|
80
|
+
end
|
81
|
+
|
82
|
+
def record_time(milliseconds = nil)
|
83
|
+
milliseconds ||= (Time.now - @start) * 1000
|
84
|
+
self['DurationMilliseconds'] = milliseconds
|
85
|
+
self['IsTrivial'] = true if milliseconds < self["TrivialDurationThresholdMilliseconds"]
|
86
|
+
self['DurationWithoutChildrenMilliseconds'] = milliseconds - @children_duration
|
87
|
+
|
88
|
+
if @parent
|
89
|
+
@parent.children_duration += milliseconds
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'mini_profiler/timer_struct'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
class MiniProfiler
|
5
|
+
|
6
|
+
# Timing system for a SQL query
|
7
|
+
class SqlTimerStruct < TimerStruct
|
8
|
+
def initialize(query, duration_ms, page, parent, skip_backtrace = false, full_backtrace = false)
|
9
|
+
|
10
|
+
stack_trace = nil
|
11
|
+
unless skip_backtrace
|
12
|
+
# Allow us to filter the stack trace
|
13
|
+
stack_trace = ""
|
14
|
+
# Clean up the stack trace if there are options to do so
|
15
|
+
Kernel.caller.each do |ln|
|
16
|
+
ln.gsub!(Rack::MiniProfiler.config.backtrace_remove, '') if Rack::MiniProfiler.config.backtrace_remove and !full_backtrace
|
17
|
+
if full_backtrace or Rack::MiniProfiler.config.backtrace_filter.nil? or ln =~ Rack::MiniProfiler.config.backtrace_filter
|
18
|
+
stack_trace << ln << "\n"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
@parent = parent
|
24
|
+
@page = page
|
25
|
+
|
26
|
+
super("ExecuteType" => 3, # TODO
|
27
|
+
"FormattedCommandString" => query,
|
28
|
+
"StackTraceSnippet" => stack_trace,
|
29
|
+
"StartMilliseconds" => ((Time.now.to_f * 1000).to_i - page['Started']) - duration_ms,
|
30
|
+
"DurationMilliseconds" => duration_ms,
|
31
|
+
"FirstFetchDurationMilliseconds" => duration_ms,
|
32
|
+
"Parameters" => nil,
|
33
|
+
"ParentTimingId" => nil,
|
34
|
+
"IsDuplicate" => false)
|
35
|
+
end
|
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
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Rack
|
2
|
+
class MiniProfiler
|
3
|
+
class AbstractStore
|
4
|
+
|
5
|
+
def save(page_struct)
|
6
|
+
raise NotImplementedError.new("save is not implemented")
|
7
|
+
end
|
8
|
+
|
9
|
+
def load(id)
|
10
|
+
raise NotImplementedError.new("load is not implemented")
|
11
|
+
end
|
12
|
+
|
13
|
+
def set_unviewed(user, id)
|
14
|
+
raise NotImplementedError.new("set_unviewed is not implemented")
|
15
|
+
end
|
16
|
+
|
17
|
+
def set_viewed(user, id)
|
18
|
+
raise NotImplementedError.new("set_viewed is not implemented")
|
19
|
+
end
|
20
|
+
|
21
|
+
def get_unviewed_ids(user)
|
22
|
+
raise NotImplementedError.new("get_unviewed_ids is not implemented")
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|