Loading core/java/com/android/internal/jank/FrameTracker.java +26 −2 Original line number Diff line number Diff line Loading @@ -25,6 +25,9 @@ import static android.view.SurfaceControl.JankData.JANK_SURFACEFLINGER_GPU_DEADL import static android.view.SurfaceControl.JankData.PREDICTION_ERROR; import static android.view.SurfaceControl.JankData.SURFACE_FLINGER_SCHEDULING; import static com.android.internal.jank.InteractionJankMonitor.ACTION_METRICS_LOGGED; import static com.android.internal.jank.InteractionJankMonitor.ACTION_SESSION_BEGIN; import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.HardwareRendererObserver; Loading Loading @@ -72,6 +75,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener private long mEndVsyncId = INVALID_ID; private boolean mMetricsFinalized; private boolean mCancelled = false; private FrameTrackerListener mListener; private static class JankInfo { long frameVsyncId; Loading Loading @@ -109,7 +113,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener @NonNull SurfaceControlWrapper surfaceControlWrapper, @NonNull ChoreographerWrapper choreographer, @NonNull FrameMetricsWrapper metrics, int traceThresholdMissedFrames, int traceThresholdFrameTimeMillis) { int traceThresholdFrameTimeMillis, @Nullable FrameTrackerListener listener) { mSession = session; mRendererWrapper = renderer; mMetricsWrapper = metrics; Loading @@ -120,6 +124,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener mObserver = new HardwareRendererObserver(this, mMetricsWrapper.getTiming(), handler); mTraceThresholdMissedFrames = traceThresholdMissedFrames; mTraceThresholdFrameTimeMillis = traceThresholdFrameTimeMillis; mListener = listener; // If the surface isn't valid yet, wait until it's created. if (viewRootWrapper.getSurfaceControl().isValid()) { Loading Loading @@ -165,11 +170,15 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener */ public synchronized void begin() { mBeginVsyncId = mChoreographer.getVsyncId() + 1; mSession.setTimeStamp(System.nanoTime()); Trace.beginAsyncSection(mSession.getName(), (int) mBeginVsyncId); mRendererWrapper.addObserver(mObserver); if (mSurfaceControl != null) { mSurfaceControlWrapper.addJankStatsListener(this, mSurfaceControl); } if (mListener != null) { mListener.onNotifyCujEvents(mSession, ACTION_SESSION_BEGIN); } } /** Loading Loading @@ -224,7 +233,6 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener } 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. Loading Loading @@ -371,6 +379,9 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener missedAppFramesCount + missedSfFramesCounts, maxFrameTimeNanos, missedSfFramesCounts); if (mListener != null) { mListener.onNotifyCujEvents(mSession, ACTION_METRICS_LOGGED); } } if (DEBUG) { Log.i(TAG, "FrameTracker: CUJ=" + mSession.getName() Loading Loading @@ -495,4 +506,17 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener return mChoreographer.getVsyncId(); } } /** * A listener that notifies cuj events. */ public interface FrameTrackerListener { /** * Notify that the CUJ session was created. * * @param session the CUJ session * @param action the specific action */ void onNotifyCujEvents(Session session, String action); } } core/java/com/android/internal/jank/InteractionJankMonitor.java +39 −1 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.internal.jank; import static android.content.Intent.FLAG_RECEIVER_REGISTERED_ONLY; import static com.android.internal.jank.FrameTracker.ChoreographerWrapper; import static com.android.internal.jank.FrameTracker.SurfaceControlWrapper; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_ALL_APPS_SCROLL; Loading Loading @@ -48,9 +50,12 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN import android.annotation.IntDef; import android.annotation.NonNull; import android.content.Context; import android.content.Intent; import android.os.Build; import android.os.HandlerExecutor; import android.os.HandlerThread; import android.os.SystemProperties; import android.provider.DeviceConfig; import android.util.Log; import android.util.SparseArray; Loading @@ -59,6 +64,7 @@ import android.view.View; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.jank.FrameTracker.FrameMetricsWrapper; import com.android.internal.jank.FrameTracker.FrameTrackerListener; import com.android.internal.jank.FrameTracker.ThreadedRendererWrapper; import com.android.internal.jank.FrameTracker.ViewRootWrapper; import com.android.internal.util.PerfettoTrigger; Loading @@ -74,6 +80,8 @@ import java.util.concurrent.TimeUnit; */ public class InteractionJankMonitor { private static final String TAG = InteractionJankMonitor.class.getSimpleName(); private static final String ACTION_PREFIX = InteractionJankMonitor.class.getCanonicalName(); private static final String DEFAULT_WORKER_NAME = TAG + "-Worker"; private static final long DEFAULT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(5L); private static final String SETTINGS_ENABLED_KEY = "enabled"; Loading @@ -90,6 +98,14 @@ public class InteractionJankMonitor { private static final int DEFAULT_TRACE_THRESHOLD_MISSED_FRAMES = 3; private static final int DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS = 64; public static final String ACTION_SESSION_BEGIN = ACTION_PREFIX + ".ACTION_SESSION_BEGIN"; public static final String ACTION_SESSION_END = ACTION_PREFIX + ".ACTION_SESSION_END"; public static final String ACTION_METRICS_LOGGED = ACTION_PREFIX + ".ACTION_METRICS_LOGGED"; public static final String BUNDLE_KEY_CUJ_NAME = ACTION_PREFIX + ".CUJ_NAME"; public static final String BUNDLE_KEY_TIMESTAMP = ACTION_PREFIX + ".TIMESTAMP"; @VisibleForTesting public static final String PROP_NOTIFY_CUJ_EVENT = "debug.notify_cuj_events"; // Every value must have a corresponding entry in CUJ_STATSD_INTERACTION_TYPE. public static final int CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE = 0; public static final int CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE_LOCK = 1; Loading Loading @@ -256,15 +272,28 @@ public class InteractionJankMonitor { */ @VisibleForTesting public FrameTracker createFrameTracker(View v, Session session) { final Context c = v.getContext().getApplicationContext(); synchronized (this) { boolean needListener = SystemProperties.getBoolean(PROP_NOTIFY_CUJ_EVENT, false); FrameTrackerListener eventsListener = !needListener ? null : (s, act) -> notifyEvents(c, act, s); return new FrameTracker(session, mWorker.getThreadHandler(), new ThreadedRendererWrapper(v.getThreadedRenderer()), new ViewRootWrapper(v.getViewRootImpl()), new SurfaceControlWrapper(), new ChoreographerWrapper(Choreographer.getInstance()), mMetrics, mTraceThresholdMissedFrames, mTraceThresholdFrameTimeMillis); mTraceThresholdMissedFrames, mTraceThresholdFrameTimeMillis, eventsListener); } } private void notifyEvents(Context context, String action, Session session) { Intent intent = new Intent(action); intent.putExtra(BUNDLE_KEY_CUJ_NAME, getNameOfCuj(session.getCuj())); intent.putExtra(BUNDLE_KEY_TIMESTAMP, session.getTimeStamp()); intent.addFlags(FLAG_RECEIVER_REGISTERED_ONLY); context.sendBroadcast(intent); } /** * Begin a trace session. * Loading Loading @@ -479,6 +508,7 @@ public class InteractionJankMonitor { public static class Session { @CujType private int mCujType; private long mTimeStamp; public Session(@CujType int cujType) { mCujType = cujType; Loading @@ -505,5 +535,13 @@ public class InteractionJankMonitor { public String getName() { return "J<" + getNameOfCuj(mCujType) + ">"; } public void setTimeStamp(long timeStamp) { mTimeStamp = timeStamp; } public long getTimeStamp() { return mTimeStamp; } } } core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java +2 −1 Original line number Diff line number Diff line Loading @@ -105,7 +105,8 @@ public class FrameTrackerTest { mTracker = Mockito.spy( new FrameTracker(session, handler, mRenderer, mViewRootWrapper, mSurfaceControlWrapper, mChoreographer, mWrapper, /*traceThresholdMissedFrames=*/ 1, /*traceThresholdFrameTimeMillis=*/ -1)); /*traceThresholdMissedFrames=*/ 1, /*traceThresholdFrameTimeMillis=*/ -1, null)); doNothing().when(mTracker).triggerPerfetto(); } Loading core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java +6 −15 Original line number Diff line number Diff line Loading @@ -96,7 +96,7 @@ public class InteractionJankMonitorTest { new ViewRootWrapper(mView.getViewRootImpl()), new SurfaceControlWrapper(), mock(FrameTracker.ChoreographerWrapper.class), new FrameMetricsWrapper(), /*traceThresholdMissedFrames=*/ 1, /*traceThresholdFrameTimeMillis=*/ -1)); /*traceThresholdFrameTimeMillis=*/ -1, null)); doReturn(tracker).when(monitor).createFrameTracker(any(), any()); // Simulate a trace session and see if begin / end are invoked. Loading @@ -123,21 +123,12 @@ public class InteractionJankMonitorTest { @Test public void testCheckInitState() { InteractionJankMonitor monitor = new InteractionJankMonitor(mWorker); View view = new View(mActivity); assertThat(view.isAttachedToWindow()).isFalse(); // Should return false if invoking begin / end without init invocation. assertThat(monitor.begin(mView, CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse(); // Should return false if the view passed in is not attached to window yet. assertThat(monitor.begin(view, 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 { assertThat(monitor.begin(mView, CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue(); assertThat(monitor.end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue(); } catch (Exception ex) { thrown = true; } finally { assertThat(thrown).isFalse(); } } @Test Loading @@ -152,7 +143,7 @@ public class InteractionJankMonitorTest { new ViewRootWrapper(mView.getViewRootImpl()), new SurfaceControlWrapper(), mock(FrameTracker.ChoreographerWrapper.class), new FrameMetricsWrapper(), /*traceThresholdMissedFrames=*/ 1, /*traceThresholdFrameTimeMillis=*/ -1)); /*traceThresholdFrameTimeMillis=*/ -1, null)); doReturn(tracker).when(monitor).createFrameTracker(any(), any()); assertThat(monitor.begin(mView, session.getCuj())).isTrue(); Loading Loading
core/java/com/android/internal/jank/FrameTracker.java +26 −2 Original line number Diff line number Diff line Loading @@ -25,6 +25,9 @@ import static android.view.SurfaceControl.JankData.JANK_SURFACEFLINGER_GPU_DEADL import static android.view.SurfaceControl.JankData.PREDICTION_ERROR; import static android.view.SurfaceControl.JankData.SURFACE_FLINGER_SCHEDULING; import static com.android.internal.jank.InteractionJankMonitor.ACTION_METRICS_LOGGED; import static com.android.internal.jank.InteractionJankMonitor.ACTION_SESSION_BEGIN; import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.HardwareRendererObserver; Loading Loading @@ -72,6 +75,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener private long mEndVsyncId = INVALID_ID; private boolean mMetricsFinalized; private boolean mCancelled = false; private FrameTrackerListener mListener; private static class JankInfo { long frameVsyncId; Loading Loading @@ -109,7 +113,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener @NonNull SurfaceControlWrapper surfaceControlWrapper, @NonNull ChoreographerWrapper choreographer, @NonNull FrameMetricsWrapper metrics, int traceThresholdMissedFrames, int traceThresholdFrameTimeMillis) { int traceThresholdFrameTimeMillis, @Nullable FrameTrackerListener listener) { mSession = session; mRendererWrapper = renderer; mMetricsWrapper = metrics; Loading @@ -120,6 +124,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener mObserver = new HardwareRendererObserver(this, mMetricsWrapper.getTiming(), handler); mTraceThresholdMissedFrames = traceThresholdMissedFrames; mTraceThresholdFrameTimeMillis = traceThresholdFrameTimeMillis; mListener = listener; // If the surface isn't valid yet, wait until it's created. if (viewRootWrapper.getSurfaceControl().isValid()) { Loading Loading @@ -165,11 +170,15 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener */ public synchronized void begin() { mBeginVsyncId = mChoreographer.getVsyncId() + 1; mSession.setTimeStamp(System.nanoTime()); Trace.beginAsyncSection(mSession.getName(), (int) mBeginVsyncId); mRendererWrapper.addObserver(mObserver); if (mSurfaceControl != null) { mSurfaceControlWrapper.addJankStatsListener(this, mSurfaceControl); } if (mListener != null) { mListener.onNotifyCujEvents(mSession, ACTION_SESSION_BEGIN); } } /** Loading Loading @@ -224,7 +233,6 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener } 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. Loading Loading @@ -371,6 +379,9 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener missedAppFramesCount + missedSfFramesCounts, maxFrameTimeNanos, missedSfFramesCounts); if (mListener != null) { mListener.onNotifyCujEvents(mSession, ACTION_METRICS_LOGGED); } } if (DEBUG) { Log.i(TAG, "FrameTracker: CUJ=" + mSession.getName() Loading Loading @@ -495,4 +506,17 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener return mChoreographer.getVsyncId(); } } /** * A listener that notifies cuj events. */ public interface FrameTrackerListener { /** * Notify that the CUJ session was created. * * @param session the CUJ session * @param action the specific action */ void onNotifyCujEvents(Session session, String action); } }
core/java/com/android/internal/jank/InteractionJankMonitor.java +39 −1 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.internal.jank; import static android.content.Intent.FLAG_RECEIVER_REGISTERED_ONLY; import static com.android.internal.jank.FrameTracker.ChoreographerWrapper; import static com.android.internal.jank.FrameTracker.SurfaceControlWrapper; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_ALL_APPS_SCROLL; Loading Loading @@ -48,9 +50,12 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN import android.annotation.IntDef; import android.annotation.NonNull; import android.content.Context; import android.content.Intent; import android.os.Build; import android.os.HandlerExecutor; import android.os.HandlerThread; import android.os.SystemProperties; import android.provider.DeviceConfig; import android.util.Log; import android.util.SparseArray; Loading @@ -59,6 +64,7 @@ import android.view.View; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.jank.FrameTracker.FrameMetricsWrapper; import com.android.internal.jank.FrameTracker.FrameTrackerListener; import com.android.internal.jank.FrameTracker.ThreadedRendererWrapper; import com.android.internal.jank.FrameTracker.ViewRootWrapper; import com.android.internal.util.PerfettoTrigger; Loading @@ -74,6 +80,8 @@ import java.util.concurrent.TimeUnit; */ public class InteractionJankMonitor { private static final String TAG = InteractionJankMonitor.class.getSimpleName(); private static final String ACTION_PREFIX = InteractionJankMonitor.class.getCanonicalName(); private static final String DEFAULT_WORKER_NAME = TAG + "-Worker"; private static final long DEFAULT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(5L); private static final String SETTINGS_ENABLED_KEY = "enabled"; Loading @@ -90,6 +98,14 @@ public class InteractionJankMonitor { private static final int DEFAULT_TRACE_THRESHOLD_MISSED_FRAMES = 3; private static final int DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS = 64; public static final String ACTION_SESSION_BEGIN = ACTION_PREFIX + ".ACTION_SESSION_BEGIN"; public static final String ACTION_SESSION_END = ACTION_PREFIX + ".ACTION_SESSION_END"; public static final String ACTION_METRICS_LOGGED = ACTION_PREFIX + ".ACTION_METRICS_LOGGED"; public static final String BUNDLE_KEY_CUJ_NAME = ACTION_PREFIX + ".CUJ_NAME"; public static final String BUNDLE_KEY_TIMESTAMP = ACTION_PREFIX + ".TIMESTAMP"; @VisibleForTesting public static final String PROP_NOTIFY_CUJ_EVENT = "debug.notify_cuj_events"; // Every value must have a corresponding entry in CUJ_STATSD_INTERACTION_TYPE. public static final int CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE = 0; public static final int CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE_LOCK = 1; Loading Loading @@ -256,15 +272,28 @@ public class InteractionJankMonitor { */ @VisibleForTesting public FrameTracker createFrameTracker(View v, Session session) { final Context c = v.getContext().getApplicationContext(); synchronized (this) { boolean needListener = SystemProperties.getBoolean(PROP_NOTIFY_CUJ_EVENT, false); FrameTrackerListener eventsListener = !needListener ? null : (s, act) -> notifyEvents(c, act, s); return new FrameTracker(session, mWorker.getThreadHandler(), new ThreadedRendererWrapper(v.getThreadedRenderer()), new ViewRootWrapper(v.getViewRootImpl()), new SurfaceControlWrapper(), new ChoreographerWrapper(Choreographer.getInstance()), mMetrics, mTraceThresholdMissedFrames, mTraceThresholdFrameTimeMillis); mTraceThresholdMissedFrames, mTraceThresholdFrameTimeMillis, eventsListener); } } private void notifyEvents(Context context, String action, Session session) { Intent intent = new Intent(action); intent.putExtra(BUNDLE_KEY_CUJ_NAME, getNameOfCuj(session.getCuj())); intent.putExtra(BUNDLE_KEY_TIMESTAMP, session.getTimeStamp()); intent.addFlags(FLAG_RECEIVER_REGISTERED_ONLY); context.sendBroadcast(intent); } /** * Begin a trace session. * Loading Loading @@ -479,6 +508,7 @@ public class InteractionJankMonitor { public static class Session { @CujType private int mCujType; private long mTimeStamp; public Session(@CujType int cujType) { mCujType = cujType; Loading @@ -505,5 +535,13 @@ public class InteractionJankMonitor { public String getName() { return "J<" + getNameOfCuj(mCujType) + ">"; } public void setTimeStamp(long timeStamp) { mTimeStamp = timeStamp; } public long getTimeStamp() { return mTimeStamp; } } }
core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java +2 −1 Original line number Diff line number Diff line Loading @@ -105,7 +105,8 @@ public class FrameTrackerTest { mTracker = Mockito.spy( new FrameTracker(session, handler, mRenderer, mViewRootWrapper, mSurfaceControlWrapper, mChoreographer, mWrapper, /*traceThresholdMissedFrames=*/ 1, /*traceThresholdFrameTimeMillis=*/ -1)); /*traceThresholdMissedFrames=*/ 1, /*traceThresholdFrameTimeMillis=*/ -1, null)); doNothing().when(mTracker).triggerPerfetto(); } Loading
core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java +6 −15 Original line number Diff line number Diff line Loading @@ -96,7 +96,7 @@ public class InteractionJankMonitorTest { new ViewRootWrapper(mView.getViewRootImpl()), new SurfaceControlWrapper(), mock(FrameTracker.ChoreographerWrapper.class), new FrameMetricsWrapper(), /*traceThresholdMissedFrames=*/ 1, /*traceThresholdFrameTimeMillis=*/ -1)); /*traceThresholdFrameTimeMillis=*/ -1, null)); doReturn(tracker).when(monitor).createFrameTracker(any(), any()); // Simulate a trace session and see if begin / end are invoked. Loading @@ -123,21 +123,12 @@ public class InteractionJankMonitorTest { @Test public void testCheckInitState() { InteractionJankMonitor monitor = new InteractionJankMonitor(mWorker); View view = new View(mActivity); assertThat(view.isAttachedToWindow()).isFalse(); // Should return false if invoking begin / end without init invocation. assertThat(monitor.begin(mView, CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse(); // Should return false if the view passed in is not attached to window yet. assertThat(monitor.begin(view, 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 { assertThat(monitor.begin(mView, CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue(); assertThat(monitor.end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue(); } catch (Exception ex) { thrown = true; } finally { assertThat(thrown).isFalse(); } } @Test Loading @@ -152,7 +143,7 @@ public class InteractionJankMonitorTest { new ViewRootWrapper(mView.getViewRootImpl()), new SurfaceControlWrapper(), mock(FrameTracker.ChoreographerWrapper.class), new FrameMetricsWrapper(), /*traceThresholdMissedFrames=*/ 1, /*traceThresholdFrameTimeMillis=*/ -1)); /*traceThresholdFrameTimeMillis=*/ -1, null)); doReturn(tracker).when(monitor).createFrameTracker(any(), any()); assertThat(monitor.begin(mView, session.getCuj())).isTrue(); Loading