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,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