Restrict the frame-regression release gate to the emulator

The on-device profile is demoted to a diagnostic: the run force-stops
the app and seeds/removes a file in its storage, and the developer may
be using the phone while a release runs. The release gate is now
explicitly (1) TestNoFramesWhileIdle in the go-test suite and (2)
scripts/profile_emulator.sh on the EMULATOR.

- script: auto-select picks an emulator only (previously any connected
  device); no emulator -> clear error; a physical device passed via -s
  prints a DIAGNOSTIC (non-gating) banner and a heads-up.
- doc: architecture.md §11 states the two gate tiers and the diagnostic
  use of the physical-device run.
This commit is contained in:
Greg Pomerantz 2026-08-20 14:40:24 -04:00
parent ac1c32e4c0
commit 8bdf916d8a
2 changed files with 42 additions and 19 deletions

View File

@ -568,17 +568,24 @@ cannot measure this app because it renders into a `SurfaceView`).
`[0, MaxScroll]`) and emit a frame; `open <path>` opens a file from any
page (same `OpenFile` path as a browser tap) and emits. This lets a test
drive scrolls and file opens deterministically without pixel taps.
- **Frame-regression guard** (pre-release): frame emission is event-driven, so
a healthy app emits small per-action bursts and nothing while idle. Two
layers check that: `TestNoFramesWhileIdle` (internal/test/e2e, in the
regular go-test suite) asserts the logic emits ZERO frames across an idle
window after the browser and the editor (load + scroll + find cycle)
settle; and `scripts/profile_emulator.sh` runs the full sequence on a
device/emulator — 8 s idle, open, 16 s idle, three scrolls — via the debug
commands above, seeds a known 4000-line file for the open, and FAILs if any
idle-split phase of the CSV exceeds its frame budget (a ≥ 1/s spinner
exceeds a 16 s idle budget; a faster one balloons a phase or shows up in
PERF-PRESENT).
- **Frame-regression guard**: frame emission is event-driven, so a healthy
app emits small per-action bursts and nothing while idle. The guard has
two RELEASE-GATE tiers and one diagnostic use:
1. `TestNoFramesWhileIdle` (internal/test/e2e, in the regular go-test
suite) asserts the logic emits ZERO frames across an idle window
after the browser and the editor (load + scroll + find cycle) settle.
2. `scripts/profile_emulator.sh`, run on the **emulator** (auto-selected;
physical devices are never auto-picked), as the pre-release checklist
item: it drives 8 s idle, open, 16 s idle, three scrolls via the debug
commands above, seeds a known 4000-line file for the open, and FAILs
if any idle-split phase of the CSV exceeds its frame budget (a ≥ 1/s
spinner exceeds a 16 s idle budget; a faster one balloons a phase or
shows up in PERF-PRESENT).
The same script on a **physical device is diagnostic only, never a gate**
(pass `-s <serial>` explicitly; a banner says so): the run force-stops the
app and seeds/removes a file in its storage, and the developer may be using
the phone while a release runs. It exists to investigate a suspected
frame/battery problem, not to certify a release.
- The profiler is owned by the goroutine that creates it and is single-goroutine
(no locks). It does **not** `Sync()` the CSV per flush (only per row batch) to
avoid periodic fsync hitches in the logic path.

View File

@ -1,14 +1,22 @@
#!/usr/bin/env bash
# Pre-release frame-regression profile for the Pad editor.
#
# RELEASE GATE ON THE EMULATOR ONLY. The frame-regression guard has two
# tiers (architecture.md §11):
# 1. TestNoFramesWhileIdle (internal/test/e2e) — headless, in the regular
# go-test suite, runs on every build;
# 2. this script, run on the EMULATOR, as the pre-release checklist item.
# Runs on a PHYSICAL device are DIAGNOSTIC only — never a gate. The script
# force-stops the app and seeds/removes a file in its storage; the developer
# may be using the phone while a release runs, so physical devices are
# never auto-selected. Pass -s <phone serial> explicitly for a diagnostic
# run (a banner reminds you the result is informational).
#
# Measures whether the app generates frames WITHOUT a cause. Frame emission
# is event-driven (architecture.md §2), so a correct app emits a small
# number of frames per user action and NOTHING while idle. This script
# drives a fixed sequence of actions on a connected device/emulator and
# checks the in-app profiler's CSV for phases that exceed their frame
# budget. The headless contract of the same guard is
# TestNoFramesWhileIdle (internal/test/e2e), which runs in the regular
# go-test suite.
# drives a fixed sequence of actions and checks the in-app profiler's CSV
# for phases that exceed their frame budget.
#
# Usage:
# scripts/profile_emulator.sh [-s SERIAL] [-o OUT_DIR]
@ -60,15 +68,23 @@ BUDGET_OPEN="${BUDGET_OPEN:-20}"
BUDGET_SCROLL="${BUDGET_SCROLL:-8}"
if [ -z "$SERIAL" ]; then
SERIAL=$(adb devices | awk 'NR>1 && $2=="device" {print $1; exit}')
# Auto-select an EMULATOR only: physical devices are diagnostic runs and
# must be requested explicitly (the developer may be using the phone).
SERIAL=$(adb devices | awk 'NR>1 && $2=="device" && $1 ~ /^emulator-/ {print $1; exit}')
if [ -z "$SERIAL" ]; then
echo "no connected device; pass -s SERIAL" >&2
echo "no connected emulator; start one (adb emulator / AVD Manager)" >&2
echo "or pass -s <phone serial> for a DIAGNOSTIC (non-gating) run" >&2
exit 1
fi
fi
adb() { command adb -s "$SERIAL" "$@"; }
echo "== profiling on $SERIAL =="
case "$SERIAL" in
emulator-*) echo "== release-gate profile on emulator $SERIAL ==" ;;
*) echo "== DIAGNOSTIC (non-gating) profile on physical device $SERIAL =="
echo " (release gate = emulator; this run force-stops the app and"
echo " seeds a file in its storage — do not run on a phone in use)" ;;
esac
adb shell "mkdir -p $PERFDIR $NOTES"
adb shell "touch $PERFDIR/enable"
adb shell appops set "$PKG" MANAGE_EXTERNAL_STORAGE allow 2>/dev/null || true