From a689d580491cb97168a4c5d20b3c1a3a2d32b30e Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Mon, 2 Dec 2024 16:25:02 +0100 Subject: [PATCH 01/29] wip --- CHANGELOG.md | 1 - Sources/Sentry/SentryTimeToDisplayTracker.m | 21 +-- ...SentryUIViewControllerPerformanceTracker.m | 10 +- .../include/SentryTimeToDisplayTracker.h | 9 +- ...SentryUIViewControllerPerformanceTracker.h | 3 + .../SentryInternal/SentryInternal.h | 49 ++++++- Sources/SentrySwiftUI/SentryTracedView.swift | 135 +++++++++++++----- 7 files changed, 174 insertions(+), 54 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42433a5dd9c..9e6b4a83bad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,6 @@ - Load integration from same binary (#4541) - Masking for fast animations #4574 - ### Improvements - impr: Speed up getBinaryImages V2 (#4539). Follow up on (#4435) diff --git a/Sources/Sentry/SentryTimeToDisplayTracker.m b/Sources/Sentry/SentryTimeToDisplayTracker.m index 242ad76db5e..8703d482949 100644 --- a/Sources/Sentry/SentryTimeToDisplayTracker.m +++ b/Sources/Sentry/SentryTimeToDisplayTracker.m @@ -36,15 +36,15 @@ @implementation SentryTimeToDisplayTracker { BOOL _waitForFullDisplay; BOOL _initialDisplayReported; BOOL _fullyDisplayedReported; - NSString *_controllerName; + NSString *_name; } -- (instancetype)initForController:(UIViewController *)controller - waitForFullDisplay:(BOOL)waitForFullDisplay - dispatchQueueWrapper:(SentryDispatchQueueWrapper *)dispatchQueueWrapper +- (instancetype)initWithName:(NSString *)name + waitForFullDisplay:(BOOL)waitForFullDisplay + dispatchQueueWrapper:(SentryDispatchQueueWrapper *)dispatchQueueWrapper { if (self = [super init]) { - _controllerName = [SwiftDescriptor getObjectClassName:controller]; + _name = name; _waitForFullDisplay = waitForFullDisplay; _dispatchQueueWrapper = dispatchQueueWrapper; _initialDisplayReported = NO; @@ -53,6 +53,11 @@ - (instancetype)initForController:(UIViewController *)controller return self; } +- (instancetype)initWithName:(NSString *)name + waitForFullDisplay:(BOOL)waitForFullDisplay { + return [self initWithName:name waitForFullDisplay:waitForFullDisplay dispatchQueueWrapper:SentryDependencyContainer.sharedInstance.dispatchQueueWrapper]; +} + - (BOOL)startForTracer:(SentryTracer *)tracer { if (SentryDependencyContainer.sharedInstance.framesTracker.isRunning == NO) { @@ -64,7 +69,7 @@ - (BOOL)startForTracer:(SentryTracer *)tracer SENTRY_LOG_DEBUG(@"Starting initial display span"); self.initialDisplaySpan = [tracer startChildWithOperation:SentrySpanOperationUILoadInitialDisplay - description:[NSString stringWithFormat:@"%@ initial display", _controllerName]]; + description:[NSString stringWithFormat:@"%@ initial display", _name]]; self.initialDisplaySpan.origin = SentryTraceOriginAutoUITimeToDisplay; if (self.waitForFullDisplay) { @@ -72,7 +77,7 @@ - (BOOL)startForTracer:(SentryTracer *)tracer self.fullDisplaySpan = [tracer startChildWithOperation:SentrySpanOperationUILoadFullDisplay description:[NSString stringWithFormat:@"%@ full display", - _controllerName]]; + _name]]; self.fullDisplaySpan.origin = SentryTraceOriginManualUITimeToDisplay; // By concept TTID and TTFD spans should have the same beginning, @@ -146,7 +151,7 @@ - (void)finishSpansIfNotFinished if (self.fullDisplaySpan.isFinished == NO) { SENTRY_LOG_WARN(@"You didn't call SentrySDK.reportFullyDisplayed() for UIViewController: " @"%@. Finishing full display span with status: %@.", - _controllerName, nameForSentrySpanStatus(kSentrySpanStatusDeadlineExceeded)); + _name, nameForSentrySpanStatus(kSentrySpanStatusDeadlineExceeded)); [self.fullDisplaySpan finishWithStatus:kSentrySpanStatusDeadlineExceeded]; } diff --git a/Sources/Sentry/SentryUIViewControllerPerformanceTracker.m b/Sources/Sentry/SentryUIViewControllerPerformanceTracker.m index 513f2a66538..a41d273c476 100644 --- a/Sources/Sentry/SentryUIViewControllerPerformanceTracker.m +++ b/Sources/Sentry/SentryUIViewControllerPerformanceTracker.m @@ -166,9 +166,9 @@ - (void)startRootSpanFor:(UIViewController *)controller [self.currentTTDTracker finishSpansIfNotFinished]; SentryTimeToDisplayTracker *ttdTracker = - [[SentryTimeToDisplayTracker alloc] initForController:controller - waitForFullDisplay:self.enableWaitForFullDisplay - dispatchQueueWrapper:_dispatchQueueWrapper]; + [[SentryTimeToDisplayTracker alloc] initWithName: [SwiftDescriptor getObjectClassName:controller] + waitForFullDisplay:self.enableWaitForFullDisplay + dispatchQueueWrapper:_dispatchQueueWrapper]; if ([ttdTracker startForTracer:(SentryTracer *)vcSpan]) { objc_setAssociatedObject(controller, &SENTRY_UI_PERFORMANCE_TRACKER_TTD_TRACKER, ttdTracker, @@ -184,6 +184,10 @@ - (void)reportFullyDisplayed [self.currentTTDTracker reportFullyDisplayed]; } +- (void)setTimeToDisplayTracker:(SentryTimeToDisplayTracker *)ttdTracker { + self.currentTTDTracker = ttdTracker; +} + - (void)viewControllerViewWillAppear:(UIViewController *)controller callbackToOrigin:(void (^)(void))callbackToOrigin { diff --git a/Sources/Sentry/include/SentryTimeToDisplayTracker.h b/Sources/Sentry/include/SentryTimeToDisplayTracker.h index 06a3a25a1ff..a5d39d90bb1 100644 --- a/Sources/Sentry/include/SentryTimeToDisplayTracker.h +++ b/Sources/Sentry/include/SentryTimeToDisplayTracker.h @@ -25,9 +25,12 @@ SENTRY_NO_INIT @property (nonatomic, readonly) BOOL waitForFullDisplay; -- (instancetype)initForController:(UIViewController *)controller - waitForFullDisplay:(BOOL)waitForFullDisplay - dispatchQueueWrapper:(SentryDispatchQueueWrapper *)dispatchQueueWrapper; +- (instancetype)initWithName:(NSString *)name + waitForFullDisplay:(BOOL)waitForFullDisplay + dispatchQueueWrapper:(SentryDispatchQueueWrapper *)dispatchQueueWrapper; + +- (instancetype)initWithName:(NSString *)name + waitForFullDisplay:(BOOL)waitForFullDisplay; - (BOOL)startForTracer:(SentryTracer *)tracer; diff --git a/Sources/Sentry/include/SentryUIViewControllerPerformanceTracker.h b/Sources/Sentry/include/SentryUIViewControllerPerformanceTracker.h index ed520b34f52..ef8f28b239a 100644 --- a/Sources/Sentry/include/SentryUIViewControllerPerformanceTracker.h +++ b/Sources/Sentry/include/SentryUIViewControllerPerformanceTracker.h @@ -4,6 +4,7 @@ @class SentrySpan; @class SentryInAppLogic; +@class SentryTimeToDisplayTracker; @class UIViewController; NS_ASSUME_NONNULL_BEGIN @@ -101,6 +102,8 @@ static NSString *const SENTRY_UI_PERFORMANCE_TRACKER_TTD_TRACKER - (void)reportFullyDisplayed; +- (void)setTimeToDisplayTracker:(SentryTimeToDisplayTracker *)ttdTracker; + @end NS_ASSUME_NONNULL_END diff --git a/Sources/SentrySwiftUI/SentryInternal/SentryInternal.h b/Sources/SentrySwiftUI/SentryInternal/SentryInternal.h index ce265ced396..2d4538d8070 100644 --- a/Sources/SentrySwiftUI/SentryInternal/SentryInternal.h +++ b/Sources/SentrySwiftUI/SentryInternal/SentryInternal.h @@ -7,12 +7,22 @@ #import +#if __has_include() +#import +#elif __has_include("Sentry.h") +#import "Sentry.h" +#endif + NS_ASSUME_NONNULL_BEGIN typedef NS_ENUM(NSInteger, SentryTransactionNameSource); @class SentrySpanId; -@protocol SentrySpan; +@class SentrySpan; + +@interface SentryTracer : NSObject +@end + typedef NS_ENUM(NSUInteger, SentrySpanStatus); @@ -56,4 +66,41 @@ typedef NS_ENUM(NSUInteger, SentrySpanStatus); @end +@interface SentryTimeToDisplayTracker : NSObject +-(instancetype)init NS_UNAVAILABLE; ++(instancetype) new NS_UNAVAILABLE; + +@property (nullable, nonatomic, weak, readonly) SentrySpan *initialDisplaySpan; + +@property (nullable, nonatomic, weak, readonly) SentrySpan *fullDisplaySpan; + +@property (nonatomic, readonly) BOOL waitForFullDisplay; + +- (instancetype)initWithName:(NSString *)name + waitForFullDisplay:(BOOL)waitForFullDisplay + dispatchQueueWrapper:(SentryDispatchQueueWrapper *)dispatchQueueWrapper; + +- (instancetype)initWithName:(NSString *)name + waitForFullDisplay:(BOOL)waitForFullDisplay; + +- (BOOL)startForTracer:(SentryTracer *)tracer; + +- (void)reportInitialDisplay; + +- (void)reportFullyDisplayed; + +- (void)finishSpansIfNotFinished; + +@end + +@interface SentryUIViewControllerPerformanceTracker : NSObject + +@property (nonatomic, readonly, class) SentryUIViewControllerPerformanceTracker *shared; + +- (void)reportFullyDisplayed; + +- (void)setTimeToDisplayTracker:(SentryTimeToDisplayTracker *)ttdTracker; + +@end + NS_ASSUME_NONNULL_END diff --git a/Sources/SentrySwiftUI/SentryTracedView.swift b/Sources/SentrySwiftUI/SentryTracedView.swift index 97e94ea9367..b74edd3e720 100644 --- a/Sources/SentrySwiftUI/SentryTracedView.swift +++ b/Sources/SentrySwiftUI/SentryTracedView.swift @@ -1,15 +1,51 @@ #if canImport(SwiftUI) + import Foundation -#if SENTRY_NO_UIKIT -import SentryWithoutUIKit -#else -import Sentry -#endif #if CARTHAGE || SWIFT_PACKAGE @_implementationOnly import SentryInternal +import Sentry #endif import SwiftUI +@available(iOS 13, macOS 10.15, tvOS 13, *) +struct SentryTrackView: UIViewRepresentable { + + let name : String + let waitForFullDisplay : Bool + + class SentryView: UIView { + + let name : String + let waitForFullDisplay : Bool + + init(name: String, waitForFullDisplay: Bool) { + self.name = name + self.waitForFullDisplay = waitForFullDisplay + super.init(frame: CGRect(origin: .zero, size: CGSize(width: 1, height: 1))) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func didMoveToWindow() { + super.didMoveToWindow() + + let ttdTracker = SentryTimeToDisplayTracker(name: "", waitForFullDisplay: true) + print(ttdTracker); + } + } + + func makeUIView(context: Context) -> UIView { + let view = SentryView(name: name, waitForFullDisplay: waitForFullDisplay) + view.isUserInteractionEnabled = false + return view + } + + func updateUIView(_ uiView: UIView, context: Context) { + } +} + /// A control to measure the performance of your views and send the result as a transaction to Sentry.io. /// /// You create a transaction by wrapping your views with this. @@ -39,61 +75,84 @@ import SwiftUI @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6.0, *) public struct SentryTracedView: View { @State var viewAppeared = false - + let content: () -> Content let name: String let nameSource: SentryTransactionNameSource let traceOrigin = "auto.ui.swift_ui" - + public init(_ viewName: String? = nil, content: @escaping () -> Content) { self.content = content self.name = viewName ?? SentryTracedView.extractName(content: Content.self) self.nameSource = viewName == nil ? .component : .custom } - + private static func extractName(content: Any) -> String { var result = String(describing: content) - + if let index = result.firstIndex(of: "<") { result = String(result[result.startIndex ..< index]) } - + return result } - + public var body: some View { - if viewAppeared { - return self.content().onAppear() + let content = !viewAppeared ? content() : tracedContent() + return content.onAppear { + viewAppeared = true } - - var transactionCreated = false - if SentryPerformanceTracker.shared.activeSpanId() == nil { - transactionCreated = true - let transactionId = SentryPerformanceTracker.shared.startSpan(withName: self.name, nameSource: nameSource, operation: "ui.load", origin: self.traceOrigin) - SentryPerformanceTracker.shared.pushActiveSpan(transactionId) - - //According to Apple's documentation, the call to `body` needs to be fast - //and can be made many times in one frame. Therefore they don't use async code to process the view. - //Scheduling to finish the transaction at the end of the main loop seems the least hack solution right now. - //'onAppear' is not a suitable place to do this because it may happen before other view `body` property get called. - DispatchQueue.main.async { - SentryPerformanceTracker.shared.popActiveSpan() - SentryPerformanceTracker.shared.finishSpan(transactionId) - } - } - - let id = SentryPerformanceTracker.shared.startSpan(withName: transactionCreated ? "\(self.name) - body" : self.name, nameSource: nameSource, operation: "ui.load", origin: self.traceOrigin) - - SentryPerformanceTracker.shared.pushActiveSpan(id) + } + + private func tracedContent() -> Content { + let transactionCreated = ensureTransactionExists() + + let spanId = createAndPushBodySpan(transactionCreated: transactionCreated) defer { - SentryPerformanceTracker.shared.popActiveSpan() - SentryPerformanceTracker.shared.finishSpan(id) + finishSpan(spanId) } - - return self.content().onAppear { - self.viewAppeared = true + + return content() + } + + private func ensureTransactionExists() -> Bool { + guard SentryPerformanceTracker.shared.activeSpanId() == nil else { return false } + + let transactionId = SentryPerformanceTracker.shared.startSpan( + withName: name, + nameSource: nameSource, + operation: "ui.load", + origin: traceOrigin + ) + SentryPerformanceTracker.shared.pushActiveSpan(transactionId) + + //According to Apple's documentation, the call to body needs to be fast + //and can be made many times in one frame. Therefore they don't use async code to process the view. + //Scheduling to finish the transaction at the end of the main loop seems the least hack solution right now. + //'onAppear' is not a suitable place to do this because it may happen before other view body property get called. + DispatchQueue.main.async { + self.finishSpan(transactionId) } + + return true + } + + private func createAndPushBodySpan(transactionCreated: Bool) -> SpanId { + let spanName = transactionCreated ? "\(name) - body" : name + let spanId = SentryPerformanceTracker.shared.startSpan( + withName: spanName, + nameSource: nameSource, + operation: "ui.load", + origin: traceOrigin + ) + SentryPerformanceTracker.shared.pushActiveSpan(spanId) + return spanId + } + + private func finishSpan(_ spanId: SpanId) { + SentryPerformanceTracker.shared.popActiveSpan() + SentryPerformanceTracker.shared.finishSpan(spanId) } } From fd905daff59254c873fb7c1e3f46cffa65b176e4 Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Wed, 4 Dec 2024 12:42:34 +0100 Subject: [PATCH 02/29] more changes --- .../SentryRedactModifierTests.swift | 0 .../iOS-SwiftUI.xcodeproj/project.pbxproj | 19 +++++ .../iOS-SwiftUI/iOS-SwiftUI/ContentView.swift | 12 ++- Sentry.xcodeproj/project.pbxproj | 33 +------- Sources/Sentry/SentryHub.m | 6 +- .../SentryPerformanceTrackingIntegration.m | 2 +- Sources/Sentry/SentryProfiler.mm | 2 +- ...SentryUIViewControllerPerformanceTracker.m | 15 +++- ...SentryUIViewControllerPerformanceTracker.h | 2 +- .../SentryInternal/SentryInternal.h | 5 ++ Sources/SentrySwiftUI/SentryTracedView.swift | 80 +++++++------------ Sources/SentrySwiftUI/TracingView.swift | 59 ++++++++++++++ .../SentryAppLaunchProfilingTests.swift | 8 +- .../SentryTimeToDisplayTrackerTest.swift | 4 +- Tests/SentryTests/SentryHubTests.swift | 11 +-- 15 files changed, 151 insertions(+), 107 deletions(-) rename {Tests/SentryTests/SwiftUI => Samples/iOS-SwiftUI/iOS-SwiftUI-UITests}/SentryRedactModifierTests.swift (100%) create mode 100644 Sources/SentrySwiftUI/TracingView.swift diff --git a/Tests/SentryTests/SwiftUI/SentryRedactModifierTests.swift b/Samples/iOS-SwiftUI/iOS-SwiftUI-UITests/SentryRedactModifierTests.swift similarity index 100% rename from Tests/SentryTests/SwiftUI/SentryRedactModifierTests.swift rename to Samples/iOS-SwiftUI/iOS-SwiftUI-UITests/SentryRedactModifierTests.swift diff --git a/Samples/iOS-SwiftUI/iOS-SwiftUI.xcodeproj/project.pbxproj b/Samples/iOS-SwiftUI/iOS-SwiftUI.xcodeproj/project.pbxproj index 1d1ef792ba6..7149192537c 100644 --- a/Samples/iOS-SwiftUI/iOS-SwiftUI.xcodeproj/project.pbxproj +++ b/Samples/iOS-SwiftUI/iOS-SwiftUI.xcodeproj/project.pbxproj @@ -21,6 +21,9 @@ D8199DCE29376FD90074249E /* SentrySwiftUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D8BBD38B2901AE400011F850 /* SentrySwiftUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; D832FAF02982A908007A9A5F /* FormScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = D832FAEF2982A908007A9A5F /* FormScreen.swift */; }; D85388D12980222500B63908 /* UIKitScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85388D02980222500B63908 /* UIKitScreen.swift */; }; + D8F0F3C02D0068A100826CE3 /* SentrySwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D8BBD38B2901AE400011F850 /* SentrySwiftUI.framework */; }; + D8F0F3C12D0068A100826CE3 /* SentrySwiftUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D8BBD38B2901AE400011F850 /* SentrySwiftUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + D8F0F3C42D0068AB00826CE3 /* SentryRedactModifierTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F0F3C32D0068AB00826CE3 /* SentryRedactModifierTests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -130,6 +133,17 @@ name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; + D8F0F3C22D0068A100826CE3 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + D8F0F3C12D0068A100826CE3 /* SentrySwiftUI.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ @@ -159,6 +173,7 @@ D8A22A7C2915231F006907D9 /* SentrySpanStatus.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SentrySpanStatus.h; sourceTree = ""; }; D8A22A7D2915238A006907D9 /* SentryTracer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryTracer.h; path = ../../../../Sources/Sentry/include/SentryTracer.h; sourceTree = ""; }; D8A22A7E2915238A006907D9 /* SentryPerformanceTracker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryPerformanceTracker.h; path = ../../../../Sources/Sentry/include/SentryPerformanceTracker.h; sourceTree = ""; }; + D8F0F3C32D0068AB00826CE3 /* SentryRedactModifierTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryRedactModifierTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -166,6 +181,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + D8F0F3C02D0068A100826CE3 /* SentrySwiftUI.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -198,6 +214,7 @@ isa = PBXGroup; children = ( 7B64385926A6C0A6000D0F65 /* LaunchUITests.swift */, + D8F0F3C32D0068AB00826CE3 /* SentryRedactModifierTests.swift */, 7B64385B26A6C0A6000D0F65 /* Info.plist */, ); path = "iOS-SwiftUI-UITests"; @@ -306,6 +323,7 @@ 7B64385326A6C0A6000D0F65 /* Sources */, 7B64385426A6C0A6000D0F65 /* Frameworks */, 7B64385526A6C0A6000D0F65 /* Resources */, + D8F0F3C22D0068A100826CE3 /* Embed Frameworks */, ); buildRules = ( ); @@ -535,6 +553,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + D8F0F3C42D0068AB00826CE3 /* SentryRedactModifierTests.swift in Sources */, 7B64385A26A6C0A6000D0F65 /* LaunchUITests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Samples/iOS-SwiftUI/iOS-SwiftUI/ContentView.swift b/Samples/iOS-SwiftUI/iOS-SwiftUI/ContentView.swift index be7b0b0f7ff..07608490656 100644 --- a/Samples/iOS-SwiftUI/iOS-SwiftUI/ContentView.swift +++ b/Samples/iOS-SwiftUI/iOS-SwiftUI/ContentView.swift @@ -115,12 +115,12 @@ struct ContentView: View { } var body: some View { - return SentryTracedView("Content View Body") { - NavigationView { + return SentryTracedView("Content View Body", waitForFullDisplay: true) { NavigationView { VStack(alignment: HorizontalAlignment.center, spacing: 16) { Group { Text(getCurrentTracer()?.transactionContext.name ?? "NO SPAN") .accessibilityIdentifier("TRANSACTION_NAME") + Text(getCurrentTracer()?.transactionContext.spanId.sentrySpanIdString ?? "NO ID") .accessibilityIdentifier("TRANSACTION_ID") .sentryReplayMask() @@ -128,6 +128,14 @@ struct ContentView: View { Text(getCurrentTracer()?.transactionContext.origin ?? "NO ORIGIN") .accessibilityIdentifier("TRACE_ORIGIN") }.sentryReplayUnmask() + .onAppear { + Task { + if #available(iOS 16.0, *) { + try? await Task.sleep(for: .seconds(2)) + } + SentrySDK.reportFullyDisplayed() + } + } SentryTracedView("Child Span") { VStack { Text(getCurrentSpan()?.spanDescription ?? "NO SPAN") diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index a0441c02971..c44f502fc34 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -830,9 +830,6 @@ D84F833E2A1CC401005828E0 /* SentrySwiftAsyncIntegration.m in Sources */ = {isa = PBXBuildFile; fileRef = D84F833C2A1CC401005828E0 /* SentrySwiftAsyncIntegration.m */; }; D851527F2C9971020070F669 /* SentryStringUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = D851527E2C9971020070F669 /* SentryStringUtils.h */; }; D85152832C997A280070F669 /* SentryStringUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85152822C997A1F0070F669 /* SentryStringUtils.swift */; }; - D85153002CA2B5F60070F669 /* SentrySwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D8199DAA29376E9B0074249E /* SentrySwiftUI.framework */; }; - D85153012CA2B5F60070F669 /* SentrySwiftUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D8199DAA29376E9B0074249E /* SentrySwiftUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - D851530C2CA2B7B00070F669 /* SentryRedactModifierTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D851530B2CA2B7A30070F669 /* SentryRedactModifierTests.swift */; }; D85596F3280580F10041FF8B /* SentryScreenshotIntegration.m in Sources */ = {isa = PBXBuildFile; fileRef = D85596F1280580F10041FF8B /* SentryScreenshotIntegration.m */; }; D855AD62286ED6A4002573E1 /* SentryCrashTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D855AD61286ED6A4002573E1 /* SentryCrashTests.m */; }; D855B3E827D652AF00BCED76 /* SentryCoreDataTrackingIntegrationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D855B3E727D652AF00BCED76 /* SentryCoreDataTrackingIntegrationTest.swift */; }; @@ -928,6 +925,7 @@ D8DBE0D22C0EFFC300FAB1FD /* SentryReplayOptionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8DBE0D12C0EFFC300FAB1FD /* SentryReplayOptionsTests.swift */; }; D8F016B32B9622D6007B9AFB /* SentryId.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F016B22B9622D6007B9AFB /* SentryId.swift */; }; D8F016B62B962548007B9AFB /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F016B52B962548007B9AFB /* StringExtensions.swift */; }; + D8F0F3B72D005B0400826CE3 /* TracingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F0F3B62D005AFB00826CE3 /* TracingView.swift */; }; D8F67AEE2BE0D19200C9197B /* UIImageHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F67AED2BE0D19200C9197B /* UIImageHelper.swift */; }; D8F67AF12BE0D33F00C9197B /* UIImageHelperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F67AEF2BE0D31A00C9197B /* UIImageHelperTests.swift */; }; D8F67AF42BE10F9600C9197B /* UIRedactBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F67AF22BE10F7600C9197B /* UIRedactBuilderTests.swift */; }; @@ -977,13 +975,6 @@ remoteGlobalIDString = D84DAD4C2B17428D003CF120; remoteInfo = SentryTestUtilsDynamic; }; - D85153022CA2B5F60070F669 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 6327C5CA1EB8A783004E799B /* Project object */; - proxyType = 1; - remoteGlobalIDString = D8199DA929376E9B0074249E; - remoteInfo = SentrySwiftUI; - }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -994,7 +985,6 @@ dstSubfolderSpec = 10; files = ( D84DAD5A2B1742C1003CF120 /* SentryTestUtilsDynamic.framework in Embed Frameworks */, - D85153012CA2B5F60070F669 /* SentrySwiftUI.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -1908,7 +1898,6 @@ D8511F722BAC8F750015E6FD /* Sentry.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = Sentry.modulemap; sourceTree = ""; }; D851527E2C9971020070F669 /* SentryStringUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SentryStringUtils.h; sourceTree = ""; }; D85152822C997A1F0070F669 /* SentryStringUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryStringUtils.swift; sourceTree = ""; }; - D851530B2CA2B7A30070F669 /* SentryRedactModifierTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryRedactModifierTests.swift; sourceTree = ""; }; D85596F1280580F10041FF8B /* SentryScreenshotIntegration.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryScreenshotIntegration.m; sourceTree = ""; }; D855AD61286ED6A4002573E1 /* SentryCrashTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryCrashTests.m; sourceTree = ""; }; D855B3E727D652AF00BCED76 /* SentryCoreDataTrackingIntegrationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryCoreDataTrackingIntegrationTest.swift; sourceTree = ""; }; @@ -2018,6 +2007,7 @@ D8F016B52B962548007B9AFB /* StringExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtensions.swift; sourceTree = ""; }; D8F01DE42A126B62008F4996 /* HybridPod.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; path = HybridPod.podspec; sourceTree = ""; }; D8F01DE52A126BF5008F4996 /* HybridTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HybridTest.swift; sourceTree = ""; }; + D8F0F3B62D005AFB00826CE3 /* TracingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TracingView.swift; sourceTree = ""; }; D8F67AED2BE0D19200C9197B /* UIImageHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImageHelper.swift; sourceTree = ""; }; D8F67AEF2BE0D31A00C9197B /* UIImageHelperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImageHelperTests.swift; sourceTree = ""; }; D8F67AF22BE10F7600C9197B /* UIRedactBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIRedactBuilderTests.swift; sourceTree = ""; }; @@ -2049,7 +2039,6 @@ 8431F01C29B2854200D8DC56 /* libSentryTestUtils.a in Frameworks */, D84DAD592B1742C1003CF120 /* SentryTestUtilsDynamic.framework in Frameworks */, 63AA766A1EB8CB2F00D153DE /* Sentry.framework in Frameworks */, - D85153002CA2B5F60070F669 /* SentrySwiftUI.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2545,7 +2534,6 @@ 7BF536D224BEF240004FA6A2 /* TestUtils */, D81FDF0F280E9FEC0045E0E4 /* Tools */, 7B6C5ED4264E62B60010D138 /* Transaction */, - D851530A2CA2B7960070F669 /* SwiftUI */, ); path = SentryTests; sourceTree = ""; @@ -3689,6 +3677,7 @@ D8199DB529376ECC0074249E /* SentrySwiftUI.h */, D88D25E92B8E0BAC0073C3D5 /* module.modulemap */, D8199DB629376ECC0074249E /* SentryTracedView.swift */, + D8F0F3B62D005AFB00826CE3 /* TracingView.swift */, D8A65B5C2C98656000974B74 /* SentryReplayView.swift */, ); path = SentrySwiftUI; @@ -3733,14 +3722,6 @@ path = SentryTestUtilsDynamic; sourceTree = ""; }; - D851530A2CA2B7960070F669 /* SwiftUI */ = { - isa = PBXGroup; - children = ( - D851530B2CA2B7A30070F669 /* SentryRedactModifierTests.swift */, - ); - path = SwiftUI; - sourceTree = ""; - }; D85596EF280580BE0041FF8B /* Screenshot */ = { isa = PBXGroup; children = ( @@ -4344,7 +4325,6 @@ dependencies = ( 63AA766C1EB8CB2F00D153DE /* PBXTargetDependency */, D84DAD5C2B1742C1003CF120 /* PBXTargetDependency */, - D85153032CA2B5F60070F669 /* PBXTargetDependency */, ); name = SentryTests; packageProductDependencies = ( @@ -5044,7 +5024,6 @@ D8AE48C12C57B1550092A2A6 /* SentryLevelTests.swift in Sources */, 63FE721820DA66EC00CDBAE8 /* TestThread.m in Sources */, 7B4D308A26FC616B00C94DE9 /* SentryHttpTransportTests.swift in Sources */, - D851530C2CA2B7B00070F669 /* SentryRedactModifierTests.swift in Sources */, 7B4E23B6251A07BD00060D68 /* SentryDispatchQueueWrapperTests.swift in Sources */, 63FE720720DA66EC00CDBAE8 /* SentryCrashReportFilter_Tests.m in Sources */, 8F73BC312B02B87E00C3CEF4 /* SentryInstallationTests.swift in Sources */, @@ -5171,6 +5150,7 @@ files = ( D8199DC129376EEC0074249E /* SentryTracedView.swift in Sources */, D8199DBF29376EE20074249E /* SentryInternal.m in Sources */, + D8F0F3B72D005B0400826CE3 /* TracingView.swift in Sources */, D8A65B5D2C98656800974B74 /* SentryReplayView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -5211,11 +5191,6 @@ target = D84DAD4C2B17428D003CF120 /* SentryTestUtilsDynamic */; targetProxy = D84DAD5B2B1742C1003CF120 /* PBXContainerItemProxy */; }; - D85153032CA2B5F60070F669 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = D8199DA929376E9B0074249E /* SentrySwiftUI */; - targetProxy = D85153022CA2B5F60070F669 /* PBXContainerItemProxy */; - }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ diff --git a/Sources/Sentry/SentryHub.m b/Sources/Sentry/SentryHub.m index a83f66c71c0..0a2ac01c790 100644 --- a/Sources/Sentry/SentryHub.m +++ b/Sources/Sentry/SentryHub.m @@ -742,11 +742,7 @@ - (BOOL)eventContainsOnlyHandledErrors:(NSDictionary *)eventDictionary - (void)reportFullyDisplayed { #if SENTRY_HAS_UIKIT - if (_client.options.enableTimeToFullDisplayTracing) { - [SentryUIViewControllerPerformanceTracker.shared reportFullyDisplayed]; - } else { - SENTRY_LOG_DEBUG(@"The options `enableTimeToFullDisplay` is disabled."); - } + [SentryUIViewControllerPerformanceTracker.shared reportFullyDisplayed]; #endif // SENTRY_HAS_UIKIT } diff --git a/Sources/Sentry/SentryPerformanceTrackingIntegration.m b/Sources/Sentry/SentryPerformanceTrackingIntegration.m index 8f2eb8a2098..a8bd8868fde 100644 --- a/Sources/Sentry/SentryPerformanceTrackingIntegration.m +++ b/Sources/Sentry/SentryPerformanceTrackingIntegration.m @@ -46,7 +46,7 @@ - (BOOL)installWithOptions:(SentryOptions *)options binaryImageCache:[SentryDependencyContainer.sharedInstance binaryImageCache]]; [self.swizzling start]; - SentryUIViewControllerPerformanceTracker.shared.enableWaitForFullDisplay + SentryUIViewControllerPerformanceTracker.shared.alwaysWaitForFullDisplay = options.enableTimeToFullDisplayTracing; return YES; diff --git a/Sources/Sentry/SentryProfiler.mm b/Sources/Sentry/SentryProfiler.mm index 77c4e52c85a..177e0a2be3f 100644 --- a/Sources/Sentry/SentryProfiler.mm +++ b/Sources/Sentry/SentryProfiler.mm @@ -42,7 +42,7 @@ [SentryDependencyContainer.sharedInstance.dispatchQueueWrapper dispatchAsyncWithBlock:^{ BOOL shouldStopAndTransmitLaunchProfile = options.profilesSampleRate != nil; # if SENTRY_HAS_UIKIT - if (SentryUIViewControllerPerformanceTracker.shared.enableWaitForFullDisplay) { + if (SentryUIViewControllerPerformanceTracker.shared.alwaysWaitForFullDisplay) { shouldStopAndTransmitLaunchProfile = NO; } # endif // SENTRY_HAS_UIKIT diff --git a/Sources/Sentry/SentryUIViewControllerPerformanceTracker.m b/Sources/Sentry/SentryUIViewControllerPerformanceTracker.m index a41d273c476..12414f435cc 100644 --- a/Sources/Sentry/SentryUIViewControllerPerformanceTracker.m +++ b/Sources/Sentry/SentryUIViewControllerPerformanceTracker.m @@ -45,7 +45,7 @@ - (instancetype)init self.inAppLogic = [[SentryInAppLogic alloc] initWithInAppIncludes:options.inAppIncludes inAppExcludes:options.inAppExcludes]; - _enableWaitForFullDisplay = NO; + _alwaysWaitForFullDisplay = NO; _dispatchQueueWrapper = SentryDependencyContainer.sharedInstance.dispatchQueueWrapper; } return self; @@ -167,7 +167,7 @@ - (void)startRootSpanFor:(UIViewController *)controller SentryTimeToDisplayTracker *ttdTracker = [[SentryTimeToDisplayTracker alloc] initWithName: [SwiftDescriptor getObjectClassName:controller] - waitForFullDisplay:self.enableWaitForFullDisplay + waitForFullDisplay:self.alwaysWaitForFullDisplay dispatchQueueWrapper:_dispatchQueueWrapper]; if ([ttdTracker startForTracer:(SentryTracer *)vcSpan]) { @@ -181,7 +181,16 @@ - (void)startRootSpanFor:(UIViewController *)controller - (void)reportFullyDisplayed { - [self.currentTTDTracker reportFullyDisplayed]; + SentryTimeToDisplayTracker* tracker = self.currentTTDTracker; + if (tracker) { + if (tracker.waitForFullDisplay) { + [self.currentTTDTracker reportFullyDisplayed]; + } else { + SENTRY_LOG_DEBUG(@"Transaction is not waiting for full display report. You can enable `enableTimeToFullDisplay` option, or use the waitForFullDisplay property in our `SentryTracedView` view for SwiftUI."); + } + } else { + SENTRY_LOG_DEBUG(@"No screen transaction being tracked right now.") + } } - (void)setTimeToDisplayTracker:(SentryTimeToDisplayTracker *)ttdTracker { diff --git a/Sources/Sentry/include/SentryUIViewControllerPerformanceTracker.h b/Sources/Sentry/include/SentryUIViewControllerPerformanceTracker.h index ef8f28b239a..0355832393f 100644 --- a/Sources/Sentry/include/SentryUIViewControllerPerformanceTracker.h +++ b/Sources/Sentry/include/SentryUIViewControllerPerformanceTracker.h @@ -31,7 +31,7 @@ static NSString *const SENTRY_UI_PERFORMANCE_TRACKER_TTD_TRACKER @property (nonatomic, strong) SentryInAppLogic *inAppLogic; -@property (nonatomic) BOOL enableWaitForFullDisplay; +@property (nonatomic) BOOL alwaysWaitForFullDisplay; /** * Measures @c controller's @c loadView method. diff --git a/Sources/SentrySwiftUI/SentryInternal/SentryInternal.h b/Sources/SentrySwiftUI/SentryInternal/SentryInternal.h index 2d4538d8070..ae6a5279bdc 100644 --- a/Sources/SentrySwiftUI/SentryInternal/SentryInternal.h +++ b/Sources/SentrySwiftUI/SentryInternal/SentryInternal.h @@ -103,4 +103,9 @@ typedef NS_ENUM(NSUInteger, SentrySpanStatus); @end +@interface SentrySDK () +@property (nonatomic, nullable, readonly, class) SentryOptions *options; +@end + + NS_ASSUME_NONNULL_END diff --git a/Sources/SentrySwiftUI/SentryTracedView.swift b/Sources/SentrySwiftUI/SentryTracedView.swift index b74edd3e720..9e13ad02e17 100644 --- a/Sources/SentrySwiftUI/SentryTracedView.swift +++ b/Sources/SentrySwiftUI/SentryTracedView.swift @@ -1,50 +1,13 @@ #if canImport(SwiftUI) import Foundation +import SwiftUI + #if CARTHAGE || SWIFT_PACKAGE @_implementationOnly import SentryInternal import Sentry #endif -import SwiftUI -@available(iOS 13, macOS 10.15, tvOS 13, *) -struct SentryTrackView: UIViewRepresentable { - - let name : String - let waitForFullDisplay : Bool - - class SentryView: UIView { - - let name : String - let waitForFullDisplay : Bool - - init(name: String, waitForFullDisplay: Bool) { - self.name = name - self.waitForFullDisplay = waitForFullDisplay - super.init(frame: CGRect(origin: .zero, size: CGSize(width: 1, height: 1))) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func didMoveToWindow() { - super.didMoveToWindow() - - let ttdTracker = SentryTimeToDisplayTracker(name: "", waitForFullDisplay: true) - print(ttdTracker); - } - } - - func makeUIView(context: Context) -> UIView { - let view = SentryView(name: name, waitForFullDisplay: waitForFullDisplay) - view.isUserInteractionEnabled = false - return view - } - - func updateUIView(_ uiView: UIView, context: Context) { - } -} /// A control to measure the performance of your views and send the result as a transaction to Sentry.io. /// @@ -79,13 +42,21 @@ public struct SentryTracedView: View { let content: () -> Content let name: String let nameSource: SentryTransactionNameSource + let waitforFullDisplay: Bool let traceOrigin = "auto.ui.swift_ui" - public init(_ viewName: String? = nil, content: @escaping () -> Content) { + /// Creates a view that measures the performance of its `content`. + /// + /// - Parameter viewName: The name that will be used for the span, if nil we try to get the name of the content class. + /// - Parameter waitForFullDisplay: Indicates whether this view transaction should wait for `SentrySDK.reportFullyDisplayed()` + /// in case you need to track some asyncronous task. This is ignored for any `SentryTracedView` that is child of another `SentryTracedView` + /// - Parameter content: The content that you want to track the performance + public init(_ viewName: String? = nil, waitForFullDisplay: Bool? = nil, @ViewBuilder content: @escaping () -> Content) { self.content = content self.name = viewName ?? SentryTracedView.extractName(content: Content.self) self.nameSource = viewName == nil ? .component : .custom + self.waitforFullDisplay = waitForFullDisplay ?? SentrySDK.options?.enableTimeToFullDisplayTracing ?? false } private static func extractName(content: Any) -> String { @@ -99,25 +70,30 @@ public struct SentryTracedView: View { } public var body: some View { - let content = !viewAppeared ? content() : tracedContent() - return content.onAppear { - viewAppeared = true - } - } + var trace: SentryTracer? + var spanId: SpanId? - private func tracedContent() -> Content { - let transactionCreated = ensureTransactionExists() + if !viewAppeared { + trace = ensureTransactionExists() + spanId = createAndPushBodySpan(transactionCreated: trace != nil) + } - let spanId = createAndPushBodySpan(transactionCreated: transactionCreated) defer { - finishSpan(spanId) + if let spanId = spanId { + finishSpan(spanId) + } } + // We need to add a UIView to the view hierarchy to be able to + // monitor ui life cycles. We will use the background modifier + // to add this tracking view behind the content. return content() + .background(TracingView(name: self.name, waitForFullDisplay: self.waitforFullDisplay, tracer: trace)) + .onAppear { viewAppeared = true } } - private func ensureTransactionExists() -> Bool { - guard SentryPerformanceTracker.shared.activeSpanId() == nil else { return false } + private func ensureTransactionExists() -> SentryTracer? { + guard SentryPerformanceTracker.shared.activeSpanId() == nil else { return nil } let transactionId = SentryPerformanceTracker.shared.startSpan( withName: name, @@ -135,7 +111,7 @@ public struct SentryTracedView: View { self.finishSpan(transactionId) } - return true + return SentryPerformanceTracker.shared.getSpan(transactionId) as? SentryTracer } private func createAndPushBodySpan(transactionCreated: Bool) -> SpanId { diff --git a/Sources/SentrySwiftUI/TracingView.swift b/Sources/SentrySwiftUI/TracingView.swift new file mode 100644 index 00000000000..0f7cd1cec59 --- /dev/null +++ b/Sources/SentrySwiftUI/TracingView.swift @@ -0,0 +1,59 @@ +#if canImport(SwiftUI) + +import Foundation +import SwiftUI + +#if CARTHAGE || SWIFT_PACKAGE +@_implementationOnly import SentryInternal +#endif + + +@available(iOS 13, macOS 10.15, tvOS 13, *) +struct TracingView: UIViewRepresentable { + + let name: String + let waitForFullDisplay: Bool + let tracer: SentryTracer? + + class SentryTracingView: UIView { + + let tracer: SentryTracer? + let tracker: SentryTimeToDisplayTracker? + + init(name: String, waitForFullDisplay: Bool, tracer: SentryTracer?) { + self.tracer = tracer + if let tracer = self.tracer { + let tracker = SentryTimeToDisplayTracker(name: name, waitForFullDisplay: waitForFullDisplay) + self.tracker = tracker + SentryUIViewControllerPerformanceTracker.shared.setTimeToDisplay(tracker) + tracker.start(for: tracer) + } else { + tracker = nil + } + + super.init(frame: CGRect(origin: .zero, size: CGSize(width: 1, height: 1))) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func didMoveToWindow() { + super.didMoveToWindow() + // Reports initial display when this view is added to the view hierarchy + // This is the equivalent of viewDidAppear of a UIViewController + tracker?.reportInitialDisplay() + } + } + + func makeUIView(context: Context) -> UIView { + let view = SentryTracingView(name: name, waitForFullDisplay: waitForFullDisplay, tracer: tracer) + view.isUserInteractionEnabled = false + return view + } + + func updateUIView(_ uiView: UIView, context: Context) { + //Intentionally left blank. Nothing to update here. + } +} +#endif diff --git a/Tests/SentryProfilerTests/SentryAppLaunchProfilingTests.swift b/Tests/SentryProfilerTests/SentryAppLaunchProfilingTests.swift index 8e165de7cd9..f3ff29ec503 100644 --- a/Tests/SentryProfilerTests/SentryAppLaunchProfilingTests.swift +++ b/Tests/SentryProfilerTests/SentryAppLaunchProfilingTests.swift @@ -36,7 +36,7 @@ final class SentryAppLaunchProfilingSwiftTests: XCTestCase { let appStartMeasurement = fixture.getAppStartMeasurement(type: .cold) SentrySDK.setAppStartMeasurement(appStartMeasurement) let tracer = try fixture.newTransaction(testingAppLaunchSpans: true, automaticTransaction: true) - let ttd = SentryTimeToDisplayTracker(for: UIViewController(nibName: nil, bundle: nil), waitForFullDisplay: true, dispatchQueueWrapper: fixture.dispatchQueueWrapper) + let ttd = SentryTimeToDisplayTracker(name: "UIViewController", waitForFullDisplay: true, dispatchQueueWrapper: fixture.dispatchQueueWrapper) ttd.start(for: tracer) ttd.reportInitialDisplay() ttd.reportFullyDisplayed() @@ -55,7 +55,7 @@ final class SentryAppLaunchProfilingSwiftTests: XCTestCase { XCTAssert(try XCTUnwrap(SentryTraceProfiler.getCurrentProfiler()).isRunning()) SentrySDK.setStart(fixture.options) - let ttd = SentryTimeToDisplayTracker(for: UIViewController(nibName: nil, bundle: nil), waitForFullDisplay: true, dispatchQueueWrapper: fixture.dispatchQueueWrapper) + let ttd = SentryTimeToDisplayTracker(name: "UIViewController", waitForFullDisplay: true, dispatchQueueWrapper: fixture.dispatchQueueWrapper) ttd.start(for: try XCTUnwrap(sentry_launchTracer)) ttd.reportInitialDisplay() ttd.reportFullyDisplayed() @@ -76,7 +76,7 @@ final class SentryAppLaunchProfilingSwiftTests: XCTestCase { let appStartMeasurement = fixture.getAppStartMeasurement(type: .cold) SentrySDK.setAppStartMeasurement(appStartMeasurement) let tracer = try fixture.newTransaction(testingAppLaunchSpans: true, automaticTransaction: true) - let ttd = SentryTimeToDisplayTracker(for: UIViewController(nibName: nil, bundle: nil), waitForFullDisplay: false, dispatchQueueWrapper: fixture.dispatchQueueWrapper) + let ttd = SentryTimeToDisplayTracker(name: "UIViewController", waitForFullDisplay: false, dispatchQueueWrapper: fixture.dispatchQueueWrapper) ttd.start(for: tracer) ttd.reportInitialDisplay() fixture.displayLinkWrapper.call() @@ -94,7 +94,7 @@ final class SentryAppLaunchProfilingSwiftTests: XCTestCase { XCTAssert(try XCTUnwrap(SentryTraceProfiler.getCurrentProfiler()).isRunning()) SentrySDK.setStart(fixture.options) - let ttd = SentryTimeToDisplayTracker(for: UIViewController(nibName: nil, bundle: nil), waitForFullDisplay: false, dispatchQueueWrapper: fixture.dispatchQueueWrapper) + let ttd = SentryTimeToDisplayTracker(name: "UIViewController", waitForFullDisplay: false, dispatchQueueWrapper: fixture.dispatchQueueWrapper) ttd.start(for: try XCTUnwrap(sentry_launchTracer)) ttd.reportInitialDisplay() fixture.displayLinkWrapper.call() diff --git a/Tests/SentryTests/Integrations/Performance/UIViewController/SentryTimeToDisplayTrackerTest.swift b/Tests/SentryTests/Integrations/Performance/UIViewController/SentryTimeToDisplayTrackerTest.swift index 077bf7479f4..e03c32085bf 100644 --- a/Tests/SentryTests/Integrations/Performance/UIViewController/SentryTimeToDisplayTrackerTest.swift +++ b/Tests/SentryTests/Integrations/Performance/UIViewController/SentryTimeToDisplayTrackerTest.swift @@ -22,7 +22,7 @@ class SentryTimeToDisplayTrackerTest: XCTestCase { } func getSut(for controller: UIViewController, waitForFullDisplay: Bool) -> SentryTimeToDisplayTracker { - return SentryTimeToDisplayTracker(for: controller, waitForFullDisplay: waitForFullDisplay, dispatchQueueWrapper: SentryDispatchQueueWrapper()) + return SentryTimeToDisplayTracker(name: String(describing: controller), waitForFullDisplay: waitForFullDisplay, dispatchQueueWrapper: SentryDispatchQueueWrapper()) } func getTracer() throws -> SentryTracer { @@ -305,7 +305,7 @@ class SentryTimeToDisplayTrackerTest: XCTestCase { func testReportFullyDisplayed_GetsDispatchedOnMainQueue() { let dispatchQueueWrapper = TestSentryDispatchQueueWrapper() - let sut = SentryTimeToDisplayTracker(for: UIViewController(), waitForFullDisplay: true, dispatchQueueWrapper: dispatchQueueWrapper) + let sut = SentryTimeToDisplayTracker(name: "UIViewController", waitForFullDisplay: true, dispatchQueueWrapper: dispatchQueueWrapper) let invocationsBefore = dispatchQueueWrapper.blockOnMainInvocations.count sut.reportFullyDisplayed() diff --git a/Tests/SentryTests/SentryHubTests.swift b/Tests/SentryTests/SentryHubTests.swift index 30a21d12f48..1643f77a099 100644 --- a/Tests/SentryTests/SentryHubTests.swift +++ b/Tests/SentryTests/SentryHubTests.swift @@ -893,24 +893,21 @@ class SentryHubTests: XCTestCase { #if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) func test_reportFullyDisplayed_enableTimeToFullDisplay_YES() { - fixture.options.enableTimeToFullDisplayTracing = true let sut = fixture.getSut(fixture.options) - let testTTDTracker = TestTimeToDisplayTracker() + let testTTDTracker = TestTimeToDisplayTracker(waitForFullDisplay: true) Dynamic(SentryUIViewControllerPerformanceTracker.shared).currentTTDTracker = testTTDTracker sut.reportFullyDisplayed() XCTAssertTrue(testTTDTracker.registerFullDisplayCalled) - } func test_reportFullyDisplayed_enableTimeToFullDisplay_NO() { - fixture.options.enableTimeToFullDisplayTracing = false let sut = fixture.getSut(fixture.options) - let testTTDTracker = TestTimeToDisplayTracker() + let testTTDTracker = TestTimeToDisplayTracker(waitForFullDisplay: false) Dynamic(SentryUIViewControllerPerformanceTracker.shared).currentTTDTracker = testTTDTracker @@ -1189,8 +1186,8 @@ class SentryHubTests: XCTestCase { #if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) class TestTimeToDisplayTracker: SentryTimeToDisplayTracker { - init() { - super.init(for: UIViewController(), waitForFullDisplay: false, dispatchQueueWrapper: SentryDispatchQueueWrapper()) + init(waitForFullDisplay: Bool = false) { + super.init(name: "UIViewController", waitForFullDisplay: waitForFullDisplay, dispatchQueueWrapper: SentryDispatchQueueWrapper()) } var registerFullDisplayCalled = false From e024c6ca6244d15ed4def40ba59e8a7040a7c197 Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Wed, 4 Dec 2024 12:43:26 +0100 Subject: [PATCH 03/29] Update CHANGELOG.md --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e6b4a83bad..6531d5acf29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Features + +- SwiftUI time for initial display and time for full display () + ## 8.42.0-beta.1 ### Features From 671956fa14ae2ceedd19a7325a2b3dd2b10754a5 Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Wed, 4 Dec 2024 11:45:57 +0000 Subject: [PATCH 04/29] Format code --- .../iOS-SwiftUI/iOS-SwiftUI/ContentView.swift | 2 +- Sources/Sentry/SentryTimeToDisplayTracker.m | 17 +++++++++-------- .../SentryUIViewControllerPerformanceTracker.m | 17 ++++++++++------- .../Sentry/include/SentryTimeToDisplayTracker.h | 3 +-- .../SentryInternal/SentryInternal.h | 13 +++++-------- Sources/SentrySwiftUI/SentryTracedView.swift | 3 +-- Sources/SentrySwiftUI/TracingView.swift | 1 - 7 files changed, 27 insertions(+), 29 deletions(-) diff --git a/Samples/iOS-SwiftUI/iOS-SwiftUI/ContentView.swift b/Samples/iOS-SwiftUI/iOS-SwiftUI/ContentView.swift index 07608490656..6262d3fc452 100644 --- a/Samples/iOS-SwiftUI/iOS-SwiftUI/ContentView.swift +++ b/Samples/iOS-SwiftUI/iOS-SwiftUI/ContentView.swift @@ -213,7 +213,7 @@ struct ContentView: View { } SecondView() } - } + } } } } diff --git a/Sources/Sentry/SentryTimeToDisplayTracker.m b/Sources/Sentry/SentryTimeToDisplayTracker.m index 8703d482949..29ccd541fad 100644 --- a/Sources/Sentry/SentryTimeToDisplayTracker.m +++ b/Sources/Sentry/SentryTimeToDisplayTracker.m @@ -53,9 +53,11 @@ - (instancetype)initWithName:(NSString *)name return self; } -- (instancetype)initWithName:(NSString *)name - waitForFullDisplay:(BOOL)waitForFullDisplay { - return [self initWithName:name waitForFullDisplay:waitForFullDisplay dispatchQueueWrapper:SentryDependencyContainer.sharedInstance.dispatchQueueWrapper]; +- (instancetype)initWithName:(NSString *)name waitForFullDisplay:(BOOL)waitForFullDisplay +{ + return [self initWithName:name + waitForFullDisplay:waitForFullDisplay + dispatchQueueWrapper:SentryDependencyContainer.sharedInstance.dispatchQueueWrapper]; } - (BOOL)startForTracer:(SentryTracer *)tracer @@ -67,17 +69,16 @@ - (BOOL)startForTracer:(SentryTracer *)tracer } SENTRY_LOG_DEBUG(@"Starting initial display span"); - self.initialDisplaySpan = [tracer - startChildWithOperation:SentrySpanOperationUILoadInitialDisplay - description:[NSString stringWithFormat:@"%@ initial display", _name]]; + self.initialDisplaySpan = + [tracer startChildWithOperation:SentrySpanOperationUILoadInitialDisplay + description:[NSString stringWithFormat:@"%@ initial display", _name]]; self.initialDisplaySpan.origin = SentryTraceOriginAutoUITimeToDisplay; if (self.waitForFullDisplay) { SENTRY_LOG_DEBUG(@"Starting full display span"); self.fullDisplaySpan = [tracer startChildWithOperation:SentrySpanOperationUILoadFullDisplay - description:[NSString stringWithFormat:@"%@ full display", - _name]]; + description:[NSString stringWithFormat:@"%@ full display", _name]]; self.fullDisplaySpan.origin = SentryTraceOriginManualUITimeToDisplay; // By concept TTID and TTFD spans should have the same beginning, diff --git a/Sources/Sentry/SentryUIViewControllerPerformanceTracker.m b/Sources/Sentry/SentryUIViewControllerPerformanceTracker.m index 12414f435cc..f2ed5351b87 100644 --- a/Sources/Sentry/SentryUIViewControllerPerformanceTracker.m +++ b/Sources/Sentry/SentryUIViewControllerPerformanceTracker.m @@ -165,10 +165,10 @@ - (void)startRootSpanFor:(UIViewController *)controller [self.currentTTDTracker finishSpansIfNotFinished]; - SentryTimeToDisplayTracker *ttdTracker = - [[SentryTimeToDisplayTracker alloc] initWithName: [SwiftDescriptor getObjectClassName:controller] - waitForFullDisplay:self.alwaysWaitForFullDisplay - dispatchQueueWrapper:_dispatchQueueWrapper]; + SentryTimeToDisplayTracker *ttdTracker = [[SentryTimeToDisplayTracker alloc] + initWithName:[SwiftDescriptor getObjectClassName:controller] + waitForFullDisplay:self.alwaysWaitForFullDisplay + dispatchQueueWrapper:_dispatchQueueWrapper]; if ([ttdTracker startForTracer:(SentryTracer *)vcSpan]) { objc_setAssociatedObject(controller, &SENTRY_UI_PERFORMANCE_TRACKER_TTD_TRACKER, ttdTracker, @@ -181,19 +181,22 @@ - (void)startRootSpanFor:(UIViewController *)controller - (void)reportFullyDisplayed { - SentryTimeToDisplayTracker* tracker = self.currentTTDTracker; + SentryTimeToDisplayTracker *tracker = self.currentTTDTracker; if (tracker) { if (tracker.waitForFullDisplay) { [self.currentTTDTracker reportFullyDisplayed]; } else { - SENTRY_LOG_DEBUG(@"Transaction is not waiting for full display report. You can enable `enableTimeToFullDisplay` option, or use the waitForFullDisplay property in our `SentryTracedView` view for SwiftUI."); + SENTRY_LOG_DEBUG(@"Transaction is not waiting for full display report. You can enable " + @"`enableTimeToFullDisplay` option, or use the waitForFullDisplay " + @"property in our `SentryTracedView` view for SwiftUI."); } } else { SENTRY_LOG_DEBUG(@"No screen transaction being tracked right now.") } } -- (void)setTimeToDisplayTracker:(SentryTimeToDisplayTracker *)ttdTracker { +- (void)setTimeToDisplayTracker:(SentryTimeToDisplayTracker *)ttdTracker +{ self.currentTTDTracker = ttdTracker; } diff --git a/Sources/Sentry/include/SentryTimeToDisplayTracker.h b/Sources/Sentry/include/SentryTimeToDisplayTracker.h index a5d39d90bb1..306ff1dcc9c 100644 --- a/Sources/Sentry/include/SentryTimeToDisplayTracker.h +++ b/Sources/Sentry/include/SentryTimeToDisplayTracker.h @@ -29,8 +29,7 @@ SENTRY_NO_INIT waitForFullDisplay:(BOOL)waitForFullDisplay dispatchQueueWrapper:(SentryDispatchQueueWrapper *)dispatchQueueWrapper; -- (instancetype)initWithName:(NSString *)name - waitForFullDisplay:(BOOL)waitForFullDisplay; +- (instancetype)initWithName:(NSString *)name waitForFullDisplay:(BOOL)waitForFullDisplay; - (BOOL)startForTracer:(SentryTracer *)tracer; diff --git a/Sources/SentrySwiftUI/SentryInternal/SentryInternal.h b/Sources/SentrySwiftUI/SentryInternal/SentryInternal.h index ae6a5279bdc..2c33ef72f4a 100644 --- a/Sources/SentrySwiftUI/SentryInternal/SentryInternal.h +++ b/Sources/SentrySwiftUI/SentryInternal/SentryInternal.h @@ -8,9 +8,9 @@ #import #if __has_include() -#import +# import #elif __has_include("Sentry.h") -#import "Sentry.h" +# import "Sentry.h" #endif NS_ASSUME_NONNULL_BEGIN @@ -23,7 +23,6 @@ typedef NS_ENUM(NSInteger, SentryTransactionNameSource); @interface SentryTracer : NSObject @end - typedef NS_ENUM(NSUInteger, SentrySpanStatus); @interface SentryPerformanceTracker : NSObject @@ -67,8 +66,8 @@ typedef NS_ENUM(NSUInteger, SentrySpanStatus); @end @interface SentryTimeToDisplayTracker : NSObject --(instancetype)init NS_UNAVAILABLE; -+(instancetype) new NS_UNAVAILABLE; +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; @property (nullable, nonatomic, weak, readonly) SentrySpan *initialDisplaySpan; @@ -80,8 +79,7 @@ typedef NS_ENUM(NSUInteger, SentrySpanStatus); waitForFullDisplay:(BOOL)waitForFullDisplay dispatchQueueWrapper:(SentryDispatchQueueWrapper *)dispatchQueueWrapper; -- (instancetype)initWithName:(NSString *)name - waitForFullDisplay:(BOOL)waitForFullDisplay; +- (instancetype)initWithName:(NSString *)name waitForFullDisplay:(BOOL)waitForFullDisplay; - (BOOL)startForTracer:(SentryTracer *)tracer; @@ -107,5 +105,4 @@ typedef NS_ENUM(NSUInteger, SentrySpanStatus); @property (nonatomic, nullable, readonly, class) SentryOptions *options; @end - NS_ASSUME_NONNULL_END diff --git a/Sources/SentrySwiftUI/SentryTracedView.swift b/Sources/SentrySwiftUI/SentryTracedView.swift index 9e13ad02e17..883d9ab23f7 100644 --- a/Sources/SentrySwiftUI/SentryTracedView.swift +++ b/Sources/SentrySwiftUI/SentryTracedView.swift @@ -4,11 +4,10 @@ import Foundation import SwiftUI #if CARTHAGE || SWIFT_PACKAGE -@_implementationOnly import SentryInternal import Sentry +@_implementationOnly import SentryInternal #endif - /// A control to measure the performance of your views and send the result as a transaction to Sentry.io. /// /// You create a transaction by wrapping your views with this. diff --git a/Sources/SentrySwiftUI/TracingView.swift b/Sources/SentrySwiftUI/TracingView.swift index 0f7cd1cec59..72c29d9438d 100644 --- a/Sources/SentrySwiftUI/TracingView.swift +++ b/Sources/SentrySwiftUI/TracingView.swift @@ -7,7 +7,6 @@ import SwiftUI @_implementationOnly import SentryInternal #endif - @available(iOS 13, macOS 10.15, tvOS 13, *) struct TracingView: UIViewRepresentable { From 5c531b6f2cd164cdf0105d880e8cbaa46abf0e68 Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Wed, 4 Dec 2024 12:46:10 +0100 Subject: [PATCH 05/29] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6531d5acf29..092efee3c2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### Features -- SwiftUI time for initial display and time for full display () +- SwiftUI time for initial display and time for full display (#4596) ## 8.42.0-beta.1 From e6c6f18bbb58d1796e5d3196d86a9ece419a499e Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Wed, 4 Dec 2024 13:15:11 +0100 Subject: [PATCH 06/29] fix for macOS --- .../SentryInternal/SentryInternal.h | 1 + Sources/SentrySwiftUI/SentryTracedView.swift | 27 ++++++++++++++----- Sources/SentrySwiftUI/TracingView.swift | 2 +- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/Sources/SentrySwiftUI/SentryInternal/SentryInternal.h b/Sources/SentrySwiftUI/SentryInternal/SentryInternal.h index 2c33ef72f4a..8852d961d0d 100644 --- a/Sources/SentrySwiftUI/SentryInternal/SentryInternal.h +++ b/Sources/SentrySwiftUI/SentryInternal/SentryInternal.h @@ -19,6 +19,7 @@ typedef NS_ENUM(NSInteger, SentryTransactionNameSource); @class SentrySpanId; @class SentrySpan; +@class SentryDispatchQueueWrapper; @interface SentryTracer : NSObject @end diff --git a/Sources/SentrySwiftUI/SentryTracedView.swift b/Sources/SentrySwiftUI/SentryTracedView.swift index 883d9ab23f7..2e9637b4f4a 100644 --- a/Sources/SentrySwiftUI/SentryTracedView.swift +++ b/Sources/SentrySwiftUI/SentryTracedView.swift @@ -41,10 +41,11 @@ public struct SentryTracedView: View { let content: () -> Content let name: String let nameSource: SentryTransactionNameSource - let waitforFullDisplay: Bool - let traceOrigin = "auto.ui.swift_ui" +#if canImport(SwiftUI) && canImport(UIKit) && os(iOS) || os(tvOS) + let waitforFullDisplay: Bool + /// Creates a view that measures the performance of its `content`. /// /// - Parameter viewName: The name that will be used for the span, if nil we try to get the name of the content class. @@ -57,6 +58,17 @@ public struct SentryTracedView: View { self.nameSource = viewName == nil ? .component : .custom self.waitforFullDisplay = waitForFullDisplay ?? SentrySDK.options?.enableTimeToFullDisplayTracing ?? false } +#else + /// Creates a view that measures the performance of its `content`. + /// + /// - Parameter viewName: The name that will be used for the span, if nil we try to get the name of the content class. + /// - Parameter content: The content that you want to track the performance + public init(_ viewName: String? = nil, @ViewBuilder content: @escaping () -> Content) { + self.content = content + self.name = viewName ?? SentryTracedView.extractName(content: Content.self) + self.nameSource = viewName == nil ? .component : .custom + } +#endif private static func extractName(content: Any) -> String { var result = String(describing: content) @@ -83,12 +95,15 @@ public struct SentryTracedView: View { } } - // We need to add a UIView to the view hierarchy to be able to - // monitor ui life cycles. We will use the background modifier - // to add this tracking view behind the content. return content() - .background(TracingView(name: self.name, waitForFullDisplay: self.waitforFullDisplay, tracer: trace)) .onAppear { viewAppeared = true } +#if canImport(SwiftUI) && canImport(UIKit) && os(iOS) || os(tvOS) + // We need to add a UIView to the view hierarchy to be able to + // monitor ui life cycles. We will use the background modifier + // to add this tracking view behind the content. + .background(TracingView(name: self.name, waitForFullDisplay: self.waitforFullDisplay, tracer: trace)) +#endif + } private func ensureTransactionExists() -> SentryTracer? { diff --git a/Sources/SentrySwiftUI/TracingView.swift b/Sources/SentrySwiftUI/TracingView.swift index 72c29d9438d..cb10d0c9c02 100644 --- a/Sources/SentrySwiftUI/TracingView.swift +++ b/Sources/SentrySwiftUI/TracingView.swift @@ -1,4 +1,4 @@ -#if canImport(SwiftUI) +#if canImport(SwiftUI) && canImport(UIKit) && os(iOS) || os(tvOS) import Foundation import SwiftUI From d6884c501162230d03a2bc327292fe555e5d617e Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Wed, 4 Dec 2024 13:36:38 +0100 Subject: [PATCH 07/29] fix tests --- SentryTestUtils/ClearTestState.swift | 2 +- ...SentryPerformanceTrackingIntegrationTests.swift | 4 ++-- ...ryUIViewControllerPerformanceTrackerTests.swift | 14 +++++++------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/SentryTestUtils/ClearTestState.swift b/SentryTestUtils/ClearTestState.swift index 36ec3b75773..485fa70054e 100644 --- a/SentryTestUtils/ClearTestState.swift +++ b/SentryTestUtils/ClearTestState.swift @@ -37,7 +37,7 @@ class TestCleanup: NSObject { setenv("ActivePrewarm", "0", 1) SentryAppStartTracker.load() - SentryUIViewControllerPerformanceTracker.shared.enableWaitForFullDisplay = false + SentryUIViewControllerPerformanceTracker.shared.alwaysWaitForFullDisplay = false SentryDependencyContainer.sharedInstance().swizzleWrapper.removeAllCallbacks() SentryDependencyContainer.sharedInstance().fileManager.clearDiskState() diff --git a/Tests/SentryTests/Integrations/Performance/SentryPerformanceTrackingIntegrationTests.swift b/Tests/SentryTests/Integrations/Performance/SentryPerformanceTrackingIntegrationTests.swift index 37376eabe95..35144a884f7 100644 --- a/Tests/SentryTests/Integrations/Performance/SentryPerformanceTrackingIntegrationTests.swift +++ b/Tests/SentryTests/Integrations/Performance/SentryPerformanceTrackingIntegrationTests.swift @@ -74,7 +74,7 @@ class SentryPerformanceTrackingIntegrationTests: XCTestCase { options.enableTimeToFullDisplayTracing = true sut.install(with: options) - XCTAssertTrue(SentryUIViewControllerPerformanceTracker.shared.enableWaitForFullDisplay) + XCTAssertTrue(SentryUIViewControllerPerformanceTracker.shared.alwaysWaitForFullDisplay) } func testConfigure_dontWaitForDisplay() { @@ -85,7 +85,7 @@ class SentryPerformanceTrackingIntegrationTests: XCTestCase { options.enableTimeToFullDisplayTracing = false sut.install(with: options) - XCTAssertFalse(SentryUIViewControllerPerformanceTracker.shared.enableWaitForFullDisplay) + XCTAssertFalse(SentryUIViewControllerPerformanceTracker.shared.alwaysWaitForFullDisplay) } #endif diff --git a/Tests/SentryTests/Integrations/Performance/UIViewController/SentryUIViewControllerPerformanceTrackerTests.swift b/Tests/SentryTests/Integrations/Performance/UIViewController/SentryUIViewControllerPerformanceTrackerTests.swift index 0358748c697..a27e04fe51e 100644 --- a/Tests/SentryTests/Integrations/Performance/UIViewController/SentryUIViewControllerPerformanceTrackerTests.swift +++ b/Tests/SentryTests/Integrations/Performance/UIViewController/SentryUIViewControllerPerformanceTrackerTests.swift @@ -356,7 +356,7 @@ class SentryUIViewControllerPerformanceTrackerTests: XCTestCase { func testReportFullyDisplayed() throws { let sut = fixture.getSut() - sut.enableWaitForFullDisplay = true + sut.alwaysWaitForFullDisplay = true let viewController = fixture.viewController let tracker = fixture.tracker var tracer: SentryTracer? @@ -661,7 +661,7 @@ class SentryUIViewControllerPerformanceTrackerTests: XCTestCase { var tracer: SentryTracer? - sut.enableWaitForFullDisplay = true + sut.alwaysWaitForFullDisplay = true sut.viewControllerLoadView(firstController) { tracer = self.getStack(tracker).first as? SentryTracer @@ -678,7 +678,7 @@ class SentryUIViewControllerPerformanceTrackerTests: XCTestCase { var tracer: SentryTracer? - sut.enableWaitForFullDisplay = false + sut.alwaysWaitForFullDisplay = false sut.viewControllerLoadView(firstController) { tracer = self.getStack(tracker).first as? SentryTracer @@ -711,7 +711,7 @@ class SentryUIViewControllerPerformanceTrackerTests: XCTestCase { func test_OnlyViewDidLoadTTFDEnabled_CreatesTTIDAndTTFDSpans() throws { let sut = fixture.getSut() - sut.enableWaitForFullDisplay = true + sut.alwaysWaitForFullDisplay = true let tracker = fixture.tracker var tracer: SentryTracer! @@ -776,7 +776,7 @@ class SentryUIViewControllerPerformanceTrackerTests: XCTestCase { var firstTracer: SentryTracer? var secondTracer: SentryTracer? - sut.enableWaitForFullDisplay = true + sut.alwaysWaitForFullDisplay = true let expectedFirstTTFDStartTimestamp = fixture.dateProvider.date() @@ -823,7 +823,7 @@ class SentryUIViewControllerPerformanceTrackerTests: XCTestCase { var firstTracer: SentryTracer? var secondTracer: SentryTracer? - sut.enableWaitForFullDisplay = true + sut.alwaysWaitForFullDisplay = true sut.viewControllerLoadView(firstController) { firstTracer = self.getStack(tracker).first as? SentryTracer @@ -863,7 +863,7 @@ class SentryUIViewControllerPerformanceTrackerTests: XCTestCase { var firstTracer: SentryTracer? var secondTracer: SentryTracer? - sut.enableWaitForFullDisplay = true + sut.alwaysWaitForFullDisplay = true let expectedFirstTTFDStartTimestamp = fixture.dateProvider.date() sut.viewControllerLoadView(firstController) { From ebdbc79ee118724ecd623b0046fb00cc2732a745 Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Wed, 4 Dec 2024 15:04:32 +0100 Subject: [PATCH 08/29] Update SentryTracedView.swift --- Sources/SentrySwiftUI/SentryTracedView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SentrySwiftUI/SentryTracedView.swift b/Sources/SentrySwiftUI/SentryTracedView.swift index 2e9637b4f4a..319b11fcfb1 100644 --- a/Sources/SentrySwiftUI/SentryTracedView.swift +++ b/Sources/SentrySwiftUI/SentryTracedView.swift @@ -2,9 +2,9 @@ import Foundation import SwiftUI +import Sentry #if CARTHAGE || SWIFT_PACKAGE -import Sentry @_implementationOnly import SentryInternal #endif From 78e6b6641294d5049f128c705590a4ad2b71a397 Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Wed, 4 Dec 2024 14:05:38 +0000 Subject: [PATCH 09/29] Format code --- Sources/SentrySwiftUI/SentryTracedView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SentrySwiftUI/SentryTracedView.swift b/Sources/SentrySwiftUI/SentryTracedView.swift index 319b11fcfb1..590d68e0160 100644 --- a/Sources/SentrySwiftUI/SentryTracedView.swift +++ b/Sources/SentrySwiftUI/SentryTracedView.swift @@ -1,8 +1,8 @@ #if canImport(SwiftUI) import Foundation -import SwiftUI import Sentry +import SwiftUI #if CARTHAGE || SWIFT_PACKAGE @_implementationOnly import SentryInternal From 04c6fdd39bb43bd23330597e3492d7c22dc2e7af Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Wed, 4 Dec 2024 16:18:07 +0100 Subject: [PATCH 10/29] Update SentryTimeToDisplayTrackerTest.swift --- .../SentryTimeToDisplayTrackerTest.swift | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Tests/SentryTests/Integrations/Performance/UIViewController/SentryTimeToDisplayTrackerTest.swift b/Tests/SentryTests/Integrations/Performance/UIViewController/SentryTimeToDisplayTrackerTest.swift index e03c32085bf..e9d2ebe7781 100644 --- a/Tests/SentryTests/Integrations/Performance/UIViewController/SentryTimeToDisplayTrackerTest.swift +++ b/Tests/SentryTests/Integrations/Performance/UIViewController/SentryTimeToDisplayTrackerTest.swift @@ -21,8 +21,8 @@ class SentryTimeToDisplayTrackerTest: XCTestCase { framesTracker.start() } - func getSut(for controller: UIViewController, waitForFullDisplay: Bool) -> SentryTimeToDisplayTracker { - return SentryTimeToDisplayTracker(name: String(describing: controller), waitForFullDisplay: waitForFullDisplay, dispatchQueueWrapper: SentryDispatchQueueWrapper()) + func getSut(name: String, waitForFullDisplay: Bool) -> SentryTimeToDisplayTracker { + return SentryTimeToDisplayTracker(name:name, waitForFullDisplay: waitForFullDisplay, dispatchQueueWrapper: SentryDispatchQueueWrapper()) } func getTracer() throws -> SentryTracer { @@ -52,7 +52,7 @@ class SentryTimeToDisplayTrackerTest: XCTestCase { func testNoSpansCreated_WhenFramesTrackerNotRunning() throws { fixture.framesTracker.stop() - let sut = fixture.getSut(for: UIViewController(), waitForFullDisplay: false) + let sut = fixture.getSut(name: "UIViewController", waitForFullDisplay: false) fixture.dateProvider.setDate(date: Date(timeIntervalSince1970: 7)) let tracer = try fixture.getTracer() @@ -67,7 +67,7 @@ class SentryTimeToDisplayTrackerTest: XCTestCase { } func testReportInitialDisplay_notWaitingForFullDisplay() throws { - let sut = fixture.getSut(for: UIViewController(), waitForFullDisplay: false) + let sut = fixture.getSut(name: "UIViewController", waitForFullDisplay: false) fixture.dateProvider.setDate(date: Date(timeIntervalSince1970: 7)) let tracer = try fixture.getTracer() @@ -101,7 +101,7 @@ class SentryTimeToDisplayTrackerTest: XCTestCase { func testReportInitialDisplay_waitForFullDisplay() throws { fixture.dateProvider.setDate(date: Date(timeIntervalSince1970: 7)) - let sut = fixture.getSut(for: UIViewController(), waitForFullDisplay: true) + let sut = fixture.getSut(name: "UIViewController", waitForFullDisplay: true) let tracer = try fixture.getTracer() sut.start(for: tracer) @@ -130,7 +130,7 @@ class SentryTimeToDisplayTrackerTest: XCTestCase { } func testReportFullDisplay_notWaitingForFullDisplay() throws { - let sut = fixture.getSut(for: UIViewController(), waitForFullDisplay: false) + let sut = fixture.getSut(name: "UIViewController", waitForFullDisplay: false) let tracer = try fixture.getTracer() sut.start(for: tracer) @@ -150,7 +150,7 @@ class SentryTimeToDisplayTrackerTest: XCTestCase { func testReportFullDisplay_waitingForFullDisplay() throws { fixture.dateProvider.setDate(date: Date(timeIntervalSince1970: 9)) - let sut = fixture.getSut(for: UIViewController(), waitForFullDisplay: true) + let sut = fixture.getSut(name: "UIViewController", waitForFullDisplay: true) let tracer = try fixture.getTracer() sut.start(for: tracer) @@ -184,7 +184,7 @@ class SentryTimeToDisplayTrackerTest: XCTestCase { func testWaitingForFullDisplay_ReportFullDisplayBeforeInitialDisplay() throws { fixture.dateProvider.setDate(date: Date(timeIntervalSince1970: 9)) - let sut = fixture.getSut(for: UIViewController(), waitForFullDisplay: true) + let sut = fixture.getSut(name: "UIViewController", waitForFullDisplay: true) let tracer = try fixture.getTracer() sut.start(for: tracer) @@ -224,7 +224,7 @@ class SentryTimeToDisplayTrackerTest: XCTestCase { } func testTracerFinishesBeforeReportInitialDisplay_FinishesInitialDisplaySpan() throws { - let sut = fixture.getSut(for: UIViewController(), waitForFullDisplay: false) + let sut = fixture.getSut(name: "UIViewController", waitForFullDisplay: false) fixture.dateProvider.setDate(date: Date(timeIntervalSince1970: 7)) let tracer = try fixture.getTracer() @@ -254,7 +254,7 @@ class SentryTimeToDisplayTrackerTest: XCTestCase { fixture.dateProvider.setDate(date: Date(timeIntervalSince1970: 9)) fixture.dateProvider.driftTimeForEveryRead = true - let sut = fixture.getSut(for: UIViewController(), waitForFullDisplay: true) + let sut = fixture.getSut(name: "UIViewController", waitForFullDisplay: true) let tracer = try fixture.getTracer() sut.start(for: tracer) @@ -269,7 +269,7 @@ class SentryTimeToDisplayTrackerTest: XCTestCase { let tracer = try fixture.getTracer() - let sut = fixture.getSut(for: UIViewController(), waitForFullDisplay: true) + let sut = fixture.getSut(name: "UIViewController", waitForFullDisplay: true) sut.start(for: tracer) @@ -319,7 +319,7 @@ class SentryTimeToDisplayTrackerTest: XCTestCase { let tracer = try fixture.getTracer() - let sut = fixture.getSut(for: UIViewController(), waitForFullDisplay: false) + let sut = fixture.getSut(name: "UIViewController", waitForFullDisplay: false) sut.start(for: tracer) @@ -351,7 +351,7 @@ class SentryTimeToDisplayTrackerTest: XCTestCase { fixture.dateProvider.setDate(date: Date(timeIntervalSince1970: 7)) - let sut = fixture.getSut(for: UIViewController(), waitForFullDisplay: false) + let sut = fixture.getSut(name: "UIViewController", waitForFullDisplay: false) let tracer = try fixture.getTracer() sut.start(for: tracer) @@ -384,7 +384,7 @@ class SentryTimeToDisplayTrackerTest: XCTestCase { fixture.dateProvider.setDate(date: Date(timeIntervalSince1970: 7)) - let sut = fixture.getSut(for: UIViewController(), waitForFullDisplay: true) + let sut = fixture.getSut(name: "UIViewController", waitForFullDisplay: true) let tracer = try fixture.getTracer() sut.start(for: tracer) @@ -416,7 +416,7 @@ class SentryTimeToDisplayTrackerTest: XCTestCase { func testFinish_WithoutCallingReportFullyDisplayed() throws { fixture.dateProvider.setDate(date: Date(timeIntervalSince1970: 9)) - let sut = fixture.getSut(for: UIViewController(), waitForFullDisplay: true) + let sut = fixture.getSut(name: "UIViewController", waitForFullDisplay: true) let tracer = try fixture.getTracer() sut.start(for: tracer) @@ -451,7 +451,7 @@ class SentryTimeToDisplayTrackerTest: XCTestCase { func testFinish_WithoutTTID() throws { fixture.dateProvider.setDate(date: Date(timeIntervalSince1970: 9)) - let sut = fixture.getSut(for: UIViewController(), waitForFullDisplay: true) + let sut = fixture.getSut(name: "UIViewController", waitForFullDisplay: true) let tracer = try fixture.getTracer() sut.start(for: tracer) From 818aa34d91c8077a6a81bb506b0686734a1ec8b6 Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Wed, 4 Dec 2024 15:19:15 +0000 Subject: [PATCH 11/29] Format code --- .../UIViewController/SentryTimeToDisplayTrackerTest.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/SentryTests/Integrations/Performance/UIViewController/SentryTimeToDisplayTrackerTest.swift b/Tests/SentryTests/Integrations/Performance/UIViewController/SentryTimeToDisplayTrackerTest.swift index e9d2ebe7781..865e5e45599 100644 --- a/Tests/SentryTests/Integrations/Performance/UIViewController/SentryTimeToDisplayTrackerTest.swift +++ b/Tests/SentryTests/Integrations/Performance/UIViewController/SentryTimeToDisplayTrackerTest.swift @@ -22,7 +22,7 @@ class SentryTimeToDisplayTrackerTest: XCTestCase { } func getSut(name: String, waitForFullDisplay: Bool) -> SentryTimeToDisplayTracker { - return SentryTimeToDisplayTracker(name:name, waitForFullDisplay: waitForFullDisplay, dispatchQueueWrapper: SentryDispatchQueueWrapper()) + return SentryTimeToDisplayTracker(name: name, waitForFullDisplay: waitForFullDisplay, dispatchQueueWrapper: SentryDispatchQueueWrapper()) } func getTracer() throws -> SentryTracer { From 6bbebc7387753132268c9632ddb921818ec1729e Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Wed, 4 Dec 2024 16:30:00 +0100 Subject: [PATCH 12/29] Update SentrySDKTests.swift --- Tests/SentryTests/SentrySDKTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/SentryTests/SentrySDKTests.swift b/Tests/SentryTests/SentrySDKTests.swift index 75239a90d05..245a4f9f18e 100644 --- a/Tests/SentryTests/SentrySDKTests.swift +++ b/Tests/SentryTests/SentrySDKTests.swift @@ -688,8 +688,8 @@ class SentrySDKTests: XCTestCase { SentrySDK.start(options: fixture.options) - let testTTDTracker = TestTimeToDisplayTracker() - + let testTTDTracker = TestTimeToDisplayTracker(waitForFullDisplay: true) + Dynamic(SentryUIViewControllerPerformanceTracker.shared).currentTTDTracker = testTTDTracker SentrySDK.reportFullyDisplayed() From a970eb3db914795387e8c3006c35b9808ca54fe2 Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Thu, 5 Dec 2024 09:56:32 +0100 Subject: [PATCH 13/29] UITests --- .../iOS-SwiftUI-UITests/LaunchUITests.swift | 8 ++++ .../SentryRedactModifierTests.swift | 14 +++---- .../iOS-SwiftUI/iOS-SwiftUI/ContentView.swift | 37 ++++++++++++++++--- 3 files changed, 45 insertions(+), 14 deletions(-) diff --git a/Samples/iOS-SwiftUI/iOS-SwiftUI-UITests/LaunchUITests.swift b/Samples/iOS-SwiftUI/iOS-SwiftUI-UITests/LaunchUITests.swift index 6b45f4a3dc1..822fa607e57 100644 --- a/Samples/iOS-SwiftUI/iOS-SwiftUI-UITests/LaunchUITests.swift +++ b/Samples/iOS-SwiftUI/iOS-SwiftUI-UITests/LaunchUITests.swift @@ -38,4 +38,12 @@ class LaunchUITests: XCTestCase { formScreenNavigationBar/*@START_MENU_TOKEN@*/.buttons["Test"]/*[[".otherElements[\"Test\"].buttons[\"Test\"]",".buttons[\"Test\"]"],[[[-1,1],[-1,0]]],[0]]@END_MENU_TOKEN@*/.tap() XCTAssertEqual(app.staticTexts["SPAN_ID"].label, "NO SPAN") } + + func testTTID_TTFD() { + let app = XCUIApplication() + app.launch() + app.buttons["Show TTD"].tap() + + XCTAssertEqual(app.staticTexts["TTDInfo"].label, "TTID and TTFD found") + } } diff --git a/Samples/iOS-SwiftUI/iOS-SwiftUI-UITests/SentryRedactModifierTests.swift b/Samples/iOS-SwiftUI/iOS-SwiftUI-UITests/SentryRedactModifierTests.swift index 48caa3246a4..5f07710a29e 100644 --- a/Samples/iOS-SwiftUI/iOS-SwiftUI-UITests/SentryRedactModifierTests.swift +++ b/Samples/iOS-SwiftUI/iOS-SwiftUI-UITests/SentryRedactModifierTests.swift @@ -7,17 +7,15 @@ import XCTest class SentryRedactModifierTests: XCTestCase { func testViewMask() throws { - let text = Text("Hello, World!") - let redactedText = text.sentryReplayMask() - - XCTAssertTrue(redactedText is ModifiedContent) + let redactedText = Text("Hello, World!").sentryReplayMask() + let description = String(describing: redactedText) + XCTAssertTrue(description.starts(with: "AnyView(ModifiedContent")) } func testViewUnmask() throws { - let text = Text("Hello, World!") - let redactedText = text.sentryReplayUnmask() - - XCTAssertTrue(redactedText is ModifiedContent) + let redactedText = Text("Hello, World!").sentryReplayUnmask() + let description = String(describing: redactedText) + XCTAssertTrue(description.starts(with: "AnyView(ModifiedContent")) } } diff --git a/Samples/iOS-SwiftUI/iOS-SwiftUI/ContentView.swift b/Samples/iOS-SwiftUI/iOS-SwiftUI/ContentView.swift index 6262d3fc452..ae439fb9733 100644 --- a/Samples/iOS-SwiftUI/iOS-SwiftUI/ContentView.swift +++ b/Samples/iOS-SwiftUI/iOS-SwiftUI/ContentView.swift @@ -15,6 +15,8 @@ class DataBag { struct ContentView: View { + @State var TTDInfo: String = "" + var addBreadcrumbAction: () -> Void = { let crumb = Breadcrumb(level: SentryLevel.info, category: "Debug") crumb.message = "tapped addBreadcrumb" @@ -89,12 +91,34 @@ struct ContentView: View { } } + func showTTD() { + guard let tracer = getCurrentTracer() else { return } + + var log = [String]() + + if !hasTTID(tracer: tracer) { log.append("TTID not found") } + if !hasTTFD(tracer: tracer) { log.append("TTFD not found") } + + if log.isEmpty { + log.append("TTID and TTFD found") + } + TTDInfo = log.joined(separator: "\n") + } + func getCurrentTracer() -> SentryTracer? { if DataBag.shared.info["initialTransaction"] == nil { DataBag.shared.info["initialTransaction"] = SentrySDK.span as? SentryTracer } return DataBag.shared.info["initialTransaction"] as? SentryTracer } + + func hasTTID(tracer: SentryTracer?) -> Bool { + tracer?.children.contains { $0.spanDescription?.contains("initial display") == true } == true + } + + func hasTTFD(tracer: SentryTracer?) -> Bool { + tracer?.children.contains { $0.spanDescription?.contains("full display") == true } == true + } func getCurrentSpan() -> Span? { @@ -129,12 +153,7 @@ struct ContentView: View { .accessibilityIdentifier("TRACE_ORIGIN") }.sentryReplayUnmask() .onAppear { - Task { - if #available(iOS 16.0, *) { - try? await Task.sleep(for: .seconds(2)) - } - SentrySDK.reportFullyDisplayed() - } + SentrySDK.reportFullyDisplayed() } SentryTracedView("Child Span") { VStack { @@ -173,6 +192,9 @@ struct ContentView: View { Button(action: captureTransactionAction) { Text("Capture Transaction") } + Button(action: showTTD) { + Text("Show TTD") + } } VStack(spacing: 16) { Button(action: { @@ -212,6 +234,9 @@ struct ContentView: View { .background(Color.white) } SecondView() + + Text(TTDInfo) + .accessibilityIdentifier("TTDInfo") } } } From d51b0ee9f848610981bd055610851afb7365f07b Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Thu, 5 Dec 2024 14:53:13 +0100 Subject: [PATCH 14/29] Update CHANGELOG.md --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24f63bd1b40..82652ad96fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,13 @@ # Changelog -## 8.42.0-beta.2 +## Unreleased ### Features - SwiftUI time for initial display and time for full display (#4596) +## 8.42.0-beta.2 + ### Fixes - Fix GraphQL context for HTTP client error tracking (#4567) From 45623f30c42d6c488b197c2325c133ff02c0dd97 Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Tue, 10 Dec 2024 10:24:30 +0100 Subject: [PATCH 15/29] Update SentryRedactModifierTests.swift --- .../SentryRedactModifierTests.swift | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/Samples/iOS-SwiftUI/iOS-SwiftUI-UITests/SentryRedactModifierTests.swift b/Samples/iOS-SwiftUI/iOS-SwiftUI-UITests/SentryRedactModifierTests.swift index 5f07710a29e..62a46640a57 100644 --- a/Samples/iOS-SwiftUI/iOS-SwiftUI-UITests/SentryRedactModifierTests.swift +++ b/Samples/iOS-SwiftUI/iOS-SwiftUI-UITests/SentryRedactModifierTests.swift @@ -5,19 +5,17 @@ import SwiftUI import XCTest class SentryRedactModifierTests: XCTestCase { - func testViewMask() throws { - let redactedText = Text("Hello, World!").sentryReplayMask() - let description = String(describing: redactedText) - XCTAssertTrue(description.starts(with: "AnyView(ModifiedContent")) + let text = Text("Hello, World!") + let redactedText = text.sentryReplayMask() + XCTAssertTrue(redactedText is ModifiedContent) } func testViewUnmask() throws { - let redactedText = Text("Hello, World!").sentryReplayUnmask() - let description = String(describing: redactedText) - XCTAssertTrue(description.starts(with: "AnyView(ModifiedContent")) + let text = Text("Hello, World!") + let redactedText = text.sentryReplayUnmask() + XCTAssertTrue(redactedText is ModifiedContent) } - } #endif From 331d1dfe4b3b262d49d44107b33aa9bb84211e43 Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Thu, 12 Dec 2024 15:07:58 +0100 Subject: [PATCH 16/29] use onAppear --- .../iOS-SwiftUI/iOS-SwiftUI/ContentView.swift | 10 ++-- Sentry.xcodeproj/project.pbxproj | 10 ++-- Sources/SentrySwiftUI/SentryTracedView.swift | 35 +++++++---- Sources/SentrySwiftUI/TracingView.swift | 58 ------------------- 4 files changed, 34 insertions(+), 79 deletions(-) delete mode 100644 Sources/SentrySwiftUI/TracingView.swift diff --git a/Samples/iOS-SwiftUI/iOS-SwiftUI/ContentView.swift b/Samples/iOS-SwiftUI/iOS-SwiftUI/ContentView.swift index ae439fb9733..2ab8c32b129 100644 --- a/Samples/iOS-SwiftUI/iOS-SwiftUI/ContentView.swift +++ b/Samples/iOS-SwiftUI/iOS-SwiftUI/ContentView.swift @@ -137,7 +137,7 @@ struct ContentView: View { return DataBag.shared.info["lastSpan"] as? Span } - + var body: some View { return SentryTracedView("Content View Body", waitForFullDisplay: true) { NavigationView { VStack(alignment: HorizontalAlignment.center, spacing: 16) { @@ -155,7 +155,7 @@ struct ContentView: View { .onAppear { SentrySDK.reportFullyDisplayed() } - SentryTracedView("Child Span") { + //SentryTracedView("Child Span") { VStack { Text(getCurrentSpan()?.spanDescription ?? "NO SPAN") .accessibilityIdentifier("CHILD_NAME") @@ -165,7 +165,7 @@ struct ContentView: View { Text(getCurrentSpan()?.origin ?? "NO CHILD ORIGIN") .accessibilityIdentifier("CHILD_TRACE_ORIGIN") } - } + //} HStack (spacing: 30) { VStack(spacing: 16) { @@ -245,9 +245,9 @@ struct ContentView: View { struct SecondView: View { var body: some View { - SentryTracedView("Second View") { + //SentryTracedView("Second View") { Text("This is the detail view 1") - } + //} } } diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index 4d981888919..32ad5a386c2 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -925,7 +925,6 @@ D8DBE0D22C0EFFC300FAB1FD /* SentryReplayOptionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8DBE0D12C0EFFC300FAB1FD /* SentryReplayOptionsTests.swift */; }; D8F016B32B9622D6007B9AFB /* SentryId.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F016B22B9622D6007B9AFB /* SentryId.swift */; }; D8F016B62B962548007B9AFB /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F016B52B962548007B9AFB /* StringExtensions.swift */; }; - D8F0F3B72D005B0400826CE3 /* TracingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F0F3B62D005AFB00826CE3 /* TracingView.swift */; }; D8F67AEE2BE0D19200C9197B /* UIImageHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F67AED2BE0D19200C9197B /* UIImageHelper.swift */; }; D8F67AF12BE0D33F00C9197B /* UIImageHelperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F67AEF2BE0D31A00C9197B /* UIImageHelperTests.swift */; }; D8F67AF42BE10F9600C9197B /* UIRedactBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F67AF22BE10F7600C9197B /* UIRedactBuilderTests.swift */; }; @@ -2008,7 +2007,6 @@ D8F016B52B962548007B9AFB /* StringExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtensions.swift; sourceTree = ""; }; D8F01DE42A126B62008F4996 /* HybridPod.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; path = HybridPod.podspec; sourceTree = ""; }; D8F01DE52A126BF5008F4996 /* HybridTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HybridTest.swift; sourceTree = ""; }; - D8F0F3B62D005AFB00826CE3 /* TracingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TracingView.swift; sourceTree = ""; }; D8F67AED2BE0D19200C9197B /* UIImageHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImageHelper.swift; sourceTree = ""; }; D8F67AEF2BE0D31A00C9197B /* UIImageHelperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImageHelperTests.swift; sourceTree = ""; }; D8F67AF22BE10F7600C9197B /* UIRedactBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIRedactBuilderTests.swift; sourceTree = ""; }; @@ -3678,7 +3676,6 @@ D8199DB529376ECC0074249E /* SentrySwiftUI.h */, D88D25E92B8E0BAC0073C3D5 /* module.modulemap */, D8199DB629376ECC0074249E /* SentryTracedView.swift */, - D8F0F3B62D005AFB00826CE3 /* TracingView.swift */, D8A65B5C2C98656000974B74 /* SentryReplayView.swift */, ); path = SentrySwiftUI; @@ -5152,7 +5149,6 @@ files = ( D8199DC129376EEC0074249E /* SentryTracedView.swift in Sources */, D8199DBF29376EE20074249E /* SentryInternal.m in Sources */, - D8F0F3B72D005B0400826CE3 /* TracingView.swift in Sources */, D8A65B5D2C98656800974B74 /* SentryReplayView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -5324,6 +5320,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_DYLIB_INSTALL_NAME = "$(DYLIB_INSTALL_NAME_BASE:standardizepath)/$(EXECUTABLE_PATH)"; MODULEMAP_PRIVATE_FILE = ""; + OTHER_SWIFT_FLAGS = ""; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -5360,6 +5357,7 @@ "@loader_path/Frameworks", ); MODULEMAP_PRIVATE_FILE = ""; + OTHER_SWIFT_FLAGS = ""; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; USE_HEADERMAP = YES; @@ -5516,6 +5514,7 @@ "@loader_path/Frameworks", ); MODULEMAP_PRIVATE_FILE = ""; + OTHER_SWIFT_FLAGS = ""; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -5640,6 +5639,7 @@ "@loader_path/Frameworks", ); MODULEMAP_PRIVATE_FILE = ""; + OTHER_SWIFT_FLAGS = ""; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -6115,6 +6115,7 @@ "@loader_path/Frameworks", ); MODULEMAP_PRIVATE_FILE = ""; + OTHER_SWIFT_FLAGS = ""; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; USE_HEADERMAP = YES; @@ -6341,6 +6342,7 @@ "@loader_path/Frameworks", ); MODULEMAP_PRIVATE_FILE = ""; + OTHER_SWIFT_FLAGS = ""; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; diff --git a/Sources/SentrySwiftUI/SentryTracedView.swift b/Sources/SentrySwiftUI/SentryTracedView.swift index 590d68e0160..0440a848cd5 100644 --- a/Sources/SentrySwiftUI/SentryTracedView.swift +++ b/Sources/SentrySwiftUI/SentryTracedView.swift @@ -8,6 +8,11 @@ import SwiftUI @_implementationOnly import SentryInternal #endif +class SentryTraceViewModel { + var tracker: SentryTimeToDisplayTracker? +} + + /// A control to measure the performance of your views and send the result as a transaction to Sentry.io. /// /// You create a transaction by wrapping your views with this. @@ -36,7 +41,8 @@ import SwiftUI /// @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6.0, *) public struct SentryTracedView: View { - @State var viewAppeared = false + @State private var viewAppeared = false + @State private var viewModel = SentryTraceViewModel() let content: () -> Content let name: String @@ -86,24 +92,29 @@ public struct SentryTracedView: View { if !viewAppeared { trace = ensureTransactionExists() + if let trace = trace { startTTDTraker(for: trace) } spanId = createAndPushBodySpan(transactionCreated: trace != nil) } defer { - if let spanId = spanId { - finishSpan(spanId) - } + if let spanId = spanId { finishSpan(spanId) } } return content() - .onAppear { viewAppeared = true } -#if canImport(SwiftUI) && canImport(UIKit) && os(iOS) || os(tvOS) - // We need to add a UIView to the view hierarchy to be able to - // monitor ui life cycles. We will use the background modifier - // to add this tracking view behind the content. - .background(TracingView(name: self.name, waitForFullDisplay: self.waitforFullDisplay, tracer: trace)) -#endif - + .onAppear { + if !viewAppeared { + viewAppeared = true + viewModel.tracker?.reportInitialDisplay() + } + } + } + + private func startTTDTraker(for trace: SentryTracer) { + let tracker = SentryTimeToDisplayTracker(name: self.name, waitForFullDisplay: self.waitforFullDisplay) + SentryUIViewControllerPerformanceTracker.shared.setTimeToDisplay(tracker) + tracker.start(for: trace) + + viewModel.tracker = tracker } private func ensureTransactionExists() -> SentryTracer? { diff --git a/Sources/SentrySwiftUI/TracingView.swift b/Sources/SentrySwiftUI/TracingView.swift deleted file mode 100644 index cb10d0c9c02..00000000000 --- a/Sources/SentrySwiftUI/TracingView.swift +++ /dev/null @@ -1,58 +0,0 @@ -#if canImport(SwiftUI) && canImport(UIKit) && os(iOS) || os(tvOS) - -import Foundation -import SwiftUI - -#if CARTHAGE || SWIFT_PACKAGE -@_implementationOnly import SentryInternal -#endif - -@available(iOS 13, macOS 10.15, tvOS 13, *) -struct TracingView: UIViewRepresentable { - - let name: String - let waitForFullDisplay: Bool - let tracer: SentryTracer? - - class SentryTracingView: UIView { - - let tracer: SentryTracer? - let tracker: SentryTimeToDisplayTracker? - - init(name: String, waitForFullDisplay: Bool, tracer: SentryTracer?) { - self.tracer = tracer - if let tracer = self.tracer { - let tracker = SentryTimeToDisplayTracker(name: name, waitForFullDisplay: waitForFullDisplay) - self.tracker = tracker - SentryUIViewControllerPerformanceTracker.shared.setTimeToDisplay(tracker) - tracker.start(for: tracer) - } else { - tracker = nil - } - - super.init(frame: CGRect(origin: .zero, size: CGSize(width: 1, height: 1))) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func didMoveToWindow() { - super.didMoveToWindow() - // Reports initial display when this view is added to the view hierarchy - // This is the equivalent of viewDidAppear of a UIViewController - tracker?.reportInitialDisplay() - } - } - - func makeUIView(context: Context) -> UIView { - let view = SentryTracingView(name: name, waitForFullDisplay: waitForFullDisplay, tracer: tracer) - view.isUserInteractionEnabled = false - return view - } - - func updateUIView(_ uiView: UIView, context: Context) { - //Intentionally left blank. Nothing to update here. - } -} -#endif From f1b663270b83f5f50a646e54228fdc56ac2571c2 Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Thu, 12 Dec 2024 14:09:03 +0000 Subject: [PATCH 17/29] Format code --- Sources/SentrySwiftUI/SentryTracedView.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/SentrySwiftUI/SentryTracedView.swift b/Sources/SentrySwiftUI/SentryTracedView.swift index 0440a848cd5..de549e3b46a 100644 --- a/Sources/SentrySwiftUI/SentryTracedView.swift +++ b/Sources/SentrySwiftUI/SentryTracedView.swift @@ -12,7 +12,6 @@ class SentryTraceViewModel { var tracker: SentryTimeToDisplayTracker? } - /// A control to measure the performance of your views and send the result as a transaction to Sentry.io. /// /// You create a transaction by wrapping your views with this. From 6dc28e5e5f771e3b87ff3e6b5ff9476a1a04e84f Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Thu, 12 Dec 2024 15:19:52 +0100 Subject: [PATCH 18/29] Update SentryTracedView.swift --- Sources/SentrySwiftUI/SentryTracedView.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Sources/SentrySwiftUI/SentryTracedView.swift b/Sources/SentrySwiftUI/SentryTracedView.swift index de549e3b46a..144e7b1ef44 100644 --- a/Sources/SentrySwiftUI/SentryTracedView.swift +++ b/Sources/SentrySwiftUI/SentryTracedView.swift @@ -91,7 +91,9 @@ public struct SentryTracedView: View { if !viewAppeared { trace = ensureTransactionExists() +#if canImport(SwiftUI) && canImport(UIKit) && os(iOS) || os(tvOS) if let trace = trace { startTTDTraker(for: trace) } +#endif spanId = createAndPushBodySpan(transactionCreated: trace != nil) } @@ -107,7 +109,7 @@ public struct SentryTracedView: View { } } } - +#if canImport(SwiftUI) && canImport(UIKit) && os(iOS) || os(tvOS) private func startTTDTraker(for trace: SentryTracer) { let tracker = SentryTimeToDisplayTracker(name: self.name, waitForFullDisplay: self.waitforFullDisplay) SentryUIViewControllerPerformanceTracker.shared.setTimeToDisplay(tracker) @@ -115,6 +117,7 @@ public struct SentryTracedView: View { viewModel.tracker = tracker } +#endif private func ensureTransactionExists() -> SentryTracer? { guard SentryPerformanceTracker.shared.activeSpanId() == nil else { return nil } From 588764eabdbafc18974d75b6c7a9416df927b8c9 Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Thu, 12 Dec 2024 16:00:33 +0100 Subject: [PATCH 19/29] fix test --- Samples/iOS-SwiftUI/iOS-SwiftUI/ContentView.swift | 8 ++++---- Sources/SentrySwiftUI/SentryTracedView.swift | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Samples/iOS-SwiftUI/iOS-SwiftUI/ContentView.swift b/Samples/iOS-SwiftUI/iOS-SwiftUI/ContentView.swift index 2ab8c32b129..1cf4df2a620 100644 --- a/Samples/iOS-SwiftUI/iOS-SwiftUI/ContentView.swift +++ b/Samples/iOS-SwiftUI/iOS-SwiftUI/ContentView.swift @@ -155,7 +155,7 @@ struct ContentView: View { .onAppear { SentrySDK.reportFullyDisplayed() } - //SentryTracedView("Child Span") { + SentryTracedView("Child Span") { VStack { Text(getCurrentSpan()?.spanDescription ?? "NO SPAN") .accessibilityIdentifier("CHILD_NAME") @@ -165,7 +165,7 @@ struct ContentView: View { Text(getCurrentSpan()?.origin ?? "NO CHILD ORIGIN") .accessibilityIdentifier("CHILD_TRACE_ORIGIN") } - //} + } HStack (spacing: 30) { VStack(spacing: 16) { @@ -245,9 +245,9 @@ struct ContentView: View { struct SecondView: View { var body: some View { - //SentryTracedView("Second View") { + SentryTracedView("Second View") { Text("This is the detail view 1") - //} + } } } diff --git a/Sources/SentrySwiftUI/SentryTracedView.swift b/Sources/SentrySwiftUI/SentryTracedView.swift index 144e7b1ef44..d764483841f 100644 --- a/Sources/SentrySwiftUI/SentryTracedView.swift +++ b/Sources/SentrySwiftUI/SentryTracedView.swift @@ -91,10 +91,10 @@ public struct SentryTracedView: View { if !viewAppeared { trace = ensureTransactionExists() + spanId = createAndPushBodySpan(transactionCreated: trace != nil) #if canImport(SwiftUI) && canImport(UIKit) && os(iOS) || os(tvOS) if let trace = trace { startTTDTraker(for: trace) } #endif - spanId = createAndPushBodySpan(transactionCreated: trace != nil) } defer { From 15cfd3531e7142b67b5ee3c344cfceea92c7e351 Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Fri, 13 Dec 2024 11:08:29 +0100 Subject: [PATCH 20/29] Update Sources/Sentry/SentryUIViewControllerPerformanceTracker.m Co-authored-by: Philipp Hofmann --- Sources/Sentry/SentryUIViewControllerPerformanceTracker.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Sentry/SentryUIViewControllerPerformanceTracker.m b/Sources/Sentry/SentryUIViewControllerPerformanceTracker.m index f2ed5351b87..ee36693b545 100644 --- a/Sources/Sentry/SentryUIViewControllerPerformanceTracker.m +++ b/Sources/Sentry/SentryUIViewControllerPerformanceTracker.m @@ -186,7 +186,7 @@ - (void)reportFullyDisplayed if (tracker.waitForFullDisplay) { [self.currentTTDTracker reportFullyDisplayed]; } else { - SENTRY_LOG_DEBUG(@"Transaction is not waiting for full display report. You can enable " + SENTRY_LOG_WARN(@"Transaction is not waiting for full display report. You can enable " @"`enableTimeToFullDisplay` option, or use the waitForFullDisplay " @"property in our `SentryTracedView` view for SwiftUI."); } From edba1bac52737ea663f3fae9f8ebd806e89b1e89 Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Fri, 13 Dec 2024 10:14:59 +0000 Subject: [PATCH 21/29] Format code --- Sources/Sentry/SentryUIViewControllerPerformanceTracker.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Sentry/SentryUIViewControllerPerformanceTracker.m b/Sources/Sentry/SentryUIViewControllerPerformanceTracker.m index ee36693b545..c0c22357f1b 100644 --- a/Sources/Sentry/SentryUIViewControllerPerformanceTracker.m +++ b/Sources/Sentry/SentryUIViewControllerPerformanceTracker.m @@ -187,8 +187,8 @@ - (void)reportFullyDisplayed [self.currentTTDTracker reportFullyDisplayed]; } else { SENTRY_LOG_WARN(@"Transaction is not waiting for full display report. You can enable " - @"`enableTimeToFullDisplay` option, or use the waitForFullDisplay " - @"property in our `SentryTracedView` view for SwiftUI."); + @"`enableTimeToFullDisplay` option, or use the waitForFullDisplay " + @"property in our `SentryTracedView` view for SwiftUI."); } } else { SENTRY_LOG_DEBUG(@"No screen transaction being tracked right now.") From 98e4c6ed3d8e34c346e8f509510317b4309b8c12 Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Wed, 18 Dec 2024 10:16:29 +0100 Subject: [PATCH 22/29] refactoring --- Sources/Sentry/SentryTimeToDisplayTracker.m | 7 ----- ...SentryUIViewControllerPerformanceTracker.m | 30 +++++++++++-------- .../include/SentryTimeToDisplayTracker.h | 2 -- ...SentryUIViewControllerPerformanceTracker.h | 5 +++- .../SentryInternal/SentryInternal.h | 4 ++- Sources/SentrySwiftUI/SentryTracedView.swift | 21 +++++++------ 6 files changed, 35 insertions(+), 34 deletions(-) diff --git a/Sources/Sentry/SentryTimeToDisplayTracker.m b/Sources/Sentry/SentryTimeToDisplayTracker.m index 29ccd541fad..89136a75560 100644 --- a/Sources/Sentry/SentryTimeToDisplayTracker.m +++ b/Sources/Sentry/SentryTimeToDisplayTracker.m @@ -53,13 +53,6 @@ - (instancetype)initWithName:(NSString *)name return self; } -- (instancetype)initWithName:(NSString *)name waitForFullDisplay:(BOOL)waitForFullDisplay -{ - return [self initWithName:name - waitForFullDisplay:waitForFullDisplay - dispatchQueueWrapper:SentryDependencyContainer.sharedInstance.dispatchQueueWrapper]; -} - - (BOOL)startForTracer:(SentryTracer *)tracer { if (SentryDependencyContainer.sharedInstance.framesTracker.isRunning == NO) { diff --git a/Sources/Sentry/SentryUIViewControllerPerformanceTracker.m b/Sources/Sentry/SentryUIViewControllerPerformanceTracker.m index c0c22357f1b..fab7fc261fd 100644 --- a/Sources/Sentry/SentryUIViewControllerPerformanceTracker.m +++ b/Sources/Sentry/SentryUIViewControllerPerformanceTracker.m @@ -163,19 +163,11 @@ - (void)startRootSpanFor:(UIViewController *)controller return; } - [self.currentTTDTracker finishSpansIfNotFinished]; - - SentryTimeToDisplayTracker *ttdTracker = [[SentryTimeToDisplayTracker alloc] - initWithName:[SwiftDescriptor getObjectClassName:controller] - waitForFullDisplay:self.alwaysWaitForFullDisplay - dispatchQueueWrapper:_dispatchQueueWrapper]; + SentryTimeToDisplayTracker *ttdTracker = [self startTimeToDisplayTrackerForScreen:[SwiftDescriptor getObjectClassName:controller] waitForFullDisplay:self.alwaysWaitForFullDisplay tracer:(SentryTracer *)vcSpan]; - if ([ttdTracker startForTracer:(SentryTracer *)vcSpan]) { + if (ttdTracker) { objc_setAssociatedObject(controller, &SENTRY_UI_PERFORMANCE_TRACKER_TTD_TRACKER, ttdTracker, OBJC_ASSOCIATION_ASSIGN); - self.currentTTDTracker = ttdTracker; - } else { - self.currentTTDTracker = nil; } } @@ -195,9 +187,23 @@ - (void)reportFullyDisplayed } } -- (void)setTimeToDisplayTracker:(SentryTimeToDisplayTracker *)ttdTracker -{ +- (SentryTimeToDisplayTracker *)startTimeToDisplayTrackerForScreen:(NSString *)screenName + waitForFullDisplay:(BOOL)waitForFullDisplay + tracer:(SentryTracer *)tracer { + [self.currentTTDTracker finishSpansIfNotFinished]; + + SentryTimeToDisplayTracker *ttdTracker = [[SentryTimeToDisplayTracker alloc] + initWithName:screenName + waitForFullDisplay:waitForFullDisplay + dispatchQueueWrapper:_dispatchQueueWrapper]; + + if ([ttdTracker startForTracer:tracer] == NO) { + self.currentTTDTracker = nil; + return nil; + } + self.currentTTDTracker = ttdTracker; + return ttdTracker; } - (void)viewControllerViewWillAppear:(UIViewController *)controller diff --git a/Sources/Sentry/include/SentryTimeToDisplayTracker.h b/Sources/Sentry/include/SentryTimeToDisplayTracker.h index 306ff1dcc9c..7d14a08e49b 100644 --- a/Sources/Sentry/include/SentryTimeToDisplayTracker.h +++ b/Sources/Sentry/include/SentryTimeToDisplayTracker.h @@ -29,8 +29,6 @@ SENTRY_NO_INIT waitForFullDisplay:(BOOL)waitForFullDisplay dispatchQueueWrapper:(SentryDispatchQueueWrapper *)dispatchQueueWrapper; -- (instancetype)initWithName:(NSString *)name waitForFullDisplay:(BOOL)waitForFullDisplay; - - (BOOL)startForTracer:(SentryTracer *)tracer; - (void)reportInitialDisplay; diff --git a/Sources/Sentry/include/SentryUIViewControllerPerformanceTracker.h b/Sources/Sentry/include/SentryUIViewControllerPerformanceTracker.h index 0355832393f..4b69b3359a0 100644 --- a/Sources/Sentry/include/SentryUIViewControllerPerformanceTracker.h +++ b/Sources/Sentry/include/SentryUIViewControllerPerformanceTracker.h @@ -6,6 +6,7 @@ @class SentryInAppLogic; @class SentryTimeToDisplayTracker; @class UIViewController; +@class SentryTracer; NS_ASSUME_NONNULL_BEGIN @@ -102,7 +103,9 @@ static NSString *const SENTRY_UI_PERFORMANCE_TRACKER_TTD_TRACKER - (void)reportFullyDisplayed; -- (void)setTimeToDisplayTracker:(SentryTimeToDisplayTracker *)ttdTracker; +- (nullable SentryTimeToDisplayTracker *)startTimeToDisplayTrackerForScreen:(NSString *)screenName + waitForFullDisplay:(BOOL)waitForFullDisplay + tracer:(SentryTracer *)tracer; @end diff --git a/Sources/SentrySwiftUI/SentryInternal/SentryInternal.h b/Sources/SentrySwiftUI/SentryInternal/SentryInternal.h index 8852d961d0d..2b7f4023cc4 100644 --- a/Sources/SentrySwiftUI/SentryInternal/SentryInternal.h +++ b/Sources/SentrySwiftUI/SentryInternal/SentryInternal.h @@ -98,7 +98,9 @@ typedef NS_ENUM(NSUInteger, SentrySpanStatus); - (void)reportFullyDisplayed; -- (void)setTimeToDisplayTracker:(SentryTimeToDisplayTracker *)ttdTracker; +- (nullable SentryTimeToDisplayTracker *)startTimeToDisplayTrackerForScreen:(NSString *)screenName + waitForFullDisplay:(BOOL)waitForFullDisplay + tracer:(SentryTracer *)tracer; @end diff --git a/Sources/SentrySwiftUI/SentryTracedView.swift b/Sources/SentrySwiftUI/SentryTracedView.swift index d764483841f..13f5aa562a6 100644 --- a/Sources/SentrySwiftUI/SentryTracedView.swift +++ b/Sources/SentrySwiftUI/SentryTracedView.swift @@ -10,6 +10,7 @@ import SwiftUI class SentryTraceViewModel { var tracker: SentryTimeToDisplayTracker? + var viewAppeared: Bool = false } /// A control to measure the performance of your views and send the result as a transaction to Sentry.io. @@ -40,7 +41,6 @@ class SentryTraceViewModel { /// @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6.0, *) public struct SentryTracedView: View { - @State private var viewAppeared = false @State private var viewModel = SentryTraceViewModel() let content: () -> Content @@ -89,11 +89,13 @@ public struct SentryTracedView: View { var trace: SentryTracer? var spanId: SpanId? - if !viewAppeared { + if viewModel.viewAppeared { trace = ensureTransactionExists() spanId = createAndPushBodySpan(transactionCreated: trace != nil) #if canImport(SwiftUI) && canImport(UIKit) && os(iOS) || os(tvOS) - if let trace = trace { startTTDTraker(for: trace) } + if let trace = trace { + startTTDTraker(for: trace) + } #endif } @@ -103,19 +105,16 @@ public struct SentryTracedView: View { return content() .onAppear { - if !viewAppeared { - viewAppeared = true + if !viewModel.viewAppeared { + viewModel.viewAppeared = true viewModel.tracker?.reportInitialDisplay() } } } + #if canImport(SwiftUI) && canImport(UIKit) && os(iOS) || os(tvOS) - private func startTTDTraker(for trace: SentryTracer) { - let tracker = SentryTimeToDisplayTracker(name: self.name, waitForFullDisplay: self.waitforFullDisplay) - SentryUIViewControllerPerformanceTracker.shared.setTimeToDisplay(tracker) - tracker.start(for: trace) - - viewModel.tracker = tracker + private func startTTDTraker(for tracer: SentryTracer) { + viewModel.tracker = SentryUIViewControllerPerformanceTracker.shared.startTimeToDisplay(forScreen: self.name, waitForFullDisplay: self.waitforFullDisplay, tracer: tracer) } #endif From facf4a3813160f0f77ffb187c9b40de56cd182fb Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Wed, 18 Dec 2024 09:23:35 +0000 Subject: [PATCH 23/29] Format code --- .../SentryUIViewControllerPerformanceTracker.m | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/Sources/Sentry/SentryUIViewControllerPerformanceTracker.m b/Sources/Sentry/SentryUIViewControllerPerformanceTracker.m index fab7fc261fd..afee027d0cd 100644 --- a/Sources/Sentry/SentryUIViewControllerPerformanceTracker.m +++ b/Sources/Sentry/SentryUIViewControllerPerformanceTracker.m @@ -163,7 +163,10 @@ - (void)startRootSpanFor:(UIViewController *)controller return; } - SentryTimeToDisplayTracker *ttdTracker = [self startTimeToDisplayTrackerForScreen:[SwiftDescriptor getObjectClassName:controller] waitForFullDisplay:self.alwaysWaitForFullDisplay tracer:(SentryTracer *)vcSpan]; + SentryTimeToDisplayTracker *ttdTracker = + [self startTimeToDisplayTrackerForScreen:[SwiftDescriptor getObjectClassName:controller] + waitForFullDisplay:self.alwaysWaitForFullDisplay + tracer:(SentryTracer *)vcSpan]; if (ttdTracker) { objc_setAssociatedObject(controller, &SENTRY_UI_PERFORMANCE_TRACKER_TTD_TRACKER, ttdTracker, @@ -189,19 +192,20 @@ - (void)reportFullyDisplayed - (SentryTimeToDisplayTracker *)startTimeToDisplayTrackerForScreen:(NSString *)screenName waitForFullDisplay:(BOOL)waitForFullDisplay - tracer:(SentryTracer *)tracer { + tracer:(SentryTracer *)tracer +{ [self.currentTTDTracker finishSpansIfNotFinished]; - SentryTimeToDisplayTracker *ttdTracker = [[SentryTimeToDisplayTracker alloc] - initWithName:screenName - waitForFullDisplay:waitForFullDisplay - dispatchQueueWrapper:_dispatchQueueWrapper]; + SentryTimeToDisplayTracker *ttdTracker = + [[SentryTimeToDisplayTracker alloc] initWithName:screenName + waitForFullDisplay:waitForFullDisplay + dispatchQueueWrapper:_dispatchQueueWrapper]; if ([ttdTracker startForTracer:tracer] == NO) { self.currentTTDTracker = nil; return nil; } - + self.currentTTDTracker = ttdTracker; return ttdTracker; } From d3bc4ca2fb3fba0ade5168751aae5a612e7b9f10 Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Wed, 18 Dec 2024 12:38:31 +0100 Subject: [PATCH 24/29] move code to viewModel --- Sources/SentrySwiftUI/SentryTracedView.swift | 102 ++++++++++--------- 1 file changed, 52 insertions(+), 50 deletions(-) diff --git a/Sources/SentrySwiftUI/SentryTracedView.swift b/Sources/SentrySwiftUI/SentryTracedView.swift index 13f5aa562a6..0783a026909 100644 --- a/Sources/SentrySwiftUI/SentryTracedView.swift +++ b/Sources/SentrySwiftUI/SentryTracedView.swift @@ -11,6 +11,50 @@ import SwiftUI class SentryTraceViewModel { var tracker: SentryTimeToDisplayTracker? var viewAppeared: Bool = false + +#if canImport(SwiftUI) && canImport(UIKit) && os(iOS) || os(tvOS) + func startTTDTraker(for tracer: SentryTracer, name: String, waitForFullDisplay: Bool) { + tracker = SentryUIViewControllerPerformanceTracker.shared.startTimeToDisplay(forScreen: name, waitForFullDisplay: waitForFullDisplay, tracer: tracer) + } +#endif + + func startRootTransaction(name: String, source: SentryTransactionNameSource, traceOrigin: String) -> SentryTracer? { + guard SentryPerformanceTracker.shared.activeSpanId() == nil else { return nil } + + let transactionId = SentryPerformanceTracker.shared.startSpan( + withName: name, + nameSource: source, + operation: "ui.load", + origin: traceOrigin + ) + SentryPerformanceTracker.shared.pushActiveSpan(transactionId) + + //According to Apple's documentation, the call to body needs to be fast + //and can be made many times in one frame. Therefore they don't use async code to process the view. + //Scheduling to finish the transaction at the end of the main loop seems the least hack solution right now. + //'onAppear' is not a suitable place to do this because it may happen before other view body property get called. + DispatchQueue.main.async { + self.finishSpan(transactionId) + } + + return SentryPerformanceTracker.shared.getSpan(transactionId) as? SentryTracer + } + + func createBodySpan(name: String, source: SentryTransactionNameSource, traceOrigin: String) -> SpanId { + let spanId = SentryPerformanceTracker.shared.startSpan( + withName: name, + nameSource: source, + operation: "ui.load", + origin: traceOrigin + ) + SentryPerformanceTracker.shared.pushActiveSpan(spanId) + return spanId + } + + func finishSpan(_ spanId: SpanId) { + SentryPerformanceTracker.shared.popActiveSpan() + SentryPerformanceTracker.shared.finishSpan(spanId) + } } /// A control to measure the performance of your views and send the result as a transaction to Sentry.io. @@ -89,18 +133,21 @@ public struct SentryTracedView: View { var trace: SentryTracer? var spanId: SpanId? - if viewModel.viewAppeared { - trace = ensureTransactionExists() - spanId = createAndPushBodySpan(transactionCreated: trace != nil) + if !viewModel.viewAppeared { + trace = viewModel.startRootTransaction(name: self.name, source: self.nameSource, traceOrigin: self.traceOrigin) + let name = trace != nil ? "\(name) - body" : name + spanId = viewModel.createBodySpan(name: name, source: self.nameSource, traceOrigin: self.traceOrigin) #if canImport(SwiftUI) && canImport(UIKit) && os(iOS) || os(tvOS) if let trace = trace { - startTTDTraker(for: trace) + viewModel.startTTDTraker(for: trace, name: name, waitForFullDisplay: waitforFullDisplay) } #endif } defer { - if let spanId = spanId { finishSpan(spanId) } + if let spanId = spanId { + viewModel.finishSpan(spanId) + } } return content() @@ -111,51 +158,6 @@ public struct SentryTracedView: View { } } } - -#if canImport(SwiftUI) && canImport(UIKit) && os(iOS) || os(tvOS) - private func startTTDTraker(for tracer: SentryTracer) { - viewModel.tracker = SentryUIViewControllerPerformanceTracker.shared.startTimeToDisplay(forScreen: self.name, waitForFullDisplay: self.waitforFullDisplay, tracer: tracer) - } -#endif - - private func ensureTransactionExists() -> SentryTracer? { - guard SentryPerformanceTracker.shared.activeSpanId() == nil else { return nil } - - let transactionId = SentryPerformanceTracker.shared.startSpan( - withName: name, - nameSource: nameSource, - operation: "ui.load", - origin: traceOrigin - ) - SentryPerformanceTracker.shared.pushActiveSpan(transactionId) - - //According to Apple's documentation, the call to body needs to be fast - //and can be made many times in one frame. Therefore they don't use async code to process the view. - //Scheduling to finish the transaction at the end of the main loop seems the least hack solution right now. - //'onAppear' is not a suitable place to do this because it may happen before other view body property get called. - DispatchQueue.main.async { - self.finishSpan(transactionId) - } - - return SentryPerformanceTracker.shared.getSpan(transactionId) as? SentryTracer - } - - private func createAndPushBodySpan(transactionCreated: Bool) -> SpanId { - let spanName = transactionCreated ? "\(name) - body" : name - let spanId = SentryPerformanceTracker.shared.startSpan( - withName: spanName, - nameSource: nameSource, - operation: "ui.load", - origin: traceOrigin - ) - SentryPerformanceTracker.shared.pushActiveSpan(spanId) - return spanId - } - - private func finishSpan(_ spanId: SpanId) { - SentryPerformanceTracker.shared.popActiveSpan() - SentryPerformanceTracker.shared.finishSpan(spanId) - } } @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6.0, *) From 036a741d58e1c51cde8327565e90ff6b36f49337 Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Wed, 18 Dec 2024 13:29:03 +0100 Subject: [PATCH 25/29] Update SentryTracedView.swift --- Sources/SentrySwiftUI/SentryTracedView.swift | 50 +++++++++++--------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/Sources/SentrySwiftUI/SentryTracedView.swift b/Sources/SentrySwiftUI/SentryTracedView.swift index 0783a026909..72d3221616f 100644 --- a/Sources/SentrySwiftUI/SentryTracedView.swift +++ b/Sources/SentrySwiftUI/SentryTracedView.swift @@ -18,7 +18,15 @@ class SentryTraceViewModel { } #endif - func startRootTransaction(name: String, source: SentryTransactionNameSource, traceOrigin: String) -> SentryTracer? { + func startSpan(name: String, source: SentryTransactionNameSource, traceOrigin: String)-> SpanId? { + guard !viewAppeared else { return nil } + + let trace = startRootTransaction(name: name, source: source, traceOrigin: traceOrigin) + let name = trace != nil ? "\(name) - body" : name + return createBodySpan(name: name, source: source, traceOrigin: traceOrigin) + } + + private func startRootTransaction(name: String, source: SentryTransactionNameSource, traceOrigin: String) -> SentryTracer? { guard SentryPerformanceTracker.shared.activeSpanId() == nil else { return nil } let transactionId = SentryPerformanceTracker.shared.startSpan( @@ -37,10 +45,18 @@ class SentryTraceViewModel { self.finishSpan(transactionId) } - return SentryPerformanceTracker.shared.getSpan(transactionId) as? SentryTracer + + let trace = SentryPerformanceTracker.shared.getSpan(transactionId) as? SentryTracer +#if canImport(SwiftUI) && canImport(UIKit) && os(iOS) || os(tvOS) + if let trace = trace { + startTTDTraker(for: trace, name: name, waitForFullDisplay: waitforFullDisplay) + } +#endif + + return trace } - func createBodySpan(name: String, source: SentryTransactionNameSource, traceOrigin: String) -> SpanId { + private func createBodySpan(name: String, source: SentryTransactionNameSource, traceOrigin: String) -> SpanId { let spanId = SentryPerformanceTracker.shared.startSpan( withName: name, nameSource: source, @@ -55,6 +71,12 @@ class SentryTraceViewModel { SentryPerformanceTracker.shared.popActiveSpan() SentryPerformanceTracker.shared.finishSpan(spanId) } + + func viewDidAppear() { + guard !viewAppeared else { return } + viewAppeared = true + tracker?.reportInitialDisplay() + } } /// A control to measure the performance of your views and send the result as a transaction to Sentry.io. @@ -130,19 +152,7 @@ public struct SentryTracedView: View { } public var body: some View { - var trace: SentryTracer? - var spanId: SpanId? - - if !viewModel.viewAppeared { - trace = viewModel.startRootTransaction(name: self.name, source: self.nameSource, traceOrigin: self.traceOrigin) - let name = trace != nil ? "\(name) - body" : name - spanId = viewModel.createBodySpan(name: name, source: self.nameSource, traceOrigin: self.traceOrigin) -#if canImport(SwiftUI) && canImport(UIKit) && os(iOS) || os(tvOS) - if let trace = trace { - viewModel.startTTDTraker(for: trace, name: name, waitForFullDisplay: waitforFullDisplay) - } -#endif - } + var spanId: SpanId? = viewModel.startSpan(name: self.name, source: self.nameSource, traceOrigin: self.traceOrigin) defer { if let spanId = spanId { @@ -150,13 +160,7 @@ public struct SentryTracedView: View { } } - return content() - .onAppear { - if !viewModel.viewAppeared { - viewModel.viewAppeared = true - viewModel.tracker?.reportInitialDisplay() - } - } + return content().onAppear(perform: viewModel.viewDidAppear) } } From b4ddf1236c3f1fe4225fb77a26902cf861d547b8 Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Wed, 18 Dec 2024 13:37:04 +0100 Subject: [PATCH 26/29] Update SentryTracedView.swift --- Sources/SentrySwiftUI/SentryTracedView.swift | 51 ++++++++++---------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/Sources/SentrySwiftUI/SentryTracedView.swift b/Sources/SentrySwiftUI/SentryTracedView.swift index 72d3221616f..7f4f56331e6 100644 --- a/Sources/SentrySwiftUI/SentryTracedView.swift +++ b/Sources/SentrySwiftUI/SentryTracedView.swift @@ -12,18 +12,23 @@ class SentryTraceViewModel { var tracker: SentryTimeToDisplayTracker? var viewAppeared: Bool = false -#if canImport(SwiftUI) && canImport(UIKit) && os(iOS) || os(tvOS) - func startTTDTraker(for tracer: SentryTracer, name: String, waitForFullDisplay: Bool) { - tracker = SentryUIViewControllerPerformanceTracker.shared.startTimeToDisplay(forScreen: name, waitForFullDisplay: waitForFullDisplay, tracer: tracer) + let name: String + let nameSource: SentryTransactionNameSource + let waitForFullDisplay: Bool + let traceOrigin = "auto.ui.swift_ui" + + init(name: String, nameSource: SentryTransactionNameSource, waitForFullDisplay: Bool) { + self.name = name + self.nameSource = nameSource + self.waitForFullDisplay = waitForFullDisplay } -#endif - func startSpan(name: String, source: SentryTransactionNameSource, traceOrigin: String)-> SpanId? { + func startSpan()-> SpanId? { guard !viewAppeared else { return nil } - let trace = startRootTransaction(name: name, source: source, traceOrigin: traceOrigin) + let trace = startRootTransaction(name: name, source: nameSource, traceOrigin: traceOrigin) let name = trace != nil ? "\(name) - body" : name - return createBodySpan(name: name, source: source, traceOrigin: traceOrigin) + return createBodySpan(name: name, source: nameSource, traceOrigin: traceOrigin) } private func startRootTransaction(name: String, source: SentryTransactionNameSource, traceOrigin: String) -> SentryTracer? { @@ -45,15 +50,13 @@ class SentryTraceViewModel { self.finishSpan(transactionId) } - - let trace = SentryPerformanceTracker.shared.getSpan(transactionId) as? SentryTracer + let tracer = SentryPerformanceTracker.shared.getSpan(transactionId) as? SentryTracer #if canImport(SwiftUI) && canImport(UIKit) && os(iOS) || os(tvOS) - if let trace = trace { - startTTDTraker(for: trace, name: name, waitForFullDisplay: waitforFullDisplay) + if let tracer = tracer { + tracker = SentryUIViewControllerPerformanceTracker.shared.startTimeToDisplay(forScreen: name, waitForFullDisplay: waitForFullDisplay, tracer: tracer) } #endif - - return trace + return tracer } private func createBodySpan(name: String, source: SentryTransactionNameSource, traceOrigin: String) -> SpanId { @@ -107,16 +110,10 @@ class SentryTraceViewModel { /// @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6.0, *) public struct SentryTracedView: View { - @State private var viewModel = SentryTraceViewModel() - + @State private var viewModel : SentryTraceViewModel let content: () -> Content - let name: String - let nameSource: SentryTransactionNameSource - let traceOrigin = "auto.ui.swift_ui" #if canImport(SwiftUI) && canImport(UIKit) && os(iOS) || os(tvOS) - let waitforFullDisplay: Bool - /// Creates a view that measures the performance of its `content`. /// /// - Parameter viewName: The name that will be used for the span, if nil we try to get the name of the content class. @@ -125,9 +122,10 @@ public struct SentryTracedView: View { /// - Parameter content: The content that you want to track the performance public init(_ viewName: String? = nil, waitForFullDisplay: Bool? = nil, @ViewBuilder content: @escaping () -> Content) { self.content = content - self.name = viewName ?? SentryTracedView.extractName(content: Content.self) - self.nameSource = viewName == nil ? .component : .custom - self.waitforFullDisplay = waitForFullDisplay ?? SentrySDK.options?.enableTimeToFullDisplayTracing ?? false + let name = viewName ?? SentryTracedView.extractName(content: Content.self) + let nameSource = viewName == nil ? SentryTransactionNameSource.component : SentryTransactionNameSource.custom + let waitforFullDisplay = waitForFullDisplay ?? SentrySDK.options?.enableTimeToFullDisplayTracing ?? false + self.viewModel = SentryTraceViewModel(name: name, nameSource: nameSource, waitForFullDisplay: waitforFullDisplay) } #else /// Creates a view that measures the performance of its `content`. @@ -136,8 +134,9 @@ public struct SentryTracedView: View { /// - Parameter content: The content that you want to track the performance public init(_ viewName: String? = nil, @ViewBuilder content: @escaping () -> Content) { self.content = content - self.name = viewName ?? SentryTracedView.extractName(content: Content.self) - self.nameSource = viewName == nil ? .component : .custom + let name = viewName ?? SentryTracedView.extractName(content: Content.self) + let nameSource = viewName == nil ? SentryTransactionNameSource.component : SentryTransactionNameSource.custom + self.viewModel = SentryTraceViewModel(name: name, nameSource: nameSource, waitForFullDisplay: false) } #endif @@ -152,7 +151,7 @@ public struct SentryTracedView: View { } public var body: some View { - var spanId: SpanId? = viewModel.startSpan(name: self.name, source: self.nameSource, traceOrigin: self.traceOrigin) + let spanId = viewModel.startSpan() defer { if let spanId = spanId { From 4c749b48d25c7e15afd38ba7aba915c39805148d Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Thu, 19 Dec 2024 14:07:50 +0100 Subject: [PATCH 27/29] More change --- .github/workflows/test.yml | 14 +- .../iOS-SwiftUI.xcodeproj/project.pbxproj | 19 +- Sentry.xcodeproj/project.pbxproj | 323 +++++++++++++++++- .../xcschemes/SentrySwiftUI.xcscheme | 12 +- .../SentryInternal/SentryInternal.h | 13 +- Sources/SentrySwiftUI/SentryTracedView.swift | 35 +- .../SentryRedactModifierTests.swift | 0 .../SentryTests-Bridging-Header.h | 9 + .../SentryTraceViewModelTest.swift | 90 +++++ scripts/xcode-test.sh | 7 +- 10 files changed, 483 insertions(+), 39 deletions(-) rename {Samples/iOS-SwiftUI/iOS-SwiftUI-UITests => Tests/SentrySwiftUITests}/SentryRedactModifierTests.swift (100%) create mode 100644 Tests/SentrySwiftUITests/SentryTests-Bridging-Header.h create mode 100644 Tests/SentrySwiftUITests/SentryTraceViewModelTest.swift diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 70b86d93f92..f2728c13fb9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -66,7 +66,7 @@ jobs: raw-test-output.log unit-tests: - name: Unit ${{matrix.platform}} - Xcode ${{matrix.xcode}} - OS ${{matrix.test-destination-os}} + name: Unit ${{matrix.platform}} - Xcode ${{matrix.xcode}} - OS ${{matrix.test-destination-os}} ${{matrix.scheme}} runs-on: ${{matrix.runs-on}} timeout-minutes: 20 needs: build-test-server @@ -115,6 +115,14 @@ jobs: platform: "tvOS" xcode: "15.4" test-destination-os: "17.5" + + # iOS 17 + - runs-on: macos-14 + platform: "iOS" + xcode: "15.4" + test-destination-os: "17.2" + device: "iPhone 15" + scheme: "SentrySwiftUI" steps: - uses: actions/checkout@v4 @@ -140,14 +148,14 @@ jobs: # We split building and running tests in two steps so we know how long running the tests takes. - name: Build tests id: build_tests - run: ./scripts/xcode-test.sh ${{matrix.platform}} ${{matrix.test-destination-os}} $GITHUB_REF_NAME ci build-for-testing "${{matrix.device}}" TestCI + run: ./scripts/xcode-test.sh ${{matrix.platform}} ${{matrix.test-destination-os}} $GITHUB_REF_NAME ci build-for-testing "${{matrix.device}}" TestCI ${{matrix.scheme}} - name: Run tests # We call a script with the platform so the destination # passed to xcodebuild doesn't end up in the job name, # because GitHub Actions don't provide an easy way of # manipulating string in expressions. - run: ./scripts/xcode-test.sh ${{matrix.platform}} ${{matrix.test-destination-os}} $GITHUB_REF_NAME ci test-without-building "${{matrix.device}}" TestCI + run: ./scripts/xcode-test.sh ${{matrix.platform}} ${{matrix.test-destination-os}} $GITHUB_REF_NAME ci test-without-building "${{matrix.device}}" TestCI ${{matrix.scheme}} - name: Slowest Tests if: ${{ always() }} diff --git a/Samples/iOS-SwiftUI/iOS-SwiftUI.xcodeproj/project.pbxproj b/Samples/iOS-SwiftUI/iOS-SwiftUI.xcodeproj/project.pbxproj index 7149192537c..3f480ed0958 100644 --- a/Samples/iOS-SwiftUI/iOS-SwiftUI.xcodeproj/project.pbxproj +++ b/Samples/iOS-SwiftUI/iOS-SwiftUI.xcodeproj/project.pbxproj @@ -23,7 +23,6 @@ D85388D12980222500B63908 /* UIKitScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85388D02980222500B63908 /* UIKitScreen.swift */; }; D8F0F3C02D0068A100826CE3 /* SentrySwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D8BBD38B2901AE400011F850 /* SentrySwiftUI.framework */; }; D8F0F3C12D0068A100826CE3 /* SentrySwiftUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D8BBD38B2901AE400011F850 /* SentrySwiftUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - D8F0F3C42D0068AB00826CE3 /* SentryRedactModifierTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F0F3C32D0068AB00826CE3 /* SentryRedactModifierTests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -111,6 +110,13 @@ remoteGlobalIDString = 63AA76651EB8CB2F00D153DE; remoteInfo = SentryTests; }; + D833D61A2D13216300961E7A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 84D4FEA828ECD52700EDAAFE /* Sentry.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = D833D60D2D1320B500961E7A; + remoteInfo = SentrySwiftUITests; + }; D8BBD38A2901AE400011F850 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 84D4FEA828ECD52700EDAAFE /* Sentry.xcodeproj */; @@ -173,7 +179,6 @@ D8A22A7C2915231F006907D9 /* SentrySpanStatus.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SentrySpanStatus.h; sourceTree = ""; }; D8A22A7D2915238A006907D9 /* SentryTracer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryTracer.h; path = ../../../../Sources/Sentry/include/SentryTracer.h; sourceTree = ""; }; D8A22A7E2915238A006907D9 /* SentryPerformanceTracker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryPerformanceTracker.h; path = ../../../../Sources/Sentry/include/SentryPerformanceTracker.h; sourceTree = ""; }; - D8F0F3C32D0068AB00826CE3 /* SentryRedactModifierTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryRedactModifierTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -206,6 +211,7 @@ 8425DE232B52241000113FEF /* SentryProfilerTests.xctest */, 8425DE252B52241000113FEF /* libSentryTestUtils.a */, 8425DE272B52241000113FEF /* SentryTestUtilsDynamic.framework */, + D833D61B2D13216300961E7A /* libSentrySwiftUITests.a */, ); name = Products; sourceTree = ""; @@ -214,7 +220,6 @@ isa = PBXGroup; children = ( 7B64385926A6C0A6000D0F65 /* LaunchUITests.swift */, - D8F0F3C32D0068AB00826CE3 /* SentryRedactModifierTests.swift */, 7B64385B26A6C0A6000D0F65 /* Info.plist */, ); path = "iOS-SwiftUI-UITests"; @@ -480,6 +485,13 @@ remoteRef = 84D4FEB328ECD52E00EDAAFE /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; + D833D61B2D13216300961E7A /* libSentrySwiftUITests.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libSentrySwiftUITests.a; + remoteRef = D833D61A2D13216300961E7A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; D8BBD38B2901AE400011F850 /* SentrySwiftUI.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; @@ -553,7 +565,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - D8F0F3C42D0068AB00826CE3 /* SentryRedactModifierTests.swift in Sources */, 7B64385A26A6C0A6000D0F65 /* LaunchUITests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index 32ad5a386c2..f584ca25c04 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -812,6 +812,11 @@ D82915632C85EF0C00A6CDD4 /* SentryViewPhotographerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D82915622C85EF0C00A6CDD4 /* SentryViewPhotographerTests.swift */; }; D8292D7D2A39A027009872F7 /* UrlSanitizedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8292D7C2A39A027009872F7 /* UrlSanitizedTests.swift */; }; D82DD1CD2BEEB1A0001AB556 /* SentrySRDefaultBreadcrumbConverterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D82DD1CC2BEEB1A0001AB556 /* SentrySRDefaultBreadcrumbConverterTests.swift */; }; + D833D72A2D1321C100961E7A /* Resources in Resources */ = {isa = PBXBuildFile; fileRef = 630C01951EC341D600C52CEF /* Resources */; }; + D833D73B2D1321FF00961E7A /* SentryRedactModifierTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D833D6152D13215900961E7A /* SentryRedactModifierTests.swift */; }; + D833D73C2D13220500961E7A /* SentryTraceViewModelTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = D833D6162D13215900961E7A /* SentryTraceViewModelTest.swift */; }; + D833D7512D13263800961E7A /* SentrySwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D8199DAA29376E9B0074249E /* SentrySwiftUI.framework */; }; + D833D7522D13263800961E7A /* SentrySwiftUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D8199DAA29376E9B0074249E /* SentrySwiftUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; D8370B6A273DF1E900F66E2D /* SentryNSURLSessionTaskSearch.m in Sources */ = {isa = PBXBuildFile; fileRef = D8370B68273DF1E900F66E2D /* SentryNSURLSessionTaskSearch.m */; }; D8370B6C273DF20F00F66E2D /* SentryNSURLSessionTaskSearch.h in Headers */ = {isa = PBXBuildFile; fileRef = D8370B6B273DF20F00F66E2D /* SentryNSURLSessionTaskSearch.h */; }; D83D079B2B7F9D1C00CC9674 /* SentryMsgPackSerializer.h in Headers */ = {isa = PBXBuildFile; fileRef = D83D07992B7F9D1C00CC9674 /* SentryMsgPackSerializer.h */; }; @@ -967,6 +972,13 @@ remoteGlobalIDString = 63AA759A1EB8AEF500D153DE; remoteInfo = Sentry; }; + D833D7382D1321F700961E7A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 6327C5CA1EB8A783004E799B /* Project object */; + proxyType = 1; + remoteGlobalIDString = D8199DA929376E9B0074249E; + remoteInfo = SentrySwiftUI; + }; D84DAD5B2B1742C1003CF120 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 6327C5CA1EB8A783004E799B /* Project object */; @@ -977,6 +989,17 @@ /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ + D833D7532D13263800961E7A /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + D833D7522D13263800961E7A /* SentrySwiftUI.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; D84DAD5D2B1742C1003CF120 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -1880,6 +1903,10 @@ D8292D7A2A38AF04009872F7 /* HTTPHeaderSanitizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPHeaderSanitizer.swift; sourceTree = ""; }; D8292D7C2A39A027009872F7 /* UrlSanitizedTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UrlSanitizedTests.swift; sourceTree = ""; }; D82DD1CC2BEEB1A0001AB556 /* SentrySRDefaultBreadcrumbConverterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentrySRDefaultBreadcrumbConverterTests.swift; sourceTree = ""; }; + D833D6152D13215900961E7A /* SentryRedactModifierTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryRedactModifierTests.swift; sourceTree = ""; }; + D833D6162D13215900961E7A /* SentryTraceViewModelTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryTraceViewModelTest.swift; sourceTree = ""; }; + D833D7342D1321C100961E7A /* SentrySwiftUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SentrySwiftUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + D833D74D2D1323F800961E7A /* SentryTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SentryTests-Bridging-Header.h"; sourceTree = ""; }; D8370B68273DF1E900F66E2D /* SentryNSURLSessionTaskSearch.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryNSURLSessionTaskSearch.m; sourceTree = ""; }; D8370B6B273DF20F00F66E2D /* SentryNSURLSessionTaskSearch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryNSURLSessionTaskSearch.h; path = include/SentryNSURLSessionTaskSearch.h; sourceTree = ""; }; D83D07992B7F9D1C00CC9674 /* SentryMsgPackSerializer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryMsgPackSerializer.h; path = include/SentryMsgPackSerializer.h; sourceTree = ""; }; @@ -2065,6 +2092,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + D833D7252D1321C100961E7A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D833D7512D13263800961E7A /* SentrySwiftUI.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; D84DAD4A2B17428D003CF120 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -2312,6 +2347,7 @@ 8431EFD929B27B1100D8DC56 /* SentryProfilerTests.xctest */, 8431F00A29B284F200D8DC56 /* libSentryTestUtils.a */, D84DAD4D2B17428D003CF120 /* SentryTestUtilsDynamic.framework */, + D833D7342D1321C100961E7A /* SentrySwiftUITests.xctest */, ); name = Products; sourceTree = ""; @@ -2483,6 +2519,7 @@ 63AA75931EB8AEDB00D153DE /* SentryTests */, 8431EFDB29B27B3D00D8DC56 /* SentryProfilerTests */, D8F01DE32A125D7B008F4996 /* HybridSDKTest */, + D833D60F2D1320DF00961E7A /* SentrySwiftUITests */, ); path = Tests; sourceTree = ""; @@ -3711,6 +3748,16 @@ name = Tools; sourceTree = ""; }; + D833D60F2D1320DF00961E7A /* SentrySwiftUITests */ = { + isa = PBXGroup; + children = ( + D833D6152D13215900961E7A /* SentryRedactModifierTests.swift */, + D833D6162D13215900961E7A /* SentryTraceViewModelTest.swift */, + D833D74D2D1323F800961E7A /* SentryTests-Bridging-Header.h */, + ); + path = SentrySwiftUITests; + sourceTree = ""; + }; D84DAD4E2B17428D003CF120 /* SentryTestUtilsDynamic */ = { isa = PBXGroup; children = ( @@ -4389,6 +4436,27 @@ productReference = D8199DAA29376E9B0074249E /* SentrySwiftUI.framework */; productType = "com.apple.product-type.framework"; }; + D833D61E2D1321C100961E7A /* SentrySwiftUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = D833D72D2D1321C100961E7A /* Build configuration list for PBXNativeTarget "SentrySwiftUITests" */; + buildPhases = ( + D833D6232D1321C100961E7A /* Sources */, + D833D7252D1321C100961E7A /* Frameworks */, + D833D7292D1321C100961E7A /* Resources */, + D833D7532D13263800961E7A /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + D833D7392D1321F700961E7A /* PBXTargetDependency */, + ); + name = SentrySwiftUITests; + packageProductDependencies = ( + ); + productName = "Tests-iOS"; + productReference = D833D7342D1321C100961E7A /* SentrySwiftUITests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; D84DAD4C2B17428D003CF120 /* SentryTestUtilsDynamic */ = { isa = PBXNativeTarget; buildConfigurationList = D84DAD512B17428D003CF120 /* Build configuration list for PBXNativeTarget "SentryTestUtilsDynamic" */; @@ -4464,6 +4532,7 @@ 8431EECF29B27B1100D8DC56 /* SentryProfilerTests */, 8431F00929B284F200D8DC56 /* SentryTestUtils */, D84DAD4C2B17428D003CF120 /* SentryTestUtilsDynamic */, + D833D61E2D1321C100961E7A /* SentrySwiftUITests */, ); }; /* End PBXProject section */ @@ -4500,6 +4569,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + D833D7292D1321C100961E7A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D833D72A2D1321C100961E7A /* Resources in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; D84DAD4B2B17428D003CF120 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -5153,6 +5230,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + D833D6232D1321C100961E7A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D833D73B2D1321FF00961E7A /* SentryRedactModifierTests.swift in Sources */, + D833D73C2D13220500961E7A /* SentryTraceViewModelTest.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; D84DAD492B17428D003CF120 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -5184,6 +5270,11 @@ target = 63AA759A1EB8AEF500D153DE /* Sentry */; targetProxy = D8199DC429376FC10074249E /* PBXContainerItemProxy */; }; + D833D7392D1321F700961E7A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D8199DA929376E9B0074249E /* SentrySwiftUI */; + targetProxy = D833D7382D1321F700961E7A /* PBXContainerItemProxy */; + }; D84DAD5C2B1742C1003CF120 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = D84DAD4C2B17428D003CF120 /* SentryTestUtilsDynamic */; @@ -5732,7 +5823,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = 63AA76AE1EB9D5CD00D153DE /* SentryTests.xcconfig */; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_ENABLE_MODULES = YES; CLANG_WARN_BOOL_CONVERSION = YES; @@ -5794,7 +5884,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = 63AA76AE1EB9D5CD00D153DE /* SentryTests.xcconfig */; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_ENABLE_MODULES = YES; CLANG_WARN_BOOL_CONVERSION = YES; @@ -5825,7 +5914,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = 63AA76AE1EB9D5CD00D153DE /* SentryTests.xcconfig */; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_ENABLE_MODULES = YES; CLANG_WARN_BOOL_CONVERSION = YES; @@ -5856,7 +5944,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = 63AA76AE1EB9D5CD00D153DE /* SentryTests.xcconfig */; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_ENABLE_MODULES = YES; CLANG_WARN_BOOL_CONVERSION = YES; @@ -5887,7 +5974,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = 63AA76AE1EB9D5CD00D153DE /* SentryTests.xcconfig */; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_ENABLE_MODULES = YES; CLANG_WARN_BOOL_CONVERSION = YES; @@ -6206,7 +6292,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = 63AA76AE1EB9D5CD00D153DE /* SentryTests.xcconfig */; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_ENABLE_MODULES = YES; CLANG_WARN_BOOL_CONVERSION = YES; @@ -6571,6 +6656,219 @@ }; name = Release; }; + D833D72E2D1321C100961E7A /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 63AA76AE1EB9D5CD00D153DE /* SentryTests.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = ""; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = ""; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + OTHER_SWIFT_FLAGS = "-DSENTRY_USE_UIKIT"; + PRODUCT_BUNDLE_IDENTIFIER = io.sentry.Sentry.tests; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; + SWIFT_INCLUDE_PATHS = ""; + SWIFT_OBJC_BRIDGING_HEADER = "Tests/SentrySwiftUITests/SentryTests-Bridging-Header.h"; + SWIFT_OBJC_INTEROP_MODE = objc; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_SWIFT3_OBJC_INFERENCE = Off; + SWIFT_VERSION = 5.0; + TVOS_DEPLOYMENT_TARGET = 13.0; + }; + name = Debug; + }; + D833D72F2D1321C100961E7A /* DebugWithoutUIKit */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 63AA76AE1EB9D5CD00D153DE /* SentryTests.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = ""; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = ""; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = io.sentry.Sentry.tests; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; + SWIFT_INCLUDE_PATHS = ""; + SWIFT_OBJC_BRIDGING_HEADER = "Tests/SentrySwiftUITests/SentryTests-Bridging-Header.h"; + SWIFT_OBJC_INTEROP_MODE = objc; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_SWIFT3_OBJC_INFERENCE = Off; + SWIFT_VERSION = 5.0; + TVOS_DEPLOYMENT_TARGET = 13.0; + }; + name = DebugWithoutUIKit; + }; + D833D7302D1321C100961E7A /* Test */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 63AA76AE1EB9D5CD00D153DE /* SentryTests.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = ""; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = ""; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + OTHER_SWIFT_FLAGS = "-DSENTRY_USE_UIKIT"; + PRODUCT_BUNDLE_IDENTIFIER = io.sentry.Sentry.tests; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; + SWIFT_INCLUDE_PATHS = ""; + SWIFT_OBJC_BRIDGING_HEADER = "Tests/SentrySwiftUITests/SentryTests-Bridging-Header.h"; + SWIFT_OBJC_INTEROP_MODE = objc; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_SWIFT3_OBJC_INFERENCE = Off; + SWIFT_VERSION = 5.0; + TVOS_DEPLOYMENT_TARGET = 13.0; + }; + name = Test; + }; + D833D7312D1321C100961E7A /* TestCI */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 63AA76AE1EB9D5CD00D153DE /* SentryTests.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = ""; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = ""; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + OTHER_SWIFT_FLAGS = "-DSENTRY_USE_UIKIT"; + PRODUCT_BUNDLE_IDENTIFIER = io.sentry.Sentry.tests; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; + SWIFT_INCLUDE_PATHS = ""; + SWIFT_OBJC_BRIDGING_HEADER = "Tests/SentrySwiftUITests/SentryTests-Bridging-Header.h"; + SWIFT_OBJC_INTEROP_MODE = objc; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_SWIFT3_OBJC_INFERENCE = Off; + SWIFT_VERSION = 5.0; + TVOS_DEPLOYMENT_TARGET = 13.0; + }; + name = TestCI; + }; + D833D7322D1321C100961E7A /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 63AA76AE1EB9D5CD00D153DE /* SentryTests.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = ""; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = ""; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + OTHER_SWIFT_FLAGS = "-DSENTRY_USE_UIKIT"; + PRODUCT_BUNDLE_IDENTIFIER = io.sentry.Sentry.tests; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; + SWIFT_INCLUDE_PATHS = ""; + SWIFT_OBJC_BRIDGING_HEADER = "Tests/SentrySwiftUITests/SentryTests-Bridging-Header.h"; + SWIFT_OBJC_INTEROP_MODE = objc; + SWIFT_SWIFT3_OBJC_INFERENCE = Off; + SWIFT_VERSION = 5.0; + TVOS_DEPLOYMENT_TARGET = 13.0; + }; + name = Release; + }; + D833D7332D1321C100961E7A /* ReleaseWithoutUIKit */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 63AA76AE1EB9D5CD00D153DE /* SentryTests.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_ENABLE_MODULES = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Manual; + DEVELOPMENT_TEAM = ""; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = ""; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + OTHER_SWIFT_FLAGS = "-DSENTRY_USE_UIKIT"; + PRODUCT_BUNDLE_IDENTIFIER = io.sentry.Sentry.tests; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = ""; + SWIFT_INCLUDE_PATHS = ""; + SWIFT_OBJC_BRIDGING_HEADER = "Tests/SentrySwiftUITests/SentryTests-Bridging-Header.h"; + SWIFT_OBJC_INTEROP_MODE = objc; + SWIFT_SWIFT3_OBJC_INFERENCE = Off; + SWIFT_VERSION = 5.0; + TVOS_DEPLOYMENT_TARGET = 13.0; + }; + name = ReleaseWithoutUIKit; + }; D84DAD522B17428D003CF120 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -6974,6 +7272,19 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + D833D72D2D1321C100961E7A /* Build configuration list for PBXNativeTarget "SentrySwiftUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D833D72E2D1321C100961E7A /* Debug */, + D833D72F2D1321C100961E7A /* DebugWithoutUIKit */, + D833D7302D1321C100961E7A /* Test */, + D833D7312D1321C100961E7A /* TestCI */, + D833D7322D1321C100961E7A /* Release */, + D833D7332D1321C100961E7A /* ReleaseWithoutUIKit */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; D84DAD512B17428D003CF120 /* Build configuration list for PBXNativeTarget "SentryTestUtilsDynamic" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/Sentry.xcodeproj/xcshareddata/xcschemes/SentrySwiftUI.xcscheme b/Sentry.xcodeproj/xcshareddata/xcschemes/SentrySwiftUI.xcscheme index 8ed1c486c91..cc826bfbcc0 100644 --- a/Sentry.xcodeproj/xcshareddata/xcschemes/SentrySwiftUI.xcscheme +++ b/Sentry.xcodeproj/xcshareddata/xcschemes/SentrySwiftUI.xcscheme @@ -23,11 +23,21 @@ + + + + +@end +#endif + NS_ASSUME_NONNULL_BEGIN typedef NS_ENUM(NSInteger, SentryTransactionNameSource); @class SentrySpanId; -@class SentrySpan; @class SentryDispatchQueueWrapper; -@interface SentryTracer : NSObject -@end - typedef NS_ENUM(NSUInteger, SentrySpanStatus); @interface SentryPerformanceTracker : NSObject diff --git a/Sources/SentrySwiftUI/SentryTracedView.swift b/Sources/SentrySwiftUI/SentryTracedView.swift index 7f4f56331e6..8a26792493e 100644 --- a/Sources/SentrySwiftUI/SentryTracedView.swift +++ b/Sources/SentrySwiftUI/SentryTracedView.swift @@ -9,8 +9,9 @@ import SwiftUI #endif class SentryTraceViewModel { - var tracker: SentryTimeToDisplayTracker? - var viewAppeared: Bool = false + private var transactionId: SpanId? + private var viewAppeared: Bool = false + private var tracker: SentryTimeToDisplayTracker? let name: String let nameSource: SentryTransactionNameSource @@ -23,32 +24,25 @@ class SentryTraceViewModel { self.waitForFullDisplay = waitForFullDisplay } - func startSpan()-> SpanId? { + func startSpan() -> SpanId? { guard !viewAppeared else { return nil } - let trace = startRootTransaction(name: name, source: nameSource, traceOrigin: traceOrigin) + let trace = startRootTransaction() let name = trace != nil ? "\(name) - body" : name - return createBodySpan(name: name, source: nameSource, traceOrigin: traceOrigin) + return createBodySpan(name: name) } - private func startRootTransaction(name: String, source: SentryTransactionNameSource, traceOrigin: String) -> SentryTracer? { + private func startRootTransaction() -> SentryTracer? { guard SentryPerformanceTracker.shared.activeSpanId() == nil else { return nil } let transactionId = SentryPerformanceTracker.shared.startSpan( withName: name, - nameSource: source, + nameSource: nameSource, operation: "ui.load", origin: traceOrigin ) SentryPerformanceTracker.shared.pushActiveSpan(transactionId) - - //According to Apple's documentation, the call to body needs to be fast - //and can be made many times in one frame. Therefore they don't use async code to process the view. - //Scheduling to finish the transaction at the end of the main loop seems the least hack solution right now. - //'onAppear' is not a suitable place to do this because it may happen before other view body property get called. - DispatchQueue.main.async { - self.finishSpan(transactionId) - } + self.transactionId = transactionId let tracer = SentryPerformanceTracker.shared.getSpan(transactionId) as? SentryTracer #if canImport(SwiftUI) && canImport(UIKit) && os(iOS) || os(tvOS) @@ -59,10 +53,10 @@ class SentryTraceViewModel { return tracer } - private func createBodySpan(name: String, source: SentryTransactionNameSource, traceOrigin: String) -> SpanId { + private func createBodySpan(name: String) -> SpanId { let spanId = SentryPerformanceTracker.shared.startSpan( withName: name, - nameSource: source, + nameSource: nameSource, operation: "ui.load", origin: traceOrigin ) @@ -79,6 +73,11 @@ class SentryTraceViewModel { guard !viewAppeared else { return } viewAppeared = true tracker?.reportInitialDisplay() + + if let transactionId = transactionId { + self.finishSpan(transactionId) + SentryPerformanceTracker.shared.popActiveSpan() + } } } @@ -110,7 +109,7 @@ class SentryTraceViewModel { /// @available(iOS 13, macOS 10.15, tvOS 13, watchOS 6.0, *) public struct SentryTracedView: View { - @State private var viewModel : SentryTraceViewModel + @State private var viewModel: SentryTraceViewModel let content: () -> Content #if canImport(SwiftUI) && canImport(UIKit) && os(iOS) || os(tvOS) diff --git a/Samples/iOS-SwiftUI/iOS-SwiftUI-UITests/SentryRedactModifierTests.swift b/Tests/SentrySwiftUITests/SentryRedactModifierTests.swift similarity index 100% rename from Samples/iOS-SwiftUI/iOS-SwiftUI-UITests/SentryRedactModifierTests.swift rename to Tests/SentrySwiftUITests/SentryRedactModifierTests.swift diff --git a/Tests/SentrySwiftUITests/SentryTests-Bridging-Header.h b/Tests/SentrySwiftUITests/SentryTests-Bridging-Header.h new file mode 100644 index 00000000000..a8f1ccd050f --- /dev/null +++ b/Tests/SentrySwiftUITests/SentryTests-Bridging-Header.h @@ -0,0 +1,9 @@ +#import "SentryHub+Private.h" +#import "SentryPerformanceTracker.h" +#import "SentrySDK+Private.h" +#import "SentrySDK+Tests.h" +#import "SentryTracer.h" + +@interface SentryPerformanceTracker () +- (void)clear; +@end diff --git a/Tests/SentrySwiftUITests/SentryTraceViewModelTest.swift b/Tests/SentrySwiftUITests/SentryTraceViewModelTest.swift new file mode 100644 index 00000000000..8026bf1a1fa --- /dev/null +++ b/Tests/SentrySwiftUITests/SentryTraceViewModelTest.swift @@ -0,0 +1,90 @@ +#if canImport(UIKit) && canImport(SwiftUI) +@testable import Sentry +@testable import SentrySwiftUI +import XCTest + +class SentryTraceViewModelTestCase: XCTestCase { + + override func tearDown() { + super.tearDown() + SentryPerformanceTracker.shared.clear() + } + + func testCreateTransaction() throws { + let option = Options() + SentrySDK.setCurrentHub(SentryHub(client: SentryClient(options: option), andScope: nil)) + + let viewModel = SentryTraceViewModel(name: "TestView", nameSource: .component, waitForFullDisplay: false) + let spanId = viewModel.startSpan() + + let tracer = try XCTUnwrap(SentrySDK.span as? SentryTracer) + + XCTAssertEqual(tracer.transactionContext.name, "TestView") + XCTAssertEqual(tracer.children.first?.spanId, spanId) + XCTAssertEqual(tracer.children.first?.spanDescription, "TestView - body") + } + + func testRootTransactionStarted() throws { + let option = Options() + SentrySDK.setCurrentHub(SentryHub(client: SentryClient(options: option), andScope: nil)) + + let viewModel = SentryTraceViewModel(name: "RootTransactionTest", nameSource: .component, waitForFullDisplay: true) + _ = viewModel.startSpan() + + let tracer = try XCTUnwrap(SentrySDK.span as? SentryTracer) + XCTAssertEqual(tracer.transactionContext.name, "RootTransactionTest") + XCTAssertEqual(tracer.transactionContext.operation, "ui.load") + XCTAssertEqual(tracer.transactionContext.origin, "auto.ui.swift_ui") + } + + func testNoRootTransactionForCurrentTransactionRunning() throws { + let option = Options() + SentrySDK.setCurrentHub(SentryHub(client: SentryClient(options: option), andScope: nil)) + + let testSpan = SentryPerformanceTracker.shared.startSpan(withName: "Test Root", nameSource: .component, operation: "Testing", origin: "Test") + SentryPerformanceTracker.shared.pushActiveSpan(testSpan) + + let viewModel = SentryTraceViewModel(name: "ViewContent", + nameSource: .component, + waitForFullDisplay: true) + _ = viewModel.startSpan() + + let tracer = try XCTUnwrap(SentrySDK.span as? SentryTracer) + XCTAssertEqual(tracer.transactionContext.name, "Test Root") + XCTAssertEqual(tracer.children.count, 1) + XCTAssertEqual(tracer.children.first?.spanDescription, "ViewContent") + } + + func testNoTransactionWhenViewAppeared() { + let option = Options() + SentrySDK.setCurrentHub(SentryHub(client: SentryClient(options: option), andScope: nil)) + + let viewModel = SentryTraceViewModel(name: "TestView", nameSource: .component, waitForFullDisplay: false) + viewModel.viewDidAppear() + + let spanId = viewModel.startSpan() + XCTAssertNil(spanId, "Span should not be created if the view has already appeared.") + } + + func testFinishSpan() throws { + let option = Options() + SentrySDK.setCurrentHub(SentryHub(client: SentryClient(options: option), andScope: nil)) + + let viewModel = SentryTraceViewModel(name: "FinishSpanTest", nameSource: .component, waitForFullDisplay: false) + let spanId = try XCTUnwrap(viewModel.startSpan()) + XCTAssertNotNil(spanId, "Span should be created.") + + let tracer = try XCTUnwrap(SentrySDK.span as? SentryTracer) + + viewModel.finishSpan(spanId) + viewModel.viewDidAppear() + + // Verify that the span was popped and finished + XCTAssertNil(SentryPerformanceTracker.shared.activeSpanId(), "Active span should be nil after finishing the span.") + + XCTAssertTrue(tracer.isFinished, "The transaction should be finished.") + XCTAssertTrue(tracer.children.first?.isFinished == true, "The body span should be finished") + } +} + +#endif diff --git a/scripts/xcode-test.sh b/scripts/xcode-test.sh index 38a2f9f7c72..b2b9cd34735 100755 --- a/scripts/xcode-test.sh +++ b/scripts/xcode-test.sh @@ -16,6 +16,7 @@ COMMAND="${5:-test}" DEVICE=${6:-iPhone 14} CONFIGURATION_OVERRIDE="${7:-}" DERIVED_DATA_PATH="${8:-}" +TEST_SCHEME="${9:Sentry}" case $PLATFORM in @@ -90,7 +91,7 @@ esac if [ $RUN_BUILD == true ]; then env NSUnbufferedIO=YES xcodebuild \ -workspace Sentry.xcworkspace \ - -scheme Sentry \ + -scheme "$TEST_SCHEME" \ -configuration "$CONFIGURATION" \ -destination "$DESTINATION" \ -derivedDataPath "$DERIVED_DATA_PATH" \ @@ -101,7 +102,7 @@ fi if [ $RUN_BUILD_FOR_TESTING == true ]; then env NSUnbufferedIO=YES xcodebuild \ -workspace Sentry.xcworkspace \ - -scheme Sentry \ + -scheme "$TEST_SCHEME" \ -configuration "$CONFIGURATION" \ -destination "$DESTINATION" -quiet \ build-for-testing @@ -110,7 +111,7 @@ fi if [ $RUN_TEST_WITHOUT_BUILDING == true ]; then env NSUnbufferedIO=YES xcodebuild \ -workspace Sentry.xcworkspace \ - -scheme Sentry \ + -scheme "$TEST_SCHEME" \ -configuration "$CONFIGURATION" \ -destination "$DESTINATION" \ test-without-building | From 951bab76dccd6d353c0d413cb80d21fc5528b474 Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Fri, 20 Dec 2024 08:17:59 +0100 Subject: [PATCH 28/29] Update test.yml --- .github/workflows/test.yml | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 54ce5359355..f21a0979c88 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -84,21 +84,24 @@ jobs: xcode: "14.3" test-destination-os: "16.4" device: "iPhone 14" - + scheme: "Sentry" + # iOS 17 - runs-on: macos-14 platform: "iOS" xcode: "15.4" test-destination-os: "17.2" device: "iPhone 15" - + scheme: "Sentry" + # iOS 18 - runs-on: macos-15 platform: "iOS" xcode: "16.1" test-destination-os: "18.1" device: "iPhone 16" - + scheme: "Sentry" + # We don't run the unit tests on macOS 13 cause we run them on all on GH actions available iOS versions. # The chance of missing a bug solely on tvOS 16 that doesn't occur on iOS, macOS 12 or macOS 14 is minimal. # We are running tests on macOS 14 and later, as there were OS-internal changes introduced in succeeding versions. @@ -108,13 +111,15 @@ jobs: platform: "macOS" xcode: "15.4" test-destination-os: "latest" - + scheme: "Sentry" + # macOS 15 - runs-on: macos-15 platform: "macOS" xcode: "16.1" test-destination-os: "latest" - + scheme: "Sentry" + # Catalyst. We test the latest version, as the risk something breaking on Catalyst and not # on an older iOS or macOS version is low. # In addition we are running tests on macOS 14, as there were OS-internal changes introduced in succeeding versions. @@ -122,11 +127,13 @@ jobs: platform: "Catalyst" xcode: "15.4" test-destination-os: "latest" - + scheme: "Sentry" + - runs-on: macos-15 platform: "Catalyst" xcode: "16.1" test-destination-os: "latest" + scheme: "Sentry" # We don't run the unit tests on tvOS 16 cause we run them on all on GH actions available iOS versions. # The chance of missing a bug solely on tvOS 16 that doesn't occur on iOS, tvOS 15 or tvOS 16 is minimal. @@ -137,6 +144,7 @@ jobs: platform: "tvOS" xcode: "15.4" test-destination-os: "17.5" + scheme: "Sentry" # iOS 17 - runs-on: macos-14 @@ -151,6 +159,7 @@ jobs: platform: "tvOS" xcode: "16.1" test-destination-os: "18.1" + scheme: "Sentry" steps: - uses: actions/checkout@v4 From c95d3eca69eb1674b80f365f98b86cb282c4cc52 Mon Sep 17 00:00:00 2001 From: Dhiogo Brustolin Date: Fri, 20 Dec 2024 14:46:02 +0100 Subject: [PATCH 29/29] Update scripts/xcode-test.sh --- scripts/xcode-test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/xcode-test.sh b/scripts/xcode-test.sh index 18e71aff518..f4af0171327 100755 --- a/scripts/xcode-test.sh +++ b/scripts/xcode-test.sh @@ -15,7 +15,7 @@ COMMAND="${4:-test}" DEVICE=${5:-iPhone 14} CONFIGURATION_OVERRIDE="${6:-}" DERIVED_DATA_PATH="${7:-}" -TEST_SCHEME="${8:Sentry}" +TEST_SCHEME="${8:-Sentry}" case $PLATFORM in