Skip to content

Commit f576ad5

Browse files
Fireperf: dropping app start events when they are prewarmed. (#9104)
* Prototype of dropping app start events when they are prewarmed. * fixing a bug and adding tests for isApplicationPrewarmed * remove comments * formatting * added a plist flag to disable double dispatch * adding RC * formatting * use GULAppEnvironmentUtil for checking iOS version * add tests * formatting * comment * drop counters * dropped _as duration counter * formatting * Revert "formatting" This reverts commit bd6b82b. * Revert "dropped _as duration counter" This reverts commit ef8a901. * Revert "drop counters" This reverts commit 38d2032. * fix most PR comments * enum * formatting * move dropping all events out of isApplicationPreWarmed * fix tests * formatting * default to OnlyActivePrewarm * code review changes * formatting * fix names in comments * comments * formatting * fix test name Co-authored-by: Leo Zhan <[email protected]>
1 parent b1a7034 commit f576ad5

File tree

6 files changed

+306
-1
lines changed

6 files changed

+306
-1
lines changed

FirebasePerformance/Sources/AppActivity/FPRAppActivityTracker.m

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@
2424
#import "FirebasePerformance/Sources/Timer/FIRTrace+Internal.h"
2525
#import "FirebasePerformance/Sources/Timer/FIRTrace+Private.h"
2626

27+
#import <GoogleUtilities/GULAppEnvironmentUtil.h>
28+
2729
static NSDate *appStartTime = nil;
30+
static NSDate *doubleDispatchTime = nil;
31+
static NSDate *applicationDidFinishLaunchTime = nil;
2832
static NSTimeInterval gAppStartMaxValidDuration = 60 * 60; // 60 minutes.
2933
static FPRCPUGaugeData *gAppStartCPUGaugeData = nil;
3034
static FPRMemoryGaugeData *gAppStartMemoryGaugeData = nil;
@@ -56,6 +60,9 @@ @interface FPRAppActivityTracker ()
5660
/** Tracks if the gauge metrics are dispatched. */
5761
@property(nonatomic) BOOL appStartGaugeMetricDispatched;
5862

63+
/** Firebase Performance Configuration object */
64+
@property(nonatomic) FPRConfigurations *configurations;
65+
5966
/** Starts tracking app active sessions. */
6067
- (void)startAppActivityTracking;
6168

@@ -66,12 +73,28 @@ @implementation FPRAppActivityTracker
6673
+ (void)load {
6774
// This is an approximation of the app start time.
6875
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+
6987
gAppStartCPUGaugeData = fprCollectCPUMetric();
7088
gAppStartMemoryGaugeData = fprCollectMemoryMetric();
7189
[[NSNotificationCenter defaultCenter] addObserver:self
7290
selector:@selector(windowDidBecomeVisible:)
7391
name:UIWindowDidBecomeVisibleNotification
7492
object:nil];
93+
94+
[[NSNotificationCenter defaultCenter] addObserver:self
95+
selector:@selector(applicationDidFinishLaunching:)
96+
name:UIApplicationDidFinishLaunchingNotification
97+
object:nil];
7598
}
7699

77100
+ (void)windowDidBecomeVisible:(NSNotification *)notification {
@@ -83,6 +106,13 @@ + (void)windowDidBecomeVisible:(NSNotification *)notification {
83106
object:nil];
84107
}
85108

109+
+ (void)applicationDidFinishLaunching:(NSNotification *)notification {
110+
applicationDidFinishLaunchTime = [NSDate date];
111+
[[NSNotificationCenter defaultCenter] removeObserver:self
112+
name:UIApplicationDidFinishLaunchingNotification
113+
object:nil];
114+
}
115+
86116
+ (instancetype)sharedInstance {
87117
static FPRAppActivityTracker *instance;
88118
static dispatch_once_t onceToken;
@@ -99,6 +129,7 @@ - (instancetype)initAppActivityTracker {
99129
self = [super init];
100130
_applicationState = FPRApplicationStateUnknown;
101131
_appStartGaugeMetricDispatched = NO;
132+
_configurations = [FPRConfigurations sharedInstance];
102133
return self;
103134
}
104135

@@ -121,6 +152,70 @@ - (FIRTrace *)activeTrace {
121152
return self.backgroundSessionTrace;
122153
}
123154

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+
124219
/**
125220
* This gets called whenever the app becomes active. A new trace will be created to track the active
126221
* foreground session. Any background session trace that was running in the past will be stopped.
@@ -173,7 +268,8 @@ - (void)appDidBecomeActiveNotification:(NSNotification *)notification {
173268
// happens a lot later.
174269
// Dropping the app start trace in such situations where the launch time is taking more than
175270
// 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]) {
177273
[self.appStartTrace stop];
178274
} else {
179275
[self.appStartTrace cancel];

FirebasePerformance/Sources/Configurations/FPRConfigurations.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,22 @@
1616

1717
NS_ASSUME_NONNULL_BEGIN
1818

19+
/**
20+
* Different modes of prewarm-detection
21+
* KeepNone = No app start events are allowed
22+
* ActivePrewarm = Only detect prewarming using ActivePrewarm environment
23+
* DoubleDispatch = Only detect prewarming using double dispatch method
24+
* ActivePrewarmOrDoubleDispatch = Detect prewarming using both ActivePrewarm and double dispatch
25+
* KeepAll = All app start events are allowed
26+
*/
27+
typedef NS_ENUM(NSInteger, PrewarmDetectionMode) {
28+
PrewarmDetectionModeKeepNone = 0,
29+
PrewarmDetectionModeActivePrewarm = 1,
30+
PrewarmDetectionModeDoubleDispatch = 2,
31+
PrewarmDetectionModeActivePrewarmOrDoubleDispatch = 3,
32+
PrewarmDetectionModeKeepAll = 4
33+
};
34+
1935
/** A typedef for ensuring that config names are one of the below specified strings. */
2036
typedef NSString* const FPRConfigName;
2137

@@ -62,6 +78,14 @@ FOUNDATION_EXTERN FPRConfigName kFPRConfigInstrumentationEnabled;
6278

6379
#pragma mark - Configuration fetcher methods.
6480

81+
/**
82+
* Returns the mode that prewarm-detection should drop events in.
83+
*
84+
* @see PrewarmDetectionMode
85+
* @return filter mode of app start traces prewarm-detection
86+
*/
87+
- (PrewarmDetectionMode)prewarmDetectionMode;
88+
6589
/**
6690
* Returns the percentage of instances that would send trace events. Range [0-1].
6791
*

FirebasePerformance/Sources/Configurations/FPRConfigurations.m

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,15 @@ - (int)logSource {
300300
return logSource;
301301
}
302302

303+
- (PrewarmDetectionMode)prewarmDetectionMode {
304+
PrewarmDetectionMode mode = PrewarmDetectionModeActivePrewarm;
305+
if (self.remoteConfigFlags) {
306+
mode = [self.remoteConfigFlags getIntValueForFlag:@"fpr_prewarm_detection"
307+
defaultValue:(int)mode];
308+
}
309+
return mode;
310+
}
311+
303312
#pragma mark - Log sampling configurations.
304313

305314
- (float)logTraceSamplingRate {

FirebasePerformance/Sources/Configurations/FPRRemoteConfigFlags.m

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ - (instancetype)initWithRemoteConfig:(FIRRemoteConfig *)config {
103103
[keysToCache setObject:@(FPRConfigValueTypeInteger)
104104
forKey:@"fpr_session_gauge_memory_capture_frequency_bg_ms"];
105105
[keysToCache setObject:@(FPRConfigValueTypeInteger) forKey:@"fpr_session_max_duration_min"];
106+
[keysToCache setObject:@(FPRConfigValueTypeInteger) forKey:@"fpr_prewarm_detection"];
106107
self.configKeys = [keysToCache copy];
107108

108109
[self update];

FirebasePerformance/Tests/Unit/Configurations/FPRConfigurationsTest.m

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,4 +539,30 @@ - (void)testSessionMaxLengthDurationRemoteConfigOverrides {
539539
[configFlags resetCache];
540540
}
541541

542+
- (void)testPrewarmDetectionRemoteConfigOverrides {
543+
FPRConfigurations *configurations =
544+
[[FPRConfigurations alloc] initWithSources:FPRConfigurationSourceRemoteConfig];
545+
546+
FPRFakeRemoteConfig *remoteConfig = [[FPRFakeRemoteConfig alloc] init];
547+
FPRRemoteConfigFlags *configFlags =
548+
[[FPRRemoteConfigFlags alloc] initWithRemoteConfig:(FIRRemoteConfig *)remoteConfig];
549+
configurations.remoteConfigFlags = configFlags;
550+
configFlags.lastFetchedTime = [NSDate date];
551+
552+
NSUserDefaults *userDefaults = [[NSUserDefaults alloc] init];
553+
configFlags.userDefaults = userDefaults;
554+
555+
NSString *configKey =
556+
[NSString stringWithFormat:@"%@.%@", kFPRConfigPrefix, @"fpr_prewarm_detection"];
557+
[userDefaults setObject:@(0) forKey:configKey];
558+
XCTAssertEqual([configurations prewarmDetectionMode], 0);
559+
560+
[userDefaults setObject:@(1) forKey:configKey];
561+
XCTAssertEqual([configurations prewarmDetectionMode], 1);
562+
563+
[userDefaults setObject:@(2) forKey:configKey];
564+
XCTAssertEqual([configurations prewarmDetectionMode], 2);
565+
[configFlags resetCache];
566+
}
567+
542568
@end

0 commit comments

Comments
 (0)