From 534067493c77a396841429941c7265f92ec39808 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Mon, 21 Sep 2026 12:34:34 -0400 Subject: [PATCH] [MG_Remote, android-plugin] (P6): the device can run the split retrace, and only the app can spell the server's path Two small pieces, so an Android spawn arm is a flag rather than a project. THE RUNNER GAINS --transport. It already had a generic env passthrough - one intent extra carries `K=V;K=V` all the way to setenv before the library is dlopened - so the transport itself costs one entry and no new plumbing in the five files between here and the device. monolith passes NOTHING rather than MOBILEGL_TRANSPORT=monolith, so the default arm's environment stays byte-identical to what it was before this option existed: a control that changed when the option landed would not be one. THE SERVER PATH CANNOT COME FROM THE HOST. Every other knob can, but this one names a file that must be EXEC-ABLE, and on Android there is exactly one such place: an APK's lib//. W^X has forbidden the app's own data dirs since API 29, and the packager only puts a file in lib// if it is named lib*.so - which is why the server executable wears that name, and what P0 spike A proved on two devices. nativeLibraryDir contains an install-time hash, so no host-side script can spell the path; the process about to launch the server is the only one that can. TraceReplayActivity resolves it, and only when the caller actually asked for spawn. AN EXPLICIT MOBILEGL_IPC_SERVER_PATH STILL WINS. A caller that set one is pointing somewhere deliberately - at a pushed build, say - and silently replacing it would make that knob untestable on the one platform where it matters most. The APK must be built with -Pmobilegl.buildDisaggregated=ON or none of this is reachable: without it ConfigLoader has no transport parser, accepts the value, ignores it (CONTRACT-P5 rule 5), and the run is monolith under a name that says spawn. The --transport help says so where somebody will read it. --- .../plugin/trace/TraceReplayActivity.java | 39 +++++++++++++++++-- .../trace_replay/run_android_retrace_local.py | 24 +++++++++++- 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/android-plugin/app/src/trace/java/top/mobilegl/plugin/trace/TraceReplayActivity.java b/android-plugin/app/src/trace/java/top/mobilegl/plugin/trace/TraceReplayActivity.java index a3038f2c..512f4004 100644 --- a/android-plugin/app/src/trace/java/top/mobilegl/plugin/trace/TraceReplayActivity.java +++ b/android-plugin/app/src/trace/java/top/mobilegl/plugin/trace/TraceReplayActivity.java @@ -45,7 +45,10 @@ public final class TraceReplayActivity extends Activity { request = TraceReplayRequest.from( intent, getFilesDir(), - getString(top.mobilegl.plugin.R.string.mobilegl_default_backend) + getString(top.mobilegl.plugin.R.string.mobilegl_default_backend), + // P6: the ONE place that knows where an exec-able file lives. See + // resolveSpawnServerPath. + getApplicationInfo().nativeLibraryDir ); statusView = new TextView(this); statusView.setText("Waiting for render surface\n" + request.outputDir); @@ -354,7 +357,37 @@ public final class TraceReplayActivity extends Activity { this.envOverrides = envOverrides; } - static TraceReplayRequest from(Intent intent, File filesDir, String defaultBackend) { + // P6: MOBILEGL_TRANSPORT=spawn NEEDS AN ABSOLUTE PATH TO AN EXEC-ABLE FILE, and on + // Android there is exactly one such place. An APK's lib// is the only directory an + // untrusted_app may exec from - W^X has forbidden the app's own data dirs since API 29 - + // and the packager only puts a file there if it is named lib*.so, which is why the server + // executable wears that name (P0 spike A proved the arrangement on two devices). + // + // NOTHING OFF THE DEVICE CAN SPELL THAT PATH. nativeLibraryDir contains an + // install-time hash, so the host-side runner cannot pass MOBILEGL_IPC_SERVER_PATH the way + // it passes every other knob; the path has to be resolved HERE, by the process that is + // about to launch the server. Left unresolved, LaunchServer refuses BY NAME and the arm + // reds on "unresolvable image" - honest, but about the wrong thing. + // + // AN EXPLICIT VALUE WINS. A caller that set MOBILEGL_IPC_SERVER_PATH itself is pointing + // somewhere deliberately, and silently replacing it would make that knob untestable. + static String resolveSpawnServerPath(String envOverrides, String nativeLibraryDir) { + if (nativeLibraryDir == null || nativeLibraryDir.isEmpty()) { + return envOverrides; + } + if (!envOverrides.contains("MOBILEGL_TRANSPORT=spawn")) { + return envOverrides; + } + if (envOverrides.contains("MOBILEGL_IPC_SERVER_PATH=")) { + return envOverrides; + } + String serverPath = new File(nativeLibraryDir, "libMobileGLServer.so").getAbsolutePath(); + String entry = "MOBILEGL_IPC_SERVER_PATH=" + serverPath; + return envOverrides.isEmpty() ? entry : envOverrides + ";" + entry; + } + + static TraceReplayRequest from(Intent intent, File filesDir, String defaultBackend, + String nativeLibraryDir) { String outputDir = readString(intent, "output_dir", new File(filesDir, "trace-replay").getAbsolutePath()); String diffPath = readString(intent, "diff_path", ""); String benchmarkResultPath = @@ -389,7 +422,7 @@ public final class TraceReplayActivity extends Activity { intent.getIntExtra("benchmark_tail_frames", 200), intent.getBooleanExtra("benchmark_finish", true), benchmarkResultPath, - readString(intent, "mobilegl_env", "") + resolveSpawnServerPath(readString(intent, "mobilegl_env", ""), nativeLibraryDir) ); } diff --git a/tools/trace_replay/run_android_retrace_local.py b/tools/trace_replay/run_android_retrace_local.py index 9d48a919..46f54069 100644 --- a/tools/trace_replay/run_android_retrace_local.py +++ b/tools/trace_replay/run_android_retrace_local.py @@ -403,6 +403,18 @@ def parse_args(): help="Do not glFinish at every frame boundary, so frame times measure CPU submission " "only instead of GPU completion.", ) + parser.add_argument( + "--transport", + choices=("monolith", "inproc", "spawn"), + default="monolith", + help="MOBILEGL_TRANSPORT for the replay. `spawn` runs the server role in a SECOND PROCESS " + "on the device, launched out of the APK's lib// - which requires an APK built " + "with -Pmobilegl.buildDisaggregated=ON, or ConfigLoader has no parser, accepts the " + "value and ignores it (CONTRACT-P5 rule 5) and the run is monolith under a name that " + "says otherwise. MOBILEGL_IPC_SERVER_PATH is NOT passed from here: nativeLibraryDir " + "carries an install-time hash, so only the app can spell it, and " + "TraceReplayActivity.resolveSpawnServerPath does.", + ) parser.add_argument( "--benchmark-timeout-seconds", type=int, @@ -412,6 +424,16 @@ def parse_args(): return parser.parse_args() +def transport_env(args): + """The transport knob as env overrides, or nothing at all for monolith. + + An EMPTY list on monolith rather than MOBILEGL_TRANSPORT=monolith, so the default arm's + environment is byte-identical to what it was before this option existed - the control arm has + to stay a control. + """ + return [] if args.transport == "monolith" else [f"MOBILEGL_TRANSPORT={args.transport}"] + + def main(): args = parse_args() selected_backends = args.backend or list(BACKENDS) @@ -435,7 +457,7 @@ def main(): failures += run_benchmark_case(case, backend, args) continue print(f"=== Android retrace: {case['name']} / {backend} ===", flush=True) - rc = run_case(case, backend, env_overrides=args.env) + rc = run_case(case, backend, env_overrides=transport_env(args) + list(args.env)) try: render_summary() except Exception as error: