]> git.meshlink.io Git - utcp/commitdiff
Add a tool to generate a stream with a configurable bitrate.
authorGuus Sliepen <guus@sliepen.org>
Sun, 29 Mar 2020 17:30:01 +0000 (19:30 +0200)
committerGuus Sliepen <guus@sliepen.org>
Sun, 29 Mar 2020 17:30:01 +0000 (19:30 +0200)
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
benchmark-stream [new file with mode: 0755]
stream.c [new file with mode: 0644]

index 6cb1bc9ab5e8bd1716d5e2932e49666ac0c881e8..134ca516bf34d3605fa00cc5c1bb9dd67bc2f388 100644 (file)
--- 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 (executable)
index 0000000..319a642
--- /dev/null
@@ -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 - </dev/null | ./stream -r $STREAMRATE -t $SIZE -v &
+sleep 0.1
+./stream -r $STREAMRATE -t $SIZE | ip netns exec utcp-right time socat - TCP4:192.168.1.1:9999 2>$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 </dev/null | ./stream -r $STREAMRATE -t $SIZE -v &
+sleep 0.1
+./stream -r $STREAMRATE -t $SIZE | ip netns exec utcp-right time ./test 192.168.1.1 9999 2>$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 (file)
index 0000000..378809c
--- /dev/null
+++ b/stream.c
@@ -0,0 +1,156 @@
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <getopt.h>
+#include <err.h>
+#include <time.h>
+#include <unistd.h>
+
+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");
+       }
+}