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,57 @@
1
+ # Send a notice to Prowl (http://prowl.weks.net/).
2
+ #
3
+ # apikey - The String API key.
4
+
5
+ CONTACT_DEPS[:prowl] = ['prowly']
6
+ CONTACT_DEPS[:prowl].each do |d|
7
+ require d
8
+ end
9
+
10
+ module God
11
+ module Contacts
12
+ class Prowl < Contact
13
+
14
+ class << self
15
+ attr_accessor :apikey
16
+ end
17
+
18
+ def valid?
19
+ valid = true
20
+ valid &= complain("Attribute 'apikey' must be specified", self) if self.apikey.nil?
21
+ valid
22
+ end
23
+
24
+ attr_accessor :apikey
25
+
26
+ def notify(message, time, priority, category, host)
27
+ result = Prowly.notify do |n|
28
+ n.apikey = arg(:apikey)
29
+ n.priority = map_priority(priority.to_i)
30
+ n.application = category || "God"
31
+ n.event = "on " + host.to_s
32
+ n.description = message.to_s + " at " + time.to_s
33
+ end
34
+
35
+ if result.succeeded?
36
+ self.info = "sent prowl notification to #{self.name}"
37
+ else
38
+ self.info = "failed to send prowl notification to #{self.name}: #{result.message}"
39
+ end
40
+ rescue Object => e
41
+ applog(nil, :info, "failed to send prowl notification to #{self.name}: #{e.message}")
42
+ applog(nil, :debug, e.backtrace.join("\n"))
43
+ end
44
+
45
+ def map_priority(priority)
46
+ case priority
47
+ when 1 then Prowly::Notification::Priority::EMERGENCY
48
+ when 2 then Prowly::Notification::Priority::HIGH
49
+ when 3 then Prowly::Notification::Priority::NORMAL
50
+ when 4 then Prowly::Notification::Priority::MODERATE
51
+ when 5 then Prowly::Notification::Priority::VERY_LOW
52
+ else Prowly::Notification::Priority::NORMAL
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,55 @@
1
+ # Send a notice to Scout (http://scoutapp.com/).
2
+ #
3
+ # client_key - The String client key.
4
+ # plugin_id - The String plugin id.
5
+
6
+ require 'net/http'
7
+ require 'uri'
8
+
9
+ module God
10
+ module Contacts
11
+
12
+ class Scout < Contact
13
+ class << self
14
+ attr_accessor :client_key, :plugin_id
15
+ attr_accessor :format
16
+ end
17
+
18
+ self.format = lambda do |message, priority, category, host|
19
+ text = "Message: #{message}\n"
20
+ text += "Host: #{host}\n" if host
21
+ text += "Priority: #{priority}\n" if priority
22
+ text += "Category: #{category}\n" if category
23
+ return text
24
+ end
25
+
26
+ attr_accessor :client_key, :plugin_id
27
+
28
+ def valid?
29
+ valid = true
30
+ valid &= complain("Attribute 'client_key' must be specified", self) unless arg(:client_key)
31
+ valid &= complain("Attribute 'plugin_id' must be specified", self) unless arg(:plugin_id)
32
+ valid
33
+ end
34
+
35
+ def notify(message, time, priority, category, host)
36
+ data = {
37
+ :client_key => arg(:client_key),
38
+ :plugin_id => arg(:plugin_id),
39
+ :format => 'xml',
40
+ 'alert[subject]' => message,
41
+ 'alert[body]' => Scout.format.call(message, priority, category, host)
42
+ }
43
+
44
+ uri = URI.parse('http://scoutapp.com/alerts/create')
45
+ Net::HTTP.post_form(uri, data)
46
+
47
+ self.info = "sent scout alert to plugin ##{plugin_id}"
48
+ rescue => e
49
+ applog(nil, :info, "failed to send scout alert to plugin ##{plugin_id}: #{e.message}")
50
+ applog(nil, :debug, e.backtrace.join("\n"))
51
+ end
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,59 @@
1
+ # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2
+ # Send a notice to a SENSU client socket, port 3030 on 'localhost' only.
3
+ # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4
+ # [mandatory]
5
+ # check_name - a unique check name
6
+ #
7
+ # [optional]
8
+ # status_code - status codes used are 0 for OK, 1 for WARNING, 2 for CRITICAL, and 3 or greater to indicate UNKNOWN or CUSTOM.
9
+ # handler - default handler
10
+ #
11
+
12
+ CONTACT_DEPS[:sensu] = ['json']
13
+ CONTACT_DEPS[:sensu].each do |d|
14
+ require d
15
+ end
16
+
17
+ module God
18
+ module Contacts
19
+
20
+ class Sensu < Contact
21
+ class << self
22
+ attr_accessor :check_name, :status_code, :handler, :host, :port
23
+ end
24
+
25
+ self.status_code = 2
26
+ self.handler = 'default'
27
+ self.host = 'localhost'
28
+ self.port = 3030
29
+
30
+ def valid?
31
+ valid = true
32
+ valid &= complain("Attribute 'check_name' must be specified", self) unless arg(:check_name)
33
+ valid
34
+ end
35
+
36
+ attr_accessor :check_name, :status_code, :handler, :host, :port
37
+
38
+ def sensu_client_socket(msg)
39
+ u = UDPSocket.new
40
+ u.send(msg + "\n", 0, arg(:host).nil? ? self.host : arg(:host), arg(:port).nil? ? self.port : arg(:port))
41
+ u.close
42
+ end
43
+
44
+ def notify(message, time, priority, category, host)
45
+ data = {
46
+ :category => category,
47
+ :message => message,
48
+ :priority => priority,
49
+ :host => host,
50
+ :time => time,
51
+ }
52
+ parcel = { 'name' => arg(:check_name), 'status' => arg(:status_code).nil? ? self.status_code : arg(:status_code), 'output' => data.to_json, 'handler' => arg(:handler).empty? ? self.handler : arg(:handler), 'executed' => Time.now.to_i }
53
+ sensu_client_socket parcel.to_json
54
+ self.info = "notified sensu: #{arg(:check_name)}"
55
+ end
56
+ end
57
+ end
58
+ end
59
+
@@ -0,0 +1,98 @@
1
+ # Send a message to a Slack channel
2
+ #
3
+ # account - The name of your Slack account (visible in URL, e.g. foo.slack.com)
4
+ # token - The token of the webhook created in Slack
5
+ # channel - The name of the channel to send the message to, prefixed with #
6
+ # notify_channel - Whether to send an "@channel" in the message, to alert everyone in the channel
7
+ # format - An optional format string to change how the alert is displayed
8
+
9
+ require 'net/http'
10
+ require 'uri'
11
+
12
+ CONTACT_DEPS[:slack] = ['json']
13
+ CONTACT_DEPS[:slack].each do |d|
14
+ require d
15
+ end
16
+
17
+ module God
18
+ module Contacts
19
+
20
+ class Slack < Contact
21
+ class << self
22
+ attr_accessor :url, :channel, :notify_channel, :format, :username, :emoji
23
+ end
24
+
25
+ self.channel = "#general"
26
+ self.notify_channel = false
27
+ self.format = "%{priority} alert on %{host}: %{message} (%{category}, %{time})"
28
+
29
+ def valid?
30
+ valid = true
31
+ valid &= complain("Attribute 'url' must be specified", self) unless arg(:url)
32
+ valid
33
+ end
34
+
35
+ attr_accessor :url, :channel, :notify_channel, :format, :username, :emoji
36
+
37
+ def text(data)
38
+ text = ""
39
+ text << "<!channel> " if arg(:notify_channel)
40
+
41
+ if RUBY_VERSION =~ /^1\.8/
42
+ text << arg(:format).gsub(/%\{(\w+)\}/) do |match|
43
+ data[$1.to_sym]
44
+ end
45
+ else
46
+ text << arg(:format) % data
47
+ end
48
+
49
+ text
50
+ end
51
+
52
+ def notify(message, time, priority, category, host)
53
+ text = text({
54
+ :message => message,
55
+ :time => time,
56
+ :priority => priority,
57
+ :category => category,
58
+ :host => host
59
+ })
60
+
61
+ request(text)
62
+ end
63
+
64
+ def api_url
65
+ URI.parse arg(:url)
66
+ end
67
+
68
+ def request(text)
69
+ http = Net::HTTP.new(api_url.host, api_url.port)
70
+ http.use_ssl = true
71
+
72
+ req = Net::HTTP::Post.new(api_url.request_uri)
73
+ req.body = {
74
+ :link_names => 1,
75
+ :text => text,
76
+ :channel => arg(:channel)
77
+ }.tap { |payload|
78
+ payload[:username] = arg(:username) if arg(:username)
79
+ payload[:icon_emoji] = arg(:emoji) if arg(:emoji)
80
+ }.to_json
81
+
82
+ res = http.request(req)
83
+
84
+ case res
85
+ when Net::HTTPSuccess
86
+ self.info = "successfully notified slack on channel #{arg(:channel)}"
87
+ else
88
+ self.info = "failed to send webhook to #{arg(:url)}: #{res.error!}"
89
+ end
90
+ rescue Object => e
91
+ applog(nil, :info, "failed to send webhook to #{arg(:url)}: #{e.message}")
92
+ applog(nil, :debug, e.backtrace.join("\n"))
93
+ end
94
+
95
+ end
96
+
97
+ end
98
+ end
@@ -0,0 +1,46 @@
1
+ # Send a notice to statsd
2
+ #
3
+ # host - statsd host
4
+ # port - statsd port (optional)
5
+
6
+ require 'statsd-ruby'
7
+
8
+ module God
9
+ module Contacts
10
+
11
+ class Statsd < Contact
12
+ class << self
13
+ attr_accessor :host, :port
14
+ end
15
+
16
+ attr_accessor :host, :port
17
+
18
+ def valid?
19
+ valid = true
20
+ valid &= complain("Attribute 'statsd_host' must be specified", self) unless arg(:host)
21
+ valid
22
+ end
23
+
24
+ def notify(message, time, priority, category, hostname)
25
+ statsd = ::Statsd.new host, (port ? port.to_i : 8125) # 8125 is the default statsd port
26
+
27
+ hostname.gsub! /\./, '_'
28
+ app = message.gsub /([^\s]*).*/, '\1'
29
+
30
+ [
31
+ 'cpu out of bounds',
32
+ 'memory out of bounds',
33
+ 'process is flapping'
34
+ ].each do |event_type|
35
+ statsd.increment "god.#{event_type.gsub(/\s/, '_')}.#{hostname}.#{app}" if message.include? event_type
36
+ end
37
+
38
+ self.info = 'sent statsd alert'
39
+ rescue => e
40
+ applog(nil, :info, "failed to send statsd alert: #{e.message}")
41
+ applog(nil, :debug, e.backtrace.join("\n"))
42
+ end
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,51 @@
1
+ # Send a notice to a Twitter account (http://twitter.com/).
2
+ #
3
+ # consumer_token - The String OAuth consumer token (defaults to God's
4
+ # existing consumer token).
5
+ # consumer_secret - The String OAuth consumer secret (defaults to God's
6
+ # existing consumer secret).
7
+ # access_token - The String OAuth access token.
8
+ # access_secret - The String OAuth access secret.
9
+
10
+ CONTACT_DEPS[:twitter] = ['twitter']
11
+ CONTACT_DEPS[:twitter].each do |d|
12
+ require d
13
+ end
14
+
15
+ module God
16
+ module Contacts
17
+ class Twitter < Contact
18
+ class << self
19
+ attr_accessor :consumer_token, :consumer_secret,
20
+ :access_token, :access_secret
21
+ end
22
+
23
+ self.consumer_token = 'gOhjax6s0L3mLeaTtBWPw'
24
+ self.consumer_secret = 'yz4gpAVXJHKxvsGK85tEyzQJ7o2FEy27H1KEWL75jfA'
25
+
26
+ def valid?
27
+ valid = true
28
+ valid &= complain("Attribute 'consumer_token' must be specified", self) unless arg(:consumer_token)
29
+ valid &= complain("Attribute 'consumer_secret' must be specified", self) unless arg(:consumer_secret)
30
+ valid &= complain("Attribute 'access_token' must be specified", self) unless arg(:access_token)
31
+ valid &= complain("Attribute 'access_secret' must be specified", self) unless arg(:access_secret)
32
+ valid
33
+ end
34
+
35
+ attr_accessor :consumer_token, :consumer_secret,
36
+ :access_token, :access_secret
37
+
38
+ def notify(message, time, priority, category, host)
39
+ oauth = ::Twitter::OAuth.new(arg(:consumer_token), arg(:consumer_secret))
40
+ oauth.authorize_from_access(arg(:access_token), arg(:access_secret))
41
+
42
+ ::Twitter::Base.new(oauth).update(message)
43
+
44
+ self.info = "sent twitter update"
45
+ rescue => e
46
+ applog(nil, :info, "failed to send twitter update: #{e.message}")
47
+ applog(nil, :debug, e.backtrace.join("\n"))
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,74 @@
1
+ # Send a notice to a webhook.
2
+ #
3
+ # url - The String webhook URL.
4
+ # format - The Symbol format [ :form | :json ] (default: :form).
5
+
6
+ require 'net/http'
7
+ require 'uri'
8
+
9
+ CONTACT_DEPS[:webhook] = ['json']
10
+ CONTACT_DEPS[:webhook].each do |d|
11
+ require d
12
+ end
13
+
14
+ module God
15
+ module Contacts
16
+
17
+ class Webhook < Contact
18
+ class << self
19
+ attr_accessor :url, :format
20
+ end
21
+
22
+ self.format = :form
23
+
24
+ def valid?
25
+ valid = true
26
+ valid &= complain("Attribute 'url' must be specified", self) unless arg(:url)
27
+ valid &= complain("Attribute 'format' must be one of [ :form | :json ]", self) unless [:form, :json].include?(arg(:format))
28
+ valid
29
+ end
30
+
31
+ attr_accessor :url, :format
32
+
33
+ def notify(message, time, priority, category, host)
34
+ data = {
35
+ :message => message,
36
+ :time => time,
37
+ :priority => priority,
38
+ :category => category,
39
+ :host => host
40
+ }
41
+
42
+ uri = URI.parse(arg(:url))
43
+ http = Net::HTTP.new(uri.host, uri.port)
44
+ http.use_ssl = true if uri.scheme == "https"
45
+
46
+ req = nil
47
+ res = nil
48
+
49
+ case arg(:format)
50
+ when :form
51
+ req = Net::HTTP::Post.new(uri.request_uri)
52
+ req.set_form_data(data)
53
+ when :json
54
+ req = Net::HTTP::Post.new(uri.request_uri)
55
+ req.body = data.to_json
56
+ end
57
+
58
+ res = http.request(req)
59
+
60
+ case res
61
+ when Net::HTTPSuccess
62
+ self.info = "sent webhook to #{arg(:url)}"
63
+ else
64
+ self.info = "failed to send webhook to #{arg(:url)}: #{res.error!}"
65
+ end
66
+ rescue Object => e
67
+ applog(nil, :info, "failed to send webhook to #{arg(:url)}: #{e.message}")
68
+ applog(nil, :debug, e.backtrace.join("\n"))
69
+ end
70
+
71
+ end
72
+
73
+ end
74
+ end