rack-mini-profiler 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rack-mini-profiler might be problematic. Click here for more details.

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
@@ -0,0 +1,10 @@
1
+ class Rack::MiniProfiler::Context
2
+ attr_accessor :inject_js,:current_timer,:page_struct,:skip_backtrace,:full_backtrace,:discard, :mpt_init
3
+
4
+ def initialize(opts = {})
5
+ opts.each do |k,v|
6
+ self.instance_variable_set('@' + k, v)
7
+ end
8
+ end
9
+
10
+ end
@@ -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, a[0])
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
- def self.instance
24
- @@instance
25
- end
24
+ class << self
25
+
26
+ include Rack::MiniProfiler::ProfilingMethods
26
27
 
27
- def self.generate_id
28
- rand(36**20).to_s(36)
29
- end
28
+ def generate_id
29
+ rand(36**20).to_s(36)
30
+ end
30
31
 
31
- def self.reset_config
32
- @config = Config.default
33
- end
32
+ def reset_config
33
+ @config = Config.default
34
+ end
34
35
 
35
- # So we can change the configuration if we want
36
- def self.config
37
- @config ||= Config.default
38
- end
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
- def self.share_template
41
- return @share_template unless @share_template.nil?
42
- @share_template = ::File.read(::File.expand_path("../html/share.html", ::File.dirname(__FILE__)))
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 = self.class.has_profiling_cookie?(env)
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
- self.class.set_profiling_cookie(headers)
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['skip-backtrace'] = true
197
+ current.skip_backtrace = true
195
198
  elsif env["QUERY_STRING"] =~ /pp=full-backtrace/
196
- current['full-backtrace'] = true
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
- require 'stacktrace'
207
- if !t.respond_to? :stacktrace
208
- quit_sampler = true
209
- return
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[:discard]
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['page_struct']
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['inject_js'] \
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
- #headers['Content-Length'] = body.length
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
- body << "\n\n"
335
+ fulldump << "\n\n"
336
+ distinct = {}
321
337
  trace.each do |frame|
322
- body << "#{frame.klass} #{frame.method}\n"
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['page_struct']["Id"]] + (@storage.get_unviewed_ids(user(env)) || [])
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['page_struct']["Id"]
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['inject_js'] = false
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['inject_js'] = false
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
- 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
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" => 0,
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
 
@@ -1,4 +1,13 @@
1
- module SqlPatches
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
- if SqlPatches.class_exists? "Sequel::Database" then
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.instance.record_sql(message, duration) if ::Rack::MiniProfiler.instance
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 instance.config.skip_schema_queries and name =~ /SCHEMA/
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
- instance.record_sql(sql, elapsed_time)
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
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "rack-mini-profiler"
3
- s.version = "0.1.3"
3
+ s.version = "0.1.4"
4
4
  s.summary = "Profiles loading speed for rack applications."
5
5
  s.authors = ["Aleks Totic","Sam Saffron", "Robin Ward"]
6
6
  s.date = "2012-04-02"
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.3
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: 809318397
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: 809318397
144
+ hash: 887503633
143
145
  requirements: []
144
146
  rubyforge_project:
145
147
  rubygems_version: 1.8.24