#!/usr/bin/env bash # One-shot jank A/B for the phone (usage: ADB_SERIAL=10.0.0.5:5555 ./scripts/jank_ab.sh) # # What it does: # 1. Installs the release APK (build with ./scripts/build.sh aarch64 first). # 2. Verifies thread priorities (thumb-* at nice 2, slint vsync at 0). # 3. Runs TWO 30-second scroll windows — real textures, then grey 1x1 # images — with logcat capture + cadence analysis in between. # THE USER MUST SCROLL WITH A FINGER during each window # (moderate drag, no fling — the reported repro). # 4. Prints a comparison: % of scroll events on the 8.3 ms grid, # stall count (gap > 16 ms), worst gaps. # # Interpretation: # real janky + grey smooth -> texture/GPU path is the culprit # both janky -> base render/present/event path # both smooth -> the priority fix worked; ship it set -u E="adb ${ADB_SERIAL:+-s $ADB_SERIAL}" APK=target/release/apk/gallery.apk PKG=wow.gmp.gallery CMD=/sdcard/Download/.gallery_cmd GREY=/sdcard/Download/.gallery_grey PERF=/sdcard/Download/.gallery_perf.log [ -f "$APK" ] || { echo "build first: ./scripts/build.sh aarch64"; exit 1; } scroll_window() { # $1 = label, $2 = tag, $3 = seconds local out="/tmp/jank_$2.txt" $E shell "echo -n '' > $PERF" 2>/dev/null || true echo "### $1 — scroll now (moderate drag, no fling)..." sleep "$3" $E pull "$PERF" "$out" >/dev/null 2>&1 python3 - "$out" << 'EOF' import re, sys ev = [] for line in open(sys.argv[1], errors="ignore"): m = re.match(r"SCR +([\d.]+)", line) if m: ev.append(float(m.group(1))) if len(ev) < 10: print(f" {sys.argv[1]}: only {len(ev)} scroll events — not enough data"); sys.exit() gaps = [ev[i+1]-ev[i] for i in range(len(ev)-1)] # drop the first gap (window edge) and any multi-event same-ms catch-ups # are kept — they ARE the jank signature gaps = gaps[1:] on_grid = sum(1 for g in gaps if 6.5 <= g <= 10.5) stalls = [g for g in gaps if g > 16] import collections buckets = collections.Counter() for g in sorted(stalls): buckets['16-25' if g<25 else '25-40' if g<40 else '40-65' if g<65 else '>65'] += 1 print(f" events={len(ev)} on ~8ms grid={100*on_grid/len(gaps):.0f}% " f"stalls(>16ms)={len(stalls)} p95={sorted(gaps)[int(len(gaps)*0.95)]:.1f}ms " f"worst={max(gaps):.1f}ms stall buckets: {dict(buckets)}") # tick-stall context: how many TICK stalls, any APLY nearby EOF echo " TICK-stall lines: $(grep -c '^TICK' $out 2>/dev/null) APLY lines: $(grep -c '^APLY' $out 2>/dev/null)" } check_prios() { local pid; pid=$($E shell pidof $PKG) echo "### thread priorities (pid $pid)" $E shell "for t in /proc/$pid/task/*; do c=\$(cat \$t/comm 2>/dev/null); case \$c in slint*|thumb*) n=\$(awk -F')' '{print \$2}' \$t/stat | awk '{print \$17}'); echo \" \$c nice=\$n\";; esac; done" } install_and_start() { # $1 = "grey" to enable the grey knob before launch $E install -r "$APK" >/dev/null if [ "${1:-}" = grey ]; then $E shell "touch $GREY"; else $E shell "rm -f $GREY"; fi $E shell am force-stop $PKG $E shell am start -n $PKG/android.app.NativeActivity >/dev/null sleep 8 } echo "=== installing $APK ===" install_and_start check_prios echo echo "=== window 1: REAL textures (30 s) ===" scroll_window "real" real 30 echo echo "=== restarting with grey 1x1 images ===" install_and_start grey check_prios echo echo "=== window 2: GREY images (30 s) ===" $E logcat -d 2>/dev/null | grep "grey images ON" || echo " WARNING: grey knob not active!" scroll_window "grey" grey 30 $E shell "rm -f $GREY $CMD" echo echo "done. Compare the two summaries above." echo " (raw captures: /tmp/jank_real.txt /tmp/jank_grey.txt)"