From 60a8affb4bc57d93876629f1485126700479ec2f Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Tue, 18 Oct 2022 14:28:47 +0200 Subject: [PATCH 1/5] RUMM-2606 Launch Time Publisher --- Datadog/Datadog.xcodeproj/project.pbxproj | 24 +-- .../Context/LaunchTimePublisher.swift | 40 +++++ .../Context/LaunchTimeReader.swift | 37 ---- Sources/Datadog/DatadogCore/DatadogCore.swift | 2 +- .../DatadogInternal/Context/LaunchTime.swift | 6 +- .../RUM/RUMMonitor/Scopes/RUMViewScope.swift | 15 +- .../_Datadog_Private/ObjcAppLaunchHandler.m | 170 +++++++++++------- .../include/ObjcAppLaunchHandler.h | 36 +++- .../LaunchTimePublisherTests.swift | 63 +++++++ .../DatadogCore/LaunchTimeReaderTests.swift | 78 -------- .../DatadogInternal/DatadogContextMock.swift | 1 + .../RUMMonitor/Scopes/RUMViewScopeTests.swift | 44 ++++- 12 files changed, 308 insertions(+), 208 deletions(-) create mode 100644 Sources/Datadog/DatadogCore/Context/LaunchTimePublisher.swift delete mode 100644 Sources/Datadog/DatadogCore/Context/LaunchTimeReader.swift create mode 100644 Tests/DatadogTests/Datadog/DatadogCore/LaunchTimePublisherTests.swift delete mode 100644 Tests/DatadogTests/Datadog/DatadogCore/LaunchTimeReaderTests.swift diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index 10ab2384b5..bd04bdab3d 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -665,6 +665,7 @@ D255382A288F0B2400727FAD /* LowPowerModePublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2553828288F0B2300727FAD /* LowPowerModePublisher.swift */; }; D255382C288F161500727FAD /* BatteryStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = D255382B288F161500727FAD /* BatteryStatus.swift */; }; D255382D288F161500727FAD /* BatteryStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = D255382B288F161500727FAD /* BatteryStatus.swift */; }; + D2612F48290197C700509B7D /* LaunchTimePublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2C7E3AD28FEBDA10023B2CC /* LaunchTimePublisher.swift */; }; D26C49AF2886DC7B00802B2D /* ApplicationStatePublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D26C49AE2886DC7B00802B2D /* ApplicationStatePublisherTests.swift */; }; D26C49B02886DC7B00802B2D /* ApplicationStatePublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D26C49AE2886DC7B00802B2D /* ApplicationStatePublisherTests.swift */; }; D26C49B22886E10E00802B2D /* AppStateHistoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D26C49B12886E10E00802B2D /* AppStateHistoryTests.swift */; }; @@ -697,8 +698,6 @@ D2A1EE27287C35DE00D28DFB /* ContextValueReaderMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A1EE25287C35DE00D28DFB /* ContextValueReaderMock.swift */; }; D2A1EE29287D914900D28DFB /* LaunchTime.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A1EE28287D914900D28DFB /* LaunchTime.swift */; }; D2A1EE2A287D914900D28DFB /* LaunchTime.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A1EE28287D914900D28DFB /* LaunchTime.swift */; }; - D2A1EE2C287D91D900D28DFB /* LaunchTimeReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A1EE2B287D91D900D28DFB /* LaunchTimeReader.swift */; }; - D2A1EE2D287D91D900D28DFB /* LaunchTimeReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A1EE2B287D91D900D28DFB /* LaunchTimeReader.swift */; }; D2A1EE2F287DA4F200D28DFB /* UserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A1EE2E287DA4F200D28DFB /* UserInfo.swift */; }; D2A1EE30287DA4F200D28DFB /* UserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A1EE2E287DA4F200D28DFB /* UserInfo.swift */; }; D2A1EE32287DA51900D28DFB /* UserInfoPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A1EE31287DA51900D28DFB /* UserInfoPublisher.swift */; }; @@ -709,8 +708,8 @@ D2A1EE39287EEB7600D28DFB /* NetworkConnectionInfoPublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A1EE37287EBE4200D28DFB /* NetworkConnectionInfoPublisherTests.swift */; }; D2A1EE3B287EECC000D28DFB /* CarrierInfoPublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A1EE3A287EECA800D28DFB /* CarrierInfoPublisherTests.swift */; }; D2A1EE3C287EECC200D28DFB /* CarrierInfoPublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A1EE3A287EECA800D28DFB /* CarrierInfoPublisherTests.swift */; }; - D2A1EE3E2885D7EC00D28DFB /* LaunchTimeReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A1EE3D2885D7EC00D28DFB /* LaunchTimeReaderTests.swift */; }; - D2A1EE3F2885D7EC00D28DFB /* LaunchTimeReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A1EE3D2885D7EC00D28DFB /* LaunchTimeReaderTests.swift */; }; + D2A1EE3E2885D7EC00D28DFB /* LaunchTimePublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A1EE3D2885D7EC00D28DFB /* LaunchTimePublisherTests.swift */; }; + D2A1EE3F2885D7EC00D28DFB /* LaunchTimePublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A1EE3D2885D7EC00D28DFB /* LaunchTimePublisherTests.swift */; }; D2A1EE442886B8B400D28DFB /* UserInfoPublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A1EE432886B8B400D28DFB /* UserInfoPublisherTests.swift */; }; D2A1EE452886B8B400D28DFB /* UserInfoPublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2A1EE432886B8B400D28DFB /* UserInfoPublisherTests.swift */; }; D2B3F0442823EE8400C2B5EE /* DataBlockTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2B3F0432823EE8300C2B5EE /* DataBlockTests.swift */; }; @@ -724,6 +723,7 @@ D2B3F052282E827700C2B5EE /* DDHTTPHeadersWriter+apiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D2B3F051282E826A00C2B5EE /* DDHTTPHeadersWriter+apiTests.m */; }; D2B3F053282E827B00C2B5EE /* DDHTTPHeadersWriter+apiTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D2B3F051282E826A00C2B5EE /* DDHTTPHeadersWriter+apiTests.m */; }; D2C7E3AB28F97DCF0023B2CC /* BatteryStatusPublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2C7E3AA28F97DCF0023B2CC /* BatteryStatusPublisherTests.swift */; }; + D2C7E3AE28FEBDA10023B2CC /* LaunchTimePublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2C7E3AD28FEBDA10023B2CC /* LaunchTimePublisher.swift */; }; D2CB6E0C27C50EAE00A62B57 /* Datadog.h in Headers */ = {isa = PBXBuildFile; fileRef = 61133B85242393DE00786299 /* Datadog.h */; settings = {ATTRIBUTES = (Public, ); }; }; D2CB6E0D27C50EAE00A62B57 /* ObjcAppLaunchHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 6179FFD1254ADB1100556A0B /* ObjcAppLaunchHandler.h */; settings = {ATTRIBUTES = (Public, ); }; }; D2CB6E0E27C50EAE00A62B57 /* ObjcExceptionHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 9E68FB54244707FD0013A8AA /* ObjcExceptionHandler.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -1914,13 +1914,12 @@ D2A1EE22287740B500D28DFB /* ApplicationStatePublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationStatePublisher.swift; sourceTree = ""; }; D2A1EE25287C35DE00D28DFB /* ContextValueReaderMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextValueReaderMock.swift; sourceTree = ""; }; D2A1EE28287D914900D28DFB /* LaunchTime.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchTime.swift; sourceTree = ""; }; - D2A1EE2B287D91D900D28DFB /* LaunchTimeReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchTimeReader.swift; sourceTree = ""; }; D2A1EE2E287DA4F200D28DFB /* UserInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInfo.swift; sourceTree = ""; }; D2A1EE31287DA51900D28DFB /* UserInfoPublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInfoPublisher.swift; sourceTree = ""; }; D2A1EE34287EB8DB00D28DFB /* ServerOffsetPublisherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerOffsetPublisherTests.swift; sourceTree = ""; }; D2A1EE37287EBE4200D28DFB /* NetworkConnectionInfoPublisherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkConnectionInfoPublisherTests.swift; sourceTree = ""; }; D2A1EE3A287EECA800D28DFB /* CarrierInfoPublisherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarrierInfoPublisherTests.swift; sourceTree = ""; }; - D2A1EE3D2885D7EC00D28DFB /* LaunchTimeReaderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchTimeReaderTests.swift; sourceTree = ""; }; + D2A1EE3D2885D7EC00D28DFB /* LaunchTimePublisherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchTimePublisherTests.swift; sourceTree = ""; }; D2A1EE432886B8B400D28DFB /* UserInfoPublisherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserInfoPublisherTests.swift; sourceTree = ""; }; D2B3F0432823EE8300C2B5EE /* DataBlockTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataBlockTests.swift; sourceTree = ""; }; D2B3F04628292D6E00C2B5EE /* DataMigratorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataMigratorTests.swift; sourceTree = ""; }; @@ -1928,6 +1927,7 @@ D2B3F04C282A85FD00C2B5EE /* DatadogCore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatadogCore.swift; sourceTree = ""; }; D2B3F051282E826A00C2B5EE /* DDHTTPHeadersWriter+apiTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "DDHTTPHeadersWriter+apiTests.m"; sourceTree = ""; }; D2C7E3AA28F97DCF0023B2CC /* BatteryStatusPublisherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryStatusPublisherTests.swift; sourceTree = ""; }; + D2C7E3AD28FEBDA10023B2CC /* LaunchTimePublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchTimePublisher.swift; sourceTree = ""; }; D2CB6ED127C50EAE00A62B57 /* Datadog.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Datadog.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D2CB6F8F27C520D400A62B57 /* DatadogTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DatadogTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; D2CB6FB027C5217A00A62B57 /* DatadogObjc.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DatadogObjc.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -4419,7 +4419,7 @@ D20605A82874C1CD0047275C /* NetworkConnectionInfoPublisher.swift */, D20605B12874E1660047275C /* CarrierInfoPublisher.swift */, D2A1EE31287DA51900D28DFB /* UserInfoPublisher.swift */, - D2A1EE2B287D91D900D28DFB /* LaunchTimeReader.swift */, + D2C7E3AD28FEBDA10023B2CC /* LaunchTimePublisher.swift */, D2A1EE22287740B500D28DFB /* ApplicationStatePublisher.swift */, D2553825288F0B1A00727FAD /* BatteryStatusPublisher.swift */, D2553828288F0B2300727FAD /* LowPowerModePublisher.swift */, @@ -4435,7 +4435,7 @@ D2A1EE3A287EECA800D28DFB /* CarrierInfoPublisherTests.swift */, D2A1EE37287EBE4200D28DFB /* NetworkConnectionInfoPublisherTests.swift */, D2A1EE432886B8B400D28DFB /* UserInfoPublisherTests.swift */, - D2A1EE3D2885D7EC00D28DFB /* LaunchTimeReaderTests.swift */, + D2A1EE3D2885D7EC00D28DFB /* LaunchTimePublisherTests.swift */, D26C49AE2886DC7B00802B2D /* ApplicationStatePublisherTests.swift */, D2C7E3AA28F97DCF0023B2CC /* BatteryStatusPublisherTests.swift */, D21C26D028A64599005DD405 /* MessageBusTests.swift */, @@ -5412,7 +5412,6 @@ 61133BE02423979B00786299 /* Datadog.swift in Sources */, 61E945E32869BF3D00A946C4 /* CoreLogger.swift in Sources */, 61133BCB2423979B00786299 /* CarrierInfoProvider.swift in Sources */, - D2A1EE2C287D91D900D28DFB /* LaunchTimeReader.swift in Sources */, 61C5A89024509AA700DA608C /* TracingFeature.swift in Sources */, 61E5333624B84B43003D6C4E /* RUMMonitor.swift in Sources */, 9EB4B862274E79D50041CD03 /* WKUserContentController+Datadog.swift in Sources */, @@ -5442,6 +5441,7 @@ 61133BE82423979B00786299 /* LogFileOutput.swift in Sources */, 61133BD72423979B00786299 /* DataUploadWorker.swift in Sources */, 61D3E0D4277B23F1008BE766 /* KronosTimeStorage.swift in Sources */, + D2C7E3AE28FEBDA10023B2CC /* LaunchTimePublisher.swift in Sources */, 61133BD12423979B00786299 /* FilesOrchestrator.swift in Sources */, D20605A3287464F40047275C /* ContextValuePublisher.swift in Sources */, 61133BCD2423979B00786299 /* NetworkConnectionInfoProvider.swift in Sources */, @@ -5700,7 +5700,7 @@ 61B5E42726DFB145000B0A5F /* DDDatadog+apiTests.m in Sources */, 6121627C247D220500AC5D67 /* TracingWithLoggingIntegrationTests.swift in Sources */, 61FF282124B8981D000B3D9B /* RUMEventBuilderTests.swift in Sources */, - D2A1EE3E2885D7EC00D28DFB /* LaunchTimeReaderTests.swift in Sources */, + D2A1EE3E2885D7EC00D28DFB /* LaunchTimePublisherTests.swift in Sources */, 61FD9FD22853562B00214BD9 /* RUMDeviceInfoTests.swift in Sources */, 61B5E42926DFB60A000B0A5F /* DDConfiguration+apiTests.m in Sources */, D232CAD52832D762001B262C /* DatadogCoreMock.swift in Sources */, @@ -5959,6 +5959,7 @@ D2CB6E1327C50EAE00A62B57 /* SwiftUIViewModifier.swift in Sources */, D2CB6E1427C50EAE00A62B57 /* SwiftUIExtensions.swift in Sources */, D2CB6E1527C50EAE00A62B57 /* ServerDateCorrector.swift in Sources */, + D2612F48290197C700509B7D /* LaunchTimePublisher.swift in Sources */, D2CB6E1627C50EAE00A62B57 /* RUMWithCrashContextIntegration.swift in Sources */, D2CB6E1727C50EAE00A62B57 /* OTSpan.swift in Sources */, D2CB6E1827C50EAE00A62B57 /* RUMViewIdentity.swift in Sources */, @@ -6087,7 +6088,6 @@ D2CB6E7E27C50EAE00A62B57 /* RUMMonitor.swift in Sources */, D26C49C0288982DA00802B2D /* FeatureUpload.swift in Sources */, D2CB6E8027C50EAE00A62B57 /* WKUserContentController+Datadog.swift in Sources */, - D2A1EE2D287D91D900D28DFB /* LaunchTimeReader.swift in Sources */, D2CB6E8127C50EAE00A62B57 /* DataUploader.swift in Sources */, D2CB6E8227C50EAE00A62B57 /* DeleteAllDataMigrator.swift in Sources */, D2CB6E8327C50EAE00A62B57 /* RUMUserActionScope.swift in Sources */, @@ -6376,7 +6376,7 @@ D2CB6F7F27C520D400A62B57 /* DDDatadog+apiTests.m in Sources */, D2CB6F8027C520D400A62B57 /* TracingWithLoggingIntegrationTests.swift in Sources */, D2CB6F8227C520D400A62B57 /* RUMEventBuilderTests.swift in Sources */, - D2A1EE3F2885D7EC00D28DFB /* LaunchTimeReaderTests.swift in Sources */, + D2A1EE3F2885D7EC00D28DFB /* LaunchTimePublisherTests.swift in Sources */, 61FD9FD12853562A00214BD9 /* RUMDeviceInfoTests.swift in Sources */, D2CB6F8327C520D400A62B57 /* DDConfiguration+apiTests.m in Sources */, D232CAD62832D762001B262C /* DatadogCoreMock.swift in Sources */, diff --git a/Sources/Datadog/DatadogCore/Context/LaunchTimePublisher.swift b/Sources/Datadog/DatadogCore/Context/LaunchTimePublisher.swift new file mode 100644 index 0000000000..5723936606 --- /dev/null +++ b/Sources/Datadog/DatadogCore/Context/LaunchTimePublisher.swift @@ -0,0 +1,40 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-2020 Datadog, Inc. + */ + +import Foundation + +#if SPM_BUILD +import _Datadog_Private +#endif + +internal struct LaunchTimePublisher: ContextValuePublisher { + private typealias AppLaunchHandler = __dd_private_AppLaunchHandler + + var initialValue: LaunchTime + + init() { + initialValue = LaunchTime( + launchTime: AppLaunchHandler.shared.launchTime?.doubleValue, + launchDate: AppLaunchHandler.shared.launchDate, + isActivePrewarm: AppLaunchHandler.shared.isActivePrewarm + ) + } + + func publish(to receiver: @escaping ContextValueReceiver) { + AppLaunchHandler.shared.setCallback { handler in + let value = LaunchTime( + launchTime: handler.launchTime?.doubleValue, + launchDate: handler.launchDate, + isActivePrewarm: handler.isActivePrewarm + ) + receiver(value) + } + } + + func cancel() { + AppLaunchHandler.shared.setCallback { _ in } + } +} diff --git a/Sources/Datadog/DatadogCore/Context/LaunchTimeReader.swift b/Sources/Datadog/DatadogCore/Context/LaunchTimeReader.swift deleted file mode 100644 index 18999855a1..0000000000 --- a/Sources/Datadog/DatadogCore/Context/LaunchTimeReader.swift +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. - * This product includes software developed at Datadog (https://www.datadoghq.com/). - * Copyright 2019-2020 Datadog, Inc. - */ - -import Foundation - -#if SPM_BUILD -import _Datadog_Private -#endif - -internal struct LaunchTimeReader: ContextValueReader { - /// Lock object to sync launch time read. - let lock: Any - - init() { - lock = NSObject() - } - - func read(to receiver: inout LaunchTime) { - receiver = __dd_private_objc_sync_LaunchTime(lock) - } -} - -private func __dd_private_objc_sync_LaunchTime(_ lock: Any) -> LaunchTime { - // Even if __dd_private_AppLaunchTime() is using a lock behind the - // scenes, TSAN will report a data race if there are no - // synchronizations at this level. - objc_sync_enter(lock) - let time = LaunchTime( - launchTime: __dd_private_AppLaunchTime(), - isActivePrewarm: __dd_private_isActivePrewarm() - ) - objc_sync_exit(lock) - return time -} diff --git a/Sources/Datadog/DatadogCore/DatadogCore.swift b/Sources/Datadog/DatadogCore/DatadogCore.swift index a8a0553179..688576c550 100644 --- a/Sources/Datadog/DatadogCore/DatadogCore.swift +++ b/Sources/Datadog/DatadogCore/DatadogCore.swift @@ -449,7 +449,7 @@ extension DatadogContextProvider { self.init(context: context) subscribe(\.serverTimeOffset, to: ServerOffsetPublisher(provider: serverDateProvider)) - assign(reader: LaunchTimeReader(), to: \.launchTime) + subscribe(\.launchTime, to: LaunchTimePublisher()) if #available(iOS 12, tvOS 12, *) { subscribe(\.networkConnectionInfo, to: NWPathMonitorPublisher()) diff --git a/Sources/Datadog/DatadogInternal/Context/LaunchTime.swift b/Sources/Datadog/DatadogInternal/Context/LaunchTime.swift index 58ce5a9c7b..97b40b8c51 100644 --- a/Sources/Datadog/DatadogInternal/Context/LaunchTime.swift +++ b/Sources/Datadog/DatadogInternal/Context/LaunchTime.swift @@ -13,7 +13,9 @@ import Foundation /// /// If the `UIApplication.didBecomeActiveNotification` has not yet been received by the /// time this value is provided, it will represent the time interval between now and the process start time. - /* public */ let launchTime: TimeInterval + /* public */ let launchTime: TimeInterval? + + /* public */ let launchDate: Date? /// Returns `true` if the application is pre-warmed. /* public */ let isActivePrewarm: Bool @@ -22,6 +24,6 @@ import Foundation extension LaunchTime { /// Returns a zero launch time with inactive pre-warm. /* public */ static var zero: LaunchTime { - .init(launchTime: 0, isActivePrewarm: false) + .init(launchTime: 0, launchDate: nil, isActivePrewarm: false) } } diff --git a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift index f18304351e..7958880336 100644 --- a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift +++ b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift @@ -316,11 +316,20 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { if context.launchTime.isActivePrewarm { // Set `active_pre_warm` attribute to true in case - // of pre-warmed app + // of pre-warmed app. attributes[Constants.activePrewarm] = true - } else { + } else if let launchTime = context.launchTime.launchTime { // Report Application Launch Time only if not pre-warmed - loadingTime = context.launchTime.launchTime.toInt64Nanoseconds + loadingTime = launchTime.toInt64Nanoseconds + } else if let launchDate = context.launchTime.launchDate { + // The launchTime can be `nil` if the application is not yet + // active (UIApplicationDidBecomeActiveNotification). That is + // the case when instrumenting a SwiftUI application that start + // a RUM view on `SwiftUI.View.onAppear`. + // + // In that case, we consider the time between the application + // launch and the view start as the application loadint time. + loadingTime = viewStartTime.timeIntervalSince(launchDate).toInt64Nanoseconds } let actionEvent = RUMActionEvent( diff --git a/Sources/_Datadog_Private/ObjcAppLaunchHandler.m b/Sources/_Datadog_Private/ObjcAppLaunchHandler.m index 97e16351d4..1851c560f8 100644 --- a/Sources/_Datadog_Private/ObjcAppLaunchHandler.m +++ b/Sources/_Datadog_Private/ObjcAppLaunchHandler.m @@ -4,97 +4,129 @@ * Copyright 2019-2020 Datadog, Inc. */ -#import "ObjcAppLaunchHandler.h" -#import #import #import +#import + +#import "ObjcAppLaunchHandler.h" -// `AppLaunchHandler` aims to track some times as part of the sequence described in Apple's "About the App Launch Sequence" -// https://developer.apple.com/documentation/uikit/app_and_environment/responding_to_the_launch_of_your_app/about_the_app_launch_sequence - -// A Read-Write lock to allow concurrent reads of TimeToApplicationDidBecomeActive, unless the initial (and only) write is locking it. -static pthread_rwlock_t rwLock; -// The framework load time in seconds relative to the absolute reference date of Jan 1 2001 00:00:00 GMT. -static NSTimeInterval FrameworkLoadTime = 0.0; -// The time interval between the application starts and it's responsive and accepts touch events. -static NSTimeInterval TimeToApplicationDidBecomeActive = 0.0; -// System sets environment variable ActivePrewarm to 1 when app is pre-warmed. -static BOOL isActivePrewarm = NO; // A very long application launch time is most-likely the result of a pre-warmed process. // We consider 30s as a threshold for pre-warm detection. -static NSTimeInterval ValidAppLaunchTimeThreshold = 30; - -NS_INLINE NSTimeInterval QueryProcessStartTimeWithFallback(NSTimeInterval fallbackTime) { - NSTimeInterval processStartTime; - // Query the current process' start time: - // https://www.freebsd.org/cgi/man.cgi?sysctl(3) - // https://github.com/darwin-on-arm/xnu/blob/707bfdc4e9a46e3612e53994fffc64542d3f7e72/bsd/sys/sysctl.h#L681 - // https://github.com/darwin-on-arm/xnu/blob/707bfdc4e9a46e3612e53994fffc64542d3f7e72/bsd/sys/proc.h#L97 - - struct kinfo_proc kip; - size_t kipSize = sizeof(kip); - int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()}; - int res = sysctl(mib, 4, &kip, &kipSize, NULL, 0); - - if (res == 0) { - // The process' start time is provided relative to 1 Jan 1970 - struct timeval startTime = kip.kp_proc.p_starttime; - processStartTime = startTime.tv_sec + startTime.tv_usec / USEC_PER_SEC; - // Convert to time since 1 Jan 2001 to align with CFAbsoluteTimeGetCurrent() - processStartTime -= kCFAbsoluteTimeIntervalSince1970; - } else { - // Fallback to less accurate delta with DD's framework load time - processStartTime = fallbackTime; - } - return processStartTime; +#define COLD_START_TIME_THRESHOLD 30 + +/// Get the process start time from kernel. +/// +/// The time intervale is related to the 1 January 2001 00:00:00 GMT reference date. +/// +/// - Parameter timeInterval: Pointer to time interval to hold the process start time interval. +int processStartTimeIntervalSinceReferenceDate(NSTimeInterval *timeInterval); + +/// `AppLaunchHandler` aims to track some times as part of the sequence +/// described in Apple's "About the App Launch Sequence" +/// +/// ref. https://developer.apple.com/documentation/uikit/app_and_environment/responding_to_the_launch_of_your_app/about_the_app_launch_sequence +@implementation __dd_private_AppLaunchHandler { + NSTimeInterval _processStartTime; + NSTimeInterval _timeToApplicationDidBecomeActive; + AppLaunchCallback _callback; } -NS_INLINE NSTimeInterval ComputeProcessTimeFromStart() { - NSTimeInterval now = CFAbsoluteTimeGetCurrent(); - NSTimeInterval processStartTime = QueryProcessStartTimeWithFallback(FrameworkLoadTime); - return now - processStartTime; -} +/// Shared instance of the Application Launch Handler. +static __dd_private_AppLaunchHandler *_shared; -@interface AppLaunchHandler : NSObject -@end +/// The framework load time in seconds relative to the absolute reference date of Jan 1 2001 00:00:00 GMT. +static NSTimeInterval _frameworkLoadTime; -@implementation AppLaunchHandler +@synthesize isActivePrewarm = _isActivePrewarm; + (void)load { // This is called at the `_Datadog_Private` load time, keep the work minimal - FrameworkLoadTime = CFAbsoluteTimeGetCurrent(); - - isActivePrewarm = [NSProcessInfo.processInfo.environment[@"ActivePrewarm"] isEqualToString:@"1"]; + _frameworkLoadTime = CFAbsoluteTimeGetCurrent(); + _shared = [[self alloc] initWithProcessInfo:NSProcessInfo.processInfo]; NSNotificationCenter * __weak center = NSNotificationCenter.defaultCenter; - id __block token = [center - addObserverForName:UIApplicationDidBecomeActiveNotification - object:nil - queue:NSOperationQueue.mainQueue - usingBlock:^(NSNotification *_){ + id __block token = [center addObserverForName:UIApplicationDidBecomeActiveNotification + object:nil + queue:NSOperationQueue.mainQueue + usingBlock:^(NSNotification *_){ - pthread_rwlock_init(&rwLock, NULL); - pthread_rwlock_wrlock(&rwLock); - TimeToApplicationDidBecomeActive = ComputeProcessTimeFromStart(); - pthread_rwlock_unlock(&rwLock); + @synchronized(_shared) { + _shared->_timeToApplicationDidBecomeActive = CFAbsoluteTimeGetCurrent() - _shared->_processStartTime; + _shared->_callback(_shared); + } [center removeObserver:token]; token = nil; }]; } -@end ++ (__dd_private_AppLaunchHandler *)shared { + return _shared; +} + +- (instancetype)initWithProcessInfo:(NSProcessInfo *)processInfo { + NSTimeInterval startTime; + if (processStartTimeIntervalSinceReferenceDate(&startTime) != 0) { + startTime = _frameworkLoadTime; + } -CFTimeInterval __dd_private_AppLaunchTime() { - pthread_rwlock_rdlock(&rwLock); - CFTimeInterval time = TimeToApplicationDidBecomeActive; - pthread_rwlock_unlock(&rwLock); - if (time == 0) time = ComputeProcessTimeFromStart(); - return time; + // The ActivePrewarm variable indicates whether the app was launched via pre-warming. + BOOL isActivePrewarm = [processInfo.environment[@"ActivePrewarm"] isEqualToString:@"1"]; + return [self initWithStartTime:startTime isActivePrewarm:isActivePrewarm]; } -BOOL __dd_private_isActivePrewarm() { - if (isActivePrewarm) return isActivePrewarm; - CFTimeInterval time = __dd_private_AppLaunchTime(); - return time > ValidAppLaunchTimeThreshold; +- (instancetype)initWithStartTime:(NSTimeInterval)startTime isActivePrewarm:(BOOL)isActivePrewarm { + self = [super init]; + if (!self) return nil; + _processStartTime = startTime; + _isActivePrewarm = isActivePrewarm; + _callback = ^(__dd_private_AppLaunchHandler *handler) {}; + return self; +} + +- (NSDate *)launchDate { + @synchronized(self) { + return [NSDate dateWithTimeIntervalSinceReferenceDate:_processStartTime]; + } +} + +- (NSNumber *)launchTime { + @synchronized(self) { + return _timeToApplicationDidBecomeActive > 0 ? + @(_timeToApplicationDidBecomeActive) : nil; + } +} + +- (BOOL)isActivePrewarm { + @synchronized(self) { + if (_isActivePrewarm) return _isActivePrewarm; + return _timeToApplicationDidBecomeActive > COLD_START_TIME_THRESHOLD; + } +} + +- (void)setCallback:(AppLaunchCallback)callback { + @synchronized(self) { + _callback = callback; + } +} + +@end + +int processStartTimeIntervalSinceReferenceDate(NSTimeInterval *timeInterval) { + // Query the current process' start time: + // https://www.freebsd.org/cgi/man.cgi?sysctl(3) + // https://github.com/darwin-on-arm/xnu/blob/707bfdc4e9a46e3612e53994fffc64542d3f7e72/bsd/sys/sysctl.h#L681 + // https://github.com/darwin-on-arm/xnu/blob/707bfdc4e9a46e3612e53994fffc64542d3f7e72/bsd/sys/proc.h#L97 + struct kinfo_proc kip; + size_t kipSize = sizeof(kip); + int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()}; + int res = sysctl(mib, 4, &kip, &kipSize, NULL, 0); + if (res != 0) return res; + + // The process' start time is provided relative to 1 Jan 1970 + struct timeval startTime = kip.kp_proc.p_starttime; + NSTimeInterval processStartTime = startTime.tv_sec + startTime.tv_usec / USEC_PER_SEC; + // Convert to time since 1 Jan 2001 to align with CFAbsoluteTimeGetCurrent() + *timeInterval = processStartTime - kCFAbsoluteTimeIntervalSince1970; + return res; } diff --git a/Sources/_Datadog_Private/include/ObjcAppLaunchHandler.h b/Sources/_Datadog_Private/include/ObjcAppLaunchHandler.h index 1233fead24..8096c17abc 100644 --- a/Sources/_Datadog_Private/include/ObjcAppLaunchHandler.h +++ b/Sources/_Datadog_Private/include/ObjcAppLaunchHandler.h @@ -4,16 +4,46 @@ * Copyright 2019-2020 Datadog, Inc. */ -#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/// `AppLaunchHandler` aims to track some times as part of the sequence +/// described in Apple's "About the App Launch Sequence" +/// +/// ref. https://developer.apple.com/documentation/uikit/app_and_environment/responding_to_the_launch_of_your_app/about_the_app_launch_sequence +@interface __dd_private_AppLaunchHandler : NSObject + +typedef void (^AppLaunchCallback) (__dd_private_AppLaunchHandler *handler); + +/// Sole instance of the Application Launch Handler. +@property (class, readonly) __dd_private_AppLaunchHandler *shared; + +/// Returns the Application process launch date. +@property (atomic, readonly) NSDate* launchDate; /// Returns the time interval between startup of the application process and the /// `UIApplicationDidBecomeActiveNotification`. /// /// If the `UIApplicationDidBecomeActiveNotification` has not been reached yet, /// it returns time interval between startup of the application process and now. -CFTimeInterval __dd_private_AppLaunchTime(void); +@property (atomic, readonly, nullable) NSNumber* launchTime; /// Returns `true` when the application is pre-warmed. /// /// System sets environment variable `ActivePrewarm` to 1 when app is pre-warmed. -BOOL __dd_private_isActivePrewarm(void); +@property (atomic, readonly) BOOL isActivePrewarm; + +/// Sets the callback to be invoked when the application becomes active. +/// +/// The closure get the updated handler as argument. +/// +/// - Parameter callback: The callback closure. +- (void)setCallback:(AppLaunchCallback)callback; + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Tests/DatadogTests/Datadog/DatadogCore/LaunchTimePublisherTests.swift b/Tests/DatadogTests/Datadog/DatadogCore/LaunchTimePublisherTests.swift new file mode 100644 index 0000000000..7c5fe782e9 --- /dev/null +++ b/Tests/DatadogTests/Datadog/DatadogCore/LaunchTimePublisherTests.swift @@ -0,0 +1,63 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-2020 Datadog, Inc. + */ + +import XCTest +@testable import Datadog + +class LaunchTimePublisherTests: XCTestCase { + override func tearDown() { + super.tearDown() + setenv("ActivePrewarm", "", 1) + } + + func testGivenStartedApplication_itHasLaunchDate() throws { + // Given + let publisher = LaunchTimePublisher() + + // When + let launchTime = publisher.initialValue + + // Then + XCTAssertNotNil(launchTime.launchDate) + } + + func testThreadSafety() { + let handler = __dd_private_AppLaunchHandler.shared + + // swiftlint:disable opening_brace + callConcurrently( + closures: [ + { _ = handler.launchTime }, + { handler.setCallback { _ in } }, + ], + iterations: 1_000 + ) + // swiftlint:enable opening_brace + } + + func testIsActivePrewarm_returnsTrue() { + // Given + setenv("ActivePrewarm", "1", 1) + NSClassFromString("__dd_private_AppLaunchHandler")?.load() + + // When + let publisher = LaunchTimePublisher() + + // Then + XCTAssertTrue(publisher.initialValue.isActivePrewarm) + } + + func testIsActivePrewarm_returnsFalse() { + // Given + NSClassFromString("__dd_private_AppLaunchHandler")?.load() + + // When + let publisher = LaunchTimePublisher() + + // Then + XCTAssertFalse(publisher.initialValue.isActivePrewarm) + } +} diff --git a/Tests/DatadogTests/Datadog/DatadogCore/LaunchTimeReaderTests.swift b/Tests/DatadogTests/Datadog/DatadogCore/LaunchTimeReaderTests.swift deleted file mode 100644 index 6cc0d0d2e5..0000000000 --- a/Tests/DatadogTests/Datadog/DatadogCore/LaunchTimeReaderTests.swift +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. - * This product includes software developed at Datadog (https://www.datadoghq.com/). - * Copyright 2019-2020 Datadog, Inc. - */ - -import XCTest -@testable import Datadog - -class LaunchTimeReaderTests: XCTestCase { - override func tearDown() { - super.tearDown() - setenv("ActivePrewarm", "", 1) - } - - func testGivenStartedApplication_whenRequestingLaunchTimeAtAnyTime_itReturnsTheSameValue() throws { - // Given - let reader = LaunchTimeReader() - - // When - var values: [TimeInterval] = [] - try (0..<10).forEach { _ in - Thread.sleep(forTimeInterval: 0.01) - var launchTime = LaunchTime(launchTime: .mockRandom(), isActivePrewarm: false) - reader.read(to: &launchTime) - try values.append(XCTUnwrap(launchTime.launchTime)) - } - - // Then - let uniqueValues = Set(values) - XCTAssertEqual(uniqueValues.count, 1, "All collected `launchTime` values should be the same.") - XCTAssertGreaterThan(values[0], 0) - } - - func testThreadSafety() { - let reader = LaunchTimeReader() - - // swiftlint:disable opening_brace - callConcurrently( - closures: [ - { - var launchTime: LaunchTime = .mockAny() - reader.read(to: &launchTime) - } - ], - iterations: 1_000 - ) - // swiftlint:enable opening_brace - } - - func testIsActivePrewarm_returnsTrue() { - // Given - let reader = LaunchTimeReader() - - // When - setenv("ActivePrewarm", "1", 1) - NSClassFromString("AppLaunchHandler")?.load() - - var launchTime = LaunchTime(launchTime: 0, isActivePrewarm: false) - reader.read(to: &launchTime) - - // Then - XCTAssertTrue(launchTime.isActivePrewarm) - } - - func testIsActivePrewarm_returnsFalse() { - // Given - let reader = LaunchTimeReader() - - // When - NSClassFromString("AppLaunchHandler")?.load() - var launchTime = LaunchTime(launchTime: 0, isActivePrewarm: true) - reader.read(to: &launchTime) - - // Then - XCTAssertFalse(launchTime.isActivePrewarm) - } -} diff --git a/Tests/DatadogTests/Datadog/Mocks/DatadogInternal/DatadogContextMock.swift b/Tests/DatadogTests/Datadog/Mocks/DatadogInternal/DatadogContextMock.swift index 547d564bb8..8f6583c7ba 100644 --- a/Tests/DatadogTests/Datadog/Mocks/DatadogInternal/DatadogContextMock.swift +++ b/Tests/DatadogTests/Datadog/Mocks/DatadogInternal/DatadogContextMock.swift @@ -64,6 +64,7 @@ extension LaunchTime: AnyMockable { static func mockAny() -> LaunchTime { .init( launchTime: .mockAny(), + launchDate: .mockAny(), isActivePrewarm: .mockAny() ) } diff --git a/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMViewScopeTests.swift b/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMViewScopeTests.swift index 8db1204c29..5b12c8d3f0 100644 --- a/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMViewScopeTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMViewScopeTests.swift @@ -88,7 +88,7 @@ class RUMViewScopeTests: XCTestCase { // Given let currentTime: Date = .mockDecember15th2019At10AMUTC() var context = self.context - context.launchTime = .init(launchTime: 2, isActivePrewarm: false) + context.launchTime = .init(launchTime: 2, launchDate: nil, isActivePrewarm: false) let scope = RUMViewScope( isInitialView: true, @@ -137,7 +137,11 @@ class RUMViewScopeTests: XCTestCase { let custonContext: DatadogContext = .mockWith(source: source) var context = self.context - context.launchTime = .init(launchTime: 2, isActivePrewarm: false) + context.launchTime = .init( + launchTime: 2, + launchDate: nil, + isActivePrewarm: false + ) let scope = RUMViewScope( isInitialView: true, @@ -164,10 +168,44 @@ class RUMViewScopeTests: XCTestCase { XCTAssertEqual(event.source, .init(rawValue: source)) } + func testWhenNoLoadingTime_itSendsApplicationStartAction_basedOnLoadingDate() throws { + // Given + var context = self.context + let date = Date() + context.launchTime = .init( + launchTime: nil, + launchDate: date.addingTimeInterval(-2), + isActivePrewarm: false + ) + + let scope: RUMViewScope = .mockWith( + isInitialView: true, + parent: parent, + dependencies: .mockAny(), + identity: mockView, + startTime: date + ) + + // When + _ = scope.process( + command: RUMCommandMock(), + context: context, + writer: writer + ) + + // Then + let event = try XCTUnwrap(writer.events(ofType: RUMActionEvent.self).first) + XCTAssertEqual(event.action.loadingTime, 2_000_000_000) // 2e+9 ns + } + func testWhenActivePrewarm_itSendsApplicationStartAction_withoutLoadingTime() throws { // Given var context = self.context - context.launchTime = .init(launchTime: 2, isActivePrewarm: true) + context.launchTime = .init( + launchTime: 2, + launchDate: nil, + isActivePrewarm: true + ) let scope: RUMViewScope = .mockWith( isInitialView: true, From fbc11e4af4f2d83b891fe8177d84db1e25e663a3 Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Fri, 21 Oct 2022 12:48:13 +0200 Subject: [PATCH 2/5] RUMM-2606 avoid recursive lock --- .../DatadogIntegrationTests.xcscheme | 1 + .../Context/LaunchTimePublisher.swift | 12 ++++---- .../_Datadog_Private/ObjcAppLaunchHandler.m | 28 +++++++++---------- .../include/ObjcAppLaunchHandler.h | 15 +++++----- .../LaunchTimePublisherTests.swift | 4 ++- 5 files changed, 30 insertions(+), 30 deletions(-) diff --git a/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogIntegrationTests.xcscheme b/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogIntegrationTests.xcscheme index 1b7b188ffe..5d40d059a2 100644 --- a/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogIntegrationTests.xcscheme +++ b/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogIntegrationTests.xcscheme @@ -180,6 +180,7 @@ buildConfiguration = "Integration" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + enableThreadSanitizer = "YES" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/Sources/Datadog/DatadogCore/Context/LaunchTimePublisher.swift b/Sources/Datadog/DatadogCore/Context/LaunchTimePublisher.swift index 5723936606..04125657e6 100644 --- a/Sources/Datadog/DatadogCore/Context/LaunchTimePublisher.swift +++ b/Sources/Datadog/DatadogCore/Context/LaunchTimePublisher.swift @@ -13,7 +13,7 @@ import _Datadog_Private internal struct LaunchTimePublisher: ContextValuePublisher { private typealias AppLaunchHandler = __dd_private_AppLaunchHandler - var initialValue: LaunchTime + let initialValue: LaunchTime init() { initialValue = LaunchTime( @@ -24,17 +24,17 @@ internal struct LaunchTimePublisher: ContextValuePublisher { } func publish(to receiver: @escaping ContextValueReceiver) { - AppLaunchHandler.shared.setCallback { handler in + AppLaunchHandler.shared.setApplicationDidBecomeActiveCallback { launchTime in let value = LaunchTime( - launchTime: handler.launchTime?.doubleValue, - launchDate: handler.launchDate, - isActivePrewarm: handler.isActivePrewarm + launchTime: launchTime, + launchDate: initialValue.launchDate, + isActivePrewarm: initialValue.isActivePrewarm ) receiver(value) } } func cancel() { - AppLaunchHandler.shared.setCallback { _ in } + AppLaunchHandler.shared.setApplicationDidBecomeActiveCallback { _ in } } } diff --git a/Sources/_Datadog_Private/ObjcAppLaunchHandler.m b/Sources/_Datadog_Private/ObjcAppLaunchHandler.m index 1851c560f8..ab9ca65965 100644 --- a/Sources/_Datadog_Private/ObjcAppLaunchHandler.m +++ b/Sources/_Datadog_Private/ObjcAppLaunchHandler.m @@ -28,21 +28,17 @@ @implementation __dd_private_AppLaunchHandler { NSTimeInterval _processStartTime; NSTimeInterval _timeToApplicationDidBecomeActive; - AppLaunchCallback _callback; + BOOL _isActivePrewarm; + UIApplicationDidBecomeActiveCallback _applicationDidBecomeActiveCallback; } /// Shared instance of the Application Launch Handler. static __dd_private_AppLaunchHandler *_shared; -/// The framework load time in seconds relative to the absolute reference date of Jan 1 2001 00:00:00 GMT. -static NSTimeInterval _frameworkLoadTime; - -@synthesize isActivePrewarm = _isActivePrewarm; - + (void)load { // This is called at the `_Datadog_Private` load time, keep the work minimal - _frameworkLoadTime = CFAbsoluteTimeGetCurrent(); - _shared = [[self alloc] initWithProcessInfo:NSProcessInfo.processInfo]; + _shared = [[self alloc] initWithProcessInfo:NSProcessInfo.processInfo + loadTime:CFAbsoluteTimeGetCurrent()]; NSNotificationCenter * __weak center = NSNotificationCenter.defaultCenter; id __block token = [center addObserverForName:UIApplicationDidBecomeActiveNotification @@ -51,8 +47,9 @@ + (void)load { usingBlock:^(NSNotification *_){ @synchronized(_shared) { - _shared->_timeToApplicationDidBecomeActive = CFAbsoluteTimeGetCurrent() - _shared->_processStartTime; - _shared->_callback(_shared); + NSTimeInterval time = CFAbsoluteTimeGetCurrent() - _shared->_processStartTime; + _shared->_timeToApplicationDidBecomeActive = time; + _shared->_applicationDidBecomeActiveCallback(time); } [center removeObserver:token]; @@ -64,10 +61,11 @@ + (__dd_private_AppLaunchHandler *)shared { return _shared; } -- (instancetype)initWithProcessInfo:(NSProcessInfo *)processInfo { +- (instancetype)initWithProcessInfo:(NSProcessInfo *)processInfo loadTime:(NSTimeInterval)loadTime { NSTimeInterval startTime; if (processStartTimeIntervalSinceReferenceDate(&startTime) != 0) { - startTime = _frameworkLoadTime; + // fallback on the loading time + startTime = loadTime; } // The ActivePrewarm variable indicates whether the app was launched via pre-warming. @@ -80,7 +78,7 @@ - (instancetype)initWithStartTime:(NSTimeInterval)startTime isActivePrewarm:(BOO if (!self) return nil; _processStartTime = startTime; _isActivePrewarm = isActivePrewarm; - _callback = ^(__dd_private_AppLaunchHandler *handler) {}; + _applicationDidBecomeActiveCallback = ^(NSTimeInterval _) {}; return self; } @@ -104,9 +102,9 @@ - (BOOL)isActivePrewarm { } } -- (void)setCallback:(AppLaunchCallback)callback { +- (void)setApplicationDidBecomeActiveCallback:(UIApplicationDidBecomeActiveCallback)callback { @synchronized(self) { - _callback = callback; + _applicationDidBecomeActiveCallback = callback; } } diff --git a/Sources/_Datadog_Private/include/ObjcAppLaunchHandler.h b/Sources/_Datadog_Private/include/ObjcAppLaunchHandler.h index 8096c17abc..a290ec6aad 100644 --- a/Sources/_Datadog_Private/include/ObjcAppLaunchHandler.h +++ b/Sources/_Datadog_Private/include/ObjcAppLaunchHandler.h @@ -14,7 +14,7 @@ NS_ASSUME_NONNULL_BEGIN /// ref. https://developer.apple.com/documentation/uikit/app_and_environment/responding_to_the_launch_of_your_app/about_the_app_launch_sequence @interface __dd_private_AppLaunchHandler : NSObject -typedef void (^AppLaunchCallback) (__dd_private_AppLaunchHandler *handler); +typedef void (^UIApplicationDidBecomeActiveCallback) (NSTimeInterval); /// Sole instance of the Application Launch Handler. @property (class, readonly) __dd_private_AppLaunchHandler *shared; @@ -22,11 +22,9 @@ typedef void (^AppLaunchCallback) (__dd_private_AppLaunchHandler *handler); /// Returns the Application process launch date. @property (atomic, readonly) NSDate* launchDate; -/// Returns the time interval between startup of the application process and the -/// `UIApplicationDidBecomeActiveNotification`. -/// -/// If the `UIApplicationDidBecomeActiveNotification` has not been reached yet, -/// it returns time interval between startup of the application process and now. +/// Returns the time interval in seconds between startup of the application process and the +/// `UIApplicationDidBecomeActiveNotification`. Or `nil` If the +/// `UIApplicationDidBecomeActiveNotification` has not been reached yet. @property (atomic, readonly, nullable) NSNumber* launchTime; /// Returns `true` when the application is pre-warmed. @@ -36,10 +34,11 @@ typedef void (^AppLaunchCallback) (__dd_private_AppLaunchHandler *handler); /// Sets the callback to be invoked when the application becomes active. /// -/// The closure get the updated handler as argument. +/// The closure get the updated handler as argument. You will not get any +/// notification if the application became active before setting the callback /// /// - Parameter callback: The callback closure. -- (void)setCallback:(AppLaunchCallback)callback; +- (void)setApplicationDidBecomeActiveCallback:(UIApplicationDidBecomeActiveCallback)callback; - (instancetype)init NS_UNAVAILABLE; + (instancetype)new NS_UNAVAILABLE; diff --git a/Tests/DatadogTests/Datadog/DatadogCore/LaunchTimePublisherTests.swift b/Tests/DatadogTests/Datadog/DatadogCore/LaunchTimePublisherTests.swift index 7c5fe782e9..a4e3023bff 100644 --- a/Tests/DatadogTests/Datadog/DatadogCore/LaunchTimePublisherTests.swift +++ b/Tests/DatadogTests/Datadog/DatadogCore/LaunchTimePublisherTests.swift @@ -31,7 +31,9 @@ class LaunchTimePublisherTests: XCTestCase { callConcurrently( closures: [ { _ = handler.launchTime }, - { handler.setCallback { _ in } }, + { _ = handler.launchDate }, + { _ = handler.isActivePrewarm }, + { handler.setApplicationDidBecomeActiveCallback { _ in } } ], iterations: 1_000 ) From f4d4fef4693ae4348a0d373cbc905f81cffdd19d Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Mon, 24 Oct 2022 13:49:33 +0200 Subject: [PATCH 3/5] Apply suggestions from code review Co-Authored-By: Maciek Grzybowski --- .../xcschemes/DatadogIntegrationTests.xcscheme | 1 - .../Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift | 4 ++-- Sources/_Datadog_Private/ObjcAppLaunchHandler.m | 2 +- .../RUM/RUMMonitor/Scopes/RUMViewScopeTests.swift | 11 ++--------- 4 files changed, 5 insertions(+), 13 deletions(-) diff --git a/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogIntegrationTests.xcscheme b/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogIntegrationTests.xcscheme index 5d40d059a2..1b7b188ffe 100644 --- a/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogIntegrationTests.xcscheme +++ b/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/DatadogIntegrationTests.xcscheme @@ -180,7 +180,6 @@ buildConfiguration = "Integration" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - enableThreadSanitizer = "YES" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift index 7958880336..37f0faa917 100644 --- a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift +++ b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift @@ -324,11 +324,11 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { } else if let launchDate = context.launchTime.launchDate { // The launchTime can be `nil` if the application is not yet // active (UIApplicationDidBecomeActiveNotification). That is - // the case when instrumenting a SwiftUI application that start + // the case when instrumenting a SwiftUI application that starts // a RUM view on `SwiftUI.View.onAppear`. // // In that case, we consider the time between the application - // launch and the view start as the application loadint time. + // launch and the view start as the application loading time. loadingTime = viewStartTime.timeIntervalSince(launchDate).toInt64Nanoseconds } diff --git a/Sources/_Datadog_Private/ObjcAppLaunchHandler.m b/Sources/_Datadog_Private/ObjcAppLaunchHandler.m index ab9ca65965..e7083ae387 100644 --- a/Sources/_Datadog_Private/ObjcAppLaunchHandler.m +++ b/Sources/_Datadog_Private/ObjcAppLaunchHandler.m @@ -16,7 +16,7 @@ /// Get the process start time from kernel. /// -/// The time intervale is related to the 1 January 2001 00:00:00 GMT reference date. +/// The time interval is related to the 1 January 2001 00:00:00 GMT reference date. /// /// - Parameter timeInterval: Pointer to time interval to hold the process start time interval. int processStartTimeIntervalSinceReferenceDate(NSTimeInterval *timeInterval); diff --git a/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMViewScopeTests.swift b/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMViewScopeTests.swift index 5b12c8d3f0..3ccda62a22 100644 --- a/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMViewScopeTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMViewScopeTests.swift @@ -134,14 +134,7 @@ class RUMViewScopeTests: XCTestCase { // Given let currentTime: Date = .mockDecember15th2019At10AMUTC() let source = String.mockAnySource() - let custonContext: DatadogContext = .mockWith(source: source) - - var context = self.context - context.launchTime = .init( - launchTime: 2, - launchDate: nil, - isActivePrewarm: false - ) + let customContext: DatadogContext = .mockWith(source: source) let scope = RUMViewScope( isInitialView: true, @@ -159,7 +152,7 @@ class RUMViewScopeTests: XCTestCase { // When _ = scope.process( command: RUMCommandMock(time: currentTime), - context: custonContext, + context: customContext, writer: writer ) From 3be6dc8229bceac20335cf6f88c39871739db77c Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Mon, 24 Oct 2022 14:55:04 +0200 Subject: [PATCH 4/5] RUMM-2606 Make launchTime optional --- .../DatadogCore/Context/LaunchTimePublisher.swift | 11 +++++++---- .../DatadogInternal/Context/DatadogContext.swift | 4 ++-- .../DatadogInternal/Context/LaunchTime.swift | 10 ++-------- .../RUM/RUMMonitor/Scopes/RUMViewScope.swift | 13 +++++++------ Tests/DatadogBenchmarkTests/BenchmarkMocks.swift | 2 +- .../DatadogCore/LaunchTimePublisherTests.swift | 6 +++--- .../RUM/RUMMonitor/Scopes/RUMViewScopeTests.swift | 8 ++++++-- 7 files changed, 28 insertions(+), 26 deletions(-) diff --git a/Sources/Datadog/DatadogCore/Context/LaunchTimePublisher.swift b/Sources/Datadog/DatadogCore/Context/LaunchTimePublisher.swift index 04125657e6..2e3531ce2a 100644 --- a/Sources/Datadog/DatadogCore/Context/LaunchTimePublisher.swift +++ b/Sources/Datadog/DatadogCore/Context/LaunchTimePublisher.swift @@ -13,7 +13,7 @@ import _Datadog_Private internal struct LaunchTimePublisher: ContextValuePublisher { private typealias AppLaunchHandler = __dd_private_AppLaunchHandler - let initialValue: LaunchTime + let initialValue: LaunchTime? init() { initialValue = LaunchTime( @@ -23,12 +23,15 @@ internal struct LaunchTimePublisher: ContextValuePublisher { ) } - func publish(to receiver: @escaping ContextValueReceiver) { + func publish(to receiver: @escaping ContextValueReceiver) { + let launchDate = AppLaunchHandler.shared.launchDate + let isActivePrewarm = AppLaunchHandler.shared.isActivePrewarm + AppLaunchHandler.shared.setApplicationDidBecomeActiveCallback { launchTime in let value = LaunchTime( launchTime: launchTime, - launchDate: initialValue.launchDate, - isActivePrewarm: initialValue.isActivePrewarm + launchDate: launchDate, + isActivePrewarm: isActivePrewarm ) receiver(value) } diff --git a/Sources/Datadog/DatadogInternal/Context/DatadogContext.swift b/Sources/Datadog/DatadogInternal/Context/DatadogContext.swift index 3c6a9884dc..23f6198d8d 100644 --- a/Sources/Datadog/DatadogInternal/Context/DatadogContext.swift +++ b/Sources/Datadog/DatadogInternal/Context/DatadogContext.swift @@ -61,8 +61,8 @@ public struct DatadogContext { /// Application launch time. /// - /// Can be `zero` if the launch could not yet been evaluated. - var launchTime: LaunchTime = .zero + /// Can be `nil` if the launch could not yet been evaluated. + var launchTime: LaunchTime? /// Provides the history of app foreground / background states. var applicationStateHistory: AppStateHistory diff --git a/Sources/Datadog/DatadogInternal/Context/LaunchTime.swift b/Sources/Datadog/DatadogInternal/Context/LaunchTime.swift index 97b40b8c51..08249d9144 100644 --- a/Sources/Datadog/DatadogInternal/Context/LaunchTime.swift +++ b/Sources/Datadog/DatadogInternal/Context/LaunchTime.swift @@ -15,15 +15,9 @@ import Foundation /// time this value is provided, it will represent the time interval between now and the process start time. /* public */ let launchTime: TimeInterval? - /* public */ let launchDate: Date? + /// The date when the application process started. + /* public */ let launchDate: Date /// Returns `true` if the application is pre-warmed. /* public */ let isActivePrewarm: Bool } - -extension LaunchTime { - /// Returns a zero launch time with inactive pre-warm. - /* public */ static var zero: LaunchTime { - .init(launchTime: 0, launchDate: nil, isActivePrewarm: false) - } -} diff --git a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift index 37f0faa917..240df78113 100644 --- a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift +++ b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift @@ -314,21 +314,22 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { var attributes = self.attributes var loadingTime: Int64? = nil - if context.launchTime.isActivePrewarm { + if context.launchTime?.isActivePrewarm == true { // Set `active_pre_warm` attribute to true in case // of pre-warmed app. attributes[Constants.activePrewarm] = true - } else if let launchTime = context.launchTime.launchTime { + } else if let launchTime = context.launchTime?.launchTime { // Report Application Launch Time only if not pre-warmed loadingTime = launchTime.toInt64Nanoseconds - } else if let launchDate = context.launchTime.launchDate { + } else if let launchDate = context.launchTime?.launchDate { // The launchTime can be `nil` if the application is not yet // active (UIApplicationDidBecomeActiveNotification). That is - // the case when instrumenting a SwiftUI application that starts - // a RUM view on `SwiftUI.View.onAppear`. + // the case when instrumenting a SwiftUI application that start + // a RUM view on `SwiftUI.View/onAppear`. // // In that case, we consider the time between the application - // launch and the view start as the application loading time. + // launch and the first view start as the application loading + // time. loadingTime = viewStartTime.timeIntervalSince(launchDate).toInt64Nanoseconds } diff --git a/Tests/DatadogBenchmarkTests/BenchmarkMocks.swift b/Tests/DatadogBenchmarkTests/BenchmarkMocks.swift index 35a61fb625..6ffc1db58a 100644 --- a/Tests/DatadogBenchmarkTests/BenchmarkMocks.swift +++ b/Tests/DatadogBenchmarkTests/BenchmarkMocks.swift @@ -46,7 +46,7 @@ extension DatadogContext: AnyMockable { sdkInitDate: .mockRandomInThePast(), device: DeviceInfo(), userInfo: nil, - launchTime: .zero, + launchTime: nil, applicationStateHistory: .active(since: Date()), networkConnectionInfo: .unknown, carrierInfo: nil, diff --git a/Tests/DatadogTests/Datadog/DatadogCore/LaunchTimePublisherTests.swift b/Tests/DatadogTests/Datadog/DatadogCore/LaunchTimePublisherTests.swift index a4e3023bff..e75788c4c8 100644 --- a/Tests/DatadogTests/Datadog/DatadogCore/LaunchTimePublisherTests.swift +++ b/Tests/DatadogTests/Datadog/DatadogCore/LaunchTimePublisherTests.swift @@ -21,7 +21,7 @@ class LaunchTimePublisherTests: XCTestCase { let launchTime = publisher.initialValue // Then - XCTAssertNotNil(launchTime.launchDate) + XCTAssertNotNil(launchTime?.launchDate) } func testThreadSafety() { @@ -49,7 +49,7 @@ class LaunchTimePublisherTests: XCTestCase { let publisher = LaunchTimePublisher() // Then - XCTAssertTrue(publisher.initialValue.isActivePrewarm) + XCTAssertTrue(publisher.initialValue?.isActivePrewarm ?? false) } func testIsActivePrewarm_returnsFalse() { @@ -60,6 +60,6 @@ class LaunchTimePublisherTests: XCTestCase { let publisher = LaunchTimePublisher() // Then - XCTAssertFalse(publisher.initialValue.isActivePrewarm) + XCTAssertFalse(publisher.initialValue?.isActivePrewarm ?? true) } } diff --git a/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMViewScopeTests.swift b/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMViewScopeTests.swift index 3ccda62a22..29e9c6fa48 100644 --- a/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMViewScopeTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/RUMMonitor/Scopes/RUMViewScopeTests.swift @@ -88,7 +88,11 @@ class RUMViewScopeTests: XCTestCase { // Given let currentTime: Date = .mockDecember15th2019At10AMUTC() var context = self.context - context.launchTime = .init(launchTime: 2, launchDate: nil, isActivePrewarm: false) + context.launchTime = .init( + launchTime: 2, + launchDate: .distantPast, + isActivePrewarm: false + ) let scope = RUMViewScope( isInitialView: true, @@ -196,7 +200,7 @@ class RUMViewScopeTests: XCTestCase { var context = self.context context.launchTime = .init( launchTime: 2, - launchDate: nil, + launchDate: .distantPast, isActivePrewarm: true ) From e7474974f075d11bc3ac960c9c3604e9c865a0bb Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Wed, 2 Nov 2022 12:38:42 +0100 Subject: [PATCH 5/5] Update Sources/Datadog/DatadogInternal/Context/LaunchTime.swift Co-authored-by: Maciek Grzybowski --- Sources/Datadog/DatadogInternal/Context/LaunchTime.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Sources/Datadog/DatadogInternal/Context/LaunchTime.swift b/Sources/Datadog/DatadogInternal/Context/LaunchTime.swift index 08249d9144..82dfc811f1 100644 --- a/Sources/Datadog/DatadogInternal/Context/LaunchTime.swift +++ b/Sources/Datadog/DatadogInternal/Context/LaunchTime.swift @@ -11,8 +11,7 @@ import Foundation /// The app process launch duration (in seconds) measured as the time from process start time /// to receiving `UIApplication.didBecomeActiveNotification` notification. /// - /// If the `UIApplication.didBecomeActiveNotification` has not yet been received by the - /// time this value is provided, it will represent the time interval between now and the process start time. + /// If the `UIApplication.didBecomeActiveNotification` has not yet been received the value will be `nil`. /* public */ let launchTime: TimeInterval? /// The date when the application process started.