Skip to content

Instantly share code, notes, and snippets.

@cinsk
Last active August 29, 2015 14:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cinsk/f8375d0b0169bacabc54 to your computer and use it in GitHub Desktop.
Save cinsk/f8375d0b0169bacabc54 to your computer and use it in GitHub Desktop.
Check the status of the processes (# of processes, # of fds, memory usage, etc.) in Nagios format
#!/usr/bin/env ruby
require 'timeout'
require 'getoptlong'
RLIMIT_MAP = {
"Max cpu time" => Process::RLIMIT_CPU,
"Max file size" => Process::RLIMIT_FSIZE,
"Max data size" => Process::RLIMIT_DATA,
"Max stack size" => Process::RLIMIT_STACK,
"Max core file size" => Process::RLIMIT_CORE,
"Max resident set" => Process::RLIMIT_RSS,
"Max processes" => Process::RLIMIT_NPROC,
"Max open files" => Process::RLIMIT_NOFILE,
"Max locked memory" => Process::RLIMIT_MEMLOCK,
"Max address space" => Process::RLIMIT_AS,
}
fdopen_safe_percent = nil
mem_safe_percent = nil
def rlimits(pid = nil)
pid = Process.pid unless pid
lims = {}
IO.foreach("/proc/#{pid}/limits") do |line|
fields = line.chomp.split(/[[:space:]][[:space:]]+/)
key = RLIMIT_MAP[fields[0]]
if key
soft = fields[1]
hard = fields[2]
soft = (soft == "unlimited") ? Process::RLIM_INFINITY : soft.to_i
hard = (hard == "unlimited") ? Process::RLIM_INFINITY : hard.to_i
lims[key] = [soft, hard]
end
end
lims
rescue
nil
end
def ps_by_command(cmdname)
ps = {}
Timeout.timeout(1) do
IO.popen("ps -C #{cmdname} -o pid=,etime=,nlwp=,%mem=") do |io|
while line = io.gets
toks = line.strip.chomp.split(/ +/)
continue if toks.length != 4
p = {}
pid = toks[0].to_i
p[:pid] = pid
p[:elapsed] = toks[1]
p[:nlwp] = toks[2].to_i
p[:mem] = toks[3].to_f
ps[pid] = p
end
end
end
ps
rescue Timeout::Error => ex
puts "timeout: #{ex}"
ps
end
def proc_fd_info(pid = nil)
pid = Process.pid unless pid
fds = 0
Timeout.timeout(1) do
IO.popen("ls /proc/#{pid}/fd | wc -l") do |io|
fds = io.gets.to_i
end
end
limits = rlimits(pid)
[ fds, limits[Process::RLIMIT_NOFILE][0] ]
end
class Result
RESULT_TYPES = [ :OK, :WARNING, :CRITICAL ]
def initialize(result = :OK)
@result = result
end
def get()
@result
end
def reset()
@result = :OK
end
def set(result)
idx_new = RESULT_TYPES.find_index(result)
idx_old = RESULT_TYPES.find_index(@result)
@result = result if idx_new > idx_old
end
def to_s
return @result.to_s
end
end
def help_and_exit
msg = <<EOF
Usage: #{File.basename($PROGRAM_NAME)} [OPTION...] COMMAND_NAME
-n, --expected=N Set the expected number of processes to N
-f, --fd=PERCENTAGE Set the state to WARNING if the open FDs percent
is equal to or greater than PERCENTAGE
-m, --mem=PERCENTAGE Set the state to WARNING if the memory usage
is greater than PERCENTAGE
-c, --classname NAME Set the name of the class to NAME.
Uppercase of COMMAND_NAME is used by default.
-l, --filelock FILE If FILE does not exist, do nothing but report ok.
--help Show help messages and exit
This program will examine the system for the process started with
COMMAND_NAME. It reports in Nagios plugin format; OK iff the number
of processes meets the condition provided by '-n' option. Otherwise
it reports as either WARNING or CRITICAL.
PERCENTAGE should be a floating point value between 0 and 1.
Send a bug report to <cinsk dot shin at samsung dot com>
EOF
puts msg
exit 0
end
result = Result.new
class_name = nil
num_expected = nil
predicate_file = nil
begin
options =
GetoptLong.new(
[ '--help', GetoptLong::NO_ARGUMENT ],
[ '--nexpected', '-n', GetoptLong::REQUIRED_ARGUMENT ],
[ '--fd', '-f', GetoptLong::REQUIRED_ARGUMENT ],
[ '--mem', '-m', GetoptLong::REQUIRED_ARGUMENT ],
[ '--filelock', '-l', GetoptLong::REQUIRED_ARGUMENT ],
[ '--classname', '-c', GetoptLong::REQUIRED_ARGUMENT ])
options.each do |opt, arg|
case opt
when '--nexpected'
if arg.to_i < 1
STDERR.write("#{File.basename($PROGRAM_NAME)}: invalid value for `-n'\n")
exit 1
end
num_expected = arg.to_i
when '--fd' then fdopen_safe_percent = arg.to_f
when '--mem' then mem_safe_percent = arg.to_f
when '--classname' then class_name = arg
when '--filelock' then predicate_file = arg
when '--help' then help_and_exit
end
end
rescue => ex
puts "ex: #{ex}"
STDERR.write("#{File.basename($PROGRAM_NAME)}: Try `--help' for help\n")
exit 1
end
# puts "argv: #{ARGV}"
cmd_name = ARGV.shift
unless cmd_name
STDERR.write("#{File.basename($PROGRAM_NAME)}: argument required\n");
exit 1
end
class_name = cmd_name.upcase unless class_name
procs = {}
while cmd_name
procs.merge! ps_by_command(cmd_name)
cmd_name = ARGV.shift
end
if num_expected
result.set(:CRITICAL) if procs.length < num_expected
result.set(:WARNING) if procs.length > num_expected
end
procs.each_value do |info|
nopen = proc_fd_info info[:pid]
info[:fds] = nopen
result.set(:WARNING) if fdopen_safe_percent and nopen[0].to_f / nopen[1] >= fdopen_safe_percent
result.set(:WARNING) if mem_safe_percent and info[:mem] >= mem_safe_percent
end
expected = num_expected
expected = "??" unless num_expected
result.reset if predicate_file and File.exists?(predicate_file)
print "#{class_name} #{result} - #{procs.length}/#{expected} process(es); | "
#puts " PID NLWP MEM ELAPSED FDS"
procs.each_value do |info|
fdinfo = if info[:fds][1] == Process::RLIM_INFINITY
"#{info[:fds][0]}/unlimited"
else
"#{info[:fds][0]}/#{info[:fds][1]}"
end
puts "PID=#{info[:pid].to_s};NLWP=#{info[:nlwp].to_s};MEM=#{info[:mem].to_s};ELAPSED=#{info[:elapsed]};FDS=#{fdinfo};"
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment