scout_apm 3.0.0.pre13 → 3.0.0.pre14

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