1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00

test_utils: add UDP benchmark

This commit is contained in:
Benjamin Valentin 2021-08-04 16:04:54 +02:00
parent a59974cf69
commit 73b929b3b9
9 changed files with 417 additions and 0 deletions

View File

@ -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

View File

@ -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();
}
}

View File

@ -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 <benjamin.valentin@ml-pa.com>
*/
#ifndef TEST_UTILS_BENCHMARK_UDP_H
#define TEST_UTILS_BENCHMARK_UDP_H
#include <stdbool.h>
#include <stdint.h>
#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 */
/** @} */

View File

@ -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

View File

@ -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 <benjamin.valentin@ml-pa.com>
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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] <server> <port>\n", argv[0]);
return -1;
}
/** @} */

View File

@ -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

View File

@ -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

View File

@ -0,0 +1 @@
include $(RIOTBASE)/Makefile.base

View File

@ -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 <benjamin.valentin@ml-pa.com>
*/
#include <stdio.h>
#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);
}
/** @} */