god 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.txt +9 -1
- data/Manifest.txt +13 -1
- data/README.txt +2 -0
- data/Rakefile +2 -2
- data/examples/gravatar.god +8 -2
- data/examples/local.god +4 -4
- data/ext/god/Makefile +149 -0
- data/ext/god/extconf.rb +8 -0
- data/ext/god/kqueue_handler.c +122 -0
- data/ext/god/netlink_handler.c +140 -0
- data/lib/god.rb +37 -4
- data/lib/god/conditions/always.rb +9 -1
- data/lib/god/conditions/cpu_usage.rb +2 -2
- data/lib/god/conditions/memory_usage.rb +2 -2
- data/lib/god/conditions/process_exits.rb +28 -0
- data/lib/god/conditions/process_running.rb +25 -0
- data/lib/god/event_handler.rb +45 -0
- data/lib/god/event_handlers/kqueue_handler.rb +15 -0
- data/lib/god/event_handlers/netlink_handler.rb +11 -0
- data/lib/god/hub.rb +84 -0
- data/lib/god/meddle.rb +1 -17
- data/lib/god/metric.rb +60 -0
- data/lib/god/timer.rb +21 -26
- data/lib/god/watch.rb +90 -58
- data/test/configs/real.rb +2 -5
- data/test/configs/test.rb +75 -0
- data/test/helper.rb +1 -0
- data/test/test_condition.rb +1 -1
- data/test/test_god.rb +3 -2
- data/test/test_metric.rb +60 -0
- data/test/test_timer.rb +21 -11
- data/test/test_watch.rb +49 -78
- metadata +20 -6
- data/lib/god/conditions/process_not_running.rb +0 -22
data/lib/god.rb
CHANGED
@@ -11,7 +11,8 @@ require 'god/behaviors/clean_pid_file'
|
|
11
11
|
|
12
12
|
require 'god/condition'
|
13
13
|
require 'god/conditions/timeline'
|
14
|
-
require 'god/conditions/
|
14
|
+
require 'god/conditions/process_running'
|
15
|
+
require 'god/conditions/process_exits'
|
15
16
|
require 'god/conditions/memory_usage'
|
16
17
|
require 'god/conditions/cpu_usage'
|
17
18
|
require 'god/conditions/always'
|
@@ -19,17 +20,49 @@ require 'god/conditions/always'
|
|
19
20
|
require 'god/reporter'
|
20
21
|
require 'god/server'
|
21
22
|
require 'god/timer'
|
23
|
+
require 'god/hub'
|
24
|
+
|
25
|
+
require 'god/metric'
|
22
26
|
|
23
27
|
require 'god/watch'
|
24
28
|
require 'god/meddle'
|
25
29
|
|
30
|
+
require 'god/event_handler'
|
31
|
+
|
32
|
+
Thread.abort_on_exception = true
|
33
|
+
|
26
34
|
module God
|
27
|
-
VERSION = '0.
|
35
|
+
VERSION = '0.2.0'
|
28
36
|
|
29
|
-
|
37
|
+
case RUBY_PLATFORM
|
38
|
+
when /darwin/i, /bsd/i
|
39
|
+
$:.unshift File.join(File.dirname(__FILE__), *%w[.. ext god])
|
40
|
+
require 'god/event_handlers/kqueue_handler'
|
41
|
+
EventHandler.handler = KQueueHandler
|
42
|
+
when /linux/i
|
43
|
+
$:.unshift File.join(File.dirname(__FILE__), *%w[.. ext god])
|
44
|
+
require 'god/event_handlers/netlink_handler'
|
45
|
+
EventHandler.handler = NetlinkHandler
|
46
|
+
else
|
47
|
+
raise NotImplementedError, "Platform not supported for EventHandler"
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.meddle(options = {})
|
30
51
|
m = Meddle.new(options)
|
52
|
+
|
53
|
+
# yeild to the config file
|
31
54
|
yield m
|
55
|
+
|
56
|
+
# start event handler system
|
57
|
+
EventHandler.start
|
58
|
+
|
59
|
+
# start the timer system
|
60
|
+
Timer.get
|
61
|
+
|
62
|
+
# start monitoring each watch
|
32
63
|
m.monitor
|
33
|
-
|
64
|
+
|
65
|
+
# join the timer thread to we don't exit
|
66
|
+
Timer.get.join
|
34
67
|
end
|
35
68
|
end
|
@@ -2,8 +2,16 @@ module God
|
|
2
2
|
module Conditions
|
3
3
|
|
4
4
|
class Always < PollCondition
|
5
|
+
attr_accessor :what
|
6
|
+
|
7
|
+
def valid?
|
8
|
+
valid = true
|
9
|
+
valid &= complain("You must specify the 'what' attribute for :always") if self.what.nil?
|
10
|
+
valid
|
11
|
+
end
|
12
|
+
|
5
13
|
def test
|
6
|
-
|
14
|
+
@what
|
7
15
|
end
|
8
16
|
end
|
9
17
|
|
@@ -31,10 +31,10 @@ module God
|
|
31
31
|
pid = File.open(self.pid_file).read.strip
|
32
32
|
process = System::Process.new(pid)
|
33
33
|
@timeline.push(process.percent_cpu)
|
34
|
-
if @timeline.select { |x| x > self.above }.size
|
34
|
+
if @timeline.select { |x| x > self.above }.size >= self.times.first
|
35
|
+
@timeline.clear
|
35
36
|
return true
|
36
37
|
else
|
37
|
-
@timeline.clear
|
38
38
|
return false
|
39
39
|
end
|
40
40
|
end
|
@@ -31,10 +31,10 @@ module God
|
|
31
31
|
pid = File.open(self.pid_file).read.strip
|
32
32
|
process = System::Process.new(pid)
|
33
33
|
@timeline.push(process.memory)
|
34
|
-
if @timeline.select { |x| x > self.above }.size
|
34
|
+
if @timeline.select { |x| x > self.above }.size >= self.times.first
|
35
|
+
@timeline.clear
|
35
36
|
return true
|
36
37
|
else
|
37
|
-
@timeline.clear
|
38
38
|
return false
|
39
39
|
end
|
40
40
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module God
|
2
|
+
module Conditions
|
3
|
+
|
4
|
+
class ProcessExits < EventCondition
|
5
|
+
attr_accessor :pid_file
|
6
|
+
|
7
|
+
def valid?
|
8
|
+
valid = true
|
9
|
+
valid &= complain("You must specify the 'pid_file' attribute for :process_exits") if self.pid_file.nil?
|
10
|
+
valid
|
11
|
+
end
|
12
|
+
|
13
|
+
def register
|
14
|
+
pid = File.open(self.pid_file).read.strip.to_i
|
15
|
+
|
16
|
+
EventHandler.register(pid, :proc_exit) {
|
17
|
+
Hub.trigger(self)
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
def deregister
|
22
|
+
pid = File.open(self.pid_file).read.strip.to_i
|
23
|
+
EventHandler.deregister(pid, :proc_exit)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module God
|
2
|
+
module Conditions
|
3
|
+
|
4
|
+
class ProcessRunning < PollCondition
|
5
|
+
attr_accessor :pid_file, :running
|
6
|
+
|
7
|
+
def valid?
|
8
|
+
valid = true
|
9
|
+
valid &= complain("You must specify the 'pid_file' attribute for :process_running") if self.pid_file.nil?
|
10
|
+
valid &= complain("You must specify the 'running' attribute for :process_running") if self.running.nil?
|
11
|
+
valid
|
12
|
+
end
|
13
|
+
|
14
|
+
def test
|
15
|
+
return !self.running unless File.exist?(self.pid_file)
|
16
|
+
|
17
|
+
pid = File.open(self.pid_file).read.strip
|
18
|
+
active = System::Process.new(pid).exists?
|
19
|
+
|
20
|
+
(self.running && active) || (!self.running && !active)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module God
|
2
|
+
class EventHandler
|
3
|
+
@@actions = {}
|
4
|
+
@@handler = nil
|
5
|
+
|
6
|
+
def self.handler=(value)
|
7
|
+
@@handler = value
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.register(pid, event, &block)
|
11
|
+
@@actions[pid] ||= {}
|
12
|
+
@@actions[pid][event] = block
|
13
|
+
@@handler.register_process(pid, @@actions[pid].keys)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.deregister(pid, event=nil)
|
17
|
+
if watching_pid? pid
|
18
|
+
if event.nil?
|
19
|
+
@@actions.delete(pid)
|
20
|
+
@@handler.register_process(pid, []) if system("kill -0 #{pid} &> /dev/null")
|
21
|
+
else
|
22
|
+
@@actions[pid].delete(event)
|
23
|
+
@@handler.register_process(pid, @@actions[pid].keys) if system("kill -0 #{pid} &> /dev/null")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.call(pid, event)
|
29
|
+
@@actions[pid][event].call
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.watching_pid?(pid)
|
33
|
+
@@actions[pid]
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.start
|
37
|
+
Thread.new do
|
38
|
+
loop do
|
39
|
+
@@handler.handle_events
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'kqueue_handler_ext'
|
2
|
+
|
3
|
+
module God
|
4
|
+
class KQueueHandler
|
5
|
+
def self.register_process(pid, events)
|
6
|
+
monitor_process(pid, events_mask(events))
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.events_mask(events)
|
10
|
+
events.inject(0) do |mask, event|
|
11
|
+
mask |= event_mask(event)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'netlink_handler_ext'
|
2
|
+
|
3
|
+
module God
|
4
|
+
class NetlinkHandler
|
5
|
+
def self.register_process(pid, events)
|
6
|
+
# netlink doesn't need to do this
|
7
|
+
# it just reads from the eventhandler actions to see if the pid
|
8
|
+
# matches the list we're looking for -- Kev
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
data/lib/god/hub.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
module God
|
2
|
+
|
3
|
+
class Hub
|
4
|
+
# directory to hold conditions and their corresponding metric
|
5
|
+
# key: condition
|
6
|
+
# val: metric
|
7
|
+
@@directory = {}
|
8
|
+
|
9
|
+
def self.attach(condition, metric)
|
10
|
+
# add the condition to the directory
|
11
|
+
@@directory[condition] = metric
|
12
|
+
|
13
|
+
# schedule poll condition
|
14
|
+
# register event condition
|
15
|
+
if condition.kind_of?(PollCondition)
|
16
|
+
Timer.get.schedule(condition, 0)
|
17
|
+
else
|
18
|
+
condition.register
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.detach(condition)
|
23
|
+
# remove the condition from the directory
|
24
|
+
@@directory.delete(condition)
|
25
|
+
|
26
|
+
# unschedule any pending polls
|
27
|
+
Timer.get.unschedule(condition)
|
28
|
+
|
29
|
+
# deregister event condition
|
30
|
+
if condition.kind_of?(EventCondition)
|
31
|
+
condition.deregister
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.trigger(condition)
|
36
|
+
if condition.kind_of?(PollCondition)
|
37
|
+
self.handle_poll(condition)
|
38
|
+
elsif condition.kind_of?(EventCondition)
|
39
|
+
self.handle_event(condition)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.handle_poll(condition)
|
44
|
+
Thread.new do
|
45
|
+
metric = @@directory[condition]
|
46
|
+
watch = metric.watch
|
47
|
+
|
48
|
+
watch.mutex.synchronize do
|
49
|
+
result = condition.test
|
50
|
+
|
51
|
+
puts watch.name + ' ' + condition.class.name + " [#{result}]"
|
52
|
+
|
53
|
+
condition.after
|
54
|
+
|
55
|
+
p metric.destination
|
56
|
+
|
57
|
+
if dest = metric.destination[result]
|
58
|
+
watch.move(dest)
|
59
|
+
else
|
60
|
+
# reschedule
|
61
|
+
Timer.get.schedule(condition)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.handle_event(condition)
|
68
|
+
Thread.new do
|
69
|
+
metric = @@directory[condition]
|
70
|
+
watch = metric.watch
|
71
|
+
|
72
|
+
watch.mutex.synchronize do
|
73
|
+
puts watch.name + ' ' + condition.class.name + " [true]"
|
74
|
+
|
75
|
+
p metric.destination
|
76
|
+
|
77
|
+
dest = metric.destination[true]
|
78
|
+
watch.move(dest)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
data/lib/god/meddle.rb
CHANGED
@@ -5,13 +5,12 @@ module God
|
|
5
5
|
attr_accessor :server
|
6
6
|
|
7
7
|
# api
|
8
|
-
attr_accessor :watches
|
8
|
+
attr_accessor :watches
|
9
9
|
|
10
10
|
# Create a new instance that is ready for use by a configuration file
|
11
11
|
def initialize(options = {})
|
12
12
|
self.watches = []
|
13
13
|
self.server = Server.new(self, options[:host], options[:port])
|
14
|
-
self.timer = Timer.new
|
15
14
|
end
|
16
15
|
|
17
16
|
# Instantiate a new, empty Watch object and pass it to the mandatory
|
@@ -34,21 +33,6 @@ module God
|
|
34
33
|
def monitor
|
35
34
|
@watches.each { |w| w.monitor }
|
36
35
|
end
|
37
|
-
|
38
|
-
# def monitor
|
39
|
-
# threads = []
|
40
|
-
#
|
41
|
-
# @watches.each do |w|
|
42
|
-
# threads << Thread.new do
|
43
|
-
# while true do
|
44
|
-
# w.run
|
45
|
-
# sleep self.interval
|
46
|
-
# end
|
47
|
-
# end
|
48
|
-
# end
|
49
|
-
#
|
50
|
-
# threads.each { |t| t.join }
|
51
|
-
# end
|
52
36
|
end
|
53
37
|
|
54
38
|
end
|
data/lib/god/metric.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
module God
|
2
|
+
|
3
|
+
class Metric < Base
|
4
|
+
attr_accessor :watch, :destination, :conditions
|
5
|
+
|
6
|
+
def initialize(watch, destination)
|
7
|
+
self.watch = watch
|
8
|
+
self.destination = destination
|
9
|
+
self.conditions = []
|
10
|
+
end
|
11
|
+
|
12
|
+
# Instantiate a Condition of type +kind+ and pass it into the optional
|
13
|
+
# block. Attributes of the condition must be set in the config file
|
14
|
+
def condition(kind)
|
15
|
+
# create the condition
|
16
|
+
begin
|
17
|
+
c = Condition.generate(kind)
|
18
|
+
rescue NoSuchConditionError => e
|
19
|
+
abort e.message
|
20
|
+
end
|
21
|
+
|
22
|
+
# send to block so config can set attributes
|
23
|
+
yield(c) if block_given?
|
24
|
+
|
25
|
+
# call prepare on the condition
|
26
|
+
c.prepare
|
27
|
+
|
28
|
+
# abort if the Condition is invalid, the Condition will have printed
|
29
|
+
# out its own error messages by now
|
30
|
+
unless c.valid?
|
31
|
+
abort
|
32
|
+
end
|
33
|
+
|
34
|
+
# inherit interval from meddle if no poll condition specific interval was set
|
35
|
+
if c.kind_of?(PollCondition) && !c.interval
|
36
|
+
if self.watch.interval
|
37
|
+
c.interval = self.watch.interval
|
38
|
+
else
|
39
|
+
abort "No interval set for Condition '#{c.class.name}' in Watch '#{self.watch.name}', and no default Watch interval from which to inherit"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# remember
|
44
|
+
self.conditions << c
|
45
|
+
end
|
46
|
+
|
47
|
+
def enable
|
48
|
+
self.conditions.each do |c|
|
49
|
+
Hub.attach(c, self)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def disable
|
54
|
+
self.conditions.each do |c|
|
55
|
+
Hub.detach(c)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
data/lib/god/timer.rb
CHANGED
@@ -1,13 +1,11 @@
|
|
1
1
|
module God
|
2
2
|
|
3
3
|
class TimerEvent
|
4
|
-
attr_accessor :
|
4
|
+
attr_accessor :condition, :at
|
5
5
|
|
6
|
-
def initialize(
|
7
|
-
self.watch = watch
|
6
|
+
def initialize(condition, interval)
|
8
7
|
self.condition = condition
|
9
|
-
self.
|
10
|
-
self.at = Time.now.to_i + condition.interval
|
8
|
+
self.at = Time.now.to_i + interval
|
11
9
|
end
|
12
10
|
end
|
13
11
|
|
@@ -16,6 +14,16 @@ module God
|
|
16
14
|
|
17
15
|
attr_reader :events
|
18
16
|
|
17
|
+
@@timer = nil
|
18
|
+
|
19
|
+
def self.get
|
20
|
+
@@timer ||= Timer.new
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.reset
|
24
|
+
@@timer = nil
|
25
|
+
end
|
26
|
+
|
19
27
|
# Start the scheduler loop to handle events
|
20
28
|
def initialize
|
21
29
|
@events = []
|
@@ -42,31 +50,18 @@ module God
|
|
42
50
|
end
|
43
51
|
|
44
52
|
# Create and register a new TimerEvent with the given parameters
|
45
|
-
def
|
46
|
-
@events << TimerEvent.new(
|
53
|
+
def schedule(condition, interval = condition.interval)
|
54
|
+
@events << TimerEvent.new(condition, interval)
|
47
55
|
@events.sort! { |x, y| x.at <=> y.at }
|
48
56
|
end
|
49
57
|
|
58
|
+
# Remove any TimerEvents for the given condition
|
59
|
+
def unschedule(condition)
|
60
|
+
@events.reject! { |x| x.condition == condition }
|
61
|
+
end
|
62
|
+
|
50
63
|
def trigger(event)
|
51
|
-
|
52
|
-
|
53
|
-
Thread.new do
|
54
|
-
w = event.watch
|
55
|
-
c = event.condition
|
56
|
-
|
57
|
-
w.mutex.synchronize do
|
58
|
-
if c.test
|
59
|
-
puts w.name + ' ' + c.class.name + ' [ok]'
|
60
|
-
else
|
61
|
-
puts w.name + ' ' + c.class.name + ' [fail]'
|
62
|
-
c.after
|
63
|
-
w.action(event.command, c)
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
# reschedule
|
68
|
-
timer.register(w, c, event.command)
|
69
|
-
end
|
64
|
+
Hub.trigger(event.condition)
|
70
65
|
end
|
71
66
|
|
72
67
|
# Join the timer thread
|