From d07ec0255b2764f4451402989405afe65157b36b Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Wed, 7 Oct 2026 12:15:12 -0400 Subject: [PATCH] [Tools] (P14): benchmark runs default to no per-frame glFinish, plus FCL and layer-split helpers. --- tools/device_bench/disagg/fcl_p14.sh | 83 +++++++++++++++++++++ tools/device_bench/disagg/layers.py | 92 ++++++++++++++++++++++++ tools/device_bench/disagg/matrix_p14.sh | 2 +- tools/device_bench/disagg/profile_p14.sh | 2 +- 4 files changed, 177 insertions(+), 2 deletions(-) create mode 100644 tools/device_bench/disagg/fcl_p14.sh create mode 100644 tools/device_bench/disagg/layers.py diff --git a/tools/device_bench/disagg/fcl_p14.sh b/tools/device_bench/disagg/fcl_p14.sh new file mode 100644 index 00000000..b23666b0 --- /dev/null +++ b/tools/device_bench/disagg/fcl_p14.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env bash +# FCL Minecraft in-world fps for P14: swaps FCL's built-in libMobileGL.so (rooted device), picks +# the backend through FCL's own renderer id and the transport through debug.mobilegl.transport, +# launches FCL (it auto-enters the last world), and reads MobileGL's opt-in fps log +# (debug.mobilegl.fps_log=1, one line about every 2 s) over a steady window. +# +# SER=HA27Q3LQ LIBS="dev= feat=" CONFIGS="dev:DirectGLES feat:DirectGLES:inproc ..." \ +# REPS=2 WARMUP=45 WINDOW=30 bash fcl_p14.sh run|restore +# +# The original library is saved once to /data/local/tmp/p14-fcl-orig.so and put back by +# `restore` (md5-checked), along with config.json and the two properties. Clocks are the +# caller's (pin_clocks.sh). FCL can die by itself at start-up (DEBTS.md, SIGRTMIN+2): a run with +# no fps lines is retried once. +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}" +FCL="${FCL:-com.tungsten.fcl.mgdebug.debug}" +REPS="${REPS:-2}" WARMUP="${WARMUP:-45}" WINDOW="${WINDOW:-30}" +OUT="${PERF_WORK:-$REPO_ROOT/.trace-work/perf-p14}/${TAG:-fcl}" +ESPRYT_ID=5e273ee2-baca-4c81-8e48-b63feefb9ba8 +MAGMA_ID=2be0dc10-1eef-4ce2-b512-b266dd33fd9e +mkdir -p "$OUT" +su_sh() { adb -s "$SER" shell "su -c '$1'"; } +libdir() { su_sh "dirname /data/app/*/$FCL-*/lib/arm64/libMobileGL.so" | tr -d '\r' | head -1; } +CFG=/data/data/$FCL/files/config.json + +save_original() { + local dir; dir=$(libdir) + # The renderer switch, as a device-side script: sed's own quotes cannot survive two shells. + printf '%s\n' 'sed -i "s/\"renderer\": \"[^\"]*\"/\"renderer\": \"$1\"/" "$2"' >"$OUT/p14-fcl-set.sh" + adb -s "$SER" push "$(cygpath -w "$OUT/p14-fcl-set.sh")" /data/local/tmp/p14-fcl-set.sh >/dev/null + su_sh "test -f /data/local/tmp/p14-fcl-orig.so || cp $dir/libMobileGL.so /data/local/tmp/p14-fcl-orig.so; test -f /data/local/tmp/p14-fcl-config.json || cp $CFG /data/local/tmp/p14-fcl-config.json" +} + +install_lib() { # $1 host path + local dir; dir=$(libdir) + adb -s "$SER" push "$(cygpath -w "$1")" /data/local/tmp/p14-fcl-lib.so >/dev/null + # cat into the existing file keeps its owner, mode and SELinux label. + su_sh "cat /data/local/tmp/p14-fcl-lib.so > $dir/libMobileGL.so; rm -f /data/local/tmp/p14-fcl-lib.so" +} + +run_one() { # name backend transport rep + local name="$1" backend="$2" transport="$3" rep="$4" dir="$OUT/$1-$2-$3-r$4" + mkdir -p "$dir" + local id=$ESPRYT_ID; [ "$backend" = DirectVulkan ] && id=$MAGMA_ID + su_sh "am force-stop $FCL; sh /data/local/tmp/p14-fcl-set.sh $id $CFG; setprop debug.mobilegl.fps_log 1; setprop debug.mobilegl.transport $transport" + adb -s "$SER" logcat -c + local t0; t0=$(adb -s "$SER" shell dumpsys battery | tr -d '\r' | sed -n 's/.*temperature: \([0-9]*\).*/\1/p') + su_sh "input keyevent KEYCODE_WAKEUP; monkey -p $FCL -c android.intent.category.LAUNCHER 1" >/dev/null 2>&1 + sleep "$WARMUP" + local pid; pid=$(adb -s "$SER" shell pidof $FCL | tr -d '\r' | awk '{print $NF}') + adb -s "$SER" shell "su -c 'for t in /proc/$pid/task/*; do echo \$(cat \$t/comm) \$(cut -d\" \" -f1 \$t/schedstat); done'" >"$dir/cpu0.txt" 2>/dev/null + sleep "$WINDOW" + adb -s "$SER" shell "su -c 'for t in /proc/$pid/task/*; do echo \$(cat \$t/comm) \$(cut -d\" \" -f1 \$t/schedstat); done'" >"$dir/cpu1.txt" 2>/dev/null + adb -s "$SER" logcat -d >"$dir/logcat.txt" + su_sh "am force-stop $FCL" + local t1; t1=$(adb -s "$SER" shell dumpsys battery | tr -d '\r' | sed -n 's/.*temperature: \([0-9]*\).*/\1/p') + local lines; lines=$(grep -a "MobileGL fps:" "$dir/logcat.txt" | wc -l) + echo "name=$name backend=$backend transport=$transport rep=$rep pid=$pid temp0=$t0 temp1=$t1 fpslines=$lines" >"$dir/meta.txt" + grep -aE "Active backend type|debug.mobilegl.transport|MOBILEGL_TRANSPORT=inproc|run-ahead ARMED" "$dir/logcat.txt" | head -5 >"$dir/markers.txt" + echo "$(cat "$dir/meta.txt") :: $(grep -a 'MobileGL fps:' "$dir/logcat.txt" | tail -3 | sed 's/.*MobileGL fps: //' | tr '\n' '|')" + [ "$lines" -gt 0 ] +} + +case "${1:-run}" in + run) + save_original + for ((r = 1; r <= REPS; r++)); do + for cfg in $CONFIGS; do + IFS=: read -r name backend transport <<<"$cfg" + transport="${transport:-monolith}" + lib=$(echo " $LIBS " | sed -n "s/.* $name=\([^ ]*\) .*/\1/p") + install_lib "$lib" + run_one "$name" "$backend" "$transport" "$r" || { sleep 10; run_one "$name" "$backend" "$transport" "$r" || true; } + sleep "${COOL:-20}" + done + done ;; + restore) + dir=$(libdir) + su_sh "am force-stop $FCL; cat /data/local/tmp/p14-fcl-orig.so > $dir/libMobileGL.so && md5sum $dir/libMobileGL.so; cat /data/local/tmp/p14-fcl-config.json > $CFG; setprop debug.mobilegl.fps_log ''; setprop debug.mobilegl.transport ''" ;; +esac diff --git a/tools/device_bench/disagg/layers.py b/tools/device_bench/disagg/layers.py new file mode 100644 index 00000000..b66a4a80 --- /dev/null +++ b/tools/device_bench/disagg/layers.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +"""Split one thread of a simpleperf profile by LAYER, in ms per frame. + + python layers.py [--tail-ms=N] + +Each sample goes to the layer of the first frame, walking from the leaf toward the root, that +belongs to one: the GPU driver (vendor GLES/Vulkan libraries, kgsl), the backend (MG_Backend), +the applier (MGPipeApply*, the record sink, the wire decoder), the client half (the frontend, +the validate point and its emitters, the verb port, the wire encoder), or the replay harness +(apitrace parsing and dispatch). Library code with no layer of its own (malloc, memcpy, atomics) +lands in the layer that called it. Kernel frames count as the driver only below a driver frame. +""" +import os +import sys +from collections import Counter + +sys.path.insert(0, os.environ.get("SIMPLEPERF_DIR", "")) +from simpleperf_report_lib import ReportLib # noqa: E402 + +LAYERS = ["driver", "backend", "applier", "client half", "harness", "other"] + + +def layer_of(symbol, dso): + if any(k in dso for k in ("adreno", "libGLESv2", "libEGL_", "vulkan.", "libgsl", "libllvm-glnext", "msm_kgsl")): + return "driver" + if "MG_Backend::" in symbol: + return "backend" + if ("MGPipeApply" in symbol or "RecordVerbSink" in symbol or "PipeWireDecoder" in symbol + or "PipeApplier::" in symbol or "ServerLoop::" in symbol): + return "applier" + if any(k in symbol for k in ("MG_Pipe::", "MG_Impl::", "MG_Record::", "MG_State::", "MG_Remote::Client", + "PipeWireEncoder", "MG_Util::")) or "libMobileGL" in dso and symbol.startswith("gl"): + return "client half" + if any(k in symbol for k in ("trace::", "retrace", "Brotli", "mobilegl_apitrace", "mobilegl_trace")): + return "harness" + return None + + +def tail_start(data, symfs, tail_ms): + if not tail_ms: + return 0 + lib = ReportLib() + lib.SetRecordFile(data) + lib.SetSymfs(symfs) + last = 0 + while True: + sample = lib.GetNextSample() + if sample is None: + break + chain = lib.GetCallChainOfCurrentSample() + if any("retrace::retraceCall" in chain.entries[i].symbol.symbol_name for i in range(chain.nr)): + last = max(last, sample.time) + return last - int(tail_ms * 1e6) + + +def main(): + args = [a for a in sys.argv[1:] if not a.startswith("--tail-ms=")] + tail = next((float(a.split("=", 1)[1]) for a in sys.argv[1:] if a.startswith("--tail-ms=")), 0.0) + data, symfs, comm, cpu = args[0], args[1], args[2], float(args[3]) + start = tail_start(data, symfs, tail) + lib = ReportLib() + lib.SetRecordFile(data) + lib.SetSymfs(symfs) + counts = Counter() + total = 0 + while True: + sample = lib.GetNextSample() + if sample is None: + break + if comm not in sample.thread_comm or sample.time < start: + continue + total += 1 + leaf = lib.GetSymbolOfCurrentSample() + frames = [(leaf.symbol_name, leaf.dso_name)] + chain = lib.GetCallChainOfCurrentSample() + frames += [(chain.entries[i].symbol.symbol_name, chain.entries[i].symbol.dso_name) for i in range(chain.nr)] + found = None + for symbol, dso in frames: + if "kallsyms" in dso: + continue + found = layer_of(symbol, dso) + if found: + break + counts[found or "other"] += 1 + print(f"{comm}: {total} samples, {cpu:.2f} ms/frame") + for name in LAYERS: + share = counts[name] / max(1, total) + print(f" {name:12} {100 * share:5.1f}% {cpu * share:6.2f} ms/frame") + + +if __name__ == "__main__": + main() diff --git a/tools/device_bench/disagg/matrix_p14.sh b/tools/device_bench/disagg/matrix_p14.sh index 900c2641..0b4a20a2 100644 --- a/tools/device_bench/disagg/matrix_p14.sh +++ b/tools/device_bench/disagg/matrix_p14.sh @@ -136,7 +136,7 @@ run_one() { done fi local passflag="" - [ "$mode" = benchmark ] && passflag="--ez benchmark true --es benchmark_result_path $app/output/benchmark.json" + [ "$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 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 diff --git a/tools/device_bench/disagg/profile_p14.sh b/tools/device_bench/disagg/profile_p14.sh index acf52a08..c0fad28e 100644 --- a/tools/device_bench/disagg/profile_p14.sh +++ b/tools/device_bench/disagg/profile_p14.sh @@ -37,7 +37,7 @@ adb -s "$SER" shell "su -c 'pkill -INT simpleperf; rm -f /data/local/tmp/p14perf adb -s "$SER" shell "su -c 'simpleperf record --app $pkg -e cpu-clock --call-graph fp -f $FREQ -o /data/local/tmp/p14perf.data'" >"$OUT/record.log" 2>&1 & sp_pid=$! sleep 2 -adb -s "$SER" shell "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 $args --ez use_pbuffer true --ez benchmark true --es benchmark_result_path $app/output/benchmark.json --es mobilegl_env '$env'" >/dev/null +adb -s "$SER" shell "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 $args --ez use_pbuffer true --ez benchmark true --ez benchmark_finish ${FINISH:-false} --es benchmark_result_path $app/output/benchmark.json --es mobilegl_env '$env'" >/dev/null waited=0 until adb -s "$SER" exec-out "run-as $pkg cat $app/output/benchmark.json" 2>/dev/null | grep -q '"fps"'; do sleep 1; waited=$((waited + 1)); [ "$waited" -ge 600 ] && break