fix(android,ios): make native metrics attributable to a device and a footprint - #250
fix(android,ios): make native metrics attributable to a device and a footprint#250gmaclennan wants to merge 3 commits into
Conversation
…print Reading the `comapeo.app.exit` data we have collected so far turned up two things that make it much less useful than intended. It carries no device attributes. Grouping 512 exits by `device_class` returns a single "—" bucket, while `comapeo.boot.phase_duration_ms` next to it slices cleanly into low/mid/high. So we can see that the FGS was killed but not what kind of hardware killed it, which is the first question anyone asks of an OOM statistic. The backend's metrics layer injects `platform` / `device_class` / `os_major` centrally, but these emissions go to `Sentry.metrics()` directly — the main process has no `SentryFgsBridge.init` to hang shared attributes off — so thread them through the collector, which already has the Context. The footprint at death is unreadable. `pss_kb` / `rss_kb` are *numeric* attributes, and Explore cannot group by those — the same reason the durations here are already pre-bucketed strings. Emit them additionally as `comapeo.app.exit.rss_bytes` / `comapeo.app.exit.pss_bytes` distributions, carrying the same attributes, so the fleet question "how big was the process when the kernel took it" becomes a percentile query. A zero from `ApplicationExitInfo` (some reasons, some vendors) means "not measured" and is skipped rather than recorded as a real zero that would drag every percentile down. The raw numeric attributes stay, harmless and useful if Explore ever groups by them.
|
Verified end to end on a local Release build (Pixel_7a_API_34,
And the footprint at death is readable for the first time: Consistent with the running-process gauges from #249 on the same device: peak 256 MB, settled ~172 MB, died holding 183 MB. |
The same gap the exit metrics had applies to every metric emitted natively. `rootkey.load` (2,352 samples) and `rootkey.wrapper_key.created` (536) both come back with `device_class` and `platform` as "—", and so does the iOS `comapeo.app.exit` from MetricKit. StrongBox availability and keystore migration outcomes are close to being *device* facts, so not being able to slice them by device class or OS version wastes most of what they could tell us. Fix it at the wrapper rather than per call site: `countMetric` on both platforms now merges `platform` / `device_class` / `os_major` — the same names the backend's metrics layer injects centrally, so a native metric and a Node one slice in one Explore query. Call-site attributes win on a key collision. Computed once per process: the answer cannot change within one. The exit collector keeps threading them itself, since it calls `Sentry.metrics()` directly and the main process has no `SentryFgsBridge.init` to hang shared attributes off.
|
Extended after auditing the rest of the native metrics: the missing-device-attribution problem was not limited to the exit collector.
So the fix now lands at the wrapper instead of per call site — The exit collector still threads them itself, since it calls Mirrored |
|
Verified the extended fix on a local Release build (Pixel_7a_API_34,
|
… emitter Review follow-ups: Android metric emission (FGS bridge countMetric and the exit collector, both processes) now funnels through SentryMetricEmit, which injects the device attributes and runs SentryMetricScrub — closing the scrub gap on the exit-collector path and removing the manual deviceTags threading. DeviceTags.compute is memoized (and a failure can no longer abort SentryFgsBridge.init). iOS DeviceTags.compute drops the UIKit branch so the first (MetricKit background queue) caller never touches UIDevice off the main thread. Attribute names move to SentryTags constants on both platforms. New emitter-level tests pin the injection, merge direction, and scrub; docs correct the iOS exit-collector claim and the Node-parity overstatement, and note the report-time os_major caveat.
Reading the
comapeo.app.exitdata collected so far turned up two things that make it much less useful than intended.It carries no device attributes. Grouping 512 exits by
device_classreturns a single "—" bucket, whilecomapeo.boot.phase_duration_msnext to it slices cleanly into low/mid/high. We can see that the FGS was killed, but not what kind of hardware killed it — the first question anyone asks of an OOM statistic. Android metric emission now funnels through a small shared emitter,SentryMetricEmit, which injectsplatform/device_class/os_majorand runs the metric scrub before callingSentry.metrics()— one path serves the FGS bridge'scountMetricand the exit collector in both processes, so a call site can forget neither the attributes nor the scrub. On iOSSentryNativeBridge.countMetricdoes the same. Call-site attributes win on a key collision, andDeviceTags.computeis memoized (it is a process constant several startup paths ask for). The attribute names live inSentryTagson both platforms, pinned by tests against the backend's spelling.The footprint at death is unreadable.
pss_kb/rss_kbare numeric attributes and Explore cannot group by those — the same reason the durations here are already pre-bucketed strings. They are now additionally emitted ascomapeo.app.exit.rss_bytesandcomapeo.app.exit.pss_bytesdistributions carrying the same attributes, so "how big was the process when the kernel took it" becomes a percentile query. A zero fromApplicationExitInfo(some reasons, some vendors) means "not measured" and is skipped, rather than recorded as a real zero that would drag every percentile towards it.The raw numeric attributes stay — harmless, and useful if Explore ever groups by them.
These pair with
comapeo.backend.rss_peak_bytesfrom #249: one says what the process grew to, the other what it died holding. One documented caveat: exit records are decoded on the launch after the death, soos_majorreflects the OS at report time — exits straddling an OS update carry the new version.On iOS,
DeviceTags.compute()now reads onlyProcessInfo— its first caller is MetricKit's background delivery queue, which must not touchUIDeviceoff the main thread. iOS exits still come from MetricKit viaAppExitMetricsCollector(already routed throughcountMetric, so they pick up the attributes); MetricKit reports a daily aggregate window rather than per-process footprint, so there is no distribution equivalent to add there.Emitter-level JVM tests pin the attribute injection, the merge direction, and the scrub on both the count and distribution paths;
:comapeo-core-react-native:testDebugUnitTestis green at 161 tests and the Swift package tests at 146.