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.
Files changed (42) hide show
  1. data/.gitignore +4 -0
  2. data/CHANGELOG +32 -0
  3. data/Gemfile +15 -0
  4. data/Gemfile.lock +64 -0
  5. data/README.md +110 -0
  6. data/Rakefile +40 -0
  7. data/autotest/discover.rb +2 -0
  8. data/lib/mini_profiler/body_add_proxy.rb +45 -0
  9. data/lib/mini_profiler/client_timer_struct.rb +76 -0
  10. data/lib/mini_profiler/config.rb +52 -0
  11. data/lib/mini_profiler/context.rb +10 -0
  12. data/lib/mini_profiler/page_timer_struct.rb +53 -0
  13. data/lib/mini_profiler/profiler.rb +405 -0
  14. data/lib/mini_profiler/profiling_methods.rb +73 -0
  15. data/lib/mini_profiler/request_timer_struct.rb +96 -0
  16. data/lib/mini_profiler/sql_timer_struct.rb +48 -0
  17. data/lib/mini_profiler/storage/abstract_store.rb +27 -0
  18. data/lib/mini_profiler/storage/file_store.rb +108 -0
  19. data/lib/mini_profiler/storage/memory_store.rb +68 -0
  20. data/lib/mini_profiler/storage/redis_store.rb +44 -0
  21. data/lib/mini_profiler/timer_struct.rb +31 -0
  22. data/lib/mini_profiler_rails/railtie.rb +84 -0
  23. data/lib/patches/sql_patches.rb +185 -0
  24. data/lib/rack-mini-profiler.rb +6 -0
  25. data/rack-mini-profiler.gemspec +23 -0
  26. data/spec/components/body_add_proxy_spec.rb +90 -0
  27. data/spec/components/client_timer_struct_spec.rb +165 -0
  28. data/spec/components/file_store_spec.rb +47 -0
  29. data/spec/components/memory_store_spec.rb +40 -0
  30. data/spec/components/page_timer_struct_spec.rb +33 -0
  31. data/spec/components/profiler_spec.rb +126 -0
  32. data/spec/components/redis_store_spec.rb +44 -0
  33. data/spec/components/request_timer_struct_spec.rb +148 -0
  34. data/spec/components/sql_timer_struct_spec.rb +63 -0
  35. data/spec/components/timer_struct_spec.rb +47 -0
  36. data/spec/fixtures/simple_client_request.yml +17 -0
  37. data/spec/fixtures/weird_client_request.yml +13 -0
  38. data/spec/integration/mini_profiler_spec.rb +142 -0
  39. data/spec/spec_helper.rb +31 -0
  40. data/spec/support/expand_each_to_matcher.rb +8 -0
  41. data/test_old/config.ru +41 -0
  42. 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