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.
@@ -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 = 'rZlycOOTnzxZvxTmFuOEV0dSmu4P5m5bLrCtwJHVXPA='.freeze
20
- @@instance = nil
21
+ VERSION = '104'.freeze
21
22
 
22
- def self.instance
23
- @@instance
24
- end
23
+ class << self
24
+
25
+ include Rack::MiniProfiler::ProfilingMethods
25
26
 
26
- def self.generate_id
27
- rand(36**20).to_s(36)
28
- end
27
+ def generate_id
28
+ rand(36**20).to_s(36)
29
+ end
29
30
 
30
- # Defaults for MiniProfiler's configuration
31
- def self.configuration_defaults
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
- def self.reset_configuration
46
- @configuration = configuration_defaults
47
- end
35
+ # So we can change the configuration if we want
36
+ def config
37
+ @config ||= Config.default
38
+ end
48
39
 
49
- # So we can change the configuration if we want
50
- def self.configuration
51
- @configuration ||= configuration_defaults.dup
52
- end
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
- def self.share_template
55
- return @share_template unless @share_template.nil?
56
- @share_template = ::File.read(::File.expand_path("../html/share.html", ::File.dirname(__FILE__)))
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, opts={})
63
- @@instance = self
64
- MiniProfiler.configuration.merge!(opts)
65
- @options = MiniProfiler.configuration
97
+ def initialize(app, config = nil)
98
+ MiniProfiler.config.merge!(config)
99
+ @config = MiniProfiler.config
66
100
  @app = app
67
- @options[:base_url_path] << "/" unless @options[:base_url_path].end_with? "/"
68
- unless @options[:storage_instance]
69
- @storage = @options[:storage_instance] = @options[:storage].new(@options[:storage_options])
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
- options[:user_provider].call(env)
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
- page_struct['ClientTimings'].init_from_form_data(env, page_struct)
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\}/, @options[:base_url_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'][(@options[:base_url_path].length)..1000]
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
- def self.current
124
- Thread.current['profiler.mini.private']
125
- end
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
- def self.current=(c)
128
- # we use TLS cause we need access to this from sql blocks and code blocks that have no access to env
129
- Thread.current['profiler.mini.private'] = c
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 self.create_current(env={}, options={})
145
- # profiling the request
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
- status = headers = body = nil
183
+ status = headers = body = nil
184
+ path = env['PATH_INFO']
154
185
 
155
- # only profile if authorized
156
- return @app.call(env) unless @options[:authorize_cb].call(env)
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
- # handle all /mini-profiler requests here
159
- return serve_html(env) if env['PATH_INFO'].start_with? @options[:base_url_path]
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, @options)
162
- if env["QUERY_STRING"] =~ /pp=skip-backtrace/
163
- current['skip-backtrace'] = true
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
- i = 10000 # for sanity never grab more than 10k samples
176
- unless done_sampling || i < 0
177
- i -= 1
178
- backtraces << t.backtrace
179
- sleep 0.001
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, body = @app.call(env)
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
- page_struct = current['page_struct']
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['inject_js'] \
298
+ if current.inject_js \
212
299
  && headers.has_key?('Content-Type') \
213
300
  && !headers['Content-Type'].match(/text\/html/).nil? then
214
- body = MiniProfiler::BodyAddProxy.new(body, self.get_profile_script(env))
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['page_struct']["Id"]] + (@storage.get_unviewed_ids(user(env)) || [])
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 = @options[:base_url_path]
397
+ path = @config.base_url_path
243
398
  version = MiniProfiler::VERSION
244
- position = @options[:position]
399
+ position = @config.position
245
400
  showTrivial = false
246
401
  showChildren = false
247
402
  maxTracesToShow = 10
248
403
  showControls = false
249
- currentId = current['page_struct']["Id"]
404
+ currentId = current.page_struct["Id"]
250
405
  authorized = true
251
- useExistingjQuery = false
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['inject_js'] = false
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['inject_js'] = false
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
- attr_reader :children_duration
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 add_child(request_timer)
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
- @children_duration += request_timer['DurationMilliseconds']
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