Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/dart/lib/src/hub_adapter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ class HubAdapter implements Hub {
@override
SentrySpanV2 startIdleSpan(
String name, {
Duration idleTimeout = const Duration(seconds: 5),
Duration idleTimeout = const Duration(seconds: 3),
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

default should be 3 seconds

Duration finalTimeout = const Duration(seconds: 30),
bool trimIdleSpanEndTimestamp = true,
Map<String, SentryAttribute>? attributes,
Expand Down
2 changes: 1 addition & 1 deletion packages/dart/lib/src/noop_hub.dart
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ class NoOpHub implements Hub {
@override
SentrySpanV2 startIdleSpan(
String name, {
Duration idleTimeout = const Duration(seconds: 5),
Duration idleTimeout = const Duration(seconds: 3),
Duration finalTimeout = const Duration(seconds: 30),
bool trimIdleSpanEndTimestamp = true,
Map<String, SentryAttribute>? attributes,
Expand Down
4 changes: 2 additions & 2 deletions packages/flutter/example/lib/auto_close_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ class AutoCloseScreen extends StatefulWidget {
}

class AutoCloseScreenState extends State<AutoCloseScreen> {
final dio = Dio()..addSentry();

@override
void initState() {
super.initState();
_doComplexOperationThenClose();
}

Future<void> _doComplexOperationThenClose() async {
final dio = Dio();
dio.addSentry();
try {
// Add a bit of delay to demonstrate TTFD
await Future.delayed(const Duration(seconds: 3));
Expand Down
30 changes: 17 additions & 13 deletions packages/flutter/lib/src/navigation/sentry_display.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import '../../sentry_flutter.dart';
import '../utils/internal_logger.dart';

/// Represents the current route and allows to report the time to full display.
///
Expand All @@ -24,20 +25,23 @@ class SentryDisplay {
if (options is! SentryFlutterOptions) {
return;
}
try {
return options.timeToDisplayTracker.reportFullyDisplayed(
spanId: spanId,
);
} catch (exception, stackTrace) {
if (options.automatedTestMode) {
rethrow;
if (options.traceLifecycle == SentryTraceLifecycle.streaming) {
return options.timeToDisplayTrackerV2.reportFullyDisplayed(spanId);
} else {
try {
return options.timeToDisplayTracker.reportFullyDisplayed(
spanId: spanId,
);
} catch (exception, stackTrace) {
if (options.automatedTestMode) {
rethrow;
}
internalLogger.error(
'Error while reporting TTFD',
error: exception,
stackTrace: stackTrace,
);
}
options.log(
SentryLevel.error,
'Error while reporting TTFD',
exception: exception,
stackTrace: stackTrace,
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import '../event_processor/flutter_enricher_event_processor.dart';
import '../integrations/web_session_integration.dart';
import '../web/web_session_handler.dart';
import 'time_to_display_tracker.dart';
import 'time_to_display_tracker_v2.dart';

/// This key must be used so that the web interface displays the events nicely
/// See https://develop.sentry.dev/sdk/event-payloads/breadcrumbs/
Expand Down Expand Up @@ -96,6 +97,7 @@ class SentryNavigatorObserver extends RouteObserver<PageRoute<dynamic>> {
_hub.options.sdk.addIntegration('UINavigationTracing');
}
_timeToDisplayTracker = _initializeTimeToDisplayTracker();
_timeToDisplayTrackerV2 = _initializeTimeToDisplayTrackerV2();
final webSessionIntegration = _hub.options.integrations
.whereType<WebSessionIntegration>()
.firstOrNull;
Expand All @@ -113,6 +115,15 @@ class SentryNavigatorObserver extends RouteObserver<PageRoute<dynamic>> {
}
}

TimeToDisplayTrackerV2? _initializeTimeToDisplayTrackerV2() {
final options = _hub.options;
if (options is SentryFlutterOptions) {
return options.timeToDisplayTrackerV2;
} else {
return null;
}
}

final Hub _hub;
final bool _enableAutoTransactions;
final bool _enableNewTraceOnNavigation;
Expand All @@ -122,6 +133,7 @@ class SentryNavigatorObserver extends RouteObserver<PageRoute<dynamic>> {
final AdditionalInfoExtractor? _additionalInfoProvider;
final List<String> _ignoreRoutes;
TimeToDisplayTracker? _timeToDisplayTracker;
TimeToDisplayTrackerV2? _timeToDisplayTrackerV2;

WebSessionHandler? _webSessionHandler;

Expand Down Expand Up @@ -167,7 +179,11 @@ class SentryNavigatorObserver extends RouteObserver<PageRoute<dynamic>> {
_startNewTraceIfEnabled();

// App start TTID/TTFD is taken care of by app start integrations
_instrumentTimeToDisplayOnPush(routeName, route.settings.arguments);
if (_hub.options.traceLifecycle == SentryTraceLifecycle.streaming) {
_timeToDisplayTrackerV2?.trackRoute(routeName);
} else {
_instrumentTimeToDisplayOnPush(routeName, route.settings.arguments);
}
}
}

Expand Down Expand Up @@ -214,8 +230,12 @@ class SentryNavigatorObserver extends RouteObserver<PageRoute<dynamic>> {

_addWebSessions(from: route, to: previousRoute);

final timestamp = _hub.options.clock();
_finishTransaction(endTimestamp: timestamp);
if (_hub.options.traceLifecycle == SentryTraceLifecycle.streaming) {
_timeToDisplayTrackerV2?.cancelCurrentRoute();
} else {
final timestamp = _hub.options.clock();
_finishTransaction(endTimestamp: timestamp);
}
}

void _startNewTraceIfEnabled() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// ignore_for_file: invalid_use_of_internal_member

import 'package:meta/meta.dart';

import '../../sentry_flutter.dart';
import '../frame_callback_handler.dart';

@internal
class TimeToDisplayTrackerV2 {
final Hub _hub;
final FrameCallbackHandler _frameCallbackHandler;
SentrySpanV2? _routeSpan;
SentrySpanV2? _ttfdSpan;

TimeToDisplayTrackerV2({
Hub? hub,
FrameCallbackHandler? frameCallbackHandler,
}) : _hub = hub ?? HubAdapter(),
_frameCallbackHandler =
frameCallbackHandler ?? DefaultFrameCallbackHandler();

SpanId? get ttfdSpanId => _ttfdSpan?.spanId;

void trackRoute(String routeName) {
cancelCurrentRoute();

final routeSpan = _hub.startIdleSpan(routeName, attributes: {
SemanticAttributesConstants.sentryOp:
SentryAttribute.string(SentrySpanOperations.uiLoad),
SemanticAttributesConstants.sentryOrigin: SentryAttribute.string(
SentryTraceOrigins.autoNavigationRouteObserver),
});
_routeSpan = routeSpan;

final ttidSpan = _hub.startInactiveSpan(
'$routeName initial display',
parentSpan: routeSpan,
attributes: {
SemanticAttributesConstants.sentryOp:
SentryAttribute.string(SentrySpanOperations.uiTimeToInitialDisplay),
SemanticAttributesConstants.sentryOrigin: SentryAttribute.string(
SentryTraceOrigins.autoNavigationRouteObserver),
},
);

_ttfdSpan = _hub.startInactiveSpan(
'$routeName full display',
parentSpan: routeSpan,
attributes: {
SemanticAttributesConstants.sentryOp:
SentryAttribute.string(SentrySpanOperations.uiTimeToFullDisplay),
SemanticAttributesConstants.sentryOrigin: SentryAttribute.string(
SentryTraceOrigins.autoNavigationRouteObserver),
},
);

_frameCallbackHandler.addPostFrameCallback((_) {
ttidSpan.end();
});
}

void reportFullyDisplayed(SpanId spanId) {
if (_ttfdSpan?.spanId != spanId) return;
_ttfdSpan?.end();
_ttfdSpan = null;
}

void cancelCurrentRoute() {
final ttfdSpan = _ttfdSpan;
if (ttfdSpan != null && !ttfdSpan.isEnded) {
ttfdSpan
..status = SentrySpanStatusV2.cancelled
..end();
}
_ttfdSpan = null;

final routeSpan = _routeSpan;
if (routeSpan != null && !routeSpan.isEnded) {
routeSpan
..status = SentrySpanStatusV2.cancelled
..end();
}
_routeSpan = null;
}
}
16 changes: 11 additions & 5 deletions packages/flutter/lib/src/sentry_flutter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import 'native/sentry_native_binding.dart';
import 'profiling.dart';
import 'replay/integration.dart';
import 'screenshot/screenshot_support.dart';
import 'utils/internal_logger.dart';
import 'utils/platform_dispatcher_wrapper.dart';
import 'version.dart';
import 'view_hierarchy/view_hierarchy_integration.dart';
Expand Down Expand Up @@ -313,13 +314,18 @@ mixin SentryFlutter {
if (options is! SentryFlutterOptions) {
return null;
}
final transactionId = options.timeToDisplayTracker.transactionId;
if (transactionId == null) {
hub.options.log(SentryLevel.error,
'Could not process TTFD for screen ${SentryNavigatorObserver.currentRouteName} - transactionId should not be null');
final SpanId? spanId;
if (options.traceLifecycle == SentryTraceLifecycle.streaming) {
spanId = options.timeToDisplayTrackerV2.ttfdSpanId;
} else {
spanId = options.timeToDisplayTracker.transactionId;
}
if (spanId == null) {
internalLogger.error(
'Could not process TTFD for screen ${SentryNavigatorObserver.currentRouteName} - spanId should not be null');
return null;
}
return SentryDisplay(transactionId, hub: hub);
return SentryDisplay(spanId, hub: hub);
}

/// Pauses the app hang tracking.
Expand Down
4 changes: 4 additions & 0 deletions packages/flutter/lib/src/sentry_flutter_options.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:sentry/sentry.dart';
import 'binding_wrapper.dart';
import 'event_processor/screenshot_event_processor.dart';
import 'navigation/time_to_display_tracker.dart';
import 'navigation/time_to_display_tracker_v2.dart';
import 'renderer/renderer.dart';
import 'screenshot/sentry_screenshot_quality.dart';
import 'sentry_privacy_options.dart';
Expand Down Expand Up @@ -219,6 +220,9 @@ class SentryFlutterOptions extends SentryOptions {
options: this,
);

@meta.internal
late TimeToDisplayTrackerV2 timeToDisplayTrackerV2 = TimeToDisplayTrackerV2();

/// Sets the Proguard uuid for Android platform.
String? proguardUuid;

Expand Down
Loading
Loading