Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@ Use these directly when steering/debugging:
- `/mms-recipe-doctor` — check tool, skill, harness, runtime-context, and fixture/profile readiness before a run.
- `/mms-recipe-harness` — install/verify Mobile or Extension runtime harness.
- `/mms-recipe-cook` — author/refine the executable recipe.
- `/mms-recipe-performance` — diagnose real-app bottlenecks and prove paired device performance.
- `/mms-recipe-quality` — critique recipe/evidence and force weak proof into explicit gaps.
- `/mms-recipe-evidence` — format artifacts into reviewer-ready PR text.
- `/mms-recipe-wallet-control` — optional wallet/app primitives for setup, navigation, state, and screenshots.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Capture adapter contract

The screen adapter owns checkout, instrumentation, restart and real UI actions.
The kit intentionally supplies no private bridge, navigation recipe or source
patcher. Reuse the flow's existing harness/collector, or prepare one before the
series. Python 3 and an executable adapter are required on the measurement host.

The driver calls:

```text
./capture-block.sh STATE REQUESTED_REF ABSOLUTE_BLOCK_DIR ROUNDS
```

The adapter must finish the block before returning. On its owned checkout it:

1. Selects the requested exact commit; restores only its own instrumentation
and original ref on exit/failure. Never reset unrelated files. Apply the same
measurement patch in each state, retain its diff/hash and verify the served
bundle changed as intended. A checkout alone does not reload Metro's bundle.
2. Restarts the same app/dev client, reinstalls probes lost on restart and
verifies the fixture, actual sampler configuration and unobscured control.
3. Settles for the fixed interval and performs one untimed warm-up per scenario.
4. Resets the per-open probe, invokes the real action, then collects every rAF
callback through and beyond the end mark. A wait after tapping is collection
time, not the metric's window. Exit nonzero for setup/capture failures.
5. Writes exactly ROUNDS raw captures per scenario. Keep source/build/device and
sampler readback, invocation and failure logs alongside them. Do not change
product values to manufacture a measurement.

These are the original public command forms to compose into the adapter, with
the recipe and its `out`/`rounds` parameters defined for the chosen screen:

```bash
"$HARNESS" call app.lifecycle command=restart --adapter mobile --target "$REPO" --json
"$HARNESS" run "$FLOW_RECIPE" out="$BLOCK_DIR/data" rounds="$ROUNDS" \
--adapter mobile --target "$REPO" --hud show --artifacts-dir "$BLOCK_DIR/recipe-run" --json
adb -s "$ANDROID_SERIAL" shell input tap "$TAP_X" "$TAP_Y"
adb -s "$ANDROID_SERIAL" shell input keyevent KEYCODE_BACK
```

Bind the physical device, Android emulator serial or iOS simulator UDID and the harness target explicitly. For iOS, use the resolved harness/simulator UI actions instead of adb. Do not assume the active device,
coordinates or a recipe parameter exists. The last two commands illustrate UI
actions inside the prepared flow; they are not an extra tap after the recipe.

Each `data/timed-N.json` is this object, or a JSON string encoding it:

```json
{
"marks": [{"n": "press", "t": 1000}, {"n": "open-complete", "t": 2000}],
"frames": [900, 1100, 1500, 2100],
"traces": [{"n": "Screen Interactive", "ph": "s", "t": 1050}, {"n": "Screen Interactive", "ph": "e", "t": 2000}],
"calls": [["event", "Screen Viewed", "viewed", 2100]]
}
```

`traces` and `calls` are optional. The first occurrence of each mark/span phase
is used. An end-mark list requires every named mark and takes their maximum,
not a subsequent layout caused by a later re-render. `--event` matches the call's third field and uses its fourth as the JS
timestamp. Do not mix host wall time with these monotonic timestamps.

For existing exports, the reporter also reads `blocks.txt` lines such as
`a1 baseline status=pass`, with `--rounds` supplied explicitly. Preserve the
alternating block order and use `--scenario` to describe its actual raw layout.
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
# Mobile bottleneck diagnosis

Use the task-locked harness and an owned device, iOS simulator or Android
emulator session. The same recipe, timing boundaries, CPU capture and elimination
method apply to each. Resolve the iOS simulator UDID through the runtime contract
and use the harness's simulator lifecycle/UI actions; bind Android to its exact
adb serial, including an emulator serial. Resolve the
current app, Metro endpoint, device serial and recipe commands from its runtime
contract/help. Preparation stays outside the measured window. A dev client with
Metro can compare JS changes without a native build; record its native revision,
served bundle SHA and dev mode. Do not call dev-bundle numbers release timings.

Follow the real-device diagnosis and final-confirmation rule in the skill's
How to use section. Emulator CPU allocation, host load,
debug mode and virtual rendering can distort frame rates. Record the target,
OS/runtime, host and build mode; keep target and host allocation fixed within
each comparison. Simulator results apply to that environment and do not establish
physical-device latency or native GPU performance.

## Verify the requested source first

Before source attribution or a timed open, resolve the requested baseline to an
exact commit. For "main", fetch and resolve `origin/main`; a branch name or an
idle checkout is not evidence of that state. Compare it with the task checkout's
HEAD before creating the task branch. If preparation was skipped or the slot is
warm, this check is still required. Preserve unrelated work and use the approved
workspace/prepare path to select the requested commit. Verify the served bundle
after reload and retain its source receipt. If dependencies or native
compatibility prevent that state from running, report BLOCKED instead of
measuring an older release and calling it main.

Use the product's production trace sampler during diagnosis as well as paired
proof, including custom sampler overrides. Record the effective rate and its
source. Dev builds that sample every span can add synchronous CPU work and send
the investigation toward a development-only cost.

## Classify the delay

Measure the UI gesture, JS handler entry, first following JS rAF callback, first
layout, and the actual ready/transition boundary separately. Use one monotonic
JS clock. JS handler timing excludes any wait before that handler executes.

| Observation during the same action | Next evidence |
| --- | --- |
| JS rAF stops, long JS tasks, tap handler/layout delayed | JS CPU sampling and React commit attribution |
| JS continues but native frames miss deadlines | Android frame timeline/Perfetto or iOS native profiler for UI/render work |
| Both stall | Correlate both timelines; do not force a single-thread explanation |
| Idle JS/native threads while readiness waits | Request/dependency and lifecycle trace |

Use the RN performance monitor for triage, then save repeatable evidence. JS rAF
cadence is not compositor FPS. On Android, collect package-scoped `dumpsys gfxinfo`
frame stats or Perfetto around the recipe action, with timestamps and support
limits. A screenshot, delayed timer, or total render count alone cannot classify
the bottleneck.

## Automate a CPU profile

Profile a separate open from the timing series. Navigate and settle first, start
Hermes/JS sampling immediately before the same recipe-driven gesture, stop after
the declared ready boundary, and retain the recipe trace alongside the profile.

The native bridge already exposes `profiler-start` and `profiler-stop --out`.
Find the installed bridge through the task harness's help/runtime and confirm
these commands exist. They send CDP `Profiler.start`/`Profiler.stop`; Hermes may
reject `Profiler.enable` even when start/stop work. For example, in a tracked
shell job with `set -e` and the task's resolved command paths:

```bash
# BRIDGE is the resolved mobile bridge; FLOW_RECIPE performs one measured open.
# Preparation, warm-up and navigation have already completed.
OUT=<new-task-artifacts-directory>
mkdir -p "$OUT"
node "$BRIDGE" profiler-start > "$OUT/profile-start.json"
trap 'node "$BRIDGE" profiler-stop --out "$OUT/interrupted.cpuprofile"' EXIT
"$HARNESS" run "$FLOW_RECIPE" --artifacts-dir "$OUT/recipe" --json
node "$BRIDGE" profiler-stop --out "$OUT/open.cpuprofile" > "$OUT/profile-stop.json"
trap - EXIT
python3 "$PERF_SKILL_DIR/scripts/profile-flame.py" \
"$OUT/open.cpuprofile" "$OUT/flame.html"
```

Bind the bridge to the same slot endpoint as the harness using its documented
runtime options. Do not guess a default port. If its wrapper lacks these
commands, use one CDP connection to the exact Metro `/json/list` app target:
`Profiler.start`, execute the recipe, `Profiler.stop`, save `result.profile`.
Always stop sampling on failure; retain the failure rather than presenting a
partial run as a success.

Open `flame.html` and inspect its sample-weighted caller graph and self-time
ranking. Also import `.cpuprofile` into RN DevTools if source maps are needed.
Record sample count, sample gaps, window length and whether the slow action is
actually represented. Zero samples, root/idle-only samples, or a sampler starved
during the stall are missing CPU attribution. Keep that evidence and diagnose
the profiler path; a React trace or wrapper timer does not become a CPU profile.
Correlate sampled functions with current source and React commits before naming
a component as the cause. Profiling overhead belongs outside the paired proof.

## Keep inspector messages small

Large CDP replies can stall the app themselves. In a prior Android investigation,
a full fiber-tree reply never completed, left a zero-byte output and caused an
ANR. The Android main-thread stack was in inspector `send`, large-object
allocation and GC. A reduced collect of about 125 KB completed; about 23 MB of
trace data delivered as many `Tracing.dataCollected` events also completed.
The failed single-message size was not measured. These observations establish
neither a universal safe cap nor the exact failure threshold.

Start with a short action-only sampling window. Read small summaries instead of
serializing an entire fiber tree. For custom probes, keep bounded records on the
runtime and retrieve bounded slices. Where the installed runtime supports native
sampling to a device file, stop there and `adb pull` the file. Chunked tracing
also avoids one giant reply. `Profiler.stop` itself may return one large message;
splitting its JSON on the host after receipt cannot prevent that stall.

Record actual serialized bytes per message or chunk, total bytes, elapsed
transfer time and the largest successful payload. If a transfer fails, record
the attempted size only if known, its timeout/error, app responsiveness and
package-scoped ANR stack. State the threshold as an observed bound, or unknown.
Do not repeatedly grow payloads until the phone ANRs. Shorten the window or use
a supported file/stream path before repeating the user interaction.

## Eliminate causes

Use reversible probes and retain each diagnostic patch. Before choosing a fix,
measure how much of the delay comes from mounting the content: compare the normal
flow with a temporary blank shell that preserves the route, gesture, transition
and ready observations. Put the switch above the tested subtree so its hooks do
not run while omitted. Returning null after those hooks ran does not remove their
mount cost; keep the surrounding component's hook order stable.

Add sections back progressively, or halve the expensive subtree. If omitting
content barely helps, investigate work before navigation, surrounding providers
or native rendering instead. Runtime/library frames still count when this
screen causes them; attribute them to the app's mounted work. If a candidate
explains only a small part of a large JS gap, continue this coarse elimination
before settling on that micro-optimization. Choose the largest proven removable
cost, not the first small positive result. Keep fixture and state unchanged.

Read runtime switches at mount so one session can rotate conditions between
opens. Record each condition, first JS frame, readiness, long gaps and relevant
CPU/React evidence. Confirm with an on/off/on reversal. A missing view that moves
work elsewhere is an attribution experiment, not a shippable fix. Do not hide
errors, remove required functionality, or claim several switches prove each
one's contribution. A valid fix may reduce work or defer it past first response;
retain all controls and measure full readiness and deferred work separately.
Trace the causal chain from gesture through scheduled work
and layout/transition, including hidden mounted children and pre-existing screen
work when the evidence points there.

Propose the smallest fix to the proven chain. For an authorised fix, prove the code
states with [paired device measurement](paired-device-measurement.md), including
adjacent-screen/deferred-work checks. For a requested PR comparison, use that
reference directly; no new fix or approval cycle is needed.

## Existing guides

Use the installed `mms-performance/references/` copies when available, or these
source guides. Keep their detailed tool instructions in one place:

- [React/JS profiling](https://github.com/MetaMask/skills/blob/main/domains/performance/skills/performance/references/js-profile-react.md)
- [JS and UI frame rates](https://github.com/MetaMask/skills/blob/main/domains/performance/skills/performance/references/js-measure-fps.md)
- [Performance timeline](https://github.com/MetaMask/skills/blob/main/domains/performance/skills/performance/references/js-performance-panel.md)
- [MetaMask tools](https://github.com/MetaMask/skills/blob/main/domains/performance/skills/performance/references/mm-tools.md)
Loading
Loading