lib/zeusd/process.rb
module Zeusd
class Process
CASTINGS = {
"pid" => ->(x){x.to_i},
"ppid" => ->(x){x.to_i},
"pgid" => ->(x){x.to_i},
"stat" => ->(x){x.to_s},
"command" => ->(x){x.to_s}
}
attr_accessor :attributes, :children
def initialize(attributes_or_pid)
if attributes_or_pid.is_a? Hash
self.attributes = attributes_or_pid
else
self.attributes = {"pid" => attributes_or_pid.to_i}
reload!
end
end
def to_json(*args)
attributes.to_json(*args)
end
def self.ps(options = {})
keywords = Array(options[:keywords]) | %w[pid ppid pgid stat command]
command = ["ps"].tap do |ps|
ps << "-o #{keywords.join(',')}"
ps << "-p #{options[:pid].to_i}" if options[:pid]
end.join(" ")
header, *rows = `#{command}`.split("\n")
keys = header.downcase.split
glob_columns = 0...(keys.length-1)
cmd_columns = (keys.length-1)..-1
Array(rows.map(&:split)).map do |parts|
Hash[keys.zip(parts[glob_columns] << parts[cmd_columns].join(" "))] # Attributes
end
end
def self.all(options = {})
ps(options).map do |attributes|
self.new(attributes)
end
end
def self.where(criteria, options = {})
all(options).select do |process|
criteria.all? do |key, value|
process.send(key) == value
end
end
end
def self.find(pid)
if attributes = ps(:pid => pid).first
self.new(attributes)
end
end
def self.wait(pids = [])
pids = Array(pids).map(&:to_s)
loop do
break if (self.ps.map{|x| x["pid"]} & pids).length.zero?
sleep(0.1)
end
end
def self.kill!(pids, options = {})
pids = Array(pids).map(&:to_i).select{|x| x > 0}
processes = pids.map{|pid| self.new(pid)}
signal = options.fetch(:signal, "TERM")
wait = options.fetch(:wait, false)
return false if processes.any?(&:dead?)
if system("kill -#{signal} #{processes.map(&:pid).join(' ')}")
self.wait(pids) if wait
true
else
false
end
end
def reload!
self.attributes = self.class.ps(:pid => pid).first || {}
@children = nil
!attributes.empty?
end
def cwd
@cwd ||= (path = `lsof -p #{pid}`.split("\n").find{|x| x[" cwd "]}.split.last.strip) ? Pathname.new(path).realpath : nil
end
def pid
attributes["pid"]
end
def ppid
attributes["ppid"]
end
def pgid
attributes["pgid"]
end
def state
reload!
attributes["stat"]
end
def command
attributes["command"]
end
def asleep?
!!state.to_s["S"]
end
def alive?
reload!
!attributes.empty?
end
def dead?
!alive?
end
def kill!(options = {})
return false if dead?
opts = options.dup
pids = [pid].tap do |x|
x.concat(descendants.map(&:pid)) if !!opts.delete(:recursive)
end
self.class.kill!(pids, opts)
end
def descendants(options = {})
children(options).map do |child_process|
[child_process].concat(Array(child_process.descendants))
end.flatten.compact
end
def children(options = {})
@children = self.class.where("ppid" => pid)
end
def attributes=(hash)
@attributes = hash.reduce({}) do |seed, (key, value)|
value = CASTINGS[key] ? CASTINGS[key].call(value) : value
seed.merge(key => value)
end
end
end
end