sensu-plugins-graphite-donotuse 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -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