scout_apm 2.4.10 → 2.4.11.pre

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: 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: