god 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|