ace-eye 0.6.1
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.
- checksums.yaml +7 -0
- data/.gitignore +38 -0
- data/.rspec +2 -0
- data/.travis.yml +6 -0
- data/CHANGES.md +77 -0
- data/Gemfile +6 -0
- data/LICENSE +22 -0
- data/README.md +212 -0
- data/Rakefile +35 -0
- data/bin/eye +5 -0
- data/bin/loader_eye +72 -0
- data/bin/runner +16 -0
- data/examples/dependency.eye +17 -0
- data/examples/notify.eye +19 -0
- data/examples/plugin/README.md +15 -0
- data/examples/plugin/main.eye +15 -0
- data/examples/plugin/plugin.rb +63 -0
- data/examples/process_thin.rb +29 -0
- data/examples/processes/em.rb +57 -0
- data/examples/processes/forking.rb +20 -0
- data/examples/processes/sample.rb +144 -0
- data/examples/processes/thin.ru +12 -0
- data/examples/puma.eye +29 -0
- data/examples/rbenv.eye +11 -0
- data/examples/sidekiq.eye +23 -0
- data/examples/test.eye +87 -0
- data/examples/thin-farm.eye +30 -0
- data/examples/unicorn.eye +39 -0
- data/eye.gemspec +40 -0
- data/lib/eye.rb +28 -0
- data/lib/eye/application.rb +73 -0
- data/lib/eye/checker.rb +258 -0
- data/lib/eye/checker/children_count.rb +44 -0
- data/lib/eye/checker/children_memory.rb +12 -0
- data/lib/eye/checker/cpu.rb +17 -0
- data/lib/eye/checker/cputime.rb +13 -0
- data/lib/eye/checker/file_ctime.rb +24 -0
- data/lib/eye/checker/file_size.rb +34 -0
- data/lib/eye/checker/file_touched.rb +15 -0
- data/lib/eye/checker/http.rb +96 -0
- data/lib/eye/checker/memory.rb +17 -0
- data/lib/eye/checker/nop.rb +6 -0
- data/lib/eye/checker/runtime.rb +18 -0
- data/lib/eye/checker/socket.rb +159 -0
- data/lib/eye/child_process.rb +101 -0
- data/lib/eye/cli.rb +185 -0
- data/lib/eye/cli/commands.rb +78 -0
- data/lib/eye/cli/render.rb +130 -0
- data/lib/eye/cli/server.rb +93 -0
- data/lib/eye/client.rb +32 -0
- data/lib/eye/config.rb +91 -0
- data/lib/eye/control.rb +2 -0
- data/lib/eye/controller.rb +54 -0
- data/lib/eye/controller/commands.rb +88 -0
- data/lib/eye/controller/helpers.rb +101 -0
- data/lib/eye/controller/load.rb +224 -0
- data/lib/eye/controller/options.rb +18 -0
- data/lib/eye/controller/send_command.rb +177 -0
- data/lib/eye/controller/status.rb +72 -0
- data/lib/eye/dsl.rb +53 -0
- data/lib/eye/dsl/application_opts.rb +39 -0
- data/lib/eye/dsl/chain.rb +12 -0
- data/lib/eye/dsl/child_process_opts.rb +13 -0
- data/lib/eye/dsl/config_opts.rb +55 -0
- data/lib/eye/dsl/group_opts.rb +32 -0
- data/lib/eye/dsl/helpers.rb +20 -0
- data/lib/eye/dsl/main.rb +51 -0
- data/lib/eye/dsl/opts.rb +151 -0
- data/lib/eye/dsl/process_opts.rb +36 -0
- data/lib/eye/dsl/pure_opts.rb +121 -0
- data/lib/eye/dsl/validation.rb +88 -0
- data/lib/eye/group.rb +140 -0
- data/lib/eye/group/chain.rb +81 -0
- data/lib/eye/loader.rb +10 -0
- data/lib/eye/local.rb +100 -0
- data/lib/eye/logger.rb +104 -0
- data/lib/eye/notify.rb +118 -0
- data/lib/eye/notify/jabber.rb +30 -0
- data/lib/eye/notify/mail.rb +48 -0
- data/lib/eye/process.rb +85 -0
- data/lib/eye/process/children.rb +60 -0
- data/lib/eye/process/commands.rb +280 -0
- data/lib/eye/process/config.rb +81 -0
- data/lib/eye/process/controller.rb +73 -0
- data/lib/eye/process/data.rb +78 -0
- data/lib/eye/process/monitor.rb +108 -0
- data/lib/eye/process/notify.rb +32 -0
- data/lib/eye/process/scheduler.rb +82 -0
- data/lib/eye/process/states.rb +86 -0
- data/lib/eye/process/states_history.rb +66 -0
- data/lib/eye/process/system.rb +97 -0
- data/lib/eye/process/trigger.rb +34 -0
- data/lib/eye/process/validate.rb +33 -0
- data/lib/eye/process/watchers.rb +66 -0
- data/lib/eye/reason.rb +20 -0
- data/lib/eye/server.rb +60 -0
- data/lib/eye/sigar.rb +5 -0
- data/lib/eye/system.rb +139 -0
- data/lib/eye/system_resources.rb +99 -0
- data/lib/eye/trigger.rb +136 -0
- data/lib/eye/trigger/check_dependency.rb +30 -0
- data/lib/eye/trigger/flapping.rb +41 -0
- data/lib/eye/trigger/stop_children.rb +17 -0
- data/lib/eye/trigger/transition.rb +15 -0
- data/lib/eye/trigger/wait_dependency.rb +49 -0
- data/lib/eye/utils.rb +45 -0
- data/lib/eye/utils/alive_array.rb +57 -0
- data/lib/eye/utils/celluloid_chain.rb +71 -0
- data/lib/eye/utils/celluloid_klass.rb +5 -0
- data/lib/eye/utils/leak_19.rb +10 -0
- data/lib/eye/utils/mini_active_support.rb +111 -0
- data/lib/eye/utils/pmap.rb +7 -0
- data/lib/eye/utils/tail.rb +20 -0
- metadata +398 -0
@@ -0,0 +1,93 @@
|
|
1
|
+
module Eye::Cli::Server
|
2
|
+
private
|
3
|
+
|
4
|
+
def server_started?
|
5
|
+
_cmd(:ping) == :pong
|
6
|
+
end
|
7
|
+
|
8
|
+
def loader_path
|
9
|
+
filename = File.expand_path(File.join(File.dirname(__FILE__), %w[.. .. .. bin loader_eye]))
|
10
|
+
File.exists?(filename) ? filename : nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def ruby_path
|
14
|
+
require 'rbconfig'
|
15
|
+
RbConfig::CONFIG['bindir'] + '/ruby'
|
16
|
+
end
|
17
|
+
|
18
|
+
def ensure_loader_path
|
19
|
+
unless loader_path
|
20
|
+
error! "start monitoring needs to run under ruby with installed gem 'eye'"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def server_start_foreground(conf = nil)
|
25
|
+
ensure_loader_path
|
26
|
+
Eye::Local.ensure_eye_dir
|
27
|
+
|
28
|
+
if server_started?
|
29
|
+
_cmd(:quit) && sleep(1) # stop previous server
|
30
|
+
end
|
31
|
+
|
32
|
+
args = []
|
33
|
+
args += ['--config', conf] if conf
|
34
|
+
args += ['--logger', 'stdout']
|
35
|
+
if Eye::Local.local_runner
|
36
|
+
args += ['--stop_all']
|
37
|
+
args += ['--dir', Eye::Local.dir]
|
38
|
+
args += ['--config', Eye::Local.eyefile] unless conf
|
39
|
+
end
|
40
|
+
|
41
|
+
Process.exec(ruby_path, loader_path, *args)
|
42
|
+
end
|
43
|
+
|
44
|
+
def server_start(configs)
|
45
|
+
ensure_loader_path
|
46
|
+
Eye::Local.ensure_eye_dir
|
47
|
+
|
48
|
+
ensure_stop_previous_server
|
49
|
+
|
50
|
+
args = []
|
51
|
+
args += ['--dir', Eye::Local.dir] if Eye::Local.local_runner
|
52
|
+
|
53
|
+
opts = {:out => '/dev/null', :err => '/dev/null', :in => '/dev/null',
|
54
|
+
:chdir => '/', :pgroup => true}
|
55
|
+
|
56
|
+
pid = Process.spawn(ruby_path, loader_path, *args, opts)
|
57
|
+
Process.detach(pid)
|
58
|
+
File.open(Eye::Local.pid_path, 'w'){|f| f.write(pid) }
|
59
|
+
|
60
|
+
unless wait_server
|
61
|
+
error! 'server has not started in 15 seconds, something is very wrong'
|
62
|
+
end
|
63
|
+
|
64
|
+
configs.unshift(Eye::Local.eyeconfig) if File.exists?(Eye::Local.eyeconfig)
|
65
|
+
configs << Eye::Local.eyefile if Eye::Local.local_runner
|
66
|
+
|
67
|
+
say 'Eye started!', :green
|
68
|
+
|
69
|
+
if !configs.empty?
|
70
|
+
say_load_result cmd(:load, *configs)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def ensure_stop_previous_server
|
75
|
+
Eye::Local.ensure_eye_dir
|
76
|
+
pid = File.read(Eye::Local.pid_path).to_i rescue nil
|
77
|
+
if pid
|
78
|
+
Process.kill(9, pid) rescue nil
|
79
|
+
end
|
80
|
+
File.delete(Eye::Local.pid_path) rescue nil
|
81
|
+
true
|
82
|
+
end
|
83
|
+
|
84
|
+
def wait_server(timeout = 15)
|
85
|
+
Timeout.timeout(timeout) do
|
86
|
+
sleep 0.3 while !server_started?
|
87
|
+
end
|
88
|
+
true
|
89
|
+
rescue Timeout::Error
|
90
|
+
false
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
data/lib/eye/client.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'timeout'
|
3
|
+
|
4
|
+
class Eye::Client
|
5
|
+
attr_reader :socket_path
|
6
|
+
|
7
|
+
def initialize(socket_path)
|
8
|
+
@socket_path = socket_path
|
9
|
+
end
|
10
|
+
|
11
|
+
def command(cmd, *args)
|
12
|
+
attempt_command(Marshal.dump([cmd, *args]))
|
13
|
+
end
|
14
|
+
|
15
|
+
def attempt_command(pack)
|
16
|
+
Timeout.timeout(Eye::Local.client_timeout) do
|
17
|
+
return send_request(pack)
|
18
|
+
end
|
19
|
+
|
20
|
+
rescue Timeout::Error, EOFError
|
21
|
+
:timeouted
|
22
|
+
end
|
23
|
+
|
24
|
+
def send_request(pack)
|
25
|
+
UNIXSocket.open(@socket_path) do |socket|
|
26
|
+
socket.write(pack)
|
27
|
+
data = socket.read
|
28
|
+
res = Marshal.load(data) rescue :corrupted_data
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
data/lib/eye/config.rb
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
class Eye::Config
|
2
|
+
|
3
|
+
attr_reader :settings, :applications
|
4
|
+
|
5
|
+
def initialize(settings = {}, applications = {})
|
6
|
+
@settings = settings
|
7
|
+
@applications = applications
|
8
|
+
end
|
9
|
+
|
10
|
+
def merge(other_config)
|
11
|
+
Eye::Config.new(@settings.merge(other_config.settings), @applications.merge(other_config.applications))
|
12
|
+
end
|
13
|
+
|
14
|
+
def merge!(other_config)
|
15
|
+
@settings.merge!(other_config.settings)
|
16
|
+
@applications.merge!(other_config.applications)
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_h
|
20
|
+
{:settings => @settings, :applications => @applications}
|
21
|
+
end
|
22
|
+
|
23
|
+
# raise an error if config wrong
|
24
|
+
def validate!(localize = true)
|
25
|
+
all_processes = processes
|
26
|
+
|
27
|
+
# Check blank pid_files
|
28
|
+
no_pid_file = all_processes.select{|c| c[:pid_file].blank? }
|
29
|
+
if no_pid_file.present?
|
30
|
+
raise Eye::Dsl::Error, "blank pid_file for: #{no_pid_file.map{|c| c[:name]} * ', '}"
|
31
|
+
end
|
32
|
+
|
33
|
+
# Check duplicates of the full pid_file
|
34
|
+
|
35
|
+
dupl_pids = all_processes.each_with_object(Hash.new(0)) do |o, h|
|
36
|
+
ex_pid_file = Eye::System.normalized_file(o[:pid_file], o[:working_dir])
|
37
|
+
h[ex_pid_file] += 1
|
38
|
+
end
|
39
|
+
dupl_pids = dupl_pids.select{|k,v| v>1}
|
40
|
+
|
41
|
+
if dupl_pids.present?
|
42
|
+
raise Eye::Dsl::Error, "duplicate pid_files: #{dupl_pids.inspect}"
|
43
|
+
end
|
44
|
+
|
45
|
+
# Check duplicates of the full_name
|
46
|
+
dupl_names = all_processes.each_with_object(Hash.new(0)) do |o, h|
|
47
|
+
full_name = "#{o[:application]}:#{o[:group]}:#{o[:name]}"
|
48
|
+
h[full_name] += 1
|
49
|
+
end
|
50
|
+
dupl_names = dupl_names.select{|k,v| v>1}
|
51
|
+
|
52
|
+
if dupl_names.present?
|
53
|
+
raise Eye::Dsl::Error, "duplicate names: #{dupl_names.inspect}"
|
54
|
+
end
|
55
|
+
|
56
|
+
# validate processes with their own validate
|
57
|
+
all_processes.each do |process_cfg|
|
58
|
+
Eye::Process.validate process_cfg, localize
|
59
|
+
end
|
60
|
+
|
61
|
+
# just to be sure ENV was not removed
|
62
|
+
ENV[''] rescue raise Eye::Dsl::Error.new("ENV is not a hash '#{ENV.inspect}'")
|
63
|
+
end
|
64
|
+
|
65
|
+
def processes
|
66
|
+
applications.values.map{|e| (e[:groups] || {}).values.map{|c| (c[:processes] || {}).values} }.flatten
|
67
|
+
end
|
68
|
+
|
69
|
+
def application_names
|
70
|
+
applications.keys
|
71
|
+
end
|
72
|
+
|
73
|
+
def delete_app(name)
|
74
|
+
applications.delete(name)
|
75
|
+
end
|
76
|
+
|
77
|
+
def delete_group(name)
|
78
|
+
applications.each do |app_name, app_cfg|
|
79
|
+
(app_cfg[:groups] || {}).delete(name)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def delete_process(name)
|
84
|
+
applications.each do |app_name, app_cfg|
|
85
|
+
(app_cfg[:groups] || {}).each do |gr_name, gr_cfg|
|
86
|
+
(gr_cfg[:processes] || {}).delete(name)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
data/lib/eye/control.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'celluloid'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
require_relative 'utils/celluloid_klass'
|
5
|
+
require_relative 'utils/pmap'
|
6
|
+
|
7
|
+
require_relative 'utils/leak_19'
|
8
|
+
require_relative 'utils/mini_active_support'
|
9
|
+
|
10
|
+
# Extend all objects with logger
|
11
|
+
Object.send(:include, Eye::Logger::ObjectExt)
|
12
|
+
|
13
|
+
Eye::Sigar # needs to preload
|
14
|
+
|
15
|
+
class Eye::Controller
|
16
|
+
include Celluloid
|
17
|
+
|
18
|
+
autoload :Load, 'eye/controller/load'
|
19
|
+
autoload :Helpers, 'eye/controller/helpers'
|
20
|
+
autoload :Commands, 'eye/controller/commands'
|
21
|
+
autoload :Status, 'eye/controller/status'
|
22
|
+
autoload :SendCommand, 'eye/controller/send_command'
|
23
|
+
autoload :Options, 'eye/controller/options'
|
24
|
+
|
25
|
+
include Eye::Controller::Load
|
26
|
+
include Eye::Controller::Helpers
|
27
|
+
include Eye::Controller::Commands
|
28
|
+
include Eye::Controller::Status
|
29
|
+
include Eye::Controller::SendCommand
|
30
|
+
include Eye::Controller::Options
|
31
|
+
|
32
|
+
attr_reader :applications, :current_config
|
33
|
+
|
34
|
+
exclusive :load # load is hard command, so better to run it safely blocked
|
35
|
+
|
36
|
+
def initialize
|
37
|
+
@applications = []
|
38
|
+
@current_config = Eye::Config.new
|
39
|
+
|
40
|
+
Celluloid::logger = Eye::Logger.new('celluloid')
|
41
|
+
Eye::SystemResources.cache
|
42
|
+
|
43
|
+
info "starting #{Eye::ABOUT} <#{$$}>"
|
44
|
+
end
|
45
|
+
|
46
|
+
def settings
|
47
|
+
current_config.settings
|
48
|
+
end
|
49
|
+
|
50
|
+
def logger_tag
|
51
|
+
'Eye'
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module Eye::Controller::Commands
|
2
|
+
|
3
|
+
NOT_IMPORTANT_COMMANDS = [:info_data, :short_data, :debug_data, :history_data, :ping,
|
4
|
+
:logger_dev, :match, :explain, :check]
|
5
|
+
|
6
|
+
# Main method, answer for the client command
|
7
|
+
def command(cmd, *args)
|
8
|
+
msg = "command: #{cmd} #{args * ', '}"
|
9
|
+
|
10
|
+
log_str = "=> #{msg}"
|
11
|
+
NOT_IMPORTANT_COMMANDS.include?(cmd) ? debug(log_str) : info(log_str)
|
12
|
+
|
13
|
+
start_at = Time.now
|
14
|
+
cmd = cmd.to_sym
|
15
|
+
|
16
|
+
res = case cmd
|
17
|
+
when :start, :stop, :restart, :unmonitor, :monitor, :break_chain
|
18
|
+
send_command(cmd, *args)
|
19
|
+
when :delete
|
20
|
+
exclusive{ send_command(cmd, *args) }
|
21
|
+
when :signal
|
22
|
+
signal(*args)
|
23
|
+
when :load
|
24
|
+
load(*args)
|
25
|
+
when :quit
|
26
|
+
quit
|
27
|
+
when :stop_all
|
28
|
+
stop_all(*args)
|
29
|
+
when :check
|
30
|
+
check(*args)
|
31
|
+
when :explain
|
32
|
+
explain(*args)
|
33
|
+
when :match
|
34
|
+
match(*args)
|
35
|
+
when :ping
|
36
|
+
:pong
|
37
|
+
when :logger_dev
|
38
|
+
Eye::Logger.dev
|
39
|
+
|
40
|
+
# object commands, for api
|
41
|
+
when :info_data
|
42
|
+
info_data(*args)
|
43
|
+
when :short_data
|
44
|
+
short_data(*args)
|
45
|
+
when :debug_data
|
46
|
+
debug_data(*args)
|
47
|
+
when :history_data
|
48
|
+
history_data(*args)
|
49
|
+
|
50
|
+
else
|
51
|
+
:unknown_command
|
52
|
+
end
|
53
|
+
|
54
|
+
GC.start
|
55
|
+
|
56
|
+
log_str = "<= #{msg} (#{Time.now - start_at}s)"
|
57
|
+
NOT_IMPORTANT_COMMANDS.include?(cmd) ? debug(log_str) : info(log_str)
|
58
|
+
|
59
|
+
res
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def quit
|
65
|
+
info 'Quit!'
|
66
|
+
Eye::System.send_signal($$, :TERM)
|
67
|
+
sleep 1
|
68
|
+
Eye::System.send_signal($$, :KILL)
|
69
|
+
end
|
70
|
+
|
71
|
+
# stop all processes and wait
|
72
|
+
def stop_all(timeout = nil)
|
73
|
+
exclusive do
|
74
|
+
send_command :break_chain, 'all'
|
75
|
+
send_command :stop, 'all'
|
76
|
+
end
|
77
|
+
|
78
|
+
# wait until all processes goes to unmonitored
|
79
|
+
timeout ||= 100
|
80
|
+
|
81
|
+
all_processes.pmap do |p|
|
82
|
+
p.wait_for_condition(timeout, 0.3) do
|
83
|
+
p.state_name == :unmonitored
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module Eye::Controller::Helpers
|
2
|
+
|
3
|
+
def set_proc_line
|
4
|
+
str = Eye::PROCLINE
|
5
|
+
str += " [#{@applications.map(&:name) * ', '}]" if @applications.present?
|
6
|
+
str += " (v #{ENV['EYE_V']})" if ENV['EYE_V']
|
7
|
+
str += " (in #{Eye::Local.dir})"
|
8
|
+
$0 = str
|
9
|
+
end
|
10
|
+
|
11
|
+
def save_cache
|
12
|
+
File.open(Eye::Local.cache_path, 'w') { |f| f.write(cache_str) }
|
13
|
+
rescue => ex
|
14
|
+
log_ex(ex)
|
15
|
+
end
|
16
|
+
|
17
|
+
def cache_str
|
18
|
+
all_processes.map{ |p| "#{p.full_name}=#{p.state}" } * "\n"
|
19
|
+
end
|
20
|
+
|
21
|
+
def process_by_name(name)
|
22
|
+
name = name.to_s
|
23
|
+
all_processes.detect { |c| c.name == name }
|
24
|
+
end
|
25
|
+
|
26
|
+
def process_by_full_name(name)
|
27
|
+
name = name.to_s
|
28
|
+
all_processes.detect { |c| c.full_name == name }
|
29
|
+
end
|
30
|
+
|
31
|
+
def find_nearest_process(name, group_name = nil, app_name = nil)
|
32
|
+
return process_by_full_name(name) if name.include?(':')
|
33
|
+
|
34
|
+
if app_name
|
35
|
+
app = application_by_name(app_name)
|
36
|
+
app.groups.each do |gr|
|
37
|
+
p = gr.processes.detect { |c| c.name == name }
|
38
|
+
return p if p
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
if group_name
|
43
|
+
gr = group_by_name(group_name)
|
44
|
+
p = gr.processes.detect { |c| c.name == name }
|
45
|
+
return p if p
|
46
|
+
end
|
47
|
+
|
48
|
+
process_by_name(name)
|
49
|
+
end
|
50
|
+
|
51
|
+
def group_by_name(name)
|
52
|
+
name = name.to_s
|
53
|
+
all_groups.detect { |c| c.name == name }
|
54
|
+
end
|
55
|
+
|
56
|
+
def application_by_name(name)
|
57
|
+
name = name.to_s
|
58
|
+
@applications.detect { |c| c.name == name }
|
59
|
+
end
|
60
|
+
|
61
|
+
def all_processes
|
62
|
+
processes = []
|
63
|
+
all_groups.each do |gr|
|
64
|
+
processes += gr.processes.to_a
|
65
|
+
end
|
66
|
+
|
67
|
+
processes
|
68
|
+
end
|
69
|
+
|
70
|
+
def all_groups
|
71
|
+
groups = []
|
72
|
+
@applications.each do |app|
|
73
|
+
groups += app.groups.to_a
|
74
|
+
end
|
75
|
+
|
76
|
+
groups
|
77
|
+
end
|
78
|
+
|
79
|
+
# {'app_name' => {'group_name' => {'process_name' => 'pid_file'}}}
|
80
|
+
def short_tree
|
81
|
+
res = {}
|
82
|
+
@applications.each do |app|
|
83
|
+
res2 = {}
|
84
|
+
|
85
|
+
app.groups.each do |group|
|
86
|
+
res3 = {}
|
87
|
+
|
88
|
+
group.processes.each do |process|
|
89
|
+
res3[process.name] = process[:pid_file_ex]
|
90
|
+
end
|
91
|
+
|
92
|
+
res2[group.name] = res3
|
93
|
+
end
|
94
|
+
|
95
|
+
res[app.name] = res2
|
96
|
+
end
|
97
|
+
|
98
|
+
res
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|