aboutsummaryrefslogtreecommitdiffstats
path: root/research
diff options
context:
space:
mode:
Diffstat (limited to 'research')
-rw-r--r--research/experiments/019-idle-links/2026-09-29-drain-linux-aarch64.jsonl16
-rw-r--r--research/experiments/019-idle-links/2026-09-29-transport-linux-aarch64.jsonl30
-rw-r--r--research/experiments/019-idle-links/quic/src/main.rs49
-rw-r--r--research/experiments/019-idle-links/run.py193
4 files changed, 267 insertions, 21 deletions
diff --git a/research/experiments/019-idle-links/2026-09-29-drain-linux-aarch64.jsonl b/research/experiments/019-idle-links/2026-09-29-drain-linux-aarch64.jsonl
new file mode 100644
index 0000000..f3758ff
--- /dev/null
+++ b/research/experiments/019-idle-links/2026-09-29-drain-linux-aarch64.jsonl
@@ -0,0 +1,16 @@
+{"side": "pure", "messages": 20000, "item_bytes": 48, "first_seen": 1793, "seconds": 0.0169, "per_second": 1076457, "sender_cpu_us_per_message": 0.47, "sender_link_cpu_us_per_message": 0.26, "receiver_cpu_us_per_message": 1.08, "receiver_link_cpu_us_per_message": 0.33}
+{"side": "quinn", "messages": 20000, "size": 61, "read": 20000, "seconds": 0.004, "per_second": 4676464, "client_cpu_us_per_message": 0.34, "service_cpu_us_per_message": 0.23}
+{"side": "pure", "messages": 20000, "item_bytes": 48, "first_seen": 1281, "seconds": 0.0166, "per_second": 1125905, "sender_cpu_us_per_message": 0.46, "sender_link_cpu_us_per_message": 0.27, "receiver_cpu_us_per_message": 1.1, "receiver_link_cpu_us_per_message": 0.32}
+{"side": "quinn", "messages": 20000, "size": 61, "read": 20000, "seconds": 0.003, "per_second": 6193413, "client_cpu_us_per_message": 0.25, "service_cpu_us_per_message": 0.18}
+{"side": "pure", "messages": 20000, "item_bytes": 48, "first_seen": 769, "seconds": 0.0196, "per_second": 983509, "sender_cpu_us_per_message": 0.48, "sender_link_cpu_us_per_message": 0.27, "receiver_cpu_us_per_message": 1.2, "receiver_link_cpu_us_per_message": 0.33}
+{"side": "quinn", "messages": 20000, "size": 61, "read": 20000, "seconds": 0.003, "per_second": 6330681, "client_cpu_us_per_message": 0.24, "service_cpu_us_per_message": 0.18}
+{"side": "pure", "messages": 20000, "item_bytes": 1000, "first_seen": 306, "seconds": 0.0572, "per_second": 344364, "sender_cpu_us_per_message": 2.71, "sender_link_cpu_us_per_message": 2.19, "receiver_cpu_us_per_message": 4.16, "receiver_link_cpu_us_per_message": 2.18}
+{"side": "quinn", "messages": 20000, "size": 1013, "read": 20000, "seconds": 0.039, "per_second": 511194, "client_cpu_us_per_message": 2.04, "service_cpu_us_per_message": 1.93}
+{"side": "pure", "messages": 20000, "item_bytes": 1000, "first_seen": 331, "seconds": 0.0566, "per_second": 347422, "sender_cpu_us_per_message": 2.63, "sender_link_cpu_us_per_message": 2.22, "receiver_cpu_us_per_message": 4.28, "receiver_link_cpu_us_per_message": 2.18}
+{"side": "quinn", "messages": 20000, "size": 1013, "read": 20000, "seconds": 0.039, "per_second": 511684, "client_cpu_us_per_message": 2.04, "service_cpu_us_per_message": 1.88}
+{"side": "pure", "messages": 20000, "item_bytes": 1000, "first_seen": 84, "seconds": 0.0544, "per_second": 365774, "sender_cpu_us_per_message": 2.72, "sender_link_cpu_us_per_message": 2.25, "receiver_cpu_us_per_message": 4.09, "receiver_link_cpu_us_per_message": 2.22}
+{"side": "quinn", "messages": 20000, "size": 1013, "read": 20000, "seconds": 0.042, "per_second": 480746, "client_cpu_us_per_message": 2.15, "service_cpu_us_per_message": 1.89}
+{"side": "pure", "messages": 20000, "item_bytes": 16000, "first_seen": 7, "seconds": 1.0315, "per_second": 19382, "sender_cpu_us_per_message": 48.02, "sender_link_cpu_us_per_message": 44.77, "receiver_cpu_us_per_message": 66.42, "receiver_link_cpu_us_per_message": 39.39}
+{"side": "quinn", "messages": 20000, "size": 16013, "read": 20000, "seconds": 0.619, "per_second": 32335, "client_cpu_us_per_message": 31.01, "service_cpu_us_per_message": 28.92}
+{"side": "pure", "messages": 20000, "item_bytes": 16000, "first_seen": 17, "seconds": 0.7495, "per_second": 26662, "sender_cpu_us_per_message": 38.13, "sender_link_cpu_us_per_message": 36.46, "receiver_cpu_us_per_message": 52.28, "receiver_link_cpu_us_per_message": 32.05}
+{"side": "quinn", "messages": 20000, "size": 16013, "read": 20000, "seconds": 0.635, "per_second": 31504, "client_cpu_us_per_message": 31.87, "service_cpu_us_per_message": 30.71}
diff --git a/research/experiments/019-idle-links/2026-09-29-transport-linux-aarch64.jsonl b/research/experiments/019-idle-links/2026-09-29-transport-linux-aarch64.jsonl
new file mode 100644
index 0000000..72f3b95
--- /dev/null
+++ b/research/experiments/019-idle-links/2026-09-29-transport-linux-aarch64.jsonl
@@ -0,0 +1,30 @@
+{"side": "pure", "item_bytes": 48, "records": 200000, "record_bytes": 61, "seconds": 0.036, "per_second": 5615011, "packets": 11453, "wrong": 0, "resent": 0, "link_us_per_record": 0.142, "listener_us_per_record": 0.15, "other_us_per_record": 0.011}
+{"side": "quinn", "item_bytes": 48, "messages": 200000, "size": 61, "read": 200000, "seconds": 0.026, "per_second": 7715099, "client_cpu_us_per_message": 0.14, "service_cpu_us_per_message": 0.13}
+{"side": "pure", "item_bytes": 48, "records": 200000, "record_bytes": 61, "seconds": 0.039, "per_second": 5093829, "packets": 11403, "wrong": 0, "resent": 0, "link_us_per_record": 0.145, "listener_us_per_record": 0.158, "other_us_per_record": 0.024}
+{"side": "quinn", "item_bytes": 48, "messages": 200000, "size": 61, "read": 200000, "seconds": 0.026, "per_second": 7729533, "client_cpu_us_per_message": 0.14, "service_cpu_us_per_message": 0.13}
+{"side": "pure", "item_bytes": 48, "records": 200000, "record_bytes": 61, "seconds": 0.054, "per_second": 3701858, "packets": 11398, "wrong": 0, "resent": 0, "link_us_per_record": 0.175, "listener_us_per_record": 0.185, "other_us_per_record": 0.035}
+{"side": "quinn", "item_bytes": 48, "messages": 200000, "size": 61, "read": 200000, "seconds": 0.058, "per_second": 3453915, "client_cpu_us_per_message": 0.17, "service_cpu_us_per_message": 0.15}
+{"side": "pure", "item_bytes": 48, "records": 200000, "record_bytes": 61, "seconds": 0.059, "per_second": 3367697, "packets": 11469, "wrong": 0, "resent": 0, "link_us_per_record": 0.226, "listener_us_per_record": 0.225, "other_us_per_record": 0.038}
+{"side": "quinn", "item_bytes": 48, "messages": 200000, "size": 61, "read": 200000, "seconds": 0.031, "per_second": 6458727, "client_cpu_us_per_message": 0.16, "service_cpu_us_per_message": 0.15}
+{"side": "pure", "item_bytes": 48, "records": 200000, "record_bytes": 61, "seconds": 0.037, "per_second": 5390888, "packets": 11413, "wrong": 0, "resent": 0, "link_us_per_record": 0.14, "listener_us_per_record": 0.148, "other_us_per_record": 0.022}
+{"side": "quinn", "item_bytes": 48, "messages": 200000, "size": 61, "read": 200000, "seconds": 0.026, "per_second": 7795676, "client_cpu_us_per_message": 0.14, "service_cpu_us_per_message": 0.13}
+{"side": "pure", "item_bytes": 1000, "records": 50000, "record_bytes": 1013, "seconds": 0.073, "per_second": 684187, "packets": 50000, "wrong": 0, "resent": 0, "link_us_per_record": 1.435, "listener_us_per_record": 1.437, "other_us_per_record": 0.094}
+{"side": "quinn", "item_bytes": 1000, "messages": 50000, "size": 1013, "read": 50000, "seconds": 0.096, "per_second": 521389, "client_cpu_us_per_message": 1.95, "service_cpu_us_per_message": 1.85}
+{"side": "pure", "item_bytes": 1000, "records": 50000, "record_bytes": 1013, "seconds": 0.074, "per_second": 674802, "packets": 50000, "wrong": 0, "resent": 0, "link_us_per_record": 1.452, "listener_us_per_record": 1.451, "other_us_per_record": 0.078}
+{"side": "quinn", "item_bytes": 1000, "messages": 50000, "size": 1013, "read": 50000, "seconds": 0.099, "per_second": 507322, "client_cpu_us_per_message": 2.01, "service_cpu_us_per_message": 1.94}
+{"side": "pure", "item_bytes": 1000, "records": 50000, "record_bytes": 1013, "seconds": 0.074, "per_second": 674399, "packets": 50000, "wrong": 0, "resent": 0, "link_us_per_record": 1.456, "listener_us_per_record": 1.457, "other_us_per_record": 0.11}
+{"side": "quinn", "item_bytes": 1000, "messages": 50000, "size": 1013, "read": 50000, "seconds": 0.096, "per_second": 519206, "client_cpu_us_per_message": 1.96, "service_cpu_us_per_message": 1.85}
+{"side": "pure", "item_bytes": 1000, "records": 50000, "record_bytes": 1013, "seconds": 0.077, "per_second": 651514, "packets": 50000, "wrong": 0, "resent": 0, "link_us_per_record": 1.505, "listener_us_per_record": 1.49, "other_us_per_record": 0.109}
+{"side": "quinn", "item_bytes": 1000, "messages": 50000, "size": 1013, "read": 50000, "seconds": 0.099, "per_second": 507608, "client_cpu_us_per_message": 2.01, "service_cpu_us_per_message": 1.92}
+{"side": "pure", "item_bytes": 1000, "records": 50000, "record_bytes": 1013, "seconds": 0.076, "per_second": 657794, "packets": 50000, "wrong": 0, "resent": 0, "link_us_per_record": 1.493, "listener_us_per_record": 1.493, "other_us_per_record": 0.129}
+{"side": "quinn", "item_bytes": 1000, "messages": 50000, "size": 1013, "read": 50000, "seconds": 0.098, "per_second": 511069, "client_cpu_us_per_message": 1.99, "service_cpu_us_per_message": 1.89}
+{"side": "pure", "item_bytes": 16000, "records": 5000, "record_bytes": 16013, "seconds": 0.118, "per_second": 42284, "packets": 80000, "wrong": 0, "resent": 0, "link_us_per_record": 23.345, "listener_us_per_record": 23.36, "other_us_per_record": 0.886}
+{"side": "quinn", "item_bytes": 16000, "messages": 5000, "size": 16013, "read": 5000, "seconds": 0.154, "per_second": 32537, "client_cpu_us_per_message": 31.08, "service_cpu_us_per_message": 29.32}
+{"side": "pure", "item_bytes": 16000, "records": 5000, "record_bytes": 16013, "seconds": 0.113, "per_second": 44238, "packets": 80000, "wrong": 0, "resent": 0, "link_us_per_record": 22.325, "listener_us_per_record": 22.354, "other_us_per_record": 1.221}
+{"side": "quinn", "item_bytes": 16000, "messages": 5000, "size": 16013, "read": 5000, "seconds": 0.159, "per_second": 31400, "client_cpu_us_per_message": 32.06, "service_cpu_us_per_message": 29.72}
+{"side": "pure", "item_bytes": 16000, "records": 5000, "record_bytes": 16013, "seconds": 0.115, "per_second": 43645, "packets": 80000, "wrong": 0, "resent": 0, "link_us_per_record": 22.617, "listener_us_per_record": 22.608, "other_us_per_record": 1.144}
+{"side": "quinn", "item_bytes": 16000, "messages": 5000, "size": 16013, "read": 5000, "seconds": 0.154, "per_second": 32407, "client_cpu_us_per_message": 31.07, "service_cpu_us_per_message": 29.11}
+{"side": "pure", "item_bytes": 16000, "records": 5000, "record_bytes": 16013, "seconds": 0.118, "per_second": 42553, "packets": 80000, "wrong": 0, "resent": 0, "link_us_per_record": 23.195, "listener_us_per_record": 23.227, "other_us_per_record": 1.129}
+{"side": "quinn", "item_bytes": 16000, "messages": 5000, "size": 16013, "read": 5000, "seconds": 0.16, "per_second": 31164, "client_cpu_us_per_message": 32.34, "service_cpu_us_per_message": 30.05}
+{"side": "pure", "item_bytes": 16000, "records": 5000, "record_bytes": 16013, "seconds": 0.114, "per_second": 43757, "packets": 80000, "wrong": 0, "resent": 0, "link_us_per_record": 22.538, "listener_us_per_record": 22.583, "other_us_per_record": 1.04}
+{"side": "quinn", "item_bytes": 16000, "messages": 5000, "size": 16013, "read": 5000, "seconds": 0.153, "per_second": 32692, "client_cpu_us_per_message": 30.93, "service_cpu_us_per_message": 29.93}
diff --git a/research/experiments/019-idle-links/quic/src/main.rs b/research/experiments/019-idle-links/quic/src/main.rs
index 28cda77..80e663f 100644
--- a/research/experiments/019-idle-links/quic/src/main.rs
+++ b/research/experiments/019-idle-links/quic/src/main.rs
@@ -116,24 +116,27 @@ fn serve(cert: Vec<u8>, key: Vec<u8>, port: u16) {
/// A drain's stream begins with these bytes; an order's never does (a client number that large).
const DRAIN: &[u8; 4] = b"pure";
-/// Messages each after its u32 length until a length of 0; then how many, as the answer.
+/// Messages each after its u32 length until a length of 0, read a chunk at
+/// a time as they come; then how many, as the answer.
async fn drain(send: &mut quinn::SendStream, recv: &mut quinn::RecvStream) {
- let mut count = 0u64;
- let mut buffer = Vec::new();
- loop {
- let mut length = [0u8; 4];
- if recv.read_exact(&mut length).await.is_err() {
- return;
- }
- let length = u32::from_le_bytes(length) as usize;
- if length == 0 {
- break;
- }
- buffer.resize(length, 0);
- if recv.read_exact(&mut buffer).await.is_err() {
- return;
+ let (mut count, mut pending, mut done) = (0u64, Vec::<u8>::new(), false);
+ while !done {
+ let Ok(Some(chunk)) = recv.read_chunk(1 << 20, true).await else { return };
+ pending.extend_from_slice(&chunk.bytes);
+ let mut at = 0;
+ while pending.len() - at >= 4 {
+ let length = u32::from_le_bytes(pending[at..at + 4].try_into().unwrap()) as usize;
+ if length == 0 {
+ done = true;
+ break;
+ }
+ if pending.len() - at - 4 < length {
+ break;
+ }
+ at += 4 + length;
+ count += 1;
}
- count += 1;
+ pending.drain(..at);
}
let _ = send.write_all(&count.to_le_bytes()).await;
let _ = send.finish();
@@ -356,12 +359,18 @@ fn drain_client(port: u16, count: u64, size: usize) {
endpoint.set_default_client_config(client_config());
let connection = endpoint.connect(SocketAddr::from(([127, 0, 0, 1], port)), "localhost").unwrap().await.unwrap();
let (mut send, mut recv) = connection.open_bi().await.unwrap();
- let message = vec![b'x'; size];
+ // The messages, each after its length, written 64 KiB at a time.
+ let mut frame = (size as u32).to_le_bytes().to_vec();
+ frame.extend(std::iter::repeat_n(b'x', size));
+ let per_chunk = ((64 << 10) / frame.len()).max(1);
+ let chunk: Vec<u8> = frame.iter().copied().cycle().take(frame.len() * per_chunk).collect();
let began = Instant::now();
send.write_all(DRAIN).await.unwrap();
- for _ in 0..count {
- send.write_all(&(size as u32).to_le_bytes()).await.unwrap();
- send.write_all(&message).await.unwrap();
+ let mut left = count as usize;
+ while left > 0 {
+ let n = left.min(per_chunk);
+ send.write_all(&chunk[..n * frame.len()]).await.unwrap();
+ left -= n;
}
send.write_all(&0u32.to_le_bytes()).await.unwrap();
send.finish().unwrap();
diff --git a/research/experiments/019-idle-links/run.py b/research/experiments/019-idle-links/run.py
index 63f88a2..96a0cbc 100644
--- a/research/experiments/019-idle-links/run.py
+++ b/research/experiments/019-idle-links/run.py
@@ -29,9 +29,17 @@ network namespace of its own (unshare -rn) whose loopback delays UDP:
senders the node never heard from, and new quinn clients, each send one
order and wait until it is stored, one at a time.
+With --transport SIZE:COUNT,... (M5), transport.c drains a backlog of
+COUNT records through a link and a listener in one process (no app, no
+store), then quinn sends as many messages of the same size over one
+stream; --rounds times each. With --drain SIZES (M5 as the design says),
+two pure-serve nodes: the sender's app saves --counts orders while the
+receiver is away, then the receiver starts and the link drains them;
+quinn does the same over one stream.
+
python3 research/experiments/019-idle-links/run.py [--counts 1000,10000]
[--store DIRECTORY] [--out FILE.jsonl] [--quic] [--restart [--no-memo]]
- [--first-contact 5,25,50]
+ [--first-contact 5,25,50] [--transport 48:200000 --rounds 5] [--drain 48,1000]
"""
import argparse
import json
@@ -384,6 +392,143 @@ with tempfile.TemporaryDirectory(prefix='pure-m4-') as d:
file.write(line + '\n')
+def thread_cpu(pid, name):
+ """CPU ms of the process's threads named name (all threads when None),
+ from the scheduler's nanoseconds (schedstat), not clock ticks."""
+ total = 0
+ for task in Path(f'/proc/{pid}/task').iterdir():
+ try:
+ if name and (task / 'comm').read_text().strip() != name:
+ continue
+ total += int((task / 'schedstat').read_text().split()[0])
+ except OSError:
+ pass
+ return total / 1e6
+
+
+def post_all(port, n, size, threads=8):
+ """n orders (item of size bytes) posted to the sender app, over threads connections."""
+ import http.client
+ import threading
+ body = json.dumps({'item': 'x' * size, 'count': 1}).encode()
+ left = [n]
+ lock = threading.Lock()
+
+ def work():
+ connection = http.client.HTTPConnection('127.0.0.1', port, timeout=60)
+ while True:
+ with lock:
+ if not left[0]:
+ break
+ left[0] -= 1
+ connection.request('POST', '/inputs/buy', body, {'Content-Type': 'application/json'})
+ response = connection.getresponse()
+ response.read()
+ assert response.status == 200, response.status
+ connection.close()
+ workers = [threading.Thread(target=work) for _ in range(threads)]
+ for w in workers:
+ w.start()
+ for w in workers:
+ w.join()
+
+
+def drain_pure(work, n, size):
+ """M5 for Pure: n saved sends of an order whose item is size bytes wait
+ in the sender's outbox (the receiver is not there); then the receiver
+ starts, and the link drains them. Rate from the first applied to the
+ last; CPU of both nodes, and of their link threads alone."""
+ sender_module, receiver_module = work / 'drain-sender.pmod', work / 'drain-receiver.pmod'
+ for module, text in ((sender_module, check.LINK_SENDER), (receiver_module, check.LINK_RECEIVER)):
+ source = module.with_suffix('.aup')
+ source.write_text(text)
+ check.compile_module(Path(os.environ.get('AUP', ROOT.parent / 'aup/build/aup')), source, module)
+ keys = {}
+ for name in ('a', 'b'):
+ node, info = start([ROOT / 'build/pure-serve', '--module', receiver_module, '--store', work / f'key-{name}',
+ '--port', free_port(socket.SOCK_STREAM), '--key', work / f'{name}.key'])
+ keys[name] = info['public_key']
+ node.terminate()
+ node.communicate(timeout=60)
+ (work / 'drain-senders').write_text(f'drain-sender.ordered {keys["a"]}\n')
+ link_port, port_a, port_b = free_port(socket.SOCK_DGRAM), free_port(socket.SOCK_STREAM), free_port(socket.SOCK_STREAM)
+ a, _ = start([ROOT / 'build/pure-serve', '--module', sender_module, '--store', work / 'drain-a', '--port', port_a,
+ '--threads', '2', '--key', work / 'a.key', '--send', f'ordered=pure://127.0.0.1:{link_port}/order',
+ '--seal', f'ordered={keys["b"]}'])
+ post_all(port_a, n, size)
+ assert view(port_a, 'sent') == n
+ b, _ = start([ROOT / 'build/pure-serve', '--module', receiver_module, '--store', work / 'drain-b', '--port', port_b,
+ '--threads', '2', '--key', work / 'b.key', '--link-port', link_port, '--senders', work / 'drain-senders'])
+ cpu = {name: (thread_cpu(pid, None), thread_cpu(pid, thread)) for name, pid, thread in
+ (('sender', a.pid, 'pure-link'), ('receiver', b.pid, 'pure-listen'))}
+ import http.client
+ connection = http.client.HTTPConnection('127.0.0.1', port_b, timeout=30)
+ first = None
+ while True: # the count every 2 ms, over one connection
+ connection.request('GET', '/views/count')
+ count = json.loads(connection.getresponse().read())
+ now = time.monotonic()
+ if count and first is None:
+ first = (now, count)
+ if count >= n:
+ break
+ time.sleep(0.002)
+ connection.close()
+ seconds = now - first[0]
+ row = {'messages': n, 'item_bytes': size, 'first_seen': first[1], 'seconds': round(seconds, 4),
+ 'per_second': round((n - first[1]) / seconds) if seconds else None}
+ for name, pid, thread in (('sender', a.pid, 'pure-link'), ('receiver', b.pid, 'pure-listen')):
+ total, link = thread_cpu(pid, None) - cpu[name][0], thread_cpu(pid, thread) - cpu[name][1]
+ row[f'{name}_cpu_us_per_message'] = round(total * 1000 / n, 2)
+ row[f'{name}_link_cpu_us_per_message'] = round(link * 1000 / n, 2)
+ for name, node in (('sender', a), ('receiver', b)):
+ node.terminate()
+ _, error = node.communicate(timeout=120)
+ stats = [json.loads(line) for line in error.decode().splitlines() if line.startswith('{"active"')][-1]
+ keys = ('link_packets', 'link_records', 'link_resent', 'link_lost') if name == 'sender' else \
+ ('listener_received', 'listener_deliveries', 'bursts')
+ row[name] = {k: stats[k] for k in keys}
+ subprocess.run(['rm', '-rf', str(work / 'drain-a'), str(work / 'drain-b')], check=True) # tmpfs: room for the next
+ return row
+
+
+def drain_quic(work, n, size):
+ """The same over quinn: one connection, one stream, n messages; CPU of
+ both sides. The service only counts them (no store)."""
+ binary = Path(__file__).parent / 'quic/target/release/idle-quic'
+ import resource
+ server, info = start([binary, 'serve', work / 'cert.der', work / 'key.der'])
+ before, children = thread_cpu(server.pid, None), resource.getrusage(resource.RUSAGE_CHILDREN)
+ # The same bytes as Pure's record: its prefix (count, item's length), the item and the city ("Seoul").
+ client = subprocess.run([str(binary), 'drain', str(info['port']), str(n), str(size + 8 + 5)], capture_output=True,
+ timeout=600)
+ after = resource.getrusage(resource.RUSAGE_CHILDREN)
+ row = json.loads(client.stdout)
+ server_cpu = thread_cpu(server.pid, None) - before
+ client_cpu = (after.ru_utime + after.ru_stime - children.ru_utime - children.ru_stime) * 1000
+ row['client_cpu_us_per_message'] = round(client_cpu * 1000 / n, 2)
+ server.kill()
+ server.wait()
+ row['service_cpu_us_per_message'] = round(server_cpu * 1000 / n, 2)
+ return row
+
+
+def transport(work, size, n):
+ """The transport alone (transport.c): a backlog of n records of an order
+ with a size-byte item drained through a link and a listener in one
+ process, no app and no store."""
+ modules = {}
+ for name, text in (('sender', check.LINK_SENDER), ('receiver', check.LINK_RECEIVER)):
+ source = work / f'{name}.aup'
+ source.write_text(text)
+ modules[name] = work / f'{name}.pmod'
+ check.compile_module(Path(os.environ.get('AUP', ROOT.parent / 'aup/build/aup')), source, modules[name])
+ done = subprocess.run([str(ROOT / 'build/link-transport'), str(modules['sender']), str(modules['receiver']), str(n),
+ str(size), '0'], capture_output=True, text=True, timeout=600)
+ assert done.returncode == 0, done.stderr
+ return json.loads(done.stdout)
+
+
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--counts', default='1000,10000,100000,1000000')
@@ -399,8 +544,54 @@ def main():
parser.add_argument('--kill-at', type=int, default=20)
parser.add_argument('--first-contact', type=str, default=None,
help='M4: one-way delays in ms (for example 5,25,50), each in a namespace of its own')
+ parser.add_argument('--drain', type=str, default=None,
+ help='M5: item sizes in bytes (for example 48,1000,16000); --counts gives the messages')
+ parser.add_argument('--transport', type=str, default=None,
+ help='M5: SIZE:COUNT pairs (for example 48:200000,1000:50000): the transport alone and quinn')
+ parser.add_argument('--rounds', type=int, default=1)
args = parser.parse_args()
counts = [int(c) for c in args.counts.split(',')]
+ if args.transport:
+ subprocess.run(['make', '-s', '-C', str(ROOT), 'build/libpure.a', 'build/pure-crypto.so'], check=True)
+ subprocess.run(['cc', '-O2', '-g', '-std=c17', '-Wall', '-Wextra', '-Werror', f'-I{ROOT}/core', f'-I{ROOT}/serve',
+ '-DPURE_ASYNC_EFFECTS', str(Path(__file__).parent / 'transport.c'), str(ROOT / 'serve/link.c'),
+ str(ROOT / 'serve/crypto.c'), str(ROOT / 'build/libpure.a'), '-lm', '-pthread', '-ldl', '-o',
+ str(ROOT / 'build/link-transport')], check=True)
+ subprocess.run(['cargo', 'build', '--release', '--offline', '--quiet'], cwd=Path(__file__).parent / 'quic', check=True)
+ with tempfile.TemporaryDirectory(prefix='pure-m5-', dir=args.store) as directory:
+ work = Path(directory)
+ make_certificate(work)
+ for pair in args.transport.split(','):
+ size, n = (int(x) for x in pair.split(':'))
+ for _ in range(args.rounds):
+ for name, measure in (('pure', lambda: transport(work, size, n)), ('quinn', lambda: drain_quic(work, n, size))):
+ row = {'side': name, 'item_bytes': size, **measure()}
+ line = json.dumps(row)
+ print(line, flush=True)
+ if args.out:
+ with open(args.out, 'a') as out:
+ out.write(line + '\n')
+ return
+ if args.drain:
+ subprocess.run(['cargo', 'build', '--release', '--offline', '--quiet'], cwd=Path(__file__).parent / 'quic', check=True)
+ with tempfile.TemporaryDirectory(prefix='pure-m5-', dir=args.store) as directory:
+ work = Path(directory)
+ make_certificate(work)
+ for size in (int(s) for s in args.drain.split(',')):
+ for round_, n in enumerate(counts):
+ for name, measure in (('pure', drain_pure), ('quinn', drain_quic)):
+ case = work / f'{name}-{size}-{n}-{round_}'
+ case.mkdir()
+ if name == 'quinn':
+ for f in ('cert.der', 'key.der'):
+ (case / f).write_bytes((work / f).read_bytes())
+ row = {'side': name, **measure(case, n, size)}
+ line = json.dumps(row)
+ print(line, flush=True)
+ if args.out:
+ with open(args.out, 'a') as out:
+ out.write(line + '\n')
+ return
if args.first_contact:
subprocess.run(['cargo', 'build', '--release', '--offline', '--quiet'], cwd=Path(__file__).parent / 'quic', check=True)
for delay in args.first_contact.split(','):