Skip to content

Commit f3a18f2

Browse files
denrasebuenaflor
andauthored
Start missing TTFD for root screen transaction (#2332)
* Handle TTFD for root screen transaction * add chengelog entry * cleanup * use options clock for app start end * use timeToDisplayTracker from options, remove timeToDisplayTracker from navigator ctor * fix test * fix analyze --------- Co-authored-by: GIancarlo Buenaflor <[email protected]>
1 parent ca9f398 commit f3a18f2

15 files changed

+341
-313
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ Navigator.push(
6363
- Rounding error used on frames.total and reject frame measurements if frames.total is less than frames.slow or frames.frozen ([#2308](https://github.com/getsentry/sentry-dart/pull/2308))
6464
- iOS replay integration when only `onErrorSampleRate` is specified ([#2306](https://github.com/getsentry/sentry-dart/pull/2306))
6565
- Fix TTID timing issue ([#2326](https://github.com/getsentry/sentry-dart/pull/2326))
66+
- Start missing TTFD for root screen transaction ([#2332](https://github.com/getsentry/sentry-dart/pull/2332))
6667
- Accessing invalid json fields from `fetchNativeAppStart` should return null ([#2340](https://github.com/getsentry/sentry-dart/pull/2340))
6768
- Error when calling `SentryFlutter.reportFullyDisplayed()` twice ([#2339](https://github.com/getsentry/sentry-dart/pull/2339))
6869

flutter/lib/src/integrations/native_app_start_handler.dart

Lines changed: 28 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class NativeAppStartHandler {
2121
static const _maxAppStartMillis = 60000;
2222

2323
Future<void> call(Hub hub, SentryFlutterOptions options,
24-
{required DateTime? appStartEnd}) async {
24+
{required DateTime appStartEnd}) async {
2525
_hub = hub;
2626
_options = options;
2727

@@ -42,43 +42,33 @@ class NativeAppStartHandler {
4242
SentrySpanOperations.uiLoad,
4343
startTimestamp: appStartInfo.start,
4444
);
45-
final ttidSpan = transaction.startChild(
46-
SentrySpanOperations.uiTimeToInitialDisplay,
47-
description: '$screenName initial display',
45+
46+
await options.timeToDisplayTracker.track(
47+
transaction,
4848
startTimestamp: appStartInfo.start,
49+
endTimestamp: appStartInfo.end,
50+
origin: SentryTraceOrigins.autoUiTimeToDisplay,
4951
);
5052

51-
// Enrich Transaction
52-
5353
SentryTracer sentryTracer;
5454
if (transaction is SentryTracer) {
5555
sentryTracer = transaction;
5656
} else {
5757
return;
5858
}
5959

60-
SentryMeasurement? measurement;
61-
if (options.autoAppStart) {
62-
measurement = appStartInfo.toMeasurement();
63-
} else if (appStartEnd != null) {
64-
appStartInfo.end = appStartEnd;
65-
measurement = appStartInfo.toMeasurement();
66-
}
67-
68-
if (measurement != null) {
69-
sentryTracer.measurements[measurement.name] = measurement;
70-
await _attachAppStartSpans(appStartInfo, sentryTracer);
71-
}
72-
73-
// Finish Transaction & Span
60+
// Enrich Transaction
61+
SentryMeasurement? measurement = appStartInfo.toMeasurement();
62+
sentryTracer.measurements[measurement.name] = appStartInfo.toMeasurement();
63+
await _attachAppStartSpans(appStartInfo, sentryTracer);
7464

75-
await ttidSpan.finish(endTimestamp: appStartInfo.end);
65+
// Finish Transaction
7666
await transaction.finish(endTimestamp: appStartInfo.end);
7767
}
7868

7969
_AppStartInfo? _infoNativeAppStart(
8070
NativeAppStart nativeAppStart,
81-
DateTime? appStartEnd,
71+
DateTime appStartEnd,
8272
) {
8373
final sentrySetupStartDateTime = SentryFlutter.sentrySetupStartTime;
8474
if (sentrySetupStartDateTime == null) {
@@ -90,23 +80,18 @@ class NativeAppStartHandler {
9080
final pluginRegistrationDateTime = DateTime.fromMillisecondsSinceEpoch(
9181
nativeAppStart.pluginRegistrationTime);
9282

93-
if (_options.autoAppStart) {
94-
// We only assign the current time if it's not already set - this is useful in tests
95-
appStartEnd ??= _options.clock();
96-
97-
final duration = appStartEnd.difference(appStartDateTime);
98-
99-
// We filter out app start more than 60s.
100-
// This could be due to many different reasons.
101-
// If you do the manual init and init the SDK too late and it does not
102-
// compute the app start end in the very first Screen.
103-
// If the process starts but the App isn't in the foreground.
104-
// If the system forked the process earlier to accelerate the app start.
105-
// And some unknown reasons that could not be reproduced.
106-
// We've seen app starts with hours, days and even months.
107-
if (duration.inMilliseconds > _maxAppStartMillis) {
108-
return null;
109-
}
83+
final duration = appStartEnd.difference(appStartDateTime);
84+
85+
// We filter out app start more than 60s.
86+
// This could be due to many different reasons.
87+
// If you do the manual init and init the SDK too late and it does not
88+
// compute the app start end in the very first Screen.
89+
// If the process starts but the App isn't in the foreground.
90+
// If the system forked the process earlier to accelerate the app start.
91+
// And some unknown reasons that could not be reproduced.
92+
// We've seen app starts with hours, days and even months.
93+
if (duration.inMilliseconds > _maxAppStartMillis) {
94+
return null;
11095
}
11196

11297
List<_TimeSpan> nativeSpanTimes = [];
@@ -145,9 +130,6 @@ class NativeAppStartHandler {
145130
_AppStartInfo appStartInfo, SentryTracer transaction) async {
146131
final transactionTraceId = transaction.context.traceId;
147132
final appStartEnd = appStartInfo.end;
148-
if (appStartEnd == null) {
149-
return;
150-
}
151133

152134
final appStartSpan = await _createAndFinishSpan(
153135
tracer: transaction,
@@ -256,30 +238,24 @@ class _AppStartInfo {
256238
_AppStartInfo(
257239
this.type, {
258240
required this.start,
241+
required this.end,
259242
required this.pluginRegistration,
260243
required this.sentrySetupStart,
261244
required this.nativeSpanTimes,
262-
this.end,
263245
});
264246

265247
final _AppStartType type;
266248
final DateTime start;
249+
final DateTime end;
267250
final List<_TimeSpan> nativeSpanTimes;
268251

269-
// We allow the end to be null, since it might be set at a later time
270-
// with setAppStartEnd when autoAppStart is disabled
271-
DateTime? end;
272-
273252
final DateTime pluginRegistration;
274253
final DateTime sentrySetupStart;
275254

276-
Duration? get duration => end?.difference(start);
255+
Duration get duration => end.difference(start);
277256

278-
SentryMeasurement? toMeasurement() {
257+
SentryMeasurement toMeasurement() {
279258
final duration = this.duration;
280-
if (duration == null) {
281-
return null;
282-
}
283259
return type == _AppStartType.cold
284260
? SentryMeasurement.coldAppStart(duration)
285261
: SentryMeasurement.warmAppStart(duration);

flutter/lib/src/integrations/native_app_start_integration.dart

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,25 @@ class NativeAppStartIntegration extends Integration<SentryFlutterOptions> {
3333
void call(Hub hub, SentryFlutterOptions options) async {
3434
_frameCallbackHandler.addPostFrameCallback((timeStamp) async {
3535
try {
36-
if (!options.autoAppStart && _appStartEnd == null) {
37-
await _appStartEndCompleter.future
38-
.timeout(const Duration(seconds: 10));
36+
DateTime? appStartEnd;
37+
if (options.autoAppStart) {
38+
// ignore: invalid_use_of_internal_member
39+
appStartEnd = options.clock();
40+
} else if (_appStartEnd == null) {
41+
await _appStartEndCompleter.future.timeout(
42+
const Duration(seconds: 10),
43+
);
44+
appStartEnd = _appStartEnd;
45+
} else {
46+
appStartEnd = null;
47+
}
48+
if (appStartEnd != null) {
49+
await _nativeAppStartHandler.call(
50+
hub,
51+
options,
52+
appStartEnd: appStartEnd,
53+
);
3954
}
40-
await _nativeAppStartHandler.call(
41-
hub,
42-
options,
43-
appStartEnd: _appStartEnd,
44-
);
4555
} catch (exception, stackTrace) {
4656
options.logger(
4757
SentryLevel.error,

flutter/lib/src/navigation/sentry_navigator_observer.dart

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ class SentryNavigatorObserver extends RouteObserver<PageRoute<dynamic>> {
7878
bool setRouteNameAsTransaction = false,
7979
RouteNameExtractor? routeNameExtractor,
8080
AdditionalInfoExtractor? additionalInfoProvider,
81-
@visibleForTesting TimeToDisplayTracker? timeToDisplayTracker,
8281
List<String>? ignoreRoutes,
8382
}) : _hub = hub ?? HubAdapter(),
8483
_enableAutoTransactions = enableAutoTransactions,
@@ -92,19 +91,17 @@ class SentryNavigatorObserver extends RouteObserver<PageRoute<dynamic>> {
9291
if (enableAutoTransactions) {
9392
_hub.options.sdk.addIntegration('UINavigationTracing');
9493
}
95-
_timeToDisplayTracker =
96-
timeToDisplayTracker ?? _initializeTimeToDisplayTracker();
94+
_timeToDisplayTracker = _initializeTimeToDisplayTracker();
9795
}
9896

9997
/// Initializes the TimeToDisplayTracker with the option to enable time to full display tracing.
100-
TimeToDisplayTracker _initializeTimeToDisplayTracker() {
101-
bool enableTimeToFullDisplayTracing = false;
98+
TimeToDisplayTracker? _initializeTimeToDisplayTracker() {
10299
final options = _hub.options;
103100
if (options is SentryFlutterOptions) {
104-
enableTimeToFullDisplayTracing = options.enableTimeToFullDisplayTracing;
101+
return options.timeToDisplayTracker;
102+
} else {
103+
return null;
105104
}
106-
return TimeToDisplayTracker(
107-
enableTimeToFullDisplayTracing: enableTimeToFullDisplayTracing);
108105
}
109106

110107
final Hub _hub;
@@ -115,11 +112,7 @@ class SentryNavigatorObserver extends RouteObserver<PageRoute<dynamic>> {
115112
final AdditionalInfoExtractor? _additionalInfoProvider;
116113
final SentryNativeBinding? _native;
117114
final List<String> _ignoreRoutes;
118-
static TimeToDisplayTracker? _timeToDisplayTracker;
119-
120-
@internal
121-
static TimeToDisplayTracker? get timeToDisplayTracker =>
122-
_timeToDisplayTracker;
115+
TimeToDisplayTracker? _timeToDisplayTracker;
123116

124117
ISentrySpan? _transaction;
125118

@@ -362,7 +355,7 @@ class SentryNavigatorObserver extends RouteObserver<PageRoute<dynamic>> {
362355
}
363356

364357
if (!isAppStart) {
365-
await _timeToDisplayTracker?.trackRegularRouteTTD(
358+
await _timeToDisplayTracker?.track(
366359
transaction,
367360
startTimestamp: startTimestamp,
368361
);

flutter/lib/src/navigation/time_to_display_tracker.dart

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,38 +9,50 @@ import 'time_to_initial_display_tracker.dart';
99
@internal
1010
class TimeToDisplayTracker {
1111
final TimeToInitialDisplayTracker _ttidTracker;
12-
final TimeToFullDisplayTracker? _ttfdTracker;
13-
final bool enableTimeToFullDisplayTracing;
12+
final TimeToFullDisplayTracker _ttfdTracker;
13+
final SentryFlutterOptions options;
1414

1515
TimeToDisplayTracker({
1616
TimeToInitialDisplayTracker? ttidTracker,
1717
TimeToFullDisplayTracker? ttfdTracker,
18-
required this.enableTimeToFullDisplayTracing,
18+
required this.options,
1919
}) : _ttidTracker = ttidTracker ?? TimeToInitialDisplayTracker(),
20-
_ttfdTracker = enableTimeToFullDisplayTracing
21-
? ttfdTracker ?? TimeToFullDisplayTracker()
22-
: null;
23-
24-
Future<void> trackRegularRouteTTD(ISentrySpan transaction,
25-
{required DateTime startTimestamp}) async {
26-
await _ttidTracker.trackRegularRoute(transaction, startTimestamp);
27-
await _trackTTFDIfEnabled(transaction, startTimestamp);
28-
}
29-
30-
Future<void> _trackTTFDIfEnabled(
31-
ISentrySpan transaction, DateTime startTimestamp) async {
32-
if (enableTimeToFullDisplayTracing) {
33-
await _ttfdTracker?.track(transaction, startTimestamp);
20+
_ttfdTracker = ttfdTracker ?? TimeToFullDisplayTracker();
21+
22+
Future<void> track(
23+
ISentrySpan transaction, {
24+
required DateTime startTimestamp,
25+
DateTime? endTimestamp,
26+
String? origin,
27+
}) async {
28+
// TTID
29+
await _ttidTracker.track(
30+
transaction: transaction,
31+
startTimestamp: startTimestamp,
32+
endTimestamp: endTimestamp,
33+
origin: origin,
34+
);
35+
36+
// TTFD
37+
if (options.enableTimeToFullDisplayTracing) {
38+
await _ttfdTracker.track(
39+
transaction: transaction,
40+
startTimestamp: startTimestamp,
41+
);
3442
}
3543
}
3644

3745
@internal
3846
Future<void> reportFullyDisplayed() async {
39-
return _ttfdTracker?.reportFullyDisplayed();
47+
if (options.enableTimeToFullDisplayTracing) {
48+
return _ttfdTracker.reportFullyDisplayed();
49+
}
4050
}
4151

4252
void clear() {
4353
_ttidTracker.clear();
44-
_ttfdTracker?.clear();
54+
if (options.enableTimeToFullDisplayTracing) {
55+
_ttfdTracker.clear();
56+
}
4557
}
4658
}

0 commit comments

Comments
 (0)