rack-mini-profiler 1.0.2 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +138 -21
  3. data/README.md +201 -94
  4. data/lib/enable_rails_patches.rb +5 -0
  5. data/lib/generators/rack_mini_profiler/USAGE +9 -0
  6. data/lib/generators/rack_mini_profiler/install_generator.rb +13 -0
  7. data/lib/generators/{rack_profiler/templates/rack_profiler.rb → rack_mini_profiler/templates/rack_mini_profiler.rb} +1 -1
  8. data/lib/generators/rack_profiler/install_generator.rb +6 -3
  9. data/lib/html/dot.1.1.2.min.js +2 -0
  10. data/lib/html/includes.css +144 -45
  11. data/lib/html/includes.js +1423 -1009
  12. data/lib/html/includes.scss +538 -441
  13. data/lib/html/includes.tmpl +231 -148
  14. data/lib/html/pretty-print.js +810 -0
  15. data/lib/html/profile_handler.js +1 -1
  16. data/lib/html/rack-mini-profiler.css +3 -0
  17. data/lib/html/rack-mini-profiler.js +2 -0
  18. data/lib/html/share.html +0 -1
  19. data/lib/html/speedscope/LICENSE +21 -0
  20. data/lib/html/speedscope/README.md +3 -0
  21. data/lib/html/speedscope/demangle-cpp.1768f4cc.js +4 -0
  22. data/lib/html/speedscope/favicon-16x16.f74b3187.png +0 -0
  23. data/lib/html/speedscope/favicon-32x32.bc503437.png +0 -0
  24. data/lib/html/speedscope/file-format-schema.json +324 -0
  25. data/lib/html/speedscope/fonts/source-code-pro-regular.css +8 -0
  26. data/lib/html/speedscope/fonts/source-code-pro-v13-regular.woff +0 -0
  27. data/lib/html/speedscope/fonts/source-code-pro-v13-regular.woff2 +0 -0
  28. data/lib/html/speedscope/import.cf0fa83f.js +115 -0
  29. data/lib/html/speedscope/index.html +2 -0
  30. data/lib/html/speedscope/release.txt +3 -0
  31. data/lib/html/speedscope/reset.8c46b7a1.css +2 -0
  32. data/lib/html/speedscope/source-map.438fa06b.js +24 -0
  33. data/lib/html/speedscope/speedscope.44364064.js +200 -0
  34. data/lib/html/vendor.js +848 -0
  35. data/lib/mini_profiler/asset_version.rb +3 -2
  36. data/lib/mini_profiler/client_settings.rb +15 -7
  37. data/lib/mini_profiler/config.rb +51 -5
  38. data/lib/mini_profiler/gc_profiler.rb +1 -1
  39. data/lib/mini_profiler/profiling_methods.rb +13 -8
  40. data/lib/mini_profiler/snapshots_transporter.rb +109 -0
  41. data/lib/mini_profiler/storage/abstract_store.rb +52 -1
  42. data/lib/mini_profiler/storage/file_store.rb +7 -3
  43. data/lib/mini_profiler/storage/memcache_store.rb +13 -7
  44. data/lib/mini_profiler/storage/memory_store.rb +98 -5
  45. data/lib/mini_profiler/storage/redis_store.rb +226 -3
  46. data/lib/mini_profiler/storage.rb +7 -0
  47. data/lib/mini_profiler/timer_struct/base.rb +2 -0
  48. data/lib/mini_profiler/timer_struct/custom.rb +1 -0
  49. data/lib/mini_profiler/timer_struct/page.rb +60 -4
  50. data/lib/mini_profiler/timer_struct/request.rb +53 -11
  51. data/lib/mini_profiler/timer_struct/sql.rb +6 -2
  52. data/lib/mini_profiler/timer_struct.rb +8 -0
  53. data/lib/mini_profiler/version.rb +2 -1
  54. data/lib/{mini_profiler/profiler.rb → mini_profiler.rb} +394 -82
  55. data/lib/mini_profiler_rails/railtie.rb +88 -7
  56. data/lib/mini_profiler_rails/railtie_methods.rb +61 -0
  57. data/lib/patches/db/activerecord.rb +1 -12
  58. data/lib/patches/db/mongo.rb +1 -1
  59. data/lib/patches/db/moped.rb +1 -1
  60. data/lib/patches/db/mysql2/alias_method.rb +30 -0
  61. data/lib/patches/db/mysql2/prepend.rb +34 -0
  62. data/lib/patches/db/mysql2.rb +4 -27
  63. data/lib/patches/db/plucky.rb +4 -4
  64. data/lib/patches/db/riak.rb +1 -1
  65. data/lib/patches/net_patches.rb +21 -10
  66. data/lib/patches/sql_patches.rb +13 -5
  67. data/lib/prepend_mysql2_patch.rb +5 -0
  68. data/lib/prepend_net_http_patch.rb +5 -0
  69. data/lib/rack-mini-profiler.rb +1 -24
  70. data/rack-mini-profiler.gemspec +17 -8
  71. metadata +156 -32
  72. data/lib/html/jquery.1.7.1.js +0 -4
  73. data/lib/html/jquery.tmpl.js +0 -486
  74. data/lib/html/list.css +0 -9
  75. data/lib/html/list.js +0 -38
  76. data/lib/html/list.tmpl +0 -34
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'digest'
4
+ require 'securerandom'
5
+
3
6
  module Rack
4
7
  class MiniProfiler
5
8
  class RedisStore < AbstractStore
@@ -23,7 +26,9 @@ module Rack
23
26
  key = prefixed_id(id)
24
27
  raw = redis.get key
25
28
  begin
26
- Marshal::load(raw) if raw
29
+ # rubocop:disable Security/MarshalLoad
30
+ Marshal.load(raw) if raw
31
+ # rubocop:enable Security/MarshalLoad
27
32
  rescue
28
33
  # bad format, junk old data
29
34
  redis.del key
@@ -33,7 +38,7 @@ module Rack
33
38
 
34
39
  def set_unviewed(user, id)
35
40
  key = user_key(user)
36
- if redis.exists(prefixed_id(id))
41
+ if redis.call([:exists, prefixed_id(id)]) == 1
37
42
  expire_at = Process.clock_gettime(Process::CLOCK_MONOTONIC).to_i + redis.ttl(prefixed_id(id))
38
43
  redis.zadd(key, expire_at, id)
39
44
  end
@@ -44,7 +49,7 @@ module Rack
44
49
  key = user_key(user)
45
50
  redis.del(key)
46
51
  ids.each do |id|
47
- if redis.exists(prefixed_id(id))
52
+ if redis.call([:exists, prefixed_id(id)]) == 1
48
53
  expire_at = Process.clock_gettime(Process::CLOCK_MONOTONIC).to_i + redis.ttl(prefixed_id(id))
49
54
  redis.zadd(key, expire_at, id)
50
55
  end
@@ -108,6 +113,152 @@ unviewed_ids: #{get_unviewed_ids(user)}
108
113
  [key1, key2].compact
109
114
  end
110
115
 
116
+ COUNTER_LUA = <<~LUA
117
+ if redis.call("INCR", KEYS[1]) % ARGV[1] == 0 then
118
+ redis.call("DEL", KEYS[1])
119
+ return 1
120
+ else
121
+ return 0
122
+ end
123
+ LUA
124
+
125
+ COUNTER_LUA_SHA = Digest::SHA1.hexdigest(COUNTER_LUA)
126
+
127
+ def should_take_snapshot?(period)
128
+ 1 == cached_redis_eval(
129
+ COUNTER_LUA,
130
+ COUNTER_LUA_SHA,
131
+ reraise: false,
132
+ keys: [snapshot_counter_key()],
133
+ argv: [period]
134
+ )
135
+ end
136
+
137
+ def push_snapshot(page_struct, group_name, config)
138
+ group_zset_key = group_snapshot_zset_key(group_name)
139
+ group_hash_key = group_snapshot_hash_key(group_name)
140
+ overview_zset_key = snapshot_overview_zset_key
141
+
142
+ id = page_struct[:id]
143
+ score = page_struct.duration_ms.to_s
144
+
145
+ per_group_limit = config.max_snapshots_per_group.to_s
146
+ groups_limit = config.max_snapshot_groups.to_s
147
+ bytes = Marshal.dump(page_struct)
148
+
149
+ lua = <<~LUA
150
+ local group_zset_key = KEYS[1]
151
+ local group_hash_key = KEYS[2]
152
+ local overview_zset_key = KEYS[3]
153
+
154
+ local id = ARGV[1]
155
+ local score = tonumber(ARGV[2])
156
+ local group_name = ARGV[3]
157
+ local per_group_limit = tonumber(ARGV[4])
158
+ local groups_limit = tonumber(ARGV[5])
159
+ local prefix = ARGV[6]
160
+ local bytes = ARGV[7]
161
+
162
+ local current_group_score = redis.call("ZSCORE", overview_zset_key, group_name)
163
+ if current_group_score == false or score > tonumber(current_group_score) then
164
+ redis.call("ZADD", overview_zset_key, score, group_name)
165
+ end
166
+
167
+ local do_save = true
168
+ local overview_size = redis.call("ZCARD", overview_zset_key)
169
+ while (overview_size > groups_limit) do
170
+ local lowest_group = redis.call("ZRANGE", overview_zset_key, 0, 0)[1]
171
+ redis.call("ZREM", overview_zset_key, lowest_group)
172
+ if lowest_group == group_name then
173
+ do_save = false
174
+ else
175
+ local lowest_group_zset_key = prefix .. "-mp-group-snapshot-zset-key-" .. lowest_group
176
+ local lowest_group_hash_key = prefix .. "-mp-group-snapshot-hash-key-" .. lowest_group
177
+ redis.call("DEL", lowest_group_zset_key, lowest_group_hash_key)
178
+ end
179
+ overview_size = overview_size - 1
180
+ end
181
+
182
+ if do_save then
183
+ redis.call("ZADD", group_zset_key, score, id)
184
+ local group_size = redis.call("ZCARD", group_zset_key)
185
+ while (group_size > per_group_limit) do
186
+ local lowest_snapshot_id = redis.call("ZRANGE", group_zset_key, 0, 0)[1]
187
+ redis.call("ZREM", group_zset_key, lowest_snapshot_id)
188
+ if lowest_snapshot_id == id then
189
+ do_save = false
190
+ else
191
+ redis.call("HDEL", group_hash_key, lowest_snapshot_id)
192
+ end
193
+ group_size = group_size - 1
194
+ end
195
+ if do_save then
196
+ redis.call("HSET", group_hash_key, id, bytes)
197
+ end
198
+ end
199
+ LUA
200
+ redis.eval(
201
+ lua,
202
+ keys: [group_zset_key, group_hash_key, overview_zset_key],
203
+ argv: [id, score, group_name, per_group_limit, groups_limit, @prefix, bytes]
204
+ )
205
+ end
206
+
207
+ def fetch_snapshots_overview
208
+ overview_zset_key = snapshot_overview_zset_key
209
+ groups = redis
210
+ .zrange(overview_zset_key, 0, -1, withscores: true)
211
+ .map { |(name, worst_score)| [name, { worst_score: worst_score }] }
212
+
213
+ prefixed_group_names = groups.map { |(group_name, _)| group_snapshot_zset_key(group_name) }
214
+ metadata = redis.eval(<<~LUA, keys: prefixed_group_names)
215
+ local metadata = {}
216
+ for i, k in ipairs(KEYS) do
217
+ local best = redis.call("ZRANGE", k, 0, 0, "WITHSCORES")[2]
218
+ local count = redis.call("ZCARD", k)
219
+ metadata[i] = {best, count}
220
+ end
221
+ return metadata
222
+ LUA
223
+ groups.each.with_index do |(_, hash), index|
224
+ best, count = metadata[index]
225
+ hash[:best_score] = best.to_f
226
+ hash[:snapshots_count] = count.to_i
227
+ end
228
+ groups.to_h
229
+ end
230
+
231
+ def fetch_snapshots_group(group_name)
232
+ group_hash_key = group_snapshot_hash_key(group_name)
233
+ snapshots = []
234
+ corrupt_snapshots = []
235
+ redis.hgetall(group_hash_key).each do |id, bytes|
236
+ # rubocop:disable Security/MarshalLoad
237
+ snapshots << Marshal.load(bytes)
238
+ # rubocop:enable Security/MarshalLoad
239
+ rescue
240
+ corrupt_snapshots << id
241
+ end
242
+ if corrupt_snapshots.size > 0
243
+ cleanup_corrupt_snapshots(corrupt_snapshots, group_name)
244
+ end
245
+ snapshots
246
+ end
247
+
248
+ def load_snapshot(id, group_name)
249
+ group_hash_key = group_snapshot_hash_key(group_name)
250
+ bytes = redis.hget(group_hash_key, id)
251
+ return if !bytes
252
+ begin
253
+ # rubocop:disable Security/MarshalLoad
254
+ Marshal.load(bytes)
255
+ # rubocop:enable Security/MarshalLoad
256
+ rescue
257
+ cleanup_corrupt_snapshots([id], group_name)
258
+ nil
259
+ end
260
+ end
261
+
111
262
  private
112
263
 
113
264
  def user_key(user)
@@ -125,6 +276,78 @@ unviewed_ids: #{get_unviewed_ids(user)}
125
276
  end
126
277
  end
127
278
 
279
+ def snapshot_counter_key
280
+ @snapshot_counter_key ||= "#{@prefix}-mini-profiler-snapshots-counter"
281
+ end
282
+
283
+ def group_snapshot_zset_key(group_name)
284
+ # if you change this key, remember to change it in the LUA script in
285
+ # the push_snapshot method as well
286
+ "#{@prefix}-mp-group-snapshot-zset-key-#{group_name}"
287
+ end
288
+
289
+ def group_snapshot_hash_key(group_name)
290
+ # if you change this key, remember to change it in the LUA script in
291
+ # the push_snapshot method as well
292
+ "#{@prefix}-mp-group-snapshot-hash-key-#{group_name}"
293
+ end
294
+
295
+ def snapshot_overview_zset_key
296
+ "#{@prefix}-mp-overviewgroup-snapshot-zset-key"
297
+ end
298
+
299
+ def cached_redis_eval(script, script_sha, reraise: true, argv: [], keys: [])
300
+ begin
301
+ redis.evalsha(script_sha, argv: argv, keys: keys)
302
+ rescue ::Redis::CommandError => e
303
+ if e.message.start_with?('NOSCRIPT')
304
+ redis.eval(script, argv: argv, keys: keys)
305
+ else
306
+ raise e if reraise
307
+ end
308
+ end
309
+ end
310
+
311
+ def cleanup_corrupt_snapshots(corrupt_snapshots_ids, group_name)
312
+ group_hash_key = group_snapshot_hash_key(group_name)
313
+ group_zset_key = group_snapshot_zset_key(group_name)
314
+ overview_zset_key = snapshot_overview_zset_key
315
+ lua = <<~LUA
316
+ local group_hash_key = KEYS[1]
317
+ local group_zset_key = KEYS[2]
318
+ local overview_zset_key = KEYS[3]
319
+ local group_name = ARGV[1]
320
+ for i, k in ipairs(ARGV) do
321
+ if k ~= group_name then
322
+ redis.call("HDEL", group_hash_key, k)
323
+ redis.call("ZREM", group_zset_key, k)
324
+ end
325
+ end
326
+ if redis.call("ZCARD", group_zset_key) == 0 then
327
+ redis.call("ZREM", overview_zset_key, group_name)
328
+ redis.call("DEL", group_hash_key, group_zset_key)
329
+ else
330
+ local worst_score = tonumber(redis.call("ZRANGE", group_zset_key, -1, -1, "WITHSCORES")[2])
331
+ redis.call("ZADD", overview_zset_key, worst_score, group_name)
332
+ end
333
+ LUA
334
+ redis.eval(
335
+ lua,
336
+ keys: [group_hash_key, group_zset_key, overview_zset_key],
337
+ argv: [group_name, *corrupt_snapshots_ids]
338
+ )
339
+ end
340
+
341
+ # only used in tests
342
+ def wipe_snapshots_data
343
+ keys = redis.keys(group_snapshot_hash_key('*'))
344
+ keys += redis.keys(group_snapshot_zset_key('*'))
345
+ redis.del(
346
+ keys,
347
+ snapshot_overview_zset_key,
348
+ snapshot_counter_key
349
+ )
350
+ end
128
351
  end
129
352
  end
130
353
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mini_profiler/storage/abstract_store'
4
+ require 'mini_profiler/storage/memcache_store'
5
+ require 'mini_profiler/storage/memory_store'
6
+ require 'mini_profiler/storage/redis_store'
7
+ require 'mini_profiler/storage/file_store'
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'json'
4
+
3
5
  module Rack
4
6
  class MiniProfiler
5
7
  module TimerStruct
@@ -6,6 +6,7 @@ module Rack
6
6
  # Timing system for a custom timers such as cache, redis, RPC, external API
7
7
  # calls, etc.
8
8
  class Custom < TimerStruct::Base
9
+ attr_accessor :parent
9
10
  def initialize(type, duration_ms, page, parent)
10
11
  @parent = parent
11
12
  @page = page
@@ -10,6 +10,53 @@ module Rack
10
10
  # :has_many TimerStruct::Sql children
11
11
  # :has_many TimerStruct::Custom children
12
12
  class Page < TimerStruct::Base
13
+ class << self
14
+ def from_hash(hash)
15
+ hash = symbolize_hash(hash)
16
+ if hash.key?(:custom_timing_names)
17
+ hash[:custom_timing_names] = []
18
+ end
19
+ hash.delete(:started_formatted)
20
+ if hash.key?(:duration_milliseconds)
21
+ hash[:duration_milliseconds] = 0
22
+ end
23
+ page = self.allocate
24
+ page.instance_variable_set(:@attributes, hash)
25
+ page
26
+ end
27
+
28
+ private
29
+
30
+ def symbolize_hash(hash)
31
+ new_hash = {}
32
+ hash.each do |k, v|
33
+ sym_k = String === k ? k.to_sym : k
34
+ if Hash === v
35
+ new_hash[sym_k] = symbolize_hash(v)
36
+ elsif Array === v
37
+ new_hash[sym_k] = symbolize_array(v)
38
+ else
39
+ new_hash[sym_k] = v
40
+ end
41
+ end
42
+ new_hash
43
+ end
44
+
45
+ def symbolize_array(array)
46
+ array.map do |item|
47
+ if Array === item
48
+ symbolize_array(item)
49
+ elsif Hash === item
50
+ symbolize_hash(item)
51
+ else
52
+ item
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ attr_reader :attributes
59
+
13
60
  def initialize(env)
14
61
  timer_id = MiniProfiler.generate_id
15
62
  page_name = env['PATH_INFO']
@@ -39,8 +86,13 @@ module Rack
39
86
  executed_scalars: 0,
40
87
  executed_non_queries: 0,
41
88
  custom_timing_names: [],
42
- custom_timing_stats: {}
89
+ custom_timing_stats: {},
90
+ custom_fields: {},
91
+ has_flamegraph: false,
92
+ flamegraph: nil
43
93
  )
94
+ self[:request_method] = env['REQUEST_METHOD']
95
+ self[:request_path] = env['PATH_INFO']
44
96
  name = "#{env['REQUEST_METHOD']} http://#{env['SERVER_NAME']}:#{env['SERVER_PORT']}#{env['SCRIPT_NAME']}#{env['PATH_INFO']}"
45
97
  self[:root] = TimerStruct::Request.createRoot(name, self)
46
98
  end
@@ -61,17 +113,21 @@ module Rack
61
113
  @attributes[:root]
62
114
  end
63
115
 
116
+ def attributes_to_serialize
117
+ @attributes.keys - [:flamegraph]
118
+ end
119
+
64
120
  def to_json(*a)
65
- ::JSON.generate(@attributes.merge(self.extra_json))
121
+ ::JSON.generate(@attributes.slice(*attributes_to_serialize).merge(extra_json))
66
122
  end
67
123
 
68
124
  def as_json(options = nil)
69
- super(options).merge!(extra_json)
125
+ super(options).slice(*attributes_to_serialize.map(&:to_s)).merge!(extra_json)
70
126
  end
71
127
 
72
128
  def extra_json
73
129
  {
74
- started: '/Date(%d)/' % @attributes[:started_at],
130
+ started_formatted: '/Date(%d)/' % @attributes[:started_at],
75
131
  duration_milliseconds: @attributes[:root][:duration_milliseconds],
76
132
  custom_timing_names: @attributes[:custom_timing_stats].keys.sort
77
133
  }
@@ -11,7 +11,7 @@ module Rack
11
11
  end
12
12
  end
13
13
 
14
- attr_accessor :children_duration
14
+ attr_accessor :children_duration, :start, :parent
15
15
 
16
16
  def initialize(name, page, parent)
17
17
  start_millis = (Process.clock_gettime(Process::CLOCK_MONOTONIC) * 1000).to_i - page[:started]
@@ -62,10 +62,6 @@ module Rack
62
62
  self[:start_milliseconds]
63
63
  end
64
64
 
65
- def start
66
- @start
67
- end
68
-
69
65
  def depth
70
66
  self[:depth]
71
67
  end
@@ -91,6 +87,20 @@ module Rack
91
87
  end
92
88
  end
93
89
 
90
+ def move_child(child, destination)
91
+ if index = self[:children].index(child)
92
+ self[:children].slice!(index)
93
+ self[:has_children] = self[:children].size > 0
94
+
95
+ destination[:children].push(child)
96
+ destination[:has_children] = true
97
+
98
+ child[:parent_timing_id] = destination[:id]
99
+ child.parent = destination
100
+ child.adjust_depth
101
+ end
102
+ end
103
+
94
104
  def add_sql(query, elapsed_ms, page, params = nil, skip_backtrace = false, full_backtrace = false)
95
105
  TimerStruct::Sql.new(query, elapsed_ms, page, self, params, skip_backtrace, full_backtrace).tap do |timer|
96
106
  self[:sql_timings].push(timer)
@@ -102,6 +112,19 @@ module Rack
102
112
  end
103
113
  end
104
114
 
115
+ def move_sql(sql, destination)
116
+ if index = self[:sql_timings].index(sql)
117
+ self[:sql_timings].slice!(index)
118
+ self[:has_sql_timings] = self[:sql_timings].size > 0
119
+ self[:sql_timings_duration_milliseconds] -= sql[:duration_milliseconds]
120
+ destination[:sql_timings].push(sql)
121
+ destination[:has_sql_timings] = true
122
+ destination[:sql_timings_duration_milliseconds] += sql[:duration_milliseconds]
123
+ sql[:parent_timing_id] = destination[:id]
124
+ sql.parent = destination
125
+ end
126
+ end
127
+
105
128
  def add_custom(type, elapsed_ms, page)
106
129
  TimerStruct::Custom.new(type, elapsed_ms, page, self).tap do |timer|
107
130
  timer[:parent_timing_id] = self[:id]
@@ -119,18 +142,37 @@ module Rack
119
142
  end
120
143
  end
121
144
 
145
+ def move_custom(type, custom, destination)
146
+ if index = self[:custom_timings][type]&.index(custom)
147
+ custom[:parent_timing_id] = destination[:id]
148
+ custom.parent = destination
149
+ self[:custom_timings][type].slice!(index)
150
+ if self[:custom_timings][type].size == 0
151
+ self[:custom_timings].delete(type)
152
+ self[:custom_timing_stats].delete(type)
153
+ else
154
+ self[:custom_timing_stats][type][:count] -= 1
155
+ self[:custom_timing_stats][type][:duration] -= custom[:duration_milliseconds]
156
+ end
157
+ destination[:custom_timings][type] ||= []
158
+ destination[:custom_timings][type].push(custom)
159
+ destination[:custom_timing_stats][type] ||= { count: 0, duration: 0.0 }
160
+ destination[:custom_timing_stats][type][:count] += 1
161
+ destination[:custom_timing_stats][type][:duration] += custom[:duration_milliseconds]
162
+ end
163
+ end
164
+
122
165
  def record_time(milliseconds = nil)
123
166
  milliseconds ||= (Process.clock_gettime(Process::CLOCK_MONOTONIC) - @start) * 1000
124
167
  self[:duration_milliseconds] = milliseconds
125
168
  self[:is_trivial] = true if milliseconds < self[:trivial_duration_threshold_milliseconds]
126
- self[:duration_without_children_milliseconds] = milliseconds - @children_duration
127
-
128
- if @parent
129
- @parent.children_duration += milliseconds
130
- end
131
-
169
+ self[:duration_without_children_milliseconds] = milliseconds - self[:children].sum(&:duration_ms)
132
170
  end
133
171
 
172
+ def adjust_depth
173
+ self[:depth] = self.parent ? self.parent[:depth] + 1 : 0
174
+ self[:children].each(&:adjust_depth)
175
+ end
134
176
  end
135
177
  end
136
178
  end
@@ -1,11 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'erb'
4
+
3
5
  module Rack
4
6
  class MiniProfiler
5
7
 
6
8
  # Timing system for a SQL query
7
9
  module TimerStruct
8
10
  class Sql < TimerStruct::Base
11
+ attr_accessor :parent
12
+
9
13
  def initialize(query, duration_ms, page, parent, params = nil, skip_backtrace = false, full_backtrace = false)
10
14
 
11
15
  stack_trace = nil
@@ -36,12 +40,12 @@ module Rack
36
40
  start_millis = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) * 1000).to_i - page[:started]) - duration_ms
37
41
  super(
38
42
  execute_type: 3, # TODO
39
- formatted_command_string: query,
43
+ formatted_command_string: query ? ERB::Util.html_escape(query) : nil,
40
44
  stack_trace_snippet: stack_trace,
41
45
  start_milliseconds: start_millis,
42
46
  duration_milliseconds: duration_ms,
43
47
  first_fetch_duration_milliseconds: duration_ms,
44
- parameters: trim_binds(params),
48
+ parameters: query ? trim_binds(params) : nil,
45
49
  parent_timing_id: nil,
46
50
  is_duplicate: false
47
51
  )
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mini_profiler/timer_struct/base'
4
+ require 'mini_profiler/timer_struct/page'
5
+ require 'mini_profiler/timer_struct/sql'
6
+ require 'mini_profiler/timer_struct/custom'
7
+ require 'mini_profiler/timer_struct/client'
8
+ require 'mini_profiler/timer_struct/request'
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Rack
4
4
  class MiniProfiler
5
- VERSION = '1.0.2'
5
+ VERSION = '3.1.0'
6
+ SOURCE_CODE_URI = 'https://github.com/MiniProfiler/rack-mini-profiler'
6
7
  end
7
8
  end