1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.internal.jank; 18 19 import static android.Manifest.permission.READ_DEVICE_CONFIG; 20 import static android.content.pm.PackageManager.PERMISSION_GRANTED; 21 import static android.provider.DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR; 22 23 import static com.android.internal.jank.FrameTracker.REASON_CANCEL_NORMAL; 24 import static com.android.internal.jank.FrameTracker.REASON_CANCEL_TIMEOUT; 25 import static com.android.internal.jank.FrameTracker.REASON_END_NORMAL; 26 27 import android.Manifest; 28 import android.annotation.ColorInt; 29 import android.annotation.NonNull; 30 import android.annotation.RequiresPermission; 31 import android.annotation.UiThread; 32 import android.annotation.WorkerThread; 33 import android.app.ActivityThread; 34 import android.content.Context; 35 import android.graphics.Color; 36 import android.os.Build; 37 import android.os.Handler; 38 import android.os.HandlerExecutor; 39 import android.os.HandlerThread; 40 import android.os.SystemClock; 41 import android.provider.DeviceConfig; 42 import android.text.TextUtils; 43 import android.util.Log; 44 import android.util.SparseArray; 45 import android.view.Choreographer; 46 import android.view.SurfaceControl; 47 import android.view.View; 48 49 import com.android.internal.annotations.GuardedBy; 50 import com.android.internal.annotations.VisibleForTesting; 51 import com.android.internal.jank.FrameTracker.ChoreographerWrapper; 52 import com.android.internal.jank.FrameTracker.FrameMetricsWrapper; 53 import com.android.internal.jank.FrameTracker.FrameTrackerListener; 54 import com.android.internal.jank.FrameTracker.Reasons; 55 import com.android.internal.jank.FrameTracker.SurfaceControlWrapper; 56 import com.android.internal.jank.FrameTracker.ThreadedRendererWrapper; 57 import com.android.internal.jank.FrameTracker.ViewRootWrapper; 58 import com.android.internal.util.PerfettoTrigger; 59 60 import java.time.Instant; 61 import java.util.concurrent.ThreadLocalRandom; 62 import java.util.concurrent.TimeUnit; 63 import java.util.function.Supplier; 64 65 /** 66 * This class lets users begin and end the always on tracing mechanism. 67 * 68 * Enabling for local development: 69 *<pre> 70 * adb shell device_config put interaction_jank_monitor enabled true 71 * adb shell device_config put interaction_jank_monitor sampling_interval 1 72 * </pre> 73 * On debuggable builds, an overlay can be used to display the name of the 74 * currently running cuj using: 75 * <pre> 76 * adb shell device_config put interaction_jank_monitor debug_overlay_enabled true 77 * </pre> 78 * <b>NOTE</b>: The overlay will interfere with metrics, so it should only be used 79 * for understanding which UI events correspond to which CUJs. 80 * 81 * @hide 82 */ 83 public class InteractionJankMonitor { 84 private static final String TAG = InteractionJankMonitor.class.getSimpleName(); 85 private static final String ACTION_PREFIX = InteractionJankMonitor.class.getCanonicalName(); 86 87 private static final String DEFAULT_WORKER_NAME = TAG + "-Worker"; 88 private static final long DEFAULT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(2L); 89 static final long EXECUTOR_TASK_TIMEOUT = 500; 90 private static final String SETTINGS_ENABLED_KEY = "enabled"; 91 private static final String SETTINGS_SAMPLING_INTERVAL_KEY = "sampling_interval"; 92 private static final String SETTINGS_THRESHOLD_MISSED_FRAMES_KEY = 93 "trace_threshold_missed_frames"; 94 private static final String SETTINGS_THRESHOLD_FRAME_TIME_MILLIS_KEY = 95 "trace_threshold_frame_time_millis"; 96 private static final String SETTINGS_DEBUG_OVERLAY_ENABLED_KEY = "debug_overlay_enabled"; 97 /** Default to being enabled on debug builds. */ 98 private static final boolean DEFAULT_ENABLED = Build.IS_DEBUGGABLE; 99 /** Default to collecting data for all CUJs. */ 100 private static final int DEFAULT_SAMPLING_INTERVAL = 1; 101 /** Default to triggering trace if 3 frames are missed OR a frame takes at least 64ms */ 102 private static final int DEFAULT_TRACE_THRESHOLD_MISSED_FRAMES = 3; 103 private static final int DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS = 64; 104 private static final boolean DEFAULT_DEBUG_OVERLAY_ENABLED = false; 105 106 private static final int MAX_LENGTH_SESSION_NAME = 100; 107 108 public static final String ACTION_SESSION_END = ACTION_PREFIX + ".ACTION_SESSION_END"; 109 public static final String ACTION_SESSION_CANCEL = ACTION_PREFIX + ".ACTION_SESSION_CANCEL"; 110 111 // These are not the CUJ constants you are looking for. These constants simply forward their 112 // definition from {@link Cuj}. They are here only as a transition measure until all references 113 // have been updated to the new location. 114 @Deprecated public static final int CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE; 115 @Deprecated public static final int CUJ_NOTIFICATION_SHADE_SCROLL_FLING = Cuj.CUJ_NOTIFICATION_SHADE_SCROLL_FLING; 116 @Deprecated public static final int CUJ_NOTIFICATION_SHADE_ROW_EXPAND = Cuj.CUJ_NOTIFICATION_SHADE_ROW_EXPAND; 117 @Deprecated public static final int CUJ_NOTIFICATION_SHADE_ROW_SWIPE = Cuj.CUJ_NOTIFICATION_SHADE_ROW_SWIPE; 118 @Deprecated public static final int CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE = Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE; 119 @Deprecated public static final int CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE = Cuj.CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE; 120 @Deprecated public static final int CUJ_NOTIFICATION_HEADS_UP_APPEAR = Cuj.CUJ_NOTIFICATION_HEADS_UP_APPEAR; 121 @Deprecated public static final int CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR = Cuj.CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR; 122 @Deprecated public static final int CUJ_NOTIFICATION_ADD = Cuj.CUJ_NOTIFICATION_ADD; 123 @Deprecated public static final int CUJ_NOTIFICATION_REMOVE = Cuj.CUJ_NOTIFICATION_REMOVE; 124 @Deprecated public static final int CUJ_NOTIFICATION_APP_START = Cuj.CUJ_NOTIFICATION_APP_START; 125 @Deprecated public static final int CUJ_LOCKSCREEN_PASSWORD_APPEAR = Cuj.CUJ_LOCKSCREEN_PASSWORD_APPEAR; 126 @Deprecated public static final int CUJ_LOCKSCREEN_PATTERN_APPEAR = Cuj.CUJ_LOCKSCREEN_PATTERN_APPEAR; 127 @Deprecated public static final int CUJ_LOCKSCREEN_PIN_APPEAR = Cuj.CUJ_LOCKSCREEN_PIN_APPEAR; 128 @Deprecated public static final int CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR = Cuj.CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR; 129 @Deprecated public static final int CUJ_LOCKSCREEN_PATTERN_DISAPPEAR = Cuj.CUJ_LOCKSCREEN_PATTERN_DISAPPEAR; 130 @Deprecated public static final int CUJ_LOCKSCREEN_PIN_DISAPPEAR = Cuj.CUJ_LOCKSCREEN_PIN_DISAPPEAR; 131 @Deprecated public static final int CUJ_LOCKSCREEN_TRANSITION_FROM_AOD = Cuj.CUJ_LOCKSCREEN_TRANSITION_FROM_AOD; 132 @Deprecated public static final int CUJ_LOCKSCREEN_TRANSITION_TO_AOD = Cuj.CUJ_LOCKSCREEN_TRANSITION_TO_AOD; 133 @Deprecated public static final int CUJ_SETTINGS_PAGE_SCROLL = Cuj.CUJ_SETTINGS_PAGE_SCROLL; 134 @Deprecated public static final int CUJ_LOCKSCREEN_UNLOCK_ANIMATION = Cuj.CUJ_LOCKSCREEN_UNLOCK_ANIMATION; 135 @Deprecated public static final int CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON = Cuj.CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON; 136 @Deprecated public static final int CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER = Cuj.CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER; 137 @Deprecated public static final int CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE = Cuj.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE; 138 @Deprecated public static final int CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON = Cuj.CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON; 139 @Deprecated public static final int CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP = Cuj.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP; 140 @Deprecated public static final int CUJ_PIP_TRANSITION = Cuj.CUJ_PIP_TRANSITION; 141 @Deprecated public static final int CUJ_USER_SWITCH = Cuj.CUJ_USER_SWITCH; 142 @Deprecated public static final int CUJ_SPLASHSCREEN_AVD = Cuj.CUJ_SPLASHSCREEN_AVD; 143 @Deprecated public static final int CUJ_SPLASHSCREEN_EXIT_ANIM = Cuj.CUJ_SPLASHSCREEN_EXIT_ANIM; 144 @Deprecated public static final int CUJ_SCREEN_OFF = Cuj.CUJ_SCREEN_OFF; 145 @Deprecated public static final int CUJ_SCREEN_OFF_SHOW_AOD = Cuj.CUJ_SCREEN_OFF_SHOW_AOD; 146 @Deprecated public static final int CUJ_UNFOLD_ANIM = Cuj.CUJ_UNFOLD_ANIM; 147 @Deprecated public static final int CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS = Cuj.CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS; 148 @Deprecated public static final int CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS = Cuj.CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS; 149 @Deprecated public static final int CUJ_SUW_LOADING_TO_NEXT_FLOW = Cuj.CUJ_SUW_LOADING_TO_NEXT_FLOW; 150 @Deprecated public static final int CUJ_SUW_LOADING_SCREEN_FOR_STATUS = Cuj.CUJ_SUW_LOADING_SCREEN_FOR_STATUS; 151 @Deprecated public static final int CUJ_SPLIT_SCREEN_RESIZE = Cuj.CUJ_SPLIT_SCREEN_RESIZE; 152 @Deprecated public static final int CUJ_SETTINGS_SLIDER = Cuj.CUJ_SETTINGS_SLIDER; 153 @Deprecated public static final int CUJ_TAKE_SCREENSHOT = Cuj.CUJ_TAKE_SCREENSHOT; 154 @Deprecated public static final int CUJ_VOLUME_CONTROL = Cuj.CUJ_VOLUME_CONTROL; 155 @Deprecated public static final int CUJ_BIOMETRIC_PROMPT_TRANSITION = Cuj.CUJ_BIOMETRIC_PROMPT_TRANSITION; 156 @Deprecated public static final int CUJ_SETTINGS_TOGGLE = Cuj.CUJ_SETTINGS_TOGGLE; 157 @Deprecated public static final int CUJ_SHADE_DIALOG_OPEN = Cuj.CUJ_SHADE_DIALOG_OPEN; 158 @Deprecated public static final int CUJ_USER_DIALOG_OPEN = Cuj.CUJ_USER_DIALOG_OPEN; 159 @Deprecated public static final int CUJ_TASKBAR_EXPAND = Cuj.CUJ_TASKBAR_EXPAND; 160 @Deprecated public static final int CUJ_TASKBAR_COLLAPSE = Cuj.CUJ_TASKBAR_COLLAPSE; 161 @Deprecated public static final int CUJ_SHADE_CLEAR_ALL = Cuj.CUJ_SHADE_CLEAR_ALL; 162 @Deprecated public static final int CUJ_LOCKSCREEN_OCCLUSION = Cuj.CUJ_LOCKSCREEN_OCCLUSION; 163 @Deprecated public static final int CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION = Cuj.CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION; 164 @Deprecated public static final int CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER = Cuj.CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER; 165 @Deprecated public static final int CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY = Cuj.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY; 166 @Deprecated public static final int CUJ_PREDICTIVE_BACK_CROSS_TASK = Cuj.CUJ_PREDICTIVE_BACK_CROSS_TASK; 167 @Deprecated public static final int CUJ_PREDICTIVE_BACK_HOME = Cuj.CUJ_PREDICTIVE_BACK_HOME; 168 @Deprecated public static final int CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_WORKSPACE = Cuj.CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_WORKSPACE; 169 @Deprecated public static final int CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_TASKBAR = Cuj.CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_TASKBAR; 170 @Deprecated public static final int CUJ_LAUNCHER_SAVE_APP_PAIR = Cuj.CUJ_LAUNCHER_SAVE_APP_PAIR; 171 @Deprecated public static final int CUJ_LAUNCHER_ALL_APPS_SEARCH_BACK = Cuj.CUJ_LAUNCHER_ALL_APPS_SEARCH_BACK; 172 @Deprecated public static final int CUJ_LAUNCHER_TASKBAR_ALL_APPS_CLOSE_BACK = Cuj.CUJ_LAUNCHER_TASKBAR_ALL_APPS_CLOSE_BACK; 173 @Deprecated public static final int CUJ_LAUNCHER_TASKBAR_ALL_APPS_SEARCH_BACK = Cuj.CUJ_LAUNCHER_TASKBAR_ALL_APPS_SEARCH_BACK; 174 @Deprecated public static final int CUJ_LAUNCHER_WIDGET_PICKER_CLOSE_BACK = Cuj.CUJ_LAUNCHER_WIDGET_PICKER_CLOSE_BACK; 175 @Deprecated public static final int CUJ_LAUNCHER_WIDGET_PICKER_SEARCH_BACK = Cuj.CUJ_LAUNCHER_WIDGET_PICKER_SEARCH_BACK; 176 @Deprecated public static final int CUJ_LAUNCHER_WIDGET_BOTTOM_SHEET_CLOSE_BACK = Cuj.CUJ_LAUNCHER_WIDGET_BOTTOM_SHEET_CLOSE_BACK; 177 @Deprecated public static final int CUJ_LAUNCHER_WIDGET_EDU_SHEET_CLOSE_BACK = Cuj.CUJ_LAUNCHER_WIDGET_EDU_SHEET_CLOSE_BACK; 178 179 private static class InstanceHolder { 180 public static final InteractionJankMonitor INSTANCE = 181 new InteractionJankMonitor(new HandlerThread(DEFAULT_WORKER_NAME)); 182 } 183 184 @GuardedBy("mLock") 185 private final SparseArray<RunningTracker> mRunningTrackers = new SparseArray<>(); 186 private final Handler mWorker; 187 private final DisplayResolutionTracker mDisplayResolutionTracker; 188 private final Object mLock = new Object(); 189 private @ColorInt int mDebugBgColor = Color.CYAN; 190 private double mDebugYOffset = 0.1; 191 private InteractionMonitorDebugOverlay mDebugOverlay; 192 193 private volatile boolean mEnabled = DEFAULT_ENABLED; 194 private int mSamplingInterval = DEFAULT_SAMPLING_INTERVAL; 195 private int mTraceThresholdMissedFrames = DEFAULT_TRACE_THRESHOLD_MISSED_FRAMES; 196 private int mTraceThresholdFrameTimeMillis = DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS; 197 198 /** 199 * Get the singleton of InteractionJankMonitor. 200 * 201 * @return instance of InteractionJankMonitor 202 */ getInstance()203 public static InteractionJankMonitor getInstance() { 204 return InstanceHolder.INSTANCE; 205 } 206 207 /** 208 * This constructor should be only public to tests. 209 * 210 * @param worker the worker thread for the callbacks 211 */ 212 @VisibleForTesting 213 @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG) InteractionJankMonitor(@onNull HandlerThread worker)214 public InteractionJankMonitor(@NonNull HandlerThread worker) { 215 worker.start(); 216 mWorker = worker.getThreadHandler(); 217 mDisplayResolutionTracker = new DisplayResolutionTracker(mWorker); 218 219 final Context context = ActivityThread.currentApplication(); 220 if (context == null || context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) != PERMISSION_GRANTED) { 221 Log.w(TAG, "Initializing without READ_DEVICE_CONFIG permission." 222 + " enabled=" + mEnabled + ", interval=" + mSamplingInterval 223 + ", missedFrameThreshold=" + mTraceThresholdMissedFrames 224 + ", frameTimeThreshold=" + mTraceThresholdFrameTimeMillis 225 + ", package=" + (context == null ? "null" : context.getPackageName())); 226 return; 227 } 228 229 // Post initialization to the background in case we're running on the main thread. 230 mWorker.post(() -> { 231 try { 232 updateProperties(DeviceConfig.getProperties(NAMESPACE_INTERACTION_JANK_MONITOR)); 233 DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_INTERACTION_JANK_MONITOR, 234 new HandlerExecutor(mWorker), this::updateProperties); 235 } catch (SecurityException ex) { 236 Log.d(TAG, "Can't get properties: READ_DEVICE_CONFIG granted=" 237 + context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) 238 + ", package=" + context.getPackageName()); 239 } 240 }); 241 } 242 243 /** 244 * Creates a {@link FrameTracker} instance. 245 * 246 * @param config the conifg associates with this tracker 247 * @return instance of the FrameTracker 248 */ 249 @VisibleForTesting createFrameTracker(Configuration config)250 public FrameTracker createFrameTracker(Configuration config) { 251 final View view = config.mView; 252 253 final ThreadedRendererWrapper threadedRenderer = 254 view == null ? null : new ThreadedRendererWrapper(view.getThreadedRenderer()); 255 final ViewRootWrapper viewRoot = 256 view == null ? null : new ViewRootWrapper(view.getViewRootImpl()); 257 final SurfaceControlWrapper surfaceControl = new SurfaceControlWrapper(); 258 final ChoreographerWrapper choreographer = 259 new ChoreographerWrapper(Choreographer.getInstance()); 260 final FrameTrackerListener eventsListener = new FrameTrackerListener() { 261 @Override 262 public void onCujEvents(FrameTracker tracker, String action, int reason) { 263 config.getHandler().runWithScissors(() -> 264 handleCujEvents(config.mCujType, tracker, action, reason), 265 EXECUTOR_TASK_TIMEOUT); 266 } 267 268 @Override 269 public void triggerPerfetto(Configuration config) { 270 mWorker.post(() -> PerfettoTrigger.trigger(config.getPerfettoTrigger())); 271 } 272 }; 273 final FrameMetricsWrapper frameMetrics = new FrameMetricsWrapper(); 274 275 return new FrameTracker(config, threadedRenderer, viewRoot, 276 surfaceControl, choreographer, frameMetrics, 277 new FrameTracker.StatsLogWrapper(mDisplayResolutionTracker), 278 mTraceThresholdMissedFrames, mTraceThresholdFrameTimeMillis, 279 eventsListener); 280 } 281 282 @UiThread handleCujEvents( @uj.CujType int cuj, FrameTracker tracker, String action, @Reasons int reason)283 private void handleCujEvents( 284 @Cuj.CujType int cuj, FrameTracker tracker, String action, @Reasons int reason) { 285 // Clear the running and timeout tasks if the end / cancel was fired within the tracker. 286 // Or we might have memory leaks. 287 if (needRemoveTasks(action, reason)) { 288 removeTrackerIfCurrent(cuj, tracker, reason); 289 } 290 } 291 needRemoveTasks(String action, @Reasons int reason)292 private static boolean needRemoveTasks(String action, @Reasons int reason) { 293 final boolean badEnd = action.equals(ACTION_SESSION_END) && reason != REASON_END_NORMAL; 294 final boolean badCancel = action.equals(ACTION_SESSION_CANCEL) 295 && !(reason == REASON_CANCEL_NORMAL || reason == REASON_CANCEL_TIMEOUT); 296 return badEnd || badCancel; 297 } 298 299 /** 300 * @param cujType cuj type 301 * @return true if the cuj is under instrumenting, false otherwise. 302 */ isInstrumenting(@uj.CujType int cujType)303 public boolean isInstrumenting(@Cuj.CujType int cujType) { 304 synchronized (mLock) { 305 return mRunningTrackers.contains(cujType); 306 } 307 } 308 309 /** 310 * Begins a trace session. 311 * 312 * @param v an attached view. 313 * @param cujType the specific {@link Cuj.CujType}. 314 * @return boolean true if the tracker is started successfully, false otherwise. 315 */ begin(View v, @Cuj.CujType int cujType)316 public boolean begin(View v, @Cuj.CujType int cujType) { 317 try { 318 return begin(Configuration.Builder.withView(cujType, v)); 319 } catch (IllegalArgumentException ex) { 320 Log.d(TAG, "Build configuration failed!", ex); 321 return false; 322 } 323 } 324 325 /** 326 * Begins a trace session. 327 * 328 * @param builder the builder of the configurations for instrumenting the CUJ. 329 * @return boolean true if the tracker is begun successfully, false otherwise. 330 */ begin(@onNull Configuration.Builder builder)331 public boolean begin(@NonNull Configuration.Builder builder) { 332 try { 333 final Configuration config = builder.build(); 334 postEventLogToWorkerThread((unixNanos, elapsedNanos, realtimeNanos) -> { 335 EventLogTags.writeJankCujEventsBeginRequest( 336 config.mCujType, unixNanos, elapsedNanos, realtimeNanos, config.mTag); 337 }); 338 final TrackerResult result = new TrackerResult(); 339 final boolean success = config.getHandler().runWithScissors( 340 () -> result.mResult = beginInternal(config), EXECUTOR_TASK_TIMEOUT); 341 if (!success) { 342 Log.d(TAG, "begin failed due to timeout, CUJ=" + Cuj.getNameOfCuj(config.mCujType)); 343 return false; 344 } 345 return result.mResult; 346 } catch (IllegalArgumentException ex) { 347 Log.d(TAG, "Build configuration failed!", ex); 348 return false; 349 } 350 } 351 352 @UiThread beginInternal(@onNull Configuration conf)353 private boolean beginInternal(@NonNull Configuration conf) { 354 int cujType = conf.mCujType; 355 if (!shouldMonitor()) { 356 return false; 357 } 358 359 RunningTracker tracker = putTrackerIfNoCurrent(cujType, () -> 360 new RunningTracker( 361 conf, createFrameTracker(conf), () -> cancel(cujType, REASON_CANCEL_TIMEOUT))); 362 if (tracker == null) { 363 return false; 364 } 365 366 tracker.mTracker.begin(); 367 // Cancel the trace if we don't get an end() call in specified duration. 368 scheduleTimeoutAction(tracker.mConfig, tracker.mTimeoutAction); 369 370 return true; 371 } 372 373 /** 374 * Check if the monitoring is enabled and if it should be sampled. 375 */ 376 @VisibleForTesting shouldMonitor()377 public boolean shouldMonitor() { 378 return mEnabled && (ThreadLocalRandom.current().nextInt(mSamplingInterval) == 0); 379 } 380 381 @VisibleForTesting scheduleTimeoutAction(Configuration config, Runnable action)382 public void scheduleTimeoutAction(Configuration config, Runnable action) { 383 config.getHandler().postDelayed(action, config.mTimeout); 384 } 385 386 /** 387 * Ends a trace session. 388 * 389 * @param cujType the specific {@link Cuj.CujType}. 390 * @return boolean true if the tracker is ended successfully, false otherwise. 391 */ end(@uj.CujType int cujType)392 public boolean end(@Cuj.CujType int cujType) { 393 postEventLogToWorkerThread((unixNanos, elapsedNanos, realtimeNanos) -> { 394 EventLogTags.writeJankCujEventsEndRequest( 395 cujType, unixNanos, elapsedNanos, realtimeNanos); 396 }); 397 RunningTracker tracker = getTracker(cujType); 398 // Skip this call since we haven't started a trace yet. 399 if (tracker == null) { 400 return false; 401 } 402 try { 403 final TrackerResult result = new TrackerResult(); 404 final boolean success = tracker.mConfig.getHandler().runWithScissors( 405 () -> result.mResult = endInternal(tracker), EXECUTOR_TASK_TIMEOUT); 406 if (!success) { 407 Log.d(TAG, "end failed due to timeout, CUJ=" + Cuj.getNameOfCuj(cujType)); 408 return false; 409 } 410 return result.mResult; 411 } catch (IllegalArgumentException ex) { 412 Log.d(TAG, "Execute end task failed!", ex); 413 return false; 414 } 415 } 416 417 @UiThread endInternal(RunningTracker tracker)418 private boolean endInternal(RunningTracker tracker) { 419 if (removeTrackerIfCurrent(tracker, REASON_END_NORMAL)) { 420 return false; 421 } 422 tracker.mTracker.end(REASON_END_NORMAL); 423 return true; 424 } 425 426 /** 427 * Cancels the trace session. 428 * 429 * @return boolean true if the tracker is cancelled successfully, false otherwise. 430 */ cancel(@uj.CujType int cujType)431 public boolean cancel(@Cuj.CujType int cujType) { 432 postEventLogToWorkerThread((unixNanos, elapsedNanos, realtimeNanos) -> { 433 EventLogTags.writeJankCujEventsCancelRequest( 434 cujType, unixNanos, elapsedNanos, realtimeNanos); 435 }); 436 return cancel(cujType, REASON_CANCEL_NORMAL); 437 } 438 439 /** 440 * Cancels the trace session. 441 * 442 * @return boolean true if the tracker is cancelled successfully, false otherwise. 443 */ 444 @VisibleForTesting cancel(@uj.CujType int cujType, @Reasons int reason)445 public boolean cancel(@Cuj.CujType int cujType, @Reasons int reason) { 446 RunningTracker tracker = getTracker(cujType); 447 // Skip this call since we haven't started a trace yet. 448 if (tracker == null) { 449 return false; 450 } 451 try { 452 final TrackerResult result = new TrackerResult(); 453 final boolean success = tracker.mConfig.getHandler().runWithScissors( 454 () -> result.mResult = cancelInternal(tracker, reason), EXECUTOR_TASK_TIMEOUT); 455 if (!success) { 456 Log.d(TAG, "cancel failed due to timeout, CUJ=" + Cuj.getNameOfCuj(cujType)); 457 return false; 458 } 459 return result.mResult; 460 } catch (IllegalArgumentException ex) { 461 Log.d(TAG, "Execute cancel task failed!", ex); 462 return false; 463 } 464 } 465 466 @UiThread cancelInternal(RunningTracker tracker, @Reasons int reason)467 private boolean cancelInternal(RunningTracker tracker, @Reasons int reason) { 468 if (removeTrackerIfCurrent(tracker, reason)) { 469 return false; 470 } 471 tracker.mTracker.cancel(reason); 472 return true; 473 } 474 475 @UiThread putTrackerIfNoCurrent( @uj.CujType int cuj, Supplier<RunningTracker> supplier)476 private RunningTracker putTrackerIfNoCurrent( 477 @Cuj.CujType int cuj, Supplier<RunningTracker> supplier) { 478 synchronized (mLock) { 479 if (mRunningTrackers.contains(cuj)) { 480 return null; 481 } 482 483 RunningTracker tracker = supplier.get(); 484 if (tracker == null) { 485 return null; 486 } 487 488 mRunningTrackers.put(cuj, tracker); 489 if (mDebugOverlay != null) { 490 mDebugOverlay.onTrackerAdded(cuj, tracker); 491 } 492 493 return tracker; 494 } 495 } 496 getTracker(@uj.CujType int cuj)497 private RunningTracker getTracker(@Cuj.CujType int cuj) { 498 synchronized (mLock) { 499 return mRunningTrackers.get(cuj); 500 } 501 } 502 503 /** 504 * @return {@code true} if another tracker is current 505 */ 506 @UiThread removeTrackerIfCurrent(RunningTracker tracker, int reason)507 private boolean removeTrackerIfCurrent(RunningTracker tracker, int reason) { 508 return removeTrackerIfCurrent(tracker.mConfig.mCujType, tracker.mTracker, reason); 509 } 510 511 /** 512 * @return {@code true} if another tracker is current 513 */ 514 @UiThread removeTrackerIfCurrent(@uj.CujType int cuj, FrameTracker tracker, int reason)515 private boolean removeTrackerIfCurrent(@Cuj.CujType int cuj, FrameTracker tracker, int reason) { 516 synchronized (mLock) { 517 RunningTracker running = mRunningTrackers.get(cuj); 518 if (running == null || running.mTracker != tracker) { 519 return true; 520 } 521 522 running.mConfig.getHandler().removeCallbacks(running.mTimeoutAction); 523 mRunningTrackers.remove(cuj); 524 if (mDebugOverlay != null) { 525 mDebugOverlay.onTrackerRemoved(cuj, reason, mRunningTrackers); 526 } 527 return false; 528 } 529 } 530 531 @WorkerThread 532 @VisibleForTesting updateProperties(DeviceConfig.Properties properties)533 public void updateProperties(DeviceConfig.Properties properties) { 534 for (String property : properties.getKeyset()) { 535 switch (property) { 536 case SETTINGS_SAMPLING_INTERVAL_KEY -> 537 mSamplingInterval = properties.getInt(property, DEFAULT_SAMPLING_INTERVAL); 538 case SETTINGS_THRESHOLD_MISSED_FRAMES_KEY -> 539 mTraceThresholdMissedFrames = 540 properties.getInt(property, DEFAULT_TRACE_THRESHOLD_MISSED_FRAMES); 541 case SETTINGS_THRESHOLD_FRAME_TIME_MILLIS_KEY -> 542 mTraceThresholdFrameTimeMillis = 543 properties.getInt(property, DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS); 544 case SETTINGS_ENABLED_KEY -> 545 mEnabled = properties.getBoolean(property, DEFAULT_ENABLED); 546 case SETTINGS_DEBUG_OVERLAY_ENABLED_KEY -> { 547 // Never allow the debug overlay to be used on user builds 548 boolean debugOverlayEnabled = Build.IS_DEBUGGABLE 549 && properties.getBoolean(property, DEFAULT_DEBUG_OVERLAY_ENABLED); 550 if (debugOverlayEnabled && mDebugOverlay == null) { 551 mDebugOverlay = new InteractionMonitorDebugOverlay( 552 mLock, mDebugBgColor, mDebugYOffset); 553 } else if (!debugOverlayEnabled && mDebugOverlay != null) { 554 mDebugOverlay.dispose(); 555 mDebugOverlay = null; 556 } 557 } 558 default -> Log.w(TAG, "Got a change event for an unknown property: " 559 + property + " => " + properties.getString(property, "")); 560 } 561 } 562 } 563 564 /** 565 * A helper method to translate interaction type to CUJ name. 566 * 567 * @param interactionType the interaction type defined in AtomsProto.java 568 * @return the name of the interaction type 569 * @deprecated use {@link Cuj#getNameOfInteraction(int)} 570 */ 571 @Deprecated getNameOfInteraction(int interactionType)572 public static String getNameOfInteraction(int interactionType) { 573 return Cuj.getNameOfInteraction(interactionType); 574 } 575 576 /** 577 * A helper method to translate CUJ type to CUJ name. 578 * 579 * @param cujType the cuj type defined in this file 580 * @return the name of the cuj type 581 * @deprecated use {@link Cuj#getNameOfCuj(int)} 582 */ 583 @Deprecated getNameOfCuj(int cujType)584 public static String getNameOfCuj(int cujType) { 585 return Cuj.getNameOfCuj(cujType); 586 } 587 588 /** 589 * Configures the debug overlay used for displaying interaction names on the screen while they 590 * occur. 591 * 592 * @param bgColor the background color of the box used to display the CUJ names 593 * @param yOffset number between 0 and 1 to indicate where the top of the box should be relative 594 * to the height of the screen 595 */ configDebugOverlay(@olorInt int bgColor, double yOffset)596 public void configDebugOverlay(@ColorInt int bgColor, double yOffset) { 597 mDebugBgColor = bgColor; 598 mDebugYOffset = yOffset; 599 } 600 postEventLogToWorkerThread(TimeFunction logFunction)601 private void postEventLogToWorkerThread(TimeFunction logFunction) { 602 final Instant now = Instant.now(); 603 final long unixNanos = TimeUnit.NANOSECONDS.convert(now.getEpochSecond(), TimeUnit.SECONDS) 604 + now.getNano(); 605 final long elapsedNanos = SystemClock.elapsedRealtimeNanos(); 606 final long realtimeNanos = SystemClock.uptimeNanos(); 607 608 mWorker.post(() -> logFunction.invoke(unixNanos, elapsedNanos, realtimeNanos)); 609 } 610 611 private static class TrackerResult { 612 private boolean mResult; 613 } 614 615 /** 616 * Configurations used while instrumenting the CUJ. <br/> 617 * <b>It may refer to an attached view, don't use static reference for any purpose.</b> 618 */ 619 public static class Configuration { 620 private final View mView; 621 private final Context mContext; 622 private final long mTimeout; 623 private final String mTag; 624 private final String mSessionName; 625 private final boolean mSurfaceOnly; 626 private final SurfaceControl mSurfaceControl; 627 private final @Cuj.CujType int mCujType; 628 private final boolean mDeferMonitor; 629 private final Handler mHandler; 630 631 /** 632 * A builder for building Configuration. {@link #setView(View)} is essential 633 * if {@link #setSurfaceOnly(boolean)} is not set, otherwise both 634 * {@link #setSurfaceControl(SurfaceControl)} and {@link #setContext(Context)} 635 * are necessary<br/> 636 * <b>It may refer to an attached view, don't use static reference for any purpose.</b> 637 */ 638 public static class Builder { 639 private View mAttrView = null; 640 private Context mAttrContext = null; 641 private long mAttrTimeout = DEFAULT_TIMEOUT_MS; 642 private String mAttrTag = ""; 643 private boolean mAttrSurfaceOnly; 644 private SurfaceControl mAttrSurfaceControl; 645 private final @Cuj.CujType int mAttrCujType; 646 private boolean mAttrDeferMonitor = true; 647 648 /** 649 * Creates a builder which instruments only surface. 650 * @param cuj The enum defined in {@link Cuj.CujType}. 651 * @param context context 652 * @param surfaceControl surface control 653 * @return builder 654 */ withSurface(@uj.CujType int cuj, @NonNull Context context, @NonNull SurfaceControl surfaceControl)655 public static Builder withSurface(@Cuj.CujType int cuj, @NonNull Context context, 656 @NonNull SurfaceControl surfaceControl) { 657 return new Builder(cuj) 658 .setContext(context) 659 .setSurfaceControl(surfaceControl) 660 .setSurfaceOnly(true); 661 } 662 663 /** 664 * Creates a builder which instruments both surface and view. 665 * @param cuj The enum defined in {@link Cuj.CujType}. 666 * @param view view 667 * @return builder 668 */ withView(@uj.CujType int cuj, @NonNull View view)669 public static Builder withView(@Cuj.CujType int cuj, @NonNull View view) { 670 return new Builder(cuj) 671 .setView(view) 672 .setContext(view.getContext()); 673 } 674 Builder(@uj.CujType int cuj)675 private Builder(@Cuj.CujType int cuj) { 676 mAttrCujType = cuj; 677 } 678 679 /** 680 * Specifies a view, must be set if {@link #setSurfaceOnly(boolean)} is set to false. 681 * @param view an attached view 682 * @return builder 683 */ setView(@onNull View view)684 private Builder setView(@NonNull View view) { 685 mAttrView = view; 686 return this; 687 } 688 689 /** 690 * @param timeout duration to cancel the instrumentation in ms 691 * @return builder 692 */ setTimeout(long timeout)693 public Builder setTimeout(long timeout) { 694 mAttrTimeout = timeout; 695 return this; 696 } 697 698 /** 699 * @param tag The postfix of the CUJ in the output trace. 700 * It provides a brief description for the CUJ like the concrete class 701 * who is dealing with the CUJ or the important state with the CUJ, etc. 702 * @return builder 703 */ setTag(@onNull String tag)704 public Builder setTag(@NonNull String tag) { 705 mAttrTag = tag; 706 return this; 707 } 708 709 /** 710 * Indicates if only instrument with surface, 711 * if true, must also setup with {@link #setContext(Context)} 712 * and {@link #setSurfaceControl(SurfaceControl)}. 713 * @param surfaceOnly true if only instrument with surface, false otherwise 714 * @return builder Surface only builder. 715 */ setSurfaceOnly(boolean surfaceOnly)716 private Builder setSurfaceOnly(boolean surfaceOnly) { 717 mAttrSurfaceOnly = surfaceOnly; 718 return this; 719 } 720 721 /** 722 * Specifies a context, must set if {@link #setSurfaceOnly(boolean)} is set. 723 */ setContext(Context context)724 private Builder setContext(Context context) { 725 mAttrContext = context; 726 return this; 727 } 728 729 /** 730 * Specifies a surface control, must be set if {@link #setSurfaceOnly(boolean)} is set. 731 */ setSurfaceControl(SurfaceControl surfaceControl)732 private Builder setSurfaceControl(SurfaceControl surfaceControl) { 733 mAttrSurfaceControl = surfaceControl; 734 return this; 735 } 736 737 /** 738 * Indicates if the instrument should be deferred to the next frame. 739 * @param defer true if the instrument should be deferred to the next frame. 740 * @return builder 741 */ setDeferMonitorForAnimationStart(boolean defer)742 public Builder setDeferMonitorForAnimationStart(boolean defer) { 743 mAttrDeferMonitor = defer; 744 return this; 745 } 746 747 /** 748 * Builds the {@link Configuration} instance 749 * @return the instance of {@link Configuration} 750 * @throws IllegalArgumentException if any invalid attribute is set 751 */ build()752 public Configuration build() throws IllegalArgumentException { 753 return new Configuration( 754 mAttrCujType, mAttrView, mAttrTag, mAttrTimeout, 755 mAttrSurfaceOnly, mAttrContext, mAttrSurfaceControl, 756 mAttrDeferMonitor); 757 } 758 } 759 Configuration(@uj.CujType int cuj, View view, @NonNull String tag, long timeout, boolean surfaceOnly, Context context, SurfaceControl surfaceControl, boolean deferMonitor)760 private Configuration(@Cuj.CujType int cuj, View view, @NonNull String tag, long timeout, 761 boolean surfaceOnly, Context context, SurfaceControl surfaceControl, 762 boolean deferMonitor) { 763 mCujType = cuj; 764 mTag = tag; 765 mSessionName = generateSessionName(Cuj.getNameOfCuj(cuj), tag); 766 mTimeout = timeout; 767 mView = view; 768 mSurfaceOnly = surfaceOnly; 769 mContext = context != null 770 ? context 771 : (view != null ? view.getContext().getApplicationContext() : null); 772 mSurfaceControl = surfaceControl; 773 mDeferMonitor = deferMonitor; 774 validate(); 775 mHandler = mSurfaceOnly ? mContext.getMainThreadHandler() : mView.getHandler(); 776 } 777 778 @VisibleForTesting generateSessionName( @onNull String cujName, @NonNull String cujPostfix)779 public static String generateSessionName( 780 @NonNull String cujName, @NonNull String cujPostfix) { 781 final boolean hasPostfix = !TextUtils.isEmpty(cujPostfix); 782 if (hasPostfix) { 783 final int remaining = MAX_LENGTH_SESSION_NAME - cujName.length(); 784 if (cujPostfix.length() > remaining) { 785 cujPostfix = cujPostfix.substring(0, remaining - 3).concat("..."); 786 } 787 } 788 // The max length of the whole string should be: 789 // 105 with postfix, 83 without postfix 790 return hasPostfix 791 ? TextUtils.formatSimple("J<%s::%s>", cujName, cujPostfix) 792 : TextUtils.formatSimple("J<%s>", cujName); 793 } 794 validate()795 private void validate() { 796 boolean shouldThrow = false; 797 final StringBuilder msg = new StringBuilder(); 798 799 if (mTag == null) { 800 shouldThrow = true; 801 msg.append("Invalid tag; "); 802 } 803 if (mTimeout < 0) { 804 shouldThrow = true; 805 msg.append("Invalid timeout value; "); 806 } 807 if (mSurfaceOnly) { 808 if (mContext == null) { 809 shouldThrow = true; 810 msg.append("Must pass in a context if only instrument surface; "); 811 } 812 if (mSurfaceControl == null || !mSurfaceControl.isValid()) { 813 shouldThrow = true; 814 msg.append("Must pass in a valid surface control if only instrument surface; "); 815 } 816 } else { 817 if (!hasValidView()) { 818 shouldThrow = true; 819 boolean attached = false; 820 boolean hasViewRoot = false; 821 boolean hasRenderer = false; 822 if (mView != null) { 823 attached = mView.isAttachedToWindow(); 824 hasViewRoot = mView.getViewRootImpl() != null; 825 hasRenderer = mView.getThreadedRenderer() != null; 826 } 827 String err = "invalid view: view=" + mView + ", attached=" + attached 828 + ", hasViewRoot=" + hasViewRoot + ", hasRenderer=" + hasRenderer; 829 msg.append(err); 830 } 831 } 832 if (shouldThrow) { 833 throw new IllegalArgumentException(msg.toString()); 834 } 835 } 836 hasValidView()837 boolean hasValidView() { 838 return mSurfaceOnly 839 || (mView != null && mView.isAttachedToWindow() 840 && mView.getViewRootImpl() != null && mView.getThreadedRenderer() != null); 841 } 842 843 /** 844 * @return true if only instrumenting surface, false otherwise 845 */ isSurfaceOnly()846 public boolean isSurfaceOnly() { 847 return mSurfaceOnly; 848 } 849 850 /** 851 * @return the surafce control which is instrumenting 852 */ getSurfaceControl()853 public SurfaceControl getSurfaceControl() { 854 return mSurfaceControl; 855 } 856 857 /** 858 * @return a view which is attached to the view tree. 859 */ 860 @VisibleForTesting getView()861 public View getView() { 862 return mView; 863 } 864 865 /** 866 * @return true if the monitoring should be deferred to the next frame, false otherwise. 867 */ shouldDeferMonitor()868 public boolean shouldDeferMonitor() { 869 return mDeferMonitor; 870 } 871 872 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) getHandler()873 public Handler getHandler() { 874 return mHandler; 875 } 876 877 /** 878 * @return the ID of the display this interaction in on. 879 */ 880 @VisibleForTesting getDisplayId()881 public int getDisplayId() { 882 return (mSurfaceOnly ? mContext : mView.getContext()).getDisplayId(); 883 } 884 getSessionName()885 public String getSessionName() { 886 return mSessionName; 887 } 888 getStatsdInteractionType()889 public int getStatsdInteractionType() { 890 return Cuj.getStatsdInteractionType(mCujType); 891 } 892 893 /** Describes whether the measurement from this session should be written to statsd. */ logToStatsd()894 public boolean logToStatsd() { 895 return Cuj.logToStatsd(mCujType); 896 } 897 getPerfettoTrigger()898 public String getPerfettoTrigger() { 899 return TextUtils.formatSimple( 900 "com.android.telemetry.interaction-jank-monitor-%d", mCujType); 901 } 902 getCujType()903 public @Cuj.CujType int getCujType() { 904 return mCujType; 905 } 906 } 907 908 @FunctionalInterface 909 private interface TimeFunction { invoke(long unixNanos, long elapsedNanos, long realtimeNanos)910 void invoke(long unixNanos, long elapsedNanos, long realtimeNanos); 911 } 912 913 static class RunningTracker { 914 public final Configuration mConfig; 915 public final FrameTracker mTracker; 916 public final Runnable mTimeoutAction; 917 RunningTracker(Configuration config, FrameTracker tracker, Runnable timeoutAction)918 RunningTracker(Configuration config, FrameTracker tracker, Runnable timeoutAction) { 919 this.mConfig = config; 920 this.mTracker = tracker; 921 this.mTimeoutAction = timeoutAction; 922 } 923 } 924 } 925