mcproc 2016.2.20
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.
- checksums.yaml +7 -0
- data/Announce.txt +135 -0
- data/Gemfile +9 -0
- data/History.txt +469 -0
- data/LICENSE +22 -0
- data/README.md +37 -0
- data/Rakefile +185 -0
- data/TODO.md +37 -0
- data/bin/mcproc +134 -0
- data/doc/intro.asciidoc +20 -0
- data/doc/mcproc.asciidoc +1592 -0
- data/ext/god/.gitignore +5 -0
- data/ext/god/extconf.rb +56 -0
- data/ext/god/kqueue_handler.c +133 -0
- data/ext/god/netlink_handler.c +182 -0
- data/lib/god.rb +780 -0
- data/lib/god/behavior.rb +52 -0
- data/lib/god/behaviors/clean_pid_file.rb +21 -0
- data/lib/god/behaviors/clean_unix_socket.rb +21 -0
- data/lib/god/behaviors/notify_when_flapping.rb +51 -0
- data/lib/god/cli/command.rb +268 -0
- data/lib/god/cli/run.rb +170 -0
- data/lib/god/cli/version.rb +23 -0
- data/lib/god/compat19.rb +33 -0
- data/lib/god/condition.rb +96 -0
- data/lib/god/conditions/always.rb +36 -0
- data/lib/god/conditions/complex.rb +86 -0
- data/lib/god/conditions/cpu_usage.rb +80 -0
- data/lib/god/conditions/degrading_lambda.rb +52 -0
- data/lib/god/conditions/disk_usage.rb +32 -0
- data/lib/god/conditions/file_mtime.rb +28 -0
- data/lib/god/conditions/file_touched.rb +44 -0
- data/lib/god/conditions/flapping.rb +128 -0
- data/lib/god/conditions/http_response_code.rb +184 -0
- data/lib/god/conditions/lambda.rb +25 -0
- data/lib/god/conditions/memory_usage.rb +82 -0
- data/lib/god/conditions/process_exits.rb +66 -0
- data/lib/god/conditions/process_running.rb +63 -0
- data/lib/god/conditions/socket_responding.rb +142 -0
- data/lib/god/conditions/tries.rb +44 -0
- data/lib/god/configurable.rb +57 -0
- data/lib/god/contact.rb +114 -0
- data/lib/god/contacts/airbrake.rb +44 -0
- data/lib/god/contacts/campfire.rb +121 -0
- data/lib/god/contacts/email.rb +130 -0
- data/lib/god/contacts/hipchat.rb +117 -0
- data/lib/god/contacts/jabber.rb +75 -0
- data/lib/god/contacts/prowl.rb +57 -0
- data/lib/god/contacts/scout.rb +55 -0
- data/lib/god/contacts/sensu.rb +59 -0
- data/lib/god/contacts/slack.rb +98 -0
- data/lib/god/contacts/statsd.rb +46 -0
- data/lib/god/contacts/twitter.rb +51 -0
- data/lib/god/contacts/webhook.rb +74 -0
- data/lib/god/driver.rb +238 -0
- data/lib/god/errors.rb +24 -0
- data/lib/god/event_handler.rb +112 -0
- data/lib/god/event_handlers/dummy_handler.rb +13 -0
- data/lib/god/event_handlers/kqueue_handler.rb +17 -0
- data/lib/god/event_handlers/netlink_handler.rb +13 -0
- data/lib/god/logger.rb +109 -0
- data/lib/god/metric.rb +87 -0
- data/lib/god/process.rb +381 -0
- data/lib/god/registry.rb +32 -0
- data/lib/god/simple_logger.rb +59 -0
- data/lib/god/socket.rb +113 -0
- data/lib/god/sugar.rb +62 -0
- data/lib/god/sys_logger.rb +45 -0
- data/lib/god/system/portable_poller.rb +42 -0
- data/lib/god/system/process.rb +50 -0
- data/lib/god/system/slash_proc_poller.rb +92 -0
- data/lib/god/task.rb +552 -0
- data/lib/god/timeline.rb +25 -0
- data/lib/god/trigger.rb +43 -0
- data/lib/god/watch.rb +340 -0
- data/mcproc.gemspec +192 -0
- data/test/configs/child_events/child_events.god +44 -0
- data/test/configs/child_events/simple_server.rb +3 -0
- data/test/configs/child_polls/child_polls.god +37 -0
- data/test/configs/child_polls/simple_server.rb +12 -0
- data/test/configs/complex/complex.god +59 -0
- data/test/configs/complex/simple_server.rb +3 -0
- data/test/configs/contact/contact.god +118 -0
- data/test/configs/contact/simple_server.rb +3 -0
- data/test/configs/daemon_events/daemon_events.god +37 -0
- data/test/configs/daemon_events/simple_server.rb +8 -0
- data/test/configs/daemon_events/simple_server_stop.rb +11 -0
- data/test/configs/daemon_polls/daemon_polls.god +17 -0
- data/test/configs/daemon_polls/simple_server.rb +6 -0
- data/test/configs/degrading_lambda/degrading_lambda.god +31 -0
- data/test/configs/degrading_lambda/tcp_server.rb +15 -0
- data/test/configs/keepalive/keepalive.god +9 -0
- data/test/configs/keepalive/keepalive.rb +12 -0
- data/test/configs/lifecycle/lifecycle.god +25 -0
- data/test/configs/matias/matias.god +50 -0
- data/test/configs/real.rb +59 -0
- data/test/configs/running_load/running_load.god +16 -0
- data/test/configs/stop_options/simple_server.rb +12 -0
- data/test/configs/stop_options/stop_options.god +39 -0
- data/test/configs/stress/simple_server.rb +3 -0
- data/test/configs/stress/stress.god +15 -0
- data/test/configs/task/logs/.placeholder +0 -0
- data/test/configs/task/task.god +26 -0
- data/test/configs/test.rb +61 -0
- data/test/configs/usr1_trapper.rb +10 -0
- data/test/helper.rb +172 -0
- data/test/suite.rb +6 -0
- data/test/test_airbrake.rb +14 -0
- data/test/test_behavior.rb +18 -0
- data/test/test_campfire.rb +22 -0
- data/test/test_condition.rb +52 -0
- data/test/test_conditions_disk_usage.rb +50 -0
- data/test/test_conditions_http_response_code.rb +109 -0
- data/test/test_conditions_process_running.rb +40 -0
- data/test/test_conditions_socket_responding.rb +176 -0
- data/test/test_conditions_tries.rb +67 -0
- data/test/test_contact.rb +109 -0
- data/test/test_driver.rb +26 -0
- data/test/test_email.rb +34 -0
- data/test/test_event_handler.rb +82 -0
- data/test/test_god.rb +710 -0
- data/test/test_god_system.rb +201 -0
- data/test/test_handlers_kqueue_handler.rb +16 -0
- data/test/test_hipchat.rb +23 -0
- data/test/test_jabber.rb +29 -0
- data/test/test_logger.rb +55 -0
- data/test/test_metric.rb +74 -0
- data/test/test_process.rb +263 -0
- data/test/test_prowl.rb +15 -0
- data/test/test_registry.rb +15 -0
- data/test/test_sensu.rb +11 -0
- data/test/test_slack.rb +57 -0
- data/test/test_socket.rb +34 -0
- data/test/test_statsd.rb +22 -0
- data/test/test_sugar.rb +42 -0
- data/test/test_system_portable_poller.rb +17 -0
- data/test/test_system_process.rb +30 -0
- data/test/test_task.rb +246 -0
- data/test/test_timeline.rb +37 -0
- data/test/test_trigger.rb +63 -0
- data/test/test_watch.rb +286 -0
- data/test/test_webhook.rb +22 -0
- metadata +475 -0
data/ext/god/.gitignore
ADDED
data/ext/god/extconf.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'mkmf'
|
2
|
+
|
3
|
+
fail = false
|
4
|
+
|
5
|
+
def create_dummy_makefile
|
6
|
+
File.open("Makefile", 'w') do |f|
|
7
|
+
f.puts "all:"
|
8
|
+
f.puts "install:"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
have_func('rb_wait_for_single_fd')
|
13
|
+
case RUBY_PLATFORM
|
14
|
+
when /bsd/i, /darwin/i
|
15
|
+
unless have_header('sys/event.h')
|
16
|
+
puts
|
17
|
+
puts "Missing 'sys/event.h' header"
|
18
|
+
fail = true
|
19
|
+
end
|
20
|
+
|
21
|
+
if fail
|
22
|
+
puts
|
23
|
+
puts "Events handler could not be compiled (see above error). Your god installation will not support event conditions."
|
24
|
+
create_dummy_makefile
|
25
|
+
else
|
26
|
+
create_makefile 'kqueue_handler_ext'
|
27
|
+
end
|
28
|
+
when /linux/i
|
29
|
+
unless have_header('linux/netlink.h')
|
30
|
+
puts
|
31
|
+
puts "Missing 'linux/netlink.h' header(s)"
|
32
|
+
puts "You may need to install a header package for your system"
|
33
|
+
fail = true
|
34
|
+
end
|
35
|
+
|
36
|
+
unless have_header('linux/connector.h') && have_header('linux/cn_proc.h')
|
37
|
+
puts
|
38
|
+
puts "Missing 'linux/connector.h', or 'linux/cn_proc.h' header(s)"
|
39
|
+
puts "These are only available in Linux kernel 2.6.15 and later (run `uname -a` to find yours)"
|
40
|
+
puts "You may need to install a header package for your system"
|
41
|
+
fail = true
|
42
|
+
end
|
43
|
+
|
44
|
+
if fail
|
45
|
+
puts
|
46
|
+
puts "Events handler could not be compiled (see above error). Your god installation will not support event conditions."
|
47
|
+
create_dummy_makefile
|
48
|
+
else
|
49
|
+
create_makefile 'netlink_handler_ext'
|
50
|
+
end
|
51
|
+
else
|
52
|
+
puts
|
53
|
+
puts "Unsupported platform '#{RUBY_PLATFORM}'. Supported platforms are BSD, DARWIN, and LINUX."
|
54
|
+
puts "Your god installation will not support event conditions."
|
55
|
+
create_dummy_makefile
|
56
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__OpenBSD__) || defined(__NetBSD__)
|
2
|
+
|
3
|
+
#include <ruby.h>
|
4
|
+
#include <sys/event.h>
|
5
|
+
#include <sys/time.h>
|
6
|
+
#include <errno.h>
|
7
|
+
|
8
|
+
#ifdef HAVE_RB_WAIT_FOR_SINGLE_FD
|
9
|
+
#include <ruby/io.h>
|
10
|
+
#endif
|
11
|
+
|
12
|
+
static VALUE mGod;
|
13
|
+
static VALUE cKQueueHandler;
|
14
|
+
static VALUE cEventHandler;
|
15
|
+
|
16
|
+
static ID proc_exit;
|
17
|
+
static ID proc_fork;
|
18
|
+
static ID m_call;
|
19
|
+
static ID m_size;
|
20
|
+
static ID m_deregister;
|
21
|
+
|
22
|
+
static int kq;
|
23
|
+
int num_events;
|
24
|
+
|
25
|
+
#define NUM_EVENTS FIX2INT(rb_funcall(rb_cv_get(cEventHandler, "@@actions"), m_size, 0))
|
26
|
+
|
27
|
+
VALUE
|
28
|
+
kqh_event_mask(VALUE klass, VALUE sym)
|
29
|
+
{
|
30
|
+
ID id = SYM2ID(sym);
|
31
|
+
if (proc_exit == id) {
|
32
|
+
return UINT2NUM(NOTE_EXIT);
|
33
|
+
} else if (proc_fork == id) {
|
34
|
+
return UINT2NUM(NOTE_FORK);
|
35
|
+
} else {
|
36
|
+
rb_raise(rb_eNotImpError, "Event `%s` not implemented", rb_id2name(id));
|
37
|
+
}
|
38
|
+
|
39
|
+
return Qnil;
|
40
|
+
}
|
41
|
+
|
42
|
+
|
43
|
+
VALUE
|
44
|
+
kqh_monitor_process(VALUE klass, VALUE pid, VALUE mask)
|
45
|
+
{
|
46
|
+
struct kevent new_event;
|
47
|
+
ID event;
|
48
|
+
|
49
|
+
(void)event; //!< Silence warning about unused var, should be removed?
|
50
|
+
|
51
|
+
u_int fflags = NUM2UINT(mask);
|
52
|
+
|
53
|
+
EV_SET(&new_event, FIX2UINT(pid), EVFILT_PROC,
|
54
|
+
EV_ADD | EV_ENABLE, fflags, 0, 0);
|
55
|
+
|
56
|
+
if (-1 == kevent(kq, &new_event, 1, NULL, 0, NULL)) {
|
57
|
+
rb_raise(rb_eStandardError, "%s", strerror(errno));
|
58
|
+
}
|
59
|
+
|
60
|
+
num_events = NUM_EVENTS;
|
61
|
+
|
62
|
+
return Qnil;
|
63
|
+
}
|
64
|
+
|
65
|
+
VALUE
|
66
|
+
kqh_handle_events()
|
67
|
+
{
|
68
|
+
int nevents, i, num_to_fetch;
|
69
|
+
struct kevent *events;
|
70
|
+
|
71
|
+
#ifdef HAVE_RB_WAIT_FOR_SINGLE_FD
|
72
|
+
rb_wait_for_single_fd(kq, RB_WAITFD_IN, NULL);
|
73
|
+
#else
|
74
|
+
fd_set read_set;
|
75
|
+
|
76
|
+
FD_ZERO(&read_set);
|
77
|
+
FD_SET(kq, &read_set);
|
78
|
+
|
79
|
+
// Don't actually run this method until we've got an event
|
80
|
+
rb_thread_select(kq + 1, &read_set, NULL, NULL, NULL);
|
81
|
+
#endif
|
82
|
+
// Grabbing num_events once for thread safety
|
83
|
+
num_to_fetch = num_events;
|
84
|
+
events = (struct kevent*)malloc(num_to_fetch * sizeof(struct kevent));
|
85
|
+
|
86
|
+
if (NULL == events) {
|
87
|
+
rb_raise(rb_eStandardError, "%s", strerror(errno));
|
88
|
+
}
|
89
|
+
|
90
|
+
nevents = kevent(kq, NULL, 0, events, num_to_fetch, NULL);
|
91
|
+
|
92
|
+
if (-1 == nevents) {
|
93
|
+
free(events);
|
94
|
+
rb_raise(rb_eStandardError, "%s", strerror(errno));
|
95
|
+
} else {
|
96
|
+
for (i = 0; i < nevents; i++) {
|
97
|
+
if (events[i].fflags & NOTE_EXIT) {
|
98
|
+
rb_funcall(cEventHandler, m_call, 2, INT2NUM(events[i].ident), ID2SYM(proc_exit));
|
99
|
+
} else if (events[i].fflags & NOTE_FORK) {
|
100
|
+
rb_funcall(cEventHandler, m_call, 2, INT2NUM(events[i].ident), ID2SYM(proc_fork));
|
101
|
+
}
|
102
|
+
}
|
103
|
+
}
|
104
|
+
|
105
|
+
free(events);
|
106
|
+
|
107
|
+
return INT2FIX(nevents);
|
108
|
+
}
|
109
|
+
|
110
|
+
void
|
111
|
+
Init_kqueue_handler_ext()
|
112
|
+
{
|
113
|
+
kq = kqueue();
|
114
|
+
|
115
|
+
if (kq == -1) {
|
116
|
+
rb_raise(rb_eStandardError, "kqueue initilization failed");
|
117
|
+
}
|
118
|
+
|
119
|
+
proc_exit = rb_intern("proc_exit");
|
120
|
+
proc_fork = rb_intern("proc_fork");
|
121
|
+
m_call = rb_intern("call");
|
122
|
+
m_size = rb_intern("size");
|
123
|
+
m_deregister = rb_intern("deregister");
|
124
|
+
|
125
|
+
mGod = rb_const_get(rb_cObject, rb_intern("God"));
|
126
|
+
cEventHandler = rb_const_get(mGod, rb_intern("EventHandler"));
|
127
|
+
cKQueueHandler = rb_define_class_under(mGod, "KQueueHandler", rb_cObject);
|
128
|
+
rb_define_singleton_method(cKQueueHandler, "monitor_process", kqh_monitor_process, 2);
|
129
|
+
rb_define_singleton_method(cKQueueHandler, "handle_events", kqh_handle_events, 0);
|
130
|
+
rb_define_singleton_method(cKQueueHandler, "event_mask", kqh_event_mask, 1);
|
131
|
+
}
|
132
|
+
|
133
|
+
#endif
|
@@ -0,0 +1,182 @@
|
|
1
|
+
#ifdef __linux__ /* only build on linux */
|
2
|
+
|
3
|
+
#include <ruby.h>
|
4
|
+
|
5
|
+
#ifdef HAVE_RB_WAIT_FOR_SINGLE_FD
|
6
|
+
#include <ruby/io.h>
|
7
|
+
#endif
|
8
|
+
|
9
|
+
|
10
|
+
#include <sys/types.h>
|
11
|
+
#include <unistd.h>
|
12
|
+
#include <sys/socket.h>
|
13
|
+
#include <linux/netlink.h>
|
14
|
+
#include <linux/connector.h>
|
15
|
+
#define _LINUX_TIME_H
|
16
|
+
#include <linux/cn_proc.h>
|
17
|
+
#include <errno.h>
|
18
|
+
|
19
|
+
static VALUE mGod;
|
20
|
+
static VALUE cNetlinkHandler;
|
21
|
+
static VALUE cEventHandler;
|
22
|
+
|
23
|
+
static ID proc_exit;
|
24
|
+
static ID proc_fork;
|
25
|
+
static ID m_call;
|
26
|
+
static ID m_watching_pid;
|
27
|
+
|
28
|
+
static int nl_sock; /* socket for netlink connection */
|
29
|
+
|
30
|
+
|
31
|
+
VALUE
|
32
|
+
nlh_handle_events()
|
33
|
+
{
|
34
|
+
char buff[CONNECTOR_MAX_MSG_SIZE];
|
35
|
+
struct nlmsghdr *hdr;
|
36
|
+
struct proc_event *event;
|
37
|
+
|
38
|
+
VALUE extra_data;
|
39
|
+
|
40
|
+
#ifdef HAVE_RB_WAIT_FOR_SINGLE_FD
|
41
|
+
int ret;
|
42
|
+
if((ret = rb_wait_for_single_fd(nl_sock, RB_WAITFD_IN, NULL)) < 0){
|
43
|
+
rb_raise(rb_eStandardError, "%s", strerror(errno));
|
44
|
+
}
|
45
|
+
/* If there were no events detected, return */
|
46
|
+
if(!(ret & RB_WAITFD_IN)){
|
47
|
+
return INT2FIX(0);
|
48
|
+
}
|
49
|
+
#else
|
50
|
+
fd_set fds;
|
51
|
+
|
52
|
+
FD_ZERO(&fds);
|
53
|
+
FD_SET(nl_sock, &fds);
|
54
|
+
|
55
|
+
if (0 > rb_thread_select(nl_sock + 1, &fds, NULL, NULL, NULL)) {
|
56
|
+
rb_raise(rb_eStandardError, "%s", strerror(errno));
|
57
|
+
}
|
58
|
+
|
59
|
+
/* If there were no events detected, return */
|
60
|
+
if (! FD_ISSET(nl_sock, &fds)) {
|
61
|
+
return INT2FIX(0);
|
62
|
+
}
|
63
|
+
#endif
|
64
|
+
|
65
|
+
/* if there are events, make calls */
|
66
|
+
if (-1 == recv(nl_sock, buff, sizeof(buff), 0)) {
|
67
|
+
rb_raise(rb_eStandardError, "%s", strerror(errno));
|
68
|
+
}
|
69
|
+
|
70
|
+
hdr = (struct nlmsghdr *)buff;
|
71
|
+
|
72
|
+
if (NLMSG_ERROR == hdr->nlmsg_type) {
|
73
|
+
rb_raise(rb_eStandardError, "%s", strerror(errno));
|
74
|
+
} else if (NLMSG_DONE == hdr->nlmsg_type) {
|
75
|
+
|
76
|
+
event = (struct proc_event *)((struct cn_msg *)NLMSG_DATA(hdr))->data;
|
77
|
+
|
78
|
+
switch(event->what) {
|
79
|
+
case PROC_EVENT_EXIT:
|
80
|
+
if (Qnil == rb_funcall(cEventHandler, m_watching_pid, 1, INT2FIX(event->event_data.exit.process_pid))) {
|
81
|
+
return INT2FIX(0);
|
82
|
+
}
|
83
|
+
|
84
|
+
extra_data = rb_hash_new();
|
85
|
+
rb_hash_aset(extra_data, ID2SYM(rb_intern("pid")), INT2FIX(event->event_data.exit.process_pid));
|
86
|
+
rb_hash_aset(extra_data, ID2SYM(rb_intern("exit_code")), INT2FIX(event->event_data.exit.exit_code));
|
87
|
+
rb_hash_aset(extra_data, ID2SYM(rb_intern("exit_signal")), INT2FIX(event->event_data.exit.exit_signal));
|
88
|
+
rb_hash_aset(extra_data, ID2SYM(rb_intern("thread_group_id")), INT2FIX(event->event_data.exit.process_tgid));
|
89
|
+
|
90
|
+
rb_funcall(cEventHandler, m_call, 3, INT2FIX(event->event_data.exit.process_pid), ID2SYM(proc_exit), extra_data);
|
91
|
+
return INT2FIX(1);
|
92
|
+
|
93
|
+
case PROC_EVENT_FORK:
|
94
|
+
if (Qnil == rb_funcall(cEventHandler, m_watching_pid, 1, INT2FIX(event->event_data.fork.parent_pid))) {
|
95
|
+
return INT2FIX(0);
|
96
|
+
}
|
97
|
+
|
98
|
+
extra_data = rb_hash_new();
|
99
|
+
rb_hash_aset(extra_data, ID2SYM(rb_intern("parent_pid")), INT2FIX(event->event_data.fork.parent_pid));
|
100
|
+
rb_hash_aset(extra_data, ID2SYM(rb_intern("parent_thread_group_id")), INT2FIX(event->event_data.fork.parent_tgid));
|
101
|
+
rb_hash_aset(extra_data, ID2SYM(rb_intern("child_pid")), INT2FIX(event->event_data.fork.child_pid));
|
102
|
+
rb_hash_aset(extra_data, ID2SYM(rb_intern("child_thread_group_id")), INT2FIX(event->event_data.fork.child_tgid));
|
103
|
+
|
104
|
+
rb_funcall(cEventHandler, m_call, 3, INT2FIX(event->event_data.fork.parent_pid), ID2SYM(proc_fork), extra_data);
|
105
|
+
return INT2FIX(1);
|
106
|
+
|
107
|
+
default:
|
108
|
+
break;
|
109
|
+
}
|
110
|
+
}
|
111
|
+
|
112
|
+
return Qnil;
|
113
|
+
}
|
114
|
+
|
115
|
+
|
116
|
+
#define NL_MESSAGE_SIZE (sizeof(struct nlmsghdr) + sizeof(struct cn_msg) + \
|
117
|
+
sizeof(int))
|
118
|
+
|
119
|
+
void
|
120
|
+
connect_to_netlink()
|
121
|
+
{
|
122
|
+
struct sockaddr_nl sa_nl; /* netlink interface info */
|
123
|
+
char buff[NL_MESSAGE_SIZE];
|
124
|
+
struct nlmsghdr *hdr; /* for telling netlink what we want */
|
125
|
+
struct cn_msg *msg; /* the actual connector message */
|
126
|
+
|
127
|
+
/* connect to netlink socket */
|
128
|
+
nl_sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR);
|
129
|
+
|
130
|
+
if (-1 == nl_sock) {
|
131
|
+
rb_raise(rb_eStandardError, "%s", strerror(errno));
|
132
|
+
}
|
133
|
+
|
134
|
+
bzero(&sa_nl, sizeof(sa_nl));
|
135
|
+
sa_nl.nl_family = AF_NETLINK;
|
136
|
+
sa_nl.nl_groups = CN_IDX_PROC;
|
137
|
+
sa_nl.nl_pid = getpid();
|
138
|
+
|
139
|
+
if (-1 == bind(nl_sock, (struct sockaddr *)&sa_nl, sizeof(sa_nl))) {
|
140
|
+
rb_raise(rb_eStandardError, "%s", strerror(errno));
|
141
|
+
}
|
142
|
+
|
143
|
+
/* Fill header */
|
144
|
+
hdr = (struct nlmsghdr *)buff;
|
145
|
+
hdr->nlmsg_len = NL_MESSAGE_SIZE;
|
146
|
+
hdr->nlmsg_type = NLMSG_DONE;
|
147
|
+
hdr->nlmsg_flags = 0;
|
148
|
+
hdr->nlmsg_seq = 0;
|
149
|
+
hdr->nlmsg_pid = getpid();
|
150
|
+
|
151
|
+
/* Fill message */
|
152
|
+
msg = (struct cn_msg *)NLMSG_DATA(hdr);
|
153
|
+
msg->id.idx = CN_IDX_PROC; /* Connecting to process information */
|
154
|
+
msg->id.val = CN_VAL_PROC;
|
155
|
+
msg->seq = 0;
|
156
|
+
msg->ack = 0;
|
157
|
+
msg->flags = 0;
|
158
|
+
msg->len = sizeof(int);
|
159
|
+
*(int*)msg->data = PROC_CN_MCAST_LISTEN;
|
160
|
+
|
161
|
+
if (-1 == send(nl_sock, hdr, hdr->nlmsg_len, 0)) {
|
162
|
+
rb_raise(rb_eStandardError, "%s", strerror(errno));
|
163
|
+
}
|
164
|
+
}
|
165
|
+
|
166
|
+
void
|
167
|
+
Init_netlink_handler_ext()
|
168
|
+
{
|
169
|
+
proc_exit = rb_intern("proc_exit");
|
170
|
+
proc_fork = rb_intern("proc_fork");
|
171
|
+
m_call = rb_intern("call");
|
172
|
+
m_watching_pid = rb_intern("watching_pid?");
|
173
|
+
|
174
|
+
mGod = rb_const_get(rb_cObject, rb_intern("God"));
|
175
|
+
cEventHandler = rb_const_get(mGod, rb_intern("EventHandler"));
|
176
|
+
cNetlinkHandler = rb_define_class_under(mGod, "NetlinkHandler", rb_cObject);
|
177
|
+
rb_define_singleton_method(cNetlinkHandler, "handle_events", nlh_handle_events, 0);
|
178
|
+
|
179
|
+
connect_to_netlink();
|
180
|
+
}
|
181
|
+
|
182
|
+
#endif
|
data/lib/god.rb
ADDED
@@ -0,0 +1,780 @@
|
|
1
|
+
# Bail out before loading anything unless this flag is set.
|
2
|
+
#
|
3
|
+
# We are doing this to guard against bundler autoloading because there is
|
4
|
+
# no value in loading god in most processes.
|
5
|
+
if $load_god
|
6
|
+
|
7
|
+
# core
|
8
|
+
require 'stringio'
|
9
|
+
require 'fileutils'
|
10
|
+
|
11
|
+
begin
|
12
|
+
require 'fastthread'
|
13
|
+
rescue LoadError
|
14
|
+
ensure
|
15
|
+
require 'thread'
|
16
|
+
end
|
17
|
+
|
18
|
+
# stdlib
|
19
|
+
|
20
|
+
# internal requires
|
21
|
+
require 'god/errors'
|
22
|
+
require 'god/simple_logger'
|
23
|
+
require 'god/logger'
|
24
|
+
require 'god/sugar'
|
25
|
+
|
26
|
+
require 'god/system/process'
|
27
|
+
require 'god/system/portable_poller'
|
28
|
+
require 'god/system/slash_proc_poller'
|
29
|
+
|
30
|
+
require 'god/timeline'
|
31
|
+
require 'god/configurable'
|
32
|
+
|
33
|
+
require 'god/task'
|
34
|
+
|
35
|
+
require 'god/behavior'
|
36
|
+
require 'god/behaviors/clean_pid_file'
|
37
|
+
require 'god/behaviors/clean_unix_socket'
|
38
|
+
require 'god/behaviors/notify_when_flapping'
|
39
|
+
|
40
|
+
require 'god/condition'
|
41
|
+
require 'god/conditions/process_running'
|
42
|
+
require 'god/conditions/process_exits'
|
43
|
+
require 'god/conditions/tries'
|
44
|
+
require 'god/conditions/memory_usage'
|
45
|
+
require 'god/conditions/cpu_usage'
|
46
|
+
require 'god/conditions/always'
|
47
|
+
require 'god/conditions/lambda'
|
48
|
+
require 'god/conditions/degrading_lambda'
|
49
|
+
require 'god/conditions/flapping'
|
50
|
+
require 'god/conditions/http_response_code'
|
51
|
+
require 'god/conditions/disk_usage'
|
52
|
+
require 'god/conditions/complex'
|
53
|
+
require 'god/conditions/file_mtime'
|
54
|
+
require 'god/conditions/file_touched'
|
55
|
+
require 'god/conditions/socket_responding'
|
56
|
+
|
57
|
+
require 'god/socket'
|
58
|
+
require 'god/driver'
|
59
|
+
|
60
|
+
require 'god/metric'
|
61
|
+
require 'god/watch'
|
62
|
+
|
63
|
+
require 'god/trigger'
|
64
|
+
require 'god/event_handler'
|
65
|
+
require 'god/registry'
|
66
|
+
require 'god/process'
|
67
|
+
|
68
|
+
require 'god/cli/version'
|
69
|
+
require 'god/cli/command'
|
70
|
+
|
71
|
+
# ruby 1.8 specific configuration
|
72
|
+
if RUBY_VERSION < '1.9'
|
73
|
+
$KCODE = 'u'
|
74
|
+
end
|
75
|
+
|
76
|
+
CONTACT_DEPS = { }
|
77
|
+
CONTACT_LOAD_SUCCESS = { }
|
78
|
+
|
79
|
+
def load_contact(name)
|
80
|
+
require "god/contacts/#{name}"
|
81
|
+
CONTACT_LOAD_SUCCESS[name] = true
|
82
|
+
rescue LoadError
|
83
|
+
CONTACT_LOAD_SUCCESS[name] = false
|
84
|
+
end
|
85
|
+
|
86
|
+
require 'god/contact'
|
87
|
+
load_contact(:campfire)
|
88
|
+
load_contact(:hipchat)
|
89
|
+
load_contact(:email)
|
90
|
+
load_contact(:jabber)
|
91
|
+
load_contact(:prowl)
|
92
|
+
load_contact(:scout)
|
93
|
+
load_contact(:statsd)
|
94
|
+
load_contact(:twitter)
|
95
|
+
load_contact(:webhook)
|
96
|
+
load_contact(:airbrake)
|
97
|
+
load_contact(:slack)
|
98
|
+
load_contact(:sensu)
|
99
|
+
|
100
|
+
$:.unshift File.join(File.dirname(__FILE__), *%w[.. ext god])
|
101
|
+
|
102
|
+
# App wide logging system
|
103
|
+
LOG = God::Logger.new
|
104
|
+
|
105
|
+
def applog(watch, level, text)
|
106
|
+
LOG.log(watch, level, text)
|
107
|
+
end
|
108
|
+
|
109
|
+
# The $run global determines whether god should be started when the
|
110
|
+
# program would normally end. This should be set to true if when god
|
111
|
+
# should be started (e.g. `god -c <config file>`) and false otherwise
|
112
|
+
# (e.g. `god status`)
|
113
|
+
$run ||= nil
|
114
|
+
|
115
|
+
GOD_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
116
|
+
|
117
|
+
# Return the binding of god's root level
|
118
|
+
def root_binding
|
119
|
+
binding
|
120
|
+
end
|
121
|
+
|
122
|
+
module Kernel
|
123
|
+
alias_method :abort_orig, :abort
|
124
|
+
|
125
|
+
def abort(text = nil)
|
126
|
+
$run = false
|
127
|
+
applog(nil, :error, text) if text
|
128
|
+
exit(1)
|
129
|
+
end
|
130
|
+
|
131
|
+
alias_method :exit_orig, :exit
|
132
|
+
|
133
|
+
def exit(code = 0)
|
134
|
+
$run = false
|
135
|
+
exit_orig(code)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
class Module
|
140
|
+
def safe_attr_accessor(*args)
|
141
|
+
args.each do |arg|
|
142
|
+
define_method((arg.to_s + "=").intern) do |other|
|
143
|
+
if !self.running && self.inited
|
144
|
+
abort "God.#{arg} must be set before any Tasks are defined"
|
145
|
+
end
|
146
|
+
|
147
|
+
if self.running && self.inited
|
148
|
+
applog(nil, :warn, "God.#{arg} can't be set while god is running")
|
149
|
+
return
|
150
|
+
end
|
151
|
+
|
152
|
+
instance_variable_set(('@' + arg.to_s).intern, other)
|
153
|
+
end
|
154
|
+
|
155
|
+
define_method(arg) do
|
156
|
+
instance_variable_get(('@' + arg.to_s).intern)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
module God
|
163
|
+
# The String version number for this package.
|
164
|
+
VERSION = '2016.2.20'
|
165
|
+
|
166
|
+
# The Integer number of lines of backlog to keep for the logger.
|
167
|
+
LOG_BUFFER_SIZE_DEFAULT = 100
|
168
|
+
|
169
|
+
# An Array of directory paths to be used as the default PID file directory.
|
170
|
+
# This list will be searched in order and the first one that has write
|
171
|
+
# permissions will be used.
|
172
|
+
PID_FILE_DIRECTORY_DEFAULTS = ['/var/run/god', '~/.god/pids']
|
173
|
+
|
174
|
+
# The default Integer port number for the DRb communcations channel.
|
175
|
+
DRB_PORT_DEFAULT = 17165
|
176
|
+
|
177
|
+
# The default Array of String IPs that will allow DRb communication access.
|
178
|
+
DRB_ALLOW_DEFAULT = ['127.0.0.1']
|
179
|
+
|
180
|
+
# The default Symbol log level.
|
181
|
+
LOG_LEVEL_DEFAULT = :info
|
182
|
+
|
183
|
+
# The default Integer number of seconds to wait for god to terminate when
|
184
|
+
# issued the quit command.
|
185
|
+
TERMINATE_TIMEOUT_DEFAULT = 10
|
186
|
+
|
187
|
+
# The default Integer number of seconds to wait for a process to terminate.
|
188
|
+
STOP_TIMEOUT_DEFAULT = 10
|
189
|
+
|
190
|
+
# The default String signal to send for the stop command.
|
191
|
+
STOP_SIGNAL_DEFAULT = 'TERM'
|
192
|
+
|
193
|
+
class << self
|
194
|
+
# user configurable
|
195
|
+
safe_attr_accessor :pid,
|
196
|
+
:host,
|
197
|
+
:port,
|
198
|
+
:allow,
|
199
|
+
:log_buffer_size,
|
200
|
+
:pid_file_directory,
|
201
|
+
:log_file,
|
202
|
+
:log_level,
|
203
|
+
:use_events,
|
204
|
+
:terminate_timeout,
|
205
|
+
:socket_user,
|
206
|
+
:socket_group,
|
207
|
+
:socket_perms
|
208
|
+
|
209
|
+
# internal
|
210
|
+
attr_accessor :inited,
|
211
|
+
:running,
|
212
|
+
:pending_watches,
|
213
|
+
:pending_watch_states,
|
214
|
+
:server,
|
215
|
+
:watches,
|
216
|
+
:groups,
|
217
|
+
:contacts,
|
218
|
+
:contact_groups,
|
219
|
+
:main
|
220
|
+
end
|
221
|
+
|
222
|
+
# Initialize class instance variables.
|
223
|
+
self.pid = nil
|
224
|
+
self.host = nil
|
225
|
+
self.port = nil
|
226
|
+
self.allow = nil
|
227
|
+
self.log_buffer_size = nil
|
228
|
+
self.pid_file_directory = nil
|
229
|
+
self.log_level = nil
|
230
|
+
self.terminate_timeout = nil
|
231
|
+
self.socket_user = nil
|
232
|
+
self.socket_group = nil
|
233
|
+
self.socket_perms = 0755
|
234
|
+
|
235
|
+
# Initialize internal data.
|
236
|
+
#
|
237
|
+
# Returns nothing.
|
238
|
+
def self.internal_init
|
239
|
+
# Only do this once.
|
240
|
+
return if self.inited
|
241
|
+
|
242
|
+
# Variable init.
|
243
|
+
self.watches = {}
|
244
|
+
self.groups = {}
|
245
|
+
self.pending_watches = []
|
246
|
+
self.pending_watch_states = {}
|
247
|
+
self.contacts = {}
|
248
|
+
self.contact_groups = {}
|
249
|
+
|
250
|
+
# Set defaults.
|
251
|
+
self.log_buffer_size ||= LOG_BUFFER_SIZE_DEFAULT
|
252
|
+
self.port ||= DRB_PORT_DEFAULT
|
253
|
+
self.allow ||= DRB_ALLOW_DEFAULT
|
254
|
+
self.log_level ||= LOG_LEVEL_DEFAULT
|
255
|
+
self.terminate_timeout ||= TERMINATE_TIMEOUT_DEFAULT
|
256
|
+
|
257
|
+
# Additional setup.
|
258
|
+
self.setup
|
259
|
+
|
260
|
+
# Log level.
|
261
|
+
log_level_map = {:debug => Logger::DEBUG,
|
262
|
+
:info => Logger::INFO,
|
263
|
+
:warn => Logger::WARN,
|
264
|
+
:error => Logger::ERROR,
|
265
|
+
:fatal => Logger::FATAL}
|
266
|
+
LOG.level = log_level_map[self.log_level]
|
267
|
+
|
268
|
+
# Init has been executed.
|
269
|
+
self.inited = true
|
270
|
+
|
271
|
+
# Not yet running.
|
272
|
+
self.running = false
|
273
|
+
end
|
274
|
+
|
275
|
+
# Instantiate a new, empty Watch object and pass it to the mandatory block.
|
276
|
+
# The attributes of the watch will be set by the configuration file. Aborts
|
277
|
+
# on duplicate watch name, invalid watch, or conflicting group name.
|
278
|
+
#
|
279
|
+
# Returns nothing.
|
280
|
+
def self.watch(&block)
|
281
|
+
self.task(Watch, &block)
|
282
|
+
end
|
283
|
+
|
284
|
+
# Instantiate a new, empty Task object and yield it to the mandatory block.
|
285
|
+
# The attributes of the task will be set by the configuration file. Aborts
|
286
|
+
# on duplicate task name, invalid task, or conflicting group name.
|
287
|
+
#
|
288
|
+
# Returns nothing.
|
289
|
+
def self.task(klass = Task)
|
290
|
+
# Ensure internal init has run.
|
291
|
+
self.internal_init
|
292
|
+
|
293
|
+
t = klass.new
|
294
|
+
yield(t)
|
295
|
+
|
296
|
+
# Do the post-configuration.
|
297
|
+
t.prepare
|
298
|
+
|
299
|
+
# If running, completely remove the watch (if necessary) to prepare for
|
300
|
+
# the reload
|
301
|
+
existing_watch = self.watches[t.name]
|
302
|
+
if self.running && existing_watch
|
303
|
+
self.pending_watch_states[existing_watch.name] = existing_watch.state
|
304
|
+
self.unwatch(existing_watch)
|
305
|
+
end
|
306
|
+
|
307
|
+
# Ensure the new watch has a unique name.
|
308
|
+
if self.watches[t.name] || self.groups[t.name]
|
309
|
+
abort "Task name '#{t.name}' already used for a Task or Group"
|
310
|
+
end
|
311
|
+
|
312
|
+
# Ensure watch is internally valid.
|
313
|
+
t.valid? || abort("Task '#{t.name}' is not valid (see above)")
|
314
|
+
|
315
|
+
# Add to list of watches.
|
316
|
+
self.watches[t.name] = t
|
317
|
+
|
318
|
+
# Add to pending watches.
|
319
|
+
self.pending_watches << t
|
320
|
+
|
321
|
+
# Add to group if specified.
|
322
|
+
if t.group
|
323
|
+
# Ensure group name hasn't been used for a watch already.
|
324
|
+
if self.watches[t.group]
|
325
|
+
abort "Group name '#{t.group}' already used for a Task"
|
326
|
+
end
|
327
|
+
|
328
|
+
self.groups[t.group] ||= []
|
329
|
+
self.groups[t.group] << t
|
330
|
+
end
|
331
|
+
|
332
|
+
# Register watch.
|
333
|
+
t.register!
|
334
|
+
|
335
|
+
# Log.
|
336
|
+
if self.running && existing_watch
|
337
|
+
applog(t, :info, "#{t.name} Reloaded config")
|
338
|
+
elsif self.running
|
339
|
+
applog(t, :info, "#{t.name} Loaded config")
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
# Unmonitor and remove the given watch from god.
|
344
|
+
#
|
345
|
+
# watch - The Watch to remove.
|
346
|
+
#
|
347
|
+
# Returns nothing.
|
348
|
+
def self.unwatch(watch)
|
349
|
+
# Unmonitor.
|
350
|
+
watch.unmonitor unless watch.state == :unmonitored
|
351
|
+
|
352
|
+
# Unregister.
|
353
|
+
watch.unregister!
|
354
|
+
|
355
|
+
# Remove from watches.
|
356
|
+
self.watches.delete(watch.name)
|
357
|
+
|
358
|
+
# Remove from groups.
|
359
|
+
if watch.group
|
360
|
+
self.groups[watch.group].delete(watch)
|
361
|
+
end
|
362
|
+
|
363
|
+
applog(watch, :info, "#{watch.name} unwatched")
|
364
|
+
end
|
365
|
+
|
366
|
+
# Instantiate a new Contact of the given kind and send it to the block.
|
367
|
+
# Then prepare, validate, and record the Contact. Aborts on invalid kind,
|
368
|
+
# duplicate contact name, invalid contact, or conflicting group name.
|
369
|
+
#
|
370
|
+
# kind - The Symbol contact class specifier.
|
371
|
+
#
|
372
|
+
# Returns nothing.
|
373
|
+
def self.contact(kind)
|
374
|
+
# Ensure internal init has run.
|
375
|
+
self.internal_init
|
376
|
+
|
377
|
+
# Verify contact has been loaded.
|
378
|
+
if CONTACT_LOAD_SUCCESS[kind] == false
|
379
|
+
applog(nil, :error, "A required dependency for the #{kind} contact is unavailable.")
|
380
|
+
applog(nil, :error, "Run the following commands to install the dependencies:")
|
381
|
+
CONTACT_DEPS[kind].each do |d|
|
382
|
+
applog(nil, :error, " [sudo] gem install #{d}")
|
383
|
+
end
|
384
|
+
abort
|
385
|
+
end
|
386
|
+
|
387
|
+
# Create the contact.
|
388
|
+
begin
|
389
|
+
c = Contact.generate(kind)
|
390
|
+
rescue NoSuchContactError => e
|
391
|
+
abort e.message
|
392
|
+
end
|
393
|
+
|
394
|
+
# Send to block so config can set attributes.
|
395
|
+
yield(c) if block_given?
|
396
|
+
|
397
|
+
# Call prepare on the contact.
|
398
|
+
c.prepare
|
399
|
+
|
400
|
+
# Remove existing contacts of same name.
|
401
|
+
existing_contact = self.contacts[c.name]
|
402
|
+
if self.running && existing_contact
|
403
|
+
self.uncontact(existing_contact)
|
404
|
+
end
|
405
|
+
|
406
|
+
# Warn and noop if the contact has been defined before.
|
407
|
+
if self.contacts[c.name] || self.contact_groups[c.name]
|
408
|
+
applog(nil, :warn, "Contact name '#{c.name}' already used for a Contact or Contact Group")
|
409
|
+
return
|
410
|
+
end
|
411
|
+
|
412
|
+
# Abort if the Contact is invalid, the Contact will have printed out its
|
413
|
+
# own error messages by now.
|
414
|
+
unless Contact.valid?(c) && c.valid?
|
415
|
+
abort "Exiting on invalid contact"
|
416
|
+
end
|
417
|
+
|
418
|
+
# Add to list of contacts.
|
419
|
+
self.contacts[c.name] = c
|
420
|
+
|
421
|
+
# Add to contact group if specified.
|
422
|
+
if c.group
|
423
|
+
# Ensure group name hasn't been used for a contact already.
|
424
|
+
if self.contacts[c.group]
|
425
|
+
abort "Contact Group name '#{c.group}' already used for a Contact"
|
426
|
+
end
|
427
|
+
|
428
|
+
self.contact_groups[c.group] ||= []
|
429
|
+
self.contact_groups[c.group] << c
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
# Remove the given contact from god.
|
434
|
+
#
|
435
|
+
# contact - The Contact to remove.
|
436
|
+
#
|
437
|
+
# Returns nothing.
|
438
|
+
def self.uncontact(contact)
|
439
|
+
self.contacts.delete(contact.name)
|
440
|
+
if contact.group
|
441
|
+
self.contact_groups[contact.group].delete(contact)
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
def self.watches_by_name(name)
|
446
|
+
case name
|
447
|
+
when "", nil then self.watches.values.dup
|
448
|
+
else Array(self.watches[name] || self.groups[name]).dup
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
452
|
+
# Control the lifecycle of the given task(s).
|
453
|
+
#
|
454
|
+
# name - The String name of a task/group. If empty, invokes command for all tasks.
|
455
|
+
# command - The String command to run. Valid commands are:
|
456
|
+
# "start", "monitor", "restart", "stop", "unmonitor", "remove".
|
457
|
+
#
|
458
|
+
# Returns an Array of String task names affected by the command.
|
459
|
+
def self.control(name, command)
|
460
|
+
# Get the list of items.
|
461
|
+
items = self.watches_by_name(name)
|
462
|
+
|
463
|
+
jobs = []
|
464
|
+
|
465
|
+
# Do the command.
|
466
|
+
case command
|
467
|
+
when "start", "monitor"
|
468
|
+
items.each { |w| jobs << Thread.new { w.monitor if w.state != :up } }
|
469
|
+
when "restart"
|
470
|
+
items.each { |w| jobs << Thread.new { w.move(:restart) } }
|
471
|
+
when "stop"
|
472
|
+
items.each { |w| jobs << Thread.new { w.action(:stop); w.unmonitor if w.state != :unmonitored } }
|
473
|
+
when "unmonitor"
|
474
|
+
items.each { |w| jobs << Thread.new { w.unmonitor if w.state != :unmonitored } }
|
475
|
+
when "remove"
|
476
|
+
items.each { |w| self.unwatch(w) }
|
477
|
+
else
|
478
|
+
raise InvalidCommandError.new
|
479
|
+
end
|
480
|
+
|
481
|
+
jobs.each { |j| j.join }
|
482
|
+
|
483
|
+
items.map { |x| x.name }
|
484
|
+
end
|
485
|
+
|
486
|
+
# Unmonitor and stop all tasks.
|
487
|
+
#
|
488
|
+
# Returns true on success, false if all tasks could not be stopped within 10
|
489
|
+
# seconds
|
490
|
+
def self.stop_all
|
491
|
+
self.watches.sort.each do |name, w|
|
492
|
+
Thread.new do
|
493
|
+
w.action(:stop)
|
494
|
+
w.unmonitor if w.state != :unmonitored
|
495
|
+
end
|
496
|
+
end
|
497
|
+
|
498
|
+
terminate_timeout.times do
|
499
|
+
return true unless self.watches.map { |name, w| w.alive? }.any?
|
500
|
+
sleep 1
|
501
|
+
end
|
502
|
+
|
503
|
+
return false
|
504
|
+
end
|
505
|
+
|
506
|
+
# Force the termination of god.
|
507
|
+
# * Clean up pid file if one exists
|
508
|
+
# * Stop DRb service
|
509
|
+
# * Hard exit using exit!
|
510
|
+
#
|
511
|
+
# Never returns because the process will no longer exist!
|
512
|
+
def self.terminate
|
513
|
+
FileUtils.rm_f(self.pid) if self.pid
|
514
|
+
self.server.stop if self.server
|
515
|
+
exit!(0)
|
516
|
+
end
|
517
|
+
|
518
|
+
# Gather the status of each task.
|
519
|
+
#
|
520
|
+
# Examples
|
521
|
+
#
|
522
|
+
# God.status
|
523
|
+
# # => { 'mongrel' => :up, 'nginx' => :up }
|
524
|
+
#
|
525
|
+
# Returns a Hash where the key is the String task name and the value is the
|
526
|
+
# Symbol status.
|
527
|
+
def self.status
|
528
|
+
info = {}
|
529
|
+
self.watches.map do |name, w|
|
530
|
+
info[name] = {:state => w.state, :group => w.group}
|
531
|
+
end
|
532
|
+
info
|
533
|
+
end
|
534
|
+
|
535
|
+
# Send a signal to each task.
|
536
|
+
#
|
537
|
+
# name - The String name of the task or group.
|
538
|
+
# signal - The String or integer signal to send. e.g. 'HUP', 9.
|
539
|
+
#
|
540
|
+
# Returns an Array of String names of the tasks affected.
|
541
|
+
def self.signal(name, signal)
|
542
|
+
items = watches_by_name(name)
|
543
|
+
jobs = []
|
544
|
+
items.each { |w| jobs << Thread.new { w.signal(signal) } }
|
545
|
+
jobs.each { |j| j.join }
|
546
|
+
items.map { |x| x.name }
|
547
|
+
end
|
548
|
+
|
549
|
+
# Log lines for the given task since the specified time.
|
550
|
+
#
|
551
|
+
# watch_name - The String name of the task (may be abbreviated).
|
552
|
+
# since - The Time since which to report log lines.
|
553
|
+
#
|
554
|
+
# Raises God::NoSuchWatchError if no tasks matched.
|
555
|
+
# Returns the String of newline separated log lines.
|
556
|
+
def self.running_log(watch_name, since)
|
557
|
+
matches = pattern_match(watch_name, self.watches.keys)
|
558
|
+
|
559
|
+
unless matches.first
|
560
|
+
raise NoSuchWatchError.new
|
561
|
+
end
|
562
|
+
|
563
|
+
LOG.watch_log_since(matches.first, since)
|
564
|
+
end
|
565
|
+
|
566
|
+
# Load a config file into a running god instance. Rescues any exceptions
|
567
|
+
# that the config may raise and reports these back to the caller.
|
568
|
+
#
|
569
|
+
# code - The String config file contents.
|
570
|
+
# filename - The filename of the config file.
|
571
|
+
# action - The optional String command specifying how to deal with
|
572
|
+
# existing watches. Valid options are: 'stop', 'remove' or
|
573
|
+
# 'leave' (default).
|
574
|
+
#
|
575
|
+
# Returns a three-tuple Array [loaded_names, errors, unloaded_names] where:
|
576
|
+
# loaded_names - The Array of String task names that were loaded.
|
577
|
+
# errors - The String of error messages produced during the
|
578
|
+
# load phase. Will be a blank String if no errors
|
579
|
+
# were encountered.
|
580
|
+
# unloaded_names - The Array of String task names that were unloaded
|
581
|
+
# from the system (if 'remove' or 'stop' was
|
582
|
+
# specified as the action).
|
583
|
+
def self.running_load(code, filename, action = nil)
|
584
|
+
errors = ""
|
585
|
+
loaded_watches = []
|
586
|
+
unloaded_watches = []
|
587
|
+
jobs = []
|
588
|
+
|
589
|
+
begin
|
590
|
+
LOG.start_capture
|
591
|
+
|
592
|
+
Gem.clear_paths
|
593
|
+
eval(code, root_binding, filename)
|
594
|
+
self.pending_watches.each do |w|
|
595
|
+
if previous_state = self.pending_watch_states[w.name]
|
596
|
+
w.monitor unless previous_state == :unmonitored
|
597
|
+
else
|
598
|
+
w.monitor if w.autostart?
|
599
|
+
end
|
600
|
+
end
|
601
|
+
loaded_watches = self.pending_watches.map { |w| w.name }
|
602
|
+
self.pending_watches.clear
|
603
|
+
self.pending_watch_states.clear
|
604
|
+
|
605
|
+
self.watches.each do |name, watch|
|
606
|
+
next if loaded_watches.include?(name)
|
607
|
+
|
608
|
+
case action
|
609
|
+
when 'stop'
|
610
|
+
jobs << Thread.new(watch) { |w| w.action(:stop); self.unwatch(w) }
|
611
|
+
unloaded_watches << name
|
612
|
+
when 'remove'
|
613
|
+
jobs << Thread.new(watch) { |w| self.unwatch(w) }
|
614
|
+
unloaded_watches << name
|
615
|
+
when 'leave', '', nil
|
616
|
+
# Do nothing
|
617
|
+
else
|
618
|
+
raise InvalidCommandError, "Unknown action: #{action}"
|
619
|
+
end
|
620
|
+
end
|
621
|
+
|
622
|
+
# Make sure we quit capturing when we're done.
|
623
|
+
LOG.finish_capture
|
624
|
+
rescue Exception => e
|
625
|
+
# Don't ever let running_load take down god.
|
626
|
+
errors << LOG.finish_capture
|
627
|
+
|
628
|
+
unless e.instance_of?(SystemExit)
|
629
|
+
errors << e.message << "\n"
|
630
|
+
errors << e.backtrace.join("\n")
|
631
|
+
end
|
632
|
+
end
|
633
|
+
|
634
|
+
jobs.each { |t| t.join }
|
635
|
+
|
636
|
+
[loaded_watches, errors, unloaded_watches]
|
637
|
+
end
|
638
|
+
|
639
|
+
# Load the given file(s) according to the given glob.
|
640
|
+
#
|
641
|
+
# glob - The glob-enabled String path to load.
|
642
|
+
#
|
643
|
+
# Returns nothing.
|
644
|
+
def self.load(glob)
|
645
|
+
Dir[glob].each do |f|
|
646
|
+
Kernel.load f
|
647
|
+
end
|
648
|
+
end
|
649
|
+
|
650
|
+
# Setup pid file directory and log system.
|
651
|
+
#
|
652
|
+
# Returns nothing.
|
653
|
+
def self.setup
|
654
|
+
if self.pid_file_directory
|
655
|
+
# Pid file dir was specified, ensure it is created and writable.
|
656
|
+
unless File.exist?(self.pid_file_directory)
|
657
|
+
begin
|
658
|
+
FileUtils.mkdir_p(self.pid_file_directory)
|
659
|
+
rescue Errno::EACCES => e
|
660
|
+
abort "Failed to create pid file directory: #{e.message}"
|
661
|
+
end
|
662
|
+
end
|
663
|
+
|
664
|
+
unless File.writable?(self.pid_file_directory)
|
665
|
+
abort "The pid file directory (#{self.pid_file_directory}) is not writable by #{Etc.getlogin}"
|
666
|
+
end
|
667
|
+
else
|
668
|
+
# No pid file dir specified, try defaults.
|
669
|
+
PID_FILE_DIRECTORY_DEFAULTS.each do |idir|
|
670
|
+
dir = File.expand_path(idir)
|
671
|
+
begin
|
672
|
+
FileUtils.mkdir_p(dir)
|
673
|
+
if File.writable?(dir)
|
674
|
+
self.pid_file_directory = dir
|
675
|
+
break
|
676
|
+
end
|
677
|
+
rescue Errno::EACCES => e
|
678
|
+
end
|
679
|
+
end
|
680
|
+
|
681
|
+
unless self.pid_file_directory
|
682
|
+
dirs = PID_FILE_DIRECTORY_DEFAULTS.map { |x| File.expand_path(x) }
|
683
|
+
abort "No pid file directory exists, could be created, or is writable at any of #{dirs.join(', ')}"
|
684
|
+
end
|
685
|
+
end
|
686
|
+
|
687
|
+
if God::Logger.syslog
|
688
|
+
LOG.info("Syslog enabled.")
|
689
|
+
else
|
690
|
+
LOG.info("Syslog disabled.")
|
691
|
+
end
|
692
|
+
|
693
|
+
applog(nil, :info, "Using pid file directory: #{self.pid_file_directory}")
|
694
|
+
end
|
695
|
+
|
696
|
+
# Initialize and startup the machinery that makes god work.
|
697
|
+
#
|
698
|
+
# Returns nothing.
|
699
|
+
def self.start
|
700
|
+
self.internal_init
|
701
|
+
|
702
|
+
# Instantiate server.
|
703
|
+
self.server = Socket.new(self.port, self.socket_user, self.socket_group, self.socket_perms)
|
704
|
+
|
705
|
+
# Start monitoring any watches set to autostart.
|
706
|
+
self.watches.values.each { |w| w.monitor if w.autostart? }
|
707
|
+
|
708
|
+
# Clear pending watches.
|
709
|
+
self.pending_watches.clear
|
710
|
+
|
711
|
+
# Mark as running.
|
712
|
+
self.running = true
|
713
|
+
|
714
|
+
# Don't exit.
|
715
|
+
self.main =
|
716
|
+
Thread.new do
|
717
|
+
loop do
|
718
|
+
sleep 60
|
719
|
+
end
|
720
|
+
end
|
721
|
+
end
|
722
|
+
|
723
|
+
# Prevent god from exiting.
|
724
|
+
#
|
725
|
+
# Returns nothing.
|
726
|
+
def self.join
|
727
|
+
self.main.join if self.main
|
728
|
+
end
|
729
|
+
|
730
|
+
# Returns the version String.
|
731
|
+
def self.version
|
732
|
+
God::VERSION
|
733
|
+
end
|
734
|
+
|
735
|
+
# To be called on program exit to start god.
|
736
|
+
#
|
737
|
+
# Returns nothing.
|
738
|
+
def self.at_exit
|
739
|
+
self.start
|
740
|
+
self.join
|
741
|
+
end
|
742
|
+
|
743
|
+
# private
|
744
|
+
|
745
|
+
# Match a shortened pattern against a list of String candidates.
|
746
|
+
# The pattern is expanded into a regular expression by
|
747
|
+
# inserting .* between each character.
|
748
|
+
#
|
749
|
+
# pattern - The String containing the abbreviation.
|
750
|
+
# list - The Array of Strings to match against.
|
751
|
+
#
|
752
|
+
# Examples
|
753
|
+
#
|
754
|
+
# list = %w{ foo bar bars }
|
755
|
+
# pattern = 'br'
|
756
|
+
# God.pattern_match(list, pattern)
|
757
|
+
# # => ['bar', 'bars']
|
758
|
+
#
|
759
|
+
# Returns the Array of matching name Strings.
|
760
|
+
def self.pattern_match(pattern, list)
|
761
|
+
regex = pattern.split('').join('.*')
|
762
|
+
|
763
|
+
list.select do |item|
|
764
|
+
item =~ Regexp.new(regex)
|
765
|
+
end.sort_by { |x| x.size }
|
766
|
+
end
|
767
|
+
end
|
768
|
+
|
769
|
+
# Runs immediately before the program exits. If $run is true,
|
770
|
+
# start god, if $run is false, exit normally.
|
771
|
+
#
|
772
|
+
# Returns nothing.
|
773
|
+
at_exit do
|
774
|
+
God.at_exit if $run
|
775
|
+
end
|
776
|
+
|
777
|
+
end
|
778
|
+
|
779
|
+
# provide a new name for the parnt project
|
780
|
+
McProc = God
|