X-Git-Url: https://gerrit.fd.io/r/gitweb?a=blobdiff_plain;f=src%2Fvpp-api%2Fpython%2Fvpp_papi%2Fvpp_papi.py;h=6c17fa88c955f90506cf6385eb2aa94ea7a30e28;hb=e64e5fff4;hp=05688cec7319ff86f2992a33402a789bfa102182;hpb=ead1e536d66d83b546528c32e2112085a97c8e13;p=vpp.git diff --git a/src/vpp-api/python/vpp_papi/vpp_papi.py b/src/vpp-api/python/vpp_papi/vpp_papi.py index 05688cec731..6c17fa88c95 100644 --- a/src/vpp-api/python/vpp_papi/vpp_papi.py +++ b/src/vpp-api/python/vpp_papi/vpp_papi.py @@ -17,6 +17,7 @@ from __future__ import print_function from __future__ import absolute_import import ctypes +import ipaddress import sys import multiprocessing as mp import os @@ -27,6 +28,8 @@ import threading import fnmatch import weakref import atexit +import time +from . vpp_format import verify_enum_hint from . vpp_serializer import VPPType, VPPEnumType, VPPUnionType from . vpp_serializer import VPPMessage, vpp_get_type, VPPTypeAlias @@ -76,6 +79,26 @@ else: return d.items() +def add_convenience_methods(): + # provide convenience methods to IP[46]Address.vapi_af + def _vapi_af(self): + if 6 == self._version: + return VppEnum.vl_api_address_family_t.ADDRESS_IP6.value + if 4 == self._version: + return VppEnum.vl_api_address_family_t.ADDRESS_IP4.value + raise ValueError("Invalid _version.") + + def _vapi_af_name(self): + if 6 == self._version: + return 'ip6' + if 4 == self._version: + return 'ip4' + raise ValueError("Invalid _version.") + + ipaddress._IPAddressBase.vapi_af = property(_vapi_af) + ipaddress._IPAddressBase.vapi_af_name = property(_vapi_af_name) + + class VppApiDynamicMethodHolder(object): pass @@ -112,6 +135,7 @@ class VPPRuntimeError(RuntimeError): class VPPValueError(ValueError): pass + class VPPApiJSONFiles(object): @classmethod def find_api_dir(cls, dirs): @@ -294,6 +318,7 @@ class VPPApiJSONFiles(object): self.logger.error('Not implemented error for {}'.format(m[0])) return messages, services + class VPPApiClient(object): """VPP interface. @@ -353,6 +378,7 @@ class VPPApiClient(object): self.use_socket = use_socket self.server_address = server_address self._apifiles = apifiles + self.stats = {} if use_socket: from . vpp_transport_socket import VppTransport @@ -381,16 +407,20 @@ class VPPApiClient(object): # Basic sanity check if len(self.messages) == 0 and not testmode: raise VPPValueError(1, 'Missing JSON message definitions') + if not(verify_enum_hint(VppEnum.vl_api_address_family_t)): + raise VPPRuntimeError("Invalid address family hints. " + "Cannot continue.") self.transport = VppTransport(self, read_timeout=read_timeout, server_address=server_address) # Make sure we allow VPP to clean up the message rings. atexit.register(vpp_atexit, weakref.ref(self)) + add_convenience_methods() + def get_function(self, name): return getattr(self._api, name) - class ContextId(object): """Multiprocessing-safe provider of unique context IDs.""" def __init__(self): @@ -596,6 +626,24 @@ class VPPApiClient(object): raise VPPValueError('Invalid argument {} to {}' .format(list(d), msg.name)) + def _add_stat(self, name, ms): + if not name in self.stats: + self.stats[name] = {'max': ms, 'count': 1, 'avg': ms} + else: + if ms > self.stats[name]['max']: + self.stats[name]['max'] = ms + self.stats[name]['count'] += 1 + n = self.stats[name]['count'] + self.stats[name]['avg'] = self.stats[name]['avg'] * (n - 1) / n + ms / n + + def get_stats(self): + s = '\n=== API PAPI STATISTICS ===\n' + s += '{:<30} {:>4} {:>6} {:>6}\n'.format('message', 'cnt', 'avg', 'max') + for n in sorted(self.stats.items(), key=lambda v: v[1]['avg'], reverse=True): + s += '{:<30} {:>4} {:>6.2f} {:>6.2f}\n'.format(n[0], n[1]['count'], + n[1]['avg'], n[1]['max']) + return s + def _call_vpp(self, i, msgdef, multipart, **kwargs): """Given a message, send the message and await a reply. @@ -611,7 +659,7 @@ class VPPApiClient(object): the response. It will raise an IOError exception if there was no response within the timeout window. """ - + ts = time.time() if 'context' not in kwargs: context = self.get_context() kwargs['context'] = context @@ -620,6 +668,7 @@ class VPPApiClient(object): kwargs['_vl_msg_id'] = i no_type_conversion = kwargs.pop('_no_type_conversion', False) + timeout = kwargs.pop('_timeout', None) try: if self.transport.socket_index: @@ -645,7 +694,7 @@ class VPPApiClient(object): # Block until we get a reply. rl = [] while (True): - r = self.read_blocking(no_type_conversion) + r = self.read_blocking(no_type_conversion, timeout) if r is None: raise VPPIOError(2, 'VPP API client: read failed') msgname = type(r).__name__ @@ -668,6 +717,8 @@ class VPPApiClient(object): if len(s) > 80: s = s[:80] + "..." self.logger.debug(s) + te = time.time() + self._add_stat(msgdef.name, (te - ts) * 1000) return rl def _call_vpp_async(self, i, msg, **kwargs): @@ -699,10 +750,10 @@ class VPPApiClient(object): self.transport.write(b) return context - def read_blocking(self, no_type_conversion=False): + def read_blocking(self, no_type_conversion=False, timeout=None): """Get next received message from transport within timeout, decoded. - Note that noticifations have context zero + Note that notifications have context zero and are not put into receive queue (at least for socket transport), use async_thread with registered callback for processing them. @@ -720,8 +771,9 @@ class VPPApiClient(object): :type no_type_conversion: bool :returns: Decoded message, or None if no message (within timeout). :rtype: Whatever VPPType.unpack returns, depends on no_type_conversion. + :raises VppTransportShmemIOError if timed out. """ - msg = self.transport.read() + msg = self.transport.read(timeout=timeout) if not msg: return None return self.decode_incoming_msg(msg, no_type_conversion) @@ -759,6 +811,34 @@ class VPPApiClient(object): if self.event_callback: self.event_callback(msgname, r) + def validate_message_table(self, namecrctable): + """Take a dictionary of name_crc message names + and returns an array of missing messages""" + + missing_table = [] + for name_crc in namecrctable: + i = self.transport.get_msg_index(name_crc) + if i <= 0: + missing_table.append(name_crc) + return missing_table + + def dump_message_table(self): + """Return VPPs API message table as name_crc dictionary""" + return self.transport.message_table + + def dump_message_table_filtered(self, msglist): + """Return VPPs API message table as name_crc dictionary, + filtered by message name list.""" + + replies = [self.services[n]['reply'] for n in msglist] + message_table_filtered = {} + for name in msglist + replies: + for k,v in self.transport.message_table.items(): + if k.startswith(name): + message_table_filtered[k] = v + break + return message_table_filtered + def __repr__(self): return "