diff options
Diffstat (limited to 'research/experiments/026-apps-on-one-host/memory.py')
| -rw-r--r-- | research/experiments/026-apps-on-one-host/memory.py | 76 |
1 files changed, 76 insertions, 0 deletions
diff --git a/research/experiments/026-apps-on-one-host/memory.py b/research/experiments/026-apps-on-one-host/memory.py new file mode 100644 index 0000000..05c93c5 --- /dev/null +++ b/research/experiments/026-apps-on-one-host/memory.py @@ -0,0 +1,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() |
