# 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=` (default `2f7cbe2e`) and `PERF_WORK=` (default `/.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//-r/`. 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 `. Per-tid blocked-syscall histogram (`/proc//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 /32 via 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 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).