From f0bec42645aa845d1c1d71c6e18ac1521e808fcc Mon Sep 17 00:00:00 2001 From: eunhhu Date: Sat, 26 Sep 2026 05:29:44 +0100 Subject: Benchmark the HTTP hosts against axum A C load generator, an axum service with the same document contract (in memory, fdatasync, and crash-safe replacement), and a runner that pins the servers and the load to separate CPUs. On the Pi 5, Pure uses about 40% less memory, wins with a new connection per request and with eight durable writers (3.2x the crash-safe axum), but loses 5x on keep-alive reads and 1.5x on 64 durable writers: no persistent connections, one thread, eight connection slots and two fsyncs per commit. Note 075 records the numbers and the targets. --- benchmarks/http/run.py | 133 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 benchmarks/http/run.py (limited to 'benchmarks/http/run.py') diff --git a/benchmarks/http/run.py b/benchmarks/http/run.py new file mode 100644 index 0000000..8202440 --- /dev/null +++ b/benchmarks/http/run.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +"""HTTP benchmarks: Pure's document servers against an axum service with the +same contract, on the same machine, with the same load generator. + +Servers are pinned to CPUs 0-2 and the load to CPU 3; stores live under +--directory (use the device you care about: fsync costs are the device's). +Prints one JSON object per (server, workload) and a summary table. +""" +import argparse +import json +import os +import select +import shutil +import signal +import struct +import subprocess +import sys +import tempfile +import time +import http.client +from pathlib import Path + +HERE = Path(__file__).resolve().parent +PURE = HERE.parent.parent / 'prototypes' / 'connections' + + +def start(args): + process = subprocess.Popen(['taskset', '-c', '0-2'] + [str(a) for a in args], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + ready, _, _ = select.select([process.stdout], [], [], 20) + assert ready, 'server did not start' + port = json.loads(process.stdout.readline())['port'] + return process, port + + +def rss_kib(pid, field): + for line in Path(f'/proc/{pid}/status').read_text().splitlines(): + if line.startswith(field): + return int(line.split()[1]) + return 0 + + +def put(port, key, body, expected): + connection = http.client.HTTPConnection('127.0.0.1', port, timeout=30) + headers = {'Content-Length': str(len(body) + 8)} + headers['If-None-Match' if expected == 0 else 'If-Match'] = '*' if expected == 0 else f'"{expected}"' + connection.request('PUT', f'/objects/{key}', struct.pack('9s} {'p50 us':>8s} {'p99 us':>8s} {'errors':>7s} {'non-2xx':>8s}") + for r in results: + print(f"{r['workload']:32s} {r['server']:14s} {r['ok_per_second']:9d} {r['p50_us']:8d} {r['p99_us']:8d} " + f"{r['errors']:7d} {r['4xx'] + r['5xx']:8d}") + print() + for name in servers: + rows = [r for r in results if r['server'] == name] + if rows: + print(f"{name:14s} idle RSS {rows[0]['idle_rss_kib']} KiB, peak RSS {rows[0]['peak_rss_kib']} KiB") + + +if __name__ == '__main__': + sys.exit(main()) -- cgit v1.2.3