From 08b0ddfc7b4128fc2466b50356d245f0b4969fa3 Mon Sep 17 00:00:00 2001 From: "Gun.io Whitespace Robot" Date: Sat, 17 Dec 2011 06:25:38 -0500 Subject: [PATCH] Remove whitespace [Gun.io WhitespaceBot] --- Announce.txt | 12 +- History.txt | 2 +- README.txt | 12 +- examples/events.god | 16 +- examples/gravatar.god | 12 +- examples/single.god | 14 +- ext/god/extconf.rb | 6 +- ext/god/kqueue_handler.c | 36 +-- ext/god/netlink_handler.c | 54 ++--- ideas/execve/execve.c | 8 +- ideas/future.god | 14 +- init/god | 4 +- lib/god.rb | 162 ++++++------- lib/god/behavior.rb | 26 +- lib/god/behaviors/clean_pid_file.rb | 8 +- lib/god/behaviors/clean_unix_socket.rb | 18 +- lib/god/behaviors/notify_when_flapping.rb | 22 +- lib/god/cli/command.rb | 70 +++--- lib/god/cli/run.rb | 68 +++--- lib/god/cli/version.rb | 10 +- lib/god/condition.rb | 40 +-- lib/god/conditions/always.rb | 8 +- lib/god/conditions/complex.rb | 34 +-- lib/god/conditions/cpu_usage.rb | 26 +- lib/god/conditions/degrading_lambda.rb | 14 +- lib/god/conditions/disk_usage.rb | 10 +- lib/god/conditions/flapping.rb | 44 ++-- lib/god/conditions/http_response_code.rb | 34 +-- lib/god/conditions/lambda.rb | 2 +- lib/god/conditions/memory_usage.rb | 24 +- lib/god/conditions/process_exits.rb | 22 +- lib/god/conditions/process_running.rb | 18 +- lib/god/conditions/tries.rb | 18 +- lib/god/configurable.rb | 20 +- lib/god/contact.rb | 34 +-- lib/god/contacts/email.rb | 20 +- lib/god/contacts/jabber.rb | 16 +- lib/god/dependency_graph.rb | 14 +- lib/god/diagnostics.rb | 8 +- lib/god/driver.rb | 36 +-- lib/god/errors.rb | 16 +- lib/god/event_handler.rb | 38 +-- lib/god/event_handlers/dummy_handler.rb | 4 +- lib/god/event_handlers/kqueue_handler.rb | 4 +- lib/god/event_handlers/netlink_handler.rb | 2 +- lib/god/logger.rb | 36 +-- lib/god/metric.rb | 22 +- lib/god/process.rb | 90 +++---- lib/god/registry.rb | 12 +- lib/god/simple_logger.rb | 24 +- lib/god/socket.rb | 22 +- lib/god/sugar.rb | 28 +-- lib/god/system/portable_poller.rb | 14 +- lib/god/system/process.rb | 16 +- lib/god/system/slash_proc_poller.rb | 22 +- lib/god/task.rb | 184 +++++++------- lib/god/timeline.rb | 8 +- lib/god/trigger.rb | 20 +- lib/god/watch.rb | 72 +++--- site/index.html | 90 +++---- test/configs/child_events/child_events.god | 8 +- test/configs/child_polls/child_polls.god | 8 +- test/configs/complex/complex.god | 12 +- test/configs/contact/contact.god | 8 +- test/configs/daemon_events/daemon_events.god | 8 +- test/configs/daemon_polls/daemon_polls.god | 4 +- .../degrading_lambda/degrading_lambda.god | 4 +- test/configs/matias/matias.god | 10 +- test/configs/real.rb | 12 +- test/configs/running_load/running_load.god | 2 +- test/configs/stress/stress.god | 2 +- test/configs/task/task.god | 8 +- test/configs/test.rb | 12 +- test/helper.rb | 14 +- test/test_behavior.rb | 4 +- test/test_condition.rb | 20 +- test/test_conditions_disk_usage.rb | 26 +- test/test_conditions_http_response_code.rb | 28 +-- test/test_conditions_process_running.rb | 16 +- test/test_conditions_tries.rb | 20 +- test/test_contact.rb | 36 +-- test/test_dependency_graph.rb | 26 +- test/test_driver.rb | 4 +- test/test_email.rb | 6 +- test/test_event_handler.rb | 22 +- test/test_god.rb | 228 +++++++++--------- test/test_handlers_kqueue_handler.rb | 6 +- test/test_logger.rb | 32 +-- test/test_metric.rb | 30 +-- test/test_process.rb | 86 +++---- test/test_registry.rb | 2 +- test/test_socket.rb | 6 +- test/test_sugar.rb | 14 +- test/test_system_portable_poller.rb | 2 +- test/test_system_process.rb | 10 +- test/test_task.rb | 116 ++++----- test/test_timeline.rb | 16 +- test/test_trigger.rb | 30 +-- test/test_watch.rb | 122 +++++----- 99 files changed, 1380 insertions(+), 1380 deletions(-) diff --git a/Announce.txt b/Announce.txt index 3a9a2d11..f72f62da 100644 --- a/Announce.txt +++ b/Announce.txt @@ -52,7 +52,7 @@ EXAMPLE The easiest way to understand how god will make your life better is by looking at a sample config file. The following configuration file is what I use at gravatar.com to keep the mongrels running: # run with: god -c /path/to/gravatar.god -# +# # This is the actual config file used to keep the mongrels of # gravatar.com running. @@ -61,7 +61,7 @@ RAILS_ROOT = "/Users/tom/dev/gravatar2" %w{8200 8201 8202}.each do |port| God.watch do |w| w.name = "gravatar2-mongrel-#{port}" - w.interval = 30.seconds # default + w.interval = 30.seconds # default w.start = "mongrel_rails start -c #{RAILS_ROOT} -p #{port} \ -P #{RAILS_ROOT}/log/mongrel.#{port}.pid -d" w.stop = "mongrel_rails stop -P #{RAILS_ROOT}/log/mongrel.#{port}.pid" @@ -69,7 +69,7 @@ RAILS_ROOT = "/Users/tom/dev/gravatar2" w.start_grace = 10.seconds w.restart_grace = 10.seconds w.pid_file = File.join(RAILS_ROOT, "log/mongrel.#{port}.pid") - + w.behavior(:clean_pid_file) w.start_if do |start| @@ -78,19 +78,19 @@ RAILS_ROOT = "/Users/tom/dev/gravatar2" c.running = false end end - + w.restart_if do |restart| restart.condition(:memory_usage) do |c| c.above = 150.megabytes c.times = [3, 5] # 3 out of 5 intervals end - + restart.condition(:cpu_usage) do |c| c.above = 50.percent c.times = 5 end end - + # lifecycle w.lifecycle do |on| on.condition(:flapping) do |c| diff --git a/History.txt b/History.txt index f69d1c98..c8525c0d 100644 --- a/History.txt +++ b/History.txt @@ -142,7 +142,7 @@ * Minor Enhancement * Allow extra args to pass through to config file - + == 0.5.1 / 2007-10-08 * Bug Fixes diff --git a/README.txt b/README.txt index 7aabb9e4..b63912f0 100644 --- a/README.txt +++ b/README.txt @@ -1,16 +1,16 @@ god by Tom Preston-Werner Kevin Clark (kqueue/netlink support) - + http://god.rubyforge.org == DESCRIPTION: - -God is an easy to configure, easy to extend monitoring framework written + +God is an easy to configure, easy to extend monitoring framework written in Ruby. -Keeping your server processes and tasks running should be a simple part of -your deployment process. God aims to be the simplest, most powerful +Keeping your server processes and tasks running should be a simple part of +your deployment process. God aims to be the simplest, most powerful monitoring application available. == DOCUMENTATION: @@ -24,7 +24,7 @@ Sign up for the god mailing list at http://groups.google.com/group/god-rb == INSTALL: $ sudo gem install god - + == CONTRIBUTE: Latest code is available at http://github.com/mojombo/god diff --git a/examples/events.god b/examples/events.god index 34a1aa22..d38d82c5 100644 --- a/examples/events.god +++ b/examples/events.god @@ -14,23 +14,23 @@ RAILS_ROOT = ENV['GOD_TEST_RAILS_ROOT'] w.stop = "mongrel_rails stop -P #{RAILS_ROOT}/log/mongrel.#{port}.pid -c #{RAILS_ROOT}" w.pid_file = File.join(RAILS_ROOT, "log/mongrel.#{port}.pid") w.log = File.join(RAILS_ROOT, "log/commands.#{port}.log") - + # clean pid files before start if necessary w.behavior(:clean_pid_file) - + # determine the state on startup w.transition(:init, { true => :up, false => :start }) do |on| on.condition(:process_running) do |c| c.running = true end end - + # determine when process has finished starting w.transition([:start, :restart], :up) do |on| on.condition(:process_running) do |c| c.running = true end - + # failsafe on.condition(:tries) do |c| c.times = 8 @@ -43,7 +43,7 @@ RAILS_ROOT = ENV['GOD_TEST_RAILS_ROOT'] w.transition(:up, :start) do |on| on.condition(:process_exits) end - + # restart if memory or cpu is too high w.transition(:up, :restart) do |on| on.condition(:memory_usage) do |c| @@ -51,13 +51,13 @@ RAILS_ROOT = ENV['GOD_TEST_RAILS_ROOT'] c.above = 50.megabytes c.times = [3, 5] end - + on.condition(:cpu_usage) do |c| c.interval = 10 c.above = 10.percent c.times = 5 end - + on.condition(:http_response_code) do |c| c.host = 'localhost' c.port = port @@ -67,7 +67,7 @@ RAILS_ROOT = ENV['GOD_TEST_RAILS_ROOT'] c.times = [3, 5] end end - + # lifecycle w.lifecycle do |on| on.condition(:flapping) do |c| diff --git a/examples/gravatar.god b/examples/gravatar.god index a676ddc9..a7b61040 100644 --- a/examples/gravatar.god +++ b/examples/gravatar.god @@ -1,5 +1,5 @@ # run with: god -c /path/to/gravatar.god -# +# # This is the actual config file used to keep the mongrels of # gravatar.com running. @@ -8,7 +8,7 @@ RAILS_ROOT = "/Users/tom/dev/gravatar2" %w{8200 8201 8202}.each do |port| God.watch do |w| w.name = "gravatar2-mongrel-#{port}" - w.interval = 30.seconds # default + w.interval = 30.seconds # default w.start = "mongrel_rails start -c #{RAILS_ROOT} -p #{port} \ -P #{RAILS_ROOT}/log/mongrel.#{port}.pid -d" w.stop = "mongrel_rails stop -P #{RAILS_ROOT}/log/mongrel.#{port}.pid" @@ -16,7 +16,7 @@ RAILS_ROOT = "/Users/tom/dev/gravatar2" w.start_grace = 10.seconds w.restart_grace = 10.seconds w.pid_file = File.join(RAILS_ROOT, "log/mongrel.#{port}.pid") - + w.behavior(:clean_pid_file) w.start_if do |start| @@ -25,19 +25,19 @@ RAILS_ROOT = "/Users/tom/dev/gravatar2" c.running = false end end - + w.restart_if do |restart| restart.condition(:memory_usage) do |c| c.above = 150.megabytes c.times = [3, 5] # 3 out of 5 intervals end - + restart.condition(:cpu_usage) do |c| c.above = 50.percent c.times = 5 end end - + # lifecycle w.lifecycle do |on| on.condition(:flapping) do |c| diff --git a/examples/single.god b/examples/single.god index c55372fc..229d331c 100644 --- a/examples/single.god +++ b/examples/single.god @@ -7,23 +7,23 @@ God.watch do |w| w.stop = "mongrel_rails stop -P #{RAILS_ROOT}/log/mongrel.pid" w.restart = "mongrel_rails restart -P #{RAILS_ROOT}/log/mongrel.pid" w.pid_file = File.join(RAILS_ROOT, "log/mongrel.pid") - + # clean pid files before start if necessary w.behavior(:clean_pid_file) - + # determine the state on startup w.transition(:init, { true => :up, false => :start }) do |on| on.condition(:process_running) do |c| c.running = true end end - + # determine when process has finished starting w.transition([:start, :restart], :up) do |on| on.condition(:process_running) do |c| c.running = true end - + # failsafe on.condition(:tries) do |c| c.times = 5 @@ -35,7 +35,7 @@ God.watch do |w| w.transition(:up, :start) do |on| on.condition(:process_exits) end - + # restart if memory or cpu is too high w.transition(:up, :restart) do |on| on.condition(:memory_usage) do |c| @@ -43,14 +43,14 @@ God.watch do |w| c.above = 50.megabytes c.times = [3, 5] end - + on.condition(:cpu_usage) do |c| c.interval = 10 c.above = 10.percent c.times = [3, 5] end end - + # lifecycle w.lifecycle do |on| on.condition(:flapping) do |c| diff --git a/ext/god/extconf.rb b/ext/god/extconf.rb index 46835750..41ee9467 100644 --- a/ext/god/extconf.rb +++ b/ext/god/extconf.rb @@ -16,7 +16,7 @@ def create_dummy_makefile puts "Missing 'sys/event.h' header" fail = true end - + if fail puts puts "Events handler could not be compiled (see above error). Your god installation will not support event conditions." @@ -31,7 +31,7 @@ def create_dummy_makefile puts "You may need to install a header package for your system" fail = true end - + unless have_header('linux/connector.h') && have_header('linux/cn_proc.h') puts puts "Missing 'linux/connector.h', or 'linux/cn_proc.h' header(s)" @@ -39,7 +39,7 @@ def create_dummy_makefile puts "You may need to install a header package for your system" fail = true end - + if fail puts puts "Events handler could not be compiled (see above error). Your god installation will not support event conditions." diff --git a/ext/god/kqueue_handler.c b/ext/god/kqueue_handler.c index 7936993d..da7b03cf 100644 --- a/ext/god/kqueue_handler.c +++ b/ext/god/kqueue_handler.c @@ -31,28 +31,28 @@ kqh_event_mask(VALUE klass, VALUE sym) } else { rb_raise(rb_eNotImpError, "Event `%s` not implemented", rb_id2name(id)); } - + return Qnil; } - + VALUE kqh_monitor_process(VALUE klass, VALUE pid, VALUE mask) { struct kevent new_event; ID event; - + u_int fflags = NUM2UINT(mask); - + EV_SET(&new_event, FIX2UINT(pid), EVFILT_PROC, EV_ADD | EV_ENABLE, fflags, 0, 0); - + if (-1 == kevent(kq, &new_event, 1, NULL, 0, NULL)) { rb_raise(rb_eStandardError, strerror(errno)); } - + num_events = NUM_EVENTS; - + return Qnil; } @@ -62,23 +62,23 @@ kqh_handle_events() int nevents, i, num_to_fetch; struct kevent *events; fd_set read_set; - + FD_ZERO(&read_set); FD_SET(kq, &read_set); - + // Don't actually run this method until we've got an event - rb_thread_select(kq + 1, &read_set, NULL, NULL, NULL); - + rb_thread_select(kq + 1, &read_set, NULL, NULL, NULL); + // Grabbing num_events once for thread safety num_to_fetch = num_events; events = (struct kevent*)malloc(num_to_fetch * sizeof(struct kevent)); - + if (NULL == events) { rb_raise(rb_eStandardError, strerror(errno)); } - + nevents = kevent(kq, NULL, 0, events, num_to_fetch, NULL); - + if (-1 == nevents) { free(events); rb_raise(rb_eStandardError, strerror(errno)); @@ -91,7 +91,7 @@ kqh_handle_events() } } } - + free(events); return INT2FIX(nevents); @@ -101,17 +101,17 @@ void Init_kqueue_handler_ext() { kq = kqueue(); - + if (kq == -1) { rb_raise(rb_eStandardError, "kqueue initilization failed"); } - + proc_exit = rb_intern("proc_exit"); proc_fork = rb_intern("proc_fork"); m_call = rb_intern("call"); m_size = rb_intern("size"); m_deregister = rb_intern("deregister"); - + mGod = rb_const_get(rb_cObject, rb_intern("God")); cEventHandler = rb_const_get(mGod, rb_intern("EventHandler")); cKQueueHandler = rb_define_class_under(mGod, "KQueueHandler", rb_cObject); diff --git a/ext/god/netlink_handler.c b/ext/god/netlink_handler.c index 32c53d7a..b467c441 100644 --- a/ext/god/netlink_handler.c +++ b/ext/god/netlink_handler.c @@ -27,42 +27,42 @@ nlh_handle_events() char buff[CONNECTOR_MAX_MSG_SIZE]; struct nlmsghdr *hdr; struct proc_event *event; - + VALUE extra_data; - + fd_set fds; - + FD_ZERO(&fds); FD_SET(nl_sock, &fds); - + if (0 > rb_thread_select(nl_sock + 1, &fds, NULL, NULL, NULL)) { rb_raise(rb_eStandardError, strerror(errno)); } - + /* If there were no events detected, return */ if (! FD_ISSET(nl_sock, &fds)) { return INT2FIX(0); } - - /* if there are events, make calls */ + + /* if there are events, make calls */ if (-1 == recv(nl_sock, buff, sizeof(buff), 0)) { rb_raise(rb_eStandardError, strerror(errno)); } - + hdr = (struct nlmsghdr *)buff; - + if (NLMSG_ERROR == hdr->nlmsg_type) { rb_raise(rb_eStandardError, strerror(errno)); - } else if (NLMSG_DONE == hdr->nlmsg_type) { - + } else if (NLMSG_DONE == hdr->nlmsg_type) { + event = (struct proc_event *)((struct cn_msg *)NLMSG_DATA(hdr))->data; - + switch(event->what) { case PROC_EVENT_EXIT: if (Qnil == rb_funcall(cEventHandler, m_watching_pid, 1, INT2FIX(event->event_data.exit.process_pid))) { return INT2FIX(0); } - + extra_data = rb_hash_new(); rb_hash_aset(extra_data, ID2SYM(rb_intern("pid")), INT2FIX(event->event_data.exit.process_pid)); rb_hash_aset(extra_data, ID2SYM(rb_intern("exit_code")), INT2FIX(event->event_data.exit.exit_code)); @@ -71,18 +71,18 @@ nlh_handle_events() rb_funcall(cEventHandler, m_call, 3, INT2FIX(event->event_data.exit.process_pid), ID2SYM(proc_exit), extra_data); return INT2FIX(1); - + case PROC_EVENT_FORK: if (Qnil == rb_funcall(cEventHandler, m_watching_pid, 1, INT2FIX(event->event_data.fork.parent_pid))) { return INT2FIX(0); } - + extra_data = rb_hash_new(); rb_hash_aset(extra_data, rb_intern("parent_pid"), INT2FIX(event->event_data.fork.parent_pid)); rb_hash_aset(extra_data, rb_intern("parent_thread_group_id"), INT2FIX(event->event_data.fork.parent_tgid)); rb_hash_aset(extra_data, rb_intern("child_pid"), INT2FIX(event->event_data.fork.child_pid)); rb_hash_aset(extra_data, rb_intern("child_thread_group_id"), INT2FIX(event->event_data.fork.child_tgid)); - + rb_funcall(cEventHandler, m_call, 3, INT2FIX(event->event_data.fork.parent_pid), ID2SYM(proc_fork), extra_data); return INT2FIX(1); @@ -93,7 +93,7 @@ nlh_handle_events() break; } } - + return Qnil; } @@ -108,23 +108,23 @@ connect_to_netlink() char buff[NL_MESSAGE_SIZE]; struct nlmsghdr *hdr; /* for telling netlink what we want */ struct cn_msg *msg; /* the actual connector message */ - + /* connect to netlink socket */ nl_sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR); - + if (-1 == nl_sock) { rb_raise(rb_eStandardError, strerror(errno)); } - + bzero(&sa_nl, sizeof(sa_nl)); sa_nl.nl_family = AF_NETLINK; sa_nl.nl_groups = CN_IDX_PROC; sa_nl.nl_pid = getpid(); - + if (-1 == bind(nl_sock, (struct sockaddr *)&sa_nl, sizeof(sa_nl))) { rb_raise(rb_eStandardError, strerror(errno)); } - + /* Fill header */ hdr = (struct nlmsghdr *)buff; hdr->nlmsg_len = NL_MESSAGE_SIZE; @@ -132,7 +132,7 @@ connect_to_netlink() hdr->nlmsg_flags = 0; hdr->nlmsg_seq = 0; hdr->nlmsg_pid = getpid(); - + /* Fill message */ msg = (struct cn_msg *)NLMSG_DATA(hdr); msg->id.idx = CN_IDX_PROC; /* Connecting to process information */ @@ -142,7 +142,7 @@ connect_to_netlink() msg->flags = 0; msg->len = sizeof(int); *(int*)msg->data = PROC_CN_MCAST_LISTEN; - + if (-1 == send(nl_sock, hdr, hdr->nlmsg_len, 0)) { rb_raise(rb_eStandardError, strerror(errno)); } @@ -150,17 +150,17 @@ connect_to_netlink() void Init_netlink_handler_ext() -{ +{ proc_exit = rb_intern("proc_exit"); proc_fork = rb_intern("proc_fork"); m_call = rb_intern("call"); m_watching_pid = rb_intern("watching_pid?"); - + mGod = rb_const_get(rb_cObject, rb_intern("God")); cEventHandler = rb_const_get(mGod, rb_intern("EventHandler")); cNetlinkHandler = rb_define_class_under(mGod, "NetlinkHandler", rb_cObject); rb_define_singleton_method(cNetlinkHandler, "handle_events", nlh_handle_events, 0); - + connect_to_netlink(); } diff --git a/ideas/execve/execve.c b/ideas/execve/execve.c index 4439807f..37d07b2e 100644 --- a/ideas/execve/execve.c +++ b/ideas/execve/execve.c @@ -12,18 +12,18 @@ void Init_execve() { VALUE method_execve(VALUE self, VALUE r_cmd, VALUE r_env) { char *shell = (char *)dln_find_exe("sh", 0); char *arg[] = { "sh", "-c", StringValuePtr(r_cmd), (char *)0 }; - + struct RArray *env_array; env_array = RARRAY(r_env); char *env[env_array->len + 1]; - + int i; for(i = 0; i < env_array->len; i++) { env[i] = StringValuePtr(env_array->ptr[i]); } - + env[env_array->len] = (char *)0; - + execve(shell, arg, env); return Qnil; } \ No newline at end of file diff --git a/ideas/future.god b/ideas/future.god index 6723030e..9a28eb5f 100644 --- a/ideas/future.god +++ b/ideas/future.god @@ -22,17 +22,17 @@ God.watch do |w| w.start = "mongrel_rails start -P ./log/mongrel.pid -c #{RAILS_ROOT} -d" w.stop = "mongrel_rails stop -P ./log/mongrel.pid -c #{RAILS_ROOT}" w.pid_file = File.join(RAILS_ROOT, "log/mongrel.pid") - + # clean pid files before start if necessary w.behavior(:clean_pid_file) - + # determine the state on startup w.transition(:init, { true => :up, false => :start }) do |on| on.condition(:process_running) do |c| c.running = true end end - + # determine when process has finished starting w.transition([:start, :restart], :up) do |on| on.condition(:process_running) do |c| @@ -44,7 +44,7 @@ God.watch do |w| w.transition(:up, :start) do |on| on.condition(:process_exits) end - + # restart if memory or cpu is too high w.transition(:up, :restart) do |on| on.condition(:memory_usage) do |c| @@ -52,14 +52,14 @@ God.watch do |w| c.above = 50.megabytes c.times = [3, 5] end - + on.condition(:cpu_usage) do |c| c.interval = 10 c.above = 10.percent c.times = [3, 5] end end - + # lifecycle w.lifecycle do |on| on.condition(:flapping) do |c| @@ -71,7 +71,7 @@ God.watch do |w| c.giveup_tries = 5 c.notify = 'tom' end - + on.condition(:memory_usage) do |c| c.interval = 20 c.above = 40.megabytes diff --git a/init/god b/init/god index 2c7d7336..bda4cfe3 100755 --- a/init/god +++ b/init/god @@ -5,7 +5,7 @@ # chkconfig: - 85 15 # description: God is an easy to configure, easy to extend monitoring \ # framework written in Ruby. -# +# CONF_DIR=/etc/god @@ -37,6 +37,6 @@ case "$1" in echo "Usage: god {start|stop|restart|status}" exit 1 ;; -esac +esac exit $RETVAL diff --git a/lib/god.rb b/lib/god.rb index fa56ce7b..0804c436 100644 --- a/lib/god.rb +++ b/lib/god.rb @@ -99,15 +99,15 @@ def root_binding module Kernel alias_method :abort_orig, :abort - + def abort(text = nil) $run = false applog(nil, :error, text) if text exit(1) end - + alias_method :exit_orig, :exit - + def exit(code = 0) $run = false exit_orig(code) @@ -121,15 +121,15 @@ def safe_attr_accessor(*args) if !self.running && self.inited abort "God.#{arg} must be set before any Tasks are defined" end - + if self.running && self.inited applog(nil, :warn, "God.#{arg} can't be set while god is running") return end - + instance_variable_set(('@' + arg.to_s).intern, other) end - + define_method(arg) do instance_variable_get(('@' + arg.to_s).intern) end @@ -139,13 +139,13 @@ def safe_attr_accessor(*args) module God VERSION = '0.7.9' - + LOG_BUFFER_SIZE_DEFAULT = 100 PID_FILE_DIRECTORY_DEFAULTS = ['/var/run/god', '~/.god/pids'] DRB_PORT_DEFAULT = 17165 DRB_ALLOW_DEFAULT = ['127.0.0.1'] LOG_LEVEL_DEFAULT = :info - + class << self # user configurable safe_attr_accessor :pid, @@ -157,7 +157,7 @@ class << self :log_file, :log_level, :use_events - + # internal attr_accessor :inited, :running, @@ -170,7 +170,7 @@ class << self :contact_groups, :main end - + # initialize class instance variables self.pid = nil self.host = nil @@ -179,14 +179,14 @@ class << self self.log_buffer_size = nil self.pid_file_directory = nil self.log_level = nil - + # Initialize internal data. # # Returns nothing def self.internal_init # only do this once return if self.inited - + # variable init self.watches = {} self.groups = {} @@ -194,16 +194,16 @@ def self.internal_init self.pending_watch_states = {} self.contacts = {} self.contact_groups = {} - + # set defaults self.log_buffer_size ||= LOG_BUFFER_SIZE_DEFAULT self.port ||= DRB_PORT_DEFAULT self.allow ||= DRB_ALLOW_DEFAULT self.log_level ||= LOG_LEVEL_DEFAULT - + # additional setup self.setup - + # log level log_level_map = {:debug => Logger::DEBUG, :info => Logger::INFO, @@ -211,14 +211,14 @@ def self.internal_init :error => Logger::ERROR, :fatal => Logger::FATAL} LOG.level = log_level_map[self.log_level] - + # init has been executed self.inited = true - + # not yet running self.running = false end - + # Instantiate a new, empty Watch object and pass it to the mandatory # block. The attributes of the watch will be set by the configuration # file. @@ -231,7 +231,7 @@ def self.internal_init def self.watch(&block) self.task(Watch, &block) end - + # Instantiate a new, empty Task object and yield it to the mandatory # block. The attributes of the task will be set by the configuration # file. @@ -243,13 +243,13 @@ def self.watch(&block) # Returns nothing def self.task(klass = Task) self.internal_init - + t = klass.new yield(t) - + # do the post-configuration t.prepare - + # if running, completely remove the watch (if necessary) to # prepare for the reload existing_watch = self.watches[t.name] @@ -257,35 +257,35 @@ def self.task(klass = Task) self.pending_watch_states[existing_watch.name] = existing_watch.state self.unwatch(existing_watch) end - + # ensure the new watch has a unique name if self.watches[t.name] || self.groups[t.name] abort "Task name '#{t.name}' already used for a Task or Group" end - + # ensure watch is internally valid t.valid? || abort("Task '#{t.name}' is not valid (see above)") - + # add to list of watches self.watches[t.name] = t - + # add to pending watches self.pending_watches << t - + # add to group if specified if t.group # ensure group name hasn't been used for a watch already if self.watches[t.group] abort "Group name '#{t.group}' already used for a Task" end - + self.groups[t.group] ||= [] self.groups[t.group] << t end - + # register watch t.register! - + # log if self.running && existing_watch applog(t, :info, "#{t.name} Reloaded config") @@ -293,7 +293,7 @@ def self.task(klass = Task) applog(t, :info, "#{t.name} Loaded config") end end - + # Unmonitor and remove the given watch from god. # +watch+ is the Watch to remove # @@ -301,21 +301,21 @@ def self.task(klass = Task) def self.unwatch(watch) # unmonitor watch.unmonitor unless watch.state == :unmonitored - + # unregister watch.unregister! - + # remove from watches self.watches.delete(watch.name) - + # remove from groups if watch.group self.groups[watch.group].delete(watch) end - + applog(watch, :info, "#{watch.name} unwatched") end - + # Instantiate a new Contact of the given kind and send it to the block. # Then prepare, validate, and record the Contact. # +kind+ is the contact class specifier @@ -328,53 +328,53 @@ def self.unwatch(watch) # Returns nothing def self.contact(kind) self.internal_init - + # create the contact begin c = Contact.generate(kind) rescue NoSuchContactError => e abort e.message end - + # send to block so config can set attributes yield(c) if block_given? - + # call prepare on the contact c.prepare - + # remove existing contacts of same name existing_contact = self.contacts[c.name] if self.running && existing_contact self.uncontact(existing_contact) end - + # warn and noop if the contact has been defined before if self.contacts[c.name] || self.contact_groups[c.name] applog(nil, :warn, "Contact name '#{c.name}' already used for a Contact or Contact Group") return end - + # abort if the Contact is invalid, the Contact will have printed # out its own error messages by now unless Contact.valid?(c) && c.valid? abort "Exiting on invalid contact" end - + # add to list of contacts self.contacts[c.name] = c - + # add to contact group if specified if c.group # ensure group name hasn't been used for a contact already if self.contacts[c.group] abort "Contact Group name '#{c.group}' already used for a Contact" end - + self.contact_groups[c.group] ||= [] self.contact_groups[c.group] << c end end - + # Remove the given contact from god. # +contact+ is the Contact to remove # @@ -385,7 +385,7 @@ def self.uncontact(contact) self.contact_groups[contact.group].delete(contact) end end - + # Control the lifecycle of the given task(s). # +name+ is the name of a task/group (String) # +command+ is the command to run (String) @@ -400,9 +400,9 @@ def self.uncontact(contact) def self.control(name, command) # get the list of items items = Array(self.watches[name] || self.groups[name]).dup - + jobs = [] - + # do the command case command when "start", "monitor" @@ -418,12 +418,12 @@ def self.control(name, command) else raise InvalidCommandError.new end - + jobs.each { |j| j.join } - + items.map { |x| x.name } end - + # Unmonitor and stop all tasks. # # Returns true on success @@ -435,15 +435,15 @@ def self.stop_all w.action(:stop) if w.alive? end end - + 10.times do return true unless self.watches.map { |name, w| w.alive? }.any? sleep 1 end - + return false end - + # Force the termination of god. # * Clean up pid file if one exists # * Stop DRb service @@ -455,7 +455,7 @@ def self.terminate self.server.stop if self.server exit!(0) end - + # Gather the status of each task. # # Examples @@ -470,7 +470,7 @@ def self.status end info end - + # Log lines for the given task since the specified time. # +watch_name+ is the name of the task (may be abbreviated) # +since+ is the Time since which to report log lines @@ -480,14 +480,14 @@ def self.status # Returns String:joined_log_lines def self.running_log(watch_name, since) matches = pattern_match(watch_name, self.watches.keys) - + unless matches.first raise NoSuchWatchError.new end - + LOG.watch_log_since(matches.first, since) end - + # Load a config file into a running god instance. Rescues any exceptions # that the config may raise and reports these back to the caller. # +code+ is a String containing the config file @@ -497,10 +497,10 @@ def self.running_log(watch_name, since) def self.running_load(code, filename) errors = "" watches = [] - + begin LOG.start_capture - + Gem.clear_paths eval(code, root_binding, filename) self.pending_watches.each do |w| @@ -516,17 +516,17 @@ def self.running_load(code, filename) rescue Exception => e # don't ever let running_load take down god errors << LOG.finish_capture - + unless e.instance_of?(SystemExit) errors << e.message << "\n" errors << e.backtrace.join("\n") end end - + names = watches.map { |x| x.name } [names, errors] end - + # Load the given file(s) according to the given glob. # +glob+ is the glob-enabled path to load # @@ -536,7 +536,7 @@ def self.load(glob) Kernel.load f end end - + def self.setup if self.pid_file_directory # pid file dir was specified, ensure it is created and writable @@ -547,7 +547,7 @@ def self.setup abort "Failed to create pid file directory: #{e.message}" end end - + unless File.writable?(self.pid_file_directory) abort "The pid file directory (#{self.pid_file_directory}) is not writable by #{Etc.getlogin}" end @@ -564,54 +564,54 @@ def self.setup rescue Errno::EACCES => e end end - + unless self.pid_file_directory dirs = PID_FILE_DIRECTORY_DEFAULTS.map { |x| File.expand_path(x) } abort "No pid file directory exists, could be created, or is writable at any of #{dirs.join(', ')}" end end - + applog(nil, :info, "Using pid file directory: #{self.pid_file_directory}") end - + # Initialize and startup the machinery that makes god work. # # Returns nothing def self.start self.internal_init - + # instantiate server self.server = Socket.new(self.port) - + # start monitoring any watches set to autostart self.watches.values.each { |w| w.monitor if w.autostart? } - + # clear pending watches self.pending_watches.clear - + # mark as running self.running = true - + # don't exit - self.main = + self.main = Thread.new do loop do sleep 60 end end - + self.main.join end - + # To be called on program exit to start god # # Returns nothing def self.at_exit self.start end - + # private - + # Match a shortened pattern against a list of String candidates. # The pattern is expanded into a regular expression by # inserting .* between each character. @@ -628,7 +628,7 @@ def self.at_exit # Returns String[]:matched_elements def self.pattern_match(pattern, list) regex = pattern.split('').join('.*') - + list.select do |item| item =~ Regexp.new(regex) end diff --git a/lib/god/behavior.rb b/lib/god/behavior.rb index 478e6ef2..a2721b84 100644 --- a/lib/god/behavior.rb +++ b/lib/god/behavior.rb @@ -1,10 +1,10 @@ module God - + class Behavior include Configurable - + attr_accessor :watch - + # Generate a Behavior of the given kind. The proper class is found by camel casing the # kind (which is given as an underscored symbol). # +kind+ is the underscored symbol representing the class (e.g. foo_bar for God::Behaviors::FooBar) @@ -16,31 +16,31 @@ def self.generate(kind, watch) rescue NameError raise NoSuchBehaviorError.new("No Behavior found with the class name God::Behaviors::#{sym}") end - + def valid? true end - + ####### - + def before_start end - + def after_start end - + def before_restart end - + def after_restart end - + def before_stop end - + def after_stop end - + # Construct the friendly name of this Behavior, looks like: # # Behavior FooBar on Watch 'baz' @@ -48,5 +48,5 @@ def friendly_name "Behavior " + super + " on Watch '#{self.watch.name}'" end end - + end \ No newline at end of file diff --git a/lib/god/behaviors/clean_pid_file.rb b/lib/god/behaviors/clean_pid_file.rb index df404fa8..11839b48 100644 --- a/lib/god/behaviors/clean_pid_file.rb +++ b/lib/god/behaviors/clean_pid_file.rb @@ -1,21 +1,21 @@ module God module Behaviors - + class CleanPidFile < Behavior def valid? valid = true valid &= complain("Attribute 'pid_file' must be specified", self) if self.watch.pid_file.nil? valid end - + def before_start File.delete(self.watch.pid_file) - + "deleted pid file" rescue "no pid file to delete" end end - + end end \ No newline at end of file diff --git a/lib/god/behaviors/clean_unix_socket.rb b/lib/god/behaviors/clean_unix_socket.rb index 6b505311..da93d0c3 100644 --- a/lib/god/behaviors/clean_unix_socket.rb +++ b/lib/god/behaviors/clean_unix_socket.rb @@ -1,21 +1,21 @@ module God module Behaviors - + class CleanUnixSocket < Behavior def valid? valid = true valid &= complain("Attribute 'unix_socket' must be specified", self) if self.watch.unix_socket.nil? valid - end - - def before_start + end + + def before_start File.delete(self.watch.unix_socket) - - "deleted unix socket" - rescue - "no unix socket to delete" + + "deleted unix socket" + rescue + "no unix socket to delete" end end - + end end \ No newline at end of file diff --git a/lib/god/behaviors/notify_when_flapping.rb b/lib/god/behaviors/notify_when_flapping.rb index 6ede3a4a..a7c20c8e 100644 --- a/lib/god/behaviors/notify_when_flapping.rb +++ b/lib/god/behaviors/notify_when_flapping.rb @@ -1,44 +1,44 @@ module God module Behaviors - + class NotifyWhenFlapping < Behavior - attr_accessor :failures # number of failures + attr_accessor :failures # number of failures attr_accessor :seconds # number of seconds attr_accessor :notifier # class to notify with - + def initialize super @startup_times = [] end - + def valid? valid = true valid &= complain("Attribute 'failures' must be specified", self) unless self.failures valid &= complain("Attribute 'seconds' must be specified", self) unless self.seconds valid &= complain("Attribute 'notifier' must be specified", self) unless self.notifier - + # Must take one arg or variable args unless self.notifier.respond_to?(:notify) and [1,-1].include?(self.notifier.method(:notify).arity) valid &= complain("The 'notifier' must have a method 'notify' which takes 1 or variable args", self) end - + valid end - + def before_start now = Time.now.to_i @startup_times << now check_for_flapping(now) end - + def before_restart now = Time.now.to_i @startup_times << now check_for_flapping(now) end - + private - + def check_for_flapping(now) @startup_times.select! {|time| time >= now - self.seconds } if @startup_times.length >= self.failures @@ -46,6 +46,6 @@ def check_for_flapping(now) end end end - + end end \ No newline at end of file diff --git a/lib/god/cli/command.rb b/lib/god/cli/command.rb index 9da357f7..8c4dbf86 100644 --- a/lib/god/cli/command.rb +++ b/lib/god/cli/command.rb @@ -1,20 +1,20 @@ module God module CLI - + class Command def initialize(command, options, args) @command = command @options = options @args = args - + dispatch end - + def setup # connect to drb unix socket DRb.start_service("druby://127.0.0.1:0") @server = DRbObject.new(nil, God::Socket.socket(@options[:port])) - + # ping server to ensure that it is responsive begin @server.ping @@ -23,7 +23,7 @@ def setup abort end end - + def dispatch if %w{load status log quit terminate}.include?(@command) setup @@ -38,19 +38,19 @@ def dispatch abort end end - + def load_command file = @args[1] - + puts "Sending '#{@command}' command" puts - + unless File.exist?(file) abort "File not found: #{file}" end - + names, errors = *@server.running_load(File.read(file), File.expand_path(file)) - + # output response unless names.empty? puts 'The following tasks were affected:' @@ -58,13 +58,13 @@ def load_command puts ' ' + w end end - + unless errors.empty? puts errors exit(1) end end - + def status_command watches = {} @server.status.each do |name, status| @@ -83,17 +83,17 @@ def status_command end end end - + def log_command begin Signal.trap('INT') { exit } name = @args[1] - + unless name puts "You must specify a Task or Group name" exit! end - + t = Time.at(0) loop do print @server.running_log(name, t) @@ -106,7 +106,7 @@ def log_command puts "The server went away" end end - + def quit_command begin @server.terminate @@ -115,7 +115,7 @@ def quit_command puts 'Stopped god' end end - + def terminate_command t = Thread.new { loop { STDOUT.print('.'); STDOUT.flush; sleep(1) } } if @server.stop_all @@ -125,7 +125,7 @@ def terminate_command t.kill; STDOUT.puts puts 'Could not stop all watches within 10 seconds' end - + begin @server.terminate abort 'Could not stop god' @@ -133,13 +133,13 @@ def terminate_command puts 'Stopped god' end end - + def check_command Thread.new do begin event_system = God::EventHandler.event_system puts "using event system: #{event_system}" - + if God::EventHandler.loaded? puts "starting event handler" God::EventHandler.start @@ -147,48 +147,48 @@ def check_command puts "[fail] event system did not load" exit(1) end - + puts 'forking off new process' - + pid = fork do loop { sleep(1) } end - + puts "forked process with pid = #{pid}" - + God::EventHandler.register(pid, :proc_exit) do puts "[ok] process exit event received" exit!(0) end - + sleep(1) - + puts "killing process" - + ::Process.kill('KILL', pid) rescue => e puts e.message puts e.backtrace.join("\n") end end - + sleep(2) - + puts "[fail] never received process exit event" exit(1) end - + def lifecycle_command # get the name of the watch/group name = @args[1] - + puts "Sending '#{@command}' command" - + t = Thread.new { loop { sleep(1); STDOUT.print('.'); STDOUT.flush; sleep(1) } } - + # send @command watches = @server.control(name, @command) - + # output response t.kill; STDOUT.puts unless watches.empty? @@ -201,6 +201,6 @@ def lifecycle_command end end end # Command - + end end diff --git a/lib/god/cli/run.rb b/lib/god/cli/run.rb index 80161fde..69db91ea 100644 --- a/lib/god/cli/run.rb +++ b/lib/god/cli/run.rb @@ -1,6 +1,6 @@ module God module CLI - + class Run def initialize(options) @options = options @@ -8,14 +8,14 @@ def initialize(options) unless @options[:syslog] Logger.syslog = false end - + dispatch end - + def dispatch # have at_exit start god $run = true - + # run if @options[:daemonize] run_daemonized @@ -23,7 +23,7 @@ def dispatch run_in_front end end - + def attach process = System::Process.new(@options[:attach]) Thread.new do @@ -36,54 +36,54 @@ def attach end end end - + def default_run # start attached pid watcher if necessary if @options[:attach] self.attach end - + if @options[:port] God.port = @options[:port] end - + if @options[:events] God::EventHandler.load end - + # set log level, defaults to WARN if @options[:log_level] God.log_level = @options[:log_level] else God.log_level = @options[:daemonize] ? :warn : :info end - + if @options[:config] unless File.exist?(@options[:config]) abort "File not found: #{@options[:config]}" end - + # start the event handler God::EventHandler.start if God::EventHandler.loaded? - + load_config @options[:config] end end - + def run_in_front require 'god' - + if @options[:bleakhouse] BleakHouseDiagnostic.install end - + default_run - + log_file = God.log_file - log_file = File.expand_path(@options[:log]) if @options[:log] + log_file = File.expand_path(@options[:log]) if @options[:log] if log_file puts "Sending output to log file: #{log_file}" - + # reset file descriptors STDIN.reopen "/dev/null" STDOUT.reopen(log_file, "a") @@ -91,30 +91,30 @@ def run_in_front STDOUT.sync = true end end - + def run_daemonized # trap and ignore SIGHUP Signal.trap('HUP') {} - + pid = fork do begin require 'god' - + log_file = @options[:log] || God.log_file || "/dev/null" - + # reset file descriptors STDIN.reopen "/dev/null" STDOUT.reopen(log_file, "a") STDERR.reopen STDOUT STDOUT.sync = true - + # set pid if requested if @options[:pid] # and as deamon - God.pid = @options[:pid] + God.pid = @options[:pid] end - + default_run - + unless God::EventHandler.loaded? puts puts "***********************************************************************" @@ -125,23 +125,23 @@ def run_daemonized puts "***********************************************************************" puts end - + rescue => e puts e.message puts e.backtrace.join("\n") abort "There was a fatal system error while starting god (see above)" end end - + if @options[:pid] File.open(@options[:pid], 'w') { |f| f.write pid } end - + ::Process.detach pid - + exit end - + def load_config(config) if File.directory? config files_loaded = false @@ -157,7 +157,7 @@ def load_config(config) end end end - + def load_god_file(god_file) load File.expand_path(god_file) rescue Exception => e @@ -170,8 +170,8 @@ def load_god_file(god_file) return false end end - + end # Run - + end end diff --git a/lib/god/cli/version.rb b/lib/god/cli/version.rb index fa1f6d85..57b87f1e 100644 --- a/lib/god/cli/version.rb +++ b/lib/god/cli/version.rb @@ -1,23 +1,23 @@ module God module CLI - + class Version def self.version require 'god' - + # print version puts "Version #{God::VERSION}" exit end - + def self.version_extended puts "Version: #{God::VERSION}" puts "Polls: enabled" puts "Events: " + God::EventHandler.event_system - + exit end end - + end end \ No newline at end of file diff --git a/lib/god/condition.rb b/lib/god/condition.rb index 7eb11ea7..3ede8ece 100644 --- a/lib/god/condition.rb +++ b/lib/god/condition.rb @@ -1,29 +1,29 @@ module God - + class Condition < Behavior attr_accessor :transition, :notify, :info, :phase - + # Generate a Condition of the given kind. The proper class if found by camel casing the # kind (which is given as an underscored symbol). # +kind+ is the underscored symbol representing the class (e.g. :foo_bar for God::Conditions::FooBar) def self.generate(kind, watch) sym = kind.to_s.capitalize.gsub(/_(.)/){$1.upcase}.intern c = God::Conditions.const_get(sym).new - + unless c.kind_of?(PollCondition) || c.kind_of?(EventCondition) || c.kind_of?(TriggerCondition) - abort "Condition '#{c.class.name}' must subclass God::PollCondition, God::EventCondition, or God::TriggerCondition" + abort "Condition '#{c.class.name}' must subclass God::PollCondition, God::EventCondition, or God::TriggerCondition" end - + if !EventHandler.loaded? && c.kind_of?(EventCondition) abort "Condition '#{c.class.name}' requires an event system but none has been loaded" end - + c.watch = watch c rescue NameError raise NoSuchConditionError.new("No Condition found with the class name God::Conditions::#{sym}") end - + def self.valid?(condition) valid = true if condition.notify @@ -35,7 +35,7 @@ def self.valid?(condition) end valid end - + # Construct the friendly name of this Condition, looks like: # # Condition FooBar on Watch 'baz' @@ -43,15 +43,15 @@ def friendly_name "Condition #{self.class.name.split('::').last} on Watch '#{self.watch.name}'" end end - + class PollCondition < Condition - # all poll conditions can specify a poll interval + # all poll conditions can specify a poll interval attr_accessor :interval - + # Override this method in your Conditions (optional) def before end - + # Override this method in your Conditions (mandatory) # # Return true if the test passes (everything is ok) @@ -59,38 +59,38 @@ def before def test raise AbstractMethodNotOverriddenError.new("PollCondition#test must be overridden in subclasses") end - + # Override this method in your Conditions (optional) def after end end - + class EventCondition < Condition def register raise AbstractMethodNotOverriddenError.new("EventCondition#register must be overridden in subclasses") end - + def deregister raise AbstractMethodNotOverriddenError.new("EventCondition#deregister must be overridden in subclasses") end end - + class TriggerCondition < Condition def process(event, payload) raise AbstractMethodNotOverriddenError.new("TriggerCondition#process must be overridden in subclasses") end - + def trigger self.watch.trigger(self) end - + def register Trigger.register(self) end - + def deregister Trigger.deregister(self) end end - + end \ No newline at end of file diff --git a/lib/god/conditions/always.rb b/lib/god/conditions/always.rb index f651863a..d817bd00 100644 --- a/lib/god/conditions/always.rb +++ b/lib/god/conditions/always.rb @@ -3,21 +3,21 @@ module Conditions class Always < PollCondition attr_accessor :what - + def initialize self.info = "always" end - + def valid? valid = true valid &= complain("Attribute 'what' must be specified", self) if self.what.nil? valid end - + def test @what end end - + end end \ No newline at end of file diff --git a/lib/god/conditions/complex.rb b/lib/god/conditions/complex.rb index 50dd8ed4..42b39810 100644 --- a/lib/god/conditions/complex.rb +++ b/lib/god/conditions/complex.rb @@ -1,60 +1,60 @@ module God module Conditions - + class Complex < PollCondition - AND = 0x1 + AND = 0x1 OR = 0x2 NOT = 0x4 - + def initialize() super - + @oper_stack = [] @op_stack = [] - + @this = nil end - + def valid? @oper_stack.inject(true) { |acc, oper| acc & oper.valid? } end - + def prepare @oper_stack.each { |oper| oper.prepare } end - + def new_oper(kind, op) oper = Condition.generate(kind, self.watch) @oper_stack.push(oper) @op_stack.push(op) oper end - + def this(kind) @this = Condition.generate(kind, self.watch) yield @this if block_given? end - + def and(kind) oper = new_oper(kind, 0x1) yield oper if block_given? end - + def and_not(kind) oper = new_oper(kind, 0x5) yield oper if block_given? end - + def or(kind) oper = new_oper(kind, 0x2) yield oper if block_given? end - + def or_not(kind) oper = new_oper(kind, 0x6) yield oper if block_given? end - + def test if @this.nil? # Although this() makes sense semantically and therefore @@ -71,16 +71,16 @@ def test else res = @this.test end - + @op_stack.each do |op| cond = @oper_stack.shift eval "res " + ((0 < op & AND) ? "&&" : "||") + "= " + ((0 < op & NOT) ? "!" : "") + "cond.test" @oper_stack.push cond end - + res end end - + end end \ No newline at end of file diff --git a/lib/god/conditions/cpu_usage.rb b/lib/god/conditions/cpu_usage.rb index c52007fc..886b2be1 100644 --- a/lib/god/conditions/cpu_usage.rb +++ b/lib/god/conditions/cpu_usage.rb @@ -1,9 +1,9 @@ module God module Conditions - + # Condition Symbol :cpu_usage # Type: Poll - # + # # Trigger when the percent of CPU use of a process is above a specified limit. # On multi-core systems, this number could conceivably be above 100. # @@ -11,7 +11,7 @@ module Conditions # Required # +pid_file+ is the pid file of the process in question. Automatically # populated for Watches. - # +above+ is the percent CPU above which to trigger the condition. You + # +above+ is the percent CPU above which to trigger the condition. You # may use #percent to clarify this amount (see examples). # # Examples @@ -30,42 +30,42 @@ module Conditions # end class CpuUsage < PollCondition attr_accessor :above, :times, :pid_file - + def initialize super self.above = nil self.times = [1, 1] end - + def prepare if self.times.kind_of?(Integer) self.times = [self.times, self.times] end - + @timeline = Timeline.new(self.times[1]) end - + def reset @timeline.clear end - + def pid self.pid_file ? File.read(self.pid_file).strip.to_i : self.watch.pid end - + def valid? valid = true valid &= complain("Attribute 'pid_file' must be specified", self) if self.pid_file.nil? && self.watch.pid_file.nil? valid &= complain("Attribute 'above' must be specified", self) if self.above.nil? valid end - + def test process = System::Process.new(self.pid) @timeline.push(process.percent_cpu) - + history = "[" + @timeline.map { |x| "#{x > self.above ? '*' : ''}#{x}%%" }.join(", ") + "]" - + if @timeline.select { |x| x > self.above }.size >= self.times.first self.info = "cpu out of bounds #{history}" return true @@ -75,6 +75,6 @@ def test end end end - + end end \ No newline at end of file diff --git a/lib/god/conditions/degrading_lambda.rb b/lib/god/conditions/degrading_lambda.rb index 5bb97dd0..7616318b 100644 --- a/lib/god/conditions/degrading_lambda.rb +++ b/lib/god/conditions/degrading_lambda.rb @@ -1,21 +1,21 @@ module God module Conditions - + # This condition degrades its interval by a factor of two for 3 tries before failing class DegradingLambda < PollCondition attr_accessor :lambda - + def initialize super @tries = 0 end - + def valid? valid = true valid &= complain("Attribute 'lambda' must be specified", self) if self.lambda.nil? valid end - + def test puts "Calling test. Interval at #{self.interval}" @original_interval ||= self.interval @@ -30,13 +30,13 @@ def test @tries = 0 self.interval = @original_interval end - + self.info = "lambda condition was not satisfied" false end - + private - + def pass? begin Timeout::timeout(@interval) { diff --git a/lib/god/conditions/disk_usage.rb b/lib/god/conditions/disk_usage.rb index 140b57b2..a9fff710 100644 --- a/lib/god/conditions/disk_usage.rb +++ b/lib/god/conditions/disk_usage.rb @@ -1,27 +1,27 @@ module God module Conditions - + class DiskUsage < PollCondition attr_accessor :above, :mount_point - + def initialize super self.above = nil self.mount_point = nil end - + def valid? valid = true valid &= complain("Attribute 'mount_point' must be specified", self) if self.mount_point.nil? valid &= complain("Attribute 'above' must be specified", self) if self.above.nil? valid end - + def test usage = `df | grep -i " #{self.mount_point}$" | awk '{print $5}' | sed 's/%//'` usage.to_i > self.above end end - + end end diff --git a/lib/god/conditions/flapping.rb b/lib/god/conditions/flapping.rb index 78bf3293..bc63a817 100644 --- a/lib/god/conditions/flapping.rb +++ b/lib/god/conditions/flapping.rb @@ -1,11 +1,11 @@ module God module Conditions - + # Condition Symbol :flapping # Type: Trigger - # + # # Trigger when a Task transitions to or from a state or states a given number - # of times within a given period. + # of times within a given period. # # Paramaters # Required @@ -24,7 +24,7 @@ module Conditions # it has been disabled by the condition. # +retry_times+ is the number of times after which to permanently unmonitor # the Task. - # +retry_within+ is the number of seconds within which + # +retry_within+ is the number of seconds within which # # Examples # @@ -37,16 +37,16 @@ class Flapping < TriggerCondition :retry_in, :retry_times, :retry_within - + def initialize self.info = "process is flapping" end - + def prepare @timeline = Timeline.new(self.times) @retry_timeline = Timeline.new(self.retry_times) end - + def valid? valid = true valid &= complain("Attribute 'times' must be specified", self) if self.times.nil? @@ -54,21 +54,21 @@ def valid? valid &= complain("Attributes 'from_state', 'to_state', or both must be specified", self) if self.from_state.nil? && self.to_state.nil? valid end - + def process(event, payload) begin if event == :state_change event_from_state, event_to_state = *payload - + from_state_match = !self.from_state || self.from_state && Array(self.from_state).include?(event_from_state) to_state_match = !self.to_state || self.to_state && Array(self.to_state).include?(event_to_state) - + if from_state_match && to_state_match @timeline << Time.now - + concensus = (@timeline.size == self.times) duration = (@timeline.last - @timeline.first) < self.within - + if concensus && duration @timeline.clear trigger @@ -81,21 +81,21 @@ def process(event, payload) puts e.backtrace.join("\n") end end - + private - + def retry_mechanism if self.retry_in @retry_timeline << Time.now - + concensus = (@retry_timeline.size == self.retry_times) duration = (@retry_timeline.last - @retry_timeline.first) < self.retry_within - + if concensus && duration # give up Thread.new do sleep 1 - + # log msg = "#{self.watch.name} giving up" applog(self.watch, :info, msg) @@ -104,17 +104,17 @@ def retry_mechanism # try again later Thread.new do sleep 1 - + # log msg = "#{self.watch.name} auto-reenable monitoring in #{self.retry_in} seconds" applog(self.watch, :info, msg) - + sleep self.retry_in - + # log msg = "#{self.watch.name} auto-reenabling monitoring" applog(self.watch, :info, msg) - + if self.watch.state == :unmonitored self.watch.monitor end @@ -123,6 +123,6 @@ def retry_mechanism end end end - + end end \ No newline at end of file diff --git a/lib/god/conditions/http_response_code.rb b/lib/god/conditions/http_response_code.rb index a005ef2e..f31489ae 100644 --- a/lib/god/conditions/http_response_code.rb +++ b/lib/god/conditions/http_response_code.rb @@ -2,10 +2,10 @@ module God module Conditions - + # Condition Symbol :http_response_code # Type: Poll - # + # # Trigger based on the response from an HTTP request. # # Paramaters @@ -71,7 +71,7 @@ class HttpResponseCode < PollCondition :timeout, # e.g. 60.seconds :path, # e.g. '/' :headers # e.g. {'Host' => 'myvirtual.mydomain.com'} - + def initialize super self.port = 80 @@ -80,24 +80,24 @@ def initialize self.times = [1, 1] self.timeout = 60.seconds end - + def prepare self.code_is = Array(self.code_is).map { |x| x.to_i } if self.code_is self.code_is_not = Array(self.code_is_not).map { |x| x.to_i } if self.code_is_not - + if self.times.kind_of?(Integer) self.times = [self.times, self.times] end - + @timeline = Timeline.new(self.times[1]) @history = Timeline.new(self.times[1]) end - + def reset @timeline.clear @history.clear end - + def valid? valid = true valid &= complain("Attribute 'host' must be specified", self) if self.host.nil? @@ -105,15 +105,15 @@ def valid? (self.code_is.nil? && self.code_is_not.nil?) || (self.code_is && self.code_is_not) valid end - + def test response = nil - + Net::HTTP.start(self.host, self.port) do |http| http.read_timeout = self.timeout response = http.get(self.path, self.headers) end - + actual_response_code = response.code.to_i if self.code_is && self.code_is.include?(actual_response_code) pass(actual_response_code) @@ -135,9 +135,9 @@ def test rescue Exception => failure self.code_is ? fail(failure.class.name) : pass(failure.class.name) end - + private - + def pass(code) @timeline << true if @timeline.select { |x| x }.size >= self.times.first @@ -148,21 +148,21 @@ def pass(code) false end end - + def fail(code) @timeline << false self.info = "http response nominal #{history(code, false)}" false end - + def history(code, passed) entry = code.to_s.dup entry = '*' + entry if passed @history << entry '[' + @history.join(", ") + ']' end - + end - + end end \ No newline at end of file diff --git a/lib/god/conditions/lambda.rb b/lib/god/conditions/lambda.rb index 0eb72c26..5d8a1aa5 100644 --- a/lib/god/conditions/lambda.rb +++ b/lib/god/conditions/lambda.rb @@ -1,6 +1,6 @@ module God module Conditions - + class Lambda < PollCondition attr_accessor :lambda diff --git a/lib/god/conditions/memory_usage.rb b/lib/god/conditions/memory_usage.rb index dc573c88..a5d95e10 100644 --- a/lib/god/conditions/memory_usage.rb +++ b/lib/god/conditions/memory_usage.rb @@ -1,9 +1,9 @@ module God module Conditions - + # Condition Symbol :memory_usage # Type: Poll - # + # # Trigger when the resident memory of a process is above a specified limit. # # Paramaters @@ -32,42 +32,42 @@ module Conditions # end class MemoryUsage < PollCondition attr_accessor :above, :times, :pid_file - + def initialize super self.above = nil self.times = [1, 1] end - + def prepare if self.times.kind_of?(Integer) self.times = [self.times, self.times] end - + @timeline = Timeline.new(self.times[1]) end - + def reset @timeline.clear end - + def pid self.pid_file ? File.read(self.pid_file).strip.to_i : self.watch.pid end - + def valid? valid = true valid &= complain("Attribute 'pid_file' must be specified", self) if self.pid_file.nil? && self.watch.pid_file.nil? valid &= complain("Attribute 'above' must be specified", self) if self.above.nil? valid end - + def test process = System::Process.new(self.pid) @timeline.push(process.memory) - + history = "[" + @timeline.map { |x| "#{x > self.above ? '*' : ''}#{x}kb" }.join(", ") + "]" - + if @timeline.select { |x| x > self.above }.size >= self.times.first self.info = "memory out of bounds #{history}" return true @@ -77,6 +77,6 @@ def test end end end - + end end \ No newline at end of file diff --git a/lib/god/conditions/process_exits.rb b/lib/god/conditions/process_exits.rb index 54a82b92..4566a65a 100644 --- a/lib/god/conditions/process_exits.rb +++ b/lib/god/conditions/process_exits.rb @@ -1,9 +1,9 @@ module God module Conditions - + # Condition Symbol :process_exits # Type: Event - # + # # Trigger when a process exits. # # Paramaters @@ -24,41 +24,41 @@ module Conditions # end class ProcessExits < EventCondition attr_accessor :pid_file - + def initialize self.info = "process exited" end - + def valid? true end - + def pid self.pid_file ? File.read(self.pid_file).strip.to_i : self.watch.pid end - + def register pid = self.pid - + begin EventHandler.register(pid, :proc_exit) do |extra| formatted_extra = extra.size > 0 ? " #{extra.inspect}" : "" self.info = "process #{pid} exited#{formatted_extra}" self.watch.trigger(self) end - + msg = "#{self.watch.name} registered 'proc_exit' event for pid #{pid}" applog(self.watch, :info, msg) rescue StandardError raise EventRegistrationFailedError.new end end - + def deregister pid = self.pid if pid EventHandler.deregister(pid, :proc_exit) - + msg = "#{self.watch.name} deregistered 'proc_exit' event for pid #{pid}" applog(self.watch, :info, msg) else @@ -67,6 +67,6 @@ def deregister end end end - + end end \ No newline at end of file diff --git a/lib/god/conditions/process_running.rb b/lib/god/conditions/process_running.rb index 2877f364..0720a473 100644 --- a/lib/god/conditions/process_running.rb +++ b/lib/god/conditions/process_running.rb @@ -1,16 +1,16 @@ module God module Conditions - + # Condition Symbol :process_running # Type: Poll - # + # # Trigger when a process is running or not running depending on attributes. # # Paramaters # Required # +pid_file+ is the pid file of the process in question. Automatically # populated for Watches. - # +running" specifies whether you want to trigger if the process is + # +running" specifies whether you want to trigger if the process is # running (true) or whether it is not running (false) # # Examples @@ -35,24 +35,24 @@ module Conditions # end class ProcessRunning < PollCondition attr_accessor :running, :pid_file - + def pid self.pid_file ? File.read(self.pid_file).strip.to_i : self.watch.pid end - + def valid? valid = true valid &= complain("Attribute 'pid_file' must be specified", self) if self.pid_file.nil? && self.watch.pid_file.nil? valid &= complain("Attribute 'running' must be specified", self) if self.running.nil? valid end - + def test self.info = [] - + pid = self.pid active = pid && System::Process.new(pid).exists? - + if (self.running && active) self.info.concat(["process is running"]) true @@ -69,6 +69,6 @@ def test end end end - + end end \ No newline at end of file diff --git a/lib/god/conditions/tries.rb b/lib/god/conditions/tries.rb index fdc03add..d666485c 100644 --- a/lib/god/conditions/tries.rb +++ b/lib/god/conditions/tries.rb @@ -1,35 +1,35 @@ module God module Conditions - + class Tries < PollCondition attr_accessor :times, :within - + def prepare @timeline = Timeline.new(self.times) end - + def reset @timeline.clear end - + def valid? valid = true valid &= complain("Attribute 'times' must be specified", self) if self.times.nil? valid end - + def test @timeline << Time.now - + concensus = (@timeline.size == self.times) duration = self.within.nil? || (@timeline.last - @timeline.first) < self.within - + if within history = "[#{@timeline.size}/#{self.times} within #{(@timeline.last - @timeline.first).to_i}s]" else history = "[#{@timeline.size}/#{self.times}]" end - + if concensus && duration self.info = "tries exceeded #{history}" return true @@ -39,6 +39,6 @@ def test end end end - + end end \ No newline at end of file diff --git a/lib/god/configurable.rb b/lib/god/configurable.rb index ae6f0f02..c2cc2496 100644 --- a/lib/god/configurable.rb +++ b/lib/god/configurable.rb @@ -1,18 +1,18 @@ module God - + module Configurable # Override this method in your Configurable (optional) # # Called once after the Configurable has been sent to the block and attributes have been # set. Do any post-processing on attributes here def prepare - + end - + def reset - + end - + # Override this method in your Configurable (optional) # # Called once during evaluation of the config file. Return true if valid, false otherwise @@ -29,16 +29,16 @@ def reset def valid? true end - + def base_name x = 1 # fix for MRI's local scope optimization bug DO NOT REMOVE! self.class.name.split('::').last end - + def friendly_name base_name end - + def self.complain(text, c = nil) watch = c.watch rescue nil msg = "" @@ -48,10 +48,10 @@ def self.complain(text, c = nil) applog(watch, :error, msg) false end - + def complain(text, c = nil) Configurable.complain(text, c) end end - + end \ No newline at end of file diff --git a/lib/god/contact.rb b/lib/god/contact.rb index 5de3181d..7b782dc5 100644 --- a/lib/god/contact.rb +++ b/lib/god/contact.rb @@ -1,29 +1,29 @@ module God - + class Contact include Configurable - + attr_accessor :name, :group, :info - + def self.generate(kind) sym = kind.to_s.capitalize.gsub(/_(.)/){$1.upcase}.intern c = God::Contacts.const_get(sym).new - + unless c.kind_of?(Contact) - abort "Contact '#{c.class.name}' must subclass God::Contact" + abort "Contact '#{c.class.name}' must subclass God::Contact" end - + c rescue NameError raise NoSuchContactError.new("No Contact found with the class name God::Contacts::#{sym}") end - + def self.valid?(contact) valid = true valid &= Configurable.complain("Attribute 'name' must be specified", contact) if contact.name.nil? valid end - + # Normalize the given notify specification into canonical form. # +spec+ is the notify spec as a String, Array of Strings, or Hash # @@ -32,7 +32,7 @@ def self.valid?(contact) # Where :contacts will be present and point to an Array of Strings. Both # :priority and :category may not be present but if they are, they will each # contain a single String. - # + # # Returns normalized notify spec # Raises ArgumentError on invalid spec (message contains details) def self.normalize(spec) @@ -46,7 +46,7 @@ def self.normalize(spec) {:contacts => spec} when Hash copy = spec.dup - + # check :contacts if contacts = copy.delete(:contacts) case contacts @@ -63,27 +63,27 @@ def self.normalize(spec) else raise ArgumentError.new("must have a :contacts key") end - + # remove priority and category copy.delete(:priority) copy.delete(:category) - + # check for invalid keys unless copy.empty? raise ArgumentError.new("contains extra elements: #{copy.inspect}") end - + # normalize spec[:contacts] &&= Array(spec[:contacts]) spec[:priority] &&= spec[:priority].to_s spec[:category] &&= spec[:category].to_s - + spec else raise ArgumentError.new("must be a String (contact name), Array (of contact names), or Hash (contact specification)") end end - + # Abstract # Send the message to the external source # +message+ is the message body returned from the condition @@ -94,7 +94,7 @@ def self.normalize(spec) def notify(message, time, priority, category, host) raise AbstractMethodNotOverriddenError.new("Contact#notify must be overridden in subclasses") end - + # Construct the friendly name of this Contact, looks like: # # Contact FooBar @@ -102,5 +102,5 @@ def friendly_name super + " Contact '#{self.name}'" end end - + end \ No newline at end of file diff --git a/lib/god/contacts/email.rb b/lib/god/contacts/email.rb index 36b59eae..e56785ff 100644 --- a/lib/god/contacts/email.rb +++ b/lib/god/contacts/email.rb @@ -3,16 +3,16 @@ module God module Contacts - + class Email < Contact class << self attr_accessor :message_settings, :delivery_method, :server_settings, :sendmail_settings, :format end - + self.message_settings = {:from => 'god@example.com'} - + self.delivery_method = :smtp # or :sendmail - + self.server_settings = {:address => 'localhost', :port => 25} # :domain @@ -23,7 +23,7 @@ class << self self.sendmail_settings = {:location => '/usr/sbin/sendmail', :arguments => '-i -t' } - + self.format = lambda do |name, email, message, time, priority, category, host| <<-EOF From: god <#{self.message_settings[:from]}> @@ -38,15 +38,15 @@ class << self Category: #{category} EOF end - + attr_accessor :email - + def valid? valid = true valid &= complain("Attribute 'email' must be specified", self) if self.email.nil? valid end - + def notify(message, time, priority, category, host) begin body = Email.format.call(self.name, self.email, message, time, priority, category, host) @@ -59,7 +59,7 @@ def notify(message, time, priority, category, host) else raise "unknown delivery method: #{Email.delivery_method}" end - + self.info = "sent email to #{self.email}" rescue => e applog(nil, :info, "failed to send email to #{self.email}: #{e.message}") @@ -75,7 +75,7 @@ def notify_smtp(mail) args << Email.server_settings[:domain] args << Email.server_settings[:user_name] args << Email.server_settings[:password] - args << Email.server_settings[:authentication] + args << Email.server_settings[:authentication] end Net::SMTP.start(*args) do |smtp| diff --git a/lib/god/contacts/jabber.rb b/lib/god/contacts/jabber.rb index a52f8617..d1469534 100644 --- a/lib/god/contacts/jabber.rb +++ b/lib/god/contacts/jabber.rb @@ -17,11 +17,11 @@ module XMPP4R module God module Contacts - class Jabber < Contact + class Jabber < Contact class << self attr_accessor :settings, :format end - + self.format = lambda do |message, priority, category, host| text = "Message: #{message}\n" text += "Host: #{host}\n" if host @@ -29,13 +29,13 @@ class << self text += "Category: #{category}\n" if category return text end - + attr_accessor :jabber_id - + def valid? valid = true end - + def notify(message, time, priority, category, host) begin jabber_id = XMPP4R::JID::new "#{Jabber.settings[:jabber_id]}/God" @@ -44,7 +44,7 @@ def notify(message, time, priority, category, host) jabber_client.auth Jabber.settings[:password] body = Jabber.format.call message, priority, category, host - + message = XMPP4R::Message::new self.jabber_id, body message.set_type :normal message.set_id '1' @@ -55,11 +55,11 @@ def notify(message, time, priority, category, host) rescue => e puts e.message puts e.backtrace.join("\n") - + self.info = "failed to send jabber message to #{self.jabber_id}: #{e.message}" end end end - + end end \ No newline at end of file diff --git a/lib/god/dependency_graph.rb b/lib/god/dependency_graph.rb index 7a863427..13713671 100644 --- a/lib/god/dependency_graph.rb +++ b/lib/god/dependency_graph.rb @@ -1,17 +1,17 @@ module God class DependencyGraph attr_accessor :nodes - + def initialize self.nodes = {} end - + def add(a, b) node_a = self.nodes[a] || Node.new(a) node_b = self.nodes[b] || Node.new(b) - + node_a.add(node_b) - + self.nodes[a] ||= node_a self.nodes[b] ||= node_b end @@ -23,16 +23,16 @@ class DependencyGraph class Node attr_accessor :name attr_accessor :dependencies - + def initialize(name) self.name = name self.dependencies = [] end - + def add(node) self.dependencies << node unless self.dependencies.include?(node) end - + def has_node?(node) (self == node) || self.dependencies.any { |x| x.has_node?(node) } end diff --git a/lib/god/diagnostics.rb b/lib/god/diagnostics.rb index d9be6e4a..2ce69094 100644 --- a/lib/god/diagnostics.rb +++ b/lib/god/diagnostics.rb @@ -11,21 +11,21 @@ def start_dike class BleakHouseDiagnostic LOG_FILE = File.join(File.dirname(__FILE__), *%w[.. .. logs bleak.log]) - + class << self attr_accessor :logger end - + def self.install require 'bleak_house' self.logger = BleakHouse::Logger.new File.delete(LOG_FILE) rescue nil end - + def self.snapshot(name) self.logger.snapshot(LOG_FILE, name, false) if self.logger end - + def self.spin(delay = 1) Thread.new do loop do diff --git a/lib/god/driver.rb b/lib/god/driver.rb index 8188faf3..bd536a0b 100644 --- a/lib/god/driver.rb +++ b/lib/god/driver.rb @@ -3,7 +3,7 @@ class TimedEvent include Comparable attr_accessor :at - + # Instantiate a new TimedEvent that will be triggered after the specified delay # +delay+ is the number of seconds from now at which to trigger # @@ -23,13 +23,13 @@ def <=>(other) class DriverEvent < TimedEvent attr_accessor :condition, :task - + def initialize(delay, task, condition) super delay self.task = task self.condition = condition end - + def handle_event @task.handle_poll(@condition) end @@ -44,7 +44,7 @@ def initialize(task, name, args) self.name = name self.args = args end - + # Handle the next queued operation that was issued asynchronously # # Returns nothing @@ -52,8 +52,8 @@ def handle_event @task.send(@name, *@args) end end - - class DriverEventQueue + + class DriverEventQueue def initialize @shutdown = false @waiting = [] @@ -63,9 +63,9 @@ def initialize self.taint end - # + # # Wake any sleeping threads after setting the sentinel - # + # def shutdown @shutdown = true begin @@ -103,8 +103,8 @@ def pop alias shift pop alias deq pop - # - # Add an event to the queue, wake any waiters if what we added needs to + # + # Add an event to the queue, wake any waiters if what we added needs to # happen sooner than the next pending event # def push(event) @@ -150,7 +150,7 @@ def num_waiting class Driver attr_reader :thread - + # Instantiate a new Driver and start the scheduler loop to handle events # +task+ is the Task this Driver belongs to # @@ -158,7 +158,7 @@ class Driver def initialize(task) @task = task @events = God::DriverEventQueue.new - + @thread = Thread.new do loop do begin @@ -174,14 +174,14 @@ def initialize(task) end end end - + # Clear all events for this Driver - # + # # Returns nothing def clear_events @events.clear end - + # Queue an asynchronous message # +name+ is the Symbol name of the operation # +args+ is an optional Array of arguments @@ -190,7 +190,7 @@ def clear_events def message(name, args = []) @events.push(DriverOperation.new(@task, name, args)) end - + # Create and schedule a new DriverEvent # +condition+ is the Condition # +delay+ is the number of seconds to delay (default: interval defined in condition) @@ -198,9 +198,9 @@ def message(name, args = []) # Returns nothing def schedule(condition, delay = condition.interval) applog(nil, :debug, "driver schedule #{condition} in #{delay} seconds") - + @events.push(DriverEvent.new(delay, @task, condition)) end end # Driver - + end # God diff --git a/lib/god/errors.rb b/lib/god/errors.rb index 6ba50c16..5b4814d9 100644 --- a/lib/god/errors.rb +++ b/lib/god/errors.rb @@ -1,24 +1,24 @@ module God - + class AbstractMethodNotOverriddenError < StandardError end - + class NoSuchWatchError < StandardError end - + class NoSuchConditionError < StandardError end - + class NoSuchBehaviorError < StandardError end - + class NoSuchContactError < StandardError end - + class InvalidCommandError < StandardError end - + class EventRegistrationFailedError < StandardError end - + end \ No newline at end of file diff --git a/lib/god/event_handler.rb b/lib/god/event_handler.rb index c46965ef..2bd6b3e9 100644 --- a/lib/god/event_handler.rb +++ b/lib/god/event_handler.rb @@ -3,15 +3,15 @@ class EventHandler @@actions = {} @@handler = nil @@loaded = false - + def self.loaded? @@loaded end - + def self.event_system @@handler::EVENT_SYSTEM end - + def self.load begin case RUBY_PLATFORM @@ -31,13 +31,13 @@ def self.load @@loaded = false end end - + def self.register(pid, event, &block) @@actions[pid] ||= {} @@actions[pid][event] = block @@handler.register_process(pid, @@actions[pid].keys) end - + def self.deregister(pid, event=nil) if watching_pid? pid running = ::Process.kill(0, pid.to_i) rescue false @@ -50,15 +50,15 @@ def self.deregister(pid, event=nil) end end end - + def self.call(pid, event, extra_data = {}) @@actions[pid][event].call(extra_data) if watching_pid?(pid) && @@actions[pid][event] end - + def self.watching_pid?(pid) @@actions[pid] end - + def self.start Thread.new do loop do @@ -71,41 +71,41 @@ def self.start end end end - + # do a real test to make sure events are working properly @@loaded = self.operational? end - + def self.operational? com = [false] - + Thread.new do begin event_system = God::EventHandler.event_system - + pid = fork do loop { sleep(1) } end - + self.register(pid, :proc_exit) do com[0] = true end - + ::Process.kill('KILL', pid) - + sleep(0.1) - + self.deregister(pid, :proc_exit) rescue nil rescue => e puts e.message puts e.backtrace.join("\n") end end.join - + sleep(0.1) - + com.first end - + end end \ No newline at end of file diff --git a/lib/god/event_handlers/dummy_handler.rb b/lib/god/event_handlers/dummy_handler.rb index e453dbbd..2e700d4b 100644 --- a/lib/god/event_handlers/dummy_handler.rb +++ b/lib/god/event_handlers/dummy_handler.rb @@ -1,11 +1,11 @@ module God class DummyHandler EVENT_SYSTEM = "none" - + def self.register_process raise NotImplementedError end - + def self.handle_events raise NotImplementedError end diff --git a/lib/god/event_handlers/kqueue_handler.rb b/lib/god/event_handlers/kqueue_handler.rb index 1068bed4..7d01e1a9 100644 --- a/lib/god/event_handlers/kqueue_handler.rb +++ b/lib/god/event_handlers/kqueue_handler.rb @@ -3,11 +3,11 @@ module God class KQueueHandler EVENT_SYSTEM = "kqueue" - + def self.register_process(pid, events) monitor_process(pid, events_mask(events)) end - + def self.events_mask(events) events.inject(0) do |mask, event| mask |= event_mask(event) diff --git a/lib/god/event_handlers/netlink_handler.rb b/lib/god/event_handlers/netlink_handler.rb index eb7e1573..6d775850 100644 --- a/lib/god/event_handlers/netlink_handler.rb +++ b/lib/god/event_handlers/netlink_handler.rb @@ -3,7 +3,7 @@ module God class NetlinkHandler EVENT_SYSTEM = "netlink" - + def self.register_process(pid, events) # netlink doesn't need to do this # it just reads from the eventhandler actions to see if the pid diff --git a/lib/god/logger.rb b/lib/god/logger.rb index 5215c6b0..bb59707b 100644 --- a/lib/god/logger.rb +++ b/lib/god/logger.rb @@ -1,20 +1,20 @@ module God - + class Logger < SimpleLogger SYSLOG_EQUIVALENTS = {:fatal => :crit, :error => :err, :warn => :debug, :info => :debug, :debug => :debug} - + attr_accessor :logs - + class << self attr_accessor :syslog end - + self.syslog ||= true - + # Instantiate a new Logger object def initialize super($stdout) @@ -26,17 +26,17 @@ def initialize @templog.level = Logger::INFO load_syslog end - + # If Logger.syslog is true then attempt to load the syslog bindings. If syslog # cannot be loaded, then set Logger.syslog to false and continue. # # Returns nothing def load_syslog return unless Logger.syslog - + begin require 'syslog' - + # Ensure that Syslog is open begin Syslog.open('god') @@ -47,7 +47,7 @@ def load_syslog Logger.syslog = false end end - + # Log a message # +watch+ is the String name of the Watch (may be nil if not Watch is applicable) # +level+ is the log level [:debug|:info|:warn|:error|:fatal] @@ -57,7 +57,7 @@ def load_syslog def log(watch, level, text) # initialize watch log if necessary self.logs[watch.name] ||= Timeline.new(God::LOG_BUFFER_SIZE_DEFAULT) if watch - + # push onto capture and timeline for the given watch @templogio.truncate(0) @templogio.rewind @@ -66,14 +66,14 @@ def log(watch, level, text) @capture.puts(@templogio.string.dup) if @capture self.logs[watch.name] << [Time.now, @templogio.string.dup] if watch end - + # send to regular logger self.send(level, text % []) - + # send to syslog Syslog.send(SYSLOG_EQUIVALENTS[level], text) if Logger.syslog end - + # Get all log output for a given Watch since a certain Time. # +watch_name+ is the String name of the Watch # +since+ is the Time since which to fetch log lines @@ -82,7 +82,7 @@ def log(watch, level, text) def watch_log_since(watch_name, since) # initialize watch log if necessary self.logs[watch_name] ||= Timeline.new(God::LOG_BUFFER_SIZE_DEFAULT) - + # get and join lines since given time @mutex.synchronize do self.logs[watch_name].select do |x| @@ -92,9 +92,9 @@ def watch_log_since(watch_name, since) end.join end end - + # private - + # Enable capturing of log # # Returns nothing @@ -103,7 +103,7 @@ def start_capture @capture = StringIO.new end end - + # Disable capturing of log and return what was captured since # capturing was enabled with Logger#start_capture # @@ -116,5 +116,5 @@ def finish_capture end end end - + end \ No newline at end of file diff --git a/lib/god/metric.rb b/lib/god/metric.rb index 936f2ab6..7a709a82 100644 --- a/lib/god/metric.rb +++ b/lib/god/metric.rb @@ -1,14 +1,14 @@ module God - + class Metric attr_accessor :watch, :destination, :conditions - + def initialize(watch, destination = nil) self.watch = watch self.destination = destination self.conditions = [] end - + # Instantiate a Condition of type +kind+ and pass it into the optional # block. Attributes of the condition must be set in the config file def condition(kind) @@ -18,18 +18,18 @@ def condition(kind) rescue NoSuchConditionError => e abort e.message end - + # send to block so config can set attributes yield(c) if block_given? - + # call prepare on the condition c.prepare - + # test generic and specific validity unless Condition.valid?(c) && c.valid? abort "Exiting on invalid condition" end - + # inherit interval from watch if no poll condition specific interval was set if c.kind_of?(PollCondition) && !c.interval if self.watch.interval @@ -38,22 +38,22 @@ def condition(kind) abort "No interval set for Condition '#{c.class.name}' in Watch '#{self.watch.name}', and no default Watch interval from which to inherit" end end - + # remember self.conditions << c end - + def enable self.conditions.each do |c| self.watch.attach(c) end end - + def disable self.conditions.each do |c| self.watch.detach(c) end end end - + end \ No newline at end of file diff --git a/lib/god/process.rb b/lib/god/process.rb index 6817416e..fdb16e67 100644 --- a/lib/god/process.rb +++ b/lib/god/process.rb @@ -1,19 +1,19 @@ module God class Process WRITES_PID = [:start, :restart] - + attr_accessor :name, :uid, :gid, :log, :start, :stop, :restart, :unix_socket, :chroot, :env - + def initialize self.log = '/dev/null' - + @pid_file = nil @tracking_pid = true @user_log = false @pid = nil @unix_socket = nil end - + def alive? if self.pid System::Process.new(self.pid).exists? @@ -21,7 +21,7 @@ def alive? false end end - + def file_writable?(file) pid = fork do uid_num = Etc.getpwnam(self.uid).uid if self.uid @@ -34,29 +34,29 @@ def file_writable?(file) File.writable?(file_in_chroot(file)) ? exit(0) : exit(1) end - + wpid, status = ::Process.waitpid2(pid) status.exitstatus == 0 ? true : false end - + def valid? # determine if we're tracking pid or not self.pid_file - + valid = true - + # a start command must be specified if self.start.nil? valid = false applog(self, :error, "No start command was specified") end - + # self-daemonizing processes must specify a stop command if !@tracking_pid && self.stop.nil? valid = false applog(self, :error, "No stop command was specified") end - + # uid must exist if specified if self.uid begin @@ -66,7 +66,7 @@ def valid? applog(self, :error, "UID for '#{self.uid}' does not exist") end end - + # gid must exist if specified if self.gid begin @@ -76,25 +76,25 @@ def valid? applog(self, :error, "GID for '#{self.gid}' does not exist") end end - + # pid dir must exist if specified if !@tracking_pid && !File.exist?(File.dirname(self.pid_file)) valid = false applog(self, :error, "PID file directory '#{File.dirname(self.pid_file)}' does not exist") end - + # pid dir must be writable if specified if !@tracking_pid && File.exist?(File.dirname(self.pid_file)) && !file_writable?(File.dirname(self.pid_file)) valid = false applog(self, :error, "PID file directory '#{File.dirname(self.pid_file)}' is not writable by #{self.uid || Etc.getlogin}") end - + # log dir must exist if !File.exist?(File.dirname(self.log)) valid = false applog(self, :error, "Log directory '#{File.dirname(self.log)}' does not exist") end - + # log file or dir must be writable if File.exist?(self.log) unless file_writable?(self.log) @@ -120,10 +120,10 @@ def valid? LOG.log(self, :error, "CHROOT directory '#{self.chroot}' does not contain '/dev/null'") end end - + valid end - + # DON'T USE THIS INTERNALLY. Use the instance variable. -- Kev # No really, trust me. Use the instance variable. def pid_file=(value) @@ -133,14 +133,14 @@ def pid_file=(value) else @tracking_pid = true end - + @pid_file = value end - + def pid_file @pid_file ||= default_pid_file end - + # Fetch the PID from pid_file. If the pid_file does not # exist, then use the PID from the last time it was read. # If it has never been read, then return nil. @@ -149,7 +149,7 @@ def pid_file def pid contents = File.read(self.pid_file).strip rescue '' real_pid = contents =~ /^\d+$/ ? contents.to_i : nil - + if real_pid @pid = real_pid real_pid @@ -157,35 +157,35 @@ def pid @pid end end - + def start! call_action(:start) end - + def stop! call_action(:stop) end - + def restart! call_action(:restart) end - + def default_pid_file File.join(God.pid_file_directory, "#{self.name}.pid") end - + def call_action(action) command = send(action) - + if action == :stop && command.nil? pid = self.pid name = self.name command = lambda do applog(self, :info, "#{self.name} stop: default lambda killer") - + ::Process.kill('TERM', pid) rescue nil applog(self, :info, "#{self.name} sent SIGTERM") - + # Poll to see if it's dead 5.times do begin @@ -195,18 +195,18 @@ def call_action(action) applog(self, :info, "#{self.name} process stopped") return end - + sleep 1 end - + ::Process.kill('KILL', pid) rescue nil applog(self, :info, "#{self.name} still alive; sent SIGKILL") end end - + if command.kind_of?(String) pid = nil - + if @tracking_pid # double fork god-daemonized processes # we don't want to wait for them to finish @@ -218,7 +218,7 @@ def call_action(action) pid = self.spawn(command) puts pid.to_s # send pid back to forker end - + ::Process.waitpid(opid, 0) w.close pid = r.gets.chomp @@ -233,19 +233,19 @@ def call_action(action) pid = self.spawn(command) status = ::Process.waitpid2(pid, 0) exit_code = status[1] >> 8 - + if exit_code != 0 applog(self, :warn, "#{self.name} #{action} command exited with non-zero code = #{exit_code}") end - + ensure_stop if action == :stop end - + if @tracking_pid or (@pid_file.nil? and WRITES_PID.include?(action)) File.open(default_pid_file, 'w') do |f| f.write pid end - + @tracking_pid = true @pid_file = default_pid_file end @@ -256,7 +256,7 @@ def call_action(action) raise NotImplementedError end end - + # Fork/exec the given command, returns immediately # +command+ is the String containing the shell command # @@ -276,7 +276,7 @@ def spawn(command) STDIN.reopen "/dev/null" STDOUT.reopen file_in_chroot(self.log), "a" STDERR.reopen STDOUT - + # close any other file descriptors 3.upto(256){|fd| IO::new(fd).close rescue nil} @@ -289,7 +289,7 @@ def spawn(command) exec command unless command.empty? end end - + # Ensure that a stop command actually stops the process. Force kill # if necessary. # @@ -299,7 +299,7 @@ def ensure_stop applog(self, :warn, "#{self.name} stop called but pid is uknown") return end - + # Poll to see if it's dead 10.times do begin @@ -308,10 +308,10 @@ def ensure_stop # It died. Good. return end - + sleep 1 end - + # last resort ::Process.kill('KILL', self.pid) rescue nil applog(self, :warn, "#{self.name} process still running 10 seconds after stop command returned. Force killing.") diff --git a/lib/god/registry.rb b/lib/god/registry.rb index 2a5e71d9..d0f7542e 100644 --- a/lib/god/registry.rb +++ b/lib/god/registry.rb @@ -2,29 +2,29 @@ module God def self.registry @registry ||= Registry.new end - + class Registry def initialize @storage = {} end - + def add(item) # raise TypeError unless item.is_a? God::Process @storage[item.name] = item end - + def remove(item) @storage.delete(item.name) end - + def size @storage.size end - + def [](name) @storage[name] end - + def reset @storage.clear end diff --git a/lib/god/simple_logger.rb b/lib/god/simple_logger.rb index 97677095..afd15117 100644 --- a/lib/god/simple_logger.rb +++ b/lib/god/simple_logger.rb @@ -1,53 +1,53 @@ module God - + class SimpleLogger DEBUG = 2 INFO = 4 WARN = 8 ERROR = 16 FATAL = 32 - + SEV_LABEL = {DEBUG => 'DEBUG', INFO => 'INFO', WARN => 'WARN', ERROR => 'ERROR', FATAL => 'FATAL'} - + attr_accessor :datetime_format, :level - + def initialize(io) @io = io @level = INFO @datetime_format = "%Y-%m-%d %H:%M:%S" end - + def output(level, msg) return if level < self.level - + time = Time.now.strftime(self.datetime_format) label = SEV_LABEL[level] @io.print("#{label[0..0]} [#{time}] #{label.rjust(5)}: #{msg}\n") end - + def fatal(msg) self.output(FATAL, msg) end - + def error(msg) self.output(ERROR, msg) end - + def warn(msg) self.output(WARN, msg) end - + def info(msg) self.output(INFO, msg) end - + def debug(msg) self.output(DEBUG, msg) end end - + end \ No newline at end of file diff --git a/lib/god/socket.rb b/lib/god/socket.rb index 72914032..0b178f8f 100644 --- a/lib/god/socket.rb +++ b/lib/god/socket.rb @@ -5,7 +5,7 @@ module God # The God::Server oversees the DRb server which dishes out info on this God daemon. class Socket attr_reader :port - + # The location of the socket for a given port # +port+ is the port number # @@ -13,7 +13,7 @@ class Socket def self.socket_file(port) "/tmp/god.#{port}.sock" end - + # The address of the socket for a given port # +port+ is the port number # @@ -21,40 +21,40 @@ def self.socket_file(port) def self.socket(port) "drbunix://#{self.socket_file(port)}" end - + # The location of the socket for this Server # # Returns String (file location) def socket_file self.class.socket_file(@port) end - + # The address of the socket for this Server # # Returns String (drb address) def socket self.class.socket(@port) end - + # Create a new Server and star the DRb server # +port+ is the port on which to start the DRb service (default nil) def initialize(port = nil) @port = port start end - + # Returns true def ping true end - + # Forward API calls to God # # Returns whatever the forwarded call returns def method_missing(*args, &block) God.send(*args, &block) end - + # Stop the DRb server and delete the socket file # # Returns nothing @@ -62,9 +62,9 @@ def stop DRb.stop_service FileUtils.rm_f(self.socket_file) end - + private - + # Start the DRb server. Abort if there is already a running god instance # on the socket. # @@ -77,7 +77,7 @@ def start applog(nil, :info, "Socket already in use") DRb.start_service server = DRbObject.new(nil, self.socket) - + begin Timeout.timeout(5) do server.ping diff --git a/lib/god/sugar.rb b/lib/god/sugar.rb index 11795572..6b625baf 100644 --- a/lib/god/sugar.rb +++ b/lib/god/sugar.rb @@ -2,45 +2,45 @@ class Numeric def seconds self end - + alias :second :seconds - + def minutes self * 60 end - + alias :minute :minutes - + def hours self * 3600 end - + alias :hour :hours - + def days self * 86400 end - + alias :day :days - + def kilobytes self end - + alias :kilobyte :kilobytes - + def megabytes self * 1024 end - + alias :megabyte :megabytes - + def gigabytes self * (1024 ** 2) end - + alias :gigabyte :gigabytes - + def percent self end diff --git a/lib/god/system/portable_poller.rb b/lib/god/system/portable_poller.rb index 95eb0e5a..d9a28759 100644 --- a/lib/god/system/portable_poller.rb +++ b/lib/god/system/portable_poller.rb @@ -8,31 +8,31 @@ def initialize(pid) def memory ps_int('rss') end - + # Percentage memory usage def percent_memory ps_float('%mem') end - + # Percentage CPU usage def percent_cpu ps_float('%cpu') end - + private - + def ps_int(keyword) `ps -o #{keyword}= -p #{@pid}`.to_i end - + def ps_float(keyword) `ps -o #{keyword}= -p #{@pid}`.to_f end - + def ps_string(keyword) `ps -o #{keyword}= -p #{@pid}`.strip end - + def time_string_to_seconds(text) _, minutes, seconds, useconds = *text.match(/(\d+):(\d{2}).(\d{2})/) (minutes.to_i * 60) + seconds.to_i diff --git a/lib/god/system/process.rb b/lib/god/system/process.rb index e2741bf0..0ce0051b 100644 --- a/lib/god/system/process.rb +++ b/lib/god/system/process.rb @@ -1,34 +1,34 @@ module God module System - + class Process def initialize(pid) @pid = pid.to_i @poller = fetch_system_poller.new(@pid) end - + # Return true if this process is running, false otherwise def exists? !!::Process.kill(0, @pid) rescue false end - + # Memory usage in kilobytes (resident set size) def memory @poller.memory end - + # Percentage memory usage def percent_memory @poller.percent_memory end - + # Percentage CPU usage def percent_cpu @poller.percent_cpu end - + private - + def fetch_system_poller if SlashProcPoller.usable? SlashProcPoller @@ -37,6 +37,6 @@ def fetch_system_poller end end end - + end end \ No newline at end of file diff --git a/lib/god/system/slash_proc_poller.rb b/lib/god/system/slash_proc_poller.rb index 31879c8c..2c256edb 100644 --- a/lib/god/system/slash_proc_poller.rb +++ b/lib/god/system/slash_proc_poller.rb @@ -4,12 +4,12 @@ class SlashProcPoller < PortablePoller @@kb_per_page = 4 # TODO: Need to make this portable @@hertz = 100 @@total_mem = nil - + MeminfoPath = '/proc/meminfo' UptimePath = '/proc/uptime' - + RequiredPaths = [MeminfoPath, UptimePath] - + # FreeBSD has /proc by default, but nothing mounted there! # So we should check for the actual required paths! # Returns true if +RequiredPaths+ are readable. @@ -18,29 +18,29 @@ def self.usable? test(?r, path) end end - + def initialize(pid) super(pid) - + unless @@total_mem # in K File.open(MeminfoPath) do |f| @@total_mem = f.gets.split[1] end end end - + def memory stat[:rss].to_i * @@kb_per_page rescue # This shouldn't fail is there's an error (or proc doesn't exist) 0 end - + def percent_memory (memory / @@total_mem.to_f) * 100 rescue # This shouldn't fail is there's an error (or proc doesn't exist) 0 end - + # TODO: Change this to calculate the wma instead def percent_cpu stats = stat @@ -54,14 +54,14 @@ def percent_cpu rescue # This shouldn't fail is there's an error (or proc doesn't exist) 0 end - + private - + # in seconds def uptime File.read(UptimePath).split[0].to_f end - + def stat stats = {} stats[:pid], stats[:comm], stats[:state], stats[:ppid], stats[:pgrp], diff --git a/lib/god/task.rb b/lib/god/task.rb index 7c88da79..bd0ff185 100644 --- a/lib/god/task.rb +++ b/lib/god/task.rb @@ -1,87 +1,87 @@ module God - + class Task attr_accessor :name, :interval, :group, :valid_states, :initial_state, :driver - + attr_writer :autostart def autostart?; @autostart; end - + # api attr_accessor :state, :behaviors, :metrics, :directory - + def initialize @autostart ||= true - + # initial state is unmonitored self.state = :unmonitored - + # the list of behaviors self.behaviors = [] - + # the list of conditions for each action self.metrics = {nil => [], :unmonitored => [], :stop => []} - + # the condition -> metric lookup self.directory = {} - + # driver self.driver = Driver.new(self) end - + def prepare self.valid_states.each do |state| self.metrics[state] ||= [] end end - + def valid? valid = true - + # a name must be specified if self.name.nil? valid = false applog(self, :error, "No name was specified") end - + # valid_states must be specified if self.valid_states.nil? valid = false applog(self, :error, "No valid_states array was specified") end - + # valid_states must be specified if self.initial_state.nil? valid = false applog(self, :error, "No initial_state was specified") end - + valid end - + ########################################################################### # # Advanced mode # ########################################################################### - + def canonical_hash_form(to) to.instance_of?(Symbol) ? {true => to} : to end - + # Define a transition handler which consists of a set of conditions def transition(start_states, end_states) # convert end_states into canonical hash form canonical_end_states = canonical_hash_form(end_states) - + Array(start_states).each do |start_state| # validate start state unless self.valid_states.include?(start_state) abort "Invalid state :#{start_state}. Must be one of the symbols #{self.valid_states.map{|x| ":#{x}"}.join(', ')}" end - + # create a new metric to hold the watch, end states, and conditions m = Metric.new(self, canonical_end_states) - + if block_given? # let the config file define some conditions on the metric yield(m) @@ -91,54 +91,54 @@ def transition(start_states, end_states) c.what = true end end - + # populate the condition -> metric directory m.conditions.each do |c| self.directory[c] = m end - + # record the metric self.metrics[start_state] ||= [] self.metrics[start_state] << m end end - + def lifecycle # create a new metric to hold the watch and conditions m = Metric.new(self) - + # let the config file define some conditions on the metric yield(m) - + # populate the condition -> metric directory m.conditions.each do |c| self.directory[c] = m end - + # record the metric self.metrics[nil] << m end - + ########################################################################### # # Lifecycle # ########################################################################### - + # Enable monitoring # # Returns nothing def monitor self.move(self.initial_state) end - + # Disable monitoring # # Returns nothing def unmonitor self.move(:unmonitored) end - + # Move to the givent state # +to_state+ is the Symbol representing the state to move to # @@ -146,85 +146,85 @@ def unmonitor def move(to_state) if Thread.current != self.driver.thread # called from outside Driver - + # send an async message to Driver self.driver.message(:move, [to_state]) else # called from within Driver - + # record original info orig_to_state = to_state from_state = self.state - + # log msg = "#{self.name} move '#{from_state}' to '#{to_state}'" applog(self, :info, msg) - + # cleanup from current state self.driver.clear_events self.metrics[from_state].each { |m| m.disable } if to_state == :unmonitored self.metrics[nil].each { |m| m.disable } end - + # perform action self.action(to_state) - + # enable simple mode if [:start, :restart].include?(to_state) && self.metrics[to_state].empty? to_state = :up end - + # move to new state self.metrics[to_state].each { |m| m.enable } - + # if no from state, enable lifecycle metric if from_state == :unmonitored self.metrics[nil].each { |m| m.enable } end - + # set state self.state = to_state - + # broadcast to interested TriggerConditions Trigger.broadcast(self, :state_change, [from_state, orig_to_state]) - + # log msg = "#{self.name} moved '#{from_state}' to '#{to_state}'" applog(self, :info, msg) end - + self end - + # Notify the Driver that an EventCondition has triggered # # Returns nothing def trigger(condition) self.driver.message(:handle_event, [condition]) end - + ########################################################################### # # Actions # ########################################################################### - + def method_missing(sym, *args) unless (sym.to_s =~ /=$/) super end - + base = sym.to_s.chop.intern - + unless self.valid_states.include?(base) super end - + self.class.send(:attr_accessor, base) self.send(sym, *args) end - + # Perform the given action # +a+ is the action Symbol # +c+ is the Condition @@ -233,25 +233,25 @@ def method_missing(sym, *args) def action(a, c = nil) if Thread.current != self.driver.thread # called from outside Driver - + # send an async message to Driver self.driver.message(:action, [a, c]) else # called from within Driver - + if self.respond_to?(a) command = self.send(a) - + case command when String msg = "#{self.name} #{a}: #{command}" applog(self, :info, msg) - + system(command) when Proc msg = "#{self.name} #{a}: lambda" applog(self, :info, msg) - + command.call else raise NotImplementedError @@ -259,13 +259,13 @@ def action(a, c = nil) end end end - + ########################################################################### # # Events # ########################################################################### - + def attach(condition) case condition when PollCondition @@ -274,7 +274,7 @@ def attach(condition) condition.register end end - + def detach(condition) case condition when PollCondition @@ -283,27 +283,27 @@ def detach(condition) condition.deregister end end - + ########################################################################### # # Registration # ########################################################################### - + def register! # override if necessary end - + def unregister! # override if necessary end - + ########################################################################### # # Handlers # ########################################################################### - + # Evaluate and handle the given poll condition. Handles logging # notifications, and moving to the new state if necessary # +condition+ is the Condition to handle @@ -312,23 +312,23 @@ def unregister! def handle_poll(condition) # lookup metric metric = self.directory[condition] - + # run the test result = condition.test - + # log messages = self.log_line(self, metric, condition, result) - + # notify if condition.notify && self.trigger?(metric, result) self.notify(condition, messages.last) end - + # after-condition condition.after - + # get the destination - dest = + dest = if result && condition.transition # condition override condition.transition @@ -336,7 +336,7 @@ def handle_poll(condition) # regular metric.destination && metric.destination[result] end - + # transition or reschedule if dest # transition @@ -345,7 +345,7 @@ def handle_poll(condition) rescue EventRegistrationFailedError msg = self.name + ' Event registration failed, moving back to previous state' applog(self, :info, msg) - + dest = self.state retry end @@ -354,7 +354,7 @@ def handle_poll(condition) self.driver.schedule(condition) end end - + # Asynchronously evaluate and handle the given event condition. Handles logging # notifications, and moving to the new state if necessary # +condition+ is the Condition to handle @@ -363,17 +363,17 @@ def handle_poll(condition) def handle_event(condition) # lookup metric metric = self.directory[condition] - + # log messages = self.log_line(self, metric, condition, true) - + # notify if condition.notify && self.trigger?(metric, true) self.notify(condition, messages.last) end - + # get the destination - dest = + dest = if condition.transition # condition override condition.transition @@ -381,12 +381,12 @@ def handle_event(condition) # regular metric.destination && metric.destination[true] end - + if dest self.move(dest) end end - + # Determine whether a trigger happened # +metric+ is the Metric # +result+ is the result from the condition's test @@ -395,7 +395,7 @@ def handle_event(condition) def trigger?(metric, result) metric.destination && metric.destination[result] end - + # Log info about the condition and return the list of messages logged # +watch+ is the Watch # +metric+ is the Metric @@ -404,15 +404,15 @@ def trigger?(metric, result) # # Returns String[] def log_line(watch, metric, condition, result) - status = + status = if self.trigger?(metric, result) "[trigger]" else "[ok]" end - + messages = [] - + # log info if available if condition.info Array(condition.info).each do |condition_info| @@ -423,14 +423,14 @@ def log_line(watch, metric, condition, result) messages << "#{watch.name} #{status} (#{condition.base_name})" status == '[trigger]' ? applog(watch, :warn, messages.last) : applog(watch, :info, messages.last) end - + # log debug_message = watch.name + ' ' + condition.base_name + " [#{result}] " + self.dest_desc(metric, condition) applog(watch, :debug, debug_message) - + messages end - + # Format the destination specification for use in debug logging # +metric+ is the Metric # +condition+ is the Condition @@ -447,7 +447,7 @@ def dest_desc(metric, condition) end end end - + # Notify all recipeients of the given condition with the specified message # +condition+ is the Condition # +message+ is the String message to send @@ -456,7 +456,7 @@ def dest_desc(metric, condition) def notify(condition, message) spec = Contact.normalize(condition.notify) unmatched = [] - + # resolve contacts resolved_contacts = spec[:contacts].inject([]) do |acc, contact_name_or_group| @@ -465,23 +465,23 @@ def notify(condition, message) acc += cons acc end - + # warn about unmatched contacts unless unmatched.empty? msg = "#{condition.watch.name} no matching contacts for '#{unmatched.join(", ")}'" applog(condition.watch, :warn, msg) end - + # notify each contact resolved_contacts.each do |c| host = `hostname`.chomp rescue 'none' c.notify(message, Time.now, spec[:priority], spec[:category], host) - + msg = "#{condition.watch.name} #{c.info ? c.info : "notification sent for contact: #{c.name}"} (#{c.base_name})" - + applog(condition.watch, :info, msg % []) end end end - + end diff --git a/lib/god/timeline.rb b/lib/god/timeline.rb index 4d021bd4..61f2f59e 100644 --- a/lib/god/timeline.rb +++ b/lib/god/timeline.rb @@ -1,5 +1,5 @@ module God - + class Timeline < Array # Instantiate a new Timeline # +max_size+ is the maximum size to which the timeline should grow @@ -9,7 +9,7 @@ def initialize(max_size) super() @max_size = max_size end - + # Push a value onto the Timeline # +val+ is the value to push # @@ -18,8 +18,8 @@ def push(val) self.concat([val]) shift if size > @max_size end - + alias_method :<<, :push end - + end \ No newline at end of file diff --git a/lib/god/trigger.rb b/lib/god/trigger.rb index 0f022840..f753dc2b 100644 --- a/lib/god/trigger.rb +++ b/lib/god/trigger.rb @@ -1,43 +1,43 @@ module God - + class Trigger - + class << self attr_accessor :triggers # {task.name => condition} end - + # init self.triggers = {} @mutex = Mutex.new - + def self.register(condition) @mutex.synchronize do self.triggers[condition.watch.name] ||= [] self.triggers[condition.watch.name] << condition end end - + def self.deregister(condition) @mutex.synchronize do self.triggers[condition.watch.name].delete(condition) self.triggers.delete(condition.watch.name) if self.triggers[condition.watch.name].empty? end end - + def self.broadcast(task, message, payload) return unless self.triggers[task.name] - + @mutex.synchronize do self.triggers[task.name].each do |t| t.process(message, payload) end end end - + def self.reset self.triggers.clear end - + end - + end \ No newline at end of file diff --git a/lib/god/watch.rb b/lib/god/watch.rb index 34589cf4..ececbdd8 100644 --- a/lib/god/watch.rb +++ b/lib/god/watch.rb @@ -2,43 +2,43 @@ require 'forwardable' module God - + class Watch < Task VALID_STATES = [:init, :up, :start, :restart] INITIAL_STATE = :init - + # config attr_accessor :grace, :start_grace, :stop_grace, :restart_grace - + extend Forwardable def_delegators :@process, :name, :uid, :gid, :start, :stop, :restart, :name=, :uid=, :gid=, :start=, :stop=, :restart=, :pid_file, :pid_file=, :log, :log=, :alive?, :pid, :unix_socket, :unix_socket=, :chroot, :chroot=, :env, :env= - # + # def initialize super - + @process = God::Process.new - + # valid states self.valid_states = VALID_STATES self.initial_state = INITIAL_STATE - + # no grace period by default self.grace = self.start_grace = self.stop_grace = self.restart_grace = 0 end - + def valid? super && @process.valid? end - + ########################################################################### # # Behavior # ########################################################################### - + def behavior(kind) # create the behavior begin @@ -46,47 +46,47 @@ def behavior(kind) rescue NoSuchBehaviorError => e abort e.message end - + # send to block so config can set attributes yield(b) if block_given? - + # abort if the Behavior is invalid, the Behavior will have printed # out its own error messages by now abort unless b.valid? - + self.behaviors << b end - + ########################################################################### # # Simple mode # ########################################################################### - + def start_if self.transition(:up, :start) do |on| yield(on) end end - + def restart_if self.transition(:up, :restart) do |on| yield(on) end end - - def stop_if - self.transition(:up, :stop) do |on| - yield(on) - end + + def stop_if + self.transition(:up, :stop) do |on| + yield(on) + end end - + ########################################################################### # # Lifecycle # ########################################################################### - + # Enable monitoring def monitor # start monitoring at the first available of the init or up states @@ -96,22 +96,22 @@ def monitor self.move(:up) end end - + ########################################################################### # # Actions # ########################################################################### - + def action(a, c = nil) if Thread.current != self.driver.thread # called from outside Driver - + # send an async message to Driver self.driver.message(:action, [a, c]) else # called from within Driver - + case a when :start call_action(c, :start) @@ -129,10 +129,10 @@ def action(a, c = nil) sleep(self.stop_grace + self.grace) end end - + self end - + def call_action(condition, action) # before before_items = self.behaviors @@ -144,15 +144,15 @@ def call_action(condition, action) applog(self, :info, msg) end end - + # log if self.send(action) msg = "#{self.name} #{action}: #{self.send(action).to_s}" applog(self, :info, msg) end - + @process.call_action(action) - + # after after_items = self.behaviors after_items += [condition] if condition @@ -164,20 +164,20 @@ def call_action(condition, action) end end end - + ########################################################################### # # Registration # ########################################################################### - + def register! God.registry.add(@process) end - + def unregister! God.registry.remove(@process) end end - + end diff --git a/site/index.html b/site/index.html index 7f991fd6..4f4657c1 100644 --- a/site/index.html +++ b/site/index.html @@ -46,18 +46,18 @@

Features

  • Easily control non-daemonizing scripts
  • - +

    Installation (v 0.7.3)

    The best way to get god is via rubygems:

    $ sudo gem install god

    You can also peruse or clone the code from git://github.com/mojombo/god.git

    - +

    Requirements

    - +

    God currently only works on Linux (kernel 2.6.15+), BSD, and Darwin systems. No support for Windows is planned. Event based conditions on Linux systems require the cn (connector) kernel module loaded or compiled in to the kernel and god must be run as root.

    - +

    The following systems have been tested. Help us test it on others!

    - +