diff options
| author | 2021-01-13 19:36:00 +0000 | |
|---|---|---|
| committer | 2021-01-13 19:36:00 +0000 | |
| commit | c31ec3d518c84f63615b34cb5ff1fe20f84d5964 (patch) | |
| tree | 2fd10a1c37a71f3bbef0c41ce2d886308f3311e7 | |
| parent | 96292972865297702b53ebb3ccb72c2d3592654c (diff) | |
| parent | 2ee0ab9556f6e38b5ad78938487c144c5dfbb418 (diff) | |
Merge "Migrate FrameTracker to use ST Jank information"
18 files changed, 547 insertions, 274 deletions
diff --git a/core/java/android/view/FrameMetrics.java b/core/java/android/view/FrameMetrics.java index 388096e09631..c2566cc78bb0 100644 --- a/core/java/android/view/FrameMetrics.java +++ b/core/java/android/view/FrameMetrics.java @@ -207,7 +207,7 @@ public final class FrameMetrics { Index.FRAME_COMPLETED, }) @Retention(RetentionPolicy.SOURCE) - private @interface Index { + public @interface Index { int FLAGS = 0; int FRAME_TIMELINE_VSYNC_ID = 1; int INTENDED_VSYNC = 2; diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 2f973571fff7..63ee927ded3d 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -266,12 +266,12 @@ public final class SurfaceControl implements Parcelable { /** @hide */ @IntDef(flag = true, value = {JANK_NONE, - JANK_DISPLAY, + DISPLAY_HAL, JANK_SURFACEFLINGER_DEADLINE_MISSED, JANK_SURFACEFLINGER_GPU_DEADLINE_MISSED, JANK_APP_DEADLINE_MISSED, - JANK_PREDICTION_EXPIRED, - JANK_SURFACEFLINGER_EARLY_LATCH}) + PREDICTION_ERROR, + SURFACE_FLINGER_SCHEDULING}) @Retention(RetentionPolicy.SOURCE) public @interface JankType {} @@ -281,7 +281,7 @@ public final class SurfaceControl implements Parcelable { public static final int JANK_NONE = 0x0; // Jank not related to SurfaceFlinger or the App - public static final int JANK_DISPLAY = 0x1; + public static final int DISPLAY_HAL = 0x1; // SF took too long on the CPU public static final int JANK_SURFACEFLINGER_DEADLINE_MISSED = 0x2; // SF took too long on the GPU @@ -291,9 +291,16 @@ public final class SurfaceControl implements Parcelable { // Predictions live for 120ms, if prediction is expired for a frame, there is definitely a // jank // associated with the App if this is for a SurfaceFrame, and SF for a DisplayFrame. - public static final int JANK_PREDICTION_EXPIRED = 0x10; + public static final int PREDICTION_ERROR = 0x10; // Latching a buffer early might cause an early present of the frame - public static final int JANK_SURFACEFLINGER_EARLY_LATCH = 0x20; + public static final int SURFACE_FLINGER_SCHEDULING = 0x20; + // A buffer is said to be stuffed if it was expected to be presented on a vsync but was + // presented later because the previous buffer was presented in its expected vsync. This + // usually happens if there is an unexpectedly long frame causing the rest of the buffers + // to enter a stuffed state. + public static final int BUFFER_STUFFING = 0x40; + // Jank due to unknown reasons. + public static final int UNKNOWN = 0x80; public JankData(long frameVsyncId, @JankType int jankType) { this.frameVsyncId = frameVsyncId; diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 0097b2e107a1..7e0ebbcc61d2 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1787,18 +1787,18 @@ public final class ViewRootImpl implements ViewParent, /** Register callbacks to be notified when the ViewRootImpl surface changes. */ - interface SurfaceChangedCallback { + public interface SurfaceChangedCallback { void surfaceCreated(Transaction t); void surfaceReplaced(Transaction t); void surfaceDestroyed(); } private final ArrayList<SurfaceChangedCallback> mSurfaceChangedCallbacks = new ArrayList<>(); - void addSurfaceChangedCallback(SurfaceChangedCallback c) { + public void addSurfaceChangedCallback(SurfaceChangedCallback c) { mSurfaceChangedCallbacks.add(c); } - void removeSurfaceChangedCallback(SurfaceChangedCallback c) { + public void removeSurfaceChangedCallback(SurfaceChangedCallback c) { mSurfaceChangedCallbacks.remove(c); } diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java index 36514ff07753..e82cc737a395 100644 --- a/core/java/com/android/internal/jank/FrameTracker.java +++ b/core/java/com/android/internal/jank/FrameTracker.java @@ -16,13 +16,28 @@ package com.android.internal.jank; +import static android.view.SurfaceControl.JankData.BUFFER_STUFFING; +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 android.annotation.NonNull; +import android.annotation.Nullable; import android.graphics.HardwareRendererObserver; import android.os.Handler; import android.os.Trace; 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.ViewRootImpl; import com.android.internal.jank.InteractionJankMonitor.Session; import com.android.internal.util.FrameworkStatsLog; @@ -31,68 +46,141 @@ import com.android.internal.util.FrameworkStatsLog; * A class that allows the app to get the frame metrics from HardwareRendererObserver. * @hide */ -public class FrameTracker implements HardwareRendererObserver.OnFrameMetricsAvailableListener { - private static final String TAG = FrameTracker.class.getSimpleName(); +public class FrameTracker extends SurfaceControl.OnJankDataListener + implements HardwareRendererObserver.OnFrameMetricsAvailableListener { + private static final String TAG = "FrameTracker"; private static final boolean DEBUG = false; - //TODO (163431584): need also consider other refresh rates. - private static final long JANK_THRESHOLD_NANOS = 1000000000 / 60; - private static final long UNKNOWN_TIMESTAMP = -1; + + private static final long INVALID_ID = -1; public static final int NANOS_IN_MILLISECOND = 1_000_000; private final HardwareRendererObserver mObserver; + private SurfaceControl mSurfaceControl; 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 Session mSession; + private final ViewRootWrapper mViewRoot; + private final SurfaceControlWrapper mSurfaceControlWrapper; + private final ViewRootImpl.SurfaceChangedCallback mSurfaceChangedCallback; + private final Handler mHandler; + private final ChoreographerWrapper mChoreographer; - private long mBeginTime = UNKNOWN_TIMESTAMP; - private long mEndTime = UNKNOWN_TIMESTAMP; - private boolean mSessionEnd; + private long mBeginVsyncId = INVALID_ID; + private long mEndVsyncId = INVALID_ID; + private boolean mMetricsFinalized; private boolean mCancelled = false; - private int mTotalFramesCount = 0; - private int mMissedFramesCount = 0; - private int mSfMissedFramesCount = 0; - private long mMaxFrameTimeNanos = 0; - private Session mSession; + private static class JankInfo { + long frameVsyncId; + long totalDurationNanos; + boolean isFirstFrame; + boolean hwuiCallbackFired; + boolean surfaceControlCallbackFired; + @JankType int jankType; + + static JankInfo createFromHwuiCallback(long frameVsyncId, long totalDurationNanos, + boolean isFirstFrame) { + return new JankInfo(frameVsyncId, true, false, JANK_NONE, totalDurationNanos, + isFirstFrame); + } + + static JankInfo createFromSurfaceControlCallback(long frameVsyncId, + @JankType int jankType) { + return new JankInfo(frameVsyncId, false, true, jankType, 0, false /* isFirstFrame */); + } + + private JankInfo(long frameVsyncId, boolean hwuiCallbackFired, + boolean surfaceControlCallbackFired, @JankType int jankType, + long totalDurationNanos, boolean isFirstFrame) { + this.frameVsyncId = frameVsyncId; + this.hwuiCallbackFired = hwuiCallbackFired; + this.surfaceControlCallbackFired = surfaceControlCallbackFired; + this.totalDurationNanos = totalDurationNanos; + this.jankType = jankType; + this.isFirstFrame = isFirstFrame; + } + } public FrameTracker(@NonNull Session session, @NonNull Handler handler, - @NonNull ThreadedRendererWrapper renderer, @NonNull FrameMetricsWrapper metrics, - int traceThresholdMissedFrames, int traceThresholdFrameTimeMillis) { + @NonNull ThreadedRendererWrapper renderer, @NonNull ViewRootWrapper viewRootWrapper, + @NonNull SurfaceControlWrapper surfaceControlWrapper, + @NonNull ChoreographerWrapper choreographer, + @NonNull FrameMetricsWrapper metrics, int traceThresholdMissedFrames, + int traceThresholdFrameTimeMillis) { mSession = session; mRendererWrapper = renderer; mMetricsWrapper = metrics; + mViewRoot = viewRootWrapper; + mChoreographer = choreographer; + mSurfaceControlWrapper = surfaceControlWrapper; + mHandler = handler; mObserver = new HardwareRendererObserver(this, mMetricsWrapper.getTiming(), handler); mTraceThresholdMissedFrames = traceThresholdMissedFrames; mTraceThresholdFrameTimeMillis = traceThresholdFrameTimeMillis; + + // If the surface isn't valid yet, wait until it's created. + if (viewRootWrapper.getSurfaceControl().isValid()) { + mSurfaceControl = viewRootWrapper.getSurfaceControl(); + } + mSurfaceChangedCallback = new ViewRootImpl.SurfaceChangedCallback() { + @Override + public void surfaceCreated(SurfaceControl.Transaction t) { + synchronized (FrameTracker.this) { + if (mSurfaceControl == null) { + mSurfaceControl = viewRootWrapper.getSurfaceControl(); + if (mBeginVsyncId != INVALID_ID) { + mSurfaceControlWrapper.addJankStatsListener( + FrameTracker.this, mSurfaceControl); + } + } + } + } + + @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(() -> { + synchronized (FrameTracker.this) { + if (!mMetricsFinalized) { + finish(mJankInfos.size() - 1); + } + } + }, 50); + } + }; + viewRootWrapper.addSurfaceChangedCallback(mSurfaceChangedCallback); } /** * Begin a trace session of the CUJ. */ public synchronized void begin() { - long timestamp = System.nanoTime(); - if (DEBUG) { - Log.d(TAG, "begin: time(ns)=" + timestamp + ", begin(ns)=" + mBeginTime - + ", end(ns)=" + mEndTime + ", session=" + mSession.getName()); - } - mBeginTime = timestamp; - mEndTime = UNKNOWN_TIMESTAMP; - Trace.beginAsyncSection(mSession.getName(), (int) mBeginTime); + mBeginVsyncId = mChoreographer.getVsyncId() + 1; + Trace.beginAsyncSection(mSession.getName(), (int) mBeginVsyncId); mRendererWrapper.addObserver(mObserver); + if (mSurfaceControl != null) { + mSurfaceControlWrapper.addJankStatsListener(this, mSurfaceControl); + } } /** * End the trace session of the CUJ. */ public synchronized void end() { - long timestamp = System.nanoTime(); - if (DEBUG) { - Log.d(TAG, "end: time(ns)=" + timestamp + ", begin(ns)=" + mBeginTime - + ", end(ns)=" + mEndTime + ", session=" + mSession.getName()); + mEndVsyncId = mChoreographer.getVsyncId(); + Trace.endAsyncSection(mSession.getName(), (int) mBeginVsyncId); + if (mEndVsyncId == mBeginVsyncId) { + cancel(); } - mEndTime = timestamp; - Trace.endAsyncSection(mSession.getName(), (int) mBeginTime); // 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. @@ -102,14 +190,45 @@ public class FrameTracker implements HardwareRendererObserver.OnFrameMetricsAvai * Cancel the trace session of the CUJ. */ public synchronized void cancel() { - if (mBeginTime == UNKNOWN_TIMESTAMP || mEndTime != UNKNOWN_TIMESTAMP) return; - if (DEBUG) { - Log.d(TAG, "cancel: time(ns)=" + System.nanoTime() + ", begin(ns)=" + mBeginTime - + ", end(ns)=" + mEndTime + ", session=" + mSession.getName()); - } - Trace.endAsyncSection(mSession.getName(), (int) mBeginTime); - mRendererWrapper.removeObserver(mObserver); + if (mBeginVsyncId == INVALID_ID || mEndVsyncId != INVALID_ID) return; + Trace.endAsyncSection(mSession.getName(), (int) mBeginVsyncId); mCancelled = true; + removeObservers(); + } + + @Override + public synchronized void onJankDataAvailable(SurfaceControl.JankData[] jankData) { + if (mCancelled) { + return; + } + + for (SurfaceControl.JankData jankStat : jankData) { + if (!isInRange(jankStat.frameVsyncId)) { + continue; + } + JankInfo info = findJankInfo(jankStat.frameVsyncId); + if (info != null) { + info.surfaceControlCallbackFired = true; + info.jankType = jankStat.jankType; + } else { + mJankInfos.put((int) jankStat.frameVsyncId, + JankInfo.createFromSurfaceControlCallback( + jankStat.frameVsyncId, jankStat.jankType)); + } + } + processJankInfos(); + } + + private @Nullable 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 @@ -121,60 +240,152 @@ public class FrameTracker implements HardwareRendererObserver.OnFrameMetricsAvai // Since this callback might come a little bit late after the end() call. // We should keep tracking the begin / end timestamp. // Then 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]; - long vsyncTimestamp = mMetricsWrapper.getMetric(FrameMetrics.VSYNC_TIMESTAMP); - // Discard the frame metrics which is not in the trace session. - if (vsyncTimestamp < mBeginTime) return; - - // We stop getting callback when the vsync is later than the end calls. - if (mEndTime != UNKNOWN_TIMESTAMP && vsyncTimestamp > mEndTime && !mSessionEnd) { - mSessionEnd = true; - // The tracing has been ended, remove the observer, see if need to trigger perfetto. - mRendererWrapper.removeObserver(mObserver); - - // Log the frame stats as counters to make them easily accessible in traces. - Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#sfMissedFrames", - mSfMissedFramesCount); - Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#missedFrames", - mMissedFramesCount); - Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#totalFrames", - mTotalFramesCount); - Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#maxFrameTimeMillis", - (int) (mMaxFrameTimeNanos / NANOS_IN_MILLISECOND)); - - // Trigger perfetto if necessary. - boolean overMissedFramesThreshold = mTraceThresholdMissedFrames != -1 - && (mMissedFramesCount + mSfMissedFramesCount) >= mTraceThresholdMissedFrames; - boolean overFrameTimeThreshold = mTraceThresholdFrameTimeMillis != -1 - && mMaxFrameTimeNanos >= mTraceThresholdFrameTimeMillis * NANOS_IN_MILLISECOND; - if (overMissedFramesThreshold || overFrameTimeThreshold) { - triggerPerfetto(); - } - if (mSession.logToStatsd()) { - FrameworkStatsLog.write( - FrameworkStatsLog.UI_INTERACTION_FRAME_INFO_REPORTED, - mSession.getStatsdInteractionType(), - mTotalFramesCount, - mMissedFramesCount, - mMaxFrameTimeNanos, - mSfMissedFramesCount); + 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(); + } + + /** + * Finds the first index in {@link #mJankInfos} which happened on or after {@link #mEndVsyncId}, + * or -1 if the session hasn't ended yet. + */ + private int getIndexOnOrAfterEnd() { + if (mEndVsyncId == INVALID_ID || mMetricsFinalized) { + return -1; + } + JankInfo last = mJankInfos.size() == 0 ? null : mJankInfos.valueAt(mJankInfos.size() - 1); + if (last == null) { + return -1; + } + if (last.frameVsyncId < mEndVsyncId) { + return -1; + } + + int lastIndex = -1; + for (int i = mJankInfos.size() - 1; i >= 0; i--) { + JankInfo info = mJankInfos.valueAt(i); + if (info.frameVsyncId >= mEndVsyncId) { + if (info.hwuiCallbackFired && info.surfaceControlCallbackFired) { + lastIndex = i; + } + } else { + break; } + } + return lastIndex; + } + + private void processJankInfos() { + int indexOnOrAfterEnd = getIndexOnOrAfterEnd(); + if (indexOnOrAfterEnd == -1) { return; } + finish(indexOnOrAfterEnd); + } - long totalDurationNanos = mMetricsWrapper.getMetric(FrameMetrics.TOTAL_DURATION); - boolean isFirstFrame = mMetricsWrapper.getMetric(FrameMetrics.FIRST_DRAW_FRAME) == 1; - boolean isJankyFrame = !isFirstFrame && totalDurationNanos > JANK_THRESHOLD_NANOS; + private void finish(int indexOnOrAfterEnd) { + + mMetricsFinalized = true; - mTotalFramesCount += 1; + // The tracing has been ended, remove the observer, see if need to trigger perfetto. + removeObservers(); - if (!isFirstFrame) { - mMaxFrameTimeNanos = Math.max(totalDurationNanos, mMaxFrameTimeNanos); + int totalFramesCount = 0; + long maxFrameTimeNanos = 0; + int missedAppFramesCount = 0; + int missedSfFramesCounts = 0; + + for (int i = 0; i <= indexOnOrAfterEnd; i++) { + JankInfo info = mJankInfos.valueAt(i); + if (info.isFirstFrame) { + continue; + } + if (info.surfaceControlCallbackFired) { + totalFramesCount++; + + // Only count missed frames if it's not stuffed. + if ((info.jankType & PREDICTION_ERROR) != 0 + || ((info.jankType & JANK_APP_DEADLINE_MISSED) != 0 + && (info.jankType & BUFFER_STUFFING) == 0)) { + Log.w(TAG, "Missed App frame:" + info.jankType); + missedAppFramesCount++; + } + 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) { + Log.w(TAG, "Missed SF frame:" + info.jankType); + missedSfFramesCounts++; + } + // TODO (b/174755489): Early latch currently gets fired way too often, so we have + // to ignore it for now. + if (!info.hwuiCallbackFired) { + Log.w(TAG, "Missing HWUI jank callback for vsyncId: " + info.frameVsyncId); + } + } + if (info.hwuiCallbackFired) { + maxFrameTimeNanos = Math.max(info.totalDurationNanos, maxFrameTimeNanos); + if (!info.surfaceControlCallbackFired) { + Log.w(TAG, "Missing SF jank callback for vsyncId: " + info.frameVsyncId); + } + } + } + + // Log the frame stats as counters to make them easily accessible in traces. + Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#missedAppFrames", + missedAppFramesCount); + Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#missedSfFrames", + missedSfFramesCounts); + Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#totalFrames", + totalFramesCount); + Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#maxFrameTimeMillis", + (int) (maxFrameTimeNanos / NANOS_IN_MILLISECOND)); + + // Trigger perfetto if necessary. + boolean overMissedFramesThreshold = mTraceThresholdMissedFrames != -1 + && missedAppFramesCount + missedSfFramesCounts >= mTraceThresholdMissedFrames; + boolean overFrameTimeThreshold = mTraceThresholdFrameTimeMillis != -1 + && maxFrameTimeNanos >= mTraceThresholdFrameTimeMillis * NANOS_IN_MILLISECOND; + if (overMissedFramesThreshold || overFrameTimeThreshold) { + triggerPerfetto(); + } + if (mSession.logToStatsd()) { + FrameworkStatsLog.write( + FrameworkStatsLog.UI_INTERACTION_FRAME_INFO_REPORTED, + mSession.getStatsdInteractionType(), + totalFramesCount, + missedAppFramesCount + missedSfFramesCounts, + maxFrameTimeNanos, + missedSfFramesCounts); } + if (DEBUG) { + Log.i(TAG, "FrameTracker: CUJ=" + mSession.getName() + + " totalFrames=" + totalFramesCount + + " missedAppFrames=" + missedAppFramesCount + + " missedSfFrames=" + missedSfFramesCounts + + " maxFrameTimeMillis=" + maxFrameTimeNanos / NANOS_IN_MILLISECOND); + } + } - // TODO(b/171049584): Also update mSfMissedFramesCount once the data is available. - if (isJankyFrame) { - mMissedFramesCount += 1; + private void removeObservers() { + mRendererWrapper.removeObserver(mObserver); + mSurfaceControlWrapper.removeJankStatsListener(this); + if (mSurfaceChangedCallback != null) { + mViewRoot.removeSurfaceChangedCallback(mSurfaceChangedCallback); } } @@ -189,7 +400,7 @@ public class FrameTracker implements HardwareRendererObserver.OnFrameMetricsAvai * A wrapper class that we can spy FrameMetrics (a final class) in unit tests. */ public static class FrameMetricsWrapper { - private FrameMetrics mFrameMetrics; + private final FrameMetrics mFrameMetrics; public FrameMetricsWrapper() { mFrameMetrics = new FrameMetrics(); @@ -217,7 +428,7 @@ public class FrameTracker implements HardwareRendererObserver.OnFrameMetricsAvai * A wrapper class that we can spy ThreadedRenderer (a final class) in unit tests. */ public static class ThreadedRendererWrapper { - private ThreadedRenderer mRenderer; + private final ThreadedRenderer mRenderer; public ThreadedRendererWrapper(ThreadedRenderer renderer) { mRenderer = renderer; @@ -239,4 +450,49 @@ public class FrameTracker implements HardwareRendererObserver.OnFrameMetricsAvai mRenderer.removeObserver(observer); } } + + public static class ViewRootWrapper { + private final ViewRootImpl mViewRoot; + + public ViewRootWrapper(ViewRootImpl viewRoot) { + mViewRoot = viewRoot; + } + + public void addSurfaceChangedCallback(ViewRootImpl.SurfaceChangedCallback callback) { + mViewRoot.addSurfaceChangedCallback(callback); + } + + public void removeSurfaceChangedCallback(ViewRootImpl.SurfaceChangedCallback callback) { + mViewRoot.removeSurfaceChangedCallback(callback); + } + + public SurfaceControl getSurfaceControl() { + return mViewRoot.getSurfaceControl(); + } + } + + 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(); + } + } } diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java index 1e2ce288974c..7c10a0a4556f 100644 --- a/core/java/com/android/internal/jank/InteractionJankMonitor.java +++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java @@ -16,6 +16,7 @@ package com.android.internal.jank; +import static com.android.internal.jank.FrameTracker.*; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_PIP; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_ICON; @@ -42,11 +43,14 @@ import android.os.HandlerThread; import android.provider.DeviceConfig; import android.util.Log; import android.util.SparseArray; +import android.view.Choreographer; +import android.view.SurfaceControl; import android.view.View; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.jank.FrameTracker.FrameMetricsWrapper; import com.android.internal.jank.FrameTracker.ThreadedRendererWrapper; +import com.android.internal.jank.FrameTracker.ViewRootWrapper; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -125,13 +129,11 @@ public class InteractionJankMonitor { private final DeviceConfig.OnPropertiesChangedListener mPropertiesChangedListener = this::updateProperties; - private ThreadedRendererWrapper mRenderer; private FrameMetricsWrapper mMetrics; private SparseArray<FrameTracker> mRunningTrackers; private SparseArray<Runnable> mTimeoutActions; private HandlerThread mWorker; - private boolean mInitialized; private boolean mEnabled = DEFAULT_ENABLED; private int mSamplingInterval = DEFAULT_SAMPLING_INTERVAL; private int mTraceThresholdMissedFrames = DEFAULT_TRACE_THRESHOLD_MISSED_FRAMES; @@ -188,43 +190,21 @@ public class InteractionJankMonitor { mRunningTrackers = new SparseArray<>(); mTimeoutActions = new SparseArray<>(); mWorker = worker; - } - - /** - * Init InteractionJankMonitor for later instrumentation. - * - * @param view Any view in the view tree to get context and ThreadedRenderer. - * @return boolean true if the instance has been initialized successfully. - */ - public boolean init(@NonNull View view) { - if (!mInitialized) { - synchronized (this) { - if (!mInitialized) { - if (!view.isAttachedToWindow()) { - Log.d(TAG, "Expect an attached view!", new Throwable()); - return false; - } - mRenderer = new ThreadedRendererWrapper(view.getThreadedRenderer()); - mMetrics = new FrameMetricsWrapper(); - mWorker.start(); - mEnabled = DEFAULT_ENABLED; - mSamplingInterval = DEFAULT_SAMPLING_INTERVAL; - mInitialized = true; - - // Post initialization to the background in case we're running on the main - // thread. - mWorker.getThreadHandler().post( - () -> mPropertiesChangedListener.onPropertiesChanged( - DeviceConfig.getProperties( - DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR))); - DeviceConfig.addOnPropertiesChangedListener( - DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR, - new HandlerExecutor(mWorker.getThreadHandler()), - mPropertiesChangedListener); - } - } - } - return true; + mMetrics = new FrameMetricsWrapper(); + mWorker.start(); + mEnabled = DEFAULT_ENABLED; + mSamplingInterval = DEFAULT_SAMPLING_INTERVAL; + + // Post initialization to the background in case we're running on the main + // thread. + mWorker.getThreadHandler().post( + () -> mPropertiesChangedListener.onPropertiesChanged( + DeviceConfig.getProperties( + DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR))); + DeviceConfig.addOnPropertiesChangedListener( + DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR, + new HandlerExecutor(mWorker.getThreadHandler()), + mPropertiesChangedListener); } /** @@ -234,37 +214,39 @@ public class InteractionJankMonitor { * @return instance of the FrameTracker */ @VisibleForTesting - public FrameTracker createFrameTracker(Session session) { + public FrameTracker createFrameTracker(View v, Session session) { synchronized (this) { - if (!mInitialized) return null; - return new FrameTracker(session, mWorker.getThreadHandler(), mRenderer, mMetrics, + return new FrameTracker(session, mWorker.getThreadHandler(), + new ThreadedRendererWrapper(v.getThreadedRenderer()), + new ViewRootWrapper(v.getViewRootImpl()), new SurfaceControlWrapper(), + new ChoreographerWrapper(Choreographer.getInstance()), mMetrics, mTraceThresholdMissedFrames, mTraceThresholdFrameTimeMillis); } } /** - * Begin a trace session, must invoke {@link #init(View)} before invoking this method. + * Begin a trace session. * * @param cujType the specific {@link InteractionJankMonitor.CujType}. * @return boolean true if the tracker is started successfully, false otherwise. */ - public boolean begin(@CujType int cujType) { + public boolean begin(View v, @CujType int cujType) { synchronized (this) { - return begin(cujType, DEFAULT_TIMEOUT_MS); + return begin(v, cujType, DEFAULT_TIMEOUT_MS); } } /** - * Begin a trace session, must invoke {@link #init(View)} before invoking this method. + * Begin a trace session. * * @param cujType the specific {@link InteractionJankMonitor.CujType}. * @param timeout the elapsed time in ms until firing the timeout action. * @return boolean true if the tracker is started successfully, false otherwise. */ - public boolean begin(@CujType int cujType, long timeout) { + public boolean begin(View v, @CujType int cujType, long timeout) { synchronized (this) { - if (!mInitialized) { - Log.d(TAG, "Not initialized!", new Throwable()); + if (!v.isAttachedToWindow()) { + Log.d(TAG, "View not attached!", new Throwable()); return false; } boolean shouldSample = ThreadLocalRandom.current().nextInt() % mSamplingInterval == 0; @@ -276,7 +258,7 @@ public class InteractionJankMonitor { if (tracker != null) return false; // begin a new trace session. - tracker = createFrameTracker(new Session(cujType)); + tracker = createFrameTracker(v, new Session(cujType)); mRunningTrackers.put(cujType, tracker); tracker.begin(); @@ -289,7 +271,7 @@ public class InteractionJankMonitor { } /** - * End a trace session, must invoke {@link #init(View)} before invoking this method. + * End a trace session. * * @param cujType the specific {@link InteractionJankMonitor.CujType}. * @return boolean true if the tracker is ended successfully, false otherwise. @@ -297,10 +279,7 @@ public class InteractionJankMonitor { public boolean end(@CujType int cujType) { //TODO (163505250): This should be no-op if not in droid food rom. synchronized (this) { - if (!mInitialized) { - Log.d(TAG, "Not initialized!", new Throwable()); - return false; - } + // remove the timeout action first. Runnable timeout = mTimeoutActions.get(cujType); if (timeout != null) { @@ -318,17 +297,13 @@ public class InteractionJankMonitor { } /** - * Cancel the trace session, must invoke {@link #init(View)} before invoking this method. + * Cancel the trace session. * * @return boolean true if the tracker is cancelled successfully, false otherwise. */ public boolean cancel(@CujType int cujType) { //TODO (163505250): This should be no-op if not in droid food rom. synchronized (this) { - if (!mInitialized) { - Log.d(TAG, "Not initialized!", new Throwable()); - return false; - } // remove the timeout action first. Runnable timeout = mTimeoutActions.get(cujType); if (timeout != null) { @@ -347,7 +322,6 @@ public class InteractionJankMonitor { private FrameTracker getTracker(@CujType int cuj) { synchronized (this) { - if (!mInitialized) return null; return mRunningTrackers.get(cuj); } } @@ -376,7 +350,6 @@ public class InteractionJankMonitor { @VisibleForTesting public void trigger(Session session) { synchronized (this) { - if (!mInitialized) return; mWorker.getThreadHandler().post( () -> PerfettoTrigger.trigger(session.getPerfettoTrigger())); } diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index bc9f3b6fe9a7..b8e18072d6e5 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -1663,21 +1663,22 @@ static void nativeAddJankDataListener(JNIEnv* env, jclass clazz, if (surface == nullptr) { return; } - JankDataListenerWrapper* wrapper = + sp<JankDataListenerWrapper> wrapper = reinterpret_cast<JankDataListenerWrapper*>(jankDataCallbackListenerPtr); TransactionCompletedListener::getInstance()->addJankListener(wrapper, surface); } static void nativeRemoveJankDataListener(JNIEnv* env, jclass clazz, jlong jankDataCallbackListenerPtr) { - JankDataListenerWrapper* wrapper = + sp<JankDataListenerWrapper> wrapper = reinterpret_cast<JankDataListenerWrapper*>(jankDataCallbackListenerPtr); TransactionCompletedListener::getInstance()->removeJankListener(wrapper); } static jlong nativeCreateJankDataListenerWrapper(JNIEnv* env, jclass clazz, jobject jankDataListenerObject) { - return reinterpret_cast<jlong>(new JankDataListenerWrapper(env, jankDataListenerObject)); + return reinterpret_cast<jlong>( + new JankDataListenerWrapper(env, jankDataListenerObject)); } static jint nativeGetGPUContextPriority(JNIEnv* env, jclass clazz) { diff --git a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java index 7f7bfa3c5e3d..10aaf317fb49 100644 --- a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java +++ b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java @@ -16,6 +16,12 @@ package com.android.internal.jank; +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 com.android.internal.jank.FrameTracker.SurfaceControlWrapper; +import static com.android.internal.jank.FrameTracker.ViewRootWrapper; import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE; import static com.google.common.truth.Truth.assertThat; @@ -23,6 +29,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.only; import static org.mockito.Mockito.verify; @@ -30,12 +37,17 @@ import static org.mockito.Mockito.when; import android.os.Handler; import android.view.FrameMetrics; +import android.view.SurfaceControl; +import android.view.SurfaceControl.JankData; +import android.view.SurfaceControl.JankData.JankType; +import android.view.SurfaceControl.OnJankDataListener; import android.view.View; import android.view.ViewAttachTestActivity; import androidx.test.filters.SmallTest; import androidx.test.rule.ActivityTestRule; +import com.android.internal.jank.FrameTracker.ChoreographerWrapper; import com.android.internal.jank.FrameTracker.FrameMetricsWrapper; import com.android.internal.jank.FrameTracker.ThreadedRendererWrapper; import com.android.internal.jank.InteractionJankMonitor.Session; @@ -43,6 +55,7 @@ import com.android.internal.jank.InteractionJankMonitor.Session; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import java.util.concurrent.TimeUnit; @@ -58,6 +71,11 @@ public class FrameTrackerTest { private FrameTracker mTracker; private ThreadedRendererWrapper mRenderer; private FrameMetricsWrapper mWrapper; + private SurfaceControlWrapper mSurfaceControlWrapper; + private ViewRootWrapper mViewRootWrapper; + private ChoreographerWrapper mChoreographer; + private ArgumentCaptor<OnJankDataListener> mListenerCapture; + private SurfaceControl mSurfaceControl; @Before public void setup() { @@ -68,63 +86,116 @@ public class FrameTrackerTest { Handler handler = mRule.getActivity().getMainThreadHandler(); mWrapper = Mockito.spy(new FrameMetricsWrapper()); - // For simplicity - provide current timestamp anytime mWrapper asked for VSYNC_TIMESTAMP - when(mWrapper.getMetric(FrameMetrics.VSYNC_TIMESTAMP)) - .then(unusedInvocation -> System.nanoTime()); mRenderer = Mockito.spy(new ThreadedRendererWrapper(view.getThreadedRenderer())); doNothing().when(mRenderer).addObserver(any()); doNothing().when(mRenderer).removeObserver(any()); + mSurfaceControl = new SurfaceControl.Builder().setName("Surface").build(); + mViewRootWrapper = mock(ViewRootWrapper.class); + when(mViewRootWrapper.getSurfaceControl()).thenReturn(mSurfaceControl); + mSurfaceControlWrapper = mock(SurfaceControlWrapper.class); + + mListenerCapture = ArgumentCaptor.forClass(OnJankDataListener.class); + doNothing().when(mSurfaceControlWrapper).addJankStatsListener( + mListenerCapture.capture(), any()); + mChoreographer = mock(ChoreographerWrapper.class); + + Session session = new Session(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE); mTracker = Mockito.spy( - new FrameTracker(session, handler, mRenderer, mWrapper, + new FrameTracker(session, handler, mRenderer, mViewRootWrapper, + mSurfaceControlWrapper, mChoreographer, mWrapper, /*traceThresholdMissedFrames=*/ 1, /*traceThresholdFrameTimeMillis=*/ -1)); doNothing().when(mTracker).triggerPerfetto(); } @Test - public void testOnlyFirstFrameOverThreshold() { + public void testOnlyFirstWindowFrameOverThreshold() { // Just provide current timestamp anytime mWrapper asked for VSYNC_TIMESTAMP when(mWrapper.getMetric(FrameMetrics.VSYNC_TIMESTAMP)) .then(unusedInvocation -> System.nanoTime()); + when(mChoreographer.getVsyncId()).thenReturn(100L); mTracker.begin(); verify(mRenderer, only()).addObserver(any()); // send first frame with a long duration - should not be taken into account - setupFirstFrameMockWithDuration(100); - mTracker.onFrameMetricsAvailable(0); + sendFirstWindowFrame(100, JANK_APP_DEADLINE_MISSED, 100L); // send another frame with a short duration - should not be considered janky - setupOtherFrameMockWithDuration(5); - mTracker.onFrameMetricsAvailable(0); + sendFirstWindowFrame(5, JANK_NONE, 101L); // end the trace session, the last janky frame is after the end() so is discarded. + when(mChoreographer.getVsyncId()).thenReturn(101L); mTracker.end(); - setupOtherFrameMockWithDuration(500); - mTracker.onFrameMetricsAvailable(0); + sendFrame(500, JANK_APP_DEADLINE_MISSED, 102L); + + verify(mRenderer).removeObserver(any()); + verify(mTracker, never()).triggerPerfetto(); + } + + @Test + public void testSfJank() { + when(mChoreographer.getVsyncId()).thenReturn(100L); + mTracker.begin(); + verify(mRenderer, only()).addObserver(any()); + + // send first frame - not janky + sendFrame(4, JANK_NONE, 100L); + + // send another frame - should be considered janky + sendFrame(40, JANK_SURFACEFLINGER_DEADLINE_MISSED, 101L); + + // end the trace session + when(mChoreographer.getVsyncId()).thenReturn(101L); + mTracker.end(); + sendFrame(4, JANK_NONE, 102L); + + verify(mRenderer).removeObserver(any()); + + // We detected a janky frame - trigger Perfetto + verify(mTracker).triggerPerfetto(); + } + + @Test + public void testFirstFrameJankyNoTrigger() { + when(mChoreographer.getVsyncId()).thenReturn(100L); + mTracker.begin(); + verify(mRenderer, only()).addObserver(any()); + + // send first frame - janky + sendFrame(40, JANK_APP_DEADLINE_MISSED, 100L); + + // send another frame - not jank + sendFrame(4, JANK_NONE, 101L); + + // end the trace session + when(mChoreographer.getVsyncId()).thenReturn(101L); + mTracker.end(); + sendFrame(4, JANK_NONE, 102L); verify(mRenderer).removeObserver(any()); + + // We detected a janky frame - trigger Perfetto verify(mTracker, never()).triggerPerfetto(); } @Test public void testOtherFrameOverThreshold() { + when(mChoreographer.getVsyncId()).thenReturn(100L); mTracker.begin(); verify(mRenderer, only()).addObserver(any()); // send first frame - not janky - setupFirstFrameMockWithDuration(4); - mTracker.onFrameMetricsAvailable(0); + sendFrame(4, JANK_NONE, 100L); // send another frame - should be considered janky - setupOtherFrameMockWithDuration(40); - mTracker.onFrameMetricsAvailable(0); + sendFrame(40, JANK_APP_DEADLINE_MISSED, 101L); // end the trace session + when(mChoreographer.getVsyncId()).thenReturn(101L); mTracker.end(); - setupOtherFrameMockWithDuration(5); - mTracker.onFrameMetricsAvailable(0); + sendFrame(4, JANK_NONE, 102L); verify(mRenderer).removeObserver(any()); @@ -134,29 +205,23 @@ public class FrameTrackerTest { @Test public void testLastFrameOverThresholdBeforeEnd() { + when(mChoreographer.getVsyncId()).thenReturn(100L); mTracker.begin(); verify(mRenderer, only()).addObserver(any()); // send first frame - not janky - setupFirstFrameMockWithDuration(4); - mTracker.onFrameMetricsAvailable(0); + sendFrame(4, JANK_NONE, 100L); // send another frame - not janky - setupOtherFrameMockWithDuration(4); - mTracker.onFrameMetricsAvailable(0); + sendFrame(4, JANK_NONE, 101L); // end the trace session, simulate one more valid callback came after the end call. - when(mWrapper.getMetric(FrameMetrics.VSYNC_TIMESTAMP)) - .thenReturn(System.nanoTime()); - setupOtherFrameMockWithDuration(50); + when(mChoreographer.getVsyncId()).thenReturn(102L); mTracker.end(); - mTracker.onFrameMetricsAvailable(0); + sendFrame(50, JANK_APP_DEADLINE_MISSED, 102L); - // One more callback with VSYNC after the end() timestamp. - when(mWrapper.getMetric(FrameMetrics.VSYNC_TIMESTAMP)) - .thenReturn(System.nanoTime()); - setupOtherFrameMockWithDuration(5); - mTracker.onFrameMetricsAvailable(0); + // One more callback with VSYNC after the end() vsync id. + sendFrame(4, JANK_NONE, 103L); verify(mRenderer).removeObserver(any()); @@ -166,20 +231,18 @@ public class FrameTrackerTest { @Test public void testBeginCancel() { + when(mChoreographer.getVsyncId()).thenReturn(100L); mTracker.begin(); verify(mRenderer).addObserver(any()); // First frame - not janky - setupFirstFrameMockWithDuration(4); - mTracker.onFrameMetricsAvailable(0); + sendFrame(4, JANK_NONE, 100L); // normal frame - not janky - setupOtherFrameMockWithDuration(12); - mTracker.onFrameMetricsAvailable(0); + sendFrame(4, JANK_NONE, 101L); // a janky frame - setupOtherFrameMockWithDuration(30); - mTracker.onFrameMetricsAvailable(0); + sendFrame(50, JANK_APP_DEADLINE_MISSED, 102L); mTracker.cancel(); verify(mRenderer).removeObserver(any()); @@ -187,15 +250,26 @@ public class FrameTrackerTest { verify(mTracker, never()).triggerPerfetto(); } - private void setupFirstFrameMockWithDuration(long durationMillis) { - doReturn(1L).when(mWrapper).getMetric(FrameMetrics.FIRST_DRAW_FRAME); - doReturn(TimeUnit.MILLISECONDS.toNanos(durationMillis)) - .when(mWrapper).getMetric(FrameMetrics.TOTAL_DURATION); + private void sendFirstWindowFrame(long durationMillis, + @JankType int jankType, long vsyncId) { + sendFrame(durationMillis, jankType, vsyncId, true /* firstWindowFrame */); } - private void setupOtherFrameMockWithDuration(long durationMillis) { - doReturn(0L).when(mWrapper).getMetric(FrameMetrics.FIRST_DRAW_FRAME); + private void sendFrame(long durationMillis, + @JankType int jankType, long vsyncId) { + sendFrame(durationMillis, jankType, vsyncId, false /* firstWindowFrame */); + } + + private void sendFrame(long durationMillis, + @JankType int jankType, long vsyncId, boolean firstWindowFrame) { + when(mWrapper.getTiming()).thenReturn(new long[] { 0, vsyncId }); + doReturn(firstWindowFrame ? 1L : 0L).when(mWrapper) + .getMetric(FrameMetrics.FIRST_DRAW_FRAME); doReturn(TimeUnit.MILLISECONDS.toNanos(durationMillis)) .when(mWrapper).getMetric(FrameMetrics.TOTAL_DURATION); + mTracker.onFrameMetricsAvailable(0); + mListenerCapture.getValue().onJankDataAvailable(new JankData[] { + new JankData(vsyncId, jankType) + }); } } diff --git a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java index 0ef5643b0905..c4c475b6a0e9 100644 --- a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java +++ b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java @@ -16,6 +16,8 @@ package com.android.internal.jank; +import static com.android.internal.jank.FrameTracker.SurfaceControlWrapper; +import static com.android.internal.jank.FrameTracker.ViewRootWrapper; import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE; import static com.android.internal.jank.InteractionJankMonitor.CUJ_TO_STATSD_INTERACTION_TYPE; @@ -27,6 +29,7 @@ import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -85,21 +88,19 @@ public class InteractionJankMonitorTest { public void testBeginEnd() { // Should return false if the view is not attached. InteractionJankMonitor monitor = spy(new InteractionJankMonitor(mWorker)); - assertThat(monitor.init(new View(mActivity))).isFalse(); - - // Verify we init InteractionJankMonitor correctly. - assertThat(monitor.init(mView)).isTrue(); verify(mWorker).start(); Session session = new Session(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE); FrameTracker tracker = spy(new FrameTracker(session, mWorker.getThreadHandler(), new ThreadedRendererWrapper(mView.getThreadedRenderer()), + new ViewRootWrapper(mView.getViewRootImpl()), new SurfaceControlWrapper(), + mock(FrameTracker.ChoreographerWrapper.class), new FrameMetricsWrapper(), /*traceThresholdMissedFrames=*/ 1, /*traceThresholdFrameTimeMillis=*/ -1)); - doReturn(tracker).when(monitor).createFrameTracker(any()); + doReturn(tracker).when(monitor).createFrameTracker(any(), any()); // Simulate a trace session and see if begin / end are invoked. - assertThat(monitor.begin(session.getCuj())).isTrue(); + assertThat(monitor.begin(mView, session.getCuj())).isTrue(); verify(tracker).begin(); assertThat(monitor.end(session.getCuj())).isTrue(); verify(tracker).end(); @@ -108,7 +109,6 @@ public class InteractionJankMonitorTest { @Test public void testDisabledThroughDeviceConfig() { InteractionJankMonitor monitor = new InteractionJankMonitor(mWorker); - monitor.init(mView); HashMap<String, String> propertiesValues = new HashMap<>(); propertiesValues.put("enabled", "false"); @@ -116,7 +116,7 @@ public class InteractionJankMonitorTest { DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR, propertiesValues); monitor.getPropertiesChangedListener().onPropertiesChanged(properties); - assertThat(monitor.begin(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse(); + assertThat(monitor.begin(mView, CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse(); assertThat(monitor.end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse(); } @@ -125,14 +125,13 @@ public class InteractionJankMonitorTest { InteractionJankMonitor monitor = new InteractionJankMonitor(mWorker); // Should return false if invoking begin / end without init invocation. - assertThat(monitor.begin(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse(); + assertThat(monitor.begin(mView, CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse(); assertThat(monitor.end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse(); // Everything should be fine if invoking init first. boolean thrown = false; try { - monitor.init(mView); - assertThat(monitor.begin(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue(); + assertThat(monitor.begin(mView, CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue(); assertThat(monitor.end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue(); } catch (Exception ex) { thrown = true; @@ -146,16 +145,17 @@ public class InteractionJankMonitorTest { InteractionJankMonitor monitor = spy(new InteractionJankMonitor(mWorker)); ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class); - assertThat(monitor.init(mView)).isTrue(); Session session = new Session(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE); FrameTracker tracker = spy(new FrameTracker(session, mWorker.getThreadHandler(), new ThreadedRendererWrapper(mView.getThreadedRenderer()), + new ViewRootWrapper(mView.getViewRootImpl()), new SurfaceControlWrapper(), + mock(FrameTracker.ChoreographerWrapper.class), new FrameMetricsWrapper(), /*traceThresholdMissedFrames=*/ 1, /*traceThresholdFrameTimeMillis=*/ -1)); - doReturn(tracker).when(monitor).createFrameTracker(any()); + doReturn(tracker).when(monitor).createFrameTracker(any(), any()); - assertThat(monitor.begin(session.getCuj())).isTrue(); + assertThat(monitor.begin(mView, session.getCuj())).isTrue(); verify(tracker).begin(); verify(mWorker.getThreadHandler(), atLeastOnce()).sendMessageAtTime(captor.capture(), anyLong()); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index 519ce3611ce7..e706f76faca1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -152,8 +152,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, public void onPipAnimationStart(PipAnimationController.PipTransitionAnimator animator) { final int direction = animator.getTransitionDirection(); if (direction == TRANSITION_DIRECTION_TO_PIP) { - InteractionJankMonitor.getInstance().begin( - InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_PIP, 2000); + // TODO (b//169221267): Add jank listener for transactions without buffer updates. + //InteractionJankMonitor.getInstance().begin( + // InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_PIP, 2000); } sendOnPipTransitionStarted(direction); } @@ -166,8 +167,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, animator.getAnimationType()); sendOnPipTransitionFinished(direction); if (direction == TRANSITION_DIRECTION_TO_PIP) { - InteractionJankMonitor.getInstance().end( - InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_PIP); + // TODO (b//169221267): Add jank listener for transactions without buffer updates. + //InteractionJankMonitor.getInstance().end( + // InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_PIP); } } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ChoreographerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ChoreographerCompat.java deleted file mode 100644 index 76b447e3fa93..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ChoreographerCompat.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2017 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.systemui.shared.system; - -import static android.view.Choreographer.CALLBACK_INPUT; - -import android.view.Choreographer; - -/** - * Wraps the internal choreographer. - */ -public class ChoreographerCompat { - - /** - * Posts an input callback to the choreographer. - */ - public static void postInputFrame(Choreographer choreographer, Runnable runnable) { - choreographer.postCallback(CALLBACK_INPUT, runnable, null); - } - - public static Choreographer getSfInstance() { - return Choreographer.getSfInstance(); - } -} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java index 27cb4f60bd9c..19e7d7ea4e6e 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java @@ -49,16 +49,12 @@ public final class InteractionJankMonitorWrapper { public @interface CujType { } - public static void init(@NonNull View view) { - InteractionJankMonitor.getInstance().init(view); + public static boolean begin(View v, @CujType int cujType) { + return InteractionJankMonitor.getInstance().begin(v, cujType); } - public static boolean begin(@CujType int cujType) { - return InteractionJankMonitor.getInstance().begin(cujType); - } - - public static boolean begin(@CujType int cujType, long timeout) { - return InteractionJankMonitor.getInstance().begin(cujType, timeout); + public static boolean begin(View v, @CujType int cujType, long timeout) { + return InteractionJankMonitor.getInstance().begin(v, cujType, timeout); } public static boolean end(@CujType int cujType) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java index 45e8098e71d3..88b9c6cbddfd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java @@ -233,7 +233,8 @@ public class ActivityLaunchAnimator { @Override public void onAnimationStart(Animator animation) { - InteractionJankMonitor.getInstance().begin(CUJ_NOTIFICATION_APP_START); + InteractionJankMonitor.getInstance().begin(mSourceNotification, + CUJ_NOTIFICATION_APP_START); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java index 86ebc6b2c4ce..724921b3f7c8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java @@ -762,7 +762,8 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView @Override public void onAnimationStart(Animator animation) { mWasCancelled = false; - InteractionJankMonitor.getInstance().begin(getCujType(isAppearing)); + InteractionJankMonitor.getInstance().begin(ActivatableNotificationView.this, + getCujType(isAppearing)); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index e332f18ce183..f4d01f320e46 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -1553,7 +1553,8 @@ public class NotificationStackScrollLayoutController { // In the intercept we only start tracing when it's not a down (otherwise that down // would be duplicated when intercepted). if (scrollWantsIt && ev.getActionMasked() != MotionEvent.ACTION_DOWN) { - InteractionJankMonitor.getInstance().begin(CUJ_NOTIFICATION_SHADE_SCROLL_FLING); + InteractionJankMonitor.getInstance().begin(mView, + CUJ_NOTIFICATION_SHADE_SCROLL_FLING); } return swipeWantsIt || scrollWantsIt || expandWantsIt; } @@ -1619,7 +1620,7 @@ public class NotificationStackScrollLayoutController { case MotionEvent.ACTION_DOWN: if (scrollerWantsIt) { InteractionJankMonitor.getInstance() - .begin(CUJ_NOTIFICATION_SHADE_SCROLL_FLING); + .begin(mView, CUJ_NOTIFICATION_SHADE_SCROLL_FLING); } break; case MotionEvent.ACTION_UP: diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index ba08e76e6f66..194776e09725 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -1266,7 +1266,7 @@ public class NotificationPanelViewController extends PanelViewController { private void traceQsJank(boolean startTracing, boolean wasCancelled) { InteractionJankMonitor monitor = InteractionJankMonitor.getInstance(); if (startTracing) { - monitor.begin(CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE); + monitor.begin(mView, CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE); } else { if (wasCancelled) { monitor.cancel(CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE); @@ -1479,7 +1479,7 @@ public class NotificationPanelViewController extends PanelViewController { return; } mExpectingSynthesizedDown = true; - InteractionJankMonitor.getInstance().begin(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE); + InteractionJankMonitor.getInstance().begin(mView, CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE); onTrackingStarted(); updatePanelExpanded(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java index 619aadba9da0..af595b60daa1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java @@ -146,7 +146,6 @@ public class NotificationShadeWindowView extends FrameLayout { protected void onAttachedToWindow() { super.onAttachedToWindow(); setWillNotDraw(!DEBUG); - InteractionJankMonitor.getInstance().init(this); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java index 8ed971087508..62ded0a2d6e1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java @@ -360,7 +360,8 @@ public abstract class PanelViewController { protected void startExpandMotion(float newX, float newY, boolean startTracking, float expandedHeight) { if (!mHandlingPointerUp) { - InteractionJankMonitor.getInstance().begin(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE); + InteractionJankMonitor.getInstance().begin(mView, + CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE); } mInitialOffsetOnTouch = expandedHeight; mInitialTouchY = newY; @@ -862,7 +863,7 @@ public abstract class PanelViewController { mView.getViewTreeObserver().removeOnGlobalLayoutListener(this); if (mAnimateAfterExpanding) { notifyExpandingStarted(); - InteractionJankMonitor.getInstance().begin( + InteractionJankMonitor.getInstance().begin(mView, CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE); fling(0, true /* expand */); } else { diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java index 83ad437eba29..52ed2788d795 100644 --- a/services/core/java/com/android/server/wm/WindowAnimator.java +++ b/services/core/java/com/android/server/wm/WindowAnimator.java @@ -134,8 +134,6 @@ public class WindowAnimator { // Schedule next frame already such that back-pressure happens continuously. scheduleAnimation(); - mTransaction.setFrameTimelineVsync(vsyncId); - mCurrentTime = frameTimeNs / TimeUtils.NANOS_PER_MS; mBulkUpdateParams = SET_ORIENTATION_CHANGE_COMPLETE; if (DEBUG_WINDOW_TRACE) { |