scout_apm 3.0.0.pre13 → 3.0.0.pre14

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/lib/scout_apm.rb +20 -10
  3. data/lib/scout_apm/agent.rb +114 -319
  4. data/lib/scout_apm/agent/exit_handler.rb +66 -0
  5. data/lib/scout_apm/agent/preconditions.rb +69 -0
  6. data/lib/scout_apm/agent_context.rb +234 -0
  7. data/lib/scout_apm/app_server_load.rb +24 -14
  8. data/lib/scout_apm/background_job_integrations/resque.rb +7 -8
  9. data/lib/scout_apm/background_job_integrations/sidekiq.rb +2 -2
  10. data/lib/scout_apm/background_recorder.rb +8 -3
  11. data/lib/scout_apm/background_worker.rb +14 -7
  12. data/lib/scout_apm/config.rb +35 -26
  13. data/lib/scout_apm/context.rb +11 -4
  14. data/lib/scout_apm/db_query_metric_set.rb +17 -5
  15. data/lib/scout_apm/debug.rb +1 -1
  16. data/lib/scout_apm/environment.rb +10 -14
  17. data/lib/scout_apm/framework_integrations/sinatra.rb +1 -1
  18. data/lib/scout_apm/git_revision.rb +13 -8
  19. data/lib/scout_apm/histogram.rb +1 -1
  20. data/lib/scout_apm/instant/middleware.rb +7 -7
  21. data/lib/scout_apm/instant_reporting.rb +7 -7
  22. data/lib/scout_apm/instrument_manager.rb +87 -0
  23. data/lib/scout_apm/instruments/action_controller_rails_2.rb +12 -7
  24. data/lib/scout_apm/instruments/action_controller_rails_3_rails4.rb +17 -12
  25. data/lib/scout_apm/instruments/action_view.rb +11 -7
  26. data/lib/scout_apm/instruments/active_record.rb +25 -11
  27. data/lib/scout_apm/instruments/elasticsearch.rb +10 -6
  28. data/lib/scout_apm/instruments/grape.rb +12 -8
  29. data/lib/scout_apm/instruments/http_client.rb +10 -6
  30. data/lib/scout_apm/instruments/influxdb.rb +10 -6
  31. data/lib/scout_apm/instruments/middleware_detailed.rb +11 -5
  32. data/lib/scout_apm/instruments/middleware_summary.rb +11 -5
  33. data/lib/scout_apm/instruments/mongoid.rb +10 -5
  34. data/lib/scout_apm/instruments/moped.rb +11 -6
  35. data/lib/scout_apm/instruments/net_http.rb +10 -6
  36. data/lib/scout_apm/instruments/percentile_sampler.rb +8 -6
  37. data/lib/scout_apm/instruments/process/process_cpu.rb +8 -4
  38. data/lib/scout_apm/instruments/process/process_memory.rb +15 -10
  39. data/lib/scout_apm/instruments/rails_router.rb +12 -6
  40. data/lib/scout_apm/instruments/redis.rb +10 -6
  41. data/lib/scout_apm/instruments/samplers.rb +11 -0
  42. data/lib/scout_apm/instruments/sinatra.rb +5 -4
  43. data/lib/scout_apm/layaway.rb +21 -20
  44. data/lib/scout_apm/layaway_file.rb +8 -3
  45. data/lib/scout_apm/layer.rb +3 -3
  46. data/lib/scout_apm/layer_converters/converter_base.rb +6 -7
  47. data/lib/scout_apm/layer_converters/database_converter.rb +1 -1
  48. data/lib/scout_apm/layer_converters/histograms.rb +2 -2
  49. data/lib/scout_apm/layer_converters/slow_job_converter.rb +4 -3
  50. data/lib/scout_apm/layer_converters/slow_request_converter.rb +5 -4
  51. data/lib/scout_apm/logger.rb +143 -0
  52. data/lib/scout_apm/middleware.rb +7 -9
  53. data/lib/scout_apm/periodic_work.rb +28 -0
  54. data/lib/scout_apm/reporter.rb +14 -8
  55. data/lib/scout_apm/reporting.rb +135 -0
  56. data/lib/scout_apm/request_manager.rb +4 -6
  57. data/lib/scout_apm/serializers/payload_serializer.rb +1 -1
  58. data/lib/scout_apm/slow_job_policy.rb +6 -2
  59. data/lib/scout_apm/slow_job_record.rb +5 -5
  60. data/lib/scout_apm/slow_request_policy.rb +6 -2
  61. data/lib/scout_apm/slow_transaction.rb +5 -5
  62. data/lib/scout_apm/store.rb +22 -16
  63. data/lib/scout_apm/synchronous_recorder.rb +7 -3
  64. data/lib/scout_apm/tasks/doctor.rb +75 -0
  65. data/lib/scout_apm/tasks/support.rb +22 -0
  66. data/lib/scout_apm/tracer.rb +5 -5
  67. data/lib/scout_apm/tracked_request.rb +43 -19
  68. data/lib/scout_apm/utils/active_record_metric_name.rb +66 -8
  69. data/lib/scout_apm/utils/backtrace_parser.rb +1 -1
  70. data/lib/scout_apm/utils/installed_gems.rb +7 -3
  71. data/lib/scout_apm/utils/klass_helper.rb +8 -2
  72. data/lib/scout_apm/utils/scm.rb +1 -1
  73. data/lib/scout_apm/utils/sql_sanitizer.rb +3 -3
  74. data/lib/scout_apm/version.rb +1 -1
  75. data/lib/tasks/doctor.rake +11 -0
  76. data/scout_apm.gemspec +1 -0
  77. data/test/test_helper.rb +17 -2
  78. data/test/unit/agent_test.rb +1 -54
  79. data/test/unit/config_test.rb +9 -5
  80. data/test/unit/context_test.rb +4 -4
  81. data/test/unit/db_query_metric_set_test.rb +11 -4
  82. data/test/unit/fake_store_test.rb +1 -1
  83. data/test/unit/git_revision_test.rb +3 -3
  84. data/test/unit/instruments/net_http_test.rb +2 -1
  85. data/test/unit/instruments/percentile_sampler_test.rb +5 -9
  86. data/test/unit/layaway_test.rb +10 -5
  87. data/test/unit/layer_converters/metric_converter_test.rb +2 -2
  88. data/test/unit/slow_request_policy_test.rb +7 -3
  89. data/test/unit/sql_sanitizer_test.rb +0 -6
  90. data/test/unit/store_test.rb +11 -8
  91. data/test/unit/utils/active_record_metric_name_test.rb +45 -7
  92. metadata +27 -5
  93. data/lib/scout_apm/agent/logging.rb +0 -74
  94. data/lib/scout_apm/agent/reporting.rb +0 -129
  95. data/lib/scout_apm/utils/null_logger.rb +0 -13
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1c1368bf440c4ba499c194aea1fd5b7c6e3f50d6
4
- data.tar.gz: 97282f623f2aa89e3dea524e54ff163966089a31
3
+ metadata.gz: 5f476cb8dead66959ed3a2e07a102d4d6ee50c79
4
+ data.tar.gz: 28266d8577a792a8faec963bf01e260360817e81
5
5
  SHA512:
6
- metadata.gz: 0a0b71432f8c8a1170f281c57f71a8a9fad671fc4374a28eeda03568fdf1e632611edc2287fb3a713f3f7f3c541ef857f778c1925de6b4b4b1227acaca3e6d77
7
- data.tar.gz: bffb7ea8daab689945ead233e3a8178fd9907bb9ee96eb924a4361d6e4db82e6526d35405d10970ff32d0eea01f6db92e76211c0df86d69488d6742a059a3ba4
6
+ metadata.gz: bd5647c9cd02c2fe3e2d670bd71af12cde9d7df06cca03e16561873ee02beab8dda5b5001ec3c397b9dbb1df9b31dde57ef3e7e1ef0ecfdd2755e4ecfae92f69
7
+ data.tar.gz: 031e7b683fc6ab6cd3c56a52e03a81a3e1a059c1ffbea594e0cc5ca0d3a67120ca7b35a31726becc0acaf39e54f9bd77d09488c3ed4c89bd524d56d07dcf959e
@@ -81,16 +81,18 @@ require 'scout_apm/instruments/elasticsearch'
81
81
  require 'scout_apm/instruments/active_record'
82
82
  require 'scout_apm/instruments/action_controller_rails_2'
83
83
  require 'scout_apm/instruments/action_controller_rails_3_rails4'
84
+ require 'scout_apm/instruments/action_view'
84
85
  require 'scout_apm/instruments/middleware_summary'
85
86
  require 'scout_apm/instruments/middleware_detailed' # Disabled by default, see the file for more details
86
87
  require 'scout_apm/instruments/rails_router'
87
88
  require 'scout_apm/instruments/grape'
88
89
  require 'scout_apm/instruments/sinatra'
90
+ require 'allocations'
91
+
89
92
  require 'scout_apm/instruments/process/process_cpu'
90
93
  require 'scout_apm/instruments/process/process_memory'
91
94
  require 'scout_apm/instruments/percentile_sampler'
92
- require 'scout_apm/instruments/action_view'
93
- require 'allocations'
95
+ require 'scout_apm/instruments/samplers'
94
96
 
95
97
  begin
96
98
  require 'stacks'
@@ -105,7 +107,6 @@ require 'scout_apm/utils/active_record_metric_name'
105
107
  require 'scout_apm/utils/backtrace_parser'
106
108
  require 'scout_apm/utils/installed_gems'
107
109
  require 'scout_apm/utils/klass_helper'
108
- require 'scout_apm/utils/null_logger'
109
110
  require 'scout_apm/utils/scm'
110
111
  require 'scout_apm/utils/sql_sanitizer'
111
112
  require 'scout_apm/utils/time'
@@ -116,8 +117,8 @@ require 'scout_apm/utils/gzip_helper'
116
117
  require 'scout_apm/config'
117
118
  require 'scout_apm/environment'
118
119
  require 'scout_apm/agent'
119
- require 'scout_apm/agent/logging'
120
- require 'scout_apm/agent/reporting'
120
+ require 'scout_apm/logger'
121
+ require 'scout_apm/reporting'
121
122
  require 'scout_apm/layaway'
122
123
  require 'scout_apm/layaway_file'
123
124
  require 'scout_apm/reporter'
@@ -171,6 +172,14 @@ require 'scout_apm/remote/message'
171
172
  require 'scout_apm/remote/recorder'
172
173
  require 'scout_apm/instruments/resque'
173
174
 
175
+ require 'scout_apm/agent_context'
176
+ require 'scout_apm/instrument_manager'
177
+ require 'scout_apm/periodic_work'
178
+ require 'scout_apm/agent/preconditions'
179
+ require 'scout_apm/agent/exit_handler'
180
+ require 'scout_apm/tasks/doctor'
181
+ require 'scout_apm/tasks/support'
182
+
174
183
  if defined?(Rails) && defined?(Rails::VERSION) && defined?(Rails::VERSION::MAJOR) && Rails::VERSION::MAJOR >= 3 && defined?(Rails::Railtie)
175
184
  module ScoutApm
176
185
  class Railtie < Rails::Railtie
@@ -181,17 +190,18 @@ if defined?(Rails) && defined?(Rails::VERSION) && defined?(Rails::VERSION::MAJOR
181
190
  app.middleware.use ScoutApm::Middleware
182
191
 
183
192
  # Attempt to start right away, this will work best for preloading apps, Unicorn & Puma & similar
184
- ScoutApm::Agent.instance.start
185
- end
186
- end
187
- class Railtie < Rails::Railtie
188
- initializer 'scout_apm.start' do |app|
193
+ ScoutApm::Agent.instance.install
194
+
189
195
  # Install the middleware every time in development mode.
190
196
  # The middleware is a noop if dev_trace is not enabled in config
191
197
  if Rails.env.development?
192
198
  app.middleware.use ScoutApm::Instant::Middleware
193
199
  end
194
200
  end
201
+
202
+ rake_tasks do
203
+ load "tasks/doctor.rake"
204
+ end
195
205
  end
196
206
  end
197
207
  else
@@ -1,240 +1,116 @@
1
1
  module ScoutApm
2
- # The agent gathers performance data from a Ruby application. One Agent instance is created per-Ruby process.
2
+ # The entry-point for the ScoutApm Agent.
3
3
  #
4
- # Each Agent object creates a worker thread (unless monitoring is disabled or we're forking).
5
- # The worker thread wakes up every +Agent#period+, merges in-memory metrics w/those saved to disk,
6
- # saves tshe merged data to disk, and sends it to the Scout server.
4
+ # Only one Agent instance is created per-Ruby process, and it coordinates the lifecycle of the monitoring.
5
+ # - initializes various data stores
6
+ # - coordinates configuration & logging
7
+ # - starts background threads, running periodically
8
+ # - installs shutdown hooks
7
9
  class Agent
8
10
  # see self.instance
9
11
  @@instance = nil
10
12
 
11
- # Accessors below are for associated classes
12
- attr_accessor :store
13
- attr_reader :recorder
14
- attr_accessor :layaway
15
- attr_accessor :config
16
- attr_accessor :logger
17
- attr_accessor :log_file # path to the log file
18
- attr_accessor :options # options passed to the agent when +#start+ is called.
19
- attr_accessor :metric_lookup # Hash used to lookup metric ids based on their name and scope
20
- attr_reader :slow_request_policy
21
- attr_reader :slow_job_policy
22
- attr_reader :process_start_time # used when creating slow transactions to report how far from startup the transaction was recorded.
23
- attr_reader :ignored_uris
13
+ attr_reader :context
24
14
 
25
- # Histogram of the cumulative requests since the start of the process
26
- attr_reader :request_histograms
15
+ attr_accessor :options # options passed to the agent when +#start+ is called.
27
16
 
28
- # Histogram of the requests, distinct by reporting period (minute)
29
- # { StoreReportingPeriodTimestamp => RequestHistograms }
30
- attr_reader :request_histograms_by_time
17
+ attr_reader :instrument_manager
31
18
 
32
19
  # All access to the agent is thru this class method to ensure multiple Agent instances are not initialized per-Ruby process.
33
20
  def self.instance(options = {})
34
21
  @@instance ||= self.new(options)
35
22
  end
36
23
 
37
- # Note - this doesn't start instruments or the worker thread. This is handled via +#start+ as we don't
38
- # want to start the worker thread or install instrumentation if (1) disabled for this environment (2) a worker thread shouldn't
39
- # be started (when forking).
24
+ # First call of the agent. Does very little so that the object can be created, and exist.
40
25
  def initialize(options = {})
41
- @started = false
42
- @process_start_time = Time.now
43
- @options ||= options
44
-
45
- # until the agent is started, there's no recorder
46
- @recorder = nil
47
-
48
- # Start up without attempting to load a configuration file. We need to be
49
- # able to lookup configuration options like "application_root" which would
50
- # then in turn influence where the configuration file came from.
51
- #
52
- # Later in initialization, we reset @config to include the file.
53
- @config = ScoutApm::Config.without_file
54
-
55
- @slow_request_policy = ScoutApm::SlowRequestPolicy.new
56
- @slow_job_policy = ScoutApm::SlowJobPolicy.new
57
- @request_histograms = ScoutApm::RequestHistograms.new
58
- @request_histograms_by_time = Hash.new { |h, k| h[k] = ScoutApm::RequestHistograms.new }
59
-
60
- @store = ScoutApm::Store.new
61
-
62
- @layaway = ScoutApm::Layaway.new(config, environment)
63
- @metric_lookup = Hash.new
64
-
65
- @installed_instruments = []
26
+ @options = options
27
+ @context = ScoutApm::AgentContext.new
66
28
  end
67
29
 
68
- def environment
69
- ScoutApm::Environment.instance
30
+ def logger
31
+ context.logger
70
32
  end
71
33
 
72
- def apm_enabled?
73
- config.value('monitor')
74
- end
34
+ # Finishes setting up the instrumentation, configuration, and attempts to start the agent.
35
+ def install(force=false)
36
+ context.config = ScoutApm::Config.with_file(context, context.config.value("config_file"))
75
37
 
76
- # If true, the agent will start regardless of safety checks. Currently just used for testing.
77
- def force?
78
- @options[:force]
79
- end
38
+ context.logger.info "Scout Agent [#{ScoutApm::VERSION}] Initialized"
80
39
 
81
- def preconditions_met?(options={})
82
- if !apm_enabled?
83
- logger.warn "Monitoring isn't enabled for the [#{environment.env}] environment. #{force? ? 'Forcing agent to start' : 'Not starting agent'}"
84
- return false unless force?
85
- end
40
+ @instrument_manager = ScoutApm::InstrumentManager.new(context)
41
+ @instrument_manager.install! if should_load_instruments? || force
86
42
 
87
- if !environment.application_name
88
- logger.warn "An application name could not be determined. Specify the :name value in scout_apm.yml. #{force? ? 'Forcing agent to start' : 'Not starting agent'}."
89
- return false unless force?
90
- end
43
+ install_background_job_integrations
44
+ install_app_server_integration
91
45
 
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
46
+ # XXX: Should this happen at application start?
47
+ # Should this ever happen after fork?
48
+ # We start a thread in this, which can screw stuff up when we then fork.
49
+ #
50
+ # Save it into a variable to prevent it from ever running twice
51
+ @app_server_load ||= AppServerLoad.new(context).run
96
52
 
97
- if app_server_missing?(options) && background_job_missing?
98
- if force?
99
- logger.warn "Agent starting (forced)"
100
- else
101
- logger.warn "Deferring agent start. Standing by for first request"
102
- end
103
- return false unless force?
104
- end
53
+ logger.info "Scout Agent [#{ScoutApm::VERSION}] installed"
105
54
 
106
- if started?
107
- logger.warn "Already started agent."
108
- return false
109
- end
55
+ context.installed!
110
56
 
111
- if defined?(::ScoutRails)
112
- logger.warn "ScoutAPM is incompatible with the old Scout Rails plugin. Please remove scout_rails from your Gemfile"
113
- return false unless force?
57
+ if ScoutApm::Agent::Preconditions.check?(context) || force
58
+ start
114
59
  end
115
-
116
- true
117
60
  end
118
61
 
119
- # This is called via +ScoutApm::Agent.instance.start+ when ScoutApm is required in a Ruby application.
120
- # It initializes the agent and starts the worker thread (if appropiate).
121
- def start(options = {})
122
- @options.merge!(options)
123
-
124
- @config = ScoutApm::Config.with_file(@config.value("config_file"))
125
- layaway.config = config
126
-
127
- init_logger
128
- logger.info "Attempting to start Scout Agent [#{ScoutApm::VERSION}] on [#{environment.hostname}]"
129
-
130
- @recorder = create_recorder
131
-
132
- @config.log_settings
133
-
134
- @ignored_uris = ScoutApm::IgnoredUris.new(config.value('ignore'))
135
-
136
- load_instruments if should_load_instruments?(options)
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")
62
+ # Unconditionally starts the agent. This includes verifying instruments are
63
+ # installed, and starting the background worker.
64
+ #
65
+ # Does not attempt to start twice.
66
+ def start(_opts={})
67
+ if context.started?
68
+ start_background_worker unless background_worker_running?
69
+ return
142
70
  end
143
71
 
144
- return false unless preconditions_met?(options)
145
- @started = true
146
- logger.info "Starting monitoring for [#{environment.application_name}]. Framework [#{environment.framework}] App Server [#{environment.app_server}] Background Job Framework [#{environment.background_job_name}]."
72
+ install unless context.installed?
147
73
 
148
- [ ScoutApm::Instruments::Process::ProcessCpu.new(environment.processors, logger),
149
- ScoutApm::Instruments::Process::ProcessMemory.new(logger),
150
- ScoutApm::Instruments::PercentileSampler.new(logger, request_histograms_by_time),
151
- ].each { |s| store.add_sampler(s) }
74
+ context.started!
152
75
 
153
- app_server_load_hook
76
+ log_environment
154
77
 
155
- if environment.background_job_integration
156
- environment.background_job_integration.install
157
- logger.info "Installed Background Job Integration [#{environment.background_job_name}]"
158
- end
159
-
160
- # start_background_worker? is true on non-forking servers, and directly
161
- # starts the background worker. On forking servers, a server-specific
162
- # hook is inserted to start the background worker after forking.
163
- if start_background_worker?
164
- start_background_worker
165
- logger.info "Scout Agent [#{ScoutApm::VERSION}] Initialized"
166
- else
167
- environment.app_server_integration.install
168
- logger.info "Scout Agent [#{ScoutApm::VERSION}] loaded in [#{environment.app_server}] master process. Monitoring will start after server forks its workers."
169
- end
78
+ start_background_worker
170
79
  end
171
80
 
172
- # Sends a ping to APM right away, smoothes out onboarding
173
- # Collects up any relevant info (framework, app server, system time, ruby version, etc)
174
- def app_server_load_hook
175
- AppServerLoad.new.run
176
- end
81
+ def log_environment
82
+ bg_names = context.environment.background_job_integrations.map{|bg| bg.name }.join(", ")
177
83
 
178
- def exit_handler_supported?
179
- if environment.sinatra?
180
- logger.debug "Exit handler not supported for Sinatra"
181
- false
182
- elsif environment.jruby?
183
- logger.debug "Exit handler not supported for JRuby"
184
- false
185
- elsif environment.rubinius?
186
- logger.debug "Exit handler not supported for Rubinius"
187
- false
188
- else
189
- true
190
- end
84
+ logger.info(
85
+ "Scout Agent [#{ScoutApm::VERSION}] starting for [#{context.environment.application_name}] " +
86
+ "Framework [#{context.environment.framework}] " +
87
+ "App Server [#{context.environment.app_server}] " +
88
+ "Background Job Framework [#{bg_names}] " +
89
+ "Hostname [#{context.environment.hostname}]"
90
+ )
191
91
  end
192
92
 
193
- # at_exit, calls Agent#shutdown to wrapup metric reporting.
194
- def install_exit_handler
195
- logger.debug "Shutdown handler not supported" and return unless exit_handler_supported?
196
- logger.debug "Installing Shutdown Handler"
197
-
198
- at_exit do
199
- logger.info "Shutting down Scout Agent"
200
- # MRI 1.9 bug drops exit codes.
201
- # http://bugs.ruby-lang.org/issues/5218
202
- if environment.ruby_19?
203
- status = $!.status if $!.is_a?(SystemExit)
204
- shutdown
205
- exit status if status
206
- else
207
- shutdown
208
- end
93
+ # Attempts to install all background job integrations. This can come up if
94
+ # an app has both Resque and Sidekiq - we want both to be installed if
95
+ # possible, it's no harm to have the "wrong" one also installed while running.
96
+ def install_background_job_integrations
97
+ context.environment.background_job_integrations.each do |int|
98
+ int.install
99
+ logger.info "Installed Background Job Integration [#{int.name}]"
209
100
  end
210
101
  end
211
102
 
212
- # Called via an at_exit handler, it:
213
- # (1) Stops the background worker
214
- # (2) Stores metrics locally (forcing current-minute metrics to be written)
215
- # It does not attempt to actually report metrics.
216
- def shutdown
217
- logger.info "Shutting down ScoutApm"
218
-
219
- return if !started?
220
-
221
- return if @shutdown
222
- @shutdown = true
223
-
224
- if @background_worker
225
- logger.info("Stopping background worker")
226
- @background_worker.stop
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
232
- end
233
- ScoutApm::Instruments::Stacks.uninstall
103
+ # This sets up the background worker thread to run at the correct time,
104
+ # either immediately, or after a fork into the actual unicorn/puma/etc
105
+ # worker
106
+ def install_app_server_integration
107
+ context.environment.app_server_integration.install
108
+ logger.info "Installed Application Server Integration [#{context.environment.app_server}]."
234
109
  end
235
110
 
236
- def started?
237
- @started
111
+ # If true, the agent will start regardless of safety checks.
112
+ def force?
113
+ @options[:force]
238
114
  end
239
115
 
240
116
  # The worker thread will automatically start UNLESS:
@@ -242,161 +118,80 @@ module ScoutApm
242
118
  # * A supported application server is detected, but it forks. In this case,
243
119
  # the agent is started in the forked process.
244
120
  def start_background_worker?
245
- return true if environment.app_server == :thin
246
- return true if environment.app_server == :webrick
247
121
  return true if force?
248
- return !environment.forking?
122
+ return !context.environment.forking?
249
123
  end
250
124
 
251
- def background_worker_running?
252
- @background_worker_thread &&
253
- @background_worker_thread.alive? &&
254
- @background_worker &&
255
- @background_worker.running?
125
+ def should_load_instruments?
126
+ return true if context.config.value('dev_trace')
127
+ # XXX: If monitor is true, we want to install, right?
128
+ # return false if context.config.value('monitor')
129
+ context.environment.app_server_integration.found? || context.environment.background_job_integration
256
130
  end
257
131
 
132
+ #################################
133
+ # Background Worker Lifecycle #
134
+ #################################
135
+
258
136
  # Creates the worker thread. The worker thread is a loop that runs continuously. It sleeps for +Agent#period+ and when it wakes,
259
137
  # processes data, either saving it to disk or reporting to Scout.
260
- def start_background_worker
261
- if !apm_enabled?
262
- logger.debug "Not starting background worker as monitoring isn't enabled."
138
+ # => true if thread & worker got started
139
+ # => false if it wasn't started (either due to already running, or other preconditions)
140
+ def start_background_worker(quiet=false)
141
+ if !context.config.value('monitor')
142
+ logger.debug "Not starting background worker as monitoring isn't enabled." unless quiet
263
143
  return false
264
144
  end
265
- logger.info "Not starting background worker, already started" and return if background_worker_running?
145
+
146
+ if background_worker_running?
147
+ logger.info "Not starting background worker, already started" unless quiet
148
+ return false
149
+ end
150
+
151
+ if context.shutting_down?
152
+ logger.info "Not starting background worker, already in process of shutting down" unless quiet
153
+ return false
154
+ end
155
+
266
156
  logger.info "Initializing worker thread."
267
157
 
268
- install_exit_handler
158
+ ScoutApm::Agent::ExitHandler.new(context).install
269
159
 
270
- if ScoutApm::Agent.instance.config.value('profile')
160
+ if context.config.value('profile')
271
161
  # After we fork, setup the handlers here.
272
162
  ScoutApm::Instruments::Stacks.install
273
163
  ScoutApm::Instruments::Stacks.start
274
164
  end
275
165
 
276
- @recorder = create_recorder
277
- logger.info("recorder is now: #{@recorder.class}")
166
+ periodic_work = ScoutApm::PeriodicWork.new(context)
278
167
 
279
- @background_worker = ScoutApm::BackgroundWorker.new
168
+ @background_worker = ScoutApm::BackgroundWorker.new(context)
280
169
  @background_worker_thread = Thread.new do
281
170
  @background_worker.start {
282
- ScoutApm::Debug.instance.call_periodic_hooks
283
- ScoutApm::Agent.instance.process_metrics
284
- clean_old_percentiles
171
+ periodic_work.run
285
172
  }
286
173
  end
287
- end
288
174
 
289
- def clean_old_percentiles
290
- request_histograms_by_time.
291
- keys.
292
- select {|timestamp| timestamp.age_in_seconds > 60 * 10 }.
293
- each {|old_timestamp| request_histograms_by_time.delete(old_timestamp) }
175
+ return true
294
176
  end
295
177
 
296
- # If we want to skip the app_server_check, then we must load it.
297
- def should_load_instruments?(options={})
298
- return true if options[:skip_app_server_check]
299
- return true if config.value('dev_trace')
300
- return false if !apm_enabled?
301
- environment.app_server_integration.found? || !background_job_missing?
302
- end
303
-
304
- # Loads the instrumention logic.
305
- def load_instruments
306
- case environment.framework
307
- when :rails then
308
- install_instrument(ScoutApm::Instruments::ActionControllerRails2)
309
- when :rails3_or_4 then
310
- install_instrument(ScoutApm::Instruments::ActionControllerRails3Rails4)
311
- install_instrument(ScoutApm::Instruments::RailsRouter)
312
-
313
- if config.value("detailed_middleware")
314
- install_instrument(ScoutApm::Instruments::MiddlewareDetailed)
315
- else
316
- install_instrument(ScoutApm::Instruments::MiddlewareSummary)
178
+ def stop_background_worker
179
+ if @background_worker
180
+ logger.info("Stopping background worker")
181
+ @background_worker.stop
182
+ context.store.write_to_layaway(context.layaway, :force)
183
+ if @background_worker_thread.alive?
184
+ @background_worker_thread.wakeup
185
+ @background_worker_thread.join
317
186
  end
318
187
  end
319
-
320
- install_instrument(ScoutApm::Instruments::ActionView)
321
- install_instrument(ScoutApm::Instruments::ActiveRecord)
322
- install_instrument(ScoutApm::Instruments::Moped)
323
- install_instrument(ScoutApm::Instruments::Mongoid)
324
- install_instrument(ScoutApm::Instruments::NetHttp)
325
- install_instrument(ScoutApm::Instruments::HttpClient)
326
- install_instrument(ScoutApm::Instruments::Redis)
327
- install_instrument(ScoutApm::Instruments::InfluxDB)
328
- install_instrument(ScoutApm::Instruments::Elasticsearch)
329
- install_instrument(ScoutApm::Instruments::Grape)
330
- rescue
331
- logger.warn "Exception loading instruments:"
332
- logger.warn $!.message
333
- logger.warn $!.backtrace
334
- end
335
-
336
- def install_instrument(instrument_klass)
337
- # Don't attempt to install the same instrument twice
338
- return if @installed_instruments.any? { |already_installed_instrument| instrument_klass === already_installed_instrument }
339
-
340
- # Allow users to skip individual instruments via the config file
341
- instrument_short_name = instrument_klass.name.split("::").last
342
- if (config.value("disabled_instruments") || []).include?(instrument_short_name)
343
- logger.info "Skipping Disabled Instrument: #{instrument_short_name} - To re-enable, change `disabled_instruments` key in scout_apm.yml"
344
- return
345
- end
346
-
347
- instance = instrument_klass.new
348
- @installed_instruments << instance
349
- instance.install
350
- end
351
-
352
- def app_server_missing?(options = {})
353
- !environment.app_server_integration(true).found? && !options[:skip_app_server_check]
354
- end
355
-
356
- def background_job_missing?(options = {})
357
- environment.background_job_integration.nil? && !options[:skip_background_job_check]
358
188
  end
359
189
 
360
- def clear_recorder
361
- @recorder = nil
362
- end
363
-
364
- def create_recorder
365
- if @recorder
366
- return @recorder
367
- end
368
-
369
- if config.value("async_recording")
370
- logger.debug("Using asynchronous recording")
371
- ScoutApm::BackgroundRecorder.new(logger).start
372
- else
373
- logger.debug("Using synchronous recording")
374
- ScoutApm::SynchronousRecorder.new(logger).start
375
- end
376
- end
377
-
378
- def start_remote_server(bind, port)
379
- return if @remote_server && @remote_server.running?
380
-
381
- logger.info("Starting Remote Agent Server")
382
-
383
- # Start the listening web server only in parent process.
384
- @remote_server = ScoutApm::Remote::Server.new(
385
- bind,
386
- port,
387
- ScoutApm::Remote::Router.new(ScoutApm::SynchronousRecorder.new(logger), logger),
388
- logger
389
- )
390
-
391
- @remote_server.start
392
- end
393
-
394
- # Execute this in the child process of a remote agent. The parent is
395
- # expected to have its accepting webserver up and running
396
- def use_remote_recorder(host, port)
397
- logger.debug("Becoming Remote Agent (reporting to: #{host}:#{port})")
398
- @recorder = ScoutApm::Remote::Recorder.new(host, port, logger)
399
- @store = ScoutApm::FakeStore.new
190
+ def background_worker_running?
191
+ @background_worker_thread &&
192
+ @background_worker_thread.alive? &&
193
+ @background_worker &&
194
+ @background_worker.running?
400
195
  end
401
196
  end
402
197
  end