24
24
#import " FirebasePerformance/Sources/Timer/FIRTrace+Internal.h"
25
25
#import " FirebasePerformance/Sources/Timer/FIRTrace+Private.h"
26
26
27
+ #import < GoogleUtilities/GULAppEnvironmentUtil.h>
28
+
27
29
static NSDate *appStartTime = nil ;
30
+ static NSDate *doubleDispatchTime = nil ;
31
+ static NSDate *applicationDidFinishLaunchTime = nil ;
28
32
static NSTimeInterval gAppStartMaxValidDuration = 60 * 60 ; // 60 minutes.
29
33
static FPRCPUGaugeData *gAppStartCPUGaugeData = nil ;
30
34
static FPRMemoryGaugeData *gAppStartMemoryGaugeData = nil ;
@@ -56,6 +60,9 @@ @interface FPRAppActivityTracker ()
56
60
/* * Tracks if the gauge metrics are dispatched. */
57
61
@property (nonatomic ) BOOL appStartGaugeMetricDispatched;
58
62
63
+ /* * Firebase Performance Configuration object */
64
+ @property (nonatomic ) FPRConfigurations *configurations;
65
+
59
66
/* * Starts tracking app active sessions. */
60
67
- (void )startAppActivityTracking ;
61
68
@@ -66,12 +73,28 @@ @implementation FPRAppActivityTracker
66
73
+ (void )load {
67
74
// This is an approximation of the app start time.
68
75
appStartTime = [NSDate date ];
76
+
77
+ // Double dispatch is used to detect prewarming, but if it causes hang or crash in the future
78
+ // developers can disable it by setting a plist flag "fireperf_disable_dd" to true
79
+ if ([[[NSBundle mainBundle ] objectForInfoDictionaryKey: @" fireperf_disable_dd" ] boolValue ] == NO ) {
80
+ dispatch_async (dispatch_get_main_queue (), ^{
81
+ dispatch_async (dispatch_get_main_queue (), ^{
82
+ doubleDispatchTime = [NSDate date ];
83
+ });
84
+ });
85
+ }
86
+
69
87
gAppStartCPUGaugeData = fprCollectCPUMetric ();
70
88
gAppStartMemoryGaugeData = fprCollectMemoryMetric ();
71
89
[[NSNotificationCenter defaultCenter ] addObserver: self
72
90
selector: @selector (windowDidBecomeVisible: )
73
91
name: UIWindowDidBecomeVisibleNotification
74
92
object: nil ];
93
+
94
+ [[NSNotificationCenter defaultCenter ] addObserver: self
95
+ selector: @selector (applicationDidFinishLaunching: )
96
+ name: UIApplicationDidFinishLaunchingNotification
97
+ object: nil ];
75
98
}
76
99
77
100
+ (void )windowDidBecomeVisible : (NSNotification *)notification {
@@ -83,6 +106,13 @@ + (void)windowDidBecomeVisible:(NSNotification *)notification {
83
106
object: nil ];
84
107
}
85
108
109
+ + (void )applicationDidFinishLaunching : (NSNotification *)notification {
110
+ applicationDidFinishLaunchTime = [NSDate date ];
111
+ [[NSNotificationCenter defaultCenter ] removeObserver: self
112
+ name: UIApplicationDidFinishLaunchingNotification
113
+ object: nil ];
114
+ }
115
+
86
116
+ (instancetype )sharedInstance {
87
117
static FPRAppActivityTracker *instance;
88
118
static dispatch_once_t onceToken;
@@ -99,6 +129,7 @@ - (instancetype)initAppActivityTracker {
99
129
self = [super init ];
100
130
_applicationState = FPRApplicationStateUnknown;
101
131
_appStartGaugeMetricDispatched = NO ;
132
+ _configurations = [FPRConfigurations sharedInstance ];
102
133
return self;
103
134
}
104
135
@@ -121,6 +152,70 @@ - (FIRTrace *)activeTrace {
121
152
return self.backgroundSessionTrace ;
122
153
}
123
154
155
+ /* *
156
+ * Checks if prewarming is available for the platform on current device.
157
+ * It is available when running iOS 15 and above.
158
+ *
159
+ * @return true if the platform could prewarm apps on the current device
160
+ */
161
+ + (BOOL )isPrewarmAvailable {
162
+ if (![[[GULAppEnvironmentUtil applePlatform ] lowercaseString ] isEqualToString: @" ios" ]) {
163
+ return NO ;
164
+ }
165
+ NSString *systemVersion = [GULAppEnvironmentUtil systemVersion ];
166
+ if ([systemVersion length ] == 0 ) {
167
+ return NO ;
168
+ }
169
+ return [systemVersion compare: @" 15" options: NSNumericSearch] != NSOrderedAscending;
170
+ }
171
+
172
+ /* *
173
+ RC flag for dropping all app start events
174
+ */
175
+ - (BOOL )isAppStartEnabled {
176
+ return [self .configurations prewarmDetectionMode ] != PrewarmDetectionModeKeepNone;
177
+ }
178
+
179
+ /* *
180
+ RC flag for enabling prewarm-detection using ActivePrewarm environment variable
181
+ */
182
+ - (BOOL )isActivePrewarmEnabled {
183
+ PrewarmDetectionMode mode = [self .configurations prewarmDetectionMode ];
184
+ return (mode == PrewarmDetectionModeActivePrewarm ||
185
+ mode == PrewarmDetectionModeActivePrewarmOrDoubleDispatch);
186
+ }
187
+
188
+ /* *
189
+ RC flag for enabling prewarm-detection using double dispatch method
190
+ */
191
+ - (BOOL )isDoubleDispatchEnabled {
192
+ PrewarmDetectionMode mode = [self .configurations prewarmDetectionMode ];
193
+ return (mode == PrewarmDetectionModeDoubleDispatch ||
194
+ mode == PrewarmDetectionModeActivePrewarmOrDoubleDispatch);
195
+ }
196
+
197
+ /* *
198
+ Checks if the current app start is a prewarmed app start
199
+ */
200
+ - (BOOL )isApplicationPreWarmed {
201
+ if (![FPRAppActivityTracker isPrewarmAvailable ]) {
202
+ return NO ;
203
+ }
204
+
205
+ NSDictionary <NSString *, NSString *> *environment = [NSProcessInfo processInfo ].environment ;
206
+ BOOL activePrewarmFlagValue = [environment[@" ActivePrewarm" ] boolValue ];
207
+ if ([self isActivePrewarmEnabled ] && activePrewarmFlagValue == YES ) {
208
+ return YES ;
209
+ }
210
+
211
+ if ([self isDoubleDispatchEnabled ] &&
212
+ [doubleDispatchTime compare: applicationDidFinishLaunchTime] == NSOrderedAscending) {
213
+ return YES ;
214
+ }
215
+
216
+ return NO ;
217
+ }
218
+
124
219
/* *
125
220
* This gets called whenever the app becomes active. A new trace will be created to track the active
126
221
* foreground session. Any background session trace that was running in the past will be stopped.
@@ -173,7 +268,8 @@ - (void)appDidBecomeActiveNotification:(NSNotification *)notification {
173
268
// happens a lot later.
174
269
// Dropping the app start trace in such situations where the launch time is taking more than
175
270
// 60 minutes. This is an approximation, but a more agreeable timelimit for app start.
176
- if (currentTimeSinceEpoch - startTimeSinceEpoch < gAppStartMaxValidDuration ) {
271
+ if ((currentTimeSinceEpoch - startTimeSinceEpoch < gAppStartMaxValidDuration ) &&
272
+ [self isAppStartEnabled ] && ![self isApplicationPreWarmed ]) {
177
273
[self .appStartTrace stop ];
178
274
} else {
179
275
[self .appStartTrace cancel ];
0 commit comments