piko-sharp-kit 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.
- checksums.yaml +7 -0
- data/piko-sharp-kit.gemspec +11 -0
- data/rack-mini-profiler-4.0.1/CHANGELOG.md +424 -0
- data/rack-mini-profiler-4.0.1/README.md +542 -0
- data/rack-mini-profiler-4.0.1/lib/enable_rails_patches.rb +5 -0
- data/rack-mini-profiler-4.0.1/lib/generators/rack_mini_profiler/USAGE +9 -0
- data/rack-mini-profiler-4.0.1/lib/generators/rack_mini_profiler/install_generator.rb +13 -0
- data/rack-mini-profiler-4.0.1/lib/generators/rack_mini_profiler/templates/rack_mini_profiler.rb +8 -0
- data/rack-mini-profiler-4.0.1/lib/generators/rack_profiler/USAGE +2 -0
- data/rack-mini-profiler-4.0.1/lib/generators/rack_profiler/install_generator.rb +16 -0
- data/rack-mini-profiler-4.0.1/lib/html/dot.1.1.2.min.js +2 -0
- data/rack-mini-profiler-4.0.1/lib/html/includes.css +474 -0
- data/rack-mini-profiler-4.0.1/lib/html/includes.js +1520 -0
- data/rack-mini-profiler-4.0.1/lib/html/includes.scss +698 -0
- data/rack-mini-profiler-4.0.1/lib/html/includes.tmpl +333 -0
- data/rack-mini-profiler-4.0.1/lib/html/pretty-print.js +810 -0
- data/rack-mini-profiler-4.0.1/lib/html/profile_handler.js +1 -0
- data/rack-mini-profiler-4.0.1/lib/html/rack-mini-profiler.css +3 -0
- data/rack-mini-profiler-4.0.1/lib/html/rack-mini-profiler.js +2 -0
- data/rack-mini-profiler-4.0.1/lib/html/share.html +10 -0
- data/rack-mini-profiler-4.0.1/lib/html/speedscope/LICENSE +21 -0
- data/rack-mini-profiler-4.0.1/lib/html/speedscope/README.md +3 -0
- data/rack-mini-profiler-4.0.1/lib/html/speedscope/demangle-cpp.1768f4cc.js +4 -0
- data/rack-mini-profiler-4.0.1/lib/html/speedscope/favicon-16x16.f74b3187.png +0 -0
- data/rack-mini-profiler-4.0.1/lib/html/speedscope/favicon-32x32.bc503437.png +0 -0
- data/rack-mini-profiler-4.0.1/lib/html/speedscope/file-format-schema.json +324 -0
- data/rack-mini-profiler-4.0.1/lib/html/speedscope/fonts/source-code-pro-regular.css +8 -0
- data/rack-mini-profiler-4.0.1/lib/html/speedscope/fonts/source-code-pro-v13-regular.woff +0 -0
- data/rack-mini-profiler-4.0.1/lib/html/speedscope/fonts/source-code-pro-v13-regular.woff2 +0 -0
- data/rack-mini-profiler-4.0.1/lib/html/speedscope/import.8ae8aa3d.js +123 -0
- data/rack-mini-profiler-4.0.1/lib/html/speedscope/index.html +2 -0
- data/rack-mini-profiler-4.0.1/lib/html/speedscope/release.txt +3 -0
- data/rack-mini-profiler-4.0.1/lib/html/speedscope/reset.8c46b7a1.css +2 -0
- data/rack-mini-profiler-4.0.1/lib/html/speedscope/source-map.438fa06b.js +24 -0
- data/rack-mini-profiler-4.0.1/lib/html/speedscope/speedscope.f27db165.js +193 -0
- data/rack-mini-profiler-4.0.1/lib/html/vendor.js +848 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/actions.rb +157 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/asset_version.rb +6 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/client_settings.rb +127 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/config.rb +146 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/context.rb +14 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/gc_profiler.rb +158 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/profiling_methods.rb +167 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/snapshots_transporter.rb +111 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/storage/abstract_store.rb +97 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/storage/file_store.rb +179 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/storage/memcache_store.rb +105 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/storage/memory_store.rb +233 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/storage/redis_store.rb +353 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/storage.rb +7 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/timer_struct/base.rb +39 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/timer_struct/client.rb +90 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/timer_struct/custom.rb +25 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/timer_struct/page.rb +146 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/timer_struct/request.rb +186 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/timer_struct/sql.rb +89 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/timer_struct.rb +8 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/version.rb +8 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/views.rb +198 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler.rb +670 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler_rails/railtie.rb +234 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler_rails/railtie_methods.rb +61 -0
- data/rack-mini-profiler-4.0.1/lib/patches/db/activerecord.rb +44 -0
- data/rack-mini-profiler-4.0.1/lib/patches/db/mongo.rb +18 -0
- data/rack-mini-profiler-4.0.1/lib/patches/db/moped.rb +14 -0
- data/rack-mini-profiler-4.0.1/lib/patches/db/mysql2/alias_method.rb +30 -0
- data/rack-mini-profiler-4.0.1/lib/patches/db/mysql2/prepend.rb +34 -0
- data/rack-mini-profiler-4.0.1/lib/patches/db/mysql2.rb +7 -0
- data/rack-mini-profiler-4.0.1/lib/patches/db/neo4j.rb +16 -0
- data/rack-mini-profiler-4.0.1/lib/patches/db/nobrainer.rb +31 -0
- data/rack-mini-profiler-4.0.1/lib/patches/db/oracle_enhanced.rb +70 -0
- data/rack-mini-profiler-4.0.1/lib/patches/db/pg/alias_method.rb +121 -0
- data/rack-mini-profiler-4.0.1/lib/patches/db/pg/prepend.rb +115 -0
- data/rack-mini-profiler-4.0.1/lib/patches/db/pg.rb +7 -0
- data/rack-mini-profiler-4.0.1/lib/patches/db/plucky.rb +49 -0
- data/rack-mini-profiler-4.0.1/lib/patches/db/riak.rb +105 -0
- data/rack-mini-profiler-4.0.1/lib/patches/db/rsolr.rb +26 -0
- data/rack-mini-profiler-4.0.1/lib/patches/db/sequel.rb +12 -0
- data/rack-mini-profiler-4.0.1/lib/patches/net_patches.rb +27 -0
- data/rack-mini-profiler-4.0.1/lib/patches/sql_patches.rb +74 -0
- data/rack-mini-profiler-4.0.1/lib/prepend_mysql2_patch.rb +5 -0
- data/rack-mini-profiler-4.0.1/lib/prepend_net_http_patch.rb +5 -0
- data/rack-mini-profiler-4.0.1/lib/prepend_pg_patch.rb +5 -0
- data/rack-mini-profiler-4.0.1/lib/rack-mini-profiler.rb +13 -0
- data/rack-mini-profiler-4.0.1/rack-mini-profiler.gemspec +49 -0
- metadata +124 -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,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
|