sensu 0.9.9 → 0.9.10
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.
- data/CHANGELOG.md +16 -0
- data/lib/sensu/client.rb +34 -32
- data/lib/sensu/constants.rb +1 -1
- data/lib/sensu/extensions.rb +1 -1
- data/lib/sensu/extensions/handlers/debug.rb +1 -1
- data/lib/sensu/extensions/mutators/only_check_output.rb +1 -1
- data/lib/sensu/io.rb +4 -0
- data/lib/sensu/rabbitmq.rb +0 -3
- data/lib/sensu/server.rb +48 -24
- data/lib/sensu/settings.rb +63 -56
- data/sensu.gemspec +2 -0
- metadata +38 -22
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,19 @@
|
|
1
|
+
## 0.9.10 - 2013-01-30
|
2
|
+
|
3
|
+
### Features
|
4
|
+
|
5
|
+
Handlers can be subdued like checks, suppression windows.
|
6
|
+
|
7
|
+
### Non-backwards compatible changes
|
8
|
+
|
9
|
+
Extensions have access to settings.
|
10
|
+
|
11
|
+
### Other
|
12
|
+
|
13
|
+
Client queue names are now determined by the broker (RabbitMQ).
|
14
|
+
|
15
|
+
Improved zombie reaping.
|
16
|
+
|
1
17
|
## 0.9.9 - 2013-01-14
|
2
18
|
|
3
19
|
### Features
|
data/lib/sensu/client.rb
CHANGED
@@ -134,42 +134,44 @@ module Sensu
|
|
134
134
|
|
135
135
|
def setup_subscriptions
|
136
136
|
@logger.debug('subscribing to client subscriptions')
|
137
|
-
@
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
:
|
144
|
-
|
145
|
-
|
146
|
-
@check_request_queue.bind(@amq.fanout(exchange_name))
|
147
|
-
end
|
148
|
-
@check_request_queue.subscribe do |payload|
|
149
|
-
begin
|
150
|
-
check = JSON.parse(payload, :symbolize_names => true)
|
151
|
-
@logger.info('received check request', {
|
152
|
-
:check => check
|
137
|
+
@check_request_queue = @amq.queue('', :auto_delete => true) do |queue|
|
138
|
+
@settings[:client][:subscriptions].uniq.each do |exchange_name|
|
139
|
+
@logger.debug('binding queue to exchange', {
|
140
|
+
:queue => {
|
141
|
+
:name => queue.name
|
142
|
+
},
|
143
|
+
:exchange => {
|
144
|
+
:name => exchange_name
|
145
|
+
}
|
153
146
|
})
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
147
|
+
queue.bind(@amq.fanout(exchange_name))
|
148
|
+
end
|
149
|
+
queue.subscribe do |payload|
|
150
|
+
begin
|
151
|
+
check = JSON.parse(payload, :symbolize_names => true)
|
152
|
+
@logger.info('received check request', {
|
159
153
|
:check => check
|
160
154
|
})
|
161
|
-
check[:
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
155
|
+
if @settings.check_exists?(check[:name])
|
156
|
+
check.merge!(@settings[:checks][check[:name]])
|
157
|
+
execute_check(check)
|
158
|
+
elsif @safe_mode
|
159
|
+
@logger.warn('check is not defined', {
|
160
|
+
:check => check
|
161
|
+
})
|
162
|
+
check[:output] = 'Check is not defined (safe mode)'
|
163
|
+
check[:status] = 3
|
164
|
+
check[:handle] = false
|
165
|
+
publish_result(check)
|
166
|
+
else
|
167
|
+
execute_check(check)
|
168
|
+
end
|
169
|
+
rescue JSON::ParserError => error
|
170
|
+
@logger.warn('check request payload must be valid json', {
|
171
|
+
:payload => payload,
|
172
|
+
:error => error.to_s
|
173
|
+
})
|
167
174
|
end
|
168
|
-
rescue JSON::ParserError => error
|
169
|
-
@logger.warn('check request payload must be valid json', {
|
170
|
-
:payload => payload,
|
171
|
-
:error => error.to_s
|
172
|
-
})
|
173
175
|
end
|
174
176
|
end
|
175
177
|
end
|
data/lib/sensu/constants.rb
CHANGED
data/lib/sensu/extensions.rb
CHANGED
data/lib/sensu/io.rb
CHANGED
data/lib/sensu/rabbitmq.rb
CHANGED
data/lib/sensu/server.rb
CHANGED
@@ -94,12 +94,20 @@ module Sensu
|
|
94
94
|
end
|
95
95
|
end
|
96
96
|
|
97
|
-
def
|
97
|
+
def action_subdued?(check, handler=nil)
|
98
98
|
subdue = false
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
99
|
+
subdue_at = handler ? 'handler' : 'publisher'
|
100
|
+
conditions = Array.new
|
101
|
+
if check[:subdue]
|
102
|
+
conditions.push(check[:subdue])
|
103
|
+
end
|
104
|
+
if handler && handler[:subdue]
|
105
|
+
conditions.push(handler[:subdue])
|
106
|
+
end
|
107
|
+
conditions.each do |condition|
|
108
|
+
if condition.has_key?(:begin) && condition.has_key?(:end)
|
109
|
+
begin_time = Time.parse(condition[:begin])
|
110
|
+
end_time = Time.parse(condition[:end])
|
103
111
|
if end_time < begin_time
|
104
112
|
if Time.now < end_time
|
105
113
|
begin_time = Time.parse('12:00:00 AM')
|
@@ -111,19 +119,26 @@ module Sensu
|
|
111
119
|
subdue = true
|
112
120
|
end
|
113
121
|
end
|
114
|
-
if
|
115
|
-
days =
|
122
|
+
if condition.has_key?(:days)
|
123
|
+
days = condition[:days].map(&:downcase)
|
116
124
|
if days.include?(Time.now.strftime('%A').downcase)
|
117
125
|
subdue = true
|
118
126
|
end
|
119
127
|
end
|
120
|
-
if subdue &&
|
121
|
-
subdue =
|
128
|
+
if subdue && condition.has_key?(:exceptions)
|
129
|
+
subdue = condition[:exceptions].none? do |exception|
|
122
130
|
Time.now >= Time.parse(exception[:begin]) && Time.now <= Time.parse(exception[:end])
|
123
131
|
end
|
124
132
|
end
|
133
|
+
if subdue
|
134
|
+
if subdue_at == (condition[:at] || 'handler')
|
135
|
+
break
|
136
|
+
else
|
137
|
+
subdue = false
|
138
|
+
end
|
139
|
+
end
|
125
140
|
end
|
126
|
-
subdue
|
141
|
+
subdue
|
127
142
|
end
|
128
143
|
|
129
144
|
def filter_attributes_match?(hash_one, hash_two)
|
@@ -202,8 +217,8 @@ module Sensu
|
|
202
217
|
})
|
203
218
|
next
|
204
219
|
end
|
205
|
-
if
|
206
|
-
@logger.info('
|
220
|
+
if action_subdued?(event[:check], handler)
|
221
|
+
@logger.info('action is subdued', {
|
207
222
|
:event => event,
|
208
223
|
:handler => handler
|
209
224
|
})
|
@@ -266,31 +281,36 @@ module Sensu
|
|
266
281
|
end
|
267
282
|
|
268
283
|
def mutate_event_data(mutator_name, event, &block)
|
269
|
-
on_error = Proc.new do |error|
|
270
|
-
@logger.error('mutator error', {
|
271
|
-
:event => event,
|
272
|
-
:mutator => mutator,
|
273
|
-
:error => error.to_s
|
274
|
-
})
|
275
|
-
end
|
276
284
|
case
|
277
285
|
when mutator_name.nil?
|
278
286
|
block.call(event.to_json)
|
279
287
|
when @settings.mutator_exists?(mutator_name)
|
280
288
|
mutator = @settings[:mutators][mutator_name]
|
289
|
+
on_error = Proc.new do |error|
|
290
|
+
@logger.error('mutator error', {
|
291
|
+
:event => event,
|
292
|
+
:mutator => mutator,
|
293
|
+
:error => error.to_s
|
294
|
+
})
|
295
|
+
end
|
281
296
|
execute_command(mutator[:command], event.to_json, on_error) do |output, status|
|
282
297
|
if status == 0
|
283
298
|
block.call(output)
|
284
299
|
else
|
285
|
-
on_error.call('non-zero exit status (' + status + '): ' + output)
|
300
|
+
on_error.call('non-zero exit status (' + status.to_s + '): ' + output)
|
286
301
|
end
|
287
302
|
end
|
288
303
|
when @extensions.mutator_exists?(mutator_name)
|
289
|
-
@extensions[:mutators][mutator_name]
|
304
|
+
extension = @extensions[:mutators][mutator_name]
|
305
|
+
extension.run(event, @settings.to_hash) do |output, status|
|
290
306
|
if status == 0
|
291
307
|
block.call(output)
|
292
308
|
else
|
293
|
-
|
309
|
+
@logger.error('mutator error', {
|
310
|
+
:event => event,
|
311
|
+
:extension => extension,
|
312
|
+
:error => 'non-zero exit status (' + status.to_s + '): ' + output
|
313
|
+
})
|
294
314
|
end
|
295
315
|
end
|
296
316
|
else
|
@@ -365,7 +385,7 @@ module Sensu
|
|
365
385
|
end
|
366
386
|
@handlers_in_progress_count -= 1
|
367
387
|
when 'extension'
|
368
|
-
handler.run(event_data) do |output, status|
|
388
|
+
handler.run(event_data, @settings.to_hash) do |output, status|
|
369
389
|
output.split(/\n+/).each do |line|
|
370
390
|
@logger.info(line)
|
371
391
|
end
|
@@ -536,8 +556,12 @@ module Sensu
|
|
536
556
|
@master_timers << EM::Timer.new(scheduling_delay) do
|
537
557
|
interval = testing? ? 0.5 : check[:interval]
|
538
558
|
@master_timers << EM::PeriodicTimer.new(interval) do
|
539
|
-
unless
|
559
|
+
unless action_subdued?(check)
|
540
560
|
publish_check_request(check)
|
561
|
+
else
|
562
|
+
@logger.info('action is subdued', {
|
563
|
+
:check => check
|
564
|
+
})
|
541
565
|
end
|
542
566
|
end
|
543
567
|
end
|
data/lib/sensu/settings.rb
CHANGED
@@ -20,11 +20,15 @@ module Sensu
|
|
20
20
|
@indifferent_access = true
|
21
21
|
end
|
22
22
|
|
23
|
-
def
|
23
|
+
def to_hash
|
24
24
|
unless @indifferent_access
|
25
25
|
indifferent_access!
|
26
26
|
end
|
27
|
-
@settings
|
27
|
+
@settings
|
28
|
+
end
|
29
|
+
|
30
|
+
def [](key)
|
31
|
+
to_hash[key]
|
28
32
|
end
|
29
33
|
|
30
34
|
SETTINGS_CATEGORIES.each do |category|
|
@@ -137,6 +141,55 @@ module Sensu
|
|
137
141
|
exit 2
|
138
142
|
end
|
139
143
|
|
144
|
+
def validate_subdue(condition, details={})
|
145
|
+
type = details.has_key?(:check) ? 'check' : 'handler'
|
146
|
+
unless condition.is_a?(Hash)
|
147
|
+
invalid(type + ' subdue must be a hash', details)
|
148
|
+
end
|
149
|
+
if condition.has_key?(:at)
|
150
|
+
unless %w[handler publisher].include?(condition[:at])
|
151
|
+
invalid(type + ' subdue at must be either handler or publisher', details)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
if condition.has_key?(:begin) || condition.has_key?(:end)
|
155
|
+
begin
|
156
|
+
Time.parse(condition[:begin])
|
157
|
+
Time.parse(condition[:end])
|
158
|
+
rescue
|
159
|
+
invalid(type + ' subdue begin & end times must be valid', details)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
if condition.has_key?(:days)
|
163
|
+
unless condition[:days].is_a?(Array)
|
164
|
+
invalid(type + ' subdue days must be an array', details)
|
165
|
+
end
|
166
|
+
condition[:days].each do |day|
|
167
|
+
days = %w[sunday monday tuesday wednesday thursday friday saturday]
|
168
|
+
unless day.is_a?(String) && days.include?(day.downcase)
|
169
|
+
invalid(type + ' subdue days must be valid days of the week', details)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
if condition.has_key?(:exceptions)
|
174
|
+
unless condition[:exceptions].is_a?(Array)
|
175
|
+
invalid(type + ' subdue exceptions must be an array', details)
|
176
|
+
end
|
177
|
+
condition[:exceptions].each do |exception|
|
178
|
+
unless exception.is_a?(Hash)
|
179
|
+
invalid(type + ' subdue exceptions must each be a hash', details)
|
180
|
+
end
|
181
|
+
if exception.has_key?(:begin) || exception.has_key?(:end)
|
182
|
+
begin
|
183
|
+
Time.parse(exception[:begin])
|
184
|
+
Time.parse(exception[:end])
|
185
|
+
rescue
|
186
|
+
invalid(type + ' subdue exception begin & end times must be valid', details)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
140
193
|
def validate_checks
|
141
194
|
unless @settings[:checks].is_a?(Hash)
|
142
195
|
invalid('checks must be a hash')
|
@@ -209,60 +262,9 @@ module Sensu
|
|
209
262
|
end
|
210
263
|
end
|
211
264
|
if check.has_key?(:subdue)
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
})
|
216
|
-
end
|
217
|
-
if check[:subdue].has_key?(:begin) || check[:subdue].has_key?(:end)
|
218
|
-
begin
|
219
|
-
Time.parse(check[:subdue][:begin])
|
220
|
-
Time.parse(check[:subdue][:end])
|
221
|
-
rescue
|
222
|
-
invalid('check subdue begin & end times must be valid', {
|
223
|
-
:check => check
|
224
|
-
})
|
225
|
-
end
|
226
|
-
end
|
227
|
-
if check[:subdue].has_key?(:days)
|
228
|
-
unless check[:subdue][:days].is_a?(Array)
|
229
|
-
invalid('check subdue days must be an array', {
|
230
|
-
:check => check
|
231
|
-
})
|
232
|
-
end
|
233
|
-
check[:subdue][:days].each do |day|
|
234
|
-
days = %w[sunday monday tuesday wednesday thursday friday saturday]
|
235
|
-
unless day.is_a?(String) && days.include?(day.downcase)
|
236
|
-
invalid('check subdue days must be valid days of the week', {
|
237
|
-
:check => check
|
238
|
-
})
|
239
|
-
end
|
240
|
-
end
|
241
|
-
end
|
242
|
-
if check[:subdue].has_key?(:exceptions)
|
243
|
-
unless check[:subdue][:exceptions].is_a?(Array)
|
244
|
-
invalid('check subdue exceptions must be an array', {
|
245
|
-
:check => check
|
246
|
-
})
|
247
|
-
end
|
248
|
-
check[:subdue][:exceptions].each do |exception|
|
249
|
-
unless exception.is_a?(Hash)
|
250
|
-
invalid('check subdue exceptions must each be a hash', {
|
251
|
-
:check => check
|
252
|
-
})
|
253
|
-
end
|
254
|
-
if exception.has_key?(:begin) || exception.has_key?(:end)
|
255
|
-
begin
|
256
|
-
Time.parse(exception[:begin])
|
257
|
-
Time.parse(exception[:end])
|
258
|
-
rescue
|
259
|
-
invalid('check subdue exception begin & end times must be valid', {
|
260
|
-
:check => check
|
261
|
-
})
|
262
|
-
end
|
263
|
-
end
|
264
|
-
end
|
265
|
-
end
|
265
|
+
validate_subdue(check[:subdue], {
|
266
|
+
:check => check
|
267
|
+
})
|
266
268
|
end
|
267
269
|
end
|
268
270
|
end
|
@@ -459,6 +461,11 @@ module Sensu
|
|
459
461
|
end
|
460
462
|
end
|
461
463
|
end
|
464
|
+
if handler.has_key?(:subdue)
|
465
|
+
validate_subdue(handler[:subdue], {
|
466
|
+
:handler => handler
|
467
|
+
})
|
468
|
+
end
|
462
469
|
end
|
463
470
|
end
|
464
471
|
end
|
data/sensu.gemspec
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
1
2
|
require File.join(File.dirname(__FILE__), 'lib', 'sensu', 'constants')
|
2
3
|
|
3
4
|
Gem::Specification.new do |s|
|
@@ -13,6 +14,7 @@ Gem::Specification.new do |s|
|
|
13
14
|
s.has_rdoc = false
|
14
15
|
|
15
16
|
s.add_dependency('eventmachine', '1.0.0')
|
17
|
+
s.add_dependency('amq-client', '0.9.11')
|
16
18
|
s.add_dependency('amqp', '0.9.8')
|
17
19
|
s.add_dependency('json')
|
18
20
|
s.add_dependency('cabin', '0.4.4')
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sensu
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 47
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 9
|
9
|
-
-
|
10
|
-
version: 0.9.
|
9
|
+
- 10
|
10
|
+
version: 0.9.10
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Sean Porter
|
@@ -16,7 +16,7 @@ autorequire:
|
|
16
16
|
bindir: bin
|
17
17
|
cert_chain: []
|
18
18
|
|
19
|
-
date: 2013-01-
|
19
|
+
date: 2013-01-30 00:00:00 -08:00
|
20
20
|
default_executable:
|
21
21
|
dependencies:
|
22
22
|
- !ruby/object:Gem::Dependency
|
@@ -36,9 +36,25 @@ dependencies:
|
|
36
36
|
type: :runtime
|
37
37
|
version_requirements: *id001
|
38
38
|
- !ruby/object:Gem::Dependency
|
39
|
-
name:
|
39
|
+
name: amq-client
|
40
40
|
prerelease: false
|
41
41
|
requirement: &id002 !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - "="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
hash: 45
|
47
|
+
segments:
|
48
|
+
- 0
|
49
|
+
- 9
|
50
|
+
- 11
|
51
|
+
version: 0.9.11
|
52
|
+
type: :runtime
|
53
|
+
version_requirements: *id002
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: amqp
|
56
|
+
prerelease: false
|
57
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
42
58
|
none: false
|
43
59
|
requirements:
|
44
60
|
- - "="
|
@@ -50,11 +66,11 @@ dependencies:
|
|
50
66
|
- 8
|
51
67
|
version: 0.9.8
|
52
68
|
type: :runtime
|
53
|
-
version_requirements: *
|
69
|
+
version_requirements: *id003
|
54
70
|
- !ruby/object:Gem::Dependency
|
55
71
|
name: json
|
56
72
|
prerelease: false
|
57
|
-
requirement: &
|
73
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
58
74
|
none: false
|
59
75
|
requirements:
|
60
76
|
- - ">="
|
@@ -64,11 +80,11 @@ dependencies:
|
|
64
80
|
- 0
|
65
81
|
version: "0"
|
66
82
|
type: :runtime
|
67
|
-
version_requirements: *
|
83
|
+
version_requirements: *id004
|
68
84
|
- !ruby/object:Gem::Dependency
|
69
85
|
name: cabin
|
70
86
|
prerelease: false
|
71
|
-
requirement: &
|
87
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
72
88
|
none: false
|
73
89
|
requirements:
|
74
90
|
- - "="
|
@@ -80,11 +96,11 @@ dependencies:
|
|
80
96
|
- 4
|
81
97
|
version: 0.4.4
|
82
98
|
type: :runtime
|
83
|
-
version_requirements: *
|
99
|
+
version_requirements: *id005
|
84
100
|
- !ruby/object:Gem::Dependency
|
85
101
|
name: em-redis-unified
|
86
102
|
prerelease: false
|
87
|
-
requirement: &
|
103
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
88
104
|
none: false
|
89
105
|
requirements:
|
90
106
|
- - "="
|
@@ -96,11 +112,11 @@ dependencies:
|
|
96
112
|
- 1
|
97
113
|
version: 0.4.1
|
98
114
|
type: :runtime
|
99
|
-
version_requirements: *
|
115
|
+
version_requirements: *id006
|
100
116
|
- !ruby/object:Gem::Dependency
|
101
117
|
name: thin
|
102
118
|
prerelease: false
|
103
|
-
requirement: &
|
119
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
104
120
|
none: false
|
105
121
|
requirements:
|
106
122
|
- - "="
|
@@ -112,11 +128,11 @@ dependencies:
|
|
112
128
|
- 0
|
113
129
|
version: 1.5.0
|
114
130
|
type: :runtime
|
115
|
-
version_requirements: *
|
131
|
+
version_requirements: *id007
|
116
132
|
- !ruby/object:Gem::Dependency
|
117
133
|
name: async_sinatra
|
118
134
|
prerelease: false
|
119
|
-
requirement: &
|
135
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
120
136
|
none: false
|
121
137
|
requirements:
|
122
138
|
- - "="
|
@@ -128,11 +144,11 @@ dependencies:
|
|
128
144
|
- 0
|
129
145
|
version: 1.0.0
|
130
146
|
type: :runtime
|
131
|
-
version_requirements: *
|
147
|
+
version_requirements: *id008
|
132
148
|
- !ruby/object:Gem::Dependency
|
133
149
|
name: rake
|
134
150
|
prerelease: false
|
135
|
-
requirement: &
|
151
|
+
requirement: &id009 !ruby/object:Gem::Requirement
|
136
152
|
none: false
|
137
153
|
requirements:
|
138
154
|
- - ">="
|
@@ -142,11 +158,11 @@ dependencies:
|
|
142
158
|
- 0
|
143
159
|
version: "0"
|
144
160
|
type: :development
|
145
|
-
version_requirements: *
|
161
|
+
version_requirements: *id009
|
146
162
|
- !ruby/object:Gem::Dependency
|
147
163
|
name: rspec
|
148
164
|
prerelease: false
|
149
|
-
requirement: &
|
165
|
+
requirement: &id010 !ruby/object:Gem::Requirement
|
150
166
|
none: false
|
151
167
|
requirements:
|
152
168
|
- - ">="
|
@@ -156,11 +172,11 @@ dependencies:
|
|
156
172
|
- 0
|
157
173
|
version: "0"
|
158
174
|
type: :development
|
159
|
-
version_requirements: *
|
175
|
+
version_requirements: *id010
|
160
176
|
- !ruby/object:Gem::Dependency
|
161
177
|
name: em-http-request
|
162
178
|
prerelease: false
|
163
|
-
requirement: &
|
179
|
+
requirement: &id011 !ruby/object:Gem::Requirement
|
164
180
|
none: false
|
165
181
|
requirements:
|
166
182
|
- - ">="
|
@@ -170,7 +186,7 @@ dependencies:
|
|
170
186
|
- 0
|
171
187
|
version: "0"
|
172
188
|
type: :development
|
173
|
-
version_requirements: *
|
189
|
+
version_requirements: *id011
|
174
190
|
description: A monitoring framework that aims to be simple, malleable, and scalable. Uses the publish/subscribe model.
|
175
191
|
email:
|
176
192
|
- portertech@gmail.com
|