Files
MobileGL/docs/Disaggregated/notes/p7/device-window-1-runbook.md
T
swung0x48 9cea514010 [docs] (P7): ph-f.md §6/§7 record the F fix round - the fingerprint refusal is one-directional and an un-redeployed phone shows as "no Welcome", the unix pairing window and the two pre-auth exposures are §12 debts, the token minimum is TCP-only, and the token examples are 16+ bytes
- ph-f.md: §0 rows F-a..F-d with package-tree SHAs; §2.2 states the asymmetry (new server + old client -> Refuse{WireFingerprint}; old server + new client -> the old AcceptPair closes both after 2000 ms and the client sees `no Welcome ... (rc=6)`, no Refuse frame); §6 the four fix commits with their red-once lines; §6.4 the unix arrival-order pairing (250 ms / 2000 ms) as a local-peer debt with the SELinux assumption named, and the two pre-auth exposures; §7 the gates on 2b2a0ae0
- CONTRACT-P7 §12: the two pre-auth exposures (fork before Hello with a 10 s wait; ValidatePeerHandshake before AuthenticatePeerToken sends the fingerprint and build stamp to an unauthenticated peer), the unix pairing debt, and the fingerprint asymmetry, for PH-7 (5)
- PLAN-PH-P34B-P7: PH-7 (3) says the minimum is checked at the TCP listen; PH-8 says "ignored, server-sized" rather than "clamped"; the no-token listen row's remaining-work cell no longer claims the smoke is unreferenced
- device-window-1-runbook §2 step 3: the un-redeployed-phone symptom, and where to look first
- ROADMAP §6 and peer-loss-probe: `MOBILEGL_IPC_TOKEN=devtoken` (8 bytes, which now exits 72) becomes a 25-byte token in every example
2026-09-22 21:55:39 -04:00

609 lines
36 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 设备窗口 #1 运行手册(wave 1,Redmi `2f7cbe2e`)
本文是 [`PLAN-PH-P34B-P7.md`](PLAN-PH-P34B-P7.md) §3 wave 1 的**可逐条粘贴**执行稿:一次
reboot-clean 会话里打三笔债——P7-7 实验 E0–E3、P6.5 残余的 39 例 DirectGLES TCP golden
矩阵、P3b/P4b + P7 出口门 5 的 CTS `$BASE`。
> **它不做什么。** 不改任何源码,不调阈值,不删用例。每一步都先说明**什么结果支持哪个假设**,
> 再说明证据落在哪里。没有跑到的步骤写进 `unrun.md`,不按「大概没问题」记过。
设备由集成者独占;本手册假定执行者就是持机的人。所有命令块都标了它跑在哪个 shell 里:
**[Win]** = Windows(PowerShell 或 Git Bash,`adb` 在这一侧),**[WSL]** = WSL Arch。
---
## 0. 设备与环境事实(不要重新发现)
来自 [`../p65/validation-status.md`](../p65/validation-status.md) 与
[`../../../../tools/device_bench/pin_device.sh`](../../../../tools/device_bench/pin_device.sh) 的 `2f7cbe2e` 行:
| 事实 | 值 | 出处 |
|---|---|---|
| 设备 | Redmi M332BF / SM8750 / **Adreno 830v2**,aarch64 | `pin_device.sh:73-74` |
| LAN IP | `192.168.21.181` | `validation-status.md:130` |
| GPU 钉频上限 | **1050 MHz**(本板 `thermal_pwrlevel=1` 闩死,`max_gpuclk` 只读 1050000000) | `pin_device.sh:85-97` |
| 大核 / 小核钉频 | policy6 `1958400`,policy0 `1555200` | `pin_device.sh:82` |
| 绝对时间可比性 | **不可**与 `35d0befa` 的行比较(那台还能到 1100) | `pin_device.sh:92-93` |
| 测试包 | `top.mobilegl.plugin.trace`(可用 `MOBILEGL_TRACE_PACKAGE` 改) | `run_android_retrace_local.py:34-64` |
| Doze 状态文件 | `/home/swung/.cache/mobilegl/tcp-server/2f7cbe2e-top.mobilegl.plugin.trace.json`,内容 `originally_whitelisted=false` | `validation-status.md:211-213` |
| 已知真机 DirectVulkan 分歧 | 两次观测 **0.988998 / 0.976494**,inproc 与 spawn 一致,**同 pbuffer 的 monolith = 1.0** | `CURRENT_STAGE_PROGRESS.md:158-160` |
| 已知 GLES 失败 | `minecraft-1.21.11-main-menu` SSIM 0.890161(trace 的 UBO offset 不满足本机 32 字节对齐,非分离缺陷) | `../p65/mainmenu-trace-difference.md` |
| 出口门 3 分母排除 | `minecraft-1.21.4-rd12-odinlite-in-world`、`create-indirect`(OQ-17 dev 侧崩溃) | ID-P7-4 |
**`--use-pbuffer` 在本机的 DirectVulkan 上是可用的**:`CURRENT_STAGE_PROGRESS.md:158` 记的
monolith 1.0 就是同一 pbuffer 口径取的。(`tools/cts/README.md:120-124` 说 DirectVulkan 的
pbuffer 在 Adreno 上不可用——那条**只针对 CTS 的 dEQP platform 端口**,retrace 走 MobileGL 自
己的 EGL,两者不是同一条路径。若当天 E0 的 DirectVulkan pbuffer 臂在 `eglMakeCurrent` 上红,
改用窗口表面重跑并在证据里写明换了口径,不要把它当成分歧。)
证据根目录(**每一步都写进去**):
```
docs/Disaggregated/notes/p7/device-window-1/
00-session/ boot id、pin 前后、APK 身份、设备信息
E0-attribution/ 归属与重复确定性
E1-lockstep/ RUN_AHEAD=0 对照
E2-credit/ PRESENT_CREDIT 1 vs 3
E3-caps/ 两臂 caps 日志比对
P65-tcp-matrix/ 39 例 DirectGLES TCP golden 矩阵
CTS-base/ monolith × DirectVulkan 的五个块
unrun.md 窗口内没跑到的步骤,逐条写明原因
```
`.trace-work/` 与 `/home/swung/` 下的原始产物**不入库**;入库的是 `result.json`、
`*-actual.png`(只在分歧 case 上)、`summary.json`、`transport-proof.json`、`pin-*.txt`、
`run.json` 和各步的一段结论。大于 ~2 MiB 的原图留在 WSL 并在文里记绝对路径与 SHA-256。
---
## 1. 窗口前置(**不占设备时间**,前一天做完)
1. **主机工具自测**(全部不需要设备、不需要 GPU):
```bash
# [WSL] 在本窗口要用的那棵树里
cd /home/swung/w7/p7-runner
python3 tools/trace_replay/test_run_tcp_matrix.py # 18 tests, OK
python3 tools/trace_replay/test_compare_actuals.py # 10 tests, OK
python3 tools/trace_replay/compare_actuals.py --self-test
python3 scripts/ci/tcp_lane_tools_test.py # 6 tests, OK
python3 tools/trace_replay/test_android_lifecycle.py # 含 SpawnServerPath 的解析门
```
2. **构建并签名 APK**(`-Pmobilegl.debuggableRelease=true` **不是可选项**:没有它
release APK 不可 `run-as`,`trace-replay-ci.sh` 取不到私有角色日志,而「没有库日志」与
「跑了但没产生日志」在结果里长得一模一样,见 `tools/device_bench/p6/README.md`):
```bash
# [WSL 或 Win] 见 tools/device_bench/p6/README.md「Building the APK」
gradle --no-daemon -p <tree>/android-plugin :app:assembleTraceRelease \
-Pmobilegl.buildDisaggregated=ON -Pmobilegl.buildDisaggregatedInproc=ON \
-Pmobilegl.pipePush=ON -Pmobilegl.debuggableRelease=true \
-Pmobilegl.applicationIdSuffix=.p7w1 -Pmobilegl.apkSuffix=p7w1
apksigner sign --ks ~/.android/debug.keystore --ks-pass pass:android \
--ks-key-alias androiddebugkey --key-pass pass:android \
--out MobileGL-plugin-trace-release-p7w1.apk <unsigned>.apk
```
`applicationIdSuffix` 让它**并排**装在既有 trace 安装旁边(两个不同签名的 APK 不能共用
application id,`adb install -r` 会 `INSTALL_FAILED_UPDATE_INCOMPATIBLE`)。用它就必须同时
设 `MOBILEGL_TRACE_PACKAGE`,见 §3。
3. **主机侧 TCP 矩阵的制品先备好**(第 7 步用):一份 WSL 的 `libMobileGL.so` +
`mobilegl_trace_replay`,以及 `ctest --show-only=json-v1` 的 catalog。两端必须共享同一个
**显式** `MOBILEGL_BUILD_STAMP`,否则 `MOBILEGL_IPC_REQUIRE_SAME_BUILD=1` 会在握手上拒绝。
**设备上的 server 也必须是本次构建**(wave 2-F 起 TCP 握手换成了「一条连接 + `Welcome.dataNonce`」):
新 APK 的 server 对旧 client 会回 `Refuse{WireFingerprint}`,但**旧 APK 的 server 对新 client 没有任何
Refuse 帧**——旧 supervisor 等第二条连接 2000 ms 后把两条都关掉(设备日志 `only 1 of 2 connections
arrived`),主机侧只看到 `MG_Remote client: no Welcome ... (rc=6)`;看到这条先回 §3 核对
`apk-install.txt` 的 `lastUpdateTime`,再怀疑别的:
```bash
# [WSL]
cd /home/swung/w7/p7-runner
ctest --test-dir build-split --show-only=json-v1 > /home/swung/p7w1-catalog.json
sha256sum build-split/libMobileGL.so build-split/<...>/mobilegl_trace_replay \
> docs/Disaggregated/notes/p7/device-window-1/00-session/host-artifacts.sha256
```
4. **CTS 的 caselist**:见 §8 —— **树内生成不了**,必须在窗口前解决,否则第 8 步只能记 unrun。
---
## 2. 会话开始:reboot-clean、钉频、唤醒(~10 min)
```bash
# [Win] 0) 记下重启前的 boot id,重启,再记一次。两者相同 = 没有真的重启,按失败处理。
adb -s 2f7cbe2e shell cat /proc/sys/kernel/random/boot_id | tee 00-session/boot-id-before.txt
adb -s 2f7cbe2e reboot
adb -s 2f7cbe2e wait-for-device
adb -s 2f7cbe2e shell 'while [ "$(getprop sys.boot_completed)" != 1 ]; do sleep 2; done'
adb -s 2f7cbe2e shell cat /proc/sys/kernel/random/boot_id | tee 00-session/boot-id-after.txt
# 1) 唤醒并让屏幕别灭。Dozing 时 native supervisor 会停在 do_freezer_trap,
# 前台 Service 的 partial wake lock 显示 DISABLED(validation-status.md:141-143)。
adb -s 2f7cbe2e shell input keyevent KEYCODE_WAKEUP
adb -s 2f7cbe2e shell svc power stayon usb
adb -s 2f7cbe2e shell dumpsys deviceidle get deep | tee 00-session/deviceidle-before.txt
# 2) 钉频。需要 root(脚本自己会先验 su)。0=PINNED / 1=DRIFT / 2=UNPINNED。
bash tools/device_bench/pin_device.sh 2f7cbe2e pin | tee 00-session/pin-session-start.txt
bash tools/device_bench/pin_device.sh 2f7cbe2e check | tee 00-session/pin-check-00.txt
```
**纪律:每一个臂开始前 `check`、结束后再 `check`。** 任一次不是 `PINNED`,那个臂的数据作废
(`status: UNPROVEN`),不降级使用。`check` 的三值语义见 `pin_device.sh:19-31`。
> 判别:`boot-id-before` ≠ `boot-id-after` ⇒ 窗口是 reboot-clean 的;相同 ⇒ 重启没发生,
> 停下重来,不要把它记成干净窗口。
---
## 3. APK 安装身份(~5 min)
```bash
# [Win] 注意:Git Bash 里若设了 MSYS_NO_PATHCONV=1,adb.exe 收到 /c/... 会 "failed to stat"——
# APK 参数用 Windows 路径($(cygpath -w ...));设备侧 /data/... 路径才需要 MSYS_NO_PATHCONV。
# flavour 后缀在 applicationIdSuffix 之后:包名是 top.mobilegl.plugin.p7w1.trace。
adb -s 2f7cbe2e install -r "$(cygpath -w MobileGL-plugin-trace-release-p7w1.apk)"
adb -s 2f7cbe2e shell pm list packages | grep mobilegl | tee 00-session/packages.txt
adb -s 2f7cbe2e shell dumpsys package top.mobilegl.plugin.p7w1.trace \
| grep -E 'versionName|lastUpdateTime|codePath|nativeLibraryPath' | tee 00-session/apk-install.txt
sha256sum MobileGL-plugin-trace-release-p7w1.apk | tee 00-session/apk.sha256
# 等 dex2oat 退出再开始任何臂:一条 am_kill ... due to installPackageLI 落在臂里量的是安装器。
adb -s 2f7cbe2e shell 'n=0; while pgrep dex2oat >/dev/null && [ $n -lt 150 ]; do sleep 2; n=$((n+1)); done; echo "dex2oat idle after $((n*2))s"'
# (`pgrep -f dex2oat` 会匹配到承载这条循环的 sh 自己,永远不退出;按进程名匹配。)
# 本窗口的其余步骤都要看得见这个 id:
export MOBILEGL_TRACE_PACKAGE=top.mobilegl.plugin.p7w1.trace # [Win] 每个 shell 都要设
export MOBILEGL_TRACE_SKIP_INSTALL=1 # 已装好,别让每个 case 再装一次
```
> 判别:`00-session/apk.sha256` + `apk-install.txt` 的 `lastUpdateTime` 是**这组图像由哪个二进
> 制产生**的唯一答案。`run_android_retrace_local.py --archive-dir` 写的 `run.json` 里也会带
> 同一个 `apk_sha256`(它自己重新算,所以两者不一致本身就是发现)。
---
## 4. E0:分歧归属与重复确定性(~1.5 h)
**问题**:DirectVulkan 的 0.988998 / 0.976494 是**哪些 case**贡献的?同一个 case 跑三遍是不是
逐位相同?
> **窗口预算说明(必须读)。** 39 例 × 2 传输 × 3 遍 = 234 次回放,按每例 45–90 s 计约
> **3–4 h**,一个窗口里放不下它加第 7、8 步。所以 E0 拆成 **E0a 普查(1 遍)→ E0b 确定性
> (只对分歧集 + 两个对照 case 跑 3 遍)**。判别力不受损:确定性问题只对已经分歧的 case 有
> 意义。若当天时间充裕,E0b 直接把 `--case` 换成 `--matrix` 就是完整版。
### E0a:一遍全矩阵,得到分歧 case 列表(~50 min)
```bash
# [Win] 对照臂:monolith。这是「同 pbuffer 下 DirectVulkan 本来是 1.0」的复验。
bash tools/device_bench/pin_device.sh 2f7cbe2e check | tee E0-attribution/pin-before-monolith.txt
python tools/trace_replay/run_android_retrace_local.py \
--matrix --backend DirectVulkan --use-pbuffer --transport monolith --repeat 1 \
--archive-dir .trace-work/p7w1/E0a-monolith \
2>&1 | tee E0-attribution/E0a-monolith.log
bash tools/device_bench/pin_device.sh 2f7cbe2e check | tee E0-attribution/pin-after-monolith.txt
# [Win] 验收臂:inproc。同一进程内跨真实 ring 到 apply 线程;--require-inproc 由脚本自动带上。
bash tools/device_bench/pin_device.sh 2f7cbe2e check | tee E0-attribution/pin-before-inproc.txt
python tools/trace_replay/run_android_retrace_local.py \
--matrix --backend DirectVulkan --use-pbuffer --transport inproc --repeat 1 \
--archive-dir .trace-work/p7w1/E0a-inproc \
2>&1 | tee E0-attribution/E0a-inproc.log
bash tools/device_bench/pin_device.sh 2f7cbe2e check | tee E0-attribution/pin-after-inproc.txt
```
`--matrix` 取的是 **manifest 的 CI split 子集(39 例)**,按各 case 自己的 `ci_backends` 过滤
(DirectVulkan 下是 39 例,DirectGLES 下是 38 例,差的一例是 DirectVulkan-only 的
`iterationrp`)。`--all` 会多带上 `ci:false` 的 rd12——**ID-P7-4 把它排除在出口门 3 的分母外**,
所以这里用 `--matrix`,不用 `--all`。
`iterationrp` 需要 apk.yml 的三个 Magma 旋钮(`.github/workflows/apk.yml:460-462`,原文逐字):
```bash
# [Win] 只在跑 iterationrp 时导出;它们由 trace-replay-ci.sh 自己读环境,不走 --env
export MOBILEGL_MAGMA_FIX_ITERATIONRP_SUBGROUP_SCRATCH=1
export MOBILEGL_MAGMA_DERIVE_NUM_SUBGROUPS=1
export MOBILEGL_MAGMA_ITERATIONRP_FIX_BARRIER=1
```
`create-indirect` 与 rd12 按 ID-P7-4 排除:它们在 `dev@81b17c0b` 就在 Adreno 830 上坏,不是本
分支造成的。rd12 不在 `--matrix` 里;`create-indirect` **在**,所以它的红**单列**,不计入出口
门 3 的分母(在 `E0-attribution/exclusions.md` 里写清楚,别在汇总表里悄悄删行)。
汇总:
```bash
# [WSL 或 Win]
python3 tools/trace_replay/compare_actuals.py summary .trace-work/p7w1/E0a-inproc \
--json E0-attribution/E0a-inproc-summary.json | tee E0-attribution/E0a-inproc-summary.txt
python3 tools/trace_replay/compare_actuals.py summary .trace-work/p7w1/E0a-monolith \
--json E0-attribution/E0a-monolith-summary.json | tee E0-attribution/E0a-monolith-summary.txt
```
> **判别**:monolith 全 1.0 而 inproc 有 N 个 case < 阈值 ⇒ 分歧属于**分离路径**,且那 N 个
> case 的名字就是 wave 2 的工作清单;monolith 也分歧 ⇒ 问题不在分离,在 Magma 本身(整条
> run-ahead / credit 线索出局,E1/E2 不必跑,直接进 E3 与 wave 2-B)。
证据:`E0-attribution/{E0a-*-summary.json,E0a-*.log}`,以及**只对分歧 case** 归档的
`*-actual.png` + `result.json`(从 `.trace-work/p7w1/E0a-inproc/<case>-DirectVulkan/repeat-01/`
拷进 `E0-attribution/divergent/`)。分歧 case 名单写成 `E0-attribution/divergent-cases.md`。
### E0b:分歧集 × 3 遍 × 两传输(~40 min)
```bash
# [Win] $D = E0a 得到的分歧 case,逐个 --case;外加两个 E0a 通过的 case 作对照
python tools/trace_replay/run_android_retrace_local.py \
--case <divergent-1> --case <divergent-2> ... --case <control-1> --case <control-2> \
--backend DirectVulkan --use-pbuffer --transport inproc --repeat 3 \
--archive-dir .trace-work/p7w1/E0b-inproc 2>&1 | tee E0-attribution/E0b-inproc.log
python tools/trace_replay/run_android_retrace_local.py \
--case <同一组> --backend DirectVulkan --use-pbuffer --transport monolith --repeat 3 \
--archive-dir .trace-work/p7w1/E0b-monolith 2>&1 | tee E0-attribution/E0b-monolith.log
python3 tools/trace_replay/compare_actuals.py summary .trace-work/p7w1/E0b-inproc \
--json E0-attribution/E0b-inproc-summary.json | tee E0-attribution/E0b-inproc-summary.txt
```
汇总表的列就是要回答的问题:`case × repeat × ssim_vs_golden × ssim_vs_first × bit-identical`。
`ssim_vs_first` 用的是 `result.json` 自己记的 crop,和 `ssim_vs_golden` 描述同一个矩形;
SSIM 的实现是 retrace 门自己的那一个(`trace_replay_core.cpp:630/:671` 的逐字转写)。
> **判别**:三遍 `bit-identical=yes` ⇒ 分歧是**确定性**的,可以在桌面上用固定输入追(E4/E5 有
> 意义,RenderDoc 抓一次就够);三遍不逐位相同、`ssim_vs_first < 1` ⇒ 分歧带**时序/竞态**成分,
> 先看 E1(run-ahead 排队)再谈内容路径,且任何单次截图的对比都不可作结论。
---
## 5. E1:`MOBILEGL_IPC_RUN_AHEAD=0` 的 lockstep 对照(~20 min,判别力最高)
这是 §0 结论 3 说的「从未在 golden 口径下跑过、且不需要改任何代码」的那个实验。
```bash
# [Win] 与 E0b 完全相同的 case 集、相同 pbuffer、相同 3 遍,只多一个 --env
bash tools/device_bench/pin_device.sh 2f7cbe2e check | tee E1-lockstep/pin-before.txt
python tools/trace_replay/run_android_retrace_local.py \
--case <与 E0b 同一组> --backend DirectVulkan --use-pbuffer --transport inproc --repeat 3 \
--env MOBILEGL_IPC_RUN_AHEAD=0 \
--archive-dir .trace-work/p7w1/E1-inproc-ra0 2>&1 | tee E1-lockstep/E1.log
bash tools/device_bench/pin_device.sh 2f7cbe2e check | tee E1-lockstep/pin-after.txt
python3 tools/trace_replay/compare_actuals.py summary .trace-work/p7w1/E1-inproc-ra0 \
--json E1-lockstep/E1-summary.json | tee E1-lockstep/E1-summary.txt
```
### 证明 lockstep 真的生效的那一行日志
**不能指望 `running lockstep`。** 那句话来自 `MG_Remote/Client/ClientSession.cpp:1877-1879`:
```
MG_Remote client: run-ahead requested, server does not publish kCapRunAheadApply -
running lockstep. The selected backend has not enabled its implementation-readiness gate
```
它的守卫是 `else if (MG_Config::Ipc.RunAhead != 0 && m_barrierArmed)`(`:1873`)——即
「**旋钮开着**但 server 没有那个 cap」。`RUN_AHEAD=0` 时这一句**根本不会打印**,
`LatchRunAheadFromCaps` 一行也不打。所以证据是**一条在 + 一条不在**:
| 必须出现 | 出处 |
|---|---|
| `Config: IPC ring=... verb-barrier=1 run-ahead=0 present-credit=... strict=1 ... role-split-state=1` | `ConfigLoader.cpp:459-462`,只在非 monolith 打 |
| `Config: MOBILEGL_TRANSPORT=inproc - the MGPipe record stream` | ConfigLoader 的 inproc 臂,pull 库写不出来 |
| 必须**不**出现 | 出处 |
|---|---|
| `MG_Remote client: run-ahead ARMED - the server publishes kCapRunAheadApply` | `ClientSession.cpp:1869-1872` |
```bash
# [Win/WSL] 逐遍核对,三遍都要
for r in .trace-work/p7w1/E1-inproc-ra0/*/repeat-*; do
echo "== $r"
grep -c 'run-ahead ARMED' "$r/mobilegl.log" || true # 必须是 0
grep -o 'run-ahead=[0-9]*' "$r/mobilegl.log" | sort -u # 必须只有 run-ahead=0
grep -c 'MOBILEGL_TRANSPORT=inproc - the MGPipe record stream' "$r/mobilegl.log"
done | tee E1-lockstep/arm-proof.txt
```
这个形状与 `../p5f/magma-runahead.md:120` 记的一致:「Magma inproc RA=0 | 实际 Config
run-ahead=0,无 ARMED」。
> **已知副作用,先说在前面**:`run_android_retrace_local.py --transport inproc` 会自动带上
> `--require-inproc`,而那个门要求 `Config: IPC` 行里 `run-ahead=1`
> (`android-plugin/trace-replay-ci.sh:601`)。所以 **E1 的每个 case 都会以非零退出码结束,
> 且退出发生在 `result.json` 已经拉回来之后**(`trace-replay-ci.sh:548` 拉结果、`:586` 判
> `passed`、`:587` 才跑 arm proof)。这不是失败:按上表自己核对 `Config: IPC` 行即可,SSIM 与
> 归档照常可用。把 `E1.log` 里的非零退出连同这句说明一起入库,**不要**为了让它绿而去掉
> `--env MOBILEGL_IPC_RUN_AHEAD=0`。
> **判别(本窗口判别力最高的一行)**:分歧 case 在 RA=0 下回到 **1.0** ⇒ 原因在 run-ahead 这条
> 线(未等待的发布排队 / 延迟退休 / present credit),wave 2 从 `ClientSession` 的发布路径查;
> **不变** ⇒ run-ahead 整条线出局,原因在 **server 的内容路径**(Magma 的 wire 应用),E2 的
> credit 扫描多半也会不变,直接转 E3 + wave 2-B/C。
---
## 6. E2:`MOBILEGL_IPC_PRESENT_CREDIT` 1 vs 3(~25 min)
只在 E1 「不变」**或**「部分回到 1.0」时才有意义;E1 全数回 1.0 时这一步降级为确认性抽查。
```bash
# [Win] 两个臂,其余参数与 E0b 逐字相同
for c in 1 3; do
bash tools/device_bench/pin_device.sh 2f7cbe2e check | tee E2-credit/pin-before-credit$c.txt
python tools/trace_replay/run_android_retrace_local.py \
--case <与 E0b 同一组> --backend DirectVulkan --use-pbuffer --transport inproc --repeat 3 \
--env MOBILEGL_IPC_PRESENT_CREDIT=$c \
--archive-dir .trace-work/p7w1/E2-credit$c 2>&1 | tee E2-credit/E2-credit$c.log
python3 tools/trace_replay/compare_actuals.py summary .trace-work/p7w1/E2-credit$c \
--json E2-credit/E2-credit$c-summary.json | tee E2-credit/E2-credit$c-summary.txt
bash tools/device_bench/pin_device.sh 2f7cbe2e check | tee E2-credit/pin-after-credit$c.txt
done
grep -ho 'present-credit=[0-9]*' .trace-work/p7w1/E2-credit*/*/repeat-*/mobilegl.log \
| sort | uniq -c | tee E2-credit/credit-actually-applied.txt
```
最后那一行不是装饰:`present-credit=` 来自 `ConfigLoader.cpp:459-462` 的同一条 `Config: IPC`
行,它是「旋钮真的到达了库」的唯一证据。两个臂各自只应出现自己的值。
> **判别**:credit=1(最严格的节流)下分歧消失、credit=3 下仍在 ⇒ 是**帧间排队深度**问题,与
> E1 的结论应当一致,wave 2 查 present credit 的释放点;两个 credit 表现相同 ⇒ 排队深度不是
> 变量,credit 线索与 run-ahead 线索一起出局。
---
## 7. E3:两臂 caps 日志比对(~10 min,不占额外回放)
E3 不需要新回放,读 E0a/E0b 已经产生的日志即可。
```bash
# [Win/WSL] 两臂、两个角色的日志一起扫
for d in .trace-work/p7w1/E0a-monolith .trace-work/p7w1/E0a-inproc; do
echo "=== $d"
grep -rh 'CapsMirror::.* read before the first CapsSnapshot - answering a PLACEHOLDER' "$d" | sort | uniq -c
grep -rh 'the server does not consume MGPipe subsystem' "$d" | sort | uniq -c
grep -rh 'CapsMirror generation .* adopted' "$d" | sort | uniq -c
grep -rhE 'GL_(UNIFORM_BUFFER|SHADER_STORAGE_BUFFER)_OFFSET_ALIGNMENT: [0-9]+' "$d" | sort | uniq -c
done | tee E3-caps/caps-compare.txt
```
精确出处,免得当天去找:
| 行 | 出处 | 期望 |
|---|---|---|
| `MG_Remote client: CapsMirror::<accessor> read before the first CapsSnapshot - answering a PLACEHOLDER. Expected exactly once at startup, from LogBackendInfo (MG_Backend/Init.cpp:21); anything later means a caps read beat the handshake` | `MG_Remote/Client/CapsMirror.cpp:47-51` | 启动期**恰好一条**(来自 `LogBackendInfo`)。多于一条 ⇒ 有 caps 读跑在握手前面,客户端在用占位能力决策 |
| `MG_Remote client: the server does not consume MGPipe subsystem 0x%llx - this family emits NOTHING and the legacy pull path runs for it (R-8). callMask=...` | `CapsMirror.cpp:143-149` | **零条**。出现即该 family 整族不过线、走 legacy pull,是分歧的一等嫌疑 |
| `MG_Remote client: CapsMirror generation N adopted (backend=..., callMask=0x...)` | `CapsMirror.cpp:81-83` | 至少一条,且 `backend` 是 DirectVulkan 对应值;`callMask` 两臂应相同 |
| `GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT: 32`(SSBO 64) | `MG_Util/BackendLoaders/OpenGL/Loader.cpp:1094`;本机实测值见 `../p65/mainmenu-trace-difference.md` | Adreno 830 的真值是 **32 / 64**(`Loader.h:1263-1265`)。inproc 臂的 client 若打出 256(`Loader.h:1261/:1266` 的默认值)⇒ client 用了默认而不是远端上报的对齐 |
> **判别**:inproc 臂出现 `does not consume MGPipe subsystem` 或 placeholder 多于一条 ⇒ 分歧
> 是**能力协商**问题,与 run-ahead 无关,归 wave 2-C(OQ-8/OQ-10 同族);两臂 caps 逐字相同 ⇒
> 能力线索出局,留下 Magma wire 应用(wave 2-B)。
---
## 8. P6.5 残余:39 例 DirectGLES TCP golden 矩阵(~2.5–3 h)
口径就是 `validation-status.md:231-233` 欠的那一条:**WSL 作 client,Redmi 作 server**,
逐例要求 `run-ahead ARMED`。
```bash
# [Win] 8.1 起设备端 supervisor(前台 Service),并临时豁免 Doze
python3 tools/trace_replay/tcp_device_server.py start \
--serial 2f7cbe2e --package top.mobilegl.plugin.p7w1.trace \
--listen tcp://0.0.0.0:40613 --token <TOKEN> --allow-idle \
--state-file /home/swung/.cache/mobilegl/tcp-server/2f7cbe2e-top.mobilegl.plugin.p7w1.trace.json \
| tee P65-tcp-matrix/server-start.txt
adb -s 2f7cbe2e logcat -s MobileGLServer -d | tee P65-tcp-matrix/server-ready.txt
```
`--allow-idle` 是**显式**选项,它把改动前的原状态存进 `--state-file`(重复 start 不覆盖原
值),`stop` 再按原值恢复——不要手工 `dumpsys deviceidle whitelist -`。
```bash
# [WSL] 8.2 LAN 直连。WSL 默认经 eth2/xray_tun 到手机时只回第一个 MSS
# (validation-status.md:144-146),所以加一条只针对手机的临时 host route。
sudo ip route add 192.168.21.181/32 via 192.168.31.1 dev eth0 metric 10
ping -c 3 192.168.21.181 | tee P65-tcp-matrix/route.txt
# [WSL] 8.3 正式矩阵。--require-run-ahead 强制 RUN_AHEAD=1 + VERB_BARRIER=1,
# 并逐例要求私有 client 日志有 ARMED、没有 lockstep / DISARMED。
cd /home/swung/w7/p7-runner
export MOBILEGL_BUILD_STAMP=<两端共享的显式 stamp>
python3 tools/trace_replay/run_tcp_matrix.py \
--catalog /home/swung/p7w1-catalog.json \
--runner /home/swung/w7/p7-runner/build-split/<...>/mobilegl_trace_replay \
--library /home/swung/w7/p7-runner/build-split/libMobileGL.so \
--endpoint tcp://192.168.21.181:40613 --token <TOKEN> \
--backend DirectGLES --credit 2 --require-run-ahead \
--wake-adb-serial 2f7cbe2e \
--out /home/swung/p7w1-tcp-matrix 2>&1 | tee P65-tcp-matrix/matrix.log
# 中断 / 超时后原样续跑(同一 library、runner、peer):
# ... --out /home/swung/p7w1-tcp-matrix --resume
```
`--wake-adb-serial 2f7cbe2e` 让 driver 每 15 秒打一次 `KEYCODE_WAKEUP`
(`run_tcp_matrix.py:363-371`),这是长 case 跨越息屏的唯一保险。看门狗是
**300 秒无日志进展** + **7200 秒绝对上限**两条独立线,和 manifest 的 `timeout_seconds` 无关。
一条 case 超时或留下残留进程时 driver 会**停下整个矩阵**(避免把对端不健康级联成一串 Busy),
checkpoint 保留;先查对端再 `--resume`。
**预期里已经有一条红**:`minecraft-1.21.11-main-menu`,SSIM ≈ 0.890161。它的成因已定位为
trace 的 UBO offset 不满足本机 32 字节对齐(`../p65/mainmenu-trace-difference.md`),**单列,
不当成新发现,也不改阈值**。
```bash
# [WSL] 8.4 收口
python3 - <<'PY' | tee P65-tcp-matrix/summary.txt
import json, collections
rows = json.load(open('/home/swung/p7w1-tcp-matrix/results.json'))
print(collections.Counter(r['status'] for r in rows))
for r in rows:
if r['status'] != 'passed':
print(r['case'], r['backend'], r['status'], r.get('error'))
PY
cp /home/swung/p7w1-tcp-matrix/results.json P65-tcp-matrix/results.json
```
每行都带 `backend`、`golden`、`alternate_golden`、`ssim_threshold`、`requested_arm`、
`actual_arm`,所以这份 `results.json` 自己就能回答「用哪张图、哪个阈值、实际有没有 ARMED」。
> **判别**:38 例(DirectGLES 下 39 例里 `iterationrp` 是 DirectVulkan-only)逐例 ARMED 且除
> main-menu 外全绿 ⇒ P6.5 的这笔残余结清;出现新的红 ⇒ 按 case 名单单列,不与 DirectVulkan
> 的分歧混记。
---
## 9. CTS `$BASE`:monolith × DirectVulkan 的五个块(~1.5–2 h,可无人值守)
出口门 5 要的是「**迁移落地之前**的基线读数」。五个块(`PLAN-PH-P34B-P7.md` §1.3 出口门 5):
`texture_*`、`shader_image_*`、`packed_pixels`、`direct_state_access.*`、`uniform_buffer_object*`。
### 9.1 两个必须先解决的前置(**当天临时抱佛脚会浪费整个窗口**)
**(a) caselist 树内生成不了。** `tools/cts/` 有完整 harness,但**没有任何 caselist 入库**,
mustpass 清单也不在本仓:
```
$ find tools/cts -iname '*caselist*' -o -iname '*mustpass*' -o -name '*.txt'
(空)
```
本机 `/home/swung/cts-src` 的 checkout 里 `external/openglcts/data/` 是**空的**,也没有任何
mustpass 文件,所以连它也生成不出来。正确做法,窗口**之前**做完:
```bash
# [WSL] 取一个 release tag 的完整 checkout(用 tag,不用 main,读数才可引用)
git -C "$CTS" checkout opengl-cts-4.6.8.1
python "$CTS"/external/fetch_sources.py
ls "$CTS"/external/openglcts/data/mustpass/gl/khronos_mustpass/4.6.*/ # gl46-main.txt 在这里
# 从 mustpass 切出五个块,入库到 tools/cts/caselists/p7-*.txt
cd /home/swung/w7/p7-runner
mkdir -p tools/cts/caselists
MP="$CTS/external/openglcts/data/mustpass/gl/khronos_mustpass/4.6.8/gl46-main.txt" # 以实际路径为准
grep -E '^KHR-GL46\.texture_' "$MP" > tools/cts/caselists/p7-texture.txt
grep -E '^KHR-GL46\.shader_image_' "$MP" > tools/cts/caselists/p7-shader-image.txt
grep -E '^KHR-GL46\.packed_pixels' "$MP" > tools/cts/caselists/p7-packed-pixels.txt
grep -E '^KHR-GL46\.direct_state_access\.' "$MP" > tools/cts/caselists/p7-dsa.txt
grep -E '^KHR-GL46\.uniform_buffer_object' "$MP" > tools/cts/caselists/p7-ubo.txt
wc -l tools/cts/caselists/p7-*.txt # 入库时把这几个数写进提交信息
```
入库这五个文件的提交要单独说明它取自哪个 tag、哪个 mustpass 文件、各多少条——
**读数只有连着 caselist 的出处才可引用**。
**(b) `KHR-GL46` 在 Android 跑器上多半够不着。** `tools/cts/skills/gl-cts-on-mobilegl/SKILL.md`
的全部参考读数都是 **KHR-GL33**,并明说「`KHR-GL33` 不需要解门——包注册表无条件注册它」,
暗示别的包**可能**被门住;README 的 4.5 读数取自**桌面 lavapipe**,不是设备。平台端口本身
会把包要求的版本原样传给 `eglCreateContext`(`tcuMobileGLPlatform.cpp:401-405`),所以这是
「MobileGL 的 Android EGL 能否给出 4.6 core context」+「这个 arm64 glcts 构建里有没有
KHR-GL46 包」两个问题,**都没有树内证据**。窗口前必须先 5 分钟验掉:
```bash
# [Win] 设备上已部署的 glcts,列一下包与其中一个块的条数(只读,不跑用例)
adb -s 2f7cbe2e shell 'cd /data/local/tmp/mgcts && LD_LIBRARY_PATH=. \
MOBILEGL_BACKEND_TYPE=DirectVulkan ./glcts --deqp-runmode=stdout-caselist \
--deqp-case=KHR-GL46.texture_\* 2>&1 | head -20'
```
有输出 ⇒ 按 9.2 跑 `KHR-GL46.*`。无输出或 context 创建失败 ⇒ **改用 `KHR-GL33` 的同名块**,
并在 `CTS-base/README.md` 里写明基线的 API 版本是 33 而不是 46,**AFTER 读数必须用同一个版本**
(0.5 pp 是两个同口径读数的差,跨版本比较没有意义)。这是给集成者的裁定项,不是执行者当场
能定的事。
### 9.2 preflight 与跑
```bash
# [Win] 先 preflight,绝不跳过:它一秒内证明这台设备 + 这个库能给出 core context 且 FBO 回读正确
adb -s 2f7cbe2e shell 'cd /data/local/tmp/mgcts && LD_LIBRARY_PATH=. ./mgprobe \
--backend DirectVulkan --surface imagereader --lib ./libMobileGL.so' | tee CTS-base/preflight.txt
# 期望 PASS ... user_fbo=ok;DirectVulkan 的 default_fb=broken 是已知且不设门的
# [Win] 五个块,逐块一个 outdir。monolith 是 $BASE 的定义,所以不传任何 MOBILEGL_TRANSPORT。
for b in texture shader-image packed-pixels dsa ubo; do
bash tools/device_bench/pin_device.sh 2f7cbe2e check | tee CTS-base/pin-before-$b.txt
python tools/cts/scripts/run_cts.py \
--serial 2f7cbe2e --backend DirectVulkan \
--caselist tools/cts/caselists/p7-$b.txt \
--outdir /c/.../CTS-base/runs/$b \
--env MOBILEGL_CTS_FBO_COLOR_TEXTURE=1 \
--surface fbo --gl-config-name rgba8888d24s8 --cpu-mask fast \
--chunk-timeout 900 \
--skip-file tools/cts/caselists/p7-skip.txt 2>&1 | tee CTS-base/run-$b.log
python tools/cts/scripts/qpa_report.py /c/.../CTS-base/runs/$b --label "DirectVulkan-$b" \
| tee CTS-base/report-$b.txt
bash tools/device_bench/pin_device.sh 2f7cbe2e check | tee CTS-base/pin-after-$b.txt
done
```
不可省的三个参数,理由见 `tools/cts/skills/gl-cts-on-mobilegl/SKILL.md`「Required flags」:
| 参数 | 没有它会怎样 |
|---|---|
| `--surface fbo` | DirectVulkan 从**默认 framebuffer** 回读全零且不报 GL 错,整套读数接近 0 而与一致性无关 |
| `--env MOBILEGL_CTS_FBO_COLOR_TEXTURE=1` | `--surface fbo` 单独不够:dEQP 的 FboRenderContext 挂的是 **renderbuffer**,DirectVulkan 从 renderbuffer-attached FBO 一样回读零。实测影响 46.15% → 72.74%;DirectGLES 两种方式逐位相同,是证明这个开关中性的对照 |
| `--deqp-terminate-on-device-lost=disable`(`run_cts.py` 自己加,`:281`) | 默认 enable 会在每个 case 后调 GL 4.5 的 `glGetGraphicsResetStatus()`,3.3 core 里没有,第一个 case 就段错误 |
`run_cts.py` 的超时是**「device 侧 .qpa 900 秒不增长」**而不是墙钟上限(`:66-76`)——一个 chunk
合法地跑一小时,而 GPU hang 会让日志停止增长。到期它按 caselist 路径 `pkill` 设备侧 glcts 并报
124。它还区分「case 崩了」与「设备没了」(`device_alive`,`:36-44`),会等设备回来、重新拉那份
在 `/data/local/tmp` 上活下来的 `.qpa`,把当时打开的那个 case 记成 DeviceHang 并隔离。
结果落点:每块 `outdir` 下 `chunk*.qpa`、`crashed.txt`、`hung.txt`、`unrun.txt`、`skipped.txt`。
**`unrun.txt` 非空就不能把这块当完整读数**——`qpa_report.py` 会把隔离与未跑的单独列出并排除在
比率之外,照抄它的口径。
> **判别**:五块都有 `unrun.txt` 为空的完整读数 ⇒ `$BASE` 成立,wave 4 的 AFTER 读数按同一
> caselist、同一 flag、同一 API 版本比 0.5 pp;任一块 `unrun.txt` 非空或 `hung.txt` 有新条目
> ⇒ 该块只记为**部分读数**并把 hang 的 case 加进 `p7-skip.txt`,下一窗口补。
---
## 10. 临时环境恢复(**窗口结束必做**)
顺序不能反:先停 server(它会按状态文件恢复 Doze 豁免),再撤路由,最后解钉频。
```bash
# [WSL] 1) 停测试包并恢复本次添加的包级 idle 豁免(validation-status.md:215-218)
python3 <tree>/tools/trace_replay/tcp_device_server.py stop --serial 2f7cbe2e \
--package top.mobilegl.plugin.p7w1.trace \
--state-file /home/swung/.cache/mobilegl/tcp-server/2f7cbe2e-top.mobilegl.plugin.p7w1.trace.json
# [WSL root] 2) 撤掉本次新增的临时网络辅助配置
sudo ip route del 192.168.21.181/32 via 192.168.31.1 dev eth0 metric 10
adb -s 2f7cbe2e forward --remove tcp:40615 # 若本窗口用过;40613 的 --forward 同理
# [Win] 3) 解钉频并复核。unpin 之后 check 应报 UNPINNED(退出码 2,对这个动作而言即成功)
bash tools/device_bench/pin_device.sh 2f7cbe2e unpin | tee 00-session/pin-session-end.txt
bash tools/device_bench/pin_device.sh 2f7cbe2e check | tee 00-session/pin-check-final.txt
# [Win] 4) 还原屏幕常亮,并核对 Doze 白名单确实回到会话前的样子
adb -s 2f7cbe2e shell svc power stayon false
adb -s 2f7cbe2e shell dumpsys deviceidle whitelist | grep -i mobilegl | tee 00-session/deviceidle-after.txt
```
`00-session/deviceidle-after.txt` 必须与 `deviceidle-before.txt` 对本包一致。不一致就地修,
不要留给下一个窗口。若本窗口用了并排安装的 `.p7w1` 包,明确决定是卸载还是保留,并写进
`00-session/README.md`——一台共享设备上留一个来历不明的安装,是下一次 A/B 的噪声源。
---
## 11. 收口清单
窗口结束时 `docs/Disaggregated/notes/p7/device-window-1/` 里应当有:
- [ ] `00-session/`:两个 boot id、pin 起止与每臂前后的 `check`、APK SHA-256 与安装时间、Doze 前后
- [ ] `E0-attribution/divergent-cases.md`:**分歧 case 名单**(本窗口最重要的一件产出)+ 两臂 summary
- [ ] `E0-attribution/exclusions.md`:按 ID-P7-4 单列的 `create-indirect`(与未纳入 `--matrix` 的 rd12)
- [ ] `E1-lockstep/arm-proof.txt`:三遍都 `run-ahead=0` 且 `run-ahead ARMED` 计数为 0
- [ ] `E1-lockstep/E1-summary.json` 与一句结论:RA=0 下**回到 1.0 / 不变**
- [ ] `E2-credit/credit-actually-applied.txt`:两个臂各自只出现自己的 `present-credit=`
- [ ] `E3-caps/caps-compare.txt`:placeholder 条数、subsystem 拒绝条数、两臂 `callMask`、UBO/SSBO 对齐实测值
- [ ] `P65-tcp-matrix/results.json` + `summary.txt`:逐例 `actual_arm`,红的单列
- [ ] `CTS-base/report-*.txt` ×5 + 各自的 `unrun.txt`/`hung.txt` 状态,以及 §9.1(b) 的 API 版本裁定
- [ ] `unrun.md`:没跑到的步骤逐条写原因
写完之后,`PLAN-PH-P34B-P7.md` §1.3「出口门 3」一行应当从「不可测」变成「已定位到 N 个
case,原因指向 X」——那句话就是 wave 2 派活的依据。