scout_apm 2.2.0.pre3 → 2.3.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (127) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/CHANGELOG.markdown +147 -2
  4. data/Guardfile +43 -0
  5. data/Rakefile +2 -2
  6. data/ext/allocations/allocations.c +6 -0
  7. data/ext/allocations/extconf.rb +1 -0
  8. data/ext/rusage/README.md +26 -0
  9. data/ext/rusage/extconf.rb +5 -0
  10. data/ext/rusage/rusage.c +52 -0
  11. data/lib/scout_apm.rb +28 -15
  12. data/lib/scout_apm/agent.rb +89 -37
  13. data/lib/scout_apm/agent/logging.rb +6 -1
  14. data/lib/scout_apm/agent/reporting.rb +9 -6
  15. data/lib/scout_apm/app_server_load.rb +21 -10
  16. data/lib/scout_apm/attribute_arranger.rb +6 -3
  17. data/lib/scout_apm/background_job_integrations/delayed_job.rb +71 -1
  18. data/lib/scout_apm/background_job_integrations/resque.rb +85 -0
  19. data/lib/scout_apm/background_job_integrations/sidekiq.rb +22 -20
  20. data/lib/scout_apm/background_recorder.rb +43 -0
  21. data/lib/scout_apm/background_worker.rb +19 -15
  22. data/lib/scout_apm/config.rb +138 -28
  23. data/lib/scout_apm/db_query_metric_set.rb +80 -0
  24. data/lib/scout_apm/db_query_metric_stats.rb +102 -0
  25. data/lib/scout_apm/debug.rb +37 -0
  26. data/lib/scout_apm/environment.rb +22 -15
  27. data/lib/scout_apm/git_revision.rb +51 -0
  28. data/lib/scout_apm/histogram.rb +11 -2
  29. data/lib/scout_apm/instant/assets/xmlhttp_instrumentation.html +2 -2
  30. data/lib/scout_apm/instant/middleware.rb +196 -54
  31. data/lib/scout_apm/instruments/action_controller_rails_3_rails4.rb +89 -68
  32. data/lib/scout_apm/instruments/action_view.rb +49 -0
  33. data/lib/scout_apm/instruments/active_record.rb +127 -3
  34. data/lib/scout_apm/instruments/grape.rb +4 -3
  35. data/lib/scout_apm/instruments/middleware_detailed.rb +4 -6
  36. data/lib/scout_apm/instruments/mongoid.rb +24 -3
  37. data/lib/scout_apm/instruments/net_http.rb +7 -2
  38. data/lib/scout_apm/instruments/percentile_sampler.rb +36 -19
  39. data/lib/scout_apm/instruments/process/process_cpu.rb +3 -2
  40. data/lib/scout_apm/instruments/process/process_memory.rb +3 -3
  41. data/lib/scout_apm/instruments/resque.rb +40 -0
  42. data/lib/scout_apm/layaway.rb +67 -28
  43. data/lib/scout_apm/layer.rb +19 -59
  44. data/lib/scout_apm/layer_children_set.rb +77 -0
  45. data/lib/scout_apm/layer_converters/allocation_metric_converter.rb +5 -6
  46. data/lib/scout_apm/layer_converters/converter_base.rb +201 -14
  47. data/lib/scout_apm/layer_converters/database_converter.rb +55 -0
  48. data/lib/scout_apm/layer_converters/depth_first_walker.rb +22 -10
  49. data/lib/scout_apm/layer_converters/error_converter.rb +5 -7
  50. data/lib/scout_apm/layer_converters/find_layer_by_type.rb +34 -0
  51. data/lib/scout_apm/layer_converters/histograms.rb +14 -0
  52. data/lib/scout_apm/layer_converters/job_converter.rb +36 -50
  53. data/lib/scout_apm/layer_converters/metric_converter.rb +17 -19
  54. data/lib/scout_apm/layer_converters/request_queue_time_converter.rb +10 -12
  55. data/lib/scout_apm/layer_converters/slow_job_converter.rb +41 -115
  56. data/lib/scout_apm/layer_converters/slow_request_converter.rb +33 -117
  57. data/lib/scout_apm/limited_layer.rb +126 -0
  58. data/lib/scout_apm/metric_meta.rb +0 -5
  59. data/lib/scout_apm/metric_set.rb +9 -1
  60. data/lib/scout_apm/metric_stats.rb +7 -8
  61. data/lib/scout_apm/rack.rb +26 -0
  62. data/lib/scout_apm/remote/message.rb +23 -0
  63. data/lib/scout_apm/remote/recorder.rb +57 -0
  64. data/lib/scout_apm/remote/router.rb +49 -0
  65. data/lib/scout_apm/remote/server.rb +58 -0
  66. data/lib/scout_apm/reporter.rb +51 -15
  67. data/lib/scout_apm/request_histograms.rb +4 -0
  68. data/lib/scout_apm/request_manager.rb +2 -1
  69. data/lib/scout_apm/scored_item_set.rb +7 -0
  70. data/lib/scout_apm/serializers/db_query_serializer_to_json.rb +15 -0
  71. data/lib/scout_apm/serializers/histograms_serializer_to_json.rb +21 -0
  72. data/lib/scout_apm/serializers/payload_serializer.rb +10 -3
  73. data/lib/scout_apm/serializers/payload_serializer_to_json.rb +6 -6
  74. data/lib/scout_apm/serializers/slow_jobs_serializer_to_json.rb +2 -1
  75. data/lib/scout_apm/server_integrations/puma.rb +5 -2
  76. data/lib/scout_apm/slow_job_policy.rb +1 -10
  77. data/lib/scout_apm/slow_job_record.rb +6 -1
  78. data/lib/scout_apm/slow_request_policy.rb +1 -10
  79. data/lib/scout_apm/slow_transaction.rb +20 -2
  80. data/lib/scout_apm/store.rb +66 -12
  81. data/lib/scout_apm/synchronous_recorder.rb +26 -0
  82. data/lib/scout_apm/tracked_request.rb +136 -71
  83. data/lib/scout_apm/utils/active_record_metric_name.rb +8 -4
  84. data/lib/scout_apm/utils/backtrace_parser.rb +3 -3
  85. data/lib/scout_apm/utils/gzip_helper.rb +24 -0
  86. data/lib/scout_apm/utils/numbers.rb +14 -0
  87. data/lib/scout_apm/utils/scm.rb +14 -0
  88. data/lib/scout_apm/version.rb +1 -1
  89. data/scout_apm.gemspec +5 -4
  90. data/test/test_helper.rb +18 -0
  91. data/test/unit/config_test.rb +59 -8
  92. data/test/unit/db_query_metric_set_test.rb +56 -0
  93. data/test/unit/db_query_metric_stats_test.rb +113 -0
  94. data/test/unit/git_revision_test.rb +15 -0
  95. data/test/unit/histogram_test.rb +14 -0
  96. data/test/unit/instruments/net_http_test.rb +21 -0
  97. data/test/unit/instruments/percentile_sampler_test.rb +137 -0
  98. data/test/unit/layaway_test.rb +20 -0
  99. data/test/unit/layer_children_set_test.rb +88 -0
  100. data/test/unit/layer_converters/depth_first_walker_test.rb +66 -0
  101. data/test/unit/layer_converters/metric_converter_test.rb +22 -0
  102. data/test/unit/layer_converters/stubs.rb +33 -0
  103. data/test/unit/limited_layer_test.rb +53 -0
  104. data/test/unit/remote/test_message.rb +13 -0
  105. data/test/unit/remote/test_router.rb +33 -0
  106. data/test/unit/remote/test_server.rb +15 -0
  107. data/test/unit/serializers/payload_serializer_test.rb +3 -12
  108. data/test/unit/store_test.rb +66 -0
  109. data/test/unit/test_tracked_request.rb +87 -0
  110. data/test/unit/utils/active_record_metric_name_test.rb +8 -0
  111. data/test/unit/utils/backtrace_parser_test.rb +5 -0
  112. data/test/unit/utils/numbers_test.rb +15 -0
  113. data/test/unit/utils/scm.rb +17 -0
  114. metadata +125 -30
  115. data/ext/stacks/extconf.rb +0 -37
  116. data/ext/stacks/scout_atomics.h +0 -86
  117. data/ext/stacks/stacks.c +0 -811
  118. data/lib/scout_apm/capacity.rb +0 -57
  119. data/lib/scout_apm/deploy_integrations/capistrano_2.cap +0 -12
  120. data/lib/scout_apm/deploy_integrations/capistrano_2.rb +0 -83
  121. data/lib/scout_apm/deploy_integrations/capistrano_3.cap +0 -12
  122. data/lib/scout_apm/deploy_integrations/capistrano_3.rb +0 -88
  123. data/lib/scout_apm/instruments/delayed_job.rb +0 -57
  124. data/lib/scout_apm/serializers/deploy_serializer.rb +0 -16
  125. data/lib/scout_apm/trace_compactor.rb +0 -312
  126. data/lib/scout_apm/utils/fake_stacks.rb +0 -87
  127. data/tester.rb +0 -53
@@ -10,9 +10,9 @@ module ScoutApm
10
10
 
11
11
  # Accessors below are for associated classes
12
12
  attr_accessor :store
13
+ attr_reader :recorder
13
14
  attr_accessor :layaway
14
15
  attr_accessor :config
15
- attr_accessor :capacity
16
16
  attr_accessor :logger
17
17
  attr_accessor :log_file # path to the log file
18
18
  attr_accessor :options # options passed to the agent when +#start+ is called.
@@ -42,6 +42,9 @@ module ScoutApm
42
42
  @process_start_time = Time.now
43
43
  @options ||= options
44
44
 
45
+ # until the agent is started, there's no recorder
46
+ @recorder = nil
47
+
45
48
  # Start up without attempting to load a configuration file. We need to be
46
49
  # able to lookup configuration options like "application_root" which would
47
50
  # then in turn influence where the configuration file came from.
@@ -55,10 +58,10 @@ module ScoutApm
55
58
  @request_histograms_by_time = Hash.new { |h, k| h[k] = ScoutApm::RequestHistograms.new }
56
59
 
57
60
  @store = ScoutApm::Store.new
61
+
58
62
  @layaway = ScoutApm::Layaway.new(config, environment)
59
63
  @metric_lookup = Hash.new
60
64
 
61
- @capacity = ScoutApm::Capacity.new
62
65
  @installed_instruments = []
63
66
  end
64
67
 
@@ -86,6 +89,11 @@ module ScoutApm
86
89
  return false unless force?
87
90
  end
88
91
 
92
+ if environment.interactive?
93
+ logger.warn "Agent attempting to load in interactive mode. #{force? ? 'Forcing agent to start' : 'Not starting agent'}"
94
+ return false unless force?
95
+ end
96
+
89
97
  if app_server_missing?(options) && background_job_missing?
90
98
  if force?
91
99
  logger.warn "Agent starting (forced)"
@@ -119,22 +127,27 @@ module ScoutApm
119
127
  init_logger
120
128
  logger.info "Attempting to start Scout Agent [#{ScoutApm::VERSION}] on [#{environment.hostname}]"
121
129
 
122
- @ignored_uris = ScoutApm::IgnoredUris.new(config.value('ignore'))
130
+ @recorder = create_recorder
123
131
 
124
- if environment.deploy_integration
125
- logger.info "Starting monitoring for [#{environment.deploy_integration.name}]]."
126
- return environment.deploy_integration.install
127
- end
132
+ @config.log_settings
133
+
134
+ @ignored_uris = ScoutApm::IgnoredUris.new(config.value('ignore'))
128
135
 
129
136
  load_instruments if should_load_instruments?(options)
130
137
 
138
+ if !@config.any_keys_found?
139
+ logger.info("No configuration file loaded, and no configuration found in ENV. " +
140
+ "For assistance configuring Scout, visit " +
141
+ "http://help.apm.scoutapp.com/#configuration-options")
142
+ end
143
+
131
144
  return false unless preconditions_met?(options)
132
145
  @started = true
133
146
  logger.info "Starting monitoring for [#{environment.application_name}]. Framework [#{environment.framework}] App Server [#{environment.app_server}] Background Job Framework [#{environment.background_job_name}]."
134
147
 
135
148
  [ ScoutApm::Instruments::Process::ProcessCpu.new(environment.processors, logger),
136
149
  ScoutApm::Instruments::Process::ProcessMemory.new(logger),
137
- ScoutApm::Instruments::PercentileSampler.new(logger, 95),
150
+ ScoutApm::Instruments::PercentileSampler.new(logger, request_histograms_by_time),
138
151
  ].each { |s| store.add_sampler(s) }
139
152
 
140
153
  app_server_load_hook
@@ -202,20 +215,21 @@ module ScoutApm
202
215
  # It does not attempt to actually report metrics.
203
216
  def shutdown
204
217
  logger.info "Shutting down ScoutApm"
218
+
205
219
  return if !started?
206
220
 
221
+ return if @shutdown
222
+ @shutdown = true
223
+
207
224
  if @background_worker
208
225
  logger.info("Stopping background worker")
209
226
  @background_worker.stop
210
227
  store.write_to_layaway(layaway, :force)
228
+ if @background_worker_thread.alive?
229
+ @background_worker_thread.wakeup
230
+ @background_worker_thread.join
231
+ end
211
232
  end
212
-
213
- logger.debug "Joining background worker thread"
214
- if @background_worker_thread
215
- @background_worker_thread.wakeup
216
- @background_worker_thread.join
217
- end
218
- ScoutApm::Instruments::Stacks.uninstall
219
233
  end
220
234
 
221
235
  def started?
@@ -234,7 +248,10 @@ module ScoutApm
234
248
  end
235
249
 
236
250
  def background_worker_running?
237
- !! @background_worker_thread
251
+ @background_worker_thread &&
252
+ @background_worker_thread.alive? &&
253
+ @background_worker &&
254
+ @background_worker.running?
238
255
  end
239
256
 
240
257
  # Creates the worker thread. The worker thread is a loop that runs continuously. It sleeps for +Agent#period+ and when it wakes,
@@ -249,15 +266,13 @@ module ScoutApm
249
266
 
250
267
  install_exit_handler
251
268
 
252
- if ScoutApm::Agent.instance.config.value('profile')
253
- # After we fork, setup the handlers here.
254
- ScoutApm::Instruments::Stacks.install
255
- ScoutApm::Instruments::Stacks.start
256
- end
269
+ @recorder = create_recorder
270
+ logger.info("recorder is now: #{@recorder.class}")
257
271
 
258
272
  @background_worker = ScoutApm::BackgroundWorker.new
259
273
  @background_worker_thread = Thread.new do
260
274
  @background_worker.start {
275
+ ScoutApm::Debug.instance.call_periodic_hooks
261
276
  ScoutApm::Agent.instance.process_metrics
262
277
  clean_old_percentiles
263
278
  }
@@ -281,22 +296,21 @@ module ScoutApm
281
296
 
282
297
  # Loads the instrumention logic.
283
298
  def load_instruments
284
- if !background_job_missing?
285
- case environment.background_job_name
286
- when :delayed_job
287
- install_instrument(ScoutApm::Instruments::DelayedJob)
288
- end
289
- else
290
- case environment.framework
291
- when :rails then install_instrument(ScoutApm::Instruments::ActionControllerRails2)
292
- when :rails3_or_4 then
293
- install_instrument(ScoutApm::Instruments::ActionControllerRails3Rails4)
299
+ case environment.framework
300
+ when :rails then
301
+ install_instrument(ScoutApm::Instruments::ActionControllerRails2)
302
+ when :rails3_or_4 then
303
+ install_instrument(ScoutApm::Instruments::ActionControllerRails3Rails4)
304
+ install_instrument(ScoutApm::Instruments::RailsRouter)
305
+
306
+ if config.value("detailed_middleware")
307
+ install_instrument(ScoutApm::Instruments::MiddlewareDetailed)
308
+ else
294
309
  install_instrument(ScoutApm::Instruments::MiddlewareSummary)
295
- install_instrument(ScoutApm::Instruments::RailsRouter)
296
- # when :sinatra then install_instrument(ScoutApm::Instruments::Sinatra)
297
310
  end
298
311
  end
299
312
 
313
+ install_instrument(ScoutApm::Instruments::ActionView)
300
314
  install_instrument(ScoutApm::Instruments::ActiveRecord)
301
315
  install_instrument(ScoutApm::Instruments::Moped)
302
316
  install_instrument(ScoutApm::Instruments::Mongoid)
@@ -328,10 +342,6 @@ module ScoutApm
328
342
  instance.install
329
343
  end
330
344
 
331
- def deploy_integration
332
- environment.deploy_integration
333
- end
334
-
335
345
  def app_server_missing?(options = {})
336
346
  !environment.app_server_integration(true).found? && !options[:skip_app_server_check]
337
347
  end
@@ -339,5 +349,47 @@ module ScoutApm
339
349
  def background_job_missing?(options = {})
340
350
  environment.background_job_integration.nil? && !options[:skip_background_job_check]
341
351
  end
352
+
353
+ def clear_recorder
354
+ @recorder = nil
355
+ end
356
+
357
+ def create_recorder
358
+ if @recorder
359
+ return @recorder
360
+ end
361
+
362
+ if config.value("async_recording")
363
+ logger.debug("Using asynchronous recording")
364
+ ScoutApm::BackgroundRecorder.new(logger).start
365
+ else
366
+ logger.debug("Using synchronous recording")
367
+ ScoutApm::SynchronousRecorder.new(logger).start
368
+ end
369
+ end
370
+
371
+ def start_remote_server(bind, port)
372
+ return if @remote_server && @remote_server.running?
373
+
374
+ logger.info("Starting Remote Agent Server")
375
+
376
+ # Start the listening web server only in parent process.
377
+ @remote_server = ScoutApm::Remote::Server.new(
378
+ bind,
379
+ port,
380
+ ScoutApm::Remote::Router.new(ScoutApm::SynchronousRecorder.new(logger), logger),
381
+ logger
382
+ )
383
+
384
+ @remote_server.start
385
+ end
386
+
387
+ # Execute this in the child process of a remote agent. The parent is
388
+ # expected to have its accepting webserver up and running
389
+ def use_remote_recorder(host, port)
390
+ logger.debug("Becoming Remote Agent (reporting to: #{host}:#{port})")
391
+ @recorder = ScoutApm::Remote::Recorder.new(host, port, logger)
392
+ @store = ScoutApm::FakeStore.new
393
+ end
342
394
  end
343
395
  end
@@ -6,7 +6,12 @@ module ScoutApm
6
6
  "#{environment.root}/log"
7
7
  end
8
8
 
9
- def init_logger
9
+ def init_logger(opts={})
10
+ if opts[:force]
11
+ @log_file = nil
12
+ @logger = nil
13
+ end
14
+
10
15
  begin
11
16
  @log_file ||= determine_log_destination
12
17
  rescue => e
@@ -24,7 +24,7 @@ module ScoutApm
24
24
  report_to_server
25
25
  end
26
26
 
27
- # In a running app, one process will get one period ready for delivery, the others will see 0.
27
+ # In a running app, one process will get the period ready for delivery, the others will see 0.
28
28
  def report_to_server
29
29
  period_to_report = ScoutApm::StoreReportingPeriodTimestamp.minutes_ago(2)
30
30
 
@@ -58,6 +58,8 @@ module ScoutApm
58
58
  slow_transactions = reporting_period.slow_transactions_payload
59
59
  jobs = reporting_period.jobs
60
60
  slow_jobs = reporting_period.slow_jobs_payload
61
+ histograms = reporting_period.histograms
62
+ db_query_metrics = reporting_period.db_query_metrics_payload
61
63
 
62
64
  metadata = {
63
65
  :app_root => ScoutApm::Environment.instance.root.to_s,
@@ -68,10 +70,10 @@ module ScoutApm
68
70
  :platform => "ruby",
69
71
  }
70
72
 
71
- log_deliver(metrics, slow_transactions, metadata, slow_jobs)
73
+ log_deliver(metrics, slow_transactions, metadata, slow_jobs, histograms)
72
74
 
73
- payload = ScoutApm::Serializers::PayloadSerializer.serialize(metadata, metrics, slow_transactions, jobs, slow_jobs)
74
- # logger.debug("Payload: #{payload}")
75
+ payload = ScoutApm::Serializers::PayloadSerializer.serialize(metadata, metrics, slow_transactions, jobs, slow_jobs, histograms, db_query_metrics)
76
+ logger.debug("Sending payload w/ Headers: #{headers.inspect}")
75
77
 
76
78
  reporter.report(payload, headers)
77
79
  rescue => e
@@ -80,7 +82,7 @@ module ScoutApm
80
82
  logger.debug e.backtrace
81
83
  end
82
84
 
83
- def log_deliver(metrics, slow_transactions, metadata, jobs_traces)
85
+ def log_deliver(metrics, slow_transactions, metadata, jobs_traces, histograms)
84
86
  total_request_count = metrics.
85
87
  select { |meta,stats| meta.metric_name =~ /\AController/ }.
86
88
  inject(0) {|sum, (_, stat)| sum + stat.call_count }
@@ -97,9 +99,10 @@ module ScoutApm
97
99
  metrics_clause = "#{metrics.length} Metrics for #{total_request_count} requests"
98
100
  slow_trans_clause = "#{slow_transactions.length} Slow Transaction Traces"
99
101
  job_clause = "#{jobs_traces.length} Job Traces"
102
+ histogram_clause = "#{histograms.length} Histograms"
100
103
 
101
104
  logger.info "#{time_clause} Delivering #{metrics_clause} and #{slow_trans_clause} and #{job_clause}, #{process_log_str}."
102
- # logger.debug("Metrics: #{metrics.pretty_inspect}\nSlowTrans: #{slow_transactions.pretty_inspect}\nMetadata: #{metadata.inspect.pretty_inspect}")
105
+ logger.debug("\n\nMetrics: #{metrics.pretty_inspect}\nSlowTrans: #{slow_transactions.pretty_inspect}\nMetadata: #{metadata.inspect.pretty_inspect}\n\n")
103
106
  end
104
107
 
105
108
  # TODO: Move this into PayloadSerializer?
@@ -27,19 +27,30 @@ module ScoutApm
27
27
  end
28
28
 
29
29
  def data
30
- { :server_time => Time.now,
31
- :framework => ScoutApm::Environment.instance.framework_integration.human_name,
32
- :framework_version => ScoutApm::Environment.instance.framework_integration.version,
33
- :environment => ScoutApm::Environment.instance.framework_integration.env,
34
- :app_server => ScoutApm::Environment.instance.app_server,
30
+ { :server_time => to_s_safe(Time.now),
31
+ :framework => to_s_safe(ScoutApm::Environment.instance.framework_integration.human_name),
32
+ :framework_version => to_s_safe(ScoutApm::Environment.instance.framework_integration.version),
33
+ :environment => to_s_safe(ScoutApm::Environment.instance.framework_integration.env),
34
+ :app_server => to_s_safe(ScoutApm::Environment.instance.app_server),
35
35
  :ruby_version => RUBY_VERSION,
36
- :hostname => ScoutApm::Environment.instance.hostname,
37
- :database_engine => ScoutApm::Environment.instance.database_engine, # Detected
38
- :database_adapter => ScoutApm::Environment.instance.raw_database_adapter, # Raw
39
- :application_name => ScoutApm::Environment.instance.application_name,
36
+ :hostname => to_s_safe(ScoutApm::Environment.instance.hostname),
37
+ :database_engine => to_s_safe(ScoutApm::Environment.instance.database_engine), # Detected
38
+ :database_adapter => to_s_safe(ScoutApm::Environment.instance.raw_database_adapter), # Raw
39
+ :application_name => to_s_safe(ScoutApm::Environment.instance.application_name),
40
40
  :libraries => ScoutApm::Utils::InstalledGems.new.run,
41
- :paas => ScoutApm::Environment.instance.platform_integration.name
41
+ :paas => to_s_safe(ScoutApm::Environment.instance.platform_integration.name),
42
+ :git_sha => to_s_safe(ScoutApm::Environment.instance.git_revision.sha)
42
43
  }
43
44
  end
45
+
46
+ # Calls `.to_s` on the object passed in.
47
+ # Returns literal string 'to_s error' if the object does not respond to .to_s
48
+ def to_s_safe(obj)
49
+ if obj.respond_to?(:to_s)
50
+ obj.to_s
51
+ else
52
+ 'to_s error'
53
+ end
54
+ end
44
55
  end
45
56
  end
@@ -5,8 +5,6 @@ module ScoutApm
5
5
  def self.call(subject, attributes_list)
6
6
  attributes_list.inject({}) do |attribute_hash, attribute|
7
7
  case attribute
8
- when :traces
9
- attribute_hash[attribute] = subject.traces.to_a
10
8
  when Array
11
9
  attribute_hash[attribute[0]] = subject.send(attribute[1])
12
10
  when :bucket
@@ -14,7 +12,12 @@ module ScoutApm
14
12
  when :name
15
13
  attribute_hash[attribute] = subject.bucket_name
16
14
  when Symbol
17
- attribute_hash[attribute] = subject.send(attribute)
15
+ data = subject.send(attribute)
16
+ if data.respond_to?(:as_json)
17
+ attribute_hash[attribute] = data.as_json
18
+ else
19
+ attribute_hash[attribute] = data
20
+ end
18
21
  end
19
22
  attribute_hash
20
23
  end
@@ -1,6 +1,9 @@
1
1
  module ScoutApm
2
2
  module BackgroundJobIntegrations
3
3
  class DelayedJob
4
+ ACTIVE_JOB_KLASS = 'ActiveJob::QueueAdapters::DelayedJobAdapter::JobWrapper'.freeze
5
+ DJ_PERFORMABLE_METHOD = 'Delayed::PerformableMethod'.freeze
6
+
4
7
  attr_reader :logger
5
8
 
6
9
  def name
@@ -8,12 +11,79 @@ module ScoutApm
8
11
  end
9
12
 
10
13
  def present?
11
- defined?(::Delayed::Job) && (File.basename($0) =~ /\Adelayed_job/)
14
+ defined?(::Delayed::Job)
12
15
  end
13
16
 
14
17
  def forking?
15
18
  false
16
19
  end
20
+
21
+ def install
22
+ plugin = Class.new(Delayed::Plugin) do
23
+ require 'delayed_job'
24
+
25
+ callbacks do |lifecycle|
26
+ lifecycle.around(:invoke_job) do |job, *args, &block|
27
+ ScoutApm::Agent.instance.start_background_worker unless ScoutApm::Agent.instance.background_worker_running?
28
+
29
+ name = begin
30
+ case job.payload_object.class.to_s
31
+
32
+ # ActiveJob's class wraps the actual job class
33
+ when ACTIVE_JOB_KLASS
34
+ job.payload_object.job_data["job_class"]
35
+
36
+ # An adhoc job, called like `@user.delay.fib(10)`.
37
+ # returns a string like "User#fib"
38
+ when DJ_PERFORMABLE_METHOD
39
+ job.name
40
+
41
+ # A "real" job called like `Delayed::Job.enqueue(MyJob.new)`
42
+ # returns "MyJob"
43
+ else
44
+ job.payload_object.class.to_s
45
+ end
46
+ rescue
47
+ # Fall back to whatever DJ thinks the name is.
48
+ job.name
49
+ end
50
+
51
+ queue = job.queue || "default"
52
+
53
+ req = ScoutApm::RequestManager.lookup
54
+ req.job!
55
+
56
+ begin
57
+ latency = Time.now - job.created_at
58
+ req.annotate_request(:queue_latency => latency)
59
+ rescue
60
+ end
61
+
62
+ queue_layer = ScoutApm::Layer.new('Queue', queue)
63
+ job_layer = ScoutApm::Layer.new('Job', name)
64
+
65
+ begin
66
+ req.start_layer(queue_layer)
67
+ started_queue = true
68
+ req.start_layer(job_layer)
69
+ started_job = true
70
+
71
+ # Call the job itself.
72
+ block.call(job, *args)
73
+ rescue
74
+ req.error!
75
+ raise
76
+ ensure
77
+ req.stop_layer if started_job
78
+ req.stop_layer if started_queue
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ Delayed::Worker.plugins << plugin # ScoutApm::BackgroundJobIntegrations::DelayedJobPlugin
85
+ end
17
86
  end
18
87
  end
19
88
  end
89
+