blob: 0b3a87bd6d7a9d26f563930caa44ce1dbde10eed [file] [log] [blame]
Allen Hairaefcdcd2015-03-03 15:24:53 -08001/*
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
17package android.support.test.jank.internal;
18
19import android.app.UiAutomation;
20import android.os.Bundle;
21import android.os.ParcelFileDescriptor;
Allen Hair07778072015-03-06 16:58:26 -080022import android.support.test.jank.GfxMonitor;
Allen Hairaefcdcd2015-03-03 15:24:53 -080023
24import java.io.BufferedReader;
Allen Hairaefcdcd2015-03-03 15:24:53 -080025import java.io.InputStreamReader;
26import java.io.IOException;
27import java.util.ArrayList;
Allen Hair07778072015-03-06 16:58:26 -080028import java.util.Collections;
Allen Hair9c0e5622015-04-08 15:13:18 -070029import java.util.EnumMap;
Allen Haird6d9f312015-05-21 15:54:46 -070030import java.util.Iterator;
Allen Hair9c0e5622015-04-08 15:13:18 -070031import java.util.Map;
Allen Hairaefcdcd2015-03-03 15:24:53 -080032import java.util.List;
33import java.util.regex.Pattern;
34import java.util.regex.Matcher;
35
36import 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 */
44class GfxMonitorImpl implements JankMonitor {
45
Allen Hairaefcdcd2015-03-03 15:24:53 -080046 // Patterns used for parsing dumpsys gfxinfo output
Allen Hair9c0e5622015-04-08 15:13:18 -070047 public enum JankStat {
Allen Haireb1d7fe2015-05-18 15:07:49 -070048 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 Hair9c0e5622015-04-08 15:13:18 -070061
62 private boolean mSuccessfulParse = false;
63 private Pattern mParsePattern;
64 private int mGroupIndex;
Allen Haireb1d7fe2015-05-18 15:07:49 -070065 private Class mType;
Allen Hair9c0e5622015-04-08 15:13:18 -070066
Allen Haireb1d7fe2015-05-18 15:07:49 -070067 JankStat(Pattern pattern, int groupIndex, Class type) {
Allen Hair9c0e5622015-04-08 15:13:18 -070068 mParsePattern = pattern;
69 mGroupIndex = groupIndex;
Allen Haireb1d7fe2015-05-18 15:07:49 -070070 mType = type;
Allen Hair9c0e5622015-04-08 15:13:18 -070071 }
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 Haireb1d7fe2015-05-18 15:07:49 -070090
91 Class getType() {
92 return mType;
93 }
Allen Hair9c0e5622015-04-08 15:13:18 -070094 }
95
96 // Metrics accumulated for each iteration
Allen Haireb1d7fe2015-05-18 15:07:49 -070097 private Map<JankStat, List<? extends Number>> mAccumulatedStats =
98 new EnumMap<JankStat, List<? extends Number>>(JankStat.class);
99
Allen Hairaefcdcd2015-03-03 15:24:53 -0800100
101 // Used to invoke dumpsys gfxinfo
102 private UiAutomation mUiAutomation;
103 private String mProcess;
104
Allen Hairaefcdcd2015-03-03 15:24:53 -0800105
106 public GfxMonitorImpl(UiAutomation automation, String process) {
107 mUiAutomation = automation;
108 mProcess = process;
Allen Hair9c0e5622015-04-08 15:13:18 -0700109
110 for (JankStat stat : JankStat.values()) {
Allen Haireb1d7fe2015-05-18 15:07:49 -0700111 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 Hair9c0e5622015-04-08 15:13:18 -0700119 }
Allen Hairaefcdcd2015-03-03 15:24:53 -0800120 }
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 Hair9c0e5622015-04-08 15:13:18 -0700135
Allen Hairaefcdcd2015-03-03 15:24:53 -0800136 @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 Hair9c0e5622015-04-08 15:13:18 -0700143 // 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 Hairaefcdcd2015-03-03 15:24:53 -0800155 String line;
156 while ((line = stream.readLine()) != null) {
Allen Hair9c0e5622015-04-08 15:13:18 -0700157
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 Haireb1d7fe2015-05-18 15:07:49 -0700164 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 Hair9c0e5622015-04-08 15:13:18 -0700174 break;
175 }
Allen Hairaefcdcd2015-03-03 15:24:53 -0800176 }
177 }
Allen Hairaefcdcd2015-03-03 15:24:53 -0800178
Allen Hair9c0e5622015-04-08 15:13:18 -0700179 // 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 Hairaefcdcd2015-03-03 15:24:53 -0800185 }
Allen Hairaefcdcd2015-03-03 15:24:53 -0800186
Allen Haireb1d7fe2015-05-18 15:07:49 -0700187 List<Integer> totalFrames = (List<Integer>)mAccumulatedStats.get(JankStat.TOTAL_FRAMES);
Allen Hair9c0e5622015-04-08 15:13:18 -0700188 return totalFrames.get(totalFrames.size()-1);
189 }
Allen Hairaefcdcd2015-03-03 15:24:53 -0800190
Allen Haireb1d7fe2015-05-18 15:07:49 -0700191 private void putAvgMaxInteger(Bundle metrics, String averageKey, String maxKey,
Allen Hair9c0e5622015-04-08 15:13:18 -0700192 List<Integer> values) {
Allen Hairef392f02015-03-09 15:43:51 -0700193
Allen Hair9c0e5622015-04-08 15:13:18 -0700194 metrics.putDouble(averageKey, MetricsHelper.computeAverageInt(values));
195 metrics.putInt(maxKey, Collections.max(values));
Allen Hairaefcdcd2015-03-03 15:24:53 -0800196 }
197
Allen Haireb1d7fe2015-05-18 15:07:49 -0700198 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 Haird6d9f312015-05-21 15:54:46 -0700205 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 Hairaefcdcd2015-03-03 15:24:53 -0800220 public Bundle getMetrics() {
221 Bundle metrics = new Bundle();
Allen Hair07778072015-03-06 16:58:26 -0800222
Allen Haird6d9f312015-05-21 15:54:46 -0700223 // Retrieve the total number of frames
224 List<Integer> totals = (List<Integer>)mAccumulatedStats.get(JankStat.TOTAL_FRAMES);
225
Allen Hair07778072015-03-06 16:58:26 -0800226 // Store average and max jank
Allen Haireb1d7fe2015-05-18 15:07:49 -0700227 putAvgMaxDouble(metrics, GfxMonitor.KEY_AVG_NUM_JANKY, GfxMonitor.KEY_MAX_NUM_JANKY,
228 (List<Double>)mAccumulatedStats.get(JankStat.NUM_JANKY));
Allen Hair07778072015-03-06 16:58:26 -0800229
Allen Hairef392f02015-03-09 15:43:51 -0700230 // Store average and max percentile frame times
Allen Haireb1d7fe2015-05-18 15:07:49 -0700231 putAvgMaxInteger(metrics, GfxMonitor.KEY_AVG_FRAME_TIME_90TH_PERCENTILE,
Allen Hair9c0e5622015-04-08 15:13:18 -0700232 GfxMonitor.KEY_MAX_FRAME_TIME_90TH_PERCENTILE,
Allen Haireb1d7fe2015-05-18 15:07:49 -0700233 (List<Integer>)mAccumulatedStats.get(JankStat.FRAME_TIME_90TH));
234 putAvgMaxInteger(metrics, GfxMonitor.KEY_AVG_FRAME_TIME_95TH_PERCENTILE,
Allen Hair9c0e5622015-04-08 15:13:18 -0700235 GfxMonitor.KEY_MAX_FRAME_TIME_95TH_PERCENTILE,
Allen Haireb1d7fe2015-05-18 15:07:49 -0700236 (List<Integer>)mAccumulatedStats.get(JankStat.FRAME_TIME_95TH));
237 putAvgMaxInteger(metrics, GfxMonitor.KEY_AVG_FRAME_TIME_99TH_PERCENTILE,
Allen Hair9c0e5622015-04-08 15:13:18 -0700238 GfxMonitor.KEY_MAX_FRAME_TIME_99TH_PERCENTILE,
Allen Haireb1d7fe2015-05-18 15:07:49 -0700239 (List<Integer>)mAccumulatedStats.get(JankStat.FRAME_TIME_99TH));
Allen Hairef392f02015-03-09 15:43:51 -0700240
Allen Hair07778072015-03-06 16:58:26 -0800241 // Store average and max missed vsync
Allen Haird6d9f312015-05-21 15:54:46 -0700242 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 Hair07778072015-03-06 16:58:26 -0800246
247 // Store average and max high input latency
Allen Haird6d9f312015-05-21 15:54:46 -0700248 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 Hair07778072015-03-06 16:58:26 -0800252
253 // Store average and max slow ui thread
Allen Haird6d9f312015-05-21 15:54:46 -0700254 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 Hair07778072015-03-06 16:58:26 -0800258
259 // Store average and max slow bitmap uploads
Allen Haird6d9f312015-05-21 15:54:46 -0700260 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 Hair07778072015-03-06 16:58:26 -0800264
265 // Store average and max slow draw
Allen Haird6d9f312015-05-21 15:54:46 -0700266 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 Hairaefcdcd2015-03-03 15:24:53 -0800270
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}