High performance python based SNMP poller

In my last post, I wrote a basic SNMP poller using python easysnmp module. To make it more practical and industry ready I have further enriched it with following:

  • Read SNMPv2 and SNMPv3 devices from CSV file.
  • Based on the field count in device record use SNMPv2 or SNMPv3 module.
  • Poll thousands of devices by running parallel processes (default 50).
  • Insert network stats in influxDB database using authenticated REST calls to influxDB (Read previous post for more).

Here is our sample device CSV with mix of SNMPv2 and SNMPv3 devices.


192.168.0.20;snmpv2_comm_string  <-- SNMP V2 device
192.168.0.4;security_username;auth_password;privacy_password   <-- SNMP V3 device

And here is the python code that reads above CSV and then runs parallel pollers to get network stats from devices. Depending upon number of devices and resources you can tune number parallel processes (default 50).


#!/usr/bin/python3
from multiprocessing import Pool
from easysnmp import Session
import requests
from requests.auth import HTTPBasicAuth
import datetime

DeviceFile = "devices.csv"
#Read all devices in a list
DeviceList = [line.rstrip('\n') for line in open(DeviceFile)]

# InfluxDB URI
dburi='http://influxdb_server:8086/write?db=poll_data_db'
# To convert readings in MBits
factor = 1000000

def SNMPpollv2(DeviceParams):
      hostname, community = DeviceParams.split(";")
      try:
          session = Session(hostname=hostname, community=community, version=2)
          return(pollDevice(session, hostname))
      except:
          return("ERROR - SNMPv2 error")

def SNMPpollv3(DeviceParams):
     hostname, security_username, auth_password, privacy_password = DeviceParams.split(";")
     try:
         session = Session(hostname=hostname, version=3, security_level="auth_with_privacy", security_username=security_username, auth_protocol="SHA", auth_password=auth_password, privacy_protocol="AES", privacy_password=privacy_password)
         return(pollDevice(session, hostname))
     except:
         return("ERROR - SNMPv3 error")

def pollDevice(session, hostname):
      outDict = {}
      inDict = {}
      inErrDict = {}
      outErrDict = {}
      nameDict = {}
      IFstats = {}
      hostData = {"Host": hostname}

      try:
         for item in session.walk(oids=u'1.3.6.1.2.1.31.1.1.1.10'):
            outDict[item.oid_index] = {"Out": item.value}
      except:
         return({"Host": hostname, "Error": "ERROR - SNMP error"})

      for item in session.walk(oids=u'1.3.6.1.2.1.31.1.1.1.6'):
         inDict[item.oid_index] = {"In": item.value}

      for item in session.walk(oids=u'1.3.6.1.2.1.2.2.1.14'):
         inErrDict[item.oid_index] = {"inError": item.value}

      for item in session.walk(oids=u'1.3.6.1.2.1.2.2.1.20'):
         outErrDict[item.oid_index] = {"outError": item.value}

      for item in session.walk(oids=u'1.3.6.1.2.1.2.2.1.2'):
         nameDict[item.oid_index] = {"Interface": item.value}

      for index in outDict.keys():
         IFName = nameDict[index].get('Interface')
         IFOut = int(outDict[index].get('Out')) / factor
         IFIn = int(inDict[index].get('In')) /factor
         IFinErr = int(inErrDict[index].get('inError')) / factor
         IFoutErr = int(outErrDict[index].get('outError')) / factor
         IFstats[IFName] = {"IFOut": IFOut, "IFIn": IFIn, "IFinError": IFinErr, "IFoutError": IFoutErr}

      hostData['IFstats'] = IFstats
      DeviceIP = hostData['Host']
      #print(hostData['IFstats'].keys())
      TimeStamp = datetime.datetime.now().isoformat()
      for iface in hostData['IFstats'].keys():
         IFOut, IFIn, IFinError, IFoutError = hostData['IFstats'][iface]['IFOut'], hostData['IFstats'][iface]['IFIn'], hostData['IFstats'][iface]['IFinError'], hostData['IFstats'][iface]['IFoutError']
         iface = iface.split(" ")[-1]
         print("{};{};{};{};{};{};{}".format(TimeStamp, DeviceIP, iface, IFOut, IFIn, IFinError, IFoutError))
         # my_poll_data is measurement, hostname and ifname are tags
         dbpayload = "my_poll_data,hostname={},ifname={} ifout={},ifin={},ifinerr={},ifouterr={}".format(DeviceIP, iface, IFOut, IFIn, IFinError, IFoutError)
         dbres=requests.post(dburi, data=dbpayload, auth=HTTPBasicAuth('influxdb_user', 'influxdb_pass'))
         #print(dbres.status_code)

      return(hostData)

def StartPoll(device):
    if len(device.split(";")) == 4:
        return(SNMPpollv3(device))
    elif len(device.split(";")) == 2:
        return(SNMPpollv2(device))
    else:
        return("Invalid device entity")

#Start 50 pollers, you can tune this number depending on number of devices and resources
with Pool(50) as p:
    p.map(StartPoll, DeviceList)

Output directly goes into influxDB like:


> use poll_data_db
Using database my_poll_data_db

> select * from my_poll_data limit 5

name: my_poll_data
time                hostname     ifin          ifinerr ifname     ifout         ifouterr
----                --------     ----          ------- ------     -----         --------
1581574955890329609 192.168.0.4 0             0       ETH-1-4-10 0             0
1581574955908883568 192.168.0.4 0             0       ETH-1-4-10 0             0
1581574955926601405 192.168.0.4 0             0       ETH-1-4-10 0             0
1581574955933837394 192.168.0.20 251795.283794 0       XAUI-1-4-4 294671.125993 0.000267
1581574955935290812 192.168.0.20 251795.283794 0       XAUI-1-4-4 294671.125993 0.000267