eleven / ocho Public

Clone
git clone https://amadan.net/eleven/ocho

Plain git — no account needed to clone.

Download

Download this file

πŸ€– Native performance instrumentation (iOS + Mac)

The answer to "the app has lots of little cuts": both native apps carry a small, always-there instrumentation layer (ios/LChatCore/Sources/LChatCore/ Perf.swift) so a slow moment on any device leaves a record β€” without a third-party APM SDK, and without anything leaving the device. Streaming member telemetry to Datadog/Sentry/Firebase would sit badly with the E2EE posture (and add the kind of dependency the second rule exists to avoid), so the stack is Apple-native + self-owned end to end.

The three layers

  1. Signposts (always on, every build). os_signpost intervals around the seams that hurt, via Perf.begin("name", slowMs:) β†’ span.end(). Cost is nanoseconds; nothing is stored or sent. Attach Instruments to any build (subsystem = the bundle id, category perf) and the seams below appear as named intervals; hangs and first-frame land in the Points of Interest track.
  1. The slow-event ledger (consent-gated). A span that beats its budget, a main-thread hang (HangWatchdog β€” ClickLab's 200ms ping generalised into LChatCore for both apps), or a noteworthy event (socket reconnect) appends one JSON line to Library/Logs/perf-events.jsonl in the app's container. ~512KB cap with one .prev rotation. Events carry a fixed name, a duration, and at most a URL path or row count β€” never content, names, or keys.
  1. MetricKit (consent-gated). MetricKitCollector subscribes to the OS's own APM: daily payloads (launch-time histograms, hang totals, memory) and hang diagnostics with call trees β€” real-device stack traces for hangs nobody was profiling. Raw payloads are filed under Library/Logs/perf-diagnostics/ (newest 24 kept) with a compact ledger line each. The Mac delivers a subset (no hang diagnostics) β€” that's why the watchdog exists on both platforms.

The gate

  • Debug builds record by default (opt out: the Settings toggle, or defaults write <bundle id> perfDiagnostics -bool NO).
  • Release builds record only with the consent toggle β€” iOS: the server settings cog β†’ Performance; Mac: Settings β†’ Advanced β†’ Performance (Paul's call, 2026-07-20). Signposts stay on either way.
  • The extensions (NSE, share) never call Perf.install(), so for them the layer is inert.

Instrumented seams

| Span | Where | Budget | |---|---|---| | boot.firstFrame | launch hook β†’ RootView first appear (iOS) | recorded always | | threads.hydrate / threads.refresh | ThreadStore | 500ms / 2500ms | | feed.openThread | ChatFeedModel.loadHistory (open a chat β†’ rendered) | 1500ms | | feed.foldPage | ChatFeedModel.foldPageDetached (200-row decrypt+fold) | 250ms | | api.get / api.post | LChatAPI choke points (detail = path, never query/token) | 1000ms | | socket.connect | ChatSocket connect β†’ handshake (failed attempts unrecorded) | 3000ms | | image.load | BlobImageLoader fetch+decrypt+decode | 1000ms | | main.hang | HangWatchdog (>250ms main-thread wedge β€” Apple's responsiveness bar) | β€” | | frame.hitch | HitchRecorder (iOS only: rendered-frame gap β‰₯120ms; the felt-jank band below the watchdog β€” the Mac has no CADisplayLink and keeps just the watchdog) | β€” | | socket.reconnect | note event, 30s same-name floor | β€” |

Adding one is two lines (Perf.begin/end) β€” budgets are per-call-site, so pick the number at which you'd actually want a report.

Getting the data off a device

  • Settings β†’ Performance β†’ Prepare performance report (iOS shares the file; the Mac reveals it in Finder): one self-contained performance-report.json β€” device model, OS, app version, the ledger, and the newest MetricKit payloads (2MB cap).
  • Dev pull without the UI: xcrun devicectl device copy from --device <id> --source Library/Logs/perf-events.jsonl --domain-type appDataContainer --domain-identifier com.elevenmessenger.messenger (same pattern as hitches.log).
  • Also free, zero code: Xcode Organizer (Hangs / Launch / Terminations from TestFlight + opted-in App Store users) and the App Store Connect perf-power-metrics + diagnostic-signature APIs (the existing ASC key can drive a periodic pull).

What this is not (yet)

The report is the streaming unit by design (PerfExport is self-contained), but nothing streams anywhere today. The intended follow-up is an opt-in upload of that JSON to a webhook-ingest feed (docs/webhooks.md) so reports land as encrypted messages in a team thread β€” an Aikido-style stream we own, with the server still seeing only ciphertext. Third-party APM stays off the table.

Related: HitchLogger.swift (iOS, DEBUG-only frame-drop breadcrumbs β€” still marked temporary) and ClickLab.swift (Mac click-eating rig) predate this layer and stay as-is; fold them in only when their bugs close.