[Tools] (P14): benchmark runs default to no per-frame glFinish, plus FCL and layer-split helpers.

This commit is contained in:
2026-10-07 12:15:12 -04:00
parent 4f4297f347
commit d07ec0255b
4 changed files with 177 additions and 2 deletions
+83
View File
@@ -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=<path> feat=<path>" 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
+92
View File
@@ -0,0 +1,92 @@
#!/usr/bin/env python3
"""Split one thread of a simpleperf profile by LAYER, in ms per frame.
python layers.py <perf.data> <binary_cache> <thread comm> <cpu_ms_per_frame> [--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()
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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