com.facebook.react.modules.debug.FpsDebugFrameCallback Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of react-native Show documentation
Show all versions of react-native Show documentation
A framework for building native apps with React
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.modules.debug;
import javax.annotation.Nullable;
import java.util.Map;
import java.util.TreeMap;
import android.annotation.TargetApi;
import android.view.Choreographer;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.infer.annotation.Assertions;
/**
* Each time a frame is drawn, records whether it should have expected any more callbacks since
* the last time a frame was drawn (i.e. was a frame skipped?). Uses this plus total elapsed time
* to determine FPS. Can also record total and expected frame counts, though NB, since the expected
* frame rate is estimated, the expected frame count will lose accuracy over time.
*
* Also records the JS FPS, i.e. the frames per second with which either JS updated the UI or was
* idle and not trying to update the UI. This is different from the FPS above since JS rendering is
* async.
*
* TargetApi 16 for use of Choreographer.
*/
@TargetApi(16)
public class FpsDebugFrameCallback implements Choreographer.FrameCallback {
public static class FpsInfo {
public final int totalFrames;
public final int totalJsFrames;
public final int totalExpectedFrames;
public final int total4PlusFrameStutters;
public final double fps;
public final double jsFps;
public final int totalTimeMs;
public FpsInfo(
int totalFrames,
int totalJsFrames,
int totalExpectedFrames,
int total4PlusFrameStutters,
double fps,
double jsFps,
int totalTimeMs) {
this.totalFrames = totalFrames;
this.totalJsFrames = totalJsFrames;
this.totalExpectedFrames = totalExpectedFrames;
this.total4PlusFrameStutters = total4PlusFrameStutters;
this.fps = fps;
this.jsFps = jsFps;
this.totalTimeMs = totalTimeMs;
}
}
private static final double EXPECTED_FRAME_TIME = 16.9;
private final Choreographer mChoreographer;
private final ReactContext mReactContext;
private final UIManagerModule mUIManagerModule;
private final DidJSUpdateUiDuringFrameDetector mDidJSUpdateUiDuringFrameDetector;
private boolean mShouldStop = false;
private long mFirstFrameTime = -1;
private long mLastFrameTime = -1;
private int mNumFrameCallbacks = 0;
private int mExpectedNumFramesPrev = 0;
private int m4PlusFrameStutters = 0;
private int mNumFrameCallbacksWithBatchDispatches = 0;
private boolean mIsRecordingFpsInfoAtEachFrame = false;
private @Nullable TreeMap mTimeToFps;
public FpsDebugFrameCallback(Choreographer choreographer, ReactContext reactContext) {
mChoreographer = choreographer;
mReactContext = reactContext;
mUIManagerModule = reactContext.getNativeModule(UIManagerModule.class);
mDidJSUpdateUiDuringFrameDetector = new DidJSUpdateUiDuringFrameDetector();
}
@Override
public void doFrame(long l) {
if (mShouldStop) {
return;
}
if (mFirstFrameTime == -1) {
mFirstFrameTime = l;
}
long lastFrameStartTime = mLastFrameTime;
mLastFrameTime = l;
if (mDidJSUpdateUiDuringFrameDetector.getDidJSHitFrameAndCleanup(
lastFrameStartTime,
l)) {
mNumFrameCallbacksWithBatchDispatches++;
}
mNumFrameCallbacks++;
int expectedNumFrames = getExpectedNumFrames();
int framesDropped = expectedNumFrames - mExpectedNumFramesPrev - 1;
if (framesDropped >= 4) {
m4PlusFrameStutters++;
}
if (mIsRecordingFpsInfoAtEachFrame) {
Assertions.assertNotNull(mTimeToFps);
FpsInfo info = new FpsInfo(
getNumFrames(),
getNumJSFrames(),
expectedNumFrames,
m4PlusFrameStutters,
getFPS(),
getJSFPS(),
getTotalTimeMS());
mTimeToFps.put(System.currentTimeMillis(), info);
}
mExpectedNumFramesPrev = expectedNumFrames;
mChoreographer.postFrameCallback(this);
}
public void start() {
mShouldStop = false;
mReactContext.getCatalystInstance().addBridgeIdleDebugListener(
mDidJSUpdateUiDuringFrameDetector);
mUIManagerModule.setViewHierarchyUpdateDebugListener(mDidJSUpdateUiDuringFrameDetector);
mChoreographer.postFrameCallback(this);
}
public void startAndRecordFpsAtEachFrame() {
mTimeToFps = new TreeMap();
mIsRecordingFpsInfoAtEachFrame = true;
start();
}
public void stop() {
mShouldStop = true;
mReactContext.getCatalystInstance().removeBridgeIdleDebugListener(
mDidJSUpdateUiDuringFrameDetector);
mUIManagerModule.setViewHierarchyUpdateDebugListener(null);
}
public double getFPS() {
if (mLastFrameTime == mFirstFrameTime) {
return 0;
}
return ((double) (getNumFrames()) * 1e9) / (mLastFrameTime - mFirstFrameTime);
}
public double getJSFPS() {
if (mLastFrameTime == mFirstFrameTime) {
return 0;
}
return ((double) (getNumJSFrames()) * 1e9) / (mLastFrameTime - mFirstFrameTime);
}
public int getNumFrames() {
return mNumFrameCallbacks - 1;
}
public int getNumJSFrames() {
return mNumFrameCallbacksWithBatchDispatches - 1;
}
public int getExpectedNumFrames() {
double totalTimeMS = getTotalTimeMS();
int expectedFrames = (int) (totalTimeMS / EXPECTED_FRAME_TIME + 1);
return expectedFrames;
}
public int get4PlusFrameStutters() {
return m4PlusFrameStutters;
}
public int getTotalTimeMS() {
return (int) ((double) mLastFrameTime - mFirstFrameTime) / 1000000;
}
/**
* Returns the FpsInfo as if stop had been called at the given upToTimeMs. Only valid if
* monitoring was started with {@link #startAndRecordFpsAtEachFrame()}.
*/
public @Nullable FpsInfo getFpsInfo(long upToTimeMs) {
Assertions.assertNotNull(mTimeToFps, "FPS was not recorded at each frame!");
Map.Entry bestEntry = mTimeToFps.floorEntry(upToTimeMs);
if (bestEntry == null) {
return null;
}
return bestEntry.getValue();
}
public void reset() {
mFirstFrameTime = -1;
mLastFrameTime = -1;
mNumFrameCallbacks = 0;
m4PlusFrameStutters = 0;
mNumFrameCallbacksWithBatchDispatches = 0;
mIsRecordingFpsInfoAtEachFrame = false;
mTimeToFps = null;
}
}