aboutsummaryrefslogtreecommitdiffstats
path: root/research/experiments/026-apps-on-one-host/isolation.py
blob: 9ef7ad47a348f0bc45ff2b11e508670fe7faaab4 (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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
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()