scout_apm 3.0.0.pre3 → 3.0.0.pre4

Sign up to get free protection for your applications and to get access to all the features.
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