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,224 @@
1
+ module Eye::Controller::Load
2
+
3
+ def check(filename)
4
+ { filename => catch_load_error(filename) { parse_config(filename).to_h } }
5
+ end
6
+
7
+ def explain(filename)
8
+ { filename => catch_load_error(filename) { parse_config(filename).to_h } }
9
+ end
10
+
11
+ def load(*args)
12
+ h = args.extract_options!
13
+ obj_strs = args.flatten
14
+ info "=> loading: #{obj_strs}"
15
+
16
+ res = Hash.new
17
+
18
+ globbing(*obj_strs).each do |filename|
19
+ res[filename] = catch_load_error(filename) do
20
+ cfg = parse_config(filename)
21
+ load_config(filename, cfg)
22
+ nil
23
+ end
24
+ end
25
+
26
+ set_proc_line
27
+
28
+ info "<= loading: #{obj_strs}, in: <#{$$}>"
29
+
30
+ res
31
+ end
32
+
33
+ private
34
+
35
+ # regexp for clean backtrace to show for user
36
+ BT_REGX = %r[/lib/eye/|lib/celluloid|internal:prelude|logger.rb:|active_support/core_ext|shellwords.rb|kernel/bootstrap].freeze
37
+
38
+ def catch_load_error(filename = nil, &block)
39
+ { :error => false, :config => yield }
40
+
41
+ rescue Eye::Dsl::Error, Exception, NoMethodError => ex
42
+ raise if ex.class.to_s.include?('RR') # skip RR exceptions
43
+
44
+ error "loading: config error <#{filename}>: #{ex.message}"
45
+
46
+ # filter backtrace for user output
47
+ bt = (ex.backtrace || [])
48
+ bt = bt.reject{|line| line.to_s =~ BT_REGX } unless ENV['EYE_FULL_BACKTRACE']
49
+ error bt.join("\n")
50
+
51
+ res = { :error => true, :message => ex.message }
52
+ res.merge!(:backtrace => bt) if bt.present?
53
+ res
54
+ end
55
+
56
+ def globbing(*obj_strs)
57
+ res = []
58
+ return res if obj_strs.empty?
59
+
60
+ obj_strs.each do |filename|
61
+ mask = if File.directory?(filename)
62
+ File.join filename, '{*.eye}'
63
+ else
64
+ filename
65
+ end
66
+
67
+ debug "loading: globbing mask #{mask}"
68
+
69
+ sub = []
70
+ Dir[mask].each do |config_path|
71
+ sub << config_path
72
+ end
73
+ sub = [mask] if sub.empty?
74
+
75
+ res += sub
76
+ end
77
+
78
+ res
79
+ end
80
+
81
+ # return: result, config
82
+ def parse_config(filename)
83
+ debug "parsing: #{filename}"
84
+
85
+ cfg = Eye::Dsl.parse(nil, filename)
86
+ @current_config.merge(cfg).validate!(false) # just validate summary config here
87
+ Eye.parsed_config = nil # remove link on config, for better gc
88
+ cfg
89
+ end
90
+
91
+ # !!! exclusive operation
92
+ def load_config(filename, config)
93
+ info "loading: #{filename}"
94
+ new_cfg = @current_config.merge(config)
95
+ new_cfg.validate!
96
+
97
+ load_options(new_cfg.settings)
98
+ create_objects(new_cfg.applications, config.application_names)
99
+ @current_config = new_cfg
100
+ end
101
+
102
+ # load global config options
103
+ def load_options(opts)
104
+ return if opts.blank?
105
+
106
+ opts.each do |key, value|
107
+ method = "set_opt_#{key}"
108
+ send(method, value) if value && respond_to?(method)
109
+ end
110
+ end
111
+
112
+ # create objects as diff, from configs
113
+ def create_objects(apps_config, changed_apps = [])
114
+ debug 'creating objects'
115
+
116
+ apps_config.each do |app_name, app_cfg|
117
+ update_or_create_application(app_name, app_cfg.clone) if changed_apps.include?(app_name)
118
+ end
119
+
120
+ # sorting applications
121
+ @applications.sort_by!(&:name)
122
+ end
123
+
124
+ def update_or_create_application(app_name, app_config)
125
+ @old_groups = {}
126
+ @old_processes = {}
127
+
128
+ app = @applications.detect { |c| c.name == app_name }
129
+
130
+ if app
131
+ app.groups.each do |group|
132
+ @old_groups[group.name] = group
133
+ group.processes.each do |proc|
134
+ @old_processes[group.name + ":" + proc.name] = proc
135
+ end
136
+ end
137
+
138
+ @applications.delete(app)
139
+
140
+ debug "updating app: #{app_name}"
141
+ else
142
+ debug "creating app: #{app_name}"
143
+ end
144
+
145
+ app = Eye::Application.new(app_name, app_config)
146
+ @applications << app
147
+ @added_groups, @added_processes = [], []
148
+
149
+ new_groups = app_config.delete(:groups) || {}
150
+ new_groups.each do |group_name, group_cfg|
151
+ group = update_or_create_group(group_name, group_cfg.clone)
152
+ app.add_group(group)
153
+ group.resort_processes
154
+ end
155
+
156
+ # now, need to clear @old_groups, and @old_processes
157
+ @old_groups.each{|_, group| group.clear; group.send_command(:delete) }
158
+ @old_processes.each{|_, process| process.send_command(:delete) if process.alive? }
159
+
160
+ # schedule monitoring for new groups, processes
161
+ added_fully_groups = []
162
+ @added_groups.each do |group|
163
+ if group.processes.size > 0 && (group.processes.pure - @added_processes).size == 0
164
+ added_fully_groups << group
165
+ @added_processes -= group.processes.pure
166
+ end
167
+ end
168
+
169
+ added_fully_groups.each{|group| group.send_command :monitor }
170
+ @added_processes.each{|process| process.send_command :monitor }
171
+
172
+ # remove links to prevent memory leaks
173
+ @old_groups = nil
174
+ @old_processes = nil
175
+ @added_groups = nil
176
+ @added_processes = nil
177
+
178
+ app.resort_groups
179
+
180
+ app
181
+ end
182
+
183
+ def update_or_create_group(group_name, group_config)
184
+ group = if @old_groups[group_name]
185
+ debug "updating group: #{group_name}"
186
+ group = @old_groups.delete(group_name)
187
+ group.schedule :update_config, group_config, Eye::Reason::User.new(:'load config')
188
+ group.clear
189
+ group
190
+ else
191
+ debug "creating group: #{group_name}"
192
+ gr = Eye::Group.new(group_name, group_config)
193
+ @added_groups << gr
194
+ gr
195
+ end
196
+
197
+ processes = group_config.delete(:processes) || {}
198
+ processes.each do |process_name, process_cfg|
199
+ process = update_or_create_process(process_name, process_cfg.clone)
200
+ group.add_process(process)
201
+ end
202
+
203
+ group
204
+ end
205
+
206
+ def update_or_create_process(process_name, process_cfg)
207
+ postfix = ":" + process_name
208
+ name = process_cfg[:group] + postfix
209
+ key = @old_processes[name] ? name : @old_processes.keys.detect { |n| n.end_with?(postfix) }
210
+
211
+ if @old_processes[key]
212
+ debug "updating process: #{name}"
213
+ process = @old_processes.delete(key)
214
+ process.schedule :update_config, process_cfg, Eye::Reason::User.new(:'load config')
215
+ process
216
+ else
217
+ debug "creating process: #{name}"
218
+ process = Eye::Process.new(process_cfg)
219
+ @added_processes << process
220
+ process
221
+ end
222
+ end
223
+
224
+ end
@@ -0,0 +1,18 @@
1
+ module Eye::Controller::Options
2
+
3
+ def set_opt_logger(logger_args)
4
+ # do not apply logger, if in stdout state
5
+ if !%w{stdout stderr}.include?(Eye::Logger.dev)
6
+ Eye::Logger.link_logger(*logger_args)
7
+ end
8
+ end
9
+
10
+ def set_opt_logger_level(level)
11
+ Eye::Logger.log_level = level
12
+ end
13
+
14
+ def set_opt_http(opts = {})
15
+ warn "Warning, set http options not in reel-eye gem" if opts.present?
16
+ end
17
+
18
+ end
@@ -0,0 +1,177 @@
1
+ module Eye::Controller::SendCommand
2
+
3
+ def send_command(command, *args)
4
+ matched_objects(*args) do |obj|
5
+ if command.to_sym == :delete
6
+ remove_object_from_tree(obj)
7
+
8
+ set_proc_line
9
+ end
10
+
11
+ obj.send_command(command)
12
+ end
13
+ end
14
+
15
+ def match(*args)
16
+ matched_objects(*args)
17
+ end
18
+
19
+ def signal(signal, *args)
20
+ matched_objects(*args) do |obj|
21
+ obj.send_command :signal, signal || 0
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ class Error < Exception; end
28
+
29
+ def matched_objects(*args, &block)
30
+ objs = find_objects(*args)
31
+ res = objs.map(&:full_name)
32
+ objs.each{|obj| block[obj] } if block
33
+ {:result => res}
34
+
35
+ rescue Error => ex
36
+ log_ex(ex)
37
+ {:error => ex.message}
38
+
39
+ rescue Celluloid::DeadActorError => ex
40
+ log_ex(ex)
41
+ {:error => "'#{ex.message}', try again!"}
42
+ end
43
+
44
+ def remove_object_from_tree(obj)
45
+ klass = obj.class
46
+
47
+ if klass == Eye::Application
48
+ @applications.delete(obj)
49
+ @current_config.delete_app(obj.name)
50
+ end
51
+
52
+ if klass == Eye::Group
53
+ @applications.each{|app| app.groups.delete(obj) }
54
+ @current_config.delete_group(obj.name)
55
+ end
56
+
57
+ if klass == Eye::Process
58
+ @applications.each{|app| app.groups.each{|gr| gr.processes.delete(obj) }}
59
+ @current_config.delete_process(obj.name)
60
+ end
61
+ end
62
+
63
+ # find object to action, restart ... (app, group or process)
64
+ # nil if not found
65
+ def find_objects(*args)
66
+ h = args.extract_options!
67
+ obj_strs = args
68
+
69
+ return [] if obj_strs.blank?
70
+
71
+ if obj_strs.size == 1 && (obj_strs[0].to_s.strip == 'all' || obj_strs[0].to_s.strip == '*')
72
+ if h[:application]
73
+ return @applications.select { |app| app.name == h[:application]}
74
+ else
75
+ return @applications.dup
76
+ end
77
+ end
78
+
79
+ res = Eye::Utils::AliveArray.new
80
+ obj_strs.map{|c| c.to_s.split(',')}.flatten.each do |mask|
81
+ objs = find_objects_by_mask(mask.to_s.strip)
82
+ objs.select! { |obj| obj.app_name == h[:application] } if h[:application]
83
+ res += objs
84
+ end
85
+ res
86
+ end
87
+
88
+ def find_objects_by_mask(mask)
89
+ res = find_all_objects_by_mask(mask)
90
+
91
+ if res.size > 1
92
+ final = Eye::Utils::AliveArray.new
93
+
94
+ # try to find exactly matched
95
+ if mask[-1] != '*'
96
+ r = exact_regexp(mask)
97
+ res.each do |obj|
98
+ final << obj if obj.name =~ r || obj.full_name =~ r
99
+ end
100
+ end
101
+
102
+ res = final if final.present?
103
+ final = Eye::Utils::AliveArray.new
104
+
105
+ # remove inherited targets
106
+ res.each do |obj|
107
+ sub_object = res.any?{|a| a.sub_object?(obj) }
108
+ final << obj unless sub_object
109
+ end
110
+
111
+ res = final
112
+
113
+ # try to remove objects with different applications
114
+ apps, objs = Eye::Utils::AliveArray.new, Eye::Utils::AliveArray.new
115
+ res.each do |obj|
116
+ if obj.is_a?(Eye::Application)
117
+ apps << obj
118
+ else
119
+ objs << obj
120
+ end
121
+ end
122
+
123
+ return apps if apps.size > 0
124
+
125
+ if objs.map(&:app_name).uniq.size > 1
126
+ raise Error, "cannot match targets from different applications: #{res.map(&:full_name)}"
127
+ end
128
+ end
129
+
130
+ res
131
+ end
132
+
133
+ def find_all_objects_by_mask(mask)
134
+ res = Eye::Utils::AliveArray
135
+ r = left_regexp(mask)
136
+
137
+ # find app
138
+ res = @applications.select{|a| a.name =~ r || a.full_name =~ r }
139
+
140
+ # find group
141
+ @applications.each do |a|
142
+ res += a.groups.select{|gr| gr.name =~ r || gr.full_name =~ r }
143
+ end
144
+
145
+ # find process
146
+ @applications.each do |a|
147
+ a.groups.each do |gr|
148
+ gr.processes.each do |p|
149
+ res << p if p.name =~ r || p.full_name =~ r
150
+
151
+ # child matching
152
+ if p.children.present?
153
+ children = p.children.values
154
+ res += children.select do |ch|
155
+ name = ch.name rescue ''
156
+ full_name = ch.full_name rescue ''
157
+ name =~ r || full_name =~ r
158
+ end
159
+ end
160
+
161
+ end
162
+ end
163
+ end
164
+
165
+ res
166
+ end
167
+
168
+ def left_regexp(mask)
169
+ str = Regexp.escape(mask).gsub('\*', '.*?')
170
+ %r|\A#{str}|
171
+ end
172
+
173
+ def exact_regexp(mask)
174
+ str = Regexp.escape(mask).gsub('\*', '.*?')
175
+ %r|\A#{str}\z|
176
+ end
177
+ end
@@ -0,0 +1,72 @@
1
+ module Eye::Controller::Status
2
+
3
+ def debug_data(*args)
4
+ h = args.extract_options!
5
+ actors = Celluloid::Actor.all.map{|actor| actor.__klass__ }.group_by{|a| a}.map{|k,v| [k, v.size]}.sort_by{|a|a[1]}.reverse
6
+
7
+ res = {
8
+ :about => Eye::ABOUT,
9
+ :resources => Eye::SystemResources.resources($$),
10
+ :ruby => RUBY_DESCRIPTION,
11
+ :gems => %w|Celluloid Celluloid::IO StateMachine NIO Sigar|.map{|c| gem_version(c) },
12
+ :logger => Eye::Logger.args.present? ? [Eye::Logger.dev, *Eye::Logger.args] : Eye::Logger.dev,
13
+ :pid_path => Eye::Local::pid_path,
14
+ :sock_path => Eye::Local::socket_path,
15
+ :actors => actors
16
+ }
17
+
18
+ res[:config_yaml] = YAML.dump(current_config.to_h) if h[:config].present?
19
+
20
+ res
21
+ end
22
+
23
+ def info_data(*args)
24
+ {:subtree => info_objects(*args).map{|a| a.status_data } }
25
+ end
26
+
27
+ def short_data(*args)
28
+ {:subtree => info_objects(*args).select{ |o| o.class == Eye::Application }.map{|a| a.status_data_short } }
29
+ end
30
+
31
+ def history_data(*args)
32
+ res = {}
33
+ history_objects(*args).each do |process|
34
+ res[process.full_name] = process.schedule_history.reject{|c| c[:state] == :check_crash }
35
+ end
36
+ res
37
+ end
38
+
39
+ private
40
+
41
+ def info_objects(*args)
42
+ res = []
43
+ return @applications if args.empty?
44
+ matched_objects(*args){|obj| res << obj }
45
+ res
46
+ end
47
+
48
+ def gem_version(klass)
49
+ v = nil
50
+ begin
51
+ v = eval("#{klass}::VERSION::STRING")
52
+ rescue
53
+ v = eval("#{klass}::VERSION") rescue ''
54
+ end
55
+ "#{klass}=#{v}"
56
+ end
57
+
58
+ def history_objects(*args)
59
+ args = ['*'] if args.empty?
60
+ res = []
61
+ matched_objects(*args) do |obj|
62
+ if obj.is_a?(Eye::Process)
63
+ res << obj
64
+ elsif obj.is_a?(Eye::ChildProcess)
65
+ else
66
+ res += obj.processes.to_a
67
+ end
68
+ end
69
+ Eye::Utils::AliveArray.new(res)
70
+ end
71
+
72
+ end