From 69f205b0e9e3e6de5089cb738ecd76deba2652bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20Andra=C5=A1ec?= Date: Mon, 17 Jan 2022 17:37:37 +0100 Subject: [PATCH 1/8] trim end in tracer --- dart/lib/src/protocol/sentry_span.dart | 15 +++---- dart/lib/src/sentry_tracer.dart | 25 ++++++++++- dart/test/sentry_tracer_test.dart | 58 +++++++++++++++++++++++--- 3 files changed, 83 insertions(+), 15 deletions(-) diff --git a/dart/lib/src/protocol/sentry_span.dart b/dart/lib/src/protocol/sentry_span.dart index d262e85f30..4251664214 100644 --- a/dart/lib/src/protocol/sentry_span.dart +++ b/dart/lib/src/protocol/sentry_span.dart @@ -9,7 +9,7 @@ import '../utils.dart'; class SentrySpan extends ISentrySpan { final SentrySpanContext _context; - DateTime? _timestamp; + DateTime? _endTimestamp; final DateTime _startTimestamp = getUtcDateTime(); final Hub _hub; @@ -36,7 +36,7 @@ class SentrySpan extends ISentrySpan { } @override - Future finish({SpanStatus? status}) async { + Future finish({SpanStatus? status, DateTime? endTimestamp}) async { if (finished) { return; } @@ -44,7 +44,7 @@ class SentrySpan extends ISentrySpan { if (status != null) { _status = status; } - _timestamp = getUtcDateTime(); + _endTimestamp = endTimestamp ?? getUtcDateTime(); // associate error if (_throwable != null) { @@ -116,7 +116,7 @@ class SentrySpan extends ISentrySpan { DateTime get startTimestamp => _startTimestamp; @override - DateTime? get endTimestamp => _timestamp; + DateTime? get endTimestamp => _endTimestamp; @override SentrySpanContext get context => _context; @@ -125,8 +125,9 @@ class SentrySpan extends ISentrySpan { final json = _context.toJson(); json['start_timestamp'] = formatDateAsIso8601WithMillisPrecision(_startTimestamp); - if (_timestamp != null) { - json['timestamp'] = formatDateAsIso8601WithMillisPrecision(_timestamp!); + if (_endTimestamp != null) { + json['timestamp'] = + formatDateAsIso8601WithMillisPrecision(_endTimestamp!); } if (_data.isNotEmpty) { json['data'] = _data; @@ -141,7 +142,7 @@ class SentrySpan extends ISentrySpan { } @override - bool get finished => _timestamp != null; + bool get finished => _endTimestamp != null; @override dynamic get throwable => _throwable; diff --git a/dart/lib/src/sentry_tracer.dart b/dart/lib/src/sentry_tracer.dart index d6a37eda07..9dd8b8e7ce 100644 --- a/dart/lib/src/sentry_tracer.dart +++ b/dart/lib/src/sentry_tracer.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:meta/meta.dart'; +import 'utils.dart'; import '../sentry.dart'; import 'sentry_tracer_finish_status.dart'; @@ -16,9 +17,12 @@ class SentryTracer extends ISentrySpan { final Map _extra = {}; Timer? _autoFinishAfterTimer; var _finishStatus = SentryTracerFinishStatus.notFinishing(); + var _trimEnd = false; SentryTracer(SentryTransactionContext transactionContext, this._hub, - {bool waitForChildren = false, Duration? autoFinishAfter}) { + {bool waitForChildren = false, + Duration? autoFinishAfter, + bool trimEnd = false}) { _rootSpan = SentrySpan( this, transactionContext, @@ -32,6 +36,7 @@ class SentryTracer extends ISentrySpan { }); } name = transactionContext.name; + _trimEnd = trimEnd; } @override @@ -41,7 +46,23 @@ class SentryTracer extends ISentrySpan { if (!_rootSpan.finished && (!_waitForChildren || _haveAllChildrenFinished())) { _rootSpan.status ??= status; - await _rootSpan.finish(); + + var _rootEndTimestamp = getUtcDateTime(); + if (_trimEnd && children.isNotEmpty) { + final childEndTimestamps = children + .where((child) => child.endTimestamp != null) + .map((child) => child.endTimestamp!); + + if (childEndTimestamps.isNotEmpty) { + final oldestChildEndTimestamp = + childEndTimestamps.reduce((a, b) => a.isAfter(b) ? a : b); + if (_rootEndTimestamp.isAfter(oldestChildEndTimestamp)) { + _rootEndTimestamp = oldestChildEndTimestamp; + } + } + } + + await _rootSpan.finish(endTimestamp: _rootEndTimestamp); // finish unfinished spans otherwise transaction gets dropped for (final span in _children) { diff --git a/dart/test/sentry_tracer_test.dart b/dart/test/sentry_tracer_test.dart index 9f41214f8d..27c4a88ac7 100644 --- a/dart/test/sentry_tracer_test.dart +++ b/dart/test/sentry_tracer_test.dart @@ -236,6 +236,53 @@ void main() { await sut.finish(); expect(sut.finished, true); }); + + test('end trimmed to last child', () async { + final sut = fixture.getSut( + trimEnd: true, autoFinishAfter: Duration(milliseconds: 200)); + + final childA = sut.startChild('operation-a', description: 'description'); + final childB = sut.startChild('operation-b', description: 'description'); + + await childA.finish(); + await Future.delayed(Duration(milliseconds: 10)); + await childB.finish(); + await Future.delayed(Duration(milliseconds: 210)); + + expect(sut.endTimestamp, childB.endTimestamp); + }); + + test('end trimmed to child', () async { + final sut = fixture.getSut( + trimEnd: true, autoFinishAfter: Duration(milliseconds: 200)); + + final childA = sut.startChild('operation-a', description: 'description'); + + await childA.finish(); + await Future.delayed(Duration(milliseconds: 210)); + + expect(sut.endTimestamp, childA.endTimestamp); + }); + + test('end not trimmed when no child', () async { + final sut = fixture.getSut( + trimEnd: true, autoFinishAfter: Duration(milliseconds: 200)); + + await Future.delayed(Duration(milliseconds: 210)); + + expect(sut.endTimestamp, isNotNull); + }); + + test('end not trimmed when no finished child', () async { + final sut = fixture.getSut( + trimEnd: true, autoFinishAfter: Duration(milliseconds: 200)); + + sut.startChild('operation-a', description: 'description'); + + await Future.delayed(Duration(milliseconds: 210)); + + expect(sut.endTimestamp, isNotNull); + }); } class Fixture { @@ -244,6 +291,7 @@ class Fixture { SentryTracer getSut({ bool? sampled = true, bool waitForChildren = false, + bool trimEnd = false, Duration? autoFinishAfter, }) { final context = SentryTransactionContext( @@ -251,11 +299,9 @@ class Fixture { 'op', sampled: sampled, ); - return SentryTracer( - context, - hub, - waitForChildren: waitForChildren, - autoFinishAfter: autoFinishAfter, - ); + return SentryTracer(context, hub, + waitForChildren: waitForChildren, + autoFinishAfter: autoFinishAfter, + trimEnd: trimEnd); } } From 055ebd5f241f47a67b736b5f4d62f4478a48a87e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20Andra=C5=A1ec?= Date: Tue, 18 Jan 2022 11:29:03 +0100 Subject: [PATCH 2/8] test end timestamp on sentry span --- dart/test/sentry_span_test.dart | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/dart/test/sentry_span_test.dart b/dart/test/sentry_span_test.dart index 0a1b0f811a..ef3f3f8f2c 100644 --- a/dart/test/sentry_span_test.dart +++ b/dart/test/sentry_span_test.dart @@ -181,6 +181,16 @@ void main() { expect(numberOfCallbackCalls, 1); }); + + test('optional endTimestamp set instead of current time', () async { + final sut = fixture.getSut(); + + final endTimestamp = DateTime.now().add(Duration(days: 1)); + + await sut.finish(endTimestamp: endTimestamp); + + expect(sut.endTimestamp, endTimestamp); + }); } class Fixture { From bdef0562972b9431ba28c99292b10205be8baf39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20Andra=C5=A1ec?= Date: Tue, 18 Jan 2022 12:57:47 +0100 Subject: [PATCH 3/8] Introduce trimEnd param --- dart/lib/src/hub.dart | 13 +++++++------ dart/lib/src/hub_adapter.dart | 4 ++++ dart/lib/src/noop_hub.dart | 2 ++ dart/lib/src/sentry.dart | 4 ++++ dart/test/default_integrations_test.dart | 1 + dart/test/mocks/mock_hub.dart | 2 ++ dio/test/mocks/mock_hub.dart | 2 ++ .../navigation/sentry_navigator_observer.dart | 1 + flutter/test/mocks.dart | 3 +++ flutter/test/mocks.mocks.dart | 18 ++++++++++-------- .../test/sentry_navigator_observer_test.dart | 7 +++++++ logging/test/mock_hub.dart | 2 ++ 12 files changed, 45 insertions(+), 14 deletions(-) diff --git a/dart/lib/src/hub.dart b/dart/lib/src/hub.dart index 624f83684e..b57e4b6c56 100644 --- a/dart/lib/src/hub.dart +++ b/dart/lib/src/hub.dart @@ -342,6 +342,7 @@ class Hub { bool? bindToScope, bool? waitForChildren, Duration? autoFinishAfter, + bool? trimEnd, Map? customSamplingContext, }) => startTransactionWithContext( @@ -353,6 +354,7 @@ class Hub { bindToScope: bindToScope, waitForChildren: waitForChildren, autoFinishAfter: autoFinishAfter, + trimEnd: trimEnd, customSamplingContext: customSamplingContext, ); @@ -363,6 +365,7 @@ class Hub { bool? bindToScope, bool? waitForChildren, Duration? autoFinishAfter, + bool? trimEnd, }) { if (!_isEnabled) { _options.logger( @@ -386,12 +389,10 @@ class Hub { transactionContext = transactionContext.copyWith(sampled: sampled); } - final tracer = SentryTracer( - transactionContext, - this, - waitForChildren: waitForChildren ?? false, - autoFinishAfter: autoFinishAfter, - ); + final tracer = SentryTracer(transactionContext, this, + waitForChildren: waitForChildren ?? false, + autoFinishAfter: autoFinishAfter, + trimEnd: trimEnd ?? false); if (bindToScope ?? false) { item.scope.span = tracer; } diff --git a/dart/lib/src/hub_adapter.dart b/dart/lib/src/hub_adapter.dart index 3f7803fc29..fb7019a0ba 100644 --- a/dart/lib/src/hub_adapter.dart +++ b/dart/lib/src/hub_adapter.dart @@ -104,6 +104,7 @@ class HubAdapter implements Hub { bool? bindToScope, bool? waitForChildren, Duration? autoFinishAfter, + bool? trimEnd, }) => Sentry.startTransactionWithContext( transactionContext, @@ -111,6 +112,7 @@ class HubAdapter implements Hub { bindToScope: bindToScope, waitForChildren: waitForChildren, autoFinishAfter: autoFinishAfter, + trimEnd: trimEnd, ); @override @@ -121,6 +123,7 @@ class HubAdapter implements Hub { bool? bindToScope, bool? waitForChildren, Duration? autoFinishAfter, + bool? trimEnd, Map? customSamplingContext, }) => Sentry.startTransaction( @@ -130,6 +133,7 @@ class HubAdapter implements Hub { bindToScope: bindToScope, waitForChildren: waitForChildren, autoFinishAfter: autoFinishAfter, + trimEnd: trimEnd, customSamplingContext: customSamplingContext, ); diff --git a/dart/lib/src/noop_hub.dart b/dart/lib/src/noop_hub.dart index 9963771496..ae3570ecf4 100644 --- a/dart/lib/src/noop_hub.dart +++ b/dart/lib/src/noop_hub.dart @@ -80,6 +80,7 @@ class NoOpHub implements Hub { bool? bindToScope, bool? waitForChildren, Duration? autoFinishAfter, + bool? trimEnd, Map? customSamplingContext, }) => NoOpSentrySpan(); @@ -91,6 +92,7 @@ class NoOpHub implements Hub { bool? bindToScope, bool? waitForChildren, Duration? autoFinishAfter, + bool? trimEnd, }) => NoOpSentrySpan(); diff --git a/dart/lib/src/sentry.dart b/dart/lib/src/sentry.dart index 3d1a74f02b..a443ce3f87 100644 --- a/dart/lib/src/sentry.dart +++ b/dart/lib/src/sentry.dart @@ -231,6 +231,7 @@ class Sentry { bool? bindToScope, bool? waitForChildren, Duration? autoFinishAfter, + bool? trimEnd, Map? customSamplingContext, }) => _hub.startTransaction( @@ -240,6 +241,7 @@ class Sentry { bindToScope: bindToScope, waitForChildren: waitForChildren, autoFinishAfter: autoFinishAfter, + trimEnd: trimEnd, customSamplingContext: customSamplingContext, ); @@ -250,6 +252,7 @@ class Sentry { bool? bindToScope, bool? waitForChildren, Duration? autoFinishAfter, + bool? trimEnd, }) => _hub.startTransactionWithContext( transactionContext, @@ -257,6 +260,7 @@ class Sentry { bindToScope: bindToScope, waitForChildren: waitForChildren, autoFinishAfter: autoFinishAfter, + trimEnd: trimEnd, ); /// Gets the current active transaction or span. diff --git a/dart/test/default_integrations_test.dart b/dart/test/default_integrations_test.dart index 026025cb5e..5b0afa2ec8 100644 --- a/dart/test/default_integrations_test.dart +++ b/dart/test/default_integrations_test.dart @@ -190,6 +190,7 @@ class PrintRecursionMockHub extends MockHub { bool? bindToScope, bool? waitForChildren, Duration? autoFinishAfter, + bool? trimEnd, }) { return NoOpSentrySpan(); } diff --git a/dart/test/mocks/mock_hub.dart b/dart/test/mocks/mock_hub.dart index 5fddcff7f7..98d992329b 100644 --- a/dart/test/mocks/mock_hub.dart +++ b/dart/test/mocks/mock_hub.dart @@ -127,6 +127,7 @@ class MockHub implements Hub { bool? bindToScope, bool? waitForChildren, Duration? autoFinishAfter, + bool? trimEnd, Map? customSamplingContext, }) { return NoOpSentrySpan(); @@ -139,6 +140,7 @@ class MockHub implements Hub { bool? bindToScope, bool? waitForChildren, Duration? autoFinishAfter, + bool? trimEnd, }) { return NoOpSentrySpan(); } diff --git a/dio/test/mocks/mock_hub.dart b/dio/test/mocks/mock_hub.dart index 18b9d46c79..3aa45a9ce1 100644 --- a/dio/test/mocks/mock_hub.dart +++ b/dio/test/mocks/mock_hub.dart @@ -133,6 +133,7 @@ class MockHub implements Hub { bool? bindToScope, bool? waitForChildren, Duration? autoFinishAfter, + bool? trimEnd, Map? customSamplingContext, }) { return NoOpSentrySpan(); @@ -145,6 +146,7 @@ class MockHub implements Hub { bool? bindToScope, bool? waitForChildren, Duration? autoFinishAfter, + bool? trimEnd, }) { return NoOpSentrySpan(); } diff --git a/flutter/lib/src/navigation/sentry_navigator_observer.dart b/flutter/lib/src/navigation/sentry_navigator_observer.dart index 1d64b6e356..7630f5787a 100644 --- a/flutter/lib/src/navigation/sentry_navigator_observer.dart +++ b/flutter/lib/src/navigation/sentry_navigator_observer.dart @@ -153,6 +153,7 @@ class SentryNavigatorObserver extends RouteObserver> { 'navigation', waitForChildren: true, autoFinishAfter: Duration(seconds: 3), + trimEnd: true, ); if (arguments != null) { _transaction?.setData('route_settings_arguments', arguments); diff --git a/flutter/test/mocks.dart b/flutter/test/mocks.dart index 98aff4d1da..626152198c 100644 --- a/flutter/test/mocks.dart +++ b/flutter/test/mocks.dart @@ -14,6 +14,7 @@ ISentrySpan startTransactionShim( bool? bindToScope, bool? waitForChildren, Duration? autoFinishAfter, + bool? trimEnd, Map? customSamplingContext, }) { return MockNoOpSentrySpan(); @@ -189,6 +190,7 @@ class NoOpHub implements Hub { bool? bindToScope, bool? waitForChildren, Duration? autoFinishAfter, + bool? trimEnd, Map? customSamplingContext, }) { return NoOpSentrySpan(); @@ -201,6 +203,7 @@ class NoOpHub implements Hub { bool? bindToScope, bool? waitForChildren, Duration? autoFinishAfter, + bool? trimEnd, }) { return NoOpSentrySpan(); } diff --git a/flutter/test/mocks.mocks.dart b/flutter/test/mocks.mocks.dart index 53e0f52cba..542e60468a 100644 --- a/flutter/test/mocks.mocks.dart +++ b/flutter/test/mocks.mocks.dart @@ -1,5 +1,5 @@ // Mocks generated by Mockito 5.0.16 from annotations -// in sentry_flutter/test/mocks.dart. +// in sentry_flutter/example/ios/.symlinks/plugins/sentry_flutter/test/mocks.dart. // Do not manually edit this file. import 'dart:async' as _i6; @@ -84,11 +84,8 @@ class MockNoOpSentrySpan extends _i1.Mock implements _i2.NoOpSentrySpan { super.noSuchMethod(Invocation.setter(#status, status), returnValueForMissingStub: null); @override - _i6.Future finish( - {_i3.SpanStatus? status, Duration? autoFinishAfter}) => - (super.noSuchMethod( - Invocation.method(#finish, [], - {#status: status, #autoFinishAfter: autoFinishAfter}), + _i6.Future finish({_i3.SpanStatus? status}) => + (super.noSuchMethod(Invocation.method(#finish, [], {#status: status}), returnValue: Future.value(), returnValueForMissingStub: Future.value()) as _i6.Future); @override @@ -213,6 +210,7 @@ class MockHub extends _i1.Mock implements _i4.Hub { bool? bindToScope, bool? waitForChildren, Duration? autoFinishAfter, + bool? trimEnd, Map? customSamplingContext}) => (super.noSuchMethod( Invocation.method(#startTransaction, [ @@ -223,6 +221,7 @@ class MockHub extends _i1.Mock implements _i4.Hub { #bindToScope: bindToScope, #waitForChildren: waitForChildren, #autoFinishAfter: autoFinishAfter, + #trimEnd: trimEnd, #customSamplingContext: customSamplingContext }), returnValue: _i10.startTransactionShim(name, operation, @@ -230,6 +229,7 @@ class MockHub extends _i1.Mock implements _i4.Hub { bindToScope: bindToScope, waitForChildren: waitForChildren, autoFinishAfter: autoFinishAfter, + trimEnd: trimEnd, customSamplingContext: customSamplingContext)) as _i2.ISentrySpan); @override @@ -238,7 +238,8 @@ class MockHub extends _i1.Mock implements _i4.Hub { {Map? customSamplingContext, bool? bindToScope, bool? waitForChildren, - Duration? autoFinishAfter}) => + Duration? autoFinishAfter, + bool? trimEnd}) => (super.noSuchMethod( Invocation.method(#startTransactionWithContext, [ transactionContext @@ -246,7 +247,8 @@ class MockHub extends _i1.Mock implements _i4.Hub { #customSamplingContext: customSamplingContext, #bindToScope: bindToScope, #waitForChildren: waitForChildren, - #autoFinishAfter: autoFinishAfter + #autoFinishAfter: autoFinishAfter, + #trimEnd: trimEnd }), returnValue: _FakeISentrySpan_2()) as _i2.ISentrySpan); @override diff --git a/flutter/test/sentry_navigator_observer_test.dart b/flutter/test/sentry_navigator_observer_test.dart index 6afb05ed49..a6f2719a66 100644 --- a/flutter/test/sentry_navigator_observer_test.dart +++ b/flutter/test/sentry_navigator_observer_test.dart @@ -25,6 +25,7 @@ void main() { waitForChildren: anyNamed('waitForChildren'), autoFinishAfter: anyNamed('autoFinishAfter'), customSamplingContext: anyNamed('customSamplingContext'), + trimEnd: anyNamed('trimEnd'), )).thenReturn(thenReturnSpan); } @@ -48,6 +49,7 @@ void main() { 'navigation', waitForChildren: true, autoFinishAfter: Duration(seconds: 3), + trimEnd: true, )); hub.configureScope((scope) { @@ -70,6 +72,7 @@ void main() { 'navigation', waitForChildren: true, autoFinishAfter: Duration(seconds: 3), + trimEnd: true, )); hub.configureScope((scope) { @@ -92,6 +95,7 @@ void main() { 'navigation', waitForChildren: true, autoFinishAfter: Duration(seconds: 3), + trimEnd: true, )); hub.configureScope((scope) { @@ -115,6 +119,7 @@ void main() { 'navigation', waitForChildren: true, autoFinishAfter: Duration(seconds: 3), + trimEnd: true, )); hub.configureScope((scope) { @@ -174,6 +179,7 @@ void main() { 'navigation', waitForChildren: true, autoFinishAfter: Duration(seconds: 3), + trimEnd: true, )); hub.configureScope((scope) { @@ -235,6 +241,7 @@ void main() { 'navigation', waitForChildren: true, autoFinishAfter: Duration(seconds: 3), + trimEnd: true, )); hub.configureScope((scope) { diff --git a/logging/test/mock_hub.dart b/logging/test/mock_hub.dart index a96614b870..c670303cc2 100644 --- a/logging/test/mock_hub.dart +++ b/logging/test/mock_hub.dart @@ -88,6 +88,7 @@ class MockHub implements Hub { bool? bindToScope, bool? waitForChildren, Duration? autoFinishAfter, + bool? trimEnd, Map? customSamplingContext, }) => NoOpSentrySpan(); @@ -99,6 +100,7 @@ class MockHub implements Hub { bool? bindToScope, bool? waitForChildren, Duration? autoFinishAfter, + bool? trimEnd, }) => NoOpSentrySpan(); } From 319623d3d1d3b2f65d22988bd298698498bbdb2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20Andra=C5=A1ec?= Date: Tue, 18 Jan 2022 13:11:05 +0100 Subject: [PATCH 4/8] update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1993b4121f..16176d3542 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +# Feat: Auto Transactions: Duration Trimming (#668) + # 6.3.0-beta.2 * Feat: Improve configuration options of `SentryNavigatorObserver` (#684) From 1cf132fd766d1760c057c87418271863e0781764 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20Andra=C5=A1ec?= Date: Tue, 18 Jan 2022 13:31:20 +0100 Subject: [PATCH 5/8] Incorporate review feedback --- dart/lib/src/hub.dart | 2 +- dart/lib/src/protocol/sentry_span.dart | 12 +++++----- dart/lib/src/sentry_tracer.dart | 31 ++++++++++++++++++-------- dart/test/sentry_tracer_test.dart | 2 +- 4 files changed, 30 insertions(+), 17 deletions(-) diff --git a/dart/lib/src/hub.dart b/dart/lib/src/hub.dart index b57e4b6c56..3b7ebb192c 100644 --- a/dart/lib/src/hub.dart +++ b/dart/lib/src/hub.dart @@ -392,7 +392,7 @@ class Hub { final tracer = SentryTracer(transactionContext, this, waitForChildren: waitForChildren ?? false, autoFinishAfter: autoFinishAfter, - trimEnd: trimEnd ?? false); + trimEnd: trimEnd ?? false,); if (bindToScope ?? false) { item.scope.span = tracer; } diff --git a/dart/lib/src/protocol/sentry_span.dart b/dart/lib/src/protocol/sentry_span.dart index 4251664214..2e445b6dc8 100644 --- a/dart/lib/src/protocol/sentry_span.dart +++ b/dart/lib/src/protocol/sentry_span.dart @@ -9,7 +9,7 @@ import '../utils.dart'; class SentrySpan extends ISentrySpan { final SentrySpanContext _context; - DateTime? _endTimestamp; + DateTime? _timestamp; final DateTime _startTimestamp = getUtcDateTime(); final Hub _hub; @@ -44,7 +44,7 @@ class SentrySpan extends ISentrySpan { if (status != null) { _status = status; } - _endTimestamp = endTimestamp ?? getUtcDateTime(); + _timestamp = endTimestamp ?? getUtcDateTime(); // associate error if (_throwable != null) { @@ -116,7 +116,7 @@ class SentrySpan extends ISentrySpan { DateTime get startTimestamp => _startTimestamp; @override - DateTime? get endTimestamp => _endTimestamp; + DateTime? get endTimestamp => _timestamp; @override SentrySpanContext get context => _context; @@ -125,9 +125,9 @@ class SentrySpan extends ISentrySpan { final json = _context.toJson(); json['start_timestamp'] = formatDateAsIso8601WithMillisPrecision(_startTimestamp); - if (_endTimestamp != null) { + if (_timestamp != null) { json['timestamp'] = - formatDateAsIso8601WithMillisPrecision(_endTimestamp!); + formatDateAsIso8601WithMillisPrecision(_timestamp!); } if (_data.isNotEmpty) { json['data'] = _data; @@ -142,7 +142,7 @@ class SentrySpan extends ISentrySpan { } @override - bool get finished => _endTimestamp != null; + bool get finished => _timestamp != null; @override dynamic get throwable => _throwable; diff --git a/dart/lib/src/sentry_tracer.dart b/dart/lib/src/sentry_tracer.dart index 9dd8b8e7ce..47e0054605 100644 --- a/dart/lib/src/sentry_tracer.dart +++ b/dart/lib/src/sentry_tracer.dart @@ -17,8 +17,21 @@ class SentryTracer extends ISentrySpan { final Map _extra = {}; Timer? _autoFinishAfterTimer; var _finishStatus = SentryTracerFinishStatus.notFinishing(); - var _trimEnd = false; - + late final bool _trimEnd; + + /// If [waitForChildren] is true, this transaction will not finish until all + /// its children are finished. + /// + /// When [autoFinishAfter] is provided, started transactions will + /// automatically be finished after this duration. + /// + /// If [trimEnd] is true, sets the end timestamp of the transaction to the + /// highest timestamp of child spans, trimming the duration of the + /// transaction. This is useful to discard extra time in the transaction that + /// is not accounted for in child spans, like what happens in the + /// [SentryNavigatorObserver] idle transactions, where we finish the + /// transaction after a given "idle time" and we don't want this "idle time" + /// to be part of the transaction. SentryTracer(SentryTransactionContext transactionContext, this._hub, {bool waitForChildren = false, Duration? autoFinishAfter, @@ -47,6 +60,13 @@ class SentryTracer extends ISentrySpan { (!_waitForChildren || _haveAllChildrenFinished())) { _rootSpan.status ??= status; + // finish unfinished spans otherwise transaction gets dropped + for (final span in _children) { + if (!span.finished) { + await span.finish(status: SpanStatus.deadlineExceeded()); + } + } + var _rootEndTimestamp = getUtcDateTime(); if (_trimEnd && children.isNotEmpty) { final childEndTimestamps = children @@ -64,13 +84,6 @@ class SentryTracer extends ISentrySpan { await _rootSpan.finish(endTimestamp: _rootEndTimestamp); - // finish unfinished spans otherwise transaction gets dropped - for (final span in _children) { - if (!span.finished) { - await span.finish(status: SpanStatus.deadlineExceeded()); - } - } - // remove from scope _hub.configureScope((scope) { if (scope.span == this) { diff --git a/dart/test/sentry_tracer_test.dart b/dart/test/sentry_tracer_test.dart index 27c4a88ac7..cb40f60268 100644 --- a/dart/test/sentry_tracer_test.dart +++ b/dart/test/sentry_tracer_test.dart @@ -302,6 +302,6 @@ class Fixture { return SentryTracer(context, hub, waitForChildren: waitForChildren, autoFinishAfter: autoFinishAfter, - trimEnd: trimEnd); + trimEnd: trimEnd,); } } From 7c3c306583abb69c5fa1b3a41ed631a065cad8e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20Andra=C5=A1ec?= Date: Tue, 18 Jan 2022 13:31:45 +0100 Subject: [PATCH 6/8] format --- dart/lib/src/hub.dart | 11 +++++++---- dart/lib/src/protocol/sentry_span.dart | 3 +-- dart/test/sentry_tracer_test.dart | 11 +++++++---- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/dart/lib/src/hub.dart b/dart/lib/src/hub.dart index 3b7ebb192c..05b01306dd 100644 --- a/dart/lib/src/hub.dart +++ b/dart/lib/src/hub.dart @@ -389,10 +389,13 @@ class Hub { transactionContext = transactionContext.copyWith(sampled: sampled); } - final tracer = SentryTracer(transactionContext, this, - waitForChildren: waitForChildren ?? false, - autoFinishAfter: autoFinishAfter, - trimEnd: trimEnd ?? false,); + final tracer = SentryTracer( + transactionContext, + this, + waitForChildren: waitForChildren ?? false, + autoFinishAfter: autoFinishAfter, + trimEnd: trimEnd ?? false, + ); if (bindToScope ?? false) { item.scope.span = tracer; } diff --git a/dart/lib/src/protocol/sentry_span.dart b/dart/lib/src/protocol/sentry_span.dart index 2e445b6dc8..ce856a00ab 100644 --- a/dart/lib/src/protocol/sentry_span.dart +++ b/dart/lib/src/protocol/sentry_span.dart @@ -126,8 +126,7 @@ class SentrySpan extends ISentrySpan { json['start_timestamp'] = formatDateAsIso8601WithMillisPrecision(_startTimestamp); if (_timestamp != null) { - json['timestamp'] = - formatDateAsIso8601WithMillisPrecision(_timestamp!); + json['timestamp'] = formatDateAsIso8601WithMillisPrecision(_timestamp!); } if (_data.isNotEmpty) { json['data'] = _data; diff --git a/dart/test/sentry_tracer_test.dart b/dart/test/sentry_tracer_test.dart index cb40f60268..ed1e3c26e7 100644 --- a/dart/test/sentry_tracer_test.dart +++ b/dart/test/sentry_tracer_test.dart @@ -299,9 +299,12 @@ class Fixture { 'op', sampled: sampled, ); - return SentryTracer(context, hub, - waitForChildren: waitForChildren, - autoFinishAfter: autoFinishAfter, - trimEnd: trimEnd,); + return SentryTracer( + context, + hub, + waitForChildren: waitForChildren, + autoFinishAfter: autoFinishAfter, + trimEnd: trimEnd, + ); } } From a8ad9fa7901f23d22a563b3c60e927bdb41d9d34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20Andra=C5=A1ec?= Date: Tue, 18 Jan 2022 13:34:05 +0100 Subject: [PATCH 7/8] Use correct issue number in changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16176d3542..36b935363a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Unreleased -# Feat: Auto Transactions: Duration Trimming (#668) +# Feat: Auto Transactions: Duration Trimming (#702) # 6.3.0-beta.2 From cf02a15943b499add22397c46aba20a95f350185 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20Andra=C5=A1ec?= Date: Tue, 18 Jan 2022 14:37:28 +0100 Subject: [PATCH 8/8] update readme --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fe28252cf..9ae5eaef35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Unreleased -# Feat: Auto Transactions: Duration Trimming (#702) +# Feat: Auto transactions duration trimming (#702) * Fix: `maxRequestBodySize` should be `never` by default when using the FailedRequestClientAdapter directly (#701) # 6.3.0-beta.2