rack-mini-profiler 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

data/CHANGELOG 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