scout_apm 3.0.0.pre22 → 3.0.0.pre23

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