docs: P0's iris half done -- bench feature, emulator smoke run, APK

RUST.md's P0 box gets the iris-half account: the fixture, the scroll/stream
mechanism, the report fields, build commands (all clean), packaging (no
cargo xtask apk yet, so a new Gradle release build type on top of cargo
ndk), and the emulator smoke run's report next to Compose's own. Used a
second, differently-named AVD rather than contend with the session already
on this checkout's own emulator.

DECISIONS.md's P0 entry gets a matching summary bullet. IRIS.md records
AndroidAppState::platform_ready. IRIS_TODO.md notes the one gap found:
no read-only selectable text primitive, so the bench report's TextEdit
picks up a keyboard on tap it has nothing to type into.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
irisandClaude Fable 5.1 committed 2026-09-05 21:35:51 -04:00
1 parent 683db4908a
commit 00767eed4d
4 files changed
+228

No files matched your search

+174
View File
@@ -3487,6 +3487,180 @@ device.
this session was told not to touch `iris/`), and anything past the
emulator — the actual on-phone runs and Iris's pass/fail call.
**iris half: done, 2026-09-05.** A `bench` Cargo feature on
`iris-android-app`, built on top of `transcript-screen`
(`bench = ["transcript-screen", "dep:libc", "dep:tokio"]`,
`iris/android-app/Cargo.toml`), gives `lib.rs`'s `ActiveClient`
priority a third `AndroidAppState` (`bench_client::BenchClient`)
over `TranscriptClient` when both features are listed together --
matching the exact build command below, which lists both.
**Fixture.** `include_str!("../../../app/bench-fixture/assets/
transcript.jsonl")` (1,915,760 bytes) at compile time -- no asset
pipeline needed the way the Compose half's Gradle source set does.
`bench_client::parse_fixture` splits the same way `BenchFixture.kt`
does: the first 3,200 non-blank lines parsed as `serde_json::Value`s
and folded once through `client_core::transcript_fold::fold_page`
(the real fold a `/transcript` page goes through), the rest parsed
as `event_model::SeqEvent`s and held back as the streaming tail.
`build.rs` (transcript-screen's own) now exits early under `bench`
before requiring a live server's host/port/token/CA -- `BenchClient`
never calls `build_transport()`, so that requirement made no sense
for a build that talks to nothing.
**"Run benchmark" (`.label("Run benchmark")`) and "Copy report"
(`.label("Copy report")`)** sit in a fixed bar above the transcript;
a selectable `TextEdit` (`.attr::<Selectable>(())`, the same
attribute the composer field uses) below it shows the report text.
Pressing "Run benchmark" resets `FrameReport`, then drives
`List::scroll` in ~60Hz steps (`ANIM_STEP_MS = 16`) to animate each
900px/200ms swipe rather than jumping it -- iris's `List` has no
built-in tween the way `animateScrollBy(tween(...))` gives Compose,
so this is the one place the two backends' bench code has to differ
in shape rather than only in numbers -- through the same
`rsc.tasks.redraw_handle()` + manual `request_redraw()` per step
`transcript_client.rs` already established (a `Tasks::spawn`d
future's *automatic* redraw fires once, after the whole future
completes, which would show nothing moving until the run ends).
After the scroll loop, `List::jump_to_end()` pins to the newest
content (matching `stream-bench.sh`'s "Jump to latest" tap), then
400 fixture events replay at 20/s through `fold_event` -- the same
fold path a live SSE frame takes in `transcript_client.rs`'s own
`apply_event` -- each one triggering `rebuild_transcript`'s full
`transcript_ui::build_tree` rebuild, same tradeoff as
`TranscriptClient`/`desktop-app`. A battery sampler runs
concurrently on its own `tokio::spawn`d task (not through
`ctx.update`, since a JNI battery read needs no widget-tree access),
attaching whichever thread it runs on via a stored `JavaVM` --
`AndroidAppState::platform_ready` (new, `IRIS.md`) is what hands
`bench_client.rs` that `JavaVM` + a `GlobalRef` to the view, since
neither was reachable from `AndroidAppState::new` before this box.
**Report fields.** `FrameStats`'s existing `Display` (frames, janky
%, p50/p90/p99, worst, and I5's own `cpu_p50`/`gpu_wait_p50` CPU/GPU
split) plus a `bench:`-shaped tail this box added: process CPU time
via `libc::getrusage(RUSAGE_SELF)` (user+system time; chosen over
parsing `/proc/self/stat` by hand to avoid assuming `USER_HZ`), peak
RSS from `/proc/self/status`'s `VmHWM` (same source `BenchRun.kt`
reads), and battery current sampled once a second via
`BatteryManager.getIntProperty(BATTERY_PROPERTY_CURRENT_NOW)`
through direct JNI calls (`bench_jni.rs`'s `PlatformHandle` --
`android_view::context`'s own `Context`/`Resources` wrappers have no
`getSystemService`, so this calls it directly rather than growing
that crate's wrapper for two one-off calls). `0`/`Integer.MIN_VALUE`
read as "unavailable" rather than folded into the average, matching
`BatterySampler`'s own rule and UI_RULES.md's "never present an
inferred value as a measured one." The report is logged under the
existing `iris-android-app` logcat tag on a line starting `iris
bench report:` (grep-able the same way `transcript_client.rs`'s
"Frame report" control already is), shown in the on-screen
`TextEdit`, and copied to the system clipboard by "Copy report"
through `ClipboardManager.setPrimaryClip` (`bench_jni.rs`, same
`PlatformHandle`).
**Build commands, all clean this pass:**
- `cargo fmt --all -- --check` (iris workspace) and
`cd iris/android-app && cargo fmt --all -- --check`: clean.
- `cargo clippy --workspace --all-targets` (iris workspace): clean
(only the pre-existing `wgpu`/`winit`/`naga` future-incompat
notice).
- `cargo test --workspace` (iris workspace): 39 + 8 + 10 = the same
pre-existing counts, all passing, unaffected by this box (it
touched no logic under test there beyond `AndroidAppState`'s new
default no-op method).
- `cargo ndk -t x86_64 -P 26 clippy --features "transcript-screen
force-gles bench" --lib -- -D warnings` (`iris/android-app`):
clean.
- `cargo ndk -t arm64-v8a -P 26 -o app/src/main/jniLibs/ build
--release --features "transcript-screen force-gles bench"`:
clean, `arm64-v8a/libmain.so` produced. The pre-existing "unused
dependency `tabs-ui`" Cargo advisory also appears on a plain
`--features transcript-screen` build with no `bench` (confirmed
by building that combination alone with fake env vars) -- not
something this box introduced, and not a clippy/rustc warning
(AGENTS.md's "keep the build clean" gate is `cargo clippy`, which
stays silent on it).
**Packaging.** No `cargo xtask apk` exists for `iris/android-app`
yet (I2's own Gradle project is the only pipeline), so this reused
that split rather than inventing one: `cargo ndk --release` above
builds the cdylib straight into `app/src/main/jniLibs/`, then a new
`release` build type in `app/build.gradle` (there was previously
only `debug`) packages and signs it --
`AI_APP_KEYSTORE=~/.config/ai-app/release.jks` +
`AI_APP_KEYSTORE_PASSWORD` (the same key `app/build-apk.sh`
generates for the Compose app) via `gradle :app:assembleRelease`,
with `applicationIdSuffix ".bench"` so it installs beside the plain
tabs demo rather than replacing it. `aapt2 dump badging` on the
result: `package: name='dev.iris.android.demo.bench'`, one native
library, `lib/arm64-v8a/libmain.so`. `apksigner verify
--print-certs` shows the same `CN=ai-app` certificate
`compose-bench-arm64.apk` is signed with.
**Emulator smoke run, 2026-09-05.** This checkout's own AVD
(`ai-app-2`) was in use by the session recording I5's clean-scroll
comparison in this same file (its Compose app was in the
foreground, confirmed via `dumpsys window`/`dumpsys activity
processes` before touching anything) -- rather than contend for it
(AGENTS.md's "coordinate with peer agents"), a second,
differently-named AVD was created (`AVD_NAME=ai-app-2-bench emu
up`, `pixel_10`/`android-36`/`google_apis`/`x86_64`, cold boot, host
GPU, no `EMU_GPU=software`), with 12GB of the VM's memory still
available after both were up (this-machine-android's "two are
comfortable" guidance). Installed via `adb -s emulator-5556 install
-r`, launched, driven by `ui-trace record -s emulator-5556 --do
"tap 'Run benchmark'"` (the control resolved by its accessibility
label, per AGENTS.md's "no coordinate" rule), then read back over
`adb logcat`:
iris bench report
frames=372 janky%=56.99 p50=19.5ms p90=219.5ms p99=284.5ms worst=369.3ms (measures redraw-start to after present() is called, not GPU/compositor completion) cpu_p50=0.4ms gpu_wait_p50=13.9ms (redraw-start-to-submit vs. submit-to-after-present)
scroll: 6 cycles (24 swipes), streamed 400/400 fixture events
process CPU time over this run: 24665ms
peak RSS: 224600kB
battery current: mean 900000µA over 21 samples (min 900000, max 900000)
"Copy report" was pressed immediately after and logged `iris bench
report: copied to clipboard` (`ClipboardManager.setPrimaryClip`
succeeded). No crash (`adb logcat`'s `FATAL`/`AndroidRuntime` lines
checked -- only `ui-trace`'s own runtime, unrelated), process alive
throughout (`dumpsys activity processes`), 400/400 stream events
confirmed sent.
Read this the same way the Compose half's own box already asks to
read its number: this is software-rasterised (well, GLES-over-virgl
under `force-gles`, per I5's "Where iris's frame time goes")
emulator output, "the harness runs end to end and produces every
field P0 asked for," not a phone number -- and the battery current
is again the emulator's fixed 900000µA mocked charger reporting a
constant, exactly what the Compose box's own run found, not a real
battery answering. `cpu_p50=0.4ms` (iris's own per-frame CPU work)
against a much larger `gpu_wait_p50`/`p50` again matches I5's "Where
iris's frame time goes" finding under real GPU rendering (`-gpu
host`, `force-gles`) -- the frame-time budget here is dominated by
the driver/compositor wait, not by iris's layout or primitive
building, though this run's `janky%`/`p90`/`p99` are considerably
worse than that earlier isolated pass, most likely the cost of this
AVD's very first cold boot plus running two emulators on this VM at
once (a fair comparison against Compose would need both apps run
back-to-back on the same freshly-booted device, not attempted this
pass since the second AVD was torn down immediately after per
AGENTS.md's "stop yours when you are done with it").
Copied to `~/host/bench/iris-bench-arm64.apk` (15,445,468 bytes) and
`~/host/bench/README.md`'s "iris" section filled in (install, open,
tap "Run benchmark", read the report from the on-screen text or
logcat, tap "Copy report", paste back).
**Not done this pass**: the actual on-phone runs and Iris's
pass/fail call between the two reports (P0's own pass condition) --
that needs Iris's phone, which this session has no access to.
`iris/src/android/view.rs` was touched (`AndroidAppState::
platform_ready`, `new_peer`'s wiring) -- confirmed to not be one of
the three files the concurrent `device_limits()` work on this
branch was using (`iris/core/src/render/mod.rs`,
`iris/src/android/render.rs`, `iris/src/default/render.rs`).
- [ ] **P1 — session screen parity.** History paging backward (with the
page-boundary healing `client-core` does not have yet, below),
`TranscriptSource`-backed cache/server stitching, jump-to-latest,