summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/com/android/internal/jank/FrameTracker.java33
-rw-r--r--core/java/com/android/internal/jank/InteractionJankMonitor.java80
-rw-r--r--core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java240
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java11
4 files changed, 356 insertions, 8 deletions
diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java
index 1c0da1846536..7fe44c7dd674 100644
--- a/core/java/com/android/internal/jank/FrameTracker.java
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -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;
@@ -679,6 +682,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;
@@ -791,6 +802,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 {
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 6344568480b7..2574927af360 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -96,6 +96,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;
@@ -103,6 +104,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;
@@ -142,6 +144,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 {
@@ -158,6 +168,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. */
@@ -165,6 +176,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;
@@ -340,6 +352,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;
@@ -529,7 +544,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);
}
}
@@ -695,7 +710,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;
}
@@ -746,7 +761,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;
}
@@ -754,6 +769,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));
+ }
}
}
@@ -763,9 +785,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));
+ }
}
}
@@ -778,6 +807,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);
}
@@ -818,6 +857,39 @@ public class InteractionJankMonitor {
}
/**
+ * 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.
*
* @param cujType the cuj type defined in this file
diff --git a/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java b/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java
new file mode 100644
index 000000000000..99b9f2f35fd4
--- /dev/null
+++ b/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java
@@ -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();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 0be3bb69d136..0dcba50df4ca 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -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