rack-mini-profiler 1.0.2 → 3.1.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 +4 -4
- data/CHANGELOG.md +145 -21
- data/README.md +201 -94
- data/lib/enable_rails_patches.rb +5 -0
- data/lib/generators/rack_mini_profiler/USAGE +9 -0
- data/lib/generators/rack_mini_profiler/install_generator.rb +13 -0
- data/lib/generators/{rack_profiler/templates/rack_profiler.rb → rack_mini_profiler/templates/rack_mini_profiler.rb} +1 -1
- data/lib/generators/rack_profiler/install_generator.rb +6 -3
- data/lib/html/dot.1.1.2.min.js +2 -0
- data/lib/html/includes.css +144 -45
- data/lib/html/includes.js +1420 -1009
- data/lib/html/includes.scss +538 -441
- data/lib/html/includes.tmpl +231 -148
- data/lib/html/pretty-print.js +810 -0
- data/lib/html/profile_handler.js +1 -1
- data/lib/html/rack-mini-profiler.css +3 -0
- data/lib/html/rack-mini-profiler.js +2 -0
- data/lib/html/share.html +0 -1
- data/lib/html/speedscope/LICENSE +21 -0
- data/lib/html/speedscope/README.md +3 -0
- data/lib/html/speedscope/demangle-cpp.1768f4cc.js +4 -0
- data/lib/html/speedscope/favicon-16x16.f74b3187.png +0 -0
- data/lib/html/speedscope/favicon-32x32.bc503437.png +0 -0
- data/lib/html/speedscope/file-format-schema.json +324 -0
- data/lib/html/speedscope/fonts/source-code-pro-regular.css +8 -0
- data/lib/html/speedscope/fonts/source-code-pro-v13-regular.woff +0 -0
- data/lib/html/speedscope/fonts/source-code-pro-v13-regular.woff2 +0 -0
- data/lib/html/speedscope/import.cf0fa83f.js +115 -0
- data/lib/html/speedscope/index.html +2 -0
- data/lib/html/speedscope/release.txt +3 -0
- data/lib/html/speedscope/reset.8c46b7a1.css +2 -0
- data/lib/html/speedscope/source-map.438fa06b.js +24 -0
- data/lib/html/speedscope/speedscope.44364064.js +200 -0
- data/lib/html/vendor.js +848 -0
- data/lib/mini_profiler/asset_version.rb +3 -2
- data/lib/mini_profiler/client_settings.rb +15 -7
- data/lib/mini_profiler/config.rb +51 -5
- data/lib/mini_profiler/gc_profiler.rb +1 -1
- data/lib/mini_profiler/profiling_methods.rb +13 -8
- data/lib/mini_profiler/snapshots_transporter.rb +109 -0
- data/lib/mini_profiler/storage/abstract_store.rb +52 -1
- data/lib/mini_profiler/storage/file_store.rb +7 -3
- data/lib/mini_profiler/storage/memcache_store.rb +13 -7
- data/lib/mini_profiler/storage/memory_store.rb +100 -7
- data/lib/mini_profiler/storage/redis_store.rb +226 -3
- data/lib/mini_profiler/storage.rb +7 -0
- data/lib/mini_profiler/timer_struct/base.rb +2 -0
- data/lib/mini_profiler/timer_struct/custom.rb +1 -0
- data/lib/mini_profiler/timer_struct/page.rb +60 -4
- data/lib/mini_profiler/timer_struct/request.rb +53 -11
- data/lib/mini_profiler/timer_struct/sql.rb +6 -2
- data/lib/mini_profiler/timer_struct.rb +8 -0
- data/lib/mini_profiler/version.rb +2 -1
- data/lib/{mini_profiler/profiler.rb → mini_profiler.rb} +400 -83
- data/lib/mini_profiler_rails/railtie.rb +89 -7
- data/lib/mini_profiler_rails/railtie_methods.rb +61 -0
- data/lib/patches/db/activerecord.rb +1 -12
- data/lib/patches/db/mongo.rb +1 -1
- data/lib/patches/db/moped.rb +1 -1
- data/lib/patches/db/mysql2/alias_method.rb +30 -0
- data/lib/patches/db/mysql2/prepend.rb +34 -0
- data/lib/patches/db/mysql2.rb +4 -27
- data/lib/patches/db/plucky.rb +4 -4
- data/lib/patches/db/riak.rb +1 -1
- data/lib/patches/net_patches.rb +21 -10
- data/lib/patches/sql_patches.rb +13 -5
- data/lib/prepend_mysql2_patch.rb +5 -0
- data/lib/prepend_net_http_patch.rb +5 -0
- data/lib/rack-mini-profiler.rb +1 -24
- data/rack-mini-profiler.gemspec +17 -8
- metadata +156 -32
- data/lib/html/jquery.1.7.1.js +0 -4
- data/lib/html/jquery.tmpl.js +0 -486
- data/lib/html/list.css +0 -9
- data/lib/html/list.js +0 -38
- data/lib/html/list.tmpl +0 -34
@@ -1,5 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'digest'
|
4
|
+
require 'securerandom'
|
5
|
+
|
3
6
|
module Rack
|
4
7
|
class MiniProfiler
|
5
8
|
class RedisStore < AbstractStore
|
@@ -23,7 +26,9 @@ module Rack
|
|
23
26
|
key = prefixed_id(id)
|
24
27
|
raw = redis.get key
|
25
28
|
begin
|
26
|
-
|
29
|
+
# rubocop:disable Security/MarshalLoad
|
30
|
+
Marshal.load(raw) if raw
|
31
|
+
# rubocop:enable Security/MarshalLoad
|
27
32
|
rescue
|
28
33
|
# bad format, junk old data
|
29
34
|
redis.del key
|
@@ -33,7 +38,7 @@ module Rack
|
|
33
38
|
|
34
39
|
def set_unviewed(user, id)
|
35
40
|
key = user_key(user)
|
36
|
-
if redis.exists
|
41
|
+
if redis.call([:exists, prefixed_id(id)]) == 1
|
37
42
|
expire_at = Process.clock_gettime(Process::CLOCK_MONOTONIC).to_i + redis.ttl(prefixed_id(id))
|
38
43
|
redis.zadd(key, expire_at, id)
|
39
44
|
end
|
@@ -44,7 +49,7 @@ module Rack
|
|
44
49
|
key = user_key(user)
|
45
50
|
redis.del(key)
|
46
51
|
ids.each do |id|
|
47
|
-
if redis.exists
|
52
|
+
if redis.call([:exists, prefixed_id(id)]) == 1
|
48
53
|
expire_at = Process.clock_gettime(Process::CLOCK_MONOTONIC).to_i + redis.ttl(prefixed_id(id))
|
49
54
|
redis.zadd(key, expire_at, id)
|
50
55
|
end
|
@@ -108,6 +113,152 @@ unviewed_ids: #{get_unviewed_ids(user)}
|
|
108
113
|
[key1, key2].compact
|
109
114
|
end
|
110
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
|
+
|
111
262
|
private
|
112
263
|
|
113
264
|
def user_key(user)
|
@@ -125,6 +276,78 @@ unviewed_ids: #{get_unviewed_ids(user)}
|
|
125
276
|
end
|
126
277
|
end
|
127
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
|
128
351
|
end
|
129
352
|
end
|
130
353
|
end
|
@@ -10,6 +10,53 @@ module Rack
|
|
10
10
|
# :has_many TimerStruct::Sql children
|
11
11
|
# :has_many TimerStruct::Custom children
|
12
12
|
class Page < TimerStruct::Base
|
13
|
+
class << self
|
14
|
+
def from_hash(hash)
|
15
|
+
hash = symbolize_hash(hash)
|
16
|
+
if hash.key?(:custom_timing_names)
|
17
|
+
hash[:custom_timing_names] = []
|
18
|
+
end
|
19
|
+
hash.delete(:started_formatted)
|
20
|
+
if hash.key?(:duration_milliseconds)
|
21
|
+
hash[:duration_milliseconds] = 0
|
22
|
+
end
|
23
|
+
page = self.allocate
|
24
|
+
page.instance_variable_set(:@attributes, hash)
|
25
|
+
page
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def symbolize_hash(hash)
|
31
|
+
new_hash = {}
|
32
|
+
hash.each do |k, v|
|
33
|
+
sym_k = String === k ? k.to_sym : k
|
34
|
+
if Hash === v
|
35
|
+
new_hash[sym_k] = symbolize_hash(v)
|
36
|
+
elsif Array === v
|
37
|
+
new_hash[sym_k] = symbolize_array(v)
|
38
|
+
else
|
39
|
+
new_hash[sym_k] = v
|
40
|
+
end
|
41
|
+
end
|
42
|
+
new_hash
|
43
|
+
end
|
44
|
+
|
45
|
+
def symbolize_array(array)
|
46
|
+
array.map do |item|
|
47
|
+
if Array === item
|
48
|
+
symbolize_array(item)
|
49
|
+
elsif Hash === item
|
50
|
+
symbolize_hash(item)
|
51
|
+
else
|
52
|
+
item
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
attr_reader :attributes
|
59
|
+
|
13
60
|
def initialize(env)
|
14
61
|
timer_id = MiniProfiler.generate_id
|
15
62
|
page_name = env['PATH_INFO']
|
@@ -39,8 +86,13 @@ module Rack
|
|
39
86
|
executed_scalars: 0,
|
40
87
|
executed_non_queries: 0,
|
41
88
|
custom_timing_names: [],
|
42
|
-
custom_timing_stats: {}
|
89
|
+
custom_timing_stats: {},
|
90
|
+
custom_fields: {},
|
91
|
+
has_flamegraph: false,
|
92
|
+
flamegraph: nil
|
43
93
|
)
|
94
|
+
self[:request_method] = env['REQUEST_METHOD']
|
95
|
+
self[:request_path] = env['PATH_INFO']
|
44
96
|
name = "#{env['REQUEST_METHOD']} http://#{env['SERVER_NAME']}:#{env['SERVER_PORT']}#{env['SCRIPT_NAME']}#{env['PATH_INFO']}"
|
45
97
|
self[:root] = TimerStruct::Request.createRoot(name, self)
|
46
98
|
end
|
@@ -61,17 +113,21 @@ module Rack
|
|
61
113
|
@attributes[:root]
|
62
114
|
end
|
63
115
|
|
116
|
+
def attributes_to_serialize
|
117
|
+
@attributes.keys - [:flamegraph]
|
118
|
+
end
|
119
|
+
|
64
120
|
def to_json(*a)
|
65
|
-
::JSON.generate(@attributes.merge(
|
121
|
+
::JSON.generate(@attributes.slice(*attributes_to_serialize).merge(extra_json))
|
66
122
|
end
|
67
123
|
|
68
124
|
def as_json(options = nil)
|
69
|
-
super(options).merge!(extra_json)
|
125
|
+
super(options).slice(*attributes_to_serialize.map(&:to_s)).merge!(extra_json)
|
70
126
|
end
|
71
127
|
|
72
128
|
def extra_json
|
73
129
|
{
|
74
|
-
|
130
|
+
started_formatted: '/Date(%d)/' % @attributes[:started_at],
|
75
131
|
duration_milliseconds: @attributes[:root][:duration_milliseconds],
|
76
132
|
custom_timing_names: @attributes[:custom_timing_stats].keys.sort
|
77
133
|
}
|
@@ -11,7 +11,7 @@ module Rack
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
-
attr_accessor :children_duration
|
14
|
+
attr_accessor :children_duration, :start, :parent
|
15
15
|
|
16
16
|
def initialize(name, page, parent)
|
17
17
|
start_millis = (Process.clock_gettime(Process::CLOCK_MONOTONIC) * 1000).to_i - page[:started]
|
@@ -62,10 +62,6 @@ module Rack
|
|
62
62
|
self[:start_milliseconds]
|
63
63
|
end
|
64
64
|
|
65
|
-
def start
|
66
|
-
@start
|
67
|
-
end
|
68
|
-
|
69
65
|
def depth
|
70
66
|
self[:depth]
|
71
67
|
end
|
@@ -91,6 +87,20 @@ module Rack
|
|
91
87
|
end
|
92
88
|
end
|
93
89
|
|
90
|
+
def move_child(child, destination)
|
91
|
+
if index = self[:children].index(child)
|
92
|
+
self[:children].slice!(index)
|
93
|
+
self[:has_children] = self[:children].size > 0
|
94
|
+
|
95
|
+
destination[:children].push(child)
|
96
|
+
destination[:has_children] = true
|
97
|
+
|
98
|
+
child[:parent_timing_id] = destination[:id]
|
99
|
+
child.parent = destination
|
100
|
+
child.adjust_depth
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
94
104
|
def add_sql(query, elapsed_ms, page, params = nil, skip_backtrace = false, full_backtrace = false)
|
95
105
|
TimerStruct::Sql.new(query, elapsed_ms, page, self, params, skip_backtrace, full_backtrace).tap do |timer|
|
96
106
|
self[:sql_timings].push(timer)
|
@@ -102,6 +112,19 @@ module Rack
|
|
102
112
|
end
|
103
113
|
end
|
104
114
|
|
115
|
+
def move_sql(sql, destination)
|
116
|
+
if index = self[:sql_timings].index(sql)
|
117
|
+
self[:sql_timings].slice!(index)
|
118
|
+
self[:has_sql_timings] = self[:sql_timings].size > 0
|
119
|
+
self[:sql_timings_duration_milliseconds] -= sql[:duration_milliseconds]
|
120
|
+
destination[:sql_timings].push(sql)
|
121
|
+
destination[:has_sql_timings] = true
|
122
|
+
destination[:sql_timings_duration_milliseconds] += sql[:duration_milliseconds]
|
123
|
+
sql[:parent_timing_id] = destination[:id]
|
124
|
+
sql.parent = destination
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
105
128
|
def add_custom(type, elapsed_ms, page)
|
106
129
|
TimerStruct::Custom.new(type, elapsed_ms, page, self).tap do |timer|
|
107
130
|
timer[:parent_timing_id] = self[:id]
|
@@ -119,18 +142,37 @@ module Rack
|
|
119
142
|
end
|
120
143
|
end
|
121
144
|
|
145
|
+
def move_custom(type, custom, destination)
|
146
|
+
if index = self[:custom_timings][type]&.index(custom)
|
147
|
+
custom[:parent_timing_id] = destination[:id]
|
148
|
+
custom.parent = destination
|
149
|
+
self[:custom_timings][type].slice!(index)
|
150
|
+
if self[:custom_timings][type].size == 0
|
151
|
+
self[:custom_timings].delete(type)
|
152
|
+
self[:custom_timing_stats].delete(type)
|
153
|
+
else
|
154
|
+
self[:custom_timing_stats][type][:count] -= 1
|
155
|
+
self[:custom_timing_stats][type][:duration] -= custom[:duration_milliseconds]
|
156
|
+
end
|
157
|
+
destination[:custom_timings][type] ||= []
|
158
|
+
destination[:custom_timings][type].push(custom)
|
159
|
+
destination[:custom_timing_stats][type] ||= { count: 0, duration: 0.0 }
|
160
|
+
destination[:custom_timing_stats][type][:count] += 1
|
161
|
+
destination[:custom_timing_stats][type][:duration] += custom[:duration_milliseconds]
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
122
165
|
def record_time(milliseconds = nil)
|
123
166
|
milliseconds ||= (Process.clock_gettime(Process::CLOCK_MONOTONIC) - @start) * 1000
|
124
167
|
self[:duration_milliseconds] = milliseconds
|
125
168
|
self[:is_trivial] = true if milliseconds < self[:trivial_duration_threshold_milliseconds]
|
126
|
-
self[:duration_without_children_milliseconds] = milliseconds -
|
127
|
-
|
128
|
-
if @parent
|
129
|
-
@parent.children_duration += milliseconds
|
130
|
-
end
|
131
|
-
|
169
|
+
self[:duration_without_children_milliseconds] = milliseconds - self[:children].sum(&:duration_ms)
|
132
170
|
end
|
133
171
|
|
172
|
+
def adjust_depth
|
173
|
+
self[:depth] = self.parent ? self.parent[:depth] + 1 : 0
|
174
|
+
self[:children].each(&:adjust_depth)
|
175
|
+
end
|
134
176
|
end
|
135
177
|
end
|
136
178
|
end
|
@@ -1,11 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'erb'
|
4
|
+
|
3
5
|
module Rack
|
4
6
|
class MiniProfiler
|
5
7
|
|
6
8
|
# Timing system for a SQL query
|
7
9
|
module TimerStruct
|
8
10
|
class Sql < TimerStruct::Base
|
11
|
+
attr_accessor :parent
|
12
|
+
|
9
13
|
def initialize(query, duration_ms, page, parent, params = nil, skip_backtrace = false, full_backtrace = false)
|
10
14
|
|
11
15
|
stack_trace = nil
|
@@ -36,12 +40,12 @@ module Rack
|
|
36
40
|
start_millis = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) * 1000).to_i - page[:started]) - duration_ms
|
37
41
|
super(
|
38
42
|
execute_type: 3, # TODO
|
39
|
-
formatted_command_string: query,
|
43
|
+
formatted_command_string: query ? ERB::Util.html_escape(query) : nil,
|
40
44
|
stack_trace_snippet: stack_trace,
|
41
45
|
start_milliseconds: start_millis,
|
42
46
|
duration_milliseconds: duration_ms,
|
43
47
|
first_fetch_duration_milliseconds: duration_ms,
|
44
|
-
parameters: trim_binds(params),
|
48
|
+
parameters: query ? trim_binds(params) : nil,
|
45
49
|
parent_timing_id: nil,
|
46
50
|
is_duplicate: false
|
47
51
|
)
|
@@ -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'
|