god 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,44 @@
1
+ module God
2
+ module Conditions
3
+
4
+ class CpuUsage < PollCondition
5
+ attr_accessor :pid_file, :above, :times
6
+
7
+ def initialize
8
+ super
9
+ self.above = nil
10
+ self.times = [1, 1]
11
+ end
12
+
13
+ def prepare
14
+ if self.times.kind_of?(Integer)
15
+ self.times = [self.times, self.times]
16
+ end
17
+
18
+ @timeline = Timeline.new(self.times[1])
19
+ end
20
+
21
+ def valid?
22
+ valid = true
23
+ valid &= complain("You must specify the 'pid_file' attribute for :memory_usage") if self.pid_file.nil?
24
+ valid &= complain("You must specify the 'above' attribute for :memory_usage") if self.above.nil?
25
+ valid
26
+ end
27
+
28
+ def test
29
+ return false unless File.exist?(self.pid_file)
30
+
31
+ pid = File.open(self.pid_file).read.strip
32
+ process = System::Process.new(pid)
33
+ @timeline.push(process.percent_cpu)
34
+ if @timeline.select { |x| x > self.above }.size < self.times.first
35
+ return true
36
+ else
37
+ @timeline.clear
38
+ return false
39
+ end
40
+ end
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,44 @@
1
+ module God
2
+ module Conditions
3
+
4
+ class MemoryUsage < PollCondition
5
+ attr_accessor :pid_file, :above, :times
6
+
7
+ def initialize
8
+ super
9
+ self.above = nil
10
+ self.times = [1, 1]
11
+ end
12
+
13
+ def prepare
14
+ if self.times.kind_of?(Integer)
15
+ self.times = [self.times, self.times]
16
+ end
17
+
18
+ @timeline = Timeline.new(self.times[1])
19
+ end
20
+
21
+ def valid?
22
+ valid = true
23
+ valid &= complain("You must specify the 'pid_file' attribute for :memory_usage") if self.pid_file.nil?
24
+ valid &= complain("You must specify the 'above' attribute for :memory_usage") if self.above.nil?
25
+ valid
26
+ end
27
+
28
+ def test
29
+ return false unless File.exist?(self.pid_file)
30
+
31
+ pid = File.open(self.pid_file).read.strip
32
+ process = System::Process.new(pid)
33
+ @timeline.push(process.memory)
34
+ if @timeline.select { |x| x > self.above }.size < self.times.first
35
+ return true
36
+ else
37
+ @timeline.clear
38
+ return false
39
+ end
40
+ end
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,22 @@
1
+ module God
2
+ module Conditions
3
+
4
+ class ProcessNotRunning < PollCondition
5
+ attr_accessor :pid_file
6
+
7
+ def valid?
8
+ valid = true
9
+ valid &= complain("You must specify the 'pid_file' attribute for :process_not_running") if self.pid_file.nil?
10
+ valid
11
+ end
12
+
13
+ def test
14
+ return false unless File.exist?(self.pid_file)
15
+
16
+ pid = File.open(self.pid_file).read.strip
17
+ System::Process.new(pid).exists?
18
+ end
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,17 @@
1
+ module God
2
+ module Conditions
3
+
4
+ class Timeline < Array
5
+ def initialize(max_size)
6
+ super()
7
+ @max_size = max_size
8
+ end
9
+
10
+ def push(val)
11
+ unshift(val)
12
+ pop if size > @max_size
13
+ end
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,12 @@
1
+ module God
2
+
3
+ class AbstractMethodNotOverriddenError < StandardError
4
+ end
5
+
6
+ class NoSuchConditionError < StandardError
7
+ end
8
+
9
+ class NoSuchBehaviorError < StandardError
10
+ end
11
+
12
+ end
@@ -0,0 +1,54 @@
1
+ module God
2
+
3
+ class Meddle < Base
4
+ # drb
5
+ attr_accessor :server
6
+
7
+ # api
8
+ attr_accessor :watches, :timer
9
+
10
+ # Create a new instance that is ready for use by a configuration file
11
+ def initialize(options = {})
12
+ self.watches = []
13
+ self.server = Server.new(self, options[:host], options[:port])
14
+ self.timer = Timer.new
15
+ end
16
+
17
+ # Instantiate a new, empty Watch object and pass it to the mandatory
18
+ # block. The attributes of the watch will be set by the configuration
19
+ # file.
20
+ def watch
21
+ w = Watch.new(self)
22
+ yield(w)
23
+
24
+ # ensure the new watch has a unique name
25
+ unless @watches.select { |x| x.name == w.name }.empty?
26
+ abort "Duplicate Watch with name '#{w.name}'"
27
+ end
28
+
29
+ # add to list of watches
30
+ @watches << w
31
+ end
32
+
33
+ # Schedule all poll conditions and register all condition events
34
+ def monitor
35
+ @watches.each { |w| w.monitor }
36
+ 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
+ end
53
+
54
+ end
@@ -0,0 +1,25 @@
1
+ require 'drb'
2
+
3
+ module God
4
+
5
+ class Reporter
6
+ def initialize(host = nil, port = nil)
7
+ @host = host
8
+ @port = port || 7777
9
+ @service = nil
10
+ end
11
+
12
+ def method_missing(*args, &block)
13
+ service.send(*args, &block)
14
+ end
15
+
16
+ private
17
+
18
+ def service
19
+ return @service if @service
20
+ DRb.start_service
21
+ @service = DRbObject.new(nil, "druby://#{@host}:#{@port}")
22
+ end
23
+ end
24
+
25
+ end
@@ -0,0 +1,28 @@
1
+ require 'drb'
2
+
3
+ # The God::Server oversees the DRb server which dishes out info on this God daemon.
4
+
5
+ module God
6
+
7
+ class Server
8
+ attr_reader :host, :port
9
+
10
+ def initialize(meddle = nil, host = nil, port = nil)
11
+ @meddle = meddle
12
+ @host = host
13
+ @port = port || 7777
14
+ start
15
+ end
16
+
17
+ def method_missing(*args, &block)
18
+ @meddle.send(*args, &block)
19
+ end
20
+
21
+ private
22
+
23
+ def start
24
+ @drb ||= DRb.start_service("druby://#{@host}:#{@port}", self)
25
+ end
26
+ end
27
+
28
+ end
@@ -0,0 +1,56 @@
1
+ module God
2
+ module System
3
+
4
+ class Process
5
+ def initialize(pid)
6
+ @pid = pid.to_i
7
+ end
8
+
9
+ # Return true if this process is running, false otherwise
10
+ def exists?
11
+ cmd_name = RUBY_PLATFORM =~ /solaris/i ? "args" : "command"
12
+ !ps_string(cmd_name).empty?
13
+ end
14
+
15
+ # Memory usage in kilobytes (resident set size)
16
+ def memory
17
+ ps_int('rss')
18
+ end
19
+
20
+ # Percentage memory usage
21
+ def percent_memory
22
+ ps_float('%mem')
23
+ end
24
+
25
+ # Percentage CPU usage
26
+ def percent_cpu
27
+ ps_float('%cpu')
28
+ end
29
+
30
+ # Seconds of CPU time (accumulated cpu time, user + system)
31
+ def cpu_time
32
+ time_string_to_seconds(ps_string('time'))
33
+ end
34
+
35
+ private
36
+
37
+ def ps_int(keyword)
38
+ `ps -o #{keyword}= -p #{@pid}`.to_i
39
+ end
40
+
41
+ def ps_float(keyword)
42
+ `ps -o #{keyword}= -p #{@pid}`.to_f
43
+ end
44
+
45
+ def ps_string(keyword)
46
+ `ps -o #{keyword}= -p #{@pid}`.strip
47
+ end
48
+
49
+ def time_string_to_seconds(text)
50
+ _, minutes, seconds, useconds = *text.match(/(\d+):(\d{2}).(\d{2})/)
51
+ (minutes.to_i * 60) + seconds.to_i
52
+ end
53
+ end
54
+
55
+ end
56
+ end
@@ -0,0 +1,78 @@
1
+ module God
2
+
3
+ class TimerEvent
4
+ attr_accessor :watch, :condition, :command, :at
5
+
6
+ def initialize(watch, condition, command)
7
+ self.watch = watch
8
+ self.condition = condition
9
+ self.command = command
10
+ self.at = Time.now.to_i + condition.interval
11
+ end
12
+ end
13
+
14
+ class Timer < Base
15
+ INTERVAL = 0.25
16
+
17
+ attr_reader :events
18
+
19
+ # Start the scheduler loop to handle events
20
+ def initialize
21
+ @events = []
22
+
23
+ @timer = Thread.new do
24
+ loop do
25
+ # get the current time
26
+ t = Time.now.to_i
27
+
28
+ # iterate over each event and trigger any that are due
29
+ @events.each do |event|
30
+ if t >= event.at
31
+ self.trigger(event)
32
+ @events.delete(event)
33
+ else
34
+ break
35
+ end
36
+ end
37
+
38
+ # sleep until next check
39
+ sleep INTERVAL
40
+ end
41
+ end
42
+ end
43
+
44
+ # Create and register a new TimerEvent with the given parameters
45
+ def register(watch, condition, command)
46
+ @events << TimerEvent.new(watch, condition, command)
47
+ @events.sort! { |x, y| x.at <=> y.at }
48
+ end
49
+
50
+ def trigger(event)
51
+ timer = self
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
70
+ end
71
+
72
+ # Join the timer thread
73
+ def join
74
+ @timer.join
75
+ end
76
+ end
77
+
78
+ end
@@ -0,0 +1,151 @@
1
+ module God
2
+
3
+ class Watch < Base
4
+ # config
5
+ attr_accessor :name, :start, :stop, :restart, :interval, :grace
6
+
7
+ # api
8
+ attr_accessor :behaviors, :conditions
9
+
10
+ # internal
11
+ attr_accessor :mutex
12
+
13
+ #
14
+ def initialize(meddle)
15
+ @meddle = meddle
16
+
17
+ # no grace period by default
18
+ self.grace = 0
19
+
20
+ # keep track of which action each condition belongs to
21
+ @action = nil
22
+
23
+ self.behaviors = []
24
+
25
+ # the list of conditions for each action
26
+ self.conditions = {:start => [],
27
+ :restart => []}
28
+
29
+ # mutex
30
+ self.mutex = Mutex.new
31
+ end
32
+
33
+ def behavior(kind)
34
+ # create the behavior
35
+ begin
36
+ b = Behavior.generate(kind)
37
+ rescue NoSuchBehaviorError => e
38
+ abort e.message
39
+ end
40
+
41
+ # send to block so config can set attributes
42
+ yield(b) if block_given?
43
+
44
+ # abort if the Behavior is invalid, the Behavior will have printed
45
+ # out its own error messages by now
46
+ abort unless b.valid?
47
+
48
+ self.behaviors << b
49
+ end
50
+
51
+ def start_if
52
+ @action = :start
53
+ yield(self)
54
+ @action = nil
55
+ end
56
+
57
+ def restart_if
58
+ @action = :restart
59
+ yield(self)
60
+ @action = nil
61
+ end
62
+
63
+ # Instantiate a Condition of type +kind+ and pass it into the optional
64
+ # block. Attributes of the condition must be set in the config file
65
+ def condition(kind)
66
+ # must be in a _if block
67
+ unless @action
68
+ abort "Watch#condition can only be called from inside a start_if block"
69
+ end
70
+
71
+ # create the condition
72
+ begin
73
+ c = Condition.generate(kind)
74
+ rescue NoSuchConditionError => e
75
+ abort e.message
76
+ end
77
+
78
+ # send to block so config can set attributes
79
+ yield(c) if block_given?
80
+
81
+ # call prepare on the condition
82
+ c.prepare
83
+
84
+ # abort if the Condition is invalid, the Condition will have printed
85
+ # out its own error messages by now
86
+ unless c.valid?
87
+ abort
88
+ end
89
+
90
+ # inherit interval from meddle if no poll condition specific interval was set
91
+ if c.kind_of?(PollCondition) && !c.interval
92
+ if self.interval
93
+ c.interval = self.interval
94
+ else
95
+ abort "No interval set for Condition '#{c.class.name}' in Watch '#{self.name}', and no default Watch interval from which to inherit"
96
+ end
97
+ end
98
+
99
+ self.conditions[@action] << c
100
+ end
101
+
102
+ # Schedule all poll conditions and register all condition events
103
+ def monitor
104
+ [:start, :restart].each do |cmd|
105
+ self.conditions[cmd].each do |c|
106
+ @meddle.timer.register(self, c, cmd) if c.kind_of?(PollCondition)
107
+ end
108
+ end
109
+ end
110
+
111
+ def action(a, c)
112
+ case a
113
+ when :start
114
+ puts self.start
115
+ call_action(c, :start, self.start)
116
+ sleep(self.grace)
117
+ when :restart
118
+ if self.restart
119
+ puts self.restart
120
+ call_action(c, :restart, self.restart)
121
+ else
122
+ action(:stop, c)
123
+ action(:start, c)
124
+ end
125
+ sleep(self.grace)
126
+ when :stop
127
+ puts self.stop
128
+ call_action(c, :stop, self.stop)
129
+ sleep(self.grace)
130
+ end
131
+ end
132
+
133
+ def call_action(condition, action, command)
134
+ # before
135
+ (self.behaviors + [condition]).each { |b| b.send("before_#{action}") }
136
+
137
+ # action
138
+ if command.kind_of?(String)
139
+ # string command
140
+ system(command)
141
+ else
142
+ # lambda command
143
+ command.call
144
+ end
145
+
146
+ # after
147
+ (self.behaviors + [condition]).each { |b| b.send("after_#{action}") }
148
+ end
149
+ end
150
+
151
+ end