1/* 2 * Copyright (C) 2017 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 17package android.support.v4.app; 18 19 20import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; 21 22import android.app.Activity; 23import android.os.Build; 24import android.os.Handler; 25import android.os.HandlerThread; 26import android.support.annotation.IntDef; 27import android.support.annotation.NonNull; 28import android.support.annotation.Nullable; 29import android.support.annotation.RequiresApi; 30import android.support.annotation.RestrictTo; 31import android.util.SparseIntArray; 32import android.view.Window; 33 34import java.lang.annotation.Retention; 35import java.lang.annotation.RetentionPolicy; 36import java.lang.ref.WeakReference; 37import java.util.ArrayList; 38 39/** 40 * This class can be used to record and return data about per-frame durations. It returns those 41 * results in an array per metric type, with the results indicating how many samples were 42 * recorded for each duration value. The details of the durations data are described in 43 * {@link #getMetrics()}. 44 * <p> 45 * For more information on the various metrics tracked, see the documentation for the 46 * <a href="https://developer.android.com/reference/android/view/FrameMetrics.html">FrameMetrics 47 * </a> API added in API 24 as well as the 48 * <a href="https://developer.android.com/studio/profile/dev-options-rendering.html">GPU Profiling 49 * guide</a>. 50 */ 51public class FrameMetricsAggregator { 52 53 private static final String TAG = "FrameMetrics"; 54 private static final boolean DBG = false; 55 56 /** 57 * The index in the metrics array where the data for {@link #TOTAL_DURATION} 58 * is stored. 59 * @see #getMetrics() 60 */ 61 public static final int TOTAL_INDEX = 0; 62 /** 63 * The index in the metrics array where the data for {@link #INPUT_DURATION} 64 * is stored. 65 * @see #getMetrics() 66 */ 67 public static final int INPUT_INDEX = 1; 68 /** 69 * The index in the metrics array where the data for {@link #LAYOUT_MEASURE_DURATION} 70 * is stored. 71 * @see #getMetrics() 72 */ 73 public static final int LAYOUT_MEASURE_INDEX = 2; 74 /** 75 * The index in the metrics array where the data for {@link #DRAW_DURATION} 76 * is stored. 77 * @see #getMetrics() 78 */ 79 public static final int DRAW_INDEX = 3; 80 /** 81 * The index in the metrics array where the data for {@link #SYNC_DURATION} 82 * is stored. 83 * @see #getMetrics() 84 */ 85 public static final int SYNC_INDEX = 4; 86 /** 87 * The index in the metrics array where the data for {@link #SYNC_DURATION} 88 * is stored. 89 * @see #getMetrics() 90 */ 91 public static final int COMMAND_INDEX = 5; 92 /** 93 * The index in the metrics array where the data for {@link #COMMAND_DURATION} 94 * is stored. 95 * @see #getMetrics() 96 */ 97 public static final int SWAP_INDEX = 6; 98 /** 99 * The index in the metrics array where the data for {@link #DELAY_DURATION} 100 * is stored. 101 * @see #getMetrics() 102 */ 103 public static final int DELAY_INDEX = 7; 104 /** 105 * The index in the metrics array where the data for {@link #ANIMATION_DURATION} 106 * is stored. 107 * @see #getMetrics() 108 */ 109 public static final int ANIMATION_INDEX = 8; 110 private static final int LAST_INDEX = 8; 111 112 /** 113 * A flag indicating that the metrics should track the total duration. This 114 * flag may be OR'd with the other flags here when calling {@link #FrameMetricsAggregator(int)} 115 * to indicate all of the metrics that should be tracked for that activity. 116 */ 117 public static final int TOTAL_DURATION = 1 << TOTAL_INDEX; 118 /** 119 * A flag indicating that the metrics should track the input duration. This 120 * flag may be OR'd with the other flags here when calling {@link #FrameMetricsAggregator(int)} 121 * to indicate all of the metrics that should be tracked for that activity. 122 */ 123 public static final int INPUT_DURATION = 1 << INPUT_INDEX; 124 /** 125 * A flag indicating that the metrics should track the layout duration. This 126 * flag may be OR'd with the other flags here when calling {@link #FrameMetricsAggregator(int)} 127 * to indicate all of the metrics that should be tracked for that activity. 128 */ 129 public static final int LAYOUT_MEASURE_DURATION = 1 << LAYOUT_MEASURE_INDEX; 130 /** 131 * A flag indicating that the metrics should track the draw duration. This 132 * flag may be OR'd with the other flags here when calling {@link #FrameMetricsAggregator(int)} 133 * to indicate all of the metrics that should be tracked for that activity. 134 */ 135 public static final int DRAW_DURATION = 1 << DRAW_INDEX; 136 /** 137 * A flag indicating that the metrics should track the sync duration. This 138 * flag may be OR'd with the other flags here when calling {@link #FrameMetricsAggregator(int)} 139 * to indicate all of the metrics that should be tracked for that activity. 140 */ 141 public static final int SYNC_DURATION = 1 << SYNC_INDEX; 142 /** 143 * A flag indicating that the metrics should track the command duration. This 144 * flag may be OR'd with the other flags here when calling {@link #FrameMetricsAggregator(int)} 145 * to indicate all of the metrics that should be tracked for that activity. 146 */ 147 public static final int COMMAND_DURATION = 1 << COMMAND_INDEX; 148 /** 149 * A flag indicating that the metrics should track the swap duration. This 150 * flag may be OR'd with the other flags here when calling {@link #FrameMetricsAggregator(int)} 151 * to indicate all of the metrics that should be tracked for that activity. 152 */ 153 public static final int SWAP_DURATION = 1 << SWAP_INDEX; 154 /** 155 * A flag indicating that the metrics should track the delay duration. This 156 * flag may be OR'd with the other flags here when calling {@link #FrameMetricsAggregator(int)} 157 * to indicate all of the metrics that should be tracked for that activity. 158 */ 159 public static final int DELAY_DURATION = 1 << DELAY_INDEX; 160 /** 161 * A flag indicating that the metrics should track the animation duration. This 162 * flag may be OR'd with the other flags here when calling {@link #FrameMetricsAggregator(int)} 163 * to indicate all of the metrics that should be tracked for that activity. 164 */ 165 public static final int ANIMATION_DURATION = 1 << ANIMATION_INDEX; 166 /** 167 * A flag indicating that the metrics should track all durations. This is 168 * a shorthand for OR'ing all of the duration flags. This 169 * flag may be OR'd with the other flags here when calling {@link #FrameMetricsAggregator(int)} 170 * to indicate the metrics that should be tracked for that activity. 171 */ 172 public static final int EVERY_DURATION = 0x1ff; 173 174 private FrameMetricsBaseImpl mInstance; 175 176 /** @hide */ 177 @RestrictTo(LIBRARY_GROUP) 178 @Retention(RetentionPolicy.SOURCE) 179 @IntDef( 180 flag = true, 181 value = { 182 TOTAL_DURATION, 183 INPUT_DURATION, 184 LAYOUT_MEASURE_DURATION, 185 DRAW_DURATION, 186 SYNC_DURATION, 187 COMMAND_DURATION, 188 SWAP_DURATION, 189 DELAY_DURATION, 190 ANIMATION_DURATION, 191 EVERY_DURATION 192 }) 193 public @interface MetricType {} 194 195 /** 196 * Constructs a FrameMetricsAggregator object that will track {@link #TOTAL_DURATION} 197 * metrics. If more fine-grained metrics are needed, use {@link #FrameMetricsAggregator(int)} 198 * instead. 199 */ 200 public FrameMetricsAggregator() { 201 this(TOTAL_DURATION); 202 } 203 204 /** 205 * Constructs a FrameMetricsAggregator object that will track the metrics specified bty 206 * {@code metricTypeFlags}, which is a value derived by OR'ing together metrics constants 207 * such as {@link #TOTAL_DURATION} to specify all metrics that should be tracked. For example, 208 * {@code TOTAL_DURATION | DRAW_DURATION} will track both the total and draw durations 209 * for every frame. 210 * 211 * @param metricTypeFlags A bitwise collection of flags indicating which metrics should 212 * be recorded. 213 */ 214 public FrameMetricsAggregator(@MetricType int metricTypeFlags) { 215 if (Build.VERSION.SDK_INT >= 24) { 216 mInstance = new FrameMetricsApi24Impl(metricTypeFlags); 217 } else { 218 mInstance = new FrameMetricsBaseImpl(); 219 } 220 } 221 222 /** 223 * Starts recording frame metrics for the given activity. 224 * 225 * @param activity The Activity object which will have its metrics measured. 226 */ 227 public void add(@NonNull Activity activity) { 228 mInstance.add(activity); 229 } 230 231 /** 232 * Stops recording metrics for {@code activity} and returns the collected metrics so far. 233 * Recording will continue if there are still other activities being tracked. Calling 234 * remove() does not reset the metrics array; you must call {@link #reset()} to clear the 235 * data. 236 * 237 * @param activity The Activity to stop tracking metrics for. 238 * @return An array whose index refers to the type of metric stored in that item's 239 * SparseIntArray object, e.g., data for {@code TOTAL_DURATION} is stored in 240 * the {@code [TOTAL_INDEX]} item. 241 * @see #getMetrics() 242 */ 243 @Nullable 244 public SparseIntArray[] remove(@NonNull Activity activity) { 245 return mInstance.remove(activity); 246 } 247 248 /** 249 * Stops recording metrics for all Activities currently being tracked. Like {@link 250 * #remove(Activity)}, this method returns the currently-collected metrics. Calling 251 * stop() does not reset the metrics array; you must call {@link #reset()} to clear the 252 * data. 253 * 254 * @return An array whose index refers to the type of metric stored in that item's 255 * SparseIntArray object, e.g., data for {@code TOTAL_DURATION} is stored in 256 * the {@code [TOTAL_INDEX]} item. 257 * @see #remove(Activity) 258 * @see #getMetrics() 259 */ 260 @Nullable 261 public SparseIntArray[] stop() { 262 return mInstance.stop(); 263 } 264 265 /** 266 * Resets the metrics data and returns the currently-collected metrics. 267 * 268 * @return An array whose index refers to the type of metric stored in that item's 269 * SparseIntArray object, e.g., data for {@code TOTAL_DURATION} is stored in 270 * the {@code [TOTAL_INDEX]} item. 271 * @see #getMetrics() 272 */ 273 @Nullable 274 public SparseIntArray[] reset() { 275 return mInstance.reset(); 276 } 277 278 /** 279 * Returns the currently-collected metrics in an array of SparseIntArray objects. 280 * The index of the array indicates which metric's data is stored in that 281 * SparseIntArray object. For example, results for total duration will be in 282 * the {@code [TOTAL_INDEX]} item. 283 * <p> 284 * The return value may be null if no metrics were tracked. This is especially true on releases 285 * earlier than API 24, as the FrameMetrics system does not exist on these earlier release. 286 * If the return value is not null, any of the objects at a given index in the array 287 * may still be null, which indicates that data was not being tracked for that type of metric. 288 * For example, if the FrameMetricsAggregator was created with a call to 289 * {@code new FrameMetricsAggregator(TOTAL_DURATION | DRAW_DURATION)}, then the SparseIntArray 290 * at index {@code INPUT_INDEX} will be null. 291 * <p> 292 * For a given non-null SparseIntArray, the results stored are the number of samples at 293 * each millisecond value (rounded). For example, if a data sample consisted of total 294 * durations of 5.1ms, 5.8ms, 6.1ms, and 8.2ms, the SparseIntArray at {@code [TOTAL_DURATION]} 295 * would have key-value pairs (5, 1), (6, 2), (8, 1). 296 * 297 * @return An array whose index refers to the type of metric stored in that item's 298 * SparseIntArray object, e.g., data for {@code TOTAL_DURATION} is stored in 299 * the {@code [TOTAL_INDEX]} item. 300 */ 301 @Nullable 302 public SparseIntArray[] getMetrics() { 303 return mInstance.getMetrics(); 304 } 305 306 /** 307 * Base implementation noops everything - there's no data to return on pre-API24 releases. 308 */ 309 private static class FrameMetricsBaseImpl { 310 311 public void add(Activity activity) { 312 } 313 314 public SparseIntArray[] remove(Activity activity) { 315 return null; 316 } 317 318 public SparseIntArray[] stop() { 319 return null; 320 } 321 322 public SparseIntArray[] getMetrics() { 323 return null; 324 } 325 326 public SparseIntArray[] reset() { 327 return null; 328 } 329 } 330 331 @RequiresApi(24) 332 private static class FrameMetricsApi24Impl extends FrameMetricsBaseImpl { 333 334 private static final int NANOS_PER_MS = 1000000; 335 // rounding value adds half a millisecond, for rounding to nearest ms 336 private static final int NANOS_ROUNDING_VALUE = NANOS_PER_MS / 2; 337 private int mTrackingFlags; 338 private SparseIntArray[] mMetrics = new SparseIntArray[LAST_INDEX + 1]; 339 private ArrayList<WeakReference<Activity>> mActivities = new ArrayList<>(); 340 private static HandlerThread sHandlerThread = null; 341 private static Handler sHandler = null; 342 343 FrameMetricsApi24Impl(int trackingFlags) { 344 mTrackingFlags = trackingFlags; 345 } 346 347 Window.OnFrameMetricsAvailableListener mListener = 348 new Window.OnFrameMetricsAvailableListener() { 349 @Override 350 public void onFrameMetricsAvailable(Window window, 351 android.view.FrameMetrics frameMetrics, int dropCountSinceLastInvocation) { 352 if ((mTrackingFlags & TOTAL_DURATION) != 0) { 353 addDurationItem(mMetrics[TOTAL_INDEX], 354 frameMetrics.getMetric(android.view.FrameMetrics.TOTAL_DURATION)); 355 } 356 if ((mTrackingFlags & INPUT_DURATION) != 0) { 357 addDurationItem(mMetrics[INPUT_INDEX], 358 frameMetrics.getMetric( 359 android.view.FrameMetrics.INPUT_HANDLING_DURATION)); 360 } 361 if ((mTrackingFlags & LAYOUT_MEASURE_DURATION) != 0) { 362 addDurationItem(mMetrics[LAYOUT_MEASURE_INDEX], 363 frameMetrics.getMetric( 364 android.view.FrameMetrics.LAYOUT_MEASURE_DURATION)); 365 } 366 if ((mTrackingFlags & DRAW_DURATION) != 0) { 367 addDurationItem(mMetrics[DRAW_INDEX], 368 frameMetrics.getMetric(android.view.FrameMetrics.DRAW_DURATION)); 369 } 370 if ((mTrackingFlags & SYNC_DURATION) != 0) { 371 addDurationItem(mMetrics[SYNC_INDEX], 372 frameMetrics.getMetric(android.view.FrameMetrics.SYNC_DURATION)); 373 } 374 if ((mTrackingFlags & SWAP_DURATION) != 0) { 375 addDurationItem(mMetrics[SWAP_INDEX], 376 frameMetrics.getMetric( 377 android.view.FrameMetrics.SWAP_BUFFERS_DURATION)); 378 } 379 if ((mTrackingFlags & COMMAND_DURATION) != 0) { 380 addDurationItem(mMetrics[COMMAND_INDEX], 381 frameMetrics.getMetric( 382 android.view.FrameMetrics.COMMAND_ISSUE_DURATION)); 383 } 384 if ((mTrackingFlags & DELAY_DURATION) != 0) { 385 addDurationItem(mMetrics[DELAY_INDEX], 386 frameMetrics.getMetric( 387 android.view.FrameMetrics.UNKNOWN_DELAY_DURATION)); 388 } 389 if ((mTrackingFlags & ANIMATION_DURATION) != 0) { 390 addDurationItem(mMetrics[ANIMATION_INDEX], 391 frameMetrics.getMetric( 392 android.view.FrameMetrics.ANIMATION_DURATION)); 393 } 394 } 395 }; 396 397 void addDurationItem(SparseIntArray buckets, long duration) { 398 if (buckets != null) { 399 int durationMs = (int) ((duration + NANOS_ROUNDING_VALUE) / NANOS_PER_MS); 400 if (duration >= 0) { 401 // ignore values < 0; something must have gone wrong 402 int oldValue = buckets.get(durationMs); 403 buckets.put(durationMs, (oldValue + 1)); 404 } 405 } 406 } 407 408 @Override 409 public void add(Activity activity) { 410 if (sHandlerThread == null) { 411 sHandlerThread = new HandlerThread("FrameMetricsAggregator"); 412 sHandlerThread.start(); 413 sHandler = new Handler(sHandlerThread.getLooper()); 414 } 415 for (int i = 0; i <= LAST_INDEX; ++i) { 416 if (mMetrics[i] == null && (mTrackingFlags & (1 << i)) != 0) { 417 mMetrics[i] = new SparseIntArray(); 418 } 419 } 420 activity.getWindow().addOnFrameMetricsAvailableListener(mListener, sHandler); 421 mActivities.add(new WeakReference<>(activity)); 422 } 423 424 @Override 425 public SparseIntArray[] remove(Activity activity) { 426 for (WeakReference<Activity> activityRef : mActivities) { 427 if (activityRef.get() == activity) { 428 mActivities.remove(activityRef); 429 break; 430 } 431 } 432 activity.getWindow().removeOnFrameMetricsAvailableListener(mListener); 433 return mMetrics; 434 } 435 436 @Override 437 public SparseIntArray[] stop() { 438 int size = mActivities.size(); 439 for (int i = size - 1; i >= 0; i--) { 440 WeakReference<Activity> ref = mActivities.get(i); 441 Activity activity = ref.get(); 442 if (ref.get() != null) { 443 activity.getWindow().removeOnFrameMetricsAvailableListener(mListener); 444 mActivities.remove(i); 445 } 446 } 447 return mMetrics; 448 } 449 450 @Override 451 public SparseIntArray[] getMetrics() { 452 return mMetrics; 453 } 454 455 @Override 456 public SparseIntArray[] reset() { 457 SparseIntArray[] returnVal = mMetrics; 458 mMetrics = new SparseIntArray[LAST_INDEX + 1]; 459 return returnVal; 460 } 461 462 } 463 464} 465