Skip to content

Commit

Permalink
fix: Add app start to first finished transaction (#2252)
Browse files Browse the repository at this point in the history
The SDK added the app start measurement and spans to the first
finished auto-generated UIViewController transaction. The first finished
UIViewController transaction is not necessarily the transaction of the
first loaded screen. As auto-generated transactions for
UIViewControllers wait for all children to finish, it may kick off, for
example, a web request that lasts for a couple of seconds in the
background. In the meantime, the user navigates to another screen,
which finishes loading before the web request. The SDK added the
app start data to the wrong transaction in such an edge case. This is
fixed now by adding the app start data to the first started transaction.

Fixes GH-2248

Co-authored-by: Bruno Garcia <bruno@brunogarcia.com>
  • Loading branch information
philipphofmann and bruno-garcia authored Oct 5, 2022
1 parent fbeb49a commit 6dc0bd1
Show file tree
Hide file tree
Showing 6 changed files with 63 additions and 17 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Fixes

- Add app start measurement to first finished transaction (#2252)
- Return SentryNoOpSpan when starting a child on a finished transaction (#2239)
- Fix profiling timestamps for slow/frozen frames (#2226)

Expand Down
12 changes: 12 additions & 0 deletions Sentry.xcodeproj/xcshareddata/xcschemes/Sentry.xcscheme
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,24 @@
ReferencedContainer = "container:Sentry.xcodeproj">
</BuildableReference>
<SkippedTests>
<Test
Identifier = "SentryFileIOTrackingIntegrationTests/test_DataConsistency_readPath_disabled()">
</Test>
<Test
Identifier = "SentryFileIOTrackingIntegrationTests/test_DataConsistency_readUrl()">
</Test>
<Test
Identifier = "SentryFileIOTrackingIntegrationTests/test_DataConsistency_readUrl_disabled()">
</Test>
<Test
Identifier = "SentrySDKIntegrationTestsBase">
</Test>
<Test
Identifier = "SentrySessionGeneratorTests/testSendSessions()">
</Test>
<Test
Identifier = "SentryStacktraceBuilderTests/testAsyncStacktraces_disabled()">
</Test>
<Test
Identifier = "SentrySystemEventBreadcrumbsTest/testTimezoneChangeNotificationBreadcrumb_disabled()">
</Test>
Expand Down
12 changes: 5 additions & 7 deletions Sources/Sentry/SentryTracer.m
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ @implementation SentryTracer {
BOOL _waitForChildren;
SentryTraceContext *_traceContext;
SentryProfilesSamplerDecision *_profilesSamplerDecision;
SentryAppStartMeasurement *appStartMeasurement;
NSMutableDictionary<NSString *, id> *_tags;
NSMutableDictionary<NSString *, id> *_data;
dispatch_block_t _idleTimeoutBlock;
Expand Down Expand Up @@ -162,6 +163,7 @@ - (instancetype)initWithTransactionContext:(SentryTransactionContext *)transacti
self.finishStatus = kSentrySpanStatusUndefined;
self.idleTimeout = idleTimeout;
self.dispatchQueueWrapper = dispatchQueueWrapper;
appStartMeasurement = [self getAppStartMeasurement];

if ([self hasIdleTimeout]) {
[self dispatchIdleTimeout];
Expand Down Expand Up @@ -557,9 +559,7 @@ - (void)trimEndTimestamp

- (SentryTransaction *)toTransaction
{
SentryAppStartMeasurement *appStartMeasurement = [self getAppStartMeasurement];

NSArray<id<SentrySpan>> *appStartSpans = [self buildAppStartSpans:appStartMeasurement];
NSArray<id<SentrySpan>> *appStartSpans = [self buildAppStartSpans];

NSArray<id<SentrySpan>> *spans;
@synchronized(_children) {
Expand All @@ -573,7 +573,7 @@ - (SentryTransaction *)toTransaction

SentryTransaction *transaction = [[SentryTransaction alloc] initWithTrace:self children:spans];
transaction.transaction = self.transactionContext.name;
[self addMeasurements:transaction appStartMeasurement:appStartMeasurement];
[self addMeasurements:transaction];
return transaction;
}

Expand Down Expand Up @@ -626,8 +626,7 @@ - (nullable SentryAppStartMeasurement *)getAppStartMeasurement
return measurement;
}

- (NSArray<SentrySpan *> *)buildAppStartSpans:
(nullable SentryAppStartMeasurement *)appStartMeasurement
- (NSArray<SentrySpan *> *)buildAppStartSpans
{
if (appStartMeasurement == nil) {
return @[];
Expand Down Expand Up @@ -687,7 +686,6 @@ - (nullable SentryAppStartMeasurement *)getAppStartMeasurement
}

- (void)addMeasurements:(SentryTransaction *)transaction
appStartMeasurement:(nullable SentryAppStartMeasurement *)appStartMeasurement
{
NSString *valueKey = @"value";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ class SentryFileIOTrackingIntegrationTests: XCTestCase {
?? bundle.path(forResource: "fatal-error-binary-images-message2", ofType: "json")
}

func test_DataConsistency_readUrl() {
func test_DataConsistency_readUrl_disabled() {
SentrySDK.start(options: fixture.getOptions())

let randomValue = UUID().uuidString
Expand Down
51 changes: 43 additions & 8 deletions Tests/SentryTests/Performance/SentryTracerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -432,19 +432,41 @@ class SentryTracerTests: XCTestCase {
let appStartMeasurement = fixture.getAppStartMeasurement(type: .warm)
SentrySDK.setAppStartMeasurement(appStartMeasurement)

advanceTime(bySeconds: -(fixture.appStartDuration + 4))

let sut = fixture.getSut()
sut.startTimestamp = fixture.appStartEnd.addingTimeInterval(-5)
advanceTime(bySeconds: 1)
sut.finish()
fixture.hub.group.wait()

XCTAssertEqual(1, fixture.hub.capturedEventsWithScopes.count)
let serializedTransaction = fixture.hub.capturedEventsWithScopes.first!.event.serialize()
let measurements = serializedTransaction["measurements"] as? [String: [String: Int]]

XCTAssertEqual(["app_start_warm": ["value": 500]], measurements)
assertAppStartMeasurementOn(transaction: fixture.hub.capturedEventsWithScopes.first!.event as! Transaction, appStartMeasurement: appStartMeasurement)
}

func testAddColdStartMeasurement_PutOnFirstStartedTransaction() {
let appStartMeasurement = fixture.getAppStartMeasurement(type: .warm)
SentrySDK.setAppStartMeasurement(appStartMeasurement)

let transaction = fixture.hub.capturedEventsWithScopes.first!.event as! Transaction
assertAppStartsSpanAdded(transaction: transaction, startType: "Warm Start", operation: fixture.appStartWarmOperation, appStartMeasurement: appStartMeasurement)
advanceTime(bySeconds: 0.5)

let firstTransaction = fixture.getSut()
advanceTime(bySeconds: 0.5)

let secondTransaction = fixture.getSut()
advanceTime(bySeconds: 0.5)
secondTransaction.finish()

fixture.hub.group.wait()
XCTAssertEqual(1, fixture.hub.capturedEventsWithScopes.count)
let serializedSecondTransaction = fixture.hub.capturedEventsWithScopes.first!.event.serialize()
XCTAssertNil(serializedSecondTransaction["measurements"])

firstTransaction.finish()
fixture.hub.group.wait()

XCTAssertEqual(2, fixture.hub.capturedEventsWithScopes.count)
assertAppStartMeasurementOn(transaction: fixture.hub.capturedEventsWithScopes[1].event as! Transaction, appStartMeasurement: appStartMeasurement)
}

func testAddUnknownAppStartMeasurement_NotPutOnNextTransaction() {
Expand Down Expand Up @@ -488,8 +510,10 @@ class SentryTracerTests: XCTestCase {
let appStartMeasurement = fixture.getAppStartMeasurement(type: .warm)
SentrySDK.setAppStartMeasurement(appStartMeasurement)

advanceTime(bySeconds: fixture.appStartDuration + 5.01)

let sut = fixture.getSut()
sut.startTimestamp = fixture.appStartEnd.addingTimeInterval(5.1)
advanceTime(bySeconds: 1.0)
sut.finish()
fixture.hub.group.wait()

Expand All @@ -500,8 +524,10 @@ class SentryTracerTests: XCTestCase {
let appStartMeasurement = fixture.getAppStartMeasurement(type: .warm)
SentrySDK.setAppStartMeasurement(appStartMeasurement)

advanceTime(bySeconds: -(fixture.appStartDuration + 4.01))

let sut = fixture.getSut()
sut.startTimestamp = fixture.appStartEnd.addingTimeInterval(-5.1)
advanceTime(bySeconds: 1.0)
sut.finish()
fixture.hub.group.wait()

Expand Down Expand Up @@ -821,6 +847,15 @@ class SentryTracerTests: XCTestCase {
assertSpan("Initial Frame Render", appStartMeasurement.didFinishLaunchingTimestamp, fixture.appStartEnd)
}

private func assertAppStartMeasurementOn(transaction: Transaction, appStartMeasurement: SentryAppStartMeasurement) {
let serializedTransaction = transaction.serialize()
let measurements = serializedTransaction["measurements"] as? [String: [String: Int]]

XCTAssertEqual(["app_start_warm": ["value": 500]], measurements)

assertAppStartsSpanAdded(transaction: transaction, startType: "Warm Start", operation: fixture.appStartWarmOperation, appStartMeasurement: appStartMeasurement)
}

private func assertAppStartMeasurementNotPutOnTransaction() {
XCTAssertEqual(1, fixture.hub.capturedEventsWithScopes.count)
let serializedTransaction = fixture.hub.capturedEventsWithScopes.first!.event.serialize()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ class SentryStacktraceBuilderTests: XCTestCase {
XCTAssertTrue(filteredFrames.count == 1, "The frames must be ordered from caller to callee, or oldest to youngest.")
}

func testAsyncStacktraces() throws {
func testAsyncStacktraces_disabled() throws {
SentrySDK.start { options in
options.dsn = TestConstants.dsnAsString(username: "SentryStacktraceBuilderTests")
options.stitchAsyncCode = true
Expand Down

0 comments on commit 6dc0bd1

Please sign in to comment.