aboutsummaryrefslogtreecommitdiffstats
path: root/research/experiments/035-push/push.py
diff options
context:
space:
mode:
Diffstat (limited to 'research/experiments/035-push/push.py')
-rwxr-xr-xresearch/experiments/035-push/push.py75
1 files changed, 75 insertions, 0 deletions
diff --git a/research/experiments/035-push/push.py b/research/experiments/035-push/push.py
new file mode 100755
index 0000000..28334dd
--- /dev/null
+++ b/research/experiments/035-push/push.py
@@ -0,0 +1,75 @@
+#!/usr/bin/env python3
+"""Pushing a view against polling it (docs/design/019, research 120).
+pure-serve (connection threads THREADS, store in RAM, cores 0-2) holds
+counter.aup; benchmarks/http/watch (core 3) has CLIENTS clients follow its
+total view, by push (SSE) or by polling every MS, while one writer moves it
+RATE times a second for SECONDS. Each run says the freshness (from the
+writer's answer to a client seeing that value or a newer one), what the
+host's process used of the CPU meanwhile (/proc/PID/stat), and its RSS
+with the clients on less without them.
+
+push.py PURE_SERVE AUP THREADS SECONDS RUNS... RUNS: CLIENTS:RATE:push or CLIENTS:RATE:poll:MS, in turn ROUNDS times
+"""
+import json
+import os
+import shutil
+import statistics
+import subprocess
+import sys
+import tempfile
+import time
+from pathlib import Path
+
+HERE = Path(__file__).resolve().parent
+WATCH = HERE.parents[2] / 'benchmarks' / 'http' / 'watch'
+TICK = os.sysconf('SC_CLK_TCK')
+
+
+def cpu(pid):
+ fields = Path(f'/proc/{pid}/stat').read_text().rsplit(')', 1)[1].split()
+ return (int(fields[11]) + int(fields[12])) / TICK # utime, stime
+
+
+def rss(pid):
+ return next(int(line.split()[1]) for line in Path(f'/proc/{pid}/status').read_text().splitlines()
+ if line.startswith('VmRSS'))
+
+
+def main():
+ serve, aup, threads, seconds = Path(sys.argv[1]).resolve(), Path(sys.argv[2]).resolve(), sys.argv[3], sys.argv[4]
+ runs = sys.argv[5:]
+ work = Path(tempfile.mkdtemp(prefix='pure-035-', dir='/dev/shm'))
+ try:
+ subprocess.run([str(aup), 'build', str(HERE / 'counter.aup'), '-o', str(work / 'counter.pmod')], check=True)
+ for run in runs:
+ clients, rate, way = run.split(':', 2)
+ host = subprocess.Popen(['taskset', '-c', '0-2', str(serve), '--module', str(work / 'counter.pmod'), '--store',
+ str(work / f'store-{run.replace(":", "-")}'), '--port', '0', '--threads', threads,
+ '--connections', '32768', '--subscribers', '65536'],
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ port = json.loads(host.stdout.readline())['port']
+ time.sleep(0.3)
+ idle = rss(host.pid)
+ watcher = subprocess.Popen(['taskset', '-c', '3', str(WATCH), str(port), clients, seconds, rate, way,
+ '/views/total', '/inputs/tap'], stdout=subprocess.PIPE, text=True)
+ time.sleep(2.0 + int(clients) / 5000) # every client on, its first value had
+ followed_rss, began = rss(host.pid), cpu(host.pid)
+ started = time.monotonic()
+ out, _ = watcher.communicate(timeout=600)
+ used = cpu(host.pid) - began
+ took = time.monotonic() - started
+ host.terminate()
+ _, err = host.communicate(timeout=60)
+ stats = json.loads(err.decode().strip().splitlines()[-1])
+ result = json.loads(out)
+ result.update({'host_cpu_seconds': round(used, 2), 'host_cpu_share': round(used / took, 3),
+ 'rss_kib_per_client': round((followed_rss - idle) / int(clients), 2),
+ 'pushes': stats['pushes'], 'pushed': stats['pushed'], 'replaced': stats['push_replaced'],
+ 'requests': stats['requests']})
+ print(json.dumps(result), flush=True)
+ finally:
+ shutil.rmtree(work)
+
+
+if __name__ == '__main__':
+ main()