X-Git-Url: https://gerrit.fd.io/r/gitweb?a=blobdiff_plain;f=src%2Fvpp-api%2Fpython%2Fvpp_papi%2Fvpp_stats.py;h=4b7a9bce8b001514f8dcfceb19a8b796f6a57a4a;hb=4376ab2a926b36c1131588069a8a9cdd2520073d;hp=76e1e5435f0458b9bb3a314a21979a8f1624efa6;hpb=31555a3475a37195938378217a635b3451e449de;p=vpp.git diff --git a/src/vpp-api/python/vpp_papi/vpp_stats.py b/src/vpp-api/python/vpp_papi/vpp_stats.py index 76e1e5435f0..4b7a9bce8b0 100644 --- a/src/vpp-api/python/vpp_papi/vpp_stats.py +++ b/src/vpp-api/python/vpp_papi/vpp_stats.py @@ -1,7 +1,8 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 from __future__ import print_function from cffi import FFI +import time ffi = FFI() ffi.cdef(""" @@ -17,17 +18,17 @@ typedef enum { STAT_DIR_TYPE_COUNTER_VECTOR_SIMPLE, STAT_DIR_TYPE_COUNTER_VECTOR_COMBINED, STAT_DIR_TYPE_ERROR_INDEX, + STAT_DIR_TYPE_NAME_VECTOR, } stat_directory_type_t; typedef struct { stat_directory_type_t type; union { - uint64_t offset; uint64_t index; uint64_t value; + uint64_t *data; }; - uint64_t offset_vector; char name[128]; // TODO change this to pointer to "somewhere" } stat_segment_directory_entry_t; @@ -38,22 +39,54 @@ typedef struct union { double scalar_value; - uint64_t error_value; + counter_t *error_vector; counter_t **simple_counter_vec; vlib_counter_t **combined_counter_vec; + uint8_t **name_vector; }; } stat_segment_data_t; +typedef struct +{ + uint64_t version; + void *base; + uint64_t epoch; + uint64_t in_progress; + stat_segment_directory_entry_t *directory_vector; + uint64_t **error_vector; +} stat_segment_shared_header_t; + +typedef struct +{ + uint64_t current_epoch; + stat_segment_shared_header_t *shared_header; + stat_segment_directory_entry_t *directory_vector; + ssize_t memory_size; +} stat_client_main_t; + +stat_client_main_t * stat_client_get(void); +void stat_client_free(stat_client_main_t * sm); +int stat_segment_connect_r (char *socket_name, stat_client_main_t * sm); int stat_segment_connect (char *socket_name); +void stat_segment_disconnect_r (stat_client_main_t * sm); void stat_segment_disconnect (void); +uint32_t *stat_segment_ls_r (uint8_t ** patterns, stat_client_main_t * sm); uint32_t *stat_segment_ls (uint8_t ** pattern); +stat_segment_data_t *stat_segment_dump_r (uint32_t * stats, + stat_client_main_t * sm); stat_segment_data_t *stat_segment_dump (uint32_t * counter_vec); void stat_segment_data_free (stat_segment_data_t * res); -double stat_segment_heartbeat (void); + +double stat_segment_heartbeat_r (stat_client_main_t * sm); int stat_segment_vec_len(void *vec); uint8_t **stat_segment_string_vector(uint8_t **string_vector, char *string); -""") +char *stat_segment_index_to_name_r (uint32_t index, stat_client_main_t * sm); +uint64_t stat_segment_version(void); +uint64_t stat_segment_version_r(stat_client_main_t *sm); +void free(void *ptr); +void vac_mem_init (size_t size); +""") # noqa: E501 # Utility functions @@ -63,7 +96,7 @@ def make_string_vector(api, strings): strings = [strings] for s in strings: vec = api.stat_segment_string_vector(vec, ffi.new("char []", - s.encode())) + s.encode('utf-8'))) return vec @@ -98,42 +131,134 @@ def combined_counter_vec_list(api, e): return vec +def error_vec_list(api, e): + vec = [] + for thread in range(api.stat_segment_vec_len(e)): + vec.append(e[thread]) + return vec + + +def name_vec_list(api, e): + return [ffi.string(e[i]).decode('utf-8') for i in + range(api.stat_segment_vec_len(e)) if e[i] != ffi.NULL] + + def stat_entry_to_python(api, e): # Scalar index if e.type == 1: return e.scalar_value - return None if e.type == 2: return simple_counter_vec_list(api, e.simple_counter_vec) if e.type == 3: return combined_counter_vec_list(api, e.combined_counter_vec) if e.type == 4: - return e.error_value - return None + return error_vec_list(api, e.error_vector) + if e.type == 5: + return name_vec_list(api, e.name_vector) + raise NotImplementedError() + + +class VPPStatsIOError(IOError): + message = "Stat segment client connection returned: " \ + "%(retval)s %(strerror)s." + + strerror = {-1: "Stat client couldn't open socket", + -2: "Stat client socket open but couldn't connect", + -3: "Receiving file descriptor failed", + -4: "mmap fstat failed", + -5: "mmap map failed" + } + + def __init__(self, message=None, **kwargs): + if 'retval' in kwargs: + self.retval = kwargs['retval'] + kwargs['strerror'] = self.strerror[int(self.retval)] + + if not message: + try: + message = self.message % kwargs + except Exception: + message = self.message + else: + message = message % kwargs + + super(VPPStatsIOError, self).__init__(message) + + +class VPPStatsClientLoadError(RuntimeError): + pass class VPPStats: - def __init__(self, socketname='/var/run/stats.sock'): - self.api = ffi.dlopen('libvppapiclient.so') - rv = self.api.stat_segment_connect(socketname.encode()) + VPPStatsIOError = VPPStatsIOError + + default_socketname = '/run/vpp/stats.sock' + sharedlib_name = 'libvppapiclient.so' + + def __init__(self, socketname=default_socketname, timeout=10): + self.socketname = socketname + self.timeout = timeout + self.connected = False + try: + self.api = ffi.dlopen(VPPStats.sharedlib_name) + except Exception: + raise VPPStatsClientLoadError("Could not open: %s" % + VPPStats.sharedlib_name) + self.api.vac_mem_init(0) + + def connect(self): + self.client = self.api.stat_client_get() + + poll_end_time = time.time() + self.timeout + while time.time() < poll_end_time: + rv = self.api.stat_segment_connect_r( + self.socketname.encode('utf-8'), self.client) + # Break out if success or any other error than "no such file" + # (indicating that VPP hasn't started yet) + if rv == 0 or ffi.errno != 2: + self.connected = True + break + if rv != 0: - raise IOError() + raise VPPStatsIOError(retval=rv) def heartbeat(self): - return self.api.stat_segment_heartbeat() + if not self.connected: + self.connect() + return self.api.stat_segment_heartbeat_r(self.client) def ls(self, patterns): - return self.api.stat_segment_ls(make_string_vector(self.api, patterns)) + if not self.connected: + self.connect() + return self.api.stat_segment_ls_r(make_string_vector(self.api, + patterns), + self.client) + + def lsstr(self, patterns): + if not self.connected: + self.connect() + rv = self.api.stat_segment_ls_r(make_string_vector(self.api, + patterns), + self.client) + + if rv == ffi.NULL: + raise VPPStatsIOError() + return [ffi.string(self.api.stat_segment_index_to_name_r( + rv[i], self.client)).decode('utf-8') + for i in range(self.api.stat_segment_vec_len(rv))] def dump(self, counters): + if not self.connected: + self.connect() stats = {} - rv = self.api.stat_segment_dump(counters) + rv = self.api.stat_segment_dump_r(counters, self.client) # Raise exception and retry if rv == ffi.NULL: - raise IOError() + raise VPPStatsIOError() rv_len = self.api.stat_segment_vec_len(rv) + for i in range(rv_len): - n = ffi.string(rv[i].name).decode() + n = ffi.string(rv[i].name).decode('utf-8') e = stat_entry_to_python(self.api, rv[i]) if e is not None: stats[n] = e @@ -143,16 +268,32 @@ class VPPStats: retries = 0 while True: try: - dir = self.ls(name) - return self.dump(dir).values()[0] - except Exception as e: + d = self.ls(name) + s = self.dump(d) + if len(s) > 1: + raise AttributeError('Matches multiple counters {}' + .format(name)) + k, v = s.popitem() + return v + except VPPStatsIOError: if retries > 10: return None retries += 1 - pass + + def get_err_counter(self, name): + """Get an error counter. The errors from each worker thread + are summed""" + return sum(self.get_counter(name)) def disconnect(self): - self.api.stat_segment_disconnect() + try: + self.api.stat_segment_disconnect_r(self.client) + self.api.stat_client_free(self.client) + self.connected = False + del self.client + except AttributeError: + # no need to disconnect if we're not connected + pass def set_errors(self): '''Return all errors counters > 0''' @@ -162,18 +303,18 @@ class VPPStats: error_names = self.ls(['/err/']) error_counters = self.dump(error_names) break - except Exception as e: + except VPPStatsIOError: if retries > 10: return None retries += 1 - pass - return {k: error_counters[k] - for k in error_counters.keys() if error_counters[k]} + + return {k: sum(error_counters[k]) + for k in error_counters.keys() if sum(error_counters[k])} def set_errors_str(self): '''Return all errors counters > 0 pretty printed''' - s = 'ERRORS:\n' + s = ['ERRORS:'] error_counters = self.set_errors() for k in sorted(error_counters): - s += '{:<60}{:>10}\n'.format(k, error_counters[k]) - return s + s.append('{:<60}{:>10}'.format(k, error_counters[k])) + return '%s\n' % '\n'.join(s)