Skip to content

Commit 44da739

Browse files
committed
Document steady-memory device validation
1 parent 086ce39 commit 44da739

2 files changed

Lines changed: 141 additions & 11 deletions

File tree

app/app/src/androidTest/java/com/bringyour/network/acceptance/PhysicalLowbarSessionTest.kt

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,8 @@ class PhysicalLowbarSessionTest {
292292
.put("dnsBytes", tracked.dnsByteCount)
293293
.put("clientSendBytes", tracked.clientSendByteCount)
294294
.put("clientReceiveBytes", tracked.clientReceiveByteCount)
295+
.put("packQueueUsedBytes", tracked.packQueueUsedByteCount)
296+
.put("packQueueCapacityBytes", tracked.packQueueCapacityByteCount)
295297
.put("providerSendBytes", tracked.providerSendByteCount)
296298
.put("providerReceiveBytes", tracked.providerReceiveByteCount)
297299
.put("totalBytes", tracked.totalByteCount))
@@ -339,13 +341,53 @@ class PhysicalLowbarSessionTest {
339341
.put("goForcedGcCycles", sample.optLong("forced_gc"))
340342
.put("goroutines", sample.optLong("goroutines"))
341343
.put("poolOutstanding", sample.optLong("pool_outstanding"))
344+
.put("packetPoolOutstandingBytes", sample.optLong("packet_pool_outstanding_bytes"))
345+
.put("deviceTunEgressOutstandingBytes", sample.optLong("device_tun_egress_outstanding_bytes"))
342346
.put("poolRetainedBytes", sample.optLong("pool_retained_bytes"))
347+
.put("packetPoolRetainedBytes", sample.optLong("packet_pool_retained_bytes"))
348+
.put("largeObjectPoolRetainedBytes", sample.optLong("large_object_pool_retained_bytes"))
343349
.put("poolCapacityBytes", sample.optLong("pool_capacity_bytes"))
344350
.put("packetPressureDropCount", sample.optLong("packet_pressure_drops"))
351+
.put("packetPressureDropBytes", sample.optLong("packet_pressure_drop_bytes"))
352+
.put("packetPressureH1AckAdmitCount", sample.optLong("packet_pressure_h1_ack_admits"))
353+
.put("packetPressureAckDropCount", sample.optLong("packet_pressure_ack_drops"))
354+
.put("packetPressureOtherDropCount", sample.optLong("packet_pressure_other_drops"))
345355
.put("trackedMemoryBytes", sample.optLong("device_tracked_bytes"))
356+
.put("resendQueueUsedBytes", sample.optLong("resend_queue_used_bytes"))
357+
.put("resendQueueCapacityBytes", sample.optLong("resend_queue_capacity_bytes"))
358+
.put("receiveQueueUsedBytes", sample.optLong("receive_queue_used_bytes"))
359+
.put("receiveQueueCapacityBytes", sample.optLong("receive_queue_capacity_bytes"))
360+
.put("packQueueUsedBytes", sample.optLong("pack_queue_used_bytes"))
361+
.put("packQueueCapacityBytes", sample.optLong("pack_queue_capacity_bytes"))
346362
.put("qualityClientCount", sample.optLong("quality_clients"))
347363
.put("speedClientCount", sample.optLong("speed_clients"))
348364
.put("flowCount", sample.optLong("flows"))
365+
.put("packHandoffDropCount", sample.optLong("pack_handoff_drops"))
366+
.put("packHandoffDropBytes", sample.optLong("pack_handoff_drop_bytes"))
367+
.put("packHandoffWaitCount", sample.optLong("pack_handoff_waits"))
368+
.put("packHandoffWaitSuccessCount", sample.optLong("pack_handoff_wait_successes"))
369+
.put("packHandoffMaxCount", sample.optLong("pack_handoff_max_count"))
370+
.put("packHandoffMaxBytes", sample.optLong("pack_handoff_max_bytes"))
371+
.put("ackHandoffDropCount", sample.optLong("ack_handoff_drops"))
372+
.put("ackHandoffQueueFullDropCount", sample.optLong("ack_handoff_queue_full_drops"))
373+
.put("ackHandoffMissCount", sample.optLong("ack_handoff_misses"))
374+
.put("ackHandoffWaitCount", sample.optLong("ack_handoff_waits"))
375+
.put("ackHandoffWaitSuccessCount", sample.optLong("ack_handoff_wait_successes"))
376+
.put("ackRouteWriteCount", sample.optLong("ack_route_writes"))
377+
.put("ackRoutePriorityWriteCount", sample.optLong("ack_route_priority_writes"))
378+
.put("ackRouteWriteBlockedCount", sample.optLong("ack_route_write_blocks"))
379+
.put("ackRouteWriteErrorCount", sample.optLong("ack_route_write_errors"))
380+
.put("ackRouteWriteWaitNanos", sample.optLong("ack_route_write_wait_nanos"))
381+
.put("ackRouteWriteMaxWaitNanos", sample.optLong("ack_route_write_max_wait_nanos"))
382+
.put("initialWriteCount", sample.optLong("initial_writes"))
383+
.put("initialFrameCount", sample.optLong("initial_frames"))
384+
.put("initialMessageBytes", sample.optLong("initial_message_bytes"))
385+
.put("timeoutResendWriteCount", sample.optLong("timeout_resend_writes"))
386+
.put("carrierChangeWriteCount", sample.optLong("carrier_change_writes"))
387+
.put("selectiveGapWriteCount", sample.optLong("selective_gap_writes"))
388+
.put("ackTailProbeWriteCount", sample.optLong("ack_tail_probe_writes"))
389+
.put("cumulativeProbeWriteCount", sample.optLong("cumulative_probe_writes"))
390+
.put("recoveryWriteErrorCount", sample.optLong("recovery_write_errors"))
349391
.put("platformTransportBudgetUsedBytes", sample.optLong("transport_budget_used_bytes"))
350392
.put("platformTransportBudgetUsedCount", sample.optLong("transport_budget_used_count"))
351393
.put("platformTransportBudgetPendingH1Count", sample.optLong("transport_budget_pending_h1"))
@@ -375,7 +417,16 @@ class PhysicalLowbarSessionTest {
375417
val peakAndroidPssKib = AtomicLong()
376418
val peakGoroutines = AtomicLong()
377419
val peakPoolOutstanding = AtomicLong()
420+
val peakPacketPoolOutstandingBytes = AtomicLong()
421+
val peakDeviceTunEgressOutstandingBytes = AtomicLong()
422+
val peakPacketPoolRetainedBytes = AtomicLong()
423+
val peakLargeObjectPoolRetainedBytes = AtomicLong()
424+
val peakPackQueueUsedBytes = AtomicLong()
425+
val packQueueCapacityBytes = AtomicLong()
426+
val peakReceiveQueueUsedBytes = AtomicLong()
427+
val receiveQueueCapacityBytes = AtomicLong()
378428
val packetPressureDropCount = AtomicLong()
429+
val ackRoutePriorityWriteCount = AtomicLong()
379430

380431
fun observe(sample: JSONObject) {
381432
count.incrementAndGet()
@@ -384,7 +435,28 @@ class PhysicalLowbarSessionTest {
384435
updateMax(peakAndroidPssKib, sample.optLong("androidPssKib"))
385436
updateMax(peakGoroutines, sample.optLong("goroutines"))
386437
updateMax(peakPoolOutstanding, sample.optLong("poolOutstanding"))
438+
updateMax(
439+
peakPacketPoolOutstandingBytes,
440+
sample.optLong("packetPoolOutstandingBytes"),
441+
)
442+
updateMax(
443+
peakDeviceTunEgressOutstandingBytes,
444+
sample.optLong("deviceTunEgressOutstandingBytes"),
445+
)
446+
updateMax(peakPacketPoolRetainedBytes, sample.optLong("packetPoolRetainedBytes"))
447+
updateMax(
448+
peakLargeObjectPoolRetainedBytes,
449+
sample.optLong("largeObjectPoolRetainedBytes"),
450+
)
451+
updateMax(peakPackQueueUsedBytes, sample.optLong("packQueueUsedBytes"))
452+
updateMax(packQueueCapacityBytes, sample.optLong("packQueueCapacityBytes"))
453+
updateMax(peakReceiveQueueUsedBytes, sample.optLong("receiveQueueUsedBytes"))
454+
updateMax(receiveQueueCapacityBytes, sample.optLong("receiveQueueCapacityBytes"))
387455
updateMax(packetPressureDropCount, sample.optLong("packetPressureDropCount"))
456+
updateMax(
457+
ackRoutePriorityWriteCount,
458+
sample.optLong("ackRoutePriorityWriteCount"),
459+
)
388460
if (sample.optLong("goRuntimeBytes") > GO_RUNTIME_SPIKE_BYTES) {
389461
thresholdBreaches.incrementAndGet()
390462
}
@@ -399,7 +471,16 @@ class PhysicalLowbarSessionTest {
399471
.put("peakAndroidPssKib", peakAndroidPssKib.get())
400472
.put("peakGoroutines", peakGoroutines.get())
401473
.put("peakPoolOutstanding", peakPoolOutstanding.get())
474+
.put("peakPacketPoolOutstandingBytes", peakPacketPoolOutstandingBytes.get())
475+
.put("peakDeviceTunEgressOutstandingBytes", peakDeviceTunEgressOutstandingBytes.get())
476+
.put("peakPacketPoolRetainedBytes", peakPacketPoolRetainedBytes.get())
477+
.put("peakLargeObjectPoolRetainedBytes", peakLargeObjectPoolRetainedBytes.get())
478+
.put("peakPackQueueUsedBytes", peakPackQueueUsedBytes.get())
479+
.put("packQueueCapacityBytes", packQueueCapacityBytes.get())
480+
.put("peakReceiveQueueUsedBytes", peakReceiveQueueUsedBytes.get())
481+
.put("receiveQueueCapacityBytes", receiveQueueCapacityBytes.get())
402482
.put("packetPressureDropCount", packetPressureDropCount.get())
483+
.put("ackRoutePriorityWriteCount", ackRoutePriorityWriteCount.get())
403484

404485
private fun updateMax(target: AtomicLong, value: Long) {
405486
var current = target.get()

app/scripts/PHYSICAL_LOWBAR.md

Lines changed: 60 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,10 @@ runtime overhead, object/allocation/free counts, GC/forced-GC/pause counters,
9292
pool in-flight/retained/capacity bytes, automatic reclaim decisions and
9393
before/after values, aggregate mobile packet-pressure drops, primitive
9494
client/flow topology, platform transport-budget use, Android PSS, Java/native
95-
heaps, threads, descriptors, route state, and aggregate carrier counters. The
95+
heaps, threads, descriptors, route state, and aggregate carrier counters.
96+
Sampler schema 9 also publishes resend, Pack-handoff, and receive-reorder
97+
used/capacity bytes. The Pack/receive values are device-wide shared budgets,
98+
not one selected flow's queue. The
9699
steady streamline signal is `goRuntimeBytes`:
97100
five quiet connected minutes after burst ownership drains should have p50 and
98101
p95 at or below 24 MiB. Keep
@@ -116,20 +119,66 @@ mobile reclaimer waits for payload quiet and bounded outstanding ownership,
116119
then performs at most one pass per cooldown; use its deferred, below-target,
117120
cooldown, and before/after counters to distinguish policy from a leak.
118121
`packetPressureDropCount` is a cumulative overload counter, not a pool leak:
119-
the <=24-MiB mobile profile samples packet-root ownership every fourth ingress
120-
call below pressure, rejects a complete native ingress batch at 512 or more
121-
roots, and samples every call until ownership drains. Rejected batch ownership
122-
is returned immediately; TCP retransmission/backpressure provides recovery.
123-
The accepted performance profile keeps the H3-safe 16-message sequence and
124-
16-packet/24-KiB group ceilings, fixes Auto quality/speed windows at 4/1, and
125-
retains a 512-KiB packet warm set after reclaim. Do not raise the aggregate
126-
gate or double the per-flow/group ceilings without repeating the explicit-H3
127-
28-MiB failure test; both experiments breached it on the physical surrogate.
128-
Server/default devices do not instantiate this gate. The same mobile profile
122+
the <=24-MiB mobile profile samples exact packet-root bytes every fourth ingress
123+
call below pressure, admits the largest ordered prefix that fits below 1 MiB,
124+
and samples every call until ownership drains. The message pool gives <=256-byte
125+
ACK/control traffic its own class, so an ordinary TCP ACK is charged 256 bytes
126+
rather than one 2-KiB full-MTU root. Explicit H1 with provider work disabled may
127+
rescue exact ACK-only TCP packets from the rejected suffix up to the 2-MiB
128+
aggregate ceiling; H3, Auto, provider-on, data, and connection-state packets
129+
retain the 1-MiB base ceiling. Rejected ownership is returned immediately; TCP
130+
retransmission/backpressure provides recovery. The accepted performance profile
131+
keeps send, Transfer-ACK, forward, contract, H3, and control sequences at 16,
132+
gives only H1 receive a 64-message / 128-KiB handoff with a 10-ms Pack wait and
133+
1-ms ACK wait on the reliable carrier, retains 16-packet/24-KiB logical groups,
134+
fixes Auto quality/speed windows at 4/1, and keeps a 256-KiB packet warm set
135+
split between the small and full-MTU classes after reclaim. The mobile receive
136+
reorder budget is 1.68 MiB while providing at the 24-MiB target (with a
137+
1.5-MiB floor) and 2 MiB when provider work is off.
138+
Its shared charge is retained allocation, including pooled outer/message roots
139+
and a rounded decoded-owner envelope; the independent per-flow limit remains
140+
logical payload bytes. A newly empty flow retains one progress item, but cannot
141+
admit a second until aggregate budget returns; the mobile flow cap bounds that
142+
deliberate liveness overdraft. Do not raise the base byte gate or the H1 receive window
143+
to 128 without repeating the explicit-H3 and sustained-H1 memory gates; earlier
144+
count-based experiments reached 28.41--29.95 MiB and about 25.05 MiB
145+
respectively on the physical surrogate. Server/default devices do not
146+
instantiate this gate or pay the retained-root scan. The same mobile profile
129147
retires inactive TCP flow state after three minutes so a closed browser burst
130148
does not preserve the desktop ten-minute graph throughout the five-minute
131149
steady window.
132150

151+
The 32-message H1 ready-drain is not an additional
152+
buffer: it still stops ordinary data at 12 KiB, uses the existing 16-KiB
153+
WebSocket wrapper, and never waits for a batch to fill. The 2026-08-24 physical
154+
pass improved the ten-run Cloudflare median from 1.695 to 2.02 Mbit/s, but a
155+
45-second real fast.com burst moved 28.74 MiB H1 ingress and left 6.48 MiB in
156+
returned packet pools. Go runtime briefly reached 29.48 MiB for three sampler
157+
records before one quiet rebuild reduced it to 19.85 MiB. That run fails the
158+
active/post-burst 28-MiB gate even though it recovered; retain the speed and
159+
memory evidence together.
160+
161+
The accepted 2026-08-25 allocation-accurate pass reduced the same failure mode
162+
without shrinking the useful per-flow receive window. All ten full 1-MiB
163+
Cloudflare responses completed at 2.04--6.00 Mbit/s (2.78 median), and fast.com
164+
moved at least 20.53 MiB ingress in the inner counter bracket. Go runtime
165+
peaked at 21.77 MiB, packet roots at 1.78 MiB, and exact receive use at
166+
2.00/2.00 MiB; none of 61 samples exceeded 24 or 28 MiB. During five quiet
167+
connected minutes, runtime p50/p95/range/last were
168+
19.91/20.16/19.85--20.20/19.91 MiB, queued receive bytes were zero, the packet
169+
warm set was 256 KiB, and neither forced GC nor idle trim ran. Nine of 11
170+
bounded Pack waits succeeded; the two misses returned 2,880 bytes and every
171+
payload still completed. Pre/hot/post-recovery Wikipedia median load was
172+
455.1/627.4/613.6 ms. Preserve one hot reused-H2 5.3-second resource outlier in
173+
the record; it had no concurrent tunnel handoff/pressure drop or timeout resend,
174+
and the seven post-recovery pages had no multi-second resource tail.
175+
176+
This validates the Android Go-allocation surrogate, not iOS extension
177+
`phys_footprint` or jetsam behavior. The adjacent Direct upper-pair median was
178+
41.53 Mbit/s while the unchanged public H1 provider remained much slower;
179+
provider grouping/direct-ACK deployment to a controlled exit is required
180+
before claiming restoration of 40+ Mbit/s.
181+
133182
Parser and eligibility tests are dependency-free:
134183

135184
```sh

0 commit comments

Comments
 (0)