god 0.7.5 → 0.7.6

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,11 @@
1
+ == 0.7.6 / 2008-05-13
2
+ * Major Enhancements
3
+ * Implement System::Process methods for Linux based on /proc [Kevin Clark]
4
+ * Minor Enhancements
5
+ * Allowing directories to be loaded at start [Bert Goethals]
6
+ * Bug Fixes
7
+ * Don't leak events on error in the kqueue handler [Kevin Clark]
8
+
1
9
  == 0.7.5 / 2008-02-21
2
10
  * Bug Fixes
3
11
  * Remove Ruby's Logger and replace with custom SimpleLogger to stop threaded leak
@@ -48,7 +48,9 @@ lib/god/registry.rb
48
48
  lib/god/simple_logger.rb
49
49
  lib/god/socket.rb
50
50
  lib/god/sugar.rb
51
+ lib/god/system/portable_poller.rb
51
52
  lib/god/system/process.rb
53
+ lib/god/system/slash_proc_poller.rb
52
54
  lib/god/task.rb
53
55
  lib/god/timeline.rb
54
56
  lib/god/trigger.rb
@@ -96,6 +98,7 @@ test/test_process.rb
96
98
  test/test_registry.rb
97
99
  test/test_socket.rb
98
100
  test/test_sugar.rb
101
+ test/test_system_portable_poller.rb
99
102
  test/test_system_process.rb
100
103
  test/test_task.rb
101
104
  test/test_timeline.rb
data/Rakefile CHANGED
@@ -1,7 +1,7 @@
1
1
  require 'rubygems'
2
2
  require 'hoe'
3
3
 
4
- Hoe.new('god', '0.7.5') do |p|
4
+ Hoe.new('god', '0.7.6') do |p|
5
5
  p.rubyforge_name = 'god'
6
6
  p.author = 'Tom Preston-Werner'
7
7
  p.email = 'tom@rubyisawesome.com'
@@ -80,6 +80,7 @@ kqh_handle_events()
80
80
  nevents = kevent(kq, NULL, 0, events, num_to_fetch, NULL);
81
81
 
82
82
  if (-1 == nevents) {
83
+ free(events);
83
84
  rb_raise(rb_eStandardError, strerror(errno));
84
85
  } else {
85
86
  for (i = 0; i < nevents; i++) {
data/lib/god.rb CHANGED
@@ -20,7 +20,11 @@ end
20
20
  require 'god/errors'
21
21
  require 'god/simple_logger'
22
22
  require 'god/logger'
23
+
23
24
  require 'god/system/process'
25
+ require 'god/system/portable_poller'
26
+ require 'god/system/slash_proc_poller'
27
+
24
28
  require 'god/dependency_graph'
25
29
  require 'god/timeline'
26
30
  require 'god/configurable'
@@ -129,7 +133,7 @@ class Module
129
133
  end
130
134
 
131
135
  module God
132
- VERSION = '0.7.5'
136
+ VERSION = '0.7.6'
133
137
 
134
138
  LOG_BUFFER_SIZE_DEFAULT = 100
135
139
  PID_FILE_DIRECTORY_DEFAULTS = ['/var/run/god', '~/.god/pids']
@@ -33,6 +33,60 @@ module God
33
33
  end
34
34
  end
35
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
+ if @options[:log]
79
+ log_file = File.expand_path(@options[:log])
80
+ puts "Sending output to log file: #{log_file}"
81
+
82
+ # reset file descriptors
83
+ STDIN.reopen "/dev/null"
84
+ STDOUT.reopen(log_file, "a")
85
+ STDERR.reopen STDOUT
86
+ STDOUT.sync = true
87
+ end
88
+ end
89
+
36
90
  def run_daemonized
37
91
  # trap and ignore SIGHUP
38
92
  Signal.trap('HUP') {}
@@ -49,18 +103,8 @@ module God
49
103
  STDERR.reopen STDOUT
50
104
  STDOUT.sync = true
51
105
 
52
- # start attached pid watcher if necessary
53
- if @options[:attach]
54
- self.attach
55
- end
56
-
57
- # set port if requested
58
- if @options[:port]
59
- God.port = @options[:port]
60
- end
61
-
62
106
  # set pid if requested
63
- if @options[:pid]
107
+ if @options[:pid] # and as deamon
64
108
  God.pid = @options[:pid]
65
109
  end
66
110
 
@@ -68,9 +112,7 @@ module God
68
112
  Logger.syslog = false
69
113
  end
70
114
 
71
- if @options[:events]
72
- God::EventHandler.load
73
- end
115
+ default_run
74
116
 
75
117
  unless God::EventHandler.loaded?
76
118
  puts
@@ -83,34 +125,6 @@ module God
83
125
  puts
84
126
  end
85
127
 
86
- # load config
87
- if @options[:config]
88
- # set log level, defaults to WARN
89
- if @options[:log_level]
90
- God.log_level = @options[:log_level]
91
- else
92
- God.log_level = :warn
93
- end
94
-
95
- unless File.exist?(@options[:config])
96
- abort "File not found: #{@options[:config]}"
97
- end
98
-
99
- # start the event handler
100
- God::EventHandler.start if God::EventHandler.loaded?
101
-
102
- begin
103
- load File.expand_path(@options[:config])
104
- rescue Exception => e
105
- if e.instance_of?(SystemExit)
106
- raise
107
- else
108
- puts e.message
109
- puts e.backtrace.join("\n")
110
- abort "There was an error in your configuration file (see above)"
111
- end
112
- end
113
- end
114
128
  rescue => e
115
129
  puts e.message
116
130
  puts e.backtrace.join("\n")
@@ -127,63 +141,35 @@ module God
127
141
  exit
128
142
  end
129
143
 
130
- def run_in_front
131
- require 'god'
132
-
133
- if @options[:bleakhouse]
134
- BleakHouseDiagnostic.install
135
- end
136
-
137
- # start attached pid watcher if necessary
138
- if @options[:attach]
139
- self.attach
140
- end
141
-
142
- if @options[:port]
143
- God.port = @options[:port]
144
- end
145
-
146
- if @options[:events]
147
- God::EventHandler.load
148
- end
149
-
150
- # set log level if requested
151
- if @options[:log_level]
152
- God.log_level = @options[:log_level]
153
- end
154
-
155
- if @options[:config]
156
- unless File.exist?(@options[:config])
157
- abort "File not found: #{@options[:config]}"
144
+ def load_config(config)
145
+ if File.directory? config
146
+ files_loaded = false
147
+ Dir[File.expand_path('**/*.god', config)].each do |god_file|
148
+ files_loaded ||= load_god_file(File.expand_path(god_file))
158
149
  end
159
-
160
- # start the event handler
161
- God::EventHandler.start if God::EventHandler.loaded?
162
-
163
- begin
164
- load File.expand_path(@options[:config])
165
- rescue Exception => e
166
- if e.instance_of?(SystemExit)
167
- raise
168
- else
169
- puts e.message
170
- puts e.backtrace.join("\n")
171
- abort "There was an error in your configuration file (see above)"
172
- end
150
+ unless files_loaded
151
+ abort "No files could be loaded"
173
152
  end
174
-
175
- if @options[:log]
176
- log_file = File.expand_path(@options[:log])
177
- puts "Sending output to log file: #{log_file}"
178
-
179
- # reset file descriptors
180
- STDIN.reopen "/dev/null"
181
- STDOUT.reopen(log_file, "a")
182
- STDERR.reopen STDOUT
183
- STDOUT.sync = true
153
+ else
154
+ unless load_god_file(File.expand_path(config))
155
+ abort "File could not be loaded"
184
156
  end
185
157
  end
186
158
  end
159
+
160
+ def load_god_file(god_file)
161
+ load File.expand_path(god_file)
162
+ rescue Exception => e
163
+ if e.instance_of?(SystemExit)
164
+ raise
165
+ else
166
+ puts "There was an error in #{god_file}"
167
+ puts "\t" + e.message
168
+ puts "\t" + e.backtrace.join("\n\t")
169
+ return false
170
+ end
171
+ end
172
+
187
173
  end # Run
188
174
 
189
175
  end
@@ -0,0 +1,42 @@
1
+ module God
2
+ module System
3
+ class PortablePoller
4
+ def initialize(pid)
5
+ @pid = pid
6
+ end
7
+ # Memory usage in kilobytes (resident set size)
8
+ def memory
9
+ ps_int('rss')
10
+ end
11
+
12
+ # Percentage memory usage
13
+ def percent_memory
14
+ ps_float('%mem')
15
+ end
16
+
17
+ # Percentage CPU usage
18
+ def percent_cpu
19
+ ps_float('%cpu')
20
+ end
21
+
22
+ private
23
+
24
+ def ps_int(keyword)
25
+ `ps -o #{keyword}= -p #{@pid}`.to_i
26
+ end
27
+
28
+ def ps_float(keyword)
29
+ `ps -o #{keyword}= -p #{@pid}`.to_f
30
+ end
31
+
32
+ def ps_string(keyword)
33
+ `ps -o #{keyword}= -p #{@pid}`.strip
34
+ end
35
+
36
+ def time_string_to_seconds(text)
37
+ _, minutes, seconds, useconds = *text.match(/(\d+):(\d{2}).(\d{2})/)
38
+ (minutes.to_i * 60) + seconds.to_i
39
+ end
40
+ end
41
+ end
42
+ end
@@ -4,6 +4,7 @@ module God
4
4
  class Process
5
5
  def initialize(pid)
6
6
  @pid = pid.to_i
7
+ @poller = fetch_system_poller.new(@pid)
7
8
  end
8
9
 
9
10
  # Return true if this process is running, false otherwise
@@ -13,41 +14,27 @@ module God
13
14
 
14
15
  # Memory usage in kilobytes (resident set size)
15
16
  def memory
16
- ps_int('rss')
17
+ @poller.memory
17
18
  end
18
19
 
19
20
  # Percentage memory usage
20
21
  def percent_memory
21
- ps_float('%mem')
22
+ @poller.percent_memory
22
23
  end
23
24
 
24
25
  # Percentage CPU usage
25
26
  def percent_cpu
26
- ps_float('%cpu')
27
- end
28
-
29
- # Seconds of CPU time (accumulated cpu time, user + system)
30
- def cpu_time
31
- time_string_to_seconds(ps_string('time'))
27
+ @poller.percent_cpu
32
28
  end
33
29
 
34
30
  private
35
31
 
36
- def ps_int(keyword)
37
- `ps -o #{keyword}= -p #{@pid}`.to_i
38
- end
39
-
40
- def ps_float(keyword)
41
- `ps -o #{keyword}= -p #{@pid}`.to_f
42
- end
43
-
44
- def ps_string(keyword)
45
- `ps -o #{keyword}= -p #{@pid}`.strip
46
- end
47
-
48
- def time_string_to_seconds(text)
49
- _, minutes, seconds, useconds = *text.match(/(\d+):(\d{2}).(\d{2})/)
50
- (minutes.to_i * 60) + seconds.to_i
32
+ def fetch_system_poller
33
+ if test(?d, '/proc')
34
+ SlashProcPoller
35
+ else
36
+ PortablePoller
37
+ end
51
38
  end
52
39
  end
53
40
 
@@ -0,0 +1,62 @@
1
+ module God
2
+ module System
3
+ class SlashProcPoller < PortablePoller
4
+ @@kb_per_page = 4 # TODO: Need to make this portable
5
+ @@hertz = 100
6
+ @@total_mem = nil
7
+
8
+ def initialize(pid)
9
+ super(pid)
10
+
11
+ unless @@total_mem # in K
12
+ File.open("/proc/meminfo") do |f|
13
+ @@total_mem = f.gets.split[1]
14
+ end
15
+ end
16
+ end
17
+
18
+ def memory
19
+ stat[:rss].to_i * @@kb_per_page
20
+ end
21
+
22
+ def percent_memory
23
+ (memory / @@total_mem.to_f) * 100
24
+ end
25
+
26
+ # TODO: Change this to calculate the wma instead
27
+ def percent_cpu
28
+ stats = stat
29
+ total_time = stats[:utime].to_i + stats[:stime].to_i # in jiffies
30
+ seconds = uptime - stats[:starttime].to_i / @@hertz
31
+ if seconds == 0
32
+ 0
33
+ else
34
+ ((total_time * 1000 / @@hertz) / seconds) / 10
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ # in seconds
41
+ def uptime
42
+ File.read('/proc/uptime').split[0].to_f
43
+ end
44
+
45
+ def stat
46
+ stats = {}
47
+ stats[:pid], stats[:comm], stats[:state], stats[:ppid], stats[:pgrp],
48
+ stats[:session], stats[:tty_nr], stats[:tpgid], stats[:flags],
49
+ stats[:minflt], stats[:cminflt], stats[:majflt], stats[:cmajflt],
50
+ stats[:utime], stats[:stime], stats[:cutime], stats[:cstime],
51
+ stats[:priority], stats[:nice], _, stats[:itrealvalue],
52
+ stats[:starttime], stats[:vsize], stats[:rss], stats[:rlim],
53
+ stats[:startcode], stats[:endcode], stats[:startstack], stats[:kstkesp],
54
+ stats[:kstkeip], stats[:signal], stats[:blocked], stats[:sigignore],
55
+ stats[:sigcatch], stats[:wchan], stats[:nswap], stats[:cnswap],
56
+ stats[:exit_signal], stats[:processor], stats[:rt_priority],
57
+ stats[:policy] = File.read("/proc/#{@pid}/stat").split
58
+ stats
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,17 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ class TestSystemPortablePoller < Test::Unit::TestCase
4
+ def setup
5
+ pid = Process.pid
6
+ @process = System::PortablePoller.new(pid)
7
+ end
8
+
9
+ def test_time_string_to_seconds
10
+ assert_equal 0, @process.bypass.time_string_to_seconds('0:00:00')
11
+ assert_equal 0, @process.bypass.time_string_to_seconds('0:00:55')
12
+ assert_equal 27, @process.bypass.time_string_to_seconds('0:27:32')
13
+ assert_equal 75, @process.bypass.time_string_to_seconds('1:15:13')
14
+ assert_equal 735, @process.bypass.time_string_to_seconds('12:15:13')
15
+ end
16
+ end
17
+
@@ -26,17 +26,5 @@ class TestSystemProcess < Test::Unit::TestCase
26
26
  def test_percent_cpu
27
27
  assert_kind_of Float, @process.percent_cpu
28
28
  end
29
-
30
- def test_cpu_time
31
- assert_kind_of Integer, @process.cpu_time
32
- end
33
-
34
- def test_time_string_to_seconds
35
- assert_equal 0, @process.bypass.time_string_to_seconds('0:00:00')
36
- assert_equal 0, @process.bypass.time_string_to_seconds('0:00:55')
37
- assert_equal 27, @process.bypass.time_string_to_seconds('0:27:32')
38
- assert_equal 75, @process.bypass.time_string_to_seconds('1:15:13')
39
- assert_equal 735, @process.bypass.time_string_to_seconds('12:15:13')
40
- end
41
29
  end
42
30
 
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.2
3
3
  specification_version: 1
4
4
  name: god
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.7.5
7
- date: 2008-05-13 00:00:00 -07:00
6
+ version: 0.7.6
7
+ date: 2008-05-21 00:00:00 -07:00
8
8
  summary: Like monit, only awesome
9
9
  require_paths:
10
10
  - lib
@@ -80,7 +80,9 @@ files:
80
80
  - lib/god/simple_logger.rb
81
81
  - lib/god/socket.rb
82
82
  - lib/god/sugar.rb
83
+ - lib/god/system/portable_poller.rb
83
84
  - lib/god/system/process.rb
85
+ - lib/god/system/slash_proc_poller.rb
84
86
  - lib/god/task.rb
85
87
  - lib/god/timeline.rb
86
88
  - lib/god/trigger.rb
@@ -128,6 +130,7 @@ files:
128
130
  - test/test_registry.rb
129
131
  - test/test_socket.rb
130
132
  - test/test_sugar.rb
133
+ - test/test_system_portable_poller.rb
131
134
  - test/test_system_process.rb
132
135
  - test/test_task.rb
133
136
  - test/test_timeline.rb
@@ -152,6 +155,7 @@ test_files:
152
155
  - test/test_registry.rb
153
156
  - test/test_socket.rb
154
157
  - test/test_sugar.rb
158
+ - test/test_system_portable_poller.rb
155
159
  - test/test_system_process.rb
156
160
  - test/test_task.rb
157
161
  - test/test_timeline.rb