aboutsummaryrefslogtreecommitdiffstats
path: root/research/experiments/031-route-tokens/tokens.py
blob: 9b70be61094b512760043001da16f89b13b8c0ae (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
"""What an app's tokens cost its requests (docs/design/017's 3b, research 116).
A host of apps (one connection thread, two workers, stores in RAM, cores
0-2) holds the scoreboard (benchmarks/http/scores.aup) twice: open, with
no tokens, and held, whose tokens file grants a write token. The load
(benchmarks/http/load, core 3) reads its total view for SECONDS on
CONNECTIONS connections, keep-alive (a connection's token hashed once) or
close (a connection a request: hashed every time): open with no token,
open bearing the token all the same (its head as long, nothing checked),
and held; the three in turns, their order turned each round.

tokens.py PURE_SERVE SECONDS ROUNDS   a JSON line per run, then medians
"""
import hashlib
import json
import os
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'
TOKEN = 'a' * 64


def load(port, way, connections, seconds, mode):
    env = dict(os.environ)
    app = 'held' if way == 'held' else 'open'
    if way != 'open':
        env['PURE_LOAD_TOKEN'] = TOKEN
    out = subprocess.run(['taskset', '-c', '3', str(LOAD), str(port), str(connections), str(seconds), 'total', '1', mode,
                          '16', f'/{app}'], capture_output=True, text=True, env=env, check=True).stdout
    return json.loads(out)


def main():
    serve, seconds, rounds = Path(sys.argv[1]).resolve(), int(sys.argv[2]), int(sys.argv[3])
    work = Path(tempfile.mkdtemp(prefix='pure-031-', dir='/dev/shm'))
    try:
        module = work / 'scores.pmod'
        subprocess.run([str(AUP), 'build', str(ROOT / 'benchmarks' / 'http' / 'scores.aup'), '-o', str(module)], check=True)
        (work / 'tokens').write_text(f'write {hashlib.sha256(TOKEN.encode()).hexdigest()}\n')
        (work / 'apps.conf').write_text(f"open {module} {work / 'open'}\nheld {module} {work / 'held'} "
                                        f"tokens={work / 'tokens'}\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 = {}
        for mode, connections in (('keep-alive', 64), ('close', 16)):
            for round_ in range(1, rounds + 1):
                ways = ['open', 'bearing', 'held']
                for way in (ways if round_ % 2 else ways[::-1]):
                    result = load(port, way, connections, seconds, mode)
                    assert result['2xx'] == result['completed'] and not result['errors'], result
                    rates.setdefault((mode, way), []).append(result['requests_per_second'])
                    print(json.dumps({'mode': mode, 'round': round_, 'way': way, **result}), flush=True)
        host.terminate()
        _, error = host.communicate(timeout=60)
        stats = json.loads(error.decode().strip().splitlines()[-1])
        for mode in ('keep-alive', 'close'):
            open_, bearing, held = (statistics.median(rates[(mode, way)]) for way in ('open', 'bearing', 'held'))
            print(json.dumps({'mode': mode, 'open_median': open_, 'bearing_median': bearing, 'held_median': held,
                              'bearing_change': round((bearing / open_ - 1) * 100, 2),
                              'held_change': round((held / open_ - 1) * 100, 2)}), flush=True)
        print(json.dumps({'unauthorized': stats['unauthorized'], 'forbidden': stats['forbidden']}), flush=True)
    finally:
        shutil.rmtree(work)


if __name__ == '__main__':
    main()