From 0c3abf9e3538c482639ed32b0e85104495db5b4f Mon Sep 17 00:00:00 2001 From: Guus Sliepen Date: Sun, 29 Mar 2020 19:30:01 +0200 Subject: [PATCH] Add a tool to generate a stream with a configurable bitrate. This generates a stream with a configurable average bitrate and number of frames sent per second. The stream has embedded timestamps and counters, which can be used by the same program to verify the integrity of an incoming stream and to print the latency between sender and receiver. --- Makefile | 2 + benchmark-stream | 92 ++++++++++++++++++++++++++++ stream.c | 156 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 250 insertions(+) create mode 100755 benchmark-stream create mode 100644 stream.c diff --git a/Makefile b/Makefile index 6cb1bc9..134ca51 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,8 @@ utcp.o: utcp.c utcp.h utcp_priv.h test: utcp.o test.c +stream: stream.c + selftest: utcp.o selftest.c clean: diff --git a/benchmark-stream b/benchmark-stream new file mode 100755 index 0000000..319a642 --- /dev/null +++ b/benchmark-stream @@ -0,0 +1,92 @@ +#!/bin/bash +set -e + +# Configuration +LOG_PREFIX=/dev/shm/benchmark-log + +# Size in bytes +SIZE=2e6 + +# Rate of generated stream in bits/s +STREAMRATE=10e6 + +# Network parameters +# Some realistic values: +# - Gbit LAN connection: RATE=1gbit DELAY=0.4ms JITTER=0.04ms LOSS=0% +# - Fast WAN connection: RATE=100mbit DELAY=50ms JITTER=3ms LOSS=0% +# - 5GHz WiFi connection: RATE=90mbit DELAY=5ms JITTER=1ms LOSS=0% +RATE=100mbit +DELAY=10ms +JITTER=1ms +LOSS=0.1% + +# Maximum achievable bandwidth is limited to BUFSIZE / (2 * DELAY) +# The Linux kernel has a default maximum send buffer of 4 MiB +#export BUFSIZE=4194304 + +# Require root permissions +if [ "$USER" != "root" ]; then + exec sudo "$0" "$@" +fi + +# Remove old log files +rm -f $LOG_PREFIX-* 2>/dev/null + +# Clean up old namespaces +ip link del utcp-left 2>/dev/null || true +ip link del utcp-right 2>/dev/null || true +ip netns delete utcp-left 2>/dev/null || true +ip netns delete utcp-right 2>/dev/null || true + +# Set up the left namespace +ip netns add utcp-left +ip link add name utcp-left type veth peer name utcp-right +ip link set utcp-left netns utcp-left + +ip netns exec utcp-left ethtool -K utcp-left tso off +ip netns exec utcp-left ip link set dev lo up +ip netns exec utcp-left ip addr add dev utcp-left 192.168.1.1/24 +ip netns exec utcp-left ip link set utcp-left up + +#ip netns exec utcp-left tc qdisc del dev utcp-left root +ip netns exec utcp-left tc qdisc add dev utcp-left root netem rate $RATE delay $DELAY $JITTER loss random $LOSS + +# Set up the right namespace +ip netns add utcp-right +ip link set utcp-right netns utcp-right + +ip netns exec utcp-right ethtool -K utcp-right tso off +ip netns exec utcp-right ip link set dev lo up +ip netns exec utcp-right ip addr add dev utcp-right 192.168.1.2/24 +ip netns exec utcp-right ip link set utcp-right up + +#ip netns exec utcp-right tc qdisc del dev utcp-right root +ip netns exec utcp-right tc qdisc add dev utcp-right root netem rate $RATE delay $DELAY $JITTER loss random $LOSS +# Test using kernel TCP +ip netns exec utcp-right tcpdump -i utcp-right -w $LOG_PREFIX-socat.pcap port 9999 2>/dev/null & +ip netns exec utcp-left socat TCP4-LISTEN:9999 - $LOG_PREFIX-socat-client.txt >/dev/null +sleep 0.1 +kill $(jobs -p) 2>/dev/null + +# Test using UTCP +ip netns exec utcp-right tcpdump -i utcp-right -w $LOG_PREFIX-utcp.pcap udp port 9999 2>/dev/null & +ip netns exec utcp-left ./test 9999 2>$LOG_PREFIX-server.txt $LOG_PREFIX-client.txt >/dev/null +sleep 0.1 +kill $(jobs -p) 2>/dev/null + +# Print timing statistics +echo "Regular TCP:" +tail -2 $LOG_PREFIX-socat-client.txt + +echo +echo "UTCP:" +tail -3 $LOG_PREFIX-client.txt + +# If sudo was used, ensure the log files can be read by the user +if [ -n "$SUDO_USER" ]; then + chown $SUDO_USER $LOG_PREFIX-* +fi diff --git a/stream.c b/stream.c new file mode 100644 index 0000000..378809c --- /dev/null +++ b/stream.c @@ -0,0 +1,156 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char *argv[]) { + static const struct option longopts[] = { + {"verify", 0, NULL, 'v'}, + {"rate", 1, NULL, 'r'}, + {"fps", 1, NULL, 'f'}, + {"total", 1, NULL, 't'}, + {NULL, 0, NULL, 0}, + }; + + int opt; + bool verify = false; + float rate = 1e6; + float fps = 60; + float total = 1.0 / 0.0; + + while((opt = getopt_long(argc, argv, "vr:f:t:", longopts, &optind)) != -1) { + switch(opt) { + case 'v': + verify = true; + break; + + case 'r': + rate = atof(optarg); + break; + + case 'f': + fps = atof(optarg); + break; + + case 't': + total = atof(optarg); + break; + + default: + fprintf(stderr, "Usage: %s [-v] [-r bitrate] [-f frames_per_second]\n", argv[0]); + return 1; + } + } + + size_t framesize = rate / fps / 8; + framesize &= ~0xf; + long interval = 1e9 / fps; + + if(!framesize || interval <= 0) { + err(1, "invalid parameters"); + } + + char *buf = malloc(framesize + 16); + + if(!buf) { + err(1, "malloc(%zu)", framesize); + } + + uint64_t counter = 0; + struct timespec now, next = {0}; + clock_gettime(CLOCK_REALTIME, &now); + + while(total > 0) { + if(!verify) { + size_t tosend = framesize; + char *p = buf; + + memcpy(buf, &now, sizeof(now)); + + for(uint64_t *q = (uint64_t *)(buf + sizeof(now)); (char *)q < buf + framesize; q++) { + *q = counter++; + } + + while(tosend) { + ssize_t sent = write(1, p, tosend); + + if(sent <= 0) { + err(1, "write(1, %p, %zu)", p, tosend); + } + + tosend -= sent; + p += sent; + } + + next = now; + next.tv_nsec += interval; + + while(next.tv_nsec >= 1000000000) { + next.tv_nsec -= 1000000000; + next.tv_sec++; + } + + clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &next, NULL); + now = next; + total -= framesize; + } else { + struct timespec *ts = (struct timespec *)buf; + size_t toread = sizeof(*ts); + char *p = buf; + + while(toread) { + ssize_t result = read(0, p, toread); + + if(result <= 0) { + err(1, "read(1, %p, %zu)", p, toread); + } + + toread -= result; + p += result; + } + + clock_gettime(CLOCK_REALTIME, &now); + + toread = framesize - sizeof(now); + + while(toread) { + ssize_t result = read(0, p, toread); + + if(result <= 0) { + err(1, "read(1, %p, %zu)", p, toread); + } + + toread -= result; + p += result; + } + + clock_gettime(CLOCK_REALTIME, &next); + + for(uint64_t *q = (uint64_t *)(buf + sizeof(now)); (char *)q < buf + framesize; q++) { + if(*q != counter++) { + uint64_t offset = (counter - 1) * 8; + offset += ((counter * 8) / (framesize - sizeof(now))) * sizeof(now); + err(1, "verification failed at offset %lu", offset); + } + } + + float dt1 = now.tv_sec - ts->tv_sec + 1e-9 * (now.tv_nsec - ts->tv_nsec); + float dt2 = next.tv_sec - now.tv_sec + 1e-9 * (next.tv_nsec - now.tv_nsec); + + fprintf(stderr, "\rDelay: %8.3f ms, burst bandwidth: %8.0f Mbit/s", dt1 * 1e3, (framesize - sizeof(now)) / dt2 * 8 / 1e6); + + total -= framesize; + } + } + + if(verify) { + fprintf(stderr, "\n"); + } +} -- 2.39.2