1 /*
2  * Copyright (C) 2015 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 android.support.test.aupt;
18 
19 import android.util.Log;
20 
21 import java.lang.StringBuilder;
22 import java.util.List;
23 import java.util.regex.Pattern;
24 import java.util.regex.Matcher;
25 
26 /**
27  * This class is like a C-style struct that holds individual process information from the
28  * dumpsys graphicsstats command. It also includes an enumeration, originally from the
29  * JankTestHelper code, which pattern matches against the dump data to find the relevant
30  * information.
31  */
32 public class JankStat {
33     private static final String TAG = "JankStat";
34 
35     // Patterns used for parsing dumpsys graphicsstats
36     public enum StatPattern {
37         PACKAGE(Pattern.compile("\\s*Package: (.*)"), 1),
38         STATS_SINCE(Pattern.compile("\\s*Stats since: (\\d+)ns"), 1),
39         TOTAL_FRAMES(Pattern.compile("\\s*Total frames rendered: (\\d+)"), 1),
40         NUM_JANKY(Pattern.compile("\\s*Janky frames: (\\d+) (.*)"), 1),
41         FRAME_TIME_50TH(Pattern.compile("\\s*50th percentile: (\\d+)ms"), 1),
42         FRAME_TIME_90TH(Pattern.compile("\\s*90th percentile: (\\d+)ms"), 1),
43         FRAME_TIME_95TH(Pattern.compile("\\s*95th percentile: (\\d+)ms"), 1),
44         FRAME_TIME_99TH(Pattern.compile("\\s*99th percentile: (\\d+)ms"), 1),
45         SLOWEST_FRAMES_24H(Pattern.compile("\\s*Slowest frames over last 24h: (.*)"), 1),
46         NUM_MISSED_VSYNC(Pattern.compile("\\s*Number Missed Vsync: (\\d+)"), 1),
47         NUM_HIGH_INPUT_LATENCY(Pattern.compile("\\s*Number High input latency: (\\d+)"), 1),
48         NUM_SLOW_UI_THREAD(Pattern.compile("\\s*Number Slow UI thread: (\\d+)"), 1),
49         NUM_SLOW_BITMAP_UPLOADS(Pattern.compile("\\s*Number Slow bitmap uploads: (\\d+)"), 1),
50         NUM_SLOW_DRAW(Pattern.compile("\\s*Number Slow issue draw commands: (\\d+)"), 1);
51 
52         private Pattern mParsePattern;
53         private int mGroupIdx;
54 
StatPattern(Pattern pattern, int idx)55         StatPattern(Pattern pattern, int idx) {
56             mParsePattern = pattern;
57             mGroupIdx = idx;
58         }
59 
parse(String line)60         String parse(String line) {
61             String ret = null;
62             Matcher matcher = mParsePattern.matcher(line);
63             if (matcher.matches()) {
64                 ret = matcher.group(mGroupIdx);
65             }
66             return ret;
67         }
68     }
69 
70     public String packageName;
71     public long statsSince;
72     public int totalFrames;
73     public int jankyFrames;
74     public int frameTime50th;
75     public int frameTime90th;
76     public int frameTime95th;
77     public int frameTime99th;
78     public String slowestFrames24h;
79     public int numMissedVsync;
80     public int numHighLatency;
81     public int numSlowUiThread;
82     public int numSlowBitmap;
83     public int numSlowDraw;
84 
85     public int aggregateCount;
86 
JankStat(String pkg, long since, int total, int janky, int ft50, int ft90, int ft95, int ft99, String slow24h, int vsync, int latency, int slowUi, int slowBmp, int slowDraw, int aggCount)87     public JankStat (String pkg, long since, int total, int janky, int ft50, int ft90, int ft95,
88             int ft99, String slow24h, int vsync, int latency, int slowUi, int slowBmp, int slowDraw,
89             int aggCount) {
90         packageName = pkg;
91         statsSince = since;
92         totalFrames = total;
93         jankyFrames = janky;
94         frameTime50th = ft50;
95         frameTime90th = ft90;
96         frameTime95th = ft95;
97         frameTime99th = ft99;
98         slowestFrames24h = slow24h;
99         numMissedVsync = vsync;
100         numHighLatency = latency;
101         numSlowUiThread = slowUi;
102         numSlowBitmap = slowBmp;
103         numSlowDraw = slowDraw;
104 
105         aggregateCount = aggCount;
106     }
107 
108     /**
109      * Determines if this set of janks stats is aggregated from the
110      * previous set of metrics or if they are a new set, meaning the
111      * old process was killed, had its stats reset, and was then
112      * restarted.
113      */
isContinuedFrom(JankStat prevMetrics)114     public boolean isContinuedFrom (JankStat prevMetrics) {
115         return statsSince == prevMetrics.statsSince;
116     }
117 
118     /**
119      * Returns the percent of frames that appeared janky
120      */
getPercentJankyFrames()121     public float getPercentJankyFrames () {
122         return jankyFrames / (float)totalFrames;
123     }
124 
125     /**
126      * Returns the jank stats similar to how they are presented in the shell
127      */
128     @Override
toString()129     public String toString () {
130         String result = packageName +
131                 "\nStats since: " + statsSince +
132                 "\nTotal frames: " + totalFrames +
133                 "\nJanky frames: " + jankyFrames +
134                 "\n50th percentile: " + frameTime50th +
135                 "\n90th percentile: " + frameTime90th +
136                 "\n95th percentile: " + frameTime95th +
137                 "\n99th percentile: " + frameTime99th +
138                 "\nSlowest frames over last 24h: " + slowestFrames24h +
139                 "\nNumber Missed Vsync: " + numMissedVsync +
140                 "\nNumber High input latency: " + numHighLatency +
141                 "\nNumber Slow UI thread: " + numSlowUiThread +
142                 "\nNumber Slow bitmap uploads: " + numSlowBitmap +
143                 "\nNumber Slow draw commands: " + numSlowDraw +
144                 "\nAggregated stats count: " + aggregateCount;
145         return result;
146     }
147 
148     /**
149      * Merges the stat history of a sequence of stats.
150      *
151      * Final count value = sum of count values across stats
152      * Final ##th percentile = weighted average of ##th, weight by total frames
153      *     ## = 90, 95, and 99
154      */
mergeStatHistory(List<JankStat> statHistory)155     public static JankStat mergeStatHistory (List<JankStat> statHistory) {
156         if (statHistory.size() == 0)
157             return null;
158         else if (statHistory.size() == 1)
159             return statHistory.get(0);
160 
161         String pkg = statHistory.get(0).packageName;
162         long totalStatsSince = statHistory.get(0).statsSince;
163         int totalTotalFrames = 0;
164         int totalJankyFrames = 0;
165         int totalNumMissedVsync = 0;
166         int totalNumHighLatency = 0;
167         int totalNumSlowUiThread = 0;
168         int totalNumSlowBitmap = 0;
169         int totalNumSlowDraw = 0;
170         String totalSlow24h = "";
171 
172         for (JankStat stat : statHistory) {
173             totalTotalFrames += stat.totalFrames;
174             totalJankyFrames += stat.jankyFrames;
175             totalNumMissedVsync += stat.numMissedVsync;
176             totalNumHighLatency += stat.numHighLatency;
177             totalNumSlowUiThread += stat.numSlowUiThread;
178             totalNumSlowBitmap += stat.numSlowBitmap;
179             totalNumSlowDraw += stat.numSlowDraw;
180             totalSlow24h += stat.slowestFrames24h;
181         }
182 
183         float wgtAvgPercentile50 = 0f;
184         float wgtAvgPercentile90 = 0f;
185         float wgtAvgPercentile95 = 0f;
186         float wgtAvgPercentile99 = 0f;
187         for (JankStat stat : statHistory) {
188             float weight = ((float)stat.totalFrames / totalTotalFrames);
189             Log.v(TAG, String.format("Calculated weight is %f", weight));
190             wgtAvgPercentile90 += stat.frameTime50th * weight;
191             wgtAvgPercentile90 += stat.frameTime90th * weight;
192             wgtAvgPercentile95 += stat.frameTime95th * weight;
193             wgtAvgPercentile99 += stat.frameTime99th * weight;
194         }
195 
196         int perc50 = (int)Math.ceil(wgtAvgPercentile50);
197         int perc90 = (int)Math.ceil(wgtAvgPercentile90);
198         int perc95 = (int)Math.ceil(wgtAvgPercentile95);
199         int perc99 = (int)Math.ceil(wgtAvgPercentile99);
200 
201         return new JankStat(pkg, totalStatsSince, totalTotalFrames,
202                 totalJankyFrames, perc50, perc90, perc95, perc99, totalSlow24h,
203                 totalNumMissedVsync, totalNumHighLatency, totalNumSlowUiThread, totalNumSlowBitmap,
204                 totalNumSlowDraw, statHistory.size());
205     }
206 
207     /**
208      * Returns a long String containing each JankStat object separated by a
209      * newline. Ideally, this would omit objects with zero rendered total
210      * frames, which is junk data.
211      */
statsListToString(List<JankStat> statsList)212     public static String statsListToString (List<JankStat> statsList) {
213         StringBuilder result = new StringBuilder();
214         for (JankStat stats : statsList) {
215             result.append(stats.toString());
216             result.append("\n");
217         }
218 
219         return result.toString();
220     }
221 }
222