diff options
Diffstat (limited to 'research/experiments/031-route-tokens/tokens.py')
| -rw-r--r-- | research/experiments/031-route-tokens/tokens.py | 76 |
1 files changed, 76 insertions, 0 deletions
diff --git a/research/experiments/031-route-tokens/tokens.py b/research/experiments/031-route-tokens/tokens.py new file mode 100644 index 0000000..9b70be6 --- /dev/null +++ b/research/experiments/031-route-tokens/tokens.py @@ -0,0 +1,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() |
