• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
getAnimatorRecorder(final String histogramName)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      */
AnimationFrameTimeHistogram(String histogramName)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      */
startRecording()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      */
endRecording()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 
Recorder()93         private Recorder() {
94             mAnimator.setTimeListener(this);
95         }
96 
startRecording()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          */
endRecording()108         private boolean endRecording() {
109             boolean succeeded = mAnimator.isStarted();
110             mAnimator.end();
111             return succeeded;
112         }
113 
getFrameTimesMs()114         private long[] getFrameTimesMs() {
115             return mFrameTimesMs;
116         }
117 
getFrameTimesCount()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          */
cleanUp()126         private void cleanUp() {
127             mFrameTimesMs = null;
128         }
129 
130         @Override
onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime)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 
nativeSaveHistogram(String histogramName, long[] frameTimesMs, int count)147     private native void nativeSaveHistogram(String histogramName, long[] frameTimesMs, int count);
148 }
149