diff --git a/tools/device_bench/disagg/callee_split.py b/tools/device_bench/disagg/callee_split.py new file mode 100644 index 00000000..9d64f082 --- /dev/null +++ b/tools/device_bench/disagg/callee_split.py @@ -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 [--depth 1] [--thread NAME] + +Uses the NDK's simpleperf_report_lib (set SIMPLEPERF_DIR to /simpleperf). For every sample +whose call chain contains a frame matching , attributes the sample to the +frame directly below it (its callee), or to "" 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 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)), "") + 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[""] += 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() diff --git a/tools/device_bench/disagg/cpusampler.c b/tools/device_bench/disagg/cpusampler.c index 28470c76..cfadd460 100644 --- a/tools/device_bench/disagg/cpusampler.c +++ b/tools/device_bench/disagg/cpusampler.c @@ -5,7 +5,7 @@ // // Every interval it reads /proc//task//schedstat (on-CPU ns, not the 10 ms tick // counters of stat) for every thread of every process whose cmdline starts with -// (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 (benchmark.json) appears it dumps the // ring to 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 #include -#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) diff --git a/tools/device_bench/disagg/matrix_p14.sh b/tools/device_bench/disagg/matrix_p14.sh index 6f65d37a..78abae73 100644 --- a/tools/device_bench/disagg/matrix_p14.sh +++ b/tools/device_bench/disagg/matrix_p14.sh @@ -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 [@]: 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 @@ -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 diff --git a/tools/device_bench/disagg/profile_p14.sh b/tools/device_bench/disagg/profile_p14.sh new file mode 100644 index 00000000..0dee13e4 --- /dev/null +++ b/tools/device_bench/disagg/profile_p14.sh @@ -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"