From 73b929b3b93b614b055239d3f222bcfca860b068 Mon Sep 17 00:00:00 2001 From: Benjamin Valentin Date: Wed, 4 Aug 2021 16:04:54 +0200 Subject: [PATCH] test_utils: add UDP benchmark --- sys/Makefile | 3 + sys/auto_init/auto_init.c | 6 + sys/include/test_utils/benchmark_udp.h | 113 ++++++++++ sys/shell/commands/Makefile | 3 + sys/shell/commands/sc_benchmark_udp.c | 66 ++++++ sys/shell/commands/shell_commands.c | 7 + sys/test_utils/Makefile.dep | 5 + sys/test_utils/benchmark_udp/Makefile | 1 + sys/test_utils/benchmark_udp/benchmark_udp.c | 213 +++++++++++++++++++ 9 files changed, 417 insertions(+) create mode 100644 sys/include/test_utils/benchmark_udp.h create mode 100644 sys/shell/commands/sc_benchmark_udp.c create mode 100644 sys/test_utils/benchmark_udp/Makefile create mode 100644 sys/test_utils/benchmark_udp/benchmark_udp.c diff --git a/sys/Makefile b/sys/Makefile index 5f36158ceb..c1fa9b8f64 100644 --- a/sys/Makefile +++ b/sys/Makefile @@ -2,6 +2,9 @@ ifneq (,$(filter asymcute,$(USEMODULE))) DIRS += net/application_layer/asymcute endif +ifneq (,$(filter benchmark_udp,$(USEMODULE))) + DIRS += test_utils/benchmark_udp +endif ifneq (,$(filter bluetil_%,$(USEMODULE))) DIRS += net/ble/bluetil endif diff --git a/sys/auto_init/auto_init.c b/sys/auto_init/auto_init.c index fdc1b0b1b0..d64698370e 100644 --- a/sys/auto_init/auto_init.c +++ b/sys/auto_init/auto_init.c @@ -292,4 +292,10 @@ void auto_init(void) extern void auto_init_screen(void); auto_init_screen(); } + + if (IS_USED(MODULE_AUTO_INIT_BENCHMARK_UDP)) { + LOG_DEBUG("Auto init UDP benchmark\n"); + extern void benchmark_udp_auto_init(void); + benchmark_udp_auto_init(); + } } diff --git a/sys/include/test_utils/benchmark_udp.h b/sys/include/test_utils/benchmark_udp.h new file mode 100644 index 0000000000..3b984913bf --- /dev/null +++ b/sys/include/test_utils/benchmark_udp.h @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2021 ML!PA Consulting GmbH + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup test_utils_benchmark_udp UDP benchmark + * @ingroup sys + * + * @{ + * @file + * @brief Continuously send UDP packets with configurable size and interval. + * + * @author Benjamin Valentin + */ + +#ifndef TEST_UTILS_BENCHMARK_UDP_H +#define TEST_UTILS_BENCHMARK_UDP_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Maximum size of a benchmark packet + */ +#ifndef BENCH_PAYLOAD_SIZE_MAX +#define BENCH_PAYLOAD_SIZE_MAX (1024) +#endif + +/** + * @brief Default address of the benchmark server + */ +#ifndef BENCH_SERVER_DEFAULT +#define BENCH_SERVER_DEFAULT "fd00:dead:beef::1" +#endif + +/** + * @brief Default port of the benchmark server + */ +#ifndef BENCH_PORT_DEFAULT +#define BENCH_PORT_DEFAULT (12345) +#endif + +/** + * @brief Flag indicating the benchmark packet is a configuration command. + */ +#define BENCH_FLAG_CMD_PKT (1 << 0) + +/** + * @brief Configuration Cookie mask. + */ +#define BENCH_MASK_COOKIE (0xFFFFFF00) + +/** + * @brief Benchmark message to the server + * @note Both server and client are assumed to be little endian machines + * @{ + */ +typedef struct { + uint32_t flags; /**< must include config cookie */ + uint32_t seq_no; /**< number of packets sent sind config update */ + uint32_t replies; /**< number of replies received from server */ + uint32_t rtt_last; /**< round trip time of the last packet */ + uint8_t payload[]; /**< variable length payload */ +} benchmark_msg_ping_t; +/** @} */ + +/** + * @brief Command response from the server + * @note Both server and client are assumed to be little endian machines + * @{ + */ +typedef struct { + uint32_t flags; /**< contains new config cookie */ + uint32_t delay_us; /**< delay between benchmark messages in µs */ + uint16_t payload_len; /**< payload of benchmark messages */ +} benchmark_msg_cmd_t; +/** @} */ + +/** + * @brief This will start the benchmark process. + * Two threads will be spawned, one to send packets to the server + * and one to handle the response. + * + * @param[in] server benchmark server (address or hostname) + * @param[in] port benchmark server port + * + * @return 0 on success + * error otherwise + */ +int benchmark_udp_start(const char *server, uint16_t port); + +/** + * @brief Stop the benchmark process + * + * @return true if the benchmark process was stopped + * false if no benchmark process was running + */ +bool benchmark_udp_stop(void); + +#ifdef __cplusplus +} +#endif + +#endif /* TEST_UTILS_BENCHMARK_UDP_H */ +/** @} */ diff --git a/sys/shell/commands/Makefile b/sys/shell/commands/Makefile index 55b2904645..1ceaf16de0 100644 --- a/sys/shell/commands/Makefile +++ b/sys/shell/commands/Makefile @@ -5,6 +5,9 @@ SRC = shell_commands.c sc_sys.c ifneq (,$(filter app_metadata,$(USEMODULE))) SRC += sc_app_metadata.c endif +ifneq (,$(filter benchmark_udp,$(USEMODULE))) + SRC += sc_benchmark_udp.c +endif ifneq (,$(filter dfplayer,$(USEMODULE))) SRC += sc_dfplayer.c endif diff --git a/sys/shell/commands/sc_benchmark_udp.c b/sys/shell/commands/sc_benchmark_udp.c new file mode 100644 index 0000000000..d432ad13a6 --- /dev/null +++ b/sys/shell/commands/sc_benchmark_udp.c @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2021 ML!PA Consulting GmbH + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for + * more details. + */ + +/** + * @ingroup sys_shell_commands + * @{ + * + * @file + * + * @author Benjamin Valentin + */ + +#include +#include +#include +#include "test_utils/benchmark_udp.h" +#include "shell.h" + +static const char *bench_server = BENCH_SERVER_DEFAULT; +static uint16_t bench_port = BENCH_PORT_DEFAULT; + +int _benchmark_udp_handler(int argc, char **argv) +{ + if (argc < 2) { + goto usage; + } + + if (strcmp(argv[1], "start") == 0) { + if (argc > 2) { + bench_server = argv[2]; + } + if (argc > 3) { + bench_port = atoi(argv[3]); + } + return benchmark_udp_start(bench_server, bench_port); + } + if (strcmp(argv[1], "config") == 0) { + if (argc < 3) { + printf("server: %s\n", bench_server); + printf("port : %u\n", bench_port); + } else { + bench_server = argv[2]; + } + if (argc > 3) { + bench_port = atoi(argv[3]); + } + } + if (strcmp(argv[1], "stop") == 0) { + if (benchmark_udp_stop()) { + puts("benchmark process stopped"); + } else { + puts("no benchmark was running"); + } + return 0; + } + +usage: + printf("usage: %s [start|stop|config] \n", argv[0]); + return -1; +} +/** @} */ diff --git a/sys/shell/commands/shell_commands.c b/sys/shell/commands/shell_commands.c index 02aca6fd34..d3d8b97790 100644 --- a/sys/shell/commands/shell_commands.c +++ b/sys/shell/commands/shell_commands.c @@ -163,6 +163,10 @@ extern int _vfs_handler(int argc, char **argv); extern int _ls_handler(int argc, char **argv); #endif +#ifdef MODULE_BENCHMARK_UDP +extern int _benchmark_udp_handler(int argc, char **argv); +#endif + #ifdef MODULE_CONN_CAN extern int _can_handler(int argc, char **argv); #endif @@ -209,6 +213,9 @@ const shell_command_t _shell_command_list[] = { #ifdef MODULE_USB_BOARD_RESET {"bootloader", "Reboot to bootloader", _bootloader_handler}, #endif +#ifdef MODULE_BENCHMARK_UDP + {"bench_udp", "UDP benchmark", _benchmark_udp_handler}, +#endif #ifdef MODULE_CONFIG {"id", "Gets or sets the node's id.", _id_handler}, #endif diff --git a/sys/test_utils/Makefile.dep b/sys/test_utils/Makefile.dep index a3a1635266..f7cff929f4 100644 --- a/sys/test_utils/Makefile.dep +++ b/sys/test_utils/Makefile.dep @@ -4,3 +4,8 @@ endif ifneq (,$(filter test_utils_result_output_%,$(USEMODULE))) USEMODULE += fmt endif +ifneq (,$(filter benchmark_udp,$(USEMODULE))) + USEMODULE += netutils + USEMODULE += sema_inv + USEMODULE += sock_udp +endif diff --git a/sys/test_utils/benchmark_udp/Makefile b/sys/test_utils/benchmark_udp/Makefile new file mode 100644 index 0000000000..48422e909a --- /dev/null +++ b/sys/test_utils/benchmark_udp/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/sys/test_utils/benchmark_udp/benchmark_udp.c b/sys/test_utils/benchmark_udp/benchmark_udp.c new file mode 100644 index 0000000000..ce1b9f1741 --- /dev/null +++ b/sys/test_utils/benchmark_udp/benchmark_udp.c @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2021 ML!PA Consulting GmbH + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for + * more details. + */ + +/** + * @ingroup test_utils_benchmark_udp + * @{ + * + * @file + * + * @author Benjamin Valentin + */ + +#include +#include "net/sock/udp.h" +#include "net/utils.h" +#include "sema_inv.h" +#include "xtimer.h" + +#include "test_utils/benchmark_udp.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +#define MIN(a, b) ((a) > (b) ? (b) : (a)) + +static sock_udp_t sock; +static uint32_t delay_us = US_PER_SEC; +static uint16_t payload_size = 32; + +static char send_thread_stack[THREAD_STACKSIZE_DEFAULT]; +static char listen_thread_stack[THREAD_STACKSIZE_DEFAULT]; + +static uint8_t buf_tx[BENCH_PAYLOAD_SIZE_MAX + sizeof(benchmark_msg_ping_t)]; +static benchmark_msg_ping_t *ping = (void *)buf_tx; + +static bool running; +static sema_inv_t thread_sync; + +struct { + uint32_t seq_no; + uint32_t time_tx_us; +} record_tx[4]; + +static uint32_t _get_rtt(uint32_t seq_num, uint32_t prev) +{ + for (unsigned i = 0; i < ARRAY_SIZE(record_tx); ++i) { + if (record_tx[i].seq_no == seq_num) { + return xtimer_now_usec() - record_tx[i].time_tx_us; + } + } + + return prev; +} + +static void _put_rtt(uint32_t seq_num) { + uint8_t oldest = 0; + uint32_t oldest_diff = 0; + uint32_t now = xtimer_now_usec(); + + for (unsigned i = 0; i < ARRAY_SIZE(record_tx); ++i) { + uint32_t diff = now - record_tx[i].time_tx_us; + if (diff > oldest_diff) { + oldest_diff = diff; + oldest = i; + } + } + + record_tx[oldest].seq_no = seq_num; + record_tx[oldest].time_tx_us = now; +} + +static void *_listen_thread(void *ctx) +{ + (void)ctx; + + static uint8_t buf[BENCH_PAYLOAD_SIZE_MAX + sizeof(benchmark_msg_ping_t)]; + benchmark_msg_cmd_t *cmd = (void *)buf; + + DEBUG_PUTS("bench_udp: listen thread start"); + + while (running) { + ssize_t res; + + res = sock_udp_recv(&sock, buf, sizeof(buf), 2 * delay_us, NULL); + if (res < 0) { + if (res != -ETIMEDOUT) { + printf("Error receiving message: %zd\n", res); + } + continue; + } + + unsigned state = irq_disable(); + if (cmd->flags & BENCH_FLAG_CMD_PKT) { + ping->seq_no = 0; + ping->replies = 0; + ping->flags = cmd->flags & BENCH_MASK_COOKIE; + delay_us = cmd->delay_us; + payload_size = MIN(cmd->payload_len, BENCH_PAYLOAD_SIZE_MAX); + } else { + benchmark_msg_ping_t *pong = (void *)buf; + + ping->replies++; + ping->rtt_last = _get_rtt(pong->seq_no, ping->rtt_last); + } + irq_restore(state); + } + + DEBUG_PUTS("bench_udp: listen thread terminates"); + sema_inv_post(&thread_sync); + + return NULL; +} + +static void *_send_thread(void *ctx) +{ + sock_udp_ep_t remote = *(sock_udp_ep_t*)ctx; + + DEBUG_PUTS("sending thread start"); + + while (running) { + _put_rtt(ping->seq_no); + + if (sock_udp_send(&sock, ping, sizeof(*ping) + payload_size, &remote) < 0) { + puts("Error sending message"); + continue; + } else { + unsigned state = irq_disable(); + ping->seq_no++; + irq_restore(state); + } + + xtimer_usleep(delay_us); + } + + DEBUG_PUTS("bench_udp: sending thread terminates"); + sema_inv_post(&thread_sync); + + return NULL; +} + +int benchmark_udp_start(const char *server, uint16_t port) +{ + netif_t *netif; + sock_udp_ep_t local = { .family = AF_INET6, + .netif = SOCK_ADDR_ANY_NETIF, + .port = port }; + sock_udp_ep_t remote = { .family = AF_INET6, + .port = port }; + + /* stop threads first */ + benchmark_udp_stop(); + + if (sock_udp_create(&sock, &local, NULL, 0) < 0) { + puts("Error creating UDP sock"); + return 1; + } + + if (netutils_get_ipv6((ipv6_addr_t *)&remote.addr.ipv6, &netif, server) < 0) { + puts("can't resolve remote address"); + return 1; + } + if (netif) { + remote.netif = netif_get_id(netif); + } else { + remote.netif = SOCK_ADDR_ANY_NETIF; + } + + running = true; + thread_create(listen_thread_stack, sizeof(listen_thread_stack), + THREAD_PRIORITY_MAIN - 2, THREAD_CREATE_STACKTEST, + _listen_thread, NULL, "UDP receiver"); + thread_create(send_thread_stack, sizeof(send_thread_stack), + THREAD_PRIORITY_MAIN - 1, THREAD_CREATE_STACKTEST, + _send_thread, &remote, "UDP sender"); + return 0; +} + +bool benchmark_udp_stop(void) +{ + if (!running) { + return false; + } + + /* signal threads to stop */ + sema_inv_init(&thread_sync, 2); + running = false; + + DEBUG_PUTS("bench_udp: waiting for threads to terminate"); + + /* wait for threads to terminate */ + sema_inv_wait(&thread_sync); + sock_udp_close(&sock); + + DEBUG_PUTS("bench_udp: threads terminated"); + + /* clear cookie & stack */ + ping->flags = 0; + memset(send_thread_stack, 0, sizeof(send_thread_stack)); + memset(listen_thread_stack, 0, sizeof(listen_thread_stack)); + + return true; +} + +void benchmark_udp_auto_init(void) +{ + benchmark_udp_start(BENCH_SERVER_DEFAULT, BENCH_PORT_DEFAULT); +} +/** @} */