Blolabel
← Notes
event 7 min read

pymobiledevice3 11.22.0 reports when an iPhone stops drawing, once a second: too coarse and too strict to replace a phone agent's settle wait

A new pymobiledevice3 command reads the iPhone's own frame counter: 0 means nothing on the display rendered for about a second. For a phone agent that is tempting as a settle signal. The release's own numbers say it is twice as slow as the screenshot gate phoneshell already uses, and blind on the screens that gate was built for.

pymobiledevice3 11.22.0, released on 4 October 2026 at 17:06 UTC, adds developer dvt fps: the iPhone's own Core Animation frame rate for the whole display, which reads 0 while nothing on screen changes. The device sends one sample about every 1.02 seconds and ignores the interval it is asked for. phoneshell's screenshot-based settle gate averages 504 ms, so the counter's fastest possible "nothing is moving" takes about twice as long as the check it might replace.

Abstract technical illustration of a slow pulse train sampled at wide regular intervals over a faster flickering signal that settles into a flat line

Two DVT instruments in one day, both by a first-time contributor

ItemValueSource
11.21.0 released4 Oct 2026, 05:35 UTC: developer dvt network-statistics, per-process bytes and packets (PR #1998)v11.21.0 release notes
11.22.0 released4 Oct 2026, 17:06 UTC: developer dvt fps and Graphics.fps() (PR #1999)v11.22.0 release notes
PR #1999 opened, merged08:22 UTC, 17:06 UTC the same day, by Cassian433PR #1999
Device channelcom.apple.instruments.server.services.graphics.opengl, key CoreAnimationFramesPerSecond, already sent by dvt graphicsPR #1999
Sample periodabout 1.02 s; requested intervals 0, 0.25, 2 and 500 changed nothingPR #1999
First samplearrives about 15 ms after sampling starts, covers under a second, skipped by fps()PR #1999
Static screen0; a 10-minute idle sample on an iPhone X read 0 throughoutPR #1999
Animating screen, iPhone 89, 22, 21, 10, 32 fps over five samplesPR #1999
Devices testediPhone X and iPhone 8, iOS 16.7.x, usbmux with the DeveloperDiskImage; not tested on iOS 17 or laterPR #1999
Timestampshost receive time in UTC; the device sends only microseconds since sampling beganPR #1999
phoneshell pinpymobiledevice3==11.8.0requirements.txt

The author is plain about the limits. It is one count per second, not per-frame timing, so frame time and jank cannot be derived from it and issue #871 stays open for those. It counts the whole display, so it reflects whatever is in the foreground, including system overlays. And no iPhone on iOS 17 or later was in the test set. Our phone runs iOS 26, where developer services go through the RemoteXPC tunnel, so whether the channel answers there is not established.

Why a phone agent would want it, and why the numbers say no

Every phoneshell step reads the screen only after it stops moving. A tree read taken mid-animation is a half-built screen: the docstring of snapshot() records a home-screen read with 6 elements at 1.0 s and 12 at 2.0 s. The current gate, wait_until_stable() in phoneshell/perception/screen.py, grabs a screenshot, waits 0.3 s (0.12 s on a screen memory already knows), grabs again, and accepts once two grabs differ by at most 1.2% of pixels, with a 3.5 s cap. phoneshell/memory.py records what this costs on a real iPhone: tree read 159 ms, screenshot 72 ms, stability gate 504 ms, fixed settle sleep 600 to 800 ms.

A device-side "nothing rendered" signal looks like a cleaner answer than comparing pictures. The sample period rules it out as a replacement. A zero needs one whole window in which nothing was drawn, so the earliest verdict comes 1.02 s after a transition ends if it ends exactly on a window boundary, and close to 2.04 s if it ends just after one. In the worked example below, the gate is done at about 0.7 s and the counter at about 2.04 s.

Worked example: a transition that stops drawing at 0.4 s 0 s0.71.022.04 animation, 0 to 0.4 s screenshotgate grabgrab, still moving diff at most 1.2%: settled, about 0.7 s 0.3 s between grabs, 2 matching grabs, cap 3.5 s (phoneshell/perception/screen.py) dvt fps window 1: frames drawn, fps above 0 window 2: nothing drawn, fps 0 one sample about every 1.02 s, intervals of 0, 0.25, 2 and 500 all ignored (PR #1999) first "settled" verdict: about 2.04 s The counter answers a different question: "did anything render in the last second?" A zero needs a full quiet window, so its earliest verdict lags the screen by 1.02 to 2.04 s. Illustration built from the stated parameters, not a recording. Sources: pymobiledevice3 PR #1999; phoneshell wait_until_stable(). Milliseconds per step on a real iPhone, and the counter's floor screenshot72 tree read159 stability gate (measured)504 fixed settle sleep600-800 dvt fps, one window1,020 01,000 ms Sources: phoneshell/memory.py module docstring (measured on a real iPhone); pymobiledevice3 PR #1999 (sample period about 1.02 s).

The second problem is strictness. The gate tolerates 1.2% of pixels changing because, as its docstring says, "a countdown timer, a carousel, a spinner or a blinking cursor changes a handful of pixels every tick", and an exact-match test on Grab's "40% off flash deals 14:19" sheet never converged and turned a 2 s step into a 50 s one. A counter that must read 0 is an exact-match test at the compositor level. Core Animation counts every frame it renders, so a blinking cursor in a focused text field should keep the count above zero indefinitely. That is our expectation from how the counter is defined; we have not measured it.

Which screens can each test call settled? screenshot gate, 1.2% tolerance dvt fps reads 0 static screensettlessettles (10 min idle read 0 throughout) blinking text cursorsettlesexpected never countdown banner ("14:19")settlesexpected never spinner or carouselsettlesexpected never The gate was written for exactly these rows: an exact-match test on Grab's flash-deal sheet never converged and turned a 2 s step into a 50 s one. Requiring fps 0 is an exact-match test. Sources: phoneshell/perception/screen.py wait_until_stable() docstring; pymobiledevice3 PR #1999 ("A static screen reads 0").

What it is good for on a phone agent

It answers "did anything at all render" with no screenshot and no image comparison. That fits the check phoneshell makes after every action: _after() in phoneshell/actions.py sleeps 0.6 s (0.8 s after typing or opening an app, 0.7 s after a swipe), then calls a change real if more than 2% of pixels differ, and the MCP layer turns a "no" into the "THE SCREEN DID NOT CHANGE" notice. A tap that lights a button highlight for a few frames and then does nothing passes neither test cleanly. A zero across the sample after a tap would say that nothing was drawn at all, which is stronger evidence of a dead tap than a pixel threshold. A frame count above zero proves nothing, though, because a cursor or a clock also renders.

The decision for anyone building on this: keep the screenshot gate for settling, consider the counter as a second witness on the "nothing happened" path, and budget a full second of latency wherever you read it. Pinning matters too. Going from 11.8.0 to 11.22.0 crosses fourteen minor releases, so phoneshell's bring-up checks would need a re-run after the bump.

Check it yourself

We did not run the counter on our rig for this note. The rig's automation-off marker (runtime/automation-off) has been set since 17 September, and an unattended run does not wake a parked phone.

What would prove this wrong

If developer dvt fps on an iOS 26 iPhone reads 0 within one sample while a text field with a blinking cursor is focused, our strictness argument is wrong. If a later pymobiledevice3 release samples faster than once a second on any iOS version, the latency argument falls. We expect neither by 31 March 2027: the 1.02 s period comes from the device, not from the library.

Sources

  1. Cassian433, dvt: Add fps monitoring over the graphics channel, pymobiledevice3 PR #1999, opened and merged 4 October 2026
  2. pymobiledevice3 v11.22.0 release notes, 4 October 2026, 17:06 UTC
  3. pymobiledevice3 v11.21.0 release notes (dvt network-statistics, PR #1998), 4 October 2026, 05:35 UTC
  4. pymobiledevice3 issue #871, frame rate and jank monitoring request (left open)
  5. PyPI metadata for pymobiledevice3 (read 4 October 2026)
  6. Blolabel, earlier note on how screen waits decide agent speed
  7. phoneshell source: phoneshell/perception/screen.py, phoneshell/actions.py, phoneshell/memory.py, requirements.txt (read 4 October 2026)