1/*
2 * Copyright (C) 2013 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 com.android.uiautomator.platform;
18
19import android.os.Environment;
20import android.util.Log;
21
22import java.util.Arrays;
23import java.util.List;
24import java.util.ArrayList;
25import java.io.BufferedReader;
26import java.io.File;
27import java.io.FileWriter;
28import java.io.BufferedWriter;
29import java.io.InputStreamReader;
30import java.io.IOException;
31import java.lang.Math;
32
33/*
34 * Tools to measure jankiness through SurfaceFlinger
35 */
36public class SurfaceFlingerHelper {
37    private static String TAG = "SurfaceFlingerHelper";
38    private static int BUFFER_SIZE = 128;
39    private static int BUFFER_NUMBER = 3;
40    private static String CLEAR_BUFFER_CMD = "dumpsys SurfaceFlinger --latency-clear";
41    private static String FRAME_LATENCY_CMD = "dumpsys SurfaceFlinger --latency";
42    private static final String RAW_DATA_DIR = "UiJankinessRawData";
43    private static final String LOCAL_TMP_DIR = "/data/local/tmp/";
44    /* If the latency between two frames is greater than this number, it it treated as a pause
45     * not a jankiness */
46    private static final int PAUSE_LATENCY = 20;
47
48    /* An array list which includes the raw buffer information from frame latency tool */
49    private static List<List<String>> mFrameBufferData = new ArrayList<List<String>>(BUFFER_SIZE);
50
51    /* Record the refresh period returned from driver */
52    private static long mRefreshPeriod = -1;
53
54    /* Record the size of frame latency */
55    private static int mFrameLatencySampleSize = 0;
56
57    /* An integer array which includes delta vsync */
58    private static long[] mDeltaVsync = new long[BUFFER_SIZE];
59
60    /* Integer array for delta of delta vsync */
61    private static long[] mDelta2Vsync = new long[BUFFER_SIZE];
62
63    /* the maximum delta vsync number */
64    private static long mMaxDeltaVsync;
65
66    /* Normalized data */
67    private static double[] mNormalizedDelta2Vsync = new double[BUFFER_SIZE];
68    private static int[] mRoundNormalizedDelta2Vsync = new int[BUFFER_SIZE];
69    // Symbol of unfinished frame time */
70    public static final String PENDING_FENCE_TIME = new Long(Long.MAX_VALUE).toString();
71
72    /**
73     * Run clear buffer command and clear the saved frame buffer results
74     *
75     * @param windowName the window name that the buffer will be cleared
76     */
77    public static void clearBuffer(String windowName) {
78        // clear results
79        if (mFrameBufferData != null) {
80            mFrameBufferData.clear();
81        }
82        Arrays.fill(mDeltaVsync, -1);
83        Arrays.fill(mDelta2Vsync, -1);
84        Arrays.fill(mNormalizedDelta2Vsync, -1.0);
85        Arrays.fill(mRoundNormalizedDelta2Vsync, -1);
86        mRefreshPeriod = -1;
87        mFrameLatencySampleSize = 0;
88        mMaxDeltaVsync = 0;
89
90        Process p = null;
91        BufferedReader resultReader = null;
92        String command = CLEAR_BUFFER_CMD;
93        if (windowName != null) {
94            command = String.format("%s %s", CLEAR_BUFFER_CMD, windowName);
95        }
96        try {
97            p = Runtime.getRuntime().exec(command);
98            int status = p.waitFor();
99            if (status != 0) {
100                Log.e(TAG, String.format("Run shell command: %s, status: %s",
101                        command, status));
102            }
103        } catch (IOException e) {
104            Log.e(TAG, "// Exception from command " + command + ":", e);
105        } catch (InterruptedException e) {
106            Log.e(TAG, "// Interrupted while waiting for the command to finish. ", e);
107        } finally {
108            try {
109                if (resultReader != null) {
110                    resultReader.close();
111                }
112                if (p != null) {
113                    p.destroy();
114                }
115            } catch (IOException e) {
116                Log.e(TAG, "exception " + e);
117            }
118        }
119    }
120
121    /**
122     * Run frame latency command without ignoring pending fence time
123     *
124     * @param windowName the window name which SurfaceFlinger will acquire frame time for
125     */
126    public static boolean dumpFrameLatency(String windowName) {
127        return dumpFrameLatency(windowName, false);
128    }
129
130    /**
131     * Run frame latency command to get frame time
132     *
133     * @param windowName the window name which SurfaceFlinger will get frame time for
134     * @param ignorePendingFenceTime flag to process frames with pending fence time
135     *                              set true to ignore pending fence time
136     *                              set false to fail the test if pending fence time is not allowed
137     */
138    public static boolean dumpFrameLatency(String windowName, boolean ignorePendingFenceTime) {
139        Process p = null;
140        BufferedReader resultReader = null;
141        String command = FRAME_LATENCY_CMD;
142        if (windowName != null) {
143            command = String.format("%s %s", FRAME_LATENCY_CMD, windowName);
144        }
145        log("dump frame latency command: " + command);
146        try {
147            p = Runtime.getRuntime().exec(command);
148            int status = p.waitFor();
149            if (status != 0) {
150                Log.e(TAG, String.format("Run shell command: %s, status: %s",command, status));
151            }
152            resultReader = new BufferedReader(new InputStreamReader(p.getInputStream()));
153            String line = resultReader.readLine();
154            mRefreshPeriod = Long.parseLong(line.trim());
155            log("reading refresh period: " + mRefreshPeriod);
156            if (mRefreshPeriod < 0) {
157                return false;
158            }
159            boolean dataInvalidFlag = false;
160            while((line = resultReader.readLine()) != null) {
161                // remove lines which are empty
162                if (line.trim().isEmpty()) {
163                    break;
164                }
165                String[] bufferValues = line.split("\\s+");
166                if (bufferValues[0].trim().compareTo("0") == 0) {
167                    continue;
168                } else if (bufferValues[1].trim().compareTo(PENDING_FENCE_TIME) == 0 ) {
169                    if (ignorePendingFenceTime) {
170                        log("ignore pending fence time");
171                        dataInvalidFlag = true;
172                    } else {
173                        log("the data contains unfinished frame time, please allow the animation"
174                            + " to finish in the test before calling dumpFrameLatency.");
175                        return false;
176                    }
177                }
178                // store raw data which could have both valid and invalid data
179                List<String> delayArray = Arrays.asList(bufferValues);
180                mFrameBufferData.add(delayArray);
181                if (!dataInvalidFlag) {
182                    // only count frames which have valid time
183                    ++mFrameLatencySampleSize;
184                }
185            }
186            log("frame latency sample size: " + mFrameLatencySampleSize);
187        } catch (InterruptedException e) {
188            Log.e(TAG, "// Exception from command " + command + ":", e);
189        } catch (IOException e) {
190            Log.e(TAG, "Open file error: ", e);
191            return false;
192        }
193        finally {
194            try {
195                if (resultReader != null) {
196                    resultReader.close();
197                }
198                if (p != null) {
199                    p.destroy();
200                }
201            } catch (IOException e) {
202                Log.e(TAG, "io exception: ", e);
203            }
204        }
205        return true;
206    }
207
208    public static int getDataSampleSize() {
209        return mFrameLatencySampleSize;
210    }
211
212    public static long getRefreshPeriod() {
213        if (mRefreshPeriod < 0) {
214            // Haven't dump the frame latency yet
215            log("Run command \"" + FRAME_LATENCY_CMD + " \" before retrieving the refresh period");
216        }
217        return mRefreshPeriod;
218    }
219
220    public static String getFrameBufferData() {
221        if (mFrameBufferData.get(0) == null) {
222            log("Run command \"" + FRAME_LATENCY_CMD + " \" before retrieving frame buffer data");
223            return null;
224        }
225        String rawData = String.format("%d\n", mRefreshPeriod);
226        List<String> tempList = new ArrayList<String>(BUFFER_NUMBER);
227        for (int i = 0; i < mFrameBufferData.size(); i++) {
228            tempList = mFrameBufferData.get(i);
229            for (int j = 0; j < BUFFER_NUMBER; j++) {
230                rawData += String.format("%s", tempList.get(j));
231                if (j < BUFFER_NUMBER - 1) {
232                    rawData += "\t";
233                } else {
234                    rawData += "\n";
235                }
236            }
237        }
238        return rawData;
239    }
240
241    /**
242     * Calculate delta(vsync)
243     * @return
244     */
245    public static long[] getDeltaVsync() {
246        if (mRefreshPeriod < 0) {
247            log("Run command \"" + FRAME_LATENCY_CMD + " \" before retrieving frame latency");
248            return null;
249        }
250        if (mDeltaVsync[0] < 0 ) {
251            // keep a record of the max DeltaVsync
252            mMaxDeltaVsync = 0;
253            // get the first frame vsync time
254            long preVsyncTime = Long.parseLong(mFrameBufferData.get(0).get(1));
255            for (int i = 0; i < mFrameLatencySampleSize - 1; i++) {
256                long curVsyncTime = Long.parseLong(mFrameBufferData.get(i + 1).get(1));
257                mDeltaVsync[i] = curVsyncTime - preVsyncTime;
258                preVsyncTime = curVsyncTime;
259                if (mMaxDeltaVsync < mDeltaVsync[i]) {
260                    mMaxDeltaVsync = mDeltaVsync[i];
261                }
262            }
263        }
264        return mDeltaVsync;
265    }
266
267    /**
268     * Calculate difference between delta vsync
269     * @return
270     */
271    public static long[] getDelta2Vsync() {
272        if (mRefreshPeriod < 0) {
273            log("Run command \"" + FRAME_LATENCY_CMD + " \" before retrieving frame latency");
274            return null;
275        }
276        if (mDeltaVsync[0] < 0) {
277            getDeltaVsync();
278        }
279        if (mDelta2Vsync[0] < 0) {
280            int numDeltaVsync = mFrameLatencySampleSize - 1;
281            for (int i = 0; i < numDeltaVsync - 1; i++) {
282                mDelta2Vsync[i] = mDeltaVsync[i + 1] - mDeltaVsync[i];
283            }
284        }
285        return mDelta2Vsync;
286    }
287
288    /**
289     * normalized delta(delta(vsync)) by refresh period
290     * @return
291     */
292    public static double[] getNormalizedDelta2Vsync() {
293        if (mRefreshPeriod < 0) {
294            log("Run command \"" + FRAME_LATENCY_CMD + " \" before retrieving frame latency");
295            return null;
296        }
297        if (mDelta2Vsync[0] < 0) {
298            getDelta2Vsync();
299        }
300        if (mNormalizedDelta2Vsync[0] < 0) {
301            for (int i = 0; i < mFrameLatencySampleSize - 2; i++) {
302                mNormalizedDelta2Vsync[i] = (double)mDelta2Vsync[i] /mRefreshPeriod;
303            }
304        }
305        return mNormalizedDelta2Vsync;
306    }
307
308    public static int[] getRoundNormalizedDelta2Vsync() {
309        if (mRefreshPeriod < 0) {
310            log("Run command \"" + FRAME_LATENCY_CMD + " \" for number of jankiness.");
311            return null;
312        }
313        if (mNormalizedDelta2Vsync[0] < 0) {
314            getNormalizedDelta2Vsync();
315        }
316
317        for (int i = 0; i < mFrameLatencySampleSize - 2; i++) {
318             int value = (int)Math.round(Math.max(mNormalizedDelta2Vsync[i], 0.0));
319             mRoundNormalizedDelta2Vsync[i] = value;
320        }
321        return mRoundNormalizedDelta2Vsync;
322    }
323
324    /*
325     * Get number of jankiness using Vsync time difference
326     */
327    public static int getVsyncJankiness() {
328        if (mRefreshPeriod < 0) {
329            log("Run command \"" + FRAME_LATENCY_CMD + " \" for number of jankiness.");
330            return -1;
331        }
332        if (mRoundNormalizedDelta2Vsync[0] < 0) {
333            getRoundNormalizedDelta2Vsync();
334        }
335        int numberJankiness = 0;
336        for (int i = 0; i < mFrameLatencySampleSize - 2; i++) {
337            int value = mRoundNormalizedDelta2Vsync[i];
338            // ignore the latency which is too long
339            if (value > 0 && value < PAUSE_LATENCY) {
340                numberJankiness++;
341            }
342        }
343        return numberJankiness;
344    }
345
346    /* Track the maximum delta which shows the accumulating time
347     * before animation starts */
348    public static int getMaxDeltaVsync() {
349        return Math.round((float)mMaxDeltaVsync /mRefreshPeriod);
350    }
351
352    /**
353     * Calculate frame rate
354     * @return
355     */
356    public static double getFrameRate() {
357        if (mRefreshPeriod < 0) {
358            log("Run command \"" + FRAME_LATENCY_CMD + " \" before calcuating average frame rate");
359            return -1.0;
360        }
361        if (mFrameBufferData.get(0) == null) {
362            log("Run command \"" + FRAME_LATENCY_CMD + " \" before retrieving frame buffer data");
363            return -1.0;
364        }
365        long startTime = Long.parseLong(mFrameBufferData.get(0).get(1));
366        long endTime =  Long.parseLong(mFrameBufferData.get(mFrameLatencySampleSize - 1).get(1));
367        long totalDuration = endTime - startTime;
368        return (double)((mFrameLatencySampleSize - 1) * Math.pow(10, 9))/totalDuration;
369    }
370
371    /**
372     * Print raw data and processed results into file <testcasename_[iteration]_processed.txt>
373     * @param fileName
374     * @param index
375     */
376    public static void printData(String fileName, int index) {
377        String rawAndProcDataFileName = String.format("%s/%s_%d_processed.txt", RAW_DATA_DIR,
378                fileName, index);
379        log("write raw data and process data into file: " + rawAndProcDataFileName);
380        BufferedWriter fw = null;
381        try {
382            fw = new BufferedWriter(new FileWriter(new File(rawAndProcDataFileName), false));
383            // Show the number of jankiness first:
384            fw.write(String.format("Jankiness count: %d\n", getVsyncJankiness()));
385            fw.write(String.format("Max accumulated frames: %d\n", getMaxDeltaVsync()));
386            fw.write(String.format("Frame rate is: %f\n", getFrameRate()));
387
388            // refresh period
389            fw.write(String.valueOf(mRefreshPeriod));
390            fw.write("\n");
391            fw.write("app\tvsync\tset\tdelta(vsync)\tdelta^2(vsync)\t" +
392                    "delta^2(vsync)/refreshPeriod\t normalized delta^2(vsync)\n");
393
394            for (int i = 0; i < mFrameLatencySampleSize; i++) {
395                // write raw data
396                List<String> rawData = mFrameBufferData.get(i);
397                String line = String.format("%s\t%s\t%s\t%d\t%d\t%f\t%d\n",
398                        rawData.get(0), rawData.get(1), rawData.get(2),
399                        mDeltaVsync[i], mDelta2Vsync[i],
400                        mNormalizedDelta2Vsync[i], mRoundNormalizedDelta2Vsync[i]);
401                fw.write(line);
402            }
403        } catch (IOException e) {
404            log("Open file error: " + e.toString());
405        } finally {
406            try {
407                if (fw != null) {
408                    fw.flush();
409                    fw.close();
410                }
411            }
412            catch (IOException e) {
413                Log.e(TAG, "close file exception: ", e);
414            }
415        }
416    }
417
418    private static void log(String msg) {
419        Log.v(TAG, msg);
420    }
421}
422