blob: 86729f74cd85f8ccc08934fb51721cc76f78f490 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.jank;
import static android.view.SurfaceControl.JankData.DISPLAY_HAL;
import static android.view.SurfaceControl.JankData.JANK_APP_DEADLINE_MISSED;
import static android.view.SurfaceControl.JankData.JANK_NONE;
import static android.view.SurfaceControl.JankData.JANK_SURFACEFLINGER_DEADLINE_MISSED;
import static android.view.SurfaceControl.JankData.JANK_SURFACEFLINGER_GPU_DEADLINE_MISSED;
import static android.view.SurfaceControl.JankData.PREDICTION_ERROR;
import static android.view.SurfaceControl.JankData.SURFACE_FLINGER_SCHEDULING;
import static com.android.internal.jank.DisplayRefreshRate.UNKNOWN_REFRESH_RATE;
import static com.android.internal.jank.DisplayRefreshRate.VARIABLE_REFRESH_RATE;
import static com.android.internal.jank.InteractionJankMonitor.ACTION_SESSION_CANCEL;
import static com.android.internal.jank.InteractionJankMonitor.ACTION_SESSION_END;
import static com.android.internal.jank.InteractionJankMonitor.EXECUTOR_TASK_TIMEOUT;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UiThread;
import android.graphics.HardwareRendererObserver;
import android.os.Handler;
import android.os.Trace;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.SparseArray;
import android.view.Choreographer;
import android.view.FrameMetrics;
import android.view.SurfaceControl;
import android.view.SurfaceControl.JankData.JankType;
import android.view.ThreadedRenderer;
import android.view.View;
import android.view.ViewRootImpl;
import android.view.WindowCallbacks;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.DisplayRefreshRate.RefreshRate;
import com.android.internal.jank.InteractionJankMonitor.Configuration;
import com.android.internal.util.FrameworkStatsLog;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.concurrent.TimeUnit;
/**
* A class that allows the app to get the frame metrics from HardwareRendererObserver.
* @hide
*/
public class FrameTracker extends SurfaceControl.OnJankDataListener
implements HardwareRendererObserver.OnFrameMetricsAvailableListener {
private static final String TAG = "FrameTracker";
private static final long INVALID_ID = -1;
public static final int NANOS_IN_MILLISECOND = 1_000_000;
private static final int MAX_LENGTH_EVENT_DESC = 127;
private static final int MAX_FLUSH_ATTEMPTS = 3;
private static final int FLUSH_DELAY_MILLISECOND = 60;
static final int REASON_END_UNKNOWN = -1;
static final int REASON_END_NORMAL = 0;
static final int REASON_END_SURFACE_DESTROYED = 1;
static final int REASON_CANCEL_NORMAL = 16;
static final int REASON_CANCEL_NOT_BEGUN = 17;
static final int REASON_CANCEL_SAME_VSYNC = 18;
static final int REASON_CANCEL_TIMEOUT = 19;
/** @hide */
@IntDef({
REASON_END_UNKNOWN,
REASON_END_NORMAL,
REASON_END_SURFACE_DESTROYED,
REASON_CANCEL_NORMAL,
REASON_CANCEL_NOT_BEGUN,
REASON_CANCEL_SAME_VSYNC,
REASON_CANCEL_TIMEOUT,
})
@Retention(RetentionPolicy.SOURCE)
public @interface Reasons {
}
private final HardwareRendererObserver mObserver;
private final int mTraceThresholdMissedFrames;
private final int mTraceThresholdFrameTimeMillis;
private final ThreadedRendererWrapper mRendererWrapper;
private final FrameMetricsWrapper mMetricsWrapper;
private final SparseArray<JankInfo> mJankInfos = new SparseArray<>();
private final Configuration mConfig;
private final ViewRootWrapper mViewRoot;
private final SurfaceControlWrapper mSurfaceControlWrapper;
private final int mDisplayId;
private final ViewRootImpl.SurfaceChangedCallback mSurfaceChangedCallback;
private final Handler mHandler;
private final ChoreographerWrapper mChoreographer;
private final StatsLogWrapper mStatsLog;
private final boolean mDeferMonitoring;
private final FrameTrackerListener mListener;
@VisibleForTesting
public final boolean mSurfaceOnly;
private SurfaceControl mSurfaceControl;
private long mBeginVsyncId = INVALID_ID;
private long mEndVsyncId = INVALID_ID;
private boolean mMetricsFinalized;
private boolean mCancelled = false;
private boolean mTracingStarted = false;
private Runnable mWaitForFinishTimedOut;
private static class JankInfo {
long frameVsyncId;
long totalDurationNanos;
boolean isFirstFrame;
boolean hwuiCallbackFired;
boolean surfaceControlCallbackFired;
@JankType int jankType;
@RefreshRate int refreshRate;
static JankInfo createFromHwuiCallback(long frameVsyncId, long totalDurationNanos,
boolean isFirstFrame) {
return new JankInfo(frameVsyncId, true, false, JANK_NONE, UNKNOWN_REFRESH_RATE,
totalDurationNanos, isFirstFrame);
}
static JankInfo createFromSurfaceControlCallback(long frameVsyncId,
@JankType int jankType, @RefreshRate int refreshRate) {
return new JankInfo(
frameVsyncId, false, true, jankType, refreshRate, 0, false /* isFirstFrame */);
}
private JankInfo(long frameVsyncId, boolean hwuiCallbackFired,
boolean surfaceControlCallbackFired, @JankType int jankType,
@RefreshRate int refreshRate,
long totalDurationNanos, boolean isFirstFrame) {
this.frameVsyncId = frameVsyncId;
this.hwuiCallbackFired = hwuiCallbackFired;
this.surfaceControlCallbackFired = surfaceControlCallbackFired;
this.jankType = jankType;
this.refreshRate = refreshRate;
this.totalDurationNanos = totalDurationNanos;
this.isFirstFrame = isFirstFrame;
}
@Override
public String toString() {
StringBuilder str = new StringBuilder();
switch (jankType) {
case JANK_NONE:
str.append("JANK_NONE");
break;
case JANK_APP_DEADLINE_MISSED:
str.append("JANK_APP_DEADLINE_MISSED");
break;
case JANK_SURFACEFLINGER_DEADLINE_MISSED:
str.append("JANK_SURFACEFLINGER_DEADLINE_MISSED");
break;
case JANK_SURFACEFLINGER_GPU_DEADLINE_MISSED:
str.append("JANK_SURFACEFLINGER_GPU_DEADLINE_MISSED");
break;
case DISPLAY_HAL:
str.append("DISPLAY_HAL");
break;
case PREDICTION_ERROR:
str.append("PREDICTION_ERROR");
break;
case SURFACE_FLINGER_SCHEDULING:
str.append("SURFACE_FLINGER_SCHEDULING");
break;
default:
str.append("UNKNOWN: ").append(jankType);
break;
}
str.append(", ").append(frameVsyncId);
str.append(", ").append(totalDurationNanos);
return str.toString();
}
}
public FrameTracker(@NonNull Configuration config,
@Nullable ThreadedRendererWrapper renderer,
@Nullable ViewRootWrapper viewRootWrapper,
@NonNull SurfaceControlWrapper surfaceControlWrapper,
@NonNull ChoreographerWrapper choreographer,
@Nullable FrameMetricsWrapper metrics,
@NonNull StatsLogWrapper statsLog,
int traceThresholdMissedFrames, int traceThresholdFrameTimeMillis,
@Nullable FrameTrackerListener listener) {
mSurfaceOnly = config.isSurfaceOnly();
mConfig = config;
mHandler = config.getHandler();
mChoreographer = choreographer;
mSurfaceControlWrapper = surfaceControlWrapper;
mStatsLog = statsLog;
mDeferMonitoring = config.shouldDeferMonitor();
// HWUI instrumentation init.
mRendererWrapper = mSurfaceOnly ? null : renderer;
mMetricsWrapper = mSurfaceOnly ? null : metrics;
mViewRoot = mSurfaceOnly ? null : viewRootWrapper;
mObserver = mSurfaceOnly
? null
: new HardwareRendererObserver(this, mMetricsWrapper.getTiming(),
mHandler, /* waitForPresentTime= */ false);
mTraceThresholdMissedFrames = traceThresholdMissedFrames;
mTraceThresholdFrameTimeMillis = traceThresholdFrameTimeMillis;
mListener = listener;
mDisplayId = config.getDisplayId();
if (mSurfaceOnly) {
mSurfaceControl = config.getSurfaceControl();
mSurfaceChangedCallback = null;
} else {
// HWUI instrumentation init.
// If the surface isn't valid yet, wait until it's created.
if (mViewRoot.getSurfaceControl().isValid()) {
mSurfaceControl = mViewRoot.getSurfaceControl();
}
mSurfaceChangedCallback = new ViewRootImpl.SurfaceChangedCallback() {
@Override
public void surfaceCreated(SurfaceControl.Transaction t) {
mHandler.runWithScissors(() -> {
if (mSurfaceControl == null) {
mSurfaceControl = mViewRoot.getSurfaceControl();
if (mBeginVsyncId != INVALID_ID) {
// Previous begin invocation is not successfully, begin it again.
begin();
}
}
}, EXECUTOR_TASK_TIMEOUT);
}
@Override
public void surfaceReplaced(SurfaceControl.Transaction t) {
}
@Override
public void surfaceDestroyed() {
// Wait a while to give the system a chance for the remaining
// frames to arrive, then force finish the session.
mHandler.postDelayed(() -> {
if (!mMetricsFinalized) {
end(REASON_END_SURFACE_DESTROYED);
finish();
}
}, 50);
}
};
// This callback has a reference to FrameTracker,
// remember to remove it to avoid leakage.
mViewRoot.addSurfaceChangedCallback(mSurfaceChangedCallback);
}
}
/**
* Begin a trace session of the CUJ.
*/
@UiThread
public void begin() {
final long currentVsync = mChoreographer.getVsyncId();
// In normal case, we should begin at the next frame,
// the id of the next frame is not simply increased by 1,
// but we can exclude the current frame at least.
if (mBeginVsyncId == INVALID_ID) {
mBeginVsyncId = mDeferMonitoring ? currentVsync + 1 : currentVsync;
}
if (mSurfaceControl != null) {
if (mDeferMonitoring && currentVsync < mBeginVsyncId) {
markEvent("FT#deferMonitoring", 0);
// Normal case, we begin the instrument from the very beginning,
// will exclude the first frame.
postTraceStartMarker(this::beginInternal);
} else {
// If we don't begin the instrument from the very beginning,
// there is no need to skip the frame where the begin invocation happens.
beginInternal();
}
}
}
/**
* Start trace section at appropriate time.
*/
@VisibleForTesting
public void postTraceStartMarker(Runnable action) {
mChoreographer.mChoreographer.postCallback(Choreographer.CALLBACK_INPUT, action, null);
}
@UiThread
private void beginInternal() {
if (mCancelled || mEndVsyncId != INVALID_ID) {
return;
}
mTracingStarted = true;
String name = mConfig.getSessionName();
Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, name, name, (int) mBeginVsyncId);
markEvent("FT#beginVsync", mBeginVsyncId);
markEvent("FT#layerId", mSurfaceControl.getLayerId());
mSurfaceControlWrapper.addJankStatsListener(this, mSurfaceControl);
if (!mSurfaceOnly) {
mRendererWrapper.addObserver(mObserver);
}
}
/**
* End the trace session of the CUJ.
*/
@UiThread
public boolean end(@Reasons int reason) {
if (mCancelled || mEndVsyncId != INVALID_ID) return false;
mEndVsyncId = mChoreographer.getVsyncId();
// Cancel the session if:
// 1. The session begins and ends at the same vsync id.
// 2. The session never begun.
if (mBeginVsyncId == INVALID_ID) {
return cancel(REASON_CANCEL_NOT_BEGUN);
} else if (mEndVsyncId <= mBeginVsyncId) {
return cancel(REASON_CANCEL_SAME_VSYNC);
} else {
final String name = mConfig.getSessionName();
markEvent("FT#end", reason);
markEvent("FT#endVsync", mEndVsyncId);
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, name, (int) mBeginVsyncId);
// We don't remove observer here,
// will remove it when all the frame metrics in this duration are called back.
// See onFrameMetricsAvailable for the logic of removing the observer.
// Waiting at most 10 seconds for all callbacks to finish.
mWaitForFinishTimedOut = new Runnable() {
private int mFlushAttempts = 0;
@Override
public void run() {
if (mWaitForFinishTimedOut == null || mMetricsFinalized) {
return;
}
// Send a flush jank data transaction.
if (mSurfaceControl != null && mSurfaceControl.isValid()) {
SurfaceControl.Transaction.sendSurfaceFlushJankData(mSurfaceControl);
}
long delay;
if (mFlushAttempts < MAX_FLUSH_ATTEMPTS) {
delay = FLUSH_DELAY_MILLISECOND;
mFlushAttempts++;
} else {
mWaitForFinishTimedOut = () -> {
Log.e(TAG, "force finish cuj, time out: " + name);
finish();
};
delay = TimeUnit.SECONDS.toMillis(10);
}
mHandler.postDelayed(mWaitForFinishTimedOut, delay);
}
};
mHandler.postDelayed(mWaitForFinishTimedOut, FLUSH_DELAY_MILLISECOND);
notifyCujEvent(ACTION_SESSION_END, reason);
return true;
}
}
/**
* Cancel the trace session of the CUJ.
*/
@UiThread
public boolean cancel(@Reasons int reason) {
final boolean cancelFromEnd =
reason == REASON_CANCEL_NOT_BEGUN || reason == REASON_CANCEL_SAME_VSYNC;
if (mCancelled || (mEndVsyncId != INVALID_ID && !cancelFromEnd)) return false;
mCancelled = true;
markEvent("FT#cancel", reason);
// We don't need to end the trace section if it has never begun.
if (mTracingStarted) {
Trace.asyncTraceForTrackEnd(
Trace.TRACE_TAG_APP, mConfig.getSessionName(), (int) mBeginVsyncId);
}
// Always remove the observers in cancel call to avoid leakage.
removeObservers();
// Notify the listener the session has been cancelled.
// We don't notify the listeners if the session never begun.
notifyCujEvent(ACTION_SESSION_CANCEL, reason);
return true;
}
/**
* Mark the FrameTracker events in the trace.
*
* @param eventName The description of the trace event,
* @param eventValue The value of the related trace event
* Both shouldn't exceed {@link #MAX_LENGTH_EVENT_DESC}.
*/
private void markEvent(@NonNull String eventName, long eventValue) {
if (Trace.isTagEnabled(Trace.TRACE_TAG_APP)) {
String event = TextUtils.formatSimple("%s#%s", eventName, eventValue);
if (event.length() > MAX_LENGTH_EVENT_DESC) {
throw new IllegalArgumentException(TextUtils.formatSimple(
"The length of the trace event description <%s> exceeds %d",
event, MAX_LENGTH_EVENT_DESC));
}
Trace.instantForTrack(Trace.TRACE_TAG_APP, mConfig.getSessionName(), event);
}
}
private void notifyCujEvent(String action, @Reasons int reason) {
if (mListener == null) return;
mListener.onCujEvents(this, action, reason);
}
@Override
public void onJankDataAvailable(SurfaceControl.JankData[] jankData) {
postCallback(() -> {
if (mCancelled || mMetricsFinalized) {
return;
}
for (SurfaceControl.JankData jankStat : jankData) {
if (!isInRange(jankStat.frameVsyncId)) {
continue;
}
int refreshRate = DisplayRefreshRate.getRefreshRate(jankStat.frameIntervalNs);
JankInfo info = findJankInfo(jankStat.frameVsyncId);
if (info != null) {
info.surfaceControlCallbackFired = true;
info.jankType = jankStat.jankType;
info.refreshRate = refreshRate;
} else {
mJankInfos.put((int) jankStat.frameVsyncId,
JankInfo.createFromSurfaceControlCallback(
jankStat.frameVsyncId, jankStat.jankType, refreshRate));
}
}
processJankInfos();
});
}
/**
* For easier argument capture.
*/
@VisibleForTesting
public void postCallback(Runnable callback) {
mHandler.post(callback);
}
@Nullable
private JankInfo findJankInfo(long frameVsyncId) {
return mJankInfos.get((int) frameVsyncId);
}
private boolean isInRange(long vsyncId) {
// It's possible that we may miss a callback for the frame with vsyncId == mEndVsyncId.
// Because of that, we collect all frames even if they happen after the end so we eventually
// have a frame after the end with both callbacks present.
return vsyncId >= mBeginVsyncId;
}
@Override
public void onFrameMetricsAvailable(int dropCountSinceLastInvocation) {
postCallback(() -> {
if (mCancelled || mMetricsFinalized) {
return;
}
// Since this callback might come a little bit late after the end() call.
// We should keep tracking the begin / end timestamp that we can compare with
// vsync timestamp to check if the frame is in the duration of the CUJ.
long totalDurationNanos = mMetricsWrapper.getMetric(FrameMetrics.TOTAL_DURATION);
boolean isFirstFrame = mMetricsWrapper.getMetric(FrameMetrics.FIRST_DRAW_FRAME) == 1;
long frameVsyncId =
mMetricsWrapper.getTiming()[FrameMetrics.Index.FRAME_TIMELINE_VSYNC_ID];
if (!isInRange(frameVsyncId)) {
return;
}
JankInfo info = findJankInfo(frameVsyncId);
if (info != null) {
info.hwuiCallbackFired = true;
info.totalDurationNanos = totalDurationNanos;
info.isFirstFrame = isFirstFrame;
} else {
mJankInfos.put((int) frameVsyncId, JankInfo.createFromHwuiCallback(
frameVsyncId, totalDurationNanos, isFirstFrame));
}
processJankInfos();
});
}
@UiThread
private boolean hasReceivedCallbacksAfterEnd() {
if (mEndVsyncId == INVALID_ID) {
return false;
}
JankInfo last = mJankInfos.size() == 0 ? null : mJankInfos.valueAt(mJankInfos.size() - 1);
if (last == null) {
return false;
}
if (last.frameVsyncId < mEndVsyncId) {
return false;
}
for (int i = mJankInfos.size() - 1; i >= 0; i--) {
JankInfo info = mJankInfos.valueAt(i);
if (info.frameVsyncId >= mEndVsyncId) {
if (callbacksReceived(info)) {
return true;
}
}
}
return false;
}
@UiThread
private void processJankInfos() {
if (mMetricsFinalized) {
return;
}
if (!hasReceivedCallbacksAfterEnd()) {
return;
}
finish();
}
private boolean callbacksReceived(JankInfo info) {
return mSurfaceOnly
? info.surfaceControlCallbackFired
: info.hwuiCallbackFired && info.surfaceControlCallbackFired;
}
@UiThread
private void finish() {
if (mMetricsFinalized || mCancelled) return;
mMetricsFinalized = true;
mHandler.removeCallbacks(mWaitForFinishTimedOut);
mWaitForFinishTimedOut = null;
markEvent("FT#finish", mJankInfos.size());
// The tracing has been ended, remove the observer, see if need to trigger perfetto.
removeObservers();
final String name = mConfig.getSessionName();
int totalFramesCount = 0;
long maxFrameTimeNanos = 0;
int missedFramesCount = 0;
int missedAppFramesCount = 0;
int missedSfFramesCount = 0;
int maxSuccessiveMissedFramesCount = 0;
int successiveMissedFramesCount = 0;
@RefreshRate int refreshRate = UNKNOWN_REFRESH_RATE;
for (int i = 0; i < mJankInfos.size(); i++) {
JankInfo info = mJankInfos.valueAt(i);
final boolean isFirstDrawn = !mSurfaceOnly && info.isFirstFrame;
if (isFirstDrawn) {
continue;
}
if (info.frameVsyncId > mEndVsyncId) {
break;
}
if (info.surfaceControlCallbackFired) {
totalFramesCount++;
boolean missedFrame = false;
if ((info.jankType & JANK_APP_DEADLINE_MISSED) != 0) {
Log.w(TAG, "Missed App frame:" + info + ", CUJ=" + name);
missedAppFramesCount++;
missedFrame = true;
}
if ((info.jankType & DISPLAY_HAL) != 0
|| (info.jankType & JANK_SURFACEFLINGER_DEADLINE_MISSED) != 0
|| (info.jankType & JANK_SURFACEFLINGER_GPU_DEADLINE_MISSED) != 0
|| (info.jankType & SURFACE_FLINGER_SCHEDULING) != 0
|| (info.jankType & PREDICTION_ERROR) != 0) {
Log.w(TAG, "Missed SF frame:" + info + ", CUJ=" + name);
missedSfFramesCount++;
missedFrame = true;
}
if (missedFrame) {
missedFramesCount++;
successiveMissedFramesCount++;
} else {
maxSuccessiveMissedFramesCount = Math.max(
maxSuccessiveMissedFramesCount, successiveMissedFramesCount);
successiveMissedFramesCount = 0;
}
if (info.refreshRate != UNKNOWN_REFRESH_RATE && info.refreshRate != refreshRate) {
refreshRate = (refreshRate == UNKNOWN_REFRESH_RATE)
? info.refreshRate : VARIABLE_REFRESH_RATE;
}
// TODO (b/174755489): Early latch currently gets fired way too often, so we have
// to ignore it for now.
if (!mSurfaceOnly && !info.hwuiCallbackFired) {
markEvent("FT#MissedHWUICallback", info.frameVsyncId);
Log.w(TAG, "Missing HWUI jank callback for vsyncId: " + info.frameVsyncId
+ ", CUJ=" + name);
}
}
if (!mSurfaceOnly && info.hwuiCallbackFired) {
maxFrameTimeNanos = Math.max(info.totalDurationNanos, maxFrameTimeNanos);
if (!info.surfaceControlCallbackFired) {
markEvent("FT#MissedSFCallback", info.frameVsyncId);
Log.w(TAG, "Missing SF jank callback for vsyncId: " + info.frameVsyncId
+ ", CUJ=" + name);
}
}
}
maxSuccessiveMissedFramesCount = Math.max(
maxSuccessiveMissedFramesCount, successiveMissedFramesCount);
// Log the frame stats as counters to make them easily accessible in traces.
Trace.traceCounter(Trace.TRACE_TAG_APP, name + "#missedFrames", missedFramesCount);
Trace.traceCounter(Trace.TRACE_TAG_APP, name + "#missedAppFrames", missedAppFramesCount);
Trace.traceCounter(Trace.TRACE_TAG_APP, name + "#missedSfFrames", missedSfFramesCount);
Trace.traceCounter(Trace.TRACE_TAG_APP, name + "#totalFrames", totalFramesCount);
Trace.traceCounter(Trace.TRACE_TAG_APP, name + "#maxFrameTimeMillis",
(int) (maxFrameTimeNanos / NANOS_IN_MILLISECOND));
Trace.traceCounter(Trace.TRACE_TAG_APP, name + "#maxSuccessiveMissedFrames",
maxSuccessiveMissedFramesCount);
// Trigger perfetto if necessary.
if (mListener != null
&& shouldTriggerPerfetto(missedFramesCount, (int) maxFrameTimeNanos)) {
mListener.triggerPerfetto(mConfig);
}
if (mConfig.logToStatsd()) {
mStatsLog.write(
FrameworkStatsLog.UI_INTERACTION_FRAME_INFO_REPORTED,
mDisplayId,
refreshRate,
mConfig.getStatsdInteractionType(),
totalFramesCount,
missedFramesCount,
maxFrameTimeNanos, /* will be 0 if mSurfaceOnly == true */
missedSfFramesCount,
missedAppFramesCount,
maxSuccessiveMissedFramesCount);
}
}
ThreadedRendererWrapper getThreadedRenderer() {
return mRendererWrapper;
}
ViewRootWrapper getViewRoot() {
return mViewRoot;
}
private boolean shouldTriggerPerfetto(int missedFramesCount, int maxFrameTimeNanos) {
boolean overMissedFramesThreshold = mTraceThresholdMissedFrames != -1
&& missedFramesCount >= mTraceThresholdMissedFrames;
boolean overFrameTimeThreshold = !mSurfaceOnly && mTraceThresholdFrameTimeMillis != -1
&& maxFrameTimeNanos >= mTraceThresholdFrameTimeMillis * NANOS_IN_MILLISECOND;
return overMissedFramesThreshold || overFrameTimeThreshold;
}
/**
* Remove all the registered listeners, observers and callbacks.
*/
@VisibleForTesting
@UiThread
public void removeObservers() {
mSurfaceControlWrapper.removeJankStatsListener(this);
if (!mSurfaceOnly) {
// HWUI part.
mRendererWrapper.removeObserver(mObserver);
if (mSurfaceChangedCallback != null) {
mViewRoot.removeSurfaceChangedCallback(mSurfaceChangedCallback);
}
}
}
/**
* A wrapper class that we can spy FrameMetrics (a final class) in unit tests.
*/
public static class FrameMetricsWrapper {
private final FrameMetrics mFrameMetrics;
public FrameMetricsWrapper() {
mFrameMetrics = new FrameMetrics();
}
/**
* Wrapper method.
* @return timing data of the metrics
*/
public long[] getTiming() {
return mFrameMetrics.mTimingData;
}
/**
* Wrapper method.
* @param index specific index of the timing data
* @return the timing data of the specified index
*/
public long getMetric(int index) {
return mFrameMetrics.getMetric(index);
}
}
/**
* A wrapper class that we can spy ThreadedRenderer (a final class) in unit tests.
*/
public static class ThreadedRendererWrapper {
private final ThreadedRenderer mRenderer;
public ThreadedRendererWrapper(ThreadedRenderer renderer) {
mRenderer = renderer;
}
/**
* Wrapper method.
* @param observer observer
*/
public void addObserver(HardwareRendererObserver observer) {
mRenderer.addObserver(observer);
}
/**
* Wrapper method.
* @param observer observer
*/
public void removeObserver(HardwareRendererObserver observer) {
mRenderer.removeObserver(observer);
}
}
public static class ViewRootWrapper {
private final ViewRootImpl mViewRoot;
public ViewRootWrapper(ViewRootImpl viewRoot) {
mViewRoot = viewRoot;
}
/**
* {@link ViewRootImpl#addSurfaceChangedCallback(ViewRootImpl.SurfaceChangedCallback)}
* @param callback {@link ViewRootImpl.SurfaceChangedCallback}
*/
public void addSurfaceChangedCallback(ViewRootImpl.SurfaceChangedCallback callback) {
mViewRoot.addSurfaceChangedCallback(callback);
}
/**
* {@link ViewRootImpl#removeSurfaceChangedCallback(ViewRootImpl.SurfaceChangedCallback)}
* @param callback {@link ViewRootImpl.SurfaceChangedCallback}
*/
public void removeSurfaceChangedCallback(ViewRootImpl.SurfaceChangedCallback callback) {
mViewRoot.removeSurfaceChangedCallback(callback);
}
public SurfaceControl getSurfaceControl() {
return mViewRoot.getSurfaceControl();
}
void requestInvalidateRootRenderNode() {
mViewRoot.requestInvalidateRootRenderNode();
}
void addWindowCallbacks(WindowCallbacks windowCallbacks) {
mViewRoot.addWindowCallbacks(windowCallbacks);
}
void removeWindowCallbacks(WindowCallbacks windowCallbacks) {
mViewRoot.removeWindowCallbacks(windowCallbacks);
}
View getView() {
return mViewRoot.getView();
}
int dipToPx(int dip) {
final DisplayMetrics displayMetrics =
mViewRoot.mContext.getResources().getDisplayMetrics();
return (int) (displayMetrics.density * dip + 0.5f);
}
}
public static class SurfaceControlWrapper {
public void addJankStatsListener(SurfaceControl.OnJankDataListener listener,
SurfaceControl surfaceControl) {
SurfaceControl.addJankDataListener(listener, surfaceControl);
}
public void removeJankStatsListener(SurfaceControl.OnJankDataListener listener) {
SurfaceControl.removeJankDataListener(listener);
}
}
public static class ChoreographerWrapper {
private final Choreographer mChoreographer;
public ChoreographerWrapper(Choreographer choreographer) {
mChoreographer = choreographer;
}
public long getVsyncId() {
return mChoreographer.getVsyncId();
}
}
public static class StatsLogWrapper {
private final DisplayResolutionTracker mDisplayResolutionTracker;
public StatsLogWrapper(DisplayResolutionTracker displayResolutionTracker) {
mDisplayResolutionTracker = displayResolutionTracker;
}
/** {@see FrameworkStatsLog#write) */
public void write(int code, int displayId, @RefreshRate int refreshRate,
int arg1, long arg2, long arg3, long arg4, long arg5, long arg6, long arg7) {
FrameworkStatsLog.write(code, arg1, arg2, arg3, arg4, arg5, arg6, arg7,
mDisplayResolutionTracker.getResolution(displayId), refreshRate);
}
}
/**
* A listener that notifies cuj events.
*/
public interface FrameTrackerListener {
/**
* Notify that the CUJ session was created.
*
* @param tracker the tracker
* @param action the specific action
* @param reason the reason for the action
*/
void onCujEvents(FrameTracker tracker, String action, @Reasons int reason);
/**
* Notify that the Perfetto trace should be triggered.
*
* @param config the tracker configuration
*/
void triggerPerfetto(Configuration config);
}
}