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