aboutsummaryrefslogtreecommitdiffstats
path: root/research/experiments/026-apps-on-one-host/memory.py
diff options
context:
space:
mode:
Diffstat (limited to 'research/experiments/026-apps-on-one-host/memory.py')
-rw-r--r--research/experiments/026-apps-on-one-host/memory.py76
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()