rack-mini-profiler 1.0.2 → 3.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +138 -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 +1423 -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 +98 -5
- 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} +394 -82
- data/lib/mini_profiler_rails/railtie.rb +88 -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'
|