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 Android
TTFD uses the same native draw-detection mechanism as TTID: FirstDrawDoneListener from sentry-android-core. The difference is purely in which timestamp is treated as the completion point — for TTID it is automatic (first frame of the screen), for TTFD it is the frame that renders after reportFullyDisplayed() is called (or the component prop is toggled).
Component Path (<TimeToFullDisplay record />)
A RNSentryOnDrawReporter view is embedded in the screen's component tree with fullDisplay={true} and parentSpanId. When rendered:
- Calls
FirstDrawDoneListener.registerForNextDraw(activity, callback, buildInfo) from the Sentry Android SDK
FirstDrawDoneListener is an OnDrawListener on the Activity's ViewTreeObserver — fires once on the next draw pass
- Records
SentryAndroidDateProvider.now().nanoTimestamp() / 1e9 and stores it in a bounded LRU map (RNSentryTimeToDisplay) under "ttfd-{parentSpanId}"
30-Second Timeout
The JS integration starts a setTimeout of 30 000 ms when the TTFD span is created. If reportFullyDisplayed() has not been called by then:
- The span status is set to
deadline_exceeded
- The span 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
Two enforcement points ensure TTFD never ends before TTID:
- If the native draw callback fires before TTID has a timestamp, a
fullDisplayBeforeInitialDisplay flag is set and the TTFD end is deferred 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 from that adjusted start — identical to the TTID alignment described in ttid-android.md.
JS-Side Span Construction
Assembled in processEvent after the transaction flushes (not as a live span):
NATIVE.popTimeToDisplayFor("ttfd-{rootSpanId}") retrieves the stored draw 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 Android API: FirstDrawDoneListener
The same FirstDrawDoneListener from sentry-android-core used for TTID. Since the Android SDK is already embedded in our project, this may be directly callable from the managed .NET layer — worth verifying before implementing an independent OnDrawListener.
Implications for .NET/MAUI (Android)
- The native draw mechanism is the same as TTID —
FirstDrawDoneListener / ViewTreeObserver.OnDrawListener. No additional Android API is needed beyond what TTID requires
- The main difference from TTID is that the draw listener is registered in response to a user action (calling
reportFullyDisplayed()), not automatically on screen creation
- 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
- Frame data (
frames.total, frames.slow, frames.frozen) should be attached to the TTFD span as span attributes, as with TTID
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 Android
TTFD uses the same native draw-detection mechanism as TTID:
FirstDrawDoneListenerfromsentry-android-core. The difference is purely in which timestamp is treated as the completion point — for TTID it is automatic (first frame of the screen), for TTFD it is the frame that renders afterreportFullyDisplayed()is called (or the component prop is toggled).Component Path (
<TimeToFullDisplay record />)A
RNSentryOnDrawReporterview is embedded in the screen's component tree withfullDisplay={true}andparentSpanId. When rendered:FirstDrawDoneListener.registerForNextDraw(activity, callback, buildInfo)from the Sentry Android SDKFirstDrawDoneListeneris anOnDrawListeneron the Activity'sViewTreeObserver— fires once on the next draw passSentryAndroidDateProvider.now().nanoTimestamp() / 1e9and stores it in a bounded LRU map (RNSentryTimeToDisplay) under"ttfd-{parentSpanId}"30-Second Timeout
The JS integration starts a
setTimeoutof 30 000 ms when the TTFD span is created. IfreportFullyDisplayed()has not been called by then:deadline_exceededtime_to_full_displaymeasurement is set equal totime_to_initial_displayCalling
reportFullyDisplayed()manually cancels the timer.TTFD vs TTID Ordering Constraint
Two enforcement points ensure TTFD never ends before TTID:
fullDisplayBeforeInitialDisplayflag is set and the TTFD end is deferred 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 from that adjusted start — identical to the TTID alignment described inttid-android.md.JS-Side Span Construction
Assembled in
processEventafter the transaction flushes (not as a live span):NATIVE.popTimeToDisplayFor("ttfd-{rootSpanId}")retrieves the stored draw 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 Android API:
FirstDrawDoneListenerThe same
FirstDrawDoneListenerfromsentry-android-coreused for TTID. Since the Android SDK is already embedded in our project, this may be directly callable from the managed .NET layer — worth verifying before implementing an independentOnDrawListener.Implications for .NET/MAUI (Android)
FirstDrawDoneListener/ViewTreeObserver.OnDrawListener. No additional Android API is needed beyond what TTID requiresreportFullyDisplayed()), not automatically on screen creationSentrySDK.ReportFullyDisplayed()(or similar), gated behindEnableTimeToFullDisplayTracinginSentryOptionsframes.total,frames.slow,frames.frozen) should be attached to the TTFD span as span attributes, as with TTID