god 0.1.0 → 0.2.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/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