Cleanup and internalize FpsDebugFrameCallback (#51982)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/51982

This class should not be accessed externally. I'm making it internal.
On top of this, it was not fully reimplemented on NewArch so is not working consistently.

This is gonna break one library which is unmaintained and not properly udpated to work with NewArch
https://github.com/hannojg/react-native-performance-stats

Changelog:
[Android] [Breaking] - Cleanup and internalize FpsDebugFrameCallback

Reviewed By: huntie

Differential Revision: D76531175

fbshipit-source-id: 25598eb7c1ecf476b69bb6a2f2f8088a57b9fbc2
This commit is contained in:
Nicola Corti
2025-06-13 12:23:05 -07:00
committed by Facebook GitHub Bot
parent c64f698e5f
commit cf6569bc18
2 changed files with 11 additions and 85 deletions
@@ -2747,36 +2747,6 @@ public final class com/facebook/react/modules/debug/DevSettingsModule : com/face
public final class com/facebook/react/modules/debug/DevSettingsModule$Companion {
}
public final class com/facebook/react/modules/debug/FpsDebugFrameCallback : android/view/Choreographer$FrameCallback {
public fun <init> (Lcom/facebook/react/bridge/ReactContext;)V
public fun doFrame (J)V
public final fun get4PlusFrameStutters ()I
public final fun getExpectedNumFrames ()I
public final fun getFps ()D
public final fun getFpsInfo (J)Lcom/facebook/react/modules/debug/FpsDebugFrameCallback$FpsInfo;
public final fun getJsFPS ()D
public final fun getNumFrames ()I
public final fun getNumJSFrames ()I
public final fun getTotalTimeMS ()I
public final fun reset ()V
public final fun start ()V
public final fun start (D)V
public static synthetic fun start$default (Lcom/facebook/react/modules/debug/FpsDebugFrameCallback;DILjava/lang/Object;)V
public final fun startAndRecordFpsAtEachFrame ()V
public final fun stop ()V
}
public final class com/facebook/react/modules/debug/FpsDebugFrameCallback$FpsInfo {
public fun <init> (IIIIDDI)V
public final fun getFps ()D
public final fun getJsFps ()D
public final fun getTotal4PlusFrameStutters ()I
public final fun getTotalExpectedFrames ()I
public final fun getTotalFrames ()I
public final fun getTotalJsFrames ()I
public final fun getTotalTimeMs ()I
}
public final class com/facebook/react/modules/debug/SourceCodeModule : com/facebook/fbreact/specs/NativeSourceCodeSpec {
public static final field Companion Lcom/facebook/react/modules/debug/SourceCodeModule$Companion;
public static final field NAME Ljava/lang/String;
@@ -8,12 +8,10 @@
package com.facebook.react.modules.debug
import android.view.Choreographer
import com.facebook.infer.annotation.Assertions
import com.facebook.react.bridge.ReactContext
import com.facebook.react.bridge.UiThreadUtil
import com.facebook.react.common.build.ReactBuildConfig
import com.facebook.react.uimanager.UIManagerModule
import java.util.TreeMap
/**
* Each time a frame is drawn, records whether it should have expected any more callbacks since the
@@ -25,17 +23,8 @@ import java.util.TreeMap
* idle and not trying to update the UI. This is different from the FPS above since JS rendering is
* async.
*/
public class FpsDebugFrameCallback(private val reactContext: ReactContext) :
internal class FpsDebugFrameCallback(private val reactContext: ReactContext) :
Choreographer.FrameCallback {
public class FpsInfo(
public val totalFrames: Int,
public val totalJsFrames: Int,
public val totalExpectedFrames: Int,
public val total4PlusFrameStutters: Int,
public val fps: Double,
public val jsFps: Double,
public val totalTimeMs: Int
)
private var choreographer: Choreographer? = null
private val didJSUpdateUiDuringFrameDetector: DidJSUpdateUiDuringFrameDetector =
@@ -46,9 +35,7 @@ public class FpsDebugFrameCallback(private val reactContext: ReactContext) :
private var expectedNumFramesPrev = 0
private var fourPlusFrameStutters = 0
private var numFrameCallbacksWithBatchDispatches = 0
private var isRecordingFpsInfoAtEachFrame = false
private var targetFps = DEFAULT_FPS
private var timeToFps: TreeMap<Long, FpsInfo>? = null
override fun doFrame(l: Long) {
if (firstFrameTime == -1L) {
@@ -65,25 +52,12 @@ public class FpsDebugFrameCallback(private val reactContext: ReactContext) :
if (framesDropped >= 4) {
fourPlusFrameStutters++
}
if (isRecordingFpsInfoAtEachFrame) {
Assertions.assertNotNull(timeToFps)
val info =
FpsInfo(
numFrames,
numJSFrames,
expectedNumFrames,
fourPlusFrameStutters,
fps,
jsFPS,
totalTimeMS)
timeToFps?.put(System.currentTimeMillis(), info)
}
expectedNumFramesPrev = expectedNumFrames
choreographer?.postFrameCallback(this)
}
@JvmOverloads
public fun start(targetFps: Double = this.targetFps) {
fun start(targetFps: Double = this.targetFps) {
// T172641976: re-think if we need to implement addBridgeIdleDebugListener and
// removeBridgeIdleDebugListener for Bridgeless
@Suppress("DEPRECATION")
@@ -101,13 +75,7 @@ public class FpsDebugFrameCallback(private val reactContext: ReactContext) :
}
}
public fun startAndRecordFpsAtEachFrame() {
timeToFps = TreeMap()
isRecordingFpsInfoAtEachFrame = true
start()
}
public fun stop() {
fun stop() {
@Suppress("DEPRECATION")
if (!ReactBuildConfig.UNSTABLE_ENABLE_MINIFY_LEGACY_ARCHITECTURE) {
val uiManagerModule = reactContext.getNativeModule(UIManagerModule::class.java)
@@ -123,53 +91,41 @@ public class FpsDebugFrameCallback(private val reactContext: ReactContext) :
}
}
public val fps: Double
val fps: Double
get() =
if (lastFrameTime == firstFrameTime) {
0.0
} else numFrames.toDouble() * 1e9 / (lastFrameTime - firstFrameTime)
public val jsFPS: Double
val jsFPS: Double
get() =
if (lastFrameTime == firstFrameTime) {
0.0
} else numJSFrames.toDouble() * 1e9 / (lastFrameTime - firstFrameTime)
public val numFrames: Int
val numFrames: Int
get() = numFrameCallbacks - 1
public val numJSFrames: Int
private val numJSFrames: Int
get() = numFrameCallbacksWithBatchDispatches - 1
public val expectedNumFrames: Int
val expectedNumFrames: Int
get() {
val totalTimeMS = totalTimeMS.toDouble()
return (targetFps * totalTimeMS / 1000 + 1).toInt()
}
public fun get4PlusFrameStutters(): Int = fourPlusFrameStutters
fun get4PlusFrameStutters(): Int = fourPlusFrameStutters
public val totalTimeMS: Int
private val totalTimeMS: Int
get() = ((lastFrameTime.toDouble() - firstFrameTime) / 1000000.0).toInt()
/**
* Returns the FpsInfo as if stop had been called at the given upToTimeMs. Only valid if
* monitoring was started with [startAndRecordFpsAtEachFrame].
*/
public fun getFpsInfo(upToTimeMs: Long): FpsInfo? {
Assertions.assertNotNull(timeToFps, "FPS was not recorded at each frame!")
val (_, value) = timeToFps?.floorEntry(upToTimeMs) ?: return null
return value
}
public fun reset() {
fun reset() {
firstFrameTime = -1
lastFrameTime = -1
numFrameCallbacks = 0
fourPlusFrameStutters = 0
numFrameCallbacksWithBatchDispatches = 0
isRecordingFpsInfoAtEachFrame = false
timeToFps = null
}
private companion object {