Skip to content

Commit 04515e6

Browse files
committed
Cache slow frame budget and implement a refresh on app active
1 parent fc10cc9 commit 04515e6

File tree

3 files changed

+41
-6
lines changed

3 files changed

+41
-6
lines changed

FirebasePerformance/Sources/AppActivity/FPRScreenTraceTracker+Private.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,14 @@ FOUNDATION_EXTERN NSString *const kFPRTotalFramesCounterName;
3939

4040
/** Legacy slow frame threshold constant (formerly 1/59).
4141
* NOTE: This constant is deprecated and maintained only for test compatibility.
42-
* The actual slow frame detection now uses UIScreen.maximumFramesPerSecond dynamically.
42+
* The actual slow frame detection uses a cached value computed from
43+
* UIScreen.maximumFramesPerSecond (slow threshold = 1000 / maxFPS ms).
4344
* New code should not rely on this value.
4445
*/
4546
FOUNDATION_EXTERN CFTimeInterval const kFPRSlowFrameThreshold;
4647

4748
/** Frozen frame threshold (for time difference between current and previous frame render time)
48-
* in sec.
49+
* in sec. Frozen threshold = 700 ms (>700 ms).
4950
*/
5051
FOUNDATION_EXTERN CFTimeInterval const kFPRFrozenFrameThreshold;
5152

FirebasePerformance/Sources/AppActivity/FPRScreenTraceTracker.m

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,13 @@
2727

2828
// Note: The slow frame threshold is now dynamically computed based on UIScreen's
2929
// maximumFramesPerSecond to align with device capabilities (ProMotion, tvOS, etc.).
30+
// Slow threshold = 1000 / UIScreen.maximumFramesPerSecond ms (or 1.0 / maxFPS seconds).
3031
// For devices reporting 60 FPS, the threshold is approximately 16.67ms (1/60).
32+
// The threshold is cached and refreshed when the app becomes active.
3133
// TODO(b/73498642): Make these configurable.
3234

3335
// Legacy constant maintained for test compatibility. The actual slow frame detection
34-
// uses FPRSlowBudgetSeconds() which queries UIScreen.maximumFramesPerSecond dynamically.
36+
// uses a cached value computed from UIScreen.maximumFramesPerSecond.
3537
CFTimeInterval const kFPRSlowFrameThreshold = 1.0 / 60.0;
3638

3739
CFTimeInterval const kFPRFrozenFrameThreshold = 700.0 / 1000.0;
@@ -91,6 +93,14 @@ static inline CFTimeInterval FPRSlowBudgetSeconds(void) {
9193
}
9294
}
9395

96+
@interface FPRScreenTraceTracker ()
97+
98+
@property(nonatomic) NSInteger fpr_cachedMaxFPS;
99+
100+
@property(nonatomic) CFTimeInterval fpr_cachedSlowBudget;
101+
102+
@end
103+
94104
@implementation FPRScreenTraceTracker {
95105
/** Instance variable storing the total frames observed so far. */
96106
atomic_int_fast64_t _totalFramesCount;
@@ -146,6 +156,17 @@ - (instancetype)init {
146156
selector:@selector(appWillResignActiveNotification:)
147157
name:UIApplicationWillResignActiveNotification
148158
object:[UIApplication sharedApplication]];
159+
160+
// Initialize cached FPS and slow budget
161+
NSInteger __fps = UIScreen.mainScreen.maximumFramesPerSecond ?: 60;
162+
self.fpr_cachedMaxFPS = __fps;
163+
self.fpr_cachedSlowBudget = 1.0 / (double)__fps;
164+
165+
// Observe app becoming active to refresh FPS cache
166+
[[NSNotificationCenter defaultCenter] addObserver:self
167+
selector:@selector(appDidBecomeActive:)
168+
name:UIApplicationDidBecomeActiveNotification
169+
object:nil];
149170
}
150171
return self;
151172
}
@@ -156,6 +177,9 @@ - (void)dealloc {
156177
[[NSNotificationCenter defaultCenter] removeObserver:self
157178
name:UIApplicationDidBecomeActiveNotification
158179
object:[UIApplication sharedApplication]];
180+
[[NSNotificationCenter defaultCenter] removeObserver:self
181+
name:UIApplicationDidBecomeActiveNotification
182+
object:nil];
159183
[[NSNotificationCenter defaultCenter] removeObserver:self
160184
name:UIApplicationWillResignActiveNotification
161185
object:[UIApplication sharedApplication]];
@@ -179,6 +203,12 @@ - (void)appDidBecomeActiveNotification:(NSNotification *)notification {
179203
});
180204
}
181205

206+
- (void)appDidBecomeActive:(NSNotification *)note {
207+
NSInteger __fps = UIScreen.mainScreen.maximumFramesPerSecond ?: 60;
208+
self.fpr_cachedMaxFPS = __fps;
209+
self.fpr_cachedSlowBudget = 1.0 / (double)__fps;
210+
}
211+
182212
- (void)appWillResignActiveNotification:(NSNotification *)notification {
183213
// To get the most accurate numbers of total, frozen and slow frames, we need to capture them as
184214
// soon as we're notified of an event.
@@ -208,8 +238,9 @@ - (void)appWillResignActiveNotification:(NSNotification *)notification {
208238
- (void)displayLinkStep {
209239
static CFAbsoluteTime previousTimestamp = kFPRInvalidTime;
210240
CFAbsoluteTime currentTimestamp = self.displayLink.timestamp;
211-
RecordFrameType(currentTimestamp, previousTimestamp, &_slowFramesCount, &_frozenFramesCount,
212-
&_totalFramesCount);
241+
CFTimeInterval slowBudget = self.fpr_cachedSlowBudget;
242+
RecordFrameType(currentTimestamp, previousTimestamp, slowBudget, &_slowFramesCount,
243+
&_frozenFramesCount, &_totalFramesCount);
213244
previousTimestamp = currentTimestamp;
214245
}
215246

@@ -218,21 +249,22 @@ - (void)displayLinkStep {
218249
*
219250
* @param currentTimestamp The current timestamp of the displayLink.
220251
* @param previousTimestamp The previous timestamp of the displayLink.
252+
* @param slowBudget The cached slow frame budget in seconds (1.0 / maxFPS).
221253
* @param slowFramesCounter The value of the slowFramesCount before this function was called.
222254
* @param frozenFramesCounter The value of the frozenFramesCount before this function was called.
223255
* @param totalFramesCounter The value of the totalFramesCount before this function was called.
224256
*/
225257
FOUNDATION_STATIC_INLINE
226258
void RecordFrameType(CFAbsoluteTime currentTimestamp,
227259
CFAbsoluteTime previousTimestamp,
260+
CFTimeInterval slowBudget,
228261
atomic_int_fast64_t *slowFramesCounter,
229262
atomic_int_fast64_t *frozenFramesCounter,
230263
atomic_int_fast64_t *totalFramesCounter) {
231264
CFTimeInterval frameDuration = currentTimestamp - previousTimestamp;
232265
if (previousTimestamp == kFPRInvalidTime) {
233266
return;
234267
}
235-
CFTimeInterval slowBudget = FPRSlowBudgetSeconds();
236268
if (frameDuration > slowBudget) {
237269
atomic_fetch_add_explicit(slowFramesCounter, 1, memory_order_relaxed);
238270
}

FirebasePerformance/Tests/Unit/FPRScreenTraceTrackerTest.m

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -969,6 +969,8 @@ + (NSString *)expectedTraceNameForViewController:(UIViewController *)viewControl
969969
/** Helper to run a single FPS test case with the given tracker and expectations. */
970970
static inline void FPRRunFpsTestCase(FPRScreenTraceTrackerTest *testCase, FPRFpsCase fpsCase) {
971971
[testCase setTestMaxFPSOverride:fpsCase.fps];
972+
// Force the tracker to recompute the cache with the overridden FPS
973+
[testCase.tracker appDidBecomeActive:nil];
972974

973975
CFAbsoluteTime firstFrameRenderTimestamp = 1.0;
974976
// Frame that should be classified as slow (exceeds budget)

0 commit comments

Comments
 (0)