• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 import org.json.JSONObject;
27 import org.json.JSONException;
28 
29 /**
30  * This class is like a C-style struct that holds individual process information from the
31  * dumpsys graphicsstats command. It also includes an enumeration, originally from the
32  * JankTestHelper code, which pattern matches against the dump data to find the relevant
33  * information.
34  */
35 public class JankStat {
36     private static final String TAG = "JankStat";
37 
38     // Patterns used for parsing dumpsys graphicsstats
39     public enum StatPattern {
40         PACKAGE("package",
41                 Pattern.compile("\\s*Package: (.*)"), 1),
42 
43         STATS_SINCE("startTime",
44                 Pattern.compile("\\s*Stats since: (\\d+)ns"), 1),
45 
46         TOTAL_FRAMES("frameCount",
47                 Pattern.compile("\\s*Total frames rendered: (\\d+)"), 1),
48 
49         NUM_JANKY("jankyCount",
50                 Pattern.compile("\\s*Janky frames: (\\d+) (.*)"), 1),
51 
52         FRAME_TIME_50TH("percentile50",
53                 Pattern.compile("\\s*50th percentile: (\\d+)ms"), 1),
54 
55         FRAME_TIME_90TH("percentile90",
56                 Pattern.compile("\\s*90th percentile: (\\d+)ms"), 1),
57 
58         FRAME_TIME_95TH("percentile95",
59                 Pattern.compile("\\s*95th percentile: (\\d+)ms"), 1),
60 
61         FRAME_TIME_99TH("percentile99",
62                 Pattern.compile("\\s*99th percentile: (\\d+)ms"), 1),
63 
64         NUM_MISSED_VSYNC("missedVsyncCount",
65                 Pattern.compile("\\s*Number Missed Vsync: (\\d+)"), 1),
66 
67         NUM_HIGH_INPUT_LATENCY("highLatencyCount",
68                 Pattern.compile("\\s*Number High input latency: (\\d+)"), 1),
69 
70         NUM_SLOW_UI_THREAD("slowUIThreadCount",
71                 Pattern.compile("\\s*Number Slow UI thread: (\\d+)"), 1),
72 
73         NUM_SLOW_BITMAP_UPLOADS("slowBitmapUploadCount",
74                 Pattern.compile("\\s*Number Slow bitmap uploads: (\\d+)"), 1),
75 
76         NUM_SLOW_DRAW("slowDrawCmdCount",
77                 Pattern.compile("\\s*Number Slow issue draw commands: (\\d+)"), 1),
78 
79         AGGREGATE_COUNT("aggregateCount", null, 1);
80 
81         private String mName;
82         private Pattern mParsePattern;
83         private int mGroupIdx;
84 
85         /**
86          * Constructs each pattern for parsing the statistics
87          * generated by `dumpsys graphicsstats`
88          *
89          * "name" is a unique JSON key for the field
90          * "pattern" is the regex for parsing out the field
91          * "idx" the match-index for the relevant field in that pattern
92          */
StatPattern(String name, Pattern pattern, int idx)93         StatPattern(String name, Pattern pattern, int idx) {
94             mName = name;
95             mParsePattern = pattern;
96             mGroupIdx = idx;
97         }
98 
parse(String line)99         String parse(String line) {
100             String ret = null;
101             Matcher matcher = mParsePattern.matcher(line);
102             if (matcher.matches()) {
103                 ret = matcher.group(mGroupIdx);
104             }
105             return ret;
106         }
107 
getPattern()108         Pattern getPattern() {
109             return mParsePattern;
110         }
111 
getName()112         String getName() {
113             return mName;
114         }
115     }
116 
117     public String packageName;
118     public Long statsSince;
119     public Integer totalFrames;
120     public Integer jankyFrames;
121     public Integer frameTime50th;
122     public Integer frameTime90th;
123     public Integer frameTime95th;
124     public Integer frameTime99th;
125     public Integer numMissedVsync;
126     public Integer numHighLatency;
127     public Integer numSlowUiThread;
128     public Integer numSlowBitmap;
129     public Integer numSlowDraw;
130     public Integer aggregateCount;
131 
JankStat(String pkg, long since, int total, int janky, int ft50, int ft90, int ft95, int ft99, int vsync, int latency, int slowUi, int slowBmp, int slowDraw, int aggCount)132     public JankStat (String pkg, long since, int total, int janky, int ft50, int ft90, int ft95,
133             int ft99, int vsync, int latency, int slowUi, int slowBmp, int slowDraw,
134             int aggCount) {
135         packageName = pkg;
136         statsSince = since;
137         totalFrames = total;
138         jankyFrames = janky;
139         frameTime50th = ft50;
140         frameTime90th = ft90;
141         frameTime95th = ft95;
142         frameTime99th = ft99;
143         numMissedVsync = vsync;
144         numHighLatency = latency;
145         numSlowUiThread = slowUi;
146         numSlowBitmap = slowBmp;
147         numSlowDraw = slowDraw;
148         aggregateCount = aggCount;
149     }
150 
151     /**
152      * Determines if this set of janks stats is aggregated from the
153      * previous set of metrics or if they are a new set, meaning the
154      * old process was killed, had its stats reset, and was then
155      * restarted.
156      */
isContinuedFrom(JankStat prevMetrics)157     public boolean isContinuedFrom (JankStat prevMetrics) {
158         return statsSince == prevMetrics.statsSince;
159     }
160 
161     /**
162      * Returns the percent of frames that appeared janky
163      */
getPercentJankyFrames()164     public float getPercentJankyFrames () {
165         return jankyFrames / (float)totalFrames;
166     }
167 
168     /**
169      * Serialize this object into a JSONObject
170      */
toJson()171     public JSONObject toJson () throws JSONException {
172         return new JSONObject().
173                 put(StatPattern.PACKAGE.getName(), packageName).
174                 put(StatPattern.STATS_SINCE.getName(), statsSince).
175                 put(StatPattern.TOTAL_FRAMES.getName(), totalFrames).
176                 put(StatPattern.NUM_JANKY.getName(), jankyFrames).
177                 put(StatPattern.FRAME_TIME_50TH.getName(), frameTime50th).
178                 put(StatPattern.FRAME_TIME_90TH.getName(), frameTime90th).
179                 put(StatPattern.FRAME_TIME_95TH.getName(), frameTime95th).
180                 put(StatPattern.FRAME_TIME_99TH.getName(), frameTime99th).
181                 put(StatPattern.NUM_MISSED_VSYNC.getName(), numMissedVsync).
182                 put(StatPattern.NUM_HIGH_INPUT_LATENCY.getName(), numHighLatency).
183                 put(StatPattern.NUM_SLOW_UI_THREAD.getName(), numSlowUiThread).
184                 put(StatPattern.NUM_SLOW_BITMAP_UPLOADS.getName(), numSlowBitmap).
185                 put(StatPattern.NUM_SLOW_DRAW.getName(), numSlowDraw).
186                 put(StatPattern.AGGREGATE_COUNT.getName(), aggregateCount);
187     }
188 
189     /**
190      * @{inheritDoc}
191      */
192     @Override
toString()193     public String toString () {
194       try {
195         return toJson().toString(4);
196       } catch (JSONException e) {
197         throw new RuntimeException("Error serializing JankStat: " + e.toString());
198       }
199     }
200 
201     /**
202      * Merges the stat history of a sequence of stats.
203      *
204      * Final count value = sum of count values across stats
205      * Final ##th percentile = weighted average of ##th, weight by total frames
206      *     ## = 90, 95, and 99
207      */
mergeStatHistory(List<JankStat> statHistory)208     public static JankStat mergeStatHistory (List<JankStat> statHistory) {
209         if (statHistory.size() == 0)
210             return null;
211         else if (statHistory.size() == 1)
212             return statHistory.get(0);
213 
214         String pkg = statHistory.get(0).packageName;
215         long totalStatsSince = statHistory.get(0).statsSince;
216         int totalTotalFrames = 0;
217         int totalJankyFrames = 0;
218         int totalNumMissedVsync = 0;
219         int totalNumHighLatency = 0;
220         int totalNumSlowUiThread = 0;
221         int totalNumSlowBitmap = 0;
222         int totalNumSlowDraw = 0;
223 
224         for (JankStat stat : statHistory) {
225             totalTotalFrames += stat.totalFrames;
226             totalJankyFrames += stat.jankyFrames;
227             totalNumMissedVsync += stat.numMissedVsync;
228             totalNumHighLatency += stat.numHighLatency;
229             totalNumSlowUiThread += stat.numSlowUiThread;
230             totalNumSlowBitmap += stat.numSlowBitmap;
231             totalNumSlowDraw += stat.numSlowDraw;
232         }
233 
234         float wgtAvgPercentile50 = 0f;
235         float wgtAvgPercentile90 = 0f;
236         float wgtAvgPercentile95 = 0f;
237         float wgtAvgPercentile99 = 0f;
238 
239         for (JankStat stat : statHistory) {
240             float weight = ((float)stat.totalFrames / totalTotalFrames);
241             Log.v(TAG, String.format("Calculated weight is %f", weight));
242             wgtAvgPercentile50 += stat.frameTime50th * weight;
243             wgtAvgPercentile90 += stat.frameTime90th * weight;
244             wgtAvgPercentile95 += stat.frameTime95th * weight;
245             wgtAvgPercentile99 += stat.frameTime99th * weight;
246         }
247 
248         int perc50 = (int)Math.ceil(wgtAvgPercentile50);
249         int perc90 = (int)Math.ceil(wgtAvgPercentile90);
250         int perc95 = (int)Math.ceil(wgtAvgPercentile95);
251         int perc99 = (int)Math.ceil(wgtAvgPercentile99);
252 
253         return new JankStat(pkg, totalStatsSince, totalTotalFrames,
254                 totalJankyFrames, perc50, perc90, perc95, perc99,
255                 totalNumMissedVsync, totalNumHighLatency, totalNumSlowUiThread, totalNumSlowBitmap,
256                 totalNumSlowDraw, statHistory.size());
257     }
258 
259     /**
260      * Returns a long String containing each JankStat object separated by a
261      * newline. Ideally, this would omit objects with zero rendered total
262      * frames, which is junk data.
263      */
statsListToString(List<JankStat> statsList)264     public static String statsListToString (List<JankStat> statsList) {
265         StringBuilder result = new StringBuilder();
266         for (JankStat stats : statsList) {
267             result.append(stats.toString());
268             result.append("\n");
269         }
270 
271         return result.toString();
272     }
273 }
274