import os import fnmatch import time from hook import Hook from collections import deque # Sphinx creates auto-generated documentation by importing the python source # files and collecting the docstrings from them. The NO_VPP_PAPI flag allows # the vpp_papi_provider.py file to be importable without having to build # the whole vpp api if the user only wishes to generate the test documentation. do_import = True try: no_vpp_papi = os.getenv("NO_VPP_PAPI") if no_vpp_papi == "1": do_import = False except: pass if do_import: from vpp_papi import VPP # from vnet/vnet/mpls/mpls_types.h MPLS_IETF_MAX_LABEL = 0xfffff MPLS_LABEL_INVALID = MPLS_IETF_MAX_LABEL + 1 class L2_VTR_OP: L2_DISABLED = 0 L2_PUSH_1 = 1 L2_PUSH_2 = 2 L2_POP_1 = 3 L2_POP_2 = 4 L2_TRANSLATE_1_1 = 5 L2_TRANSLATE_1_2 = 6 L2_TRANSLATE_2_1 = 7 L2_TRANSLATE_2_2 = 8 class QOS_SOURCE: EXT = 0 VLAN = 1 MPLS = 2 IP = 3 class UnexpectedApiReturnValueError(Exception): """ exception raised when the API return value is unexpected """ pass class VppPapiProvider(object): """VPP-api provider using vpp-papi @property hook: hook object providing before and after api/cli hooks """ _zero, _negative = range(2) def __init__(self, name, shm_prefix, test_class): self.hook = Hook("vpp-papi-provider") self.name = name self.shm_prefix = shm_prefix self.test_class = test_class self._expect_api_retval = self._zero self._expect_stack = [] jsonfiles = [] install_dir = os.getenv('VPP_TEST_INSTALL_PATH') for root, dirnames, filenames in os.walk(install_dir): for filename in fnmatch.filter(filenames, '*.api.json'): jsonfiles.append(os.path.join(root, filename)) self.vpp = VPP(jsonfiles, logger=test_class.logger) self._events = deque() def __enter__(self): return self def expect_negative_api_retval(self): """ Expect API failure """ self._expect_stack.append(self._expect_api_retval) self._expect_api_retval = self._negative return self def expect_zero_api_retval(self): """ Expect API success """ self._expect_stack.append(self._expect_api_retval) self._expect_api_retval = self._zero return self def __exit__(self, exc_type, exc_value, traceback): self._expect_api_retval = self._expect_stack.pop() def register_hook(self, hook): """Replace hook registration with new hook :param hook: """ self.hook = hook def collect_events(self): """ Collect all events from the internal queue and clear the queue. """ e = self._events self._events = deque() return e def wait_for_event(self, timeout, name=None): """ Wait for and return next event. """ if name: self.test_class.logger.debug("Expecting event '%s' within %ss", name, timeout) else: self.test_class.logger.debug("Expecting event within %ss", timeout) if self._events: self.test_class.logger.debug("Not waiting, event already queued") limit = time.time() + timeout while time.time() < limit: if self._events: e = self._events.popleft() if name and type(e).__name__ != name: raise Exception( "Unexpected event received: %s, expected: %s" % (type(e).__name__, name)) self.test_class.logger.debug("Returning event %s:%s" % (name, e)) return e time.sleep(0) # yield raise Exception("Event did not occur within timeout") def __call__(self, name, event): """ Enqueue event in the internal event queue. """ # FIXME use the name instead of relying on type(e).__name__ ? # FIXME #2 if this throws, it is eaten silently, Ole? self.test_class.logger.debug("New event: %s: %s" % (name, event)) self._events.append(event) def connect(self): """Connect the API to VPP""" self.vpp.connect(self.name, self.shm_prefix) self.papi = self.vpp.api self.vpp.register_event_callback(self) def disconnect(self): """Disconnect the API from VPP""" self.vpp.disconnect() def api(self, api_fn, api_args, expected_retval=0): """ Call API function and check it's return value. Call the appropriate hooks before and after the API call :param api_fn: API function to call :param api_args: tuple of API function arguments :param expected_retval: Expected return value (Default value = 0) :returns: reply from the API """ self.hook.before_api(api_fn.__name__, api_args) reply = api_fn(**api_args) if self._expect_api_retval == self._negative: if hasattr(reply, 'retval') and reply.retval >= 0: msg = "API call passed unexpectedly: expected negative "\ "return value instead of %d in %s" % \ (reply.retval, repr(reply)) self.test_class.logger.info(msg) raise UnexpectedApiReturnValueError(msg) elif self._expect_api_retval == self._zero: if hasattr(reply, 'retval') and reply.retval != expected_retval: msg = "API call failed, expected %d return value instead "\ "of %d in %s" % (expected_retval, reply.retval, repr(reply)) self.test_class.logger.info(msg) raise UnexpectedApiReturnValueError(msg) else: raise Exception("Internal error, unexpected value for " "self._expect_api_retval %s" % self._expect_api_retval) self.hook.after_api(api_fn.__name__, api_args) return reply def cli(self, cli): """ Execute a CLI, calling the before/after hooks appropriately. :param cli: CLI to execute :returns: CLI output """ self.hook.before_cli(cli) cli += '\n' r = self.papi.cli_inband(length=len(cli), cmd=cli) self.hook.after_cli(cli) if hasattr(r, 'reply'): return r.reply.decode().rstrip('\x00') def ppcli(self, cli): """ Helper method to print CLI command in case of info logging level. :param cli: CLI to execute :returns: CLI output """ return cli + "\n" + str(self.cli(cli)) def _convert_mac(self, mac): return mac.replace(':', '').decode('hex') def show_version(self): """ """ return self.api(self.papi.show_version, {}) def pg_create_interface(self, pg_index): """ :param pg_index: """ return self.api(self.papi.pg_create_interface, {"interface_id": pg_index}) def sw_interface_dump(self, filter=None): """ :param filter: (Default value = None) """ if filter is not None: args = {"name_filter_valid": 1, "name_filter": filter} else: args = {} return self.api(self.papi.sw_interface_dump, args) def sw_interface_set_table(self, sw_if_index, is_ipv6, table_id): """ Set the IPvX Table-id for the Interface :param sw_if_index: :param is_ipv6: :param table_id: """ return self.api(self.papi.sw_interface_set_table, {'sw_if_index': sw_if_index, 'is_ipv6': is_ipv6, 'vrf_id': table_id}) def sw_interface_add_del_address(self, sw_if_index, addr, addr_len, is_ipv6=0, is_add=1, del_all=0): """ :param addr: param is_ipv6: (Default value = 0) :param sw_if_index: :param addr_len: :param is_ipv6: (Default value = 0) :param is_add: (Default value = 1) :param del_all: (Default value = 0) """ return self.api(self.papi.sw_interface_add_del_address, {'sw_if_index': sw_if_index, 'is_add': is_add, 'is_ipv6': is_ipv6, 'del_all': del_all, 'address_length': addr_len, 'address': addr}) def sw_interface_set_unnumbered(self, sw_if_index, ip_sw_if_index, is_add=1): """ Set the Interface to be unnumbered :param is_add: (Default value = 1) :param sw_if_index - interface That will be unnumbered :param ip_sw_if_index - interface with an IP addres """ return self.api(self.papi.sw_interface_set_unnumbered, {'sw_if_index': ip_sw_if_index, 'unnumbered_sw_if_index': sw_if_index, 'is_add': is_add}) def sw_interface_enable_disable_mpls(self, sw_if_index, is_enable=1): """ Enable/Disable MPLS on the interface :param sw_if_index: :param is_enable: (Default value = 1) """ return self.api(self.papi.sw_interface_set_mpls_enable, {'sw_if_index': sw_if_index, 'enable': is_enable}) def sw_interface_ra_suppress(self, sw_if_index, suppress=1): return self.api(self.papi.sw_interface_ip6nd_ra_config, {'sw_if_index': sw_if_index, 'suppress': suppress}) def set_ip_flow_hash(self, table_id, src=1, dst=1, sport=1, dport=1, proto=1, reverse=0, is_ip6=0): return self.api(self.papi.set_ip_flow_hash, {'vrf_id': table_id, 'src': src, 'dst': dst, 'dport': dport, 'sport': sport, 'proto': proto, 'reverse': reverse, 'is_ipv6': is_ip6}) def ip6_nd_proxy(self, address, sw_if_index, is_del=0): return self.api(self.papi.ip6nd_proxy_add_del, {'address': address, 'sw_if_index': sw_if_index, 'is_del': is_del}) def ip6_sw_interface_ra_config(self, sw_if_index, no, suppress, send_unicast): return self.api(self.papi.sw_interface_ip6nd_ra_config, {'sw_if_index': sw_if_index, 'is_no': no, 'suppress': suppress, 'send_unicast': send_unicast}) def ip6_sw_interface_ra_prefix(self, sw_if_index, address, address_length, use_default=0, no_advertise=0, off_link=0, no_autoconfig=0, no_onlink=0, is_no=0, val_lifetime=0xffffffff, pref_lifetime=0xffffffff): return self.api(self.papi.sw_interface_ip6nd_ra_prefix, {'sw_if_index': sw_if_index, 'address': address, 'address_length': address_length, 'use_default': use_default, 'no_advertise': no_advertise, 'off_link': off_link, 'no_autoconfig': no_autoconfig, 'no_onlink': no_onlink, 'is_no': is_no, 'val_lifetime': val_lifetime, 'pref_lifetime': pref_lifetime}) def ip6_sw_interface_enable_disable(self, sw_if_index, enable): """ Enable/Disable An interface for IPv6 """ return self.api(self.papi.sw_interface_ip6_enable_disable, {'sw_if_index': sw_if_index, 'enable': enable}) def vxlan_add_del_tunnel( self, src_addr, dst_addr, mcast_sw_if_index=0xFFFFFFFF, is_add=1, is_ipv6=0, encap_vrf_id=0, decap_next_index=0xFFFFFFFF, vni=0, instance=0xFFFFFFFF): """ :param dst_addr: :param src_addr: :param is_add: (Default value = 1) :param is_ipv6: (Default value = 0) :param encap_vrf_id: (Default value = 0) :param decap_next_index: (Default value = 0xFFFFFFFF) :param mcast_sw_if_index: (Default value = 0xFFFFFFFF) :param vni: (Default value = 0) :param instance: (Default value = 0xFFFFFFFF) """ return self.api(self.papi.vxlan_add_del_tunnel, {'is_add': is_add, 'is_ipv6': is_ip
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright(c) 2021 Cisco Systems, Inc.
*/
#include <vlib/vlib.h>
#include <vppinfra/time.h>
#include <vppinfra/cache.h>
#include <vppinfra/error.h>
#include <vnet/ethernet/ethernet.h>
#include <vnet/ip/ip.h>
#include <vnet/ip/ip_psh_cksum.h>
static_always_inline void
compute_ip_phc (void *p)
{
if ((((u8 *) p)[0] & 0xf0) == 0x40)
ip4_pseudo_header_cksum (p);
else if ((((u8 *) p)[0] & 0xf0) == 0x60)
ip6_pseudo_header_cksum (p);
}
void
compute_ip_phc_func (void **p, u32 n_packets)
{
u32 n_left_from = n_packets;
while (n_left_from >= 8)
{
clib_prefetch_load (p[4]);
clib_prefetch_load (p[5]);
clib_prefetch_load (p[6]);
clib_prefetch_load (p[7]);
compute_ip_phc (p[0]);
compute_ip_phc (p[1]);
compute_ip_phc (p[2]);
compute_ip_phc (p[3]);
n_left_from -= 4;
p += 4;
}
while (n_left_from > 0)
{
compute_ip_phc (p[0]);
n_left_from -= 1;
p += 1;
}
}
typedef struct _phc_test_data
{
const char *name;
const char *description;
u8 *data;
u32 data_size;
struct _phc_test_data *next;
} phc_test_data_t;
typedef struct
{
int verbose;
char *phc_name;
u32 warmup_rounds;
u32 rounds;
u32 n_buffers;
u32 buffer_size;
phc_test_data_t *phc_test_data;
} phc_test_main_t;
phc_test_main_t phc_test_main;
#define PHC_TEST_REGISTER_DATA(x, ...) \
__VA_ARGS__ phc_test_data_t __phc_test_data_##x; \
static void __clib_constructor __phc_test_data_fn_##x (void) \
{ \
phc_test_main_t *ptm = &phc_test_main; \
__phc_test_data_##x.next = ptm->phc_test_data; \
ptm->phc_test_data = &__phc_test_data_##x; \
} \
__VA_ARGS__ phc_test_data_t __phc_test_data_##x
// ipv4
u8 phc_ipv4_tcp_data[50] = {
0x45, 0x00, 0x05, 0xdc, 0xdb, 0x42, 0x40, 0x00, 0x40, 0x06, 0xc4, 0x85, 0xc0,
0xa8, 0x0a, 0x02, 0xc0, 0xa8, 0x0a, 0x01, 0xd8, 0xde, 0x14, 0x51, 0x34, 0x93,
0xa8, 0x1b, 0x7b, 0xef, 0x2e, 0x7e, 0x80, 0x10, 0x00, 0xe5, 0xc7, 0x03, 0x00,
0x00, 0x01, 0x01, 0x08, 0x0a, 0xce, 0xaa, 0x00, 0x2f, 0xf2, 0xc3
};
PHC_TEST_REGISTER_DATA (ipv4_tcp, static) = {
.name = "ipv4-tcp",
.description = "IPv4 TCP",
.data = phc_ipv4_tcp_data,
.data_size = sizeof (phc_ipv4_tcp_data),
};
// ip6
u8 phc_ipv6_udp_data[65] = {
0x60, 0x0d, 0xf4, 0x97, 0x00, 0x40, 0x3a, 0x40, 0xfd, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0xfd, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,
0x01, 0x80, 0x00, 0x10, 0x84, 0xb1, 0x25, 0x00, 0x01, 0x22, 0x57, 0xf0, 0x60,
0x00, 0x00, 0x00, 0x00, 0xcb, 0x4a, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,
};
PHC_TEST_REGISTER_DATA (ipv6_udp, static) = {
.name = "ipv6-udp",
.description = "IPv6 UDP",
.data = phc_ipv6_udp_data,
.data_size = sizeof (phc_ipv6_udp_data),
};
static void
fill_buffers (vlib_main_t *vm, u32 *buffer_indices, u8 *data, u32 data_size,
u32 n_buffers, u32 buffer_size)
{
int i, j;
u64 seed = clib_cpu_time_now ();
for (i = 0; i < n_buffers; i++)
{
vlib_buffer_t *b = vlib_get_buffer (vm, buffer_indices[i]);
clib_memcpy_fast (b->data, data, data_size);
b->current_data = 0;
for (j = data_size; j < buffer_size; j += 8)
*(u64 *) (b->data + j) = 1 + random_u64 (&seed);
b->current_length = buffer_size;
}
}
static clib_error_t *
test_phc_perf (vlib_main_t *vm, phc_test_main_t *ptm)
{
clib_error_t *err = 0;
u32 buffer_size = vlib_buffer_get_default_data_size (vm);
u32 n_buffers, n_alloc = 0, warmup_rounds, rounds;
u32 *buffer_indices = 0;
u64 t0[5], t1[5];
phc_test_data_t *phc_test_data = ptm->phc_test_data;
void **p = 0;
int i, j;
if (ptm->buffer_size > buffer_size)
return clib_error_return (0, "buffer size must be <= %u", buffer_size);
rounds = ptm->rounds ? ptm->rounds : 100;
n_buffers = ptm->n_buffers ? ptm->n_buffers : 256;
warmup_rounds = ptm->warmup_rounds ? ptm->warmup_rounds : 100;
buffer_size = ptm->buffer_size ? ptm->buffer_size : buffer_size;
vec_validate_aligned (p, n_buffers - 1, CLIB_CACHE_LINE_BYTES);
vec_validate_aligned (buffer_indices, n_buffers - 1, CLIB_CACHE_LINE_BYTES);
n_alloc = vlib_buffer_alloc (vm, buffer_indices, n_buffers);
if (n_alloc != n_buffers)
{
err = clib_error_return (0, "buffer alloc failure");
goto done;
}
vlib_cli_output (
vm,
"pseudo header checksum: buffer-size %u, n_buffers %u rounds %u "
"warmup-rounds %u",
buffer_size, n_buffers, rounds, warmup_rounds);
vlib_cli_output (vm, " cpu-freq %.2f GHz",
(f64) vm->clib_time.clocks_per_second * 1e-9);
while (phc_test_data)
{
fill_buffers (vm, buffer_indices, phc_test_data->data,
phc_test_data->data_size, n_buffers, buffer_size);
for (i = 0; i < n_buffers; i++)
{
vlib_buffer_t *b = vlib_get_buffer (vm, buffer_indices[i]);
p[i] = vlib_buffer_get_current (b);
}
for (i = 0; i < 5; i++)
{
for (j = 0; j < warmup_rounds; j++)
{
compute_ip_phc_func (p, n_buffers);
}
t0[i] = clib_cpu_time_now ();
for (j = 0; j < rounds; j++)
compute_ip_phc_func (p, n_buffers);
t1[i] = clib_cpu_time_now ();
}
vlib_cli_output (
vm, "===========================================================");
vlib_cli_output (vm, " Test: %s", phc_test_data->description);
vlib_cli_output (
vm, "===========================================================");
for (i = 0; i < 5; i++)
{
f64 tpp1 = (f64) (t1[i] - t0[i]) / (n_buffers * rounds);
f64 Mpps1 = vm->clib_time.clocks_per_second * 1e-6 / tpp1;
vlib_cli_output (vm, "%-2u: %.03f ticks/packet, %.02f Mpps\n", i + 1,
tpp1, Mpps1);
}
phc_test_data = phc_test_data->next;
}
done:
if (n_alloc)
vlib_buffer_free (vm, buffer_indices, n_alloc);
vec_free (p);
vec_free (buffer_indices);
return err;
}
static clib_error_t *
test_phc_command_fn (vlib_main_t *vm, unformat_input_t *input,
vlib_cli_command_t *cmd)
{
phc_test_main_t *ptm = &phc_test_main;
clib_error_t *err = 0;
while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
{
if (unformat (input, "verbose"))
ptm->verbose = 1;
else if (unformat (input, "detail"))
ptm->verbose = 2;
else if (unformat (input, "buffers %u", &ptm->n_buffers))
;
else if (unformat (input, "buffer-size %u", &ptm->buffer_size))
;
else if (unformat (input, "rounds %u", &ptm->rounds))
;
else if (unformat (input, "warmup-rounds %u", &ptm->warmup_rounds))
;
else
{
return clib_error_return (0, "unknown input '%U'",
format_unformat_error, input);
}
}
test_phc_perf (vm, ptm);
return err;
}
VLIB_CLI_COMMAND (test_phc_command, static) = {
.path = "test phc",
.short_help = "test phc [buffers <n>] [buffer-size <size>] [rounds <n>] "
"[warmup-rounds <n>]",
.function = test_phc_command_fn,
};
static clib_error_t *
phc_test_init (vlib_main_t *vm)
{
return (0);
}
VLIB_INIT_FUNCTION (phc_test_init);