stats: python vpp_stats rewrite to access stat segment directly 78/31678/12
authorOle Troan <ot@cisco.com>
Thu, 18 Mar 2021 10:12:01 +0000 (11:12 +0100)
committerNeale Ranns <neale@graphiant.com>
Thu, 25 Mar 2021 08:37:46 +0000 (08:37 +0000)
This module implement Python access to the VPP statistics segment. It
accesses the data structures directly in shared memory.
VPP uses optimistic locking, so data structures may change underneath
us while we are reading. Data is copied out and it's important to
spend as little time as possible "holding the lock".

Counters are stored in VPP as a two dimensional array.
Index by thread and index (typically sw_if_index).
Simple counters count only packets, Combined counters count packets
and octets.

Counters can be accessed in either dimension.
stat['/if/rx'] - returns 2D lists
stat['/if/rx'][0] - returns counters for all interfaces for thread 0
stat['/if/rx'][0][1] - returns counter for interface 1 on thread 0
stat['/if/rx'][0][1]['packets'] - returns the packet counter
                                  for interface 1 on thread 0
stat['/if/rx'][:, 1] - returns the counters for interface 1 on all threads
stat['/if/rx'][:, 1].packets() - returns the packet counters for
                                 interface 1 on all threads
stat['/if/rx'][:, 1].sum_packets() - returns the sum of packet counters for
                                     interface 1 on all threads
stat['/if/rx-miss'][:, 1].sum() - returns the sum of packet counters for
                                  interface 1 on all threads for simple counters

Type: refactor
Signed-off-by: Ole Troan <ot@cisco.com>
Change-Id: I1fe7f7c7d11378d06be8276db5e1900ecdb8f515
Signed-off-by: Ole Troan <ot@cisco.com>
src/plugins/urpf/ip4_urpf.c
src/plugins/urpf/ip6_urpf.c
src/plugins/urpf/test/test_urpf.py
src/vnet/ipsec/ah_encrypt.c
src/vpp-api/python/vpp_papi/vpp_stats.py [changed mode: 0644->0755]
test/framework.py
test/template_ipsec.py
test/test_ipsec_tun_if_esp.py
test/test_stats_client.py
test/vpp_interface.py
test/vpp_neighbor.py

index 25b6c94..1d32902 100644 (file)
@@ -41,7 +41,7 @@
 #include <urpf/urpf_dp.h>
 
 static char *ip4_urpf_error_strings[] = {
-#define _(a,b) "ip4-" # b,
+#define _(a, b) b,
   foreach_urpf_error
 #undef _
 };
index 3a94a45..48d9915 100644 (file)
@@ -41,7 +41,7 @@
 #include <urpf/urpf_dp.h>
 
 static char *ip6_urpf_error_strings[] = {
-#define _(a,b) "ip6-" # b,
+#define _(a, b) b,
   foreach_urpf_error
 #undef _
 };
index 64b246c..8f4e563 100644 (file)
@@ -90,7 +90,8 @@ class TestURPF(VppTestCase):
         # packets from address to which there is no route are dropped
         self.send_and_assert_no_replies(self.pg0, p_spoof_loose)
 
-        self.assert_error_counter_equal("ip4-rx-urpf-loose", N_PKTS)
+        self.assert_error_counter_equal("/err/ip4-rx-urpf-loose/uRPF Drop",
+                                        N_PKTS)
 
         #
         # crank it up to strict mode
@@ -106,7 +107,8 @@ class TestURPF(VppTestCase):
         self.send_and_assert_no_replies(self.pg0, p_spoof_strict)
         self.send_and_assert_no_replies(self.pg0, p_spoof_loose)
 
-        self.assert_error_counter_equal("ip4-rx-urpf-strict", 2 * N_PKTS)
+        self.assert_error_counter_equal("/err/ip4-rx-urpf-strict/uRPF Drop",
+                                        2 * N_PKTS)
 
         #
         # disable uRPF, all traffic should pass
@@ -136,7 +138,8 @@ class TestURPF(VppTestCase):
         self.send_and_expect(self.pg0, p_spoof_strict, self.pg1)
         self.send_and_assert_no_replies(self.pg0, p_spoof_loose)
 
-        self.assert_error_counter_equal("ip4-tx-urpf-loose", N_PKTS)
+        self.assert_error_counter_equal("/err/ip4-tx-urpf-loose/uRPF Drop",
+                                        N_PKTS)
 
         self.vapi.urpf_update(is_input=False,
                               mode=e.vl_api_urpf_mode_t.URPF_API_MODE_STRICT,
@@ -149,7 +152,8 @@ class TestURPF(VppTestCase):
         self.send_and_expect(self.pg0, p_spoof_strict, self.pg1)
         self.send_and_assert_no_replies(self.pg0, p_spoof_loose)
 
-        self.assert_error_counter_equal("ip4-tx-urpf-strict", N_PKTS)
+        self.assert_error_counter_equal("/err/ip4-tx-urpf-strict/uRPF Drop",
+                                        N_PKTS)
 
         # change the strict packet so that it would forward through pg1
         p_spoof_strict = (Ether(dst=self.pg0.local_mac,
@@ -160,7 +164,8 @@ class TestURPF(VppTestCase):
                           Raw(b'\xa5' * 100)) * N_PKTS
 
         self.send_and_assert_no_replies(self.pg0, p_spoof_strict)
-        self.assert_error_counter_equal("ip4-tx-urpf-strict", 2 * N_PKTS)
+        self.assert_error_counter_equal("/err/ip4-tx-urpf-strict/uRPF Drop",
+                                        2 * N_PKTS)
 
         # cleanup
         self.vapi.urpf_update(is_input=False,
@@ -212,7 +217,8 @@ class TestURPF(VppTestCase):
         # packets from address to which there is no route are dropped
         self.send_and_assert_no_replies(self.pg0, p_spoof_loose)
 
-        self.assert_error_counter_equal("ip6-rx-urpf-loose", N_PKTS)
+        self.assert_error_counter_equal("/err/ip6-rx-urpf-loose/uRPF Drop",
+                                        N_PKTS)
 
         #
         # crank it up to strict mode
@@ -228,7 +234,8 @@ class TestURPF(VppTestCase):
         self.send_and_assert_no_replies(self.pg0, p_spoof_strict)
         self.send_and_assert_no_replies(self.pg0, p_spoof_loose)
 
-        self.assert_error_counter_equal("ip6-rx-urpf-strict", 2 * N_PKTS)
+        self.assert_error_counter_equal("/err/ip6-rx-urpf-strict/uRPF Drop",
+                                        2 * N_PKTS)
 
         #
         # disable uRPF, all traffic should pass
@@ -258,7 +265,8 @@ class TestURPF(VppTestCase):
         self.send_and_expect(self.pg0, p_spoof_strict, self.pg1)
         self.send_and_assert_no_replies(self.pg0, p_spoof_loose)
 
-        self.assert_error_counter_equal("ip6-tx-urpf-loose", N_PKTS)
+        self.assert_error_counter_equal("/err/ip6-tx-urpf-loose/uRPF Drop",
+                                        N_PKTS)
 
         self.vapi.urpf_update(is_input=False,
                               mode=e.vl_api_urpf_mode_t.URPF_API_MODE_STRICT,
@@ -271,7 +279,8 @@ class TestURPF(VppTestCase):
         self.send_and_expect(self.pg0, p_spoof_strict, self.pg1)
         self.send_and_assert_no_replies(self.pg0, p_spoof_loose)
 
-        self.assert_error_counter_equal("ip6-tx-urpf-strict", N_PKTS)
+        self.assert_error_counter_equal("/err/ip6-tx-urpf-strict/uRPF Drop",
+                                        N_PKTS)
 
         # change the strict packet so that it would forward through pg1
         p_spoof_strict = (Ether(dst=self.pg0.local_mac,
@@ -282,7 +291,8 @@ class TestURPF(VppTestCase):
                           Raw(b'\xa5' * 100)) * N_PKTS
 
         self.send_and_assert_no_replies(self.pg0, p_spoof_strict)
-        self.assert_error_counter_equal("ip6-tx-urpf-strict", 2 * N_PKTS)
+        self.assert_error_counter_equal("/err/ip6-tx-urpf-strict/uRPF Drop",
+                                        2 * N_PKTS)
 
         # cleanup
         self.vapi.urpf_update(is_input=False,
index c962ea9..bb971e4 100644 (file)
@@ -38,11 +38,10 @@ typedef enum
     AH_ENCRYPT_N_NEXT,
 } ah_encrypt_next_t;
 
-#define foreach_ah_encrypt_error                                \
- _(RX_PKTS, "AH pkts received")                                 \
- _(CRYPTO_ENGINE_ERROR, "crypto engine error (packet dropped)") \
- _(SEQ_CYCLED, "sequence number cycled")
-
+#define foreach_ah_encrypt_error                                              \
+  _ (RX_PKTS, "AH pkts received")                                             \
+  _ (CRYPTO_ENGINE_ERROR, "crypto engine error (packet dropped)")             \
+  _ (SEQ_CYCLED, "sequence number cycled (packet dropped)")
 
 typedef enum
 {
old mode 100644 (file)
new mode 100755 (executable)
index 4b7a9bc..30bf930
 #!/usr/bin/env python3
-
-from __future__ import print_function
-from cffi import FFI
+#
+# Copyright (c) 2021 Cisco and/or its affiliates.
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+'''
+This module implement Python access to the VPP statistics segment. It
+accesses the data structures directly in shared memory.
+VPP uses optimistic locking, so data structures may change underneath
+us while we are reading. Data is copied out and it's important to
+spend as little time as possible "holding the lock".
+
+Counters are stored in VPP as a two dimensional array.
+Index by thread and index (typically sw_if_index).
+Simple counters count only packets, Combined counters count packets
+and octets.
+
+Counters can be accessed in either dimension.
+stat['/if/rx'] - returns 2D lists
+stat['/if/rx'][0] - returns counters for all interfaces for thread 0
+stat['/if/rx'][0][1] - returns counter for interface 1 on thread 0
+stat['/if/rx'][0][1]['packets'] - returns the packet counter
+                                  for interface 1 on thread 0
+stat['/if/rx'][:, 1] - returns the counters for interface 1 on all threads
+stat['/if/rx'][:, 1].packets() - returns the packet counters for
+                                 interface 1 on all threads
+stat['/if/rx'][:, 1].sum_packets() - returns the sum of packet counters for
+                                     interface 1 on all threads
+stat['/if/rx-miss'][:, 1].sum() - returns the sum of packet counters for
+                                  interface 1 on all threads for simple counters
+'''
+
+import os
+import socket
+import array
+import mmap
+from struct import Struct
 import time
-
-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_INDEX,
-  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 index;
-    uint64_t value;
-    uint64_t *data;
-  };
-  char name[128]; // TODO change this to pointer to "somewhere"
-} stat_segment_directory_entry_t;
-
-typedef struct
-{
-  char *name;
-  stat_directory_type_t type;
-  union
-  {
-    double scalar_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_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
-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.encode('utf-8')))
-    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 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
-    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 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:
-    VPPStatsIOError = VPPStatsIOError
-
+import unittest
+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')
+def get_vec_len(stats, vector_offset):
+    '''Equivalent to VPP vec_len()'''
+    return VEC_LEN_FMT.unpack_from(stats.statseg, vector_offset - 8)[0]
+
+def get_string(stats, ptr):
+    '''Get a string from a VPP vector'''
+    namevector = ptr - stats.base
+    namevectorlen = get_vec_len(stats, namevector)
+    if namevector + namevectorlen >= stats.size:
+        raise ValueError('String overruns stats segment')
+    return stats.statseg[namevector:namevector+namevectorlen-1].decode('ascii')
+
+
+class StatsVector:
+    '''A class representing a VPP vector'''
+
+    def __init__(self, stats, ptr, fmt):
+        self.vec_start = ptr - stats.base
+        self.vec_len = get_vec_len(stats, ptr - stats.base)
+        self.struct = Struct(fmt)
+        self.fmtlen = len(fmt)
+        self.elementsize = self.struct.size
+        self.statseg = stats.statseg
+        self.stats = stats
+
+        if self.vec_start + self.vec_len * self.elementsize >= stats.size:
+            raise ValueError('Vector overruns stats segment')
+
+    def __iter__(self):
+        with self.stats.lock:
+            return self.struct.iter_unpack(self.statseg[self.vec_start:self.vec_start +
+                                                        self.elementsize*self.vec_len])
+
+    def __getitem__(self, index):
+        if index > self.vec_len:
+            raise ValueError('Index beyond end of vector')
+        with self.stats.lock:
+            if self.fmtlen == 1:
+                return self.struct.unpack_from(self.statseg, self.vec_start +
+                                               (index * self.elementsize))[0]
+            return self.struct.unpack_from(self.statseg, self.vec_start +
+                                           (index * self.elementsize))
+
+class VPPStats():
+    '''Main class implementing Python access to the VPP statistics segment'''
+    # pylint: disable=too-many-instance-attributes
+    shared_headerfmt = Struct('QPQQPP')
     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.directory = {}
+        self.lock = StatsLock(self)
         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)
+        self.size = 0
+        self.last_epoch = 0
+        self.error_vectors = 0
+        self.statseg = 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 VPPStatsIOError(retval=rv)
-
-    def heartbeat(self):
-        if not self.connected:
-            self.connect()
-        return self.api.stat_segment_heartbeat_r(self.client)
+        '''Connect to stats segment'''
+        if self.connected:
+            return
+        sock = socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET)
+        sock.connect(self.socketname)
 
-    def ls(self, patterns):
-        if not self.connected:
-            self.connect()
-        return self.api.stat_segment_ls_r(make_string_vector(self.api,
-                                                             patterns),
-                                          self.client)
+        mfd = recv_fd(sock)
+        sock.close()
 
-    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)
+        stat_result = os.fstat(mfd)
+        self.statseg = mmap.mmap(mfd, stat_result.st_size, mmap.PROT_READ, mmap.MAP_SHARED)
+        socket.close(mfd)
 
-        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))]
+        self.size = stat_result.st_size
+        if self.version != 2:
+            raise Exception('Incompatbile stat segment version {}'
+                            .format(self.version))
 
-    def dump(self, counters):
-        if not self.connected:
-            self.connect()
-        stats = {}
-        rv = self.api.stat_segment_dump_r(counters, self.client)
-        # Raise exception and retry
-        if rv == ffi.NULL:
-            raise VPPStatsIOError()
-        rv_len = self.api.stat_segment_vec_len(rv)
-
-        for i in range(rv_len):
-            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
-        return stats
-
-    def get_counter(self, name):
-        retries = 0
-        while True:
-            try:
-                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
-
-    def get_err_counter(self, name):
-        """Get an error counter. The errors from each worker thread
-           are summed"""
-        return sum(self.get_counter(name))
+        self.refresh()
+        self.connected = True
 
     def disconnect(self):
-        try:
-            self.api.stat_segment_disconnect_r(self.client)
-            self.api.stat_client_free(self.client)
+        '''Disconnect from stats segment'''
+        if self.connected:
+            self.statseg.close()
             self.connected = False
-            del self.client
-        except AttributeError:
-            # no need to disconnect if we're not connected
-            pass
+
+    @property
+    def version(self):
+        '''Get version of stats segment'''
+        return self.shared_headerfmt.unpack_from(self.statseg)[0]
+
+    @property
+    def base(self):
+        '''Get base pointer of stats segment'''
+        return self.shared_headerfmt.unpack_from(self.statseg)[1]
+
+    @property
+    def epoch(self):
+        '''Get current epoch value from stats segment'''
+        return self.shared_headerfmt.unpack_from(self.statseg)[2]
+
+    @property
+    def in_progress(self):
+        '''Get value of in_progress from stats segment'''
+        return self.shared_headerfmt.unpack_from(self.statseg)[3]
+
+    @property
+    def directory_vector(self):
+        '''Get pointer of directory vector'''
+        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'
+
+    def refresh(self):
+        '''Refresh directory vector cache (epoch changed)'''
+        directory = {}
+        with self.lock:
+            for direntry in StatsVector(self, self.directory_vector, self.elementfmt):
+                path_raw = direntry[2].find(b'\x00')
+                path = direntry[2][:path_raw].decode('ascii')
+                directory[path] = StatsEntry(direntry[0], direntry[1])
+            self.last_epoch = self.epoch
+            self.directory = directory
+
+            # 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'))
+
+    def __getitem__(self, item):
+        if not self.connected:
+            self.connect()
+        if self.last_epoch != self.epoch:
+            self.refresh()
+        with self.lock:
+            return self.directory[item].get_counter(self)
+
+    def __iter__(self):
+        return iter(self.directory.items())
 
     def set_errors(self):
-        '''Return all errors counters > 0'''
-        retries = 0
-        while True:
-            try:
-                error_names = self.ls(['/err/'])
-                error_counters = self.dump(error_names)
-                break
-            except VPPStatsIOError:
-                if retries > 10:
-                    return None
-                retries += 1
-
-        return {k: sum(error_counters[k])
-                for k in error_counters.keys() if sum(error_counters[k])}
+        '''Return dictionary of error counters > 0'''
+        if not self.connected:
+            self.connect()
+
+        errors = {k:v for k, v in self.directory.items() if k.startswith("/err/")}
+        result = {}
+        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:
+                    result[k] = total
+        return result
 
     def set_errors_str(self):
         '''Return all errors counters > 0 pretty printed'''
-        s = ['ERRORS:']
+        error_string = ['ERRORS:']
         error_counters = self.set_errors()
         for k in sorted(error_counters):
-            s.append('{:<60}{:>10}'.format(k, error_counters[k]))
-        return '%s\n' % '\n'.join(s)
+            error_string.append('{:<60}{:>10}'.format(k, error_counters[k]))
+        return '%s\n' % '\n'.join(error_string)
+
+    def get_counter(self, name):
+        '''Alternative call to __getitem__'''
+        return self.__getitem__(name)
+
+    def get_err_counter(self, name):
+        '''Return a single value (sum of all threads)'''
+        if not self.connected:
+            self.connect()
+        return sum(self.directory[name].get_counter(self))
+
+    def ls(self, patterns):
+        '''Returns list of counters matching pattern'''
+        # pylint: disable=invalid-name
+        if not self.connected:
+            self.connect()
+        regex = [re.compile(i) for i in patterns]
+        return [k for k, v in self.directory.items()
+                if any(re.match(pattern, k) for pattern in regex)]
+
+    def dump(self, counters):
+        '''Given a list of counters return a dictionary of results'''
+        if not self.connected:
+            self.connect()
+        result = {}
+        for cnt in counters:
+            result[cnt] = self.__getitem__(cnt)
+        return result
+
+class StatsLock():
+    '''Stat segment optimistic locking'''
+
+    def __init__(self, stats):
+        self.stats = stats
+        self.epoch = 0
+
+    def __enter__(self):
+        acquired = self.acquire(blocking=True)
+        assert acquired, "Lock wasn't acquired, but blocking=True"
+        return self
+
+    def __exit__(self, exc_type=None, exc_value=None, traceback=None):
+        self.release()
+
+    def acquire(self, blocking=True, timeout=-1):
+        '''Acquire the lock. Await in progress to go false. Record epoch.'''
+        self.epoch = self.stats.epoch
+        if timeout > 0:
+            start = time.monotonic()
+        while self.stats.in_progress:
+            if not blocking:
+                time.sleep(0.01)
+                if timeout > 0:
+                    if start + time.monotonic() > timeout:
+                        return False
+        return True
+
+    def release(self):
+        '''Check if data read while locked is valid'''
+        if self.stats.in_progress or self.stats.epoch != self.epoch:
+            raise IOError('Optimistic lock failed, retry')
+
+    def locked(self):
+        '''Not used'''
+
+
+class StatsCombinedList(list):
+    '''Column slicing for Combined counters list'''
+
+    def __getitem__(self, item):
+        '''Supports partial numpy style 2d support. Slice by column [:,1]'''
+        if isinstance(item, int):
+            return list.__getitem__(self, item)
+        return CombinedList([row[item[1]] for row in self])
+
+class CombinedList(list):
+    '''Combined Counters 2-dimensional by thread by index of packets/octets'''
+
+    def packets(self):
+        '''Return column (2nd dimension). Packets for all threads'''
+        return [pair[0] for pair in self]
+
+    def octets(self):
+        '''Return column (2nd dimension). Octets for all threads'''
+        return [pair[1] for pair in self]
+
+    def sum_packets(self):
+        '''Return column (2nd dimension). Sum of all packets for all threads'''
+        return sum(self.packets())
+
+    def sum_octets(self):
+        '''Return column (2nd dimension). Sum of all octets for all threads'''
+        return sum(self.octets())
+
+class StatsTuple(tuple):
+    '''A Combined vector tuple (packets, octets)'''
+    def __init__(self, data):
+        self.dictionary = {'packets': data[0], 'bytes': data[1]}
+        super().__init__()
+
+    def __repr__(self):
+        return dict.__repr__(self.dictionary)
+
+    def __getitem__(self, item):
+        if isinstance(item, int):
+            return tuple.__getitem__(self, item)
+        if item == 'packets':
+            return tuple.__getitem__(self, 0)
+        return tuple.__getitem__(self, 1)
+
+class StatsSimpleList(list):
+    '''Simple Counters 2-dimensional by thread by index of packets'''
+
+    def __getitem__(self, item):
+        '''Supports partial numpy style 2d support. Slice by column [:,1]'''
+        if isinstance(item, int):
+            return list.__getitem__(self, item)
+        return SimpleList([row[item[1]] for row in self])
+
+class SimpleList(list):
+    '''Simple counter'''
+
+    def sum(self):
+        '''Sum the vector'''
+        return sum(self)
+
+class StatsEntry():
+    '''An individual stats entry'''
+    # pylint: disable=unused-argument,no-self-use
+
+    def __init__(self, stattype, statvalue):
+        self.type = stattype
+        self.value = statvalue
+
+        if stattype == 1:
+            self.function = self.scalar
+        elif stattype == 2:
+            self.function = self.simple
+        elif stattype == 3:
+            self.function = self.combined
+        elif stattype == 4:
+            self.function = self.error
+        elif stattype == 5:
+            self.function = self.name
+        else:
+            self.function = self.illegal
+
+    def illegal(self, stats):
+        '''Invalid or unknown counter type'''
+        return None
+
+    def scalar(self, stats):
+        '''Scalar counter'''
+        return self.value
+
+    def simple(self, stats):
+        '''Simple counter'''
+        counter = StatsSimpleList()
+        for threads in StatsVector(stats, self.value, 'P'):
+            clist = [v[0] for v in StatsVector(stats, threads[0], 'Q')]
+            counter.append(clist)
+        return counter
+
+    def combined(self, stats):
+        '''Combined counter'''
+        counter = StatsCombinedList()
+        for threads in StatsVector(stats, self.value, 'P'):
+            clist = [StatsTuple(cnt) for cnt in StatsVector(stats, threads[0], 'QQ')]
+            counter.append(clist)
+        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):
+        '''Name counter'''
+        counter = []
+        for name in StatsVector(stats, self.value, 'P'):
+            counter.append(get_string(stats, name[0]))
+        return counter
+
+    def get_counter(self, stats):
+        '''Return a list of counters'''
+        return self.function(stats)
+
+class TestStats(unittest.TestCase):
+    '''Basic statseg tests'''
+
+    def setUp(self):
+        '''Connect to statseg'''
+        self.stat = VPPStats()
+        self.stat.connect()
+        self.profile = cProfile.Profile()
+        self.profile.enable()
+
+    def tearDown(self):
+        '''Disconnect from statseg'''
+        self.stat.disconnect()
+        profile = Stats(self.profile)
+        profile.strip_dirs()
+        profile.sort_stats('cumtime')
+        profile.print_stats()
+        print("\n--->>>")
+
+    def test_counters(self):
+        '''Test access to statseg'''
+
+        print('/err/abf-input-ip4/missed', self.stat['/err/abf-input-ip4/missed'])
+        print('/sys/heartbeat', self.stat['/sys/heartbeat'])
+        print('/if/names', self.stat['/if/names'])
+        print('/if/rx-miss', self.stat['/if/rx-miss'])
+        print('/if/rx-miss', self.stat['/if/rx-miss'][1])
+        print('/nat44-ed/out2in/slowpath/drops', self.stat['/nat44-ed/out2in/slowpath/drops'])
+        print('Set Errors', self.stat.set_errors())
+        with self.assertRaises(KeyError):
+            print('NO SUCH COUNTER', self.stat['foobar'])
+        print('/if/rx', self.stat.get_counter('/if/rx'))
+        print('/err/ethernet-input/no error',
+              self.stat.get_err_counter('/err/ethernet-input/no error'))
+
+    def test_column(self):
+        '''Test column slicing'''
+
+        print('/if/rx-miss', self.stat['/if/rx-miss'])
+        print('/if/rx', self.stat['/if/rx'])  # All interfaces for thread #1
+        print('/if/rx thread #1', self.stat['/if/rx'][0])  # All interfaces for thread #1
+        print('/if/rx thread #1, interface #1',
+              self.stat['/if/rx'][0][1])  # All interfaces for thread #1
+        print('/if/rx if_index #1', self.stat['/if/rx'][:, 1])
+        print('/if/rx if_index #1 packets', self.stat['/if/rx'][:, 1].packets())
+        print('/if/rx if_index #1 packets', self.stat['/if/rx'][:, 1].sum_packets())
+        print('/if/rx if_index #1 packets', self.stat['/if/rx'][:, 1].octets())
+        print('/if/rx-miss', self.stat['/if/rx-miss'])
+        print('/if/rx-miss if_index #1 packets', self.stat['/if/rx-miss'][:, 1].sum())
+        print('/if/rx if_index #1 packets', self.stat['/if/rx'][0][1]['packets'])
+
+    def test_error(self):
+        '''Test the error vector'''
+
+        print('/err/ethernet-input', self.stat['/err/ethernet-input/no error'])
+        print('/err/nat44-ei-ha/pkts-processed', self.stat['/err/nat44-ei-ha/pkts-processed'])
+        print('/err/ethernet-input', self.stat.get_err_counter('/err/ethernet-input/no error'))
+        print('/err/ethernet-input', self.stat['/err/ethernet-input/no error'].sum())
+
+    def test_nat44(self):
+        '''Test the nat counters'''
+
+        print('/nat44-ei/ha/del-event-recv', self.stat['/nat44-ei/ha/del-event-recv'])
+        print('/err/nat44-ei-ha/pkts-processed', self.stat['/err/nat44-ei-ha/pkts-processed'].sum())
+
+    def test_legacy(self):
+        '''Legacy interface'''
+        directory = self.stat.ls(["^/if", "/err/ip4-input", "/sys/node/ip4-input"])
+        data = self.stat.dump(directory)
+        print(data)
+        print('Looking up sys node')
+        directory = self.stat.ls(["^/sys/node"])
+        print('Dumping sys node')
+        data = self.stat.dump(directory)
+        print(data)
+        directory = self.stat.ls(["^/foobar"])
+        data = self.stat.dump(directory)
+        print(data)
+
+if __name__ == '__main__':
+    import cProfile
+    from pstats import Stats
+
+    unittest.main()
index 22a9509..f3c74ce 100644 (file)
@@ -1177,7 +1177,7 @@ class VppTestCase(unittest.TestCase):
                           "packet counter `%s'" % counter)
 
     def assert_error_counter_equal(self, counter, expected_value):
-        counter_value = self.statistics.get_err_counter(counter)
+        counter_value = self.statistics[counter].sum()
         self.assert_equal(counter_value, expected_value,
                           "error counter `%s'" % counter)
 
index 62271d3..fd9d91f 100644 (file)
@@ -328,8 +328,9 @@ class IpsecTra4(object):
         p = self.params[socket.AF_INET]
         esn_en = p.vpp_tra_sa.esn_en
 
-        seq_cycle_node_name = ('/err/%s/sequence number cycled' %
-                               self.tra4_encrypt_node_name)
+        seq_cycle_node_name = \
+            ('/err/%s/sequence number cycled (packet dropped)' %
+             self.tra4_encrypt_node_name)
         replay_count = self.get_replay_counts(p)
         hash_failed_count = self.get_hash_failed_counts(p)
         seq_cycle_count = self.statistics.get_err_counter(seq_cycle_node_name)
index b72d464..6534dd6 100644 (file)
@@ -654,10 +654,8 @@ class TestIpsec4MultiTunIfEsp(TemplateIpsec4TunProtect,
         """Multiple IPSEC tunnel interfaces """
         for p in self.multi_params:
             self.verify_tun_44(p, count=127)
-            c = p.tun_if.get_rx_stats()
-            self.assertEqual(c['packets'], 127)
-            c = p.tun_if.get_tx_stats()
-            self.assertEqual(c['packets'], 127)
+            self.assertEqual(p.tun_if.get_rx_stats(), 127)
+            self.assertEqual(p.tun_if.get_tx_stats(), 127)
 
     def test_tun_rr_44(self):
         """ Round-robin packets acrros multiple interface """
@@ -929,10 +927,8 @@ class TestIpsec6MultiTunIfEsp(TemplateIpsec6TunProtect,
         """Multiple IPSEC tunnel interfaces """
         for p in self.multi_params:
             self.verify_tun_66(p, count=127)
-            c = p.tun_if.get_rx_stats()
-            self.assertEqual(c['packets'], 127)
-            c = p.tun_if.get_tx_stats()
-            self.assertEqual(c['packets'], 127)
+            self.assertEqual(p.tun_if.get_rx_stats(), 127)
+            self.assertEqual(p.tun_if.get_tx_stats(), 127)
 
 
 class TestIpsecGreTebIfEsp(TemplateIpsec,
@@ -1997,17 +1993,13 @@ class TestIpsec4TunProtect(TemplateIpsec,
         self.config_protect(p)
 
         self.verify_tun_44(p, count=127)
-        c = p.tun_if.get_rx_stats()
-        self.assertEqual(c['packets'], 127)
-        c = p.tun_if.get_tx_stats()
-        self.assertEqual(c['packets'], 127)
+        self.assertEqual(p.tun_if.get_rx_stats(), 127)
+        self.assertEqual(p.tun_if.get_tx_stats(), 127)
 
         self.vapi.cli("clear ipsec sa")
         self.verify_tun_64(p, count=127)
-        c = p.tun_if.get_rx_stats()
-        self.assertEqual(c['packets'], 254)
-        c = p.tun_if.get_tx_stats()
-        self.assertEqual(c['packets'], 254)
+        self.assertEqual(p.tun_if.get_rx_stats(), 254)
+        self.assertEqual(p.tun_if.get_tx_stats(), 254)
 
         # rekey - create new SAs and update the tunnel protection
         np = copy.copy(p)
@@ -2024,10 +2016,8 @@ class TestIpsec4TunProtect(TemplateIpsec,
         self.unconfig_sa(p)
 
         self.verify_tun_44(np, count=127)
-        c = p.tun_if.get_rx_stats()
-        self.assertEqual(c['packets'], 381)
-        c = p.tun_if.get_tx_stats()
-        self.assertEqual(c['packets'], 381)
+        self.assertEqual(p.tun_if.get_rx_stats(), 381)
+        self.assertEqual(p.tun_if.get_tx_stats(), 381)
 
         # teardown
         self.unconfig_protect(np)
@@ -2074,10 +2064,8 @@ class TestIpsec4TunProtectUdp(TemplateIpsec,
         p = self.ipv4_params
 
         self.verify_tun_44(p, count=127)
-        c = p.tun_if.get_rx_stats()
-        self.assertEqual(c['packets'], 127)
-        c = p.tun_if.get_tx_stats()
-        self.assertEqual(c['packets'], 127)
+        self.assertEqual(p.tun_if.get_rx_stats(), 127)
+        self.assertEqual(p.tun_if.get_tx_stats(), 127)
 
     def test_keepalive(self):
         """ IPSEC NAT Keepalive """
@@ -2168,10 +2156,8 @@ class TestIpsec4TunProtectTun(TemplateIpsec,
 
         self.verify_tun_44(p, count=127)
 
-        c = p.tun_if.get_rx_stats()
-        self.assertEqual(c['packets'], 127)
-        c = p.tun_if.get_tx_stats()
-        self.assertEqual(c['packets'], 127)
+        self.assertEqual(p.tun_if.get_rx_stats(), 127)
+        self.assertEqual(p.tun_if.get_tx_stats(), 127)
 
         # rekey - create new SAs and update the tunnel protection
         np = copy.copy(p)
@@ -2188,10 +2174,8 @@ class TestIpsec4TunProtectTun(TemplateIpsec,
         self.unconfig_sa(p)
 
         self.verify_tun_44(np, count=127)
-        c = p.tun_if.get_rx_stats()
-        self.assertEqual(c['packets'], 254)
-        c = p.tun_if.get_tx_stats()
-        self.assertEqual(c['packets'], 254)
+        self.assertEqual(p.tun_if.get_rx_stats(), 254)
+        self.assertEqual(p.tun_if.get_tx_stats(), 254)
 
         # teardown
         self.unconfig_protect(np)
@@ -2275,10 +2259,8 @@ class TestIpsec6TunProtect(TemplateIpsec,
         self.config_protect(p)
 
         self.verify_tun_66(p, count=127)
-        c = p.tun_if.get_rx_stats()
-        self.assertEqual(c['packets'], 127)
-        c = p.tun_if.get_tx_stats()
-        self.assertEqual(c['packets'], 127)
+        self.assertEqual(p.tun_if.get_rx_stats(), 127)
+        self.assertEqual(p.tun_if.get_tx_stats(), 127)
 
         # rekey - create new SAs and update the tunnel protection
         np = copy.copy(p)
@@ -2295,10 +2277,8 @@ class TestIpsec6TunProtect(TemplateIpsec,
         self.unconfig_sa(p)
 
         self.verify_tun_66(np, count=127)
-        c = p.tun_if.get_rx_stats()
-        self.assertEqual(c['packets'], 254)
-        c = p.tun_if.get_tx_stats()
-        self.assertEqual(c['packets'], 254)
+        self.assertEqual(p.tun_if.get_rx_stats(), 254)
+        self.assertEqual(p.tun_if.get_tx_stats(), 254)
 
         # bounce the interface state
         p.tun_if.admin_down()
@@ -2342,10 +2322,8 @@ class TestIpsec6TunProtect(TemplateIpsec,
         self.verify_tun_66(np3, np3, count=127)
         self.verify_drop_tun_66(np, count=127)
 
-        c = p.tun_if.get_rx_stats()
-        self.assertEqual(c['packets'], 127*9)
-        c = p.tun_if.get_tx_stats()
-        self.assertEqual(c['packets'], 127*8)
+        self.assertEqual(p.tun_if.get_rx_stats(), 127*9)
+        self.assertEqual(p.tun_if.get_tx_stats(), 127*8)
         self.unconfig_sa(np)
 
         # teardown
@@ -2363,10 +2341,8 @@ class TestIpsec6TunProtect(TemplateIpsec,
         self.config_protect(p)
 
         self.verify_tun_46(p, count=127)
-        c = p.tun_if.get_rx_stats()
-        self.assertEqual(c['packets'], 127)
-        c = p.tun_if.get_tx_stats()
-        self.assertEqual(c['packets'], 127)
+        self.assertEqual(p.tun_if.get_rx_stats(), 127)
+        self.assertEqual(p.tun_if.get_tx_stats(), 127)
 
         # teardown
         self.unconfig_protect(p)
@@ -2447,10 +2423,8 @@ class TestIpsec6TunProtectTun(TemplateIpsec,
 
         self.verify_tun_66(p, count=127)
 
-        c = p.tun_if.get_rx_stats()
-        self.assertEqual(c['packets'], 127)
-        c = p.tun_if.get_tx_stats()
-        self.assertEqual(c['packets'], 127)
+        self.assertEqual(p.tun_if.get_rx_stats(), 127)
+        self.assertEqual(p.tun_if.get_tx_stats(), 127)
 
         # rekey - create new SAs and update the tunnel protection
         np = copy.copy(p)
@@ -2467,10 +2441,8 @@ class TestIpsec6TunProtectTun(TemplateIpsec,
         self.unconfig_sa(p)
 
         self.verify_tun_66(np, count=127)
-        c = p.tun_if.get_rx_stats()
-        self.assertEqual(c['packets'], 254)
-        c = p.tun_if.get_tx_stats()
-        self.assertEqual(c['packets'], 254)
+        self.assertEqual(p.tun_if.get_rx_stats(), 254)
+        self.assertEqual(p.tun_if.get_tx_stats(), 254)
 
         # teardown
         self.unconfig_protect(np)
@@ -2630,29 +2602,23 @@ class TestIpsecItf4(TemplateIpsec,
         self.config_protect(p)
 
         self.verify_tun_44(p, count=n_pkts)
-        c = p.tun_if.get_rx_stats()
-        self.assertEqual(c['packets'], n_pkts)
-        c = p.tun_if.get_tx_stats()
-        self.assertEqual(c['packets'], n_pkts)
+        self.assertEqual(p.tun_if.get_rx_stats(), n_pkts)
+        self.assertEqual(p.tun_if.get_tx_stats(), n_pkts)
 
         p.tun_if.admin_down()
         self.verify_tun_dropped_44(p, count=n_pkts)
         p.tun_if.admin_up()
         self.verify_tun_44(p, count=n_pkts)
 
-        c = p.tun_if.get_rx_stats()
-        self.assertEqual(c['packets'], 3*n_pkts)
-        c = p.tun_if.get_tx_stats()
-        self.assertEqual(c['packets'], 2*n_pkts)
+        self.assertEqual(p.tun_if.get_rx_stats(), 3*n_pkts)
+        self.assertEqual(p.tun_if.get_tx_stats(), 2*n_pkts)
 
         # it's a v6 packet when its encrypted
         self.tun4_encrypt_node_name = "esp6-encrypt-tun"
 
         self.verify_tun_64(p, count=n_pkts)
-        c = p.tun_if.get_rx_stats()
-        self.assertEqual(c['packets'], 4*n_pkts)
-        c = p.tun_if.get_tx_stats()
-        self.assertEqual(c['packets'], 3*n_pkts)
+        self.assertEqual(p.tun_if.get_rx_stats(), 4*n_pkts)
+        self.assertEqual(p.tun_if.get_tx_stats(), 3*n_pkts)
 
         self.tun4_encrypt_node_name = "esp4-encrypt-tun"
 
@@ -2675,10 +2641,8 @@ class TestIpsecItf4(TemplateIpsec,
         self.unconfig_sa(p)
 
         self.verify_tun_44(np, count=n_pkts)
-        c = p.tun_if.get_rx_stats()
-        self.assertEqual(c['packets'], n_pkts)
-        c = p.tun_if.get_tx_stats()
-        self.assertEqual(c['packets'], n_pkts)
+        self.assertEqual(p.tun_if.get_rx_stats(), n_pkts)
+        self.assertEqual(p.tun_if.get_tx_stats(), n_pkts)
 
         # teardown
         self.unconfig_protect(np)
@@ -2735,10 +2699,8 @@ class TestIpsecItf4(TemplateIpsec,
         policer.apply_vpp_config(p.tun_if.sw_if_index, True)
 
         self.verify_tun_44(p, count=n_pkts)
-        c = p.tun_if.get_rx_stats()
-        self.assertEqual(c['packets'], n_pkts)
-        c = p.tun_if.get_tx_stats()
-        self.assertEqual(c['packets'], n_pkts)
+        self.assertEqual(p.tun_if.get_rx_stats(), n_pkts)
+        self.assertEqual(p.tun_if.get_tx_stats(), n_pkts)
 
         stats = policer.get_stats()
 
@@ -2942,29 +2904,23 @@ class TestIpsecItf6(TemplateIpsec,
         self.config_protect(p)
 
         self.verify_tun_66(p, count=n_pkts)
-        c = p.tun_if.get_rx_stats()
-        self.assertEqual(c['packets'], n_pkts)
-        c = p.tun_if.get_tx_stats()
-        self.assertEqual(c['packets'], n_pkts)
+        self.assertEqual(p.tun_if.get_rx_stats(), n_pkts)
+        self.assertEqual(p.tun_if.get_tx_stats(), n_pkts)
 
         p.tun_if.admin_down()
         self.verify_drop_tun_66(p, count=n_pkts)
         p.tun_if.admin_up()
         self.verify_tun_66(p, count=n_pkts)
 
-        c = p.tun_if.get_rx_stats()
-        self.assertEqual(c['packets'], 3*n_pkts)
-        c = p.tun_if.get_tx_stats()
-        self.assertEqual(c['packets'], 2*n_pkts)
+        self.assertEqual(p.tun_if.get_rx_stats(), 3*n_pkts)
+        self.assertEqual(p.tun_if.get_tx_stats(), 2*n_pkts)
 
         # it's a v4 packet when its encrypted
         self.tun6_encrypt_node_name = "esp4-encrypt-tun"
 
         self.verify_tun_46(p, count=n_pkts)
-        c = p.tun_if.get_rx_stats()
-        self.assertEqual(c['packets'], 4*n_pkts)
-        c = p.tun_if.get_tx_stats()
-        self.assertEqual(c['packets'], 3*n_pkts)
+        self.assertEqual(p.tun_if.get_rx_stats(), 4*n_pkts)
+        self.assertEqual(p.tun_if.get_tx_stats(), 3*n_pkts)
 
         self.tun6_encrypt_node_name = "esp6-encrypt-tun"
 
@@ -2993,10 +2949,8 @@ class TestIpsecItf6(TemplateIpsec,
         self.unconfig_sa(p)
 
         self.verify_tun_66(np, count=n_pkts)
-        c = p.tun_if.get_rx_stats()
-        self.assertEqual(c['packets'], n_pkts)
-        c = p.tun_if.get_tx_stats()
-        self.assertEqual(c['packets'], n_pkts)
+        self.assertEqual(p.tun_if.get_rx_stats(), n_pkts)
+        self.assertEqual(p.tun_if.get_tx_stats(), n_pkts)
 
         # teardown
         self.unconfig_protect(np)
@@ -3032,10 +2986,8 @@ class TestIpsecItf6(TemplateIpsec,
         policer.apply_vpp_config(p.tun_if.sw_if_index, True)
 
         self.verify_tun_66(p, count=n_pkts)
-        c = p.tun_if.get_rx_stats()
-        self.assertEqual(c['packets'], n_pkts)
-        c = p.tun_if.get_tx_stats()
-        self.assertEqual(c['packets'], n_pkts)
+        self.assertEqual(p.tun_if.get_rx_stats(), n_pkts)
+        self.assertEqual(p.tun_if.get_tx_stats(), n_pkts)
 
         stats = policer.get_stats()
 
index 300e820..0fa87a3 100644 (file)
@@ -23,8 +23,8 @@ class StatsClientTestCase(VppTestCase):
     def test_set_errors(self):
         """Test set errors"""
         self.assertEqual(self.statistics.set_errors(), {})
-        self.assertEqual(self.statistics.get_counter('/err/ethernet-input/no'),
-                         [0])
+        self.assertEqual(
+            self.statistics.get_counter('/err/ethernet-input/no error'), [0])
 
     def test_client_fd_leak(self):
         """Test file descriptor count - VPP-1486"""
index 7eb586c..b7c830b 100644 (file)
@@ -478,12 +478,12 @@ class VppInterface(metaclass=abc.ABCMeta):
         return self.name
 
     def get_rx_stats(self):
-        c = self.test.statistics.get_counter("^/if/rx$")
-        return c[0][self.sw_if_index]
+        return (self.test.statistics["/if/rx"]
+                [:, self.sw_if_index].sum_packets())
 
     def get_tx_stats(self):
-        c = self.test.statistics.get_counter("^/if/tx$")
-        return c[0][self.sw_if_index]
+        return (self.test.statistics["/if/tx"]
+                [:, self.sw_if_index].sum_packets())
 
     def set_l3_mtu(self, mtu):
         self.test.vapi.sw_interface_set_mtu(self.sw_if_index, [mtu, 0, 0, 0])
index 6f54261..9ba8700 100644 (file)
@@ -83,5 +83,5 @@ class VppNeighbor(VppObject):
         return ("%d:%s" % (self.sw_if_index, self.nbr_addr))
 
     def get_stats(self):
-        c = self._test.statistics.get_counter("/net/adjacency")
+        c = self._test.statistics["/net/adjacency"]
         return c[0][self.stats_index]