Class: Rack::MiniProfiler::RedisStore
- Inherits:
-
AbstractStore
- Object
- AbstractStore
- Rack::MiniProfiler::RedisStore
- Defined in:
- lib/mini_profiler/storage/redis_store.rb
Constant Summary collapse
- EXPIRES_IN_SECONDS =
60 * 60 * 24
- COUNTER_LUA =
<<~LUA if redis.call("INCR", KEYS[1]) % ARGV[1] == 0 then redis.call("DEL", KEYS[1]) return 1 else return 0 end LUA
- COUNTER_LUA_SHA =
Digest::SHA1.hexdigest(COUNTER_LUA)
Constants inherited from AbstractStore
Instance Attribute Summary collapse
-
#prefix ⇒ Object
readonly
Returns the value of attribute prefix.
Instance Method Summary collapse
- #allowed_tokens ⇒ Object
- #diagnostics(user) ⇒ Object
- #fetch_snapshots_group(group_name) ⇒ Object
- #fetch_snapshots_overview ⇒ Object
- #flush_tokens ⇒ Object
-
#get_unviewed_ids(user) ⇒ Object
Remove expired ids from the unviewed sorted set and return the remaining ids.
-
#initialize(args = nil) ⇒ RedisStore
constructor
A new instance of RedisStore.
- #load(id) ⇒ Object
- #load_snapshot(id, group_name) ⇒ Object
- #push_snapshot(page_struct, group_name, config) ⇒ Object
- #save(page_struct) ⇒ Object
- #set_all_unviewed(user, ids) ⇒ Object
- #set_unviewed(user, id) ⇒ Object
- #set_viewed(user, id) ⇒ Object
- #should_take_snapshot?(period) ⇒ Boolean
-
#simulate_expire ⇒ Object
Only used for testing.
Methods inherited from AbstractStore
#snapshots_group, #snapshots_overview
Constructor Details
#initialize(args = nil) ⇒ RedisStore
Returns a new instance of RedisStore.
14 15 16 17 18 19 |
# File 'lib/mini_profiler/storage/redis_store.rb', line 14 def initialize(args = nil) @args = args || {} @prefix = @args.delete(:prefix) || 'MPRedisStore' @redis_connection = @args.delete(:connection) @expires_in_seconds = @args.delete(:expires_in) || EXPIRES_IN_SECONDS end |
Instance Attribute Details
#prefix ⇒ Object (readonly)
Returns the value of attribute prefix.
10 11 12 |
# File 'lib/mini_profiler/storage/redis_store.rb', line 10 def prefix @prefix end |
Instance Method Details
#allowed_tokens ⇒ Object
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
# File 'lib/mini_profiler/storage/redis_store.rb', line 88 def allowed_tokens key1, key1_old, key2 = redis.mget("#{@prefix}-key1", "#{@prefix}-key1_old", "#{@prefix}-key2") if key1 && (key1.length == 32) return [key1, key2].compact end timeout = Rack::MiniProfiler::AbstractStore::MAX_TOKEN_AGE # TODO this could be moved to lua to correct a concurrency flaw # it is not critical cause worse case some requests will miss profiling info # no key so go ahead and set it key1 = SecureRandom.hex if key1_old && (key1_old.length == 32) key2 = key1_old redis.setex "#{@prefix}-key2", timeout, key2 else key2 = nil end redis.setex "#{@prefix}-key1", timeout, key1 redis.setex "#{@prefix}-key1_old", timeout * 2, key1 [key1, key2].compact end |
#diagnostics(user) ⇒ Object
71 72 73 74 75 76 77 |
# File 'lib/mini_profiler/storage/redis_store.rb', line 71 def diagnostics(user) client = (redis.respond_to? :_client) ? redis._client : redis.client "Redis prefix: #{@prefix} Redis location: #{client.host}:#{client.port} db: #{client.db} unviewed_ids: #{get_unviewed_ids(user)} " end |
#fetch_snapshots_group(group_name) ⇒ Object
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 |
# File 'lib/mini_profiler/storage/redis_store.rb', line 231 def fetch_snapshots_group(group_name) group_hash_key = group_snapshot_hash_key(group_name) snapshots = [] corrupt_snapshots = [] redis.hgetall(group_hash_key).each do |id, bytes| # rubocop:disable Security/MarshalLoad snapshots << Marshal.load(bytes) # rubocop:enable Security/MarshalLoad rescue corrupt_snapshots << id end if corrupt_snapshots.size > 0 cleanup_corrupt_snapshots(corrupt_snapshots, group_name) end snapshots end |
#fetch_snapshots_overview ⇒ Object
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 |
# File 'lib/mini_profiler/storage/redis_store.rb', line 207 def fetch_snapshots_overview overview_zset_key = snapshot_overview_zset_key groups = redis .zrange(overview_zset_key, 0, -1, withscores: true) .map { |(name, worst_score)| [name, { worst_score: worst_score }] } prefixed_group_names = groups.map { |(group_name, _)| group_snapshot_zset_key(group_name) } = redis.eval(<<~LUA, keys: prefixed_group_names) local metadata = {} for i, k in ipairs(KEYS) do local best = redis.call("ZRANGE", k, 0, 0, "WITHSCORES")[2] local count = redis.call("ZCARD", k) metadata[i] = {best, count} end return metadata LUA groups.each.with_index do |(_, hash), index| best, count = [index] hash[:best_score] = best.to_f hash[:snapshots_count] = count.to_i end groups.to_h end |
#flush_tokens ⇒ Object
79 80 81 |
# File 'lib/mini_profiler/storage/redis_store.rb', line 79 def flush_tokens redis.del("#{@prefix}-key1", "#{@prefix}-key1_old", "#{@prefix}-key2") end |
#get_unviewed_ids(user) ⇒ Object
Remove expired ids from the unviewed sorted set and return the remaining ids
65 66 67 68 69 |
# File 'lib/mini_profiler/storage/redis_store.rb', line 65 def get_unviewed_ids(user) key = user_key(user) redis.zremrangebyscore(key, '-inf', Process.clock_gettime(Process::CLOCK_MONOTONIC).to_i) redis.zrevrangebyscore(key, '+inf', '-inf') end |
#load(id) ⇒ Object
25 26 27 28 29 30 31 32 33 34 35 36 37 |
# File 'lib/mini_profiler/storage/redis_store.rb', line 25 def load(id) key = prefixed_id(id) raw = redis.get key begin # rubocop:disable Security/MarshalLoad Marshal.load(raw) if raw # rubocop:enable Security/MarshalLoad rescue # bad format, junk old data redis.del key nil end end |
#load_snapshot(id, group_name) ⇒ Object
248 249 250 251 252 253 254 255 256 257 258 259 260 |
# File 'lib/mini_profiler/storage/redis_store.rb', line 248 def load_snapshot(id, group_name) group_hash_key = group_snapshot_hash_key(group_name) bytes = redis.hget(group_hash_key, id) return if !bytes begin # rubocop:disable Security/MarshalLoad Marshal.load(bytes) # rubocop:enable Security/MarshalLoad rescue cleanup_corrupt_snapshots([id], group_name) nil end end |
#push_snapshot(page_struct, group_name, config) ⇒ Object
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 |
# File 'lib/mini_profiler/storage/redis_store.rb', line 137 def push_snapshot(page_struct, group_name, config) group_zset_key = group_snapshot_zset_key(group_name) group_hash_key = group_snapshot_hash_key(group_name) overview_zset_key = snapshot_overview_zset_key id = page_struct[:id] score = page_struct.duration_ms.to_s per_group_limit = config.max_snapshots_per_group.to_s groups_limit = config.max_snapshot_groups.to_s bytes = Marshal.dump(page_struct) lua = <<~LUA local group_zset_key = KEYS[1] local group_hash_key = KEYS[2] local overview_zset_key = KEYS[3] local id = ARGV[1] local score = tonumber(ARGV[2]) local group_name = ARGV[3] local per_group_limit = tonumber(ARGV[4]) local groups_limit = tonumber(ARGV[5]) local prefix = ARGV[6] local bytes = ARGV[7] local current_group_score = redis.call("ZSCORE", overview_zset_key, group_name) if current_group_score == false or score > tonumber(current_group_score) then redis.call("ZADD", overview_zset_key, score, group_name) end local do_save = true local overview_size = redis.call("ZCARD", overview_zset_key) while (overview_size > groups_limit) do local lowest_group = redis.call("ZRANGE", overview_zset_key, 0, 0)[1] redis.call("ZREM", overview_zset_key, lowest_group) if lowest_group == group_name then do_save = false else local lowest_group_zset_key = prefix .. "-mp-group-snapshot-zset-key-" .. lowest_group local lowest_group_hash_key = prefix .. "-mp-group-snapshot-hash-key-" .. lowest_group redis.call("DEL", lowest_group_zset_key, lowest_group_hash_key) end overview_size = overview_size - 1 end if do_save then redis.call("ZADD", group_zset_key, score, id) local group_size = redis.call("ZCARD", group_zset_key) while (group_size > per_group_limit) do local lowest_snapshot_id = redis.call("ZRANGE", group_zset_key, 0, 0)[1] redis.call("ZREM", group_zset_key, lowest_snapshot_id) if lowest_snapshot_id == id then do_save = false else redis.call("HDEL", group_hash_key, lowest_snapshot_id) end group_size = group_size - 1 end if do_save then redis.call("HSET", group_hash_key, id, bytes) end end LUA redis.eval( lua, keys: [group_zset_key, group_hash_key, overview_zset_key], argv: [id, score, group_name, per_group_limit, groups_limit, @prefix, bytes] ) end |
#save(page_struct) ⇒ Object
21 22 23 |
# File 'lib/mini_profiler/storage/redis_store.rb', line 21 def save(page_struct) redis.setex prefixed_id(page_struct[:id]), @expires_in_seconds, Marshal::dump(page_struct) end |
#set_all_unviewed(user, ids) ⇒ Object
48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/mini_profiler/storage/redis_store.rb', line 48 def set_all_unviewed(user, ids) key = user_key(user) redis.del(key) ids.each do |id| if redis.call([:exists, prefixed_id(id)]) == 1 expire_at = Process.clock_gettime(Process::CLOCK_MONOTONIC).to_i + redis.ttl(prefixed_id(id)) redis.zadd(key, expire_at, id) end end redis.expire(key, @expires_in_seconds) end |
#set_unviewed(user, id) ⇒ Object
39 40 41 42 43 44 45 46 |
# File 'lib/mini_profiler/storage/redis_store.rb', line 39 def set_unviewed(user, id) key = user_key(user) if redis.call([:exists, prefixed_id(id)]) == 1 expire_at = Process.clock_gettime(Process::CLOCK_MONOTONIC).to_i + redis.ttl(prefixed_id(id)) redis.zadd(key, expire_at, id) end redis.expire(key, @expires_in_seconds) end |
#set_viewed(user, id) ⇒ Object
60 61 62 |
# File 'lib/mini_profiler/storage/redis_store.rb', line 60 def set_viewed(user, id) redis.zrem(user_key(user), id) end |
#should_take_snapshot?(period) ⇒ Boolean
127 128 129 130 131 132 133 134 135 |
# File 'lib/mini_profiler/storage/redis_store.rb', line 127 def should_take_snapshot?(period) 1 == cached_redis_eval( COUNTER_LUA, COUNTER_LUA_SHA, reraise: false, keys: [snapshot_counter_key()], argv: [period] ) end |
#simulate_expire ⇒ Object
Only used for testing
84 85 86 |
# File 'lib/mini_profiler/storage/redis_store.rb', line 84 def simulate_expire redis.del("#{@prefix}-key1") end |