diff options
Diffstat (limited to 'research/experiments')
6 files changed, 166 insertions, 0 deletions
diff --git a/research/experiments/027-apps-sending/2026-10-01-load.txt b/research/experiments/027-apps-sending/2026-10-01-load.txt new file mode 100644 index 0000000..877d5fc --- /dev/null +++ b/research/experiments/027-apps-sending/2026-10-01-load.txt @@ -0,0 +1,2 @@ + 07:04:41 up 4:17, 3 users, load average: 1.33, 4.07, 4.66 + 07:05:09 up 4:17, 3 users, load average: 1.58, 3.87, 4.57 diff --git a/research/experiments/027-apps-sending/2026-10-01-sends-linux-aarch64.jsonl b/research/experiments/027-apps-sending/2026-10-01-sends-linux-aarch64.jsonl new file mode 100644 index 0000000..194d5c3 --- /dev/null +++ b/research/experiments/027-apps-sending/2026-10-01-sends-linux-aarch64.jsonl @@ -0,0 +1,10 @@ +{"round": 1, "way": "local", "sends": 2000000, "seconds": 1.778, "sends_per_second": 1124661, "cpu_us_per_send": 1.17, "handed": 1999, "link_packets": 0} +{"round": 1, "way": "link", "sends": 2000000, "seconds": 3.477, "sends_per_second": 575155, "cpu_us_per_send": 2.92, "handed": 0, "link_packets": 63888} +{"round": 2, "way": "link", "sends": 2000000, "seconds": 3.383, "sends_per_second": 591140, "cpu_us_per_send": 2.87, "handed": 0, "link_packets": 63094} +{"round": 2, "way": "local", "sends": 2000000, "seconds": 1.74, "sends_per_second": 1149587, "cpu_us_per_send": 1.17, "handed": 2000, "link_packets": 0} +{"round": 3, "way": "local", "sends": 2000000, "seconds": 1.756, "sends_per_second": 1138945, "cpu_us_per_send": 1.18, "handed": 2000, "link_packets": 0} +{"round": 3, "way": "link", "sends": 2000000, "seconds": 3.455, "sends_per_second": 578879, "cpu_us_per_send": 2.94, "handed": 0, "link_packets": 66506} +{"round": 4, "way": "link", "sends": 2000000, "seconds": 3.636, "sends_per_second": 550106, "cpu_us_per_send": 3.12, "handed": 0, "link_packets": 72477} +{"round": 4, "way": "local", "sends": 2000000, "seconds": 1.752, "sends_per_second": 1141642, "cpu_us_per_send": 1.17, "handed": 2000, "link_packets": 0} +{"round": 5, "way": "local", "sends": 2000000, "seconds": 1.721, "sends_per_second": 1161814, "cpu_us_per_send": 1.16, "handed": 2000, "link_packets": 0} +{"round": 5, "way": "link", "sends": 2000000, "seconds": 3.325, "sends_per_second": 601578, "cpu_us_per_send": 2.93, "handed": 0, "link_packets": 69839} diff --git a/research/experiments/027-apps-sending/dst.aup b/research/experiments/027-apps-sending/dst.aup new file mode 100644 index 0000000..c1c8acb --- /dev/null +++ b/research/experiments/027-apps-sending/dst.aup @@ -0,0 +1,9 @@ +state n = 0 +state total = 0 +input take = message { k: u32 } +on take { + n = n + 1 + total = total + take.k +} +view count = n +view sum = total diff --git a/research/experiments/027-apps-sending/runs.sh b/research/experiments/027-apps-sending/runs.sh new file mode 100755 index 0000000..fe4dfda --- /dev/null +++ b/research/experiments/027-apps-sending/runs.sh @@ -0,0 +1,12 @@ +#!/bin/sh +# The runs of docs/design/017's second stage, from the repository's root: +# two apps of one host, sending through the local outlet or over a +# loopback link, five rounds of 2,000 bursts (2,000,000 sends), the ways in +# turns, once the last minute's load is below 1.5. +set -e +out=research/experiments/027-apps-sending +until awk '{ exit !($1 < 1.5) }' /proc/loadavg; do sleep 10; done +echo "$(uptime)" > $out/2026-10-01-load.txt +make -s build/pure-serve +python3 $out/sends.py build/pure-serve 5 2000 > $out/2026-10-01-sends-linux-aarch64.jsonl +echo "$(uptime)" >> $out/2026-10-01-load.txt diff --git a/research/experiments/027-apps-sending/sends.py b/research/experiments/027-apps-sending/sends.py new file mode 100644 index 0000000..454dc2b --- /dev/null +++ b/research/experiments/027-apps-sending/sends.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 +"""Two apps of one host (docs/design/017, stage 2), src sending to dst +either through the local outlet (send=out=dst/take) or over a loopback +pure:// link to the host's own --link-port (sealed with its key). src +holds 1,000 items; each burst event walks them and sends one message +each, so sending (not HTTP) is what the host does. A run: BURSTS bursts +posted one after another, then the time until dst has stored every send, +and the host's CPU over it (utime + stime). dst's sum says each send came +once. In RAM, the host on cores 0-2. + +sends.py SERVE ROUNDS [BURSTS] prints a JSON line per run +""" +import http.client +import json +import os +import select +import shutil +import socket +import subprocess +import sys +import tempfile +import time +from pathlib import Path + +HERE = Path(__file__).resolve().parent +ROOT = HERE.parents[2] +AUP = ROOT.parent / 'aup' / 'build' / 'aup' +ITEMS = 1000 + + +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:] + return json.loads(error.decode().strip().splitlines()[-1]) + + +def cpu_seconds(pid): + fields = Path(f'/proc/{pid}/stat').read_text().rsplit(')', 1)[1].split() + return (int(fields[11]) + int(fields[12])) / os.sysconf('SC_CLK_TCK') + + +def run(way, serve, work, modules, key, public, bursts): + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as probe: + probe.bind(('127.0.0.1', 0)) + link_port = probe.getsockname()[1] + to = 'dst/take' if way == 'local' else f'pure://127.0.0.1:{link_port}/dst/take seal=out={public}' + (work / 'apps.conf').write_text(f"src {modules['src']} {work / 'src'} send=out={to}\n" + f"dst {modules['dst']} {work / 'dst'}\n") + args = [serve, '--apps', work / 'apps.conf', '--threads', 1, '--workers', 2] + if way == 'link': + args += ['--key', key, '--link-port', link_port, '--sender', f'src.out={public}'] + process, info = started(args) + client = http.client.HTTPConnection('127.0.0.1', info['port'], timeout=60) + + def ask(method, path): + client.request(method, path, b'' if method == 'POST' else None) + answer = client.getresponse() + body = answer.read() + assert answer.status == 200, (path, answer.status) + return body + + try: + for k in range(1, ITEMS + 1): + ask('POST', f'/src/inputs/fill?k={k}') + began, cpu = time.perf_counter(), cpu_seconds(process.pid) + for _ in range(bursts): + ask('POST', '/src/inputs/burst?step=1') + sent = bursts * ITEMS + while int(ask('GET', '/dst/views/count')) < sent: + time.sleep(0.005) + took, used = time.perf_counter() - began, cpu_seconds(process.pid) - cpu + total = int(ask('GET', '/dst/views/sum')) + assert int(ask('GET', '/dst/views/count')) == sent and total == bursts * (ITEMS * (ITEMS + 1) // 2 + ITEMS), total + finally: + client.close() + stats = stopped(process) + return {'way': way, 'sends': sent, 'seconds': round(took, 3), 'sends_per_second': round(sent / took), + 'cpu_us_per_send': round(used * 1e6 / sent, 2), 'handed': stats.get('handed', 0), + 'link_packets': stats['link_packets']} + + +def main(): + serve, rounds = Path(sys.argv[1]).resolve(), int(sys.argv[2]) + bursts = int(sys.argv[3]) if len(sys.argv) > 3 else 60 + work = Path(tempfile.mkdtemp(prefix='pure-sends-', dir='/dev/shm')) + try: + modules = {} + for name in ('src', 'dst'): + modules[name] = work / f'{name}.pmod' + subprocess.run([str(AUP), 'build', str(HERE / f'{name}.aup'), '-o', str(modules[name])], check=True) + key = work / 'host.key' + (work / 'one.conf').write_text(f"x {modules['dst']} {work / 'keys'}\n") + process, info = started([serve, '--apps', work / 'one.conf', '--key', key]) + public = info['public_key'] + stopped(process) + for round_ in range(1, rounds + 1): + for way in (['local', 'link'] if round_ % 2 else ['link', 'local']): + runs = work / f'run-{round_}-{way}' + runs.mkdir() + row = run(way, serve, runs, modules, key, public, bursts) + print(json.dumps({'round': round_, **row}), flush=True) + shutil.rmtree(runs) + finally: + shutil.rmtree(work) + + +if __name__ == '__main__': + main() diff --git a/research/experiments/027-apps-sending/src.aup b/research/experiments/027-apps-sending/src.aup new file mode 100644 index 0000000..6ec7c4c --- /dev/null +++ b/research/experiments/027-apps-sending/src.aup @@ -0,0 +1,16 @@ +record Item { + k = 0 +} +state items: list of Item +input fill = message { k: u32 } +input burst = message { step: u32 } +output out = message { k: u32 } +on fill { + items.append(Item { k: fill.k }) +} +on burst { + for i in items { + send out { k: i.k + burst.step } + } +} +view count = count(i in items where i.k > 0) |
