SNMP client: SNMP grab for network interface is optimized with a SNMP bulk command :)

This commit is contained in:
Nicolargo 2014-05-11 22:14:25 +02:00
parent 58e95d6ed3
commit 05e31d19ea
4 changed files with 125 additions and 60 deletions

View File

@ -48,7 +48,7 @@ class GlancesSNMPClient(object):
self.user = user
self.auth = auth
def __result__(self, errorIndication, errorStatus, errorIndex, varBinds):
def __get_result__(self, errorIndication, errorStatus, errorIndex, varBinds):
"""
Put results in table
"""
@ -64,7 +64,10 @@ class GlancesSNMPClient(object):
def get_by_oid(self, *oid):
"""
Process to an SNMP request (list of OID)
SNMP simple request (list of OID)
One request per OID list
* oid: oid list
> Return a dict
"""
if (self.version == '3'):
@ -79,4 +82,49 @@ class GlancesSNMPClient(object):
cmdgen.UdpTransportTarget((self.host, self.port)),
*oid
)
return self.__result__(errorIndication, errorStatus, errorIndex, varBinds)
return self.__get_result__(errorIndication, errorStatus, errorIndex, varBinds)
def __bulk_result__(self, errorIndication, errorStatus, errorIndex, varBindTable):
ret = []
if not (errorIndication or errorStatus):
for varBindTableRow in varBindTable:
item = {}
for name, val in varBindTableRow:
if (str(val) == ''):
item[name.prettyPrint()] = ''
else:
item[name.prettyPrint()] = val.prettyPrint()
ret.append(item)
return ret
def getbulk_by_oid(self, non_repeaters, max_repetitions, *oid):
"""
SNMP getbulk request
In contrast to snmpwalk, this information will typically be gathered in a
single transaction with the agent, rather than one transaction per variable found.
* non_repeaters: This specifies the number of supplied variables that should not be iterated over.
* max_repetitions: This specifies the maximum number of iterations over the repeating variables.
* oid: oid list
> Return a list of dicts
"""
if self.version.startswith('3'):
errorIndication, errorStatus, errorIndex, varBinds = self.cmdGen.getCmd(
cmdgen.UsmUserData(self.user, self.auth),
cmdgen.UdpTransportTarget((self.host, self.port)),
non_repeaters,
max_repetitions,
*oid
)
if self.version.startswith('2'):
errorIndication, errorStatus, errorIndex, varBindTable = self.cmdGen.bulkCmd(
cmdgen.CommunityData(self.community),
cmdgen.UdpTransportTarget((self.host, self.port)),
non_repeaters,
max_repetitions,
*oid
)
else:
# Bulk request are not available with SNMP version 1
return []
return self.__bulk_result__(errorIndication, errorStatus, errorIndex, varBindTable)

View File

@ -263,6 +263,6 @@ class GlancesStatsClientSNMP(GlancesStats):
try:
self._plugins[p].update(input='snmp')
except Exception as e:
# print "ERROR: Update %s failed (%s)" % (p, e)
pass
print "ERROR: Update %s failed (%s)" % (p, e)
# pass

View File

@ -29,11 +29,10 @@ from glances.plugins.glances_plugin import GlancesPlugin
# SNMP OID
# http://www.net-snmp.org/docs/mibs/interfaces.html
ifNumber_oid = { 'ifNumber': '1.3.6.1.2.1.2.1.0' }
ifIndex_oid = { 'ifIndex': '1.3.6.1.2.1.2.2.1.1.' }
snmp_oid = { 'interface_name': '1.3.6.1.2.1.2.2.1.2.',
'cumulative_rx': '1.3.6.1.2.1.2.2.1.10.',
'cumulative_tx': '1.3.6.1.2.1.2.2.1.16.' }
# Dict key = interface_name
snmp_oid = { 'interface_name': '1.3.6.1.2.1.2.2.1.2',
'cumulative_rx': '1.3.6.1.2.1.2.2.1.10',
'cumulative_tx': '1.3.6.1.2.1.2.2.1.16' }
class Plugin(GlancesPlugin):
@ -123,56 +122,53 @@ class Plugin(GlancesPlugin):
elif input == 'snmp':
# Update stats using SNMP
# !!! High CPU consumption on the client side
# !!! To be optimized with getbulk
time_since_update = getTimeSinceLastUpdate('net')
# Get number of network interfaces
try:
# IfNumber is available directly
ifNumber = int(self.set_stats_snmp(snmp_oid=ifNumber_oid)['ifNumber']) + 1
except:
# Or not...
# Walk through ifIndex
ifNumber = 1
while self.set_stats_snmp(snmp_oid={'ifIndex': ifIndex_oid['ifIndex'] + str(ifNumber)})['ifIndex'] != '':
ifNumber += 1
# SNMP bulk command to get all network interface in one shot
netiocounters = self.set_stats_snmp(snmp_oid=snmp_oid, bulk=True)
# Loop over network interfaces
network_new = {}
ifIndex = 1
ifCpt = 1
while (ifCpt < ifNumber) and (ifIndex < 1024):
# Add interface index to netif OID
net_oid = dict((k, v + str(ifIndex)) for (k, v) in snmp_oid.items())
net_stat = self.set_stats_snmp(snmp_oid=net_oid)
if str(net_stat['interface_name']) == '':
ifIndex += 1
continue
else:
ifCpt += 1
network_new[ifIndex] = net_stat
if hasattr(self, 'network_old'):
net_stat['time_since_update'] = time_since_update
net_stat['rx'] = (float(network_new[ifIndex]['cumulative_rx']) -
float(self.network_old[ifIndex]['cumulative_rx']))
net_stat['tx'] = (float(network_new[ifIndex]['cumulative_tx']) -
float(self.network_old[ifIndex]['cumulative_tx']))
net_stat['cumulative_cx'] = (float(net_stat['cumulative_rx']) +
float(net_stat['cumulative_tx']))
net_stat['cx'] = net_stat['rx'] + net_stat['tx']
self.stats.append(net_stat)
ifIndex += 1
# Save stats to compute next bitrate
self.network_old = network_new
# Previous network interface stats are stored in the network_old variable
if not hasattr(self, 'network_old'):
# First call, we init the network_old var
try:
self.network_old = netiocounters
except (IOError, UnboundLocalError):
pass
else:
# See description in the 'local' block
time_since_update = getTimeSinceLastUpdate('net')
# Loop over interfaces
network_new = netiocounters
for net in network_new:
try:
# Try necessary to manage dynamic network interface
netstat = {}
netstat['interface_name'] = net
netstat['time_since_update'] = time_since_update
netstat['cumulative_rx'] = float(network_new[net]['cumulative_rx'])
netstat['rx'] = (float(network_new[net]['cumulative_rx']) -
float(self.network_old[net]['cumulative_rx']))
netstat['cumulative_tx'] = float(network_new[net]['cumulative_tx'])
netstat['tx'] = (float(network_new[net]['cumulative_tx']) -
float(self.network_old[net]['cumulative_tx']))
netstat['cumulative_cx'] = (netstat['cumulative_rx'] +
netstat['cumulative_tx'])
netstat['cx'] = netstat['rx'] + netstat['tx']
except KeyError:
continue
else:
self.stats.append(netstat)
# Save stats to compute next bitrate
self.network_old = network_new
return self.stats
def msg_curse(self, args=None):
"""
Return the dict to display in the curse interface
Return the dict to displayoid in the curse interface
"""
#!!! TODO: Add alert on network interface bitrate

View File

@ -59,10 +59,9 @@ class GlancesPlugin(object):
self.stats = input_stats
return self.stats
def set_stats_snmp(self, snmp_oid={}):
def set_stats_snmp(self, bulk=False, snmp_oid={}):
# Update stats using SNMP
# TODO: optimisation with bulk command:
# http://pysnmp.sourceforge.net/examples/4.x/v3arch/oneliner/manager/bulkgen.html
# If bulk=True, use a bulk request instead of a get request
from glances.core.glances_snmp import GlancesSNMPClient
# Init the SNMP request
@ -72,12 +71,34 @@ class GlancesPlugin(object):
community=self.args.snmp_community)
# Process the SNMP request
snmpresult = clientsnmp.get_by_oid(*snmp_oid.values())
# Build the internal dict with the SNMP result
ret = {}
for key in snmp_oid.iterkeys():
ret[key] = snmpresult[snmp_oid[key]]
if bulk:
# Bulk request
snmpresult = clientsnmp.getbulk_by_oid(0, 10, *snmp_oid.values())
# Build the internal dict with the SNMP result
# key is the first item in the snmp_oid
index = 1
for item in snmpresult:
item_stats = {}
item_key = None
for key in snmp_oid.iterkeys():
oid = snmp_oid[key] + '.' + str(index)
if oid in item:
if item_key is None:
item_key = item[oid]
else:
item_stats[key] = item[oid]
if item_stats != {}:
ret[item_key] = item_stats
index += 1
else:
# Simple get request
snmpresult = clientsnmp.get_by_oid(*snmp_oid.values())
# Build the internal dict with the SNMP result
for key in snmp_oid.iterkeys():
ret[key] = snmpresult[snmp_oid[key]]
return ret