blob: 15077ee68f225ade3a42c66eb5a60f250419b026 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.support.test.jank.internal;
import android.app.UiAutomation;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.support.test.jank.GfxMonitor;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
import java.util.Map;
import java.util.List;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import junit.framework.Assert;
/**
* Monitors dumpsys gfxinfo to detect janky frames.
*
* Reports average and max jank. Additionally reports summary statistics for common problems that
* can lead to dropped frames.
*/
class GfxMonitorImpl implements JankMonitor {
// Patterns used for parsing dumpsys gfxinfo output
public enum JankStat {
TOTAL_FRAMES(Pattern.compile("\\s*Total frames rendered: (\\d+)"), 1),
NUM_JANKY(Pattern.compile("\\s*Janky frames: (\\d+) \\(.*\\)"), 1),
FRAME_TIME_90TH(Pattern.compile("\\s*90th percentile: (\\d+)ms"), 1),
FRAME_TIME_95TH(Pattern.compile("\\s*95th percentile: (\\d+)ms"), 1),
FRAME_TIME_99TH(Pattern.compile("\\s*99th percentile: (\\d+)ms"), 1),
NUM_MISSED_VSYNC(Pattern.compile("\\s*Number Missed Vsync: (\\d+)"), 1),
NUM_HIGH_INPUT_LATENCY(Pattern.compile("\\s*Number High input latency: (\\d+)"), 1),
NUM_SLOW_UI_THREAD(Pattern.compile("\\s*Number Slow UI thread: (\\d+)"), 1),
NUM_SLOW_BITMAP_UPLOADS(Pattern.compile("\\s*Number Slow bitmap uploads: (\\d+)"), 1),
NUM_SLOW_DRAW(Pattern.compile("\\s*Number Slow draw: (\\d+)"), 1);
private boolean mSuccessfulParse = false;
private Pattern mParsePattern;
private int mGroupIndex;
JankStat(Pattern pattern, int groupIndex) {
mParsePattern = pattern;
mGroupIndex = groupIndex;
}
String parse(String line) {
String ret = null;
Matcher matcher = mParsePattern.matcher(line);
if (matcher.matches()) {
ret = matcher.group(mGroupIndex);
mSuccessfulParse = true;
}
return ret;
}
boolean wasParsedSuccessfully() {
return mSuccessfulParse;
}
void reset() {
mSuccessfulParse = false;
}
}
// Metrics accumulated for each iteration
private Map<JankStat, List<Integer>> mAccumulatedStats =
new EnumMap<JankStat, List<Integer>>(JankStat.class);
// Used to invoke dumpsys gfxinfo
private UiAutomation mUiAutomation;
private String mProcess;
public GfxMonitorImpl(UiAutomation automation, String process) {
mUiAutomation = automation;
mProcess = process;
for (JankStat stat : JankStat.values()) {
mAccumulatedStats.put(stat, new ArrayList<Integer>());
}
}
@Override
public void startIteration() throws IOException {
// Clear out any previous data
ParcelFileDescriptor stdout = mUiAutomation.executeShellCommand(
String.format("dumpsys gfxinfo %s reset", mProcess));
// Read the output, but don't do anything with it
BufferedReader stream = new BufferedReader(new InputStreamReader(
new ParcelFileDescriptor.AutoCloseInputStream(stdout)));
while (stream.readLine() != null) {
}
}
@Override
public int stopIteration() throws IOException {
ParcelFileDescriptor stdout = mUiAutomation.executeShellCommand(
String.format("dumpsys gfxinfo %s", mProcess));
BufferedReader stream = new BufferedReader(new InputStreamReader(
new ParcelFileDescriptor.AutoCloseInputStream(stdout)));
// The frame stats section has the following output:
// Total frames rendered: ###
// Janky frames: ### (##.##%)
// 90th percentile: ##ms
// 95th percentile: ##ms
// 99th percentile: ##ms
// Number Missed Vsync: #
// Number High input latency: #
// Number Slow UI thread: #
// Number Slow bitmap uploads: #
// Number Slow draw: #
String line;
while ((line = stream.readLine()) != null) {
// Attempt to parse the line as a frame stat value
for (JankStat stat : JankStat.values()) {
String part;
if ((part = stat.parse(line)) != null) {
// Parse was successful. Add the numeric value to the accumulated list of values
// for that stat.
mAccumulatedStats.get(stat).add(Integer.parseInt(part));
break;
}
}
}
// Make sure we found all the stats
for (JankStat stat : JankStat.values()) {
if (!stat.wasParsedSuccessfully()) {
Assert.fail(String.format("Failed to parse %s", stat.name()));
}
stat.reset();
}
List<Integer> totalFrames = mAccumulatedStats.get(JankStat.TOTAL_FRAMES);
return totalFrames.get(totalFrames.size()-1);
}
private void putAvgMax(Bundle metrics, String averageKey, String maxKey,
List<Integer> values) {
metrics.putDouble(averageKey, MetricsHelper.computeAverageInt(values));
metrics.putInt(maxKey, Collections.max(values));
}
public Bundle getMetrics() {
Bundle metrics = new Bundle();
// Store average and max jank
putAvgMax(metrics, GfxMonitor.KEY_AVG_NUM_JANKY, GfxMonitor.KEY_MAX_NUM_JANKY,
mAccumulatedStats.get(JankStat.NUM_JANKY));
// Store average and max percentile frame times
putAvgMax(metrics, GfxMonitor.KEY_AVG_FRAME_TIME_90TH_PERCENTILE,
GfxMonitor.KEY_MAX_FRAME_TIME_90TH_PERCENTILE,
mAccumulatedStats.get(JankStat.FRAME_TIME_90TH));
putAvgMax(metrics, GfxMonitor.KEY_AVG_FRAME_TIME_95TH_PERCENTILE,
GfxMonitor.KEY_MAX_FRAME_TIME_95TH_PERCENTILE,
mAccumulatedStats.get(JankStat.FRAME_TIME_95TH));
putAvgMax(metrics, GfxMonitor.KEY_AVG_FRAME_TIME_99TH_PERCENTILE,
GfxMonitor.KEY_MAX_FRAME_TIME_99TH_PERCENTILE,
mAccumulatedStats.get(JankStat.FRAME_TIME_99TH));
// Store average and max missed vsync
putAvgMax(metrics, GfxMonitor.KEY_AVG_MISSED_VSYNC, GfxMonitor.KEY_MAX_MISSED_VSYNC,
mAccumulatedStats.get(JankStat.NUM_MISSED_VSYNC));
// Store average and max high input latency
putAvgMax(metrics, GfxMonitor.KEY_AVG_HIGH_INPUT_LATENCY,
GfxMonitor.KEY_MAX_HIGH_INPUT_LATENCY,
mAccumulatedStats.get(JankStat.NUM_HIGH_INPUT_LATENCY));
// Store average and max slow ui thread
putAvgMax(metrics, GfxMonitor.KEY_AVG_SLOW_UI_THREAD, GfxMonitor.KEY_MAX_SLOW_UI_THREAD,
mAccumulatedStats.get(JankStat.NUM_SLOW_UI_THREAD));
// Store average and max slow bitmap uploads
putAvgMax(metrics, GfxMonitor.KEY_AVG_SLOW_BITMAP_UPLOADS,
GfxMonitor.KEY_MAX_SLOW_BITMAP_UPLOADS,
mAccumulatedStats.get(JankStat.NUM_SLOW_BITMAP_UPLOADS));
// Store average and max slow draw
putAvgMax(metrics, GfxMonitor.KEY_AVG_SLOW_DRAW, GfxMonitor.KEY_MAX_SLOW_DRAW,
mAccumulatedStats.get(JankStat.NUM_SLOW_DRAW));
return metrics;
}
private String getMatchGroup(String input, Pattern pattern, int groupIndex) {
String ret = null;
Matcher matcher = pattern.matcher(input);
if (matcher.matches()) {
ret = matcher.group(groupIndex);
}
return ret;
}
}