ace-eye 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- 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,101 @@
|
|
1
|
+
require 'celluloid'
|
2
|
+
|
3
|
+
class Eye::ChildProcess
|
4
|
+
include Celluloid
|
5
|
+
|
6
|
+
# needs: kill_process
|
7
|
+
include Eye::Process::Commands
|
8
|
+
|
9
|
+
# easy config + defaults: prepare_config, c, []
|
10
|
+
include Eye::Process::Config
|
11
|
+
|
12
|
+
# conditional watchers: start_checkers
|
13
|
+
include Eye::Process::Watchers
|
14
|
+
|
15
|
+
# system methods: send_signal
|
16
|
+
include Eye::Process::System
|
17
|
+
|
18
|
+
# self_status_data
|
19
|
+
include Eye::Process::Data
|
20
|
+
|
21
|
+
# manage notify methods
|
22
|
+
include Eye::Process::Notify
|
23
|
+
|
24
|
+
# scheduler
|
25
|
+
include Eye::Process::Scheduler
|
26
|
+
|
27
|
+
attr_reader :pid, :name, :full_name, :config, :watchers
|
28
|
+
|
29
|
+
def initialize(pid, config = {}, logger_prefix = nil, parent_pid = nil)
|
30
|
+
raise 'Empty pid' unless pid
|
31
|
+
|
32
|
+
@pid = pid
|
33
|
+
@parent_pid = parent_pid
|
34
|
+
@config = prepare_config(config)
|
35
|
+
@name = "child-#{pid}"
|
36
|
+
@full_name = [logger_prefix, @name] * ':'
|
37
|
+
|
38
|
+
@watchers = {}
|
39
|
+
|
40
|
+
debug "start monitoring CHILD config: #{@config.inspect}"
|
41
|
+
|
42
|
+
start_checkers
|
43
|
+
end
|
44
|
+
|
45
|
+
def logger_tag
|
46
|
+
full_name
|
47
|
+
end
|
48
|
+
|
49
|
+
def state
|
50
|
+
:up
|
51
|
+
end
|
52
|
+
|
53
|
+
def up?
|
54
|
+
state == :up
|
55
|
+
end
|
56
|
+
|
57
|
+
def send_command(command, *args)
|
58
|
+
schedule command, *args, Eye::Reason::User.new(command)
|
59
|
+
end
|
60
|
+
|
61
|
+
def start
|
62
|
+
end
|
63
|
+
|
64
|
+
def stop
|
65
|
+
kill_process
|
66
|
+
end
|
67
|
+
|
68
|
+
def restart
|
69
|
+
if self[:restart_command]
|
70
|
+
execute_restart_command
|
71
|
+
else
|
72
|
+
stop
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def monitor
|
77
|
+
end
|
78
|
+
|
79
|
+
def unmonitor
|
80
|
+
end
|
81
|
+
|
82
|
+
def delete
|
83
|
+
end
|
84
|
+
|
85
|
+
def destroy
|
86
|
+
remove_watchers
|
87
|
+
terminate
|
88
|
+
end
|
89
|
+
|
90
|
+
def signal(sig)
|
91
|
+
send_signal(sig) if self.pid
|
92
|
+
end
|
93
|
+
|
94
|
+
def status_data(debug = false)
|
95
|
+
self_status_data(debug)
|
96
|
+
end
|
97
|
+
|
98
|
+
def prepare_command(command) # override
|
99
|
+
super.gsub('{PARENT_PID}', @parent_pid.to_s)
|
100
|
+
end
|
101
|
+
end
|
data/lib/eye/cli.rb
ADDED
@@ -0,0 +1,185 @@
|
|
1
|
+
gem 'thor'
|
2
|
+
require 'thor'
|
3
|
+
|
4
|
+
class Eye::Cli < Thor
|
5
|
+
autoload :Server, 'eye/cli/server'
|
6
|
+
autoload :Commands, 'eye/cli/commands'
|
7
|
+
autoload :Render, 'eye/cli/render'
|
8
|
+
|
9
|
+
include Eye::Cli::Server
|
10
|
+
include Eye::Cli::Commands
|
11
|
+
include Eye::Cli::Render
|
12
|
+
|
13
|
+
desc "info [MASK]", "processes info"
|
14
|
+
def info(mask = nil)
|
15
|
+
res = cmd(:info_data, *Array(mask))
|
16
|
+
if mask && res[:subtree] && res[:subtree].empty?
|
17
|
+
error!("command :info, objects not found!")
|
18
|
+
end
|
19
|
+
say render_info(res)
|
20
|
+
say
|
21
|
+
end
|
22
|
+
|
23
|
+
desc "status", "processes info (deprecated)"
|
24
|
+
def status
|
25
|
+
say ":status is deprecated, use :info instead", :yellow
|
26
|
+
info
|
27
|
+
end
|
28
|
+
|
29
|
+
desc "xinfo", "eye-deamon info (-c show current config)"
|
30
|
+
method_option :config, :type => :boolean, :aliases => "-c"
|
31
|
+
def xinfo
|
32
|
+
res = cmd(:debug_data, :config => options[:config])
|
33
|
+
say render_debug_info(res)
|
34
|
+
say
|
35
|
+
end
|
36
|
+
|
37
|
+
desc "oinfo", "onelined info"
|
38
|
+
def oinfo(mask = nil)
|
39
|
+
res = cmd(:short_data, *Array(mask))
|
40
|
+
say render_info(res)
|
41
|
+
say
|
42
|
+
end
|
43
|
+
|
44
|
+
desc "history [MASK,...]", "processes history"
|
45
|
+
def history(*masks)
|
46
|
+
res = cmd(:history_data, *masks)
|
47
|
+
if !masks.empty? && res && res.empty?
|
48
|
+
error!("command :history, objects not found!")
|
49
|
+
end
|
50
|
+
say render_history(res)
|
51
|
+
say
|
52
|
+
end
|
53
|
+
|
54
|
+
desc "load [CONF, ...]", "load config (run eye-daemon if not) (-f foreground load)"
|
55
|
+
method_option :foreground, :type => :boolean, :aliases => "-f"
|
56
|
+
def load(*configs)
|
57
|
+
configs.map!{ |c| File.expand_path(c) } if !configs.empty?
|
58
|
+
|
59
|
+
if options[:foreground]
|
60
|
+
# in foreground we stop another server, and run just 1 current config version
|
61
|
+
error!("foreground expected only one config") if configs.size > 1
|
62
|
+
server_start_foreground(configs.first)
|
63
|
+
|
64
|
+
elsif server_started?
|
65
|
+
say_load_result cmd(:load, *configs)
|
66
|
+
|
67
|
+
else
|
68
|
+
server_start(configs)
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
desc "quit", "eye-daemon quit"
|
74
|
+
method_option :stop_all, :type => :boolean, :aliases => "-s"
|
75
|
+
method_option :timeout, :type => :string, :aliases => "-t", :default => "600"
|
76
|
+
def quit
|
77
|
+
if options[:stop_all]
|
78
|
+
Eye::Local.client_timeout = options[:timeout].to_i
|
79
|
+
cmd(:stop_all, options[:timeout].to_i)
|
80
|
+
end
|
81
|
+
|
82
|
+
Eye::Local.client_timeout = 5
|
83
|
+
res = _cmd(:quit)
|
84
|
+
|
85
|
+
# if eye server got crazy, stop by force
|
86
|
+
ensure_stop_previous_server if res != :corrupted_data
|
87
|
+
|
88
|
+
# remove pid_file
|
89
|
+
File.delete(Eye::Local.pid_path) if File.exists?(Eye::Local.pid_path)
|
90
|
+
|
91
|
+
say "Quit :(", :yellow
|
92
|
+
end
|
93
|
+
|
94
|
+
[:start, :stop, :restart, :unmonitor, :monitor, :delete, :match].each do |_cmd|
|
95
|
+
desc "#{_cmd} MASK[,...]", "#{_cmd} app,group or process"
|
96
|
+
define_method(_cmd) do |*masks|
|
97
|
+
send_command(_cmd, *masks)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
desc "signal SIG MASK[,...]", "send signal to app,group or process"
|
102
|
+
def signal(sig, *masks)
|
103
|
+
send_command(:signal, sig, *masks)
|
104
|
+
end
|
105
|
+
|
106
|
+
desc "break MASK[,...]", "break chain executing"
|
107
|
+
def break(*masks)
|
108
|
+
send_command(:break_chain, *masks)
|
109
|
+
end
|
110
|
+
|
111
|
+
desc "trace [MASK]", "tracing log(tail + grep) for app,group or process"
|
112
|
+
def trace(mask = "")
|
113
|
+
log_trace(mask)
|
114
|
+
end
|
115
|
+
|
116
|
+
map ["-v", "--version"] => :version
|
117
|
+
desc "version", "version"
|
118
|
+
def version
|
119
|
+
say Eye::ABOUT
|
120
|
+
end
|
121
|
+
|
122
|
+
desc "check CONF", "check config file syntax"
|
123
|
+
method_option :host, :type => :string, :aliases => "-h"
|
124
|
+
method_option :verbose, :type => :boolean, :aliases => "-v"
|
125
|
+
def check(conf)
|
126
|
+
conf = File.expand_path(conf) if conf && !conf.empty?
|
127
|
+
|
128
|
+
Eye::Local.host = options[:host] if options[:host]
|
129
|
+
Eye::Dsl.verbose = options[:verbose]
|
130
|
+
|
131
|
+
say_load_result Eye::Controller.new.check(conf), :syntax => true
|
132
|
+
end
|
133
|
+
|
134
|
+
desc "explain CONF", "explain config tree"
|
135
|
+
method_option :host, :type => :string, :aliases => "-h"
|
136
|
+
method_option :verbose, :type => :boolean, :aliases => "-v"
|
137
|
+
def explain(conf)
|
138
|
+
conf = File.expand_path(conf) if conf && !conf.empty?
|
139
|
+
|
140
|
+
Eye::Local.host = options[:host] if options[:host]
|
141
|
+
Eye::Dsl.verbose = options[:verbose]
|
142
|
+
|
143
|
+
say_load_result Eye::Controller.new.explain(conf), :print_config => true, :syntax => true
|
144
|
+
end
|
145
|
+
|
146
|
+
desc "watch [MASK]", "interactive processes info"
|
147
|
+
def watch(*args)
|
148
|
+
error!("You should install watch utility") if `which watch`.empty?
|
149
|
+
|
150
|
+
cmd = if `watch --version 2>&1`.chop > '0.2.0'
|
151
|
+
"watch -n 1 --color #{$0} i #{args * ' '}"
|
152
|
+
else
|
153
|
+
"watch -n 1 #{$0} i #{args * ' '}"
|
154
|
+
end
|
155
|
+
|
156
|
+
pid = Process.spawn(cmd)
|
157
|
+
Process.waitpid(pid)
|
158
|
+
rescue Interrupt
|
159
|
+
end
|
160
|
+
|
161
|
+
private
|
162
|
+
|
163
|
+
def error!(msg)
|
164
|
+
say msg, :red
|
165
|
+
exit 1
|
166
|
+
end
|
167
|
+
|
168
|
+
def print(msg, new_line = true)
|
169
|
+
say msg if msg && !msg.empty?
|
170
|
+
say if new_line
|
171
|
+
end
|
172
|
+
|
173
|
+
def log_trace(tag = '')
|
174
|
+
log_file = cmd(:logger_dev)
|
175
|
+
if log_file && File.exists?(log_file)
|
176
|
+
Process.exec "tail -n 100 -f #{log_file} | grep '#{tag}'"
|
177
|
+
else
|
178
|
+
error! "log file not found #{log_file.inspect}"
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def self.exit_on_failure?
|
183
|
+
true
|
184
|
+
end
|
185
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Eye::Cli::Commands
|
2
|
+
private
|
3
|
+
|
4
|
+
def client
|
5
|
+
@client ||= Eye::Client.new(Eye::Local.socket_path)
|
6
|
+
end
|
7
|
+
|
8
|
+
def _cmd(cmd, *args)
|
9
|
+
client.command(cmd, *args)
|
10
|
+
rescue Errno::ECONNREFUSED, Errno::ENOENT
|
11
|
+
:not_started
|
12
|
+
end
|
13
|
+
|
14
|
+
def cmd(cmd, *args)
|
15
|
+
res = _cmd(cmd, *args)
|
16
|
+
|
17
|
+
if res == :not_started
|
18
|
+
error! "socket(#{Eye::Local.socket_path}) not found, did you run `eye load`?"
|
19
|
+
elsif res == :timeouted
|
20
|
+
error! 'eye timed out without responding...'
|
21
|
+
end
|
22
|
+
|
23
|
+
res
|
24
|
+
end
|
25
|
+
|
26
|
+
def say_load_result(res = {}, opts = {})
|
27
|
+
error!(res) unless res.is_a?(Hash)
|
28
|
+
say_filename = (res.size > 1)
|
29
|
+
error = false
|
30
|
+
res.each do |filename, _res|
|
31
|
+
say "#{filename}: ", nil, true if say_filename
|
32
|
+
show_load_message(_res, opts)
|
33
|
+
error = true if _res[:error]
|
34
|
+
end
|
35
|
+
|
36
|
+
exit(1) if error
|
37
|
+
end
|
38
|
+
|
39
|
+
def show_load_message(res, opts = {})
|
40
|
+
if res[:error]
|
41
|
+
say res[:message], :red
|
42
|
+
res[:backtrace].to_a.each{|line| say line, :red }
|
43
|
+
else
|
44
|
+
if opts[:syntax]
|
45
|
+
say 'Config ok!', :green if !res[:empty]
|
46
|
+
else
|
47
|
+
say 'Config loaded!', :green if !res[:empty]
|
48
|
+
end
|
49
|
+
|
50
|
+
if opts[:print_config]
|
51
|
+
require 'pp'
|
52
|
+
PP.pp res[:config], STDOUT, 150
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def send_command(_cmd, *args)
|
58
|
+
res = cmd(_cmd, *args)
|
59
|
+
if res == :unknown_command
|
60
|
+
error! "unknown command :#{_cmd}"
|
61
|
+
elsif res == :corrupted_data
|
62
|
+
error! 'something crazy wrong, check eye logs!'
|
63
|
+
elsif res.is_a?(Hash)
|
64
|
+
if res[:error]
|
65
|
+
error! "Error: #{res[:error]}"
|
66
|
+
elsif res = res[:result]
|
67
|
+
if res == []
|
68
|
+
error! "command :#{_cmd}, objects not found!"
|
69
|
+
else
|
70
|
+
say "command :#{_cmd} sent to [#{res * ", "}]"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
else
|
74
|
+
error! "unknown result #{res.inspect}"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
module Eye::Cli::Render
|
2
|
+
private
|
3
|
+
def render_info(data)
|
4
|
+
error!("unexpected server response #{data.inspect}") unless data.is_a?(Hash)
|
5
|
+
|
6
|
+
make_str data
|
7
|
+
end
|
8
|
+
|
9
|
+
def make_str(data, level = -1)
|
10
|
+
return nil if !data || data.empty?
|
11
|
+
|
12
|
+
if data.is_a?(Array)
|
13
|
+
data.map{|el| make_str(el, level) }.compact * "\n"
|
14
|
+
else
|
15
|
+
str = nil
|
16
|
+
|
17
|
+
if data[:name]
|
18
|
+
return make_str(data[:subtree], level) if data[:name] == '__default__'
|
19
|
+
|
20
|
+
off = level * 2
|
21
|
+
off_str = ' ' * off
|
22
|
+
|
23
|
+
short_state = (data[:type] == :application && data[:states])
|
24
|
+
is_text = data[:state] || data[:states]
|
25
|
+
|
26
|
+
name = (data[:type] == :application && !is_text) ? "\033[1m#{data[:name]}\033[0m" : data[:name].to_s
|
27
|
+
off_len = 35
|
28
|
+
str = off_str + (name + ' ').ljust(off_len - off, is_text ? '.' : ' ')
|
29
|
+
|
30
|
+
if short_state
|
31
|
+
str += ' ' + data[:states].map { |k, v| "#{k}:#{v}" }.join(', ')
|
32
|
+
elsif data[:state]
|
33
|
+
str += ' ' + data[:state].to_s
|
34
|
+
str += ' (' + resources_str(data[:resources]) + ')' if data[:resources] && data[:state].to_sym == :up
|
35
|
+
str += " (#{data[:state_reason]} at #{Eye::Utils.human_time2(data[:state_changed_at])})" if data[:state_reason] && data[:state] == 'unmonitored'
|
36
|
+
elsif data[:current_command]
|
37
|
+
chain_progress = if data[:chain_progress]
|
38
|
+
" #{data[:chain_progress][0]} of #{data[:chain_progress][1]}" rescue ''
|
39
|
+
end
|
40
|
+
str += " \e[1;33m[#{data[:current_command]}#{chain_progress}]\033[0m"
|
41
|
+
str += " (#{data[:chain_commands] * ', '})" if data[:chain_commands]
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
if data[:subtree].nil?
|
47
|
+
str
|
48
|
+
elsif !data[:subtree] && data[:type] != :application
|
49
|
+
nil
|
50
|
+
else
|
51
|
+
[str, make_str(data[:subtree], level + 1)].compact * "\n"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def resources_str(r)
|
57
|
+
return '' if !r || r.empty?
|
58
|
+
memory, cpu, start_time, pid = r[:memory], r[:cpu], r[:start_time], r[:pid]
|
59
|
+
return '' unless memory && cpu && start_time
|
60
|
+
|
61
|
+
"#{Eye::Utils.human_time(start_time)}, #{cpu.to_i}%, #{memory / 1024 / 1024}Mb, <#{pid}>"
|
62
|
+
end
|
63
|
+
|
64
|
+
def render_debug_info(data)
|
65
|
+
error!("unexpected server response #{data.inspect}") unless data.is_a?(Hash)
|
66
|
+
|
67
|
+
s = ""
|
68
|
+
|
69
|
+
if config_yaml = data.delete(:config_yaml)
|
70
|
+
s << config_yaml
|
71
|
+
|
72
|
+
else
|
73
|
+
data.each do |k, v|
|
74
|
+
s << "#{"#{k}:".ljust(10)} "
|
75
|
+
|
76
|
+
case k
|
77
|
+
when :resources
|
78
|
+
s << resources_str(v)
|
79
|
+
else
|
80
|
+
s << "#{v}"
|
81
|
+
end
|
82
|
+
|
83
|
+
s << "\n"
|
84
|
+
end
|
85
|
+
|
86
|
+
s << "\n"
|
87
|
+
end
|
88
|
+
|
89
|
+
s
|
90
|
+
end
|
91
|
+
|
92
|
+
def render_history(data)
|
93
|
+
error!("unexpected server response #{data.inspect}") unless data.is_a?(Hash)
|
94
|
+
|
95
|
+
res = []
|
96
|
+
data.each do |name, data|
|
97
|
+
res << detail_process_info(name, data)
|
98
|
+
end
|
99
|
+
|
100
|
+
res * "\n"
|
101
|
+
end
|
102
|
+
|
103
|
+
def detail_process_info(name, history)
|
104
|
+
return if history.empty?
|
105
|
+
|
106
|
+
res = "\033[1m#{name}\033[0m\n"
|
107
|
+
history = history.reverse
|
108
|
+
|
109
|
+
history.chunk{|h| [h[:state], h[:reason].to_s] }.each do |_, hist|
|
110
|
+
if hist.size >= 3
|
111
|
+
res << detail_process_info_string(hist[0])
|
112
|
+
res << detail_process_info_string(:state => "... #{hist.size - 2} times", :reason => '...')
|
113
|
+
res << detail_process_info_string(hist[-1])
|
114
|
+
else
|
115
|
+
hist.each do |h|
|
116
|
+
res << detail_process_info_string(h)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
res
|
122
|
+
end
|
123
|
+
|
124
|
+
def detail_process_info_string(h)
|
125
|
+
state = h[:state].to_s.ljust(14)
|
126
|
+
at = h[:at] ? Eye::Utils.human_time2(h[:at]) : '.' * 12
|
127
|
+
"#{at} - #{state} (#{h[:reason]})\n"
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|