miniprofiler 0.1.7.1

Sign up to get free protection for your applications and to get access to all the features.
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