scout_apm 2.4.10 → 2.4.11.pre

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: efa85b14b9a37dd4f890574b3e8a47a2023c1979
4
- data.tar.gz: 67257b0a6bab1a0a24f1a4d3f4cea803e7e31456
3
+ metadata.gz: 1686beb2285cb415ac0f73e4dfa65524b85a369d
4
+ data.tar.gz: f56d09891f9b899721c711f43dc48e06160073b1
5
5
  SHA512:
6
- metadata.gz: 47bcb3a2071df2f585c60837fac4c860159398ac8500d036875f8387aa02bea2c1f4e8ba4790322fb56ca58c85ffef482fec49c9fdb3f4030dd3ddcdff503ee5
7
- data.tar.gz: ed8061983b9e6e4f2df133ce106500401562010fa10fc670fbbcd2de9f8aeebe442d95c7b7afee6469ccb436cbdae5de29a63dd529ad274174a76ad5d0e98615
6
+ metadata.gz: 3af376cdc73869c73775ca02aaad1817fa735a7b2c73521fe2015f0584c5085e50f7ec94c40bc402363c691e70d98c6cfbc4d0887ff1b0cb7082efeae1bc2578
7
+ data.tar.gz: 6ace83f9f30fd8bfeb48875e6a58c508f2cc65f1fafd36e8fc1e6421c6556ddc096a27396dc2546ae623af9765586da976da64b6cec9654c29c71feea0927bd6
@@ -1,3 +1,7 @@
1
+ # 2.4.11
2
+
3
+ * Adds transaction + periodic reporting callback extension support
4
+
1
5
  # 2.4.10
2
6
 
3
7
  * Improve ActiveRecord instrumentation across Rails 3.2+, and adding support
@@ -172,6 +172,9 @@ require 'scout_apm/agent/exit_handler'
172
172
  require 'scout_apm/tasks/doctor'
173
173
  require 'scout_apm/tasks/support'
174
174
 
175
+ require 'scout_apm/extensions/config'
176
+ require 'scout_apm/extensions/transaction_callback_payload'
177
+
175
178
  if defined?(Rails) && defined?(Rails::VERSION) && defined?(Rails::VERSION::MAJOR) && Rails::VERSION::MAJOR >= 3 && defined?(Rails::Railtie)
176
179
  module ScoutApm
177
180
  class Railtie < Rails::Railtie
@@ -1,5 +1,8 @@
1
1
  module ScoutApm
2
2
  class AgentContext
3
+
4
+ attr_accessor :extensions
5
+
3
6
  # Initially start up without attempting to load a configuration file. We
4
7
  # need to be able to lookup configuration options like "application_root"
5
8
  # which would then in turn influence where the yaml configuration file is
@@ -9,6 +12,7 @@ module ScoutApm
9
12
  def initialize()
10
13
  @logger = LoggerFactory.build_minimal_logger
11
14
  @process_start_time = Time.now
15
+ @extensions = ScoutApm::Extensions::Config.new(self)
12
16
  end
13
17
 
14
18
  def marshal_dump
@@ -23,6 +23,15 @@ module ScoutApm
23
23
  @extra.merge({:user => @user})
24
24
  end
25
25
 
26
+ def to_flat_hash
27
+ h = to_hash
28
+ user = h.delete(:user)
29
+ if user
30
+ user.each { |k,v| h["user_#{k}"] = v}
31
+ end
32
+ h
33
+ end
34
+
26
35
  def self.current
27
36
  RequestManager.lookup.context
28
37
  end
@@ -0,0 +1,87 @@
1
+ module ScoutApm
2
+ module Extensions
3
+ # !!! Extensions are a 0.x level API and breakage is expected as the API is refined.
4
+ # Extensions fan out data collected by the agent to additional services.
5
+ class Config
6
+ attr_reader :agent_context
7
+ attr_accessor :transaction_callbacks
8
+ attr_accessor :periodic_callbacks
9
+
10
+ # Adds a new callback that runs after a transaction completes.
11
+ # These run inline during the request and thus should add minimal overhead.
12
+ # For example, a transaction callback should NOT make inline HTTP calls to outside services.
13
+ # +callback+ must be an object that respond to a +call(payload)+ method.
14
+ #
15
+ # Example:
16
+ # ScoutApm::Extensions::Config.add_transaction_callback(Proc.new { |payload| puts "Duration: #{payload.duration_ms}" })
17
+ #
18
+ # +payload+ is a +ScoutApm::Extensions::TransactionCallbackPayload+ object.
19
+ def self.add_transaction_callback(callback)
20
+ agent_context.extensions.transaction_callbacks << callback
21
+ end
22
+
23
+ # Adds a callback that runs when the per-minute report data is sent to Scout.
24
+ # These run in a background thread so external HTTP calls are OK.
25
+ # +callback+ must be an object that responds to a +call(reporting_period, metadata)+ method.
26
+ #
27
+ # Example:
28
+ # ScoutApm::Extensions::Config.add_periodic_callback(Proc.new { |reporting_period, metadata| ... })
29
+ def self.add_periodic_callback(callback)
30
+ agent_context.extensions.periodic_callbacks << callback
31
+ end
32
+
33
+ def initialize(agent_context)
34
+ @agent_context = agent_context
35
+ @transaction_callbacks = []
36
+ @periodic_callbacks = []
37
+ end
38
+
39
+ # Runs each reporting period callback.
40
+ # Each callback runs inside a begin/rescue block so a broken callback doesn't prevent other
41
+ # callbacks from executing or reporting data from being sent.
42
+ def run_periodic_callbacks(reporting_period, metadata)
43
+ return unless periodic_callbacks.any?
44
+
45
+ periodic_callbacks.each do |callback|
46
+ begin
47
+ callback.call(reporting_period, metadata)
48
+ rescue => e
49
+ logger.warn "Error running reporting callback extension=#{callback}"
50
+ logger.info e.message
51
+ logger.debug e.backtrace
52
+ end
53
+ end
54
+ end
55
+
56
+ # Runs each transaction callback.
57
+ # Each callback runs inside a begin/rescue block so a broken callback doesn't prevent other
58
+ # callbacks from executing or the transaction from being recorded.
59
+ def run_transaction_callbacks(converter_results, context, scope_layer)
60
+ # It looks like layer_finder.scope = nil when a Sidekiq job is retried
61
+ return unless scope_layer
62
+ return unless transaction_callbacks.any?
63
+
64
+ payload = ScoutApm::Extensions::TransactionCallbackPayload.new(agent_context,converter_results,context,scope_layer)
65
+
66
+ transaction_callbacks.each do |callback|
67
+ begin
68
+ callback.call(payload)
69
+ rescue => e
70
+ logger.warn "Error running transaction callback extension=#{callback}"
71
+ logger.info e.message
72
+ logger.debug e.backtrace
73
+ end
74
+ end
75
+ end
76
+
77
+ def self.agent_context
78
+ ScoutApm::Agent.instance.context
79
+ end
80
+
81
+ def logger
82
+ agent_context.logger
83
+ end
84
+
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,74 @@
1
+ module ScoutApm
2
+ module Extensions
3
+ # A +TransactionCallbackPayload+ is passed to each Transaction callback's +call+ method.
4
+ # It encapsulates the data about a specific transaction.
5
+ class TransactionCallbackPayload
6
+ # A Hash that stores the output of each layer converter by name. See the naming conventions in +TrackedRequest+.
7
+ attr_accessor :converter_results
8
+
9
+ def initialize(agent_context,converter_results,context,scope_layer)
10
+ @agent_context = agent_context
11
+ @converter_results = converter_results
12
+ @context = context
13
+ @scope_layer = scope_layer
14
+ end
15
+
16
+ # A flat hash of the context associated w/this transaction (ie user ip and another other data added to context).
17
+ def context
18
+ @context.to_flat_hash
19
+ end
20
+
21
+ # The total duration of the transaction
22
+ def duration_ms
23
+ @scope_layer.total_call_time*1000 # ms
24
+ end
25
+
26
+ # The time in queue of the transaction in ms. If not present, +nil+ is returned as this is unknown.
27
+ def queue_time_ms
28
+ # Controller logic
29
+ if converter_results[:queue_time] && converter_results[:queue].any?
30
+ converter_results[:queue_time].values.first.total_call_time*1000 # ms
31
+ # Job logic
32
+ elsif converter_results[:job]
33
+ stat = converter_results[:job].metric_set.metrics[ScoutApm::MetricMeta.new("Latency/all", :scope => transaction_name)]
34
+ stat ? stat.total_call_time*1000 : nil
35
+ else
36
+ nil
37
+ end
38
+ end
39
+
40
+ def hostname
41
+ @agent_context.environment.hostname
42
+ end
43
+
44
+ def app_name
45
+ @agent_context.config.value('name')
46
+ end
47
+
48
+ # Returns +true+ if the transaction raised an exception.
49
+ def error?
50
+ converter_results[:errors] && converter_results[:errors].any?
51
+ end
52
+
53
+ def transation_type
54
+ @scope_layer.type
55
+ end
56
+
57
+ def transaction_name
58
+ @scope_layer.legacy_metric_name
59
+ end
60
+
61
+ # Web/Job are more language-agnostic names for controller/job. For example, Python Django does not have controllers.
62
+ def transaction_type_slug
63
+ case transation_type
64
+ when 'Controller'
65
+ 'web'
66
+ when 'Job'
67
+ 'job'
68
+ else
69
+ 'transaction'
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -8,8 +8,10 @@ module ScoutApm
8
8
  meta = MetricMeta.new("ObjectAllocations", {:scope => scope_layer.legacy_metric_name})
9
9
  stat = MetricStats.new
10
10
  stat.update!(root_layer.total_allocations)
11
+ metrics = { meta => stat }
11
12
 
12
- @store.track!({ meta => stat })
13
+ @store.track!(metrics)
14
+ nil # not returning anything in the layer results ... not used
13
15
  end
14
16
  end
15
17
  end
@@ -32,6 +32,8 @@ module ScoutApm
32
32
  # only due to 1 http request)
33
33
  @db_query_metric_set.increment_transaction_count!
34
34
  @store.track_db_query_metrics!(@db_query_metric_set)
35
+
36
+ nil # not returning anything in the layer results ... not used
35
37
  end
36
38
 
37
39
  def skip_layer?(layer)
@@ -10,8 +10,10 @@ module ScoutApm
10
10
  meta = MetricMeta.new("Errors/#{scope_layer.legacy_metric_name}", {})
11
11
  stat = MetricStats.new
12
12
  stat.update!(1)
13
-
14
- @store.track!({ meta => stat })
13
+ metrics = { meta => stat }
14
+
15
+ @store.track!(metrics)
16
+ metrics # this result must be returned so it can be accessed by transaction callback extensions
15
17
  end
16
18
  end
17
19
  end
@@ -8,6 +8,7 @@ module ScoutApm
8
8
  context.request_histograms_by_time[@store.current_timestamp].
9
9
  add(request.unique_name, root_layer.total_call_time)
10
10
  end
11
+ nil # not returning anything in the layer results ... not used
11
12
  end
12
13
  end
13
14
  end
@@ -55,6 +55,7 @@ module ScoutApm
55
55
  )
56
56
 
57
57
  @store.track_job!(record)
58
+ record # this result must be returned so it can be accessed by transaction callback extensions
58
59
  end
59
60
 
60
61
  # This isn't stored as a specific layer, so grabbing it doesn't use the
@@ -38,6 +38,7 @@ module ScoutApm
38
38
 
39
39
  def record!
40
40
  @store.track!(@metrics)
41
+ @metrics # this result must be returned so it can be accessed by transaction callback extensions
41
42
  end
42
43
  end
43
44
  end
@@ -28,8 +28,10 @@ module ScoutApm
28
28
  meta = MetricMeta.new("QueueTime/Request", {:scope => scope_layer.legacy_metric_name})
29
29
  stat = MetricStats.new(true)
30
30
  stat.update!(queue_time)
31
-
32
- @store.track!({ meta => stat })
31
+ metrics = { meta => stat }
32
+
33
+ @store.track!(metrics)
34
+ metrics # this result must be returned so it can be accessed by transaction callback extensions
33
35
  end
34
36
 
35
37
  private
@@ -15,6 +15,7 @@ module ScoutApm
15
15
  # Let the store know we're here, and if it wants our data, it will call
16
16
  # back into #call
17
17
  @store.track_slow_job!(self)
18
+ nil # not returning anything in the layer results ... not used
18
19
  end
19
20
 
20
21
  #####################
@@ -11,6 +11,7 @@ module ScoutApm
11
11
  # Let the store know we're here, and if it wants our data, it will call
12
12
  # back into #call
13
13
  @store.track_slow_transaction!(self)
14
+ nil # not returning anything in the layer results ... not used
14
15
  end
15
16
 
16
17
  #####################
@@ -48,7 +48,9 @@ module ScoutApm
48
48
  begin
49
49
  merged = rps.inject { |memo, rp| memo.merge(rp) }
50
50
  logger.debug("Merged #{rps.length} reporting periods, delivering")
51
- deliver_period(merged)
51
+ metadata = metadata(merged)
52
+ deliver_period(merged,metadata)
53
+ context.extensions.run_periodic_callbacks(merged, metadata)
52
54
  true
53
55
  rescue => e
54
56
  logger.debug("Error merging reporting periods #{e.message}")
@@ -63,15 +65,8 @@ module ScoutApm
63
65
  end
64
66
  end
65
67
 
66
- def deliver_period(reporting_period)
67
- metrics = reporting_period.metrics_payload
68
- slow_transactions = reporting_period.slow_transactions_payload
69
- jobs = reporting_period.jobs
70
- slow_jobs = reporting_period.slow_jobs_payload
71
- histograms = reporting_period.histograms
72
- db_query_metrics = reporting_period.db_query_metrics_payload
73
-
74
- metadata = {
68
+ def metadata(reporting_period)
69
+ {
75
70
  :app_root => context.environment.root.to_s,
76
71
  :unique_id => ScoutApm::Utils::UniqueId.simple,
77
72
  :agent_version => ScoutApm::VERSION,
@@ -79,6 +74,15 @@ module ScoutApm
79
74
  :agent_pid => Process.pid,
80
75
  :platform => "ruby",
81
76
  }
77
+ end
78
+
79
+ def deliver_period(reporting_period,metadata)
80
+ metrics = reporting_period.metrics_payload
81
+ slow_transactions = reporting_period.slow_transactions_payload
82
+ jobs = reporting_period.jobs
83
+ slow_jobs = reporting_period.slow_jobs_payload
84
+ histograms = reporting_period.histograms
85
+ db_query_metrics = reporting_period.db_query_metrics_payload
82
86
 
83
87
  log_deliver(metrics, slow_transactions, metadata, slow_jobs, histograms)
84
88
 
@@ -64,7 +64,6 @@ module ScoutApm
64
64
  @mem_start = mem_usage
65
65
  @recorder = agent_context.recorder
66
66
  @real_request = false
67
-
68
67
  ignore_request! if @recorder.nil?
69
68
  end
70
69
 
@@ -116,6 +115,7 @@ module ScoutApm
116
115
  layer.capture_backtrace!
117
116
  end
118
117
 
118
+
119
119
  if finalized?
120
120
  stop_request
121
121
  end
@@ -273,27 +273,34 @@ module ScoutApm
273
273
 
274
274
  apply_name_override
275
275
 
276
- converters = [
277
- LayerConverters::Histograms,
278
- LayerConverters::MetricConverter,
279
- LayerConverters::ErrorConverter,
280
- LayerConverters::AllocationMetricConverter,
281
- LayerConverters::RequestQueueTimeConverter,
282
- LayerConverters::JobConverter,
283
- LayerConverters::DatabaseConverter,
276
+ # Make a constant, then call converters.dup.each so it isn't inline?
277
+ converters = {
278
+ :histograms => LayerConverters::Histograms,
279
+ :metrics => LayerConverters::MetricConverter,
280
+ :errors => LayerConverters::ErrorConverter,
281
+ :allocation_metrics => LayerConverters::AllocationMetricConverter,
282
+ :queue_time => LayerConverters::RequestQueueTimeConverter,
283
+ :job => LayerConverters::JobConverter,
284
+ :db => LayerConverters::DatabaseConverter,
284
285
 
285
- LayerConverters::SlowJobConverter,
286
- LayerConverters::SlowRequestConverter,
287
- ]
286
+ :slow_job => LayerConverters::SlowJobConverter,
287
+ :slow_req => LayerConverters::SlowRequestConverter,
288
+ }
288
289
 
289
290
  walker = LayerConverters::DepthFirstWalker.new(self.root_layer)
290
- converters = converters.map do |klass|
291
+ converter_instances = converters.inject({}) do |memo, (slug, klass)|
291
292
  instance = klass.new(@agent_context, self, layer_finder, @store)
292
293
  instance.register_hooks(walker)
293
- instance
294
+ memo[slug] = instance
295
+ memo
294
296
  end
295
297
  walker.walk
296
- converters.each {|i| i.record! }
298
+ converter_results = converter_instances.inject({}) do |memo, (slug,i)|
299
+ memo[slug] = i.record!
300
+ memo
301
+ end
302
+
303
+ @agent_context.extensions.run_transaction_callbacks(converter_results,context,layer_finder.scope)
297
304
 
298
305
  # If there's an instant_key, it means we need to report this right away
299
306
  if web? && instant?
@@ -1,4 +1,4 @@
1
1
  module ScoutApm
2
- VERSION = "2.4.10"
2
+ VERSION = "2.4.11.pre"
3
3
  end
4
4
 
@@ -0,0 +1,58 @@
1
+ require 'test_helper'
2
+
3
+ class PeriodicCallbacksTest < Minitest::Test
4
+
5
+ # We don't have a test that ensures we actually report data to the server, so we can't be 100% sure this doesn't break
6
+ # reporting.
7
+ def test_broken_callback_does_not_throw_exception
8
+ ScoutApm::Extensions::Config.add_periodic_callback(BrokenCallback.new)
9
+ # Runs via agent context as calling +add_periodic_callback+ initializing the context + extension config.
10
+ ScoutApm::Agent.instance.context.extensions.run_periodic_callbacks(reporting_period,metadata)
11
+ end
12
+
13
+ def test_callback_runs
14
+ Thread.current[:periodic_callback_output] = nil
15
+ ScoutApm::Extensions::Config.add_periodic_callback(PeriodicCallback.new)
16
+ ScoutApm::Agent.instance.context.extensions.run_periodic_callbacks(reporting_period,metadata)
17
+ assert Thread.current[:periodic_callback_output]
18
+ end
19
+
20
+ def run_proc_callback
21
+ Thread.current[:proc_periodic] = nil
22
+ ScoutApm::Extensions::Config.add_periodic_callback(Proc.new { |reporting_period, metadata| Thread.current[:proc_periodic] = Time.at(metadata[:agent_time].to_i) })
23
+ ScoutApm::Agent.instance.context.extensions.run_periodic_callbacks(reporting_period,metadata)
24
+ assert Thread.current[:proc_periodic]
25
+ end
26
+
27
+ # Doesn't respond to +call+.
28
+ class BrokenCallback
29
+ end
30
+
31
+ # Sets a Thread local so we can verify that the callback ran.
32
+ class PeriodicCallback
33
+ def call(reporting_period,metadata)
34
+ Thread.current[:periodic_callback_output] = true
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def reporting_period
41
+ rp = ScoutApm::StoreReportingPeriod.new(ScoutApm::AgentContext.new, Time.at(metadata[:agent_time].to_i))
42
+ rp.absorb_metrics!(metrics)
43
+ end
44
+
45
+ def metrics
46
+ meta = ScoutApm::MetricMeta.new("Controller/users/index")
47
+ stats = ScoutApm::MetricStats.new
48
+ stats.update!(0.1)
49
+ {
50
+ meta => stats
51
+ }
52
+ end
53
+
54
+ def metadata
55
+ {:app_root=>"/srv/rails_app", :unique_id=>"ID", :agent_version=>"2.4.10", :agent_time=>"1523287920", :agent_pid=>21581, :platform=>"ruby"}
56
+ end
57
+
58
+ end
@@ -0,0 +1,58 @@
1
+ require 'test_helper'
2
+
3
+ class TransactionCallbacksTest < Minitest::Test
4
+
5
+ def setup
6
+ # Another test could set the recorder to +FakeRecorder+, which does not call +TrackedRequest#record!+.
7
+ ScoutApm::Agent.instance.context.recorder = ScoutApm::RecorderFactory.build(ScoutApm::Agent.instance.context)
8
+ end
9
+
10
+ # This is more of an integration test to ensure that we don't break TrackedRequest.
11
+ def test_broken_callback_does_not_break_tracked_request
12
+ ScoutApm::Extensions::Config.add_transaction_callback(BrokenCallback.new)
13
+
14
+ controller_layer = ScoutApm::Layer.new("Controller", "users/index")
15
+ # why doesn't this run? check if callbacks are configured
16
+ tr = ScoutApm::TrackedRequest.new(ScoutApm::Agent.instance.context, ScoutApm::FakeStore.new)
17
+ tr.start_layer(controller_layer)
18
+ tr.stop_layer
19
+ end
20
+
21
+ def test_callback_runs
22
+ Thread.current[:transaction_callback_output] = nil
23
+ ScoutApm::Extensions::Config.add_transaction_callback(TransactionCallback.new)
24
+
25
+ controller_layer = ScoutApm::Layer.new("Controller", "users/index")
26
+
27
+ tr = ScoutApm::TrackedRequest.new(ScoutApm::Agent.instance.context, ScoutApm::FakeStore.new)
28
+ tr.start_layer(controller_layer)
29
+ tr.stop_layer
30
+
31
+ assert Thread.current[:transaction_callback_output]
32
+ end
33
+
34
+ def test_run_proc_callback
35
+ Thread.current[:proc_transaction_duration] = nil
36
+ ScoutApm::Extensions::Config.add_transaction_callback(Proc.new { |payload| Thread.current[:proc_transaction_duration] = payload.duration_ms })
37
+
38
+ controller_layer = ScoutApm::Layer.new("Controller", "users/index")
39
+
40
+ tr = ScoutApm::TrackedRequest.new(ScoutApm::Agent.instance.context, ScoutApm::FakeStore.new)
41
+ tr.start_layer(controller_layer)
42
+ tr.stop_layer
43
+
44
+ assert Thread.current[:proc_transaction_duration]
45
+ end
46
+
47
+ # Doesn't respond to +call+.
48
+ class BrokenCallback
49
+ end
50
+
51
+ # Sets a Thread local so we can verify that the callback ran.
52
+ class TransactionCallback
53
+ def call(payload)
54
+ Thread.current[:transaction_callback_output] = true
55
+ end
56
+ end
57
+
58
+ 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.4.10
4
+ version: 2.4.11.pre
5
5
  platform: ruby
6
6
  authors:
7
7
  - Derek Haynes
@@ -209,6 +209,8 @@ files:
209
209
  - lib/scout_apm/db_query_metric_stats.rb
210
210
  - lib/scout_apm/debug.rb
211
211
  - lib/scout_apm/environment.rb
212
+ - lib/scout_apm/extensions/config.rb
213
+ - lib/scout_apm/extensions/transaction_callback_payload.rb
212
214
  - lib/scout_apm/fake_store.rb
213
215
  - lib/scout_apm/framework_integrations/rails_2.rb
214
216
  - lib/scout_apm/framework_integrations/rails_3_or_4.rb
@@ -332,6 +334,8 @@ files:
332
334
  - test/unit/db_query_metric_set_test.rb
333
335
  - test/unit/db_query_metric_stats_test.rb
334
336
  - test/unit/environment_test.rb
337
+ - test/unit/extensions/periodic_callbacks_test.rb
338
+ - test/unit/extensions/transaction_callbacks_test.rb
335
339
  - test/unit/fake_store_test.rb
336
340
  - test/unit/git_revision_test.rb
337
341
  - test/unit/histogram_test.rb
@@ -379,12 +383,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
379
383
  version: '0'
380
384
  required_rubygems_version: !ruby/object:Gem::Requirement
381
385
  requirements:
382
- - - ">="
386
+ - - ">"
383
387
  - !ruby/object:Gem::Version
384
- version: '0'
388
+ version: 1.3.1
385
389
  requirements: []
386
390
  rubyforge_project: scout_apm
387
- rubygems_version: 2.4.5.2
391
+ rubygems_version: 2.4.6
388
392
  signing_key:
389
393
  specification_version: 4
390
394
  summary: Ruby application performance monitoring
@@ -398,6 +402,8 @@ test_files:
398
402
  - test/unit/db_query_metric_set_test.rb
399
403
  - test/unit/db_query_metric_stats_test.rb
400
404
  - test/unit/environment_test.rb
405
+ - test/unit/extensions/periodic_callbacks_test.rb
406
+ - test/unit/extensions/transaction_callbacks_test.rb
401
407
  - test/unit/fake_store_test.rb
402
408
  - test/unit/git_revision_test.rb
403
409
  - test/unit/histogram_test.rb
@@ -429,3 +435,4 @@ test_files:
429
435
  - test/unit/utils/backtrace_parser_test.rb
430
436
  - test/unit/utils/numbers_test.rb
431
437
  - test/unit/utils/scm.rb
438
+ has_rdoc: