Skip to content

Commit 2f9c5f9

Browse files
feat: Split up UIKit and App Init App Start Spans (#3534)
Split the UIKit and Application Init span into one span for UIKit Init and another for Application Init. The UIKit Init span ends when the users start the SentrySDK. We recommend that the SentrySDK is the first to call in the UIApplication.didFinishLaunching method because otherwise, users won't receive any potential crash reports for code running before our SDK. Therefore, we pick the start time of our SDK as when the UIApplicationDelegate.didFinishLaunching is called. Fixes GH-3345
1 parent 4603c54 commit 2f9c5f9

File tree

12 files changed

+86
-17
lines changed

12 files changed

+86
-17
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
- Add frames delay to transactions and spans (#3487, #3496)
88
- Add slow and frozen frames to spans (#3450, #3478)
9+
- Split up UIKit and App Init App Start Span (#3534)
910
- Prewarmed App Start Tracing is stable (#3536)
1011

1112
### Fixes

Sources/Sentry/SentryAppStartMeasurement.m

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ @implementation SentryAppStartMeasurement
1515
NSDate *_appStartTimestamp;
1616
NSDate *_runtimeInitTimestamp;
1717
NSDate *_moduleInitializationTimestamp;
18+
NSDate *_sdkStartTimestamp;
1819
NSDate *_didFinishLaunchingTimestamp;
1920
}
2021
# endif // SENTRY_HAS_UIKIT
@@ -25,6 +26,7 @@ - (instancetype)initWithType:(SentryAppStartType)type
2526
duration:(NSTimeInterval)duration
2627
runtimeInitTimestamp:(NSDate *)runtimeInitTimestamp
2728
moduleInitializationTimestamp:(NSDate *)moduleInitializationTimestamp
29+
sdkStartTimestamp:(NSDate *)sdkStartTimestamp
2830
didFinishLaunchingTimestamp:(NSDate *)didFinishLaunchingTimestamp
2931
{
3032
# if SENTRY_HAS_UIKIT
@@ -35,6 +37,7 @@ - (instancetype)initWithType:(SentryAppStartType)type
3537
_duration = duration;
3638
_runtimeInitTimestamp = runtimeInitTimestamp;
3739
_moduleInitializationTimestamp = moduleInitializationTimestamp;
40+
_sdkStartTimestamp = sdkStartTimestamp;
3841
_didFinishLaunchingTimestamp = didFinishLaunchingTimestamp;
3942
}
4043

Sources/Sentry/SentryAppStartTracker.m

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ - (void)buildAppStartMeasurement:(NSDate *)appStartEnd
216216
duration:appStartDuration
217217
runtimeInitTimestamp:runtimeInit
218218
moduleInitializationTimestamp:sysctl.moduleInitializationTimestamp
219+
sdkStartTimestamp:SentrySDK.startTimestamp
219220
didFinishLaunchingTimestamp:self.didFinishLaunchingTimestamp];
220221

221222
SentrySDK.appStartMeasurement = appStartMeasurement;

Sources/Sentry/SentryBuildAppStartSpans.m

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,18 @@
7474
[appStartSpans addObject:runtimeInitSpan];
7575
}
7676

77-
SentrySpan *appInitSpan = sentryBuildAppStartSpan(
78-
tracer, appStartSpan.spanId, operation, @"UIKit and Application Init");
77+
SentrySpan *appInitSpan
78+
= sentryBuildAppStartSpan(tracer, appStartSpan.spanId, operation, @"UIKit Init");
7979
[appInitSpan setStartTimestamp:appStartMeasurement.moduleInitializationTimestamp];
80-
[appInitSpan setTimestamp:appStartMeasurement.didFinishLaunchingTimestamp];
80+
[appInitSpan setTimestamp:appStartMeasurement.sdkStartTimestamp];
8181
[appStartSpans addObject:appInitSpan];
8282

83+
SentrySpan *didFinishLaunching
84+
= sentryBuildAppStartSpan(tracer, appStartSpan.spanId, operation, @"Application Init");
85+
[didFinishLaunching setStartTimestamp:appStartMeasurement.sdkStartTimestamp];
86+
[didFinishLaunching setTimestamp:appStartMeasurement.didFinishLaunchingTimestamp];
87+
[appStartSpans addObject:didFinishLaunching];
88+
8389
SentrySpan *frameRenderSpan
8490
= sentryBuildAppStartSpan(tracer, appStartSpan.spanId, operation, @"Initial Frame Render");
8591
[frameRenderSpan setStartTimestamp:appStartMeasurement.didFinishLaunchingTimestamp];

Sources/Sentry/SentrySDK.m

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#import "SentryClient+Private.h"
88
#import "SentryCrash.h"
99
#import "SentryCrashWrapper.h"
10+
#import "SentryCurrentDateProvider.h"
1011
#import "SentryDependencyContainer.h"
1112
#import "SentryDispatchQueueWrapper.h"
1213
#import "SentryFileManager.h"
@@ -42,6 +43,7 @@ @implementation SentrySDK
4243
* reenable the integrations.
4344
*/
4445
static NSUInteger startInvocations;
46+
static NSDate *_Nullable startTimestamp = nil;
4547

4648
+ (void)initialize
4749
{
@@ -135,6 +137,22 @@ + (void)setStartInvocations:(NSUInteger)value
135137
startInvocations = value;
136138
}
137139

140+
/**
141+
* Not public, only for internal use.
142+
*/
143+
+ (nullable NSDate *)startTimestamp
144+
{
145+
return startTimestamp;
146+
}
147+
148+
/**
149+
* Only needed for testing.
150+
*/
151+
+ (void)setStartTimestamp:(NSDate *)value
152+
{
153+
startTimestamp = value;
154+
}
155+
138156
+ (void)startWithOptions:(SentryOptions *)options
139157
{
140158
[SentryLog configure:options.debug diagnosticLevel:options.diagnosticLevel];
@@ -145,6 +163,7 @@ + (void)startWithOptions:(SentryOptions *)options
145163
SENTRY_LOG_DEBUG(@"Starting SDK...");
146164

147165
startInvocations++;
166+
startTimestamp = [SentryDependencyContainer.sharedInstance.dateProvider date];
148167

149168
SentryClient *newClient = [[SentryClient alloc] initWithOptions:options];
150169
[newClient.fileManager moveAppStateToPreviousAppState];
@@ -402,6 +421,8 @@ + (void)close
402421
{
403422
SENTRY_LOG_DEBUG(@"Starting to close SDK.");
404423

424+
startTimestamp = nil;
425+
405426
SentryHub *hub = SentrySDK.currentHub;
406427
[hub removeAllIntegrations];
407428

Sources/Sentry/include/HybridPublic/SentryAppStartMeasurement.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ SENTRY_NO_INIT
2626
duration:(NSTimeInterval)duration
2727
runtimeInitTimestamp:(NSDate *)runtimeInitTimestamp
2828
moduleInitializationTimestamp:(NSDate *)moduleInitializationTimestamp
29+
sdkStartTimestamp:(NSDate *)sdkStartTimestamp
2930
didFinishLaunchingTimestamp:(NSDate *)didFinishLaunchingTimestamp;
3031

3132
/**
@@ -58,6 +59,11 @@ SENTRY_NO_INIT
5859
*/
5960
@property (readonly, nonatomic, strong) NSDate *moduleInitializationTimestamp;
6061

62+
/**
63+
* When the SentrySDK start method is called.
64+
*/
65+
@property (readonly, nonatomic, strong) NSDate *sdkStartTimestamp;
66+
6167
/**
6268
* When OS posts UIApplicationDidFinishLaunchingNotification.
6369
*/

Sources/Sentry/include/SentrySDK+Private.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ SentrySDK ()
2121
+ (nullable SentryAppStartMeasurement *)getAppStartMeasurement;
2222

2323
@property (nonatomic, class) NSUInteger startInvocations;
24+
@property (nullable, nonatomic, class) NSDate *startTimestamp;
2425

2526
+ (SentryHub *)currentHub;
2627

Tests/SentryProfilerTests/SentryProfilerSwiftTests.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,8 @@ class SentryProfilerSwiftTests: XCTestCase {
228228
appStart = preWarmed ? main : appStart
229229
appStartDuration = preWarmed ? appStartDuration - runtimeInitDuration - mainDuration : appStartDuration
230230
appStartEnd = appStart.addingTimeInterval(appStartDuration)
231-
return SentryAppStartMeasurement(type: type, isPreWarmed: preWarmed, appStartTimestamp: appStart, duration: appStartDuration, runtimeInitTimestamp: runtimeInit, moduleInitializationTimestamp: main, didFinishLaunchingTimestamp: didFinishLaunching)
231+
return SentryAppStartMeasurement(type: type, isPreWarmed: preWarmed, appStartTimestamp: appStart, duration: appStartDuration, runtimeInitTimestamp: runtimeInit, moduleInitializationTimestamp: main,
232+
sdkStartTimestamp: appStart, didFinishLaunchingTimestamp: didFinishLaunching)
232233
}
233234
#endif // os(iOS) || os(tvOS) || targetEnvironment(macCatalyst)
234235
}

Tests/SentryTests/Integrations/Performance/AppStartTracking/SentryAppStartTrackerTests.swift

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class SentryAppStartTrackerTests: NotificationCenterTestCase {
2424
let appStartDuration: TimeInterval = 0.4
2525
var runtimeInitTimestamp: Date
2626
var moduleInitializationTimestamp: Date
27+
var sdkStartTimestamp: Date
2728
var didFinishLaunchingTimestamp: Date
2829

2930
init() {
@@ -50,7 +51,10 @@ class SentryAppStartTrackerTests: NotificationCenterTestCase {
5051

5152
runtimeInitTimestamp = SentryDependencyContainer.sharedInstance().dateProvider.date().addingTimeInterval(0.2)
5253
moduleInitializationTimestamp = SentryDependencyContainer.sharedInstance().dateProvider.date().addingTimeInterval(0.1)
53-
didFinishLaunchingTimestamp = SentryDependencyContainer.sharedInstance().dateProvider.date().addingTimeInterval(0.3)
54+
sdkStartTimestamp = SentryDependencyContainer.sharedInstance().dateProvider.date().addingTimeInterval(0.1)
55+
SentrySDK.startTimestamp = sdkStartTimestamp
56+
57+
didFinishLaunchingTimestamp = SentryDependencyContainer.sharedInstance().dateProvider.date().addingTimeInterval(0.2)
5458
}
5559

5660
var sut: SentryAppStartTracker {
@@ -440,10 +444,12 @@ class SentryAppStartTrackerTests: NotificationCenterTestCase {
440444
XCTAssertEqual(fixture.sysctl.processStartTimestamp, appStartMeasurement.appStartTimestamp)
441445
}
442446

443-
XCTAssertEqual(fixture.sysctl.moduleInitializationTimestamp, appStartMeasurement.moduleInitializationTimestamp)
444-
XCTAssertEqual(fixture.runtimeInitTimestamp, appStartMeasurement.runtimeInitTimestamp)
445-
XCTAssertEqual(fixture.didFinishLaunchingTimestamp, appStartMeasurement.didFinishLaunchingTimestamp)
446-
XCTAssertEqual(preWarmed, appStartMeasurement.isPreWarmed)
447+
expect(appStartMeasurement.moduleInitializationTimestamp) == fixture.sysctl.moduleInitializationTimestamp
448+
expect(appStartMeasurement.runtimeInitTimestamp) == fixture.runtimeInitTimestamp
449+
450+
expect(appStartMeasurement.sdkStartTimestamp) == fixture.sdkStartTimestamp
451+
expect(appStartMeasurement.didFinishLaunchingTimestamp) == fixture.didFinishLaunchingTimestamp
452+
expect(appStartMeasurement.isPreWarmed) == preWarmed
447453
}
448454

449455
private func assertValidHybridStart(type: SentryAppStartType) {

Tests/SentryTests/Protocol/TestData.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -328,9 +328,10 @@ class TestData {
328328
let appStartDuration = 0.5
329329
let main = appStartTimestamp.addingTimeInterval(0.15)
330330
let runtimeInit = appStartTimestamp.addingTimeInterval(0.05)
331-
let didFinishLaunching = appStartTimestamp.addingTimeInterval(0.3)
331+
let sdkStart = appStartTimestamp.addingTimeInterval(0.1)
332+
let didFinishLaunching = appStartTimestamp.addingTimeInterval(0.2)
332333

333-
return SentryAppStartMeasurement(type: type, isPreWarmed: false, appStartTimestamp: appStartTimestamp, duration: appStartDuration, runtimeInitTimestamp: runtimeInit, moduleInitializationTimestamp: main, didFinishLaunchingTimestamp: didFinishLaunching)
334+
return SentryAppStartMeasurement(type: type, isPreWarmed: false, appStartTimestamp: appStartTimestamp, duration: appStartDuration, runtimeInitTimestamp: runtimeInit, moduleInitializationTimestamp: main, sdkStartTimestamp: sdkStart, didFinishLaunchingTimestamp: didFinishLaunching)
334335
}
335336

336337
#endif // os(iOS) || os(tvOS) || targetEnvironment(macCatalyst)

Tests/SentryTests/SentrySDKTests.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import Nimble
12
@testable import Sentry
23
import SentryTestUtils
34
import XCTest
@@ -504,6 +505,23 @@ class SentrySDKTests: XCTestCase {
504505
XCTAssertEqual(1, SentrySDK.startInvocations)
505506
}
506507

508+
func testSDKStartTimestamp() {
509+
let currentDateProvider = TestCurrentDateProvider()
510+
SentryDependencyContainer.sharedInstance().dateProvider = currentDateProvider
511+
512+
expect(SentrySDK.startTimestamp) == nil
513+
514+
SentrySDK.start { options in
515+
options.dsn = SentrySDKTests.dsnAsString
516+
options.removeAllIntegrations()
517+
}
518+
519+
expect(SentrySDK.startTimestamp) == currentDateProvider.date()
520+
521+
SentrySDK.close()
522+
expect(SentrySDK.startTimestamp) == nil
523+
}
524+
507525
func testIsEnabled() {
508526
XCTAssertFalse(SentrySDK.isEnabled)
509527

Tests/SentryTests/Transaction/SentryTracerTests.swift

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,13 +78,14 @@ class SentryTracerTests: XCTestCase {
7878
let runtimeInit = appStart.addingTimeInterval(runtimeInitDuration)
7979
let mainDuration = 0.15
8080
let main = appStart.addingTimeInterval(mainDuration)
81-
let didFinishLaunching = appStart.addingTimeInterval(0.3)
81+
let sdkStart = appStart.addingTimeInterval(0.1)
82+
let didFinishLaunching = appStart.addingTimeInterval(0.2)
8283
appStart = preWarmed ? main : appStart
8384
appStartDuration = preWarmed ? appStartDuration - runtimeInitDuration - mainDuration : appStartDuration
8485

8586
appStartEnd = appStart.addingTimeInterval(appStartDuration)
8687

87-
return SentryAppStartMeasurement(type: type, isPreWarmed: preWarmed, appStartTimestamp: appStart, duration: appStartDuration, runtimeInitTimestamp: runtimeInit, moduleInitializationTimestamp: main, didFinishLaunchingTimestamp: didFinishLaunching)
88+
return SentryAppStartMeasurement(type: type, isPreWarmed: preWarmed, appStartTimestamp: appStart, duration: appStartDuration, runtimeInitTimestamp: runtimeInit, moduleInitializationTimestamp: main, sdkStartTimestamp: sdkStart, didFinishLaunchingTimestamp: didFinishLaunching)
8889
}
8990
#endif // os(iOS) || os(tvOS) || targetEnvironment(macCatalyst)
9091

@@ -763,6 +764,7 @@ class SentryTracerTests: XCTestCase {
763764
duration: 0.5,
764765
runtimeInitTimestamp: fixture.currentDateProvider.date(),
765766
moduleInitializationTimestamp: fixture.currentDateProvider.date(),
767+
sdkStartTimestamp: fixture.currentDateProvider.date(),
766768
didFinishLaunchingTimestamp: fixture.currentDateProvider.date()
767769
))
768770

@@ -1188,7 +1190,7 @@ class SentryTracerTests: XCTestCase {
11881190
#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst)
11891191
private func assertAppStartsSpanAdded(transaction: Transaction, startType: String, operation: String, appStartMeasurement: SentryAppStartMeasurement) {
11901192
let spans: [SentrySpan]? = Dynamic(transaction).spans
1191-
XCTAssertEqual(5, spans?.count)
1193+
expect(spans?.count) == 6
11921194

11931195
let appLaunchSpan = spans?.first { span in
11941196
span.spanDescription == startType
@@ -1214,7 +1216,8 @@ class SentryTracerTests: XCTestCase {
12141216

12151217
assertSpan("Pre Runtime Init", appStartMeasurement.appStartTimestamp, appStartMeasurement.runtimeInitTimestamp)
12161218
assertSpan("Runtime Init to Pre Main Initializers", appStartMeasurement.runtimeInitTimestamp, appStartMeasurement.moduleInitializationTimestamp)
1217-
assertSpan("UIKit and Application Init", appStartMeasurement.moduleInitializationTimestamp, appStartMeasurement.didFinishLaunchingTimestamp)
1219+
assertSpan("UIKit Init", appStartMeasurement.moduleInitializationTimestamp, appStartMeasurement.sdkStartTimestamp)
1220+
assertSpan("Application Init", appStartMeasurement.sdkStartTimestamp, appStartMeasurement.didFinishLaunchingTimestamp)
12181221
assertSpan("Initial Frame Render", appStartMeasurement.didFinishLaunchingTimestamp, fixture.appStartEnd)
12191222
}
12201223

@@ -1230,7 +1233,7 @@ class SentryTracerTests: XCTestCase {
12301233

12311234
private func assertPreWarmedAppStartsSpanAdded(transaction: Transaction, startType: String, operation: String, appStartMeasurement: SentryAppStartMeasurement) {
12321235
let spans: [SentrySpan]? = Dynamic(transaction).spans
1233-
XCTAssertEqual(3, spans?.count)
1236+
expect(spans?.count) == 4
12341237

12351238
let appLaunchSpan = spans?.first { span in
12361239
span.spanDescription == startType
@@ -1252,7 +1255,8 @@ class SentryTracerTests: XCTestCase {
12521255
XCTAssertEqual(timestamp, span?.timestamp)
12531256
}
12541257

1255-
assertSpan("UIKit and Application Init", appStartMeasurement.moduleInitializationTimestamp, appStartMeasurement.didFinishLaunchingTimestamp)
1258+
assertSpan("UIKit Init", appStartMeasurement.moduleInitializationTimestamp, appStartMeasurement.sdkStartTimestamp)
1259+
assertSpan("Application Init", appStartMeasurement.sdkStartTimestamp, appStartMeasurement.didFinishLaunchingTimestamp)
12561260
assertSpan("Initial Frame Render", appStartMeasurement.didFinishLaunchingTimestamp, fixture.appStartEnd)
12571261
}
12581262

0 commit comments

Comments
 (0)