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,34 @@
|
|
1
|
+
module Eye::Process::Trigger
|
2
|
+
|
3
|
+
def add_triggers
|
4
|
+
if self[:triggers]
|
5
|
+
self[:triggers].each do |type, cfg|
|
6
|
+
add_trigger(cfg)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def remove_triggers
|
12
|
+
self.triggers = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def check_triggers(transition)
|
16
|
+
self.triggers.each { |trigger| trigger.notify(transition, state_reason) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def retry_start_after_flapping
|
20
|
+
return unless unmonitored?
|
21
|
+
return unless state_reason.to_s.include?('flapping') # TODO: remove hackety
|
22
|
+
|
23
|
+
schedule :start, Eye::Reason.new(:'retry start after flapping')
|
24
|
+
self.flapping_times += 1
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def add_trigger(cfg = {})
|
30
|
+
trigger = Eye::Trigger.create(current_actor, cfg)
|
31
|
+
self.triggers << trigger if trigger
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'shellwords'
|
2
|
+
require 'etc'
|
3
|
+
|
4
|
+
module Eye::Process::Validate
|
5
|
+
|
6
|
+
class Error < Exception; end
|
7
|
+
|
8
|
+
def validate(config, localize = true)
|
9
|
+
if (str = config[:start_command])
|
10
|
+
# it should parse with Shellwords and not raise
|
11
|
+
spl = Shellwords.shellwords(str) * '#'
|
12
|
+
|
13
|
+
if config[:daemonize] && !config[:use_leaf_child]
|
14
|
+
if spl =~ %r[sh#\-c|#&&#|;#]
|
15
|
+
raise Error, "#{config[:name]}, daemonize does not support concats like '&&' in start_command"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
Shellwords.shellwords(config[:stop_command]) if config[:stop_command]
|
21
|
+
Shellwords.shellwords(config[:restart_command]) if config[:restart_command]
|
22
|
+
|
23
|
+
if localize
|
24
|
+
Etc.getpwnam(config[:uid]) if config[:uid]
|
25
|
+
Etc.getpwnam(config[:gid]) if config[:gid]
|
26
|
+
|
27
|
+
if config[:working_dir]
|
28
|
+
raise Error, "working_dir '#{config[:working_dir]}' is invalid" unless File.directory?(config[:working_dir])
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Eye::Process::Watchers
|
2
|
+
|
3
|
+
def add_watchers(force = false)
|
4
|
+
return unless self.up?
|
5
|
+
|
6
|
+
remove_watchers if force
|
7
|
+
|
8
|
+
if @watchers.blank?
|
9
|
+
# default watcher :check_alive
|
10
|
+
add_watcher(:check_alive, self[:check_alive_period]) do
|
11
|
+
check_alive
|
12
|
+
end
|
13
|
+
|
14
|
+
# monitor children pids
|
15
|
+
if self[:monitor_children]
|
16
|
+
add_watcher(:check_children, self[:children_update_period]) do
|
17
|
+
add_or_update_children
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# monitor conditional watchers
|
22
|
+
start_checkers
|
23
|
+
else
|
24
|
+
warn 'add_watchers failed, watchers are already present'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def remove_watchers
|
29
|
+
@watchers.each{|_, h| h[:timer].cancel }
|
30
|
+
@watchers = {}
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def add_watcher(type, period = 2, subject = nil, &block)
|
36
|
+
return if @watchers[type]
|
37
|
+
|
38
|
+
debug "adding watcher: #{type}(#{period})"
|
39
|
+
|
40
|
+
timer = every(period.to_f) do
|
41
|
+
debug "check #{type}"
|
42
|
+
block.call(subject)
|
43
|
+
end
|
44
|
+
|
45
|
+
@watchers[type] ||= {:timer => timer, :subject => subject}
|
46
|
+
end
|
47
|
+
|
48
|
+
def start_checkers
|
49
|
+
self[:checks].each{|name, cfg| start_checker(name, cfg) }
|
50
|
+
end
|
51
|
+
|
52
|
+
def start_checker(name, cfg)
|
53
|
+
subject = Eye::Checker.create(pid, cfg, current_actor)
|
54
|
+
|
55
|
+
# ex: {:type => :memory, :every => 5.seconds, :below => 100.megabytes, :times => [3,5]}
|
56
|
+
add_watcher("check_#{name}".to_sym, subject.every, subject, &method(:watcher_tick).to_proc) if subject
|
57
|
+
end
|
58
|
+
|
59
|
+
def watcher_tick(subject)
|
60
|
+
unless subject.check
|
61
|
+
return unless up?
|
62
|
+
subject.fire
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
data/lib/eye/reason.rb
ADDED
data/lib/eye/server.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'celluloid/io'
|
2
|
+
require 'celluloid/autostart'
|
3
|
+
|
4
|
+
class Eye::Server
|
5
|
+
include Celluloid::IO
|
6
|
+
|
7
|
+
attr_reader :socket_path, :server
|
8
|
+
|
9
|
+
def initialize(socket_path)
|
10
|
+
@socket_path = socket_path
|
11
|
+
@server = begin
|
12
|
+
UNIXServer.open(socket_path)
|
13
|
+
rescue Errno::EADDRINUSE
|
14
|
+
unlink_socket_file
|
15
|
+
UNIXServer.open(socket_path)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def run
|
20
|
+
loop { async.handle_connection @server.accept }
|
21
|
+
end
|
22
|
+
|
23
|
+
def handle_connection(socket)
|
24
|
+
text = socket.read
|
25
|
+
|
26
|
+
begin
|
27
|
+
command, *args = Marshal.load(text)
|
28
|
+
rescue => ex
|
29
|
+
error "Failed to read from socket: #{ex.message}"
|
30
|
+
return
|
31
|
+
end
|
32
|
+
|
33
|
+
response = command(command, *args)
|
34
|
+
socket.write(Marshal.dump(response))
|
35
|
+
|
36
|
+
rescue Errno::EPIPE
|
37
|
+
# client timeouted
|
38
|
+
# do nothing
|
39
|
+
|
40
|
+
ensure
|
41
|
+
socket.close
|
42
|
+
end
|
43
|
+
|
44
|
+
def command(cmd, *args)
|
45
|
+
Eye::Control.command(cmd, *args)
|
46
|
+
end
|
47
|
+
|
48
|
+
def unlink_socket_file
|
49
|
+
File.delete(@socket_path) if @socket_path
|
50
|
+
rescue
|
51
|
+
end
|
52
|
+
|
53
|
+
finalizer :close_socket
|
54
|
+
|
55
|
+
def close_socket
|
56
|
+
@server.close if @server
|
57
|
+
unlink_socket_file
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
data/lib/eye/sigar.rb
ADDED
data/lib/eye/system.rb
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'shellwords'
|
2
|
+
require 'pathname'
|
3
|
+
require 'etc'
|
4
|
+
require 'timeout'
|
5
|
+
|
6
|
+
module Eye::System
|
7
|
+
class << self
|
8
|
+
# Check that pid really exits
|
9
|
+
# very fast
|
10
|
+
# return result hash
|
11
|
+
def check_pid_alive(pid)
|
12
|
+
res = if pid
|
13
|
+
::Process.kill(0, pid)
|
14
|
+
else
|
15
|
+
false
|
16
|
+
end
|
17
|
+
|
18
|
+
{:result => res}
|
19
|
+
rescue => ex
|
20
|
+
{:error => ex}
|
21
|
+
end
|
22
|
+
|
23
|
+
# Check that pid really exits
|
24
|
+
# very fast
|
25
|
+
# return true/false
|
26
|
+
def pid_alive?(pid)
|
27
|
+
res = check_pid_alive(pid)
|
28
|
+
!!res[:result]
|
29
|
+
end
|
30
|
+
|
31
|
+
# Send signal to process (uses for kill)
|
32
|
+
# code: TERM(15), KILL(9), QUIT(3), ...
|
33
|
+
def send_signal(pid, code = :TERM)
|
34
|
+
code = 0 if code == '0'
|
35
|
+
if code.to_s.to_i != 0
|
36
|
+
code = code.to_i
|
37
|
+
code = -code if code < 0
|
38
|
+
end
|
39
|
+
code = code.to_s.upcase if code.is_a?(String) || code.is_a?(Symbol)
|
40
|
+
|
41
|
+
if pid
|
42
|
+
::Process.kill(code, pid)
|
43
|
+
{:result => :ok}
|
44
|
+
else
|
45
|
+
{:error => Exception.new('no_pid')}
|
46
|
+
end
|
47
|
+
|
48
|
+
rescue => ex
|
49
|
+
{:error => ex}
|
50
|
+
end
|
51
|
+
|
52
|
+
# Daemonize cmd, and detach
|
53
|
+
# options:
|
54
|
+
# :pid_file
|
55
|
+
# :working_dir
|
56
|
+
# :environment
|
57
|
+
# :stdin, :stdout, :stderr
|
58
|
+
def daemonize(cmd, cfg = {})
|
59
|
+
pid = ::Process::spawn(prepare_env(cfg), *Shellwords.shellwords(cmd), spawn_options(cfg))
|
60
|
+
|
61
|
+
{:pid => pid, :exitstatus => 0}
|
62
|
+
|
63
|
+
rescue Errno::ENOENT, Errno::EACCES => ex
|
64
|
+
{:error => ex}
|
65
|
+
|
66
|
+
ensure
|
67
|
+
Process.detach(pid) if pid
|
68
|
+
end
|
69
|
+
|
70
|
+
# Execute cmd with blocking, return status (be careful: inside actor blocks it mailbox, use with defer)
|
71
|
+
# options
|
72
|
+
# :working_dir
|
73
|
+
# :environment
|
74
|
+
# :stdin, :stdout, :stderr
|
75
|
+
def execute(cmd, cfg = {})
|
76
|
+
pid = ::Process::spawn(prepare_env(cfg), *Shellwords.shellwords(cmd), spawn_options(cfg))
|
77
|
+
|
78
|
+
timeout = cfg[:timeout] || 1.second
|
79
|
+
status = 0
|
80
|
+
|
81
|
+
Timeout.timeout(timeout) do
|
82
|
+
_, st = Process.waitpid2(pid)
|
83
|
+
status = st.exitstatus || st.termsig
|
84
|
+
end
|
85
|
+
|
86
|
+
{:pid => pid, :exitstatus => status}
|
87
|
+
|
88
|
+
rescue Timeout::Error => ex
|
89
|
+
if pid
|
90
|
+
warn "[#{cfg[:name]}] sending :KILL signal to <#{pid}> due to timeout (#{timeout}s)"
|
91
|
+
send_signal(pid, 9)
|
92
|
+
end
|
93
|
+
{:error => ex}
|
94
|
+
|
95
|
+
rescue Errno::ENOENT, Errno::EACCES => ex
|
96
|
+
{:error => ex}
|
97
|
+
|
98
|
+
ensure
|
99
|
+
Process.detach(pid) if pid
|
100
|
+
end
|
101
|
+
|
102
|
+
# normalize file
|
103
|
+
def normalized_file(file, working_dir = nil)
|
104
|
+
Pathname.new(file).expand_path(working_dir).to_s
|
105
|
+
end
|
106
|
+
|
107
|
+
def spawn_options(config = {})
|
108
|
+
options = {
|
109
|
+
pgroup: true,
|
110
|
+
chdir: config[:working_dir] || '/'
|
111
|
+
}
|
112
|
+
|
113
|
+
options[:out] = [config[:stdout], 'a'] if config[:stdout]
|
114
|
+
options[:err] = [config[:stderr], 'a'] if config[:stderr]
|
115
|
+
options[:in] = config[:stdin] if config[:stdin]
|
116
|
+
options[:umask] = config[:umask] if config[:umask]
|
117
|
+
options[:close_others] = false if config[:preserve_fds]
|
118
|
+
options[:unsetenv_others] = true if config[:clear_env]
|
119
|
+
|
120
|
+
if Eye::Local.root?
|
121
|
+
options[:uid] = Etc.getpwnam(config[:uid]).uid if config[:uid]
|
122
|
+
options[:gid] = Etc.getpwnam(config[:gid]).gid if config[:gid]
|
123
|
+
end
|
124
|
+
|
125
|
+
options
|
126
|
+
end
|
127
|
+
|
128
|
+
def prepare_env(config = {})
|
129
|
+
env = {}
|
130
|
+
|
131
|
+
(config[:environment] || {}).each do |k,v|
|
132
|
+
env[k.to_s] = v && v.to_s
|
133
|
+
end
|
134
|
+
|
135
|
+
env
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'celluloid'
|
2
|
+
|
3
|
+
class Eye::SystemResources
|
4
|
+
|
5
|
+
# cached system resources
|
6
|
+
class << self
|
7
|
+
|
8
|
+
def memory(pid)
|
9
|
+
cache.proc_mem(pid).try(:resident)
|
10
|
+
end
|
11
|
+
|
12
|
+
def cpu(pid)
|
13
|
+
if cpu = cache.proc_cpu(pid)
|
14
|
+
cpu.percent * 100
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def children(parent_pid)
|
19
|
+
cache.children(parent_pid)
|
20
|
+
end
|
21
|
+
|
22
|
+
def start_time(pid) # unixtime
|
23
|
+
if cpu = cache.proc_cpu(pid)
|
24
|
+
cpu.start_time.to_i / 1000
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# total cpu usage in seconds
|
29
|
+
def cputime(pid)
|
30
|
+
if cpu = cache.proc_cpu(pid)
|
31
|
+
cpu.total.to_f / 1000
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# last child in a children tree
|
36
|
+
def leaf_child(pid)
|
37
|
+
c = children(pid)
|
38
|
+
return if c.empty?
|
39
|
+
c += children(c.shift) while c.size > 1
|
40
|
+
c[0]
|
41
|
+
end
|
42
|
+
|
43
|
+
def resources(pid)
|
44
|
+
{ :memory => memory(pid),
|
45
|
+
:cpu => cpu(pid),
|
46
|
+
:start_time => start_time(pid),
|
47
|
+
:pid => pid
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
def cache
|
52
|
+
@cache ||= Cache.new
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class Cache
|
57
|
+
include Celluloid
|
58
|
+
|
59
|
+
attr_reader :expire
|
60
|
+
|
61
|
+
def initialize
|
62
|
+
clear
|
63
|
+
setup_expire
|
64
|
+
end
|
65
|
+
|
66
|
+
def setup_expire(expire = 5)
|
67
|
+
@expire = expire
|
68
|
+
@timer.cancel if @timer
|
69
|
+
@timer = every(@expire) { clear }
|
70
|
+
end
|
71
|
+
|
72
|
+
def clear
|
73
|
+
@memory = {}
|
74
|
+
@cpu = {}
|
75
|
+
@ppids = {}
|
76
|
+
end
|
77
|
+
|
78
|
+
def proc_mem(pid)
|
79
|
+
@memory[pid] ||= Eye::Sigar.proc_mem(pid) if pid
|
80
|
+
|
81
|
+
rescue ArgumentError # when incorrect PID
|
82
|
+
end
|
83
|
+
|
84
|
+
def proc_cpu(pid)
|
85
|
+
@cpu[pid] ||= Eye::Sigar.proc_cpu(pid) if pid
|
86
|
+
|
87
|
+
rescue ArgumentError # when incorrect PID
|
88
|
+
end
|
89
|
+
|
90
|
+
def children(pid)
|
91
|
+
if pid
|
92
|
+
@ppids[pid] ||= Eye::Sigar.proc_list("State.Ppid.eq=#{pid}")
|
93
|
+
else
|
94
|
+
[]
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|