Repository navigation
Add a startup scenario and a plant mechanism, with the main-thread I/O plant captured - #391
Merged
Merged
Conversation
A measurement nobody has seen fail cannot be told apart from one that always passes, and until now the only regression devicelab had been shown was typed by hand: the leak hunt's negative test commented four lines out, built, and put them back. Nobody can repeat that exactly. `lab run <scenario> --plant <name>` makes it mechanical. It applies `devicelab/plants/<name>.patch` to a clean tree, publishes, builds and installs, and reverts the patch as soon as the APK is on the device, before anything is measured; the exit trap reverts it too when the build fails. A dirty tree is refused, because the run records the commit and the patch and uncommitted changes would be a third thing it measured and did not name. `run.json` gains `plant` (name, patch sha256, base commit; null otherwise), an added field, so `schema` stays 1, and the patch is copied into the run. It is an option of the harness rather than of a scenario because what it changes is the build, which is the harness's: the apply has to come before the publish and the revert straight after the install. The self-test applies and reverts a plant in a scratch repository, and checks every shipped plant still applies to HEAD, since a patch against the demo rots as the demo moves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017etd1nNb2E2XPL6WTvkAbo
The platform measures a launch to its first frame by itself, which is time to initial display. Time to full display needs the app to say when it shows what it was opened for, and for a player that is the first frame of video. Without the call every startup trace had TTID and no TTFD, and Perfetto's `android_startup_time_to_display` left the column empty. The listener is removed on dispose like the one beside it, because the player belongs to the service and outlives the screen. An Activity reports once, so a stream switch that renders a first frame again adds nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017etd1nNb2E2XPL6WTvkAbo
`lab run startup` cold-starts the demo twenty times in one trace for perfettoagent#5: each launch is a force-stop, a dropped page cache where the device has root, `am start -W` refused unless it reports COLD, and a wait for playback so the startup runs to its first video frame inside the trace. The trace starts once the harness's own launch is playing, so the first launch after an install is a warm-up rather than a sample. Twenty, so p95 by nearest rank is the 19th launch rather than the slowest; one trace, so the distribution is one query. The `startup` data source records scheduling with blocked reasons and the atrace categories Perfetto's startup modules read; `frametimeline` rides with it because `android_frames`, and so TTID, is empty without it. SurfaceFlinger's `gfx` is left out: it was 1.2 of 2 million slices, and nothing reads it. `startup-main-thread-io` adds the `Application` subclass the demo lacks, whose `onCreate` reads and checksums a 192 MiB file on the main thread (ADR-0007 in perfettoagent). The size is measured: 64 MiB overlapped the clean launches, 192 MiB put every regressed launch above every clean one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017etd1nNb2E2XPL6WTvkAbo
…gives away - Trace processor's CSV and the Markdown table it becomes were the leak hunt's (`tp_csv_to_tsv`, `tsv_to_markdown`); they move to lib/trace_processor.sh and the startup report uses them, rather than a weaker copy that split quoted commas. - `device_has_root` in lib/device.sh is the one root probe; the leak hunt's GC and the startup scenario's page-cache drop both read it. - What the tree carried beyond the commit is decided once (`SP_TREE_NOTE`) for the log and the report, and the option's variable is `PLANT_REQUESTED`, apart from `PLANT_NAME`, which is set only once a plant is applied. - devicelab/README.md's run steps now say where a plant goes in and comes out, and that `tree_dirty` is true for a planted run by construction. - A planted run's run.json, report and patch name the plant; plants/README.md says they are the operator's record, not evidence to hand to whatever is tested on the trace. - The startup scenario writes `startup/conditions.json`, recording whether the page cache was dropped, since runs that differ in it are not comparable. - The plant's cache file outlives a run (`install -r` keeps data); the README said otherwise. - The self-test also checks that a run with no plant leaves the tree clean. - perfettoagent is cited by its full issue link rather than by a bare `#5`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017etd1nNb2E2XPL6WTvkAbo
Three planted runs and five clean ones, interleaved on the API 36 emulator, all from 7b43b5f with a clean tree. Planted, `bindApplication` is about five times as long at p50 and so is the main thread's I/O wait inside it; TTID's p50 moves by 214–392 ms. The clean pairs differ by 4 and 39 ms at p50. The sizing argument is restated on p50. The sizing run put every regressed launch above every clean one, but across five clean captures a single clean launch reached 1079 ms, so the extremes overlap and the claim that holds is the one about the middle: the smallest regressed-to-clean p50 gap is twice the whole spread of clean p50s. The clean runs also drifted over the hour, most in the tail, which is recorded as a finding rather than smoothed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017etd1nNb2E2XPL6WTvkAbo
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes ramesh130/perfettoagent#5
Adds a
startupdevicelab scenario, a--plantoption that builds a run with a known regression and takes it out before measuring, and the first plant: synchronous main-thread I/O inApplication.onCreate(perfettoagent ADR-0007: the demo has noApplicationsubclass, so the patch adds one). It also addsreportFullyDrawnat the demo's first video frame, so traces carry TTFD, and the baseline, regressed and clean-pair captures.Decisions
--plant <name>onlab run, not a separate driver. A plant changes the build, and the build belongs to the harness: the apply has to come before the publish and the revert straight after the install, in the middle oflab run. A separate driver would have to repeat that sequence. So any scenario takes any plant, and Enforce the public API surface in CI #6's three plants will each be one patch file plus one row indevicelab/plants/README.md.git apply --checkbefore the boot.run.jsongainsplant(name, patch sha256, base commit). This is an added field, soschemastays 1. The patch is also copied into the run asplant.patch.20 cold starts in one trace. p95 by nearest rank is then the 19th launch rather than the slowest. One trace means the distribution is one
android_startupsquery.SCENARIO_TRACE_FROM=playing. The first launch after an install carries one-off work, so the harness's own launch is a warm-up and not a sample. With--trace-from launchit lands in the trace, and the report expects one more startup than the scenario counted.What counts as a cold launch:
am force-stopfirst;am start -W, with the launch refused unless it reportsLaunchState: COLD;Whether the cache was dropped is recorded in
startup/conditions.json.Data sources:
startup: sched withsched_blocked_reason; atraceam,wm,view,dalvik,resandbinder_driver; the demo's own sections;compact_sched.frametimeline:android_framesis empty without it, and TTID with it.gfxis left out. It was 1.2M of 2M slices and nothing in the startup modules reads it. That took 20 launches from 192 MB to about 70 MB.TTFD. The platform cannot know when a player is "fully drawn", so the demo reports it at
onRenderedFirstFrame. TTFD includes the network fetch, which the README says.Plant size: 192 MiB, read and checksummed. Cold reads on the emulator ran at 190–720 MB/s. 64 MiB moved p50 by about 90 ms and overlapped the clean run. 192 MiB separated them. The argument is made on p50, as the captures below show.
Captures
All runs are on
superplayer_verify_36, headless, from7b43b5fwith a clean tree. The planted ones addstartup-main-thread-io(patch sha2561ccfc7b78f9cc1079b54a3dc513a5f619ff73ad4b916a149ecc9a0834624c828). Each run is 20 cold launches with the page cache dropped, andandroid_startupsfound 20/20 in every run.Times are in ms, from the trace. The last two columns are p50 over the run's startups: I/O wait is the main thread's
thread_stateD withio_wait=1insidebindApplication.bindApplicationp5020260924T011856Z-startup(B1)20260924T012233Z-startup(R1)20260924T012633Z-startup(C1)20260924T013010Z-startup(B2)20260924T013400Z-startup(R2)20260924T013758Z-startup(C2)20260924T014149Z-startup(B3)20260924T014554Z-startup(R3)bindApplicationis about 5× longer at p50, and the main thread's I/O wait inside it is also about 5×./Users/ramesh/projects/myprojects/superPlayer/devicelab/out/<run dir>/, about 70 MB each. The three sizing runs (20260924T005812Z,…010253Z,…010729Z) are there too, built from pre-review work-in-progress trees.7b43b5fgets a new sha onmain. The tree is identical, and the new sha is posted on the perfettoagent issue.Q4 evidence: is
android_startupsreliable on API 36 emulator traces?I ran
SELECT * FROM android_startupswithmodules=['android.startup.startups']through perfettoagent's pinned trace processor on B1 and R1:package=com.superplayer.demoandstartup_type=cold, which is the right count.durmatchesam start -WTotalTime launch for launch, within 1 ms (mean absolute difference 0.5 ms).android_startup_time_to_displayends 3–6 ms beforeduron average (at most 26 ms), and TTFD was found for all 160 startups.frametimeline. The first pilot, without it, found every startup and no TTID.Review
I reviewed by hand on two separate axes (sub-agents); the two are kept apart below.
Standards: no hard violations of superPlayer's binding rules, CONTRIBUTING or the devicelab conventions. Fixed:
lib/trace_processor.sh, replacing a weaker copy;device_has_rootis one probe instead of two copies;PLANT_REQUESTEDandPLANT_NAMEare split;tree_dirty's meaning for planted runs is documented;#5.Spec: meets the issue and ADR-0007. Fixed:
run.json,report.mdand patch name the plant, so the README now says they are the operator's record and not evidence to hand to the model;Carried to #10, below.
Tests
devicelab/test/selftest: devicelab 100 passed, leak-hunt 34 passed, startup 16 passed../gradlew assemble check --max-workers=1: 1187 JUnit tests, 0 failures, 0 errors.(cd demo && ./gradlew assembleDebug lintDebug spotlessCheck): green.git status --shortafter all captures: empty.Skipped, and why
devicelab/plants/startup-main-thread-io.patch, its README and this PR's commit subjects are readable bygrep_repoandgit log. The issue requires the patch to live in devicelab, so it stays. Handle audio focus, becoming-noisy, wake locks, and configuration change #10 has to build case ranges from a copy or history that excludesdevicelab/plants/, or a base that predates it.DISCARDbuffer. It pairs withwrite_into_file, and trace processor flags that combination (config_write_into_file_discard). No data-loss stats fired. It is shared by every scenario, so it is left alone here.🤖 Generated with Claude Code
https://claude.ai/code/session_017etd1nNb2E2XPL6WTvkAbo