scout_apm 3.0.0.pre22 → 3.0.0.pre23

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: f13b4a3f3c661a398f9a56edb12b5eb4d3c4bdd9
4
- data.tar.gz: 0804ece9bc0b33ab297e2d6f36bdcd1059b98853
3
+ metadata.gz: 64d1ac40c6dec93f6e7c59b358f8f696a1f53eee
4
+ data.tar.gz: 815f98467560d0c0b9c4257eafc1f45a9784246e
5
5
  SHA512:
6
- metadata.gz: a4a861ec81684ff469f17d806c781368411671e098e3ca97c383e55492160c56a9fb7a56a653248f4985fa7ddde4abfed7bdaeed96cebe092655c302761ae466
7
- data.tar.gz: 6c3d47bb26bf70e6a1523f9d25ae948278f7ae2e89380a9d404f5b3a11f5e6fbdff518215b707703250802fc0c03e517816553d2ff121258a8ccb5a543c71bde
6
+ metadata.gz: 439b25ab44127e59d906006f34e8c9efd94fa3bbafde58fbcfd1af34be1caf94b80d8ae84c6087af8e4de5e6866b5c03e609d971524e0cbc3a3aa0d2922f500b
7
+ data.tar.gz: b6c954e72a252f9dbc113ed12b659c69b9f3e19241a58395a01ff68725ad4bd0b96b2e0bb6857cd9950063469aaba76d03ff1d3a2d802ca14d218c7996dcc10c
@@ -2,6 +2,11 @@
2
2
 
3
3
  * ScoutProf BETA
4
4
 
5
+ # 2.4.11
6
+
7
+ * Adds transaction + periodic reporting callback extension support
8
+ * Use Module#prepend if available for ActiveRecord `exec_query` instrument
9
+
5
10
  # 2.4.10
6
11
 
7
12
  * Improve ActiveRecord instrumentation across Rails 3.2+, and adding support
@@ -180,6 +180,9 @@ require 'scout_apm/agent/exit_handler'
180
180
  require 'scout_apm/tasks/doctor'
181
181
  require 'scout_apm/tasks/support'
182
182
 
183
+ require 'scout_apm/extensions/config'
184
+ require 'scout_apm/extensions/transaction_callback_payload'
185
+
183
186
  if defined?(Rails) && defined?(Rails::VERSION) && defined?(Rails::VERSION::MAJOR) && Rails::VERSION::MAJOR >= 3 && defined?(Rails::Railtie)
184
187
  module ScoutApm
185
188
  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
@@ -165,15 +165,23 @@ module ScoutApm
165
165
  end
166
166
 
167
167
  def ruby_19?
168
- @ruby_19 ||= defined?(RUBY_ENGINE) && RUBY_ENGINE == "ruby" && RUBY_VERSION.match(/^1\.9/)
168
+ return @ruby_19 if defined?(@ruby_19)
169
+ @ruby_19 = defined?(RUBY_ENGINE) && RUBY_ENGINE == "ruby" && RUBY_VERSION.match(/^1\.9/)
169
170
  end
170
171
 
171
172
  def ruby_187?
172
- @ruby_187 ||= defined?(RUBY_VERSION) && RUBY_VERSION.match(/^1\.8\.7/)
173
+ return @ruby_187 if defined?(@ruby_187)
174
+ @ruby_187 = defined?(RUBY_VERSION) && RUBY_VERSION.match(/^1\.8\.7/)
173
175
  end
174
176
 
175
177
  def ruby_2?
176
- @ruby_2 ||= defined?(RUBY_VERSION) && RUBY_VERSION.match(/^2/)
178
+ return @ruby_2 if defined?(@ruby_2)
179
+ @ruby_2 = defined?(RUBY_VERSION) && RUBY_VERSION.match(/^2/)
180
+ end
181
+
182
+ # Returns true if this Ruby version supports Module#prepend.
183
+ def supports_module_prepend?
184
+ ruby_2?
177
185
  end
178
186
 
179
187
  # Returns a string representation of the OS (ex: darwin, linux)
@@ -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
@@ -118,8 +118,14 @@ module ScoutApm
118
118
  (::ActiveRecord::VERSION::MAJOR.to_i == 3 && ::ActiveRecord::VERSION::MINOR.to_i >= 2))
119
119
  if rails_3_2_or_above
120
120
  if Utils::KlassHelper.defined?("ActiveRecord::Relation")
121
- ::ActiveRecord::Relation.module_eval do
122
- include ::ScoutApm::Instruments::ActiveRecordRelationQueryInstruments
121
+ if @context.environment.supports_module_prepend?
122
+ ::ActiveRecord::Relation.module_eval do
123
+ prepend ::ScoutApm::Instruments::ActiveRecordRelationQueryInstruments
124
+ end
125
+ else
126
+ ::ActiveRecord::Relation.module_eval do
127
+ include ::ScoutApm::Instruments::ActiveRecordRelationQueryInstruments
128
+ end
123
129
  end
124
130
  end
125
131
  else
@@ -298,6 +304,10 @@ module ScoutApm
298
304
  end
299
305
 
300
306
  module ActiveRecordRelationQueryInstruments
307
+ def self.prepended(instrumented_class)
308
+ ScoutApm::Agent.instance.context.logger.info "Instrumenting ActiveRecord::Relation#exec_queries - #{instrumented_class.inspect} (prepending)"
309
+ end
310
+
301
311
  def self.included(instrumented_class)
302
312
  ScoutApm::Agent.instance.context.logger.info "Instrumenting ActiveRecord::Relation#exec_queries - #{instrumented_class.inspect}"
303
313
  instrumented_class.class_eval do
@@ -308,7 +318,7 @@ module ScoutApm
308
318
  end
309
319
  end
310
320
 
311
- def exec_queries_with_scout_instruments(*args, &block)
321
+ def exec_queries(*args, &block)
312
322
  req = ScoutApm::RequestManager.lookup
313
323
  layer = ScoutApm::Layer.new("ActiveRecord", Utils::ActiveRecordMetricName::DEFAULT_METRIC)
314
324
  layer.annotate_layer(:ignorable => true)
@@ -316,12 +326,23 @@ module ScoutApm
316
326
  req.start_layer(layer)
317
327
  req.ignore_children!
318
328
  begin
319
- exec_queries_without_scout_instruments(*args, &block)
329
+ if ScoutApm::Environment.instance.supports_module_prepend?
330
+ super(*args, &block)
331
+ else
332
+ exec_queries_without_scout_instruments(*args, &block)
333
+ end
320
334
  ensure
321
335
  req.acknowledge_children!
322
336
  req.stop_layer
323
337
  end
324
338
  end
339
+
340
+ # If prepend is not supported, rename the method and use
341
+ # alias_method_style chaining instead
342
+ if !ScoutApm::Environment.instance.supports_module_prepend?
343
+ alias_method :exec_queries_with_scout_instruments, :exec_queries
344
+ remove_method :exec_queries
345
+ end
325
346
  end
326
347
 
327
348
  module ActiveRecordUpdateInstruments
@@ -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
 
@@ -119,6 +118,7 @@ module ScoutApm
119
118
  layer.capture_backtrace!
120
119
  end
121
120
 
121
+
122
122
  if finalized?
123
123
  stop_request
124
124
  else
@@ -300,27 +300,34 @@ module ScoutApm
300
300
 
301
301
  apply_name_override
302
302
 
303
- converters = [
304
- LayerConverters::Histograms,
305
- LayerConverters::MetricConverter,
306
- LayerConverters::ErrorConverter,
307
- LayerConverters::AllocationMetricConverter,
308
- LayerConverters::RequestQueueTimeConverter,
309
- LayerConverters::JobConverter,
310
- LayerConverters::DatabaseConverter,
303
+ # Make a constant, then call converters.dup.each so it isn't inline?
304
+ converters = {
305
+ :histograms => LayerConverters::Histograms,
306
+ :metrics => LayerConverters::MetricConverter,
307
+ :errors => LayerConverters::ErrorConverter,
308
+ :allocation_metrics => LayerConverters::AllocationMetricConverter,
309
+ :queue_time => LayerConverters::RequestQueueTimeConverter,
310
+ :job => LayerConverters::JobConverter,
311
+ :db => LayerConverters::DatabaseConverter,
311
312
 
312
- LayerConverters::SlowJobConverter,
313
- LayerConverters::SlowRequestConverter,
314
- ]
313
+ :slow_job => LayerConverters::SlowJobConverter,
314
+ :slow_req => LayerConverters::SlowRequestConverter,
315
+ }
315
316
 
316
317
  walker = LayerConverters::DepthFirstWalker.new(self.root_layer)
317
- converters = converters.map do |klass|
318
+ converter_instances = converters.inject({}) do |memo, (slug, klass)|
318
319
  instance = klass.new(@agent_context, self, layer_finder, @store)
319
320
  instance.register_hooks(walker)
320
- instance
321
+ memo[slug] = instance
322
+ memo
321
323
  end
322
324
  walker.walk
323
- converters.each {|i| i.record! }
325
+ converter_results = converter_instances.inject({}) do |memo, (slug,i)|
326
+ memo[slug] = i.record!
327
+ memo
328
+ end
329
+
330
+ @agent_context.extensions.run_transaction_callbacks(converter_results,context,layer_finder.scope)
324
331
 
325
332
  # If there's an instant_key, it means we need to report this right away
326
333
  if web? && instant?
@@ -1,3 +1,3 @@
1
1
  module ScoutApm
2
- VERSION = "3.0.0.pre22"
2
+ VERSION = "3.0.0.pre23"
3
3
  end
@@ -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: 3.0.0.pre22
4
+ version: 3.0.0.pre23
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: 2018-04-11 00:00:00.000000000 Z
12
+ date: 2018-05-07 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: minitest
@@ -213,6 +213,8 @@ files:
213
213
  - lib/scout_apm/db_query_metric_stats.rb
214
214
  - lib/scout_apm/debug.rb
215
215
  - lib/scout_apm/environment.rb
216
+ - lib/scout_apm/extensions/config.rb
217
+ - lib/scout_apm/extensions/transaction_callback_payload.rb
216
218
  - lib/scout_apm/fake_store.rb
217
219
  - lib/scout_apm/framework_integrations/rails_2.rb
218
220
  - lib/scout_apm/framework_integrations/rails_3_or_4.rb
@@ -338,6 +340,8 @@ files:
338
340
  - test/unit/db_query_metric_set_test.rb
339
341
  - test/unit/db_query_metric_stats_test.rb
340
342
  - test/unit/environment_test.rb
343
+ - test/unit/extensions/periodic_callbacks_test.rb
344
+ - test/unit/extensions/transaction_callbacks_test.rb
341
345
  - test/unit/fake_store_test.rb
342
346
  - test/unit/git_revision_test.rb
343
347
  - test/unit/histogram_test.rb
@@ -391,48 +395,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
391
395
  version: 1.3.1
392
396
  requirements: []
393
397
  rubyforge_project: scout_apm
394
- rubygems_version: 2.4.5.2
398
+ rubygems_version: 2.6.12
395
399
  signing_key:
396
400
  specification_version: 4
397
401
  summary: Ruby application performance monitoring
398
- test_files:
399
- - test/data/config_test_1.yml
400
- - test/test_helper.rb
401
- - test/unit/agent_test.rb
402
- - test/unit/background_job_integrations/sidekiq_test.rb
403
- - test/unit/config_test.rb
404
- - test/unit/context_test.rb
405
- - test/unit/db_query_metric_set_test.rb
406
- - test/unit/db_query_metric_stats_test.rb
407
- - test/unit/environment_test.rb
408
- - test/unit/fake_store_test.rb
409
- - test/unit/git_revision_test.rb
410
- - test/unit/histogram_test.rb
411
- - test/unit/ignored_uris_test.rb
412
- - test/unit/instruments/active_record_instruments_test.rb
413
- - test/unit/instruments/net_http_test.rb
414
- - test/unit/instruments/percentile_sampler_test.rb
415
- - test/unit/layaway_test.rb
416
- - test/unit/layer_children_set_test.rb
417
- - test/unit/layer_converters/depth_first_walker_test.rb
418
- - test/unit/layer_converters/metric_converter_test.rb
419
- - test/unit/layer_converters/stubs.rb
420
- - test/unit/limited_layer_test.rb
421
- - test/unit/logger_test.rb
422
- - test/unit/metric_set_test.rb
423
- - test/unit/remote/test_message.rb
424
- - test/unit/remote/test_router.rb
425
- - test/unit/remote/test_server.rb
426
- - test/unit/scored_item_set_test.rb
427
- - test/unit/serializers/payload_serializer_test.rb
428
- - test/unit/slow_job_policy_test.rb
429
- - test/unit/slow_request_policy_test.rb
430
- - test/unit/sql_sanitizer_test.rb
431
- - test/unit/store_test.rb
432
- - test/unit/tracer_test.rb
433
- - test/unit/tracked_request_test.rb
434
- - test/unit/transaction_test.rb
435
- - test/unit/utils/active_record_metric_name_test.rb
436
- - test/unit/utils/backtrace_parser_test.rb
437
- - test/unit/utils/numbers_test.rb
438
- - test/unit/utils/scm.rb
402
+ test_files: []