mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Tools] (P14): the profiler records the whole run and the analysis cuts the steady tail.
This commit is contained in:
@@ -17,6 +17,26 @@ sys.path.insert(0, os.environ.get("SIMPLEPERF_DIR", ""))
|
||||
from simpleperf_report_lib import ReportLib # noqa: E402
|
||||
|
||||
|
||||
def tail_start(data, symfs, tail_ms):
|
||||
"""Timestamp (ns) where the last `tail_ms` of the recording begins, or 0 for everything."""
|
||||
if not tail_ms:
|
||||
return 0
|
||||
lib = ReportLib()
|
||||
lib.SetRecordFile(data)
|
||||
lib.SetSymfs(symfs)
|
||||
# The end is the replay's last retraced call, not the recording's last sample: the app
|
||||
# tears its context down after the benchmark ends, and that must not land in the window.
|
||||
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 short(name):
|
||||
name = name.split("(")[0]
|
||||
return name.replace("MobileGL::", "").replace("MG_Backend::DirectVulkan::", "DV::")[-110:]
|
||||
@@ -30,10 +50,12 @@ def main():
|
||||
ap.add_argument("--depth", type=int, default=1)
|
||||
ap.add_argument("--thread", default=None)
|
||||
ap.add_argument("--top", type=int, default=40)
|
||||
ap.add_argument("--tail-ms", type=float, default=0.0, help="only the last N ms of the recording")
|
||||
ap.add_argument("--leaf", action="store_true",
|
||||
help="match <function> against the LEAF only and attribute to the nearest caller "
|
||||
"outside the allocator/libc/atomics (who called malloc?)")
|
||||
args = ap.parse_args()
|
||||
start = tail_start(args.data, args.symfs, args.tail_ms)
|
||||
lib = ReportLib()
|
||||
lib.SetRecordFile(args.data)
|
||||
lib.SetSymfs(args.symfs)
|
||||
@@ -44,7 +66,7 @@ def main():
|
||||
sample = lib.GetNextSample()
|
||||
if sample is None:
|
||||
break
|
||||
if args.thread and args.thread not in sample.thread_comm:
|
||||
if (args.thread and args.thread not in sample.thread_comm) or sample.time < start:
|
||||
continue
|
||||
total += 1
|
||||
# frames leaf first: the sample's own symbol, then callchain entries (callers)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"""Inclusive share of fixed components on one thread of a simpleperf profile, converted to
|
||||
milliseconds per frame with the thread's measured CPU ms/frame.
|
||||
|
||||
python components.py <perf.data> <binary_cache> <thread comm> [cpu_ms_per_frame]
|
||||
python components.py <perf.data> <binary_cache> <thread comm> [cpu_ms_per_frame] [--tail-ms=N]
|
||||
|
||||
A sample counts toward a component when any frame of its call chain matches the component's
|
||||
pattern (first match in COMPONENTS order wins for the 'exclusive' column, so the rows of that
|
||||
@@ -15,6 +15,26 @@ from collections import Counter
|
||||
sys.path.insert(0, os.environ.get("SIMPLEPERF_DIR", ""))
|
||||
from simpleperf_report_lib import ReportLib # noqa: E402
|
||||
|
||||
|
||||
def tail_start(data, symfs, tail_ms):
|
||||
"""Timestamp (ns) where the last `tail_ms` of the recording begins, or 0 for everything."""
|
||||
if not tail_ms:
|
||||
return 0
|
||||
lib = ReportLib()
|
||||
lib.SetRecordFile(data)
|
||||
lib.SetSymfs(symfs)
|
||||
# The end is the replay's last retraced call, not the recording's last sample: the app
|
||||
# tears its context down after the benchmark ends, and that must not land in the window.
|
||||
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)
|
||||
|
||||
COMPONENTS = [
|
||||
("trace parse (harness)", ("trace::Parser::", "BrotliFile", "trace::Call::~Call", "trace::File::")),
|
||||
("validate point", ("MGPipeValidateForVerb",)),
|
||||
@@ -29,8 +49,11 @@ COMPONENTS = [
|
||||
|
||||
|
||||
def main():
|
||||
data, symfs, comm = sys.argv[1], sys.argv[2], sys.argv[3]
|
||||
cpu = float(sys.argv[4]) if len(sys.argv) > 4 else None
|
||||
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 = args[0], args[1], args[2]
|
||||
cpu = float(args[3]) if len(args) > 3 else None
|
||||
start = tail_start(data, symfs, tail)
|
||||
lib = ReportLib()
|
||||
lib.SetRecordFile(data)
|
||||
lib.SetSymfs(symfs)
|
||||
@@ -41,7 +64,7 @@ def main():
|
||||
sample = lib.GetNextSample()
|
||||
if sample is None:
|
||||
break
|
||||
if comm not in sample.thread_comm:
|
||||
if comm not in sample.thread_comm or sample.time < start:
|
||||
continue
|
||||
total += 1
|
||||
frames = [lib.GetSymbolOfCurrentSample().symbol_name]
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
#!/usr/bin/env bash
|
||||
# One simpleperf profile of a matrix_p14 arm, recorded inside the steady window.
|
||||
# ARM=monolith BACKEND=DirectVulkan WL=rd12 DELAY=30 DUR=6 TAG=prof bash profile_p14.sh
|
||||
# DELAY is the seconds from launch to the start of recording (past the load frame), DUR the
|
||||
# recording length. Records every thread of the replay process (and of the server process for
|
||||
# spawn arms) with frame-pointer call graphs, then reports on the host against the unstripped
|
||||
# libraries in SYMDIR. Clocks are expected to be pinned by the caller (pin_clocks.sh).
|
||||
# One simpleperf profile of a matrix_p14 arm.
|
||||
# ARM=monolith BACKEND=DirectVulkan WL=rd12 TAG=prof SYMDIR=<unstripped libs> bash profile_p14.sh
|
||||
# Records the whole run (every thread of the app's processes, the spawn/tcp render server
|
||||
# included, via --app) with frame-pointer call graphs, stops when benchmark.json appears, and
|
||||
# builds a binary cache against the unstripped libraries in SYMDIR. The steady window is cut out
|
||||
# afterwards: components.py / callee_split.py take --tail-ms (the steady frames' wall span from
|
||||
# benchmark.json), so no launch-to-steady delay has to be guessed. Clocks are expected to be
|
||||
# pinned by the caller (pin_clocks.sh).
|
||||
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}"
|
||||
ARM="${ARM:-monolith}" BACKEND="${BACKEND:-DirectVulkan}" WL="${WL:-rd12}"
|
||||
DELAY="${DELAY:-30}" DUR="${DUR:-6}" TAG="${TAG:-prof}"
|
||||
ARM="${ARM:-monolith}" BACKEND="${BACKEND:-DirectVulkan}" WL="${WL:-rd12}" TAG="${TAG:-prof}"
|
||||
FREQ="${FREQ:-2000}"
|
||||
PKG_FEAT="${PKG_FEAT:-top.mobilegl.plugin.p14.trace}" PKG_DEV="${PKG_DEV:-top.mobilegl.plugin.p14dev.trace}"
|
||||
SYMDIR="${SYMDIR:?unstripped library directory}"
|
||||
NDK="${NDK:-/c/Users/yello/AppData/Local/Android/Sdk/ndk/27.3.13750724}"
|
||||
SP_HOST="$NDK/simpleperf/bin/windows/x86_64/simpleperf.exe"
|
||||
OUT="${PERF_WORK:-$REPO_ROOT/.trace-work/perf-p14}/$TAG/$WL-$BACKEND-$ARM"
|
||||
mkdir -p "$OUT"
|
||||
pkg=$([ "$ARM" = dev ] && echo "$PKG_DEV" || echo "$PKG_FEAT")
|
||||
@@ -31,21 +32,24 @@ case "$WL" in
|
||||
rd12) args="--el target_call 4660351 --ei width 854 --ei height 480" ;;
|
||||
esac
|
||||
adb -s "$SER" shell "am force-stop $PKG_FEAT; am force-stop $PKG_DEV; run-as $pkg sh -c 'rm -f $app/output/*.json'" >/dev/null 2>&1
|
||||
adb -s "$SER" shell "su -c 'pkill -INT simpleperf; rm -f /data/local/tmp/p14perf.data'" >/dev/null 2>&1
|
||||
# --app follows the package's processes and their new threads from the moment they exist.
|
||||
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
|
||||
sleep "$DELAY"
|
||||
pids=$(adb -s "$SER" shell "su -c 'echo \$(pidof $pkg) \$(pgrep -f $pkg.*libMobileGLServer.so)'" | tr -d '\r' | tr -s ' ' ',' | sed 's/^,//; s/,$//')
|
||||
echo "pids=$pids" | tee "$OUT/pids.txt"
|
||||
adb -s "$SER" shell "su -c 'simpleperf record -e cpu-clock -p $pids --call-graph fp -f 4000 --duration $DUR -o /data/local/tmp/p14perf.data'" >"$OUT/record.log" 2>&1
|
||||
adb -s "$SER" exec-out "run-as $pkg cat $app/output/benchmark.json" >"$OUT/benchmark.json" 2>/dev/null
|
||||
until adb -s "$SER" exec-out "run-as $pkg cat $app/output/benchmark.json" 2>/dev/null | grep -q '"fps"'; do sleep 2; done
|
||||
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
|
||||
done
|
||||
adb -s "$SER" shell "su -c 'pkill -INT simpleperf'" >/dev/null 2>&1
|
||||
wait "$sp_pid"
|
||||
adb -s "$SER" exec-out "run-as $pkg cat $app/output/benchmark.json" >"$OUT/benchmark.json" 2>/dev/null
|
||||
adb -s "$SER" shell "am force-stop $pkg" >/dev/null 2>&1
|
||||
adb -s "$SER" shell "su -c 'chmod 644 /data/local/tmp/p14perf.data'"
|
||||
adb -s "$SER" pull /data/local/tmp/p14perf.data "$(cygpath -w "$OUT/perf.data")" >/dev/null
|
||||
W_OUT=$(cygpath -w "$OUT"); W_SYM=$(cygpath -w "$SYMDIR")
|
||||
W_SYM=$(cygpath -w "$SYMDIR")
|
||||
# binary_cache/ mirrors the device paths: our unstripped libraries by build id, the rest pulled.
|
||||
(cd "$OUT" && ANDROID_SERIAL="$SER" python "$(cygpath -w "$NDK/simpleperf/binary_cache_builder.py")" -i perf.data -lib "$W_SYM" >binary_cache.log 2>&1)
|
||||
"$SP_HOST" report -i "$W_OUT\perf.data" --symfs "$W_OUT\binary_cache" --sort comm,tid --percent-limit 1 >"$OUT/threads.txt" 2>/dev/null
|
||||
"$SP_HOST" report -i "$W_OUT\perf.data" --symfs "$W_OUT\binary_cache" --sort comm,symbol --percent-limit 0.3 >"$OUT/self.txt" 2>/dev/null
|
||||
"$SP_HOST" report -i "$W_OUT\perf.data" --symfs "$W_OUT\binary_cache" --children --sort comm,symbol --percent-limit 1.5 >"$OUT/children.txt" 2>/dev/null
|
||||
echo "wrote $OUT"; head -30 "$OUT/threads.txt"
|
||||
tail -1 "$OUT/record.log"
|
||||
echo "wrote $OUT (steady tail: $(python -c "import json,sys; f=json.load(open(sys.argv[1]))['frameTimesMs']; print(round(sum(f[4:]),1))" "$(cygpath -w "$OUT/benchmark.json")") ms)"
|
||||
|
||||
Reference in New Issue
Block a user