scout_apm 3.0.0.pre3 → 3.0.0.pre4

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: 3721ca109f1e1cb44b9b3f06ee32eda61f77a67d
4
- data.tar.gz: 19f4934a1cf694a233cc23f577fab0b10cffdcd9
3
+ metadata.gz: 65e71c8c9518a483f581e8c903ce36fa216fd964
4
+ data.tar.gz: 693bab7ca8a2189697b0802e2919ce9ebf6a81e8
5
5
  SHA512:
6
- metadata.gz: 447e27d5da749e8b58c71db8288c94f9e74cc1bd357282396ee7cfc3f93b8db63af23f79f4af35cbdf70c3d59715a9be2c1a43c12b4566335c234c11fe4b3c59
7
- data.tar.gz: 3f082fd12f47d360c91ba9d0f743e4628a19cf9009edff49ed0570923a8b6ece527dedc327b2d7cd339df4fad18ba77f8022a552cde20df2600b4fa7419c8d52
6
+ metadata.gz: a6050bfc0ea85e521707640550eee61cb147f395e7b6610abb1774faf2bdf2009557c93057e886c65f565192c7a46aa5b0ef54b315c8d8711f62d91bb5905157
7
+ data.tar.gz: d9a50a872883792a85e8cbe7987a4f9e2ace192b09b9474c388d42bb204869f326e8f46bedd41078d0505b1c7d3d0fc40a1dc9ab9fd243743e44e5af61eaa17e
data/CHANGELOG.markdown CHANGED
@@ -6,6 +6,28 @@
6
6
 
7
7
  * ScoutProf BETA
8
8
 
9
+ # 2.1.15
10
+
11
+ * Limit memory usage for very long running requests.
12
+
13
+ # 2.1.14
14
+
15
+ * Add TrackedRequest#ignore_request! to entirely ignore and stop capturing a
16
+ certain request. Use in your code by calling:
17
+ ScoutApm::RequestManager.lookup.ignore_request!
18
+
19
+ # 2.1.13
20
+
21
+ * Rework Delayed Job instrumentation to not interfere with other instruments.
22
+
23
+ # 2.1.12
24
+
25
+ * Revert 2.1.11's Delayed Job change - caused issues in a handful of environments
26
+
27
+ # 2.1.11
28
+
29
+ * Support alternate methods of launching Delayed Job
30
+
9
31
  # 2.1.10
10
32
 
11
33
  * Fix issue getting a default Application Name when it wasn't explicitly set
@@ -281,20 +281,13 @@ module ScoutApm
281
281
 
282
282
  # Loads the instrumention logic.
283
283
  def load_instruments
284
- if !background_job_missing?
285
- # case environment.background_job_name
286
- # when :delayed_job
287
- # install_instrument(ScoutApm::Instruments::DelayedJob)
288
- # end
289
- else
290
- case environment.framework
291
- when :rails then install_instrument(ScoutApm::Instruments::ActionControllerRails2)
292
- when :rails3_or_4 then
293
- install_instrument(ScoutApm::Instruments::ActionControllerRails3Rails4)
294
- install_instrument(ScoutApm::Instruments::MiddlewareSummary)
295
- install_instrument(ScoutApm::Instruments::RailsRouter)
296
- # when :sinatra then install_instrument(ScoutApm::Instruments::Sinatra)
297
- end
284
+ case environment.framework
285
+ when :rails then
286
+ install_instrument(ScoutApm::Instruments::ActionControllerRails2)
287
+ when :rails3_or_4 then
288
+ install_instrument(ScoutApm::Instruments::ActionControllerRails3Rails4)
289
+ install_instrument(ScoutApm::Instruments::MiddlewareSummary)
290
+ install_instrument(ScoutApm::Instruments::RailsRouter)
298
291
  end
299
292
 
300
293
  install_instrument(ScoutApm::Instruments::ActiveRecord)
@@ -8,7 +8,7 @@ module ScoutApm
8
8
  end
9
9
 
10
10
  def present?
11
- defined?(::Delayed::Job) && File.basename($PROGRAM_NAME).start_with?('delayed_job')
11
+ defined?(::Delayed::Job)
12
12
  end
13
13
 
14
14
  def forking?
@@ -50,6 +50,9 @@ module ScoutApm
50
50
  if ScoutApm::Agent.instance.config.value('dev_trace')
51
51
  if response.respond_to?(:body)
52
52
  req = ScoutApm::RequestManager.lookup
53
+
54
+ return [status, headers, response] if req.ignoring_request?
55
+
53
56
  slow_converter = LayerConverters::SlowRequestConverter.new(req)
54
57
  trace = slow_converter.call
55
58
  if trace
@@ -62,8 +62,10 @@ module ScoutApm
62
62
  layer = req.current_layer
63
63
  if layer && layer.type == "ActiveRecord"
64
64
  layer.annotate_layer(payload)
65
- else
65
+ elsif layer
66
66
  ScoutApm::Agent.instance.logger.debug("Expected layer type: ActiveRecord, got #{layer && layer.type}")
67
+ else
68
+ # noop, no layer at all. We're probably ignoring this req.
67
69
  end
68
70
  end
69
71
  end
@@ -22,12 +22,17 @@ module ScoutApm
22
22
  include ScoutApm::Tracer
23
23
 
24
24
  def request_with_scout_instruments(*args,&block)
25
- url = (@address + args.first.path.split('?').first)[0..99]
26
- self.class.instrument("HTTP", "request", :desc => url) do
25
+ self.class.instrument("HTTP", "request", :desc => request_scout_description(args.first)) do
27
26
  request_without_scout_instruments(*args, &block)
28
27
  end
29
28
  end
30
29
 
30
+ def request_scout_description(req)
31
+ path = req.path
32
+ path = path.path if path.respond_to?(:path)
33
+ (@address + path.split('?').first)[0..99]
34
+ end
35
+
31
36
  alias request_without_scout_instruments request
32
37
  alias request request_with_scout_instruments
33
38
  end
@@ -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
  # ScoutProf - trace_index is an index into the Stack structure in the C
@@ -59,11 +65,13 @@ module ScoutApm
59
65
  def initialize(type, name, start_time = Time.now)
60
66
  @type = type
61
67
  @name = name
62
- @annotations = {}
63
68
  @start_time = start_time
64
69
  @allocations_start = ScoutApm::Instruments::Allocations.count
65
70
  @allocations_stop = 0
66
- @children = [] # In order of calls
71
+
72
+ # initialize these only on first use
73
+ @children = nil
74
+ @annotations = nil
67
75
  @desc = nil
68
76
 
69
77
  @traces = ScoutApm::TraceSet.new
@@ -73,6 +81,7 @@ module ScoutApm
73
81
  end
74
82
 
75
83
  def add_child(child)
84
+ @children ||= LayerChildrenSet.new
76
85
  @children << child
77
86
  end
78
87
 
@@ -91,6 +100,7 @@ module ScoutApm
91
100
 
92
101
  # This data is internal to ScoutApm, to add custom information, use the Context api.
93
102
  def annotate_layer(hsh)
103
+ @annotations ||= {}
94
104
  @annotations.merge!(hsh)
95
105
  end
96
106
 
@@ -200,6 +210,7 @@ module ScoutApm
200
210
  map { |child| child.total_call_time }.
201
211
  inject(0) { |sum, time| sum + time }
202
212
  end
213
+ private :child_time
203
214
 
204
215
  ######################################
205
216
  # Allocation Calculations
@@ -225,5 +236,6 @@ module ScoutApm
225
236
  map { |child| child.total_allocations }.
226
237
  inject(0) { |sum, obj| sum + obj }
227
238
  end
239
+ private :child_allocations
228
240
  end
229
241
  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
 
@@ -187,13 +187,16 @@ module ScoutApm
187
187
  # Timing
188
188
  timing_stat = metric_hash[meta]
189
189
  timing_stat.update!(layer.total_call_time, layer.total_exclusive_time)
190
+ timing_stat.add_traces(layer.traces.as_json)
190
191
 
191
192
  # Allocations
192
193
  allocation_stat = allocation_metric_hash[meta]
193
194
  allocation_stat.update!(layer.total_allocations, layer.total_exclusive_allocations)
194
195
 
195
- # Attach Scoutprof Traces
196
- timing_stat.add_traces(layer.traces.as_json)
196
+ if LimitedLayer === layer
197
+ metric_hash[meta].call_count = layer.count
198
+ allocation_metric_hash[meta].call_count = layer.count
199
+ end
197
200
  end
198
201
 
199
202
  # Merged Metric - no specifics, just sum up by type (ActiveRecord, View, HTTP, etc)
@@ -221,7 +224,8 @@ module ScoutApm
221
224
  # an example. We start capturing before we know if a query is cached
222
225
  # or not, and want to skip any cached queries.
223
226
  def skip_layer?(layer)
224
- return true if layer.annotations[:ignorable]
227
+ return false if layer.annotations.nil?
228
+ return true if layer.annotations[:ignorable]
225
229
  end
226
230
 
227
231
  # Debug logging for scoutprof traces
@@ -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,129 @@
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
+ def add_traces(*)
85
+ # noop
86
+ end
87
+
88
+ def traces
89
+ ScoutApm::TraceSet.new
90
+ end
91
+
92
+ #######################################################################
93
+ # Many methods don't make any sense on a limited layer. Raise errors #
94
+ # aggressively for now to detect mistaken calls #
95
+ #######################################################################
96
+
97
+ def add_child
98
+ raise "Should never call add_child on a limited_layer"
99
+ end
100
+
101
+ def record_stop_time!(*)
102
+ raise "Should never call record_stop_time! on a limited_layer"
103
+ end
104
+
105
+ def record_allocations!
106
+ raise "Should never call record_allocations! on a limited_layer"
107
+ end
108
+
109
+ def desc=(*)
110
+ raise "Should never call desc on a limited_layer"
111
+ end
112
+
113
+ def annotate_layer(*)
114
+ raise "Should never call annotate_layer on a limited_layer"
115
+ end
116
+
117
+ def subscopable!
118
+ raise "Should never call subscopable! on a limited_layer"
119
+ end
120
+
121
+ def capture_backtrace!
122
+ raise "Should never call capture_backtrace on a limited_layer"
123
+ end
124
+
125
+ def caller_array
126
+ raise "Should never call caller_array on a limited_layer"
127
+ end
128
+ end
129
+ end
@@ -57,20 +57,21 @@ module ScoutApm
57
57
  end
58
58
 
59
59
  def start_layer(layer)
60
- if ignoring_children?
61
- return
62
- end
60
+ return if ignoring_children?
61
+
62
+ return ignoring_start_layer if ignoring_request?
63
63
 
64
64
  layer.start_sampling
65
65
 
66
66
  start_request(layer) unless @root_layer
67
- @layers[-1].add_child(layer) if @layers.any?
68
67
  @layers.push(layer)
69
68
  end
70
69
 
71
70
  def stop_layer
72
71
  return if ignoring_children?
73
72
 
73
+ return ignoring_stop_layer if ignoring_request?
74
+
74
75
  layer = @layers.pop
75
76
 
76
77
  # Safeguard against a mismatch in the layer tracking in an instrument.
@@ -87,6 +88,8 @@ module ScoutApm
87
88
  layer.record_stop_time!
88
89
  layer.record_allocations!
89
90
 
91
+ @layers[-1].add_child(layer) if @layers.any?
92
+
90
93
  # This must be called before checking if a backtrace should be collected as the call count influences our capture logic.
91
94
  # 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.
92
95
  update_call_counts!(layer)
@@ -115,6 +118,8 @@ module ScoutApm
115
118
 
116
119
  BACKTRACE_BLACKLIST = ["Controller", "Job"]
117
120
  def capture_backtrace?(layer)
121
+ return if ignoring_request?
122
+
118
123
  # Never capture backtraces for this kind of layer. The backtrace will
119
124
  # always be 100% framework code.
120
125
  return false if BACKTRACE_BLACKLIST.include?(layer.type)
@@ -236,6 +241,8 @@ module ScoutApm
236
241
  end
237
242
 
238
243
  def instant?
244
+ return false if ignoring_request?
245
+
239
246
  instant_key
240
247
  end
241
248
 
@@ -248,6 +255,8 @@ module ScoutApm
248
255
  def record!
249
256
  @recorded = true
250
257
 
258
+ return if ignoring_request?
259
+
251
260
  # Bail out early if the user asked us to ignore this uri
252
261
  return if ScoutApm::Agent.instance.ignored_uris.ignore?(annotations[:uri])
253
262
 
@@ -297,6 +306,8 @@ module ScoutApm
297
306
 
298
307
  # Only call this after the request is complete
299
308
  def unique_name
309
+ return nil if ignoring_request?
310
+
300
311
  @unique_name ||= begin
301
312
  scope_layer = LayerConverters::ConverterBase.new(self).scope_layer
302
313
  if scope_layer
@@ -311,6 +322,8 @@ module ScoutApm
311
322
  # Used to know when we should just create a new one (don't attempt to add
312
323
  # data to an already-recorded request). See RequestManager
313
324
  def recorded?
325
+ return ignoring_recorded? if ignoring_request?
326
+
314
327
  @recorded
315
328
  end
316
329
 
@@ -359,5 +372,48 @@ module ScoutApm
359
372
  def backtrace_threshold
360
373
  dev_trace ? 0.05 : 0.5 # the minimum threshold in seconds to record the backtrace for a metric.
361
374
  end
375
+
376
+ ################################################################################
377
+ # Ignoring the rest of a request
378
+ ################################################################################
379
+
380
+ # At any point in the request, calling code or instrumentation can call
381
+ # `ignore_request!` to immediately stop recording any information about new
382
+ # layers, and delete any existing layer info. This class will still exist,
383
+ # and respond to methods as normal, but `record!` won't be called, and no
384
+ # data will be recorded.
385
+
386
+ def ignore_request!
387
+ return if @ignoring_request
388
+
389
+ # Set instance variable
390
+ @ignoring_request = true
391
+
392
+ # Store data we'll need
393
+ @ignoring_depth = @layers.length
394
+
395
+ # Clear data
396
+ @layers = []
397
+ @root_layer = nil
398
+ @call_set = nil
399
+ @annotations = {}
400
+ @instant_key = nil
401
+ end
402
+
403
+ def ignoring_request?
404
+ @ignoring_request
405
+ end
406
+
407
+ def ignoring_start_layer
408
+ @ignoring_depth += 1
409
+ end
410
+
411
+ def ignoring_stop_layer
412
+ @ignoring_depth -= 1
413
+ end
414
+
415
+ def ignoring_recorded?
416
+ @ignoring_depth <= 0
417
+ end
362
418
  end
363
419
  end
@@ -1,3 +1,3 @@
1
1
  module ScoutApm
2
- VERSION = "3.0.0.pre3"
2
+ VERSION = "3.0.0.pre4"
3
3
  end
data/lib/scout_apm.rb CHANGED
@@ -28,6 +28,8 @@ require 'scout_apm/version'
28
28
 
29
29
  require 'scout_apm/tracked_request'
30
30
  require 'scout_apm/layer'
31
+ require 'scout_apm/limited_layer'
32
+ require 'scout_apm/layer_children_set'
31
33
  require 'scout_apm/request_manager'
32
34
  require 'scout_apm/call_set'
33
35
 
@@ -70,7 +72,6 @@ require 'scout_apm/instruments/mongoid'
70
72
  require 'scout_apm/instruments/redis'
71
73
  require 'scout_apm/instruments/influxdb'
72
74
  require 'scout_apm/instruments/elasticsearch'
73
- require 'scout_apm/instruments/delayed_job'
74
75
  require 'scout_apm/instruments/active_record'
75
76
  require 'scout_apm/instruments/action_controller_rails_2'
76
77
  require 'scout_apm/instruments/action_controller_rails_3_rails4'
data/scout_apm.gemspec CHANGED
@@ -28,4 +28,5 @@ Gem::Specification.new do |s|
28
28
  s.add_development_dependency "m"
29
29
  s.add_development_dependency "simplecov"
30
30
  s.add_development_dependency "rake-compiler"
31
+ s.add_development_dependency "addressable"
31
32
  end
@@ -0,0 +1,21 @@
1
+ require 'test_helper'
2
+
3
+ require 'scout_apm/instruments/net_http'
4
+
5
+ require 'addressable'
6
+
7
+ class NetHttpTest < Minitest::Test
8
+ def setup
9
+ ScoutApm::Instruments::NetHttp.new.install
10
+ end
11
+
12
+ def test_request_scout_description_for_uri
13
+ req = Net::HTTP::Get.new(URI('http://example.org/here'))
14
+ assert_equal '/here', Net::HTTP.new('').request_scout_description(req)
15
+ end
16
+
17
+ def test_request_scout_description_for_addressable
18
+ req = Net::HTTP::Get.new(Addressable::URI.parse('http://example.org/here'))
19
+ assert_equal '/here', Net::HTTP.new('').request_scout_description(req)
20
+ end
21
+ end
@@ -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: 3.0.0.pre3
4
+ version: 3.0.0.pre4
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-09-22 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
@@ -95,6 +95,20 @@ dependencies:
95
95
  - - ">="
96
96
  - !ruby/object:Gem::Version
97
97
  version: '0'
98
+ - !ruby/object:Gem::Dependency
99
+ name: addressable
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ type: :development
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
98
112
  description: Monitors Ruby apps and reports detailed metrics on performance to Scout.
99
113
  email:
100
114
  - support@scoutapp.com
@@ -151,7 +165,6 @@ files:
151
165
  - lib/scout_apm/instruments/action_controller_rails_2.rb
152
166
  - lib/scout_apm/instruments/action_controller_rails_3_rails4.rb
153
167
  - lib/scout_apm/instruments/active_record.rb
154
- - lib/scout_apm/instruments/delayed_job.rb
155
168
  - lib/scout_apm/instruments/elasticsearch.rb
156
169
  - lib/scout_apm/instruments/grape.rb
157
170
  - lib/scout_apm/instruments/http_client.rb
@@ -171,6 +184,7 @@ files:
171
184
  - lib/scout_apm/layaway.rb
172
185
  - lib/scout_apm/layaway_file.rb
173
186
  - lib/scout_apm/layer.rb
187
+ - lib/scout_apm/layer_children_set.rb
174
188
  - lib/scout_apm/layer_converters/allocation_metric_converter.rb
175
189
  - lib/scout_apm/layer_converters/converter_base.rb
176
190
  - lib/scout_apm/layer_converters/depth_first_walker.rb
@@ -180,6 +194,7 @@ files:
180
194
  - lib/scout_apm/layer_converters/request_queue_time_converter.rb
181
195
  - lib/scout_apm/layer_converters/slow_job_converter.rb
182
196
  - lib/scout_apm/layer_converters/slow_request_converter.rb
197
+ - lib/scout_apm/limited_layer.rb
183
198
  - lib/scout_apm/metric_meta.rb
184
199
  - lib/scout_apm/metric_set.rb
185
200
  - lib/scout_apm/metric_stats.rb
@@ -241,8 +256,11 @@ files:
241
256
  - test/unit/histogram_test.rb
242
257
  - test/unit/ignored_uris_test.rb
243
258
  - test/unit/instruments/active_record_instruments_test.rb
259
+ - test/unit/instruments/net_http_test.rb
244
260
  - test/unit/instruments/percentile_sampler_test.rb
245
261
  - test/unit/layaway_test.rb
262
+ - test/unit/layer_children_set_test.rb
263
+ - test/unit/limited_layer_test.rb
246
264
  - test/unit/metric_set_test.rb
247
265
  - test/unit/scored_item_set_test.rb
248
266
  - test/unit/serializers/payload_serializer_test.rb
@@ -291,8 +309,11 @@ test_files:
291
309
  - test/unit/histogram_test.rb
292
310
  - test/unit/ignored_uris_test.rb
293
311
  - test/unit/instruments/active_record_instruments_test.rb
312
+ - test/unit/instruments/net_http_test.rb
294
313
  - test/unit/instruments/percentile_sampler_test.rb
295
314
  - test/unit/layaway_test.rb
315
+ - test/unit/layer_children_set_test.rb
316
+ - test/unit/limited_layer_test.rb
296
317
  - test/unit/metric_set_test.rb
297
318
  - test/unit/scored_item_set_test.rb
298
319
  - test/unit/serializers/payload_serializer_test.rb
@@ -1,57 +0,0 @@
1
- module ScoutApm
2
- module Instruments
3
- class DelayedJob
4
- attr_reader :logger
5
-
6
- def initialize(logger=ScoutApm::Agent.instance.logger)
7
- @logger = logger
8
- @installed = false
9
- end
10
-
11
- def installed?
12
- @installed
13
- end
14
-
15
- def install
16
- @installed = true
17
- if defined?(::Delayed::Worker)
18
- ::Delayed::Worker.class_eval do
19
- include ScoutApm::Tracer
20
- include ScoutApm::Instruments::DelayedJobInstruments
21
- alias run_without_scout_instruments run
22
- alias run run_with_scout_instruments
23
- end
24
- end
25
- end
26
- end
27
-
28
- module DelayedJobInstruments
29
- def run_with_scout_instruments(job)
30
- scout_method_name = method_from_handler(job.handler)
31
- queue = job.queue
32
- latency = (Time.now.to_f - job.created_at.to_f) * 1000
33
-
34
- ScoutApm::Agent.instance.store.track_one!("Queue", queue, 0, {:extra_metrics => {:latency => latency}})
35
- req = ScoutApm::RequestManager.lookup
36
- req.job!
37
- req.start_layer( ScoutApm::Layer.new("Job", scout_method_name) )
38
-
39
- begin
40
- run_without_scout_instruments(job)
41
- rescue
42
- req.error!
43
- raise
44
- ensure
45
- req.stop_layer
46
- end
47
- end
48
-
49
- def method_from_handler(handler)
50
- job_handler = YAML.load(handler)
51
- klass = job_handler.object.name
52
- method = job_handler.method_name
53
- "#{klass}##{method}"
54
- end
55
- end
56
- end
57
- end