blob: bc1abd53cad33768361007f08a39c4bd42a8ac8b [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.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
private static final Pattern TOTAL_FRAMES_PATTERN =
Pattern.compile("\\s*Total frames rendered: (\\d+)");
private static final Pattern JANKY_FRAMES_PATTERN =
Pattern.compile("\\s*Janky frames: (\\d+) \\(.*\\)");
private static final Pattern MISSED_VSYNC_PATTERN =
Pattern.compile("\\s*Number Missed Vsync: (\\d+)");
private static final Pattern INPUT_LATENCY_PATTERN =
Pattern.compile("\\s*Number High input latency: (\\d+)");
private static final Pattern SLOW_UI_PATTERN =
Pattern.compile("\\s*Number Slow UI thread: (\\d+)");
private static final Pattern SLOW_BITMAP_PATTERN =
Pattern.compile("\\s*Number Slow bitmap uploads: (\\d+)");
private static final Pattern SLOW_DRAW_PATTERN =
Pattern.compile("\\s*Number Slow draw: (\\d+)");
private static final Pattern FRAME_TIME_90TH_PERCENTILE_PATTERN =
Pattern.compile("\\s*90th percentile: (\\d+)ms");
private static final Pattern FRAME_TIME_95TH_PERCENTILE_PATTERN =
Pattern.compile("\\s*95th percentile: (\\d+)ms");
private static final Pattern FRAME_TIME_99TH_PERCENTILE_PATTERN =
Pattern.compile("\\s*99th percentile: (\\d+)ms");
// Used to invoke dumpsys gfxinfo
private UiAutomation mUiAutomation;
private String mProcess;
// Metrics accumulated for each iteration
private List<Integer> jankyFrames = new ArrayList<Integer>();
private List<Integer> missedVsync = new ArrayList<Integer>();
private List<Integer> highInputLatency = new ArrayList<Integer>();
private List<Integer> slowUiThread = new ArrayList<Integer>();
private List<Integer> slowBitmapUploads = new ArrayList<Integer>();
private List<Integer> slowDraw = new ArrayList<Integer>();
private List<Integer> frameTime90thPercentile = new ArrayList<Integer>();
private List<Integer> frameTime95thPercentile = new ArrayList<Integer>();
private List<Integer> frameTime99thPercentile = new ArrayList<Integer>();
public GfxMonitorImpl(UiAutomation automation, String process) {
mUiAutomation = automation;
mProcess = process;
}
@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)));
// Wait until we enter the frame stats section
String line;
while ((line = stream.readLine()) != null) {
if (line.startsWith("Frame stats:")) {
break;
}
}
Assert.assertTrue("Failed to locate frame stats in gfxinfo output",
line != null && line.startsWith("Frame stats:"));
// The frame stats section has the following output:
// Frame stats:
// 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: #
// Get Total Frames
String part;
if ((part = getMatchGroup(stream.readLine(), TOTAL_FRAMES_PATTERN, 1)) == null) {
Assert.fail("Failed to parse total frames");
}
int totalFrames = Integer.parseInt(part);
// Get Num Janky
if ((part = getMatchGroup(stream.readLine(), JANKY_FRAMES_PATTERN, 1)) == null) {
Assert.fail("Failed to parse janky frames");
}
jankyFrames.add(Integer.parseInt(part));
// Get 90th percentile
if ((part = getMatchGroup(stream.readLine(), FRAME_TIME_90TH_PERCENTILE_PATTERN, 1)) == null) {
Assert.fail("Failed to parse 90th percentile");
}
frameTime90thPercentile.add(Integer.parseInt(part));
// Get 95th percentile
if ((part = getMatchGroup(stream.readLine(), FRAME_TIME_95TH_PERCENTILE_PATTERN, 1)) == null) {
Assert.fail("Failed to parse 95th percentile");
}
frameTime95thPercentile.add(Integer.parseInt(part));
// Get 99th percentile
if ((part = getMatchGroup(stream.readLine(), FRAME_TIME_99TH_PERCENTILE_PATTERN, 1)) == null) {
Assert.fail("Failed to parse 99th percentile");
}
frameTime99thPercentile.add(Integer.parseInt(part));
// Get Missed Vsync
if ((part = getMatchGroup(stream.readLine(), MISSED_VSYNC_PATTERN, 1)) == null) {
Assert.fail("Failed to parse number missed vsync");
}
missedVsync.add(Integer.parseInt(part));
// Get High input latency
if ((part = getMatchGroup(stream.readLine(), INPUT_LATENCY_PATTERN, 1)) == null) {
Assert.fail("Failed to parse number high input latency");
}
highInputLatency.add(Integer.parseInt(part));
// Get Slow UI thread
if ((part = getMatchGroup(stream.readLine(), SLOW_UI_PATTERN, 1)) == null) {
Assert.fail("Failed to parse number slow ui thread");
}
slowUiThread.add(Integer.parseInt(part));
// Get Slow bitmap uploads
if ((part = getMatchGroup(stream.readLine(), SLOW_BITMAP_PATTERN, 1)) == null) {
Assert.fail("Failed to parse number slow bitmap uploads");
}
slowBitmapUploads.add(Integer.parseInt(part));
// Get Slow draw
if ((part = getMatchGroup(stream.readLine(), SLOW_DRAW_PATTERN, 1)) == null) {
Assert.fail("Failed to parse number slow draw");
}
slowDraw.add(Integer.parseInt(part));
return totalFrames;
}
public Bundle getMetrics() {
Bundle metrics = new Bundle();
// Store average and max jank
metrics.putDouble(GfxMonitor.KEY_AVG_NUM_JANKY,
MetricsHelper.computeAverageInt(jankyFrames));
metrics.putInt(GfxMonitor.KEY_MAX_NUM_JANKY, Collections.max(jankyFrames));
// Store average and max percentile frame times
metrics.putDouble(GfxMonitor.KEY_AVG_FRAME_TIME_90TH_PERCENTILE,
MetricsHelper.computeAverageInt(frameTime90thPercentile));
metrics.putInt(GfxMonitor.KEY_MAX_FRAME_TIME_90TH_PERCENTILE,
Collections.max(frameTime90thPercentile));
metrics.putDouble(GfxMonitor.KEY_AVG_FRAME_TIME_95TH_PERCENTILE,
MetricsHelper.computeAverageInt(frameTime95thPercentile));
metrics.putInt(GfxMonitor.KEY_MAX_FRAME_TIME_95TH_PERCENTILE,
Collections.max(frameTime95thPercentile));
metrics.putDouble(GfxMonitor.KEY_AVG_FRAME_TIME_99TH_PERCENTILE,
MetricsHelper.computeAverageInt(frameTime99thPercentile));
metrics.putInt(GfxMonitor.KEY_MAX_FRAME_TIME_99TH_PERCENTILE,
Collections.max(frameTime99thPercentile));
// Store average and max missed vsync
metrics.putDouble(GfxMonitor.KEY_AVG_MISSED_VSYNC,
MetricsHelper.computeAverageInt(missedVsync));
metrics.putInt(GfxMonitor.KEY_MAX_MISSED_VSYNC, Collections.max(missedVsync));
// Store average and max high input latency
metrics.putDouble(GfxMonitor.KEY_AVG_HIGH_INPUT_LATENCY,
MetricsHelper.computeAverageInt(highInputLatency));
metrics.putInt(GfxMonitor.KEY_MAX_HIGH_INPUT_LATENCY, Collections.max(highInputLatency));
// Store average and max slow ui thread
metrics.putDouble(GfxMonitor.KEY_AVG_SLOW_UI_THREAD,
MetricsHelper.computeAverageInt(slowUiThread));
metrics.putInt(GfxMonitor.KEY_MAX_SLOW_UI_THREAD, Collections.max(slowUiThread));
// Store average and max slow bitmap uploads
metrics.putDouble(GfxMonitor.KEY_AVG_SLOW_BITMAP_UPLOADS,
MetricsHelper.computeAverageInt(slowUiThread));
metrics.putInt(GfxMonitor.KEY_MAX_SLOW_BITMAP_UPLOADS, Collections.max(slowUiThread));
// Store average and max slow draw
metrics.putDouble(GfxMonitor.KEY_AVG_SLOW_DRAW, MetricsHelper.computeAverageInt(slowDraw));
metrics.putInt(GfxMonitor.KEY_MAX_SLOW_DRAW, Collections.max(slowDraw));
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;
}
}