Merge "Support surface only instrumentation" into sc-v2-dev am: d6d30bf7f6 am: 60894eefb5

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/15256893

Change-Id: I2f43c2ba720d08446210639cb417af6dd2bc1266
This commit is contained in:
Wu Ahan
2021-08-02 10:33:28 +00:00
committed by Automerger Merge Worker
8 changed files with 515 additions and 218 deletions

View File

@@ -45,6 +45,7 @@ import android.view.ThreadedRenderer;
import android.view.ViewRootImpl;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.InteractionJankMonitor.Configuration;
import com.android.internal.jank.InteractionJankMonitor.Session;
import com.android.internal.util.FrameworkStatsLog;
@@ -69,6 +70,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
static final int REASON_CANCEL_NORMAL = 16;
static final int REASON_CANCEL_NOT_BEGUN = 17;
static final int REASON_CANCEL_SAME_VSYNC = 18;
static final int REASON_CANCEL_TIMEOUT = 19;
/** @hide */
@IntDef({
@@ -97,6 +99,9 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
private final Handler mHandler;
private final ChoreographerWrapper mChoreographer;
@VisibleForTesting
public final boolean mSurfaceOnly;
private long mBeginVsyncId = INVALID_ID;
private long mEndVsyncId = INVALID_ID;
private boolean mMetricsFinalized;
@@ -136,71 +141,86 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
}
public FrameTracker(@NonNull Session session, @NonNull Handler handler,
@NonNull ThreadedRendererWrapper renderer, @NonNull ViewRootWrapper viewRootWrapper,
@Nullable ThreadedRendererWrapper renderer, @Nullable ViewRootWrapper viewRootWrapper,
@NonNull SurfaceControlWrapper surfaceControlWrapper,
@NonNull ChoreographerWrapper choreographer,
@NonNull FrameMetricsWrapper metrics, int traceThresholdMissedFrames,
int traceThresholdFrameTimeMillis, @Nullable FrameTrackerListener listener) {
@Nullable FrameMetricsWrapper metrics,
int traceThresholdMissedFrames, int traceThresholdFrameTimeMillis,
@Nullable FrameTrackerListener listener, @NonNull Configuration config) {
mSurfaceOnly = config.isSurfaceOnly();
mSession = session;
mRendererWrapper = renderer;
mMetricsWrapper = metrics;
mViewRoot = viewRootWrapper;
mHandler = handler;
mChoreographer = choreographer;
mSurfaceControlWrapper = surfaceControlWrapper;
mHandler = handler;
mObserver = new HardwareRendererObserver(
this, mMetricsWrapper.getTiming(), handler, false /*waitForPresentTime*/);
// HWUI instrumentation init.
mRendererWrapper = mSurfaceOnly ? null : renderer;
mMetricsWrapper = mSurfaceOnly ? null : metrics;
mViewRoot = mSurfaceOnly ? null : viewRootWrapper;
mObserver = mSurfaceOnly
? null
: new HardwareRendererObserver(this, mMetricsWrapper.getTiming(),
handler, /* waitForPresentTime= */ false);
mTraceThresholdMissedFrames = traceThresholdMissedFrames;
mTraceThresholdFrameTimeMillis = traceThresholdFrameTimeMillis;
mListener = listener;
// If the surface isn't valid yet, wait until it's created.
if (viewRootWrapper.getSurfaceControl().isValid()) {
mSurfaceControl = viewRootWrapper.getSurfaceControl();
if (mSurfaceOnly) {
mSurfaceControl = config.getSurfaceControl();
mSurfaceChangedCallback = null;
} else {
// HWUI instrumentation init.
// If the surface isn't valid yet, wait until it's created.
if (mViewRoot.getSurfaceControl().isValid()) {
mSurfaceControl = mViewRoot.getSurfaceControl();
mSurfaceChangedCallback = null;
} else {
mSurfaceChangedCallback = new ViewRootImpl.SurfaceChangedCallback() {
@Override
public void surfaceCreated(SurfaceControl.Transaction t) {
synchronized (FrameTracker.this) {
if (mSurfaceControl == null) {
mSurfaceControl = mViewRoot.getSurfaceControl();
if (mBeginVsyncId != INVALID_ID) {
mSurfaceControlWrapper.addJankStatsListener(
FrameTracker.this, mSurfaceControl);
postTraceStartMarker();
}
}
}
}
@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 (DEBUG) {
Log.d(TAG, "surfaceDestroyed: " + mSession.getName()
+ ", finalized=" + mMetricsFinalized
+ ", info=" + mJankInfos.size()
+ ", vsync=" + mBeginVsyncId + "-" + mEndVsyncId);
}
if (!mMetricsFinalized) {
end(REASON_END_SURFACE_DESTROYED);
finish(mJankInfos.size() - 1);
}
}
}, 50);
}
};
// This callback has a reference to FrameTracker,
// remember to remove it to avoid leakage.
mViewRoot.addSurfaceChangedCallback(mSurfaceChangedCallback);
}
}
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);
postTraceStartMarker();
}
}
}
}
@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 (DEBUG) {
Log.d(TAG, "surfaceDestroyed: " + mSession.getName()
+ ", finalized=" + mMetricsFinalized
+ ", info=" + mJankInfos.size()
+ ", vsync=" + mBeginVsyncId + "-" + mEndVsyncId);
}
if (!mMetricsFinalized) {
end(REASON_END_SURFACE_DESTROYED);
finish(mJankInfos.size() - 1);
}
}
}, 50);
}
};
// This callback has a reference to FrameTracker, remember to remove it to avoid leakage.
viewRootWrapper.addSurfaceChangedCallback(mSurfaceChangedCallback);
}
/**
@@ -208,16 +228,16 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
*/
public synchronized void begin() {
mBeginVsyncId = mChoreographer.getVsyncId() + 1;
if (mSurfaceControl != null) {
postTraceStartMarker();
}
mRendererWrapper.addObserver(mObserver);
if (DEBUG) {
Log.d(TAG, "begin: " + mSession.getName() + ", begin=" + mBeginVsyncId);
}
if (mSurfaceControl != null) {
postTraceStartMarker();
mSurfaceControlWrapper.addJankStatsListener(this, mSurfaceControl);
}
if (!mSurfaceOnly) {
mRendererWrapper.addObserver(mObserver);
}
if (mListener != null) {
mListener.onCujEvents(mSession, ACTION_SESSION_BEGIN);
}
@@ -273,11 +293,12 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
* Cancel the trace session of the CUJ.
*/
public synchronized void cancel(@Reasons int reason) {
mCancelled = true;
// We don't need to end the trace section if it never begun.
if (mTracingStarted) {
Trace.endAsyncSection(mSession.getName(), (int) mBeginVsyncId);
}
mCancelled = true;
// Always remove the observers in cancel call to avoid leakage.
removeObservers();
@@ -377,7 +398,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
for (int i = mJankInfos.size() - 1; i >= 0; i--) {
JankInfo info = mJankInfos.valueAt(i);
if (info.frameVsyncId >= mEndVsyncId) {
if (info.hwuiCallbackFired && info.surfaceControlCallbackFired) {
if (isLastIndexCandidate(info)) {
lastIndex = i;
}
} else {
@@ -395,6 +416,12 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
finish(indexOnOrAfterEnd);
}
private boolean isLastIndexCandidate(JankInfo info) {
return mSurfaceOnly
? info.surfaceControlCallbackFired
: info.hwuiCallbackFired && info.surfaceControlCallbackFired;
}
private void finish(int indexOnOrAfterEnd) {
mMetricsFinalized = true;
@@ -410,7 +437,8 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
for (int i = 0; i <= indexOnOrAfterEnd; i++) {
JankInfo info = mJankInfos.valueAt(i);
if (info.isFirstFrame) {
final boolean isFirstDrawn = !mSurfaceOnly && info.isFirstFrame;
if (isFirstDrawn) {
continue;
}
if (info.surfaceControlCallbackFired) {
@@ -435,11 +463,11 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
}
// TODO (b/174755489): Early latch currently gets fired way too often, so we have
// to ignore it for now.
if (!info.hwuiCallbackFired) {
if (!mSurfaceOnly && !info.hwuiCallbackFired) {
Log.w(TAG, "Missing HWUI jank callback for vsyncId: " + info.frameVsyncId);
}
}
if (info.hwuiCallbackFired) {
if (!mSurfaceOnly && info.hwuiCallbackFired) {
maxFrameTimeNanos = Math.max(info.totalDurationNanos, maxFrameTimeNanos);
if (!info.surfaceControlCallbackFired) {
Log.w(TAG, "Missing SF jank callback for vsyncId: " + info.frameVsyncId);
@@ -462,7 +490,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
// Trigger perfetto if necessary.
boolean overMissedFramesThreshold = mTraceThresholdMissedFrames != -1
&& missedFramesCount >= mTraceThresholdMissedFrames;
boolean overFrameTimeThreshold = mTraceThresholdFrameTimeMillis != -1
boolean overFrameTimeThreshold = !mSurfaceOnly && mTraceThresholdFrameTimeMillis != -1
&& maxFrameTimeNanos >= mTraceThresholdFrameTimeMillis * NANOS_IN_MILLISECOND;
if (overMissedFramesThreshold || overFrameTimeThreshold) {
triggerPerfetto();
@@ -473,7 +501,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
mSession.getStatsdInteractionType(),
totalFramesCount,
missedFramesCount,
maxFrameTimeNanos,
maxFrameTimeNanos, /* will be 0 if mSurfaceOnly == true */
missedSfFramesCount,
missedAppFramesCount);
if (mListener != null) {
@@ -496,10 +524,13 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
*/
@VisibleForTesting
public void removeObservers() {
mRendererWrapper.removeObserver(mObserver);
mSurfaceControlWrapper.removeJankStatsListener(this);
if (mSurfaceChangedCallback != null) {
mViewRoot.removeSurfaceChangedCallback(mSurfaceChangedCallback);
if (!mSurfaceOnly) {
// HWUI part.
mRendererWrapper.removeObserver(mObserver);
if (mSurfaceChangedCallback != null) {
mViewRoot.removeSurfaceChangedCallback(mSurfaceChangedCallback);
}
}
}

View File

@@ -18,11 +18,11 @@ 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.REASON_CANCEL_NORMAL;
import static com.android.internal.jank.FrameTracker.REASON_CANCEL_NOT_BEGUN;
import static com.android.internal.jank.FrameTracker.REASON_CANCEL_TIMEOUT;
import static com.android.internal.jank.FrameTracker.REASON_END_NORMAL;
import static com.android.internal.jank.FrameTracker.SurfaceControlWrapper;
import static com.android.internal.jank.FrameTracker.REASON_END_UNKNOWN;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_ALL_APPS_SCROLL;
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;
@@ -41,6 +41,7 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_TRANSITION_TO_AOD;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_UNLOCK_ANIMATION;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__NOTIFICATION_SHADE_SWIPE;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PIP_TRANSITION;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_PAGE_SCROLL;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON;
@@ -58,6 +59,7 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_SWIPE;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_SCROLL_FLING;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -72,11 +74,15 @@ import android.text.TextUtils;
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.ChoreographerWrapper;
import com.android.internal.jank.FrameTracker.FrameMetricsWrapper;
import com.android.internal.jank.FrameTracker.FrameTrackerListener;
import com.android.internal.jank.FrameTracker.Reasons;
import com.android.internal.jank.FrameTracker.SurfaceControlWrapper;
import com.android.internal.jank.FrameTracker.ThreadedRendererWrapper;
import com.android.internal.jank.FrameTracker.ViewRootWrapper;
import com.android.internal.util.PerfettoTrigger;
@@ -103,7 +109,7 @@ public class InteractionJankMonitor {
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 long DEFAULT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(2L);
private static final String SETTINGS_ENABLED_KEY = "enabled";
private static final String SETTINGS_SAMPLING_INTERVAL_KEY = "sampling_interval";
private static final String SETTINGS_THRESHOLD_MISSED_FRAMES_KEY =
@@ -163,6 +169,8 @@ public class InteractionJankMonitor {
public static final int CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE = 32;
public static final int CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON = 33;
public static final int CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP = 34;
public static final int CUJ_PIP_TRANSITION = 35;
public static final int CUJ_WALLPAPER_TRANSITION = 36;
private static final int NO_STATSD_LOGGING = -1;
@@ -206,6 +214,8 @@ public class InteractionJankMonitor {
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_QS_TILE,
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON,
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP,
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PIP_TRANSITION,
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION,
};
private static volatile InteractionJankMonitor sInstance;
@@ -213,10 +223,10 @@ public class InteractionJankMonitor {
private final DeviceConfig.OnPropertiesChangedListener mPropertiesChangedListener =
this::updateProperties;
private FrameMetricsWrapper mMetrics;
private SparseArray<FrameTracker> mRunningTrackers;
private SparseArray<Runnable> mTimeoutActions;
private HandlerThread mWorker;
private final FrameMetricsWrapper mMetrics;
private final SparseArray<FrameTracker> mRunningTrackers;
private final SparseArray<Runnable> mTimeoutActions;
private final HandlerThread mWorker;
private boolean mEnabled = DEFAULT_ENABLED;
private int mSamplingInterval = DEFAULT_SAMPLING_INTERVAL;
@@ -260,6 +270,8 @@ public class InteractionJankMonitor {
CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE,
CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON,
CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP,
CUJ_PIP_TRANSITION,
CUJ_WALLPAPER_TRANSITION,
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {
@@ -310,24 +322,31 @@ public class InteractionJankMonitor {
}
/**
* Create a {@link FrameTracker} instance.
* Creates a {@link FrameTracker} instance.
*
* @param config the config used in instrumenting
* @param session the session associates with this tracker
* @return instance of the FrameTracker
*/
@VisibleForTesting
public FrameTracker createFrameTracker(Configuration conf, Session session) {
final View v = conf.mView;
final Context c = v.getContext().getApplicationContext();
final ThreadedRendererWrapper r = new ThreadedRendererWrapper(v.getThreadedRenderer());
final ViewRootWrapper vr = new ViewRootWrapper(v.getViewRootImpl());
final SurfaceControlWrapper sc = new SurfaceControlWrapper();
final ChoreographerWrapper cg = new ChoreographerWrapper(Choreographer.getInstance());
public FrameTracker createFrameTracker(Configuration config, Session session) {
final View view = config.mView;
final ThreadedRendererWrapper threadedRenderer =
view == null ? null : new ThreadedRendererWrapper(view.getThreadedRenderer());
final ViewRootWrapper viewRoot =
view == null ? null : new ViewRootWrapper(view.getViewRootImpl());
final SurfaceControlWrapper surfaceControl = new SurfaceControlWrapper();
final ChoreographerWrapper choreographer =
new ChoreographerWrapper(Choreographer.getInstance());
synchronized (this) {
FrameTrackerListener eventsListener = (s, act) -> handleCujEvents(c, act, s);
return new FrameTracker(session, mWorker.getThreadHandler(), r, vr, sc, cg, mMetrics,
mTraceThresholdMissedFrames, mTraceThresholdFrameTimeMillis, eventsListener);
FrameTrackerListener eventsListener =
(s, act) -> handleCujEvents(config.getContext(), act, s);
return new FrameTracker(session, mWorker.getThreadHandler(),
threadedRenderer, viewRoot, surfaceControl, choreographer, mMetrics,
mTraceThresholdMissedFrames, mTraceThresholdFrameTimeMillis,
eventsListener, config);
}
}
@@ -376,7 +395,7 @@ public class InteractionJankMonitor {
}
/**
* Begin a trace session.
* Begins a trace session.
*
* @param v an attached view.
* @param cujType the specific {@link InteractionJankMonitor.CujType}.
@@ -385,8 +404,7 @@ public class InteractionJankMonitor {
public boolean begin(View v, @CujType int cujType) {
try {
return beginInternal(
new Configuration.Builder(cujType)
.setView(v)
Configuration.Builder.withView(cujType, v)
.build());
} catch (IllegalArgumentException ex) {
Log.d(TAG, "Build configuration failed!", ex);
@@ -395,7 +413,7 @@ public class InteractionJankMonitor {
}
/**
* Begin a trace session.
* Begins a trace session.
*
* @param builder the builder of the configurations for instrumenting the CUJ.
* @return boolean true if the tracker is started successfully, false otherwise.
@@ -431,48 +449,60 @@ public class InteractionJankMonitor {
tracker.begin();
// Cancel the trace if we don't get an end() call in specified duration.
Runnable timeoutAction = () -> cancel(cujType);
mTimeoutActions.put(cujType, timeoutAction);
mWorker.getThreadHandler().postDelayed(timeoutAction, conf.mTimeout);
scheduleTimeoutAction(
cujType, conf.mTimeout, () -> cancel(cujType, REASON_CANCEL_TIMEOUT));
return true;
}
}
/**
* End a trace session.
* Schedules a timeout action.
* @param cuj cuj type
* @param timeout duration to timeout
* @param action action once timeout
*/
@VisibleForTesting
public void scheduleTimeoutAction(@CujType int cuj, long timeout, Runnable action) {
mTimeoutActions.put(cuj, action);
mWorker.getThreadHandler().postDelayed(action, timeout);
}
/**
* Ends a trace session.
*
* @param cujType the specific {@link InteractionJankMonitor.CujType}.
* @return boolean true if the tracker is ended successfully, false otherwise.
*/
public boolean end(@CujType int cujType) {
//TODO (163505250): This should be no-op if not in droid food rom.
synchronized (this) {
// remove the timeout action first.
removeTimeout(cujType);
FrameTracker tracker = getTracker(cujType);
// Skip this call since we haven't started a trace yet.
if (tracker == null) return false;
tracker.end(FrameTracker.REASON_END_NORMAL);
tracker.end(REASON_END_NORMAL);
removeTracker(cujType);
return true;
}
}
/**
* Cancel the trace session.
* Cancels 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.
return cancel(cujType, REASON_CANCEL_NORMAL);
}
boolean cancel(@CujType int cujType, @Reasons int reason) {
synchronized (this) {
// remove the timeout action first.
removeTimeout(cujType);
FrameTracker tracker = getTracker(cujType);
// Skip this call since we haven't started a trace yet.
if (tracker == null) return false;
tracker.cancel(FrameTracker.REASON_CANCEL_NORMAL);
tracker.cancel(reason);
removeTracker(cujType);
return true;
}
@@ -509,7 +539,7 @@ public class InteractionJankMonitor {
}
/**
* Trigger the perfetto daemon to collect and upload data.
* Triggers the perfetto daemon to collect and upload data.
*/
@VisibleForTesting
public void trigger(Session session) {
@@ -608,6 +638,10 @@ public class InteractionJankMonitor {
return "SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON";
case CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP:
return "STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP";
case CUJ_PIP_TRANSITION:
return "PIP_TRANSITION";
case CUJ_WALLPAPER_TRANSITION:
return "WALLPAPER_TRANSITION";
}
return "UNKNOWN";
}
@@ -618,32 +652,64 @@ public class InteractionJankMonitor {
*/
public static class Configuration {
private final View mView;
private final Context mContext;
private final long mTimeout;
private final String mTag;
private final boolean mSurfaceOnly;
private final SurfaceControl mSurfaceControl;
private final @CujType int mCujType;
/**
* A builder for building Configuration. <br/>
* A builder for building Configuration. {@link #setView(View)} is essential
* if {@link #setSurfaceOnly(boolean)} is not set, otherwise both
* {@link #setSurfaceControl(SurfaceControl)} and {@link #setContext(Context)}
* are necessary<br/>
* <b>It may refer to an attached view, don't use static reference for any purpose.</b>
*/
public static class Builder {
private View mAttrView = null;
private Context mAttrContext = null;
private long mAttrTimeout = DEFAULT_TIMEOUT_MS;
private String mAttrTag = "";
private boolean mAttrSurfaceOnly;
private SurfaceControl mAttrSurfaceControl;
private @CujType int mAttrCujType;
/**
* Creates a builder which instruments only surface.
* @param cuj The enum defined in {@link InteractionJankMonitor.CujType}.
* @param context context
* @param surfaceControl surface control
* @return builder
*/
public Builder(@CujType int cuj) {
public static Builder withSurface(@CujType int cuj, @NonNull Context context,
@NonNull SurfaceControl surfaceControl) {
return new Builder(cuj)
.setContext(context)
.setSurfaceControl(surfaceControl)
.setSurfaceOnly(true);
}
/**
* Creates a builder which instruments both surface and view.
* @param cuj The enum defined in {@link InteractionJankMonitor.CujType}.
* @param view view
* @return builder
*/
public static Builder withView(@CujType int cuj, @NonNull View view) {
return new Builder(cuj).setView(view);
}
private Builder(@CujType int cuj) {
mAttrCujType = cuj;
}
/**
* Specifies a view, must be set if {@link #setSurfaceOnly(boolean)} is set to false.
* @param view an attached view
* @return builder
*/
public Builder setView(@NonNull View view) {
private Builder setView(@NonNull View view) {
mAttrView = view;
return this;
}
@@ -669,20 +735,56 @@ public class InteractionJankMonitor {
}
/**
* Build the {@link Configuration} instance
* Indicates if only instrument with surface,
* if true, must also setup with {@link #setContext(Context)}
* and {@link #setSurfaceControl(SurfaceControl)}.
* @param surfaceOnly true if only instrument with surface, false otherwise
* @return builder Surface only builder.
*/
private Builder setSurfaceOnly(boolean surfaceOnly) {
mAttrSurfaceOnly = surfaceOnly;
return this;
}
/**
* Specifies a context, must set if {@link #setSurfaceOnly(boolean)} is set.
*/
private Builder setContext(Context context) {
mAttrContext = context;
return this;
}
/**
* Specifies a surface control, must be set if {@link #setSurfaceOnly(boolean)} is set.
*/
private Builder setSurfaceControl(SurfaceControl surfaceControl) {
mAttrSurfaceControl = surfaceControl;
return this;
}
/**
* Builds the {@link Configuration} instance
* @return the instance of {@link Configuration}
* @throws IllegalArgumentException if any invalid attribute is set
*/
public Configuration build() throws IllegalArgumentException {
return new Configuration(mAttrCujType, mAttrView, mAttrTag, mAttrTimeout);
return new Configuration(
mAttrCujType, mAttrView, mAttrTag, mAttrTimeout,
mAttrSurfaceOnly, mAttrContext, mAttrSurfaceControl);
}
}
private Configuration(@CujType int cuj, View view, String tag, long timeout) {
private Configuration(@CujType int cuj, View view, String tag, long timeout,
boolean surfaceOnly, Context context, SurfaceControl surfaceControl) {
mCujType = cuj;
mTag = tag;
mTimeout = timeout;
mView = view;
mSurfaceOnly = surfaceOnly;
mContext = context != null
? context
: (view != null ? view.getContext().getApplicationContext() : null);
mSurfaceControl = surfaceControl;
validate();
}
@@ -698,14 +800,47 @@ public class InteractionJankMonitor {
shouldThrow = true;
msg.append("Invalid timeout value; ");
}
if (mView == null || !mView.isAttachedToWindow()) {
shouldThrow = true;
msg.append("Null view or view is not attached yet; ");
if (mSurfaceOnly) {
if (mContext == null) {
shouldThrow = true;
msg.append("Must pass in a context if only instrument surface; ");
}
if (mSurfaceControl == null || !mSurfaceControl.isValid()) {
shouldThrow = true;
msg.append("Must pass in a valid surface control if only instrument surface; ");
}
} else {
if (mView == null || !mView.isAttachedToWindow()) {
shouldThrow = true;
msg.append("Null view or unattached view while instrumenting view; ");
}
}
if (shouldThrow) {
throw new IllegalArgumentException(msg.toString());
}
}
/**
* @return true if only instrumenting surface, false otherwise
*/
public boolean isSurfaceOnly() {
return mSurfaceOnly;
}
/**
* @return the surafce control which is instrumenting
*/
public SurfaceControl getSurfaceControl() {
return mSurfaceControl;
}
View getView() {
return mView;
}
Context getContext() {
return mContext;
}
}
/**
@@ -715,8 +850,8 @@ public class InteractionJankMonitor {
@CujType
private final int mCujType;
private final long mTimeStamp;
@FrameTracker.Reasons
private int mReason = FrameTracker.REASON_END_UNKNOWN;
@Reasons
private int mReason = REASON_END_UNKNOWN;
private final boolean mShouldNotify;
private final String mName;
@@ -756,15 +891,15 @@ public class InteractionJankMonitor {
return mTimeStamp;
}
public void setReason(@FrameTracker.Reasons int reason) {
public void setReason(@Reasons int reason) {
mReason = reason;
}
public int getReason() {
public @Reasons int getReason() {
return mReason;
}
/** Determine if should notify the receivers of cuj events */
/** Determines if should notify the receivers of cuj events */
public boolean shouldNotify() {
return mShouldNotify;
}

View File

@@ -23,6 +23,7 @@ import static android.view.SurfaceControl.JankData.JANK_SURFACEFLINGER_DEADLINE_
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_WALLPAPER_TRANSITION;
import static com.google.common.truth.Truth.assertThat;
@@ -50,6 +51,7 @@ 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.Configuration;
import com.android.internal.jank.InteractionJankMonitor.Session;
import org.junit.Before;
@@ -69,7 +71,6 @@ public class FrameTrackerTest {
public ActivityTestRule<ViewAttachTestActivity> mRule =
new ActivityTestRule<>(ViewAttachTestActivity.class);
private FrameTracker mTracker;
private ThreadedRendererWrapper mRenderer;
private FrameMetricsWrapper mWrapper;
private SurfaceControlWrapper mSurfaceControlWrapper;
@@ -85,7 +86,6 @@ public class FrameTrackerTest {
View view = mActivity.getWindow().getDecorView();
assertThat(view.isAttachedToWindow()).isTrue();
Handler handler = mRule.getActivity().getMainThreadHandler();
mWrapper = Mockito.spy(new FrameMetricsWrapper());
mRenderer = Mockito.spy(new ThreadedRendererWrapper(view.getThreadedRenderer()));
doNothing().when(mRenderer).addObserver(any());
@@ -103,229 +103,355 @@ public class FrameTrackerTest {
mListenerCapture.capture());
mChoreographer = mock(ChoreographerWrapper.class);
}
Session session = new Session(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX);
mTracker = Mockito.spy(
private FrameTracker spyFrameTracker(int cuj, String postfix, boolean surfaceOnly) {
Handler handler = mRule.getActivity().getMainThreadHandler();
Session session = new Session(cuj, postfix);
Configuration config = mock(Configuration.class);
when(config.isSurfaceOnly()).thenReturn(surfaceOnly);
when(config.getSurfaceControl()).thenReturn(mSurfaceControl);
FrameTracker frameTracker = Mockito.spy(
new FrameTracker(session, handler, mRenderer, mViewRootWrapper,
mSurfaceControlWrapper, mChoreographer, mWrapper,
/*traceThresholdMissedFrames=*/ 1, /*traceThresholdFrameTimeMillis=*/ -1,
null));
doNothing().when(mTracker).triggerPerfetto();
doNothing().when(mTracker).postTraceStartMarker();
/* traceThresholdMissedFrames= */ 1,
/* traceThresholdFrameTimeMillis= */ -1,
/* FrameTrackerListener= */ null, config));
doNothing().when(frameTracker).triggerPerfetto();
doNothing().when(frameTracker).postTraceStartMarker();
return frameTracker;
}
@Test
public void testOnlyFirstWindowFrameOverThreshold() {
FrameTracker tracker = spyFrameTracker(
CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
// 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();
tracker.begin();
verify(mRenderer, only()).addObserver(any());
// send first frame with a long duration - should not be taken into account
sendFirstWindowFrame(100, JANK_APP_DEADLINE_MISSED, 100L);
sendFirstWindowFrame(tracker, 100, JANK_APP_DEADLINE_MISSED, 100L);
// send another frame with a short duration - should not be considered janky
sendFirstWindowFrame(5, JANK_NONE, 101L);
sendFirstWindowFrame(tracker, 5, JANK_NONE, 101L);
// end the trace session, the last janky frame is after the end() so is discarded.
when(mChoreographer.getVsyncId()).thenReturn(102L);
mTracker.end(FrameTracker.REASON_END_NORMAL);
sendFrame(5, JANK_NONE, 102L);
sendFrame(500, JANK_APP_DEADLINE_MISSED, 103L);
tracker.end(FrameTracker.REASON_END_NORMAL);
sendFrame(tracker, 5, JANK_NONE, 102L);
sendFrame(tracker, 500, JANK_APP_DEADLINE_MISSED, 103L);
verify(mTracker).removeObservers();
verify(mTracker, never()).triggerPerfetto();
verify(tracker).removeObservers();
verify(tracker, never()).triggerPerfetto();
}
@Test
public void testSfJank() {
FrameTracker tracker = spyFrameTracker(
CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
when(mChoreographer.getVsyncId()).thenReturn(100L);
mTracker.begin();
tracker.begin();
verify(mRenderer, only()).addObserver(any());
// send first frame - not janky
sendFrame(4, JANK_NONE, 100L);
sendFrame(tracker, 4, JANK_NONE, 100L);
// send another frame - should be considered janky
sendFrame(40, JANK_SURFACEFLINGER_DEADLINE_MISSED, 101L);
sendFrame(tracker, 40, JANK_SURFACEFLINGER_DEADLINE_MISSED, 101L);
// end the trace session
when(mChoreographer.getVsyncId()).thenReturn(102L);
mTracker.end(FrameTracker.REASON_END_NORMAL);
sendFrame(4, JANK_NONE, 102L);
tracker.end(FrameTracker.REASON_END_NORMAL);
sendFrame(tracker, 4, JANK_NONE, 102L);
verify(mTracker).removeObservers();
verify(tracker).removeObservers();
// We detected a janky frame - trigger Perfetto
verify(mTracker).triggerPerfetto();
verify(tracker).triggerPerfetto();
}
@Test
public void testFirstFrameJankyNoTrigger() {
FrameTracker tracker = spyFrameTracker(
CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
when(mChoreographer.getVsyncId()).thenReturn(100L);
mTracker.begin();
tracker.begin();
verify(mRenderer, only()).addObserver(any());
// send first frame - janky
sendFrame(40, JANK_APP_DEADLINE_MISSED, 100L);
sendFrame(tracker, 40, JANK_APP_DEADLINE_MISSED, 100L);
// send another frame - not jank
sendFrame(4, JANK_NONE, 101L);
sendFrame(tracker, 4, JANK_NONE, 101L);
// end the trace session
when(mChoreographer.getVsyncId()).thenReturn(102L);
mTracker.end(FrameTracker.REASON_END_NORMAL);
sendFrame(4, JANK_NONE, 102L);
tracker.end(FrameTracker.REASON_END_NORMAL);
sendFrame(tracker, 4, JANK_NONE, 102L);
verify(mTracker).removeObservers();
verify(tracker).removeObservers();
// We detected a janky frame - trigger Perfetto
verify(mTracker, never()).triggerPerfetto();
verify(tracker, never()).triggerPerfetto();
}
@Test
public void testOtherFrameOverThreshold() {
FrameTracker tracker = spyFrameTracker(
CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
when(mChoreographer.getVsyncId()).thenReturn(100L);
mTracker.begin();
tracker.begin();
verify(mRenderer, only()).addObserver(any());
// send first frame - not janky
sendFrame(4, JANK_NONE, 100L);
sendFrame(tracker, 4, JANK_NONE, 100L);
// send another frame - should be considered janky
sendFrame(40, JANK_APP_DEADLINE_MISSED, 101L);
sendFrame(tracker, 40, JANK_APP_DEADLINE_MISSED, 101L);
// end the trace session
when(mChoreographer.getVsyncId()).thenReturn(102L);
mTracker.end(FrameTracker.REASON_END_NORMAL);
sendFrame(4, JANK_NONE, 102L);
tracker.end(FrameTracker.REASON_END_NORMAL);
sendFrame(tracker, 4, JANK_NONE, 102L);
verify(mTracker).removeObservers();
verify(tracker).removeObservers();
// We detected a janky frame - trigger Perfetto
verify(mTracker).triggerPerfetto();
verify(tracker).triggerPerfetto();
}
@Test
public void testLastFrameOverThresholdBeforeEnd() {
FrameTracker tracker = spyFrameTracker(
CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
when(mChoreographer.getVsyncId()).thenReturn(100L);
mTracker.begin();
tracker.begin();
verify(mRenderer, only()).addObserver(any());
// send first frame - not janky
sendFrame(4, JANK_NONE, 100L);
sendFrame(tracker, 4, JANK_NONE, 100L);
// send another frame - not janky
sendFrame(4, JANK_NONE, 101L);
sendFrame(tracker, 4, JANK_NONE, 101L);
// end the trace session, simulate one more valid callback came after the end call.
when(mChoreographer.getVsyncId()).thenReturn(102L);
mTracker.end(FrameTracker.REASON_END_NORMAL);
sendFrame(50, JANK_APP_DEADLINE_MISSED, 102L);
tracker.end(FrameTracker.REASON_END_NORMAL);
sendFrame(tracker, 50, JANK_APP_DEADLINE_MISSED, 102L);
// One more callback with VSYNC after the end() vsync id.
sendFrame(4, JANK_NONE, 103L);
sendFrame(tracker, 4, JANK_NONE, 103L);
verify(mTracker).removeObservers();
verify(tracker).removeObservers();
// We detected a janky frame - trigger Perfetto
verify(mTracker).triggerPerfetto();
verify(tracker).triggerPerfetto();
}
@Test
public void testBeginCancel() {
FrameTracker tracker = spyFrameTracker(
CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
when(mChoreographer.getVsyncId()).thenReturn(100L);
mTracker.begin();
tracker.begin();
verify(mRenderer).addObserver(any());
// First frame - not janky
sendFrame(4, JANK_NONE, 100L);
sendFrame(tracker, 4, JANK_NONE, 100L);
// normal frame - not janky
sendFrame(4, JANK_NONE, 101L);
sendFrame(tracker, 4, JANK_NONE, 101L);
// a janky frame
sendFrame(50, JANK_APP_DEADLINE_MISSED, 102L);
sendFrame(tracker, 50, JANK_APP_DEADLINE_MISSED, 102L);
mTracker.cancel(FrameTracker.REASON_CANCEL_NORMAL);
verify(mTracker).removeObservers();
tracker.cancel(FrameTracker.REASON_CANCEL_NORMAL);
verify(tracker).removeObservers();
// Since the tracker has been cancelled, shouldn't trigger perfetto.
verify(mTracker, never()).triggerPerfetto();
verify(tracker, never()).triggerPerfetto();
}
@Test
public void testCancelIfEndVsyncIdEqualsToBeginVsyncId() {
FrameTracker tracker = spyFrameTracker(
CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
when(mChoreographer.getVsyncId()).thenReturn(100L);
mTracker.begin();
tracker.begin();
verify(mRenderer, only()).addObserver(any());
// end the trace session
when(mChoreographer.getVsyncId()).thenReturn(101L);
mTracker.end(FrameTracker.REASON_END_NORMAL);
tracker.end(FrameTracker.REASON_END_NORMAL);
// Since the begin vsync id (101) equals to the end vsync id (101), will be treat as cancel.
verify(mTracker).cancel(FrameTracker.REASON_CANCEL_SAME_VSYNC);
verify(tracker).cancel(FrameTracker.REASON_CANCEL_SAME_VSYNC);
// Observers should be removed in this case, or FrameTracker object will be leaked.
verify(mTracker).removeObservers();
verify(tracker).removeObservers();
// Should never trigger Perfetto since it is a cancel.
verify(mTracker, never()).triggerPerfetto();
verify(tracker, never()).triggerPerfetto();
}
@Test
public void testCancelIfEndVsyncIdLessThanBeginVsyncId() {
FrameTracker tracker = spyFrameTracker(
CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
when(mChoreographer.getVsyncId()).thenReturn(100L);
mTracker.begin();
tracker.begin();
verify(mRenderer, only()).addObserver(any());
// end the trace session at the same vsync id, end vsync id will less than the begin one.
// Because the begin vsync id is supposed to the next frame,
mTracker.end(FrameTracker.REASON_END_NORMAL);
tracker.end(FrameTracker.REASON_END_NORMAL);
// The begin vsync id (101) is larger than the end one (100), will be treat as cancel.
verify(mTracker).cancel(FrameTracker.REASON_CANCEL_SAME_VSYNC);
verify(tracker).cancel(FrameTracker.REASON_CANCEL_SAME_VSYNC);
// Observers should be removed in this case, or FrameTracker object will be leaked.
verify(mTracker).removeObservers();
verify(tracker).removeObservers();
// Should never trigger Perfetto since it is a cancel.
verify(mTracker, never()).triggerPerfetto();
verify(tracker, never()).triggerPerfetto();
}
@Test
public void testCancelWhenSessionNeverBegun() {
mTracker.cancel(FrameTracker.REASON_CANCEL_NORMAL);
verify(mTracker).removeObservers();
FrameTracker tracker = spyFrameTracker(
CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
tracker.cancel(FrameTracker.REASON_CANCEL_NORMAL);
verify(tracker).removeObservers();
}
@Test
public void testEndWhenSessionNeverBegun() {
mTracker.end(FrameTracker.REASON_END_NORMAL);
verify(mTracker).removeObservers();
FrameTracker tracker = spyFrameTracker(
CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false);
tracker.end(FrameTracker.REASON_END_NORMAL);
verify(tracker).removeObservers();
}
private void sendFirstWindowFrame(long durationMillis,
@Test
public void testSurfaceOnlyOtherFrameJanky() {
FrameTracker tracker = spyFrameTracker(
CUJ_WALLPAPER_TRANSITION, CUJ_POSTFIX, /* surfaceOnly= */ true);
when(mChoreographer.getVsyncId()).thenReturn(100L);
tracker.begin();
verify(mSurfaceControlWrapper).addJankStatsListener(any(), any());
// First frame - not janky
sendFrame(tracker, JANK_NONE, 100L);
// normal frame - not janky
sendFrame(tracker, JANK_NONE, 101L);
// a janky frame
sendFrame(tracker, JANK_APP_DEADLINE_MISSED, 102L);
when(mChoreographer.getVsyncId()).thenReturn(102L);
tracker.end(FrameTracker.REASON_CANCEL_NORMAL);
// an extra frame to trigger finish
sendFrame(tracker, JANK_NONE, 103L);
verify(mSurfaceControlWrapper).removeJankStatsListener(any());
verify(tracker).triggerPerfetto();
}
@Test
public void testSurfaceOnlyFirstFrameJanky() {
FrameTracker tracker = spyFrameTracker(
CUJ_WALLPAPER_TRANSITION, CUJ_POSTFIX, /* surfaceOnly= */ true);
when(mChoreographer.getVsyncId()).thenReturn(100L);
tracker.begin();
verify(mSurfaceControlWrapper).addJankStatsListener(any(), any());
// First frame - janky
sendFrame(tracker, JANK_APP_DEADLINE_MISSED, 100L);
// normal frame - not janky
sendFrame(tracker, JANK_NONE, 101L);
// normal frame - not janky
sendFrame(tracker, JANK_NONE, 102L);
when(mChoreographer.getVsyncId()).thenReturn(102L);
tracker.end(FrameTracker.REASON_CANCEL_NORMAL);
// an extra frame to trigger finish
sendFrame(tracker, JANK_NONE, 103L);
verify(mSurfaceControlWrapper).removeJankStatsListener(any());
verify(tracker, never()).triggerPerfetto();
}
@Test
public void testSurfaceOnlyLastFrameJanky() {
FrameTracker tracker = spyFrameTracker(
CUJ_WALLPAPER_TRANSITION, CUJ_POSTFIX, /* surfaceOnly= */ true);
when(mChoreographer.getVsyncId()).thenReturn(100L);
tracker.begin();
verify(mSurfaceControlWrapper).addJankStatsListener(any(), any());
// First frame - not janky
sendFrame(tracker, JANK_NONE, 100L);
// normal frame - not janky
sendFrame(tracker, JANK_NONE, 101L);
// normal frame - not janky
sendFrame(tracker, JANK_NONE, 102L);
when(mChoreographer.getVsyncId()).thenReturn(102L);
tracker.end(FrameTracker.REASON_CANCEL_NORMAL);
// janky frame, should be ignored, trigger finish
sendFrame(tracker, JANK_APP_DEADLINE_MISSED, 103L);
verify(mSurfaceControlWrapper).removeJankStatsListener(any());
verify(tracker, never()).triggerPerfetto();
}
private void sendFirstWindowFrame(FrameTracker tracker, long durationMillis,
@JankType int jankType, long vsyncId) {
sendFrame(durationMillis, jankType, vsyncId, true /* firstWindowFrame */);
sendFrame(tracker, durationMillis, jankType, vsyncId, /* firstWindowFrame= */ true);
}
private void sendFrame(long durationMillis,
private void sendFrame(FrameTracker tracker, long durationMillis,
@JankType int jankType, long vsyncId) {
sendFrame(durationMillis, jankType, vsyncId, false /* firstWindowFrame */);
sendFrame(tracker, durationMillis, jankType, vsyncId, /* firstWindowFrame= */ false);
}
private void sendFrame(long durationMillis,
/**
* Used for surface only test.
*/
private void sendFrame(FrameTracker tracker, @JankType int jankType, long vsyncId) {
sendFrame(tracker, /* durationMillis= */ -1,
jankType, vsyncId, /* firstWindowFrame= */ false);
}
private void sendFrame(FrameTracker tracker, 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);
if (!tracker.mSurfaceOnly) {
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);
tracker.onFrameMetricsAvailable(0);
}
mListenerCapture.getValue().onJankDataAvailable(new JankData[] {
new JankData(vsyncId, jankType)
});

View File

@@ -16,8 +16,6 @@
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;
@@ -25,17 +23,17 @@ import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
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;
import static org.mockito.Mockito.when;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.provider.DeviceConfig;
import android.view.View;
import android.view.ViewAttachTestActivity;
@@ -43,8 +41,12 @@ 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.SurfaceControlWrapper;
import com.android.internal.jank.FrameTracker.ThreadedRendererWrapper;
import com.android.internal.jank.FrameTracker.ViewRootWrapper;
import com.android.internal.jank.InteractionJankMonitor.Configuration;
import com.android.internal.jank.InteractionJankMonitor.Session;
import org.junit.Before;
@@ -92,12 +94,15 @@ public class InteractionJankMonitorTest {
verify(mWorker).start();
Session session = new Session(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX);
Configuration config = mock(Configuration.class);
when(config.isSurfaceOnly()).thenReturn(false);
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, null));
new ViewRootWrapper(mView.getViewRootImpl()),
new SurfaceControlWrapper(), mock(ChoreographerWrapper.class),
new FrameMetricsWrapper(),
/* traceThresholdMissedFrames= */ 1, /* traceThresholdFrameTimeMillis= */ -1,
/* FrameTrackerListener */ null, config));
doReturn(tracker).when(monitor).createFrameTracker(any(), any());
doNothing().when(tracker).triggerPerfetto();
doNothing().when(tracker).postTraceStartMarker();
@@ -138,28 +143,30 @@ public class InteractionJankMonitorTest {
public void testBeginCancel() {
InteractionJankMonitor monitor = spy(new InteractionJankMonitor(mWorker));
ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class);
ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
Session session = new Session(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX);
Configuration config = mock(Configuration.class);
when(config.isSurfaceOnly()).thenReturn(false);
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, null));
new ViewRootWrapper(mView.getViewRootImpl()),
new SurfaceControlWrapper(), mock(FrameTracker.ChoreographerWrapper.class),
new FrameMetricsWrapper(),
/* traceThresholdMissedFrames= */ 1, /* traceThresholdFrameTimeMillis= */ -1,
/* FrameTrackerListener */ null, config));
doReturn(tracker).when(monitor).createFrameTracker(any(), any());
doNothing().when(tracker).triggerPerfetto();
doNothing().when(tracker).postTraceStartMarker();
assertThat(monitor.begin(mView, session.getCuj())).isTrue();
verify(tracker).begin();
verify(mWorker.getThreadHandler(), atLeastOnce()).sendMessageAtTime(captor.capture(),
anyLong());
Runnable runnable = captor.getValue().getCallback();
verify(monitor).scheduleTimeoutAction(anyInt(), anyLong(), captor.capture());
Runnable runnable = captor.getValue();
assertThat(runnable).isNotNull();
mWorker.getThreadHandler().removeCallbacks(runnable);
runnable.run();
verify(tracker).cancel(FrameTracker.REASON_CANCEL_NORMAL);
verify(tracker).cancel(FrameTracker.REASON_CANCEL_TIMEOUT);
}
@Test

View File

@@ -80,8 +80,7 @@ public final class InteractionJankMonitorWrapper {
public static void begin(View v, @CujType int cujType, long timeout) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return;
Configuration.Builder builder =
new Configuration.Builder(cujType)
.setView(v)
Configuration.Builder.withView(cujType, v)
.setTimeout(timeout);
InteractionJankMonitor.getInstance().begin(builder);
}

View File

@@ -2347,8 +2347,8 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
}
private Configuration.Builder createInteractionJankMonitorConf(String tag) {
return new Configuration.Builder(CUJ_LOCKSCREEN_UNLOCK_ANIMATION)
.setView(mKeyguardViewControllerLazy.get().getViewRootImpl().getView())
return Configuration.Builder.withView(CUJ_LOCKSCREEN_UNLOCK_ANIMATION,
mKeyguardViewControllerLazy.get().getViewRootImpl().getView())
.setTag(tag);
}

View File

@@ -521,8 +521,8 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
@Override
public void onAnimationStart(Animator animation) {
mWasCancelled = false;
Configuration.Builder builder = new Configuration.Builder(getCujType(isAppearing))
.setView(ActivatableNotificationView.this);
Configuration.Builder builder = Configuration.Builder
.withView(getCujType(isAppearing), ActivatableNotificationView.this);
InteractionJankMonitor.getInstance().begin(builder);
}

View File

@@ -1397,8 +1397,7 @@ public abstract class PanelViewController {
private void beginJankMonitoring(int cuj) {
InteractionJankMonitor.Configuration.Builder builder =
new InteractionJankMonitor.Configuration.Builder(cuj)
.setView(mView)
InteractionJankMonitor.Configuration.Builder.withView(cuj, mView)
.setTag(isFullyCollapsed() ? "Expand" : "Collapse");
InteractionJankMonitor.getInstance().begin(builder);
}