From 8bdf916d8afd92b400a21a9926f75e08ea70ca7d Mon Sep 17 00:00:00 2001 From: Greg Pomerantz Date: Thu, 20 Aug 2026 14:40:24 -0400 Subject: [PATCH] Restrict the frame-regression release gate to the emulator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- doc/architecture.md | 29 ++++++++++++++++++----------- scripts/profile_emulator.sh | 32 ++++++++++++++++++++++++-------- 2 files changed, 42 insertions(+), 19 deletions(-) diff --git a/doc/architecture.md b/doc/architecture.md index 8c6c032..50976a6 100644 --- a/doc/architecture.md +++ b/doc/architecture.md @@ -568,17 +568,24 @@ cannot measure this app because it renders into a `SurfaceView`). `[0, MaxScroll]`) and emit a frame; `open ` 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 ` 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. diff --git a/scripts/profile_emulator.sh b/scripts/profile_emulator.sh index efd3e54..2b1a086 100755 --- a/scripts/profile_emulator.sh +++ b/scripts/profile_emulator.sh @@ -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 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 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