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,136 @@
1
+ class Eye::Trigger
2
+ include Eye::Dsl::Validation
3
+
4
+ autoload :Flapping, 'eye/trigger/flapping'
5
+ autoload :Transition, 'eye/trigger/transition'
6
+ autoload :StopChildren, 'eye/trigger/stop_children'
7
+ autoload :WaitDependency, 'eye/trigger/wait_dependency'
8
+ autoload :CheckDependency, 'eye/trigger/check_dependency'
9
+
10
+ TYPES = {:flapping => 'Flapping', :transition => 'Transition', :stop_children => 'StopChildren',
11
+ :wait_dependency => 'WaitDependency', :check_dependency => 'CheckDependency'
12
+ }
13
+
14
+ attr_reader :message, :options, :process
15
+
16
+ def self.name_and_class(type)
17
+ type = type.to_sym
18
+ return {:name => type, :type => type} if TYPES[type]
19
+
20
+ if type =~ /\A(.*?)_?[0-9]+\z/
21
+ ctype = $1.to_sym
22
+ return {:name => type, :type => ctype} if TYPES[ctype]
23
+ end
24
+ end
25
+
26
+ def self.get_class(type)
27
+ klass = eval("Eye::Trigger::#{TYPES[type]}") rescue nil
28
+ raise "unknown trigger #{type}" unless klass
29
+ if deps = klass.requires
30
+ Array(deps).each { |d| require d }
31
+ end
32
+ klass
33
+ end
34
+
35
+ def self.create(process, options = {})
36
+ get_class(options[:type]).new(process, options)
37
+
38
+ rescue Exception, Timeout::Error => ex
39
+ log_ex(ex)
40
+ nil
41
+ end
42
+
43
+ def self.validate!(options = {})
44
+ get_class(options[:type]).validate(options)
45
+ end
46
+
47
+ def initialize(process, options = {})
48
+ @options = options
49
+ @process = process
50
+ @full_name = @process.full_name if @process
51
+
52
+ debug "add #{options}"
53
+ end
54
+
55
+ def inspect
56
+ "<#{self.class} @process='#{@full_name}' @options=#{@options}>"
57
+ end
58
+
59
+ def logger_tag
60
+ @process.logger.prefix
61
+ end
62
+
63
+ def logger_sub_tag
64
+ "trigger(#{@options[:type]})"
65
+ end
66
+
67
+ def notify(transition, reason)
68
+ debug "check (:#{transition.event}) :#{transition.from} => :#{transition.to}"
69
+ @reason = reason
70
+ @transition = transition
71
+
72
+ check(transition) if filter_transition(transition)
73
+
74
+ rescue Exception, Timeout::Error => ex
75
+ if ex.class == Eye::Process::StateError
76
+ raise ex
77
+ else
78
+ log_ex(ex)
79
+ end
80
+ end
81
+
82
+ param :to, [Symbol, Array]
83
+ param :from, [Symbol, Array]
84
+ param :event, [Symbol, Array]
85
+
86
+ def filter_transition(trans)
87
+ return true unless to || from || event
88
+
89
+ compare_state(trans.to_name, to) &&
90
+ compare_state(trans.from_name, from) &&
91
+ compare_state(trans.event, event)
92
+ end
93
+
94
+ def check(transition)
95
+ raise NotImplementedError
96
+ end
97
+
98
+ def run_in_process_context(p)
99
+ process.instance_exec(&p) if process.alive?
100
+ end
101
+
102
+ def defer(&block)
103
+ Celluloid::Future.new(&block).value
104
+ end
105
+
106
+ def self.register(base)
107
+ name = base.to_s.gsub('Eye::Trigger::', '')
108
+ type = name.underscore.to_sym
109
+ Eye::Trigger::TYPES[type] = name
110
+ Eye::Trigger.const_set(name, base)
111
+ end
112
+
113
+ def self.requires
114
+ end
115
+
116
+ class Custom < Eye::Trigger
117
+ def self.inherited(base)
118
+ super
119
+ register(base)
120
+ end
121
+ end
122
+
123
+ private
124
+
125
+ def compare_state(state_name, condition)
126
+ case condition
127
+ when Symbol
128
+ state_name == condition
129
+ when Array
130
+ condition.include?(state_name)
131
+ else
132
+ true
133
+ end
134
+ end
135
+
136
+ end
@@ -0,0 +1,30 @@
1
+ class Eye::Trigger::CheckDependency < Eye::Trigger
2
+ param :names, [Array], true, 5
3
+
4
+ def check(transition)
5
+ check_dependency(transition.to_name) if transition.from_name == :up
6
+ end
7
+
8
+ private
9
+
10
+ def check_dependency(to)
11
+ processes = names.map do |name|
12
+ Eye::Control.find_nearest_process(name, process.group_name_pure, process.app_name)
13
+ end.compact
14
+ return if processes.empty?
15
+ processes = Eye::Utils::AliveArray.new(processes)
16
+
17
+ act = case to
18
+ when :down, :restarting; :restart
19
+ when :stopping; :stop
20
+ when :unmonitored; :unmonitor
21
+ end
22
+
23
+ if act
24
+ processes.each do |p|
25
+ p.schedule act, Eye::Reason.new(:"#{act} dependecies")
26
+ end
27
+ end
28
+ end
29
+
30
+ end
@@ -0,0 +1,41 @@
1
+ class Eye::Trigger::Flapping < Eye::Trigger
2
+
3
+ # trigger :flapping, :times => 10, :within => 1.minute,
4
+ # :retry_in => 10.minutes, :retry_times => 15
5
+
6
+ param :times, [Fixnum], true, 5
7
+ param :within, [Float, Fixnum], true
8
+ param :retry_in, [Float, Fixnum]
9
+ param :retry_times, [Fixnum]
10
+
11
+ def check(transition)
12
+ on_flapping if transition.event == :crashed && !good?
13
+ end
14
+
15
+ private
16
+
17
+ def good?
18
+ states = process.states_history.states_for_period( within, @last_at )
19
+ down_count = states.count{|st| st == :down }
20
+
21
+ if down_count >= times
22
+ @last_at = process.states_history.last_state_changed_at
23
+ false
24
+ else
25
+ true
26
+ end
27
+ end
28
+
29
+ def on_flapping
30
+ debug 'flapping recognized!!!'
31
+
32
+ process.notify :error, 'flapping!'
33
+ process.schedule :unmonitor, Eye::Reason.new(:flapping)
34
+
35
+ return unless retry_in
36
+ return if retry_times && process.flapping_times >= retry_times
37
+
38
+ process.schedule_in(retry_in.to_f, :retry_start_after_flapping)
39
+ end
40
+
41
+ end
@@ -0,0 +1,17 @@
1
+ class Eye::Trigger::StopChildren < Eye::Trigger
2
+
3
+ # Kill process children when parent process crashed, or stopped:
4
+ #
5
+ # trigger :stop_children
6
+
7
+ param :timeout, [Fixnum, Float], nil, 60
8
+
9
+ # default on stopped, crashed
10
+ param_default :event, [:stopped, :crashed]
11
+
12
+ def check(trans)
13
+ debug 'stopping children'
14
+ process.children.pmap { |pid, c| c.stop }
15
+ end
16
+
17
+ end
@@ -0,0 +1,15 @@
1
+ class Eye::Trigger::Transition < Eye::Trigger
2
+
3
+ # trigger :transition, :to => :up, :from => :starting, :do => ->{ ... }
4
+
5
+ param :do, [Proc, Symbol]
6
+
7
+ def check(trans)
8
+ act = @options[:do]
9
+ if act
10
+ instance_exec(&@options[:do]) if act.is_a?(Proc)
11
+ send(act, process) if act.is_a?(Symbol)
12
+ end
13
+ end
14
+
15
+ end
@@ -0,0 +1,49 @@
1
+ class Eye::Trigger::WaitDependency < Eye::Trigger
2
+ param :names, [Array], true
3
+ param :wait_timeout, [Numeric], nil, 15.seconds
4
+ param :retry_after, [Numeric], nil, 1.minute
5
+ param :should_start, [TrueClass, FalseClass]
6
+
7
+ def check(transition)
8
+ wait_dependency if transition.to_name == :starting
9
+ end
10
+
11
+ private
12
+
13
+ def wait_dependency
14
+ processes = names.map do |name|
15
+ Eye::Control.find_nearest_process(name, process.group_name_pure, process.app_name)
16
+ end.compact
17
+ return if processes.empty?
18
+ processes = Eye::Utils::AliveArray.new(processes)
19
+
20
+ processes.each do |p|
21
+ if p.state_name != :up && (should_start == nil || should_start)
22
+ p.schedule :start, Eye::Reason.new(:start_dependency)
23
+ end
24
+ end
25
+
26
+ res = true
27
+
28
+ processes.pmap do |p|
29
+ name = p.name
30
+
31
+ res &= process.wait_for_condition(wait_timeout, 0.5) do
32
+ info "wait for #{name} until it :up"
33
+ p.state_name == :up
34
+ end
35
+ end
36
+
37
+ unless res
38
+ warn "not waited for #{names} to be up"
39
+ process.switch :unmonitoring
40
+
41
+ if retry_after
42
+ process.schedule_in retry_after, :start, Eye::Reason.new(:wait_dependency)
43
+ end
44
+
45
+ raise Eye::Process::StateError.new('stop transition because dependency is not up')
46
+ end
47
+ end
48
+
49
+ end
@@ -0,0 +1,45 @@
1
+ require 'date'
2
+
3
+ module Eye::Utils
4
+ autoload :Tail, 'eye/utils/tail'
5
+ autoload :AliveArray, 'eye/utils/alive_array'
6
+ autoload :CelluloidChain, 'eye/utils/celluloid_chain'
7
+
8
+ def self.deep_clone(value)
9
+ case
10
+ when value.is_a?(Array) then value.map{|v| deep_clone(v) }
11
+ when value.is_a?(Hash) then value.inject({}){|r, (k, v)| r[ deep_clone(k) ] = deep_clone(v); r }
12
+ else value
13
+ end
14
+ end
15
+
16
+ # deep merging b into a (a deeply changed)
17
+ def self.deep_merge!(a, b, allowed_keys = nil)
18
+ b.each do |k, v|
19
+ next if allowed_keys && !allowed_keys.include?(k)
20
+ if a[k].is_a?(Hash) && v.is_a?(Hash)
21
+ deep_merge!(a[k], v)
22
+ else
23
+ a[k] = v
24
+ end
25
+ end
26
+ a
27
+ end
28
+
29
+ D1 = '%H:%M'
30
+ D2 = '%b%d'
31
+
32
+ def self.human_time(unix_time)
33
+ time = Time.at(unix_time.to_i)
34
+ d1 = time.to_date
35
+ d2 = Time.now.to_date
36
+ time.strftime (d1 == d2) ? D1 : D2
37
+ end
38
+
39
+ DF = '%d %b %H:%M'
40
+
41
+ def self.human_time2(unix_time)
42
+ Time.at(unix_time.to_i).strftime(DF)
43
+ end
44
+
45
+ end
@@ -0,0 +1,57 @@
1
+ class Eye::Utils::AliveArray
2
+ extend Forwardable
3
+ include Enumerable
4
+
5
+ def_delegators :@arr, :[], :<<, :clear, :delete, :size, :empty?, :push,
6
+ :flatten, :present?, :uniq!, :select!
7
+
8
+ def initialize(arr = [])
9
+ @arr = arr
10
+ end
11
+
12
+ def each(&block)
13
+ @arr.each{|elem| elem && elem.alive? && block[elem] }
14
+ end
15
+
16
+ def to_a
17
+ map{|x| x }
18
+ end
19
+
20
+ def full_size
21
+ @arr.size
22
+ end
23
+
24
+ def pure
25
+ @arr
26
+ end
27
+
28
+ def sort_by(&block)
29
+ self.class.new super
30
+ end
31
+
32
+ def sort(&block)
33
+ self.class.new super
34
+ end
35
+
36
+ def +(other)
37
+ if other.is_a?(Eye::Utils::AliveArray)
38
+ @arr += other.pure
39
+ elsif other.is_a?(Array)
40
+ @arr += other
41
+ else
42
+ raise "Unexpected + #{other}"
43
+ end
44
+ self
45
+ end
46
+
47
+ def ==(other)
48
+ if other.is_a?(Eye::Utils::AliveArray)
49
+ @arr == other.pure
50
+ elsif other.is_a?(Array)
51
+ @arr == other
52
+ else
53
+ raise "Unexpected == #{other}"
54
+ end
55
+ end
56
+
57
+ end
@@ -0,0 +1,71 @@
1
+ require 'celluloid'
2
+
3
+ class Eye::Utils::CelluloidChain
4
+ include Celluloid
5
+
6
+ def initialize(target)
7
+ @target = target
8
+ @calls = []
9
+ @running = false
10
+ @target_class = @target.class
11
+ end
12
+
13
+ def add(method_name, *args, &block)
14
+ @calls << {:method_name => method_name, :args => args, :block => block}
15
+ ensure_process
16
+ end
17
+
18
+ def add_wo_dups(method_name, *args, &block)
19
+ h = {:method_name => method_name, :args => args, :block => block}
20
+ if @calls[-1] != h
21
+ @calls << h
22
+ ensure_process
23
+ end
24
+ end
25
+
26
+ def add_wo_dups_current(method_name, *args, &block)
27
+ h = {:method_name => method_name, :args => args, :block => block}
28
+ if !@calls.include?(h) && @call != h
29
+ @calls << h
30
+ ensure_process
31
+ end
32
+ end
33
+
34
+ def list
35
+ @calls
36
+ end
37
+
38
+ def names_list
39
+ list.map{|el| el[:method_name].to_sym }
40
+ end
41
+
42
+ def clear
43
+ @calls = []
44
+ end
45
+
46
+ alias :clear_pending_list :clear
47
+
48
+ # need, because of https://github.com/celluloid/celluloid/issues/22
49
+ def inspect
50
+ "Celluloid::Chain(#{@target_class}: #{@calls.size})"
51
+ end
52
+
53
+ attr_reader :running
54
+
55
+ private
56
+
57
+ def ensure_process
58
+ unless @running
59
+ @running = true
60
+ async.process
61
+ end
62
+ end
63
+
64
+ def process
65
+ while @call = @calls.shift
66
+ @running = true
67
+ @target.send(@call[:method_name], *@call[:args], &@call[:block]) if @target.alive?
68
+ end
69
+ @running = false
70
+ end
71
+ end