#! /usr/bin/python -u
#
# Copyright (c) 2021 NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
#
# This software product is a proprietary product of Nvidia Corporation
# and its affiliates (the "Company") and all right, title, and interest
# in and to the software product, including all associated intellectual
# property rights, are and shall remain exclusively with the Company.
#
# This software product is governed by the End User License Agreement
# provided with the software product.
# All Rights reserved.

# This program is a Net-SNMP pass persist script that services the
# Resource Query MIB that's been defined by Cumulus Networks.

import subprocess
import syslog
import snmp_passpersist as snmp

sysSpecificCounters = 2
oid_base = '.1.3.6.1.4.1.40310.%s' % sysSpecificCounters
# change the debug to test without snmpd
already_logged = False

def get_portlist():
    """
    This returns a list of tuples with as (ifindex, ifname)
    """
    portList = []
    try:
        # easy way to get a list of physical interfaces
        iplink = subprocess.check_output((['/sbin/ip','-o', 'link', 'show']),
                                         shell=False).splitlines()
    except:
        # if we have a problem, just create an empty list
        if not already_logged:
            syslog.syslog('Error: ip link show error is EXCEPTION=%s' % e)
            already_logged = True
        portList = []

    # make a list of tuples with (ifindex, ifname)
    for l in iplink:
        oline = l.split(':')
        ifindex = int(oline[0].strip())
        ifname = oline[1].strip()
        if not ifname.startswith("swp") or "@" in ifname or "." in ifname:
            continue
        portList.append((ifindex, ifname))

    portList.sort()
    return portList

def update():
    """
    Reads latest values for counters from the system using
    ethtool and updates the cached values.
    """
    # update the interface names with counters
    # port_data is an array indexed by the port index given by ip link show
    # it contains port_data[indexCounter]['portName'] and
    # port_data[indexCounter][ethtool counter name]
    global already_logged
    portDataList = []
    # portList is already sorted
    for ifindex, ifname in portList:
        try:
            ethtool_lines = subprocess.check_output((['/sbin/ethtool','-S', ifname]),
                                                    stderr=subprocess.STDOUT,
                                                    shell=False).splitlines()
        except BaseException as e:
            if not already_logged:
                syslog.syslog('Error: ethtool EXCEPTION=%s' % e)
                already_logged = True
            ethtool_lines = []

        # parse the ethtool output and store it
        statsDict = {}
        for l in ethtool_lines:
            (k, v) = l.split(':')
            statsDict[k.strip()] = v.strip()
        portDataList.append((ifindex, ifname, statsDict))

    # Note: these tables were upgraded to 64 bits and 32 bit counters are now obsolete
    # in the mib.
    # loop over the discardClCounters discards table
    # then the then interfaceClCounters counters table
    try:
        for entryList,counter,counterTable,counterEntry,counterNameList in allTableLists:
            for entry in entryList:
                entryName = counterNameList[entry-1]
                for ifindex,ifname,statsDict in portDataList:
                    if entryName == 'ifname':
                        pp.add_str("%s.%s.%s.%s.%s" % \
                                   (counter, counterTable, counterEntry, entry,
                                    ifindex), ifname)
                    else:
                        pp.add_cnt_64bit("%s.%s.%s.%s.%s" % \
                                   (counter, counterTable, counterEntry, entry,
                                    ifindex), statsDict.get(entryName, 0))
    except Exception as e:
        syslog.syslog('Error: Exception=%s' % e)

##################################################
discardCounters = 1            # 1.3.6.1.4.1.40310.2.1
discardClCountersTable = 2     #                     1.2
discardClCountersEntry = 1     #                     1.2.1

clPortName = 1                 #                     1.2.1.1
clL3v4InDiscards = 2           #                     1.2.1.2
clBufferOverflowDiscards = 3   #                     1.2.1.3
clL3AclDiscards = 4            #                     1.2.1.4
clL3v4BlackholeDiscards = 5    #                     1.2.1.5
clEgressQOverflowDiscards = 6  #                     1.2.1.6
clEgressNonQDiscards = 7       #                     1.2.1.7
discardTableList = [clPortName, clL3v4InDiscards, clBufferOverflowDiscards,
                    clL3AclDiscards, clL3v4BlackholeDiscards, clEgressQOverflowDiscards,
                    clEgressNonQDiscards]
discardNameList = ['ifname', 'HwIfInL3Drops','HwIfInBufferDrops','HwIfInAclDrops', None,
                   'HwIfOutQDrops','HwIfOutNonQDrops']

interfaceCounters = 2          # 1.3.6.1.4.1.40310.2.2
interfaceClCountersTable = 2   #                     2.2

interfaceClCountersEntry = 1   #                     2.2.1
clIntPortName = 1              #                     2.2.1.1
clIntInOctets = 2              #                     2.2.1.2
clIntInUcastPkts = 3           #                     2.2.1.3
clIntInBcastPkts = 4           #                     2.2.1.4
clIntInMcastPkts = 5           #                     2.2.1.5
clIntOutOctets = 6             #                     2.2.1.6
clIntOutUcastPkts = 7          #                     2.2.1.7
clIntOutBcastPkts = 8          #                     2.2.1.8
clIntOutMcastPkts = 9          #                     2.2.1.9
interfaceTableList = [clIntPortName,
                      clIntInOctets, clIntInUcastPkts, clIntInBcastPkts, clIntInMcastPkts,
                      clIntOutOctets, clIntOutUcastPkts, clIntOutBcastPkts, clIntOutMcastPkts]
interfaceNameList = ['ifname', 'HwIfInOctets', 'HwIfInUcastPkts',
                     'HwIfInBcastPkts', 'HwIfInMcastPkts',
                     'HwIfOutOctets','HwIfOutUcastPkts',
                     'HwIfOutBcastPkts','HwIfOutMcastPkts']

pfcClCountersTable = 3        # 1.3.6.1.4.1.40310.2.2.3

pfcClCountersEntry = 1        # 1.3.6.1.4.1.40310.2.2.3.1

clIntPfcPortName   = 1
clIntInPausePkt    = 2
clIntOutPausePkt   = 3
clIntInPfc0Pkt     = 4
clIntOutPfc0Pkt    = 5
clIntInPfc1Pkt     = 6
clIntOutPfc1Pkt    = 7
clIntInPfc2Pkt     = 8
clIntOutPfc2Pkt    = 9
clIntInPfc3Pkt     = 10
clIntOutPfc3Pkt    = 11
clIntInPfc4Pkt     = 12
clIntOutPfc4Pkt    = 13
clIntInPfc5Pkt     = 14
clIntOutPfc5Pkt    = 15
clIntInPfc6Pkt     = 16
clIntOutPfc6Pkt    = 17
clIntInPfc7Pkt     = 18
clIntOutPfc7Pkt    = 19

pfcTableList = [clIntPfcPortName, clIntInPausePkt, clIntOutPausePkt,
                clIntInPfc0Pkt, clIntOutPfc0Pkt, clIntInPfc1Pkt, clIntOutPfc1Pkt,
                clIntInPfc2Pkt, clIntOutPfc2Pkt, clIntInPfc3Pkt, clIntOutPfc3Pkt,
                clIntInPfc4Pkt, clIntOutPfc4Pkt, clIntInPfc5Pkt, clIntOutPfc5Pkt,
                clIntInPfc6Pkt, clIntOutPfc6Pkt, clIntInPfc7Pkt, clIntOutPfc7Pkt]
pfcNameList = ['ifname','HwIfInPausePkt', 'HwIfOutPausePkt',
               'HwIfInPfc0Pkt', 'HwIfOutPfc0Pkt', 'HwIfInPfc1Pkt',
               'HwIfOutPfc1Pkt', 'HwIfInPfc2Pkt', 'HwIfOutPfc2Pkt',
               'HwIfInPfc3Pkt', 'HwIfOutPfc3Pkt', 'HwIfInPfc4Pkt',
               'HwIfOutPfc4Pkt', 'HwIfInPfc5Pkt', 'HwIfOutPfc5Pkt',
               'HwIfInPfc6Pkt', 'HwIfOutPfc6Pkt', 'HwIfInPfc7Pkt',
               'HwIfOutPfc7Pkt']

# one list to rule them all
allTableLists = [[discardTableList, discardCounters, discardClCountersTable,
                  discardClCountersEntry, discardNameList],
                 [interfaceTableList, interfaceCounters, interfaceClCountersTable,
                  interfaceClCountersEntry, interfaceNameList],
                 [pfcTableList , interfaceCounters, pfcClCountersTable,
                  pfcClCountersEntry, pfcNameList]]
# grab the interface list from a reliable source
portList = get_portlist()
pp = snmp.PassPersist(oid_base)
pp.debug = False
if pp.debug:
    # just run the update script once and print debugs to stdout
    update()
else:
    pp.start(update, 30)
