diff --git a/CHANGELOG.md b/CHANGELOG.md index 393f25a990b..cd098f4a48e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/Sentry.xcodeproj/xcshareddata/xcschemes/Sentry.xcscheme b/Sentry.xcodeproj/xcshareddata/xcschemes/Sentry.xcscheme index 77bb934edc7..b1bbd8edbd7 100644 --- a/Sentry.xcodeproj/xcshareddata/xcschemes/Sentry.xcscheme +++ b/Sentry.xcodeproj/xcshareddata/xcschemes/Sentry.xcscheme @@ -55,12 +55,24 @@ ReferencedContainer = "container:Sentry.xcodeproj"> + + + + + + + + diff --git a/Sources/Sentry/SentryTracer.m b/Sources/Sentry/SentryTracer.m index 29baa07c68f..167363dc263 100644 --- a/Sources/Sentry/SentryTracer.m +++ b/Sources/Sentry/SentryTracer.m @@ -58,6 +58,7 @@ @implementation SentryTracer { BOOL _waitForChildren; SentryTraceContext *_traceContext; SentryProfilesSamplerDecision *_profilesSamplerDecision; + SentryAppStartMeasurement *appStartMeasurement; NSMutableDictionary *_tags; NSMutableDictionary *_data; dispatch_block_t _idleTimeoutBlock; @@ -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]; @@ -557,9 +559,7 @@ - (void)trimEndTimestamp - (SentryTransaction *)toTransaction { - SentryAppStartMeasurement *appStartMeasurement = [self getAppStartMeasurement]; - - NSArray> *appStartSpans = [self buildAppStartSpans:appStartMeasurement]; + NSArray> *appStartSpans = [self buildAppStartSpans]; NSArray> *spans; @synchronized(_children) { @@ -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; } @@ -626,8 +626,7 @@ - (nullable SentryAppStartMeasurement *)getAppStartMeasurement return measurement; } -- (NSArray *)buildAppStartSpans: - (nullable SentryAppStartMeasurement *)appStartMeasurement +- (NSArray *)buildAppStartSpans { if (appStartMeasurement == nil) { return @[]; @@ -687,7 +686,6 @@ - (nullable SentryAppStartMeasurement *)getAppStartMeasurement } - (void)addMeasurements:(SentryTransaction *)transaction - appStartMeasurement:(nullable SentryAppStartMeasurement *)appStartMeasurement { NSString *valueKey = @"value"; diff --git a/Tests/SentryTests/Integrations/Performance/IO/SentryFileIOTrackingIntegrationTests.swift b/Tests/SentryTests/Integrations/Performance/IO/SentryFileIOTrackingIntegrationTests.swift index 6bdeb55a0e7..f19ca2e3a7f 100644 --- a/Tests/SentryTests/Integrations/Performance/IO/SentryFileIOTrackingIntegrationTests.swift +++ b/Tests/SentryTests/Integrations/Performance/IO/SentryFileIOTrackingIntegrationTests.swift @@ -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 diff --git a/Tests/SentryTests/Performance/SentryTracerTests.swift b/Tests/SentryTests/Performance/SentryTracerTests.swift index 0bc3295d991..c42763268ff 100644 --- a/Tests/SentryTests/Performance/SentryTracerTests.swift +++ b/Tests/SentryTests/Performance/SentryTracerTests.swift @@ -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() { @@ -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() @@ -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() @@ -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() diff --git a/Tests/SentryTests/SentryCrash/SentryStacktraceBuilderTests.swift b/Tests/SentryTests/SentryCrash/SentryStacktraceBuilderTests.swift index 3c2ac60b926..c65beeb9c79 100644 --- a/Tests/SentryTests/SentryCrash/SentryStacktraceBuilderTests.swift +++ b/Tests/SentryTests/SentryCrash/SentryStacktraceBuilderTests.swift @@ -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