Merge "New jank flag for showing CUJ names on screen" into udc-dev

This commit is contained in:
Peter Kalauskas
2023-04-21 01:53:42 +00:00
committed by Android (Google) Code Review
4 changed files with 356 additions and 8 deletions

View File

@@ -36,6 +36,7 @@ import android.graphics.HardwareRendererObserver;
import android.os.Handler;
import android.os.Trace;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.SparseArray;
import android.view.Choreographer;
@@ -43,7 +44,9 @@ import android.view.FrameMetrics;
import android.view.SurfaceControl;
import android.view.SurfaceControl.JankData.JankType;
import android.view.ThreadedRenderer;
import android.view.View;
import android.view.ViewRootImpl;
import android.view.WindowCallbacks;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.InteractionJankMonitor.Configuration;
@@ -686,6 +689,14 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
}
}
ThreadedRendererWrapper getThreadedRenderer() {
return mRendererWrapper;
}
ViewRootWrapper getViewRoot() {
return mViewRoot;
}
private boolean shouldTriggerPerfetto(int missedFramesCount, int maxFrameTimeNanos) {
boolean overMissedFramesThreshold = mTraceThresholdMissedFrames != -1
&& missedFramesCount >= mTraceThresholdMissedFrames;
@@ -798,6 +809,28 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
public SurfaceControl getSurfaceControl() {
return mViewRoot.getSurfaceControl();
}
void requestInvalidateRootRenderNode() {
mViewRoot.requestInvalidateRootRenderNode();
}
void addWindowCallbacks(WindowCallbacks windowCallbacks) {
mViewRoot.addWindowCallbacks(windowCallbacks);
}
void removeWindowCallbacks(WindowCallbacks windowCallbacks) {
mViewRoot.removeWindowCallbacks(windowCallbacks);
}
View getView() {
return mViewRoot.getView();
}
int dipToPx(int dip) {
final DisplayMetrics displayMetrics =
mViewRoot.mContext.getResources().getDisplayMetrics();
return (int) (displayMetrics.density * dip + 0.5f);
}
}
public static class SurfaceControlWrapper {

View File

@@ -97,6 +97,7 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION;
import android.Manifest;
import android.annotation.ColorInt;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
@@ -104,6 +105,7 @@ import android.annotation.UiThread;
import android.annotation.WorkerThread;
import android.app.ActivityThread;
import android.content.Context;
import android.graphics.Color;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerExecutor;
@@ -143,6 +145,14 @@ import java.util.concurrent.TimeUnit;
* adb shell device_config put interaction_jank_monitor enabled true
* adb shell device_config put interaction_jank_monitor sampling_interval 1
*
* On debuggable builds, an overlay can be used to display the name of the
* currently running cuj using:
*
* adb shell device_config put interaction_jank_monitor debug_overlay_enabled true
*
* NOTE: The overlay will interfere with metrics, so it should only be used
* for understanding which UI events correspeond to which CUJs.
*
* @hide
*/
public class InteractionJankMonitor {
@@ -159,6 +169,7 @@ public class InteractionJankMonitor {
"trace_threshold_missed_frames";
private static final String SETTINGS_THRESHOLD_FRAME_TIME_MILLIS_KEY =
"trace_threshold_frame_time_millis";
private static final String SETTINGS_DEBUG_OVERLAY_ENABLED_KEY = "debug_overlay_enabled";
/** Default to being enabled on debug builds. */
private static final boolean DEFAULT_ENABLED = Build.IS_DEBUGGABLE;
/** Default to collecting data for all CUJs. */
@@ -166,6 +177,7 @@ public class InteractionJankMonitor {
/** Default to triggering trace if 3 frames are missed OR a frame takes at least 64ms */
private static final int DEFAULT_TRACE_THRESHOLD_MISSED_FRAMES = 3;
private static final int DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS = 64;
private static final boolean DEFAULT_DEBUG_OVERLAY_ENABLED = false;
@VisibleForTesting
public static final int MAX_LENGTH_OF_CUJ_NAME = 80;
@@ -343,6 +355,9 @@ public class InteractionJankMonitor {
private final HandlerThread mWorker;
private final DisplayResolutionTracker mDisplayResolutionTracker;
private final Object mLock = new Object();
private @ColorInt int mDebugBgColor = Color.CYAN;
private double mDebugYOffset = 0.1;
private InteractionMonitorDebugOverlay mDebugOverlay;
private volatile boolean mEnabled = DEFAULT_ENABLED;
private int mSamplingInterval = DEFAULT_SAMPLING_INTERVAL;
@@ -533,7 +548,7 @@ public class InteractionJankMonitor {
if (needRemoveTasks(action, session)) {
getTracker(session.getCuj()).getHandler().runWithScissors(() -> {
removeTimeout(session.getCuj());
removeTracker(session.getCuj());
removeTracker(session.getCuj(), session.getReason());
}, EXECUTOR_TASK_TIMEOUT);
}
}
@@ -699,7 +714,7 @@ public class InteractionJankMonitor {
if (tracker == null) return false;
// if the end call doesn't return true, another thread is handling end of the cuj.
if (tracker.end(REASON_END_NORMAL)) {
removeTracker(cujType);
removeTracker(cujType, REASON_END_NORMAL);
}
return true;
}
@@ -750,7 +765,7 @@ public class InteractionJankMonitor {
if (tracker == null) return false;
// if the cancel call doesn't return true, another thread is handling cancel of the cuj.
if (tracker.cancel(reason)) {
removeTracker(cujType);
removeTracker(cujType, reason);
}
return true;
}
@@ -758,6 +773,13 @@ public class InteractionJankMonitor {
private void putTracker(@CujType int cuj, @NonNull FrameTracker tracker) {
synchronized (mLock) {
mRunningTrackers.put(cuj, tracker);
if (mDebugOverlay != null) {
mDebugOverlay.onTrackerAdded(cuj, tracker.getViewRoot());
}
if (DEBUG) {
Log.d(TAG, "Added tracker for " + getNameOfCuj(cuj)
+ ". mRunningTrackers=" + listNamesOfCujs(mRunningTrackers));
}
}
}
@@ -767,9 +789,16 @@ public class InteractionJankMonitor {
}
}
private void removeTracker(@CujType int cuj) {
private void removeTracker(@CujType int cuj, int reason) {
synchronized (mLock) {
mRunningTrackers.remove(cuj);
if (mDebugOverlay != null) {
mDebugOverlay.onTrackerRemoved(cuj, reason, mRunningTrackers);
}
if (DEBUG) {
Log.d(TAG, "Removed tracker for " + getNameOfCuj(cuj)
+ ". mRunningTrackers=" + listNamesOfCujs(mRunningTrackers));
}
}
}
@@ -782,6 +811,16 @@ public class InteractionJankMonitor {
mTraceThresholdFrameTimeMillis = properties.getInt(
SETTINGS_THRESHOLD_FRAME_TIME_MILLIS_KEY,
DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS);
// Never allow the debug overlay to be used on user builds
boolean debugOverlayEnabled = Build.IS_DEBUGGABLE && properties.getBoolean(
SETTINGS_DEBUG_OVERLAY_ENABLED_KEY,
DEFAULT_DEBUG_OVERLAY_ENABLED);
if (debugOverlayEnabled && mDebugOverlay == null) {
mDebugOverlay = new InteractionMonitorDebugOverlay(mDebugBgColor, mDebugYOffset);
} else if (!debugOverlayEnabled && mDebugOverlay != null) {
mDebugOverlay.dispose();
mDebugOverlay = null;
}
// The memory visibility is powered by the volatile field, mEnabled.
mEnabled = properties.getBoolean(SETTINGS_ENABLED_KEY, DEFAULT_ENABLED);
}
@@ -821,6 +860,39 @@ public class InteractionJankMonitor {
return interactionType - 1;
}
/**
* Configures the debug overlay used for displaying interaction names on the screen while they
* occur.
*
* @param bgColor the background color of the box used to display the CUJ names
* @param yOffset number between 0 and 1 to indicate where the top of the box should be relative
* to the height of the screen
*/
public void configDebugOverlay(@ColorInt int bgColor, double yOffset) {
mDebugBgColor = bgColor;
mDebugYOffset = yOffset;
}
/**
* A helper method for getting a string representation of all running CUJs. For example,
* "(LOCKSCREEN_TRANSITION_FROM_AOD, IME_INSETS_ANIMATION)"
*/
private static String listNamesOfCujs(SparseArray<FrameTracker> trackers) {
if (!DEBUG) {
return null;
}
StringBuilder sb = new StringBuilder();
sb.append('(');
for (int i = 0; i < trackers.size(); i++) {
sb.append(getNameOfCuj(trackers.keyAt(i)));
if (i < trackers.size() - 1) {
sb.append(", ");
}
}
sb.append(')');
return sb.toString();
}
/**
* A helper method to translate CUJ type to CUJ name.
*

View File

@@ -0,0 +1,240 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.jank;
import static com.android.internal.jank.FrameTracker.REASON_END_NORMAL;
import android.annotation.ColorInt;
import android.app.ActivityThread;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RecordingCanvas;
import android.graphics.Rect;
import android.os.Trace;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.WindowCallbacks;
import com.android.internal.jank.FrameTracker.Reasons;
import com.android.internal.jank.InteractionJankMonitor.CujType;
/**
* An overlay that uses WindowCallbacks to draw the names of all running CUJs to the window
* associated with one of the CUJs being tracked. There's no guarantee which window it will
* draw to. NOTE: sometimes the CUJ names will remain displayed on the screen longer than they
* are actually running.
* <p>
* CUJ names will be drawn as follows:
* <ul>
* <li> Normal text indicates the CUJ is currently running
* <li> Grey text indicates the CUJ ended normally and is no longer running
* <li> Red text with a strikethrough indicates the CUJ was canceled or ended abnormally
* </ul>
*/
class InteractionMonitorDebugOverlay implements WindowCallbacks {
private static final int REASON_STILL_RUNNING = -1000;
// Sparse array where the key in the CUJ and the value is the session status, or null if
// it's currently running
private final SparseIntArray mRunningCujs = new SparseIntArray();
private FrameTracker.ViewRootWrapper mViewRoot = null;
private final Paint mDebugPaint;
private final Paint.FontMetrics mDebugFontMetrics;
// Used to display the overlay in a different color and position for different processes.
// Otherwise, two overlays will overlap and be difficult to read.
private final int mBgColor;
private final double mYOffset;
private final String mPackageName;
InteractionMonitorDebugOverlay(@ColorInt int bgColor, double yOffset) {
mBgColor = bgColor;
mYOffset = yOffset;
mDebugPaint = new Paint();
mDebugPaint.setAntiAlias(false);
mDebugFontMetrics = new Paint.FontMetrics();
final Context context = ActivityThread.currentApplication();
mPackageName = context.getPackageName();
}
void dispose() {
if (mViewRoot != null) {
mViewRoot.removeWindowCallbacks(this);
forceRedraw();
}
mViewRoot = null;
}
private boolean attachViewRootIfNeeded(FrameTracker.ViewRootWrapper viewRoot) {
if (mViewRoot == null && viewRoot != null) {
mViewRoot = viewRoot;
viewRoot.addWindowCallbacks(this);
forceRedraw();
return true;
}
return false;
}
private float getWidthOfLongestCujName(int cujFontSize) {
mDebugPaint.setTextSize(cujFontSize);
float maxLength = 0;
for (int i = 0; i < mRunningCujs.size(); i++) {
String cujName = InteractionJankMonitor.getNameOfCuj(mRunningCujs.keyAt(i));
float textLength = mDebugPaint.measureText(cujName);
if (textLength > maxLength) {
maxLength = textLength;
}
}
return maxLength;
}
private float getTextHeight(int textSize) {
mDebugPaint.setTextSize(textSize);
mDebugPaint.getFontMetrics(mDebugFontMetrics);
return mDebugFontMetrics.descent - mDebugFontMetrics.ascent;
}
private int dipToPx(int dip) {
if (mViewRoot != null) {
return mViewRoot.dipToPx(dip);
} else {
return dip;
}
}
private void forceRedraw() {
if (mViewRoot != null) {
mViewRoot.requestInvalidateRootRenderNode();
mViewRoot.getView().invalidate();
}
}
void onTrackerRemoved(@CujType int removedCuj, @Reasons int reason,
SparseArray<FrameTracker> runningTrackers) {
mRunningCujs.put(removedCuj, reason);
// If REASON_STILL_RUNNING is not in mRunningCujs, then all CUJs have ended
if (mRunningCujs.indexOfValue(REASON_STILL_RUNNING) < 0) {
mRunningCujs.clear();
dispose();
} else {
boolean needsNewViewRoot = true;
if (mViewRoot != null) {
// Check to see if this viewroot is still associated with one of the running
// trackers
for (int i = 0; i < runningTrackers.size(); i++) {
if (mViewRoot.equals(
runningTrackers.valueAt(i).getViewRoot())) {
needsNewViewRoot = false;
break;
}
}
}
if (needsNewViewRoot) {
dispose();
for (int i = 0; i < runningTrackers.size(); i++) {
if (attachViewRootIfNeeded(runningTrackers.valueAt(i).getViewRoot())) {
break;
}
}
} else {
forceRedraw();
}
}
}
void onTrackerAdded(@CujType int addedCuj, FrameTracker.ViewRootWrapper viewRoot) {
// Use REASON_STILL_RUNNING (not technically one of the '@Reasons') to indicate the CUJ
// is still running
mRunningCujs.put(addedCuj, REASON_STILL_RUNNING);
attachViewRootIfNeeded(viewRoot);
forceRedraw();
}
@Override
public void onWindowSizeIsChanging(Rect newBounds, boolean fullscreen,
Rect systemInsets, Rect stableInsets) {
}
@Override
public void onWindowDragResizeStart(Rect initialBounds, boolean fullscreen,
Rect systemInsets, Rect stableInsets) {
}
@Override
public void onWindowDragResizeEnd() {
}
@Override
public boolean onContentDrawn(int offsetX, int offsetY, int sizeX, int sizeY) {
return false;
}
@Override
public void onRequestDraw(boolean reportNextDraw) {
}
@Override
public void onPostDraw(RecordingCanvas canvas) {
Trace.beginSection("InteractionJankMonitor#drawDebug");
final int padding = dipToPx(5);
final int h = canvas.getHeight();
final int w = canvas.getWidth();
// Draw sysui CUjs near the bottom of the screen so they don't overlap with the shade,
// and draw launcher CUJs near the top of the screen so they don't overlap with gestures
final int dy = (int) (h * mYOffset);
int packageNameFontSize = dipToPx(12);
int cujFontSize = dipToPx(18);
final float cujNameTextHeight = getTextHeight(cujFontSize);
final float packageNameTextHeight = getTextHeight(packageNameFontSize);
float maxLength = getWidthOfLongestCujName(cujFontSize);
final int dx = (int) ((w - maxLength) / 2f);
canvas.translate(dx, dy);
// Draw background rectangle for displaying the text showing the CUJ name
mDebugPaint.setColor(mBgColor);
canvas.drawRect(
-padding * 2, // more padding on top so we can draw the package name
-padding,
padding * 2 + maxLength,
padding * 2 + packageNameTextHeight + cujNameTextHeight * mRunningCujs.size(),
mDebugPaint);
mDebugPaint.setTextSize(packageNameFontSize);
mDebugPaint.setColor(Color.BLACK);
mDebugPaint.setStrikeThruText(false);
canvas.translate(0, packageNameTextHeight);
canvas.drawText("package:" + mPackageName, 0, 0, mDebugPaint);
mDebugPaint.setTextSize(cujFontSize);
// Draw text for CUJ names
for (int i = 0; i < mRunningCujs.size(); i++) {
int status = mRunningCujs.valueAt(i);
if (status == REASON_STILL_RUNNING) {
mDebugPaint.setColor(Color.BLACK);
mDebugPaint.setStrikeThruText(false);
} else if (status == REASON_END_NORMAL) {
mDebugPaint.setColor(Color.GRAY);
mDebugPaint.setStrikeThruText(false);
} else {
// Cancelled, or otherwise ended for a bad reason
mDebugPaint.setColor(Color.RED);
mDebugPaint.setStrikeThruText(true);
}
String cujName = InteractionJankMonitor.getNameOfCuj(mRunningCujs.keyAt(i));
canvas.translate(0, cujNameTextHeight);
canvas.drawText(cujName, 0, 0, mDebugPaint);
}
Trace.endSection();
}
}

View File

@@ -50,6 +50,7 @@ import android.content.pm.PackageManager;
import android.content.pm.ShortcutManager;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.graphics.Color;
import android.hardware.SensorManager;
import android.hardware.SensorPrivacyManager;
import android.hardware.biometrics.BiometricManager;
@@ -113,13 +114,13 @@ import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.TestHarness;
import com.android.systemui.shared.system.PackageManagerWrapper;
import dagger.Module;
import dagger.Provides;
import java.util.Optional;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
/**
* Provides Non-SystemUI, Framework-Owned instances to the dependency graph.
*/
@@ -323,7 +324,9 @@ public class FrameworkServicesModule {
@Provides
@Singleton
static InteractionJankMonitor provideInteractionJankMonitor() {
return InteractionJankMonitor.getInstance();
InteractionJankMonitor jankMonitor = InteractionJankMonitor.getInstance();
jankMonitor.configDebugOverlay(Color.YELLOW, 0.75);
return jankMonitor;
}
@Provides