Class: ScoutRails::Agent

Inherits:
Object
  • Object
show all
Includes:
Logging, Reporting
Defined in:
lib/scout_rails/agent.rb,
lib/scout_rails/agent/logging.rb,
lib/scout_rails/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 the merged data to disk, and sends it to the Scout server.

Defined Under Namespace

Modules: Logging, Reporting

Constant Summary collapse

HTTP_HEADERS =

Headers passed up with all API requests.

{ "Agent-Hostname" => Socket.gethostname }
@@instance =

see self.instance

nil

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Reporting

#add_metric_ids, #checkin_uri, #http, #post, #process_metrics, #request

Methods included from Logging

#apply_log_format, #init_logger, #log_level, #log_path

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



32
33
34
35
36
37
38
39
40
41
# File 'lib/scout_rails/agent.rb', line 32

def initialize(options = {})
  @started = false
  @options ||= options
  @store = ScoutRails::Store.new
  @layaway = ScoutRails::Layaway.new
  @config = ScoutRails::Config.new(options[:config_path])
  @metric_lookup = Hash.new
  @process_cpu=ScoutRails::Instruments::Process::ProcessCpu.new(environment.processors)
  @process_memory=ScoutRails::Instruments::Process::ProcessMemory.new
end

Instance Attribute Details

#configObject

Returns the value of attribute config.



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

def config
  @config
end

#environmentObject

Returns the value of attribute environment.



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

def environment
  @environment
end

#layawayObject

Returns the value of attribute layaway.



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

def layaway
  @layaway
end

#log_fileObject

path to the log file



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

def log_file
  @log_file
end

#loggerObject

Returns the value of attribute logger.



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

def logger
  @logger
end

#metric_lookupObject

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



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

def metric_lookup
  @metric_lookup
end

#optionsObject

options passed to the agent when #start is called.



21
22
23
# File 'lib/scout_rails/agent.rb', line 21

def options
  @options
end

#storeObject

Accessors below are for associated classes



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

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.



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

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

Instance Method Details

#gem_rootObject



111
112
113
# File 'lib/scout_rails/agent.rb', line 111

def gem_root
  File.expand_path(File.join("..","..",".."), __FILE__)
end

#handle_exitObject

at_exit, calls Agent#shutdown to wrapup metric reporting.



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/scout_rails/agent.rb', line 79

def handle_exit
  if environment.sinatra? || environment.jruby? || environment.rubinius?
    logger.debug "Exit handler not supported"
  else
    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 # at_exit
  end
end

#install_passenger_eventsObject



123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/scout_rails/agent.rb', line 123

def install_passenger_events
  PhusionPassenger.on_event(:starting_worker_process) do |forked|
    logger.debug "Passenger is starting a worker process. Starting worker thread."
    self.class.instance.start_background_worker
  end
  # The agent's at_exit hook doesn't run when a Passenger process stops. 
  # This does run when a process stops.
  PhusionPassenger.on_event(:stopping_worker_process) do
    logger.debug "Passenger is stopping a worker process, shutting down the agent."
    ScoutRails::Agent.instance.shutdown
  end
end

#install_rainbows_worker_loopObject



147
148
149
150
151
152
153
154
155
156
# File 'lib/scout_rails/agent.rb', line 147

def install_rainbows_worker_loop
  logger.debug "Installing Rainbows worker loop."
  Rainbows::HttpServer.class_eval do
    old = instance_method(:worker_loop)
    define_method(:worker_loop) do |worker|
      ScoutRails::Agent.instance.start_background_worker
      old.bind(self).call(worker)
    end
  end
end

#install_unicorn_worker_loopObject



136
137
138
139
140
141
142
143
144
145
# File 'lib/scout_rails/agent.rb', line 136

def install_unicorn_worker_loop
  logger.debug "Installing Unicorn worker loop."
  Unicorn::HttpServer.class_eval do
    old = instance_method(:worker_loop)
    define_method(:worker_loop) do |worker|
      ScoutRails::Agent.instance.start_background_worker
      old.bind(self).call(worker)
    end
  end
end

#load_instrumentsObject

Loads the instrumention logic.



193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/scout_rails/agent.rb', line 193

def load_instruments
  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

#run_samplersObject

Called from #process_metrics, which is run via the background worker.



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/scout_rails/agent.rb', line 170

def run_samplers
  begin
    cpu_util=@process_cpu.run # returns a hash
    logger.debug "Process CPU: #{cpu_util.inspect} [#{environment.processors} CPU(s)]"
    store.track!("CPU/Utilization",cpu_util,:scope => nil) if cpu_util
  rescue => e
    logger.info "Error reading ProcessCpu"
    logger.debug e.message
    logger.debug e.backtrace.join("\n")
  end

  begin
    mem_usage=@process_memory.run # returns a single number, in MB
    logger.debug "Process Memory: #{mem_usage}MB"
    store.track!("Memory/Physical",mem_usage,:scope => nil) if mem_usage
  rescue => e
    logger.info "Error reading ProcessMemory"
    logger.debug e.message
    logger.debug e.backtrace.join("\n")
  end
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.



101
102
103
104
105
# File 'lib/scout_rails/agent.rb', line 101

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

#start(options = {}) ⇒ Object

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



49
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
# File 'lib/scout_rails/agent.rb', line 49

def start(options = {})
  @options.merge!(options)
  init_logger
  logger.info "Attempting to start Scout Agent [#{ScoutRails::VERSION}] on [#{Socket.gethostname}]"
  if !config.settings['monitor'] and !@options[:force]
    logger.warn "Monitoring isn't enabled for the [#{environment.env}] environment."
    return false
  elsif !environment.app_server
    logger.warn "Couldn't find a supported app server. Not starting agent."
    return false
  elsif started?
    logger.warn "Already started agent."
    return false
  end
  @started = true
  logger.info "Starting monitoring. Framework [#{environment.framework}] App Server [#{environment.app_server}]."
  start_instruments
  if !start_background_worker?
    logger.debug "Not starting worker thread"
    install_passenger_events if environment.app_server == :passenger
    install_unicorn_worker_loop if environment.app_server == :unicorn
    install_rainbows_worker_loop if environment.app_server == :rainbows
    return
  end
  start_background_worker
  handle_exit
  logger.info "Scout Agent [#{ScoutRails::VERSION}] Initialized"
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.



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

def start_background_worker
  logger.debug "Creating worker thread."
  @background_worker = ScoutRails::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 (Passenger). In this case, the agent is started in the forked process.

Returns:

  • (Boolean)


119
120
121
# File 'lib/scout_rails/agent.rb', line 119

def start_background_worker?
  !environment.forking? or environment.app_server == :thin
end

#start_instrumentsObject

Injects instruments into the Ruby application.



211
212
213
214
# File 'lib/scout_rails/agent.rb', line 211

def start_instruments
  logger.debug "Installing instrumentation"
  load_instruments
end

#started?Boolean

Returns:

  • (Boolean)


107
108
109
# File 'lib/scout_rails/agent.rb', line 107

def started?
  @started
end