arborist-snmp 0.1.0.pre20161005111600 → 0.4.0
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.
- checksums.yaml +4 -4
- data/lib/arborist/monitor/snmp.rb +91 -349
- data/lib/arborist/monitor/snmp/cpu.rb +140 -0
- data/lib/arborist/monitor/snmp/disk.rb +183 -0
- data/lib/arborist/monitor/snmp/memory.rb +180 -0
- data/lib/arborist/monitor/snmp/process.rb +123 -0
- data/lib/arborist/snmp.rb +35 -0
- metadata +27 -24
- checksums.yaml.gz.sig +0 -4
- data.tar.gz.sig +0 -0
- metadata.gz.sig +0 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 833fc064ac6b821016f02ec2b427c7861cc4ebd2
|
4
|
+
data.tar.gz: d14a89c993c13e874b9201388999bb77ac85c06f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: be00731e69eae712e71e9881abb20ba9d1393de4acad424b8b2c7225b95d351e211a9c20551556f76fde34e59a551ff2716f9f220a247afa35b529b688301ca5
|
7
|
+
data.tar.gz: bd6b4d1292adec637932fc8edccd62fe1db5fbaea2350995ab9c504e2eb002e36470ca7280742fb9b4eb231523fc8744bcb0d5135017bdf4bae8e23c8d5757bd
|
@@ -1,402 +1,144 @@
|
|
1
1
|
# -*- ruby -*-
|
2
2
|
# vim: set noet nosta sw=4 ts=4 :
|
3
3
|
#encoding: utf-8
|
4
|
-
|
4
|
+
|
5
|
+
require 'arborist/monitor' unless defined?( Arborist::Monitor )
|
6
|
+
require 'net-snmp2'
|
7
|
+
|
5
8
|
# SNMP checks for Arborist. Requires an SNMP agent to be installed
|
6
|
-
# on target machine, and the various "pieces" enabled
|
9
|
+
# on target machine, and the various "pieces" enabled for your platform.
|
7
10
|
#
|
8
11
|
# For example, for disk monitoring with Net-SNMP, you'll want to set
|
9
12
|
# 'includeAllDisks' in the snmpd.conf. bsnmpd on FreeBSD benefits from
|
10
13
|
# the 'bsnmp-ucd' package. Etc.
|
11
14
|
#
|
15
|
+
module Arborist::Monitor::SNMP
|
16
|
+
using Arborist::TimeRefinements
|
17
|
+
extend Configurability, Loggability
|
12
18
|
|
13
|
-
|
14
|
-
|
15
|
-
require 'snmp'
|
16
|
-
|
17
|
-
using Arborist::TimeRefinements
|
18
|
-
|
19
|
-
# SNMP specific monitors and monitor logic.
|
20
|
-
#
|
21
|
-
class Arborist::Monitor::SNMP
|
22
|
-
extend Loggability
|
23
|
-
log_to :arborist
|
24
|
-
|
25
|
-
# The version of this library.
|
26
|
-
VERSION = '0.1.0'
|
19
|
+
# Loggability API
|
20
|
+
log_to :arborist_snmp
|
27
21
|
|
28
|
-
#
|
29
|
-
|
22
|
+
# Always request the node addresses and any config.
|
23
|
+
USED_PROPERTIES = [ :addresses, :config ].freeze
|
30
24
|
|
31
25
|
# The OID that returns the system environment.
|
32
26
|
IDENTIFICATION_OID = '1.3.6.1.2.1.1.1.0'
|
33
27
|
|
34
|
-
#
|
35
|
-
# this regular expression. This includes null/union mounts
|
36
|
-
# and NFS, currently.
|
37
|
-
STORAGE_IGNORE = %r{25.3.9.(?:2|14)$}
|
38
|
-
|
39
|
-
# The OID that matches a local windows hard disk. Anything else
|
40
|
-
# is a remote (SMB) mount.
|
41
|
-
WINDOWS_DEVICE = '1.3.6.1.2.1.25.2.1.4'
|
42
|
-
|
43
|
-
# OIDS required to pull disk information from net-snmp.
|
28
|
+
# Global defaults for instances of this monitor
|
44
29
|
#
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
'
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
'1.3.6.1.2.1.25.2.3.1.2', # types
|
55
|
-
'1.3.6.1.2.1.25.2.3.1.3', # paths
|
56
|
-
'1.3.6.1.2.1.25.2.3.1.5', # totalsize
|
57
|
-
'1.3.6.1.2.1.25.2.3.1.6' # usedsize
|
58
|
-
]
|
59
|
-
|
60
|
-
# OIDS for discovering memory usage.
|
61
|
-
#
|
62
|
-
MEMORY = {
|
63
|
-
swap_total: '1.3.6.1.4.1.2021.4.3.0',
|
64
|
-
swap_avail: '1.3.6.1.4.1.2021.4.4.0',
|
65
|
-
mem_avail: '1.3.6.1.4.1.2021.4.6.0'
|
66
|
-
}
|
67
|
-
|
68
|
-
# OIDS for discovering system load.
|
69
|
-
#
|
70
|
-
LOAD = {
|
71
|
-
five_min: '1.3.6.1.4.1.2021.10.1.3.2'
|
72
|
-
}
|
73
|
-
|
74
|
-
# OIDS for discovering running processes.
|
75
|
-
#
|
76
|
-
PROCESS = {
|
77
|
-
list: '1.3.6.1.2.1.25.4.2.1.4',
|
78
|
-
args: '1.3.6.1.2.1.25.4.2.1.5'
|
79
|
-
}
|
80
|
-
|
81
|
-
|
82
|
-
# Defaults for instances of this monitor
|
83
|
-
#
|
84
|
-
DEFAULT_OPTIONS = {
|
85
|
-
timeout: 2,
|
86
|
-
retries: 1,
|
87
|
-
community: 'public',
|
88
|
-
port: 161,
|
89
|
-
storage_error_at: 95, # in percent full
|
90
|
-
load_error_at: 7,
|
91
|
-
swap_error_at: 25, # in percent remaining
|
92
|
-
mem_error_at: 51200, # in kilobytes
|
93
|
-
processes: [] # list of procs to match
|
94
|
-
}
|
95
|
-
|
96
|
-
|
97
|
-
### This monitor is complex enough to require creating an instance from the caller.
|
98
|
-
### Provide a friendlier error message the class was provided to exec() directly.
|
99
|
-
###
|
100
|
-
def self::run( nodes )
|
101
|
-
self.log.error "Please use %s via an instance." % [ self.name ]
|
102
|
-
return {}
|
30
|
+
configurability( 'arborist.snmp' ) do
|
31
|
+
setting :timeout, default: 2
|
32
|
+
setting :retries, default: 1
|
33
|
+
setting :community, default: 'public'
|
34
|
+
setting :version, default: '2c'
|
35
|
+
setting :port, default: 161
|
36
|
+
|
37
|
+
# How many hosts to check simultaneously
|
38
|
+
setting :batchsize, default: 25
|
103
39
|
end
|
104
40
|
|
105
|
-
|
106
|
-
|
107
|
-
###
|
108
|
-
def initialize( options=DEFAULT_OPTIONS )
|
109
|
-
options = DEFAULT_OPTIONS.merge( options || {} )
|
110
|
-
|
111
|
-
options.each do |name, value|
|
112
|
-
self.public_send( "#{name}=", value )
|
113
|
-
end
|
114
|
-
end
|
41
|
+
# Indicate to FFI that we're using threads.
|
42
|
+
Net::SNMP.thread_safe = true
|
115
43
|
|
116
44
|
|
117
|
-
# The
|
118
|
-
|
119
|
-
attr_reader :mode
|
45
|
+
# The system type, as advertised.
|
46
|
+
attr_reader :system
|
120
47
|
|
121
|
-
#
|
48
|
+
# The mapping of addresses back to node identifiers.
|
122
49
|
attr_reader :identifiers
|
123
50
|
|
124
|
-
# The results
|
51
|
+
# The results hash that is sent back to the manager.
|
125
52
|
attr_reader :results
|
126
53
|
|
127
|
-
# A timeout in seconds if the SNMP server isn't responding.
|
128
|
-
attr_accessor :timeout
|
129
|
-
|
130
|
-
# Retry with the timeout this many times. Defaults to 1.
|
131
|
-
attr_accessor :retries
|
132
|
-
|
133
|
-
# The SNMP UDP port, if running on non default.
|
134
|
-
attr_accessor :port
|
135
|
-
|
136
|
-
# The community string to connect with.
|
137
|
-
attr_accessor :community
|
138
|
-
|
139
|
-
# Set an error if mount points are above this percentage.
|
140
|
-
attr_accessor :storage_error_at
|
141
54
|
|
142
|
-
|
143
|
-
attr_accessor :load_error_at
|
144
|
-
|
145
|
-
# Set an error if used swap exceeds this percentage.
|
146
|
-
attr_accessor :swap_error_at
|
147
|
-
|
148
|
-
# Set an error if memory used is below this many kilobytes.
|
149
|
-
attr_accessor :mem_error_at
|
150
|
-
|
151
|
-
# Set an error if processes in this array aren't running.
|
152
|
-
attr_accessor :processes
|
153
|
-
|
154
|
-
|
155
|
-
### Set the SNMP mode, after validation.
|
156
|
-
###
|
157
|
-
def mode=( mode )
|
158
|
-
unless VALID_MODES.include?( mode.to_sym )
|
159
|
-
self.log.error "Unknown SNMP mode: %s" % [ mode ]
|
160
|
-
return nil
|
161
|
-
end
|
162
|
-
|
163
|
-
@mode = mode.to_sym
|
164
|
-
@results = {}
|
165
|
-
end
|
166
|
-
|
167
|
-
|
168
|
-
### Perform the monitoring checks.
|
55
|
+
### Connect to the SNMP daemon and yield.
|
169
56
|
###
|
170
57
|
def run( nodes )
|
171
|
-
self.log.debug "Got nodes to SNMP check: %p" % [ nodes ]
|
172
|
-
|
173
|
-
# Sanity check.
|
174
|
-
#
|
175
|
-
unless self.mode
|
176
|
-
self.log.error "You must set the 'mode' for the SNMP monitor. (%s)" % [ VALID_MODES.join( ', ' ) ]
|
177
|
-
return {}
|
178
|
-
end
|
179
58
|
|
180
|
-
# Create mapping of addresses back to node identifiers
|
59
|
+
# Create mapping of addresses back to node identifiers,
|
60
|
+
# and retain any custom (overrides) config per node.
|
181
61
|
#
|
182
|
-
@identifiers =
|
62
|
+
@identifiers = {}
|
63
|
+
@results = {}
|
64
|
+
nodes.each_pair do |(identifier, props)|
|
183
65
|
next unless props.key?( 'addresses' )
|
184
66
|
address = props[ 'addresses' ].first
|
185
|
-
|
67
|
+
self.identifiers[ address ] = [ identifier, props['config'] ]
|
186
68
|
end
|
187
69
|
|
188
70
|
# Perform the work!
|
189
71
|
#
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
72
|
+
mainstart = Time.now
|
73
|
+
threads = ThreadGroup.new
|
74
|
+
batchcount = nodes.size / Arborist::Monitor::SNMP.batchsize
|
75
|
+
self.log.debug "Starting SNMP run for %d nodes" % [ nodes.size ]
|
76
|
+
|
77
|
+
self.identifiers.keys.each_slice( Arborist::Monitor::SNMP.batchsize ).each_with_index do |slice, batch|
|
78
|
+
slicestart = Time.now
|
79
|
+
self.log.debug " %d hosts (batch %d of %d)" % [
|
80
|
+
slice.size,
|
81
|
+
batch + 1,
|
82
|
+
batchcount + 1
|
83
|
+
]
|
84
|
+
|
85
|
+
slice.each do |host|
|
86
|
+
thr = Thread.new do
|
87
|
+
config = self.identifiers[ host ].last || {}
|
88
|
+
opts = {
|
89
|
+
peername: host,
|
90
|
+
port: config[ 'port' ] || Arborist::Monitor::SNMP.port,
|
91
|
+
version: config[ 'version' ] || Arborist::Monitor::SNMP.version,
|
92
|
+
community: config[ 'community' ] || Arborist::Monitor::SNMP.community,
|
93
|
+
timeout: config[ 'timeout' ] || Arborist::Monitor::SNMP.timeout,
|
94
|
+
retries: config[ 'retries' ] || Arborist::Monitor::SNMP.retries
|
95
|
+
}
|
201
96
|
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
97
|
+
snmp = Net::SNMP::Session.open( opts )
|
98
|
+
begin
|
99
|
+
@system = snmp.get( IDENTIFICATION_OID ).varbinds.first.value
|
100
|
+
yield( host, snmp )
|
101
|
+
|
102
|
+
rescue Net::SNMP::TimeoutError, Net::SNMP::Error => err
|
103
|
+
self.log.error "%s: %s %s" % [ host, err.message, snmp.error_message ]
|
104
|
+
self.results[ host ] = {
|
105
|
+
error: "%s" % [ snmp.error_message ]
|
106
|
+
}
|
107
|
+
rescue => err
|
108
|
+
self.results[ host ] = {
|
109
|
+
error: "Uncaught exception. (%s: %s)" % [ err.class.name, err.message ]
|
110
|
+
}
|
111
|
+
ensure
|
112
|
+
snmp.close
|
216
113
|
end
|
217
|
-
rescue SNMP::RequestTimeout
|
218
|
-
self.results[ host ] = {
|
219
|
-
error: "Host is not responding to SNMP requests."
|
220
|
-
}
|
221
|
-
rescue StandardError => err
|
222
|
-
self.results[ host ] = {
|
223
|
-
error: "Network is not accessible. (%s: %s)" % [ err.class.name, err.message ]
|
224
|
-
}
|
225
114
|
end
|
115
|
+
|
116
|
+
threads.add( thr )
|
226
117
|
end
|
227
|
-
threads << thr
|
228
|
-
end
|
229
118
|
|
230
|
-
|
231
|
-
|
119
|
+
# Wait for thread completions
|
120
|
+
threads.list.map( &:join )
|
121
|
+
self.log.debug " finished after %0.1f seconds." % [ Time.now - slicestart ]
|
122
|
+
end
|
123
|
+
self.log.debug "Completed SNMP run for %d nodes after %0.1f seconds." % [ nodes.size, Time.now - mainstart ]
|
232
124
|
|
233
125
|
# Map everything back to identifier -> attribute(s), and send to the manager.
|
234
126
|
#
|
235
127
|
reply = self.results.each_with_object({}) do |(address, results), hash|
|
236
128
|
identifier = self.identifiers[ address ] or next
|
237
|
-
hash[ identifier ] = results
|
129
|
+
hash[ identifier.first ] = results
|
238
130
|
end
|
239
|
-
self.log.debug "Sending to manager: %p" % [ reply ]
|
240
131
|
return reply
|
241
|
-
end
|
242
|
-
|
243
|
-
|
244
|
-
#########
|
245
|
-
protected
|
246
|
-
#########
|
247
|
-
|
248
|
-
### Collect the load information for +host+ from an existing
|
249
|
-
### (and open) +snmp+ connection.
|
250
|
-
###
|
251
|
-
def gather_load( snmp, host )
|
252
|
-
self.log.debug "Getting system load for: %s" % [ host ]
|
253
|
-
load5 = snmp.get( SNMP::ObjectId.new( LOAD[:five_min] ) ).varbind_list.first.value.to_f
|
254
|
-
self.log.debug " Load on %s: %0.2f" % [ host, load5 ]
|
255
|
-
|
256
|
-
if load5 >= self.load_error_at
|
257
|
-
self.results[ host ] = {
|
258
|
-
error: "Load has exceeded %0.2f over a 5 minute average" % [ self.load_error_at ],
|
259
|
-
load5: load5
|
260
|
-
}
|
261
|
-
else
|
262
|
-
self.results[ host ] = { load5: load5 }
|
263
|
-
end
|
264
|
-
end
|
265
|
-
|
266
|
-
|
267
|
-
### Collect available memory information for +host+ from an existing
|
268
|
-
### (and open) +snmp+ connection.
|
269
|
-
###
|
270
|
-
def gather_free_memory( snmp, host )
|
271
|
-
self.log.debug "Getting available memory for: %s" % [ host ]
|
272
|
-
mem_avail = snmp.get( SNMP::ObjectId.new( MEMORY[:mem_avail] ) ).varbind_list.first.value.to_f
|
273
|
-
self.log.debug " Available memory on %s: %0.2f" % [ host, mem_avail ]
|
274
|
-
|
275
|
-
if mem_avail <= self.mem_error_at
|
276
|
-
self.results[ host ] = {
|
277
|
-
error: "Available memory is under %0.1fMB" % [ self.mem_error_at.to_f / 1024 ],
|
278
|
-
available_memory: mem_avail
|
279
|
-
}
|
280
|
-
else
|
281
|
-
self.results[ host ] = { available_memory: mem_avail }
|
282
|
-
end
|
283
|
-
end
|
284
|
-
|
285
|
-
|
286
|
-
### Collect used swap information for +host+ from an existing (and
|
287
|
-
### open) +snmp+ connection.
|
288
|
-
###
|
289
|
-
def gather_swap( snmp, host )
|
290
|
-
self.log.debug "Getting used swap for: %s" % [ host ]
|
291
|
-
|
292
|
-
swap_total = snmp.get( SNMP::ObjectId.new(MEMORY[:swap_total]) ).varbind_list.first.value.to_f
|
293
|
-
swap_avail = snmp.get( SNMP::ObjectId.new(MEMORY[:swap_avail]) ).varbind_list.first.value.to_f
|
294
|
-
swap_used = ( "%0.2f" % ((swap_avail / swap_total.to_f * 100 ) - 100).abs ).to_f
|
295
|
-
self.log.debug " Swap in use on %s: %0.2f" % [ host, swap_used ]
|
296
|
-
|
297
|
-
if swap_used >= self.swap_error_at
|
298
|
-
self.results[ host ] = {
|
299
|
-
error: "%0.2f%% swap in use" % [ swap_used ],
|
300
|
-
swap_used: swap_used
|
301
|
-
}
|
302
|
-
else
|
303
|
-
self.results[ host ] = { swap_used: swap_used }
|
304
|
-
end
|
305
|
-
end
|
306
|
-
|
307
|
-
|
308
|
-
### Collect mount point usage for +host+ from an existing (and open)
|
309
|
-
#### +snmp+ connection.
|
310
|
-
###
|
311
|
-
def gather_disks( snmp, host )
|
312
|
-
self.log.debug "Getting disk information for %s" % [ host ]
|
313
|
-
errors = []
|
314
|
-
results = {}
|
315
|
-
mounts = self.get_disk_percentages( snmp )
|
316
|
-
|
317
|
-
mounts.each_pair do |path, percentage|
|
318
|
-
if percentage >= self.storage_error_at
|
319
|
-
errors << "Mount %s at %d%% capacity" % [ path, percentage ]
|
320
|
-
end
|
321
|
-
end
|
322
132
|
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
self.results[ host ] = results
|
327
|
-
end
|
328
|
-
|
329
|
-
|
330
|
-
### Collect running processes on +host+ from an existing (and open)
|
331
|
-
#### +snmp+ connection.
|
332
|
-
###
|
333
|
-
def gather_processlist( snmp, host )
|
334
|
-
self.log.debug "Getting running process list for %s" % [ host ]
|
335
|
-
procs = []
|
336
|
-
|
337
|
-
snmp.walk([ PROCESS[:list], PROCESS[:args] ]) do |list|
|
338
|
-
process = list[0].value.to_s
|
339
|
-
args = list[1].value.to_s
|
340
|
-
procs << "%s %s " % [ process, args ]
|
341
|
-
end
|
342
|
-
|
343
|
-
# Check against the running stuff, setting an error if
|
344
|
-
# one isn't found.
|
345
|
-
#
|
346
|
-
errors = []
|
347
|
-
Array( self.processes ).each do |process|
|
348
|
-
process_r = Regexp.new( process )
|
349
|
-
found = procs.find{|p| p.match(process_r) }
|
350
|
-
errors << "Process '%s' is not running" % [ process, host ] unless found
|
351
|
-
end
|
352
|
-
|
353
|
-
self.log.debug " %d running processes" % [ procs.length ]
|
354
|
-
if errors.empty?
|
355
|
-
self.results[ host ] = {}
|
356
|
-
else
|
357
|
-
self.results[ host ] = { error: errors.join( ', ' ) }
|
358
|
-
end
|
133
|
+
ensure
|
134
|
+
@identifiers = {}
|
135
|
+
@results = {}
|
359
136
|
end
|
360
137
|
|
138
|
+
end # Arborist::Monitor::SNMP
|
361
139
|
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
def get_disk_percentages( snmp )
|
367
|
-
|
368
|
-
# Does this look like a windows system, or a net-snmp based one?
|
369
|
-
system_type = snmp.get( SNMP::ObjectId.new( IDENTIFICATION_OID ) ).varbind_list.first.value
|
370
|
-
disks = {}
|
371
|
-
|
372
|
-
# Windows has it's own MIBs.
|
373
|
-
#
|
374
|
-
if system_type =~ /windows/i
|
375
|
-
snmp.walk( STORAGE_WINDOWS ) do |list|
|
376
|
-
next unless list[0].value.to_s == WINDOWS_DEVICE
|
377
|
-
disks[ list[1].value.to_s ] = ( list[3].value.to_f / list[2].value.to_f ) * 100
|
378
|
-
end
|
379
|
-
return disks
|
380
|
-
end
|
381
|
-
|
382
|
-
# Everything else.
|
383
|
-
#
|
384
|
-
snmp.walk( STORAGE_NET_SNMP ) do |list|
|
385
|
-
mount = list[0].value.to_s
|
386
|
-
next if mount == 'noSuchInstance'
|
387
|
-
|
388
|
-
next if list[2].value.to_s == 'noSuchInstance'
|
389
|
-
used = list[2].value.to_i
|
390
|
-
|
391
|
-
typeoid = list[1].value.join('.').to_s
|
392
|
-
next if typeoid =~ STORAGE_IGNORE
|
393
|
-
next if mount =~ /\/(?:dev|proc)$/
|
394
|
-
|
395
|
-
self.log.debug " %s -> %s -> %s" % [ mount, typeoid, used ]
|
396
|
-
disks[ mount ] = used
|
397
|
-
end
|
398
|
-
|
399
|
-
return disks
|
400
|
-
end
|
401
|
-
end # class Arborist::Monitor::SNMP
|
140
|
+
require 'arborist/monitor/snmp/cpu'
|
141
|
+
require 'arborist/monitor/snmp/disk'
|
142
|
+
require 'arborist/monitor/snmp/process'
|
143
|
+
require 'arborist/monitor/snmp/memory'
|
402
144
|
|
@@ -0,0 +1,140 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
# vim: set noet nosta sw=4 ts=4 :
|
3
|
+
|
4
|
+
require 'arborist/monitor/snmp' unless defined?( Arborist::Monitor::SNMP )
|
5
|
+
|
6
|
+
# Machine load/cpu checks.
|
7
|
+
#
|
8
|
+
# Sets current 1, 5, and 15 minute loads under the 'load' attribute,
|
9
|
+
# and calculates/warns on cpu overutilization.
|
10
|
+
#
|
11
|
+
class Arborist::Monitor::SNMP::CPU
|
12
|
+
include Arborist::Monitor::SNMP
|
13
|
+
|
14
|
+
extend Configurability, Loggability
|
15
|
+
log_to :arborist_snmp
|
16
|
+
|
17
|
+
# OIDS for discovering system load.
|
18
|
+
#
|
19
|
+
OIDS = {
|
20
|
+
load: '1.3.6.1.4.1.2021.10.1.3',
|
21
|
+
cpu: '1.3.6.1.2.1.25.3.3.1.2'
|
22
|
+
}
|
23
|
+
|
24
|
+
# When walking load OIDS, the iterator count matches
|
25
|
+
# these labels.
|
26
|
+
#
|
27
|
+
LOADKEYS = {
|
28
|
+
1 => :load1,
|
29
|
+
2 => :load5,
|
30
|
+
3 => :load15
|
31
|
+
}
|
32
|
+
|
33
|
+
|
34
|
+
# Global defaults for instances of this monitor
|
35
|
+
#
|
36
|
+
configurability( 'arborist.snmp.cpu' ) do
|
37
|
+
# What overutilization percentage qualifies as a warning
|
38
|
+
setting :warn_at, default: 80
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
### Return the properties used by this monitor.
|
43
|
+
###
|
44
|
+
def self::node_properties
|
45
|
+
return USED_PROPERTIES
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
### Class #run creates a new instance and immediately runs it.
|
50
|
+
###
|
51
|
+
def self::run( nodes )
|
52
|
+
return new.run( nodes )
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
### Perform the monitoring checks.
|
57
|
+
###
|
58
|
+
def run( nodes )
|
59
|
+
super do |host, snmp|
|
60
|
+
self.find_load( host, snmp )
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
#########
|
66
|
+
protected
|
67
|
+
#########
|
68
|
+
|
69
|
+
### Return system CPU data.
|
70
|
+
###
|
71
|
+
def cpu( snmp )
|
72
|
+
return snmp.walk( OIDS[:cpu] )
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
### Find load data, add additional niceties for reporting.
|
77
|
+
###
|
78
|
+
def format_load( snmp )
|
79
|
+
info = { cpu: {}, load: {} }
|
80
|
+
cpus = self.cpu( snmp )
|
81
|
+
|
82
|
+
info[ :cpu ][ :count ] = cpus.size
|
83
|
+
|
84
|
+
# Windows SNMP doesn't have a concept of "load" over time,
|
85
|
+
# so we have to just use the current averaged CPU usage.
|
86
|
+
#
|
87
|
+
# This means that windows machines will very likely want to
|
88
|
+
# adjust the default "overutilization" number, considering
|
89
|
+
# it's really just how much of the CPU is used at the time of
|
90
|
+
# the monitor run, along with liberal use of the Observer "only
|
91
|
+
# alert after X events" pragmas.
|
92
|
+
#
|
93
|
+
if self.system =~ /windows\s+/i
|
94
|
+
info[ :cpu ][ :usage ] = cpus.values.inject( :+ ).to_f / cpus.size
|
95
|
+
info[ :message ] = "System is %0.1f%% in use." % [ info[ :cpu ][ :usage ] ]
|
96
|
+
|
97
|
+
# UCDavis stuff is better for alerting only after there has been
|
98
|
+
# an extended load event. Use the 5 minute average to avoid
|
99
|
+
# state changes on transient spikes.
|
100
|
+
#
|
101
|
+
else
|
102
|
+
snmp.walk( OIDS[:load] ).each_with_index do |(_, value), idx|
|
103
|
+
next unless LOADKEYS[ idx + 1 ]
|
104
|
+
info[ :load ][ LOADKEYS[idx + 1] ] = value.to_f
|
105
|
+
end
|
106
|
+
|
107
|
+
percentage = (( ( info[:load][ :load5 ] / cpus.size ) - 1 ) * 100 ).round( 1 )
|
108
|
+
|
109
|
+
if percentage < 0
|
110
|
+
info[ :message ] = "System is %0.1f%% idle." % [ percentage.abs ]
|
111
|
+
info[ :cpu ][ :usage ] = percentage + 100
|
112
|
+
else
|
113
|
+
info[ :message ] = "System is %0.1f%% overloaded." % [ percentage ]
|
114
|
+
info[ :cpu ][ :usage ] = percentage
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
return info
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
### Collect the load information for +host+ from an existing
|
123
|
+
### (and open) +snmp+ connection.
|
124
|
+
###
|
125
|
+
def find_load( host, snmp )
|
126
|
+
info = self.format_load( snmp )
|
127
|
+
|
128
|
+
config = identifiers[ host ].last || {}
|
129
|
+
warn_at = config[ 'warn_at' ] || self.class.warn_at
|
130
|
+
usage = info.dig( :cpu, :usage ) || 0
|
131
|
+
|
132
|
+
if usage >= warn_at
|
133
|
+
info[ :warning ] = "%0.1f utilization exceeds %0.1f percent" % [ usage, warn_at ]
|
134
|
+
end
|
135
|
+
|
136
|
+
self.results[ host ] = info
|
137
|
+
end
|
138
|
+
|
139
|
+
end # class Arborist::Monitor::SNMP::CPU
|
140
|
+
|
@@ -0,0 +1,183 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
# vim: set noet nosta sw=4 ts=4 :
|
3
|
+
|
4
|
+
require 'arborist/monitor/snmp' unless defined?( Arborist::Monitor::SNMP )
|
5
|
+
|
6
|
+
# Disk capacity checks.
|
7
|
+
#
|
8
|
+
# Sets all configured mounts with their current usage percentage
|
9
|
+
# in an attribute named "mounts".
|
10
|
+
#
|
11
|
+
class Arborist::Monitor::SNMP::Disk
|
12
|
+
include Arborist::Monitor::SNMP
|
13
|
+
|
14
|
+
extend Configurability, Loggability
|
15
|
+
log_to :arborist_snmp
|
16
|
+
|
17
|
+
# OIDS required to pull disk information from net-snmp.
|
18
|
+
#
|
19
|
+
STORAGE_NET_SNMP = {
|
20
|
+
path: '1.3.6.1.4.1.2021.9.1.2',
|
21
|
+
percent: '1.3.6.1.4.1.2021.9.1.9',
|
22
|
+
type: '1.3.6.1.2.1.25.3.8.1.4'
|
23
|
+
}
|
24
|
+
|
25
|
+
# The OID that matches a local windows hard disk.
|
26
|
+
#
|
27
|
+
WINDOWS_DEVICES = [
|
28
|
+
'1.3.6.1.2.1.25.2.1.4', # local disk
|
29
|
+
'1.3.6.1.2.1.25.2.1.7' # removables, but we have to include them for iscsi mounts
|
30
|
+
]
|
31
|
+
|
32
|
+
# OIDS required to pull disk information from Windows.
|
33
|
+
#
|
34
|
+
STORAGE_WINDOWS = {
|
35
|
+
type: '1.3.6.1.2.1.25.2.3.1.2',
|
36
|
+
path: '1.3.6.1.2.1.25.2.3.1.3',
|
37
|
+
total: '1.3.6.1.2.1.25.2.3.1.5',
|
38
|
+
used: '1.3.6.1.2.1.25.2.3.1.6'
|
39
|
+
}
|
40
|
+
|
41
|
+
# The fallback warning capacity.
|
42
|
+
WARN_AT = 90
|
43
|
+
|
44
|
+
|
45
|
+
# Configurability API
|
46
|
+
#
|
47
|
+
configurability( 'arborist.snmp.disk' ) do
|
48
|
+
# What percentage qualifies as a warning
|
49
|
+
setting :warn_at, default: WARN_AT
|
50
|
+
|
51
|
+
# If non-empty, only these paths are included in checks.
|
52
|
+
#
|
53
|
+
setting :include do |val|
|
54
|
+
if val
|
55
|
+
mounts = Array( val ).map{|m| Regexp.new(m) }
|
56
|
+
Regexp.union( mounts )
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Paths to exclude from checks
|
61
|
+
#
|
62
|
+
setting :exclude,
|
63
|
+
default: [ '^/dev(/.+)?$', '^/net(/.+)?$', '^/proc$', '^/run$', '^/sys/' ] do |val|
|
64
|
+
mounts = Array( val ).map{|m| Regexp.new(m) }
|
65
|
+
Regexp.union( mounts )
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
### Return the properties used by this monitor.
|
71
|
+
###
|
72
|
+
def self::node_properties
|
73
|
+
return USED_PROPERTIES
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
### Class #run creates a new instance and immediately runs it.
|
78
|
+
###
|
79
|
+
def self::run( nodes )
|
80
|
+
return new.run( nodes )
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
### Perform the monitoring checks.
|
85
|
+
###
|
86
|
+
def run( nodes )
|
87
|
+
super do |host, snmp|
|
88
|
+
self.gather_disks( host, snmp )
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
#########
|
94
|
+
protected
|
95
|
+
#########
|
96
|
+
|
97
|
+
### Collect mount point usage for +host+ from an existing (and open)
|
98
|
+
### +snmp+ connection.
|
99
|
+
###
|
100
|
+
def gather_disks( host, snmp )
|
101
|
+
mounts = self.system =~ /windows\s+/i ? self.windows_disks( snmp ) : self.unix_disks( snmp )
|
102
|
+
config = self.identifiers[ host ].last || {}
|
103
|
+
warn_at = config[ 'warn_at' ] || self.class.warn_at
|
104
|
+
|
105
|
+
includes = self.format_mounts( config, 'include' ) || self.class.include
|
106
|
+
excludes = self.format_mounts( config, 'exclude' ) || self.class.exclude
|
107
|
+
|
108
|
+
mounts.reject! do |path, percentage|
|
109
|
+
excludes.match( path ) || ( includes && ! includes.match( path ) )
|
110
|
+
end
|
111
|
+
|
112
|
+
errors = []
|
113
|
+
warnings = []
|
114
|
+
mounts.each_pair do |path, percentage|
|
115
|
+
|
116
|
+
warn = begin
|
117
|
+
if warn_at.is_a?( Hash )
|
118
|
+
warn_at[ path ] || WARN_AT
|
119
|
+
else
|
120
|
+
warn_at
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
self.log.debug "%s:%s -> at %d, warn at %d" % [ host, path, percentage, warn ]
|
125
|
+
|
126
|
+
if percentage >= warn.to_i
|
127
|
+
if percentage >= 100
|
128
|
+
errors << "%s at %d%% capacity" % [ path, percentage ]
|
129
|
+
else
|
130
|
+
warnings << "%s at %d%% capacity" % [ path, percentage ]
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
self.results[ host ] = { mounts: mounts }
|
136
|
+
self.results[ host ][ :error ] = errors.join(', ') unless errors.empty?
|
137
|
+
self.results[ host ][ :warning ] = warnings.join(', ') unless warnings.empty?
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
### Return a single regexp for the 'include' or 'exclude' section of
|
142
|
+
### resource node's +config+, or nil if nonexistent.
|
143
|
+
###
|
144
|
+
def format_mounts( config, section )
|
145
|
+
list = config[ section ] || return
|
146
|
+
mounts = Array( list ).map{|m| Regexp.new(m) }
|
147
|
+
return Regexp.union( mounts )
|
148
|
+
end
|
149
|
+
|
150
|
+
|
151
|
+
### Fetch information for Windows systems.
|
152
|
+
###
|
153
|
+
def windows_disks( snmp )
|
154
|
+
raw = snmp.get_bulk([
|
155
|
+
STORAGE_WINDOWS[:path],
|
156
|
+
STORAGE_WINDOWS[:type],
|
157
|
+
STORAGE_WINDOWS[:total],
|
158
|
+
STORAGE_WINDOWS[:used]
|
159
|
+
]).varbinds.map( &:value )
|
160
|
+
|
161
|
+
disks = {}
|
162
|
+
raw.each_slice( 4 ) do |device|
|
163
|
+
next unless device[1].respond_to?( :oid ) && WINDOWS_DEVICES.include?( device[1].oid )
|
164
|
+
next if device[2].zero?
|
165
|
+
disks[ device[0] ] = (( device[3].to_f / device[2] ) * 100).round( 1 )
|
166
|
+
end
|
167
|
+
|
168
|
+
return disks
|
169
|
+
end
|
170
|
+
|
171
|
+
|
172
|
+
### Fetch information for Unix/MacOS systems.
|
173
|
+
###
|
174
|
+
def unix_disks( snmp )
|
175
|
+
raw = snmp.get_bulk([
|
176
|
+
STORAGE_NET_SNMP[:path],
|
177
|
+
STORAGE_NET_SNMP[:percent] ]).varbinds.map( &:value )
|
178
|
+
|
179
|
+
return Hash[ *raw ]
|
180
|
+
end
|
181
|
+
|
182
|
+
end # class Arborist::Monitor::SNMP::Disk
|
183
|
+
|
@@ -0,0 +1,180 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
# vim: set noet nosta sw=4 ts=4 :
|
3
|
+
|
4
|
+
require 'arborist/monitor/snmp' unless defined?( Arborist::Monitor::SNMP )
|
5
|
+
|
6
|
+
# SNMP memory and swap utilization checks.
|
7
|
+
#
|
8
|
+
# Set 'usage' and 'available' keys as properties, in percentage/GBs,
|
9
|
+
# respectively.
|
10
|
+
#
|
11
|
+
# By default, doesn't warn on memory usage, only swap, since
|
12
|
+
# that's more indicitive of a problem. You can still set the
|
13
|
+
# 'physical_warn_at' key to force warnings on ram usage, for embedded
|
14
|
+
# systems or other similar things without virtual memory.
|
15
|
+
#
|
16
|
+
class Arborist::Monitor::SNMP::Memory
|
17
|
+
include Arborist::Monitor::SNMP
|
18
|
+
|
19
|
+
extend Configurability, Loggability
|
20
|
+
log_to :arborist_snmp
|
21
|
+
|
22
|
+
# OIDS for discovering memory usage.
|
23
|
+
#
|
24
|
+
MEMORY = {
|
25
|
+
total: '1.3.6.1.4.1.2021.4.5.0',
|
26
|
+
avail: '1.3.6.1.4.1.2021.4.6.0',
|
27
|
+
windows: {
|
28
|
+
label: '1.3.6.1.2.1.25.2.3.1.3',
|
29
|
+
units: '1.3.6.1.2.1.25.2.3.1.4',
|
30
|
+
total: '1.3.6.1.2.1.25.2.3.1.5',
|
31
|
+
used: '1.3.6.1.2.1.25.2.3.1.6'
|
32
|
+
}
|
33
|
+
}
|
34
|
+
|
35
|
+
# OIDS for discovering swap usage.
|
36
|
+
#
|
37
|
+
SWAP = {
|
38
|
+
total: '1.3.6.1.4.1.2021.4.3.0',
|
39
|
+
avail: '1.3.6.1.4.1.2021.4.4.0'
|
40
|
+
}
|
41
|
+
|
42
|
+
# Global defaults for instances of this monitor
|
43
|
+
#
|
44
|
+
configurability( 'arborist.snmp.memory' ) do
|
45
|
+
# What memory usage percentage qualifies as a warning
|
46
|
+
setting :physical_warn_at, default: nil
|
47
|
+
|
48
|
+
# What swap usage percentage qualifies as a warning
|
49
|
+
setting :swap_warn_at, default: 60
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
### Return the properties used by this monitor.
|
54
|
+
###
|
55
|
+
def self::node_properties
|
56
|
+
return USED_PROPERTIES
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
|
61
|
+
### Class #run creates a new instance and immediately runs it.
|
62
|
+
###
|
63
|
+
def self::run( nodes )
|
64
|
+
return new.run( nodes )
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
### Perform the monitoring checks.
|
69
|
+
###
|
70
|
+
def run( nodes )
|
71
|
+
super do |host, snmp|
|
72
|
+
self.gather_memory( host, snmp )
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
#########
|
78
|
+
protected
|
79
|
+
#########
|
80
|
+
|
81
|
+
### Collect available memory information for +host+ from an existing
|
82
|
+
### (and open) +snmp+ connection.
|
83
|
+
###
|
84
|
+
def gather_memory( host, snmp )
|
85
|
+
info = self.system =~ /windows\s+/i ? self.get_windows( snmp ) : self.get_mem( snmp )
|
86
|
+
|
87
|
+
config = identifiers[ host ].last || {}
|
88
|
+
physical_warn_at = config[ 'physical_warn_at' ] || self.class.physical_warn_at
|
89
|
+
swap_warn_at = config[ 'swap_warn_at' ] || self.class.swap_warn_at
|
90
|
+
|
91
|
+
self.log.debug "Memory data on %s: %p" % [ host, info ]
|
92
|
+
memory, swap = info[:memory], info[:swap]
|
93
|
+
self.results[ host ] = { memory: memory, swap: swap }
|
94
|
+
|
95
|
+
memusage = memory[ :usage ].to_i
|
96
|
+
if physical_warn_at && memusage >= physical_warn_at
|
97
|
+
self.results[ host ][ :warning ] = "%0.1f memory utilization exceeds %0.1f percent" % [
|
98
|
+
memusage,
|
99
|
+
physical_warn_at
|
100
|
+
]
|
101
|
+
end
|
102
|
+
|
103
|
+
swapusage = swap[ :usage ].to_i
|
104
|
+
if swapusage >= swap_warn_at
|
105
|
+
self.results[ host ][ :warning ] = "%0.1f swap utilization exceeds %0.1f percent" % [
|
106
|
+
swapusage,
|
107
|
+
swap_warn_at
|
108
|
+
]
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
|
113
|
+
### Return a hash of usage percentage in use, and free mem in
|
114
|
+
### megs.
|
115
|
+
###
|
116
|
+
def get_mem( snmp )
|
117
|
+
info = {}
|
118
|
+
info[ :memory ] = self.calc_memory( snmp, MEMORY )
|
119
|
+
info[ :swap ] = self.calc_memory( snmp, SWAP )
|
120
|
+
|
121
|
+
return info
|
122
|
+
end
|
123
|
+
|
124
|
+
|
125
|
+
### Windows appends virtual and physical memory onto the last two items
|
126
|
+
### of the storage iterator, because that made sense in someone's mind.
|
127
|
+
### Walk the whole oid tree, and get the values we're after, return
|
128
|
+
### a hash of usage percentage in use and free mem in megs.
|
129
|
+
###
|
130
|
+
def get_windows( snmp )
|
131
|
+
info = { memory: {}, swap: {} }
|
132
|
+
mem_idx, swap_idx = nil
|
133
|
+
|
134
|
+
snmp.walk( MEMORY[:windows][:label] ).each_with_index do |(_, val), i|
|
135
|
+
mem_idx = i + 1 if val =~ /physical memory/i
|
136
|
+
swap_idx = i + 1 if val =~ /virtual memory/i
|
137
|
+
end
|
138
|
+
return info unless mem_idx
|
139
|
+
|
140
|
+
info[ :memory ] = self.calc_windows_memory( snmp, mem_idx )
|
141
|
+
info[ :swap ] = self.calc_windows_memory( snmp, swap_idx )
|
142
|
+
|
143
|
+
return info
|
144
|
+
end
|
145
|
+
|
146
|
+
|
147
|
+
### Format usage and available amount, given an OID hash.
|
148
|
+
###
|
149
|
+
def calc_memory( snmp, oids )
|
150
|
+
info = { usage: 0, available: 0 }
|
151
|
+
avail = snmp.get( oids[:avail] ).varbinds.first.value.to_f
|
152
|
+
total = snmp.get( oids[:total] ).varbinds.first.value.to_f
|
153
|
+
used = total - avail
|
154
|
+
|
155
|
+
return info if avail.zero?
|
156
|
+
|
157
|
+
info[ :usage ] = (( used / total ) * 100 ).round( 2 )
|
158
|
+
info[ :available ] = (( total - used ) / 1024 ).round( 2 )
|
159
|
+
return info
|
160
|
+
end
|
161
|
+
|
162
|
+
|
163
|
+
### Format usage and available amount for windows.
|
164
|
+
###
|
165
|
+
def calc_windows_memory( snmp, idx)
|
166
|
+
info = { usage: 0, available: 0 }
|
167
|
+
return info unless idx
|
168
|
+
|
169
|
+
units = snmp.get( MEMORY[:windows][:units] + ".#{idx}" ).varbinds.first.value
|
170
|
+
total = snmp.get( MEMORY[:windows][:total] + ".#{idx}" ).varbinds.first.value.to_f * units
|
171
|
+
used = snmp.get( MEMORY[:windows][:used] + ".#{idx}" ).varbinds.first.value.to_f * units
|
172
|
+
|
173
|
+
info[ :usage ] = (( used / total ) * 100 ).round( 2 )
|
174
|
+
info[ :available ] = (( total - used ) / 1024 / 1024 ).round( 2 )
|
175
|
+
return info
|
176
|
+
end
|
177
|
+
|
178
|
+
|
179
|
+
end # class Arborist::Monitor::SNMP::Memory
|
180
|
+
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
# vim: set noet nosta sw=4 ts=4 :
|
3
|
+
|
4
|
+
require 'arborist/monitor/snmp' unless defined?( Arborist::Monitor::SNMP )
|
5
|
+
|
6
|
+
# SNMP running process checks.
|
7
|
+
#
|
8
|
+
# This only checks running userland processes.
|
9
|
+
#
|
10
|
+
class Arborist::Monitor::SNMP::Process
|
11
|
+
include Arborist::Monitor::SNMP
|
12
|
+
|
13
|
+
extend Configurability, Loggability
|
14
|
+
log_to :arborist_snmp
|
15
|
+
|
16
|
+
|
17
|
+
# OIDS for discovering running processes.
|
18
|
+
# Of course, Windows does it slightly differently.
|
19
|
+
#
|
20
|
+
PROCESS = {
|
21
|
+
netsnmp: {
|
22
|
+
list: '1.3.6.1.2.1.25.4.2.1.4',
|
23
|
+
args: '1.3.6.1.2.1.25.4.2.1.5'
|
24
|
+
},
|
25
|
+
windows: {
|
26
|
+
list: '1.3.6.1.2.1.25.4.2.1.2',
|
27
|
+
path: '1.3.6.1.2.1.25.4.2.1.4',
|
28
|
+
args: '1.3.6.1.2.1.25.4.2.1.5'
|
29
|
+
}
|
30
|
+
}
|
31
|
+
|
32
|
+
|
33
|
+
# Global defaults for instances of this monitor
|
34
|
+
#
|
35
|
+
configurability( 'arborist.snmp.processes' ) do
|
36
|
+
# Default list of processes to check for
|
37
|
+
setting :check, default: [] do |val|
|
38
|
+
Array( val )
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
### Return the properties used by this monitor.
|
44
|
+
###
|
45
|
+
def self::node_properties
|
46
|
+
return USED_PROPERTIES
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
### Class #run creates a new instance and immediately runs it.
|
51
|
+
###
|
52
|
+
def self::run( nodes )
|
53
|
+
return new.run( nodes )
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
### Perform the monitoring checks.
|
58
|
+
###
|
59
|
+
def run( nodes )
|
60
|
+
super do |host, snmp|
|
61
|
+
self.gather_processlist( host, snmp )
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
#########
|
67
|
+
protected
|
68
|
+
#########
|
69
|
+
|
70
|
+
### Collect running processes on +host+ from an existing (and open)
|
71
|
+
#### +snmp+ connection.
|
72
|
+
###
|
73
|
+
def gather_processlist( host, snmp )
|
74
|
+
config = self.identifiers[ host ].last || {}
|
75
|
+
errors = []
|
76
|
+
procs = self.system =~ /windows\s+/i ? self.get_windows( snmp ) : self.get_procs( snmp )
|
77
|
+
|
78
|
+
self.log.debug "Running processes for host: %s: %p" % [ host, procs ]
|
79
|
+
self.results[ host ] = { count: procs.size }
|
80
|
+
|
81
|
+
# Check against what is running.
|
82
|
+
#
|
83
|
+
Array( config['processes'] || self.class.check ).each do |process|
|
84
|
+
process_r = Regexp.new( process )
|
85
|
+
found = procs.find{|p| p.match(process_r) }
|
86
|
+
errors << "'%s' is not running" % [ process ] unless found
|
87
|
+
end
|
88
|
+
|
89
|
+
self.results[ host ][ :error ] = errors.join( ', ' ) unless errors.empty?
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
### Parse OIDS and return an Array of running processes.
|
94
|
+
### Windows specific behaviors.
|
95
|
+
###
|
96
|
+
def get_windows( snmp )
|
97
|
+
oids = [ PROCESS[:windows][:path], PROCESS[:windows][:list], PROCESS[:windows][:args] ]
|
98
|
+
return snmp.walk( oids ).each_slice( 3 ). each_with_object( [] ) do |vals, acc|
|
99
|
+
path, process, args = vals[0][1], vals[1][1], vals[2][1]
|
100
|
+
next if path.empty?
|
101
|
+
|
102
|
+
process = "%s%s" % [ path, process ]
|
103
|
+
process << " %s" % [ args ] unless args.empty?
|
104
|
+
acc << process
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
### Parse OIDS and return an Array of running processes.
|
110
|
+
###
|
111
|
+
def get_procs( snmp )
|
112
|
+
oids = [ PROCESS[:netsnmp][:list], PROCESS[:netsnmp][:args] ]
|
113
|
+
return snmp.walk( oids ).each_slice( 2 ).each_with_object( [] ) do |vals, acc|
|
114
|
+
process, args = vals[0][1], vals[1][1]
|
115
|
+
next if process.empty?
|
116
|
+
|
117
|
+
process << " %s" % [ args ] unless args.empty?
|
118
|
+
acc << process
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
end # class Arborist::Monitor::SNMP::Process
|
123
|
+
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
require 'loggability'
|
5
|
+
require 'arborist'
|
6
|
+
|
7
|
+
|
8
|
+
# Various monitoring checks using SNMP, for the Arborist monitoring toolkit.
|
9
|
+
module Arborist::SNMP
|
10
|
+
extend Loggability
|
11
|
+
|
12
|
+
# Loggability API -- set up a log host for this library
|
13
|
+
log_as :arborist_snmp
|
14
|
+
|
15
|
+
|
16
|
+
# Package version
|
17
|
+
VERSION = '0.4.0'
|
18
|
+
|
19
|
+
# Version control revision
|
20
|
+
REVISION = %q$Revision: e0b7c95a154f $
|
21
|
+
|
22
|
+
|
23
|
+
### Return the name of the library with the version, and optionally the build ID if
|
24
|
+
### +include_build+ is true.
|
25
|
+
def self::version_string( include_build: false )
|
26
|
+
str = "%p v%s" % [ self, VERSION ]
|
27
|
+
str << ' (' << REVISION.strip << ')' if include_build
|
28
|
+
return str
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
require 'arborist/monitor/snmp'
|
33
|
+
|
34
|
+
end # module Arborist::SNMP
|
35
|
+
|
metadata
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: arborist-snmp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
- Mahlon E. Smith
|
8
|
-
- Michael Granger
|
7
|
+
- Mahlon E. Smith <mahlon@martini.nu>
|
8
|
+
- Michael Granger <ged@faeriemud.org>
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain:
|
@@ -13,7 +13,7 @@ cert_chain:
|
|
13
13
|
-----BEGIN CERTIFICATE-----
|
14
14
|
MIIDbDCCAlSgAwIBAgIBATANBgkqhkiG9w0BAQUFADA+MQ8wDQYDVQQDDAZtYWhs
|
15
15
|
b24xFzAVBgoJkiaJk/IsZAEZFgdtYXJ0aW5pMRIwEAYKCZImiZPyLGQBGRYCbnUw
|
16
|
-
|
16
|
+
HhcNMTcxMTIyMjIyMTAyWhcNMTgxMTIyMjIyMTAyWjA+MQ8wDQYDVQQDDAZtYWhs
|
17
17
|
b24xFzAVBgoJkiaJk/IsZAEZFgdtYXJ0aW5pMRIwEAYKCZImiZPyLGQBGRYCbnUw
|
18
18
|
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDpXGN0YbMVpYv4EoiCxpQw
|
19
19
|
sxKdyhlkvpvENUkpEhbpnEuMKXgUfRHO4T/vBZf0h8eYgwnrHCRhAeIqesFKfoj9
|
@@ -24,52 +24,55 @@ cert_chain:
|
|
24
24
|
AgMBAAGjdTBzMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQWBBRY8ea6
|
25
25
|
+6kAaW7ukKph2/4MTAD8/TAcBgNVHREEFTATgRFtYWhsb25AbWFydGluaS5udTAc
|
26
26
|
BgNVHRIEFTATgRFtYWhsb25AbWFydGluaS5udTANBgkqhkiG9w0BAQUFAAOCAQEA
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
27
|
+
00FljTeSNaYUqJ59yLRA+i43wVNiO4ETQQu6fYQCPns12Sm90spOJb3SmTDkJ7CY
|
28
|
+
dixOQg5A3Et1LVS+Z/YfH1TAbb50oTWbZbTW2JknHS0hohq3UF1pvbkk1niZ26er
|
29
|
+
skJ352MUfcyaUkQyMmCjL/BpkDutYH5OCGh+FmK8+mH7SoC9Nr48WwH2prVdHs3y
|
30
|
+
OMWFgB33sXdj1XqOd2Rw1WPgAeMeDqWeIrRMpUhNZOwroaA1MAr60f9NIYxua/vx
|
31
|
+
n0YyneERGuHPSRZFgo72tGOqLpAlWnhPxRNqnayZmsg3hPPI87B6MTUI2UQ7VUdh
|
32
|
+
UrSf3b+cPoC8PNfjp8zsdw==
|
33
33
|
-----END CERTIFICATE-----
|
34
|
-
date:
|
34
|
+
date: 2018-04-04 00:00:00.000000000 Z
|
35
35
|
dependencies:
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
|
-
name:
|
37
|
+
name: arborist
|
38
38
|
requirement: !ruby/object:Gem::Requirement
|
39
39
|
requirements:
|
40
40
|
- - "~>"
|
41
41
|
- !ruby/object:Gem::Version
|
42
|
-
version: '1
|
42
|
+
version: '0.1'
|
43
43
|
type: :runtime
|
44
44
|
prerelease: false
|
45
45
|
version_requirements: !ruby/object:Gem::Requirement
|
46
46
|
requirements:
|
47
47
|
- - "~>"
|
48
48
|
- !ruby/object:Gem::Version
|
49
|
-
version: '1
|
49
|
+
version: '0.1'
|
50
50
|
- !ruby/object:Gem::Dependency
|
51
|
-
name:
|
51
|
+
name: net-snmp2
|
52
52
|
requirement: !ruby/object:Gem::Requirement
|
53
53
|
requirements:
|
54
54
|
- - "~>"
|
55
55
|
- !ruby/object:Gem::Version
|
56
|
-
version: '0'
|
56
|
+
version: '0.3'
|
57
57
|
type: :runtime
|
58
58
|
prerelease: false
|
59
59
|
version_requirements: !ruby/object:Gem::Requirement
|
60
60
|
requirements:
|
61
61
|
- - "~>"
|
62
62
|
- !ruby/object:Gem::Version
|
63
|
-
version: '0'
|
64
|
-
description: "\tThis library adds common SNMP support to Arborist monitors.\n"
|
65
|
-
email:
|
66
|
-
- mahlon@martini.nu
|
67
|
-
- ged@faeriemud.org
|
63
|
+
version: '0.3'
|
64
|
+
description: "\tThis library adds common SNMP resource support to Arborist monitors.\n"
|
65
|
+
email: mahlon@martini.nu
|
68
66
|
executables: []
|
69
67
|
extensions: []
|
70
68
|
extra_rdoc_files: []
|
71
69
|
files:
|
72
70
|
- lib/arborist/monitor/snmp.rb
|
71
|
+
- lib/arborist/monitor/snmp/cpu.rb
|
72
|
+
- lib/arborist/monitor/snmp/disk.rb
|
73
|
+
- lib/arborist/monitor/snmp/memory.rb
|
74
|
+
- lib/arborist/monitor/snmp/process.rb
|
75
|
+
- lib/arborist/snmp.rb
|
73
76
|
homepage: http://bitbucket.org/mahlon/Arborist-SNMP
|
74
77
|
licenses:
|
75
78
|
- BSD-3-Clause
|
@@ -85,13 +88,13 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
85
88
|
version: '2'
|
86
89
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
87
90
|
requirements:
|
88
|
-
- - "
|
91
|
+
- - ">="
|
89
92
|
- !ruby/object:Gem::Version
|
90
|
-
version:
|
93
|
+
version: '0'
|
91
94
|
requirements: []
|
92
95
|
rubyforge_project:
|
93
96
|
rubygems_version: 2.5.1
|
94
97
|
signing_key:
|
95
98
|
specification_version: 4
|
96
|
-
summary:
|
99
|
+
summary: SNMP support for Arborist monitors
|
97
100
|
test_files: []
|
checksums.yaml.gz.sig
DELETED
data.tar.gz.sig
DELETED
Binary file
|
metadata.gz.sig
DELETED