-
Notifications
You must be signed in to change notification settings - Fork 1.7k
WIP!!! - Fix app start trace outliers from network delays (#10733) #15409
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,6 +30,7 @@ | |
static NSDate *doubleDispatchTime = nil; | ||
static NSDate *applicationDidFinishLaunchTime = nil; | ||
static NSTimeInterval gAppStartMaxValidDuration = 60 * 60; // 60 minutes. | ||
static NSTimeInterval gAppStartReasonableValidDuration = 30.0; // 30 seconds, reasonable app start time??? | ||
static FPRCPUGaugeData *gAppStartCPUGaugeData = nil; | ||
static FPRMemoryGaugeData *gAppStartMemoryGaugeData = nil; | ||
static BOOL isActivePrewarm = NO; | ||
|
@@ -71,6 +72,9 @@ @interface FPRAppActivityTracker () | |
/** Tracks if the gauge metrics are dispatched. */ | ||
@property(nonatomic) BOOL appStartGaugeMetricDispatched; | ||
|
||
/** Tracks if TTI stage has been started for this instance. */ | ||
@property(nonatomic) BOOL ttiStageStarted; | ||
|
||
/** Firebase Performance Configuration object */ | ||
@property(nonatomic) FPRConfigurations *configurations; | ||
|
||
|
@@ -135,6 +139,7 @@ - (instancetype)initAppActivityTracker { | |
if (self != nil) { | ||
_applicationState = FPRApplicationStateUnknown; | ||
_appStartGaugeMetricDispatched = NO; | ||
_ttiStageStarted = NO; | ||
_configurations = [FPRConfigurations sharedInstance]; | ||
[self startTrackingNetwork]; | ||
} | ||
|
@@ -250,7 +255,7 @@ - (void)appDidBecomeActiveNotification:(NSNotification *)notification { | |
[self.appStartTrace startStageNamed:kFPRAppStartStageNameTimeToFirstDraw]; | ||
}); | ||
|
||
// If ever the app start trace had it life in background stage, do not send the trace. | ||
// If ever the app start trace had its life in background stage, do not send the trace. | ||
if (self.appStartTrace.backgroundTraceState != FPRTraceStateForegroundOnly) { | ||
self.appStartTrace = nil; | ||
} | ||
|
@@ -266,29 +271,46 @@ - (void)appDidBecomeActiveNotification:(NSNotification *)notification { | |
self.foregroundSessionTrace = appTrace; | ||
|
||
// Start measuring time to make the app interactive on the App start trace. | ||
static BOOL TTIStageStarted = NO; | ||
if (!TTIStageStarted) { | ||
if (!self.ttiStageStarted) { | ||
[self.appStartTrace startStageNamed:kFPRAppStartStageNameTimeToUserInteraction]; | ||
TTIStageStarted = YES; | ||
|
||
// Assumption here is that - the app becomes interactive in the next runloop cycle. | ||
// It is possible that the app does more things later, but for now we are not measuring that. | ||
dispatch_async(dispatch_get_main_queue(), ^{ | ||
NSTimeInterval startTimeSinceEpoch = [self.appStartTrace startTimeSinceEpoch]; | ||
NSTimeInterval currentTimeSinceEpoch = [[NSDate date] timeIntervalSince1970]; | ||
|
||
// The below check is to account for 2 scenarios. | ||
// 1. The app gets started in the background and might come to foreground a lot later. | ||
// 2. The app is launched, but immediately backgrounded for some reason and the actual launch | ||
// happens a lot later. | ||
// Dropping the app start trace in such situations where the launch time is taking more than | ||
// 60 minutes. This is an approximation, but a more agreeable timelimit for app start. | ||
if ((currentTimeSinceEpoch - startTimeSinceEpoch < gAppStartMaxValidDuration) && | ||
[self isAppStartEnabled] && ![self isApplicationPreWarmed]) { | ||
[self.appStartTrace stop]; | ||
} else { | ||
[self.appStartTrace cancel]; | ||
} | ||
self.ttiStageStarted = YES; | ||
|
||
// Use dispatch_async with a higher priority queue to reduce interference from network | ||
// operations This ensures trace completion isn't delayed by main queue congestion from network | ||
// calls | ||
__weak typeof(self) weakSelf = self; | ||
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{ | ||
dispatch_async(dispatch_get_main_queue(), ^{ | ||
__strong typeof(weakSelf) strongSelf = weakSelf; | ||
if (!strongSelf || !strongSelf.appStartTrace) { | ||
return; | ||
} | ||
|
||
NSTimeInterval startTimeSinceEpoch = [strongSelf.appStartTrace startTimeSinceEpoch]; | ||
NSTimeInterval currentTimeSinceEpoch = [[NSDate date] timeIntervalSince1970]; | ||
NSTimeInterval elapsed = currentTimeSinceEpoch - startTimeSinceEpoch; | ||
|
||
// The below check accounts for multiple scenarios: | ||
// 1. App started in background and comes to foreground later | ||
// 2. App launched but immediately backgrounded | ||
// 3. Network delays during startup inflating metrics | ||
BOOL shouldCompleteTrace = (elapsed < gAppStartMaxValidDuration) && | ||
[strongSelf isAppStartEnabled] && | ||
![strongSelf isApplicationPreWarmed]; | ||
|
||
// Additional safety: cancel if elapsed time is unreasonably long for app start | ||
if (shouldCompleteTrace && elapsed < gAppStartReasonableValidDuration) { | ||
[strongSelf.appStartTrace stop]; | ||
} else { | ||
[strongSelf.appStartTrace cancel]; | ||
if (elapsed >= gAppStartReasonableValidDuration) { | ||
// Log for debugging network related delays | ||
NSLog( | ||
@"Firebase Performance: App start trace cancelled due to excessive duration: %.2fs", | ||
elapsed); | ||
} | ||
} | ||
}); | ||
}); | ||
Comment on lines
+278
to
314
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The nested A single The use of // Defer stopping the trace to the next run loop cycle. This is to ensure that the app is
// fully interactive.
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
if (!strongSelf || !strongSelf.appStartTrace) {
return;
}
NSTimeInterval startTimeSinceEpoch = [strongSelf.appStartTrace startTimeSinceEpoch];
NSTimeInterval currentTimeSinceEpoch = [[NSDate date] timeIntervalSince1970];
NSTimeInterval elapsed = currentTimeSinceEpoch - startTimeSinceEpoch;
// The below check accounts for multiple scenarios:
// 1. App started in background and comes to foreground later
// 2. App launched but immediately backgrounded
// 3. Network delays during startup inflating metrics
BOOL shouldCompleteTrace = (elapsed < gAppStartMaxValidDuration) &&
[strongSelf isAppStartEnabled] &&
![strongSelf isApplicationPreWarmed];
// Additional safety: cancel if elapsed time is unreasonably long for app start
if (shouldCompleteTrace && elapsed < gAppStartReasonableValidDuration) {
[strongSelf.appStartTrace stop];
} else {
[strongSelf.appStartTrace cancel];
if (elapsed >= gAppStartReasonableValidDuration) {
// Log for debugging network related delays
NSLog(
@"Firebase Performance: App start trace cancelled due to excessive duration: %.2fs",
elapsed);
}
}
}); |
||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
???
in the comment suggests uncertainty. Please remove it for a more polished and professional code appearance.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will Fix, I may need input from @visumickey to see if this is ideal or an acceptable time (Hence the ???)