aboutsummaryrefslogtreecommitdiffstats
path: root/research/experiments
diff options
context:
space:
mode:
Diffstat (limited to 'research/experiments')
-rw-r--r--research/experiments/019-idle-links/2026-09-28-first-contact-linux-aarch64.jsonl3
-rw-r--r--research/experiments/019-idle-links/2026-09-28-restart-links-linux-aarch64.jsonl6
-rw-r--r--research/experiments/019-idle-links/2026-09-28-restart-links-sd-linux-aarch64.jsonl1
-rw-r--r--research/experiments/019-idle-links/2026-09-28-restart-quinn-linux-aarch64.jsonl3
-rw-r--r--research/experiments/019-idle-links/quic/src/main.rs71
-rw-r--r--research/experiments/019-idle-links/run.py142
6 files changed, 201 insertions, 25 deletions
diff --git a/research/experiments/019-idle-links/2026-09-28-first-contact-linux-aarch64.jsonl b/research/experiments/019-idle-links/2026-09-28-first-contact-linux-aarch64.jsonl
new file mode 100644
index 0000000..a404b1a
--- /dev/null
+++ b/research/experiments/019-idle-links/2026-09-28-first-contact-linux-aarch64.jsonl
@@ -0,0 +1,3 @@
+{"delay_ms_each_way": 5, "senders": 100, "pure_ms_p50": 10.585, "pure_ms_p99": 12.202, "pure_round_trips": 1.06, "quinn_ms_p50": 20.991, "quinn_ms_p99": 21.499, "quinn_round_trips": 2.1}
+{"delay_ms_each_way": 25, "senders": 100, "pure_ms_p50": 50.932, "pure_ms_p99": 52.325, "pure_round_trips": 1.02, "quinn_ms_p50": 101.153, "quinn_ms_p99": 101.799, "quinn_round_trips": 2.02}
+{"delay_ms_each_way": 50, "senders": 100, "pure_ms_p50": 100.735, "pure_ms_p99": 102.419, "pure_round_trips": 1.01, "quinn_ms_p50": 201.192, "quinn_ms_p99": 202.022, "quinn_round_trips": 2.01}
diff --git a/research/experiments/019-idle-links/2026-09-28-restart-links-linux-aarch64.jsonl b/research/experiments/019-idle-links/2026-09-28-restart-links-linux-aarch64.jsonl
new file mode 100644
index 0000000..de7c076
--- /dev/null
+++ b/research/experiments/019-idle-links/2026-09-28-restart-links-linux-aarch64.jsonl
@@ -0,0 +1,6 @@
+{"devices": 10000, "period_ms": 5000, "memo": true, "restart_ms": 13.7, "steady_cpu_ms_per_s": 130.0, "senders_steady_cpu_ms_per_s": 90.0, "recovery_cpu_ms_first_10s": 1040.0, "senders_cpu_ms_first_10s": 980.0, "generator": {"devices": 10000, "made": 80003, "stored": 80003, "sends": 80037, "again": 34, "stored_ms_p50": 0.535, "stored_ms_p99": 4.147, "stored_ms_max": 203.874, "longest_gap_ms": 0, "gap_from_ms": 0}, "timeline_100ms": [183, 207, 191, 208, 191, 212, 193, 193, 211, 193, 194, 212, 195, 197, 195, 214, 196, 196, 196, 214, 197, 198, 198, 199, 197, 198, 198, 200, 200, 200, 199, 216, 200, 201, 200, 200, 183, 199, 218, 182, 201, 202, 200, 200, 200, 202, 203, 202, 201, 201, 213, 200, 198, 200, 199, 200, 200, 200, 199, 199, 200, 200, 200, 200, 199, 201, 201, 201, 201, 201, 200, 199, 201, 191, 200, 200, 199, 202, 201, 201, 201, 202, 202, 194, 208, 191, 201, 203, 202, 201, 200, 201, 191, 204, 202, 204, 191, 203, 201, 202, 199, 189, 213, 202, 197, 199, 203, 193, 200, 200, 200, 202, 202, 201, 200, 202, 191, 201, 201, 199, 200, 201, 202, 203, 201, 196, 205, 195, 205, 194, 206, 196, 205, 196, 193, 206, 195, 205, 205, 194, 205, 194, 205, 195, 206, 194, 200, 207, 196, 196, 203, 203, 191, 208, 200, 201, 197, 205, 189, 202, 202, 203, 204, 193, 203, 203, 190, 199, 206, 205, 191, 201, 203, 200, 201, 202, 202, 190, 204, 201, 201, 203, 199, 194, 202, 202, 202, 193, 203, 204, 201, 201, 191, 202, 205, 194, 202, 205, 192, 205, 200, 200, 200, 203, 197, 202, 203, 190, 199, 201, 165, 203, 238, 203, 193, 205, 203, 193, 196, 201, 208, 199, 197, 199, 198, 198, 200, 200, 211, 189, 200, 200, 201, 205, 200, 200, 196, 207, 194, 207, 194, 199, 205, 196, 196, 206, 195, 209, 196, 198, 204, 203, 192, 203, 202, 201, 192, 204, 203, 193, 202, 200, 200, 198, 210, 199, 200, 200, 199, 201, 198, 199, 201, 200, 199, 199, 201, 201, 198, 202, 199, 201, 200, 198, 200, 203, 194, 205, 203, 197, 193, 206, 197, 204, 194, 206, 197, 195, 205, 197, 202, 201, 200, 201, 203, 203, 190, 206, 201, 192, 209, 200, 199, 192, 201, 200, 199, 207, 201, 201, 196, 201, 203, 196, 203, 200, 198, 200, 200, 202, 197, 200, 202, 202, 201, 191, 200, 199, 203, 203, 201, 197, 205, 190, 208, 202, 201, 191, 202, 203, 198, 198, 198, 210, 198, 198, 197, 199, 199, 205, 202, 202, 192, 201, 202, 202, 202, 192, 204, 196, 203, 203, 194, 203, 202, 199, 196, 204, 203, 193, 203, 202, 203, 193, 204, 201, 197, 204, 193, 206, 195, 198, 204, 202, 199, 204, 194, 203, 201, 196, 11], "killed_ms": 21101, "recovery": {"steady_per_100ms": 199.2, "silent_100ms_steps": 0, "caught_up_ms": 200}, "applied": 80003, "listener": {"listener_packets": 75720, "listener_received": 37860, "listener_deliveries": 37860, "listener_woken": 10000, "listener_remembered": 10000, "listener_swept": 0, "listener_active": 10000}}
+{"devices": 10000, "period_ms": 5000, "memo": true, "restart_ms": 18.9, "steady_cpu_ms_per_s": 130.0, "senders_steady_cpu_ms_per_s": 90.0, "recovery_cpu_ms_first_10s": 1250.0, "senders_cpu_ms_first_10s": 1090.0, "generator": {"devices": 10000, "made": 80003, "stored": 80003, "sends": 80037, "again": 34, "stored_ms_p50": 0.572, "stored_ms_p99": 4.115, "stored_ms_max": 205.52, "longest_gap_ms": 0, "gap_from_ms": 0}, "timeline_100ms": [183, 208, 190, 212, 192, 191, 208, 208, 192, 191, 210, 208, 192, 192, 212, 193, 210, 193, 194, 214, 196, 196, 197, 200, 199, 210, 198, 198, 200, 196, 197, 196, 214, 195, 200, 199, 198, 196, 198, 214, 198, 197, 198, 198, 198, 198, 215, 199, 198, 198, 205, 199, 199, 209, 199, 201, 195, 200, 199, 199, 201, 200, 200, 201, 197, 203, 203, 194, 199, 204, 199, 196, 202, 200, 203, 200, 202, 200, 199, 200, 204, 200, 200, 200, 193, 201, 201, 202, 202, 191, 201, 200, 200, 202, 202, 202, 202, 200, 201, 200, 200, 196, 198, 199, 199, 198, 209, 198, 198, 200, 200, 199, 201, 201, 201, 198, 196, 207, 198, 200, 199, 204, 192, 204, 195, 202, 204, 203, 198, 198, 203, 202, 191, 202, 203, 200, 200, 202, 200, 202, 200, 200, 199, 191, 203, 199, 202, 202, 203, 193, 199, 208, 200, 199, 199, 199, 198, 198, 199, 199, 211, 198, 199, 197, 205, 197, 198, 199, 198, 200, 202, 200, 200, 200, 203, 200, 201, 199, 200, 201, 196, 205, 195, 205, 195, 204, 198, 196, 206, 196, 204, 195, 205, 194, 207, 194, 205, 196, 206, 195, 203, 201, 201, 191, 206, 201, 202, 192, 201, 203, 157, 210, 234, 199, 200, 199, 199, 200, 200, 202, 199, 199, 203, 200, 191, 201, 202, 201, 201, 202, 202, 195, 199, 204, 193, 203, 204, 204, 191, 209, 191, 204, 204, 190, 206, 205, 193, 205, 193, 206, 198, 197, 198, 209, 199, 198, 197, 197, 210, 199, 198, 203, 194, 205, 194, 204, 193, 205, 205, 195, 197, 205, 195, 205, 195, 206, 194, 195, 211, 193, 207, 193, 204, 202, 195, 203, 203, 194, 196, 204, 206, 195, 204, 202, 194, 204, 203, 193, 205, 193, 200, 200, 200, 200, 202, 202, 201, 201, 201, 201, 201, 201, 190, 201, 205, 198, 202, 201, 200, 202, 191, 202, 200, 205, 193, 202, 203, 203, 192, 207, 196, 205, 196, 198, 200, 207, 195, 197, 207, 196, 196, 208, 197, 197, 198, 209, 197, 195, 209, 198, 194, 201, 204, 196, 202, 204, 194, 204, 202, 193, 204, 203, 195, 205, 195, 205, 194, 205, 193, 207, 196, 204, 194, 205, 195, 207, 194, 206, 195, 195, 209, 194, 207, 195, 197, 208, 195, 195, 206, 196, 209, 196, 197, 196, 207, 196, 206, 194, 206, 195, 11], "killed_ms": 21102, "recovery": {"steady_per_100ms": 199.0, "silent_100ms_steps": 0, "caught_up_ms": 200}, "applied": 80003, "listener": {"listener_packets": 75724, "listener_received": 37862, "listener_deliveries": 37862, "listener_woken": 10000, "listener_remembered": 10000, "listener_swept": 0, "listener_active": 10000}}
+{"devices": 10000, "period_ms": 5000, "memo": true, "restart_ms": 13.4, "steady_cpu_ms_per_s": 110.0, "senders_steady_cpu_ms_per_s": 80.0, "recovery_cpu_ms_first_10s": 1080.0, "senders_cpu_ms_first_10s": 990.0, "generator": {"devices": 10000, "made": 80003, "stored": 80003, "sends": 80025, "again": 22, "stored_ms_p50": 0.542, "stored_ms_p99": 4.259, "stored_ms_max": 206.349, "longest_gap_ms": 0, "gap_from_ms": 0}, "timeline_100ms": [184, 207, 190, 208, 190, 207, 191, 210, 205, 186, 199, 210, 193, 211, 191, 211, 192, 192, 210, 192, 200, 212, 195, 195, 192, 207, 201, 197, 198, 215, 200, 197, 199, 197, 198, 199, 199, 199, 199, 198, 201, 199, 201, 203, 202, 201, 204, 201, 201, 202, 201, 198, 200, 199, 210, 192, 199, 201, 201, 202, 195, 210, 192, 198, 201, 202, 200, 200, 201, 201, 202, 201, 202, 191, 201, 203, 202, 194, 204, 195, 206, 194, 206, 196, 206, 196, 205, 195, 206, 194, 196, 207, 194, 208, 195, 197, 206, 200, 195, 203, 199, 200, 200, 200, 199, 198, 199, 201, 201, 201, 202, 199, 200, 201, 200, 199, 199, 203, 201, 202, 191, 204, 201, 202, 201, 201, 200, 198, 201, 201, 191, 206, 201, 202, 195, 201, 201, 200, 202, 201, 191, 201, 203, 201, 202, 203, 194, 199, 204, 195, 202, 200, 199, 202, 203, 202, 191, 201, 202, 202, 204, 191, 204, 203, 202, 191, 203, 202, 204, 193, 205, 191, 206, 194, 205, 203, 200, 194, 204, 195, 198, 201, 209, 202, 187, 210, 193, 201, 201, 200, 202, 203, 203, 199, 192, 202, 203, 202, 202, 191, 201, 208, 199, 198, 199, 198, 198, 200, 199, 199, 181, 204, 217, 205, 195, 200, 203, 203, 195, 205, 194, 197, 207, 195, 206, 195, 206, 194, 207, 194, 207, 198, 201, 190, 201, 203, 203, 202, 190, 203, 203, 202, 192, 204, 198, 206, 200, 197, 200, 198, 205, 204, 194, 204, 193, 205, 193, 205, 206, 198, 194, 201, 201, 201, 201, 200, 201, 201, 202, 189, 200, 205, 201, 202, 201, 201, 192, 200, 202, 202, 205, 191, 202, 201, 202, 202, 193, 202, 203, 202, 193, 201, 202, 202, 193, 207, 192, 206, 199, 199, 201, 198, 199, 209, 190, 206, 203, 199, 201, 200, 201, 200, 200, 200, 192, 200, 201, 200, 202, 202, 202, 200, 199, 201, 200, 199, 201, 200, 201, 201, 191, 202, 201, 199, 197, 202, 204, 201, 203, 191, 202, 202, 201, 201, 193, 208, 196, 204, 195, 196, 205, 201, 204, 191, 203, 201, 204, 202, 192, 203, 205, 192, 202, 202, 204, 191, 202, 200, 205, 193, 207, 190, 206, 196, 201, 201, 209, 199, 200, 198, 202, 190, 200, 202, 204, 201, 201, 200, 201, 193, 202, 202, 204, 191, 200, 202, 202, 202, 192, 203, 12], "killed_ms": 21101, "recovery": {"steady_per_100ms": 199.4, "silent_100ms_steps": 0, "caught_up_ms": 200}, "applied": 80003, "listener": {"listener_packets": 75714, "listener_received": 37857, "listener_deliveries": 37857, "listener_woken": 10000, "listener_remembered": 10000, "listener_swept": 0, "listener_active": 10000}}
+{"devices": 10000, "period_ms": 5000, "memo": false, "restart_ms": 19.8, "steady_cpu_ms_per_s": 140.0, "senders_steady_cpu_ms_per_s": 100.0, "recovery_cpu_ms_first_10s": 2730.0, "senders_cpu_ms_first_10s": 860.0, "generator": {"devices": 10000, "made": 80002, "stored": 80002, "sends": 80036, "again": 34, "stored_ms_p50": 0.574, "stored_ms_p99": 4.641, "stored_ms_max": 221.053, "longest_gap_ms": 0, "gap_from_ms": 0}, "timeline_100ms": [186, 194, 196, 214, 195, 195, 212, 196, 196, 197, 192, 203, 215, 196, 197, 196, 196, 213, 196, 198, 197, 199, 197, 200, 216, 197, 200, 198, 200, 199, 199, 203, 199, 202, 186, 203, 203, 201, 201, 200, 201, 203, 201, 200, 202, 184, 207, 205, 202, 201, 201, 200, 206, 197, 201, 201, 201, 199, 199, 200, 201, 201, 200, 201, 201, 201, 200, 199, 201, 199, 201, 191, 209, 192, 199, 202, 203, 194, 204, 204, 199, 193, 202, 203, 203, 191, 202, 201, 200, 199, 201, 202, 200, 204, 200, 192, 203, 206, 195, 206, 190, 202, 201, 203, 203, 190, 202, 202, 203, 202, 190, 206, 201, 203, 202, 191, 203, 205, 197, 194, 202, 205, 203, 193, 203, 204, 193, 204, 192, 208, 192, 207, 204, 196, 197, 206, 194, 205, 194, 205, 193, 209, 195, 196, 206, 196, 206, 195, 206, 194, 201, 202, 201, 203, 192, 201, 202, 201, 202, 194, 202, 203, 201, 192, 203, 203, 204, 191, 203, 204, 192, 205, 204, 195, 204, 196, 204, 194, 204, 194, 205, 195, 206, 195, 206, 194, 207, 195, 203, 196, 205, 196, 205, 197, 195, 206, 195, 207, 195, 207, 192, 202, 202, 201, 193, 203, 202, 202, 203, 191, 157, 205, 175, 260, 194, 211, 193, 194, 211, 197, 193, 196, 212, 195, 197, 212, 196, 194, 195, 215, 195, 197, 200, 196, 211, 186, 214, 181, 210, 200, 197, 198, 215, 182, 202, 202, 200, 198, 216, 199, 193, 192, 210, 192, 211, 192, 211, 192, 193, 211, 209, 202, 199, 190, 205, 203, 193, 204, 201, 197, 201, 204, 200, 200, 194, 201, 202, 201, 200, 197, 202, 203, 201, 201, 194, 201, 203, 192, 203, 203, 201, 203, 201, 192, 205, 201, 202, 200, 190, 202, 198, 200, 210, 198, 202, 199, 201, 198, 200, 195, 202, 196, 203, 200, 203, 200, 200, 198, 200, 202, 201, 197, 195, 204, 197, 208, 196, 204, 194, 196, 205, 205, 194, 206, 196, 197, 202, 205, 194, 206, 195, 206, 194, 205, 196, 197, 206, 194, 206, 195, 201, 202, 202, 201, 194, 200, 203, 202, 201, 191, 202, 203, 202, 202, 193, 203, 204, 192, 205, 204, 195, 202, 193, 204, 196, 204, 205, 193, 204, 195, 203, 198, 202, 206, 194, 206, 195, 196, 207, 194, 206, 195, 205, 195, 196, 206, 196, 203, 194, 204, 11], "killed_ms": 21102, "recovery": {"steady_per_100ms": 199.1, "silent_100ms_steps": 0, "caught_up_ms": 300}, "applied": 80002, "listener": {"listener_packets": 75710, "listener_received": 37855, "listener_deliveries": 37855, "listener_woken": 10000, "listener_remembered": 0, "listener_swept": 0, "listener_active": 10000}}
+{"devices": 10000, "period_ms": 5000, "memo": false, "restart_ms": 14.6, "steady_cpu_ms_per_s": 150.0, "senders_steady_cpu_ms_per_s": 100.0, "recovery_cpu_ms_first_10s": 2590.0, "senders_cpu_ms_first_10s": 830.0, "generator": {"devices": 10000, "made": 80011, "stored": 80011, "sends": 80045, "again": 34, "stored_ms_p50": 0.545, "stored_ms_p99": 4.54, "stored_ms_max": 222.224, "longest_gap_ms": 0, "gap_from_ms": 0}, "timeline_100ms": [184, 209, 191, 209, 197, 191, 211, 191, 210, 194, 195, 201, 193, 212, 196, 194, 213, 195, 196, 198, 196, 215, 189, 194, 207, 199, 199, 198, 199, 202, 200, 200, 199, 198, 199, 202, 201, 200, 200, 202, 200, 198, 205, 199, 199, 200, 199, 201, 200, 202, 214, 188, 206, 209, 199, 200, 195, 200, 202, 201, 199, 200, 201, 201, 200, 201, 198, 199, 200, 201, 200, 202, 192, 201, 203, 204, 193, 203, 204, 193, 207, 196, 205, 194, 204, 194, 207, 193, 205, 196, 206, 194, 206, 194, 207, 193, 206, 193, 206, 197, 204, 193, 201, 208, 189, 208, 200, 198, 201, 201, 199, 201, 192, 211, 198, 199, 200, 200, 199, 203, 200, 190, 201, 201, 201, 200, 205, 201, 201, 192, 201, 204, 193, 204, 202, 200, 200, 200, 201, 201, 201, 191, 200, 201, 201, 201, 202, 201, 202, 202, 199, 201, 192, 203, 202, 200, 194, 201, 204, 204, 191, 202, 204, 192, 203, 204, 204, 193, 205, 197, 204, 193, 207, 192, 205, 192, 208, 203, 192, 206, 192, 205, 204, 194, 206, 192, 207, 193, 204, 206, 194, 205, 196, 196, 206, 195, 208, 195, 196, 209, 187, 197, 207, 203, 205, 201, 190, 210, 190, 200, 156, 210, 231, 195, 210, 186, 202, 204, 193, 211, 204, 196, 196, 195, 210, 193, 211, 197, 195, 196, 202, 195, 212, 194, 195, 212, 195, 194, 214, 195, 197, 197, 195, 215, 196, 197, 205, 196, 197, 199, 209, 173, 212, 216, 189, 207, 191, 207, 192, 210, 208, 199, 201, 199, 197, 199, 199, 199, 201, 201, 203, 202, 199, 200, 198, 199, 201, 199, 200, 202, 202, 201, 200, 199, 199, 199, 200, 200, 200, 201, 195, 202, 203, 194, 206, 201, 203, 190, 201, 201, 202, 198, 199, 199, 203, 201, 203, 200, 193, 209, 195, 205, 191, 204, 201, 203, 193, 202, 203, 194, 204, 204, 202, 190, 202, 203, 203, 202, 199, 195, 196, 206, 204, 190, 201, 200, 202, 201, 200, 203, 203, 191, 201, 202, 203, 201, 193, 200, 201, 205, 200, 200, 198, 198, 198, 199, 200, 210, 199, 199, 201, 199, 199, 199, 199, 198, 199, 199, 209, 193, 202, 200, 200, 200, 200, 199, 201, 200, 198, 202, 202, 193, 204, 205, 193, 204, 194, 204, 204, 195, 200, 205, 194, 204, 195, 204, 193, 203, 206, 203, 12], "killed_ms": 21002, "recovery": {"steady_per_100ms": 199.8, "silent_100ms_steps": 0, "caught_up_ms": 300}, "applied": 80011, "listener": {"listener_packets": 75854, "listener_received": 37927, "listener_deliveries": 37927, "listener_woken": 10000, "listener_remembered": 0, "listener_swept": 0, "listener_active": 10000}}
+{"devices": 10000, "period_ms": 5000, "memo": false, "restart_ms": 13.6, "steady_cpu_ms_per_s": 110.0, "senders_steady_cpu_ms_per_s": 80.0, "recovery_cpu_ms_first_10s": 2740.0, "senders_cpu_ms_first_10s": 860.0, "generator": {"devices": 10000, "made": 80005, "stored": 80005, "sends": 80027, "again": 22, "stored_ms_p50": 0.57, "stored_ms_p99": 4.765, "stored_ms_max": 212.433, "longest_gap_ms": 0, "gap_from_ms": 0}, "timeline_100ms": [176, 203, 200, 203, 202, 201, 204, 190, 201, 197, 198, 215, 191, 205, 197, 200, 197, 207, 201, 196, 196, 197, 204, 204, 203, 197, 199, 184, 213, 202, 199, 200, 200, 202, 203, 194, 201, 201, 200, 200, 202, 200, 204, 185, 201, 205, 204, 202, 200, 200, 212, 200, 199, 197, 198, 206, 200, 197, 198, 197, 208, 196, 200, 201, 198, 201, 200, 199, 199, 202, 200, 199, 203, 193, 201, 208, 193, 205, 192, 205, 205, 192, 206, 196, 204, 199, 194, 205, 198, 201, 204, 193, 205, 196, 207, 200, 196, 194, 206, 196, 199, 200, 201, 203, 202, 204, 192, 203, 202, 202, 191, 201, 202, 204, 202, 200, 192, 201, 203, 201, 204, 191, 203, 206, 193, 203, 193, 205, 203, 194, 203, 205, 195, 205, 196, 195, 206, 193, 205, 205, 194, 205, 195, 203, 200, 194, 205, 194, 206, 195, 202, 201, 202, 203, 202, 189, 204, 201, 202, 203, 196, 196, 203, 205, 191, 201, 203, 202, 201, 196, 202, 203, 199, 195, 199, 199, 202, 200, 200, 203, 202, 200, 200, 202, 192, 200, 202, 200, 203, 194, 200, 203, 199, 201, 205, 201, 201, 191, 203, 201, 197, 198, 209, 198, 201, 199, 199, 199, 199, 199, 170, 209, 209, 205, 200, 194, 210, 193, 211, 193, 193, 210, 198, 189, 214, 195, 196, 196, 212, 195, 195, 202, 194, 214, 197, 196, 197, 203, 207, 197, 196, 197, 198, 215, 199, 196, 198, 198, 203, 203, 197, 192, 210, 192, 209, 192, 210, 192, 194, 209, 205, 200, 200, 199, 201, 201, 197, 202, 201, 204, 201, 199, 200, 201, 193, 204, 205, 193, 195, 202, 209, 195, 205, 194, 203, 200, 196, 207, 194, 206, 194, 206, 195, 206, 195, 194, 208, 194, 198, 206, 202, 203, 189, 201, 202, 201, 203, 204, 194, 205, 192, 204, 204, 192, 206, 203, 193, 205, 195, 205, 192, 204, 205, 193, 206, 193, 205, 195, 207, 195, 205, 194, 206, 195, 206, 194, 206, 197, 196, 198, 208, 194, 206, 192, 201, 200, 199, 207, 193, 205, 201, 197, 207, 196, 196, 200, 198, 210, 200, 200, 199, 198, 199, 198, 198, 200, 200, 200, 201, 201, 199, 200, 201, 197, 202, 204, 202, 200, 192, 202, 203, 201, 202, 196, 201, 204, 191, 204, 204, 200, 190, 202, 201, 201, 196, 209, 196, 195, 209, 197, 11], "killed_ms": 21101, "recovery": {"steady_per_100ms": 199.4, "silent_100ms_steps": 0, "caught_up_ms": 200}, "applied": 80005, "listener": {"listener_packets": 75742, "listener_received": 37871, "listener_deliveries": 37871, "listener_woken": 10000, "listener_remembered": 0, "listener_swept": 0, "listener_active": 10000}}
diff --git a/research/experiments/019-idle-links/2026-09-28-restart-links-sd-linux-aarch64.jsonl b/research/experiments/019-idle-links/2026-09-28-restart-links-sd-linux-aarch64.jsonl
new file mode 100644
index 0000000..7cbf031
--- /dev/null
+++ b/research/experiments/019-idle-links/2026-09-28-restart-links-sd-linux-aarch64.jsonl
@@ -0,0 +1 @@
+{"devices": 10000, "period_ms": 5000, "memo": true, "restart_ms": 28.6, "steady_cpu_ms_per_s": 100.0, "senders_steady_cpu_ms_per_s": 70.0, "recovery_cpu_ms_first_10s": 1060.0, "senders_cpu_ms_first_10s": 940.0, "generator": {"devices": 10000, "made": 80007, "stored": 80007, "sends": 80276, "again": 269, "stored_ms_p50": 4.092, "stored_ms_p99": 110.889, "stored_ms_max": 267.41, "longest_gap_ms": 200, "gap_from_ms": 17700}, "timeline_100ms": [186, 197, 198, 206, 192, 190, 221, 181, 167, 239, 211, 202, 161, 136, 296, 203, 200, 187, 203, 201, 200, 194, 214, 205, 193, 128, 240, 238, 196, 197, 199, 197, 205, 195, 206, 198, 209, 193, 195, 207, 197, 196, 203, 198, 207, 163, 120, 306, 203, 157, 249, 198, 200, 212, 194, 187, 187, 213, 212, 162, 221, 212, 196, 214, 192, 202, 200, 194, 190, 222, 149, 249, 197, 199, 190, 214, 202, 195, 200, 36, 356, 187, 223, 161, 231, 196, 202, 202, 208, 194, 207, 197, 195, 199, 189, 216, 155, 252, 191, 197, 192, 207, 203, 196, 212, 196, 188, 208, 144, 246, 216, 195, 190, 207, 211, 191, 200, 208, 195, 206, 176, 213, 204, 202, 195, 190, 186, 201, 228, 192, 202, 203, 201, 206, 180, 215, 195, 193, 208, 204, 201, 192, 191, 176, 234, 214, 197, 196, 191, 173, 234, 180, 224, 192, 200, 213, 189, 205, 186, 212, 193, 213, 194, 199, 203, 191, 199, 185, 171, 240, 196, 215, 134, 264, 199, 176, 210, 0, 0, 619, 194, 200, 200, 113, 269, 217, 95, 298, 201, 216, 191, 203, 164, 236, 204, 200, 200, 194, 201, 199, 202, 201, 202, 197, 200, 200, 206, 163, 136, 301, 135, 180, 188, 291, 158, 232, 211, 205, 200, 190, 205, 193, 210, 202, 201, 199, 166, 229, 200, 201, 197, 207, 195, 196, 170, 235, 138, 255, 202, 186, 222, 201, 176, 16, 398, 206, 208, 163, 224, 198, 201, 205, 131, 261, 198, 198, 218, 192, 151, 235, 201, 210, 100, 308, 198, 192, 214, 189, 197, 185, 217, 206, 191, 214, 188, 184, 215, 211, 188, 68, 0, 541, 190, 214, 193, 123, 154, 299, 231, 152, 44, 0, 591, 216, 187, 192, 208, 203, 208, 202, 173, 218, 205, 198, 200, 132, 269, 194, 204, 198, 156, 249, 190, 197, 186, 209, 212, 204, 203, 198, 192, 206, 195, 207, 186, 207, 205, 199, 206, 202, 192, 194, 169, 141, 293, 199, 198, 199, 207, 200, 203, 187, 204, 212, 201, 191, 202, 198, 198, 196, 205, 199, 202, 199, 196, 210, 185, 213, 198, 194, 200, 210, 204, 194, 173, 230, 195, 193, 207, 201, 84, 325, 194, 196, 175, 224, 201, 185, 213, 170, 232, 192, 178, 231, 152, 243, 189, 176, 242, 184, 203, 210, 205, 201, 181, 218, 193, 210, 195, 102, 117], "killed_ms": 21102, "recovery": {"steady_per_100ms": 198.9, "silent_100ms_steps": 2, "caught_up_ms": 300}, "applied": 80007, "listener": {"listener_packets": 75909, "listener_received": 38028, "listener_deliveries": 37881, "listener_woken": 10000, "listener_remembered": 10000, "listener_swept": 0, "listener_active": 10000}}
diff --git a/research/experiments/019-idle-links/2026-09-28-restart-quinn-linux-aarch64.jsonl b/research/experiments/019-idle-links/2026-09-28-restart-quinn-linux-aarch64.jsonl
new file mode 100644
index 0000000..839d380
--- /dev/null
+++ b/research/experiments/019-idle-links/2026-09-28-restart-quinn-linux-aarch64.jsonl
@@ -0,0 +1,3 @@
+{"clients": 10000, "period_ms": 5000, "restart_ms": 1.6, "killed_at_s": 26.0, "steady_cpu_ms_per_s": 120.0, "senders_steady_cpu_ms_per_s": 120.0, "recovery_cpu_ms_first_10s": 8290.0, "senders_cpu_ms_first_10s": 9140.0, "client": {"clients": 10000, "made": 89998, "stored": 89998, "again": 10173, "stored_ms_p50": 0.148, "stored_ms_p99": 5826.357, "stored_ms_max": 11517.095, "longest_gap_ms": 900, "gap_from_ms": 26100}, "timeline_100ms": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 95, 89, 73, 196, 347, 236, 222, 201, 206, 156, 138, 138, 137, 210, 293, 248, 166, 183, 200, 198, 192, 124, 128, 135, 191, 291, 230, 218, 208, 108, 131, 157, 110, 161, 239, 292, 184, 160, 208, 201, 198, 180, 131, 97, 95, 121, 190, 157, 148, 156, 291, 281, 220, 179, 234, 224, 379, 465, 272, 215, 202, 206, 221, 344, 247, 293, 223, 209, 200, 200, 198, 202, 198, 200, 202, 201, 219, 200, 200, 201, 199, 200, 200, 200, 200, 200, 200, 200, 200, 198, 202, 198, 202, 200, 200, 200, 201, 197, 202, 200, 200, 200, 200, 200, 200, 202, 198, 200, 200, 200, 200, 200, 200, 200, 200, 200, 198, 202, 200, 200, 200, 200, 200, 198, 202, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 198, 203, 199, 200, 200, 200, 200, 200, 200, 200, 200, 198, 202, 200, 200, 201, 199, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 198, 203, 199, 200, 200, 200, 200, 200, 198, 202, 200, 201, 199, 200, 200, 200, 200, 202, 198, 202, 198, 200, 200, 200, 200, 200, 200, 200, 198, 204, 198, 200, 200, 200, 200, 200, 200, 200, 200, 198, 202, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 198, 202, 200, 200, 201, 199, 200, 200, 200, 200, 200, 200, 198, 200, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 38, 10, 78, 93, 166, 136, 137, 126, 128, 128, 116, 65, 54, 66, 100, 126, 116, 105, 122, 109, 108, 69, 48, 95, 95, 92, 101, 114, 102, 109, 101, 101, 78, 71, 54, 132, 101, 112, 102, 98, 284, 223, 111, 94, 220, 326, 387, 348, 335, 380, 336, 322, 195, 114, 188, 241, 296, 306, 409, 342, 346, 299, 273, 163, 189, 237, 321, 324, 266, 370, 351, 334, 287, 151, 180, 233, 298, 288, 316, 339, 354, 331, 307, 202, 86, 154, 195, 211, 279, 309, 483, 465, 462, 435, 243, 191, 173, 268, 324, 485, 631, 310, 264, 203, 221, 379, 301, 277, 276, 229, 216, 208, 202, 200, 200, 200, 198, 202, 200, 200, 200, 200, 200, 200, 200, 200, 198, 202, 200, 200, 200, 200, 200, 200, 200, 200, 200, 202, 198, 200, 200, 200, 200, 200, 198, 202, 200, 200, 200, 200, 200, 200, 200, 200, 200, 198, 202, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 199, 201, 200, 200, 200, 194, 206, 199, 201, 200, 200, 200, 200, 200, 200, 200, 198, 202, 200, 200, 200, 200, 199, 199, 202, 198, 202, 200, 200, 200, 200, 200, 200], "killed_ms": 26010, "recovery": {"steady_per_100ms": 200.0, "silent_100ms_steps": 9, "caught_up_ms": 11700}}
+{"clients": 10000, "period_ms": 5000, "restart_ms": 1.3, "killed_at_s": 26.0, "steady_cpu_ms_per_s": 90.0, "senders_steady_cpu_ms_per_s": 90.0, "recovery_cpu_ms_first_10s": 8300.0, "senders_cpu_ms_first_10s": 9120.0, "client": {"clients": 10000, "made": 89998, "stored": 89998, "again": 10069, "stored_ms_p50": 0.136, "stored_ms_p99": 5348.757, "stored_ms_max": 11197.085, "longest_gap_ms": 1000, "gap_from_ms": 26000}, "timeline_100ms": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 113, 74, 133, 239, 244, 237, 209, 204, 178, 174, 148, 124, 156, 197, 298, 235, 193, 193, 192, 201, 195, 109, 116, 109, 206, 267, 279, 233, 205, 113, 156, 112, 127, 235, 199, 237, 180, 187, 206, 205, 202, 147, 81, 64, 128, 183, 136, 176, 158, 150, 297, 262, 167, 230, 268, 284, 477, 383, 239, 220, 205, 200, 228, 284, 279, 292, 238, 201, 202, 199, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 198, 200, 202, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 198, 202, 198, 202, 200, 200, 200, 200, 200, 200, 200, 200, 201, 199, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 198, 202, 200, 200, 200, 200, 198, 202, 200, 200, 200, 198, 202, 198, 202, 200, 200, 201, 199, 200, 200, 200, 200, 200, 200, 200, 200, 198, 202, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 198, 203, 199, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 201, 199, 200, 198, 202, 200, 200, 200, 200, 201, 199, 200, 200, 200, 200, 200, 200, 201, 199, 200, 199, 201, 200, 200, 198, 202, 198, 202, 200, 198, 202, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 70, 45, 85, 117, 135, 134, 135, 128, 128, 122, 81, 74, 92, 98, 99, 111, 117, 109, 116, 118, 90, 44, 71, 76, 105, 101, 102, 102, 112, 121, 110, 81, 49, 85, 81, 114, 103, 95, 82, 257, 270, 214, 136, 278, 279, 275, 372, 316, 379, 339, 337, 292, 165, 255, 184, 251, 348, 364, 348, 348, 337, 316, 211, 173, 262, 226, 329, 364, 309, 362, 356, 335, 301, 152, 211, 241, 208, 365, 392, 352, 286, 320, 340, 271, 221, 194, 199, 208, 248, 456, 485, 454, 431, 376, 421, 434, 306, 271, 266, 220, 216, 200, 200, 219, 235, 206, 204, 202, 200, 200, 200, 200, 200, 200, 200, 200, 198, 202, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 198, 203, 199, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 202, 198, 200, 200, 200, 200, 198, 200, 202, 198, 202, 200, 200, 200, 198, 202, 200, 200, 200, 198, 202, 200, 200, 200, 200, 200, 198, 202, 200, 200, 199, 201, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 198, 202, 200, 200, 198, 202, 198, 2], "killed_ms": 26008, "recovery": {"steady_per_100ms": 200.0, "silent_100ms_steps": 10, "caught_up_ms": 10900}}
+{"clients": 10000, "period_ms": 5000, "restart_ms": 1.5, "killed_at_s": 26.0, "steady_cpu_ms_per_s": 120.0, "senders_steady_cpu_ms_per_s": 110.0, "recovery_cpu_ms_first_10s": 8420.0, "senders_cpu_ms_first_10s": 9150.0, "client": {"clients": 10000, "made": 89998, "stored": 89998, "again": 10115, "stored_ms_p50": 0.148, "stored_ms_p99": 5270.907, "stored_ms_max": 10060.656, "longest_gap_ms": 1000, "gap_from_ms": 26000}, "timeline_100ms": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 112, 50, 109, 169, 287, 285, 223, 210, 207, 162, 124, 120, 78, 292, 232, 288, 205, 196, 194, 196, 193, 139, 98, 131, 184, 292, 258, 212, 199, 109, 150, 148, 98, 135, 200, 279, 207, 208, 229, 203, 200, 179, 116, 71, 74, 123, 141, 158, 159, 156, 319, 264, 226, 209, 315, 218, 359, 387, 353, 195, 218, 205, 221, 295, 318, 243, 240, 222, 203, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 199, 201, 200, 200, 200, 200, 200, 200, 200, 198, 200, 202, 200, 200, 200, 200, 200, 200, 200, 199, 201, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 198, 202, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 198, 200, 202, 200, 200, 200, 200, 200, 198, 202, 200, 198, 202, 200, 199, 201, 200, 200, 198, 202, 200, 200, 200, 198, 202, 200, 200, 200, 199, 201, 200, 200, 200, 200, 200, 200, 200, 200, 200, 199, 201, 200, 200, 200, 200, 200, 200, 200, 199, 201, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 198, 202, 200, 200, 198, 202, 200, 200, 200, 200, 200, 200, 200, 200, 200, 198, 202, 200, 200, 200, 200, 200, 200, 198, 202, 200, 198, 202, 200, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 29, 45, 110, 144, 154, 142, 127, 140, 130, 110, 70, 57, 98, 131, 113, 109, 116, 118, 125, 123, 75, 64, 94, 97, 117, 102, 110, 101, 112, 117, 104, 49, 23, 88, 93, 129, 120, 108, 118, 269, 245, 192, 142, 216, 312, 347, 302, 361, 386, 344, 313, 250, 170, 200, 227, 333, 354, 409, 320, 331, 332, 309, 185, 238, 232, 278, 337, 350, 343, 323, 338, 341, 236, 242, 232, 215, 244, 414, 363, 313, 284, 303, 290, 236, 287, 209, 288, 309, 308, 462, 449, 452, 462, 329, 281, 305, 306, 260, 226, 222, 200, 200, 200, 200, 200, 201, 199, 200, 200, 198, 202, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 198, 202, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 198, 202, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 198, 201, 199, 202, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 198, 202, 200, 178, 220, 202, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 198, 202, 200, 200], "killed_ms": 26011, "recovery": {"steady_per_100ms": 200.0, "silent_100ms_steps": 10, "caught_up_ms": 10700}}
diff --git a/research/experiments/019-idle-links/quic/src/main.rs b/research/experiments/019-idle-links/quic/src/main.rs
index cdb0376..28cda77 100644
--- a/research/experiments/019-idle-links/quic/src/main.rs
+++ b/research/experiments/019-idle-links/quic/src/main.rs
@@ -20,6 +20,10 @@
//! connects again and sends it again. Then a line of JSON: orders made
//! and stored, connections made again, the orders stored in each
//! 100 ms, the time from an order made to it stored
+//! idle-quic drain PORT COUNT SIZE
+//! one client sends COUNT messages of SIZE bytes over one stream
+//! (each after its u32 length) and waits until the service says it
+//! read them all; a line of JSON: how long
use std::collections::HashMap;
use std::net::SocketAddr;
use std::sync::{Arc, Mutex};
@@ -72,7 +76,17 @@ fn serve(cert: Vec<u8>, key: Vec<u8>, port: u16) {
let Ok(connection) = incoming.await else { return };
kept.lock().unwrap().push(connection.clone());
while let Ok((mut send, mut recv)) = connection.accept_bi().await {
- let Ok(bytes) = recv.read_to_end(1024).await else { return };
+ let mut first = [0u8; 4];
+ if recv.read_exact(&mut first).await.is_err() {
+ return;
+ }
+ if first == *DRAIN {
+ drain(&mut send, &mut recv).await;
+ continue;
+ }
+ let Ok(rest) = recv.read_to_end(1024).await else { return };
+ let mut bytes = first.to_vec();
+ bytes.extend_from_slice(&rest);
if bytes.len() < 16 {
return;
}
@@ -99,6 +113,32 @@ 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.
+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;
+ }
+ count += 1;
+ }
+ let _ = send.write_all(&count.to_le_bytes()).await;
+ let _ = send.finish();
+}
+
#[derive(Debug)]
struct Anyone; // the test's own service: its certificate is not checked
@@ -308,12 +348,39 @@ fn steady(port: u16, first: u64, count: u64, period: Duration, seconds: u64, win
});
}
+/// One client, COUNT messages of SIZE bytes over one stream, and the answer.
+fn drain_client(port: u16, count: u64, size: usize) {
+ let runtime = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
+ runtime.block_on(async move {
+ let mut endpoint = Endpoint::client(SocketAddr::from(([127, 0, 0, 1], 0))).unwrap();
+ 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];
+ 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();
+ }
+ send.write_all(&0u32.to_le_bytes()).await.unwrap();
+ send.finish().unwrap();
+ let answer = recv.read_to_end(16).await.unwrap();
+ let seconds = began.elapsed().as_secs_f64();
+ let read = u64::from_le_bytes(answer[..8].try_into().unwrap());
+ println!("{{\"messages\":{count},\"size\":{size},\"read\":{read},\"seconds\":{seconds:.3},\"per_second\":{:.0}}}",
+ count as f64 / seconds);
+ });
+}
+
fn main() {
let args: Vec<String> = std::env::args().collect();
match args.get(1).map(String::as_str) {
Some("serve") if args.len() == 4 || args.len() == 5 => serve(std::fs::read(&args[2]).unwrap(),
std::fs::read(&args[3]).unwrap(),
args.get(4).map_or(0, |p| p.parse().unwrap())),
+ Some("drain") if args.len() == 5 => drain_client(args[2].parse().unwrap(), args[3].parse().unwrap(),
+ args[4].parse().unwrap()),
Some("steady") if args.len() == 8 => steady(args[2].parse().unwrap(), args[3].parse().unwrap(),
args[4].parse().unwrap(),
Duration::from_millis(args[5].parse().unwrap()),
@@ -322,7 +389,7 @@ fn main() {
args[4].parse().unwrap(), args[5].parse().unwrap()),
_ => {
eprintln!("idle-quic serve CERT.der KEY.der [PORT] | idle-quic connect PORT FIRST COUNT WINDOW | \
- idle-quic steady PORT FIRST COUNT PERIOD_MS SECONDS WINDOW");
+ idle-quic steady PORT FIRST COUNT PERIOD_MS SECONDS WINDOW | idle-quic drain PORT COUNT SIZE");
std::process::exit(2);
}
}
diff --git a/research/experiments/019-idle-links/run.py b/research/experiments/019-idle-links/run.py
index 1a179e8..63f88a2 100644
--- a/research/experiments/019-idle-links/run.py
+++ b/research/experiments/019-idle-links/run.py
@@ -16,8 +16,22 @@ clients connect, send one order each and wait until it is stored, and stay
connected at rest; the service's memory is read then; then each sends one
more order over its connection.
+With --restart (M2), n devices each make an order every --period-ms and
+send it until it is stored; at --kill-at seconds the receiver is killed
+(-9) and started again at once: the orders stored each 100 ms, the CPU of
+both sides before and after, the X25519 made (none when the node
+remembers its secrets; --no-memo forgets them), and that every order made
+was applied once. With --quic too, the same for quinn clients, which
+connect again when their connection is lost.
+
+With --first-contact DELAYS (M4), for each one-way delay in ms, in a
+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.
+
python3 research/experiments/019-idle-links/run.py [--counts 1000,10000]
- [--store DIRECTORY] [--out FILE.jsonl] [--quic]
+ [--store DIRECTORY] [--out FILE.jsonl] [--quic] [--restart [--no-memo]]
+ [--first-contact 5,25,50]
"""
import argparse
import json
@@ -197,21 +211,21 @@ def quic(counts, work, window, out):
file.write(line + '\n')
-def recovery(timeline, gap_from_ms, gap_ms):
- """From a timeline of orders stored each 100 ms: the steady rate before
- the gap, and how long after the gap began until a second of storing was
- back at 90 % of it with what waited cleared (the rate seen at least once
- and the backlog gone: the cumulative stored caught up with the steady
- line)."""
- start = gap_from_ms // 100
+def recovery(timeline, killed_ms):
+ """From a timeline of orders stored each 100 ms and when the receiver was
+ killed: the steady rate in the 5 s before; the 100 ms steps after the
+ kill with nothing stored; and how long until the orders stored since the
+ kill caught up with the steady rate (99 %), what waited included."""
+ start = killed_ms // 100
before = timeline[max(0, start - 50):start]
steady = sum(before) / len(before) if before else 0
- total_before = sum(timeline[:start])
- for b in range(start + gap_ms // 100, len(timeline)):
- expected = total_before + steady * (b + 1 - start)
- if sum(timeline[:b + 1]) >= expected * 0.99:
- return {'steady_per_100ms': round(steady, 1), 'caught_up_ms': (b + 1 - start) * 100}
- return {'steady_per_100ms': round(steady, 1), 'caught_up_ms': None}
+ silent = sum(1 for b in range(start, min(len(timeline), start + 100)) if not timeline[b])
+ since = 0
+ for b in range(start, len(timeline)):
+ since += timeline[b]
+ if b > start and since >= steady * (b + 1 - start) * 0.99:
+ return {'steady_per_100ms': round(steady, 1), 'silent_100ms_steps': silent, 'caught_up_ms': (b + 1 - start) * 100}
+ return {'steady_per_100ms': round(steady, 1), 'silent_100ms_steps': silent, 'caught_up_ms': None}
def restart_pure(n, work, keys, module, period_ms, seconds, kill_at, memo):
@@ -231,27 +245,33 @@ def restart_pure(n, work, keys, module, period_ms, seconds, kill_at, memo):
link_port, info['public_key'], 0, n, period_ms, seconds]],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
for line in generator.stderr: # progress, a line a second
- if json.loads(line).get('t', 0) >= kill_at:
+ t = json.loads(line).get('t', 0)
+ if t >= kill_at:
break
- cpu_before = cpu_ms(node.pid)
+ seen = time.monotonic()
+ cpu_before, senders_before = cpu_ms(node.pid), cpu_ms(generator.pid)
time.sleep(1.0)
steady_cpu = cpu_ms(node.pid) - cpu_before # ms of CPU in the second before the kill
+ senders_steady = cpu_ms(generator.pid) - senders_before
node.kill()
node.wait()
killed = time.monotonic()
+ killed_ms = int((t + killed - seen) * 1000) # on the generator's clock
if not memo:
(work / 'node.key.links').unlink()
node, _ = start(args)
row = {'devices': n, 'period_ms': period_ms, 'memo': memo, 'restart_ms': round((time.monotonic() - killed) * 1000, 1),
- 'steady_cpu_ms_per_s': round(steady_cpu, 1)}
- cpu_start = cpu_ms(node.pid)
+ 'steady_cpu_ms_per_s': round(steady_cpu, 1), 'senders_steady_cpu_ms_per_s': round(senders_steady, 1)}
+ cpu_start, senders_start = cpu_ms(node.pid), cpu_ms(generator.pid)
time.sleep(10)
row['recovery_cpu_ms_first_10s'] = round(cpu_ms(node.pid) - cpu_start, 1)
+ row['senders_cpu_ms_first_10s'] = round(cpu_ms(generator.pid) - senders_start, 1)
out, error = generator.communicate(timeout=600)
result = json.loads(out)
row['generator'] = {k: v for k, v in result.items() if k != 'timeline_100ms'}
row['timeline_100ms'] = result['timeline_100ms']
- row['recovery'] = recovery(result['timeline_100ms'], result['gap_from_ms'], result['longest_gap_ms'])
+ row['killed_ms'] = killed_ms
+ row['recovery'] = recovery(result['timeline_100ms'], killed_ms)
count = view(port, 'count')
row['applied'] = count
assert count == result['made'] == result['stored'], (count, result['made'], result['stored'])
@@ -273,28 +293,97 @@ def restart_quic(n, work, period_ms, seconds, kill_at):
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
began = time.monotonic()
time.sleep(kill_at)
- cpu_before = cpu_ms(server.pid)
+ cpu_before, clients_before = cpu_ms(server.pid), cpu_ms(client.pid)
time.sleep(1.0)
steady_cpu = cpu_ms(server.pid) - cpu_before
+ clients_steady = cpu_ms(client.pid) - clients_before
server.kill()
server.wait()
killed = time.monotonic()
server, _ = start(args)
row = {'clients': n, 'period_ms': period_ms, 'restart_ms': round((time.monotonic() - killed) * 1000, 1),
- 'killed_at_s': round(killed - began, 1), 'steady_cpu_ms_per_s': round(steady_cpu, 1)}
- cpu_start = cpu_ms(server.pid)
+ 'killed_at_s': round(killed - began, 1), 'steady_cpu_ms_per_s': round(steady_cpu, 1),
+ 'senders_steady_cpu_ms_per_s': round(clients_steady, 1)}
+ cpu_start, clients_start = cpu_ms(server.pid), cpu_ms(client.pid)
time.sleep(10)
row['recovery_cpu_ms_first_10s'] = round(cpu_ms(server.pid) - cpu_start, 1)
+ row['senders_cpu_ms_first_10s'] = round(cpu_ms(client.pid) - clients_start, 1)
out, _ = client.communicate(timeout=900)
result = json.loads(out)
row['client'] = {k: v for k, v in result.items() if k != 'timeline_100ms'}
row['timeline_100ms'] = result['timeline_100ms']
- row['recovery'] = recovery(result['timeline_100ms'], result['gap_from_ms'], result['longest_gap_ms'])
+ row['killed_ms'] = int((killed - began) * 1000)
+ row['recovery'] = recovery(result['timeline_100ms'], row['killed_ms'])
server.kill()
server.wait()
return row
+def first_contact(work, delay_ms, count):
+ """M4, run in a network namespace of its own whose loopback delays UDP
+ by delay_ms each way: count senders the node never heard from each send
+ one order and wait until it is stored, one at a time; then count quinn
+ clients each connect, send one and wait. How many round trips each
+ takes is the time over 2 delay_ms."""
+ source = work / 'receiver.aup'
+ source.write_text(check.LINK_RECEIVER)
+ module = work / 'receiver.pmod'
+ check.compile_module(Path(os.environ.get('AUP', ROOT.parent / 'aup/build/aup')), source, module)
+ keys = work / 'keys'
+ subprocess.run([str(ROOT / 'build/links'), 'keys', str(count), str(keys)], check=True)
+ port, link_port = free_port(socket.SOCK_STREAM), free_port(socket.SOCK_DGRAM)
+ node, info = start([ROOT / 'build/pure-serve', '--module', module, '--store', work / 'store', '--port', port,
+ '--key', work / 'node.key', '--link-port', link_port, '--senders', f'{keys}.senders'])
+ sent = subprocess.run([str(a) for a in [ROOT / 'build/links', 'send', keys, module, 'order', '127.0.0.1', link_port,
+ info['public_key'], 0, count, 1, 1]], capture_output=True, text=True)
+ node.terminate()
+ node.communicate(timeout=60)
+ pure = json.loads(sent.stdout)
+ make_certificate(work)
+ binary = Path(__file__).parent / 'quic/target/release/idle-quic'
+ server, info = start([binary, 'serve', work / 'cert.der', work / 'key.der'])
+ fresh = subprocess.run([str(binary), 'connect', str(info['port']), '0', str(count), '1'], input=b'',
+ capture_output=True, timeout=600)
+ server.kill()
+ server.wait()
+ quic = json.loads(fresh.stdout.decode().splitlines()[0])
+ rtt = 2 * delay_ms
+ return {'delay_ms_each_way': delay_ms, 'senders': count,
+ 'pure_ms_p50': pure['stored_ms_p50'], 'pure_ms_p99': pure['stored_ms_p99'],
+ 'pure_round_trips': round(pure['stored_ms_p50'] / rtt, 2),
+ 'quinn_ms_p50': quic['stored_ms_p50'], 'quinn_ms_p99': quic['stored_ms_p99'],
+ 'quinn_round_trips': round(quic['stored_ms_p50'] / rtt, 2)}
+
+
+def in_namespace(delay_ms, count, out):
+ """first_contact in `unshare -rn`, UDP alone through netem (the node's
+ HTTP view stays quick)."""
+ import shutil
+ tc = shutil.which('tc') or '/usr/sbin/tc'
+ ip = shutil.which('ip') or '/usr/sbin/ip'
+ script = f"""
+import json, subprocess, sys, tempfile
+from pathlib import Path
+sys.path.insert(0, {str(Path(__file__).parent)!r})
+import run
+subprocess.run([{ip!r}, 'link', 'set', 'lo', 'up'], check=True)
+for rule in (['qdisc', 'add', 'dev', 'lo', 'root', 'handle', '1:', 'prio'],
+ ['qdisc', 'add', 'dev', 'lo', 'parent', '1:3', 'handle', '30:', 'netem', 'delay', '{delay_ms}ms'],
+ ['filter', 'add', 'dev', 'lo', 'protocol', 'ip', 'parent', '1:0', 'prio', '3', 'u32', 'match', 'ip',
+ 'protocol', '17', '0xff', 'flowid', '1:3']):
+ subprocess.run([{tc!r}] + rule, check=True)
+with tempfile.TemporaryDirectory(prefix='pure-m4-') as d:
+ print(json.dumps(run.first_contact(Path(d), {delay_ms}, {count})))
+"""
+ done = subprocess.run(['unshare', '-rn', sys.executable, '-c', script], capture_output=True, text=True, timeout=1800)
+ assert done.returncode == 0, done.stderr[-3000:]
+ line = done.stdout.strip().splitlines()[-1]
+ print(line, flush=True)
+ if out:
+ with open(out, 'a') as file:
+ file.write(line + '\n')
+
+
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--counts', default='1000,10000,100000,1000000')
@@ -308,8 +397,15 @@ def main():
parser.add_argument('--period-ms', type=int, default=5000)
parser.add_argument('--seconds', type=int, default=40)
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')
args = parser.parse_args()
counts = [int(c) for c in args.counts.split(',')]
+ 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(','):
+ in_namespace(int(delay), counts[0], args.out)
+ return
if args.restart:
with tempfile.TemporaryDirectory(prefix='pure-m2-', dir=args.store) as directory:
work = Path(directory)