summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apct-tests/perftests/core/src/android/wm/InternalWindowOperationPerfTest.java121
-rw-r--r--apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java88
-rw-r--r--apct-tests/perftests/core/src/android/wm/WindowManagerPerfTestBase.java110
-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
7 files changed, 557 insertions, 134 deletions
diff --git a/apct-tests/perftests/core/src/android/wm/InternalWindowOperationPerfTest.java b/apct-tests/perftests/core/src/android/wm/InternalWindowOperationPerfTest.java
new file mode 100644
index 000000000000..c096cd22bdba
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/wm/InternalWindowOperationPerfTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.wm;
+
+import static android.perftests.utils.ManualBenchmarkState.StatsReport;
+
+import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
+import android.perftests.utils.ManualBenchmarkState;
+import android.perftests.utils.ManualBenchmarkState.ManualBenchmarkTest;
+import android.perftests.utils.PerfManualStatusReporter;
+import android.perftests.utils.TraceMarkParser;
+import android.perftests.utils.TraceMarkParser.TraceMarkSlice;
+import android.util.Log;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.lifecycle.Stage;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.concurrent.TimeUnit;
+
+/** Measure the performance of internal methods in window manager service by trace tag. */
+@LargeTest
+public class InternalWindowOperationPerfTest extends WindowManagerPerfTestBase {
+ private static final String TAG = InternalWindowOperationPerfTest.class.getSimpleName();
+
+ @Rule
+ public final PerfManualStatusReporter mPerfStatusReporter = new PerfManualStatusReporter();
+
+ @Rule
+ public final PerfTestActivityRule mActivityRule = new PerfTestActivityRule();
+
+ private final TraceMarkParser mTraceMarkParser = new TraceMarkParser(
+ "applyPostLayoutPolicy",
+ "applySurfaceChanges",
+ "AppTransitionReady",
+ "closeSurfaceTransactiom",
+ "openSurfaceTransaction",
+ "performLayout",
+ "performSurfacePlacement",
+ "prepareSurfaces",
+ "updateInputWindows",
+ "WSA#startAnimation",
+ "activityIdle",
+ "activityPaused",
+ "activityStopped",
+ "activityDestroyed",
+ "finishActivity",
+ "startActivityInner");
+
+ @Test
+ @ManualBenchmarkTest(
+ targetTestDurationNs = 20 * TIME_1_S_IN_NS,
+ statsReport = @StatsReport(
+ flags = StatsReport.FLAG_ITERATION | StatsReport.FLAG_MEAN
+ | StatsReport.FLAG_MAX | StatsReport.FLAG_COEFFICIENT_VAR))
+ public void testLaunchAndFinishActivity() throws Throwable {
+ final ManualBenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ long measuredTimeNs = 0;
+ boolean isTraceStarted = false;
+
+ while (state.keepRunning(measuredTimeNs)) {
+ if (!isTraceStarted && !state.isWarmingUp()) {
+ startAsyncAtrace();
+ isTraceStarted = true;
+ }
+ final long startTime = SystemClock.elapsedRealtimeNanos();
+ mActivityRule.launchActivity();
+ mActivityRule.finishActivity();
+ mActivityRule.waitForIdleSync(Stage.DESTROYED);
+ measuredTimeNs = SystemClock.elapsedRealtimeNanos() - startTime;
+ }
+
+ stopAsyncAtrace();
+
+ mTraceMarkParser.forAllSlices((key, slices) -> {
+ for (TraceMarkSlice slice : slices) {
+ state.addExtraResult(key, (long) (slice.getDurarionInSeconds() * NANOS_PER_S));
+ }
+ });
+
+ Log.i(TAG, String.valueOf(mTraceMarkParser));
+ }
+
+ private void startAsyncAtrace() throws IOException {
+ sUiAutomation.executeShellCommand("atrace -b 32768 --async_start wm");
+ // Avoid atrace isn't ready immediately.
+ SystemClock.sleep(TimeUnit.NANOSECONDS.toMillis(TIME_1_S_IN_NS));
+ }
+
+ private void stopAsyncAtrace() throws IOException {
+ final ParcelFileDescriptor pfd = sUiAutomation.executeShellCommand("atrace --async_stop");
+ final InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ mTraceMarkParser.visit(line);
+ }
+ }
+ }
+}
diff --git a/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java b/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java
index 0c3b9e537b1c..73b4a1914ad1 100644
--- a/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java
+++ b/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java
@@ -16,16 +16,13 @@
package android.wm;
-import static android.perftests.utils.ManualBenchmarkState.STATS_REPORT_COEFFICIENT_VAR;
-import static android.perftests.utils.ManualBenchmarkState.STATS_REPORT_ITERATION;
-import static android.perftests.utils.ManualBenchmarkState.STATS_REPORT_MEAN;
+import static android.perftests.utils.ManualBenchmarkState.StatsReport;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static org.hamcrest.core.AnyOf.anyOf;
import static org.hamcrest.core.Is.is;
-import android.app.Activity;
import android.app.ActivityManager.TaskSnapshot;
import android.app.ActivityTaskManager;
import android.app.IActivityTaskManager;
@@ -39,23 +36,16 @@ import android.os.SystemClock;
import android.perftests.utils.ManualBenchmarkState;
import android.perftests.utils.ManualBenchmarkState.ManualBenchmarkTest;
import android.perftests.utils.PerfManualStatusReporter;
-import android.perftests.utils.PerfTestActivity;
import android.util.Pair;
import android.view.IRecentsAnimationController;
import android.view.IRecentsAnimationRunner;
import android.view.RemoteAnimationTarget;
-import android.view.WindowManager;
import androidx.test.filters.LargeTest;
-import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.lifecycle.ActivityLifecycleCallback;
-import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
import androidx.test.runner.lifecycle.Stage;
-import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assume;
-import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
@@ -77,11 +67,10 @@ public class RecentsAnimationPerfTest extends WindowManagerPerfTestBase {
public final PerfManualStatusReporter mPerfStatusReporter = new PerfManualStatusReporter();
@Rule
- public final ActivityTestRule<PerfTestActivity> mActivityRule = new ActivityTestRule<>(
- PerfTestActivity.class, false /* initialTouchMode */, false /* launchActivity */);
+ public final PerfTestActivityRule mActivityRule =
+ new PerfTestActivityRule(true /* launchActivity */);
private long mMeasuredTimeNs;
- private LifecycleListener mLifecycleListener;
@Parameterized.Parameter(0)
public int intervalBetweenOperations;
@@ -127,24 +116,6 @@ public class RecentsAnimationPerfTest extends WindowManagerPerfTestBase {
sUiAutomation.dropShellPermissionIdentity();
}
- @Before
- @Override
- public void setUp() {
- super.setUp();
- final Activity testActivity = mActivityRule.launchActivity(null /* intent */);
- try {
- mActivityRule.runOnUiThread(() -> testActivity.getWindow()
- .addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON));
- } catch (Throwable ignored) { }
- mLifecycleListener = new LifecycleListener(testActivity);
- ActivityLifecycleMonitorRegistry.getInstance().addLifecycleCallback(mLifecycleListener);
- }
-
- @After
- public void tearDown() {
- ActivityLifecycleMonitorRegistry.getInstance().removeLifecycleCallback(mLifecycleListener);
- }
-
/** Simulate the timing of touch. */
private void makeInterval() {
SystemClock.sleep(intervalBetweenOperations);
@@ -167,8 +138,8 @@ public class RecentsAnimationPerfTest extends WindowManagerPerfTestBase {
@ManualBenchmarkTest(
warmupDurationNs = TIME_1_S_IN_NS,
targetTestDurationNs = TIME_5_S_IN_NS,
- statsReportFlags =
- STATS_REPORT_ITERATION | STATS_REPORT_MEAN | STATS_REPORT_COEFFICIENT_VAR)
+ statsReport = @StatsReport(flags = StatsReport.FLAG_ITERATION | StatsReport.FLAG_MEAN
+ | StatsReport.FLAG_COEFFICIENT_VAR))
public void testRecentsAnimation() throws Throwable {
final ManualBenchmarkState state = mPerfStatusReporter.getBenchmarkState();
final IActivityTaskManager atm = ActivityTaskManager.getService();
@@ -201,7 +172,7 @@ public class RecentsAnimationPerfTest extends WindowManagerPerfTestBase {
state.addExtraResult(finishCase.first, elapsedTimeNsOfFinish);
if (moveRecentsToTop) {
- mLifecycleListener.waitForIdleSync(Stage.STOPPED);
+ mActivityRule.waitForIdleSync(Stage.STOPPED);
startTime = SystemClock.elapsedRealtimeNanos();
atm.startActivityFromRecents(testActivityTaskId, null /* options */);
@@ -209,7 +180,7 @@ public class RecentsAnimationPerfTest extends WindowManagerPerfTestBase {
mMeasuredTimeNs += elapsedTimeNs;
state.addExtraResult("startFromRecents", elapsedTimeNs);
- mLifecycleListener.waitForIdleSync(Stage.RESUMED);
+ mActivityRule.waitForIdleSync(Stage.RESUMED);
}
makeInterval();
@@ -223,55 +194,18 @@ public class RecentsAnimationPerfTest extends WindowManagerPerfTestBase {
}
};
+ recentsSemaphore.tryAcquire();
while (state.keepRunning(mMeasuredTimeNs)) {
- Assume.assumeTrue(recentsSemaphore.tryAcquire(TIME_5_S_IN_NS, TimeUnit.NANOSECONDS));
+ mMeasuredTimeNs = 0;
final long startTime = SystemClock.elapsedRealtimeNanos();
atm.startRecentsActivity(sRecentsIntent, null /* unused */, anim);
final long elapsedTimeNsOfStart = SystemClock.elapsedRealtimeNanos() - startTime;
mMeasuredTimeNs += elapsedTimeNsOfStart;
state.addExtraResult("start", elapsedTimeNsOfStart);
- }
-
- // Ensure the last round of animation callback is done.
- recentsSemaphore.tryAcquire(TIME_5_S_IN_NS, TimeUnit.NANOSECONDS);
- recentsSemaphore.release();
- }
- private static class LifecycleListener implements ActivityLifecycleCallback {
- private final Activity mTargetActivity;
- private Stage mWaitingStage;
- private Stage mReceivedStage;
-
- LifecycleListener(Activity activity) {
- mTargetActivity = activity;
- }
-
- void waitForIdleSync(Stage state) {
- synchronized (this) {
- if (state != mReceivedStage) {
- mWaitingStage = state;
- try {
- wait(TimeUnit.NANOSECONDS.toMillis(TIME_5_S_IN_NS));
- } catch (InterruptedException impossible) { }
- }
- mWaitingStage = mReceivedStage = null;
- }
- getInstrumentation().waitForIdleSync();
- }
-
- @Override
- public void onActivityLifecycleChanged(Activity activity, Stage stage) {
- if (mTargetActivity != activity) {
- return;
- }
-
- synchronized (this) {
- mReceivedStage = stage;
- if (mWaitingStage == mReceivedStage) {
- notifyAll();
- }
- }
+ // Ensure the animation callback is done.
+ Assume.assumeTrue(recentsSemaphore.tryAcquire(TIME_5_S_IN_NS, TimeUnit.NANOSECONDS));
}
}
}
diff --git a/apct-tests/perftests/core/src/android/wm/WindowManagerPerfTestBase.java b/apct-tests/perftests/core/src/android/wm/WindowManagerPerfTestBase.java
index 4864da4b0195..4d278c3c2d9a 100644
--- a/apct-tests/perftests/core/src/android/wm/WindowManagerPerfTestBase.java
+++ b/apct-tests/perftests/core/src/android/wm/WindowManagerPerfTestBase.java
@@ -18,9 +18,21 @@ package android.wm;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import android.app.Activity;
import android.app.UiAutomation;
+import android.content.Intent;
+import android.perftests.utils.PerfTestActivity;
-import org.junit.Before;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.lifecycle.ActivityLifecycleCallback;
+import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
+import androidx.test.runner.lifecycle.Stage;
+
+import org.junit.BeforeClass;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.util.concurrent.TimeUnit;
public class WindowManagerPerfTestBase {
static final UiAutomation sUiAutomation = getInstrumentation().getUiAutomation();
@@ -28,10 +40,102 @@ public class WindowManagerPerfTestBase {
static final long TIME_1_S_IN_NS = 1 * NANOS_PER_S;
static final long TIME_5_S_IN_NS = 5 * NANOS_PER_S;
- @Before
- public void setUp() {
+ @BeforeClass
+ public static void setUpOnce() {
// In order to be closer to the real use case.
sUiAutomation.executeShellCommand("input keyevent KEYCODE_WAKEUP");
sUiAutomation.executeShellCommand("wm dismiss-keyguard");
+ getInstrumentation().getContext().startActivity(new Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_HOME).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ }
+
+ /**
+ * Provides an activity that keeps screen on and is able to wait for a stable lifecycle stage.
+ */
+ static class PerfTestActivityRule extends ActivityTestRule<PerfTestActivity> {
+ private final Intent mStartIntent =
+ new Intent().putExtra(PerfTestActivity.INTENT_EXTRA_KEEP_SCREEN_ON, true);
+ private final LifecycleListener mLifecycleListener = new LifecycleListener();
+
+ PerfTestActivityRule() {
+ this(false /* launchActivity */);
+ }
+
+ PerfTestActivityRule(boolean launchActivity) {
+ super(PerfTestActivity.class, false /* initialTouchMode */, launchActivity);
+ }
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ final Statement wrappedStatement = new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ ActivityLifecycleMonitorRegistry.getInstance()
+ .addLifecycleCallback(mLifecycleListener);
+ base.evaluate();
+ ActivityLifecycleMonitorRegistry.getInstance()
+ .removeLifecycleCallback(mLifecycleListener);
+ }
+ };
+ return super.apply(wrappedStatement, description);
+ }
+
+ @Override
+ protected Intent getActivityIntent() {
+ return mStartIntent;
+ }
+
+ @Override
+ public PerfTestActivity launchActivity(Intent intent) {
+ final PerfTestActivity activity = super.launchActivity(intent);
+ mLifecycleListener.setTargetActivity(activity);
+ return activity;
+ }
+
+ PerfTestActivity launchActivity() {
+ return launchActivity(mStartIntent);
+ }
+
+ void waitForIdleSync(Stage state) {
+ mLifecycleListener.waitForIdleSync(state);
+ }
+ }
+
+ static class LifecycleListener implements ActivityLifecycleCallback {
+ private Activity mTargetActivity;
+ private Stage mWaitingStage;
+ private Stage mReceivedStage;
+
+ void setTargetActivity(Activity activity) {
+ mTargetActivity = activity;
+ mReceivedStage = mWaitingStage = null;
+ }
+
+ void waitForIdleSync(Stage stage) {
+ synchronized (this) {
+ if (stage != mReceivedStage) {
+ mWaitingStage = stage;
+ try {
+ wait(TimeUnit.NANOSECONDS.toMillis(TIME_5_S_IN_NS));
+ } catch (InterruptedException impossible) { }
+ }
+ mWaitingStage = mReceivedStage = null;
+ }
+ getInstrumentation().waitForIdleSync();
+ }
+
+ @Override
+ public void onActivityLifecycleChanged(Activity activity, Stage stage) {
+ if (mTargetActivity != activity) {
+ return;
+ }
+
+ synchronized (this) {
+ mReceivedStage = stage;
+ if (mWaitingStage == mReceivedStage) {
+ notifyAll();
+ }
+ }
+ }
}
}
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 + "}";
+ }
+ }
+}