rack-mini-profiler 2.3.3 → 3.1.0

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.
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'securerandom'
4
+
3
5
  module Rack
4
6
  class MiniProfiler
5
7
  class MemoryStore < AbstractStore
@@ -53,6 +55,7 @@ module Rack
53
55
 
54
56
  @token1, @token2, @cycle_at = nil
55
57
  @snapshots_cycle = 0
58
+ @snapshot_groups = {}
56
59
  @snapshots = []
57
60
 
58
61
  initialize_locks
@@ -152,28 +155,69 @@ module Rack
152
155
  end
153
156
  end
154
157
 
155
- def push_snapshot(page_struct, config)
158
+ def push_snapshot(page_struct, group_name, config)
159
+ @snapshots_lock.synchronize do
160
+ group = @snapshot_groups[group_name]
161
+ if !group
162
+ @snapshot_groups[group_name] = {
163
+ worst_score: page_struct.duration_ms,
164
+ best_score: page_struct.duration_ms,
165
+ snapshots: [page_struct]
166
+ }
167
+ if @snapshot_groups.size > config.max_snapshot_groups
168
+ group_keys = @snapshot_groups.keys
169
+ group_keys.sort_by! do |key|
170
+ @snapshot_groups[key][:worst_score]
171
+ end
172
+ group_keys.reverse!
173
+ group_keys.pop(group_keys.size - config.max_snapshot_groups)
174
+ @snapshot_groups = @snapshot_groups.slice(*group_keys)
175
+ end
176
+ else
177
+ snapshots = group[:snapshots]
178
+ snapshots << page_struct
179
+ snapshots.sort_by!(&:duration_ms)
180
+ snapshots.reverse!
181
+ if snapshots.size > config.max_snapshots_per_group
182
+ snapshots.pop(snapshots.size - config.max_snapshots_per_group)
183
+ end
184
+ group[:worst_score] = snapshots[0].duration_ms
185
+ group[:best_score] = snapshots[-1].duration_ms
186
+ end
187
+ end
188
+ end
189
+
190
+ def fetch_snapshots_overview
156
191
  @snapshots_lock.synchronize do
157
- @snapshots << page_struct
158
- @snapshots.sort_by! { |s| s.duration_ms }
159
- @snapshots.reverse!
160
- if @snapshots.size > config.snapshots_limit
161
- @snapshots.slice!(-1)
192
+ groups = {}
193
+ @snapshot_groups.each do |name, group|
194
+ groups[name] = {
195
+ worst_score: group[:worst_score],
196
+ best_score: group[:best_score],
197
+ snapshots_count: group[:snapshots].size
198
+ }
162
199
  end
200
+ groups
163
201
  end
164
202
  end
165
203
 
166
- def fetch_snapshots(batch_size: 200, &blk)
204
+ def fetch_snapshots_group(group_name)
167
205
  @snapshots_lock.synchronize do
168
- @snapshots.each_slice(batch_size) do |batch|
169
- blk.call(batch)
206
+ group = @snapshot_groups[group_name]
207
+ if group
208
+ group[:snapshots].dup
209
+ else
210
+ []
170
211
  end
171
212
  end
172
213
  end
173
214
 
174
- def load_snapshot(id)
215
+ def load_snapshot(id, group_name)
175
216
  @snapshots_lock.synchronize do
176
- @snapshots.find { |s| s[:id] == id }
217
+ group = @snapshot_groups[group_name]
218
+ if group
219
+ group[:snapshots].find { |s| s[:id] == id }
220
+ end
177
221
  end
178
222
  end
179
223
 
@@ -182,7 +226,7 @@ module Rack
182
226
  # used in tests only
183
227
  def wipe_snapshots_data
184
228
  @snapshots_cycle = 0
185
- @snapshots = []
229
+ @snapshot_groups = {}
186
230
  end
187
231
  end
188
232
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'digest'
4
+ require 'securerandom'
4
5
 
5
6
  module Rack
6
7
  class MiniProfiler
@@ -25,7 +26,9 @@ module Rack
25
26
  key = prefixed_id(id)
26
27
  raw = redis.get key
27
28
  begin
28
- Marshal::load(raw) if raw
29
+ # rubocop:disable Security/MarshalLoad
30
+ Marshal.load(raw) if raw
31
+ # rubocop:enable Security/MarshalLoad
29
32
  rescue
30
33
  # bad format, junk old data
31
34
  redis.del key
@@ -131,81 +134,127 @@ unviewed_ids: #{get_unviewed_ids(user)}
131
134
  )
132
135
  end
133
136
 
134
- def push_snapshot(page_struct, config)
135
- zset_key = snapshot_zset_key()
136
- hash_key = snapshot_hash_key()
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
137
141
 
138
142
  id = page_struct[:id]
139
- score = page_struct.duration_ms
140
- limit = config.snapshots_limit
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
141
147
  bytes = Marshal.dump(page_struct)
142
148
 
143
149
  lua = <<~LUA
144
- local zset_key = KEYS[1]
145
- local hash_key = KEYS[2]
150
+ local group_zset_key = KEYS[1]
151
+ local group_hash_key = KEYS[2]
152
+ local overview_zset_key = KEYS[3]
153
+
146
154
  local id = ARGV[1]
147
155
  local score = tonumber(ARGV[2])
148
- local bytes = ARGV[3]
149
- local limit = tonumber(ARGV[4])
150
- redis.call("ZADD", zset_key, score, id)
151
- redis.call("HSET", hash_key, id, bytes)
152
- if redis.call("ZCARD", zset_key) > limit then
153
- local lowest_snapshot_id = redis.call("ZRANGE", zset_key, 0, 0)[1]
154
- redis.call("ZREM", zset_key, lowest_snapshot_id)
155
- redis.call("HDEL", hash_key, lowest_snapshot_id)
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
156
198
  end
157
199
  LUA
158
200
  redis.eval(
159
201
  lua,
160
- keys: [zset_key, hash_key],
161
- argv: [id, score, bytes, limit]
202
+ keys: [group_zset_key, group_hash_key, overview_zset_key],
203
+ argv: [id, score, group_name, per_group_limit, groups_limit, @prefix, bytes]
162
204
  )
163
205
  end
164
206
 
165
- def fetch_snapshots(batch_size: 200, &blk)
166
- zset_key = snapshot_zset_key()
167
- hash_key = snapshot_hash_key()
168
- iteration = 0
169
- corrupt_snapshots = []
170
- while true
171
- ids = redis.zrange(
172
- zset_key,
173
- batch_size * iteration,
174
- batch_size * iteration + batch_size - 1
175
- )
176
- break if ids.size == 0
177
- batch = redis.mapped_hmget(hash_key, *ids).to_a
178
- batch.map! do |id, bytes|
179
- begin
180
- Marshal.load(bytes)
181
- rescue
182
- corrupt_snapshots << id
183
- nil
184
- end
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}
185
220
  end
186
- batch.compact!
187
- blk.call(batch) if batch.size != 0
188
- break if ids.size < batch_size
189
- iteration += 1
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
190
241
  end
191
242
  if corrupt_snapshots.size > 0
192
- redis.pipelined do
193
- redis.zrem(zset_key, corrupt_snapshots)
194
- redis.hdel(hash_key, corrupt_snapshots)
195
- end
243
+ cleanup_corrupt_snapshots(corrupt_snapshots, group_name)
196
244
  end
245
+ snapshots
197
246
  end
198
247
 
199
- def load_snapshot(id)
200
- hash_key = snapshot_hash_key()
201
- bytes = redis.hget(hash_key, id)
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
202
252
  begin
253
+ # rubocop:disable Security/MarshalLoad
203
254
  Marshal.load(bytes)
255
+ # rubocop:enable Security/MarshalLoad
204
256
  rescue
205
- redis.pipelined do
206
- redis.zrem(snapshot_zset_key(), id)
207
- redis.hdel(hash_key, id)
208
- end
257
+ cleanup_corrupt_snapshots([id], group_name)
209
258
  nil
210
259
  end
211
260
  end
@@ -231,12 +280,20 @@ unviewed_ids: #{get_unviewed_ids(user)}
231
280
  @snapshot_counter_key ||= "#{@prefix}-mini-profiler-snapshots-counter"
232
281
  end
233
282
 
234
- def snapshot_zset_key
235
- @snapshot_zset_key ||= "#{@prefix}-mini-profiler-snapshots-zset"
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}"
236
287
  end
237
288
 
238
- def snapshot_hash_key
239
- @snapshot_hash_key ||= "#{@prefix}-mini-profiler-snapshots-hash"
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"
240
297
  end
241
298
 
242
299
  def cached_redis_eval(script, script_sha, reraise: true, argv: [], keys: [])
@@ -251,13 +308,45 @@ unviewed_ids: #{get_unviewed_ids(user)}
251
308
  end
252
309
  end
253
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
+
254
341
  # only used in tests
255
342
  def wipe_snapshots_data
256
- redis.pipelined do
257
- redis.del(snapshot_counter_key())
258
- redis.del(snapshot_zset_key())
259
- redis.del(snapshot_hash_key())
260
- end
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
+ )
261
350
  end
262
351
  end
263
352
  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
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'erb'
4
+
3
5
  module Rack
4
6
  class MiniProfiler
5
7
 
@@ -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,7 +2,7 @@
2
2
 
3
3
  module Rack
4
4
  class MiniProfiler
5
- VERSION = '2.3.3'
5
+ VERSION = '3.1.0'
6
6
  SOURCE_CODE_URI = 'https://github.com/MiniProfiler/rack-mini-profiler'
7
7
  end
8
8
  end