class PassengerMonitor::Monitor

Constants

DEFAULT_LOG_FILE
DEFAULT_MEMORY_LIMIT

Default allowed memory limit for a passenger worker (MB)

DEFAULT_PROCESS_NAME_REGEX
DEFAULT_WAIT_TIME

default waiting time after graceful kill attempt to kill process forcefully

Public Class Methods

new(params = {}) click to toggle source

Sets memory limit, log file, wait time, process name regex and logger

# File lib/passenger_monitor/monitor.rb, line 29
def initialize(params = {})
  params = params.to_hash
  @memory_limit = fetch_memory_limit(params)
  @log_file = fetch_log_file(params)
  @wait_time = fetch_wait_time(params)
  @process_name_regex = fetch_process_name_regex(params)
  @logger = Logger.new(@log_file)
end
run(config = {}) click to toggle source

Initialize the service and apply a check on all passenger processes given `configurations` (defaults to `{}`)

Parameters:

config

Hash which includes the configurations keys i.e.

1. :memory_limit - allowed memory limit for a passenger worker
2. :log_file - the name of the log file
3. :wait_time - the time to wait to kill the worker forcefully
4. :process_name_regex - regex for the passenger worker of the application
# File lib/passenger_monitor/monitor.rb, line 23
def self.run(config = {})
  new(config).check
end

Public Instance Methods

check() click to toggle source

Checks memory of all the passenger processes and for bloadted workers it creates thread for each to kill it.

# File lib/passenger_monitor/monitor.rb, line 41
def check
  @logger.info 'Checking bloated Passenger workers'

  threads = []

  passenger_workers_details.each_line do |line|
    next unless (line =~ @process_name_regex)

    pid, memory_usage =  extract_stats(line)

    # If a given passenger process is bloated try to
    # kill it gracefully and if it fails, force killing it
    if bloated?(pid, memory_usage)
      threads << Thread.new { self.handle_bloated_process(pid) }
    end
  end

  threads.map(&:join)
  @logger.info 'Finished checking for bloated Passenger workers'
end
handle_bloated_process(pid) click to toggle source

Handles bloated processes:

1. Kill it gracefully
2. Wait for the given time
3. if it still exists then kill it forcefully.

Parameters:

pid

Process ID.

# File lib/passenger_monitor/monitor.rb, line 72
def handle_bloated_process(pid)
  kill(pid)
  wait
  kill!(pid) if process_running?(pid)
end

Private Instance Methods

bloated?(pid, size) click to toggle source

Check if a given process is exceeding memory limit

# File lib/passenger_monitor/monitor.rb, line 143
def bloated?(pid, size)
  bloated = size > @memory_limit
  @logger.error "Found bloated worker: #{pid} - #{size}MB" if bloated
  bloated
end
extract_stats(line) click to toggle source

Extracts pid and memory usage of a single Passenger worker

# File lib/passenger_monitor/monitor.rb, line 137
def extract_stats(line)
  stats = line.split
  return stats[0].to_i, stats[3].to_f
end
fetch_log_file(params) click to toggle source
# File lib/passenger_monitor/monitor.rb, line 84
def fetch_log_file(params)
  params.key?(:log_file) ? params[:log_file] : DEFAULT_LOG_FILE
end
fetch_memory_limit(params) click to toggle source
# File lib/passenger_monitor/monitor.rb, line 80
def fetch_memory_limit(params)
  params.key?(:memory_limit) ? params[:memory_limit].to_f : DEFAULT_MEMORY_LIMIT
end
fetch_process_name_regex(params) click to toggle source
# File lib/passenger_monitor/monitor.rb, line 92
def fetch_process_name_regex(params)
  if params.key?(:process_name_regex)
    Regexp.new(params[:process_name_regex].to_s)
  else
    DEFAULT_PROCESS_NAME_REGEX
  end
end
fetch_wait_time(params) click to toggle source
# File lib/passenger_monitor/monitor.rb, line 88
def fetch_wait_time(params)
  params.key?(:wait_time) ? params[:wait_time].to_i : DEFAULT_WAIT_TIME
end
kill(pid) click to toggle source

Kill it gracefully

# File lib/passenger_monitor/monitor.rb, line 125
def kill(pid)
  @logger.error "Trying to kill #{pid} gracefully..."
  Process.kill("SIGUSR1", pid)
end
kill!(pid) click to toggle source

Kill it forcefully

# File lib/passenger_monitor/monitor.rb, line 131
def kill!(pid)
  @logger.fatal "Force kill: #{pid}"
  Process.kill("TERM", pid)
end
passenger_workers_details() click to toggle source

Fetches the stats of passenger-memory-stats from system. Using `env -i` to remove surrounding environment of bundle.

# File lib/passenger_monitor/monitor.rb, line 102
def passenger_workers_details
  passenger_memory_status_path = `env -i which passenger-memory-stats`
  `env -i #{ passenger_memory_status_path }`
end
process_running?(pid) click to toggle source

Checks if a given process is still running

Parameters:

pid

Process ID.

# File lib/passenger_monitor/monitor.rb, line 112
def process_running?(pid)
  Process.getpgid(pid) != -1
rescue Errno::ESRCH
  false
end
wait() click to toggle source

Wait for process to be killed

# File lib/passenger_monitor/monitor.rb, line 119
def wait
  @logger.error "Waiting for worker to shutdown..."
  sleep(DEFAULT_WAIT_TIME)
end