Allen Hair | aefcdcd | 2015-03-03 15:24:53 -0800 | [diff] [blame] | 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.jank.internal; |
| 18 | |
| 19 | import android.app.UiAutomation; |
| 20 | import android.os.Bundle; |
| 21 | import android.os.ParcelFileDescriptor; |
Allen Hair | 0777807 | 2015-03-06 16:58:26 -0800 | [diff] [blame] | 22 | import android.support.test.jank.GfxMonitor; |
Allen Hair | aefcdcd | 2015-03-03 15:24:53 -0800 | [diff] [blame] | 23 | |
| 24 | import java.io.BufferedReader; |
Allen Hair | aefcdcd | 2015-03-03 15:24:53 -0800 | [diff] [blame] | 25 | import java.io.InputStreamReader; |
| 26 | import java.io.IOException; |
| 27 | import java.util.ArrayList; |
Allen Hair | 0777807 | 2015-03-06 16:58:26 -0800 | [diff] [blame] | 28 | import java.util.Collections; |
Allen Hair | 9c0e562 | 2015-04-08 15:13:18 -0700 | [diff] [blame] | 29 | import java.util.EnumMap; |
Allen Hair | d6d9f31 | 2015-05-21 15:54:46 -0700 | [diff] [blame^] | 30 | import java.util.Iterator; |
Allen Hair | 9c0e562 | 2015-04-08 15:13:18 -0700 | [diff] [blame] | 31 | import java.util.Map; |
Allen Hair | aefcdcd | 2015-03-03 15:24:53 -0800 | [diff] [blame] | 32 | import java.util.List; |
| 33 | import java.util.regex.Pattern; |
| 34 | import java.util.regex.Matcher; |
| 35 | |
| 36 | import junit.framework.Assert; |
| 37 | |
| 38 | /** |
| 39 | * Monitors dumpsys gfxinfo to detect janky frames. |
| 40 | * |
| 41 | * Reports average and max jank. Additionally reports summary statistics for common problems that |
| 42 | * can lead to dropped frames. |
| 43 | */ |
| 44 | class GfxMonitorImpl implements JankMonitor { |
| 45 | |
Allen Hair | aefcdcd | 2015-03-03 15:24:53 -0800 | [diff] [blame] | 46 | // Patterns used for parsing dumpsys gfxinfo output |
Allen Hair | 9c0e562 | 2015-04-08 15:13:18 -0700 | [diff] [blame] | 47 | public enum JankStat { |
Allen Hair | eb1d7fe | 2015-05-18 15:07:49 -0700 | [diff] [blame] | 48 | TOTAL_FRAMES(Pattern.compile("\\s*Total frames rendered: (\\d+)"), 1, Integer.class), |
| 49 | NUM_JANKY(Pattern.compile("\\s*Janky frames: (\\d+) \\((\\d+(\\.\\d+))%\\)"), 2, |
| 50 | Double.class), |
| 51 | FRAME_TIME_90TH(Pattern.compile("\\s*90th percentile: (\\d+)ms"), 1, Integer.class), |
| 52 | FRAME_TIME_95TH(Pattern.compile("\\s*95th percentile: (\\d+)ms"), 1, Integer.class), |
| 53 | FRAME_TIME_99TH(Pattern.compile("\\s*99th percentile: (\\d+)ms"), 1, Integer.class), |
| 54 | NUM_MISSED_VSYNC(Pattern.compile("\\s*Number Missed Vsync: (\\d+)"), 1, Integer.class), |
| 55 | NUM_HIGH_INPUT_LATENCY(Pattern.compile("\\s*Number High input latency: (\\d+)"), 1, |
| 56 | Integer.class), |
| 57 | NUM_SLOW_UI_THREAD(Pattern.compile("\\s*Number Slow UI thread: (\\d+)"), 1, Integer.class), |
| 58 | NUM_SLOW_BITMAP_UPLOADS(Pattern.compile("\\s*Number Slow bitmap uploads: (\\d+)"), 1, |
| 59 | Integer.class), |
| 60 | NUM_SLOW_DRAW(Pattern.compile("\\s*Number Slow draw: (\\d+)"), 1, Integer.class); |
Allen Hair | 9c0e562 | 2015-04-08 15:13:18 -0700 | [diff] [blame] | 61 | |
| 62 | private boolean mSuccessfulParse = false; |
| 63 | private Pattern mParsePattern; |
| 64 | private int mGroupIndex; |
Allen Hair | eb1d7fe | 2015-05-18 15:07:49 -0700 | [diff] [blame] | 65 | private Class mType; |
Allen Hair | 9c0e562 | 2015-04-08 15:13:18 -0700 | [diff] [blame] | 66 | |
Allen Hair | eb1d7fe | 2015-05-18 15:07:49 -0700 | [diff] [blame] | 67 | JankStat(Pattern pattern, int groupIndex, Class type) { |
Allen Hair | 9c0e562 | 2015-04-08 15:13:18 -0700 | [diff] [blame] | 68 | mParsePattern = pattern; |
| 69 | mGroupIndex = groupIndex; |
Allen Hair | eb1d7fe | 2015-05-18 15:07:49 -0700 | [diff] [blame] | 70 | mType = type; |
Allen Hair | 9c0e562 | 2015-04-08 15:13:18 -0700 | [diff] [blame] | 71 | } |
| 72 | |
| 73 | String parse(String line) { |
| 74 | String ret = null; |
| 75 | Matcher matcher = mParsePattern.matcher(line); |
| 76 | if (matcher.matches()) { |
| 77 | ret = matcher.group(mGroupIndex); |
| 78 | mSuccessfulParse = true; |
| 79 | } |
| 80 | return ret; |
| 81 | } |
| 82 | |
| 83 | boolean wasParsedSuccessfully() { |
| 84 | return mSuccessfulParse; |
| 85 | } |
| 86 | |
| 87 | void reset() { |
| 88 | mSuccessfulParse = false; |
| 89 | } |
Allen Hair | eb1d7fe | 2015-05-18 15:07:49 -0700 | [diff] [blame] | 90 | |
| 91 | Class getType() { |
| 92 | return mType; |
| 93 | } |
Allen Hair | 9c0e562 | 2015-04-08 15:13:18 -0700 | [diff] [blame] | 94 | } |
| 95 | |
| 96 | // Metrics accumulated for each iteration |
Allen Hair | eb1d7fe | 2015-05-18 15:07:49 -0700 | [diff] [blame] | 97 | private Map<JankStat, List<? extends Number>> mAccumulatedStats = |
| 98 | new EnumMap<JankStat, List<? extends Number>>(JankStat.class); |
| 99 | |
Allen Hair | aefcdcd | 2015-03-03 15:24:53 -0800 | [diff] [blame] | 100 | |
| 101 | // Used to invoke dumpsys gfxinfo |
| 102 | private UiAutomation mUiAutomation; |
| 103 | private String mProcess; |
| 104 | |
Allen Hair | aefcdcd | 2015-03-03 15:24:53 -0800 | [diff] [blame] | 105 | |
| 106 | public GfxMonitorImpl(UiAutomation automation, String process) { |
| 107 | mUiAutomation = automation; |
| 108 | mProcess = process; |
Allen Hair | 9c0e562 | 2015-04-08 15:13:18 -0700 | [diff] [blame] | 109 | |
| 110 | for (JankStat stat : JankStat.values()) { |
Allen Hair | eb1d7fe | 2015-05-18 15:07:49 -0700 | [diff] [blame] | 111 | if (stat.getType().equals(Integer.class)) { |
| 112 | mAccumulatedStats.put(stat, new ArrayList<Integer>()); |
| 113 | } else if (stat.getType().equals(Double.class)) { |
| 114 | mAccumulatedStats.put(stat, new ArrayList<Double>()); |
| 115 | } else { |
| 116 | // Shouldn't get here |
| 117 | throw new IllegalStateException("Unsupported JankStat type"); |
| 118 | } |
Allen Hair | 9c0e562 | 2015-04-08 15:13:18 -0700 | [diff] [blame] | 119 | } |
Allen Hair | aefcdcd | 2015-03-03 15:24:53 -0800 | [diff] [blame] | 120 | } |
| 121 | |
| 122 | @Override |
| 123 | public void startIteration() throws IOException { |
| 124 | // Clear out any previous data |
| 125 | ParcelFileDescriptor stdout = mUiAutomation.executeShellCommand( |
| 126 | String.format("dumpsys gfxinfo %s reset", mProcess)); |
| 127 | |
| 128 | // Read the output, but don't do anything with it |
| 129 | BufferedReader stream = new BufferedReader(new InputStreamReader( |
| 130 | new ParcelFileDescriptor.AutoCloseInputStream(stdout))); |
| 131 | while (stream.readLine() != null) { |
| 132 | } |
| 133 | } |
| 134 | |
Allen Hair | 9c0e562 | 2015-04-08 15:13:18 -0700 | [diff] [blame] | 135 | |
Allen Hair | aefcdcd | 2015-03-03 15:24:53 -0800 | [diff] [blame] | 136 | @Override |
| 137 | public int stopIteration() throws IOException { |
| 138 | ParcelFileDescriptor stdout = mUiAutomation.executeShellCommand( |
| 139 | String.format("dumpsys gfxinfo %s", mProcess)); |
| 140 | BufferedReader stream = new BufferedReader(new InputStreamReader( |
| 141 | new ParcelFileDescriptor.AutoCloseInputStream(stdout))); |
| 142 | |
Allen Hair | 9c0e562 | 2015-04-08 15:13:18 -0700 | [diff] [blame] | 143 | // The frame stats section has the following output: |
| 144 | // Total frames rendered: ### |
| 145 | // Janky frames: ### (##.##%) |
| 146 | // 90th percentile: ##ms |
| 147 | // 95th percentile: ##ms |
| 148 | // 99th percentile: ##ms |
| 149 | // Number Missed Vsync: # |
| 150 | // Number High input latency: # |
| 151 | // Number Slow UI thread: # |
| 152 | // Number Slow bitmap uploads: # |
| 153 | // Number Slow draw: # |
| 154 | |
Allen Hair | aefcdcd | 2015-03-03 15:24:53 -0800 | [diff] [blame] | 155 | String line; |
| 156 | while ((line = stream.readLine()) != null) { |
Allen Hair | 9c0e562 | 2015-04-08 15:13:18 -0700 | [diff] [blame] | 157 | |
| 158 | // Attempt to parse the line as a frame stat value |
| 159 | for (JankStat stat : JankStat.values()) { |
| 160 | String part; |
| 161 | if ((part = stat.parse(line)) != null) { |
| 162 | // Parse was successful. Add the numeric value to the accumulated list of values |
| 163 | // for that stat. |
Allen Hair | eb1d7fe | 2015-05-18 15:07:49 -0700 | [diff] [blame] | 164 | if (stat.getType().equals(Integer.class)) { |
| 165 | List<Integer> stats = (List<Integer>)mAccumulatedStats.get(stat); |
| 166 | stats.add(Integer.valueOf(part)); |
| 167 | } else if (stat.getType().equals(Double.class)) { |
| 168 | List<Double> stats = (List<Double>)mAccumulatedStats.get(stat); |
| 169 | stats.add(Double.valueOf(part)); |
| 170 | } else { |
| 171 | // Shouldn't get here |
| 172 | throw new IllegalStateException("Unsupported JankStat type"); |
| 173 | } |
Allen Hair | 9c0e562 | 2015-04-08 15:13:18 -0700 | [diff] [blame] | 174 | break; |
| 175 | } |
Allen Hair | aefcdcd | 2015-03-03 15:24:53 -0800 | [diff] [blame] | 176 | } |
| 177 | } |
Allen Hair | aefcdcd | 2015-03-03 15:24:53 -0800 | [diff] [blame] | 178 | |
Allen Hair | 9c0e562 | 2015-04-08 15:13:18 -0700 | [diff] [blame] | 179 | // Make sure we found all the stats |
| 180 | for (JankStat stat : JankStat.values()) { |
| 181 | if (!stat.wasParsedSuccessfully()) { |
| 182 | Assert.fail(String.format("Failed to parse %s", stat.name())); |
| 183 | } |
| 184 | stat.reset(); |
Allen Hair | aefcdcd | 2015-03-03 15:24:53 -0800 | [diff] [blame] | 185 | } |
Allen Hair | aefcdcd | 2015-03-03 15:24:53 -0800 | [diff] [blame] | 186 | |
Allen Hair | eb1d7fe | 2015-05-18 15:07:49 -0700 | [diff] [blame] | 187 | List<Integer> totalFrames = (List<Integer>)mAccumulatedStats.get(JankStat.TOTAL_FRAMES); |
Allen Hair | 9c0e562 | 2015-04-08 15:13:18 -0700 | [diff] [blame] | 188 | return totalFrames.get(totalFrames.size()-1); |
| 189 | } |
Allen Hair | aefcdcd | 2015-03-03 15:24:53 -0800 | [diff] [blame] | 190 | |
Allen Hair | eb1d7fe | 2015-05-18 15:07:49 -0700 | [diff] [blame] | 191 | private void putAvgMaxInteger(Bundle metrics, String averageKey, String maxKey, |
Allen Hair | 9c0e562 | 2015-04-08 15:13:18 -0700 | [diff] [blame] | 192 | List<Integer> values) { |
Allen Hair | ef392f0 | 2015-03-09 15:43:51 -0700 | [diff] [blame] | 193 | |
Allen Hair | 9c0e562 | 2015-04-08 15:13:18 -0700 | [diff] [blame] | 194 | metrics.putDouble(averageKey, MetricsHelper.computeAverageInt(values)); |
| 195 | metrics.putInt(maxKey, Collections.max(values)); |
Allen Hair | aefcdcd | 2015-03-03 15:24:53 -0800 | [diff] [blame] | 196 | } |
| 197 | |
Allen Hair | eb1d7fe | 2015-05-18 15:07:49 -0700 | [diff] [blame] | 198 | private void putAvgMaxDouble(Bundle metrics, String averageKey, String maxKey, |
| 199 | List<Double> values) { |
| 200 | |
| 201 | metrics.putDouble(averageKey, MetricsHelper.computeAverageFloat(values)); |
| 202 | metrics.putDouble(maxKey, Collections.max(values)); |
| 203 | } |
| 204 | |
Allen Hair | d6d9f31 | 2015-05-21 15:54:46 -0700 | [diff] [blame^] | 205 | private List<Double> transformToPercentage(List<Integer> values, List<Integer> totals) { |
| 206 | List<Double> ret = new ArrayList<Double>(values.size()); |
| 207 | |
| 208 | Iterator<Integer> valuesItr = values.iterator(); |
| 209 | Iterator<Integer> totalsItr = totals.iterator(); |
| 210 | while (valuesItr.hasNext()) { |
| 211 | double value = (double)valuesItr.next().intValue(); |
| 212 | double total = (double)totalsItr.next().intValue(); |
| 213 | |
| 214 | ret.add(value / total * 100.0f); |
| 215 | } |
| 216 | |
| 217 | return ret; |
| 218 | } |
| 219 | |
Allen Hair | aefcdcd | 2015-03-03 15:24:53 -0800 | [diff] [blame] | 220 | public Bundle getMetrics() { |
| 221 | Bundle metrics = new Bundle(); |
Allen Hair | 0777807 | 2015-03-06 16:58:26 -0800 | [diff] [blame] | 222 | |
Allen Hair | d6d9f31 | 2015-05-21 15:54:46 -0700 | [diff] [blame^] | 223 | // Retrieve the total number of frames |
| 224 | List<Integer> totals = (List<Integer>)mAccumulatedStats.get(JankStat.TOTAL_FRAMES); |
| 225 | |
Allen Hair | 0777807 | 2015-03-06 16:58:26 -0800 | [diff] [blame] | 226 | // Store average and max jank |
Allen Hair | eb1d7fe | 2015-05-18 15:07:49 -0700 | [diff] [blame] | 227 | putAvgMaxDouble(metrics, GfxMonitor.KEY_AVG_NUM_JANKY, GfxMonitor.KEY_MAX_NUM_JANKY, |
| 228 | (List<Double>)mAccumulatedStats.get(JankStat.NUM_JANKY)); |
Allen Hair | 0777807 | 2015-03-06 16:58:26 -0800 | [diff] [blame] | 229 | |
Allen Hair | ef392f0 | 2015-03-09 15:43:51 -0700 | [diff] [blame] | 230 | // Store average and max percentile frame times |
Allen Hair | eb1d7fe | 2015-05-18 15:07:49 -0700 | [diff] [blame] | 231 | putAvgMaxInteger(metrics, GfxMonitor.KEY_AVG_FRAME_TIME_90TH_PERCENTILE, |
Allen Hair | 9c0e562 | 2015-04-08 15:13:18 -0700 | [diff] [blame] | 232 | GfxMonitor.KEY_MAX_FRAME_TIME_90TH_PERCENTILE, |
Allen Hair | eb1d7fe | 2015-05-18 15:07:49 -0700 | [diff] [blame] | 233 | (List<Integer>)mAccumulatedStats.get(JankStat.FRAME_TIME_90TH)); |
| 234 | putAvgMaxInteger(metrics, GfxMonitor.KEY_AVG_FRAME_TIME_95TH_PERCENTILE, |
Allen Hair | 9c0e562 | 2015-04-08 15:13:18 -0700 | [diff] [blame] | 235 | GfxMonitor.KEY_MAX_FRAME_TIME_95TH_PERCENTILE, |
Allen Hair | eb1d7fe | 2015-05-18 15:07:49 -0700 | [diff] [blame] | 236 | (List<Integer>)mAccumulatedStats.get(JankStat.FRAME_TIME_95TH)); |
| 237 | putAvgMaxInteger(metrics, GfxMonitor.KEY_AVG_FRAME_TIME_99TH_PERCENTILE, |
Allen Hair | 9c0e562 | 2015-04-08 15:13:18 -0700 | [diff] [blame] | 238 | GfxMonitor.KEY_MAX_FRAME_TIME_99TH_PERCENTILE, |
Allen Hair | eb1d7fe | 2015-05-18 15:07:49 -0700 | [diff] [blame] | 239 | (List<Integer>)mAccumulatedStats.get(JankStat.FRAME_TIME_99TH)); |
Allen Hair | ef392f0 | 2015-03-09 15:43:51 -0700 | [diff] [blame] | 240 | |
Allen Hair | 0777807 | 2015-03-06 16:58:26 -0800 | [diff] [blame] | 241 | // Store average and max missed vsync |
Allen Hair | d6d9f31 | 2015-05-21 15:54:46 -0700 | [diff] [blame^] | 242 | List<Double> missedVsyncPercent = transformToPercentage( |
| 243 | (List<Integer>)mAccumulatedStats.get(JankStat.NUM_MISSED_VSYNC), totals); |
| 244 | putAvgMaxDouble(metrics, GfxMonitor.KEY_AVG_MISSED_VSYNC, GfxMonitor.KEY_MAX_MISSED_VSYNC, |
| 245 | missedVsyncPercent); |
Allen Hair | 0777807 | 2015-03-06 16:58:26 -0800 | [diff] [blame] | 246 | |
| 247 | // Store average and max high input latency |
Allen Hair | d6d9f31 | 2015-05-21 15:54:46 -0700 | [diff] [blame^] | 248 | List<Double> highInputLatencyPercent = transformToPercentage( |
| 249 | (List<Integer>)mAccumulatedStats.get(JankStat.NUM_HIGH_INPUT_LATENCY), totals); |
| 250 | putAvgMaxDouble(metrics, GfxMonitor.KEY_AVG_HIGH_INPUT_LATENCY, |
| 251 | GfxMonitor.KEY_MAX_HIGH_INPUT_LATENCY, highInputLatencyPercent); |
Allen Hair | 0777807 | 2015-03-06 16:58:26 -0800 | [diff] [blame] | 252 | |
| 253 | // Store average and max slow ui thread |
Allen Hair | d6d9f31 | 2015-05-21 15:54:46 -0700 | [diff] [blame^] | 254 | List<Double> slowUiThreadPercent = transformToPercentage( |
| 255 | (List<Integer>)mAccumulatedStats.get(JankStat.NUM_SLOW_UI_THREAD), totals); |
| 256 | putAvgMaxDouble(metrics, GfxMonitor.KEY_AVG_SLOW_UI_THREAD, |
| 257 | GfxMonitor.KEY_MAX_SLOW_UI_THREAD, slowUiThreadPercent); |
Allen Hair | 0777807 | 2015-03-06 16:58:26 -0800 | [diff] [blame] | 258 | |
| 259 | // Store average and max slow bitmap uploads |
Allen Hair | d6d9f31 | 2015-05-21 15:54:46 -0700 | [diff] [blame^] | 260 | List<Double> slowBitMapUploadsPercent = transformToPercentage( |
| 261 | (List<Integer>)mAccumulatedStats.get(JankStat.NUM_SLOW_BITMAP_UPLOADS), totals); |
| 262 | putAvgMaxDouble(metrics, GfxMonitor.KEY_AVG_SLOW_BITMAP_UPLOADS, |
| 263 | GfxMonitor.KEY_MAX_SLOW_BITMAP_UPLOADS, slowBitMapUploadsPercent); |
Allen Hair | 0777807 | 2015-03-06 16:58:26 -0800 | [diff] [blame] | 264 | |
| 265 | // Store average and max slow draw |
Allen Hair | d6d9f31 | 2015-05-21 15:54:46 -0700 | [diff] [blame^] | 266 | List<Double> slowDrawPercent = transformToPercentage( |
| 267 | (List<Integer>)mAccumulatedStats.get(JankStat.NUM_SLOW_DRAW), totals); |
| 268 | putAvgMaxDouble(metrics, GfxMonitor.KEY_AVG_SLOW_DRAW, GfxMonitor.KEY_MAX_SLOW_DRAW, |
| 269 | slowDrawPercent); |
Allen Hair | aefcdcd | 2015-03-03 15:24:53 -0800 | [diff] [blame] | 270 | |
| 271 | return metrics; |
| 272 | } |
| 273 | |
| 274 | private String getMatchGroup(String input, Pattern pattern, int groupIndex) { |
| 275 | String ret = null; |
| 276 | Matcher matcher = pattern.matcher(input); |
| 277 | if (matcher.matches()) { |
| 278 | ret = matcher.group(groupIndex); |
| 279 | } |
| 280 | return ret; |
| 281 | } |
| 282 | } |