god 0.6.0 → 0.7.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.
Files changed (43) hide show
  1. data/History.txt +67 -1
  2. data/Manifest.txt +3 -4
  3. data/Rakefile +1 -1
  4. data/bin/god +19 -1
  5. data/lib/god.rb +86 -49
  6. data/lib/god/cli/command.rb +7 -1
  7. data/lib/god/cli/run.rb +58 -0
  8. data/lib/god/condition.rb +6 -2
  9. data/lib/god/conditions/cpu_usage.rb +7 -6
  10. data/lib/god/conditions/http_response_code.rb +5 -1
  11. data/lib/god/conditions/memory_usage.rb +7 -6
  12. data/lib/god/conditions/process_exits.rb +15 -10
  13. data/lib/god/conditions/process_running.rb +17 -13
  14. data/lib/god/diagnostics.rb +37 -0
  15. data/lib/god/driver.rb +108 -0
  16. data/lib/god/event_handler.rb +41 -1
  17. data/lib/god/logger.rb +69 -19
  18. data/lib/god/metric.rb +2 -2
  19. data/lib/god/process.rb +84 -27
  20. data/lib/god/task.rb +286 -29
  21. data/lib/god/timeline.rb +20 -31
  22. data/lib/god/watch.rb +26 -15
  23. data/test/configs/child_events/child_events.god +0 -5
  24. data/test/configs/child_polls/simple_server.rb +1 -1
  25. data/test/configs/daemon_events/simple_server_stop.rb +2 -0
  26. data/test/configs/stress/stress.god +1 -1
  27. data/test/configs/test.rb +12 -28
  28. data/test/test_condition.rb +8 -0
  29. data/test/test_conditions_http_response_code.rb +5 -5
  30. data/test/test_conditions_process_running.rb +6 -4
  31. data/test/test_driver.rb +11 -0
  32. data/test/test_event_handler.rb +7 -0
  33. data/test/test_god.rb +63 -62
  34. data/test/test_metric.rb +0 -16
  35. data/test/test_process.rb +29 -1
  36. data/test/test_task.rb +177 -1
  37. data/test/test_timeline.rb +2 -1
  38. data/test/test_watch.rb +24 -6
  39. metadata +6 -8
  40. data/lib/god/hub.rb +0 -222
  41. data/lib/god/timer.rb +0 -87
  42. data/test/test_hub.rb +0 -240
  43. data/test/test_timer.rb +0 -69
@@ -1,4 +1,70 @@
1
- == 0.6.0 /
1
+ == 0.7.0 / 2008-02-01
2
+ * Minor Enhancements
3
+ * Better default pid_file_directory behavior
4
+ * Add --attach <pid> to specify that god should quit if <pid> exits
5
+ * Bug Fixes
6
+ * Handle ECONNRESET in HttpResponseCode
7
+
8
+ == 0.6.12 / 2008-01-31
9
+ * Minor Enhancements
10
+ * Allow log file output for non-daemonized god
11
+ * Switch to SIGTERM from SIGHUP for default lambda killer
12
+
13
+ == 0.6.11 / 2008-01-31
14
+ * Major Enhancements
15
+ * HUGE refactor of timer system to simplify scheduling
16
+ * Minor Enhancements
17
+ * Check for a truly working event system and disallow event conditions if none is present
18
+
19
+ == 0.6.10 / 2008-01-24
20
+ * Bug Fixes
21
+ * Fix ensure_stop nil pid no local variable bug
22
+
23
+ == 0.6.9 / 2008-01-23
24
+ * Bug Fixes
25
+ * Fix Timer condition dedup behavior
26
+
27
+ == 0.6.8 / 2008-01-23
28
+ * Minor Enhancements
29
+ * Warn if a command returns a non-zero exit code
30
+ * Ensure that stop command actually stops process
31
+
32
+ == 0.6.7 / 2008-01-22
33
+ * Minor Enhancements
34
+ * Add --no-syslog option to disable Syslog
35
+ * Allow contact redeclaration (dups are ignored)
36
+
37
+ == 0.6.6 / 2008-01-07
38
+ * Bug Fixes
39
+ * Redo Timer mutexing to reduce synchronization needs
40
+
41
+ == 0.6.5 / 2008-01-04
42
+ * Bug Fixes
43
+ * Fix Timer descheduling deadlock issue
44
+ * Change HttpResponseCode to use GET instead of HEAD
45
+
46
+ == 0.6.4 / 2008-12-31
47
+ * Bug Fixes
48
+ * Refactor Hub to clarify mutexing
49
+ * Eliminate potential iteration problem in Timer
50
+ * Add caching PID accessor to process to solve event deregistration failure
51
+
52
+ == 0.6.3 / 2007-12-18
53
+ * Minor Enhancements
54
+ * Output ProcessExits registration/deregistration info
55
+
56
+ == 0.6.2 / 2007-12-17
57
+ * Minor Enhancements
58
+ * Output registered PID for ProcessExits
59
+ * Bug Fixes
60
+ * Fix `god remove <group>` not working for unmonitored watches
61
+
62
+ == 0.6.1 / 2007-12-14
63
+
64
+ * Minor Enhancement
65
+ * Log when state change is complete
66
+
67
+ == 0.6.0 / 2007-12-4
2
68
 
3
69
  * Minor Enhancement
4
70
  * Move Syslog calls into God::Logger and clean up all calling code
@@ -34,12 +34,13 @@ lib/god/configurable.rb
34
34
  lib/god/contact.rb
35
35
  lib/god/contacts/email.rb
36
36
  lib/god/dependency_graph.rb
37
+ lib/god/diagnostics.rb
38
+ lib/god/driver.rb
37
39
  lib/god/errors.rb
38
40
  lib/god/event_handler.rb
39
41
  lib/god/event_handlers/dummy_handler.rb
40
42
  lib/god/event_handlers/kqueue_handler.rb
41
43
  lib/god/event_handlers/netlink_handler.rb
42
- lib/god/hub.rb
43
44
  lib/god/logger.rb
44
45
  lib/god/metric.rb
45
46
  lib/god/process.rb
@@ -49,7 +50,6 @@ lib/god/sugar.rb
49
50
  lib/god/system/process.rb
50
51
  lib/god/task.rb
51
52
  lib/god/timeline.rb
52
- lib/god/timer.rb
53
53
  lib/god/trigger.rb
54
54
  lib/god/watch.rb
55
55
  test/configs/child_events/child_events.god
@@ -84,10 +84,10 @@ test/test_conditions_process_running.rb
84
84
  test/test_conditions_tries.rb
85
85
  test/test_contact.rb
86
86
  test/test_dependency_graph.rb
87
+ test/test_driver.rb
87
88
  test/test_event_handler.rb
88
89
  test/test_god.rb
89
90
  test/test_handlers_kqueue_handler.rb
90
- test/test_hub.rb
91
91
  test/test_logger.rb
92
92
  test/test_metric.rb
93
93
  test/test_process.rb
@@ -97,6 +97,5 @@ test/test_sugar.rb
97
97
  test/test_system_process.rb
98
98
  test/test_task.rb
99
99
  test/test_timeline.rb
100
- test/test_timer.rb
101
100
  test/test_trigger.rb
102
101
  test/test_watch.rb
data/Rakefile CHANGED
@@ -1,7 +1,7 @@
1
1
  require 'rubygems'
2
2
  require 'hoe'
3
3
 
4
- Hoe.new('god', '0.6.0') do |p|
4
+ Hoe.new('god', '0.7.0') do |p|
5
5
  p.rubyforge_name = 'god'
6
6
  p.author = 'Tom Preston-Werner'
7
7
  p.email = 'tom@rubyisawesome.com'
data/bin/god CHANGED
@@ -9,7 +9,7 @@ require 'optparse'
9
9
  require 'drb'
10
10
 
11
11
  begin
12
- options = {:daemonize => true, :port => 17165}
12
+ options = {:daemonize => true, :port => 17165, :syslog => true}
13
13
 
14
14
  opts = OptionParser.new do |opts|
15
15
  opts.banner = <<-EOF
@@ -71,10 +71,28 @@ begin
71
71
  opts.on("-V", "Print extended version and build information") do
72
72
  options[:info] = true
73
73
  end
74
+
75
+ opts.on("--log-level LEVEL", "Log level [debug|info|warn|error|fatal]") do |x|
76
+ options[:log_level] = x.to_sym
77
+ end
78
+
79
+ opts.on("--no-syslog", "Disable output to syslog") do
80
+ options[:syslog] = false
81
+ end
82
+
83
+ opts.on("--attach PID", "Quit god when the attached process dies") do |x|
84
+ options[:attach] = x
85
+ end
74
86
  end
75
87
 
76
88
  opts.parse!
77
89
 
90
+ # validate
91
+ if options[:log_level] && ![:debug, :info, :warn, :error, :fatal].include?(options[:log_level])
92
+ abort("Invalid log level '#{options[:log_level]}'")
93
+ end
94
+
95
+ # dispatch
78
96
  if !options[:config] && options[:version]
79
97
  require 'god'
80
98
  God::CLI::Version.version
data/lib/god.rb CHANGED
@@ -6,9 +6,16 @@ require 'rubygems'
6
6
  # core
7
7
  require 'stringio'
8
8
  require 'logger'
9
+ require 'fileutils'
10
+
11
+ begin
12
+ require 'fastthread'
13
+ rescue LoadError
14
+ ensure
15
+ require 'thread'
16
+ end
9
17
 
10
18
  # stdlib
11
- require 'syslog'
12
19
 
13
20
  # internal requires
14
21
  require 'god/errors'
@@ -42,8 +49,7 @@ require 'god/contact'
42
49
  require 'god/contacts/email'
43
50
 
44
51
  require 'god/socket'
45
- require 'god/timer'
46
- require 'god/hub'
52
+ require 'god/driver'
47
53
 
48
54
  require 'god/metric'
49
55
  require 'god/watch'
@@ -58,6 +64,8 @@ require 'god/sugar'
58
64
  require 'god/cli/version'
59
65
  require 'god/cli/command'
60
66
 
67
+ require 'god/diagnostics'
68
+
61
69
  $:.unshift File.join(File.dirname(__FILE__), *%w[.. ext god])
62
70
 
63
71
  # App wide logging system
@@ -76,13 +84,6 @@ $run ||= nil
76
84
 
77
85
  GOD_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
78
86
 
79
- # Ensure that Syslog is open
80
- begin
81
- Syslog.open('god')
82
- rescue RuntimeError
83
- Syslog.reopen('god')
84
- end
85
-
86
87
  # Return the binding of god's root level
87
88
  def root_binding
88
89
  binding
@@ -132,12 +133,13 @@ class Module
132
133
  end
133
134
 
134
135
  module God
135
- VERSION = '0.6.0'
136
+ VERSION = '0.7.0'
136
137
 
137
- LOG_BUFFER_SIZE_DEFAULT = 1000
138
- PID_FILE_DIRECTORY_DEFAULT = '/var/run/god'
138
+ LOG_BUFFER_SIZE_DEFAULT = 10
139
+ PID_FILE_DIRECTORY_DEFAULTS = ['/var/run/god', '~/.god/pids']
139
140
  DRB_PORT_DEFAULT = 17165
140
141
  DRB_ALLOW_DEFAULT = ['127.0.0.1']
142
+ LOG_LEVEL_DEFAULT = :info
141
143
 
142
144
  class << self
143
145
  # user configurable
@@ -146,7 +148,8 @@ module God
146
148
  :port,
147
149
  :allow,
148
150
  :log_buffer_size,
149
- :pid_file_directory
151
+ :pid_file_directory,
152
+ :log_level
150
153
 
151
154
  # internal
152
155
  attr_accessor :inited,
@@ -157,7 +160,8 @@ module God
157
160
  :watches,
158
161
  :groups,
159
162
  :contacts,
160
- :contact_groups
163
+ :contact_groups,
164
+ :main
161
165
  end
162
166
 
163
167
  # initialize class instance variables
@@ -167,6 +171,7 @@ module God
167
171
  self.allow = nil
168
172
  self.log_buffer_size = nil
169
173
  self.pid_file_directory = nil
174
+ self.log_level = nil
170
175
 
171
176
  # Initialize internal data.
172
177
  #
@@ -185,10 +190,20 @@ module God
185
190
 
186
191
  # set defaults
187
192
  self.log_buffer_size ||= LOG_BUFFER_SIZE_DEFAULT
188
- self.pid_file_directory ||= PID_FILE_DIRECTORY_DEFAULT
189
193
  self.port ||= DRB_PORT_DEFAULT
190
194
  self.allow ||= DRB_ALLOW_DEFAULT
191
- LOG.level = Logger::INFO
195
+ self.log_level ||= LOG_LEVEL_DEFAULT
196
+
197
+ # additional setup
198
+ self.setup
199
+
200
+ # log level
201
+ log_level_map = {:debug => Logger::DEBUG,
202
+ :info => Logger::INFO,
203
+ :warn => Logger::WARN,
204
+ :error => Logger::ERROR,
205
+ :fatal => Logger::FATAL}
206
+ LOG.level = log_level_map[self.log_level]
192
207
 
193
208
  # init has been executed
194
209
  self.inited = true
@@ -290,6 +305,8 @@ module God
290
305
  if watch.group
291
306
  self.groups[watch.group].delete(watch)
292
307
  end
308
+
309
+ applog(watch, :info, "#{watch.name} unwatched")
293
310
  end
294
311
 
295
312
  # Instantiate a new Contact of the given kind and send it to the block.
@@ -324,9 +341,10 @@ module God
324
341
  self.uncontact(existing_contact)
325
342
  end
326
343
 
327
- # ensure the new contact has a unique name
344
+ # warn and noop if the contact has been defined before
328
345
  if self.contacts[c.name] || self.contact_groups[c.name]
329
- abort "Contact name '#{c.name}' already used for a Contact or Contact Group"
346
+ applog(nil, :warn, "Contact name '#{c.name}' already used for a Contact or Contact Group")
347
+ return
330
348
  end
331
349
 
332
350
  # abort if the Contact is invalid, the Contact will have printed
@@ -373,30 +391,30 @@ module God
373
391
  #
374
392
  # Returns String[]:task_names
375
393
  def self.control(name, command)
376
- # get the list of watches
377
- watches = Array(self.watches[name] || self.groups[name])
394
+ # get the list of items
395
+ items = Array(self.watches[name] || self.groups[name]).dup
378
396
 
379
397
  jobs = []
380
398
 
381
399
  # do the command
382
400
  case command
383
401
  when "start", "monitor"
384
- watches.each { |w| jobs << Thread.new { w.monitor if w.state != :up } }
402
+ items.each { |w| jobs << Thread.new { w.monitor if w.state != :up } }
385
403
  when "restart"
386
- watches.each { |w| jobs << Thread.new { w.move(:restart) } }
404
+ items.each { |w| jobs << Thread.new { w.move(:restart) } }
387
405
  when "stop"
388
- watches.each { |w| jobs << Thread.new { w.unmonitor.action(:stop) if w.state != :unmonitored } }
406
+ items.each { |w| jobs << Thread.new { w.unmonitor.action(:stop) if w.state != :unmonitored } }
389
407
  when "unmonitor"
390
- watches.each { |w| jobs << Thread.new { w.unmonitor if w.state != :unmonitored } }
408
+ items.each { |w| jobs << Thread.new { w.unmonitor if w.state != :unmonitored } }
391
409
  when "remove"
392
- watches.each { |w| jobs << Thread.new { self.unwatch(w) } }
410
+ items.each { |w| self.unwatch(w) }
393
411
  else
394
412
  raise InvalidCommandError.new
395
413
  end
396
414
 
397
415
  jobs.each { |j| j.join }
398
416
 
399
- watches.map { |x| x.name }
417
+ items.map { |x| x.name }
400
418
  end
401
419
 
402
420
  # Unmonitor and stop all tasks.
@@ -513,20 +531,40 @@ module God
513
531
  end
514
532
 
515
533
  def self.setup
516
- # Make pid directory
517
- unless test(?d, self.pid_file_directory)
518
- begin
519
- FileUtils.mkdir_p(self.pid_file_directory)
520
- rescue Errno::EACCES => e
521
- abort "Failed to create pid file directory: #{e.message}"
534
+ if self.pid_file_directory
535
+ # pid file dir was specified, ensure it is created and writable
536
+ unless File.exist?(self.pid_file_directory)
537
+ begin
538
+ FileUtils.mkdir_p(self.pid_file_directory)
539
+ rescue Errno::EACCES => e
540
+ abort "Failed to create pid file directory: #{e.message}"
541
+ end
542
+ end
543
+
544
+ unless File.writable?(self.pid_file_directory)
545
+ abort "The pid file directory (#{self.pid_file_directory}) is not writable by #{Etc.getlogin}"
546
+ end
547
+ else
548
+ # no pid file dir specified, try defaults
549
+ PID_FILE_DIRECTORY_DEFAULTS.each do |idir|
550
+ dir = File.expand_path(idir)
551
+ begin
552
+ FileUtils.mkdir_p(dir)
553
+ if File.writable?(dir)
554
+ self.pid_file_directory = dir
555
+ break
556
+ end
557
+ rescue Errno::EACCES => e
558
+ end
559
+ end
560
+
561
+ unless self.pid_file_directory
562
+ dirs = PID_FILE_DIRECTORY_DEFAULTS.map { |x| File.expand_path(x) }
563
+ abort "No pid file directory exists, could be created, or is writable at any of #{dirs.join(', ')}"
522
564
  end
523
565
  end
524
- end
525
-
526
- def self.validater
527
- unless test(?w, self.pid_file_directory)
528
- abort "The pid file directory (#{self.pid_file_directory}) is not writable by #{Etc.getlogin}"
529
- end
566
+
567
+ applog(nil, :info, "Using pid file directory: #{self.pid_file_directory}")
530
568
  end
531
569
 
532
570
  # Initialize and startup the machinery that makes god work.
@@ -534,18 +572,10 @@ module God
534
572
  # Returns nothing
535
573
  def self.start
536
574
  self.internal_init
537
- self.setup
538
- self.validater
539
575
 
540
576
  # instantiate server
541
577
  self.server = Socket.new(self.port)
542
578
 
543
- # start event handler system
544
- EventHandler.start if EventHandler.loaded?
545
-
546
- # start the timer system
547
- Timer.get
548
-
549
579
  # start monitoring any watches set to autostart
550
580
  self.watches.values.each { |w| w.monitor if w.autostart? }
551
581
 
@@ -555,8 +585,15 @@ module God
555
585
  # mark as running
556
586
  self.running = true
557
587
 
558
- # join the timer thread so we don't exit
559
- Timer.get.join
588
+ # don't exit
589
+ self.main =
590
+ Thread.new do
591
+ loop do
592
+ sleep 60
593
+ end
594
+ end
595
+
596
+ self.main.join
560
597
  end
561
598
 
562
599
  # To be called on program exit to start god
@@ -77,6 +77,12 @@ module God
77
77
  begin
78
78
  Signal.trap('INT') { exit }
79
79
  name = @args[1]
80
+
81
+ unless name
82
+ puts "You must specify a Task or Group name"
83
+ exit!
84
+ end
85
+
80
86
  t = Time.at(0)
81
87
  loop do
82
88
  print @server.running_log(name, t)
@@ -141,7 +147,7 @@ module God
141
147
 
142
148
  God::EventHandler.register(pid, :proc_exit) do
143
149
  puts "[ok] process exit event received"
144
- exit(0)
150
+ exit!(0)
145
151
  end
146
152
 
147
153
  sleep(1)
@@ -12,6 +12,7 @@ module God
12
12
  # have at_exit start god
13
13
  $run = true
14
14
 
15
+ # run
15
16
  if @options[:daemonize]
16
17
  run_daemonized
17
18
  else
@@ -19,6 +20,19 @@ module God
19
20
  end
20
21
  end
21
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
+
22
36
  def run_daemonized
23
37
  # trap and ignore SIGHUP
24
38
  Signal.trap('HUP') {}
@@ -40,6 +54,11 @@ module God
40
54
  puts
41
55
  end
42
56
 
57
+ # start attached pid watcher if necessary
58
+ if @options[:attach]
59
+ self.attach
60
+ end
61
+
43
62
  # set port if requested
44
63
  if @options[:port]
45
64
  God.port = @options[:port]
@@ -50,12 +69,26 @@ module God
50
69
  God.pid = @options[:pid]
51
70
  end
52
71
 
72
+ unless @options[:syslog]
73
+ Logger.syslog = false
74
+ end
75
+
53
76
  # load config
54
77
  if @options[:config]
78
+ # set log level, defaults to WARN
79
+ if @options[:log_level]
80
+ God.log_level = @options[:log_level]
81
+ else
82
+ God.log_level = :warn
83
+ end
84
+
55
85
  unless File.exist?(@options[:config])
56
86
  abort "File not found: #{@options[:config]}"
57
87
  end
58
88
 
89
+ # start the event handler
90
+ God::EventHandler.start if God::EventHandler.loaded?
91
+
59
92
  begin
60
93
  load File.expand_path(@options[:config])
61
94
  rescue Exception => e
@@ -73,6 +106,7 @@ module God
73
106
  STDIN.reopen "/dev/null"
74
107
  STDOUT.reopen(log_file, "a")
75
108
  STDERR.reopen STDOUT
109
+ STDOUT.sync = true
76
110
  rescue => e
77
111
  puts e.message
78
112
  puts e.backtrace.join("\n")
@@ -92,15 +126,28 @@ module God
92
126
  def run_in_front
93
127
  require 'god'
94
128
 
129
+ # start attached pid watcher if necessary
130
+ if @options[:attach]
131
+ self.attach
132
+ end
133
+
95
134
  if @options[:port]
96
135
  God.port = @options[:port]
97
136
  end
98
137
 
138
+ # set log level if requested
139
+ if @options[:log_level]
140
+ God.log_level = @options[:log_level]
141
+ end
142
+
99
143
  if @options[:config]
100
144
  unless File.exist?(@options[:config])
101
145
  abort "File not found: #{@options[:config]}"
102
146
  end
103
147
 
148
+ # start the event handler
149
+ God::EventHandler.start if God::EventHandler.loaded?
150
+
104
151
  begin
105
152
  load File.expand_path(@options[:config])
106
153
  rescue Exception => e
@@ -112,6 +159,17 @@ module God
112
159
  abort "There was an error in your configuration file (see above)"
113
160
  end
114
161
  end
162
+
163
+ if @options[:log]
164
+ log_file = File.expand_path(@options[:log])
165
+ puts "Sending output to log file: #{log_file}"
166
+
167
+ # reset file descriptors
168
+ STDIN.reopen "/dev/null"
169
+ STDOUT.reopen(log_file, "a")
170
+ STDERR.reopen STDOUT
171
+ STDOUT.sync = true
172
+ end
115
173
  end
116
174
  end
117
175
  end # Run