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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Unreleased

# Feat: Auto transactions duration trimming (#702)
* Add `SentryAssetBundle` for automatic spans for asset loading (#685)
* Feat: Configure idle transaction duration (#705)
* Fix: `maxRequestBodySize` should be `never` by default when using the FailedRequestClientAdapter directly (#701)
Expand Down
4 changes: 4 additions & 0 deletions dart/lib/src/hub.dart
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@ class Hub {
bool? bindToScope,
bool? waitForChildren,
Duration? autoFinishAfter,
bool? trimEnd,
Map<String, dynamic>? customSamplingContext,
}) =>
startTransactionWithContext(
Expand All @@ -353,6 +354,7 @@ class Hub {
bindToScope: bindToScope,
waitForChildren: waitForChildren,
autoFinishAfter: autoFinishAfter,
trimEnd: trimEnd,
customSamplingContext: customSamplingContext,
);

Expand All @@ -363,6 +365,7 @@ class Hub {
bool? bindToScope,
bool? waitForChildren,
Duration? autoFinishAfter,
bool? trimEnd,
}) {
if (!_isEnabled) {
_options.logger(
Expand Down Expand Up @@ -391,6 +394,7 @@ class Hub {
this,
waitForChildren: waitForChildren ?? false,
autoFinishAfter: autoFinishAfter,
trimEnd: trimEnd ?? false,
);
if (bindToScope ?? false) {
item.scope.span = tracer;
Expand Down
4 changes: 4 additions & 0 deletions dart/lib/src/hub_adapter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,15 @@ class HubAdapter implements Hub {
bool? bindToScope,
bool? waitForChildren,
Duration? autoFinishAfter,
bool? trimEnd,
}) =>
Sentry.startTransactionWithContext(
transactionContext,
customSamplingContext: customSamplingContext,
bindToScope: bindToScope,
waitForChildren: waitForChildren,
autoFinishAfter: autoFinishAfter,
trimEnd: trimEnd,
);

@override
Expand All @@ -121,6 +123,7 @@ class HubAdapter implements Hub {
bool? bindToScope,
bool? waitForChildren,
Duration? autoFinishAfter,
bool? trimEnd,
Map<String, dynamic>? customSamplingContext,
}) =>
Sentry.startTransaction(
Expand All @@ -130,6 +133,7 @@ class HubAdapter implements Hub {
bindToScope: bindToScope,
waitForChildren: waitForChildren,
autoFinishAfter: autoFinishAfter,
trimEnd: trimEnd,
customSamplingContext: customSamplingContext,
);

Expand Down
2 changes: 2 additions & 0 deletions dart/lib/src/noop_hub.dart
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ class NoOpHub implements Hub {
bool? bindToScope,
bool? waitForChildren,
Duration? autoFinishAfter,
bool? trimEnd,
Map<String, dynamic>? customSamplingContext,
}) =>
NoOpSentrySpan();
Expand All @@ -91,6 +92,7 @@ class NoOpHub implements Hub {
bool? bindToScope,
bool? waitForChildren,
Duration? autoFinishAfter,
bool? trimEnd,
}) =>
NoOpSentrySpan();

Expand Down
4 changes: 2 additions & 2 deletions dart/lib/src/protocol/sentry_span.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,15 @@ class SentrySpan extends ISentrySpan {
}

@override
Future<void> finish({SpanStatus? status}) async {
Future<void> finish({SpanStatus? status, DateTime? endTimestamp}) async {
if (finished) {
return;
}

if (status != null) {
_status = status;
}
_timestamp = getUtcDateTime();
_timestamp = endTimestamp ?? getUtcDateTime();

// associate error
if (_throwable != null) {
Expand Down
4 changes: 4 additions & 0 deletions dart/lib/src/sentry.dart
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ class Sentry {
bool? bindToScope,
bool? waitForChildren,
Duration? autoFinishAfter,
bool? trimEnd,
Map<String, dynamic>? customSamplingContext,
}) =>
_hub.startTransaction(
Expand All @@ -240,6 +241,7 @@ class Sentry {
bindToScope: bindToScope,
waitForChildren: waitForChildren,
autoFinishAfter: autoFinishAfter,
trimEnd: trimEnd,
customSamplingContext: customSamplingContext,
);

Expand All @@ -250,13 +252,15 @@ class Sentry {
bool? bindToScope,
bool? waitForChildren,
Duration? autoFinishAfter,
bool? trimEnd,
}) =>
_hub.startTransactionWithContext(
transactionContext,
customSamplingContext: customSamplingContext,
bindToScope: bindToScope,
waitForChildren: waitForChildren,
autoFinishAfter: autoFinishAfter,
trimEnd: trimEnd,
);

/// Gets the current active transaction or span.
Expand Down
40 changes: 37 additions & 3 deletions dart/lib/src/sentry_tracer.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:async';

import 'package:meta/meta.dart';
import 'utils.dart';

import '../sentry.dart';
import 'sentry_tracer_finish_status.dart';
Expand All @@ -16,9 +17,25 @@ class SentryTracer extends ISentrySpan {
final Map<String, dynamic> _extra = {};
Timer? _autoFinishAfterTimer;
var _finishStatus = SentryTracerFinishStatus.notFinishing();

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}) {
{bool waitForChildren = false,
Duration? autoFinishAfter,
bool trimEnd = false}) {
_rootSpan = SentrySpan(
this,
transactionContext,
Expand All @@ -32,6 +49,7 @@ class SentryTracer extends ISentrySpan {
});
}
name = transactionContext.name;
_trimEnd = trimEnd;
}

@override
Expand All @@ -41,7 +59,6 @@ class SentryTracer extends ISentrySpan {
if (!_rootSpan.finished &&
(!_waitForChildren || _haveAllChildrenFinished())) {
_rootSpan.status ??= status;
await _rootSpan.finish();

// finish unfinished spans otherwise transaction gets dropped
for (final span in _children) {
Expand All @@ -50,6 +67,23 @@ class SentryTracer extends ISentrySpan {
}
}

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);

// remove from scope
_hub.configureScope((scope) {
if (scope.span == this) {
Expand Down
1 change: 1 addition & 0 deletions dart/test/default_integrations_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ class PrintRecursionMockHub extends MockHub {
bool? bindToScope,
bool? waitForChildren,
Duration? autoFinishAfter,
bool? trimEnd,
}) {
return NoOpSentrySpan();
}
Expand Down
2 changes: 2 additions & 0 deletions dart/test/mocks/mock_hub.dart
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ class MockHub implements Hub {
bool? bindToScope,
bool? waitForChildren,
Duration? autoFinishAfter,
bool? trimEnd,
Map<String, dynamic>? customSamplingContext,
}) {
return NoOpSentrySpan();
Expand All @@ -139,6 +140,7 @@ class MockHub implements Hub {
bool? bindToScope,
bool? waitForChildren,
Duration? autoFinishAfter,
bool? trimEnd,
}) {
return NoOpSentrySpan();
}
Expand Down
10 changes: 10 additions & 0 deletions dart/test/sentry_span_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
49 changes: 49 additions & 0 deletions dart/test/sentry_tracer_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -244,6 +291,7 @@ class Fixture {
SentryTracer getSut({
bool? sampled = true,
bool waitForChildren = false,
bool trimEnd = false,
Duration? autoFinishAfter,
}) {
final context = SentryTransactionContext(
Expand All @@ -256,6 +304,7 @@ class Fixture {
hub,
waitForChildren: waitForChildren,
autoFinishAfter: autoFinishAfter,
trimEnd: trimEnd,
);
}
}
2 changes: 2 additions & 0 deletions dio/test/mocks/mock_hub.dart
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ class MockHub implements Hub {
bool? bindToScope,
bool? waitForChildren,
Duration? autoFinishAfter,
bool? trimEnd,
Map<String, dynamic>? customSamplingContext,
}) {
return NoOpSentrySpan();
Expand All @@ -145,6 +146,7 @@ class MockHub implements Hub {
bool? bindToScope,
bool? waitForChildren,
Duration? autoFinishAfter,
bool? trimEnd,
}) {
return NoOpSentrySpan();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ class SentryNavigatorObserver extends RouteObserver<PageRoute<dynamic>> {
'navigation',
waitForChildren: true,
autoFinishAfter: _autoFinishAfter,
trimEnd: true,
);
if (arguments != null) {
_transaction?.setData('route_settings_arguments', arguments);
Expand Down
3 changes: 3 additions & 0 deletions flutter/test/mocks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ ISentrySpan startTransactionShim(
bool? bindToScope,
bool? waitForChildren,
Duration? autoFinishAfter,
bool? trimEnd,
Map<String, dynamic>? customSamplingContext,
}) {
return MockNoOpSentrySpan();
Expand Down Expand Up @@ -189,6 +190,7 @@ class NoOpHub implements Hub {
bool? bindToScope,
bool? waitForChildren,
Duration? autoFinishAfter,
bool? trimEnd,
Map<String, dynamic>? customSamplingContext,
}) {
return NoOpSentrySpan();
Expand All @@ -201,6 +203,7 @@ class NoOpHub implements Hub {
bool? bindToScope,
bool? waitForChildren,
Duration? autoFinishAfter,
bool? trimEnd,
}) {
return NoOpSentrySpan();
}
Expand Down
Loading