Skip to content

Telemetry - #458

Draft
pblazej wants to merge 7 commits into
mainfrom
blaze/telemetry
Draft

pblazej wants to merge 7 commits into
mainfrom
blaze/telemetry

Conversation

@pblazej

@pblazej pblazej commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Summary

registerGlobals now wires livekit-client's telemetry pipeline to the platform. The instrumentation — when a connect span starts, what a stats window holds, how a subscribe ends at first media — stays in livekit-client and is reused unchanged; this PR adds the two things a phone knows and a browser does not.

  • A write-ahead file cache. LKBatchStore.swift and BatchStore.kt are a directory of batch files mirroring the Rust core's FileCache: written before the network is tried, renamed into place so a crash never leaves half a batch readable, pruned oldest-first above 4 MiB / 512 batches / 24 h, and one batch always survives. A session that ends with the app being killed, or an hour spent offline, replays at the next launch instead of being lost.
  • The device state SPEC's cadence policy needs. LKDeviceState.swift (ProcessInfo.thermalStateDidChangeNotification, NSProcessInfoPowerStateDidChange, a DispatchSource memory-pressure source) and DeviceStateMonitor.kt (PowerManager.addThermalStatusListener, ACTION_POWER_SAVE_MODE_CHANGED, onTrimMemory), each mapping the platform's levels onto SPEC's names so an iOS record and an Android record say the same thing.
  • No mobile vocabulary reaches livekit-client. It receives a TelemetryStorage it never inspects, an event name it never interprets, and a cadence factor without a reason attached. The thermal/low-power/memory mapping lives in this package.
  • The bridge calls are blocking and synchronous, with base64 bodies, because the pipeline's queue path has no await in it — which is also why this is native code here rather than expo-file-system or the maintained react-native-fs fork, all of which are async.

Depends on

livekit/client-sdk-js#2109. This uses Telemetry.configure({ storage }), Telemetry.emit and Telemetry.setCadenceFactor, none of which exist in a released livekit-client. Local verification was done against that branch.

There is also a pre-existing blocker unrelated to telemetry: src/e2ee/RNE2EEManager.ts does not type-check against livekit-client 2.22 (three Uint8Array<ArrayBufferLike> vs Uint8Array<ArrayBuffer> errors from the typed-array variance that package introduced). That breaks bob build, so npm i of this package fails against a current livekit-client.

Testing

  • Cache across a kill, on the simulator. With the collector down the app cached 36 batches to Library/Caches/livekit-telemetry; killed from simctl; relaunched with the collector back; all 45 records delivered, including the 35 per-tick self-reports that reconstruct the offline period after the fact. Zero rejected.
  • Device state reaches the collector: lk.device.thermal.changed=nominal, lk.device.low_power.changed=false, lk.device.app_state.changed=foreground, with service.name=livekit-client-react-native os.name=ios os.version=18.6.
  • yarn typescript and yarn lint clean apart from the pre-existing RNE2EEManager errors above.
  • Android is written but not built: there is no Android SDK/NDK on the machine this was developed on.

Notes for review

  • telemetry-poc/ is a bare RN 0.82 app used as the harness — it links this package's pod and proves the native store on a simulator without dragging in WebRTC. Happy to drop it from the PR or move it under example/ if it does not belong here.
  • Two gotchas worth the comments they got: a blocking synchronous method must return an object (two of these returned Bool, and the TurboModule interop retained it as a pointer and segfaulted the app with nothing JavaScript could see); and an event sent from inside a native method races the listener JS registers around it, so the first device state comes back on a promise.

Before undrafting

  • Telemetry client-sdk-js#2109 merged and released, and the dependency bumped
  • RNE2EEManager type errors fixed against livekit-client ≥ 2.22
  • Android store built and smoke-tested on a device
  • lk.device.memory.changed exercised (the simulator gives no way to drive DispatchSource's levels)
  • Decide whether telemetry-poc/ stays

pblazej and others added 7 commits September 18, 2026 09:52
The same src/telemetry/index.ts that client-sdk-js runs in Chromium, copied
with no edits into a bare RN app: Metro resolves the OTLP encoder and Hermes
executes both serializers, which is the whole claim behind "one implementation
for web and React Native".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
livekit-client carries the pipeline; React Native supplies the two things it
cannot know — which platform this is, and that an app going to the background is
the last chance to upload, since there is no visibilitychange here.

registerGlobals calls it, and it is imported after the DOMException and
TextEncoder polyfills: livekit-client evaluates both at module scope and Hermes
has neither.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Thermal state, low power mode and memory pressure: the three signals SPEC's
cadence policy needs and no browser API exposes. LKDeviceState.swift watches
ProcessInfo's two notifications and a DispatchSource memory-pressure source;
DeviceStateMonitor.kt watches PowerManager's thermal listener, the power-save
broadcast and onTrimMemory. Both map the platform's levels onto SPEC's names, so
a record from iOS and one from Android say the same thing.

The first state comes back on startDeviceStateUpdates' promise rather than as an
event — an event sent from inside that call arrives before JS has registered its
listener, and RCTEventEmitter drops it without a word JS can see.

Verified on the simulator: thermal nominal and low power false reach the
collector alongside app_state. Memory pressure has no simulator trigger.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
React Native follows the phones: thermal, low power and memory pressure belong to
the Rust core, which this package will bind over UniFFI, so livekit-client never
has to know a phone gets hot. The native monitors stay — they are the device half
of that design and they work — and the JS bridge that was carrying their values
is gone. What crosses from JavaScript is what a JavaScript runtime knows: the
platform's name and the app lifecycle.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…adence

LKBatchStore.swift and BatchStore.kt are a directory of batch files mirroring the
Rust core's FileCache: written before the network is tried, renamed into place so
a crash never leaves half a batch readable, pruned oldest-first above the byte,
count and age budgets, and one batch always survives. livekit-client stores
through it and never learns what it is.

Proved on the simulator: 36 batches cached with the collector down, app killed,
and all 45 records delivered on relaunch.

Device state comes back the same way — through two verbs that name no platform.
The RN package maps thermal, low power and memory pressure onto SPEC's events and
onto a cadence factor itself, so the mobile vocabulary stays here.

One gotcha worth the comment it got: a blocking synchronous method must return an
object. Two of these returned Bool, and the TurboModule interop retained it as a
pointer and segfaulted the app with no word JavaScript could see.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Sep 18, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: d63adbe

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@livekit/react-native Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pblazej pblazej mentioned this pull request Sep 18, 2026
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant