arborist-snmp 0.4.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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���