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/ext/god/kqueue_handler.c
CHANGED
@@ -5,9 +5,9 @@
|
|
5
5
|
#include <sys/time.h>
|
6
6
|
#include <errno.h>
|
7
7
|
|
8
|
+
static VALUE mGod;
|
8
9
|
static VALUE cKQueueHandler;
|
9
10
|
static VALUE cEventHandler;
|
10
|
-
static VALUE mGod;
|
11
11
|
|
12
12
|
static ID proc_exit;
|
13
13
|
static ID proc_fork;
|
@@ -119,4 +119,4 @@ Init_kqueue_handler_ext()
|
|
119
119
|
rb_define_singleton_method(cKQueueHandler, "event_mask", kqh_event_mask, 1);
|
120
120
|
}
|
121
121
|
|
122
|
-
#endif
|
122
|
+
#endif
|
data/ext/god/netlink_handler.c
CHANGED
@@ -2,15 +2,16 @@
|
|
2
2
|
|
3
3
|
#include <ruby.h>
|
4
4
|
#include <sys/types.h>
|
5
|
+
#include <unistd.h>
|
5
6
|
#include <sys/socket.h>
|
6
7
|
#include <linux/netlink.h>
|
7
8
|
#include <linux/connector.h>
|
8
9
|
#include <linux/cn_proc.h>
|
9
10
|
#include <errno.h>
|
10
11
|
|
12
|
+
static VALUE mGod;
|
11
13
|
static VALUE cNetlinkHandler;
|
12
14
|
static VALUE cEventHandler;
|
13
|
-
static VALUE mGod;
|
14
15
|
|
15
16
|
static ID proc_exit;
|
16
17
|
static ID proc_fork;
|
@@ -71,6 +72,12 @@ nlh_handle_events()
|
|
71
72
|
|
72
73
|
rb_funcall(cEventHandler, m_call, 2, INT2FIX(event->event_data.fork.parent_pid), ID2SYM(proc_fork));
|
73
74
|
return INT2FIX(1);
|
75
|
+
|
76
|
+
case PROC_EVENT_NONE:
|
77
|
+
case PROC_EVENT_EXEC:
|
78
|
+
case PROC_EVENT_UID:
|
79
|
+
case PROC_EVENT_GID:
|
80
|
+
break;
|
74
81
|
}
|
75
82
|
}
|
76
83
|
|
@@ -87,17 +94,23 @@ connect_to_netlink()
|
|
87
94
|
struct sockaddr_nl sa_nl; /* netlink interface info */
|
88
95
|
char buff[NL_MESSAGE_SIZE];
|
89
96
|
struct nlmsghdr *hdr; /* for telling netlink what we want */
|
90
|
-
struct cn_msg *msg; /* the actual connector message
|
97
|
+
struct cn_msg *msg; /* the actual connector message */
|
91
98
|
|
92
99
|
/* connect to netlink socket */
|
93
100
|
nl_sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR);
|
94
101
|
|
102
|
+
if (-1 == nl_sock) {
|
103
|
+
rb_raise(rb_eStandardError, strerror(errno));
|
104
|
+
}
|
105
|
+
|
95
106
|
bzero(&sa_nl, sizeof(sa_nl));
|
96
107
|
sa_nl.nl_family = AF_NETLINK;
|
97
108
|
sa_nl.nl_groups = CN_IDX_PROC;
|
98
109
|
sa_nl.nl_pid = getpid();
|
99
110
|
|
100
|
-
bind(nl_sock, (struct sockaddr *)&sa_nl, sizeof(sa_nl))
|
111
|
+
if (-1 == bind(nl_sock, (struct sockaddr *)&sa_nl, sizeof(sa_nl))) {
|
112
|
+
rb_raise(rb_eStandardError, strerror(errno));
|
113
|
+
}
|
101
114
|
|
102
115
|
/* Fill header */
|
103
116
|
hdr = (struct nlmsghdr *)buff;
|
@@ -137,4 +150,4 @@ Init_netlink_handler_ext()
|
|
137
150
|
connect_to_netlink();
|
138
151
|
}
|
139
152
|
|
140
|
-
#endif
|
153
|
+
#endif
|
data/lib/god.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
$:.unshift File.dirname(__FILE__) # For use/testing when no gem is installed
|
2
2
|
|
3
|
+
require 'syslog'
|
4
|
+
|
3
5
|
# internal requires
|
4
|
-
require 'god/base'
|
5
6
|
require 'god/errors'
|
6
7
|
|
7
8
|
require 'god/system/process'
|
@@ -23,46 +24,147 @@ require 'god/timer'
|
|
23
24
|
require 'god/hub'
|
24
25
|
|
25
26
|
require 'god/metric'
|
26
|
-
|
27
27
|
require 'god/watch'
|
28
|
-
require 'god/meddle'
|
29
28
|
|
30
29
|
require 'god/event_handler'
|
30
|
+
require 'god/registry'
|
31
|
+
require 'god/process'
|
32
|
+
|
33
|
+
require 'god/sugar'
|
31
34
|
|
32
|
-
|
35
|
+
$:.unshift File.join(File.dirname(__FILE__), *%w[.. ext god])
|
36
|
+
|
37
|
+
begin
|
38
|
+
Syslog.open('god')
|
39
|
+
rescue RuntimeError
|
40
|
+
Syslog.reopen('god')
|
41
|
+
end
|
42
|
+
|
43
|
+
God::EventHandler.load
|
33
44
|
|
34
45
|
module God
|
35
|
-
VERSION = '0.
|
46
|
+
VERSION = '0.3.0'
|
47
|
+
|
48
|
+
class << self
|
49
|
+
attr_accessor :inited, :host, :port
|
50
|
+
|
51
|
+
# drb
|
52
|
+
attr_accessor :server
|
53
|
+
|
54
|
+
# api
|
55
|
+
attr_accessor :watches, :groups
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.init
|
59
|
+
# only do this once
|
60
|
+
return if self.inited
|
61
|
+
|
62
|
+
# variable init
|
63
|
+
self.watches = {}
|
64
|
+
self.groups = {}
|
65
|
+
|
66
|
+
# yield to the config file
|
67
|
+
yield self if block_given?
|
68
|
+
|
69
|
+
# instantiate server
|
70
|
+
self.server = Server.new(self.host, self.port)
|
71
|
+
|
72
|
+
# init has been executed
|
73
|
+
self.inited = true
|
74
|
+
end
|
75
|
+
|
76
|
+
# Where pid files created by god will go by default
|
77
|
+
def self.pid_file_directory
|
78
|
+
@pid_file_directory ||= '/var/run/god'
|
79
|
+
end
|
36
80
|
|
37
|
-
|
38
|
-
|
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"
|
81
|
+
def self.pid_file_directory=(value)
|
82
|
+
@pid_file_directory = value
|
48
83
|
end
|
49
84
|
|
50
|
-
|
51
|
-
|
85
|
+
# Instantiate a new, empty Watch object and pass it to the mandatory
|
86
|
+
# block. The attributes of the watch will be set by the configuration
|
87
|
+
# file.
|
88
|
+
def self.watch
|
89
|
+
self.init
|
52
90
|
|
53
|
-
|
54
|
-
yield
|
91
|
+
w = Watch.new
|
92
|
+
yield(w)
|
93
|
+
|
94
|
+
# ensure the new watch has a unique name
|
95
|
+
if self.watches[w.name] || self.groups[w.name]
|
96
|
+
abort "Watch name '#{w.name}' already used for a Watch or Group"
|
97
|
+
end
|
98
|
+
|
99
|
+
# add to list of watches
|
100
|
+
self.watches[w.name] = w
|
101
|
+
|
102
|
+
# add to group if specified
|
103
|
+
if w.group
|
104
|
+
# ensure group name hasn't been used for a watch already
|
105
|
+
if self.watches[w.group]
|
106
|
+
abort "Group name '#{w.group}' already used for a Watch"
|
107
|
+
end
|
108
|
+
|
109
|
+
self.groups[w.group] ||= []
|
110
|
+
self.groups[w.group] << w.name
|
111
|
+
end
|
112
|
+
|
113
|
+
# register watch
|
114
|
+
w.register!
|
115
|
+
end
|
116
|
+
|
117
|
+
def self.control(name, command)
|
118
|
+
# get the list of watches
|
119
|
+
watches = Array(self.watches[name] || self.groups[name])
|
120
|
+
|
121
|
+
# do the command
|
122
|
+
case command
|
123
|
+
when "start", "monitor"
|
124
|
+
watches.each { |w| w.monitor }
|
125
|
+
when "restart"
|
126
|
+
watches.each { |w| w.move(:restart) }
|
127
|
+
when "stop"
|
128
|
+
watches.each { |w| w.unmonitor.action(:stop) }
|
129
|
+
when "unmonitor"
|
130
|
+
watches.each { |w| w.unmonitor }
|
131
|
+
else
|
132
|
+
raise InvalidCommandError.new
|
133
|
+
end
|
134
|
+
|
135
|
+
watches
|
136
|
+
end
|
137
|
+
|
138
|
+
def self.start
|
139
|
+
# make sure there's something to do
|
140
|
+
if self.watches.nil? || self.watches.empty?
|
141
|
+
abort "You must specify at least one watch!"
|
142
|
+
end
|
55
143
|
|
56
144
|
# start event handler system
|
57
|
-
EventHandler.start
|
145
|
+
EventHandler.start if EventHandler.loaded?
|
58
146
|
|
59
147
|
# start the timer system
|
60
148
|
Timer.get
|
61
149
|
|
62
|
-
# start monitoring
|
63
|
-
|
150
|
+
# start monitoring any watches set to autostart
|
151
|
+
self.watches.values.each { |w| w.monitor if w.autostart? }
|
64
152
|
|
65
|
-
# join the timer thread
|
153
|
+
# join the timer thread so we don't exit
|
66
154
|
Timer.get.join
|
67
|
-
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def self.at_exit
|
158
|
+
self.start
|
159
|
+
end
|
160
|
+
|
161
|
+
def self.load(glob)
|
162
|
+
Dir[glob].each do |f|
|
163
|
+
Kernel.load f
|
164
|
+
end
|
165
|
+
end
|
68
166
|
end
|
167
|
+
|
168
|
+
at_exit do
|
169
|
+
God.at_exit
|
170
|
+
end
|
data/lib/god/behavior.rb
CHANGED
@@ -1,12 +1,16 @@
|
|
1
1
|
module God
|
2
2
|
|
3
|
-
class Behavior
|
3
|
+
class Behavior
|
4
|
+
attr_accessor :watch
|
5
|
+
|
4
6
|
# Generate a Behavior of the given kind. The proper class if found by camel casing the
|
5
7
|
# kind (which is given as an underscored symbol).
|
6
8
|
# +kind+ is the underscored symbol representing the class (e.g. foo_bar for God::Behaviors::FooBar)
|
7
|
-
def self.generate(kind)
|
9
|
+
def self.generate(kind, watch)
|
8
10
|
sym = kind.to_s.capitalize.gsub(/_(.)/){$1.upcase}.intern
|
9
|
-
God::Behaviors.const_get(sym).new
|
11
|
+
b = God::Behaviors.const_get(sym).new
|
12
|
+
b.watch = watch
|
13
|
+
b
|
10
14
|
rescue NameError
|
11
15
|
raise NoSuchBehaviorError.new("No Behavior found with the class name God::Behaviors::#{sym}")
|
12
16
|
end
|
@@ -59,6 +63,7 @@ module God
|
|
59
63
|
protected
|
60
64
|
|
61
65
|
def complain(text)
|
66
|
+
Syslog.err(text)
|
62
67
|
puts text
|
63
68
|
false
|
64
69
|
end
|
@@ -2,20 +2,14 @@ module God
|
|
2
2
|
module Behaviors
|
3
3
|
|
4
4
|
class CleanPidFile < Behavior
|
5
|
-
attr_accessor :pid_file
|
6
|
-
|
7
|
-
def initialize
|
8
|
-
self.pid_file = nil
|
9
|
-
end
|
10
|
-
|
11
5
|
def valid?
|
12
6
|
valid = true
|
13
|
-
valid &= complain("You must specify the 'pid_file' attribute for :clean_pid_file") if self.pid_file.nil?
|
7
|
+
valid &= complain("You must specify the 'pid_file' attribute on the Watch for :clean_pid_file") if self.watch.pid_file.nil?
|
14
8
|
valid
|
15
9
|
end
|
16
10
|
|
17
11
|
def before_start
|
18
|
-
File.delete(self.pid_file) rescue nil
|
12
|
+
File.delete(self.watch.pid_file) rescue nil
|
19
13
|
end
|
20
14
|
end
|
21
15
|
|
data/lib/god/condition.rb
CHANGED
@@ -4,15 +4,16 @@ module God
|
|
4
4
|
# Generate a Condition of the given kind. The proper class if found by camel casing the
|
5
5
|
# kind (which is given as an underscored symbol).
|
6
6
|
# +kind+ is the underscored symbol representing the class (e.g. foo_bar for God::Conditions::FooBar)
|
7
|
-
def self.generate(kind)
|
7
|
+
def self.generate(kind, watch)
|
8
8
|
sym = kind.to_s.capitalize.gsub(/_(.)/){$1.upcase}.intern
|
9
|
-
|
9
|
+
c = God::Conditions.const_get(sym).new
|
10
10
|
|
11
|
-
unless
|
12
|
-
abort "Condition '#{
|
11
|
+
unless c.kind_of?(PollCondition) || c.kind_of?(EventCondition)
|
12
|
+
abort "Condition '#{c.class.name}' must subclass either God::PollCondition or God::EventCondition"
|
13
13
|
end
|
14
14
|
|
15
|
-
|
15
|
+
c.watch = watch
|
16
|
+
c
|
16
17
|
rescue NameError
|
17
18
|
raise NoSuchConditionError.new("No Condition found with the class name God::Conditions::#{sym}")
|
18
19
|
end
|
@@ -2,7 +2,7 @@ module God
|
|
2
2
|
module Conditions
|
3
3
|
|
4
4
|
class CpuUsage < PollCondition
|
5
|
-
attr_accessor :
|
5
|
+
attr_accessor :above, :times
|
6
6
|
|
7
7
|
def initialize
|
8
8
|
super
|
@@ -20,15 +20,15 @@ module God
|
|
20
20
|
|
21
21
|
def valid?
|
22
22
|
valid = true
|
23
|
-
valid &= complain("You must specify the 'pid_file' attribute for :memory_usage") if self.pid_file.nil?
|
23
|
+
valid &= complain("You must specify the 'pid_file' attribute on the Watch for :memory_usage") if self.watch.pid_file.nil?
|
24
24
|
valid &= complain("You must specify the 'above' attribute for :memory_usage") if self.above.nil?
|
25
25
|
valid
|
26
26
|
end
|
27
27
|
|
28
28
|
def test
|
29
|
-
return false unless File.exist?(self.pid_file)
|
29
|
+
return false unless File.exist?(self.watch.pid_file)
|
30
30
|
|
31
|
-
pid = File.
|
31
|
+
pid = File.read(self.watch.pid_file).strip
|
32
32
|
process = System::Process.new(pid)
|
33
33
|
@timeline.push(process.percent_cpu)
|
34
34
|
if @timeline.select { |x| x > self.above }.size >= self.times.first
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module God
|
2
|
+
module Conditions
|
3
|
+
|
4
|
+
class Lambda < PollCondition
|
5
|
+
attr_accessor :lambda
|
6
|
+
|
7
|
+
def valid?
|
8
|
+
valid = true
|
9
|
+
valid &= complain("You must specify the 'lambda' attribute for :lambda") if self.lambda.nil?
|
10
|
+
valid
|
11
|
+
end
|
12
|
+
|
13
|
+
def test
|
14
|
+
self.lambda.call()
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -2,7 +2,7 @@ module God
|
|
2
2
|
module Conditions
|
3
3
|
|
4
4
|
class MemoryUsage < PollCondition
|
5
|
-
attr_accessor :
|
5
|
+
attr_accessor :above, :times
|
6
6
|
|
7
7
|
def initialize
|
8
8
|
super
|
@@ -20,15 +20,15 @@ module God
|
|
20
20
|
|
21
21
|
def valid?
|
22
22
|
valid = true
|
23
|
-
valid &= complain("You must specify the 'pid_file' attribute for :memory_usage") if self.pid_file.nil?
|
23
|
+
valid &= complain("You must specify the 'pid_file' attribute on the Watch for :memory_usage") if self.watch.pid_file.nil?
|
24
24
|
valid &= complain("You must specify the 'above' attribute for :memory_usage") if self.above.nil?
|
25
25
|
valid
|
26
26
|
end
|
27
27
|
|
28
28
|
def test
|
29
|
-
return false unless File.exist?(self.pid_file)
|
29
|
+
return false unless File.exist?(self.watch.pid_file)
|
30
30
|
|
31
|
-
pid = File.
|
31
|
+
pid = File.read(self.watch.pid_file).strip
|
32
32
|
process = System::Process.new(pid)
|
33
33
|
@timeline.push(process.memory)
|
34
34
|
if @timeline.select { |x| x > self.above }.size >= self.times.first
|
@@ -2,24 +2,22 @@ module God
|
|
2
2
|
module Conditions
|
3
3
|
|
4
4
|
class ProcessExits < EventCondition
|
5
|
-
attr_accessor :pid_file
|
6
|
-
|
7
5
|
def valid?
|
8
6
|
valid = true
|
9
|
-
valid &= complain("You must specify the 'pid_file' attribute for :process_exits") if self.pid_file.nil?
|
7
|
+
valid &= complain("You must specify the 'pid_file' attribute on the Watch for :process_exits") if self.watch.pid_file.nil?
|
10
8
|
valid
|
11
9
|
end
|
12
10
|
|
13
11
|
def register
|
14
|
-
pid = File.
|
12
|
+
pid = File.read(self.watch.pid_file).strip.to_i
|
15
13
|
|
16
|
-
EventHandler.register(pid, :proc_exit)
|
14
|
+
EventHandler.register(pid, :proc_exit) do
|
17
15
|
Hub.trigger(self)
|
18
|
-
|
16
|
+
end
|
19
17
|
end
|
20
18
|
|
21
19
|
def deregister
|
22
|
-
pid = File.
|
20
|
+
pid = File.read(self.watch.pid_file).strip.to_i
|
23
21
|
EventHandler.deregister(pid, :proc_exit)
|
24
22
|
end
|
25
23
|
end
|
@@ -2,19 +2,19 @@ module God
|
|
2
2
|
module Conditions
|
3
3
|
|
4
4
|
class ProcessRunning < PollCondition
|
5
|
-
attr_accessor :
|
5
|
+
attr_accessor :running
|
6
6
|
|
7
7
|
def valid?
|
8
8
|
valid = true
|
9
|
-
valid &= complain("You must specify the 'pid_file' attribute for :process_running") if self.pid_file.nil?
|
9
|
+
valid &= complain("You must specify the 'pid_file' attribute on the Watch for :process_running") if self.watch.pid_file.nil?
|
10
10
|
valid &= complain("You must specify the 'running' attribute for :process_running") if self.running.nil?
|
11
11
|
valid
|
12
12
|
end
|
13
13
|
|
14
14
|
def test
|
15
|
-
return !self.running unless File.exist?(self.pid_file)
|
15
|
+
return !self.running unless File.exist?(self.watch.pid_file)
|
16
16
|
|
17
|
-
pid = File.
|
17
|
+
pid = File.read(self.watch.pid_file).strip
|
18
18
|
active = System::Process.new(pid).exists?
|
19
19
|
|
20
20
|
(self.running && active) || (!self.running && !active)
|