From cf6569bc18082253fa84feecdfaa7a28413bc993 Mon Sep 17 00:00:00 2001 From: Nicola Corti Date: Fri, 13 Jun 2025 12:23:05 -0700 Subject: [PATCH] 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 --- .../ReactAndroid/api/ReactAndroid.api | 30 --------- .../modules/debug/FpsDebugFrameCallback.kt | 66 ++++--------------- 2 files changed, 11 insertions(+), 85 deletions(-) diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index f8082a886b3..f4e0a5b99a9 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -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 (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 (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; diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/debug/FpsDebugFrameCallback.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/debug/FpsDebugFrameCallback.kt index 3e5d9b10d41..49ab4609d51 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/debug/FpsDebugFrameCallback.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/debug/FpsDebugFrameCallback.kt @@ -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? = 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 {