1 /*
2  * Copyright (C) 2018 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.server.wm.flicker.monitor;
18 
19 import static android.view.FrameStats.UNDEFINED_TIME_NANO;
20 
21 import android.app.Instrumentation;
22 import android.util.Log;
23 import android.view.FrameStats;
24 
25 /**
26  * Monitors {@link android.view.WindowAnimationFrameStats} to detect janky frames.
27  *
28  * <p>Adapted from {@link androidx.test.jank.internal.WindowAnimationFrameStatsMonitorImpl} using
29  * the same threshold to determine jank.
30  */
31 public class WindowAnimationFrameStatsMonitor extends TransitionMonitor {
32 
33     private static final String TAG = "FLICKER";
34     // Maximum normalized error in frame duration before the frame is considered janky
35     private static final double MAX_ERROR = 0.5f;
36     // Maximum normalized frame duration before the frame is considered a pause
37     private static final double PAUSE_THRESHOLD = 15.0f;
38     private Instrumentation mInstrumentation;
39     private FrameStats mStats;
40     private int mNumJankyFrames;
41     private long mLongestFrameNano = 0L;
42 
43     /** Constructs a WindowAnimationFrameStatsMonitor instance. */
WindowAnimationFrameStatsMonitor(Instrumentation instrumentation)44     public WindowAnimationFrameStatsMonitor(Instrumentation instrumentation) {
45         mInstrumentation = instrumentation;
46     }
47 
analyze()48     private void analyze() {
49         int frameCount = mStats.getFrameCount();
50         long refreshPeriodNano = mStats.getRefreshPeriodNano();
51 
52         // Skip first frame
53         for (int i = 2; i < frameCount; i++) {
54             // Handle frames that have not been presented.
55             if (mStats.getFramePresentedTimeNano(i) == UNDEFINED_TIME_NANO) {
56                 // The animation must not have completed. Warn and break out of the loop.
57                 Log.w(TAG, "Skipping fenced frame.");
58                 break;
59             }
60             long frameDurationNano =
61                     mStats.getFramePresentedTimeNano(i) - mStats.getFramePresentedTimeNano(i - 1);
62             double normalized = (double) frameDurationNano / refreshPeriodNano;
63             if (normalized < PAUSE_THRESHOLD) {
64                 if (normalized > 1.0f + MAX_ERROR) {
65                     mNumJankyFrames++;
66                 }
67                 mLongestFrameNano = Math.max(mLongestFrameNano, frameDurationNano);
68             }
69         }
70     }
71 
72     @Override
start()73     public void start() {
74         // Clear out any previous data
75         mNumJankyFrames = 0;
76         mLongestFrameNano = 0;
77         mInstrumentation.getUiAutomation().clearWindowAnimationFrameStats();
78     }
79 
80     @Override
stop()81     public void stop() {
82         mStats = mInstrumentation.getUiAutomation().getWindowAnimationFrameStats();
83         analyze();
84     }
85 
jankyFramesDetected()86     public boolean jankyFramesDetected() {
87         return mStats.getFrameCount() > 0 && mNumJankyFrames > 0;
88     }
89 
90     @Override
toString()91     public String toString() {
92         return mStats.toString()
93                 + " RefreshPeriodNano:"
94                 + mStats.getRefreshPeriodNano()
95                 + " NumJankyFrames:"
96                 + mNumJankyFrames
97                 + " LongestFrameNano:"
98                 + mLongestFrameNano;
99     }
100 }
101