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

Merge pull request #16710 from benpicco/benchmark_udp

add UDP benchmark:
a nice tool to check reliability and performance of networks and nodes under high pkg pressure.
This commit is contained in:
Karl Fessel 2021-09-03 11:22:06 +02:00 committed by GitHub
commit 6b47efb57b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 933 additions and 1 deletions

2
dist/tools/Makefile vendored
View File

@ -1,4 +1,4 @@
HOST_TOOLS=ethos uhcpd sliptty zep_dispatch
HOST_TOOLS=benchmark_udp ethos uhcpd sliptty zep_dispatch
.PHONY: all $(HOST_TOOLS)

19
dist/tools/benchmark_udp/Makefile vendored Normal file
View File

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

51
dist/tools/benchmark_udp/README.md vendored Normal file
View File

@ -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 <interval>` to control the send interval in µs
- `-s <size>` 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 <address> <port>
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=\"<addr>\"
CFLAGS += -DBENCH_PORT_DEFAULT=<port>

276
dist/tools/benchmark_udp/main.c vendored Normal file
View File

@ -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 <benjamin.valentin@ml-pa.com>
*/
#include <arpa/inet.h>
#include <netdb.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/random.h>
#include <sys/time.h>
#include <unistd.h>
#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] <address> <port>\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 <interval>\tsend interval in µs\n");
fprintf(stderr, "\t-s <size>\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;
}
/** @} */

View File

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

View File

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

View File

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

View File

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

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