aboutsummaryrefslogtreecommitdiffstats
path: root/research/experiments/035-push/under_load.py
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-10-04 16:20:17 +0100
committereunhhu <[email protected]>2026-10-04 16:20:17 +0100
commitbcfc4fe3baa94bc054f949e65389e05969b7ff50 (patch)
tree80b6540957659890e9c45a45404e710f6a2b2097 /research/experiments/035-push/under_load.py
parent50d8263492d69ca8521d3b1d51f60b551c76c443 (diff)
downloadpure-bcfc4fe3baa94bc054f949e65389e05969b7ff50.tar.gz
pure-bcfc4fe3baa94bc054f949e65389e05969b7ff50.zip
Wake each thread at most once a push, end streams only of stopped apps, and show a subscriber costs writes nothing (019)
Diffstat (limited to 'research/experiments/035-push/under_load.py')
-rwxr-xr-xresearch/experiments/035-push/under_load.py85
1 files changed, 85 insertions, 0 deletions
diff --git a/research/experiments/035-push/under_load.py b/research/experiments/035-push/under_load.py
new file mode 100755
index 0000000..bcf1554
--- /dev/null
+++ b/research/experiments/035-push/under_load.py
@@ -0,0 +1,85 @@
+#!/usr/bin/env python3
+"""What a subscriber costs writes (docs/design/019, research 120). pure-serve
+(connection threads 3, store in RAM, cores 0-2) holds the scoreboard
+(benchmarks/http/scores.aup); benchmarks/http/load (core 3) posts points
+on 64 connections for SECONDS, with no subscriber, or with SUBSCRIBERS
+following its total view (which every write changes), in turns, ROUNDS
+times.
+
+under_load.py PURE_SERVE AUP SECONDS ROUNDS SUBSCRIBERS a JSON line per run, then medians
+"""
+import json
+import shutil
+import socket
+import statistics
+import subprocess
+import sys
+import tempfile
+import threading
+import time
+from pathlib import Path
+
+HERE = Path(__file__).resolve().parent
+ROOT = HERE.parents[2]
+LOAD = ROOT / 'benchmarks' / 'http' / 'load'
+
+
+def main():
+ serve, aup, seconds, rounds, subscribers = Path(sys.argv[1]).resolve(), Path(sys.argv[2]).resolve(), *sys.argv[3:6]
+ work = Path(tempfile.mkdtemp(prefix='pure-035-load-', dir='/dev/shm'))
+ try:
+ subprocess.run([str(aup), 'build', str(ROOT / 'benchmarks' / 'http' / 'scores.aup'), '-o', str(work / 'scores.pmod')],
+ check=True)
+ host = subprocess.Popen(['taskset', '-c', '0-2', str(serve), '--module', str(work / 'scores.pmod'), '--store',
+ str(work / 'store'), '--port', '0', '--threads', '3'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ port = json.loads(host.stdout.readline())['port']
+ rates = {}
+ for round_ in range(1, int(rounds) + 1):
+ ways = ['none', 'subscribed']
+ for way in (ways if round_ % 2 else ways[::-1]):
+ socks, taken, stop = [], [0], threading.Event()
+ if way == 'subscribed':
+ for _ in range(int(subscribers)):
+ s = socket.create_connection(('127.0.0.1', port))
+ s.sendall(b'GET /views/total HTTP/1.1\r\nHost: x\r\nAccept: text/event-stream\r\n\r\n')
+ socks.append(s)
+
+ def follow():
+ while not stop.is_set():
+ for s in socks:
+ s.setblocking(False)
+ try:
+ while s.recv(65536):
+ taken[0] += 1
+ except BlockingIOError:
+ pass
+ time.sleep(0.005)
+
+ reader = threading.Thread(target=follow)
+ reader.start()
+ time.sleep(0.5)
+ out = subprocess.run(['taskset', '-c', '3', str(LOAD), str(port), '64', seconds, 'score', '64', 'keep-alive'],
+ capture_output=True, text=True, check=True).stdout
+ result = json.loads(out)
+ stop.set()
+ for s in socks:
+ s.close()
+ if socks:
+ reader.join()
+ assert result['2xx'] == result['completed'] and not result['errors'], result
+ rates.setdefault(way, []).append(result['requests_per_second'])
+ print(json.dumps({'round': round_, 'way': way, 'writes_per_second': result['requests_per_second'],
+ 'p99_us': result['p99_us'], 'reads_by_subscribers': taken[0]}), flush=True)
+ time.sleep(0.5)
+ host.terminate()
+ _, err = host.communicate(timeout=60)
+ stats = json.loads(err.decode().strip().splitlines()[-1])
+ none, subscribed = statistics.median(rates['none']), statistics.median(rates['subscribed'])
+ print(json.dumps({'none_median': none, 'subscribed_median': subscribed, 'change': round((subscribed / none - 1) * 100, 1),
+ 'pushes': stats['pushes'], 'pushed': stats['pushed']}), flush=True)
+ finally:
+ shutil.rmtree(work)
+
+
+if __name__ == '__main__':
+ main()