aboutsummaryrefslogtreecommitdiffstats
path: root/research
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-10-04 13:33:01 +0100
committereunhhu <[email protected]>2026-10-04 13:33:01 +0100
commit09a34871f97c9917735b4dfd40cbad22ccf9b001 (patch)
treebeef276ee5b114af09fe4bd1f611662e91fd9e67 /research
parentd806c3197500565d20787161d1e1e94ce39df7a1 (diff)
downloadpure-09a34871f97c9917735b4dfd40cbad22ccf9b001.tar.gz
pure-09a34871f97c9917735b4dfd40cbad22ccf9b001.zip
Say what a transient state saves a write (018 stage 1, research 117)
Diffstat (limited to 'research')
-rw-r--r--research/experiments/032-transient-states/2026-10-04-ram.jsonl7
-rw-r--r--research/experiments/032-transient-states/2026-10-04-sd.jsonl7
-rwxr-xr-xresearch/experiments/032-transient-states/transient.py73
3 files changed, 87 insertions, 0 deletions
diff --git a/research/experiments/032-transient-states/2026-10-04-ram.jsonl b/research/experiments/032-transient-states/2026-10-04-ram.jsonl
new file mode 100644
index 0000000..0a87923
--- /dev/null
+++ b/research/experiments/032-transient-states/2026-10-04-ram.jsonl
@@ -0,0 +1,7 @@
+{"medium": "ram", "round": 1, "way": "stored", "written_per_request": 0.0, "requests_per_second": 132446, "completed": 1324463, "2xx": 1324463, "4xx": 0, "5xx": 0, "errors": 0, "p50_us": 116, "p99_us": 144, "max_us": 6656}
+{"medium": "ram", "round": 1, "way": "transient", "written_per_request": 0.0, "requests_per_second": 131252, "completed": 1312517, "2xx": 1312517, "4xx": 0, "5xx": 0, "errors": 0, "p50_us": 116, "p99_us": 152, "max_us": 8192}
+{"medium": "ram", "round": 2, "way": "transient", "written_per_request": 0.0, "requests_per_second": 132522, "completed": 1325225, "2xx": 1325225, "4xx": 0, "5xx": 0, "errors": 0, "p50_us": 116, "p99_us": 144, "max_us": 4608}
+{"medium": "ram", "round": 2, "way": "stored", "written_per_request": 0.0, "requests_per_second": 128044, "completed": 1280441, "2xx": 1280441, "4xx": 0, "5xx": 0, "errors": 0, "p50_us": 120, "p99_us": 184, "max_us": 6144}
+{"medium": "ram", "round": 3, "way": "stored", "written_per_request": 0.0, "requests_per_second": 130576, "completed": 1305763, "2xx": 1305763, "4xx": 0, "5xx": 0, "errors": 0, "p50_us": 120, "p99_us": 152, "max_us": 5888}
+{"medium": "ram", "round": 3, "way": "transient", "written_per_request": 0.0, "requests_per_second": 133163, "completed": 1331634, "2xx": 1331634, "4xx": 0, "5xx": 0, "errors": 0, "p50_us": 116, "p99_us": 144, "max_us": 3968}
+{"medium": "ram", "stored_median": 130576, "transient_median": 132522, "change": 1.5, "written_per_request": {"stored": 0.0, "transient": 0.0}}
diff --git a/research/experiments/032-transient-states/2026-10-04-sd.jsonl b/research/experiments/032-transient-states/2026-10-04-sd.jsonl
new file mode 100644
index 0000000..0f55fbe
--- /dev/null
+++ b/research/experiments/032-transient-states/2026-10-04-sd.jsonl
@@ -0,0 +1,7 @@
+{"medium": "sd", "round": 1, "way": "stored", "written_per_request": 1032.9, "requests_per_second": 7598, "completed": 75981, "2xx": 75981, "4xx": 0, "5xx": 0, "errors": 0, "p50_us": 1600, "p99_us": 7168, "max_us": 59392}
+{"medium": "sd", "round": 1, "way": "transient", "written_per_request": 0.0, "requests_per_second": 129617, "completed": 1296169, "2xx": 1296169, "4xx": 0, "5xx": 0, "errors": 0, "p50_us": 116, "p99_us": 168, "max_us": 11776}
+{"medium": "sd", "round": 2, "way": "transient", "written_per_request": 0.0, "requests_per_second": 132888, "completed": 1328882, "2xx": 1328882, "4xx": 0, "5xx": 0, "errors": 0, "p50_us": 116, "p99_us": 136, "max_us": 5120}
+{"medium": "sd", "round": 2, "way": "stored", "written_per_request": 1032.9, "requests_per_second": 7513, "completed": 75137, "2xx": 75137, "4xx": 0, "5xx": 0, "errors": 0, "p50_us": 1536, "p99_us": 7168, "max_us": 106496}
+{"medium": "sd", "round": 3, "way": "stored", "written_per_request": 1033.4, "requests_per_second": 7601, "completed": 76193, "2xx": 76193, "4xx": 0, "5xx": 0, "errors": 0, "p50_us": 1536, "p99_us": 7168, "max_us": 59392}
+{"medium": "sd", "round": 3, "way": "transient", "written_per_request": 0.0, "requests_per_second": 132305, "completed": 1323051, "2xx": 1323051, "4xx": 0, "5xx": 0, "errors": 0, "p50_us": 116, "p99_us": 152, "max_us": 4096}
+{"medium": "sd", "stored_median": 7598, "transient_median": 132305, "change": 1641.3, "written_per_request": {"stored": 1032.9, "transient": 0.0}}
diff --git a/research/experiments/032-transient-states/transient.py b/research/experiments/032-transient-states/transient.py
new file mode 100755
index 0000000..87153c8
--- /dev/null
+++ b/research/experiments/032-transient-states/transient.py
@@ -0,0 +1,73 @@
+#!/usr/bin/env python3
+"""What a transient state saves (docs/design/018 stage 1, research 117). A
+host of apps (one connection thread, two workers, cores 0-2) holds the
+scoreboard (benchmarks/http/scores.aup) twice: stored, as it is, and
+transient, its board a transient state. The load (benchmarks/http/load,
+core 3) posts points for SECONDS on CONNECTIONS connections, keep-alive, to
+one of them; the two in turns, their order turned each round. The stores
+are in RAM (/dev/shm) or on the SD card (~/.cache). Each run says what the
+host wrote to the device meanwhile (/proc/PID/io write_bytes).
+
+transient.py PURE_SERVE ram|sd SECONDS ROUNDS a JSON line per run, then medians
+"""
+import json
+import shutil
+import statistics
+import subprocess
+import sys
+import tempfile
+from pathlib import Path
+
+HERE = Path(__file__).resolve().parent
+ROOT = HERE.parents[2]
+AUP = ROOT.parent / 'aup' / 'build' / 'aup'
+LOAD = ROOT / 'benchmarks' / 'http' / 'load'
+
+
+def written(pid):
+ for line in Path(f'/proc/{pid}/io').read_text().splitlines():
+ if line.startswith('write_bytes:'):
+ return int(line.split()[1])
+
+
+def main():
+ serve, medium, seconds, rounds = Path(sys.argv[1]).resolve(), sys.argv[2], int(sys.argv[3]), int(sys.argv[4])
+ base = Path('/dev/shm') if medium == 'ram' else Path.home() / '.cache'
+ base.mkdir(parents=True, exist_ok=True)
+ work = Path(tempfile.mkdtemp(prefix='pure-032-', dir=base))
+ try:
+ source = (ROOT / 'benchmarks' / 'http' / 'scores.aup').read_text()
+ (work / 'transient.aup').write_text(source.replace('state board', 'transient state board'))
+ for name, path in (('stored', ROOT / 'benchmarks' / 'http' / 'scores.aup'), ('transient', work / 'transient.aup')):
+ subprocess.run([str(AUP), 'build', str(path), '-o', str(work / f'{name}.pmod')], check=True)
+ (work / 'apps.conf').write_text(f"stored {work / 'stored.pmod'} {work / 'stored'}\n"
+ f"transient {work / 'transient.pmod'} {work / 'transient'}\n")
+ host = subprocess.Popen(['taskset', '-c', '0-2', str(serve), '--apps', str(work / 'apps.conf'), '--port', '0',
+ '--threads', '1', '--workers', '2'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ port = json.loads(host.stdout.readline())['port']
+ rates, bytes_ = {}, {}
+ for round_ in range(1, rounds + 1):
+ ways = ['stored', 'transient']
+ for way in (ways if round_ % 2 else ways[::-1]):
+ before = written(host.pid)
+ out = subprocess.run(['taskset', '-c', '3', str(LOAD), str(port), '16', str(seconds), 'score', '64',
+ 'keep-alive', '16', f'/{way}'], capture_output=True, text=True, check=True).stdout
+ result = json.loads(out)
+ assert result['2xx'] == result['completed'] and not result['errors'], result
+ per = (written(host.pid) - before) / result['completed']
+ rates.setdefault(way, []).append(result['requests_per_second'])
+ bytes_.setdefault(way, []).append(per)
+ print(json.dumps({'medium': medium, 'round': round_, 'way': way, 'written_per_request': round(per, 1),
+ **result}), flush=True)
+ host.terminate()
+ host.communicate(timeout=60)
+ stored, transient = statistics.median(rates['stored']), statistics.median(rates['transient'])
+ print(json.dumps({'medium': medium, 'stored_median': stored, 'transient_median': transient,
+ 'change': round((transient / stored - 1) * 100, 1),
+ 'written_per_request': {w: round(statistics.median(bytes_[w]), 1) for w in bytes_}}), flush=True)
+ finally:
+ shutil.rmtree(work)
+
+
+if __name__ == '__main__':
+ main()