aboutsummaryrefslogtreecommitdiffstats
path: root/research/experiments/035-push/push.py
blob: f854bb33b359095cea939bc5b5556a8e2eb769c7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#!/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(['cc', '-O2', '-Wall', '-Wextra', '-Werror', '-o', str(WATCH), str(WATCH) + '.c'], check=True)
        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()