Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
16 changes: 10 additions & 6 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 @@ -25,17 +26,20 @@ class SentryDisplay {
return;
}
try {
return options.timeToDisplayTracker.reportFullyDisplayed(
spanId: spanId,
);
if (options.traceLifecycle == SentryTraceLifecycle.streaming) {
options.timeToDisplayTrackerV2.reportFullyDisplayed(spanId);
} else {
await options.timeToDisplayTracker.reportFullyDisplayed(
spanId: spanId,
);
}
} catch (exception, stackTrace) {
if (options.automatedTestMode) {
rethrow;
}
options.log(
SentryLevel.error,
internalLogger.error(
'Error while reporting TTFD',
exception: exception,
error: exception,
stackTrace: stackTrace,
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import 'package:sentry/src/sentry_tracer.dart';
import '../../sentry_flutter.dart';
import '../event_processor/flutter_enricher_event_processor.dart';
import '../integrations/web_session_integration.dart';
import '../utils/internal_logger.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 +98,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 +116,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 +134,7 @@ class SentryNavigatorObserver extends RouteObserver<PageRoute<dynamic>> {
final AdditionalInfoExtractor? _additionalInfoProvider;
final List<String> _ignoreRoutes;
TimeToDisplayTracker? _timeToDisplayTracker;
TimeToDisplayTrackerV2? _timeToDisplayTrackerV2;

WebSessionHandler? _webSessionHandler;

Expand Down Expand Up @@ -214,8 +227,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 All @@ -226,17 +243,24 @@ class SentryNavigatorObserver extends RouteObserver<PageRoute<dynamic>> {

void _instrumentTimeToDisplayOnPush(String routeName, Object? arguments) {
if (!_enableAutoTransactions) {
internalLogger.info(
'SentryNavigatorObserver: auto transactions disabled, skipping time-to-display instrumentation.',
);
return;
}

// Clearing the display tracker here is safe since didPush happens before the Widget is built
_timeToDisplayTracker?.clear();
if (_hub.options.traceLifecycle == SentryTraceLifecycle.streaming) {
_timeToDisplayTrackerV2?.trackRoute(routeName);
} else {
// Clearing the display tracker here is safe since didPush happens before the Widget is built
_timeToDisplayTracker?.clear();

DateTime timestamp = _hub.options.clock();
_finishTransaction(endTimestamp: timestamp);
DateTime timestamp = _hub.options.clock();
_finishTransaction(endTimestamp: timestamp);

final transactionContext = _createTransactionContext(routeName);
_startTransaction(timestamp, transactionContext, arguments);
final transactionContext = _createTransactionContext(routeName);
_startTransaction(timestamp, transactionContext, arguments);
}
}

void _addWebSessions({Route<dynamic>? from, Route<dynamic>? to}) async {
Expand Down Expand Up @@ -346,10 +370,9 @@ class SentryNavigatorObserver extends RouteObserver<PageRoute<dynamic>> {
);
}
} catch (exception, stacktrace) {
_hub.options.log(
SentryLevel.error,
'Error while finishing transaction',
exception: exception,
internalLogger.error(
'SentryNavigatorObserver: error while finishing transaction',
error: exception,
stackTrace: stacktrace,
);
if (_hub.options.automatedTestMode) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// ignore_for_file: invalid_use_of_internal_member

import 'package:meta/meta.dart';

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

@internal
class TimeToDisplayTrackerV2 {
final Hub _hub;
final FrameCallbackHandler _frameCallbackHandler;
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),
});
final ttidSpan = _hub.startInactiveSpan(
'$routeName initial display',
parentSpan: routeSpan,
attributes: {
SemanticAttributesConstants.sentryOp:
SentryAttribute.string(SentrySpanOperations.uiTimeToInitialDisplay),
SemanticAttributesConstants.sentryOrigin: SentryAttribute.string(
SentryTraceOrigins.autoNavigationRouteObserver),
},
);

if (_hub.options
case SentryFlutterOptions(enableTimeToFullDisplayTracing: true)) {
_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) {
final ttfdSpanId = _ttfdSpan?.spanId;
if (ttfdSpanId != spanId) {
internalLogger.debug(
'Ignoring reportFullyDisplayed for span $spanId because active TTFD span is $ttfdSpanId.',
);
return;
}
_ttfdSpan?.end();
_ttfdSpan = null;
}

void cancelCurrentRoute() {
_ttfdSpan = null;

// Cancel any active idle span (navigation or user interaction) so
// startIdleSpan can create a fresh one on the next route.
final activeSpan = _hub.getActiveSpan();
if (activeSpan is IdleRecordingSentrySpanV2) {
activeSpan
..status = SentrySpanStatusV2.cancelled
..end();
}
}
}
21 changes: 16 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,23 @@ 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');
if (!options.enableTimeToFullDisplayTracing) {
internalLogger
.debug('TTFD is not enabled. Returning null for currentDisplay.');
return null;
}
return SentryDisplay(transactionId, hub: hub);
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(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
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// ignore_for_file: invalid_use_of_internal_member

import 'package:sentry_flutter/src/navigation/time_to_display_tracker_v2.dart';

class FakeTimeToDisplayTrackerV2 extends TimeToDisplayTrackerV2 {
final List<String> trackRouteCalls = [];
int cancelCurrentRouteCalls = 0;

@override
void trackRoute(String routeName) {
trackRouteCalls.add(routeName);
}

@override
void cancelCurrentRoute() {
cancelCurrentRouteCalls++;
}
}
Loading
Loading