class ScoutApm::Agent

The entry-point for the ScoutApm Agent.

Only one Agent instance is created per-Ruby process, and it coordinates the lifecycle of the monitoring.

- initializes various data stores
- coordinates configuration & logging
- starts background threads, running periodically
- installs shutdown hooks

Constants

ERROR_SEND_FREQUENCY

seconds to batch error reports

Attributes

context[R]
instrument_manager[R]
options[RW]

Public Class Methods

instance(options = {}) click to toggle source

All access to the agent is thru this class method to ensure multiple Agent instances are not initialized per-Ruby process.

# File lib/scout_apm/agent.rb, line 20
def self.instance(options = {})
  @@instance ||= self.new(options)
end
new(options = {}) click to toggle source

First call of the agent. Does very little so that the object can be created, and exist.

# File lib/scout_apm/agent.rb, line 25
def initialize(options = {})
  @options = options
  @context = ScoutApm::AgentContext.new
end

Public Instance Methods

background_worker_running?() click to toggle source
# File lib/scout_apm/agent.rb, line 197
def background_worker_running?
  @background_worker_thread          &&
    @background_worker_thread.alive? &&
    @background_worker               &&
    @background_worker.running?
end
error_service_background_worker_running?() click to toggle source
# File lib/scout_apm/agent.rb, line 217
def error_service_background_worker_running?
  @error_service_background_worker_thread          &&
    @error_service_background_worker_thread.alive? &&
    @error_service_background_worker               &&
    @error_service_background_worker.running?
end
force?() click to toggle source

If true, the agent will start regardless of safety checks.

# File lib/scout_apm/agent.rb, line 123
def force?
  @options[:force]
end
install(force=false) click to toggle source

Finishes setting up the instrumentation, configuration, and attempts to start the agent.

# File lib/scout_apm/agent.rb, line 35
def install(force=false)
  context.config = ScoutApm::Config.with_file(context, context.config.value("config_file"))

  logger.info "Scout Agent [#{ScoutApm::VERSION}] Initialized"

  if should_load_instruments? || force
    instrument_manager.install!
    install_background_job_integrations
    install_app_server_integration
  else
    logger.info "Not Loading Instruments"
  end

  logger.info "Scout Agent [#{ScoutApm::VERSION}] Installed"

  context.installed!

  @preconditions = ScoutApm::Agent::Preconditions.new
  if @preconditions.check?(context) || force
    start
  end
end
install_app_server_integration() click to toggle source

This sets up the background worker thread to run at the correct time, either immediately, or after a fork into the actual unicorn/puma/etc worker

# File lib/scout_apm/agent.rb, line 117
def install_app_server_integration
  context.environment.app_server_integration.install
  logger.info "Installed Application Server Integration [#{context.environment.app_server}]."
end
install_background_job_integrations() click to toggle source

Attempts to install all background job integrations. This can come up if an app has both Resque and Sidekiq - we want both to be installed if possible, it's no harm to have the “wrong” one also installed while running.

# File lib/scout_apm/agent.rb, line 107
def install_background_job_integrations
  context.environment.background_job_integrations.each do |int|
    int.install
    logger.info "Installed Background Job Integration [#{int.name}]"
  end
end
log_environment() click to toggle source
# File lib/scout_apm/agent.rb, line 92
def log_environment
  bg_names = context.environment.background_job_integrations.map{|bg| bg.name }.join(", ")

  logger.info(
    "Scout Agent [#{ScoutApm::VERSION}] starting for [#{context.environment.application_name}] " +
    "Framework [#{context.environment.framework}] " +
    "App Server [#{context.environment.app_server}] " +
    "Background Job Framework [#{bg_names}] " +
    "Hostname [#{context.environment.hostname}]"
  )
end
logger() click to toggle source
# File lib/scout_apm/agent.rb, line 30
def logger
  context.logger
end
should_load_instruments?() click to toggle source

monitor is the key configuration here. If it is true, then we want the instruments. If it is false, we mostly don't want them, unless you're asking for devtrace (ie. not reporting to apm servers as a real app, but only for local browsers).

# File lib/scout_apm/agent.rb, line 140
def should_load_instruments?
  return true if context.config.value('dev_trace')
  context.config.value('monitor')
end
start(opts={}) click to toggle source

Unconditionally starts the agent. This includes verifying instruments are installed, and starting the background worker.

The monitor precondition is checked explicitly, and we will never start with monitor = false

This does not attempt to start twice

# File lib/scout_apm/agent.rb, line 64
def start(opts={})
  return unless context.config.value('monitor')

  if context.started?
    start_background_worker unless background_worker_running?
    start_error_service_background_worker unless error_service_background_worker_running?
    return
  end

  install unless context.installed?

  instrument_manager.install! if should_load_instruments?

  context.started!

  log_environment

  # Save it into a variable to prevent it from ever running twice
  @app_server_load ||= AppServerLoad.new(context).run

  start_background_worker
  start_error_service_background_worker
end
start_background_worker(quiet=false) click to toggle source

Creates the worker thread. The worker thread is a loop that runs continuously. It sleeps for +Agent#period+ and when it wakes, processes data, either saving it to disk or reporting to Scout.

> true if thread & worker got started

> false if it wasn't started (either due to already running, or other preconditions)

# File lib/scout_apm/agent.rb, line 153
def start_background_worker(quiet=false)
  if !context.config.value('monitor')
    logger.debug "Not starting background worker as monitoring isn't enabled." unless quiet
    return false
  end

  if background_worker_running?
    logger.info "Not starting background worker, already started" unless quiet
    return false
  end

  if context.shutting_down?
    logger.info "Not starting background worker, already in process of shutting down" unless quiet
    return false
  end

  logger.info "Initializing worker thread."

  ScoutApm::Agent::ExitHandler.new(context).install

  periodic_work = ScoutApm::PeriodicWork.new(context)

  @background_worker = ScoutApm::BackgroundWorker.new(context)
  @background_worker_thread = Thread.new do
    @background_worker.start {
      periodic_work.run
    }
  end

  return true
end
start_background_worker?() click to toggle source

The worker thread will automatically start UNLESS:

  • A supported application server isn't detected (example: running via Rails console)

  • A supported application server is detected, but it forks. In this case, the agent is started in the forked process.

# File lib/scout_apm/agent.rb, line 131
def start_background_worker?
  return true if force?
  return !context.environment.forking?
end
start_error_service_background_worker() click to toggle source
# File lib/scout_apm/agent.rb, line 206
def start_error_service_background_worker
  periodic_work = ScoutApm::ErrorService::PeriodicWork.new(context)

  @error_service_background_worker = ScoutApm::BackgroundWorker.new(context, ERROR_SEND_FREQUENCY)
  @error_service_background_worker_thread = Thread.new do
    @error_service_background_worker.start { 
      periodic_work.run
    }
  end
end
stop_background_worker() click to toggle source
# File lib/scout_apm/agent.rb, line 185
def stop_background_worker
  if @background_worker
    logger.info("Stopping background worker")
    @background_worker.stop
    context.store.write_to_layaway(context.layaway, :force)
    if @background_worker_thread.alive?
      @background_worker_thread.wakeup
      @background_worker_thread.join
    end
  end
end