dosire-god 0.7.9

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 (108) hide show
  1. data/History.txt +261 -0
  2. data/Manifest.txt +107 -0
  3. data/README.txt +59 -0
  4. data/Rakefile +35 -0
  5. data/bin/god +127 -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/behavior.rb +52 -0
  14. data/lib/god/behaviors/clean_pid_file.rb +21 -0
  15. data/lib/god/behaviors/clean_unix_socket.rb +21 -0
  16. data/lib/god/behaviors/notify_when_flapping.rb +51 -0
  17. data/lib/god/cli/command.rb +206 -0
  18. data/lib/god/cli/run.rb +177 -0
  19. data/lib/god/cli/version.rb +23 -0
  20. data/lib/god/condition.rb +96 -0
  21. data/lib/god/conditions/always.rb +23 -0
  22. data/lib/god/conditions/complex.rb +86 -0
  23. data/lib/god/conditions/cpu_usage.rb +80 -0
  24. data/lib/god/conditions/degrading_lambda.rb +52 -0
  25. data/lib/god/conditions/disk_usage.rb +27 -0
  26. data/lib/god/conditions/flapping.rb +128 -0
  27. data/lib/god/conditions/http_response_code.rb +168 -0
  28. data/lib/god/conditions/lambda.rb +25 -0
  29. data/lib/god/conditions/memory_usage.rb +82 -0
  30. data/lib/god/conditions/process_exits.rb +72 -0
  31. data/lib/god/conditions/process_running.rb +74 -0
  32. data/lib/god/conditions/tries.rb +44 -0
  33. data/lib/god/configurable.rb +57 -0
  34. data/lib/god/contact.rb +106 -0
  35. data/lib/god/contacts/email.rb +95 -0
  36. data/lib/god/dependency_graph.rb +41 -0
  37. data/lib/god/diagnostics.rb +37 -0
  38. data/lib/god/driver.rb +206 -0
  39. data/lib/god/errors.rb +24 -0
  40. data/lib/god/event_handler.rb +111 -0
  41. data/lib/god/event_handlers/dummy_handler.rb +13 -0
  42. data/lib/god/event_handlers/kqueue_handler.rb +17 -0
  43. data/lib/god/event_handlers/netlink_handler.rb +13 -0
  44. data/lib/god/logger.rb +120 -0
  45. data/lib/god/metric.rb +59 -0
  46. data/lib/god/process.rb +327 -0
  47. data/lib/god/registry.rb +32 -0
  48. data/lib/god/simple_logger.rb +53 -0
  49. data/lib/god/socket.rb +96 -0
  50. data/lib/god/sugar.rb +47 -0
  51. data/lib/god/system/portable_poller.rb +42 -0
  52. data/lib/god/system/process.rb +42 -0
  53. data/lib/god/system/slash_proc_poller.rb +82 -0
  54. data/lib/god/task.rb +487 -0
  55. data/lib/god/timeline.rb +25 -0
  56. data/lib/god/trigger.rb +43 -0
  57. data/lib/god/watch.rb +183 -0
  58. data/lib/god.rb +644 -0
  59. data/test/configs/child_events/child_events.god +44 -0
  60. data/test/configs/child_events/simple_server.rb +3 -0
  61. data/test/configs/child_polls/child_polls.god +37 -0
  62. data/test/configs/child_polls/simple_server.rb +12 -0
  63. data/test/configs/complex/complex.god +59 -0
  64. data/test/configs/complex/simple_server.rb +3 -0
  65. data/test/configs/contact/contact.god +74 -0
  66. data/test/configs/contact/simple_server.rb +3 -0
  67. data/test/configs/daemon_events/daemon_events.god +37 -0
  68. data/test/configs/daemon_events/simple_server.rb +8 -0
  69. data/test/configs/daemon_events/simple_server_stop.rb +11 -0
  70. data/test/configs/daemon_polls/daemon_polls.god +17 -0
  71. data/test/configs/daemon_polls/simple_server.rb +6 -0
  72. data/test/configs/degrading_lambda/degrading_lambda.god +31 -0
  73. data/test/configs/degrading_lambda/tcp_server.rb +15 -0
  74. data/test/configs/matias/matias.god +50 -0
  75. data/test/configs/real.rb +59 -0
  76. data/test/configs/running_load/running_load.god +16 -0
  77. data/test/configs/stress/simple_server.rb +3 -0
  78. data/test/configs/stress/stress.god +15 -0
  79. data/test/configs/task/logs/.placeholder +0 -0
  80. data/test/configs/task/task.god +26 -0
  81. data/test/configs/test.rb +61 -0
  82. data/test/helper.rb +151 -0
  83. data/test/suite.rb +6 -0
  84. data/test/test_behavior.rb +21 -0
  85. data/test/test_condition.rb +50 -0
  86. data/test/test_conditions_disk_usage.rb +56 -0
  87. data/test/test_conditions_http_response_code.rb +109 -0
  88. data/test/test_conditions_process_running.rb +44 -0
  89. data/test/test_conditions_tries.rb +67 -0
  90. data/test/test_contact.rb +109 -0
  91. data/test/test_dependency_graph.rb +62 -0
  92. data/test/test_driver.rb +11 -0
  93. data/test/test_event_handler.rb +80 -0
  94. data/test/test_god.rb +598 -0
  95. data/test/test_handlers_kqueue_handler.rb +16 -0
  96. data/test/test_logger.rb +63 -0
  97. data/test/test_metric.rb +72 -0
  98. data/test/test_process.rb +246 -0
  99. data/test/test_registry.rb +15 -0
  100. data/test/test_socket.rb +42 -0
  101. data/test/test_sugar.rb +42 -0
  102. data/test/test_system_portable_poller.rb +17 -0
  103. data/test/test_system_process.rb +30 -0
  104. data/test/test_task.rb +262 -0
  105. data/test/test_timeline.rb +37 -0
  106. data/test/test_trigger.rb +59 -0
  107. data/test/test_watch.rb +279 -0
  108. metadata +186 -0
@@ -0,0 +1,206 @@
1
+ module God
2
+ module CLI
3
+
4
+ class Command
5
+ def initialize(command, options, args)
6
+ @command = command
7
+ @options = options
8
+ @args = args
9
+
10
+ dispatch
11
+ end
12
+
13
+ def setup
14
+ # connect to drb unix socket
15
+ DRb.start_service("druby://127.0.0.1:0")
16
+ @server = DRbObject.new(nil, God::Socket.socket(@options[:port]))
17
+
18
+ # ping server to ensure that it is responsive
19
+ begin
20
+ @server.ping
21
+ rescue DRb::DRbConnError
22
+ puts "The server is not available (or you do not have permissions to access it)"
23
+ abort
24
+ end
25
+ end
26
+
27
+ def dispatch
28
+ if %w{load status log quit terminate}.include?(@command)
29
+ setup
30
+ send("#{@command}_command")
31
+ elsif %w{start stop restart monitor unmonitor remove}.include?(@command)
32
+ setup
33
+ lifecycle_command
34
+ elsif @command == 'check'
35
+ check_command
36
+ else
37
+ puts "Command '#{@command}' is not valid. Run 'god --help' for usage"
38
+ abort
39
+ end
40
+ end
41
+
42
+ def load_command
43
+ file = @args[1]
44
+
45
+ puts "Sending '#{@command}' command"
46
+ puts
47
+
48
+ unless File.exist?(file)
49
+ abort "File not found: #{file}"
50
+ end
51
+
52
+ names, errors = *@server.running_load(File.read(file), File.expand_path(file))
53
+
54
+ # output response
55
+ unless names.empty?
56
+ puts 'The following tasks were affected:'
57
+ names.each do |w|
58
+ puts ' ' + w
59
+ end
60
+ end
61
+
62
+ unless errors.empty?
63
+ puts errors
64
+ exit(1)
65
+ end
66
+ end
67
+
68
+ def status_command
69
+ watches = {}
70
+ @server.status.each do |name, status|
71
+ g = status[:group] || ''
72
+ unless watches.has_key?(g)
73
+ watches[g] = {}
74
+ end
75
+ watches[g][name] = status
76
+ end
77
+ watches.keys.sort.each do |group|
78
+ puts "#{group}:" unless group.empty?
79
+ watches[group].keys.sort.each do |name|
80
+ state = watches[group][name][:state]
81
+ print " " unless group.empty?
82
+ puts "#{name}: #{state}"
83
+ end
84
+ end
85
+ end
86
+
87
+ def log_command
88
+ begin
89
+ Signal.trap('INT') { exit }
90
+ name = @args[1]
91
+
92
+ unless name
93
+ puts "You must specify a Task or Group name"
94
+ exit!
95
+ end
96
+
97
+ t = Time.at(0)
98
+ loop do
99
+ print @server.running_log(name, t)
100
+ t = Time.now
101
+ sleep 1
102
+ end
103
+ rescue God::NoSuchWatchError
104
+ puts "No such watch"
105
+ rescue DRb::DRbConnError
106
+ puts "The server went away"
107
+ end
108
+ end
109
+
110
+ def quit_command
111
+ begin
112
+ @server.terminate
113
+ abort 'Could not stop god'
114
+ rescue DRb::DRbConnError
115
+ puts 'Stopped god'
116
+ end
117
+ end
118
+
119
+ def terminate_command
120
+ t = Thread.new { loop { STDOUT.print('.'); STDOUT.flush; sleep(1) } }
121
+ if @server.stop_all
122
+ t.kill; STDOUT.puts
123
+ puts 'Stopped all watches'
124
+ else
125
+ t.kill; STDOUT.puts
126
+ puts 'Could not stop all watches within 10 seconds'
127
+ end
128
+
129
+ begin
130
+ @server.terminate
131
+ abort 'Could not stop god'
132
+ rescue DRb::DRbConnError
133
+ puts 'Stopped god'
134
+ end
135
+ end
136
+
137
+ def check_command
138
+ Thread.new do
139
+ begin
140
+ event_system = God::EventHandler.event_system
141
+ puts "using event system: #{event_system}"
142
+
143
+ if God::EventHandler.loaded?
144
+ puts "starting event handler"
145
+ God::EventHandler.start
146
+ else
147
+ puts "[fail] event system did not load"
148
+ exit(1)
149
+ end
150
+
151
+ puts 'forking off new process'
152
+
153
+ pid = fork do
154
+ loop { sleep(1) }
155
+ end
156
+
157
+ puts "forked process with pid = #{pid}"
158
+
159
+ God::EventHandler.register(pid, :proc_exit) do
160
+ puts "[ok] process exit event received"
161
+ exit!(0)
162
+ end
163
+
164
+ sleep(1)
165
+
166
+ puts "killing process"
167
+
168
+ ::Process.kill('KILL', pid)
169
+ rescue => e
170
+ puts e.message
171
+ puts e.backtrace.join("\n")
172
+ end
173
+ end
174
+
175
+ sleep(2)
176
+
177
+ puts "[fail] never received process exit event"
178
+ exit(1)
179
+ end
180
+
181
+ def lifecycle_command
182
+ # get the name of the watch/group
183
+ name = @args[1]
184
+
185
+ puts "Sending '#{@command}' command"
186
+
187
+ t = Thread.new { loop { sleep(1); STDOUT.print('.'); STDOUT.flush; sleep(1) } }
188
+
189
+ # send @command
190
+ watches = @server.control(name, @command)
191
+
192
+ # output response
193
+ t.kill; STDOUT.puts
194
+ unless watches.empty?
195
+ puts 'The following watches were affected:'
196
+ watches.each do |w|
197
+ puts ' ' + w
198
+ end
199
+ else
200
+ puts 'No matching task or group'
201
+ end
202
+ end
203
+ end # Command
204
+
205
+ end
206
+ end
@@ -0,0 +1,177 @@
1
+ module God
2
+ module CLI
3
+
4
+ class Run
5
+ def initialize(options)
6
+ @options = options
7
+
8
+ dispatch
9
+ end
10
+
11
+ def dispatch
12
+ # have at_exit start god
13
+ $run = true
14
+
15
+ # run
16
+ if @options[:daemonize]
17
+ run_daemonized
18
+ else
19
+ run_in_front
20
+ end
21
+ end
22
+
23
+ def attach
24
+ process = System::Process.new(@options[:attach])
25
+ Thread.new do
26
+ loop do
27
+ unless process.exists?
28
+ applog(nil, :info, "Going down because attached process #{@options[:attach]} exited")
29
+ exit!
30
+ end
31
+ sleep 5
32
+ end
33
+ end
34
+ end
35
+
36
+ def default_run
37
+ # start attached pid watcher if necessary
38
+ if @options[:attach]
39
+ self.attach
40
+ end
41
+
42
+ if @options[:port]
43
+ God.port = @options[:port]
44
+ end
45
+
46
+ if @options[:events]
47
+ God::EventHandler.load
48
+ end
49
+
50
+ # set log level, defaults to WARN
51
+ if @options[:log_level]
52
+ God.log_level = @options[:log_level]
53
+ else
54
+ God.log_level = @options[:daemonize] ? :warn : :info
55
+ end
56
+
57
+ if @options[:config]
58
+ unless File.exist?(@options[:config])
59
+ abort "File not found: #{@options[:config]}"
60
+ end
61
+
62
+ # start the event handler
63
+ God::EventHandler.start if God::EventHandler.loaded?
64
+
65
+ load_config @options[:config]
66
+ end
67
+ end
68
+
69
+ def run_in_front
70
+ require 'god'
71
+
72
+ if @options[:bleakhouse]
73
+ BleakHouseDiagnostic.install
74
+ end
75
+
76
+ default_run
77
+
78
+ log_file = God.log_file
79
+ log_file = File.expand_path(@options[:log]) if @options[:log]
80
+ if log_file
81
+ puts "Sending output to log file: #{log_file}"
82
+
83
+ # reset file descriptors
84
+ STDIN.reopen "/dev/null"
85
+ STDOUT.reopen(log_file, "a")
86
+ STDERR.reopen STDOUT
87
+ STDOUT.sync = true
88
+ end
89
+ end
90
+
91
+ def run_daemonized
92
+ # trap and ignore SIGHUP
93
+ Signal.trap('HUP') {}
94
+
95
+ pid = fork do
96
+ begin
97
+ require 'god'
98
+
99
+ log_file = @options[:log] || God.log_file || "/dev/null"
100
+
101
+ # reset file descriptors
102
+ STDIN.reopen "/dev/null"
103
+ STDOUT.reopen(log_file, "a")
104
+ STDERR.reopen STDOUT
105
+ STDOUT.sync = true
106
+
107
+ # set pid if requested
108
+ if @options[:pid] # and as deamon
109
+ God.pid = @options[:pid]
110
+ end
111
+
112
+ unless @options[:syslog]
113
+ Logger.syslog = false
114
+ end
115
+
116
+ default_run
117
+
118
+ unless God::EventHandler.loaded?
119
+ puts
120
+ puts "***********************************************************************"
121
+ puts "*"
122
+ puts "* Event conditions are not available for your installation of god."
123
+ puts "* You may still use and write custom conditions using the poll system"
124
+ puts "*"
125
+ puts "***********************************************************************"
126
+ puts
127
+ end
128
+
129
+ rescue => e
130
+ puts e.message
131
+ puts e.backtrace.join("\n")
132
+ abort "There was a fatal system error while starting god (see above)"
133
+ end
134
+ end
135
+
136
+ if @options[:pid]
137
+ File.open(@options[:pid], 'w') { |f| f.write pid }
138
+ end
139
+
140
+ ::Process.detach pid
141
+
142
+ exit
143
+ end
144
+
145
+ def load_config(config)
146
+ if File.directory? config
147
+ files_loaded = false
148
+ Dir[File.expand_path('**/*.god', config)].each do |god_file|
149
+ files_loaded ||= load_god_file(File.expand_path(god_file))
150
+ end
151
+ unless files_loaded
152
+ abort "No files could be loaded"
153
+ end
154
+ else
155
+ unless load_god_file(File.expand_path(config))
156
+ abort "File could not be loaded"
157
+ end
158
+ end
159
+ end
160
+
161
+ def load_god_file(god_file)
162
+ load File.expand_path(god_file)
163
+ rescue Exception => e
164
+ if e.instance_of?(SystemExit)
165
+ raise
166
+ else
167
+ puts "There was an error in #{god_file}"
168
+ puts "\t" + e.message
169
+ puts "\t" + e.backtrace.join("\n\t")
170
+ return false
171
+ end
172
+ end
173
+
174
+ end # Run
175
+
176
+ end
177
+ end
@@ -0,0 +1,23 @@
1
+ module God
2
+ module CLI
3
+
4
+ class Version
5
+ def self.version
6
+ require 'god'
7
+
8
+ # print version
9
+ puts "Version #{God::VERSION}"
10
+ exit
11
+ end
12
+
13
+ def self.version_extended
14
+ puts "Version: #{God::VERSION}"
15
+ puts "Polls: enabled"
16
+ puts "Events: " + God::EventHandler.event_system
17
+
18
+ exit
19
+ end
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,96 @@
1
+ module God
2
+
3
+ class Condition < Behavior
4
+ attr_accessor :transition, :notify, :info, :phase
5
+
6
+ # Generate a Condition of the given kind. The proper class if found by camel casing the
7
+ # kind (which is given as an underscored symbol).
8
+ # +kind+ is the underscored symbol representing the class (e.g. :foo_bar for God::Conditions::FooBar)
9
+ def self.generate(kind, watch)
10
+ sym = kind.to_s.capitalize.gsub(/_(.)/){$1.upcase}.intern
11
+ c = God::Conditions.const_get(sym).new
12
+
13
+ unless c.kind_of?(PollCondition) || c.kind_of?(EventCondition) || c.kind_of?(TriggerCondition)
14
+ abort "Condition '#{c.class.name}' must subclass God::PollCondition, God::EventCondition, or God::TriggerCondition"
15
+ end
16
+
17
+ if !EventHandler.loaded? && c.kind_of?(EventCondition)
18
+ abort "Condition '#{c.class.name}' requires an event system but none has been loaded"
19
+ end
20
+
21
+ c.watch = watch
22
+ c
23
+ rescue NameError
24
+ raise NoSuchConditionError.new("No Condition found with the class name God::Conditions::#{sym}")
25
+ end
26
+
27
+ def self.valid?(condition)
28
+ valid = true
29
+ if condition.notify
30
+ begin
31
+ Contact.normalize(condition.notify)
32
+ rescue ArgumentError => e
33
+ valid &= Configurable.complain("Attribute 'notify' " + e.message, condition)
34
+ end
35
+ end
36
+ valid
37
+ end
38
+
39
+ # Construct the friendly name of this Condition, looks like:
40
+ #
41
+ # Condition FooBar on Watch 'baz'
42
+ def friendly_name
43
+ "Condition #{self.class.name.split('::').last} on Watch '#{self.watch.name}'"
44
+ end
45
+ end
46
+
47
+ class PollCondition < Condition
48
+ # all poll conditions can specify a poll interval
49
+ attr_accessor :interval
50
+
51
+ # Override this method in your Conditions (optional)
52
+ def before
53
+ end
54
+
55
+ # Override this method in your Conditions (mandatory)
56
+ #
57
+ # Return true if the test passes (everything is ok)
58
+ # Return false otherwise
59
+ def test
60
+ raise AbstractMethodNotOverriddenError.new("PollCondition#test must be overridden in subclasses")
61
+ end
62
+
63
+ # Override this method in your Conditions (optional)
64
+ def after
65
+ end
66
+ end
67
+
68
+ class EventCondition < Condition
69
+ def register
70
+ raise AbstractMethodNotOverriddenError.new("EventCondition#register must be overridden in subclasses")
71
+ end
72
+
73
+ def deregister
74
+ raise AbstractMethodNotOverriddenError.new("EventCondition#deregister must be overridden in subclasses")
75
+ end
76
+ end
77
+
78
+ class TriggerCondition < Condition
79
+ def process(event, payload)
80
+ raise AbstractMethodNotOverriddenError.new("TriggerCondition#process must be overridden in subclasses")
81
+ end
82
+
83
+ def trigger
84
+ self.watch.trigger(self)
85
+ end
86
+
87
+ def register
88
+ Trigger.register(self)
89
+ end
90
+
91
+ def deregister
92
+ Trigger.deregister(self)
93
+ end
94
+ end
95
+
96
+ end
@@ -0,0 +1,23 @@
1
+ module God
2
+ module Conditions
3
+
4
+ class Always < PollCondition
5
+ attr_accessor :what
6
+
7
+ def initialize
8
+ self.info = "always"
9
+ end
10
+
11
+ def valid?
12
+ valid = true
13
+ valid &= complain("Attribute 'what' must be specified", self) if self.what.nil?
14
+ valid
15
+ end
16
+
17
+ def test
18
+ @what
19
+ end
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,86 @@
1
+ module God
2
+ module Conditions
3
+
4
+ class Complex < PollCondition
5
+ AND = 0x1
6
+ OR = 0x2
7
+ NOT = 0x4
8
+
9
+ def initialize()
10
+ super
11
+
12
+ @oper_stack = []
13
+ @op_stack = []
14
+
15
+ @this = nil
16
+ end
17
+
18
+ def valid?
19
+ @oper_stack.inject(true) { |acc, oper| acc & oper.valid? }
20
+ end
21
+
22
+ def prepare
23
+ @oper_stack.each { |oper| oper.prepare }
24
+ end
25
+
26
+ def new_oper(kind, op)
27
+ oper = Condition.generate(kind, self.watch)
28
+ @oper_stack.push(oper)
29
+ @op_stack.push(op)
30
+ oper
31
+ end
32
+
33
+ def this(kind)
34
+ @this = Condition.generate(kind, self.watch)
35
+ yield @this if block_given?
36
+ end
37
+
38
+ def and(kind)
39
+ oper = new_oper(kind, 0x1)
40
+ yield oper if block_given?
41
+ end
42
+
43
+ def and_not(kind)
44
+ oper = new_oper(kind, 0x5)
45
+ yield oper if block_given?
46
+ end
47
+
48
+ def or(kind)
49
+ oper = new_oper(kind, 0x2)
50
+ yield oper if block_given?
51
+ end
52
+
53
+ def or_not(kind)
54
+ oper = new_oper(kind, 0x6)
55
+ yield oper if block_given?
56
+ end
57
+
58
+ def test
59
+ if @this.nil?
60
+ # Although this() makes sense semantically and therefore
61
+ # encourages easy-to-read conditions, being able to omit it
62
+ # allows for more DRY code in some cases, so we deal with a
63
+ # nil @this here by initially setting res to true or false,
64
+ # depending on whether the first operator used is AND or OR
65
+ # respectively.
66
+ if 0 < @op_stack[0] & AND
67
+ res = true
68
+ else
69
+ res = false
70
+ end
71
+ else
72
+ res = @this.test
73
+ end
74
+
75
+ @op_stack.each do |op|
76
+ cond = @oper_stack.shift
77
+ eval "res " + ((0 < op & AND) ? "&&" : "||") + "= " + ((0 < op & NOT) ? "!" : "") + "cond.test"
78
+ @oper_stack.push cond
79
+ end
80
+
81
+ res
82
+ end
83
+ end
84
+
85
+ end
86
+ end
@@ -0,0 +1,80 @@
1
+ module God
2
+ module Conditions
3
+
4
+ # Condition Symbol :cpu_usage
5
+ # Type: Poll
6
+ #
7
+ # Trigger when the percent of CPU use of a process is above a specified limit.
8
+ # On multi-core systems, this number could conceivably be above 100.
9
+ #
10
+ # Paramaters
11
+ # Required
12
+ # +pid_file+ is the pid file of the process in question. Automatically
13
+ # populated for Watches.
14
+ # +above+ is the percent CPU above which to trigger the condition. You
15
+ # may use #percent to clarify this amount (see examples).
16
+ #
17
+ # Examples
18
+ #
19
+ # Trigger if the process is using more than 25 percent of the cpu (from a Watch):
20
+ #
21
+ # on.condition(:cpu_usage) do |c|
22
+ # c.above = 25.percent
23
+ # end
24
+ #
25
+ # Non-Watch Tasks must specify a PID file:
26
+ #
27
+ # on.condition(:cpu_usage) do |c|
28
+ # c.above = 25.percent
29
+ # c.pid_file = "/var/run/mongrel.3000.pid"
30
+ # end
31
+ class CpuUsage < PollCondition
32
+ attr_accessor :above, :times, :pid_file
33
+
34
+ def initialize
35
+ super
36
+ self.above = nil
37
+ self.times = [1, 1]
38
+ end
39
+
40
+ def prepare
41
+ if self.times.kind_of?(Integer)
42
+ self.times = [self.times, self.times]
43
+ end
44
+
45
+ @timeline = Timeline.new(self.times[1])
46
+ end
47
+
48
+ def reset
49
+ @timeline.clear
50
+ end
51
+
52
+ def pid
53
+ self.pid_file ? File.read(self.pid_file).strip.to_i : self.watch.pid
54
+ end
55
+
56
+ def valid?
57
+ valid = true
58
+ valid &= complain("Attribute 'pid_file' must be specified", self) if self.pid_file.nil? && self.watch.pid_file.nil?
59
+ valid &= complain("Attribute 'above' must be specified", self) if self.above.nil?
60
+ valid
61
+ end
62
+
63
+ def test
64
+ process = System::Process.new(self.pid)
65
+ @timeline.push(process.percent_cpu)
66
+
67
+ history = "[" + @timeline.map { |x| "#{x > self.above ? '*' : ''}#{x}%%" }.join(", ") + "]"
68
+
69
+ if @timeline.select { |x| x > self.above }.size >= self.times.first
70
+ self.info = "cpu out of bounds #{history}"
71
+ return true
72
+ else
73
+ self.info = "cpu within bounds #{history}"
74
+ return false
75
+ end
76
+ end
77
+ end
78
+
79
+ end
80
+ end