rack-mini-profiler 2.3.3 → 3.1.0

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