scout_apm 2.1.14 → 2.1.15

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: faa58ca3a2800e8aade5ade08a31d2c5db1e5a7d
4
- data.tar.gz: ceb42f746d3b72eb84610522eeee9247fd68cf81
3
+ metadata.gz: 2432d4b3a94011da560df94d67bfda1c0e58ed80
4
+ data.tar.gz: bafd8c126d8ea5e1329d7d9e119ce24cacea9af3
5
5
  SHA512:
6
- metadata.gz: b692efc8a697ffb71657d9c2493c1c4fd2bc003f5970155339654679832d6b7fd9da95d26bc0d2b46c153980da697532376556556ff8b2ba10140cc5fb398180
7
- data.tar.gz: 3ae789a0000e598a39bce414c4955d13fa354e38f716d53ddb6041160a89fc9a500c3f1d1144095e0487722eedc2951cb6681e8db4f8917bf162adbc52112b51
6
+ metadata.gz: 766e322ec27e6ba797cb7756e67975e2b4c41b3cc5c1ad957054445e798b05c75878e28d46641f6d6fc5ff86dfb8351d4500a5264f5aa7d3f9f778715b8c7517
7
+ data.tar.gz: 62a7ce7ee17fcfed6011fba1b62fb40795ba7c5b31fc6a7bd2d20f17c9af2c84ffe3c9c3bd56a513b6561f8cb3170fe078c76e1dd6892df22a03d1facc0555e0
@@ -1,3 +1,7 @@
1
+ # 2.1.15
2
+
3
+ * Limit memory usage for very long running requests.
4
+
1
5
  # 2.1.14
2
6
 
3
7
  * Add TrackedRequest#ignore_request! to entirely ignore and stop capturing a
@@ -27,6 +27,8 @@ require 'scout_apm/version'
27
27
 
28
28
  require 'scout_apm/tracked_request'
29
29
  require 'scout_apm/layer'
30
+ require 'scout_apm/limited_layer'
31
+ require 'scout_apm/layer_children_set'
30
32
  require 'scout_apm/request_manager'
31
33
  require 'scout_apm/call_set'
32
34
 
@@ -13,13 +13,17 @@ module ScoutApm
13
13
  # instrumentation for an example of how this is useful
14
14
  attr_accessor :name
15
15
 
16
- # An array of children layers, in call order.
16
+ # An array of children layers
17
17
  # For instance, if we are in a middleware, there will likely be only a single
18
18
  # child, which is another middleware. In a Controller, we may have a handful
19
19
  # of children: [ActiveRecord, ActiveRecord, View, HTTP Call].
20
20
  #
21
21
  # This useful to get actual time spent in this layer vs. children time
22
- attr_reader :children
22
+ #
23
+ # TODO: Check callers for compatibility w/ nil to avoid making an empty array
24
+ def children
25
+ @children || LayerChildrenSet.new
26
+ end
23
27
 
24
28
  # Time objects recording the start & stop times of this layer
25
29
  attr_reader :start_time, :stop_time
@@ -38,6 +42,8 @@ module ScoutApm
38
42
  # Known Keys:
39
43
  # :record_count - The number of rows returned by an AR query (From notification instantiation.active_record)
40
44
  # :class_name - The ActiveRecord class name (From notification instantiation.active_record)
45
+ #
46
+ # If no annotations are ever set, this will return nil
41
47
  attr_reader :annotations
42
48
 
43
49
  BACKTRACE_CALLER_LIMIT = 50 # maximum number of lines to send thru for backtrace analysis
@@ -45,15 +51,18 @@ module ScoutApm
45
51
  def initialize(type, name, start_time = Time.now)
46
52
  @type = type
47
53
  @name = name
48
- @annotations = {}
49
54
  @start_time = start_time
50
55
  @allocations_start = ScoutApm::Instruments::Allocations.count
51
56
  @allocations_stop = 0
52
- @children = [] # In order of calls
57
+
58
+ # initialize these only on first use
59
+ @children = nil
60
+ @annotations = nil
53
61
  @desc = nil
54
62
  end
55
63
 
56
64
  def add_child(child)
65
+ @children ||= LayerChildrenSet.new
57
66
  @children << child
58
67
  end
59
68
 
@@ -72,6 +81,7 @@ module ScoutApm
72
81
 
73
82
  # This data is internal to ScoutApm, to add custom information, use the Context api.
74
83
  def annotate_layer(hsh)
84
+ @annotations ||= {}
75
85
  @annotations.merge!(hsh)
76
86
  end
77
87
 
@@ -144,6 +154,7 @@ module ScoutApm
144
154
  map { |child| child.total_call_time }.
145
155
  inject(0) { |sum, time| sum + time }
146
156
  end
157
+ private :child_time
147
158
 
148
159
  ######################################
149
160
  # Allocation Calculations
@@ -169,5 +180,6 @@ module ScoutApm
169
180
  map { |child| child.total_allocations }.
170
181
  inject(0) { |sum, obj| sum + obj }
171
182
  end
183
+ private :child_allocations
172
184
  end
173
185
  end
@@ -0,0 +1,72 @@
1
+ module ScoutApm
2
+ # A set of children records for any given Layer. This implements some
3
+ # rate-limiting logic.
4
+ #
5
+ # We store the first `unique_cutoff` count of each layer type. So if cutoff
6
+ # is 1000, we'd store 1000 HTTP layers, and 1000 ActiveRecord calls, and 1000
7
+ # of each other layer type. After that, make a LimitedLayer object and store
8
+ # only aggregate counts and times of future layers of that type. (So the
9
+ # 1001st an onward of ActiveRecord would get only aggregate times, and
10
+ # counts, without any detail about the SQL called)
11
+ #
12
+ # When the set of children is small, keep them unique
13
+ # When the set of children gets large enough, stop keeping details
14
+ #
15
+ # The next optimization, which is not yet implemented:
16
+ # when the set of children gets larger, attempt to merge them without data loss
17
+ class LayerChildrenSet
18
+ include Enumerable
19
+
20
+ # By default, how many unique children of a type do we store before
21
+ # flipping over to storing only aggregate info.
22
+ DEFAULT_UNIQUE_CUTOFF = 2000
23
+ attr_reader :unique_cutoff
24
+
25
+ # The Set of children objects
26
+ attr_reader :children
27
+ private :children
28
+
29
+ def initialize(unique_cutoff = DEFAULT_UNIQUE_CUTOFF)
30
+ @children = Hash.new { |hash, key| hash[key] = Set.new }
31
+ @limited_layers = nil # populated when needed
32
+ @unique_cutoff = unique_cutoff
33
+ end
34
+
35
+ # Add a new layer into this set
36
+ # Only add completed layers - otherwise this will collect up incorrect info
37
+ # into the created LimitedLayer, since it will "freeze" any current data for
38
+ # total_call_time and similar methods.
39
+ def <<(child)
40
+ metric_type = child.type
41
+ set = children[metric_type]
42
+
43
+ if set.size >= unique_cutoff
44
+ # find limited_layer
45
+ @limited_layers || init_limited_layers
46
+ @limited_layers[metric_type].absorb(child)
47
+ else
48
+ # we have space just add it
49
+ set << child
50
+ end
51
+ end
52
+
53
+ def each
54
+ children.each do |_type, set|
55
+ set.each do |child_layer|
56
+ yield child_layer
57
+ end
58
+ end
59
+
60
+ if @limited_layers
61
+ @limited_layers.each do |_type, limited_layer|
62
+ yield limited_layer
63
+ end
64
+ end
65
+ end
66
+
67
+ # hold off initializing this until we know we need it
68
+ def init_limited_layers
69
+ @limited_layers ||= Hash.new { |hash, key| hash[key] = LimitedLayer.new(key) }
70
+ end
71
+ end
72
+ end
@@ -177,7 +177,7 @@ module ScoutApm
177
177
  meta_options = make_meta_options(layer)
178
178
 
179
179
  meta = MetricMeta.new(layer.legacy_metric_name, meta_options)
180
- meta.extra.merge!(layer.annotations)
180
+ meta.extra.merge!(layer.annotations) if layer.annotations
181
181
 
182
182
  store_backtrace(layer, meta)
183
183
 
@@ -191,6 +191,11 @@ module ScoutApm
191
191
  # allocations
192
192
  stat = allocation_metric_hash[meta]
193
193
  stat.update!(layer.total_allocations, layer.total_exclusive_allocations)
194
+
195
+ if LimitedLayer === layer
196
+ metric_hash[meta].call_count = layer.count
197
+ allocation_metric_hash[meta].call_count = layer.count
198
+ end
194
199
  end
195
200
 
196
201
  # Merged Metric - no specifics, just sum up by type (ActiveRecord, View, HTTP, etc)
@@ -218,7 +223,8 @@ module ScoutApm
218
223
  # an example. We start capturing before we know if a query is cached
219
224
  # or not, and want to skip any cached queries.
220
225
  def skip_layer?(layer)
221
- return true if layer.annotations[:ignorable]
226
+ return false if layer.annotations.nil?
227
+ return true if layer.annotations[:ignorable]
222
228
  end
223
229
  end
224
230
  end
@@ -57,7 +57,7 @@ module ScoutApm
57
57
  walker.walk do |layer|
58
58
  next if layer == job_layer
59
59
  next if layer == queue_layer
60
- next if layer.annotations[:ignorable]
60
+ next if skip_layer?(layer)
61
61
 
62
62
  # we don't need to use the full metric name for scoped metrics as we
63
63
  # only display metrics aggregrated by type, just use "ActiveRecord"
@@ -21,7 +21,7 @@ module ScoutApm
21
21
  metric_hash = Hash.new
22
22
 
23
23
  walker.walk do |layer|
24
- next if layer.annotations[:ignorable]
24
+ next if skip_layer?(layer)
25
25
 
26
26
  meta_options = if layer == scope_layer # We don't scope the controller under itself
27
27
  {}
@@ -0,0 +1,122 @@
1
+ module ScoutApm
2
+ # A LimitedLayer is a lossy-compression approach to fall back on once we max out
3
+ # the number of detailed layer objects we store. See LayerChildrenSet for the
4
+ # logic on when that change over happens
5
+ #
6
+ # QUESTION: What do we do if we attempt to merge an item that has children?
7
+ class LimitedLayer
8
+ attr_reader :type
9
+
10
+ def initialize(type)
11
+ @type = type
12
+
13
+ @total_call_time = 0
14
+ @total_exclusive_time = 0
15
+ @total_allocations = 0
16
+ @total_exclusive_allocations = 0
17
+ @total_layers = 0
18
+ end
19
+
20
+ def absorb(layer)
21
+ @total_layers += 1
22
+
23
+ @total_call_time += layer.total_call_time
24
+ @total_exclusive_time += layer.total_exclusive_time
25
+
26
+ @total_allocations += layer.total_allocations
27
+ @total_exclusive_allocations += layer.total_exclusive_allocations
28
+ end
29
+
30
+ def total_call_time
31
+ @total_call_time
32
+ end
33
+
34
+ def total_exclusive_time
35
+ @total_exclusive_time
36
+ end
37
+
38
+ def total_allocations
39
+ @total_allocations
40
+ end
41
+
42
+ def total_exclusive_allocations
43
+ @total_exclusive_allocations
44
+ end
45
+
46
+ def count
47
+ @total_layers
48
+ end
49
+
50
+ # This is the old style name. This function is used for now, but should be
51
+ # removed, and the new type & name split should be enforced through the
52
+ # app.
53
+ def legacy_metric_name
54
+ "#{type}/Limited"
55
+ end
56
+
57
+ def children
58
+ Set.new
59
+ end
60
+
61
+ def annotations
62
+ nil
63
+ end
64
+
65
+ def to_s
66
+ "<LimitedLayer type=#{type} count=#{count}>"
67
+ end
68
+
69
+ ######################################################
70
+ # Stub out some methods with static default values #
71
+ ######################################################
72
+ def subscopable?
73
+ false
74
+ end
75
+
76
+ def desc
77
+ nil
78
+ end
79
+
80
+ def backtrace
81
+ nil
82
+ end
83
+
84
+
85
+ #######################################################################
86
+ # Many methods don't make any sense on a limited layer. Raise errors #
87
+ # aggressively for now to detect mistaken calls #
88
+ #######################################################################
89
+
90
+ def add_child
91
+ raise "Should never call add_child on a limited_layer"
92
+ end
93
+
94
+ def record_stop_time!(*)
95
+ raise "Should never call record_stop_time! on a limited_layer"
96
+ end
97
+
98
+ def record_allocations!
99
+ raise "Should never call record_allocations! on a limited_layer"
100
+ end
101
+
102
+ def desc=(*)
103
+ raise "Should never call desc on a limited_layer"
104
+ end
105
+
106
+ def annotate_layer(*)
107
+ raise "Should never call annotate_layer on a limited_layer"
108
+ end
109
+
110
+ def subscopable!
111
+ raise "Should never call subscopable! on a limited_layer"
112
+ end
113
+
114
+ def capture_backtrace!
115
+ raise "Should never call capture_backtrace on a limited_layer"
116
+ end
117
+
118
+ def caller_array
119
+ raise "Should never call caller_array on a limited_layer"
120
+ end
121
+ end
122
+ end
@@ -62,7 +62,6 @@ module ScoutApm
62
62
  return ignoring_start_layer if ignoring_request?
63
63
 
64
64
  start_request(layer) unless @root_layer
65
- @layers[-1].add_child(layer) if @layers.any?
66
65
  @layers.push(layer)
67
66
  end
68
67
 
@@ -86,6 +85,8 @@ module ScoutApm
86
85
  layer.record_stop_time!
87
86
  layer.record_allocations!
88
87
 
88
+ @layers[-1].add_child(layer) if @layers.any?
89
+
89
90
  # This must be called before checking if a backtrace should be collected as the call count influences our capture logic.
90
91
  # We call `#update_call_counts in stop layer to ensure the layer has a final desc. Layer#desc is updated during the AR instrumentation flow.
91
92
  update_call_counts!(layer)
@@ -1,4 +1,4 @@
1
1
  module ScoutApm
2
- VERSION = "2.1.14"
2
+ VERSION = "2.1.15"
3
3
  end
4
4
 
@@ -0,0 +1,88 @@
1
+ require 'test_helper'
2
+ require 'scout_apm/layer_children_set'
3
+
4
+ class LayerChildrenSetTest < Minitest::Test
5
+ SET = ScoutApm::LayerChildrenSet
6
+
7
+ def test_limit_default
8
+ assert_equal SET::DEFAULT_UNIQUE_CUTOFF, SET.new.unique_cutoff
9
+ end
10
+
11
+ # Add 5, make sure they're all in the children list we get back.
12
+ def test_add_layer_before_limit
13
+ s = SET.new(5)
14
+
15
+ 5.times do
16
+ s << make_layer("LayerType", "LayerName")
17
+ end
18
+
19
+ children = s.to_a
20
+ assert_equal 5, children.size
21
+
22
+ # Don't care about order
23
+ (0..4).each do |i|
24
+ assert children.include?(lookup_layer(i))
25
+ end
26
+ end
27
+
28
+ def test_add_layer_after_limit
29
+ s = SET.new(5)
30
+
31
+ 10.times do
32
+ s << make_layer("LayerType", "LayerName")
33
+ end
34
+
35
+ children = s.to_a
36
+ # 6 = 5 real ones + 1 merged.
37
+ assert_equal 6, children.size
38
+
39
+ # Don't care about order
40
+ (0..4).each do |i|
41
+ assert children.include?(lookup_layer(i))
42
+ end
43
+
44
+ # Don't care about order
45
+ (5..9).each do |i|
46
+ assert ! children.include?(lookup_layer(i))
47
+ end
48
+
49
+ limited_layer = children.last
50
+ assert_equal ScoutApm::LimitedLayer, limited_layer.class
51
+ assert_equal 5, limited_layer.count
52
+ end
53
+
54
+ def test_add_layer_with_different_type_after_limit
55
+ s = SET.new(5)
56
+
57
+ # Add 20 items
58
+ 10.times do
59
+ s << make_layer("LayerType", "LayerName")
60
+ s << make_layer("DifferentLayerType", "LayerName")
61
+ end
62
+
63
+ children = s.to_a
64
+
65
+ # Tyo types, so 2 distinct limitdlayer objects
66
+ limited_layers = children.select{ |l| ScoutApm::LimitedLayer === l }
67
+ assert_equal 2, limited_layers.length
68
+
69
+ # 5 unchanged children each for the two layer types, plus the 2 limitd when each overran their limit
70
+ assert_equal 12, children.length
71
+ limited_layers.each { |ml| assert_equal 5, ml.count }
72
+ end
73
+
74
+ #############
75
+ # Helpers #
76
+ #############
77
+
78
+ def make_layer(type, name)
79
+ @made_layers ||= []
80
+ l = ScoutApm::Layer.new(type, name)
81
+ @made_layers << l
82
+ l
83
+ end
84
+
85
+ def lookup_layer(i)
86
+ @made_layers[i]
87
+ end
88
+ end
@@ -0,0 +1,53 @@
1
+ require 'test_helper'
2
+ require 'ostruct'
3
+
4
+ class LimitedLayerTest < Minitest::Test
5
+
6
+ def test_counts_while_absorbing
7
+ ll = ScoutApm::LimitedLayer.new("ActiveRecord")
8
+ assert_equal 0, ll.count
9
+
10
+ ll.absorb faux_layer("ActiveRecord", "User#Find", 2, 1, 200, 100)
11
+ assert_equal 1, ll.count
12
+
13
+ ll.absorb faux_layer("ActiveRecord", "User#Find", 2, 1, 200, 100)
14
+ assert_equal 2, ll.count
15
+ end
16
+
17
+ def test_sums_values_while_absorbing
18
+ ll = ScoutApm::LimitedLayer.new("ActiveRecord")
19
+
20
+ ll.absorb faux_layer("ActiveRecord", "User#Find", 2, 1, 200, 100)
21
+ assert_equal 1, ll.total_exclusive_time
22
+ assert_equal 2, ll.total_call_time
23
+ assert_equal 100, ll.total_exclusive_allocations
24
+ assert_equal 200, ll.total_allocations
25
+
26
+
27
+ ll.absorb faux_layer("ActiveRecord", "User#Find", 4, 3, 400, 300)
28
+ assert_equal 4, ll.total_exclusive_time # 3 + 1
29
+ assert_equal 6, ll.total_call_time # 4 + 2
30
+ assert_equal 400, ll.total_exclusive_allocations # 300 + 100
31
+ assert_equal 600, ll.total_allocations # 400 + 200
32
+ end
33
+
34
+ def test_the_name
35
+ ll = ScoutApm::LimitedLayer.new("ActiveRecord")
36
+ assert_equal "ActiveRecord/Limited", ll.legacy_metric_name
37
+ end
38
+
39
+ #############
40
+ # Helpers #
41
+ #############
42
+
43
+ def faux_layer(type, name, tct, tet, a_tct, a_tet)
44
+ OpenStruct.new(
45
+ :type => type,
46
+ :name => name,
47
+ :total_call_time => tct,
48
+ :total_exclusive_time => tet,
49
+ :total_allocations => a_tct,
50
+ :total_exclusive_allocations => a_tet,
51
+ )
52
+ end
53
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scout_apm
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.14
4
+ version: 2.1.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - Derek Haynes
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2016-11-01 00:00:00.000000000 Z
12
+ date: 2016-11-10 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: minitest
@@ -180,6 +180,7 @@ files:
180
180
  - lib/scout_apm/layaway.rb
181
181
  - lib/scout_apm/layaway_file.rb
182
182
  - lib/scout_apm/layer.rb
183
+ - lib/scout_apm/layer_children_set.rb
183
184
  - lib/scout_apm/layer_converters/allocation_metric_converter.rb
184
185
  - lib/scout_apm/layer_converters/converter_base.rb
185
186
  - lib/scout_apm/layer_converters/depth_first_walker.rb
@@ -189,6 +190,7 @@ files:
189
190
  - lib/scout_apm/layer_converters/request_queue_time_converter.rb
190
191
  - lib/scout_apm/layer_converters/slow_job_converter.rb
191
192
  - lib/scout_apm/layer_converters/slow_request_converter.rb
193
+ - lib/scout_apm/limited_layer.rb
192
194
  - lib/scout_apm/metric_meta.rb
193
195
  - lib/scout_apm/metric_set.rb
194
196
  - lib/scout_apm/metric_stats.rb
@@ -251,6 +253,8 @@ files:
251
253
  - test/unit/instruments/net_http_test.rb
252
254
  - test/unit/instruments/percentile_sampler_test.rb
253
255
  - test/unit/layaway_test.rb
256
+ - test/unit/layer_children_set_test.rb
257
+ - test/unit/limited_layer_test.rb
254
258
  - test/unit/metric_set_test.rb
255
259
  - test/unit/scored_item_set_test.rb
256
260
  - test/unit/serializers/payload_serializer_test.rb
@@ -301,6 +305,8 @@ test_files:
301
305
  - test/unit/instruments/net_http_test.rb
302
306
  - test/unit/instruments/percentile_sampler_test.rb
303
307
  - test/unit/layaway_test.rb
308
+ - test/unit/layer_children_set_test.rb
309
+ - test/unit/limited_layer_test.rb
304
310
  - test/unit/metric_set_test.rb
305
311
  - test/unit/scored_item_set_test.rb
306
312
  - test/unit/serializers/payload_serializer_test.rb