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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 833fc064ac6b821016f02ec2b427c7861cc4ebd2
4
- data.tar.gz: d14a89c993c13e874b9201388999bb77ac85c06f
2
+ SHA256:
3
+ metadata.gz: 1249bdfdca3585ad83e5257085356af249e49ddd5691d1e5b8cd77f792c81c9a
4
+ data.tar.gz: 3b85bd63b62caa5554ee76f3737556b07e970999cc7ddf0153fd51169e4b7e54
5
5
  SHA512:
6
- metadata.gz: be00731e69eae712e71e9881abb20ba9d1393de4acad424b8b2c7225b95d351e211a9c20551556f76fde34e59a551ff2716f9f220a247afa35b529b688301ca5
7
- data.tar.gz: bd6b4d1292adec637932fc8edccd62fe1db5fbaea2350995ab9c504e2eb002e36470ca7280742fb9b4eb231523fc8744bcb0d5135017bdf4bae8e23c8d5757bd
6
+ metadata.gz: 61a8a588eb87e99848c41c312b3eaa690a4c65ffb13d565266f89bdb850790822d5ea26132e69e26133f5bfae1eb5ed4e57c7bbd0f06c52d9d0dacc034c1d115
7
+ data.tar.gz: bfd1b4c4b1d7feb5c6b1726f4c73748d0e357a27983f69c8441a5882495d9d725dc04bab7550da22827fd1a7fe3316f703dc75e409fef7d048f5a9847aa243c3
@@ -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�SNKk v�2d6���L-�+T?�^�z��_��o�ݏ��7�b�d)��k
Binary file
@@ -3,7 +3,7 @@
3
3
  #encoding: utf-8
4
4
 
5
5
  require 'arborist/monitor' unless defined?( Arborist::Monitor )
6
- require 'net-snmp2'
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['config'] ]
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
- peername: host,
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
- @system = snmp.get( IDENTIFICATION_OID ).varbinds.first.value
100
- yield( host, snmp )
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: "Uncaught exception. (%s: %s)" % [ err.class.name, err.message ]
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 = self.cpu( snmp )
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.values.inject( :+ ).to_f / cpus.size
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 + 1 ]
104
- info[ :load ][ LOADKEYS[idx + 1] ] = value.to_f
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 ) - 1 ) * 100 ).round( 1 )
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: '1.3.6.1.4.1.2021.9.1.2',
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: '1.3.6.1.2.1.25.3.8.1.4'
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: '1.3.6.1.2.1.25.2.3.1.2',
36
- path: '1.3.6.1.2.1.25.2.3.1.3',
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: '1.3.6.1.2.1.25.2.3.1.6'
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(/.+)?$', '^/proc$', '^/run$', '^/sys/' ] do |val|
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
- return USED_PROPERTIES
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
- 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
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
- mounts.reject! do |path, percentage|
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
- 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
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
- self.log.debug "%s:%s -> at %d, warn at %d" % [ host, path, percentage, warn ]
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 percentage >= warn.to_i
127
- if percentage >= 100
128
- errors << "%s at %d%% capacity" % [ path, percentage ]
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, percentage ]
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
- raw = snmp.get_bulk([
155
- STORAGE_WINDOWS[:path],
156
- STORAGE_WINDOWS[:type],
157
- STORAGE_WINDOWS[:total],
158
- STORAGE_WINDOWS[:used]
159
- ]).varbinds.map( &:value )
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
- 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 )
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
- raw = snmp.get_bulk([
176
- STORAGE_NET_SNMP[:path],
177
- STORAGE_NET_SNMP[:percent] ]).varbinds.map( &:value )
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
- return Hash[ *raw ]
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] ).varbinds.first.value.to_f
152
- total = snmp.get( oids[:total] ).varbinds.first.value.to_f
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}" ).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
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['processes'] || self.class.check ).each do |process|
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
- process = "%s%s" % [ path, process ]
103
- process << " %s" % [ args ] unless args.empty?
104
- acc << process
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
- process << " %s" % [ args ] unless args.empty?
118
- acc << process
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,10 @@
1
+ # -*- ruby -*-
2
+ # vim: set noet nosta sw=4 ts=4 :
3
+
4
+ require 'arborist/monitor/snmp' unless defined?( Arborist::Monitor::SNMP )
5
+
6
+ # Namespace for UPS check classes.
7
+ class Arborist::Monitor::SNMP::UPS
8
+ include Arborist::Monitor::SNMP
9
+
10
+ end
@@ -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
+
@@ -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.4.0'
18
+ VERSION = '0.7.0'
18
19
 
19
20
  # Version control revision
20
- REVISION = %q$Revision: e0b7c95a154f $
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.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
- MIIDbDCCAlSgAwIBAgIBATANBgkqhkiG9w0BAQUFADA+MQ8wDQYDVQQDDAZtYWhs
14
+ MIIDMDCCAhigAwIBAgIBAjANBgkqhkiG9w0BAQsFADA+MQ8wDQYDVQQDDAZtYWhs
15
15
  b24xFzAVBgoJkiaJk/IsZAEZFgdtYXJ0aW5pMRIwEAYKCZImiZPyLGQBGRYCbnUw
16
- HhcNMTcxMTIyMjIyMTAyWhcNMTgxMTIyMjIyMTAyWjA+MQ8wDQYDVQQDDAZtYWhs
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
- AgMBAAGjdTBzMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQWBBRY8ea6
25
- +6kAaW7ukKph2/4MTAD8/TAcBgNVHREEFTATgRFtYWhsb25AbWFydGluaS5udTAc
26
- BgNVHRIEFTATgRFtYWhsb25AbWFydGluaS5udTANBgkqhkiG9w0BAQUFAAOCAQEA
27
- 00FljTeSNaYUqJ59yLRA+i43wVNiO4ETQQu6fYQCPns12Sm90spOJb3SmTDkJ7CY
28
- dixOQg5A3Et1LVS+Z/YfH1TAbb50oTWbZbTW2JknHS0hohq3UF1pvbkk1niZ26er
29
- skJ352MUfcyaUkQyMmCjL/BpkDutYH5OCGh+FmK8+mH7SoC9Nr48WwH2prVdHs3y
30
- OMWFgB33sXdj1XqOd2Rw1WPgAeMeDqWeIrRMpUhNZOwroaA1MAr60f9NIYxua/vx
31
- n0YyneERGuHPSRZFgo72tGOqLpAlWnhPxRNqnayZmsg3hPPI87B6MTUI2UQ7VUdh
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: 2018-04-04 00:00:00.000000000 Z
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: net-snmp2
50
+ name: netsnmp
52
51
  requirement: !ruby/object:Gem::Requirement
53
52
  requirements:
54
53
  - - "~>"
55
54
  - !ruby/object:Gem::Version
56
- version: '0.3'
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: '0.3'
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
- rubyforge_project:
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
@@ -0,0 +1 @@
1
+ ����ARl΂t�@�����"-Z��E6M�n�0�D�;�{�6��M�_O���