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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 639227f1252bedce794f5bfabada161a9ff3d8a2
4
- data.tar.gz: 713cc9b8bf1fc6fc467b688d9d38656be3e193c9
3
+ metadata.gz: 833fc064ac6b821016f02ec2b427c7861cc4ebd2
4
+ data.tar.gz: d14a89c993c13e874b9201388999bb77ac85c06f
5
5
  SHA512:
6
- metadata.gz: 94b2a8a87fe45817464ddff645ab1058a6ab3577afd893cfc0b2837737ce3240567cf52c6982379a2886ba9590cf0674106ab601cf5ad0e82cc869e005754ffb
7
- data.tar.gz: b34cc56ac99c63333a020b5ecf201daf74d310dbe2447fec3568713e128fec3c0cdaaa36a15d7833b4cc2975f5bb14943b9127ef3b62ca3c884106fc1a203212
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. For your platform.
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
- require 'loggability'
14
- require 'arborist/monitor' unless defined?( Arborist::Monitor )
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
- # "Modes" that this monitor understands.
29
- VALID_MODES = %i[ disk load memory swap process ]
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
- # For net-snmp systems, ignore mount types that match
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
- STORAGE_NET_SNMP = [
46
- '1.3.6.1.4.1.2021.9.1.2', # paths
47
- '1.3.6.1.2.1.25.3.8.1.4', # types
48
- '1.3.6.1.4.1.2021.9.1.9' # percents
49
- ]
50
-
51
- # OIDS required to pull disk information from Windows.
52
- #
53
- STORAGE_WINDOWS = [
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
- ### Create a new instance of this monitor.
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 mode (section) that this SMMP instance should check.
118
- # Must be a +VALID_MODES+ mode.
119
- attr_reader :mode
45
+ # The system type, as advertised.
46
+ attr_reader :system
120
47
 
121
- # Mapping of node addresses back to the node identifier.
48
+ # The mapping of addresses back to node identifiers.
122
49
  attr_reader :identifiers
123
50
 
124
- # The results from the SNMP daemons, keyed by address.
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
- # Set an error if the 5 minute load average exceeds this.
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 = nodes.each_with_object({}) do |(identifier, props), hash|
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
- hash[ address ] = identifier
67
+ self.identifiers[ address ] = [ identifier, props['config'] ]
186
68
  end
187
69
 
188
70
  # Perform the work!
189
71
  #
190
- threads = []
191
- self.identifiers.keys.each do |host|
192
- thr = Thread.new do
193
- Thread.current.abort_on_exception = true
194
- opts = {
195
- host: host,
196
- port: self.port,
197
- community: self.community,
198
- timeout: self.timeout,
199
- retries: self.retries
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
- begin
203
- SNMP::Manager.open( opts ) do |snmp|
204
- case self.mode
205
- when :disk
206
- self.gather_disks( snmp, host )
207
- when :load
208
- self.gather_load( snmp, host )
209
- when :memory
210
- self.gather_free_memory( snmp, host )
211
- when :swap
212
- self.gather_swap( snmp, host )
213
- when :process
214
- self.gather_processlist( snmp, host )
215
- end
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
- # Wait for thread completion
231
- threads.map( &:join )
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
- results[ :mounts ] = mounts
324
- results[ :error ] = errors.join( ', ' ) unless errors.empty?
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
- ### Given a SNMP object, return a hash of:
363
- ###
364
- ### device path => percentage full
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.1.0.pre20161005111600
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
- HhcNMTYwNjI5MjMzMzI2WhcNMTcwNjI5MjMzMzI2WjA+MQ8wDQYDVQQDDAZtYWhs
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
- EU39m2ZKYqAAu71bwjWl5zEAk0aE4ojMLIMWpWE6IwCr9FZVpC1B+LyEboWFljId
28
- R0udISkfM+kQ3FRzmnwwQLaYJyhDPEWbQ1O6P6wHCaUQ23A1P++dZf8PWuZkS6Dn
29
- C1q7Zq4EAZEBLUSK69iPP4jCLjIp3YBQ88D1/egA+hkrR/19m236PvhhaM9FTgQv
30
- LtL61M3ZtlTanoXiNbXRXwRnODzvjFpQRiiBiazCDBYj8oYDsNj+qNw/iZlZlzw5
31
- F6uYXeS4YCZP453ZcpgZkXo3F5RheTrkdf04DMwUpQPMKog9QmRSTlCxzH69kivQ
32
- IfRp+58YwWwtAIQPZoY6Rg==
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: 2016-10-05 00:00:00.000000000 Z
34
+ date: 2018-04-04 00:00:00.000000000 Z
35
35
  dependencies:
36
36
  - !ruby/object:Gem::Dependency
37
- name: snmp
37
+ name: arborist
38
38
  requirement: !ruby/object:Gem::Requirement
39
39
  requirements:
40
40
  - - "~>"
41
41
  - !ruby/object:Gem::Version
42
- version: '1.2'
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.2'
49
+ version: '0.1'
50
50
  - !ruby/object:Gem::Dependency
51
- name: arborist
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: 1.3.1
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: Common SNMP support for Arborist
99
+ summary: SNMP support for Arborist monitors
97
100
  test_files: []
checksums.yaml.gz.sig DELETED
@@ -1,4 +0,0 @@
1
- �d���v�b���0L�:!���������ܷ�6�
2
- \U`\0ףc� x��r�8Y=�1� �o���݋���G�D�����e�J"�%�ع��/
3
- s˲"��z�˴Ķ���JҺ�1pU�֬��� �_b#�V"A
4
- !€��S�y*�/|�m���`�겶�/#pR�x���[`�'�N�b�m��'X��2�Ju�:��I�G�OS�����XS��e��rgr�˻4�|�Q�n�[۬�ÑU���
data.tar.gz.sig DELETED
Binary file
metadata.gz.sig DELETED
@@ -1,2 +0,0 @@
1
- ��U�ޥ�w�����$A5��ڻ�^|�ܕ���bM�:�*��QmA���1-b��,T�Ԑt݃]ٖ<¨��L@�[���!���0�ݸ�*�M�e@"ǔ&m{�9N�4�?����ŗS~��+�}�t���*� l���k��\���s �qlʈ������35&�*U>o"������
2
- *ׇ����)d}�!J�0Oʼn;[ꓦk�{� E*qa\E�.��&���BC�*�}������$ʇ���h"l�gb@��r/v