summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Winson Chung <winsonc@google.com> 2021-01-13 19:36:00 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2021-01-13 19:36:00 +0000
commitc31ec3d518c84f63615b34cb5ff1fe20f84d5964 (patch)
tree2fd10a1c37a71f3bbef0c41ce2d886308f3311e7
parent96292972865297702b53ebb3ccb72c2d3592654c (diff)
parent2ee0ab9556f6e38b5ad78938487c144c5dfbb418 (diff)
Merge "Migrate FrameTracker to use ST Jank information"
-rw-r--r--core/java/android/view/FrameMetrics.java2
-rw-r--r--core/java/android/view/SurfaceControl.java19
-rw-r--r--core/java/android/view/ViewRootImpl.java6
-rw-r--r--core/java/com/android/internal/jank/FrameTracker.java422
-rw-r--r--core/java/com/android/internal/jank/InteractionJankMonitor.java97
-rw-r--r--core/jni/android_view_SurfaceControl.cpp7
-rw-r--r--core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java158
-rw-r--r--core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java28
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java10
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/ChoreographerCompat.java37
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java5
-rw-r--r--services/core/java/com/android/server/wm/WindowAnimator.java2
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) {