aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHECKPOINT.md3
-rw-r--r--README.md8
-rw-r--r--docs/research/089-messenger.md80
-rw-r--r--research/experiments/015-messenger-latency/2026-09-27-sd-and-ram-linux-aarch64.jsonl8
-rw-r--r--research/experiments/015-messenger-latency/latency.py120
5 files changed, 215 insertions, 4 deletions
diff --git a/CHECKPOINT.md b/CHECKPOINT.md
index 3373ba7..d144896 100644
--- a/CHECKPOINT.md
+++ b/CHECKPOINT.md
@@ -15,8 +15,9 @@
| 앱 표현 | 상태(수, text와 커서·마크, 레코드의 리스트와 키 있는 리스트, 인덱스), 입력(키, text, 필드 있는 메시지, 타이머), 원자적 이벤트 핸들러, 순수 보기(조건식, 표와 걸러진 표, count·sum·max·min·first), `fn`·`use` 라이브러리, text 비교, 시간(`now`, `every`, `floor`, `%`). 로드할 때 타입·스택·합류·커서와 인덱스의 소속을 모두 검증하고 이벤트의 실행량에 상한이 있다 | 008, aup `docs/design/002-dsl-direction.md`, aup `make check`(파이썬 참조 모델 대조, 진단 132건) |
| 영속 | 선언한 상태가 곧 영속 상태다. 트리 저장소에 커밋당 동기화 한 번(헤드를 바이트 바로 앞에 두어 한 연속 구간), 이벤트 묶음마다 저장 한 번. `kill -9` 뒤 확인 응답한 이벤트가 모두 남는다. 레코드·키가 바뀐 모듈로 바꿔도 데이터가 이름으로 옮겨진다 | [076](docs/research/076-one-sync-commit.md), [081](docs/research/081-tail-records.md), 008 상태 영속·교체 |
| 인터페이스 | 한 명세가 TUI(셀 단위 갱신), CLI, 문장(NUI), 웹 GUI가 된다. 저장하면 실행 중인 앱이 상태를 이어 새 모듈로 바뀐다(`aup watch`, `vega … --watch`) | Vega `docs/design/001-first-component.md`, Vega `make check`(가상 터미널, 헤드리스 Chromium 포함) |
-| HTTP 서비스 | 보기가 GET, 입력이 POST다(JSON 본문과 그 안의 그룹 객체를 제자리에서 읽는다, 라우터 코드 없음). 앱의 `send`는 이벤트와 함께 저장되고, `--send 출력=URL`로 다른 앱이나 HTTP 끝점에 순서대로 묶어 전해지며, 받는 앱은 번호마다 한 번 적용한다(서버를 죽였다 살려도 정확히 한 번). `--seal 출력=공개키`면 받는 노드의 키로 봉인해(HPKE, 전달선마다 세션 하나, 보내는 노드의 키로 인증) 사이의 중계가 읽지도 바꾸지도 못하고, 받는 노드는 `--sender 이름=키`로 보낸 이를 가린다(봉인 전달 462k/s, 봉인 없이 466k/s). 답은 저장 뒤에 나간다. 상태를 가진 앱은 소유자 스레드 하나가 입력을 적용하고, 연결 스레드는 마지막 저장의 스냅숏으로 보기를 답한다(쓰기가 동기화하는 동안에도 읽기는 기다리지 않는다) | 008 HTTP 호스트, [077](docs/research/077-apps-as-http-services.md), [080](docs/research/080-snapshot-reads.md), [082](docs/research/082-json-in-place.md), [083](docs/research/083-delivery-over-http.md), [086](docs/research/086-sealed-sends.md), [088](docs/research/088-sealed-sessions.md) |
+| HTTP 서비스 | 보기가 GET, 입력이 POST다(JSON 본문과 그 안의 그룹 객체를 제자리에서 읽는다, 라우터 코드 없음). 앱의 `send`는 이벤트와 함께 저장되고, `--send 출력=URL`로 다른 앱이나 HTTP 끝점에 순서대로 묶어 전해지며, 받는 앱은 번호마다 한 번 적용한다(서버를 죽였다 살려도 정확히 한 번). `--seal 출력=공개키`면 받는 노드의 키로 봉인해(HPKE, 전달선마다 세션 하나, 보내는 노드의 키로 인증) 사이의 중계가 읽지도 바꾸지도 못하고, 받는 노드는 `--sender 이름=키`로 보낸 이를 가린다(봉인 전달 462k/s, 봉인 없이 468k/s). 답은 저장 뒤에 나간다. 상태를 가진 앱은 소유자 스레드 하나가 입력을 적용하고, 연결 스레드는 마지막 저장의 스냅숏으로 보기를 답한다(쓰기가 동기화하는 동안에도 읽기는 기다리지 않는다) | 008 HTTP 호스트, [077](docs/research/077-apps-as-http-services.md), [080](docs/research/080-snapshot-reads.md), [082](docs/research/082-json-in-place.md), [083](docs/research/083-delivery-over-http.md), [086](docs/research/086-sealed-sends.md), [088](docs/research/088-sealed-sessions.md) |
| 성능(Pi 5) | 2026-09-27, 같은 기기에서 axum·drogon과 함께 쟀다. 문서 서버(한 스레드): 64 B GET 139k/s(CPU 1초당 140k; axum 127k·63k, drogon 147k·63k), 1 MiB GET 2,362/s(axum 1,304, drogon 754), 충돌 안전 PUT은 axum-atomic의 5.1–5.8배. 앱 서비스: 내구성 있는 쓰기 64연결 21.3k/s(axum·drogon 내구 약 200/s), 합계 GET 126k/s·두 스레드 153k(axum 136k, drogon 151k), 20행 페이지 두 스레드 148k(axum 93k, drogon 138k), 쓰기와 섞인 읽기 119k/s(axum·drogon 내구 1.1–1.4k/s). 유휴 RSS 2.3 MiB(axum 2.8, drogon 10.9), 최대 3.2–5.8 MiB(axum 6.6–9.7, drogon 11.9–78.8) | [075](docs/research/075-http-host-against-axum.md), [077](docs/research/077-apps-as-http-services.md), [080](docs/research/080-snapshot-reads.md), [081](docs/research/081-tail-records.md), [087](docs/research/087-small-reads-and-idle-memory.md) |
+| 표면과 메신저 | 사람을 위한 표면이 노드 안에서 돈다(`pure-serve --surface`): 마지막 저장의 스냅숏을 제자리에서 읽고, 친 키와 글자는 HTTP 요청과 같은 줄로 소유자에게 간다. Vega의 디스플레이·픽셀이 그 표면이다. 두 노드의 메신저(Vega `examples/messenger.aup`)는 서로에게 봉인·인증해 보내고, 한 줄이 상대 노드에 보이기까지 RAM 저장소에서 p50 0.79 ms·p99 1.1 ms, SD 카드에서 p50 3.5 ms(꼬리는 SD의 동기화) | [설계 010](docs/design/010-surfaces.md), [089](docs/research/089-messenger.md) |
키로 나눈 소유자와 저장을 넘길 스레드는 하지 않기로 했다. 이 장치는 동기화를 한 줄로 세워, 나누면 쓰기가 23% 줄고 넘겨도
기기의 일은 줄지 않는다([084](docs/research/084-parallel-syncs.md)). 대신 압축한 파일이 한 주기의 0을 미리 가져 커밋이 새
diff --git a/README.md b/README.md
index bf9c167..b9fa6e4 100644
--- a/README.md
+++ b/README.md
@@ -8,12 +8,14 @@ terminal UI, a CLI, spoken sentences and a web page through [Vega](/srv/git/vega
durable, a commit being one contiguous write and one sync; views are read from a snapshot of the last save, so
reads never wait for a sync. What an app sends is saved with its event and delivered over HTTP in order and in
batches, and a receiving app applies each message once; sealed to the receiver's key (HPKE, one session a wire,
-authenticated by the sender's key), no relay between them can read or change it, at the speed of unsealed sends. Measurements against axum and drogon on a Raspberry Pi 5
+authenticated by the sender's key), no relay between them can read or change it, at the speed of unsealed sends.
+People use a node through surfaces it runs, reading its states where they are (Vega's display: a two-node messenger
+shows a line on the other node's screen in about a millisecond). Measurements against axum and drogon on a Raspberry Pi 5
are in [075](docs/research/075-http-host-against-axum.md), [077](docs/research/077-apps-as-http-services.md),
[080](docs/research/080-snapshot-reads.md), [081](docs/research/081-tail-records.md),
[082](docs/research/082-json-in-place.md), [083](docs/research/083-delivery-over-http.md),
-[086](docs/research/086-sealed-sends.md), [087](docs/research/087-small-reads-and-idle-memory.md) and
-[088](docs/research/088-sealed-sessions.md); the status table is
+[086](docs/research/086-sealed-sends.md), [087](docs/research/087-small-reads-and-idle-memory.md),
+[088](docs/research/088-sealed-sessions.md) and [089](docs/research/089-messenger.md); the status table is
[CHECKPOINT.md](CHECKPOINT.md).
The source follows the principle below, a small core with the rest selected around it
diff --git a/docs/research/089-messenger.md b/docs/research/089-messenger.md
new file mode 100644
index 0000000..a74107d
--- /dev/null
+++ b/docs/research/089-messenger.md
@@ -0,0 +1,80 @@
+# 089 — 메신저: 두 노드, 끝에서 끝까지 봉인, 노드 안의 화면
+
+2026-09-27. 로드맵의 "E2EE 초저지연 메신저"를 런타임 쪽 능력으로 처음 끝까지 이은 것. 앱은 Vega의
+`examples/messenger.aup`이다(앱 기능은 런타임 밖에 둔다). 여기서는 런타임이 무엇을 주고 얼마나 걸리는지를 적는다.
+
+착수 기준:
+
+- 바꿀 결정: 메신저를 이루는 것(봉인한 전달, 보낸 이 인증, 사람이 쓰는 화면, 그 사이의 지연)을 런타임의 일반 능력으로 줄지,
+ 메신저 전용 코드로 만들지. 전용 코드는 쓰지 않는다. 086·088의 봉인 전달과 설계 010의 표면으로 된다는 것을 보인다.
+- 최소 확인: 두 노드가 서로에게 봉인해 보내고, 한쪽 화면에서 친 줄이 다른 쪽 화면에 그려지며, 사이의 중계는 평문을 보지
+ 못한다는 것을 검사로 지킨다. 한 줄이 상대 노드에 보이기까지의 시간을 봉인 유무와 저장 장치별로 잰다.
+- 중단 기준: 메신저를 위해 런타임에 앱 전용 개념(대화, 연락처)을 넣어야 한다면 멈추고 다시 설계한다.
+
+## 이어진 것
+
+| 로드맵이 말한 것 | 무엇으로 |
+|---|---|
+| 끝에서 끝까지 암호(E2EE) | HPKE 봉인(RFC 9180). 전달선마다 세션 하나, 보내는 노드의 키로 auth 모드([088](088-sealed-sessions.md)) |
+| P2P | 노드에서 노드로. 중계·프록시가 사이에 있어도 읽지도 바꾸지도 못한다([086](086-sealed-sends.md)) |
+| 하드웨어 암호 직결 | CPU에 AES 명령이 있으면 AES-128-GCM(이 A76에서 ChaCha20-Poly1305의 2–3배), 없으면 ChaCha20-Poly1305 |
+| 메모리 포렌식 면역 | 연 평문은 요청 티켓이 반납될 때 0으로 지운다. 개인키는 OpenSSL 보안 힙에 둔다(잠김, 코어 덤프 제외, 해제 때 지움) |
+| 화면 | 노드 안의 Vega([설계 010](../design/010-surfaces.md)). 마지막 저장을 제자리에서 읽고, 친 키와 글자는 소유자의 줄로 |
+
+앱은 이렇게 생겼다. 줄 목록 상태, 화면에서 치는 텍스트 입력 `say`, 상대 노드가 보내는 메시지 입력 `heard`, 상대에게 가는
+출력 `said`다. `on say`가 줄을 더하고 `send said`한다. 노드는 이렇게 연결한다.
+
+```sh
+pure-serve --module messenger.pmod --store alice --key alice.key --name alice \
+ --send said=http://bob:8802/inputs/heard --seal said=BOB_KEY --sender bob.said=BOB_KEY \
+ --surface vega-surface.so -- display --symbols ui.vsym --live
+```
+
+런타임에 들어간 메신저 전용 코드는 없다. 대화와 연락처는 앱의 상태이고, 보낸 이는 전달의 출처와 키이며, 화면은 표면이다.
+
+## 검사
+
+Vega의 `tests/check.py`의 `messenger`는 앨리스와 밥의 노드를 띄운다. 각자 제 키를 갖고, 서로에게 봉인하며, 상대만을 보낸
+이로 받는다. 둘 사이에는 기록하는 중계를 둔다. 각 노드에는 Vega가 픽셀 표면으로 들어 있어, 저장마다 한 프레임을 파일로
+그린다.
+
+1. 앨리스의 화면에 녹화한 키로 "hello bob"과 Enter를 친다.
+2. 밥의 노드가 그 줄을 보인다.
+3. 밥은 프로그램으로(HTTP) "hi alice"를 보낸다.
+4. 앨리스의 노드와 화면에 그 줄이 그려진다.
+
+검사는 다음을 확인한다.
+
+- 두 노드의 마지막 프레임이 각 노드의 저장소를 `vega pixels`로 다시 그린 것과 바이트까지 같다.
+- 중계가 본 본문에는 줄의 글자도 필드 이름도 없다.
+- 방향마다 봉인 세션 하나가 있고, 봉인은 보낸 이의 키를 적는다.
+
+Vega 검사 25개 모두와 함께 통과한다.
+
+## 지연
+
+한 프로그램이 앨리스의 노드에 줄 300개를 하나씩 보내고 둘을 잰다. 하나는 앨리스의 노드가 저장해 답할 때까지이고, 다른
+하나는 밥의 노드가 그 줄을 보일 때까지다(밥의 제목 보기를 0.2 ms마다 읽는다). 두 노드는 직접 연결했다. 서버는 코어 0–2를
+쓰고, 변형의 순서는 라운드마다 돌렸다([latency.py](../../research/experiments/015-messenger-latency/latency.py),
+[원시 결과](../../research/experiments/015-messenger-latency/2026-09-27-sd-and-ram-linux-aarch64.jsonl)).
+
+| 저장소 | 봉인 | 앨리스 저장 p50 | 밥에게 보임 p50 | p90 | p99 |
+|---|---|---:|---:|---:|---:|
+| RAM(`/dev/shm`) | 봉인·인증 | 0.34 ms | **0.79 ms** | 0.82–0.83 ms | **1.09–1.13 ms** |
+| RAM | 없음 | 0.33 ms | 0.78 ms | 0.81–0.84 ms | 0.89–1.05 ms |
+| SD 카드(ext4) | 봉인·인증 | 1.64–1.72 ms | 3.45–3.69 ms | 6.7–6.8 ms | 62–78 ms |
+| SD 카드 | 없음 | 1.65–1.84 ms | 3.42–3.72 ms | 6.6–10.0 ms | 62–84 ms |
+
+한 줄이 두 노드의 내구 커밋 두 번, HTTP 전달 하나, 봉인과 열기를 거쳐 상대에게 보이기까지 1 ms 안쪽이다. 봉인의 몫은 잡음
+안이다. SD 카드의 긴 꼬리는 동기화 자체다. 앨리스의 저장 p99가 이미 20–66 ms이고, 밥의 저장이 또 하나를 더한다. 소프트웨어가
+줄일 몫이 아니다. NVMe 같은 장치에서는 RAM 쪽에 가까울 것이다(잴 장치가 아직 없다).
+
+## 한계와 다음
+
+- **전방 비밀성**: 없다. 받는 노드의 키를 훔치면 기록해 둔 세션이 열린다. 받는 키를 돌려 쓰기(서명한 사전 키)가 다음이다.
+- **보낸 이 인증**: auth 모드는 키 누설 사칭(KCI)에 약하다. 받는 노드의 키를 가진 쪽은 그 노드에게 누구로든 보낼 수 있다(088).
+- **오프라인**: 받는 노드가 꺼져 있으면 보내는 노드의 보낼 함(저장된 send)이 기다린다. 두 노드가 동시에 켜져 있어야 간다.
+ 늘 켜진 우편함 노드(봉인한 요청을 열지 않고 맡아 두었다 전하는 것)가 다음 후보다.
+- **여럿**: 한 출력은 한 수신자에게 간다. 연락처가 여럿이면 필드로 수신자를 고르는 전달이 필요하다.
+- **메타데이터**: 출처, 번호, 크기, 보낸 이의 공개키가 중계에 보인다.
+- **화면**: 노드 안의 표면은 키와 글자만 준다. 가리키기(터치로 행 고르기)는 표면 표에 아직 없다.
diff --git a/research/experiments/015-messenger-latency/2026-09-27-sd-and-ram-linux-aarch64.jsonl b/research/experiments/015-messenger-latency/2026-09-27-sd-and-ram-linux-aarch64.jsonl
new file mode 100644
index 0000000..41633eb
--- /dev/null
+++ b/research/experiments/015-messenger-latency/2026-09-27-sd-and-ram-linux-aarch64.jsonl
@@ -0,0 +1,8 @@
+{"round": 0, "directory": "/var/tmp", "sealed": true, "lines": 300, "saved_p50_ms": 1.64, "saved_p99_ms": 66.34, "delivered_p50_ms": 1.82, "delivered_p99_ms": 22.69, "shown_p50_ms": 3.45, "shown_p90_ms": 6.81, "shown_p99_ms": 77.82, "shown_max_ms": 82.88}
+{"round": 0, "directory": "/var/tmp", "sealed": false, "lines": 300, "saved_p50_ms": 1.84, "saved_p99_ms": 61.26, "delivered_p50_ms": 1.83, "delivered_p99_ms": 66.79, "shown_p50_ms": 3.72, "shown_p90_ms": 9.96, "shown_p99_ms": 83.57, "shown_max_ms": 285.19}
+{"round": 1, "directory": "/var/tmp", "sealed": false, "lines": 300, "saved_p50_ms": 1.65, "saved_p99_ms": 20.78, "delivered_p50_ms": 1.68, "delivered_p99_ms": 59.8, "shown_p50_ms": 3.42, "shown_p90_ms": 6.58, "shown_p99_ms": 62.0, "shown_max_ms": 83.38}
+{"round": 1, "directory": "/var/tmp", "sealed": true, "lines": 300, "saved_p50_ms": 1.72, "saved_p99_ms": 59.58, "delivered_p50_ms": 1.76, "delivered_p99_ms": 55.84, "shown_p50_ms": 3.69, "shown_p90_ms": 6.71, "shown_p99_ms": 62.16, "shown_max_ms": 80.68}
+{"round": 0, "directory": "/dev/shm", "sealed": true, "lines": 300, "saved_p50_ms": 0.34, "saved_p99_ms": 0.58, "delivered_p50_ms": 0.45, "delivered_p99_ms": 0.63, "shown_p50_ms": 0.79, "shown_p90_ms": 0.83, "shown_p99_ms": 1.13, "shown_max_ms": 4.32}
+{"round": 0, "directory": "/dev/shm", "sealed": false, "lines": 300, "saved_p50_ms": 0.33, "saved_p99_ms": 0.4, "delivered_p50_ms": 0.45, "delivered_p99_ms": 0.51, "shown_p50_ms": 0.78, "shown_p90_ms": 0.81, "shown_p99_ms": 0.89, "shown_max_ms": 0.9}
+{"round": 1, "directory": "/dev/shm", "sealed": false, "lines": 300, "saved_p50_ms": 0.33, "saved_p99_ms": 0.48, "delivered_p50_ms": 0.45, "delivered_p99_ms": 0.56, "shown_p50_ms": 0.78, "shown_p90_ms": 0.84, "shown_p99_ms": 1.05, "shown_max_ms": 3.28}
+{"round": 1, "directory": "/dev/shm", "sealed": true, "lines": 300, "saved_p50_ms": 0.34, "saved_p99_ms": 0.52, "delivered_p50_ms": 0.45, "delivered_p99_ms": 0.61, "shown_p50_ms": 0.79, "shown_p90_ms": 0.82, "shown_p99_ms": 1.09, "shown_max_ms": 3.62}
diff --git a/research/experiments/015-messenger-latency/latency.py b/research/experiments/015-messenger-latency/latency.py
new file mode 100644
index 0000000..5c67f88
--- /dev/null
+++ b/research/experiments/015-messenger-latency/latency.py
@@ -0,0 +1,120 @@
+#!/usr/bin/env python3
+"""How long a line takes from one messenger node to the other (docs/research/089-messenger.md).
+
+Two nodes of Vega's examples/messenger.aup on this machine, Alice's and Bob's,
+wired to each other directly: sealed (each with a key of its own, sealing to
+the other's, naming the other as its one sender) or plain. A program posts
+--lines lines to Alice's node one after another; for each it times the post
+(answered once Alice's node saved the line) and the moment Bob's node shows
+it (its title view counts the line; polled every 0.2 ms). Nodes on cores 0-2,
+stores on --directory. Prints one JSON line per variant and round.
+"""
+import argparse
+import http.client
+import json
+import os
+import select
+import shutil
+import signal
+import statistics
+import subprocess
+import tempfile
+import time
+from pathlib import Path
+
+HERE = Path(__file__).resolve().parent
+ROOT = HERE.parents[2]
+
+
+def start(serve, module, work, name, *extra):
+ process = subprocess.Popen(['taskset', '-c', '0-2', str(serve), '--module', str(module), '--store',
+ str(work / f'{name}-store'), '--port', '0', '--name', name, *map(str, extra)],
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ ready, _, _ = select.select([process.stdout], [], [], 10)
+ assert ready, 'node did not start'
+ return process, json.loads(process.stdout.readline())
+
+
+def stop(process):
+ process.send_signal(signal.SIGTERM)
+ _, error = process.communicate(timeout=30)
+ assert process.returncode == 0, error[-500:]
+
+
+class Client:
+ def __init__(self, port):
+ self.connection = http.client.HTTPConnection('127.0.0.1', port, timeout=10)
+
+ def call(self, method, path, body=None):
+ self.connection.request(method, path, body)
+ answer = self.connection.getresponse()
+ return answer.status, answer.read().decode()
+
+
+def run(serve, module, work, sealed, lines):
+ for name in ('alice', 'bob'):
+ shutil.rmtree(work / f'{name}-store', ignore_errors=True)
+ keys = {}
+ for name in ('alice', 'bob'):
+ extra = ['--key', work / f'{name}.key'] if sealed else []
+ process, started = start(serve, module, work, name, *extra)
+ keys[name] = started.get('public_key')
+ assert Client(started['port']).call('POST', '/inputs/rename', name.encode())[0] == 200
+ stop(process)
+ # Bob first, on a port of its own; then Alice, wired to it; then Bob again, wired to Alice.
+ def wiring(name, other, port):
+ wire = ['--send', f'said=http://127.0.0.1:{port}/inputs/heard']
+ if sealed:
+ wire += ['--key', work / f'{name}.key', '--seal', f'said={keys[other]}', '--sender',
+ f'{other}.said={keys[other]}']
+ return wire
+ bob, bob_started = start(serve, module, work, 'bob', *(['--key', work / 'bob.key'] if sealed else []))
+ alice, alice_started = start(serve, module, work, 'alice', *wiring('alice', 'bob', bob_started['port']))
+ to_alice, to_bob = Client(alice_started['port']), Client(bob_started['port'])
+ saved, seen = [], []
+ try:
+ for i in range(lines):
+ began = time.perf_counter()
+ assert to_alice.call('POST', '/inputs/say', f'line {i}'.encode())[0] == 200
+ saved.append(time.perf_counter() - began)
+ want = f'bob: {i + 1} lines'
+ while to_bob.call('GET', '/views/title')[1] != want:
+ time.sleep(0.0002)
+ seen.append(time.perf_counter() - began)
+ finally:
+ stop(alice)
+ stop(bob)
+
+ def ms(values, q):
+ return round(statistics.quantiles(values, n=100)[q - 1] * 1000, 2)
+ delivered = [b - a for a, b in zip(saved, seen)] # from Alice's answer to Bob's view
+ return {'sealed': sealed, 'lines': lines, 'saved_p50_ms': ms(saved, 50), 'saved_p99_ms': ms(saved, 99),
+ 'delivered_p50_ms': ms(delivered, 50), 'delivered_p99_ms': ms(delivered, 99),
+ 'shown_p50_ms': ms(seen, 50), 'shown_p90_ms': ms(seen, 90), 'shown_p99_ms': ms(seen, 99),
+ 'shown_max_ms': round(max(seen) * 1000, 2)}
+
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--aup', type=Path, default=ROOT.parent / 'aup' / 'build' / 'aup')
+ parser.add_argument('--vega', type=Path, default=ROOT.parent / 'vega')
+ parser.add_argument('--serve', type=Path, default=ROOT / 'build' / 'pure-serve')
+ parser.add_argument('--directory', type=Path, default=Path('/var/tmp'))
+ parser.add_argument('--lines', type=int, default=300)
+ parser.add_argument('--rounds', type=int, default=3)
+ args = parser.parse_args()
+ work = Path(tempfile.mkdtemp(prefix='pure-messenger-', dir=args.directory))
+ try:
+ module = work / 'messenger.pmod'
+ subprocess.run([str(args.aup), 'build', str(args.vega / 'examples' / 'messenger.aup'), '-o', str(module)],
+ env=dict(os.environ, AUP_PATH=str(args.vega / 'lib')), check=True)
+ for round_ in range(args.rounds):
+ for sealed in (True, False) if round_ % 2 == 0 else (False, True):
+ print(json.dumps({'round': round_, 'directory': str(args.directory),
+ **run(args.serve, module, work, sealed, args.lines)}), flush=True)
+ finally:
+ shutil.rmtree(work)
+
+
+if __name__ == '__main__':
+ main()