mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Tools] (P14): sampler sees server processes and GPU busy every tick, arms can name a second build, and a simpleperf profile helper.
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Split one function's inclusive samples by its direct callees (and by malloc/atomic leaves).
|
||||
|
||||
python callee_split.py <perf.data> <binary_cache> <function substring> [--depth 1] [--thread NAME]
|
||||
|
||||
Uses the NDK's simpleperf_report_lib (set SIMPLEPERF_DIR to <ndk>/simpleperf). For every sample
|
||||
whose call chain contains a frame matching <function substring>, attributes the sample to the
|
||||
frame directly below it (its callee), or to "<self>" when it is the leaf. Prints the percentage
|
||||
of all samples of the selected threads.
|
||||
"""
|
||||
import argparse
|
||||
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
|
||||
|
||||
|
||||
def short(name):
|
||||
name = name.split("(")[0]
|
||||
return name.replace("MobileGL::", "").replace("MG_Backend::DirectVulkan::", "DV::")[-110:]
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("data")
|
||||
ap.add_argument("symfs")
|
||||
ap.add_argument("function")
|
||||
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("--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()
|
||||
lib = ReportLib()
|
||||
lib.SetRecordFile(args.data)
|
||||
lib.SetSymfs(args.symfs)
|
||||
total = 0
|
||||
hit = 0
|
||||
callees = Counter()
|
||||
while True:
|
||||
sample = lib.GetNextSample()
|
||||
if sample is None:
|
||||
break
|
||||
if args.thread and args.thread not in sample.thread_comm:
|
||||
continue
|
||||
total += 1
|
||||
# frames leaf first: the sample's own symbol, then callchain entries (callers)
|
||||
frames = [lib.GetSymbolOfCurrentSample().symbol_name]
|
||||
chain = lib.GetCallChainOfCurrentSample()
|
||||
for i in range(chain.nr):
|
||||
frames.append(chain.entries[i].symbol.symbol_name)
|
||||
if args.leaf:
|
||||
if args.function not in frames[0]:
|
||||
continue
|
||||
hit += 1
|
||||
noise = ("scudo", "malloc", "free", "operator new", "operator delete", "__aarch64_", "@plt",
|
||||
"memmove", "memcpy", "memset", "__emutls", "pthread_", "shared_count", "__release_shared",
|
||||
"!!!", "std::__ndk1::")
|
||||
owner = next((f for f in frames[1:] if not any(n in f for n in noise)), "<none>")
|
||||
callees[short(owner)] += 1
|
||||
continue
|
||||
# find the outermost (closest to root) matching frame so recursion counts once
|
||||
idx = None
|
||||
for i in range(len(frames) - 1, -1, -1):
|
||||
if args.function in frames[i]:
|
||||
idx = i
|
||||
break
|
||||
if idx is None:
|
||||
continue
|
||||
hit += 1
|
||||
if idx == 0:
|
||||
callees["<self>"] += 1
|
||||
continue
|
||||
key = " > ".join(short(frames[j]) for j in range(idx - 1, max(idx - 1 - args.depth, -1), -1))
|
||||
callees[key] += 1
|
||||
print(f"samples={total} in '{args.function}'={hit} ({100.0 * hit / max(1, total):.1f}%)")
|
||||
for key, n in callees.most_common(args.top):
|
||||
print(f"{100.0 * n / max(1, total):6.2f}% {key}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -5,7 +5,7 @@
|
||||
//
|
||||
// Every interval it reads /proc/<pid>/task/<tid>/schedstat (on-CPU ns, not the 10 ms tick
|
||||
// counters of stat) for every thread of every process whose cmdline starts with <package>
|
||||
// (the replay process and, for spawn/tcp, the render-server process), and keeps the last
|
||||
// (the replay process) and of every libMobileGLServer.so process (spawn/tcp), and keeps the last
|
||||
// kRingSeconds of samples in memory. When <watch_file> (benchmark.json) appears it dumps the
|
||||
// ring to <out.csv> and exits; the host cuts the benchmark's tail window out of it with the
|
||||
// file's mtime as the window end (make_report.py / cpu_window.py). Timestamps are
|
||||
@@ -20,7 +20,7 @@
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define MAXP 8
|
||||
#define MAXP 16
|
||||
#define MAXT 512
|
||||
#define RING 4096
|
||||
|
||||
@@ -94,7 +94,11 @@ static int ScanPids(const char* pkg, int* pids) {
|
||||
if (e->d_name[0] < '0' || e->d_name[0] > '9') continue;
|
||||
snprintf(path, sizeof path, "/proc/%s/cmdline", e->d_name);
|
||||
if (ReadSmall(path, buf, sizeof buf) <= 0) continue;
|
||||
if (strncmp(buf, pkg, len) == 0 && (buf[len] == 0 || buf[len] == ':')) pids[n++] = atoi(e->d_name);
|
||||
// The replay process (and its :services), plus the render-server processes the spawn and
|
||||
// tcp arms exec (their cmdline is the server library's name, not the package's).
|
||||
if ((strncmp(buf, pkg, len) == 0 && (buf[len] == 0 || buf[len] == ':')) ||
|
||||
strncmp(buf, "libMobileGLServer.so", 20) == 0)
|
||||
pids[n++] = atoi(e->d_name);
|
||||
}
|
||||
closedir(d);
|
||||
return n;
|
||||
@@ -148,20 +152,20 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
closedir(d);
|
||||
}
|
||||
if (iteration % 10 == 0) {
|
||||
// gpu_clock_stats: cumulative busy time per power level; its sum is busy-since-boot.
|
||||
// (gpubusy is a sliding ~1 s window, not a counter.)
|
||||
if (ReadSmall("/sys/class/kgsl/kgsl-3d0/gpu_clock_stats", buf, sizeof buf) > 0) {
|
||||
long long sum = 0;
|
||||
for (char* q = buf; *q;) {
|
||||
char* end = q;
|
||||
const long long v = strtoll(q, &end, 10);
|
||||
if (end == q) break;
|
||||
sum += v;
|
||||
q = end;
|
||||
}
|
||||
Push(now, Pseudo("gpubusy_us"), sum);
|
||||
// gpu_clock_stats: cumulative busy time per power level; its sum is busy-since-boot.
|
||||
// (gpubusy is a sliding ~1 s window, not a counter.)
|
||||
if (ReadSmall("/sys/class/kgsl/kgsl-3d0/gpu_clock_stats", buf, sizeof buf) > 0) {
|
||||
long long sum = 0;
|
||||
for (char* q = buf; *q;) {
|
||||
char* end = q;
|
||||
const long long v = strtoll(q, &end, 10);
|
||||
if (end == q) break;
|
||||
sum += v;
|
||||
q = end;
|
||||
}
|
||||
Push(now, Pseudo("gpubusy_us"), sum);
|
||||
}
|
||||
if (iteration % 10 == 0) {
|
||||
if (ReadSmall("/sys/class/kgsl/kgsl-3d0/devfreq/cur_freq", buf, sizeof buf) > 0)
|
||||
Push(now, Pseudo("gpufreq"), atoll(buf));
|
||||
if (ReadSmall("/sys/devices/system/cpu/cpufreq/policy7/scaling_cur_freq", buf, sizeof buf) > 0)
|
||||
|
||||
@@ -33,7 +33,16 @@ 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"; }
|
||||
pkg_of() { [ "$1" = dev ] && echo "$PKG_DEV" || echo "$PKG_FEAT"; }
|
||||
# An arm is <kind>[@<tag>]: the tag picks another installed build of the trace APK
|
||||
# (top.mobilegl.plugin.<tag>.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
|
||||
@@ -44,7 +53,7 @@ wl_args() { # trace golden w h target crop
|
||||
}
|
||||
|
||||
arm_env() {
|
||||
case "$1" in
|
||||
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" ;;
|
||||
@@ -93,13 +102,13 @@ verify_run() {
|
||||
{
|
||||
echo "arm=$arm backend=$backend"
|
||||
grep -aq "Active backend type set to $backend" "$all" && echo "BACKEND=OK" || echo "BACKEND=MISSING"
|
||||
case "$arm" in
|
||||
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" in inproc|shm|tcp) grep -aq "run-ahead ARMED" "$all" && echo "RUN_AHEAD=OK" || echo "RUN_AHEAD=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"
|
||||
@@ -114,11 +123,11 @@ run_one() {
|
||||
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" >/dev/null 2>&1
|
||||
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" = tcp ]; then
|
||||
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 >"$dir/server-start.log" 2>&1 || { echo server_start_failed >"$dir/STATUS"; log "FAIL tcp server"; return 1; }
|
||||
local waited=0
|
||||
@@ -148,7 +157,7 @@ run_one() {
|
||||
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" = tcp ]; then
|
||||
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
|
||||
@@ -170,7 +179,7 @@ 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) prep_pkg "$PKG_FEAT"; prep_pkg "$PKG_DEV" ;;
|
||||
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
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
#!/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).
|
||||
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}"
|
||||
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")
|
||||
app=/data/user/0/$pkg/files/trace-replay
|
||||
case "$ARM" in
|
||||
dev|monolith) env="" ;;
|
||||
inproc) env="MOBILEGL_TRANSPORT=inproc;MOBILEGL_IPC_RUN_AHEAD=1" ;;
|
||||
shm) env="MOBILEGL_TRANSPORT=spawn;MOBILEGL_IPC_CONTROL=fork;MOBILEGL_IPC_DATA=auto;MOBILEGL_IPC_RUN_AHEAD=1" ;;
|
||||
esac
|
||||
[ -n "${EXTRA_ENV:-}" ] && env="${env:+$env;}$EXTRA_ENV"
|
||||
case "$WL" in
|
||||
openra) args="--el target_call 31249 --ei width 640 --ei height 480" ;;
|
||||
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 "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 ^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
|
||||
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")
|
||||
# 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"
|
||||
Reference in New Issue
Block a user