getsentry / getsentry/sentry-dotnet

Implement App Start metrics on iOS

Open
#5,098 1 comment 0 reactions 0 assignees View on GitHub
.NET Feature
Dominant language
C#
Stars
770
Forks
248
Avg merge
3d 4h
Merged PRs (30d)
49

Description

# Description

Part of

* [NET-185](https://linear.app/getsentry/issue/NET-185/mobile-vitals)

See also

* [NET-638](https://linear.app/getsentry/issue/NET-638/implement-app-start-metrics-on-android)

The same concept as Android applies — measure from the earliest native process initialization to first frame rendered. The overall span/transaction structure is identical (`ui.load` transaction, `app.start.cold` / `app.start.warm` child span, measurements). See [app-starts-android.md]() for the shared concepts.

iOS has one additional wrinkle: **pre-warmed starts**. Since iOS 15, the OS can pre-initialize the app process before the user taps the icon, making the measured start appear artificially short. The Cocoa SDK detects this and handles it as a distinct case.

### References

* [Mobile Vitals product docs]()
* [App Starts SDK spec]()
* [React Native initial implementation (PR #1704)]()
* [React Native refactor to standalone integration (PR #3852)]()
* [sentry-dotnet tracking issue]()

---

## How React Native Solves It on iOS

The flow mirrors Android — RN reads timing data from the native Cocoa SDK once and attaches it to the first navigation transaction. The differences are in the data shape and available sub-spans.

### Data Access

RN calls `PrivateSentrySDKOnly.appStartMeasurementWithSpans` (an internal Cocoa SDK API designed specifically for hybrid SDKs). This returns a dictionary like:

```objc
@{
@"type": @"cold" | @"warm" | @"unknown",
@"is_pre_warmed": @YES | @NO,
@"app_start_timestamp_ms": ,
@"runtime_init_timestamp_ms": ,
@"module_initialization_timestamp_ms": ,
@"sdk_start_timestamp_ms": ,
@"spans": @[
@{ @"description": @"...", @"start_timestamp_ms": ..., @"end_timestamp_ms": ... },
...
]
}
```

### iOS Native Sub-Spans

The Cocoa SDK's sub-spans differ from Android and vary based on whether the start was pre-warmed. All sub-spans use the same op as their parent (`app.start.cold` / `app.start.warm`).

**Non-pre-warmed cold start:**

| Description | Timing |
| -- | -- |
| `"Pre Runtime Init"` | App start timestamp → runtime init timestamp |
| `"Runtime Init to Pre Main Initializers"` | Runtime init → module initialization timestamp |
| `"UIKit Init"` | Module initialization → SDK start timestamp |
| `"Application Init"` | SDK start → `didFinishLaunching` timestamp |
| `"Initial Frame Render"` | `didFinishLaunching` → app start end |

**Pre-warmed start (only one sub-span):**

| Description | Timing |
| -- | -- |
| `"UIKit Init"` | Module initialization → SDK start timestamp |

### The Hybrid SDK Limitation

When the Cocoa SDK is initialized by a hybrid SDK (like RN or our .NET SDK), it **misses the** `didFinishLaunchNotification` because the hybrid layer initializes after that point. This means:

* `"Application Init"` and `"Initial Frame Render"` sub-spans are **not available** to hybrid SDKs
* The data returned via `appStartMeasurementWithSpans` only includes the spans up to SDK start time

This is a known limitation and accepted as-is — hybrid SDKs get fewer sub-spans than native-only iOS apps.

### Prerequisite for .NET

The Cocoa bindings in `ApiDefinitions.cs` do **not** currently expose `appStartMeasurementWithSpans` or `appStartMeasurement`. To implement this for iOS, these would need to be added (e.g. via `scripts/patch-cocoa-bindings.cs`).

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.