Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,6 @@ FOUNDATION_EXTERN NSString *const kFPRSlowFrameCounterName;
/** Counter name for total frames. */
FOUNDATION_EXTERN NSString *const kFPRTotalFramesCounterName;

/** Slow frame threshold (for time difference between current and previous frame render time)
* in sec.
*/
FOUNDATION_EXTERN CFTimeInterval const kFPRSlowFrameThreshold;

/** Frozen frame threshold (for time difference between current and previous frame render time)
* in sec.
*/
FOUNDATION_EXTERN CFTimeInterval const kFPRFrozenFrameThreshold;

@interface FPRScreenTraceTracker ()

/** A map table of that has the viewControllers as the keys and their associated trace as the value.
Expand Down
49 changes: 39 additions & 10 deletions FirebasePerformance/Sources/AppActivity/FPRScreenTraceTracker.m
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,21 @@
NSString *const kFPRSlowFrameCounterName = @"_fr_slo";
NSString *const kFPRTotalFramesCounterName = @"_fr_tot";

// Note: This was previously 60 FPS, but that resulted in 90% + of all frames collected to be
// flagged as slow frames, and so the threshold for iOS is being changed to 59 FPS.
// TODO(b/73498642): Make these configurable.
CFTimeInterval const kFPRSlowFrameThreshold = 1.0 / 59.0; // Anything less than 59 FPS is slow.
CFTimeInterval const kFPRFrozenFrameThreshold = 700.0 / 1000.0;
/** Frozen frame multiplier: A frozen frame is one that takes longer than approximately 42 times
* the current frame duration. This maintains backward compatibility with the old 700ms threshold
* at 60Hz (700ms ÷ 16.67ms ≈ 42 frames).
Comment on lines +29 to +30
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current documentation clearly states that frozen frame takes 700+ ms. This PR does not follow this definition. Are you going to update the Frozen Frame definition for Firebase Performance?

*
* NOTE!!!: A "frozen" frame represents missing 42 consecutive frame opportunities,
* which looks and feels equally bad to users regardless of refresh rate.
*
* Formula: frozenThreshold = kFPRFrozenFrameMultiplier * actualFrameDuration
*
* Examples (all represent missing 42 frame opportunities):
* - 60Hz: 42 × 16.67ms = 700ms (same as original threshold)
* - 120Hz: 42 × 8.33ms = 350ms (missing 42 frames at higher refresh rate)
* - 50Hz: 42 × 20ms = 840ms (missing 42 frames at lower refresh rate)
*/
static const CFTimeInterval kFPRFrozenFrameMultiplier = 42.0;

/** Constant that indicates an invalid time. */
CFAbsoluteTime const kFPRInvalidTime = -1.0;
Expand Down Expand Up @@ -142,6 +152,9 @@ - (void)dealloc {
}

- (void)appDidBecomeActiveNotification:(NSNotification *)notification {
// Resume the display link when the app becomes active
_displayLink.paused = NO;

// To get the most accurate numbers of total, frozen and slow frames, we need to capture them as
// soon as we're notified of an event.
int64_t currentTotalFrames = atomic_load_explicit(&_totalFramesCount, memory_order_relaxed);
Expand All @@ -160,6 +173,9 @@ - (void)appDidBecomeActiveNotification:(NSNotification *)notification {
}

- (void)appWillResignActiveNotification:(NSNotification *)notification {
// Pause the display link when the app goes to background to conserve battery
_displayLink.paused = YES;

// To get the most accurate numbers of total, frozen and slow frames, we need to capture them as
// soon as we're notified of an event.
int64_t currentTotalFrames = atomic_load_explicit(&_totalFramesCount, memory_order_relaxed);
Expand Down Expand Up @@ -188,8 +204,14 @@ - (void)appWillResignActiveNotification:(NSNotification *)notification {
- (void)displayLinkStep {
static CFAbsoluteTime previousTimestamp = kFPRInvalidTime;
CFAbsoluteTime currentTimestamp = self.displayLink.timestamp;
RecordFrameType(currentTimestamp, previousTimestamp, &_slowFramesCount, &_frozenFramesCount,
&_totalFramesCount);

// Calculate the current frame duration using targetTimestamp and timestamp
// This gives us the actual refresh rate of the display
CFTimeInterval actualFrameDuration =
self.displayLink.targetTimestamp - self.displayLink.timestamp;

RecordFrameType(currentTimestamp, previousTimestamp, actualFrameDuration, &_slowFramesCount,
&_frozenFramesCount, &_totalFramesCount);
previousTimestamp = currentTimestamp;
}

Expand All @@ -198,26 +220,33 @@ - (void)displayLinkStep {
*
* @param currentTimestamp The current timestamp of the displayLink.
* @param previousTimestamp The previous timestamp of the displayLink.
* @param actualFrameDuration The actual frame duration calculated from CADisplayLink's
* targetTimestamp and timestamp.
* @param slowFramesCounter The value of the slowFramesCount before this function was called.
* @param frozenFramesCounter The value of the frozenFramesCount before this function was called.
* @param totalFramesCounter The value of the totalFramesCount before this function was called.
*/
FOUNDATION_STATIC_INLINE
void RecordFrameType(CFAbsoluteTime currentTimestamp,
CFAbsoluteTime previousTimestamp,
CFTimeInterval actualFrameDuration,
atomic_int_fast64_t *slowFramesCounter,
atomic_int_fast64_t *frozenFramesCounter,
atomic_int_fast64_t *totalFramesCounter) {
CFTimeInterval frameDuration = currentTimestamp - previousTimestamp;
if (previousTimestamp == kFPRInvalidTime) {
if (previousTimestamp == kFPRInvalidTime || actualFrameDuration <= 0) {
return;
}
if (frameDuration > kFPRSlowFrameThreshold) {

if (frameDuration > actualFrameDuration) {
atomic_fetch_add_explicit(slowFramesCounter, 1, memory_order_relaxed);
}
if (frameDuration > kFPRFrozenFrameThreshold) {

CFTimeInterval frozenThreshold = kFPRFrozenFrameMultiplier * actualFrameDuration;
if (frameDuration > frozenThreshold) {
atomic_fetch_add_explicit(frozenFramesCounter, 1, memory_order_relaxed);
}

atomic_fetch_add_explicit(totalFramesCounter, 1, memory_order_relaxed);
}

Expand Down