Pulled in latest vpp_stats.py from upstream after https://gerrit.fd.io/r/c/vpp/+/35640
This commit is contained in:
@ -49,9 +49,7 @@ class MyAgent(agentx.Agent):
|
|||||||
|
|
||||||
self.logger.info("Connecting to VPP Stats Segment")
|
self.logger.info("Connecting to VPP Stats Segment")
|
||||||
vppstat = VPPStats(socketname='/run/vpp/stats.sock', timeout=2)
|
vppstat = VPPStats(socketname='/run/vpp/stats.sock', timeout=2)
|
||||||
if not vppstat.connect():
|
vppstat.connect()
|
||||||
self.logger.error("Can't connect to VPP Stats API, bailing")
|
|
||||||
return False
|
|
||||||
|
|
||||||
vpp = VPPApi(clientname='vpp-snmp-agent')
|
vpp = VPPApi(clientname='vpp-snmp-agent')
|
||||||
if not vpp.connect():
|
if not vpp.connect():
|
||||||
|
160
vppstats.py
160
vppstats.py
@ -40,26 +40,32 @@ from struct import Struct
|
|||||||
import time
|
import time
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
def recv_fd(sock):
|
||||||
|
'''Get file descriptor for memory map'''
|
||||||
|
fds = array.array("i") # Array of ints
|
||||||
|
_, ancdata, _, _ = sock.recvmsg(0, socket.CMSG_LEN(4))
|
||||||
|
for cmsg_level, cmsg_type, cmsg_data in ancdata:
|
||||||
|
if cmsg_level == socket.SOL_SOCKET and cmsg_type == socket.SCM_RIGHTS:
|
||||||
|
fds.frombytes(cmsg_data[:len(cmsg_data) - (len(cmsg_data) % fds.itemsize)])
|
||||||
|
return list(fds)[0]
|
||||||
|
|
||||||
VEC_LEN_FMT = Struct('I')
|
VEC_LEN_FMT = Struct('I')
|
||||||
|
|
||||||
|
|
||||||
def get_vec_len(stats, vector_offset):
|
def get_vec_len(stats, vector_offset):
|
||||||
'''Equivalent to VPP vec_len()'''
|
'''Equivalent to VPP vec_len()'''
|
||||||
return VEC_LEN_FMT.unpack_from(stats.statseg, vector_offset - 8)[0]
|
return VEC_LEN_FMT.unpack_from(stats.statseg, vector_offset - 8)[0]
|
||||||
|
|
||||||
|
|
||||||
def get_string(stats, ptr):
|
def get_string(stats, ptr):
|
||||||
'''Get a string from a VPP vector'''
|
'''Get a string from a VPP vector'''
|
||||||
namevector = ptr - stats.base
|
namevector = ptr - stats.base
|
||||||
namevectorlen = get_vec_len(stats, namevector)
|
namevectorlen = get_vec_len(stats, namevector)
|
||||||
if namevector + namevectorlen >= stats.size:
|
if namevector + namevectorlen >= stats.size:
|
||||||
raise IOError('String overruns stats segment')
|
raise IOError('String overruns stats segment')
|
||||||
return stats.statseg[namevector:namevector + namevectorlen -
|
return stats.statseg[namevector:namevector+namevectorlen-1].decode('ascii')
|
||||||
1].decode('ascii')
|
|
||||||
|
|
||||||
|
|
||||||
class StatsVector:
|
class StatsVector:
|
||||||
'''A class representing a VPP vector'''
|
'''A class representing a VPP vector'''
|
||||||
|
|
||||||
def __init__(self, stats, ptr, fmt):
|
def __init__(self, stats, ptr, fmt):
|
||||||
self.vec_start = ptr - stats.base
|
self.vec_start = ptr - stats.base
|
||||||
self.vec_len = get_vec_len(stats, ptr - stats.base)
|
self.vec_len = get_vec_len(stats, ptr - stats.base)
|
||||||
@ -74,8 +80,7 @@ class StatsVector:
|
|||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
with self.stats.lock:
|
with self.stats.lock:
|
||||||
return self.struct.iter_unpack(
|
return self.struct.iter_unpack(self.statseg[self.vec_start:self.vec_start +
|
||||||
self.statseg[self.vec_start:self.vec_start +
|
|
||||||
self.elementsize*self.vec_len])
|
self.elementsize*self.vec_len])
|
||||||
|
|
||||||
def __getitem__(self, index):
|
def __getitem__(self, index):
|
||||||
@ -83,12 +88,10 @@ class StatsVector:
|
|||||||
raise IOError('Index beyond end of vector')
|
raise IOError('Index beyond end of vector')
|
||||||
with self.stats.lock:
|
with self.stats.lock:
|
||||||
if self.fmtlen == 1:
|
if self.fmtlen == 1:
|
||||||
return self.struct.unpack_from(
|
return self.struct.unpack_from(self.statseg, self.vec_start +
|
||||||
self.statseg,
|
(index * self.elementsize))[0]
|
||||||
self.vec_start + (index * self.elementsize))[0]
|
return self.struct.unpack_from(self.statseg, self.vec_start +
|
||||||
return self.struct.unpack_from(
|
(index * self.elementsize))
|
||||||
self.statseg, self.vec_start + (index * self.elementsize))
|
|
||||||
|
|
||||||
|
|
||||||
class VPPStats():
|
class VPPStats():
|
||||||
'''Main class implementing Python access to the VPP statistics segment'''
|
'''Main class implementing Python access to the VPP statistics segment'''
|
||||||
@ -104,43 +107,29 @@ class VPPStats():
|
|||||||
self.connected = False
|
self.connected = False
|
||||||
self.size = 0
|
self.size = 0
|
||||||
self.last_epoch = 0
|
self.last_epoch = 0
|
||||||
self.error_vectors = 0
|
|
||||||
self.statseg = 0
|
self.statseg = 0
|
||||||
|
|
||||||
def connect(self):
|
def connect(self):
|
||||||
'''Connect to stats segment'''
|
'''Connect to stats segment'''
|
||||||
if self.connected:
|
if self.connected:
|
||||||
return True
|
return
|
||||||
try:
|
|
||||||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET)
|
sock = socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET)
|
||||||
sock.connect(self.socketname)
|
sock.connect(self.socketname)
|
||||||
except:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Get file descriptor for memory map
|
mfd = recv_fd(sock)
|
||||||
fds = array.array("i") # Array of ints
|
|
||||||
_, ancdata, _, _ = sock.recvmsg(0, socket.CMSG_LEN(4))
|
|
||||||
for cmsg_level, cmsg_type, cmsg_data in ancdata:
|
|
||||||
if cmsg_level == socket.SOL_SOCKET and cmsg_type == socket.SCM_RIGHTS:
|
|
||||||
fds.frombytes(cmsg_data[:len(cmsg_data) -
|
|
||||||
(len(cmsg_data) % fds.itemsize)])
|
|
||||||
mfd = list(fds)[0]
|
|
||||||
sock.close()
|
sock.close()
|
||||||
|
|
||||||
stat_result = os.fstat(mfd)
|
stat_result = os.fstat(mfd)
|
||||||
self.statseg = mmap.mmap(mfd, stat_result.st_size, mmap.PROT_READ,
|
self.statseg = mmap.mmap(mfd, stat_result.st_size, mmap.PROT_READ, mmap.MAP_SHARED)
|
||||||
mmap.MAP_SHARED)
|
|
||||||
os.close(mfd)
|
os.close(mfd)
|
||||||
|
|
||||||
self.size = stat_result.st_size
|
self.size = stat_result.st_size
|
||||||
if self.version != 2:
|
if self.version != 2:
|
||||||
raise Exception('Incompatbile stat segment version {}'.format(
|
raise Exception('Incompatbile stat segment version {}'
|
||||||
self.version))
|
.format(self.version))
|
||||||
return False
|
|
||||||
|
|
||||||
self.refresh()
|
self.refresh()
|
||||||
self.connected = True
|
self.connected = True
|
||||||
return True
|
|
||||||
|
|
||||||
def disconnect(self):
|
def disconnect(self):
|
||||||
'''Disconnect from stats segment'''
|
'''Disconnect from stats segment'''
|
||||||
@ -173,11 +162,6 @@ class VPPStats():
|
|||||||
'''Get pointer of directory vector'''
|
'''Get pointer of directory vector'''
|
||||||
return self.shared_headerfmt.unpack_from(self.statseg)[4]
|
return self.shared_headerfmt.unpack_from(self.statseg)[4]
|
||||||
|
|
||||||
@property
|
|
||||||
def error_vector(self):
|
|
||||||
'''Get pointer of error vector'''
|
|
||||||
return self.shared_headerfmt.unpack_from(self.statseg)[5]
|
|
||||||
|
|
||||||
elementfmt = 'IQ128s'
|
elementfmt = 'IQ128s'
|
||||||
|
|
||||||
def refresh(self, blocking=True):
|
def refresh(self, blocking=True):
|
||||||
@ -188,21 +172,13 @@ class VPPStats():
|
|||||||
try:
|
try:
|
||||||
with self.lock:
|
with self.lock:
|
||||||
self.last_epoch = self.epoch
|
self.last_epoch = self.epoch
|
||||||
for i, direntry in enumerate(
|
for i, direntry in enumerate(StatsVector(self, self.directory_vector, self.elementfmt)):
|
||||||
StatsVector(self, self.directory_vector,
|
|
||||||
self.elementfmt)):
|
|
||||||
path_raw = direntry[2].find(b'\x00')
|
path_raw = direntry[2].find(b'\x00')
|
||||||
path = direntry[2][:path_raw].decode('ascii')
|
path = direntry[2][:path_raw].decode('ascii')
|
||||||
directory[path] = StatsEntry(direntry[0], direntry[1])
|
directory[path] = StatsEntry(direntry[0], direntry[1])
|
||||||
directory_by_idx[i] = path
|
directory_by_idx[i] = path
|
||||||
self.directory = directory
|
self.directory = directory
|
||||||
self.directory_by_idx = directory_by_idx
|
self.directory_by_idx = directory_by_idx
|
||||||
|
|
||||||
# Cache the error index vectors
|
|
||||||
self.error_vectors = []
|
|
||||||
for threads in StatsVector(self, self.error_vector, 'P'):
|
|
||||||
self.error_vectors.append(
|
|
||||||
StatsVector(self, threads[0], 'Q'))
|
|
||||||
return
|
return
|
||||||
except IOError:
|
except IOError:
|
||||||
if not blocking:
|
if not blocking:
|
||||||
@ -224,32 +200,23 @@ class VPPStats():
|
|||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
return iter(self.directory.items())
|
return iter(self.directory.items())
|
||||||
|
|
||||||
|
|
||||||
def set_errors(self, blocking=True):
|
def set_errors(self, blocking=True):
|
||||||
'''Return dictionary of error counters > 0'''
|
'''Return dictionary of error counters > 0'''
|
||||||
if not self.connected:
|
if not self.connected:
|
||||||
self.connect()
|
self.connect()
|
||||||
|
|
||||||
errors = {
|
errors = {k: v for k, v in self.directory.items()
|
||||||
k: v
|
if k.startswith("/err/")}
|
||||||
for k, v in self.directory.items() if k.startswith("/err/")
|
|
||||||
}
|
|
||||||
result = {}
|
result = {}
|
||||||
while True:
|
for k in errors:
|
||||||
try:
|
try:
|
||||||
if self.last_epoch != self.epoch:
|
total = self[k].sum()
|
||||||
self.refresh(blocking)
|
|
||||||
with self.lock:
|
|
||||||
for k, entry in errors.items():
|
|
||||||
total = 0
|
|
||||||
i = entry.value
|
|
||||||
for per_thread in self.error_vectors:
|
|
||||||
total += per_thread[i]
|
|
||||||
if total:
|
if total:
|
||||||
result[k] = total
|
result[k] = total
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
return result
|
return result
|
||||||
except IOError:
|
|
||||||
if not blocking:
|
|
||||||
raise
|
|
||||||
|
|
||||||
def set_errors_str(self, blocking=True):
|
def set_errors_str(self, blocking=True):
|
||||||
'''Return all errors counters > 0 pretty printed'''
|
'''Return all errors counters > 0 pretty printed'''
|
||||||
@ -264,19 +231,8 @@ class VPPStats():
|
|||||||
return self.__getitem__(name, blocking)
|
return self.__getitem__(name, blocking)
|
||||||
|
|
||||||
def get_err_counter(self, name, blocking=True):
|
def get_err_counter(self, name, blocking=True):
|
||||||
'''Return a single value (sum of all threads)'''
|
'''Alternative call to __getitem__'''
|
||||||
if not self.connected:
|
return self.__getitem__(name, blocking).sum()
|
||||||
self.connect()
|
|
||||||
if name.startswith("/err/"):
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
if self.last_epoch != self.epoch:
|
|
||||||
self.refresh(blocking)
|
|
||||||
with self.lock:
|
|
||||||
return sum(self.directory[name].get_counter(self))
|
|
||||||
except IOError:
|
|
||||||
if not blocking:
|
|
||||||
raise
|
|
||||||
|
|
||||||
def ls(self, patterns):
|
def ls(self, patterns):
|
||||||
'''Returns list of counters matching pattern'''
|
'''Returns list of counters matching pattern'''
|
||||||
@ -286,10 +242,11 @@ class VPPStats():
|
|||||||
if not isinstance(patterns, list):
|
if not isinstance(patterns, list):
|
||||||
patterns = [patterns]
|
patterns = [patterns]
|
||||||
regex = [re.compile(i) for i in patterns]
|
regex = [re.compile(i) for i in patterns]
|
||||||
return [
|
if self.last_epoch != self.epoch:
|
||||||
k for k, v in self.directory.items() if any(
|
self.refresh()
|
||||||
re.match(pattern, k) for pattern in regex)
|
|
||||||
]
|
return [k for k, v in self.directory.items()
|
||||||
|
if any(re.match(pattern, k) for pattern in regex)]
|
||||||
|
|
||||||
def dump(self, counters, blocking=True):
|
def dump(self, counters, blocking=True):
|
||||||
'''Given a list of counters return a dictionary of results'''
|
'''Given a list of counters return a dictionary of results'''
|
||||||
@ -300,9 +257,9 @@ class VPPStats():
|
|||||||
result[cnt] = self.__getitem__(cnt,blocking)
|
result[cnt] = self.__getitem__(cnt,blocking)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
class StatsLock():
|
class StatsLock():
|
||||||
'''Stat segment optimistic locking'''
|
'''Stat segment optimistic locking'''
|
||||||
|
|
||||||
def __init__(self, stats):
|
def __init__(self, stats):
|
||||||
self.stats = stats
|
self.stats = stats
|
||||||
self.epoch = 0
|
self.epoch = 0
|
||||||
@ -339,15 +296,16 @@ class StatsLock():
|
|||||||
|
|
||||||
class StatsCombinedList(list):
|
class StatsCombinedList(list):
|
||||||
'''Column slicing for Combined counters list'''
|
'''Column slicing for Combined counters list'''
|
||||||
|
|
||||||
def __getitem__(self, item):
|
def __getitem__(self, item):
|
||||||
'''Supports partial numpy style 2d support. Slice by column [:,1]'''
|
'''Supports partial numpy style 2d support. Slice by column [:,1]'''
|
||||||
if isinstance(item, int):
|
if isinstance(item, int):
|
||||||
return list.__getitem__(self, item)
|
return list.__getitem__(self, item)
|
||||||
return CombinedList([row[item[1]] for row in self])
|
return CombinedList([row[item[1]] for row in self])
|
||||||
|
|
||||||
|
|
||||||
class CombinedList(list):
|
class CombinedList(list):
|
||||||
'''Combined Counters 2-dimensional by thread by index of packets/octets'''
|
'''Combined Counters 2-dimensional by thread by index of packets/octets'''
|
||||||
|
|
||||||
def packets(self):
|
def packets(self):
|
||||||
'''Return column (2nd dimension). Packets for all threads'''
|
'''Return column (2nd dimension). Packets for all threads'''
|
||||||
return [pair[0] for pair in self]
|
return [pair[0] for pair in self]
|
||||||
@ -364,7 +322,6 @@ class CombinedList(list):
|
|||||||
'''Return column (2nd dimension). Sum of all octets for all threads'''
|
'''Return column (2nd dimension). Sum of all octets for all threads'''
|
||||||
return sum(self.octets())
|
return sum(self.octets())
|
||||||
|
|
||||||
|
|
||||||
class StatsTuple(tuple):
|
class StatsTuple(tuple):
|
||||||
'''A Combined vector tuple (packets, octets)'''
|
'''A Combined vector tuple (packets, octets)'''
|
||||||
def __init__(self, data):
|
def __init__(self, data):
|
||||||
@ -381,26 +338,24 @@ class StatsTuple(tuple):
|
|||||||
return tuple.__getitem__(self, 0)
|
return tuple.__getitem__(self, 0)
|
||||||
return tuple.__getitem__(self, 1)
|
return tuple.__getitem__(self, 1)
|
||||||
|
|
||||||
|
|
||||||
class StatsSimpleList(list):
|
class StatsSimpleList(list):
|
||||||
'''Simple Counters 2-dimensional by thread by index of packets'''
|
'''Simple Counters 2-dimensional by thread by index of packets'''
|
||||||
|
|
||||||
def __getitem__(self, item):
|
def __getitem__(self, item):
|
||||||
'''Supports partial numpy style 2d support. Slice by column [:,1]'''
|
'''Supports partial numpy style 2d support. Slice by column [:,1]'''
|
||||||
if isinstance(item, int):
|
if isinstance(item, int):
|
||||||
return list.__getitem__(self, item)
|
return list.__getitem__(self, item)
|
||||||
return SimpleList([row[item[1]] for row in self])
|
return SimpleList([row[item[1]] for row in self])
|
||||||
|
|
||||||
|
|
||||||
class SimpleList(list):
|
class SimpleList(list):
|
||||||
'''Simple counter'''
|
'''Simple counter'''
|
||||||
|
|
||||||
def sum(self):
|
def sum(self):
|
||||||
'''Sum the vector'''
|
'''Sum the vector'''
|
||||||
return sum(self)
|
return sum(self)
|
||||||
|
|
||||||
|
|
||||||
class StatsEntry():
|
class StatsEntry():
|
||||||
'''An individual stats entry'''
|
'''An individual stats entry'''
|
||||||
|
|
||||||
# pylint: disable=unused-argument,no-self-use
|
# pylint: disable=unused-argument,no-self-use
|
||||||
|
|
||||||
def __init__(self, stattype, statvalue):
|
def __init__(self, stattype, statvalue):
|
||||||
@ -414,10 +369,8 @@ class StatsEntry():
|
|||||||
elif stattype == 3:
|
elif stattype == 3:
|
||||||
self.function = self.combined
|
self.function = self.combined
|
||||||
elif stattype == 4:
|
elif stattype == 4:
|
||||||
self.function = self.error
|
|
||||||
elif stattype == 5:
|
|
||||||
self.function = self.name
|
self.function = self.name
|
||||||
elif stattype == 7:
|
elif stattype == 6:
|
||||||
self.function = self.symlink
|
self.function = self.symlink
|
||||||
else:
|
else:
|
||||||
self.function = self.illegal
|
self.function = self.illegal
|
||||||
@ -442,19 +395,10 @@ class StatsEntry():
|
|||||||
'''Combined counter'''
|
'''Combined counter'''
|
||||||
counter = StatsCombinedList()
|
counter = StatsCombinedList()
|
||||||
for threads in StatsVector(stats, self.value, 'P'):
|
for threads in StatsVector(stats, self.value, 'P'):
|
||||||
clist = [
|
clist = [StatsTuple(cnt) for cnt in StatsVector(stats, threads[0], 'QQ')]
|
||||||
StatsTuple(cnt) for cnt in StatsVector(stats, threads[0], 'QQ')
|
|
||||||
]
|
|
||||||
counter.append(clist)
|
counter.append(clist)
|
||||||
return counter
|
return counter
|
||||||
|
|
||||||
def error(self, stats):
|
|
||||||
'''Error counter'''
|
|
||||||
counter = SimpleList()
|
|
||||||
for clist in stats.error_vectors:
|
|
||||||
counter.append(clist[self.value])
|
|
||||||
return counter
|
|
||||||
|
|
||||||
def name(self, stats):
|
def name(self, stats):
|
||||||
'''Name counter'''
|
'''Name counter'''
|
||||||
counter = []
|
counter = []
|
||||||
@ -465,7 +409,6 @@ class StatsEntry():
|
|||||||
|
|
||||||
SYMLINK_FMT1 = Struct('II')
|
SYMLINK_FMT1 = Struct('II')
|
||||||
SYMLINK_FMT2 = Struct('Q')
|
SYMLINK_FMT2 = Struct('Q')
|
||||||
|
|
||||||
def symlink(self, stats):
|
def symlink(self, stats):
|
||||||
'''Symlink counter'''
|
'''Symlink counter'''
|
||||||
b = self.SYMLINK_FMT2.pack(self.value)
|
b = self.SYMLINK_FMT2.pack(self.value)
|
||||||
@ -477,20 +420,3 @@ class StatsEntry():
|
|||||||
'''Return a list of counters'''
|
'''Return a list of counters'''
|
||||||
if stats:
|
if stats:
|
||||||
return self.function(stats)
|
return self.function(stats)
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# stat = VPPStats(socketname='/run/vpp/stats.sock', timeout=2)
|
|
||||||
# stat.connect()
|
|
||||||
# print('version ', stat.version)
|
|
||||||
# print('epoch ', stat.epoch)
|
|
||||||
# print('/if/names', stat['/if/names'])
|
|
||||||
#
|
|
||||||
# for x in range(10):
|
|
||||||
# idx=2
|
|
||||||
# print('/if/rx[%s] packets %u octets %u' % (stat['/if/names'][idx], stat['/if/rx'][:, idx].sum_packets(), stat['/if/rx'][:, idx].sum_octets()))
|
|
||||||
# print('/if/tx[%s] packets %u octets %u' % (stat['/if/names'][idx], stat['/if/tx'][:, idx].sum_packets(), stat['/if/tx'][:, idx].sum_octets()))
|
|
||||||
# print("")
|
|
||||||
# time.sleep(10)
|
|
||||||
#
|
|
||||||
# stat.disconnect()
|
|
||||||
|
Reference in New Issue
Block a user