Skip to content

Commit

Permalink
Merge 6d4738f into 6749d0f
Browse files Browse the repository at this point in the history
  • Loading branch information
philipphofmann committed Jan 3, 2024
2 parents 6749d0f + 6d4738f commit 2cfa65e
Show file tree
Hide file tree
Showing 13 changed files with 130 additions and 14 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
- TTFD waits for next drawn frame (#3505)
- Fix TTID/TTFD for app start transactions (#3512): TTID/TTFD spans and measurements for app start transaction now include the app start duration.
- Fix a race condition in SentryTracer (#3523)
- App start ends when first frame is drawn when performanceV2 is enabled (#3530)

## 8.17.2

Expand Down
1 change: 1 addition & 0 deletions Samples/iOS-Swift/iOS-Swift/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
options.attachViewHierarchy = true
options.environment = "test-app"
options.enableTimeToFullDisplayTracing = true
options.enablePerformanceV2 = true

options.add(inAppInclude: "iOS_External")

Expand Down
11 changes: 11 additions & 0 deletions Sources/Sentry/Public/SentryOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,17 @@ NS_SWIFT_NAME(Options)
*/
@property (nonatomic, assign) BOOL enableAutoPerformanceTracing;

/**
* @warning This is an experimental feature and may still have bugs.
*
* Sentry works on reworking the whole performance offering with the code Mobile Starfish, which
* aims to provide better insights into the performance of mobile apps and highlight clear actions
* to improve app performance to developers. This feature flag enables experimental features that
* impact the v1 performance offering and would require a major version update. Sentry aims to
* include most features in the next major by default.
*/
@property (nonatomic, assign) BOOL enablePerformanceV2;

/**
* A block that configures the initial scope when starting the SDK.
* @discussion The block receives a suggested default scope. You can either
Expand Down
29 changes: 27 additions & 2 deletions Sources/Sentry/SentryAppStartTracker.m
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
# import "SentryAppStartMeasurement.h"
# import "SentryAppStateManager.h"
# import "SentryDefines.h"
# import "SentryFramesTracker.h"
# import "SentryLog.h"
# import "SentrySysctl.h"
# import <Foundation/Foundation.h>
Expand All @@ -28,14 +29,16 @@
static const NSTimeInterval SENTRY_APP_START_MAX_DURATION = 180.0;

@interface
SentryAppStartTracker ()
SentryAppStartTracker () <SentryFramesTrackerListener>

@property (nonatomic, strong) SentryAppState *previousAppState;
@property (nonatomic, strong) SentryDispatchQueueWrapper *dispatchQueue;
@property (nonatomic, strong) SentryAppStateManager *appStateManager;
@property (nonatomic, strong) SentryFramesTracker *framesTracker;
@property (nonatomic, assign) BOOL wasInBackground;
@property (nonatomic, strong) NSDate *didFinishLaunchingTimestamp;
@property (nonatomic, assign) BOOL enablePreWarmedAppStartTracing;
@property (nonatomic, assign) BOOL enablePerformanceV2;

@end

Expand All @@ -55,11 +58,19 @@ + (void)load

- (instancetype)initWithDispatchQueueWrapper:(SentryDispatchQueueWrapper *)dispatchQueueWrapper
appStateManager:(SentryAppStateManager *)appStateManager
framesTracker:(SentryFramesTracker *)framesTracker
enablePreWarmedAppStartTracing:(BOOL)enablePreWarmedAppStartTracing
enablePerformanceV2:(BOOL)enablePerformanceV2
{
if (self = [super init]) {
self.dispatchQueue = dispatchQueueWrapper;
self.appStateManager = appStateManager;
_enablePerformanceV2 = enablePerformanceV2;
if (_enablePerformanceV2) {
self.framesTracker = framesTracker;
[framesTracker addListener:self];
}

self.previousAppState = [self.appStateManager loadPreviousAppState];
self.wasInBackground = NO;
self.didFinishLaunchingTimestamp =
Expand Down Expand Up @@ -222,9 +233,21 @@ - (void)buildAppStartMeasurement
}

/**
* This is when the first frame is drawn.
* This is when the window becomes visible, which is not when the first frame of the app is drawn.
* When this is posted, the app screen is usually white. The correct time when the first frame is
* drawn is called in framesTrackerHasNewFrame only when `enablePerformanceV2` is enabled.
*/
- (void)didBecomeVisible
{
if (!_enablePerformanceV2) {
[self buildAppStartMeasurement];
}
}

/**
* This is when the first frame is drawn.
*/
- (void)framesTrackerHasNewFrame
{
[self buildAppStartMeasurement];
}
Expand Down Expand Up @@ -288,6 +311,8 @@ - (void)stop
name:UIApplicationDidEnterBackgroundNotification
object:nil];

[self.framesTracker removeListener:self];

# if TEST
self.isRunning = NO;
# endif
Expand Down
4 changes: 3 additions & 1 deletion Sources/Sentry/SentryAppStartTrackingIntegration.m
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ - (BOOL)installWithOptions:(SentryOptions *)options
self.tracker = [[SentryAppStartTracker alloc]
initWithDispatchQueueWrapper:[[SentryDispatchQueueWrapper alloc] init]
appStateManager:appStateManager
enablePreWarmedAppStartTracing:options.enablePreWarmedAppStartTracing];
framesTracker:SentryDependencyContainer.sharedInstance.framesTracker
enablePreWarmedAppStartTracing:options.enablePreWarmedAppStartTracing
enablePerformanceV2:options.enablePerformanceV2];
[self.tracker start];

return YES;
Expand Down
4 changes: 4 additions & 0 deletions Sources/Sentry/SentryOptions.m
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ - (instancetype)init
self.maxAttachmentSize = 20 * 1024 * 1024;
self.sendDefaultPii = NO;
self.enableAutoPerformanceTracing = YES;
self.enablePerformanceV2 = NO;
self.enableCaptureFailedRequests = YES;
self.environment = kSentryDefaultEnvironment;
self.enableTimeToFullDisplayTracing = NO;
Expand Down Expand Up @@ -375,6 +376,9 @@ - (BOOL)validateOptions:(NSDictionary<NSString *, id> *)options
[self setBool:options[@"enableAutoPerformanceTracing"]
block:^(BOOL value) { self->_enableAutoPerformanceTracing = value; }];

[self setBool:options[@"enablePerformanceV2"]
block:^(BOOL value) { self->_enablePerformanceV2 = value; }];

[self setBool:options[@"enableCaptureFailedRequests"]
block:^(BOOL value) { self->_enableCaptureFailedRequests = value; }];

Expand Down
3 changes: 2 additions & 1 deletion Sources/Sentry/SentryTracer.m
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,6 @@ - (instancetype)initWithTransactionContext:(SentryTransactionContext *)transacti
}

#if SENTRY_HAS_UIKIT
appStartMeasurement = [self getAppStartMeasurement];
viewNames = [SentryDependencyContainer.sharedInstance.application relevantViewControllersNames];
#endif // SENTRY_HAS_UIKIT

Expand Down Expand Up @@ -497,6 +496,8 @@ - (void)finishInternal
[super finishWithStatus:_finishStatus];
}
#if SENTRY_HAS_UIKIT
appStartMeasurement = [self getAppStartMeasurement];

if (appStartMeasurement != nil) {
[self updateStartTime:appStartMeasurement.appStartTimestamp];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ SENTRY_NO_INIT

/**
* How long the app start took. From appStartTimestamp to when the SDK creates the
* AppStartMeasurement, which is done when the OS posts UIWindowDidBecomeVisibleNotification.
* AppStartMeasurement, which is done when the OS posts UIWindowDidBecomeVisibleNotification and
* when `enablePerformanceV2` is enabled when the app draws it's first frame.
*/
@property (readonly, nonatomic, assign) NSTimeInterval duration;

Expand Down
5 changes: 4 additions & 1 deletion Sources/Sentry/include/SentryAppStartTracker.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

@class SentryDispatchQueueWrapper;
@class SentryAppStateManager;
@class SentryFramesTracker;

NS_ASSUME_NONNULL_BEGIN

Expand All @@ -22,7 +23,9 @@ SENTRY_NO_INIT

- (instancetype)initWithDispatchQueueWrapper:(SentryDispatchQueueWrapper *)dispatchQueueWrapper
appStateManager:(SentryAppStateManager *)appStateManager
enablePreWarmedAppStartTracing:(BOOL)enablePreWarmedAppStartTracing;
framesTracker:(SentryFramesTracker *)framesTracker
enablePreWarmedAppStartTracing:(BOOL)enablePreWarmedAppStartTracing
enablePerformanceV2:(BOOL)enablePerformanceV2;

- (void)start;
- (void)stop;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Nimble
import SentryTestUtils
import XCTest

Expand All @@ -14,8 +15,11 @@ class SentryAppStartTrackerTests: NotificationCenterTestCase {
let fileManager: SentryFileManager
let crashWrapper = TestSentryCrashWrapper.sharedInstance()
let appStateManager: SentryAppStateManager
var displayLinkWrapper = TestDisplayLinkWrapper()
var framesTracker: SentryFramesTracker
let dispatchQueue = TestSentryDispatchQueueWrapper()
var enablePreWarmedAppStartTracing = true
var enablePerformanceV2 = false

let appStartDuration: TimeInterval = 0.4
var runtimeInitTimestamp: Date
Expand All @@ -41,6 +45,10 @@ class SentryAppStartTrackerTests: NotificationCenterTestCase {
notificationCenterWrapper: SentryNSNotificationCenterWrapper()
)

framesTracker = SentryFramesTracker(displayLinkWrapper: displayLinkWrapper, dateProvider: currentDate, keepDelayedFramesDuration: 0)
SentryDependencyContainer.sharedInstance().framesTracker = framesTracker
framesTracker.start()

runtimeInitTimestamp = SentryDependencyContainer.sharedInstance().dateProvider.date().addingTimeInterval(0.2)
moduleInitializationTimestamp = SentryDependencyContainer.sharedInstance().dateProvider.date().addingTimeInterval(0.1)
didFinishLaunchingTimestamp = SentryDependencyContainer.sharedInstance().dateProvider.date().addingTimeInterval(0.3)
Expand All @@ -50,7 +58,9 @@ class SentryAppStartTrackerTests: NotificationCenterTestCase {
let sut = SentryAppStartTracker(
dispatchQueueWrapper: TestSentryDispatchQueueWrapper(),
appStateManager: appStateManager,
enablePreWarmedAppStartTracing: enablePreWarmedAppStartTracing
framesTracker: framesTracker,
enablePreWarmedAppStartTracing: enablePreWarmedAppStartTracing,
enablePerformanceV2: enablePerformanceV2
)
return sut
}
Expand Down Expand Up @@ -80,6 +90,25 @@ class SentryAppStartTrackerTests: NotificationCenterTestCase {
assertValidStart(type: .cold)
}

func testPerformanceV2_UsesRenderedFrameAsEndTimeStamp() {
fixture.enablePerformanceV2 = true

startApp()

assertValidStart(type: .cold, expectedDuration: 0.45)
}

func testPerformanceV2_RemovesFramesTrackerListener() {
fixture.enablePerformanceV2 = true

startApp()

advanceTime(bySeconds: 0.05)
fixture.displayLinkWrapper.normalFrame()

assertValidStart(type: .cold, expectedDuration: 0.45)
}

func testSecondStart_AfterSystemReboot_IsColdStart() {
let previousBootTime = SentryDependencyContainer.sharedInstance().dateProvider.date().addingTimeInterval(-1)
let appState = SentryAppState(releaseName: TestData.appState.releaseName, osVersion: UIDevice.current.systemVersion, vendorId: TestData.someUUID, isDebugging: false, systemBootTimestamp: previousBootTime)
Expand Down Expand Up @@ -345,6 +374,9 @@ class SentryAppStartTrackerTests: NotificationCenterTestCase {
advanceTime(bySeconds: 0.1)
uiWindowDidBecomeVisible()
didBecomeActive()

advanceTime(bySeconds: 0.05)
fixture.displayLinkWrapper.normalFrame()
}

private func hybridAppStart() {
Expand Down Expand Up @@ -397,7 +429,7 @@ class SentryAppStartTrackerTests: NotificationCenterTestCase {

let expectedAppStartDuration = expectedDuration ?? fixture.appStartDuration
let actualAppStartDuration = appStartMeasurement.duration
XCTAssertEqual(expectedAppStartDuration, actualAppStartDuration, accuracy: 0.0001)
expect(actualAppStartDuration).to(beCloseTo(expectedAppStartDuration, within: 0.0001))

if preWarmed {
XCTAssertEqual(fixture.moduleInitializationTimestamp, appStartMeasurement.appStartTimestamp)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Nimble
import SentryTestUtils
import XCTest

Expand Down Expand Up @@ -108,9 +109,21 @@ class SentryAppStartTrackingIntegrationTests: NotificationCenterTestCase {

XCTAssertFalse(result)
}

func test_PerformanceV2Enabled() {
let options = fixture.options
options.enablePerformanceV2 = true

expect(self.sut.install(with: options)) == true

let tracker = Dynamic(sut).tracker.asAnyObject as? SentryAppStartTracker
expect(Dynamic(tracker).enablePerformanceV2.asBool) == true
}

func assertTrackerSetupAndRunning(_ tracker: SentryAppStartTracker) throws {
_ = try XCTUnwrap(Dynamic(tracker).dispatchQueue.asAnyObject as? SentryDispatchQueueWrapper, "Tracker does not have a dispatch queue.")

expect(Dynamic(tracker).enablePerformanceV2.asBool) == false

let appStateManager = Dynamic(tracker).appStateManager.asObject as? SentryAppStateManager

Expand Down
5 changes: 5 additions & 0 deletions Tests/SentryTests/SentryOptionsTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -726,6 +726,11 @@ - (void)testEnableAutoPerformanceTracing
[self testBooleanField:@"enableAutoPerformanceTracing"];
}

- (void)testEnablePerformanceV2
{
[self testBooleanField:@"enablePerformanceV2" defaultValue:NO];
}

#if SENTRY_HAS_UIKIT
- (void)testEnableUIViewControllerTracing
{
Expand Down
29 changes: 23 additions & 6 deletions Tests/SentryTests/Transaction/SentryTracerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -723,8 +723,23 @@ class SentryTracerTests: XCTestCase {

assertAppStartMeasurementOn(transaction: fixture.hub.capturedEventsWithScopes.first!.event as! Transaction, appStartMeasurement: appStartMeasurement)
}

func testAddAppStartMeasurementWhileTransactionRunning_PutOnNextAutoUITransaction() {
let appStartMeasurement = fixture.getAppStartMeasurement(type: .warm)

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

let sut = fixture.getSut()
advanceTime(bySeconds: 1)
SentrySDK.setAppStartMeasurement(appStartMeasurement)
sut.finish()

expect(self.fixture.hub.capturedEventsWithScopes.count) == 1

func testAddColdStartMeasurement_PutOnFirstStartedTransaction() {
assertAppStartMeasurementOn(transaction: fixture.hub.capturedEventsWithScopes.first!.event as! Transaction, appStartMeasurement: appStartMeasurement)
}

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

Expand All @@ -737,14 +752,16 @@ class SentryTracerTests: XCTestCase {
advanceTime(bySeconds: 0.5)
secondTransaction.finish()

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

firstTransaction.finish()

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

let serializedFirstTransaction = fixture.hub.capturedEventsWithScopes.invocations[1].event.serialize()
expect(serializedFirstTransaction["measurements"]) == nil

assertAppStartMeasurementOn(transaction: fixture.hub.capturedEventsWithScopes.invocations[0].event as! Transaction, appStartMeasurement: appStartMeasurement)
}

func testAddUnknownAppStartMeasurement_NotPutOnNextTransaction() {
Expand Down

0 comments on commit 2cfa65e

Please sign in to comment.