rack-mini-profiler 0.10.6 → 2.3.0

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 (74) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +129 -16
  3. data/README.md +116 -63
  4. data/lib/enable_rails_patches.rb +5 -0
  5. data/lib/generators/rack_profiler/install_generator.rb +2 -0
  6. data/lib/generators/rack_profiler/templates/rack_profiler.rb +2 -0
  7. data/lib/html/dot.1.1.2.min.js +2 -0
  8. data/lib/html/includes.css +141 -40
  9. data/lib/html/includes.js +1398 -970
  10. data/lib/html/includes.scss +547 -442
  11. data/lib/html/includes.tmpl +227 -142
  12. data/lib/html/pretty-print.js +810 -0
  13. data/lib/html/profile_handler.js +1 -1
  14. data/lib/html/rack-mini-profiler.css +3 -0
  15. data/lib/html/rack-mini-profiler.js +2 -0
  16. data/lib/html/share.html +0 -1
  17. data/lib/html/speedscope/LICENSE +21 -0
  18. data/lib/html/speedscope/README.md +3 -0
  19. data/lib/html/speedscope/demangle-cpp.1768f4cc.js +4 -0
  20. data/lib/html/speedscope/favicon-16x16.f74b3187.png +0 -0
  21. data/lib/html/speedscope/favicon-32x32.bc503437.png +0 -0
  22. data/lib/html/speedscope/file-format-schema.json +324 -0
  23. data/lib/html/speedscope/import.cf0fa83f.js +115 -0
  24. data/lib/html/speedscope/index.html +2 -0
  25. data/lib/html/speedscope/release.txt +3 -0
  26. data/lib/html/speedscope/reset.8c46b7a1.css +2 -0
  27. data/lib/html/speedscope/source-map.438fa06b.js +24 -0
  28. data/lib/html/speedscope/speedscope.44364064.js +200 -0
  29. data/lib/html/vendor.js +848 -0
  30. data/lib/mini_profiler/asset_version.rb +3 -2
  31. data/lib/mini_profiler/client_settings.rb +27 -16
  32. data/lib/mini_profiler/config.rb +73 -46
  33. data/lib/mini_profiler/context.rb +5 -3
  34. data/lib/mini_profiler/gc_profiler.rb +17 -16
  35. data/lib/mini_profiler/profiler.rb +332 -94
  36. data/lib/mini_profiler/profiling_methods.rb +20 -15
  37. data/lib/mini_profiler/snapshots_transporter.rb +109 -0
  38. data/lib/mini_profiler/storage/abstract_store.rb +80 -0
  39. data/lib/mini_profiler/storage/file_store.rb +18 -13
  40. data/lib/mini_profiler/storage/memcache_store.rb +10 -7
  41. data/lib/mini_profiler/storage/memory_store.rb +63 -13
  42. data/lib/mini_profiler/storage/redis_store.rb +143 -7
  43. data/lib/mini_profiler/timer_struct/base.rb +4 -2
  44. data/lib/mini_profiler/timer_struct/client.rb +9 -8
  45. data/lib/mini_profiler/timer_struct/custom.rb +8 -5
  46. data/lib/mini_profiler/timer_struct/page.rb +79 -24
  47. data/lib/mini_profiler/timer_struct/request.rb +83 -38
  48. data/lib/mini_profiler/timer_struct/sql.rb +25 -22
  49. data/lib/mini_profiler/version.rb +3 -1
  50. data/lib/mini_profiler_rails/railtie.rb +91 -8
  51. data/lib/mini_profiler_rails/railtie_methods.rb +61 -0
  52. data/lib/patches/db/activerecord.rb +5 -14
  53. data/lib/patches/db/mongo.rb +3 -1
  54. data/lib/patches/db/moped.rb +5 -3
  55. data/lib/patches/db/mysql2.rb +8 -6
  56. data/lib/patches/db/neo4j.rb +3 -1
  57. data/lib/patches/db/nobrainer.rb +4 -2
  58. data/lib/patches/db/oracle_enhanced.rb +4 -2
  59. data/lib/patches/db/pg.rb +41 -21
  60. data/lib/patches/db/plucky.rb +7 -5
  61. data/lib/patches/db/riak.rb +15 -13
  62. data/lib/patches/db/rsolr.rb +6 -4
  63. data/lib/patches/db/sequel.rb +2 -0
  64. data/lib/patches/net_patches.rb +20 -8
  65. data/lib/patches/sql_patches.rb +17 -7
  66. data/lib/prepend_net_http_patch.rb +5 -0
  67. data/lib/rack-mini-profiler.rb +3 -3
  68. data/rack-mini-profiler.gemspec +23 -9
  69. metadata +146 -31
  70. data/lib/html/jquery.1.7.1.js +0 -4
  71. data/lib/html/jquery.tmpl.js +0 -486
  72. data/lib/html/list.css +0 -9
  73. data/lib/html/list.js +0 -38
  74. data/lib/html/list.tmpl +0 -34
@@ -1,3 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest'
4
+
1
5
  module Rack
2
6
  class MiniProfiler
3
7
  class RedisStore < AbstractStore
@@ -8,7 +12,7 @@ module Rack
8
12
 
9
13
  def initialize(args = nil)
10
14
  @args = args || {}
11
- @prefix = @args.delete(:prefix) || 'MPRedisStore'
15
+ @prefix = @args.delete(:prefix) || 'MPRedisStore'
12
16
  @redis_connection = @args.delete(:connection)
13
17
  @expires_in_seconds = @args.delete(:expires_in) || EXPIRES_IN_SECONDS
14
18
  end
@@ -31,8 +35,8 @@ module Rack
31
35
 
32
36
  def set_unviewed(user, id)
33
37
  key = user_key(user)
34
- if redis.exists(prefixed_id(id))
35
- expire_at = Time.now.to_i + redis.ttl(prefixed_id(id))
38
+ if redis.call([:exists, prefixed_id(id)]) == 1
39
+ expire_at = Process.clock_gettime(Process::CLOCK_MONOTONIC).to_i + redis.ttl(prefixed_id(id))
36
40
  redis.zadd(key, expire_at, id)
37
41
  end
38
42
  redis.expire(key, @expires_in_seconds)
@@ -42,8 +46,8 @@ module Rack
42
46
  key = user_key(user)
43
47
  redis.del(key)
44
48
  ids.each do |id|
45
- if redis.exists(prefixed_id(id))
46
- expire_at = Time.now.to_i + redis.ttl(prefixed_id(id))
49
+ if redis.call([:exists, prefixed_id(id)]) == 1
50
+ expire_at = Process.clock_gettime(Process::CLOCK_MONOTONIC).to_i + redis.ttl(prefixed_id(id))
47
51
  redis.zadd(key, expire_at, id)
48
52
  end
49
53
  end
@@ -57,7 +61,7 @@ module Rack
57
61
  # Remove expired ids from the unviewed sorted set and return the remaining ids
58
62
  def get_unviewed_ids(user)
59
63
  key = user_key(user)
60
- redis.zremrangebyscore(key, '-inf', Time.now.to_i)
64
+ redis.zremrangebyscore(key, '-inf', Process.clock_gettime(Process::CLOCK_MONOTONIC).to_i)
61
65
  redis.zrevrangebyscore(key, '+inf', '-inf')
62
66
  end
63
67
 
@@ -101,11 +105,111 @@ unviewed_ids: #{get_unviewed_ids(user)}
101
105
  end
102
106
 
103
107
  redis.setex "#{@prefix}-key1", timeout, key1
104
- redis.setex "#{@prefix}-key1_old", timeout*2, key1
108
+ redis.setex "#{@prefix}-key1_old", timeout * 2, key1
105
109
 
106
110
  [key1, key2].compact
107
111
  end
108
112
 
113
+ COUNTER_LUA = <<~LUA
114
+ if redis.call("INCR", KEYS[1]) % ARGV[1] == 0 then
115
+ redis.call("DEL", KEYS[1])
116
+ return 1
117
+ else
118
+ return 0
119
+ end
120
+ LUA
121
+
122
+ COUNTER_LUA_SHA = Digest::SHA1.hexdigest(COUNTER_LUA)
123
+
124
+ def should_take_snapshot?(period)
125
+ 1 == cached_redis_eval(
126
+ COUNTER_LUA,
127
+ COUNTER_LUA_SHA,
128
+ reraise: false,
129
+ keys: [snapshot_counter_key()],
130
+ argv: [period]
131
+ )
132
+ end
133
+
134
+ def push_snapshot(page_struct, config)
135
+ zset_key = snapshot_zset_key()
136
+ hash_key = snapshot_hash_key()
137
+
138
+ id = page_struct[:id]
139
+ score = page_struct.duration_ms
140
+ limit = config.snapshots_limit
141
+ bytes = Marshal.dump(page_struct)
142
+
143
+ lua = <<~LUA
144
+ local zset_key = KEYS[1]
145
+ local hash_key = KEYS[2]
146
+ local id = ARGV[1]
147
+ local score = tonumber(ARGV[2])
148
+ local bytes = ARGV[3]
149
+ local limit = tonumber(ARGV[4])
150
+ redis.call("ZADD", zset_key, score, id)
151
+ redis.call("HSET", hash_key, id, bytes)
152
+ if redis.call("ZCARD", zset_key) > limit then
153
+ local lowest_snapshot_id = redis.call("ZRANGE", zset_key, 0, 0)[1]
154
+ redis.call("ZREM", zset_key, lowest_snapshot_id)
155
+ redis.call("HDEL", hash_key, lowest_snapshot_id)
156
+ end
157
+ LUA
158
+ redis.eval(
159
+ lua,
160
+ keys: [zset_key, hash_key],
161
+ argv: [id, score, bytes, limit]
162
+ )
163
+ end
164
+
165
+ def fetch_snapshots(batch_size: 200, &blk)
166
+ zset_key = snapshot_zset_key()
167
+ hash_key = snapshot_hash_key()
168
+ iteration = 0
169
+ corrupt_snapshots = []
170
+ while true
171
+ ids = redis.zrange(
172
+ zset_key,
173
+ batch_size * iteration,
174
+ batch_size * iteration + batch_size - 1
175
+ )
176
+ break if ids.size == 0
177
+ batch = redis.mapped_hmget(hash_key, *ids).to_a
178
+ batch.map! do |id, bytes|
179
+ begin
180
+ Marshal.load(bytes)
181
+ rescue
182
+ corrupt_snapshots << id
183
+ nil
184
+ end
185
+ end
186
+ batch.compact!
187
+ blk.call(batch) if batch.size != 0
188
+ break if ids.size < batch_size
189
+ iteration += 1
190
+ end
191
+ if corrupt_snapshots.size > 0
192
+ redis.pipelined do
193
+ redis.zrem(zset_key, corrupt_snapshots)
194
+ redis.hdel(hash_key, corrupt_snapshots)
195
+ end
196
+ end
197
+ end
198
+
199
+ def load_snapshot(id)
200
+ hash_key = snapshot_hash_key()
201
+ bytes = redis.hget(hash_key, id)
202
+ begin
203
+ Marshal.load(bytes)
204
+ rescue
205
+ redis.pipelined do
206
+ redis.zrem(snapshot_zset_key(), id)
207
+ redis.hdel(hash_key, id)
208
+ end
209
+ nil
210
+ end
211
+ end
212
+
109
213
  private
110
214
 
111
215
  def user_key(user)
@@ -123,6 +227,38 @@ unviewed_ids: #{get_unviewed_ids(user)}
123
227
  end
124
228
  end
125
229
 
230
+ def snapshot_counter_key
231
+ @snapshot_counter_key ||= "#{@prefix}-mini-profiler-snapshots-counter"
232
+ end
233
+
234
+ def snapshot_zset_key
235
+ @snapshot_zset_key ||= "#{@prefix}-mini-profiler-snapshots-zset"
236
+ end
237
+
238
+ def snapshot_hash_key
239
+ @snapshot_hash_key ||= "#{@prefix}-mini-profiler-snapshots-hash"
240
+ end
241
+
242
+ def cached_redis_eval(script, script_sha, reraise: true, argv: [], keys: [])
243
+ begin
244
+ redis.evalsha(script_sha, argv: argv, keys: keys)
245
+ rescue ::Redis::CommandError => e
246
+ if e.message.start_with?('NOSCRIPT')
247
+ redis.eval(script, argv: argv, keys: keys)
248
+ else
249
+ raise e if reraise
250
+ end
251
+ end
252
+ end
253
+
254
+ # only used in tests
255
+ def wipe_snapshots_data
256
+ redis.pipelined do
257
+ redis.del(snapshot_counter_key())
258
+ redis.del(snapshot_zset_key())
259
+ redis.del(snapshot_hash_key())
260
+ end
261
+ end
126
262
  end
127
263
  end
128
264
  end
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  class MiniProfiler
3
5
  module TimerStruct
4
6
  # A base class for timing structures
5
7
  class Base
6
8
 
7
- def initialize(attrs={})
9
+ def initialize(attrs = {})
8
10
  @attributes = attrs
9
11
  end
10
12
 
@@ -24,7 +26,7 @@ module Rack
24
26
  def to_json(*a)
25
27
  # this does could take in an option hash, but the only interesting there is max_nesting.
26
28
  # if this becomes an option we could increase
27
- ::JSON.generate( @attributes, :max_nesting => 100 )
29
+ ::JSON.generate(@attributes, max_nesting: 100)
28
30
  end
29
31
 
30
32
  def as_json(options = nil)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  class MiniProfiler
3
5
  module TimerStruct
@@ -15,15 +17,14 @@ module Rack
15
17
 
16
18
  # used by Railtie to instrument asset_tag for JS / CSS
17
19
  def self.instrument(name, orig)
18
- probe = "<script>mPt.probe('#{name}')</script>"
20
+ probe = "<script>mPt.probe('#{name}')</script>".dup
19
21
  wrapped = probe
20
22
  wrapped << orig
21
23
  wrapped << probe
22
24
  wrapped
23
25
  end
24
26
 
25
-
26
- def initialize(env={})
27
+ def initialize(env = {})
27
28
  super
28
29
  end
29
30
 
@@ -60,21 +61,21 @@ module Rack
60
61
  end
61
62
 
62
63
  translated.each do |name, data|
63
- h = {"Name" => name, "Start" => data[:start].to_i - baseTime}
64
+ h = { "Name" => name, "Start" => data[:start].to_i - baseTime }
64
65
  h["Duration"] = data[:finish].to_i - data[:start].to_i if data[:finish]
65
66
  timings.push(h)
66
67
  end
67
68
 
68
- clientTimes.keys.find_all{|k| k =~ /Start$/ }.each do |k|
69
+ clientTimes.keys.find_all { |k| k =~ /Start$/ }.each do |k|
69
70
  start = clientTimes[k].to_i - baseTime
70
71
  finish = clientTimes[k.sub(/Start$/, "End")].to_i - baseTime
71
72
  duration = 0
72
73
  duration = finish - start if finish > start
73
- name = k.sub(/Start$/, "").split(/(?=[A-Z])/).map{|s| s.capitalize}.join(' ')
74
- timings.push({"Name" => name, "Start" => start, "Duration" => duration}) if start >= 0
74
+ name = k.sub(/Start$/, "").split(/(?=[A-Z])/).map { |s| s.capitalize }.join(' ')
75
+ timings.push("Name" => name, "Start" => start, "Duration" => duration) if start >= 0
75
76
  end
76
77
 
77
- clientTimes.keys.find_all{|k| !(k =~ /(End|Start)$/)}.each do |k|
78
+ clientTimes.keys.find_all { |k| !(k =~ /(End|Start)$/) }.each do |k|
78
79
  timings.push("Name" => k, "Start" => clientTimes[k].to_i - baseTime, "Duration" => -1)
79
80
  end
80
81
 
@@ -1,19 +1,22 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  class MiniProfiler
3
5
  module TimerStruct
4
6
  # Timing system for a custom timers such as cache, redis, RPC, external API
5
7
  # calls, etc.
6
8
  class Custom < TimerStruct::Base
9
+ attr_accessor :parent
7
10
  def initialize(type, duration_ms, page, parent)
8
11
  @parent = parent
9
12
  @page = page
10
13
  @type = type
11
- start_millis = ((Time.now.to_f * 1000).to_i - page[:started]) - duration_ms
14
+ start_millis = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) * 1000).to_i - page[:started]) - duration_ms
12
15
  super(
13
- :type => type,
14
- :start_milliseconds => start_millis,
15
- :duration_milliseconds => duration_ms,
16
- :parent_timing_id => nil
16
+ type: type,
17
+ start_milliseconds: start_millis,
18
+ duration_milliseconds: duration_ms,
19
+ parent_timing_id: nil
17
20
  )
18
21
  end
19
22
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  class MiniProfiler
3
5
  module TimerStruct
@@ -8,34 +10,87 @@ module Rack
8
10
  # :has_many TimerStruct::Sql children
9
11
  # :has_many TimerStruct::Custom children
10
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
+
11
60
  def initialize(env)
12
61
  timer_id = MiniProfiler.generate_id
13
62
  page_name = env['PATH_INFO']
14
63
  started_at = (Time.now.to_f * 1000).to_i
64
+ started = (Process.clock_gettime(Process::CLOCK_MONOTONIC) * 1000).to_i
15
65
  machine_name = env['SERVER_NAME']
16
66
  super(
17
- :id => timer_id,
18
- :name => page_name,
19
- :started => started_at,
20
- :machine_name => machine_name,
21
- :level => 0,
22
- :user => "unknown user",
23
- :has_user_viewed => false,
24
- :client_timings => nil,
25
- :duration_milliseconds => 0,
26
- :has_trivial_timings => true,
27
- :has_all_trivial_timings => false,
28
- :trivial_duration_threshold_milliseconds => 2,
29
- :head => nil,
30
- :duration_milliseconds_in_sql => 0,
31
- :has_sql_timings => true,
32
- :has_duplicate_sql_timings => false,
33
- :executed_readers => 0,
34
- :executed_scalars => 0,
35
- :executed_non_queries => 0,
36
- :custom_timing_names => [],
37
- :custom_timing_stats => {}
67
+ id: timer_id,
68
+ name: page_name,
69
+ started: started,
70
+ started_at: started_at,
71
+ machine_name: machine_name,
72
+ level: 0,
73
+ user: "unknown user",
74
+ has_user_viewed: false,
75
+ client_timings: nil,
76
+ duration_milliseconds: 0,
77
+ has_trivial_timings: true,
78
+ has_all_trivial_timings: false,
79
+ trivial_duration_threshold_milliseconds: 2,
80
+ head: nil,
81
+ sql_count: 0,
82
+ duration_milliseconds_in_sql: 0,
83
+ has_sql_timings: true,
84
+ has_duplicate_sql_timings: false,
85
+ executed_readers: 0,
86
+ executed_scalars: 0,
87
+ executed_non_queries: 0,
88
+ custom_timing_names: [],
89
+ custom_timing_stats: {},
90
+ custom_fields: {}
38
91
  )
92
+ self[:request_method] = env['REQUEST_METHOD']
93
+ self[:request_path] = env['PATH_INFO']
39
94
  name = "#{env['REQUEST_METHOD']} http://#{env['SERVER_NAME']}:#{env['SERVER_PORT']}#{env['SCRIPT_NAME']}#{env['PATH_INFO']}"
40
95
  self[:root] = TimerStruct::Request.createRoot(name, self)
41
96
  end
@@ -66,9 +121,9 @@ module Rack
66
121
 
67
122
  def extra_json
68
123
  {
69
- :started => '/Date(%d)/' % @attributes[:started],
70
- :duration_milliseconds => @attributes[:root][:duration_milliseconds],
71
- :custom_timing_names => @attributes[:custom_timing_stats].keys.sort
124
+ started_formatted: '/Date(%d)/' % @attributes[:started_at],
125
+ duration_milliseconds: @attributes[:root][:duration_milliseconds],
126
+ custom_timing_names: @attributes[:custom_timing_stats].keys.sort
72
127
  }
73
128
  end
74
129
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rack
2
4
  class MiniProfiler
3
5
  module TimerStruct
@@ -9,37 +11,37 @@ module Rack
9
11
  end
10
12
  end
11
13
 
12
- attr_accessor :children_duration
14
+ attr_accessor :children_duration, :start, :parent
13
15
 
14
16
  def initialize(name, page, parent)
15
- start_millis = (Time.now.to_f * 1000).to_i - page[:started]
17
+ start_millis = (Process.clock_gettime(Process::CLOCK_MONOTONIC) * 1000).to_i - page[:started]
16
18
  depth = parent ? parent.depth + 1 : 0
17
19
  super(
18
- :id => MiniProfiler.generate_id,
19
- :name => name,
20
- :duration_milliseconds => 0,
21
- :duration_without_children_milliseconds => 0,
22
- :start_milliseconds => start_millis,
23
- :parent_timing_id => nil,
24
- :children => [],
25
- :has_children => false,
26
- :key_values => nil,
27
- :has_sql_timings => false,
28
- :has_duplicate_sql_timings => false,
29
- :trivial_duration_threshold_milliseconds => 2,
30
- :sql_timings => [],
31
- :sql_timings_duration_milliseconds => 0,
32
- :is_trivial => false,
33
- :is_root => false,
34
- :depth => depth,
35
- :executed_readers => 0,
36
- :executed_scalars => 0,
37
- :executed_non_queries => 0,
38
- :custom_timing_stats => {},
39
- :custom_timings => {}
20
+ id: MiniProfiler.generate_id,
21
+ name: name,
22
+ duration_milliseconds: 0,
23
+ duration_without_children_milliseconds: 0,
24
+ start_milliseconds: start_millis,
25
+ parent_timing_id: nil,
26
+ children: [],
27
+ has_children: false,
28
+ key_values: nil,
29
+ has_sql_timings: false,
30
+ has_duplicate_sql_timings: false,
31
+ trivial_duration_threshold_milliseconds: 2,
32
+ sql_timings: [],
33
+ sql_timings_duration_milliseconds: 0,
34
+ is_trivial: false,
35
+ is_root: false,
36
+ depth: depth,
37
+ executed_readers: 0,
38
+ executed_scalars: 0,
39
+ executed_non_queries: 0,
40
+ custom_timing_stats: {},
41
+ custom_timings: {}
40
42
  )
41
43
  @children_duration = 0
42
- @start = Time.now
44
+ @start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
43
45
  @parent = parent
44
46
  @page = page
45
47
  end
@@ -60,10 +62,6 @@ module Rack
60
62
  self[:start_milliseconds]
61
63
  end
62
64
 
63
- def start
64
- @start
65
- end
66
-
67
65
  def depth
68
66
  self[:depth]
69
67
  end
@@ -89,6 +87,20 @@ module Rack
89
87
  end
90
88
  end
91
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
+
92
104
  def add_sql(query, elapsed_ms, page, params = nil, skip_backtrace = false, full_backtrace = false)
93
105
  TimerStruct::Sql.new(query, elapsed_ms, page, self, params, skip_backtrace, full_backtrace).tap do |timer|
94
106
  self[:sql_timings].push(timer)
@@ -96,6 +108,20 @@ module Rack
96
108
  self[:has_sql_timings] = true
97
109
  self[:sql_timings_duration_milliseconds] += elapsed_ms
98
110
  page[:duration_milliseconds_in_sql] += elapsed_ms
111
+ page[:sql_count] += 1
112
+ end
113
+ end
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
99
125
  end
100
126
  end
101
127
 
@@ -106,28 +132,47 @@ module Rack
106
132
  self[:custom_timings][type] ||= []
107
133
  self[:custom_timings][type].push(timer)
108
134
 
109
- self[:custom_timing_stats][type] ||= {:count => 0, :duration => 0.0}
135
+ self[:custom_timing_stats][type] ||= { count: 0, duration: 0.0 }
110
136
  self[:custom_timing_stats][type][:count] += 1
111
137
  self[:custom_timing_stats][type][:duration] += elapsed_ms
112
138
 
113
- page[:custom_timing_stats][type] ||= {:count => 0, :duration => 0.0}
139
+ page[:custom_timing_stats][type] ||= { count: 0, duration: 0.0 }
114
140
  page[:custom_timing_stats][type][:count] += 1
115
141
  page[:custom_timing_stats][type][:duration] += elapsed_ms
116
142
  end
117
143
  end
118
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
+
119
165
  def record_time(milliseconds = nil)
120
- milliseconds ||= (Time.now - @start) * 1000
166
+ milliseconds ||= (Process.clock_gettime(Process::CLOCK_MONOTONIC) - @start) * 1000
121
167
  self[:duration_milliseconds] = milliseconds
122
168
  self[:is_trivial] = true if milliseconds < self[:trivial_duration_threshold_milliseconds]
123
- self[:duration_without_children_milliseconds] = milliseconds - @children_duration
124
-
125
- if @parent
126
- @parent.children_duration += milliseconds
127
- end
128
-
169
+ self[:duration_without_children_milliseconds] = milliseconds - self[:children].sum(&:duration_ms)
129
170
  end
130
171
 
172
+ def adjust_depth
173
+ self[:depth] = self.parent ? self.parent[:depth] + 1 : 0
174
+ self[:children].each(&:adjust_depth)
175
+ end
131
176
  end
132
177
  end
133
178
  end