1 // Copyright 2015 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.base; 6 7 import android.animation.Animator; 8 import android.animation.Animator.AnimatorListener; 9 import android.animation.AnimatorListenerAdapter; 10 import android.animation.TimeAnimator; 11 import android.animation.TimeAnimator.TimeListener; 12 import android.util.Log; 13 14 import org.chromium.base.annotations.MainDex; 15 16 /** 17 * Record Android animation frame rate and save it to UMA histogram. This is mainly for monitoring 18 * any jankiness of short Chrome Android animations. It is limited to few seconds of recording. 19 */ 20 @MainDex 21 public class AnimationFrameTimeHistogram { 22 private static final String TAG = "AnimationFrameTimeHistogram"; 23 private static final int MAX_FRAME_TIME_NUM = 600; // 10 sec on 60 fps. 24 25 private final Recorder mRecorder = new Recorder(); 26 private final String mHistogramName; 27 28 /** 29 * @param histogramName The histogram name that the recorded frame times will be saved. 30 * This must be also defined in histograms.xml 31 * @return An AnimatorListener instance that records frame time histogram on start and end 32 * automatically. 33 */ 34 public static AnimatorListener getAnimatorRecorder(final String histogramName) { 35 return new AnimatorListenerAdapter() { 36 private final AnimationFrameTimeHistogram mAnimationFrameTimeHistogram = 37 new AnimationFrameTimeHistogram(histogramName); 38 39 @Override 40 public void onAnimationStart(Animator animation) { 41 mAnimationFrameTimeHistogram.startRecording(); 42 } 43 44 @Override 45 public void onAnimationEnd(Animator animation) { 46 mAnimationFrameTimeHistogram.endRecording(); 47 } 48 49 @Override 50 public void onAnimationCancel(Animator animation) { 51 mAnimationFrameTimeHistogram.endRecording(); 52 } 53 }; 54 } 55 56 /** 57 * @param histogramName The histogram name that the recorded frame times will be saved. 58 * This must be also defined in histograms.xml 59 */ 60 public AnimationFrameTimeHistogram(String histogramName) { 61 mHistogramName = histogramName; 62 } 63 64 /** 65 * Start recording frame times. The recording can fail if it exceeds a few seconds. 66 */ 67 public void startRecording() { 68 mRecorder.startRecording(); 69 } 70 71 /** 72 * End recording and save it to histogram. It won't save histogram if the recording wasn't 73 * successful. 74 */ 75 public void endRecording() { 76 if (mRecorder.endRecording()) { 77 nativeSaveHistogram(mHistogramName, 78 mRecorder.getFrameTimesMs(), mRecorder.getFrameTimesCount()); 79 } 80 mRecorder.cleanUp(); 81 } 82 83 /** 84 * Record Android animation frame rate and return the result. 85 */ 86 private static class Recorder implements TimeListener { 87 // TODO(kkimlabs): If we can use in the future, migrate to Choreographer for minimal 88 // workload. 89 private final TimeAnimator mAnimator = new TimeAnimator(); 90 private long[] mFrameTimesMs; 91 private int mFrameTimesCount; 92 93 private Recorder() { 94 mAnimator.setTimeListener(this); 95 } 96 97 private void startRecording() { 98 assert !mAnimator.isRunning(); 99 mFrameTimesCount = 0; 100 mFrameTimesMs = new long[MAX_FRAME_TIME_NUM]; 101 mAnimator.start(); 102 } 103 104 /** 105 * @return Whether the recording was successful. If successful, the result is available via 106 * getFrameTimesNs and getFrameTimesCount. 107 */ 108 private boolean endRecording() { 109 boolean succeeded = mAnimator.isStarted(); 110 mAnimator.end(); 111 return succeeded; 112 } 113 114 private long[] getFrameTimesMs() { 115 return mFrameTimesMs; 116 } 117 118 private int getFrameTimesCount() { 119 return mFrameTimesCount; 120 } 121 122 /** 123 * Deallocates the temporary buffer to record frame times. Must be called after ending 124 * the recording and getting the result. 125 */ 126 private void cleanUp() { 127 mFrameTimesMs = null; 128 } 129 130 @Override 131 public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) { 132 if (mFrameTimesCount == mFrameTimesMs.length) { 133 mAnimator.end(); 134 cleanUp(); 135 Log.w(TAG, "Animation frame time recording reached the maximum number. It's either" 136 + "the animation took too long or recording end is not called."); 137 return; 138 } 139 140 // deltaTime is 0 for the first frame. 141 if (deltaTime > 0) { 142 mFrameTimesMs[mFrameTimesCount++] = deltaTime; 143 } 144 } 145 } 146 147 private native void nativeSaveHistogram(String histogramName, long[] frameTimesMs, int count); 148 } 149