summaryrefslogtreecommitdiff
path: root/apct-tests/perftests/utils
diff options
context:
space:
mode:
authorRiddle Hsu <riddlehsu@google.com>2019-09-24 17:56:05 -0600
committerRiddle Hsu <riddlehsu@google.com>2019-10-15 22:19:51 +0800
commitf5622d25a03695a70efcf82b0218cdaa07aff01e (patch)
tree8f331dadf87aec811b9bef346cb853bfeaaa81d7 /apct-tests/perftests/utils
parent1be1dd6bbf36a1738665f0803f05a43179a90ef7 (diff)
Add trace based window manager perf test
This may be temporary solution that parses the output of atrace in text format on the device side. Once the perfetto processing infrastructure is available (b/139542646) for generic test environment, we can migrate to use that. Bug: 131727899 Test: atest InternalWindowOperationPerfTest Change-Id: Ic0304c292e42159fb11dee37152867290808f281
Diffstat (limited to 'apct-tests/perftests/utils')
-rw-r--r--apct-tests/perftests/utils/src/android/perftests/utils/ManualBenchmarkState.java100
-rw-r--r--apct-tests/perftests/utils/src/android/perftests/utils/PerfTestActivity.java16
-rw-r--r--apct-tests/perftests/utils/src/android/perftests/utils/Stats.java24
-rw-r--r--apct-tests/perftests/utils/src/android/perftests/utils/TraceMarkParser.java232
4 files changed, 318 insertions, 54 deletions
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/ManualBenchmarkState.java b/apct-tests/perftests/utils/src/android/perftests/utils/ManualBenchmarkState.java
index ffe39e8679e1..a83254b463f4 100644
--- a/apct-tests/perftests/utils/src/android/perftests/utils/ManualBenchmarkState.java
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/ManualBenchmarkState.java
@@ -59,27 +59,37 @@ import java.util.concurrent.TimeUnit;
public final class ManualBenchmarkState {
private static final String TAG = ManualBenchmarkState.class.getSimpleName();
- @IntDef(prefix = {"STATS_REPORT"}, value = {
- STATS_REPORT_MEDIAN,
- STATS_REPORT_MEAN,
- STATS_REPORT_MIN,
- STATS_REPORT_MAX,
- STATS_REPORT_PERCENTILE90,
- STATS_REPORT_PERCENTILE95,
- STATS_REPORT_STDDEV,
- STATS_REPORT_ITERATION,
- })
- public @interface StatsReport {}
+ @Target(ElementType.ANNOTATION_TYPE)
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface StatsReport {
+ int FLAG_MEDIAN = 0x00000001;
+ int FLAG_MEAN = 0x00000002;
+ int FLAG_MIN = 0x00000004;
+ int FLAG_MAX = 0x00000008;
+ int FLAG_STDDEV = 0x00000010;
+ int FLAG_COEFFICIENT_VAR = 0x00000020;
+ int FLAG_ITERATION = 0x00000040;
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @IntDef(value = {
+ FLAG_MEDIAN,
+ FLAG_MEAN,
+ FLAG_MIN,
+ FLAG_MAX,
+ FLAG_STDDEV,
+ FLAG_COEFFICIENT_VAR,
+ FLAG_ITERATION,
+ })
+ @interface Flag {}
- public static final int STATS_REPORT_MEDIAN = 0x00000001;
- public static final int STATS_REPORT_MEAN = 0x00000002;
- public static final int STATS_REPORT_MIN = 0x00000004;
- public static final int STATS_REPORT_MAX = 0x00000008;
- public static final int STATS_REPORT_PERCENTILE90 = 0x00000010;
- public static final int STATS_REPORT_PERCENTILE95 = 0x00000020;
- public static final int STATS_REPORT_STDDEV = 0x00000040;
- public static final int STATS_REPORT_COEFFICIENT_VAR = 0x00000080;
- public static final int STATS_REPORT_ITERATION = 0x00000100;
+ /** Defines which type of statistics should output. */
+ @Flag int flags() default -1;
+ /** An array with value 0~100 to provide the percentiles. */
+ int[] percentiles() default {};
+ }
+
+ /** It means the entire {@link StatsReport} is not given. */
+ private static final int DEFAULT_STATS_REPORT = -2;
// TODO: Tune these values.
// warm-up for duration
@@ -116,8 +126,9 @@ public final class ManualBenchmarkState {
// The computation needs double precision, but long int is fine for final reporting.
private Stats mStats;
- private int mStatsReportFlags = STATS_REPORT_MEDIAN | STATS_REPORT_MEAN
- | STATS_REPORT_PERCENTILE90 | STATS_REPORT_PERCENTILE95 | STATS_REPORT_STDDEV;
+ private int mStatsReportFlags =
+ StatsReport.FLAG_MEDIAN | StatsReport.FLAG_MEAN | StatsReport.FLAG_STDDEV;
+ private int[] mStatsReportPercentiles = {90 , 95};
private boolean shouldReport(int statsReportFlag) {
return (mStatsReportFlags & statsReportFlag) != 0;
@@ -136,9 +147,10 @@ public final class ManualBenchmarkState {
if (targetTestDurationNs >= 0) {
mTargetTestDurationNs = targetTestDurationNs;
}
- final int statsReportFlags = testAnnotation.statsReportFlags();
- if (statsReportFlags >= 0) {
- mStatsReportFlags = statsReportFlags;
+ final StatsReport statsReport = testAnnotation.statsReport();
+ if (statsReport != null && statsReport.flags() != DEFAULT_STATS_REPORT) {
+ mStatsReportFlags = statsReport.flags();
+ mStatsReportPercentiles = statsReport.percentiles();
}
}
@@ -189,11 +201,20 @@ public final class ManualBenchmarkState {
}
/**
- * Adds additional result while this benchmark is running. It is used when a sequence of
+ * @return {@code true} if the benchmark is in warmup state. It can be used to skip the
+ * operations or measurements that are unnecessary while the test isn't running the
+ * actual benchmark.
+ */
+ public boolean isWarmingUp() {
+ return mState == WARMUP;
+ }
+
+ /**
+ * Adds additional result while this benchmark isn't warming up. It is used when a sequence of
* operations is executed consecutively, the duration of each operation can also be recorded.
*/
public void addExtraResult(String key, long duration) {
- if (mState != RUNNING) {
+ if (isWarmingUp()) {
return;
}
if (mExtraResults == null) {
@@ -221,31 +242,30 @@ public final class ManualBenchmarkState {
}
private void fillStatus(Bundle status, String key, Stats stats) {
- if (shouldReport(STATS_REPORT_ITERATION)) {
+ if (shouldReport(StatsReport.FLAG_ITERATION)) {
status.putLong(key + "_iteration", stats.getSize());
}
- if (shouldReport(STATS_REPORT_MEDIAN)) {
+ if (shouldReport(StatsReport.FLAG_MEDIAN)) {
status.putLong(key + "_median", stats.getMedian());
}
- if (shouldReport(STATS_REPORT_MEAN)) {
+ if (shouldReport(StatsReport.FLAG_MEAN)) {
status.putLong(key + "_mean", Math.round(stats.getMean()));
}
- if (shouldReport(STATS_REPORT_MIN)) {
+ if (shouldReport(StatsReport.FLAG_MIN)) {
status.putLong(key + "_min", stats.getMin());
}
- if (shouldReport(STATS_REPORT_MAX)) {
+ if (shouldReport(StatsReport.FLAG_MAX)) {
status.putLong(key + "_max", stats.getMax());
}
- if (shouldReport(STATS_REPORT_PERCENTILE90)) {
- status.putLong(key + "_percentile90", stats.getPercentile90());
- }
- if (shouldReport(STATS_REPORT_PERCENTILE95)) {
- status.putLong(key + "_percentile95", stats.getPercentile95());
+ if (mStatsReportPercentiles != null) {
+ for (int percentile : mStatsReportPercentiles) {
+ status.putLong(key + "_percentile" + percentile, stats.getPercentile(percentile));
+ }
}
- if (shouldReport(STATS_REPORT_STDDEV)) {
+ if (shouldReport(StatsReport.FLAG_STDDEV)) {
status.putLong(key + "_stddev", Math.round(stats.getStandardDeviation()));
}
- if (shouldReport(STATS_REPORT_COEFFICIENT_VAR)) {
+ if (shouldReport(StatsReport.FLAG_COEFFICIENT_VAR)) {
status.putLong(key + "_cv",
Math.round((100 * stats.getStandardDeviation() / stats.getMean())));
}
@@ -276,6 +296,6 @@ public final class ManualBenchmarkState {
public @interface ManualBenchmarkTest {
long warmupDurationNs() default -1;
long targetTestDurationNs() default -1;
- @StatsReport int statsReportFlags() default -1;
+ StatsReport statsReport() default @StatsReport(flags = DEFAULT_STATS_REPORT);
}
}
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/PerfTestActivity.java b/apct-tests/perftests/utils/src/android/perftests/utils/PerfTestActivity.java
index 375a90154b98..e934feb01a84 100644
--- a/apct-tests/perftests/utils/src/android/perftests/utils/PerfTestActivity.java
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/PerfTestActivity.java
@@ -19,8 +19,24 @@ package android.perftests.utils;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
+import android.os.Bundle;
+import android.view.WindowManager;
+/**
+ * A simple activity used for testing, e.g. performance of activity switching, or as a base
+ * container of testing view.
+ */
public class PerfTestActivity extends Activity {
+ public static final String INTENT_EXTRA_KEEP_SCREEN_ON = "keep_screen_on";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (getIntent().getBooleanExtra(INTENT_EXTRA_KEEP_SCREEN_ON, false)) {
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+ }
+
public static Intent createLaunchIntent(Context context) {
final Intent intent = new Intent();
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/Stats.java b/apct-tests/perftests/utils/src/android/perftests/utils/Stats.java
index f650e810c6de..fb516a818f75 100644
--- a/apct-tests/perftests/utils/src/android/perftests/utils/Stats.java
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/Stats.java
@@ -16,34 +16,34 @@
package android.perftests.utils;
+import android.annotation.IntRange;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Stats {
- private long mMedian, mMin, mMax, mPercentile90, mPercentile95;
+ private long mMedian, mMin, mMax;
private double mMean, mStandardDeviation;
- private final int mSize;
+ private final List<Long> mValues;
/* Calculate stats in constructor. */
public Stats(List<Long> values) {
- // make a copy since we're modifying it
- values = new ArrayList<>(values);
final int size = values.size();
if (size < 2) {
throw new IllegalArgumentException("At least two results are necessary.");
}
+ // Make a copy since we're modifying it.
+ mValues = values = new ArrayList<>(values);
+
Collections.sort(values);
- mSize = size;
mMin = values.get(0);
mMax = values.get(values.size() - 1);
mMedian = size % 2 == 0 ? (values.get(size / 2) + values.get(size / 2 - 1)) / 2 :
values.get(size / 2);
- mPercentile90 = getPercentile(values, 90);
- mPercentile95 = getPercentile(values, 95);
for (int i = 0; i < size; ++i) {
long result = values.get(i);
@@ -59,7 +59,7 @@ public class Stats {
}
public int getSize() {
- return mSize;
+ return mValues.size();
}
public double getMean() {
@@ -82,12 +82,8 @@ public class Stats {
return mStandardDeviation;
}
- public long getPercentile90() {
- return mPercentile90;
- }
-
- public long getPercentile95() {
- return mPercentile95;
+ public long getPercentile(@IntRange(from = 0, to = 100) int percentile) {
+ return getPercentile(mValues, percentile);
}
private static long getPercentile(List<Long> values, int percentile) {
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/TraceMarkParser.java b/apct-tests/perftests/utils/src/android/perftests/utils/TraceMarkParser.java
new file mode 100644
index 000000000000..1afed3a0be5b
--- /dev/null
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/TraceMarkParser.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2019 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.perftests.utils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiConsumer;
+import java.util.function.Predicate;
+
+/**
+ * Utility to get the slice of tracing_mark_write S,F,B,E (Async: start, finish, Sync: begin, end).
+ * Use {@link #visit(String)} to process the trace in text form. The filtered results can be
+ * obtained by {@link #forAllSlices(BiConsumer)}.
+ *
+ * @see android.os.Trace
+ */
+public class TraceMarkParser {
+ /** All slices by the name of {@link TraceMarkLine}. */
+ private final Map<String, List<TraceMarkSlice>> mSlicesMap = new HashMap<>();
+ /** The nested depth of each task-pid. */
+ private final Map<String, Integer> mDepthMap = new HashMap<>();
+ /** The start trace lines that haven't matched the corresponding end. */
+ private final Map<String, TraceMarkLine> mPendingStarts = new HashMap<>();
+
+ private final Predicate<TraceMarkLine> mTraceLineFilter;
+
+ public TraceMarkParser(Predicate<TraceMarkLine> traceLineFilter) {
+ mTraceLineFilter = traceLineFilter;
+ }
+
+ /** Only accept the trace event with the given names. */
+ public TraceMarkParser(String... traceNames) {
+ this(line -> {
+ for (String name : traceNames) {
+ if (name.equals(line.name)) {
+ return true;
+ }
+ }
+ return false;
+ });
+ }
+
+ /** Computes {@link TraceMarkSlice} by the given trace line. */
+ public void visit(String textTraceLine) {
+ final TraceMarkLine line = TraceMarkLine.parse(textTraceLine);
+ if (line == null) {
+ return;
+ }
+
+ if (line.isAsync) {
+ // Async-trace contains name in the start and finish event.
+ if (mTraceLineFilter.test(line)) {
+ if (line.isBegin) {
+ mPendingStarts.put(line.name, line);
+ } else {
+ final TraceMarkLine start = mPendingStarts.remove(line.name);
+ if (start != null) {
+ addSlice(start, line);
+ }
+ }
+ }
+ return;
+ }
+
+ int depth = 1;
+ if (line.isBegin) {
+ final Integer existingDepth = mDepthMap.putIfAbsent(line.taskPid, 1);
+ if (existingDepth != null) {
+ mDepthMap.put(line.taskPid, depth = existingDepth + 1);
+ }
+ // Sync-trace only contains name in the begin event.
+ if (mTraceLineFilter.test(line)) {
+ mPendingStarts.put(getSyncPendingStartKey(line, depth), line);
+ }
+ } else {
+ final Integer existingDepth = mDepthMap.get(line.taskPid);
+ if (existingDepth != null) {
+ depth = existingDepth;
+ mDepthMap.put(line.taskPid, existingDepth - 1);
+ }
+ final TraceMarkLine begin = mPendingStarts.remove(getSyncPendingStartKey(line, depth));
+ if (begin != null) {
+ addSlice(begin, line);
+ }
+ }
+ }
+
+ private static String getSyncPendingStartKey(TraceMarkLine line, int depth) {
+ return line.taskPid + "@" + depth;
+ }
+
+ private void addSlice(TraceMarkLine begin, TraceMarkLine end) {
+ mSlicesMap.computeIfAbsent(
+ begin.name, k -> new ArrayList<>()).add(new TraceMarkSlice(begin, end));
+ }
+
+ public void forAllSlices(BiConsumer<String, List<TraceMarkSlice>> consumer) {
+ for (Map.Entry<String, List<TraceMarkSlice>> entry : mSlicesMap.entrySet()) {
+ consumer.accept(entry.getKey(), entry.getValue());
+ }
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ forAllSlices((key, slices) -> {
+ double totalMs = 0;
+ for (TraceMarkSlice s : slices) {
+ totalMs += s.getDurarionInSeconds() * 1000;
+ }
+ sb.append(key).append(" count=").append(slices.size()).append(" avg=")
+ .append(totalMs / slices.size()).append("ms\n");
+ });
+
+ if (!mPendingStarts.isEmpty()) {
+ sb.append("[Warning] Unresolved events:").append(mPendingStarts).append("\n");
+ }
+ return sb.toString();
+ }
+
+ public static class TraceMarkSlice {
+ public final TraceMarkLine begin;
+ public final TraceMarkLine end;
+
+ TraceMarkSlice(TraceMarkLine begin, TraceMarkLine end) {
+ this.begin = begin;
+ this.end = end;
+ }
+
+ public double getDurarionInSeconds() {
+ return end.timestamp - begin.timestamp;
+ }
+ }
+
+ // taskPid timestamp name
+ // # Async:
+ // Binder:129_F-349 ( 1296) [003] ...1 12.2776: tracing_mark_write: S|1296|launching: a.test|0
+ // android.anim-135 ( 1296) [005] ...1 12.3361: tracing_mark_write: F|1296|launching: a.test|0
+ // # Normal:
+ // Binder:129_6-315 ( 1296) [007] ...1 97.4576: tracing_mark_write: B|1296|relayoutWindow: xxx
+ // ... there may have other nested begin/end
+ // Binder:129_6-315 ( 1296) [007] ...1 97.4580: tracing_mark_write: E|1296
+ public static class TraceMarkLine {
+ static final String EVENT_KEYWORD = ": tracing_mark_write: ";
+ static final char ASYNC_START = 'S';
+ static final char ASYNC_FINISH = 'F';
+ static final char SYNC_BEGIN = 'B';
+ static final char SYNC_END = 'E';
+
+ public final String taskPid;
+ public final double timestamp;
+ public final String name;
+ public final boolean isAsync;
+ public final boolean isBegin;
+
+ TraceMarkLine(String rawLine, int typePos, int type) throws IllegalArgumentException {
+ taskPid = rawLine.substring(0, rawLine.indexOf('(')).trim();
+ final int timeEnd = rawLine.indexOf(':', taskPid.length());
+ if (timeEnd < 0) {
+ throw new IllegalArgumentException("Timestamp end not found");
+ }
+ final int timeBegin = rawLine.lastIndexOf(' ', timeEnd);
+ if (timeBegin < 0) {
+ throw new IllegalArgumentException("Timestamp start not found");
+ }
+ timestamp = Double.parseDouble(rawLine.substring(timeBegin, timeEnd));
+ isAsync = type == ASYNC_START || type == ASYNC_FINISH;
+ isBegin = type == ASYNC_START || type == SYNC_BEGIN;
+
+ if (!isAsync && !isBegin) {
+ name = "";
+ } else {
+ // Get the position of the second '|' from "S|1234|name".
+ final int nameBegin = rawLine.indexOf('|', typePos + 2) + 1;
+ if (nameBegin == 0) {
+ throw new IllegalArgumentException("Name begin not found");
+ }
+ if (isAsync) {
+ // Get the name from "S|1234|name|0".
+ name = rawLine.substring(nameBegin, rawLine.lastIndexOf('|'));
+ } else {
+ name = rawLine.substring(nameBegin);
+ }
+ }
+ }
+
+ static TraceMarkLine parse(String rawLine) {
+ final int eventPos = rawLine.indexOf(EVENT_KEYWORD);
+ if (eventPos < 0) {
+ return null;
+ }
+ final int typePos = eventPos + EVENT_KEYWORD.length();
+ if (typePos >= rawLine.length()) {
+ return null;
+ }
+ final int type = rawLine.charAt(typePos);
+ if (type != ASYNC_START && type != ASYNC_FINISH
+ && type != SYNC_BEGIN && type != SYNC_END) {
+ return null;
+ }
+
+ try {
+ return new TraceMarkLine(rawLine, typePos, type);
+ } catch (IllegalArgumentException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return "TraceMarkLine{pid=" + taskPid + " time=" + timestamp + " name=" + name
+ + " async=" + isAsync + " begin=" + isBegin + "}";
+ }
+ }
+}