- The metric the disaggregation GO/NO-GO hangs on is per-thread CPU p50/p99, and the tree had no
first-party collector for it: no CLOCK_THREAD_CPUTIME_ID, no getrusage, no RUSAGE_THREAD, no
/proc/self/task anywhere under MobileGL/, tools/, android-plugin/ or scripts/.
- The retrace loop is the cheapest honest place to take it. Retrace runs --singlethread and
trace_benchmark states that Begin/OnFrameBoundary/End are only ever reached from that one
thread, so that thread's CPU time IS the client-side CPU cost: one extra clock_gettime per
frame, no root, no profiler, no debuggable build, no sampling - and no instrumentation
committed to a hot path inside the library.
- The CPU reading is taken before the wall reading at each boundary, so the syscall lands in the
wall delta rather than hiding inside the CPU delta. An inflated wall number is visible; a
deflated CPU number would not be.
- Report::frameCpuMs is the same length as frameMs or it is EMPTY. A clock that started failing
mid-run would otherwise be silently re-indexed and put frame N next to frame N+k, and an empty
series and a series of zeroes are different claims about the platform.
- SummarizeBenchmark is split into SummarizeSeries and reused verbatim for the CPU series rather
than duplicated: same tail window, same even-count median rule, same nearest-rank p95, or the
delta between the two series stops meaning anything.
- benchmark.json gains meanFrameCpuMs / medianFrameCpuMs / p95FrameCpuMs and the WHOLE
frameCpuTimesMs[] array beside frameTimesMs[]; result.json and the completion line gain the
three headline numbers. p99 therefore needs no device change - it is a host-side reduction over
an artefact that already exists, and run_android_retrace_local.py prints p50/p95/p99 off the
same trailing window the device summarised.
- Pre-flighted on the desktop CLI, which shares the same core: 2-frame run, cpu series aligned
with the wall series and strictly below it (wall 1290.787/20.225 ms, cpu 535.271/8.261 ms).
- PLAN-B.md §8.2 and appendix B add a batch of new runtime switches
(MOBILEGL_PIPE_PUSH / _VERIFY / _STATS / _LEGACY_MEMOS / _TEXEL_RETAIN_MB /
_INDEX_MIRROR_MB, plus MOBILEGL_IPC_* later), and §11 P0 wants them parsed beside
the existing ones. Today every knob that has to reach an Android replay costs an
edit in five files - run_android_retrace_local.py, trace-replay-ci.sh,
TraceReplayActivity's request record, the JNI marshalling, and the setenv block in
trace_replay_core.cpp. That per-knob tax is what this replaces: one extra,
`--es mobilegl_env "K=V;K=V"`, carries all of them.
- Applied last, immediately before dlopen(libMobileGL.so), so it can also override
the dedicated fields above it - MobileGL's config is read during the load, and an
escape hatch that cannot beat the defaults is not one. An entry with no '=' unsets
the variable, which is the only way to clear a default the marshalling sets.
- The existing per-knob flags stay: they carry semantics beyond a setenv (use_angle
also selects a variant, the dump lists are joined, DirectVulkan forces the
R11G11B10F fallback), and rewriting them as env strings would move that logic into
the callers.
- Surface: --env / MOBILEGL_TRACE_ENV in trace-replay-ci.sh, repeatable --env
KEY=VALUE in run_android_retrace_local.py, `mobilegl_env` intent extra,
Request::envOverrides.
- The two-level parse now lives in trace_env_overrides.hpp, beside the semicolon
splitter it shares with the texture and FBO dump lists, and
tools/trace_replay/trace_env_overrides_test.cpp pins it: the empty entries a
trailing ';' leaves behind must not become unsetenv(""), `K=` must stay a Set of
the empty string rather than an Unset (a knob read with getenv() != nullptr sees
those as opposite answers), and only the FIRST '=' may separate, or a value
carrying '=' is truncated without a word of warning. The whole MOBILEGL_PIPE_*
batch rides on this parse, and the only lane that exercised it end to end was an
on-device retrace, which would have reported a splitting bug as "the knob had no
effect".
- The check is built and RUN at build time and mobilegl_trace_replay depends on it,
so `cmake --build ... --target mobilegl_trace_replay` - the exact command of
test.yml's "Build trace replay" job, which never invokes ctest - runs it. It is
assert-free on purpose: that lane configures Release, and <cassert> under NDEBUG
would compile every check into a green run that checked nothing. Negative control:
swapping find('=') for rfind('=') fails 2 checks, and keeping the splitter's empty
entries fails 2 more.