Skip to content

Commit 340fb46

Browse files
fix: Finish TTID span when transaction finishes (#3610)
The tracer finishes when the screen is fully displayed. Therefore, we must also finish the TTID span.
1 parent 24c8a0f commit 340fb46

File tree

6 files changed

+80
-0
lines changed

6 files changed

+80
-0
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
### Fixes
1010

1111
- Don't use main thread for app hang screenshot and view hierarchy (#3547)
12+
- Finish TTID span when transaction finishes (#3610)
13+
1214

1315
## 8.20.0
1416

Sources/Sentry/SentryTimeToDisplayTracker.m

+16
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,23 @@ - (void)startForTracer:(SentryTracer *)tracer
6464
self.initialDisplaySpan.startTimestamp = tracer.startTimestamp;
6565

6666
[SentryDependencyContainer.sharedInstance.framesTracker addListener:self];
67+
68+
[tracer setShouldIgnoreWaitForChildrenCallback:^(id<SentrySpan> span) {
69+
if (span.origin == SentryTraceOriginAutoUITimeToDisplay) {
70+
return YES;
71+
} else {
72+
return NO;
73+
}
74+
}];
6775
[tracer setFinishCallback:^(SentryTracer *_tracer) {
76+
[SentryDependencyContainer.sharedInstance.framesTracker removeListener:self];
77+
78+
// The tracer finishes when the screen is fully displayed. Therefore, we must also finish
79+
// the TTID span.
80+
if (self.initialDisplaySpan.isFinished == NO) {
81+
[self.initialDisplaySpan finish];
82+
}
83+
6884
// If the start time of the tracer changes, which is the case for app start transactions, we
6985
// also need to adapt the start time of our spans.
7086
self.initialDisplaySpan.startTimestamp = _tracer.startTimestamp;

Sources/Sentry/SentryTracer.m

+8
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,10 @@ - (BOOL)hasUnfinishedChildSpansToWaitFor
476476

477477
@synchronized(_children) {
478478
for (id<SentrySpan> span in _children) {
479+
if (self.shouldIgnoreWaitForChildrenCallback != nil
480+
&& self.shouldIgnoreWaitForChildrenCallback(span)) {
481+
continue;
482+
}
479483
if (![span isFinished])
480484
return YES;
481485
}
@@ -519,6 +523,10 @@ - (void)finishInternal
519523
self.finishCallback = nil;
520524
}
521525

526+
if (self.shouldIgnoreWaitForChildrenCallback) {
527+
self.shouldIgnoreWaitForChildrenCallback = nil;
528+
}
529+
522530
// Prewarming can execute code up to viewDidLoad of a UIViewController, and keep the app in the
523531
// background. This can lead to auto-generated transactions lasting for minutes or even hours.
524532
// Therefore, we drop transactions lasting longer than SENTRY_AUTO_TRANSACTION_MAX_DURATION.

Sources/Sentry/include/SentryTracer.h

+2
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ static const NSTimeInterval SENTRY_AUTO_TRANSACTION_MAX_DURATION = 500.0;
3434

3535
@property (nullable, nonatomic, copy) void (^finishCallback)(SentryTracer *);
3636

37+
@property (nullable, nonatomic, copy) BOOL (^shouldIgnoreWaitForChildrenCallback)(id<SentrySpan>);
38+
3739
/**
3840
* Retrieves a trace context from this tracer.
3941
*/

Tests/SentryTests/Integrations/Performance/UIViewController/SentryTimeToDisplayTrackerTest.swift

+27
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,33 @@ class SentryTimeToDisplayTrackerTest: XCTestCase {
202202

203203
expect(Dynamic(self.fixture.framesTracker).listeners.count) == 0
204204
}
205+
206+
func testTracerFinishesBeforeReportInitialDisplay_FinishesInitialDisplaySpan() throws {
207+
let sut = fixture.getSut(for: UIViewController(), waitForFullDisplay: false)
208+
209+
fixture.dateProvider.setDate(date: Date(timeIntervalSince1970: 7))
210+
let tracer = try fixture.getTracer()
211+
212+
sut.start(for: tracer)
213+
expect(tracer.children.count) == 1
214+
expect(Dynamic(self.fixture.framesTracker).listeners.count) == 1
215+
216+
let ttidSpan = try XCTUnwrap(tracer.children.first, "Expected a TTID span")
217+
expect(ttidSpan.startTimestamp) == fixture.dateProvider.date()
218+
219+
fixture.dateProvider.setDate(date: Date(timeIntervalSince1970: 9))
220+
221+
tracer.finish()
222+
223+
expect(ttidSpan.timestamp) == fixture.dateProvider.date()
224+
expect(ttidSpan.isFinished) == true
225+
expect(ttidSpan.spanDescription) == "UIViewController initial display"
226+
expect(ttidSpan.status) == .ok
227+
228+
assertMeasurement(tracer: tracer, name: "time_to_initial_display", duration: 2_000)
229+
230+
expect(Dynamic(self.fixture.framesTracker).listeners.count) == 0
231+
}
205232

206233
func testCheckInitialTime() throws {
207234
fixture.dateProvider.setDate(date: Date(timeIntervalSince1970: 9))

Tests/SentryTests/Transaction/SentryTracerTests.swift

+25
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,31 @@ class SentryTracerTests: XCTestCase {
152152
XCTAssertEqual(tracerTimestamp.timeIntervalSince1970, span["timestamp"] as? TimeInterval)
153153
}
154154
}
155+
156+
func testFinish_ShouldIgnoreWaitForChildrenCallback() throws {
157+
let sut = fixture.getSut()
158+
159+
sut.shouldIgnoreWaitForChildrenCallback = { _ in
160+
return true
161+
}
162+
let child = sut.startChild(operation: fixture.transactionOperation)
163+
sut.finish()
164+
165+
expect(child.status) == .deadlineExceeded
166+
167+
assertOneTransactionCaptured(sut)
168+
169+
let serialization = try getSerializedTransaction()
170+
let spans = serialization["spans"]! as! [[String: Any]]
171+
172+
let tracerTimestamp: NSDate = sut.timestamp! as NSDate
173+
174+
expect(spans.count) == 1
175+
let span = try XCTUnwrap(spans.first, "Expected first span not to be nil")
176+
expect(span["timestamp"] as? TimeInterval) == tracerTimestamp.timeIntervalSince1970
177+
178+
expect(sut.shouldIgnoreWaitForChildrenCallback) == nil
179+
}
155180

156181
func testDeadlineTimer_FinishesTransactionAndChildren() {
157182
fixture.dispatchQueue.blockBeforeMainBlock = { true }

0 commit comments

Comments
 (0)