Skip to content

Instantly share code, notes, and snippets.

@gswallow
Created October 9, 2013 18:43
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 gswallow/6906062 to your computer and use it in GitHub Desktop.
Save gswallow/6906062 to your computer and use it in GitHub Desktop.
kludgey unicorn worker killer
#!/usr/bin/env ruby
# == Simple Daemon
#
# A simple ruby daemon that you copy and change as needed.
#
# === How does it work?
#
# All this program does is fork the current process (creates a copy of
# itself) then exits, the fork (child process) then goes on to run your
# daemon code. In this example we are just running a while loop with a
# 1 second sleep.
#
# Most of the code is dedicated to managing a pid file. We want a pid
# file so we can use a monitoring tool to make sure our daemon keeps
# running.
#
# === Why?
#
# Writing a daemon sounds hard but as you can see is not that
# complicated, so lets strip away the magic and just write some ruby.
#
# === Usage
#
# You can run this daemon by running:
#
# $ ./simple_ruby_daemon.rb
#
# or with an optional pid file location as its first argument:
#
# $ ./simple_ruby_daemon.rb tmp/simple_ruby_daemon.pid
#
# check that it is running by running the following:
#
# $ ps aux | grep simple_ruby_daemon
#
# Author:: Rufus Post (mailto:rufuspost@gmail.com)
class SimpleDaemon
# Checks to see if the current process is the child process and if not
# will update the pid file with the child pid.
def self.start pid, pidfile, outfile, errfile
unless pid.nil?
raise "Fork failed" if pid == -1
write pid, pidfile if kill pid, pidfile
exit
else
redirect outfile, errfile
end
end
# Attempts to write the pid of the forked process to the pid file.
def self.write pid, pidfile
File.open pidfile, "w" do |f|
f.write pid
end
rescue ::Exception => e
$stderr.puts "While writing the PID to file, unexpected #{e.class}: #{e}"
Process.kill "HUP", pid
end
# Try and read the existing pid from the pid file and signal the
# process. Returns true for a non blocking status.
def self.kill(pid, pidfile)
opid = open(pidfile).read.strip.to_i
Process.kill "HUP", opid
true
rescue Errno::ENOENT
$stdout.puts "#{pidfile} did not exist: Errno::ENOENT"
true
rescue Errno::ESRCH
$stdout.puts "The process #{opid} did not exist: Errno::ESRCH"
true
rescue Errno::EPERM
$stderr.puts "Lack of privileges to manage the process #{opid}: Errno::EPERM"
false
rescue ::Exception => e
$stderr.puts "While signaling the PID, unexpected #{e.class}: #{e}"
false
end
# Send stdout and stderr to log files for the child process
def self.redirect outfile, errfile
$stdin.reopen '/dev/null'
out = File.new outfile, "a"
err = File.new errfile, "a"
$stdout.reopen out
$stderr.reopen err
$stdout.sync = $stderr.sync = true
end
end
# Process name of your daemon
$0 = "unicorn-killer"
# Spawn a deamon
SimpleDaemon.start fork, (ARGV[0] || '/tmp/unicorn-killer.pid'), (ARGV[1] || '/tmp/unicorn-killer.stdout.log'), (ARGV[2] || '/tmp/unicorn-killer.stderr.log')
# Set up signals for our daemon, for now they just exit the process.
Signal.trap("HUP") { $stdout.puts "SIGHUP and exit"; exit }
Signal.trap("INT") { $stdout.puts "SIGINT and exit"; exit }
Signal.trap("QUIT") { $stdout.puts "SIGQUIT and exit"; exit }
# Remove this loop and replace with your own daemon logic.
loop do
def sleep_randomly
sleep (rand*30).ceil.to_i
end
def daemon_log(str)
puts "[#{Time.now.strftime("%m/%d/%Y-%H:%M:%S")}] #{str}"
end
def kill_unicorns
begin
# unicorn workers
#
# ps output line format:
# 31580 275444 unicorn_rails worker[15] -c /data/github/current/config/unicorn.rb -E production -D
# pid ram command
unicorn_worker_memory_limit = 300_000.to_i
lines = `ps -e -www -o pid,rss,command,args | egrep '[Uu]nicorn worker'`.split("\n")
lines.each do |line|
(pid, rss, comm, args) = line.split(' ', 4)
if rss.to_i > unicorn_worker_memory_limit
# tell the worker to die after it finishes serving its request
begin
daemon_log "Will kill #{pid} as it has grown to #{rss} bytes in size."
::Process.kill('QUIT', pid.to_i)
rescue ::Exception => e
daemon_log "Could not kill #{pid}: #{e}"
end
end
end
rescue Object
# don't die ever once we've tested this
nil
end
end
kill_unicorns
sleep_randomly
end
@gswallow
Copy link
Author

gswallow commented Oct 9, 2013

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment