arborist-snmp 0.4.0 → 0.7.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 +5 -5
- checksums.yaml.gz.sig +1 -0
- data.tar.gz.sig +0 -0
- data/lib/arborist/monitor/snmp.rb +17 -21
- data/lib/arborist/monitor/snmp/cpu.rb +11 -19
- data/lib/arborist/monitor/snmp/disk.rb +80 -38
- data/lib/arborist/monitor/snmp/memory.rb +7 -7
- data/lib/arborist/monitor/snmp/process.rb +29 -13
- data/lib/arborist/monitor/snmp/ups.rb +10 -0
- data/lib/arborist/monitor/snmp/ups/battery.rb +136 -0
- data/lib/arborist/snmp.rb +3 -2
- metadata +32 -18
- metadata.gz.sig +1 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1249bdfdca3585ad83e5257085356af249e49ddd5691d1e5b8cd77f792c81c9a
|
4
|
+
data.tar.gz: 3b85bd63b62caa5554ee76f3737556b07e970999cc7ddf0153fd51169e4b7e54
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 61a8a588eb87e99848c41c312b3eaa690a4c65ffb13d565266f89bdb850790822d5ea26132e69e26133f5bfae1eb5ed4e57c7bbd0f06c52d9d0dacc034c1d115
|
7
|
+
data.tar.gz: bfd1b4c4b1d7feb5c6b1726f4c73748d0e357a27983f69c8441a5882495d9d725dc04bab7550da22827fd1a7fe3316f703dc75e409fef7d048f5a9847aa243c3
|
checksums.yaml.gz.sig
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
)� ���1��v�Fe��W�o*���O�Q��{4��@��"ԅ�|���!Tڱ���t&@V0�?jm_�P�`����{[�Z��u�q/y��Y���=����K�N�L�J���5�]ː6�\Nω�Z�SNKkv�2d6���L-�+T?�^�z��_��o�ݏ��7�b�d)��k
|
data.tar.gz.sig
ADDED
Binary file
|
@@ -3,7 +3,7 @@
|
|
3
3
|
#encoding: utf-8
|
4
4
|
|
5
5
|
require 'arborist/monitor' unless defined?( Arborist::Monitor )
|
6
|
-
require '
|
6
|
+
require 'netsnmp'
|
7
7
|
|
8
8
|
# SNMP checks for Arborist. Requires an SNMP agent to be installed
|
9
9
|
# on target machine, and the various "pieces" enabled for your platform.
|
@@ -38,13 +38,6 @@ module Arborist::Monitor::SNMP
|
|
38
38
|
setting :batchsize, default: 25
|
39
39
|
end
|
40
40
|
|
41
|
-
# Indicate to FFI that we're using threads.
|
42
|
-
Net::SNMP.thread_safe = true
|
43
|
-
|
44
|
-
|
45
|
-
# The system type, as advertised.
|
46
|
-
attr_reader :system
|
47
|
-
|
48
41
|
# The mapping of addresses back to node identifiers.
|
49
42
|
attr_reader :identifiers
|
50
43
|
|
@@ -64,7 +57,7 @@ module Arborist::Monitor::SNMP
|
|
64
57
|
nodes.each_pair do |(identifier, props)|
|
65
58
|
next unless props.key?( 'addresses' )
|
66
59
|
address = props[ 'addresses' ].first
|
67
|
-
self.identifiers[ address ] = [ identifier, props
|
60
|
+
self.identifiers[ address ] = [ identifier, props ]
|
68
61
|
end
|
69
62
|
|
70
63
|
# Perform the work!
|
@@ -86,7 +79,7 @@ module Arborist::Monitor::SNMP
|
|
86
79
|
thr = Thread.new do
|
87
80
|
config = self.identifiers[ host ].last || {}
|
88
81
|
opts = {
|
89
|
-
|
82
|
+
host: host,
|
90
83
|
port: config[ 'port' ] || Arborist::Monitor::SNMP.port,
|
91
84
|
version: config[ 'version' ] || Arborist::Monitor::SNMP.version,
|
92
85
|
community: config[ 'community' ] || Arborist::Monitor::SNMP.community,
|
@@ -94,22 +87,17 @@ module Arborist::Monitor::SNMP
|
|
94
87
|
retries: config[ 'retries' ] || Arborist::Monitor::SNMP.retries
|
95
88
|
}
|
96
89
|
|
97
|
-
snmp = Net::SNMP::Session.open( opts )
|
98
90
|
begin
|
99
|
-
|
100
|
-
|
91
|
+
NETSNMP::Client.new( opts ) do |snmp|
|
92
|
+
Thread.current[ :system ] = snmp.get( oid: IDENTIFICATION_OID )
|
93
|
+
yield( host, snmp )
|
94
|
+
end
|
101
95
|
|
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
96
|
rescue => err
|
97
|
+
self.log.error "%s: %s\n%s" % [ host, err.message, err.backtrace.join("\n ") ]
|
108
98
|
self.results[ host ] = {
|
109
|
-
error: "
|
99
|
+
error: "Exception (%s: %s)" % [ err.class.name, err.message ]
|
110
100
|
}
|
111
|
-
ensure
|
112
|
-
snmp.close
|
113
101
|
end
|
114
102
|
end
|
115
103
|
|
@@ -135,10 +123,18 @@ module Arborist::Monitor::SNMP
|
|
135
123
|
@results = {}
|
136
124
|
end
|
137
125
|
|
126
|
+
|
127
|
+
### Return the current SNMP connection system string.
|
128
|
+
def system
|
129
|
+
return Thread.current[ :system ]
|
130
|
+
end
|
131
|
+
|
138
132
|
end # Arborist::Monitor::SNMP
|
139
133
|
|
140
134
|
require 'arborist/monitor/snmp/cpu'
|
141
135
|
require 'arborist/monitor/snmp/disk'
|
142
136
|
require 'arborist/monitor/snmp/process'
|
143
137
|
require 'arborist/monitor/snmp/memory'
|
138
|
+
require 'arborist/monitor/snmp/ups'
|
139
|
+
require 'arborist/monitor/snmp/ups/battery'
|
144
140
|
|
@@ -24,11 +24,7 @@ class Arborist::Monitor::SNMP::CPU
|
|
24
24
|
# When walking load OIDS, the iterator count matches
|
25
25
|
# these labels.
|
26
26
|
#
|
27
|
-
LOADKEYS =
|
28
|
-
1 => :load1,
|
29
|
-
2 => :load5,
|
30
|
-
3 => :load15
|
31
|
-
}
|
27
|
+
LOADKEYS = %i[ load1 load5 load15 ]
|
32
28
|
|
33
29
|
|
34
30
|
# Global defaults for instances of this monitor
|
@@ -66,18 +62,13 @@ class Arborist::Monitor::SNMP::CPU
|
|
66
62
|
protected
|
67
63
|
#########
|
68
64
|
|
69
|
-
### Return system CPU data.
|
70
|
-
###
|
71
|
-
def cpu( snmp )
|
72
|
-
return snmp.walk( OIDS[:cpu] )
|
73
|
-
end
|
74
|
-
|
75
|
-
|
76
65
|
### Find load data, add additional niceties for reporting.
|
77
66
|
###
|
78
67
|
def format_load( snmp )
|
79
68
|
info = { cpu: {}, load: {} }
|
80
|
-
cpus =
|
69
|
+
cpus = snmp.walk( oid: OIDS[:cpu] ).each_with_object( [] ) do |(_, value), acc|
|
70
|
+
acc << value
|
71
|
+
end
|
81
72
|
|
82
73
|
info[ :cpu ][ :count ] = cpus.size
|
83
74
|
|
@@ -91,20 +82,21 @@ class Arborist::Monitor::SNMP::CPU
|
|
91
82
|
# alert after X events" pragmas.
|
92
83
|
#
|
93
84
|
if self.system =~ /windows\s+/i
|
94
|
-
info[ :cpu ][ :usage ] = cpus.
|
85
|
+
info[ :cpu ][ :usage ] = cpus.inject( :+ ).to_f / cpus.size
|
95
86
|
info[ :message ] = "System is %0.1f%% in use." % [ info[ :cpu ][ :usage ] ]
|
96
87
|
|
88
|
+
|
97
89
|
# UCDavis stuff is better for alerting only after there has been
|
98
90
|
# an extended load event. Use the 5 minute average to avoid
|
99
91
|
# state changes on transient spikes.
|
100
92
|
#
|
101
93
|
else
|
102
|
-
snmp.walk( OIDS[:load] ).each_with_index do |(_, value), idx|
|
103
|
-
next unless LOADKEYS[ idx
|
104
|
-
info[ :load ][ LOADKEYS[idx
|
94
|
+
snmp.walk( oid: OIDS[:load] ).each_with_index do |(_, value), idx|
|
95
|
+
next unless LOADKEYS[ idx ]
|
96
|
+
info[ :load ][ LOADKEYS[idx] ] = value.to_f
|
105
97
|
end
|
106
98
|
|
107
|
-
percentage = (( ( info[:load][ :load5 ] / cpus.size
|
99
|
+
percentage = (( ( info[:load][ :load5 ] / cpus.size) - 1 ) * 100 ).round( 1 )
|
108
100
|
|
109
101
|
if percentage < 0
|
110
102
|
info[ :message ] = "System is %0.1f%% idle." % [ percentage.abs ]
|
@@ -125,7 +117,7 @@ class Arborist::Monitor::SNMP::CPU
|
|
125
117
|
def find_load( host, snmp )
|
126
118
|
info = self.format_load( snmp )
|
127
119
|
|
128
|
-
config = identifiers[ host ].last || {}
|
120
|
+
config = self.identifiers[ host ].last['config'] || {}
|
129
121
|
warn_at = config[ 'warn_at' ] || self.class.warn_at
|
130
122
|
usage = info.dig( :cpu, :usage ) || 0
|
131
123
|
|
@@ -17,9 +17,10 @@ class Arborist::Monitor::SNMP::Disk
|
|
17
17
|
# OIDS required to pull disk information from net-snmp.
|
18
18
|
#
|
19
19
|
STORAGE_NET_SNMP = {
|
20
|
-
path:
|
20
|
+
path: '1.3.6.1.4.1.2021.9.1.2',
|
21
21
|
percent: '1.3.6.1.4.1.2021.9.1.9',
|
22
|
-
type:
|
22
|
+
type: '1.3.6.1.2.1.25.3.8.1.4',
|
23
|
+
access: '1.3.6.1.2.1.25.3.8.1.5'
|
23
24
|
}
|
24
25
|
|
25
26
|
# The OID that matches a local windows hard disk.
|
@@ -32,15 +33,21 @@ class Arborist::Monitor::SNMP::Disk
|
|
32
33
|
# OIDS required to pull disk information from Windows.
|
33
34
|
#
|
34
35
|
STORAGE_WINDOWS = {
|
35
|
-
type:
|
36
|
-
path:
|
36
|
+
type: '1.3.6.1.2.1.25.2.3.1.2',
|
37
|
+
path: '1.3.6.1.2.1.25.2.3.1.3',
|
37
38
|
total: '1.3.6.1.2.1.25.2.3.1.5',
|
38
|
-
used:
|
39
|
+
used: '1.3.6.1.2.1.25.2.3.1.6'
|
39
40
|
}
|
40
41
|
|
41
42
|
# The fallback warning capacity.
|
42
43
|
WARN_AT = 90
|
43
44
|
|
45
|
+
# Don't alert if a mount is readonly by default.
|
46
|
+
ALERT_READONLY = false
|
47
|
+
|
48
|
+
# Access mode meanings
|
49
|
+
ACCESS_READWRITE = 1
|
50
|
+
ACCESS_READONLY = 2
|
44
51
|
|
45
52
|
# Configurability API
|
46
53
|
#
|
@@ -48,6 +55,9 @@ class Arborist::Monitor::SNMP::Disk
|
|
48
55
|
# What percentage qualifies as a warning
|
49
56
|
setting :warn_at, default: WARN_AT
|
50
57
|
|
58
|
+
# Set down if the mounts are readonly?
|
59
|
+
setting :alert_readonly, default: ALERT_READONLY
|
60
|
+
|
51
61
|
# If non-empty, only these paths are included in checks.
|
52
62
|
#
|
53
63
|
setting :include do |val|
|
@@ -60,7 +70,7 @@ class Arborist::Monitor::SNMP::Disk
|
|
60
70
|
# Paths to exclude from checks
|
61
71
|
#
|
62
72
|
setting :exclude,
|
63
|
-
default: [ '^/dev(/.+)?$', '^/net(/.+)?$', '
|
73
|
+
default: [ '^/dev(/.+)?$', '/dev$', '^/net(/.+)?$', '/proc$', '^/run$', '^/sys/', '/sys$' ] do |val|
|
64
74
|
mounts = Array( val ).map{|m| Regexp.new(m) }
|
65
75
|
Regexp.union( mounts )
|
66
76
|
end
|
@@ -70,7 +80,9 @@ class Arborist::Monitor::SNMP::Disk
|
|
70
80
|
### Return the properties used by this monitor.
|
71
81
|
###
|
72
82
|
def self::node_properties
|
73
|
-
|
83
|
+
used_properties = USED_PROPERTIES.dup
|
84
|
+
used_properties << :mounts
|
85
|
+
return used_properties
|
74
86
|
end
|
75
87
|
|
76
88
|
|
@@ -98,40 +110,54 @@ class Arborist::Monitor::SNMP::Disk
|
|
98
110
|
### +snmp+ connection.
|
99
111
|
###
|
100
112
|
def gather_disks( host, snmp )
|
101
|
-
|
102
|
-
config
|
103
|
-
warn_at
|
113
|
+
current_mounts = self.system =~ /windows\s+/i ? self.windows_disks( snmp ) : self.unix_disks( snmp )
|
114
|
+
config = self.identifiers[ host ].last['config'] || {}
|
115
|
+
warn_at = config[ 'warn_at' ] || self.class.warn_at
|
116
|
+
alert_readonly = config[ 'alert_readonly' ] || self.class.alert_readonly
|
117
|
+
|
118
|
+
self.log.debug self.identifiers[ host ]
|
104
119
|
|
105
120
|
includes = self.format_mounts( config, 'include' ) || self.class.include
|
106
121
|
excludes = self.format_mounts( config, 'exclude' ) || self.class.exclude
|
107
122
|
|
108
|
-
|
123
|
+
current_mounts.reject! do |path, data|
|
124
|
+
path = path.to_s
|
109
125
|
excludes.match( path ) || ( includes && ! includes.match( path ) )
|
110
126
|
end
|
111
127
|
|
112
128
|
errors = []
|
113
129
|
warnings = []
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
else
|
120
|
-
warn_at
|
121
|
-
end
|
130
|
+
current_mounts.each_pair do |path, data|
|
131
|
+
warn = if warn_at.is_a?( Hash )
|
132
|
+
warn_at[ path ] || self.class.warn_at
|
133
|
+
else
|
134
|
+
warn_at
|
122
135
|
end
|
123
136
|
|
124
|
-
|
137
|
+
readonly = alert_readonly.is_a?( Hash ) ? alert_readonly[ path ] : alert_readonly
|
138
|
+
|
139
|
+
self.log.debug "%s:%s -> %p, warn at %d" % [ host, path, data, warn ]
|
125
140
|
|
126
|
-
if
|
127
|
-
if
|
128
|
-
errors << "%s at %d%% capacity" % [ path,
|
141
|
+
if data[ :capacity ] >= warn.to_i
|
142
|
+
if data[ :capacity ] >= 100
|
143
|
+
errors << "%s at %d%% capacity" % [ path, data[ :capacity ] ]
|
129
144
|
else
|
130
|
-
warnings << "%s at %d%% capacity" % [ path,
|
145
|
+
warnings << "%s at %d%% capacity" % [ path, data[ :capacity ] ]
|
131
146
|
end
|
132
147
|
end
|
148
|
+
|
149
|
+
if readonly && data[ :accessmode ] == ACCESS_READONLY
|
150
|
+
errors << "%s is mounted read-only." % [ path ]
|
151
|
+
end
|
133
152
|
end
|
134
153
|
|
154
|
+
# Remove any past mounts that configuration exclusions should
|
155
|
+
# now omit.
|
156
|
+
mounts = self.identifiers[ host ].last[ 'mounts' ] || {}
|
157
|
+
mounts.keys.each{|k| mounts[k] = nil }
|
158
|
+
|
159
|
+
mounts.merge!( current_mounts )
|
160
|
+
|
135
161
|
self.results[ host ] = { mounts: mounts }
|
136
162
|
self.results[ host ][ :error ] = errors.join(', ') unless errors.empty?
|
137
163
|
self.results[ host ][ :warning ] = warnings.join(', ') unless warnings.empty?
|
@@ -151,18 +177,25 @@ class Arborist::Monitor::SNMP::Disk
|
|
151
177
|
### Fetch information for Windows systems.
|
152
178
|
###
|
153
179
|
def windows_disks( snmp )
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
180
|
+
paths = snmp.walk( oid: STORAGE_WINDOWS[:path] ).each_with_object( [] ) do |(_, value), acc|
|
181
|
+
acc << value
|
182
|
+
end
|
183
|
+
types = snmp.walk( oid: STORAGE_WINDOWS[:type] ).each_with_object( [] ) do |(_, value), acc|
|
184
|
+
acc << WINDOWS_DEVICES.include?( value )
|
185
|
+
end
|
186
|
+
totals = snmp.walk( oid: STORAGE_WINDOWS[:total] ).each_with_object( [] ) do |(_, value), acc|
|
187
|
+
acc << value
|
188
|
+
end
|
189
|
+
used = snmp.walk( oid: STORAGE_WINDOWS[:used] ).each_with_object( [] ) do |(_, value), acc|
|
190
|
+
acc << value
|
191
|
+
end
|
160
192
|
|
161
193
|
disks = {}
|
162
|
-
|
163
|
-
next
|
164
|
-
next
|
165
|
-
disks[
|
194
|
+
paths.each_with_index do |path, idx|
|
195
|
+
next if totals[ idx ].zero?
|
196
|
+
next unless types[ idx ]
|
197
|
+
disks[ path ] ||= {}
|
198
|
+
disks[ path ][ :capacity ] = (( used[idx].to_f / totals[idx] ) * 100).round( 1 )
|
166
199
|
end
|
167
200
|
|
168
201
|
return disks
|
@@ -172,11 +205,20 @@ class Arborist::Monitor::SNMP::Disk
|
|
172
205
|
### Fetch information for Unix/MacOS systems.
|
173
206
|
###
|
174
207
|
def unix_disks( snmp )
|
175
|
-
|
176
|
-
|
177
|
-
|
208
|
+
paths = snmp.walk( oid: STORAGE_NET_SNMP[:path] ).each_with_object( [] ) do |(_, value), acc|
|
209
|
+
acc << value
|
210
|
+
end
|
211
|
+
capacities = snmp.walk( oid: STORAGE_NET_SNMP[:percent] ).each_with_object( [] ) do |(_, value), acc|
|
212
|
+
acc << value
|
213
|
+
end
|
214
|
+
accessmodes = snmp.walk( oid: STORAGE_NET_SNMP[:access] ).each_with_object( [] ) do |(_, value), acc|
|
215
|
+
acc << value
|
216
|
+
end
|
178
217
|
|
179
|
-
|
218
|
+
pairs = paths.each_with_object( {} ).with_index do |(p, acc), idx|
|
219
|
+
acc[p] = { capacity: capacities[idx], accessmode: accessmodes[idx] }
|
220
|
+
end
|
221
|
+
return pairs
|
180
222
|
end
|
181
223
|
|
182
224
|
end # class Arborist::Monitor::SNMP::Disk
|
@@ -84,7 +84,7 @@ class Arborist::Monitor::SNMP::Memory
|
|
84
84
|
def gather_memory( host, snmp )
|
85
85
|
info = self.system =~ /windows\s+/i ? self.get_windows( snmp ) : self.get_mem( snmp )
|
86
86
|
|
87
|
-
config = identifiers[ host ].last || {}
|
87
|
+
config = self.identifiers[ host ].last['config'] || {}
|
88
88
|
physical_warn_at = config[ 'physical_warn_at' ] || self.class.physical_warn_at
|
89
89
|
swap_warn_at = config[ 'swap_warn_at' ] || self.class.swap_warn_at
|
90
90
|
|
@@ -131,7 +131,7 @@ class Arborist::Monitor::SNMP::Memory
|
|
131
131
|
info = { memory: {}, swap: {} }
|
132
132
|
mem_idx, swap_idx = nil
|
133
133
|
|
134
|
-
snmp.walk( MEMORY[:windows][:label] ).each_with_index do |(_, val), i|
|
134
|
+
snmp.walk( oid: MEMORY[:windows][:label] ).each_with_index do |(_, val), i|
|
135
135
|
mem_idx = i + 1 if val =~ /physical memory/i
|
136
136
|
swap_idx = i + 1 if val =~ /virtual memory/i
|
137
137
|
end
|
@@ -148,8 +148,8 @@ class Arborist::Monitor::SNMP::Memory
|
|
148
148
|
###
|
149
149
|
def calc_memory( snmp, oids )
|
150
150
|
info = { usage: 0, available: 0 }
|
151
|
-
avail = snmp.get( oids[:avail] ).
|
152
|
-
total = snmp.get( oids[:total] ).
|
151
|
+
avail = snmp.get( oid: oids[:avail] ).to_f
|
152
|
+
total = snmp.get( oid: oids[:total] ).to_f
|
153
153
|
used = total - avail
|
154
154
|
|
155
155
|
return info if avail.zero?
|
@@ -166,9 +166,9 @@ class Arborist::Monitor::SNMP::Memory
|
|
166
166
|
info = { usage: 0, available: 0 }
|
167
167
|
return info unless idx
|
168
168
|
|
169
|
-
units = snmp.get( MEMORY[:windows][:units] + ".#{idx}" )
|
170
|
-
total = snmp.get( MEMORY[:windows][:total] + ".#{idx}" ).
|
171
|
-
used = snmp.get( MEMORY[:windows][:used] + ".#{idx}" ).
|
169
|
+
units = snmp.get( oid: MEMORY[:windows][:units] + ".#{idx}" )
|
170
|
+
total = snmp.get( oid: MEMORY[:windows][:total] + ".#{idx}" ).to_f * units
|
171
|
+
used = snmp.get( oid: MEMORY[:windows][:used] + ".#{idx}" ).to_f * units
|
172
172
|
|
173
173
|
info[ :usage ] = (( used / total ) * 100 ).round( 2 )
|
174
174
|
info[ :available ] = (( total - used ) / 1024 / 1024 ).round( 2 )
|
@@ -71,7 +71,7 @@ class Arborist::Monitor::SNMP::Process
|
|
71
71
|
#### +snmp+ connection.
|
72
72
|
###
|
73
73
|
def gather_processlist( host, snmp )
|
74
|
-
config = self.identifiers[ host ].last || {}
|
74
|
+
config = self.identifiers[ host ].last['config'] || {}
|
75
75
|
errors = []
|
76
76
|
procs = self.system =~ /windows\s+/i ? self.get_windows( snmp ) : self.get_procs( snmp )
|
77
77
|
|
@@ -80,7 +80,7 @@ class Arborist::Monitor::SNMP::Process
|
|
80
80
|
|
81
81
|
# Check against what is running.
|
82
82
|
#
|
83
|
-
Array( config['
|
83
|
+
Array( config['check'] || self.class.check ).each do |process|
|
84
84
|
process_r = Regexp.new( process )
|
85
85
|
found = procs.find{|p| p.match(process_r) }
|
86
86
|
errors << "'%s' is not running" % [ process ] unless found
|
@@ -95,14 +95,24 @@ class Arborist::Monitor::SNMP::Process
|
|
95
95
|
###
|
96
96
|
def get_windows( snmp )
|
97
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
98
|
|
102
|
-
|
103
|
-
|
104
|
-
|
99
|
+
paths = snmp.walk( oid: oids[0] ).each_with_object( [] ) do |(_, value), acc|
|
100
|
+
acc << value
|
101
|
+
end
|
102
|
+
procs = snmp.walk( oid: oids[1] ).each_with_object( [] ) do |(_, value), acc|
|
103
|
+
acc << value
|
104
|
+
end
|
105
|
+
args = snmp.walk( oid: oids[2] ).each_with_object( [] ) do |(_, value), acc|
|
106
|
+
acc << value
|
105
107
|
end
|
108
|
+
|
109
|
+
return paths.zip( procs, args ).collect do |(path, process, arg)|
|
110
|
+
next unless path && process
|
111
|
+
next if path.empty?
|
112
|
+
path << process unless process.empty?
|
113
|
+
path << " %s" % [ arg.to_s ] if arg && ! arg.empty?
|
114
|
+
path
|
115
|
+
end.compact
|
106
116
|
end
|
107
117
|
|
108
118
|
|
@@ -110,13 +120,19 @@ class Arborist::Monitor::SNMP::Process
|
|
110
120
|
###
|
111
121
|
def get_procs( snmp )
|
112
122
|
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
123
|
|
117
|
-
|
118
|
-
acc <<
|
124
|
+
procs = snmp.walk( oid: oids.first ).each_with_object( [] ) do |(_, value), acc|
|
125
|
+
acc << value
|
126
|
+
end
|
127
|
+
args = snmp.walk( oid: oids.last ).each_with_object( [] ) do |(_, value), acc|
|
128
|
+
acc << value
|
119
129
|
end
|
130
|
+
|
131
|
+
return procs.zip( args ).collect do |(process, arg)|
|
132
|
+
next if process.empty?
|
133
|
+
process << " %s" % [ arg.to_s ] unless arg.empty?
|
134
|
+
process
|
135
|
+
end.compact
|
120
136
|
end
|
121
137
|
|
122
138
|
end # class Arborist::Monitor::SNMP::Process
|
@@ -0,0 +1,136 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
# vim: set noet nosta sw=4 ts=4 :
|
3
|
+
|
4
|
+
require 'arborist/monitor/snmp/ups' unless defined?( Arborist::Monitor::SNMP::UPS )
|
5
|
+
|
6
|
+
# Checks for UPS battery health.
|
7
|
+
#
|
8
|
+
# Checks the available battery percentage, if the UPS is on battery,
|
9
|
+
# and the temperature of the battery.
|
10
|
+
#
|
11
|
+
class Arborist::Monitor::SNMP::UPS::Battery
|
12
|
+
include Arborist::Monitor::SNMP
|
13
|
+
|
14
|
+
extend Configurability, Loggability
|
15
|
+
log_to :arborist_snmp
|
16
|
+
|
17
|
+
# OIDS for discovering ups status.
|
18
|
+
#
|
19
|
+
OIDS = {
|
20
|
+
battery_status: '.1.3.6.1.2.1.33.1.2.1.0', # 1 - unk, 2 - normal, 3 - low, 4 - depleted
|
21
|
+
seconds_on_battery: '.1.3.6.1.2.1.33.1.2.2.0',
|
22
|
+
est_minutes_remaining: '.1.3.6.1.2.1.33.1.2.3.0',
|
23
|
+
est_charge_remaining: '.1.3.6.1.2.1.33.1.2.4.0', # in percent
|
24
|
+
battery_voltage: '.1.3.6.1.2.1.33.1.2.5.0', # in 0.1v DC
|
25
|
+
battery_current: '.1.3.6.1.2.1.33.1.2.6.0', # in 0.1a DC
|
26
|
+
battery_temperature: '.1.3.6.1.2.1.33.1.2.7.0' # in Celcius
|
27
|
+
}
|
28
|
+
|
29
|
+
# Human-readable translations for battery status OID.
|
30
|
+
#
|
31
|
+
BATTERY_STATUS = {
|
32
|
+
1 => "Battery status is Unknown.",
|
33
|
+
2 => "Battery is OK.",
|
34
|
+
3 => "Battery is Low.",
|
35
|
+
4 => "Battery is Depleted."
|
36
|
+
}
|
37
|
+
|
38
|
+
# Global defaults for instances of this monitor
|
39
|
+
#
|
40
|
+
configurability( 'arborist.snmp.ups.battery' ) do
|
41
|
+
# What battery percentage qualifies as a warning
|
42
|
+
setting :capacity_warn_at, default: 60
|
43
|
+
|
44
|
+
# What battery temperature qualifies as a warning, in C
|
45
|
+
setting :temperature_warn_at, default: 50
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
### Return the properties used by this monitor.
|
50
|
+
###
|
51
|
+
def self::node_properties
|
52
|
+
return USED_PROPERTIES
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
### Class #run creates a new instance and immediately runs it.
|
57
|
+
###
|
58
|
+
def self::run( nodes )
|
59
|
+
return new.run( nodes )
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
### Perform the monitoring checks.
|
64
|
+
###
|
65
|
+
def run( nodes )
|
66
|
+
super do |host, snmp|
|
67
|
+
self.check_battery( host, snmp )
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
#########
|
73
|
+
protected
|
74
|
+
#########
|
75
|
+
|
76
|
+
### Query SNMP and format information into a hash.
|
77
|
+
###
|
78
|
+
def format_battery( snmp )
|
79
|
+
info = {}
|
80
|
+
|
81
|
+
# basic info that's always available
|
82
|
+
info[ :status ] = snmp.get( oid: OIDS[:battery_status] )
|
83
|
+
info[ :capacity ] = snmp.get( oid: OIDS[:est_charge_remaining] ) rescue nil
|
84
|
+
info[ :temperature ] = snmp.get( oid: OIDS[:battery_temperature] ) rescue nil
|
85
|
+
info[ :minutes_remaining ] = snmp.get( oid: OIDS[:est_minutes_remaining] ) rescue nil
|
86
|
+
|
87
|
+
# don't report voltage if the UPS doesn't
|
88
|
+
voltage = snmp.get( oid: OIDS[:battery_voltage] ) rescue nil
|
89
|
+
info[ :voltage ] = voltage / 10 if voltage
|
90
|
+
|
91
|
+
# don't report current if the UPS doesn't
|
92
|
+
current = snmp.get( oid: OIDS[:battery_current] ) rescue nil
|
93
|
+
info[ :current ] = current/10 if current
|
94
|
+
|
95
|
+
# see if we are on battery
|
96
|
+
info[ :seconds_on_battery ] = snmp.get( oid: OIDS[:seconds_on_battery] ) rescue 0
|
97
|
+
info[ :in_use ] = ( info[ :seconds_on_battery ] != 0 )
|
98
|
+
|
99
|
+
return { battery: info.compact }
|
100
|
+
end
|
101
|
+
|
102
|
+
### Parse SNMP-provided information and alert based on thresholds.
|
103
|
+
###
|
104
|
+
def check_battery( host, snmp )
|
105
|
+
info = self.format_battery( snmp )
|
106
|
+
|
107
|
+
config = self.identifiers[ host ].last['config'] || {}
|
108
|
+
cap_warn = config[ 'capacity_warn_at' ] || self.class.capacity_warn_at
|
109
|
+
temp_warn = config[ 'temperature_warn_at' ] || self.class.temperature_warn_at
|
110
|
+
|
111
|
+
in_use = info.dig( :battery, :in_use )
|
112
|
+
status = info.dig( :battery, :status )
|
113
|
+
capacity = info.dig( :battery, :capacity )
|
114
|
+
temperature = info.dig( :battery, :temperature )
|
115
|
+
warnings = []
|
116
|
+
|
117
|
+
if in_use
|
118
|
+
mins = info.dig( :battery, :minutes_remaining ) || "(unknown)"
|
119
|
+
warnings << "UPS on battery - %s minute(s) remaning." % [ mins ]
|
120
|
+
end
|
121
|
+
|
122
|
+
warnings << BATTERY_STATUS[ status ] if status != 2
|
123
|
+
|
124
|
+
warnings << "Battery remaining capacity %0.1f%% less than %0.1f percent" %
|
125
|
+
[ capacity, cap_warn ] if capacity && capacity <= cap_warn
|
126
|
+
|
127
|
+
warnings << "Battery temperature %dC greater than %dC" %
|
128
|
+
[ temperature, temp_warn ] if temperature && temperature >= temp_warn
|
129
|
+
|
130
|
+
info[ :warning ] = warnings.join( "\n" ) unless warnings.empty?
|
131
|
+
self.results[ host ] = info
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
end # class Arborist::Monitor::UPS::Battery
|
136
|
+
|
data/lib/arborist/snmp.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# -*- ruby -*-
|
2
2
|
#encoding: utf-8
|
3
|
+
# vim: set noet nosta sw=4 ts=4 :
|
3
4
|
|
4
5
|
require 'loggability'
|
5
6
|
require 'arborist'
|
@@ -14,10 +15,10 @@ module Arborist::SNMP
|
|
14
15
|
|
15
16
|
|
16
17
|
# Package version
|
17
|
-
VERSION = '0.
|
18
|
+
VERSION = '0.7.0'
|
18
19
|
|
19
20
|
# Version control revision
|
20
|
-
REVISION = %q$Revision
|
21
|
+
REVISION = %q$Revision$
|
21
22
|
|
22
23
|
|
23
24
|
### Return the name of the library with the version, and optionally the build ID if
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: arborist-snmp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mahlon E. Smith <mahlon@martini.nu>
|
@@ -11,9 +11,9 @@ bindir: bin
|
|
11
11
|
cert_chain:
|
12
12
|
- |
|
13
13
|
-----BEGIN CERTIFICATE-----
|
14
|
-
|
14
|
+
MIIDMDCCAhigAwIBAgIBAjANBgkqhkiG9w0BAQsFADA+MQ8wDQYDVQQDDAZtYWhs
|
15
15
|
b24xFzAVBgoJkiaJk/IsZAEZFgdtYXJ0aW5pMRIwEAYKCZImiZPyLGQBGRYCbnUw
|
16
|
-
|
16
|
+
HhcNMjAwNzA5MjIxMzE4WhcNMjEwNzA5MjIxMzE4WjA+MQ8wDQYDVQQDDAZtYWhs
|
17
17
|
b24xFzAVBgoJkiaJk/IsZAEZFgdtYXJ0aW5pMRIwEAYKCZImiZPyLGQBGRYCbnUw
|
18
18
|
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDpXGN0YbMVpYv4EoiCxpQw
|
19
19
|
sxKdyhlkvpvENUkpEhbpnEuMKXgUfRHO4T/vBZf0h8eYgwnrHCRhAeIqesFKfoj9
|
@@ -21,17 +21,16 @@ cert_chain:
|
|
21
21
|
Fht9ppCuNmxJNd+L3zAX8lD01RUWNRC+8L5QLCjViJtjFDDCFfh9NCirs+XnTCzo
|
22
22
|
AJgFbsZIzFJtSiXUtFgscKr4Ik8ruhRbPbYbmx9rf6W74aTMPxggq/d3gj0Eh32y
|
23
23
|
WsXsQ5giVnmkbsRkBNu3QyZ8Xr5+7mvy5AWyqXKOrcW7lnYaob6Z9x/MGXGNeD6j
|
24
|
-
|
25
|
-
+6kAaW7ukKph2/4MTAD8/
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
UrSf3b+cPoC8PNfjp8zsdw==
|
24
|
+
AgMBAAGjOTA3MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQWBBRY8ea6
|
25
|
+
+6kAaW7ukKph2/4MTAD8/TANBgkqhkiG9w0BAQsFAAOCAQEAryZehbiEumHsUsce
|
26
|
+
FoBVuDoVdlJf8ae11G8IPXnEHCT8S5b+MBSYd55V3aGQm4bKoZA3GmmD8Y0a+oxt
|
27
|
+
l2kkTvE0r1bBb/qYQI97AjnqzHByseBRoaHtJ12JtrDEdi8y4jd5AJt4aW+G/roD
|
28
|
+
I2/ymUodKw9Cteom0RdJNzBUJ+Bq+qFOy7mVKNIfhXRRFYEy11y1712FsJXqUEku
|
29
|
+
qr3lnAEvEy9hQila4NoJT2+aQEKsjVON9D3a727naKDFUcKDg6P4KqS+yOKgR+QH
|
30
|
+
D8llK3JPaqKXuJkbd8jKchDk0Q+fA8Klan5SSnm7pMD51QM1mPsVPm5bEw5ib0bn
|
31
|
+
oR3hTQ==
|
33
32
|
-----END CERTIFICATE-----
|
34
|
-
date:
|
33
|
+
date: 2020-08-01 00:00:00.000000000 Z
|
35
34
|
dependencies:
|
36
35
|
- !ruby/object:Gem::Dependency
|
37
36
|
name: arborist
|
@@ -48,19 +47,33 @@ dependencies:
|
|
48
47
|
- !ruby/object:Gem::Version
|
49
48
|
version: '0.1'
|
50
49
|
- !ruby/object:Gem::Dependency
|
51
|
-
name:
|
50
|
+
name: netsnmp
|
52
51
|
requirement: !ruby/object:Gem::Requirement
|
53
52
|
requirements:
|
54
53
|
- - "~>"
|
55
54
|
- !ruby/object:Gem::Version
|
56
|
-
version: '0.
|
55
|
+
version: '0.1'
|
56
|
+
type: :runtime
|
57
|
+
prerelease: false
|
58
|
+
version_requirements: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - "~>"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0.1'
|
63
|
+
- !ruby/object:Gem::Dependency
|
64
|
+
name: xorcist
|
65
|
+
requirement: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '1.1'
|
57
70
|
type: :runtime
|
58
71
|
prerelease: false
|
59
72
|
version_requirements: !ruby/object:Gem::Requirement
|
60
73
|
requirements:
|
61
74
|
- - "~>"
|
62
75
|
- !ruby/object:Gem::Version
|
63
|
-
version: '
|
76
|
+
version: '1.1'
|
64
77
|
description: "\tThis library adds common SNMP resource support to Arborist monitors.\n"
|
65
78
|
email: mahlon@martini.nu
|
66
79
|
executables: []
|
@@ -72,6 +85,8 @@ files:
|
|
72
85
|
- lib/arborist/monitor/snmp/disk.rb
|
73
86
|
- lib/arborist/monitor/snmp/memory.rb
|
74
87
|
- lib/arborist/monitor/snmp/process.rb
|
88
|
+
- lib/arborist/monitor/snmp/ups.rb
|
89
|
+
- lib/arborist/monitor/snmp/ups/battery.rb
|
75
90
|
- lib/arborist/snmp.rb
|
76
91
|
homepage: http://bitbucket.org/mahlon/Arborist-SNMP
|
77
92
|
licenses:
|
@@ -92,8 +107,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
92
107
|
- !ruby/object:Gem::Version
|
93
108
|
version: '0'
|
94
109
|
requirements: []
|
95
|
-
|
96
|
-
rubygems_version: 2.5.1
|
110
|
+
rubygems_version: 3.1.2
|
97
111
|
signing_key:
|
98
112
|
specification_version: 4
|
99
113
|
summary: SNMP support for Arborist monitors
|
metadata.gz.sig
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
����ARlt�@�����"-Z��E6M�n�0�D�;�{�6��M�_O���
|