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:
commit
6b47efb57b
2
dist/tools/Makefile
vendored
2
dist/tools/Makefile
vendored
@ -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
19
dist/tools/benchmark_udp/Makefile
vendored
Normal 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
51
dist/tools/benchmark_udp/README.md
vendored
Normal 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
276
dist/tools/benchmark_udp/main.c
vendored
Normal 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;
|
||||
}
|
||||
/** @} */
|
53
examples/benchmark_udp/Makefile
Normal file
53
examples/benchmark_udp/Makefile
Normal 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
|
40
examples/benchmark_udp/Makefile.ci
Normal file
40
examples/benchmark_udp/Makefile.ci
Normal 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 \
|
||||
#
|
41
examples/benchmark_udp/README.md
Normal file
41
examples/benchmark_udp/README.md
Normal 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
|
35
examples/benchmark_udp/main.c
Normal file
35
examples/benchmark_udp/main.c
Normal 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;
|
||||
}
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
113
sys/include/test_utils/benchmark_udp.h
Normal file
113
sys/include/test_utils/benchmark_udp.h
Normal 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 */
|
||||
/** @} */
|
@ -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
|
||||
|
66
sys/shell/commands/sc_benchmark_udp.c
Normal file
66
sys/shell/commands/sc_benchmark_udp.c
Normal 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;
|
||||
}
|
||||
/** @} */
|
@ -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
|
||||
|
@ -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
|
||||
|
1
sys/test_utils/benchmark_udp/Makefile
Normal file
1
sys/test_utils/benchmark_udp/Makefile
Normal file
@ -0,0 +1 @@
|
||||
include $(RIOTBASE)/Makefile.base
|
213
sys/test_utils/benchmark_udp/benchmark_udp.c
Normal file
213
sys/test_utils/benchmark_udp/benchmark_udp.c
Normal 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);
|
||||
}
|
||||
/** @} */
|
Loading…
Reference in New Issue
Block a user