From 792b34a127de71abc73f4071e8a7363864eef170 Mon Sep 17 00:00:00 2001 From: eunhhu Date: Thu, 1 Oct 2026 07:05:51 +0100 Subject: Measure sending between two apps of a host: local outlet or link (112) Two apps of one host, src sending one message for each of 1,000 items in a burst event, 2,000 bursts a run, five rounds with the ways in turns: the local outlet stores 1,141,642 sends a second at 1.17 us of the host's CPU each, a loopback pure:// link to the host's own port 578,879 at 2.93 us (1.97 times the sends, 0.40 times the CPU). 017's second stage asked for at least the link's rate at half its CPU or less: it goes in. Every run checks dst's sum: each send came once. --- .../027-apps-sending/2026-10-01-load.txt | 2 + .../2026-10-01-sends-linux-aarch64.jsonl | 10 ++ research/experiments/027-apps-sending/dst.aup | 9 ++ research/experiments/027-apps-sending/runs.sh | 12 +++ research/experiments/027-apps-sending/sends.py | 117 +++++++++++++++++++++ research/experiments/027-apps-sending/src.aup | 16 +++ 6 files changed, 166 insertions(+) create mode 100644 research/experiments/027-apps-sending/2026-10-01-load.txt create mode 100644 research/experiments/027-apps-sending/2026-10-01-sends-linux-aarch64.jsonl create mode 100644 research/experiments/027-apps-sending/dst.aup create mode 100755 research/experiments/027-apps-sending/runs.sh create mode 100644 research/experiments/027-apps-sending/sends.py create mode 100644 research/experiments/027-apps-sending/src.aup (limited to 'research') 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) -- cgit v1.2.3