miniprofiler 0.1.7.1
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/.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
|