ace-eye 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
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