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


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

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)



110
111
112
# File 'lib/scout_apm/agent.rb', line 110

def app_server_load_hook
  AppServerLoad.new.run
end

#environmentObject



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

def environment
  ScoutApm::Environment.instance
end

#exit_handler_unsupported?Boolean

Returns:

  • (Boolean)


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

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

#handle_exitObject

at_exit, calls Agent#shutdown to wrapup metric reporting.



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/scout_apm/agent.rb', line 119

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

  at_exit do
    logger.debug "Shutdown!"
    # 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

#load_instrumentsObject

Loads the instrumention logic.



175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/scout_apm/agent.rb', line 175

def load_instruments
  logger.debug "Installing instrumentation"

  case environment.framework
  when :rails
    require File.expand_path(File.join(File.dirname(__FILE__),'instruments/rails/action_controller_instruments.rb'))
  when :rails3_or_4
    require File.expand_path(File.join(File.dirname(__FILE__),'instruments/rails3_or_4/action_controller_instruments.rb'))
  end
  require File.expand_path(File.join(File.dirname(__FILE__),'instruments/active_record_instruments.rb'))
  require File.expand_path(File.join(File.dirname(__FILE__),'instruments/net_http.rb'))
  require File.expand_path(File.join(File.dirname(__FILE__),'instruments/moped_instruments.rb'))
  require File.expand_path(File.join(File.dirname(__FILE__),'instruments/mongoid_instruments.rb'))
rescue
  logger.warn "Exception loading instruments:"
  logger.warn $!.message
  logger.warn $!.backtrace
end

#preconditions_met?Boolean

Returns:

  • (Boolean)


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

def preconditions_met?
  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.found?
    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

  true
end

#should_load_instruments?Boolean

Returns:

  • (Boolean)


170
171
172
# File 'lib/scout_apm/agent.rb', line 170

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.



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

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



75
76
77
78
79
80
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
# File 'lib/scout_apm/agent.rb', line 75

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

  return false unless preconditions_met?

  @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

  if start_background_worker? # TODO: Clarify name. This is not the path that unicorn workers take....
    # This branch fires only on non-forking servers, directly starts the
    # background thread and then requests are served.
    start_background_worker
    handle_exit
    logger.info "Scout Agent [#{ScoutApm::VERSION}] Initialized"
  else
    # This branch fires on.... master only? of forking servers
    logger.debug "Not starting worker thread. Will start worker loops after forking."
    environment.app_server_integration.install
  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.



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

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


153
154
155
156
157
# File 'lib/scout_apm/agent.rb', line 153

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)


145
146
147
# File 'lib/scout_apm/agent.rb', line 145

def started?
  @started
end