1 # -*- ruby -*- |
1 # -*- ruby -*- |
2 # vim: set noet nosta sw=4 ts=4 : |
2 # vim: set noet nosta sw=4 ts=4 : |
3 |
3 |
4 require 'arborist/monitor/snmp' unless defined?( Arborist::Monitor::SNMP ) |
4 require 'arborist/monitor/snmp' unless defined?( Arborist::Monitor::SNMP ) |
5 |
5 |
6 # SNMP memory availability checks. |
6 # SNMP memory and swap utilization checks. |
7 # Returns total available memory in Kb to the 'available_memory' attribute. |
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. |
8 # |
15 # |
9 class Arborist::Monitor::SNMP::Memory |
16 class Arborist::Monitor::SNMP::Memory |
10 include Arborist::Monitor::SNMP |
17 include Arborist::Monitor::SNMP |
11 |
18 |
12 extend Loggability |
19 extend Configurability, Loggability |
13 log_to :arborist |
20 log_to :arborist_snmp |
14 |
21 |
15 # OIDS for discovering memory usage. |
22 # OIDS for discovering memory usage. |
16 # |
23 # |
17 MEMORY = { |
24 MEMORY = { |
18 mem_avail: '1.3.6.1.4.1.2021.4.6.0' |
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' |
19 } |
40 } |
20 |
41 |
21 # Global defaults for instances of this monitor |
42 # Global defaults for instances of this monitor |
22 # |
43 # |
23 DEFAULT_OPTIONS = { |
44 configurability( 'arborist.snmp.memory' ) do |
24 error_at: 95, # in percent full |
45 # What memory usage percentage qualifies as a warning |
25 } |
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 |
26 |
51 |
27 |
52 |
28 ### This monitor is complex enough to require creating an instance from the caller. |
53 ### Return the properties used by this monitor. |
29 ### Provide a friendlier error message the class was provided to exec() directly. |
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. |
30 ### |
62 ### |
31 def self::run( nodes ) |
63 def self::run( nodes ) |
32 return new.run( nodes ) |
64 return new.run( nodes ) |
33 end |
65 end |
34 |
66 |
35 |
67 |
36 ### Create a new instance of this monitor. |
|
37 ### |
|
38 def initialize( options=DEFAULT_OPTIONS ) |
|
39 options = DEFAULT_OPTIONS.merge( options || {} ) |
|
40 options.each do |name, value| |
|
41 self.public_send( "#{name.to_s}=", value ) |
|
42 end |
|
43 end |
|
44 |
|
45 # Set an error if memory used is below this many kilobytes. |
|
46 attr_accessor :error_at |
|
47 |
|
48 |
|
49 ### Perform the monitoring checks. |
68 ### Perform the monitoring checks. |
50 ### |
69 ### |
51 def run( nodes ) |
70 def run( nodes ) |
52 super do |snmp, host| |
71 super do |host, snmp| |
53 self.gather_free_memory( snmp, host ) |
72 self.gather_memory( host, snmp ) |
54 end |
73 end |
55 end |
74 end |
56 |
75 |
57 |
76 |
58 ######### |
77 ######### |
60 ######### |
79 ######### |
61 |
80 |
62 ### Collect available memory information for +host+ from an existing |
81 ### Collect available memory information for +host+ from an existing |
63 ### (and open) +snmp+ connection. |
82 ### (and open) +snmp+ connection. |
64 ### |
83 ### |
65 def gather_free_memory( snmp, host ) |
84 def gather_memory( host, snmp ) |
66 self.log.debug "Getting available memory for: %s" % [ host ] |
85 info = self.system =~ /windows\s+/i ? self.get_windows( snmp ) : self.get_mem( snmp ) |
67 mem_avail = snmp.get( SNMP::ObjectId.new( MEMORY[:mem_avail] ) ).varbind_list.first.value.to_f |
|
68 self.log.debug " Available memory on %s: %0.2f" % [ host, mem_avail ] |
|
69 |
86 |
70 config = @identifiers[ host ].last || {} |
87 config = identifiers[ host ].last || {} |
71 error_at = config['error_at'] || self.error_at |
88 physical_warn_at = config[ 'physical_warn_at' ] || self.class.physical_warn_at |
72 if mem_avail <= error_at |
89 swap_warn_at = config[ 'swap_warn_at' ] || self.class.swap_warn_at |
73 @results[ host ] = { |
90 |
74 error: "Available memory is under %0.1fMB" % [ error_at.to_f / 1024 ], |
91 self.log.debug "Memory data on %s: %p" % [ host, info ] |
75 available_memory: mem_avail |
92 memory, swap = info[:memory], info[:swap] |
76 } |
93 self.results[ host ] = { memory: memory, swap: swap } |
77 else |
94 |
78 @results[ host ] = { available_memory: mem_avail } |
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 ] |
79 end |
109 end |
80 end |
110 end |
81 |
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 |
82 end # class Arborist::Monitor::SNMP::Memory |
179 end # class Arborist::Monitor::SNMP::Memory |
83 |
180 |