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

tools/benchmark_udp: add host tool for benchmark

This commit is contained in:
Benjamin Valentin 2021-09-02 23:11:57 +02:00
parent 73b929b3b9
commit 1bc6b7eec7
4 changed files with 347 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;
}
/** @} */