mini-sharp-hub 0.0.1

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.
Files changed (86) hide show
  1. checksums.yaml +7 -0
  2. data/mini-sharp-hub.gemspec +12 -0
  3. data/rack-mini-profiler-4.0.1/CHANGELOG.md +424 -0
  4. data/rack-mini-profiler-4.0.1/README.md +542 -0
  5. data/rack-mini-profiler-4.0.1/lib/enable_rails_patches.rb +5 -0
  6. data/rack-mini-profiler-4.0.1/lib/generators/rack_mini_profiler/USAGE +9 -0
  7. data/rack-mini-profiler-4.0.1/lib/generators/rack_mini_profiler/install_generator.rb +13 -0
  8. data/rack-mini-profiler-4.0.1/lib/generators/rack_mini_profiler/templates/rack_mini_profiler.rb +8 -0
  9. data/rack-mini-profiler-4.0.1/lib/generators/rack_profiler/USAGE +2 -0
  10. data/rack-mini-profiler-4.0.1/lib/generators/rack_profiler/install_generator.rb +16 -0
  11. data/rack-mini-profiler-4.0.1/lib/html/dot.1.1.2.min.js +2 -0
  12. data/rack-mini-profiler-4.0.1/lib/html/includes.css +474 -0
  13. data/rack-mini-profiler-4.0.1/lib/html/includes.js +1520 -0
  14. data/rack-mini-profiler-4.0.1/lib/html/includes.scss +698 -0
  15. data/rack-mini-profiler-4.0.1/lib/html/includes.tmpl +333 -0
  16. data/rack-mini-profiler-4.0.1/lib/html/pretty-print.js +810 -0
  17. data/rack-mini-profiler-4.0.1/lib/html/profile_handler.js +1 -0
  18. data/rack-mini-profiler-4.0.1/lib/html/rack-mini-profiler.css +3 -0
  19. data/rack-mini-profiler-4.0.1/lib/html/rack-mini-profiler.js +2 -0
  20. data/rack-mini-profiler-4.0.1/lib/html/share.html +10 -0
  21. data/rack-mini-profiler-4.0.1/lib/html/speedscope/LICENSE +21 -0
  22. data/rack-mini-profiler-4.0.1/lib/html/speedscope/README.md +3 -0
  23. data/rack-mini-profiler-4.0.1/lib/html/speedscope/demangle-cpp.1768f4cc.js +4 -0
  24. data/rack-mini-profiler-4.0.1/lib/html/speedscope/favicon-16x16.f74b3187.png +0 -0
  25. data/rack-mini-profiler-4.0.1/lib/html/speedscope/favicon-32x32.bc503437.png +0 -0
  26. data/rack-mini-profiler-4.0.1/lib/html/speedscope/file-format-schema.json +324 -0
  27. data/rack-mini-profiler-4.0.1/lib/html/speedscope/fonts/source-code-pro-regular.css +8 -0
  28. data/rack-mini-profiler-4.0.1/lib/html/speedscope/fonts/source-code-pro-v13-regular.woff +0 -0
  29. data/rack-mini-profiler-4.0.1/lib/html/speedscope/fonts/source-code-pro-v13-regular.woff2 +0 -0
  30. data/rack-mini-profiler-4.0.1/lib/html/speedscope/import.8ae8aa3d.js +123 -0
  31. data/rack-mini-profiler-4.0.1/lib/html/speedscope/index.html +2 -0
  32. data/rack-mini-profiler-4.0.1/lib/html/speedscope/release.txt +3 -0
  33. data/rack-mini-profiler-4.0.1/lib/html/speedscope/reset.8c46b7a1.css +2 -0
  34. data/rack-mini-profiler-4.0.1/lib/html/speedscope/source-map.438fa06b.js +24 -0
  35. data/rack-mini-profiler-4.0.1/lib/html/speedscope/speedscope.f27db165.js +193 -0
  36. data/rack-mini-profiler-4.0.1/lib/html/vendor.js +848 -0
  37. data/rack-mini-profiler-4.0.1/lib/mini_profiler/actions.rb +157 -0
  38. data/rack-mini-profiler-4.0.1/lib/mini_profiler/asset_version.rb +6 -0
  39. data/rack-mini-profiler-4.0.1/lib/mini_profiler/client_settings.rb +127 -0
  40. data/rack-mini-profiler-4.0.1/lib/mini_profiler/config.rb +146 -0
  41. data/rack-mini-profiler-4.0.1/lib/mini_profiler/context.rb +14 -0
  42. data/rack-mini-profiler-4.0.1/lib/mini_profiler/gc_profiler.rb +158 -0
  43. data/rack-mini-profiler-4.0.1/lib/mini_profiler/profiling_methods.rb +167 -0
  44. data/rack-mini-profiler-4.0.1/lib/mini_profiler/snapshots_transporter.rb +111 -0
  45. data/rack-mini-profiler-4.0.1/lib/mini_profiler/storage/abstract_store.rb +97 -0
  46. data/rack-mini-profiler-4.0.1/lib/mini_profiler/storage/file_store.rb +179 -0
  47. data/rack-mini-profiler-4.0.1/lib/mini_profiler/storage/memcache_store.rb +105 -0
  48. data/rack-mini-profiler-4.0.1/lib/mini_profiler/storage/memory_store.rb +233 -0
  49. data/rack-mini-profiler-4.0.1/lib/mini_profiler/storage/redis_store.rb +353 -0
  50. data/rack-mini-profiler-4.0.1/lib/mini_profiler/storage.rb +7 -0
  51. data/rack-mini-profiler-4.0.1/lib/mini_profiler/timer_struct/base.rb +39 -0
  52. data/rack-mini-profiler-4.0.1/lib/mini_profiler/timer_struct/client.rb +90 -0
  53. data/rack-mini-profiler-4.0.1/lib/mini_profiler/timer_struct/custom.rb +25 -0
  54. data/rack-mini-profiler-4.0.1/lib/mini_profiler/timer_struct/page.rb +146 -0
  55. data/rack-mini-profiler-4.0.1/lib/mini_profiler/timer_struct/request.rb +186 -0
  56. data/rack-mini-profiler-4.0.1/lib/mini_profiler/timer_struct/sql.rb +89 -0
  57. data/rack-mini-profiler-4.0.1/lib/mini_profiler/timer_struct.rb +8 -0
  58. data/rack-mini-profiler-4.0.1/lib/mini_profiler/version.rb +8 -0
  59. data/rack-mini-profiler-4.0.1/lib/mini_profiler/views.rb +198 -0
  60. data/rack-mini-profiler-4.0.1/lib/mini_profiler.rb +670 -0
  61. data/rack-mini-profiler-4.0.1/lib/mini_profiler_rails/railtie.rb +234 -0
  62. data/rack-mini-profiler-4.0.1/lib/mini_profiler_rails/railtie_methods.rb +61 -0
  63. data/rack-mini-profiler-4.0.1/lib/patches/db/activerecord.rb +44 -0
  64. data/rack-mini-profiler-4.0.1/lib/patches/db/mongo.rb +18 -0
  65. data/rack-mini-profiler-4.0.1/lib/patches/db/moped.rb +14 -0
  66. data/rack-mini-profiler-4.0.1/lib/patches/db/mysql2/alias_method.rb +30 -0
  67. data/rack-mini-profiler-4.0.1/lib/patches/db/mysql2/prepend.rb +34 -0
  68. data/rack-mini-profiler-4.0.1/lib/patches/db/mysql2.rb +7 -0
  69. data/rack-mini-profiler-4.0.1/lib/patches/db/neo4j.rb +16 -0
  70. data/rack-mini-profiler-4.0.1/lib/patches/db/nobrainer.rb +31 -0
  71. data/rack-mini-profiler-4.0.1/lib/patches/db/oracle_enhanced.rb +70 -0
  72. data/rack-mini-profiler-4.0.1/lib/patches/db/pg/alias_method.rb +121 -0
  73. data/rack-mini-profiler-4.0.1/lib/patches/db/pg/prepend.rb +115 -0
  74. data/rack-mini-profiler-4.0.1/lib/patches/db/pg.rb +7 -0
  75. data/rack-mini-profiler-4.0.1/lib/patches/db/plucky.rb +49 -0
  76. data/rack-mini-profiler-4.0.1/lib/patches/db/riak.rb +105 -0
  77. data/rack-mini-profiler-4.0.1/lib/patches/db/rsolr.rb +26 -0
  78. data/rack-mini-profiler-4.0.1/lib/patches/db/sequel.rb +12 -0
  79. data/rack-mini-profiler-4.0.1/lib/patches/net_patches.rb +27 -0
  80. data/rack-mini-profiler-4.0.1/lib/patches/sql_patches.rb +74 -0
  81. data/rack-mini-profiler-4.0.1/lib/prepend_mysql2_patch.rb +5 -0
  82. data/rack-mini-profiler-4.0.1/lib/prepend_net_http_patch.rb +5 -0
  83. data/rack-mini-profiler-4.0.1/lib/prepend_pg_patch.rb +5 -0
  84. data/rack-mini-profiler-4.0.1/lib/rack-mini-profiler.rb +13 -0
  85. data/rack-mini-profiler-4.0.1/rack-mini-profiler.gemspec +49 -0
  86. metadata +125 -0
@@ -0,0 +1,233 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Rack
6
+ class MiniProfiler
7
+ class MemoryStore < AbstractStore
8
+
9
+ # Sub-class thread so we have a named thread (useful for debugging in Thread.list).
10
+ class CacheCleanupThread < Thread
11
+
12
+ def initialize(interval, cycle, store)
13
+ @store = store
14
+ @interval = interval
15
+ @cycle = cycle
16
+ @cycle_count = 1
17
+ super
18
+ end
19
+
20
+ def should_cleanup?
21
+ @cycle_count * @interval >= @cycle
22
+ end
23
+
24
+ # We don't want to hit the filesystem every 10s to clean up the cache so we need to do a bit of
25
+ # accounting to avoid sleeping that entire time. We don't want to sleep for the entire period because
26
+ # it means the thread will stay live in hot deployment scenarios, keeping a potentially large memory
27
+ # graph from being garbage collected upon undeploy.
28
+ def sleepy_run
29
+ cleanup if should_cleanup?
30
+ sleep(@interval)
31
+ increment_cycle
32
+ end
33
+
34
+ def cleanup
35
+ @store.cleanup_cache
36
+ @cycle_count = 1
37
+ end
38
+
39
+ def cycle_count
40
+ @cycle_count
41
+ end
42
+
43
+ def increment_cycle
44
+ @cycle_count += 1
45
+ end
46
+ end
47
+
48
+ EXPIRES_IN_SECONDS = 60 * 60 * 24
49
+ CLEANUP_INTERVAL = 10
50
+ CLEANUP_CYCLE = 3600
51
+
52
+ def initialize(args = nil)
53
+ args ||= {}
54
+ @expires_in_seconds = args.fetch(:expires_in) { EXPIRES_IN_SECONDS }
55
+
56
+ @token1, @token2, @cycle_at = nil
57
+ @snapshots_cycle = 0
58
+ @snapshot_groups = {}
59
+ @snapshots = []
60
+
61
+ initialize_locks
62
+ initialize_cleanup_thread(args)
63
+ end
64
+
65
+ def initialize_locks
66
+ @token_lock = Mutex.new
67
+ @timer_struct_lock = Mutex.new
68
+ @user_view_lock = Mutex.new
69
+ @snapshots_cycle_lock = Mutex.new
70
+ @snapshots_lock = Mutex.new
71
+ @timer_struct_cache = {}
72
+ @user_view_cache = {}
73
+ end
74
+
75
+ #FIXME: use weak ref, trouble it may be broken in 1.9 so need to use the 'ref' gem
76
+ def initialize_cleanup_thread(args = {})
77
+ cleanup_interval = args.fetch(:cleanup_interval) { CLEANUP_INTERVAL }
78
+ cleanup_cycle = args.fetch(:cleanup_cycle) { CLEANUP_CYCLE }
79
+ t = CacheCleanupThread.new(cleanup_interval, cleanup_cycle, self) do
80
+ until Thread.current[:should_exit] do
81
+ Thread.current.sleepy_run
82
+ end
83
+ end
84
+ at_exit { t[:should_exit] = true }
85
+ end
86
+
87
+ def save(page_struct)
88
+ @timer_struct_lock.synchronize {
89
+ @timer_struct_cache[page_struct[:id]] = page_struct
90
+ }
91
+ end
92
+
93
+ def load(id)
94
+ @timer_struct_lock.synchronize {
95
+ @timer_struct_cache[id]
96
+ }
97
+ end
98
+
99
+ def set_unviewed(user, id)
100
+ @user_view_lock.synchronize {
101
+ @user_view_cache[user] ||= []
102
+ @user_view_cache[user] << id
103
+ }
104
+ end
105
+
106
+ def set_viewed(user, id)
107
+ @user_view_lock.synchronize {
108
+ @user_view_cache[user] ||= []
109
+ @user_view_cache[user].delete(id)
110
+ }
111
+ end
112
+
113
+ def set_all_unviewed(user, ids)
114
+ @user_view_lock.synchronize {
115
+ @user_view_cache[user] = ids
116
+ }
117
+ end
118
+
119
+ def get_unviewed_ids(user)
120
+ @user_view_lock.synchronize {
121
+ @user_view_cache[user]
122
+ }
123
+ end
124
+
125
+ def cleanup_cache
126
+ expire_older_than = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - @expires_in_seconds) * 1000).to_i
127
+ @timer_struct_lock.synchronize {
128
+ @timer_struct_cache.delete_if { |k, v| v[:started] < expire_older_than }
129
+ }
130
+ end
131
+
132
+ def allowed_tokens
133
+ @token_lock.synchronize do
134
+
135
+ unless @cycle_at && (@cycle_at > Process.clock_gettime(Process::CLOCK_MONOTONIC))
136
+ @token2 = @token1
137
+ @token1 = SecureRandom.hex
138
+ @cycle_at = Process.clock_gettime(Process::CLOCK_MONOTONIC) + Rack::MiniProfiler::AbstractStore::MAX_TOKEN_AGE
139
+ end
140
+
141
+ [@token1, @token2].compact
142
+
143
+ end
144
+ end
145
+
146
+ def should_take_snapshot?(period)
147
+ @snapshots_cycle_lock.synchronize do
148
+ @snapshots_cycle += 1
149
+ if @snapshots_cycle % period == 0
150
+ @snapshots_cycle = 0
151
+ true
152
+ else
153
+ false
154
+ end
155
+ end
156
+ end
157
+
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
191
+ @snapshots_lock.synchronize do
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
+ }
199
+ end
200
+ groups
201
+ end
202
+ end
203
+
204
+ def fetch_snapshots_group(group_name)
205
+ @snapshots_lock.synchronize do
206
+ group = @snapshot_groups[group_name]
207
+ if group
208
+ group[:snapshots].dup
209
+ else
210
+ []
211
+ end
212
+ end
213
+ end
214
+
215
+ def load_snapshot(id, group_name)
216
+ @snapshots_lock.synchronize do
217
+ group = @snapshot_groups[group_name]
218
+ if group
219
+ group[:snapshots].find { |s| s[:id] == id }
220
+ end
221
+ end
222
+ end
223
+
224
+ private
225
+
226
+ # used in tests only
227
+ def wipe_snapshots_data
228
+ @snapshots_cycle = 0
229
+ @snapshot_groups = {}
230
+ end
231
+ end
232
+ end
233
+ end
@@ -0,0 +1,353 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest'
4
+ require 'securerandom'
5
+
6
+ module Rack
7
+ class MiniProfiler
8
+ class RedisStore < AbstractStore
9
+
10
+ attr_reader :prefix
11
+
12
+ EXPIRES_IN_SECONDS = 60 * 60 * 24
13
+
14
+ def initialize(args = nil)
15
+ @args = args || {}
16
+ @prefix = @args.delete(:prefix) || 'MPRedisStore'
17
+ @redis_connection = @args.delete(:connection)
18
+ @expires_in_seconds = @args.delete(:expires_in) || EXPIRES_IN_SECONDS
19
+ end
20
+
21
+ def save(page_struct)
22
+ redis.setex prefixed_id(page_struct[:id]), @expires_in_seconds, Marshal::dump(page_struct)
23
+ end
24
+
25
+ def load(id)
26
+ key = prefixed_id(id)
27
+ raw = redis.get key
28
+ begin
29
+ # rubocop:disable Security/MarshalLoad
30
+ Marshal.load(raw) if raw
31
+ # rubocop:enable Security/MarshalLoad
32
+ rescue
33
+ # bad format, junk old data
34
+ redis.del key
35
+ nil
36
+ end
37
+ end
38
+
39
+ def set_unviewed(user, id)
40
+ key = user_key(user)
41
+ if redis.call([:exists, prefixed_id(id)]) == 1
42
+ expire_at = Process.clock_gettime(Process::CLOCK_MONOTONIC).to_i + redis.ttl(prefixed_id(id))
43
+ redis.zadd(key, expire_at, id)
44
+ end
45
+ redis.expire(key, @expires_in_seconds)
46
+ end
47
+
48
+ def set_all_unviewed(user, ids)
49
+ key = user_key(user)
50
+ redis.del(key)
51
+ ids.each do |id|
52
+ if redis.call([:exists, prefixed_id(id)]) == 1
53
+ expire_at = Process.clock_gettime(Process::CLOCK_MONOTONIC).to_i + redis.ttl(prefixed_id(id))
54
+ redis.zadd(key, expire_at, id)
55
+ end
56
+ end
57
+ redis.expire(key, @expires_in_seconds)
58
+ end
59
+
60
+ def set_viewed(user, id)
61
+ redis.zrem(user_key(user), id)
62
+ end
63
+
64
+ # Remove expired ids from the unviewed sorted set and return the remaining ids
65
+ def get_unviewed_ids(user)
66
+ key = user_key(user)
67
+ redis.zremrangebyscore(key, '-inf', Process.clock_gettime(Process::CLOCK_MONOTONIC).to_i)
68
+ redis.zrevrangebyscore(key, '+inf', '-inf')
69
+ end
70
+
71
+ def diagnostics(user)
72
+ client = (redis.respond_to? :_client) ? redis._client : redis.client
73
+ "Redis prefix: #{@prefix}
74
+ Redis location: #{client.host}:#{client.port} db: #{client.db}
75
+ unviewed_ids: #{get_unviewed_ids(user)}
76
+ "
77
+ end
78
+
79
+ def flush_tokens
80
+ redis.del("#{@prefix}-key1", "#{@prefix}-key1_old", "#{@prefix}-key2")
81
+ end
82
+
83
+ # Only used for testing
84
+ def simulate_expire
85
+ redis.del("#{@prefix}-key1")
86
+ end
87
+
88
+ def allowed_tokens
89
+ key1, key1_old, key2 = redis.mget("#{@prefix}-key1", "#{@prefix}-key1_old", "#{@prefix}-key2")
90
+
91
+ if key1 && (key1.length == 32)
92
+ return [key1, key2].compact
93
+ end
94
+
95
+ timeout = Rack::MiniProfiler::AbstractStore::MAX_TOKEN_AGE
96
+
97
+ # TODO this could be moved to lua to correct a concurrency flaw
98
+ # it is not critical cause worse case some requests will miss profiling info
99
+
100
+ # no key so go ahead and set it
101
+ key1 = SecureRandom.hex
102
+
103
+ if key1_old && (key1_old.length == 32)
104
+ key2 = key1_old
105
+ redis.setex "#{@prefix}-key2", timeout, key2
106
+ else
107
+ key2 = nil
108
+ end
109
+
110
+ redis.setex "#{@prefix}-key1", timeout, key1
111
+ redis.setex "#{@prefix}-key1_old", timeout * 2, key1
112
+
113
+ [key1, key2].compact
114
+ end
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
+
262
+ private
263
+
264
+ def user_key(user)
265
+ "#{@prefix}-#{user}-v1"
266
+ end
267
+
268
+ def prefixed_id(id)
269
+ "#{@prefix}#{id}"
270
+ end
271
+
272
+ def redis
273
+ @redis_connection ||= begin
274
+ require 'redis' unless defined? Redis
275
+ Redis.new(@args)
276
+ end
277
+ end
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
351
+ end
352
+ end
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'
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Rack
6
+ class MiniProfiler
7
+ module TimerStruct
8
+ # A base class for timing structures
9
+ class Base
10
+
11
+ def initialize(attrs = {})
12
+ @attributes = attrs
13
+ end
14
+
15
+ def attributes
16
+ @attributes ||= {}
17
+ end
18
+
19
+ def [](name)
20
+ attributes[name]
21
+ end
22
+
23
+ def []=(name, val)
24
+ attributes[name] = val
25
+ end
26
+
27
+ def to_json(*a)
28
+ # this does could take in an option hash, but the only interesting there is max_nesting.
29
+ # if this becomes an option we could increase
30
+ ::JSON.generate(@attributes, max_nesting: 100)
31
+ end
32
+
33
+ def as_json(options = nil)
34
+ @attributes.as_json(options)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end