diff options
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 + "}"; + } + } +} |