From 73b929b3b93b614b055239d3f222bcfca860b068 Mon Sep 17 00:00:00 2001 From: Benjamin Valentin Date: Wed, 4 Aug 2021 16:04:54 +0200 Subject: [PATCH 1/3] 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); +} +/** @} */ From 1bc6b7eec71a9a638d79398679efded636b0541b Mon Sep 17 00:00:00 2001 From: Benjamin Valentin Date: Thu, 2 Sep 2021 23:11:57 +0200 Subject: [PATCH 2/3] tools/benchmark_udp: add host tool for benchmark --- dist/tools/Makefile | 2 +- dist/tools/benchmark_udp/Makefile | 19 ++ dist/tools/benchmark_udp/README.md | 51 ++++++ dist/tools/benchmark_udp/main.c | 276 +++++++++++++++++++++++++++++ 4 files changed, 347 insertions(+), 1 deletion(-) create mode 100644 dist/tools/benchmark_udp/Makefile create mode 100644 dist/tools/benchmark_udp/README.md create mode 100644 dist/tools/benchmark_udp/main.c diff --git a/dist/tools/Makefile b/dist/tools/Makefile index b19355f1a6..5f04e16986 100644 --- a/dist/tools/Makefile +++ b/dist/tools/Makefile @@ -1,4 +1,4 @@ -HOST_TOOLS=ethos uhcpd sliptty zep_dispatch +HOST_TOOLS=benchmark_udp ethos uhcpd sliptty zep_dispatch .PHONY: all $(HOST_TOOLS) diff --git a/dist/tools/benchmark_udp/Makefile b/dist/tools/benchmark_udp/Makefile new file mode 100644 index 0000000000..63829f0d63 --- /dev/null +++ b/dist/tools/benchmark_udp/Makefile @@ -0,0 +1,19 @@ +CFLAGS?=-g -O3 -Wall -Wextra + +BINARY := bin/benchmark_server +all: bin $(BINARY) + +bin: + mkdir bin + +run: + $(BINARY) :: 12345 + +RIOTBASE:=../../.. +RIOT_INCLUDES=-I$(RIOTBASE)/core/include -I$(RIOTBASE)/sys/include +SRCS:=$(wildcard *.c) +$(BINARY): $(SRCS) + $(CC) $(CFLAGS) $(CFLAGS_EXTRA) $(RIOT_INCLUDES) -I.. $(SRCS) -o $@ + +clean: + rm -f $(BINARY) diff --git a/dist/tools/benchmark_udp/README.md b/dist/tools/benchmark_udp/README.md new file mode 100644 index 0000000000..3f5a95020e --- /dev/null +++ b/dist/tools/benchmark_udp/README.md @@ -0,0 +1,51 @@ +# UDP Benchmark server + +This is a simple tool to generate load and evaluate the performance and reliability of a network. +Clients will periodically send UDP packets to the benchmark server, the server keeps track of +how many packets have been received and how many the nodes have reported to send. + +By default the server will also echo the packets back to the sender so round-trip time can be +measured. + +### Usage + +### Server + +Run the binary you find in `bin/benchmark_server`. + +e.g. to listen on all addresses on port 12345 run + + bin/benchmark_server :: 12345 + +There are a few command line options available: + + - `-i ` to control the send interval in µs + - `-s ` to control the test packet payload + - `-o` for one-way mode where only the clients send packets to the server, but the server doesn't echo them back. + +Output: + + - 'host': client address + - 'bandwidth': average bandwidth since the last configuration package + - 'num TX': number of packaged produced by the client since the configuration package + - 'num RX': number of packaged received by the server since the configuration package + - 'num RT': number of server echos received by the client since the last configuration package + - 'RTT': round trip time client->server->client (last package received by client) + +### Client + +On the application that you want to benchmark, add the `benchmark_udp` module. + +If you have the shell enabled you can then start the benchmark manually by + + bench_udp start
+ +If port is omitted it will default to `12345` (`BENCH_PORT_DEFAULT`), if the address is omitted `fd00:dead:beef::1` (`BENCH_SERVER_DEFAULT`) will be used. + +If the benchmark should be started automatically, add the `auto_init_benchmark_udp` module. +In this case, `BENCH_SERVER_DEFAULT` and `BENCH_PORT_DEFAULT` will be used. + +They can be overwritten via CFLAGS: + + CFLAGS += -DBENCH_SERVER_DEFAULT=\"\" + CFLAGS += -DBENCH_PORT_DEFAULT= diff --git a/dist/tools/benchmark_udp/main.c b/dist/tools/benchmark_udp/main.c new file mode 100644 index 0000000000..6ff4e2152d --- /dev/null +++ b/dist/tools/benchmark_udp/main.c @@ -0,0 +1,276 @@ +/* + * 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 tools + * @{ + * + * @file + * + * @author Benjamin Valentin + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "list.h" +#include "kernel_defines.h" +#include "test_utils/benchmark_udp.h" + +#define US_PER_MS (1000UL) +#define US_PER_SEC (1000 * US_PER_MS) +#define MS_PER_SEC (1000UL) + +typedef struct { + list_node_t node; + struct sockaddr_in6 addr; + struct timeval first_seen; + uint32_t seq_no; + uint32_t count_tx; + uint32_t count_rx; + uint32_t count_rt; + uint32_t rtt_us; + size_t packet_len; +} bench_client_t; + +static bool one_way; +static uint32_t cookie; +static uint32_t delay_us = 100 * US_PER_MS; /* 100 ms */ +static uint16_t payload_len = 32; + +static char addr_str[INET6_ADDRSTRLEN]; + +static bench_client_t *_find_or_add(list_node_t *head, struct sockaddr_in6 *addr, + bool *new_node) +{ + for (list_node_t* n = head->next; n; n = n->next) { + bench_client_t *node = container_of(n, bench_client_t, node); + + if (memcmp(&addr->sin6_addr, &node->addr.sin6_addr, sizeof(addr->sin6_addr)) == 0) { + *new_node = false; + return node; + } + } + + inet_ntop(AF_INET6, &addr->sin6_addr, addr_str, INET6_ADDRSTRLEN); + printf("adding [%s]:%d\n", addr_str, ntohs(addr->sin6_port)); + bench_client_t *node = calloc(1, sizeof(bench_client_t)); + memcpy(&node->addr, addr, sizeof(*addr)); + list_add(head, &node->node); + + *new_node = true; + return node; +} + +static void clrscr(void) +{ + printf("\e[1;1H\e[2J"); +} + +static uint64_t _tv_diff_msec(struct timeval *a, struct timeval *b) +{ + return (a->tv_sec - b->tv_sec) * MS_PER_SEC + + (a->tv_usec - b->tv_usec) / US_PER_MS; +} + +static void _print_stats(list_node_t *head, struct timeval *now) +{ + static uint8_t max_addr_len; + + printf("host%*s\tbandwidth\tnum TX\tnum RX", max_addr_len - 4, ""); + if (!one_way) { + printf("\t\tnum RT\t\tRTT"); + } + printf("\tpkg size\n"); + + for (list_node_t* n = head->next; n; n = n->next) { + bench_client_t *node = container_of(n, bench_client_t, node); + uint8_t addr_len; + + inet_ntop(AF_INET6, &node->addr.sin6_addr, addr_str, INET6_ADDRSTRLEN); + addr_len = printf("%s", addr_str); + + if (addr_len > max_addr_len) { + max_addr_len = addr_len; + } + + unsigned bw = (node->count_rx * node->packet_len) + / (1 + now->tv_sec - node->first_seen.tv_sec); + + unsigned success_rate = node->count_tx + ? (100 * node->count_rx) / node->count_tx + : 0; + + printf("%*s\t%4u b/s\t%u\t%u (%u%%)", + max_addr_len - addr_len, "", + bw, + node->count_tx, node->count_rx, + success_rate); + if (!one_way) { + unsigned success_rate_rt = node->count_tx + ? (100 * node->count_rt) / node->count_tx + : 0; + printf("\t%u (%u%%)\t%u µs", node->count_rt, success_rate_rt, node->rtt_us); + } + printf("\t%zu\n", node->packet_len); + } +} + +static void dispatch_loop(int sock) +{ + list_node_t head = { .next = NULL }; + struct timeval tv_now, tv_last = { 0 }; + const size_t len_total = payload_len + sizeof(benchmark_msg_ping_t); + uint8_t *buffer = malloc(len_total); + + puts("entering loop…"); + while (1) { + struct sockaddr_in6 src_addr; + socklen_t addr_len = sizeof(src_addr); + + /* receive incoming packet */ + ssize_t bytes_in = recvfrom(sock, buffer, len_total, 0, + (struct sockaddr*)&src_addr, &addr_len); + + if (bytes_in <= 0 || addr_len != sizeof(src_addr)) { + continue; + } + + bool new_node; + bench_client_t *node = _find_or_add(&head, &src_addr, &new_node); + + benchmark_msg_ping_t *ping = (void *)buffer; + node->count_tx = ping->seq_no + 1; + + if (!one_way) { + node->count_rt = ping->replies; + } + node->count_rx++; + node->packet_len = bytes_in; + + if (new_node || (ping->flags & BENCH_MASK_COOKIE) != cookie) { + benchmark_msg_cmd_t *cmd = (void *)buffer; + cmd->flags = BENCH_FLAG_CMD_PKT | cookie; + cmd->delay_us = delay_us; + cmd->payload_len = payload_len; + gettimeofday(&node->first_seen, NULL); + node->count_rx = 0; + + bytes_in = sizeof(*cmd); + new_node = true; + } else if (ping->rtt_last) { + node->rtt_us = node->rtt_us + ? (node->rtt_us + ping->rtt_last) / 2 + : ping->rtt_last; + } + + /* send reply */ + if (!one_way || new_node) { + sendto(sock, buffer, bytes_in, 0, (struct sockaddr*)&src_addr, addr_len); + } + + gettimeofday(&tv_now, NULL); + if (_tv_diff_msec(&tv_now, &tv_last) > 50) { + tv_last = tv_now; + clrscr(); + _print_stats(&head, &tv_now); + } + } +} + +static void _print_help(const char *progname) +{ + fprintf(stderr, "usage: %s [-i send interval] [-s payload size]
\n", + progname); + + fprintf(stderr, "\npositional arguments:\n"); + fprintf(stderr, "\taddress\t\tlocal address to bind to\n"); + fprintf(stderr, "\tport\t\tlocal port to bind to\n"); + + fprintf(stderr, "\noptional arguments:\n"); + fprintf(stderr, "\t-i \tsend interval in µs\n"); + fprintf(stderr, "\t-s \tadded payload size\n"); + fprintf(stderr, "\t-o one-way mode, don't echo back packets\n"); +} + +int main(int argc, char **argv) +{ + const char *progname = argv[0]; + int c; + + while ((c = getopt(argc, argv, "i:s:o")) != -1) { + switch (c) { + case 'i': + delay_us = atoi(optarg); + break; + case 's': + payload_len = atoi(optarg); + break; + case 'o': + one_way = true; + break; + default: + _print_help(progname); + exit(1); + } + } + + argc -= optind; + argv += optind; + + if (argc != 2) { + _print_help(progname); + exit(1); + } + + while (getrandom(&cookie, sizeof(cookie), 0) != sizeof(cookie)) {} + cookie &= BENCH_MASK_COOKIE; + + struct addrinfo hint = { + .ai_family = AF_INET6, + .ai_socktype = SOCK_DGRAM, + .ai_protocol = IPPROTO_UDP, + .ai_flags = AI_NUMERICHOST, + }; + + struct addrinfo *server_addr; + int res = getaddrinfo(argv[0], argv[1], + &hint, &server_addr); + if (res != 0) { + perror("getaddrinfo()"); + exit(1); + } + + int sock = socket(server_addr->ai_family, server_addr->ai_socktype, + server_addr->ai_protocol); + if (sock < 0) { + perror("socket() failed"); + exit(1); + } + + if (bind(sock, server_addr->ai_addr, server_addr->ai_addrlen) < 0) { + perror("bind() failed"); + exit(1); + } + + freeaddrinfo(server_addr); + + dispatch_loop(sock); + + close(sock); + + return 0; +} +/** @} */ From f3aee01e2913898328deb52c77f8440a059ba759 Mon Sep 17 00:00:00 2001 From: Benjamin Valentin Date: Thu, 26 Aug 2021 19:19:18 +0200 Subject: [PATCH 3/3] examples/benchmark_udp: add example for UDP benchmark --- examples/benchmark_udp/Makefile | 53 ++++++++++++++++++++++++++++++ examples/benchmark_udp/Makefile.ci | 40 ++++++++++++++++++++++ examples/benchmark_udp/README.md | 41 +++++++++++++++++++++++ examples/benchmark_udp/main.c | 35 ++++++++++++++++++++ 4 files changed, 169 insertions(+) create mode 100644 examples/benchmark_udp/Makefile create mode 100644 examples/benchmark_udp/Makefile.ci create mode 100644 examples/benchmark_udp/README.md create mode 100644 examples/benchmark_udp/main.c diff --git a/examples/benchmark_udp/Makefile b/examples/benchmark_udp/Makefile new file mode 100644 index 0000000000..cd404f4aeb --- /dev/null +++ b/examples/benchmark_udp/Makefile @@ -0,0 +1,53 @@ +# name of your application +APPLICATION = benchmark_udp + +# If no BOARD is found in the environment, use this default: +BOARD ?= native + +# This has to be the absolute path to the RIOT base directory: +RIOTBASE ?= $(CURDIR)/../.. + +# use GNRC by default +LWIP ?= 0 + +# Include packages that pull up and auto-init the link layer. +# NOTE: 6LoWPAN will be included if IEEE802.15.4 devices are present +ifeq (0,$(LWIP)) + USEMODULE += auto_init_gnrc_netif + USEMODULE += gnrc_ipv6_default + USEMODULE += gnrc_netdev_default +else + USEMODULE += lwip_ipv6 + USEMODULE += lwip_netdev + USEMODULE += netdev_default +endif + +# Add also the shell, some shell commands +USEMODULE += shell +USEMODULE += shell_commands +USEMODULE += ps +USEMODULE += netstats_l2 +USEMODULE += netstats_ipv6 + +# Add the benchmark module +USEMODULE += benchmark_udp + +# Uncomment this to automatically start sending packets to a pre-defined +# benchmark server +# +# USEMODULE += auto_init_benchmark_udp +# CFLAGS += -DBENCH_SERVER_DEFAULT=\"fd00:dead:beef::1\" +# CFLAGS += -DBENCH_PORT_DEFAULT=12345 + +# Comment this out to disable code in RIOT that does safety checking +# which is not needed in a production environment but helps in the +# development process: +DEVELHELP ?= 0 + +# Change this to 0 show compiler invocation lines by default: +QUIET ?= 1 + +include $(RIOTBASE)/Makefile.include + +# Set a custom channel if needed +include $(RIOTMAKE)/default-radio-settings.inc.mk diff --git a/examples/benchmark_udp/Makefile.ci b/examples/benchmark_udp/Makefile.ci new file mode 100644 index 0000000000..3607b9ea61 --- /dev/null +++ b/examples/benchmark_udp/Makefile.ci @@ -0,0 +1,40 @@ +BOARD_INSUFFICIENT_MEMORY := \ + arduino-duemilanove \ + arduino-leonardo \ + arduino-mega2560 \ + arduino-nano \ + arduino-uno \ + atmega1284p \ + atmega328p \ + atmega328p-xplained-mini \ + atxmega-a1u-xpro \ + atxmega-a3bu-xplained \ + bluepill-stm32f030c8 \ + derfmega128 \ + i-nucleo-lrwan1 \ + ict_panhead \ + im880b \ + m1284p \ + mega-xplained \ + microduino-corerf \ + msb-430 \ + msb-430h \ + nucleo-f030r8 \ + nucleo-f031k6 \ + nucleo-f042k6 \ + nucleo-f303k8 \ + nucleo-f334r8 \ + nucleo-l011k4 \ + nucleo-l031k6 \ + nucleo-l053r8 \ + samd10-xmini \ + slstk3400a \ + stk3200 \ + stm32f030f4-demo \ + stm32f0discovery \ + stm32l0538-disco \ + telosb \ + waspmote-pro \ + z1 \ + zigduino \ + # diff --git a/examples/benchmark_udp/README.md b/examples/benchmark_udp/README.md new file mode 100644 index 0000000000..80e3d7d6a3 --- /dev/null +++ b/examples/benchmark_udp/README.md @@ -0,0 +1,41 @@ +# UDP Benchmark + +This example uses the `benchmark_udp` module to create a stress-test for the RIOT +network stack. + +This firmware will act as a client and connect to the benchmark server you can find +in `dist/tools/benchmark_udp`. + +## Setup on Hardware + +Determine the address of your host machine that will communicate with the RIOT node. +This could be the address of your ethernet interface, or `fd00:dead:beef::1` if you +used the `gnrc_border_router` example and want to run the benchmark on a 6LoWPAN node. + +You can either start the benchmark manually by using the `bench_udp start` shell command +or you can configure it to start automatically: + + USEMODULE += auto_init_benchmark_udp + CFLAGS += -DBENCH_SERVER_DEFAULT=\"fd00:dead:beef::1\" + +## Setup on RIOT native + +First, make sure you've compiled the application by calling `make`. + +Now, create a tap interface: + + sudo ip tuntap add tap0 mode tap user ${USER} + sudo ip link set tap0 up + +If you only have a single tap device you can just use the broadcast address + + bench_udp start ff02::1 + +Otherwise use the link-local address of the `tapbr0` interface (if you did set up the tap +devices using `tapsetup`. + +## Running the benchmark server + +To run the benchmark server on your host machine, follow the instructions found in + + dist/tools/benchmark_udp diff --git a/examples/benchmark_udp/main.c b/examples/benchmark_udp/main.c new file mode 100644 index 0000000000..731f7e8368 --- /dev/null +++ b/examples/benchmark_udp/main.c @@ -0,0 +1,35 @@ +/* + * 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 examples + * @{ + * + * @file + * @brief Example application for exercising the RIOT network stack + * + * @author Benjamin Valentin + * + * @} + */ + +#include + +#include "shell.h" + +int main(void) +{ + puts("RIOT UDP stress-test application"); + + /* start shell */ + char line_buf[SHELL_DEFAULT_BUFSIZE]; + shell_run(NULL, line_buf, sizeof(line_buf)); + + /* should be never reached */ + return 0; +}