bluepill-rwgps 0.0.60

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 (48) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.rspec +1 -0
  4. data/DESIGN.md +10 -0
  5. data/Gemfile +10 -0
  6. data/LICENSE +22 -0
  7. data/README.md +10 -0
  8. data/Rakefile +38 -0
  9. data/bin/bluepill +124 -0
  10. data/bin/bpsv +3 -0
  11. data/bin/sample_forking_server +53 -0
  12. data/bluepill-rwgps.gemspec +36 -0
  13. data/examples/example.rb +87 -0
  14. data/examples/new_example.rb +89 -0
  15. data/examples/new_runit_example.rb +29 -0
  16. data/examples/runit_example.rb +26 -0
  17. data/lib/bluepill/application/client.rb +8 -0
  18. data/lib/bluepill/application/server.rb +23 -0
  19. data/lib/bluepill/application.rb +205 -0
  20. data/lib/bluepill/condition_watch.rb +50 -0
  21. data/lib/bluepill/controller.rb +121 -0
  22. data/lib/bluepill/dsl/app_proxy.rb +25 -0
  23. data/lib/bluepill/dsl/process_factory.rb +122 -0
  24. data/lib/bluepill/dsl/process_proxy.rb +44 -0
  25. data/lib/bluepill/dsl.rb +12 -0
  26. data/lib/bluepill/group.rb +72 -0
  27. data/lib/bluepill/logger.rb +63 -0
  28. data/lib/bluepill/process.rb +490 -0
  29. data/lib/bluepill/process_conditions/always_true.rb +18 -0
  30. data/lib/bluepill/process_conditions/cpu_usage.rb +19 -0
  31. data/lib/bluepill/process_conditions/http.rb +58 -0
  32. data/lib/bluepill/process_conditions/mem_usage.rb +32 -0
  33. data/lib/bluepill/process_conditions/process_condition.rb +22 -0
  34. data/lib/bluepill/process_conditions.rb +14 -0
  35. data/lib/bluepill/process_statistics.rb +27 -0
  36. data/lib/bluepill/socket.rb +58 -0
  37. data/lib/bluepill/system.rb +238 -0
  38. data/lib/bluepill/trigger.rb +60 -0
  39. data/lib/bluepill/triggers/flapping.rb +56 -0
  40. data/lib/bluepill/util/rotational_array.rb +20 -0
  41. data/lib/bluepill/version.rb +4 -0
  42. data/lib/bluepill.rb +38 -0
  43. data/local-bluepill +129 -0
  44. data/spec/lib/bluepill/logger_spec.rb +3 -0
  45. data/spec/lib/bluepill/process_statistics_spec.rb +24 -0
  46. data/spec/lib/bluepill/system_spec.rb +36 -0
  47. data/spec/spec_helper.rb +19 -0
  48. metadata +264 -0
@@ -0,0 +1,205 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require 'thread'
3
+
4
+ module Bluepill
5
+ class Application
6
+ PROCESS_COMMANDS = [:start, :stop, :restart, :unmonitor, :status]
7
+
8
+ attr_accessor :name, :logger, :base_dir, :socket, :pid_file, :kill_timeout
9
+ attr_accessor :groups, :work_queue
10
+ attr_accessor :pids_dir, :log_file
11
+
12
+ def initialize(name, options = {})
13
+ self.name = name
14
+
15
+ @foreground = options[:foreground]
16
+ self.log_file = options[:log_file]
17
+ self.base_dir = options[:base_dir] || '/var/run/bluepill'
18
+ self.pid_file = File.join(self.base_dir, 'pids', self.name + ".pid")
19
+ self.pids_dir = File.join(self.base_dir, 'pids', self.name)
20
+ self.kill_timeout = options[:kill_timeout] || 10
21
+
22
+ self.groups = {}
23
+
24
+ self.logger = Bluepill::Logger.new(:log_file => self.log_file, :stdout => foreground?).prefix_with(self.name)
25
+
26
+ self.setup_signal_traps
27
+ self.setup_pids_dir
28
+
29
+ @mutex = Mutex.new
30
+ end
31
+
32
+ def foreground?
33
+ !!@foreground
34
+ end
35
+
36
+ def mutex(&b)
37
+ @mutex.synchronize(&b)
38
+ end
39
+
40
+ def load
41
+ begin
42
+ self.start_server
43
+ rescue StandardError => e
44
+ $stderr.puts "Failed to start bluepill:"
45
+ $stderr.puts "%s `%s`" % [e.class.name, e.message]
46
+ $stderr.puts e.backtrace
47
+ exit(5)
48
+ end
49
+ end
50
+
51
+ PROCESS_COMMANDS.each do |command|
52
+ class_eval <<-END
53
+ def #{command}(group_name = nil, process_name = nil)
54
+ self.send_to_process_or_group(:#{command}, group_name, process_name)
55
+ end
56
+ END
57
+ end
58
+
59
+ def add_process(process, group_name = nil)
60
+ group_name = group_name.to_s if group_name
61
+
62
+ self.groups[group_name] ||= Group.new(group_name, :logger => self.logger.prefix_with(group_name))
63
+ self.groups[group_name].add_process(process)
64
+ end
65
+
66
+ def version
67
+ Bluepill::VERSION
68
+ end
69
+
70
+ protected
71
+ def send_to_process_or_group(method, group_name, process_name)
72
+ if group_name.nil? && process_name.nil?
73
+ self.groups.values.collect do |group|
74
+ group.send(method)
75
+ end.flatten
76
+ elsif self.groups.key?(group_name)
77
+ self.groups[group_name].send(method, process_name)
78
+ elsif process_name.nil?
79
+ # they must be targeting just by process name
80
+ process_name = group_name
81
+ self.groups.values.collect do |group|
82
+ group.send(method, process_name)
83
+ end.flatten
84
+ else
85
+ []
86
+ end
87
+ end
88
+
89
+ def start_listener
90
+ @listener_thread.kill if @listener_thread
91
+ @listener_thread = Thread.new do
92
+ loop do
93
+ begin
94
+ client = self.socket.accept
95
+ client.close_on_exec = true if client.respond_to?(:close_on_exec=)
96
+ command, *args = client.readline.strip.split(":")
97
+ response = begin
98
+ mutex { self.send(command, *args) }
99
+ rescue Exception => e
100
+ e
101
+ end
102
+ client.write(Marshal.dump(response))
103
+ rescue StandardError => e
104
+ logger.err("Got exception in cmd listener: %s `%s`" % [e.class.name, e.message])
105
+ e.backtrace.each {|l| logger.err(l)}
106
+ ensure
107
+ begin
108
+ client.close
109
+ rescue IOError
110
+ # closed stream
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
116
+
117
+ def start_server
118
+ self.kill_previous_bluepill
119
+
120
+ Daemonize.daemonize unless foreground?
121
+
122
+ self.logger.reopen
123
+
124
+ $0 = "bluepilld: #{self.name}"
125
+
126
+ self.groups.each {|_, group| group.determine_initial_state }
127
+
128
+
129
+ self.write_pid_file
130
+ self.socket = Bluepill::Socket.server(self.base_dir, self.name)
131
+ self.start_listener
132
+
133
+ self.run
134
+ end
135
+
136
+ def run
137
+ @running = true # set to false by signal trap
138
+ while @running
139
+ mutex do
140
+ System.reset_data
141
+ self.groups.each { |_, group| group.tick }
142
+ end
143
+ sleep 1
144
+ end
145
+ cleanup
146
+ end
147
+
148
+ def cleanup
149
+ File.unlink(self.socket.path) if self.socket
150
+ File.unlink(self.pid_file) if File.exists?(self.pid_file)
151
+ end
152
+
153
+ def setup_signal_traps
154
+ terminator = Proc.new do
155
+ puts "Terminating..."
156
+ @running = false
157
+ end
158
+
159
+ Signal.trap("TERM", &terminator)
160
+ Signal.trap("INT", &terminator)
161
+
162
+ Signal.trap("HUP") do
163
+ self.logger.reopen if self.logger
164
+ end
165
+ end
166
+
167
+ def setup_pids_dir
168
+ FileUtils.mkdir_p(self.pids_dir) unless File.exists?(self.pids_dir)
169
+ # we need everybody to be able to write to the pids_dir as processes managed by
170
+ # bluepill will be writing to this dir after they've dropped privileges
171
+ FileUtils.chmod(0777, self.pids_dir)
172
+ end
173
+
174
+ def kill_previous_bluepill
175
+ if File.exists?(self.pid_file)
176
+ previous_pid = File.read(self.pid_file).to_i
177
+ if System.pid_alive?(previous_pid)
178
+ begin
179
+ ::Process.kill(0, previous_pid)
180
+ puts "Killing previous bluepilld[#{previous_pid}]"
181
+ ::Process.kill(2, previous_pid)
182
+ rescue Exception => e
183
+ $stderr.puts "Encountered error trying to kill previous bluepill:"
184
+ $stderr.puts "#{e.class}: #{e.message}"
185
+ exit(4) unless e.is_a?(Errno::ESRCH)
186
+ else
187
+ kill_timeout.times do |i|
188
+ sleep 0.5
189
+ break unless System.pid_alive?(previous_pid)
190
+ end
191
+
192
+ if System.pid_alive?(previous_pid)
193
+ $stderr.puts "Previous bluepilld[#{previous_pid}] didn't die"
194
+ exit(4)
195
+ end
196
+ end
197
+ end
198
+ end
199
+ end
200
+
201
+ def write_pid_file
202
+ File.open(self.pid_file, 'w') { |x| x.write(::Process.pid) }
203
+ end
204
+ end
205
+ end
@@ -0,0 +1,50 @@
1
+ # -*- encoding: utf-8 -*-
2
+ module Bluepill
3
+ class HistoryValue < Struct.new(:value, :critical)
4
+ end
5
+
6
+ class ConditionWatch
7
+ attr_accessor :logger, :name
8
+ EMPTY_ARRAY = [].freeze # no need to recreate one every tick
9
+
10
+ def initialize(name, options = {})
11
+ @name = name
12
+
13
+ @logger = options.delete(:logger)
14
+ @fires = options.has_key?(:fires) ? Array(options.delete(:fires)) : [:restart]
15
+ @every = options.delete(:every)
16
+ @times = options.delete(:times) || [1,1]
17
+ @times = [@times, @times] unless @times.is_a?(Array) # handles :times => 5
18
+
19
+ self.clear_history!
20
+
21
+ @process_condition = ProcessConditions[@name].new(options)
22
+ end
23
+
24
+ def run(pid, tick_number = Time.now.to_i)
25
+ if @last_ran_at.nil? || (@last_ran_at + @every) <= tick_number
26
+ @last_ran_at = tick_number
27
+
28
+ value = @process_condition.run(pid)
29
+ @history << HistoryValue.new(@process_condition.format_value(value), @process_condition.check(value))
30
+ self.logger.info(self.to_s)
31
+
32
+ return @fires if self.fired?
33
+ end
34
+ EMPTY_ARRAY
35
+ end
36
+
37
+ def clear_history!
38
+ @history = Util::RotationalArray.new(@times.last)
39
+ end
40
+
41
+ def fired?
42
+ @history.count {|v| not v.critical} >= @times.first
43
+ end
44
+
45
+ def to_s
46
+ data = @history.collect {|v| "#{v.value}#{'*' unless v.critical}"}.join(", ")
47
+ "#{@name}: [#{data}]\n"
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,121 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require 'fileutils'
3
+
4
+ module Bluepill
5
+ class Controller
6
+ attr_accessor :base_dir, :log_file, :sockets_dir, :pids_dir
7
+
8
+ def initialize(options = {})
9
+ self.log_file = options[:log_file]
10
+ self.base_dir = options[:base_dir]
11
+ self.sockets_dir = File.join(base_dir, 'socks')
12
+ self.pids_dir = File.join(base_dir, 'pids')
13
+
14
+ setup_dir_structure
15
+ cleanup_bluepill_directory
16
+ end
17
+
18
+ def running_applications
19
+ Dir[File.join(sockets_dir, "*.sock")].map{|x| File.basename(x, ".sock")}
20
+ end
21
+
22
+ def handle_command(application, command, *args)
23
+ case command.to_sym
24
+ when :status
25
+ puts self.send_to_daemon(application, :status, *args)
26
+ when *Application::PROCESS_COMMANDS
27
+ # these need to be sent to the daemon and the results printed out
28
+ affected = self.send_to_daemon(application, command, *args)
29
+ if affected.empty?
30
+ puts "No processes effected"
31
+ else
32
+ puts "Sent #{command} to:"
33
+ affected.each do |process|
34
+ puts " #{process}"
35
+ end
36
+ end
37
+ when :quit
38
+ pid = pid_for(application)
39
+ if System.pid_alive?(pid)
40
+ ::Process.kill("TERM", pid)
41
+ puts "Killing bluepilld[#{pid}]"
42
+ else
43
+ puts "bluepilld[#{pid}] not running"
44
+ end
45
+ when :log
46
+ log_file_location = self.send_to_daemon(application, :log_file)
47
+ log_file_location = self.log_file if log_file_location.to_s.strip.empty?
48
+
49
+ requested_pattern = args.first
50
+ grep_pattern = self.grep_pattern(application, requested_pattern)
51
+
52
+ tail = "tail -n 100 -f #{log_file_location} | grep -E '#{grep_pattern}'"
53
+ puts "Tailing log for #{requested_pattern}..."
54
+ Kernel.exec(tail)
55
+ else
56
+ $stderr.puts "Unknown command `%s` (or application `%s` has not been loaded yet)" % [command, command]
57
+ exit(1)
58
+ end
59
+ end
60
+
61
+ def send_to_daemon(application, command, *args)
62
+ begin
63
+ verify_version!(application)
64
+
65
+ command = ([command, *args]).join(":")
66
+ response = Socket.client_command(base_dir, application, command)
67
+ if response.is_a?(Exception)
68
+ $stderr.puts "Received error from server:"
69
+ $stderr.puts response.inspect
70
+ $stderr.puts response.backtrace.join("\n")
71
+ exit(8)
72
+ else
73
+ response
74
+ end
75
+
76
+ rescue Errno::ECONNREFUSED
77
+ abort("Connection Refused: Server is not running")
78
+ end
79
+ end
80
+
81
+ def grep_pattern(application, query = nil)
82
+ pattern = [application, query].compact.join(':')
83
+ ['\[.*', Regexp.escape(pattern), '.*'].compact.join
84
+ end
85
+ private
86
+
87
+ def cleanup_bluepill_directory
88
+ self.running_applications.each do |app|
89
+ pid = pid_for(app)
90
+ if !pid || !System.pid_alive?(pid)
91
+ pid_file = File.join(self.pids_dir, "#{app}.pid")
92
+ sock_file = File.join(self.sockets_dir, "#{app}.sock")
93
+ File.unlink(pid_file) if File.exists?(pid_file)
94
+ File.unlink(sock_file) if File.exists?(sock_file)
95
+ end
96
+ end
97
+ end
98
+
99
+ def pid_for(app)
100
+ pid_file = File.join(self.pids_dir, "#{app}.pid")
101
+ File.exists?(pid_file) && File.read(pid_file).to_i
102
+ end
103
+
104
+ def setup_dir_structure
105
+ [@sockets_dir, @pids_dir].each do |dir|
106
+ FileUtils.mkdir_p(dir) unless File.exists?(dir)
107
+ end
108
+ end
109
+
110
+ def verify_version!(application)
111
+ begin
112
+ version = Socket.client_command(base_dir, application, "version")
113
+ if version != Bluepill::VERSION
114
+ abort("The running version of your daemon seems to be out of date.\nDaemon Version: #{version}, CLI Version: #{Bluepill::VERSION}")
115
+ end
116
+ rescue ArgumentError
117
+ abort("The running version of your daemon seems to be out of date.")
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ module Bluepill
3
+ class AppProxy
4
+ APP_ATTRIBUTES = [:working_dir, :uid, :gid, :environment, :auto_start ]
5
+
6
+ attr_accessor *APP_ATTRIBUTES
7
+ attr_reader :app
8
+
9
+ def initialize(app_name, options)
10
+ @app = Application.new(app_name.to_s, options)
11
+ end
12
+
13
+ def process(process_name, &process_block)
14
+ attributes = {}
15
+ APP_ATTRIBUTES.each { |a| attributes[a] = self.send(a) }
16
+
17
+ process_factory = ProcessFactory.new(attributes, process_block)
18
+
19
+ process = process_factory.create_process(process_name, @app.pids_dir)
20
+ group = process_factory.attributes.delete(:group)
21
+
22
+ @app.add_process(process, group)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,122 @@
1
+ # -*- encoding: utf-8 -*-
2
+ module Bluepill
3
+ class ProcessFactory
4
+ attr_reader :attributes
5
+
6
+ @@process_keys = Hash.new
7
+ @@pid_files = Hash.new
8
+
9
+ def initialize(attributes, process_block)
10
+ @attributes = attributes
11
+ @process_block = process_block
12
+ end
13
+
14
+ def create_process(name, pids_dir)
15
+ self.assign_default_pid_file(name, pids_dir)
16
+
17
+ process = ProcessProxy.new(name, @attributes, @process_block)
18
+ child_process_block = @attributes.delete(:child_process_block)
19
+ @attributes[:child_process_factory] = ProcessFactory.new(@attributes, child_process_block) if @attributes[:monitor_children]
20
+
21
+ self.validate_process! process
22
+ process.to_process
23
+ end
24
+
25
+ def create_child_process(name, pid, logger)
26
+ attributes = {}
27
+ [:start_grace_time, :stop_grace_time, :restart_grace_time].each {|a| attributes[a] = @attributes[a]}
28
+ attributes[:actual_pid] = pid
29
+ attributes[:logger] = logger
30
+
31
+ child = ProcessProxy.new(name, attributes, @process_block)
32
+ self.validate_child_process! child
33
+ process = child.to_process
34
+
35
+ process.determine_initial_state
36
+ process
37
+ end
38
+
39
+ protected
40
+
41
+ def assign_default_pid_file(process_name, pids_dir)
42
+ unless @attributes.key?(:pid_file)
43
+ group_name = @attributes[:group]
44
+ default_pid_name = [group_name, process_name].compact.join('_').gsub(/[^A-Za-z0-9_\-]/, "_")
45
+ @attributes[:pid_file] = File.join(pids_dir, default_pid_name + ".pid")
46
+ end
47
+ end
48
+
49
+ def validate_process!(process)
50
+ # validate uniqueness of group:process
51
+ process_key = [process.attributes[:group], process.name].join(":")
52
+ if @@process_keys.key?(process_key)
53
+ $stderr.print "Config Error: You have two entries for the process name '#{process.name}'"
54
+ $stderr.print " in the group '#{process.attributes[:group]}'" if process.attributes.key?(:group)
55
+ $stderr.puts
56
+ exit(6)
57
+ else
58
+ @@process_keys[process_key] = 0
59
+ end
60
+
61
+ # validate required attributes
62
+ [:start_command].each do |required_attr|
63
+ if !process.attributes.key?(required_attr)
64
+ $stderr.puts "Config Error: You must specify a #{required_attr} for '#{process.name}'"
65
+ exit(6)
66
+ end
67
+ end
68
+
69
+ # validate uniqueness of pid files
70
+ pid_key = process.attributes[:pid_file].strip
71
+ if @@pid_files.key?(pid_key)
72
+ $stderr.puts "Config Error: You have two entries with the pid file: #{pid_key}"
73
+ exit(6)
74
+ else
75
+ @@pid_files[pid_key] = 0
76
+ end
77
+
78
+ #validate stop_signals array
79
+ stop_grace_time = process.attributes[:stop_grace_time]
80
+ stop_signals = process.attributes[:stop_signals]
81
+
82
+ unless stop_signals.nil?
83
+ #Start with the more helpful error messages before the 'odd number' message.
84
+ delay_sum = 0
85
+ stop_signals.each_with_index do |s_or_d, i|
86
+ if i % 2 == 0
87
+ signal = s_or_d
88
+ unless signal.is_a? Symbol
89
+ $stderr.puts "Config Error: Invalid stop_signals! Expected a symbol (signal) at position #{i} instead of '#{signal}'."
90
+ exit(6)
91
+ end
92
+ else
93
+ delay = s_or_d
94
+ unless delay.is_a? Fixnum
95
+ $stderr.puts "Config Error: Invalid stop_signals! Expected a number (delay) at position #{i} instead of '#{delay}'."
96
+ exit(6)
97
+ end
98
+ delay_sum += delay
99
+ end
100
+ end
101
+
102
+ unless stop_signals.size % 2 == 1
103
+ $stderr.puts "Config Error: Invalid stop_signals! Expected an odd number of elements."
104
+ exit(6)
105
+ end
106
+
107
+ if stop_grace_time.nil? || stop_grace_time <= delay_sum
108
+ $stderr.puts "Config Error: Stop_grace_time should be greater than the sum of stop_signals delays!"
109
+ exit(6)
110
+ end
111
+ end
112
+ end
113
+
114
+ def validate_child_process!(child)
115
+ unless child.attributes.has_key?(:stop_command)
116
+ $stderr.puts "Config Error: Invalid child process monitor for #{child.name}"
117
+ $stderr.puts "You must specify a stop command to monitor child processes."
118
+ exit(6)
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,44 @@
1
+ # -*- encoding: utf-8 -*-
2
+ module Bluepill
3
+ class ProcessProxy
4
+ attr_reader :attributes, :watches, :name
5
+ def initialize(process_name, attributes, process_block)
6
+ @name = process_name
7
+ @attributes = attributes
8
+ @watches = {}
9
+
10
+ if process_block.arity == 0
11
+ instance_eval &process_block
12
+ else
13
+ instance_exec(self, &process_block)
14
+ end
15
+ end
16
+
17
+ def method_missing(name, *args)
18
+ if args.size == 1 && name.to_s =~ /^(.*)=$/
19
+ @attributes[$1.to_sym] = args.first
20
+ elsif args.size == 1
21
+ @attributes[name.to_sym] = args.first
22
+ elsif args.size == 0 && name.to_s =~ /^(.*)!$/
23
+ @attributes[$1.to_sym] = true
24
+ elsif args.empty? && @attributes.key?(name.to_sym)
25
+ @attributes[name.to_sym]
26
+ else
27
+ super
28
+ end
29
+ end
30
+
31
+ def checks(name, options = {})
32
+ @watches[name] = options
33
+ end
34
+
35
+ def monitor_children(&child_process_block)
36
+ @attributes[:monitor_children] = true
37
+ @attributes[:child_process_block] = child_process_block
38
+ end
39
+
40
+ def to_process
41
+ Process.new(@name, @watches, @attributes)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,12 @@
1
+ # -*- encoding: utf-8 -*-
2
+ module Bluepill
3
+ def self.application(app_name, options = {}, &block)
4
+ app_proxy = AppProxy.new(app_name, options)
5
+ if block.arity == 0
6
+ app_proxy.instance_eval &block
7
+ else
8
+ app_proxy.instance_exec(app_proxy, &block)
9
+ end
10
+ app_proxy.app.load
11
+ end
12
+ end
@@ -0,0 +1,72 @@
1
+ # -*- encoding: utf-8 -*-
2
+ module Bluepill
3
+ class Group
4
+ attr_accessor :name, :processes, :logger
5
+ attr_accessor :process_logger
6
+
7
+ def initialize(name, options = {})
8
+ self.name = name
9
+ self.processes = []
10
+ self.logger = options[:logger]
11
+ end
12
+
13
+ def add_process(process)
14
+ process.logger = self.logger.prefix_with(process.name)
15
+ self.processes << process
16
+ end
17
+
18
+ def tick
19
+ self.processes.each do |process|
20
+ process.tick
21
+ end
22
+ end
23
+
24
+ def determine_initial_state
25
+ self.processes.each do |process|
26
+ process.determine_initial_state
27
+ end
28
+ end
29
+
30
+ # proxied events
31
+ [:start, :unmonitor, :stop, :restart].each do |event|
32
+ class_eval <<-END
33
+ def #{event}(process_name = nil)
34
+ threads = []
35
+ affected = []
36
+ self.processes.each do |process|
37
+ next if process_name && process_name != process.name
38
+ affected << [self.name, process.name].join(":")
39
+ threads << Thread.new { process.handle_user_command("#{event}") }
40
+ end
41
+ threads.each { |t| t.join }
42
+ affected
43
+ end
44
+ END
45
+ end
46
+
47
+ def status(process_name = nil)
48
+ lines = []
49
+ if process_name.nil?
50
+ prefix = self.name ? " " : ""
51
+ lines << "#{self.name}:" if self.name
52
+
53
+ self.processes.each do |process|
54
+ lines << "%s%s(pid:%s): %s" % [prefix, process.name, process.actual_pid, process.state]
55
+ if process.monitor_children?
56
+ process.children.each do |child|
57
+ lines << " %s%s: %s" % [prefix, child.name, child.state]
58
+ end
59
+ end
60
+ end
61
+ else
62
+ self.processes.each do |process|
63
+ next if process_name != process.name
64
+ lines << "%s%s(pid:%s): %s" % [prefix, process.name, process.actual_pid, process.state]
65
+ lines << process.statistics.to_s
66
+ end
67
+ end
68
+ lines << ""
69
+ end
70
+
71
+ end
72
+ end