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,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
@@ -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
@@ -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
@@ -0,0 +1,2 @@
1
+ # controller global singlton
2
+ Eye::Control = Eye::Controller.new
@@ -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