#!/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, round) and a summary table of medians over rounds. Durable writes are only comparable on a device in the same state. An SD card slows down under sustained syncs, so the order of runs alone can change a result several times over. Each round therefore runs the servers in a rotated order, each on a fresh store, and every write workload first times the device (an in-place 4 KiB write + fdatasync); the table shows that probe next to each result. """ import argparse import json import os import select import shutil import signal import statistics 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} " f"{'non-2xx':>8s} {'probe ms':>9s}") for label, _ in workloads: for name in chosen: rows = [r for r in results if r['server'] == name and r['workload'] == label] if not rows: continue probes = [r['probe_ms'] for r in rows if r['probe_ms'] is not None] probe = f"{min(probes):.1f}-{max(probes):.1f}" if probes else '-' print(f"{label:32s} {name:14s} {median(rows, 'ok_per_second'):9.0f} {median(rows, 'p50_us'):8.0f} " f"{median(rows, 'p99_us'):8.0f} {sum(r['errors'] for r in rows):7d} " f"{sum(r['4xx'] + r['5xx'] for r in rows):8d} {probe:>9s}") print() for name in chosen: rows = [r for r in results if r['server'] == name] if rows: print(f"{name:14s} idle RSS {median(rows, 'idle_rss_kib'):.0f} KiB, " f"peak RSS {max(r['peak_rss_kib'] for r in rows)} KiB") if __name__ == '__main__': sys.exit(main())