god 0.2.0 → 0.3.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 +26 -2
- data/Manifest.txt +19 -5
- data/Rakefile +7 -2
- data/bin/god +131 -11
- data/examples/events.god +52 -0
- data/examples/gravatar.god +29 -35
- data/ext/god/extconf.rb +49 -2
- data/ext/god/kqueue_handler.c +2 -2
- data/ext/god/netlink_handler.c +17 -4
- data/lib/god.rb +127 -25
- data/lib/god/behavior.rb +8 -3
- data/lib/god/behaviors/clean_pid_file.rb +2 -8
- data/lib/god/condition.rb +6 -5
- data/lib/god/conditions/cpu_usage.rb +4 -4
- data/lib/god/conditions/lambda.rb +19 -0
- data/lib/god/conditions/memory_usage.rb +4 -4
- data/lib/god/conditions/process_exits.rb +5 -7
- data/lib/god/conditions/process_running.rb +4 -4
- data/lib/god/errors.rb +3 -0
- data/lib/god/event_handler.rb +28 -3
- data/lib/god/event_handlers/dummy_handler.rb +13 -0
- data/lib/god/event_handlers/kqueue_handler.rb +2 -0
- data/lib/god/event_handlers/netlink_handler.rb +2 -0
- data/lib/god/hub.rb +40 -23
- data/lib/god/metric.rb +4 -4
- data/lib/god/process.rb +105 -0
- data/lib/god/registry.rb +28 -0
- data/lib/god/server.rb +3 -4
- data/lib/god/sugar.rb +47 -0
- data/lib/god/system/process.rb +1 -2
- data/lib/god/timer.rb +13 -6
- data/lib/god/watch.rb +61 -29
- data/test/configs/child_events/child_events.god +25 -0
- data/test/configs/child_events/simple_server.rb +3 -0
- data/test/configs/child_polls/child_polls.god +15 -0
- data/test/configs/child_polls/simple_server.rb +3 -0
- data/test/configs/daemon_events/daemon_events.god +30 -0
- data/test/configs/daemon_events/simple_server.rb +6 -0
- data/test/configs/real.rb +47 -49
- data/test/configs/test.rb +52 -62
- data/test/helper.rb +44 -14
- data/test/test_behavior.rb +10 -2
- data/test/test_condition.rb +19 -3
- data/test/test_conditions_process_running.rb +42 -0
- data/test/test_event_handler.rb +73 -0
- data/test/test_god.rb +206 -9
- data/test/test_handlers_kqueue_handler.rb +12 -0
- data/test/test_hub.rb +157 -0
- data/test/test_metric.rb +30 -2
- data/test/test_process.rb +84 -0
- data/test/test_registry.rb +14 -0
- data/test/test_server.rb +3 -2
- data/test/test_sugar.rb +42 -0
- data/test/test_system_process.rb +1 -1
- data/test/test_timer.rb +8 -1
- data/test/test_watch.rb +137 -2
- metadata +28 -17
- data/examples/local.god +0 -60
- data/ext/god/Makefile +0 -149
- data/lib/god/base.rb +0 -13
- data/lib/god/meddle.rb +0 -38
- data/test/test_meddle.rb +0 -46
data/lib/god/errors.rb
CHANGED
data/lib/god/event_handler.rb
CHANGED
@@ -2,9 +2,34 @@ module God
|
|
2
2
|
class EventHandler
|
3
3
|
@@actions = {}
|
4
4
|
@@handler = nil
|
5
|
+
@@loaded = false
|
5
6
|
|
6
|
-
def self.
|
7
|
-
@@
|
7
|
+
def self.loaded?
|
8
|
+
@@loaded
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.event_system
|
12
|
+
@@handler::EVENT_SYSTEM
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.load
|
16
|
+
begin
|
17
|
+
case RUBY_PLATFORM
|
18
|
+
when /darwin/i, /bsd/i
|
19
|
+
require 'god/event_handlers/kqueue_handler'
|
20
|
+
@@handler = KQueueHandler
|
21
|
+
when /linux/i
|
22
|
+
require 'god/event_handlers/netlink_handler'
|
23
|
+
@@handler = NetlinkHandler
|
24
|
+
else
|
25
|
+
raise NotImplementedError, "Platform not supported for EventHandler"
|
26
|
+
end
|
27
|
+
@@loaded = true
|
28
|
+
rescue Exception
|
29
|
+
require 'god/event_handlers/dummy_handler'
|
30
|
+
@@handler = DummyHandler
|
31
|
+
@@loaded = false
|
32
|
+
end
|
8
33
|
end
|
9
34
|
|
10
35
|
def self.register(pid, event, &block)
|
@@ -26,7 +51,7 @@ module God
|
|
26
51
|
end
|
27
52
|
|
28
53
|
def self.call(pid, event)
|
29
|
-
@@actions[pid][event].call
|
54
|
+
@@actions[pid][event].call if watching_pid?(pid) && @@actions[pid][event]
|
30
55
|
end
|
31
56
|
|
32
57
|
def self.watching_pid?(pid)
|
data/lib/god/hub.rb
CHANGED
@@ -1,14 +1,18 @@
|
|
1
1
|
module God
|
2
2
|
|
3
3
|
class Hub
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
class << self
|
5
|
+
# directory to hold conditions and their corresponding metric
|
6
|
+
# key: condition
|
7
|
+
# val: metric
|
8
|
+
attr_accessor :directory
|
9
|
+
end
|
10
|
+
|
11
|
+
self.directory = {}
|
8
12
|
|
9
13
|
def self.attach(condition, metric)
|
10
14
|
# add the condition to the directory
|
11
|
-
|
15
|
+
self.directory[condition] = metric
|
12
16
|
|
13
17
|
# schedule poll condition
|
14
18
|
# register event condition
|
@@ -21,7 +25,7 @@ module God
|
|
21
25
|
|
22
26
|
def self.detach(condition)
|
23
27
|
# remove the condition from the directory
|
24
|
-
|
28
|
+
self.directory.delete(condition)
|
25
29
|
|
26
30
|
# unschedule any pending polls
|
27
31
|
Timer.get.unschedule(condition)
|
@@ -42,37 +46,50 @@ module God
|
|
42
46
|
|
43
47
|
def self.handle_poll(condition)
|
44
48
|
Thread.new do
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
49
|
+
begin
|
50
|
+
metric = self.directory[condition]
|
51
|
+
|
52
|
+
# it's possible that the timer will trigger an event before it can be cleared
|
53
|
+
# by an exiting metric, in which case it should be ignored
|
54
|
+
return if metric.nil?
|
50
55
|
|
51
|
-
|
56
|
+
watch = metric.watch
|
57
|
+
|
58
|
+
watch.mutex.synchronize do
|
59
|
+
result = condition.test
|
52
60
|
|
53
|
-
|
61
|
+
msg = watch.name + ' ' + condition.class.name + " [#{result}] " + metric.destination.inspect
|
62
|
+
Syslog.debug(msg)
|
63
|
+
puts msg
|
54
64
|
|
55
|
-
|
65
|
+
condition.after
|
56
66
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
67
|
+
dest = metric.destination[result]
|
68
|
+
if dest
|
69
|
+
watch.move(dest)
|
70
|
+
else
|
71
|
+
# reschedule
|
72
|
+
Timer.get.schedule(condition)
|
73
|
+
end
|
62
74
|
end
|
75
|
+
rescue => e
|
76
|
+
message = format("Unhandled exception (%s): %s\n%s",
|
77
|
+
e.class, e.message, e.backtrace.join("\n"))
|
78
|
+
Syslog.crit message
|
79
|
+
abort message
|
63
80
|
end
|
64
81
|
end
|
65
82
|
end
|
66
83
|
|
67
84
|
def self.handle_event(condition)
|
68
85
|
Thread.new do
|
69
|
-
metric =
|
86
|
+
metric = self.directory[condition]
|
70
87
|
watch = metric.watch
|
71
88
|
|
72
89
|
watch.mutex.synchronize do
|
73
|
-
|
74
|
-
|
75
|
-
|
90
|
+
msg = watch.name + ' ' + condition.class.name + " [true] " + metric.destination.inspect
|
91
|
+
Syslog.debug(msg)
|
92
|
+
puts msg
|
76
93
|
|
77
94
|
dest = metric.destination[true]
|
78
95
|
watch.move(dest)
|
data/lib/god/metric.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module God
|
2
2
|
|
3
|
-
class Metric
|
3
|
+
class Metric
|
4
4
|
attr_accessor :watch, :destination, :conditions
|
5
5
|
|
6
6
|
def initialize(watch, destination)
|
@@ -14,7 +14,7 @@ module God
|
|
14
14
|
def condition(kind)
|
15
15
|
# create the condition
|
16
16
|
begin
|
17
|
-
c = Condition.generate(kind)
|
17
|
+
c = Condition.generate(kind, self.watch)
|
18
18
|
rescue NoSuchConditionError => e
|
19
19
|
abort e.message
|
20
20
|
end
|
@@ -28,10 +28,10 @@ module God
|
|
28
28
|
# abort if the Condition is invalid, the Condition will have printed
|
29
29
|
# out its own error messages by now
|
30
30
|
unless c.valid?
|
31
|
-
abort
|
31
|
+
abort "Exiting on invalid condition"
|
32
32
|
end
|
33
33
|
|
34
|
-
# inherit interval from
|
34
|
+
# inherit interval from watch if no poll condition specific interval was set
|
35
35
|
if c.kind_of?(PollCondition) && !c.interval
|
36
36
|
if self.watch.interval
|
37
37
|
c.interval = self.watch.interval
|
data/lib/god/process.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module God
|
4
|
+
class Process
|
5
|
+
WRITES_PID = [:start, :restart]
|
6
|
+
|
7
|
+
attr_accessor :name, :uid, :gid, :start, :stop, :restart
|
8
|
+
|
9
|
+
def initialize(options={})
|
10
|
+
options.each do |k,v|
|
11
|
+
send("#{k}=", v)
|
12
|
+
end
|
13
|
+
|
14
|
+
@tracking_pid = false
|
15
|
+
end
|
16
|
+
|
17
|
+
# DON'T USE THIS INTERNALLY. Use the instance variable. -- Kev
|
18
|
+
# No really, trust me. Use the instance variable.
|
19
|
+
def pid_file=(value)
|
20
|
+
@tracking_pid = false
|
21
|
+
@pid_file = value
|
22
|
+
end
|
23
|
+
|
24
|
+
def pid_file
|
25
|
+
if @pid_file.nil?
|
26
|
+
@tracking_pid = true
|
27
|
+
@pid_file = default_pid_file
|
28
|
+
end
|
29
|
+
@pid_file
|
30
|
+
end
|
31
|
+
|
32
|
+
def start!
|
33
|
+
call_action(:start)
|
34
|
+
end
|
35
|
+
|
36
|
+
def stop!
|
37
|
+
call_action(:stop)
|
38
|
+
end
|
39
|
+
|
40
|
+
def restart!
|
41
|
+
call_action(:restart)
|
42
|
+
end
|
43
|
+
|
44
|
+
def call_action(action)
|
45
|
+
command = send(action)
|
46
|
+
if command.kind_of?(String)
|
47
|
+
# Make pid directory
|
48
|
+
unless test(?d, God.pid_file_directory)
|
49
|
+
begin
|
50
|
+
FileUtils.mkdir_p(God.pid_file_directory)
|
51
|
+
rescue Errno::EACCES => e
|
52
|
+
abort"Failed to create pid file directory: #{e.message}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
unless test(?w, God.pid_file_directory)
|
57
|
+
abort "The pid file directory (#{God.pid_file_directory}) is not writable by #{Etc.getlogin}"
|
58
|
+
end
|
59
|
+
|
60
|
+
# string command
|
61
|
+
# fork/exec to setuid/gid
|
62
|
+
r, w = IO.pipe
|
63
|
+
opid = fork do
|
64
|
+
STDOUT.reopen(w)
|
65
|
+
r.close
|
66
|
+
pid = fork do
|
67
|
+
::Process.setsid
|
68
|
+
::Process::Sys.setgid(Etc.getgrnam(self.gid).gid) if self.gid
|
69
|
+
::Process::Sys.setuid(Etc.getpwnam(self.uid).uid) if self.uid
|
70
|
+
Dir.chdir "/"
|
71
|
+
$0 = command
|
72
|
+
STDIN.reopen "/dev/null"
|
73
|
+
STDOUT.reopen "/dev/null", "a"
|
74
|
+
STDERR.reopen STDOUT
|
75
|
+
exec command
|
76
|
+
end
|
77
|
+
puts pid.to_s
|
78
|
+
end
|
79
|
+
|
80
|
+
::Process.waitpid(opid, 0)
|
81
|
+
w.close
|
82
|
+
pid = r.gets.chomp
|
83
|
+
|
84
|
+
if @tracking_pid or (@pid_file.nil? and WRITES_PID.include?(action))
|
85
|
+
File.open(default_pid_file, 'w') do |f|
|
86
|
+
f.write pid
|
87
|
+
end
|
88
|
+
|
89
|
+
@tracking_pid = true
|
90
|
+
@pid_file = default_pid_file
|
91
|
+
end
|
92
|
+
|
93
|
+
elsif command.kind_of?(Proc)
|
94
|
+
# lambda command
|
95
|
+
command.call
|
96
|
+
else
|
97
|
+
raise NotImplementedError
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def default_pid_file
|
102
|
+
File.join(God.pid_file_directory, "#{self.name}.pid")
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
data/lib/god/registry.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
module God
|
2
|
+
def self.registry
|
3
|
+
@registry ||= Registry.new
|
4
|
+
end
|
5
|
+
|
6
|
+
class Registry
|
7
|
+
def initialize
|
8
|
+
@storage = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def add(item)
|
12
|
+
# raise TypeError unless item.is_a? God::Process
|
13
|
+
@storage[item.name] = item
|
14
|
+
end
|
15
|
+
|
16
|
+
def size
|
17
|
+
@storage.size
|
18
|
+
end
|
19
|
+
|
20
|
+
def [](name)
|
21
|
+
@storage[name]
|
22
|
+
end
|
23
|
+
|
24
|
+
def reset
|
25
|
+
@storage.clear
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/god/server.rb
CHANGED
@@ -7,15 +7,14 @@ module God
|
|
7
7
|
class Server
|
8
8
|
attr_reader :host, :port
|
9
9
|
|
10
|
-
def initialize(
|
11
|
-
@meddle = meddle
|
10
|
+
def initialize(host = nil, port = nil)
|
12
11
|
@host = host
|
13
|
-
@port = port ||
|
12
|
+
@port = port || 17165
|
14
13
|
start
|
15
14
|
end
|
16
15
|
|
17
16
|
def method_missing(*args, &block)
|
18
|
-
|
17
|
+
God.send(*args, &block)
|
19
18
|
end
|
20
19
|
|
21
20
|
private
|
data/lib/god/sugar.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
class Numeric
|
2
|
+
def seconds
|
3
|
+
self
|
4
|
+
end
|
5
|
+
|
6
|
+
alias :second :seconds
|
7
|
+
|
8
|
+
def minutes
|
9
|
+
self * 60
|
10
|
+
end
|
11
|
+
|
12
|
+
alias :minute :minutes
|
13
|
+
|
14
|
+
def hours
|
15
|
+
self * 3600
|
16
|
+
end
|
17
|
+
|
18
|
+
alias :hour :hours
|
19
|
+
|
20
|
+
def days
|
21
|
+
self * 86400
|
22
|
+
end
|
23
|
+
|
24
|
+
alias :day :days
|
25
|
+
|
26
|
+
def kilobytes
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
alias :kilobyte :kilobytes
|
31
|
+
|
32
|
+
def megabytes
|
33
|
+
self * 1024
|
34
|
+
end
|
35
|
+
|
36
|
+
alias :megabyte :megabytes
|
37
|
+
|
38
|
+
def gigabytes
|
39
|
+
self * (1024 ** 2)
|
40
|
+
end
|
41
|
+
|
42
|
+
alias :gigabyte :gigabytes
|
43
|
+
|
44
|
+
def percent
|
45
|
+
self
|
46
|
+
end
|
47
|
+
end
|
data/lib/god/system/process.rb
CHANGED
@@ -8,8 +8,7 @@ module God
|
|
8
8
|
|
9
9
|
# Return true if this process is running, false otherwise
|
10
10
|
def exists?
|
11
|
-
|
12
|
-
!ps_string(cmd_name).empty?
|
11
|
+
system("kill -0 #{@pid} &> /dev/null")
|
13
12
|
end
|
14
13
|
|
15
14
|
# Memory usage in kilobytes (resident set size)
|
data/lib/god/timer.rb
CHANGED
@@ -9,10 +9,10 @@ module God
|
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
12
|
-
class Timer
|
12
|
+
class Timer
|
13
13
|
INTERVAL = 0.25
|
14
14
|
|
15
|
-
attr_reader :events
|
15
|
+
attr_reader :events, :timer
|
16
16
|
|
17
17
|
@@timer = nil
|
18
18
|
|
@@ -27,6 +27,7 @@ module God
|
|
27
27
|
# Start the scheduler loop to handle events
|
28
28
|
def initialize
|
29
29
|
@events = []
|
30
|
+
@mutex = Mutex.new
|
30
31
|
|
31
32
|
@timer = Thread.new do
|
32
33
|
loop do
|
@@ -37,7 +38,9 @@ module God
|
|
37
38
|
@events.each do |event|
|
38
39
|
if t >= event.at
|
39
40
|
self.trigger(event)
|
40
|
-
@
|
41
|
+
@mutex.synchronize do
|
42
|
+
@events.delete(event)
|
43
|
+
end
|
41
44
|
else
|
42
45
|
break
|
43
46
|
end
|
@@ -51,13 +54,17 @@ module God
|
|
51
54
|
|
52
55
|
# Create and register a new TimerEvent with the given parameters
|
53
56
|
def schedule(condition, interval = condition.interval)
|
54
|
-
@
|
55
|
-
|
57
|
+
@mutex.synchronize do
|
58
|
+
@events << TimerEvent.new(condition, interval)
|
59
|
+
@events.sort! { |x, y| x.at <=> y.at }
|
60
|
+
end
|
56
61
|
end
|
57
62
|
|
58
63
|
# Remove any TimerEvents for the given condition
|
59
64
|
def unschedule(condition)
|
60
|
-
@
|
65
|
+
@mutex.synchronize do
|
66
|
+
@events.reject! { |x| x.condition == condition }
|
67
|
+
end
|
61
68
|
end
|
62
69
|
|
63
70
|
def trigger(event)
|