From a9c3f67a44a4c93fa1bd372724d68ddb1d02a4bd Mon Sep 17 00:00:00 2001 From: Tom Preston-Werner Date: Sat, 16 Jun 2007 21:52:28 -0700 Subject: [PATCH] added MemoryUsage and factored out ProcessCondition --- Manifest.txt | 2 +- bin/devgod | 8 --- lib/god.rb | 3 ++ lib/god/condition.rb | 32 +++++++++++- lib/god/conditions/memory_usage.rb | 44 ++++++++++++++++ lib/god/conditions/process_not_running.rb | 57 ++++++++------------ lib/god/conditions/timeline.rb | 17 ++++++ lib/god/meddle.rb | 4 +- ...process_not_running.rb => process_condition.rb} | 26 ++++----- lib/god/watch.rb | 61 +++++++++++++++++----- test/configs/real.rb | 20 +++++-- test/test_condition.rb | 4 +- test/test_timeline.rb | 24 +++++++++ test/test_watch.rb | 26 +++++++++ 14 files changed, 241 insertions(+), 87 deletions(-) delete mode 100755 bin/devgod create mode 100644 lib/god/conditions/memory_usage.rb rewrite lib/god/conditions/process_not_running.rb (93%) create mode 100644 lib/god/conditions/timeline.rb copy lib/god/{conditions/process_not_running.rb => process_condition.rb} (50%) create mode 100644 test/test_timeline.rb diff --git a/Manifest.txt b/Manifest.txt index 8792cd4..53985d4 100644 --- a/Manifest.txt +++ b/Manifest.txt @@ -15,8 +15,8 @@ lib/god/watch.rb test/configs/no_watches.rb test/configs/real.rb test/configs/sample.rb +test/helper.rb test/test_condition.rb -test/test_helper.rb test/test_meddle.rb test/test_system_process.rb test/test_watch.rb diff --git a/bin/devgod b/bin/devgod deleted file mode 100755 index fb3d589..0000000 --- a/bin/devgod +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env ruby - -require 'rubygems' -require File.join(File.dirname(__FILE__), *%w[.. lib god]) - -file = ARGV[0] - -load file \ No newline at end of file diff --git a/lib/god.rb b/lib/god.rb index a55a6be..44152c3 100644 --- a/lib/god.rb +++ b/lib/god.rb @@ -7,7 +7,10 @@ require 'god/errors' require 'god/system/process' require 'god/condition' +require 'god/process_condition' +require 'god/conditions/timeline' require 'god/conditions/process_not_running' +require 'god/conditions/memory_usage' require 'god/watch' require 'god/meddle' diff --git a/lib/god/condition.rb b/lib/god/condition.rb index f442d06..0f68ba9 100644 --- a/lib/god/condition.rb +++ b/lib/god/condition.rb @@ -3,9 +3,17 @@ module God class Condition < Base def self.generate(kind) sym = kind.to_s.capitalize.gsub(/_(.)/){$1.upcase}.intern - God.const_get(sym).new + God::Conditions.const_get(sym).new rescue NameError - raise NoSuchConditionError.new("No Condition found with the class name God::#{sym}") + raise NoSuchConditionError.new("No Condition found with the class name God::Conditions::#{sym}") + end + + # Override this method in your Conditions (optional) + # + # Called once after the Condition has been sent to the block and attributes have been + # set. Do any post-processing on attributes here + def prepare + end # Override this method in your Conditions (optional) @@ -32,6 +40,26 @@ module God def after 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 + protected def complain(text) diff --git a/lib/god/conditions/memory_usage.rb b/lib/god/conditions/memory_usage.rb new file mode 100644 index 0000000..a5b4bc1 --- /dev/null +++ b/lib/god/conditions/memory_usage.rb @@ -0,0 +1,44 @@ +module God + module Conditions + + class MemoryUsage < ProcessCondition + attr_accessor :above, :times + + def initialize + super + self.above = nil + self.times = [1, 1] + end + + def prepare + p self.times.class + + if self.times.kind_of?(Integer) + self.times = [self.times, self.times] + end + + @timeline = Timeline.new(self.times[1]) + end + + def valid? + valid = super + valid = complain("You must specify the 'above' attribute for :memory_usage") if self.above.nil? + valid + end + + def test + return false unless super + pid = File.open(self.pid_file).read.strip + process = System::Process.new(pid) + @timeline.push(process.memory) + if @timeline.select { |x| x > self.above }.size < self.times.first + return true + else + @timeline.clear + return false + end + end + end + + end +end \ No newline at end of file diff --git a/lib/god/conditions/process_not_running.rb b/lib/god/conditions/process_not_running.rb dissimilarity index 93% index ac33a98..f9cb1d8 100644 --- a/lib/god/conditions/process_not_running.rb +++ b/lib/god/conditions/process_not_running.rb @@ -1,36 +1,21 @@ -module God - class ProcessNotRunning < Condition - attr_accessor :pid_file, :clean - - def initialize - self.pid_file = nil - self.clean = true - end - - def valid? - valid = true - valid = complain("You must specify the 'pid_file' attribute for :process_not_running") if self.pid_file.nil? - valid - end - - def test - return false unless File.exist?(self.pid_file) - pid = File.open(self.pid_file).read.strip - process_running?(pid) - end - - def after - if self.clean - File.delete(self.pid_file) rescue nil - end - end - - private - - def process_running?(pid) - cmd_name = RUBY_PLATFORM =~ /solaris/i ? "args" : "command" - ps_output = `ps -o #{cmd_name}= -p #{pid}` - !ps_output.strip.empty? - end - end -end +module God + module Conditions + + class ProcessNotRunning < ProcessCondition + def test + return false unless super + pid = File.open(self.pid_file).read.strip + process_running?(pid) + end + + private + + def process_running?(pid) + cmd_name = RUBY_PLATFORM =~ /solaris/i ? "args" : "command" + ps_output = `ps -o #{cmd_name}= -p #{pid}` + !ps_output.strip.empty? + end + end + + end +end \ No newline at end of file diff --git a/lib/god/conditions/timeline.rb b/lib/god/conditions/timeline.rb new file mode 100644 index 0000000..1459dcb --- /dev/null +++ b/lib/god/conditions/timeline.rb @@ -0,0 +1,17 @@ +module God + module Conditions + + class Timeline < Array + def initialize(max_size) + super() + @max_size = max_size + end + + def push(val) + unshift(val) + pop if size > @max_size + end + end + + end +end \ No newline at end of file diff --git a/lib/god/meddle.rb b/lib/god/meddle.rb index b68194a..cd88c75 100644 --- a/lib/god/meddle.rb +++ b/lib/god/meddle.rb @@ -27,9 +27,7 @@ module God @watches.each do |w| threads << Thread.new do while true do - if a = w.run - w.action(a) - end + w.run sleep self.interval end end diff --git a/lib/god/conditions/process_not_running.rb b/lib/god/process_condition.rb similarity index 50% copy from lib/god/conditions/process_not_running.rb copy to lib/god/process_condition.rb index ac33a98..97f5e76 100644 --- a/lib/god/conditions/process_not_running.rb +++ b/lib/god/process_condition.rb @@ -1,36 +1,28 @@ module God - class ProcessNotRunning < Condition + + class ProcessCondition < Condition attr_accessor :pid_file, :clean def initialize self.pid_file = nil self.clean = true end - + def valid? valid = true valid = complain("You must specify the 'pid_file' attribute for :process_not_running") if self.pid_file.nil? valid end - + def test - return false unless File.exist?(self.pid_file) - pid = File.open(self.pid_file).read.strip - process_running?(pid) + File.exist?(self.pid_file) end - - def after + + def before_start if self.clean File.delete(self.pid_file) rescue nil end end - - private - - def process_running?(pid) - cmd_name = RUBY_PLATFORM =~ /solaris/i ? "args" : "command" - ps_output = `ps -o #{cmd_name}= -p #{pid}` - !ps_output.strip.empty? - end end -end + +end \ No newline at end of file diff --git a/lib/god/watch.rb b/lib/god/watch.rb index 8418163..42ba7ae 100644 --- a/lib/god/watch.rb +++ b/lib/god/watch.rb @@ -2,7 +2,7 @@ module God class Watch < Base # config - attr_accessor :name, :cwd, :start, :stop, :grace + attr_accessor :name, :cwd, :start, :stop, :restart, :grace # api attr_accessor :conditions @@ -16,7 +16,8 @@ module God @action = nil # the list of conditions for each action - self.conditions = {:start => []} + self.conditions = {:start => [], + :restart => []} end def start_if @@ -25,6 +26,12 @@ module God @action = nil end + def restart_if + @action = :restart + yield(self) + @action = nil + end + # Instantiate a Condition of type +kind+ and pass it into the mandatory # block. Attributes of the condition must be set in the config file def condition(kind) @@ -42,8 +49,12 @@ module God exit end + # send to block so config can set attributes yield(c) + # call prepare on the condition + c.prepare + # exit if the Condition is invalid, the Condition will have printed # out its own error messages by now unless c.valid? @@ -54,28 +65,52 @@ module God end def run - self.conditions[:start].each do |c| - if c.test - puts self.name + ' ' + c.class.name + ' [ok]' - else - puts self.name + ' ' + c.class.name + ' [fail]' - c.after - return :start + [:start, :restart].each do |cmd| + self.conditions[cmd].each do |c| + if c.test + puts self.name + ' ' + c.class.name + ' [ok]' + else + puts self.name + ' ' + c.class.name + ' [fail]' + c.after + action(cmd, c) + return + end end end - - nil end - def action(a) + def action(a, c) case a when :start puts self.start Dir.chdir(self.cwd) do + c.before_start system(self.start) + c.after_start end sleep(self.grace) - end + when :restart + if self.restart + puts self.restart + Dir.chdir(self.cwd) do + c.before_restart + system(self.restart) + c.after_restart + end + else + self.action(:stop, c) + self.action(:start, c) + end + sleep(self.grace) + when :stop + puts self.stop + Dir.chdir(self.cwd) do + c.before_stop + system(self.stop) + c.after_stop + end + sleep(self.grace) + end end end diff --git a/test/configs/real.rb b/test/configs/real.rb index d689648..ced8948 100644 --- a/test/configs/real.rb +++ b/test/configs/real.rb @@ -1,5 +1,5 @@ if $0 == __FILE__ - require File.join(File.dirname(__FILE__), *%w[.. lib god]) + require File.join(File.dirname(__FILE__), *%w[.. .. lib god]) end RAILS_ROOT = "/Users/tom/dev/gravatar2" @@ -12,11 +12,21 @@ God.meddle do |god| w.cwd = RAILS_ROOT w.start = "mongrel_rails start -P ./log/mongrel.pid -d" w.stop = "mongrel_rails stop -P ./log/mongrel.pid" - w.grace = 10 + w.grace = 5 + + pid_file = File.join(RAILS_ROOT, "log/mongrel.pid") - w.start_if do |r| - r.condition(:process_not_running) do |c| - c.pid_file = File.join(RAILS_ROOT, "log/mongrel.pid") + w.start_if do |start| + start.condition(:process_not_running) do |c| + c.pid_file = pid_file + end + end + + w.restart_if do |restart| + restart.condition(:memory_usage) do |c| + c.pid_file = pid_file + c.above = 10 # kb + c.times = [3, 5] end end end diff --git a/test/test_condition.rb b/test/test_condition.rb index 10e360d..2abd6cf 100644 --- a/test/test_condition.rb +++ b/test/test_condition.rb @@ -2,7 +2,7 @@ require 'helper' class TestCondition < Test::Unit::TestCase def test_generate_should_return_an_object_corresponding_to_the_given_type - assert_equal ProcessNotRunning, Condition.generate(:process_not_running).class + assert_equal Conditions::ProcessNotRunning, Condition.generate(:process_not_running).class end def test_generate_should_raise_on_invalid_type @@ -12,7 +12,7 @@ class TestCondition < Test::Unit::TestCase end def test_generate_should_return_a_good_error_message_for_invalid_types - emsg = "No Condition found with the class name God::FooBar" + emsg = "No Condition found with the class name God::Conditions::FooBar" rmsg = nil begin diff --git a/test/test_timeline.rb b/test/test_timeline.rb new file mode 100644 index 0000000..8935173 --- /dev/null +++ b/test/test_timeline.rb @@ -0,0 +1,24 @@ +require 'helper' + +class TestTimeline < Test::Unit::TestCase + def setup + @timeline = Conditions::Timeline.new(5) + end + + def test_new_should_be_empty + assert_equal 0, @timeline.size + end + + def test_should_not_grow_to_more_than_size + (1..10).each do |i| + @timeline.push(i) + end + + assert_equal [10, 9, 8, 7, 6], @timeline + end + + def test_clear_should_clear_array + @timeline.push(1) + assert_equal [], @timeline.clear + end +end \ No newline at end of file diff --git a/test/test_watch.rb b/test/test_watch.rb index 431fe8d..c09fe28 100644 --- a/test/test_watch.rb +++ b/test/test_watch.rb @@ -9,15 +9,22 @@ class TestWatch < Test::Unit::TestCase assert_equal [], @watch.conditions[:start] end + def test_should_have_empty_restart_conditions + assert_equal [], @watch.conditions[:restart] + end + def test_should_have_standard_attributes assert_nothing_raised do @watch.name = 'foo' @watch.cwd = '/foo' @watch.start = 'start' @watch.stop = 'stop' + @watch.restart = 'restart' end end + # _if methods + def test_start_if_should_modify_action_within_scope assert_equal nil, @watch.instance_variable_get(:@action) @watch.start_if do |w| @@ -26,6 +33,16 @@ class TestWatch < Test::Unit::TestCase assert_equal nil, @watch.instance_variable_get(:@action) end + def test_restart_if_should_modify_action_within_scope + assert_equal nil, @watch.instance_variable_get(:@action) + @watch.restart_if do |w| + assert_equal :restart, @watch.instance_variable_get(:@action) + end + assert_equal nil, @watch.instance_variable_get(:@action) + end + + # condition + def test_condition_should_record_condition_in_correct_list cond = nil @watch.start_if do |w| @@ -35,6 +52,15 @@ class TestWatch < Test::Unit::TestCase assert_equal cond, @watch.conditions[:start].first end + def test_condition_should_record_condition_in_correct_list + cond = nil + @watch.restart_if do |w| + w.condition(:fake_condition) { |c| cond = c } + end + assert_equal 1, @watch.conditions[:restart].size + assert_equal cond, @watch.conditions[:restart].first + end + def test_condition_called_from_outside_if_block_should_raise assert_raise ExitCalledError do @watch.condition(:fake_condition) { |c| cond = c } -- 2.11.4.GIT