mcproc 2016.2.20

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 (143) hide show
  1. checksums.yaml +7 -0
  2. data/Announce.txt +135 -0
  3. data/Gemfile +9 -0
  4. data/History.txt +469 -0
  5. data/LICENSE +22 -0
  6. data/README.md +37 -0
  7. data/Rakefile +185 -0
  8. data/TODO.md +37 -0
  9. data/bin/mcproc +134 -0
  10. data/doc/intro.asciidoc +20 -0
  11. data/doc/mcproc.asciidoc +1592 -0
  12. data/ext/god/.gitignore +5 -0
  13. data/ext/god/extconf.rb +56 -0
  14. data/ext/god/kqueue_handler.c +133 -0
  15. data/ext/god/netlink_handler.c +182 -0
  16. data/lib/god.rb +780 -0
  17. data/lib/god/behavior.rb +52 -0
  18. data/lib/god/behaviors/clean_pid_file.rb +21 -0
  19. data/lib/god/behaviors/clean_unix_socket.rb +21 -0
  20. data/lib/god/behaviors/notify_when_flapping.rb +51 -0
  21. data/lib/god/cli/command.rb +268 -0
  22. data/lib/god/cli/run.rb +170 -0
  23. data/lib/god/cli/version.rb +23 -0
  24. data/lib/god/compat19.rb +33 -0
  25. data/lib/god/condition.rb +96 -0
  26. data/lib/god/conditions/always.rb +36 -0
  27. data/lib/god/conditions/complex.rb +86 -0
  28. data/lib/god/conditions/cpu_usage.rb +80 -0
  29. data/lib/god/conditions/degrading_lambda.rb +52 -0
  30. data/lib/god/conditions/disk_usage.rb +32 -0
  31. data/lib/god/conditions/file_mtime.rb +28 -0
  32. data/lib/god/conditions/file_touched.rb +44 -0
  33. data/lib/god/conditions/flapping.rb +128 -0
  34. data/lib/god/conditions/http_response_code.rb +184 -0
  35. data/lib/god/conditions/lambda.rb +25 -0
  36. data/lib/god/conditions/memory_usage.rb +82 -0
  37. data/lib/god/conditions/process_exits.rb +66 -0
  38. data/lib/god/conditions/process_running.rb +63 -0
  39. data/lib/god/conditions/socket_responding.rb +142 -0
  40. data/lib/god/conditions/tries.rb +44 -0
  41. data/lib/god/configurable.rb +57 -0
  42. data/lib/god/contact.rb +114 -0
  43. data/lib/god/contacts/airbrake.rb +44 -0
  44. data/lib/god/contacts/campfire.rb +121 -0
  45. data/lib/god/contacts/email.rb +130 -0
  46. data/lib/god/contacts/hipchat.rb +117 -0
  47. data/lib/god/contacts/jabber.rb +75 -0
  48. data/lib/god/contacts/prowl.rb +57 -0
  49. data/lib/god/contacts/scout.rb +55 -0
  50. data/lib/god/contacts/sensu.rb +59 -0
  51. data/lib/god/contacts/slack.rb +98 -0
  52. data/lib/god/contacts/statsd.rb +46 -0
  53. data/lib/god/contacts/twitter.rb +51 -0
  54. data/lib/god/contacts/webhook.rb +74 -0
  55. data/lib/god/driver.rb +238 -0
  56. data/lib/god/errors.rb +24 -0
  57. data/lib/god/event_handler.rb +112 -0
  58. data/lib/god/event_handlers/dummy_handler.rb +13 -0
  59. data/lib/god/event_handlers/kqueue_handler.rb +17 -0
  60. data/lib/god/event_handlers/netlink_handler.rb +13 -0
  61. data/lib/god/logger.rb +109 -0
  62. data/lib/god/metric.rb +87 -0
  63. data/lib/god/process.rb +381 -0
  64. data/lib/god/registry.rb +32 -0
  65. data/lib/god/simple_logger.rb +59 -0
  66. data/lib/god/socket.rb +113 -0
  67. data/lib/god/sugar.rb +62 -0
  68. data/lib/god/sys_logger.rb +45 -0
  69. data/lib/god/system/portable_poller.rb +42 -0
  70. data/lib/god/system/process.rb +50 -0
  71. data/lib/god/system/slash_proc_poller.rb +92 -0
  72. data/lib/god/task.rb +552 -0
  73. data/lib/god/timeline.rb +25 -0
  74. data/lib/god/trigger.rb +43 -0
  75. data/lib/god/watch.rb +340 -0
  76. data/mcproc.gemspec +192 -0
  77. data/test/configs/child_events/child_events.god +44 -0
  78. data/test/configs/child_events/simple_server.rb +3 -0
  79. data/test/configs/child_polls/child_polls.god +37 -0
  80. data/test/configs/child_polls/simple_server.rb +12 -0
  81. data/test/configs/complex/complex.god +59 -0
  82. data/test/configs/complex/simple_server.rb +3 -0
  83. data/test/configs/contact/contact.god +118 -0
  84. data/test/configs/contact/simple_server.rb +3 -0
  85. data/test/configs/daemon_events/daemon_events.god +37 -0
  86. data/test/configs/daemon_events/simple_server.rb +8 -0
  87. data/test/configs/daemon_events/simple_server_stop.rb +11 -0
  88. data/test/configs/daemon_polls/daemon_polls.god +17 -0
  89. data/test/configs/daemon_polls/simple_server.rb +6 -0
  90. data/test/configs/degrading_lambda/degrading_lambda.god +31 -0
  91. data/test/configs/degrading_lambda/tcp_server.rb +15 -0
  92. data/test/configs/keepalive/keepalive.god +9 -0
  93. data/test/configs/keepalive/keepalive.rb +12 -0
  94. data/test/configs/lifecycle/lifecycle.god +25 -0
  95. data/test/configs/matias/matias.god +50 -0
  96. data/test/configs/real.rb +59 -0
  97. data/test/configs/running_load/running_load.god +16 -0
  98. data/test/configs/stop_options/simple_server.rb +12 -0
  99. data/test/configs/stop_options/stop_options.god +39 -0
  100. data/test/configs/stress/simple_server.rb +3 -0
  101. data/test/configs/stress/stress.god +15 -0
  102. data/test/configs/task/logs/.placeholder +0 -0
  103. data/test/configs/task/task.god +26 -0
  104. data/test/configs/test.rb +61 -0
  105. data/test/configs/usr1_trapper.rb +10 -0
  106. data/test/helper.rb +172 -0
  107. data/test/suite.rb +6 -0
  108. data/test/test_airbrake.rb +14 -0
  109. data/test/test_behavior.rb +18 -0
  110. data/test/test_campfire.rb +22 -0
  111. data/test/test_condition.rb +52 -0
  112. data/test/test_conditions_disk_usage.rb +50 -0
  113. data/test/test_conditions_http_response_code.rb +109 -0
  114. data/test/test_conditions_process_running.rb +40 -0
  115. data/test/test_conditions_socket_responding.rb +176 -0
  116. data/test/test_conditions_tries.rb +67 -0
  117. data/test/test_contact.rb +109 -0
  118. data/test/test_driver.rb +26 -0
  119. data/test/test_email.rb +34 -0
  120. data/test/test_event_handler.rb +82 -0
  121. data/test/test_god.rb +710 -0
  122. data/test/test_god_system.rb +201 -0
  123. data/test/test_handlers_kqueue_handler.rb +16 -0
  124. data/test/test_hipchat.rb +23 -0
  125. data/test/test_jabber.rb +29 -0
  126. data/test/test_logger.rb +55 -0
  127. data/test/test_metric.rb +74 -0
  128. data/test/test_process.rb +263 -0
  129. data/test/test_prowl.rb +15 -0
  130. data/test/test_registry.rb +15 -0
  131. data/test/test_sensu.rb +11 -0
  132. data/test/test_slack.rb +57 -0
  133. data/test/test_socket.rb +34 -0
  134. data/test/test_statsd.rb +22 -0
  135. data/test/test_sugar.rb +42 -0
  136. data/test/test_system_portable_poller.rb +17 -0
  137. data/test/test_system_process.rb +30 -0
  138. data/test/test_task.rb +246 -0
  139. data/test/test_timeline.rb +37 -0
  140. data/test/test_trigger.rb +63 -0
  141. data/test/test_watch.rb +286 -0
  142. data/test/test_webhook.rb +22 -0
  143. metadata +475 -0
@@ -0,0 +1,23 @@
1
+ module God
2
+ module CLI
3
+
4
+ class Version
5
+ def self.version
6
+ require 'god'
7
+
8
+ # print version
9
+ puts "Version #{God.version}"
10
+ exit
11
+ end
12
+
13
+ def self.version_extended
14
+ puts "Version: #{God.version}"
15
+ puts "Polls: enabled"
16
+ puts "Events: " + God::EventHandler.event_system
17
+
18
+ exit
19
+ end
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,33 @@
1
+ require 'monitor'
2
+
3
+ # Taken from http://redmine.ruby-lang.org/repositories/entry/ruby-19/lib/monitor.rb
4
+
5
+ module MonitorMixin
6
+ class ConditionVariable
7
+ def wait(timeout = nil)
8
+ @monitor.__send__(:mon_check_owner)
9
+ count = @monitor.__send__(:mon_exit_for_cond)
10
+ begin
11
+ @cond.wait(@monitor.instance_variable_get("@mon_mutex"), timeout)
12
+ return true
13
+ ensure
14
+ @monitor.__send__(:mon_enter_for_cond, count)
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ # Taken from http://redmine.ruby-lang.org/repositories/entry/ruby-19/lib/thread.rb
21
+
22
+ class ConditionVariable
23
+ def wait(mutex, timeout=nil)
24
+ begin
25
+ # TODO: mutex should not be used
26
+ @waiters_mutex.synchronize do
27
+ @waiters.push(Thread.current)
28
+ end
29
+ mutex.sleep timeout
30
+ end
31
+ self
32
+ end
33
+ end
@@ -0,0 +1,96 @@
1
+ module God
2
+
3
+ class Condition < Behavior
4
+ attr_accessor :transition, :notify, :info, :phase
5
+
6
+ # Generate a Condition of the given kind. The proper class if found by camel casing the
7
+ # kind (which is given as an underscored symbol).
8
+ # +kind+ is the underscored symbol representing the class (e.g. :foo_bar for God::Conditions::FooBar)
9
+ def self.generate(kind, watch)
10
+ sym = kind.to_s.capitalize.gsub(/_(.)/){$1.upcase}.intern
11
+ c = God::Conditions.const_get(sym).new
12
+
13
+ unless c.kind_of?(PollCondition) || c.kind_of?(EventCondition) || c.kind_of?(TriggerCondition)
14
+ abort "Condition '#{c.class.name}' must subclass God::PollCondition, God::EventCondition, or God::TriggerCondition"
15
+ end
16
+
17
+ if !EventHandler.loaded? && c.kind_of?(EventCondition)
18
+ abort "Condition '#{c.class.name}' requires an event system but none has been loaded"
19
+ end
20
+
21
+ c.watch = watch
22
+ c
23
+ rescue NameError
24
+ raise NoSuchConditionError.new("No Condition found with the class name God::Conditions::#{sym}")
25
+ end
26
+
27
+ def self.valid?(condition)
28
+ valid = true
29
+ if condition.notify
30
+ begin
31
+ Contact.normalize(condition.notify)
32
+ rescue ArgumentError => e
33
+ valid &= Configurable.complain("Attribute 'notify' " + e.message, condition)
34
+ end
35
+ end
36
+ valid
37
+ end
38
+
39
+ # Construct the friendly name of this Condition, looks like:
40
+ #
41
+ # Condition FooBar on Watch 'baz'
42
+ def friendly_name
43
+ "Condition #{self.class.name.split('::').last} on Watch '#{self.watch.name}'"
44
+ end
45
+ end
46
+
47
+ class PollCondition < Condition
48
+ # all poll conditions can specify a poll interval
49
+ attr_accessor :interval
50
+
51
+ # Override this method in your Conditions (optional)
52
+ def before
53
+ end
54
+
55
+ # Override this method in your Conditions (mandatory)
56
+ #
57
+ # Return true if the test passes (everything is ok)
58
+ # Return false otherwise
59
+ def test
60
+ raise AbstractMethodNotOverriddenError.new("PollCondition#test must be overridden in subclasses")
61
+ end
62
+
63
+ # Override this method in your Conditions (optional)
64
+ def after
65
+ end
66
+ end
67
+
68
+ class EventCondition < Condition
69
+ def register
70
+ raise AbstractMethodNotOverriddenError.new("EventCondition#register must be overridden in subclasses")
71
+ end
72
+
73
+ def deregister
74
+ raise AbstractMethodNotOverriddenError.new("EventCondition#deregister must be overridden in subclasses")
75
+ end
76
+ end
77
+
78
+ class TriggerCondition < Condition
79
+ def process(event, payload)
80
+ raise AbstractMethodNotOverriddenError.new("TriggerCondition#process must be overridden in subclasses")
81
+ end
82
+
83
+ def trigger
84
+ self.watch.trigger(self)
85
+ end
86
+
87
+ def register
88
+ Trigger.register(self)
89
+ end
90
+
91
+ def deregister
92
+ Trigger.deregister(self)
93
+ end
94
+ end
95
+
96
+ end
@@ -0,0 +1,36 @@
1
+ module God
2
+ module Conditions
3
+ # Always trigger or never trigger.
4
+ #
5
+ # Examples
6
+ #
7
+ # # Always trigger.
8
+ # on.condition(:always) do |c|
9
+ # c.what = true
10
+ # end
11
+ #
12
+ # # Never trigger.
13
+ # on.condition(:always) do |c|
14
+ # c.what = false
15
+ # end
16
+ class Always < PollCondition
17
+ # The Boolean determining whether this condition will always trigger
18
+ # (true) or never trigger (false).
19
+ attr_accessor :what
20
+
21
+ def initialize
22
+ self.info = "always"
23
+ end
24
+
25
+ def valid?
26
+ valid = true
27
+ valid &= complain("Attribute 'what' must be specified", self) if self.what.nil?
28
+ valid
29
+ end
30
+
31
+ def test
32
+ @what
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,86 @@
1
+ module God
2
+ module Conditions
3
+
4
+ class Complex < PollCondition
5
+ AND = 0x1
6
+ OR = 0x2
7
+ NOT = 0x4
8
+
9
+ def initialize()
10
+ super
11
+
12
+ @oper_stack = []
13
+ @op_stack = []
14
+
15
+ @this = nil
16
+ end
17
+
18
+ def valid?
19
+ @oper_stack.inject(true) { |acc, oper| acc & oper.valid? }
20
+ end
21
+
22
+ def prepare
23
+ @oper_stack.each { |oper| oper.prepare }
24
+ end
25
+
26
+ def new_oper(kind, op)
27
+ oper = Condition.generate(kind, self.watch)
28
+ @oper_stack.push(oper)
29
+ @op_stack.push(op)
30
+ oper
31
+ end
32
+
33
+ def this(kind)
34
+ @this = Condition.generate(kind, self.watch)
35
+ yield @this if block_given?
36
+ end
37
+
38
+ def and(kind)
39
+ oper = new_oper(kind, 0x1)
40
+ yield oper if block_given?
41
+ end
42
+
43
+ def and_not(kind)
44
+ oper = new_oper(kind, 0x5)
45
+ yield oper if block_given?
46
+ end
47
+
48
+ def or(kind)
49
+ oper = new_oper(kind, 0x2)
50
+ yield oper if block_given?
51
+ end
52
+
53
+ def or_not(kind)
54
+ oper = new_oper(kind, 0x6)
55
+ yield oper if block_given?
56
+ end
57
+
58
+ def test
59
+ if @this.nil?
60
+ # Although this() makes sense semantically and therefore
61
+ # encourages easy-to-read conditions, being able to omit it
62
+ # allows for more DRY code in some cases, so we deal with a
63
+ # nil @this here by initially setting res to true or false,
64
+ # depending on whether the first operator used is AND or OR
65
+ # respectively.
66
+ if 0 < @op_stack[0] & AND
67
+ res = true
68
+ else
69
+ res = false
70
+ end
71
+ else
72
+ res = @this.test
73
+ end
74
+
75
+ @op_stack.each do |op|
76
+ cond = @oper_stack.shift
77
+ eval "res " + ((0 < op & AND) ? "&&" : "||") + "= " + ((0 < op & NOT) ? "!" : "") + "cond.test"
78
+ @oper_stack.push cond
79
+ end
80
+
81
+ res
82
+ end
83
+ end
84
+
85
+ end
86
+ end
@@ -0,0 +1,80 @@
1
+ module God
2
+ module Conditions
3
+
4
+ # Condition Symbol :cpu_usage
5
+ # Type: Poll
6
+ #
7
+ # Trigger when the percent of CPU use of a process is above a specified limit.
8
+ # On multi-core systems, this number could conceivably be above 100.
9
+ #
10
+ # Paramaters
11
+ # Required
12
+ # +pid_file+ is the pid file of the process in question. Automatically
13
+ # populated for Watches.
14
+ # +above+ is the percent CPU above which to trigger the condition. You
15
+ # may use #percent to clarify this amount (see examples).
16
+ #
17
+ # Examples
18
+ #
19
+ # Trigger if the process is using more than 25 percent of the cpu (from a Watch):
20
+ #
21
+ # on.condition(:cpu_usage) do |c|
22
+ # c.above = 25.percent
23
+ # end
24
+ #
25
+ # Non-Watch Tasks must specify a PID file:
26
+ #
27
+ # on.condition(:cpu_usage) do |c|
28
+ # c.above = 25.percent
29
+ # c.pid_file = "/var/run/mongrel.3000.pid"
30
+ # end
31
+ class CpuUsage < PollCondition
32
+ attr_accessor :above, :times, :pid_file
33
+
34
+ def initialize
35
+ super
36
+ self.above = nil
37
+ self.times = [1, 1]
38
+ end
39
+
40
+ def prepare
41
+ if self.times.kind_of?(Integer)
42
+ self.times = [self.times, self.times]
43
+ end
44
+
45
+ @timeline = Timeline.new(self.times[1])
46
+ end
47
+
48
+ def reset
49
+ @timeline.clear
50
+ end
51
+
52
+ def pid
53
+ self.pid_file ? File.read(self.pid_file).strip.to_i : self.watch.pid
54
+ end
55
+
56
+ def valid?
57
+ valid = true
58
+ valid &= complain("Attribute 'pid_file' must be specified", self) if self.pid_file.nil? && self.watch.pid_file.nil?
59
+ valid &= complain("Attribute 'above' must be specified", self) if self.above.nil?
60
+ valid
61
+ end
62
+
63
+ def test
64
+ process = System::Process.new(self.pid)
65
+ @timeline.push(process.percent_cpu)
66
+ self.info = []
67
+
68
+ history = "[" + @timeline.map { |x| "#{x > self.above ? '*' : ''}#{x}%%" }.join(", ") + "]"
69
+
70
+ if @timeline.select { |x| x > self.above }.size >= self.times.first
71
+ self.info = "cpu out of bounds #{history}"
72
+ return true
73
+ else
74
+ return false
75
+ end
76
+ end
77
+ end
78
+
79
+ end
80
+ end
@@ -0,0 +1,52 @@
1
+ module God
2
+ module Conditions
3
+
4
+ # This condition degrades its interval by a factor of two for 3 tries before failing
5
+ class DegradingLambda < PollCondition
6
+ attr_accessor :lambda
7
+
8
+ def initialize
9
+ super
10
+ @tries = 0
11
+ end
12
+
13
+ def valid?
14
+ valid = true
15
+ valid &= complain("Attribute 'lambda' must be specified", self) if self.lambda.nil?
16
+ valid
17
+ end
18
+
19
+ def test
20
+ puts "Calling test. Interval at #{self.interval}"
21
+ @original_interval ||= self.interval
22
+ unless pass?
23
+ if @tries == 2
24
+ self.info = "lambda condition was satisfied"
25
+ return true
26
+ end
27
+ self.interval = self.interval / 2.0
28
+ @tries += 1
29
+ else
30
+ @tries = 0
31
+ self.interval = @original_interval
32
+ end
33
+
34
+ self.info = "lambda condition was not satisfied"
35
+ false
36
+ end
37
+
38
+ private
39
+
40
+ def pass?
41
+ begin
42
+ Timeout::timeout(@interval) {
43
+ self.lambda.call()
44
+ }
45
+ rescue Timeout::Error
46
+ false
47
+ end
48
+ end
49
+ end
50
+
51
+ end
52
+ end
@@ -0,0 +1,32 @@
1
+ module God
2
+ module Conditions
3
+
4
+ class DiskUsage < PollCondition
5
+ attr_accessor :above, :mount_point
6
+
7
+ def initialize
8
+ super
9
+ self.above = nil
10
+ self.mount_point = nil
11
+ end
12
+
13
+ def valid?
14
+ valid = true
15
+ valid &= complain("Attribute 'mount_point' must be specified", self) if self.mount_point.nil?
16
+ valid &= complain("Attribute 'above' must be specified", self) if self.above.nil?
17
+ valid
18
+ end
19
+
20
+ def test
21
+ self.info = []
22
+ usage = `df -P | grep -i " #{self.mount_point}$" | awk '{print $5}' | sed 's/%//'`
23
+ if usage.to_i > self.above
24
+ self.info = "disk space out of bounds"
25
+ return true
26
+ else
27
+ return false
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end