From 30b4afc8e6dfb9bf27482aaf043b96daee757523 Mon Sep 17 00:00:00 2001 From: maxep Date: Fri, 15 Oct 2021 18:42:44 +0200 Subject: [PATCH] RUMM-1615 Allow launch time earlier than `UIApplicationDidBecomeActiveNotification` --- .../Core/System/LaunchTimeProvider.swift | 6 ++--- .../RUM/RUMMonitor/Scopes/RUMViewScope.swift | 2 +- .../_Datadog_Private/ObjcAppLaunchHandler.m | 24 ++++++++++--------- .../include/ObjcAppLaunchHandler.h | 5 ++++ .../Core/System/LaunchTimeProviderTests.swift | 2 +- .../Datadog/Mocks/CoreMocks.swift | 2 +- 6 files changed, 24 insertions(+), 17 deletions(-) diff --git a/Sources/Datadog/Core/System/LaunchTimeProvider.swift b/Sources/Datadog/Core/System/LaunchTimeProvider.swift index 5e8165ba1d..4b98f7c544 100644 --- a/Sources/Datadog/Core/System/LaunchTimeProvider.swift +++ b/Sources/Datadog/Core/System/LaunchTimeProvider.swift @@ -14,15 +14,15 @@ import _Datadog_Private internal protocol LaunchTimeProviderType { /// The app process launch duration (in seconds) measured as the time from loading the first SDK object into memory /// to receiving `UIApplication.didBecomeActiveNotification` notification. - var launchTime: TimeInterval? { get } + var launchTime: TimeInterval { get } } internal class LaunchTimeProvider: LaunchTimeProviderType { - var launchTime: TimeInterval? { + var launchTime: TimeInterval { // 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(self) let time = __dd_private_AppLaunchTime() objc_sync_exit(self) - return time > 0 ? time : nil + return time } } diff --git a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift index e756255696..d9ee9651b8 100644 --- a/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift +++ b/Sources/Datadog/RUM/RUMMonitor/Scopes/RUMViewScope.swift @@ -311,7 +311,7 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { crash: nil, error: nil, id: dependencies.rumUUIDGenerator.generateUnique().toRUMDataFormat, - loadingTime: dependencies.launchTimeProvider.launchTime?.toInt64Nanoseconds, + loadingTime: dependencies.launchTimeProvider.launchTime.toInt64Nanoseconds, longTask: nil, resource: nil, target: nil, diff --git a/Sources/_Datadog_Private/ObjcAppLaunchHandler.m b/Sources/_Datadog_Private/ObjcAppLaunchHandler.m index 5656944278..386f4c65b0 100644 --- a/Sources/_Datadog_Private/ObjcAppLaunchHandler.m +++ b/Sources/_Datadog_Private/ObjcAppLaunchHandler.m @@ -14,6 +14,9 @@ // 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; NS_INLINE NSTimeInterval QueryProcessStartTimeWithFallback(NSTimeInterval fallbackTime) { @@ -41,14 +44,10 @@ NS_INLINE NSTimeInterval QueryProcessStartTimeWithFallback(NSTimeInterval fallba return processStartTime; } -NS_INLINE void ComputeTimeToApplicationDidBecomeActiveWithFallback(NSTimeInterval fallbackTime) { - if (TimeToApplicationDidBecomeActive > 0) { - return; - } - +NS_INLINE NSTimeInterval ComputeProcessTimeFromStart() { NSTimeInterval now = CFAbsoluteTimeGetCurrent(); - NSTimeInterval processStartTime = QueryProcessStartTimeWithFallback(fallbackTime); - TimeToApplicationDidBecomeActive = now - processStartTime; + NSTimeInterval processStartTime = QueryProcessStartTimeWithFallback(FrameworkLoadTime); + return now - processStartTime; } @interface AppLaunchHandler : NSObject @@ -58,8 +57,10 @@ @implementation AppLaunchHandler + (void)load { // This is called at the `_Datadog_Private` load time, keep the work minimal - NSTimeInterval frameworkLoadTime = CFAbsoluteTimeGetCurrent(); - id __block token = [NSNotificationCenter.defaultCenter + FrameworkLoadTime = CFAbsoluteTimeGetCurrent(); + + NSNotificationCenter * __weak center = NSNotificationCenter.defaultCenter; + id __block token = [center addObserverForName:UIApplicationDidBecomeActiveNotification object:nil queue:NSOperationQueue.mainQueue @@ -67,10 +68,10 @@ + (void)load { pthread_rwlock_init(&rwLock, NULL); pthread_rwlock_wrlock(&rwLock); - ComputeTimeToApplicationDidBecomeActiveWithFallback(frameworkLoadTime); + TimeToApplicationDidBecomeActive = ComputeProcessTimeFromStart(); pthread_rwlock_unlock(&rwLock); - [NSNotificationCenter.defaultCenter removeObserver:token]; + [center removeObserver:token]; }]; } @@ -79,6 +80,7 @@ + (void)load { CFTimeInterval __dd_private_AppLaunchTime() { pthread_rwlock_rdlock(&rwLock); CFTimeInterval time = TimeToApplicationDidBecomeActive; + if (time == 0) time = ComputeProcessTimeFromStart(); pthread_rwlock_unlock(&rwLock); return time; } diff --git a/Sources/_Datadog_Private/include/ObjcAppLaunchHandler.h b/Sources/_Datadog_Private/include/ObjcAppLaunchHandler.h index 0707c114ca..430455a0b3 100644 --- a/Sources/_Datadog_Private/include/ObjcAppLaunchHandler.h +++ b/Sources/_Datadog_Private/include/ObjcAppLaunchHandler.h @@ -6,4 +6,9 @@ #import +/// 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); diff --git a/Tests/DatadogTests/Datadog/Core/System/LaunchTimeProviderTests.swift b/Tests/DatadogTests/Datadog/Core/System/LaunchTimeProviderTests.swift index 89f9887dd5..9a108040fa 100644 --- a/Tests/DatadogTests/Datadog/Core/System/LaunchTimeProviderTests.swift +++ b/Tests/DatadogTests/Datadog/Core/System/LaunchTimeProviderTests.swift @@ -16,7 +16,7 @@ class LaunchTimeProviderTests: XCTestCase { var values: [TimeInterval] = [] (0..<10).forEach { _ in Thread.sleep(forTimeInterval: 0.01) - values.append(provider.launchTime!) + values.append(provider.launchTime) } // Then diff --git a/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift b/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift index 164503513c..092414ea6f 100644 --- a/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift +++ b/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift @@ -625,7 +625,7 @@ class DateCorrectorMock: DateCorrectorType { } struct LaunchTimeProviderMock: LaunchTimeProviderType { - var launchTime: TimeInterval? = nil + var launchTime: TimeInterval = 0 } extension UserInfo: AnyMockable, RandomMockable {