god 0.7.22 → 0.8.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.
@@ -1,3 +1,23 @@
1
+ == 0.8.0 / 2009-11-30
2
+ * Minor Enhancements
3
+ * Rubygems decontamination
4
+ * Use Monitor instead of Mutex to provide ability to wait with a timeout
5
+ * Only generate log messages when they're being used
6
+ * Remove usage of Thread.critical in DriverEventQueue
7
+ * Update to work with latest bleak-house
8
+ * Cache some frequent lookups to reduce object creation
9
+ * Changing the @io.print call in SimpleLogger to not concatenate
10
+ the formatted results before printing
11
+ * Bug fixes
12
+ * Make sure we don't leak hash slots when processes die
13
+ * Make sure the driver is shutdown on Task#unregister!
14
+ * Fix memory leak when issuing "god load" successfully
15
+ * Fix defunct process
16
+
17
+ == NOTE
18
+ At this point I will stop giving credit in the history. Look at the author
19
+ and committer in the commit for that info.
20
+
1
21
  == 0.7.22 / 2009-10-29
2
22
  * Minor Enhancements
3
23
  * Save ARGV so we can get access to it later if we want [github.com/eric]
@@ -1,4 +1,5 @@
1
1
  ---
2
2
  :major: 0
3
- :minor: 7
4
- :patch: 22
3
+ :minor: 8
4
+ :build:
5
+ :patch: 0
@@ -1,20 +1,20 @@
1
1
  # Generated by jeweler
2
- # DO NOT EDIT THIS FILE
3
- # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in rakefile, and run the gemspec command
4
4
  # -*- encoding: utf-8 -*-
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{god}
8
- s.version = "0.7.22"
8
+ s.version = "0.8.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Tom Preston-Werner"]
12
- s.date = %q{2009-10-29}
12
+ s.date = %q{2009-11-30}
13
13
  s.default_executable = %q{god}
14
14
  s.description = %q{God is an easy to configure, easy to extend monitoring framework written in Ruby.}
15
15
  s.email = %q{tom@mojombo.com}
16
16
  s.executables = ["god"]
17
- s.extensions = ["ext/god/extconf.rb"]
17
+ s.extensions = ["ext/god/extconf.rb", "ext/god/extconf.rb"]
18
18
  s.extra_rdoc_files = [
19
19
  "README.txt"
20
20
  ]
@@ -224,3 +224,4 @@ Gem::Specification.new do |s|
224
224
  else
225
225
  end
226
226
  end
227
+
data/lib/god.rb CHANGED
@@ -1,8 +1,5 @@
1
1
  $:.unshift File.dirname(__FILE__) # For use/testing when no gem is installed
2
2
 
3
- # rubygems
4
- require 'rubygems'
5
-
6
3
  # core
7
4
  require 'stringio'
8
5
  require 'fileutils'
@@ -534,6 +531,9 @@ module God
534
531
  watches = self.pending_watches.dup
535
532
  self.pending_watches.clear
536
533
  self.pending_watch_states.clear
534
+
535
+ # make sure we quit capturing when we're done
536
+ LOG.finish_capture
537
537
  rescue Exception => e
538
538
  # don't ever let running_load take down god
539
539
  errors << LOG.finish_capture
@@ -215,6 +215,7 @@ module God
215
215
  puts "killing process"
216
216
 
217
217
  ::Process.kill('KILL', pid)
218
+ ::Process.waitpid(pid)
218
219
  rescue => e
219
220
  puts e.message
220
221
  puts e.backtrace.join("\n")
@@ -10,7 +10,6 @@
10
10
  # end
11
11
 
12
12
  module XMPP4R
13
- require 'rubygems'
14
13
  require 'xmpp4r'
15
14
  include Jabber
16
15
  end
@@ -10,7 +10,6 @@
10
10
  # c.group = 'developers'
11
11
  # end
12
12
 
13
- require 'rubygems'
14
13
  require 'twitter'
15
14
 
16
15
  module God
@@ -12,26 +12,26 @@ end
12
12
  class BleakHouseDiagnostic
13
13
  LOG_FILE = File.join(File.dirname(__FILE__), *%w[.. .. logs bleak.log])
14
14
 
15
- class << self
16
- attr_accessor :logger
17
- end
18
-
19
15
  def self.install
20
- require 'bleak_house'
21
- self.logger = BleakHouse::Logger.new
22
- File.delete(LOG_FILE) rescue nil
16
+ require 'snapshot'
17
+ self.spin
23
18
  end
24
19
 
25
- def self.snapshot(name)
26
- self.logger.snapshot(LOG_FILE, name, false) if self.logger
20
+ def self.snapshot
21
+ @count ||= 0
22
+ filename = "/tmp/god-bleak-%s-%03i.dump" % [Process.pid,@count]
23
+ STDERR.puts "** BleakHouse: working..."
24
+ BleakHouse.ext_snapshot(filename, 3)
25
+ STDERR.puts "** BleakHouse: complete\n** Bleakhouse: Run 'bleak #{filename}' to analyze."
26
+ @count += 1
27
27
  end
28
28
 
29
- def self.spin(delay = 1)
29
+ def self.spin(delay = 60)
30
30
  Thread.new do
31
31
  loop do
32
- self.snapshot
33
32
  sleep(delay)
33
+ self.snapshot
34
34
  end
35
35
  end
36
36
  end
37
- end
37
+ end
@@ -1,3 +1,5 @@
1
+ require 'monitor'
2
+
1
3
  module God
2
4
  class TimedEvent
3
5
  include Comparable
@@ -52,29 +54,24 @@ module God
52
54
  @task.send(@name, *@args)
53
55
  end
54
56
  end
55
-
56
- class DriverEventQueue
57
+
58
+ class DriverEventQueue
57
59
  def initialize
58
60
  @shutdown = false
59
- @waiting = []
60
61
  @events = []
61
- @waiting.taint
62
+ @monitor = Monitor.new
63
+ @resource = @monitor.new_cond
62
64
  @events.taint
63
65
  self.taint
64
66
  end
65
67
 
66
- #
68
+ #
67
69
  # Wake any sleeping threads after setting the sentinel
68
- #
70
+ #
69
71
  def shutdown
70
72
  @shutdown = true
71
- begin
72
- Thread.critical = true
73
- @waiting.each do |t|
74
- t.run
75
- end
76
- ensure
77
- Thread.critical = false
73
+ @monitor.synchronize do
74
+ @resource.broadcast
78
75
  end
79
76
  end
80
77
 
@@ -82,47 +79,34 @@ module God
82
79
  # Sleep until the queue has something due
83
80
  #
84
81
  def pop
85
- begin
86
- while (Thread.critical = true; @events.empty? or !@events.first.due?)
87
- @waiting.push Thread.current
88
- if @events.empty?
89
- raise ThreadError, "queue empty" if @shutdown
90
- Thread.stop
91
- else
92
- Thread.critical = false
93
- delay = @events.first.at - Time.now
94
- sleep delay if delay > 0
95
- Thread.critical = true
96
- end
82
+ @monitor.synchronize do
83
+ if @events.empty?
84
+ raise ThreadError, "queue empty" if @shutdown
85
+ @resource.wait
86
+ else !@events.first.due?
87
+ delay = @events.first.at - Time.now
88
+ @resource.wait(delay) if delay > 0
97
89
  end
90
+
98
91
  @events.shift
99
- ensure
100
- Thread.critical = false
101
92
  end
102
93
  end
103
94
 
104
95
  alias shift pop
105
96
  alias deq pop
106
97
 
107
- #
108
- # Add an event to the queue, wake any waiters if what we added needs to
98
+ #
99
+ # Add an event to the queue, wake any waiters if what we added needs to
109
100
  # happen sooner than the next pending event
110
101
  #
111
102
  def push(event)
112
- Thread.critical = true
113
- @events << event
114
- @events.sort!
115
- begin
116
- t = @waiting.shift if @events.first == event
117
- t.wakeup if t
118
- rescue ThreadError
119
- retry
120
- ensure
121
- Thread.critical = false
122
- end
123
- begin
124
- t.run if t
125
- rescue ThreadError
103
+ @monitor.synchronize do
104
+ @events << event
105
+ @events.sort!
106
+
107
+ # If we've sorted the events and found the one we're adding is at
108
+ # the front, it will likely need to run before the next due date
109
+ @resource.signal if @events.first == event
126
110
  end
127
111
  end
128
112
 
@@ -130,7 +114,7 @@ module God
130
114
  alias enq push
131
115
 
132
116
  def empty?
133
- @que.empty?
117
+ @events.empty?
134
118
  end
135
119
 
136
120
  def clear
@@ -142,16 +126,12 @@ module God
142
126
  end
143
127
 
144
128
  alias size length
145
-
146
- def num_waiting
147
- @waiting.size
148
- end
149
129
  end
150
130
 
151
131
 
152
132
  class Driver
153
133
  attr_reader :thread
154
-
134
+
155
135
  # Instantiate a new Driver and start the scheduler loop to handle events
156
136
  # +task+ is the Task this Driver belongs to
157
137
  #
@@ -176,12 +156,26 @@ module God
176
156
  end
177
157
  end
178
158
 
159
+ # Check if we're in the driver context
160
+ #
161
+ # Returns true if in driver thread
162
+ def in_driver_context?
163
+ Thread.current == @thread
164
+ end
165
+
179
166
  # Clear all events for this Driver
180
167
  #
181
168
  # Returns nothing
182
169
  def clear_events
183
170
  @events.clear
184
171
  end
172
+
173
+ # Shutdown the DriverEventQueue threads
174
+ #
175
+ # Returns nothing
176
+ def shutdown
177
+ @events.shutdown
178
+ end
185
179
 
186
180
  # Queue an asynchronous message
187
181
  # +name+ is the Symbol name of the operation
@@ -38,16 +38,12 @@ module God
38
38
  @@handler.register_process(pid, @@actions[pid].keys)
39
39
  end
40
40
 
41
- def self.deregister(pid, event=nil)
41
+ def self.deregister(pid, event)
42
42
  if watching_pid? pid
43
43
  running = ::Process.kill(0, pid.to_i) rescue false
44
- if event.nil?
45
- @@actions.delete(pid)
46
- @@handler.register_process(pid, []) if running
47
- else
48
- @@actions[pid].delete(event)
49
- @@handler.register_process(pid, @@actions[pid].keys) if running
50
- end
44
+ @@actions[pid].delete(event)
45
+ @@handler.register_process(pid, @@actions[pid].keys) if running
46
+ @@actions.delete(pid) if @@actions[pid].empty?
51
47
  end
52
48
  end
53
49
 
@@ -92,6 +88,7 @@ module God
92
88
  end
93
89
 
94
90
  ::Process.kill('KILL', pid)
91
+ ::Process.waitpid(pid)
95
92
 
96
93
  sleep(0.1)
97
94
 
@@ -108,4 +105,4 @@ module God
108
105
  end
109
106
 
110
107
  end
111
- end
108
+ end
@@ -58,21 +58,27 @@ module God
58
58
  def log(watch, level, text)
59
59
  # initialize watch log if necessary
60
60
  self.logs[watch.name] ||= Timeline.new(God::LOG_BUFFER_SIZE_DEFAULT) if watch
61
-
61
+
62
62
  # push onto capture and timeline for the given watch
63
- @templogio.truncate(0)
64
- @templogio.rewind
65
- @templog.send(level, text % [])
66
- @mutex.synchronize do
67
- @capture.puts(@templogio.string.dup) if @capture
68
- if watch && (Time.now - @spool < 2)
69
- self.logs[watch.name] << [Time.now, @templogio.string.dup]
63
+ if @capture || (watch && (Time.now - @spool < 2))
64
+ @mutex.synchronize do
65
+ @templogio.truncate(0)
66
+ @templogio.rewind
67
+ @templog.send(level, text)
68
+
69
+ message = @templogio.string.dup
70
+
71
+ if @capture
72
+ @capture.puts(message)
73
+ else
74
+ self.logs[watch.name] << [Time.now, message]
75
+ end
70
76
  end
71
77
  end
72
-
78
+
73
79
  # send to regular logger
74
- self.send(level, text % [])
75
-
80
+ self.send(level, text)
81
+
76
82
  # send to syslog
77
83
  Syslog.send(SYSLOG_EQUIVALENTS[level], text) if Logger.syslog
78
84
  end
@@ -114,7 +120,7 @@ module God
114
120
  # Returns String
115
121
  def finish_capture
116
122
  @mutex.synchronize do
117
- cap = @capture.string
123
+ cap = @capture.string if @capture
118
124
  @capture = nil
119
125
  cap
120
126
  end
@@ -144,7 +144,7 @@ module God
144
144
  #
145
145
  # Returns Task (self)
146
146
  def move(to_state)
147
- if Thread.current != self.driver.thread
147
+ if !self.driver.in_driver_context?
148
148
  # called from outside Driver
149
149
 
150
150
  # send an async message to Driver
@@ -235,7 +235,7 @@ module God
235
235
  #
236
236
  # Returns Task (self)
237
237
  def action(a, c = nil)
238
- if Thread.current != self.driver.thread
238
+ if !self.driver.in_driver_context?
239
239
  # called from outside Driver
240
240
 
241
241
  # send an async message to Driver
@@ -299,7 +299,7 @@ module God
299
299
  end
300
300
 
301
301
  def unregister!
302
- # override if necessary
302
+ driver.shutdown
303
303
  end
304
304
 
305
305
  ###########################################################################
@@ -104,7 +104,7 @@ module God
104
104
  ###########################################################################
105
105
 
106
106
  def action(a, c = nil)
107
- if Thread.current != self.driver.thread
107
+ if !self.driver.in_driver_context?
108
108
  # called from outside Driver
109
109
 
110
110
  # send an async message to Driver
@@ -177,6 +177,7 @@ module God
177
177
 
178
178
  def unregister!
179
179
  God.registry.remove(@process)
180
+ super
180
181
  end
181
182
  end
182
183
 
@@ -1,3 +1,4 @@
1
+ require 'rubygems'
1
2
  require File.join(File.dirname(__FILE__), *%w[.. lib god])
2
3
  God::EventHandler.load
3
4
 
@@ -61,20 +61,20 @@ class TestTask < Test::Unit::TestCase
61
61
 
62
62
  def test_action_should_send_string_commands_to_system
63
63
  @task.foo = 'foo'
64
- Thread.current.stubs(:==).returns(true)
64
+ @task.driver.stubs(:in_driver_context?).returns(true)
65
65
  @task.expects(:system).with('foo')
66
66
  @task.action(:foo, nil)
67
67
  end
68
68
 
69
69
  def test_action_should_call_lambda_commands
70
70
  @task.foo = lambda { }
71
- Thread.current.stubs(:==).returns(true)
71
+ @task.driver.stubs(:in_driver_context?).returns(true)
72
72
  @task.foo.expects(:call)
73
73
  @task.action(:foo, nil)
74
74
  end
75
75
 
76
76
  def test_action_should_raise_not_implemented_on_non_string_or_lambda_action
77
- Thread.current.stubs(:==).returns(true)
77
+ @task.driver.stubs(:in_driver_context?).returns(true)
78
78
  assert_raise NotImplementedError do
79
79
  @task.foo = 7
80
80
  @task.action(:foo, nil)
@@ -126,7 +126,7 @@ class TestWatch < Test::Unit::TestCase
126
126
  # move
127
127
 
128
128
  def test_move_should_not_clean_up_if_from_state_is_nil
129
- Thread.current.stubs(:==).returns(true)
129
+ @watch.driver.stubs(:in_driver_context?).returns(true)
130
130
  @watch.driver.expects(:message).never
131
131
 
132
132
  metric = nil
@@ -147,7 +147,7 @@ class TestWatch < Test::Unit::TestCase
147
147
  end
148
148
 
149
149
  def test_move_should_clean_up_from_state_if_not_nil
150
- Thread.current.stubs(:==).returns(true)
150
+ @watch.driver.stubs(:in_driver_context?).returns(true)
151
151
  @watch.driver.expects(:message).never
152
152
 
153
153
  metric = nil
@@ -170,7 +170,7 @@ class TestWatch < Test::Unit::TestCase
170
170
  end
171
171
 
172
172
  def test_move_should_call_action
173
- Thread.current.stubs(:==).returns(true)
173
+ @watch.driver.stubs(:in_driver_context?).returns(true)
174
174
  @watch.driver.expects(:message).never
175
175
 
176
176
  @watch.expects(:action).with(:start)
@@ -179,7 +179,7 @@ class TestWatch < Test::Unit::TestCase
179
179
  end
180
180
 
181
181
  def test_move_should_move_to_up_state_if_no_start_or_restart_metric
182
- Thread.current.stubs(:==).returns(true)
182
+ @watch.driver.stubs(:in_driver_context?).returns(true)
183
183
  @watch.driver.expects(:message).never
184
184
 
185
185
  [:start, :restart].each do |state|
@@ -190,7 +190,7 @@ class TestWatch < Test::Unit::TestCase
190
190
  end
191
191
 
192
192
  def test_move_should_enable_destination_metric
193
- Thread.current.stubs(:==).returns(true)
193
+ @watch.driver.stubs(:in_driver_context?).returns(true)
194
194
  @watch.driver.expects(:message).never
195
195
 
196
196
  metric = nil
@@ -213,7 +213,7 @@ class TestWatch < Test::Unit::TestCase
213
213
  # action
214
214
 
215
215
  def test_action_should_pass_start_and_stop_actions_to_call_action
216
- Thread.current.stubs(:==).returns(true)
216
+ @watch.driver.stubs(:in_driver_context?).returns(true)
217
217
  @watch.driver.expects(:message).never
218
218
 
219
219
  c = Conditions::FakePollCondition.new
@@ -224,7 +224,7 @@ class TestWatch < Test::Unit::TestCase
224
224
  end
225
225
 
226
226
  def test_action_should_do_stop_then_start_if_no_restart_command
227
- Thread.current.stubs(:==).returns(true)
227
+ @watch.driver.stubs(:in_driver_context?).returns(true)
228
228
  @watch.driver.expects(:message).never
229
229
 
230
230
  c = Conditions::FakePollCondition.new
@@ -234,7 +234,7 @@ class TestWatch < Test::Unit::TestCase
234
234
  end
235
235
 
236
236
  def test_action_should_restart_to_call_action_if_present
237
- Thread.current.stubs(:==).returns(true)
237
+ @watch.driver.stubs(:in_driver_context?).returns(true)
238
238
  @watch.driver.expects(:message).never
239
239
 
240
240
  @watch.restart = lambda { }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: god
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.22
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tom Preston-Werner
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-10-29 00:00:00 -07:00
12
+ date: 2009-11-30 00:00:00 -08:00
13
13
  default_executable: god
14
14
  dependencies: []
15
15
 
@@ -19,6 +19,7 @@ executables:
19
19
  - god
20
20
  extensions:
21
21
  - ext/god/extconf.rb
22
+ - ext/god/extconf.rb
22
23
  extra_rdoc_files:
23
24
  - README.txt
24
25
  files: