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,121 @@
1
+ class Eye::Dsl::PureOpts
2
+
3
+ def self.create_options_methods(arr, types = nil)
4
+ m = Module.new do
5
+ arr.each do |opt|
6
+ define_method("set_#{opt}") do |arg|
7
+ key = opt.to_sym
8
+
9
+ if (disallow_options && disallow_options.include?(key)) || (allow_options && !allow_options.include?(key))
10
+ raise Eye::Dsl::Error, "disallow option #{key} for #{self.class.inspect}"
11
+ end
12
+
13
+ if types
14
+ good_type = Array(types).any?{|type| arg.is_a?(type) } || arg.nil?
15
+ raise Eye::Dsl::Error, "bad :#{opt} value #{arg.inspect}, type should be #{types.inspect}" unless good_type
16
+ end
17
+
18
+ @config[key] = arg
19
+ end
20
+
21
+ define_method("get_#{opt}") do
22
+ @config[ opt.to_sym ]
23
+ end
24
+
25
+ define_method(opt) do |*args|
26
+ if args.blank?
27
+ # getter
28
+ send "get_#{opt}"
29
+ else
30
+ send "set_#{opt}", *args
31
+ end
32
+ end
33
+
34
+ define_method("#{opt}=") do |arg|
35
+ send opt, arg
36
+ end
37
+ end
38
+ end
39
+
40
+ self.send :include, m
41
+ end
42
+
43
+ attr_reader :name, :full_name
44
+ attr_reader :config, :parent
45
+
46
+ def initialize(name = nil, parent = nil, merge_parent_config = true)
47
+ @name = name.to_s
48
+ @full_name = @name
49
+
50
+ if parent
51
+ @parent = parent
52
+ if merge_parent_config
53
+ @config = Eye::Utils::deep_clone(parent.config)
54
+ parent.not_seed_options.each { |opt| @config.delete(opt) }
55
+ else
56
+ @config = {}
57
+ end
58
+ @full_name = "#{parent.full_name}:#{@full_name}"
59
+ else
60
+ @config = {}
61
+ end
62
+
63
+ @config[:name] = @name if @name.present?
64
+ end
65
+
66
+ def allow_options
67
+ nil
68
+ end
69
+
70
+ def disallow_options
71
+ []
72
+ end
73
+
74
+ def not_seed_options
75
+ []
76
+ end
77
+
78
+ def with_condition(cond = true, &block)
79
+ self.instance_eval(&block) if cond && block
80
+ end
81
+
82
+ def use(proc, *args)
83
+ if proc.is_a?(String)
84
+ self.class.with_parsed_file(proc) do |path|
85
+ if File.exists?(path)
86
+ Eye::Dsl.debug "=> load #{path}"
87
+ self.instance_eval(File.read(path))
88
+ Eye::Dsl.debug "<= load #{path}"
89
+ end
90
+ end
91
+ else
92
+ ie = if args.present?
93
+ lambda{|i| proc[i, *args] }
94
+ else
95
+ proc
96
+ end
97
+
98
+ self.instance_eval(&ie)
99
+ end
100
+ end
101
+
102
+ def nop(*args, &block); end
103
+
104
+ private
105
+
106
+ def self.with_parsed_file(file_name)
107
+ saved_parsed_filename = Eye.parsed_filename
108
+
109
+ require 'pathname'
110
+
111
+ real_filename = Eye.parsed_filename && File.symlink?(Eye.parsed_filename) ? File.readlink(Eye.parsed_filename) : Eye.parsed_filename
112
+ dirname = File.dirname(real_filename) rescue nil
113
+ path = Pathname.new(file_name).expand_path(dirname).to_s
114
+
115
+ Eye.parsed_filename = path
116
+ yield path
117
+ ensure
118
+ Eye.parsed_filename = saved_parsed_filename
119
+ end
120
+
121
+ end
@@ -0,0 +1,88 @@
1
+ module Eye::Dsl::Validation
2
+ def self.included(base)
3
+ base.extend(ClassMethods)
4
+ end
5
+
6
+ class Error < Exception; end
7
+
8
+ module ClassMethods
9
+ def inherited(subclass)
10
+ subclass.validates = self.validates.clone
11
+ subclass.should_bes = self.should_bes.clone
12
+ subclass.defaults = self.defaults.clone
13
+ subclass.variants = self.variants.clone
14
+ end
15
+
16
+ attr_accessor :validates, :should_bes, :defaults, :variants
17
+
18
+ def validates; @validates ||= {}; end
19
+ def should_bes; @should_bes ||= []; end
20
+ def defaults; @defaults ||= {}; end
21
+ def variants; @variants ||= {}; end
22
+
23
+ def param(param, types = [], should_be = false, default = nil, _variants = nil)
24
+ param = param.to_sym
25
+
26
+ validates[param] = types
27
+ should_bes << param if should_be
28
+ param_default(param, default)
29
+ variants[param] = _variants
30
+
31
+ return if param == :do
32
+
33
+ define_method "#{param}" do
34
+ value = @options[param]
35
+ value.nil? ? default : value
36
+ end
37
+ end
38
+
39
+ def param_default(param, default)
40
+ param = param.to_sym
41
+ defaults[param] = default
42
+ end
43
+
44
+ def del_param(param)
45
+ param = param.to_sym
46
+ validates.delete(param)
47
+ should_bes.delete(param)
48
+ defaults.delete(param)
49
+ variants.delete(param)
50
+ remove_method(param)
51
+ end
52
+
53
+ def validate(options = {})
54
+ options.each do |param, value|
55
+ param = param.to_sym
56
+ types = validates[param]
57
+ unless types
58
+ if param != :type
59
+ raise Error, "#{self.name} unknown param :#{param} value #{value.inspect}"
60
+ end
61
+ end
62
+
63
+ if self.variants[param]
64
+ if value && !value.is_a?(Proc)
65
+ if value.is_a?(Array)
66
+ if (value - self.variants[param]).present?
67
+ raise Error, "#{value.inspect} should be within #{self.variants[param].inspect}"
68
+ end
69
+ elsif !self.variants[param].include?(value)
70
+ raise Error, "#{value.inspect} should be within #{self.variants[param].inspect}"
71
+ end
72
+ end
73
+ end
74
+
75
+ next if types.blank?
76
+
77
+ types = Array(types)
78
+ good = types.any?{|type| value.is_a?(type) }
79
+ raise Error, "#{self.name} bad param :#{param} value #{value.inspect}, type #{types.inspect}" unless good
80
+ end
81
+
82
+ should_bes.each do |param|
83
+ raise Error, "#{self.name} for param :#{param} value should be" unless options[param.to_sym] || defaults[param.to_sym]
84
+ end
85
+ end
86
+ end
87
+
88
+ end
@@ -0,0 +1,140 @@
1
+ require 'celluloid'
2
+
3
+ class Eye::Group
4
+ include Celluloid
5
+
6
+ autoload :Chain, 'eye/group/chain'
7
+
8
+ include Eye::Process::Scheduler
9
+ include Eye::Group::Chain
10
+
11
+ attr_reader :processes, :name, :hidden, :config
12
+
13
+ def initialize(name, config)
14
+ @name = name
15
+ @config = config
16
+ @processes = Eye::Utils::AliveArray.new
17
+ @hidden = (name == '__default__')
18
+ debug 'created'
19
+ end
20
+
21
+ def logger_tag
22
+ full_name
23
+ end
24
+
25
+ def app_name
26
+ @config[:application]
27
+ end
28
+
29
+ def full_name
30
+ @full_name ||= "#{app_name}:#{@name}"
31
+ end
32
+
33
+ def update_config(cfg)
34
+ @config = cfg
35
+ @full_name = nil
36
+ end
37
+
38
+ def add_process(process)
39
+ @processes << process
40
+ end
41
+
42
+ # sort processes in name order
43
+ def resort_processes
44
+ @processes = @processes.sort_by(&:name)
45
+ end
46
+
47
+ def status_data(debug = false)
48
+ plist = @processes.map{|p| p.status_data(debug) }
49
+
50
+ h = { name: name, type: :group, subtree: plist }
51
+
52
+ h.merge!(debug: debug_data) if debug
53
+
54
+ # show current chain
55
+ if current_scheduled_command
56
+ h.update(current_command: current_scheduled_command)
57
+
58
+ if (chain_commands = scheduler_actions_list) && chain_commands.present?
59
+ h.update(chain_commands: chain_commands)
60
+ end
61
+
62
+ if @chain_processes_current && @chain_processes_count
63
+ h.update(chain_progress: [@chain_processes_current, @chain_processes_count])
64
+ end
65
+ end
66
+
67
+ h
68
+ end
69
+
70
+ def debug_data
71
+ {:queue => scheduler_actions_list, :chain => chain_status}
72
+ end
73
+
74
+ def send_command(command, *args)
75
+ info "send_command: #{command}"
76
+
77
+ case command
78
+ when :delete
79
+ delete *args
80
+ when :break_chain
81
+ break_chain *args
82
+ else
83
+ schedule command, *args, Eye::Reason::User.new(command)
84
+ end
85
+ end
86
+
87
+ def start
88
+ chain_command :start
89
+ end
90
+
91
+ def stop
92
+ async_schedule :stop
93
+ end
94
+
95
+ def restart
96
+ chain_command :restart
97
+ end
98
+
99
+ def delete
100
+ async_schedule :delete
101
+ terminate
102
+ end
103
+
104
+ def monitor
105
+ chain_command :monitor
106
+ end
107
+
108
+ def unmonitor
109
+ async_schedule :unmonitor
110
+ end
111
+
112
+ def signal(sig)
113
+ async_schedule :signal, sig
114
+ end
115
+
116
+ def break_chain
117
+ info 'break chain'
118
+ scheduler_clear_pending_list
119
+ @chain_breaker = true
120
+ end
121
+
122
+ def clear
123
+ @processes = Eye::Utils::AliveArray.new
124
+ end
125
+
126
+ def sub_object?(obj)
127
+ @processes.include?(obj)
128
+ end
129
+
130
+ private
131
+
132
+ def async_schedule(command, *args)
133
+ info "send to all processes #{command} #{args.present? ? args*',' : nil}"
134
+
135
+ @processes.each do |process|
136
+ process.send_command(command, *args)
137
+ end
138
+ end
139
+
140
+ end
@@ -0,0 +1,81 @@
1
+ module Eye::Group::Chain
2
+
3
+ private
4
+
5
+ def chain_schedule(type, grace, command, *args)
6
+ info "starting #{type} with #{grace}s chain #{command} #{args}"
7
+
8
+ @chain_processes_count = @processes.size
9
+ @chain_processes_current = 0
10
+ @chain_breaker = false
11
+
12
+ started_at = Time.now
13
+
14
+ @processes.each do | process |
15
+ chain_schedule_process(process, type, command, *args)
16
+
17
+ @chain_processes_current = @chain_processes_current.to_i + 1
18
+
19
+ # to skip last sleep
20
+ break if @chain_processes_current.to_i == @chain_processes_count.to_i
21
+ break if @chain_breaker
22
+
23
+ # wait next process
24
+ sleep grace.to_f
25
+
26
+ break if @chain_breaker
27
+ end
28
+
29
+ debug "chain finished #{Time.now - started_at}s"
30
+
31
+ @chain_processes_count = nil
32
+ @chain_processes_current = nil
33
+ end
34
+
35
+ def chain_schedule_process(process, type, command, *args)
36
+ debug "chain_schedule_process #{process.name} #{type} #{command}"
37
+
38
+ if type == :sync
39
+ # sync command, with waiting
40
+ # this is very hackety, because call method of the process without its scheduler
41
+ # need to provide some scheduler future
42
+ process.last_scheduled_reason = self.last_scheduled_reason
43
+ process.send(command, *args)
44
+ else
45
+ # async command
46
+ process.send_command(command, *args)
47
+ end
48
+ end
49
+
50
+ def chain_status
51
+ if @config[:chain]
52
+ [:start, :restart].map{|c| @config[:chain][c].try(:[], :grace) }
53
+ end
54
+ end
55
+
56
+ def chain_command(command, *args)
57
+ chain_opts = chain_options(command)
58
+ chain_schedule(chain_opts[:type], chain_opts[:grace], command, *args)
59
+ end
60
+
61
+ # with such delay will chained processes by default
62
+ DEFAULT_CHAIN = 0.2
63
+
64
+ def chain_options(command)
65
+ command = :start if command == :monitor # hack for monitor command, work as start
66
+
67
+ if @config[:chain] && @config[:chain][command]
68
+ type = @config[:chain][command].try :[], :type
69
+ type = [:async, :sync].include?(type) ? type : :async
70
+
71
+ grace = @config[:chain][command].try :[], :grace
72
+ grace = grace ? (grace.to_f rescue DEFAULT_CHAIN) : DEFAULT_CHAIN
73
+
74
+ {:type => type, :grace => grace}
75
+ else
76
+ # default chain case
77
+ {:type => :async, :grace => DEFAULT_CHAIN}
78
+ end
79
+ end
80
+
81
+ end
@@ -0,0 +1,10 @@
1
+ # add gems to $: by `gem` method
2
+ # this is only way when install eye as system wide
3
+
4
+ gem 'celluloid', '~> 0.15.0'
5
+ gem 'celluloid-io', '~> 0.15.0'
6
+ gem 'nio4r'
7
+ gem 'timers'
8
+
9
+ gem 'state_machine'
10
+ gem 'sigar', '~> 0.7.2'
@@ -0,0 +1,100 @@
1
+ require 'fileutils'
2
+
3
+ module Eye::Local
4
+ class << self
5
+ def dir
6
+ @dir ||= begin
7
+ if root?
8
+ '/var/run/eye'
9
+ else
10
+ File.expand_path(File.join(home, '.eye'))
11
+ end
12
+ end
13
+ end
14
+
15
+ def dir=(d)
16
+ @dir = d
17
+ end
18
+
19
+ def eyeconfig
20
+ if root?
21
+ '/etc/eye.conf'
22
+ else
23
+ File.expand_path(File.join(home, '.eyeconfig'))
24
+ end
25
+ end
26
+
27
+ def root?
28
+ Process::UID.eid == 0
29
+ end
30
+
31
+ def home
32
+ h = ENV['EYE_HOME'] || ENV['HOME']
33
+ raise "HOME undefined, should be HOME or EYE_HOME environment" unless h
34
+ h
35
+ end
36
+
37
+ def path(path)
38
+ File.join(dir, path)
39
+ end
40
+
41
+ def ensure_eye_dir
42
+ FileUtils.mkdir_p( dir )
43
+ end
44
+
45
+ def socket_path
46
+ path(ENV['EYE_SOCK'] || "sock#{ENV['EYE_V']}")
47
+ end
48
+
49
+ def pid_path
50
+ path(ENV['EYE_PID'] || "pid#{ENV['EYE_V']}")
51
+ end
52
+
53
+ def cache_path
54
+ path("processes#{ENV['EYE_V']}.cache")
55
+ end
56
+
57
+ def client_timeout
58
+ @client_timeout ||= 5
59
+ end
60
+
61
+ def client_timeout=(cl)
62
+ @client_timeout = cl
63
+ end
64
+
65
+ def supported_setsid?
66
+ RUBY_VERSION >= '2.0'
67
+ end
68
+
69
+ def host
70
+ @host ||= begin
71
+ require 'socket'
72
+ Socket.gethostname
73
+ end
74
+ end
75
+
76
+ def host=(hostname)
77
+ @host = hostname
78
+ end
79
+
80
+ def eyefile
81
+ @eyefile ||= find_eyefile('.')
82
+ end
83
+
84
+ def find_eyefile(start_from_dir)
85
+ fromenv = ENV['EYEFILE']
86
+ return fromenv if fromenv && !fromenv.empty? && File.exist?(fromenv)
87
+
88
+ previous = nil
89
+ current = File.expand_path(start_from_dir)
90
+
91
+ until !File.directory?(current) || current == previous
92
+ filename = File.join(current, 'Eyefile')
93
+ return filename if File.file?(filename)
94
+ current, previous = File.expand_path('..', current), current
95
+ end
96
+ end
97
+
98
+ attr_accessor :local_runner
99
+ end
100
+ end