samhendley-god 0.7.13

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.
Files changed (115) hide show
  1. data/History.txt +293 -0
  2. data/Manifest.txt +114 -0
  3. data/README.txt +60 -0
  4. data/Rakefile +35 -0
  5. data/bin/god +128 -0
  6. data/examples/events.god +84 -0
  7. data/examples/gravatar.god +54 -0
  8. data/examples/single.god +66 -0
  9. data/ext/god/extconf.rb +55 -0
  10. data/ext/god/kqueue_handler.c +123 -0
  11. data/ext/god/netlink_handler.c +167 -0
  12. data/init/god +42 -0
  13. data/lib/god.rb +667 -0
  14. data/lib/god/behavior.rb +52 -0
  15. data/lib/god/behaviors/clean_pid_file.rb +21 -0
  16. data/lib/god/behaviors/clean_unix_socket.rb +21 -0
  17. data/lib/god/behaviors/notify_when_flapping.rb +51 -0
  18. data/lib/god/cli/command.rb +229 -0
  19. data/lib/god/cli/run.rb +176 -0
  20. data/lib/god/cli/version.rb +23 -0
  21. data/lib/god/condition.rb +96 -0
  22. data/lib/god/conditions/always.rb +23 -0
  23. data/lib/god/conditions/complex.rb +86 -0
  24. data/lib/god/conditions/cpu_usage.rb +80 -0
  25. data/lib/god/conditions/degrading_lambda.rb +52 -0
  26. data/lib/god/conditions/disk_usage.rb +27 -0
  27. data/lib/god/conditions/file_mtime.rb +28 -0
  28. data/lib/god/conditions/flapping.rb +128 -0
  29. data/lib/god/conditions/http_response_code.rb +168 -0
  30. data/lib/god/conditions/lambda.rb +25 -0
  31. data/lib/god/conditions/memory_usage.rb +82 -0
  32. data/lib/god/conditions/process_exits.rb +72 -0
  33. data/lib/god/conditions/process_running.rb +74 -0
  34. data/lib/god/conditions/tries.rb +44 -0
  35. data/lib/god/configurable.rb +57 -0
  36. data/lib/god/contact.rb +106 -0
  37. data/lib/god/contacts/campfire.rb +82 -0
  38. data/lib/god/contacts/email.rb +95 -0
  39. data/lib/god/contacts/jabber.rb +65 -0
  40. data/lib/god/contacts/twitter.rb +39 -0
  41. data/lib/god/contacts/webhook.rb +47 -0
  42. data/lib/god/dependency_graph.rb +41 -0
  43. data/lib/god/diagnostics.rb +37 -0
  44. data/lib/god/driver.rb +206 -0
  45. data/lib/god/errors.rb +24 -0
  46. data/lib/god/event_handler.rb +111 -0
  47. data/lib/god/event_handlers/dummy_handler.rb +13 -0
  48. data/lib/god/event_handlers/kqueue_handler.rb +17 -0
  49. data/lib/god/event_handlers/netlink_handler.rb +13 -0
  50. data/lib/god/logger.rb +120 -0
  51. data/lib/god/metric.rb +59 -0
  52. data/lib/god/process.rb +342 -0
  53. data/lib/god/registry.rb +32 -0
  54. data/lib/god/simple_logger.rb +53 -0
  55. data/lib/god/socket.rb +96 -0
  56. data/lib/god/sugar.rb +47 -0
  57. data/lib/god/system/portable_poller.rb +42 -0
  58. data/lib/god/system/process.rb +42 -0
  59. data/lib/god/system/slash_proc_poller.rb +92 -0
  60. data/lib/god/task.rb +491 -0
  61. data/lib/god/timeline.rb +25 -0
  62. data/lib/god/trigger.rb +43 -0
  63. data/lib/god/watch.rb +184 -0
  64. data/test/configs/child_events/child_events.god +44 -0
  65. data/test/configs/child_events/simple_server.rb +3 -0
  66. data/test/configs/child_polls/child_polls.god +37 -0
  67. data/test/configs/child_polls/simple_server.rb +12 -0
  68. data/test/configs/complex/complex.god +59 -0
  69. data/test/configs/complex/simple_server.rb +3 -0
  70. data/test/configs/contact/contact.god +84 -0
  71. data/test/configs/contact/simple_server.rb +3 -0
  72. data/test/configs/daemon_events/daemon_events.god +37 -0
  73. data/test/configs/daemon_events/simple_server.rb +8 -0
  74. data/test/configs/daemon_events/simple_server_stop.rb +11 -0
  75. data/test/configs/daemon_polls/daemon_polls.god +17 -0
  76. data/test/configs/daemon_polls/simple_server.rb +6 -0
  77. data/test/configs/degrading_lambda/degrading_lambda.god +31 -0
  78. data/test/configs/degrading_lambda/tcp_server.rb +15 -0
  79. data/test/configs/matias/matias.god +50 -0
  80. data/test/configs/real.rb +59 -0
  81. data/test/configs/running_load/running_load.god +16 -0
  82. data/test/configs/stress/simple_server.rb +3 -0
  83. data/test/configs/stress/stress.god +15 -0
  84. data/test/configs/task/logs/.placeholder +0 -0
  85. data/test/configs/task/task.god +26 -0
  86. data/test/configs/test.rb +61 -0
  87. data/test/helper.rb +151 -0
  88. data/test/suite.rb +6 -0
  89. data/test/test_behavior.rb +21 -0
  90. data/test/test_campfire.rb +41 -0
  91. data/test/test_condition.rb +50 -0
  92. data/test/test_conditions_disk_usage.rb +56 -0
  93. data/test/test_conditions_http_response_code.rb +109 -0
  94. data/test/test_conditions_process_running.rb +44 -0
  95. data/test/test_conditions_tries.rb +67 -0
  96. data/test/test_contact.rb +109 -0
  97. data/test/test_dependency_graph.rb +62 -0
  98. data/test/test_driver.rb +11 -0
  99. data/test/test_email.rb +45 -0
  100. data/test/test_event_handler.rb +80 -0
  101. data/test/test_god.rb +598 -0
  102. data/test/test_handlers_kqueue_handler.rb +16 -0
  103. data/test/test_logger.rb +63 -0
  104. data/test/test_metric.rb +72 -0
  105. data/test/test_process.rb +246 -0
  106. data/test/test_registry.rb +15 -0
  107. data/test/test_socket.rb +42 -0
  108. data/test/test_sugar.rb +42 -0
  109. data/test/test_system_portable_poller.rb +17 -0
  110. data/test/test_system_process.rb +30 -0
  111. data/test/test_task.rb +262 -0
  112. data/test/test_timeline.rb +37 -0
  113. data/test/test_trigger.rb +59 -0
  114. data/test/test_watch.rb +279 -0
  115. metadata +193 -0
@@ -0,0 +1,47 @@
1
+ # Configure your watches like this:
2
+ #
3
+ # God.contact(:webhook) do |c|
4
+ # c.name = 'Tester'
5
+ # c.hook_url = 'http://hook/url'
6
+ # end
7
+
8
+ require 'net/http'
9
+ require 'uri'
10
+
11
+ module God
12
+ module Contacts
13
+
14
+ class Webhook < Contact
15
+
16
+ attr_accessor :hook_url
17
+
18
+ def valid?
19
+ valid = true
20
+ end
21
+
22
+ def notify(message, time, priority, category, host)
23
+ begin
24
+ data = {
25
+ :message => message,
26
+ :time => time,
27
+ :priority => priority,
28
+ :category => category,
29
+ :host => host
30
+ }
31
+
32
+ uri = URI.parse(self.hook_url)
33
+ Net::HTTP.post_form(uri, data)
34
+
35
+ self.info = "sent webhook to #{self.hook_url}"
36
+ rescue => e
37
+ puts e.message
38
+ puts e.backtrace.join("\n")
39
+
40
+ self.info = "failed to send webhook to #{self.hook_url}: #{e.message}"
41
+ end
42
+ end
43
+
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,41 @@
1
+ module God
2
+ class DependencyGraph
3
+ attr_accessor :nodes
4
+
5
+ def initialize
6
+ self.nodes = {}
7
+ end
8
+
9
+ def add(a, b)
10
+ node_a = self.nodes[a] || Node.new(a)
11
+ node_b = self.nodes[b] || Node.new(b)
12
+
13
+ node_a.add(node_b)
14
+
15
+ self.nodes[a] ||= node_a
16
+ self.nodes[b] ||= node_b
17
+ end
18
+ end
19
+ end
20
+
21
+ module God
22
+ class DependencyGraph
23
+ class Node
24
+ attr_accessor :name
25
+ attr_accessor :dependencies
26
+
27
+ def initialize(name)
28
+ self.name = name
29
+ self.dependencies = []
30
+ end
31
+
32
+ def add(node)
33
+ self.dependencies << node unless self.dependencies.include?(node)
34
+ end
35
+
36
+ def has_node?(node)
37
+ (self == node) || self.dependencies.any { |x| x.has_node?(node) }
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,37 @@
1
+ def start_dike
2
+ require 'dike'
3
+ Thread.new do
4
+ Dike.logfactory File.join(File.dirname(__FILE__), *%w[.. .. logs])
5
+ loop do
6
+ Dike.finger
7
+ sleep(1)
8
+ end
9
+ end
10
+ end
11
+
12
+ class BleakHouseDiagnostic
13
+ LOG_FILE = File.join(File.dirname(__FILE__), *%w[.. .. logs bleak.log])
14
+
15
+ class << self
16
+ attr_accessor :logger
17
+ end
18
+
19
+ def self.install
20
+ require 'bleak_house'
21
+ self.logger = BleakHouse::Logger.new
22
+ File.delete(LOG_FILE) rescue nil
23
+ end
24
+
25
+ def self.snapshot(name)
26
+ self.logger.snapshot(LOG_FILE, name, false) if self.logger
27
+ end
28
+
29
+ def self.spin(delay = 1)
30
+ Thread.new do
31
+ loop do
32
+ self.snapshot
33
+ sleep(delay)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,206 @@
1
+ module God
2
+ class TimedEvent
3
+ include Comparable
4
+
5
+ attr_accessor :at
6
+
7
+ # Instantiate a new TimedEvent that will be triggered after the specified delay
8
+ # +delay+ is the number of seconds from now at which to trigger
9
+ #
10
+ # Returns TimedEvent
11
+ def initialize(delay = 0)
12
+ self.at = Time.now + delay
13
+ end
14
+
15
+ def due?
16
+ Time.now >= self.at
17
+ end
18
+
19
+ def <=>(other)
20
+ self.at <=> other.at
21
+ end
22
+ end # DriverEvent
23
+
24
+ class DriverEvent < TimedEvent
25
+ attr_accessor :condition, :task
26
+
27
+ def initialize(delay, task, condition)
28
+ super delay
29
+ self.task = task
30
+ self.condition = condition
31
+ end
32
+
33
+ def handle_event
34
+ @task.handle_poll(@condition)
35
+ end
36
+ end # DriverEvent
37
+
38
+ class DriverOperation < TimedEvent
39
+ attr_accessor :task, :name, :args
40
+
41
+ def initialize(task, name, args)
42
+ super(0)
43
+ self.task = task
44
+ self.name = name
45
+ self.args = args
46
+ end
47
+
48
+ # Handle the next queued operation that was issued asynchronously
49
+ #
50
+ # Returns nothing
51
+ def handle_event
52
+ @task.send(@name, *@args)
53
+ end
54
+ end
55
+
56
+ class DriverEventQueue
57
+ def initialize
58
+ @shutdown = false
59
+ @waiting = []
60
+ @events = []
61
+ @waiting.taint
62
+ @events.taint
63
+ self.taint
64
+ end
65
+
66
+ #
67
+ # Wake any sleeping threads after setting the sentinel
68
+ #
69
+ def shutdown
70
+ @shutdown = true
71
+ begin
72
+ Thread.critical = true
73
+ @waiting.each do |t|
74
+ t.run
75
+ end
76
+ ensure
77
+ Thread.critical = false
78
+ end
79
+ end
80
+
81
+ #
82
+ # Sleep until the queue has something due
83
+ #
84
+ def pop
85
+ begin
86
+ while (Thread.critical = true; @events.empty? or !@events.first.due?)
87
+ @waiting.push Thread.current
88
+ if @events.empty?
89
+ raise ThreadError, "queue empty" if @shutdown
90
+ Thread.stop
91
+ else
92
+ Thread.critical = false
93
+ sleep @events.first.at - Time.now
94
+ Thread.critical = true
95
+ end
96
+ end
97
+ @events.shift
98
+ ensure
99
+ Thread.critical = false
100
+ end
101
+ end
102
+
103
+ alias shift pop
104
+ alias deq pop
105
+
106
+ #
107
+ # Add an event to the queue, wake any waiters if what we added needs to
108
+ # happen sooner than the next pending event
109
+ #
110
+ def push(event)
111
+ Thread.critical = true
112
+ @events << event
113
+ @events.sort!
114
+ begin
115
+ t = @waiting.shift if @events.first == event
116
+ t.wakeup if t
117
+ rescue ThreadError
118
+ retry
119
+ ensure
120
+ Thread.critical = false
121
+ end
122
+ begin
123
+ t.run if t
124
+ rescue ThreadError
125
+ end
126
+ end
127
+
128
+ alias << push
129
+ alias enq push
130
+
131
+ def empty?
132
+ @que.empty?
133
+ end
134
+
135
+ def clear
136
+ @events.clear
137
+ end
138
+
139
+ def length
140
+ @events.length
141
+ end
142
+
143
+ alias size length
144
+
145
+ def num_waiting
146
+ @waiting.size
147
+ end
148
+ end
149
+
150
+
151
+ class Driver
152
+ attr_reader :thread
153
+
154
+ # Instantiate a new Driver and start the scheduler loop to handle events
155
+ # +task+ is the Task this Driver belongs to
156
+ #
157
+ # Returns Driver
158
+ def initialize(task)
159
+ @task = task
160
+ @events = God::DriverEventQueue.new
161
+
162
+ @thread = Thread.new do
163
+ loop do
164
+ begin
165
+ @events.pop.handle_event
166
+ rescue ThreadError => e
167
+ # queue is empty
168
+ break
169
+ rescue Exception => e
170
+ message = format("Unhandled exception in driver loop - (%s): %s\n%s",
171
+ e.class, e.message, e.backtrace.join("\n"))
172
+ applog(nil, :fatal, message)
173
+ end
174
+ end
175
+ end
176
+ end
177
+
178
+ # Clear all events for this Driver
179
+ #
180
+ # Returns nothing
181
+ def clear_events
182
+ @events.clear
183
+ end
184
+
185
+ # Queue an asynchronous message
186
+ # +name+ is the Symbol name of the operation
187
+ # +args+ is an optional Array of arguments
188
+ #
189
+ # Returns nothing
190
+ def message(name, args = [])
191
+ @events.push(DriverOperation.new(@task, name, args))
192
+ end
193
+
194
+ # Create and schedule a new DriverEvent
195
+ # +condition+ is the Condition
196
+ # +delay+ is the number of seconds to delay (default: interval defined in condition)
197
+ #
198
+ # Returns nothing
199
+ def schedule(condition, delay = condition.interval)
200
+ applog(nil, :debug, "driver schedule #{condition} in #{delay} seconds")
201
+
202
+ @events.push(DriverEvent.new(delay, @task, condition))
203
+ end
204
+ end # Driver
205
+
206
+ end # God
@@ -0,0 +1,24 @@
1
+ module God
2
+
3
+ class AbstractMethodNotOverriddenError < StandardError
4
+ end
5
+
6
+ class NoSuchWatchError < StandardError
7
+ end
8
+
9
+ class NoSuchConditionError < StandardError
10
+ end
11
+
12
+ class NoSuchBehaviorError < StandardError
13
+ end
14
+
15
+ class NoSuchContactError < StandardError
16
+ end
17
+
18
+ class InvalidCommandError < StandardError
19
+ end
20
+
21
+ class EventRegistrationFailedError < StandardError
22
+ end
23
+
24
+ end
@@ -0,0 +1,111 @@
1
+ module God
2
+ class EventHandler
3
+ @@actions = {}
4
+ @@handler = nil
5
+ @@loaded = false
6
+
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
33
+ end
34
+
35
+ def self.register(pid, event, &block)
36
+ @@actions[pid] ||= {}
37
+ @@actions[pid][event] = block
38
+ @@handler.register_process(pid, @@actions[pid].keys)
39
+ end
40
+
41
+ def self.deregister(pid, event=nil)
42
+ if watching_pid? pid
43
+ running = ::Process.kill(0, pid.to_i) rescue false
44
+ if event.nil?
45
+ @@actions.delete(pid)
46
+ @@handler.register_process(pid, []) if running
47
+ else
48
+ @@actions[pid].delete(event)
49
+ @@handler.register_process(pid, @@actions[pid].keys) if running
50
+ end
51
+ end
52
+ end
53
+
54
+ def self.call(pid, event, extra_data = {})
55
+ @@actions[pid][event].call(extra_data) if watching_pid?(pid) && @@actions[pid][event]
56
+ end
57
+
58
+ def self.watching_pid?(pid)
59
+ @@actions[pid]
60
+ end
61
+
62
+ def self.start
63
+ Thread.new do
64
+ loop do
65
+ begin
66
+ @@handler.handle_events
67
+ rescue Exception => e
68
+ message = format("Unhandled exception (%s): %s\n%s",
69
+ e.class, e.message, e.backtrace.join("\n"))
70
+ applog(nil, :fatal, message)
71
+ end
72
+ end
73
+ end
74
+
75
+ # do a real test to make sure events are working properly
76
+ @@loaded = self.operational?
77
+ end
78
+
79
+ def self.operational?
80
+ com = [false]
81
+
82
+ Thread.new do
83
+ begin
84
+ event_system = God::EventHandler.event_system
85
+
86
+ pid = fork do
87
+ loop { sleep(1) }
88
+ end
89
+
90
+ self.register(pid, :proc_exit) do
91
+ com[0] = true
92
+ end
93
+
94
+ ::Process.kill('KILL', pid)
95
+
96
+ sleep(0.1)
97
+
98
+ self.deregister(pid, :proc_exit) rescue nil
99
+ rescue => e
100
+ puts e.message
101
+ puts e.backtrace.join("\n")
102
+ end
103
+ end.join
104
+
105
+ sleep(0.1)
106
+
107
+ com.first
108
+ end
109
+
110
+ end
111
+ end