blob: da6e05ac051bb3896a01de332444d9aa4f289998 [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 com.android.benchmark.results;
import android.annotation.TargetApi;
import android.view.FrameMetrics;
import org.apache.commons.math.stat.descriptive.DescriptiveStatistics;
import org.apache.commons.math.stat.descriptive.SummaryStatistics;
import java.util.ArrayList;
import java.util.List;
/**
* Utility for storing and analyzing UI benchmark results.
*/
@TargetApi(24)
public class UiBenchmarkResult {
private static final int BASE_SCORE = 100;
private static final int ZERO_SCORE_TOTAL_DURATION_MS = 32;
private static final int JANK_PENALTY_THRESHOLD_MS = 12;
private static final int ZERO_SCORE_ABOVE_THRESHOLD_MS =
ZERO_SCORE_TOTAL_DURATION_MS - JANK_PENALTY_THRESHOLD_MS;
private static final double JANK_PENALTY_PER_MS_ABOVE_THRESHOLD =
BASE_SCORE / (double)ZERO_SCORE_ABOVE_THRESHOLD_MS;
private static final int CONSISTENCY_BONUS_MAX = 100;
private static final int METRIC_WAS_JANKY = -1;
private static final int[] METRICS = new int[] {
FrameMetrics.UNKNOWN_DELAY_DURATION,
FrameMetrics.INPUT_HANDLING_DURATION,
FrameMetrics.ANIMATION_DURATION,
FrameMetrics.LAYOUT_MEASURE_DURATION,
FrameMetrics.DRAW_DURATION,
FrameMetrics.SYNC_DURATION,
FrameMetrics.COMMAND_ISSUE_DURATION,
FrameMetrics.SWAP_BUFFERS_DURATION,
FrameMetrics.TOTAL_DURATION,
};
public static final int FRAME_PERIOD_MS = 16;
private final DescriptiveStatistics[] mStoredStatistics;
public UiBenchmarkResult(List<FrameMetrics> instances) {
mStoredStatistics = new DescriptiveStatistics[METRICS.length];
insertMetrics(instances);
}
public UiBenchmarkResult(double[] values) {
mStoredStatistics = new DescriptiveStatistics[METRICS.length];
insertValues(values);
}
public void update(List<FrameMetrics> instances) {
insertMetrics(instances);
}
public void update(double[] values) {
insertValues(values);
}
public double getAverage(int id) {
int pos = getMetricPosition(id);
return mStoredStatistics[pos].getMean();
}
public double getMinimum(int id) {
int pos = getMetricPosition(id);
return mStoredStatistics[pos].getMin();
}
public double getMaximum(int id) {
int pos = getMetricPosition(id);
return mStoredStatistics[pos].getMax();
}
public int getMaximumIndex(int id) {
int pos = getMetricPosition(id);
double[] storedMetrics = mStoredStatistics[pos].getValues();
int maxIdx = 0;
for (int i = 0; i < storedMetrics.length; i++) {
if (storedMetrics[i] >= storedMetrics[maxIdx]) {
maxIdx = i;
}
}
return maxIdx;
}
public double getMetricAtIndex(int index, int metricId) {
return mStoredStatistics[getMetricPosition(metricId)].getElement(index);
}
public double getPercentile(int id, int percentile) {
if (percentile > 100) percentile = 100;
if (percentile < 0) percentile = 0;
int metricPos = getMetricPosition(id);
return mStoredStatistics[metricPos].getPercentile(percentile);
}
public int getTotalFrameCount() {
if (mStoredStatistics.length == 0) {
return 0;
}
return (int) mStoredStatistics[0].getN();
}
public int getScore() {
SummaryStatistics badFramesStats = new SummaryStatistics();
int totalFrameCount = getTotalFrameCount();
for (int i = 0; i < totalFrameCount; i++) {
double totalDuration = getMetricAtIndex(i, FrameMetrics.TOTAL_DURATION);
if (totalDuration >= 12) {
badFramesStats.addValue(totalDuration);
}
}
int length = getSortedJankFrameIndices().length;
double jankFrameCount = 100 * length / (double) totalFrameCount;
System.out.println("Mean: " + badFramesStats.getMean() + " JankP: " + jankFrameCount
+ " StdDev: " + badFramesStats.getStandardDeviation() +
" Count Bad: " + badFramesStats.getN() + " Count Jank: " + length);
return (int) Math.round(
(badFramesStats.getMean()) * jankFrameCount * badFramesStats.getStandardDeviation());
}
public int getJankPenalty() {
double total95th = mStoredStatistics[getMetricPosition(FrameMetrics.TOTAL_DURATION)]
.getPercentile(95);
System.out.println("95: " + total95th);
double aboveThreshold = total95th - JANK_PENALTY_THRESHOLD_MS;
if (aboveThreshold <= 0) {
return 0;
}
if (aboveThreshold > ZERO_SCORE_ABOVE_THRESHOLD_MS) {
return BASE_SCORE;
}
return (int) Math.ceil(JANK_PENALTY_PER_MS_ABOVE_THRESHOLD * aboveThreshold);
}
public int getConsistencyBonus() {
DescriptiveStatistics totalDurationStats =
mStoredStatistics[getMetricPosition(FrameMetrics.TOTAL_DURATION)];
double standardDeviation = totalDurationStats.getStandardDeviation();
if (standardDeviation == 0) {
return CONSISTENCY_BONUS_MAX;
}
// 1 / CV of the total duration.
double bonus = totalDurationStats.getMean() / standardDeviation;
return (int) Math.min(Math.round(bonus), CONSISTENCY_BONUS_MAX);
}
public int[] getSortedJankFrameIndices() {
ArrayList<Integer> jankFrameIndices = new ArrayList<>();
boolean tripleBuffered = false;
int totalFrameCount = getTotalFrameCount();
int totalDurationPos = getMetricPosition(FrameMetrics.TOTAL_DURATION);
for (int i = 0; i < totalFrameCount; i++) {
double thisDuration = mStoredStatistics[totalDurationPos].getElement(i);
if (!tripleBuffered) {
if (thisDuration > FRAME_PERIOD_MS) {
tripleBuffered = true;
jankFrameIndices.add(i);
}
} else {
if (thisDuration > 2 * FRAME_PERIOD_MS) {
tripleBuffered = false;
jankFrameIndices.add(i);
}
}
}
int[] res = new int[jankFrameIndices.size()];
int i = 0;
for (Integer index : jankFrameIndices) {
res[i++] = index;
}
return res;
}
private int getMetricPosition(int id) {
for (int i = 0; i < METRICS.length; i++) {
if (id == METRICS[i]) {
return i;
}
}
return -1;
}
private void insertMetrics(List<FrameMetrics> instances) {
for (FrameMetrics frame : instances) {
for (int i = 0; i < METRICS.length; i++) {
DescriptiveStatistics stats = mStoredStatistics[i];
if (stats == null) {
stats = new DescriptiveStatistics();
mStoredStatistics[i] = stats;
}
mStoredStatistics[i].addValue(frame.getMetric(METRICS[i]) / (double) 1000000);
}
}
}
private void insertValues(double[] values) {
if (values.length != METRICS.length) {
throw new IllegalArgumentException("invalid values array");
}
for (int i = 0; i < values.length; i++) {
DescriptiveStatistics stats = mStoredStatistics[i];
if (stats == null) {
stats = new DescriptiveStatistics();
mStoredStatistics[i] = stats;
}
mStoredStatistics[i].addValue(values[i]);
}
}
}