sensu-plugins-graphite-donotuse 0.0.7

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 007ce4e24dd62f30781f277bae21bf095d0ff8eb
4
+ data.tar.gz: 47df008afcaadc938f9ae9f9c037bb705ac0a6c8
5
+ SHA512:
6
+ metadata.gz: b7dfcbfedf2ea1c35988b92952ae8b953be13b430eb935c99688857553ea3b58aceda55c177b178c51e2dfd32a64e3195f93d6bc5b047367fd25873563f4185b
7
+ data.tar.gz: 1e5893098de3a5863e60cc4e0102a90d92f675f85784f8898c5b91a66a6d3665853bf998b3f6a5405ae9b922b3b0011c61d1ad444131a78ecf31b37a5c433d88
@@ -0,0 +1,49 @@
1
+ #Change Log
2
+ This project adheres to [Semantic Versioning](http://semver.org/).
3
+
4
+ This CHANGELOG follows the format listed at [Keep A Changelog](http://keepachangelog.com/)
5
+
6
+ ## [Unreleased]
7
+ - nothing
8
+
9
+ ## [0.0.7] - 2015-09-29
10
+ ### Added
11
+ - add -r option (Reverse the warning/crit scale (if value is less than instead of greater than)) to check-graphite-stats.rb
12
+
13
+ ### Changed
14
+ - The short command line option for 'Add an auth token to the HTTP request' is now -A, -a clashed with the proxy support
15
+ - Set socket's SSL mode only if using HTTPS
16
+
17
+ ## [0.0.6] - 2015-08-27
18
+ ### Added
19
+ - check on number of hosts
20
+ - -auth param allows authentication by bearer token
21
+
22
+ ## [0.0.5] - 2015-08-05
23
+ ### Changed
24
+ - general cleanup
25
+
26
+ ## [0.0.4] - 2015-07-14
27
+ ### Changed
28
+ - updated sensu-plugin gem to 1.2.0
29
+
30
+ ## [0.0.3] - 2015-06-16
31
+ ### Fixed
32
+ - removed outdated dependency on openssl
33
+
34
+ ## [0.0.2] - 2015-06-02
35
+ ### Fixed
36
+ - added binstubs
37
+ ### Changed
38
+ - removed cruft from /lib
39
+
40
+ ## 0.0.1 - 2015-04-30
41
+ ### Added
42
+ - initial release
43
+
44
+ [unreleased]: https://github.com/sensu-plugins/sensu-plugins-graphite/compare/0.0.6...HEAD
45
+ [0.0.6]: https://github.com/sensu-plugins/sensu-plugins-graphite/compare/0.0.5...0.0.6
46
+ [0.0.5]: https://github.com/sensu-plugins/sensu-plugins-graphite/compare/0.0.4...0.0.5
47
+ [0.0.4]: https://github.com/sensu-plugins/sensu-plugins-graphite/compare/0.0.3...0.0.4
48
+ [0.0.3]: https://github.com/sensu-plugins/sensu-plugins-graphite/compare/0.0.2...0.0.3
49
+ [0.0.2]: https://github.com/sensu-plugins/sensu-plugins-graphite/compare/0.0.1...0.0.2
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Sensu-Plugins
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,52 @@
1
+ ## Sensu-Plugins-graphite
2
+
3
+ [ ![Build Status](https://travis-ci.org/sensu-plugins/sensu-plugins-graphite.svg?branch=master)](https://travis-ci.org/sensu-plugins/sensu-plugins-graphite)
4
+ [![Gem Version](https://badge.fury.io/rb/sensu-plugins-graphite.svg)](http://badge.fury.io/rb/sensu-plugins-graphite)
5
+ [![Code Climate](https://codeclimate.com/github/sensu-plugins/sensu-plugins-graphite/badges/gpa.svg)](https://codeclimate.com/github/sensu-plugins/sensu-plugins-graphite)
6
+ [![Test Coverage](https://codeclimate.com/github/sensu-plugins/sensu-plugins-graphite/badges/coverage.svg)](https://codeclimate.com/github/sensu-plugins/sensu-plugins-graphite)
7
+ [![Dependency Status](https://gemnasium.com/sensu-plugins/sensu-plugins-graphite.svg)](https://gemnasium.com/sensu-plugins/sensu-plugins-graphite)
8
+ [![Codeship Status for sensu-plugins/sensu-plugins-graphite](https://codeship.com/projects/c6f4f5a0-db95-0132-445b-5ad94843e341/status?branch=master)](https://codeship.com/projects/79664)
9
+
10
+ ## Functionality
11
+
12
+ ## Files
13
+ * bin/check-graphite-data
14
+ * bin/check-graphite-replication
15
+ * bin/check-graphite-stats
16
+ * bin/check-graphite
17
+ * bin/extension-graphite
18
+ * bin/handler-graphite-event
19
+ * bin/handler-graphite-notify
20
+ * bin/handler-graphite-occurances
21
+ * bin/mutator-graphite
22
+
23
+ ## Usage
24
+
25
+ **handler-graphite-event**
26
+ ```
27
+ {
28
+ "graphite_event": {
29
+ "server_uri": "https://graphite.example.com:443/events/",
30
+ "tags": [
31
+ "custom_tag_a",
32
+ "custom_tag_b"
33
+ ]
34
+ }
35
+ }
36
+ ```
37
+
38
+ **handler-graphite-occurances**
39
+ ```
40
+ {
41
+ "graphite": {
42
+ "server":"graphite.example.com",
43
+ "port":"2003"
44
+ }
45
+ }
46
+ ```
47
+
48
+ ## Installation
49
+
50
+ [Installation and Setup](http://sensu-plugins.io/docs/installation_instructions.html)
51
+
52
+ ## Notes
@@ -0,0 +1,177 @@
1
+ #! /usr/bin/env ruby
2
+ #
3
+ # check-data
4
+ #
5
+ # DESCRIPTION:
6
+ # This plugin checks values within graphite
7
+ #
8
+ # OUTPUT:
9
+ # plain text
10
+ #
11
+ # PLATFORMS:
12
+ # Linux
13
+ #
14
+ # DEPENDENCIES:
15
+ # gem: sensu-plugin
16
+ #
17
+ # USAGE:
18
+ # #YELLOW
19
+ #
20
+ # NOTES:
21
+ #
22
+ # LICENSE:
23
+ # Copyright 2014 Sonian, Inc. and contributors. <support@sensuapp.org>
24
+ # Released under the same terms as Sensu (the MIT license); see LICENSE
25
+ # for details.
26
+ #
27
+
28
+ require 'sensu-plugin/check/cli'
29
+ require 'json'
30
+ require 'open-uri'
31
+ require 'openssl'
32
+
33
+ require 'sensu-plugins-graphite/graphite_proxy/options'
34
+ require 'sensu-plugins-graphite/graphite_proxy/proxy'
35
+
36
+ class CheckGraphiteData < Sensu::Plugin::Check::CLI
37
+ include SensuPluginsGraphite::GraphiteProxy::Options
38
+
39
+ option :reset_on_decrease,
40
+ description: 'Send OK if value has decreased on any values within END-INTERVAL to END',
41
+ short: '-r INTERVAL',
42
+ long: '--reset INTERVAL',
43
+ proc: proc(&:to_i)
44
+
45
+ option :allowed_graphite_age,
46
+ description: 'Allowed number of seconds since last data update (default: 60 seconds)',
47
+ short: '-a SECONDS',
48
+ long: '--age SECONDS',
49
+ default: 60,
50
+ proc: proc(&:to_i)
51
+
52
+ # Run checks
53
+ def run
54
+ if config[:help]
55
+ puts opt_parser if config[:help]
56
+ exit
57
+ end
58
+
59
+ proxy = SensuPluginsGraphite::GraphiteProxy::Proxy.new(config)
60
+ begin
61
+ results = proxy.retrieve_data!
62
+ results.each_pair do |_key, value|
63
+ @value = value
64
+ @data = value['data']
65
+ check_age || check(:critical) || check(:warning)
66
+ end
67
+
68
+ ok("#{name} value okay")
69
+ rescue SensuPluginsGraphite::GraphiteProxy::ProxyError => e
70
+ unknown e.message
71
+ end
72
+ end
73
+
74
+ # name used in responses
75
+ def name
76
+ base = config[:name]
77
+ @formatted ? "#{base} (#{@formatted})" : base
78
+ end
79
+
80
+ # Check the age of the data being processed
81
+ def check_age
82
+ if (Time.now.to_i - @value['end']) > config[:allowed_graphite_age]
83
+ unknown "Graphite data age is past allowed threshold (#{config[:allowed_graphite_age]} seconds)"
84
+ end
85
+ end
86
+
87
+ # grab data from graphite
88
+ def retrieve_data
89
+ unless @raw_data
90
+ begin
91
+ unless config[:server].start_with?('https://', 'http://')
92
+ config[:server].prepend('http://')
93
+ end
94
+
95
+ url = "#{config[:server]}/render?format=json&target=#{formatted_target}&from=#{config[:from]}"
96
+
97
+ url_opts = {}
98
+
99
+ if config[:no_ssl_verify]
100
+ url_opts[:ssl_verify_mode] = OpenSSL::SSL::VERIFY_NONE
101
+ end
102
+
103
+ if config[:username] && (config[:password] || config[:passfile])
104
+ if config[:passfile]
105
+ pass = File.open(config[:passfile]).readline
106
+ elsif config[:password]
107
+ pass = config[:password]
108
+ end
109
+
110
+ url_opts[:http_basic_authentication] = [config[:username], pass.chomp]
111
+ end # we don't have both username and password trying without
112
+
113
+ handle = open(url, url_opts)
114
+
115
+ @raw_data = handle.gets
116
+ if @raw_data == '[]'
117
+ unknown 'Empty data received from Graphite - metric probably doesn\'t exists'
118
+ else
119
+ @json_data = JSON.parse(@raw_data)
120
+ output = {}
121
+ @json_data.each do |raw|
122
+ raw['datapoints'].delete_if { |v| v.first.nil? }
123
+ next if raw['datapoints'].empty?
124
+ target = raw['target']
125
+ data = raw['datapoints'].map(&:first)
126
+ start = raw['datapoints'].first.last
127
+ dend = raw['datapoints'].last.last
128
+ step = ((dend - start) / raw['datapoints'].size.to_f).ceil
129
+ output[target] = { 'target' => target, 'data' => data, 'start' => start, 'end' => dend, 'step' => step }
130
+ end
131
+ output
132
+ end
133
+ rescue OpenURI::HTTPError
134
+ unknown 'Failed to connect to graphite server'
135
+ rescue NoMethodError
136
+ unknown 'No data for time period and/or target'
137
+ rescue Errno::ECONNREFUSED
138
+ unknown 'Connection refused when connecting to graphite server'
139
+ rescue Errno::ECONNRESET
140
+ unknown 'Connection reset by peer when connecting to graphite server'
141
+ rescue EOFError
142
+ unknown 'End of file error when reading from graphite server'
143
+ rescue => e
144
+ unknown "An unknown error occured: #{e.inspect}"
145
+ end
146
+ end
147
+ end
148
+
149
+ # type:: :warning or :critical
150
+ # Return alert if required
151
+ def check(type)
152
+ if config[type]
153
+ send(type, "#{@value['target']} has passed #{type} threshold (#{@data.last})") if below?(type) || above?(type)
154
+ end
155
+ end
156
+
157
+ # Check if value is below defined threshold
158
+ def below?(type)
159
+ config[:below] && @data.last < config[type]
160
+ end
161
+
162
+ # Check is value is above defined threshold
163
+ def above?(type)
164
+ (!config[:below]) && (@data.last > config[type]) && (!decreased?)
165
+ end
166
+
167
+ # Check if values have decreased within interval if given
168
+ def decreased?
169
+ if config[:reset_on_decrease]
170
+ slice = @data.slice(@data.size - config[:reset_on_decrease], @data.size)
171
+ val = slice.shift until slice.empty? || val.to_f > slice.first
172
+ !slice.empty?
173
+ else
174
+ false
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,97 @@
1
+ #! /usr/bin/env ruby
2
+ #
3
+ # check-graphite-hosts
4
+ #
5
+ # DESCRIPTION:
6
+ # This plugin checks the number of hosts within graphite that are sending
7
+ # data, and alerts if it is below a given threshold
8
+ #
9
+ # OUTPUT:
10
+ # plain text
11
+ #
12
+ # PLATFORMS:
13
+ # Linux
14
+ #
15
+ # DEPENDENCIES:
16
+ # gem: sensu-plugin
17
+ #
18
+ # USAGE:
19
+ # #YELLOW
20
+ #
21
+ # NOTES:
22
+ #
23
+ # LICENSE:
24
+ # Copyright 2014 Sonian, Inc. and contributors. <support@sensuapp.org>
25
+ # Released under the same terms as Sensu (the MIT license); see LICENSE
26
+ # for details.
27
+ #
28
+
29
+ require 'sensu-plugin/check/cli'
30
+ require 'json'
31
+ require 'open-uri'
32
+ require 'openssl'
33
+
34
+ require 'sensu-plugins-graphite/graphite_proxy/options'
35
+ require 'sensu-plugins-graphite/graphite_proxy/proxy'
36
+
37
+ class CheckGraphiteHosts < Sensu::Plugin::Check::CLI
38
+ include SensuPluginsGraphite::GraphiteProxy::Options
39
+
40
+ # Run checks
41
+ def run
42
+ if config[:help]
43
+ puts opt_parser if config[:help]
44
+ exit
45
+ end
46
+
47
+ proxy = SensuPluginsGraphite::GraphiteProxy::Proxy.new(config)
48
+ begin
49
+ results = proxy.retrieve_data!
50
+
51
+ check(:critical, results) || check(:warning, results)
52
+ ok("#{name} value (#{hosts_with_data(results)}) okay")
53
+ rescue SensuPluginsGraphite::GraphiteProxy::ProxyError => e
54
+ unknown e.message
55
+ end
56
+ end
57
+
58
+ # name used in responses
59
+ def name
60
+ base = config[:name]
61
+ @formatted ? "#{base} (#{@formatted})" : base
62
+ end
63
+
64
+ # return the number of hosts with data in the given set of results
65
+ def hosts_with_data(resultset)
66
+ resultset.count { |_host, values| !values['data'].empty? }
67
+ end
68
+
69
+ # type:: :warning or :critical
70
+ # Return alert if required
71
+ def check(type, results)
72
+ # #YELLOW
73
+ num_hosts = hosts_with_data(results)
74
+ return unless config[type] && threshold_crossed?(type, num_hosts)
75
+
76
+ msg = hosts_threshold_message(config[:target], num_hosts, type)
77
+ send(type, msg)
78
+ end
79
+
80
+ def threshold_crossed?(type, num_hosts)
81
+ below?(type, num_hosts) || above?(type, num_hosts)
82
+ end
83
+
84
+ def hosts_threshold_message(target, hosts, type)
85
+ "Number of hosts sending #{target} (#{hosts}) has passed #{type} threshold (#{config[type]})"
86
+ end
87
+
88
+ # Check if value is below defined threshold
89
+ def below?(type, val)
90
+ config[:below] && val < config[type]
91
+ end
92
+
93
+ # Check is value is above defined threshold
94
+ def above?(type, val)
95
+ (!config[:below]) && (val > config[type])
96
+ end
97
+ end
@@ -0,0 +1,223 @@
1
+ #! /usr/bin/env ruby
2
+ #
3
+ # check-replication
4
+ #
5
+ # DESCRIPTION:
6
+ # Check to ensure data gets posted and is retrievable by graphite.
7
+ # We post to each server in config[:relays] then sleep config[:sleep]
8
+ # seconds then check each of config[:graphites] to see if the data made it
9
+ # to each one. OK if all servers have the data we expected, WARN if
10
+ # config[:warning] or fewer have it. CRITICAL if config[:critical]
11
+ # or fewer have it. config[:check_id] allows you to have many of these
12
+ # checks running in different places without any conflicts. Customize it
13
+ # if you are going to run this check from multiple servers. Otherwise
14
+ # it defaults to default. (can be a descriptive string, used as a graphite key)
15
+ #
16
+ # This check is most useful when you have a cluster of carbon-relays configured
17
+ # with REPLICATION_FACTOR > 1 and more than one graphite server those
18
+ # carbon-relays are configured to post to. This check ensures that replication
19
+ # is actually happening in a timely manner.
20
+
21
+ # How it works: We generate a large random number for each of these servers
22
+ # Then we post that number to each server via a key in the form of:
23
+ # checks.graphite.check_id.replication.your_graphite_server.ip It's safe
24
+ # to throw this data away quickly. A day retention ought to be more
25
+ # than enough for anybody.
26
+ #
27
+ # OUTPUT:
28
+ # plain text
29
+ #
30
+ # PLATFORMS:
31
+ # Linux
32
+ #
33
+ # DEPENDENCIES:
34
+ # gem: sensu-plugin
35
+ # gem: rest-client
36
+ # gem: ipaddress
37
+ #
38
+ # USAGE:
39
+ # #YELLOW
40
+ #
41
+ # LICENSE:
42
+ # AJ Bourg <aj@ajbourg.com>
43
+ # Released under the same terms as Sensu (the MIT license); see LICENSE
44
+ # for details.
45
+ #
46
+
47
+ require 'sensu-plugin/check/cli'
48
+ require 'timeout'
49
+ require 'socket'
50
+ require 'rest-client'
51
+ require 'json'
52
+ require 'resolv'
53
+ require 'ipaddress'
54
+
55
+ class CheckGraphiteReplication < Sensu::Plugin::Check::CLI
56
+ option :relays,
57
+ short: '-r RELAYS',
58
+ long: '--relays RELAYS',
59
+ description: 'Comma separated list of carbon relay servers to post to.',
60
+ required: true
61
+ option :servers,
62
+ short: '-g SERVERS',
63
+ long: '--graphite SERVERS',
64
+ description: 'Comma separated list of all graphite servers to check.',
65
+ required: true
66
+ option :sleep,
67
+ short: '-s SECONDS',
68
+ long: '--sleep SECONDS',
69
+ description: 'Time to sleep between submitting and checking for value.',
70
+ default: 30,
71
+ proc: proc(&:to_i)
72
+ option :timeout,
73
+ short: '-t TIMEOUT',
74
+ long: '--timeout TIMEOUT',
75
+ description: 'Timeout limit for posting to the relay.',
76
+ default: 5,
77
+ proc: proc(&:to_i)
78
+ option :port,
79
+ short: '-p PORT',
80
+ long: '--port PORT',
81
+ description: 'Port to post to carbon-relay on.',
82
+ default: 2003,
83
+ proc: proc(&:to_i)
84
+ option :critical,
85
+ short: '-c COUNT',
86
+ long: '--critical COUNT',
87
+ description: 'Number of servers missing our test data to be critical.',
88
+ default: 2,
89
+ proc: proc(&:to_i)
90
+ option :warning,
91
+ short: '-w COUNT',
92
+ long: '--warning COUNT',
93
+ description: 'Number of servers missing our test data to be warning.',
94
+ default: 1,
95
+ proc: proc(&:to_i)
96
+ option :check_id,
97
+ short: '-i ID',
98
+ long: '--check-id ID',
99
+ description: 'Check ID to identify this check.',
100
+ default: 'default'
101
+ option :verbose,
102
+ short: '-v',
103
+ long: '--verbose',
104
+ description: 'Verbose.',
105
+ default: false,
106
+ boolean: true
107
+
108
+ def run
109
+ messages = []
110
+ servers = config[:servers].split(',')
111
+ relay_ips = find_relay_ips(config[:relays].split(','))
112
+
113
+ check_id = graphite_key(config[:check_id])
114
+
115
+ relay_ips.each do |server_name, ips|
116
+ ips.each do |ip|
117
+ messages << post_message(server_name, ip, check_id)
118
+ end
119
+ end
120
+
121
+ puts "Sleeping for #{config[:sleep]}." if config[:verbose]
122
+ sleep(config[:sleep])
123
+
124
+ fail_count = 0
125
+ # on every server, check to see if all our data replicated
126
+ servers.each do |server|
127
+ messages.each_with_index do |c|
128
+ unless check_for_message(server, c['key'], c['value'])
129
+ puts "#{c['relay']} (#{c['ip']}) didn't post to #{server}"
130
+ fail_count += 1
131
+ end
132
+ end
133
+ end
134
+
135
+ if fail_count >= config[:critical]
136
+ critical "Missing data points. #{fail_count} lookups failed."
137
+ elsif fail_count >= config[:warning]
138
+ warning "Missing data points. #{fail_count} lookups failed."
139
+ end
140
+
141
+ success_count = (messages.length * servers.length) - fail_count
142
+ ok "#{fail_count} failed checks. #{success_count} successful checks."
143
+ end
144
+
145
+ def find_relay_ips(relays)
146
+ # we may have gotten an IPAddress or a DNS hostname or a mix, so let's try
147
+
148
+ relay_ips = {}
149
+
150
+ time_out('resolving dns') do
151
+ relays.each do |r|
152
+ if IPAddress.valid? r
153
+ relay_ips[r] = [r]
154
+ else
155
+ relay_ips[r] = Resolv.getaddresses(r)
156
+ end
157
+ end
158
+ end
159
+
160
+ relay_ips
161
+ end
162
+
163
+ def post_message(server_name, ip, check_id)
164
+ server_key = graphite_key(server_name)
165
+
166
+ number = rand(10_000)
167
+ time = Time.now.to_i
168
+
169
+ ip_key = graphite_key(ip)
170
+ key = "checks.graphite.#{check_id}.replication.#{server_key}.#{ip_key}"
171
+
172
+ time_out("posting data to #{ip}") do
173
+ t = TCPSocket.new(ip, config[:port])
174
+ t.puts("#{key} #{number} #{time}")
175
+ t.close
176
+ end
177
+
178
+ if config[:verbose]
179
+ puts "Posted #{key} to #{server_name} with #{number} on IP #{ip}."
180
+ end
181
+
182
+ { 'relay' => server_name, 'ip' => ip, 'key' => key, 'value' => number }
183
+ end
184
+
185
+ # checks to see if a value landed on a graphite server
186
+ def check_for_message(server, key, value)
187
+ url = "http://#{server}/render?format=json&target=#{key}&from=-10minutes"
188
+
189
+ puts "Checking URL #{url}" if config[:verbose]
190
+ graphite_data = nil
191
+
192
+ begin
193
+ time_out("querying graphite api on #{server}") do
194
+ graphite_data = RestClient.get url
195
+ graphite_data = JSON.parse(graphite_data)
196
+ end
197
+ rescue RestClient::Exception, JSON::ParserError => e
198
+ critical "Unexpected error getting data from #{server}: #{e}"
199
+ end
200
+
201
+ success = false
202
+
203
+ # we get all the data points for the last 10 minutes, so see if our value
204
+ # appeared in any of them
205
+ graphite_data[0]['datapoints'].each do |v|
206
+ success = true if v[0] == value
207
+ end
208
+
209
+ success
210
+ end
211
+
212
+ def graphite_key(key)
213
+ key.gsub(',', '_').gsub(' ', '_').gsub('.', '_').gsub('-', '_')
214
+ end
215
+
216
+ def time_out(activity, &block)
217
+ Timeout.timeout(config[:timeout]) do
218
+ yield block
219
+ end
220
+ rescue Timeout::Error
221
+ critical "Timed out while #{activity}"
222
+ end
223
+ end