summaryrefslogtreecommitdiffstats
path: root/base/android/java/src/org/chromium/base/AnimationFrameTimeHistogram.java
blob: 1cd2acfb55beb557f72bc307f5ce912d9c41e4e9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.base;

import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorListenerAdapter;
import android.animation.TimeAnimator;
import android.animation.TimeAnimator.TimeListener;
import android.util.Log;

import org.chromium.base.annotations.MainDex;

/**
 * Record Android animation frame rate and save it to UMA histogram. This is mainly for monitoring
 * any jankiness of short Chrome Android animations. It is limited to few seconds of recording.
 */
@MainDex
public class AnimationFrameTimeHistogram {
    private static final String TAG = "AnimationFrameTimeHistogram";
    private static final int MAX_FRAME_TIME_NUM = 600; // 10 sec on 60 fps.

    private final Recorder mRecorder = new Recorder();
    private final String mHistogramName;

    /**
     * @param histogramName The histogram name that the recorded frame times will be saved.
     *                      This must be also defined in histograms.xml
     * @return An AnimatorListener instance that records frame time histogram on start and end
     *         automatically.
     */
    public static AnimatorListener getAnimatorRecorder(final String histogramName) {
        return new AnimatorListenerAdapter() {
            private final AnimationFrameTimeHistogram mAnimationFrameTimeHistogram =
                    new AnimationFrameTimeHistogram(histogramName);

            @Override
            public void onAnimationStart(Animator animation) {
                mAnimationFrameTimeHistogram.startRecording();
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                mAnimationFrameTimeHistogram.endRecording();
            }

            @Override
            public void onAnimationCancel(Animator animation) {
                mAnimationFrameTimeHistogram.endRecording();
            }
        };
    }

    /**
     * @param histogramName The histogram name that the recorded frame times will be saved.
     *                      This must be also defined in histograms.xml
     */
    public AnimationFrameTimeHistogram(String histogramName) {
        mHistogramName = histogramName;
    }

    /**
     * Start recording frame times. The recording can fail if it exceeds a few seconds.
     */
    public void startRecording() {
        mRecorder.startRecording();
    }

    /**
     * End recording and save it to histogram. It won't save histogram if the recording wasn't
     * successful.
     */
    public void endRecording() {
        if (mRecorder.endRecording()) {
            nativeSaveHistogram(mHistogramName,
                    mRecorder.getFrameTimesMs(), mRecorder.getFrameTimesCount());
        }
        mRecorder.cleanUp();
    }

    /**
     * Record Android animation frame rate and return the result.
     */
    private static class Recorder implements TimeListener {
        // TODO(kkimlabs): If we can use in the future, migrate to Choreographer for minimal
        //                 workload.
        private final TimeAnimator mAnimator = new TimeAnimator();
        private long[] mFrameTimesMs;
        private int mFrameTimesCount;

        private Recorder() {
            mAnimator.setTimeListener(this);
        }

        private void startRecording() {
            assert !mAnimator.isRunning();
            mFrameTimesCount = 0;
            mFrameTimesMs = new long[MAX_FRAME_TIME_NUM];
            mAnimator.start();
        }

        /**
         * @return Whether the recording was successful. If successful, the result is available via
         *         getFrameTimesNs and getFrameTimesCount.
         */
        private boolean endRecording() {
            boolean succeeded = mAnimator.isStarted();
            mAnimator.end();
            return succeeded;
        }

        private long[] getFrameTimesMs() {
            return mFrameTimesMs;
        }

        private int getFrameTimesCount() {
            return mFrameTimesCount;
        }

        /**
         * Deallocates the temporary buffer to record frame times. Must be called after ending
         * the recording and getting the result.
         */
        private void cleanUp() {
            mFrameTimesMs = null;
        }

        @Override
        public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
            if (mFrameTimesCount == mFrameTimesMs.length) {
                mAnimator.end();
                cleanUp();
                Log.w(TAG, "Animation frame time recording reached the maximum number. It's either"
                        + "the animation took too long or recording end is not called.");
                return;
            }

            // deltaTime is 0 for the first frame.
            if (deltaTime > 0) {
                mFrameTimesMs[mFrameTimesCount++] = deltaTime;
            }
        }
    }

    private native void nativeSaveHistogram(String histogramName, long[] frameTimesMs, int count);
}