Desrciption
Part of
What Is TTFD?
TTFD measures the time from when a screen begins loading until it is fully loaded with all its data. Unlike TTID (which fires automatically at first frame), TTFD is finished manually by the developer calling Sentry.reportFullyDisplayed() once the screen has all its content.
- Span op:
ui.load.full_display
- Measurement key:
time_to_full_display (milliseconds)
- The span is a child of the
ui.load transaction (does not count against quota)
- Opt-in only — must be enabled via
enableTimeToFullDisplayTracing (disabled by default)
- Status must be
ok for the measurement to be recorded; deadline-exceeded spans get the TTID measurement value instead
- Requires TTID to exist — if no TTID span is found, TTFD is not created at all
References
Span Lifecycle and Status Rules
| Scenario |
End timestamp |
Status |
reportFullyDisplayed() called after TTID finishes |
Timestamp of the API call |
ok |
reportFullyDisplayed() called before TTID finishes |
Clamped forward to TTID end timestamp |
ok |
| 30-second timeout fires |
TTID end timestamp |
deadline_exceeded |
| New screen navigation begins before TTFD finishes |
TTID end timestamp |
deadline_exceeded |
enableTimeToFullDisplayTracing disabled |
Span never created; API calls ignored |
— |
| No active screen-load transaction |
API call ignored |
— |
When the deadline is exceeded, time_to_full_display is set equal to time_to_initial_display — not to 30 seconds.
How React Native Implements TTFD on iOS
The iOS implementation mirrors Android in structure but uses the Cocoa SDK's SentryFramesTracker (via CADisplayLink) rather than ViewTreeObserver. The same two-path architecture applies.
Component Path (<TimeToFullDisplay record />)
A RNSentryOnDrawReporterView (subclass of UIView) is embedded in the screen's component tree with fullDisplay={true} and parentSpanId. When rendered:
- A
RNSentryFramesTrackerListener is attached to the Cocoa SDK's SentryFramesTracker
SentryFramesTracker is driven by a CADisplayLink internally. On the next frame callback it calls framesTrackerHasNewFrame:(NSDate *)newFrameDate
- The listener removes itself immediately (one-shot), then stores
[newFrameDate timeIntervalSince1970] in a bounded LRU map (RNSentryTimeToDisplay) under "ttfd-{parentSpanId}"
30-Second Timeout
Identical to Android — a setTimeout of 30 000 ms is set in JS when the span is created. On expiry:
- Span status is set to
deadline_exceeded
- End timestamp is clamped to the TTID span's end timestamp
time_to_full_display measurement is set equal to time_to_initial_display
Calling reportFullyDisplayed() manually cancels the timer.
TTFD vs TTID Ordering Constraint
Same two enforcement points as Android:
- If the frame callback fires before TTID has a timestamp, a
fullDisplayBeforeInitialDisplay flag defers the TTFD end until TTID finishes
- In post-processing (
processEvent): if ttfdEnd < ttidEnd, the TTFD end is clamped to ttidEnd
App Start Timestamp Alignment
On the first screen (app startup), both TTID and TTFD start_timestamp are backdated to the native app start timestamp, and measurements are recomputed — identical to the alignment described in ttid-ios.md.
JS-Side Span Construction
Assembled in processEvent after the transaction flushes (not as a live span):
NATIVE.popTimeToDisplayFor("ttfd-{rootSpanId}") retrieves the stored frame timestamp
- End timestamp is clamped to TTID end if needed, then checked against 30 000 ms for deadline
- A
SpanJSON is constructed with op: "ui.load.full_display", origin: "manual.ui.time_to_display"
time_to_full_display measurement set from duration if ok, or copied from time_to_initial_display if deadline_exceeded
Key iOS API: SentryFramesTracker / CADisplayLink
The same SentryFramesTracker used for TTID and slow/frozen frames. TTFD uses it in exactly the same one-shot listener pattern — the only difference is what the timestamp represents (a frame rendered after the user signals completion, rather than the first frame of the screen).
SentryFramesTracker must be running (enableAutoPerformanceTracing set on the Cocoa SDK) for this to work. If it is not running, the draw callback never fires and TTFD falls back to the 30-second deadline path.
Quirks and Limitations
- Non-iOS targets (
TARGET_OS_IOS guard) — the CADisplayLink fallback returns empty on macOS / Catalyst; the SentryFramesTracker path has the same platform restriction
- Both iOS and Android cap the timestamp store at 50 concurrent entries (LRU eviction) to prevent memory leaks
- Frame data (
frames.total, frames.slow, frames.frozen) is attached to the TTFD span as span attributes using the same spanFrameDataMap as TTID, with a 60-second cleanup for spans that never end
Implications for .NET/MAUI (iOS)
- The native draw mechanism is the same as TTID —
SentryFramesTracker / CADisplayLink via PrivateSentrySDKOnly. No additional Cocoa binding work beyond what TTID requires
- The main difference from TTID is that the frame listener is registered in response to a user action (calling
ReportFullyDisplayed()), not automatically on screen appearance
- The .NET public API should be
SentrySDK.ReportFullyDisplayed() (or similar), gated behind EnableTimeToFullDisplayTracing in SentryOptions
- The 30-second deadline and TTFD-cannot-precede-TTID constraint both need to be implemented in managed code
enableAutoPerformanceTracing must be set on the Cocoa SDK options for SentryFramesTracker to be running
- Frame data attributes should be attached to the TTFD span, matching the TTID behaviour
Desrciption
Part of
What Is TTFD?
TTFD measures the time from when a screen begins loading until it is fully loaded with all its data. Unlike TTID (which fires automatically at first frame), TTFD is finished manually by the developer calling
Sentry.reportFullyDisplayed()once the screen has all its content.ui.load.full_displaytime_to_full_display(milliseconds)ui.loadtransaction (does not count against quota)enableTimeToFullDisplayTracing(disabled by default)okfor the measurement to be recorded; deadline-exceeded spans get the TTID measurement value insteadReferences
Span Lifecycle and Status Rules
reportFullyDisplayed()called after TTID finishesokreportFullyDisplayed()called before TTID finishesokdeadline_exceededdeadline_exceededenableTimeToFullDisplayTracingdisabledWhen the deadline is exceeded,
time_to_full_displayis set equal totime_to_initial_display— not to 30 seconds.How React Native Implements TTFD on iOS
The iOS implementation mirrors Android in structure but uses the Cocoa SDK's
SentryFramesTracker(viaCADisplayLink) rather thanViewTreeObserver. The same two-path architecture applies.Component Path (
<TimeToFullDisplay record />)A
RNSentryOnDrawReporterView(subclass ofUIView) is embedded in the screen's component tree withfullDisplay={true}andparentSpanId. When rendered:RNSentryFramesTrackerListeneris attached to the Cocoa SDK'sSentryFramesTrackerSentryFramesTrackeris driven by aCADisplayLinkinternally. On the next frame callback it callsframesTrackerHasNewFrame:(NSDate *)newFrameDate[newFrameDate timeIntervalSince1970]in a bounded LRU map (RNSentryTimeToDisplay) under"ttfd-{parentSpanId}"30-Second Timeout
Identical to Android — a
setTimeoutof 30 000 ms is set in JS when the span is created. On expiry:deadline_exceededtime_to_full_displaymeasurement is set equal totime_to_initial_displayCalling
reportFullyDisplayed()manually cancels the timer.TTFD vs TTID Ordering Constraint
Same two enforcement points as Android:
fullDisplayBeforeInitialDisplayflag defers the TTFD end until TTID finishesprocessEvent): ifttfdEnd < ttidEnd, the TTFD end is clamped tottidEndApp Start Timestamp Alignment
On the first screen (app startup), both TTID and TTFD
start_timestampare backdated to the native app start timestamp, and measurements are recomputed — identical to the alignment described inttid-ios.md.JS-Side Span Construction
Assembled in
processEventafter the transaction flushes (not as a live span):NATIVE.popTimeToDisplayFor("ttfd-{rootSpanId}")retrieves the stored frame timestampSpanJSONis constructed withop: "ui.load.full_display",origin: "manual.ui.time_to_display"time_to_full_displaymeasurement set from duration ifok, or copied fromtime_to_initial_displayifdeadline_exceededKey iOS API:
SentryFramesTracker/CADisplayLinkThe same
SentryFramesTrackerused for TTID and slow/frozen frames. TTFD uses it in exactly the same one-shot listener pattern — the only difference is what the timestamp represents (a frame rendered after the user signals completion, rather than the first frame of the screen).SentryFramesTrackermust be running (enableAutoPerformanceTracingset on the Cocoa SDK) for this to work. If it is not running, the draw callback never fires and TTFD falls back to the 30-second deadline path.Quirks and Limitations
TARGET_OS_IOSguard) — theCADisplayLinkfallback returns empty on macOS / Catalyst; theSentryFramesTrackerpath has the same platform restrictionframes.total,frames.slow,frames.frozen) is attached to the TTFD span as span attributes using the samespanFrameDataMapas TTID, with a 60-second cleanup for spans that never endImplications for .NET/MAUI (iOS)
SentryFramesTracker/CADisplayLinkviaPrivateSentrySDKOnly. No additional Cocoa binding work beyond what TTID requiresReportFullyDisplayed()), not automatically on screen appearanceSentrySDK.ReportFullyDisplayed()(or similar), gated behindEnableTimeToFullDisplayTracinginSentryOptionsenableAutoPerformanceTracingmust be set on the Cocoa SDK options forSentryFramesTrackerto be running