diff options
| author | eunhhu <[email protected]> | 2026-10-01 06:25:21 +0100 |
|---|---|---|
| committer | eunhhu <[email protected]> | 2026-10-01 06:25:21 +0100 |
| commit | ddcc1202daa7de84f13ff712fdb5ad12159facc1 (patch) | |
| tree | 47deddd3c047df16c3c94d8a2ea9f4724a852083 /research/experiments/026-apps-on-one-host/isolation.py | |
| parent | be297ead16b41938e84bcc3f1be628bc8725f2f4 (diff) | |
| download | pure-ddcc1202daa7de84f13ff712fdb5ad12159facc1.tar.gz pure-ddcc1202daa7de84f13ff712fdb5ad12159facc1.zip | |
Measure one host of many apps against a process each (111)
017's first stage met its four criteria on the Pi (RAM stores, five
rounds, the order of the ways turned): app B's 99th percentile while
app A is flooded by 64 connections is 168 us, half of two processes'
336 us; the two apps busy together write 146.1k a second against
146.2k, with the same 1.4 ms tail; an app costs a host 120 KiB of its
own memory (a process: 1,311 KiB PSS) and 100 apps still run on three
threads; a host of one app keeps its speed (TPC-C +1.2% at the median
of six pairs, two of them interfered and kept; the bank's queries
within 2.6%) and its instructions (+0.94% at most).
On the way: apps hopped between workers each turn (fixed in db4ee9c),
connections of one app spread over all connection threads made the
host a third more expensive a request and its tail a kernel tick
(fixed by home threads, d24881d), and the work budget's counting cost
every table row 26 instructions (fixed in be297ea). The earlier runs
are kept apart (db4ee9c's isolation, d24881d's pairs and counts, the
interrupted first try).
Diffstat (limited to 'research/experiments/026-apps-on-one-host/isolation.py')
| -rw-r--r-- | research/experiments/026-apps-on-one-host/isolation.py | 114 |
1 files changed, 114 insertions, 0 deletions
diff --git a/research/experiments/026-apps-on-one-host/isolation.py b/research/experiments/026-apps-on-one-host/isolation.py new file mode 100644 index 0000000..9ef7ad4 --- /dev/null +++ b/research/experiments/026-apps-on-one-host/isolation.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 +"""Two apps, A and B (benchmarks/http/scores.aup, the scoreboard), as two +pure-serve processes or as one host of two apps (docs/design/017), in RAM +(/dev/shm), on cores 0-2; the load (benchmarks/http/load) on core 3. The +two ways have four threads for the apps: two processes of one connection +thread and an owner each, or one host of two connection threads and two +workers. A third, host1, is a host of one connection thread and two +workers (three threads). + +Each round, both ways in turns (the order flipped every other round): + alone: B's writes, one connection, nothing else; + flood: A's writes from 64 connections, and B's from one, at once; + both: A's and B's writes from 32 connections each. +Prints a JSON line per run: B's p50 and p99, each app's writes a second. + +isolation.py SERVE ROUNDS [SECONDS] +""" +import json +import select +import shutil +import subprocess +import sys +import tempfile +import time +from pathlib import Path + +HERE = Path(__file__).resolve().parent +ROOT = HERE.parents[2] +LOAD = ROOT / 'benchmarks' / 'http' / 'load' +AUP = ROOT.parent / 'aup' / 'build' / 'aup' + + +def started(args): + process = subprocess.Popen(['taskset', '-c', '0-2', *map(str, args), '--port', '0'], stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + ready, _, _ = select.select([process.stdout], [], [], 10) + assert ready, process.stderr.read() + return process, json.loads(process.stdout.readline()) + + +def stopped(process): + process.terminate() + _, error = process.communicate(timeout=60) + assert process.returncode == 0, error[-500:] + + +def load(port, connections, seconds, prefix): + return subprocess.Popen(['taskset', '-c', '3', str(LOAD), str(port), str(connections), str(seconds), 'score', + str(connections), 'keep-alive', '16', prefix], stdout=subprocess.PIPE, text=True) + + +def result(process): + out, _ = process.communicate() + return json.loads(out) + + +def servers(way, work, serve, module): + """Where A and B are: ([processes], {app: (port, prefix)}).""" + if way == 'processes': + a, info_a = started([serve, '--module', module, '--store', work / 'a', '--threads', 1]) + b, info_b = started([serve, '--module', module, '--store', work / 'b', '--threads', 1]) + return [a, b], {'A': (info_a['port'], ''), 'B': (info_b['port'], '')} + (work / 'apps.conf').write_text(f'a {module} {work / "a"}\nb {module} {work / "b"}\n') + host, info = started([serve, '--apps', work / 'apps.conf', '--threads', 1 if way == 'host1' else 2, '--workers', 2]) + return [host], {'A': (info['port'], '/a'), 'B': (info['port'], '/b')} + + +def run(way, kind, seconds, serve, module, round_): + work = Path(tempfile.mkdtemp(prefix='pure-apps-', dir='/dev/shm')) + try: + processes, where = servers(way, work, serve, module) + try: + if kind == 'alone': + b = result(load(where['B'][0], 1, seconds, where['B'][1])) + a = None + elif kind == 'flood': + flood = load(where['A'][0], 64, seconds + 2, where['A'][1]) + time.sleep(1) + b = result(load(where['B'][0], 1, seconds, where['B'][1])) + a = result(flood) + else: + both_a = load(where['A'][0], 32, seconds, where['A'][1]) + both_b = load(where['B'][0], 32, seconds, where['B'][1]) + a, b = result(both_a), result(both_b) + finally: + for process in processes: + stopped(process) + finally: + shutil.rmtree(work) + row = {'round': round_, 'way': way, 'kind': kind, 'b_p50_us': b['p50_us'], 'b_p99_us': b['p99_us'], + 'b_per_second': b['requests_per_second'], 'b_5xx': b['5xx']} + if a: + row.update({'a_per_second': a['requests_per_second'], 'a_p99_us': a['p99_us'], 'a_5xx': a['5xx']}) + return row + + +def main(): + serve, rounds = Path(sys.argv[1]).resolve(), int(sys.argv[2]) + seconds = float(sys.argv[3]) if len(sys.argv) > 3 else 15 + work = Path(tempfile.mkdtemp(prefix='pure-apps-module-')) + try: + module = work / 'scores.pmod' + subprocess.run([str(AUP), 'build', str(ROOT / 'benchmarks' / 'http' / 'scores.aup'), '-o', str(module)], check=True) + for round_ in range(1, rounds + 1): + ways = ['processes', 'host', 'host1'] if round_ % 2 else ['host1', 'host', 'processes'] + for kind in ('alone', 'flood', 'both'): + for way in ways: + print(json.dumps(run(way, kind, seconds, serve, module, round_)), flush=True) + finally: + shutil.rmtree(work) + + +if __name__ == '__main__': + main() |
