From 03085ff774e85b67ae8f190dbb70dd3161dab822 Mon Sep 17 00:00:00 2001 From: Ryan Doyle Date: Mon, 9 Feb 2015 10:54:26 +1100 Subject: [PATCH] Introduce a poller for the elapsed time of a process --- doc/god.asciidoc | 5 +++ examples/single.god | 5 +++ god.gemspec | 1 + lib/god.rb | 1 + lib/god/conditions/process_time.rb | 47 ++++++++++++++++++++++++++++ lib/god/system/portable_poller.rb | 15 +++++++-- lib/god/system/process.rb | 4 +++ test/test_conditions_process_time.rb | 27 ++++++++++++++++ test/test_system_portable_poller.rb | 28 ++++++++++++----- 9 files changed, 123 insertions(+), 10 deletions(-) create mode 100644 lib/god/conditions/process_time.rb create mode 100644 test/test_conditions_process_time.rb diff --git a/doc/god.asciidoc b/doc/god.asciidoc index 6a4b572f..7e78bcdf 100644 --- a/doc/god.asciidoc +++ b/doc/god.asciidoc @@ -1360,6 +1360,11 @@ God.watch do |w| c.above = 10.percent c.times = [3, 5] end + + # restart if the process is alive longer than an hour + on.condition(:process_time) do |c| + c.alive_longer_than = 1.hour + end end # lifecycle diff --git a/examples/single.god b/examples/single.god index 24312529..69deabe8 100644 --- a/examples/single.god +++ b/examples/single.god @@ -49,6 +49,11 @@ God.watch do |w| c.above = 10.percent c.times = [3, 5] end + + # restart if the process is alive longer than an hour + on.condition(:process_time) do |c| + c.alive_longer_than = 1.hour + end end # lifecycle diff --git a/god.gemspec b/god.gemspec index 402d285e..84f93772 100644 --- a/god.gemspec +++ b/god.gemspec @@ -83,6 +83,7 @@ Gem::Specification.new do |s| lib/god/conditions/process_running.rb lib/god/conditions/socket_responding.rb lib/god/conditions/tries.rb + lib/god/conditions/process_time.rb lib/god/configurable.rb lib/god/contact.rb lib/god/contacts/airbrake.rb diff --git a/lib/god.rb b/lib/god.rb index 7e3ddffd..4452beb3 100644 --- a/lib/god.rb +++ b/lib/god.rb @@ -53,6 +53,7 @@ require 'god/conditions/file_mtime' require 'god/conditions/file_touched' require 'god/conditions/socket_responding' +require 'god/conditions/process_time' require 'god/socket' require 'god/driver' diff --git a/lib/god/conditions/process_time.rb b/lib/god/conditions/process_time.rb new file mode 100644 index 00000000..925e53b7 --- /dev/null +++ b/lib/god/conditions/process_time.rb @@ -0,0 +1,47 @@ +module God + module Conditions + + # Condition Symbol :process_time + # Type: Poll + # + # Paramaters + # Required + # +pid_file+ is the pid file of the process in question. Automatically + # populated for Watches. + # +alive_longer_than+ is the amount of wall time the process is allowed + # to live for + # Examples + # + # Trigger if the process is running for longer than 1 hour + # + # on.condition(:process_time) do |c| + # c.alive_longer_than = 1.hour + # end + # + class ProcessTime < PollCondition + attr_accessor :alive_longer_than, :pid_file + + def initialize + super + self.alive_longer_than = nil + 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 'alive_longer_than' must be specified", self) if self.alive_longer_than.nil? + valid + end + + def test + process = System::Process.new(self.pid) + process.elapsed_time > self.alive_longer_than + end + end + + end +end diff --git a/lib/god/system/portable_poller.rb b/lib/god/system/portable_poller.rb index dd792cd5..c55a2c00 100644 --- a/lib/god/system/portable_poller.rb +++ b/lib/god/system/portable_poller.rb @@ -19,6 +19,11 @@ def percent_cpu ps_float('%cpu') end + # Wall time a process has been running for + def elapsed_time + ps_seconds('etime') + end + private def ps_int(keyword) @@ -33,10 +38,16 @@ def ps_string(keyword) `ps -o #{keyword}= -p #{@pid}`.strip end + def ps_seconds(keyword) + time_string_to_seconds(ps_string(keyword)) + end + def time_string_to_seconds(text) - _, minutes, seconds, useconds = *text.match(/(\d+):(\d{2}).(\d{2})/) - (minutes.to_i * 60) + seconds.to_i + units = [1.second, 1.minute, 1.hour, 1.day] + times = text.scan(/[0-9]+/) + times.reverse.map{|s| s.to_i}.zip(units).map{|p| p.reduce(:*)}.reduce(:+) end + end end end diff --git a/lib/god/system/process.rb b/lib/god/system/process.rb index ed3dc17e..174d6654 100644 --- a/lib/god/system/process.rb +++ b/lib/god/system/process.rb @@ -35,6 +35,10 @@ def percent_cpu @poller.percent_cpu end + def elapsed_time + @poller.elapsed_time + end + private def fetch_system_poller diff --git a/test/test_conditions_process_time.rb b/test/test_conditions_process_time.rb new file mode 100644 index 00000000..86f14b78 --- /dev/null +++ b/test/test_conditions_process_time.rb @@ -0,0 +1,27 @@ +require File.dirname(__FILE__) + '/helper' + +class TestConditionsTime < Minitest::Test + + def setup + @condition = Conditions::ProcessTime.new + @condition.stubs(:pid).returns(123) + + @system_process = mock() + System::Process.expects(:new).returns(@system_process) + end + + def test_test_is_true_when_a_process_is_running_longer_than_requested + @condition.alive_longer_than = 20 + @system_process.expects(:elapsed_time).returns(30) + + assert_equal true, @condition.test + end + + def test_test_is_false_when_a_process_is_running_less_than_requested + @condition.alive_longer_than = 20 + @system_process.expects(:elapsed_time).returns(10) + + assert_equal false, @condition.test + end + +end diff --git a/test/test_system_portable_poller.rb b/test/test_system_portable_poller.rb index 1e3d6b0d..517da07f 100644 --- a/test/test_system_portable_poller.rb +++ b/test/test_system_portable_poller.rb @@ -2,16 +2,28 @@ class TestSystemPortablePoller < Minitest::Test def setup - pid = Process.pid - @process = System::PortablePoller.new(pid) + @process = System::PortablePoller.new(123) end - def test_time_string_to_seconds - assert_equal 0, @process.bypass.time_string_to_seconds('0:00:00') - assert_equal 0, @process.bypass.time_string_to_seconds('0:00:55') - assert_equal 27, @process.bypass.time_string_to_seconds('0:27:32') - assert_equal 75, @process.bypass.time_string_to_seconds('1:15:13') - assert_equal 735, @process.bypass.time_string_to_seconds('12:15:13') + def test_elapsed_time_for_a_process_that_has_been_alive_for_less_than_an_hour + @process.expects(:`).returns("00:11\n") + assert_equal 11, @process.elapsed_time end + + def test_elapsed_time_for_a_process_that_has_been_alive_for_more_than_an_hour_but_less_than_a_day + @process.expects(:`).returns("03:00:01\n") + assert_equal 10801, @process.elapsed_time + end + + def test_elapsed_time_for_a_process_that_has_been_alive_for_more_than_a_day + @process.expects(:`).returns("141-23:10:18\n") + assert_equal 12265818, @process.elapsed_time + end + + def test_elapsed_time_with_whitespace_before_time + @process.expects(:`).returns(" 141-23:10:18\n") + assert_equal 12265818, @process.elapsed_time + end + end