rack-mini-profiler 2.3.4 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6021effb717c193c4c70b3ac7a3550fd99c9abfd19432ac0ab9db63f62b11321
4
- data.tar.gz: 8bce19855d2f6d908339e3108201282c519b4d8ad99b8e7e90dad9d3f718c929
3
+ metadata.gz: cde7281c7a63d3d3ac5bc7605ba2cf81dedf6b598ff17ed568610a54a50b517f
4
+ data.tar.gz: 7af763d5136493c71cc4321fe54db79372e5b9aaac4c10d31bc2f27d22f324b7
5
5
  SHA512:
6
- metadata.gz: 0c4354c194a6fa6018c57162e24e1cee85de7f296e45cd6182360b39abeb803b29a14674e2dfe93b3ba5d9bcb192814de8d091ad6b7db20d71af95e2cddcf4eb
7
- data.tar.gz: e3c7f4da5fd5c79bfdb4c3582b50a38346d38784bfd92c6e7ce3fa41713104c74b6ad5622084d42624011ef850533c403002664f44ad7f6e1b1ac16a245c633b
6
+ metadata.gz: abb415f975552a1256753e15ba9d6623aeb55fe4b058c775701f2b7d3a955f62d2a906a93929f1a22545f8558750d48c78f3b7e185f774c224d12e6a256a69a6
7
+ data.tar.gz: f5ff03170537e5dc17a826909d9b77609bc8d6f2390f42b29be9ff1af538332eaa33c269f7433d87142ba7e05315741b3ea5b4c69fcc76980a56c225338d843c
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 3.0.0 - 2022-02-24
4
+
5
+ - PERF: Improve snapshots page performance (#518) (introduces breaking changes to the API of `AbstractStore`, `MemoryStore` and `RedisStore`, and removes the `snapshots_limit` config option.)
6
+
3
7
  ## 2.3.4 - 2022-02-23
4
8
 
5
9
  - [FEATURE] Add cookie path support for subfolder sites
data/README.md CHANGED
@@ -207,7 +207,7 @@ After enabling snapshots sampling, you can see the snapshots that have been coll
207
207
 
208
208
  Access to the snapshots page is restricted to only those who can see the speed badge on their own requests, see the section below this one about access control.
209
209
 
210
- Mini Profiler will keep a maximum of 1000 snapshots by default, and you can change that via the `snapshots_limit` config. When snapshots reach the configured limit, Mini Profiler will save a new snapshot only if it's worse than at least one of the existing snapshots and delete the best one (i.e. the snapshot whose request took the least time compared to other snapshots).
210
+ Mini Profiler will keep a maximum of 50 snapshot groups and a maximum of 15 snapshots per group making the default maximum number of snapshots in the system 750. The default group and per group limits can be changed via the `max_snapshot_groups` and `max_snapshots_per_group` configuration options, see the configurations table below.
211
211
 
212
212
  #### Snapshots Transporter
213
213
 
@@ -430,7 +430,8 @@ show_total_sql_count|`false`|Displays the total number of SQL executions.
430
430
  enable_advanced_debugging_tools|`false`|Enables sensitive debugging tools that can be used via the UI. In production we recommend keeping this disabled as memory and environment debugging tools can expose contents of memory that may contain passwords. Defaults to `true` in development.
431
431
  assets_url|`nil`|See the "Register MiniProfiler's assets in the Rails assets pipeline" section above.
432
432
  snapshot_every_n_requests|`-1`|Determines how frequently snapshots are taken. See the "Snapshots Sampling" above for more details.
433
- snapshots_limit|`1000`|Determines how many snapshots Mini Profiler is allowed to keep.
433
+ max_snapshot_groups|`50`|Determines how many snapshot groups Mini Profiler is allowed to keep.
434
+ max_snapshots_per_group|`15`|Determines how many snapshots per group Mini Profiler is allowed to keep.
434
435
  snapshot_hidden_custom_fields|`[]`|Each snapshot custom field will have a dedicated column in the UI by default. Use this config to exclude certain custom fields from having their own columns.
435
436
  snapshots_transport_destination_url|`nil`|Set this config to a valid URL to enable snapshots transporter which will `POST` snapshots to the given URL. The transporter requires `snapshots_transport_auth_key` config to be set as well.
436
437
  snapshots_transport_auth_key|`nil`|`POST` requests made by the snapshots transporter to the destination URL will have a `Mini-Profiler-Transport-Auth` header with the value of this config. Make sure you use a secure and random key for this config.
@@ -40,7 +40,8 @@ module Rack
40
40
  @skip_sql_param_names = /password/ # skips parameters with the name password by default
41
41
  @enable_advanced_debugging_tools = false
42
42
  @snapshot_every_n_requests = -1
43
- @snapshots_limit = 1000
43
+ @max_snapshot_groups = 50
44
+ @max_snapshots_per_group = 15
44
45
 
45
46
  # ui parameters
46
47
  @autorized = true
@@ -81,10 +82,10 @@ module Rack
81
82
  :start_hidden, :toggle_shortcut, :html_container
82
83
 
83
84
  # snapshot related config
84
- attr_accessor :snapshot_every_n_requests, :snapshots_limit,
85
+ attr_accessor :snapshot_every_n_requests, :max_snapshots_per_group,
85
86
  :snapshot_hidden_custom_fields, :snapshots_transport_destination_url,
86
87
  :snapshots_transport_auth_key, :snapshots_redact_sql_queries,
87
- :snapshots_transport_gzip_requests
88
+ :snapshots_transport_gzip_requests, :max_snapshot_groups
88
89
 
89
90
  # Deprecated options
90
91
  attr_accessor :use_existing_jquery
@@ -130,10 +130,10 @@ module Rack
130
130
  def serve_results(env)
131
131
  request = Rack::Request.new(env)
132
132
  id = request.params['id']
133
- is_snapshot = request.params['snapshot']
134
- is_snapshot = [true, "true"].include?(is_snapshot)
133
+ group_name = request.params['group']
134
+ is_snapshot = group_name && group_name.size > 0
135
135
  if is_snapshot
136
- page_struct = @storage.load_snapshot(id)
136
+ page_struct = @storage.load_snapshot(id, group_name)
137
137
  else
138
138
  page_struct = @storage.load(id)
139
139
  end
@@ -802,16 +802,16 @@ This is the help menu of the <a href='#{Rack::MiniProfiler::SOURCE_CODE_URI}'>ra
802
802
  headers = { 'Content-Type' => 'text/html' }
803
803
  qp = Rack::Utils.parse_nested_query(env['QUERY_STRING'])
804
804
  if group_name = qp["group_name"]
805
- list = @storage.find_snapshots_group(group_name)
805
+ list = @storage.snapshots_group(group_name)
806
806
  list.each do |snapshot|
807
- snapshot[:url] = url_for_snapshot(snapshot[:id])
807
+ snapshot[:url] = url_for_snapshot(snapshot[:id], group_name)
808
808
  end
809
809
  data = {
810
810
  group_name: group_name,
811
811
  list: list
812
812
  }
813
813
  else
814
- list = @storage.snapshot_groups_overview
814
+ list = @storage.snapshots_overview
815
815
  list.each do |group|
816
816
  group[:url] = url_for_snapshots_group(group[:name])
817
817
  end
@@ -864,7 +864,7 @@ This is the help menu of the <a href='#{Rack::MiniProfiler::SOURCE_CODE_URI}'>ra
864
864
  if defined?(Rails) && defined?(ActionController::RoutingError)
865
865
  hash = Rails.application.routes.recognize_path(path, method: method)
866
866
  if hash && hash[:controller] && hash[:action]
867
- "#{method} #{hash[:controller]}##{hash[:action]}"
867
+ "#{hash[:controller]}##{hash[:action]}"
868
868
  end
869
869
  end
870
870
  rescue ActionController::RoutingError
@@ -876,8 +876,8 @@ This is the help menu of the <a href='#{Rack::MiniProfiler::SOURCE_CODE_URI}'>ra
876
876
  "/#{@config.base_url_path.gsub('/', '')}/snapshots?#{qs}"
877
877
  end
878
878
 
879
- def url_for_snapshot(id)
880
- qs = Rack::Utils.build_query({ id: id, snapshot: true })
879
+ def url_for_snapshot(id, group_name)
880
+ qs = Rack::Utils.build_query({ id: id, group: group_name })
881
881
  "/#{@config.base_url_path.gsub('/', '')}/results?#{qs}"
882
882
  end
883
883
 
@@ -902,8 +902,12 @@ This is the help menu of the <a href='#{Rack::MiniProfiler::SOURCE_CODE_URI}'>ra
902
902
  if Rack::MiniProfiler.snapshots_transporter?
903
903
  Rack::MiniProfiler::SnapshotsTransporter.transport(page_struct)
904
904
  else
905
+ group_name = rails_route_from_path(page_struct[:request_path], page_struct[:request_method])
906
+ group_name ||= page_struct[:request_path]
907
+ group_name = "#{page_struct[:request_method]} #{group_name}"
905
908
  @storage.push_snapshot(
906
909
  page_struct,
910
+ group_name,
907
911
  @config
908
912
  )
909
913
  end
@@ -45,80 +45,53 @@ module Rack
45
45
  raise NotImplementedError.new("should_take_snapshot? is not implemented")
46
46
  end
47
47
 
48
- def push_snapshot(page_struct, config)
48
+ def push_snapshot(page_struct, group_name, config)
49
49
  raise NotImplementedError.new("push_snapshot is not implemented")
50
50
  end
51
51
 
52
- def fetch_snapshots(batch_size: 200, &blk)
53
- raise NotImplementedError.new("fetch_snapshots is not implemented")
52
+ # returns a hash where the keys are group names and the values
53
+ # are hashes that contain 3 keys:
54
+ # 1. `:worst_score` => the duration of the worst/slowest snapshot in the group (float)
55
+ # 2. `:best_score` => the duration of the best/fastest snapshot in the group (float)
56
+ # 3. `:snapshots_count` => the number of snapshots in the group (integer)
57
+ def fetch_snapshots_overview
58
+ raise NotImplementedError.new("fetch_snapshots_overview is not implemented")
54
59
  end
55
60
 
56
- def snapshot_groups_overview
57
- groups = {}
58
- fetch_snapshots do |batch|
59
- batch.each do |snapshot|
60
- group_name = default_snapshot_grouping(snapshot)
61
- hash = groups[group_name] ||= {}
62
- hash[:snapshots_count] ||= 0
63
- hash[:snapshots_count] += 1
64
- if !hash[:worst_score] || hash[:worst_score] < snapshot.duration_ms
65
- groups[group_name][:worst_score] = snapshot.duration_ms
66
- end
67
- if !hash[:best_score] || hash[:best_score] > snapshot.duration_ms
68
- groups[group_name][:best_score] = snapshot.duration_ms
69
- end
70
- end
71
- end
72
- groups = groups.to_a
61
+ # @param group_name [String]
62
+ # @return [Array<Rack::MiniProfiler::TimerStruct::Page>] list of snapshots of the group. Blank array if the group doesn't exist.
63
+ def fetch_snapshots_group(group_name)
64
+ raise NotImplementedError.new("fetch_snapshots_group is not implemented")
65
+ end
66
+
67
+ def load_snapshot(id, group_name)
68
+ raise NotImplementedError.new("load_snapshot is not implemented")
69
+ end
70
+
71
+ def snapshots_overview
72
+ groups = fetch_snapshots_overview.to_a
73
73
  groups.sort_by! { |name, hash| hash[:worst_score] }
74
74
  groups.reverse!
75
75
  groups.map! { |name, hash| hash.merge(name: name) }
76
76
  groups
77
77
  end
78
78
 
79
- def find_snapshots_group(group_name)
79
+ def snapshots_group(group_name)
80
+ snapshots = fetch_snapshots_group(group_name)
80
81
  data = []
81
- fetch_snapshots do |batch|
82
- batch.each do |snapshot|
83
- snapshot_group_name = default_snapshot_grouping(snapshot)
84
- if group_name == snapshot_group_name
85
- data << {
86
- id: snapshot[:id],
87
- duration: snapshot.duration_ms,
88
- sql_count: snapshot[:sql_count],
89
- timestamp: snapshot[:started_at],
90
- custom_fields: snapshot[:custom_fields]
91
- }
92
- end
93
- end
82
+ snapshots.each do |snapshot|
83
+ data << {
84
+ id: snapshot[:id],
85
+ duration: snapshot.duration_ms,
86
+ sql_count: snapshot[:sql_count],
87
+ timestamp: snapshot[:started_at],
88
+ custom_fields: snapshot[:custom_fields]
89
+ }
94
90
  end
95
91
  data.sort_by! { |s| s[:duration] }
96
92
  data.reverse!
97
93
  data
98
94
  end
99
-
100
- def load_snapshot(id)
101
- raise NotImplementedError.new("load_snapshot is not implemented")
102
- end
103
-
104
- private
105
-
106
- def default_snapshot_grouping(snapshot)
107
- group_name = rails_route_from_path(snapshot[:request_path], snapshot[:request_method])
108
- group_name ||= snapshot[:request_path]
109
- "#{snapshot[:request_method]} #{group_name}"
110
- end
111
-
112
- def rails_route_from_path(path, method)
113
- if defined?(Rails) && defined?(ActionController::RoutingError)
114
- hash = Rails.application.routes.recognize_path(path, method: method)
115
- if hash && hash[:controller] && hash[:action]
116
- "#{hash[:controller]}##{hash[:action]}"
117
- end
118
- end
119
- rescue ActionController::RoutingError
120
- nil
121
- end
122
95
  end
123
96
  end
124
97
  end
@@ -53,6 +53,7 @@ module Rack
53
53
 
54
54
  @token1, @token2, @cycle_at = nil
55
55
  @snapshots_cycle = 0
56
+ @snapshot_groups = {}
56
57
  @snapshots = []
57
58
 
58
59
  initialize_locks
@@ -152,28 +153,69 @@ module Rack
152
153
  end
153
154
  end
154
155
 
155
- def push_snapshot(page_struct, config)
156
+ def push_snapshot(page_struct, group_name, config)
156
157
  @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)
158
+ group = @snapshot_groups[group_name]
159
+ if !group
160
+ @snapshot_groups[group_name] = {
161
+ worst_score: page_struct.duration_ms,
162
+ best_score: page_struct.duration_ms,
163
+ snapshots: [page_struct]
164
+ }
165
+ if @snapshot_groups.size > config.max_snapshot_groups
166
+ group_keys = @snapshot_groups.keys
167
+ group_keys.sort_by! do |key|
168
+ @snapshot_groups[key][:worst_score]
169
+ end
170
+ group_keys.reverse!
171
+ group_keys.pop(group_keys.size - config.max_snapshot_groups)
172
+ @snapshot_groups = @snapshot_groups.slice(*group_keys)
173
+ end
174
+ else
175
+ snapshots = group[:snapshots]
176
+ snapshots << page_struct
177
+ snapshots.sort_by!(&:duration_ms)
178
+ snapshots.reverse!
179
+ if snapshots.size > config.max_snapshots_per_group
180
+ snapshots.pop(snapshots.size - config.max_snapshots_per_group)
181
+ end
182
+ group[:worst_score] = snapshots[0].duration_ms
183
+ group[:best_score] = snapshots[-1].duration_ms
162
184
  end
163
185
  end
164
186
  end
165
187
 
166
- def fetch_snapshots(batch_size: 200, &blk)
188
+ def fetch_snapshots_overview
167
189
  @snapshots_lock.synchronize do
168
- @snapshots.each_slice(batch_size) do |batch|
169
- blk.call(batch)
190
+ groups = {}
191
+ @snapshot_groups.each do |name, group|
192
+ groups[name] = {
193
+ worst_score: group[:worst_score],
194
+ best_score: group[:best_score],
195
+ snapshots_count: group[:snapshots].size
196
+ }
170
197
  end
198
+ groups
171
199
  end
172
200
  end
173
201
 
174
- def load_snapshot(id)
202
+ def fetch_snapshots_group(group_name)
175
203
  @snapshots_lock.synchronize do
176
- @snapshots.find { |s| s[:id] == id }
204
+ group = @snapshot_groups[group_name]
205
+ if group
206
+ group[:snapshots].dup
207
+ else
208
+ []
209
+ end
210
+ end
211
+ end
212
+
213
+ def load_snapshot(id, group_name)
214
+ @snapshots_lock.synchronize do
215
+ group = @snapshot_groups[group_name]
216
+ if group
217
+ group[:snapshots].find { |s| s[:id] == id }
218
+ end
177
219
  end
178
220
  end
179
221
 
@@ -182,7 +224,7 @@ module Rack
182
224
  # used in tests only
183
225
  def wipe_snapshots_data
184
226
  @snapshots_cycle = 0
185
- @snapshots = []
227
+ @snapshot_groups = {}
186
228
  end
187
229
  end
188
230
  end
@@ -133,85 +133,127 @@ unviewed_ids: #{get_unviewed_ids(user)}
133
133
  )
134
134
  end
135
135
 
136
- def push_snapshot(page_struct, config)
137
- zset_key = snapshot_zset_key()
138
- hash_key = snapshot_hash_key()
136
+ def push_snapshot(page_struct, group_name, config)
137
+ group_zset_key = group_snapshot_zset_key(group_name)
138
+ group_hash_key = group_snapshot_hash_key(group_name)
139
+ overview_zset_key = snapshot_overview_zset_key
139
140
 
140
141
  id = page_struct[:id]
141
- score = page_struct.duration_ms
142
- limit = config.snapshots_limit
142
+ score = page_struct.duration_ms.to_s
143
+
144
+ per_group_limit = config.max_snapshots_per_group.to_s
145
+ groups_limit = config.max_snapshot_groups.to_s
143
146
  bytes = Marshal.dump(page_struct)
144
147
 
145
148
  lua = <<~LUA
146
- local zset_key = KEYS[1]
147
- local hash_key = KEYS[2]
149
+ local group_zset_key = KEYS[1]
150
+ local group_hash_key = KEYS[2]
151
+ local overview_zset_key = KEYS[3]
152
+
148
153
  local id = ARGV[1]
149
154
  local score = tonumber(ARGV[2])
150
- local bytes = ARGV[3]
151
- local limit = tonumber(ARGV[4])
152
- redis.call("ZADD", zset_key, score, id)
153
- redis.call("HSET", hash_key, id, bytes)
154
- if redis.call("ZCARD", zset_key) > limit then
155
- local lowest_snapshot_id = redis.call("ZRANGE", zset_key, 0, 0)[1]
156
- redis.call("ZREM", zset_key, lowest_snapshot_id)
157
- redis.call("HDEL", hash_key, lowest_snapshot_id)
155
+ local group_name = ARGV[3]
156
+ local per_group_limit = tonumber(ARGV[4])
157
+ local groups_limit = tonumber(ARGV[5])
158
+ local prefix = ARGV[6]
159
+ local bytes = ARGV[7]
160
+
161
+ local current_group_score = redis.call("ZSCORE", overview_zset_key, group_name)
162
+ if current_group_score == false or score > tonumber(current_group_score) then
163
+ redis.call("ZADD", overview_zset_key, score, group_name)
164
+ end
165
+
166
+ local do_save = true
167
+ local overview_size = redis.call("ZCARD", overview_zset_key)
168
+ while (overview_size > groups_limit) do
169
+ local lowest_group = redis.call("ZRANGE", overview_zset_key, 0, 0)[1]
170
+ redis.call("ZREM", overview_zset_key, lowest_group)
171
+ if lowest_group == group_name then
172
+ do_save = false
173
+ else
174
+ local lowest_group_zset_key = prefix .. "-mp-group-snapshot-zset-key-" .. lowest_group
175
+ local lowest_group_hash_key = prefix .. "-mp-group-snapshot-hash-key-" .. lowest_group
176
+ redis.call("DEL", lowest_group_zset_key, lowest_group_hash_key)
177
+ end
178
+ overview_size = overview_size - 1
179
+ end
180
+
181
+ if do_save then
182
+ redis.call("ZADD", group_zset_key, score, id)
183
+ local group_size = redis.call("ZCARD", group_zset_key)
184
+ while (group_size > per_group_limit) do
185
+ local lowest_snapshot_id = redis.call("ZRANGE", group_zset_key, 0, 0)[1]
186
+ redis.call("ZREM", group_zset_key, lowest_snapshot_id)
187
+ if lowest_snapshot_id == id then
188
+ do_save = false
189
+ else
190
+ redis.call("HDEL", group_hash_key, lowest_snapshot_id)
191
+ end
192
+ group_size = group_size - 1
193
+ end
194
+ if do_save then
195
+ redis.call("HSET", group_hash_key, id, bytes)
196
+ end
158
197
  end
159
198
  LUA
160
199
  redis.eval(
161
200
  lua,
162
- keys: [zset_key, hash_key],
163
- argv: [id, score, bytes, limit]
201
+ keys: [group_zset_key, group_hash_key, overview_zset_key],
202
+ argv: [id, score, group_name, per_group_limit, groups_limit, @prefix, bytes]
164
203
  )
165
204
  end
166
205
 
167
- def fetch_snapshots(batch_size: 200, &blk)
168
- zset_key = snapshot_zset_key()
169
- hash_key = snapshot_hash_key()
170
- iteration = 0
171
- corrupt_snapshots = []
172
- while true
173
- ids = redis.zrange(
174
- zset_key,
175
- batch_size * iteration,
176
- batch_size * iteration + batch_size - 1
177
- )
178
- break if ids.size == 0
179
- batch = redis.mapped_hmget(hash_key, *ids).to_a
180
- batch.map! do |id, bytes|
181
- begin
182
- # rubocop:disable Security/MarshalLoad
183
- Marshal.load(bytes)
184
- # rubocop:enable Security/MarshalLoad
185
- rescue
186
- corrupt_snapshots << id
187
- nil
188
- end
206
+ def fetch_snapshots_overview
207
+ overview_zset_key = snapshot_overview_zset_key
208
+ groups = redis
209
+ .zrange(overview_zset_key, 0, -1, withscores: true)
210
+ .map { |(name, worst_score)| [name, { worst_score: worst_score }] }
211
+
212
+ prefixed_group_names = groups.map { |(group_name, _)| group_snapshot_zset_key(group_name) }
213
+ metadata = redis.eval(<<~LUA, keys: prefixed_group_names)
214
+ local metadata = {}
215
+ for i, k in ipairs(KEYS) do
216
+ local best = redis.call("ZRANGE", k, 0, 0, "WITHSCORES")[2]
217
+ local count = redis.call("ZCARD", k)
218
+ metadata[i] = {best, count}
189
219
  end
190
- batch.compact!
191
- blk.call(batch) if batch.size != 0
192
- break if ids.size < batch_size
193
- iteration += 1
220
+ return metadata
221
+ LUA
222
+ groups.each.with_index do |(_, hash), index|
223
+ best, count = metadata[index]
224
+ hash[:best_score] = best.to_f
225
+ hash[:snapshots_count] = count.to_i
226
+ end
227
+ groups.to_h
228
+ end
229
+
230
+ def fetch_snapshots_group(group_name)
231
+ group_hash_key = group_snapshot_hash_key(group_name)
232
+ snapshots = []
233
+ corrupt_snapshots = []
234
+ redis.hgetall(group_hash_key).each do |id, bytes|
235
+ # rubocop:disable Security/MarshalLoad
236
+ snapshots << Marshal.load(bytes)
237
+ # rubocop:enable Security/MarshalLoad
238
+ rescue
239
+ corrupt_snapshots << id
194
240
  end
195
241
  if corrupt_snapshots.size > 0
196
- redis.pipelined do |pipeline|
197
- pipeline.zrem(zset_key, corrupt_snapshots)
198
- pipeline.hdel(hash_key, corrupt_snapshots)
199
- end
242
+ cleanup_corrupt_snapshots(corrupt_snapshots, group_name)
200
243
  end
244
+ snapshots
201
245
  end
202
246
 
203
- def load_snapshot(id)
204
- hash_key = snapshot_hash_key()
205
- bytes = redis.hget(hash_key, id)
247
+ def load_snapshot(id, group_name)
248
+ group_hash_key = group_snapshot_hash_key(group_name)
249
+ bytes = redis.hget(group_hash_key, id)
250
+ return if !bytes
206
251
  begin
207
252
  # rubocop:disable Security/MarshalLoad
208
253
  Marshal.load(bytes)
209
254
  # rubocop:enable Security/MarshalLoad
210
255
  rescue
211
- redis.pipelined do |pipeline|
212
- pipeline.zrem(snapshot_zset_key(), id)
213
- pipeline.hdel(hash_key, id)
214
- end
256
+ cleanup_corrupt_snapshots([id], group_name)
215
257
  nil
216
258
  end
217
259
  end
@@ -237,12 +279,20 @@ unviewed_ids: #{get_unviewed_ids(user)}
237
279
  @snapshot_counter_key ||= "#{@prefix}-mini-profiler-snapshots-counter"
238
280
  end
239
281
 
240
- def snapshot_zset_key
241
- @snapshot_zset_key ||= "#{@prefix}-mini-profiler-snapshots-zset"
282
+ def group_snapshot_zset_key(group_name)
283
+ # if you change this key, remember to change it in the LUA script in
284
+ # the push_snapshot method as well
285
+ "#{@prefix}-mp-group-snapshot-zset-key-#{group_name}"
242
286
  end
243
287
 
244
- def snapshot_hash_key
245
- @snapshot_hash_key ||= "#{@prefix}-mini-profiler-snapshots-hash"
288
+ def group_snapshot_hash_key(group_name)
289
+ # if you change this key, remember to change it in the LUA script in
290
+ # the push_snapshot method as well
291
+ "#{@prefix}-mp-group-snapshot-hash-key-#{group_name}"
292
+ end
293
+
294
+ def snapshot_overview_zset_key
295
+ "#{@prefix}-mp-overviewgroup-snapshot-zset-key"
246
296
  end
247
297
 
248
298
  def cached_redis_eval(script, script_sha, reraise: true, argv: [], keys: [])
@@ -257,12 +307,44 @@ unviewed_ids: #{get_unviewed_ids(user)}
257
307
  end
258
308
  end
259
309
 
310
+ def cleanup_corrupt_snapshots(corrupt_snapshots_ids, group_name)
311
+ group_hash_key = group_snapshot_hash_key(group_name)
312
+ group_zset_key = group_snapshot_zset_key(group_name)
313
+ overview_zset_key = snapshot_overview_zset_key
314
+ lua = <<~LUA
315
+ local group_hash_key = KEYS[1]
316
+ local group_zset_key = KEYS[2]
317
+ local overview_zset_key = KEYS[3]
318
+ local group_name = ARGV[1]
319
+ for i, k in ipairs(ARGV) do
320
+ if k ~= group_name then
321
+ redis.call("HDEL", group_hash_key, k)
322
+ redis.call("ZREM", group_zset_key, k)
323
+ end
324
+ end
325
+ if redis.call("ZCARD", group_zset_key) == 0 then
326
+ redis.call("ZREM", overview_zset_key, group_name)
327
+ redis.call("DEL", group_hash_key, group_zset_key)
328
+ else
329
+ local worst_score = tonumber(redis.call("ZRANGE", group_zset_key, -1, -1, "WITHSCORES")[2])
330
+ redis.call("ZADD", overview_zset_key, worst_score, group_name)
331
+ end
332
+ LUA
333
+ redis.eval(
334
+ lua,
335
+ keys: [group_hash_key, group_zset_key, overview_zset_key],
336
+ argv: [group_name, *corrupt_snapshots_ids]
337
+ )
338
+ end
339
+
260
340
  # only used in tests
261
341
  def wipe_snapshots_data
342
+ keys = redis.keys(group_snapshot_hash_key('*'))
343
+ keys += redis.keys(group_snapshot_zset_key('*'))
262
344
  redis.del(
263
- snapshot_counter_key(),
264
- snapshot_zset_key(),
265
- snapshot_hash_key(),
345
+ keys,
346
+ snapshot_overview_zset_key,
347
+ snapshot_counter_key
266
348
  )
267
349
  end
268
350
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Rack
4
4
  class MiniProfiler
5
- VERSION = '2.3.4'
5
+ VERSION = '3.0.0'
6
6
  SOURCE_CODE_URI = 'https://github.com/MiniProfiler/rack-mini-profiler'
7
7
  end
8
8
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-mini-profiler
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.4
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Saffron
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2022-02-22 00:00:00.000000000 Z
13
+ date: 2022-02-24 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rack