From cc684e79c38e0aa5ff44820a31658f457691d979 Mon Sep 17 00:00:00 2001 From: Dirk Engling Date: Fri, 30 Oct 2026 02:35:47 +0100 Subject: Add an http load generator --- tests/http_load.c | 148 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 tests/http_load.c (limited to 'tests') diff --git a/tests/http_load.c b/tests/http_load.c new file mode 100644 index 0000000..d04139b --- /dev/null +++ b/tests/http_load.c @@ -0,0 +1,148 @@ +/* An HTTP load generator speaking the tracker's own protocol. + + Each request is its own connection, which is what a BitTorrent client + normally does and what the tracker's hot path is built for: accept, read + one request, write a short reply, close. That makes this a measurement of + connection cost rather than of request cost, and connection cost is where + the tracker's system calls are. + + cc -O2 -pthread -o http_load tests/http_load.c + ./http_load 127.0.0.1 6969 8 5 10000 + + Arguments: host, port, threads, seconds, and how many distinct torrents to + announce. Cycling a fixed set reaches a steady state where almost every + announce renews a peer that is already there, rather than measuring how + fast the tracker can insert torrents it will never see again. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static struct sockaddr_in g_tracker; +static long g_torrents = 10000; +static double g_seconds = 5; +static _Atomic long long g_replies = 0; +static _Atomic long long g_failed = 0; +static _Atomic int g_running = 1; + +static double now_seconds(void) { + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec + tv.tv_usec / 1000000.0; +} + +/* %xx for all twenty bytes, which is what a client sends and what the + tracker's urlencode reader has to walk */ +static void hash_of(long which, char *out) { + int i; + + for (i = 0; i < 20; ++i) { + unsigned byte = (unsigned)((which >> ((i % 4) * 8)) & 0xff) ^ (unsigned)(i * 37); + sprintf(out + i * 3, "%%%02x", byte & 0xff); + } +} + +static void *worker(void *args) { + long index = (long)(intptr_t)args; + long which = index * 7919; + char request[512], hash[61], reply[2048]; + + while (atomic_load_explicit(&g_running, memory_order_relaxed)) { + int sock = socket(AF_INET, SOCK_STREAM, 0); + int one = 1; + ssize_t got, sent; + size_t length; + + if (sock < 0) { + atomic_fetch_add_explicit(&g_failed, 1, memory_order_relaxed); + continue; + } + setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)); + + if (connect(sock, (struct sockaddr *)&g_tracker, sizeof(g_tracker)) < 0) { + atomic_fetch_add_explicit(&g_failed, 1, memory_order_relaxed); + close(sock); + continue; + } + + hash_of(which, hash); + which = (which + 1) % g_torrents; + length = (size_t)snprintf(request, sizeof(request), + "GET /announce?info_hash=%s&peer_id=-OT0001-%06ld%06ld&port=%u&left=0 HTTP/1.0\r\n\r\n", + hash, index, which, (unsigned)(6881 + index)); + + for (sent = 0; (size_t)sent < length;) { + ssize_t wrote = write(sock, request + sent, length - (size_t)sent); + if (wrote <= 0) + break; + sent += wrote; + } + + /* Read to end of reply, which the tracker marks by closing */ + got = 0; + for (;;) { + ssize_t piece = read(sock, reply, sizeof(reply)); + if (piece <= 0) + break; + got += piece; + } + close(sock); + + if (got > 0) + atomic_fetch_add_explicit(&g_replies, 1, memory_order_relaxed); + else + atomic_fetch_add_explicit(&g_failed, 1, memory_order_relaxed); + } + return NULL; +} + +int main(int argc, char **argv) { + const char *host = argc > 1 ? argv[1] : "127.0.0.1"; + int port = argc > 2 ? atoi(argv[2]) : 6969; + long threads = argc > 3 ? atol(argv[3]) : 4; + pthread_t *pool; + double started, elapsed; + long i; + + g_seconds = argc > 4 ? atof(argv[4]) : 5; + g_torrents = argc > 5 ? atol(argv[5]) : 10000; + if (g_torrents < 1) + g_torrents = 1; + + memset(&g_tracker, 0, sizeof(g_tracker)); + g_tracker.sin_family = AF_INET; + g_tracker.sin_port = htons((uint16_t)port); + if (inet_pton(AF_INET, host, &g_tracker.sin_addr) != 1) { + fprintf(stderr, "cannot parse address %s\n", host); + return 1; + } + + pool = calloc((size_t)threads, sizeof(pthread_t)); + started = now_seconds(); + for (i = 0; i < threads; ++i) + pthread_create(pool + i, NULL, worker, (void *)(intptr_t)i); + + while (now_seconds() - started < g_seconds) + usleep(20000); + atomic_store(&g_running, 0); + + for (i = 0; i < threads; ++i) + pthread_join(pool[i], NULL); + elapsed = now_seconds() - started; + + printf("%.0f replies/s (%lld replies, %lld failed, %ld threads, %.1f s)\n", + (double)atomic_load(&g_replies) / elapsed, (long long)atomic_load(&g_replies), + (long long)atomic_load(&g_failed), threads, elapsed); + free(pool); + return 0; +} -- cgit v1.3