aboutsummaryrefslogtreecommitdiffstats
path: root/research/experiments/035-push/under_load.py
blob: bcf1554fa657d867001799520010517a81a2f7df (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
77
78
79
80
81
82
83
84
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()