arborist-snmp 0.1.0.pre20161005111600 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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