Skip to content

Commit 69e4d3e

Browse files
committed
update
1 parent 16c5ca0 commit 69e4d3e

7 files changed

+298
-202
lines changed

dart/lib/src/sentry_trace_origins.dart

+3
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,7 @@ class SentryTraceOrigins {
2727
static const autoDbDriftQueryExecutor = 'auto.db.drift.query.executor';
2828
static const autoDbDriftTransactionExecutor =
2929
'auto.db.drift.transaction.executor';
30+
static const uiLoad = 'ui.load';
31+
static const uiTimeToInitialDisplay = 'ui.load.initial_display';
32+
static const uiTimeToFullDisplay = 'ui.load.full_display';
3033
}

flutter/example/lib/main.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ Future<void> setupSentry(AppRunner appRunner, String dsn,
7676
// We can enable Sentry debug logging during development. This is likely
7777
// going to log too much for your app, but can be useful when figuring out
7878
// configuration issues, e.g. finding out why your events are not uploaded.
79-
options.debug = false;
79+
options.debug = true;
8080

8181
options.maxRequestBodySize = MaxRequestBodySize.always;
8282
options.maxResponseBodySize = MaxResponseBodySize.always;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import 'dart:async';
2+
3+
import 'package:flutter/scheduler.dart';
4+
import 'package:meta/meta.dart';
5+
6+
import '../../sentry_flutter.dart';
7+
import '../integrations/integrations.dart';
8+
import '../native/sentry_native.dart';
9+
import 'navigation_transaction_manager.dart';
10+
11+
@internal
12+
class NavigationTimingManager {
13+
static NavigationTimingManager? _instance;
14+
final Hub _hub;
15+
final Duration _autoFinishAfter;
16+
final SentryNative? _native;
17+
late final NavigationTransactionManager? _transactionManager;
18+
19+
static ISentrySpan? _ttidSpan;
20+
static ISentrySpan? _ttfdSpan;
21+
static DateTime? _startTimestamp;
22+
23+
NavigationTimingManager._({
24+
Hub? hub,
25+
Duration autoFinishAfter = const Duration(seconds: 3),
26+
SentryNative? native,
27+
}) : _hub = hub ?? HubAdapter(),
28+
_autoFinishAfter = autoFinishAfter,
29+
_native = native {
30+
_transactionManager =
31+
NavigationTransactionManager(_hub, _native, _autoFinishAfter);
32+
}
33+
34+
factory NavigationTimingManager({
35+
Hub? hub,
36+
Duration autoFinishAfter = const Duration(seconds: 3),
37+
}) {
38+
_instance ??= NavigationTimingManager._(
39+
hub: hub ?? HubAdapter(),
40+
autoFinishAfter: autoFinishAfter,
41+
native: SentryFlutter.native,
42+
);
43+
44+
return _instance!;
45+
}
46+
47+
void startMeasurement(String routeName) async {
48+
final options = _hub.options is SentryFlutterOptions
49+
// ignore: invalid_use_of_internal_member
50+
? _hub.options as SentryFlutterOptions
51+
: null;
52+
53+
// This marks the start timestamp of both TTID and TTFD spans
54+
_startTimestamp = DateTime.now();
55+
56+
// This has multiple branches
57+
// - normal screen navigation -> affects all screens
58+
// - app start navigation -> only affects root screen
59+
final isRootScreen = routeName == '/' || routeName == 'root ("/")';
60+
final didFetchAppStart = _native?.didFetchAppStart;
61+
if (isRootScreen && didFetchAppStart == false) {
62+
// App start - this is a special edge case that only happens once
63+
AppStartTracker().onAppStartComplete((appStartInfo) {
64+
// Create a transaction based on app start start time
65+
// Create ttidSpan and finish immediately with the app start start & end time
66+
// This is a small workaround to pass the correct time stamps since we mutate
67+
// timestamps of transactions or spans in history
68+
if (appStartInfo != null) {
69+
final transaction = _transactionManager?.startTransaction(
70+
routeName, appStartInfo.start);
71+
if (transaction != null) {
72+
final ttidSpan = _startTimeToInitialDisplaySpan(
73+
routeName, transaction, appStartInfo.start);
74+
ttidSpan.finish(endTimestamp: appStartInfo.end);
75+
}
76+
}
77+
});
78+
} else {
79+
DateTime? approximationEndTime;
80+
final endTimeCompleter = Completer<DateTime>();
81+
final transaction =
82+
_transactionManager?.startTransaction(routeName, _startTimestamp!);
83+
84+
if (transaction != null) {
85+
if (options?.enableTimeToFullDisplayTracing == true) {
86+
_ttfdSpan = transaction.startChild('ui.load.full_display',
87+
description: '$routeName full display',
88+
startTimestamp: _startTimestamp!);
89+
90+
_ttfdSpan = _startTimeToFullDisplaySpan(
91+
routeName, transaction, _startTimestamp!);
92+
}
93+
_ttidSpan = _startTimeToInitialDisplaySpan(
94+
routeName, transaction, _startTimestamp!);
95+
}
96+
97+
SchedulerBinding.instance.addPostFrameCallback((timeStamp) {
98+
approximationEndTime = DateTime.now();
99+
endTimeCompleter.complete(approximationEndTime);
100+
});
101+
102+
final strategyDecision =
103+
await SentryDisplayTracker().decideStrategyWithTimeout2(routeName);
104+
105+
switch (strategyDecision) {
106+
case StrategyDecision.manual:
107+
final endTimestamp = DateTime.now();
108+
final duration = endTimestamp.millisecondsSinceEpoch -
109+
_startTimestamp!.millisecondsSinceEpoch;
110+
_endTimeToInitialDisplaySpan(_ttidSpan!, transaction!, endTimestamp, duration);
111+
break;
112+
case StrategyDecision.approximation:
113+
if (approximationEndTime == null) {
114+
await endTimeCompleter.future;
115+
}
116+
final duration = approximationEndTime!.millisecondsSinceEpoch -
117+
_startTimestamp!.millisecondsSinceEpoch;
118+
_endTimeToInitialDisplaySpan(
119+
_ttidSpan!, transaction!, approximationEndTime!, duration);
120+
await _ttidSpan?.finish(endTimestamp: approximationEndTime);
121+
break;
122+
default:
123+
print('Unknown strategy decision: $strategyDecision');
124+
}
125+
}
126+
}
127+
128+
void reportInitiallyDisplayed(String routeName) {
129+
SentryDisplayTracker().reportManual2(routeName);
130+
}
131+
132+
void reportFullyDisplayed() {
133+
final endTime = DateTime.now();
134+
final transaction = Sentry.getSpan();
135+
final duration = endTime.millisecondsSinceEpoch -
136+
_startTimestamp!.millisecondsSinceEpoch;
137+
if (_ttfdSpan != null && transaction != null) {
138+
_endTimeToFullDisplaySpan(_ttfdSpan!, transaction, endTime, duration);
139+
}
140+
}
141+
142+
static ISentrySpan _startTimeToInitialDisplaySpan(
143+
String routeName, ISentrySpan transaction, DateTime startTimestamp) {
144+
return transaction.startChild(SentryTraceOrigins.uiTimeToInitialDisplay,
145+
description: '$routeName initial display',
146+
startTimestamp: startTimestamp);
147+
}
148+
149+
static ISentrySpan _startTimeToFullDisplaySpan(
150+
String routeName, ISentrySpan transaction, DateTime startTimestamp) {
151+
return transaction.startChild(SentryTraceOrigins.uiTimeToFullDisplay,
152+
description: '$routeName full display', startTimestamp: startTimestamp);
153+
}
154+
155+
static void _endTimeToInitialDisplaySpan(ISentrySpan ttidSpan,
156+
ISentrySpan transaction, DateTime endTimestamp, int duration) async {
157+
transaction.setMeasurement('time_to_initial_display', duration,
158+
unit: DurationSentryMeasurementUnit.milliSecond);
159+
await ttidSpan.finish(endTimestamp: endTimestamp);
160+
}
161+
162+
static void _endTimeToFullDisplaySpan(ISentrySpan ttfdSpan,
163+
ISentrySpan transaction, DateTime endTimestamp, int duration) async {
164+
transaction.setMeasurement('time_to_full_display', duration,
165+
unit: DurationSentryMeasurementUnit.milliSecond);
166+
await ttfdSpan.finish(endTimestamp: endTimestamp);
167+
}
168+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import '../../sentry_flutter.dart';
2+
import '../native/sentry_native.dart';
3+
4+
class NavigationTransactionManager {
5+
final Hub _hub;
6+
final SentryNative? _native;
7+
final Duration _autoFinishAfter;
8+
9+
NavigationTransactionManager(this._hub, this._native, this._autoFinishAfter);
10+
11+
ISentrySpan startTransaction(String routeName, DateTime startTime) {
12+
final transactionContext = SentryTransactionContext(
13+
routeName,
14+
'ui.load',
15+
transactionNameSource: SentryTransactionNameSource.component,
16+
origin: SentryTraceOrigins.autoNavigationRouteObserver,
17+
);
18+
19+
return _hub.startTransactionWithContext(
20+
transactionContext,
21+
waitForChildren: true,
22+
autoFinishAfter: _autoFinishAfter,
23+
trimEnd: true,
24+
startTimestamp: startTime,
25+
bindToScope: true,
26+
onFinish: (transaction) async {
27+
final nativeFrames = await _native
28+
?.endNativeFramesCollection(transaction.context.traceId);
29+
if (nativeFrames != null) {
30+
final measurements = nativeFrames.toMeasurements();
31+
for (final item in measurements.entries) {
32+
final measurement = item.value;
33+
transaction.setMeasurement(
34+
item.key,
35+
measurement.value,
36+
unit: measurement.unit,
37+
);
38+
}
39+
}
40+
},
41+
);
42+
}
43+
}

0 commit comments

Comments
 (0)