lib/arborist/monitor/snmp/memory.rb
changeset 8 e0b7c95a154f
parent 4 e6eb11b1e00d
child 14 d5cb8bd33170
equal deleted inserted replaced
7:4548e58c8c66 8:e0b7c95a154f
     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