aboutsummaryrefslogtreecommitdiffstats
path: root/research/experiments/026-apps-on-one-host/memory.py
blob: 05c93c5182484d42c14d8af276a4e62c841e5517 (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 costs a host (docs/design/017): pure-serve with N apps of the
scoreboard (benchmarks/http/scores.aup), each written once and read once
(its store opened, its snapshot published, a reader of it used), in RAM;
then the host's own memory (smaps_rollup's Private_Dirty, RSS, PSS) and
threads. Beside it, one pure-serve of one app (--module) the same way: the
process an app costs when each app is a process.

memory.py SERVE [N ...]
"""
import http.client
import json
import select
import shutil
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'


def used(pid):
    fields = {}
    for line in Path(f'/proc/{pid}/smaps_rollup').read_text().splitlines()[1:]:
        name, value = line.split(':')
        fields[name] = int(value.split()[0])
    threads = int(Path(f'/proc/{pid}/status').read_text().split('Threads:')[1].split()[0])
    return {'private_dirty_kib': fields['Private_Dirty'], 'rss_kib': fields['Rss'], 'pss_kib': fields['Pss'],
            'threads': threads}


def touched(port, prefixes):
    connection = http.client.HTTPConnection('127.0.0.1', port, timeout=30)
    for prefix in prefixes:
        for method, path, body in (('POST', f'{prefix}/inputs/score?points=1', b'k'), ('GET', f'{prefix}/views/total', None)):
            connection.request(method, path, body, {'Content-Length': str(len(body))} if body else {})
            answer = connection.getresponse()
            answer.read()
            assert answer.status == 200, (path, answer.status)
    connection.close()


def measured(args, prefixes):
    process = subprocess.Popen([*map(str, args), '--port', '0'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    select.select([process.stdout], [], [], 30)
    info = json.loads(process.stdout.readline())
    touched(info['port'], prefixes)
    result = used(process.pid)
    process.terminate()
    process.communicate(timeout=60)
    return result


def main():
    serve = Path(sys.argv[1]).resolve()
    counts = [int(n) for n in sys.argv[2:]] or [1, 10, 100]
    work = Path(tempfile.mkdtemp(prefix='pure-apps-memory-', dir='/dev/shm'))
    try:
        module = work / 'scores.pmod'
        subprocess.run([str(AUP), 'build', str(ROOT / 'benchmarks' / 'http' / 'scores.aup'), '-o', str(module)], check=True)
        one = measured([serve, '--module', module, '--store', work / 'one', '--threads', 1], [''])
        print(json.dumps({'way': 'process', 'apps': 1, **one}), flush=True)
        for n in counts:
            manifest = work / f'apps-{n}.conf'
            manifest.write_text(''.join(f'app{i} {module} {work / f"store-{n}-{i}"}\n' for i in range(n)))
            host = measured([serve, '--apps', manifest, '--threads', 1, '--workers', 2], [f'/app{i}' for i in range(n)])
            print(json.dumps({'way': 'host', 'apps': n, **host}), flush=True)
    finally:
        shutil.rmtree(work)


if __name__ == '__main__':
    main()