11import 'dart:async' ;
22
33import 'package:meta/meta.dart' ;
4+ import 'utils.dart' ;
45
56import '../sentry.dart' ;
67import 'sentry_tracer_finish_status.dart' ;
@@ -16,9 +17,25 @@ class SentryTracer extends ISentrySpan {
1617 final Map <String , dynamic > _extra = {};
1718 Timer ? _autoFinishAfterTimer;
1819 var _finishStatus = SentryTracerFinishStatus .notFinishing ();
19-
20+ late final bool _trimEnd;
21+
22+ /// If [waitForChildren] is true, this transaction will not finish until all
23+ /// its children are finished.
24+ ///
25+ /// When [autoFinishAfter] is provided, started transactions will
26+ /// automatically be finished after this duration.
27+ ///
28+ /// If [trimEnd] is true, sets the end timestamp of the transaction to the
29+ /// highest timestamp of child spans, trimming the duration of the
30+ /// transaction. This is useful to discard extra time in the transaction that
31+ /// is not accounted for in child spans, like what happens in the
32+ /// [SentryNavigatorObserver] idle transactions, where we finish the
33+ /// transaction after a given "idle time" and we don't want this "idle time"
34+ /// to be part of the transaction.
2035 SentryTracer (SentryTransactionContext transactionContext, this ._hub,
21- {bool waitForChildren = false , Duration ? autoFinishAfter}) {
36+ {bool waitForChildren = false ,
37+ Duration ? autoFinishAfter,
38+ bool trimEnd = false }) {
2239 _rootSpan = SentrySpan (
2340 this ,
2441 transactionContext,
@@ -32,6 +49,7 @@ class SentryTracer extends ISentrySpan {
3249 });
3350 }
3451 name = transactionContext.name;
52+ _trimEnd = trimEnd;
3553 }
3654
3755 @override
@@ -41,7 +59,6 @@ class SentryTracer extends ISentrySpan {
4159 if (! _rootSpan.finished &&
4260 (! _waitForChildren || _haveAllChildrenFinished ())) {
4361 _rootSpan.status ?? = status;
44- await _rootSpan.finish ();
4562
4663 // finish unfinished spans otherwise transaction gets dropped
4764 for (final span in _children) {
@@ -50,6 +67,23 @@ class SentryTracer extends ISentrySpan {
5067 }
5168 }
5269
70+ var _rootEndTimestamp = getUtcDateTime ();
71+ if (_trimEnd && children.isNotEmpty) {
72+ final childEndTimestamps = children
73+ .where ((child) => child.endTimestamp != null )
74+ .map ((child) => child.endTimestamp! );
75+
76+ if (childEndTimestamps.isNotEmpty) {
77+ final oldestChildEndTimestamp =
78+ childEndTimestamps.reduce ((a, b) => a.isAfter (b) ? a : b);
79+ if (_rootEndTimestamp.isAfter (oldestChildEndTimestamp)) {
80+ _rootEndTimestamp = oldestChildEndTimestamp;
81+ }
82+ }
83+ }
84+
85+ await _rootSpan.finish (endTimestamp: _rootEndTimestamp);
86+
5387 // remove from scope
5488 _hub.configureScope ((scope) {
5589 if (scope.span == this ) {
0 commit comments