Class: ScoutApm::Agent

Inherits:
Object
  • Object
show all
Includes:
Logging, Reporting
Defined in:
lib/scout_apm/agent.rb,
lib/scout_apm/agent/logging.rb,
lib/scout_apm/agent/reporting.rb

Overview

The agent gathers performance data from a Ruby application. One Agent instance is created per-Ruby process.

Each Agent object creates a worker thread (unless monitoring is disabled or we’re forking). The worker thread wakes up every Agent#period, merges in-memory metrics w/those saved to disk, saves tshe merged data to disk, and sends it to the Scout server.

Defined Under Namespace

Modules: Logging, Reporting

Constant Summary collapse

@@instance =

see self.instance

nil

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Reporting

#add_metric_ids, #process_metrics, #reporter, #run_samplers

Methods included from Logging

#apply_log_format, #default_log_path, #init_logger, #log_file_path, #log_level, #wants_stdout?

Constructor Details

#initialize(options = {}) ⇒ Agent

Note - this doesn’t start instruments or the worker thread. This is handled via #start as we don’t want to start the worker thread or install instrumentation if (1) disabled for this environment (2) a worker thread shouldn’t be started (when forking).



29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/scout_apm/agent.rb', line 29

def initialize(options = {})
  @started = false
  @options ||= options
  @config = ScoutApm::Config.new(options[:config_path])

  @store          = ScoutApm::Store.new
  @layaway        = ScoutApm::Layaway.new
  @metric_lookup  = Hash.new

  @capacity       = ScoutApm::Capacity.new
  @installed_instruments = []
end

Instance Attribute Details

#capacityObject

Returns the value of attribute capacity.



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

def capacity
  @capacity
end

#configObject

Returns the value of attribute config.



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

def config
  @config
end

#layawayObject

Returns the value of attribute layaway.



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

def layaway
  @layaway
end

#log_fileObject

path to the log file



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

def log_file
  @log_file
end

#loggerObject

Returns the value of attribute logger.



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

def logger
  @logger
end

#metric_lookupObject

Hash used to lookup metric ids based on their name and scope



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

def metric_lookup
  @metric_lookup
end

#optionsObject

options passed to the agent when #start is called.



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

def options
  @options
end

#storeObject

Accessors below are for associated classes



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

def store
  @store
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.



22
23
24
# File 'lib/scout_apm/agent.rb', line 22

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

Instance Method Details

#apm_enabled?Boolean

Returns:

  • (Boolean)


46
47
48
# File 'lib/scout_apm/agent.rb', line 46

def apm_enabled?
  config.value('monitor') and !@options[:force]
end

#app_server_load_hookObject

Sends a ping to APM right away, smoothes out onboarding Collects up any relevant info (framework, app server, system time, ruby version, etc)



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

def app_server_load_hook
  AppServerLoad.new.run
end

#deploy_integrationObject



214
215
216
# File 'lib/scout_apm/agent.rb', line 214

def deploy_integration
  environment.deploy_integration
end

#environmentObject



42
43
44
# File 'lib/scout_apm/agent.rb', line 42

def environment
  ScoutApm::Environment.instance
end

#exit_handler_unsupported?Boolean

Returns:

  • (Boolean)


125
126
127
# File 'lib/scout_apm/agent.rb', line 125

def exit_handler_unsupported?
  environment.sinatra? || environment.jruby? || environment.rubinius?
end

#handle_exitObject

at_exit, calls Agent#shutdown to wrapup metric reporting.



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/scout_apm/agent.rb', line 130

def handle_exit
  logger.debug "Exit handler not supported" and return if exit_handler_unsupported?

  at_exit do
    logger.info "Shutting down Scout Agent"
    # MRI 1.9 bug drops exit codes.
    # http://bugs.ruby-lang.org/issues/5218
    if environment.ruby_19?
      status = $!.status if $!.is_a?(SystemExit)
      shutdown
      exit status if status
    else
      shutdown
    end
  end
end

#install_instrument(instrument_klass) ⇒ Object



206
207
208
209
210
211
212
# File 'lib/scout_apm/agent.rb', line 206

def install_instrument(instrument_klass)
  # Don't attempt to install the same instrument twice
  return if @installed_instruments.any? { |already_installed_instrument| instrument_klass === already_installed_instrument }
  instance = instrument_klass.new
  @installed_instruments << instance
  instance.install
end

#load_instrumentsObject

Loads the instrumention logic.



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/scout_apm/agent.rb', line 185

def load_instruments
  case environment.framework
  when :rails       then install_instrument(ScoutApm::Instruments::ActionControllerRails2)
  when :rails3_or_4 then install_instrument(ScoutApm::Instruments::ActionControllerRails3)
  when :sinatra     then install_instrument(ScoutApm::Instruments::Sinatra)
  end

  install_instrument(ScoutApm::Instruments::ActiveRecord)
  install_instrument(ScoutApm::Instruments::Moped)
  install_instrument(ScoutApm::Instruments::Mongoid)
  install_instrument(ScoutApm::Instruments::NetHttp)

  if StackProf.respond_to?(:fake?) && StackProf.fake?
    logger.info 'StackProf not found - add `gem "stackprof"` to your Gemfile to enable advanced code profiling (only for Ruby 2.1+)'
  end
rescue
  logger.warn "Exception loading instruments:"
  logger.warn $!.message
  logger.warn $!.backtrace
end

#preconditions_met?(options = {}) ⇒ Boolean

Returns:

  • (Boolean)


50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/scout_apm/agent.rb', line 50

def preconditions_met?(options={})
  if !apm_enabled?
    logger.warn "Monitoring isn't enabled for the [#{environment.env}] environment."
    return false
  end

  if !environment.application_name
    logger.warn "An application name could not be determined. Specify the :name value in scout_apm.yml. Not starting agent."
    return false
  end

  if !environment.app_server_integration(true).found? && !options[:skip_app_server_check]
    logger.warn "Couldn't find a supported app server. Not starting agent."
    return false
  end

  if started?
    logger.warn "Already started agent."
    return false
  end

  if defined?(::ScoutRails)
    logger.warn "ScoutAPM is incompatible with the old Scout Rails plugin. Please remove scout_rails from your Gemfile"
    return false
  end

  true
end

#should_load_instruments?Boolean

Returns:

  • (Boolean)


180
181
182
# File 'lib/scout_apm/agent.rb', line 180

def should_load_instruments?
  environment.app_server_integration.found?
end

#shutdownObject

Called via an at_exit handler, it (1) stops the background worker and (2) runs it a final time. The final run ensures metrics are stored locally to the layaway / reported to scoutapp.com. Otherwise, in-memory metrics would be lost and a gap would appear on restarts.



150
151
152
153
154
# File 'lib/scout_apm/agent.rb', line 150

def shutdown
  return if !started?
  @background_worker.stop
  @background_worker.run_once
end

#start(options = {}) ⇒ Object

This is called via ScoutApm::Agent.instance.start when ScoutApm is required in a Ruby application. It initializes the agent and starts the worker thread (if appropiate).



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/scout_apm/agent.rb', line 81

def start(options = {})
  @options.merge!(options)
  init_logger
  logger.info "Attempting to start Scout Agent [#{ScoutApm::VERSION}] on [#{environment.hostname}]"

  if environment.deploy_integration
    logger.info "Starting monitoring for [#{environment.deploy_integration.name}]]."
    return environment.deploy_integration.install
  end

  return false unless preconditions_met?(options)

  @started = true

  logger.info "Starting monitoring for [#{environment.application_name}]. Framework [#{environment.framework}] App Server [#{environment.app_server}]."

  load_instruments if should_load_instruments?

  @samplers = [
    ScoutApm::Instruments::Process::ProcessCpu.new(environment.processors, logger),
    ScoutApm::Instruments::Process::ProcessMemory.new(logger)
  ]

  app_server_load_hook

  # start_background_worker? is true on non-forking servers, and directly
  # starts the background worker.  On forking servers, a server-specific
  # hook is inserted to start the background worker after forking.
  if start_background_worker?
    start_background_worker
    handle_exit
    logger.info "Scout Agent [#{ScoutApm::VERSION}] Initialized"
  else
    environment.app_server_integration.install
    logger.info "Scout Agent [#{ScoutApm::VERSION}] loaded in [#{environment.app_server}] master process. Monitoring will start after server forks its workers."
  end
end

#start_background_workerObject

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.



172
173
174
175
176
177
178
# File 'lib/scout_apm/agent.rb', line 172

def start_background_worker
  logger.info "Initializing worker thread."
  @background_worker = ScoutApm::BackgroundWorker.new
  @background_worker_thread = Thread.new do
    @background_worker.start { process_metrics }
  end
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)


164
165
166
167
168
# File 'lib/scout_apm/agent.rb', line 164

def start_background_worker?
  return true if environment.app_server == :thin
  return true if environment.app_server == :webrick
  return !environment.forking?
end

#started?Boolean

Returns:

  • (Boolean)


156
157
158
# File 'lib/scout_apm/agent.rb', line 156

def started?
  @started
end