Skip to content

Use usbmuxd for physical Apple device discovery instead of spawning devicectl/xctrace #1522

Closed as not planned

Description

@thymikee

Context

#1403 asked whether the usbmux client should grow beyond the runner transport, and set the bar at "a second concrete product use case". Measuring #1517 produced one.

In the final cabled capture from #1517, resolving the target device cost 6672 ms:

resolve_target_device  6672 ms
open_timing             982 ms

Device resolution is an order of magnitude more expensive than anything the transport change saved per command. It shells out to devicectl list devices, xctrace list devices, simctl list devices, plus the Android and web probes, and pays process-spawn cost for each. Earlier uncached resolutions in the same investigation measured 4.5–6.8 s.

usbmuxd answers ListDevices over a unix socket in milliseconds, and it is already the authority on which physical iOS devices are attached — the same authority #1517 now trusts for routing.

Proposal

Use usbmuxd as a fast path for physical Apple device discovery, on the plist protocol the client already speaks (runner-usbmux-protocol.ts, roughly 50 LOC of additions):

  • ListDevices to enumerate attached physical devices without spawning anything
  • optionally Listen to stream attach/detach events, which would let a long-lived daemon keep an accurate device list instead of re-probing

usbmuxd returns the UDID and connection type but not the display name, OS version, or anything about Wi-Fi-only devices, so this is a fast path rather than a replacement: it can confirm which devices are cabled immediately, with the existing tooling filling in metadata or covering Wi-Fi-only devices. The exact split is the main design question here.

Why it is worth doing

Non-goals (unchanged from #1403)

A public port-forward command, reverse forwarding for Metro or React DevTools, lockdownd services (installation proxy, AFC, syslog relay), and extracting a general-purpose usbmux package — that last one was evaluated during #1403 and rejected: one internal call site does not justify a package boundary, and a published client would immediately attract the whole libimobiledevice surface area.

Acceptance

  • Measured before/after on resolve_target_device for a cabled physical device
  • Wi-Fi-only and simulator resolution keep working unchanged
  • No behaviour change when usbmuxd is unavailable — it degrades to today's path

Activity

  1. thymikee commented on Jul 31, 2026

    @thymikee
    MemberAuthor

    Scoping this turned up something that changes its priority. The 6672 ms resolve_target_device figure that justified this issue is real, but usbmux was aimed at the wrong part of it.

    Breaking the number down by probe:

    probe cost
    vega device list 2833 ms
    xctrace list devices 1814 ms
    devicectl list devices 773 ms
    simctl list devices 768 ms
    emulator -list-avds 698 ms
    adb devices -l 539 ms

    They ran back to back — listLocalDeviceInventory awaited each platform in a for loop — so an unfiltered lookup cost the sum of every installed toolchain. The biggest single contributor was Vega, which the request could never have resolved to.

    #1524 removes the serialization: ~4.0 s → ~2.35 s measured by alternating A/B on one host. That is worth more than this issue would have been, and it helps every platform rather than just Apple.

    This issue is still valid, just smaller than advertised. With the probes concurrent, wall time is now bounded by the slowest one, so replacing devicectl/xctrace with a millisecond usbmuxd ListDevices only helps once the slower probes are also addressed — and it still cannot supply names, OS versions, or Wi-Fi-only devices on its own.

    The other half of the original argument survives untouched: Listen would let a long-lived daemon keep an accurate attached-device list instead of re-probing, and an unplugged device could fail immediately and precisely instead of as a timeout. Those remain the reasons to do this, rather than the raw resolution cost.

  2. thymikee commented on Jul 31, 2026

    @thymikee
    MemberAuthor

    Measured this properly before implementing, and the speed premise does not survive. Recommending this be closed for the discovery-latency rationale.

    What the 6672 ms actually was

    Two things, neither of which usbmux addresses:

    1. Sequential platform probes — fixed in perf(devices): probe platform inventories concurrently #1524 (~4.0 s → ~2.35 s).
    2. A cold toolchain. That capture was the first Apple tooling invocation on the host. Re-measured warm, Apple discovery is ~2.2 s.

    Where the remaining ~2.2 s lives

    Warm, per probe: simctl list devices ~0.3–1.3 s, devicectl list devices ~1.4 s, xctrace list devices ~2.2 s. devicectl and xctrace already run concurrently, so the Apple chain is bounded by xctrace, and nothing else in it matters much.

    I also tried the obvious structural fix — starting the physical probes without waiting for simctl — and benchmarked it as two prebuilt worktrees, alternating, five warm runs each:

    arm runs (ms) median
    main 2183, 2221, 2433, 2282, 2257 2257
    probes started early 2284, 2314, 2180, 2163, 2318 2284

    Indistinguishable. simctl finishes long before xctrace would, so removing the gate buys nothing warm. I am not proposing that change; it would add complexity for noise.

    Why usbmux cannot take the remaining cost

    To beat xctrace we would have to stop running it, and usbmuxd cannot replace what it provides:

    • it returns a UDID and connection type — no display name, no OS version, both of which discovery needs
    • it cannot see network-paired devices at all (established in Explore usbmux as the primary physical iOS runner transport #1403), so "usbmuxd is empty" never implies "no physical devices", and gating xctrace/devicectl on it would silently lose Wi-Fi devices

    So usbmux can only ever add a fast signal alongside the existing probes, not remove one — and the probes are the cost.

    What is still worth doing, if anything

    Only the parts that were never about latency: Listen for attach/detach so a long-lived daemon stops re-probing, and failing immediately and precisely when a cabled device is gone instead of surfacing a timeout. Those stand on their own; if they are not wanted, this issue should just be closed.

  3. thymikee commented on Jul 31, 2026

    @thymikee
    MemberAuthor

    Closing: the latency premise did not survive measurement (details in the comment above).

    In short — the 6672 ms was sequential platform probing (fixed in #1524) plus a cold toolchain. Warm Apple discovery is ~2.2 s, bounded entirely by xctrace list devices, and usbmuxd cannot replace that probe: it supplies neither display names nor OS versions, and it cannot see network-paired devices, so it can only add a signal alongside the existing probes rather than remove one. The probes are the cost.

    The two rationales that were never about latency — Listen for attach/detach so a long-lived daemon stops re-probing, and failing immediately and precisely when a cabled device disappears — are real but small, and neither is currently pulling anyone. Reopen if either becomes worth its own change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions