Class: ScoutApm::Agent

Inherits:
Object
  • Object
show all
Defined in:
lib/scout_apm/agent.rb,
lib/scout_apm/agent/exit_handler.rb,
lib/scout_apm/agent/preconditions.rb

Overview

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

Defined Under Namespace

Classes: ExitHandler, Preconditions

Constant Summary collapse

@@instance =

see self.instance

nil

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Agent

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



25
26
27
28
# File 'lib/scout_apm/agent.rb', line 25

def initialize(options = {})
  @options = options
  @context = ScoutApm::AgentContext.new
end

Instance Attribute Details

#contextObject (readonly)

Returns the value of attribute context.



13
14
15
# File 'lib/scout_apm/agent.rb', line 13

def context
  @context
end

#instrument_managerObject (readonly)

Returns the value of attribute instrument_manager.



17
18
19
# File 'lib/scout_apm/agent.rb', line 17

def instrument_manager
  @instrument_manager
end

#optionsObject

options passed to the agent when #start is called.



15
16
17
# File 'lib/scout_apm/agent.rb', line 15

def options
  @options
end

Class Method Details

.instance(options = {}) ⇒ Object

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



20
21
22
# File 'lib/scout_apm/agent.rb', line 20

def self.instance(options = {})
  @@instance ||= self.new(options)
end

Instance Method Details

#background_worker_running?Boolean

Returns:

  • (Boolean)


195
196
197
198
199
200
# File 'lib/scout_apm/agent.rb', line 195

def background_worker_running?
  @background_worker_thread          &&
    @background_worker_thread.alive? &&
    @background_worker               &&
    @background_worker.running?
end

#force?Boolean

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

Returns:

  • (Boolean)


121
122
123
# File 'lib/scout_apm/agent.rb', line 121

def force?
  @options[:force]
end

#install(force = false) ⇒ Object

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



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# 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_integrationObject

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



115
116
117
118
# File 'lib/scout_apm/agent.rb', line 115

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_integrationsObject

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.



105
106
107
108
109
110
# File 'lib/scout_apm/agent.rb', line 105

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_environmentObject



90
91
92
93
94
95
96
97
98
99
100
# File 'lib/scout_apm/agent.rb', line 90

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

#loggerObject



30
31
32
# File 'lib/scout_apm/agent.rb', line 30

def logger
  context.logger
end

#should_load_instruments?Boolean

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

Returns:

  • (Boolean)


138
139
140
141
# File 'lib/scout_apm/agent.rb', line 138

def should_load_instruments?
  return true if context.config.value('dev_trace')
  context.config.value('monitor')
end

#start(opts = {}) ⇒ Object

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



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# 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?
    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
end

#start_background_worker(quiet = false) ⇒ Object

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)



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/scout_apm/agent.rb', line 151

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?Boolean

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.

Returns:

  • (Boolean)


129
130
131
132
# File 'lib/scout_apm/agent.rb', line 129

def start_background_worker?
  return true if force?
  return !context.environment.forking?
end

#stop_background_workerObject



183
184
185
186
187
188
189
190
191
192
193
# File 'lib/scout_apm/agent.rb', line 183

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