From: Ole Troan Date: Thu, 30 Aug 2018 22:29:48 +0000 (+0200) Subject: STATS: Python binding to access VPP statistics and counters. X-Git-Tag: v18.10-rc1~287 X-Git-Url: https://gerrit.fd.io/r/gitweb?p=vpp.git;a=commitdiff_plain;h=732021070fa0c731896ab3e29f802d3834c72ab7 STATS: Python binding to access VPP statistics and counters. from vpp_papi.vpp_stats import VPPStats s = VPPStats(socketname='/var/run/stats.sock') c = s.ls('/if/rx') counters = s.dump(c) print(s.set_error_str()) Change-Id: I203ebe60b0c9ee5742aadc737c0f29051757959d Signed-off-by: Ole Troan --- diff --git a/src/vpp-api/client/libvppapiclient.map b/src/vpp-api/client/libvppapiclient.map index 81391ea7687..f51b92403da 100644 --- a/src/vpp-api/client/libvppapiclient.map +++ b/src/vpp-api/client/libvppapiclient.map @@ -21,5 +21,7 @@ VPPAPICLIENT_18.10 { stat_segment_dump; stat_segment_data_free; stat_segment_heartbeat; + stat_segment_string_vector; + stat_segment_vec_len; local: *; }; diff --git a/src/vpp-api/client/stat_client.c b/src/vpp-api/client/stat_client.c index d7d20452583..ad7078ea745 100644 --- a/src/vpp-api/client/stat_client.c +++ b/src/vpp-api/client/stat_client.c @@ -112,7 +112,7 @@ stat_segment_register (u8 * stats[]) p = hash_get_mem (sm->counter_vector_by_name, stats[i]); if (p == 0) { - fprintf (stderr, "WARN: %s not in directory!", stats[i]); + fprintf (stderr, "WARN: %s not in directory!\n", stats[i]); continue; } vec_add2 (cached_pointer_vec, cp, 1); @@ -183,7 +183,7 @@ maybe_update_cached_pointers (stat_segment_cached_pointer_t * cached_pointers) p = hash_get_mem (sm->counter_vector_by_name, cp->name); if (p == 0) { - fprintf (stderr, "WARN: %s not in directory!", cp->name); + fprintf (stderr, "WARN: %s not in directory!\n", cp->name); continue; } cp->ep = (stat_segment_directory_entry_t *) (p[0]); @@ -351,7 +351,7 @@ stat_segment_dump (u8 * stats[]) p = hash_get_mem (sm->counter_vector_by_name, stats[i]); if (p == 0) { - fprintf (stderr, "WARN: %s not in directory!", stats[i]); + fprintf (stderr, "WARN: %s not in directory!\n", stats[i]); continue; } /* Collect counter */ @@ -363,6 +363,23 @@ stat_segment_dump (u8 * stats[]) return res; } +/* Wrapper for accessing vectors from other languages */ +int +stat_segment_vec_len (void *vec) +{ + return vec_len (vec); +} + +/* Create a vector from a string (or add to existing) */ +u8 ** +stat_segment_string_vector (u8 ** string_vector, char *string) +{ + u8 *name = 0; + name = vec_dup ((u8 *) string); + vec_add1 (string_vector, (u8 *) name); + return string_vector; +} + /* * fd.io coding-style-patch-verification: ON * diff --git a/src/vpp-api/client/stat_client.h b/src/vpp-api/client/stat_client.h index 5f076686fdd..73e91b1881b 100644 --- a/src/vpp-api/client/stat_client.h +++ b/src/vpp-api/client/stat_client.h @@ -42,16 +42,15 @@ typedef struct int stat_segment_connect (char *socket_name); void stat_segment_disconnect (void); - u8 **stat_segment_ls (u8 ** pattern); stat_segment_data_t *stat_segment_dump (u8 ** counter_vec); - stat_segment_cached_pointer_t *stat_segment_register (u8 ** counter_vec); -stat_segment_data_t *stat_segment_collect (stat_segment_cached_pointer_t *); /* Collects registered counters */ - +/* Collects registered counters */ +stat_segment_data_t *stat_segment_collect (stat_segment_cached_pointer_t *); void stat_segment_data_free (stat_segment_data_t * res); - f64 stat_segment_heartbeat (void); +u8 **stat_segment_string_vector (u8 ** string_vector, char *string); +int stat_segment_vec_len (void *vec); #endif /* included_stat_client_h */ diff --git a/src/vpp-api/python/vpp_papi/vpp_stats.py b/src/vpp-api/python/vpp_papi/vpp_stats.py new file mode 100644 index 00000000000..b84a0e5dc9d --- /dev/null +++ b/src/vpp-api/python/vpp_papi/vpp_stats.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python + +from __future__ import print_function +from cffi import FFI + +ffi = FFI() +ffi.cdef(""" +typedef uint64_t counter_t; +typedef struct { + counter_t packets; + counter_t bytes; +} vlib_counter_t; + +typedef enum { + STAT_DIR_TYPE_ILLEGAL = 0, + STAT_DIR_TYPE_SCALAR_POINTER, + STAT_DIR_TYPE_VECTOR_POINTER, + STAT_DIR_TYPE_COUNTER_VECTOR_SIMPLE, + STAT_DIR_TYPE_COUNTER_VECTOR_COMBINED, + STAT_DIR_TYPE_ERROR_INDEX, + STAT_DIR_TYPE_SERIALIZED_NODES, +} stat_directory_type_t; + +typedef struct { + stat_directory_type_t type; + void *value; +} stat_segment_directory_entry_t; + +typedef struct { + char *name; + stat_directory_type_t type; + union { + double scalar_value; + uint64_t error_value; + uint64_t *vector_pointer; + counter_t **simple_counter_vec; + vlib_counter_t **combined_counter_vec; + }; +} stat_segment_data_t; + +typedef struct { + char *name; + stat_segment_directory_entry_t *ep; +} stat_segment_cached_pointer_t; + +int stat_segment_connect (char *socket_name); +void stat_segment_disconnect (void); + +uint8_t **stat_segment_ls (uint8_t **pattern); +stat_segment_data_t *stat_segment_dump (uint8_t ** counter_vec); +/* Collects registered counters */ +stat_segment_cached_pointer_t *stat_segment_register (uint8_t ** counter_vec); +stat_segment_data_t *stat_segment_collect (stat_segment_cached_pointer_t *); +void stat_segment_data_free (stat_segment_data_t * res); +double stat_segment_heartbeat (void); +int stat_segment_vec_len(void *vec); +uint8_t **stat_segment_string_vector(uint8_t **string_vector, char *string); +""") + + +# Utility functions +def make_string_vector(api, strings): + vec = ffi.NULL + if type(strings) is not list: + strings = [strings] + for s in strings: + vec = api.stat_segment_string_vector(vec, ffi.new("char []", s)) + return vec + + +def make_string_list(api, vec): + vec_len = api.stat_segment_vec_len(vec) + return [ffi.string(vec[i]) for i in range(vec_len)] + + +# 2-dimensonal array of thread, index +def simple_counter_vec_list(api, e): + vec = [] + for thread in range(api.stat_segment_vec_len(e)): + len_interfaces = api.stat_segment_vec_len(e[thread]) + if_per_thread = [e[thread][interfaces] + for interfaces in range(len_interfaces)] + vec.append(if_per_thread) + return vec + + +def vlib_counter_dict(c): + return {'packets': c.packets, + 'bytes': c.bytes} + + +def combined_counter_vec_list(api, e): + vec = [] + for thread in range(api.stat_segment_vec_len(e)): + len_interfaces = api.stat_segment_vec_len(e[thread]) + if_per_thread = [vlib_counter_dict(e[thread][interfaces]) + for interfaces in range(len_interfaces)] + vec.append(if_per_thread) + return vec + + +def stat_entry_to_python(api, e): + if e.type == 3: + return simple_counter_vec_list(e.simple_counter_vec) + if e.type == 4: + return combined_counter_vec_list(api, e.combined_counter_vec) + if e.type == 5: + return e.error_value + return None + + +class VPPStats: + def __init__(self, socketname='/var/run/stats.sock'): + self.api = ffi.dlopen('libvppapiclient.so') + rv = self.api.stat_segment_connect(socketname) + if rv != 0: + raise IOError() + + def heartbeat(self): + return self.api.stat_segment_heartbeat() + + def ls(self, patterns): + return self.api.stat_segment_ls(make_string_vector(self.api, patterns)) + + def dump(self, counters): + stats = {} + rv = self.api.stat_segment_dump(counters) + rv_len = self.api.stat_segment_vec_len(rv) + for i in range(rv_len): + n = ffi.string(rv[i].name) + e = stat_entry_to_python(self.api, rv[i]) + if e: + stats[n] = e + return stats + + def dump_str(self, counters_str): + return self.dump(make_string_vector(self.api, counters_str)) + + def disconnect(self): + self.api.stat_segment_disconnect() + + def set_errors(self): + '''Return all errors counters > 0''' + error_names = self.ls(['/err/']) + error_counters = self.dump(error_names) + return {k: error_counters[k] + for k in error_counters.keys() if error_counters[k]} + + def set_errors_str(self): + '''Return all errors counters > 0 pretty printed''' + s = 'ERRORS:\n' + error_counters = self.set_errors() + for k in sorted(error_counters): + s += '{:<60}{:>10}\n'.format(k, error_counters[k]) + return s + + def register(self): + raise NotImplemented + + def collect(self): + raise NotImplemented diff --git a/src/vpp/app/vpp_get_stats.c b/src/vpp/app/vpp_get_stats.c index c1f43a747ed..908e675b062 100644 --- a/src/vpp/app/vpp_get_stats.c +++ b/src/vpp/app/vpp_get_stats.c @@ -186,7 +186,7 @@ reconnect: break; case STAT_DIR_TYPE_COUNTER_VECTOR_COMBINED: - for (k = 0; k < vec_len (res[i].simple_counter_vec); k++) + for (k = 0; k < vec_len (res[i].combined_counter_vec); k++) for (j = 0; j < vec_len (res[i].combined_counter_vec[k]); j++) fformat (stdout, "[%d @ %d]: %lld packets, %lld bytes %s\n", j, k, res[i].combined_counter_vec[k][j].packets, diff --git a/test/framework.py b/test/framework.py index f7a155f41cb..789ec1be9b9 100644 --- a/test/framework.py +++ b/test/framework.py @@ -23,6 +23,7 @@ from vpp_pg_interface import VppPGInterface from vpp_sub_interface import VppSubInterface from vpp_lo_interface import VppLoInterface from vpp_papi_provider import VppPapiProvider +from vpp_papi.vpp_stats import VPPStats from log import RED, GREEN, YELLOW, double_line_delim, single_line_delim, \ getLogger, colorize from vpp_object import VppObjectRegistry @@ -279,6 +280,8 @@ class VppTestCase(unittest.TestCase): coredump_size, "}", "api-trace", "{", "on", "}", "api-segment", "{", "prefix", cls.shm_prefix, "}", "cpu", "{", "main-core", str(cpu_core_number), "}", + "stats", "{", "socket-name", + cls.tempdir + "/stats.sock", "}", "plugins", "{", "plugin", "dpdk_plugin.so", "{", "disable", "}", "plugin", "unittest_plugin.so", "{", "enable", "}", "}", ] @@ -387,6 +390,7 @@ class VppTestCase(unittest.TestCase): else: hook = PollHook(cls) cls.vapi.register_hook(hook) + cls.statistics = VPPStats(socketname=cls.tempdir+'/stats.sock') cls.sleep(0.1, "after vpp startup, before initial poll") try: hook.poll_vpp() @@ -498,7 +502,7 @@ class VppTestCase(unittest.TestCase): self.logger.debug(self.vapi.cli("show trace")) self.logger.info(self.vapi.ppcli("show interface")) self.logger.info(self.vapi.ppcli("show hardware")) - self.logger.info(self.vapi.ppcli("show error")) + self.logger.info(self.statistics.set_errors_str()) self.logger.info(self.vapi.ppcli("show run")) self.logger.info(self.vapi.ppcli("show log")) self.registry.remove_vpp_config(self.logger) diff --git a/test/test_ipip.py b/test/test_ipip.py index fc74d274b1f..5754bd0366b 100644 --- a/test/test_ipip.py +++ b/test/test_ipip.py @@ -144,6 +144,10 @@ class TestIPIP(VppTestCase): for p in rx: self.validate(p[1], p4_reply) + err = '/err/ipip4-input/packets decapsulated' + cnt = self.statistics.dump_str(err) + self.assertEqual(cnt[err], 10) + # IPv4 tunnel to IPv6 p_ip6 = IPv6(src="1:2:3::4", dst=self.pg0.remote_ip6) p6 = (p_ether / IP(src=self.pg1.remote_ip4, @@ -154,6 +158,9 @@ class TestIPIP(VppTestCase): for p in rx: self.validate(p[1], p6_reply) + cnt = self.statistics.dump_str(err) + self.assertEqual(cnt[err], 20) + # # Fragmentation / Reassembly and Re-fragmentation # @@ -174,6 +181,9 @@ class TestIPIP(VppTestCase): for p in rx: self.validate(p[1], p4_reply) + cnt = self.statistics.dump_str(err) + self.assertEqual(cnt[err], 1020) + f = [] r = [] for i in range(1, 90):