Disaggregated-mode device perf harness
A/B/C/D performance matrix and profiling harness for MobileGL's transport arms
(monolith / inproc / spawn+shm / spawn+tcp), built for the adb-attached
Android bench device. Written from Git Bash on a Windows host; requires
MSYS_NO_PATHCONV=1 (the scripts set it themselves).
Prerequisites: the disaggregated trace APK installed
(./gradlew -p android-plugin :app:assembleTraceRelease -Pmobilegl.buildDisaggregated=ON -Pmobilegl.buildDisaggregatedInproc=ON -Pmobilegl.pipePush=ON -Pmobilegl.debuggableRelease=true,
then sign with the debug keystore and adb install -r), and trace fixtures under
tools/trace_replay/fixtures/.
All scripts honor SER=<adb serial> (default 2f7cbe2e) and
PERF_WORK=<results root> (default <repo>/.trace-work/perf-compare, git-ignored).
run_matrix.sh — perf matrix driver
bash run_matrix.sh [smoke|prep|openra|rd12|all]
Interleaved A/B/C/D rounds (monolith -> inproc -> shm -> tcp), N reps per arm
(OPENRA_REPS / RD12_REPS, default 3). Interleaving is the substitute for
frequency pinning on unrooted devices. Per run it force-stops the app, clears
stale results, starts/stops the tcp server for the tcp arm, verifies the arm
markers in logcat (Config: MOBILEGL_TRANSPORT=..., spawn ARMED,
run-ahead ARMED — a run without them is invalid), and pulls
result.json / benchmark.json / PipeStats dumps into
$PERF_WORK/<workload>/<arm>-r<n>/.
benchmark mode never snapshots, so ssim stays -1; the matrix adds one non-benchmark ssim round per workload/arm for the golden gate.
make_report.py — aggregate into REPORT.md
python make_report.py [results_dir]
Renders raw per-rep tables, best-of-N aggregates with spread, the ssim gate,
per-run arm verification, and merged client+server PipeStats rollups.
Note the spawn-arm traps: client-side frames=0 is structural (use the server
dump), and logcat MGPipe stats: mixes non-counter lines — trust the JSON dumps.
profile_matrix.sh + run_profile_batch.sh — profiling lane
Same runner with PROF=1: adds simpleperf (perf_event_paranoid=-1 device)
for the client and server processes, ftrace sched capture for exact wakeup
latencies (shell-writable on this device), and the /proc sampler below.
bash run_profile_batch.sh # the canned batch; edit the run list inside
PROF=1 ONE=tcp WL=rd12 REP=1 bash profile_matrix.sh one # single arm
ONE / WL / REP / EXTRA_ENV / EXTRA_SERVER_ENV select and parameterize
a single run; e.g. EXTRA_SERVER_ENV=MOBILEGL_IPC_SPIN_US=2000 for the
deep-idle-exit A/B.
sampler.c — device-side /proc sampler
sampler <pid> <duration_s> <interval_ms> <out.csv>. Per-tid blocked-syscall
histogram (/proc/<tid>/syscall) + CPU/switch counters. Exists because strace
attach is SELinux-blocked for shell uid on the bench device, while same-uid
/proc reads via run-as work. Build for the device with the NDK
(aarch64-linux-android*-clang), push, run under run-as.
tcp_path_check.sh — run before any cross-host measurement
Connects from WSL to a closed port on the phone. End to end, the phone refuses
it; if the connect is accepted, something on the host (here v2rayN's xray_tun,
WSL's default route) is terminating TCP and relaying it. That fakes sub-ms RTTs,
and on 2026-09-28 it stalled the MobileGL control stream. The stall read as a
server hang at eglMakeCurrent. Exit 0 = end to end, 1 = middlebox,
2 = filtered. Fix: ip route replace <phone>/32 via <LAN gateway> dev eth0
as root in WSL (temporary; delete it afterwards).
fcl_accept.sh — P12 exit gate (a)
FCL's Minecraft renders into the Render Server's on-screen window on the same phone, then
fcl_accept.sh <backend> kill kills the server and checks the client latches DEVICE LOST and FCL
survives. Needs the FCL fork's version setting "keepRunningInBackground" ON in config.json
(FCL pauses the game the moment its Activity leaves the foreground). The server is started through
the Render Server screen's documented extras. Results: docs/Disaggregated/notes/p12/FCL-ACCEPTANCE.md.
crosshost_accept.sh + crosshost_report.py — P12 exit gate (b)
A WSL client replays the openra and rd12 traces against the phone's render server over
TCP + stream (ssim gate, then PRESENT_CREDIT 1/2/3 benchmarks). Adds a temporary /32 route
around the host's TUN proxy, checks the path with tcp_path_check.sh, and removes the route and
stops the server on exit. crosshost_report.py reduces the output to a table. Results and the
P6.5 link numbers: docs/Disaggregated/notes/p12/CROSSHOST-ACCEPTANCE.md.
netbench.sh — Wi-Fi TCP throughput baseline
Understates the link: the toybox nc sink capped at 18-22 MiB/s while the render server
read 52 MB/s on the same Wi-Fi. Use the server's P65ServerFrame read rate as the throughput number (enable it with MOBILEGL_SERVER_FRAME_STATS=1 or setprop debug.mobilegl.server_frame_stats 1; it is off by default).
WSL -> phone bulk transfer over toybox nc sink, 3 reps; refuses to run
unless tcp_path_check.sh passes. The nc listener must keep stdin open or it
exits on EOF and RSTs the connection, and pkill must be anchored
('^toybox nc') or it kills the adb shell that is about to start the listener.
phone_strace.sh — session-child strace
Attaches the device /system/bin/strace to the server's session child as the
app uid via run-as (shell-uid ptrace is blocked).