#!/usr/bin/env bash # MobileGL - tools/device_bench/disagg/matrix_p14.sh # Copyright (c) 2025-2026 MobileGL-Dev # Licensed under the GNU Lesser General Public License v3.0: # https://www.gnu.org/licenses/gpl-3.0.txt # https://www.gnu.org/licenses/lgpl-3.0.txt # SPDX-License-Identifier: LGPL-3.0-only # End of Source File Header # P14 perf matrix: dev (pull monolith, baseline APK) vs feat (monolith / inproc / spawn+shm / # spawn+tcp-localhost) x {Espryt, Magma} x {openra, rd12}, on a rooted device with pinned clocks. # # SER=HA27Q3LQ PKG_FEAT=top.mobilegl.plugin.p14.trace PKG_DEV=top.mobilegl.plugin.p14dev.trace \ # ARMS="dev monolith inproc shm tcp" BACKENDS="DirectGLES DirectVulkan" WLS="openra rd12" REPS=3 \ # TAG=base bash matrix_p14.sh prep|run|ssim # # Per run: force-stop, clear outputs, start cpusampler (per-thread schedstat + kgsl gpubusy, 10 ms) # watching benchmark.json, launch the replay, wait, pull result/benchmark/logcat/sampler CSV, and # verify the arm and backend markers. Rounds interleave arms (and backends) so drift spreads evenly. # Results: $PERF_WORK////-r/ ; reduce with p14_report.py. set -u export MSYS_NO_PATHCONV=1 MSYS2_ARG_CONV_EXCL='*' SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" SER="${SER:-HA27Q3LQ}" PKG_FEAT="${PKG_FEAT:-top.mobilegl.plugin.p14.trace}" PKG_DEV="${PKG_DEV:-top.mobilegl.plugin.p14dev.trace}" ARMS=(${ARMS:-dev monolith inproc shm tcp}) BACKENDS=(${BACKENDS:-DirectGLES DirectVulkan}) WLS=(${WLS:-openra rd12}) REPS="${REPS:-3}" TAG="${TAG:-base}" FIXTURES="${FIXTURES:-$REPO_ROOT/tools/trace_replay/fixtures}" ROOT="${PERF_WORK:-$REPO_ROOT/.trace-work/perf-p14}/$TAG" TIMEOUT="${TIMEOUT:-900}" EXTRA_ENV="${EXTRA_ENV:-}" COOL_C="${COOL_C:-38}" # wait until battery temperature is below this before a run LOG="$ROOT/matrix.log" SAMPLER=/data/local/tmp/cpusampler shq() { adb -s "$SER" shell "$1"; } log() { local line; line="[$(date '+%F %T')] $*"; mkdir -p "$ROOT"; echo "$line" >>"$LOG"; echo "$line"; } # An arm is [@]: the tag picks another installed build of the trace APK # (top.mobilegl.plugin..trace), so two builds can be interleaved in one matrix. arm_kind() { echo "${1%@*}"; } pkg_of() { case "$1" in *@*) echo "top.mobilegl.plugin.${1#*@}.trace" ;; dev) echo "$PKG_DEV" ;; *) echo "$PKG_FEAT" ;; esac } app_of() { echo "/data/user/0/$1/files/trace-replay"; } wl_args() { # trace golden w h target crop case "$1" in openra) echo "openra openra.0000031249.png 640 480 31249 1 1 638 478" ;; rd12) echo "rd12 minecraft-1.21.4-rd12-odinlite-in-world.0004660351.png 854 480 4660351 0 0 0 0" ;; # P15: GPU-bound shaderpack scene (a single-frame trim: ssim only, not a benchmark). bsl) echo "bsl minecraft-1.21.4-fabric-iris-bsl-in-world.0000110725.png 854 480 110725 0 0 0 0" ;; # P15 S0b: FCL's MC 1.21.5 vanilla scene, a 252-frame window (benchmark + ssim on the final frame). fcl) echo "fcl minecraft-1.21.5-vanilla-fcl-in-world.0005998912.png 854 480 5998912 0 0 0 0" ;; esac } arm_env() { case "$(arm_kind "$1")" in dev|monolith) echo "" ;; inproc) echo "MOBILEGL_TRANSPORT=inproc;MOBILEGL_IPC_RUN_AHEAD=1" ;; shm) echo "MOBILEGL_TRANSPORT=spawn;MOBILEGL_IPC_CONTROL=fork;MOBILEGL_IPC_DATA=auto;MOBILEGL_IPC_RUN_AHEAD=1" ;; tcp) echo "MOBILEGL_TRANSPORT=spawn;MOBILEGL_IPC_CONTROL=tcp://127.0.0.1:40613;MOBILEGL_IPC_DATA=stream;MOBILEGL_IPC_RUN_AHEAD=1" ;; esac } temp_dc() { adb -s "$SER" shell dumpsys battery 2>/dev/null | tr -d '\r' | sed -n 's/.*temperature: \([0-9]*\).*/\1/p' | head -1; } cool_down() { local t; t=$(temp_dc); local waited=0 while [ -n "$t" ] && [ "$t" -ge $((COOL_C * 10)) ] && [ "$waited" -lt 900 ]; do sleep 15; waited=$((waited + 15)); t=$(temp_dc) done [ "$waited" -gt 0 ] && log "cooled ${waited}s to ${t}" } prep_pkg() { local pkg="$1" app; app=$(app_of "$pkg") local tmp="$ROOT/fixtures"; mkdir -p "$tmp" for wl in ${PREP_WLS:-openra rd12}; do local tgz case $wl in openra) tgz="$FIXTURES/openra.tgz" ;; rd12) tgz="$FIXTURES/minecraft-1.21.4-rd12-odinlite-in-world.tgz" ;; bsl) tgz="$FIXTURES/minecraft-1.21.4-fabric-iris-bsl-in-world.tgz" ;; fcl) tgz="${FCL_FIXTURES:-$FIXTURES}/minecraft-1.21.5-vanilla-fcl-in-world.tgz" ;; esac set -- $(wl_args $wl) local golden="$FIXTURES/$2"; [ "$wl" = fcl ] && golden="${FCL_FIXTURES:-$FIXTURES}/$2" if [ ! -f "$tmp/$wl.trace" ]; then rm -rf "$tmp/x"; mkdir -p "$tmp/x"; tar xzf "$tgz" -C "$tmp/x" || exit 1 mv "$(find "$tmp/x" -name '*.trace' | head -1)" "$tmp/$wl.trace"; rm -rf "$tmp/x" fi adb -s "$SER" push "$(cygpath -w "$tmp/$wl.trace")" /data/local/tmp/p14-$wl.trace >/dev/null || exit 1 adb -s "$SER" push "$(cygpath -w "$golden")" /data/local/tmp/p14-$wl.png >/dev/null || exit 1 shq "am force-stop $pkg; run-as $pkg sh -c 'mkdir -p $app/input-$wl $app/output'" shq "cat /data/local/tmp/p14-$wl.trace | run-as $pkg sh -c 'cat > $app/input-$wl/trace.trace'" shq "cat /data/local/tmp/p14-$wl.png | run-as $pkg sh -c 'cat > $app/input-$wl/golden.png'" local size host; size=$(shq "run-as $pkg sh -c 'wc -c < $app/input-$wl/trace.trace'" | tr -d '\r ') host=$(wc -c <"$tmp/$wl.trace") [ "$size" = "$host" ] || { log "FATAL: $pkg $wl size $size != $host"; exit 1; } log "prep $pkg $wl ok ($size bytes)" done shq "rm -f /data/local/tmp/p14-*.trace /data/local/tmp/p14-*.png; chmod 755 $SAMPLER" } verify_run() { local dir="$1" arm="$2" backend="$3" all="$1/markers.txt" v="$1/verify.txt" grep -aE "Config: MOBILEGL_TRANSPORT|spawn ARMED|control=tcp data=stream|run-ahead ARMED|running lockstep|DISARMED|Active backend type|debug.mobilegl.backend|Fatal\{" \ "$dir/logcat.txt" "$dir"/mgl.*.log 2>/dev/null >"$all" { echo "arm=$arm backend=$backend" grep -aq "Active backend type set to $backend" "$all" && echo "BACKEND=OK" || echo "BACKEND=MISSING" case "$(arm_kind "$arm")" in dev|monolith) grep -aqE "Config: MOBILEGL_TRANSPORT=(inproc|spawn)" "$all" && echo "ARM=UNEXPECTED_SPLIT" || echo "ARM=OK(monolith)" ;; inproc) grep -aq "Config: MOBILEGL_TRANSPORT=inproc - the MGPipe record stream" "$all" && echo "ARM=OK(inproc)" || echo "ARM=MISSING" ;; shm) grep -aq "spawn ARMED - the server role runs in pid" "$all" && echo "ARM=OK(spawn)" || echo "ARM=MISSING" ;; tcp) grep -aq "control=tcp data=stream server=" "$all" && echo "ARM=OK(tcp)" || echo "ARM=MISSING" ;; esac case "$(arm_kind "$arm")" in inproc|shm|tcp) grep -aq "run-ahead ARMED" "$all" && echo "RUN_AHEAD=OK" || echo "RUN_AHEAD=MISSING" ;; esac grep -aq "running lockstep" "$all" && echo "LOCKSTEP=PRESENT" echo "FATAL_LINES=$(grep -ac 'Fatal{' "$all")" } >"$v" } # run_one run_one() { local wl="$1" backend="$2" arm="$3" rep="$4" mode="$5" local pkg app dir; pkg=$(pkg_of "$arm"); app=$(app_of "$pkg") dir="$ROOT/$wl/$backend/$arm-r$rep"; rm -rf "$dir"; mkdir -p "$dir" set -- $(wl_args "$wl"); local w="$3" h="$4" target="$5" cx="$6" cy="$7" cw="$8" ch="$9" cool_down local t0 epoch0 status=""; t0=$(temp_dc); epoch0=$(date +%s) log "BEGIN $TAG $wl/$backend/$arm r$rep $mode temp=$t0" shq "am force-stop $PKG_FEAT; am force-stop $PKG_DEV; am force-stop $pkg" >/dev/null 2>&1 shq "run-as $pkg sh -c 'rm -f $app/output/*.json $app/output/*.log $app/output/*.png /data/user/0/$pkg/files/mgl.*.log'" >/dev/null 2>&1 shq "su -c 'pkill -f cpusampler; rm -f /data/local/tmp/p14cpu.csv'" >/dev/null 2>&1 adb -s "$SER" logcat -c >/dev/null 2>&1 if [ "$(arm_kind "$arm")" = tcp ]; then python "$(cygpath -w "$REPO_ROOT/tools/trace_replay/tcp_device_server.py")" start --serial "$SER" --package "$pkg" \ --backend "$backend" --listen tcp://127.0.0.1:40613 ${EXTRA_SERVER_ENV:+--env "$EXTRA_SERVER_ENV"} >"$dir/server-start.log" 2>&1 || { echo server_start_failed >"$dir/STATUS"; log "FAIL tcp server"; return 1; } local waited=0 until adb -s "$SER" logcat -d 2>/dev/null | grep -q "listening on tcp://127.0.0.1:40613"; do sleep 1; waited=$((waited + 1)); [ $waited -ge 30 ] && { echo server_not_listening >"$dir/STATUS"; log "FAIL tcp listen"; return 1; } done fi local passflag="" [ "$mode" = benchmark ] && passflag="--ez benchmark true --ez benchmark_finish ${FINISH:-false} --es benchmark_result_path $app/output/benchmark.json" local env; env="$(arm_env "$arm")"; [ -n "$EXTRA_ENV" ] && env="${env:+$env;}$EXTRA_ENV" if [ "$mode" = benchmark ]; then # P15 validity rule: frequency/thermal sampler for the whole run (devstate.sh judges it). tr -d '\r' < "$SCRIPT_DIR/freq_sampler.sh" > "$dir/freq_sampler.lf.sh"; adb -s "$SER" push "$(cygpath -w "$dir/freq_sampler.lf.sh")" /data/local/tmp/p15-freq_sampler.sh >/dev/null 2>&1 shq "su -c 'chmod 755 /data/local/tmp/p15-freq_sampler.sh; pkill -f p15-freq_sampler.sh; rm -f /data/local/tmp/p15freq.txt; (nohup sh /data/local/tmp/p15-freq_sampler.sh /data/local/tmp/p15freq.txt 2 >/dev/null 2>&1 &)'" >/dev/null 2>&1 shq "su -c 'nohup taskset 01 $SAMPLER $pkg 10 $app/output/benchmark.json /data/local/tmp/p14cpu.csv $TIMEOUT >/dev/null 2>&1 &'" >/dev/null 2>&1 fi local am_out am_out=$(shq "am start -a top.mobilegl.plugin.TRACE_REPLAY -n $pkg/top.mobilegl.plugin.trace.TraceReplayActivity --es trace_path $app/input-$wl/trace.trace --es golden_path $app/input-$wl/golden.png --es output_dir $app/output --es backend $backend --el target_call $target --ei width $w --ei height $h --es ssim_threshold 0.99 --ez use_pbuffer true $passflag --ei crop_x $cx --ei crop_y $cy --ei crop_width $cw --ei crop_height $ch --es mobilegl_env '$env'" 2>&1) printf '%s\n' "$am_out" >"$dir/am-start.txt" local deadline=$((epoch0 + TIMEOUT)) got=0 while [ "$(date +%s)" -lt "$deadline" ]; do if adb -s "$SER" exec-out "run-as $pkg cat $app/output/result.json" 2>/dev/null | head -c 400 | grep -q '"passed"'; then got=1; break; fi if [ $(( $(date +%s) - epoch0 )) -gt 25 ] && [ -z "$(shq "pidof $pkg" 2>/dev/null | tr -d '\r ')" ]; then status=process_died; break; fi sleep 2 done [ $got -eq 1 ] && { status=finished; sleep 2; } || [ -n "$status" ] || status=timeout local t1; t1=$(temp_dc) adb -s "$SER" logcat -d >"$dir/logcat.txt" 2>/dev/null for f in result.json benchmark.json; do adb -s "$SER" exec-out "run-as $pkg cat $app/output/$f" >"$dir/$f" 2>/dev/null { [ -s "$dir/$f" ] && ! head -c 200 "$dir/$f" | grep -q "No such file"; } || rm -f "$dir/$f" done if [ "$(arm_kind "$arm")" = tcp ]; then for f in mgl.client.log mgl.server.log; do adb -s "$SER" exec-out "run-as $pkg cat /data/user/0/$pkg/files/$f" >"$dir/$f" 2>/dev/null; done python "$(cygpath -w "$REPO_ROOT/tools/trace_replay/tcp_device_server.py")" stop --serial "$SER" --package "$pkg" >/dev/null 2>&1 fi if [ "$mode" = benchmark ]; then sleep 1 adb -s "$SER" exec-out "su -c 'cat /data/local/tmp/p14cpu.csv'" >"$dir/cpu.csv" 2>/dev/null shq "su -c 'pkill -f cpusampler'" >/dev/null 2>&1 shq "su -c 'pkill -f p15-freq_sampler.sh'" >/dev/null 2>&1 adb -s "$SER" exec-out "su -c 'cat /data/local/tmp/p15freq.txt'" >"$dir/freq.txt" 2>/dev/null SER=$SER bash "$SCRIPT_DIR/pin_clocks.sh" show >"$dir/pin.txt" 2>&1 SER=$SER bash "$SCRIPT_DIR/devstate.sh" "$dir" trace >/dev/null 2>&1 fi shq "am force-stop $pkg" >/dev/null 2>&1 local passed="none"; [ -f "$dir/result.json" ] && passed=$(sed -n 's/.*"passed": *\([a-z]*\).*/\1/p' "$dir/result.json" | head -1) echo "$status" >"$dir/STATUS" echo "tag=$TAG wl=$wl backend=$backend arm=$arm rep=$rep mode=$mode status=$status passed=$passed dur=$(( $(date +%s) - epoch0 )) temp0=$t0 temp1=$t1" >"$dir/meta.txt" verify_run "$dir" "$arm" "$backend" local fps="-"; [ -f "$dir/benchmark.json" ] && fps=$(sed -n 's/.*"fps": *\([0-9.]*\).*/\1/p' "$dir/benchmark.json" | head -1) log "END $TAG $wl/$backend/$arm r$rep $mode status=$status passed=$passed fps=$fps temp=$t1 :: $(tr '\n' ' ' <"$dir/verify.txt") $(cat "$dir/state.txt" 2>/dev/null)" } main() { log "=== matrix_p14 $1 tag=$TAG arms=${ARMS[*]} backends=${BACKENDS[*]} wls=${WLS[*]} reps=$REPS ===" shq "input keyevent KEYCODE_WAKEUP; svc power stayon usb" >/dev/null 2>&1 case "$1" in prep) for p in ${PREP_PKGS:-$PKG_FEAT $PKG_DEV}; do prep_pkg "$p"; done ;; run) for wl in "${WLS[@]}"; do for ((r = 1; r <= REPS; r++)); do for backend in "${BACKENDS[@]}"; do for arm in "${ARMS[@]}"; do run_one "$wl" "$backend" "$arm" "$r" benchmark || true; done done done done ;; ssim) for wl in "${WLS[@]}"; do for backend in "${BACKENDS[@]}"; do for arm in "${ARMS[@]}"; do run_one "$wl" "$backend" "$arm" ssim ssim || true done; done; done ;; esac log "=== done ===" } main "${1:-run}"