Skip to content

Commit

Permalink
feat: Time to initial and full display (#2724)
Browse files Browse the repository at this point in the history
A new Span is created to track time for initial display in automatic UIViewController performance tracking.

A new API was added to SentrySDK where users may report a full display. This is meant to be called when the view controller content is fully loaded and ready to be used.

Co-authored-by: Philipp Hofmann <philipp.hofmann@sentry.io>
Co-authored-by: Andrew McKnight <andrew.mcknight@sentry.io>
  • Loading branch information
3 people authored Mar 30, 2023
1 parent 3f366ee commit cf724da
Show file tree
Hide file tree
Showing 47 changed files with 954 additions and 73 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

### Features

- Time to initial and full display (#2724)
- Add `name` and `geo` to User (#2710)

### Fixes

- Correctly track and send GPU frame render data in profiles (#2823)
Expand Down
2 changes: 1 addition & 1 deletion Samples/iOS-Swift/iOS-Swift/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
options.attachScreenshot = true
options.attachViewHierarchy = true
options.environment = "test-app"
options.enableTimeToFullDisplay = true

let isBenchmarking = ProcessInfo.processInfo.arguments.contains("--io.sentry.test.benchmarking")
options.enableAutoPerformanceTracing = !isBenchmarking

// the benchmark test starts and stops a custom transaction using a UIButton, and automatic user interaction tracing stops the transaction that begins with that button press after the idle timeout elapses, stopping the profiler (only one profiler runs regardless of the number of concurrent transactions)
options.enableUserInteractionTracing = !isBenchmarking
Expand Down
7 changes: 6 additions & 1 deletion Samples/iOS-Swift/iOS-Swift/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,12 @@ class ViewController: UIViewController {
uiTestNameLabel.text = uiTestName
}
}


override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
SentrySDK.reportFullyDisplayed()
}

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ class CoreDataViewController: UITableViewController {
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
}

SentrySDK.reportFullyDisplayed()
self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(requestNewPerson(_:)))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class LoremIpsumViewController: UIViewController {
if let contents = FileManager.default.contents(atPath: path) {
DispatchQueue.main.async {
self.textView.text = String(data: contents, encoding: .utf8)
SentrySDK.reportFullyDisplayed()
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ class NibViewController: UIViewController {
UIAssert.checkForViewControllerLifeCycle(span, viewController: "NibViewController")
}

override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
SentrySDK.reportFullyDisplayed()
}

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ class PerformanceViewController: UIViewController {
fatalError("init(coder:) has not been implemented")
}

override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
SentrySDK.reportFullyDisplayed()
}

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
startTest()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ class PermissionsViewController: UIViewController {
@objc func requestLocationPermission() {
locationManager.requestWhenInUseAuthorization()
}

override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
SentrySDK.reportFullyDisplayed()
}

@objc func requestPushPermission() {
UNUserNotificationCenter.current()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ class SplitViewController: UISplitViewController {
super.init(coder: coder)
initialize()
}

override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
SentrySDK.reportFullyDisplayed()
}

private func initialize() {
self.modalPresentationStyle = .fullScreen
Expand All @@ -24,6 +29,11 @@ class SplitRootViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}

override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
SentrySDK.reportFullyDisplayed()
}

@IBAction func close() {
parent?.dismiss(animated: false, completion: nil)
Expand Down Expand Up @@ -57,6 +67,11 @@ class SecondarySplitViewController: UIViewController {

spanObserver = createTransactionObserver(forCallback: assertTransaction)
}

override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
SentrySDK.reportFullyDisplayed()
}

override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class TableViewController: UITableViewController {
super.viewDidLoad()

spanObserver = createTransactionObserver(forCallback: assertTransaction)
SentrySDK.reportFullyDisplayed()
}

func assertTransaction(span: Span) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@ class TraceTestViewController: UIViewController {
}
let session = URLSession(configuration: URLSessionConfiguration.default)
let dataTask = session.dataTask(with: imgUrl) { (data, _, error) in
//Simulated delay in the download
DispatchQueue.main.async {
if let err = error {
SentrySDK.capture(error: err)
} else if let image = data {
self.imageView.image = UIImage(data: image)
self.appendLifeCycleStep("GET https://sentry-brand.storage.googleapis.com/sentry-logo-black.png")
}
SentrySDK.reportFullyDisplayed()
}
}

Expand Down
31 changes: 19 additions & 12 deletions Sentry.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -767,6 +767,9 @@
D8B76B0828081461000A58C4 /* TestSentryScreenShot.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8B76B0728081461000A58C4 /* TestSentryScreenShot.swift */; };
D8BBD32728FD9FC00011F850 /* SentrySwift.h in Headers */ = {isa = PBXBuildFile; fileRef = D8BBD32628FD9FBF0011F850 /* SentrySwift.h */; };
D8BD2E6829361A0F00D96C6A /* PrivatesHeader.h in Headers */ = {isa = PBXBuildFile; fileRef = D8BD2E67293619F600D96C6A /* PrivatesHeader.h */; settings = {ATTRIBUTES = (Private, ); }; };
D8BFE37229A3782F002E73F3 /* SentryTimeToDisplayTracker.h in Headers */ = {isa = PBXBuildFile; fileRef = D8BFE37029A3782F002E73F3 /* SentryTimeToDisplayTracker.h */; };
D8BFE37329A3782F002E73F3 /* SentryTimeToDisplayTracker.m in Sources */ = {isa = PBXBuildFile; fileRef = D8BFE37129A3782F002E73F3 /* SentryTimeToDisplayTracker.m */; };
D8BFE37929A76666002E73F3 /* SentryTimeToDisplayTrackerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8BFE37729A76519002E73F3 /* SentryTimeToDisplayTrackerTest.swift */; };
D8C67E9B28000E24007E326E /* SentryUIApplication.h in Headers */ = {isa = PBXBuildFile; fileRef = D8C67E9928000E23007E326E /* SentryUIApplication.h */; };
D8C67E9C28000E24007E326E /* SentryScreenshot.h in Headers */ = {isa = PBXBuildFile; fileRef = D8C67E9A28000E23007E326E /* SentryScreenshot.h */; };
D8CB74152947246600A5F964 /* SentryEnvelopeAttachmentHeader.h in Headers */ = {isa = PBXBuildFile; fileRef = D8CB74142947246600A5F964 /* SentryEnvelopeAttachmentHeader.h */; };
Expand Down Expand Up @@ -1675,6 +1678,9 @@
D8BBD32628FD9FBF0011F850 /* SentrySwift.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentrySwift.h; path = include/SentrySwift.h; sourceTree = "<group>"; };
D8BD2E27292D1F7300D96C6A /* SDK.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = SDK.xcconfig; sourceTree = "<group>"; };
D8BD2E67293619F600D96C6A /* PrivatesHeader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = PrivatesHeader.h; path = include/HybridPublic/PrivatesHeader.h; sourceTree = "<group>"; };
D8BFE37029A3782F002E73F3 /* SentryTimeToDisplayTracker.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryTimeToDisplayTracker.h; path = include/SentryTimeToDisplayTracker.h; sourceTree = "<group>"; };
D8BFE37129A3782F002E73F3 /* SentryTimeToDisplayTracker.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryTimeToDisplayTracker.m; sourceTree = "<group>"; };
D8BFE37729A76519002E73F3 /* SentryTimeToDisplayTrackerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryTimeToDisplayTrackerTest.swift; sourceTree = "<group>"; };
D8C67E9928000E23007E326E /* SentryUIApplication.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryUIApplication.h; path = include/SentryUIApplication.h; sourceTree = "<group>"; };
D8C67E9A28000E23007E326E /* SentryScreenshot.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryScreenshot.h; path = include/SentryScreenshot.h; sourceTree = "<group>"; };
D8CB74142947246600A5F964 /* SentryEnvelopeAttachmentHeader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryEnvelopeAttachmentHeader.h; path = include/SentryEnvelopeAttachmentHeader.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2163,7 +2169,6 @@
7BD7299B24654CD500EA3610 /* Helper */,
7B944FA924697E9700A10721 /* Integrations */,
7BBD18AF24517E5D00427C76 /* Networking */,
7B42602C26302DE500B36EDD /* Performance */,
035E73C627D5661A005EEB11 /* Profiling */,
7B3D0474249A3D5800E106B6 /* Protocol */,
63FE71D220DA66C500CDBAE8 /* SentryCrash */,
Expand Down Expand Up @@ -2545,16 +2550,6 @@
path = Protocol;
sourceTree = "<group>";
};
7B42602C26302DE500B36EDD /* Performance */ = {
isa = PBXGroup;
children = (
8EAC7FF7265C8910005B44E5 /* SentryTracerTests.swift */,
7BBEB16026AEE5EF00C06C03 /* SentryTracer+Test.h */,
7BEFB043270B0F630025F808 /* SentryTracerObjCTests.m */,
);
path = Performance;
sourceTree = "<group>";
};
7B634595280EB94200CFA05A /* UIEvents */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -2585,6 +2580,9 @@
D88817DB26D72B7B00BF2251 /* SentryTraceStateTests.swift */,
7BE912AE272166DD00E49E62 /* SentryNoOpSpanTests.swift */,
D880E3A628573E87008A90DB /* SentryBaggageTests.swift */,
8EAC7FF7265C8910005B44E5 /* SentryTracerTests.swift */,
7BBEB16026AEE5EF00C06C03 /* SentryTracer+Test.h */,
7BEFB043270B0F630025F808 /* SentryTracerObjCTests.m */,
D8137D52272B53070082656C /* TestSentrySpan.h */,
D8137D53272B53070082656C /* TestSentrySpan.m */,
);
Expand Down Expand Up @@ -2825,6 +2823,8 @@
8EAE9809261E9F530073B6B3 /* SentryPerformanceTracker.h */,
8EBF870726140D37001A6853 /* SentryPerformanceTracker.m */,
0A4EDEA828D3461B00FA67CB /* SentryPerformanceTracker+Private.h */,
D8BFE37029A3782F002E73F3 /* SentryTimeToDisplayTracker.h */,
D8BFE37129A3782F002E73F3 /* SentryTimeToDisplayTracker.m */,
);
name = UIViewController;
sourceTree = "<group>";
Expand Down Expand Up @@ -2946,6 +2946,7 @@
8EA1ED0E2669152F00E62B98 /* SentryUIViewControllerPerformanceTrackerTests.swift */,
7BEF4956270C4B9D00F8F30E /* SentryUIViewControllerSwizzlingTests.swift */,
7BC9CD4326A99F660047518E /* SentryUIViewControllerSwizzling+Test.h */,
D8BFE37729A76519002E73F3 /* SentryTimeToDisplayTrackerTest.swift */,
);
path = UIViewController;
sourceTree = "<group>";
Expand Down Expand Up @@ -3501,6 +3502,7 @@
7B4E375525822C4500059C93 /* SentryAttachment.h in Headers */,
63FE716F20DA4C1100CDBAE8 /* SentryCrashCPU_Apple.h in Headers */,
639FCFA81EBC80CC00778193 /* SentryFrame.h in Headers */,
D8BFE37229A3782F002E73F3 /* SentryTimeToDisplayTracker.h in Headers */,
8E8C57A625EEFC43001CEEFA /* SentryTracesSampler.h in Headers */,
7B634599280EB9D100CFA05A /* SentryUIEventTrackingIntegration.h in Headers */,
63FE716D20DA4C1100CDBAE8 /* SentryCrashSysCtl.h in Headers */,
Expand Down Expand Up @@ -3944,6 +3946,7 @@
03F84D3827DD4191008FE43F /* SentryBacktrace.cpp in Sources */,
63FE712720DA4C1000CDBAE8 /* SentryCrashThread.c in Sources */,
7B127B0F27CF6F4700A71ED2 /* SentryANRTrackingIntegration.m in Sources */,
D8BFE37329A3782F002E73F3 /* SentryTimeToDisplayTracker.m in Sources */,
15360CCF2432777500112302 /* SentrySessionTracker.m in Sources */,
6334314320AD9AE40077E581 /* SentryMechanism.m in Sources */,
63FE70D320DA4C1000CDBAE8 /* SentryCrashMonitor_AppState.c in Sources */,
Expand Down Expand Up @@ -4218,6 +4221,7 @@
7B4E23B6251A07BD00060D68 /* SentryDispatchQueueWrapperTests.swift in Sources */,
63FE720720DA66EC00CDBAE8 /* SentryCrashReportFilter_Tests.m in Sources */,
7B569E002590EEF600B653FC /* SentryScope+Equality.m in Sources */,
D8BFE37929A76666002E73F3 /* SentryTimeToDisplayTrackerTest.swift in Sources */,
7BF536D424BEF255004FA6A2 /* SentryAssertions.swift in Sources */,
7BC6EC14255C415E0059822A /* SentryExceptionTests.swift in Sources */,
7B82722927A319E900F4BFF4 /* SentryAutoSessionTrackingIntegrationTests.swift in Sources */,
Expand Down Expand Up @@ -4506,7 +4510,10 @@
ENABLE_STRICT_OBJC_MSGSEND = NO;
GCC_C_LANGUAGE_STANDARD = "compiler-default";
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)";
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_SHADOW = YES;
INFOPLIST_FILE = Sources/Sentry/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
Expand Down
1 change: 1 addition & 0 deletions SentryTestUtils/ClearTestState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class TestCleanup: NSObject {

setenv("ActivePrewarm", "0", 1)
SentryAppStartTracker.load()
SentryUIViewControllerPerformanceTracker.shared.enableWaitForFullDisplay = false
#endif

SentryDependencyContainer.reset()
Expand Down
1 change: 1 addition & 0 deletions SentryTestUtils/SentryTestUtils-ObjC-BridgingHeader.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@
#import "SentryTransport.h"
#import "SentryTransportAdapter.h"
#import "SentryUIDeviceWrapper.h"
#import "SentryUIViewControllerPerformanceTracker.h"
11 changes: 10 additions & 1 deletion SentryTestUtils/TestCurrentDateProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,18 @@ import Foundation
public class TestCurrentDateProvider: NSObject, CurrentDateProvider {

private var internalDate = Date(timeIntervalSinceReferenceDate: 0)

public var driftTimeForEveryRead = false

public func date() -> Date {
internalDate

defer {
if driftTimeForEveryRead {
internalDate = internalDate.addingTimeInterval(0.1)
}
}

return internalDate
}

public func setDate(date: Date) {
Expand Down
7 changes: 7 additions & 0 deletions Sources/Sentry/Public/SentryHub.h
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,13 @@ SENTRY_NO_INIT
*/
- (void)setUser:(SentryUser *_Nullable)user;

/**
* Reports to the ongoing UIViewController transaction
* that the screen contents are fully loaded and displayed,
* which will create a new span.
*/
- (void)reportFullyDisplayed;

/**
* Waits synchronously for the SDK to flush out all queued and cached items for up to the specified
* timeout in seconds. If there is no internet connection, the function returns immediately. The SDK
Expand Down
12 changes: 12 additions & 0 deletions Sources/Sentry/Public/SentryOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,18 @@ NS_SWIFT_NAME(Options)

#endif

/**
* @warning This is an experimental feature and may still have bugs.
* @brief By enabling this, every UIViewController tracing transaction will wait
* for a call to @c SentrySDK.reportFullyDisplayed().
* @discussion Use this in conjunction with @c enableUIViewControllerTracing.
* If @c SentrySDK.reportFullyDisplayed() is not called, the transaction will finish
* automatically after 30 seconds and the `Time to full display` Span will be
* finished with @c DeadlineExceeded status.
* @note Default value is `NO`.
*/
@property (nonatomic) BOOL enableTimeToFullDisplay;

@end

NS_ASSUME_NONNULL_END
10 changes: 10 additions & 0 deletions Sources/Sentry/Public/SentrySDK.h
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,16 @@ SENTRY_NO_INIT
*/
+ (void)crash;

/**
* Reports to the ongoing UIViewController transaction
* that the screen contents are fully loaded and displayed,
* which will create a new span.
*
* For more information see our documentation:
* https://docs.sentry.io/platforms/cocoa/performance/instrumentation/automatic-instrumentation/#time-to-full-display
*/
+ (void)reportFullyDisplayed;

/**
* Waits synchronously for the SDK to flush out all queued and cached items for up to the specified
* timeout in seconds. If there is no internet connection, the function returns immediately. The SDK
Expand Down
Loading

0 comments on commit cf724da

Please sign in to comment.