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.
Files changed (114) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +38 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +6 -0
  5. data/CHANGES.md +77 -0
  6. data/Gemfile +6 -0
  7. data/LICENSE +22 -0
  8. data/README.md +212 -0
  9. data/Rakefile +35 -0
  10. data/bin/eye +5 -0
  11. data/bin/loader_eye +72 -0
  12. data/bin/runner +16 -0
  13. data/examples/dependency.eye +17 -0
  14. data/examples/notify.eye +19 -0
  15. data/examples/plugin/README.md +15 -0
  16. data/examples/plugin/main.eye +15 -0
  17. data/examples/plugin/plugin.rb +63 -0
  18. data/examples/process_thin.rb +29 -0
  19. data/examples/processes/em.rb +57 -0
  20. data/examples/processes/forking.rb +20 -0
  21. data/examples/processes/sample.rb +144 -0
  22. data/examples/processes/thin.ru +12 -0
  23. data/examples/puma.eye +29 -0
  24. data/examples/rbenv.eye +11 -0
  25. data/examples/sidekiq.eye +23 -0
  26. data/examples/test.eye +87 -0
  27. data/examples/thin-farm.eye +30 -0
  28. data/examples/unicorn.eye +39 -0
  29. data/eye.gemspec +40 -0
  30. data/lib/eye.rb +28 -0
  31. data/lib/eye/application.rb +73 -0
  32. data/lib/eye/checker.rb +258 -0
  33. data/lib/eye/checker/children_count.rb +44 -0
  34. data/lib/eye/checker/children_memory.rb +12 -0
  35. data/lib/eye/checker/cpu.rb +17 -0
  36. data/lib/eye/checker/cputime.rb +13 -0
  37. data/lib/eye/checker/file_ctime.rb +24 -0
  38. data/lib/eye/checker/file_size.rb +34 -0
  39. data/lib/eye/checker/file_touched.rb +15 -0
  40. data/lib/eye/checker/http.rb +96 -0
  41. data/lib/eye/checker/memory.rb +17 -0
  42. data/lib/eye/checker/nop.rb +6 -0
  43. data/lib/eye/checker/runtime.rb +18 -0
  44. data/lib/eye/checker/socket.rb +159 -0
  45. data/lib/eye/child_process.rb +101 -0
  46. data/lib/eye/cli.rb +185 -0
  47. data/lib/eye/cli/commands.rb +78 -0
  48. data/lib/eye/cli/render.rb +130 -0
  49. data/lib/eye/cli/server.rb +93 -0
  50. data/lib/eye/client.rb +32 -0
  51. data/lib/eye/config.rb +91 -0
  52. data/lib/eye/control.rb +2 -0
  53. data/lib/eye/controller.rb +54 -0
  54. data/lib/eye/controller/commands.rb +88 -0
  55. data/lib/eye/controller/helpers.rb +101 -0
  56. data/lib/eye/controller/load.rb +224 -0
  57. data/lib/eye/controller/options.rb +18 -0
  58. data/lib/eye/controller/send_command.rb +177 -0
  59. data/lib/eye/controller/status.rb +72 -0
  60. data/lib/eye/dsl.rb +53 -0
  61. data/lib/eye/dsl/application_opts.rb +39 -0
  62. data/lib/eye/dsl/chain.rb +12 -0
  63. data/lib/eye/dsl/child_process_opts.rb +13 -0
  64. data/lib/eye/dsl/config_opts.rb +55 -0
  65. data/lib/eye/dsl/group_opts.rb +32 -0
  66. data/lib/eye/dsl/helpers.rb +20 -0
  67. data/lib/eye/dsl/main.rb +51 -0
  68. data/lib/eye/dsl/opts.rb +151 -0
  69. data/lib/eye/dsl/process_opts.rb +36 -0
  70. data/lib/eye/dsl/pure_opts.rb +121 -0
  71. data/lib/eye/dsl/validation.rb +88 -0
  72. data/lib/eye/group.rb +140 -0
  73. data/lib/eye/group/chain.rb +81 -0
  74. data/lib/eye/loader.rb +10 -0
  75. data/lib/eye/local.rb +100 -0
  76. data/lib/eye/logger.rb +104 -0
  77. data/lib/eye/notify.rb +118 -0
  78. data/lib/eye/notify/jabber.rb +30 -0
  79. data/lib/eye/notify/mail.rb +48 -0
  80. data/lib/eye/process.rb +85 -0
  81. data/lib/eye/process/children.rb +60 -0
  82. data/lib/eye/process/commands.rb +280 -0
  83. data/lib/eye/process/config.rb +81 -0
  84. data/lib/eye/process/controller.rb +73 -0
  85. data/lib/eye/process/data.rb +78 -0
  86. data/lib/eye/process/monitor.rb +108 -0
  87. data/lib/eye/process/notify.rb +32 -0
  88. data/lib/eye/process/scheduler.rb +82 -0
  89. data/lib/eye/process/states.rb +86 -0
  90. data/lib/eye/process/states_history.rb +66 -0
  91. data/lib/eye/process/system.rb +97 -0
  92. data/lib/eye/process/trigger.rb +34 -0
  93. data/lib/eye/process/validate.rb +33 -0
  94. data/lib/eye/process/watchers.rb +66 -0
  95. data/lib/eye/reason.rb +20 -0
  96. data/lib/eye/server.rb +60 -0
  97. data/lib/eye/sigar.rb +5 -0
  98. data/lib/eye/system.rb +139 -0
  99. data/lib/eye/system_resources.rb +99 -0
  100. data/lib/eye/trigger.rb +136 -0
  101. data/lib/eye/trigger/check_dependency.rb +30 -0
  102. data/lib/eye/trigger/flapping.rb +41 -0
  103. data/lib/eye/trigger/stop_children.rb +17 -0
  104. data/lib/eye/trigger/transition.rb +15 -0
  105. data/lib/eye/trigger/wait_dependency.rb +49 -0
  106. data/lib/eye/utils.rb +45 -0
  107. data/lib/eye/utils/alive_array.rb +57 -0
  108. data/lib/eye/utils/celluloid_chain.rb +71 -0
  109. data/lib/eye/utils/celluloid_klass.rb +5 -0
  110. data/lib/eye/utils/leak_19.rb +10 -0
  111. data/lib/eye/utils/mini_active_support.rb +111 -0
  112. data/lib/eye/utils/pmap.rb +7 -0
  113. data/lib/eye/utils/tail.rb +20 -0
  114. 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
@@ -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