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