god 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/god.rb CHANGED
@@ -11,7 +11,8 @@ require 'god/behaviors/clean_pid_file'
11
11
 
12
12
  require 'god/condition'
13
13
  require 'god/conditions/timeline'
14
- require 'god/conditions/process_not_running'
14
+ require 'god/conditions/process_running'
15
+ require 'god/conditions/process_exits'
15
16
  require 'god/conditions/memory_usage'
16
17
  require 'god/conditions/cpu_usage'
17
18
  require 'god/conditions/always'
@@ -19,17 +20,49 @@ require 'god/conditions/always'
19
20
  require 'god/reporter'
20
21
  require 'god/server'
21
22
  require 'god/timer'
23
+ require 'god/hub'
24
+
25
+ require 'god/metric'
22
26
 
23
27
  require 'god/watch'
24
28
  require 'god/meddle'
25
29
 
30
+ require 'god/event_handler'
31
+
32
+ Thread.abort_on_exception = true
33
+
26
34
  module God
27
- VERSION = '0.1.0'
35
+ VERSION = '0.2.0'
28
36
 
29
- def self.meddle(options = {})
37
+ case RUBY_PLATFORM
38
+ when /darwin/i, /bsd/i
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"
48
+ end
49
+
50
+ def self.meddle(options = {})
30
51
  m = Meddle.new(options)
52
+
53
+ # yeild to the config file
31
54
  yield m
55
+
56
+ # start event handler system
57
+ EventHandler.start
58
+
59
+ # start the timer system
60
+ Timer.get
61
+
62
+ # start monitoring each watch
32
63
  m.monitor
33
- m.timer.join
64
+
65
+ # join the timer thread to we don't exit
66
+ Timer.get.join
34
67
  end
35
68
  end
@@ -2,8 +2,16 @@ module God
2
2
  module Conditions
3
3
 
4
4
  class Always < PollCondition
5
+ attr_accessor :what
6
+
7
+ def valid?
8
+ valid = true
9
+ valid &= complain("You must specify the 'what' attribute for :always") if self.what.nil?
10
+ valid
11
+ end
12
+
5
13
  def test
6
- false
14
+ @what
7
15
  end
8
16
  end
9
17
 
@@ -31,10 +31,10 @@ module God
31
31
  pid = File.open(self.pid_file).read.strip
32
32
  process = System::Process.new(pid)
33
33
  @timeline.push(process.percent_cpu)
34
- if @timeline.select { |x| x > self.above }.size < self.times.first
34
+ if @timeline.select { |x| x > self.above }.size >= self.times.first
35
+ @timeline.clear
35
36
  return true
36
37
  else
37
- @timeline.clear
38
38
  return false
39
39
  end
40
40
  end
@@ -31,10 +31,10 @@ module God
31
31
  pid = File.open(self.pid_file).read.strip
32
32
  process = System::Process.new(pid)
33
33
  @timeline.push(process.memory)
34
- if @timeline.select { |x| x > self.above }.size < self.times.first
34
+ if @timeline.select { |x| x > self.above }.size >= self.times.first
35
+ @timeline.clear
35
36
  return true
36
37
  else
37
- @timeline.clear
38
38
  return false
39
39
  end
40
40
  end
@@ -0,0 +1,28 @@
1
+ module God
2
+ module Conditions
3
+
4
+ class ProcessExits < EventCondition
5
+ attr_accessor :pid_file
6
+
7
+ def valid?
8
+ valid = true
9
+ valid &= complain("You must specify the 'pid_file' attribute for :process_exits") if self.pid_file.nil?
10
+ valid
11
+ end
12
+
13
+ def register
14
+ pid = File.open(self.pid_file).read.strip.to_i
15
+
16
+ EventHandler.register(pid, :proc_exit) {
17
+ Hub.trigger(self)
18
+ }
19
+ end
20
+
21
+ def deregister
22
+ pid = File.open(self.pid_file).read.strip.to_i
23
+ EventHandler.deregister(pid, :proc_exit)
24
+ end
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,25 @@
1
+ module God
2
+ module Conditions
3
+
4
+ class ProcessRunning < PollCondition
5
+ attr_accessor :pid_file, :running
6
+
7
+ def valid?
8
+ valid = true
9
+ valid &= complain("You must specify the 'pid_file' attribute for :process_running") if self.pid_file.nil?
10
+ valid &= complain("You must specify the 'running' attribute for :process_running") if self.running.nil?
11
+ valid
12
+ end
13
+
14
+ def test
15
+ return !self.running unless File.exist?(self.pid_file)
16
+
17
+ pid = File.open(self.pid_file).read.strip
18
+ active = System::Process.new(pid).exists?
19
+
20
+ (self.running && active) || (!self.running && !active)
21
+ end
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,45 @@
1
+ module God
2
+ class EventHandler
3
+ @@actions = {}
4
+ @@handler = nil
5
+
6
+ def self.handler=(value)
7
+ @@handler = value
8
+ end
9
+
10
+ def self.register(pid, event, &block)
11
+ @@actions[pid] ||= {}
12
+ @@actions[pid][event] = block
13
+ @@handler.register_process(pid, @@actions[pid].keys)
14
+ end
15
+
16
+ def self.deregister(pid, event=nil)
17
+ if watching_pid? pid
18
+ if event.nil?
19
+ @@actions.delete(pid)
20
+ @@handler.register_process(pid, []) if system("kill -0 #{pid} &> /dev/null")
21
+ else
22
+ @@actions[pid].delete(event)
23
+ @@handler.register_process(pid, @@actions[pid].keys) if system("kill -0 #{pid} &> /dev/null")
24
+ end
25
+ end
26
+ end
27
+
28
+ def self.call(pid, event)
29
+ @@actions[pid][event].call
30
+ end
31
+
32
+ def self.watching_pid?(pid)
33
+ @@actions[pid]
34
+ end
35
+
36
+ def self.start
37
+ Thread.new do
38
+ loop do
39
+ @@handler.handle_events
40
+ end
41
+ end
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,15 @@
1
+ require 'kqueue_handler_ext'
2
+
3
+ module God
4
+ class KQueueHandler
5
+ def self.register_process(pid, events)
6
+ monitor_process(pid, events_mask(events))
7
+ end
8
+
9
+ def self.events_mask(events)
10
+ events.inject(0) do |mask, event|
11
+ mask |= event_mask(event)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ require 'netlink_handler_ext'
2
+
3
+ module God
4
+ class NetlinkHandler
5
+ def self.register_process(pid, events)
6
+ # netlink doesn't need to do this
7
+ # it just reads from the eventhandler actions to see if the pid
8
+ # matches the list we're looking for -- Kev
9
+ end
10
+ end
11
+ end
data/lib/god/hub.rb ADDED
@@ -0,0 +1,84 @@
1
+ module God
2
+
3
+ class Hub
4
+ # directory to hold conditions and their corresponding metric
5
+ # key: condition
6
+ # val: metric
7
+ @@directory = {}
8
+
9
+ def self.attach(condition, metric)
10
+ # add the condition to the directory
11
+ @@directory[condition] = metric
12
+
13
+ # schedule poll condition
14
+ # register event condition
15
+ if condition.kind_of?(PollCondition)
16
+ Timer.get.schedule(condition, 0)
17
+ else
18
+ condition.register
19
+ end
20
+ end
21
+
22
+ def self.detach(condition)
23
+ # remove the condition from the directory
24
+ @@directory.delete(condition)
25
+
26
+ # unschedule any pending polls
27
+ Timer.get.unschedule(condition)
28
+
29
+ # deregister event condition
30
+ if condition.kind_of?(EventCondition)
31
+ condition.deregister
32
+ end
33
+ end
34
+
35
+ def self.trigger(condition)
36
+ if condition.kind_of?(PollCondition)
37
+ self.handle_poll(condition)
38
+ elsif condition.kind_of?(EventCondition)
39
+ self.handle_event(condition)
40
+ end
41
+ end
42
+
43
+ def self.handle_poll(condition)
44
+ Thread.new do
45
+ metric = @@directory[condition]
46
+ watch = metric.watch
47
+
48
+ watch.mutex.synchronize do
49
+ result = condition.test
50
+
51
+ puts watch.name + ' ' + condition.class.name + " [#{result}]"
52
+
53
+ condition.after
54
+
55
+ p metric.destination
56
+
57
+ if dest = metric.destination[result]
58
+ watch.move(dest)
59
+ else
60
+ # reschedule
61
+ Timer.get.schedule(condition)
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ def self.handle_event(condition)
68
+ Thread.new do
69
+ metric = @@directory[condition]
70
+ watch = metric.watch
71
+
72
+ watch.mutex.synchronize do
73
+ puts watch.name + ' ' + condition.class.name + " [true]"
74
+
75
+ p metric.destination
76
+
77
+ dest = metric.destination[true]
78
+ watch.move(dest)
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ end
data/lib/god/meddle.rb CHANGED
@@ -5,13 +5,12 @@ module God
5
5
  attr_accessor :server
6
6
 
7
7
  # api
8
- attr_accessor :watches, :timer
8
+ attr_accessor :watches
9
9
 
10
10
  # Create a new instance that is ready for use by a configuration file
11
11
  def initialize(options = {})
12
12
  self.watches = []
13
13
  self.server = Server.new(self, options[:host], options[:port])
14
- self.timer = Timer.new
15
14
  end
16
15
 
17
16
  # Instantiate a new, empty Watch object and pass it to the mandatory
@@ -34,21 +33,6 @@ module God
34
33
  def monitor
35
34
  @watches.each { |w| w.monitor }
36
35
  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
36
  end
53
37
 
54
38
  end
data/lib/god/metric.rb ADDED
@@ -0,0 +1,60 @@
1
+ module God
2
+
3
+ class Metric < Base
4
+ attr_accessor :watch, :destination, :conditions
5
+
6
+ def initialize(watch, destination)
7
+ self.watch = watch
8
+ self.destination = destination
9
+ self.conditions = []
10
+ end
11
+
12
+ # Instantiate a Condition of type +kind+ and pass it into the optional
13
+ # block. Attributes of the condition must be set in the config file
14
+ def condition(kind)
15
+ # create the condition
16
+ begin
17
+ c = Condition.generate(kind)
18
+ rescue NoSuchConditionError => e
19
+ abort e.message
20
+ end
21
+
22
+ # send to block so config can set attributes
23
+ yield(c) if block_given?
24
+
25
+ # call prepare on the condition
26
+ c.prepare
27
+
28
+ # abort if the Condition is invalid, the Condition will have printed
29
+ # out its own error messages by now
30
+ unless c.valid?
31
+ abort
32
+ end
33
+
34
+ # inherit interval from meddle if no poll condition specific interval was set
35
+ if c.kind_of?(PollCondition) && !c.interval
36
+ if self.watch.interval
37
+ c.interval = self.watch.interval
38
+ else
39
+ abort "No interval set for Condition '#{c.class.name}' in Watch '#{self.watch.name}', and no default Watch interval from which to inherit"
40
+ end
41
+ end
42
+
43
+ # remember
44
+ self.conditions << c
45
+ end
46
+
47
+ def enable
48
+ self.conditions.each do |c|
49
+ Hub.attach(c, self)
50
+ end
51
+ end
52
+
53
+ def disable
54
+ self.conditions.each do |c|
55
+ Hub.detach(c)
56
+ end
57
+ end
58
+ end
59
+
60
+ end
data/lib/god/timer.rb CHANGED
@@ -1,13 +1,11 @@
1
1
  module God
2
2
 
3
3
  class TimerEvent
4
- attr_accessor :watch, :condition, :command, :at
4
+ attr_accessor :condition, :at
5
5
 
6
- def initialize(watch, condition, command)
7
- self.watch = watch
6
+ def initialize(condition, interval)
8
7
  self.condition = condition
9
- self.command = command
10
- self.at = Time.now.to_i + condition.interval
8
+ self.at = Time.now.to_i + interval
11
9
  end
12
10
  end
13
11
 
@@ -16,6 +14,16 @@ module God
16
14
 
17
15
  attr_reader :events
18
16
 
17
+ @@timer = nil
18
+
19
+ def self.get
20
+ @@timer ||= Timer.new
21
+ end
22
+
23
+ def self.reset
24
+ @@timer = nil
25
+ end
26
+
19
27
  # Start the scheduler loop to handle events
20
28
  def initialize
21
29
  @events = []
@@ -42,31 +50,18 @@ module God
42
50
  end
43
51
 
44
52
  # Create and register a new TimerEvent with the given parameters
45
- def register(watch, condition, command)
46
- @events << TimerEvent.new(watch, condition, command)
53
+ def schedule(condition, interval = condition.interval)
54
+ @events << TimerEvent.new(condition, interval)
47
55
  @events.sort! { |x, y| x.at <=> y.at }
48
56
  end
49
57
 
58
+ # Remove any TimerEvents for the given condition
59
+ def unschedule(condition)
60
+ @events.reject! { |x| x.condition == condition }
61
+ end
62
+
50
63
  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
64
+ Hub.trigger(event.condition)
70
65
  end
71
66
 
72
67
  # Join the timer thread